New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problem getting SSL client certificate authentication to work #2316

Closed
zionun opened this Issue Sep 25, 2014 · 20 comments

Comments

Projects
None yet
@zionun

zionun commented Sep 25, 2014

Hello,
I'm struggling getting my code to work. What I want to achieve is Apache SSL client certificate authentication. I put both CA and client certs in my app bundle (DER format) and I used the code that I saw here and on many other sites:

AFSecurityPolicy *securityPolicy = [[AFSecurityPolicy alloc] init]; [securityPolicy setAllowInvalidCertificates:YES]; [securityPolicy setSSLPinningMode:AFSSLPinningModeCertificate];

I get the error:
Error Domain=NSURLErrorDomain Code=-1012 "The operation couldn’t be completed."

I also tried to manually load the certificates using:
[securityPolicy setPinnedCertificates:[NSArray arrayWithObjects:[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"MyCA" ofType:@"cer"]],[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"client" ofType:@"cer"]], nil]];

I tried all combinations of YES/NO for the following parameters:
validatesCertificateName
validatesDomainName

And I also tried to pass a pkcs certificate, or the single DER client certificate without the CA one.
None of this worked.

Using a browser or an Android device, the authentication mechanism works fine.

What am I doing wrong here?
Could you please point me in the right direction?

Thank you very much

@johnellm

This comment has been minimized.

johnellm commented Sep 25, 2014

We have the same issue on iOS 8 only. It seems there may be a bug with NSURLRequest and willSendRequestForAuthenticationChallenge sometimes not firing.

More info here:

https://devforums.apple.com/message/1043184#1043184

@zionun

This comment has been minimized.

zionun commented Sep 25, 2014

Unfortunately, it happens to me both on iOS7 and iOS8.

@FizzyMobile

This comment has been minimized.

FizzyMobile commented Oct 10, 2014

I have the same problem as zionun mentioned in the original post.

I put all 4 certificates from certificate chain to application bundle, set
[securityPolicy setSSLPinningMode:AFSSLPinningModeCertificate];
and I'm getting
Error Domain=NSURLErrorDomain Code=-1012 "The operation couldn’t be completed."

What I found is that in AFSecurityPolicy.m mehtod - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain counter doesn't reach proper value to return YES (2 of 3 certificates are matching, one is not), but I've checked every certificate from debugger and public keys are OK.

Heres the part I'm talking about:

NSUInteger trustedCertificateCount = 0;
for (NSData *trustChainCertificate in serverCertificates) {
   // NOTE:
   // for the certificate that containsObject returns NO, public keys are the same, 
   // yet NSData byte size is not the same
   if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
      trustedCertificateCount++;
   }
}
return trustedCertificateCount == [serverCertificates count];
@break2k

This comment has been minimized.

break2k commented Nov 3, 2014

any updates here?

@zionun

This comment has been minimized.

zionun commented Nov 3, 2014

I didn't find any solution so I had to fall back to NSURLConnection core methods.

@break2k

This comment has been minimized.

break2k commented Nov 3, 2014

okay thank you very much

@mattt

This comment has been minimized.

Contributor

mattt commented Nov 17, 2014

AFSecurityPolicy provides SSL/TLS certificate pinning to validate server certificates. To do certificate authentication, you would customize the block property corresponding to the willSendRequestForAuthenticationChallenge: delegate method.

@mattt mattt closed this Nov 17, 2014

@zionun

This comment has been minimized.

zionun commented Jan 24, 2015

I confirm that the customization suggested by mattt solved my problem.

@wwwebster

This comment has been minimized.

wwwebster commented Jun 10, 2015

@zionun, can you outline how you provide the certificate to the challenge object please? What format is the certificate and do you have to use the Security framework please?

@zionun

This comment has been minimized.

zionun commented Jun 25, 2015

Hi, sorry for the late. Hope this reply is still useful to you.
The certificate is saved in p12 format.
You need to change the filename of your cert and the passphrase in these two lines:

NSData *p12Data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"cert" ofType:@"p12"]];

CFStringRef password = CFSTR("YOURPASSPHRASE");

Here is the full code:

AFHTTPRequestSerializer *reqSerializer = [AFHTTPRequestSerializer serializer];

NSMutableURLRequest *request;

request = [reqSerializer requestWithMethod:method URLString:[actionURL absoluteString] parameters:nil error:nil];

AFSecurityPolicy *securityPolicy = [[AFSecurityPolicy alloc] init];
[securityPolicy setAllowInvalidCertificates:kAllowsInvalidSSLCertificate];

AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFHTTPResponseSerializer serializer];

