Skip to content

Commit

Permalink
feat(cat-voices): vault crypto (#1011)
Browse files Browse the repository at this point in the history
* chore: wip

* feat: first iteration of AesCryptoService

* feat: refactor SecureStorageVault to use CryptoService

* fix: keychain clearing vault

* chore: spacing in SecureStorageVault

* test: AesCryptoService

* chore: wip

* feat: vault crypto service versioning + algorithm ID

* test: fix vault tests

* fix: vault singleton registration

* fix: keychain adjustments

* chore: benchmark crypto service

* fix: Use Pbkdf2 instead of Argon2id key derivation algorithm

* docs: improve docs for CryptoService, VaultCryptoService and Keychain

* Update catalyst_voices/packages/catalyst_voices_models/lib/src/errors/crypto_exception.dart

Co-authored-by: Dominik Toton <[email protected]>

* fix: missing props in exceptions

* docs: CryptoService and KeyDerivation connection

* change random number to max 255

* fix: typo

---------

Co-authored-by: Dominik Toton <[email protected]>
  • Loading branch information
damian-molinski and dtscalac authored Oct 17, 2024
1 parent 1956ae7 commit 57c47ce
Show file tree
Hide file tree
Showing 20 changed files with 679 additions and 322 deletions.
1 change: 1 addition & 0 deletions .config/dictionaries/project.dic
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,4 @@ xctestrun
xcworkspace
xvfb
yoroi
Pbkdf2
2 changes: 1 addition & 1 deletion catalyst_voices/lib/dependency/dependencies.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ final class Dependencies extends DependencyProvider {

void _registerServices() {
registerLazySingleton<Storage>(() => const SecureStorage());
registerLazySingleton<Vault>(() => const SecureStorageVault());
registerLazySingleton<Vault>(SecureStorageVault.new);
registerLazySingleton<DummyAuthStorage>(
() => const SecureDummyAuthStorage(),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@ final class SessionBloc extends Bloc<SessionEvent, SessionState>
) async {
if (!await _keychain.hasSeedPhrase) {
emit(const VisitorSessionState());
} else if (await _keychain.isUnlocked) {
// TODO(damian-molinski): we shouldn't keep the keychain unlocked
// after leaving the app. In the future once keychain stays locked
// when leaving the app the logic here is not needed.
await _keychain.lock();
emit(const GuestSessionState());
} else {
emit(const GuestSessionState());
}
Expand All @@ -56,7 +50,9 @@ final class SessionBloc extends Bloc<SessionEvent, SessionState>
LockSessionEvent event,
Emitter<SessionState> emit,
) async {
await _keychain.lock();
if (await _keychain.hasLock) {
await _keychain.lock();
}
emit(const GuestSessionState());
}

Expand Down Expand Up @@ -88,6 +84,10 @@ final class SessionBloc extends Bloc<SessionEvent, SessionState>
GuestSessionEvent event,
Emitter<SessionState> emit,
) async {
if (await _keychain.hasLock && !await _keychain.isUnlocked) {
await _keychain.unlock(_dummyUnlockFactor);
}

await _keychain.setLockAndBeginWith(
seedPhrase: _dummySeedPhrase,
unlockFactor: _dummyUnlockFactor,
Expand All @@ -101,6 +101,10 @@ final class SessionBloc extends Bloc<SessionEvent, SessionState>
ActiveUserSessionEvent event,
Emitter<SessionState> emit,
) async {
if (await _keychain.hasLock && !await _keychain.isUnlocked) {
await _keychain.unlock(_dummyUnlockFactor);
}

await _keychain.setLockAndBeginWith(
seedPhrase: _dummySeedPhrase,
unlockFactor: _dummyUnlockFactor,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'package:equatable/equatable.dart';

sealed class CryptoException extends Equatable implements Exception {
const CryptoException();

@override
List<Object?> get props => [];
}

/// Usually thrown when trying to decrypt with invalid key
final class CryptoAuthenticationException extends CryptoException {
const CryptoAuthenticationException();

@override
String toString() => 'CryptoAuthenticationException';
}

/// Thrown when trying to decrypt tampered data
final class CryptoDataMalformed extends CryptoException {
final String? message;

const CryptoDataMalformed([this.message]);

@override
String toString() {
if (message != null) return 'CryptoDataMalformed: $message';
return 'CryptoDataMalformed';
}

@override
List<Object?> get props => [message];
}

final class CryptoVersionUnsupported extends CryptoException {
final String? message;

const CryptoVersionUnsupported([this.message]);

@override
String toString() {
if (message != null) return 'CryptoVersionUnsupported: $message';
return 'CryptoVersionUnsupported';
}

@override
List<Object?> get props => [message];
}

final class CryptoAlgorithmUnsupported extends CryptoException {
final String? message;

const CryptoAlgorithmUnsupported([this.message]);

@override
String toString() {
if (message != null) return 'CryptoAlgorithmUnsupported: $message';
return 'CryptoAlgorithmUnsupported';
}

@override
List<Object?> get props => [message];
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export 'crypto_exception.dart';
export 'network_error.dart';
export 'secure_storage_error.dart';
export 'vault_exception.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:equatable/equatable.dart';

sealed class VaultException extends Equatable implements Exception {
const VaultException();

@override
List<Object?> get props => [];
}

final class LockNotFoundException extends VaultException {
final String? message;

const LockNotFoundException([this.message]);

@override
String toString() {
if (message != null) return 'LockNotFoundException: $message';
return 'LockNotFoundException';
}

@override
List<Object?> get props => [message];
}

final class VaultLockedException extends VaultException {
const VaultLockedException();

@override
String toString() => 'VaultLockedException';
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,5 @@ export 'storage/dummy_auth_storage.dart';
export 'storage/secure_storage.dart';
export 'storage/storage.dart';
export 'storage/vault/lock_factor.dart';
export 'storage/vault/lock_factor_codec.dart' show LockFactorCodec;
export 'storage/vault/secure_storage_vault.dart';
export 'storage/vault/vault.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'package:flutter/foundation.dart';

/// An abstract interface that defines cryptographic operations such as
/// key derivation, encryption, decryption, and key verification.
// TODO(damian-molinski): Expose KeyDerivation interface and have it
// delegate implementation
abstract interface class CryptoService {
/// Derives a cryptographic key from a given seed, with an optional salt.
///
/// The derived key is generated based on the provided [seed], which serves
/// as the primary input. Optionally, a [salt] can be used to further
/// randomize the key derivation process, increasing security.
///
/// - [seed]: The main input data used for key derivation.
/// - [salt]: Optional salt value to randomize the derived key (can be null).
///
/// Returns a [Future] that completes with the derived key as a [Uint8List].
Future<Uint8List> deriveKey(
Uint8List seed, {
Uint8List? salt,
});

/// Verifies if a given cryptographic key is correctly derived from a seed.
///
/// This method checks whether the provided [key] matches the key that would
/// be derived from the given [seed]. This can be useful to verify integrity
/// or correctness of the key derivation process.
///
/// - [seed]: The input data used for key derivation.
/// - [key]: The derived key that needs to be verified.
///
/// Returns a [Future] that completes with `true` if the [key] is valid and
/// correctly derived from the [seed], or `false` otherwise.
Future<bool> verifyKey(
Uint8List seed, {
required Uint8List key,
});

/// Decrypts the provided [data] using the specified cryptographic [key],
/// usually build using [deriveKey].
///
/// This method takes encrypted [data] and decrypts it using the provided
/// [key]. The decryption algorithm and the format of the data should be
/// defined by the implementing class.
///
/// - [data]: The encrypted data to be decrypted.
/// - [key]: The key used for decryption.
///
/// Returns a [Future] that completes with the decrypted data as a
/// [Uint8List].
Future<Uint8List> decrypt(
Uint8List data, {
required Uint8List key,
});

/// Encrypts the provided [data] using the specified cryptographic [key],
/// usually build using [deriveKey].
///
/// This method takes plaintext [data] and encrypts it using the provided
/// [key]. The encryption algorithm and format of the output should be defined
/// by the implementing class.
///
/// - [data]: The plaintext data to be encrypted.
/// - [key]: The key used for encryption.
///
/// Returns a [Future] that completes with the encrypted data as a
/// [Uint8List].
Future<Uint8List> encrypt(
Uint8List data, {
required Uint8List key,
});
}
Loading

0 comments on commit 57c47ce

Please sign in to comment.