1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PQC対応のnginxを使用したレガシーWebサーバーの保護

Last updated at Posted at 2025-10-22

この記事は AIによって生成されたコンテンツを含みます。
(スクショ、コマンド出力、ログ、トレースなどには含みません)

はじめに

量子コンピューターの進化により、従来の公開鍵暗号(RSA や ECDSAなど)が将来的に破られる可能性があると言われています。量子コンピューターの脅威に備えるため、耐量子暗号(PQC: Post-Quantum Cryptography)の導入が現実的な課題になりつつあります。

Open Quantum Safe (OQS) は、耐量子暗号の実装と普及を目指すオープンソース・プロジェクトであり、TLSやSSHなどの主要プロトコルにPQCを組み込むためのツール群を提供しています。

本記事では、OQSが提供するDockerイメージ openquantumsafe/nginx をリバース・プロキシー方式で構成することで、既存のレガシー暗号を使用するWebサーバーを変更せずに、nginxで耐量子TLS通信を構築する方法をご紹介します。

image001.png

トップに戻る

OQS nginx Dockerイメージの概要

このDockerイメージは、OpenSSL と OQS Providerを組み込んだ nginx を含み、耐量子の鍵交換と認証を TLS 1.3で実現します。

  • 非rootユーザー対応:Kubernetesなどの制限環境でも安全に実行可能

  • 耐量子暗号対応:Kyber、Falcon、Dilithiumなど、OQS Providerがサポートするアルゴリズムに対応

  • 基本的に通常のnginxと同じ構成ファイルを使用するため、リバース・プロキシーなど一般的なnginxで実現できる構成を実現可能

トップに戻る

検証環境の概要

今回の検証は、サーバーとなる2台の仮想マシン(Ubuntu 24.04.2 LTS)と、クライアントとなるWindows 11 マシンで行いました。

構成要素の配置は前出の図と同じで、curl がPQC未対応クライアント、Chrome がハイブリッドPQC対応クライアント、nginx がPQC対応リバース・プロキシー、http-https-echo がPQC未対応アプリとなっています。

image002.png

TLS通信のネゴシエーションは目に見えないため、以下の方法で可視化を試みています。

  • nginx稼働マシンからtcpdumpを使ってpcapファイルを取得し、Wiresharkで内容を確認
    (暗号通信の内容を読み取るのが目的ではなく、ネゴシエーションの過程を観察)

  • nginx(リバース・プロキシー構成)のバックエンドに、HTTPリクエストの内容を返すサービスを置き、nginxが挿入したHTTPヘッダー情報を確認

  • Chromeブラウザーの開発者モードを使用し、TLSに関連する情報を表示

トップに戻る

検証環境の実装方法

OQS nginxを通常のdockerコマンドで起動する方法については、openquantumsafe/nginx サイトや oqs-demos/nginx/USAGE.md に詳しく説明されていますので、個人的な好みもあり、今回はdocker composeを使用する方法で行います。

常時稼働させるコンテナーの場合、docker composeは比較的管理しやすいと思います。

トップに戻る

docker composeの使用

使用したdocker-compose.ymlファイルは以下のとおりです。
ドキュメントの記述に合わせてポート番号を4433にしていますが、一般ユーザーからもアクセスしてもらう場合は、443:4433というports定義を使用することになると思います。

services:
  nginx:
    # 使用するDockerイメージ(Open Quantum Safe対応のnginx)
    image: openquantumsafe/nginx:latest
    
    # コンテナー名
    container_name: nginx.server.my.org
    
    # ポートマッピング(ホストポート:コンテナポート)
    ports:
      - "4433:4433"
    
    # ボリュームマウント(ホストパス:コンテナパス)
    volumes:
      # ログディレクトリ
      - ./nginx-logs:/opt/nginx/logs
      
      # PKIディレクトリ
      - ./server-pki:/opt/nginx/pki

      # Nginx 設定ファイルディレクトリ
      - ./nginx-conf:/opt/nginx/nginx-conf

    # コンテナーが停止された場合、手動停止しない限り自動再起動
    restart: unless-stopped

ファイルを置く場所として、oqs-nginxというフォルダーを作成しました。

このフォルダーの中に、コンテナー内部のフォルダーにマッピングする実体として、nginx-logs, nginx-conf, server-pki というフォルダーを作成(mkdir)しています。

