From 333df1f3c704f5fc3389e76078ad55f7ea138f6a Mon Sep 17 00:00:00 2001 From: Sarah Otto Date: Thu, 7 Mar 2024 11:44:34 +0100 Subject: [PATCH] add support for OKP-Keys and Ed25519 Signature --- lib/crypto_keys.dart | 2 + lib/src/algorithms.dart | 18 ++++ lib/src/asymmetric_operator.dart | 39 +++++++- lib/src/impl.dart | 49 +++++++++- lib/src/keys.dart | 31 +++++++ lib/src/okp_keys.dart | 27 ++++++ lib/src/pointycastle_ext.dart | 63 +++++++++++++ pubspec.yaml | 1 + test/crypto_keys_test.dart | 154 ++++++++++++++++++++++++++++++- 9 files changed, 377 insertions(+), 7 deletions(-) create mode 100644 lib/src/okp_keys.dart diff --git a/lib/crypto_keys.dart b/lib/crypto_keys.dart index 3a39058..5dac432 100644 --- a/lib/crypto_keys.dart +++ b/lib/crypto_keys.dart @@ -3,6 +3,7 @@ library crypto_keys; import 'dart:convert'; import 'dart:typed_data'; +import 'package:ed25519_edwards/ed25519_edwards.dart' as ed; import 'package:pointycastle/export.dart' as pc; import 'src/algorithms.dart'; @@ -15,6 +16,7 @@ export 'src/algorithms.dart' part 'src/asymmetric_operator.dart'; part 'src/ec_keys.dart'; part 'src/keys.dart'; +part 'src/okp_keys.dart'; part 'src/operator.dart'; part 'src/rsa_keys.dart'; part 'src/symmetric_keys.dart'; diff --git a/lib/src/algorithms.dart b/lib/src/algorithms.dart index a851ed6..e3d4723 100644 --- a/lib/src/algorithms.dart +++ b/lib/src/algorithms.dart @@ -171,6 +171,9 @@ class _SigAlgorithms extends Identifier { /// Contains the identifiers for supported ECDSA signing algorithms final ecdsa = _EcdsaSigAlgorithms(); + /// Contains Identifier for supported edDSA signing algorithms + final eddsa = AlgorithmIdentifier._('sig/edDSA', () => pce.EdDSASigner()); + _SigAlgorithms() : super._('sig'); } @@ -253,6 +256,18 @@ class _Curves { /// P-256K final p256k = const Identifier._('curve/P-256K'); + + /// Ed25519 + final ed25519 = const Identifier._('curve/Ed25519'); + + /// X25519 + final x25519 = const Identifier._('curve/X25519'); + + /// Ed448 + final ed448 = const Identifier._('curve/Ed448'); + + /// X448 + final x448 = const Identifier._('curve/X448'); } /// An identifier for uniquely identify algorithms and other objects @@ -306,6 +321,9 @@ class AlgorithmIdentifier extends Identifier { /// ECDSA using P-521 and SHA-512 'ES512': algorithms.signing.ecdsa.sha512, + /// EdDSA using Ed25519 or Ed448 with SHA-512 + 'EdDSA': algorithms.signing.eddsa, + /// RSASSA-PSS using SHA-256 and MGF1 with SHA-256 'PS256': null, diff --git a/lib/src/asymmetric_operator.dart b/lib/src/asymmetric_operator.dart index b2fbcc9..cdce0c6 100644 --- a/lib/src/asymmetric_operator.dart +++ b/lib/src/asymmetric_operator.dart @@ -13,7 +13,7 @@ abstract class _AsymmetricOperator implements Operator { case 'P-521': return pc.ECCurve_secp521r1(); } - throw ArgumentError('Unknwon curve type $name'); + throw ArgumentError('Unknown curve type $name'); } pc.ECDomainParameters get ecDomainParameters => @@ -35,6 +35,26 @@ abstract class _AsymmetricOperator implements Operator { k.exponent, )); } + if (key is OkpPublicKey) { + var k = key as OkpPublicKey; + + if (k.curve == curves.ed25519) { + return pc.PublicKeyParameter( + pc.Ed25519PublicKey(k.okpPublicKey)); + } else { + throw UnsupportedError('${k.curve} is not supported for signing now'); + } + } + if (key is OkpPrivateKey) { + var k = key as OkpPrivateKey; + + if (k.curve == curves.ed25519) { + return pc.PrivateKeyParameter( + pc.Ed25519PrivateKey(k.okpPrivateKey)); + } else { + throw UnsupportedError('${k.curve} is not supported for signing now'); + } + } var d = ecDomainParameters; if (key is EcPrivateKey) { @@ -65,9 +85,19 @@ class _AsymmetricSigner extends Signer @override Signature sign(List data) { data = data is Uint8List ? data : Uint8List.fromList(data); + + if (key is OkpKey) { + if (_algorithm.algorithmName != 'EdDSA') { + throw ArgumentError( + '${_algorithm.algorithmName} cannot be used with this key.'); + } + _algorithm.init(true, pc.Ed25519CipherParameters(keyParameter)); + return Signature( + (_algorithm.generateSignature(data) as pc.EdSignature).bytes); + } + _algorithm.init( true, pc.ParametersWithRandom(keyParameter, DefaultSecureRandom())); - if (key is RsaKey) { return Signature( (_algorithm.generateSignature(data) as pc.RSASignature).bytes); @@ -89,6 +119,7 @@ class _AsymmetricSigner extends Signer return Signature(bytes); } + throw UnsupportedError('Unknown key type $key'); } } @@ -125,6 +156,10 @@ class _AsymmetricVerifier extends Verifier _bigIntFromBytes(signature.data.skip(l)), )); } + if (key is OkpKey) { + _algorithm.init(false, pc.Ed25519CipherParameters(keyParameter)); + return _algorithm.verifySignature(data, pc.EdSignature(signature.data)); + } throw UnsupportedError('Unknown key type $key'); } } diff --git a/lib/src/impl.dart b/lib/src/impl.dart index af8ff8d..caa6798 100644 --- a/lib/src/impl.dart +++ b/lib/src/impl.dart @@ -1,9 +1,10 @@ -import '../crypto_keys.dart'; - import 'dart:typed_data'; + import 'package:collection/collection.dart'; import 'package:quiver/core.dart'; +import '../crypto_keys.dart'; + class RsaPublicKeyImpl extends PublicKey with Key implements RsaPublicKey, RsaKey { @@ -87,6 +88,28 @@ class EcPublicKeyImpl extends PublicKey with Key implements EcPublicKey, EcKey { other.curve == curve); } +class OkpPublicKeyImpl extends PublicKey + with Key + implements OkpPublicKey, OkpKey { + @override + final Identifier curve; + + @override + final Uint8List okpPublicKey; + + OkpPublicKeyImpl({required this.okpPublicKey, required this.curve}); + + @override + int get hashCode => hash2(okpPublicKey, curve); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is OkpPublicKey && + other.okpPublicKey == okpPublicKey && + other.curve == curve); +} + class EcPrivateKeyImpl extends PrivateKey with Key implements EcPrivateKey, EcKey { @@ -109,6 +132,28 @@ class EcPrivateKeyImpl extends PrivateKey other.curve == curve); } +class OkpPrivateKeyImpl extends PrivateKey + with Key + implements OkpPrivateKey, OkpKey { + @override + final Identifier curve; + + @override + final Uint8List okpPrivateKey; + + OkpPrivateKeyImpl({required this.okpPrivateKey, required this.curve}); + + @override + int get hashCode => hash2(okpPrivateKey, curve); + + @override + bool operator ==(Object other) => + identical(this, other) || + (other is EcPrivateKey && + other.eccPrivateKey == okpPrivateKey && + other.curve == curve); +} + class SymmetricKeyImpl extends Object with Key, PublicKey, PrivateKey implements SymmetricKey { diff --git a/lib/src/keys.dart b/lib/src/keys.dart index 25e6233..a87f69b 100644 --- a/lib/src/keys.dart +++ b/lib/src/keys.dart @@ -103,6 +103,21 @@ class KeyPair { curve: curve)); } + factory KeyPair.generateOkp(Identifier curve) { + if (curve == curves.ed25519) { + var key = ed.generateKey(); + + return KeyPair( + publicKey: OkpPublicKey( + curve: curve, + okpPublicKey: Uint8List.fromList(key.publicKey.bytes)), + privateKey: OkpPrivateKey( + curve: curve, okpPrivateKey: ed.seed(key.privateKey))); + } else { + throw UnsupportedError('Curve ${curve.name} is not supported'); + } + } + /// Create a key pair from a JsonWebKey factory KeyPair.fromJwk(Map jwk) { switch (jwk['kty']) { @@ -143,6 +158,18 @@ class KeyPair { yCoordinate: _base64ToInt(jwk['y']), curve: _parseCurve(jwk['crv'])) : null); + case 'OKP': + return KeyPair( + privateKey: jwk.containsKey('d') && jwk.containsKey('crv') + ? OkpPrivateKey( + okpPrivateKey: Uint8List.fromList(_base64ToBytes(jwk['d'])), + curve: _parseCurve(jwk['crv'])) + : null, + publicKey: jwk.containsKey('x') && jwk.containsKey('crv') + ? OkpPublicKey( + okpPublicKey: Uint8List.fromList(_base64ToBytes(jwk['x'])), + curve: _parseCurve(jwk['crv'])) + : null); } throw ArgumentError('Unknown key type ${jwk['kty']}'); } @@ -182,6 +209,10 @@ Identifier _parseCurve(String name) { 'P-256K': curves.p256k, 'P-384': curves.p384, 'P-521': curves.p521, + 'Ed25519': curves.ed25519, + 'X25519': curves.x25519, + 'Ed448': curves.ed448, + 'X448': curves.x448 }[name]; if (v == null) { throw UnsupportedError('Unknown curve $name'); diff --git a/lib/src/okp_keys.dart b/lib/src/okp_keys.dart new file mode 100644 index 0000000..2aebacb --- /dev/null +++ b/lib/src/okp_keys.dart @@ -0,0 +1,27 @@ +part of '../crypto_keys.dart'; + +/// Base class for Octet Key Pairs (OKP-Keys) +abstract class OkpKey extends Key { + /// The cryptographic curve used with the key + Identifier get curve; +} + +/// An OKP public key +abstract class OkpPublicKey extends OkpKey implements PublicKey { + /// The public key value + Uint8List get okpPublicKey; + + factory OkpPublicKey( + {required Uint8List okpPublicKey, + required Identifier curve}) = OkpPublicKeyImpl; +} + +/// An OKP private key +abstract class OkpPrivateKey extends OkpKey implements PrivateKey { + /// The OKP private key value + Uint8List get okpPrivateKey; + + factory OkpPrivateKey( + {required Uint8List okpPrivateKey, + required Identifier curve}) = OkpPrivateKeyImpl; +} diff --git a/lib/src/pointycastle_ext.dart b/lib/src/pointycastle_ext.dart index a102cf9..8ac2320 100644 --- a/lib/src/pointycastle_ext.dart +++ b/lib/src/pointycastle_ext.dart @@ -1,5 +1,6 @@ import 'dart:typed_data'; +import 'package:ed25519_edwards/ed25519_edwards.dart' as ed; import 'package:pointycastle/export.dart'; class ParametersWithIVAndAad @@ -283,3 +284,65 @@ class AESKeyWrap implements BlockCipher { throw UnsupportedError('Should not be called.'); } } + +class EdDSASigner implements Signer { + Uint8List? _key; + @override + String get algorithmName => 'EdDSA'; + + EdDSASigner(); + + @override + EdSignature generateSignature(Uint8List message) { + var private = ed.newKeyFromSeed(_key!); + return EdSignature(ed.sign(private, message)); + } + + @override + void init(bool forSigning, CipherParameters params) { + if (params is! Ed25519CipherParameters) { + throw ArgumentError('Invalid CipherParameters'); + } + if (forSigning && params.key is! PrivateKey) { + throw ArgumentError('Private Key needed for signing'); + } else { + if (params.key is PrivateKey) { + _key = (params.key as Ed25519PrivateKey).privateKey; + } else { + _key = (params.key as Ed25519PublicKey).publicKey; + } + } + } + + @override + void reset() {} + + @override + bool verifySignature(Uint8List message, Signature signature) { + if (signature is! EdSignature) { + throw ArgumentError('Invalid signature'); + } + return ed.verify(ed.PublicKey(_key!), message, signature.bytes); + } +} + +class Ed25519CipherParameters extends AsymmetricKeyParameter { + Ed25519CipherParameters(AsymmetricKeyParameter params) : super(params.key); +} + +class Ed25519PublicKey extends AsymmetricKey implements PublicKey { + Uint8List publicKey; + Ed25519PublicKey(this.publicKey); +} + +class Ed25519PrivateKey extends AsymmetricKey implements PrivateKey { + Uint8List privateKey; + + Ed25519PrivateKey(this.privateKey); +} + +class EdSignature implements Signature { + Uint8List bytes; + + EdSignature(this.bytes); +} diff --git a/pubspec.yaml b/pubspec.yaml index f696c6d..9b2b003 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: meta: ^1.3.0 collection: ^1.14.13 quiver: ^3.0.0 + ed25519_edwards: ^0.3.1 dev_dependencies: test: ^1.16.2 diff --git a/test/crypto_keys_test.dart b/test/crypto_keys_test.dart index 0703601..6dbca3a 100644 --- a/test/crypto_keys_test.dart +++ b/test/crypto_keys_test.dart @@ -1,7 +1,8 @@ -import 'package:test/test.dart'; -import 'package:crypto_keys/crypto_keys.dart'; -import 'dart:typed_data'; import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:crypto_keys/crypto_keys.dart'; +import 'package:test/test.dart'; void _testSigning( KeyPair keyPair, AlgorithmIdentifier algorithm, Uint8List data, @@ -1108,6 +1109,153 @@ void main() { } }); }); + + group('EdDsa Signing', () { + test('Example Signing Using EdDSA with ED25519', () { + var jwk = { + 'kty': 'OKP', + 'crv': 'Ed25519', + 'd': 'nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A', + 'x': '11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo' + }; + var keyPair = KeyPair.fromJwk(jwk); + + var data = Uint8List.fromList([ + 101, + 121, + 74, + 104, + 98, + 71, + 99, + 105, + 79, + 105, + 74, + 70, + 90, + 69, + 82, + 84, + 81, + 83, + 74, + 57, + 46, + 82, + 88, + 104, + 104, + 98, + 88, + 66, + 115, + 90, + 83, + 66, + 118, + 90, + 105, + 66, + 70, + 90, + 68, + 73, + 49, + 78, + 84, + 69, + 53, + 73, + 72, + 78, + 112, + 90, + 50, + 53, + 112, + 98, + 109, + 99 + ]); + var signature = Uint8List.fromList([ + 134, + 12, + 152, + 210, + 41, + 127, + 48, + 96, + 163, + 63, + 66, + 115, + 150, + 114, + 214, + 27, + 83, + 207, + 58, + 222, + 254, + 211, + 211, + 198, + 114, + 243, + 32, + 220, + 2, + 27, + 65, + 30, + 157, + 89, + 184, + 98, + 141, + 195, + 81, + 226, + 72, + 184, + 139, + 41, + 70, + 142, + 14, + 65, + 133, + 91, + 15, + 183, + 216, + 59, + 177, + 91, + 233, + 2, + 191, + 204, + 184, + 205, + 10, + 2 + ]); + _testSigning(keyPair, algorithms.signing.eddsa, data, signature, false); + }); + + test('Example Signing Using EdDSA with ED25519 generated Keys', () { + var text = 'Can we sign and verify this text'; + var data = utf8.encode(text); + + var keyPair = KeyPair.generateOkp(curves.ed25519); + + _testSigning(keyPair, algorithms.signing.eddsa, data); + }); + }); }); group('Encryption', () {