从X.509证书中提取Golang主题DN。

huangapple go评论77阅读模式
英文:

golang subject dn from x509 cert

问题

在Go语言中,可以通过以下方式将x509证书的完整主题DN(或颁发者DN)作为字符串获取:

package main

import (
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"io/ioutil"
	"log"
)

func main() {
	// 从文件中读取证书内容
	certBytes, err := ioutil.ReadFile("certificate.pem")
	if err != nil {
		log.Fatal(err)
	}

	// 解析证书
	block, _ := pem.Decode(certBytes)
	if block == nil {
		log.Fatal("Failed to parse certificate")
	}
	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		log.Fatal(err)
	}

	// 获取完整主题DN
	subjectDN := cert.Subject.String()
	fmt.Println("Subject DN:", subjectDN)

	// 获取颁发者DN
	issuerDN := cert.Issuer.String()
	fmt.Println("Issuer DN:", issuerDN)
}

请将上述代码保存为.go文件,并将证书文件命名为certificate.pem,然后运行代码即可获取完整主题DN和颁发者DN的字符串表示。

英文:

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

答案1

得分: 8

解决方案(感谢同事):

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 {
   // 错误处理
}
fmt.Println(subj)

输出(示例):

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

这似乎是唯一的“简单”解决方案。

英文:

Solution (thanks to a colleague):

var oid = map[string]string{
&quot;2.5.4.3&quot;:                    &quot;CN&quot;,
&quot;2.5.4.4&quot;:                    &quot;SN&quot;,
&quot;2.5.4.5&quot;:                    &quot;serialNumber&quot;,
&quot;2.5.4.6&quot;:                    &quot;C&quot;,
&quot;2.5.4.7&quot;:                    &quot;L&quot;,
&quot;2.5.4.8&quot;:                    &quot;ST&quot;,
&quot;2.5.4.9&quot;:                    &quot;streetAddress&quot;,
&quot;2.5.4.10&quot;:                   &quot;O&quot;,
&quot;2.5.4.11&quot;:                   &quot;OU&quot;,
&quot;2.5.4.12&quot;:                   &quot;title&quot;,
&quot;2.5.4.17&quot;:                   &quot;postalCode&quot;,
&quot;2.5.4.42&quot;:                   &quot;GN&quot;,
&quot;2.5.4.43&quot;:                   &quot;initials&quot;,
&quot;2.5.4.44&quot;:                   &quot;generationQualifier&quot;,
&quot;2.5.4.46&quot;:                   &quot;dnQualifier&quot;,
&quot;2.5.4.65&quot;:                   &quot;pseudonym&quot;,
&quot;0.9.2342.19200300.100.1.25&quot;: &quot;DC&quot;,
&quot;1.2.840.113549.1.9.1&quot;:       &quot;emailAddress&quot;,
&quot;0.9.2342.19200300.100.1.1&quot;:  &quot;userid&quot;,
}
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 {
// &lt;oid name&gt;=&lt;value&gt;
subject = append(subject, fmt.Sprintf(&quot;%s=%s&quot;, name, v))
} else {
// &lt;oid&gt;=&lt;value&gt; if no &lt;oid name&gt; is found
subject = append(subject, fmt.Sprintf(&quot;%s=%s&quot;, i.Type.String(), v))
}
} else {
// &lt;oid&gt;=&lt;value in default format&gt; if value is not string
subject = append(subject, fmt.Sprintf(&quot;%s=%v&quot;, i.Type.String, v))
}
}
}
return sep + strings.Join(subject, sep), nil
}

calling the function:

subj, err := getDNFromCert(x509Cert.Subject, &quot;/&quot;)
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

答案2

得分: 7

为了从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.Println(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
}

// 手动解析证书主题以获取UID字段,该字段被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
}

// 从Subject的未解析对象列表中读取UID
for _, n := range cert.Subject.Names {
    if n.Type.Equal(oidUserID) {
        if v, ok := n.Value.(string); ok {
            UID = v
        }
    }
}

fmt.Println(UID)
英文:

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, &amp;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 = &quot;0.9.2342.19200300.100.1.1&quot;
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, &amp;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)

答案3

得分: 1

我今天面临了同样的任务。你可以通过以下方式从证书中获取主题:

// d 是包含你的证书的 []byte
cert, err := x509.ParseCertificate(d)
fmt.Printf("%+v\n", cert.Subject.ToRDNSequence())
// 输出: CN=client1,OU=MyClients,O=MongoDB-Cluster,L=Austin,ST=TX,C=US
英文:

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(&quot;%+v\n&quot;, cert.Subject.ToRDNSequence())
// Output: CN=client1,OU=MyClients,O=MongoDB-Cluster,L=Austin,ST=TX,C=US