トップに戻る

サーバー証明書の作成(自己署名証明書)

nginx が使用するサーバー証明書を作成します。

今回は、ハイブリッドPQCに対応している Chrome を使用して検証を行うため、接続実績のあるサイト:https://test.openquantumsafe.org:6003/ と同じ仕様で、ECDSA(楕円曲線デジタル署名アルゴリズム)を使った証明書を作成したいと思います。

  1. 鍵と証明書を格納するためのフォルダーを作成します。(docker composeの定義とも合わせます)

    mkdir -p server-pki && cd server-pki
    

    ※今回の証明書は従来のアルゴリズムを使用するため、PQC対応ではない openssl コマンドで作成しています。(OpenSSL 3.0.13 30 Jan 2024)

  2. CA鍵と証明書を作成します。

    # CA秘密鍵の作成
    openssl ecparam -name prime256v1 -genkey -noout -out CA.key
    
    # CA証明書の作成(自己署名)
    openssl req -x509 -new -key CA.key -out CA.crt -subj "/CN=PQC Test CA" -days 365
    

  3. サーバー証明書のためのOpenSSL設定ファイルを作成します。

    openssl-san.cnf という名前で、以下の内容を書き込みます。

    [ req ]
    default_bits       = 2048
    distinguished_name = req_distinguished_name
    req_extensions     = v3_req
    prompt             = no
    
    [ req_distinguished_name ]
    CN = ubuntu2.iss.local
    
    [ v3_req ]
    subjectAltName = @alt_names
    
    [ alt_names ]
    DNS.1 = ubuntu2.iss.local
    DNS.2 = localhost
    
    

    ※ブラウザーで Certificate Alternative Name missing の警告が出るのを防ぐために、設定ファイルを使用して subjectAltName を指定しています。PQC検証の動作に影響があるわけではないので、テスト用に警告を無視する想定であれば、次の openssl req コマンドで -config openssl-san.cnf を省略し、代わりに -subj "/CN=ubuntu2.iss.local" を付ける方法もあります。

  4. サーバー鍵と CSR を作成します。

    # サーバー秘密鍵の作成
    openssl ecparam -name prime256v1 -genkey -noout -out server.key
    
    # CSR(証明書署名要求)の作成(SAN付き)
    openssl req -new -key server.key -out server.csr -config openssl-san.cnf
    

  5. サーバー証明書に署名します。

    openssl x509 -req -in server.csr -out server.crt -CA CA.crt -CAkey CA.key -CAcreateserial -days 365 -extfile openssl-san.cnf -extensions v3_req
    

    途中の openssl コマンドには何も応答がないのですが、最後の openssl x509 コマンドには以下の応答がありました。

    Certificate request self-signature ok
    subject=CN = ubuntu2.iss.local
    

トップに戻る

nginx構成ファイルの作成

oqs-demos/nginx/nginx-conf/nginx.conf をベースに、リバース・プロキシーの定義を加えたものを用意します。

# 使用するワーカープロセス数を自動で決定(CPUコア数に基づく)
worker_processes auto;

events {
    # 各ワーカープロセスが同時に処理できる接続数の上限
    worker_connections 1024;
}

