如何在我的iOS应用程序中使用CFStream将客户端SSL证书发送到服务器?

时间:2013-06-10 06:48:34

标签: ios ssl websocket certificate

我们有一个使用SSL的WebSocket安全服务器。我们希望在我们的iOS客户端中放置客户端SSL证书,以确保与服务器通信时的安全性。

因为我们正在使用WebSocket,所以在iOS客户端中,我们使用SocketRocket(Objective-C WebSocket客户端库)来实现WebSocket通信。

问题是我不知道如何将我的客户端SSL证书发送到服务器。

我可以设置CFStream的属性,例如kCFStreamPropertySocketSecurityLevel。但我不知道它是如何工作的。我在CFStream中找不到任何关于证书的文档。

我知道当我们需要连接到HTTPS服务器时,我们可以在NSURLConnection中使用didReceiveAuthenticationChallenge。但据我所知,CFStream中没有对应物。

有人有什么想法吗?

1 个答案:

答案 0 :(得分:12)

经过大量的学习和尝试,我现在可以自己回答。也希望它对你有用。

实际上,我需要的是使用CFStream实现客户端SSL身份验证。所以我需要这样做:

  1. 将PKCS#12文件放入我的应用包
  2. 将此文件作为NSData发送至pkcsData
  3. 使用方法SecPKCS12Import导入pkcsData
  4. 从上面导入的数据中获取身份和证书,并生成证书数组
  5. 将数组设置为kCFStreamSSLCertificates
  6. kCFStreamPropertySSLSettings中的键CFWriteStreamRef

    以下示例代码:

      // Read .p12 file
      NSString *path = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"];
      NSData *pkcs12data = [[NSData alloc] initWithContentsOfFile:path];
    
      // Import .p12 data
      CFArrayRef keyref = NULL;
      OSStatus sanityChesk = SecPKCS12Import((__bridge CFDataRef)pkcs12data,
                                             (__bridge CFDictionaryRef)[NSDictionary
                                                                        dictionaryWithObject:@"123456"
                                                                        forKey:(__bridge id)kSecImportExportPassphrase],
                                             &keyref);
      if (sanityChesk != noErr) {
        NSLog(@"Error while importing pkcs12 [%ld]", sanityChesk);
      } else
        NSLog(@"Success opening p12 certificate.");
    
      // Identity
      CFDictionaryRef identityDict = CFArrayGetValueAtIndex(keyref, 0);
      SecIdentityRef identityRef = (SecIdentityRef)CFDictionaryGetValue(identityDict,
                                                                        kSecImportItemIdentity);
    
      // Cert
      SecCertificateRef cert = NULL;
      OSStatus status = SecIdentityCopyCertificate(identityRef, &cert);
      if (status)
        NSLog(@"SecIdentityCopyCertificate failed.");
    
      // the certificates array, containing the identity then the root certificate
      NSArray *myCerts = [[NSArray alloc] initWithObjects:(__bridge id)identityRef, (__bridge id)cert, nil];
    
      //
      [SSLOptions setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCFStreamSSLAllowsExpiredRoots];
      [SSLOptions setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCFStreamSSLAllowsExpiredCertificates];
      [SSLOptions setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCFStreamSSLAllowsAnyRoot];
      [SSLOptions setObject:[NSNumber numberWithBool:NO] forKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
      [SSLOptions setObject:@"test.domain.com:443" forKey:(NSString *)kCFStreamSSLPeerName];
      [SSLOptions setObject:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(NSString*)kCFStreamSSLLevel];
      [SSLOptions setObject:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(NSString*)kCFStreamPropertySocketSecurityLevel];
      [SSLOptions setObject:myCerts forKey:(NSString *)kCFStreamSSLCertificates];
      [SSLOptions setObject:[NSNumber numberWithBool:NO] forKey:(NSString *)kCFStreamSSLIsServer];
    
      [_outputStream setProperty:SSLOptions
                            forKey:(__bridge id)kCFStreamPropertySSLSettings];
    

    因为我使用SocketRocket,所以我在自己的fork中添加了这些代码:https://github.com/nickcheng/SocketRocket