Skip to content

Commit

Permalink
feat(iOS): Allow custom CA to be used on webview requests (#865)
Browse files Browse the repository at this point in the history
* Allow custom CA to be used on webview requests

* Add documentation and an example for Custom CA / SSL Pinning
  • Loading branch information
eaceto authored and Titozzz committed Sep 21, 2019
1 parent 552472c commit 136fbd8
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 8 deletions.
29 changes: 29 additions & 0 deletions docs/Custom-iOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions ios/RNCWebView.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
42 changes: 34 additions & 8 deletions ios/RNCWebView.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -646,19 +647,44 @@ + (void)setClientAuthenticationCredential:(nullable NSURLCredential*)credential
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) {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
return;
}
if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {
completionHandler(NSURLSessionAuthChallengeUseCredential, clientAuthenticationCredential);
} else {
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);
}
}

#pragma mark - WKNavigationDelegate methods
Expand Down

0 comments on commit 136fbd8

Please sign in to comment.