http {
    # MIMEタイプの定義ファイルを読み込む(ファイルの種類に応じたContent-Typeを設定)
    include       ../conf/mime.types;

    # デフォルトのMIMEタイプ(未定義のファイルタイプに適用)
    default_type application/octet-stream;
    
    # sendfileを有効化(ファイル送信のパフォーマンス向上)
    sendfile on;

    # Keep-Alive接続のタイムアウト時間(秒)
    keepalive_timeout 65;

    # バックエンドサーバーの定義(リバースプロキシ先)
    upstream backend_servers {
        server 192.168.254.30:8443;
    }

    server {
        # サーバーがSSLで待ち受けるポートとアドレス(4433番ポート)
        listen       0.0.0.0:4433 ssl;

        # アクセスログとエラーログの保存先
        access_log  /opt/nginx/logs/access.log;
        error_log   /opt/nginx/logs/error.log;
        
        # SSL証明書と秘密鍵の指定
        ssl_certificate      /opt/nginx/pki/server.crt;
        ssl_certificate_key  /opt/nginx/pki/server.key;

        # SSLセッションキャッシュの設定(1MBの共有メモリ)
        ssl_session_cache    shared:SSL:1m;

        # SSLセッションのタイムアウト時間
        ssl_session_timeout  5m;

        # 使用するSSLプロトコルの指定(TLS 1.3のみ)
        ssl_protocols TLSv1.3;

        # Open Quantum Safe対応のKEM(鍵共有アルゴリズム)を選択可能 (コメントアウトしていないのでデフォルトを使用) https://github.com/open-quantum-safe/liboqs#supported-algorithms
        # Example (nginxがサポートする文字列の長さに制限があるため注意):
        # ssl_ecdh_curve oqs_kem_default:frodo976shake:frodo1344shake:p256_kyber512:kyber768:kyber1024:ntru_hps2048509:ntru_hps2048677:ntru_hrss701:lightsaber:saber:kyber512:X25519;

        # リバースプロキシの設定(すべてのリクエストをバックエンドに転送)
        location / {
            # バックエンドサーバーへの転送先
            proxy_pass https://backend_servers/;
            
            # クライアント情報をヘッダーに追加(バックエンドでの識別やログに使用)
            proxy_set_header Host ubuntu2.iss.local;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Port $server_port;

            # SSL関連の情報をヘッダーに追加(セキュリティ分析やログ用)
            proxy_set_header X-SSL-Protocol $ssl_protocol;
            proxy_set_header X-SSL-Cipher $ssl_cipher;
            proxy_set_header X-SSL-Ciphers $ssl_ciphers;
            proxy_set_header X-SSL-Curve $ssl_curve;
            proxy_set_header X-SSL-Curves $ssl_curves;
            proxy_set_header X-Client-Cert $ssl_client_cert;
            proxy_set_header X-Client-Verify $ssl_client_verify;

            # バックエンドのSSL証明書検証を無効化(テスト環境などで使用)
            proxy_ssl_verify off;

            # リダイレクトを無効化(バックエンドからのリダイレクトをそのまま返す)
            proxy_redirect off;           
        }
    }
}

ここまでの構成を完了したフォルダー構造は、以下のとおりです。
(テストの都合上、nginx.conf は、複数の実ファイルをシンボリックリンクで切り替えています)

khrz@ubuntu2:~/oqs-nginx$ ls -lR
.:
total 16
-rw-rw-r-- 1 khrz khrz  761 Oct 17 08:29 docker-compose.yml
drwxrwxr-x 2 khrz khrz 4096 Oct 17 08:30 nginx-conf
drwxrwxr-x 2 khrz khrz 4096 Oct 20 23:04 nginx-logs
drwxrwxr-x 2 khrz khrz 4096 Oct 20 23:06 server-pki

./nginx-conf:
total 8
lrwxrwxrwx 1 khrz khrz   16 Oct 17 05:32 nginx.conf -> nginx_local.conf
-rw-rw-r-- 1 khrz khrz 3634 Oct 17 08:30 nginx_local.conf
-rw-rw-r-- 1 khrz khrz 1571 Oct 16 07:08 nginx_testfire.conf

./nginx-logs:
total 16
-rw-r--r-- 1 khrz khrz 8811 Oct 20 22:45 access.log
-rw-r--r-- 1 khrz khrz 1257 Oct 14 09:55 error.log

./server-pki:
total 28
-rw-rw-r-- 1 khrz khrz 583 Oct 20 23:06 CA.crt
-rw------- 1 khrz khrz 227 Oct 20 23:06 CA.key
-rw-rw-r-- 1 khrz khrz  41 Oct 20 23:06 CA.srl
-rw-rw-r-- 1 khrz khrz 280 Oct 20 23:06 openssl-san.cnf
-rw-rw-r-- 1 khrz khrz 623 Oct 20 23:06 server.crt
-rw-rw-r-- 1 khrz khrz 448 Oct 20 23:06 server.csr
-rw------- 1 khrz khrz 227 Oct 20 23:06 server.key

起動方法は以下のとおりです。

  1. コマンドラインで、docker-compose.yml ファイルが置かれているフォルダー(今回は oqs-nginx)に移動(cd)します。

  2. 以下のコマンドで起動します:

    docker compose up -d

トップに戻る

バックエンドWebサーバーの構成

本質的な話ではありませんが、nginx をリバース・プロキシーとして構成する以上、実体となるWebサーバーが必要です。

