X509v3 Subject Key Identifier

X509証明書のv3拡張にはSubject Key Identifier (SKI)というものがある。

このSKIはそのままでX509証明書に含まれる公開鍵の識別子である。

$ openssl x509 -text -noout -in server.crt
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 13130459247485585328 (0xb638c31e101f07b0)
    Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN=root ca
        Validity
            Not Before: Aug  4 14:53:26 2020 GMT
            Not After : Aug  4 14:53:26 2021 GMT
        Subject: CN=server
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:18:76:24:20:7e:90:d5:8e:a9:76:3d:1b:0f:56:
                    01:f1:4e:6f:7f:43:71:2c:3f:86:15:45:08:03:2e:
                    d4:2d:d1:12:c9:f1:9e:4f:a6:03:dc:6b:50:ab:a7:
                    c0:4a:62:cc:0f:64:7b:00:fe:9a:d7:84:89:5b:35:
                    e4:e8:c2:50:32
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Subject Alternative Name:
                DNS:localhost
            X509v3 Subject Key Identifier:
                5B:77:97:D1:B9:03:E2:C4:31:F6:84:3F:E6:70:51:BB:86:DD:DD:87
    Signature Algorithm: ecdsa-with-SHA256
         30:46:02:21:00:e0:39:08:21:72:e8:8c:0c:65:5f:e4:34:ed:
         04:bb:69:80:75:e8:d8:05:23:33:e3:6c:27:5d:49:50:a4:45:
         72:02:21:00:af:e8:84:50:95:fc:36:6f:01:13:03:90:f2:f0:
         21:20:cc:0d:42:70:98:c4:af:6a:78:5d:ef:7f:cf:d8:d3:a8

このSKIはopensslコマンドを使用する場合、証明書署名時に以下のような設定のextfileをオプションで与えると書き込まれる

subjectKeyIdentifier = hash

そしてこのhash、ただ公開鍵のハッシュを求めるのかと思ったら違った。。。

OpenSSLの説明(x509v3_config)を見ると2つの設定があるらしい。

  • hashが設定されていたらRFC5258に従って計算したハッシュ値が入る
  • 他の設定(違う文字列?)なら16進の文字列が入る(非推奨)

とのこと

そしてRFC5258には公開鍵からSKIを求める方法は一般的に2つあると書いてある

  • 1つは公開鍵のBIT STRING(excluding the tag, length and number of unused bits)のSHA1ハッシュ
  • もう1つは4bitのタイプフィールドとそれに続く公開鍵のBIT STRINGのSHA1ハッシュの最下位60bit

らしいが、

あくまで一般的な方法の例なので、ユニークな識別子を生成できるなら他の方法でも良い。

ここでopensslのhashという設定は前者になる模様

そんでもってここで言うBIT STRINGとは公開鍵ファイルからタグや長さ等のメタ情報を除いたもの。

これはopensslのasn1parseで求めることができる。

  1. まずパースして構造をチェック
    $ openssl asn1parse -in pub.key
        0:d=0  hl=2 l=  89 cons: SEQUENCE
        2:d=1  hl=2 l=  19 cons: SEQUENCE
        4:d=2  hl=2 l=   7 prim: OBJECT            :id-ecPublicKey
       13:d=2  hl=2 l=   8 prim: OBJECT            :prime256v1
       23:d=1  hl=2 l=  66 prim: BIT STRING
    ここでは23がBIT STRINGのオフセットである(オフセットは鍵のアルゴリズムによって変わる)
  2. BIT STRINGのオフセットを設定してパース
    $ openssl asn1parse -in pub.key -strparse 23 -noout -out pub.bin


これでBIT STRINGが求められたのであとはハッシュ(SHA1)の計算をするだけ

$ openssl sha1 pub.bin
SHA1(pub.bin)= 5b7797d1b903e2c431f6843fe67051bb86dddd87


↓ 証明書のSKI(再掲)

X509v3 Subject Key Identifier:
                5B:77:97:D1:B9:03:E2:C4:31:F6:84:3F:E6:70:51:BB:86:DD:DD:87


