背景
セキュリティの重要性が叫ばれる今日この頃です。自分は、証明書という言葉はよく耳にしていましたが、実際にどんな役割を持ち、どのような構造になっているのかは十分に理解できていませんでした。そこで、手元で分解しながら確認してみました。
サーバー証明書って?
各種webページを暗号化された状態で通信する(https通信)時に必要なものです。今このページを見ている時にも使用されています。
参考ページ
目的
サーバー証明書の中身を分解して、サーバー証明書が信頼できるものかを手動で順を追って確認していきます。
この記事では署名部分にフォーカスします。実際の通信に関しては扱いません。
使用する証明書によっては手順や内容などが異なる可能性があります。ご了承ください。明らかに間違っている部分などありましたら指摘頂けるとありがたいです。
環境
Windows + WSL(Ubuntu)で行いましたが、一般的なコマンドなので他の環境でも大丈夫だと思います。
前提知識
- 公開鍵方式暗号(暗号化と復号化で異なる鍵を使用する暗号方式)
- ハッシュ関数(任意のデータから、他と衝突しにくい固定長の値を得る関数。非可逆変換)
- BASE64(バイナリデータを文字列データに変換する処理。可逆変換)
参考ページ
サーバー証明書取得
Chromeでの手順を示しますが、他のブラウザでも取得可能になってると思います。
これで、対象webページが使用しているサーバー証明書が取得できます。
- 任意のhttpsアドレスのwebページにアクセス
- アドレスが表示されている左横のボタンをクリック
- 「この接続は保護されています」をクリック
- 「証明書は有効です」をクリック
- 「詳細」タブを表示
- 「エクスポート」ボタンをクリック
分解&検証(本題)
sample.crtというファイルに保存したと仮定してコマンド例を示します。
先に全体像を記載します。以下手順は使用者の手順になります。Issuer秘密鍵は当然公開されていません。今回の手順でも使用しません。Issuerにより発行された証明書の検証をする流れです。

