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 4 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
 
142
 
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.
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
 ## JavaScript Interface
174
 ## JavaScript Interface
146
 
175
 
147
 To use your custom web view, you'll need to create a class for it. Your class must:
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
 @property (nonatomic, copy) NSString *allowingReadAccessToURL;
53
 @property (nonatomic, copy) NSString *allowingReadAccessToURL;
54
 
54
 
55
 + (void)setClientAuthenticationCredential:(nullable NSURLCredential*)credential;
55
 + (void)setClientAuthenticationCredential:(nullable NSURLCredential*)credential;
56
++ (void)setCustomCertificatesForHost:(nullable NSDictionary *)certificates;
56
 - (void)postMessage:(NSString *)message;
57
 - (void)postMessage:(NSString *)message;
57
 - (void)injectJavaScript:(NSString *)script;
58
 - (void)injectJavaScript:(NSString *)script;
58
 - (void)goForward;
59
 - (void)goForward;

+ 34
- 8
ios/RNCWebView.m View File

16
 static NSTimer *keyboardTimer;
16
 static NSTimer *keyboardTimer;
17
 static NSString *const MessageHandlerName = @"ReactNativeWebView";
17
 static NSString *const MessageHandlerName = @"ReactNativeWebView";
18
 static NSURLCredential* clientAuthenticationCredential;
18
 static NSURLCredential* clientAuthenticationCredential;
19
+static NSDictionary* customCertificatesForHost;
19
 
20
 
20
 // runtime trick to remove WKWebView keyboard default toolbar
21
 // runtime trick to remove WKWebView keyboard default toolbar
21
 // see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279
22
 // see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279
646
   clientAuthenticationCredential = credential;
647
   clientAuthenticationCredential = credential;
647
 }
648
 }
648
 
649
 
650
++ (void)setCustomCertificatesForHost:(nullable NSDictionary*)certificates {
651
+    customCertificatesForHost = certificates;
652
+}
653
+
649
 - (void)                    webView:(WKWebView *)webView
654
 - (void)                    webView:(WKWebView *)webView
650
   didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
655
   didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
651
                   completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable))completionHandler
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
     completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
687
     completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
661
-  }
662
 }
688
 }
663
 
689
 
664
 #pragma mark - WKNavigationDelegate methods
690
 #pragma mark - WKNavigationDelegate methods