ATS通信の診断メモ

## Motivation

iOS9からATS (Apple Transport Security)によって, HTTPSでなければ通信がブロックされます。
じゃあ、HTTPS通信であれば問題ないのかといえば、必ずしもそうもいきません。

例えば、開発用サーバがオレオレSSLみたいな証明書で対応している場合だと、TLSや要件でブロックされることもあります。

> NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)

ATSの要件をクリアするため、ATSをすべてオフしてしまうこともできますが、ちょっと乱暴でリジェクト対象でもあります。現実的な方法としては、例外としてTLSプロトコルのバージョンや特定のドメイン名を記載し条件を緩和する方法があります。しかし、どんな設定ならばATS要件をクリアできるかが分かりににくいところです。

## ATS診断

macOSには標準で `nscurl` コマンドに `--ats-diagnostics` オプションを追加して実行すると、ATSの要件を満たしているか診断を下してくれます。

```
$ nscurl --ats-diagnostics --verbose https://<ドメイン名> 
```

`--verbose`オプションも追加しておけば、 `Plist`ファイルの例外設定に何を記載すべきを教えてくれます。

## チェックポイント

`Result : PASS`:  その設定で通信が成功したことを示します。

`Result : FAIL`:  接続先がATSの要件を満たしていないので、そのドメインを例外として設定する必要があります。

## 例外設定

```
TLSv1.0 with PFS disabled and insecure HTTP allowed
ATS Dictionary:
{
    NSExceptionDomains =     {
        "dev.*****.com" =         {
            NSExceptionAllowsInsecureHTTPLoads = true;
            NSExceptionMinimumTLSVersion = "TLSv1.0";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
```
TLSの最低バージョンを1.0 `NSExceptionMinimumTLSVersion = "TLSv1.0"` とし、ForwardSecrecyを必須にしない `NSExceptionRequiresForwardSecrecy = false` とすることで接続できることが分かります。 また、設定のPlistをそのままダンプした形式で出力されているので、このままアプリケーションの設定に追記すれば大丈夫です。

## Reference 

詳細は [Cocoa Keys](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW57)の **Using the nscurl Tool to Diagnose ATS Connection Issues** を参照下さい。


## 実行例

例えば、 `dev.*****.com `というドメイン名で実行すると、次のようになります。

