Golang PKI入门 – 6 用中文翻译
这篇文章的对象
-
- Golang で DN (Subject や Issuer) を扱いたい人
- Golang で DN (Subject や Issuer) を扱うライブラリを探している人
DN指的是什么?
在X.500和X.501定义的目录服务模型中,DN(可分辨名)是用于唯一标识对象的识别名称。由于X.509 PKI采用了目录服务模型,作为其中一个对象的主体(Subject)和发行者(Issuer)也通过DN表示。在目录中,对象以基于对象类的条目形式表示。目录由条目组成的树状结构构成,这棵树被称为DIT(目录信息树)。条目基于类具有多个属性,属性具有属性类型和属性值。RDN(相对可分辨名)用于在DIT中的同一层级内(从上层条目的视角)唯一标识该条目,而DN则用于在DIT中唯一标识条目。RDN由一个或多个属性的集合(无顺序)表示。作为一个条目的RDN,其作为“属性集合”的RDN不能与同一层级内其他条目的RDN重叠。RDN在同一层级内可作为条目的唯一标识符的“属性集合”使用。DN由表示该对象条目的RDN以及所有上层条目的RDN序列(有序)表示。由于DN是在DIT中每个层级中唯一的RDN序列,它成为了标识DIT内唯一条目的标识符。有关DN和RDN的详细信息,请参考X.501。
DN通过以下的方式来定义。
CN=hoge@example.com,O=example,C=JP
DN是由RDN序列(有顺序)组成的。RDN由多个无序属性(AttributeTypeAndValue)组成。属性(AttributeTypeAndValue)由属性类型(AttributeType)和属性值(AttributeValue)组成。
以前述的例子为例,DN: CN=hoge@example.com,O=example,C=JP代表了一个由3个层级条目组成的对象。CN=hoge@example.com表示末端条目的RDN,O=example表示中间条目的RDN,C=JP表示根条目的RDN。这些RDN的顺序形成了该对象的序列(以树的根节点的RDN为首),这就是该对象的DN。
在此例中,以RDN C=JP为例,C=JP是一个具有属性类型和属性值对的属性值(AttributeTypeAndValue),其属性类型是C(CountryName),属性值是JP。
由于RDN可以包含多个属性,因此也可以定义具有多个属性的RDN,例如:
OU=Sales + OU=Dev
这表示一个RDN中包含两个属性(OU=Sales和OU=Dev)。请注意,以下两个作为DN的情况是不同的。
-
- CN=hoge@example.com,OU=Sales,OU=Dev,O=example,C=JP
-
- DN具有5个RDN序列,每个RDN都具有一个属性。
CN=hoge@example.com,OU=Sales + OU=Dev,O=example,C=JP
DN具有4个RDN序列,其中3个RDN分别具有一个属性,而1个RDN具有两个属性。(多值RDN)
在RFC 5280中,将DN定义为以下ASN.1结构体。
Name ::= CHOICE { -- only one possibility for now --
rdnSequence RDNSequence }
RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
RelativeDistinguishedName ::=
SET SIZE (1..MAX) OF AttributeTypeAndValue
AttributeTypeAndValue ::= SEQUENCE {
type AttributeType,
value AttributeValue }
AttributeType ::= OBJECT IDENTIFIER
AttributeValue ::= ANY -- DEFINED BY AttributeType
这是一个Distinguished name类型,它拥有Name类型的信息要素rdnSequence。
RDNSequence类型是一个RelativeDistinguishedName类型的SEQUENCE类型。
RelativeDistinguishedName类型是一个AttributeTypeAndValue类型的SET类型。
AttributeTypeAndValue类型是一个SEQUENCE类型,它包含两个信息要素,即AttributeType类型的type和AttributeValue类型的value。
AttributeType类型是OBJECT IDENTIFIER类型。
AttributeValue类型是与OBJECT IDENTIFIER类型对应的任意类型。
3. 在使用Golang处理DN时的挑战。
只有在创建CSR、证书或CRL时,才需要处理Golang中的Issuer或Subject等DN。在这种情况下,Golang可以用来处理DN。
pkix.Name 構造体
pkix.RDNSequence 構造体
有两种使用方法。无论使用哪种结构体,都会存在问题。
使用pkix.Name结构体的挑战
如果使用pkix.Name结构体,将会遇到以下三个问题。
根据包含在RDN中的AttributeTypeAndValue的AttributeType决定生成的DN的RDN顺序。
使用pkix.Name结构,根据RDN中包含的AttributeTypeAndValue的AttributeType确定RDN的顺序。因此,无法指定RDN的顺序。
RDN 中包含的 AttributeTypeAndValue的 AttributeType 不能有相同的 RDN。
AttributeType不可以拥有相同的RDN。(可以拥有Multi Value RDN)
具体来说,不能创建以下类似的(具有两个OU的)DN:
C=JP,O=example,OU=Sales,OU=Dev,CN=hoge@example.com
在 AttributeValue 中无法指定编码方式。
由于编码的属性值(ASN.1 字符类型)是根据分配的字符类型自动确定的,因此无法指定。
pkix.RDNSequence 是一个数据结构。
当使用pkix.RDNSequence结构体时,可以自由设置RDN的结构和顺序。然而,在AttributeValue中指定编码存在挑战。
用 AttributeValue 设置编码规范很麻烦。
在 pkix.RDNSequence 结构中,无法直接简单地为 AttributeValue 进行编码设置。如果仅仅将字符串赋值给 AttributeValue,就像在 pkix.Name 结构中一样,根据字符串中使用的字符类型,将自动确定 AttributeValue 的编码,并无法进行指定。
要明确指定编码,需要将所需的 ASN.1 字符类型的 asn1.RawValue 实例赋值给 AttributeValue 结构体。(由于可能不太清晰,建议查看示例代码)
var b []byte
st := "あ"
b, err := asn1.MarshalWithParams(st, "utf8") //"あ"をUTF8String でエンコード。範囲外であればエラー
if err != nil {
log.Fatalf("ERROR:%v\n", err)
}
atv1 := pkix.AttributeTypeAndValue{
Type: asn1.ObjectIdentifier{2, 5, 4, 3}, //CommonName
Value: asn1.RawValue{
Tag: asn1.TagUTF8String, //UTF8String の ObjectIdentifier
FullBytes: b, //UTF8String でエンコードされたバイナリ
},
}
//CN=あ の DN
dn1 := pkix.RDNSequence{pkix.RelativeDistinguishedNameSET{atv1}}
4. 解决方案 ‘àn)
经过实践发现,如果在Golang中处理DN(Distinguished Name),传统的方法存在一些问题。因此,我们开发了一个名为dnutil的库来解决这些问题。使用这个库,您可以自由地创建复杂结构的DN,并且可以轻松地指定每个RDN(Relative Distinguished Name)的AttributeValue的编码方式。
5. 使用 dnutil 的方法
在 dnutil 中创建 DN 是很简单的。如果想要创建以下类型的 DN,在Chinese的修饰下,这样做就可以:
C=JP,O=example,OU=Ext,OU=Dev+OU=Sales,CN=ex+E=ex@example.com
AttributeType: ASN.1文字型
C: PrintableString
O: UTF8String
OU=Ext: UTF8String
OU=Dev: UTF8String
OU=Sales: UTF8String
CN:UTF8String
E(ElectronicMailAddress):IA5String
只需要按以下方式实例化 DN 结构体。
var d = dnutil.DN{
dnutil.RDN{dnutil.AttributeTypeAndValue{Type: dnutil.CountryName, Value: dnutil.AttributeValue{Encoding: dnutil.PrintableString, Value: "JP"}}},
dnutil.RDN{dnutil.AttributeTypeAndValue{Type: dnutil.OrganizationName, Value: dnutil.AttributeValue{Encoding: dnutil.UTF8String, Value: "example"}}},
dnutil.RDN{dnutil.AttributeTypeAndValue{Type: dnutil.OrganizationalUnit, Value: dnutil.AttributeValue{Encoding: dnutil.UTF8String, Value: "Ext"}}},
dnutil.RDN{
dnutil.AttributeTypeAndValue{Type: dnutil.OrganizationalUnit, Value: dnutil.AttributeValue{Encoding: dnutil.UTF8String, Value: "Dev"}},
dnutil.AttributeTypeAndValue{Type: dnutil.OrganizationalUnit, Value: dnutil.AttributeValue{Encoding: dnutil.UTF8String, Value: "Sales"}},},
dnutil.RDN{
dnutil.AttributeTypeAndValue{Type: dnutil.CommonName, Value: dnutil.AttributeValue{Encoding: dnutil.UTF8String, Value: "ex"}},
dnutil.AttributeTypeAndValue{Type: dnutil.ElectronicMailAddress, Value: dnutil.AttributeValue{Encoding: dnutil.IA5String, Value: "ex@example.com"}}},
}
要从创建的DN实例中创建一个ASN.1 DER格式的DN,可以使用MarshalDN()函数。
b, err := dnutil.MarshalDN(d)
我将创建的DN转换为PEM格式并输出,并使用openssl进行了内容确认。可以看到它已经变成了目标的DN形式。
$ openssl asn1parse -in dn01.pem
0:d=0 hl=2 l= 115 cons: SEQUENCE
2:d=1 hl=2 l= 11 cons: SET
4:d=2 hl=2 l= 9 cons: SEQUENCE
6:d=3 hl=2 l= 3 prim: OBJECT :countryName
11:d=3 hl=2 l= 2 prim: PRINTABLESTRING :JP
15:d=1 hl=2 l= 16 cons: SET
17:d=2 hl=2 l= 14 cons: SEQUENCE
19:d=3 hl=2 l= 3 prim: OBJECT :organizationName
24:d=3 hl=2 l= 7 prim: UTF8STRING :example
33:d=1 hl=2 l= 12 cons: SET
35:d=2 hl=2 l= 10 cons: SEQUENCE
37:d=3 hl=2 l= 3 prim: OBJECT :organizationalUnitName
42:d=3 hl=2 l= 3 prim: UTF8STRING :Ext
47:d=1 hl=2 l= 26 cons: SET
49:d=2 hl=2 l= 10 cons: SEQUENCE
51:d=3 hl=2 l= 3 prim: OBJECT :organizationalUnitName
56:d=3 hl=2 l= 3 prim: UTF8STRING :Dev
61:d=2 hl=2 l= 12 cons: SEQUENCE
63:d=3 hl=2 l= 3 prim: OBJECT :organizationalUnitName
68:d=3 hl=2 l= 5 prim: UTF8STRING :Sales
75:d=1 hl=2 l= 40 cons: SET
77:d=2 hl=2 l= 9 cons: SEQUENCE
79:d=3 hl=2 l= 3 prim: OBJECT :commonName
84:d=3 hl=2 l= 2 prim: UTF8STRING :ex
88:d=2 hl=2 l= 27 cons: SEQUENCE
90:d=3 hl=2 l= 9 prim: OBJECT :emailAddress
101:d=3 hl=2 l= 14 prim: IA5STRING :ex@example.com
如果想要从ASN.1的DER格式的DN中创建DN实例,可以使用ParseDERDN()函数。
//CN=abc (UTF8String)
b := []byte{0x30, 0x0e, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x03, 0x61, 0x62, 0x63}
dn, err := dnutil.ParseDERDn(b)
我用dnutil创建了一个创建ASN1的DER格式的DN和CSR的代码示例。有兴趣的人可以尝试一下。
5. 限制事项
以下是 AttributeValue 可用的 ASN.1 编码有三种。在 DirectoryString 中,不支持 TeletexString、UniversalString 和 BMPString。
PrintableString
UTF8String
IA5String
以下列出了可在AttributeType中使用的选项。
2.5.4.6 CountryName
2.5.4.10 OrganizationName
2.5.4.11 OrganizationalUnit
2.5.4.46 DnQualifier
2.5.4.8 StateOrProvinceName
2.5.4.3 CommonName
2.5.4.5 SerialNumber
2.5.4.7 LocalityName
2.5.4.12 Title
2.5.4.4 Surname
2.5.4.42 GivenName
2.5.4.43 Initials
2.5.4.65 Pseudonym
2.5.4.44 GenerationQualifier
1.2.840.113549.1.9.1 ElectronicMailAddress
0.9.2342.19200300.100.1.25 DomainComponent
不同的 AttributeType 可以使用相应的 AttributeValue 在 ASN.1 中进行编码,具体如下:
CountryName : PrintableString
OrganizationName : PrintableString or UTF8String
OrganizationalUnit : PrintableString or UTF8String
DnQualifier : PrintableString
StateOrProvinceName : PrintableString or UTF8String
CommonName : PrintableString or UTF8String
SerialNumber : PrintableString
LocalityName : PrintableString or UTF8String
Title : PrintableString or UTF8String
Surname : PrintableString or UTF8String
GivenName : PrintableString or UTF8String
Initials : PrintableString or UTF8String
Pseudonym : PrintableString or UTF8String
GenerationQualifier : PrintableString or UTF8String
ElectronicMailAddress : IA5String
DomainComponent : IA5String
5. 许可证
dnutil 的许可证是 BSD 3-Clause。