feat(iOS): Allow custom CA to be used on webview requests (#865)

* Allow custom CA to be used on webview requests

* Add documentation and an example for Custom CA / SSL Pinning
This commit is contained in:
Ezequiel Aceto 2019-09-20 21:44:53 -03:00 committed by Thibault Malbranche
parent 552472c414
commit 136fbd8491
3 changed files with 64 additions and 8 deletions

View File

@ -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:

View File

@ -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;

View File

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