diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 59da0b5..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/package.json b/package.json index a3eff97..19d3f4f 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "@noble/post-quantum": "^0.5.2", "@scure/bip39": "^2.0.1", "axios": "^1.11.0", - "buffer": "^6.0.3", "flexsearch": "^0.8.205", "hash-wasm": "^4.12.0", "husky": "^9.1.7", diff --git a/src/email-crypto/emailKeys.ts b/src/email-crypto/emailKeys.ts index 8c2732e..e11e3bc 100644 --- a/src/email-crypto/emailKeys.ts +++ b/src/email-crypto/emailKeys.ts @@ -1,28 +1,29 @@ import { generateEccKeys } from '../asymmetric-crypto'; import { generateKyberKeys } from '../post-quantum-crypto'; -import { PublicKeys, PrivateKeys } from '../types'; +import { EmailKeys } from '../types'; /** * Generates public and private keys for email service. * * @returns The user's private and public keys */ -export async function generateEmailKeys(): Promise<{ publicKeys: PublicKeys; privateKeys: PrivateKeys }> { +export async function generateEmailKeys(): Promise { try { const kyberKeys = generateKyberKeys(); const keys = await generateEccKeys(); - const privateKeys: PrivateKeys = { - eccPrivateKey: keys.privateKey, - kyberPrivateKey: kyberKeys.secretKey, + const emailKeys: EmailKeys = { + publicKeys: { + eccPublicKey: keys.publicKey, + kyberPublicKey: kyberKeys.publicKey, + }, + privateKeys: { + eccPrivateKey: keys.privateKey, + kyberPrivateKey: kyberKeys.secretKey, + }, }; - const publicKeys: PublicKeys = { - eccPublicKey: keys.publicKey, - kyberPublicKey: kyberKeys.publicKey, - }; - - return { publicKeys, privateKeys }; + return emailKeys; } catch (error) { throw new Error('Failed to generate keys for email service', { cause: error }); } diff --git a/src/keystore-crypto/core.ts b/src/keystore-crypto/core.ts index 8f46905..8cb177e 100644 --- a/src/keystore-crypto/core.ts +++ b/src/keystore-crypto/core.ts @@ -1,8 +1,10 @@ import { encryptSymmetrically, decryptSymmetrically } from '../symmetric-crypto'; -import { base64ToUint8Array, uint8ArrayToBase64, UTF8ToUint8, mnemonicToBytes } from '../utils'; +import { base64ToUint8Array, uint8ArrayToBase64, UTF8ToUint8, mnemonicToBytes, publicKeyToBase64 } from '../utils'; import { deriveSymmetricCryptoKeyFromContext } from '../derive-key'; import { CONTEXT_ENC_KEYSTORE, AES_KEY_BIT_LENGTH, CONTEXT_RECOVERY } from '../constants'; import { getBytesFromData } from '../hash'; +import { EmailKeys, EncryptedKeystore, KeystoreType } from 'types'; +import { exportPrivateKey, importPrivateKey, importPublicKey } from '../asymmetric-crypto'; /** * Encrypts the keystore content using symmetric encryption @@ -15,15 +17,30 @@ import { getBytesFromData } from '../hash'; */ export async function encryptKeystoreContent( secretKey: CryptoKey, - content: string, - userID: string, - tag: string, -): Promise { + keys: EmailKeys, + userEmail: string, + type: KeystoreType, +): Promise { try { - const aux = UTF8ToUint8(userID + tag); - const message = base64ToUint8Array(content); - const result = await encryptSymmetrically(secretKey, message, aux); - return result; + const aux = UTF8ToUint8(userEmail + type); + const publicKeys = await publicKeyToBase64(keys.publicKeys); + const kyberPrivateKeyEnc = await encryptSymmetrically(secretKey, keys.privateKeys.kyberPrivateKey, aux); + const eccPrivateKey = await exportPrivateKey(keys.privateKeys.eccPrivateKey); + const eccPrivateKeyEnc = await encryptSymmetrically(secretKey, eccPrivateKey, aux); + const encryptedKeys = { + publicKeys, + privateKeys: { + kyberPrivateKeyBase64: uint8ArrayToBase64(kyberPrivateKeyEnc), + eccPrivateKeyBase64: uint8ArrayToBase64(eccPrivateKeyEnc), + }, + }; + + const keystore: EncryptedKeystore = { + userEmail, + type, + encryptedKeys, + }; + return keystore; } catch (error) { throw new Error('Failed to encrypt keystore content', { cause: error }); } @@ -40,15 +57,29 @@ export async function encryptKeystoreContent( */ export async function decryptKeystoreContent( secretKey: CryptoKey, - encryptedKeys: Uint8Array, - userEmail: string, - tag: string, -): Promise { + encryptedKeystore: EncryptedKeystore, +): Promise { try { - const aux = UTF8ToUint8(userEmail + tag); - const content = await decryptSymmetrically(secretKey, encryptedKeys, aux); - const result = uint8ArrayToBase64(content); - return result; + const aux = UTF8ToUint8(encryptedKeystore.userEmail + encryptedKeystore.type); + const kyberPublicKey = base64ToUint8Array(encryptedKeystore.encryptedKeys.publicKeys.kyberPublicKeyBase64); + const eccPublicArray = base64ToUint8Array(encryptedKeystore.encryptedKeys.publicKeys.eccPublicKeyBase64); + const eccPublicKey = await importPublicKey(eccPublicArray); + const encKyberPrivateKey = base64ToUint8Array(encryptedKeystore.encryptedKeys.privateKeys.kyberPrivateKeyBase64); + const kyberPrivateKey = await decryptSymmetrically(secretKey, encKyberPrivateKey, aux); + const eccEncArray = base64ToUint8Array(encryptedKeystore.encryptedKeys.privateKeys.eccPrivateKeyBase64); + const eccKey = await decryptSymmetrically(secretKey, eccEncArray, aux); + const eccPrivateKey = await importPrivateKey(eccKey); + const keys = { + publicKeys: { + kyberPublicKey, + eccPublicKey, + }, + privateKeys: { + kyberPrivateKey, + eccPrivateKey, + }, + }; + return keys; } catch (error) { throw new Error('Failed to decrypt keystore content', { cause: error }); } diff --git a/src/keystore-crypto/emailEncryptionKey.ts b/src/keystore-crypto/emailEncryptionKey.ts index e13a247..f6caa5b 100644 --- a/src/keystore-crypto/emailEncryptionKey.ts +++ b/src/keystore-crypto/emailEncryptionKey.ts @@ -1,5 +1,5 @@ import { EmailKeys, EncryptedKeystore, KeystoreType } from '../types'; -import { emailKeysToBase64, base64ToEmailKeys, genMnemonic } from '../utils'; +import { genMnemonic } from '../utils'; import { encryptKeystoreContent, decryptKeystoreContent, deriveEncryptionKeystoreKey, deriveRecoveryKey } from './core'; import { generateEmailKeys } from '../email-crypto'; @@ -20,23 +20,14 @@ export async function createEncryptionAndRecoveryKeystores( }> { try { const keys = await generateEmailKeys(); - const content = await emailKeysToBase64(keys); const secretKey = await deriveEncryptionKeystoreKey(baseKey); - const ciphertext = await encryptKeystoreContent(secretKey, content, userEmail, KeystoreType.ENCRYPTION); - const encryptionKeystore: EncryptedKeystore = { - userEmail, - type: KeystoreType.ENCRYPTION, - encryptedKeys: ciphertext, - }; + const encryptionKeystore = await encryptKeystoreContent(secretKey, keys, userEmail, KeystoreType.ENCRYPTION); + const recoveryCodes = genMnemonic(); const recoveryKey = await deriveRecoveryKey(recoveryCodes); - const encKeys = await encryptKeystoreContent(recoveryKey, content, userEmail, KeystoreType.RECOVERY); - const recoveryKeystore: EncryptedKeystore = { - userEmail, - type: KeystoreType.RECOVERY, - encryptedKeys: encKeys, - }; + const recoveryKeystore = await encryptKeystoreContent(recoveryKey, keys, userEmail, KeystoreType.RECOVERY); + return { encryptionKeystore, recoveryKeystore, recoveryCodes }; } catch (error) { throw new Error('Failed to create encryption and recovery keystores', { cause: error }); @@ -60,13 +51,7 @@ export async function openEncryptionKeystore( throw new Error('Input is invalid'); } const secretKey = await deriveEncryptionKeystoreKey(baseKey); - const json = await decryptKeystoreContent( - secretKey, - encryptedKeystore.encryptedKeys, - encryptedKeystore.userEmail, - KeystoreType.ENCRYPTION, - ); - const keys: EmailKeys = await base64ToEmailKeys(json); + const keys = await decryptKeystoreContent(secretKey, encryptedKeystore); return keys; } catch (error) { throw new Error('Failed to open encryption keystore', { cause: error }); @@ -90,13 +75,7 @@ export async function openRecoveryKeystore( throw new Error('Input is invalid'); } const recoveryKey = await deriveRecoveryKey(recoveryCodes); - const json = await decryptKeystoreContent( - recoveryKey, - encryptedKeystore.encryptedKeys, - encryptedKeystore.userEmail, - KeystoreType.RECOVERY, - ); - const keys: EmailKeys = await base64ToEmailKeys(json); + const keys = await decryptKeystoreContent(recoveryKey, encryptedKeystore); return keys; } catch (error) { throw new Error('Failed to open recovery keystore', { cause: error }); diff --git a/src/keystore-service/api.ts b/src/keystore-service/api.ts index 5378ef8..d0f8cb6 100644 --- a/src/keystore-service/api.ts +++ b/src/keystore-service/api.ts @@ -1,6 +1,6 @@ import axios, { AxiosResponse, AxiosError } from 'axios'; -import { KeystoreType, EncryptedKeystore, PublicKeys } from '../types'; -import { base64ToEncryptedKeystore, encryptedKeystoreToBase64, base64ToPublicKey } from '../utils'; +import { KeystoreType, EncryptedKeystore, PublicKeys, PublicKeysBase64 } from '../types'; +import { base64ToPublicKey } from '../utils'; export class KeyServiceAPI { private readonly baseUrl: string; @@ -35,10 +35,11 @@ export class KeyServiceAPI { * @param url - The user specific part of the url * @returns Server response */ - async sendEncryptedKeystoreToServer(encryptedKeystore: string, url: string): Promise { + async uploadKeystoreToServer(encryptedKeystore: EncryptedKeystore): Promise { try { + const url = `${this.baseUrl}/uploadKeystore`; const response = await axios.post( - this.baseUrl + `${url}`, + url, { encryptedKeystore }, { withCredentials: true, @@ -61,10 +62,11 @@ export class KeyServiceAPI { * @param url - The user-specific part of the url * @returns The user's encrypted keystore as base64 string */ - async requestEncryptedKeystore(userID: string, keystoreType: KeystoreType): Promise { + async getKeystoreFromServer(userID: string, keystoreType: KeystoreType): Promise { try { - const url = `/downloadKeystore/${userID}/${keystoreType}`; - const response = await axios.get(this.baseUrl + `${url}`, { + const url = `${this.baseUrl}/downloadKeystore`; + const response = await axios.get(url, { + params: { userID, keystoreType }, withCredentials: true, headers: { 'Content-Type': 'application/json', @@ -80,67 +82,16 @@ export class KeyServiceAPI { } } - /** - * Uploads encrypted keystore to the server - * - * @param encryptedKeystore - The encrypted keystore - * @returns Server response - */ - async uploadKeystoreToServer(encryptedKeystore: EncryptedKeystore): Promise { - try { - const userID = encryptedKeystore.userEmail; - const keystoreType = encryptedKeystore.type; - const url = `/uploadKeystore/${userID}/${keystoreType}`; - const ciphertextBase64 = encryptedKeystoreToBase64(encryptedKeystore); - const result = await this.sendEncryptedKeystoreToServer(ciphertextBase64, url); - return result; - } catch (error) { - throw new Error('Failed to upload keystore to the server', { cause: error }); - } - } - - /** - * Gets a user's encrypted keystore containing encryption keys from the server - * - * @returns Encrypted keystore containing encryption keys - */ - async getEncryptionKeystoreFromServer(userEmail: string): Promise { - return this.getKeystoreFromServer(userEmail, KeystoreType.ENCRYPTION); - } - - /** - * Gets a user's encrypted Recovery Keystore from the server - * - * @returns Encrypted Recovery Keystore - */ - async getRecoveryKeystoreFromServer(userEmail: string): Promise { - return this.getKeystoreFromServer(userEmail, KeystoreType.RECOVERY); - } - - /** - * Gets a user's encrypted keystore from the server - * - * @param type - The requested keystore's type - * @returns Encrypted Keytore - */ - async getKeystoreFromServer(userEmail: string, type: KeystoreType): Promise { - try { - const response = await this.requestEncryptedKeystore(userEmail, type); - const result = base64ToEncryptedKeystore(response); - return result; - } catch (error) { - throw new Error('Failed to retrieve keystore from the server', { cause: error }); - } - } /** * Obtains recipients public keys from the server * * @param emails - The recipients' emails * @returns The list of recipients' public keys */ - async getRecipientsPublicKeysFromServer(emails: string[]): Promise { + async getRecipientsPublicKeysFromServer(emails: string[]): Promise { try { - const response = await axios.get('/api/getPublicKeys', { + const url = `${this.baseUrl}/getPublicKeys`; + const response = await axios.get(url, { params: { emails: emails, }, @@ -166,7 +117,7 @@ export class KeyServiceAPI { */ async getRecipientsPublicKeys(emails: string[]): Promise { try { - const publicKeysBase64: string[] = await this.getRecipientsPublicKeysFromServer(emails); + const publicKeysBase64: PublicKeysBase64[] = await this.getRecipientsPublicKeysFromServer(emails); const result: PublicKeys[] = []; for (const keyBase64 of publicKeysBase64) { const publicKeys = await base64ToPublicKey(keyBase64); diff --git a/src/types.ts b/src/types.ts index 15ecaa9..5560845 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,7 @@ export type EncryptedKeystore = { userEmail: string; type: KeystoreType; - encryptedKeys: Uint8Array; + encryptedKeys: EmailKeysEncrypted; }; export type User = { @@ -18,16 +18,31 @@ export type PublicKeys = { kyberPublicKey: Uint8Array; }; +export type PublicKeysBase64 = { + eccPublicKeyBase64: string; + kyberPublicKeyBase64: string; +}; + export type PrivateKeys = { eccPrivateKey: CryptoKey; kyberPrivateKey: Uint8Array; }; +export type PrivateKeysEncrypted = { + eccPrivateKeyBase64: string; + kyberPrivateKeyBase64: string; +}; + export type EmailKeys = { publicKeys: PublicKeys; privateKeys: PrivateKeys; }; +export type EmailKeysEncrypted = { + publicKeys: PublicKeysBase64; + privateKeys: PrivateKeysEncrypted; +}; + export type HybridEncryptedEmail = { encryptedKey: HybridEncKey; enc: Uint8Array; @@ -92,6 +107,6 @@ export interface EmailSearchResult { } export enum KeystoreType { - ENCRYPTION = 'Encryption keystore', - RECOVERY = 'Recovery keystore', + ENCRYPTION = 'Encryption', + RECOVERY = 'Recovery', } diff --git a/src/utils/keyConverters.ts b/src/utils/keyConverters.ts index c4269ac..d299e06 100644 --- a/src/utils/keyConverters.ts +++ b/src/utils/keyConverters.ts @@ -1,92 +1,6 @@ import { uint8ArrayToBase64, base64ToUint8Array } from '.'; -import { EmailKeys, EncryptedKeystore, PublicKeys, PrivateKeys } from '../types'; -import { exportPublicKey, exportPrivateKey, importPublicKey, importPrivateKey } from '../asymmetric-crypto'; - -/** - * Converts encryption keys to base64 - * - * @param keys - The encryption keys - * @returns The resulting base64 string - */ -export async function emailKeysToBase64(keys: EmailKeys): Promise { - try { - const publicKeys = await publicKeyToBase64(keys.publicKeys); - const privateKeys = await privateKeyToBase64(keys.privateKeys); - const json = JSON.stringify({ - publicKeys, - privateKeys, - }); - const base64 = btoa(json); - return base64; - } catch (error) { - throw new Error('Failed to convert email keys to base64', { cause: error }); - } -} - -/** - * Converts encrypted keystore to base64 - * - * @param keystore - The encrypted keystore - * @returns The resulting base64 string - */ -export function encryptedKeystoreToBase64(keystore: EncryptedKeystore): string { - try { - const ciphertext = uint8ArrayToBase64(keystore.encryptedKeys); - const json = JSON.stringify({ - userEmail: keystore.userEmail, - type: keystore.type.toString(), - encryptedKeys: ciphertext, - }); - const base64 = btoa(json); - return base64; - } catch (error) { - throw new Error('Failed to convert encrypted keystore to base64', { cause: error }); - } -} - -/** - * Converts base64 to email keys - * - * @param base64 - The base64 input - * @returns The resulting email keys - */ -export async function base64ToEmailKeys(base64: string): Promise { - try { - const json = atob(base64); - const obj = JSON.parse(json); - const publicKeys = await base64ToPublicKey(obj.publicKeys); - const privateKeys = await base64ToPrivateKey(obj.privateKeys); - const result: EmailKeys = { - publicKeys, - privateKeys, - }; - return result; - } catch (error) { - throw new Error('Failed to convert base64 to encryption key', { cause: error }); - } -} - -/** - * Converts base64 to encrypted keystore - * - * @param base64 - The base64 input - * @returns The resulting encrypted keystore - */ -export function base64ToEncryptedKeystore(base64: string): EncryptedKeystore { - try { - const json = atob(base64); - const obj = JSON.parse(json); - const ciphertext = base64ToUint8Array(obj.encryptedKeys); - const result: EncryptedKeystore = { - userEmail: obj.userEmail, - type: obj.type, - encryptedKeys: ciphertext, - }; - return result; - } catch (error) { - throw new Error('Failed to convert base64 to encrypted keystore', { cause: error }); - } -} +import { PublicKeys, PublicKeysBase64 } from '../types'; +import { exportPublicKey, importPublicKey } from '../asymmetric-crypto'; /** * Converts a base64 string into PublicKeys type. @@ -94,13 +8,11 @@ export function base64ToEncryptedKeystore(base64: string): EncryptedKeystore { * @param base64 - The base64 representation of the public key. * @returns The resulting PublicKeys. */ -export async function base64ToPublicKey(base64: string): Promise { +export async function base64ToPublicKey(base64: PublicKeysBase64): Promise { try { - const json = atob(base64); - const obj = JSON.parse(json); - const eccPublicKeyBytes = base64ToUint8Array(obj.eccPublicKey); + const eccPublicKeyBytes = base64ToUint8Array(base64.eccPublicKeyBase64); const eccPublicKey = await importPublicKey(eccPublicKeyBytes); - const kyberPublicKey = base64ToUint8Array(obj.kyberPublicKey); + const kyberPublicKey = base64ToUint8Array(base64.kyberPublicKeyBase64); return { eccPublicKey: eccPublicKey, kyberPublicKey: kyberPublicKey, @@ -111,63 +23,20 @@ export async function base64ToPublicKey(base64: string): Promise { } /** - * Converts a PublicKeys type into base64 string. + * Converts a PublicKeys type into PublicKeysBase64. * * @param key - The PublicKeys key. - * @returns The resulting base64 string. + * @returns The resulting PublicKeysBase64. */ -export async function publicKeyToBase64(key: PublicKeys): Promise { +export async function publicKeyToBase64(key: PublicKeys): Promise { try { const eccPublicKeyArray = await exportPublicKey(key.eccPublicKey); - const json = JSON.stringify({ - eccPublicKey: uint8ArrayToBase64(eccPublicKeyArray), - kyberPublicKey: uint8ArrayToBase64(key.kyberPublicKey), - }); - const base64 = btoa(json); - return base64; - } catch (error) { - throw new Error('Failed to convert key of the type PublicKeys to base64', { cause: error }); - } -} - -/** - * Converts a base64 string into PrivateKeys type. - * - * @param base64 - The base64 representation of the private key. - * @returns The resulting PrivateKeys. - */ -export async function base64ToPrivateKey(base64: string): Promise { - try { - const json = atob(base64); - const obj = JSON.parse(json); - const eccPrivateKeyBytes = base64ToUint8Array(obj.eccPrivateKey); - const eccPrivateKey = await importPrivateKey(eccPrivateKeyBytes); - const kyberPrivateKey = base64ToUint8Array(obj.kyberPrivateKey); - return { - eccPrivateKey, - kyberPrivateKey, + const keys = { + eccPublicKeyBase64: uint8ArrayToBase64(eccPublicKeyArray), + kyberPublicKeyBase64: uint8ArrayToBase64(key.kyberPublicKey), }; + return keys; } catch (error) { - throw new Error('Failed to convert base64 to PrivateKeys', { cause: error }); - } -} - -/** - * Converts a PrivateKeys type into base64 string. - * - * @param key - The PrivateKeys key. - * @returns The resulting base64 string. - */ -export async function privateKeyToBase64(key: PrivateKeys): Promise { - try { - const eccPrivateKeyArray = await exportPrivateKey(key.eccPrivateKey); - const json = JSON.stringify({ - eccPrivateKey: uint8ArrayToBase64(eccPrivateKeyArray), - kyberPrivateKey: uint8ArrayToBase64(key.kyberPrivateKey), - }); - const base64 = btoa(json); - return base64; - } catch (error) { - throw new Error('Failed to convert key of the type PrivateKeys to base64', { cause: error }); + throw new Error('Failed to convert key of the type PublicKeys to PublicKeysBase64', { cause: error }); } } diff --git a/tests/email-search/helper.ts b/tests/email-search/helper.ts index 1b43d78..f856e4d 100644 --- a/tests/email-search/helper.ts +++ b/tests/email-search/helper.ts @@ -50,7 +50,7 @@ export function getEmailSize(email: Email): number { return emailToBinary(email).byteLength; } -export const generateEmailWithGivenText = (data: string): Email => { +const generateEmailWithGivenText = (data: string): Email => { const sender = randomUser(); const recipient = randomUser(); diff --git a/tests/keystore-crypto/converters.test.ts b/tests/keystore-crypto/converters.test.ts deleted file mode 100644 index e4e0497..0000000 --- a/tests/keystore-crypto/converters.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { - base64ToEncryptedKeystore, - base64ToEmailKeys, - encryptedKeystoreToBase64, - emailKeysToBase64, -} from '../../src/utils'; -import { EncryptedKeystore, EmailKeys, KeystoreType } from '../../src/types'; -import { generateEccKeys } from '../../src/asymmetric-crypto'; -import { generateKyberKeys } from '../../src/post-quantum-crypto/kyber768'; -import { encryptSymmetrically, genSymmetricCryptoKey } from '../../src/symmetric-crypto'; - -describe('Test converter functions', () => { - it('should thow an error if not correct key', async () => { - const serializedKey = 'bad key'; - await expect(base64ToEmailKeys(serializedKey)).rejects.toThrowError(/Failed to convert base64 to encryption key/); - expect(() => base64ToEncryptedKeystore(serializedKey)).toThrowError( - /Failed to convert base64 to encrypted keystore/, - ); - }); - - it('should sucessfully serialize and decerialize an encryption key', async () => { - const keyPair = await generateEccKeys(); - const kyberKeyPair = generateKyberKeys(); - const key: EmailKeys = { - publicKeys: { - eccPublicKey: keyPair.publicKey, - kyberPublicKey: kyberKeyPair.publicKey, - }, - privateKeys: { - eccPrivateKey: keyPair.privateKey, - kyberPrivateKey: kyberKeyPair.secretKey, - }, - }; - const serializedKey = await emailKeysToBase64(key); - const deserializedKey = await base64ToEmailKeys(serializedKey); - - expect(deserializedKey).toStrictEqual(key); - }); - - it('should sucessfully serialize and decerialize an encrypted keystore', async () => { - const sk = await genSymmetricCryptoKey(); - const message = new Uint8Array([1, 2, 3, 4, 5]); - const aux = new Uint8Array([2, 3, 4, 5, 6, 7, 8, 9, 10]); - const freeFiled = new Uint8Array([7, 7, 7]); - const cipher = await encryptSymmetrically(sk, message, aux, freeFiled); - - const keystore: EncryptedKeystore = { - userEmail: 'mock-user-email', - type: KeystoreType.ENCRYPTION, - encryptedKeys: cipher, - }; - const serializedKeystore = await encryptedKeystoreToBase64(keystore); - const deserializedKeystore = await base64ToEncryptedKeystore(serializedKeystore); - - expect(deserializedKeystore).toStrictEqual(keystore); - }); -}); diff --git a/tests/keystore-crypto/core.test.ts b/tests/keystore-crypto/core.test.ts index 4048516..43cea2d 100644 --- a/tests/keystore-crypto/core.test.ts +++ b/tests/keystore-crypto/core.test.ts @@ -2,16 +2,17 @@ import { describe, expect, it } from 'vitest'; import { genSymmetricCryptoKey } from '../../src/symmetric-crypto'; import { encryptKeystoreContent, decryptKeystoreContent } from '../../src/keystore-crypto/core'; import { KeystoreType } from '../../src/types'; +import { generateEmailKeys } from '../../src/email-crypto'; describe('Test keystore keys functions', () => { it('should sucessfully create and open a keystore', async () => { const key = await genSymmetricCryptoKey(); - const mockContext = btoa('mock context string'); - const mockUserID = 'mock user ID'; + const mockKeys = await generateEmailKeys(); + const mockUserEmail = 'mock user email'; const mockTag = KeystoreType.ENCRYPTION; - const keystore = await encryptKeystoreContent(key, mockContext, mockUserID, mockTag); - const decryptedContent = await decryptKeystoreContent(key, keystore, mockUserID, mockTag); + const keystore = await encryptKeystoreContent(key, mockKeys, mockUserEmail, mockTag); + const decryptedContent = await decryptKeystoreContent(key, keystore); - expect(mockContext).toBe(decryptedContent); + expect(mockKeys).toEqual(decryptedContent); }); }); diff --git a/tests/keystore-service/api.test.ts b/tests/keystore-service/api.test.ts index a45b61e..85db35e 100644 --- a/tests/keystore-service/api.test.ts +++ b/tests/keystore-service/api.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import axios from 'axios'; import { KeystoreType, EncryptedKeystore } from '../../src/types'; import { getKeyServiceAPI } from '../../src/keystore-service'; -import { encryptedKeystoreToBase64 } from '../../src/utils'; vi.mock('axios'); @@ -13,17 +12,26 @@ describe('Test keystore send/get functions', () => { const mockUserEmail = 'mock email'; const mockType = KeystoreType.ENCRYPTION; - const mockCiphertext = new Uint8Array([1, 2, 3, 4, 5]); + const mockEncryptedKeys = { + publicKeys: { + eccPublicKeyBase64: 'mock ecc public key base64', + kyberPublicKeyBase64: 'mock kyber public key base64', + }, + privateKeys: { + eccPrivateKeyBase64: 'mock ecc private key base64', + kyberPrivateKeyBase64: 'mock kyber private key base64', + }, + }; + const mockEncryptedKeystore: EncryptedKeystore = { userEmail: mockUserEmail, - encryptedKeys: mockCiphertext, + encryptedKeys: mockEncryptedKeys, type: mockType, }; - const mockEncryptedKeystoreBase64 = encryptedKeystoreToBase64(mockEncryptedKeystore); const service = getKeyServiceAPI('test-base-url'); describe('sendKeystore', () => { - const url = `/uploadKeystore/${mockUserEmail}`; + const url = 'test-base-url/uploadKeystore'; it('should successfully send keystore with valid parameters', async () => { const mockResponse = { @@ -36,12 +44,12 @@ describe('Test keystore send/get functions', () => { vi.mocked(axios.post).mockResolvedValue(mockResponse); - const result = await service.sendEncryptedKeystoreToServer(mockEncryptedKeystoreBase64, url); + const result = await service.uploadKeystoreToServer(mockEncryptedKeystore); expect(axios.post).toHaveBeenCalledWith( - 'test-base-url' + url, + url, { - encryptedKeystore: mockEncryptedKeystoreBase64, + encryptedKeystore: mockEncryptedKeystore, }, { withCredentials: true, @@ -65,7 +73,7 @@ describe('Test keystore send/get functions', () => { vi.mocked(axios.post).mockRejectedValueOnce(unauthorizedError); vi.mocked(axios.isAxiosError).mockReturnValueOnce(true); - await expect(service.sendEncryptedKeystoreToServer(mockEncryptedKeystoreBase64, url)).rejects.toThrow( + await expect(service.uploadKeystoreToServer(mockEncryptedKeystore)).rejects.toThrow( 'Unauthorized: Invalid or expired token', ); }); @@ -82,7 +90,7 @@ describe('Test keystore send/get functions', () => { vi.mocked(axios.post).mockRejectedValueOnce(forbiddenError); vi.mocked(axios.isAxiosError).mockReturnValueOnce(true); - await expect(service.sendEncryptedKeystoreToServer(mockEncryptedKeystoreBase64, url)).rejects.toThrow( + await expect(service.uploadKeystoreToServer(mockEncryptedKeystore)).rejects.toThrow( 'Forbidden: Insufficient permissions', ); }); @@ -91,9 +99,7 @@ describe('Test keystore send/get functions', () => { const networkError = new Error('Network Error'); vi.mocked(axios.post).mockRejectedValueOnce(networkError); - await expect(service.sendEncryptedKeystoreToServer(mockEncryptedKeystoreBase64, url)).rejects.toThrow( - /Failed to send keystore/, - ); + await expect(service.uploadKeystoreToServer(mockEncryptedKeystore)).rejects.toThrow(/Failed to send keystore/); }); it('should handle axios errors with an empty response', async () => { const errorWithNoResponse = { @@ -103,18 +109,16 @@ describe('Test keystore send/get functions', () => { vi.mocked(axios.post).mockRejectedValueOnce(errorWithNoResponse); vi.mocked(axios.isAxiosError).mockReturnValueOnce(true); - await expect(service.sendEncryptedKeystoreToServer(mockEncryptedKeystoreBase64, url)).rejects.toThrow( - /AxiosError/, - ); + await expect(service.uploadKeystoreToServer(mockEncryptedKeystore)).rejects.toThrow(/AxiosError/); }); }); describe('getKeystore', () => { - const url = `/downloadKeystore/${mockUserEmail}/${mockType}`; + const url = '/downloadKeystore'; it('should successfully retrieve keystore with valid parameters', async () => { const mockResponse = { - data: mockEncryptedKeystoreBase64, + data: mockEncryptedKeystore, status: 200, statusText: 'OK', headers: {}, @@ -123,15 +127,16 @@ describe('Test keystore send/get functions', () => { vi.mocked(axios.get).mockResolvedValueOnce(mockResponse); - const result = await service.requestEncryptedKeystore(mockUserEmail, mockType); + const result = await service.getKeystoreFromServer(mockUserEmail, mockType); expect(axios.get).toHaveBeenCalledWith('test-base-url' + url, { withCredentials: true, + params: { userID: mockUserEmail, keystoreType: mockType }, headers: { 'Content-Type': 'application/json', }, }); - expect(result).toEqual(mockEncryptedKeystoreBase64); + expect(result).toEqual(mockEncryptedKeystore); }); it('should handle 401 unauthorized error', async () => { @@ -146,7 +151,7 @@ describe('Test keystore send/get functions', () => { vi.mocked(axios.get).mockRejectedValueOnce(unauthorizedError); vi.mocked(axios.isAxiosError).mockReturnValueOnce(true); - await expect(service.requestEncryptedKeystore(mockUserEmail, mockType)).rejects.toThrow( + await expect(service.getKeystoreFromServer(mockUserEmail, mockType)).rejects.toThrow( 'Unauthorized: Invalid or expired token', ); }); @@ -163,7 +168,7 @@ describe('Test keystore send/get functions', () => { vi.mocked(axios.get).mockRejectedValueOnce(forbiddenError); vi.mocked(axios.isAxiosError).mockReturnValueOnce(true); - await expect(service.requestEncryptedKeystore(mockUserEmail, mockType)).rejects.toThrow( + await expect(service.getKeystoreFromServer(mockUserEmail, mockType)).rejects.toThrow( 'Forbidden: Insufficient permissions', ); }); @@ -180,7 +185,7 @@ describe('Test keystore send/get functions', () => { vi.mocked(axios.get).mockRejectedValueOnce(notFoundError); vi.mocked(axios.isAxiosError).mockReturnValueOnce(true); - await expect(service.requestEncryptedKeystore(mockUserEmail, mockType)).rejects.toThrow( + await expect(service.getKeystoreFromServer(mockUserEmail, mockType)).rejects.toThrow( 'Keystore not found for the specified user', ); }); @@ -189,7 +194,7 @@ describe('Test keystore send/get functions', () => { const networkError = new Error('Network Error'); vi.mocked(axios.get).mockRejectedValueOnce(networkError); - await expect(service.requestEncryptedKeystore(mockUserEmail, mockType)).rejects.toThrow( + await expect(service.getKeystoreFromServer(mockUserEmail, mockType)).rejects.toThrow( /Failed to retrive keystore/, ); }); @@ -202,7 +207,7 @@ describe('Test keystore send/get functions', () => { vi.mocked(axios.get).mockRejectedValueOnce(errorWithoutResponce); vi.mocked(axios.isAxiosError).mockReturnValueOnce(true); - await expect(service.requestEncryptedKeystore(mockUserEmail, mockType)).rejects.toThrow(/AxiosError/); + await expect(service.getKeystoreFromServer(mockUserEmail, mockType)).rejects.toThrow(/AxiosError/); }); }); }); diff --git a/tests/keystore-service/service.test.ts b/tests/keystore-service/service.test.ts deleted file mode 100644 index cdc8cd7..0000000 --- a/tests/keystore-service/service.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import axios from 'axios'; -import { EncryptedKeystore, KeystoreType } from '../../src/types'; -import { encryptedKeystoreToBase64 } from '../../src/utils'; -import { getKeyServiceAPI } from '../../src/keystore-service'; - -vi.mock('axios'); - -describe('Test keystore send/get service functions', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - const mockUserEmail = 'mock email'; - const service = getKeyServiceAPI('test-base-url'); - const mockType = KeystoreType.ENCRYPTION; - const mockCiphertext = new Uint8Array([1, 2, 3, 4, 5]); - const mockEncryptedKeystore: EncryptedKeystore = { - userEmail: mockUserEmail, - encryptedKeys: mockCiphertext, - type: mockType, - }; - const mockEncryptedKeystoreBase64 = encryptedKeystoreToBase64(mockEncryptedKeystore); - - const credentialField = { - withCredentials: true, - headers: { - 'Content-Type': 'application/json', - }, - }; - - describe('sendKeystore', () => { - const mockResponse = { - data: { success: true, message: 'Keystore uploaded successfully' }, - status: 200, - statusText: 'OK', - headers: {}, - config: {}, - }; - const url = `test-base-url/uploadKeystore/${mockUserEmail}`; - - it('should successfully send an encryption keystore', async () => { - vi.mocked(axios.post).mockResolvedValue(mockResponse); - - const result = await service.uploadKeystoreToServer(mockEncryptedKeystore); - - expect(axios.post).toHaveBeenCalledWith( - url + `/${mockType}`, - { - encryptedKeystore: mockEncryptedKeystoreBase64, - }, - credentialField, - ); - expect(result).toEqual(mockResponse); - }); - - it('should succethrow an error if cannot send an encryption keystore', async () => { - const unauthorizedError = { - isAxiosError: true, - response: { - status: 401, - data: { message: 'Unauthorized' }, - }, - }; - - vi.mocked(axios.post).mockRejectedValueOnce(unauthorizedError); - vi.mocked(axios.isAxiosError).mockReturnValueOnce(true); - - await expect(service.uploadKeystoreToServer(mockEncryptedKeystore)).rejects.toThrow( - /Failed to upload keystore to the server/, - ); - }); - }); - - describe('getKeystore', () => { - const mockResponse = { - data: mockEncryptedKeystoreBase64, - status: 200, - statusText: 'OK', - headers: {}, - config: {}, - }; - - const url = `test-base-url/downloadKeystore/${mockUserEmail}`; - it('should successfully retrieve encryption keystore', async () => { - const mockType = KeystoreType.ENCRYPTION; - - vi.mocked(axios.get).mockResolvedValueOnce(mockResponse); - const result = await service.getEncryptionKeystoreFromServer(mockUserEmail); - - expect(axios.get).toHaveBeenCalledWith(url + `/${mockType}`, credentialField); - expect(result).toStrictEqual(mockEncryptedKeystore); - }); - - it('should successfully retrieve recovery keystore', async () => { - const mockType = KeystoreType.RECOVERY; - - vi.mocked(axios.get).mockResolvedValueOnce(mockResponse); - const result = await service.getRecoveryKeystoreFromServer(mockUserEmail); - - expect(axios.get).toHaveBeenCalledWith(url + `/${mockType}`, credentialField); - expect(result).toEqual(mockEncryptedKeystore); - }); - }); -}); diff --git a/tests/utils/keyConverters.test.ts b/tests/utils/keyConverters.test.ts index aacb9bc..260fd4e 100644 --- a/tests/utils/keyConverters.test.ts +++ b/tests/utils/keyConverters.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { publicKeyToBase64, base64ToPublicKey, privateKeyToBase64, base64ToPrivateKey } from '../../src/utils'; -import { PrivateKeys, PublicKeys } from '../../src/types'; +import { publicKeyToBase64, base64ToPublicKey } from '../../src/utils'; +import { PublicKeys } from '../../src/types'; import { generateEccKeys } from '../../src/asymmetric-crypto'; import { generateKyberKeys } from '../../src/post-quantum-crypto'; @@ -16,35 +16,6 @@ describe('key converters', () => { const base64 = await publicKeyToBase64(key); const result = await base64ToPublicKey(base64); expect(result).toEqual(key); - - const privateKey: PrivateKeys = { - eccPrivateKey: eccKeyPair.privateKey, - kyberPrivateKey: kyberKeyPair.secretKey, - }; - const base64Private = await privateKeyToBase64(privateKey); - const resultPrivate = await base64ToPrivateKey(base64Private); - expect(resultPrivate).toEqual(privateKey); - }); - - it('throws error if public.private keys are mixed up', async () => { - const eccKeyPair = await generateEccKeys(); - const kyberKeyPair = await generateKyberKeys(); - - const key: PublicKeys = { - eccPublicKey: eccKeyPair.publicKey, - kyberPublicKey: kyberKeyPair.publicKey, - }; - const base64 = await publicKeyToBase64(key); - - const privateKey: PrivateKeys = { - eccPrivateKey: eccKeyPair.privateKey, - kyberPrivateKey: kyberKeyPair.secretKey, - }; - const base64Private = await privateKeyToBase64(privateKey); - - await expect(base64ToPublicKey(base64Private)).rejects.toThrowError(/Failed to convert base64 to PublicKeys/); - - await expect(base64ToPrivateKey(base64)).rejects.toThrowError(/Failed to convert base64 to PrivateKeys/); }); it('throws error if key converter to base64 fails', async () => { @@ -56,19 +27,7 @@ describe('key converters', () => { kyberPublicKey: kyberKeyPair.publicKey, }; await expect(publicKeyToBase64(bad_key)).rejects.toThrowError( - /Failed to convert key of the type PublicKeys to base64/, + /Failed to convert key of the type PublicKeys to PublicKeysBase64/, ); - - const bad_private_key: PrivateKeys = { - eccPrivateKey: eccKeyPair.publicKey, - kyberPrivateKey: kyberKeyPair.secretKey, - }; - await expect(privateKeyToBase64(bad_private_key)).rejects.toThrowError( - /Failed to convert key of the type PrivateKeys to base64/, - ); - }); - it('throws error if base64 to public key converter fails', async () => { - const bad_key = 'base base 64 key'; - await expect(base64ToPublicKey(bad_key)).rejects.toThrowError(/Failed to convert base64 to PublicKeys/); }); }); diff --git a/yarn.lock b/yarn.lock index 15b382b..ddfe2b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -894,11 +894,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - baseline-browser-mapping@^2.8.25: version "2.8.29" resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.29.tgz#d8800b71399c783cb1bf2068c2bcc3b6cfd7892c" @@ -937,14 +932,6 @@ browserslist@^4.24.0: node-releases "^2.0.27" update-browserslist-db "^1.1.4" -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" @@ -1496,11 +1483,6 @@ idb@^8.0.3: resolved "https://registry.yarnpkg.com/idb/-/idb-8.0.3.tgz#c91e558f15a8d53f1d7f53a094d226fc3ad71fd9" integrity sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg== -ieee754@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - ignore@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"