X509 Subject Formatting

Problem this snippet solves:

TMOS v12.1.4.1 and v13 have a change where the output of X509::subject is formatted to match OpenSSL output. See https://cdn.f5.com/product/bugtracker/ID607410.html


<= TMOS v12 is CN=USERNAME,OU=CONTRACTOR,OU=PKI,OU=DEPT,O=COMPANY,C=US

>= TMOS v13 is C=US,O=COMPANY,OU=DEPT,OU=PKI,OU=CONTRACTOR,CN=USERNAME

How to use this snippet:

This procedure will change the output format to either v12 or v13 ( or you can easily modify it to create your own output ). Default output is v12.


Use v13 output:

set serialNumber [ call formatSubject [ X509::subject [SSL::cert 0] ] 13 ]


Use v12 output:

set serialNumber [ call formatSubject [ X509::subject [SSL::cert 0] ] ]


Code :

proc formatSubject { subject { f 12 }} {
    # subject is the subject string eg CN=USERNAME,OU=CONTRACTOR,OU=PKI,OU=DEPT,O=COMPANY,C=US
    # f is format version and is either 12 or 13. Default 12
    # This procedure formats the subject field according to TMOS v12 or v13 format
    # v12 is CN=USERNAME,OU=CONTRACTOR,OU=PKI,OU=DEPT,O=COMPANY,C=US
    # v13 is C=US,O=COMPANY,OU=DEPT,OU=PKI,OU=CONTRACTOR,CN=USERNAME
    # See https://cdn.f5.com/product/bugtracker/ID607410.html
    # dn is an array with format [ CN O { OU } CN ]

    array set dn {
        OU {}
    } 
    set subject [regsub -all {(".*?),(.*?")} $subject "\\1--COMMA--\\2"]
    
    foreach i [ split $subject , ] {
        set j [ split [ string trim $i] = ]
        if { [lindex $j 0] == "OU" } {
            lappend dn(OU) [lindex $j 1]
        } else {
            array set dn [ list [lindex $j 0] [lindex $j 1] ]
        }
    }
    # Loop through OUs and create a string
    set ouString ""
    foreach ou $dn(OU) {
        append ouString "OU=$ou, "
    }
																		
    if { $f == 12 } {
        set order { CN OU O C }
    } else {
        set order { C O OU CN }
    }
							  
    set returnString ""
    foreach a $order {
        if { [info exists dn($a)] } {
            if { $a == "OU" } { 
                append returnString $ouString 
            } else {
                append returnString "$a=$dn($a), "
            }
        }
    }
    set returnString [string map {"--COMMA--" ","} $returnString]
    return [ string trimright $returnString ", " ]
}

Tested this on version:

13.0
Published Oct 18, 2019
Version 1.0

Was this article helpful?

15 Comments

  • That’s correct, regsub - - all should modify both of those. I’ll test it out later
  •  : The regsub try to match the largest pattern...

    The first match string is:

    London, City of", L=Bar, O=Foo, OU=TEST, CN="F5lab

    The second is

    local"

    So you have to force stoping at the first "... replace the .* with [^"]*

     

    The problem with this expression is it will also match this string:

     

    f", L=Bar, O=Foo, OU=TEST, CN="

     

    This expression seems to work with your example:

     

    % set subject [regsub -all {([a-zA-Z]+ ?= ?"[^"]+),([^"]+")} $subject "\\1--COMMA--\\2"]

     

    C=GB, ST="London--COMMA-- City of", L=Bar, O=Foo, OU=TEST, CN="F5lab--COMMA-- local"

     

  • OK I understand now, the regsub -all is greedy so it matches as much as it can.

     

    Thanks again both of you for your help with that. All is good now

  • Updated to work better with commas and made less greedy with the use of ? in the regsub regex

  • Hi  , I had the same requirement and it seems the change is the following:

    <= TMOS v12 is CN=LASTNAME\, FIRSTNAME,OU=CONTRACTOR,OU=PKI,OU=DEPT,O=COMPANY,C=US

    >= TMOS v13 is C=US, O=COMPANY, OU=DEPT, OU=PKI, OU=CONTRACTOR, CN="LASTNAME, FIRSTNAME"

    So differences are:

    • Reverse Ordered elements (full reverse may be better than defining the order in the irule)
    • space after comma separator
    • double quotes around fields with special characters (like comma)
    • unescaped special character between double quotes

    I wrote following procs to manage conversion from v12 to v13 and from v13 to v12 :

    proc subject_v13_to_v12 {subject_openssl} {
        # Create the empty subject
        set new_subject ""
        # For each subject element, do the following actions:
        # - remove double quotes arround the value
        # - escape the comma character
        # - remove space around comma separator
        # Insert the value at the begining of the subject (to reverse order)
        foreach {type value} [ split  [regsub -all {(".*?),(.*?")} $subject_openssl "\\1--COMMA--\\2"] ",=" ] {
            set new_subject "{[string trim $type]=[string trim [string map {"--COMMA--" "\\," "+" "\\+" "\\" "\\\\" "<" "\\<" ">" "\\>" ";" "\\;" } [string trim $value {"}]]]} $new_subject"
        }
        # Return the joined values with comma
        return [join  $new_subject ","]
    }
    proc subject_v12_to_v13 {subject_rfc2253} {
        # Create the empty subject
        set new_subject ""
        # For each subject element, do the following actions:
        # - add double quotes arround the value
        # - unescape the comma character
        # - Add space around comma separator
        # Insert the value at the begining of the subject (to reverse order)
        foreach {type value} [ split  [string map {"\\," "--COMMA--"} $subject_rfc2253] ",=" ] {
            set new_subject "{[string trim $type]=[expr {[string match "*--COMMA--*" $value] ? "\"[string trim [string map {"--COMMA--" ","} $value]]\""  : [string trim $value] }]} $new_subject"
        }
        # Return the joined values with comma and space
        return [join  $new_subject ", "]
    }

    I got the following results:

     subject_v12_to_v13 {CN=F5lab\, local,OU=TEST,O=Foo,L=Bar,ST=London\, City of,C=GB}
    C=GB, ST="London, City of", L=Bar, O=Foo, OU=TEST, CN="F5lab, local"
     
    % subject_v13_to_v12 {C=GB, ST="London, City of", L=Bar, O=Foo, OU=TEST, CN="F5lab, local"}
    CN=F5lab\, local,OU=TEST,O=Foo,L=Bar,ST=London\, City of,C=GB