Skip to content

Commit

Permalink
Merge branch 'trunk' into sync_skip_deletes
Browse files Browse the repository at this point in the history
  • Loading branch information
murali-shris authored Dec 19, 2024
2 parents b8816d4 + 366aa9a commit 2ee1f5a
Show file tree
Hide file tree
Showing 17 changed files with 293 additions and 64 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/at_client_sdk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
with:
sdk: ${{ matrix.dart-channel}}

- uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with:
go-version: 'stable'
cache-dependency-path: tools/osv-scanner/go.sum
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/scorecards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: SARIF file
path: results.sarif
retention-days: 5

# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@babb554ede22fd5605947329c4d04d8e7a0b8155 # v3.27.7
uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9
with:
sarif_file: results.sarif
9 changes: 6 additions & 3 deletions packages/at_client/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
## 3.4.0
- feat: Allows clients to skip delete commits until a specific commitID
- feat: Allows clients to skip delete commits until a specific commitID during initial sync
## 3.3.1
- fix: isInSync bug fix for apkam connection
- fix: remove deprecated isPaginated param from SyncVerbBuilder in SyncServiceImpl
- build[deps]: Upgraded dependencies for the following packages:
- at_commons to v5.1.1
- at_persistence_secondary_server to v3.1.0
- at_commons to v5.1.2
- feat: Introduce "publicKeyHash" which uses SHA hashing to verify change in the encryption public key
## 3.3.0
- feat: add the AtClientBindings mixin which was initially added to the
noports_core package but has broader applicability.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'package:at_chops/at_chops.dart';
import 'package:at_client/src/client/at_client_spec.dart';
import 'package:at_client/src/decryption_service/decryption.dart';
import 'package:at_client/src/response/default_response_parser.dart';
import 'package:at_client/src/util/encryption_util.dart';
import 'package:at_commons/at_builders.dart';
import 'package:at_commons/at_commons.dart';
import 'package:at_utils/at_logger.dart';
import 'package:at_chops/at_chops.dart';

/// Class responsible for decrypting the value of shared key's that are not owned
/// by currentAtSign
Expand Down Expand Up @@ -50,14 +50,26 @@ class SharedKeyDecryption implements AtKeyDecryption {
intent: Intent.fetchEncryptionPublicKey,
exceptionScenario: ExceptionScenario.localVerbExecutionFailed);
}
if (currentAtSignPublicKey != null &&
(atKey.metadata.pubKeyCS != null &&
atKey.metadata.pubKeyCS !=
EncryptionUtil.md5CheckSum(currentAtSignPublicKey))) {
if (currentAtSignPublicKey.isNullOrEmpty) {
throw AtPublicKeyNotFoundException('Public key cannot be null or empty');
}

final isPubKeyHashMismatch = atKey.metadata.pubKeyHash != null &&
atKey.metadata.pubKeyHash?.hash !=
AtChops.hashWith(HashingAlgoType.fromString(
atKey.metadata.pubKeyHash!.hashingAlgo))
.hash(currentAtSignPublicKey!.codeUnits);

final isPubKeyCSMismatch = atKey.metadata.pubKeyCS != null &&
atKey.metadata.pubKeyCS !=
EncryptionUtil.md5CheckSum(currentAtSignPublicKey!);

if (isPubKeyHashMismatch || isPubKeyCSMismatch) {
throw AtPublicKeyChangeException(
'Public key has changed. Cannot decrypt shared key ${atKey.toString()}',
intent: Intent.fetchEncryptionPublicKey,
exceptionScenario: ExceptionScenario.decryptionFailed);
'Public key has changed. Cannot decrypt shared key ${atKey.toString()}',
intent: Intent.fetchEncryptionPublicKey,
exceptionScenario: ExceptionScenario.decryptionFailed,
);
}

AtEncryptionResult decryptionResultFromAtChops;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:at_chops/at_chops.dart';
import 'package:at_client/at_client.dart';
import 'package:at_client/src/client/secondary.dart';
import 'package:at_client/src/encryption_service/encryption.dart';
Expand All @@ -9,7 +10,6 @@ import 'package:at_commons/at_builders.dart';
import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart';
import 'package:at_utils/at_logger.dart';
import 'package:meta/meta.dart';
import 'package:at_chops/at_chops.dart';

