TLS における認証のメモ

はじめに

TLS において、どうやってサーバ認証しているのかを忘れがちなのでまとめ直してみる。

TLS のサーバ認証

サーバ側は、サーバ証明書と秘密鍵をあらかじめ準備する必要がある。 TLS 通信では、サーバ側はサーバ証明書を送り、クライアント側で証明書を検証することで正しい通信先であることを確認する(TLS の真正性)。

検証の概要

クライアントは送られてきた証明書が、クライアントの CA バンドル(トラストストア)からトラストチェーンを構築できるか検証する。 また、有効期限や OCSP ステープリングによる失効確認も含まれる。

ただし、証明書自体は、機密情報でもなんでもないので、別途、通信先が正式なサーバの証明書の保持者であることを確認する。 言い換えると、証明書に対応する秘密鍵を持っていることを確認する(例えば、秘密鍵で署名された何かを、証明書の公開鍵で検証する)。 この検証は TLS のフローに含まれている

TLS1.2

RSA による暗号化

TLS では、クライアントとサーバで共通鍵(プレマスターシークレット)を作成するが、その方法は Cipher Suite で決定する。 RSA の鍵交換を選択すると、クライアントが生成したプレマスターシークレットをサーバ証明書の公開鍵で暗号化する。 サーバ側が秘密鍵を持っている場合のみ復号でき、共通鍵を共有できるため、前述した 「通信先が正式なサーバが証明書の保持者であること」を確認できる。 復号できなければ、Finished メッセージ等の検証ができないので、エラーとなる。

PFS

現在一般的な PFS を満たす鍵交換では、エフェメラルな鍵交換である DHE や ECDHE を使用する。 この場合、ServerKeyExchange と ClinetKeyExchange により DH パラメータを交換し共通鍵を生成する。 ServerKeyExchange には次のように署名が含まれる。この署名がサーバの秘密鍵によって行われるので、クライアントはサーバ証明書の公開鍵で検証できる。

なお、ServerKeyExchange が中間者によってリプレイアタックされないように Client/Server Random を含めた署名となっている。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
      struct {
          select (KeyExchangeAlgorithm) {
              case dh_anon:
                  ServerDHParams params;
              case dhe_dss:
              case dhe_rsa:
                  ServerDHParams params;
                  digitally-signed struct {
                      opaque client_random[32];
                      opaque server_random[32];
                      ServerDHParams params;
                  } signed_params;
              case rsa:
              case dh_dss:
              case dh_rsa:
                  struct {} ;
                 /* message is omitted for rsa, dh_dss, and dh_rsa */
              /* may be extended, e.g., for ECDH -- see [TLSECC] */
          };
      } ServerKeyExchange;

クライアント認証

クライアント認証をする際のサーバ側の検証フローは、クライアント側と同じである。 TLS において、クライアントがクライアント証明書に対応する秘密鍵を持っていることは CertificateVerify で証明される。 CertificateVerify は、クライアントの秘密鍵で署名したメッセージなので、受け取ったサーバ側はクライアント証明書の公開鍵で検証できる。

なお、CertificateVerify は、これまでのハンドシェイクメッセージに対する署名となっている点が ServerKeyExchange とは異なる。 これまでのハンドシェイクメッセージには Client/Server Random が含まれるので、こちらもリプレイアタックに耐性がある。

1
2
3
4
5
      struct {
           digitally-signed struct {
               opaque handshake_messages[handshake_messages_length];
           }
      } CertificateVerify;

TLS1.3

TLS 1.3 は PFS のみとなっており、また、鍵交換と秘密鍵の検証はメッセージが分かれている。 サーバ認証およびクライアント認証どちらも CertificateVerify を使用する。 CertificateVerify はこれまでのハンドシェイクメッセージを使用から署名を作成する。

https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.3

1
2
3
4
      struct {
          SignatureScheme algorithm;
          opaque signature<0..2^16-1>;
      } CertificateVerify;
1
 Transcript-Hash(Handshake Context, Certificate)