Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Client SSL Certificate from .mobileconfig within Enterprise iOS app

We're trying to use Client SSL certificates for user authentication in an enterprise iOS app.

  • We can generate the client ssl cert on the server
  • The user can install this through a .mobileconfig
  • Authentication to a web server in Safari works with the installed cert.
  • Making an http request from inside an iOS app fails (the certificate is not used).

How do we get this to work? Thanks!

like image 826
Christopher Stott Avatar asked Apr 20 '15 17:04

Christopher Stott


1 Answers

Overview:

You have installed the Client SSL Certificate on the device keychain.

Safari.app and Mail.app has access to this keychain while the iOS app doesn't.

The reason is the apps that we develop are sandboxed and doesn't have any access rights outside of it in the non-jailbroken device.

As safari has access to it,it had no trouble connecting and authenticating against the server challenge.

Solution:

Include the exported P12 file with the App bundle and refer to it to find the correct client certificate the server was looking for.It is actually a workaround. The hardcoding is the reliable way to grab the P12 file.

Implementation:

Method in question is willSendRequestForAuthenticationChallenge in NSURLConenction delegate. You need to account for NSURLAuthenticationMethodClientCertificate challenge type inorder to handle the server challenge. This is where we implemented the magic to extract the correct certificate identity from the embedded P12 file. Code is below

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    if ([challenge previousFailureCount] > 0) {
       //this will cause an authentication failure
       [[challenge sender] cancelAuthenticationChallenge:challenge];
       NSLog(@"Bad Username Or Password");        
       return;
    }



     //this is checking the server certificate
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            SecTrustResultType result;
            //This takes the serverTrust object and checkes it against your keychain
            SecTrustEvaluate(challenge.protectionSpace.serverTrust, &result);

            //if we want to ignore invalid server for certificates, we just accept the server
            if (kSPAllowInvalidServerCertificates) {
                [challenge.sender useCredential:[NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] forAuthenticationChallenge: challenge];
                return;
            } else if(result == kSecTrustResultProceed || result == kSecTrustResultConfirm ||  result == kSecTrustResultUnspecified) {
                //When testing this against a trusted server I got kSecTrustResultUnspecified every time. But the other two match the description of a trusted server
                [challenge.sender useCredential:[NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] forAuthenticationChallenge: challenge];
                return;
            }
        } else if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {
        //this handles authenticating the client certificate

       /* 
 What we need to do here is get the certificate and an an identity so we can do this:
   NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:myCerts persistence:NSURLCredentialPersistencePermanent];
   [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];

   It's easy to load the certificate using the code in -installCertificate
   It's more difficult to get the identity.
   We can get it from a .p12 file, but you need a passphrase:
   */

   NSString *p12Path = [[BundleManager bundleForCurrentSkin] pathForResource:kP12FileName ofType:@"p12"];
   NSData *p12Data = [[NSData alloc] initWithContentsOfFile:p12Path];

   CFStringRef password = CFSTR("PASSWORD");
   const void *keys[] = { kSecImportExportPassphrase };
   const void *values[] = { password };
   CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
   CFArrayRef p12Items;

   OSStatus result = SecPKCS12Import((CFDataRef)p12Data, optionsDictionary, &p12Items);

   if(result == noErr) {
             CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0);
             SecIdentityRef identityApp =(SecIdentityRef)CFDictionaryGetValue(identityDict,kSecImportItemIdentity);

             SecCertificateRef certRef;
             SecIdentityCopyCertificate(identityApp, &certRef);

             SecCertificateRef certArray[1] = { certRef };
             CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
             CFRelease(certRef);

             NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp certificates:(NSArray *)myCerts persistence:NSURLCredentialPersistencePermanent];
             CFRelease(myCerts);

             [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
         }
    } else if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodDefault || [[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodNTLM) {
   // For normal authentication based on username and password. This could be NTLM or Default.

        DAVCredentials *cred = _parentSession.credentials;
        NSURLCredential *credential = [NSURLCredential credentialWithUser:cred.username password:cred.password persistence:NSURLCredentialPersistenceForSession];
    [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
    } else {
        //If everything fails, we cancel the challenge.
        [[challenge sender] cancelAuthenticationChallenge:challenge];
    }
}

Reference: Ref1, Ref2, Ref3

Hope this helps

like image 69
Durai Amuthan.H Avatar answered Nov 03 '22 12:11

Durai Amuthan.H