iOS9から導入されたATSとは?そして回避する方法は?
(2016/06/18 追記)
以下の記事によると、2017年1月1日から全てのiOSアプリにATSに対応しないといけなくなるそうです。
その場合はこの記事にあるような回避策は有効じゃない可能性があります。ご承知ください。
japan.cnet.com
(以下本編)
※ この記事は2015年10月3日の情報を元に公開しています。
概要
Xcode6を使ってた時代はWebViewがちゃんと動いていたのですが、最近Xcode7にアップデートして動かしてみると、ページが表示されなくなって焦ったわけで。。
そこで調べてみるとATSという機能によって動かないことがわかったので、その知見を共有しておきます。
ATSとは
ATSとは「App Transport Security」の略で、ざっくり言うと「接続先URLがHTTPSでかつセキュアでないサーバー証明書を使ってないなら接続しねーぞこら。」という機能です。
OSX, iOS アプリケーションが NSURLConnection, CFURL, NSURLSession を利用してサーバに接続する際 現時点で最善に近いセキュアな接続を達成するために導入されたものとのこと。
開発者はATS導入を受けて、サーバー証明書をAppleが要求する暗号化スイートに変更するか、ATS機能側の例外設定を適切にする必要があります。
ATSはiOS9.0以降、MacOSX10.11以降で使用可能となります(というか勝手に機能しだします。)
ATSのデフォルト動作
NSURLConnection
, CFURL
, NSURLSession
を利用してサーバーと接続する場合はATSが動作し、下記要求を満たさない場合は接続できなくなります。(CFNetwork SSLHandshake failed (-9824)
なんてエラーが表示される)
These are the App Transport Security requirements:
- The server must support at least Transport Layer Security (TLS) protocol version 1.2.
- Connection ciphers are limited to those that provide forward secrecy (see the list of ciphers below.)
- Certificates must be signed using a SHA256 or greater signature hash algorithm, with either a 2048-bit or greater RSA key or a 256-bit or greater Elliptic-Curve (ECC) key.
Invalid certificates result in a hard failure and no connection.
- サーバは TLS 1.2 をサポートしていなければならない
- 利用できる暗号化スイート(Cipher Suite)は、Forward Secrecy を提供できる(下記リスト)ものに限られる
- 利用されるサーバ証明書はSHA256以上のハッシュアルゴリズムによって署名されており、2048ビット以上のRSA 鍵、もしくは256ビット以上のECC鍵が使われている必要がある
検証できない証明書はエラーとなり接続ができない
なお利用できる暗号化スイート(Cipher Suite)のリストは以下の通り。
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
- TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
Cipher suiteを調べる
エラーCipher suiteを確認するために接続先のWebサーバーで
$ openssl ciphers -v
と叩いても確認はできるらしいですが、今回はQUALYS SSL LabsのSSL Testを使って確認してみました(使い方はドメイン貼り付けてスタートさせるだけなので割愛)。
実行させてみると以下の様なリストがあるので、AppleのデフォルトのCipher suiteと比較します。
結果を見てみると、僕の場合はどうも鍵交換アルゴリズム(kx)がDHEが使われていることが問題のようです(ATSのデフォルト設定ではECDHEのみ許可されている)。
回避方法
もちろん証明書は現時点で最もセキュリティが高い暗号化スイートを利用しているのにこしたことが無いが、どうしてもATSを有効にするのは厳しい状況もあるはず(例えば自社で証明書を管理していないとかの政治的な事情とか)。
公式ドキュメントにも書かれている通り、ATS機能についての例外設定が可能です。
例外設定はinfo.plistに書きます(Xcode使ってるならポチポチで可能)。
設定構造はこんな感じです。
- NSAppTransportSecurity (Dictionary)
- NSAllowsArbitraryLoads (Boolean)
- NSExceptionDomains (Dictionary)
- <domain-name-for-exception-as-string> (Dictionary)
- NSExceptionMinimumTLSVersion (String)
- NSExceptionRequiresForwardSecrecy (Boolc)
- NSExceptionAllowsInsecureHTTPLoads (Boolean)
- NSRequiresCertificateTransparency (Boolean)
- NSIncludesSubdomains (Boolean)
- NSThirdPartyExceptionMinimumTLSVersion (String)
- NSThirdPartyExceptionRequiresForwardSecrecy (Boolean)
- NSThirdPartyExceptionAllowsInsecureHTTPLoads (Boolean)
- <domain-name-for-exception-as-string> (Dictionary)
NSAppTransportSecurity
トップレベルのDictionary。この配下に設定を加えていく
NSAllowsArbitraryLoads
こいつをYESすると、ATS機能が無効となる。つまりデフォルトはNO
NSExceptionDomains
特定のドメインに対する個別の設定を行うDictionary
<domain-name-for-exception-as-string>
特定のドメインを記載するDictionaly。keyに「apple.com」のようにドメインを設定する
NSExceptionMinimumTLSVersion
接続を許可するTLSのミニマムのバージョンを記載する。使用できる文字列は
- TLSv1.0
- TLSv1.1
- TLSv1.2(デフォルトはこれ)
NSExceptionRequiresForwardSecrecy
許可する鍵交換アルゴリズムの設定。デフォルトはYES。
YESの場合は上記のCipher Suiteのリストに限られるが、Noにすると以下のリストも許可するようになる。
- TLS_RSA_WITH_AES_256_GCM_SHA384
- TLS_RSA_WITH_AES_128_GCM_SHA256
- TLS_RSA_WITH_AES_256_CBC_SHA256
- TLS_RSA_WITH_AES_256_CBC_SHA
- TLS_RSA_WITH_AES_128_CBC_SHA256
- TLS_RSA_WITH_AES_128_CBC_SHA
NSExceptionAllowsInsecureHTTPLoads
セキュアでないHTTPS接続を許可するかどうか。
NOにすると許可せず、YESにすると許可する。(デフォルトはNO)
- セキュアでないHTTPS接続
- no certificate
- with an error for a self-signed
- expired
- hostname mismatch certificate.
NSIncludesSubdomains
指定したトップレベルドメインに対する全てのサブドメインにおいて、設定を適用されるかどうか。
NOにすると設定を適用せず、YESにすると設定を適用させる。(デフォルトはNO)
NSThirdPartyExceptionMinimumTLSVersion
アプリ開発者が変更不可能なサービスに対するTLSのミニマムのバージョンを設定する。
(Appleがどうやって変更不可能かどうかを判断しているのかは不明でした...)
NSThirdPartyExceptionRequiresForwardSecrecy
アプリ開発者が変更不可能なサービスに対する、許可する鍵交換アルゴリズムを設定する。
(これもNSThirdPartyExceptionMinimumTLSVersion同様不明...)
NSThirdPartyExceptionAllowsInsecureHTTPLoads
アプリ開発者が変更不可能なサービスに対する、セキュアでないHTTPS接続を許可するかどうかを設定する。
(これもNSThirdPartyExceptionMinimumTLSVersion同様不明...)
今回の場合は鍵交換アルゴリズムが許可されていないものだったので、NSExceptionRequiresForwardSecrecyをNOにすることに。
Xcode上では感じで設定。
内部ではこんな感じ。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <false/> <key>NSExceptionDomains</key> <dict> <key>example.com</key> <dict> <key>NSExceptionMinimumTLSVersion</key> <string>TLSv1.2</string> <key>NSExceptionRequiresForwardSecrecy</key> <false/> <key>NSExceptionAllowsInsecureHTTPLoads</key> <false/> <key>NSIncludesSubdomains</key> <false/> </dict> </dict> </dict> ... </dict> </plist>
そして再ビルドするとページが表示されました!!
ひとまず助かったという感じですが、理想はサーバー証明書をAppleが要求する暗号化スイートを使ったものにすることなので油断はしてはいけないですねー。