Skip to content
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 · 22 comments
Closed

Problem getting SSL client certificate authentication to work #2316

zionun opened this issue Sep 25, 2014 · 22 comments

Comments

@zionun
Copy link

@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
Copy link

@johnellm 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
Copy link
Author

@zionun zionun commented Sep 25, 2014

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

@FizzyMobile
Copy link

@FizzyMobile 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
Copy link

@break2k break2k commented Nov 3, 2014

any updates here?

@zionun
Copy link
Author

@zionun zionun commented Nov 3, 2014

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

@break2k
Copy link

@break2k break2k commented Nov 3, 2014

okay thank you very much

@mattt
Copy link
Contributor

@mattt 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
Copy link
Author

@zionun zionun commented Jan 24, 2015

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

@wwwebster
Copy link

@wwwebster 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
Copy link
Author

@zionun 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
Copy link

@wwwebster 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
Copy link
Author

@zionun 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
Copy link

@Adrian2013 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
Copy link

@patrickferreira 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
Copy link

@flash23 flash23 commented Jun 29, 2016

I am looking for same answer like patrickferreira.

@ankittyagi
Copy link

@ankittyagi ankittyagi commented Aug 26, 2016

I am also looking for same answer like @patrickferreira.

@SandyChapman
Copy link

@SandyChapman 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
Copy link

@hardik38737 hardik38737 commented Oct 6, 2016

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

@zionun
Copy link
Author

@zionun 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
Copy link

@brainiumSumita brainiumSumita commented Mar 10, 2017

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

@garg204
Copy link

@garg204 garg204 commented May 4, 2019

Hi

i tried to set security policy. It seems its not working for me. All api request succeeded even if i do not attach certificate in application bundle. I am writing below code in my custom class.

  • (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration {
    if (self = [super init]) {
    self.urlSessionManager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    self.urlSessionManager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
    }

    return self;
    }

@iradization
Copy link

@iradization iradization commented Dec 11, 2019

Hi @zionun, I could see that you simply pick the first certificate in your example, but what If you have multiple certificates on the p12 file and you wish to provide the right one for this server.

Perhaps you you know anyway to find the right match (maybe via certificate host) ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet