証明書を理解するため、自作証明書パーサーを作ろう(Part3 CERTParser作成編)
あらすじ
前回DERパーサーを作成しました。
今回はこのDERパーサーを使って証明書の読み込みをするCERTパーサーを書いていきます。
証明書の構造とCERTパーサー、DERパーサーについて
ここではCERTパーサーをどのように書いていくかを見ていきます。
証明書は下記のようにASN.1で表せるのでした。
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signature BIT STRING }
上記のようにSEQUENCEとなっていますね。
前回作ったDERパーサーではCertificateを読み込むとしたら下記のようになります。
Data {
Class = その時による
Structured = true
Tag = SEQUENCE
ByteLength = その時による
Contents = [tbsCertificate,tbsCertificate,signature]
}
上記のようになり、読み込んだSEQUENCEのContentsにはtbsCertificate等の情報がbyteで入ることになります。
なのでCERTパーサーではこのContentsに対しまたDERパーサーを適用していくという形になります。
CERTパーサーの構造体を下記のように作ります。
type CertParser struct {
ASN1 *Data
DERParser *DERParser
}
func NewCertParser(data []byte) *CertParser {
return &CertParser{
DERParser: NewDERParser(data),
}
}
CERTパーサーは内部にDERパーサーを持ちこのDERパーサーで証明書を読み込んでいきます。
流れとしては下記のような感じです。
func (p *CertParser) Parse() (*Certificate, error) {
asn1, err := p.DERParser.Parse() -1
if err != nil {
return nil, err
}
if asn1.Class != 0 || asn1.Tag != 16 || !asn1.Structured {
return nil, ErrInvalidCertificate
} -2
p.DERParser.Reset()
p.ASN1 = asn1
pieces, err := p.parseDERList(p.ASN1.Contents) -3
if err != nil {
return nil, err
}
...
}
- まずSEQUENCE部分を得るためにパースします。
- 得られたデータはSEQUENCEであるはずなのでそうでなかったらエラー
- parseDERListでSEQUENCEを読み込み、ASN.1の配列が返る。
上記で得られたpiecesをつかってtbsCertificate、signatureAlgorithm、signatureを読み込んでいきます。
TBSCertificateの読み込み
まずはTBSCertificateから読み込んでいきます。
TBSCertificateの構造は下記となっています。
TBSCertificate ::= SEQUENCE {
version [0] Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
extensions [3] Extensions OPTIONAL
-- If present, version MUST be v3 -- }
ここで出てくる[0]や[1]とはOptionalなフィールドのことで、存在するかわからない値です。
加えてVersionのところにあるDefaultは存在しなければDefaultの横にある値,v1を適用するという意味となっています。
これを上から読み込んでいきます。
TBSCertificateもSEQUENCEなのでparseDERListを使って読み込み、ここの要素をさらに読み込んでいきます。
func (p *CertParser) parseTBSCertificate(asn1 *Data) (*TBSCertificate, error) {
if asn1.Class != 0 || asn1.Tag != 16 || !asn1.Structured {
return nil, ErrInvalidCertificate
}
tbs := &TBSCertificate{
ASN1: asn1,
}
pieces, err := p.parseDERList(asn1.Contents)
if err != nil {
return nil, err
}
//versionを除いて最低6個は子供がいないといけない(versionはないときもある)
if len(pieces) < 6 {
return nil, ErrInvalidTBSCertChild
}
curPieceNum := 0
versionExists := false
tbs.Version, versionExists, err = p.parseVersion(pieces[curPieceNum])
if err != nil {
return nil, err
}
if versionExists {
curPieceNum = 1
}
tbs.SerialNumber = p.parseToHex(pieces[curPieceNum])
tbs.Signature, err = p.parseAlgorithmIdentifier(pieces[curPieceNum+1])
if err != nil {
return nil, err
}
tbs.Issuer, err = p.parseName(pieces[curPieceNum+2])
if err != nil {
return nil, err
}
tbs.Validity, err = p.parseValidity(pieces[curPieceNum+3])
if err != nil {
return nil, err
}
tbs.Subject, err = p.parseName(pieces[curPieceNum+4])
if err != nil {
return nil, err
}
tbs.SubjectPublicKeyInfo, err = p.parseSubjectPublicKeyInfo(pieces[curPieceNum+5])
if err != nil {
return nil, err
}
err = p.parseOptional(pieces[curPieceNum+6:], tbs)
if err != nil {
return nil, err
}
return tbs, nil
}
versionが存在したかしないかでpiecesをずらすか、ずらさないかを決めています。
version
parseVersionを作って読み込んでいきます。
versionはOptionalなパラメータでしたが、このときPart1で後述するとした型の中のClassの一つであるCONTXET_SPECIFICが関わってきます。
|型 (1byte) |
|class(2bit),isStructured(1bit),tag(5bit)|
Classは0,1,2,3の4種類あり、それぞれ
UNIVERSAL=0
APPLICATION=1
CONTEXT_SPECIFIC=2
PRIVATE=3
となります。
今までTagはINTEGERであったりSTRINGであったりSEQUENCEであったり構造を表していましたが、
Optionalなフィールドの場合、ClassがCONTEXT_SPECIFICとなり、
ClassがCONTEXT_SPECIFICの場合、TagはOptionalの中でも何番目か?ということを表します。
TBSCertificate ::= SEQUENCE {
version [0] Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
extensions [3] Extensions OPTIONAL
-- If present, version MUST be v3 -- }
の例では、
versionは0番目なのでTag=0
issuerUniqueIDはTag=1
...
となっていきます。
これを踏まえてコードを見ていきましょう。(後で思ったのですが、ここのコードはparseOptionalに統合しても良かったかもしれません)
Version ::= INTEGER { v1(0), v2(1), v3(2) }
func (p *CertParser) parseVersion(asn1 *Data) (int, bool, error) {
if asn1.Tag != 0 || asn1.Class != ASN1_CONTEXT_SPECIFIC {
return 1, false, nil
}
p.DERParser.ResetWithNewData(asn1.Contents)
version, err := p.DERParser.Parse()
if err != nil {
return 1, false, err
}
p.DERParser.Reset()
//expect version.Content is only 1byte
if len(version.Contents) != 1 {
return 1, false, ErrInvalidVersion
}
versionNum, err := Bytes2Int(version.Contents)
if err != nil {
return 1, false, err
}
return versionNum + 1, true, nil
}
CONTEXT_SPECIFICかどうかを確認し違うならVersionは存在しません。
IntegerなのでContentsはIntのByte表現となってるのでByteをIntに戻してあげます。
v1=0,v2=1...と一個ずれているので+1して戻しています。
serialNumber
SerialNumber ::= INTEGER
で、INTEGERなのですが、
Part1の証明書の構造を表示したときにSerialNumberは16進数で表示されていたので、今回は16進数に変換してあげましょう。
Serial Number:
1c:38:e2:3e:1c:c2:cd:9f:09:f9:d2:56:ce:97:fe:bc:ca:92:24:34
func parseHex(b []byte) string {
return hex.EncodeToString(b)
}
func (p *CertParser) parseToHex(asn1 *Data) string {
return parseHex(asn1.Contents)
}
asn.1の値を16進数にしてあげるだけです。
Signature
signatureはAlgorithmIdentifierとなっていました。
AlgorithmIdentifierは下記の構造です。
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
parametersはOptionalです。
OBJECT IDENTIFIERはTagが06となっているあらかじめ用意されている型です。
値には、下記のようにbyte列で表されます。
[]byte{0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04}
このbyte列はOIDと呼ばれるオブジェクト識別子をencodeしたもので、oidは下記のようなintと.で表されています。下記はmd5WithRsaEncryptionのことです。
1.2.840.113549.1.1.4
byte列表現か、int表現のどちらであっても結局やりたいことはあどんなアルゴリズムを使っているかの特定です。
実際のコードでは下記の構造体を作って、Algorithmに1.2.840.113549.1.1.4みたいな形式のstringを入れています。
type AlgorithmIdentifier struct {
Algorithm string
Parameters *Parameters //Optional
}
ただ、やりたいことは後にOIDを使ってどんなアルゴリズムを使っているかの特定なので、
1.2.840.113549.1.1.4 -> []byte{0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04}のデコードをせずbyte列をそのまま使ってもよさそうです。
[レポジトリ x509.go parseAlgorithmIdentifier]
デコードについては下記が詳しいです。
issuer,subject
issuer,subjectともに型がNameとなっています。
Nameの構造は結構複雑で
Name = SEQUENCE OF RelativeDistinguishedName
RelativeDistinguishedName =
SET SIZE (1..MAX) OF AttributeTypeAndValue
AttributeTypeAndValue = SEQUENCE {
type AttributeType,
value AttributeValue }
AttributeType = OBJECT IDENTIFIER
AttributeValue = ANY -- DEFINED BY AttributeType
となっているので、まとめると下記のような感じとなります。
{
[AttributeTypeAndValue,AttributeTypeAndValue,...],
[AttributeTypeAndValue,AttributeTypeAndValue,...],
[AttributeTypeAndValue,AttributeTypeAndValue,...],
...
}
Issuer: C = JP, ST = Tokyo, L = Shibuya, O = TestCorp, OU = TestUnit, CN = Cube
上記の具体例をもとに考えてみると、
{
[{type:C value:JP},]
[{type:ST value:Tokyo},],
[{type:L value:Shibuya},],
...
}
となるのですが、これだと単純に下記のような構造で良い気がしないでしょうか。
{
AttributeTypeAndValue,
AttributeTypeAndValue,
AttributeTypeAndValue,
...
}
わざわざ [AttributeTypeAndValue,AttributeTypeAndValue,...],とするのには、
Multi-valued RDNという要素が関わってくるのですが、下記が詳しいです。
今回はMulti-valuedではないのでSETの要素が一つとしてパースしています。
func (p *CertParser) parseName(asn1 *Data) (*Name, error) {
piece, err := p.parseDERList(asn1.Contents) -1
if err != nil {
return nil, err
}
if len(piece) != 6 {
return nil, ErrInvalidNameChild
}
country, err := p.getNameChildContent(piece[0]) -2
if err != nil {
return nil, err
}
...
p.DERParser.Reset()
return &Name{
Country: country,
StateOrProvince: stateOrProvince,
Locality: locality,
Organization: organization,
OrganizationUnit: organizationUnit,
Common: common,
}, nil
}
- まずは最初のSEQUENCE OF 部分をParse,IssuerとSubjectでは要素が6つある想定。
- 順々にCountry,StateOrProvince...とParseしていく。
var (
OID_CommonName []byte = []byte{0x55, 0x04, 0x03}
OID_CountryName []byte = []byte{0x55, 0x04, 0x06}
OID_LocalityName []byte = []byte{0x55, 0x04, 0x07}
OID_StateOrProvinceName []byte = []byte{0x55, 0x04, 0x08}
OID_OrganizationName []byte = []byte{0x55, 0x04, 0x0A}
OID_OrganizationalUnitName []byte = []byte{0x55, 0x04, 0x0B}
OIDNames [][]byte = [][]byte{OID_CommonName, OID_CountryName, OID_LocalityName, OID_StateOrProvinceName, OID_OrganizationName, OID_OrganizationalUnitName}
)
func (p *CertParser) getNameChildContent(childRoot *Data) (string, error) {
p.DERParser.ResetWithNewData(childRoot.Contents)
attr, err := p.DERParser.Parse() -1
if err != nil {
return "", err
}
checkOID := func(oid []byte) bool {
for _, name := range OIDNames {
if bytes.Equal(oid, name) {
return true
}
}
return false
}
oidRawdata := attr.Contents[:5] -2
p.DERParser.ResetWithNewData(oidRawdata)
oid, err := p.DERParser.Parse()
if err != nil {
return "", err
}
if !checkOID(oid.Contents) {
return "", ErrInvalidNameOID
}
contentRawData := attr.Contents[5:]
p.DERParser.ResetWithNewData(contentRawData)
content, err := p.DERParser.Parse() -3
if err != nil {
return "", err
}
p.DERParser.Reset()
return string(content.Contents), nil
}
- setOF部分をパース,出力されるのはAttributeTypeAndValue(Sequence)
- AttributeTypeAndValueのtype(OID)だけを抽出してパース
- AttributeTypeAndValueのvalueをパース
Validity
まずはasn.1から。
Validity ::= SEQUENCE {
notBefore Time,
notAfter Time }
Time ::= CHOICE {
utcTime UTCTime,
generalTime GeneralizedTime
}
CHOICEはどちらかの型になるという意味です。
今回は UTCTime or GeneralizedTimeですね。
UTCTimeは2049年まで、GeneralizedTimeは2050年から使用するとなっているので、
今回はUTCTimeの場合だけ考えます。
UTCTimeはtag=17で、値がYYMMDDhhmmssZのフォーマットで書かれます。
例として、値が下記とします。
"32 32 31 30 31 33 30 34 34 34 34 37 5a"
これをstringにすると下記になります。
"221013044447Z"
上記より
data = "221013044447Z"として
data[:2]が年、
data[2:4]が月、
data[4:6]が日、
data[6:8]が時間、
data[8:10]が分数、
data[10:12]が秒数
data[13]=Z
となっていることがわかります。
あとはこれを各種言語のTime型に合わせる形です。
func (p *CertParser) parseValidity(asn1 *Data) (*VerifyPeriod, error) {
pieces, err := p.parseDERList(asn1.Contents) -1
if err != nil {
return nil, err
}
if len(pieces) != 2 {
return nil, ErrInvalidValidityChild
}
toTime := func(b []byte) (time.Time, error) {
data := string(b)
year, err := strconv.Atoi(data[:2])
if err != nil {
return time.Time{}, err
}
year += 2000
month, err := strconv.Atoi(data[2:4])
if err != nil {
return time.Time{}, err
}
day, err := strconv.Atoi(data[4:6])
if err != nil {
return time.Time{}, err
}
hour, err := strconv.Atoi(data[6:8])
if err != nil {
return time.Time{}, err
}
minute, err := strconv.Atoi(data[8:10])
if err != nil {
return time.Time{}, err
}
sec, err := strconv.Atoi(data[10:12])
if err != nil {
return time.Time{}, err
}
gmt, err := time.LoadLocation("GMT")
if err != nil {
return time.Time{}, err
}
return time.Date(
year,
time.Month(month),
day,
hour,
minute,
sec,
0,
gmt,
), nil
} -2
notBefore, err := toTime(pieces[0].Contents)
if err != nil {
return nil, err
}
notAfter, err := toTime(pieces[1].Contents)
if err != nil {
return nil, err
}
return &VerifyPeriod{
NotBefore: notBefore,
NotAfter: notAfter,
}, nil
}
- SEQUENCE部分をパース
- asn.1の値をtimeに変換
subjectPublicKeyInfo
まずはasn.1から。
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
AlgorithmIdentifierはsignatureのところで解説しました。
今回はalgorithmがRSA(1.2.840.113549.1.1.1)として解説します。
BIT STRINGについて解説していきます。
someMember BIT STRING - primitive
someMember BIT STRING{ - structed
yellow
red
green
}
上記のようにBIT STRINGには二種類あり、structedだとBIT STRINGのあとに構造体のメンバーが書かれます。
今回のsubjectPublicKeyはprimitiveです。
structedの場合値はそのままで、
primitiveの場合は、
先頭1byteがUnusedBitとなり0~7の数となります。
UnusedBitは値部分の後ろの何bitが使われていないかを表しているので、下記のような構造です。
|UnusedBit(1byte)|後続のbyte...|未使用bit|
さて、BITSTRINGから上手くUsedBitだけを取り出した後はBITSTRING自体が何を表すかを解析しなければいけません。
今回のSubjectPublicKeyInfoでBITSTRINGが何を表しているかはalgorithmによって変わってきます。
RSA
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e }
DH
DHPublicKey ::= INTEGER
なのでBITSTRINGをさらにパースして情報を取り出していくという形をとっていきます。
var (
OID_RSA = "1.2.840.113549.1.1.1"
ValidAlgos = []string{OID_RSA}
)
func (p *CertParser) parseSubjectPublicKeyInfo(asn1 *Data) (*SubjectPublicKeyInfo, error) {
if asn1.Class != 0 || asn1.Tag != 16 || !asn1.Structured {
return nil, ErrInvalidSubjectPublicKey
}
pubKey := &SubjectPublicKeyInfo{}
pieces, err := p.parseDERList(asn1.Contents) -1
if err != nil {
return nil, err
}
if len(pieces) != 2 {
return nil, ErrInvalidSubjectPublicKeyChild
}
checkAlgo := func(str string) bool {
for _, algo := range ValidAlgos {
if str == algo {
return true
}
}
return false
}
algorithm, err := p.parseAlgorithmIdentifier(pieces[0]) -2
if err != nil {
return nil, err
}
if !checkAlgo(algorithm.Algorithm) {
return nil, ErrInvalidSigAlgo
}
subjectPublicKey := p.parseBitString(pieces[1]) -3
//subjectPublicKeyの中身のmodulesとexponentを取り出す
p.DERParser.ResetWithNewData(subjectPublicKey.Bytes)
keyValueData, err := p.DERParser.Parse()
if err != nil {
return nil, err
}
//TODO algorithmに応じてBIT STRINGのパースを変更
pieces, err = p.parseDERList(keyValueData.Contents) -4
if err != nil {
return nil, err
}
if len(pieces) != 2 {
return nil, ErrInvalidKeyValueChild
}
modulus := p.parseToHex(pieces[0])
exponent := p.parseToHex(pieces[1])
pubKey.Algorithm = algorithm
pubKey.SubjectPublicKey = &SubjectPublicKey{
Modulus: modulus,
Exponent: exponent,
}
return pubKey, nil
}
- SEQUENCEをパース
- AlgorithmIdentifierをパース
- BIT STRINGをパース
- 今回はRSAとしているのでSEQUENCEをパースし、ModulusとExponentを取り出す
extensions
TBSCertificate ::= SEQUENCE {
...
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
extensions [3] Extensions OPTIONAL
-- If present, version MUST be v3 -- }
issuerUniqueID,subjectUniqueID,extensionsはOptionalとなっていました。
今回作成した証明書ではissuerUniqueID,subjectUniqueIDは存在しないので取り扱わず、
またextensionsも様々なextensionがあるのですが、作成した証明書に存在する三種(SubjectKeyIdentifier、AuthorityKeyIdentifier、BasicConstraints)のみ取り扱います。
最初にasn.1から。
Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
Extension ::= SEQUENCE {
extnID OBJECT IDENTIFIER,
critical BOOLEAN DEFAULT FALSE,
extnValue OCTET STRING
-- contains the DER encoding of an ASN.1 value
-- corresponding to the extension type identified
-- by extnID
}
とあるので、まずExtensionsをパースしてからここのExtensionをさらにパースしていくことになります。
func (p *CertParser) parseOptional(data []*Data, tbs *TBSCertificate) error {
//ここもdataを回してoptionのTagごとに分岐
for _, d := range data {
if d.Class != ASN1_CONTEXT_SPECIFIC {
return ErrContextSpecific
}
switch d.Tag {
case 1:
continue
case 2:
continue
case 3:
extensions, err := p.parseExtensions(data[0])
if err != nil {
return err
}
tbs.Extensions = extensions
default:
return ErrInvalidOptionalNum
}
}
return nil
}
Optional部分を取り扱う際に、以前説明したようにCONTEXT_SPECIFICとTagでSEQUENCE中のどの要素なのかを判別しています。
func (p *CertParser) parseExtensions(asn1 *Data) ([]Extension, error) {
p.DERParser.ResetWithNewData(asn1.Contents)
extensionsData, err := p.DERParser.Parse()
if err != nil {
return nil, err
}
pieces, err := p.parseDERList(extensionsData.Contents) -1
if err != nil {
return nil, err
}
extensionLen := len(pieces) -2
extensions := make([]Extension, extensionLen)
for i := 0; i < extensionLen; i++ {
extension, err := p.parseExtension(pieces[i]) -3
if err != nil {
return nil, err
}
extensions[i] = extension
}
return extensions, nil
}
- SEQUENCE OFをパース
- Extensionが何個あるかを取得
- Extensionをパース
func (p *CertParser) parseExtension(asn1 *Data) (Extension, error) {
pieces, err := p.parseDERList(asn1.Contents) -1
if err != nil {
return nil, err
}
if len(pieces) != 2 && len(pieces) != 3 { -2
return nil, ErrInvalidExtensionChild
}
oid := p.parseObjectIdent(pieces[0].Contents) -3
switch oid {
case oid_subjectKeyIdentifier:
return p.parseSubjectKeyIdentifier(pieces[1:], oid)
case oid_authorityKeyIdentifier:
return p.parseAuthorityKeyIdentifier(pieces[1:], oid)
case oid_basicConstraints:
return p.parseBasicConstraints(pieces[1:], oid)
default:
return nil, ErrInvalidExtension
}
}
parseExtensionを見ていくにあたり、asn.1を再掲します。
Extension ::= SEQUENCE {
extnID OBJECT IDENTIFIER,
critical BOOLEAN DEFAULT FALSE,
extnValue OCTET STRING
-- contains the DER encoding of an ASN.1 value
-- corresponding to the extension type identified
-- by extnID
}
- SEQUENCEをパース
- criticalがdefaultなのでSEQUENCEの子供は2か3となる
- oidをパース
oidによってどうパースするかを分岐させて、criticalとextnValueを渡しています。
ExtensionのextnValueがSubjectKeyIdentifier等になっていきます。
次からここのExtensionについて見ていきます。
今回取り上げなかったExtensionも同じように実装すればできるはずですので、興味がある方はぜひ。(SANとか)
SubjectKeyIdentifier
まずはasn.1から。
doesn't have critical
SubjectKeyIdentifier ::= KeyIdentifier
KeyIdentifire ::= OCTET STRING
SubjectKeyIdentifierはcriticalを含みません。
func (p *CertParser) parseSubjectKeyIdentifier(data []*Data, oid string) (*SubjectKeyIdentifier, error) {
if len(data) == 2 { -1
return nil, ErrExtensionCanNotBeCritical
}
p.DERParser.ResetWithNewData(data[0].Contents)
keyIdent, err := p.DERParser.Parse() -2
if err != nil {
return nil, err
}
return &SubjectKeyIdentifier{
OID: oid,
KeyIdentidier: p.parseToHex(keyIdent), -3
}, nil
}
- criticalを含んでいる場合はエラー
- OCTET STRINGをパース
- 16進数で格納
3.について
証明書のデータを表示すると下記のようになっていたので16進数で格納します。
X509v3 Subject Key Identifier:
B4:7D:B7:8D:C3:78:94:3B:C9:24:11:6A:A9:A4:26:B7:91:85:BA:FF
AuthorityKeyIdentifier
doesn't have critical
AuthorityKeyIdentifier ::= SEQUENCE {
keyIdentifier [0] KeyIdentifier OPTIONAL,
authorityCertIssuer [1] GeneralNames OPTIONAL,
authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
KeyIdentifire ::= OCTET STRING
CertificateSerialNumber ::= INTEGER
AuthorityKeyIdentifierもcriticalを含みません。
OptionalがあるのでここでもContextSpecificが使用されます。
extnValueがAuthorityKeyIdentifierの場合、OCTET STRINGの中身はSEQUENCEなのでもう一度パースが必要になります。
func (p *CertParser) parseAuthorityKeyIdentifier(data []*Data, oid string) (*AuthorityKeyIdentifier, error) {
if len(data) == 2 { -1
return nil, ErrExtensionCanNotBeCritical
}
p.DERParser.ResetWithNewData(data[0].Contents)
seq, err := p.DERParser.Parse() -2
if err != nil {
return nil, err
}
pieces, err := p.parseDERList(seq.Contents) -3
if err != nil {
return nil, err
}
ident := &AuthorityKeyIdentifier{
OID: oid,
}
for _, piece := range pieces { -4
if piece.Class != ASN1_CONTEXT_SPECIFIC {
return nil, ErrContextSpecific
}
switch piece.Tag {
case 0:
hex := p.parseToHex(piece)
ident.KeyIdentifier = &hex
case 1:
name, err := p.parseGeneralNames(piece)
if err != nil {
return nil, err
}
ident.AuthorityCertIssuer = name
case 2:
hex := p.parseToHex(piece)
ident.AuthorityKeyIdentifier = &hex
default:
return nil, ErrInvalidOptionalNum
}
}
return ident, nil
}
- criticalがあればエラー
- extnValue(OCTET STRING)をパース
- SEQUENCEをパース
- Optional部分を処理
GenralNamesについてですが、構造が難しく、本証明書には含まれていないため今回は実装していません。一応asn.1だけ載せておきます。
GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
GeneralName ::= CHOICE {
otherName [0] OtherName,
rfc822Name [1] IA5String,
dNSName [2] IA5String,
x400Address [3] ORAddress,
directoryName [4] Name,
ediPartyName [5] EDIPartyName,
uniformResourceIdentifier [6] IA5String,
iPAddress [7] OCTET STRING,
registeredID [8] OBJECT IDENTIFIER }
OtherName ::= SEQUENCE {
type-id OBJECT IDENTIFIER,
value [0] EXPLICIT ANY DEFINED BY type-id }
EDIPartyName ::= SEQUENCE {
nameAssigner [0] DirectoryString OPTIONAL,
partyName [1] DirectoryString }
CHOICEは番号の中からどれか一つを選んで使うというものです。
BasicConstraints
may have critical
BasicConstraints ::= SEQUENCE {
cA BOOLEAN DEFAULT FALSE,
pathLenConstraint INTEGER (0..MAX) OPTIONAL
}
BasicConstraintsは以前に二つと違いcriticalを含む可能性があります。
後は以前に出てきたものばかりですが、Optionalがあったりdefaultがあるので処理の部分が複雑になります。
func (p *CertParser) parseBasicConstraints(data []*Data, oid string) (*BasicConstraints, error) {
critical := false
component := data[0]
if len(data) == 2 { -1
var err error
critical, err = p.parseBoolean(component)
if err != nil {
return nil, err
}
component = data[1]
}
basicConstraints := &BasicConstraints{OID: oid, Critical: critical}
p.DERParser.ResetWithNewData(component.Contents)
seq, err := p.DERParser.Parse() -2
if err != nil {
return nil, err
}
pieces, err := p.parseDERList(seq.Contents) -3
if err != nil {
return nil, err
}
for _, d := range pieces { -4
switch d.Tag {
case ASN1_INTEGER:
i, err := Bytes2Int(d.Contents)
if err != nil {
return nil, err
}
basicConstraints.PathLenConstraint = &i
case ASN1_BOOLEAN:
isCA, err := p.parseBoolean(d)
if err != nil {
return nil, err
}
basicConstraints.CA = isCA
default:
return nil, ErrInvalidOptionalNum
}
}
return basicConstraints, nil
}
- len=2の場合はcrtiticalを含みます。
- extnValue(OCTET STRING)をパース
- SEQUENCEをパース
- あとはOptional部分をパースしていきます。
func (p *CertParser) parseBoolean(asn1 *Data) (bool, error) {
if asn1.Tag != ASN1_BOOLEAN {
return false, ErrInvalidBasicConstraintsBoolean
}
i, err := Bytes2Int(asn1.Contents)
if err != nil {
return false, err
}
if i == 0 {
return false, nil
}
return true, nil
booleanは値が0だったらfalse,それ以外だったらtrueです。
ここまででようやくTBSCertificateが終わりです。お疲れさまでした。
Certificateの残りの部分
後は、
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signature BIT STRING }
signatureAlgorithmとsignatureだけですが、この二つの型はTBSCertificateの中で出てきた型ですのでこれまでのようにやるだけです。
func (p *CertParser) Parse() (*Certificate, error) {
...
sigAlgo, err := p.parseAlgorithmIdentifier(pieces[1]) -1
if err != nil {
return nil, err
}
sigVal, err := p.parseSignatureValue(pieces[2]) -2
if err != nil {
return nil, err
}
return &Certificate{
TbsCertificate: tbsCert,
SignatureAlgorithm: sigAlgo,
SignatureValue: sigVal,
}, nil
}
func (p *CertParser) parseSignatureValue(asn1 *Data) (*SignatureValue, error) {
if asn1.Class != 0 || asn1.Tag != 3 || asn1.Structured {
return nil, ErrInvalidSigValue
}
sig := &SignatureValue{}
bits := p.parseBitString(asn1)
sig.Value = bits.Bytes
return sig, nil
}
[レポジトリ x509.go (CERTParser)Parse]
- AlgorithmIdentifierをパースします。
- SignatureValueはBIT STRINGなのでパースしていきます。
sig.ValueにBIT STRINGのUsedBitを入れていますが、値を取りやすくするためにUsedBitを入れているだけなので別にBIT STRINGをそのまま使っても良いと思います。
これでCertificateから情報の読み取りは完了しました。お疲れ様です。
おわりに
随分と長くなってしまいましたが、ここまで読んでくださりありがとうございます。
アルゴリズムをRSAに絞ったり、限られたExtensionしか実装しない等の省略はありますが、
openssl x509 -in server.crt -noout -text
基本的に上記のコマンドで表示された情報を出力することを目的にしたので、
- 実装したい項目がある証明書を作り、ゴールとなる値を理解する
- RFC等を読んでASN.1を知る
- ゴールの値を出力するコードを書く
の順番でやるのが良いかと思います。
パーサーを書いてよかった点として、Multi-valued RDNのような例があると知れたことです。
最初なんでCとかOUが複数ある構造になるんだろう?asn.1の読み取り間違いか?と思ったりしま した。
実際は、Multi-valued RDNに対応するためだった訳ですが、悩んだ分資料に当たる力もつきましたし良しとします。
次回は情報を取得することができたので証明書のVerifyをやっていきます。
次回
Discussion