ATS(App Transport Security)についてサーバーが対応しているかをnscurlで調査する

確認した時のメモ。
iOSもしくはMacアプリから通信するサーバーがATSに対応しているか確認する際にnscurlを使ってみた時のメモ。
Appleに詳しいわけではないため、間違っている部分などあればご指摘頂けると幸いです。

# ATS(App Transport Security)って何?

[NSAppTransportSecurity](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW33)

iOS及びMacOSのアプリで外部サーバーと通信する際にデータ流出などを防ぐために安全な通信を行うためのレギュレーション?のようなものをAppleが規定し、それに準拠しましょうというようなものだという認識です。(違っていたらごめんなさい)

必要要件は以下に記載があります。

[Requirements for Connecting Using ATS](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW57)

ざっくりいうと以下という感じかと思います

+ HTTPSでの接続とする(HTTPはダメ)
+ トランスポートレイヤーのプロトコルとしてTLS1.2を使う
+ 規定された暗号スイートを使う
+ SSL証明書の規定

ATS自体は「iOS 9.0 or OS X v10.11 SDKs or later」ということでiOS9からデフォルトで適用されているようですが、全てのドメインでATSを無効化することができました。(具体的にはNSAllowsArbitraryLoadsをYESにする)

しかし、上記のNSAllowsArbitraryLoadsについては「iOS 10 and later, and macOS 10.12 and later」では無視するよという記述があり、今までのようにとりあえずATS無効化ということでできないように見受けられます。

ATSの無効化だけでなく、特定のドメインごとにATSの例外を設定することができるのですが、こちらについてiOS10以降無視するような記述がないので特定のドメインであればiOS10でも通信することができる設定とすることができそうです。
しかし、AppStore審査のタイミングでチェックがあるなどの可能性もあり、可能ならばサーバー側でATS対応した方が良いと思われます。(実際に通信の安全性もあがる)

なお、ブラウザアプリの場合、ATS未対応のサーバーとの通知が必要だよなーと思っていたのですが、iOS10から設定が一つ増え、ブラウザの用途の場合には設定を有効化することでHTTP通信のみ許容するサーバーとも通信ができるような記事がありました。

[[iOS 10] iOS 10 での ATS 対応について](http://dev.classmethod.jp/smartphone/ats-in-ios-10/)

# nscurlを使って通信先サーバーがATSに対応しているか確認する

[Using the nscurl Tool to Diagnose ATS Connection Issues](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW41)

[接続先がATS (App Transport Security)に対応しているか、または例外の設定をnscurlコマンドで簡単に調べる](http://blog.kishikawakatsumi.com/entry/2015/10/19/121257)


前置きが長くなってしまいましたが、サーバーがATSに対応しているか否かを`nscurl`コマンドを使って確認します。
上記のコマンドはOS X v10.11以降であればインストールされているようです。

Appleのドキュメントにも書いてありますが、以下のようなフォーマットで対象のURLの調査ができます。

```bash
$/usr/bin/nscurl --ats-diagnostics [--verbose] URL
```

`--versbose`オプションを付与すると出力する情報が増え、チェック対象のURLに対してどのような例外設定をPlistファイルに書けば良いかまで教えてくれます。

試しに使ってみます。

```bash
$/usr/bin/nscurl --ats-diagnostics https://www.apple.com

Starting ATS Diagnostics

Configuring ATS Info.plist keys and displaying the result of HTTPS loads to https://www.apple.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
---

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

Configuring TLS exceptions for www.apple.com

---
TLSv1.2
Result : PASS
---

---
TLSv1.1
Result : PASS
---
・・・
```

長くなるので全ての標準出力は掲載しておりませんが、まず最初にATSでの接続が可能であるか確認(Default ATS Secure Connection)し、その後、各設定を行った状態での結果が記載されます。
サーバーがATSに対応していることが確認できます。

試しにTLSv1.2プロトコルでの暗号化を設定していない環境を作成し、対象のドメインに対してコマンドを実行してみました。

```bash
$/usr/bin/nscurl --ats-diagnostics https://www.xxxx.xxx.com

Starting ATS Diagnostics

Configuring ATS Info.plist keys and displaying the result of HTTPS loads to https://www.xxxxx.xxxxx/index.html.
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
2016-10-07 15:01:28.345 nscurl[58609:4114863] CFNetwork SSLHandshake failed (-9824)
2016-10-07 15:01:28.346 nscurl[58609:4114863] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
Result : FAIL
---

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

Allowing Arbitrary Loads

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

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

Configuring TLS exceptions for 
www.xxxx.com

---
TLSv1.2
2016-10-07 15:01:28.520 nscurl[58609:4114863] CFNetwork SSLHandshake failed (-9824)
2016-10-07 15:01:28.521 nscurl[58609:4114863] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
Result : FAIL
---
```

こちらだとDefault ATS Secure ConnectionについてはFailとなっており、対象サーバーに対してATSが対応していないことが確認できます。

二つ目の「Allowing Arbitrary Loads」については記載したようにATS自体の設定をOFFにした場合に接続できるかという検証なので当たり前ですがPASSとなっております。
以降では各設定を行った時に通信が成功するか否かが確認できます。

`--verbose`オプションを付与した時の結果も見てみます。
こちらでは例外の設定をPlistにどう書けばいいかを合わせて出力してくれるようです。

```bash
/usr/bin/nscurl --verbose --ats-diagnostics https://www.xxxx.com/index.html
Starting ATS Diagnostics

Configuring ATS Info.plist keys and displaying the result of HTTPS loads to https://www.xxxxx.com/index.html.
A test will "PASS" if URLSession:task:didCompleteWithError: returns a nil error.
================================================================================

Default ATS Secure Connection
---
ATS Default Connection
ATS Dictionary:
{
}
2016-10-10 21:33:18.456 nscurl[66237:5507912] CFNetwork SSLHandshake failed (-9824)
2016-10-10 21:33:18.457 nscurl[66237:5507912] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
Result : FAIL
Error : 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=0x7f9629743590 {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://www.xxxx.com/index.html, NSErrorFailingURLStringKey=https://www.xxxxx.com/index.html, _kCFStreamErrorDomainKey=3}
---

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

Allowing Arbitrary Loads

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

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

Configuring TLS exceptions for www.xxxxxx.com

---
TLSv1.2
ATS Dictionary:
{
    NSExceptionDomains =     {
        "www.xxxxxx.com" =         {
            NSExceptionMinimumTLSVersion = "TLSv1.2";
        };
    };
}
2016-10-10 21:33:18.687 nscurl[66237:5507912] CFNetwork SSLHandshake failed (-9824)
2016-10-10 21:33:18.688 nscurl[66237:5507912] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
Result : FAIL
Error : 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=0x7f9629632860 {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://www.xxxx.com/index.html, NSErrorFailingURLStringKey=https://www.xxxx.com/index.html, _kCFStreamErrorDomainKey=3}
---
```