Be an Engineer.

社会人からWEBエンジニアになった人間の備忘録的勉強記録

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.

日本語でおk

  • サーバは 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と比較します。

f:id:shirakiya:20151004014455p:plain

結果を見てみると、僕の場合はどうも鍵交換アルゴリズム(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)

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にすると以下のリストも許可するようになる。

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上では感じで設定。

f:id:shirakiya:20151003234647p:plain

内部ではこんな感じ。

<?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が要求する暗号化スイートを使ったものにすることなので油断はしてはいけないですねー。

参考資料