🍚

TLSの基本を理解したい(よくあるnginx.confを例に)

2024/04/12に公開

みなさん、どうやって nginx.conf を書いてますか。
私は DigitalOcean のジェネレーターを使う方法が最も好きです。
https://www.digitalocean.com/community/tools/nginx?global.app.lang=ja

さてここで、TLS関係の設定が多数生成されますよね。でも、それぞれ何を設定しているのでしょうか?

http {
    # SSL
    ssl_session_timeout    1d;
    ssl_session_cache      shared:SSL:10m;
    ssl_session_tickets    off;

    # Diffie-Hellman parameter for DHE ciphersuites
    ssl_dhparam            /etc/nginx/dhparam.pem;

    # Mozilla Intermediate configuration
    ssl_protocols          TLSv1.2 TLSv1.3;
    ssl_ciphers            ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

    # OCSP Stapling
    ssl_stapling           on;
    ssl_stapling_verify    on;
    resolver               1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
    resolver_timeout       2s;
}

server {
    # SSL
    ssl_certificate         /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
}

今日は、いつもおまじないのように入れているこの設定について、各行が何を意図しているのかまとめることで、TLSをより深く理解してみようと思います。

基本的なハンドシェイクとセッションキャッシュ

    ssl_session_timeout    1d;
    ssl_session_cache      shared:SSL:10m;

TLSは、実際の通信開始までにハンドシェイクが必要です。この章では、ハンドシェイクの仕組みについて説明しつつ、セッションキャッシュについても触れたいと思います。

そもそもハンドシェイクでは、クライアント・サーバー間で往復の通信が発生します。この通信遅延はバカにならず、TLS通信のパフォーマンスに影響します。対処として真っ先に思いつくことは、HTTPの Connection: Keep-alive を使い、そもそもセッションが切れないようにすることです。しかしそれ以外にも、一度切れたセッション情報の一部を使い回す TLS session resumption により、往復する回数を減らすことができます。章冒頭のnginxの設定は、セッション情報をキャッシュし、かつ全てのワーカープロセスでそのキャッシュを共有する設定です。具体的には、過去のセッション情報を Session ID または Session Ticket という情報をキーにキャッシュしておきます。

さて、端的にTLSの仕組みを言うと、TLSでは、

  1. 利用するTLSバージョンや暗号方式のネゴシエーション
  2. クライアントはサーバー証明書を検証してサーバーを信頼できるかどうかを判定
  3. 信頼する場合、「すごくよく考えられた方法」で、サーバーとクライアントで同じ共通鍵を算出

します。そして、3. で得られた共通鍵による共通鍵暗号を用いて、HTTPの通信を暗号化します。このように、暗号化通信に至る前に、いくつかのハンドシェイクが発生して通信遅延が発生しますし、クライアント・サーバーの処理負担も発生します。

特に後者は公開鍵暗号方式を使いますが、公開鍵暗号は共通鍵暗号に比べて非常に重たい処理のため、処理負担が大きいです。互いに共通の共通鍵を生成する「すごくよく考えられた方法」のことを鍵交換といいますが、セッションキャッシュによって鍵交換の負担を減らせるのであれば、パフォーマンス上のメリットになるわけです。ただ、セッションキャッシュといっても、HTTPの通信を暗号化するための共通鍵そのものを使い回すわけではない ということには注意してください。詳しくは以降で説明します。

TLSハンドシェイク

まず、鍵交換を置いておいて、改めてTLSのハンドシェイクを理解しましょう。本記事構成の都合上、しばらくはTLS1.2を前提に解説します。TLS1.3は全くの別物ですから気をつけてください。

まず、Session resumptionをしない場合のハンドシェイクは以下のようになります。

Session resumptionをしない場合のハンドシェイク

      ClientHello                  -------->
                                                      ServerHello
                                                     Certificate*
                                               ServerKeyExchange*
                                              CertificateRequest*
                                   <--------      ServerHelloDone
      Certificate*
      ClientKeyExchange
      CertificateVerify*
      [ChangeCipherSpec]
      Finished                     -------->
                                               [ChangeCipherSpec]
                                   <--------             Finished
      Application Data             <------->     Application Data