今回は、nginxがセットするヘッダーの情報を可視化するために、軽量な mendhak/http-https-echo というDockerイメージを利用します。

こちらも docker compose で起動したいと思いますので、以下の docker-compose.yml ファイルを作成します。

services:
  http-echo:
    # 使用するDockerイメージ(HTTP/HTTPSリクエストの情報をJSONで返すエコーサーバー)
    image: mendhak/http-https-echo:31

    # ホストのポート8080と8443をコンテナーの同じポートにマッピング
    # 8080: HTTP用、8443: HTTPS用
    ports:
      - "8080:8080"
      - "8443:8443"

    # コンテナーが停止された場合、手動停止しない限り自動再起動
    restart: unless-stopped

使用方法は以下のとおりです。

  1. 上記 docker-compose.yml ファイルを、任意のディレクトリーに保存します。

  2. コマンドラインで、そのディレクトリーに移動(cd)します。

  3. 以下のコマンドで起動します:

    docker compose up -d

正常に起動すると、通常のブラウザー (または curl) から、稼働マシン上のポート 8080 / 8443 にアクセスできます。

  • http://<稼働マシンのIP>:8080
  • https://<稼働マシンのIP>:8443

トップに戻る

検証結果

以下に検証結果をまとめます。

フローの可視化には、Wiresharkのフローグラフ機能 からエクスポートしたものを利用しています。

ハイブリッドPQCアルゴリズムのTLSネゴシエーション・フロー

以下は、[Chrome] – [OQS nginx] – [バックエンド] 構成における TLSネゴシエーションの様子です。(取得されたpcapからメインのフローをフィルターしたもの)

image003.jpg

ネゴシエーションは2箇所で発生しています。
1つ目は [Chrome] – [nginx] 間、2つ目は [nginx] – [バックエンド] 間 です。

このうち、ハイブリッドPQCアルゴリズムの通信が成立しているのは、1つ目だけです。

Chromeがnginxに送ったClient Helloの抜粋

  • Internet Protocol Version 4, Src: 192.168.254.104, Dst: 192.168.254.31
  • Transmission Control Protocol, Src Port: 56014, Dst Port: 4433, Seq: 1, Ack: 1, Len: 1765
  • Transport Layer Security
    • [Stream index: 0]
    • TLSv1.3 Record Layer: Handshake Protocol: Client Hello
      • Content Type: Handshake (22)
      • Version: TLS 1.0 (0x0301)
      • Length: 1760
      • Handshake Protocol: Client Hello
        • Handshake Type: Client Hello (1)
        • Length: 1756
        • Version: TLS 1.2 (0x0303)
        • Random: 2994d197b09686e07f11415f72eb9993b53b2bb07cd998db89a2d85d7e7bdff1
        • Session ID Length: 32
        • Session ID: ef27de22c339ed94af0284fcc439467eb6a7b4a5454a191affd86af8e39022ad
        • Cipher Suites Length: 32
        • Cipher Suites (16 suites)
          • Cipher Suite: Reserved (GREASE) (0x4a4a)
          • Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
          • Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)
          • Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303)
          • Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
          • Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
          • Cipher Suite: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca9)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca8)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
          • Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c)
          • Cipher Suite: TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d)
          • Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
          • Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0x0035)
        • Compression Methods Length: 1
        • Compression Methods (1 method)
        • Extensions Length: 1651
        • Extension: Reserved (GREASE) (len=0)
        • Extension: signature_algorithms (len=18)
        • Extension: key_share (len=1263) X25519MLKEM768, x25519
          • Type: key_share (51)
          • Length: 1263
          • Key Share extension
            • Client Key Share Length: 1261
            • Key Share Entry: Group: Reserved (GREASE), Key Exchange length: 1
            • Key Share Entry: Group: X25519MLKEM768, Key Exchange length: 1216
            • Key Share Entry: Group: x25519, Key Exchange length: 32
        • Extension: extended_master_secret (len=0)
        • Extension: encrypted_client_hello (len=250)
        • Extension: supported_groups (len=12)
          • Type: supported_groups (10)
          • Length: 12
          • Supported Groups List Length: 10
          • Supported Groups (5 groups)
            • Supported Group: Reserved (GREASE) (0xfafa)
            • Supported Group: X25519MLKEM768 (0x11ec)
            • Supported Group: x25519 (0x001d)
            • Supported Group: secp256r1 (0x0017)
            • Supported Group: secp384r1 (0x0018)

