diff --git a/docs/Custom-iOS.md b/docs/Custom-iOS.md index 3e84342..a4f29bf 100644 --- a/docs/Custom-iOS.md +++ b/docs/Custom-iOS.md @@ -142,6 +142,35 @@ If you open webpages that needs a Client Certificate for Authentication, you can This can be paired with a call from Javascript to pass a string label for the certificate stored in keychain and use native calls to fetch the certificate to create a credential object. This call can be made anywhere that makes sense for your application (e.g. as part of the user authentication stack). The only requirement is to make this call before displaying any webviews. +### Allowing custom CAs (Certifica Authorities) and enabling SSL Pinning + +If you need to connect to a server which has a self signed certificate, or want to perform SSL Pinning on the webview requests, you need to pass a dictionary with the host as the key, and the certificate as the value of each item: + + +```objc +-(void)installCerts { + + // Get the bundle where the certificates in DER format are present. + NSBundle *bundle = [NSBundle mainBundle]; + + NSMutableDictionary* certMap = [NSMutableDictionary new]; + + NSData *rootCertData = [NSData dataWithContentsOfFile:[bundle pathForResource:@"example_ca" ofType:@"der"]]; + + SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (CFDataRef) rootCertData); + + OSStatus err = SecItemAdd((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:(id) kSecClassCertificate, kSecClass, certificate, kSecValueRef, nil], NULL); + + [certMap setObject:(__bridge id _Nonnull)(certificate) forKey:@"example.com"]; + + [RNCWebView setCustomCertificatesForHost:certMap]; +} + +``` + +Multiple hosts can be added to the directionary, and only one certificate for a host is allowed. The verification will succeed if any of the certificates in the chain of the request matches the one defined for the request's host. + + ## JavaScript Interface To use your custom web view, you'll need to create a class for it. Your class must: diff --git a/ios/RNCWebView.h b/ios/RNCWebView.h index b2faebf..90c996a 100644 --- a/ios/RNCWebView.h +++ b/ios/RNCWebView.h @@ -53,6 +53,7 @@ @property (nonatomic, copy) NSString *allowingReadAccessToURL; + (void)setClientAuthenticationCredential:(nullable NSURLCredential*)credential; ++ (void)setCustomCertificatesForHost:(nullable NSDictionary *)certificates; - (void)postMessage:(NSString *)message; - (void)injectJavaScript:(NSString *)script; - (void)goForward; diff --git a/ios/RNCWebView.m b/ios/RNCWebView.m index bd59aa6..ec098b4 100644 --- a/ios/RNCWebView.m +++ b/ios/RNCWebView.m @@ -16,6 +16,7 @@ static NSTimer *keyboardTimer; static NSString *const MessageHandlerName = @"ReactNativeWebView"; static NSURLCredential* clientAuthenticationCredential; +static NSDictionary* customCertificatesForHost; // runtime trick to remove WKWebView keyboard default toolbar // see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279 @@ -646,19 +647,44 @@ static NSURLCredential* clientAuthenticationCredential; clientAuthenticationCredential = credential; } ++ (void)setCustomCertificatesForHost:(nullable NSDictionary*)certificates { + customCertificatesForHost = certificates; +} + - (void) webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable))completionHandler { - if (!clientAuthenticationCredential) { + NSString* host = nil; + if (webView.URL != nil) { + host = webView.URL.host; + } + if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) { + completionHandler(NSURLSessionAuthChallengeUseCredential, clientAuthenticationCredential); + return; + } + if ([[challenge protectionSpace] serverTrust] != nil && customCertificatesForHost != nil && host != nil) { + SecCertificateRef localCertificate = (__bridge SecCertificateRef)([customCertificatesForHost objectForKey:host]); + if (localCertificate != nil) { + NSData *localCertificateData = (NSData*) CFBridgingRelease(SecCertificateCopyData(localCertificate)); + SecTrustRef trust = [[challenge protectionSpace] serverTrust]; + long count = SecTrustGetCertificateCount(trust); + for (long i = 0; i < count; i++) { + SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(trust, i); + if (serverCertificate == nil) { continue; } + NSData *serverCertificateData = (NSData *) CFBridgingRelease(SecCertificateCopyData(serverCertificate)); + if ([serverCertificateData isEqualToData:localCertificateData]) { + NSURLCredential *useCredential = [NSURLCredential credentialForTrust:trust]; + if (challenge.sender != nil) { + [challenge.sender useCredential:useCredential forAuthenticationChallenge:challenge]; + } + completionHandler(NSURLSessionAuthChallengeUseCredential, useCredential); + return; + } + } + } + } completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); - return; - } - if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) { - completionHandler(NSURLSessionAuthChallengeUseCredential, clientAuthenticationCredential); - } else { - completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); - } } #pragma mark - WKNavigationDelegate methods