langでPKI入門 - 4


1. この記事の対象の人

  • Golang で、証明書失効リスト( CRL )を作りたい人

2. 概要

この記事では、
1. Go で秘密鍵と証明書を生成
2. Go で失効させる証明書のリストを生成
3. Go で Issuing Distribution Point のExtensionを作成
4. Go で証明書失効リスト( CRL )を作成
5. OpenSSL で証明書失効リスト( CRL )の中身を確認
します。

3. Golang で自己署名 CA 証明書と秘密鍵を作成

証明書失効リストを発行する自己署名 CA の「証明書」と「秘密鍵」を作成します。
詳細な説明は、GolangでPKI入門 - 2 を参照ください。
証明書失効リスト作成時に引数で必要になるので、秘密鍵はDER形式にしておきます。


    //PrivateKey of Self Sign CA Certificate
    privateCaKey, err := rsa.GenerateKey(rand.Reader, 2048)
    publicCaKey := privateCaKey.Public()

    //[RFC5280]
    subjectCa := pkix.Name{
        CommonName:         "ca01",
        OrganizationalUnit: []string{"Example Org Unit"},
        Organization:       []string{"Example Org"},
        Country:            []string{"JP"},
    }

    caTpl := &x509.Certificate{
        SerialNumber:          big.NewInt(1),
        Subject:               subjectCa,
        NotAfter:              time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC),
        NotBefore:             time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC),
        IsCA:                  true,
        KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
        BasicConstraintsValid: true,
    }

    //Self Sign CA Certificate
    caCertificate, err := x509.CreateCertificate(rand.Reader, caTpl, caTpl, publicCaKey, privateCaKey)


    //Convert to ASN.1 DER encoded form
    derCaCert, err = x509.ParseCertificate(caCertificate)
    if err != nil {
        log.Fatalf("ERROR:%v\n", err)
    }

4. Golang で証明書失効リストを作成

失効させる証明書のリストを作成

    var rcs []pkix.RevokedCertificate
    rc := pkix.RevokedCertificate{
        SerialNumber:   big.NewInt(100),
        RevocationTime: time.Now(),
    }

    rcs = append(rcs, rc)

    rc = pkix.RevokedCertificate{
        SerialNumber:   big.NewInt(108),
        RevocationTime: time.Now(),
    }

    rcs = append(rcs, rc)

ここでは、シリアルが100と108の証明書を失効させます。

証明書失効リストの crlExtensions に Issuing Distribution Point を追加

Go で証明書失効リストを作成するときに利用する x509.RevocationList には、直接 Issuing Distribution Point を追加するFieldはありません。
別途 Issuing Distribution Point 用の構造体を作成して、Extensionに追加してやる必要があります。
RFC5280 では、Issuing Distribution Pointは以下のように定義されています。

   id-ce-issuingDistributionPoint OBJECT IDENTIFIER ::= { id-ce 28 }

   IssuingDistributionPoint ::= SEQUENCE {
        distributionPoint          [0] DistributionPointName OPTIONAL,
        onlyContainsUserCerts      [1] BOOLEAN DEFAULT FALSE,
        onlyContainsCACerts        [2] BOOLEAN DEFAULT FALSE,
        onlySomeReasons            [3] ReasonFlags OPTIONAL,
        indirectCRL                [4] BOOLEAN DEFAULT FALSE,
        onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE }

   DistributionPointName ::= CHOICE {
        fullName                [0]     GeneralNames,
        nameRelativeToCRLIssuer [1]     RelativeDistinguishedName }

   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 }

上記に従い、issuingDistributionPoint と distributionPointName を 以下の Go の構造体として定義しました。

// RFC5280, 5.2.5
type issuingDistributionPoint struct {
    DistributionPoint          distributionPointName `asn1:"optional,tag:0"`
    OnlyContainsUserCerts      bool                  `asn1:"optional,tag:1"`
    OnlyContainsCACerts        bool                  `asn1:"optional,tag:2"`
    OnlySomeReasons            asn1.BitString        `asn1:"optional,tag:3"`
    IndirectCRL                bool                  `asn1:"optional,tag:4"`
    OnlyContainsAttributeCerts bool                  `asn1:"optional,tag:5"`
}

type distributionPointName struct {
    FullName     []asn1.RawValue  `asn1:"optional,tag:0"`
    RelativeName pkix.RDNSequence `asn1:"optional,tag:1"`
}

distributionPointName の FullName フィールドの型は GeneralNames です。
GeneralName の uniformResourceIdentifier で証明書失効リストの取得先を指定したいので、
asn1.RawValue 型で以下のように設定
Class: 2
Context-specific ( asn1.RawValue の定義による)
Tag: 6
GeneralName の6番目つまり uniformResourceIdentifier
Bytes: []byte("http://www.example.com/example.crl")
uniformResourceIdentifier の エンコーディングは IA5String です。ただ、crl への URI で使われている範囲の文字列は IA5String と UTF8 で同じbyteになるので直接 byte 配列として渡しています。

    dp := distributionPointName{
        FullName: []asn1.RawValue{
            {Tag: 6, Class: 2, Bytes: []byte("http://www.example.com/example.crl")},
        },
    }

Extension に作成した IssuingDistributionPoint を設定します。


var oidExtensionIssuingDistributionPoint = []int{2, 5, 29, 28}

    idp := issuingDistributionPoint{
        DistributionPoint: dp,
    }

    v, err := asn1.Marshal(idp)

    cdpExt := pkix.Extension{
        Id:       oidExtensionIssuingDistributionPoint,
        Critical: true,
        Value:    v,
    }

x509.RevocationList 構造体の設定