nginxがChromeに返したServer Helloの抜粋

  • Internet Protocol Version 4, Src: 192.168.254.31, Dst: 192.168.254.104
  • Transmission Control Protocol, Src Port: 4433, Dst Port: 56014, Seq: 1, Ack: 1766, Len: 1871
  • Transport Layer Security
    • [Stream index: 0]
    • TLSv1.3 Record Layer: Handshake Protocol: Server Hello
      • Content Type: Handshake (22)
      • Version: TLS 1.2 (0x0303)
      • Length: 1210
      • Handshake Protocol: Server Hello
        • Handshake Type: Server Hello (2)
        • Length: 1206
        • Version: TLS 1.2 (0x0303)
        • Random: b21bae7da19c5789b1fa764184701f7562fee327bb979585d8a67ebdff2cbec1
        • Session ID Length: 32
        • Session ID: ef27de22c339ed94af0284fcc439467eb6a7b4a5454a191affd86af8e39022ad
        • Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
        • Compression Method: null (0)
        • Extensions Length: 1134
        • Extension: supported_versions (len=2) TLS 1.3
        • Extension: key_share (len=1124) X25519MLKEM768
          • Type: key_share (51)
          • Length: 1124
          • Key Share extension
            • Key Share Entry: Group: X25519MLKEM768, Key Exchange length: 1120

このように、Chrome 側が Client Hello で提示した選択肢の中から、nginx 側が X25519MLKEM768 を選択し、Server Helloで返しています。

X25519MLKEM768 は、TLS 1.3 におけるハイブリッド耐量子鍵交換方式の1つです。従来の楕円曲線暗号(ECDH)である X25519 と、PQCである ML-KEM(Module-Lattice-based Key Encapsulation Mechanism) を組み合わせています。

PQCはまだ新しく、理論的には安全でも、実装や運用面で未知の脆弱性がある可能性があります。そこで、従来の暗号(X25519)とPQC(ML-KEM768)を組み合わせることで、どちらか一方が破られても、直ちに危険にさらされることがないように配慮されています。

トップに戻る

従来の暗号アルゴリズムのTLSネゴシエーション・フロー

2つ目のネゴシエーションを見てみましょう。

nginxがバックエンドに送ったClient Helloの抜粋

  • Internet Protocol Version 4, Src: 192.168.254.31, Dst: 192.168.254.30
  • Transmission Control Protocol, Src Port: 46370, Dst Port: 8443, Seq: 1, Ack: 1, Len: 410
  • Transport Layer Security
    • [Stream index: 1]
    • TLSv1.3 Record Layer: Handshake Protocol: Client Hello
      • Content Type: Handshake (22)
      • Version: TLS 1.0 (0x0301)
      • Length: 405
      • Handshake Protocol: Client Hello
        • Handshake Type: Client Hello (1)
        • Length: 401
        • Version: TLS 1.2 (0x0303)
        • Random: 5325c660228fb708ef600b9e0967e74bf6dbe217e09fb65f82e1cdc283a9df38
        • Session ID Length: 32
        • Session ID: bad242c39c58538e95537e779abcd4b2d15124e6d5a21a7a64909a3fb5cea4be
        • Cipher Suites Length: 60
        • Cipher Suites (30 suites)
          • Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)
          • Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303)
          • Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
          • Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
          • Cipher Suite: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 (0x009f)
          • Cipher Suite: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca9)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0cca8)
          • Cipher Suite: TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0ccaa)
          • Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
          • Cipher Suite: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (0x009e)
          • Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 (0xc024)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (0xc028)
          • Cipher Suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 (0x006b)
          • Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 (0xc023)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (0xc027)
          • Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 (0x0067)
          • Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
          • Cipher Suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA (0x0039)
          • Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
          • Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA (0x0033)
          • Cipher Suite: TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d)
          • Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c)
          • Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA256 (0x003d)
          • Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA256 (0x003c)
          • Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0x0035)
          • Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
        • Compression Methods Length: 1
        • Compression Methods (1 method)
        • Extensions Length: 268
        • Extension: renegotiation_info (len=1)
        • Extension: ec_point_formats (len=4)
        • Extension: supported_groups (len=22)
          • Type: supported_groups (10)
          • Length: 22
          • Supported Groups List Length: 20
          • Supported Groups (10 groups)
            • Supported Group: x25519 (0x001d)
            • Supported Group: x448 (0x001e)
            • Supported Group: secp256r1 (0x0017)
            • Supported Group: secp384r1 (0x0018)
            • Supported Group: secp521r1 (0x0019)
            • Supported Group: MLKEM512 (0x0200)
            • Supported Group: MLKEM768 (0x0201)
            • Supported Group: MLKEM1024 (0x0202)
            • Supported Group: X25519MLKEM768 (0x11ec)
            • Supported Group: SecP256r1MLKEM768 (0x11eb)
        • Extension: session_ticket (len=0)
        • Extension: encrypt_then_mac (len=0)
        • Extension: extended_master_secret (len=0)
        • Extension: signature_algorithms (len=156)
        • Extension: supported_versions (len=5) TLS 1.3, TLS 1.2
        • Extension: psk_key_exchange_modes (len=2)
        • Extension: key_share (len=38) x25519
          • Type: key_share (51)
          • Length: 38
          • Key Share extension
            • Client Key Share Length: 36
            • Key Share Entry: Group: x25519, Key Exchange length: 32