以後の図も全て、左がクライアント・右がサーバーです。まずクライアントがClientHelloを送り、それにサーバーがServerHelloなど必要な情報を応答します。この時点でクライアントにはサーバー証明書が送信されます。その後、様々な処理が続き、ChangeCipherSpec以降でやっと共通鍵による暗号化が始まります。Application Dataの内容が共通鍵で暗号化される内容で、HTTPSの場合は HTTP 1.1 GET / のようなメッセージのやりとりが暗号化されます。

さて、ClientHelloではSession IDが送信されるのですが、通常はSession IDが0です。これはすなわちセッションキャッシュがない場合で、新規セッションとして上のとおりにハンドシェイクが進みます。では、もしSession IDが0でなく、クライアントとサーバー側で同じSession IDのセッションを記録しているとすると、どうなるのでしょう。

Session resumptionをする場合のハンドシェイク

      ClientHello                   -------->
                                                       ServerHello
                                                [ChangeCipherSpec]
                                    <--------             Finished
      [ChangeCipherSpec]
      Finished                      -------->
      Application Data              <------->     Application Data

大幅にメッセージが削減できました。削減できたメッセージを見てみます。例えば、Certificate(ServerCertificate)が省略されていますが、これはサーバー証明書や中間証明書の送信を行うメッセージです。通常、クライアントはサーバー証明書を検証し、自身のルート証明書からチェーンできるかどうかを確認しますが、この工程が省略できるというわけです(検証については本記事の後半で述べたいと思います)。

また、例えば ClientKeyExchangeは、共通鍵を作るための鍵交換に必要なやりとりで、これも省略されました。ここについては次の節で詳しく見ていきましょう。

共通鍵の生成と必要な情報の交換

まず、古典的なRSAによる鍵交換を前提として説明します(また、前の節から引き続き、TLS1.2を前提とします)。そのうえで、Application Dataの暗号通信に使う共通鍵を作成する手順は以下のとおりです。


RSAによる鍵交換アルゴリズムの概要(オレンジ背景は秘匿すべき情報)

  1. ClientHello: クライアントはサーバーにランダム値(クライアントランダム)を送ります。
  2. Certificate(ServerCertificate): サーバーはクライアントにランダム値(サーバーランダム)と、サーバーの公開鍵を送ります。
  3. ClientKeyExchange: クライアントはプリマスタシークレットを生成し、サーバーの公開鍵で暗号化してサーバーに送ります。
  4. サーバーは自身の秘密鍵でプリマスタシークレットを復号します。
  5. クライアント・サーバーの両方は、クライアントランダム・サーバーランダム・プリマスタシークレットが入手できました。疑似乱数関数によりマスタシークレットを生成する ←この時点でクライアント・サーバーの両方で共通のマスタシークレットを入手
  6. マスタシークレットから必要なキーブロックを計算し、これを基にApplication Dataの暗号通信に使う共通鍵を取り出し、利用します。

繰り返しですが、Application Dataの暗号通信に使う共通鍵そのものをやりとりするわけでは決してないという点に注意してください。共通鍵は互いに計算しますが、その計算結果が同じになるように必要な情報をやりとりする、というのが正しいです。

さて、Session resumptionを使う場合はどうなるのでしょうか。答えは、プリマスタシークレットがキャッシュされるです。上の手順でいうと、2. のうちサーバー公開鍵送信、3.と4.の全てが不要となります。プリマスタシークレットの暗号化・復号が不要になります[1]し、そもそもクライアントサーバー間の往復数も削減される、というわけです。

ここで、クライアントランダム・サーバーランダムはUnixtimeを基とした乱数のシード値に相当しますので、毎回変わります。仮にSession resumptionをする場合でも、このシード値が必ず毎回変わることで、結果として実際の暗号通信に使われる共通鍵は毎回変わります。セッションの再利用による高速化と、ある程度のセキュアさを両立しているというわけです。

Diffie-Hellmanによる鍵交換

    ssl_dhparam            /etc/nginx/dhparam.pem;

Diffie-Hellman暗号(DH暗号) を鍵交換アルゴリズムとして使う設定です。前の節でRSAによる鍵交換について話しましたが、この設定によりDHによる鍵交換(DHEECDHE)ができるようになります。最近のnginxでは、あらかじめ以下のコマンドでDHパラメータを作成する必要があります。

    openssl dhparam -out /etc/nginx/dhparam.pem 2048