ちょっと表現が違いますが、見比べてみると全く同じSHA1の160bitのハッシュ値ですねヽ(・∀・)ノ ワチョーイ♪

DenyHostsでSSHブルートフォースアタックに対抗 CentOS

たまたま/var/log/secureを除いたら怪しいログが!

sshd[4113]: Invalid user shashi from 114.141.167.190 port 57982
sshd[4113]: input_userauth_request: invalid user shashi [preauth]
sshd[4113]: pam_unix(sshd:auth): check pass; user unknown
sshd[4113]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=114.141.167.190
sshd[4113]: Failed password for invalid user shashi from 114.141.167.190 port 57982 ssh2
sshd[4113]: Received disconnect from 114.141.167.190 port 57982:11: Bye Bye [preauth]
sshd[4113]: Disconnected from 114.141.167.190 port 57982 [preauth]
sshd[5949]: Invalid user joerg from 181.49.246.20 port 33874
sshd[5949]: input_userauth_request: invalid user joerg [preauth]
sshd[5949]: pam_unix(sshd:auth): check pass; user unknown
sshd[5949]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=181.49.246.20
sshd[5949]: Failed password for invalid user joerg from 181.49.246.20 port 33874 ssh2
sshd[5949]: Received disconnect from 181.49.246.20 port 33874:11: Bye Bye [preauth]
sshd[5949]: Disconnected from 181.49.246.20 port 33874 [preauth]
sshd[18062]: Invalid user admin from 106.241.33.158 port 23663
sshd[18062]: input_userauth_request: invalid user admin [preauth]
sshd[18062]: pam_unix(sshd:auth): check pass; user unknown
sshd[18062]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=106.241.33.158
sshd[18062]: Failed password for invalid user admin from 106.241.33.158 port 23663 ssh2
sshd[18062]: Received disconnect from 106.241.33.158 port 23663:11: Bye Bye [preauth]
sshd[18062]: Disconnected from 106.241.33.158 port 23663 [preauth]

明らかな攻撃ですね。

何も対策してなかったので対策しないといけないですね。

調べると色々あるようです。
日本以外からのアクセスを拒否するとか、手動で拒否するIPを追加するとか。。。

まあエンジニアなので自動化します!

自動化するソフトとしてはDenyHosts, Fail2banといったところが有名なようです。

DenyHostsはSSH用で拒否するIPをhosts.deny(拒否IPリスト)に追加していくという単純なもの。

Fail2banはiptableベースでSSH以外にも対応しているらしい。

今回はDenyHostsで対応することに。

昔はepelのリポジトリにあったようですが、現在は削除されている模様。

というわけでgithubのリポジトリにrpmがあるのでgithubからインストールします。(2020-07現在の最新版は3.1.2)
https://github.com/denyhosts/denyhosts/releases

$ yum install https://github.com/denyhosts/denyhosts/releases/download/v3.1/DenyHosts-3.1.2-1.noarch.rpm
$ $ rpm -ql DenyHosts
/etc/denyhosts.conf
/usr/local/bin/daemon-control-dist
/usr/local/bin/denyhosts.py
/usr/local/lib/python2.7/dist-packages/DenyHosts-3.1.2.egg-info
/usr/local/lib/python2.7/dist-packages/DenyHosts/__init__.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/__init__.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/__init__.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/allowedhosts.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/allowedhosts.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/allowedhosts.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/constants.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/constants.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/constants.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/counter.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/counter.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/counter.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/daemon.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/daemon.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/daemon.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/deny_hosts.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/deny_hosts.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/deny_hosts.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/denyfileutil.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/denyfileutil.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/denyfileutil.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/filetracker.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/filetracker.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/filetracker.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/lockfile.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/lockfile.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/lockfile.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/loginattempt.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/loginattempt.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/loginattempt.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/plugin.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/plugin.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/plugin.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/prefs.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/prefs.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/prefs.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/purgecounter.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/purgecounter.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/purgecounter.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/python_version.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/python_version.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/python_version.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/regex.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/regex.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/regex.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/report.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/report.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/report.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/restricted.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/restricted.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/restricted.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/sync.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/sync.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/sync.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/util.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/util.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/util.pyo
/usr/local/lib/python2.7/dist-packages/DenyHosts/version.py
/usr/local/lib/python2.7/dist-packages/DenyHosts/version.pyc
/usr/local/lib/python2.7/dist-packages/DenyHosts/version.pyo
/usr/share/man/man8
/usr/share/man/man8/denyhosts.8.gz

