Apple の TLS 証明書無効の影響を受けた話


認証認可技術 Advent Calendar 2019 15日目は TLS に関する記事です。TLS は 通信相手の認証、通信内容の暗号化、改ざんの検出を可能とするという通信プロトコルであり、HTTPS 通信で使用され、また OAuth 2.0 は TLS を前提とした仕様になっていたりととても重要な認証技術要素の一つです。その TLS について、今年行われた Apple の TLS 証明書無効化 による影響を受けたというお話を軽く紹介したいと思います。

はじまり

ちょうど一ヶ月ほど前に、最新の macOS Catalina を搭載した新型 MacBoo Pro が発表されました。タイミングよく開発機を新調しようとしていたところに発表され、人柱になるかもしれませんが(なりました)購入したわけです。で、ちょうど先週納品されてウキウキしながらセットアップし始めたのですが・・・

しかし、即この TLS 証明書無効化による影響をうけてドハマリしたわけです

Apple の TLS 証明書無効化

Apple は2019年6月5日、以前より危険性が指摘されてきたハッシュアルゴリズムSHA-1を使用した TLS 証明書 を、iOS 13 および macOS 10.15(つまり Catalina)から無効とする方針を表明したことは皆さんご存知でしょうか? 当時 ITmedia エンタープライズ などのニュースサイトでも取り上げられていました(自分は記憶にありませんでしたが。。。)

Apple からの公式発表は以下になります。

SHA-1 を使った TLS 証明書はすでに Chrome、Firefox、MS Edge、Internet Explorer 11 でもサポートされていないので、特に気にする人はいなかったかもしれません。

受けた影響

自分はソフトウェア開発用途で Mac を使うため、開封の儀を済ませてとりあえずパッケージマネージャーの Homebrew をインストールしようとしたわけです。 Homebrew はターミナルより以下のコマンドを実行するとインストールすることができます。

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

このコマンドは https://raw.githubusercontent.com/Homebrew/install/master/install の Ruby で記述されたプログラムを取得し実行するのですが、 スクリプト中では某設計図共有サイトより Git リポジトリをフェッチしてインストールを行っています。ここでサーバーの TLS 証明書が信頼できずにアクセスできずエラーとなってしまいました。

企業内ネットワークだとよくある話だと思いますが、社内からのインターネットアクセスにはプロキシサーバを経由する必要があり、かつ特定サイトにアクセスするには別途自己署名のルート証明書を手元のマシンにインポートし、信頼するように設定が必要でした。今回すでにその設定は行っていましたが、なぜかアクセスできずでした。 切り分けのため curl コマンド で Git リポジトリに直接アクセスしても以下のようにエラーになってしまいます。Windows マシンからだと問題なくアクセスできるのに、です。

curl: (60) SSL certificate problem: self signed certificate in certificate chain
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

これは Catalina があやしいなぁと思いつつ、とりあえずエラーメッセージ + mac + Catalina とかでググってみると、以下の Symfony(PHP のフレームワーク) の CLI ツールの Issue が見つかりました。

コメントを読んでいくと、ここで先の Apple の公式発表ページ を発見したわけです。

何が問題だったか

実は問題は、ニュースサイトの見出しにあったSHA-1ではありませんでした。前述の Apple の公式発表や英語のニュースサイトをちゃんと読むと、SHA-1 以外に無効となる条件が書かれているのです。以下、原文の引用です。

All TLS server certificates must comply with these new security requirements in iOS 13 and macOS 10.15:

  • TLS server certificates and issuing CAs using RSA keys must use key sizes greater than or equal to 2048 bits. Certificates using RSA key sizes smaller than 2048 bits are no longer trusted for TLS.
  • TLS server certificates and issuing CAs must use a hash algorithm from the SHA-2 family in the signature algorithm. SHA-1 signed certificates are no longer trusted for TLS.
  • TLS server certificates must present the DNS name of the server in the Subject Alternative Name extension of the certificate. DNS names in the CommonName of a certificate are no longer trusted.

