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
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export {
FailedToCreateKeyStores,
FailedToOpenRecoveryKeyStore,
FailedToChangePasswordForKeyStore,
InvalidInputKeyStore,
} from './keystore-crypto';
export { generateKyberKeys, encapsulateKyber, decapsulateKyber } from './post-quantum-crypto';
export { encryptSymmetrically, decryptSymmetrically, genSymmetricKey } from './symmetric-crypto';
Expand Down
7 changes: 4 additions & 3 deletions src/keystore-crypto/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ export async function encryptKeystoreContent(
salt?: Uint8Array,
): Promise<EncryptedKeystore> {
try {
const aux = UTF8ToUint8(userEmail + type);
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 keystoreSalt = salt ? uint8ArrayToBase64(salt) : undefined;
const keystore: EncryptedKeystore = {
userEmail,
type,
Expand All @@ -52,7 +53,7 @@ export async function decryptKeystoreContent(
encryptedKeystore: EncryptedKeystore,
): Promise<HybridKeyPair> {
try {
const aux = UTF8ToUint8(encryptedKeystore.userEmail + encryptedKeystore.type);
const aux = UTF8ToUint8(encryptedKeystore.userEmail + encryptedKeystore.type + encryptedKeystore.publicKey);
const publicKey = base64ToUint8Array(encryptedKeystore.publicKey);
const ciphertext = base64ToUint8Array(encryptedKeystore.privateKeyEncrypted);
const secretKey = await decryptSymmetrically(kesytoreOpeningKey, ciphertext, aux);
Expand Down
8 changes: 6 additions & 2 deletions src/keystore-crypto/emailEncryptionKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
FailedToCreateKeyStores,
FailedToOpenRecoveryKeyStore,
FailedToChangePasswordForKeyStore,
InvalidInputKeyStore,
} from './errors';
import { ARGON2ID_SALT_BYTE_LENGTH } from '../constants';

Expand Down Expand Up @@ -67,12 +68,13 @@ export async function openEncryptionKeystore(
try {
const salt = encryptedKeystore.salt ? base64ToUint8Array(encryptedKeystore.salt) : new Uint8Array();
if (encryptedKeystore.type !== KeystoreType.ENCRYPTION || salt.length !== ARGON2ID_SALT_BYTE_LENGTH) {
throw new Error('Input is invalid');
throw new InvalidInputKeyStore();
}
const secretKey = await deriveEncryptionKeystoreKey(password, salt);
const keys = await decryptKeystoreContent(secretKey, encryptedKeystore);
return keys;
} catch (error) {
if (error instanceof InvalidInputKeyStore) throw error;
throw new FailedToOpenEncryptionKeyStore(error instanceof Error ? error.message : String(error));
}
}
Expand All @@ -91,12 +93,13 @@ export async function openRecoveryKeystore(
): Promise<HybridKeyPair> {
try {
if (encryptedKeystore.type !== KeystoreType.RECOVERY) {
throw new Error('Input is invalid');
throw new InvalidInputKeyStore();
}
const recoveryKey = await deriveRecoveryKey(recoveryCodes);
const keys = await decryptKeystoreContent(recoveryKey, encryptedKeystore);
return keys;
} catch (error) {
if (error instanceof InvalidInputKeyStore) throw error;
throw new FailedToOpenRecoveryKeyStore(error instanceof Error ? error.message : String(error));
}
}
Expand Down Expand Up @@ -129,6 +132,7 @@ export async function changePasswordForEncryptionKeystore(

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

export class InvalidInputKeyStore extends Error {
constructor() {
super('Invalid input');

Object.setPrototypeOf(this, InvalidInputKeyStore.prototype);
}
}

export class FailedToOpenEncryptionKeyStore extends Error {
constructor(errorMsg?: string) {
super('Failed to open encryption keystore: ' + errorMsg);
Expand Down
45 changes: 43 additions & 2 deletions tests/keystore-crypto/emailEncryptionKeys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import {
FailedToCreateKeyStores,
FailedToOpenRecoveryKeyStore,
FailedToChangePasswordForKeyStore,
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 { genHybridKeys } from '../../src/hybrid-crypto';

describe('Test keystore create/open functions', async () => {
const mockUserEmail = 'mock user email';
Expand Down Expand Up @@ -57,8 +61,8 @@ describe('Test keystore create/open functions', async () => {
password,
);

await expect(openEncryptionKeystore(recoveryKeystore, password)).rejects.toThrow(FailedToOpenEncryptionKeyStore);
await expect(openRecoveryKeystore(recoveryCodes, encryptionKeystore)).rejects.toThrow(FailedToOpenRecoveryKeyStore);
await expect(openEncryptionKeystore(recoveryKeystore, password)).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 () => {
Expand Down Expand Up @@ -102,4 +106,41 @@ describe('Test keystore create/open functions', async () => {
FailedToChangePasswordForKeyStore,
);
});

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);

const wrongEmailKeystore = { ...encryptionKeystore };
wrongEmailKeystore.userEmail = 'wrong email';
await expect(openEncryptionKeystore(wrongEmailKeystore, password)).rejects.toThrow(FailedToOpenEncryptionKeyStore);

const wrongPublicKeyKeystore = { ...encryptionKeystore };
const newKeys = genHybridKeys();
wrongPublicKeyKeystore.publicKey = uint8ArrayToBase64(newKeys.publicKey);
await expect(openEncryptionKeystore(wrongPublicKeyKeystore, password)).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);
});
});
Loading