【发布时间】:2016-12-31 17:46:00
【问题描述】:
是否有任何简单的方法可以从 x509 证书中以字符串形式获取完整的主题 DN(或颁发者 DN)?
我在 pkix.Name 中找不到像“.String()”这样的方法
【问题讨论】:
是否有任何简单的方法可以从 x509 证书中以字符串形式获取完整的主题 DN(或颁发者 DN)?
我在 pkix.Name 中找不到像“.String()”这样的方法
【问题讨论】:
这是我最终用来直接解析字符串的函数,而不使用任何 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()
}
这是获取 RDNS 序列的代码。该示例获取主题名称。
...
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))
...
【讨论】:
现在你可以使用:
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
【讨论】:
直接使用
fmt.Sprintf("%+v", cert.Subject.ToRDNSequence())
在 go 1.9 中完成这项工作。 对于 go (>=1.10) 的高级版本,它适用于 pkix.Name 中的“.String()”
【讨论】:
为了从 x509 证书中获取完整的主题 DN(或颁发者 DN),您可以使用以下代码:
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(未在标准库 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
【讨论】:
解决方案(感谢一位同事):
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
这似乎是唯一“简单”的解决方案
【讨论】:
namespace.ToRDNSequence()不会重新创建证书的原始RDNS序列,它将添加仅在stdlib中定义的对象,因此将丢失那些未定义的对象.请参阅下面我的答案中的工作代码示例。