x509.RevocationList 構造体に設定したい値を入れていきます。

    crlTpl := &x509.RevocationList{
        SignatureAlgorithm:  x509.SHA256WithRSA,
        RevokedCertificates: rcs,
        Number:              big.NewInt(2),
        ThisUpdate:          time.Now(),
        NextUpdate:          time.Now().Add(24 * time.Hour),
        ExtraExtensions:     []pkix.Extension{cdpExt},
    }

証明書失効リストを作成

証明書失効リストを発行します

    var derCrl []byte
    derCrl, err = x509.CreateRevocationList(rand.Reader, crlTpl, derCaCert, privateCaKey)
    if err != nil {
        log.Fatalf("ERROR:%v\n", err)
    }

    f, err = os.Create("ca01.crl")
    if err != nil {
        log.Fatalf("ERROR:%v\n", err)
    }

    err = pem.Encode(f, &pem.Block{Type: "X509 CRL", Bytes: derCrl})
    if err != nil {
        log.Fatalf("ERROR:%v\n", err)
    }
    err = f.Close()

5. 証明書失効リストを確認する

発行した証明書失効リストを Openssl で確認します。設定した要素がすべて入っていますね。

$ openssl crl -inform pem -in example.crl -text
Certificate Revocation List (CRL):
        Version 2 (0x1)
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = JP, O = Example Org, OU = Example Org Unit, CN = ca01
        Last Update: Oct 24 04:16:04 2020 GMT
        Next Update: Oct 25 04:16:04 2020 GMT
        CRL extensions:
            X509v3 Authority Key Identifier:
                keyid:0A:42:8D:9B:23:A9:77:11:FF:FD:0F:CC:58:F4:36:F4:98:06:7F:28

            X509v3 CRL Number:
                2
            X509v3 Issuing Distribution Point: critical
                Full Name:
                  URI:http://www.example.com/example.crl

Revoked Certificates:
    Serial Number: 64
        Revocation Date: Oct 24 04:16:04 2020 GMT
    Serial Number: 6C
        Revocation Date: Oct 24 04:16:04 2020 GMT
    Signature Algorithm: sha256WithRSAEncryption
         6c:0d:23:e8:50:bf:84:ae:10:85:3e:43:28:0f:43:fd:58:cb:
         83:8c:7c:a8:5c:7d:78:71:f1:0c:03:97:43:88:8c:32:02:5c:
         a6:6c:e2:a4:7d:94:56:08:a8:9c:17:95:b4:be:11:bb:65:52:
         43:25:de:c0:d5:d0:df:ac:0f:ca:8c:a7:23:82:19:12:e2:9d:
         49:83:9e:ca:bc:2e:f3:60:79:39:47:cb:ed:17:52:25:9f:42:
         26:9e:1b:67:5f:af:e1:3a:14:67:5f:4f:de:10:c5:32:03:7f:
         40:a0:b6:bc:3f:05:33:73:91:0b:73:4e:f2:3c:be:b0:e4:63:
         e0:d0:81:6e:91:14:d9:04:35:21:3e:22:1e:31:bd:47:40:c9:
         69:f0:e5:57:bc:c3:2c:ae:b8:06:38:35:f1:59:6f:45:2c:45:
         08:2e:63:49:ab:f5:54:0b:54:d2:a8:fc:62:ea:a5:46:62:28:
         a9:89:76:96:cf:47:28:3d:81:c3:e9:fb:ce:54:a8:07:71:6d:
         c6:d8:b7:e7:33:b0:05:df:c4:79:56:e1:99:ed:9f:33:f8:15:
         b9:32:4e:82:4c:0c:a7:a5:23:d4:f7:e1:94:26:2b:e0:55:1a:
         38:f6:72:21:a9:e0:29:06:80:9a:05:e3:43:c2:4a:dd:74:c6:
         d6:79:ec:9d
-----BEGIN X509 CRL-----
MIICKDCCARACAQEwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCSlAxFDASBgNV
BAoTC0V4YW1wbGUgT3JnMRkwFwYDVQQLExBFeGFtcGxlIE9yZyBVbml0MQ0wCwYD
VQQDEwRjYTAxFw0yMDEwMjQwNDE2MDRaFw0yMDEwMjUwNDE2MDRaMCgwEgIBZBcN
MjAxMDI0MDQxNjA0WjASAgFsFw0yMDEwMjQwNDE2MDRaoGUwYzAfBgNVHSMEGDAW
gBQKQo2bI6l3Ef/9D8xY9Db0mAZ/KDAKBgNVHRQEAwIBAjA0BgNVHRwBAf8EKjAo
oCagJIYiaHR0cDovL3d3dy5leGFtcGxlLmNvbS9leGFtcGxlLmNybDANBgkqhkiG
9w0BAQsFAAOCAQEAbA0j6FC/hK4QhT5DKA9D/VjLg4x8qFx9eHHxDAOXQ4iMMgJc
pmzipH2UVgionBeVtL4Ru2VSQyXewNXQ36wPyoynI4IZEuKdSYOeyrwu82B5OUfL
7RdSJZ9CJp4bZ1+v4ToUZ19P3hDFMgN/QKC2vD8FM3ORC3NO8jy+sORj4NCBbpEU
2QQ1IT4iHjG9R0DJafDlV7zDLK64Bjg18VlvRSxFCC5jSav1VAtU0qj8YuqlRmIo
qYl2ls9HKD2Bw+n7zlSoB3Ftxti35zOwBd/EeVbhme2fM/gVuTJOgkwMp6Uj1Pfh
lCYr4FUaOPZyIangKQaAmgXjQ8JK3XTG1nnsnQ==
-----END X509 CRL-----

6. コード

コードはこちら
https://github.com/tardevnull/gopkicookbook4