```
Starting ATS Diagnostics

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

Default ATS Secure Connection
---
ATS Default Connection
ATS Dictionary:
{
}
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x7fad21c43010 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=54, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://dev.*****.com/, NSErrorFailingURLKey=https://dev.*****.com/, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=54, NSLocalizedDescription=The network connection was lost.}
---

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

Allowing Arbitrary Loads

---
Allow All Loads
ATS Dictionary:
{
    NSAllowsArbitraryLoads = true;
}
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x7fad21d24c40 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=54, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://dev.*****.com/, NSErrorFailingURLKey=https://dev.*****.com/, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=54, NSLocalizedDescription=The network connection was lost.}
---

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

Configuring TLS exceptions for dev.*****.com

---
TLSv1.2
ATS Dictionary:
{
    NSExceptionDomains =     {
        "dev.*****.com" =         {
            NSExceptionMinimumTLSVersion = "TLSv1.2";
        };
    };
}
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x7fad21f00250 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=54, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://dev.*****.com/, NSErrorFailingURLKey=https://dev.*****.com/, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=54, NSLocalizedDescription=The network connection was lost.}
---

---
TLSv1.1
ATS Dictionary:
{
    NSExceptionDomains =     {
        "dev.*****.com" =         {
            NSExceptionMinimumTLSVersion = "TLSv1.1";
        };
    };
}
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x7fad21e0fe80 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=54, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://dev.*****.com/, NSErrorFailingURLKey=https://dev.*****.com/, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=54, NSLocalizedDescription=The network connection was lost.}
---

---
TLSv1.0
ATS Dictionary:
{
    NSExceptionDomains =     {
        "dev.*****.com" =         {
            NSExceptionMinimumTLSVersion = "TLSv1.0";
        };
    };
}
Result : FAIL
Error : Error Domain=NSURLErrorDo

main Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x7fad21d0cda0 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=54, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://dev.*****.com/, NSErrorFailingURLKey=https://dev.*****.com/, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=54, NSLocalizedDescription=The network connection was lost.}
---

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

Configuring PFS exceptions for dev.*****.com

---
Disabling Perfect Forward Secrecy
ATS Dictionary:
{
    NSExceptionDomains =     {
        "dev.*****.com" =         {
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x7fad21f08870 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=54, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://dev.*****.com/, NSErrorFailingURLKey=https://dev.*****.com/, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=54, NSLocalizedDescription=The network connection was lost.}
---

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

Configuring PFS exceptions and allowing insecure HTTP for dev.*****.com

---
Disabling Perfect Forward Secrecy and Allowing Insecure HTTP
ATS Dictionary:
{
    NSExceptionDomains =     {
        "dev.*****.com" =         {
            NSExceptionAllowsInsecureHTTPLoads = true;
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x7fad21d1f090 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=54, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://dev.*****.com/, NSErrorFailingURLKey=https://dev.*****.com/, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=54, NSLocalizedDescription=The network connection was lost.}
---

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

Configuring TLS exceptions with PFS disabled for dev.*****.com

---
TLSv1.2 with PFS disabled
ATS Dictionary:
{
    NSExceptionDomains =     {
        "dev.*****.com" =         {
            NSExceptionMinimumTLSVersion = "TLSv1.2";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x7fad21e1b090 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=54, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://dev.*****.com/, NSErrorFailingURLKey=https://dev.*****.com/, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=54, NSLocalizedDescription=The network connection was lost.}
---

---
TLSv1.1 with PFS disabled
ATS Dictionary:
{
    NSExceptionDomains =     {
        "dev.*****.com" =         {
            NSExceptionMinimumTLSVersion = "TLSv1.1";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x7fad21d29720 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=54, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://dev.*****.com/, NSErrorFailingURLKey=https://dev.*****.com/, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=54, NSLocalizedDescription=The network connection was lost.}
---

---
TLSv1.0 with PFS disabled
ATS Dictionary:
{
    NSExceptionDomains =     {
        "dev.*****.com" =         {
            NSExceptionMinimumTLSVersion = "TLSv1.0";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x7fad21d29e70 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=54, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://dev.*****.com/, NSErrorFailingURLKey=https://dev.*****.com/, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=54, NSLocalizedDescription=The network connection was lost.}
---

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

Configuring TLS exceptions with PFS disabled and insecure HTTP allowed for dev.*****.com

---
TLSv1.2 with PFS disabled and insecure HTTP allowed
ATS Dictionary:
{
    NSExceptionDomains =     {
        "dev.*****.com" =         {
            NSExceptionAllowsInsecureHTTPLoads = true;
            NSExceptionMinimumTLSVersion = "TLSv1.2";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x7fad21e0f1f0 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=54, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://dev.*****.com/, NSErrorFailingURLKey=https://dev.*****.com/, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=54, NSLocalizedDescription=The network connection was lost.}
---

---
TLSv1.1 with PFS disabled and insecure HTTP allowed
ATS Dictionary:
{
    NSExceptionDomains =     {
        "dev.*****.com" =         {
            NSExceptionAllowsInsecureHTTPLoads = true;
            NSExceptionMinimumTLSVersion = "TLSv1.1";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x7fad21f14e00 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=54, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://dev.*****.com/, NSErrorFailingURLKey=https://dev.*****.com/, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=54, NSLocalizedDescription=The network connection was lost.}
---

---
TLSv1.0 with PFS disabled and insecure HTTP allowed
ATS Dictionary:
{
    NSExceptionDomains =     {
        "dev.*****.com" =         {
            NSExceptionAllowsInsecureHTTPLoads = true;
            NSExceptionMinimumTLSVersion = "TLSv1.0";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x7fad21d28300 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=54, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://dev.*****.com/, NSErrorFailingURLKey=https://dev.*****.com/, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=54, NSLocalizedDescription=The network connection was lost.}
---

```