Client certificate authentication in UIWebView iOS

10,558

To avoid any problem in the UIWebView, you have to make a reques to you website root, with the client certificate, before the request of the web view. You can use the UIWebViewDelegate method:

-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

After this, the UIWebView will be able to load everything without any problem.

If you are new to Objective-C, I guess you are also new to the Foundation framework so here's a bit of help.

To solve this, I used ASIHTTPRequest as it was already embedded in our project. But you can use a NSURLConnection and do the logic in the NSURLConnectionDelegate method:

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

So, here's my code to provide a client certificate to an ASIHTTPRequest prior to a UIWebView request:

-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{

  SecIdentityRef identity = NULL;
  SecTrustRef trust       = NULL;  
  NSData *PKCS12Data      = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"test.cert" ofType:@"pfx"]];

  [self extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data];

  NSURL *serverUrl              = [NSURL URLWithString:URL_SECURE_SERVER];
  ASIHTTPRequest *firstRequest  = [ASIHTTPRequest requestWithURL:serverUrl];

  [firstRequest setValidatesSecureCertificate:NO];
  [firstRequest setClientCertificateIdentity:identity];
  [firstRequest startSynchronous];

  return YES;
}

I'm sending the request synchronously to ensure its completion before letting the UIWebView starts its loading.

I use a method to retrieve the identity from the certificate, which is:

- (BOOL)extractIdentity:(SecIdentityRef *)outIdentity andTrust:(SecTrustRef*)outTrust fromPKCS12Data:(NSData *)inPKCS12Data
{
  OSStatus securityError          = errSecSuccess;
  NSDictionary *optionsDictionary = [NSDictionary dictionaryWithObject:@"mobigate" forKey:(id)kSecImportExportPassphrase];

  CFArrayRef items  = CFArrayCreate(NULL, 0, 0, NULL);
  securityError     = SecPKCS12Import((CFDataRef)inPKCS12Data,(CFDictionaryRef)optionsDictionary,&items);

  if (securityError == 0) { 
    CFDictionaryRef myIdentityAndTrust  = CFArrayGetValueAtIndex (items, 0);
    const void *tempIdentity            = NULL;

    tempIdentity = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemIdentity);
    *outIdentity = (SecIdentityRef)tempIdentity;

    const void *tempTrust = NULL;

    tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);
    *outTrust = (SecTrustRef)tempTrust;

  } 
  else {
    NSLog(@"Failed with error code %d",(int)securityError);
    return NO;
  }

  return YES;
}

Here the same technique, but using the NSURLConnection instead of the ASIHTTPRequest

  • get your SecIdentityRef and your SecCertificateRef
  • create a NSURLCredential with those infos
  • send back this NSURLCredential to the [challenge sender] in the connection:didReceiveAuthenticationChallenge: method

to use a certificate with a NSURLConnection, you have to implement the the NSURLConnectionDelegate method:

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

In this method, the NSURLConnection is telling you that it received a challenge. You will have to create a NSURLCredential to send back to the [challenge sender]

So you create your NSURLCredential:

+ (NSURLCredential *)credentialWithIdentity:(SecIdentityRef)identity certificates:(NSArray *)certArray persistence:(NSURLCredentialPersistence)persistence
{

  NSString *certPath = [[NSBundle mainBundle] pathForResource:@"certificate" ofType:@"cer"];
  NSData *certData   = [[NSData alloc] initWithContentsOfFile:certPath];

  SecIdentityRef myIdentity;  // ???

  SecCertificateRef myCert = SecCertificateCreateWithData(NULL, (CFDataRef)certData);
  [certData release];
  SecCertificateRef certArray[1] = { myCert };
  CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
  CFRelease(myCert);
  NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity
                              certificates:(NSArray *)myCerts
                               persistence:NSURLCredentialPersistencePermanent];
  CFRelease(myCerts);
}

And finally use it with

- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

on [challenge sender]

You should have everything needed. Good luck.

Share:
10,558

Related videos on Youtube

danisoft_2
Author by

danisoft_2

Updated on June 04, 2022

Comments

  • danisoft_2
    danisoft_2 about 2 years

    I'm kind of new in objective c, but I'm developing an app which has a UIWebView that loads some web content. All the web pages require client certificate for authentication and I'm struggling with it for dew days. Does anyone know the flow how to implement it in UIWebView?

    Thanks!

    • smat88dd
      smat88dd over 8 years
      Its possible to use ios identities now with SFSafariViewController (Safari Services framework)
  • danisoft_2
    danisoft_2 over 12 years
    I checked it out and I'm still struggling with the NSURLConnectionDelegate function: didReceiveAuthenticationChallenge... Can you elaborate how to use it instead of the ASIHTTPRequest class?
  • teriiehina
    teriiehina over 12 years
    what part are you struggling on ?
  • danisoft_2
    danisoft_2 over 12 years
    how to perform the request. From what I understood, i need to replace the 'default behaviour' of the uiwebview with the 'shouldStartLoadWithRequest' and within this function I should do the logic. Now the issue is like you said, i need to perform the connection itself using the NSURLConnection with the certificate data. I'm not sure how to do it.
  • teriiehina
    teriiehina over 12 years
    if you want to use a NSURLConnection, the code serving the user certificate should be place in the NSURLConnectionDelegate methode - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChalle‌​nge *)challenge
  • danisoft_2
    danisoft_2 about 12 years
    Thanks, the issue is that I need to use a client cert that was not installed by my app. In that case, using cert from NSBundle doesn't work and I'm not sure Apple enables third party application to access the shared keychain (like Safari and other 'native' apps can). Any suggestions?
  • Omer
    Omer about 11 years
    Does anybody used this? I tried it and is not working for me, I'm testing on iOS 5 and iOS 6. Thanks
  • kervich
    kervich almost 11 years
    You don't need shared keychain. Simply add the certificate file to the app bundle in Build Rules in Xcode. If you don't add the certificate, [[NSBundle mainBundle] pathForResource:@"certificate" ofType:@"cer"] will return nil and all subsequent calls that use certPart/certData will fail.
  • smat88dd
    smat88dd over 8 years
    Its possible to use ios identities now with SFSafariViewController (Safari Services framework) @danisoft_2