バックエンドがnginxに返したServer Helloの抜粋

  • Internet Protocol Version 4, Src: 192.168.254.30, Dst: 192.168.254.31
  • Transmission Control Protocol, Src Port: 8443, Dst Port: 46370, Seq: 1, Ack: 411, Len: 2302
  • Transport Layer Security
    • [Stream index: 1]
    • TLSv1.3 Record Layer: Handshake Protocol: Server Hello
      • Content Type: Handshake (22)
      • Version: TLS 1.2 (0x0303)
      • Length: 122
      • Handshake Protocol: Server Hello
        • Handshake Type: Server Hello (2)
        • Length: 118
        • Version: TLS 1.2 (0x0303)
        • Random: d3efeef649beba572f2dd3a7597150f06c78712bcd456516cf5496985bcab286
        • Session ID Length: 32
        • Session ID: bad242c39c58538e95537e779abcd4b2d15124e6d5a21a7a64909a3fb5cea4be
        • Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)
        • Compression Method: null (0)
        • Extensions Length: 46
        • Extension: supported_versions (len=2) TLS 1.3
        • Extension: key_share (len=36) x25519
          • Type: key_share (51)
          • Length: 36
          • Key Share extension
            • Key Share Entry: Group: x25519, Key Exchange length: 32

このように、nginx 側が Client Hello で提示した選択肢として、supported_groups の中には PQCアルゴリズムが含まれていますが、key_share の中には含まれていないため、バックエンド側は従来のアルゴリズムである x25519 を選択し、Server Hello で返しています。

比較のために、Chrome から直接バックエンドとTLSネゴシエーションを行うケースを確認してみます。

