Commit 6c2a6071 authored by Lorenzo Pichilli's avatar Lorenzo Pichilli

Added SslCertificate class and X509Certificate class and parser, updated ServerTrustChallenge class

parent b9d14828
## 3.4.0
- Added `requestFocusNodeHref`, `requestImageRef`, `getMetaTags`, `getMetaThemeColor`, `getScrollX`, `getScrollY` webview methods
- Added `requestFocusNodeHref`, `requestImageRef`, `getMetaTags`, `getMetaThemeColor`, `getScrollX`, `getScrollY`, `getCertificate` webview methods
- Added `WebStorage`, `LocalStorage` and `SessionStorage` class to manage `window.localStorage` and `window.sessionStorage` JavaScript [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)
- Added `supportZoom` webview option also on iOS
- Added `HttpOnly`, `SameSite` set cookie options
......@@ -8,8 +8,8 @@
- Added `animated` option to `scrollTo` and `scrollBy` webview methods
- Added error and message to the `ServerTrustChallenge` class for iOS (class used by the `onReceivedServerTrustAuthRequest` event)
- Added `contentInsetAdjustmentBehavior` webview iOS-specific option
- Added `getCertificate` android-specific webview method
- Added `copy` and `copyWithValue` methods for webview class options
- Added X509Certificate class and parser
- Fixed `zoomBy`, `setOptions` webview methods on Android
- Fixed `databaseEnabled` android webview option default value to `true`
......@@ -20,7 +20,7 @@
- Moved `supportZoom` webview option to cross-platform
- `builtInZoomControls` android webview options changed default value to `true`
- Updated `ServerTrustChallenge` class used by the `onReceivedServerTrustAuthRequest` event
- The method `getOptions`could return null now
- The method `getOptions` could return null now
## 3.3.0+3
......
......@@ -973,7 +973,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
public Map<String, Object> getCertificate() {
if (webView != null)
return webView.getSslCertificate();
return webView.getCertificateMap();
return null;
}
......
......@@ -488,7 +488,7 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
break;
case "getCertificate":
if (webView != null) {
result.success(webView.getSslCertificate());
result.success(webView.getCertificateMap());
} else {
result.success(null);
}
......
......@@ -1864,108 +1864,56 @@ final public class InAppWebView extends InputAwareWebView {
return obj;
}
public Map<String, Object> getSslCertificate() {
SslCertificate sslCertificate = getCertificate();
SslCertificate.DName issuedByName = sslCertificate.getIssuedBy();
Map<String, Object> issuedBy = new HashMap<>();
issuedBy.put("CName", issuedByName.getCName());
issuedBy.put("DName", issuedByName.getDName());
issuedBy.put("OName", issuedByName.getOName());
issuedBy.put("UName", issuedByName.getUName());
SslCertificate.DName issuedToName = sslCertificate.getIssuedTo();
Map<String, Object> issuedTo = new HashMap<>();
issuedTo.put("CName", issuedToName.getCName());
issuedTo.put("DName", issuedToName.getDName());
issuedTo.put("OName", issuedToName.getOName());
issuedTo.put("UName", issuedToName.getUName());
Map<String, Object> x509CertificateMap = new HashMap<>();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
X509Certificate x509Certificate = sslCertificate.getX509Certificate();
if (x509Certificate != null) {
x509CertificateMap.put("basicConstraints", x509Certificate.getBasicConstraints());
try {
x509CertificateMap.put("extendedKeyUsage", x509Certificate.getExtendedKeyUsage());
} catch (CertificateParsingException e) {
x509CertificateMap.put("extendedKeyUsage", null);
}
Map<String, Object> issuerDN = new HashMap<>();
issuerDN.put("name", x509Certificate.getIssuerDN().getName());
x509CertificateMap.put("issuerDN", issuerDN);
x509CertificateMap.put("issuerUniqueID", x509Certificate.getIssuerUniqueID());
Map<String, Object> issuerX500Principal = new HashMap<>();
issuerX500Principal.put("name", x509Certificate.getIssuerX500Principal().getName());
issuerX500Principal.put("encoded", x509Certificate.getIssuerX500Principal().getEncoded());
x509CertificateMap.put("issuerX500Principal", issuerX500Principal);
x509CertificateMap.put("keyUsage", x509Certificate.getKeyUsage());
x509CertificateMap.put("notAfter", x509Certificate.getNotAfter().getTime());
x509CertificateMap.put("notBefore", x509Certificate.getNotBefore().getTime());
x509CertificateMap.put("serialNumber", x509Certificate.getSerialNumber().longValue());
x509CertificateMap.put("sigAlgName", x509Certificate.getSigAlgName());
x509CertificateMap.put("sigAlgOID", x509Certificate.getSigAlgOID());
x509CertificateMap.put("sigAlgParams", x509Certificate.getSigAlgParams());
x509CertificateMap.put("signature", x509Certificate.getSignature());
Map<String, Object> subjectDN = new HashMap<>();
subjectDN.put("name", x509Certificate.getSubjectDN().getName());
x509CertificateMap.put("subjectDN", subjectDN);
x509CertificateMap.put("subjectUniqueID", x509Certificate.getSubjectUniqueID());
Map<String, Object> subjectX500Principal = new HashMap<>();
subjectX500Principal.put("name", x509Certificate.getSubjectX500Principal().getName());
subjectX500Principal.put("encoded", x509Certificate.getSubjectX500Principal().getEncoded());
x509CertificateMap.put("subjectX500Principal", subjectX500Principal);
public Map<String, Object> getCertificateMap() {
return InAppWebView.getCertificateMap(getCertificate());
}
public static Map<String, Object> getCertificateMap(SslCertificate sslCertificate) {
if (sslCertificate != null) {
SslCertificate.DName issuedByName = sslCertificate.getIssuedBy();
Map<String, Object> issuedBy = new HashMap<>();
issuedBy.put("CName", issuedByName.getCName());
issuedBy.put("DName", issuedByName.getDName());
issuedBy.put("OName", issuedByName.getOName());
issuedBy.put("UName", issuedByName.getUName());
SslCertificate.DName issuedToName = sslCertificate.getIssuedTo();
Map<String, Object> issuedTo = new HashMap<>();
issuedTo.put("CName", issuedToName.getCName());
issuedTo.put("DName", issuedToName.getDName());
issuedTo.put("OName", issuedToName.getOName());
issuedTo.put("UName", issuedToName.getUName());
byte[] x509CertificateData = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
try {
x509CertificateMap.put("TBSCertificate", x509Certificate.getTBSCertificate());
X509Certificate certificate = sslCertificate.getX509Certificate();
if (certificate != null) {
x509CertificateData = certificate.getEncoded();
}
} catch (CertificateEncodingException e) {
x509CertificateMap.put("TBSCertificate", null);
e.printStackTrace();
}
x509CertificateMap.put("version", x509Certificate.getVersion());
x509CertificateMap.put("criticalExtensionOIDs", x509Certificate.getCriticalExtensionOIDs());
x509CertificateMap.put("nonCriticalExtensionOIDs", x509Certificate.getNonCriticalExtensionOIDs());
} else {
try {
x509CertificateMap.put("encoded", x509Certificate.getEncoded());
x509CertificateData = Util.getX509CertFromSslCertHack(sslCertificate).getEncoded();
} catch (CertificateEncodingException e) {
x509CertificateMap.put("encoded", null);
e.printStackTrace();
}
}
Map<String, Object> publicKey = new HashMap<>();
publicKey.put("algorithm", x509Certificate.getPublicKey().getAlgorithm());
publicKey.put("encoded", x509Certificate.getPublicKey().getEncoded());
publicKey.put("format", x509Certificate.getPublicKey().getFormat());
x509CertificateMap.put("publicKey", publicKey);
x509CertificateMap.put("type", x509Certificate.getType());
x509CertificateMap.put("hasUnsupportedCriticalExtension", x509Certificate.hasUnsupportedCriticalExtension());
Map<String, Object> obj = new HashMap<>();
obj.put("issuedBy", issuedBy);
obj.put("issuedTo", issuedTo);
obj.put("validNotAfterDate", sslCertificate.getValidNotAfterDate().getTime());
obj.put("validNotBeforeDate", sslCertificate.getValidNotBeforeDate().getTime());
obj.put("x509Certificate", x509CertificateData);
try {
x509Certificate.checkValidity();
x509CertificateMap.put("valid", true);
} catch (CertificateExpiredException e) {
x509CertificateMap.put("valid", false);
} catch (CertificateNotYetValidException e) {
x509CertificateMap.put("valid", false);
}
}
return obj;
}
Map<String, Object> obj = new HashMap<>();
obj.put("issuedBy", issuedBy);
obj.put("issuedTo", issuedTo);
obj.put("validNotAfterDate", sslCertificate.getValidNotAfterDate().getTime());
obj.put("validNotBeforeDate", sslCertificate.getValidNotBeforeDate().getTime());
obj.put("x509Certificate", x509CertificateMap);
return obj;
return null;
}
@Override
......
......@@ -301,7 +301,6 @@ public class InAppWebViewClient extends WebViewClient {
url = new URL(view.getUrl());
} catch (MalformedURLException e) {
e.printStackTrace();
Log.e(LOG_TAG, e.getMessage());
credentialsProposed = null;
previousAuthRequestFailureCount = 0;
......@@ -378,32 +377,6 @@ public class InAppWebViewClient extends WebViewClient {
});
}
/**
* SslCertificate class does not has a public getter for the underlying
* X509Certificate, we can only do this by hack. This only works for andorid 4.0+
* https://groups.google.com/forum/#!topic/android-developers/eAPJ6b7mrmg
*/
public static X509Certificate getX509CertFromSslCertHack(SslCertificate sslCert) {
X509Certificate x509Certificate = null;
Bundle bundle = SslCertificate.saveState(sslCert);
byte[] bytes = bundle.getByteArray("x509-certificate");
if (bytes == null) {
x509Certificate = null;
} else {
try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
x509Certificate = (X509Certificate) cert;
} catch (CertificateException e) {
x509Certificate = null;
}
}
return x509Certificate;
}
@Override
public void onReceivedSslError(final WebView view, final SslErrorHandler handler, final SslError error) {
URL url;
......@@ -411,7 +384,6 @@ public class InAppWebViewClient extends WebViewClient {
url = new URL(error.getUrl());
} catch (MalformedURLException e) {
e.printStackTrace();
Log.e(LOG_TAG, e.getMessage());
handler.cancel();
return;
}
......@@ -430,19 +402,7 @@ public class InAppWebViewClient extends WebViewClient {
obj.put("port", port);
obj.put("androidError", error.getPrimaryError());
obj.put("iosError", null);
obj.put("serverCertificate", null);
try {
X509Certificate certificate;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
certificate = error.getCertificate().getX509Certificate();
} else {
certificate = getX509CertFromSslCertHack(error.getCertificate());
}
obj.put("serverCertificate", certificate.getEncoded());
} catch (CertificateEncodingException e) {
e.printStackTrace();
Log.e(LOG_TAG,e.getLocalizedMessage());
}
obj.put("sslCertificate", InAppWebView.getCertificateMap(error.getCertificate()));
String message;
switch (error.getPrimaryError()) {
......@@ -511,7 +471,6 @@ public class InAppWebViewClient extends WebViewClient {
url = new URL(view.getUrl());
} catch (MalformedURLException e) {
e.printStackTrace();
Log.e(LOG_TAG, e.getMessage());
request.cancel();
return;
}
......@@ -658,7 +617,6 @@ public class InAppWebViewClient extends WebViewClient {
uri = new URI(scheme, tempUrl.getUserInfo(), tempUrl.getHost(), tempUrl.getPort(), tempUrl.getPath(), tempUrl.getQuery(), tempUrl.getRef());
} catch (Exception e) {
e.printStackTrace();
Log.d(LOG_TAG, e.getMessage());
return null;
}
}
......@@ -677,7 +635,6 @@ public class InAppWebViewClient extends WebViewClient {
flutterResult = Util.invokeMethodAndWait(channel, "onLoadResourceCustomScheme", obj);
} catch (InterruptedException e) {
e.printStackTrace();
Log.e(LOG_TAG, e.getMessage());
return null;
}
......@@ -691,7 +648,6 @@ public class InAppWebViewClient extends WebViewClient {
response = webView.contentBlockerHandler.checkUrl(webView, url, res.get("content-type").toString());
} catch (Exception e) {
e.printStackTrace();
Log.e(LOG_TAG, e.getMessage());
}
if (response != null)
return response;
......@@ -706,7 +662,6 @@ public class InAppWebViewClient extends WebViewClient {
response = webView.contentBlockerHandler.checkUrl(webView, url);
} catch (Exception e) {
e.printStackTrace();
Log.e(LOG_TAG, e.getMessage());
}
}
return response;
......@@ -763,7 +718,6 @@ public class InAppWebViewClient extends WebViewClient {
flutterResult = Util.invokeMethodAndWait(channel, "shouldInterceptRequest", obj);
} catch (InterruptedException e) {
e.printStackTrace();
Log.e(LOG_TAG, e.getMessage());
return null;
}
......
package com.pichillilorenzo.flutter_inappwebview;
import android.content.res.AssetManager;
import android.net.http.SslCertificate;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
......@@ -8,6 +9,7 @@ import android.os.Looper;
import android.os.Parcelable;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
......@@ -16,6 +18,7 @@ import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.HashMap;
......@@ -205,4 +208,30 @@ public class Util {
throw new RuntimeException(e);
}
}
/**
* SslCertificate class does not has a public getter for the underlying
* X509Certificate, we can only do this by hack. This only works for andorid 4.0+
* https://groups.google.com/forum/#!topic/android-developers/eAPJ6b7mrmg
*/
public static X509Certificate getX509CertFromSslCertHack(SslCertificate sslCert) {
X509Certificate x509Certificate = null;
Bundle bundle = SslCertificate.saveState(sslCert);
byte[] bytes = bundle.getByteArray("x509-certificate");
if (bytes == null) {
x509Certificate = null;
} else {
try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
x509Certificate = (X509Certificate) cert;
} catch (CertificateException e) {
x509Certificate = null;
}
}
return x509Certificate;
}
}
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"android":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+3/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"e2e","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-06-13 01:39:11.912485","version":"1.17.1"}
\ No newline at end of file
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"android":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+3/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"e2e","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-06-14 18:36:37.641339","version":"1.17.1"}
\ No newline at end of file
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
......@@ -107,7 +108,6 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
setState(() {
this.url = url;
});
},
onProgressChanged: (InAppWebViewController controller, int progress) {
setState(() {
......
......@@ -26,7 +26,6 @@
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappwebview/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappwebview/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappwebview/example/build" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/Flutter/App.framework/flutter_assets/packages" />
<excludeFolder url="file://$MODULE_DIR$/flutter_inappbrowser_tests/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/flutter_inappbrowser_tests/.pub" />
<excludeFolder url="file://$MODULE_DIR$/flutter_inappbrowser_tests/build" />
......
......@@ -491,6 +491,13 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
result(false)
}
break
case "getCertificate":
if webView != nil {
result(webView!.getCertificateMap())
} else {
result(false)
}
break
default:
result(FlutterMethodNotImplemented)
break
......
......@@ -328,6 +328,55 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS
result(false)
}
break
case "requestFocusNodeHref":
if webView != nil {
webView!.requestFocusNodeHref { (value, error) in
if let err = error {
print(err.localizedDescription)
result(nil)
return
}
result(value)
}
} else {
result(false)
}
break
case "requestImageRef":
if webView != nil {
webView!.requestImageRef { (value, error) in
if let err = error {
print(err.localizedDescription)
result(nil)
return
}
result(value)
}
} else {
result(false)
}
break
case "getScrollX":
if webView != nil {
result(Int(webView!.scrollView.contentOffset.x))
} else {
result(false)
}
break
case "getScrollY":
if webView != nil {
result(Int(webView!.scrollView.contentOffset.y))
} else {
result(false)
}
break
case "getCertificate":
if webView != nil {
result(webView!.getCertificateMap())
} else {
result(false)
}
break
default:
result(FlutterMethodNotImplemented)
break
......
......@@ -799,6 +799,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
var channel: FlutterMethodChannel?
var options: InAppWebViewOptions?
var currentURL: URL?
var x509CertificateData: Data?
static var sslCertificateMap: [String: Data] = [:] // [URL host name : x509Certificate Data]
var startPageTime: Int64 = 0
static var credentialsProposed: [URLCredential] = []
var lastScrollX: CGFloat = 0
......@@ -1863,6 +1865,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
self.x509CertificateData = nil
self.startPageTime = currentTimeInMilliSeconds()
onLoadStart(url: (currentURL?.absoluteString)!)
......@@ -2557,6 +2561,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
let data = CFDataGetBytePtr(serverCertificateCFData)
let size = CFDataGetLength(serverCertificateCFData)
serverCertificateData = NSData(bytes: data, length: size)
if (x509CertificateData == nil) {
x509CertificateData = Data(serverCertificateData!)
InAppWebView.sslCertificateMap[challenge.protectionSpace.host] = x509CertificateData;
}
}
let error = secResult != SecTrustResultType.proceed ? secResult.rawValue : nil
......@@ -2591,7 +2599,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
"realm": challenge.protectionSpace.realm,
"port": challenge.protectionSpace.port,
"previousFailureCount": challenge.previousFailureCount,
"serverCertificate": serverCertificateData,
"sslCertificate": InAppWebView.getCertificateMap(x509Certificate:
((serverCertificateData != nil) ? Data(serverCertificateData!) : nil)),
"androidError": nil,
"iosError": error,
"message": message,
......@@ -2892,6 +2901,29 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
self.scrollView.subviews.first?.resignFirstResponder()
}
public func getCertificate() -> Data? {
var x509Certificate = self.x509CertificateData
if x509Certificate == nil, let scheme = url?.scheme, scheme == "https",
let host = url?.host, let cert = InAppWebView.sslCertificateMap[host] {
x509Certificate = cert
}
return x509Certificate
}
public func getCertificateMap() -> [String: Any?]? {
return InAppWebView.getCertificateMap(x509Certificate: getCertificate())
}
public static func getCertificateMap(x509Certificate: Data?) -> [String: Any?]? {
return x509Certificate != nil ? [
"issuedBy": nil,
"issuedTo": nil,
"validNotAfterDate": nil,
"validNotBeforeDate": nil,
"x509Certificate": x509Certificate
] : nil;
}
public func dispose() {
stopLoading()
configuration.userContentController.removeScriptMessageHandler(forName: "consoleLog")
......
......@@ -37,3 +37,4 @@ export 'src/http_auth_credentials_database.dart';
export 'src/web_storage_manager.dart';
export 'src/context_menu.dart';
export 'src/web_storage.dart';
export 'src/X509Certificate/main.dart';
\ No newline at end of file
This diff is collapsed.
class ASN1DistinguishedNames {
final String _oid;
final String _representation;
const ASN1DistinguishedNames._internal(this._oid, this._representation);
static List<ASN1DistinguishedNames> values = [
ASN1DistinguishedNames.COMMON_NAME,
ASN1DistinguishedNames.DN_QUALIFIER,
ASN1DistinguishedNames.SERIAL_NUMBER,
ASN1DistinguishedNames.GIVEN_NAME,
ASN1DistinguishedNames.SURNAME,
ASN1DistinguishedNames.ORGANIZATIONAL_UNIT_NAME,
ASN1DistinguishedNames.ORGANIZATION_NAME,
ASN1DistinguishedNames.STREET_ADDRESS,
ASN1DistinguishedNames.LOCALITY_NAME,
ASN1DistinguishedNames.STATE_OR_PROVINCE_NAME,
ASN1DistinguishedNames.COUNTRY_NAME,
ASN1DistinguishedNames.EMAIL,
];
static ASN1DistinguishedNames fromValue(String oid) {
return ASN1DistinguishedNames.values.firstWhere((element) => element.oid() == oid, orElse: () => null);
}
String oid() => _oid;
String representation() => _representation;
@override
String toString() => "($_oid, $_representation)";
static const COMMON_NAME = const ASN1DistinguishedNames._internal("2.5.4.3", "CN");
static const DN_QUALIFIER = const ASN1DistinguishedNames._internal("2.5.4.46", "DNQ");
static const SERIAL_NUMBER = const ASN1DistinguishedNames._internal("2.5.4.5", "SERIALNUMBER");
static const GIVEN_NAME = const ASN1DistinguishedNames._internal("2.5.4.42", "GIVENNAME");
static const SURNAME = const ASN1DistinguishedNames._internal("2.5.4.4", "SURNAME");
static const ORGANIZATIONAL_UNIT_NAME = const ASN1DistinguishedNames._internal("2.5.4.11", "OU");
static const ORGANIZATION_NAME = const ASN1DistinguishedNames._internal("2.5.4.10", "O");
static const STREET_ADDRESS = const ASN1DistinguishedNames._internal("2.5.4.9", "STREET");
static const LOCALITY_NAME = const ASN1DistinguishedNames._internal("2.5.4.7", "L");
static const STATE_OR_PROVINCE_NAME = const ASN1DistinguishedNames._internal("2.5.4.8", "ST");
static const COUNTRY_NAME = const ASN1DistinguishedNames._internal("2.5.4.6", "C");
static const EMAIL = const ASN1DistinguishedNames._internal("1.2.840.113549.1.9.1", "E");
bool operator ==(value) => value == _oid;
@override
int get hashCode => _oid.hashCode;
}
\ No newline at end of file
class ASN1IdentifierClass {
final int _value;
const ASN1IdentifierClass._internal(this._value);
static List<ASN1IdentifierClass> values = [
ASN1IdentifierClass.UNIVERSAL,
ASN1IdentifierClass.APPLICATION,
ASN1IdentifierClass.CONTEXT_SPECIFIC,
ASN1IdentifierClass.PRIVATE,
];
static ASN1IdentifierClass fromValue(int value) {
if (value != null)
return ASN1IdentifierClass.values.firstWhere((element) => element.toValue() == value, orElse: () => null);
return null;
}
int toValue() => _value;
static const UNIVERSAL = const ASN1IdentifierClass._internal(0x00);
static const APPLICATION = const ASN1IdentifierClass._internal(0x40);
static const CONTEXT_SPECIFIC = const ASN1IdentifierClass._internal(0x80);
static const PRIVATE = const ASN1IdentifierClass._internal(0xC0);
String toString() {
switch (this.toValue()) {
case 0x00:
return "UNIVERSAL";
case 0x40:
return "APPLICATION";
case 0x80:
return "CONTEXT_SPECIFIC";
case 0xC0:
return "PRIVATE";
}
}
bool operator ==(value) => value == _value;
@override
int get hashCode => _value.hashCode;
}
class ASN1IdentifierTagNumber {
final int _value;
const ASN1IdentifierTagNumber._internal(this._value);
static List<ASN1IdentifierTagNumber> values = [
ASN1IdentifierTagNumber.END_OF_CONTENT,
ASN1IdentifierTagNumber.BOOLEAN,
ASN1IdentifierTagNumber.INTEGER,
ASN1IdentifierTagNumber.BIT_STRING,
ASN1IdentifierTagNumber.OCTET_STRING,
ASN1IdentifierTagNumber.NULL,
ASN1IdentifierTagNumber.OBJECT_IDENTIFIER,
ASN1IdentifierTagNumber.OBJECT_DESCRIPTOR,
ASN1IdentifierTagNumber.EXTERNAL,
ASN1IdentifierTagNumber.READ,
ASN1IdentifierTagNumber.ENUMERATED,
ASN1IdentifierTagNumber.EMBEDDED_PDV,
ASN1IdentifierTagNumber.UTF8_STRING,
ASN1IdentifierTagNumber.RELATIVE_OID,
ASN1IdentifierTagNumber.SEQUENCE,
ASN1IdentifierTagNumber.SET,
ASN1IdentifierTagNumber.NUMERIC_STRING,
ASN1IdentifierTagNumber.PRINTABLE_STRING,
ASN1IdentifierTagNumber.T61_STRING,
ASN1IdentifierTagNumber.VIDEOTEX_STRING,
ASN1IdentifierTagNumber.IA5_STRING,
ASN1IdentifierTagNumber.UTC_TIME,
ASN1IdentifierTagNumber.GENERALIZED_TIME,
ASN1IdentifierTagNumber.GRAPHIC_STRING,
ASN1IdentifierTagNumber.VISIBLE_STRING,
ASN1IdentifierTagNumber.GENERAL_STRING,
ASN1IdentifierTagNumber.UNIVERSAL_STRING,
ASN1IdentifierTagNumber.CHARACTER_STRING,
ASN1IdentifierTagNumber.BMP_STRING,
];
static ASN1IdentifierTagNumber fromValue(int value) {
if (value != null)
return ASN1IdentifierTagNumber.values.firstWhere((element) => element.toValue() == value, orElse: () => null);
return null;
}
int toValue() => _value;
static const END_OF_CONTENT = const ASN1IdentifierTagNumber._internal(0x00);
static const BOOLEAN = const ASN1IdentifierTagNumber._internal(0x01);
static const INTEGER = const ASN1IdentifierTagNumber._internal(0x02);
static const BIT_STRING = const ASN1IdentifierTagNumber._internal(0x03);
static const OCTET_STRING = const ASN1IdentifierTagNumber._internal(0x04);
static const NULL = const ASN1IdentifierTagNumber._internal(0x05);
static const OBJECT_IDENTIFIER = const ASN1IdentifierTagNumber._internal(0x06);
static const OBJECT_DESCRIPTOR = const ASN1IdentifierTagNumber._internal(0x07);
static const EXTERNAL = const ASN1IdentifierTagNumber._internal(0x08);
static const READ = const ASN1IdentifierTagNumber._internal(0x09);
static const ENUMERATED = const ASN1IdentifierTagNumber._internal(0x0A);
static const EMBEDDED_PDV = const ASN1IdentifierTagNumber._internal(0x0B);
static const UTF8_STRING = const ASN1IdentifierTagNumber._internal(0x0C);
static const RELATIVE_OID = const ASN1IdentifierTagNumber._internal(0x0D);
static const SEQUENCE = const ASN1IdentifierTagNumber._internal(0x10);
static const SET = const ASN1IdentifierTagNumber._internal(0x11);
static const NUMERIC_STRING = const ASN1IdentifierTagNumber._internal(0x12);
static const PRINTABLE_STRING = const ASN1IdentifierTagNumber._internal(0x13);
static const T61_STRING = const ASN1IdentifierTagNumber._internal(0x14);
static const VIDEOTEX_STRING = const ASN1IdentifierTagNumber._internal(0x15);
static const IA5_STRING = const ASN1IdentifierTagNumber._internal(0x16);
static const UTC_TIME = const ASN1IdentifierTagNumber._internal(0x17);
static const GENERALIZED_TIME = const ASN1IdentifierTagNumber._internal(0x18);
static const GRAPHIC_STRING = const ASN1IdentifierTagNumber._internal(0x19);
static const VISIBLE_STRING = const ASN1IdentifierTagNumber._internal(0x1A);
static const GENERAL_STRING = const ASN1IdentifierTagNumber._internal(0x1B);
static const UNIVERSAL_STRING = const ASN1IdentifierTagNumber._internal(0x1C);
static const CHARACTER_STRING = const ASN1IdentifierTagNumber._internal(0x1D);
static const BMP_STRING = const ASN1IdentifierTagNumber._internal(0x1E);
String toString() {
switch (this.toValue()) {
case 0x00:
return "END_OF_CONTENT";
case 0x01:
return "BOOLEAN";
case 0x02:
return "INTEGER";
case 0x03:
return "BIT_STRING";
case 0x04:
return "OCTET_STRING";
case 0x05:
return "NULL";
case 0x06:
return "OBJECT_IDENTIFIER";
case 0x07:
return "OBJECT_DESCRIPTOR";
case 0x08:
return "EXTERNAL";
case 0x09:
return "READ";
case 0x0A:
return "ENUMERATED";
case 0x0B:
return "EMBEDDED_PDV";
case 0x0C:
return "UTF8_STRING";
case 0x0D:
return "RELATIVE_OID";
case 0x10:
return "SEQUENCE";
case 0x11:
return "SET";
case 0x12:
return "NUMERIC_STRING";
case 0x13:
return "PRINTABLE_STRING";
case 0x14:
return "T61_STRING";
case 0x15:
return "VIDEOTEX_STRING";
case 0x16:
return "IA5_STRING";
case 0x17:
return "UTC_TIME";
case 0x18:
return "GENERALIZED_TIME";
case 0x19:
return "GRAPHIC_STRING";
case 0x1A:
return "VISIBLE_STRING";
case 0x1B:
return "GENERAL_STRING";
case 0x1C:
return "UNIVERSAL_STRING";
case 0x1D:
return "CHARACTER_STRING";
case 0x1E:
return "BMP_STRING";
}
}
bool operator ==(value) => value == _value;
@override
int get hashCode => _value.hashCode;
}
class ASN1Identifier {
int rawValue;
ASN1Identifier(this.rawValue);
bool isPrimitive() {
return (rawValue & 0x20) == 0;
}
bool isConstructed() {
return (rawValue & 0x20) != 0;
}
ASN1IdentifierTagNumber tagNumber() {
return ASN1IdentifierTagNumber.fromValue(rawValue & 0x1F) ?? ASN1IdentifierTagNumber.END_OF_CONTENT;
}
ASN1IdentifierClass typeClass() {
for (var tc in [ASN1IdentifierClass.APPLICATION, ASN1IdentifierClass.CONTEXT_SPECIFIC, ASN1IdentifierClass.PRIVATE]) {
if ((rawValue & tc.toValue()) == tc.toValue()) {
return tc;
}
}
return ASN1IdentifierClass.UNIVERSAL;
}
String get description {
var tc = typeClass();
var tn = tagNumber();
if (tc == ASN1IdentifierClass.UNIVERSAL) {
return tn.toString();
} else {
return "$tc(${tn.toValue()})";
}
}
@override
String toString() {
return description;
}
}
\ No newline at end of file
import 'dart:typed_data';
import 'x509_certificate.dart';
import 'asn1_identifier.dart';
import 'oid.dart';
class ASN1Object {
/// This property contains the DER encoded object
Uint8List encoded;
/// This property contains the decoded Swift object whenever is possible
dynamic value;
ASN1Identifier identifier;
List<ASN1Object> sub;
ASN1Object parent;
ASN1Object subAtIndex(int index) {
if (sub != null && index >= 0 && index < sub.length) {
return sub[index];
}
return null;
}
int subCount() {
return sub?.length ?? 0;
}
ASN1Object findOid({OID oid, String oidValue}) {
oidValue = oid != null ? oid.toValue() : oidValue;
for (var child in (sub ?? <ASN1Object>[])) {
if (child.identifier?.tagNumber() == ASN1IdentifierTagNumber.OBJECT_IDENTIFIER) {
if (child.value == oidValue) {
return child;
}
}
else {
var result = child.findOid(oidValue: oidValue);
if (result != null) {
return result;
}
}
}
return null;
}
String get description {
return printAsn1();
}
String printAsn1({insets = ""}) {
var output = insets;
output += identifier?.description?.toUpperCase() ?? "";
output += (value != null ? ": $value" : "");
if (identifier?.typeClass() == ASN1IdentifierClass.UNIVERSAL && identifier?.tagNumber() == ASN1IdentifierTagNumber.OBJECT_IDENTIFIER) {
var descr = OID.fromValue(value?.toString() ?? "")?.name();
if (descr != null) {
output += " ($descr)";
}
}
output += sub != null && sub.length > 0 ? " {" : "";
output += "\n";
for (var item in (sub ?? <ASN1Object>[])) {
output += item.printAsn1(insets: insets + " ");
}
output += sub != null && sub.length > 0 ? "}\n" : "";
return output;
}
@override
String toString() {
return description;
}
ASN1Object atIndex(X509BlockPosition x509blockPosition) {
if (sub != null && x509blockPosition.index < sub.length) {
return sub[x509blockPosition.index];
}
return null;
}
}
\ No newline at end of file
export 'asn1_decoder.dart';
export 'asn1_distinguished_names.dart';
export 'asn1_identifier.dart';
export 'asn1_object.dart';
export 'oid.dart';
export 'x509_certificate.dart';
export 'x509_extension.dart';
export 'x509_public_key.dart';
This diff is collapsed.
This diff is collapsed.
import 'x509_certificate.dart';
import 'asn1_object.dart';
import 'oid.dart';
class X509Extension {
ASN1Object block;
X509Extension({this.block});
String get oid => block.subAtIndex(0)?.value;
String get name => OID.fromValue(oid ?? "")?.name();
bool get isCritical {
if ((block.sub?.length ?? 0) > 2) {
return block.subAtIndex(1)?.value ?? false;
}
return false;
}
dynamic get value {
var sub = block.sub;
if (sub != null && sub.length > 0) {
var valueBlock = sub.last;
if (valueBlock != null) {
return firstLeafValue(block: valueBlock);
}
}
return null;
}
ASN1Object get valueAsBlock {
var sub = block.sub;
if (sub != null && sub.length > 0) {
return sub.last;
}
return null;
}
List<String> get valueAsStrings {
var result = <String>[];
var sub = <ASN1Object>[];
try {
sub = block.sub?.last?.sub?.last?.sub;
} catch (e) {}
for (var item in sub) {
var name = item.value;
if (name != null) {
result.add(name);
}
}
return result;
}
}
\ No newline at end of file
import 'dart:typed_data';
import 'asn1_decoder.dart';
import 'asn1_object.dart';
import 'oid.dart';
class X509PublicKey {
ASN1Object pkBlock;
X509PublicKey({this.pkBlock});
String get algOid => pkBlock?.subAtIndex(0)?.subAtIndex(0)?.value;
String get algName => OID.fromValue(algOid ?? "")?.name();
String get algParams => pkBlock?.subAtIndex(0)?.subAtIndex(1)?.value;
Uint8List get encoded {
var oid = OID.fromValue(algOid);
var keyData = pkBlock?.subAtIndex(1)?.value ?? null;
if (oid != null && algOid != null && keyData != null) {
if (oid == OID.ecPublicKey) {
return Uint8List.fromList(keyData);
} else if (oid == OID.rsaEncryption) {
List<ASN1Object> publicKeyAsn1Objects;
try {
publicKeyAsn1Objects = ASN1DERDecoder.decode(data: keyData.toList(growable: true));
} catch(e) {}
if (publicKeyAsn1Objects != null && publicKeyAsn1Objects.length > 0) {
var publicKeyModulus = publicKeyAsn1Objects.first?.subAtIndex(0)?.value;
if (publicKeyModulus != null) {
return Uint8List.fromList(publicKeyModulus);
}
}
}
}
return null;
}
Map<String, dynamic> toMap() {
return {
"algOid": algOid,
"algName": algName,
"algParams": algParams,
"encoded": encoded,
};
}
Map<String, dynamic> toJson() {
return toMap();
}
}
\ No newline at end of file
import 'dart:developer';
import 'dart:io';
import 'dart:async';
import 'dart:collection';
......@@ -9,6 +10,8 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'X509Certificate/asn1_distinguished_names.dart';
import 'X509Certificate/x509_certificate.dart';
import 'package:html/parser.dart' show parse;
......@@ -369,7 +372,37 @@ class InAppWebViewController {
int androidError = call.arguments["androidError"];
int iosError = call.arguments["iosError"];
String message = call.arguments["message"];
Uint8List serverCertificate = call.arguments["serverCertificate"];
Map<String, dynamic> sslCertificateMap = call.arguments["sslCertificate"]?.cast<String, dynamic>();
SslCertificate sslCertificate;
if (sslCertificateMap != null) {
if (Platform.isIOS) {
try {
X509Certificate x509certificate = X509Certificate.fromData(data: sslCertificateMap["x509Certificate"]);
sslCertificate = SslCertificate(
issuedBy: SslCertificateDName(
CName: x509certificate.issuer(dn: ASN1DistinguishedNames.COMMON_NAME) ?? "",
DName: x509certificate.issuerDistinguishedName ?? "",
OName: x509certificate.issuer(dn: ASN1DistinguishedNames.ORGANIZATION_NAME) ?? "",
UName: x509certificate.issuer(dn: ASN1DistinguishedNames.ORGANIZATIONAL_UNIT_NAME) ?? ""),
issuedTo: SslCertificateDName(
CName: x509certificate.subject(dn: ASN1DistinguishedNames.COMMON_NAME) ?? "",
DName: x509certificate.subjectDistinguishedName ?? "",
OName: x509certificate.subject(dn: ASN1DistinguishedNames.ORGANIZATION_NAME) ?? "",
UName: x509certificate.subject(dn: ASN1DistinguishedNames.ORGANIZATIONAL_UNIT_NAME) ?? ""),
validNotAfterDate: x509certificate.notAfter,
validNotBeforeDate: x509certificate.notBefore,
x509Certificate: x509certificate,
);
} catch(e, stacktrace) {
print(e);
print(stacktrace);
return null;
}
} else {
sslCertificate = SslCertificate.fromMap(sslCertificateMap);
}
}
AndroidSslError androidSslError = androidError != null ? AndroidSslError.fromValue(androidError) : null;
IOSSslError iosSslError = iosError != null ? IOSSslError.fromValue(iosError) : null;
......@@ -381,7 +414,7 @@ class InAppWebViewController {
androidError: androidSslError,
iosError: iosSslError,
message: message,
serverCertificate: serverCertificate);
sslCertificate: sslCertificate);
if (_webview != null &&
_webview.onReceivedServerTrustAuthRequest != null)
return (await _webview.onReceivedServerTrustAuthRequest(
......@@ -1655,6 +1688,46 @@ class InAppWebViewController {
return await _channel.invokeMethod('getScrollY', args);
}
///Gets the SSL certificate for the main top-level page or null if there is no certificate (the site is not secure).
///
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#getCertificate()
Future<SslCertificate> getCertificate() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic> sslCertificateMap = (await _channel.invokeMethod('getCertificate', args))?.cast<String, dynamic>();
if (sslCertificateMap != null) {
if (Platform.isIOS) {
try {
X509Certificate x509certificate = X509Certificate.fromData(data: sslCertificateMap["x509Certificate"]);
return SslCertificate(
issuedBy: SslCertificateDName(
CName: x509certificate.issuer(dn: ASN1DistinguishedNames.COMMON_NAME) ?? "",
DName: x509certificate.issuerDistinguishedName ?? "",
OName: x509certificate.issuer(dn: ASN1DistinguishedNames.ORGANIZATION_NAME) ?? "",
UName: x509certificate.issuer(dn: ASN1DistinguishedNames.ORGANIZATIONAL_UNIT_NAME) ?? ""),
issuedTo: SslCertificateDName(
CName: x509certificate.subject(dn: ASN1DistinguishedNames.COMMON_NAME) ?? "",
DName: x509certificate.subjectDistinguishedName ?? "",
OName: x509certificate.subject(dn: ASN1DistinguishedNames.ORGANIZATION_NAME) ?? "",
UName: x509certificate.subject(dn: ASN1DistinguishedNames.ORGANIZATIONAL_UNIT_NAME) ?? ""),
validNotAfterDate: x509certificate.notAfter,
validNotBeforeDate: x509certificate.notBefore,
x509Certificate: x509certificate,
);
} catch(e, stacktrace) {
print(e);
print(stacktrace);
return null;
}
} else {
return SslCertificate.fromMap(sslCertificateMap);
}
}
return null;
}
///Gets the default user agent.
///
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebSettings#getDefaultUserAgent(android.content.Context)
......@@ -1794,16 +1867,6 @@ class AndroidInAppWebViewController {
return await _controller._channel.invokeMethod('clearHistory', args);
}
///Gets the SSL certificate for the main top-level page or null if there is no certificate (the site is not secure).
///
///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#getCertificate()
Future<AndroidSslCertificate> getCertificate() async {
Map<String, dynamic> args = <String, dynamic>{};
var sslCertificateMap = (await _controller._channel.invokeMethod('getCertificate', args))?.cast<String, dynamic>();
return sslCertificateMap != null ? AndroidSslCertificate.fromMap(sslCertificateMap) : null;
}
///Clears the client certificate preferences stored in response to proceeding/cancelling client cert requests.
///Note that WebView automatically clears these preferences when the system keychain is updated.
///The preferences are shared by all the WebViews that are created by the embedder application.
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment