diff --git a/CHANGELOG.md b/CHANGELOG.md index eca483a8..b179f671 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## 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 diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser/InAppBrowserActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser/InAppBrowserActivity.java index f7d63369..996e995f 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser/InAppBrowserActivity.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser/InAppBrowserActivity.java @@ -973,7 +973,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha public Map getCertificate() { if (webView != null) - return webView.getSslCertificate(); + return webView.getCertificateMap(); return null; } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java index 750471e6..8d881af4 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java @@ -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); } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java index 2d5deb98..3ab31112 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java @@ -1864,108 +1864,56 @@ final public class InAppWebView extends InputAwareWebView { return obj; } - public Map getSslCertificate() { - SslCertificate sslCertificate = getCertificate(); + public Map getCertificateMap() { + return InAppWebView.getCertificateMap(getCertificate()); + } - SslCertificate.DName issuedByName = sslCertificate.getIssuedBy(); - Map issuedBy = new HashMap<>(); - issuedBy.put("CName", issuedByName.getCName()); - issuedBy.put("DName", issuedByName.getDName()); - issuedBy.put("OName", issuedByName.getOName()); - issuedBy.put("UName", issuedByName.getUName()); + public static Map getCertificateMap(SslCertificate sslCertificate) { + if (sslCertificate != null) { + SslCertificate.DName issuedByName = sslCertificate.getIssuedBy(); + Map 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 issuedTo = new HashMap<>(); - issuedTo.put("CName", issuedToName.getCName()); - issuedTo.put("DName", issuedToName.getDName()); - issuedTo.put("OName", issuedToName.getOName()); - issuedTo.put("UName", issuedToName.getUName()); + SslCertificate.DName issuedToName = sslCertificate.getIssuedTo(); + Map issuedTo = new HashMap<>(); + issuedTo.put("CName", issuedToName.getCName()); + issuedTo.put("DName", issuedToName.getDName()); + issuedTo.put("OName", issuedToName.getOName()); + issuedTo.put("UName", issuedToName.getUName()); - Map 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()); + byte[] x509CertificateData = null; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { try { - x509CertificateMap.put("extendedKeyUsage", x509Certificate.getExtendedKeyUsage()); - } catch (CertificateParsingException e) { - x509CertificateMap.put("extendedKeyUsage", null); - } - - Map issuerDN = new HashMap<>(); - issuerDN.put("name", x509Certificate.getIssuerDN().getName()); - x509CertificateMap.put("issuerDN", issuerDN); - - x509CertificateMap.put("issuerUniqueID", x509Certificate.getIssuerUniqueID()); - - Map 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 subjectDN = new HashMap<>(); - subjectDN.put("name", x509Certificate.getSubjectDN().getName()); - x509CertificateMap.put("subjectDN", subjectDN); - - x509CertificateMap.put("subjectUniqueID", x509Certificate.getSubjectUniqueID()); - - Map subjectX500Principal = new HashMap<>(); - subjectX500Principal.put("name", x509Certificate.getSubjectX500Principal().getName()); - subjectX500Principal.put("encoded", x509Certificate.getSubjectX500Principal().getEncoded()); - x509CertificateMap.put("subjectX500Principal", subjectX500Principal); - - 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); - } - - Map 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()); - - try { - x509Certificate.checkValidity(); - x509CertificateMap.put("valid", true); - } catch (CertificateExpiredException e) { - x509CertificateMap.put("valid", false); - } catch (CertificateNotYetValidException e) { - x509CertificateMap.put("valid", false); + e.printStackTrace(); } } + + Map 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); + + return obj; } - Map 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 diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java index c006d8ff..099f3f7b 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java @@ -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; } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/Util.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/Util.java index fda2cb3c..0d05da5e 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/Util.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/Util.java @@ -1,6 +1,7 @@ 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; + } } diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies index b6fd9f35..7c453ff2 100755 --- a/example/.flutter-plugins-dependencies +++ b/example/.flutter-plugins-dependencies @@ -1 +1 @@ -{"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 diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index e132a74a..61893af3 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -1,3 +1,4 @@ +import 'dart:developer'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -107,7 +108,6 @@ class _InAppWebViewExampleScreenState extends State { setState(() { this.url = url; }); - }, onProgressChanged: (InAppWebViewController controller, int progress) { setState(() { diff --git a/flutter_inappwebview.iml b/flutter_inappwebview.iml index 111d60ac..c6749d82 100755 --- a/flutter_inappwebview.iml +++ b/flutter_inappwebview.iml @@ -26,7 +26,6 @@ - diff --git a/ios/Classes/FlutterWebViewController.swift b/ios/Classes/FlutterWebViewController.swift index 56c8f522..bb483f6c 100755 --- a/ios/Classes/FlutterWebViewController.swift +++ b/ios/Classes/FlutterWebViewController.swift @@ -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 diff --git a/ios/Classes/InAppBrowserWebViewController.swift b/ios/Classes/InAppBrowserWebViewController.swift index e20e89b8..3fd99234 100755 --- a/ios/Classes/InAppBrowserWebViewController.swift +++ b/ios/Classes/InAppBrowserWebViewController.swift @@ -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 diff --git a/ios/Classes/InAppWebView.swift b/ios/Classes/InAppWebView.swift index 22b8ed1c..addda0d1 100755 --- a/ios/Classes/InAppWebView.swift +++ b/ios/Classes/InAppWebView.swift @@ -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") diff --git a/lib/flutter_inappwebview.dart b/lib/flutter_inappwebview.dart index 22a69b39..2c95f52f 100755 --- a/lib/flutter_inappwebview.dart +++ b/lib/flutter_inappwebview.dart @@ -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 diff --git a/lib/src/X509Certificate/asn1_decoder.dart b/lib/src/X509Certificate/asn1_decoder.dart new file mode 100644 index 00000000..5097d0d5 --- /dev/null +++ b/lib/src/X509Certificate/asn1_decoder.dart @@ -0,0 +1,433 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'asn1_identifier.dart'; +import 'asn1_object.dart'; + +class ASN1DERDecoder { + static List decode({@required List data}) { + var iterator = data.iterator; + return parse(iterator: iterator); + } + + static List parse({@required Iterator iterator}) { + var result = []; + + while (iterator.moveNext()) { + var nextValue = iterator.current; + + var asn1obj = ASN1Object(); + asn1obj.identifier = ASN1Identifier(nextValue); + + if (asn1obj.identifier.isConstructed()) { + + var contentData = loadSubContent(iterator: iterator); + + if (contentData.isEmpty) { + asn1obj.sub = parse(iterator: iterator); + } else { + var subIterator = contentData.iterator; + asn1obj.sub = parse(iterator: subIterator); + } + + asn1obj.value = null; + + asn1obj.encoded = Uint8List.fromList(contentData); + + for (var item in asn1obj.sub) { + item.parent = asn1obj; + } + } else { + + if (asn1obj.identifier.typeClass() == ASN1IdentifierClass.UNIVERSAL) { + + var contentData = loadSubContent(iterator: iterator); + + asn1obj.encoded = Uint8List.fromList(contentData); + + // decode the content data with come more convenient format + + var tagNumber = asn1obj.identifier.tagNumber(); + + if (tagNumber == ASN1IdentifierTagNumber.END_OF_CONTENT) { + return result; + } + else if (tagNumber == ASN1IdentifierTagNumber.BOOLEAN) { + var value = contentData.length > 0 ? contentData.first : null; + if (value != null) { + asn1obj.value = value > 0 ? true : false; + } + } + else if (tagNumber == ASN1IdentifierTagNumber.INTEGER) { + while (contentData.length > 0 && contentData.first == 0) { + contentData.removeAt(0); // remove not significant digit + } + asn1obj.value = contentData; + } + else if (tagNumber == ASN1IdentifierTagNumber.NULL) { + asn1obj.value = null; + } + else if (tagNumber == ASN1IdentifierTagNumber.OBJECT_IDENTIFIER) { + asn1obj.value = decodeOid(contentData: contentData); + } + else if ([ + ASN1IdentifierTagNumber.UTF8_STRING, + ASN1IdentifierTagNumber.PRINTABLE_STRING, + ASN1IdentifierTagNumber.NUMERIC_STRING, + ASN1IdentifierTagNumber.GENERAL_STRING, + ASN1IdentifierTagNumber.UNIVERSAL_STRING, + ASN1IdentifierTagNumber.CHARACTER_STRING, + ASN1IdentifierTagNumber.T61_STRING].contains(tagNumber)) { + asn1obj.value = utf8.decode(contentData, allowMalformed: true); + } + else if (tagNumber == ASN1IdentifierTagNumber.BMP_STRING) { + asn1obj.value = String.fromCharCodes(contentData); + } + else if ([ + ASN1IdentifierTagNumber.VISIBLE_STRING, + ASN1IdentifierTagNumber.IA5_STRING + ].contains(tagNumber)) { + asn1obj.value = ascii.decode(contentData, allowInvalid: true); + } + else if (tagNumber == ASN1IdentifierTagNumber.UTC_TIME) { + asn1obj.value = utcTimeToDate(contentData: contentData); + } + else if (tagNumber == ASN1IdentifierTagNumber.GENERALIZED_TIME) { + asn1obj.value = generalizedTimeToDate(contentData: contentData); + } + else if (tagNumber == ASN1IdentifierTagNumber.BIT_STRING) { + if (contentData.length > 0) { + contentData.removeAt(0); // unused bits + } + asn1obj.value = contentData; + } + else if (tagNumber == ASN1IdentifierTagNumber.OCTET_STRING) { + try { + var subIterator = contentData.iterator; + asn1obj.sub = parse(iterator: subIterator); + } catch (e) { + + var str; + try { + str = utf8.decode(contentData); + } catch(e) {} + if (str != null) { + asn1obj.value = str; + } else { + asn1obj.value = contentData; + } + + } + } + else { + // print("unsupported tag: ${asn1obj.identifier.tagNumber()}"); + asn1obj.value = contentData; + } + } else { + // custom/private tag + + var contentData = loadSubContent(iterator: iterator); + + var str; + try { + str = utf8.decode(contentData); + } catch(e) {} + if (str != null) { + asn1obj.value = str; + } else { + asn1obj.value = contentData; + } + } + } + result.add(asn1obj); + } + + return result; + } + + static BigInt getContentLength({@required Iterator iterator}) { + if (iterator.moveNext()) { + var first = iterator.current; + if (first != null) { + + if ((first & 0x80) != 0) { // long + + var octetsToRead = first - 0x80; + var data = []; + for (var i = 0; i < octetsToRead; i++) { + if (iterator.moveNext()) { + var n = iterator.current; + if (n != null) { + data.add(n); + } + } + } + + return toIntValue(data) ?? BigInt.from(0); + + } else { // short + return BigInt.from(first); + } + + } + } + return BigInt.from(0); + } + + static List loadSubContent({@required Iterator iterator}) { + var len = getContentLength(iterator: iterator); + int int64MaxValue = 9223372036854775807; + + if (len >= BigInt.from(int64MaxValue)) { + return []; + } + + var byteArray = []; + + for (var i = 0; i < len.toInt(); i++) { + if (iterator.moveNext()) { + var n = iterator.current; + if (n != null) { + byteArray.add(n); + } + } else { + break; + // throw ASN1OutOfBufferError(); + } + } + + return byteArray; + } + + /// Decode DER OID bytes to String with dot notation + static String decodeOid({@required List contentData}) { + if (contentData.isEmpty) { + return ""; + } + + var oid = ""; + + var first = contentData.removeAt(0); + oid += "${(first / 40).truncate()}.${first % 40}"; + + var t = 0; + while (contentData.length > 0) { + var n = contentData.removeAt(0); + t = (t << 7) | (n & 0x7F); + if ((n & 0x80) == 0) { + oid += ".$t"; + t = 0; + } + } + return oid; + } + + ///Converts a UTCTime value to a date. + /// + ///Note: GeneralizedTime has 4 digits for the year and is used for X.509 + ///dates past 2049. Parsing that structure hasn't been implemented yet. + /// + ///[contentData] the UTCTime value to convert. + static DateTime utcTimeToDate({@required List contentData}) { + /* The following formats can be used: + YYMMDDhhmmZ + YYMMDDhhmm+hh'mm' + YYMMDDhhmm-hh'mm' + YYMMDDhhmmssZ + YYMMDDhhmmss+hh'mm' + YYMMDDhhmmss-hh'mm' + Where: + YY is the least significant two digits of the year + MM is the month (01 to 12) + DD is the day (01 to 31) + hh is the hour (00 to 23) + mm are the minutes (00 to 59) + ss are the seconds (00 to 59) + Z indicates that local time is GMT, + indicates that local time is + later than GMT, and - indicates that local time is earlier than GMT + hh' is the absolute value of the offset from GMT in hours + mm' is the absolute value of the offset from GMT in minutes */ + + String utc; + try { + utc = utf8.decode(contentData); + } catch(e) {} + if (utc == null) { + return null; + } + + // if YY >= 50 use 19xx, if YY < 50 use 20xx + var year = int.parse(utc.substring(0, 2), radix: 10); + year = (year >= 50) ? 1900 + year : 2000 + year; + var MM = int.parse(utc.substring(2, 4), radix: 10); + var DD = int.parse(utc.substring(4, 6), radix: 10); + var hh = int.parse(utc.substring(6, 8), radix: 10); + var mm = int.parse(utc.substring(8, 10), radix: 10); + var ss = 0; + + int end; + String c; + // not just YYMMDDhhmmZ + if(utc.length > 11) { + // get character after minutes + c = utc[10]; + end = 10; + + // see if seconds are present + if(c != '+' && c != '-') { + // get seconds + ss = int.parse(utc.substring(10, 12), radix: 10); + end += 2; + } + } + + var date = DateTime.utc( + year, + MM, + DD, + hh, + mm, + ss, + 0 + ); + + if(end != null) { + // get +/- after end of time + c = utc[end]; + if(c == '+' || c == '-') { + // get hours+minutes offset + var hhoffset = int.parse(utc.substring(end + 1, end + 1 + 2), radix: 10); + var mmoffset = int.parse(utc.substring(end + 4, end + 4 + 2), radix: 10); + + // calculate offset in milliseconds + var offset = hhoffset * 60 + mmoffset; + offset *= 60000; + + var offsetDuration = Duration(milliseconds: offset); + // apply offset + if(c == '+') { + date.subtract(offsetDuration); + } else { + date.add(offsetDuration); + } + } + } + + return date; + } + + ///Converts a GeneralizedTime value to a date. + /// + ///[contentData] the GeneralizedTime value to convert. + static DateTime generalizedTimeToDate({@required List contentData}) { + /* The following formats can be used: + YYYYMMDDHHMMSS + YYYYMMDDHHMMSS.fff + YYYYMMDDHHMMSSZ + YYYYMMDDHHMMSS.fffZ + YYYYMMDDHHMMSS+hh'mm' + YYYYMMDDHHMMSS.fff+hh'mm' + YYYYMMDDHHMMSS-hh'mm' + YYYYMMDDHHMMSS.fff-hh'mm' + Where: + YYYY is the year + MM is the month (01 to 12) + DD is the day (01 to 31) + hh is the hour (00 to 23) + mm are the minutes (00 to 59) + ss are the seconds (00 to 59) + .fff is the second fraction, accurate to three decimal places + Z indicates that local time is GMT, + indicates that local time is + later than GMT, and - indicates that local time is earlier than GMT + hh' is the absolute value of the offset from GMT in hours + mm' is the absolute value of the offset from GMT in minutes */ + + String gentime; + try { + gentime = utf8.decode(contentData); + } catch(e) {} + if (gentime == null) { + return null; + } + + // if YY >= 50 use 19xx, if YY < 50 use 20xx + var YYYY = int.parse(gentime.substring(0, 4), radix: 10); + var MM = int.parse(gentime.substring(4, 6), radix: 10); + var DD = int.parse(gentime.substring(6, 8), radix: 10); + var hh = int.parse(gentime.substring(8, 10), radix: 10); + var mm = int.parse(gentime.substring(10, 12), radix: 10); + var ss = int.parse(gentime.substring(12, 14), radix: 10); + + double fff = 0.0; + var offset = 0; + var isUTC = false; + + if(gentime[gentime.length - 1] == 'Z') { + isUTC = true; + } + + var end = gentime.length - 5; + var c = gentime[end]; + if(c == '+' || c == '-') { + // get hours+minutes offset + var hhoffset = int.parse(gentime.substring(end + 1, end + 1 + 2), radix: 10); + var mmoffset = int.parse(gentime.substring(end + 4, end + 4 + 2), radix: 10); + + // calculate offset in milliseconds + offset = hhoffset * 60 + mmoffset; + offset *= 60000; + + // apply offset + if(c == '+') { + offset *= -1; + } + + isUTC = true; + } + + // check for second fraction + if(gentime[14] == '.') { + fff = double.parse(gentime.substring(14)) * 1000; + } + + var date = DateTime.utc( + YYYY, + MM, + DD, + hh, + mm, + ss, + fff.toInt() + ); + + if(isUTC) { + var offsetDuration = Duration(milliseconds: offset); + date.add(offsetDuration); + } + + + return date; + } +} + +BigInt toIntValue(List data) { + if (data.length > 8) { + return null; + } + + BigInt value = BigInt.from(0); + for (var index = 0; index < data.length; index++) { + var byte = data[index]; + value += BigInt.from(byte << 8*(data.length-index-1)); + } + return value; +} + +class ASN1OutOfBufferError extends Error { + +} + +class ASN1ParseError extends Error { + +} \ No newline at end of file diff --git a/lib/src/X509Certificate/asn1_distinguished_names.dart b/lib/src/X509Certificate/asn1_distinguished_names.dart new file mode 100644 index 00000000..2f8798b0 --- /dev/null +++ b/lib/src/X509Certificate/asn1_distinguished_names.dart @@ -0,0 +1,50 @@ +class ASN1DistinguishedNames { + final String _oid; + final String _representation; + + const ASN1DistinguishedNames._internal(this._oid, this._representation); + + static List 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 diff --git a/lib/src/X509Certificate/asn1_identifier.dart b/lib/src/X509Certificate/asn1_identifier.dart new file mode 100644 index 00000000..f255c5aa --- /dev/null +++ b/lib/src/X509Certificate/asn1_identifier.dart @@ -0,0 +1,229 @@ +class ASN1IdentifierClass { + final int _value; + + const ASN1IdentifierClass._internal(this._value); + + static List 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 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 diff --git a/lib/src/X509Certificate/asn1_object.dart b/lib/src/X509Certificate/asn1_object.dart new file mode 100644 index 00000000..c5ee91b0 --- /dev/null +++ b/lib/src/X509Certificate/asn1_object.dart @@ -0,0 +1,84 @@ +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 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 ?? [])) { + 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 ?? [])) { + 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 diff --git a/lib/src/X509Certificate/main.dart b/lib/src/X509Certificate/main.dart new file mode 100644 index 00000000..0e27305d --- /dev/null +++ b/lib/src/X509Certificate/main.dart @@ -0,0 +1,8 @@ +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'; diff --git a/lib/src/X509Certificate/oid.dart b/lib/src/X509Certificate/oid.dart new file mode 100644 index 00000000..9287cb5d --- /dev/null +++ b/lib/src/X509Certificate/oid.dart @@ -0,0 +1,315 @@ +class OID { + final String _value; + + const OID._internal(this._value); + + static List values = [ + OID.etsiQcsCompliance, + OID.etsiQcsRetentionPeriod, + OID.etsiQcsQcSSCD, + OID.dsa, + OID.ecPublicKey, + OID.prime256v1, + OID.ecdsaWithSHA256, + OID.ecdsaWithSHA512, + OID.rsaEncryption, + OID.md2WithRSAEncryption, + OID.md4WithRSAEncryption, + OID.md5WithRSAEncryption, + OID.sha1WithRSAEncryption, + OID.RSAES_OAEP, + OID.mgf1, + OID.pSpecified, + OID.RSASSA_PSS, + OID.sha256WithRSAEncryption, + OID.sha384WithRSAEncryption, + OID.sha512WithRSAEncryption, + OID.pkcs7data, + OID.pkcs7signedData, + OID.pkcs7envelopedData, + OID.emailAddress, + OID.signingCertificateV2, + OID.contentType, + OID.messageDigest, + OID.signingTime, + OID.dsaWithSha1, + OID.certificateExtension, + OID.jurisdictionOfIncorporationSP, + OID.jurisdictionOfIncorporationC, + OID.authorityInfoAccess, + OID.qcStatements, + OID.cps, + OID.unotice, + OID.serverAuth, + OID.clientAuth, + OID.ocsp, + OID.caIssuers, + OID.dateOfBirth, + OID.desCBC, + OID.sha1, + OID.sha256, + OID.sha384, + OID.sha512, + OID.md5, + OID.VeriSignEVpolicy, + OID.extendedValidation, + OID.organizationValidated, + OID.subjectKeyIdentifier, + OID.keyUsage, + OID.subjectAltName, + OID.issuerAltName, + OID.basicConstraints, + OID.cRLDistributionPoints, + OID.certificatePolicies, + OID.authorityKeyIdentifier, + OID.extKeyUsage, + OID.subjectDirectoryAttributes, + OID.organizationName, + OID.organizationalUnitName, + OID.businessCategory, + OID.postalCode, + OID.commonName, + OID.surname, + OID.givenName, + OID.dnQualifier, + OID.serialNumber, + OID.countryName, + OID.localityName, + OID.stateOrProvinceName, + OID.streetAddress, + OID.desEDE3CBC, + OID.aes128CBC, + OID.aes192CBC, + OID.aes256CBC, + OID.nsCertType, + OID.nsComment, + OID.privateKeyUsagePeriod, + OID.cRLNumber, + OID.cRLReason, + OID.expirationDate, + OID.instructionCode, + OID.invalidityDate, + OID.deltaCRLIndicator, + OID.issuingDistributionPoint, + OID.certificateIssuer, + OID.nameConstraints, + OID.policyMappings, + OID.policyConstraints, + OID.freshestCRL, + OID.inhibitAnyPolicy, + OID.codeSigning, + OID.emailProtection, + OID.timeStamping, + ]; + + static OID fromValue(String value) { + return OID.values.firstWhere((element) => element.toValue() == value, orElse: () => null); + } + + String toValue() => _value; + + String name() => _oidMapName[this._value]; + + @override + String toString() => "($_value, ${name()})"; + + static const etsiQcsCompliance = const OID._internal("0.4.0.1862.1.1"); + static const etsiQcsRetentionPeriod = const OID._internal("0.4.0.1862.1.3"); + static const etsiQcsQcSSCD = const OID._internal("0.4.0.1862.1.4"); + static const dsa = const OID._internal("1.2.840.10040.4.1"); + static const ecPublicKey = const OID._internal("1.2.840.10045.2.1"); + static const prime256v1 = const OID._internal("1.2.840.10045.3.1.7"); + static const ecdsaWithSHA256 = const OID._internal("1.2.840.10045.4.3.2"); + static const ecdsaWithSHA512 = const OID._internal("1.2.840.10045.4.3.4"); + static const rsaEncryption = const OID._internal("1.2.840.113549.1.1.1"); + static const md2WithRSAEncryption = const OID._internal("1.2.840.113549.1.1.2"); + static const md4WithRSAEncryption = const OID._internal("1.2.840.113549.1.1.3"); + static const md5WithRSAEncryption = const OID._internal("1.2.840.113549.1.1.4"); + static const sha1WithRSAEncryption = const OID._internal("1.2.840.113549.1.1.5"); + static const RSAES_OAEP = const OID._internal("1.2.840.113549.1.1.7"); + static const mgf1 = const OID._internal(".2.840.113549.1.1.8"); + static const pSpecified = const OID._internal(".2.840.113549.1.1.9"); + static const RSASSA_PSS = const OID._internal(".2.840.113549.1.1.10"); + static const sha256WithRSAEncryption = const OID._internal("1.2.840.113549.1.1.11"); + static const sha384WithRSAEncryption = const OID._internal("1.2.840.113549.1.1.12"); + static const sha512WithRSAEncryption = const OID._internal("1.2.840.113549.1.1.13"); + static const pkcs7data = const OID._internal("1.2.840.113549.1.7.1"); + static const pkcs7signedData = const OID._internal("1.2.840.113549.1.7.2"); + static const pkcs7envelopedData = const OID._internal("1.2.840.113549.1.7.3"); + static const emailAddress = const OID._internal("1.2.840.113549.1.9.1"); + static const signingCertificateV2 = const OID._internal("1.2.840.113549.1.9.16.2.47"); + static const contentType = const OID._internal("1.2.840.113549.1.9.3"); + static const messageDigest = const OID._internal("1.2.840.113549.1.9.4"); + static const signingTime = const OID._internal("1.2.840.113549.1.9.5"); + static const dsaWithSha1 = const OID._internal("1.2.840.10040.4.3"); + static const certificateExtension = const OID._internal("1.3.6.1.4.1.11129.2.4.2"); + static const jurisdictionOfIncorporationSP = const OID._internal("1.3.6.1.4.1.311.60.2.1.2"); + static const jurisdictionOfIncorporationC = const OID._internal("1.3.6.1.4.1.311.60.2.1.3"); + static const authorityInfoAccess = const OID._internal("1.3.6.1.5.5.7.1.1"); + static const qcStatements = const OID._internal("1.3.6.1.5.5.7.1.3"); + static const cps = const OID._internal("1.3.6.1.5.5.7.2.1"); + static const unotice = const OID._internal("1.3.6.1.5.5.7.2.2"); + static const serverAuth = const OID._internal("1.3.6.1.5.5.7.3.1"); + static const clientAuth = const OID._internal("1.3.6.1.5.5.7.3.2"); + static const ocsp = const OID._internal("1.3.6.1.5.5.7.48.1"); + static const caIssuers = const OID._internal("1.3.6.1.5.5.7.48.2"); + static const dateOfBirth = const OID._internal("1.3.6.1.5.5.7.9.1"); + static const desCBC = const OID._internal("1.3.14.3.2.7"); + static const sha1 = const OID._internal("1.3.14.3.2.26"); + static const sha256 = const OID._internal("2.16.840.1.101.3.4.2.1"); + static const sha384 = const OID._internal("2.16.840.1.101.3.4.2.2"); + static const sha512 = const OID._internal("2.16.840.1.101.3.4.2.3"); + static const md5 = const OID._internal("1.2.840.113549.2.5"); + static const VeriSignEVpolicy = const OID._internal("2.16.840.1.113733.1.7.23.6"); + static const extendedValidation = const OID._internal("2.23.140.1.1"); + static const organizationValidated = const OID._internal("2.23.140.1.2.2"); + static const subjectKeyIdentifier = const OID._internal("2.5.29.14"); + static const keyUsage = const OID._internal("2.5.29.15"); + static const subjectAltName = const OID._internal("2.5.29.17"); + static const issuerAltName = const OID._internal("2.5.29.18"); + static const basicConstraints = const OID._internal("2.5.29.19"); + static const cRLDistributionPoints = const OID._internal("2.5.29.31"); + static const certificatePolicies = const OID._internal("2.5.29.32"); + static const authorityKeyIdentifier = const OID._internal("2.5.29.35"); + static const extKeyUsage = const OID._internal("2.5.29.37"); + static const subjectDirectoryAttributes = const OID._internal("2.5.29.9"); + static const organizationName = const OID._internal("2.5.4.10"); + static const organizationalUnitName = const OID._internal("2.5.4.11"); + static const businessCategory = const OID._internal("2.5.4.15"); + static const postalCode = const OID._internal("2.5.4.17"); + static const commonName = const OID._internal("2.5.4.3"); + static const surname = const OID._internal("2.5.4.4"); + static const givenName = const OID._internal("2.5.4.42"); + static const dnQualifier = const OID._internal("2.5.4.46"); + static const serialNumber = const OID._internal("2.5.4.5"); + static const countryName = const OID._internal("2.5.4.6"); + static const localityName = const OID._internal("2.5.4.7"); + static const stateOrProvinceName = const OID._internal("2.5.4.8"); + static const streetAddress = const OID._internal("2.5.4.9"); + static const desEDE3CBC = const OID._internal("1.2.840.113549.3.7"); + static const aes128CBC = const OID._internal("2.16.840.1.101.3.4.1.2"); + static const aes192CBC = const OID._internal("2.16.840.1.101.3.4.1.22"); + static const aes256CBC = const OID._internal("2.16.840.1.101.3.4.1.42"); + static const nsCertType = const OID._internal("2.16.840.1.113730.1.1"); + static const nsComment = const OID._internal("2.16.840.1.113730.1.13"); + static const privateKeyUsagePeriod = const OID._internal("2.5.29.16"); + static const cRLNumber = const OID._internal("2.5.29.20"); + static const cRLReason = const OID._internal("2.5.29.21"); + static const expirationDate = const OID._internal("2.5.29.22"); + static const instructionCode = const OID._internal("2.5.29.23"); + static const invalidityDate = const OID._internal("2.5.29.24"); + static const deltaCRLIndicator = const OID._internal("2.5.29.27"); + static const issuingDistributionPoint = const OID._internal("2.5.29.28"); + static const certificateIssuer = const OID._internal("2.5.29.29"); + static const nameConstraints = const OID._internal("2.5.29.30"); + static const policyMappings = const OID._internal("2.5.29.33"); + static const policyConstraints = const OID._internal("2.5.29.36"); + static const freshestCRL = const OID._internal("2.5.29.46"); + static const inhibitAnyPolicy = const OID._internal("2.5.29.54"); + static const codeSigning = const OID._internal("1.3.6.1.5.5.7.3.3"); + static const emailProtection = const OID._internal("1.3.6.1.5.5.7.3.4"); + static const timeStamping = const OID._internal("1.3.6.1.5.5.7.3.8"); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; + + static const Map _oidMapName = { + "0.4.0.1862.1.1": "etsiQcsCompliance", + "0.4.0.1862.1.3": "etsiQcsRetentionPeriod", + "0.4.0.1862.1.4": "etsiQcsQcSSCD", + "1.2.840.10040.4.1": "dsa", + "1.2.840.10045.2.1": "ecPublicKey", + "1.2.840.10045.3.1.7": "prime256v1", + "1.2.840.10045.4.3.2": "ecdsaWithSHA256", + "1.2.840.10045.4.3.4": "ecdsaWithSHA512", + "1.2.840.113549.1.1.1": "rsaEncryption", + "1.2.840.113549.1.1.2": "md2WithRSAEncryption", + "1.2.840.113549.1.1.3": "md4WithRSAEncryption", + "1.2.840.113549.1.1.4": "md5WithRSAEncryption", + "1.2.840.113549.1.1.5": "sha1WithRSAEncryption", + "1.2.840.113549.1.1.7": "RSAES-OAEP", + ".2.840.113549.1.1.8": "mgf1", + ".2.840.113549.1.1.9": "pSpecified", + ".2.840.113549.1.1.10": "RSASSA-PSS", + "1.2.840.113549.1.1.11": "sha256WithRSAEncryption", + "1.2.840.113549.1.1.12": "sha384WithRSAEncryption", + "1.2.840.113549.1.1.13": "sha512WithRSAEncryption", + "1.2.840.113549.1.7.1": "data", + "1.2.840.113549.1.7.2": "signedData", + "1.2.840.113549.1.9.1": "emailAddress", + "1.2.840.113549.1.9.16.2.47": "signingCertificateV2", + "1.2.840.113549.1.9.3": "contentType", + "1.2.840.113549.1.9.4": "messageDigest", + "1.2.840.113549.1.9.5": "signingTime", + "1.2.840.10040.4.3": 'dsa-with-sha1', + "1.3.6.1.4.1.11129.2.4.2": "certificateExtension", + "1.3.6.1.4.1.311.60.2.1.2": "jurisdictionOfIncorporationSP", + "1.3.6.1.4.1.311.60.2.1.3": "jurisdictionOfIncorporationC", + "1.3.6.1.5.5.7.1.1": "authorityInfoAccess", + "1.3.6.1.5.5.7.1.3": "qcStatements", + "1.3.6.1.5.5.7.2.1": "cps", + "1.3.6.1.5.5.7.2.2": "unotice", + "1.3.6.1.5.5.7.3.1": "serverAuth", + "1.3.6.1.5.5.7.3.2": "clientAuth", + "1.3.6.1.5.5.7.48.1": "ocsp", + "1.3.6.1.5.5.7.48.2": "caIssuers", + "1.3.6.1.5.5.7.9.1": "dateOfBirth", + "1.3.14.3.2.7": "desCBC", + "1.3.14.3.2.26": "sha1", + "2.16.840.1.101.3.4.2.1": "sha256", + "2.16.840.1.101.3.4.2.2": "sha384", + "2.16.840.1.101.3.4.2.3": "sha512", + "1.2.840.113549.2.5": "md5", + "2.16.840.1.113733.1.7.23.6": "VeriSign EV policy", + "2.23.140.1.1": "extendedValidation", + "2.23.140.1.2.2": "extendedValidation", + "2.5.29.14": "subjectKeyIdentifier", + "2.5.29.15": "keyUsage", + "2.5.29.17": "subjectAltName", + "2.5.29.18": "issuerAltName", + "2.5.29.19": "basicConstraints", + "2.5.29.31": "cRLDistributionPoints", + "2.5.29.32": "certificatePolicies", + "2.5.29.35": "authorityKeyIdentifier", + "2.5.29.37": "extKeyUsage", + "2.5.29.9": "subjectDirectoryAttributes", + "2.5.4.10": "organizationName", + "2.5.4.11": "organizationalUnitName", + "2.5.4.15": "businessCategory", + "2.5.4.17": "postalCode", + "2.5.4.3": "commonName", + "2.5.4.4": "surname", + "2.5.4.42": "givenName", + "2.5.4.46": "dnQualifier", + "2.5.4.5": "serialNumber", + "2.5.4.6": "countryName", + "2.5.4.7": "localityName", + "2.5.4.8": "stateOrProvinceName", + "2.5.4.9": "streetAddress", + "1.2.840.113549.3.7": "des-EDE3-CBC", + "2.16.840.1.101.3.4.1.2": "aes128-CBC", + "2.16.840.1.101.3.4.1.22": "aes192-CBC", + "2.16.840.1.101.3.4.1.42": "aes256-CBC", + "2.16.840.1.113730.1.1": "nsCertType", + "2.16.840.1.113730.1.13": "nsComment", + "2.5.29.16": "privateKeyUsagePeriod", + "2.5.29.20": "cRLNumber", + "2.5.29.21": "cRLReason", + "2.5.29.22": "expirationDate", + "2.5.29.23": "instructionCode", + "2.5.29.24": "invalidityDate", + "2.5.29.27": "deltaCRLIndicator", + "2.5.29.28": "issuingDistributionPoint", + "2.5.29.29": "certificateIssuer", + "2.5.29.30": "nameConstraints", + "2.5.29.33": "policyMappings", + "2.5.29.36": "policyConstraints", + "2.5.29.46": "freshestCRL", + "2.5.29.54": "inhibitAnyPolicy", + "1.3.6.1.5.5.7.3.3": "codeSigning", + "1.3.6.1.5.5.7.3.4": "emailProtection", + "1.3.6.1.5.5.7.3.8": "timeStamping", + }; +} diff --git a/lib/src/X509Certificate/x509_certificate.dart b/lib/src/X509Certificate/x509_certificate.dart new file mode 100644 index 00000000..f8e02a4d --- /dev/null +++ b/lib/src/X509Certificate/x509_certificate.dart @@ -0,0 +1,450 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/foundation.dart'; +import 'asn1_decoder.dart'; +import 'asn1_object.dart'; +import 'oid.dart'; +import 'x509_public_key.dart'; + +import 'x509_extension.dart'; +import 'asn1_distinguished_names.dart'; + +///Class that represents a X.509 certificate. +///This provides a standard way to access all the attributes of an X.509 certificate. +class X509Certificate { + List asn1; + ASN1Object block1; + + ///Returns the encoded form of this certificate. It is + ///assumed that each certificate type would have only a single + ///form of encoding; for example, X.509 certificates would + ///be encoded as ASN.1 DER. + Uint8List encoded; + + static const beginPemBlock = "-----BEGIN CERTIFICATE-----"; + static const endPemBlock = "-----END CERTIFICATE-----"; + + X509Certificate({ASN1Object asn1}) { + if (asn1 != null) { + var block1 = asn1.subAtIndex(0); + if (block1 == null) { + throw ASN1ParseError(); + } + } + } + + static X509Certificate fromData({@required Uint8List data}) { + var decoded = utf8.decode(data, allowMalformed: true); + if (decoded.contains(X509Certificate.beginPemBlock)) { + return X509Certificate.fromPemData(pem: data); + } else { + return X509Certificate.fromDerData(der: data); + } + } + + static X509Certificate fromDerData({@required Uint8List der}) { + var asn1 = ASN1DERDecoder.decode(data: der.toList(growable: true)); + if (asn1.length > 0) { + var block1 = asn1.first?.subAtIndex(0); + if (block1 != null) { + var certificate = X509Certificate(); + certificate.asn1 = asn1; + certificate.block1 = block1; + certificate.encoded = der; + return certificate; + } + } + throw ASN1ParseError(); + } + + static X509Certificate fromPemData({@required Uint8List pem}) { + var derData = X509Certificate.decodeToDER(pemData: pem); + if (derData == null) { + throw ASN1ParseError(); + } + return X509Certificate.fromDerData(der: derData); + } + + ///Read possible PEM encoding + static Uint8List decodeToDER({@required pemData}) { + var pem = String.fromCharCodes(pemData); + if (pem != null && pem.contains(X509Certificate.beginPemBlock)) { + var lines = pem.split("\n"); + var base64buffer = ""; + var certLine = false; + for (var line in lines) { + if (line == X509Certificate.endPemBlock) { + certLine = false; + } + if (certLine) { + base64buffer += line; + } + if (line == X509Certificate.beginPemBlock) { + certLine = true; + } + } + + Uint8List derDataDecoded; + try { + derDataDecoded = Uint8List.fromList(utf8.encode(base64buffer)); + } catch (e) {} + if (derDataDecoded != null) { + return derDataDecoded; + } + } + return null; + } + + String get description => + asn1.fold("", (value, element) => value + element.description + "\n"); + + ///Checks that the given date is within the certificate's validity period. + bool checkValidity({DateTime date}) { + if (date == null) { + date = DateTime.now(); + } + if (notBefore != null && notAfter != null) { + return date.isAfter(notBefore) && date.isBefore(notAfter); + } + return false; + } + + ///Gets the version (version number) value from the certificate. + int get version { + var v = firstLeafValue(block: block1) as List; + if (v != null) { + var index = toIntValue(v); + if (index != null) { + return index.toInt() + 1; + } + } + return null; + } + + ///Gets the serialNumber value from the certificate. + List get serialNumber => + block1.atIndex(X509BlockPosition.serialNumber)?.value as List; + + ///Returns the issuer (issuer distinguished name) value from the certificate as a String. + String get issuerDistinguishedName { + var issuerBlock = block1.atIndex(X509BlockPosition.issuer); + if (issuerBlock != null) { + return blockDistinguishedName(block: issuerBlock); + } + return null; + } + + List get issuerOIDs { + var result = []; + var issuerBlock = block1.atIndex(X509BlockPosition.issuer); + if (issuerBlock != null) { + for (var sub in (issuerBlock.sub ?? [])) { + var value = firstLeafValue(block: sub) as String; + if (value != null) { + result.add(value); + } + } + } + return result; + } + + String issuer({String oid, ASN1DistinguishedNames dn}) { + if (oid == null && dn != null) { + oid = dn.oid(); + } + if (oid != null) { + var issuerBlock = block1.atIndex(X509BlockPosition.issuer); + if (issuerBlock != null) { + var oidBlock = issuerBlock.findOid(oidValue: oid); + if (oidBlock != null) { + var sub = oidBlock.parent?.sub; + if (sub != null && sub.length > 0) { + return sub.last.value as String; + } else { + return null; + } + } + } + } + return null; + } + + ///Returns the subject (subject distinguished name) value from the certificate as a String. + String get subjectDistinguishedName { + var subjectBlock = block1.atIndex(X509BlockPosition.subject); + if (subjectBlock != null) { + return blockDistinguishedName(block: subjectBlock); + } + return null; + } + + List get subjectOIDs { + var result = []; + var subjectBlock = block1.atIndex(X509BlockPosition.subject); + if (subjectBlock != null) { + for (var sub in (subjectBlock.sub ?? [])) { + var value = firstLeafValue(block: sub) as String; + if (value != null) { + result.add(value); + } + } + } + return result; + } + + String subject({String oid, ASN1DistinguishedNames dn}) { + if (oid == null && dn != null) { + oid = dn.oid(); + } + if (oid != null) { + var subjectBlock = block1.atIndex(X509BlockPosition.subject); + if (subjectBlock != null) { + var oidBlock = subjectBlock.findOid(oidValue: oid); + if (oidBlock != null) { + var sub = oidBlock.parent?.sub; + if (sub != null && sub.length > 0) { + return sub.last.value as String; + } else { + return null; + } + } + } + } + return null; + } + + ///Gets the notBefore date from the validity period of the certificate. + DateTime get notBefore => + block1.atIndex(X509BlockPosition.dateValidity)?.subAtIndex(0)?.value + as DateTime; + + ///Gets the notAfter date from the validity period of the certificate. + DateTime get notAfter { + var value = block1 + .atIndex(X509BlockPosition.dateValidity) + ?.subAtIndex(1) + ?.value as DateTime; + return value; + } + + /// Gets the signature value (the raw signature bits) from the certificate. + List get signature => asn1[0].subAtIndex(2)?.value as List; + + /// Gets the signature algorithm name for the certificate signature algorithm. + String get sigAlgName => OID.fromValue(sigAlgOID ?? "")?.name(); + + /// Gets the signature algorithm OID string from the certificate. + String get sigAlgOID => block1.subAtIndex(2)?.subAtIndex(0)?.value as String; + + /// Gets the DER-encoded signature algorithm parameters from this certificate's signature algorithm. + List get sigAlgParams => null; + + ///Gets a boolean array representing bits of the KeyUsage extension, (OID = 2.5.29.15). + ///``` + ///KeyUsage ::= BIT STRING { + ///digitalSignature (0), + ///nonRepudiation (1), + ///keyEncipherment (2), + ///dataEncipherment (3), + ///keyAgreement (4), + ///keyCertSign (5), + ///cRLSign (6), + ///encipherOnly (7), + ///decipherOnly (8) + ///} + ///``` + List get keyUsage { + var result = []; + var oidBlock = block1.findOid(oid: OID.keyUsage); + if (oidBlock != null) { + var sub = oidBlock.parent?.sub; + if (sub != null && sub.length > 0) { + var data = sub.last.subAtIndex(0)?.value as List; + int bits = (data != null && data.length > 0) ? data.first ?? 0 : 0; + for (var index = 0; index < 7; index++) { + var value = bits & (1 << index).toUnsigned(8) != 0; + result.insert(0, value); + } + } + } + return result; + } + + ///Gets a list of Strings representing the OBJECT IDENTIFIERs of the ExtKeyUsageSyntax field of + ///the extended key usage extension, (OID = 2.5.29.37). + List get extendedKeyUsage => + extensionObject(oid: OID.extKeyUsage)?.valueAsStrings ?? []; + + ///Gets a collection of subject alternative names from the SubjectAltName extension, (OID = 2.5.29.17). + List get subjectAlternativeNames => + extensionObject(oid: OID.subjectAltName)?.valueAsStrings ?? []; + + ///Gets a collection of issuer alternative names from the IssuerAltName extension, (OID = 2.5.29.18). + List get issuerAlternativeNames => + extensionObject(oid: OID.issuerAltName)?.valueAsStrings ?? []; + + ///Gets the informations of the public key from this certificate. + X509PublicKey get publicKey { + var pkBlock = block1.atIndex(X509BlockPosition.publicKey); + if (pkBlock != null) { + return X509PublicKey(pkBlock: pkBlock); + } + return null; + } + + ///Get a list of critical extension OID codes + List get criticalExtensionOIDs { + var extensionBlocks = this.extensionBlocks; + if (extensionBlocks == null) { + return []; + } + return extensionBlocks + .map((block) => X509Extension(block: block)) + .where((extension) => extension.isCritical) + .map((extension) => extension.oid) + .toList(); + } + + ///Get a list of non critical extension OID codes + List get nonCriticalExtensionOIDs { + var extensionBlocks = this.extensionBlocks; + if (extensionBlocks == null) { + return []; + } + return extensionBlocks + .map((block) => X509Extension(block: block)) + .where((extension) => !extension.isCritical) + .map((extension) => extension.oid) + .toList(); + } + + ///Gets the certificate constraints path length from the + ///critical BasicConstraints extension, (OID = 2.5.29.19). + int get basicConstraints => extensionObject(oid: OID.basicConstraints)?.value as int ?? -1; + + List get extensionBlocks => + block1.atIndex(X509BlockPosition.extensions)?.subAtIndex(0)?.sub; + + ///Gets the extension information of the given OID code or enum. + X509Extension extensionObject({String oidValue, OID oid}) { + if (oidValue == null && oid != null) { + oidValue = oid.toValue(); + } + if (oidValue != null) { + var block = block1 + .atIndex(X509BlockPosition.extensions) + ?.findOid(oidValue: oidValue) + ?.parent; + if (block != null) { + return X509Extension(block: block); + } + } + return null; + } + + ///Format subject/issuer information in RFC1779 + String blockDistinguishedName({@required ASN1Object block}) { + var result = ""; + List oidNames = [ + 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 + ]; + for (var oidName in oidNames) { + var oidBlock = block.findOid(oidValue: oidName.oid()); + if (oidBlock != null) { + if (result.isNotEmpty) { + result += ", "; + } + result += oidName.representation(); + result += "="; + + var sub = oidBlock.parent?.sub; + if (sub != null && sub.length > 0) { + var value = sub.last.value as String; + if (value != null) { + var specialChar = ",+=\n<>#;\\"; + var quote = ""; + for (var i = 0; i < value.length; i++) { + var char = value[i]; + if (specialChar.contains(char)) { + quote = "\""; + } + } + result += quote; + result += value; + result += quote; + } + } + + + } + } + return result; + } + + @override + String toString() { + return description; + } + + Map toMap() { + return { + "basicConstraints": basicConstraints, + "subjectAlternativeNames": subjectAlternativeNames, + "issuerAlternativeNames": issuerAlternativeNames, + "extendedKeyUsage": extendedKeyUsage, + "issuerDistinguishedName": issuerDistinguishedName, + "keyUsage": keyUsage, + "notAfter": notAfter, + "notBefore": notBefore, + "serialNumber": serialNumber, + "sigAlgName": sigAlgName, + "sigAlgOID": sigAlgOID, + "sigAlgParams": sigAlgParams, + "signature": signature, + "subjectDistinguishedName": subjectDistinguishedName, + "version": version, + "criticalExtensionOIDs": criticalExtensionOIDs, + "nonCriticalExtensionOIDs": nonCriticalExtensionOIDs, + "encoded": encoded, + "publicKey": publicKey?.toMap(), + }; + } + + Map toJson() { + return toMap(); + } +} + +dynamic firstLeafValue({@required ASN1Object block}) { + var sub = block.sub; + if (sub != null && sub.length > 0) { + var subFirst = sub.first; + if (subFirst != null) { + return firstLeafValue(block: subFirst); + } + } + return block.value; +} + +enum X509BlockPosition { + version, + serialNumber, + signatureAlg, + issuer, + dateValidity, + subject, + publicKey, + extensions +} diff --git a/lib/src/X509Certificate/x509_extension.dart b/lib/src/X509Certificate/x509_extension.dart new file mode 100644 index 00000000..f1934429 --- /dev/null +++ b/lib/src/X509Certificate/x509_extension.dart @@ -0,0 +1,55 @@ +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 get valueAsStrings { + var result = []; + var sub = []; + 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 diff --git a/lib/src/X509Certificate/x509_public_key.dart b/lib/src/X509Certificate/x509_public_key.dart new file mode 100644 index 00000000..9a8c2f9f --- /dev/null +++ b/lib/src/X509Certificate/x509_public_key.dart @@ -0,0 +1,54 @@ +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 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 toMap() { + return { + "algOid": algOid, + "algName": algName, + "algParams": algParams, + "encoded": encoded, + }; + } + + Map toJson() { + return toMap(); + } +} \ No newline at end of file diff --git a/lib/src/in_app_webview_controller.dart b/lib/src/in_app_webview_controller.dart index 308cfb27..a0d5e76c 100644 --- a/lib/src/in_app_webview_controller.dart +++ b/lib/src/in_app_webview_controller.dart @@ -1,3 +1,4 @@ +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 sslCertificateMap = call.arguments["sslCertificate"]?.cast(); + + 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 getCertificate() async { + Map args = {}; + + Map sslCertificateMap = (await _channel.invokeMethod('getCertificate', args))?.cast(); + + 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 getCertificate() async { - Map args = {}; - var sslCertificateMap = (await _controller._channel.invokeMethod('getCertificate', args))?.cast(); - - 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. diff --git a/lib/src/types.dart b/lib/src/types.dart index cae00c4b..cc050fb1 100755 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'dart:convert'; +import 'X509Certificate/x509_certificate.dart'; import 'package:uuid/uuid.dart'; import 'package:flutter/foundation.dart'; @@ -924,15 +925,15 @@ class ServerTrustChallenge { ///**NOTE**: on iOS this value is always an empty string. String message; - ///The `X509Certificate` used to create the server SSL certificate. - Uint8List serverCertificate; + ///The SSL certificate used for this challenge. + SslCertificate sslCertificate; ServerTrustChallenge( {@required this.protectionSpace, this.androidError, this.iosError, this.message, - this.serverCertificate}) + this.sslCertificate}) : assert(protectionSpace != null); Map toMap() { @@ -941,7 +942,7 @@ class ServerTrustChallenge { "androidError": androidError?.toValue(), "iosError": iosError?.toValue(), "message": message, - "serverCertificate": serverCertificate + "sslCertificate": sslCertificate?.toMap() }; } @@ -3741,14 +3742,25 @@ class IOSUIScrollViewContentInsetAdjustmentBehavior { int get hashCode => _value.hashCode; } -class AndroidSslCertificate { - AndroidSslCertificateDName issuedBy; - AndroidSslCertificateDName issuedTo; - int validNotAfterDate; - int validNotBeforeDate; - AndroidX509Certificate x509Certificate; +///SSL certificate info (certificate details) class. +class SslCertificate { - AndroidSslCertificate({ + ///Name of the entity this certificate is issued by + SslCertificateDName issuedBy; + + ///Name of the entity this certificate is issued to + SslCertificateDName issuedTo; + + ///Not-after date from the validity period + DateTime validNotAfterDate; + + ///Not-before date from the validity period + DateTime validNotBeforeDate; + + ///The original source certificate, if available. + X509Certificate x509Certificate; + + SslCertificate({ this.issuedBy, this.issuedTo, this.validNotAfterDate, @@ -3756,13 +3768,21 @@ class AndroidSslCertificate { this.x509Certificate }); - static AndroidSslCertificate fromMap(Map map) { - return map != null ? AndroidSslCertificate( - issuedBy: AndroidSslCertificateDName.fromMap(map["issuedBy"]?.cast()), - issuedTo: AndroidSslCertificateDName.fromMap(map["issuedTo"]?.cast()), - validNotAfterDate: map["validNotAfterDate"], - validNotBeforeDate: map["validNotBeforeDate"], - x509Certificate: AndroidX509Certificate.fromMap(map["x509Certificate"]?.cast()), + static SslCertificate fromMap(Map map) { + X509Certificate x509Certificate; + try { + x509Certificate = X509Certificate.fromData(data: map["x509Certificate"]); + } catch (e, stacktrace) { + print(e); + print(stacktrace); + } + + return map != null ? SslCertificate( + issuedBy: SslCertificateDName.fromMap(map["issuedBy"]?.cast()), + issuedTo: SslCertificateDName.fromMap(map["issuedTo"]?.cast()), + validNotAfterDate: DateTime.fromMillisecondsSinceEpoch(map["validNotAfterDate"]), + validNotBeforeDate: DateTime.fromMillisecondsSinceEpoch(map["validNotBeforeDate"]), + x509Certificate: x509Certificate, ) : null; } @@ -3770,8 +3790,8 @@ class AndroidSslCertificate { return { "issuedBy": issuedBy?.toMap(), "issuedTo": issuedTo?.toMap(), - "validNotAfterDate": validNotAfterDate, - "validNotBeforeDate": validNotBeforeDate, + "validNotAfterDate": validNotAfterDate.millisecondsSinceEpoch, + "validNotBeforeDate": validNotBeforeDate.millisecondsSinceEpoch, "x509Certificate": x509Certificate?.toMap(), }; } @@ -3786,25 +3806,30 @@ class AndroidSslCertificate { } } -class AndroidSslCertificateDName { +///Distinguished name helper class. Used by [SslCertificate]. +class SslCertificateDName { + ///Common-name (CN) component of the name // ignore: non_constant_identifier_names String CName; + ///Distinguished name (normally includes CN, O, and OU names) // ignore: non_constant_identifier_names String DName; + ///Organization (O) component of the name // ignore: non_constant_identifier_names String OName; + ///Organizational Unit (OU) component of the name // ignore: non_constant_identifier_names String UName; // ignore: non_constant_identifier_names - AndroidSslCertificateDName({this.CName, this.DName, this.OName, this.UName}); + SslCertificateDName({this.CName = "", this.DName = "", this.OName = "", this.UName = ""}); - static AndroidSslCertificateDName fromMap(Map map) { - return map != null ? AndroidSslCertificateDName( - CName: map["CName"], - DName: map["DName"], - OName: map["OName"], - UName: map["UName"], + static SslCertificateDName fromMap(Map map) { + return map != null ? SslCertificateDName( + CName: map["CName"] ?? "", + DName: map["DName"] ?? "", + OName: map["OName"] ?? "", + UName: map["UName"] ?? "", ) : null; } @@ -3826,229 +3851,3 @@ class AndroidSslCertificateDName { return toMap().toString(); } } - -class AndroidX509Certificate { - int basicConstraints; - List extendedKeyUsage; - AndroidX509CertificatePrincipal issuerDN; - List issuerUniqueID; - AndroidX500Principal issuerX500Principal; - List keyUsage; - int notAfter; - int notBefore; - int serialNumber; - String sigAlgName; - String sigAlgOID; - Uint8List sigAlgParams; - Uint8List signature; - AndroidX509CertificatePrincipal subjectDN; - List subjectUniqueID; - AndroidX500Principal subjectX500Principal; - // ignore: non_constant_identifier_names - Uint8List TBSCertificate; - int version; - Set criticalExtensionOIDs; - Set nonCriticalExtensionOIDs; - Uint8List encoded; - AndroidX509CertificatePublicKey publicKey; - String type; - bool hasUnsupportedCriticalExtension; - bool valid; - - AndroidX509Certificate({ - this.basicConstraints, - this.extendedKeyUsage, - this.issuerDN, - this.issuerUniqueID, - this.issuerX500Principal, - this.keyUsage, - this.notAfter, - this.notBefore, - this.serialNumber, - this.sigAlgName, - this.sigAlgOID, - this.sigAlgParams, - this.signature, - this.subjectDN, - this.subjectUniqueID, - this.subjectX500Principal, - // ignore: non_constant_identifier_names - this.TBSCertificate, - this.version, - this.criticalExtensionOIDs, - this.nonCriticalExtensionOIDs, - this.encoded, - this.publicKey, - this.type, - this.hasUnsupportedCriticalExtension, - this.valid - }); - - static AndroidX509Certificate fromMap(Map map) { - return map != null ? AndroidX509Certificate( - basicConstraints: map["basicConstraints"], - extendedKeyUsage: map["extendedKeyUsage"], - issuerDN: AndroidX509CertificatePrincipal.fromMap(map["issuerDN"]), - issuerUniqueID: map["issuerUniqueID"], - issuerX500Principal: AndroidX500Principal.fromMap(map["issuerX500Principal"]), - keyUsage: map["keyUsage"], - notAfter: map["notAfter"], - notBefore: map["notBefore"], - serialNumber: map["serialNumber"], - sigAlgName: map["sigAlgName"], - sigAlgOID: map["sigAlgOID"], - sigAlgParams: map["sigAlgParams"], - signature: map["signature"], - subjectDN: AndroidX509CertificatePrincipal.fromMap(map["subjectDN"]), - subjectUniqueID: map["subjectUniqueID"], - subjectX500Principal: AndroidX500Principal.fromMap(map["subjectX500Principal"]), - TBSCertificate: map["TBSCertificate"], - version: map["version"], - criticalExtensionOIDs: map["criticalExtensionOIDs"], - nonCriticalExtensionOIDs: map["nonCriticalExtensionOIDs"], - encoded: map["encoded"], - publicKey: AndroidX509CertificatePublicKey.fromMap(map["publicKey"]), - type: map["type"], - hasUnsupportedCriticalExtension: map["hasUnsupportedCriticalExtension"], - valid: map["valid"] - ) : null; - } - - Map toMap() { - return { - "basicConstraints": basicConstraints, - "extendedKeyUsage": extendedKeyUsage, - "issuerDN": issuerDN?.toMap(), - "issuerUniqueID": issuerUniqueID, - "issuerX500Principal": issuerX500Principal?.toMap(), - "keyUsage": keyUsage, - "notAfter": notAfter, - "notBefore": notBefore, - "serialNumber": serialNumber, - "sigAlgName": sigAlgName, - "sigAlgOID": sigAlgOID, - "sigAlgParams": sigAlgParams, - "signature": signature, - "subjectDN": subjectDN?.toMap(), - "subjectUniqueID": subjectUniqueID, - "subjectX500Principal": subjectX500Principal?.toMap(), - "TBSCertificate": TBSCertificate, - "version": version, - "criticalExtensionOIDs": criticalExtensionOIDs, - "nonCriticalExtensionOIDs": nonCriticalExtensionOIDs, - "encoded": encoded, - "publicKey": publicKey?.toMap(), - "type": type, - "hasUnsupportedCriticalExtension": hasUnsupportedCriticalExtension, - "valid": valid, - }; - } - - Map toJson() { - return this.toMap(); - } - - @override - String toString() { - return toMap().toString(); - } -} - -class AndroidX509CertificatePrincipal { - String name; - - AndroidX509CertificatePrincipal({ - this.name - }); - - static AndroidX509CertificatePrincipal fromMap(Map map) { - return map != null ? AndroidX509CertificatePrincipal( - name: map["name"], - ) : null; - } - - Map toMap() { - return { - "name": name, - }; - } - - Map toJson() { - return this.toMap(); - } - - @override - String toString() { - return toMap().toString(); - } -} - -class AndroidX500Principal { - String name; - Uint8List encoded; - - AndroidX500Principal({ - this.name, - this.encoded - }); - - static AndroidX500Principal fromMap(Map map) { - return map != null ? AndroidX500Principal( - name: map["name"], - encoded: map["encoded"], - ) : null; - } - - Map toMap() { - return { - "name": name, - "encoded": encoded, - }; - } - - Map toJson() { - return this.toMap(); - } - - @override - String toString() { - return toMap().toString(); - } -} - -class AndroidX509CertificatePublicKey { - String algorithm; - Uint8List encoded; - String format; - - AndroidX509CertificatePublicKey({ - this.algorithm, - this.encoded, - this.format - }); - - static AndroidX509CertificatePublicKey fromMap(Map map) { - return map != null ? AndroidX509CertificatePublicKey( - algorithm: map["algorithm"], - encoded: map["encoded"], - format: map["format"], - ) : null; - } - - Map toMap() { - return { - "algorithm": algorithm, - "encoded": encoded, - "format": format, - }; - } - - Map toJson() { - return this.toMap(); - } - - @override - String toString() { - return toMap().toString(); - } -} \ No newline at end of file