Skip to content

Commit 0394d3c

Browse files
Merge pull request #22 from internxt/encrypt_attach_aeparate
[PB-5714] Encrypt email attachements separately
2 parents 4e34d4d + 2cd395e commit 0394d3c

File tree

12 files changed

+146
-173
lines changed

12 files changed

+146
-173
lines changed

src/email-crypto/converters.ts

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { UTF8ToUint8, uint8ToUTF8 } from '../utils';
2-
import { EmailBody, User, Email } from '../types';
1+
import { UTF8ToUint8 } from '../utils';
2+
import { User, Email } from '../types';
33
import { concatBytes } from '@noble/hashes/utils.js';
44

55
export function userToBytes(user: User): Uint8Array {
@@ -20,37 +20,6 @@ export function recipientsToBytes(recipients: User[]): Uint8Array {
2020
}
2121
}
2222

23-
/**
24-
* Converts an EmailBody type into a Uint8Array array.
25-
*
26-
* @param body - The email body.
27-
* @returns The Uint8Array array representation of the EmailBody type.
28-
*/
29-
export function emailBodyToBinary(body: EmailBody): Uint8Array {
30-
try {
31-
const json = JSON.stringify(body);
32-
return UTF8ToUint8(json);
33-
} catch (error) {
34-
throw new Error('Failed to convert EmailBody to Uint8Array', { cause: error });
35-
}
36-
}
37-
38-
/**
39-
* Converts an Uint8Array array into EmailBody type.
40-
*
41-
* @param array - The Uint8Array array.
42-
* @returns The EmailBody type representation of the Uint8Array.
43-
*/
44-
export function binaryToEmailBody(array: Uint8Array): EmailBody {
45-
try {
46-
const json = uint8ToUTF8(array);
47-
const email: EmailBody = JSON.parse(json);
48-
return email;
49-
} catch (error) {
50-
throw new Error('Failed to convert Uint8Array to EmailBody', { cause: error });
51-
}
52-
}
53-
5423
/**
5524
* Converts an Email type into a Uint8Array array.
5625
*

src/email-crypto/core.ts

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { HybridEncKey, PwdProtectedKey, PublicKeys, PrivateKeys, EmailBody } from '../types';
1+
import { HybridEncKey, PwdProtectedKey, PublicKeys, PrivateKeys, EmailBody, EmailBodyEncrypted } from '../types';
22
import { genSymmetricCryptoKey, encryptSymmetrically, decryptSymmetrically } from '../symmetric-crypto';
3-
import { emailBodyToBinary, binaryToEmailBody } from './converters';
43
import { encapsulateKyber, decapsulateKyber } from '../post-quantum-crypto';
54
import { deriveWrappingKey, wrapKey, unwrapKey, importWrappingKey } from '../key-wrapper';
65
import { deriveSecretKey } from '../asymmetric-crypto';
@@ -19,8 +18,11 @@ export async function encryptEmailContentSymmetrically(
1918
email: EmailBody,
2019
aux: Uint8Array,
2120
emailID: string,
22-
): Promise<{ enc: Uint8Array; encryptionKey: CryptoKey }> {
21+
): Promise<{ enc: EmailBodyEncrypted; encryptionKey: CryptoKey }> {
2322
try {
23+
if (!email.text) {
24+
throw new Error('Invalid input');
25+
}
2426
const encryptionKey = await genSymmetricCryptoKey();
2527
const enc = await encryptEmailContentSymmetricallyWithKey(email, encryptionKey, aux, emailID);
2628
return { enc, encryptionKey };
@@ -43,13 +45,17 @@ export async function encryptEmailContentAndSubjectSymmetrically(
4345
subject: string,
4446
aux: Uint8Array,
4547
emailID: string,
46-
): Promise<{ enc: Uint8Array; subjectEnc: Uint8Array; encryptionKey: CryptoKey }> {
48+
): Promise<{ enc: EmailBodyEncrypted; encSubject: string; encryptionKey: CryptoKey }> {
4749
try {
50+
if (!subject || !email.text) {
51+
throw new Error('Invalid input');
52+
}
4853
const encryptionKey = await genSymmetricCryptoKey();
4954
const enc = await encryptEmailContentSymmetricallyWithKey(email, encryptionKey, aux, emailID);
5055
const subjectBuff = UTF8ToUint8(subject);
5156
const subjectEnc = await encryptSymmetrically(encryptionKey, subjectBuff, aux);
52-
return { enc, encryptionKey, subjectEnc };
57+
const encSubject = uint8ArrayToBase64(subjectEnc);
58+
return { enc, encSubject, encryptionKey };
5359
} catch (error) {
5460
throw new Error('Failed to symmetrically encrypt email and subject', { cause: error });
5561
}
@@ -63,16 +69,17 @@ export async function encryptEmailContentAndSubjectSymmetrically(
6369
* @returns The decrypted email
6470
*/
6571
export async function decryptEmailAndSubjectSymmetrically(
66-
emailCiphertext: Uint8Array,
67-
encSubject: Uint8Array,
6872
encryptionKey: CryptoKey,
6973
aux: Uint8Array,
74+
encSubject: string,
75+
enc: EmailBodyEncrypted,
7076
): Promise<{ body: EmailBody; subject: string }> {
7177
try {
72-
const binaryEmail = await decryptSymmetrically(encryptionKey, emailCiphertext, aux);
73-
const subject = await decryptSymmetrically(encryptionKey, encSubject, aux);
74-
const body = binaryToEmailBody(binaryEmail);
75-
return { body, subject: uint8ToUTF8(subject) };
78+
const array = base64ToUint8Array(encSubject);
79+
const subjectArray = await decryptSymmetrically(encryptionKey, array, aux);
80+
const body = await decryptEmailSymmetrically(encryptionKey, aux, enc);
81+
const subject = uint8ToUTF8(subjectArray);
82+
return { body, subject };
7683
} catch (error) {
7784
throw new Error('Failed to symmetrically decrypt email and subject', { cause: error });
7885
}
@@ -89,17 +96,61 @@ export async function encryptEmailContentSymmetricallyWithKey(
8996
encryptionKey: CryptoKey,
9097
aux: Uint8Array,
9198
emailID: string,
92-
): Promise<Uint8Array> {
99+
): Promise<EmailBodyEncrypted> {
93100
try {
94101
const freeField = uuidToBytes(emailID);
95-
const binaryEmail = emailBodyToBinary(emailBody);
96-
const ciphertext = await encryptSymmetrically(encryptionKey, binaryEmail, aux, freeField);
97-
return ciphertext;
102+
const text = UTF8ToUint8(emailBody.text);
103+
const encryptedText = await encryptSymmetrically(encryptionKey, text, aux, freeField);
104+
const encText = uint8ArrayToBase64(encryptedText);
105+
const result: EmailBodyEncrypted = { encText };
106+
107+
if (emailBody.attachments) {
108+
const encryptedAttachements = await encryptEmailAttachements(emailBody.attachments, encryptionKey, aux, emailID);
109+
result.encAttachments = encryptedAttachements?.map(uint8ArrayToBase64);
110+
}
111+
return result;
98112
} catch (error) {
99113
throw new Error('Failed to symmetrically encrypt email with the given key', { cause: error });
100114
}
101115
}
102116

117+
async function encryptEmailAttachements(
118+
attachments: string[],
119+
encryptionKey: CryptoKey,
120+
aux: Uint8Array,
121+
emailID: string,
122+
): Promise<Uint8Array[]> {
123+
try {
124+
const freeField = uuidToBytes(emailID);
125+
const encryptedAttachments = await Promise.all(
126+
attachments.map((attachment) => {
127+
const binaryAttachment = UTF8ToUint8(attachment);
128+
return encryptSymmetrically(encryptionKey, binaryAttachment, aux, freeField);
129+
}),
130+
);
131+
return encryptedAttachments;
132+
} catch (error) {
133+
throw new Error('Failed to symmetrically encrypt email attachements', { cause: error });
134+
}
135+
}
136+
137+
async function decryptEmailAttachements(
138+
encryptedAttachments: Uint8Array[],
139+
encryptionKey: CryptoKey,
140+
aux: Uint8Array,
141+
): Promise<Uint8Array[]> {
142+
try {
143+
const decryptedAttachments = await Promise.all(
144+
encryptedAttachments.map((attachment) => {
145+
return decryptSymmetrically(encryptionKey, attachment, aux);
146+
}),
147+
);
148+
return decryptedAttachments;
149+
} catch (error) {
150+
throw new Error('Failed to symmetrically decrypt email attachements', { cause: error });
151+
}
152+
}
153+
103154
/**
104155
* Decrypts symmetrically encrypted email.
105156
*
@@ -108,14 +159,22 @@ export async function encryptEmailContentSymmetricallyWithKey(
108159
* @returns The decrypted email
109160
*/
110161
export async function decryptEmailSymmetrically(
111-
emailCiphertext: Uint8Array,
112162
encryptionKey: CryptoKey,
113163
aux: Uint8Array,
164+
enc: EmailBodyEncrypted,
114165
): Promise<EmailBody> {
115166
try {
116-
const binaryEmail = await decryptSymmetrically(encryptionKey, emailCiphertext, aux);
117-
const body = binaryToEmailBody(binaryEmail);
118-
return body;
167+
const cipher = base64ToUint8Array(enc.encText);
168+
const textArray = await decryptSymmetrically(encryptionKey, cipher, aux);
169+
const text = uint8ToUTF8(textArray);
170+
const result: EmailBody = { text };
171+
172+
if (enc.encAttachments) {
173+
const encAttachements = enc.encAttachments?.map(base64ToUint8Array);
174+
const attachmentsArray = await decryptEmailAttachements(encAttachements, encryptionKey, aux);
175+
result.attachments = attachmentsArray?.map((att) => uint8ToUTF8(att));
176+
}
177+
return result;
119178
} catch (error) {
120179
throw new Error('Failed to symmetrically decrypt email', { cause: error });
121180
}

src/email-crypto/hybridEncyptedEmail.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { base64ToUint8Array, uint8ArrayToBase64 } from '../utils';
21
import { PublicKeys, PrivateKeys, HybridEncryptedEmail, Email, UserWithPublicKeys } from '../types';
32
import {
43
encryptEmailContentSymmetrically,
@@ -25,8 +24,7 @@ export async function encryptEmailHybrid(
2524
const aux = getAux(email.params);
2625
const { enc, encryptionKey } = await encryptEmailContentSymmetrically(email.body, aux, email.id);
2726
const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient.publicKeys, senderPrivateKey);
28-
const encryptedText = uint8ArrayToBase64(enc);
29-
return { enc: encryptedText, encryptedKey, recipientEmail: recipient.email, params: email.params, id: email.id };
27+
return { enc, encryptedKey, recipientEmail: recipient.email, params: email.params, id: email.id };
3028
} catch (error) {
3129
throw new Error('Failed to encrypt email with hybrid encryption', { cause: error });
3230
}
@@ -48,13 +46,12 @@ export async function encryptEmailHybridForMultipleRecipients(
4846
try {
4947
const aux = getAux(email.params);
5048
const { enc, encryptionKey } = await encryptEmailContentSymmetrically(email.body, aux, email.id);
51-
const encryptedText = uint8ArrayToBase64(enc);
5249

5350
const encryptedEmails: HybridEncryptedEmail[] = [];
5451
for (const recipient of recipients) {
5552
const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient.publicKeys, senderPrivateKey);
5653
encryptedEmails.push({
57-
enc: encryptedText,
54+
enc,
5855
encryptedKey,
5956
recipientEmail: recipient.email,
6057
params: email.params,
@@ -83,8 +80,7 @@ export async function decryptEmailHybrid(
8380
try {
8481
const aux = getAux(encryptedEmail.params);
8582
const encryptionKey = await decryptKeysHybrid(encryptedEmail.encryptedKey, senderPublicKeys, recipientPrivateKeys);
86-
const enc = base64ToUint8Array(encryptedEmail.enc);
87-
const body = await decryptEmailSymmetrically(enc, encryptionKey, aux);
83+
const body = await decryptEmailSymmetrically(encryptionKey, aux, encryptedEmail.enc);
8884
return { body, params: encryptedEmail.params, id: encryptedEmail.id };
8985
} catch (error) {
9086
throw new Error('Failed to decrypt email with hybrid encryption', { cause: error });

src/email-crypto/hybridEncyptedEmailAndSubject.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { base64ToUint8Array, uint8ArrayToBase64 } from '../utils';
21
import { PublicKeys, PrivateKeys, HybridEncryptedEmail, Email, UserWithPublicKeys } from '../types';
32
import {
43
encryptEmailContentAndSubjectSymmetrically,
@@ -23,17 +22,15 @@ export async function encryptEmailAndSubjectHybrid(
2322
): Promise<HybridEncryptedEmail> {
2423
try {
2524
const aux = getAuxWithoutSubject(email.params);
26-
const { enc, encryptionKey, subjectEnc } = await encryptEmailContentAndSubjectSymmetrically(
25+
const { enc, encSubject, encryptionKey } = await encryptEmailContentAndSubjectSymmetrically(
2726
email.body,
2827
email.params.subject,
2928
aux,
3029
email.id,
3130
);
32-
const encryptedText = uint8ArrayToBase64(enc);
33-
const encSubjectStr = uint8ArrayToBase64(subjectEnc);
3431
const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient.publicKeys, senderPrivateKey);
35-
const params = { ...email.params, subject: encSubjectStr };
36-
return { enc: encryptedText, encryptedKey, recipientEmail: recipient.email, params, id: email.id };
32+
const params = { ...email.params, subject: encSubject };
33+
return { enc, encryptedKey, recipientEmail: recipient.email, params, id: email.id };
3734
} catch (error) {
3835
throw new Error('Failed to encrypt the email and its subject with hybrid encryption', { cause: error });
3936
}
@@ -54,20 +51,18 @@ export async function encryptEmailAndSubjectHybridForMultipleRecipients(
5451
): Promise<HybridEncryptedEmail[]> {
5552
try {
5653
const aux = getAuxWithoutSubject(email.params);
57-
const { enc, encryptionKey, subjectEnc } = await encryptEmailContentAndSubjectSymmetrically(
54+
const { enc, encSubject, encryptionKey } = await encryptEmailContentAndSubjectSymmetrically(
5855
email.body,
5956
email.params.subject,
6057
aux,
6158
email.id,
6259
);
63-
const encSubjectStr = uint8ArrayToBase64(subjectEnc);
64-
const encryptedText = uint8ArrayToBase64(enc);
6560

6661
const encryptedEmails: HybridEncryptedEmail[] = [];
6762
for (const recipient of recipients) {
6863
const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient.publicKeys, senderPrivateKey);
69-
const params = { ...email.params, subject: encSubjectStr };
70-
encryptedEmails.push({ enc: encryptedText, encryptedKey, recipientEmail: recipient.email, params, id: email.id });
64+
const params = { ...email.params, subject: encSubject };
65+
encryptedEmails.push({ enc, encryptedKey, recipientEmail: recipient.email, params, id: email.id });
7166
}
7267
return encryptedEmails;
7368
} catch (error) {
@@ -93,9 +88,12 @@ export async function decryptEmailAndSubjectHybrid(
9388
try {
9489
const aux = getAuxWithoutSubject(encryptedEmail.params);
9590
const encryptionKey = await decryptKeysHybrid(encryptedEmail.encryptedKey, senderPublicKeys, recipientPrivateKeys);
96-
const encSubject = base64ToUint8Array(encryptedEmail.params.subject);
97-
const enc = base64ToUint8Array(encryptedEmail.enc);
98-
const { body, subject } = await decryptEmailAndSubjectSymmetrically(enc, encSubject, encryptionKey, aux);
91+
const { body, subject } = await decryptEmailAndSubjectSymmetrically(
92+
encryptionKey,
93+
aux,
94+
encryptedEmail.params.subject,
95+
encryptedEmail.enc,
96+
);
9997
const params = { ...encryptedEmail.params, subject };
10098
return { body, params, id: encryptedEmail.id };
10199
} catch (error) {

src/email-crypto/pwdProtectedEmail.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { PwdProtectedEmail, Email } from '../types';
2-
import { base64ToUint8Array, uint8ArrayToBase64 } from '../utils';
32
import {
43
encryptEmailContentSymmetrically,
54
decryptEmailSymmetrically,
@@ -22,9 +21,8 @@ export async function createPwdProtectedEmail(email: Email, password: string): P
2221
}
2322
const aux = getAux(email.params);
2423
const { enc, encryptionKey } = await encryptEmailContentSymmetrically(email.body, aux, email.id);
25-
const encryptedText = uint8ArrayToBase64(enc);
2624
const encryptedKey = await passwordProtectKey(encryptionKey, password);
27-
return { enc: encryptedText, encryptedKey, params: email.params, id: email.id };
25+
return { enc, encryptedKey, params: email.params, id: email.id };
2826
} catch (error) {
2927
throw new Error('Failed to password-protect email', { cause: error });
3028
}
@@ -41,8 +39,7 @@ export async function decryptPwdProtectedEmail(encryptedEmail: PwdProtectedEmail
4139
try {
4240
const aux = getAux(encryptedEmail.params);
4341
const encryptionKey = await removePasswordProtection(encryptedEmail.encryptedKey, password);
44-
const enc = base64ToUint8Array(encryptedEmail.enc);
45-
const body = await decryptEmailSymmetrically(enc, encryptionKey, aux);
42+
const body = await decryptEmailSymmetrically(encryptionKey, aux, encryptedEmail.enc);
4643
return { body, params: encryptedEmail.params, id: encryptedEmail.id };
4744
} catch (error) {
4845
throw new Error('Failed to decrypt password-protect email', { cause: error });

src/email-crypto/pwdProtectedEmailAndSubject.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { base64ToUint8Array, uint8ArrayToBase64 } from '../utils';
21
import { PwdProtectedEmail, Email } from '../types';
32
import {
43
encryptEmailContentAndSubjectSymmetrically,
@@ -21,17 +20,15 @@ export async function createPwdProtectedEmailAndSubject(email: Email, password:
2120
throw new Error('Failed to password-protect email and subject: Invalid email structure');
2221
}
2322
const aux = getAuxWithoutSubject(email.params);
24-
const { enc, encryptionKey, subjectEnc } = await encryptEmailContentAndSubjectSymmetrically(
23+
const { enc, encryptionKey, encSubject } = await encryptEmailContentAndSubjectSymmetrically(
2524
email.body,
2625
email.params.subject,
2726
aux,
2827
email.id,
2928
);
30-
const encryptedText = uint8ArrayToBase64(enc);
3129
const encryptedKey = await passwordProtectKey(encryptionKey, password);
32-
const encSubjectStr = uint8ArrayToBase64(subjectEnc);
33-
const params = { ...email.params, subject: encSubjectStr };
34-
return { enc: encryptedText, encryptedKey, params, id: email.id };
30+
const params = { ...email.params, subject: encSubject };
31+
return { enc, encryptedKey, params, id: email.id };
3532
} catch (error) {
3633
throw new Error('Failed to password-protect email and subject', { cause: error });
3734
}
@@ -51,9 +48,12 @@ export async function decryptPwdProtectedEmailAndSubject(
5148
try {
5249
const aux = getAuxWithoutSubject(encryptedEmail.params);
5350
const encryptionKey = await removePasswordProtection(encryptedEmail.encryptedKey, password);
54-
const encSubject = base64ToUint8Array(encryptedEmail.params.subject);
55-
const enc = base64ToUint8Array(encryptedEmail.enc);
56-
const { body, subject } = await decryptEmailAndSubjectSymmetrically(enc, encSubject, encryptionKey, aux);
51+
const { body, subject } = await decryptEmailAndSubjectSymmetrically(
52+
encryptionKey,
53+
aux,
54+
encryptedEmail.params.subject,
55+
encryptedEmail.enc,
56+
);
5757
const params = { ...encryptedEmail.params, subject };
5858
return { body, params, id: encryptedEmail.id };
5959
} catch (error) {

0 commit comments

Comments
 (0)