/// Contains the common code for [SharedKeyEncryption] and [StreamEncryption]
abstract class AbstractAtKeyEncryption implements AtKeyEncryption {
Expand Down Expand Up @@ -49,8 +49,15 @@ abstract class AbstractAtKeyEncryption implements AtKeyEncryption {

if (storeSharedKeyEncryptedWithData) {
atKey.metadata.sharedKeyEnc = theirEncryptedSymmetricKeyCopy;
// This is a legacy checksum with MD5 algo.
atKey.metadata.pubKeyCS =
EncryptionUtil.md5CheckSum(await _getSharedWithPublicKey(atKey));
// Hashed the encryption public key with sha512. This is to ensure the encryption
// public key of the receiver are same during encryption and decryption process.
String hash = await AtChops.hashWith(HashingAlgoType.sha512)
.hash((await _getSharedWithPublicKey(atKey)).codeUnits);
atKey.metadata.pubKeyHash =
PublicKeyHash(hash, HashingAlgoType.sha512.name);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class AtClientPreference {
int syncBatchSize = 5;

/// The number of keys to pull from cloud secondary to local secondary in a single call.
int syncPageLimit = 10;
int syncPageLimit = 25;

// Default chunk size for file encryption and decryption
int fileEncryptionChunkSize = 4096;
Expand Down
11 changes: 11 additions & 0 deletions packages/at_client/lib/src/response/at_notification.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:convert';

import 'package:at_client/at_client.dart';

class AtNotification {
Expand Down Expand Up @@ -34,6 +36,15 @@ class AtNotification {
metadata.skeEncAlgo =
json['metadata'][AtConstants.sharedKeyEncryptedEncryptingAlgo];
metadata.sharedKeyEnc = json['metadata'][AtConstants.sharedKeyEncrypted];
// AtContants.sharedWithPublicKeyHash will be sent by the server starting v3.0.52
// Notifications received from Secondary server before 3.0.52 does not contain
// AtConstants.sharedWithPublicKeyHash. Therefore, check for null.
if (json['metadata'][AtConstants.sharedWithPublicKeyHash] != null) {
var publicKeyHash =
jsonDecode(json['metadata'][AtConstants.sharedWithPublicKeyHash]);
metadata.pubKeyHash =
PublicKeyHash(publicKeyHash['hash'], publicKeyHash['hashingAlgo']);
}
}

return AtNotification(json['id'], json['key'], json['from'], json['to'],
Expand Down
43 changes: 27 additions & 16 deletions packages/at_client/lib/src/service/sync_service_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -553,15 +553,15 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener {
Future<List<dynamic>> _getEntriesToSyncFromServer(
int lastReceivedServerCommitId, int serverCommitId,
{int? localCommitIdBeforeSync, int? skipDeletesUntil}) async {
// Sync verb syntax has to be changed before removing these deprecations
var syncBuilder = SyncVerbBuilder()
..commitId = lastReceivedServerCommitId
..regex = _atClient.getPreferences()!.syncRegex
..limit = _atClient.getPreferences()!.syncPageLimit
..isPaginated = true;
..regex = _atClient.getPreferences()!.syncRegex;
if (_shouldSkipDeletes(skipDeletesUntil, serverCommitId)) {
syncBuilder.skipDeletesUntil = skipDeletesUntil;
}

_logger.finer(_logger.getLogMessageWithClientParticulars(
_atClient.getPreferences()!.atClientParticulars,
'syncBuilder ${syncBuilder.buildCommand()}'));
Expand Down Expand Up @@ -784,6 +784,13 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener {
if (metadata.pubKeyCS != null) {
metadataStr += ':pubKeyCS:${metadata.pubKeyCS}';
}
if (metadata.pubKeyHash != null) {
metadataStr +=
':${AtConstants.sharedWithPublicKeyHash}:${metadata.pubKeyHash?.hash}';
metadataStr +=
':${AtConstants.sharedWithPublicKeyHashingAlgo}:${metadata.pubKeyHash?.hashingAlgo}';
}

if (metadata.encoding != null) {
metadataStr += ':encoding:${metadata.encoding}';
}
Expand Down Expand Up @@ -811,13 +818,8 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener {
///Throws [AtClientException] if cloud secondary is not reachable
@override
Future<bool> isInSync() async {
late RemoteSecondary remoteSecondary;
try {
remoteSecondary = RemoteSecondary(
_atClient.getCurrentAtSign()!, _atClient.getPreferences()!,
atChops: _atClient.atChops);
var serverCommitId =
await _getServerCommitId(remoteSecondary: remoteSecondary);
var serverCommitId = await _getServerCommitId();

var lastReceivedServerCommitId = await getLastReceivedServerCommitId();

Expand All @@ -838,8 +840,6 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener {
var cause = (e is AtException) ? e.getTraceMessage() : e.toString();
_logger.severe('exception in isInSync $cause');
throw AtClientException.message(e.toString());
} finally {
unawaited(remoteSecondary.atLookUp.close());
}
}

Expand All @@ -848,8 +848,7 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener {
_logger.finest('*** isInSync..sync in progress');
return true;
}
var serverCommitId =
await _getServerCommitId(remoteSecondary: _remoteSecondary);
var serverCommitId = await _getServerCommitId();
var lastReceivedServerCommitId = await getLastReceivedServerCommitId();
var lastSyncedEntry = await syncUtil.getLastSyncedEntry(
_atClient.getPreferences()!.syncRegex,
Expand All @@ -867,11 +866,10 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener {

/// Returns the cloud secondary latest commit id. if null, returns -1.
///Throws [AtLookUpException] if secondary is not reachable
Future<int> _getServerCommitId({RemoteSecondary? remoteSecondary}) async {
remoteSecondary ??= _remoteSecondary;
Future<int> _getServerCommitId() async {
// ignore: no_leading_underscores_for_local_identifiers
var _serverCommitId = await syncUtil.getLatestServerCommitId(
remoteSecondary, _atClient.getPreferences()!.syncRegex);
_remoteSecondary, _atClient.getPreferences()!.syncRegex);
// If server commit id is null, set to -1;
_serverCommitId ??= -1;
_logger.info(_logger.getLogMessageWithClientParticulars(
Expand Down Expand Up @@ -1015,6 +1013,12 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener {
builder.atKey.metadata.pubKeyCS =
metaData[AtConstants.sharedWithPublicKeyCheckSum];
}
if (metaData[AtConstants.sharedWithPublicKeyHash] != null) {
Map pubKeyHash =
jsonDecode(metaData[AtConstants.sharedWithPublicKeyHash]);
builder.atKey.metadata.pubKeyHash =
PublicKeyHash(pubKeyHash['hash'], pubKeyHash['hashingAlgo']);
}
if (metaData[AtConstants.encoding] != null) {
builder.atKey.metadata.encoding = metaData[AtConstants.encoding];
}
Expand All @@ -1036,6 +1040,13 @@ class SyncServiceImpl implements SyncService, AtSignChangeListener {
builder.atKey.metadata.skeEncAlgo =
metaData[AtConstants.sharedKeyEncryptedEncryptingAlgo];
}

if (metaData[AtConstants.sharedWithPublicKeyHash] != null &&
metaData[AtConstants.sharedWithPublicKeyHashingAlgo] != null) {
builder.atKey.metadata.pubKeyHash = PublicKeyHash(
metaData[AtConstants.sharedWithPublicKeyHash],
metaData[AtConstants.sharedWithPublicKeyHashingAlgo]);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

import 'dart:async';

import 'package:at_client/src/encryption_service/encryption.dart';
import 'package:at_client/src/preference/at_client_preference.dart';
import 'package:at_client/src/service/notification_service.dart';
import 'package:at_client/src/transformer/at_transformer.dart';
import 'package:at_client/src/util/at_client_util.dart';
import 'package:at_commons/at_builders.dart';
import 'package:at_commons/at_commons.dart';
import 'package:at_client/src/encryption_service/encryption.dart';

/// Class is responsible for taking the [NotificationParams] and converting into [NotifyVerbBuilder]
class NotificationRequestTransformer
Expand Down Expand Up @@ -96,6 +96,8 @@ class NotificationRequestTransformer
notificationParams.atKey.metadata.skeEncKeyName;
builder.atKey.metadata.skeEncAlgo =
notificationParams.atKey.metadata.skeEncAlgo;
builder.atKey.metadata.pubKeyHash =
notificationParams.atKey.metadata.pubKeyHash;
}

Future<String> _encryptNotificationValue(AtKey atKey, String value) async {
Expand Down
2 changes: 2 additions & 0 deletions packages/at_client/lib/src/util/at_client_util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ class AtClientUtil {
metadataMap[AtConstants.sharedKeyEncryptedEncryptingAlgo];
metadata.isPublic = isPublic;
metadata.isCached = isCached;
metadata.pubKeyHash = PublicKeyHash.fromJson(
metadataMap[AtConstants.sharedWithPublicKeyHash]);

return metadata;
}
Expand Down
8 changes: 4 additions & 4 deletions packages/at_client/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,19 @@ dependencies:
async: ^2.9.0
at_utf7: ^1.0.0
at_base2e15: ^1.0.0
at_commons: ^5.1.1
at_commons: ^5.1.2
at_utils: ^3.0.19
at_chops: ^2.2.0
at_lookup: ^3.0.49
at_auth: ^2.0.7
at_auth: ^2.0.10
at_persistence_spec: ^2.0.14
at_persistence_secondary_server: ^3.1.0
meta: ^1.8.0
version: ^3.0.2

dev_dependencies:
lints: ^4.0.0
test: ^1.21.4
lints: ^5.0.0
test: ^1.25.8
at_demo_data: ^1.0.1
coverage: ^1.5.0
mocktail: ^1.0.3
Loading

0 comments on commit 2ee1f5a

Please sign in to comment.