この記事はAIによって生成されたコンテンツを含みます。(スクショ、コマンド出力、ログ、トレースなどには含みません)
はじめに
以前の記事「OpenSSLを使ってPQC(Post-Quantum Cryptography)対応の自己署名証明書を作成する方法」では、OQSが提供するDockerイメージ openquantumsafe/oqs-ossl3 を使用して、さまざまな署名アルゴリズムの自己署名証明書を作成する手順をご紹介しました。
今回は、作成した自己署名証明書とのTLS接続検証を行うために、簡易的なサーバーを作ってみたいと思います。
以前の記事でも、1つの証明書を使ってサーバーを起動する方法はご紹介しましたが、今回は13種類の証明書すべてを、それぞれ別ポートでLISTENするサーバーを起動してみます。検証したい組み合わせのサーバーを毎回上げ下げする必要がないため、PQC関連のテストを行う際に役立つ場面があると思います。
サーバーの起動方法
事前準備
サーバーの起動には、Docker compose を使用します。その定義ファイルや関連ファイルをまとめておくために、任意のディレクトリーを作成します。
この記事では「pqc-test-server」という名前のディレクトリーを作成しました。
その下に「pqc-certs」というサブ・ディレクトリーを作成し、以前の記事「OpenSSLを使ってPQC(Post-Quantum Cryptography)対応の自己署名証明書を作成する方法」の手順に従って、13種類の自己署名証明書を作成します。
証明書作成後のフォルダー構造は、以下のとおりです。
. (~/pqc-test-server)
├── compose.yaml (このあと説明します)
└── pqc-certs
├── ecdsa.crt
├── ecdsa.key
├── falcon1024.crt
├── falcon1024.key
├── falcon512.crt
├── falcon512.key
├── mldsa44.crt
├── mldsa44.key
├── mldsa65.crt
├── mldsa65.key
├── mldsa87.crt
├── mldsa87.key
├── openssl.cnf
├── p256_falcon512.crt
├── p256_falcon512.key
├── p384_mldsa65.crt
├── p384_mldsa65.key
├── p521_falcon1024.crt
├── p521_falcon1024.key
├── p521_mldsa87.crt
├── p521_mldsa87.key
├── rsa.crt
├── rsa.key
├── rsa3072_falcon512.crt
├── rsa3072_falcon512.key
├── rsa3072_mldsa44.crt
└── rsa3072_mldsa44.key
※動作比較のために、PQCだけでなくコンポジットや従来暗号の証明書も含まれています。
Docker compose定義の作成
当初は、13種類の証明書をそれぞれ個別ポートに割り当てて openssl s_server を起動すればよいと考えていました。
しかし、実際に接続を試していく中で、TLS 1.3 の KEX (Key Exchange: 鍵交換) におけるSupported Groups 指定が大きく影響することがわかったので、従来のKEX、ハイブリッドのKEX、PQCのKEXという3つに大別して、13種類の署名アルゴリズム ✕ 3種類のKEX、あわせて 39種類のポートをオープンすることにします。
KEX系列
| 系列 | ポート範囲 | KEX (`-groups`) | 説明 |
|---|---|---|---|
| Classical 系列 | 7001–7013 | x25519 | 古典 ECC のみで鍵交換 |
| Hybrid 系列 | 7101–7113 | x25519:mlkem768 | 古典 + PQC のハイブリッド鍵交換 |
| PQC 系列 | 7201–7213 | mlkem768 | PQC KEM のみで鍵交換 |
署名アルゴリズム (KEX系列共通)
| ポート番号 (x: 0, 1, 2) | 証明書の署名アルゴリズム |
|---|---|
| 7x01 | ECDSA |
| 7x02 | RSA |
| 7x03 | p256_falcon512 |
| 7x04 | p384_mldsa65 |
| 7x05 | p521_falcon1024 |
| 7x06 | p521_mldsa87 |
| 7x07 | rsa3072_falcon512 |
| 7x08 | rsa3072_mldsa44 |
| 7x09 | FALCON-512 |
| 7x10 | FALCON-1024 |
| 7x11 | ML-DSA44 |
| 7x12 | ML-DSA65 |
| 7x13 | ML-DSA87 |
単一TLSサーバーの起動コマンド
検証用TLSサーバーの起動には、openssl s_serverコマンドを使用します。以下のコマンドは、最小構成の一例です。
openssl s_server \
-accept 7001 \
-www \
-cert /certs/ecdsa.crt \
-key /certs/ecdsa.key \
-groups x25519 \
-tls1_3
各オプションの意味は以下のとおりです。
- -accept 7001
TLS サーバーとして ポート 7001 をリッスンします。
s_server は単体で簡易 Web サーバーとしても動作するため、このポートに対して TLS クライアント(openssl s_client、ブラウザーなど) が接続できます。
- -www
s_server に 簡単な HTTP 応答機能を追加します。
ブラウザーで https://<IPアドレス>:7001/ にアクセスした際に、接続確認用のページを返すため、TLS 動作確認に便利です。
- -cert /certs/ecdsa.crt
サーバーがクライアントに提示する サーバー証明書を指定します。
この例では、ECDSA で署名された X.509 証明書のファイル名を指定しています。
- -key /certs/ecdsa.key
上記の証明書に対応する 秘密鍵ファイルを指定します。
証明書 (.crt) と秘密鍵 (.key) のペアが揃っている必要があります。
- -groups x25519
TLS 1.3 の 鍵交換(KEX: Key Exchange)で使用するグループを指定します。
x25519 は従来型(古典暗号) の ECDH (楕円曲線 Diffie-Hellman) による鍵交換方式で、TLS 1.3 では最も一般的な安全な鍵交換グループの一つです。
- -tls1_3
サーバーを TLS 1.3 のみ有効にして起動します。
TLS 1.3 では鍵交換アルゴリズムは暗号スイートから切り離され、-groups (Supported Groups) で指定する方式に変更されています。PQC 移行期の実験では、このオプションで TLS1.3 に固定しておくと挙動が安定します。
複数TLSサーバーを起動するDocker composeファイル
先ほどのフォルダー内に以下の compose.yaml を作成し、docker compose up -d コマンドを呼び出すことで、39ポートをLISTENする検証用TLSサーバーを起動することができます。
compose.yaml
services:
oqs-ossl3-lab:
image: openquantumsafe/oqs-ossl3:latest
container_name: oqs-ossl3-lab
restart: unless-stopped
volumes:
- ./pqc-certs:/certs:ro
ports:
# 700x: Classical KEX (x25519)
- "7001:7001"
- "7002:7002"
- "7003:7003"
- "7004:7004"
- "7005:7005"
- "7006:7006"
- "7007:7007"
- "7008:7008"
- "7009:7009"
- "7010:7010"
- "7011:7011"
- "7012:7012"
- "7013:7013"
# 710x: Hybrid KEX (x25519:mlkem768)
- "7101:7101"
- "7102:7102"
- "7103:7103"
- "7104:7104"
- "7105:7105"
- "7106:7106"
- "7107:7107"
- "7108:7108"
- "7109:7109"
- "7110:7110"
- "7111:7111"
- "7112:7112"
- "7113:7113"
# 720x: PQC KEX (mlkem768)
- "7201:7201"
- "7202:7202"
- "7203:7203"
- "7204:7204"
- "7205:7205"
- "7206:7206"
- "7207:7207"
- "7208:7208"
- "7209:7209"
- "7210:7210"
- "7211:7211"
- "7212:7212"
- "7213:7213"
command: >
/bin/sh -c '
set -e
# ---------- 700x: Classical (x25519) ----------
/opt/openssl/bin/openssl s_server -accept 7001 -www \
-cert /certs/ecdsa.crt -key /certs/ecdsa.key \
-groups x25519 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7002 -www \
-cert /certs/rsa.crt -key /certs/rsa.key \
-groups x25519 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7003 -www \
-cert /certs/p256_falcon512.crt -key /certs/p256_falcon512.key \
-groups x25519 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7004 -www \
-cert /certs/p384_mldsa65.crt -key /certs/p384_mldsa65.key \
-groups x25519 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7005 -www \
-cert /certs/p521_falcon1024.crt -key /certs/p521_falcon1024.key \
-groups x25519 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7006 -www \
-cert /certs/p521_mldsa87.crt -key /certs/p521_mldsa87.key \
-groups x25519 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7007 -www \
-cert /certs/rsa3072_falcon512.crt -key /certs/rsa3072_falcon512.key \
-groups x25519 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7008 -www \
-cert /certs/rsa3072_mldsa44.crt -key /certs/rsa3072_mldsa44.key \
-groups x25519 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7009 -www \
-cert /certs/falcon512.crt -key /certs/falcon512.key \
-groups x25519 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7010 -www \
-cert /certs/falcon1024.crt -key /certs/falcon1024.key \
-groups x25519 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7011 -www \
-cert /certs/mldsa44.crt -key /certs/mldsa44.key \
-groups x25519 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7012 -www \
-cert /certs/mldsa65.crt -key /certs/mldsa65.key \
-groups x25519 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7013 -www \
-cert /certs/mldsa87.crt -key /certs/mldsa87.key \
-groups x25519 -tls1_3 &
# ---------- 710x: Hybrid (x25519:mlkem768) ----------
/opt/openssl/bin/openssl s_server -accept 7101 -www \
-cert /certs/ecdsa.crt -key /certs/ecdsa.key \
-groups x25519:mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7102 -www \
-cert /certs/rsa.crt -key /certs/rsa.key \
-groups x25519:mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7103 -www \
-cert /certs/p256_falcon512.crt -key /certs/p256_falcon512.key \
-groups x25519:mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7104 -www \
-cert /certs/p384_mldsa65.crt -key /certs/p384_mldsa65.key \
-groups x25519:mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7105 -www \
-cert /certs/p521_falcon1024.crt -key /certs/p521_falcon1024.key \
-groups x25519:mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7106 -www \
-cert /certs/p521_mldsa87.crt -key /certs/p521_mldsa87.key \
-groups x25519:mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7107 -www \
-cert /certs/rsa3072_falcon512.crt -key /certs/rsa3072_falcon512.key \
-groups x25519:mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7108 -www \
-cert /certs/rsa3072_mldsa44.crt -key /certs/rsa3072_mldsa44.key \
-groups x25519:mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7109 -www \
-cert /certs/falcon512.crt -key /certs/falcon512.key \
-groups x25519:mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7110 -www \
-cert /certs/falcon1024.crt -key /certs/falcon1024.key \
-groups x25519:mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7111 -www \
-cert /certs/mldsa44.crt -key /certs/mldsa44.key \
-groups x25519:mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7112 -www \
-cert /certs/mldsa65.crt -key /certs/mldsa65.key \
-groups x25519:mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7113 -www \
-cert /certs/mldsa87.crt -key /certs/mldsa87.key \
-groups x25519:mlkem768 -tls1_3 &
# ---------- 720x: PQC (mlkem768) ----------
/opt/openssl/bin/openssl s_server -accept 7201 -www \
-cert /certs/ecdsa.crt -key /certs/ecdsa.key \
-groups mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7202 -www \
-cert /certs/rsa.crt -key /certs/rsa.key \
-groups mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7203 -www \
-cert /certs/p256_falcon512.crt -key /certs/p256_falcon512.key \
-groups mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7204 -www \
-cert /certs/p384_mldsa65.crt -key /certs/p384_mldsa65.key \
-groups mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7205 -www \
-cert /certs/p521_falcon1024.crt -key /certs/p521_falcon1024.key \
-groups mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7206 -www \
-cert /certs/p521_mldsa87.crt -key /certs/p521_mldsa87.key \
-groups mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7207 -www \
-cert /certs/rsa3072_falcon512.crt -key /certs/rsa3072_falcon512.key \
-groups mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7208 -www \
-cert /certs/rsa3072_mldsa44.crt -key /certs/rsa3072_mldsa44.key \
-groups mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7209 -www \
-cert /certs/falcon512.crt -key /certs/falcon512.key \
-groups mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7210 -www \
-cert /certs/falcon1024.crt -key /certs/falcon1024.key \
-groups mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7211 -www \
-cert /certs/mldsa44.crt -key /certs/mldsa44.key \
-groups mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7212 -www \
-cert /certs/mldsa65.crt -key /certs/mldsa65.key \
-groups mlkem768 -tls1_3 &
/opt/openssl/bin/openssl s_server -accept 7213 -www \
-cert /certs/mldsa87.crt -key /certs/mldsa87.key \
-groups mlkem768 -tls1_3 &
wait
'
起動後、期待通りのポートが LISTENしているかを確認します。
~/pqc-test-server$ netstat -a | grep LISTEN | grep :7 | sort
tcp 0 0 0.0.0.0:7001 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7002 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7003 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7004 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7005 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7006 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7007 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7008 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7009 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7010 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7011 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7012 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7013 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7101 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7102 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7103 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7104 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7105 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7106 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7107 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7108 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7109 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7110 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7111 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7112 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7113 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7201 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7202 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7203 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7204 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7205 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7206 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7207 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7208 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7209 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7210 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7211 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7212 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:7213 0.0.0.0:* LISTEN
tcp6 0 0 [::]:7001 [::]:* LISTEN
tcp6 0 0 [::]:7002 [::]:* LISTEN
tcp6 0 0 [::]:7003 [::]:* LISTEN
tcp6 0 0 [::]:7004 [::]:* LISTEN
tcp6 0 0 [::]:7005 [::]:* LISTEN
tcp6 0 0 [::]:7006 [::]:* LISTEN
tcp6 0 0 [::]:7007 [::]:* LISTEN
tcp6 0 0 [::]:7008 [::]:* LISTEN
tcp6 0 0 [::]:7009 [::]:* LISTEN
tcp6 0 0 [::]:7010 [::]:* LISTEN
tcp6 0 0 [::]:7011 [::]:* LISTEN
tcp6 0 0 [::]:7012 [::]:* LISTEN
tcp6 0 0 [::]:7013 [::]:* LISTEN
tcp6 0 0 [::]:7101 [::]:* LISTEN
tcp6 0 0 [::]:7102 [::]:* LISTEN
tcp6 0 0 [::]:7103 [::]:* LISTEN
tcp6 0 0 [::]:7104 [::]:* LISTEN
tcp6 0 0 [::]:7105 [::]:* LISTEN
tcp6 0 0 [::]:7106 [::]:* LISTEN
tcp6 0 0 [::]:7107 [::]:* LISTEN
tcp6 0 0 [::]:7108 [::]:* LISTEN
tcp6 0 0 [::]:7109 [::]:* LISTEN
tcp6 0 0 [::]:7110 [::]:* LISTEN
tcp6 0 0 [::]:7111 [::]:* LISTEN
tcp6 0 0 [::]:7112 [::]:* LISTEN
tcp6 0 0 [::]:7113 [::]:* LISTEN
tcp6 0 0 [::]:7201 [::]:* LISTEN
tcp6 0 0 [::]:7202 [::]:* LISTEN
tcp6 0 0 [::]:7203 [::]:* LISTEN
tcp6 0 0 [::]:7204 [::]:* LISTEN
tcp6 0 0 [::]:7205 [::]:* LISTEN
tcp6 0 0 [::]:7206 [::]:* LISTEN
tcp6 0 0 [::]:7207 [::]:* LISTEN
tcp6 0 0 [::]:7208 [::]:* LISTEN
tcp6 0 0 [::]:7209 [::]:* LISTEN
tcp6 0 0 [::]:7210 [::]:* LISTEN
tcp6 0 0 [::]:7211 [::]:* LISTEN
tcp6 0 0 [::]:7212 [::]:* LISTEN
tcp6 0 0 [::]:7213 [::]:* LISTEN
クライアントからの接続テスト
ブラウザーからの接続テスト
ブラウザーの代表として、Firefox 147.0.2 とChrome 144.0.7559.110 を使ったテストを行いました。
接続が成功するケースでは、以下のような OpenSSL s_server の出力する画面が表示されます。
失敗する場合は、それぞれ以下の画面になりました。
-
Firefox 147.0.2
localhost:7003 への接続中にエラーが発生しました。Cannot communicate securely with peer: no common encryption algorithm(s). エラーコード: SSL_ERROR_NO_CYPHER_OVERLAP
-
Chrome 144.0.7559.110
localhost ではサポートされていないプロトコルが使用されています。 ERR_SSL_VERSION_OR_CIPHER_MISMATCH
ブラウザーの結果
結果を集計すると、以下のとおりです。(Firefox/Chrome共通)
-
成功したもの
-
ポート 7001 ECDSA (x25519 Classical KEX)
-
ポート 7002 RSA (x25519 Classical KEX)
-
ポート 7101 ECDSA (x25519:mlkem768 Hybrid KEX)
-
ポート 7102 RSA (x25519:mlkem768 Hybrid KEX)
-
-
失敗したもの
-
上記以外のすべてのポート、つまり:
-
ECDSAとRSAで署名されたもの以外、つまりPQCやコンポジット証明書はすべて失敗
-
mlkem768 PQC KEXはすべて失敗
-
-
今回使用した Firefox 147 および Chrome 144 は、すでに Hybrid KEX(X25519 + ML-KEM)を標準サポートしていますが、失敗したケースは、サーバー証明書が PQC署名 (ML-DSA / FALCON) やコンポジット証明書であり、まだブラウザーがサポートしていない状態です。
OpenSSL s_clientコマンドからの接続テスト
接続 → 情報表示 → 即終了が可能な 以下のOpenssl s_client コマンドを使用することで、シンプルに検証することができます。
echo | openssl s_client -connect <ホスト名>:<ポート番号> -brief
-
echo | ...
echo は空行(改行だけ)を出力し、それを s_client の 標準入力に渡すために使います。s_client は本来「対話的モード」で動作し、接続後にユーザーの入力を待ち続けますが、空行をパイプで渡すことで、TLS ハンドシェイク完了、サーバー証明書などの情報を出力、入力が EOF (終端) となった時点で 自動終了という流れになります。
-
-brief
TLS ハンドシェイク結果を 簡潔に(brief)表示します。具体的には、サーバー証明書の基本情報、TLS バージョン、暗号スイート、鍵交換方式 (KEX)、ハンドシェイク成功/失敗といった、最小限の情報だけが表示され、大量の内部状態やデバッグメッセージは省略されます。テスト結果だけを確認したい時に便利なオプションです。
OpenSSLは、以下の3つのバージョンを使用しました。
-
OpenSSL 3.0.13 (Ubuntu 24) 【レガシー openssl】
WSL上で動いている Ubuntu 24 に入っている opensslコマンドで、PQC未対応です。
-
OpenSSL 3.6.1 (Windows 11) 【PQCに一部対応の openssl】
別にインストールした Windows版の opensslコマンドです。OpenSSL 3.5以降は、ML-KEM、ML-DSA、SLH-DSAなどのPQCアルゴリズムに対応しています。PQC KEXである ポート7201-7213と接続する場合のみ、
-groups mlkem768オプションを使用しました。(使用しない場合は接続に失敗しました)
-
OpenSSL 3.6.0 (openquantumsafe/oqs-ossl3) 【OQSに対応の openssl】
検証用TLSサーバーを動作させている openquantumsafe/oqs-ossl3 と同一イメージに入っている opensslコマンドです。Open Quantum Safe (OQS) で実装されているコンポジット証明書やPQCアルゴリズムに対応しています。すべてのケースで、サーバー側の設定と同一の
-groupsオプションを指定しました。(使用しない場合は接続に失敗しました)検証コマンドの呼び出しは、以下のDockerコマンド経由で行います。
docker run --rm -it --network host openquantumsafe/oqs-ossl3 \ sh -lc 'echo | /opt/openssl/bin/openssl s_client \ -connect localhost:7001 \ -groups x25519 \ -brief'- docker run --rm -it:使い捨てでコンテナーを起動します。
- --network host:ホスト側のポートへ localhost:<PORT> でアクセスできるようにします。
接続が成功するケースでは、以下のような出力が得られ、「Hash used」や「Signature type」の部分に接続先サーバーごとの違いが出ました。
"Can't use SSL_get_servername
depth=0 C = JP, ST = Osaka, L = Osaka, O = QuantumSafe Sample Org, OU = PQC Lab, CN = sample.qs.local
verify error:num=18:self-signed certificate
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: C = JP, ST = Osaka, L = Osaka, O = QuantumSafe Sample Org, OU = PQC Lab, CN = sample.qs.local
Hash used: SHA256
Signature type: ECDSA
Verification error: self-signed certificate
Server Temp Key: X25519, 253 bits
DONE"
失敗したケースでは、以下のような出力となりました。
-
レガシー openssl (一例)
4007B51C27710000:error:0A000410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:../ssl/record/rec_layer_s3.c:1599:SSL alert number 40
-
PQCに一部対応の openssl (一例)
"Connecting to ::1 A4000000:error:0A000410:SSL routines:ssl3_read_bytes:ssl/tls alert handshake failure:ssl\record\rec_layer_s3.c:918:SSL alert number 40 A4000000:error:0A000197:SSL routines:SSL_shutdown:shutdown while in init:ssl\ssl_lib.c:2804:"
レガシー openssl クライアントの結果
レガシーopensslクライアントの結果は、ブラウザーと同じ傾向を示しており、以下のとおりでした。
-
成功したもの
- ポート 7001 ECDSA (x25519 Classical KEX)
Hash used: SHA256、Signature type: ECDSA を表示
- ポート 7002 RSA (x25519 Classical KEX)
Hash used: SHA256、Signature type: RSA-PSS を表示
- ポート 7101 ECDSA (x25519:mlkem768 Hybrid KEX)
Hash used: SHA256、Signature type: ECDSA を表示
- ポート 7102 RSA (x25519:mlkem768 Hybrid KEX)
Hash used: SHA256、Signature type: RSA-PSS を表示
-
失敗したもの
-
上記以外のすべてのポート、つまり:
-
ECDSAとRSAで署名されたもの以外、つまりPQCやコンポジット証明書はすべて失敗
-
mlkem768 PQC KEXはすべて失敗
-
-
今回はネットワークトレースを取得していないので未確認ですが、レガシーKEXしかサポートしないクライアントがハイブリッドKEXのサーバーと接続できている理由は、TLS1.3 のハイブリッド KEXが 複数方式を単に並べて送るだけの仕組みなので、クライアントが理解できる部分「x25519」だけを選択し、サーバー側がクライアントの選択に従ってレガシーKEXで接続しているためと考えられます。
PQCに一部対応のopensslクライアントの結果
PQCに一部対応のopensslクライアントの結果は、以下のとおりでした。
-
成功したもの
- ポート 7001 ECDSA (x25519 Classical KEX)
Hash used: SHA256、Signature type: ecdsa_secp256r1_sha256 を表示
- ポート 7002 RSA (x25519 Classical KEX)
Hash used: SHA256、Signature type: rsa_pss_rsae_sha256 を表示
- ポート 7011 ML-DSA44 (x25519 Classical KEX)
Hash used: UNDEF、Signature type: mldsa44 を表示
- ポート 7012 ML-DSA65 (x25519 Classical KEX)
Hash used: UNDEF、Signature type: mldsa65 を表示
- ポート 7013 ML-DSA87 (x25519 Classical KEX)
Hash used: UNDEF、Signature type: mldsa87 を表示
- ポート 7101 ECDSA (x25519:mlkem768 Hybrid KEX)
Hash used: SHA256、Signature type: ecdsa_secp256r1_sha256 を表示
- ポート 7102 RSA (x25519:mlkem768 Hybrid KEX)
Hash used: SHA256、Signature type: rsa_pss_rsae_sha256 を表示
- ポート 7111 ML-DSA44 (x25519:mlkem768 Hybrid KEX)
Hash used: UNDEF、Signature type: mldsa44 を表示
- ポート 7112 ML-DSA65 (x25519:mlkem768 Hybrid KEX)
Hash used: UNDEF、Signature type: mldsa65 を表示
- ポート 7113 ML-DSA87 (x25519:mlkem768 Hybrid KEX)
Hash used: UNDEF、Signature type: mldsa87 を表示
-
設定を変えて成功したもの (
-groups mlkem768を指定)- ポート 7201 ECDSA (mlkem768 PQC KEX)
Hash used: SHA256、Signature type: ecdsa_secp256r1_sha256 を表示
- ポート 7202 RSA (mlkem768 PQC KEX)
Hash used: SHA256、Signature type: rsa_pss_rsae_sha256 を表示
- ポート 7211 ML-DSA44 (mlkem768 PQC KEX)
Hash used: UNDEF、Signature type: mldsa44 を表示
- ポート 7212 ML-DSA65 (mlkem768 PQC KEX)
Hash used: UNDEF、Signature type: mldsa65 を表示
- ポート 7213 ML-DSA87 (mlkem768 PQC KEX)
Hash used: UNDEF、Signature type: mldsa87 を表示
-
失敗したもの
-
上記以外のすべてのポート、つまり:
-
コンポジット証明書とFALCONはすべて失敗
-
mlkem768 PQC KEXは、-groups mlkem768 を指定しないデフォルト状態だとすべて失敗
-
-
OpenSSL 3.5以降において、ML-DSA は正式対応していますが、FALCON やコンポジット証明書は未対応であり、失敗したのは仕様上の制約と考えられます。
OQSに対応の openssl クライアントの結果
OQSに対応の openssl クライアントの結果は、以下のとおりでした。
-
設定を変えて成功したもの (
-groups x25519,x25519:mlkem768,mlkem768のいずれかをサーバーの設定に合わせて指定)- ポート 7001 ECDSA (x25519 Classical KEX)
Hash used: SHA256、Signature type: ecdsa_secp256r1_sha256 を表示
- ポート 7002 RSA (x25519 Classical KEX)
Hash used: SHA256、Signature type: rsa_pss_rsae_sha256 を表示
- ポート 7003 p256_falcon512 (x25519 Classical KEX)
Hash used: UNDEF、Signature type: p256_falcon512 を表示
- ポート 7004 p384_mldsa65 (x25519 Classical KEX)
Hash used: UNDEF、Signature type: p384_mldsa65 を表示
- ポート 7005 p521_falcon1024 (x25519 Classical KEX)
Hash used: UNDEF、Signature type: p521_falcon1024 を表示
- ポート 7006 p521_mldsa87 (x25519 Classical KEX)
Hash used: UNDEF、Signature type: p521_mldsa87 を表示
- ポート 7007 rsa3072_falcon512 (x25519 Classical KEX)
Hash used: UNDEF、Signature type: rsa3072_falcon512 を表示
- ポート 7008 rsa3072_mldsa44 (x25519 Classical KEX)
Hash used: UNDEF、Signature type: rsa3072_mldsa44 を表示
- ポート 7009 FALCON-512 (x25519 Classical KEX)
Hash used: UNDEF、Signature type: falcon512 を表示
- ポート 7010 FALCON-1024 (x25519 Classical KEX)
Hash used: UNDEF、Signature type: falcon1024 を表示
- ポート 7011 ML-DSA44 (x25519 Classical KEX)
Hash used: UNDEF、Signature type: mldsa44 を表示
- ポート 7012 ML-DSA65 (x25519 Classical KEX)
Hash used: UNDEF、Signature type: mldsa65 を表示
- ポート 7013 ML-DSA87 (x25519 Classical KEX)
Hash used: UNDEF、Signature type: mldsa87 を表示
- ポート 7101 ECDSA (x25519:mlkem768 Hybrid KEX)
Hash used: SHA256、Signature type: ecdsa_secp256r1_sha256 を表示
- ポート 7102 RSA (x25519:mlkem768 Hybrid KEX)
Hash used: SHA256、Signature type: rsa_pss_rsae_sha256 を表示
- ポート 7103 p256_falcon512 (x25519:mlkem768 Hybrid KEX)
Hash used: UNDEF、Signature type: p256_falcon512 を表示
- ポート 7104 p384_mldsa65 (x25519:mlkem768 Hybrid KEX)
Hash used: UNDEF、Signature type: p384_mldsa65 を表示
- ポート 7105 p521_falcon1024 (x25519:mlkem768 Hybrid KEX)
Hash used: UNDEF、Signature type: p521_falcon1024 を表示
- ポート 7106 p521_mldsa87 (x25519:mlkem768 Hybrid KEX)
Hash used: UNDEF、Signature type: p521_mldsa87 を表示
- ポート 7107 rsa3072_falcon512 (x25519:mlkem768 Hybrid KEX)
Hash used: UNDEF、Signature type: rsa3072_falcon512 を表示
- ポート 7108 rsa3072_mldsa44 (x25519:mlkem768 Hybrid KEX)
Hash used: UNDEF、Signature type: rsa3072_mldsa44 を表示
- ポート 7109 FALCON-512 (x25519:mlkem768 Hybrid KEX)
Hash used: UNDEF、Signature type: falcon512 を表示
- ポート 7110 FALCON-1024 (x25519:mlkem768 Hybrid KEX)
Hash used: UNDEF、Signature type: falcon1024 を表示
- ポート 7111 ML-DSA44 (x25519:mlkem768 Hybrid KEX)
Hash used: UNDEF、Signature type: mldsa44 を表示
- ポート 7112 ML-DSA65 (x25519:mlkem768 Hybrid KEX)
Hash used: UNDEF、Signature type: mldsa65 を表示
- ポート 7113 ML-DSA87 (x25519:mlkem768 Hybrid KEX)
Hash used: UNDEF、Signature type: mldsa87 を表示
- ポート 7201 ECDSA (mlkem768 PQC KEX)
Hash used: SHA256、Signature type: ecdsa_secp256r1_sha256 を表示
- ポート 7202 RSA (mlkem768 PQC KEX)
Hash used: SHA256、Signature type: rsa_pss_rsae_sha256 を表示
- ポート 7203 p256_falcon512 (mlkem768 PQC KEX)
Hash used: UNDEF、Signature type: p256_falcon512 を表示
- ポート 7204 p384_mldsa65 (mlkem768 PQC KEX)
Hash used: UNDEF、Signature type: p384_mldsa65 を表示
- ポート 7205 p521_falcon1024 (mlkem768 PQC KEX)
Hash used: UNDEF、Signature type: p521_falcon1024 を表示
- ポート 7206 p521_mldsa87 (mlkem768 PQC KEX)
Hash used: UNDEF、Signature type: p521_mldsa87 を表示
- ポート 7207 rsa3072_falcon512 (mlkem768 PQC KEX)
Hash used: UNDEF、Signature type: rsa3072_falcon512 を表示
- ポート 7208 rsa3072_mldsa44 (mlkem768 PQC KEX)
Hash used: UNDEF、Signature type: rsa3072_mldsa44 を表示
- ポート 7209 FALCON-512 (mlkem768 PQC KEX)
Hash used: UNDEF、Signature type: falcon512 を表示
- ポート 7210 FALCON-1024 (mlkem768 PQC KEX)
Hash used: UNDEF、Signature type: falcon1024 を表示
- ポート 7211 ML-DSA44 (mlkem768 PQC KEX)
Hash used: UNDEF、Signature type: mldsa44 を表示
- ポート 7212 ML-DSA65 (mlkem768 PQC KEX)
Hash used: UNDEF、Signature type: mldsa65 を表示
- ポート 7213 ML-DSA87 (mlkem768 PQC KEX)
Hash used: UNDEF、Signature type: mldsa87 を表示
-
失敗したもの
- -groups を指定しないデフォルト状態だとすべて失敗
まとめ
今回の検証では、13種類の署名アルゴリズム × 3種類の KEX(古典 / ハイブリッド / PQC)= 39 ポートの TLS1.3 テスト環境を構築し、ブラウザー / OpenSSL / OQS実装の挙動を比較しました。
結果として、現在のエコシステムは次のように整理できます。
-
Firefox / Chrome は Hybrid KEX(X25519MLKEM768)を標準サポートしており、鍵交換自体は問題なく行えるが、PQC署名やコンポジット証明書は使用できず失敗する。
-
レガシーのOpenSSLは PQC署名にも ハイブリッドKEXやPQC KEX にも未対応で、レガシー証明書のみ成功する。ただし、ハイブリッドKEXのサーバー相手でもレガシーKEXで接続できるため、見かけ上の結果はハイブリッドKEXに対応済みのブラウザーと同様になる。
-
OpenSSL 3.5+ (一部PQC対応版) は ML-DSA 署名のみ扱えるため、ML-DSA の証明書は接続可能だが、FALCONやコンポジット証明書は失敗する。
-
OQS‑OpenSSLは唯一、署名方式・KEX方式の全組み合わせを処理でき、39ポートすべて成功する。
このように、PQC移行には、KEX だけでなく 証明書と Web PKI 全体の進化が不可欠であることが分かります。
過去の記事「ネットワーク・スキャンによる簡易的なクリプト・インベントリーの作成」でご紹介した Pythonスクリプトを使った場合、ブラウザー や レガシー openssl と同様の結果となり、Classical KEX または Hybrid KEX の ECDSA および RSA証明書だけが取得できました。
これは、使用した Python 3.10.11 が、内部的に OpenSSL 1.1.1t を使用しており、レガシーのOpenSSLと同じ挙動になるためと考えられます。
また、商用製品である Guardium Cryptography Manager 2.0 FP1 を使用したところ、PQCに一部対応の openssl 3.5以降のクライアントと同様の結果となり、コンポジット証明書とFALCONについて未検出、かつ PQC KEX についても未検出 (-groupsオプションに相当する設定箇所なし)という結果でした。恐らく、内部的にOQSではない一般的なOpenSSLベースの仕組みを使用していると推測されます。
