web-dev-qa-db-ja.com

iOS:キーチェーンにSSL証明書をプレインストールする-プログラムで

ユーザーがサイトにアクセスする前に、キーチェーンに証明書をインストール/保存したい。 HTTPSサーバーがあり、アプリはユーザーが https:// mysite にアクセスする前にユーザーを認証します。キーチェーンのPOSTリクエストを介して証明書をインストール/保存する方法はありますか? ORその証明書(ファイル)をリソースバンドルにコピーして、信頼できるとマークします。

ありがとう

al

19
Deam

Der形式のサーバー証明書を取得したら、次のコードを試すことができます。

+ (void) addCertToKeychain:(NSData*)certInDer
{
    OSStatus            err = noErr;
    SecCertificateRef   cert;

    cert = SecCertificateCreateWithData(NULL, (CFDataRef) certInDer);
    assert(cert != NULL);

    CFTypeRef result;

    NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
                          (id)kSecClassCertificate, kSecClass,
                          cert, kSecValueRef, 
                          nil];

    err = SecItemAdd((CFDictionaryRef)dict, &result);
    assert(err == noErr || err == errSecDuplicateItem);

    CFRelease(cert);
}

アプリケーションのキーチェーンサンドボックスに証明書が追加されます。つまり、他のアプリケーションが証明書を信頼することはありません。

15
MrTJ

差出人: http://blog.asolutions.com/2011/02/using-tls-with-self-signed-certificates-or-custom-root-certificates-in-ios/

サーバーの証明書をキーチェーンに追加するか、手動で検証を実行するかの2つのオプションを利用できます。アプローチに関係なく、アプリにDERでエンコードされたX.509公開証明書を含める必要があります。以下の例では、「ios-trusted-cert.der」という名前で、それを使用してSecCertificateRefを作成します。 (サーバーの証明書がルート認証局へのチェーンの一部である場合は、サーバーの証明書ではなくルート認証局をインストールする必要があります。)

NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSData *iosTrustedCertDerData =
  [NSData dataWithContentsOfFile:[bundle pathForResource:@"ios-trusted-cert"
                                                    ofType:@"der"]];
SecCertificateRef certificate =
  SecCertificateCreateWithData(NULL,
                               (CFDataRef) iosTrustedCertDerData);

SecCertificateCreateWithDataはメモリ所有権の作成ルールに従うため、メモリリークを回避するために、不要になったときにCFReleaseする必要があることに注意してください。

次に、アプリのキーチェーンに証明書を追加できます。これは、iOSが作成するすべての新しいソケットに対して証明書を信頼するようにする場合に適しています。

- (void) useKeychain: (SecCertificateRef) certificate {
  OSStatus err =
    SecItemAdd((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
                                  (id) kSecClassCertificate, kSecClass,
                                  certificate, kSecValueRef,
                                  nil],
               NULL);
  if ((err == noErr) || // success!
    (err == errSecDuplicateItem)) { // the cert was already added.  Success!
    // create your socket normally.
    // This is oversimplified.  Refer to the CFNetwork Guide for more details.
    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;
    CFStreamCreatePairWithSocketToHost(NULL,
                                       (CFStringRef)@"localhost",
                                       8443,
                                       &readStream,
                                       &writeStream);
    CFReadStreamSetProperty(readStream,
                            kCFStreamPropertySocketSecurityLevel,
                            kCFStreamSocketSecurityLevelTLSv1);
    CFReadStreamOpen(readStream);
    CFWriteStreamOpen(writeStream);
  } else {
    // handle the error.  There is probably something wrong with your cert.
  }
}

作成しているソケットの証明書のみを確認し、アプリ内の他のソケットの証明書を確認したくない場合は、証明書への信頼を手動で確認できます。まず、ソケットを作成し(サーバーが、クライアントと同じマシンのポート8443でリッスンしていると想定)、SSL設定で証明書チェーンの検証を無効にします。