[operation setSecurityPolicy:securityPolicy];

[operation setWillSendRequestForAuthenticationChallengeBlock:^(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge) {
    if ([challenge previousFailureCount] > 0) {
        //this will cause an authentication failure
        [[challenge sender] cancelAuthenticationChallenge:challenge];
        NSLog(@"Bad Username Or Password");
        return;
    }

    //this is checking the server certificate
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        SecTrustResultType result;
        //This takes the serverTrust object and checkes it against your keychain
        SecTrustEvaluate(challenge.protectionSpace.serverTrust, &result);

        //if we want to ignore invalid server for certificates, we just accept the server
        if (kAllowsInvalidSSLCertificate) {
            [challenge.sender useCredential:[NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] forAuthenticationChallenge: challenge];
            return;
        } else if(result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) {
            //When testing this against a trusted server I got kSecTrustResultUnspecified every time. But the other two match the description of a trusted server
            [challenge.sender useCredential:[NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] forAuthenticationChallenge: challenge];
            return;
        }
    } else if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {
        //this handles authenticating the client certificate

        /*
         What we need to do here is get the certificate and an an identity so we can do this:
         NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:myCerts persistence:NSURLCredentialPersistencePermanent];
         [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];

         It's easy to load the certificate using the code in -installCertificate
         It's more difficult to get the identity.
         We can get it from a .p12 file, but you need a passphrase:
         */

        NSData *p12Data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"cert" ofType:@"p12"]];

        CFStringRef password = CFSTR("YOURPASSPHRASE");
        const void *keys[] = { kSecImportExportPassphrase };
        const void *values[] = { password };
        CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
        CFArrayRef p12Items;

        OSStatus result = SecPKCS12Import((__bridge CFDataRef)p12Data, optionsDictionary, &p12Items);

        if(result == noErr) {
            CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0);
            SecIdentityRef identityApp =(SecIdentityRef)CFDictionaryGetValue(identityDict,kSecImportItemIdentity);

            SecCertificateRef certRef;
            SecIdentityCopyCertificate(identityApp, &certRef);

            SecCertificateRef certArray[1] = { certRef };
            CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
            CFRelease(certRef);

            NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp certificates:(__bridge NSArray *)myCerts persistence:NSURLCredentialPersistencePermanent];
            CFRelease(myCerts);

            [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
        } else {
            [[challenge sender] cancelAuthenticationChallenge:challenge];
        }
    } else if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodDefault || [[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodNTLM) {

        // For normal authentication based on username and password. This could be NTLM or Default.
        /*
         DAVCredentials *cred = _parentSession.credentials;
         NSURLCredential *credential = [NSURLCredential credentialWithUser:cred.username password:cred.password persistence:NSURLCredentialPersistenceForSession];
         [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
         */

        NSLog(@"BASIC AUTHENTICATION");

    } else {
        //If everything fails, we cancel the challenge.
        [[challenge sender] cancelAuthenticationChallenge:challenge];
    }
}];

[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {

    NSLog(@"Success");

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {

    NSLog(@"Failure");

}];

[[NSOperationQueue mainQueue] addOperation:operation];
@wwwebster

This comment has been minimized.

wwwebster commented Jul 7, 2015

Thanks @zionun, It's so simple! </sarcasm>

Thanks for the help! I'm not working on the project that needed this at the minute, but I'll refer back to this when I am. Cheers!

@zionun

This comment has been minimized.

zionun commented Jul 7, 2015

You're welcome!
I just forgot to mention that kAllowsInvalidSSLCertificate is a constant I defined as a boolean, and you can set it to YES or NO depending on the need of allowing or not allowing self-signed certificate to be trusted.
Bye!

@Adrian2013

This comment has been minimized.

Adrian2013 commented Aug 12, 2015

Hi @zionun ,

Thanks a lot...... its work for me... i try this for " .bks " certificate also(with Password) and its works
//==================================
NSData *p12Data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"clienttruststore" ofType:@"bks"]];
//=======================================================

@patrickferreira

This comment has been minimized.

patrickferreira commented Jun 21, 2016

@zionun
Thanks a lot for this, helped me a lot understand how to handle client certificates.

I had to migrate my project to AFNetworking 3, any clue how to make your code work with it and AFHTTPSessionManager?

Cheers!

@flash23

This comment has been minimized.

flash23 commented Jun 29, 2016

I am looking for same answer like patrickferreira.

@ankittyagi

This comment has been minimized.

ankittyagi commented Aug 26, 2016

I am also looking for same answer like @patrickferreira.

@SandyChapman

This comment has been minimized.