実は、前節で紹介したRSA鍵交換は、現在あまり安全ではないと言われています(よく勘違いされますが、RSA暗号が安全でないわけではなく、RSAによる鍵交換が安全ではないだけです)。理由は、RSA鍵交換は前方秘匿性がないアルゴリズムだからです。前方秘匿性とは、攻撃者が過去の暗号化通信を記録してした場合、将来サーバーの秘密鍵を入手できたら、この過去の解読が可能かどうかを表す性質のことです。RSA鍵交換では、サーバーの公開鍵により暗号化されたプリマスタシークレットそのものをやりとりしました。つまり、サーバーの秘密鍵が漏洩すると、プリマスタシークレットが解読できてしまいます。そのため、前方秘匿性がなく、安全ではないということになります。


DHによる鍵交換アルゴリズムの概要(オレンジ背景は秘匿すべき情報)

一方、DHによる鍵交換では、プリマスタシークレットそのものをやりとりしません。サーバーとクライアントが互いに使い捨ての公開鍵・秘密鍵を生成し、ClientKeyExchangeとServerKeyExchangeで公開鍵を交換しあいます。加えて、ServerKeyExhangeでは、後の計算に使う共通パラメータ(g, p)をクライアントに送り共有します。結果、サーバーとクライアントはそれぞれ、自分の使い捨て公開鍵と秘密鍵、相手の使い捨て公開鍵、共通パラメータ(g、p)を知ることができます。そして、DH暗号の数学的性質のおかげで、この情報からすごく頑張って計算すると、互いに全く同じプリマスタシークレットを入手できます(計算内容は私には理解できません・・・)。共通パラメータ(g、p)と公開鍵はいずれも公開情報にしてよいので、秘密鍵が漏れない限りは安全です。

ここで、鍵が使い捨てであることもポイントの一つです。秘密鍵を長期間保存しない=サーバーに侵入されても被害が極小的であると考えられています。

このDHによる鍵交換はECDHEという楕円関数Diffie-Hellman鍵交換が主流です。現代のセキュアなTLSには、事実上ECDHEが必要不可欠となっています。

プロトコルとCipher

    ssl_protocols          TLSv1.2 TLSv1.3;
    ssl_ciphers            ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

プロトコルバージョン

TLSのバージョンは現在TLS1.2とTLS1.3だけがセキュアです。後は全て脆弱です。さて、TLS1.3は、1.2までと比べ、バージョンのネゴシエーション方法とハンドシェイクがどちらも全く異なります。理解に必要なので、まずはTLS1.3と他の違いについて説明させてください。

TLS1.3のネゴシエーション

TLS1.2までは、クライアントがClientHelloで使いたいバージョンをVersionフィールドに指定して送り、サーバーがその応答のServerHelloで決定します。もしClientHelloで指定されたバージョンをサーバーが対応していない場合、バージョンを下げることを提案される場合がありました。これをクライアントが許容できる場合はよいのですが、できない場合はAlertを応答して拒否し、通信が失敗します。

一方、TLS1.3では、ClientHelloで使いたいバージョンを最初から全てリスト化して、supported_versions拡張フィールドで送信します。サーバーはその中から利用するバージョンを決定しますので、より早い段階で通信バージョンが決まります[2]

TLS1.3のハンドシェイク

さて、TLS1.3のハンドシェイクを見てみます。

Key  ^ ClientHello
Exch | + key_share*
     | + signature_algorithms*
     | + psk_key_exchange_modes*
     v + pre_shared_key*       -------->
                                                  ServerHello  ^ Key
                                                 + key_share*  | Exch
                                            + pre_shared_key*  v
                                        {EncryptedExtensions}  ^  Server
                                        {CertificateRequest*}  v  Params
                                               {Certificate*}  ^
                                         {CertificateVerify*}  | Auth
                                                   {Finished}  v
                               <--------  [Application Data*]
     ^ {Certificate*}
Auth | {CertificateVerify*}
     v {Finished}              -------->
       [Application Data]      <------->  [Application Data]

TLS1.3では、RSAによる鍵交換は禁止されており、主にECDHE鍵交換が使われます。ECDHE鍵交換では、前述の通りプリマスタシークレットそのものを送ることはなく、サーバーとクライアントが独自に計算したプリマスタシークレットが同じ値になるように必要な情報だけ送り合います。この情報は、TLS1.2では ClientKeyExchange により送信していましたが、TLS1.3では、この鍵交換を ClientHello でまとめて送ってしまう ことにしました。すると、暗号化通信を開始するまでに1往復(1RTT)で済むので、通信遅延が減って効率的です。これを ClientHelloの key_share 拡張と呼びます。

