SSL証明書のCA選びを間違えると詰む話

SSL証明書が原因で、Appleからのウェブフック(Webhook)を受け取れなかった苦い経験談です。 Appleは、何が問題なのか親切に教えてくれないんですよね…。

問題の状況

Apple IDの退会などのイベントが発生した際、私たちのサービスにHTTPリクエストを送信してくれる機能があります。 apple2ourserver

これを活用すれば、Apple IDの退会時に自社サービスのあアカウントも自動的に休眠処理する機能を実装できます。

Appleのダッシュボードでエンドポイントを設定するだけで簡単に終わると思っていました。 しかし、ウェブフックのリクエストが全く届かない…。

奇妙なことに、ngrokやウェブフックテストサイトでは正常にリクエストが受信できていました。 apple2otherserver

テストサービスと自社APIサービスの違いは何か?

自社サーバーとテスト用サービス、一体何が違って通信ができなかったのでしょうか? SSLの問題を疑い、SSLチェックサイトで確認してみました。

そこで目についたのが、Trustedの項目でJavaが赤色で表示されていることでした。 sslchecker_trusted

Javaで「Trustedではない」というのは、どういう意味なのでしょうか?

SSL通信プロセスの理解

Trustedとは、クライアントがそのサーバー証明書を信頼できるかどうかを示す状態です。 これを理解するために、SSL通信のプロセスを段階別に整理しました。

1. 準備段階

ssl_step1

  1. CA(認証局)は非対称鍵(公開鍵/秘密鍵)を生成します。
  2. クライアントは信頼できるCAのリストを管理します。
    • OS、Mozilla、Javaなどで管理されるトラストストア(Trust Store)は異なる場合があります。

2. 証明書発行・設定段階

ssl_step2

  1. サーバーは非対称鍵を生成し、サーバー公開鍵をCAに渡します。
  2. CAはサーバー公開鍵をハッシュ化した後、自身の秘密鍵で署名して証明書を発行します。
  3. サーバーはCAが発行した証明書を設定します。

ここでいう「CA署名(CA Signature)」とは、CAがサーバー公開鍵を検証した後に署名した値を指します。

3. クライアント・サーバー通信段階

ssl_step3

  1. クライアントがサーバーにSSL接続を要求します。
  2. サーバーは証明書をクライアントに渡します。
  3. クライアントは証明書のCA署名を検証します。
    • この時、クライアントは自身のトラストストアに保存されているCA公開鍵を使用します。
  4. 検証が完了すると、双方は通信に使用する共通鍵を交換します。
  5. 共通鍵でデータを暗号化し、リクエスト/レスポンスをやり取りします。

通信エラーの原因

私が使用した証明書のCAが、Javaのトラストストアに含まれていませんでした。 AppleのウェブフッククライアントはJavaで開発されており、Javaが信頼していないCAでSSL接続しようとするとエラーが発生します。

つまり、Javaクライアントの立場からすると、私たちのサーバー証明書は「信頼できない状態」だったのです。

まとめ

  1. CAの選択によって、一部のクライアントが証明書を信頼してくれないことがあります。
  2. 今回のケースでは、韓国のCAであるTuringSignの公開鍵がJavaに内蔵されておらず、通信エラーが発生しました。
  3. Java環境を含むグローバルサービスと通信する可能性がある場合は、DigicertLet's Encryptのような広く普及しているCAを選ぶのが安全です。
    • ちなみに、Let's Encryptは無料で利用可能です。