OpenSSLを利用して接続したServerの証明書検証について


はじめに

間違いがある場合はご指摘いただけると嬉しい
あと、素直にSwiftかObj-Cで実装したほうが絶対にいい

どのような方向けのTipsか?

  1. Verisign、Let's Encryptなどから発行されたサーバ証明書および中間証明書を持つSSLサーバに、 iOS端末からNSURLConnectionなどではなく、OpenSSLのAPIを用いてSSL接続を行いたい
  2. ただし、接続後にサーバからクライアント側に渡されたサーバ証明書および中間証明書が正しいかどうかを、iOSのシステムに組み込まれたルートCA証明書まで辿って検証したい
  3. ここで、iOSに於いてはルートCA証明書リストへのアクセスは不可能(と思われる)
  4. つまり、OpenSSLで提供されている、SSL_CTX_load_verify_locationsやSSL_CTX_set_default_verify_pathsといったAPIが利用できず、BundleにルートCA証明書リストなどを追加しておかなくてはならない(オレオレ証明書を通すのと同様に)
  5. これは嫌だ!

という方向け

解決方法

OpenSSL(C/C++)での実装

  1. SSL_set_verifyを用いて、SSL_VERIFY_NONEにしておく (なお、verifycallbackを利用する、という手段は未検証)
  2. SSL_Connectする
  3. SSL_get_peer_certificate_chainする
    これにより、サーバ証明書(と中間証明書)が得られる
    得られた証明書は、X509形式のポインタで表されているため、OpenSSLで提供されるAPIを利用して、iOSで取り扱うことのできる、DER形式のバイナリ列に直す
  unsigned char* buf = NULL; // NULL  
  int len = i2d_X509(cert, &buf); // NULLを与えると、新たにmallocしてDERを返してくれる。勿論、後ほどfree必須

Objective-C側の実装

CFMutableArrayRef certs = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); // 最大配列個数 => 指定なし
for (サーバから返答された証明書チェーンに含まれる証明書の個数) {
  CFDataRef data = CFDataCreate(kCFAllocatorDefault, buf, len); // 前節3.で得られたDERへのポインタと長さ
  SecCertificateRef cert = SecCertificateCreateWithData(NULL, data);
  CFArrayAppendValue(certs, cert);
}
SecTrustRef trust;
OSStatus status = SecTrustCreateWithCertificates(certs, SecPolicyreateBasicX509(), &trust);
SecTrustResultType r = kSecTrustResultInvalid;
status = SecTrustEvaluate(trust, &r);

これで、rの返り値がkSecTrustResultProceed==1もしくはkSecTrustResultUnspecified==4であれば、サーバ証明書検証成功1
なお、サーバから渡された証明書に中間CAが含まれていないなどのミスでルートCAまで辿れなかったり、オレオレ証明書+オレオレCA証明書(システムにインストールされていない証明書)がサーバから渡された場合は、kSecTrustResultRecoverableTrustFailure == 5となる

最後に

この辺りを実装したコードは、
https://github.com/linear-rpc/linear-objc/blob/master/src/LinearSSLClient.mm#L89
にあるので、興味があればご参照を


  1. https://developer.apple.com/library/mac/qa/qa1360/_index.html
    The semantics behind receiving a kSecTrustResultUnspecified (4) error from Security APIs is that the certificate is indeed valid. However, the user has not explicitly set the trust settings for the certificate via Keychain Access.