tgo
tgo

Reputation: 181

golang subject dn from x509 cert

Is there any easy way to get the complete subject DN (or issuer DN) from an x509 certificate in go as a string?

I was not able to find any methods like ".String()" in pkix.Name

Upvotes: 10

Views: 7712

Answers (7)

Erkin Djindjiev
Erkin Djindjiev

Reputation: 577

Here's a function I ended up using to parse the string directly, without utilizing any of the go internal libs.

func parseIssuerDn(issuer string) map[string]string {

    trackerResultMap := map[string]string{"C=": "", "O=": "", "CN=": "", "ST=": "", "L=": "", "OU=": ""}

    for tracker, _ := range trackerResultMap {
        index := strings.Index(issuer, tracker)

        if index < 0 {
            continue
        }

        var res string

        // track quotes for delimited fields so we know not to split on the comma
        quoteCount := 0

        for i := index + len(tracker); i < len(issuer); i++ {

            char := issuer[i]

            // if ", we need to count and delimit
            if char == 34 {
                quoteCount++
                if quoteCount == 2 {
                    break
                } else {
                    continue
                }
            }

            // comma, lets stop here but only if we don't have quotes
            if char == 44 && quoteCount == 0 {
                break
            }

            // add this individual char
            res += string(rune(char))

        }

        trackerResultMap[strings.TrimSuffix(tracker, "=")] = strings.TrimPrefix(res, "=")
    }

    for k, v := range trackerResultMap {
        if len(v) == 0 {
            delete(trackerResultMap, k)
        }
    }

    return trackerResultMap
}

Upvotes: 0

chmike
chmike

Reputation: 22174

Here is the function that I use.

var (
    cnNameOid = asn1.ObjectIdentifier{2, 5, 4, 3}
    emailOid  = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}
    userIDOid = asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 1}
    dcNameOid = asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 25}
)

// RDNSToString returns the Relative Distinguish Name as a string.
func RDNSToString(rdns *pkix.RDNSequence) string {
    var buf strings.Builder
    for _, rdn := range *rdns {
        if len(rdn) == 0 {
            continue
        }
        for _, atv := range rdn {
            value, ok := atv.Value.(string)
            if !ok {
                continue
            }
            t := atv.Type
            if len(t) == 4 && t[0] == 2 && t[1] == 5 && t[2] == 4 {
                switch t[3] {
                case 3:
                    buf.WriteString("/CN=") // common name
                    buf.WriteString(value)
                case 4:
                    buf.WriteString("/SN=") // surname
                    buf.WriteString(value)
                case 5:
                    buf.WriteString("/SERIALNUMBER=")
                    buf.WriteString(value)
                case 6:
                    buf.WriteString("/C=") // country
                    buf.WriteString(value)
                case 7:
                    buf.WriteString("/L=") // locality
                    buf.WriteString(value)
                case 8:
                    buf.WriteString("/ST=") // state
                    buf.WriteString(value)
                case 9:
                    buf.WriteString("/STREET=")
                    buf.WriteString(value)
                case 10:
                    buf.WriteString("/O=") // organization
                    buf.WriteString(value)
                case 11:
                    buf.WriteString("/OU=") // organization unit
                    buf.WriteString(value)
                case 12:
                    buf.WriteString("/T=") // title
                    buf.WriteString(value)
                case 17:
                    buf.WriteString("/PC=") // postal code
                    buf.WriteString(value)
                case 42:
                    buf.WriteString("/GN=") // given name
                    buf.WriteString(value)
                case 43:
                    buf.WriteString("/initials=")
                    buf.WriteString(value)
                case 44:
                    buf.WriteString("/generationQualifier=")
                    buf.WriteString(value)
                case 46:
                    buf.WriteString("/dnQualifier=")
                    buf.WriteString(value)
                case 65:
                    buf.WriteString("/pseudonym=")
                    buf.WriteString(value)
                }
            } else if t.Equal(dcNameOid) {
                buf.WriteString("/DC=") // domain component
                buf.WriteString(value)
            } else if t.Equal(emailOid) {
                buf.WriteString("/MAIL=")
                buf.WriteString(value)
            } else if t.Equal(userIDOid) {
                buf.WriteString("/UID=") // user ID
                buf.WriteString(value)
            }
        }
    }
    return buf.String()
}

This is the code to get the RDNSequence. The example gets the Subject name.

...
cert, err := x509.ParseCertificate(certData)
if err != nil {
    ...
}

var rdns pkix.RDNSequence
if _, err := asn1.Unmarshal(cert.RawSubject, &rdns); err != nil {
    ...
}

fmt.Println("Subject:", RDNSToString(&rdns))
...

Upvotes: 0

Daniil Rutskiy
Daniil Rutskiy

Reputation: 726

For now you can just use:

cert, _ := x509.ParseCertificate(certDERBlock.Bytes)
fmt.Println(cert.Subject.CommonName)

