diff --git a/lib/network/components/js/md5.dart b/lib/network/components/js/md5.dart index 85c06fd..b84f3b0 100644 --- a/lib/network/components/js/md5.dart +++ b/lib/network/components/js/md5.dart @@ -18,7 +18,7 @@ import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:flutter_js/flutter_js.dart'; -import 'package:proxypin/network/util/lists.dart'; +import 'package:proxypin/network/util/lang.dart'; /// JsMd5 /// @author Hongen Wang diff --git a/lib/network/components/script_manager.dart b/lib/network/components/script_manager.dart index 7efa42a..ebc89d2 100644 --- a/lib/network/components/script_manager.dart +++ b/lib/network/components/script_manager.dart @@ -23,7 +23,7 @@ import 'package:proxypin/network/components/js/file.dart'; import 'package:proxypin/network/components/js/md5.dart'; import 'package:proxypin/network/http/http.dart'; import 'package:proxypin/network/http/http_headers.dart'; -import 'package:proxypin/network/util/lists.dart'; +import 'package:proxypin/network/util/lang.dart'; import 'package:proxypin/network/util/logger.dart'; import 'package:proxypin/network/util/random.dart'; import 'package:proxypin/ui/component/device.dart'; diff --git a/lib/network/util/cert/cert_data.dart b/lib/network/util/cert/cert_data.dart new file mode 100644 index 0000000..655805f --- /dev/null +++ b/lib/network/util/cert/cert_data.dart @@ -0,0 +1,267 @@ +import 'dart:typed_data'; + +import 'extension.dart'; +import 'key_usage.dart'; + +class X509CertificateData { + /// The subject data of the certificate + Map subject; + + /// The version of the certificate + int version; + + BigInt serialNumber; + + /// The signatureAlgorithm of the certificate + String signatureAlgorithm; + + /// The readable name of the signatureAlgorithm of the certificate + String? signatureAlgorithmReadableName; + + Map issuer; + + /// The validity of the certificate + @Deprecated('Use tbsCertificate.validity instead') + X509CertificateValidity validity; + + /// The sha1 thumbprint for the certificate + String? sha1Thumbprint; + + /// The sha256 thumbprint for the certificate + String? sha256Thumbprint; + + /// The md5 thumbprint for the certificate + String? md5Thumbprint; + + /// The public key data from the certificate + X509CertificatePublicKeyData publicKeyData; + + /// The subject alternative names + List? subjectAlternativNames; + + /// The plain certificate pem string, that was used to decode. + String? plain; + + /// The extended key usage extension + List? extKeyUsage; + + /// The certificate extensions + X509CertificateDataExtensions? extensions; + + /// The signature + String? signature; + + /// The tbsCertificateSeq as base64 string + String? tbsCertificateSeqAsString; + + X509CertificateData({ + required this.version, + required this.serialNumber, + required this.signatureAlgorithm, + required this.issuer, + required this.validity, + required this.subject, + // required this.tbsCertificate, + this.signatureAlgorithmReadableName, + this.sha1Thumbprint, + this.sha256Thumbprint, + this.md5Thumbprint, + required this.publicKeyData, + required this.subjectAlternativNames, + this.plain, + this.extKeyUsage, + this.extensions, + this.tbsCertificateSeqAsString, + required this.signature, + }); +} + +class SubjectPublicKeyInfo { + /// The algorithm of the public key + String? algorithm; + + /// The readable name of the algorithm + String? algorithmReadableName; + + /// The parameter of the public key + String? parameter; + + /// The readable name of the parameter + String? parameterReadableName; + + /// The key length of the public key + int? length; + + /// The sha1 thumbprint of the public key + String? sha1Thumbprint; + + /// The sha256 thumbprint of the public key + String? sha256Thumbprint; + + /// The bytes representing the public key as String + String? bytes; + + /// The exponent used on a RSA public key + int? exponent; + + SubjectPublicKeyInfo({ + this.algorithm, + this.length, + this.sha1Thumbprint, + this.sha256Thumbprint, + this.bytes, + this.algorithmReadableName, + this.parameter, + this.parameterReadableName, + this.exponent, + }); +} + +class X509CertificateValidity { + /// The start date + DateTime notBefore; + + /// The end date + DateTime notAfter; + + X509CertificateValidity({required this.notBefore, required this.notAfter}); +} + +// +/// Model that represents the extensions of a x509Certificate +/// +class X509CertificateDataExtensions { + /// The subject alternative names + List? subjectAlternativNames; + + /// The extended key usage extension + List? extKeyUsage; + + /// The key usage extension + List? keyUsage; + + /// The cA field of the basic constraints extension + bool? cA; + + /// The pathLenConstraint field of the basic constraints extension + int? pathLenConstraint; + + /// The base64 encoded VMC logo + VmcData? vmc; + + /// The distribution points for the crl files. Normally a url. + List? cRLDistributionPoints; + + X509CertificateDataExtensions({ + this.subjectAlternativNames, + this.extKeyUsage, + this.keyUsage, + this.cA, + this.pathLenConstraint, + this.vmc, + this.cRLDistributionPoints, + }); +} + +/// +/// Model that a public key from a X509Certificate +/// +class X509CertificatePublicKeyData { + /// The algorithm of the public key + String? algorithm; + + /// The readable name of the algorithm + String? algorithmReadableName; + + /// The parameter of the public key + String? parameter; + + /// The readable name of the parameter + String? parameterReadableName; + + /// The key length of the public key + int? length; + + /// The sha1 thumbprint of the public key + String? sha1Thumbprint; + + /// The sha256 thumbprint of the public key + String? sha256Thumbprint; + + /// The bytes representing the public key as String + String? bytes; + + Uint8List? plainSha1; + + /// The exponent used on a RSA public key + int? exponent; + + X509CertificatePublicKeyData({ + this.algorithm, + this.length, + this.sha1Thumbprint, + this.sha256Thumbprint, + this.bytes, + this.plainSha1, + this.algorithmReadableName, + this.parameter, + this.parameterReadableName, + this.exponent, + }); + + static Uint8List? plainSha1FromJson(List? json) { + if (json == null) { + return null; + } + return Uint8List.fromList(json); + } + + static List? plainSha1ToJson(Uint8List? object) { + if (object == null) { + return null; + } + return object.toList(); + } + + X509CertificatePublicKeyData.fromSubjectPublicKeyInfo( + SubjectPublicKeyInfo info) { + algorithm = info.algorithm; + length = info.length; + sha1Thumbprint = info.sha1Thumbprint; + sha256Thumbprint = info.sha256Thumbprint; + bytes = info.bytes; + algorithmReadableName = info.algorithmReadableName; + parameter = info.parameter; + parameterReadableName = info.parameterReadableName; + exponent = info.exponent; + } +} + +class VmcData { + /// The base64 encoded logo + String? base64Logo; + + /// The logo type + String? type; + + /// The hash + String? hash; + + /// The readable version of the algorithm of the hash + String? hashAlgorithmReadable; + + /// The algorithm of the hash + String? hashAlgorithm; + + VmcData({ + this.base64Logo, + this.hash, + this.hashAlgorithm, + this.hashAlgorithmReadable, + this.type, + }); + + String getFullSvgData() { + return 'data:$type;base64,$base64Logo'; + } +} diff --git a/lib/network/util/cert/extension.dart b/lib/network/util/cert/extension.dart index b756e7e..dd049a2 100644 --- a/lib/network/util/cert/extension.dart +++ b/lib/network/util/cert/extension.dart @@ -21,3 +21,13 @@ class Extension { Extension(this.extnId, this.critical, this.value); } + +enum ExtendedKeyUsage { + SERVER_AUTH, + CLIENT_AUTH, + CODE_SIGNING, + EMAIL_PROTECTION, + TIME_STAMPING, + OCSP_SIGNING, + BIMI +} \ No newline at end of file diff --git a/lib/network/util/cert/key_usage.dart b/lib/network/util/cert/key_usage.dart index b134f1a..63f8ddc 100644 --- a/lib/network/util/cert/key_usage.dart +++ b/lib/network/util/cert/key_usage.dart @@ -2,7 +2,36 @@ import 'dart:typed_data'; import 'package:pointycastle/pointycastle.dart'; -class KeyUsage { +enum KeyUsage { + /// 0 + DIGITAL_SIGNATURE, + + /// 1 (Also called contentCommitment now) + NON_REPUDIATION, + + /// 2 + KEY_ENCIPHERMENT, + + /// 3 + DATA_ENCIPHERMENT, + + /// 4 + KEY_AGREEMENT, + + /// 5 + KEY_CERT_SIGN, + + /// 6 + CRL_SIGN, + + /// 7 + ENCIPHER_ONLY, + + /// 8 + DECIPHER_ONLY +} + +class ExtensionKeyUsage { static const int digitalSignature = (1 << 7); static const int nonRepudiation = (1 << 6); static const int keyEncipherment = (1 << 5); @@ -16,7 +45,7 @@ class KeyUsage { final ASN1BitString bitString; final bool critical; - KeyUsage(int usage, {this.critical = true}) : bitString = ASN1BitString.fromBytes(keyUsageBytes(usage)); + ExtensionKeyUsage(int usage, {this.critical = true}) : bitString = ASN1BitString.fromBytes(keyUsageBytes(usage)); static Uint8List keyUsageBytes(int valueBytes) { var bytes = [valueBytes]; diff --git a/lib/network/util/cert/pkcs12.dart b/lib/network/util/cert/pkcs12.dart index df4d85d..a0c0a62 100644 --- a/lib/network/util/cert/pkcs12.dart +++ b/lib/network/util/cert/pkcs12.dart @@ -1,10 +1,12 @@ import 'dart:typed_data'; -import 'package:basic_utils/basic_utils.dart'; import 'package:pointycastle/asn1.dart'; import 'package:pointycastle/export.dart'; +import '../crypto.dart'; +import '../lang.dart'; import 'der.dart'; +import 'x509.dart'; class Pkcs12 { /// diff --git a/lib/network/util/cert/x509.dart b/lib/network/util/cert/x509.dart index 779cf56..fa042d4 100644 --- a/lib/network/util/cert/x509.dart +++ b/lib/network/util/cert/x509.dart @@ -1,23 +1,29 @@ // ignore_for_file: constant_identifier_names, depend_on_referenced_packages import 'dart:convert'; +import 'dart:io'; import 'dart:typed_data'; -import 'package:basic_utils/basic_utils.dart'; import 'package:crypto/crypto.dart'; -import 'package:proxypin/network/util/cert/extension.dart'; -import 'package:proxypin/network/util/cert/key_usage.dart' as x509; import 'package:pointycastle/asn1/unsupported_object_identifier_exception.dart'; import 'package:pointycastle/pointycastle.dart'; +import 'package:proxypin/network/util/cert/extension.dart'; +import '../crypto.dart'; +import '../lang.dart'; import 'basic_constraints.dart'; +import 'cert_data.dart'; +import 'key_usage.dart'; /// @author wanghongen /// 2023/7/26 -class X509Generate { +class X509Utils { static const String BEGIN_CERT = '-----BEGIN CERTIFICATE-----'; static const String END_CERT = '-----END CERTIFICATE-----'; + static const BEGIN_CRL = '-----BEGIN X509 CRL-----'; + static const END_CRL = '-----END X509 CRL-----'; + //所在国家 static const String COUNTRY_NAME = "2.5.4.6"; static const String SERIAL_NUMBER = "2.5.4.5"; @@ -28,7 +34,7 @@ class X509Generate { // Add Issuer var issuerSeq = ASN1Sequence(); for (var k in subject.keys) { - var s = X509Generate._identifier(k, subject[k]!); + var s = X509Utils._identifier(k, subject[k]!); issuerSeq.add(s); } var derEncoded = issuerSeq.encode(); @@ -42,6 +48,72 @@ class X509Generate { return hexString; } + /// + /// Encode the given [asn1Object] to PEM format and adding the [begin] and [end]. + /// + static String encodeASN1ObjectToPem(ASN1Object asn1Object, String begin, String end, {String newLine = '\n'}) { + var bytes = asn1Object.encode(); + var chunks = Strings.chunk(base64.encode(bytes), 64); + return '$begin$newLine${chunks.join(newLine)}$newLine$end'; + } + + /// + /// Converts the given DER encoded CRL to a PEM string with the corresponding + /// headers. The given [bytes] can be taken directly from a .crl file. + /// + static String crlDerToPem(Uint8List bytes) { + return formatKeyString(base64.encode(bytes), BEGIN_CRL, END_CRL); + } + + /// + /// Formats the given [key] by chunking the [key] and adding the [begin] and [end] to the [key]. + /// + /// The line length will be defined by the given [chunkSize]. The default value is 64. + /// + /// Each line will be delimited by the given [lineDelimiter]. The default value is '\n'.w + /// + static String formatKeyString(String key, String begin, String end, + {int chunkSize = 64, String lineDelimiter = '\n'}) { + var sb = StringBuffer(); + var chunks = Strings.chunk(key, chunkSize); + if (Strings.isNotEmpty(begin)) { + sb.write(begin + lineDelimiter); + } + for (var s in chunks) { + sb.write(s + lineDelimiter); + } + if (Strings.isNotEmpty(end)) { + sb.write(end); + return sb.toString(); + } else { + var tmp = sb.toString(); + return tmp.substring(0, tmp.lastIndexOf(lineDelimiter)); + } + } + + /// + /// Parses the given PEM to a [X509CertificateData] object. + /// + /// Throws an [ASN1Exception] if the pem could not be read by the [ASN1Parser]. + /// + static X509CertificateData x509CertificateFromPem(String pem) { + var bytes = CryptoUtils.getBytesFromPEMString(pem); + var asn1Parser = ASN1Parser(bytes); + var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence; + + var x509 = _x509FromAsn1Sequence(topLevelSeq); + + var sha1String = CryptoUtils.getHash(bytes, algorithmName: 'SHA-1'); + var md5String = CryptoUtils.getHash(bytes, algorithmName: 'MD5'); + var sha256String = CryptoUtils.getHash(bytes, algorithmName: 'SHA-256'); + + x509.plain = pem; + x509.sha1Thumbprint = sha1String; + x509.md5Thumbprint = md5String; + x509.sha256Thumbprint = sha256String; + return x509; + } + /// /// Generates a self signed certificate /// @@ -62,7 +134,7 @@ class X509Generate { String serialNumber = '1', Map? issuer, Map? subject, - x509.KeyUsage? keyUsage, + ExtensionKeyUsage? keyUsage, List? extKeyUsage, BasicConstraints? basicConstraints, }) { @@ -82,7 +154,7 @@ class X509Generate { blockProtocol.add(ASN1Null()); data.add(blockProtocol); - issuer ??= Map.from(caRoot.tbsCertificate!.subject); + issuer ??= Map.from(caRoot.subject); // Add Issuer var issuerSeq = ASN1Sequence(); @@ -100,7 +172,7 @@ class X509Generate { // Add Subject var subjectSeq = ASN1Sequence(); - subject ??= Map.from(caRoot.tbsCertificate!.subject); + subject ??= Map.from(caRoot.subject); for (var k in subject.keys) { var s = _identifier(k, subject[k]!); @@ -114,7 +186,7 @@ class X509Generate { // Add Extensions - if (IterableUtils.isNotNullOrEmpty(sans) || keyUsage != null || IterableUtils.isNotNullOrEmpty(extKeyUsage)) { + if (Lists.isNotEmpty(sans) || keyUsage != null || Lists.isNotEmpty(extKeyUsage)) { var extensionTopSequence = ASN1Sequence(); // Add basic constraints 2.5.29.19 @@ -140,9 +212,9 @@ class X509Generate { } //2.5.29.17 - if (IterableUtils.isNotNullOrEmpty(sans)) { + if (sans != null && sans.isNotEmpty) { var sanList = ASN1Sequence(); - for (var s in sans!) { + for (var s in sans) { sanList.add(ASN1PrintableString(stringValue: s, tag: 0x82)); } var octetString = ASN1OctetString(octets: sanList.encode()); @@ -171,12 +243,151 @@ class X509Generate { var encode = _rsaSign(data.encode(), privateKey, _getDigestFromOi(caRoot.signatureAlgorithm)); outer.add(ASN1BitString(stringValues: encode)); - var chunks = StringUtils.chunk(base64Encode(outer.encode()), 64); + var chunks = Strings.chunk(base64Encode(outer.encode()), 64); return '$BEGIN_CERT\n${chunks.join('\r\n')}\n$END_CERT'; } - static ASN1Sequence? keyUsageSequence(x509.KeyUsage keyUsages) { + static X509CertificateData _x509FromAsn1Sequence(ASN1Sequence topLevelSeq) { + var tbsCertificateSeq = topLevelSeq.elements!.elementAt(0) as ASN1Sequence; + var signatureAlgorithmSeq = topLevelSeq.elements!.elementAt(1) as ASN1Sequence; + var signateureSeq = topLevelSeq.elements!.elementAt(2) as ASN1BitString; + + // tbsCertificate + var element = 0; + // Version + var version = 1; + if (tbsCertificateSeq.elements!.elementAt(0) is ASN1Integer) { + // The version ASN1Object ist missing use version 1 + version = 1; + element = -1; + } else { + // Version 1 (int = 0), version 2 (int = 1) or version 3 (int = 2) + var versionObject = tbsCertificateSeq.elements!.elementAt(element + 0); + version = versionObject.valueBytes!.elementAt(2); + version++; + } + + // Serial Number + var serialInteger = tbsCertificateSeq.elements!.elementAt(element + 1) as ASN1Integer; + var serialNumber = serialInteger.integer; + + // Signature + // var signatureSequence = tbsCertificateSeq.elements!.elementAt(element + 2) as ASN1Sequence; + // var o = signatureSequence.elements!.elementAt(0) as ASN1ObjectIdentifier; + // var signatureAlgorithm = o.objectIdentifierAsString!; + // var signatureAlgorithmReadable = o.readableName!; + + // Issuer + var issuerSequence = tbsCertificateSeq.elements!.elementAt(element + 3) as ASN1Sequence; + var issuer = _getDnFromSeq(issuerSequence); + + // Validity + var validitySequence = tbsCertificateSeq.elements!.elementAt(element + 4) as ASN1Sequence; + var validity = _getValidityFromSeq(validitySequence); + + // Subject + var subjectSequence = tbsCertificateSeq.elements!.elementAt(element + 5) as ASN1Sequence; + var subject = _getDnFromSeq(subjectSequence); + + // Subject Public Key Info + var pubKeySequence = tbsCertificateSeq.elements!.elementAt(element + 6) as ASN1Sequence; + var subjectPublicKeyInfo = _getSubjectPublicKeyInfoFromSeq(pubKeySequence); + + X509CertificateDataExtensions? extensions; + if (version > 1 && tbsCertificateSeq.elements!.length > element + 7) { + var extensionObject = tbsCertificateSeq.elements!.elementAt(element + 7); + var extParser = ASN1Parser(extensionObject.valueBytes); + var extSequence = extParser.nextObject() as ASN1Sequence; + extensions = _getExtensionsFromSeq(extSequence); + } + + // signatureAlgorithm + var pubKeyOid = signatureAlgorithmSeq.elements!.elementAt(0) as ASN1ObjectIdentifier; + + // signatureValue + var sigAsString = _bytesAsString(signateureSeq.valueBytes!); + + return X509CertificateData( + version: version, + serialNumber: serialNumber!, + signatureAlgorithm: pubKeyOid.objectIdentifierAsString!, + signatureAlgorithmReadableName: pubKeyOid.readableName, + signature: sigAsString, + issuer: issuer, + validity: validity, + subject: subject, + publicKeyData: X509CertificatePublicKeyData.fromSubjectPublicKeyInfo(subjectPublicKeyInfo), + subjectAlternativNames: extensions?.subjectAlternativNames, + extKeyUsage: extensions?.extKeyUsage, + extensions: extensions, + // tbsCertificate: tbsCertificate, + tbsCertificateSeqAsString: base64.encode( + tbsCertificateSeq.encode(), + ), + ); + } + + static X509CertificateDataExtensions _getExtensionsFromSeq(ASN1Sequence extSequence) { + List? sans; + List? keyUsage; + List? extKeyUsage; + List basicConstraints; + var extensions = X509CertificateDataExtensions(); + for (var subseq in extSequence.elements!) { + var seq = subseq as ASN1Sequence; + var oi = seq.elements!.elementAt(0) as ASN1ObjectIdentifier; + if (oi.objectIdentifierAsString == '2.5.29.17') { + if (seq.elements!.length == 3) { + sans = _fetchSansFromExtension(seq.elements!.elementAt(2)); + } else { + sans = _fetchSansFromExtension(seq.elements!.elementAt(1)); + } + extensions.subjectAlternativNames = sans; + } + + var keyUsageSequence = ASN1Sequence(); + keyUsageSequence.add(ASN1ObjectIdentifier.fromIdentifierString('2.5.29.15')); + + if (oi.objectIdentifierAsString == '2.5.29.15') { + if (seq.elements!.length == 3) { + keyUsage = _fetchKeyUsageFromExtension(seq.elements!.elementAt(2)); + } else { + keyUsage = _fetchKeyUsageFromExtension(seq.elements!.elementAt(1)); + } + extensions.keyUsage = keyUsage; + } + if (oi.objectIdentifierAsString == '2.5.29.37') { + if (seq.elements!.length == 3) { + extKeyUsage = _fetchExtendedKeyUsageFromExtension(seq.elements!.elementAt(2)); + } else { + extKeyUsage = _fetchExtendedKeyUsageFromExtension(seq.elements!.elementAt(1)); + } + extensions.extKeyUsage = extKeyUsage; + } + if (oi.objectIdentifierAsString == '2.5.29.19') { + if (seq.elements!.length == 3) { + basicConstraints = _fetchBasicConstraintsFromExtension(seq.elements!.elementAt(2)); + } else { + basicConstraints = [null, null]; + } + + extensions.cA = basicConstraints[0]; + extensions.pathLenConstraint = basicConstraints[1]; + } + if (oi.objectIdentifierAsString == '1.3.6.1.5.5.7.1.12') { + var vmcData = _fetchVmcLogo(seq.elements!.elementAt(1)); + extensions.vmc = vmcData; + } + if (oi.objectIdentifierAsString == '2.5.29.31') { + var cRLDistributionPoints = _fetchCrlDistributionPoints(seq.elements!.elementAt(1)); + extensions.cRLDistributionPoints = cRLDistributionPoints; + } + } + return extensions; + } + + static ASN1Sequence? keyUsageSequence(ExtensionKeyUsage keyUsages) { var octetString = ASN1OctetString(octets: keyUsages.bitString.encode()); var keyUsageSequence = ASN1Sequence(); @@ -190,11 +401,11 @@ class X509Generate { } static ASN1Sequence? extendedKeyUsageEncodings(List? extKeyUsage) { - if (IterableUtils.isNullOrEmpty(extKeyUsage)) { + if (extKeyUsage == null || extKeyUsage.isEmpty) { return null; } var extKeyUsageList = ASN1Sequence(); - for (var s in extKeyUsage!) { + for (var s in extKeyUsage) { var oi = []; switch (s) { case ExtendedKeyUsage.SERVER_AUTH: @@ -231,6 +442,354 @@ class X509Generate { return extKeyUsageSequence; } + static SubjectPublicKeyInfo _getSubjectPublicKeyInfoFromSeq(ASN1Sequence pubKeySequence) { + var algSeq = pubKeySequence.elements!.elementAt(0) as ASN1Sequence; + var algOi = algSeq.elements!.elementAt(0) as ASN1ObjectIdentifier; + var asn1AlgParameters = algSeq.elements!.elementAt(1); + var algParameters = ''; + var algParametersReadable = ''; + if (asn1AlgParameters is ASN1ObjectIdentifier) { + algParameters = asn1AlgParameters.objectIdentifierAsString!; + algParametersReadable = asn1AlgParameters.readableName!; + } + + var pubBitString = pubKeySequence.elements!.elementAt(1) as ASN1BitString; + var asn1PubKeyParser = ASN1Parser(pubBitString.stringValues as Uint8List?); + ASN1Object? next; + try { + next = asn1PubKeyParser.nextObject(); + } catch (e) { + // continue + } + int pubKeyLength; + int? exponent; + var pubKeyAsBytes = pubKeySequence.encodedBytes; + if (next != null && next is ASN1Sequence) { + var s = next; + var key = s.elements!.elementAt(0) as ASN1Integer; + if (s.elements!.length == 2 && s.elements!.elementAt(1) is ASN1Integer) { + var asn1Exponent = s.elements!.elementAt(1) as ASN1Integer; + exponent = asn1Exponent.integer!.toInt(); + } + pubKeyLength = key.integer!.bitLength; + //pubKeyAsBytes = s.encodedBytes; + } else { + //pubKeyAsBytes = pubBitString.valueBytes; + var length = pubBitString.valueBytes!.elementAt(0) == 0 + ? (pubBitString.valueByteLength! - 1) + : pubBitString.valueByteLength; + pubKeyLength = length! * 8; + } + + var pubKeyThumbprint = CryptoUtils.getHash(pubKeySequence.encodedBytes!, algorithmName: 'SHA-1'); + var pubKeySha256Thumbprint = CryptoUtils.getHash(pubKeySequence.encodedBytes!, algorithmName: 'SHA-256'); + + return SubjectPublicKeyInfo( + algorithm: algOi.objectIdentifierAsString, + algorithmReadableName: algOi.readableName, + parameter: algParameters != '' ? algParameters : null, + parameterReadableName: algParametersReadable != '' ? algParametersReadable : null, + length: pubKeyLength, + bytes: _bytesAsString(pubKeyAsBytes!), + sha1Thumbprint: pubKeyThumbprint, + sha256Thumbprint: pubKeySha256Thumbprint, + exponent: exponent, + ); + } + + /// + /// Converts the bytes to a hex string + /// + static String _bytesAsString(Uint8List bytes) { + var b = StringBuffer(); + for (var v in bytes) { + var s = v.toRadixString(16); + if (s.length == 1) { + b.write('0$s'); + } else { + b.write(s); + } + } + return b.toString().toUpperCase(); + } + + /// + /// Fetches the base64 encoded VMC logo from the given [extData] + /// + static VmcData _fetchVmcLogo(ASN1Object extData) { + var octet = extData as ASN1OctetString; + var vmcParser = ASN1Parser(octet.valueBytes); + var topSeq = vmcParser.nextObject() as ASN1Sequence; + var obj1 = topSeq.elements!.elementAt(0); + var obj1Parser = ASN1Parser(obj1.valueBytes); + var obj2 = obj1Parser.nextObject(); + var obj2Parser = ASN1Parser(obj2.valueBytes); + var obj2Seq = obj2Parser.nextObject() as ASN1Sequence; + var nextSeq = obj2Seq.elements!.elementAt(0) as ASN1Sequence; + var finalSeq = nextSeq.elements!.elementAt(0) as ASN1Sequence; + + var data = VmcData(); + // Parse fileType + var ia5 = finalSeq.elements!.elementAt(0) as ASN1IA5String; + var fileType = ia5.stringValue!; + + // Parse hash + var hashSeq = finalSeq.elements!.elementAt(1) as ASN1Sequence; + var hasFinalSeq = hashSeq.elements!.elementAt(0) as ASN1Sequence; + var algSeq = hasFinalSeq.elements!.elementAt(0) as ASN1Sequence; + var oi = algSeq.elements!.elementAt(0) as ASN1ObjectIdentifier; + data.hashAlgorithm = oi.objectIdentifierAsString; + data.hashAlgorithmReadable = oi.readableName; + var octetString = hasFinalSeq.elements!.elementAt(1) as ASN1OctetString; + var hash = _bytesAsString(octetString.octets!); + data.hash = hash; + + // Parse base64 logo + var logoSeq = finalSeq.elements!.elementAt(2) as ASN1Sequence; + var ia5Logo = logoSeq.elements!.elementAt(0) as ASN1IA5String; + var base64LogoGzip = ia5Logo.stringValue; + var gzip = base64LogoGzip!.substring(base64LogoGzip.indexOf(',') + 1); + final decodedData = GZipCodec().decode(base64.decode(gzip)); + var base64Logo = base64.encode(decodedData); + + data.base64Logo = base64Logo; + data.type = fileType; + + return data; + } + + /// + /// Parses the given object identifier values to the internal enum + /// + static List _fetchKeyUsageFromExtension(ASN1Object extData) { + var keyUsage = []; + var octet = extData as ASN1OctetString; + var keyUsageParser = ASN1Parser(octet.valueBytes); + var keyUsageBitString = keyUsageParser.nextObject() as ASN1BitString; + if (keyUsageBitString.valueBytes?.isEmpty ?? true) { + return keyUsage; + } + + final Uint8List bytes = keyUsageBitString.valueBytes!; + final int lastBitsToSkip = bytes.first; + final int amountOfBytes = bytes.length - 1; //don't count the first byte + + for (int bitCounter = 0; bitCounter < amountOfBytes * 8 - lastBitsToSkip; ++bitCounter) { + final int byteIndex = bitCounter ~/ 8; // the current byte + final int bitIndex = bitCounter % 8; // the current bit + if (byteIndex >= amountOfBytes) { + return keyUsage; + } + + final int byte = bytes[1 + byteIndex]; //skip the first byte + final bool keyBit = _getBitOfByte(byte, bitIndex); + + if (keyBit == true && KeyUsage.values.length > bitCounter) { + keyUsage.add(KeyUsage.values[bitCounter]); + } + } + return keyUsage; + } + + /// From left to right. Returns [true] for 1 and [false] for [0]. + static bool _getBitOfByte(int byte, int bitIndex) { + final int shift = 7 - bitIndex; + final int shiftedByte = byte >> shift; + if (shiftedByte & 1 == 1) { + return true; + } else { + return false; + } + } + + /// + /// Parses the given object identifier values to the internal enum + /// + static List _fetchExtendedKeyUsageFromExtension(ASN1Object extData) { + var extKeyUsage = []; + var octet = extData as ASN1OctetString; + var keyUsageParser = ASN1Parser(octet.valueBytes); + var keyUsageSeq = keyUsageParser.nextObject() as ASN1Sequence; + for (var oi in keyUsageSeq.elements!) { + if (oi is ASN1ObjectIdentifier) { + var s = oi.objectIdentifierAsString; + switch (s) { + case '1.3.6.1.5.5.7.3.1': + extKeyUsage.add(ExtendedKeyUsage.SERVER_AUTH); + break; + case '1.3.6.1.5.5.7.3.2': + extKeyUsage.add(ExtendedKeyUsage.CLIENT_AUTH); + break; + case '1.3.6.1.5.5.7.3.3': + extKeyUsage.add(ExtendedKeyUsage.CODE_SIGNING); + break; + case '1.3.6.1.5.5.7.3.4': + extKeyUsage.add(ExtendedKeyUsage.EMAIL_PROTECTION); + break; + case '1.3.6.1.5.5.7.3.8': + extKeyUsage.add(ExtendedKeyUsage.TIME_STAMPING); + break; + case '1.3.6.1.5.5.7.3.9': + extKeyUsage.add(ExtendedKeyUsage.OCSP_SIGNING); + break; + case '1.3.6.1.5.5.7.3.31': + extKeyUsage.add(ExtendedKeyUsage.BIMI); + break; + default: + } + } + } + return extKeyUsage; + } + + /// + /// Parses the given ASN1Object to the two basic constraint + /// fields cA and pathLenConstraint. Returns a list of types [bool, int] if + /// cA is true and a valid pathLenConstraint is specified, else the + /// corresponding element will be null. + /// + static List _fetchBasicConstraintsFromExtension(ASN1Object extData) { + var basicConstraints = [null, null]; + var octet = extData as ASN1OctetString; + var constraintParser = ASN1Parser(octet.valueBytes); + var constraintSeq = constraintParser.nextObject() as ASN1Sequence; + for (var obj in constraintSeq.elements!) { + if (obj is ASN1Boolean) { + basicConstraints[0] = obj.boolValue; + } + if (obj is ASN1Integer) { + basicConstraints[1] = obj.integer!.toInt(); + } + } + return basicConstraints; + } + + /// + /// Fetches a list of subject alternative names from the given [extData] + /// + static List _fetchSansFromExtension(ASN1Object extData) { + var sans = []; + var octet = extData as ASN1OctetString; + var sanParser = ASN1Parser(octet.valueBytes); + var sanSeq = sanParser.nextObject() as ASN1Sequence; + for (var san in sanSeq.elements!) { + if (san.tag == 135) { + var sb = StringBuffer(); + if (san.valueByteLength == 16) { + //IPv6 + for (var i = 0; i < (san.valueByteLength ?? 0); i++) { + if (sb.isNotEmpty && i % 2 == 0) { + sb.write(':'); + } + sb.write(san.valueBytes![i].toRadixString(16).padLeft(2, '0')); + } + } else { + //IPv4 and others + for (var b in san.valueBytes!) { + if (sb.isNotEmpty) { + sb.write('.'); + } + sb.write(b); + } + } + sans.add(sb.toString()); + } else if (san.tag == 164) { + // WE HAVE CONSTRUCTED SAN + var constructedParser = ASN1Parser(san.valueBytes); + var seq = constructedParser.nextObject() as ASN1Sequence; + var sanValue = 'DirName:'; + for (var san in seq.elements!) { + var set = san as ASN1Set; + var seq = set.elements!.elementAt(0) as ASN1Sequence; + var oid = seq.elements!.elementAt(0) as ASN1ObjectIdentifier; + var object = seq.elements!.elementAt(1); + var value = ''; + sanValue = '$sanValue/'; + if (object is ASN1UTF8String) { + var objectAsUtf8 = object; + value = objectAsUtf8.utf8StringValue!; + } else if (object is ASN1PrintableString) { + var objectPrintable = object; + value = objectPrintable.stringValue!; + } + sanValue = '$sanValue${oid.readableName}=$value'; + } + sans.add(sanValue); + } else { + var s = String.fromCharCodes(san.valueBytes!); + sans.add(s); + } + } + return sans; + } + + static List _fetchCrlDistributionPoints(ASN1Object extData) { + var cRLDistributionPoints = []; + + var octet = extData as ASN1OctetString; + var parser = ASN1Parser(octet.valueBytes); + var topSeq = parser.nextObject() as ASN1Sequence; + for (var e in topSeq.elements!) { + var seq = e as ASN1Sequence; + var o1 = seq.elements!.elementAt(0); + var parser = ASN1Parser(o1.valueBytes); + var o2 = parser.nextObject(); + parser = ASN1Parser(o2.valueBytes); + var o3 = parser.nextObject(); + var point = String.fromCharCodes(o3.valueBytes!.toList()); + cRLDistributionPoints.add(point); + } + return cRLDistributionPoints; + } + + static X509CertificateValidity _getValidityFromSeq(ASN1Sequence validitySequence) { + DateTime? asn1FromDateTime; + DateTime? asn1ToDateTime; + if (validitySequence.elements!.elementAt(0) is ASN1UtcTime) { + var asn1From = validitySequence.elements!.elementAt(0) as ASN1UtcTime; + asn1FromDateTime = asn1From.time; + } else { + var asn1From = validitySequence.elements!.elementAt(0) as ASN1GeneralizedTime; + asn1FromDateTime = asn1From.dateTimeValue; + } + if (validitySequence.elements!.elementAt(1) is ASN1UtcTime) { + var asn1To = validitySequence.elements!.elementAt(1) as ASN1UtcTime; + asn1ToDateTime = asn1To.time; + } else { + var asn1To = validitySequence.elements!.elementAt(1) as ASN1GeneralizedTime; + asn1ToDateTime = asn1To.dateTimeValue; + } + + return X509CertificateValidity( + notBefore: asn1FromDateTime!, + notAfter: asn1ToDateTime!, + ); + } + + static Map _getDnFromSeq(ASN1Sequence issuerSequence) { + var dnData = {}; + for (var s in issuerSequence.elements as dynamic) { + for (var ele in s.elements!) { + var seq = ele as ASN1Sequence; + var o = seq.elements!.elementAt(0) as ASN1ObjectIdentifier; + var object = seq.elements!.elementAt(1); + String? value = ''; + if (object is ASN1UTF8String) { + var objectAsUtf8 = object; + value = objectAsUtf8.utf8StringValue; + } else if (object is ASN1PrintableString) { + var objectPrintable = object; + value = objectPrintable.stringValue; + } else if (object is ASN1TeletextString) { + var objectTeletext = object; + value = objectTeletext.stringValue; + } + dnData.putIfAbsent(o.objectIdentifierAsString!, () => value ?? ''); + } + } + return dnData; + } + static ASN1Set _identifier(String k, String value) { ASN1ObjectIdentifier oIdentifier; try { diff --git a/lib/network/util/crts.dart b/lib/network/util/crts.dart index 0373146..6cf7163 100644 --- a/lib/network/util/crts.dart +++ b/lib/network/util/crts.dart @@ -20,16 +20,20 @@ import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; -import 'package:basic_utils/basic_utils.dart'; -import 'package:proxypin/network/util/logger.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:pointycastle/api.dart'; +import 'package:pointycastle/asymmetric/api.dart'; import 'package:proxypin/network/util/cert/basic_constraints.dart'; import 'package:proxypin/network/util/cert/pkcs12.dart'; import 'package:proxypin/network/util/cert/x509.dart'; +import 'package:proxypin/network/util/logger.dart'; import 'package:proxypin/network/util/random.dart'; import 'package:proxypin/utils/lang.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:proxypin/network/util/cert/key_usage.dart' as x509; +import 'cert/cert_data.dart'; +import 'cert/extension.dart'; +import 'cert/key_usage.dart'; +import 'crypto.dart'; import 'file_read.dart'; Future main() async { @@ -37,8 +41,7 @@ Future main() async { CertificateManager.caCert.tbsCertificateSeqAsString; String cer = CertificateManager.get('www.jianshu.com')!; - var x509certificateFromPem = X509Utils.x509CertificateFromPem(cer); - print(x509certificateFromPem.plain!); + print(cer); } enum StartState { uninitialized, initializing, initialized } @@ -104,7 +107,7 @@ class CertificateManager { x509Subject['CN'] = host; - var csrPem = X509Generate.generateSelfSignedCertificate(caRoot, serverPubKey, caPriKey, 365, + var csrPem = X509Utils.generateSelfSignedCertificate(caRoot, serverPubKey, caPriKey, 365, sans: [host], serialNumber: Random().nextInt(1000000).toString(), subject: x509Subject); return csrPem; } @@ -115,8 +118,8 @@ class CertificateManager { await initCAConfig(); } - var subject = caCert.tbsCertificate!.subject; - return '${X509Generate.getSubjectHashName(subject)}.0'; + var subject = caCert.subject; + return '${X509Utils.getSubjectHashName(subject)}.0'; } //重新生成根证书 @@ -139,7 +142,7 @@ class CertificateManager { }; x509Subject['CN'] = 'ProxyPin CA (${DateTime.now().dateFormat()},${RandomUtil.randomString(6).toUpperCase()})'; - var csrPem = X509Generate.generateSelfSignedCertificate( + var csrPem = X509Utils.generateSelfSignedCertificate( _caCert, serverPubKey, serverPriKey, @@ -148,7 +151,7 @@ class CertificateManager { serialNumber: DateTime.now().millisecondsSinceEpoch.toString(), issuer: x509Subject, subject: x509Subject, - keyUsage: x509.KeyUsage(x509.KeyUsage.keyCertSign), + keyUsage: ExtensionKeyUsage(ExtensionKeyUsage.keyCertSign), extKeyUsage: [ExtendedKeyUsage.SERVER_AUTH], basicConstraints: BasicConstraints(isCA: true), ); diff --git a/lib/network/util/crypto.dart b/lib/network/util/crypto.dart new file mode 100644 index 0000000..1afd285 --- /dev/null +++ b/lib/network/util/crypto.dart @@ -0,0 +1,394 @@ +import 'dart:convert'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:pointycastle/api.dart'; +import 'package:pointycastle/asn1/asn1_object.dart'; +import 'package:pointycastle/asn1/asn1_parser.dart'; +import 'package:pointycastle/asn1/primitives/asn1_bit_string.dart'; +import 'package:pointycastle/asn1/primitives/asn1_integer.dart'; +import 'package:pointycastle/asn1/primitives/asn1_object_identifier.dart'; +import 'package:pointycastle/asn1/primitives/asn1_octet_string.dart'; +import 'package:pointycastle/asn1/primitives/asn1_sequence.dart'; +import 'package:pointycastle/asymmetric/api.dart'; +import 'package:pointycastle/key_generators/api.dart'; +import 'package:pointycastle/key_generators/rsa_key_generator.dart'; +import 'package:pointycastle/paddings/pkcs7.dart'; +import 'package:pointycastle/random/fortuna_random.dart'; + +import 'lang.dart'; + +class CryptoUtils { + /// ignore: constant_identifier_names + static const String BEGIN_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----'; + static const String END_PUBLIC_KEY = '-----END PUBLIC KEY-----'; + + // ignore: constant_identifier_names + static const BEGIN_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----'; + static const END_PRIVATE_KEY = '-----END PRIVATE KEY-----'; + + static const BEGIN_RSA_PRIVATE_KEY = '-----BEGIN RSA PRIVATE KEY-----'; + static const END_RSA_PRIVATE_KEY = '-----END RSA PRIVATE KEY-----'; + + static const BEGIN_EC_PRIVATE_KEY = '-----BEGIN EC PRIVATE KEY-----'; + static const String END_EC_PRIVATE_KEY = '-----END EC PRIVATE KEY-----'; + + /// + /// Get a hash for the given [bytes] using the given [algorithm] + /// + /// The default [algorithm] used is **SHA-256**. All supported algorihms are : + /// + /// * SHA-1 + /// * SHA-224 + /// * SHA-256 + /// * SHA-384 + /// * SHA-512 + /// * SHA-512/224 + /// * SHA-512/256 + /// * MD5 + /// + static String getHash(Uint8List bytes, {String algorithmName = 'SHA-256'}) { + var hash = getHashPlain(bytes, algorithmName: algorithmName); + + const hexDigits = '0123456789abcdef'; + var charCodes = Uint8List(hash.length * 2); + for (var i = 0, j = 0; i < hash.length; i++) { + var byte = hash[i]; + charCodes[j++] = hexDigits.codeUnitAt((byte >> 4) & 0xF); + charCodes[j++] = hexDigits.codeUnitAt(byte & 0xF); + } + + return String.fromCharCodes(charCodes).toUpperCase(); + } + + /// + /// Get a hash for the given [bytes] using the given [algorithm] + /// + /// The default [algorithm] used is **SHA-256**. All supported algorihms are : + /// + /// * SHA-1 + /// * SHA-224 + /// * SHA-256 + /// * SHA-384 + /// * SHA-512 + /// * SHA-512/224 + /// * SHA-512/256 + /// * MD5 + /// + static Uint8List getHashPlain(Uint8List bytes, {String algorithmName = 'SHA-256'}) { + Uint8List hash; + switch (algorithmName) { + case 'SHA-1': + hash = Digest('SHA-1').process(bytes); + break; + case 'SHA-224': + hash = Digest('SHA-224').process(bytes); + break; + case 'SHA-256': + hash = Digest('SHA-256').process(bytes); + break; + case 'SHA-384': + hash = Digest('SHA-384').process(bytes); + break; + case 'SHA-512': + hash = Digest('SHA-512').process(bytes); + break; + case 'SHA-512/224': + hash = Digest('SHA-512/224').process(bytes); + break; + case 'SHA-512/256': + hash = Digest('SHA-512/256').process(bytes); + break; + case 'MD5': + hash = Digest('MD5').process(bytes); + break; + default: + throw ArgumentError('Hash not supported'); + } + + return hash; + } + + /// + /// Returns the private key type of the given [pem] + /// + static String getPrivateKeyType(String pem) { + if (pem.startsWith(BEGIN_RSA_PRIVATE_KEY)) { + return 'RSA_PKCS1'; + } else if (pem.startsWith(BEGIN_PRIVATE_KEY)) { + return 'RSA'; + } else if (pem.startsWith(BEGIN_EC_PRIVATE_KEY)) { + return 'ECC'; + } + return 'RSA'; + } + + /// + /// Generates a RSA [AsymmetricKeyPair] with the given [keySize]. + /// The default value for the [keySize] is 2048 bits. + /// + /// The following keySize is supported: + /// * 1024 + /// * 2048 + /// * 3072 + /// * 4096 + /// * 8192 + /// + static AsymmetricKeyPair generateRSAKeyPair({int keySize = 2048}) { + var keyParams = RSAKeyGeneratorParameters(BigInt.parse('65537'), keySize, 12); + + var secureRandom = getSecureRandom(); + + var rngParams = ParametersWithRandom(keyParams, secureRandom); + var generator = RSAKeyGenerator(); + generator.init(rngParams); + + return generator.generateKeyPair(); + } + + /// + /// Decode a [RSAPublicKey] from the given [pem] String. + /// + static RSAPublicKey rsaPublicKeyFromPem(String pem) { + var bytes = CryptoUtils.getBytesFromPEMString(pem); + return rsaPublicKeyFromDERBytes(bytes); + } + + /// + /// Decode the given [bytes] into an [RSAPublicKey]. + /// + static RSAPublicKey rsaPublicKeyFromDERBytes(Uint8List bytes) { + var asn1Parser = ASN1Parser(bytes); + var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence; + ASN1Sequence publicKeySeq; + if (topLevelSeq.elements![1].runtimeType == ASN1BitString) { + var publicKeyBitString = topLevelSeq.elements![1] as ASN1BitString; + + var publicKeyAsn = ASN1Parser(publicKeyBitString.stringValues as Uint8List?); + publicKeySeq = publicKeyAsn.nextObject() as ASN1Sequence; + } else { + publicKeySeq = topLevelSeq; + } + var modulus = publicKeySeq.elements![0] as ASN1Integer; + var exponent = publicKeySeq.elements![1] as ASN1Integer; + + var rsaPublicKey = RSAPublicKey(modulus.integer!, exponent.integer!); + + return rsaPublicKey; + } + + /// + /// Decode a [RSAPrivateKey] from the given [pem] String. + /// + static RSAPrivateKey rsaPrivateKeyFromPem(String pem) { + var bytes = getBytesFromPEMString(pem); + return rsaPrivateKeyFromDERBytes(bytes); + } + + // + /// Helper function for decoding the base64 in [pem]. + /// + /// Throws an ArgumentError if the given [pem] is not sourounded by begin marker -----BEGIN and + /// endmarker -----END or the [pem] consists of less than two lines. + /// + /// The PEM header check can be skipped by setting the optional paramter [checkHeader] to false. + /// + static Uint8List getBytesFromPEMString(String pem, {bool checkHeader = true}) { + var lines = LineSplitter.split(pem).map((line) => line.trim()).where((line) => line.isNotEmpty).toList(); + String base64; + if (checkHeader) { + if (lines.length < 2 || !lines.first.startsWith('-----BEGIN') || !lines.last.startsWith('-----END')) { + throw ArgumentError('The given string does not have the correct ' + 'begin/end markers expected in a PEM file.'); + } + base64 = lines.sublist(1, lines.length - 1).join(''); + } else { + base64 = lines.join(''); + } + + return Uint8List.fromList(base64Decode(base64)); + } + + /// + /// Decode the given [bytes] into an [RSAPrivateKey]. + /// + static RSAPrivateKey rsaPrivateKeyFromDERBytes(Uint8List bytes) { + var asn1Parser = ASN1Parser(bytes); + var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence; + //ASN1Object version = topLevelSeq.elements[0]; + //ASN1Object algorithm = topLevelSeq.elements[1]; + var privateKey = topLevelSeq.elements![2]; + + asn1Parser = ASN1Parser(privateKey.valueBytes); + var pkSeq = asn1Parser.nextObject() as ASN1Sequence; + + var modulus = pkSeq.elements![1] as ASN1Integer; + //ASN1Integer publicExponent = pkSeq.elements[2] as ASN1Integer; + var privateExponent = pkSeq.elements![3] as ASN1Integer; + var p = pkSeq.elements![4] as ASN1Integer; + var q = pkSeq.elements![5] as ASN1Integer; + //ASN1Integer exp1 = pkSeq.elements[6] as ASN1Integer; + //ASN1Integer exp2 = pkSeq.elements[7] as ASN1Integer; + //ASN1Integer co = pkSeq.elements[8] as ASN1Integer; + + var rsaPrivateKey = RSAPrivateKey(modulus.integer!, privateExponent.integer!, p.integer, q.integer); + + return rsaPrivateKey; + } + + /// + /// Enode the given [publicKey] to PEM format using the PKCS#8 standard. + /// + static String encodeRSAPublicKeyToPem(RSAPublicKey publicKey) { + var algorithmSeq = ASN1Sequence(); + var paramsAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0])); + algorithmSeq.add(ASN1ObjectIdentifier.fromName('rsaEncryption')); + algorithmSeq.add(paramsAsn1Obj); + + var publicKeySeq = ASN1Sequence(); + publicKeySeq.add(ASN1Integer(publicKey.modulus)); + publicKeySeq.add(ASN1Integer(publicKey.exponent)); + var publicKeySeqBitString = ASN1BitString(stringValues: Uint8List.fromList(publicKeySeq.encode())); + + var topLevelSeq = ASN1Sequence(); + topLevelSeq.add(algorithmSeq); + topLevelSeq.add(publicKeySeqBitString); + var dataBase64 = base64.encode(topLevelSeq.encode()); + var chunks = Strings.chunk(dataBase64, 64); + + return '$BEGIN_PUBLIC_KEY\n${chunks.join('\n')}\n$END_PUBLIC_KEY'; + } + + /// + /// Enode the given [rsaPrivateKey] to PEM format using the PKCS#1 standard. + /// + /// The ASN1 structure is decripted at . + /// + /// ``` + /// RSAPrivateKey ::= SEQUENCE { + /// version Version, + /// modulus INTEGER, -- n + /// publicExponent INTEGER, -- e + /// privateExponent INTEGER, -- d + /// prime1 INTEGER, -- p + /// prime2 INTEGER, -- q + /// exponent1 INTEGER, -- d mod (p-1) + /// exponent2 INTEGER, -- d mod (q-1) + /// coefficient INTEGER, -- (inverse of q) mod p + /// otherPrimeInfos OtherPrimeInfos OPTIONAL + /// } + /// ``` + static String encodeRSAPrivateKeyToPemPkcs1(RSAPrivateKey rsaPrivateKey) { + var version = ASN1Integer(BigInt.from(0)); + var modulus = ASN1Integer(rsaPrivateKey.n); + var publicExponent = ASN1Integer(BigInt.parse('65537')); + var privateExponent = ASN1Integer(rsaPrivateKey.privateExponent); + + var p = ASN1Integer(rsaPrivateKey.p); + var q = ASN1Integer(rsaPrivateKey.q); + var dP = rsaPrivateKey.privateExponent! % (rsaPrivateKey.p! - BigInt.from(1)); + var exp1 = ASN1Integer(dP); + var dQ = rsaPrivateKey.privateExponent! % (rsaPrivateKey.q! - BigInt.from(1)); + var exp2 = ASN1Integer(dQ); + var iQ = rsaPrivateKey.q!.modInverse(rsaPrivateKey.p!); + var co = ASN1Integer(iQ); + + var topLevelSeq = ASN1Sequence(); + topLevelSeq.add(version); + topLevelSeq.add(modulus); + topLevelSeq.add(publicExponent); + topLevelSeq.add(privateExponent); + topLevelSeq.add(p); + topLevelSeq.add(q); + topLevelSeq.add(exp1); + topLevelSeq.add(exp2); + topLevelSeq.add(co); + var dataBase64 = base64.encode(topLevelSeq.encode()); + var chunks = Strings.chunk(dataBase64, 64); + return '$BEGIN_RSA_PRIVATE_KEY\n${chunks.join('\n')}\n$END_RSA_PRIVATE_KEY'; + } + + /// + /// Enode the given [rsaPrivateKey] to PEM format using the PKCS#8 standard. + /// + /// The ASN1 structure is decripted at . + /// ``` + /// PrivateKeyInfo ::= SEQUENCE { + /// version Version, + /// algorithm AlgorithmIdentifier, + /// PrivateKey BIT STRING + /// } + /// ``` + /// + static String encodeRSAPrivateKeyToPem(RSAPrivateKey rsaPrivateKey) { + var version = ASN1Integer(BigInt.from(0)); + + var algorithmSeq = ASN1Sequence(); + var algorithmAsn1Obj = + ASN1Object.fromBytes(Uint8List.fromList([0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x1])); + var paramsAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0])); + algorithmSeq.add(algorithmAsn1Obj); + algorithmSeq.add(paramsAsn1Obj); + + var privateKeySeq = ASN1Sequence(); + var modulus = ASN1Integer(rsaPrivateKey.n); + var publicExponent = ASN1Integer(BigInt.parse('65537')); + var privateExponent = ASN1Integer(rsaPrivateKey.privateExponent); + var p = ASN1Integer(rsaPrivateKey.p); + var q = ASN1Integer(rsaPrivateKey.q); + var dP = rsaPrivateKey.privateExponent! % (rsaPrivateKey.p! - BigInt.from(1)); + var exp1 = ASN1Integer(dP); + var dQ = rsaPrivateKey.privateExponent! % (rsaPrivateKey.q! - BigInt.from(1)); + var exp2 = ASN1Integer(dQ); + var iQ = rsaPrivateKey.q!.modInverse(rsaPrivateKey.p!); + var co = ASN1Integer(iQ); + + privateKeySeq.add(version); + privateKeySeq.add(modulus); + privateKeySeq.add(publicExponent); + privateKeySeq.add(privateExponent); + privateKeySeq.add(p); + privateKeySeq.add(q); + privateKeySeq.add(exp1); + privateKeySeq.add(exp2); + privateKeySeq.add(co); + var publicKeySeqOctetString = ASN1OctetString(octets: Uint8List.fromList(privateKeySeq.encode())); + + var topLevelSeq = ASN1Sequence(); + topLevelSeq.add(version); + topLevelSeq.add(algorithmSeq); + topLevelSeq.add(publicKeySeqOctetString); + var dataBase64 = base64.encode(topLevelSeq.encode()); + var chunks = Strings.chunk(dataBase64, 64); + return '$BEGIN_PRIVATE_KEY\n${chunks.join('\n')}\n$END_PRIVATE_KEY'; + } + + /// + /// Generates a secure [FortunaRandom] + /// + static SecureRandom getSecureRandom() { + var secureRandom = FortunaRandom(); + var random = Random.secure(); + var seeds = List.generate(32, (_) => random.nextInt(256)); + secureRandom.seed(KeyParameter(Uint8List.fromList(seeds))); + return secureRandom; + } + + /// + /// Revomes the PKCS7 / PKCS5 padding from the [padded] bytes + /// + static Uint8List removePKCS7Padding(Uint8List padded) => + padded.sublist(0, padded.length - PKCS7Padding().padCount(padded)); + + /// + /// Adds a PKCS7 / PKCS5 padding to the given [bytes] and [blockSizeBytes] + /// + static Uint8List addPKCS7Padding(Uint8List bytes, int blockSizeBytes) { + final padLength = blockSizeBytes - (bytes.length % blockSizeBytes); + + final padded = Uint8List(bytes.length + padLength)..setAll(0, bytes); + PKCS7Padding().addPadding(padded, bytes.length); + + return padded; + } +} diff --git a/lib/network/util/lang.dart b/lib/network/util/lang.dart new file mode 100644 index 0000000..dda9212 --- /dev/null +++ b/lib/network/util/lang.dart @@ -0,0 +1,70 @@ +import 'dart:typed_data'; + +dynamic getFirstElement(List? list) { + return list?.firstOrNull; +} + +///获取list元素类型 +/// @author wanghongen +class Lists { + static bool isNotEmpty(List? list) { + return list != null && list.isNotEmpty; + } + + static Type getElementType(dynamic list) { + if (list == null || list.isEmpty || list is! List) { + return Null; + } + + var type = list.first.runtimeType; + + return type; + } + + ///转换指定类型 + static List convertList(List list) { + return list.map((e) => e as T).toList(); + } +} + +class Strings { + /// + /// Splits the given String [s] in chunks with the given [chunkSize]. + /// + static List chunk(String s, int chunkSize) { + var chunked = []; + for (var i = 0; i < s.length; i += chunkSize) { + var end = (i + chunkSize < s.length) ? i + chunkSize : s.length; + chunked.add(s.substring(i, end)); + } + return chunked; + } + + static bool isNotEmpty(String? s) { + return s != null && s.isNotEmpty; + } +} + +class HexUtils { + static String bytesToHex(List bytes) { + return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(); + } + + static Uint8List decode(String hex) { + var str = hex.replaceAll(" ", ""); + str = str.toLowerCase(); + if (str.length % 2 != 0) { + str = "0$str"; + } + var l = str.length ~/ 2; + var result = Uint8List(l); + for (var i = 0; i < l; ++i) { + var x = int.parse(str.substring(i * 2, (2 * (i + 1))), radix: 16); + if (x.isNaN) { + throw ArgumentError('Expected hex string'); + } + result[i] = x; + } + return result; + } +} diff --git a/lib/network/util/lists.dart b/lib/network/util/lists.dart deleted file mode 100644 index 99f0d67..0000000 --- a/lib/network/util/lists.dart +++ /dev/null @@ -1,22 +0,0 @@ -dynamic getFirstElement(List? list) { - return list?.firstOrNull; -} - -///获取list元素类型 -/// @author wanghongen -class Lists { - static Type getElementType(dynamic list) { - if (list == null || list.isEmpty || list is! List) { - return Null; - } - - var type = list.first.runtimeType; - - return type; - } - - ///转换指定类型 - static List convertList(List list) { - return list.map((e) => e as T).toList(); - } -} diff --git a/lib/ui/component/cert_hash.dart b/lib/ui/component/cert_hash.dart index b0cc3d5..687ce5f 100644 --- a/lib/ui/component/cert_hash.dart +++ b/lib/ui/component/cert_hash.dart @@ -17,7 +17,6 @@ import 'dart:io'; import 'dart:typed_data'; -import 'package:basic_utils/basic_utils.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -117,9 +116,8 @@ class _CertHashPageState extends State { if (content.isEmpty) return; try { var caCert = X509Utils.x509CertificateFromPem(content); - var subject = caCert.tbsCertificate?.subject; - if (subject == null) return; - var subjectHashName = X509Generate.getSubjectHashName(subject); + var subject = caCert.subject; + var subjectHashName = X509Utils.getSubjectHashName(subject); decodeData.text = '$subjectHashName.0'; } catch (e) { FlutterToastr.show(localizations.decodeFail, context, duration: 3, backgroundColor: Colors.red); diff --git a/lib/ui/component/multi_window.dart b/lib/ui/component/multi_window.dart index 14d0849..3b7712d 100644 --- a/lib/ui/component/multi_window.dart +++ b/lib/ui/component/multi_window.dart @@ -27,7 +27,7 @@ import 'package:proxypin/network/components/rewrite/request_rewrite_manager.dart import 'package:proxypin/network/components/rewrite/rewrite_rule.dart'; import 'package:proxypin/network/components/script_manager.dart'; import 'package:proxypin/network/http/http.dart'; -import 'package:proxypin/network/util/lists.dart'; +import 'package:proxypin/network/util/lang.dart'; import 'package:proxypin/network/util/logger.dart'; import 'package:proxypin/ui/component/cert_hash.dart'; import 'package:proxypin/ui/component/device.dart'; diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index c6f7be7..3f8c288 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include @@ -31,9 +31,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) proxy_manager_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ProxyManagerPlugin"); proxy_manager_plugin_register_with_registrar(proxy_manager_registrar); - g_autoptr(FlPluginRegistrar) screen_retriever_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); - screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); + g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); + screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 81c7476..4ed09c7 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -8,7 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_desktop_context_menu flutter_js proxy_manager - screen_retriever + screen_retriever_linux url_launcher_linux window_manager ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index f27b94a..17cf8cc 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,7 +12,7 @@ import flutter_desktop_context_menu import flutter_js import path_provider_foundation import proxy_manager -import screen_retriever +import screen_retriever_macos import share_plus import shared_preferences_foundation import url_launcher_macos @@ -26,7 +26,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterJsPlugin.register(with: registry.registrar(forPlugin: "FlutterJsPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ProxyManagerPlugin.register(with: registry.registrar(forPlugin: "ProxyManagerPlugin")) - ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) + ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.yaml b/pubspec.yaml index 9911c25..e52b289 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,12 +13,11 @@ dependencies: flutter_localizations: sdk: flutter intl: any - crypto: ^3.0.6 cupertino_icons: ^1.0.8 - basic_utils: ^5.7.0 + pointycastle: ^3.9.1 logger: ^2.4.0 date_format: ^2.0.9 - window_manager: ^0.4.2 + window_manager: ^0.4.3 desktop_multi_window: git: url: https://gitee.com/wanghongenpin/flutter-plugins.git diff --git a/test/cert_test.dart b/test/cert_test.dart index 94c39c0..00f214d 100644 --- a/test/cert_test.dart +++ b/test/cert_test.dart @@ -1,10 +1,13 @@ import 'dart:io'; import 'dart:math'; -import 'package:basic_utils/basic_utils.dart'; +import 'package:pointycastle/asymmetric/api.dart'; import 'package:proxypin/network/util/cert/basic_constraints.dart'; +import 'package:proxypin/network/util/cert/cert_data.dart'; +import 'package:proxypin/network/util/cert/extension.dart'; import 'package:proxypin/network/util/cert/key_usage.dart' as x509; import 'package:proxypin/network/util/cert/x509.dart'; +import 'package:proxypin/network/util/crypto.dart'; void main() async { var caPem = await File('assets/certs/ca.crt').readAsString(); @@ -77,8 +80,8 @@ String generate(X509CertificateData caRoot, RSAPublicKey serverPubKey, RSAPrivat }; x509Subject['CN'] = 'ProxyPin CA (wanghongen)'; - var csrPem = X509Generate.generateSelfSignedCertificate(caRoot, serverPubKey, caPriKey, 365, - keyUsage: x509.KeyUsage(x509.KeyUsage.keyCertSign | x509.KeyUsage.cRLSign), + var csrPem = X509Utils.generateSelfSignedCertificate(caRoot, serverPubKey, caPriKey, 365, + keyUsage: x509.ExtensionKeyUsage(x509.ExtensionKeyUsage.keyCertSign | x509.ExtensionKeyUsage.cRLSign), extKeyUsage: [ExtendedKeyUsage.SERVER_AUTH], basicConstraints: BasicConstraints(isCA: true), sans: [x509Subject['CN']!], diff --git a/test/x509_test.dart b/test/x509_test.dart index bbdfc41..1eb0cfe 100644 --- a/test/x509_test.dart +++ b/test/x509_test.dart @@ -1,10 +1,6 @@ import 'dart:io'; -import 'dart:typed_data'; -import 'package:basic_utils/basic_utils.dart'; -import 'package:proxypin/network/util/cert/basic_constraints.dart'; import 'package:proxypin/network/util/cert/x509.dart'; -import 'package:pointycastle/pointycastle.dart'; void main() async { // encoding(); @@ -20,8 +16,8 @@ void main() async { // var caPem = File('/Users/wanghongen/Downloads/proxyman.crt').readAsStringSync(); //生成 公钥和私钥 var caRoot = X509Utils.x509CertificateFromPem(caPem); - var subject = caRoot.tbsCertificate!.subject; - var d = X509Generate.getSubjectHashName(subject); + var subject = caRoot.subject; + var d = X509Utils.getSubjectHashName(subject); //16进制 print(d); @@ -34,83 +30,6 @@ void main() async { //获取证书 subject hash -void encoding() { - var basicConstraints = BasicConstraints(isCA: true); - - var extensionTopSequence = ASN1Sequence(); - - // Add basic constraints 2.5.29.19 - var basicConstraintsValue = ASN1Sequence(); - basicConstraintsValue.add(ASN1Boolean(basicConstraints.isCA)); - if (basicConstraints.pathLenConstraint != null) { - basicConstraintsValue.add(ASN1Integer(BigInt.from(basicConstraints.pathLenConstraint!))); - } - - var octetString = ASN1OctetString(octets: basicConstraintsValue.encode()); - var basicConstraintsSequence = ASN1Sequence(); - basicConstraintsSequence.add(ASN1ObjectIdentifier.fromIdentifierString('2.5.29.19')); - if (basicConstraints.critical) { - basicConstraintsSequence.add(ASN1Boolean(true)); - } - basicConstraintsSequence.add(octetString); - extensionTopSequence.add(basicConstraintsSequence); - - // Add key usage 2.5.29.15 - var keyUsage = [KeyUsage.KEY_CERT_SIGN, KeyUsage.CRL_SIGN]; - extensionTopSequence.add(keyUsageSequence(keyUsage)!); - - //2.5.29.17 - var sans = ['ProxyPin']; - if (IterableUtils.isNotNullOrEmpty(sans)) { - var sanList = ASN1Sequence(); - for (var s in sans) { - sanList.add(ASN1PrintableString(stringValue: s, tag: 0x82)); - } - var octetString = ASN1OctetString(octets: sanList.encode()); - - var sanSequence = ASN1Sequence(); - sanSequence.add(ASN1ObjectIdentifier.fromIdentifierString('2.5.29.17')); - sanSequence.add(octetString); - extensionTopSequence.add(sanSequence); - } - - // Add ext key usage 2.5.29.37 - var extKeyUsage = [ExtendedKeyUsage.SERVER_AUTH]; - var extKeyUsageSequence = extendedKeyUsageEncodings(extKeyUsage); - if (extKeyUsageSequence != null) { - extensionTopSequence.add(extKeyUsageSequence); - } - - var extObj = ASN1Object(tag: 0xA3); - extObj.valueBytes = extensionTopSequence.encode(); - - print(Int8List.view(extensionTopSequence.encode().buffer)); - // print(Int8List.view(extObj.encode().buffer)); -} - -void _basicConstraints() { - var basicConstraints = BasicConstraints(isCA: true); - var basicConstraintsValue = ASN1Sequence(); - - basicConstraintsValue.add(ASN1Boolean(basicConstraints.isCA)); - if (basicConstraints.pathLenConstraint != null) { - basicConstraintsValue.add(ASN1Integer(BigInt.from(basicConstraints.pathLenConstraint!))); - } - - print(Int8List.view(basicConstraintsValue.encode().buffer)); - - var octetString = ASN1OctetString(octets: basicConstraintsValue.encode()); - print(Int8List.view(octetString.encode().buffer)); - - var basicConstraintsSequence = ASN1Sequence(); - basicConstraintsSequence.add(ASN1ObjectIdentifier.fromIdentifierString('2.5.29.19')); - basicConstraintsSequence.add(ASN1Boolean(true)); - basicConstraintsSequence.add(octetString); - - print(Int8List.view(basicConstraintsSequence.encode().buffer)); - //[48, 15, 6, 3, 85, 29, 19, 1, 1, -1, 4, 5, 48, 3, 1, 1, -1] -} - // class KeyUsage { // static const int keyCertSign = (1 << 2); // static const int cRLSign = (1 << 1); @@ -168,89 +87,3 @@ void _basicConstraints() { // return 8 - bits; // } // } - -ASN1Sequence? keyUsageSequence(List? keyUsages) { - int valueBytes = 0; // the last bit of the 2 bytes is always set - for (KeyUsage usage in keyUsages!) { - switch (usage) { - case KeyUsage.KEY_CERT_SIGN: - valueBytes |= (1 << 2); - break; - case KeyUsage.CRL_SIGN: - valueBytes |= (1 << 1); - break; - // Add other cases as needed - default: - throw Error(); - } - } - - var bytes = [valueBytes]; - if (valueBytes > 0xFF) { - final int firstValueByte = (valueBytes & int.parse("ff00", radix: 16)) >> 8; - final int secondValueByte = (valueBytes & int.parse("00ff", radix: 16)); - bytes = [firstValueByte, secondValueByte]; - } - - final Uint8List keyUsageBytes = Uint8List.fromList([ - // BitString identifier - 3, - // Length - bytes.length + 1, - // Unused bytes at the end - 1, - ...bytes - ]); - - print(keyUsageBytes); - var octetString = ASN1OctetString(octets: ASN1BitString.fromBytes(keyUsageBytes).encode()); - - var keyUsageSequence = ASN1Sequence(); - keyUsageSequence.add(ASN1ObjectIdentifier.fromIdentifierString('2.5.29.15')); - keyUsageSequence.add(ASN1Boolean(true)); - keyUsageSequence.add(octetString); - - return keyUsageSequence; -} - -ASN1Sequence? extendedKeyUsageEncodings(List? extKeyUsage) { - if (IterableUtils.isNullOrEmpty(extKeyUsage)) { - return null; - } - var extKeyUsageList = ASN1Sequence(); - for (var s in extKeyUsage!) { - var oi = []; - switch (s) { - case ExtendedKeyUsage.SERVER_AUTH: - oi = [1, 3, 6, 1, 5, 5, 7, 3, 1]; - break; - case ExtendedKeyUsage.CLIENT_AUTH: - oi = [1, 3, 6, 1, 5, 5, 7, 3, 2]; - break; - case ExtendedKeyUsage.CODE_SIGNING: - oi = [1, 3, 6, 1, 5, 5, 7, 3, 3]; - break; - case ExtendedKeyUsage.EMAIL_PROTECTION: - oi = [1, 3, 6, 1, 5, 5, 7, 3, 4]; - break; - case ExtendedKeyUsage.TIME_STAMPING: - oi = [1, 3, 6, 1, 5, 5, 7, 3, 8]; - break; - case ExtendedKeyUsage.OCSP_SIGNING: - oi = [1, 3, 6, 1, 5, 5, 7, 3, 9]; - break; - case ExtendedKeyUsage.BIMI: - oi = [1, 3, 6, 1, 5, 5, 7, 3, 31]; - break; - } - - extKeyUsageList.add(ASN1ObjectIdentifier(oi)); - } - - var octetString = ASN1OctetString(octets: extKeyUsageList.encode()); - - var extKeyUsageSequence = ASN1Sequence(); - extKeyUsageSequence.add(ASN1ObjectIdentifier.fromIdentifierString('2.5.29.37')); - extKeyUsageSequence.add(octetString); - return extKeyUsageSequence; -} diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index cb0a6ff..2c58eab 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include @@ -31,8 +31,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); ProxyManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ProxyManagerPlugin")); - ScreenRetrieverPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); + ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index a4ea3e4..862f263 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -9,7 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_js permission_handler_windows proxy_manager - screen_retriever + screen_retriever_windows share_plus url_launcher_windows win32audio