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鈥檛 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
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:
Unfortunately, it happens to me both on iOS7 and iOS8.
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鈥檛 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];
any updates here?
I didn't find any solution so I had to fall back to NSURLConnection core methods.
okay thank you very much
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.
I confirm that the customization suggested by mattt solved my problem.
@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?
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];
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!
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!
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"]];
//=======================================================
@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!
I am looking for same answer like patrickferreira.
I am also looking for same answer like @patrickferreira.
@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.
@SandyChapman : Can you please share the whole AFURLSessionManager code.
@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;
}
}];
Hello, Can anyone provide working code For SSL pinning using AFnetworking 3.0 ?
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;
}
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) ?
Most helpful comment
@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'ssetSessionDidReceiveAuthenticationChallengeBlock:You need to change the filename of your cert and the passphrase in these two lines:
Here is the code: