Create SSL connection using certificate

ios, nsurlconnection, objective-c, ssl

Solution

If this is a self signed certificate to be used in server trust authentication, you should do the following:

Convert the .CRT encoded certificate into a .DER encoded certificate. On the terminal type:

`$: openssl x509 -in certificate.crt -outform der -out "com.server.trust_cert.der"`

(choose your own meaningful name)

Put the .DER encoded certificate into the bundle.

Implement the method `connection:didReceiveAuthenticationChallenge:` as follows. Important: always check for errors and bail out and let the authentication fail if anything is wrong!!

Test it thoroughly!

- (void)connection:(NSURLConnection *)connection
    didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust])
    {
        do
        {
            SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
            if (serverTrust == nil)
                break; // failed

            SecTrustResultType trustResult;
            OSStatus status = SecTrustEvaluate(serverTrust, &trustResult);
            if (!(errSecSuccess == status))
                break; // fatal error in trust evaluation -> failed

            if (!((trustResult == kSecTrustResultProceed) 
               || (trustResult == kSecTrustResultUnspecified)))
            {
                break; // see "Certificate, Key, and Trust Services Reference" 
                       // for explanation of result codes.
            }

            SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
            if (serverCertificate == nil)
                break; // failed

            CFDataRef serverCertificateData = SecCertificateCopyData(serverCertificate);
            if (serverCertificateData == nil)
                break; // failed

            const UInt8* const data = CFDataGetBytePtr(serverCertificateData);
            const CFIndex size = CFDataGetLength(serverCertificateData);
            NSData* server_cert = [NSData dataWithBytes:data length:(NSUInteger)size];
            CFRelease(serverCertificateData);

            NSString* file = [[NSBundle mainBundle] pathForResource:@"com.server.trust_cert"
                                                             ofType:@"der"];
            NSData* my_cert = [NSData dataWithContentsOfFile:file];

            if (server_cert == nil || my_cert == nil)
                break; // failed

            const BOOL equal = [server_cert isEqualToData:my_cert];
            if (!equal)
                break; // failed 

            // Athentication succeeded:
            return [[challenge sender] useCredential:[NSURLCredential credentialForTrust:serverTrust]
                          forAuthenticationChallenge:challenge];
        } while (0);

        // Authentication failed: 
        return [[challenge sender] cancelAuthenticationChallenge:challenge];
    }
}

Note:

A possible improvement of the above technique is to use "public key pinning".

Must Reads:

HTTPS Server Trust Evaluation (Official Apple Documentation, Technical Note TN2232)

Certificate, Key, and Trust Services Reference (Official Apple Reference Documentation)

Problem

I have ssl certificate (.cer) which was provided to me as file. I added it to bundle and want to use it communicating with server. I used apple provided code: ``` - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{ DLog(@"didReceiveAuthenticationChallenge : %@",challenge); if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { NSString *filePath = [[NSBundle mainBundle] pathForResource:@"certificate" ofType:@"cer"]; NSData *certData = [NSData dataWithContentsOfFile:filePath]; CFDataRef myCertData = (__bridge CFDataRef)certData; SecCertificateRef myCert = SecCertificateCreateWithData(NULL, myCertData); SecPolicyRef myPolicy = SecPolicyCreateBasicX509(); // 3 SecCertificateRef certArray[1] = { myCert }; CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL); SecTrustRef myTrust; OSStatus status = SecTrustCreateWithCertificates( myCerts, myPolicy, &myTrust); // 4 SecTrustResultType trustResult = 0; if (status == noErr) { status = SecTrustEvaluate(myTrust, &trustResult); // 5 } // If the trust result is kSecTrustResultInvalid, kSecTrustResultDeny, kSecTrustResultFatalTrustFailure, you cannot proceed and should fail gracefully. BOOL proceed = NO; switch (trustResult) { case kSecTrustResultProceed: // 1 DLog(@"Proceed"); proceed = YES; break; case kSecTrustResultConfirm: // 2 DLog(@"Confirm"); proceed = YES; break; case kSecTrustResultUnspecified: // 4 DLog(@"Unspecified"); break; case kSecTrustResultRecoverableTrustFailure: // 5 DLog(@"TrustFailure"); proceed = [self recoverFromTrustFailure:myTrust]; break; case kSecTrustResultDeny: // 3 DLog(@"Deny"); break; case kSecTrustResultFatalTrustFailure: // 6 DLog(@"FatalTrustFailure"); break; case kSecTrustResultOtherError: // 7 DLog(@"OtherError"); break; case kSecTrustResultInvalid: // 0 DLog(@"Invalid"); break; default: DLog(@"Default"); break; } if (myPolicy) CFRelease(myPolicy); if (proceed) { [challenge.sender useCredential:[NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] forAuthenticationChallenge: challenge]; }else{ [[challenge sender] cancelAuthenticationChallenge:challenge]; } } } - (BOOL) recoverFromTrustFailure:(SecTrustRef) myTrust { SecTrustResultType trustResult; OSStatus status = SecTrustEvaluate(myTrust, &trustResult); // 1 //Get time used to verify trust CFAbsoluteTime trustTime,currentTime,timeIncrement,newTime; CFDateRef newDate; if (trustResult == kSecTrustResultRecoverableTrustFailure) {// 2 trustTime = SecTrustGetVerifyTime(myTrust); // 3 timeIncrement = 31536000; // 4 currentTime = CFAbsoluteTimeGetCurrent(); // 5 newTime = currentTime - timeIncrement; // 6 if (trustTime - newTime){ // 7 newDate = CFDateCreate(NULL, newTime); // 8 SecTrustSetVerifyDate(myTrust, newDate); // 9 status = SecTrustEvaluate(myTrust, &trustResult); // 10 } } if (trustResult != kSecTrustResultProceed) { DLog(@"Failed with status : %li",trustResult); // 11 return NO; }else{ DLog(@"Procced"); return YES; } } ``` However i am getting kSecTrustResultRecoverableTrustFailure . Also used apple sample in this situation but it didn't helped. Maybe some one could help me on this ? Thank you.

Original source