diff --git a/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java b/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java index ab869cf..08ce7ce 100644 --- a/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java +++ b/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java @@ -84,6 +84,12 @@ import java.util.Map; import javax.annotation.Nullable; +import java.security.cert.X509Certificate; +import java.security.PrivateKey; +import android.webkit.ClientCertRequest; +import android.os.AsyncTask; +import android.security.KeyChain; + /** * Manages instances of {@link WebView} * <p> @@ -140,6 +146,8 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> { protected @Nullable String mUserAgent = null; protected @Nullable String mUserAgentWithApplicationName = null; + private static String certificateAlias = null; + public RNCWebViewManager() { mWebViewConfig = new WebViewConfig() { public void configWebView(WebView webView) { @@ -151,6 +159,10 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> { mWebViewConfig = webViewConfig; } + public static void setCertificateAlias(String alias) { + certificateAlias = alias; + } + protected static void dispatchEvent(WebView webView, Event event) { ReactContext reactContext = (ReactContext) webView.getContext(); EventDispatcher eventDispatcher = @@ -562,7 +574,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> { @Override protected void addEventEmitters(ThemedReactContext reactContext, WebView view) { // Do not register default touch emitter and let WebView implementation handle touches - view.setWebViewClient(new RNCWebViewClient()); + view.setWebViewClient(new RNCWebViewClient(reactContext)); } @Override @@ -742,12 +754,56 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> { protected static class RNCWebViewClient extends WebViewClient { + protected ReactContext reactContext; protected boolean mLastLoadFailed = false; protected @Nullable ReadableArray mUrlPrefixesForDefaultIntent; protected RNCWebView.ProgressChangedFilter progressChangedFilter = null; protected @Nullable String ignoreErrFailedForThisURL = null; + public RNCWebViewClient(ReactContext reactContext) { + this.reactContext = reactContext; + } + + @Override + public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) { + class SslStuff { + PrivateKey privKey; + X509Certificate[] certChain; + + public SslStuff(PrivateKey privKey, X509Certificate[] certChain) { + this.privKey = privKey; + this.certChain = certChain; + } + } + + if (certificateAlias != null) { + AsyncTask<Void, Void, SslStuff> task = new AsyncTask<Void, Void, SslStuff>() { + @Override + protected SslStuff doInBackground(Void... params) { + try { + PrivateKey privKey = KeyChain.getPrivateKey(reactContext, certificateAlias); + X509Certificate[] certChain = KeyChain.getCertificateChain(reactContext, certificateAlias); + + return new SslStuff(privKey, certChain); + } catch (Exception e) { + return null; + } + } + + @Override + protected void onPostExecute(SslStuff sslStuff) { + if (sslStuff != null) { + request.proceed(sslStuff.privKey, sslStuff.certChain); + } + } + }; + task.execute(); + } else { + super.onReceivedClientCertRequest(view, request); + } + } + public void setIgnoreErrFailedForThisURL(@Nullable String url) { ignoreErrFailedForThisURL = url; } diff --git a/node_modules/react-native-webview/apple/RNCWebView.m b/node_modules/react-native-webview/apple/RNCWebView.m index 02b4238..04bad05 100644 --- a/node_modules/react-native-webview/apple/RNCWebView.m +++ b/node_modules/react-native-webview/apple/RNCWebView.m @@ -17,6 +17,9 @@ #import "objc/runtime.h" +#import "SecureStorage.h" +#import <MMKV/MMKV.h> + static NSTimer *keyboardTimer; static NSString *const HistoryShimName = @"ReactNativeHistoryShim"; static NSString *const MessageHandlerName = @"ReactNativeWebView"; @@ -737,6 +740,68 @@ + (void)setCustomCertificatesForHost:(nullable NSDictionary*)certificates { customCertificatesForHost = certificates; } +-(NSURLCredential *)getUrlCredential:(NSURLAuthenticationChallenge *)challenge path:(NSString *)path password:(NSString *)password +{ + NSString *authMethod = [[challenge protectionSpace] authenticationMethod]; + SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; + + if ([authMethod isEqualToString:NSURLAuthenticationMethodServerTrust] || path == nil || password == nil) { + return [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; + } else if (path && password) { + NSMutableArray *policies = [NSMutableArray array]; + [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)challenge.protectionSpace.host)]; + SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies); + + SecTrustResultType result; + SecTrustEvaluate(serverTrust, &result); + + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) + { + return [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; + } + + NSData *p12data = [NSData dataWithContentsOfFile:path]; + NSDictionary* options = @{ (id)kSecImportExportPassphrase:password }; + CFArrayRef rawItems = NULL; + OSStatus status = SecPKCS12Import((__bridge CFDataRef)p12data, + (__bridge CFDictionaryRef)options, + &rawItems); + + if (status != noErr) { + return [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; + } + + NSArray* items = (NSArray*)CFBridgingRelease(rawItems); + NSDictionary* firstItem = nil; + if ((status == errSecSuccess) && ([items count]>0)) { + firstItem = items[0]; + } + + SecIdentityRef identity = (SecIdentityRef)CFBridgingRetain(firstItem[(id)kSecImportItemIdentity]); + SecCertificateRef certificate = NULL; + if (identity) { + SecIdentityCopyCertificate(identity, &certificate); + if (certificate) { CFRelease(certificate); } + } + + NSMutableArray *certificates = [[NSMutableArray alloc] init]; + [certificates addObject:CFBridgingRelease(certificate)]; + + return [NSURLCredential credentialWithIdentity:identity certificates:certificates persistence:NSURLCredentialPersistenceNone]; + } + + return [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; +} + +- (NSString *)stringToHex:(NSString *)string +{ + char *utf8 = (char *)[string UTF8String]; + NSMutableString *hex = [NSMutableString string]; + while (*utf8) [hex appendFormat:@"%02X", *utf8++ & 0x00FF]; + + return [[NSString stringWithFormat:@"%@", hex] lowercaseString]; +} + - (void) webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable))completionHandler @@ -746,7 +811,34 @@ - (void) webView:(WKWebView *)webView host = webView.URL.host; } if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) { - completionHandler(NSURLSessionAuthChallengeUseCredential, clientAuthenticationCredential); + NSString *host = challenge.protectionSpace.host; + + // Read the clientSSL info from MMKV + __block NSDictionary *clientSSL; + SecureStorage *secureStorage = [[SecureStorage alloc] init]; + + // https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/src/loader.js#L31 + [secureStorage getSecureKey:[self stringToHex:@"com.MMKV.default"] callback:^(NSArray *response) { + // Error happened + if ([response objectAtIndex:0] != [NSNull null]) { + return; + } + NSString *key = [response objectAtIndex:1]; + NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding]; + MMKV *mmkv = [MMKV mmkvWithID:@"default" cryptKey:cryptKey mode:MMKVMultiProcess]; + + clientSSL = [mmkv getObjectOfClass:[NSDictionary class] forKey:host]; + }]; + + NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; + + if (clientSSL != (id)[NSNull null]) { + NSString *path = [clientSSL objectForKey:@"path"]; + NSString *password = [clientSSL objectForKey:@"password"]; + credential = [self getUrlCredential:challenge path:path password:password]; + } + + completionHandler(NSURLSessionAuthChallengeUseCredential, credential); return; } if ([[challenge protectionSpace] serverTrust] != nil && customCertificatesForHost != nil && host != nil) {