Hello everyone,
Trying to implement the client certificate authentication on iOS app’s WKWebVIew, but it does not work.
In particular, somehow didReceiveAuthenticationChallenge delegete is called twice in one request.
First, authenticationMethod is NSURLAuthenticationMethodClientCertificate, then at second time, authenticationMethod is NSURLAuthenticationMethodServerTrust. And finally didFinishNavigation delegete gets a error “The server “www.example.com” requires a client certificate.”
My code (abstract) is below.
Is there anyone who has some idea? Thanks in advance for your help.
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:URL];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler
{
NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod];
if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) // called 1st
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *DocumentsDirPath = [paths objectAtIndex:0];
NSString *certName = @"clientcert.p12";
NSString *certPath = [NSString stringWithFormat:@"%@/%@",DocumentsDirPath,certName];
NSString *certPass = @"certpassword";
SecIdentityRef myIdentity = [self getClientCertificate:certPath password:certPass]; // definition will appear at the end
SecCertificateRef certificateRef;
SecIdentityCopyCertificate(myIdentity, &certificateRef);
SecCertificateRef certs[1] = { certificateRef };
CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certs, 1, NULL);
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity
certificates:(__bridge NSArray *)myCerts
persistence:NSURLCredentialPersistencePermanent];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}
else if([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) // called 2nd
{
SecTrustRef secTrustRef = challenge.protectionSpace.serverTrust;
if (secTrustRef != NULL)
{
SecTrustResultType result;
OSErr er = SecTrustEvaluate(secTrustRef, &result);
if (er != noErr){
NSLog(@"error");
}
switch ( result )
{
case kSecTrustResultProceed:
NSLog(@"kSecTrustResultProceed");
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
break;
case kSecTrustResultUnspecified: // called 2nd
NSLog(@"kSecTrustResultUnspecified");
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:secTrustRef]);
break;
case kSecTrustResultRecoverableTrustFailure:
NSLog(@"kSecTrustResultRecoverableTrustFailure");
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:secTrustRef]);
break;
}
}
} else {
NSLog(@"else");
}
}
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
if (!error){
NSLog(@"didFailProvisionalNavigation Succeeded.");
} else {
NSString *message = [error localizedDescription];
NSLog(@"didFailProvisionalNavigation Failed. message: %@", message); // The server “www.example.com” requires a client certificate.
}
}
- (SecIdentityRef)getClientCertificate:(NSString *)path password:(NSString *)pass
{
SecCertificateRef identityApp = nil;
NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:path];
CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data;
CFStringRef password = (__bridge CFStringRef)pass;
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import(inPKCS12Data, options, &items);
if (securityError == errSecSuccess) {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
identityApp = (SecCertificateRef)CFDictionaryGetValue(identityDict, kSecImportItemCertChain);
} else {
// ERROR
}
return identityApp;
}