iOSアプリで自己証明書を使ったhttps通信

のくみあわせで自己署名証明書 (self-signed certificate) を使う場合の話。

allowsAnyHTTPSCertificateForHost: を override するとかいうマヌケなことはしないこと。 (昔のクソ blog などによく書かれていた完全に間違った方法。このご時世では使えなくなっている気がするが未確認)

1. TN2232 を読む

2. TN2326 にある手順で Certificates を作成

3. server.crt を変換 (CA の certificate のはそのままで OK)

  openssl x509 -in server.crt -outform DER -out server.der

4. DER 形式の ceriticate (2 つ) を xcode の project に import

5. -[NSURLSessionTaskDelegate URLSession:task:didReceiveChallenge:completionHandler:] を実装

エラー処理などを省略して雑に書くとこんな感じになる:

if ([challenge previousFailureCount] == 0 && [challenge proposedCredential] && [[challenge proposedCredential] hasPassword]) {
  completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
  return;
}

NSURLProtectionSpace * protectionSpace = [challenge protectionSpace];
SecTrustRef trust = [protectionSpace serverTrust];
NSURLCredential * credential = [NSURLCredential credentialForTrust: trust];

NSArray *certs = @[
  CFBridgingRelease(SecCertificateCreateWithData(NULL, dataForCA)),
  CFBridgingRelease(SecCertificateCreateWithData(NULL, dataForServer))
];

SecTrustSetAnchorCertificatesOnly(trust, true);
SecTrustSetAnchorCertificates(trust, (CFArrayRef)certs);

SecTrustEvaluateAsync(
  trust,
  NULL,
  ^(SecTrustRef  _Nonnull trustRef, SecTrustResultType trustResult) {
    BOOL trusted = (trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified);
    if (trusted)
      completion(NSURLSessionAuthChallengeUseCredential, credential);
    else
      completion(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
  });

おまけ: stone の設定方法

stone -z cert=server.crt -z key=server.key HOST:PORT SSL-PORT/ssl

おまけ2: Android

※ 古い情報なので最新の情報を確認の上で使うように

あたりを参照のこと。

なお、 BouncyCastle の version に注意。1.47 以降ではなく 1.46 を使う必要がある。 でないと “java.io.IOException: Wrong version of key store.” で死ぬ。