Chromeがバックエンドに送ったClient Helloの抜粋

  • Internet Protocol Version 4, Src: 192.168.254.104, Dst: 192.168.254.30
  • Transmission Control Protocol, Src Port: 61641, Dst Port: 8443, Seq: 1, Ack: 1, Len: 1765
  • Transport Layer Security
    • [Stream index: 0]
    • TLSv1.3 Record Layer: Handshake Protocol: Client Hello
      • Content Type: Handshake (22)
      • Version: TLS 1.0 (0x0301)
      • Length: 1760
      • Handshake Protocol: Client Hello
        • Handshake Type: Client Hello (1)
        • Length: 1756
        • Version: TLS 1.2 (0x0303)
        • Random: 8c70d7c10f71d7207f69725d1c5d86b16d1b22897ea58ce2ec95db974a1baf6b
        • Session ID Length: 32
        • Session ID: f97eccd0152d5ba04a5c6ae76f600c0175d65c42b4d93f9fd62e6c271efd0812
        • Cipher Suites Length: 32
        • Cipher Suites (16 suites)
          • Cipher Suite: Reserved (GREASE) (0xcaca)
          • Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
          • Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)
          • Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303)
          • Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
          • Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
          • Cipher Suite: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca9)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca8)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
          • Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
          • Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c)
          • Cipher Suite: TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d)
          • Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
          • Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0x0035)
        • Compression Methods Length: 1
        • Compression Methods (1 method)
        • Extensions Length: 1651
        • Extension: Reserved (GREASE) (len=0)
        • Extension: status_request (len=5)
        • Extension: compress_certificate (len=3)
        • Extension: extended_master_secret (len=0)
        • Extension: renegotiation_info (len=1)
        • Extension: supported_versions (len=7) TLS 1.3, TLS 1.2
        • Extension: session_ticket (len=0)
        • Extension: encrypted_client_hello (len=250)
        • Extension: application_settings (len=5)
        • Extension: psk_key_exchange_modes (len=2)
        • Extension: application_layer_protocol_negotiation (len=14)
        • Extension: signed_certificate_timestamp (len=0)
        • Extension: ec_point_formats (len=2)
        • Extension: key_share (len=1263) X25519MLKEM768, x25519
          • Type: key_share (51)
          • Length: 1263
          • Key Share extension
            • Client Key Share Length: 1261
            • Key Share Entry: Group: Reserved (GREASE), Key Exchange length: 1
            • Key Share Entry: Group: X25519MLKEM768, Key Exchange length: 1216
            • Key Share Entry: Group: x25519, Key Exchange length: 32
        • Extension: supported_groups (len=12)
          • Type: supported_groups (10)
          • Length: 12
          • Supported Groups List Length: 10
          • Supported Groups (5 groups)
            • Supported Group: Reserved (GREASE) (0x8a8a)
            • Supported Group: X25519MLKEM768 (0x11ec)
            • Supported Group: x25519 (0x001d)
            • Supported Group: secp256r1 (0x0017)
            • Supported Group: secp384r1 (0x0018)

バックエンドがChromeに返したServer Helloの抜粋

  • Internet Protocol Version 4, Src: 192.168.254.30, Dst: 192.168.254.104
  • Transmission Control Protocol, Src Port: 8443, Dst Port: 61641, Seq: 1, Ack: 1766, Len: 2317
  • Transport Layer Security
    • [Stream index: 0]
    • TLSv1.3 Record Layer: Handshake Protocol: Server Hello
      • Content Type: Handshake (22)
      • Version: TLS 1.2 (0x0303)
      • Length: 122
      • Handshake Protocol: Server Hello
        • Handshake Type: Server Hello (2)
        • Length: 118
        • Version: TLS 1.2 (0x0303)
        • Random: 76a0ceca5f19880004af39a12c721d52c0b6a514c1dea1462d589c4ffa8dd4cf
        • Session ID Length: 32
        • Session ID: f97eccd0152d5ba04a5c6ae76f600c0175d65c42b4d93f9fd62e6c271efd0812
        • Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)
        • Compression Method: null (0)
        • Extensions Length: 46
        • Extension: supported_versions (len=2) TLS 1.3
        • Extension: key_share (len=36) x25519
          • Type: key_share (51)
          • Length: 36
          • Key Share extension
            • Key Share Entry: Group: x25519, Key Exchange length: 32

このように、Chrome 側は ハイブリッドPQCアルゴリズムを含む選択肢を Client Hello の key_share で示しましたが、バックエンド側は従来のアルゴリズムである x25519 を選択し、Server Hello で返しています。

トップに戻る

nginxが挿入したHTTPヘッダーを確認する

バックエンドで稼働する mendhak/http-https-echo は、リクエスト内容をJSON形式で返すことで、nginxがセットしたヘッダーの情報を可視化することができます。

以下は、[Chrome] – [OQS nginx] – [バックエンド] 構成において、Chromeブラウザーに返されたコンテンツです。"x-ssl-curve" というヘッダーに注目してください。

{
  "path": "/",
  "headers": {
    "host": "ubuntu2.iss.local",
    "x-real-ip": "192.168.254.104",
    "x-forwarded-for": "192.168.254.104",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "4433",
    "x-ssl-protocol": "TLSv1.3",
    "x-ssl-cipher": "TLS_AES_128_GCM_SHA256",
    "x-ssl-ciphers": "0x4a4a:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256: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:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA:AES256-SHA",
    "x-ssl-curve": "X25519MLKEM768",
    "x-ssl-curves": "0xfafa:X25519MLKEM768:X25519:prime256v1:secp384r1",
    "x-client-verify": "NONE",
    "connection": "close",
    "cache-control": "max-age=0",
    "sec-ch-ua": "\"Google Chrome\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\"",
    "upgrade-insecure-requests": "1",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    "sec-fetch-site": "none",
    "sec-fetch-mode": "navigate",
    "sec-fetch-user": "?1",
    "sec-fetch-dest": "document",
    "accept-encoding": "gzip, deflate, br, zstd",
    "accept-language": "ja"
  },
  "method": "GET",
  "body": "",
  "fresh": false,
  "hostname": "ubuntu2.iss.local",
  "ip": "192.168.254.104",
  "ips": [
    "192.168.254.104"
  ],
  "protocol": "https",
  "query": {},
  "subdomains": [
    "ubuntu2"
  ],
  "xhr": false,
  "os": {
    "hostname": "5da998794c28"
  },
  "connection": {
    "servername": false
  },
  "clientCertificate": {}
}