Cipher suite

Cipher suite(暗号化スイート)は、TLSで使う暗号の方式を指定するものです。ここまで説明した鍵交換アルゴリズムや共通鍵暗号方式の他にも指定すべき暗号形式があり、それらの組み合わせのことをCipher suiteと呼びます。

なお、Cipher suiteは、ClientHelloで候補値を提示し、ServerHelloで確定します。このネゴシエーション方法はTLS1.2と1.3で共通ですが、Cipher Suiteの命名規則はTLS1.2と1.3で全く異なりますので注意してください。

命名規則とそれぞれの意味は以下のとおりです。

  • TLS1.2の場合
    TLS_鍵交換アルゴリズム(Kx)_認証(Au)_WITH_共通鍵暗号(Enc)_ハッシュ(Mac)
    例)TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

  • TLS1.3の場合
    TLS_AEAD(Enc+Mac)_ハッシュ(Hash)
    例)TLS_AES_256_GCM_SHA384

名前 意味
Kx / 鍵交換アルゴリズム プリマスタシークレットそのもの、またはその基となるデータのやりとりをする方法。RSA、ECDHEなど。
Au / 認証 サーバーがなりすましでないことを検証するために使う。サーバー証明書の秘密鍵を使ってデジタル署名し、クライアントがサーバーの公開鍵を用いて検証する、公開鍵暗号方式。RSA、ECDSAなど。
Enc / 共通鍵暗号 Application Dataの暗号化につかう、共通鍵暗号方式。AESなど。
Mac / ハッシュ 暗号化データが改ざんされていないことを検証するために使う。SHA-256、SHA-384など。

当然セキュアなCipher suiteを選ぶ必要がありますが、今回の nginx.conf では、MozillaのIntermediate configurationで設定されるCipher suiteがデフォルトでリストアップされるようです。普通は変えなくてよいと思います。nginxに関する気をつけたい点として、TLS1.3のCipher suiteはこの ssl_ciphers では指定できない(自動で有効になるはず)です。そのため、この ssl_ciphers オプションでは、TLS1.2のCipher suiteだけ指定するはずです。最新のnginxの仕組みに詳しくないので、間違っていたら教えてください。

さて、続きの節で、ここまで説明していない暗号化の仕組みに触れたいと思います。

TLS1.3における鍵交換アルゴリズムと認証

TLS1.3とそれ以前の一番の違いは、Cipher suiteで鍵交換アルゴリズム(Kx)と認証(Au)を指定しないことです。理由は、それらは Cipher suite として交換する必要がなく、代わりに別の拡張フィールドでネゴシエーションするようになったからです。具体的には、鍵交換アルゴリズム(Kx)は supported_groups 拡張、認証(Au)は signature_algorithms 拡張でネゴシエーションします。その結果、TLS1.3ではCipher Suiteが整理され、以下の5つだけに集約されました。

  1. TLS_AES256_GCM_SHA384
  2. TLS_AES128_GCM_SHA256
  3. TLS_CHACHA20_POLY1305_SHA256
  4. TLS_AES128_CCM_SHA256
  5. TLS_AES128_CCM_8_SHA256

現実的なOSやブラウザでは、上3つ(更に言うと1. と3.のみ)が使われることが多い印象です。

ハッシュ、改ざん検知

ハッシュは、TLSメッセージの改ざんがないことを保証するため使われます。TLS1.2までは、HMAC(Hash based MAC)方式、すなわち送信データと共通鍵を合わせてハッシュ関数に入れることでメッセージ認証コードを作り、暗号化データと併せて送ることで、受信側で検証を実現していました。ハッシュ関数はその引数が1文字でも異なれば違う値が算出されますので、受信側でもハッシュ計算をすれば改ざんが検知できるというわけです。

TLS1.3では共通鍵暗号がこのメッセージ認証コード生成を兼ねるようになり、AEAD(Authenticated Encryption with Associated Data)と呼ばれるようになります。、例えばTLS1.2ではEncとしてAES256が指定されていましたが、TLS1.3で似たようなことをやろうとするとEnc+MacとしてAES256-GCMと呼ばれる方式を指定します。

