iOS NSURLErrorDomain Code=-1005 with ClientCertificate Validation challenge

I am trying to implement https client certificate authentication in iOS with

NSURLSession
. Here is what I am doing:


-(void) httpPostWithCustomDelegate :(NSDictionary *) params 
{ 
     NSString *ppyRequestURL = [NSString stringWithFormat:@"%@/fetchcountryCities", PPBaseURL]; 
     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:ppyRequestURL]  cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0]; 
     [request setHTTPMethod:@"POST"]; 
     [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];      
     NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; 
     NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];     
      NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data,           NSURLResponse *response, NSError *error) 
          { 
               Log(@"ASDAD"); 
          }]; 
     [postDataTask resume]; 
}


I am providing the client certificate in challenge handler like this:


- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge      completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
     { 
          if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
          { 
               NSURLCredential *credential = [NSURLCredential  credentialForTrust:challenge.protectionSpace.serverTrust];                completionHandler(NSURLSessionAuthChallengeUseCredential,credential); 
          } 
          else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) 
          { 
               NSURLCredential *credential = [self provideClientCertificate];                
               completionHandler(NSURLSessionAuthChallengeUseCredential, credential); 
          } 
     }


Here is how I load my client certificate,


- (NSURLCredential *)provideClientCertificate 
{ 
     SecIdentityRef identity = [self findClientCertificate]; 
     if (!identity) { return nil; } 
     SecCertificateRef certificate = NULL; 
     SecIdentityCopyCertificate (identity, &certificate); 
     const void *certs[] = {certificate}; 
     CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL); 
     NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray *)certArray persistence:NSURLCredentialPersistencePermanent]; 
     CFRelease(certArray); return credential; 
}



- (SecIdentityRef)findClientCertificate 
{ 
     SecIdentityRef clientCertificate = NULL; 
     if (clientCertificate) 
     { 
          CFRelease(clientCertificate); 
          clientCertificate = NULL; 
     } 
     NSString *pkcs12Path = [[NSBundle mainBundle] pathForResource:@"johndoe" ofType:@"p12"]; 
     NSData *pkcs12Data = [[NSData alloc] initWithContentsOfFile:pkcs12Path]; 
     CFDataRef inPKCS12Data = (__bridge CFDataRef)pkcs12Data; 
     CFStringRef password = CFSTR("password"); 
     const void *keys[] = { kSecImportExportPassphrase }; 
     const void *values[] = { password }; 
     CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL); 
     CFArrayRef items = NULL; OSStatus err = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items);      
     CFRelease(optionsDictionary); 
     CFRelease(password); 
     if (err == errSecSuccess && CFArrayGetCount(items) > 0) 
     { 
          CFDictionaryRef pkcsDict = CFArrayGetValueAtIndex(items, 0); 
          SecTrustRef trust = (SecTrustRef)CFDictionaryGetValue(pkcsDict, kSecImportItemTrust); if (trust != NULL) 
          { 
               clientCertificate = (SecIdentityRef)CFDictionaryGetValue(pkcsDict, kSecImportItemIdentity);                
               CFRetain(clientCertificate); 
          } 
     } 
     if (items) { CFRelease(items); } 
     return clientCertificate; 
}



Now when the API is called I am getting back this error:

Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x7f8428df4d40 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=-4, _kCFStreamErrorDomainKey=4}}


Whats going wrong here?

Replies

I did check with charles proxy to find out more details. To my surprise when I added the client certificate to charles proxy I am getting back response from server, so am I missing some settings in plist or issue with loading p12?


Here is my plist settings,

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains</key>
  <dict>
  <key>test.domain.com</key>
  <dict>
  <key>NSExceptionAllowsInsecureHTTPLoads</key>
  <true/>
  <key>NSExceptionMinimumTLSVersion</key>
  <string>TLSv1.2</string>
  <key>NSExceptionRequiresForwardSecrecy</key>
  <true/>
  <key>NSIncludesSubdomains</key>
  <true/>
  <key>NSRequiresCertificateTransparency</key>
  <false/>
  <key>NSThirdPartyExceptionAllowsInsecureHTTPLoads</key>
  <false/>
  <key>NSThirdPartyExceptionMinimumTLSVersion</key>
  <string>TLSv1.2</string>
  <key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
  <true/>
  </dict>
  </dict>
</dict>

Error -1005 is

NSURLErrorNetworkConnectionLost
, which means that the network connection just disappeared out from underneath NSURLSession. The most common cause for this is the server unexpected dropping the connection. It is possible that this is an App Transport Security issue so, just to rule that out, I recommend that you (temporarily) set your ATS dictionary to contain a single key,
NSAllowsArbitraryLoads
, with the value set to true.

Also, your

URLSession:didReceiveChallenge:completionHandler:
method is wrong. It should filter out the challenges it cares about and then, for any challenges it does not understand, resolve them with
NSURLSessionAuthChallengePerformDefaultHandling
.

Your current code also disables server trust evaluation completely. Is that what you intended to do? It’s generally a bad idea in a real app

If none of the above helps you should look at a packet trace to see what’s happening on the ‘wire’.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for your response. I have tried the code in IOS 8.4 and IOS 9.0. Now I am getting time out error. I am getting back both server trust and client certificate challenges. After serving the certificate for client certificate challenge I am getting back time out error after 50 seconds. We have other apps (Web and Android)connecting with same API without any issues.

If you connect with TLSTool running on 10.11.x, what do you see? Make sure to use the

-cert
option to supply your client identity.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"