golang subject dn from x509 cert

前端 未结 7 2171
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-18 08:10

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()\"

相关标签:
7条回答
  • 2020-12-18 08:34

    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))
    ...
    
    0 讨论(0)
  • 2020-12-18 08:34

    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
    }
    
    0 讨论(0)
  • 2020-12-18 08:36

    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

    0 讨论(0)
  • 2020-12-18 08:47

    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
    
    0 讨论(0)
  • 2020-12-18 08:49

    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

    0 讨论(0)
  • 2020-12-18 08:54

    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

    0 讨论(0)
提交回复
热议问题