24/7 twenty-four seven

iOS/OS X application programing topics.

接続先がATS (App Transport Security)に対応しているか、または例外の設定をnscurlコマンドで簡単に調べる

TL;DR,

$ nscurl --ats-diagnostics --verbose https://kishikawakatsumi.com/のようにnscurlコマンドに--ats-diagnostics --verboseオプションをつけて実行すると、指定したドメインがATSの要件を満たしているかどうかをチェックし、デフォルトの設定でエラーが起こる場合はエラー回避するための設定まで教えてくれます。

developer.apple.com


iOS 9からATS (App Transport Security)の仕組みが導入され、HTTP(HTTPSでない)通信はブロックされ、HTTPSでも接続先がATSの要件を満たしてない通信についてはデフォルトで失敗するように変更されました。

HTTPの通信はブロックされます。

App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
Error Domain=NSURLErrorDomain Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." UserInfo={NSUnderlyingError=0x7f9bb4099c00 {Error Domain=kCFErrorDomainCFNetwork Code=-1022 "(null)"}, NSErrorFailingURLStringKey=http://kishikawakatsumi.com/, NSErrorFailingURLKey=http://kishikawakatsumi.com/, NSLocalizedDescription=The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.}


HTTPSの通信でもTLS 1.0を使用しているなど、接続先がATSの要件を満たしていない場合はエラーになります。

CFNetwork SSLHandshake failed (-9824)
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={_kCFStreamErrorCodeKey=-9824, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSUnderlyingError=0x7fd8e9d09ae0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9824, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9824}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://kishikawakatsumi.com/, NSErrorFailingURLStringKey=https://kishikawakatsumi.com/, _kCFStreamErrorDomainKey=3}


この問題に対応するベストな方法はATSの要件を満たすように接続先を対応することですが、自分が管理しているサイトではないなどの場合は、クライアント側でATSをオフにするか、ドメインごとにATSの例外を設定することで通信できるようになります。

ATSをすべてオフにしてしまうのは乱暴なので、できるだけドメインごとの例外で対処したいところです。 ドメインごとの対応状況を調べて、またATSの例外設定のPlistをどのように記述したらいいのかまで教えてくれるのがnscurlコマンドです。

下記のように--ats-diagnosticsオプションを付けてnscurlコマンドを実行します。

$ nscurl --ats-diagnostics https://kishikawakatsumi.com/


すると、次のように順に設定を変えながら接続をテストして結果が報告されます。 Result : PASSになっていれば、その設定で通信が成功したことを示します。

Configuring ATS Info.plist keys and displaying the result of HTTPS loads to https://kishikawakatsumi.com/.
A test will "PASS" if URLSession:task:didCompleteWithError: returns a nil error.
Use '--verbose' to view the ATS dictionaries used and to display the error received in URLSession:task:didCompleteWithError:.
================================================================================

Default ATS Secure Connection
---
ATS Default Connection
Result : PASS
---

================================================================================

Allowing Arbitrary Loads

---
Allow All Loads
Result : PASS
---

(略)

---
TLSv1.1 with PFS disabled and insecure HTTP allowed
2015-10-19 12:01:31.289 nscurl[18324:1989232] CFNetwork SSLHandshake failed (-9801)
2015-10-19 12:01:31.337 nscurl[18324:1989232] CFNetwork SSLHandshake failed (-9801)
2015-10-19 12:01:31.383 nscurl[18324:1989232] CFNetwork SSLHandshake failed (-9801)
2015-10-19 12:01:31.384 nscurl[18324:1989232] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9801)
Result : FAIL
---

---
TLSv1.0 with PFS disabled and insecure HTTP allowed
Result : PASS
---

================================================================================


Result : FAILがある場合、接続先がATSの要件を満たしていないので、そのドメインを例外として設定する必要があります。 このとき、先ほどのコマンドに--verboseオプションを付けて実行すると、例外の設定をPlistにどう書けばいいかを合わせて出力してくれます。

$ nscurl --ats-diagnostics --verbose https://kishikawakatsumi.com/


下記の例だと、NSAllowsArbitraryLoads = trueはATSをオフにすることになるので、当たり前ですが通信に成功します。 順に設定を調べて行って、一番最後の、TLSの最低バージョンを1.0 NSExceptionMinimumTLSVersion = "TLSv1.0" とし、ForwardSecrecyを必須にしない NSExceptionRequiresForwardSecrecy = false とすることで接続できることがわかります。 設定のPlistをそのままダンプした形式で出力されているので、このままアプリケーションの設定に転記すればOKです。

================================================================================

Allowing Arbitrary Loads

---
Allow All Loads
ATS Dictionary:
{
    NSAllowsArbitraryLoads = true;
}
Result : PASS
---

================================================================================

Configuring TLS exceptions for kishikawakatsumi.com

---
TLSv1.2
ATS Dictionary:
{
    NSExceptionDomains =     {
        "kishikawakatsumi.com" =         {
            NSExceptionMinimumTLSVersion = "TLSv1.2";
        };
    };
}
2015-10-19 12:03:48.189 nscurl[18336:1992700] CFNetwork SSLHandshake failed (-9824)
2015-10-19 12:03:48.190 nscurl[18336:1992700] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
Result : FAIL

(略)

---
TLSv1.0 with PFS disabled and insecure HTTP allowed
ATS Dictionary:
{
    NSExceptionDomains =     {
        "kishikawakatsumi.com" =         {
            NSExceptionAllowsInsecureHTTPLoads = true;
            NSExceptionMinimumTLSVersion = "TLSv1.0";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : PASS
---