Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Client Certificate Authentication on iOS UIWebView Works on iOS 6.1 but not iOS 7

Tags:

ios

ssl

ios7

I am trying to use Client Certificate authentication to access a secure website. The code I am using works fine in iOS 6.1 but fails with the server returning a 403.7 error when using iOS 7.

I use the connection:willSendRequestForAuthenticationChallenge handler to check the authentication method and provide the client certificate.

My code is:

- (void)connection: (NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{

    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        NSLog(@"Trust Challenge");
        SecTrustResultType trustResultType;
        OSStatus err = SecTrustEvaluate(challenge.protectionSpace.serverTrust, &trustResultType);

        NSLog(@"SecTrustResult %u %d",trustResultType, (int)err);

        if (trustResultType == kSecTrustResultProceed || trustResultType == kSecTrustResultConfirm || trustResultType == kSecTrustResultUnspecified) {
            [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
        }
        else{
            [challenge.sender cancelAuthenticationChallenge:challenge];
        }

    } else {
        NSString *path = [[NSBundle mainBundle]pathForResource:@"mycert" ofType:@"pfx"];
        NSData *p12data = [NSData dataWithContentsOfFile:path];

        CFDataRef inP12data = (__bridge CFDataRef)p12data;

        SecIdentityRef myIdentity;
        SecTrustRef myTrust;
        extractIdentityAndTrust(inP12data, &myIdentity, &myTrust);
        assert(myIdentity != nil);
        assert(myTrust != nil);

        long count = SecTrustGetCertificateCount(myTrust);
        NSMutableArray* myCertificates = nil;
        if(count > 1) {
            myCertificates = [NSMutableArray arrayWithCapacity:count];
            for(int i = 1; i < count; ++i) {
                [myCertificates addObject:(__bridge id)SecTrustGetCertificateAtIndex(myTrust, i)];
            }
        }

        NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:myCertificates persistence:NSURLCredentialPersistenceNone];
        assert(credential != nil);

        NSLog(@"User: %@, certificates %@ identity:%@", [credential user], [credential certificates], [credential identity]);
        [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
    }
}

I use this function to extract the contents of the certificate:

OSStatus extractIdentityAndTrust(CFDataRef inP12data, SecIdentityRef *identity, SecTrustRef *trust)
{
    OSStatus securityError = errSecSuccess;

    CFStringRef password = CFSTR("password");
    const void *keys[] = { kSecImportExportPassphrase };
    const void *values[] = { password };

    CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);

    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
    securityError = SecPKCS12Import(inP12data, options, &items);

    if (securityError == 0) {
        CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0);
        const void *tempIdentity = NULL;
        tempIdentity = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity);
        *identity = (SecIdentityRef)tempIdentity;
        const void *tempTrust = NULL;
        tempTrust = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemTrust);
        *trust = (SecTrustRef)tempTrust;

        CFIndex count = CFArrayGetCount(items);
        NSLog(@"Certificates found: %ld",count);
    }

    if (options) {
        CFRelease(options);
    }

    return securityError;
}

The mycert.pfx file contains the intermediate certificate along with the client cert.

The connection:didFailWithError function is never called.

-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
        NSLog(@"Error: %@", [error userInfo]);
    }

So it seems like the certificate negotiation succeeds at some level.

My problem is similar to SSL - behaves differently in iOS7? but I am using Windows Server 2008 R2 with IIS 7.5. The TLS 1.1 and TLS 1.2 protocols have been enabled on the server.

A WireShark trace shows that the certificate frame during the TLS handshake is empty when using iOS 7. The certificate is sent and verified when using iOS 6.1.

I can access the site in iOS 7 using Safari.

Any help is greatly appreciated.

like image 453
Jeff Martin Avatar asked Oct 20 '22 16:10

Jeff Martin


1 Answers

I was able to find a solution with the help of Apple Developer support. The solution involves creating a custom NSURLProtocol. I used the Apple sample code at https://developer.apple.com/library/ios/#samplecode/CustomHTTPProtocol/. The sample code shows how to override HTTPS server trust evaluation so it needs to be modified to work with client certificate authentication.

I modified the AppDelegate didRecieveAuthenticationChallenge function.

- (void)customHTTPProtocol:(CustomHTTPProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
// A CustomHTTPProtocol delegate callback, called when the protocol has an authenticate
// challenge that the delegate accepts via -   customHTTPProtocol:canAuthenticateAgainstProtectionSpace:.
// In this specific case it's only called to handle server trust authentication challenges.
// It evaluates the trust based on both the global set of trusted anchors and the list of trusted
// anchors returned by the CredentialsManager.
{
    OSStatus            err;
    NSURLCredential *   credential;

    assert(protocol != nil);
    assert(challenge != nil);

    credential = nil;

    // Handle ServerTrust and Client Certificate challenges

    NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod];
    if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        NSLog(@"Trust Challange");
        SecTrustResultType trustResultType;
        err = SecTrustEvaluate(challenge.protectionSpace.serverTrust, &trustResultType);

        NSLog(@"SecTrustResult %u %d",trustResultType, (int)err);

        if (trustResultType == kSecTrustResultProceed || trustResultType == kSecTrustResultUnspecified) {
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            assert(credential != nil);
        }
    } else {
        NSString *path = [[NSBundle mainBundle]pathForResource:@"mycert" ofType:@"pfx"];
        NSData *p12data = [NSData dataWithContentsOfFile:path];

        SecIdentityRef identity = NULL;
        SecCertificateRef certificate = NULL;

        [Util identity:&identity andCertificate:&certificate fromPKCS12Data:p12data withPassphrase:@"asia1215"];

        assert(identity != NULL);

        NSArray *certArray = [NSArray arrayWithObject:(__bridge id)certificate];
        credential = [NSURLCredential credentialWithIdentity:identity certificates:certArray persistence:NSURLCredentialPersistencePermanent];
    }

    [protocol resolveAuthenticationChallenge:challenge withCredential:credential];
}
like image 86
Jeff Martin Avatar answered Oct 28 '22 22:10

Jeff Martin