Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,12 @@ expect(decryptedEmail).toStrictEqual(email);

// keystore
const userEmail = 'user email';
const password = 'user password';
const mnemonic = 'user mnemonic';
const { encryptionKeystore, recoveryKeystore, recoveryCodes } = await createEncryptionAndRecoveryKeystores(
userEmail,
password
mnemonic
);
const resultEnc = await openEncryptionKeystore(encryptionKeystore, password);
const resultEnc = await openEncryptionKeystore(encryptionKeystore, mnemonic);
const resultRec = await openRecoveryKeystore(recoveryCodes, recoveryKeystore);

expect(resultEnc).toStrictEqual(resultRec);
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ export {
createEncryptionAndRecoveryKeystores,
openEncryptionKeystore,
openRecoveryKeystore,
changePasswordForEncryptionKeystore,
changeMnemonicForEncryptionKeystore,
FailedToOpenEncryptionKeyStore,
FailedToCreateKeyStores,
FailedToOpenRecoveryKeyStore,
FailedToChangePasswordForKeyStore,
FailedToChangeMnemonicForKeyStore,
InvalidInputKeyStore,
} from './keystore-crypto';
export { generateKyberKeys, encapsulateKyber, decapsulateKyber } from './post-quantum-crypto';
Expand Down
33 changes: 1 addition & 32 deletions src/keystore-crypto/core.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { encryptSymmetrically, decryptSymmetrically } from '../symmetric-crypto';
import { base64ToUint8Array, uint8ArrayToBase64, UTF8ToUint8 } from '../utils';
import { deriveKeyFromMnemonic, deriveSymmetricKeyFromContext } from '../derive-key';
import { deriveKeyFromMnemonic } from '../derive-key';
import { CONTEXT_ENC_KEYSTORE, CONTEXT_RECOVERY } from '../constants';
import { EncryptedKeystore, HybridKeyPair, KeystoreType } from '../types';
import { getKeyFromPasswordAndSalt, getKeyFromPassword } from '../derive-password';

/**
* Encrypts the user's hybrid key using symmetric encryption to get a keystore
Expand All @@ -19,21 +18,18 @@ export async function encryptKeystoreContent(
keys: HybridKeyPair,
userEmail: string,
type: KeystoreType,
salt?: Uint8Array,
): Promise<EncryptedKeystore> {
try {
const publicKey = uint8ArrayToBase64(keys.publicKey);

const aux = UTF8ToUint8(userEmail + type + publicKey);
const secretKeyEncrypted = await encryptSymmetrically(secretKey, keys.secretKey, aux);

const keystoreSalt = salt ? uint8ArrayToBase64(salt) : undefined;
const keystore: EncryptedKeystore = {
userEmail,
type,
publicKey,
privateKeyEncrypted: uint8ArrayToBase64(secretKeyEncrypted),
salt: keystoreSalt,
};
return keystore;
} catch (error) {
Expand Down Expand Up @@ -85,30 +81,3 @@ export async function deriveRecoveryKey(recoveryCodes: string): Promise<Uint8Arr
export async function deriveEncryptionKeystoreKeyFromMnemonic(mnemonic: string): Promise<Uint8Array> {
return deriveKeyFromMnemonic(mnemonic, CONTEXT_ENC_KEYSTORE);
}

/**
* Derives a secret key for protecting the encryption keystore
*
* @param password - The user's password
* @param salt - The keystore salt
* @returns The derived secret key for protecting the encryption keystore
*/
export async function deriveEncryptionKeystoreKey(password: string, salt: Uint8Array): Promise<Uint8Array> {
const baseKey = await getKeyFromPasswordAndSalt(password, salt);
return deriveSymmetricKeyFromContext(CONTEXT_ENC_KEYSTORE, baseKey);
}

/**
* Derives a secret key for protecting the encryption keystore
*
* @param password - The user's password
* @param salt - The keystore salt
* @returns The derived secret key for protecting the encryption keystore
*/
export async function deriveNewEncryptionKeystoreKey(
password: string,
): Promise<{ secretKey: Uint8Array; salt: Uint8Array }> {
const { key, salt } = await getKeyFromPassword(password);
const secretKey = deriveSymmetricKeyFromContext(CONTEXT_ENC_KEYSTORE, key);
return { secretKey, salt };
}
50 changes: 23 additions & 27 deletions src/keystore-crypto/emailEncryptionKey.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
import { EncryptedKeystore, KeystoreType, HybridKeyPair } from '../types';
import { base64ToUint8Array, genMnemonic } from '../utils';
import { genMnemonic } from '../utils';
import {
encryptKeystoreContent,
decryptKeystoreContent,
deriveEncryptionKeystoreKey,
deriveNewEncryptionKeystoreKey,
deriveEncryptionKeystoreKeyFromMnemonic,
deriveRecoveryKey,
} from './core';
import { genHybridKeys } from '../hybrid-crypto';
import {
FailedToOpenEncryptionKeyStore,
FailedToCreateKeyStores,
FailedToOpenRecoveryKeyStore,
FailedToChangePasswordForKeyStore,
FailedToChangeMnemonicForKeyStore,
InvalidInputKeyStore,
} from './errors';
import { ARGON2ID_SALT_BYTE_LENGTH } from '../constants';

