Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to send a client SSL certificate to server by using CFStream in my iOS app?

We have a WebSocket security server working on SSL. We hope to put a client SSL certificate in our iOS client, in order to ensure the security when communicating with server.

Because we are using WebSocket, in iOS client, we use SocketRocket(Objective-C WebSocket client library) to implement WebSocket communication.

The problem is I have no idea on how to send my client SSL certificate to server.

I can set the properties of CFStream, like kCFStreamPropertySocketSecurityLevel. But I don't know how it works. And I can't find any docs about certificate in CFStream.

I know that when we need to connect to a HTTPS server, we can use didReceiveAuthenticationChallenge in NSURLConnection. But And as I know, there wasn't a counterpart in CFStream.

Could someone have any ideas?

like image 212
nickcheng Avatar asked Jun 10 '13 06:06

nickcheng


People also ask

How do I get client authentication certificate?

Create a client certificate request. After receiving the certificate, export it to a password-protected PKCS12 file and send the password and the file to the user. Make sure the file is securely sent.


1 Answers

After a lot of study and trying, I can answer myself now. Also hope it'll be useful for you.

Actually, what I need is implementing client SSL authentication by using CFStream. So I need to do these:

  1. Put PKCS #12 file in my app bundle
  2. Read this file as NSData to pkcsData
  3. Use method SecPKCS12Import to import pkcsData
  4. Get identity and cert from the data you imported above, and generate a certificates array
  5. Set the array to key kCFStreamSSLCertificates in kCFStreamPropertySSLSettings of your CFWriteStreamRef

Sample code below:

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

Because I use SocketRocket, I've added these code in my own fork: https://github.com/nickcheng/SocketRocket

like image 117
nickcheng Avatar answered Nov 04 '22 08:11

nickcheng