一方、TLS1.3でもハッシュ方式は指定されています。これは共通鍵生成アルゴリズムの中で使われるハッシュ関数を指定するものであり、改ざんを検知するためのハッシュではありません。

証明書チェーン

    ssl_certificate         /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

証明書ファイル(.pem)ファイルを3つ指定しています。また、Let's Encrypt でサーバー証明書を発行をした場合、/etc/letscnrypt/live/example.com/ には4ファイル生成されている場合もあります。具体的には、上記に追加して cert.pem がありますよね。では、なぜ3つや4つあるのでしょうか?

証明書の発行プロセス

まず、cert.pem というファイルはサーバー証明書本体です。そもそもサーバー証明書とは、認証局(CA) と呼ばれる第三者が、独自の方法でドメイン保有者を認証し、「あなたはexample.comの持ち主ですよ」と証明してくれるものです。

サーバー証明書を認証局に発行してほしい場合、まずは CSR(Certificate Signing Request) という署名リクエストを生成しなくてはなりません。CSRには、サーバーの情報(例: ホスト名、場合によっては組織名情報)が含まれていす。このCSRを認証局に送ると、認証局が彼らの秘密鍵でデジタル署名し、 サーバー証明書(cert.pem) を発行してくれます。

さて、CSR発行時、同時に公開鍵と秘密鍵のペアが生成されます。秘密鍵は、privkey.pem としてサーバー内部に保存されます。一方公開鍵は、実はCSRの中に含める必要があります。また、CSRそのものがインターネット経路上で改ざんされていないことを証明するために、CSRはサーバーの秘密鍵で署名されます。

ここまでの説明をまとめると、サーバー証明書は、 サーバーの公開鍵とサーバーの情報(=ホスト名や組織名)を認証局がデジタル署名したもの であることが分かります。結果として、クライアントから見れば、認証局の公開鍵を使って検証が可能です。結果として、通信相手のサーバーが確かに「サーバーの公開鍵」を使っていて、この「サーバーの情報(=ホスト名や組織名)」に該当することを担保できます。結果として、通信相手のなりすましを防ぐことができます。

信頼チェーン

でもちょっと待ってください。確かに、認証局が署名したものであることは検証できますが、そもそもその認証局を信じてよいのかどうやって判断するのでしょうか。

実は、技術的には誰でも認証局になることができます。仮に私が今から認証局を始めたとして、私の認証局を信じる人は居るでしょうか。たぶんいないですよね。

実際には、TLS通信を行うクライアントには、必ず ルート証明書 と呼ばれる信頼できる証明書があらかじめインストールされています。この ルート証明書から署名された証明書は信じる、そうでない証明書は信じない 、というわけです。私の認証局(プライベートCAと呼ぶことがあります)のルート証明書はみなさんのクライアントにインストールされていないはずですから、これは信じるべきではない、と判断できることになります。


証明書チェーン


証明書チェーンの例。zenn.devのサーバー証明書は、IssureがGTS CA 1P5であり、これが中間証明書である。その中間証明書はさらにGTS Root R1というルート証明書にチェーンしており、クライアントにはGTS Root R1を信頼する設定がされている

ただ、この話には続きがあります。ルート証明書がサーバー証明書を署名してくれるのかな、と思うかもしれませんが、現実にはそんなことは絶対ありません。ルート証明書が 中間証明書 を署名、中間証明書がサーバー証明書を署名、という形で、間に必ず1つ中間の証明書を挟みます[3]。中間証明書が必要な理由は様々ですが、ルート証明書の秘密鍵が万が一流出すると大変な事態ですから、そのリスクを抑えるために中間証明書を挟んでいる、というのがよくある説明のようです。

当初の話題に戻ると、chain.pemは中間証明書で、fullchain.pem は サーバー証明書+中間証明書 のファイルでした。なぜまとめたファイルがあるかというと、サーバーはクライアントから ClientHello を受け付けたとき、fullchain.pem 、つまりをサーバー証明書+中間証明書を両方応答する必要があるからです。でないと、クライアントが自身のルート証明書から署名の検証ができませんよね。

RSA署名証明書とECDSA署名証明書

この節は理解を助けるための補足です。