このように、Chromeの場合は、ハイブリッドPQCアルゴリズム X25519MLKEM768 が選択されていることがわかります。

この "x-ssl-curve" というヘッダーは、nginx.conf 内で proxy_set_header X-SSL-Curve $ssl_curve; として定義されています。$ssl_curve は、nginxのSSL/TLSモジュールで使用される組み込み変数で、SSL/TLS接続で楕円曲線ディフィー・ヘルマン鍵交換 (ECDHE) に使用された楕円曲線の名前を保持しています。

比較のために、[curl] – [OQS nginx] – [バックエンド] 構成の場合を見てみます。

{
  "path": "/",
  "headers": {
    "host": "ubuntu2.iss.local",
    "x-real-ip": "192.168.254.104",
    "x-forwarded-for": "192.168.254.104",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "4433",
    "x-ssl-protocol": "TLSv1.3",
    "x-ssl-cipher": "TLS_AES_256_GCM_SHA384",
    "x-ssl-ciphers": "TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA",
    "x-ssl-curve": "X25519",
    "x-ssl-curves": "X25519:prime256v1:secp384r1",
    "x-client-verify": "NONE",
    "connection": "close",
    "user-agent": "curl/8.14.1",
    "accept": "*/*"
  },
  "method": "GET",
  "body": "",
  "fresh": false,
  "hostname": "ubuntu2.iss.local",
  "ip": "192.168.254.104",
  "ips": [
    "192.168.254.104"
  ],
  "protocol": "https",
  "query": {},
  "subdomains": [
    "ubuntu2"
  ],
  "xhr": false,
  "os": {
    "hostname": "80c9e0d7211c"
  },
  "connection": {
    "servername": false
  },
  "clientCertificate": {}
}

このように、curl の場合は、従来のアルゴリズム X25519 が選択されていることがわかります。

トップに戻る

ChromeブラウザーのTLS暗号情報を確認する

Chromeブラウザーの開発者ツールで、TLS接続に使用された暗号アルゴリズムを確認できます。
開発者ツールの Privacy and security タブを開くと、Security > Overview にTLSの情報が表示されます。

ハイブリッドPQCアルゴリズムが使用されている場合 (nginx経由でバックエンドに接続)

image004.jpg

従来のアルゴリズムが使用されている場合 (バックエンドにダイレクトに接続)

image005.jpg

トップに戻る

まとめ

OQS版の nginx をリバース・プロキシー構成で使用することで、バックエンドのWebサービスを変更することなく、ハイブリッドPQCアルゴリズムに対応した通信を行えることが確認できました。将来的には入手可能なほとんどのインフラがPQC対応になると予想されますが、現在はまだ過渡期ですので、プロキシーのような移行ソリューションが必要になる場面も多いと思われます。

図や検証結果には含まれていませんが、Chrome だけでなく Firefox 144 や Edge 141.0.3537.85 でも、ハイブリッドPQC での接続に成功しています。

なお、基本的に既存Webサービスの改修は必要ありませんが、リバース・プロキシーとの相性が悪い場合はリンク切れなどが起こりますので、アプリの改修が必要になる可能性があります。また、PQC未対応のクライアントから接続した場合は警告を出すなど、アプリ側に追加ロジックが欲しくなるかもしれません。

さらに、OQS nginxのサイト上に、THIS IS NOT FIT FOR PRODUCTIVE USE⁠ の記載があることについても留意が必要です。重要なアプリケーションを本番環境で利用する場合は、IBM Quantum Safe Remediator のような商用製品の利用についてもご検討ください。

トップに戻る

参考文献

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?