4 Replies
      Latest reply: Sep 21, 2016 2:13 AM by eskimo RSS
      mobtz Level 1 Level 1 (0 points)

        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?

        • Re: iOS NSURLErrorDomain Code=-1005 with ClientCertificate Validation challenge
          mobtz Level 1 Level 1 (0 points)

          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>
          
            • Re: iOS NSURLErrorDomain Code=-1005 with ClientCertificate Validation challenge
              eskimo Apple Staff Apple Staff (7,005 points)

              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"