SandyChapman commented Sep 12, 2016

@patrickferreira @flash23 @ankittyagi : check out the following method on AFURLSessionManager:

- (void)setTaskDidReceiveAuthenticationChallengeBlock:([long block variable type here])block;

The implementation should be nearly identical to the example above.

@hardik38737

This comment has been minimized.

hardik38737 commented Oct 6, 2016

@SandyChapman : Can you please share the whole AFURLSessionManager code.

@zionun

This comment has been minimized.

zionun commented Mar 8, 2017

@patrickferreira @flash23 @ankittyagi @hardik38737
I know it's been a lot since you asked for AFNetworking 3.x version of this stuff, but I could only dig into it yesterday. You have probably already resolved this, but still I'd like to share my code should anyone search for a solution.

For me, the thing that got it working was to use AFURLSessionManager's setSessionDidReceiveAuthenticationChallengeBlock:

You need to change the filename of your cert and the passphrase in these two lines:

NSData *p12Data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"cert" ofType:@"p12"]];

CFStringRef password = CFSTR("YOURPASSPHRASE");

Here is the code:

#define kAllowsInvalidSSLCertificate YES

AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
AFSecurityPolicy* securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
[securityPolicy setValidatesDomainName:NO];
[securityPolicy setAllowInvalidCertificates:kAllowsInvalidSSLCertificate];
[manager setSecurityPolicy:securityPolicy];

[manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession * _Nonnull session, NSURLAuthenticationChallenge * _Nonnull challenge, NSURLCredential *__autoreleasing  _Nullable * _Nullable credential) {
    
    if ([challenge previousFailureCount] > 0) {
        //this will cause an authentication failure
        NSLog(@"Bad Username Or Password");
        return NSURLSessionAuthChallengeCancelAuthenticationChallenge;
    }
    
    //this is checking the server certificate
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        SecTrustResultType result;
        //This takes the serverTrust object and checkes it against your keychain
        SecTrustEvaluate(challenge.protectionSpace.serverTrust, &result);
        
        //if we want to ignore invalid server for certificates, we just accept the server
        if (kAllowsInvalidSSLCertificate) {
            *credential = [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust];
            return NSURLSessionAuthChallengeUseCredential;
            
        } else if(result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) {
            //When testing this against a trusted server I got kSecTrustResultUnspecified every time. But the other two match the description of a trusted server
            *credential = [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust];
            return NSURLSessionAuthChallengeUseCredential;
        }
    } else if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {
        //this handles authenticating the client certificate
        
        //
        // What we need to do here is get the certificate and an an identity
        // It's easy to load the certificate using the code in -installCertificate
        // It's more difficult to get the identity.
        // We can get it from a .p12 file, but you need a passphrase:
        //

        NSData *p12Data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"cert" ofType:@"p12"]];

        CFStringRef password = CFSTR("YOURPASSPHRASE");
        const void *keys[] = { kSecImportExportPassphrase };
        const void *values[] = { password };
        CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
        CFArrayRef p12Items;

        OSStatus result = SecPKCS12Import((__bridge CFDataRef)p12Data, optionsDictionary, &p12Items);

        if(result == noErr) {
            CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0);
            SecIdentityRef identityApp =(SecIdentityRef)CFDictionaryGetValue(identityDict,kSecImportItemIdentity);

            SecCertificateRef certRef;
            SecIdentityCopyCertificate(identityApp, &certRef);

            SecCertificateRef certArray[1] = { certRef };
            CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
            CFRelease(certRef);

            NSURLCredential *myCredential = [NSURLCredential credentialWithIdentity:identityApp certificates:(__bridge NSArray *)myCerts persistence:NSURLCredentialPersistencePermanent];
            CFRelease(myCerts);
            
            *credential = myCredential;
            
        } else {

            return NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            
        }

    } else if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodDefault || [[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodNTLM) {
        
        // For normal authentication based on username and password. This could be NTLM or Default.
        //
        //  DAVCredentials *cred = _parentSession.credentials;
        //  *credential = [NSURLCredential credentialWithUser:cred.username password:cred.password persistence:NSURLCredentialPersistenceForSession];
        //
        
        NSLog(@"BASIC AUTHENTICATION");
        
        return NSURLSessionAuthChallengePerformDefaultHandling;
        
    } else {
        //If everything fails, we cancel the challenge.
        return NSURLSessionAuthChallengeCancelAuthenticationChallenge;
    }
}];
@brainiumSumita

This comment has been minimized.

brainiumSumita commented Mar 10, 2017

Hello, Can anyone provide working code For SSL pinning using AFnetworking 3.0 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment