Yappli Tech Blog

株式会社ヤプリの開発メンバーによるブログです。最新の技術情報からチーム・働き方に関するテーマまで、日々の熱い想いを持って発信していきます。

OpenSSLを1.0.2からバージョンアップしたら本番環境でSOAP通信できなくなった話

この記事は 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のバージョンや設定されているセキュリティレベルを一度確認してみると良いかと思います。

さいごに

ヤプリではサーバーサイドエンジニアを随時募集しています! 興味を持った方、是非一度カジュアル面談を受けてみませんか??

open.talentio.com

最後まで読んでいただきありがとうございました!


  1. Diffie-Hellman鍵交換法(DH鍵交換法)における鍵交換プロセスを通じて生成された共通鍵のこと。Diffie-Hellman鍵交換法の詳細については【図解】素数とDiffie-Hellman鍵交換法 ~わかりやすい計算例とシーケンス,RFCや種類,アルゴリズムについて~ | SEの道標を参照下さい