Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 2 additions & 33 deletions src/email-crypto/converters.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { UTF8ToUint8, uint8ToUTF8 } from '../utils';
import { EmailBody, User, Email } from '../types';
import { UTF8ToUint8 } from '../utils';
import { User, Email } from '../types';
import { concatBytes } from '@noble/hashes/utils.js';

export function userToBytes(user: User): Uint8Array {
Expand All @@ -20,37 +20,6 @@ export function recipientsToBytes(recipients: User[]): Uint8Array {
}
}

/**
* Converts an EmailBody type into a Uint8Array array.
*
* @param body - The email body.
* @returns The Uint8Array array representation of the EmailBody type.
*/
export function emailBodyToBinary(body: EmailBody): Uint8Array {
try {
const json = JSON.stringify(body);
return UTF8ToUint8(json);
} catch (error) {
throw new Error('Failed to convert EmailBody to Uint8Array', { cause: error });
}
}

/**
* Converts an Uint8Array array into EmailBody type.
*
* @param array - The Uint8Array array.
* @returns The EmailBody type representation of the Uint8Array.
*/
export function binaryToEmailBody(array: Uint8Array): EmailBody {
try {
const json = uint8ToUTF8(array);
const email: EmailBody = JSON.parse(json);
return email;
} catch (error) {
throw new Error('Failed to convert Uint8Array to EmailBody', { cause: error });
}
}