https://golang.org/pkg/crypto/x509/#Certificate
https://golang.org/pkg/crypto/x509/pkix/#Name

Upvotes: 0

Guillaume Cisco
Guillaume Cisco

Reputation: 2945

Using directly fmt.Sprintf("%+v", cert.Subject.ToRDNSequence())

does the work in go 1.9. For superior versions of go (>=1.10), it works with ".String()" in pkix.Name

Upvotes: 0

Trane9991
Trane9991

Reputation: 385

In order to get complete subject DN (or issuer DN) from an x509 certificate, you may use next code:

cert, err := x509.ParseCertificate(certData)
if err != nil {
    return err
}

var subject pkix.RDNSequence
if _, err := asn1.Unmarshal(cert.RawSubject, &subject); err != nil {
    return err
}

fmt.Plrintln(subject.String()

Similarly, if you need to get only some specific object value from the subject (or issuer) you may use next approach. Example below retrieves UID from subject (which is not defined in the stdlib https://github.com/golang/go/issues/25667)

// http://www.alvestrand.no/objectid/0.9.2342.19200300.100.1.1.html
const oidUserID = "0.9.2342.19200300.100.1.1"
var UID string

cert, err := x509.ParseCertificate(certData)
if err != nil {
    return err
}

// manually parsing the Certificate subject to get the
// UID field, which is being ignored by the stdlib
// https://github.com/golang/go/issues/25667
var subject pkix.RDNSequence
if _, err := asn1.Unmarshal(cert.RawSubject, &subject); err != nil {
    return err
}

for _, s := range subject {
    for _, i := range s {
        if i.Type.String() == oidUserID {
            if v, ok := i.Value.(string); ok {
                UID = v
            }
        }
    }
}

fmt.Println(UID)

UPDATE: Simplified way to get the UID, thanks to @FiloSottile:

// http://www.alvestrand.no/objectid/0.9.2342.19200300.100.1.1.html
var oidUserID = []int{0, 9, 2342, 19200300, 100, 1, 1}
var UID string

cert, err := x509.ParseCertificate(certData)
if err != nil {
    return err
}

// reading the UID from list of unprased 
// objects from Subject
for _, n := range cert.Subject.Names {
    if n.Type.Equal(oidUserID) {
        if v, ok := n.Value.(string); ok {
            UID = v
        }
    }
}

fmt.Println(UID)

Upvotes: 8

Nikolay Bystritskiy
Nikolay Bystritskiy

Reputation: 550

I faced the same task today. You could get subject from certificate this way:

// d is []byte with your certificate
cert, err := x509.ParseCertificate(d)
fmt.Printf("%+v\n", cert.Subject.ToRDNSequence())

// Output: CN=client1,OU=MyClients,O=MongoDB-Cluster,L=Austin,ST=TX,C=US

Upvotes: 1

tgo
tgo

Reputation: 181

Solution (thanks to a colleague):

var oid = map[string]string{
    "2.5.4.3":                    "CN",
    "2.5.4.4":                    "SN",
    "2.5.4.5":                    "serialNumber",
    "2.5.4.6":                    "C",
    "2.5.4.7":                    "L",
    "2.5.4.8":                    "ST",
    "2.5.4.9":                    "streetAddress",
    "2.5.4.10":                   "O",
    "2.5.4.11":                   "OU",
    "2.5.4.12":                   "title",
    "2.5.4.17":                   "postalCode",
    "2.5.4.42":                   "GN",
    "2.5.4.43":                   "initials",
    "2.5.4.44":                   "generationQualifier",
    "2.5.4.46":                   "dnQualifier",
    "2.5.4.65":                   "pseudonym",
    "0.9.2342.19200300.100.1.25": "DC",
    "1.2.840.113549.1.9.1":       "emailAddress",
    "0.9.2342.19200300.100.1.1":  "userid",
}

func getDNFromCert(namespace pkix.Name, sep string) (string, error) {
    subject := []string{}
    for _, s := range namespace.ToRDNSequence() {
        for _, i := range s {
            if v, ok := i.Value.(string); ok {
                if name, ok := oid[i.Type.String()]; ok {
                    // <oid name>=<value>
                    subject = append(subject, fmt.Sprintf("%s=%s", name, v))
                } else {
                    // <oid>=<value> if no <oid name> is found
                    subject = append(subject, fmt.Sprintf("%s=%s", i.Type.String(), v))
                }
            } else {
                // <oid>=<value in default format> if value is not string
                subject = append(subject, fmt.Sprintf("%s=%v", i.Type.String, v))
            }
        }
    }
    return sep + strings.Join(subject, sep), nil
}

calling the function:

subj, err := getDNFromCert(x509Cert.Subject, "/")
if err != nil {
   // do error handling
}
fmt.Println(subj)

output (example):

/C=US/O=some organization/OU=unit/CN=common name

this seems to be the only "easy" solution

Upvotes: 8

Related Questions