ハトネコエ Web がくしゅうちょう

プログラミングやサーバー関連、チームマネジメントなど得た技術のまとめ

gRPCのクライアント実装でサーバー証明書の検証を入れる方法

このあいだの gRPC の記事の「失敗5」の項で

サーバー側の証明書の正当性を検証するステップはスキップするという方向の実装

にしたのですが、サーバー証明書の検証をおこなう方法がわかったので書いておきます。

なお、今回言う「サーバー証明書」というのは、
自分の運営するドメインに対して取得した X.509証明書 の意味で捉えてください。
よく言う「SSL証明書」ですね。(現在はSSL通信ではなくTLS通信だけど……

方法1: サーバー証明書を用いる

Let's Encrypt での認証後、生成されるサーバー証明書 cert.pem をサーバーから持ってきて、
それを証明書検証に用いることも可能です。
……が、これは Let's Encrypt の仕様上、認証後から3ヶ月で使えなくなってしまうものです。

更新のたびにサーバーから取りに行くのは現実的ではなさそうです。

方法2: ルート証明書を用いる

Let's Encrypt で SSL/TLS 化した場合、それはちゃんと認証局によって認証されたものですから、
ルート証明書 を用いて サーバー証明書の正当性の検証 がおこなえます。

これについて詳しく書きます。
ドメインを認証しサーバー証明書を発行したのが、中間認証局「Let's Encrypt Authority X3」だとします。
その中間認証局認証局として承認したのはルート証明局「DST Root CA X3」です。

ルート証明書というのは、このルート証明局の持つ公開鍵情報を持っています。

クライアントはサーバーから、サーバー証明書の正当性を検証するため、
サーバー証明書や中間証明書を受け取ります。

サーバー証明書や中間証明書は X.509 の形式であり、その中にはその証明書を発行した認証局情報や、
証明書の正当性を検証するための Signature 情報を含みます。

クライアントは、認証局情報を元にその認証局の公開鍵を確認し、
証明書の Signature を公開鍵で復号したものと、証明書自身を使ってハッシュ化した値を比較。
一致するならば、それは正当な証明書と認定されます。

参考: PKI(後編)---X.509証明書とPKIの仕組み(3ページ目) | 日経 xTECH(クロステック)

この計算をおこなうために、ルート認証局の公開鍵情報が必要なのです。

で、実は gRPC のリポジトリにそのルート証明書が置いてあります!

grpc/roots.pem at master · grpc/grpc · GitHub

README によると、https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins にある certdata.txt から
https://github.com/agl/extract-nss-root-certs を使って作られているので、
個人で生成し直すことも可能そうです。

特に古くなさそうであれば、そのままダウンロードして使ってみましょう。

実装例

ほとんど前回の記事と同じで、

creds := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})

certFile := build.Default.GOPATH + "/src/google.golang.org/grpc/roots.pem"
creds, err := credentials.NewClientTLSFromFile(certFile, "")

になったくらいの差です。(roots.pem は grpc ディレクトリ以下に入れてみました)

なお、roots.pem の情報を見ると

openssl x509 -text -fingerprint -noout -in roots.pem

# 中略
Validity
    Not Before: Sep  1 12:00:00 1998 GMT
    Not After : Jan 28 12:00:00 2028 GMT

と書いてあるので、
2028年1月28日以降も同様のルート証明書を使い続けることは出来なさそうなので注意です。

おまけ

特定のドメインに関する中間認証局とルート認証局を確認するコマンド

openssl s_client -showcerts -verify 5 -connect yahoo.co.jp:443 < /dev/null

verify depth is 5
CONNECTED(00000003)
depth=2 C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root
verify return:1
depth=1 C = JP, O = "Cybertrust Japan Co., Ltd.", CN = Cybertrust Japan Public CA G3
verify return:1
depth=0 C = JP, ST = Tokyo, L = Chiyoda-ku, O = Yahoo Japan Corporation, OU = EDGE_20190131, CN = *.yahoo.co.jp
verify return:1
---
Certificate chain
 0 s:/C=JP/ST=Tokyo/L=Chiyoda-ku/O=Yahoo Japan Corporation/OU=EDGE_20190131/CN=*.yahoo.co.jp
   i:/C=JP/O=Cybertrust Japan Co., Ltd./CN=Cybertrust Japan Public CA G3

# 以下略