diff --git a/README.md b/README.md index ed6e2d0..2f401ab 100644 --- a/README.md +++ b/README.md @@ -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); diff --git a/src/index.ts b/src/index.ts index 12a1d8d..117c378 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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'; diff --git a/src/keystore-crypto/core.ts b/src/keystore-crypto/core.ts index c240865..52c1865 100644 --- a/src/keystore-crypto/core.ts +++ b/src/keystore-crypto/core.ts @@ -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 @@ -19,7 +18,6 @@ export async function encryptKeystoreContent( keys: HybridKeyPair, userEmail: string, type: KeystoreType, - salt?: Uint8Array, ): Promise { try { const publicKey = uint8ArrayToBase64(keys.publicKey); @@ -27,13 +25,11 @@ export async function encryptKeystoreContent( 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) { @@ -85,30 +81,3 @@ export async function deriveRecoveryKey(recoveryCodes: string): Promise { 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 { - 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 }; -} diff --git a/src/keystore-crypto/emailEncryptionKey.ts b/src/keystore-crypto/emailEncryptionKey.ts index 5a1a0e3..81fbe4e 100644 --- a/src/keystore-crypto/emailEncryptionKey.ts +++ b/src/keystore-crypto/emailEncryptionKey.ts @@ -1,10 +1,9 @@ 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'; @@ -12,25 +11,24 @@ 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; @@ -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); @@ -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 { 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) { @@ -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)); } } diff --git a/src/keystore-crypto/errors.ts b/src/keystore-crypto/errors.ts index 367e07d..2207cb6 100644 --- a/src/keystore-crypto/errors.ts +++ b/src/keystore-crypto/errors.ts @@ -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); } } diff --git a/src/types.ts b/src/types.ts index 75dde60..c3c7f0d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,7 +3,6 @@ export type EncryptedKeystore = { type: KeystoreType; publicKey: string; privateKeyEncrypted: string; - salt?: string; }; export type RecipientWithPublicKey = { diff --git a/tests/keystore-crypto/emailEncryptionKeys.test.ts b/tests/keystore-crypto/emailEncryptionKeys.test.ts index bff300f..5771608 100644 --- a/tests/keystore-crypto/emailEncryptionKeys.test.ts +++ b/tests/keystore-crypto/emailEncryptionKeys.test.ts @@ -3,16 +3,15 @@ import { createEncryptionAndRecoveryKeystores, openEncryptionKeystore, openRecoveryKeystore, - changePasswordForEncryptionKeystore, + changeMnemonicForEncryptionKeystore, FailedToOpenEncryptionKeyStore, FailedToCreateKeyStores, FailedToOpenRecoveryKeyStore, - FailedToChangePasswordForKeyStore, + FailedToChangeMnemonicForKeyStore, InvalidInputKeyStore, } from '../../src/keystore-crypto'; import { XWING_PUBLIC_KEY_LENGTH, XWING_SECRET_KEY_LENGTH } from '../../src/constants'; -import { generateSalt } from '../../src/derive-password'; -import { uint8ArrayToBase64 } from '../../src/utils'; +import { genMnemonic, uint8ArrayToBase64 } from '../../src/utils'; import { genHybridKeys } from '../../src/hybrid-crypto'; describe('Test keystore create/open functions', async () => { @@ -23,17 +22,17 @@ describe('Test keystore create/open functions', async () => { vi.restoreAllMocks(); }); - it('should throw an error if no password for keystore creation', async () => { + it('should throw an error if no mnemonic for keystore creation', async () => { await expect(createEncryptionAndRecoveryKeystores(mockUserEmail, '')).rejects.toThrow(FailedToCreateKeyStores); }); it('should successfully create and open encryption keystore', async () => { - const password = 'user password'; + const mnemonic = genMnemonic(); const { encryptionKeystore, recoveryKeystore, recoveryCodes } = await createEncryptionAndRecoveryKeystores( mockUserEmail, - password, + mnemonic, ); - const resultEnc = await openEncryptionKeystore(encryptionKeystore, password); + const resultEnc = await openEncryptionKeystore(encryptionKeystore, mnemonic); const resultRec = await openRecoveryKeystore(recoveryCodes, recoveryKeystore); expect(resultEnc).toStrictEqual(resultRec); @@ -43,11 +42,11 @@ describe('Test keystore create/open functions', async () => { expect(resultEnc.secretKey.length).toBe(XWING_SECRET_KEY_LENGTH); }); - it('should throw an error if no password for keystore opening', async () => { - const password = 'user password'; + it('should throw an error if no mnemonic for keystore opening', async () => { + const mnemonic = genMnemonic(); const { encryptionKeystore, recoveryKeystore } = await createEncryptionAndRecoveryKeystores( mockUserEmail, - password, + mnemonic, ); await expect(openEncryptionKeystore(encryptionKeystore, '')).rejects.toThrow(FailedToOpenEncryptionKeyStore); @@ -55,92 +54,70 @@ describe('Test keystore create/open functions', async () => { }); it('should throw an error if wrong keystore type', async () => { - const password = 'user password'; + const mnemonic = genMnemonic(); const { encryptionKeystore, recoveryKeystore, recoveryCodes } = await createEncryptionAndRecoveryKeystores( mockUserEmail, - password, + mnemonic, ); - await expect(openEncryptionKeystore(recoveryKeystore, password)).rejects.toThrow(InvalidInputKeyStore); + await expect(openEncryptionKeystore(recoveryKeystore, mnemonic)).rejects.toThrow(InvalidInputKeyStore); await expect(openRecoveryKeystore(recoveryCodes, encryptionKeystore)).rejects.toThrow(InvalidInputKeyStore); }); - it('should successfully re-encrypt and open encryption keystore with a new password', async () => { - const password = 'user password'; - const { encryptionKeystore } = await createEncryptionAndRecoveryKeystores(mockUserEmail, password); - const resultEnc = await openEncryptionKeystore(encryptionKeystore, password); + it('should successfully re-encrypt and open encryption keystore with a new mnemonic', async () => { + const mnemonic = genMnemonic(); + const { encryptionKeystore } = await createEncryptionAndRecoveryKeystores(mockUserEmail, mnemonic); + const resultEnc = await openEncryptionKeystore(encryptionKeystore, mnemonic); - const newPassword = 'a very new user password'; - const { newKeystore, keys } = await changePasswordForEncryptionKeystore(encryptionKeystore, password, newPassword); + const newMnemonic = genMnemonic(); + const { newKeystore, keys } = await changeMnemonicForEncryptionKeystore(encryptionKeystore, mnemonic, newMnemonic); - const resultNew = await openEncryptionKeystore(newKeystore, newPassword); + const resultNew = await openEncryptionKeystore(newKeystore, newMnemonic); + expect(newMnemonic).not.toEqual(mnemonic); expect(resultEnc).toStrictEqual(keys); expect(resultEnc).toStrictEqual(resultNew); - expect(newKeystore.salt).not.toEqual(encryptionKeystore.salt); - expect(newKeystore.salt).toBeDefined(); }); - it('should throw an error if re-encrypted keystore is opened with old password or salt', async () => { - const password = 'user password'; - const { encryptionKeystore } = await createEncryptionAndRecoveryKeystores(mockUserEmail, password); - const resultEnc = await openEncryptionKeystore(encryptionKeystore, password); + it('should throw an error if re-encrypted keystore is opened with old mnemonic', async () => { + const mnemonic = genMnemonic(); + const { encryptionKeystore } = await createEncryptionAndRecoveryKeystores(mockUserEmail, mnemonic); + const resultEnc = await openEncryptionKeystore(encryptionKeystore, mnemonic); - const newPassword = 'a very new user password'; - const { newKeystore, keys } = await changePasswordForEncryptionKeystore(encryptionKeystore, password, newPassword); + const newMnemonic = genMnemonic(); + const { newKeystore, keys } = await changeMnemonicForEncryptionKeystore(encryptionKeystore, mnemonic, newMnemonic); expect(resultEnc).toStrictEqual(keys); - await expect(openEncryptionKeystore(newKeystore, password)).rejects.toThrow(FailedToOpenEncryptionKeyStore); + await expect(openEncryptionKeystore(newKeystore, mnemonic)).rejects.toThrow(FailedToOpenEncryptionKeyStore); - await expect(openEncryptionKeystore(encryptionKeystore, newPassword)).rejects.toThrow( + await expect(openEncryptionKeystore(encryptionKeystore, newMnemonic)).rejects.toThrow( FailedToOpenEncryptionKeyStore, ); }); - it('should throw an error if no password for keystore re-encryption', async () => { - const password = 'user password'; - const { encryptionKeystore } = await createEncryptionAndRecoveryKeystores(mockUserEmail, password); + it('should throw an error if no mnemonic for keystore re-encryption', async () => { + const mnemonic = genMnemonic(); + const { encryptionKeystore } = await createEncryptionAndRecoveryKeystores(mockUserEmail, mnemonic); - await expect(changePasswordForEncryptionKeystore(encryptionKeystore, password, '')).rejects.toThrow( - FailedToChangePasswordForKeyStore, + await expect(changeMnemonicForEncryptionKeystore(encryptionKeystore, mnemonic, '')).rejects.toThrow( + FailedToChangeMnemonicForKeyStore, ); }); - it('should throw an error if salt, email or pk changed', async () => { - const password = 'user password'; - const { encryptionKeystore } = await createEncryptionAndRecoveryKeystores(mockUserEmail, password); - - const wrongSaltKeystore = { ...encryptionKeystore }; - wrongSaltKeystore.salt = uint8ArrayToBase64(generateSalt()); - await expect(openEncryptionKeystore(wrongSaltKeystore, password)).rejects.toThrow(FailedToOpenEncryptionKeyStore); + it('should throw an error if email or pk changed', async () => { + const mnemonic = genMnemonic(); + const { encryptionKeystore } = await createEncryptionAndRecoveryKeystores(mockUserEmail, mnemonic); const wrongEmailKeystore = { ...encryptionKeystore }; wrongEmailKeystore.userEmail = 'wrong email'; - await expect(openEncryptionKeystore(wrongEmailKeystore, password)).rejects.toThrow(FailedToOpenEncryptionKeyStore); + await expect(openEncryptionKeystore(wrongEmailKeystore, mnemonic)).rejects.toThrow(FailedToOpenEncryptionKeyStore); const wrongPublicKeyKeystore = { ...encryptionKeystore }; const newKeys = genHybridKeys(); wrongPublicKeyKeystore.publicKey = uint8ArrayToBase64(newKeys.publicKey); - await expect(openEncryptionKeystore(wrongPublicKeyKeystore, password)).rejects.toThrow( + await expect(openEncryptionKeystore(wrongPublicKeyKeystore, mnemonic)).rejects.toThrow( FailedToOpenEncryptionKeyStore, ); }); - - it('should throw an error if salt is too short or too long', async () => { - const password = 'user password'; - const { encryptionKeystore } = await createEncryptionAndRecoveryKeystores(mockUserEmail, password); - - const shortSaltKeystore = { ...encryptionKeystore }; - shortSaltKeystore.salt = 'WzEsIDIsIDMsIDQsIDUsIDYsIDcsIDhd'; - - expect(shortSaltKeystore.salt).not.toEqual(encryptionKeystore.salt); - await expect(openEncryptionKeystore(shortSaltKeystore, password)).rejects.toThrow(InvalidInputKeyStore); - - const longSaltKeystore = { ...encryptionKeystore }; - longSaltKeystore.salt = 'WzEsIDIsIDMsIDQsIDUsIDYsIDcsIDgsIDksIDEwLCAxMSwgMTIsIDEzLCAxNCwgMTUsIDE2LCAxN10='; - - expect(longSaltKeystore.salt).not.toEqual(encryptionKeystore.salt); - await expect(openEncryptionKeystore(longSaltKeystore, password)).rejects.toThrow(InvalidInputKeyStore); - }); }); diff --git a/tests/keystore-crypto/keys.test.ts b/tests/keystore-crypto/keys.test.ts index f794f01..f721764 100644 --- a/tests/keystore-crypto/keys.test.ts +++ b/tests/keystore-crypto/keys.test.ts @@ -1,23 +1,20 @@ import { describe, expect, it } from 'vitest'; -import { deriveEncryptionKeystoreKey, deriveRecoveryKey } from '../../src/keystore-crypto/core'; +import { deriveEncryptionKeystoreKeyFromMnemonic, deriveRecoveryKey } from '../../src/keystore-crypto/core'; import { AES_KEY_BYTE_LENGTH } from '../../src/constants'; import { genMnemonic } from '../../src/utils'; -import { generateSalt } from '../../src/derive-password'; describe('Test keystore key generation functions', () => { it('correct symmetric key length', async () => { - const password = 'user password'; - const salt = generateSalt(); - const key = await deriveEncryptionKeystoreKey(password, salt); + const mnemonic = genMnemonic(); + const key = await deriveEncryptionKeystoreKeyFromMnemonic(mnemonic); expect(key.length).toBe(AES_KEY_BYTE_LENGTH); }); it('should give different derived keys for the same baseKey', async () => { const codes = genMnemonic(); - const password = 'user password'; - const salt = generateSalt(); + const mnemonic = genMnemonic(); - const encryptionKey = await deriveEncryptionKeystoreKey(password, salt); + const encryptionKey = await deriveEncryptionKeystoreKeyFromMnemonic(mnemonic); const recoveryKey = await deriveRecoveryKey(codes); expect(encryptionKey).not.toStrictEqual(recoveryKey);