サーバー証明書の発行時、公開鍵と秘密鍵のペアを作り、それに署名をするという話をしました。この署名アルゴリズムは、TLS1.2の認証(Au)で説明したそのものです。

すなわち、TLS1.2では、サーバー証明書の発行時にどのように署名したかどうかで、使える Cipher Suite が制限される、ということになります。ここで、署名方式はRSAとECDSAがあり、CSR発行の時点でどちらかまたは両方(デュアルスタック)の署名方式で発行することになります。よって、例えばサーバー証明書の発行時にRSA署名のサーバー証明書しか発行していない場合、ECDSA署名のCipher Suiteは使えないことになります。そのため、Cipher Suiteの設定時は、自身がRSA・ECDSAのいずれでサーバー証明書を署名したかを確認し、それに対応するもの選択しなくてはなりません(注意: でも、TLS1.3では、Cipher Suiteと認証(Au)は関係がありませんでしたね! ですからこれは、TLS1.2までの話です)。

なお、RSA署名とECDSA署名では、ECDSA署名の方がパフォーマンス面で優位です。現在 Let's encrypt でよく用いられるcertbotではRSA署名のサーバー証明書が標準で発行されてしまいますが、 –key-type ecdsa オプションでECDSA署名のサーバー証明書を発行可能です。なお、クライアント側もECDSA署名への対応は増えてきていますが、まだRSA対応を切って良い段階ではありません。デュアルスタックにできるので、デュアルスタックにしましょう。

OCSP

    ssl_stapling           on;
    ssl_stapling_verify    on;
    resolver               1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
    resolver_timeout       2s;

OCSP(Online Certificate Status Protocol)は、サーバー証明書の失効状態を確認する方法です。サーバー証明書にはかならず有効期限がありますが、秘密鍵の漏洩などなんらかの理由で有効期限前に失効させたいことがあります。もともとは認証局がCRL(Certificate Revocation List)というリストをつくり、クライアントがCRLをチェックし、有効性を確認していました。でも、CRLが膨大になると、クライアント側で検索処理の時間がかかるという課題がありました。

OCSPはその課題を解決するものです。OCSPレスポンダと呼ばれるサーバーに証明書の失効状態を問い合わせすると、その失効有無が応答されます。しかし、クライアントから直接OCSPレスポンダにアクセスすると、クライアントは「Webサーバー」「OCSPレスポンダ」の両方にアクセスしなくてはならず、非効率です。併せて、OCSPレスポンダ側は、クライアントのIPアドレスとアクセス先ホスト名を知ることができてしまう、というプライバシーの問題も懸念されました。


OCSP stapling有無での動作差分

そこで、OCSP stapling という方法が考えられました。OSCP staplingは、サーバー側でOSCPレスポンダにアクセスし、自身のサーバー証明書と同時に失効情報を送ってしまおうという方法です。OSCPレスポンダの情報は一定期間であればWebサーバー側でのキャッシュが認められており、パフォーマンスの懸念もほとんどありません。なお、当然失効情報の偽装を防ぐ必要がありますが、これはデジタル署名により担保されています。


zenn.devのOCSPレスポンダ

ちなみに、OSCPレスポンダの宛先情報は、サーバー証明書のAIA(Authority Information Access)というフィールドに記載されます。これは通常ホスト名で書かれており、この名前解決をしてHTTP(Port 80)でアクセスをします。そのために nginx.conf ではフルリゾルバを指定していたのですね。

Reference

脚注
  1. 公開鍵暗号方式の処理は共通鍵暗号方式の処理に比べて重たく、時間がかかります。そのため、公開鍵暗号方式の処理の回数を減らすというのは非常に重要です。 ↩︎

  2. TLS1.3であっても、Versionというフィールドは残りますが、後方互換性のために1.3ではなく1.2が入ります。TLS1.3はsupported_versionsしか見ない、TLS1.2以下はVersionフィールドしか見ない、という訳ですね。ちなみに、ClientHelloは暗号化されませんので、Wiresharkで比較的簡単に見ることができますから、余裕のある方はopensslコマンドで送信したClientHelloを確認してみるとよいと思います。 ↩︎

  3. 現実にはクロスルート証明書という証明書が使われる可能性があり、その場合もう1つ挟む可能性があります。この記事ではクロスルート証明書については扱わないものとします ↩︎

Discussion