From e364d3f1eadc4d996eb9b37edb2238ef576e8627 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Wed, 27 May 2026 16:58:59 +0200 Subject: [PATCH 01/17] make subject encryption optional, add standard errors --- src/email-crypto/core.ts | 38 ++++---- src/email-crypto/coreSubject.ts | 88 +++++++++++++++++++ src/email-crypto/errors.ts | 71 +++++++++++++++ .../hybridEncryptedEmailAndSubject.ts | 86 ++++++++++++++++++ src/email-crypto/hybridEncyptedEmail.ts | 17 +++- src/email-crypto/index.ts | 4 + src/email-crypto/pwdProtectedEmail.ts | 14 ++- src/index.ts | 17 ++++ src/types.ts | 21 ++++- tests/email-crypto/core.test.ts | 47 +++++++--- tests/email-crypto/hybridEmail.test.ts | 44 +++++++--- tests/email-crypto/pwdProtectedEmail.test.ts | 22 +++-- tests/utils/converters.test.ts | 7 ++ 13 files changed, 415 insertions(+), 61 deletions(-) create mode 100644 src/email-crypto/coreSubject.ts create mode 100644 src/email-crypto/errors.ts create mode 100644 src/email-crypto/hybridEncryptedEmailAndSubject.ts diff --git a/src/email-crypto/core.ts b/src/email-crypto/core.ts index 357a7f0..5c25e09 100644 --- a/src/email-crypto/core.ts +++ b/src/email-crypto/core.ts @@ -1,9 +1,10 @@ -import { HybridEncKey, PwdProtectedKey, EmailBody, EmailBodyEncrypted, RecipientWithPublicKey } from '../types'; +import { HybridEncKey, PwdProtectedKey, EmailBody, RecipientWithPublicKey, EmailBodyEncrypted } from '../types'; import { encryptSymmetrically, decryptSymmetrically, genSymmetricKey } from '../symmetric-crypto'; import { encapsulateHybrid, decapsulateHybrid } from '../hybrid-crypto'; import { wrapKey, unwrapKey } from '../key-wrapper'; import { getKeyFromPassword, getKeyFromPasswordAndSalt } from '../derive-password'; import { UTF8ToUint8, base64ToUint8Array, uint8ArrayToBase64, uint8ToUTF8 } from '../utils'; +import { EmailHybridDecryptionError, EmailHybridEncryptionError, InvalidInputEmail, EmailSymmetricDecryptionError, EmailSymmetricEncryptionError, EmailPasswordOpenError, EmailPasswordProtectError } from './errors'; /** * Symmetrically encrypts email body. @@ -20,15 +21,16 @@ export async function encryptEmailBody( encryptionKey: Uint8Array; }> { try { - if (!body.text || !body.subject) { - throw new Error('Invalid input'); + if (!body.text) { + throw new InvalidInputEmail(); } const encryptionKey = genSymmetricKey(); const encEmailBody = await encryptEmailBodyWithKey(body, encryptionKey, aux); return { encEmailBody, encryptionKey }; } catch (error) { - throw new Error('Failed to symmetrically encrypt email body', { cause: error }); + if (error instanceof InvalidInputEmail) throw error; + throw new EmailSymmetricEncryptionError(error instanceof Error ? error.message : String(error)); } } @@ -47,13 +49,10 @@ export async function encryptEmailBodyWithKey( ): Promise { try { const text = UTF8ToUint8(body.text); - const subject = UTF8ToUint8(body.subject); - const subjectEnc = await encryptSymmetrically(encryptionKey, subject, aux); + const encryptedText = await encryptSymmetrically(encryptionKey, text, aux); const encText = uint8ArrayToBase64(encryptedText); - const encSubject = uint8ArrayToBase64(subjectEnc); - const enc: EmailBodyEncrypted = { encText, encSubject }; - + const enc: EmailBodyEncrypted = { encText }; if (body.attachments) { const promises = body.attachments.map((attachment) => { const binaryAttachment = UTF8ToUint8(attachment); @@ -62,10 +61,9 @@ export async function encryptEmailBodyWithKey( const encryptedAttachments = await Promise.all(promises); enc.encAttachments = encryptedAttachments?.map(uint8ArrayToBase64); } - - return enc; + return enc; } catch (error) { - throw new Error('Failed to encrypt email body', { cause: error }); + throw new EmailSymmetricEncryptionError(error instanceof Error ? error.message : String(error)); } } @@ -83,13 +81,11 @@ export async function decryptEmailBody( aux?: Uint8Array, ): Promise { try { - const encSubject = base64ToUint8Array(encEmailBody.encSubject); - const subjectArray = await decryptSymmetrically(encryptionKey, encSubject, aux); - const subject = uint8ToUTF8(subjectArray); + const encText = base64ToUint8Array(encEmailBody.encText); const textArray = await decryptSymmetrically(encryptionKey, encText, aux); const text = uint8ToUTF8(textArray); - const body: EmailBody = { text, subject }; + const body: EmailBody = { text }; if (encEmailBody.encAttachments) { const encAttachments = encEmailBody.encAttachments?.map(base64ToUint8Array); @@ -100,7 +96,7 @@ export async function decryptEmailBody( return body; } catch (error) { - throw new Error('Failed to symmetrically decrypt email body', { cause: error }); + throw new EmailSymmetricDecryptionError(error instanceof Error ? error.message : String(error)); } } @@ -127,7 +123,7 @@ export async function encryptKeysHybrid( encryptedForEmail: recipient.email, }; } catch (error) { - throw new Error('Failed to encrypt email key using hybrid encryption', { cause: error }); + throw new EmailHybridEncryptionError(error instanceof Error ? error.message : String(error)); } } @@ -149,7 +145,7 @@ export async function decryptKeysHybrid( const encryptionKey = await unwrapKey(encKey, sharedSecret); return encryptionKey; } catch (error) { - throw new Error('Failed to decrypt email key encrypted via hybrid encryption', { cause: error }); + throw new EmailHybridDecryptionError(error instanceof Error ? error.message : String(error)); } } @@ -168,7 +164,7 @@ export async function passwordProtectKey(emailEncryptionKey: Uint8Array, passwor const encryptedKeyStr = uint8ArrayToBase64(encryptedKey); return { encryptedKey: encryptedKeyStr, salt: saltStr }; } catch (error) { - throw new Error('Failed to password-protect email key', { cause: error }); + throw new EmailPasswordProtectError(error instanceof Error ? error.message : String(error)); } } @@ -190,6 +186,6 @@ export async function removePasswordProtection( const encryptionKey = await unwrapKey(encryptedKey, key); return encryptionKey; } catch (error) { - throw new Error('Failed to remove password-protection from email key', { cause: error }); + throw new EmailPasswordOpenError(error instanceof Error ? error.message : String(error)); } } diff --git a/src/email-crypto/coreSubject.ts b/src/email-crypto/coreSubject.ts new file mode 100644 index 0000000..fbc34c3 --- /dev/null +++ b/src/email-crypto/coreSubject.ts @@ -0,0 +1,88 @@ +import { EmailBodyAndSubject, EmailBodyAndSubjectEncrypted } from '../types'; +import { encryptSymmetrically, decryptSymmetrically, genSymmetricKey } from '../symmetric-crypto'; +import { encryptEmailBodyWithKey, decryptEmailBody } from './core'; +import { UTF8ToUint8, base64ToUint8Array, uint8ArrayToBase64, uint8ToUTF8 } from '../utils'; +import { InvalidInputEmail, EmailSymmetricDecryptionError, EmailSymmetricEncryptionError } from './errors'; + +/** + * Symmetrically encrypts email body and subject. + * + * @param body - The email body and subject to encrypt. + * @param isSubjectEncrypted - A boolean indicating whether the email subject should be encrypted. Defaults to true. + * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). + * @returns The resulting encrypted email body and symmetric key used for encryption + */ +export async function encryptEmailBodyAndSubject( + body: EmailBodyAndSubject, + aux?: Uint8Array, +): Promise<{ + encEmailBody: EmailBodyAndSubjectEncrypted; + encryptionKey: Uint8Array; +}> { + try { + if (!body.text || !body.subject) { + throw new InvalidInputEmail(); + } + const encryptionKey = genSymmetricKey(); + const encEmailBody = await encryptEmailBodyAndSubjectWithKey(body, encryptionKey, aux); + + return { encEmailBody, encryptionKey }; + } catch (error) { + if (error instanceof InvalidInputEmail) throw error; + throw new EmailSymmetricEncryptionError(error instanceof Error ? error.message : String(error)); + } +} + +/** + * Symmetrically encrypts email body and subject with the given key. + * + * @param body - The email body and subject to encrypt. + * @param encryptionKey - The symmetric key to encrypt the email. + * @param isSubjectEncrypted - A boolean indicating whether the email subject should be encrypted. Defaults to true. + * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). + * @returns The resulting encrypted email body and symmetric key used for encryption + */ +export async function encryptEmailBodyAndSubjectWithKey( + body: EmailBodyAndSubject, + encryptionKey: Uint8Array, + aux?: Uint8Array, +): Promise { + try { + const enc = await encryptEmailBodyWithKey(body, encryptionKey, aux); + const subject = UTF8ToUint8(body.subject); + const subjectEnc = await encryptSymmetrically(encryptionKey, subject, aux); + const encSubject = uint8ArrayToBase64(subjectEnc); + + return { ...enc, encSubject }; + } catch (error) { + if (error instanceof InvalidInputEmail) throw error; + throw new EmailSymmetricEncryptionError(error instanceof Error ? error.message : String(error)); + } +} + + +/** + * Decrypts symmetrically encrypted email body and subject. + * + * @param encEmailBody - The email body and subject to decrypt. + * @param encryptionKey - The symmetric key to decrypt the email. + * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). + * @returns The resulting decrypted email body + */ +export async function decryptEmailBodyAndSubject( + encEmailBody: EmailBodyAndSubjectEncrypted, + encryptionKey: Uint8Array, + aux?: Uint8Array, +): Promise { + try { + const encSubject = base64ToUint8Array(encEmailBody.encSubject); + const subjectArray = await decryptSymmetrically(encryptionKey, encSubject, aux); + const subject = uint8ToUTF8(subjectArray); + const body = await decryptEmailBody(encEmailBody, encryptionKey, aux); + + return {...body, subject }; + } catch (error) { + if (error instanceof InvalidInputEmail) throw error; + throw new EmailSymmetricDecryptionError(error instanceof Error ? error.message : String(error)); + } +} \ No newline at end of file diff --git a/src/email-crypto/errors.ts b/src/email-crypto/errors.ts new file mode 100644 index 0000000..ea52cf8 --- /dev/null +++ b/src/email-crypto/errors.ts @@ -0,0 +1,71 @@ +export class FailedToEncryptEmail extends Error { + constructor(errorMsg?: string) { + super('Failed to encrypt email: ' + errorMsg); + + Object.setPrototypeOf(this, FailedToEncryptEmail.prototype); + } +} + +export class EmailSymmetricEncryptionError extends Error { + constructor(errorMsg?: string) { + super('Failed to symmetrically encrypt email: ' + errorMsg); + + Object.setPrototypeOf(this, EmailSymmetricEncryptionError.prototype); + } +} + +export class EmailSymmetricDecryptionError extends Error { + constructor(errorMsg?: string) { + super('Failed to symmetrically decrypt email: ' + errorMsg); + + Object.setPrototypeOf(this, EmailSymmetricDecryptionError.prototype); + } +} + +export class EmailHybridEncryptionError extends Error { + constructor(errorMsg?: string) { + super('Failed to hybridly encrypt the key: ' + errorMsg); + + Object.setPrototypeOf(this, EmailHybridEncryptionError.prototype); + } +} + +export class EmailHybridDecryptionError extends Error { + constructor(errorMsg?: string) { + super('Failed to hybridly decrypt the key: ' + errorMsg); + + Object.setPrototypeOf(this, EmailHybridDecryptionError.prototype); + } +} + +export class EmailPasswordProtectError extends Error { + constructor(errorMsg?: string) { + super('Failed to password-protect the key: ' + errorMsg); + + Object.setPrototypeOf(this, EmailPasswordProtectError.prototype); + } +} + +export class EmailPasswordOpenError extends Error { + constructor(errorMsg?: string) { + super('Failed to open password-protected key: ' + errorMsg); + + Object.setPrototypeOf(this, EmailPasswordOpenError.prototype); + } +} + +export class InvalidInputEmail extends Error { + constructor() { + super('Invalid input'); + + Object.setPrototypeOf(this, InvalidInputEmail.prototype); + } +} + +export class FailedToDecryptEmail extends Error { + constructor(errorMsg?: string) { + super('Failed to decrypt email: ' + errorMsg); + + Object.setPrototypeOf(this, FailedToDecryptEmail.prototype); + } +} diff --git a/src/email-crypto/hybridEncryptedEmailAndSubject.ts b/src/email-crypto/hybridEncryptedEmailAndSubject.ts new file mode 100644 index 0000000..b70bf70 --- /dev/null +++ b/src/email-crypto/hybridEncryptedEmailAndSubject.ts @@ -0,0 +1,86 @@ +import { RecipientWithPublicKey, EmailBodyAndSubject, HybridEncryptedEmailAndSubject } from '../types'; +import { encryptKeysHybrid, decryptKeysHybrid } from './core'; +import { encryptEmailBodyAndSubject, decryptEmailBodyAndSubject } from './coreSubject'; +import { FailedToDecryptEmail, FailedToEncryptEmail, EmailHybridDecryptionError, EmailHybridEncryptionError, InvalidInputEmail, EmailSymmetricDecryptionError, EmailSymmetricEncryptionError } from './errors'; + +/** + * Encrypts the email body using hybrid encryption. + * + * @param body - The email body and subjectto encrypt. + * @param recipientPublicKeys - The public keys of the recipient. + * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). + * @returns The encrypted email body + */ +export async function encryptEmailAndSubjectHybrid( + body: EmailBodyAndSubject, + recipient: RecipientWithPublicKey, + aux?: Uint8Array, +): Promise { + try { + const { encryptionKey, encEmailBody } = await encryptEmailBodyAndSubject(body, aux); + const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient); + return { encEmailBody, encryptedKey }; + } catch (error) { + if (error instanceof InvalidInputEmail) throw error; + if (error instanceof EmailSymmetricEncryptionError) throw error; + if (error instanceof EmailHybridEncryptionError) throw error; + throw new FailedToEncryptEmail(error instanceof Error ? error.message : String(error)); + } +} + +/** + * Encrypts the email body using hybrid encryption for multiple recipients. + * + * @param body - The email body to encrypt for multiple recipients. + * @param recipients - The recipients with corresponding public keys. + * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). + * @returns The set of encrypted email bodies + */ +export async function encryptEmailAndSubjectHybridForMultipleRecipients( + body: EmailBodyAndSubject, + recipients: RecipientWithPublicKey[], + aux?: Uint8Array, +): Promise { + try { + const { encryptionKey, encEmailBody } = await encryptEmailBodyAndSubject(body, aux); + + const encryptedEmails: HybridEncryptedEmailAndSubject[] = []; + for (const recipient of recipients) { + const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient); + encryptedEmails.push({ + encEmailBody: encEmailBody, + encryptedKey, + }); + } + return encryptedEmails; + } catch (error) { + if (error instanceof InvalidInputEmail) throw error; + if (error instanceof EmailSymmetricEncryptionError) throw error; + if (error instanceof EmailHybridEncryptionError) throw error; + throw new FailedToEncryptEmail(error instanceof Error ? error.message : String(error)); + } +} + +/** + * Decrypts the email using hybrid encryption. + * + * @param hybridEmail - The encrypted email. + * @param recipientPrivateHybridKeys - The private key of the recipient. + * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). + * @returns The decrypted email body + */ +export async function decryptEmailAndSubjectHybrid( + hybridEmail: HybridEncryptedEmailAndSubject, + recipientPrivateHybridKeys: Uint8Array, + aux?: Uint8Array, +): Promise { + try { + const encryptionKey = await decryptKeysHybrid(hybridEmail.encryptedKey, recipientPrivateHybridKeys); + return await decryptEmailBodyAndSubject(hybridEmail.encEmailBody, encryptionKey, aux); + } catch (error) { + if (error instanceof InvalidInputEmail) throw error; + if (error instanceof EmailHybridDecryptionError) throw error; + if (error instanceof EmailSymmetricDecryptionError) throw error; + throw new FailedToDecryptEmail(error instanceof Error ? error.message : String(error)); + } +} diff --git a/src/email-crypto/hybridEncyptedEmail.ts b/src/email-crypto/hybridEncyptedEmail.ts index f20aa39..64125ca 100644 --- a/src/email-crypto/hybridEncyptedEmail.ts +++ b/src/email-crypto/hybridEncyptedEmail.ts @@ -1,6 +1,6 @@ import { HybridEncryptedEmail, EmailBody, RecipientWithPublicKey } from '../types'; import { decryptEmailBody, encryptKeysHybrid, decryptKeysHybrid, encryptEmailBody } from './core'; - +import { FailedToDecryptEmail, FailedToEncryptEmail, EmailHybridDecryptionError, EmailHybridEncryptionError, InvalidInputEmail, EmailSymmetricDecryptionError, EmailSymmetricEncryptionError } from './errors'; /** * Encrypts the email body using hybrid encryption. * @@ -19,7 +19,11 @@ export async function encryptEmailHybrid( const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient); return { encEmailBody, encryptedKey }; } catch (error) { - throw new Error('Failed to encrypt email body with hybrid encryption', { cause: error }); + if (error instanceof InvalidInputEmail) throw error; + if (error instanceof EmailSymmetricEncryptionError) throw error; + if (error instanceof EmailHybridEncryptionError) throw error; + throw new FailedToEncryptEmail(error instanceof Error ? error.message : String(error)); + } } @@ -49,7 +53,10 @@ export async function encryptEmailHybridForMultipleRecipients( } return encryptedEmails; } catch (error) { - throw new Error('Failed to encrypt email to multiple recipients with hybrid encryption', { cause: error }); + if (error instanceof InvalidInputEmail) throw error; + if (error instanceof EmailSymmetricEncryptionError) throw error; + if (error instanceof EmailHybridEncryptionError) throw error; + throw new FailedToEncryptEmail(error instanceof Error ? error.message : String(error)); } } @@ -71,6 +78,8 @@ export async function decryptEmailHybrid( const body = await decryptEmailBody(encEmailBody.encEmailBody, encryptionKey, aux); return body; } catch (error) { - throw new Error('Failed to decrypt email with hybrid encryption', { cause: error }); + if (error instanceof EmailHybridDecryptionError) throw error; + if (error instanceof EmailSymmetricDecryptionError) throw error; + throw new FailedToDecryptEmail(error instanceof Error ? error.message : String(error)); } } diff --git a/src/email-crypto/index.ts b/src/email-crypto/index.ts index 31aa0c7..080fa78 100644 --- a/src/email-crypto/index.ts +++ b/src/email-crypto/index.ts @@ -1,4 +1,8 @@ export * from './hybridEncyptedEmail'; export * from './pwdProtectedEmail'; +export * from './hybridEncryptedEmailAndSubject'; +export * from './pwdProtectedEmailAndSubject'; export * from './emailKeys'; export * from './core'; +export * from './coreSubject'; +export * from './errors'; diff --git a/src/email-crypto/pwdProtectedEmail.ts b/src/email-crypto/pwdProtectedEmail.ts index eddabe5..f63e468 100644 --- a/src/email-crypto/pwdProtectedEmail.ts +++ b/src/email-crypto/pwdProtectedEmail.ts @@ -1,6 +1,6 @@ import { PwdProtectedEmail, EmailBody } from '../types'; import { decryptEmailBody, passwordProtectKey, removePasswordProtection, encryptEmailBody } from './core'; - +import { EmailSymmetricEncryptionError, FailedToDecryptEmail, FailedToEncryptEmail, EmailPasswordProtectError, EmailSymmetricDecryptionError, EmailPasswordOpenError , InvalidInputEmail} from './errors'; /** * Creates a password-protected email. * @@ -20,7 +20,10 @@ export async function createPwdProtectedEmail( return { encEmailBody, encryptedKey }; } catch (error) { - throw new Error('Failed to password-protect email', { cause: error }); + if (error instanceof InvalidInputEmail) throw error; + if (error instanceof EmailSymmetricEncryptionError) throw error; + if (error instanceof EmailPasswordProtectError) throw error; + throw new FailedToEncryptEmail(error instanceof Error ? error.message : String(error)); } } @@ -42,6 +45,9 @@ export async function decryptPwdProtectedEmail( const body = await decryptEmailBody(encryptedEmail.encEmailBody, encryptionKey, aux); return body; } catch (error) { - throw new Error('Failed to decrypt password-protect email', { cause: error }); + if (error instanceof InvalidInputEmail) throw error; + if (error instanceof EmailPasswordOpenError) throw error; + if (error instanceof EmailSymmetricDecryptionError) throw error; + throw new FailedToDecryptEmail(error instanceof Error ? error.message : String(error)); } -} +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 117c378..6e5b921 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,14 +5,31 @@ export { encryptEmailHybrid, encryptEmailHybridForMultipleRecipients, decryptEmailHybrid, + encryptEmailAndSubjectHybrid, + encryptEmailAndSubjectHybridForMultipleRecipients, + decryptEmailAndSubjectHybrid, createPwdProtectedEmail, decryptPwdProtectedEmail, + createPwdProtectedEmailAndSubject, + decryptPwdProtectedEmailAndSubject, generateEmailKeys, decryptEmailBody, encryptEmailBody, encryptEmailBodyWithKey, + encryptEmailBodyAndSubject, + encryptEmailBodyAndSubjectWithKey, + decryptEmailBodyAndSubject, deriveDatabaseKey, deriveEmailDraftKey, + FailedToEncryptEmail, + FailedToDecryptEmail, + InvalidInputEmail, + EmailSymmetricEncryptionError, + EmailSymmetricDecryptionError, + EmailHybridEncryptionError, + EmailHybridDecryptionError, + EmailPasswordProtectError, + EmailPasswordOpenError, } from './email-crypto'; export { hashDataArray, diff --git a/src/types.ts b/src/types.ts index c3c7f0d..65b7a7d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,11 +20,22 @@ export type HybridEncryptedEmail = { encEmailBody: EmailBodyEncrypted; }; +export type HybridEncryptedEmailAndSubject = { + encryptedKey: HybridEncKey; + encEmailBody: EmailBodyAndSubjectEncrypted ; +}; + export type PwdProtectedEmail = { encryptedKey: PwdProtectedKey; encEmailBody: EmailBodyEncrypted; }; +export type PwdProtectedEmailAndSubject = { + encryptedKey: PwdProtectedKey; + encEmailBody: EmailBodyAndSubjectEncrypted; +}; + + export type HybridEncKey = { hybridCiphertext: string; encryptedKey: string; @@ -38,16 +49,22 @@ export type PwdProtectedKey = { export type EmailBodyEncrypted = { encText: string; - encSubject: string; encAttachments?: string[]; }; +export type EmailBodyAndSubjectEncrypted = EmailBodyEncrypted & { + encSubject: string; + }; + export type EmailBody = { text: string; - subject: string; attachments?: string[]; }; +export type EmailBodyAndSubject = EmailBody & { + subject: string; + }; + export enum KeystoreType { ENCRYPTION = 'Encryption', RECOVERY = 'Recovery', diff --git a/tests/email-crypto/core.test.ts b/tests/email-crypto/core.test.ts index 1b513ea..8b114c7 100644 --- a/tests/email-crypto/core.test.ts +++ b/tests/email-crypto/core.test.ts @@ -1,24 +1,23 @@ import { describe, expect, it } from 'vitest'; -import { EmailBody } from '../../src/types'; -import { decryptEmailBody, encryptEmailBody, deriveDatabaseKey, deriveEmailDraftKey } from '../../src/email-crypto'; -import { generateUuid, genMnemonic } from '../../src/utils'; +import { EmailBody, EmailBodyAndSubject } from '../../src/types'; +import { decryptEmailBody, encryptEmailBody, deriveDatabaseKey, deriveEmailDraftKey, encryptEmailBodyAndSubject, decryptEmailBodyAndSubject } from '../../src/email-crypto'; +import { genMnemonic } from '../../src/utils'; import { genSymmetricKey } from '../../src/symmetric-crypto'; import { AES_KEY_BYTE_LENGTH } from '../../src/constants'; +import { EmailSymmetricDecryptionError, InvalidInputEmail } from '../../src/email-crypto/errors'; describe('Test email crypto functions', () => { const emailBody: EmailBody = { text: 'test body', + }; + + const emailBodyAndSubject: EmailBodyAndSubject = { + text: 'test body', subject: 'test subject', }; const aux = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - it('should generate email id', async () => { - const result1 = generateUuid(); - const result2 = generateUuid(); - expect(result1).not.toEqual(result2); - expect(result1).toHaveLength(36); - }); it('should encrypt and decrypt email', async () => { const { encEmailBody, encryptionKey } = await encryptEmailBody(emailBody, aux); @@ -26,16 +25,35 @@ describe('Test email crypto functions', () => { expect(result).toEqual(emailBody); }); + it('should encrypt and decrypt email with subject', async () => { + const { encEmailBody, encryptionKey } = await encryptEmailBodyAndSubject(emailBodyAndSubject, aux); + const result = await decryptEmailBodyAndSubject(encEmailBody, encryptionKey, aux); + expect(result).toEqual(emailBodyAndSubject); + }); + it('should throw an error if decryption fails', async () => { const { encEmailBody, encryptionKey } = await encryptEmailBody(emailBody, aux); const badEncryptionKey = await genSymmetricKey(); - await expect(decryptEmailBody(encEmailBody, badEncryptionKey, aux)).rejects.toThrowError( - /Failed to symmetrically decrypt email body/, + await expect(decryptEmailBody(encEmailBody, badEncryptionKey, aux)).rejects.toThrow( + EmailSymmetricDecryptionError, + ); + + const badAux = new Uint8Array([4, 5, 6, 7, 8]); + await expect(decryptEmailBody(encEmailBody, encryptionKey, badAux)).rejects.toThrow( + EmailSymmetricDecryptionError, + ); + }); + + it('should throw an error if decryption fails', async () => { + const { encEmailBody, encryptionKey } = await encryptEmailBodyAndSubject(emailBodyAndSubject, aux); + const badEncryptionKey = await genSymmetricKey(); + await expect(decryptEmailBodyAndSubject(encEmailBody, badEncryptionKey, aux)).rejects.toThrow( + EmailSymmetricDecryptionError, ); const badAux = new Uint8Array([4, 5, 6, 7, 8]); - await expect(decryptEmailBody(encEmailBody, encryptionKey, badAux)).rejects.toThrowError( - /Failed to symmetrically decrypt email body/, + await expect(decryptEmailBodyAndSubject(encEmailBody, encryptionKey, badAux)).rejects.toThrow( + EmailSymmetricDecryptionError, ); }); @@ -43,7 +61,8 @@ describe('Test email crypto functions', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const badEmail: any = {}; badEmail.self = badEmail; - await expect(encryptEmailBody(badEmail, aux)).rejects.toThrowError(/Failed to symmetrically encrypt email body/); + await expect(encryptEmailBody(badEmail, aux)).rejects.toThrow(InvalidInputEmail); + await expect(encryptEmailBodyAndSubject(badEmail, aux)).rejects.toThrow(InvalidInputEmail); }); it('should derive symmetric key for database encryption', async () => { diff --git a/tests/email-crypto/hybridEmail.test.ts b/tests/email-crypto/hybridEmail.test.ts index dcd137f..c440914 100644 --- a/tests/email-crypto/hybridEmail.test.ts +++ b/tests/email-crypto/hybridEmail.test.ts @@ -4,13 +4,20 @@ import { decryptEmailHybrid, encryptEmailHybridForMultipleRecipients, generateEmailKeys, + encryptEmailAndSubjectHybrid, + decryptEmailAndSubjectHybrid, } from '../../src/email-crypto'; -import { EmailBody, HybridEncryptedEmail, HybridEncKey } from '../../src/types'; +import { EmailBody, HybridEncryptedEmail, HybridEncKey, EmailBodyAndSubject, HybridEncryptedEmailAndSubject } from '../../src/types'; +import { EmailHybridDecryptionError, EmailHybridEncryptionError} from '../../src/email-crypto/errors'; describe('Test email crypto functions', async () => { const email: EmailBody = { text: 'test body', + }; + + const emailAndSubject: EmailBodyAndSubject = { + text: 'test body', subject: 'test subject', }; @@ -28,29 +35,35 @@ describe('Test email crypto functions', async () => { it('should encrypt and decrypt email sucessfully', async () => { const encryptedEmail = await encryptEmailHybrid(email, bobWithPublicKeys); - - expect(encryptedEmail.encEmailBody.encSubject).not.toBe(email.subject); const decryptedEmail = await decryptEmailHybrid(encryptedEmail, bobPrivateKeys); expect(decryptedEmail).toStrictEqual(email); }); + it('should encrypt and decrypt email and subject sucessfully', async () => { + const encryptedEmail = await encryptEmailAndSubjectHybrid(emailAndSubject, bobWithPublicKeys); + + expect(encryptedEmail.encEmailBody?.encSubject).not.toBe(emailAndSubject.subject); + const decryptedEmail = await decryptEmailAndSubjectHybrid(encryptedEmail, bobPrivateKeys); + + expect(decryptedEmail).toStrictEqual(emailAndSubject); + }); + it('should throw an error if public key is given instead of the secret one', async () => { const badRecipient = { email: 'alice email', publicHybridKey: alicePrivateKeys, }; - await expect(encryptEmailHybrid(email, badRecipient)).rejects.toThrowError( - /Failed to encrypt email body with hybrid encryption/, + await expect(encryptEmailHybrid(email, badRecipient)).rejects.toThrow(EmailHybridEncryptionError ); }); it('should throw an error if not intended recipient', async () => { const encryptedEmail = await encryptEmailHybrid(email, bobWithPublicKeys); - await expect(decryptEmailHybrid(encryptedEmail, alicePrivateKeys)).rejects.toThrowError( - /Failed to decrypt email with hybrid encryption/, + await expect(decryptEmailHybrid(encryptedEmail, alicePrivateKeys)).rejects.toThrow( + EmailHybridDecryptionError ); }); @@ -61,6 +74,13 @@ describe('Test email crypto functions', async () => { encryptedForEmail: 'mock recipient email', }; const badEncryptedEmail: HybridEncryptedEmail = { + encryptedKey: encKey, + encEmailBody: { + encText: 'mock encrypted text', + }, + }; + + const badEncryptedEmailAndSubject: HybridEncryptedEmailAndSubject = { encryptedKey: encKey, encEmailBody: { encText: 'mock encrypted text', @@ -68,8 +88,12 @@ describe('Test email crypto functions', async () => { }, }; - await expect(decryptEmailHybrid(badEncryptedEmail, bobPrivateKeys)).rejects.toThrowError( - /Failed to decrypt email with hybrid encryption/, + await expect(decryptEmailHybrid(badEncryptedEmail, bobPrivateKeys)).rejects.toThrow( + EmailHybridDecryptionError + ); + + await expect(decryptEmailAndSubjectHybrid(badEncryptedEmailAndSubject, bobPrivateKeys)).rejects.toThrow( + EmailHybridDecryptionError ); }); @@ -93,6 +117,6 @@ describe('Test email crypto functions', async () => { }; await expect( encryptEmailHybridForMultipleRecipients(email, [bobWithPublicKeys, badEveWithPublicKeys]), - ).rejects.toThrowError(/Failed to encrypt email to multiple recipients with hybrid encryption/); + ).rejects.toThrow(EmailHybridEncryptionError); }); }); diff --git a/tests/email-crypto/pwdProtectedEmail.test.ts b/tests/email-crypto/pwdProtectedEmail.test.ts index 9389a56..a8cb222 100644 --- a/tests/email-crypto/pwdProtectedEmail.test.ts +++ b/tests/email-crypto/pwdProtectedEmail.test.ts @@ -1,10 +1,14 @@ import { describe, expect, it } from 'vitest'; -import { createPwdProtectedEmail, decryptPwdProtectedEmail } from '../../src/email-crypto'; -import { EmailBody } from '../../src/types'; +import { createPwdProtectedEmail, createPwdProtectedEmailAndSubject, decryptPwdProtectedEmail, decryptPwdProtectedEmailAndSubject, EmailPasswordOpenError, InvalidInputEmail } from '../../src/email-crypto'; +import { EmailBody, EmailBodyAndSubject } from '../../src/types'; describe('Test email crypto functions', () => { const email: EmailBody = { text: 'Hi Bob, This is a test message. -Alice.', + }; + + const emailAndSubject: EmailBodyAndSubject = { + text: 'Hi Bob, This is a test message. -Alice.', subject: 'test subject', }; @@ -16,18 +20,24 @@ describe('Test email crypto functions', () => { expect(decryptedEmail).toStrictEqual(email); }); + it('should encrypt and decrypt email and subjectsucessfully', async () => { + const encryptedEmail = await createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret); + const decryptedEmail = await decryptPwdProtectedEmailAndSubject(encryptedEmail, sharedSecret); + expect(decryptedEmail).toStrictEqual(emailAndSubject); + }); + it('should throw an error if encryption fails', async () => { const badEmail = {} as unknown as EmailBody; - await expect(createPwdProtectedEmail(badEmail, sharedSecret)).rejects.toThrowError( - /Failed to password-protect email/, + await expect(createPwdProtectedEmail(badEmail, sharedSecret)).rejects.toThrow( + InvalidInputEmail ); }); it('should throw an error if a different secret used for decryption', async () => { const encryptedEmail = await createPwdProtectedEmail(email, sharedSecret); const wrongSecret = 'different secret'; - await expect(decryptPwdProtectedEmail(encryptedEmail, wrongSecret)).rejects.toThrowError( - /Failed to decrypt password-protect email/, + await expect(decryptPwdProtectedEmail(encryptedEmail, wrongSecret)).rejects.toThrow( + EmailPasswordOpenError ); }); }); diff --git a/tests/utils/converters.test.ts b/tests/utils/converters.test.ts index 4903b29..48a6738 100644 --- a/tests/utils/converters.test.ts +++ b/tests/utils/converters.test.ts @@ -53,4 +53,11 @@ describe('test uuid and mnemonic generation', () => { expect(result).toBe(mnemonic); }); + + it('should generate unique id', async () => { + const result1 = generateUuid(); + const result2 = generateUuid(); + expect(result1).not.toEqual(result2); + expect(result1).toHaveLength(36); + }); }); From 95ad34567f9a2904447758dcf9edd29ca8e3a4ef Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Wed, 27 May 2026 17:07:16 +0200 Subject: [PATCH 02/17] apply lint --- src/email-crypto/core.ts | 13 +++- src/email-crypto/coreSubject.ts | 15 ++--- .../hybridEncryptedEmailAndSubject.ts | 16 +++-- src/email-crypto/hybridEncyptedEmail.ts | 11 +++- src/email-crypto/pwdProtectedEmail.ts | 12 +++- .../pwdProtectedEmailAndSubject.ts | 64 +++++++++++++++++++ src/types.ts | 15 ++--- tests/email-crypto/core.test.ts | 20 +++--- tests/email-crypto/hybridEmail.test.ts | 25 ++++---- tests/email-crypto/pwdProtectedEmail.test.ts | 21 +++--- tests/utils/converters.test.ts | 2 +- 11 files changed, 156 insertions(+), 58 deletions(-) create mode 100644 src/email-crypto/pwdProtectedEmailAndSubject.ts diff --git a/src/email-crypto/core.ts b/src/email-crypto/core.ts index 5c25e09..4a960f4 100644 --- a/src/email-crypto/core.ts +++ b/src/email-crypto/core.ts @@ -4,7 +4,15 @@ import { encapsulateHybrid, decapsulateHybrid } from '../hybrid-crypto'; import { wrapKey, unwrapKey } from '../key-wrapper'; import { getKeyFromPassword, getKeyFromPasswordAndSalt } from '../derive-password'; import { UTF8ToUint8, base64ToUint8Array, uint8ArrayToBase64, uint8ToUTF8 } from '../utils'; -import { EmailHybridDecryptionError, EmailHybridEncryptionError, InvalidInputEmail, EmailSymmetricDecryptionError, EmailSymmetricEncryptionError, EmailPasswordOpenError, EmailPasswordProtectError } from './errors'; +import { + EmailHybridDecryptionError, + EmailHybridEncryptionError, + InvalidInputEmail, + EmailSymmetricDecryptionError, + EmailSymmetricEncryptionError, + EmailPasswordOpenError, + EmailPasswordProtectError, +} from './errors'; /** * Symmetrically encrypts email body. @@ -61,7 +69,7 @@ export async function encryptEmailBodyWithKey( const encryptedAttachments = await Promise.all(promises); enc.encAttachments = encryptedAttachments?.map(uint8ArrayToBase64); } - return enc; + return enc; } catch (error) { throw new EmailSymmetricEncryptionError(error instanceof Error ? error.message : String(error)); } @@ -81,7 +89,6 @@ export async function decryptEmailBody( aux?: Uint8Array, ): Promise { try { - const encText = base64ToUint8Array(encEmailBody.encText); const textArray = await decryptSymmetrically(encryptionKey, encText, aux); const text = uint8ToUTF8(textArray); diff --git a/src/email-crypto/coreSubject.ts b/src/email-crypto/coreSubject.ts index fbc34c3..95898dd 100644 --- a/src/email-crypto/coreSubject.ts +++ b/src/email-crypto/coreSubject.ts @@ -52,7 +52,7 @@ export async function encryptEmailBodyAndSubjectWithKey( const subject = UTF8ToUint8(body.subject); const subjectEnc = await encryptSymmetrically(encryptionKey, subject, aux); const encSubject = uint8ArrayToBase64(subjectEnc); - + return { ...enc, encSubject }; } catch (error) { if (error instanceof InvalidInputEmail) throw error; @@ -60,7 +60,6 @@ export async function encryptEmailBodyAndSubjectWithKey( } } - /** * Decrypts symmetrically encrypted email body and subject. * @@ -75,14 +74,14 @@ export async function decryptEmailBodyAndSubject( aux?: Uint8Array, ): Promise { try { - const encSubject = base64ToUint8Array(encEmailBody.encSubject); - const subjectArray = await decryptSymmetrically(encryptionKey, encSubject, aux); - const subject = uint8ToUTF8(subjectArray); - const body = await decryptEmailBody(encEmailBody, encryptionKey, aux); + const encSubject = base64ToUint8Array(encEmailBody.encSubject); + const subjectArray = await decryptSymmetrically(encryptionKey, encSubject, aux); + const subject = uint8ToUTF8(subjectArray); + const body = await decryptEmailBody(encEmailBody, encryptionKey, aux); - return {...body, subject }; + return { ...body, subject }; } catch (error) { if (error instanceof InvalidInputEmail) throw error; throw new EmailSymmetricDecryptionError(error instanceof Error ? error.message : String(error)); } -} \ No newline at end of file +} diff --git a/src/email-crypto/hybridEncryptedEmailAndSubject.ts b/src/email-crypto/hybridEncryptedEmailAndSubject.ts index b70bf70..9a1762d 100644 --- a/src/email-crypto/hybridEncryptedEmailAndSubject.ts +++ b/src/email-crypto/hybridEncryptedEmailAndSubject.ts @@ -1,7 +1,15 @@ -import { RecipientWithPublicKey, EmailBodyAndSubject, HybridEncryptedEmailAndSubject } from '../types'; +import { RecipientWithPublicKey, EmailBodyAndSubject, HybridEncryptedEmailAndSubject } from '../types'; import { encryptKeysHybrid, decryptKeysHybrid } from './core'; import { encryptEmailBodyAndSubject, decryptEmailBodyAndSubject } from './coreSubject'; -import { FailedToDecryptEmail, FailedToEncryptEmail, EmailHybridDecryptionError, EmailHybridEncryptionError, InvalidInputEmail, EmailSymmetricDecryptionError, EmailSymmetricEncryptionError } from './errors'; +import { + FailedToDecryptEmail, + FailedToEncryptEmail, + EmailHybridDecryptionError, + EmailHybridEncryptionError, + InvalidInputEmail, + EmailSymmetricDecryptionError, + EmailSymmetricEncryptionError, +} from './errors'; /** * Encrypts the email body using hybrid encryption. @@ -24,7 +32,7 @@ export async function encryptEmailAndSubjectHybrid( if (error instanceof InvalidInputEmail) throw error; if (error instanceof EmailSymmetricEncryptionError) throw error; if (error instanceof EmailHybridEncryptionError) throw error; - throw new FailedToEncryptEmail(error instanceof Error ? error.message : String(error)); + throw new FailedToEncryptEmail(error instanceof Error ? error.message : String(error)); } } @@ -75,7 +83,7 @@ export async function decryptEmailAndSubjectHybrid( aux?: Uint8Array, ): Promise { try { - const encryptionKey = await decryptKeysHybrid(hybridEmail.encryptedKey, recipientPrivateHybridKeys); + const encryptionKey = await decryptKeysHybrid(hybridEmail.encryptedKey, recipientPrivateHybridKeys); return await decryptEmailBodyAndSubject(hybridEmail.encEmailBody, encryptionKey, aux); } catch (error) { if (error instanceof InvalidInputEmail) throw error; diff --git a/src/email-crypto/hybridEncyptedEmail.ts b/src/email-crypto/hybridEncyptedEmail.ts index 64125ca..e1847d7 100644 --- a/src/email-crypto/hybridEncyptedEmail.ts +++ b/src/email-crypto/hybridEncyptedEmail.ts @@ -1,6 +1,14 @@ import { HybridEncryptedEmail, EmailBody, RecipientWithPublicKey } from '../types'; import { decryptEmailBody, encryptKeysHybrid, decryptKeysHybrid, encryptEmailBody } from './core'; -import { FailedToDecryptEmail, FailedToEncryptEmail, EmailHybridDecryptionError, EmailHybridEncryptionError, InvalidInputEmail, EmailSymmetricDecryptionError, EmailSymmetricEncryptionError } from './errors'; +import { + FailedToDecryptEmail, + FailedToEncryptEmail, + EmailHybridDecryptionError, + EmailHybridEncryptionError, + InvalidInputEmail, + EmailSymmetricDecryptionError, + EmailSymmetricEncryptionError, +} from './errors'; /** * Encrypts the email body using hybrid encryption. * @@ -23,7 +31,6 @@ export async function encryptEmailHybrid( if (error instanceof EmailSymmetricEncryptionError) throw error; if (error instanceof EmailHybridEncryptionError) throw error; throw new FailedToEncryptEmail(error instanceof Error ? error.message : String(error)); - } } diff --git a/src/email-crypto/pwdProtectedEmail.ts b/src/email-crypto/pwdProtectedEmail.ts index f63e468..c31ea5c 100644 --- a/src/email-crypto/pwdProtectedEmail.ts +++ b/src/email-crypto/pwdProtectedEmail.ts @@ -1,6 +1,14 @@ import { PwdProtectedEmail, EmailBody } from '../types'; import { decryptEmailBody, passwordProtectKey, removePasswordProtection, encryptEmailBody } from './core'; -import { EmailSymmetricEncryptionError, FailedToDecryptEmail, FailedToEncryptEmail, EmailPasswordProtectError, EmailSymmetricDecryptionError, EmailPasswordOpenError , InvalidInputEmail} from './errors'; +import { + EmailSymmetricEncryptionError, + FailedToDecryptEmail, + FailedToEncryptEmail, + EmailPasswordProtectError, + EmailSymmetricDecryptionError, + EmailPasswordOpenError, + InvalidInputEmail, +} from './errors'; /** * Creates a password-protected email. * @@ -50,4 +58,4 @@ export async function decryptPwdProtectedEmail( if (error instanceof EmailSymmetricDecryptionError) throw error; throw new FailedToDecryptEmail(error instanceof Error ? error.message : String(error)); } -} \ No newline at end of file +} diff --git a/src/email-crypto/pwdProtectedEmailAndSubject.ts b/src/email-crypto/pwdProtectedEmailAndSubject.ts new file mode 100644 index 0000000..54291b7 --- /dev/null +++ b/src/email-crypto/pwdProtectedEmailAndSubject.ts @@ -0,0 +1,64 @@ +import { EmailBodyAndSubject, PwdProtectedEmailAndSubject } from '../types'; +import { passwordProtectKey, removePasswordProtection } from './core'; +import { encryptEmailBodyAndSubject, decryptEmailBodyAndSubject } from './coreSubject'; +import { + FailedToDecryptEmail, + FailedToEncryptEmail, + InvalidInputEmail, + EmailPasswordOpenError, + EmailPasswordProtectError, + EmailSymmetricDecryptionError, + EmailSymmetricEncryptionError, +} from './errors'; + +/** + * Creates a password-protected email. + * + * @param email - The email to password-protect + * @param password - The secret password shared among recipients + * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). + * @returns The password-protected email + */ +export async function createPwdProtectedEmailAndSubject( + emailBody: EmailBodyAndSubject, + password: string, + aux?: Uint8Array, +): Promise { + try { + const { encryptionKey, encEmailBody } = await encryptEmailBodyAndSubject(emailBody, aux); + const encryptedKey = await passwordProtectKey(encryptionKey, password); + + return { encEmailBody, encryptedKey }; + } catch (error) { + if (error instanceof InvalidInputEmail) throw error; + if (error instanceof EmailSymmetricEncryptionError) throw error; + if (error instanceof EmailPasswordProtectError) throw error; + throw new FailedToEncryptEmail(error instanceof Error ? error.message : String(error)); + } +} + +/** + * Opens a password-protected email and subject. + * + * @param encryptedEmail - The encrypted email and subject + * @param password - The secret password shared among recipients. + * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). + * @returns The decrypted email body + */ +export async function decryptPwdProtectedEmailAndSubject( + encryptedEmail: PwdProtectedEmailAndSubject, + password: string, + aux?: Uint8Array, +): Promise { + try { + const encryptionKey = await removePasswordProtection(encryptedEmail.encryptedKey, password); + + const body = await decryptEmailBodyAndSubject(encryptedEmail.encEmailBody, encryptionKey, aux); + return body; + } catch (error) { + if (error instanceof InvalidInputEmail) throw error; + if (error instanceof EmailPasswordOpenError) throw error; + if (error instanceof EmailSymmetricDecryptionError) throw error; + throw new FailedToDecryptEmail(error instanceof Error ? error.message : String(error)); + } +} diff --git a/src/types.ts b/src/types.ts index 65b7a7d..d509628 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,7 +22,7 @@ export type HybridEncryptedEmail = { export type HybridEncryptedEmailAndSubject = { encryptedKey: HybridEncKey; - encEmailBody: EmailBodyAndSubjectEncrypted ; + encEmailBody: EmailBodyAndSubjectEncrypted; }; export type PwdProtectedEmail = { @@ -35,7 +35,6 @@ export type PwdProtectedEmailAndSubject = { encEmailBody: EmailBodyAndSubjectEncrypted; }; - export type HybridEncKey = { hybridCiphertext: string; encryptedKey: string; @@ -52,18 +51,18 @@ export type EmailBodyEncrypted = { encAttachments?: string[]; }; -export type EmailBodyAndSubjectEncrypted = EmailBodyEncrypted & { - encSubject: string; - }; +export type EmailBodyAndSubjectEncrypted = EmailBodyEncrypted & { + encSubject: string; +}; export type EmailBody = { text: string; attachments?: string[]; }; -export type EmailBodyAndSubject = EmailBody & { - subject: string; - }; +export type EmailBodyAndSubject = EmailBody & { + subject: string; +}; export enum KeystoreType { ENCRYPTION = 'Encryption', diff --git a/tests/email-crypto/core.test.ts b/tests/email-crypto/core.test.ts index 8b114c7..a99d0ad 100644 --- a/tests/email-crypto/core.test.ts +++ b/tests/email-crypto/core.test.ts @@ -1,6 +1,13 @@ import { describe, expect, it } from 'vitest'; import { EmailBody, EmailBodyAndSubject } from '../../src/types'; -import { decryptEmailBody, encryptEmailBody, deriveDatabaseKey, deriveEmailDraftKey, encryptEmailBodyAndSubject, decryptEmailBodyAndSubject } from '../../src/email-crypto'; +import { + decryptEmailBody, + encryptEmailBody, + deriveDatabaseKey, + deriveEmailDraftKey, + encryptEmailBodyAndSubject, + decryptEmailBodyAndSubject, +} from '../../src/email-crypto'; import { genMnemonic } from '../../src/utils'; import { genSymmetricKey } from '../../src/symmetric-crypto'; import { AES_KEY_BYTE_LENGTH } from '../../src/constants'; @@ -18,14 +25,13 @@ describe('Test email crypto functions', () => { const aux = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); - it('should encrypt and decrypt email', async () => { const { encEmailBody, encryptionKey } = await encryptEmailBody(emailBody, aux); const result = await decryptEmailBody(encEmailBody, encryptionKey, aux); expect(result).toEqual(emailBody); }); - it('should encrypt and decrypt email with subject', async () => { + it('should encrypt and decrypt email with subject', async () => { const { encEmailBody, encryptionKey } = await encryptEmailBodyAndSubject(emailBodyAndSubject, aux); const result = await decryptEmailBodyAndSubject(encEmailBody, encryptionKey, aux); expect(result).toEqual(emailBodyAndSubject); @@ -34,14 +40,10 @@ describe('Test email crypto functions', () => { it('should throw an error if decryption fails', async () => { const { encEmailBody, encryptionKey } = await encryptEmailBody(emailBody, aux); const badEncryptionKey = await genSymmetricKey(); - await expect(decryptEmailBody(encEmailBody, badEncryptionKey, aux)).rejects.toThrow( - EmailSymmetricDecryptionError, - ); + await expect(decryptEmailBody(encEmailBody, badEncryptionKey, aux)).rejects.toThrow(EmailSymmetricDecryptionError); const badAux = new Uint8Array([4, 5, 6, 7, 8]); - await expect(decryptEmailBody(encEmailBody, encryptionKey, badAux)).rejects.toThrow( - EmailSymmetricDecryptionError, - ); + await expect(decryptEmailBody(encEmailBody, encryptionKey, badAux)).rejects.toThrow(EmailSymmetricDecryptionError); }); it('should throw an error if decryption fails', async () => { diff --git a/tests/email-crypto/hybridEmail.test.ts b/tests/email-crypto/hybridEmail.test.ts index c440914..76b763d 100644 --- a/tests/email-crypto/hybridEmail.test.ts +++ b/tests/email-crypto/hybridEmail.test.ts @@ -8,8 +8,14 @@ import { decryptEmailAndSubjectHybrid, } from '../../src/email-crypto'; -import { EmailBody, HybridEncryptedEmail, HybridEncKey, EmailBodyAndSubject, HybridEncryptedEmailAndSubject } from '../../src/types'; -import { EmailHybridDecryptionError, EmailHybridEncryptionError} from '../../src/email-crypto/errors'; +import { + EmailBody, + HybridEncryptedEmail, + HybridEncKey, + EmailBodyAndSubject, + HybridEncryptedEmailAndSubject, +} from '../../src/types'; +import { EmailHybridDecryptionError, EmailHybridEncryptionError } from '../../src/email-crypto/errors'; describe('Test email crypto functions', async () => { const email: EmailBody = { @@ -40,7 +46,7 @@ describe('Test email crypto functions', async () => { expect(decryptedEmail).toStrictEqual(email); }); - it('should encrypt and decrypt email and subject sucessfully', async () => { + it('should encrypt and decrypt email and subject sucessfully', async () => { const encryptedEmail = await encryptEmailAndSubjectHybrid(emailAndSubject, bobWithPublicKeys); expect(encryptedEmail.encEmailBody?.encSubject).not.toBe(emailAndSubject.subject); @@ -55,16 +61,13 @@ describe('Test email crypto functions', async () => { publicHybridKey: alicePrivateKeys, }; - await expect(encryptEmailHybrid(email, badRecipient)).rejects.toThrow(EmailHybridEncryptionError - ); + await expect(encryptEmailHybrid(email, badRecipient)).rejects.toThrow(EmailHybridEncryptionError); }); it('should throw an error if not intended recipient', async () => { const encryptedEmail = await encryptEmailHybrid(email, bobWithPublicKeys); - await expect(decryptEmailHybrid(encryptedEmail, alicePrivateKeys)).rejects.toThrow( - EmailHybridDecryptionError - ); + await expect(decryptEmailHybrid(encryptedEmail, alicePrivateKeys)).rejects.toThrow(EmailHybridDecryptionError); }); it('should throw an error if hybrid email decryption fails', async () => { @@ -88,12 +91,10 @@ describe('Test email crypto functions', async () => { }, }; - await expect(decryptEmailHybrid(badEncryptedEmail, bobPrivateKeys)).rejects.toThrow( - EmailHybridDecryptionError - ); + await expect(decryptEmailHybrid(badEncryptedEmail, bobPrivateKeys)).rejects.toThrow(EmailHybridDecryptionError); await expect(decryptEmailAndSubjectHybrid(badEncryptedEmailAndSubject, bobPrivateKeys)).rejects.toThrow( - EmailHybridDecryptionError + EmailHybridDecryptionError, ); }); diff --git a/tests/email-crypto/pwdProtectedEmail.test.ts b/tests/email-crypto/pwdProtectedEmail.test.ts index a8cb222..54cb7dd 100644 --- a/tests/email-crypto/pwdProtectedEmail.test.ts +++ b/tests/email-crypto/pwdProtectedEmail.test.ts @@ -1,5 +1,12 @@ import { describe, expect, it } from 'vitest'; -import { createPwdProtectedEmail, createPwdProtectedEmailAndSubject, decryptPwdProtectedEmail, decryptPwdProtectedEmailAndSubject, EmailPasswordOpenError, InvalidInputEmail } from '../../src/email-crypto'; +import { + createPwdProtectedEmail, + createPwdProtectedEmailAndSubject, + decryptPwdProtectedEmail, + decryptPwdProtectedEmailAndSubject, + EmailPasswordOpenError, + InvalidInputEmail, +} from '../../src/email-crypto'; import { EmailBody, EmailBodyAndSubject } from '../../src/types'; describe('Test email crypto functions', () => { @@ -7,7 +14,7 @@ describe('Test email crypto functions', () => { text: 'Hi Bob, This is a test message. -Alice.', }; - const emailAndSubject: EmailBodyAndSubject = { + const emailAndSubject: EmailBodyAndSubject = { text: 'Hi Bob, This is a test message. -Alice.', subject: 'test subject', }; @@ -20,7 +27,7 @@ describe('Test email crypto functions', () => { expect(decryptedEmail).toStrictEqual(email); }); - it('should encrypt and decrypt email and subjectsucessfully', async () => { + it('should encrypt and decrypt email and subjectsucessfully', async () => { const encryptedEmail = await createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret); const decryptedEmail = await decryptPwdProtectedEmailAndSubject(encryptedEmail, sharedSecret); expect(decryptedEmail).toStrictEqual(emailAndSubject); @@ -28,16 +35,12 @@ describe('Test email crypto functions', () => { it('should throw an error if encryption fails', async () => { const badEmail = {} as unknown as EmailBody; - await expect(createPwdProtectedEmail(badEmail, sharedSecret)).rejects.toThrow( - InvalidInputEmail - ); + await expect(createPwdProtectedEmail(badEmail, sharedSecret)).rejects.toThrow(InvalidInputEmail); }); it('should throw an error if a different secret used for decryption', async () => { const encryptedEmail = await createPwdProtectedEmail(email, sharedSecret); const wrongSecret = 'different secret'; - await expect(decryptPwdProtectedEmail(encryptedEmail, wrongSecret)).rejects.toThrow( - EmailPasswordOpenError - ); + await expect(decryptPwdProtectedEmail(encryptedEmail, wrongSecret)).rejects.toThrow(EmailPasswordOpenError); }); }); diff --git a/tests/utils/converters.test.ts b/tests/utils/converters.test.ts index 48a6738..1194764 100644 --- a/tests/utils/converters.test.ts +++ b/tests/utils/converters.test.ts @@ -54,7 +54,7 @@ describe('test uuid and mnemonic generation', () => { expect(result).toBe(mnemonic); }); - it('should generate unique id', async () => { + it('should generate unique id', async () => { const result1 = generateUuid(); const result2 = generateUuid(); expect(result1).not.toEqual(result2); From b4c0170f410cf8aebf11e93295e22a7083724ec0 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Wed, 27 May 2026 17:47:55 +0200 Subject: [PATCH 03/17] fix comments and readme --- .husky/pre-commit | 2 +- README.md | 15 +++++++++++++-- src/email-crypto/coreSubject.ts | 2 -- .../hybridEncryptedEmailAndSubject.ts | 12 ++++++------ src/email-crypto/pwdProtectedEmailAndSubject.ts | 4 ++-- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 85cf02d..4861384 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,2 +1,2 @@ -yarn run lint:fix; npx lint-staged; yarn run test \ No newline at end of file +yarn run lint:fix; npx lint-staged --allow-empty; yarn run test \ No newline at end of file diff --git a/README.md b/README.md index 2f401ab..6727a8b 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ const { key, salt } = await getKeyFromPassword(password); // Hybrid email encryption const email: EmailBody = { text: 'email text', - subject: 'email subject', + attachments: ['email attachements'], }; const { secretKey: bobPrivateKeys, publicKey: bobPublicKeys } = await generateEmailKeys(); const bobWithPublicKeys = { @@ -119,9 +119,20 @@ const bobWithPublicKeys = { const encryptedEmail = await encryptEmailHybrid(email, bobWithPublicKeys); const decryptedEmail = await decryptEmailHybrid(encryptedEmail, bobPrivateKeys); -expect(encryptedEmail.encEmailBody.encSubject).not.toBe(email.subject); expect(decryptedEmailBody).toStrictEqual(email); +// Hybrid email and subject encryption +const emailAndSubject: EmailBodyAndSubject = { + text: 'email text', + subject: 'email subject' + attachments: ['email attachements'], +}; +const encryptedEmailAndSubject = await encryptEmailAndSubjectHybrid(emailAndSubject, bobWithPublicKeys); +const decryptedEmailAndSubject = await decryptEmailAndSubjectHybrid(encryptedEmailAndSubject, bobPrivateKeys); + +expect(encryptedEmailAndSubject.encEmailBody.encSubject).not.toBe(emailAndSubject.subject); +expect(decryptedEmailAndSubject).toStrictEqual(emailAndSubject); + // password-protected email const sharedSecret = 'secret shared between Alice and Bob'; diff --git a/src/email-crypto/coreSubject.ts b/src/email-crypto/coreSubject.ts index 95898dd..ab129f4 100644 --- a/src/email-crypto/coreSubject.ts +++ b/src/email-crypto/coreSubject.ts @@ -8,7 +8,6 @@ import { InvalidInputEmail, EmailSymmetricDecryptionError, EmailSymmetricEncrypt * Symmetrically encrypts email body and subject. * * @param body - The email body and subject to encrypt. - * @param isSubjectEncrypted - A boolean indicating whether the email subject should be encrypted. Defaults to true. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). * @returns The resulting encrypted email body and symmetric key used for encryption */ @@ -38,7 +37,6 @@ export async function encryptEmailBodyAndSubject( * * @param body - The email body and subject to encrypt. * @param encryptionKey - The symmetric key to encrypt the email. - * @param isSubjectEncrypted - A boolean indicating whether the email subject should be encrypted. Defaults to true. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). * @returns The resulting encrypted email body and symmetric key used for encryption */ diff --git a/src/email-crypto/hybridEncryptedEmailAndSubject.ts b/src/email-crypto/hybridEncryptedEmailAndSubject.ts index 9a1762d..84b4f2b 100644 --- a/src/email-crypto/hybridEncryptedEmailAndSubject.ts +++ b/src/email-crypto/hybridEncryptedEmailAndSubject.ts @@ -12,9 +12,9 @@ import { } from './errors'; /** - * Encrypts the email body using hybrid encryption. + * Encrypts the email body and its subject using hybrid encryption. * - * @param body - The email body and subjectto encrypt. + * @param body - The email body and subject to encrypt. * @param recipientPublicKeys - The public keys of the recipient. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). * @returns The encrypted email body @@ -37,9 +37,9 @@ export async function encryptEmailAndSubjectHybrid( } /** - * Encrypts the email body using hybrid encryption for multiple recipients. + * Encrypts the email body and its subject using hybrid encryption for multiple recipients. * - * @param body - The email body to encrypt for multiple recipients. + * @param body - The email body and subject to encrypt for multiple recipients. * @param recipients - The recipients with corresponding public keys. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). * @returns The set of encrypted email bodies @@ -70,9 +70,9 @@ export async function encryptEmailAndSubjectHybridForMultipleRecipients( } /** - * Decrypts the email using hybrid encryption. + * Decrypts the email and its subject using hybrid encryption. * - * @param hybridEmail - The encrypted email. + * @param hybridEmail - The encrypted email and subject. * @param recipientPrivateHybridKeys - The private key of the recipient. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). * @returns The decrypted email body diff --git a/src/email-crypto/pwdProtectedEmailAndSubject.ts b/src/email-crypto/pwdProtectedEmailAndSubject.ts index 54291b7..77bc069 100644 --- a/src/email-crypto/pwdProtectedEmailAndSubject.ts +++ b/src/email-crypto/pwdProtectedEmailAndSubject.ts @@ -12,9 +12,9 @@ import { } from './errors'; /** - * Creates a password-protected email. + * Creates a password-protected email and subject. * - * @param email - The email to password-protect + * @param email - The email and subject to password-protect * @param password - The secret password shared among recipients * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). * @returns The password-protected email From 3e7957430e1239e78f32310309374e2918629eaa Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Wed, 27 May 2026 18:19:40 +0200 Subject: [PATCH 04/17] add more tests --- .../hybridEncryptedEmailAndSubject.ts | 3 + src/email-crypto/hybridEncyptedEmail.ts | 3 + src/email-crypto/pwdProtectedEmail.ts | 3 +- tests/email-crypto/hybridEmail.test.ts | 109 +++++++++++++++++- tests/email-crypto/pwdProtectedEmail.test.ts | 28 ++++- 5 files changed, 141 insertions(+), 5 deletions(-) diff --git a/src/email-crypto/hybridEncryptedEmailAndSubject.ts b/src/email-crypto/hybridEncryptedEmailAndSubject.ts index 84b4f2b..9c1033c 100644 --- a/src/email-crypto/hybridEncryptedEmailAndSubject.ts +++ b/src/email-crypto/hybridEncryptedEmailAndSubject.ts @@ -50,6 +50,9 @@ export async function encryptEmailAndSubjectHybridForMultipleRecipients( aux?: Uint8Array, ): Promise { try { + if (!recipients || recipients.length === 0) { + throw new InvalidInputEmail(); + } const { encryptionKey, encEmailBody } = await encryptEmailBodyAndSubject(body, aux); const encryptedEmails: HybridEncryptedEmailAndSubject[] = []; diff --git a/src/email-crypto/hybridEncyptedEmail.ts b/src/email-crypto/hybridEncyptedEmail.ts index e1847d7..31440a4 100644 --- a/src/email-crypto/hybridEncyptedEmail.ts +++ b/src/email-crypto/hybridEncyptedEmail.ts @@ -48,6 +48,9 @@ export async function encryptEmailHybridForMultipleRecipients( aux?: Uint8Array, ): Promise { try { + if (!recipients || recipients.length === 0) { + throw new InvalidInputEmail(); + } const { encryptionKey, encEmailBody } = await encryptEmailBody(body, aux); const encryptedEmails: HybridEncryptedEmail[] = []; diff --git a/src/email-crypto/pwdProtectedEmail.ts b/src/email-crypto/pwdProtectedEmail.ts index c31ea5c..c9ed5cc 100644 --- a/src/email-crypto/pwdProtectedEmail.ts +++ b/src/email-crypto/pwdProtectedEmail.ts @@ -50,8 +50,7 @@ export async function decryptPwdProtectedEmail( ): Promise { try { const encryptionKey = await removePasswordProtection(encryptedEmail.encryptedKey, password); - const body = await decryptEmailBody(encryptedEmail.encEmailBody, encryptionKey, aux); - return body; + return await decryptEmailBody(encryptedEmail.encEmailBody, encryptionKey, aux); } catch (error) { if (error instanceof InvalidInputEmail) throw error; if (error instanceof EmailPasswordOpenError) throw error; diff --git a/tests/email-crypto/hybridEmail.test.ts b/tests/email-crypto/hybridEmail.test.ts index 76b763d..32f8167 100644 --- a/tests/email-crypto/hybridEmail.test.ts +++ b/tests/email-crypto/hybridEmail.test.ts @@ -6,6 +6,7 @@ import { generateEmailKeys, encryptEmailAndSubjectHybrid, decryptEmailAndSubjectHybrid, + encryptEmailAndSubjectHybridForMultipleRecipients, } from '../../src/email-crypto'; import { @@ -14,8 +15,9 @@ import { HybridEncKey, EmailBodyAndSubject, HybridEncryptedEmailAndSubject, + RecipientWithPublicKey, } from '../../src/types'; -import { EmailHybridDecryptionError, EmailHybridEncryptionError } from '../../src/email-crypto/errors'; +import { EmailHybridDecryptionError, EmailHybridEncryptionError, EmailSymmetricDecryptionError, InvalidInputEmail } from '../../src/email-crypto/errors'; describe('Test email crypto functions', async () => { const email: EmailBody = { @@ -62,6 +64,7 @@ describe('Test email crypto functions', async () => { }; await expect(encryptEmailHybrid(email, badRecipient)).rejects.toThrow(EmailHybridEncryptionError); + await expect(encryptEmailAndSubjectHybrid(emailAndSubject, badRecipient)).rejects.toThrow(EmailHybridEncryptionError); }); it('should throw an error if not intended recipient', async () => { @@ -108,6 +111,21 @@ describe('Test email crypto functions', async () => { expect(encryptedEmail[0].encEmailBody).toBe(encryptedEmail[1].encEmailBody); }); + it('should encrypt email and subject to multiple senders sucessfully', async () => { + const encryptedEmail = await encryptEmailAndSubjectHybridForMultipleRecipients(emailAndSubject, [ + bobWithPublicKeys, + aliceWithPublicKeys, + ]); + + expect(encryptedEmail.length).toBe(2); + expect(encryptedEmail[0].encEmailBody).toBe(encryptedEmail[1].encEmailBody); + + const emailDecryptedByBob = await decryptEmailAndSubjectHybrid(encryptedEmail[0], bobPrivateKeys); + const emailDecryptedByAlice = await decryptEmailAndSubjectHybrid(encryptedEmail[1], alicePrivateKeys); + + expect(emailDecryptedByBob).toStrictEqual(emailDecryptedByAlice); + }); + it('should throw an error if encryption to multiple recipients fails', async () => { const badEvePublicKeys = new Uint8Array(); @@ -119,5 +137,94 @@ describe('Test email crypto functions', async () => { await expect( encryptEmailHybridForMultipleRecipients(email, [bobWithPublicKeys, badEveWithPublicKeys]), ).rejects.toThrow(EmailHybridEncryptionError); + + await expect( + encryptEmailAndSubjectHybridForMultipleRecipients(emailAndSubject, [bobWithPublicKeys, badEveWithPublicKeys]), + ).rejects.toThrow(EmailHybridEncryptionError); + }); + + it('should throw an error if no recipients are provided', async () => { + await expect( + encryptEmailHybridForMultipleRecipients(email, []), + ).rejects.toThrow(InvalidInputEmail); + + await expect( + encryptEmailHybridForMultipleRecipients(email, undefined as unknown as RecipientWithPublicKey[]), + ).rejects.toThrow(InvalidInputEmail); + + await expect( + encryptEmailAndSubjectHybridForMultipleRecipients(emailAndSubject, []), + ).rejects.toThrow(InvalidInputEmail); + + await expect( + encryptEmailAndSubjectHybridForMultipleRecipients(emailAndSubject, undefined as unknown as RecipientWithPublicKey[]), + ).rejects.toThrow(InvalidInputEmail); + }); + + it('should throw an error if input is invalid', async () => { + + await expect( + encryptEmailHybrid( {} as EmailBody, bobWithPublicKeys), + ).rejects.toThrow(InvalidInputEmail); + + await expect( + encryptEmailAndSubjectHybrid( {} as EmailBodyAndSubject, bobWithPublicKeys), + ).rejects.toThrow(InvalidInputEmail); + + await expect( + encryptEmailHybridForMultipleRecipients( {} as EmailBody, [ + bobWithPublicKeys, + aliceWithPublicKeys, + ]), + ).rejects.toThrow(InvalidInputEmail); + + await expect( + encryptEmailAndSubjectHybridForMultipleRecipients( {} as EmailBodyAndSubject, [ + bobWithPublicKeys, + aliceWithPublicKeys, + ]), + ).rejects.toThrow(InvalidInputEmail); + + await expect( + decryptEmailHybrid( {} as HybridEncryptedEmail, bobPrivateKeys), + ).rejects.toThrow(EmailHybridDecryptionError); + + await expect( + decryptEmailAndSubjectHybrid( {} as HybridEncryptedEmailAndSubject, bobPrivateKeys), + ).rejects.toThrow(EmailHybridDecryptionError); + }); + + it('should throw an error if encrypted email is modified', async () => { + const encryptedEmail = await encryptEmailHybrid(emailAndSubject, bobWithPublicKeys); + + const modifiedCiphertext = encryptedEmail; + modifiedCiphertext.encEmailBody.encText += 'modified ciphertext'; + await expect( + decryptEmailHybrid(modifiedCiphertext, bobPrivateKeys), + ).rejects.toThrow(EmailSymmetricDecryptionError); + + const modifiedKey = encryptedEmail; + modifiedKey.encryptedKey.encryptedKey += 'modified key'; + await expect( + decryptEmailHybrid(modifiedCiphertext, bobPrivateKeys), + ).rejects.toThrow(EmailHybridDecryptionError); + + }); + + it('should throw an error if encrypted email and subject are modified', async () => { + const encryptedEmail = await encryptEmailAndSubjectHybrid(emailAndSubject, bobWithPublicKeys); + + const modifiedCiphertext = encryptedEmail; + modifiedCiphertext.encEmailBody.encText += 'modified ciphertext'; + await expect( + decryptEmailAndSubjectHybrid(modifiedCiphertext, bobPrivateKeys), + ).rejects.toThrow(EmailSymmetricDecryptionError); + + const modifiedKey = encryptedEmail; + modifiedKey.encryptedKey.encryptedKey += 'modified key'; + await expect( + decryptEmailAndSubjectHybrid(modifiedCiphertext, bobPrivateKeys), + ).rejects.toThrow(EmailHybridDecryptionError); + }); }); diff --git a/tests/email-crypto/pwdProtectedEmail.test.ts b/tests/email-crypto/pwdProtectedEmail.test.ts index 54cb7dd..cb81360 100644 --- a/tests/email-crypto/pwdProtectedEmail.test.ts +++ b/tests/email-crypto/pwdProtectedEmail.test.ts @@ -5,6 +5,7 @@ import { decryptPwdProtectedEmail, decryptPwdProtectedEmailAndSubject, EmailPasswordOpenError, + EmailSymmetricDecryptionError, InvalidInputEmail, } from '../../src/email-crypto'; import { EmailBody, EmailBodyAndSubject } from '../../src/types'; @@ -34,13 +35,36 @@ describe('Test email crypto functions', () => { }); it('should throw an error if encryption fails', async () => { - const badEmail = {} as unknown as EmailBody; - await expect(createPwdProtectedEmail(badEmail, sharedSecret)).rejects.toThrow(InvalidInputEmail); + await expect(createPwdProtectedEmail( {} as unknown as EmailBody, sharedSecret)).rejects.toThrow(InvalidInputEmail); + await expect(createPwdProtectedEmailAndSubject( {} as unknown as EmailBodyAndSubject, sharedSecret)).rejects.toThrow(InvalidInputEmail); }); it('should throw an error if a different secret used for decryption', async () => { const encryptedEmail = await createPwdProtectedEmail(email, sharedSecret); const wrongSecret = 'different secret'; await expect(decryptPwdProtectedEmail(encryptedEmail, wrongSecret)).rejects.toThrow(EmailPasswordOpenError); + + const encryptedEmailAndSubject = await createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret); + await expect(decryptPwdProtectedEmailAndSubject(encryptedEmailAndSubject, wrongSecret)).rejects.toThrow(EmailPasswordOpenError); + }); + + it('should throw an error if password-protected email is modified', async () => { + const encryptedEmail = await createPwdProtectedEmail(email, sharedSecret); + + const modifiedCiphertext = encryptedEmail; + modifiedCiphertext.encEmailBody.encText += 'modified ciphertext'; + await expect( + decryptPwdProtectedEmail(modifiedCiphertext, sharedSecret), + ).rejects.toThrow(EmailSymmetricDecryptionError); + }); + + it('should throw an error if password-protected email and subject are modified', async () => { + const encryptedEmail = await createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret); + + const modifiedCiphertext = encryptedEmail; + modifiedCiphertext.encEmailBody.encText += 'modified ciphertext'; + await expect( + decryptPwdProtectedEmailAndSubject(modifiedCiphertext, sharedSecret), + ).rejects.toThrow(EmailSymmetricDecryptionError); }); }); From 97571dce630afb11c41ca037bb03e154908b3ee1 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Wed, 27 May 2026 18:25:59 +0200 Subject: [PATCH 05/17] up the version --- package.json | 2 +- tests/email-crypto/hybridEmail.test.ts | 92 ++++++++++---------- tests/email-crypto/pwdProtectedEmail.test.ts | 24 ++--- 3 files changed, 59 insertions(+), 59 deletions(-) diff --git a/package.json b/package.json index ab8e7cb..ed8683a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "internxt-crypto", - "version": "1.3.1", + "version": "1.4.0", "main": "dist/index.js", "types": "dist/index.d.ts", "module": "dist/index.js", diff --git a/tests/email-crypto/hybridEmail.test.ts b/tests/email-crypto/hybridEmail.test.ts index 32f8167..8e9d3f6 100644 --- a/tests/email-crypto/hybridEmail.test.ts +++ b/tests/email-crypto/hybridEmail.test.ts @@ -17,7 +17,12 @@ import { HybridEncryptedEmailAndSubject, RecipientWithPublicKey, } from '../../src/types'; -import { EmailHybridDecryptionError, EmailHybridEncryptionError, EmailSymmetricDecryptionError, InvalidInputEmail } from '../../src/email-crypto/errors'; +import { + EmailHybridDecryptionError, + EmailHybridEncryptionError, + EmailSymmetricDecryptionError, + InvalidInputEmail, +} from '../../src/email-crypto/errors'; describe('Test email crypto functions', async () => { const email: EmailBody = { @@ -64,7 +69,9 @@ describe('Test email crypto functions', async () => { }; await expect(encryptEmailHybrid(email, badRecipient)).rejects.toThrow(EmailHybridEncryptionError); - await expect(encryptEmailAndSubjectHybrid(emailAndSubject, badRecipient)).rejects.toThrow(EmailHybridEncryptionError); + await expect(encryptEmailAndSubjectHybrid(emailAndSubject, badRecipient)).rejects.toThrow( + EmailHybridEncryptionError, + ); }); it('should throw an error if not intended recipient', async () => { @@ -111,7 +118,7 @@ describe('Test email crypto functions', async () => { expect(encryptedEmail[0].encEmailBody).toBe(encryptedEmail[1].encEmailBody); }); - it('should encrypt email and subject to multiple senders sucessfully', async () => { + it('should encrypt email and subject to multiple senders sucessfully', async () => { const encryptedEmail = await encryptEmailAndSubjectHybridForMultipleRecipients(emailAndSubject, [ bobWithPublicKeys, aliceWithPublicKeys, @@ -138,77 +145,67 @@ describe('Test email crypto functions', async () => { encryptEmailHybridForMultipleRecipients(email, [bobWithPublicKeys, badEveWithPublicKeys]), ).rejects.toThrow(EmailHybridEncryptionError); - await expect( + await expect( encryptEmailAndSubjectHybridForMultipleRecipients(emailAndSubject, [bobWithPublicKeys, badEveWithPublicKeys]), ).rejects.toThrow(EmailHybridEncryptionError); }); it('should throw an error if no recipients are provided', async () => { - await expect( - encryptEmailHybridForMultipleRecipients(email, []), - ).rejects.toThrow(InvalidInputEmail); + await expect(encryptEmailHybridForMultipleRecipients(email, [])).rejects.toThrow(InvalidInputEmail); - await expect( + await expect( encryptEmailHybridForMultipleRecipients(email, undefined as unknown as RecipientWithPublicKey[]), ).rejects.toThrow(InvalidInputEmail); - await expect( - encryptEmailAndSubjectHybridForMultipleRecipients(emailAndSubject, []), - ).rejects.toThrow(InvalidInputEmail); + await expect(encryptEmailAndSubjectHybridForMultipleRecipients(emailAndSubject, [])).rejects.toThrow( + InvalidInputEmail, + ); await expect( - encryptEmailAndSubjectHybridForMultipleRecipients(emailAndSubject, undefined as unknown as RecipientWithPublicKey[]), + encryptEmailAndSubjectHybridForMultipleRecipients( + emailAndSubject, + undefined as unknown as RecipientWithPublicKey[], + ), ).rejects.toThrow(InvalidInputEmail); }); it('should throw an error if input is invalid', async () => { + await expect(encryptEmailHybrid({} as EmailBody, bobWithPublicKeys)).rejects.toThrow(InvalidInputEmail); - await expect( - encryptEmailHybrid( {} as EmailBody, bobWithPublicKeys), - ).rejects.toThrow(InvalidInputEmail); + await expect(encryptEmailAndSubjectHybrid({} as EmailBodyAndSubject, bobWithPublicKeys)).rejects.toThrow( + InvalidInputEmail, + ); await expect( - encryptEmailAndSubjectHybrid( {} as EmailBodyAndSubject, bobWithPublicKeys), + encryptEmailHybridForMultipleRecipients({} as EmailBody, [bobWithPublicKeys, aliceWithPublicKeys]), ).rejects.toThrow(InvalidInputEmail); await expect( - encryptEmailHybridForMultipleRecipients( {} as EmailBody, [ - bobWithPublicKeys, - aliceWithPublicKeys, - ]), + encryptEmailAndSubjectHybridForMultipleRecipients({} as EmailBodyAndSubject, [ + bobWithPublicKeys, + aliceWithPublicKeys, + ]), ).rejects.toThrow(InvalidInputEmail); - await expect( - encryptEmailAndSubjectHybridForMultipleRecipients( {} as EmailBodyAndSubject, [ - bobWithPublicKeys, - aliceWithPublicKeys, - ]), - ).rejects.toThrow(InvalidInputEmail); - - await expect( - decryptEmailHybrid( {} as HybridEncryptedEmail, bobPrivateKeys), - ).rejects.toThrow(EmailHybridDecryptionError); + await expect(decryptEmailHybrid({} as HybridEncryptedEmail, bobPrivateKeys)).rejects.toThrow( + EmailHybridDecryptionError, + ); - await expect( - decryptEmailAndSubjectHybrid( {} as HybridEncryptedEmailAndSubject, bobPrivateKeys), - ).rejects.toThrow(EmailHybridDecryptionError); + await expect(decryptEmailAndSubjectHybrid({} as HybridEncryptedEmailAndSubject, bobPrivateKeys)).rejects.toThrow( + EmailHybridDecryptionError, + ); }); - it('should throw an error if encrypted email is modified', async () => { + it('should throw an error if encrypted email is modified', async () => { const encryptedEmail = await encryptEmailHybrid(emailAndSubject, bobWithPublicKeys); const modifiedCiphertext = encryptedEmail; modifiedCiphertext.encEmailBody.encText += 'modified ciphertext'; - await expect( - decryptEmailHybrid(modifiedCiphertext, bobPrivateKeys), - ).rejects.toThrow(EmailSymmetricDecryptionError); + await expect(decryptEmailHybrid(modifiedCiphertext, bobPrivateKeys)).rejects.toThrow(EmailSymmetricDecryptionError); const modifiedKey = encryptedEmail; modifiedKey.encryptedKey.encryptedKey += 'modified key'; - await expect( - decryptEmailHybrid(modifiedCiphertext, bobPrivateKeys), - ).rejects.toThrow(EmailHybridDecryptionError); - + await expect(decryptEmailHybrid(modifiedCiphertext, bobPrivateKeys)).rejects.toThrow(EmailHybridDecryptionError); }); it('should throw an error if encrypted email and subject are modified', async () => { @@ -216,15 +213,14 @@ describe('Test email crypto functions', async () => { const modifiedCiphertext = encryptedEmail; modifiedCiphertext.encEmailBody.encText += 'modified ciphertext'; - await expect( - decryptEmailAndSubjectHybrid(modifiedCiphertext, bobPrivateKeys), - ).rejects.toThrow(EmailSymmetricDecryptionError); + await expect(decryptEmailAndSubjectHybrid(modifiedCiphertext, bobPrivateKeys)).rejects.toThrow( + EmailSymmetricDecryptionError, + ); const modifiedKey = encryptedEmail; modifiedKey.encryptedKey.encryptedKey += 'modified key'; - await expect( - decryptEmailAndSubjectHybrid(modifiedCiphertext, bobPrivateKeys), - ).rejects.toThrow(EmailHybridDecryptionError); - + await expect(decryptEmailAndSubjectHybrid(modifiedCiphertext, bobPrivateKeys)).rejects.toThrow( + EmailHybridDecryptionError, + ); }); }); diff --git a/tests/email-crypto/pwdProtectedEmail.test.ts b/tests/email-crypto/pwdProtectedEmail.test.ts index cb81360..7c38251 100644 --- a/tests/email-crypto/pwdProtectedEmail.test.ts +++ b/tests/email-crypto/pwdProtectedEmail.test.ts @@ -35,8 +35,10 @@ describe('Test email crypto functions', () => { }); it('should throw an error if encryption fails', async () => { - await expect(createPwdProtectedEmail( {} as unknown as EmailBody, sharedSecret)).rejects.toThrow(InvalidInputEmail); - await expect(createPwdProtectedEmailAndSubject( {} as unknown as EmailBodyAndSubject, sharedSecret)).rejects.toThrow(InvalidInputEmail); + await expect(createPwdProtectedEmail({} as unknown as EmailBody, sharedSecret)).rejects.toThrow(InvalidInputEmail); + await expect(createPwdProtectedEmailAndSubject({} as unknown as EmailBodyAndSubject, sharedSecret)).rejects.toThrow( + InvalidInputEmail, + ); }); it('should throw an error if a different secret used for decryption', async () => { @@ -45,7 +47,9 @@ describe('Test email crypto functions', () => { await expect(decryptPwdProtectedEmail(encryptedEmail, wrongSecret)).rejects.toThrow(EmailPasswordOpenError); const encryptedEmailAndSubject = await createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret); - await expect(decryptPwdProtectedEmailAndSubject(encryptedEmailAndSubject, wrongSecret)).rejects.toThrow(EmailPasswordOpenError); + await expect(decryptPwdProtectedEmailAndSubject(encryptedEmailAndSubject, wrongSecret)).rejects.toThrow( + EmailPasswordOpenError, + ); }); it('should throw an error if password-protected email is modified', async () => { @@ -53,18 +57,18 @@ describe('Test email crypto functions', () => { const modifiedCiphertext = encryptedEmail; modifiedCiphertext.encEmailBody.encText += 'modified ciphertext'; - await expect( - decryptPwdProtectedEmail(modifiedCiphertext, sharedSecret), - ).rejects.toThrow(EmailSymmetricDecryptionError); + await expect(decryptPwdProtectedEmail(modifiedCiphertext, sharedSecret)).rejects.toThrow( + EmailSymmetricDecryptionError, + ); }); - it('should throw an error if password-protected email and subject are modified', async () => { + it('should throw an error if password-protected email and subject are modified', async () => { const encryptedEmail = await createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret); const modifiedCiphertext = encryptedEmail; modifiedCiphertext.encEmailBody.encText += 'modified ciphertext'; - await expect( - decryptPwdProtectedEmailAndSubject(modifiedCiphertext, sharedSecret), - ).rejects.toThrow(EmailSymmetricDecryptionError); + await expect(decryptPwdProtectedEmailAndSubject(modifiedCiphertext, sharedSecret)).rejects.toThrow( + EmailSymmetricDecryptionError, + ); }); }); From 858de0f76489a24c0fa464e028b1a0a969e3cec0 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 28 May 2026 11:50:10 +0200 Subject: [PATCH 06/17] remove body, add more tests, simplify errors --- README.md | 71 +----------------- src/email-crypto/core.ts | 60 +++++++-------- src/email-crypto/coreSubject.ts | 55 +++++++------- .../hybridEncryptedEmailAndSubject.ts | 30 ++++---- src/email-crypto/hybridEncyptedEmail.ts | 37 +++++---- src/email-crypto/pwdProtectedEmail.ts | 10 +-- .../pwdProtectedEmailAndSubject.ts | 12 ++- src/index.ts | 12 +-- src/types.ts | 8 +- tests/email-crypto/core.test.ts | 34 ++++----- tests/email-crypto/hybridEmail.test.ts | 14 ++-- tests/email-crypto/pwdProtectedEmail.test.ts | 4 +- .../pwdProtectedEmailCoreErrors.test.ts | 73 ++++++++++++++++++ .../pwdProtectedEmailNobleErrors.test.ts | 75 +++++++++++++++++++ 14 files changed, 284 insertions(+), 211 deletions(-) create mode 100644 tests/email-crypto/pwdProtectedEmailCoreErrors.test.ts create mode 100644 tests/email-crypto/pwdProtectedEmailNobleErrors.test.ts diff --git a/README.md b/README.md index 6727a8b..88825e1 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ const emailAndSubject: EmailBodyAndSubject = { const encryptedEmailAndSubject = await encryptEmailAndSubjectHybrid(emailAndSubject, bobWithPublicKeys); const decryptedEmailAndSubject = await decryptEmailAndSubjectHybrid(encryptedEmailAndSubject, bobPrivateKeys); -expect(encryptedEmailAndSubject.encEmailBody.encSubject).not.toBe(emailAndSubject.subject); +expect(encryptedEmailAndSubject.encEmail.encSubject).not.toBe(emailAndSubject.subject); expect(decryptedEmailAndSubject).toStrictEqual(emailAndSubject); @@ -152,73 +152,4 @@ const resultRec = await openRecoveryKeystore(recoveryCodes, recoveryKeystore); expect(resultEnc).toStrictEqual(resultRec); -// Email storage and search - -// Between sessions emails are stored encrypted in IndexedDB. The encryption key is derived from user's mnemonic -// During the session, all emails are decrypted and stored in the cache (up to 600 MB, if excides - we delete oldests emails) -// For search, we build a search index from cache, then use Flexsearch for the search. -// The search is doen separately for email content, subject, sender and recivers. - -// Open IndexedDB database -const userID = 'user ID'; -const db = await openDatabase(userID); -const mnemonic = genMnemonic(); - -// Derive key for encrypting emails before storing them in a local database -const key = await deriveDatabaseKey(mnemonic); - -// Derive key for encrypting email draft -const key = await deriveDatabaseKey(mnemonic); - -// Encrypt and store one or several emails -await encryptAndStoreEmail(email, key, db); -await encryptAndStoreManyEmail(emails, key, db); - -// Delete given email by its ID -await deleteEmail(emailID, db); - -// Delete oldests emails -const number = 5; -await deleteOldestEmails(number, db); - -// Get all emails with or without sorting -const allEmails = await getAndDecryptAllEmails(key, db); -const newestFirst = await getAllEmailsSortedNewestFirst(db, key); -const oldestFirst = await getAllEmailsSortedOldestFirst(db, key); - -// Get the number of stored emails -const count = await getEmailCount(db); - -// Close IndexedDB database -closeDatabase(db); - -// Delete IndexedDB database -await deleteDatabase(userID); - -// Create email cache -const esCache = await createCacheFromDB(key, db); - -// Add one or multiple emails to cache -const result = addEmailToCache(email, esCache); -expect(result.success).toBe(true); - -const result = addEmailsToCache(emails, esCache); -expect(result.success).toBe(true); - -// Get email from cache by its ID -const email = await getEmailFromCache(emailID, esCache); - -// Delete email from cache by its ID -await deleteEmailFromCache(emailID, esCache); - -// Create search index and search by query - const searchIndex = await buildSearchIndexFromCache(esCache); - const query = 'keywords to search'; - const options = { - fields: ['subject'], // in which fields to search, all by deafult (subject, body, from, to) - limit: 5, // result limit, 50 by default - boost: { subject: 3, body: 1, from: 2, to: 2 }, // custom waights for matches in different email parts - }; - const result: EmailSearchResult = await searchEmails(query, esCache, searchIndex); - ``` \ No newline at end of file diff --git a/src/email-crypto/core.ts b/src/email-crypto/core.ts index 4a960f4..2c862ae 100644 --- a/src/email-crypto/core.ts +++ b/src/email-crypto/core.ts @@ -15,54 +15,54 @@ import { } from './errors'; /** - * Symmetrically encrypts email body. + * Symmetrically encrypts email. * - * @param body - The email body to encrypt. + * @param email - The email to encrypt. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The resulting encrypted email body and symmetric key used for encryption + * @returns The resulting encrypted email and symmetric key used for encryption */ -export async function encryptEmailBody( - body: EmailBody, +export async function encryptEmail( + email: EmailBody, aux?: Uint8Array, ): Promise<{ - encEmailBody: EmailBodyEncrypted; + encEmail: EmailBodyEncrypted; encryptionKey: Uint8Array; }> { - try { - if (!body.text) { + if (!email.text) { throw new InvalidInputEmail(); } + try { + const encryptionKey = genSymmetricKey(); - const encEmailBody = await encryptEmailBodyWithKey(body, encryptionKey, aux); + const encEmail = await encryptEmailWithKey(email, encryptionKey, aux); - return { encEmailBody, encryptionKey }; + return { encEmail, encryptionKey }; } catch (error) { - if (error instanceof InvalidInputEmail) throw error; throw new EmailSymmetricEncryptionError(error instanceof Error ? error.message : String(error)); } } /** - * Symmetrically encrypts email body with the given key. + * Symmetrically encrypts email with the given key. * - * @param body - The email body to encrypt. + * @param email - The email to encrypt. * @param encryptionKey - The symmetric key to encrypt the email. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The resulting encrypted email body and symmetric key used for encryption + * @returns The resulting encrypted email and symmetric key used for encryption */ -export async function encryptEmailBodyWithKey( - body: EmailBody, +export async function encryptEmailWithKey( + email: EmailBody, encryptionKey: Uint8Array, aux?: Uint8Array, ): Promise { try { - const text = UTF8ToUint8(body.text); + const text = UTF8ToUint8(email.text); const encryptedText = await encryptSymmetrically(encryptionKey, text, aux); const encText = uint8ArrayToBase64(encryptedText); const enc: EmailBodyEncrypted = { encText }; - if (body.attachments) { - const promises = body.attachments.map((attachment) => { + if (email.attachments) { + const promises = email.attachments.map((attachment) => { const binaryAttachment = UTF8ToUint8(attachment); return encryptSymmetrically(encryptionKey, binaryAttachment, aux); }); @@ -76,32 +76,32 @@ export async function encryptEmailBodyWithKey( } /** - * Decrypts symmetrically encrypted email body. + * Decrypts symmetrically encrypted email. * - * @param encEmailBody - The email body to decrypt. + * @param encEmail - The email to decrypt. * @param encryptionKey - The symmetric key to decrypt the email. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The resulting decrypted email body + * @returns The resulting decrypted email */ -export async function decryptEmailBody( - encEmailBody: EmailBodyEncrypted, +export async function decryptEmail( + encEmail: EmailBodyEncrypted, encryptionKey: Uint8Array, aux?: Uint8Array, ): Promise { try { - const encText = base64ToUint8Array(encEmailBody.encText); + const encText = base64ToUint8Array(encEmail.encText); const textArray = await decryptSymmetrically(encryptionKey, encText, aux); const text = uint8ToUTF8(textArray); - const body: EmailBody = { text }; + const email: EmailBody = { text }; - if (encEmailBody.encAttachments) { - const encAttachments = encEmailBody.encAttachments?.map(base64ToUint8Array); + if (encEmail.encAttachments) { + const encAttachments = encEmail.encAttachments?.map(base64ToUint8Array); const promises = encAttachments?.map((encAtt) => decryptSymmetrically(encryptionKey, encAtt, aux)); const decryptedAttachments = await Promise.all(promises); - body.attachments = decryptedAttachments?.map((att) => uint8ToUTF8(att)); + email.attachments = decryptedAttachments?.map((att) => uint8ToUTF8(att)); } - return body; + return email; } catch (error) { throw new EmailSymmetricDecryptionError(error instanceof Error ? error.message : String(error)); } diff --git a/src/email-crypto/coreSubject.ts b/src/email-crypto/coreSubject.ts index ab129f4..717f94e 100644 --- a/src/email-crypto/coreSubject.ts +++ b/src/email-crypto/coreSubject.ts @@ -1,85 +1,82 @@ import { EmailBodyAndSubject, EmailBodyAndSubjectEncrypted } from '../types'; import { encryptSymmetrically, decryptSymmetrically, genSymmetricKey } from '../symmetric-crypto'; -import { encryptEmailBodyWithKey, decryptEmailBody } from './core'; +import { encryptEmailWithKey, decryptEmail } from './core'; import { UTF8ToUint8, base64ToUint8Array, uint8ArrayToBase64, uint8ToUTF8 } from '../utils'; import { InvalidInputEmail, EmailSymmetricDecryptionError, EmailSymmetricEncryptionError } from './errors'; /** - * Symmetrically encrypts email body and subject. + * Symmetrically encrypts email and subject. * - * @param body - The email body and subject to encrypt. + * @param email - The email and subject to encrypt. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The resulting encrypted email body and symmetric key used for encryption + * @returns The resulting encrypted email and symmetric key used for encryption */ -export async function encryptEmailBodyAndSubject( - body: EmailBodyAndSubject, +export async function encryptEmailAndSubject( + email: EmailBodyAndSubject, aux?: Uint8Array, ): Promise<{ - encEmailBody: EmailBodyAndSubjectEncrypted; + encEmail: EmailBodyAndSubjectEncrypted; encryptionKey: Uint8Array; }> { - try { - if (!body.text || !body.subject) { + if (!email.text || !email.subject) { throw new InvalidInputEmail(); } + try { const encryptionKey = genSymmetricKey(); - const encEmailBody = await encryptEmailBodyAndSubjectWithKey(body, encryptionKey, aux); + const encEmail = await encryptEmailAndSubjectWithKey(email, encryptionKey, aux); - return { encEmailBody, encryptionKey }; + return { encEmail, encryptionKey }; } catch (error) { - if (error instanceof InvalidInputEmail) throw error; throw new EmailSymmetricEncryptionError(error instanceof Error ? error.message : String(error)); } } /** - * Symmetrically encrypts email body and subject with the given key. + * Symmetrically encrypts email and subject with the given key. * - * @param body - The email body and subject to encrypt. + * @param email - The email and subject to encrypt. * @param encryptionKey - The symmetric key to encrypt the email. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The resulting encrypted email body and symmetric key used for encryption + * @returns The resulting encrypted email and symmetric key used for encryption */ -export async function encryptEmailBodyAndSubjectWithKey( - body: EmailBodyAndSubject, +export async function encryptEmailAndSubjectWithKey( + email: EmailBodyAndSubject, encryptionKey: Uint8Array, aux?: Uint8Array, ): Promise { try { - const enc = await encryptEmailBodyWithKey(body, encryptionKey, aux); - const subject = UTF8ToUint8(body.subject); + const enc = await encryptEmailWithKey(email, encryptionKey, aux); + const subject = UTF8ToUint8(email.subject); const subjectEnc = await encryptSymmetrically(encryptionKey, subject, aux); const encSubject = uint8ArrayToBase64(subjectEnc); return { ...enc, encSubject }; } catch (error) { - if (error instanceof InvalidInputEmail) throw error; throw new EmailSymmetricEncryptionError(error instanceof Error ? error.message : String(error)); } } /** - * Decrypts symmetrically encrypted email body and subject. + * Decrypts symmetrically encrypted email and email subject. * - * @param encEmailBody - The email body and subject to decrypt. + * @param encEmail - The encrypted email and subject to decrypt. * @param encryptionKey - The symmetric key to decrypt the email. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The resulting decrypted email body + * @returns The resulting decrypted email and subject */ -export async function decryptEmailBodyAndSubject( - encEmailBody: EmailBodyAndSubjectEncrypted, +export async function decryptEmailAndSubject( + encEmail: EmailBodyAndSubjectEncrypted, encryptionKey: Uint8Array, aux?: Uint8Array, ): Promise { try { - const encSubject = base64ToUint8Array(encEmailBody.encSubject); + const encSubject = base64ToUint8Array(encEmail.encSubject); const subjectArray = await decryptSymmetrically(encryptionKey, encSubject, aux); const subject = uint8ToUTF8(subjectArray); - const body = await decryptEmailBody(encEmailBody, encryptionKey, aux); + const email = await decryptEmail(encEmail, encryptionKey, aux); - return { ...body, subject }; + return { ...email, subject }; } catch (error) { - if (error instanceof InvalidInputEmail) throw error; throw new EmailSymmetricDecryptionError(error instanceof Error ? error.message : String(error)); } } diff --git a/src/email-crypto/hybridEncryptedEmailAndSubject.ts b/src/email-crypto/hybridEncryptedEmailAndSubject.ts index 9c1033c..1a279e3 100644 --- a/src/email-crypto/hybridEncryptedEmailAndSubject.ts +++ b/src/email-crypto/hybridEncryptedEmailAndSubject.ts @@ -1,6 +1,6 @@ import { RecipientWithPublicKey, EmailBodyAndSubject, HybridEncryptedEmailAndSubject } from '../types'; import { encryptKeysHybrid, decryptKeysHybrid } from './core'; -import { encryptEmailBodyAndSubject, decryptEmailBodyAndSubject } from './coreSubject'; +import { encryptEmailAndSubject, decryptEmailAndSubject } from './coreSubject'; import { FailedToDecryptEmail, FailedToEncryptEmail, @@ -12,22 +12,22 @@ import { } from './errors'; /** - * Encrypts the email body and its subject using hybrid encryption. + * Encrypts the email and its subject using hybrid encryption. * - * @param body - The email body and subject to encrypt. + * @param email - The email and subject to encrypt. * @param recipientPublicKeys - The public keys of the recipient. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The encrypted email body + * @returns The encrypted email and subject */ export async function encryptEmailAndSubjectHybrid( - body: EmailBodyAndSubject, + email: EmailBodyAndSubject, recipient: RecipientWithPublicKey, aux?: Uint8Array, ): Promise { try { - const { encryptionKey, encEmailBody } = await encryptEmailBodyAndSubject(body, aux); + const { encryptionKey, encEmail } = await encryptEmailAndSubject(email, aux); const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient); - return { encEmailBody, encryptedKey }; + return { encEmail, encryptedKey }; } catch (error) { if (error instanceof InvalidInputEmail) throw error; if (error instanceof EmailSymmetricEncryptionError) throw error; @@ -37,15 +37,15 @@ export async function encryptEmailAndSubjectHybrid( } /** - * Encrypts the email body and its subject using hybrid encryption for multiple recipients. + * Encrypts the email and its subject using hybrid encryption for multiple recipients. * - * @param body - The email body and subject to encrypt for multiple recipients. + * @param email - The email and subject to encrypt for multiple recipients. * @param recipients - The recipients with corresponding public keys. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The set of encrypted email bodies + * @returns The set of encrypted emails and subjects */ export async function encryptEmailAndSubjectHybridForMultipleRecipients( - body: EmailBodyAndSubject, + email: EmailBodyAndSubject, recipients: RecipientWithPublicKey[], aux?: Uint8Array, ): Promise { @@ -53,13 +53,13 @@ export async function encryptEmailAndSubjectHybridForMultipleRecipients( if (!recipients || recipients.length === 0) { throw new InvalidInputEmail(); } - const { encryptionKey, encEmailBody } = await encryptEmailBodyAndSubject(body, aux); + const { encryptionKey, encEmail } = await encryptEmailAndSubject(email, aux); const encryptedEmails: HybridEncryptedEmailAndSubject[] = []; for (const recipient of recipients) { const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient); encryptedEmails.push({ - encEmailBody: encEmailBody, + encEmail, encryptedKey, }); } @@ -78,7 +78,7 @@ export async function encryptEmailAndSubjectHybridForMultipleRecipients( * @param hybridEmail - The encrypted email and subject. * @param recipientPrivateHybridKeys - The private key of the recipient. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The decrypted email body + * @returns The decrypted email and subject */ export async function decryptEmailAndSubjectHybrid( hybridEmail: HybridEncryptedEmailAndSubject, @@ -87,7 +87,7 @@ export async function decryptEmailAndSubjectHybrid( ): Promise { try { const encryptionKey = await decryptKeysHybrid(hybridEmail.encryptedKey, recipientPrivateHybridKeys); - return await decryptEmailBodyAndSubject(hybridEmail.encEmailBody, encryptionKey, aux); + return await decryptEmailAndSubject(hybridEmail.encEmail, encryptionKey, aux); } catch (error) { if (error instanceof InvalidInputEmail) throw error; if (error instanceof EmailHybridDecryptionError) throw error; diff --git a/src/email-crypto/hybridEncyptedEmail.ts b/src/email-crypto/hybridEncyptedEmail.ts index 31440a4..b166ec7 100644 --- a/src/email-crypto/hybridEncyptedEmail.ts +++ b/src/email-crypto/hybridEncyptedEmail.ts @@ -1,5 +1,5 @@ import { HybridEncryptedEmail, EmailBody, RecipientWithPublicKey } from '../types'; -import { decryptEmailBody, encryptKeysHybrid, decryptKeysHybrid, encryptEmailBody } from './core'; +import { decryptEmail, encryptKeysHybrid, decryptKeysHybrid, encryptEmail } from './core'; import { FailedToDecryptEmail, FailedToEncryptEmail, @@ -10,22 +10,22 @@ import { EmailSymmetricEncryptionError, } from './errors'; /** - * Encrypts the email body using hybrid encryption. + * Encrypts the email using hybrid encryption. * - * @param body - The email body to encrypt. + * @param email - The email to encrypt. * @param recipientPublicKeys - The public keys of the recipient. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The encrypted email body + * @returns The encrypted email */ export async function encryptEmailHybrid( - body: EmailBody, + email: EmailBody, recipient: RecipientWithPublicKey, aux?: Uint8Array, ): Promise { try { - const { encryptionKey, encEmailBody } = await encryptEmailBody(body, aux); + const { encryptionKey, encEmail } = await encryptEmail(email, aux); const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient); - return { encEmailBody, encryptedKey }; + return { encEmail, encryptedKey }; } catch (error) { if (error instanceof InvalidInputEmail) throw error; if (error instanceof EmailSymmetricEncryptionError) throw error; @@ -35,15 +35,15 @@ export async function encryptEmailHybrid( } /** - * Encrypts the email body using hybrid encryption for multiple recipients. + * Encrypts the email using hybrid encryption for multiple recipients. * - * @param body - The email body to encrypt for multiple recipients. + * @param email - The email to encrypt for multiple recipients. * @param recipients - The recipients with corresponding public keys. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The set of encrypted email bodies + * @returns The set of encrypted emails */ export async function encryptEmailHybridForMultipleRecipients( - body: EmailBody, + email: EmailBody, recipients: RecipientWithPublicKey[], aux?: Uint8Array, ): Promise { @@ -51,13 +51,13 @@ export async function encryptEmailHybridForMultipleRecipients( if (!recipients || recipients.length === 0) { throw new InvalidInputEmail(); } - const { encryptionKey, encEmailBody } = await encryptEmailBody(body, aux); + const { encryptionKey, encEmail } = await encryptEmail(email, aux); const encryptedEmails: HybridEncryptedEmail[] = []; for (const recipient of recipients) { const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient); encryptedEmails.push({ - encEmailBody: encEmailBody, + encEmail, encryptedKey, }); } @@ -73,20 +73,19 @@ export async function encryptEmailHybridForMultipleRecipients( /** * Decrypts the email using hybrid encryption. * - * @param encEmailBody - The encrypted email. + * @param encEmail - The encrypted email. * @param recipientPrivateHybridKeys - The private key of the recipient. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The decrypted email body + * @returns The decrypted email */ export async function decryptEmailHybrid( - encEmailBody: HybridEncryptedEmail, + encEmail: HybridEncryptedEmail, recipientPrivateHybridKeys: Uint8Array, aux?: Uint8Array, ): Promise { try { - const encryptionKey = await decryptKeysHybrid(encEmailBody.encryptedKey, recipientPrivateHybridKeys); - const body = await decryptEmailBody(encEmailBody.encEmailBody, encryptionKey, aux); - return body; + const encryptionKey = await decryptKeysHybrid(encEmail.encryptedKey, recipientPrivateHybridKeys); + return await decryptEmail(encEmail.encEmail, encryptionKey, aux); } catch (error) { if (error instanceof EmailHybridDecryptionError) throw error; if (error instanceof EmailSymmetricDecryptionError) throw error; diff --git a/src/email-crypto/pwdProtectedEmail.ts b/src/email-crypto/pwdProtectedEmail.ts index c9ed5cc..fba80e9 100644 --- a/src/email-crypto/pwdProtectedEmail.ts +++ b/src/email-crypto/pwdProtectedEmail.ts @@ -1,5 +1,5 @@ import { PwdProtectedEmail, EmailBody } from '../types'; -import { decryptEmailBody, passwordProtectKey, removePasswordProtection, encryptEmailBody } from './core'; +import { decryptEmail, passwordProtectKey, removePasswordProtection, encryptEmail } from './core'; import { EmailSymmetricEncryptionError, FailedToDecryptEmail, @@ -18,15 +18,15 @@ import { * @returns The password-protected email */ export async function createPwdProtectedEmail( - emailBody: EmailBody, + email: EmailBody, password: string, aux?: Uint8Array, ): Promise { try { - const { encryptionKey, encEmailBody } = await encryptEmailBody(emailBody, aux); + const { encryptionKey, encEmail } = await encryptEmail(email, aux); const encryptedKey = await passwordProtectKey(encryptionKey, password); - return { encEmailBody, encryptedKey }; + return { encEmail, encryptedKey }; } catch (error) { if (error instanceof InvalidInputEmail) throw error; if (error instanceof EmailSymmetricEncryptionError) throw error; @@ -50,7 +50,7 @@ export async function decryptPwdProtectedEmail( ): Promise { try { const encryptionKey = await removePasswordProtection(encryptedEmail.encryptedKey, password); - return await decryptEmailBody(encryptedEmail.encEmailBody, encryptionKey, aux); + return await decryptEmail(encryptedEmail.encEmail, encryptionKey, aux); } catch (error) { if (error instanceof InvalidInputEmail) throw error; if (error instanceof EmailPasswordOpenError) throw error; diff --git a/src/email-crypto/pwdProtectedEmailAndSubject.ts b/src/email-crypto/pwdProtectedEmailAndSubject.ts index 77bc069..b80ecc0 100644 --- a/src/email-crypto/pwdProtectedEmailAndSubject.ts +++ b/src/email-crypto/pwdProtectedEmailAndSubject.ts @@ -1,6 +1,6 @@ import { EmailBodyAndSubject, PwdProtectedEmailAndSubject } from '../types'; import { passwordProtectKey, removePasswordProtection } from './core'; -import { encryptEmailBodyAndSubject, decryptEmailBodyAndSubject } from './coreSubject'; +import { encryptEmailAndSubject, decryptEmailAndSubject } from './coreSubject'; import { FailedToDecryptEmail, FailedToEncryptEmail, @@ -20,15 +20,15 @@ import { * @returns The password-protected email */ export async function createPwdProtectedEmailAndSubject( - emailBody: EmailBodyAndSubject, + email: EmailBodyAndSubject, password: string, aux?: Uint8Array, ): Promise { try { - const { encryptionKey, encEmailBody } = await encryptEmailBodyAndSubject(emailBody, aux); + const { encryptionKey, encEmail } = await encryptEmailAndSubject(email, aux); const encryptedKey = await passwordProtectKey(encryptionKey, password); - return { encEmailBody, encryptedKey }; + return { encEmail, encryptedKey }; } catch (error) { if (error instanceof InvalidInputEmail) throw error; if (error instanceof EmailSymmetricEncryptionError) throw error; @@ -52,9 +52,7 @@ export async function decryptPwdProtectedEmailAndSubject( ): Promise { try { const encryptionKey = await removePasswordProtection(encryptedEmail.encryptedKey, password); - - const body = await decryptEmailBodyAndSubject(encryptedEmail.encEmailBody, encryptionKey, aux); - return body; + return await decryptEmailAndSubject(encryptedEmail.encEmail, encryptionKey, aux); } catch (error) { if (error instanceof InvalidInputEmail) throw error; if (error instanceof EmailPasswordOpenError) throw error; diff --git a/src/index.ts b/src/index.ts index 6e5b921..c3ec703 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,12 +13,12 @@ export { createPwdProtectedEmailAndSubject, decryptPwdProtectedEmailAndSubject, generateEmailKeys, - decryptEmailBody, - encryptEmailBody, - encryptEmailBodyWithKey, - encryptEmailBodyAndSubject, - encryptEmailBodyAndSubjectWithKey, - decryptEmailBodyAndSubject, + decryptEmail, + encryptEmail, + encryptEmailWithKey, + encryptEmailAndSubject, + encryptEmailAndSubjectWithKey, + decryptEmailAndSubject, deriveDatabaseKey, deriveEmailDraftKey, FailedToEncryptEmail, diff --git a/src/types.ts b/src/types.ts index d509628..4e3b786 100644 --- a/src/types.ts +++ b/src/types.ts @@ -17,22 +17,22 @@ export type HybridKeyPair = { export type HybridEncryptedEmail = { encryptedKey: HybridEncKey; - encEmailBody: EmailBodyEncrypted; + encEmail: EmailBodyEncrypted; }; export type HybridEncryptedEmailAndSubject = { encryptedKey: HybridEncKey; - encEmailBody: EmailBodyAndSubjectEncrypted; + encEmail: EmailBodyAndSubjectEncrypted; }; export type PwdProtectedEmail = { encryptedKey: PwdProtectedKey; - encEmailBody: EmailBodyEncrypted; + encEmail: EmailBodyEncrypted; }; export type PwdProtectedEmailAndSubject = { encryptedKey: PwdProtectedKey; - encEmailBody: EmailBodyAndSubjectEncrypted; + encEmail: EmailBodyAndSubjectEncrypted; }; export type HybridEncKey = { diff --git a/tests/email-crypto/core.test.ts b/tests/email-crypto/core.test.ts index a99d0ad..1d6f0e0 100644 --- a/tests/email-crypto/core.test.ts +++ b/tests/email-crypto/core.test.ts @@ -1,12 +1,12 @@ import { describe, expect, it } from 'vitest'; import { EmailBody, EmailBodyAndSubject } from '../../src/types'; import { - decryptEmailBody, - encryptEmailBody, + decryptEmail, + encryptEmail, deriveDatabaseKey, deriveEmailDraftKey, - encryptEmailBodyAndSubject, - decryptEmailBodyAndSubject, + encryptEmailAndSubject, + decryptEmailAndSubject, } from '../../src/email-crypto'; import { genMnemonic } from '../../src/utils'; import { genSymmetricKey } from '../../src/symmetric-crypto'; @@ -15,7 +15,7 @@ import { EmailSymmetricDecryptionError, InvalidInputEmail } from '../../src/emai describe('Test email crypto functions', () => { const emailBody: EmailBody = { - text: 'test body', + text: 'test email', }; const emailBodyAndSubject: EmailBodyAndSubject = { @@ -26,35 +26,35 @@ describe('Test email crypto functions', () => { const aux = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); it('should encrypt and decrypt email', async () => { - const { encEmailBody, encryptionKey } = await encryptEmailBody(emailBody, aux); - const result = await decryptEmailBody(encEmailBody, encryptionKey, aux); + const { encEmail, encryptionKey } = await encryptEmail(emailBody, aux); + const result = await decryptEmail(encEmail, encryptionKey, aux); expect(result).toEqual(emailBody); }); it('should encrypt and decrypt email with subject', async () => { - const { encEmailBody, encryptionKey } = await encryptEmailBodyAndSubject(emailBodyAndSubject, aux); - const result = await decryptEmailBodyAndSubject(encEmailBody, encryptionKey, aux); + const { encEmail, encryptionKey } = await encryptEmailAndSubject(emailBodyAndSubject, aux); + const result = await decryptEmailAndSubject(encEmail, encryptionKey, aux); expect(result).toEqual(emailBodyAndSubject); }); it('should throw an error if decryption fails', async () => { - const { encEmailBody, encryptionKey } = await encryptEmailBody(emailBody, aux); + const { encEmail, encryptionKey } = await encryptEmail(emailBody, aux); const badEncryptionKey = await genSymmetricKey(); - await expect(decryptEmailBody(encEmailBody, badEncryptionKey, aux)).rejects.toThrow(EmailSymmetricDecryptionError); + await expect(decryptEmail(encEmail, badEncryptionKey, aux)).rejects.toThrow(EmailSymmetricDecryptionError); const badAux = new Uint8Array([4, 5, 6, 7, 8]); - await expect(decryptEmailBody(encEmailBody, encryptionKey, badAux)).rejects.toThrow(EmailSymmetricDecryptionError); + await expect(decryptEmail(encEmail, encryptionKey, badAux)).rejects.toThrow(EmailSymmetricDecryptionError); }); it('should throw an error if decryption fails', async () => { - const { encEmailBody, encryptionKey } = await encryptEmailBodyAndSubject(emailBodyAndSubject, aux); + const { encEmail, encryptionKey } = await encryptEmailAndSubject(emailBodyAndSubject, aux); const badEncryptionKey = await genSymmetricKey(); - await expect(decryptEmailBodyAndSubject(encEmailBody, badEncryptionKey, aux)).rejects.toThrow( + await expect(decryptEmailAndSubject(encEmail, badEncryptionKey, aux)).rejects.toThrow( EmailSymmetricDecryptionError, ); const badAux = new Uint8Array([4, 5, 6, 7, 8]); - await expect(decryptEmailBodyAndSubject(encEmailBody, encryptionKey, badAux)).rejects.toThrow( + await expect(decryptEmailAndSubject(encEmail, encryptionKey, badAux)).rejects.toThrow( EmailSymmetricDecryptionError, ); }); @@ -63,8 +63,8 @@ describe('Test email crypto functions', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const badEmail: any = {}; badEmail.self = badEmail; - await expect(encryptEmailBody(badEmail, aux)).rejects.toThrow(InvalidInputEmail); - await expect(encryptEmailBodyAndSubject(badEmail, aux)).rejects.toThrow(InvalidInputEmail); + await expect(encryptEmail(badEmail, aux)).rejects.toThrow(InvalidInputEmail); + await expect(encryptEmailAndSubject(badEmail, aux)).rejects.toThrow(InvalidInputEmail); }); it('should derive symmetric key for database encryption', async () => { diff --git a/tests/email-crypto/hybridEmail.test.ts b/tests/email-crypto/hybridEmail.test.ts index 8e9d3f6..f9d0e09 100644 --- a/tests/email-crypto/hybridEmail.test.ts +++ b/tests/email-crypto/hybridEmail.test.ts @@ -56,7 +56,7 @@ describe('Test email crypto functions', async () => { it('should encrypt and decrypt email and subject sucessfully', async () => { const encryptedEmail = await encryptEmailAndSubjectHybrid(emailAndSubject, bobWithPublicKeys); - expect(encryptedEmail.encEmailBody?.encSubject).not.toBe(emailAndSubject.subject); + expect(encryptedEmail.encEmail?.encSubject).not.toBe(emailAndSubject.subject); const decryptedEmail = await decryptEmailAndSubjectHybrid(encryptedEmail, bobPrivateKeys); expect(decryptedEmail).toStrictEqual(emailAndSubject); @@ -88,14 +88,14 @@ describe('Test email crypto functions', async () => { }; const badEncryptedEmail: HybridEncryptedEmail = { encryptedKey: encKey, - encEmailBody: { + encEmail: { encText: 'mock encrypted text', }, }; const badEncryptedEmailAndSubject: HybridEncryptedEmailAndSubject = { encryptedKey: encKey, - encEmailBody: { + encEmail: { encText: 'mock encrypted text', encSubject: 'mock encrypted subject', }, @@ -115,7 +115,7 @@ describe('Test email crypto functions', async () => { ]); expect(encryptedEmail.length).toBe(2); - expect(encryptedEmail[0].encEmailBody).toBe(encryptedEmail[1].encEmailBody); + expect(encryptedEmail[0].encEmail).toBe(encryptedEmail[1].encEmail); }); it('should encrypt email and subject to multiple senders sucessfully', async () => { @@ -125,7 +125,7 @@ describe('Test email crypto functions', async () => { ]); expect(encryptedEmail.length).toBe(2); - expect(encryptedEmail[0].encEmailBody).toBe(encryptedEmail[1].encEmailBody); + expect(encryptedEmail[0].encEmail).toBe(encryptedEmail[1].encEmail); const emailDecryptedByBob = await decryptEmailAndSubjectHybrid(encryptedEmail[0], bobPrivateKeys); const emailDecryptedByAlice = await decryptEmailAndSubjectHybrid(encryptedEmail[1], alicePrivateKeys); @@ -200,7 +200,7 @@ describe('Test email crypto functions', async () => { const encryptedEmail = await encryptEmailHybrid(emailAndSubject, bobWithPublicKeys); const modifiedCiphertext = encryptedEmail; - modifiedCiphertext.encEmailBody.encText += 'modified ciphertext'; + modifiedCiphertext.encEmail.encText += 'modified ciphertext'; await expect(decryptEmailHybrid(modifiedCiphertext, bobPrivateKeys)).rejects.toThrow(EmailSymmetricDecryptionError); const modifiedKey = encryptedEmail; @@ -212,7 +212,7 @@ describe('Test email crypto functions', async () => { const encryptedEmail = await encryptEmailAndSubjectHybrid(emailAndSubject, bobWithPublicKeys); const modifiedCiphertext = encryptedEmail; - modifiedCiphertext.encEmailBody.encText += 'modified ciphertext'; + modifiedCiphertext.encEmail.encText += 'modified ciphertext'; await expect(decryptEmailAndSubjectHybrid(modifiedCiphertext, bobPrivateKeys)).rejects.toThrow( EmailSymmetricDecryptionError, ); diff --git a/tests/email-crypto/pwdProtectedEmail.test.ts b/tests/email-crypto/pwdProtectedEmail.test.ts index 7c38251..6406f92 100644 --- a/tests/email-crypto/pwdProtectedEmail.test.ts +++ b/tests/email-crypto/pwdProtectedEmail.test.ts @@ -56,7 +56,7 @@ describe('Test email crypto functions', () => { const encryptedEmail = await createPwdProtectedEmail(email, sharedSecret); const modifiedCiphertext = encryptedEmail; - modifiedCiphertext.encEmailBody.encText += 'modified ciphertext'; + modifiedCiphertext.encEmail.encText += 'modified ciphertext'; await expect(decryptPwdProtectedEmail(modifiedCiphertext, sharedSecret)).rejects.toThrow( EmailSymmetricDecryptionError, ); @@ -66,7 +66,7 @@ describe('Test email crypto functions', () => { const encryptedEmail = await createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret); const modifiedCiphertext = encryptedEmail; - modifiedCiphertext.encEmailBody.encText += 'modified ciphertext'; + modifiedCiphertext.encEmail.encText += 'modified ciphertext'; await expect(decryptPwdProtectedEmailAndSubject(modifiedCiphertext, sharedSecret)).rejects.toThrow( EmailSymmetricDecryptionError, ); diff --git a/tests/email-crypto/pwdProtectedEmailCoreErrors.test.ts b/tests/email-crypto/pwdProtectedEmailCoreErrors.test.ts new file mode 100644 index 0000000..b19d638 --- /dev/null +++ b/tests/email-crypto/pwdProtectedEmailCoreErrors.test.ts @@ -0,0 +1,73 @@ +import { vi, it, describe, beforeEach, expect } from 'vitest'; +import { createPwdProtectedEmail, createPwdProtectedEmailAndSubject, decryptPwdProtectedEmail, decryptPwdProtectedEmailAndSubject } from '../../src/email-crypto'; +import { FailedToDecryptEmail, FailedToEncryptEmail } from '../../src/email-crypto/errors'; +import { EmailBody, EmailBodyAndSubject, PwdProtectedEmail, PwdProtectedEmailAndSubject } from '../../src/types'; +import * as core from '../../src/email-crypto/core'; + +vi.mock('../../src/email-crypto/core', async () => { + const actual = await vi.importActual( + '../../src/email-crypto/core' + ); + + return { + ...actual, + passwordProtectKey: vi.fn(), + removePasswordProtection: vi.fn(), + }; +}); + +describe('Test email crypto functions', () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + const email: EmailBody = { + text: 'Hi Bob, This is a test message. -Alice.', + attachments: ['file1.txt', 'file2.txt'], + }; + + + const emailAndSubject: EmailBodyAndSubject = { + text: 'Hi Bob, This is a test message. -Alice.', + attachments: ['file1.txt', 'file2.txt'], + subject: 'test subject', + }; + + const sharedSecret = 'test shared secret'; + +it('throws FailedToEncryptEmail when encryption fails', async () => { + const spy = vi.spyOn(core, 'passwordProtectKey'); + + spy.mockRejectedValue( + new Error('passwordProtectKey: unexpected failure'), + ); + + await expect( + createPwdProtectedEmail(email, sharedSecret), + ).rejects.toBeInstanceOf(FailedToEncryptEmail); + + await expect( + createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret), + ).rejects.toBeInstanceOf(FailedToEncryptEmail); +}); + +it('throws FailedToDecryptEmail when decryption fails', async () => { + const encryptedEmail = {} as PwdProtectedEmail; + const encryptedEmailAndSubject = {} as PwdProtectedEmailAndSubject; + + const spy = vi.spyOn(core, 'removePasswordProtection'); + + spy.mockRejectedValue( + new Error('removePasswordProtection: unexpected failure'), + ); + + await expect( + decryptPwdProtectedEmail(encryptedEmail, sharedSecret), + ).rejects.toBeInstanceOf(FailedToDecryptEmail); + + await expect( + decryptPwdProtectedEmailAndSubject(encryptedEmailAndSubject, sharedSecret), + ).rejects.toBeInstanceOf(FailedToDecryptEmail); +}); + +}); \ No newline at end of file diff --git a/tests/email-crypto/pwdProtectedEmailNobleErrors.test.ts b/tests/email-crypto/pwdProtectedEmailNobleErrors.test.ts new file mode 100644 index 0000000..725f02b --- /dev/null +++ b/tests/email-crypto/pwdProtectedEmailNobleErrors.test.ts @@ -0,0 +1,75 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { + createPwdProtectedEmail, + createPwdProtectedEmailAndSubject, + EmailPasswordProtectError, + EmailSymmetricEncryptionError, +} from '../../src/email-crypto'; +import { EmailBody, EmailBodyAndSubject } from '../../src/types'; +import * as nobleUtils from '@noble/hashes/utils.js'; +import * as nobleWrapper from '@noble/ciphers/aes.js'; + +// Noble is ESM module and doesn't work with spyOn directly (module namespace is not configurable in ESM), must be mocked before. +// To mock it but keep the original implementation for most tests, we use importActual. +// vi.resetAllMocks(); before each test is a must to reset the mock back to importActual. +vi.mock('@noble/hashes/utils.js', async () => { + const actual = await vi.importActual< + typeof import('@noble/hashes/utils.js') + >('@noble/hashes/utils.js'); + + return { + ...actual, + randomBytes: vi.fn(actual.randomBytes), + }; +}); + + +vi.mock('@noble/ciphers/aes.js', async () => { + const actual = await vi.importActual< + typeof import('@noble/ciphers/aes.js') + >('@noble/ciphers/aes.js'); + + return { + ...actual, + aeskw: vi.fn(actual.aeskw), + }; +}); + + +describe('Test email crypto functions', () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + const email: EmailBody = { + text: 'Hi Bob, This is a test message. -Alice.', + }; + + const emailAndSubject: EmailBodyAndSubject = { + text: 'Hi Bob, This is a test message. -Alice.', + subject: 'test subject', + }; + + const sharedSecret = 'test shared secret'; + + it('throws EmailSymmetricEncryptionError when symmetric encryption fails', async () => { + + vi.spyOn(nobleUtils, 'randomBytes').mockImplementation(() => { + throw new Error('randomBytes: unexpected crypto failure'); + }); + + await expect(createPwdProtectedEmail(email, sharedSecret)).rejects.toThrow(EmailSymmetricEncryptionError); + await expect(createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret)).rejects.toThrow(EmailSymmetricEncryptionError); + }); + + it('throws EmailPasswordProtectError when key wrapping fails', async () => { + + vi.spyOn(nobleWrapper, 'aeskw').mockImplementation(() => { + throw new Error('aeskw: unexpected crypto failure'); + }); + + await expect(createPwdProtectedEmail(email, sharedSecret)).rejects.toThrow(EmailPasswordProtectError); + await expect(createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret)).rejects.toThrow(EmailPasswordProtectError); + }); + +}); From cc70210fcf091eab7642610ee1ebe6abda409693 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 28 May 2026 11:50:49 +0200 Subject: [PATCH 07/17] add missing style changes --- src/email-crypto/core.ts | 5 +- src/email-crypto/coreSubject.ts | 4 +- src/email-crypto/hybridEncyptedEmail.ts | 2 +- .../pwdProtectedEmailCoreErrors.test.ts | 59 ++++++++----------- .../pwdProtectedEmailNobleErrors.test.ts | 27 ++++----- 5 files changed, 42 insertions(+), 55 deletions(-) diff --git a/src/email-crypto/core.ts b/src/email-crypto/core.ts index 2c862ae..6f49a04 100644 --- a/src/email-crypto/core.ts +++ b/src/email-crypto/core.ts @@ -29,10 +29,9 @@ export async function encryptEmail( encryptionKey: Uint8Array; }> { if (!email.text) { - throw new InvalidInputEmail(); - } + throw new InvalidInputEmail(); + } try { - const encryptionKey = genSymmetricKey(); const encEmail = await encryptEmailWithKey(email, encryptionKey, aux); diff --git a/src/email-crypto/coreSubject.ts b/src/email-crypto/coreSubject.ts index 717f94e..c8d42b2 100644 --- a/src/email-crypto/coreSubject.ts +++ b/src/email-crypto/coreSubject.ts @@ -19,8 +19,8 @@ export async function encryptEmailAndSubject( encryptionKey: Uint8Array; }> { if (!email.text || !email.subject) { - throw new InvalidInputEmail(); - } + throw new InvalidInputEmail(); + } try { const encryptionKey = genSymmetricKey(); const encEmail = await encryptEmailAndSubjectWithKey(email, encryptionKey, aux); diff --git a/src/email-crypto/hybridEncyptedEmail.ts b/src/email-crypto/hybridEncyptedEmail.ts index b166ec7..f9f2968 100644 --- a/src/email-crypto/hybridEncyptedEmail.ts +++ b/src/email-crypto/hybridEncyptedEmail.ts @@ -15,7 +15,7 @@ import { * @param email - The email to encrypt. * @param recipientPublicKeys - The public keys of the recipient. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The encrypted email + * @returns The encrypted email */ export async function encryptEmailHybrid( email: EmailBody, diff --git a/tests/email-crypto/pwdProtectedEmailCoreErrors.test.ts b/tests/email-crypto/pwdProtectedEmailCoreErrors.test.ts index b19d638..a4889ab 100644 --- a/tests/email-crypto/pwdProtectedEmailCoreErrors.test.ts +++ b/tests/email-crypto/pwdProtectedEmailCoreErrors.test.ts @@ -1,13 +1,16 @@ import { vi, it, describe, beforeEach, expect } from 'vitest'; -import { createPwdProtectedEmail, createPwdProtectedEmailAndSubject, decryptPwdProtectedEmail, decryptPwdProtectedEmailAndSubject } from '../../src/email-crypto'; +import { + createPwdProtectedEmail, + createPwdProtectedEmailAndSubject, + decryptPwdProtectedEmail, + decryptPwdProtectedEmailAndSubject, +} from '../../src/email-crypto'; import { FailedToDecryptEmail, FailedToEncryptEmail } from '../../src/email-crypto/errors'; import { EmailBody, EmailBodyAndSubject, PwdProtectedEmail, PwdProtectedEmailAndSubject } from '../../src/types'; import * as core from '../../src/email-crypto/core'; vi.mock('../../src/email-crypto/core', async () => { - const actual = await vi.importActual( - '../../src/email-crypto/core' - ); + const actual = await vi.importActual('../../src/email-crypto/core'); return { ...actual, @@ -26,7 +29,6 @@ describe('Test email crypto functions', () => { attachments: ['file1.txt', 'file2.txt'], }; - const emailAndSubject: EmailBodyAndSubject = { text: 'Hi Bob, This is a test message. -Alice.', attachments: ['file1.txt', 'file2.txt'], @@ -35,39 +37,30 @@ describe('Test email crypto functions', () => { const sharedSecret = 'test shared secret'; -it('throws FailedToEncryptEmail when encryption fails', async () => { - const spy = vi.spyOn(core, 'passwordProtectKey'); + it('throws FailedToEncryptEmail when encryption fails', async () => { + const spy = vi.spyOn(core, 'passwordProtectKey'); - spy.mockRejectedValue( - new Error('passwordProtectKey: unexpected failure'), - ); + spy.mockRejectedValue(new Error('passwordProtectKey: unexpected failure')); - await expect( - createPwdProtectedEmail(email, sharedSecret), - ).rejects.toBeInstanceOf(FailedToEncryptEmail); + await expect(createPwdProtectedEmail(email, sharedSecret)).rejects.toBeInstanceOf(FailedToEncryptEmail); - await expect( - createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret), - ).rejects.toBeInstanceOf(FailedToEncryptEmail); -}); + await expect(createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret)).rejects.toBeInstanceOf( + FailedToEncryptEmail, + ); + }); -it('throws FailedToDecryptEmail when decryption fails', async () => { - const encryptedEmail = {} as PwdProtectedEmail; - const encryptedEmailAndSubject = {} as PwdProtectedEmailAndSubject; + it('throws FailedToDecryptEmail when decryption fails', async () => { + const encryptedEmail = {} as PwdProtectedEmail; + const encryptedEmailAndSubject = {} as PwdProtectedEmailAndSubject; - const spy = vi.spyOn(core, 'removePasswordProtection'); + const spy = vi.spyOn(core, 'removePasswordProtection'); - spy.mockRejectedValue( - new Error('removePasswordProtection: unexpected failure'), - ); - - await expect( - decryptPwdProtectedEmail(encryptedEmail, sharedSecret), - ).rejects.toBeInstanceOf(FailedToDecryptEmail); + spy.mockRejectedValue(new Error('removePasswordProtection: unexpected failure')); - await expect( - decryptPwdProtectedEmailAndSubject(encryptedEmailAndSubject, sharedSecret), - ).rejects.toBeInstanceOf(FailedToDecryptEmail); -}); + await expect(decryptPwdProtectedEmail(encryptedEmail, sharedSecret)).rejects.toBeInstanceOf(FailedToDecryptEmail); -}); \ No newline at end of file + await expect(decryptPwdProtectedEmailAndSubject(encryptedEmailAndSubject, sharedSecret)).rejects.toBeInstanceOf( + FailedToDecryptEmail, + ); + }); +}); diff --git a/tests/email-crypto/pwdProtectedEmailNobleErrors.test.ts b/tests/email-crypto/pwdProtectedEmailNobleErrors.test.ts index 725f02b..bd318e0 100644 --- a/tests/email-crypto/pwdProtectedEmailNobleErrors.test.ts +++ b/tests/email-crypto/pwdProtectedEmailNobleErrors.test.ts @@ -9,13 +9,11 @@ import { EmailBody, EmailBodyAndSubject } from '../../src/types'; import * as nobleUtils from '@noble/hashes/utils.js'; import * as nobleWrapper from '@noble/ciphers/aes.js'; -// Noble is ESM module and doesn't work with spyOn directly (module namespace is not configurable in ESM), must be mocked before. -// To mock it but keep the original implementation for most tests, we use importActual. +// Noble is ESM module and doesn't work with spyOn directly (module namespace is not configurable in ESM), must be mocked before. +// To mock it but keep the original implementation for most tests, we use importActual. // vi.resetAllMocks(); before each test is a must to reset the mock back to importActual. vi.mock('@noble/hashes/utils.js', async () => { - const actual = await vi.importActual< - typeof import('@noble/hashes/utils.js') - >('@noble/hashes/utils.js'); + const actual = await vi.importActual('@noble/hashes/utils.js'); return { ...actual, @@ -23,11 +21,8 @@ vi.mock('@noble/hashes/utils.js', async () => { }; }); - vi.mock('@noble/ciphers/aes.js', async () => { - const actual = await vi.importActual< - typeof import('@noble/ciphers/aes.js') - >('@noble/ciphers/aes.js'); + const actual = await vi.importActual('@noble/ciphers/aes.js'); return { ...actual, @@ -35,7 +30,6 @@ vi.mock('@noble/ciphers/aes.js', async () => { }; }); - describe('Test email crypto functions', () => { beforeEach(() => { vi.resetAllMocks(); @@ -52,24 +46,25 @@ describe('Test email crypto functions', () => { const sharedSecret = 'test shared secret'; - it('throws EmailSymmetricEncryptionError when symmetric encryption fails', async () => { - + it('throws EmailSymmetricEncryptionError when symmetric encryption fails', async () => { vi.spyOn(nobleUtils, 'randomBytes').mockImplementation(() => { throw new Error('randomBytes: unexpected crypto failure'); }); await expect(createPwdProtectedEmail(email, sharedSecret)).rejects.toThrow(EmailSymmetricEncryptionError); - await expect(createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret)).rejects.toThrow(EmailSymmetricEncryptionError); + await expect(createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret)).rejects.toThrow( + EmailSymmetricEncryptionError, + ); }); it('throws EmailPasswordProtectError when key wrapping fails', async () => { - vi.spyOn(nobleWrapper, 'aeskw').mockImplementation(() => { throw new Error('aeskw: unexpected crypto failure'); }); await expect(createPwdProtectedEmail(email, sharedSecret)).rejects.toThrow(EmailPasswordProtectError); - await expect(createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret)).rejects.toThrow(EmailPasswordProtectError); + await expect(createPwdProtectedEmailAndSubject(emailAndSubject, sharedSecret)).rejects.toThrow( + EmailPasswordProtectError, + ); }); - }); From b06b48c6e873f9184c8973628cc8804c4c8eea16 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 28 May 2026 12:05:34 +0200 Subject: [PATCH 08/17] remove body from types --- README.md | 6 ++--- src/email-crypto/core.ts | 18 +++++++-------- src/email-crypto/coreSubject.ts | 14 ++++++------ .../hybridEncryptedEmailAndSubject.ts | 8 +++---- src/email-crypto/hybridEncyptedEmail.ts | 8 +++---- src/email-crypto/pwdProtectedEmail.ts | 8 +++---- .../pwdProtectedEmailAndSubject.ts | 8 +++---- src/types.ts | 16 +++++++------- tests/email-crypto/core.test.ts | 22 +++++++++---------- tests/email-crypto/hybridEmail.test.ts | 22 +++++++++---------- tests/email-crypto/pwdProtectedEmail.test.ts | 10 ++++----- .../pwdProtectedEmailCoreErrors.test.ts | 6 ++--- .../pwdProtectedEmailNobleErrors.test.ts | 6 ++--- 13 files changed, 76 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 88825e1..3a43ab0 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ const password = 'your password'; const { key, salt } = await getKeyFromPassword(password); // Hybrid email encryption -const email: EmailBody = { +const email: Email = { text: 'email text', attachments: ['email attachements'], }; @@ -119,10 +119,10 @@ const bobWithPublicKeys = { const encryptedEmail = await encryptEmailHybrid(email, bobWithPublicKeys); const decryptedEmail = await decryptEmailHybrid(encryptedEmail, bobPrivateKeys); -expect(decryptedEmailBody).toStrictEqual(email); +expect(decryptedEmail).toStrictEqual(email); // Hybrid email and subject encryption -const emailAndSubject: EmailBodyAndSubject = { +const emailAndSubject: EmailAndSubject = { text: 'email text', subject: 'email subject' attachments: ['email attachements'], diff --git a/src/email-crypto/core.ts b/src/email-crypto/core.ts index 6f49a04..9f4c138 100644 --- a/src/email-crypto/core.ts +++ b/src/email-crypto/core.ts @@ -1,4 +1,4 @@ -import { HybridEncKey, PwdProtectedKey, EmailBody, RecipientWithPublicKey, EmailBodyEncrypted } from '../types'; +import { HybridEncKey, PwdProtectedKey, Email, RecipientWithPublicKey, EmailEncrypted } from '../types'; import { encryptSymmetrically, decryptSymmetrically, genSymmetricKey } from '../symmetric-crypto'; import { encapsulateHybrid, decapsulateHybrid } from '../hybrid-crypto'; import { wrapKey, unwrapKey } from '../key-wrapper'; @@ -22,10 +22,10 @@ import { * @returns The resulting encrypted email and symmetric key used for encryption */ export async function encryptEmail( - email: EmailBody, + email: Email, aux?: Uint8Array, ): Promise<{ - encEmail: EmailBodyEncrypted; + encEmail: EmailEncrypted; encryptionKey: Uint8Array; }> { if (!email.text) { @@ -50,16 +50,16 @@ export async function encryptEmail( * @returns The resulting encrypted email and symmetric key used for encryption */ export async function encryptEmailWithKey( - email: EmailBody, + email: Email, encryptionKey: Uint8Array, aux?: Uint8Array, -): Promise { +): Promise { try { const text = UTF8ToUint8(email.text); const encryptedText = await encryptSymmetrically(encryptionKey, text, aux); const encText = uint8ArrayToBase64(encryptedText); - const enc: EmailBodyEncrypted = { encText }; + const enc: EmailEncrypted = { encText }; if (email.attachments) { const promises = email.attachments.map((attachment) => { const binaryAttachment = UTF8ToUint8(attachment); @@ -83,15 +83,15 @@ export async function encryptEmailWithKey( * @returns The resulting decrypted email */ export async function decryptEmail( - encEmail: EmailBodyEncrypted, + encEmail: EmailEncrypted, encryptionKey: Uint8Array, aux?: Uint8Array, -): Promise { +): Promise { try { const encText = base64ToUint8Array(encEmail.encText); const textArray = await decryptSymmetrically(encryptionKey, encText, aux); const text = uint8ToUTF8(textArray); - const email: EmailBody = { text }; + const email: Email = { text }; if (encEmail.encAttachments) { const encAttachments = encEmail.encAttachments?.map(base64ToUint8Array); diff --git a/src/email-crypto/coreSubject.ts b/src/email-crypto/coreSubject.ts index c8d42b2..f0002eb 100644 --- a/src/email-crypto/coreSubject.ts +++ b/src/email-crypto/coreSubject.ts @@ -1,4 +1,4 @@ -import { EmailBodyAndSubject, EmailBodyAndSubjectEncrypted } from '../types'; +import { EmailAndSubject, EmailAndSubjectEncrypted } from '../types'; import { encryptSymmetrically, decryptSymmetrically, genSymmetricKey } from '../symmetric-crypto'; import { encryptEmailWithKey, decryptEmail } from './core'; import { UTF8ToUint8, base64ToUint8Array, uint8ArrayToBase64, uint8ToUTF8 } from '../utils'; @@ -12,10 +12,10 @@ import { InvalidInputEmail, EmailSymmetricDecryptionError, EmailSymmetricEncrypt * @returns The resulting encrypted email and symmetric key used for encryption */ export async function encryptEmailAndSubject( - email: EmailBodyAndSubject, + email: EmailAndSubject, aux?: Uint8Array, ): Promise<{ - encEmail: EmailBodyAndSubjectEncrypted; + encEmail: EmailAndSubjectEncrypted; encryptionKey: Uint8Array; }> { if (!email.text || !email.subject) { @@ -40,10 +40,10 @@ export async function encryptEmailAndSubject( * @returns The resulting encrypted email and symmetric key used for encryption */ export async function encryptEmailAndSubjectWithKey( - email: EmailBodyAndSubject, + email: EmailAndSubject, encryptionKey: Uint8Array, aux?: Uint8Array, -): Promise { +): Promise { try { const enc = await encryptEmailWithKey(email, encryptionKey, aux); const subject = UTF8ToUint8(email.subject); @@ -65,10 +65,10 @@ export async function encryptEmailAndSubjectWithKey( * @returns The resulting decrypted email and subject */ export async function decryptEmailAndSubject( - encEmail: EmailBodyAndSubjectEncrypted, + encEmail: EmailAndSubjectEncrypted, encryptionKey: Uint8Array, aux?: Uint8Array, -): Promise { +): Promise { try { const encSubject = base64ToUint8Array(encEmail.encSubject); const subjectArray = await decryptSymmetrically(encryptionKey, encSubject, aux); diff --git a/src/email-crypto/hybridEncryptedEmailAndSubject.ts b/src/email-crypto/hybridEncryptedEmailAndSubject.ts index 1a279e3..180a011 100644 --- a/src/email-crypto/hybridEncryptedEmailAndSubject.ts +++ b/src/email-crypto/hybridEncryptedEmailAndSubject.ts @@ -1,4 +1,4 @@ -import { RecipientWithPublicKey, EmailBodyAndSubject, HybridEncryptedEmailAndSubject } from '../types'; +import { RecipientWithPublicKey, EmailAndSubject, HybridEncryptedEmailAndSubject } from '../types'; import { encryptKeysHybrid, decryptKeysHybrid } from './core'; import { encryptEmailAndSubject, decryptEmailAndSubject } from './coreSubject'; import { @@ -20,7 +20,7 @@ import { * @returns The encrypted email and subject */ export async function encryptEmailAndSubjectHybrid( - email: EmailBodyAndSubject, + email: EmailAndSubject, recipient: RecipientWithPublicKey, aux?: Uint8Array, ): Promise { @@ -45,7 +45,7 @@ export async function encryptEmailAndSubjectHybrid( * @returns The set of encrypted emails and subjects */ export async function encryptEmailAndSubjectHybridForMultipleRecipients( - email: EmailBodyAndSubject, + email: EmailAndSubject, recipients: RecipientWithPublicKey[], aux?: Uint8Array, ): Promise { @@ -84,7 +84,7 @@ export async function decryptEmailAndSubjectHybrid( hybridEmail: HybridEncryptedEmailAndSubject, recipientPrivateHybridKeys: Uint8Array, aux?: Uint8Array, -): Promise { +): Promise { try { const encryptionKey = await decryptKeysHybrid(hybridEmail.encryptedKey, recipientPrivateHybridKeys); return await decryptEmailAndSubject(hybridEmail.encEmail, encryptionKey, aux); diff --git a/src/email-crypto/hybridEncyptedEmail.ts b/src/email-crypto/hybridEncyptedEmail.ts index f9f2968..75be566 100644 --- a/src/email-crypto/hybridEncyptedEmail.ts +++ b/src/email-crypto/hybridEncyptedEmail.ts @@ -1,4 +1,4 @@ -import { HybridEncryptedEmail, EmailBody, RecipientWithPublicKey } from '../types'; +import { HybridEncryptedEmail, Email, RecipientWithPublicKey } from '../types'; import { decryptEmail, encryptKeysHybrid, decryptKeysHybrid, encryptEmail } from './core'; import { FailedToDecryptEmail, @@ -18,7 +18,7 @@ import { * @returns The encrypted email */ export async function encryptEmailHybrid( - email: EmailBody, + email: Email, recipient: RecipientWithPublicKey, aux?: Uint8Array, ): Promise { @@ -43,7 +43,7 @@ export async function encryptEmailHybrid( * @returns The set of encrypted emails */ export async function encryptEmailHybridForMultipleRecipients( - email: EmailBody, + email: Email, recipients: RecipientWithPublicKey[], aux?: Uint8Array, ): Promise { @@ -82,7 +82,7 @@ export async function decryptEmailHybrid( encEmail: HybridEncryptedEmail, recipientPrivateHybridKeys: Uint8Array, aux?: Uint8Array, -): Promise { +): Promise { try { const encryptionKey = await decryptKeysHybrid(encEmail.encryptedKey, recipientPrivateHybridKeys); return await decryptEmail(encEmail.encEmail, encryptionKey, aux); diff --git a/src/email-crypto/pwdProtectedEmail.ts b/src/email-crypto/pwdProtectedEmail.ts index fba80e9..ef2d3df 100644 --- a/src/email-crypto/pwdProtectedEmail.ts +++ b/src/email-crypto/pwdProtectedEmail.ts @@ -1,4 +1,4 @@ -import { PwdProtectedEmail, EmailBody } from '../types'; +import { PwdProtectedEmail, Email } from '../types'; import { decryptEmail, passwordProtectKey, removePasswordProtection, encryptEmail } from './core'; import { EmailSymmetricEncryptionError, @@ -18,7 +18,7 @@ import { * @returns The password-protected email */ export async function createPwdProtectedEmail( - email: EmailBody, + email: Email, password: string, aux?: Uint8Array, ): Promise { @@ -41,13 +41,13 @@ export async function createPwdProtectedEmail( * @param encryptedEmail - The encrypted email * @param password - The secret password shared among recipients. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The decrypted email body + * @returns The decrypted email */ export async function decryptPwdProtectedEmail( encryptedEmail: PwdProtectedEmail, password: string, aux?: Uint8Array, -): Promise { +): Promise { try { const encryptionKey = await removePasswordProtection(encryptedEmail.encryptedKey, password); return await decryptEmail(encryptedEmail.encEmail, encryptionKey, aux); diff --git a/src/email-crypto/pwdProtectedEmailAndSubject.ts b/src/email-crypto/pwdProtectedEmailAndSubject.ts index b80ecc0..2bdf596 100644 --- a/src/email-crypto/pwdProtectedEmailAndSubject.ts +++ b/src/email-crypto/pwdProtectedEmailAndSubject.ts @@ -1,4 +1,4 @@ -import { EmailBodyAndSubject, PwdProtectedEmailAndSubject } from '../types'; +import { EmailAndSubject, PwdProtectedEmailAndSubject } from '../types'; import { passwordProtectKey, removePasswordProtection } from './core'; import { encryptEmailAndSubject, decryptEmailAndSubject } from './coreSubject'; import { @@ -20,7 +20,7 @@ import { * @returns The password-protected email */ export async function createPwdProtectedEmailAndSubject( - email: EmailBodyAndSubject, + email: EmailAndSubject, password: string, aux?: Uint8Array, ): Promise { @@ -43,13 +43,13 @@ export async function createPwdProtectedEmailAndSubject( * @param encryptedEmail - The encrypted email and subject * @param password - The secret password shared among recipients. * @param aux - An optional auxilary sting for AEAD (e.g., email ID or timestamp). - * @returns The decrypted email body + * @returns The decrypted email and subject */ export async function decryptPwdProtectedEmailAndSubject( encryptedEmail: PwdProtectedEmailAndSubject, password: string, aux?: Uint8Array, -): Promise { +): Promise { try { const encryptionKey = await removePasswordProtection(encryptedEmail.encryptedKey, password); return await decryptEmailAndSubject(encryptedEmail.encEmail, encryptionKey, aux); diff --git a/src/types.ts b/src/types.ts index 4e3b786..47e7843 100644 --- a/src/types.ts +++ b/src/types.ts @@ -17,22 +17,22 @@ export type HybridKeyPair = { export type HybridEncryptedEmail = { encryptedKey: HybridEncKey; - encEmail: EmailBodyEncrypted; + encEmail: EmailEncrypted; }; export type HybridEncryptedEmailAndSubject = { encryptedKey: HybridEncKey; - encEmail: EmailBodyAndSubjectEncrypted; + encEmail: EmailAndSubjectEncrypted; }; export type PwdProtectedEmail = { encryptedKey: PwdProtectedKey; - encEmail: EmailBodyEncrypted; + encEmail: EmailEncrypted; }; export type PwdProtectedEmailAndSubject = { encryptedKey: PwdProtectedKey; - encEmail: EmailBodyAndSubjectEncrypted; + encEmail: EmailAndSubjectEncrypted; }; export type HybridEncKey = { @@ -46,21 +46,21 @@ export type PwdProtectedKey = { salt: string; }; -export type EmailBodyEncrypted = { +export type EmailEncrypted = { encText: string; encAttachments?: string[]; }; -export type EmailBodyAndSubjectEncrypted = EmailBodyEncrypted & { +export type EmailAndSubjectEncrypted = EmailEncrypted & { encSubject: string; }; -export type EmailBody = { +export type Email = { text: string; attachments?: string[]; }; -export type EmailBodyAndSubject = EmailBody & { +export type EmailAndSubject = Email & { subject: string; }; diff --git a/tests/email-crypto/core.test.ts b/tests/email-crypto/core.test.ts index 1d6f0e0..015b34e 100644 --- a/tests/email-crypto/core.test.ts +++ b/tests/email-crypto/core.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { EmailBody, EmailBodyAndSubject } from '../../src/types'; +import { Email, EmailAndSubject } from '../../src/types'; import { decryptEmail, encryptEmail, @@ -14,31 +14,31 @@ import { AES_KEY_BYTE_LENGTH } from '../../src/constants'; import { EmailSymmetricDecryptionError, InvalidInputEmail } from '../../src/email-crypto/errors'; describe('Test email crypto functions', () => { - const emailBody: EmailBody = { + const email: Email = { text: 'test email', }; - const emailBodyAndSubject: EmailBodyAndSubject = { - text: 'test body', - subject: 'test subject', + const emailAndSubject: EmailAndSubject = { + text: 'test email text', + subject: 'test email subject', }; const aux = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); it('should encrypt and decrypt email', async () => { - const { encEmail, encryptionKey } = await encryptEmail(emailBody, aux); + const { encEmail, encryptionKey } = await encryptEmail(email, aux); const result = await decryptEmail(encEmail, encryptionKey, aux); - expect(result).toEqual(emailBody); + expect(result).toEqual(email); }); it('should encrypt and decrypt email with subject', async () => { - const { encEmail, encryptionKey } = await encryptEmailAndSubject(emailBodyAndSubject, aux); + const { encEmail, encryptionKey } = await encryptEmailAndSubject(emailAndSubject, aux); const result = await decryptEmailAndSubject(encEmail, encryptionKey, aux); - expect(result).toEqual(emailBodyAndSubject); + expect(result).toEqual(emailAndSubject); }); it('should throw an error if decryption fails', async () => { - const { encEmail, encryptionKey } = await encryptEmail(emailBody, aux); + const { encEmail, encryptionKey } = await encryptEmail(email, aux); const badEncryptionKey = await genSymmetricKey(); await expect(decryptEmail(encEmail, badEncryptionKey, aux)).rejects.toThrow(EmailSymmetricDecryptionError); @@ -47,7 +47,7 @@ describe('Test email crypto functions', () => { }); it('should throw an error if decryption fails', async () => { - const { encEmail, encryptionKey } = await encryptEmailAndSubject(emailBodyAndSubject, aux); + const { encEmail, encryptionKey } = await encryptEmailAndSubject(emailAndSubject, aux); const badEncryptionKey = await genSymmetricKey(); await expect(decryptEmailAndSubject(encEmail, badEncryptionKey, aux)).rejects.toThrow( EmailSymmetricDecryptionError, diff --git a/tests/email-crypto/hybridEmail.test.ts b/tests/email-crypto/hybridEmail.test.ts index f9d0e09..d76ccbf 100644 --- a/tests/email-crypto/hybridEmail.test.ts +++ b/tests/email-crypto/hybridEmail.test.ts @@ -10,10 +10,10 @@ import { } from '../../src/email-crypto'; import { - EmailBody, + Email, HybridEncryptedEmail, HybridEncKey, - EmailBodyAndSubject, + EmailAndSubject, HybridEncryptedEmailAndSubject, RecipientWithPublicKey, } from '../../src/types'; @@ -25,13 +25,13 @@ import { } from '../../src/email-crypto/errors'; describe('Test email crypto functions', async () => { - const email: EmailBody = { - text: 'test body', + const email: Email = { + text: 'test email text', }; - const emailAndSubject: EmailBodyAndSubject = { - text: 'test body', - subject: 'test subject', + const emailAndSubject: EmailAndSubject = { + text: 'test email text', + subject: 'test email subject', }; const { secretKey: alicePrivateKeys, publicKey: alicePublicKeys } = await generateEmailKeys(); @@ -170,18 +170,18 @@ describe('Test email crypto functions', async () => { }); it('should throw an error if input is invalid', async () => { - await expect(encryptEmailHybrid({} as EmailBody, bobWithPublicKeys)).rejects.toThrow(InvalidInputEmail); + await expect(encryptEmailHybrid({} as Email, bobWithPublicKeys)).rejects.toThrow(InvalidInputEmail); - await expect(encryptEmailAndSubjectHybrid({} as EmailBodyAndSubject, bobWithPublicKeys)).rejects.toThrow( + await expect(encryptEmailAndSubjectHybrid({} as EmailAndSubject, bobWithPublicKeys)).rejects.toThrow( InvalidInputEmail, ); await expect( - encryptEmailHybridForMultipleRecipients({} as EmailBody, [bobWithPublicKeys, aliceWithPublicKeys]), + encryptEmailHybridForMultipleRecipients({} as Email, [bobWithPublicKeys, aliceWithPublicKeys]), ).rejects.toThrow(InvalidInputEmail); await expect( - encryptEmailAndSubjectHybridForMultipleRecipients({} as EmailBodyAndSubject, [ + encryptEmailAndSubjectHybridForMultipleRecipients({} as EmailAndSubject, [ bobWithPublicKeys, aliceWithPublicKeys, ]), diff --git a/tests/email-crypto/pwdProtectedEmail.test.ts b/tests/email-crypto/pwdProtectedEmail.test.ts index 6406f92..dadb3c4 100644 --- a/tests/email-crypto/pwdProtectedEmail.test.ts +++ b/tests/email-crypto/pwdProtectedEmail.test.ts @@ -8,14 +8,14 @@ import { EmailSymmetricDecryptionError, InvalidInputEmail, } from '../../src/email-crypto'; -import { EmailBody, EmailBodyAndSubject } from '../../src/types'; +import { Email, EmailAndSubject } from '../../src/types'; describe('Test email crypto functions', () => { - const email: EmailBody = { + const email: Email = { text: 'Hi Bob, This is a test message. -Alice.', }; - const emailAndSubject: EmailBodyAndSubject = { + const emailAndSubject: EmailAndSubject = { text: 'Hi Bob, This is a test message. -Alice.', subject: 'test subject', }; @@ -35,8 +35,8 @@ describe('Test email crypto functions', () => { }); it('should throw an error if encryption fails', async () => { - await expect(createPwdProtectedEmail({} as unknown as EmailBody, sharedSecret)).rejects.toThrow(InvalidInputEmail); - await expect(createPwdProtectedEmailAndSubject({} as unknown as EmailBodyAndSubject, sharedSecret)).rejects.toThrow( + await expect(createPwdProtectedEmail({} as unknown as Email, sharedSecret)).rejects.toThrow(InvalidInputEmail); + await expect(createPwdProtectedEmailAndSubject({} as unknown as EmailAndSubject, sharedSecret)).rejects.toThrow( InvalidInputEmail, ); }); diff --git a/tests/email-crypto/pwdProtectedEmailCoreErrors.test.ts b/tests/email-crypto/pwdProtectedEmailCoreErrors.test.ts index a4889ab..792eb7e 100644 --- a/tests/email-crypto/pwdProtectedEmailCoreErrors.test.ts +++ b/tests/email-crypto/pwdProtectedEmailCoreErrors.test.ts @@ -6,7 +6,7 @@ import { decryptPwdProtectedEmailAndSubject, } from '../../src/email-crypto'; import { FailedToDecryptEmail, FailedToEncryptEmail } from '../../src/email-crypto/errors'; -import { EmailBody, EmailBodyAndSubject, PwdProtectedEmail, PwdProtectedEmailAndSubject } from '../../src/types'; +import { Email, EmailAndSubject, PwdProtectedEmail, PwdProtectedEmailAndSubject } from '../../src/types'; import * as core from '../../src/email-crypto/core'; vi.mock('../../src/email-crypto/core', async () => { @@ -24,12 +24,12 @@ describe('Test email crypto functions', () => { vi.resetAllMocks(); }); - const email: EmailBody = { + const email: Email = { text: 'Hi Bob, This is a test message. -Alice.', attachments: ['file1.txt', 'file2.txt'], }; - const emailAndSubject: EmailBodyAndSubject = { + const emailAndSubject: EmailAndSubject = { text: 'Hi Bob, This is a test message. -Alice.', attachments: ['file1.txt', 'file2.txt'], subject: 'test subject', diff --git a/tests/email-crypto/pwdProtectedEmailNobleErrors.test.ts b/tests/email-crypto/pwdProtectedEmailNobleErrors.test.ts index bd318e0..b9d176f 100644 --- a/tests/email-crypto/pwdProtectedEmailNobleErrors.test.ts +++ b/tests/email-crypto/pwdProtectedEmailNobleErrors.test.ts @@ -5,7 +5,7 @@ import { EmailPasswordProtectError, EmailSymmetricEncryptionError, } from '../../src/email-crypto'; -import { EmailBody, EmailBodyAndSubject } from '../../src/types'; +import { Email, EmailAndSubject } from '../../src/types'; import * as nobleUtils from '@noble/hashes/utils.js'; import * as nobleWrapper from '@noble/ciphers/aes.js'; @@ -35,11 +35,11 @@ describe('Test email crypto functions', () => { vi.resetAllMocks(); }); - const email: EmailBody = { + const email: Email = { text: 'Hi Bob, This is a test message. -Alice.', }; - const emailAndSubject: EmailBodyAndSubject = { + const emailAndSubject: EmailAndSubject = { text: 'Hi Bob, This is a test message. -Alice.', subject: 'test subject', }; From a68e007e8def322e654f046846c6194c997fdda3 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 28 May 2026 12:41:42 +0200 Subject: [PATCH 09/17] remove github token --- .github/workflows/sonar-analysis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/sonar-analysis.yml b/.github/workflows/sonar-analysis.yml index 2334c95..77236ce 100644 --- a/.github/workflows/sonar-analysis.yml +++ b/.github/workflows/sonar-analysis.yml @@ -38,5 +38,4 @@ jobs: - name: Analyze with SonarCloud uses: SonarSource/sonarqube-scan-action@v5.0.0 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Generate a token on Sonarcloud.io, add it to the secrets of this repo with the name SONAR_TOKEN (Settings > Secrets > Actions > add new repository secret) From eb7e907982d8314c1ac8d47f93d5827c4e373045 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 28 May 2026 14:32:48 +0200 Subject: [PATCH 10/17] remove playwright as it's in dependencies already --- .github/workflows/ci.yml | 3 --- .github/workflows/sonar-analysis.yml | 3 --- 2 files changed, 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79a8954..3a1f840 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,9 +29,6 @@ jobs: - name: Install dependencies run: yarn - - name: Install Playwright Browsers - run: yarn playwright install - - name: Build run: yarn run build diff --git a/.github/workflows/sonar-analysis.yml b/.github/workflows/sonar-analysis.yml index 77236ce..c5aedb3 100644 --- a/.github/workflows/sonar-analysis.yml +++ b/.github/workflows/sonar-analysis.yml @@ -31,9 +31,6 @@ jobs: - name: Install run: yarn install - - name: Check and Install Playwright - run: yarn playwright install - # Analyze with SonarCloud - name: Analyze with SonarCloud uses: SonarSource/sonarqube-scan-action@v5.0.0 From 56c5a43263c69d96dec74ad708ef4c2a5a989737 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 28 May 2026 14:37:41 +0200 Subject: [PATCH 11/17] install only chromium --- .github/workflows/ci.yml | 3 +++ .github/workflows/sonar-analysis.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a1f840..01f0565 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,9 @@ jobs: - name: Install dependencies run: yarn + - name: Install Playwright Browsers + run: yarn playwright install --with-deps chromium + - name: Build run: yarn run build diff --git a/.github/workflows/sonar-analysis.yml b/.github/workflows/sonar-analysis.yml index c5aedb3..a21bbcf 100644 --- a/.github/workflows/sonar-analysis.yml +++ b/.github/workflows/sonar-analysis.yml @@ -30,6 +30,9 @@ jobs: - name: Install run: yarn install + + - name: Check and Install Playwright + run: yarn playwright install --with-deps chromium # Analyze with SonarCloud - name: Analyze with SonarCloud From 519f36eee195a4b2ce18fc58975aef82dc54e9a2 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 28 May 2026 14:43:35 +0200 Subject: [PATCH 12/17] remove with dep --- .github/workflows/ci.yml | 2 +- .github/workflows/sonar-analysis.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01f0565..c338a48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: run: yarn - name: Install Playwright Browsers - run: yarn playwright install --with-deps chromium + run: yarn playwright install chromium - name: Build run: yarn run build diff --git a/.github/workflows/sonar-analysis.yml b/.github/workflows/sonar-analysis.yml index a21bbcf..a56be3e 100644 --- a/.github/workflows/sonar-analysis.yml +++ b/.github/workflows/sonar-analysis.yml @@ -30,9 +30,9 @@ jobs: - name: Install run: yarn install - + - name: Check and Install Playwright - run: yarn playwright install --with-deps chromium + run: yarn playwright install chromium # Analyze with SonarCloud - name: Analyze with SonarCloud From 36e8b7f3a6df48dc4d42811a6134706d9130c889 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 28 May 2026 14:56:43 +0200 Subject: [PATCH 13/17] remove node version --- .github/workflows/sonar-analysis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/sonar-analysis.yml b/.github/workflows/sonar-analysis.yml index a56be3e..4ed8c4e 100644 --- a/.github/workflows/sonar-analysis.yml +++ b/.github/workflows/sonar-analysis.yml @@ -25,7 +25,6 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node_version }} cache: yarn - name: Install From 6525784b4fdf4f068a2872c33a436e1311e7b665 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 28 May 2026 15:01:06 +0200 Subject: [PATCH 14/17] remove yarn cache --- .github/workflows/ci.yml | 3 +-- .github/workflows/sonar-analysis.yml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c338a48..2ccab59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,6 @@ jobs: uses: actions/setup-node@v4 with: node-version: '24.x' - cache: yarn - name: Add .npmrc run: cp .npmrc.template .npmrc @@ -30,7 +29,7 @@ jobs: run: yarn - name: Install Playwright Browsers - run: yarn playwright install chromium + run: yarn playwright install - name: Build run: yarn run build diff --git a/.github/workflows/sonar-analysis.yml b/.github/workflows/sonar-analysis.yml index 4ed8c4e..827a67c 100644 --- a/.github/workflows/sonar-analysis.yml +++ b/.github/workflows/sonar-analysis.yml @@ -25,7 +25,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - cache: yarn + node-version: ${{ matrix.node_version }} - name: Install run: yarn install From 063c51ec370da50cc6d0aa17003becd931fec0a2 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 28 May 2026 15:04:38 +0200 Subject: [PATCH 15/17] back to 20 --- .github/workflows/ci.yml | 3 ++- .github/workflows/publish.yml | 2 +- .github/workflows/sonar-analysis.yml | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ccab59..067d1c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,8 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: '24.x' + node-version: '20.x' + cache: yarn - name: Add .npmrc run: cp .npmrc.template .npmrc diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0284f6e..1a86ec3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,7 +19,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: '24.x' + node-version: '20.x' cache: yarn - name: Install dependencies diff --git a/.github/workflows/sonar-analysis.yml b/.github/workflows/sonar-analysis.yml index 827a67c..16a8dd3 100644 --- a/.github/workflows/sonar-analysis.yml +++ b/.github/workflows/sonar-analysis.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node_version: [24.x] + node_version: [20.x] steps: - name: Checkout @@ -26,6 +26,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node_version }} + cache: yarn - name: Install run: yarn install From ba19687423c5acdccd6c3659219dbeef9a5530ef Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 28 May 2026 15:10:49 +0200 Subject: [PATCH 16/17] try 22 --- .github/workflows/ci.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/sonar-analysis.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 067d1c5..1a17117 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: '22.x' cache: yarn - name: Add .npmrc diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1a86ec3..83f5d94 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,7 +19,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: '22.x' cache: yarn - name: Install dependencies diff --git a/.github/workflows/sonar-analysis.yml b/.github/workflows/sonar-analysis.yml index 16a8dd3..b9e842a 100644 --- a/.github/workflows/sonar-analysis.yml +++ b/.github/workflows/sonar-analysis.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node_version: [20.x] + node_version: [22.x] steps: - name: Checkout From 1b3aa037577ebee9cd0627b6c5641b00f462e404 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 28 May 2026 15:18:51 +0200 Subject: [PATCH 17/17] remove shallow clones --- .github/workflows/sonar-analysis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sonar-analysis.yml b/.github/workflows/sonar-analysis.yml index b9e842a..6e0dc40 100644 --- a/.github/workflows/sonar-analysis.yml +++ b/.github/workflows/sonar-analysis.yml @@ -21,6 +21,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Setup Node uses: actions/setup-node@v4 @@ -32,7 +34,7 @@ jobs: run: yarn install - name: Check and Install Playwright - run: yarn playwright install chromium + run: yarn playwright install # Analyze with SonarCloud - name: Analyze with SonarCloud