From c80c68bb72efe4a365ad0561a97f8e8cb8223d77 Mon Sep 17 00:00:00 2001 From: Jonas Roussel <go.jroussel@gmail.com> Date: Sun, 29 Jan 2023 16:28:09 +0100 Subject: [PATCH] v2.7.0 --- CHANGELOG.md | 5 + lib/dart_jsonwebtoken.dart | 1 - lib/src/algorithms.dart | 15 +- lib/src/crypto_utils.dart | 1161 ++++++++++++++++++++++++++++++++++++ lib/src/errors.dart | 3 + lib/src/keys.dart | 27 +- lib/src/parser.dart | 326 ---------- pubspec.yaml | 2 +- 8 files changed, 1197 insertions(+), 343 deletions(-) create mode 100644 lib/src/crypto_utils.dart delete mode 100644 lib/src/parser.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index a42e683..a56deb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.7.0 + +- `parsing.dart` has been replaced by more accurate CryptoUtils functions `https://github.com/Ephenodrom/Dart-Basic-Utils` +- Fixing `_ECDSAAlgorithm.sign` method that did not filling the gap in the ECDSA curve signatures + ## 2.6.4 - Fixing `ECPrivateKey.raw` initialize `size` diff --git a/lib/dart_jsonwebtoken.dart b/lib/dart_jsonwebtoken.dart index 6a909b5..56a5f3f 100644 --- a/lib/dart_jsonwebtoken.dart +++ b/lib/dart_jsonwebtoken.dart @@ -4,4 +4,3 @@ export 'src/jwt.dart'; export 'src/errors.dart'; export 'src/algorithms.dart'; export 'src/keys.dart'; -export 'src/parser.dart'; diff --git a/lib/src/algorithms.dart b/lib/src/algorithms.dart index ae34d7e..6871562 100644 --- a/lib/src/algorithms.dart +++ b/lib/src/algorithms.dart @@ -241,10 +241,21 @@ class _ECDSAAlgorithm extends JWTAlgorithm { Uint8List.fromList(body), ) as pc.ECSignature; + final rBytes = bigIntToBytes(signature.r).toList(); + while (rBytes.length < 32) { + rBytes.add(0); + } + + final sBytes = bigIntToBytes(signature.s).toList(); + while (sBytes.length < 32) { + sBytes.add(0); + } + final len = privateKey.size; final bytes = Uint8List(len * 2); - bytes.setRange(0, len, bigIntToBytes(signature.r).toList().reversed); - bytes.setRange(len, len * 2, bigIntToBytes(signature.s).toList().reversed); + + bytes.setRange(0, len, rBytes.reversed); + bytes.setRange(len, len * 2, sBytes.reversed); return bytes; } diff --git a/lib/src/crypto_utils.dart b/lib/src/crypto_utils.dart new file mode 100644 index 0000000..64b5daf --- /dev/null +++ b/lib/src/crypto_utils.dart @@ -0,0 +1,1161 @@ +// +// 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<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; + } + + /// + /// 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 = <int>[]; + 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 <https://tools.ietf.org/html/rfc8017#page-53>. + /// + /// ``` + /// 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 <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 = 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 = 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(); + var 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; + var 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 <https://tools.ietf.org/html/rfc5915> + /// + /// ```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 <https://tools.ietf.org/html/rfc5480> + /// + /// ```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 (<https://tools.ietf.org/html/rfc5915>) and PKCS8 (<https://datatracker.ietf.org/doc/html/rfc5208>) + /// + static ECPrivateKey ecPrivateKeyFromDerBytes(Uint8List bytes, + {bool pkcs8 = false}) { + var asn1Parser = ASN1Parser(bytes); + var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence; + var curveName; + var 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; + var 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<RSAPublicKey>(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<RSAPrivateKey>(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<RSAPrivateKey>(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<RSAPublicKey>(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<RSAPublicKey>(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<ECPrivateKey>(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<ECPublicKey>(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 <https://datatracker.ietf.org/doc/html/rfc3447#page-8> + /// + static BigInt osp2i(Iterable<int> 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 <https://datatracker.ietf.org/doc/html/rfc3447#page-8> + /// + 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<RSAPrivateKey>(privateKey), salt)); + + var sig = signer.generateSignature(dataToSign); + + return sig.bytes; + } +} diff --git a/lib/src/errors.dart b/lib/src/errors.dart index ee09602..83189c0 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -49,4 +49,7 @@ class JWTUndefinedError extends JWTError { @override String toString() => 'JWTUndefinedError: $message'; + + @override + StackTrace? get stackTrace => error.stackTrace; } diff --git a/lib/src/keys.dart b/lib/src/keys.dart index 5753272..2889c51 100644 --- a/lib/src/keys.dart +++ b/lib/src/keys.dart @@ -2,7 +2,7 @@ import 'package:pointycastle/pointycastle.dart' as pc; import 'package:ed25519_edwards/ed25519_edwards.dart' as ed; import 'errors.dart'; -import 'parser.dart'; +import 'crypto_utils.dart'; abstract class JWTKey {} @@ -18,9 +18,11 @@ class RSAPrivateKey extends JWTKey { late pc.RSAPrivateKey key; RSAPrivateKey(String pem) { - final _key = parseRSAPrivateKeyPEM(pem); - if (_key == null) throw JWTParseError('RSAPrivateKey is invalid'); - key = _key; + if (pem.startsWith(CryptoUtils.BEGIN_RSA_PRIVATE_KEY)) { + key = CryptoUtils.rsaPrivateKeyFromPemPkcs1(pem); + } else { + key = CryptoUtils.rsaPrivateKeyFromPem(pem); + } } RSAPrivateKey.raw(pc.RSAPrivateKey _key) : key = _key; @@ -32,9 +34,11 @@ class RSAPublicKey extends JWTKey { late pc.RSAPublicKey key; RSAPublicKey(String pem) { - final _key = parseRSAPublicKeyPEM(pem); - if (_key == null) throw JWTParseError('RSAPublicKey is invalid'); - key = _key; + if (pem.startsWith(CryptoUtils.BEGIN_RSA_PUBLIC_KEY)) { + key = CryptoUtils.rsaPublicKeyFromPemPkcs1(pem); + } else { + key = CryptoUtils.rsaPublicKeyFromPem(pem); + } } RSAPublicKey.raw(pc.RSAPublicKey _key) : key = _key; @@ -47,10 +51,9 @@ class ECPrivateKey extends JWTKey { late int size; ECPrivateKey(String pem) { - final _key = parseECPrivateKeyPEM(pem); - final _params = _key?.parameters; + final _key = CryptoUtils.ecPrivateKeyFromPem(pem); + final _params = _key.parameters; - if (_key == null) throw JWTParseError('ECPrivateKey is invalid'); if (_params == null) { throw JWTParseError('ECPrivateKey parameters are invalid'); } @@ -79,9 +82,7 @@ class ECPublicKey extends JWTKey { late pc.ECPublicKey key; ECPublicKey(String pem) { - final _key = parseECPublicKeyPEM(pem); - if (_key == null) throw JWTParseError('ECPublicKey is invalid'); - key = _key; + key = CryptoUtils.ecPublicKeyFromPem(pem); } ECPublicKey.raw(pc.ECPublicKey _key) : key = _key; diff --git a/lib/src/parser.dart b/lib/src/parser.dart deleted file mode 100644 index 1301e94..0000000 --- a/lib/src/parser.dart +++ /dev/null @@ -1,326 +0,0 @@ -import 'dart:typed_data'; -import 'dart:convert'; - -import 'package:pointycastle/pointycastle.dart'; -import 'package:pointycastle/ecc/ecc_fp.dart' as ecc_fp; - -import 'utils.dart'; - -// RSA Private Key -> PKCS#1 format -const String _pkcs1RSAPrivateHeader = '-----BEGIN RSA PRIVATE KEY-----'; -const String _pkcs1RSAPrivateFooter = '-----END RSA PRIVATE KEY-----'; - -// RSA Private Key -> PKCS#8 format -const String _pkcs8RSAPrivateHeader = '-----BEGIN PRIVATE KEY-----'; -const String _pkcs8RSAPrivateFooter = '-----END PRIVATE KEY-----'; - -// RSA Public Key -> PKCS#1 format -const String _pkcs1RSAPublicHeader = '-----BEGIN RSA PUBLIC KEY-----'; -const String _pkcs1RSAPublicFooter = '-----END RSA PUBLIC KEY-----'; - -// RSA Public Key -> PKCS#1 format -const String _pkcs8RSAPublicHeader = '-----BEGIN PUBLIC KEY-----'; -const String _pkcs8RSAPublicFooter = '-----END PUBLIC KEY-----'; - -// ECDSA Private Key -> SEC 1 format -const String _sec1ECPrivateHeader = '-----BEGIN EC PRIVATE KEY-----'; -const String _sec1ECPrivateFooter = '-----END EC PRIVATE KEY-----'; - -// ECDSA Private Key -> PKCS#8 format -const String _pkcs8ECPrivateHeader = '-----BEGIN PRIVATE KEY-----'; -const String _pkcs8ECPrivateFooter = '-----END PRIVATE KEY-----'; - -// ECDSA Public Key -> PKCS#8 format -const String _pkcs8ECPublicHeader = '-----BEGIN PUBLIC KEY-----'; -const String _pkcs8ECPublicFooter = '-----END PUBLIC KEY-----'; - -// ECDSA Curves OID to DomainName -const Map<String, String> _ecCurves = { - '1.2.840.10045.3.1.7': 'prime256v1', // P-256 - '1.3.132.0.10': 'secp256k1', // P-256 - '1.3.132.0.34': 'secp384r1', // P-384 - '1.3.132.0.35': 'secp521r1', // P-512 -}; - -/// RSA Private Key -> PKCS#1 parser -RSAPrivateKey? _pkcs1RSAPrivateKey(Uint8List bytes) { - final parser = ASN1Parser(bytes); - final seq = parser.nextObject() as ASN1Sequence; - final values = seq.elements?.cast<ASN1Integer>(); - - if (values == null) return null; - - final modulus = values[1].integer; - final privateExponent = values[3].integer; - final prime1 = values[4].integer; - final prime2 = values[5].integer; - - if (modulus == null || - privateExponent == null || - prime1 == null || - prime2 == null) return null; - - return RSAPrivateKey( - modulus, - privateExponent, - prime1, - prime2, - ); -} - -/// RSA Private Key -> PKCS#8 parser -RSAPrivateKey? _pkcs8RSAPrivateKey(Uint8List bytes) { - final parser = ASN1Parser(bytes); - final seq = parser.nextObject() as ASN1Sequence; - - final keySeq = seq.elements?[2] as ASN1OctetString?; - if (keySeq == null) return null; - final keyParser = ASN1Parser(keySeq.octets); - - final valuesSeq = keyParser.nextObject() as ASN1Sequence; - final values = valuesSeq.elements?.cast<ASN1Integer>(); - - if (values == null) return null; - - final modulus = values[1].integer; - final privateExponent = values[3].integer; - final prime1 = values[4].integer; - final prime2 = values[5].integer; - - if (modulus == null || - privateExponent == null || - prime1 == null || - prime2 == null) return null; - - return RSAPrivateKey( - modulus, - privateExponent, - prime1, - prime2, - ); -} - -/// RSA Public Key -> PKCS#1 parser -RSAPublicKey? _pkcs1RSAPublicKey(Uint8List bytes) { - final parser = ASN1Parser(bytes); - final seq = parser.nextObject() as ASN1Sequence; - final values = seq.elements?.cast<ASN1Integer>(); - - if (values == null) return null; - - final modulus = values[0].integer; - final publicExponent = values[1].integer; - - if (modulus == null || publicExponent == null) return null; - - return RSAPublicKey( - modulus, - publicExponent, - ); -} - -/// RSA Public Key -> PKCS#8 parser -RSAPublicKey? _pkcs8RSAPublicKey(Uint8List bytes) { - final parser = ASN1Parser(bytes); - final seq = parser.nextObject() as ASN1Sequence; - - final keySeq = seq.elements?[1] as ASN1BitString?; - if (keySeq == null || keySeq.stringValues == null) return null; - final keyParser = ASN1Parser(Uint8List.fromList(keySeq.stringValues!)); - - final valuesSeq = keyParser.nextObject() as ASN1Sequence; - final values = valuesSeq.elements?.cast<ASN1Integer>(); - - if (values == null) return null; - - final modulus = values[0].integer; - final publicExponent = values[1].integer; - - if (modulus == null || publicExponent == null) return null; - - return RSAPublicKey(modulus, publicExponent); -} - -/// ECDSA Private Key -> SEC 1 parser -ECPrivateKey? _sec1ECPrivateKey(Uint8List bytes) { - final parser = ASN1Parser(bytes); - final seq = parser.nextObject() as ASN1Sequence; - - final privateKey = seq.elements?[1] as ASN1OctetString?; - if (privateKey == null) return null; - - final params = seq.elements?[2]; - if (params == null || params.valueBytes == null) return null; - final paramsParser = ASN1Parser(params.valueBytes); - final oid = (paramsParser.nextObject() as ASN1ObjectIdentifier) - .objectIdentifierAsString; - final curve = _ecCurves[oid]; - - if (privateKey.valueBytes == null || curve == null) return null; - - return ECPrivateKey( - decodeBigInt(privateKey.valueBytes!), - ECDomainParameters(curve), - ); -} - -/// ECDSA Private Key -> PKCS#8 parser -ECPrivateKey? _pkcs8ECPrivateKey(Uint8List bytes) { - final parser = ASN1Parser(bytes); - final seq = parser.nextObject() as ASN1Sequence; - if (seq.elements == null) return null; - - final oidSeq = seq.elements?[1] as ASN1Sequence?; - if (oidSeq == null || oidSeq.elements == null) return null; - final oid = - (oidSeq.elements![1] as ASN1ObjectIdentifier).objectIdentifierAsString; - final curve = _ecCurves[oid]; - - final privateKeyParser = ASN1Parser(seq.elements![2].valueBytes); - final privateKeySeq = privateKeyParser.nextObject() as ASN1Sequence; - if (privateKeySeq.elements == null) return null; - final privateKey = (privateKeySeq.elements![1] as ASN1OctetString); - - if (privateKey.valueBytes == null || curve == null) return null; - - return ECPrivateKey( - decodeBigInt(privateKey.valueBytes!), - ECDomainParameters(curve), - ); -} - -/// ECDSA Public Key -> PKCS#8 parser -ECPublicKey? _pkcs8ECPublicKey(Uint8List bytes) { - final parser = ASN1Parser(bytes); - final seq = parser.nextObject() as ASN1Sequence; - if (seq.elements == null) return null; - - final oidSeq = seq.elements![0] as ASN1Sequence; - if (oidSeq.elements == null) return null; - final oid = - (oidSeq.elements![1] as ASN1ObjectIdentifier).objectIdentifierAsString; - final curve = _ecCurves[oid]; - - if (curve == null) return null; - - var publicKeyBytes = seq.elements![1].valueBytes; - if (publicKeyBytes == null) return null; - if (publicKeyBytes[0] == 0) { - publicKeyBytes = publicKeyBytes.sublist(1); - } - - final compressed = publicKeyBytes[0] != 4; - final x = publicKeyBytes.sublist(1, (publicKeyBytes.length / 2).round()); - final y = publicKeyBytes.sublist(1 + x.length, publicKeyBytes.length); - final bigX = decodeBigIntWithSign(1, x); - final bigY = decodeBigIntWithSign(1, y); - final params = ECDomainParameters(curve); - - return 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, - ); -} - -/// Parse RSA private key from pem string -RSAPrivateKey? parseRSAPrivateKeyPEM(String pem) { - if (pem.contains(_pkcs1RSAPrivateHeader) && - pem.contains(_pkcs1RSAPrivateFooter)) { - final data = pem - .substring( - pem.indexOf(_pkcs1RSAPrivateHeader) + _pkcs1RSAPrivateHeader.length, - pem.indexOf(_pkcs1RSAPrivateFooter), - ) - .replaceAll(RegExp(r'[\n\r ]'), ''); - - return _pkcs1RSAPrivateKey(base64.decode(data)); - } else if (pem.contains(_pkcs8RSAPrivateHeader) && - pem.contains(_pkcs8RSAPrivateFooter)) { - final data = pem - .substring( - pem.indexOf(_pkcs8RSAPrivateHeader) + _pkcs8RSAPrivateHeader.length, - pem.indexOf(_pkcs8RSAPrivateFooter), - ) - .replaceAll(RegExp(r'[\n\r ]'), ''); - - return _pkcs8RSAPrivateKey(base64.decode(data)); - } else { - return null; - } -} - -/// Parse RSA public key from pem string -RSAPublicKey? parseRSAPublicKeyPEM(String pem) { - if (pem.contains(_pkcs1RSAPublicHeader) && - pem.contains(_pkcs1RSAPublicFooter)) { - final data = pem - .substring( - pem.indexOf(_pkcs1RSAPublicHeader) + _pkcs1RSAPublicHeader.length, - pem.indexOf(_pkcs1RSAPublicFooter), - ) - .replaceAll(RegExp(r'[\n\r ]'), ''); - - return _pkcs1RSAPublicKey(base64.decode(data)); - } else if (pem.contains(_pkcs8RSAPublicHeader) && - pem.contains(_pkcs8RSAPublicFooter)) { - final data = pem - .substring( - pem.indexOf(_pkcs8RSAPublicHeader) + _pkcs8RSAPublicHeader.length, - pem.indexOf(_pkcs8RSAPublicFooter), - ) - .replaceAll(RegExp(r'[\n\r ]'), ''); - - return _pkcs8RSAPublicKey(base64.decode(data)); - } else { - return null; - } -} - -/// Parse ECDSA private key from pem string -ECPrivateKey? parseECPrivateKeyPEM(String pem) { - if (pem.contains(_sec1ECPrivateHeader) && - pem.contains(_sec1ECPrivateFooter)) { - final data = pem - .substring( - pem.indexOf(_sec1ECPrivateHeader) + _sec1ECPrivateHeader.length, - pem.indexOf(_sec1ECPrivateFooter), - ) - .replaceAll(RegExp(r'[\n\r ]'), ''); - - return _sec1ECPrivateKey(base64.decode(data)); - } else if (pem.contains(_pkcs8ECPrivateHeader) && - pem.contains(_pkcs8ECPrivateFooter)) { - final data = pem - .substring( - pem.indexOf(_pkcs8ECPrivateHeader) + _pkcs8ECPrivateHeader.length, - pem.indexOf(_pkcs8ECPrivateFooter), - ) - .replaceAll(RegExp(r'[\n\r ]'), ''); - - return _pkcs8ECPrivateKey(base64.decode(data)); - } else { - return null; - } -} - -/// Parse ECDSA public key from pem string -ECPublicKey? parseECPublicKeyPEM(String pem) { - if (pem.contains(_pkcs8ECPublicHeader) && - pem.contains(_pkcs8ECPublicFooter)) { - final data = pem - .substring( - pem.indexOf(_pkcs8ECPublicHeader) + _pkcs8ECPublicHeader.length, - pem.indexOf(_pkcs8ECPublicFooter), - ) - .replaceAll(RegExp(r'[\n\r ]'), ''); - - return _pkcs8ECPublicKey(base64.decode(data)); - } else { - return null; - } -} diff --git a/pubspec.yaml b/pubspec.yaml index d7f7909..b936f57 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.6.4 +version: 2.7.0 repository: https://github.com/jonasroussel/dart_jsonwebtoken homepage: https://github.com/jonasroussel/dart_jsonwebtoken#readme