/**
* Generates hybrid keys and creates encrypted main and recovery keystores
* The main keystore encryption key is derived from the user's password
* The main keystore encryption key is derived from the user's mnemonic
* The recovery keystore encryption key is derived from the recovery codes
*
* @param userEmail - The user's email
* @param password - The user's password
* @param mnemonic - The user's mnemonic
* @returns The encryption keys
*
* @returns The encryption and recovery keystores, recovery codes and hybrid keys
*/
export async function createEncryptionAndRecoveryKeystores(
userEmail: string,
password: string,
mnemonic: string,
): Promise<{
encryptionKeystore: EncryptedKeystore;
recoveryKeystore: EncryptedKeystore;
Expand All @@ -40,8 +38,8 @@ export async function createEncryptionAndRecoveryKeystores(
try {
const keys = genHybridKeys();

const { secretKey, salt } = await deriveNewEncryptionKeystoreKey(password);
const encryptionKeystore = await encryptKeystoreContent(secretKey, keys, userEmail, KeystoreType.ENCRYPTION, salt);
const secretKey = await deriveEncryptionKeystoreKeyFromMnemonic(mnemonic);
const encryptionKeystore = await encryptKeystoreContent(secretKey, keys, userEmail, KeystoreType.ENCRYPTION);

const recoveryCodes = genMnemonic();
const recoveryKey = await deriveRecoveryKey(recoveryCodes);
Expand All @@ -55,22 +53,21 @@ export async function createEncryptionAndRecoveryKeystores(

/**
* Opens the encryption keystore and returns the email encryption keys
* The decryption key is derived from the user password
* The decryption key is derived from the user mnemonic
*
* @param encryptedKeystore - The encrypted keystore containing encryption keys
* @param password - The user's password
* @param mnemonic - The user's mnemonic
* @returns The encryption keys
*/
export async function openEncryptionKeystore(
encryptedKeystore: EncryptedKeystore,
password: string,
mnemonic: string,
): Promise<HybridKeyPair> {
try {
const salt = encryptedKeystore.salt ? base64ToUint8Array(encryptedKeystore.salt) : new Uint8Array();
if (encryptedKeystore.type !== KeystoreType.ENCRYPTION || salt.length !== ARGON2ID_SALT_BYTE_LENGTH) {
if (encryptedKeystore.type !== KeystoreType.ENCRYPTION) {
throw new InvalidInputKeyStore();
}
const secretKey = await deriveEncryptionKeystoreKey(password, salt);
const secretKey = await deriveEncryptionKeystoreKeyFromMnemonic(mnemonic);
const keys = await decryptKeystoreContent(secretKey, encryptedKeystore);
return keys;
} catch (error) {
Expand Down Expand Up @@ -105,34 +102,33 @@ export async function openRecoveryKeystore(
}

/**
* Re-encrypts the encryption keystore with a new password
* The decryption key is derived from the user password
* Re-encrypts the encryption keystore with a new mnemonic
* The decryption key is derived from the user mnemonic
*
* @param encryptedKeystore - The encrypted keystore containing encryption keys
* @param oldPassword - The user's old password
* @param newPassword - The user's new password
* @param oldMnemonic - The user's old mnemonic
* @param newMnemonic - The user's new mnemonic
* @returns The keys and new re-encrypted keystore
*/
export async function changePasswordForEncryptionKeystore(
export async function changeMnemonicForEncryptionKeystore(
encryptedKeystore: EncryptedKeystore,
oldPassword: string,
newPassword: string,
oldMnemonic: string,
newMnemonic: string,
): Promise<{ keys: HybridKeyPair; newKeystore: EncryptedKeystore }> {
try {
const keys = await openEncryptionKeystore(encryptedKeystore, oldPassword);
const keys = await openEncryptionKeystore(encryptedKeystore, oldMnemonic);

const { secretKey, salt } = await deriveNewEncryptionKeystoreKey(newPassword);
const secretKey = await deriveEncryptionKeystoreKeyFromMnemonic(newMnemonic);
const newKeystore = await encryptKeystoreContent(
secretKey,
keys,
encryptedKeystore.userEmail,
KeystoreType.ENCRYPTION,
salt,
);

return { newKeystore, keys };
} catch (error) {
if (error instanceof InvalidInputKeyStore) throw error;
throw new FailedToChangePasswordForKeyStore(error instanceof Error ? error.message : String(error));
throw new FailedToChangeMnemonicForKeyStore(error instanceof Error ? error.message : String(error));
}
}
4 changes: 2 additions & 2 deletions src/keystore-crypto/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ export class FailedToOpenRecoveryKeyStore extends Error {
}
}

export class FailedToChangePasswordForKeyStore extends Error {
export class FailedToChangeMnemonicForKeyStore extends Error {
constructor(errorMsg?: string) {
super('Error while fetching message: ' + errorMsg);

Object.setPrototypeOf(this, FailedToChangePasswordForKeyStore.prototype);
Object.setPrototypeOf(this, FailedToChangeMnemonicForKeyStore.prototype);
}
}
1 change: 0 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ export type EncryptedKeystore = {
type: KeystoreType;
publicKey: string;
privateKeyEncrypted: string;
salt?: string;
};

export type RecipientWithPublicKey = {
Expand Down
Loading
Loading