From 0c24fa3a1399b711b8ce8e85cca8c143a0a203bd Mon Sep 17 00:00:00 2001 From: Murali Date: Wed, 8 Jan 2025 15:20:11 +0530 Subject: [PATCH 1/4] feat: added rsa4096 and ed25519 implementation --- .../lib/src/algorithm/at_algorithm.dart | 9 ++-- .../src/algorithm/ed25519_signing_algo.dart | 45 +++++++++++++++++ packages/at_chops/lib/src/at_chops_impl.dart | 6 +-- packages/at_chops/lib/src/key/key_names.dart | 2 + .../at_chops/lib/src/util/at_chops_util.dart | 8 +++ packages/at_chops/pubspec.yaml | 3 +- .../test/ed25519_signing_algo_test.dart | 50 +++++++++++++++++++ .../test/rsa_encryption_algo_test.dart | 24 +++++++++ 8 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 packages/at_chops/lib/src/algorithm/ed25519_signing_algo.dart create mode 100644 packages/at_chops/test/ed25519_signing_algo_test.dart diff --git a/packages/at_chops/lib/src/algorithm/at_algorithm.dart b/packages/at_chops/lib/src/algorithm/at_algorithm.dart index f41a6d71..8da00cdd 100644 --- a/packages/at_chops/lib/src/algorithm/at_algorithm.dart +++ b/packages/at_chops/lib/src/algorithm/at_algorithm.dart @@ -45,11 +45,12 @@ abstract class ASymmetricEncryptionAlgorithm /// Interface for data signing. Data is signed using private key from a key pair /// Signed data signature is verified with public key of the key pair. abstract class AtSigningAlgorithm { - /// Signs the data using [AtPrivateKey] of [AsymmetricKeyPair] - Uint8List sign(Uint8List data); + /// Signs the data using private key of asymmetric key pair + FutureOr sign(Uint8List data); - /// Verifies the data signature using [AtPublicKey] of [AsymmetricKeyPair] or the passed [publicKey] - bool verify(Uint8List signedData, Uint8List signature, {String? publicKey}); + /// Verifies the data signature using public key of asymmetric key pair or the passed [publicKey] + FutureOr verify(Uint8List signedData, Uint8List signature, + {String? publicKey}); } /// Interface for hashing data. Refer [DefaultHash] for sample implementation. diff --git a/packages/at_chops/lib/src/algorithm/ed25519_signing_algo.dart b/packages/at_chops/lib/src/algorithm/ed25519_signing_algo.dart new file mode 100644 index 00000000..fc3b5328 --- /dev/null +++ b/packages/at_chops/lib/src/algorithm/ed25519_signing_algo.dart @@ -0,0 +1,45 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:at_chops/at_chops.dart'; +import 'package:at_chops/src/algorithm/at_algorithm.dart'; +import 'package:at_commons/at_commons.dart'; +import 'package:better_cryptography/better_cryptography.dart'; + +/// Data signing and verification for Ed25519 - elliptic curve algorithm +/// Keypair for the algorithm has to generated using [AtChopsUtil.generateEd25519KeyPair()] +class Ed25519SigningAlgo implements AtSigningAlgorithm { + final _algorithm = Ed25519(); + SimpleKeyPair? _ed25519KeyPair; + + set edd25519KeyPair(SimpleKeyPair value) { + _ed25519KeyPair = value; + } + + Ed25519SigningAlgo(); + + @override + Future sign(Uint8List data) async { + if (_ed25519KeyPair == null) { + throw AtSigningException( + 'edd25519 key pair has to be set for signing operation'); + } + final signature = await _algorithm.sign(data, keyPair: _ed25519KeyPair!); + return Uint8List.fromList(signature.bytes); + } + + @override + Future verify(Uint8List signedData, Uint8List signature, + {String? publicKey}) async { + if (publicKey == null) { + throw AtSigningException( + 'public key has to be passed for signature verification'); + } + return await _algorithm.verify( + signedData, + signature: Signature(signature, + publicKey: + SimplePublicKey(publicKey.codeUnits, type: KeyPairType.ed25519)), + ); + } +} diff --git a/packages/at_chops/lib/src/at_chops_impl.dart b/packages/at_chops/lib/src/at_chops_impl.dart index 55b18450..ecc297a2 100644 --- a/packages/at_chops/lib/src/at_chops_impl.dart +++ b/packages/at_chops/lib/src/at_chops_impl.dart @@ -168,7 +168,8 @@ class AtChopsImpl extends AtChops { ..atSigningMetaData = atSigningMetadata ..atSigningResultType = AtSigningResultType.bytes; try { - atSigningResult.result = base64Encode(signingAlgorithm.sign(data)); + atSigningResult.result = + base64Encode(signingAlgorithm.sign(data) as List); } on AtSigningException { rethrow; } @@ -210,9 +211,8 @@ class AtChopsImpl extends AtChops { EncryptionKeyType encryptionKeyType, String? keyName) { switch (encryptionKeyType) { case EncryptionKeyType.rsa2048: - return RsaEncryptionAlgo.fromKeyPair(_getEncryptionKeyPair(keyName)!); case EncryptionKeyType.rsa4096: - throw AtEncryptionException('EncryptionKeyType.rsa4096 not supported'); + return RsaEncryptionAlgo.fromKeyPair(_getEncryptionKeyPair(keyName)!); case EncryptionKeyType.ecc: throw AtEncryptionException('EncryptionKeyType.ecc not supported'); case EncryptionKeyType.aes128: diff --git a/packages/at_chops/lib/src/key/key_names.dart b/packages/at_chops/lib/src/key/key_names.dart index 92ddaaae..dbec6349 100644 --- a/packages/at_chops/lib/src/key/key_names.dart +++ b/packages/at_chops/lib/src/key/key_names.dart @@ -1,4 +1,6 @@ class KeyNames { static const String selfEncryptionKey = 'selfEncryptionKey'; static const String apkamSymmetricKey = 'apkamSymmetricKey'; + static const String rsa2048EncKey = 'rsa2048EncKey'; + static const String rsa4096EncKey = 'rsa4096EncKey'; } diff --git a/packages/at_chops/lib/src/util/at_chops_util.dart b/packages/at_chops/lib/src/util/at_chops_util.dart index 5fee6f64..b49d0a68 100644 --- a/packages/at_chops/lib/src/util/at_chops_util.dart +++ b/packages/at_chops/lib/src/util/at_chops_util.dart @@ -5,7 +5,9 @@ import 'package:at_chops/src/key/at_key_pair.dart'; import 'package:at_chops/src/key/impl/aes_key.dart'; import 'package:at_chops/src/key/impl/at_encryption_key_pair.dart'; import 'package:at_chops/src/key/impl/at_pkam_key_pair.dart'; +import 'package:at_chops/src/key/impl/at_signing_key_pair.dart'; import 'package:at_chops/src/key/key_type.dart'; +import 'package:better_cryptography/better_cryptography.dart'; import 'package:crypton/crypton.dart'; import 'package:encrypt/encrypt.dart'; @@ -50,6 +52,12 @@ class AtChopsUtil { return ECKeypair.fromRandom(); } + /// Generates an symmetric keypair for ED25519 elliptic curve signing and verification + static Future generateEd25519KeyPair() async { + return await Ed25519().newKeyPair(); + } + + /// Generates symmetric AES key based on [keyType] static SymmetricKey generateSymmetricKey(EncryptionKeyType keyType) { switch (keyType) { case EncryptionKeyType.aes128: diff --git a/packages/at_chops/pubspec.yaml b/packages/at_chops/pubspec.yaml index 2999b53a..efed060c 100644 --- a/packages/at_chops/pubspec.yaml +++ b/packages/at_chops/pubspec.yaml @@ -1,6 +1,6 @@ name: at_chops description: Package for at_protocol cryptographic and hashing operations -version: 2.2.0 +version: 3.0.0 repository: https://github.com/atsign-foundation/at_libraries environment: @@ -18,6 +18,7 @@ dependencies: at_commons: ^5.0.2 at_utils: ^3.0.19 cryptography: ^2.7.0 + better_cryptography: ^1.0.0+1 dev_dependencies: lints: ^5.0.0 diff --git a/packages/at_chops/test/ed25519_signing_algo_test.dart b/packages/at_chops/test/ed25519_signing_algo_test.dart new file mode 100644 index 00000000..d108b6d2 --- /dev/null +++ b/packages/at_chops/test/ed25519_signing_algo_test.dart @@ -0,0 +1,50 @@ +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:at_chops/at_chops.dart'; +import 'package:at_chops/src/algorithm/ed25519_signing_algo.dart'; +import 'package:test/test.dart'; + +void main() { + group('A group of tests for ed25519 signing and verification', () { + test('Test data signing and verification using generated keypair', + () async { + final ed25519KeyPair = await AtChopsUtil.generateEd25519KeyPair(); + final dataToSign = 'Hello World@123!'; + final signingAlgo = Ed25519SigningAlgo(); + signingAlgo.edd25519KeyPair = ed25519KeyPair; + final signature = + await signingAlgo.sign(Uint8List.fromList(dataToSign.codeUnits)); + final publicKeyBytes = (await ed25519KeyPair.extractPublicKey()).bytes; + print(publicKeyBytes.length); + final verifyResult = await signingAlgo.verify( + Uint8List.fromList(dataToSign.codeUnits), signature, + publicKey: String.fromCharCodes(publicKeyBytes)); + expect(verifyResult, true); + }); + test('Test data signing and verification - pass incorrect public key', + () async { + final ed25519KeyPair = await AtChopsUtil.generateEd25519KeyPair(); + final dataToSign = 'Hello World@123!'; + final signingAlgo = Ed25519SigningAlgo(); + signingAlgo.edd25519KeyPair = ed25519KeyPair; + final signature = + await signingAlgo.sign(Uint8List.fromList(dataToSign.codeUnits)); + final publicKeyBytes = (await ed25519KeyPair.extractPublicKey()).bytes; + print(publicKeyBytes.length); + const characters = + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + final random = Random(); + final wrongPublicKey = String.fromCharCodes( + Iterable.generate( + 32, + (_) => characters.codeUnitAt(random.nextInt(characters.length)), + ), + ); + final verifyResult = await signingAlgo.verify( + Uint8List.fromList(dataToSign.codeUnits), signature, + publicKey: wrongPublicKey); + expect(verifyResult, false); + }); + }); +} diff --git a/packages/at_chops/test/rsa_encryption_algo_test.dart b/packages/at_chops/test/rsa_encryption_algo_test.dart index 2a51bab0..b9b45ae4 100644 --- a/packages/at_chops/test/rsa_encryption_algo_test.dart +++ b/packages/at_chops/test/rsa_encryption_algo_test.dart @@ -21,6 +21,20 @@ void main() { var decryptedData = defaultEncryptionAlgo.decrypt(encryptedData); expect(utf8.decode(decryptedData), dataToEncrypt); }); + test('Test asymmetric encryption/decryption using rsa 4096', () { + var defaultEncryptionAlgo = RsaEncryptionAlgo(); + var rsa2048KeyPair = + AtChopsUtil.generateAtEncryptionKeyPair(keySize: 4096); + var rsaPublicKey = rsa2048KeyPair.atPublicKey; + var dataToEncrypt = 'Hello World12!@'; + defaultEncryptionAlgo.atPublicKey = rsaPublicKey; + var encryptedData = + defaultEncryptionAlgo.encrypt(utf8.encode(dataToEncrypt)); + var rsaPrivateKey = rsa2048KeyPair.atPrivateKey; + defaultEncryptionAlgo.atPrivateKey = rsaPrivateKey; + var decryptedData = defaultEncryptionAlgo.decrypt(encryptedData); + expect(utf8.decode(decryptedData), dataToEncrypt); + }); test('Test encrypt throws exception when passed public key is null', () { var defaultEncryptionAlgo = RsaEncryptionAlgo(); var dataToEncrypt = 'Hello World12!@'; @@ -57,6 +71,16 @@ void main() { var decryptedData = defaultEncryptionAlgo.decrypt(encryptedData); expect(utf8.decode(decryptedData), dataToEncrypt); }); + test('Test asymmetric encryption/decryption using rsa 4096 key pair', () { + var rsa2048KeyPair = + AtChopsUtil.generateAtEncryptionKeyPair(keySize: 4096); + var defaultEncryptionAlgo = RsaEncryptionAlgo.fromKeyPair(rsa2048KeyPair); + var dataToEncrypt = 'Hello World12!@'; + var encryptedData = + defaultEncryptionAlgo.encrypt(utf8.encode(dataToEncrypt)); + var decryptedData = defaultEncryptionAlgo.decrypt(encryptedData); + expect(utf8.decode(decryptedData), dataToEncrypt); + }); test('Test encrypt throws exception when encryption keypair is null', () { var defaultEncryptionAlgo = RsaEncryptionAlgo.fromKeyPair(null); var dataToEncrypt = 'Hello World12!@'; From 06636ffdb699bd4ebe7a5d7d22754c6f78a97fd6 Mon Sep 17 00:00:00 2001 From: Murali Date: Wed, 8 Jan 2025 21:06:37 +0530 Subject: [PATCH 2/4] fix: analyzer issue --- packages/at_chops/lib/src/algorithm/at_algorithm.dart | 1 - packages/at_chops/lib/src/algorithm/ed25519_signing_algo.dart | 1 - packages/at_chops/lib/src/util/at_chops_util.dart | 1 - 3 files changed, 3 deletions(-) diff --git a/packages/at_chops/lib/src/algorithm/at_algorithm.dart b/packages/at_chops/lib/src/algorithm/at_algorithm.dart index 8da00cdd..aacc76cd 100644 --- a/packages/at_chops/lib/src/algorithm/at_algorithm.dart +++ b/packages/at_chops/lib/src/algorithm/at_algorithm.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:at_chops/src/algorithm/at_iv.dart'; -import 'package:at_chops/src/key/at_key_pair.dart'; import 'package:at_chops/src/key/at_private_key.dart'; import 'package:at_chops/src/key/at_public_key.dart'; import 'package:at_chops/src/model/hash_params.dart'; diff --git a/packages/at_chops/lib/src/algorithm/ed25519_signing_algo.dart b/packages/at_chops/lib/src/algorithm/ed25519_signing_algo.dart index fc3b5328..9f15b1b9 100644 --- a/packages/at_chops/lib/src/algorithm/ed25519_signing_algo.dart +++ b/packages/at_chops/lib/src/algorithm/ed25519_signing_algo.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:typed_data'; -import 'package:at_chops/at_chops.dart'; import 'package:at_chops/src/algorithm/at_algorithm.dart'; import 'package:at_commons/at_commons.dart'; import 'package:better_cryptography/better_cryptography.dart'; diff --git a/packages/at_chops/lib/src/util/at_chops_util.dart b/packages/at_chops/lib/src/util/at_chops_util.dart index b49d0a68..84ec7c4d 100644 --- a/packages/at_chops/lib/src/util/at_chops_util.dart +++ b/packages/at_chops/lib/src/util/at_chops_util.dart @@ -5,7 +5,6 @@ import 'package:at_chops/src/key/at_key_pair.dart'; import 'package:at_chops/src/key/impl/aes_key.dart'; import 'package:at_chops/src/key/impl/at_encryption_key_pair.dart'; import 'package:at_chops/src/key/impl/at_pkam_key_pair.dart'; -import 'package:at_chops/src/key/impl/at_signing_key_pair.dart'; import 'package:at_chops/src/key/key_type.dart'; import 'package:better_cryptography/better_cryptography.dart'; import 'package:crypton/crypton.dart'; From 8615052f9d81f0fe8209285b1f088fc56612849a Mon Sep 17 00:00:00 2001 From: Murali Date: Thu, 9 Jan 2025 10:30:22 +0530 Subject: [PATCH 3/4] fix: added comment --- packages/at_chops/lib/src/at_chops_impl.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/at_chops/lib/src/at_chops_impl.dart b/packages/at_chops/lib/src/at_chops_impl.dart index ecc297a2..395ea3b7 100644 --- a/packages/at_chops/lib/src/at_chops_impl.dart +++ b/packages/at_chops/lib/src/at_chops_impl.dart @@ -229,7 +229,8 @@ class AtChopsImpl extends AtChops { if (keyName == null) { return atChopsKeys.atEncryptionKeyPair!; } - // #TODO plugin implementation for different keyNames + // #TODO For now return atEncryptionKeyPair which can be rsa2048 or rsa4096. + // #TODO When we remove atChopsKeys from AtChopsImpl constructor, plugin implementation for different keyNames return null; } From 21375e87edda9a77e64bf4454dbe2ae4de4fa4e4 Mon Sep 17 00:00:00 2001 From: Murali Date: Wed, 15 Jan 2025 16:00:42 +0530 Subject: [PATCH 4/4] fix: review comments --- packages/at_chops/lib/src/algorithm/ed25519_signing_algo.dart | 4 ++-- packages/at_chops/test/ed25519_signing_algo_test.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/at_chops/lib/src/algorithm/ed25519_signing_algo.dart b/packages/at_chops/lib/src/algorithm/ed25519_signing_algo.dart index 9f15b1b9..e4c4daf4 100644 --- a/packages/at_chops/lib/src/algorithm/ed25519_signing_algo.dart +++ b/packages/at_chops/lib/src/algorithm/ed25519_signing_algo.dart @@ -11,7 +11,7 @@ class Ed25519SigningAlgo implements AtSigningAlgorithm { final _algorithm = Ed25519(); SimpleKeyPair? _ed25519KeyPair; - set edd25519KeyPair(SimpleKeyPair value) { + set ed25519KeyPair(SimpleKeyPair value) { _ed25519KeyPair = value; } @@ -21,7 +21,7 @@ class Ed25519SigningAlgo implements AtSigningAlgorithm { Future sign(Uint8List data) async { if (_ed25519KeyPair == null) { throw AtSigningException( - 'edd25519 key pair has to be set for signing operation'); + 'ed25519 key pair has to be set for signing operation'); } final signature = await _algorithm.sign(data, keyPair: _ed25519KeyPair!); return Uint8List.fromList(signature.bytes); diff --git a/packages/at_chops/test/ed25519_signing_algo_test.dart b/packages/at_chops/test/ed25519_signing_algo_test.dart index d108b6d2..198aae93 100644 --- a/packages/at_chops/test/ed25519_signing_algo_test.dart +++ b/packages/at_chops/test/ed25519_signing_algo_test.dart @@ -12,7 +12,7 @@ void main() { final ed25519KeyPair = await AtChopsUtil.generateEd25519KeyPair(); final dataToSign = 'Hello World@123!'; final signingAlgo = Ed25519SigningAlgo(); - signingAlgo.edd25519KeyPair = ed25519KeyPair; + signingAlgo.ed25519KeyPair = ed25519KeyPair; final signature = await signingAlgo.sign(Uint8List.fromList(dataToSign.codeUnits)); final publicKeyBytes = (await ed25519KeyPair.extractPublicKey()).bytes; @@ -27,7 +27,7 @@ void main() { final ed25519KeyPair = await AtChopsUtil.generateEd25519KeyPair(); final dataToSign = 'Hello World@123!'; final signingAlgo = Ed25519SigningAlgo(); - signingAlgo.edd25519KeyPair = ed25519KeyPair; + signingAlgo.ed25519KeyPair = ed25519KeyPair; final signature = await signingAlgo.sign(Uint8List.fromList(dataToSign.codeUnits)); final publicKeyBytes = (await ed25519KeyPair.extractPublicKey()).bytes;