diff --git a/CHANGELOG.md b/CHANGELOG.md index 01f17db..e3fc019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.9.0 + +- Adding `basic_utils` package to handle PEM & key parsing +- A lot of new class factory to create `Keys` (e.g. `RSAPublicKey.cert` and `.bytes`) + ## 2.8.2 - Downgraing `collection` to 1.7.1 to be compatible with flutter_test diff --git a/lib/src/crypto_utils.dart b/lib/src/crypto_utils.dart deleted file mode 100644 index 3a09f7a..0000000 --- a/lib/src/crypto_utils.dart +++ /dev/null @@ -1,1161 +0,0 @@ -// -// The credit of this code goes to this package: https://github.com/Ephenodrom/Dart-Basic-Utils -// I simply used the CryptoUtils part of it to not overload the package with too much useless code -// - -import 'dart:convert'; -import 'dart:math'; -import 'dart:typed_data'; - -import 'package:pointycastle/asn1/object_identifiers.dart'; -import 'package:pointycastle/export.dart'; -import 'package:pointycastle/pointycastle.dart'; -import 'package:pointycastle/src/utils.dart'; -import 'package:pointycastle/ecc/ecc_fp.dart' as ecc_fp; - -/// -/// Helper class for cryptographic operations -/// -class CryptoUtils { - static const BEGIN_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----'; - static const END_PRIVATE_KEY = '-----END PRIVATE KEY-----'; - - static const BEGIN_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----'; - static const END_PUBLIC_KEY = '-----END PUBLIC KEY-----'; - - static const BEGIN_EC_PRIVATE_KEY = '-----BEGIN EC PRIVATE KEY-----'; - static const END_EC_PRIVATE_KEY = '-----END EC PRIVATE KEY-----'; - - static const BEGIN_EC_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----'; - static const END_EC_PUBLIC_KEY = '-----END PUBLIC KEY-----'; - - static const BEGIN_RSA_PRIVATE_KEY = '-----BEGIN RSA PRIVATE KEY-----'; - static const END_RSA_PRIVATE_KEY = '-----END RSA PRIVATE KEY-----'; - - static const BEGIN_RSA_PUBLIC_KEY = '-----BEGIN RSA PUBLIC KEY-----'; - static const END_RSA_PUBLIC_KEY = '-----END RSA PUBLIC KEY-----'; - - static final _byteMask = BigInt.from(0xff); - - /// - /// Splits the given String [s] in chunks with the given [chunkSize]. - /// - static List chunk(String s, int chunkSize) { - var chunked = []; - for (var i = 0; i < s.length; i += chunkSize) { - var end = (i + chunkSize < s.length) ? i + chunkSize : s.length; - chunked.add(s.substring(i, end)); - } - return chunked; - } - - /// - /// Converts the [RSAPublicKey.modulus] from the given [publicKey] to a [Uint8List]. - /// - static Uint8List rsaPublicKeyModulusToBytes(RSAPublicKey publicKey) => - encodeBigInt(publicKey.modulus); - - /// - /// Converts the [RSAPublicKey.exponent] from the given [publicKey] to a [Uint8List]. - /// - static Uint8List rsaPublicKeyExponentToBytes(RSAPublicKey publicKey) => - encodeBigInt(publicKey.exponent); - - /// - /// Converts the [RSAPrivateKey.modulus] from the given [privateKey] to a [Uint8List]. - /// - static Uint8List rsaPrivateKeyModulusToBytes(RSAPrivateKey privateKey) => - encodeBigInt(privateKey.modulus); - - /// - /// Converts the [RSAPrivateKey.exponent] from the given [privateKey] to a [Uint8List]. - /// - static Uint8List rsaPrivateKeyExponentToBytes(RSAPrivateKey privateKey) => - encodeBigInt(privateKey.exponent); - - /// - /// Get a SHA1 Thumbprint for the given [bytes]. - /// - @Deprecated('Use [getHash]') - static String getSha1ThumbprintFromBytes(Uint8List bytes) { - return getHash(bytes, algorithmName: 'SHA-1'); - } - - /// - /// Get a SHA256 Thumbprint for the given [bytes]. - /// - @Deprecated('Use [getHash]') - static String getSha256ThumbprintFromBytes(Uint8List bytes) { - return getHash(bytes, algorithmName: 'SHA-256'); - } - - /// - /// Get a MD5 Thumbprint for the given [bytes]. - /// - @Deprecated('Use [getHash]') - static String getMd5ThumbprintFromBytes(Uint8List bytes) { - return getHash(bytes, algorithmName: 'MD5'); - } - - /// - /// 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; - } - - /// - /// Generates a RSA [AsymmetricKeyPair] with the given [keySize]. - /// The default value for the [keySize] is 2048 bits. - /// - /// The following keySize is supported: - /// * 1024 - /// * 2048 - /// * 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(); - } - - /// - /// Generates a elliptic curve [AsymmetricKeyPair]. - /// - /// The default curve is **prime256v1** - /// - /// The following curves are supported: - /// - /// * brainpoolp160r1 - /// * brainpoolp160t1 - /// * brainpoolp192r1 - /// * brainpoolp192t1 - /// * brainpoolp224r1 - /// * brainpoolp224t1 - /// * brainpoolp256r1 - /// * brainpoolp256t1 - /// * brainpoolp320r1 - /// * brainpoolp320t1 - /// * brainpoolp384r1 - /// * brainpoolp384t1 - /// * brainpoolp512r1 - /// * brainpoolp512t1 - /// * GostR3410-2001-CryptoPro-A - /// * GostR3410-2001-CryptoPro-B - /// * GostR3410-2001-CryptoPro-C - /// * GostR3410-2001-CryptoPro-XchA - /// * GostR3410-2001-CryptoPro-XchB - /// * prime192v1 - /// * prime192v2 - /// * prime192v3 - /// * prime239v1 - /// * prime239v2 - /// * prime239v3 - /// * prime256v1 - /// * secp112r1 - /// * secp112r2 - /// * secp128r1 - /// * secp128r2 - /// * secp160k1 - /// * secp160r1 - /// * secp160r2 - /// * secp192k1 - /// * secp192r1 - /// * secp224k1 - /// * secp224r1 - /// * secp256k1 - /// * secp256r1 - /// * secp384r1 - /// * secp521r1 - /// - static AsymmetricKeyPair generateEcKeyPair({String curve = 'prime256v1'}) { - var ecDomainParameters = ECDomainParameters(curve); - var keyParams = ECKeyGeneratorParameters(ecDomainParameters); - - var secureRandom = _getSecureRandom(); - - var rngParams = ParametersWithRandom(keyParams, secureRandom); - var generator = ECKeyGenerator(); - generator.init(rngParams); - - return generator.generateKeyPair(); - } - - /// - /// Generates a secure [FortunaRandom] - /// - static SecureRandom _getSecureRandom() { - var secureRandom = FortunaRandom(); - var random = Random.secure(); - var seeds = []; - for (var i = 0; i < 32; i++) { - seeds.add(random.nextInt(255)); - } - secureRandom.seed(KeyParameter(Uint8List.fromList(seeds))); - return secureRandom; - } - - /// - /// 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 = chunk(dataBase64, 64); - - return '$BEGIN_PUBLIC_KEY\n${chunks.join('\n')}\n$END_PUBLIC_KEY'; - } - - /// - /// Enode the given [rsaPublicKey] to PEM format using the PKCS#1 standard. - /// - /// The ASN1 structure is decripted at . - /// - /// ``` - /// RSAPublicKey ::= SEQUENCE { - /// modulus INTEGER, -- n - /// publicExponent INTEGER -- e - /// } - /// ``` - /// - static String encodeRSAPublicKeyToPemPkcs1(RSAPublicKey rsaPublicKey) { - var topLevelSeq = ASN1Sequence(); - topLevelSeq.add(ASN1Integer(rsaPublicKey.modulus)); - topLevelSeq.add(ASN1Integer(rsaPublicKey.exponent)); - - var dataBase64 = base64.encode(topLevelSeq.encode()); - var chunks = chunk(dataBase64, 64); - - return '$BEGIN_RSA_PUBLIC_KEY\n${chunks.join('\n')}\n$END_RSA_PUBLIC_KEY'; - } - - /// - /// Enode the given [rsaPrivateKey] to PEM format using the PKCS#1 standard. - /// - /// The ASN1 structure is decripted at . - /// - /// ``` - /// RSAPrivateKey ::= SEQUENCE { - /// version Version, - /// modulus INTEGER, -- n - /// publicExponent INTEGER, -- e - /// privateExponent INTEGER, -- d - /// prime1 INTEGER, -- p - /// prime2 INTEGER, -- q - /// exponent1 INTEGER, -- d mod (p-1) - /// exponent2 INTEGER, -- d mod (q-1) - /// coefficient INTEGER, -- (inverse of q) mod p - /// otherPrimeInfos OtherPrimeInfos OPTIONAL - /// } - /// ``` - static String encodeRSAPrivateKeyToPemPkcs1(RSAPrivateKey rsaPrivateKey) { - var version = ASN1Integer(BigInt.from(0)); - var modulus = ASN1Integer(rsaPrivateKey.n); - var publicExponent = ASN1Integer(BigInt.parse('65537')); - var privateExponent = ASN1Integer(rsaPrivateKey.privateExponent); - - var p = ASN1Integer(rsaPrivateKey.p); - var q = ASN1Integer(rsaPrivateKey.q); - var dP = - rsaPrivateKey.privateExponent! % (rsaPrivateKey.p! - BigInt.from(1)); - var exp1 = ASN1Integer(dP); - var dQ = - rsaPrivateKey.privateExponent! % (rsaPrivateKey.q! - BigInt.from(1)); - var exp2 = ASN1Integer(dQ); - var iQ = rsaPrivateKey.q!.modInverse(rsaPrivateKey.p!); - var co = ASN1Integer(iQ); - - var topLevelSeq = ASN1Sequence(); - topLevelSeq.add(version); - topLevelSeq.add(modulus); - topLevelSeq.add(publicExponent); - topLevelSeq.add(privateExponent); - topLevelSeq.add(p); - topLevelSeq.add(q); - topLevelSeq.add(exp1); - topLevelSeq.add(exp2); - topLevelSeq.add(co); - var dataBase64 = base64.encode(topLevelSeq.encode()); - var chunks = chunk(dataBase64, 64); - return '$BEGIN_RSA_PRIVATE_KEY\n${chunks.join('\n')}\n$END_RSA_PRIVATE_KEY'; - } - - /// - /// Enode the given [rsaPrivateKey] to PEM format using the PKCS#8 standard. - /// - /// The ASN1 structure is decripted at . - /// ``` - /// PrivateKeyInfo ::= SEQUENCE { - /// version Version, - /// algorithm AlgorithmIdentifier, - /// PrivateKey BIT STRING - /// } - /// ``` - /// - static String encodeRSAPrivateKeyToPem(RSAPrivateKey rsaPrivateKey) { - var version = ASN1Integer(BigInt.from(0)); - - var algorithmSeq = ASN1Sequence(); - var algorithmAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList( - [0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x1])); - var paramsAsn1Obj = ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0])); - algorithmSeq.add(algorithmAsn1Obj); - algorithmSeq.add(paramsAsn1Obj); - - var privateKeySeq = ASN1Sequence(); - var modulus = ASN1Integer(rsaPrivateKey.n); - var publicExponent = ASN1Integer(BigInt.parse('65537')); - var privateExponent = ASN1Integer(rsaPrivateKey.privateExponent); - var p = ASN1Integer(rsaPrivateKey.p); - var q = ASN1Integer(rsaPrivateKey.q); - var dP = - rsaPrivateKey.privateExponent! % (rsaPrivateKey.p! - BigInt.from(1)); - var exp1 = ASN1Integer(dP); - var dQ = - rsaPrivateKey.privateExponent! % (rsaPrivateKey.q! - BigInt.from(1)); - var exp2 = ASN1Integer(dQ); - var iQ = rsaPrivateKey.q!.modInverse(rsaPrivateKey.p!); - var co = ASN1Integer(iQ); - - privateKeySeq.add(version); - privateKeySeq.add(modulus); - privateKeySeq.add(publicExponent); - privateKeySeq.add(privateExponent); - privateKeySeq.add(p); - privateKeySeq.add(q); - privateKeySeq.add(exp1); - privateKeySeq.add(exp2); - privateKeySeq.add(co); - var publicKeySeqOctetString = - ASN1OctetString(octets: Uint8List.fromList(privateKeySeq.encode())); - - var topLevelSeq = ASN1Sequence(); - topLevelSeq.add(version); - topLevelSeq.add(algorithmSeq); - topLevelSeq.add(publicKeySeqOctetString); - var dataBase64 = base64.encode(topLevelSeq.encode()); - var chunks = chunk(dataBase64, 64); - return '$BEGIN_PRIVATE_KEY\n${chunks.join('\n')}\n$END_PRIVATE_KEY'; - } - - /// - /// Decode a [RSAPrivateKey] from the given [pem] String. - /// - static RSAPrivateKey rsaPrivateKeyFromPem(String pem) { - var bytes = getBytesFromPEMString(pem); - return rsaPrivateKeyFromDERBytes(bytes); - } - - /// - /// 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; - } - - /// - /// Decode a [RSAPrivateKey] from the given [pem] string formated in the pkcs1 standard. - /// - static RSAPrivateKey rsaPrivateKeyFromPemPkcs1(String pem) { - var bytes = getBytesFromPEMString(pem); - return rsaPrivateKeyFromDERBytesPkcs1(bytes); - } - - /// - /// Decode the given [bytes] into an [RSAPrivateKey]. - /// - /// The [bytes] need to follow the the pkcs1 standard - /// - static RSAPrivateKey rsaPrivateKeyFromDERBytesPkcs1(Uint8List bytes) { - var asn1Parser = ASN1Parser(bytes); - 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; - } - - /// - /// 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 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 [RSAPublicKey] from the given [pem] string formated in the pkcs1 standard. - /// - static RSAPublicKey rsaPublicKeyFromPemPkcs1(String pem) { - var bytes = CryptoUtils.getBytesFromPEMString(pem); - return rsaPublicKeyFromDERBytesPkcs1(bytes); - } - - /// - /// Decode the given [bytes] into an [RSAPublicKey]. - /// - /// The [bytes] need to follow the the pkcs1 standard - /// - static RSAPublicKey rsaPublicKeyFromDERBytesPkcs1(Uint8List bytes) { - var publicKeyAsn = ASN1Parser(bytes); - var publicKeySeq = publicKeyAsn.nextObject() as ASN1Sequence; - var modulus = publicKeySeq.elements![0] as ASN1Integer; - var exponent = publicKeySeq.elements![1] as ASN1Integer; - - var rsaPublicKey = RSAPublicKey(modulus.integer!, exponent.integer!); - return rsaPublicKey; - } - - /// - /// Enode the given elliptic curve [publicKey] to PEM format. - /// - /// This is descripted in - /// - /// ```ASN1 - /// ECPrivateKey ::= SEQUENCE { - /// version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), - /// privateKey OCTET STRING - /// parameters [0] ECParameters {{ NamedCurve }} OPTIONAL - /// publicKey [1] BIT STRING OPTIONAL - /// } - /// - /// ``` - /// - /// As descripted in the mentioned RFC, all optional values will always be set. - /// - static String encodeEcPrivateKeyToPem(ECPrivateKey ecPrivateKey) { - var outer = ASN1Sequence(); - - var version = ASN1Integer(BigInt.from(1)); - var privateKeyAsBytes = i2osp(ecPrivateKey.d!); - var privateKey = ASN1OctetString(octets: privateKeyAsBytes); - var choice = ASN1Sequence(tag: 0xA0); - - choice.add( - ASN1ObjectIdentifier.fromName(ecPrivateKey.parameters!.domainName)); - - var publicKey = ASN1Sequence(tag: 0xA1); - var q = ecPrivateKey.parameters!.G * ecPrivateKey.d!; - var encodedBytes = q!.getEncoded(false); - var subjectPublicKey = ASN1BitString(stringValues: encodedBytes); - publicKey.add(subjectPublicKey); - - outer.add(version); - outer.add(privateKey); - outer.add(choice); - outer.add(publicKey); - var dataBase64 = base64.encode(outer.encode()); - var chunks = chunk(dataBase64, 64); - - return '$BEGIN_EC_PRIVATE_KEY\n${chunks.join('\n')}\n$END_EC_PRIVATE_KEY'; - } - - /// - /// Enode the given elliptic curve [publicKey] to PEM format. - /// - /// This is descripted in - /// - /// ```ASN1 - /// SubjectPublicKeyInfo ::= SEQUENCE { - /// algorithm AlgorithmIdentifier, - /// subjectPublicKey BIT STRING - /// } - /// ``` - /// - static String encodeEcPublicKeyToPem(ECPublicKey publicKey) { - var outer = ASN1Sequence(); - var algorithm = ASN1Sequence(); - algorithm.add(ASN1ObjectIdentifier.fromName('ecPublicKey')); - algorithm - .add(ASN1ObjectIdentifier.fromName(publicKey.parameters!.domainName)); - var encodedBytes = publicKey.Q!.getEncoded(false); - - var subjectPublicKey = ASN1BitString(stringValues: encodedBytes); - - outer.add(algorithm); - outer.add(subjectPublicKey); - var dataBase64 = base64.encode(outer.encode()); - var chunks = chunk(dataBase64, 64); - - return '$BEGIN_EC_PUBLIC_KEY\n${chunks.join('\n')}\n$END_EC_PUBLIC_KEY'; - } - - /// - /// Decode a [ECPublicKey] from the given [pem] String. - /// - /// Throws an ArgumentError if the given string [pem] is null or empty. - /// - static ECPublicKey ecPublicKeyFromPem(String pem) { - if (pem.isEmpty) { - throw ArgumentError('Argument must not be null.'); - } - var bytes = CryptoUtils.getBytesFromPEMString(pem); - return ecPublicKeyFromDerBytes(bytes); - } - - /// - /// Decode a [ECPrivateKey] from the given [pem] String. - /// - /// Throws an ArgumentError if the given string [pem] is null or empty. - /// - static ECPrivateKey ecPrivateKeyFromPem(String pem) { - if (pem.isEmpty) { - throw ArgumentError('Argument must not be null.'); - } - var bytes = CryptoUtils.getBytesFromPEMString(pem); - return ecPrivateKeyFromDerBytes( - bytes, - pkcs8: pem.startsWith(BEGIN_PRIVATE_KEY), - ); - } - - /// - /// Decode the given [bytes] into an [ECPrivateKey]. - /// - /// [pkcs8] defines the ASN1 format of the given [bytes]. The default is false, so SEC1 is assumed. - /// - /// Supports SEC1 () and PKCS8 () - /// - static ECPrivateKey ecPrivateKeyFromDerBytes(Uint8List bytes, - {bool pkcs8 = false}) { - var asn1Parser = ASN1Parser(bytes); - var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence; - String? curveName; - Uint8List x; - if (pkcs8) { - // Parse the PKCS8 format - var innerSeq = topLevelSeq.elements!.elementAt(1) as ASN1Sequence; - var b2 = innerSeq.elements!.elementAt(1) as ASN1ObjectIdentifier; - var b2Data = b2.objectIdentifierAsString; - var b2Curvedata = ObjectIdentifiers.getIdentifierByIdentifier(b2Data); - if (b2Curvedata != null) { - curveName = b2Curvedata['readableName']; - } - - var octetString = topLevelSeq.elements!.elementAt(2) as ASN1OctetString; - asn1Parser = ASN1Parser(octetString.valueBytes); - var octetStringSeq = asn1Parser.nextObject() as ASN1Sequence; - var octetStringKeyData = - octetStringSeq.elements!.elementAt(1) as ASN1OctetString; - - x = octetStringKeyData.valueBytes!; - } else { - // Parse the SEC1 format - var privateKeyAsOctetString = - topLevelSeq.elements!.elementAt(1) as ASN1OctetString; - var choice = topLevelSeq.elements!.elementAt(2); - var s = ASN1Sequence(); - var parser = ASN1Parser(choice.valueBytes); - while (parser.hasNext()) { - s.add(parser.nextObject()); - } - var curveNameOi = s.elements!.elementAt(0) as ASN1ObjectIdentifier; - var data = ObjectIdentifiers.getIdentifierByIdentifier( - curveNameOi.objectIdentifierAsString); - if (data != null) { - curveName = data['readableName']; - } - - x = privateKeyAsOctetString.valueBytes!; - } - - return ECPrivateKey(osp2i(x), ECDomainParameters(curveName!)); - } - - /// - /// Decode the given [bytes] into an [ECPublicKey]. - /// - static ECPublicKey ecPublicKeyFromDerBytes(Uint8List bytes) { - if (bytes.elementAt(0) == 0) { - bytes = bytes.sublist(1); - } - var asn1Parser = ASN1Parser(bytes); - var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence; - - var algorithmIdentifierSequence = topLevelSeq.elements![0] as ASN1Sequence; - var curveNameOi = algorithmIdentifierSequence.elements!.elementAt(1) - as ASN1ObjectIdentifier; - String? curveName; - var data = ObjectIdentifiers.getIdentifierByIdentifier( - curveNameOi.objectIdentifierAsString); - if (data != null) { - curveName = data['readableName']; - } - - var subjectPublicKey = topLevelSeq.elements![1] as ASN1BitString; - var compressed = false; - var pubBytes = subjectPublicKey.valueBytes!; - if (pubBytes.elementAt(0) == 0) { - pubBytes = pubBytes.sublist(1); - } - - // Looks good so far! - var firstByte = pubBytes.elementAt(0); - if (firstByte != 4) { - compressed = true; - } - var x = pubBytes.sublist(1, (pubBytes.length / 2).round()); - var y = pubBytes.sublist(1 + x.length, pubBytes.length); - var params = ECDomainParameters(curveName!); - var bigX = decodeBigIntWithSign(1, x); - var bigY = decodeBigIntWithSign(1, y); - var pubKey = ECPublicKey( - ecc_fp.ECPoint( - params.curve as ecc_fp.ECCurve, - params.curve.fromBigInteger(bigX) as ecc_fp.ECFieldElement?, - params.curve.fromBigInteger(bigY) as ecc_fp.ECFieldElement?, - compressed), - params); - return pubKey; - } - - /// - /// Encrypt the given [message] using the given RSA [publicKey]. - /// - static String rsaEncrypt(String message, RSAPublicKey publicKey) { - var cipher = RSAEngine() - ..init(true, PublicKeyParameter(publicKey)); - var cipherText = cipher.process(Uint8List.fromList(message.codeUnits)); - - return String.fromCharCodes(cipherText); - } - - /// - /// Decrypt the given [cipherMessage] using the given RSA [privateKey]. - /// - static String rsaDecrypt(String cipherMessage, RSAPrivateKey privateKey) { - var cipher = RSAEngine() - ..init(false, PrivateKeyParameter(privateKey)); - var decrypted = cipher.process(Uint8List.fromList(cipherMessage.codeUnits)); - - return String.fromCharCodes(decrypted); - } - - /// - /// Signing the given [dataToSign] with the given [privateKey]. - /// - /// The default [algorithm] used is **SHA-256/RSA**. All supported algorihms are : - /// - /// * MD2/RSA - /// * MD4/RSA - /// * MD5/RSA - /// * RIPEMD-128/RSA - /// * RIPEMD-160/RSA - /// * RIPEMD-256/RSA - /// * SHA-1/RSA - /// * SHA-224/RSA - /// * SHA-256/RSA - /// * SHA-384/RSA - /// * SHA-512/RSA - /// - static Uint8List rsaSign(RSAPrivateKey privateKey, Uint8List dataToSign, - {String algorithmName = 'SHA-256/RSA'}) { - var signer = Signer(algorithmName) as RSASigner; - - signer.init(true, PrivateKeyParameter(privateKey)); - - var sig = signer.generateSignature(dataToSign); - - return sig.bytes; - } - - /// - /// Verifying the given [signedData] with the given [publicKey] and the given [signature]. - /// Will return **true** if the given [signature] matches the [signedData]. - /// - /// The default [algorithm] used is **SHA-256/RSA**. All supported algorihms are : - /// - /// * MD2/RSA - /// * MD4/RSA - /// * MD5/RSA - /// * RIPEMD-128/RSA - /// * RIPEMD-160/RSA - /// * RIPEMD-256/RSA - /// * SHA-1/RSA - /// * SHA-224/RSA - /// * SHA-256/RSA - /// * SHA-384/RSA - /// * SHA-512/RSA - /// - static bool rsaVerify( - RSAPublicKey publicKey, Uint8List signedData, Uint8List signature, - {String algorithm = 'SHA-256/RSA'}) { - final sig = RSASignature(signature); - - final verifier = Signer(algorithm); - - verifier.init(false, PublicKeyParameter(publicKey)); - - try { - return verifier.verifySignature(signedData, sig); - } on ArgumentError { - return false; - } - } - - /// - /// Verifying the given [signedData] with the given [publicKey], the given [signature] and the given [saltLength]. - /// Will return **true** if the given [signature] matches the [signedData]. - /// - /// The default [algorithm] used is **SHA-256/PSS**. All supported algorihms are : - /// - /// * MD2/PSS - /// * MD4/PSS - /// * MD5/PSS - /// * RIPEMD-128/PSS - /// * RIPEMD-160/PSS - /// * RIPEMD-256/PSS - /// * SHA-1/PSS - /// * SHA-224/PSS - /// * SHA-256/PSS - /// * SHA-384/PSS - /// * SHA-512/PSS - /// - static bool rsaPssVerify(RSAPublicKey publicKey, Uint8List signedData, - Uint8List signature, int saltLength, - {String algorithm = 'SHA-256/PSS'}) { - final sig = PSSSignature(signature); - - final verifier = Signer(algorithm) as PSSSigner; - - var params = ParametersWithSaltConfiguration( - PublicKeyParameter(publicKey), - _getSecureRandom(), - saltLength, - ); - - verifier.init(false, params); - - try { - return verifier.verifySignature(signedData, sig); - } on ArgumentError { - return false; - } - } - - /// - /// Signing the given [dataToSign] with the given [privateKey]. - /// - /// The default [algorithm] used is **SHA-1/ECDSA**. All supported algorihms are : - /// - /// * SHA-1/ECDSA - /// * SHA-224/ECDSA - /// * SHA-256/ECDSA - /// * SHA-384/ECDSA - /// * SHA-512/ECDSA - /// * SHA-1/DET-ECDSA - /// * SHA-224/DET-ECDSA - /// * SHA-256/DET-ECDSA - /// * SHA-384/DET-ECDSA - /// * SHA-512/DET-ECDSA - /// - static ECSignature ecSign(ECPrivateKey privateKey, Uint8List dataToSign, - {String algorithmName = 'SHA-1/ECDSA'}) { - var signer = Signer(algorithmName) as ECDSASigner; - - var params = ParametersWithRandom( - PrivateKeyParameter(privateKey), _getSecureRandom()); - signer.init(true, params); - - var sig = signer.generateSignature(dataToSign) as ECSignature; - - return sig; - } - - /// - /// Convert ECSignature [signature] to DER encoded base64 string. - /// ``` - /// ECDSA-Sig-Value ::= SEQUENCE { - /// r INTEGER, - /// s INTEGER - /// } - ///``` - /// This is mainly used for passing signature as string via request/response use cases - /// - static String ecSignatureToBase64(ECSignature signature) { - var outer = ASN1Sequence(); - outer.add(ASN1Integer(signature.r)); - outer.add(ASN1Integer(signature.s)); - - return base64.encode(outer.encode()); - } - - /// - /// Converts a [base64] DER encoded string to an ECSignature. The der encoded content must follow the following structure. - /// ``` - /// ECDSA-Sig-Value ::= SEQUENCE { - /// r INTEGER, - /// s INTEGER - /// } - ///``` - /// - static ECSignature ecSignatureFromBase64(String b64) { - var data = base64.decode(b64); - - return ecSignatureFromDerBytes(data); - } - - /// - /// Converts the given DER bytes to an ECSignature. The der encoded content must follow the following structure. - /// ``` - /// ECDSA-Sig-Value ::= SEQUENCE { - /// r INTEGER, - /// s INTEGER - /// } - ///``` - /// - static ECSignature ecSignatureFromDerBytes(Uint8List data) { - var parser = ASN1Parser(data); - var outer = parser.nextObject() as ASN1Sequence; - var el1 = outer.elements!.elementAt(0) as ASN1Integer; - var el2 = outer.elements!.elementAt(1) as ASN1Integer; - - var sig = ECSignature(el1.integer!, el2.integer!); - - return sig; - } - - /// - /// Verifying the given [signedData] with the given [publicKey] and the given [signature]. - /// Will return **true** if the given [signature] matches the [signedData]. - /// - /// The default [algorithm] used is **SHA-1/ECDSA**. All supported algorihms are : - /// - /// * SHA-1/ECDSA - /// * SHA-224/ECDSA - /// * SHA-256/ECDSA - /// * SHA-384/ECDSA - /// * SHA-512/ECDSA - /// * SHA-1/DET-ECDSA - /// * SHA-224/DET-ECDSA - /// * SHA-256/DET-ECDSA - /// * SHA-384/DET-ECDSA - /// * SHA-512/DET-ECDSA - /// - static bool ecVerify( - ECPublicKey publicKey, Uint8List signedData, ECSignature signature, - {String algorithm = 'SHA-1/ECDSA'}) { - final verifier = Signer(algorithm) as ECDSASigner; - - verifier.init(false, PublicKeyParameter(publicKey)); - - try { - return verifier.verifySignature(signedData, signature); - } on ArgumentError { - return false; - } - } - - /// - /// Verifying the given [signedData] with the given [publicKey] and the given [signature] in base64. - /// Will return **true** if the given [signature] matches the [signedData]. - /// - /// The default [algorithm] used is **SHA-1/ECDSA**. All supported algorihms are : - /// - /// * SHA-1/ECDSA - /// * SHA-224/ECDSA - /// * SHA-256/ECDSA - /// * SHA-384/ECDSA - /// * SHA-512/ECDSA - /// * SHA-1/DET-ECDSA - /// * SHA-224/DET-ECDSA - /// * SHA-256/DET-ECDSA - /// * SHA-384/DET-ECDSA - /// * SHA-512/DET-ECDSA - /// - static bool ecVerifyBase64( - ECPublicKey publicKey, Uint8List origData, String signature, - {String algorithm = 'SHA-1/ECDSA'}) { - var ecSignature = ecSignatureFromBase64(signature); - - return ecVerify(publicKey, origData, ecSignature, algorithm: algorithm); - } - - /// - /// Returns the modulus of the given [pem] that represents an RSA private key. - /// - /// This equals the following openssl command: - /// ``` - /// openssl rsa -noout -modulus -in FILE.key - /// ``` - /// - static BigInt getModulusFromRSAPrivateKeyPem(String pem) { - RSAPrivateKey privateKey; - switch (_getPrivateKeyType(pem)) { - case 'RSA': - privateKey = rsaPrivateKeyFromPem(pem); - return privateKey.modulus!; - case 'RSA_PKCS1': - privateKey = rsaPrivateKeyFromPemPkcs1(pem); - return privateKey.modulus!; - case 'ECC': - throw ArgumentError('ECC private key not supported.'); - default: - privateKey = rsaPrivateKeyFromPem(pem); - return privateKey.modulus!; - } - } - - /// - /// 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'; - } - - /// - /// Conversion of bytes to integer according to RFC 3447 at - /// - static BigInt osp2i(Iterable bytes, {Endian endian = Endian.big}) { - var result = BigInt.from(0); - if (endian == Endian.little) { - bytes = bytes.toList().reversed; - } - - for (var byte in bytes) { - result = result << 8; - result |= BigInt.from(byte); - } - - return result; - } - - /// - /// Conversion of integer to bytes according to RFC 3447 at - /// - static Uint8List i2osp(BigInt number, - {int? outLen, Endian endian = Endian.big}) { - var size = (number.bitLength + 7) >> 3; - if (outLen == null) { - outLen = size; - } else if (outLen < size) { - throw Exception('Number too large'); - } - final result = Uint8List(outLen); - var pos = endian == Endian.big ? outLen - 1 : 0; - for (var i = 0; i < size; i++) { - result[pos] = (number & _byteMask).toInt(); - if (endian == Endian.big) { - pos -= 1; - } else { - pos += 1; - } - number = number >> 8; - } - return result; - } - - /// - /// Signing the given [dataToSign] with the given [privateKey] and the given [salt]. - /// - /// The default [algorithm] used is **SHA-256/PSS**. All supported algorihms are : - /// - /// * MD2/PSS - /// * MD4/PSS - /// * MD5/PSS - /// * RIPEMD-128/PSS - /// * RIPEMD-160/PSS - /// * RIPEMD-256/PSS - /// * SHA-1/PSS - /// * SHA-224/PSS - /// * SHA-256/PSS - /// * SHA-384/PSS - /// * SHA-512/PSS - /// - static Uint8List rsaPssSign( - RSAPrivateKey privateKey, Uint8List dataToSign, Uint8List salt, - {String algorithm = 'SHA-256/PSS'}) { - var signer = Signer(algorithm) as PSSSigner; - signer.init( - true, - ParametersWithSalt( - PrivateKeyParameter(privateKey), salt)); - - var sig = signer.generateSignature(dataToSign); - - return sig.bytes; - } -} diff --git a/lib/src/keys.dart b/lib/src/keys.dart index bed7c29..37401a3 100644 --- a/lib/src/keys.dart +++ b/lib/src/keys.dart @@ -1,8 +1,11 @@ +import 'dart:typed_data'; + +import 'package:basic_utils/basic_utils.dart'; +import 'package:dart_jsonwebtoken/src/utils.dart'; +import 'package:ed25519_edwards/ed25519_edwards.dart' as ed; import 'package:pointycastle/pointycastle.dart' as pc; -import 'package:ed25519_edwards/ed25519_edwards.dart' as ed; import 'exceptions.dart'; -import 'crypto_utils.dart'; abstract class JWTKey {} @@ -27,6 +30,8 @@ class RSAPrivateKey extends JWTKey { RSAPrivateKey.raw(pc.RSAPrivateKey _key) : key = _key; RSAPrivateKey.clone(RSAPrivateKey _key) : key = _key.key; + RSAPrivateKey.bytes(Uint8List bytes) + : key = CryptoUtils.rsaPrivateKeyFromDERBytes(bytes); } /// For RSA algorithm, in verify method @@ -43,6 +48,26 @@ class RSAPublicKey extends JWTKey { RSAPublicKey.raw(pc.RSAPublicKey _key) : key = _key; RSAPublicKey.clone(RSAPublicKey _key) : key = _key.key; + RSAPublicKey.bytes(Uint8List bytes) { + try { + key = CryptoUtils.rsaPublicKeyFromDERBytesPkcs1(bytes); + } catch (_) { + key = CryptoUtils.rsaPublicKeyFromDERBytes(bytes); + } + } + RSAPublicKey.cert(String pem) { + final x509 = X509Utils.x509CertificateFromPem(pem); + final bytes = x509.tbsCertificate?.subjectPublicKeyInfo.bytes; + if (bytes == null) { + throw JWTParseException('x509 Certificate parsing failed'); + } + + try { + key = CryptoUtils.rsaPublicKeyFromDERBytesPkcs1(hexToUint8List(bytes)); + } catch (_) { + key = CryptoUtils.rsaPublicKeyFromDERBytes(hexToUint8List(bytes)); + } + } } /// For ECDSA algorithm, in sign method @@ -75,6 +100,8 @@ class ECPrivateKey extends JWTKey { ECPrivateKey.clone(ECPrivateKey _key) : key = _key.key, size = _key.size; + ECPrivateKey.bytes(Uint8List bytes) + : key = CryptoUtils.ecPrivateKeyFromDerBytes(bytes); } /// For ECDSA algorithm, in verify method @@ -87,6 +114,8 @@ class ECPublicKey extends JWTKey { ECPublicKey.raw(pc.ECPublicKey _key) : key = _key; ECPublicKey.clone(ECPublicKey _key) : key = _key.key; + ECPublicKey.bytes(Uint8List bytes) + : key = CryptoUtils.ecPublicKeyFromDerBytes(bytes); } /// For EdDSA algorithm, in sign method diff --git a/lib/src/utils.dart b/lib/src/utils.dart index ddbb193..c76e54e 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -89,3 +89,19 @@ BigInt bigIntFromBytes(Uint8List bytes) { return bytes.fold(BigInt.zero, (a, b) => a * _b256 + BigInt.from(b)); } + +Uint8List hexToUint8List(String hex) { + if (hex.length % 2 != 0) { + throw 'Odd number of hex digits'; + } + var l = hex.length ~/ 2; + var result = Uint8List(l); + for (var i = 0; i < l; ++i) { + var x = int.parse(hex.substring(2 * i, 2 * (i + 1)), radix: 16); + if (x.isNaN) { + throw 'Expected hex string'; + } + result[i] = x; + } + return result; +} diff --git a/pubspec.yaml b/pubspec.yaml index bb75e1c..b486b90 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: dart_jsonwebtoken description: A dart implementation of the famous javascript library 'jsonwebtoken' (JWT). -version: 2.8.2 +version: 2.9.0 repository: https://github.com/jonasroussel/dart_jsonwebtoken homepage: https://github.com/jonasroussel/dart_jsonwebtoken#readme @@ -19,6 +19,7 @@ dependencies: convert: ^3.1.1 collection: ^1.17.1 ed25519_edwards: ^0.3.1 + basic_utils: ^5.6.1 dev_dependencies: lints: ^2.1.1