その SSL、そんな実装で大丈夫か？ (Unity/C# 編)

2016年末から iOS の ATS 必須化が始まりますが、皆様 HTTPS への対応はお済みでしょうか。

今回は、Unity/C#(mono) で HTTPS を実装する場合の注意点を書いていきます。

## mono にはルート証明書が入っていない（重要！）

mono にはデフォルトでルート証明書がインストールされておらず、全ての SSL 通信は証明書の検証ができずに失敗します。

> ***[Mono - FAQ: Security](http://www.mono-project.com/docs/faq/security/)***
Note that a default installation of Mono doesn’t trust anyone!

全ての証明書を無条件で信頼するように実装すれば、いわゆるオレオレ証明書であっても無事に SSL で通信が出来るようになりますが、オレオレ詐欺にあっさりと引っかかるクライアントが出来てしまいます。

折角メッセージを SSL で暗号化しても、相手が詐欺師では、まったく意味がありません。

## ではどうすればいいのか

正しく検証を行うには、 ```UnityEngine.WWW``` 又は ```UnityEngine.Networking.UnityWebRequest``` を使うようにしましょう。
これらの API は OS にインストールされているルート証明書で検証してくれます。

それでも、何らかの理由により C# で実装するには、あまりオススメは出来ないが次のような手段が考えられる。

```csharp:example
using System.Collections.Generic;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

class ExampleClass
{
    void Example()
    {
        System.Net.ServicePointManager.ServerCertificateValidationCallback = OnRemoteCertificateValidationCallback;

        var request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create("https://api.example.com");
        request.Method = "GET";

        var response = (System.Net.HttpWebResponse)request.GetResponse();
        var statusCode = (int)response.StatusCode;
        response.Close();

        Debug.Log("status: " + statusCode);
    }


    // 信頼する証明書のハッシュリスト
    readonly List<string> TrustedThumbprints = new List<string>()
    {
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    };

    // 無視する X509 チェーンステータスを定義
    const X509ChainStatusFlags IgnoreChainStatus = 
            X509ChainStatusFlags.RevocationStatusUnknown |  // 証明書が失効しているかどうか判断できない
            X509ChainStatusFlags.OfflineRevocation |  // 証明書失効リストが使えなかった
            X509ChainStatusFlags.PartialChain |  // X509チェーンをルート証明書に構築できなかった
            X509ChainStatusFlags.UntrustedRoot;  // ルート証明書が信頼されていない

    private bool OnRemoteCertificateValidationCallback(
      object sender,
      X509Certificate certificate,
      X509Chain chain,
      SslPolicyErrors sslPolicyErrors)
    {
        // エラーがなければ OK
        if (sslPolicyErrors == SslPolicyErrors.None)
        {
            return true;
        }

        // 信頼するハッシュリストと比較し、一致するなら OK
        if (TrustedThumbprints.Contains(((X509Certificate2)certificate).Thumbprint))
        {
            return true;
        }

        // SslPolicyError.RemoteCertificateChainErrors 以外のエラーがあるなら NG
        if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0)
        {
            return false;
        }

        // IgnoreChainStatus 以外のチェーンエラーがあるなら NG
        for (int i = 0; i < chain.ChainStatus.Length; ++i)
        {
            if ((chain.ChainStatus[i].Status & ~IgnoreChainStatus) != 0)
            {
                return false;
            }
        }

        // 証明書チェーン内に信頼する証明書と一致するものがあれば OK とする
        for (int i = 0; i < chain.ChainElements.Count; ++i)
        {
            var element = chain.ChainElements[i];
            if (TrustedThumbprints.Contains(element.Certificate.Thumbprint))
            {
                TrustedThumbprints.Add(element.Certificate.Thumbprint);
                return true;
            }
        }
        return false;
    }
}
```

上記の実装には次のような条件がある。

- 接続しうるドメインネームにて提供されるサービスを信頼できる
- TrustedThumbprints と一致する証明書とサーバー証明書間の中間証明書を信頼できる
- サーバー証明書の CA 証明書が変更になる場合は事前に対策が必要


## 利用している HTTP ライブラリをチェックしよう

Unity 標準の機能を使っていない場合、利用している HTTP ライブラリがどのような実装になっているか確認してみましょう。 *Asset Store* で販売されている **UniWeb** というアセットは次のような実装になっています。

```csharp:Request.cs
#if USE_SSL
        static bool ValidateServerCertificate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            //This is where you implement logic to determine if you trust the certificate.
            //By default, we trust all certificates.
            return true;
        }
#endif
```

証明書の検証はライブラリ利用者が実装する形になっており、デフォルトでは全ての証明書を信頼するようになっています。

## ATS との関係

iOS の ATS が有効になることにより、非セキュアな HTTP 通信は使えなくなりますが、ATS の制限の対象になるのは ***NSURLSession*** や ***NSURLConnection*** 等の API であり、***System.Net.HttpWebRequest*** の実装はこれらの API を使用しないので制限の対象外となります。

> ***[Cocoa Keys](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html) - Using ATS in Apple Frameworks***
App Transport Security (ATS) is enforced by the NSURLSession class and all APIs that use it. ATS is automatically enabled when you link your app against the iOS 9.0 SDK or later or against the OS X v10.11 SDK or later. (The older NSURLConnection class also enforces ATS when you link against the iOS 9.0 SDK or later or against the OS X v10.11 SDK or later.) ATS protections are not available when using lower-level networking APIs provided by Apple, or when using third-party networking libraries.


## さいごに

Apple の ATS 必須化はデベロッパーにとっては、面倒くさい対応ごとが増えたという印象にとらえられるかもしれませんが、デベロッパーの安易な HTTP の利用を抑制することで、ユーザーのデータ通信の安全性を向上させる意図があります。
ユーザーのデータを守るため、自社サービスを守るため、適切なプロトコルを選択しましょう。