Remove basic_utils dependency

This commit is contained in:
wanghongenpin
2024-10-29 01:31:50 +08:00
parent fc1e2ce237
commit 47e6c51813
21 changed files with 1389 additions and 244 deletions

View File

@@ -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

View File

@@ -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';

View 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';
}
}

View File

@@ -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
}

View File

@@ -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];

View File

@@ -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 {
///

View File

@@ -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 {

View File

@@ -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),
);

View 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;
}
}

View 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;
}
}

View File

@@ -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();
}
}