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