/**
* Converts an Email type into a Uint8Array array.
*
Expand Down
97 changes: 78 additions & 19 deletions src/email-crypto/core.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { HybridEncKey, PwdProtectedKey, PublicKeys, PrivateKeys, EmailBody } from '../types';
import { HybridEncKey, PwdProtectedKey, PublicKeys, PrivateKeys, EmailBody, EmailBodyEncrypted } from '../types';
import { genSymmetricCryptoKey, encryptSymmetrically, decryptSymmetrically } from '../symmetric-crypto';
import { emailBodyToBinary, binaryToEmailBody } from './converters';
import { encapsulateKyber, decapsulateKyber } from '../post-quantum-crypto';
import { deriveWrappingKey, wrapKey, unwrapKey, importWrappingKey } from '../key-wrapper';
import { deriveSecretKey } from '../asymmetric-crypto';
Expand All @@ -19,8 +18,11 @@ export async function encryptEmailContentSymmetrically(
email: EmailBody,
aux: Uint8Array,
emailID: string,
): Promise<{ enc: Uint8Array; encryptionKey: CryptoKey }> {
): Promise<{ enc: EmailBodyEncrypted; encryptionKey: CryptoKey }> {
try {
if (!email.text) {
throw new Error('Invalid input');
}
const encryptionKey = await genSymmetricCryptoKey();
const enc = await encryptEmailContentSymmetricallyWithKey(email, encryptionKey, aux, emailID);
return { enc, encryptionKey };
Expand All @@ -43,13 +45,17 @@ export async function encryptEmailContentAndSubjectSymmetrically(
subject: string,
aux: Uint8Array,
emailID: string,
): Promise<{ enc: Uint8Array; subjectEnc: Uint8Array; encryptionKey: CryptoKey }> {
): Promise<{ enc: EmailBodyEncrypted; encSubject: string; encryptionKey: CryptoKey }> {
try {
if (!subject || !email.text) {
throw new Error('Invalid input');
}
const encryptionKey = await genSymmetricCryptoKey();
const enc = await encryptEmailContentSymmetricallyWithKey(email, encryptionKey, aux, emailID);
const subjectBuff = UTF8ToUint8(subject);
const subjectEnc = await encryptSymmetrically(encryptionKey, subjectBuff, aux);
return { enc, encryptionKey, subjectEnc };
const encSubject = uint8ArrayToBase64(subjectEnc);
return { enc, encSubject, encryptionKey };
} catch (error) {
throw new Error('Failed to symmetrically encrypt email and subject', { cause: error });
}
Expand All @@ -63,16 +69,17 @@ export async function encryptEmailContentAndSubjectSymmetrically(
* @returns The decrypted email
*/
export async function decryptEmailAndSubjectSymmetrically(
emailCiphertext: Uint8Array,
encSubject: Uint8Array,
encryptionKey: CryptoKey,
aux: Uint8Array,
encSubject: string,
enc: EmailBodyEncrypted,
): Promise<{ body: EmailBody; subject: string }> {
try {
const binaryEmail = await decryptSymmetrically(encryptionKey, emailCiphertext, aux);
const subject = await decryptSymmetrically(encryptionKey, encSubject, aux);
const body = binaryToEmailBody(binaryEmail);
return { body, subject: uint8ToUTF8(subject) };
const array = base64ToUint8Array(encSubject);
const subjectArray = await decryptSymmetrically(encryptionKey, array, aux);
const body = await decryptEmailSymmetrically(encryptionKey, aux, enc);
const subject = uint8ToUTF8(subjectArray);
return { body, subject };
} catch (error) {
throw new Error('Failed to symmetrically decrypt email and subject', { cause: error });
}
Expand All @@ -89,17 +96,61 @@ export async function encryptEmailContentSymmetricallyWithKey(
encryptionKey: CryptoKey,
aux: Uint8Array,
emailID: string,
): Promise<Uint8Array> {
): Promise<EmailBodyEncrypted> {
try {
const freeField = uuidToBytes(emailID);
const binaryEmail = emailBodyToBinary(emailBody);
const ciphertext = await encryptSymmetrically(encryptionKey, binaryEmail, aux, freeField);
return ciphertext;
const text = UTF8ToUint8(emailBody.text);
const encryptedText = await encryptSymmetrically(encryptionKey, text, aux, freeField);
const encText = uint8ArrayToBase64(encryptedText);
const result: EmailBodyEncrypted = { encText };

if (emailBody.attachments) {
const encryptedAttachements = await encryptEmailAttachements(emailBody.attachments, encryptionKey, aux, emailID);
result.encAttachments = encryptedAttachements?.map(uint8ArrayToBase64);
}
return result;
} catch (error) {
throw new Error('Failed to symmetrically encrypt email with the given key', { cause: error });
}
}

async function encryptEmailAttachements(
attachments: string[],
encryptionKey: CryptoKey,
aux: Uint8Array,
emailID: string,
): Promise<Uint8Array[]> {
try {
const freeField = uuidToBytes(emailID);
const encryptedAttachments = await Promise.all(
attachments.map((attachment) => {
const binaryAttachment = UTF8ToUint8(attachment);
return encryptSymmetrically(encryptionKey, binaryAttachment, aux, freeField);
}),
);
return encryptedAttachments;
} catch (error) {
throw new Error('Failed to symmetrically encrypt email attachements', { cause: error });
}
}

async function decryptEmailAttachements(
encryptedAttachments: Uint8Array[],
encryptionKey: CryptoKey,
aux: Uint8Array,
): Promise<Uint8Array[]> {
try {
const decryptedAttachments = await Promise.all(
encryptedAttachments.map((attachment) => {
return decryptSymmetrically(encryptionKey, attachment, aux);
}),
);
return decryptedAttachments;
} catch (error) {
throw new Error('Failed to symmetrically decrypt email attachements', { cause: error });
}
}

/**
* Decrypts symmetrically encrypted email.
*
Expand All @@ -108,14 +159,22 @@ export async function encryptEmailContentSymmetricallyWithKey(
* @returns The decrypted email
*/
export async function decryptEmailSymmetrically(
emailCiphertext: Uint8Array,
encryptionKey: CryptoKey,
aux: Uint8Array,
enc: EmailBodyEncrypted,
): Promise<EmailBody> {
try {
const binaryEmail = await decryptSymmetrically(encryptionKey, emailCiphertext, aux);
const body = binaryToEmailBody(binaryEmail);
return body;
const cipher = base64ToUint8Array(enc.encText);
const textArray = await decryptSymmetrically(encryptionKey, cipher, aux);
const text = uint8ToUTF8(textArray);
const result: EmailBody = { text };

if (enc.encAttachments) {
const encAttachements = enc.encAttachments?.map(base64ToUint8Array);
const attachmentsArray = await decryptEmailAttachements(encAttachements, encryptionKey, aux);
result.attachments = attachmentsArray?.map((att) => uint8ToUTF8(att));
}
return result;
} catch (error) {
throw new Error('Failed to symmetrically decrypt email', { cause: error });
}
Expand Down
10 changes: 3 additions & 7 deletions src/email-crypto/hybridEncyptedEmail.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { base64ToUint8Array, uint8ArrayToBase64 } from '../utils';
import { PublicKeys, PrivateKeys, HybridEncryptedEmail, Email, UserWithPublicKeys } from '../types';
import {
encryptEmailContentSymmetrically,
Expand All @@ -25,8 +24,7 @@ export async function encryptEmailHybrid(
const aux = getAux(email.params);
const { enc, encryptionKey } = await encryptEmailContentSymmetrically(email.body, aux, email.id);
const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient.publicKeys, senderPrivateKey);
const encryptedText = uint8ArrayToBase64(enc);
return { enc: encryptedText, encryptedKey, recipientEmail: recipient.email, params: email.params, id: email.id };
return { enc, encryptedKey, recipientEmail: recipient.email, params: email.params, id: email.id };
} catch (error) {
throw new Error('Failed to encrypt email with hybrid encryption', { cause: error });
}
Expand All @@ -48,13 +46,12 @@ export async function encryptEmailHybridForMultipleRecipients(
try {
const aux = getAux(email.params);
const { enc, encryptionKey } = await encryptEmailContentSymmetrically(email.body, aux, email.id);
const encryptedText = uint8ArrayToBase64(enc);

const encryptedEmails: HybridEncryptedEmail[] = [];
for (const recipient of recipients) {
const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient.publicKeys, senderPrivateKey);
encryptedEmails.push({
enc: encryptedText,
enc,
encryptedKey,
recipientEmail: recipient.email,
params: email.params,
Expand Down Expand Up @@ -83,8 +80,7 @@ export async function decryptEmailHybrid(
try {
const aux = getAux(encryptedEmail.params);
const encryptionKey = await decryptKeysHybrid(encryptedEmail.encryptedKey, senderPublicKeys, recipientPrivateKeys);
const enc = base64ToUint8Array(encryptedEmail.enc);
const body = await decryptEmailSymmetrically(enc, encryptionKey, aux);
const body = await decryptEmailSymmetrically(encryptionKey, aux, encryptedEmail.enc);
return { body, params: encryptedEmail.params, id: encryptedEmail.id };
} catch (error) {
throw new Error('Failed to decrypt email with hybrid encryption', { cause: error });
Expand Down
26 changes: 12 additions & 14 deletions src/email-crypto/hybridEncyptedEmailAndSubject.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { base64ToUint8Array, uint8ArrayToBase64 } from '../utils';
import { PublicKeys, PrivateKeys, HybridEncryptedEmail, Email, UserWithPublicKeys } from '../types';
import {
encryptEmailContentAndSubjectSymmetrically,
Expand All @@ -23,17 +22,15 @@ export async function encryptEmailAndSubjectHybrid(
): Promise<HybridEncryptedEmail> {
try {
const aux = getAuxWithoutSubject(email.params);
const { enc, encryptionKey, subjectEnc } = await encryptEmailContentAndSubjectSymmetrically(
const { enc, encSubject, encryptionKey } = await encryptEmailContentAndSubjectSymmetrically(
email.body,
email.params.subject,
aux,
email.id,
);
const encryptedText = uint8ArrayToBase64(enc);
const encSubjectStr = uint8ArrayToBase64(subjectEnc);
const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient.publicKeys, senderPrivateKey);
const params = { ...email.params, subject: encSubjectStr };
return { enc: encryptedText, encryptedKey, recipientEmail: recipient.email, params, id: email.id };
const params = { ...email.params, subject: encSubject };
return { enc, encryptedKey, recipientEmail: recipient.email, params, id: email.id };
} catch (error) {
throw new Error('Failed to encrypt the email and its subject with hybrid encryption', { cause: error });
}
Expand All @@ -54,20 +51,18 @@ export async function encryptEmailAndSubjectHybridForMultipleRecipients(
): Promise<HybridEncryptedEmail[]> {
try {
const aux = getAuxWithoutSubject(email.params);
const { enc, encryptionKey, subjectEnc } = await encryptEmailContentAndSubjectSymmetrically(
const { enc, encSubject, encryptionKey } = await encryptEmailContentAndSubjectSymmetrically(
email.body,
email.params.subject,
aux,
email.id,
);
const encSubjectStr = uint8ArrayToBase64(subjectEnc);
const encryptedText = uint8ArrayToBase64(enc);

const encryptedEmails: HybridEncryptedEmail[] = [];
for (const recipient of recipients) {
const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient.publicKeys, senderPrivateKey);
const params = { ...email.params, subject: encSubjectStr };
encryptedEmails.push({ enc: encryptedText, encryptedKey, recipientEmail: recipient.email, params, id: email.id });
const params = { ...email.params, subject: encSubject };
encryptedEmails.push({ enc, encryptedKey, recipientEmail: recipient.email, params, id: email.id });
}
return encryptedEmails;
} catch (error) {
Expand All @@ -93,9 +88,12 @@ export async function decryptEmailAndSubjectHybrid(
try {
const aux = getAuxWithoutSubject(encryptedEmail.params);
const encryptionKey = await decryptKeysHybrid(encryptedEmail.encryptedKey, senderPublicKeys, recipientPrivateKeys);
const encSubject = base64ToUint8Array(encryptedEmail.params.subject);
const enc = base64ToUint8Array(encryptedEmail.enc);
const { body, subject } = await decryptEmailAndSubjectSymmetrically(enc, encSubject, encryptionKey, aux);
const { body, subject } = await decryptEmailAndSubjectSymmetrically(
encryptionKey,
aux,
encryptedEmail.params.subject,
encryptedEmail.enc,
);
const params = { ...encryptedEmail.params, subject };
return { body, params, id: encryptedEmail.id };
} catch (error) {
Expand Down
7 changes: 2 additions & 5 deletions src/email-crypto/pwdProtectedEmail.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { PwdProtectedEmail, Email } from '../types';
import { base64ToUint8Array, uint8ArrayToBase64 } from '../utils';
import {
encryptEmailContentSymmetrically,
decryptEmailSymmetrically,
Expand All @@ -22,9 +21,8 @@ export async function createPwdProtectedEmail(email: Email, password: string): P
}
const aux = getAux(email.params);
const { enc, encryptionKey } = await encryptEmailContentSymmetrically(email.body, aux, email.id);
const encryptedText = uint8ArrayToBase64(enc);
const encryptedKey = await passwordProtectKey(encryptionKey, password);
return { enc: encryptedText, encryptedKey, params: email.params, id: email.id };
return { enc, encryptedKey, params: email.params, id: email.id };
} catch (error) {
throw new Error('Failed to password-protect email', { cause: error });
}
Expand All @@ -41,8 +39,7 @@ export async function decryptPwdProtectedEmail(encryptedEmail: PwdProtectedEmail
try {
const aux = getAux(encryptedEmail.params);
const encryptionKey = await removePasswordProtection(encryptedEmail.encryptedKey, password);
const enc = base64ToUint8Array(encryptedEmail.enc);
const body = await decryptEmailSymmetrically(enc, encryptionKey, aux);
const body = await decryptEmailSymmetrically(encryptionKey, aux, encryptedEmail.enc);
return { body, params: encryptedEmail.params, id: encryptedEmail.id };
} catch (error) {
throw new Error('Failed to decrypt password-protect email', { cause: error });
Expand Down
18 changes: 9 additions & 9 deletions src/email-crypto/pwdProtectedEmailAndSubject.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { base64ToUint8Array, uint8ArrayToBase64 } from '../utils';
import { PwdProtectedEmail, Email } from '../types';
import {
encryptEmailContentAndSubjectSymmetrically,
Expand All @@ -21,17 +20,15 @@ export async function createPwdProtectedEmailAndSubject(email: Email, password:
throw new Error('Failed to password-protect email and subject: Invalid email structure');
}
const aux = getAuxWithoutSubject(email.params);
const { enc, encryptionKey, subjectEnc } = await encryptEmailContentAndSubjectSymmetrically(
const { enc, encryptionKey, encSubject } = await encryptEmailContentAndSubjectSymmetrically(
email.body,
email.params.subject,
aux,
email.id,
);
const encryptedText = uint8ArrayToBase64(enc);
const encryptedKey = await passwordProtectKey(encryptionKey, password);
const encSubjectStr = uint8ArrayToBase64(subjectEnc);
const params = { ...email.params, subject: encSubjectStr };
return { enc: encryptedText, encryptedKey, params, id: email.id };
const params = { ...email.params, subject: encSubject };
return { enc, encryptedKey, params, id: email.id };
} catch (error) {
throw new Error('Failed to password-protect email and subject', { cause: error });
}
Expand All @@ -51,9 +48,12 @@ export async function decryptPwdProtectedEmailAndSubject(
try {
const aux = getAuxWithoutSubject(encryptedEmail.params);
const encryptionKey = await removePasswordProtection(encryptedEmail.encryptedKey, password);
const encSubject = base64ToUint8Array(encryptedEmail.params.subject);
const enc = base64ToUint8Array(encryptedEmail.enc);
const { body, subject } = await decryptEmailAndSubjectSymmetrically(enc, encSubject, encryptionKey, aux);
const { body, subject } = await decryptEmailAndSubjectSymmetrically(
encryptionKey,
aux,
encryptedEmail.params.subject,
encryptedEmail.enc,
);
const params = { ...encryptedEmail.params, subject };
return { body, params, id: encryptedEmail.id };
} catch (error) {
Expand Down
Loading