Browse Source

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
Ezequiel Aceto 5 years ago
parent
commit
136fbd8491
3 changed files with 64 additions and 8 deletions
  1. 29
    0
      docs/Custom-iOS.md
  2. 1
    0
      ios/RNCWebView.h
  3. 34
    8
      ios/RNCWebView.m

+ 29
- 0
docs/Custom-iOS.md View File

@@ -142,6 +142,35 @@ If you open webpages that needs a Client Certificate for Authentication, you can
142 142
 
143 143
 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.
144 144
 
145
+### Allowing custom CAs (Certifica Authorities) and enabling SSL Pinning
146
+
147
+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:
148
+
149
+
150
+```objc
151
+-(void)installCerts {
152
+
153
+  // Get the bundle where the certificates in DER format are present.
154
+  NSBundle *bundle = [NSBundle mainBundle];
155
+  
156
+  NSMutableDictionary* certMap = [NSMutableDictionary new];
157
+
158
+  NSData *rootCertData = [NSData dataWithContentsOfFile:[bundle pathForResource:@"example_ca" ofType:@"der"]];
159
+
160
+  SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (CFDataRef) rootCertData);
161
+   
162
+  OSStatus err = SecItemAdd((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:(id) kSecClassCertificate, kSecClass, certificate, kSecValueRef, nil], NULL);
163
+  
164
+  [certMap setObject:(__bridge id _Nonnull)(certificate) forKey:@"example.com"];
165
+
166
+  [RNCWebView setCustomCertificatesForHost:certMap];
167
+}
168
+
169
+```
170
+
171
+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.
172
+
173
+
145 174
 ## JavaScript Interface
146 175
 
147 176
 To use your custom web view, you'll need to create a class for it. Your class must:

+ 1
- 0
ios/RNCWebView.h View File

@@ -53,6 +53,7 @@
53 53
 @property (nonatomic, copy) NSString *allowingReadAccessToURL;
54 54
 
55 55
 + (void)setClientAuthenticationCredential:(nullable NSURLCredential*)credential;
56
++ (void)setCustomCertificatesForHost:(nullable NSDictionary *)certificates;
56 57
 - (void)postMessage:(NSString *)message;
57 58
 - (void)injectJavaScript:(NSString *)script;
58 59
 - (void)goForward;

+ 34
- 8
ios/RNCWebView.m View File

@@ -16,6 +16,7 @@
16 16
 static NSTimer *keyboardTimer;
17 17
 static NSString *const MessageHandlerName = @"ReactNativeWebView";
18 18
 static NSURLCredential* clientAuthenticationCredential;
19
+static NSDictionary* customCertificatesForHost;
19 20
 
20 21
 // runtime trick to remove WKWebView keyboard default toolbar
21 22
 // see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279
@@ -646,19 +647,44 @@ static NSURLCredential* clientAuthenticationCredential;
646 647
   clientAuthenticationCredential = credential;
647 648
 }
648 649
 
650
++ (void)setCustomCertificatesForHost:(nullable NSDictionary*)certificates {
651
+    customCertificatesForHost = certificates;
652
+}
653
+
649 654
 - (void)                    webView:(WKWebView *)webView
650 655
   didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
651 656
                   completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable))completionHandler
652 657
 {
653
-  if (!clientAuthenticationCredential) {
654
-    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
655
-    return;
656
-  }
657
-  if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {
658
-    completionHandler(NSURLSessionAuthChallengeUseCredential, clientAuthenticationCredential);
659
-  } else {
658
+    NSString* host = nil;
659
+    if (webView.URL != nil) {
660
+        host = webView.URL.host;
661
+    }
662
+    if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {
663
+        completionHandler(NSURLSessionAuthChallengeUseCredential, clientAuthenticationCredential);
664
+        return;
665
+    }
666
+    if ([[challenge protectionSpace] serverTrust] != nil && customCertificatesForHost != nil && host != nil) {
667
+        SecCertificateRef localCertificate = (__bridge SecCertificateRef)([customCertificatesForHost objectForKey:host]);
668
+        if (localCertificate != nil) {
669
+            NSData *localCertificateData = (NSData*) CFBridgingRelease(SecCertificateCopyData(localCertificate));
670
+            SecTrustRef trust = [[challenge protectionSpace] serverTrust];
671
+            long count = SecTrustGetCertificateCount(trust);
672
+            for (long i = 0; i < count; i++) {
673
+                SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(trust, i);
674
+                if (serverCertificate == nil) { continue; }
675
+                NSData *serverCertificateData = (NSData *) CFBridgingRelease(SecCertificateCopyData(serverCertificate));
676
+                if ([serverCertificateData isEqualToData:localCertificateData]) {
677
+                    NSURLCredential *useCredential = [NSURLCredential credentialForTrust:trust];
678
+                    if (challenge.sender != nil) {
679
+                        [challenge.sender useCredential:useCredential forAuthenticationChallenge:challenge];
680
+                    }
681
+                    completionHandler(NSURLSessionAuthChallengeUseCredential, useCredential);
682
+                    return;
683
+                }
684
+            }
685
+        }
686
+    }
660 687
     completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
661
-  }
662 688
 }
663 689
 
664 690
 #pragma mark - WKNavigationDelegate methods