golangでX.509証明書をいじってみる...
まずはX.509証明書のお話を...
X.509証明書とは
X.509証明書は公開鍵証明書の標準としてITU-Tが策定したもの
基本的に証明書と言ったら公開鍵証明書を指すが、属性証明書、特定証明書というものもある。
公開鍵証明書はさらにCA(Certification Authority)証明書とEE(End Entity)証明書という2種類に分けられる。
CA証明書は認証局に対して発行する証明書。CA自身の秘密鍵で署名した自己署名証明書と他のCAで発行された証明書がある。
それに対してEE証明書はPKIユーザー向けの証明書。Webクライアント、Webサーバーで利用されるもの。
詳しいお話は日本語だとIPAの資料がまとまっている気がする
3.3 電子証明書
証明書の作成
まずはCA秘密鍵の作成
ここからはopensslコマンドで色々やっていく
$ openssl genres -out ca.key
CA公開鍵の作成
$ openssl rsa -in ca.key -pubout -out ca.pub.key
自己証明書署名要求の作成
$ openssl req -new -key ca.key -out ca.csr
...
証明書署名要求の内容確認
$ openssl req -text -noout -in ca.csr
Certificate Request:
Data:
Version: 0 (0x0)
Subject: C=JP, ST=Tokyo, O=CA, CN=ca.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:b5:74:92:48:77:60:d4:14:8a:50:ad:6c:01:ed:
4d:6f:0d:dd:99:4a:d1:8a:eb:61:78:d2:71:47:2c:
4f:d9:8b:41:9b:20:16:02:df:b5:0a:3f:12:35:5a:
1d:c1:c3:58:a9:b2:9f:60:41:32:cf:3b:b8:a0:ce:
4f:be:bd:28:6d:83:f5:ca:84:56:60:ed:4f:53:cc:
4c:37:1a:55:de:06:5b:f8:3b:0b:7c:c9:fe:bf:fc:
03:04:06:ef:91:dd:e4:39:b7:82:71:bd:40:2b:c1:
b8:f8:3d:84:6e:fe:b0:02:7d:15:f9:c7:1c:44:17:
08:b0:f4:4e:91:0b:0c:69:45:01:3a:30:8e:49:8a:
19:0c:97:56:a6:ea:e6:34:78:64:a9:96:fe:3d:72:
3d:1d:e2:99:62:72:14:fa:f8:29:c5:1a:c7:c0:61:
d0:07:70:4d:5a:fe:d2:43:67:5a:27:7f:8c:51:ca:
f5:6c:0b:c6:86:a0:af:6f:d4:78:10:6e:47:f0:89:
06:3d:d0:e8:ce:2c:50:81:13:94:7b:7d:46:36:f6:
d3:86:d0:cb:0e:0f:6e:cf:0b:8f:6f:bf:d1:30:bc:
2c:4d:f0:64:2d:c6:5f:2f:14:7b:18:a0:b2:2c:b4:
74:67:14:55:81:44:9e:bc:97:f1:42:c6:15:d1:f7:
d8:23
Exponent: 65537 (0x10001)
Attributes:
a0:00
Signature Algorithm: sha256WithRSAEncryption
8a:be:80:c9:3e:18:3c:58:2a:c1:88:fa:82:d1:f6:ea:89:4d:
5d:b4:d5:f7:4b:78:a8:47:d2:c5:89:d5:47:c6:86:df:9a:ee:
27:56:9a:b5:bd:16:e5:f7:97:b0:41:c9:66:18:fc:72:b7:c2:
97:3f:03:b6:38:ad:f8:5d:99:6c:b4:7e:4a:2d:e7:65:14:55:
ea:89:ff:3c:32:28:dd:81:0f:de:bd:58:16:99:6d:cf:f2:45:
25:88:d8:d6:6d:29:39:ce:1b:ce:1c:ca:56:93:b4:82:b3:6d:
37:d1:b8:90:db:2d:8b:f5:26:16:8a:c5:de:58:52:db:44:c1:
ca:70:48:3f:d2:c1:60:bd:93:94:c1:52:a7:f0:f9:73:0a:cd:
be:a4:7c:6f:97:72:7c:37:60:2e:a6:b3:f5:62:9d:30:11:a0:
0c:b4:29:98:ae:96:df:6d:e5:5e:af:ac:c6:3f:bc:9e:3b:75:
6c:60:d9:d1:64:ed:2a:30:f6:57:ef:3b:98:20:4b:16:9b:93:
94:4b:1a:1e:2d:14:b2:4b:ad:fd:b9:81:49:34:cf:78:97:e0:
a6:4a:5a:24:a3:45:a3:9c:89:ee:47:a1:72:10:02:15:48:17:
12:49:a0:c0:50:82:9b:1c:d0:0d:fd:78:f3:a4:21:44:ea:ba:
32:d6:7d:fb
証明書署名要求を使用して署名された公開鍵証明書を作成
$ openssl x509 -req -in ca.csr -signkey ca.key -days 10000 -out ca.crt
証明書の内容確認
$ openssl x509 -text -noout -in ca.crt
Certificate:
Data:
Version: 1 (0x0)
Serial Number: 11311120728934412255 (0x9cf92c5fc3b16fdf)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=JP, ST=Tokyo, O=CA, CN=ca.com
Validity
Not Before: Jun 8 16:45:52 2020 GMT
Not After : Oct 25 16:45:52 2047 GMT
Subject: C=JP, ST=Tokyo, O=CA, CN=ca.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:b5:74:92:48:77:60:d4:14:8a:50:ad:6c:01:ed:
4d:6f:0d:dd:99:4a:d1:8a:eb:61:78:d2:71:47:2c:
4f:d9:8b:41:9b:20:16:02:df:b5:0a:3f:12:35:5a:
1d:c1:c3:58:a9:b2:9f:60:41:32:cf:3b:b8:a0:ce:
4f:be:bd:28:6d:83:f5:ca:84:56:60:ed:4f:53:cc:
4c:37:1a:55:de:06:5b:f8:3b:0b:7c:c9:fe:bf:fc:
03:04:06:ef:91:dd:e4:39:b7:82:71:bd:40:2b:c1:
b8:f8:3d:84:6e:fe:b0:02:7d:15:f9:c7:1c:44:17:
08:b0:f4:4e:91:0b:0c:69:45:01:3a:30:8e:49:8a:
19:0c:97:56:a6:ea:e6:34:78:64:a9:96:fe:3d:72:
3d:1d:e2:99:62:72:14:fa:f8:29:c5:1a:c7:c0:61:
d0:07:70:4d:5a:fe:d2:43:67:5a:27:7f:8c:51:ca:
f5:6c:0b:c6:86:a0:af:6f:d4:78:10:6e:47:f0:89:
06:3d:d0:e8:ce:2c:50:81:13:94:7b:7d:46:36:f6:
d3:86:d0:cb:0e:0f:6e:cf:0b:8f:6f:bf:d1:30:bc:
2c:4d:f0:64:2d:c6:5f:2f:14:7b:18:a0:b2:2c:b4:
74:67:14:55:81:44:9e:bc:97:f1:42:c6:15:d1:f7:
d8:23
Exponent: 65537 (0x10001)
Signature Algorithm: sha1WithRSAEncryption
a1:84:89:20:5b:c5:b1:c7:ea:90:4c:af:2f:aa:98:29:70:a5:
cf:14:b0:a3:a5:82:66:71:2e:54:93:ba:02:4f:80:bb:c2:fa:
6f:1b:61:a9:1d:34:03:0c:7f:ae:ee:0a:e4:98:9d:d3:f5:d0:
79:37:0f:c9:87:44:e7:25:ca:bc:45:9c:a9:09:26:a5:82:22:
e4:8d:46:32:b4:27:fe:1f:96:a3:d9:66:f7:22:49:9c:f2:5e:
63:73:c3:dd:a0:a7:38:f3:13:d2:ec:26:67:a8:9d:f8:b2:33:
62:79:2d:89:3c:ed:1a:0d:94:44:54:57:ad:3d:5d:74:11:74:
b9:ee:f6:ff:7e:90:0d:a5:76:80:af:a9:8f:75:cb:28:9e:66:
ca:b2:07:b5:b2:c4:20:9f:55:f5:93:36:30:b6:78:93:c9:d6:
97:a5:3f:4f:55:4f:25:9f:0f:6d:40:0e:3d:72:ca:63:87:8b:
8f:12:31:28:ce:6d:e4:a8:c3:eb:09:a7:12:9a:28:f0:7d:4d:
ea:6f:d7:2a:af:44:53:92:47:20:62:c6:db:0c:eb:ba:70:8d:
c9:37:4b:8a:df:13:4c:44:c2:4b:27:62:d4:20:5d:78:29:ad:
fa:7c:4c:1b:6e:6f:35:59:4a:5a:84:04:45:56:26:73:3e:02:
e2:16:15:b3
見ての通りIssuerとSubjectが同じである。
自己署名証明書ってやつですね。
コレでCAの証明書生成は終わりです。
次はユーザー用の証明書を作成する
まずユーザー用の秘密鍵の生成
$ openssl genres -out private.key
証明書署名要求の作成
$ openssl req -new -key private.key -out x509.csr
証明書署名要求の内容確認
$ openssl req -text -noout -in x509.csr
Certificate Request:
Data:
Version: 0 (0x0)
Subject: C=JP, ST=Tokyo, O=gucchisk, CN=gucchi.info
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:c1:88:ef:3c:a4:c7:59:3f:00:88:39:20:37:19:
8d:18:0e:ee:3e:2e:1d:ec:78:90:c5:31:a0:f6:6d:
4d:8c:22:ba:94:05:f5:da:9a:94:65:86:66:16:9a:
2c:cb:0f:3f:ac:66:fd:a5:3b:0f:06:fe:5c:02:a5:
6c:13:5d:50:ad:65:05:b4:43:26:04:8a:34:69:68:
a7:29:c2:f6:a0:05:4d:a0:23:bf:05:70:70:5f:27:
d1:d3:b9:b0:ab:51:1d:bd:62:a3:27:9a:a2:0c:ab:
c5:23:79:d7:c0:69:1d:77:23:34:f4:30:e2:17:28:
21:ed:84:6b:55:40:99:f6:40:aa:79:14:86:71:b6:
40:45:bf:3b:89:c8:b0:d0:20:b6:c1:0a:8f:39:9f:
a6:4f:06:11:22:db:0d:cc:8b:8b:44:46:74:61:88:
7b:c8:8c:11:bb:f5:f4:ab:ad:98:90:8e:0c:0d:21:
10:5f:62:97:83:df:94:ca:19:ee:1b:25:5a:cc:33:
ca:b7:f0:63:35:96:6e:9e:6d:56:a6:4d:ca:6d:9c:
ca:f0:8a:81:33:13:04:44:bb:38:fd:d5:fa:76:c1:
75:57:6b:8a:a6:c6:6d:04:c1:ba:5e:6c:c3:ea:db:
8e:2a:84:9b:5e:d8:2e:a8:81:44:af:60:67:7d:83:
07:09
Exponent: 65537 (0x10001)
Attributes:
a0:00
Signature Algorithm: sha256WithRSAEncryption
44:fb:d4:1b:30:f6:54:bd:5f:b2:3c:83:b4:e1:0a:37:72:ad:
60:8e:0c:c3:3c:8c:d0:d6:69:3a:1b:4b:19:c8:7f:5f:62:52:
ad:3f:a9:50:61:7c:3e:31:20:99:b5:bd:8b:22:00:37:45:71:
3b:67:1f:48:66:3d:c5:c9:0a:11:d1:02:11:37:28:bc:90:12:
1d:b0:7b:9d:1f:8e:09:d3:1a:78:51:56:c7:bc:4e:24:2b:0e:
33:d5:2a:59:d8:1b:11:52:87:3e:0f:22:91:ca:a3:e9:03:1b:
3b:c4:96:33:0a:25:e5:ee:63:df:e8:27:93:b7:de:9c:0e:31:
41:d1:80:8e:d0:ff:e8:8e:e7:d3:b5:d5:c4:f8:50:e7:99:86:
dc:73:3b:c6:6c:4f:76:02:61:e6:44:49:b1:21:c3:ca:f2:7f:
79:7b:ba:94:47:05:77:1a:48:5c:63:04:b5:07:0c:23:3b:17:
29:75:81:9c:28:1d:0e:df:f0:50:f3:9f:fe:5a:70:9b:99:f8:
6c:9f:7a:b9:29:03:97:6c:50:e2:ec:27:6f:4d:3d:d4:d1:42:
e3:50:72:29:4c:1f:89:9d:12:f2:00:26:35:7c:f4:e1:97:05:
e7:25:e9:51:41:b6:cc:e5:73:6f:79:5f:a2:c5:6c:15:5c:1b:
f4:85:b1:cb
CA秘密鍵で署名した証明書を生成
$ openssl x509 -req -in x509.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -out x509.crt
証明書の内容確認
$ openssl x509 -text -noout -in x509.crt
Certificate:
Data:
Version: 1 (0x0)
Serial Number: 12170714301618296499 (0xa8e7102c92b996b3)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=JP, ST=Tokyo, O=CA, CN=ca.com
Validity
Not Before: Jun 9 15:38:57 2020 GMT
Not After : Jun 9 15:38:57 2021 GMT
Subject: C=JP, ST=Tokyo, O=gucchisk, CN=gucchi.info
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:c1:88:ef:3c:a4:c7:59:3f:00:88:39:20:37:19:
8d:18:0e:ee:3e:2e:1d:ec:78:90:c5:31:a0:f6:6d:
4d:8c:22:ba:94:05:f5:da:9a:94:65:86:66:16:9a:
2c:cb:0f:3f:ac:66:fd:a5:3b:0f:06:fe:5c:02:a5:
6c:13:5d:50:ad:65:05:b4:43:26:04:8a:34:69:68:
a7:29:c2:f6:a0:05:4d:a0:23:bf:05:70:70:5f:27:
d1:d3:b9:b0:ab:51:1d:bd:62:a3:27:9a:a2:0c:ab:
c5:23:79:d7:c0:69:1d:77:23:34:f4:30:e2:17:28:
21:ed:84:6b:55:40:99:f6:40:aa:79:14:86:71:b6:
40:45:bf:3b:89:c8:b0:d0:20:b6:c1:0a:8f:39:9f:
a6:4f:06:11:22:db:0d:cc:8b:8b:44:46:74:61:88:
7b:c8:8c:11:bb:f5:f4:ab:ad:98:90:8e:0c:0d:21:
10:5f:62:97:83:df:94:ca:19:ee:1b:25:5a:cc:33:
ca:b7:f0:63:35:96:6e:9e:6d:56:a6:4d:ca:6d:9c:
ca:f0:8a:81:33:13:04:44:bb:38:fd:d5:fa:76:c1:
75:57:6b:8a:a6:c6:6d:04:c1:ba:5e:6c:c3:ea:db:
8e:2a:84:9b:5e:d8:2e:a8:81:44:af:60:67:7d:83:
07:09
Exponent: 65537 (0x10001)
Signature Algorithm: sha1WithRSAEncryption
a7:a7:14:04:53:d7:68:cb:fd:32:cb:3a:8d:6d:6c:5a:1f:8f:
58:04:0d:9d:ad:c0:2d:d6:79:4e:90:3f:97:29:6a:80:51:d1:
04:d6:27:67:62:d9:21:0e:0d:66:d6:0b:89:e3:f5:98:c8:7e:
0a:57:be:40:6c:bc:df:65:ac:32:59:ea:46:30:7b:96:ee:bc:
bd:24:d3:df:6c:4c:56:3e:29:7c:db:5d:ed:93:19:3a:9d:12:
6a:4e:81:53:2d:f0:24:25:3b:f3:bc:1b:b4:0d:31:ed:a8:48:
b5:45:d1:59:3b:50:23:ef:a0:25:90:40:ee:4e:af:43:54:40:
dc:1b:33:05:aa:e1:5f:17:8c:ba:e2:22:54:d4:7c:07:2f:c9:
15:24:a8:3f:9d:6b:7e:a0:05:bd:0f:00:0e:82:d3:83:2b:9d:
b6:a2:2b:10:b2:42:65:ee:6b:36:81:a9:3f:f5:4b:2d:3a:ee:
46:03:b3:d1:c7:e6:7f:e4:7c:97:aa:cc:7f:50:ff:c8:95:bc:
6d:84:a7:ff:17:ff:2b:60:2e:3d:e2:93:8b:df:5d:c3:24:a8:
f4:e8:16:01:8a:43:95:e6:7c:87:f0:ba:4d:9a:b3:81:b9:50:
46:33:d0:f8:1e:98:6e:01:9b:7d:2a:91:1c:e7:82:4a:12:74:
ef:65:ef:3a
SANSを設定した証明書を作る場合はこちら
$ openssl x509 -req -in x509.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days -out x509_sans.crt -extfile sans.txt
sans.txtの中身はこんな感じ
subjectAltName = DNS:gucchi.info, DNS:*.gucchi.info
証明書の内容確認
$ openssl x509 -text -noout -in x509_sans.crt
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 12170714301618296498 (0xa8e7102c92b996b2)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=JP, ST=Tokyo, O=CA, CN=ca.com
Validity
Not Before: Jun 9 15:37:20 2020 GMT
Not After : Jun 9 15:37:20 2021 GMT
Subject: C=JP, ST=Tokyo, O=gucchisk, CN=gucchi.info
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:c1:88:ef:3c:a4:c7:59:3f:00:88:39:20:37:19:
8d:18:0e:ee:3e:2e:1d:ec:78:90:c5:31:a0:f6:6d:
4d:8c:22:ba:94:05:f5:da:9a:94:65:86:66:16:9a:
2c:cb:0f:3f:ac:66:fd:a5:3b:0f:06:fe:5c:02:a5:
6c:13:5d:50:ad:65:05:b4:43:26:04:8a:34:69:68:
a7:29:c2:f6:a0:05:4d:a0:23:bf:05:70:70:5f:27:
d1:d3:b9:b0:ab:51:1d:bd:62:a3:27:9a:a2:0c:ab:
c5:23:79:d7:c0:69:1d:77:23:34:f4:30:e2:17:28:
21:ed:84:6b:55:40:99:f6:40:aa:79:14:86:71:b6:
40:45:bf:3b:89:c8:b0:d0:20:b6:c1:0a:8f:39:9f:
a6:4f:06:11:22:db:0d:cc:8b:8b:44:46:74:61:88:
7b:c8:8c:11:bb:f5:f4:ab:ad:98:90:8e:0c:0d:21:
10:5f:62:97:83:df:94:ca:19:ee:1b:25:5a:cc:33:
ca:b7:f0:63:35:96:6e:9e:6d:56:a6:4d:ca:6d:9c:
ca:f0:8a:81:33:13:04:44:bb:38:fd:d5:fa:76:c1:
75:57:6b:8a:a6:c6:6d:04:c1:ba:5e:6c:c3:ea:db:
8e:2a:84:9b:5e:d8:2e:a8:81:44:af:60:67:7d:83:
07:09
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:gucchi.info, DNS:*.gucchi.info
Signature Algorithm: sha1WithRSAEncryption
90:a6:d9:91:29:87:fb:46:32:b4:8a:77:a0:9c:c1:a7:38:ce:
ea:4a:9d:aa:0a:5a:21:7f:59:a9:ce:89:7a:e3:03:2e:18:07:
67:d2:f8:19:10:4b:7f:9e:2e:ba:d9:ba:ae:14:a0:9f:fc:e3:
08:8b:87:37:f3:6b:80:de:75:a8:43:cd:3d:b1:50:27:be:05:
0a:20:3c:2e:cf:2c:d0:91:43:47:3b:61:c8:d6:46:8c:a0:6f:
40:06:4e:f5:27:f4:6d:00:26:a0:dd:65:a8:30:17:d1:11:87:
24:2e:76:c9:9a:7e:fa:67:2f:52:79:a4:39:6b:53:0e:a1:af:
10:2a:ef:22:75:14:ce:e8:b7:ee:ac:bb:a9:18:5a:e1:44:48:
02:b5:63:e5:3b:bf:3e:92:37:3a:c2:e4:42:e9:76:24:79:ac:
c0:f1:95:da:10:44:69:0c:32:d4:ec:e6:86:40:1d:84:2c:53:
4e:6f:29:62:16:19:dd:38:0c:1d:08:44:8c:8a:d2:8c:d0:3e:
26:cc:67:6f:61:1b:8e:6a:53:b2:20:fc:2e:d1:36:66:7c:39:
0b:cf:cb:9d:e0:e6:a7:ac:bc:7f:f8:de:34:92:97:32:09:86:
30:d5:93:49:20:22:b1:3e:f6:f8:47:9c:27:f8:f9:dd:b0:78:
d8:74:72:39
こんな感じでX509v3 extensions以下にSANsが設定されている
最後に証明書の署名を検証する
$ openssl verify -CAfile ca.crt x509.crt
x509.crt: OK
OK!!(当たり前)
golangでX.509証明書をゴニョゴニョ
やっとここからgolangで作成した証明書を見ていく
もちろん検証も行う
まずはCAの公開鍵証明書の登録
ファイルを読み込んで証明書のpoolに加える(登録)
f, err := os.Open("ca.crt")
b, err := ioutil.ReadAll(f)
pool := x509.NewCertPool()
ok := pool.AppendCertsFromPEM(b)
次に検証したい証明書を読み込む
f, err = os.Open(certFile)
b, err = ioutil.ReadAll(f)
block, _ := pem.Decode(b)
cert, err := x509.ParseCertificate(block.Bytes)
証明書プールへの追加はPEM用のインターフェイスがあったが、直接PEMフォーマットからx509.Certificate型にするインターフェイスはないのでpemモジュールを使って一度pem.Blockにしてからx509.Certificateにする。
そして検証
hostname := "gucchi.info"
opts := x509.VerifyOptions{
Roots: pool,
DNSName: hostname,
}
if _, err := cert.Verify(opts); err == nil {
fmt.Println("Verify: OK")
} else {
fmt.Printf("Verify: NG, %s\n", err.Error())
}
x509.VerifyOptionsに必須のRootsプロパティに先程の証明書プールをセットし、ホスト名もチェックしたければホスト名をDNSNameに設定する。
そしてcert.Verify(opts)
成功ならばnilが返ってきて、失敗ならば理由が入ったerrorが返ってくる。
ちなみに期限切れの証明書だった場合はこんな感じに表示される。
Verify: NG, x509: certificate has expired or is not yet valid: current time 2020-06-12T23:14:49+09:00 is after 2020-06-10T15:39:58Z
VerifyHostname()はホスト名のチェックのみで署名の検証を行わない。
SANsが設定されている場合はCommonNameに適したホスト名かチェックする。
SANsが設定されていない場合はCommonNameは無視してSANsの設定に適したホスト名かチェックする。
X.509証明書の扱いはこんな感じ。
結構簡単にできますね。
今回のソースコードはこちらに置いておきます
https://github.com/gucchisk/go-samples/tree/master/x509cert