Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS: Pre install SSL certificate in keychain - programmatically

I want to install/save a certificate in the keychain before the user visits the site. I have a HTTPS server, and my app authenticates the user before they go to https://mysite.

Is there a way that I can install/save the certificate via a POST request in the keychain or can I copy that certificate (the file) to the resource bundle to mark it trusted?

like image 498
Deam Avatar asked Mar 16 '11 10:03

Deam


2 Answers

Once you have the server certificate in der format you can try the following code:

+ (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);
}

It will add the certificate to the keychain sandbox of your application i.e. no other application will trust your cert.

like image 142
MrTJ Avatar answered Oct 06 '22 00:10

MrTJ


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

You have two options available: add your server’s certificate to the keychain or perform validation manually. Regardless of your approach, you’ll need to include a DER-encoded X.509 public certificate in your app. In the example below, it is named “ios-trusted-cert.der”) and create a SecCertificateRef with it. (If your server’s certificate is part of a chain to a root certificate authority, you should install the root certificate authority rather than your server’s certificate.)

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

Remember that SecCertificateCreateWithData follows the create rule of memory ownership, so you must CFRelease it when you no longer need it to avoid memory leaks.

Next, you can add your cert to your app’s keychain. This is appropriate when you want iOS to trust your cert for every new socket you create.

- (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.
  }
}

If you only want to verify the cert for the socket you are creating and for no other sockets in your app, you can verify your trust in the cert manually. First, create a socket (assuming your server is listening on port 8443 on the same machine as your client) and disable its certificate chain validation in its ssl settings:

- (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);
}

Then, when you receive a callback that your socket is ready to write data, you should verify trust in the certificate your server included before writing any data to or reading any data from the server. First (1), create a client SSL policy with the hostname of the server to which you connected. The hostname is included in the server’s cert to authenticate that the server to which DNS directed you is the server you trust. Next (2), you grab the actual server certificates from the socket. There may be multiple certificates associated with the server if the server’s certificate is part of a certificate chain. When you have the actual server certificates, you can (3) create a trust object. The trust object represents a local context for trust evaluations. It isolates individual trust evaluations whereas the keychain certificates apply to all trusted sockets. After you have a trust object, you can (4) set the anchor certificates, which are the certificates you trust. Finally (5), you can evaluate the trust object and discover whether the server can be trusted.

#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;
  }
}
like image 26
Deam Avatar answered Oct 06 '22 00:10

Deam