7

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

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

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

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

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

有人有什么想法吗?

4

1 回答 1

14

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

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

  1. 将 PKCS #12 文件放入我的应用程序包中
  2. 阅读此NSData文件pkcsData
  3. 使用方法SecPKCS12Import导入pkcsData
  4. 从上面导入的数据中获取身份和证书,并生成证书数组
  5. 将数组设置为键入您kCFStreamSSLCertificateskCFStreamPropertySSLSettingsCFWriteStreamRef

下面的示例代码:

  // 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

于 2013-06-14T10:26:18.000 回答