Additionally, all TLS server certificates issued after July 1, 2019 (as indicated in the NotBefore field of the certificate) must follow these guidelines:

  • TLS server certificates must contain an ExtendedKeyUsage (EKU) extension containing the id-kp-serverAuth OID.
  • TLS server certificates must have a validity period of 825 days or fewer (as expressed in the NotBefore and NotAfter fields of the certificate).

SHA-1 以外にもいくつかNGとなる条件が書かれています。

  • RSA使う場合はキーサイズは 2048bit 未満だとNG
  • ハッシュアルゴリズム SHA-1 だとNG(これが記事の見出しにかかれている点)
  • 証明書の中に SAN(Subject Alternative Name) で DNS 名が入っていないとNG(CommonName ではNG)
  • 加えて 2019年7月1日 以降に発行された証明書の場合は、
    • EKU(ExtendedKeyUsage) にサーバ認証(1.3.6.1.5.5.7.3.1) が入っていないとNG
    • 有効期間が 825日(27ヶ月) より長いとNG

というのが無効となる条件だったわけです。

実際、自分がエラーとなってしまった証明書の中を見てみると、上記条件の一つに引っかかっていました。。。

解決方法

原因が分かったものの、自己署名のルート証明書で署名されるサーバー証明書側(つまり、社内のプロキシサーバー側)を対応してもらう必要があります。そのうち対応してくれるとは思いますが、とても待ってられません というわけで別の対応を考えました。

やっては駄目な方法

たとえば curl だと -k--insecure オプション指定し、アクセス先サーバー証明書の検証を行わないという方法があります。が、これだとニセサイトにすり替えられても検知できず超危険です。絶対にやめましょう

MITM

MITM(Man-in-the-middle attack:中間者攻撃)という TLS のようなセキュアチャンネル上の通信の盗聴方式があります。これは、クライアントとサーバー間の通信にて、間で一旦セキュアチャンネルの通信を終了して平文に復号化しつつ、再度暗号化してクライアントに流すという方式です。この方式を TLS で利用し、先の Apple の信頼済み証明書の要件 に合うように自己署名のサーバー署名書を生成すればうまくいきそうです。ただし、 Apple が駄目よと言っているリスクのあるサーバー証明書を受け入れてしまうのでその点は注意。あくまで妥協案であり、プロキシサーバー側が正式対応されるまでのワークアラウンドです。

というわけで実装してみた

Go 言語は自前で TLS を実装していることで知られています([go-nuts] Why did Go implement its own SSL library?)ので、Apple のこの変更の影響を受けずに TLS を処理することができます。というわけで Go で自前のプロキシを実装してみました。幸いにも MITM のサンプルは、作って学ぶ 「Https Man in The Middle Proxy」 in Go などがありますので参考にして実装することができます。今回、下記のようなシーケンスで実装しています。ざっくり処理概要を言うと、

  • 初回アクセス時に自前プロキシにて別途バックエンドに TLS 接続を行いサーバー証明書が Apple 要件を満たすかチェックし、問題なければ MITM せずにそのまま通信
  • Apple 要件に引っかかってしまう場合は、要件を満たすサーバー証明書を生成した上で MITM した通信を行う

なお、今回作ったものをもうちょいブラッシュアップ&pacファイル対応など追加して作ったものをこちらに置いています。同じようにこの TLS 問題に遭遇した方には役立つかもしれません

まとめ

今回は Apple の TLS 証明書無効にまつわるお話の紹介でした。私が遭遇した社内プロキシ起因の問題だけでなく、開発環境やテスト環境などでオレオレなサーバー証明書を使って構築されている方は、同じように影響を受ける可能性があります(前述のSymfony CLI の Issue なんかはまさにツールで開発用途に TLS 証明書を生成しており、有効期限が長すぎて Catalina では使えなくなってしまった、という問題のようです)。

というわけで無事にセットアップできましたー!