これでインストール完了。

本体は/usr/local/bin/denyhosts.pyというpythonスクリプトです。

いちいち起動するのはアホなんでsystemdに登録します。

そこで使用するのが/usr/local/bin/daemon-control-dist

こちらを/etc/init.d以下にコピーします

$ cp /usr/local/bin/daemon-control-dist /etc/init.d/denyhosts

ちょこっと中身も編集します。

###############################################
# Edit these to suit your configuration #
###############################################
DENYHOSTS_BIN = "/usr/sbin/denyhosts.py"
DENYHOSTS_LOCK = "/run/denyhosts.pid"
DENYHOSTS_CFG = "/etc/denyhosts.conf"
PYTHON_BIN = "/usr/bin/python3"

ここのDENYHOSTS_BINが異なるので実際のパスに変更

python3はデフォでは入ってないのでPYTHON_BINをpythonに

編集した結果が以下

###############################################
#    Edit these to suit your configuration    #
###############################################
DENYHOSTS_BIN = "/usr/local/bin/denyhosts.py"
DENYHOSTS_LOCK = "/run/denyhosts.pid"
DENYHOSTS_CFG = "/etc/denyhosts.conf"
PYTHON_BIN = "/usr/bin/python"

これでOK.

余談ですが、実はこのコードの下にOS判定のコードがあるんですが、いけてなくてうまくCentOSだと認識してくれません。
しかも、判定されても/usr/bin/denyhosts.pyに設定されるので使えない。。。

これで使えると思いきや、実はさっきyumでインストールしたDenyHostsのライブラリにパスが通ってない。。。
pythonのモジュール検索パスに含まれていないところにインストールされています。。。

というわけで、検索パス上にリンクを作ります(もちろんパスを追加してもOK)

$ ln -s /usr/local/lib/python2.7/dist-packages/DenyHosts /usr/lib/python2.7/site-packages/DenyHosts


そして、DenyHostsで使用しているpythonライブラリをインストールします。
基本的にはipaddrだけだと思います。

$ pip install ipaddr




そして最後に肝心のdenyhostsの設定です。
/etc/denyhosts.confを編集します

デフォルトでは監視するログファイルが/var/log/auth.logになっているので/var/log/secureに変更します

SECURE_LOG = /var/log/secure

これでsystemdでdenyhostsを起動すれば動くはず。

$ sudo systemctl start denyhosts


ちゃんと動いていれば、しばらく攻撃を受け続けた後に/etc/hosts.denyにIPが追加されているはず。

我が家では1日で約300件追加されてました。。。(それでもまだ攻撃を受けている。。。)

ちなみに主な設定項目の意味はこちら

SECURE_LOG 監視するsshdのログファイル
HOST_DENY アクセス拒否リスト
PURGE_DENY アクセス拒否リストにから削除する経過時間 空の場合永遠
BLOCK_SERVICE アクセス拒否リストを使用して拒否するサービス
DENY_THRESHOLD_INVALID アクセス拒否リストに入れる存在しないユーザーでのログイン試行回数
DENY_THRESHOLD_VALID アクセス拒否リストに入れるログイン失敗回数
DENY_THRESHOLD_ROOT アクセス拒否リストに入れるrootでのログイン試行回数
IPTABLES iptablesのパス iptablesを使って拒否する場合
DAEMON_SLEEP
DAEMON_PURGE アクセス拒否の解除を判断する間隔 PURGE_DENYが空の場合は無意味
SYNC_SERVER アクセス拒否リストを同期するサーバー 同期機能を使用する場合
SYNC_INTERVAL アクセス拒否リストの同期間隔
SYNC_UPLOAD アクセス拒否リストをサーバーに送るかどうか
SYNC_DOWNLOAD アクセス拒否リストをサーバーから取ってくるかどうか

というわけでグッドラック!

golangとX.509証明書とopenssl

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