Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Secure atKeys with pass-phrase #703

Open
wants to merge 6 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 38 additions & 15 deletions packages/at_auth/lib/src/at_auth_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,9 @@ class AtAuthImpl implements AtAuth {
AtAuthKeys? atAuthKeys;
var enrollmentIdFromRequest = atAuthRequest.enrollmentId;
if (atAuthRequest.atKeysFilePath != null) {
atAuthKeys = _decryptAtKeysFile(
await _readAtKeysFile(atAuthRequest.atKeysFilePath),
atAuthRequest.authMode);
atAuthKeys = await _prepareAtAuthKeysFromFilePath(atAuthRequest);
} else if (atAuthRequest.encryptedKeysMap != null) {
atAuthKeys = _decryptAtKeysFile(
atAuthKeys = _decryptAtKeysWithSelfEncKey(
atAuthRequest.encryptedKeysMap!, PkamAuthMode.keysFile);
} else {
atAuthKeys = atAuthRequest.atAuthKeys;
Expand Down Expand Up @@ -247,7 +245,7 @@ class AtAuthImpl implements AtAuth {
return enrollmentIdFromServer!;
}

AtAuthKeys _decryptAtKeysFile(
AtAuthKeys _decryptAtKeysWithSelfEncKey(
Map<String, dynamic> jsonData, PkamAuthMode authMode) {
var securityKeys = AtAuthKeys();
String decryptionKey = jsonData[auth_constants.defaultSelfEncryptionKey]!;
Expand Down Expand Up @@ -285,21 +283,46 @@ class AtAuthImpl implements AtAuth {

///method to read and return data from .atKeysFile
///returns map containing encryption keys
Future<Map<String, String>> _readAtKeysFile(String? atKeysFilePath) async {
if (atKeysFilePath == null || atKeysFilePath.isEmpty) {
Future<AtAuthKeys> _prepareAtAuthKeysFromFilePath(
AtAuthRequest atAuthRequest) async {
if (atAuthRequest.atKeysFilePath == null ||
atAuthRequest.atKeysFilePath!.isEmpty) {
throw AtException(
'atKeys filePath is empty. atKeysFile is required to authenticate');
}
if (!File(atKeysFilePath).existsSync()) {
if (!File(atAuthRequest.atKeysFilePath!).existsSync()) {
throw AtException(
'provided keys file does not exist. Please check whether the file path $atKeysFilePath is valid');
'provided keys file does not exist. Please check whether the file path ${atAuthRequest.atKeysFilePath} is valid');
}
String atAuthData = await File(atKeysFilePath).readAsString();
Map<String, String> jsonData = <String, String>{};
json.decode(atAuthData).forEach((String key, dynamic value) {
jsonData[key] = value.toString();
});
return jsonData;

String atAuthData =
await File(atAuthRequest.atKeysFilePath!).readAsString();
Map<String, dynamic> decodedAtKeysData = jsonDecode(atAuthData);
// If it contains "iv(InitializationVector)", it means the data is encrypted with a
// passphrase. Decrypt it.
if (decodedAtKeysData.containsKey('iv') &&
atAuthRequest.passPhrase.isNullOrEmpty) {
throw AtDecryptionException(
'Pass Phrase is required for password protected atKeys file');
}
if (decodedAtKeysData.containsKey('iv')) {
_logger.info(
'Found encrypted atKeys files. Decrypting with the given pass-phrase');
AtEncrypted atEncrypted = AtEncrypted.fromJson(decodedAtKeysData);

if (atEncrypted.hashingAlgoType == null) {
throw AtDecryptionException(
'Hashing algo type is required for decryption of AtKeys file');
}

String decryptedAtKeys =
await AtKeysCrypto.fromHashingAlgorithm(atEncrypted.hashingAlgoType!)
.decrypt(atEncrypted, atAuthRequest.passPhrase!);
decodedAtKeysData = jsonDecode(decryptedAtKeys);
}
// This is to decrypt the atKeys encrypted with self Encryption key.
return _decryptAtKeysWithSelfEncKey(
decodedAtKeysData, atAuthRequest.authMode);
}

AtAuthKeys _generateKeyPairs(PkamAuthMode authMode, {String? publicKeyId}) {
Expand Down
3 changes: 3 additions & 0 deletions packages/at_auth/lib/src/auth/at_auth_request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,7 @@ class AtAuthRequest {

/// Hashing algorithm to use for pkam authentication
HashingAlgoType hashingAlgoType = HashingAlgoType.sha256;

/// The pass phrase to password protect the AtKeys file.
String? passPhrase;
}
1 change: 1 addition & 0 deletions packages/at_auth/lib/src/keys/at_auth_keys.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class AtAuthKeys {

AtAuthKeys();

@Deprecated('Use toJson()')
Map<String, String?> toMap() {
var keysMap = <String, String?>{};
keysMap[auth_constants.apkamPrivateKey] = apkamPrivateKey;
Expand Down
28 changes: 17 additions & 11 deletions packages/at_chops/lib/at_chops.dart
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
library at_chops;

export 'src/algorithm/aes_encryption_algo.dart';
export 'src/algorithm/algo_type.dart';
export 'src/algorithm/at_iv.dart';
export 'src/algorithm/default_signing_algo.dart';
export 'src/algorithm/ecc_signing_algo.dart';
export 'src/algorithm/pkam_signing_algo.dart';
export 'src/algorithm/rsa_encryption_algo.dart';
export 'src/at_chops_base.dart';
export 'src/at_chops_impl.dart';
// Class to encrypt/decrypt atKeys file based on the password specified.
export 'src/at_keys_crypto.dart';
export 'src/key/at_key_pair.dart';
export 'src/key/at_private_key.dart';
export 'src/key/at_public_key.dart';
export 'src/key/impl/aes_key.dart';
export 'src/key/impl/at_chops_keys.dart';
export 'src/key/impl/at_encryption_key_pair.dart';
export 'src/key/impl/at_pkam_key_pair.dart';
export 'src/key/impl/aes_key.dart';
export 'src/key/key_type.dart';
export 'src/metadata/at_signing_input.dart';
export 'src/metadata/encryption_metadata.dart';
export 'src/metadata/encryption_result.dart';
export 'src/metadata/signing_metadata.dart';
export 'src/metadata/signing_result.dart';
// A model class which represents the encrypted AtKeys with a passphrase.
export 'src/model/at_encrypted.dart';
// Class representing the hashing parameters to pass to an hashing algorithm.
export 'src/model/hash_params.dart' hide HashParams;
export 'src/util/at_chops_util.dart';
export 'src/algorithm/algo_type.dart';
export 'src/algorithm/at_iv.dart';
export 'src/algorithm/aes_encryption_algo.dart';
export 'src/algorithm/rsa_encryption_algo.dart';
export 'src/algorithm/default_signing_algo.dart';
export 'src/algorithm/pkam_signing_algo.dart';
export 'src/algorithm/ecc_signing_algo.dart';
export 'src/key/at_key_pair.dart';
export 'src/key/at_public_key.dart';
export 'src/key/at_private_key.dart';
73 changes: 70 additions & 3 deletions packages/at_chops/lib/src/algorithm/aes_encryption_algo.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import 'dart:typed_data';

import 'package:at_chops/at_chops.dart';
import 'package:at_chops/src/algorithm/at_algorithm.dart';
import 'package:at_chops/src/algorithm/at_iv.dart';
import 'package:at_chops/src/key/impl/aes_key.dart';
import 'package:at_commons/at_commons.dart';
import 'package:encrypt/encrypt.dart';

class AESEncryptionAlgo implements SymmetricEncryptionAlgorithm {
/// A class that provides AES encryption and decryption for Uint8List,
/// implementing the [SymmetricEncryptionAlgorithm] interface.
class AESEncryptionAlgo
implements SymmetricEncryptionAlgorithm<Uint8List, Uint8List> {
final AESKey _aesKey;

AESEncryptionAlgo(this._aesKey);

@override
Expand All @@ -33,3 +37,66 @@ class AESEncryptionAlgo implements SymmetricEncryptionAlgorithm {
return IV(Uint8List(16));
}
}

/// A class that provides AES encryption and decryption for strings,
/// implementing the [SymmetricEncryptionAlgorithm] interface.
///
/// This class uses an [AESKey] to perform encryption and decryption of strings.
/// The key and an [InitialisationVector] (IV) are used for encryption, and the
/// same key must be used for decryption.
class StringAESEncryptor
implements SymmetricEncryptionAlgorithm<String, String> {
/// The AES key used for encryption and decryption.
final AESKey _aesKey;

/// Constructs an instance of [StringAESEncryptor] with the provided [_aesKey].
///
/// [_aesKey]: The key used for AES encryption and decryption, represented
/// in Base64 format.
StringAESEncryptor(this._aesKey);

/// Decrypts the given [encryptedData] using the provided [iv] (Initialisation Vector).
///
/// The [iv] used for encryption must be the same for decryption. If [iv] is
/// not provided, an [AtDecryptionException] will be thrown, as the IV is
/// mandatory for the AES decryption process.
///
/// - [encryptedData]: The Base64-encoded string that represents the encrypted data.
/// - [iv]: The Initialisation Vector used during encryption. Must be the same
/// IV that was used to encrypt the data.
///
/// Returns a [String] that represents the decrypted data.
///
/// Throws an [AtDecryptionException] if the [iv] is missing.

@override
String decrypt(String encryptedData, {InitialisationVector? iv}) {
// The IV used for encryption, the same IV must be used for decryption.
if (iv == null) {
throw AtDecryptionException(
'Initialisation Vector (IV) is required for decryption');
}
var aesEncrypter = Encrypter(AES(Key.fromBase64(_aesKey.key)));
return aesEncrypter.decrypt(Encrypted.fromBase64(encryptedData),
iv: IV(iv.ivBytes));
}

/// Encrypts the given [plainData] using AES encryption and an optional [iv].
///
/// If no [iv] is provided, a random 16-byte IV will be generated using
/// [AtChopsUtil.generateRandomIV]. The resulting encrypted data will be
/// Base64-encoded.
///
/// - [plainData]: The string that needs to be encrypted.
/// - [iv]: The Initialisation Vector used for encryption. If not provided,
/// a random 16-byte IV will be generated.
///
/// Returns a [String] that contains the encrypted data, encoded in Base64 format.
@override
String encrypt(String plainData, {InitialisationVector? iv}) {
iv ??= AtChopsUtil.generateRandomIV(16);
var aesEncrypter = Encrypter(AES(Key.fromBase64(_aesKey.key)));
final encrypted = aesEncrypter.encrypt(plainData, iv: IV(iv.ivBytes));
return encrypted.base64;
}
}
24 changes: 23 additions & 1 deletion packages/at_chops/lib/src/algorithm/algo_type.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
// ignore: constant_identifier_names
import 'package:at_commons/at_commons.dart';

enum SigningAlgoType { ecc_secp256r1, rsa2048, rsa4096 }

enum HashingAlgoType { sha256, sha512, md5 }
enum HashingAlgoType {
sha256,
sha512,
md5,
argon2id;

static HashingAlgoType fromString(String name) {
switch (name.toLowerCase()) {
sitaram-kalluri marked this conversation as resolved.
Show resolved Hide resolved
case 'sha256':
return HashingAlgoType.sha256;
case 'sha512':
return HashingAlgoType.sha512;
case 'md5':
return HashingAlgoType.md5;
case 'argon2id':
return HashingAlgoType.argon2id;
default:
throw AtException('Invalid hashing algo type');
}
}
}
52 changes: 52 additions & 0 deletions packages/at_chops/lib/src/algorithm/argon2id_hashing_algo.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import 'dart:async';
import 'dart:convert';

import 'package:at_chops/src/algorithm/at_algorithm.dart';
import 'package:at_chops/src/model/hash_params.dart';
import 'package:cryptography/cryptography.dart';

/// A class that implements the Argon2id hashing algorithm for password hashing.
///
/// This class provides a method to hash a given password using the Argon2id
/// algorithm, which is a memory-hard, CPU-intensive key derivation function
/// suitable for password hashing and encryption key derivation.
///
/// The class uses the `cryptography` package's `Argon2id` algorithm for deriving
/// a key from a password and encodes the result into a Base64 string.
class Argon2idHashingAlgo implements AtHashingAlgorithm<String, String> {
/// Hashes a given password using the Argon2id algorithm.
///
/// The [password] parameter is required, and it represents the password or
/// passphrase to be hashed.
///
/// The [hashParams] parameter is optional. It allows customizing the Argon2id
/// parameters, such as:
/// - [HashParams.parallelism]: The degree of parallelism (threads) to use.
/// - [HashParams.memory]: The amount of memory (in KB) to use.
/// - [HashParams.iterations]: The number of iterations (time cost) to apply.
/// - [HashParams.hashLength]: The length of the resulting hash (in bytes).
///
/// If [hashParams] is not provided, default values will be used.
///
/// The method returns a [Future] that resolves to a Base64-encoded string
/// representing the hashed value of the input password.
///
/// Throws:
/// - [ArgumentError] if the provided password is null or empty.
///
/// Returns a Base64-encoded string representing the derived key.
@override
Future<String> hash(String password, {ArgonHashParams? hashParams}) async {
hashParams ??= ArgonHashParams();
final argon2id = Argon2id(
parallelism: hashParams.parallelism,
memory: hashParams.memory,
iterations: hashParams.iterations,
hashLength: hashParams.hashLength);

SecretKey secretKey = await argon2id.deriveKeyFromPassword(
password: password, nonce: password.codeUnits);

return Base64Encoder().convert(await secretKey.extractBytes());
}
}
23 changes: 14 additions & 9 deletions packages/at_chops/lib/src/algorithm/at_algorithm.dart
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
import 'dart:async';
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';

/// Interface for encrypting and decrypting data. Check [DefaultEncryptionAlgo] for sample implementation.
abstract class AtEncryptionAlgorithm {
abstract class AtEncryptionAlgorithm<T, V> {
/// Encrypts the passed bytes. Bytes are passed as [Uint8List]. Encode String data type to [Uint8List] using [utf8.encode].
Uint8List encrypt(Uint8List plainData);
V encrypt(T plainData);

/// Decrypts the passed encrypted bytes.
Uint8List decrypt(Uint8List encryptedData);
V decrypt(T encryptedData);
}

/// Interface for symmetric encryption algorithms. Check [AESEncryptionAlgo] for sample implementation.
abstract class SymmetricEncryptionAlgorithm extends AtEncryptionAlgorithm {
abstract class SymmetricEncryptionAlgorithm<T, V>
extends AtEncryptionAlgorithm<T, V> {
@override
Uint8List encrypt(Uint8List plainData, {InitialisationVector iv});
V encrypt(T plainData, {InitialisationVector iv});

@override
Uint8List decrypt(Uint8List encryptedData, {InitialisationVector iv});
V decrypt(T encryptedData, {InitialisationVector iv});
}

/// Interface for asymmetric encryption algorithms. Check [DefaultEncryptionAlgo] for sample implementation.
abstract class ASymmetricEncryptionAlgorithm extends AtEncryptionAlgorithm {
abstract class ASymmetricEncryptionAlgorithm
extends AtEncryptionAlgorithm<Uint8List, Uint8List> {
AtPublicKey? atPublicKey;
AtPrivateKey? atPrivateKey;

Expand All @@ -48,7 +53,7 @@ abstract class AtSigningAlgorithm {
}

/// Interface for hashing data. Refer [DefaultHash] for sample implementation.
abstract class AtHashingAlgorithm {
abstract class AtHashingAlgorithm<K, V> {
/// Hashes the passed data
String hash(Uint8List data);
FutureOr<V> hash(K data, {covariant HashParams? hashParams});
}
5 changes: 3 additions & 2 deletions packages/at_chops/lib/src/algorithm/default_hashing_algo.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import 'package:at_chops/src/algorithm/at_algorithm.dart';
import 'package:at_chops/src/model/hash_params.dart';
import 'package:crypto/crypto.dart';

class DefaultHash implements AtHashingAlgorithm {
class DefaultHash implements AtHashingAlgorithm<List<int>, String> {
@override
String hash(List<int> data) {
String hash(List<int> data, {HashParams? hashParams}) {
return md5.convert(data).toString();
}
}
Loading