diff --git a/packages/at_secondary_server/CHANGELOG.md b/packages/at_secondary_server/CHANGELOG.md index 58e639b7c..2b155b113 100644 --- a/packages/at_secondary_server/CHANGELOG.md +++ b/packages/at_secondary_server/CHANGELOG.md @@ -1,3 +1,5 @@ +# 3.1.1 +- fix: Store "publicKeyHash" value in the keystore # 3.1.0 - feat: sync skip deletes until changes - fix: Enable persistence of the Initialization Vector for "defaultEncryptionPrivateKey" and "selfEncryptionKey" in diff --git a/packages/at_secondary_server/lib/src/notification/resource_manager.dart b/packages/at_secondary_server/lib/src/notification/resource_manager.dart index 43e018780..0487a13a5 100644 --- a/packages/at_secondary_server/lib/src/notification/resource_manager.dart +++ b/packages/at_secondary_server/lib/src/notification/resource_manager.dart @@ -227,6 +227,10 @@ class ResourceManager { commandBody = '${AtConstants.encryptingKeyName}:${atNotification.atMetadata!.encKeyName}:$commandBody'; } + if (atNotification.atMetadata!.pubKeyHash != null) { + commandBody = + '${AtConstants.sharedWithPublicKeyHash}:${atNotification.atMetadata!.pubKeyHash?.hash}:${AtConstants.sharedWithPublicKeyHashingAlgo}:${atNotification.atMetadata!.pubKeyHash?.hashingAlgo}:$commandBody'; + } if (atNotification.atMetadata!.pubKeyCS != null) { commandBody = '${AtConstants.sharedWithPublicKeyCheckSum}:${atNotification.atMetadata!.pubKeyCS}:$commandBody'; diff --git a/packages/at_secondary_server/lib/src/verb/handler/abstract_update_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/abstract_update_verb_handler.dart index d3aade830..1e53d6aeb 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/abstract_update_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/abstract_update_verb_handler.dart @@ -191,6 +191,13 @@ abstract class AbstractUpdateVerbHandler extends ChangeVerbHandler { verbParams[AtConstants.sharedKeyEncryptedEncryptingKeyName]; metadata.skeEncAlgo = verbParams[AtConstants.sharedKeyEncryptedEncryptingAlgo]; + if (verbParams[AtConstants.sharedWithPublicKeyHash].isNotNullOrEmpty && + verbParams[AtConstants.sharedWithPublicKeyHashingAlgo] + .isNotNullOrEmpty) { + metadata.pubKeyHash = PublicKeyHash( + verbParams[AtConstants.sharedWithPublicKeyHash]!, + verbParams[AtConstants.sharedWithPublicKeyHashingAlgo]!); + } updateParams.metadata = metadata; return updateParams; diff --git a/packages/at_secondary_server/lib/src/verb/handler/monitor_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/monitor_verb_handler.dart index 5d77a768f..3f79b7b82 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/monitor_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/monitor_verb_handler.dart @@ -138,6 +138,8 @@ class MonitorVerbHandler extends AbstractVerbHandler { "skeEncKeyName": atNotification.atMetadata?.skeEncKeyName, "skeEncAlgo": atNotification.atMetadata?.skeEncAlgo, "sharedKeyEnc": atNotification.atMetadata?.sharedKeyEnc, + "pubKeyHash": + jsonEncode(atNotification.atMetadata?.pubKeyHash?.toJson()) }; await _checkAndSend(notification); @@ -222,7 +224,8 @@ class Notification { "availableAt": atNotification.atMetadata?.availableAt.toString(), "expiresAt": (atNotification.atMetadata?.expiresAt ?? atNotification.expiresAt) - .toString() + .toString(), + 'pubKeyHash': jsonEncode(atNotification.atMetadata?.pubKeyHash?.toJson()) }; } diff --git a/packages/at_secondary_server/lib/src/verb/handler/notify_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/notify_verb_handler.dart index 2f7e10075..15fc6d1ca 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/notify_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/notify_verb_handler.dart @@ -358,6 +358,13 @@ class NotifyVerbHandler extends AbstractVerbHandler { if (verbParams[AtConstants.sharedWithPublicKeyCheckSum] != null) { atMetadata.pubKeyCS = verbParams[AtConstants.sharedWithPublicKeyCheckSum]; } + if (verbParams[AtConstants.sharedWithPublicKeyHash].isNotNullOrEmpty && + verbParams[AtConstants.sharedWithPublicKeyHashingAlgo] + .isNotNullOrEmpty) { + atMetadata.pubKeyHash = PublicKeyHash( + verbParams[AtConstants.sharedWithPublicKeyHash]!, + verbParams[AtConstants.sharedWithPublicKeyHashingAlgo]!); + } if (verbParams[AtConstants.encryptingKeyName] != null) { atMetadata.encKeyName = verbParams[AtConstants.encryptingKeyName]; } diff --git a/packages/at_secondary_server/lib/src/verb/handler/sync_progressive_verb_handler.dart b/packages/at_secondary_server/lib/src/verb/handler/sync_progressive_verb_handler.dart index 461b06708..d6bde3b40 100644 --- a/packages/at_secondary_server/lib/src/verb/handler/sync_progressive_verb_handler.dart +++ b/packages/at_secondary_server/lib/src/verb/handler/sync_progressive_verb_handler.dart @@ -153,11 +153,24 @@ class SyncProgressiveVerbHandler extends AbstractVerbHandler { if (metaData == null) { return metaDataMap; } - metaData.toJson().forEach((key, value) { - if (value != null) { - metaDataMap[key] = value.toString(); + Iterator itr = metaData.toJson().entries.iterator; + while (itr.moveNext()) { + // The value of [AtConstants.sharedWithPublicKeyHash] stores a Map containing + // the hash value and the hashing algorithm used for hashing the data. + // For example, {"hash":"dummy_value", "hashingAlgo":"sha512"}. + // Using toString() will not allow convert this into a Map, which is necessary + // for constructing the PublicKeyHash type on the client side. + // Therefore, a JSON-encoded string is used here, and on the client side, + // "jsonDecode" will be used to retrieve the Map and build the PublicKeyHash instance. + if (itr.current.key == AtConstants.sharedWithPublicKeyHash && + itr.current.value != null) { + metaDataMap[itr.current.key] = jsonEncode(itr.current.value); + continue; + } + if (itr.current.value != null) { + metaDataMap[itr.current.key] = itr.current.value.toString(); } - }); + } return metaDataMap; } diff --git a/packages/at_secondary_server/pubspec.yaml b/packages/at_secondary_server/pubspec.yaml index fc4553d6b..1aa03443d 100644 --- a/packages/at_secondary_server/pubspec.yaml +++ b/packages/at_secondary_server/pubspec.yaml @@ -1,6 +1,6 @@ name: at_secondary description: Implementation of secondary server. -version: 3.1.0 +version: 3.1.1 repository: https://github.com/atsign-foundation/at_server homepage: https://www.example.com publish_to: none diff --git a/packages/at_secondary_server/test/sync_verb_test.dart b/packages/at_secondary_server/test/sync_verb_test.dart index bbae7fc34..0b1e9fc9f 100644 --- a/packages/at_secondary_server/test/sync_verb_test.dart +++ b/packages/at_secondary_server/test/sync_verb_test.dart @@ -2,6 +2,7 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:io'; +import 'package:at_chops/at_chops.dart'; import 'package:at_commons/at_commons.dart'; import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart'; import 'package:at_secondary/src/connection/inbound/inbound_connection_impl.dart'; @@ -10,8 +11,8 @@ import 'package:at_secondary/src/utils/handler_util.dart'; import 'package:at_secondary/src/utils/secondary_util.dart'; import 'package:at_secondary/src/verb/handler/sync_progressive_verb_handler.dart'; import 'package:at_server_spec/at_verb_spec.dart'; -import 'package:test/test.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; import 'test_utils.dart'; @@ -115,7 +116,10 @@ void main() { ..ttb = 1000 ..ttr = 100 ..isBinary = false - ..encoding = 'base64')); + ..encoding = 'base64' + ..pubKeyHash = + PublicKeyHash('dummy_hash', HashingAlgoType.sha512.name) + ..pubKeyCS = 'dummy_pub_key_cs')); verbHandler = SyncProgressiveVerbHandler(keyStoreManager.getKeyStore()); var response = Response(); @@ -136,6 +140,9 @@ void main() { expect(syncResponseMap['metadata']['ttr'], '100'); expect(syncResponseMap['metadata']['isBinary'], 'false'); expect(syncResponseMap['metadata']['encoding'], 'base64'); + expect(syncResponseMap['metadata']['pubKeyCS'], 'dummy_pub_key_cs'); + expect(syncResponseMap['metadata']['pubKeyHash'], + '{"hash":"dummy_hash","hashingAlgo":"sha512"}'); }); when(() => mockKeyStore.isKeyExists(any())).thenReturn(true); diff --git a/tests/at_end2end_test/test/lookup_verb_test.dart b/tests/at_end2end_test/test/lookup_verb_test.dart index 831f03498..14dc155d1 100644 --- a/tests/at_end2end_test/test/lookup_verb_test.dart +++ b/tests/at_end2end_test/test/lookup_verb_test.dart @@ -1,6 +1,9 @@ +import 'dart:convert'; import 'dart:math'; import 'package:test/test.dart'; +import 'package:uuid/uuid.dart'; +import 'package:version/version.dart'; import 'e2e_test_utils.dart' as e2e; @@ -34,16 +37,54 @@ void main() { ///Update verb on bob atsign var lastValue = Random().nextInt(5); var value = 'Q7878R$lastValue'; - await sh1.writeCommand('update:$atSign_2:special-code$atSign_1 $value'); + var randomId = Uuid().v4(); + await sh1.writeCommand( + 'update:$atSign_2:special-code-$randomId$atSign_1 $value'); String response = await sh1.read(); print('update verb response : $response'); assert( (!response.contains('Invalid syntax')) && (!response.contains('null'))); ///lookup verb alice atsign - await sh2.writeCommand('lookup:special-code$atSign_1'); + await sh2.writeCommand('lookup:special-code-$randomId$atSign_1'); response = await sh2.read(timeoutMillis: 4000); print('lookup verb response : $response'); expect(response, contains('data:$value')); }, timeout: Timeout(Duration(minutes: 3))); + + test('A test to verify lookup metadata contains public key hash value', + () async { + Version atSign1ServerVersion = Version.parse(await sh1.getVersion()); + if (atSign1ServerVersion < Version(3, 1, 1)) { + print( + 'Found $atSign_1 with server version: $atSign1ServerVersion. This test is only applicable for server version least 3.1.1. Skipping the test'); + return; + } + Version atSign2ServerVersion = Version.parse(await sh2.getVersion()); + if (atSign2ServerVersion < Version(3, 1, 1)) { + print( + 'Found $atSign_2 with server version: $atSign2ServerVersion. This test is only applicable for server version least 3.1.1. Skipping the test'); + return; + } + var lastValue = Random().nextInt(5); + var randomHashValue = Uuid().v4().hashCode; + var value = 'Q7878R$lastValue'; + await sh1.writeCommand( + 'update:pubKeyHash:hashedValue-$randomHashValue:hashingAlgo:sha512:$atSign_2:special-code-$randomHashValue$atSign_1 $value'); + String response = await sh1.read(); + assert( + (!response.contains('Invalid syntax')) && (!response.contains('null'))); + + ///lookup verb alice atsign + await sh2.writeCommand('lookup:all:special-code-$randomHashValue$atSign_1'); + response = await sh2.read(timeoutMillis: 4000); + response = response.replaceAll('data:', ''); + var decodedResponse = jsonDecode(response); + expect(decodedResponse['key'], + '$atSign_2:special-code-$randomHashValue$atSign_1'); + expect(decodedResponse['metaData']['pubKeyHash']['hash'], + 'hashedValue-$randomHashValue'); + expect(decodedResponse['metaData']['pubKeyHash']['hashingAlgo'], 'sha512'); + expect(decodedResponse['data'], value); + }); } diff --git a/tests/at_functional_test/test/sync_verb_test.dart b/tests/at_functional_test/test/sync_verb_test.dart index 98a1ed35f..062889bac 100644 --- a/tests/at_functional_test/test/sync_verb_test.dart +++ b/tests/at_functional_test/test/sync_verb_test.dart @@ -70,6 +70,26 @@ void main() { assert((response.contains('Invalid syntax'))); }); + test('A test to verify publicKeyHash in set in sync response', () async { + String namespace = '.func.test'; + String randomId = Uuid().v4(); + + var response = await firstAtSignConnection.sendRequestToServer( + 'update:pubKeyHash:dummy_hash:hashingAlgo:sha512:$secondAtSign:twitter-$randomId$namespace$firstAtSign bob_tweet'); + assert( + (!response.contains('Invalid syntax')) && (!response.contains('null'))); + String commitId = response.replaceAll('data:', ''); + int syncId = int.parse(commitId); + // sync with regex + response = await firstAtSignConnection + .sendRequestToServer('sync:from:${syncId - 1}:limit:5:$namespace'); + response = response.replaceAll('data:', ''); + var responseMap = jsonDecode(response); + var publicKeyHashMap = jsonDecode(responseMap[0]['metadata']['pubKeyHash']); + expect(publicKeyHashMap['hash'], 'dummy_hash'); + expect(publicKeyHashMap['hashingAlgo'], 'sha512'); + }); + group('A group of tests to verify sync entries', () { late OutboundConnectionFactory authenticatedSocket = OutboundConnectionFactory();