Голанг тема дн от x509 cert

Есть ли простой способ получить полный DN субъекта (или DN эмитента) из сертификата x509 в виде строки?

Я не смог найти какие-либо методы, такие как ".String()" в pkix.Name

5 ответов

Решение (спасибо коллеге):

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
}

вызов функции:

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

вывод (пример):

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

кажется, это единственное "простое" решение

Чтобы получить полный DN субъекта (или DN эмитента) из сертификата x509, вы можете использовать следующий код:

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

Точно так же, если вам нужно получить только определенное значение объекта от субъекта (или эмитента), вы можете использовать следующий подход. Пример ниже извлекает UID из субъекта (который не определен в 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)

ОБНОВЛЕНИЕ: Упрощенный способ получения UID благодаря @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)

Я столкнулся с той же задачей сегодня. Вы можете получить предмет из сертификата следующим образом:

// 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

Вот функция, которую я использовал для непосредственного анализа строки, без использования каких-либо внутренних библиотек go.

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
}

Вот функция, которую я использую.

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()
}

Это код для получения RDNSequence. Пример получает имя субъекта.

...
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))
...

Используя напрямую fmt.Sprintf("%+v", cert.Subject.ToRDNSequence())

выполняет работу в го 1.9. Для улучшенных версий go (>=1.10) он работает с ".String()" в pkix.Name

На данный момент вы можете просто использовать:

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

https://golang.org/pkg/crypto/x509/
https://golang.org/pkg/crypto/x509/pkix/

Другие вопросы по тегам