答案4

得分: 0

在Go 1.9中,直接使用以下代码可以实现相同的功能:

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

对于更高版本的Go(>=1.10),可以使用pkix.Name.String()方法来实现相同的功能。

英文:

Using directly

fmt.Sprintf(&quot;%+v&quot;, cert.Subject.ToRDNSequence())

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

答案5

得分: 0

现在你可以使用以下代码:

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

英文:

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

答案6

得分: 0

这是我使用的函数。

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将相对可区分名称作为字符串返回。
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))
...
英文:

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 &amp;&amp; t[0] == 2 &amp;&amp; t[1] == 5 &amp;&amp; t[2] == 4 {
				switch t[3] {
				case 3:
					buf.WriteString(&quot;/CN=&quot;) // common name
					buf.WriteString(value)
				case 4:
					buf.WriteString(&quot;/SN=&quot;) // surname
					buf.WriteString(value)
				case 5:
					buf.WriteString(&quot;/SERIALNUMBER=&quot;)
					buf.WriteString(value)
				case 6:
					buf.WriteString(&quot;/C=&quot;) // country
					buf.WriteString(value)
				case 7:
					buf.WriteString(&quot;/L=&quot;) // locality
					buf.WriteString(value)
				case 8:
					buf.WriteString(&quot;/ST=&quot;) // state
					buf.WriteString(value)
				case 9:
					buf.WriteString(&quot;/STREET=&quot;)
					buf.WriteString(value)
				case 10:
					buf.WriteString(&quot;/O=&quot;) // organization
					buf.WriteString(value)
				case 11:
					buf.WriteString(&quot;/OU=&quot;) // organization unit
					buf.WriteString(value)
				case 12:
					buf.WriteString(&quot;/T=&quot;) // title
					buf.WriteString(value)
				case 17:
					buf.WriteString(&quot;/PC=&quot;) // postal code
					buf.WriteString(value)
				case 42:
					buf.WriteString(&quot;/GN=&quot;) // given name
					buf.WriteString(value)
				case 43:
					buf.WriteString(&quot;/initials=&quot;)
					buf.WriteString(value)
				case 44:
					buf.WriteString(&quot;/generationQualifier=&quot;)
					buf.WriteString(value)
				case 46:
					buf.WriteString(&quot;/dnQualifier=&quot;)
					buf.WriteString(value)
				case 65:
					buf.WriteString(&quot;/pseudonym=&quot;)
					buf.WriteString(value)
				}
			} else if t.Equal(dcNameOid) {
				buf.WriteString(&quot;/DC=&quot;) // domain component
				buf.WriteString(value)
			} else if t.Equal(emailOid) {
				buf.WriteString(&quot;/MAIL=&quot;)
				buf.WriteString(value)
			} else if t.Equal(userIDOid) {
				buf.WriteString(&quot;/UID=&quot;) // 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, &amp;rdns); err != nil {
    ...
}

fmt.Println(&quot;Subject:&quot;, RDNSToString(&amp;rdns))
...

答案7

得分: 0

这是我最终使用的一个函数,用于直接解析字符串,而不使用任何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

		// 跟踪定界字段的引号,以便我们知道不要在逗号上拆分
		quoteCount := 0

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

			char := issuer[i]

			// 如果是引号,我们需要计数和定界
			if char == 34 {
				quoteCount++
				if quoteCount == 2 {
					break
				} else {
					continue
				}
			}

			// 逗号,让我们在这里停下来,但前提是我们没有引号
			if char == 44 && quoteCount == 0 {
				break
			}

			// 添加这个单独的字符
			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
}
英文:

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{&quot;C=&quot;: &quot;&quot;, &quot;O=&quot;: &quot;&quot;, &quot;CN=&quot;: &quot;&quot;, &quot;ST=&quot;: &quot;&quot;, &quot;L=&quot;: &quot;&quot;, &quot;OU=&quot;: &quot;&quot;}
for tracker, _ := range trackerResultMap {
index := strings.Index(issuer, tracker)
if index &lt; 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 &lt; len(issuer); i++ {
char := issuer[i]
// if &quot;, 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&#39;t have quotes
if char == 44 &amp;&amp; quoteCount == 0 {
break
}
// add this individual char
res += string(rune(char))
}
trackerResultMap[strings.TrimSuffix(tracker, &quot;=&quot;)] = strings.TrimPrefix(res, &quot;=&quot;)
}
for k, v := range trackerResultMap {
if len(v) == 0 {
delete(trackerResultMap, k)
}
}
return trackerResultMap
}

huangapple
  • 本文由 发表于 2016年8月24日 22:17:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/39125873.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定