- (void) verifiesManually: (SecCertificateRef) certificate {
  CFReadStreamRef readStream;
  CFWriteStreamRef writeStream;
  CFStreamCreatePairWithSocketToHost(NULL,
                                     (CFStringRef)@"localhost",
                                     8443,
                                     &readStream,
                                     &writeStream);
  // Set this kCFStreamPropertySocketSecurityLevel before
  // setting kCFStreamPropertySSLSettings.
  // Setting kCFStreamPropertySocketSecurityLevel
  // appears to override previous settings in kCFStreamPropertySSLSettings
  CFReadStreamSetProperty(readStream,
                          kCFStreamPropertySocketSecurityLevel,
                          kCFStreamSocketSecurityLevelTLSv1);
  // this disables certificate chain validation in ssl settings.
  NSDictionary *sslSettings =
    [NSDictionary dictionaryWithObjectsAndKeys:
     (id)kCFBooleanFalse, (id)kCFStreamSSLValidatesCertificateChain,
     nil];
  CFReadStreamSetProperty(readStream,
                          kCFStreamPropertySSLSettings,
                          sslSettings);
  NSInputStream *inputStream = (NSInputStream *)readStream;
  NSOutputStream *outputStream = (NSOutputStream *)writeStream;
  [inputStream setDelegate:self];
  [outputStream setDelegate:self];
  [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                         forMode:NSDefaultRunLoopMode];
  [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                          forMode:NSDefaultRunLoopMode];
  CFReadStreamOpen(readStream);
  CFWriteStreamOpen(writeStream);
}

次に、ソケットがデータを書き込む準備ができているというコールバックを受信したら、サーバーにデータを書き込んだり、サーバーからデータを読み取ったりする前に、サーバーに含まれている証明書の信頼性を確認する必要があります。まず(1)、接続したサーバーのホスト名を使用してクライアントSSLポリシーを作成します。ホスト名はサーバーの証明書に含まれており、DNSの送信先のサーバーが信頼できるサーバーであることを認証します。次に(2)、ソケットから実際のサーバー証明書を取得します。サーバーの証明書が証明書チェーンの一部である場合、サーバーに複数の証明書が関連付けられている可能性があります。実際のサーバー証明書がある場合は、(3)信頼オブジェクトを作成できます。信頼オブジェクトは、信頼評価のローカルコンテキストを表します。キーチェーン証明書がすべての信頼できるソケットに適用されるのに対し、それは個々の信頼評価を分離します。信頼オブジェクトを取得したら、(4)信頼する証明書であるアンカー証明書を設定できます。最後に(5)、信頼オブジェクトを評価し、サーバーが信頼できるかどうかを確認できます。

#pragma mark -
#pragma mark NSStreamDelegate
- (void)stream:(NSStream *)aStream
   handleEvent:(NSStreamEvent)eventCode {
  switch (eventCode) {
    case NSStreamEventNone:
    break;
    case NSStreamEventOpenCompleted:
    break;
    case NSStreamEventHasBytesAvailable:
    break;
    case NSStreamEventHasSpaceAvailable:
      // #1
      // NO for client, YES for server.  In this example, we are a client
      // replace "localhost" with the name of the server to which you are connecting
      SecPolicyRef policy = SecPolicyCreateSSL(NO, CFSTR("localhost"));
      SecTrustRef trust = NULL;
      // #2
      CFArrayRef streamCertificates =
        [aStream propertyForKey:(NSString *) kCFStreamPropertySSLPeerCertificates];
      // #3
      SecTrustCreateWithCertificates(streamCertificates,
                                     policy,
                                     &trust);
      // #4
      SecTrustSetAnchorCertificates(trust,
                                    (CFArrayRef) [NSArray arrayWithObject:(id) self.certificate]);
      // #5
      SecTrustResultType trustResultType = kSecTrustResultInvalid;
      OSStatus status = SecTrustEvaluate(trust, &trustResultType);
      if (status == errSecSuccess) {
        // expect trustResultType == kSecTrustResultUnspecified
        // until my cert exists in the keychain see technote for more detail.
        if (trustResultType == kSecTrustResultUnspecified) {
          NSLog(@"We can trust this certificate! TrustResultType: %d", trustResultType);
        } else {
          NSLog(@"Cannot trust certificate. TrustResultType: %d", trustResultType);
        }
      } else {
        NSLog(@"Creating trust failed: %d", status);
        [aStream close];
      }
      if (trust) {
        CFRelease(trust);
      }
      if (policy) {
        CFRelease(policy);
      }
    break;
    case NSStreamEventErrorOccurred:
      NSLog(@"unexpected NSStreamEventErrorOccurred: %@", [aStream streamError]);
    break;
    case NSStreamEventEndEncountered:
    break;
    default:
    break;
  }
}
7
Deam