1. crt形式をder形式に変換
crtファイルは、BASE64エンコードされたpem形式のテキストデータの場合と、バイナリのder形式の場合があります。今回はテキストデータだったので、バイナリのder形式にします。
# ヘッダ除去
sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' sample.crt | sed '1d;$d' > sample.crt_nohead
# 改行除去
cat sample.crt_nohead | sed -z 's/\r\n//g' > sample.crt_nocrlf
# デコード
base64 -d sample.crt_nocrlf > sample.der
2. der形式ファイルの中身をテキスト化しておく
der形式ファイルは、X.509という約束事に基づいて情報を保持しているバイナリファイルです。テキスト化するコマンドもあるので一度出力します。
openssl x509 -inform DER -in sample.der -text -noout > sample.text
# 実はテキストで表示するだけなら前述der形式に変換しなくても直接可能ですが今回は詳細にステップ踏みました
openssl x509 -in sample.crt -text -noout > sample.text
中身を見るだけだと、ここでこの記事が終わりますが、含まれている主要情報を確認します。今回使用する情報の階層は以下の通りになっています。※実際には他の多くの情報が含まれます。
- Data (証明書本体)
- Subject Public Key Info (公開鍵)
- X509v3 extensions
- Authority Information Access (Issuer情報)
- Signature Algorithm (署名アルゴリズム)
- Signature Value (署名データ)
3. 発行者(Issuer)の公開鍵を取り出す
物理文書の「証明書」もそうですが、証明というのは自分自身で行いません。第三者が証明します。その証明の検証を行うのに必要なのが、第三者であるIssuerの公開鍵です。
# Issuer証明書のURLを確認する(出力内容のCA Issuers という表示に続く部分)
cat sample.text | grep -A3 "Authority Information Access"
## 出力例
# Authority Information Access:
# OCSP - URI:http://xxxxxxxxxxxx
# CA Issuers - URI:http://xxxxxxxxxxxx
# Issuer証明書ダウンロード(crtファイル)
curl {確認したIssuer証明書のURL} -o sample_issuer.crt
# Issuer証明書に含まれるIssuerの公開鍵抽出
openssl x509 -in sample_issuer.crt -pubkey -noout > sample_issuer_pub.pem
4. 証明書から「署名対象部分(TBSCertificate=Data)」を抽出
Issuer証明書でなく、検証対象の証明書です。前述の証明書本体(Data)部分に対して署名が行われます。
X.509の約束事はASN.1書式で保存されています。構造を確認し、該当部分だけ抽出します。
# 構造上位置確認(最初のd=1の行の行頭値(開始バイトオフセット)。今回は4)
openssl asn1parse -in sample.crt > sample_asn1_structure.txt
## 出力例(最初の数行)
# 0:d=0 hl=4 l=2163 cons: SEQUENCE
# 4:d=1 hl=4 l=1883 cons: SEQUENCE
# 8:d=2 hl=2 l= 3 cons: cont [ 0 ]
# 10:d=3 hl=2 l= 1 prim: INTEGER :02
# 抽出実行
openssl asn1parse -in sample.crt -strparse {確認した値} -out sample.tbs.der -noout
5. 署名アルゴリズム(Signature Algorithm)を確認
# 2件表示される。今回は両方同じ内容。正式には後ろの方のはず。
cat sample.text | grep "Signature Algorithm"
## 出力例
# Signature Algorithm: sha256WithRSAEncryption
# Signature Algorithm: sha256WithRSAEncryption
今回はsha256WithRSAEncryptionを得ました。
6. 署名値(Signature Value)を取り出す
このコマンドではテキスト化した情報から取り出していますが、本来はX.509形式に基づいてバイナリとして抽出するはずです。
cat sample.text | \
awk '/Signature Value:/,/^$/{print}' | \ # 最後の部分取り出し
sed '1d' | \ # Signature Value 記載部取り出し
tr -d ' \n:' | \ # 空白や改行を削除
xxd -r -p > sample.sign.bin # 16進数ダンプ文字列をバイナリに変換
7. Issuerの公開鍵を使って署名を検証
Issuer の公開鍵を使って署名を検証し、RSA 署名であれば内部的には復号相当の処理が行われます
openssl pkeyutl -verifyrecover -pubin -inkey sample_issuer_pub.pem -in sample.sign.bin -out sample.decrypted_sign.bin
復号された内容はASN.1形式のバイナリです。その中にハッシュ値が含まれています。
openssl asn1parse -in sample.decrypted_sign.bin -inform DER
## 出力例
# 0:d=0 hl=2 l= 49 cons: SEQUENCE
# 2:d=1 hl=2 l= 13 cons: SEQUENCE
# 4:d=2 hl=2 l= 9 prim: OBJECT :sha256
# 15:d=2 hl=2 l= 0 prim: NULL
# 17:d=1 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:{署名値に含まれるハッシュ値}
以下のような情報階層になっているそうです。
- DigestInfo
- AlgorithmIdentifier
- algorithm
- parameters
- digest (今回目的のハッシュ値)
- AlgorithmIdentifier
8. 証明書本体(TBSCertificate=Data)のハッシュ値を算出
署名アルゴリズムに従ってハッシュ値を算出します
openssl dgst -sha256 sample.tbs.der
## 出力例
# SHA2-256(sample.tbs.der)= {証明書本体データから算出したハッシュ値}
9. 比較
大文字小文字の差はあっても7と8で同じ内容になっている事が確認できました。
信頼できるIssuerが提供する公開鍵で署名データが復号できたという事は、Issuerが保持している秘密鍵(Issuerのみ所有)で暗号化した署名データという事になります。署名データに含まれる証明書本体のハッシュ値(Issuerが算出)と、証明書を受け取った側が算出した証明書本体のハッシュ値が同一である事で、内容が改ざんされていない(Issuerが署名したデータと同一である)事が保証されます。
10. おまけ
ハッシュ値の検証(手順7~9)は、以下コマンドでも行えます。
# Issuer公開鍵と署名を使って、証明書本体を使用する
openssl dgst -sha256 -verify sample_issuer_pub.pem -signature sample.sign.bin sample.tbs.der
## 出力例
# Verified OK
証明書チェーンの話
証明書が信頼できる事を、Issuerが証明してくれた事はわかりましたが、そのIssuerが信頼できるかという問題が発生します。この問題に関しては、Issuerが発行している証明書(上記手順3で行ったsample_issuer.crt)を同様にチェックする事で信頼されている事を確認できます。最終的にルート証明書にたどり着きます。ルート証明書は一般的にOSに信頼済みの証明書として組み込まれ検証に使われます。
自己署名証明書
自分でサーバー証明書を作る事も可能です。それを使ってhttps接続のwebサイトを構築する事も可能です。
# 秘密鍵を作成
openssl genrsa 2048 > self_server.key
# CSR を作成
openssl req -new -key self_server.key > self_server.csr
# 自己署名証明書の作成
openssl x509 -days 3650 -req -sha256 -signkey self_server.key < self_server.csr > self_server.crt
# テキスト化
openssl x509 -in self_server.crt -text -noout > self_server.text
しかし、この手順で作成した自己署名証明書(いわゆるオレオレ証明書)では、発行者(Issuer) の情報がありませんでした。前述の手順3が行えない事になります。結果ブラウザでアクセスすると「信頼されていない接続先」などのワーニングが出てくるはずです。
ただ、ルート認証局の証明書、中間証明局の証明書も自分で準備して、OSにインストールしたりして警告出ない様にする事も可能なようです。
たとえば、相手から「この証明書をPCにインストールしてから接続してください」と案内された場合は、目的や発行元を必ず確認しましょう。内容が不明なままインストールするのは避けるべきです。(そもそも、正体不明なファイルをPCにインストールする事自体が危ないですが)
※少なくとも正規の配布元・配布経路・証明書ピンニングなどの確認しましょう!!