mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-05-14 15:48:03 +08:00
Merge branch 'dependencies' into flutter-3.19.6
# Conflicts: # pubspec.yaml
This commit is contained in:
@@ -99,7 +99,7 @@ class ProxyServer {
|
||||
///检查是否监听端口 没有监听则启动
|
||||
Future<void> startForCheck() async {
|
||||
try {
|
||||
var socket = await Socket.connect('127.0.0.1', port, timeout: const Duration(milliseconds: 100));
|
||||
var socket = await Socket.connect('127.0.0.1', port, timeout: const Duration(milliseconds: 150));
|
||||
socket.close();
|
||||
} catch (e) {
|
||||
await start();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
267
lib/network/util/cert/cert_data.dart
Normal file
267
lib/network/util/cert/cert_data.dart
Normal file
@@ -0,0 +1,267 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'extension.dart';
|
||||
import 'key_usage.dart';
|
||||
|
||||
class X509CertificateData {
|
||||
/// The subject data of the certificate
|
||||
Map<String, String?> 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<String, String?> 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<String>? subjectAlternativNames;
|
||||
|
||||
/// The plain certificate pem string, that was used to decode.
|
||||
String? plain;
|
||||
|
||||
/// The extended key usage extension
|
||||
List<ExtendedKeyUsage>? 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<String>? subjectAlternativNames;
|
||||
|
||||
/// The extended key usage extension
|
||||
List<ExtendedKeyUsage>? extKeyUsage;
|
||||
|
||||
/// The key usage extension
|
||||
List<KeyUsage>? 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<String>? 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<int>? json) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
return Uint8List.fromList(json);
|
||||
}
|
||||
|
||||
static List<int>? 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';
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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];
|
||||
|
||||
@@ -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 {
|
||||
///
|
||||
|
||||
@@ -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<String, String>? issuer,
|
||||
Map<String, String>? subject,
|
||||
x509.KeyUsage? keyUsage,
|
||||
ExtensionKeyUsage? keyUsage,
|
||||
List<ExtendedKeyUsage>? 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<String>? sans;
|
||||
List<KeyUsage>? keyUsage;
|
||||
List<ExtendedKeyUsage>? extKeyUsage;
|
||||
List<dynamic> 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<ExtendedKeyUsage>? 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 = <int>[];
|
||||
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<KeyUsage> _fetchKeyUsageFromExtension(ASN1Object extData) {
|
||||
var keyUsage = <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<ExtendedKeyUsage> _fetchExtendedKeyUsageFromExtension(ASN1Object extData) {
|
||||
var extKeyUsage = <ExtendedKeyUsage>[];
|
||||
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<dynamic> _fetchBasicConstraintsFromExtension(ASN1Object extData) {
|
||||
var basicConstraints = <dynamic>[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<String> _fetchSansFromExtension(ASN1Object extData) {
|
||||
var sans = <String>[];
|
||||
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<String> _fetchCrlDistributionPoints(ASN1Object extData) {
|
||||
var cRLDistributionPoints = <String>[];
|
||||
|
||||
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<String, String> _getDnFromSeq(ASN1Sequence issuerSequence) {
|
||||
var dnData = <String, String>{};
|
||||
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 {
|
||||
|
||||
@@ -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<void> main() async {
|
||||
@@ -37,8 +41,7 @@ Future<void> 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),
|
||||
);
|
||||
|
||||
394
lib/network/util/crypto.dart
Normal file
394
lib/network/util/crypto.dart
Normal file
@@ -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 <https://tools.ietf.org/html/rfc8017#page-54>.
|
||||
///
|
||||
/// ```
|
||||
/// 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 <https://tools.ietf.org/html/rfc5208>.
|
||||
/// ```
|
||||
/// 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<int>.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;
|
||||
}
|
||||
}
|
||||
70
lib/network/util/lang.dart
Normal file
70
lib/network/util/lang.dart
Normal file
@@ -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<T> convertList<T>(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<String> chunk(String s, int chunkSize) {
|
||||
var chunked = <String>[];
|
||||
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<int> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<T> convertList<T>(List list) {
|
||||
return list.map((e) => e as T).toList();
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:date_format/date_format.dart';
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:proxypin/network/bin/configuration.dart';
|
||||
import 'package:proxypin/network/http/http.dart';
|
||||
import 'package:proxypin/network/util/logger.dart';
|
||||
@@ -29,6 +28,7 @@ import 'package:proxypin/utils/files.dart';
|
||||
import 'package:proxypin/utils/har.dart';
|
||||
import 'package:proxypin/utils/listenable_list.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
///历史存储
|
||||
///@Author WangHongEn
|
||||
|
||||
@@ -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';
|
||||
@@ -80,7 +79,7 @@ class _CertHashPageState extends State<CertHashPage> {
|
||||
FilledButton.icon(
|
||||
onPressed: () {
|
||||
getSubjectName();
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
style: buttonStyle,
|
||||
icon: const Icon(Icons.play_arrow_rounded),
|
||||
@@ -117,9 +116,8 @@ class _CertHashPageState extends State<CertHashPage> {
|
||||
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);
|
||||
|
||||
@@ -2,10 +2,13 @@ import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_code_editor/flutter_code_editor.dart';
|
||||
import 'package:flutter_js/flutter_js.dart';
|
||||
import 'package:highlight/languages/javascript.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_highlight/themes/monokai-sublime.dart';
|
||||
import 'package:flutter_js/flutter_js.dart';
|
||||
import 'package:flutter_toastr/flutter_toastr.dart';
|
||||
import 'package:highlight/languages/javascript.dart';
|
||||
import 'package:proxypin/network/components/js/file.dart';
|
||||
import 'package:proxypin/network/components/js/md5.dart';
|
||||
|
||||
@@ -19,21 +22,31 @@ class JavaScript extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _JavaScriptState extends State<JavaScript> {
|
||||
static JavascriptRuntime flutterJs = getJavascriptRuntime();
|
||||
//重置环境
|
||||
static bool resetEnvironment = true;
|
||||
|
||||
static JavascriptRuntime? flutterJs;
|
||||
|
||||
late CodeController code;
|
||||
|
||||
List<Text> outLines = [];
|
||||
|
||||
ScrollController inputScrollController = ScrollController();
|
||||
ScrollController outputScrollController = ScrollController();
|
||||
|
||||
AppLocalizations get localizations => AppLocalizations.of(context)!;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (resetEnvironment || flutterJs == null) {
|
||||
flutterJs = getJavascriptRuntime();
|
||||
}
|
||||
// register channel callback
|
||||
final channelCallbacks = JavascriptRuntime.channelFunctionsRegistered[flutterJs.getEngineInstanceId()];
|
||||
final channelCallbacks = JavascriptRuntime.channelFunctionsRegistered[flutterJs!.getEngineInstanceId()];
|
||||
channelCallbacks!["ConsoleLog"] = consoleLog;
|
||||
Md5Bridge.registerMd5(flutterJs);
|
||||
FileBridge.registerFile(flutterJs);
|
||||
Md5Bridge.registerMd5(flutterJs!);
|
||||
FileBridge.registerFile(flutterJs!);
|
||||
|
||||
code = CodeController(language: javascript, text: 'console.log("Hello, World!")');
|
||||
}
|
||||
@@ -41,6 +54,12 @@ class _JavaScriptState extends State<JavaScript> {
|
||||
@override
|
||||
void dispose() {
|
||||
code.dispose();
|
||||
inputScrollController.dispose();
|
||||
outputScrollController.dispose();
|
||||
if (resetEnvironment) {
|
||||
flutterJs?.dispose();
|
||||
flutterJs = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -48,17 +67,18 @@ class _JavaScriptState extends State<JavaScript> {
|
||||
var level = args.removeAt(0);
|
||||
String output = args.join(' ');
|
||||
if (level == 'info') level = 'warn';
|
||||
outLines.add(Text(output, style: TextStyle(color: level == 'error' ? Colors.red : Colors.white, fontSize: 13)));
|
||||
setState(() {
|
||||
outLines.add(Text(output, style: TextStyle(color: level == 'error' ? Colors.red : Colors.white, fontSize: 13)));
|
||||
print(outLines);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color primaryColor = Theme.of(context).colorScheme.primary;
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text("JavaScript", style: TextStyle(fontSize: 16)), centerTitle: true),
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(title: const Text("JavaScript", style: TextStyle(fontSize: 16)), centerTitle: true),
|
||||
body: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
@@ -82,15 +102,16 @@ class _JavaScriptState extends State<JavaScript> {
|
||||
onPressed: () async {
|
||||
outLines.clear();
|
||||
//失去焦点
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
var jsResult = await flutterJs.evaluateAsync(code.text);
|
||||
FocusScope.of(context).unfocus();
|
||||
var jsResult = await flutterJs!.evaluateAsync(code.text);
|
||||
if (jsResult.isPromise || jsResult.rawResult is Future) {
|
||||
jsResult = await flutterJs.handlePromise(jsResult);
|
||||
jsResult = await flutterJs!.handlePromise(jsResult);
|
||||
}
|
||||
|
||||
if (jsResult.isError) {
|
||||
outLines.add(Text(jsResult.toString(), style: const TextStyle(color: Colors.red, fontSize: 13)));
|
||||
setState(() {});
|
||||
setState(() {
|
||||
outLines
|
||||
.add(Text(jsResult.toString(), style: const TextStyle(color: Colors.red, fontSize: 13)));
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.play_arrow_rounded),
|
||||
@@ -103,22 +124,85 @@ class _JavaScriptState extends State<JavaScript> {
|
||||
height: 320,
|
||||
child: CodeTheme(
|
||||
data: CodeThemeData(styles: monokaiSublimeTheme),
|
||||
child: SingleChildScrollView(
|
||||
child: CodeField(
|
||||
minLines: 16,
|
||||
textStyle: const TextStyle(fontSize: 12),
|
||||
controller: code,
|
||||
gutterStyle: const GutterStyle(width: 50, margin: 0),
|
||||
)))),
|
||||
child: Scrollbar(
|
||||
controller: inputScrollController,
|
||||
thumbVisibility: true,
|
||||
interactive: true,
|
||||
trackVisibility: true,
|
||||
thickness: 8,
|
||||
child: SingleChildScrollView(
|
||||
controller: inputScrollController,
|
||||
scrollDirection: Axis.vertical,
|
||||
child: CodeField(
|
||||
minLines: 16,
|
||||
background: Colors.grey.shade800,
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
textStyle: const TextStyle(fontSize: 13),
|
||||
controller: code,
|
||||
enableSuggestions: true,
|
||||
onTapOutside: (event) => FocusScope.of(context).unfocus(),
|
||||
gutterStyle: const GutterStyle(width: 50, margin: 0),
|
||||
))))),
|
||||
const SizedBox(height: 10),
|
||||
TextButton(onPressed: () {}, child: const Text("Output:", style: TextStyle(fontSize: 16))),
|
||||
Row(children: [
|
||||
Text("Output:", style: TextStyle(fontSize: 16, color: primaryColor, fontWeight: FontWeight.w500)),
|
||||
const SizedBox(width: 15),
|
||||
//copy
|
||||
IconButton(
|
||||
icon: Icon(Icons.copy, color: primaryColor, size: 18),
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: outLines.join("\n")));
|
||||
FlutterToastr.show(localizations.copied, context, duration: 3);
|
||||
}),
|
||||
]),
|
||||
Expanded(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(10),
|
||||
color: Colors.black,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: outLines)))),
|
||||
color: Colors.grey.shade800,
|
||||
child: Scrollbar(
|
||||
controller: outputScrollController,
|
||||
thumbVisibility: true,
|
||||
trackVisibility: true,
|
||||
child: SingleChildScrollView(
|
||||
controller: outputScrollController,
|
||||
child: SelectionArea(
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: outLines)))))),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new widget for the fullscreen CodeField
|
||||
class FullScreenCodeField extends StatelessWidget {
|
||||
final CodeController code;
|
||||
|
||||
FullScreenCodeField({required this.code});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("FullScreen Code Editor"),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.close),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Expanded(
|
||||
child: CodeTheme(
|
||||
data: CodeThemeData(styles: monokaiSublimeTheme),
|
||||
child: CodeField(
|
||||
background: Colors.grey.shade800,
|
||||
minLines: 50,
|
||||
textStyle: const TextStyle(fontSize: 12),
|
||||
controller: code,
|
||||
gutterStyle: const GutterStyle(width: 50, margin: 0),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,16 +18,14 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:proxypin/network/bin/server.dart';
|
||||
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/logger.dart';
|
||||
import 'package:proxypin/ui/component/cert_hash.dart';
|
||||
import 'package:proxypin/ui/component/device.dart';
|
||||
@@ -41,7 +39,6 @@ import 'package:proxypin/ui/desktop/request/request_editor.dart';
|
||||
import 'package:proxypin/ui/desktop/toolbar/setting/request_rewrite.dart';
|
||||
import 'package:proxypin/ui/desktop/toolbar/setting/script.dart';
|
||||
import 'package:proxypin/utils/platform.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
@@ -216,38 +213,6 @@ void registerMethodHandler() {
|
||||
return getApplicationSupportDirectory().then((it) => it.path);
|
||||
}
|
||||
|
||||
if (call.method == 'getSaveLocation') {
|
||||
String? path = (await getSaveLocation(suggestedName: call.arguments))?.path;
|
||||
if (Platform.isWindows) windowManager.blur();
|
||||
return path;
|
||||
}
|
||||
|
||||
if (call.method == 'saveFile') {
|
||||
String? path = (await FilePicker.platform.saveFile(fileName: call.arguments));
|
||||
return path;
|
||||
}
|
||||
|
||||
if (call.method == 'openFile') {
|
||||
List<String> extensions =
|
||||
call.arguments is List ? Lists.convertList<String>(call.arguments) : <String>[call.arguments];
|
||||
|
||||
XTypeGroup typeGroup =
|
||||
XTypeGroup(extensions: extensions, uniformTypeIdentifiers: Platform.isMacOS ? const ['public.item'] : null);
|
||||
final XFile? file = await openFile(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
|
||||
if (Platform.isWindows) windowManager.blur();
|
||||
return file?.path;
|
||||
}
|
||||
|
||||
if (call.method == 'pickFile') {
|
||||
List<String> extensions =
|
||||
call.arguments is List ? Lists.convertList<String>(call.arguments) : <String>[call.arguments];
|
||||
|
||||
var file =
|
||||
(await FilePicker.platform.pickFiles(allowedExtensions: extensions, type: FileType.custom))?.files.single;
|
||||
if (Platform.isWindows) windowManager.blur();
|
||||
return file?.path;
|
||||
}
|
||||
|
||||
if (call.method == 'launchUrl') {
|
||||
return launchUrl(Uri.parse(call.arguments));
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -203,9 +202,10 @@ class _QrDecodeState extends State<_QrDecode> with AutomaticKeepAliveClientMixin
|
||||
}
|
||||
|
||||
if (Platforms.isDesktop()) {
|
||||
String? file = await DesktopMultiWindow.invokeMethod(0, 'pickFile', <String>['jpg', 'png', 'jpeg']);
|
||||
if (widget.windowId != null) WindowController.fromWindowId(widget.windowId!).show();
|
||||
return file;
|
||||
//<String>['jpg', 'png', 'jpeg']
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.image);
|
||||
if (result == null || result.files.isEmpty) return null;
|
||||
return result.files.single.path;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -18,7 +18,7 @@ import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:date_format/date_format.dart';
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_toastr/flutter_toastr.dart';
|
||||
@@ -233,14 +233,14 @@ class _HistoryListState extends State<_HistoryListWidget> {
|
||||
|
||||
//导入har
|
||||
import() async {
|
||||
const XTypeGroup typeGroup = XTypeGroup(label: 'Har', extensions: <String>['har']);
|
||||
final XFile? file = await openFile(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
|
||||
if (file == null) {
|
||||
final results = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['har']);
|
||||
if (results == null || results.files.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
var file = results.files.first;
|
||||
try {
|
||||
var historyItem = await storage.addHarFile(file);
|
||||
var historyItem = await storage.addHarFile(file.xFile);
|
||||
setState(() {
|
||||
toRequestsView(historyItem);
|
||||
FlutterToastr.show(localizations.importSuccess, context);
|
||||
@@ -370,14 +370,15 @@ class _HistoryListState extends State<_HistoryListWidget> {
|
||||
//文件名称
|
||||
String fileName =
|
||||
'${item.name.contains("ProxyPin") ? '' : 'ProxyPin'}${item.name}.har'.replaceAll(" ", "_").replaceAll(":", "_");
|
||||
final FileSaveLocation? result = await getSaveLocation(suggestedName: fileName);
|
||||
if (result == null) {
|
||||
|
||||
final String? path = await FilePicker.platform.saveFile(fileName: fileName);
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//获取请求
|
||||
List<HttpRequest> requests = await storage.getRequests(item);
|
||||
var file = await File(result.path).create();
|
||||
var file = await File(path).create();
|
||||
await Har.writeFile(requests, file, title: item.name);
|
||||
if (mounted) FlutterToastr.show(localizations.exportSuccess, context);
|
||||
Future.delayed(const Duration(seconds: 30), () => item.requests = null);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_desktop_context_menu/flutter_desktop_context_menu.dart';
|
||||
@@ -170,8 +170,8 @@ class DesktopRequestListState extends State<DesktopRequestListWidget> with Autom
|
||||
|
||||
///导出
|
||||
export(String fileName) async {
|
||||
final FileSaveLocation? result = await getSaveLocation(suggestedName: fileName);
|
||||
if (result == null) {
|
||||
var path = await FilePicker.platform.saveFile(fileName: fileName);
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ class DesktopRequestListState extends State<DesktopRequestListWidget> with Autom
|
||||
List<HttpRequest>? requests = currentView();
|
||||
if (requests == null) return;
|
||||
|
||||
var file = await File(result.path).create();
|
||||
var file = await File(path).create();
|
||||
await Har.writeFile(requests, file, title: fileName);
|
||||
|
||||
if (mounted) FlutterToastr.show(AppLocalizations.of(context)!.exportSuccess, context);
|
||||
|
||||
@@ -17,7 +17,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -154,14 +154,16 @@ class RequestRewriteState extends State<RequestRewriteWidget> {
|
||||
|
||||
//导入js
|
||||
import() async {
|
||||
String? file = await DesktopMultiWindow.invokeMethod(0, 'openFile', 'config');
|
||||
WindowController.fromWindowId(widget.windowId).show();
|
||||
if (file == null) {
|
||||
FilePickerResult? result =
|
||||
await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['config', 'json']);
|
||||
if (result == null || result.files.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
var file = result.files.single;
|
||||
|
||||
try {
|
||||
List json = jsonDecode(await File(file).readAsString());
|
||||
List json = jsonDecode(await File(file.path!).readAsString());
|
||||
for (var item in json) {
|
||||
var rule = RequestRewriteRule.formJson(item);
|
||||
var items = (item['items'] as List).map((e) => RewriteItem.fromJson(e)).toList();
|
||||
@@ -350,9 +352,8 @@ class _RequestRuleListState extends State<RequestRuleList> {
|
||||
if (indexes.isEmpty) return;
|
||||
|
||||
String fileName = 'proxypin-rewrites.config';
|
||||
String? saveLocation = await DesktopMultiWindow.invokeMethod(0, 'getSaveLocation', fileName);
|
||||
WindowController.fromWindowId(widget.windowId).show();
|
||||
if (saveLocation == null) {
|
||||
var path = await FilePicker.platform.saveFile(fileName: fileName);
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -365,8 +366,7 @@ class _RequestRuleListState extends State<RequestRuleList> {
|
||||
list.add(json);
|
||||
}
|
||||
|
||||
final XFile xFile = XFile.fromData(utf8.encode(jsonEncode(list)), mimeType: 'json');
|
||||
await xFile.saveTo(saveLocation);
|
||||
await File(path).writeAsBytes(utf8.encode(jsonEncode(list)));
|
||||
if (mounted) FlutterToastr.show(localizations.exportSuccess, context);
|
||||
}
|
||||
|
||||
@@ -521,7 +521,7 @@ class _RewriteRuleEditState extends State<RewriteRuleEdit> {
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
|
||||
content: Container(
|
||||
width: 550,
|
||||
constraints: const BoxConstraints(minHeight: 200,maxHeight: 550),
|
||||
constraints: const BoxConstraints(minHeight: 200, maxHeight: 550),
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
|
||||
@@ -18,7 +18,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -151,14 +151,13 @@ class _ScriptWidgetState extends State<ScriptWidget> {
|
||||
|
||||
//导入js
|
||||
import() async {
|
||||
String? file = await DesktopMultiWindow.invokeMethod(0, 'openFile', 'json');
|
||||
WindowController.fromWindowId(widget.windowId).show();
|
||||
if (file == null) {
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['json']);
|
||||
if (result == null || result.files.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
var file = result.files.single.path;
|
||||
try {
|
||||
var json = jsonDecode(await File(file).readAsString());
|
||||
var json = jsonDecode(await File(file!).readAsString());
|
||||
var scriptManager = (await ScriptManager.instance);
|
||||
if (json is List<dynamic>) {
|
||||
for (var item in json) {
|
||||
@@ -616,9 +615,8 @@ class _ScriptListState extends State<ScriptList> {
|
||||
if (indexes.isEmpty) return;
|
||||
//文件名称
|
||||
String fileName = 'proxypin-scripts.json';
|
||||
String? saveLocation = await DesktopMultiWindow.invokeMethod(0, 'getSaveLocation', fileName);
|
||||
WindowController.fromWindowId(widget.windowId).show();
|
||||
if (saveLocation == null) {
|
||||
String? path = await FilePicker.platform.saveFile(fileName: fileName);
|
||||
if (path == null) {
|
||||
return;
|
||||
}
|
||||
var scriptManager = await ScriptManager.instance;
|
||||
@@ -631,8 +629,7 @@ class _ScriptListState extends State<ScriptList> {
|
||||
json.add(map);
|
||||
}
|
||||
|
||||
final XFile xFile = XFile.fromData(utf8.encode(jsonEncode(json)), mimeType: 'json');
|
||||
await xFile.saveTo(saveLocation);
|
||||
await File(path).writeAsBytes(utf8.encode(jsonEncode(json)));
|
||||
if (mounted) FlutterToastr.show(localizations.exportSuccess, context);
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,12 @@ class _SocketLaunchState extends State<SocketLaunch> with WindowListener, Widget
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
if (widget.proxyServer.isRunning) {
|
||||
widget.proxyServer.startForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
if (state == AppLifecycleState.detached) {
|
||||
logger.d('AppLifecycleState.detached');
|
||||
widget.onStop?.call();
|
||||
|
||||
@@ -18,7 +18,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:date_format/date_format.dart';
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
@@ -156,15 +156,13 @@ class _MobileHistoryState extends State<MobileHistory> {
|
||||
|
||||
//导入har
|
||||
import(HistoryStorage storage) async {
|
||||
const XTypeGroup typeGroup =
|
||||
XTypeGroup(label: 'har', extensions: <String>['har'], uniformTypeIdentifiers: ["public.item"]);
|
||||
final XFile? file = await openFile(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
|
||||
if (file == null) {
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['har']);
|
||||
if (result == null || result.files.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var historyItem = await storage.addHarFile(file);
|
||||
var historyItem = await storage.addHarFile(result.files.single.xFile);
|
||||
setState(() {
|
||||
toRequestsView(historyItem, storage);
|
||||
FlutterToastr.show(localizations.importSuccess, context);
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
*/
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -135,11 +136,11 @@ class _DomainFilterState extends State<DomainFilter> {
|
||||
|
||||
//导入
|
||||
import() async {
|
||||
final XFile? file = await openFile();
|
||||
if (file == null) {
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['config', 'json']);
|
||||
if (result == null || result.files.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
var file = File(result.files.single.path!);
|
||||
try {
|
||||
List json = jsonDecode(await file.readAsString());
|
||||
for (var item in json) {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
@@ -98,10 +98,12 @@ class _MobileRequestRewriteState extends State<MobileRequestRewrite> {
|
||||
|
||||
//导入
|
||||
import() async {
|
||||
final XFile? file = await openFile();
|
||||
if (file == null) {
|
||||
FilePickerResult? result =
|
||||
await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['config', 'json']);
|
||||
if (result == null || result.files.isEmpty) {
|
||||
return;
|
||||
}
|
||||
var file = result.files.single.xFile;
|
||||
|
||||
try {
|
||||
List json = jsonDecode(utf8.decode(await file.readAsBytes()));
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_code_editor/flutter_code_editor.dart';
|
||||
@@ -118,11 +118,11 @@ class _MobileScriptState extends State<MobileScript> {
|
||||
|
||||
//导入js
|
||||
import() async {
|
||||
final XFile? file = await openFile();
|
||||
if (file == null) {
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['json']);
|
||||
if (result == null || result.files.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
var file = result.files.single.xFile;
|
||||
try {
|
||||
var scriptManager = (await ScriptManager.instance);
|
||||
var json = jsonDecode(utf8.decode(await file.readAsBytes()));
|
||||
@@ -470,7 +470,11 @@ class _ScriptEditState extends State<ScriptEdit> {
|
||||
CodeTheme(
|
||||
data: CodeThemeData(styles: monokaiSublimeTheme),
|
||||
child: SingleChildScrollView(
|
||||
child: CodeField(textStyle: const TextStyle(fontSize: 14), controller: script)))
|
||||
child: CodeField(
|
||||
textStyle: const TextStyle(fontSize: 13),
|
||||
enableSuggestions: true,
|
||||
onTapOutside: (event) => FocusScope.of(context).unfocus(),
|
||||
controller: script)))
|
||||
],
|
||||
))));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <desktop_multi_window/desktop_multi_window_plugin.h>
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <flutter_desktop_context_menu/flutter_desktop_context_menu_plugin.h>
|
||||
#include <flutter_js/flutter_js_plugin.h>
|
||||
#include <proxy_manager/proxy_manager_plugin.h>
|
||||
@@ -19,9 +18,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) desktop_multi_window_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopMultiWindowPlugin");
|
||||
desktop_multi_window_plugin_register_with_registrar(desktop_multi_window_registrar);
|
||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) flutter_desktop_context_menu_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterDesktopContextMenuPlugin");
|
||||
flutter_desktop_context_menu_plugin_register_with_registrar(flutter_desktop_context_menu_registrar);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_multi_window
|
||||
file_selector_linux
|
||||
flutter_desktop_context_menu
|
||||
flutter_js
|
||||
proxy_manager
|
||||
|
||||
@@ -7,7 +7,6 @@ import Foundation
|
||||
|
||||
import desktop_multi_window
|
||||
import device_info_plus
|
||||
import file_selector_macos
|
||||
import flutter_desktop_context_menu
|
||||
import flutter_js
|
||||
import path_provider_foundation
|
||||
@@ -21,7 +20,6 @@ import window_manager
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FlutterMultiWindowPlugin.register(with: registry.registrar(forPlugin: "FlutterMultiWindowPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FlutterDesktopContextMenuPlugin.register(with: registry.registrar(forPlugin: "FlutterDesktopContextMenuPlugin"))
|
||||
FlutterJsPlugin.register(with: registry.registrar(forPlugin: "FlutterJsPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
|
||||
26
pubspec.yaml
26
pubspec.yaml
@@ -13,35 +13,39 @@ dependencies:
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
intl: any
|
||||
crypto: ^3.0.3
|
||||
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
|
||||
path: packages/desktop_multi_window
|
||||
path_provider: ^2.1.4
|
||||
url_launcher: ^6.3.1
|
||||
file_picker: ^7.1.0+1
|
||||
proxy_manager: ^0.0.3
|
||||
permission_handler: ^11.3.1
|
||||
qr_flutter: ^4.1.0
|
||||
flutter_qr_reader: ^1.0.5
|
||||
flutter_toastr: ^1.0.3
|
||||
share_plus: ^10.0.0
|
||||
brotli: ^0.6.0
|
||||
flutter_js: ^0.8.1
|
||||
flutter_code_editor: ^0.3.2
|
||||
flutter_code_editor:
|
||||
git:
|
||||
url: https://github.com/wanghongenpin/flutter-code-editor.git
|
||||
ref: secure-keyboard
|
||||
flutter_desktop_context_menu: ^0.2.0
|
||||
file_picker: ^7.1.0+1
|
||||
file_selector: ^1.0.3
|
||||
win32audio: ^1.3.1
|
||||
device_info_plus: ^10.1.2
|
||||
shared_preferences: ^2.2.3
|
||||
image_pickers: ^2.0.5+2
|
||||
|
||||
qr_flutter: ^4.1.0
|
||||
flutter_qr_reader: ^1.0.5
|
||||
|
||||
brotli: ^0.6.0
|
||||
win32audio: ^1.3.1
|
||||
|
||||
url_launcher: ^6.3.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
@@ -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']!],
|
||||
|
||||
@@ -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<KeyUsage>? 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(<int>[
|
||||
// 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<ExtendedKeyUsage>? extKeyUsage) {
|
||||
if (IterableUtils.isNullOrEmpty(extKeyUsage)) {
|
||||
return null;
|
||||
}
|
||||
var extKeyUsageList = ASN1Sequence();
|
||||
for (var s in extKeyUsage!) {
|
||||
var oi = <int>[];
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <desktop_multi_window/desktop_multi_window_plugin.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <flutter_desktop_context_menu/flutter_desktop_context_menu_plugin.h>
|
||||
#include <flutter_js/flutter_js_plugin.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
@@ -21,8 +20,6 @@
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
DesktopMultiWindowPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("DesktopMultiWindowPlugin"));
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
FlutterDesktopContextMenuPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterDesktopContextMenuPlugin"));
|
||||
FlutterJsPluginRegisterWithRegistrar(
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_multi_window
|
||||
file_selector_windows
|
||||
flutter_desktop_context_menu
|
||||
flutter_js
|
||||
permission_handler_windows
|
||||
|
||||
Reference in New Issue
Block a user