この記事は Yappli Advent Calendar 2024 の8日目の記事です。
はじめに
こんにちは。サーバーサイドエンジニアの佐野きよ(@Kiyo_Karl2)です。
先月PHP + Laravel on EC2で動いているサービスをECS/Fargateへ移行したのですが、その際にサーバーにインストールされているOpenSSLのバージョンが1.0.2と古いバージョンだったのでついでにアップデートを実施しました。
アップデートを実施したところステージング環境の動作検証では問題無くSOAP通信できていたのですが、本番環境で実行したらSOAP通信で失敗するという事象が発生しました。
今回はその事象について紹介したいと思います。
発生した事象の詳細と原因
ローカルで本番環境と同じXMLファイルを利用して実行すると以下のようなエラーが発生していることがわかりました。
https://external-wsdl.com
はSOAP通信で利用しているXMLファイルがホスティングされている外部サーバーです。
(外部サービスとの連携でSOAP通信をしており、その外部サービスからXMLファイルが提供されています。セキュリティの都合でURLは適当なものにしています。)
WARNING SoapClient::SoapClient(): SSL operation failed with code 1. OpenSSL Error messages: error:141A318A:SSL routines:tls_process_ske_dhe:dh key too small SoapFault SOAP-ERROR: Parsing WSDL: Couldn't load from 'https://external-wsdl.com' : failed to load external entity "https://external-wsdl.com".
上記エラーを見ると、dh key too small
とあり、どうやら https://external-wsdl.com
のサーバー証明書で利用されているDH鍵1の長さが弊社のサーバーが要求する水準より短かかったために発生しているエラーのようです。
このエラーが発生したのは、上述した通りECS/Fargateへ移行した際にOpenSSLのバージョンがあがったことに起因します。
実はOpenSSLのバージョン1.1.1以降では、SECLEVEL2
がデフォルトで適用されるように変更されたのですが、この変更によりDH鍵長は最低でも2048ビット以上であることが要求されるようになります。
1.0.2から1.1.1以降のバージョンにアップデートしたことにより、デフォルトのセキュリティレベル2が適用されDH鍵長の要件が厳しくなったことで今回の事象が発生しました。
wiki.debian.org
なぜ本番環境でのみ発生していたのか?
実は上述したXMLファイルがホスティングされているURLがステージングと本番環境で異なっていたため、この差異が怪しいということは推測できました。
つまり、ステージングではDH鍵長が2048ビットのものがSSL通信で利用されており、本番環境では2048ビット未満のものがSSL通信で利用されているのではないか?ということです。
なので、それぞれの環境で利用されているDH鍵長の長さを確認することができれば裏取りをすることができそうです。
testssl.sh
testssl.shというシェルスクリプトベースのSSL/TLSのテストツールがあり、これを利用することで該当サーバーで利用されるDH鍵長を調べることができます。
以下が実行結果の抜粋です。
XMLファイルがホスティングされている本番とステージングのURLに対してtestsslを実行すると、本番では DH 1024
と出力され、ステージングでは DH 2048
というワードが出力されていることから、ステージング環境でのみ2048ビットのDH鍵が利用されていることがわかります。
本番
→ ./testssl.sh prod-domain:443 ##################################################################### testssl.sh version 3.2rc3 from https://testssl.sh/dev/ (65c463f 2024-11-19 20:49:27) This program is free software. Distribution and modification under GPLv2 permitted. USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK! Please file bugs @ https://testssl.sh/bugs/ ##################################################################### Using OpenSSL 3.4.0 22 Oct 2024 [~96 ciphers] on xxxxxx:/opt/homebrew/bin/openssl Start 2024-11-20 13:47:50 -->> xxx.xxx.xx.xx:443 (prod-domain) <<-- Service detected: HTTP Testing protocols via sockets except NPN+ALPN SSLv2 not offered (OK) SSLv3 not offered (OK) TLS 1 not offered TLS 1.1 not offered TLS 1.2 offered (OK) TLS 1.3 not offered and downgraded to a weaker protocol NPN/SPDY not offered ALPN/HTTP2 http/1.1 (offered) Testing cipher categories NULL ciphers (no encryption) not offered (OK) Anonymous NULL Ciphers (no authentication) not offered (OK) Export ciphers (w/o ADH+NULL) not offered (OK) ...(省略) Testing server's cipher preferences Hexcode Cipher Suite Name (OpenSSL) KeyExch. Encryption Bits Cipher Suite Name (IANA/RFC) ----------------------------------------------------------------------------------------------------------------------------- SSLv2 - SSLv3 - TLSv1 - TLSv1.1 - TLSv1.2 (no server order, thus listed by strength) x9f DHE-RSA-AES256-GCM-SHA384 DH 1024 AESGCM 256 TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 x6b DHE-RSA-AES256-SHA256 DH 1024 AES 256 TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 x39 DHE-RSA-AES256-SHA DH 1024 AES 256 TLS_DHE_RSA_WITH_AES_256_CBC_SHA x9d AES256-GCM-SHA384 RSA AESGCM 256 TLS_RSA_WITH_AES_256_GCM_SHA384 x3d AES256-SHA256 RSA AES 256 TLS_RSA_WITH_AES_256_CBC_SHA256 x9e DHE-RSA-AES128-GCM-SHA256 DH 1024 AESGCM 128 TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 x67 DHE-RSA-AES128-SHA256 DH 1024 AES 128 TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 x33 DHE-RSA-AES128-SHA DH 1024 AES 128 TLS_DHE_RSA_WITH_AES_128_CBC_SHA ... TLSv1.3 - ...(省略)
ステージング
→ ./testssl.sh stg-domain ##################################################################### testssl.sh version 3.2rc3 from https://testssl.sh/dev/ (65c463f 2024-11-19 20:49:27) This program is free software. Distribution and modification under GPLv2 permitted. USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK! Please file bugs @ https://testssl.sh/bugs/ ##################################################################### Using OpenSSL 3.4.0 22 Oct 2024 [~96 ciphers] on xxxxxx:/opt/homebrew/bin/openssl Start 2024-11-20 13:49:33 -->> xxx.xxx.xx.xx:443 (stg-domain) <<-- Service detected: HTTP Testing protocols via sockets except NPN+ALPN SSLv2 not offered (OK) SSLv3 not offered (OK) TLS 1 not offered TLS 1.1 not offered TLS 1.2 offered (OK) TLS 1.3 not offered and downgraded to a weaker protocol NPN/SPDY not offered ALPN/HTTP2 not offered Testing cipher categories NULL ciphers (no encryption) not offered (OK) Anonymous NULL Ciphers (no authentication) offered (NOT ok) Export ciphers (w/o ADH+NULL) not offered (OK) ...(省略) Testing server's cipher preferences Hexcode Cipher Suite Name (OpenSSL) KeyExch. Encryption Bits Cipher Suite Name (IANA/RFC) ----------------------------------------------------------------------------------------------------------------------------- SSLv2 - SSLv3 - TLSv1 - TLSv1.1 - TLSv1.2 (no server order, thus listed by strength) xc030 ECDHE-RSA-AES256-GCM-SHA384 ECDH 256 AESGCM 256 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 xc028 ECDHE-RSA-AES256-SHA384 ECDH 256 AES 256 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 xc014 ECDHE-RSA-AES256-SHA ECDH 256 AES 256 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA x9f DHE-RSA-AES256-GCM-SHA384 DH 2048 AESGCM 256 TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 x6b DHE-RSA-AES256-SHA256 DH 2048 AES 256 TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 x39 DHE-RSA-AES256-SHA DH 2048 AES 256 TLS_DHE_RSA_WITH_AES_256_CBC_SHA x88 DHE-RSA-CAMELLIA256-SHA DH 2048 Camellia 256 TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA xc019 AECDH-AES256-SHA ECDH 256 AES 256 TLS_ECDH_anon_WITH_AES_256_CBC_SHA x9d AES256-GCM-SHA384 RSA AESGCM 256 TLS_RSA_WITH_AES_256_GCM_SHA384 x3d AES256-SHA256 RSA AES 256 TLS_RSA_WITH_AES_256_CBC_SHA256 x35 AES256-SHA RSA AES 256 TLS_RSA_WITH_AES_256_CBC_SHA x84 CAMELLIA256-SHA RSA Camellia 256 TLS_RSA_WITH_CAMELLIA_256_CBC_SHA xc02f ECDHE-RSA-AES128-GCM-SHA256 ECDH 256 AESGCM 128 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 xc027 ECDHE-RSA-AES128-SHA256 ECDH 256 AES 128 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 xc013 ECDHE-RSA-AES128-SHA ECDH 256 AES 128 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA x9e DHE-RSA-AES128-GCM-SHA256 DH 2048 AESGCM 128 TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 x67 DHE-RSA-AES128-SHA256 DH 2048 AES 128 TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 x33 DHE-RSA-AES128-SHA DH 2048 AES 128 TLS_DHE_RSA_WITH_AES_128_CBC_SHA x9a DHE-RSA-SEED-SHA DH 2048 SEED 128 TLS_DHE_RSA_WITH_SEED_CBC_SHA x45 DHE-RSA-CAMELLIA128-SHA DH 2048 Camellia 128 TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA xc018 AECDH-AES128-SHA ECDH 256 AES 128 TLS_ECDH_anon_WITH_AES_128_CBC_SHA x9c AES128-GCM-SHA256 RSA AESGCM 128 TLS_RSA_WITH_AES_128_GCM_SHA256 ... TLSv1.3 - ...(省略)
どのように解決したか
解決方法は大きく分けて2通りあるかと思います。
- 該当サーバーで利用するDH鍵長を2048ビット以上のものにする
- SOAP通信している該当の箇所だけセキュリティレベルを1に下げる
今回は対象のサーバーが外部連携しているサーバーであり、弊社管轄外のサーバーだったため後者の対応を取りました。
イメージとしては以下のような感じで stream_context_create()を利用してSSL通信時にセキュリティレベルをさげるコンテキストオプションを付与するように改修しました。(あくまでも解説用のサンプルであり、動作保証はありません。)
<?php // HTTPヘッダーを設定 $aHTTP['http']['header'] = "User-Agent: PHP-SOAP/5.5.11\r\n"; $aHTTP['http']['header'] .= "username: XXXXXXXXXXX\r\n" . "password: XXXXX\r\n"; // SSL通信のセキュリティレベルを1に設定 $aHTTP['ssl'] = [ 'security_level' => 1, // OpenSSLのセキュリティレベルを1に設定 ]; $context = stream_context_create($aHTTP); $client = new SoapClient("https://ocppws-cert.extra.bcv.org.ve:443/AltoValor/BancoUniversal?WSDL", [ 'trace' => 1, // デバッグ用 'stream_context' => $context, ]); // SOAPメソッドを呼び出す $result = $client->jornadaActiva();
まとめ
本番環境でしか発生しないエラーだったため、再現確認が難しかったのと、なぜ本番環境で発生したのか?という原因調査が大変でした。dh key too small
というエラーにもし遭遇したら、OpenSSLのバージョンや設定されているセキュリティレベルを一度確認してみると良いかと思います。
さいごに
ヤプリではサーバーサイドエンジニアを随時募集しています! 興味を持った方、是非一度カジュアル面談を受けてみませんか??
最後まで読んでいただきありがとうございました!
- Diffie-Hellman鍵交換法(DH鍵交換法)における鍵交換プロセスを通じて生成された共通鍵のこと。Diffie-Hellman鍵交換法の詳細については【図解】素数とDiffie-Hellman鍵交換法 ~わかりやすい計算例とシーケンス,RFCや種類,アルゴリズムについて~ | SEの道標を参照下さい↩