Skip to content

Commit 4e34d4d

Browse files
Merge pull request #21 from internxt/email_base64
[PB-5714] Email base64
2 parents 8d71afb + 10ea100 commit 4e34d4d

File tree

13 files changed

+54
-564
lines changed

13 files changed

+54
-564
lines changed

src/email-crypto/converters.ts

Lines changed: 2 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,7 @@
1-
import { uint8ArrayToBase64, base64ToUint8Array, UTF8ToUint8, uint8ToUTF8 } from '../utils';
2-
import {
3-
EmailBody,
4-
HybridEncKey,
5-
PwdProtectedKey,
6-
User,
7-
EmailPublicParameters,
8-
HybridEncryptedEmail,
9-
PwdProtectedEmail,
10-
Email,
11-
} from '../types';
1+
import { UTF8ToUint8, uint8ToUTF8 } from '../utils';
2+
import { EmailBody, User, Email } from '../types';
123
import { concatBytes } from '@noble/hashes/utils.js';
134

14-
/**
15-
* Converts a User type into a base64 string.
16-
*
17-
* @param user - The given user.
18-
* @returns The base64 representation of the user.
19-
*/
20-
export function userToBase64(user: User): string {
21-
try {
22-
const json = JSON.stringify(user);
23-
return btoa(json);
24-
} catch (error) {
25-
throw new Error('Failed to convert User to base64', { cause: error });
26-
}
27-
}
28-
295
export function userToBytes(user: User): Uint8Array {
306
try {
317
const json = JSON.stringify(user);
@@ -44,22 +20,6 @@ export function recipientsToBytes(recipients: User[]): Uint8Array {
4420
}
4521
}
4622

47-
/**
48-
* Converts a base64 string into a User type
49-
*
50-
* @param base64 - The base64 representation of the user.
51-
* @returns The User type.
52-
*/
53-
export function base64ToUser(base64: string): User {
54-
try {
55-
const json = atob(base64);
56-
const user: User = JSON.parse(json);
57-
return user;
58-
} catch (error) {
59-
throw new Error('Failed to convert base64 to User', { cause: error });
60-
}
61-
}
62-
6323
/**
6424
* Converts an EmailBody type into a Uint8Array array.
6525
*
@@ -91,163 +51,6 @@ export function binaryToEmailBody(array: Uint8Array): EmailBody {
9151
}
9252
}
9353

94-
/**
95-
* Converts a hybrid key of the type HybridEncKey into base64 string.
96-
*
97-
* @param encHybridKey - The HybridEncKey key.
98-
* @returns The resulting base64 key encoding.
99-
*/
100-
export function encHybridKeyToBase64(encHybridKey: HybridEncKey): string {
101-
try {
102-
const json = JSON.stringify({
103-
kyberCiphertext: uint8ArrayToBase64(encHybridKey.kyberCiphertext),
104-
encryptedKey: uint8ArrayToBase64(encHybridKey.encryptedKey),
105-
});
106-
const base64 = btoa(json);
107-
return base64;
108-
} catch (error) {
109-
throw new Error('Failed to convert hybrid key to base64', { cause: error });
110-
}
111-
}
112-
113-
/**
114-
* Converts a base64 string into a hybrid key of the type HybridEncKey.
115-
*
116-
* @param base - The base64 encoding of the hybrid key.
117-
* @returns The resulting HybridEncKey key.
118-
*/
119-
export function base64ToEncHybridKey(base64: string): HybridEncKey {
120-
try {
121-
const json = atob(base64);
122-
const obj = JSON.parse(json);
123-
return {
124-
encryptedKey: base64ToUint8Array(obj.encryptedKey),
125-
kyberCiphertext: base64ToUint8Array(obj.kyberCiphertext),
126-
};
127-
} catch (error) {
128-
throw new Error('Failed to convert base64 to hybrid key', { cause: error });
129-
}
130-
}
131-
132-
/**
133-
* Converts a password-protected key of the type PwdProtectedKey into base64 string.
134-
*
135-
* @param pwdProtectedKey - The password-protected key of the type PwdProtectedKey.
136-
* @returns The resulting base64 key encoding.
137-
*/
138-
export function pwdProtectedKeyToBase64(pwdProtectedKey: PwdProtectedKey): string {
139-
try {
140-
const json = JSON.stringify({
141-
encryptedKey: uint8ArrayToBase64(pwdProtectedKey.encryptedKey),
142-
salt: uint8ArrayToBase64(pwdProtectedKey.salt),
143-
});
144-
const base64 = btoa(json);
145-
return base64;
146-
} catch (error) {
147-
throw new Error('Failed to convert password-protected key to base64', { cause: error });
148-
}
149-
}
150-
151-
/**
152-
* Converts a base64 string into a password-protected key of the type PwdProtectedKey.
153-
*
154-
* @param base64 - The base64 string.
155-
* @returns The resulting PwdProtectedKey key.
156-
*/
157-
export function base64ToPwdProtectedKey(base64: string): PwdProtectedKey {
158-
try {
159-
const json = atob(base64);
160-
const obj = JSON.parse(json);
161-
return {
162-
encryptedKey: base64ToUint8Array(obj.encryptedKey),
163-
salt: base64ToUint8Array(obj.salt),
164-
};
165-
} catch (error) {
166-
throw new Error('Failed to convert base64 to password-protected key', { cause: error });
167-
}
168-
}
169-
170-
/**
171-
* Converts an email public parameters of type EmailPublicParameters into base64 string.
172-
*
173-
* @param params - The EmailPublicParameters email paramaters.
174-
* @returns The resulting base64 string encoding.
175-
*/
176-
export function paramsToBase64(params: EmailPublicParameters): string {
177-
try {
178-
const json = JSON.stringify({
179-
...params,
180-
sender: userToBase64(params.sender),
181-
recipient: userToBase64(params.recipient),
182-
recipients: params.recipients?.map(userToBase64),
183-
});
184-
const base64 = btoa(json);
185-
return base64;
186-
} catch (error) {
187-
throw new Error('Failed to convert email public parameters to base64', { cause: error });
188-
}
189-
}
190-
191-
/**
192-
* Converts a base64 string into an email paramaters of the type EmailPublicParameters.
193-
*
194-
* @param base64 - The base64 string.
195-
* @returns The resulting EmailPublicParameters email parameters.
196-
*/
197-
export function base64ToParams(base64: string): EmailPublicParameters {
198-
try {
199-
const json = atob(base64);
200-
const obj = JSON.parse(json);
201-
return {
202-
...obj,
203-
sender: base64ToUser(obj.sender),
204-
recipient: base64ToUser(obj.recipient),
205-
recipients: obj.recipients?.map(base64ToUser),
206-
};
207-
} catch (error) {
208-
throw new Error('Failed to convert base64 to email params', { cause: error });
209-
}
210-
}
211-
212-
/**
213-
* Converts an encrypted via hybrid encryption email into base64 string.
214-
*
215-
* @param email - The HybridEncryptedEmail encrypted via hybrid encryption email.
216-
* @returns The resulting base64 string encoding.
217-
*/
218-
export function hybridEncyptedEmailToBase64(email: HybridEncryptedEmail): string {
219-
try {
220-
const json = JSON.stringify({
221-
encryptedKey: encHybridKeyToBase64(email.encryptedKey),
222-
enc: uint8ArrayToBase64(email.enc),
223-
recipientEmail: email.recipientEmail,
224-
});
225-
const base64 = btoa(json);
226-
return base64;
227-
} catch (error) {
228-
throw new Error('Failed to convert hybrid email to base64', { cause: error });
229-
}
230-
}
231-
232-
/**
233-
* Converts a pwd protected email into base64 string.
234-
*
235-
* @param email - The PwdProtectedEmail pwd protected email.
236-
* @returns The resulting base64 string encoding.
237-
*/
238-
export function pwdProtectedEmailToBase64(email: PwdProtectedEmail): string {
239-
try {
240-
const json = JSON.stringify({
241-
encryptedKey: pwdProtectedKeyToBase64(email.encryptedKey),
242-
enc: uint8ArrayToBase64(email.enc),
243-
});
244-
const base64 = btoa(json);
245-
return base64;
246-
} catch (error) {
247-
throw new Error('Failed to convert pwd protected email to base64', { cause: error });
248-
}
249-
}
250-
25154
/**
25255
* Converts an Email type into a Uint8Array array.
25356
*

src/email-crypto/core.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { encapsulateKyber, decapsulateKyber } from '../post-quantum-crypto';
55
import { deriveWrappingKey, wrapKey, unwrapKey, importWrappingKey } from '../key-wrapper';
66
import { deriveSecretKey } from '../asymmetric-crypto';
77
import { getKeyFromPassword, getKeyFromPasswordAndSalt } from '../derive-key';
8-
import { UTF8ToUint8, uint8ToUTF8, uuidToBytes } from '../utils';
8+
import { UTF8ToUint8, base64ToUint8Array, uint8ArrayToBase64, uint8ToUTF8, uuidToBytes } from '../utils';
99

1010
/**
1111
* Symmetrically encrypts an email with a randomly sampled key.
@@ -141,7 +141,10 @@ export async function encryptKeysHybrid(
141141
);
142142
const wrappingKey = await deriveWrappingKey(eccSecret, kyberSecret);
143143
const encryptedKey = await wrapKey(emailEncryptionKey, wrappingKey);
144-
return { encryptedKey, kyberCiphertext };
144+
const encryptedKeyBase64 = uint8ArrayToBase64(encryptedKey);
145+
const kyberCiphertextBase64 = uint8ArrayToBase64(kyberCiphertext);
146+
147+
return { encryptedKey: encryptedKeyBase64, kyberCiphertext: kyberCiphertextBase64 };
145148
} catch (error) {
146149
throw new Error('Failed to encrypt email key using hybrid encryption', { cause: error });
147150
}
@@ -161,10 +164,12 @@ export async function decryptKeysHybrid(
161164
recipientPrivateKey: PrivateKeys,
162165
): Promise<CryptoKey> {
163166
try {
167+
const kyberCiphertext = base64ToUint8Array(encryptedKey.kyberCiphertext);
168+
const encKey = base64ToUint8Array(encryptedKey.encryptedKey);
164169
const eccSecret = await deriveSecretKey(senderPublicKey.eccPublicKey, recipientPrivateKey.eccPrivateKey);
165-
const kyberSecret = decapsulateKyber(encryptedKey.kyberCiphertext, recipientPrivateKey.kyberPrivateKey);
170+
const kyberSecret = decapsulateKyber(kyberCiphertext, recipientPrivateKey.kyberPrivateKey);
166171
const wrappingKey = await deriveWrappingKey(eccSecret, kyberSecret);
167-
const encryptionKey = await unwrapKey(encryptedKey.encryptedKey, wrappingKey);
172+
const encryptionKey = await unwrapKey(encKey, wrappingKey);
168173
return encryptionKey;
169174
} catch (error) {
170175
throw new Error('Failed to decrypt email key encrypted via hybrid encryption', { cause: error });
@@ -183,7 +188,9 @@ export async function passwordProtectKey(emailEncryptionKey: CryptoKey, password
183188
const { key, salt } = await getKeyFromPassword(password);
184189
const wrappingKey = await importWrappingKey(key);
185190
const encryptedKey = await wrapKey(emailEncryptionKey, wrappingKey);
186-
return { encryptedKey, salt };
191+
const saltStr = uint8ArrayToBase64(salt);
192+
const encryptedKeyStr = uint8ArrayToBase64(encryptedKey);
193+
return { encryptedKey: encryptedKeyStr, salt: saltStr };
187194
} catch (error) {
188195
throw new Error('Failed to password-protect email key', { cause: error });
189196
}
@@ -201,9 +208,11 @@ export async function removePasswordProtection(
201208
password: string,
202209
): Promise<CryptoKey> {
203210
try {
204-
const key = await getKeyFromPasswordAndSalt(password, emailEncryptionKey.salt);
211+
const salt = base64ToUint8Array(emailEncryptionKey.salt);
212+
const encryptedKey = base64ToUint8Array(emailEncryptionKey.encryptedKey);
213+
const key = await getKeyFromPasswordAndSalt(password, salt);
205214
const wrappingKey = await importWrappingKey(key);
206-
const encryptionKey = await unwrapKey(emailEncryptionKey.encryptedKey, wrappingKey);
215+
const encryptionKey = await unwrapKey(encryptedKey, wrappingKey);
207216
return encryptionKey;
208217
} catch (error) {
209218
throw new Error('Failed to remove password-protection from email key', { cause: error });

src/email-crypto/hybridEncyptedEmail.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { base64ToUint8Array, uint8ArrayToBase64 } from '../utils';
12
import { PublicKeys, PrivateKeys, HybridEncryptedEmail, Email, UserWithPublicKeys } from '../types';
23
import {
34
encryptEmailContentSymmetrically,
@@ -24,7 +25,8 @@ export async function encryptEmailHybrid(
2425
const aux = getAux(email.params);
2526
const { enc, encryptionKey } = await encryptEmailContentSymmetrically(email.body, aux, email.id);
2627
const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient.publicKeys, senderPrivateKey);
27-
return { enc, encryptedKey, recipientEmail: recipient.email, params: email.params, id: email.id };
28+
const encryptedText = uint8ArrayToBase64(enc);
29+
return { enc: encryptedText, encryptedKey, recipientEmail: recipient.email, params: email.params, id: email.id };
2830
} catch (error) {
2931
throw new Error('Failed to encrypt email with hybrid encryption', { cause: error });
3032
}
@@ -46,11 +48,18 @@ export async function encryptEmailHybridForMultipleRecipients(
4648
try {
4749
const aux = getAux(email.params);
4850
const { enc, encryptionKey } = await encryptEmailContentSymmetrically(email.body, aux, email.id);
51+
const encryptedText = uint8ArrayToBase64(enc);
4952

5053
const encryptedEmails: HybridEncryptedEmail[] = [];
5154
for (const recipient of recipients) {
5255
const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient.publicKeys, senderPrivateKey);
53-
encryptedEmails.push({ enc, encryptedKey, recipientEmail: recipient.email, params: email.params, id: email.id });
56+
encryptedEmails.push({
57+
enc: encryptedText,
58+
encryptedKey,
59+
recipientEmail: recipient.email,
60+
params: email.params,
61+
id: email.id,
62+
});
5463
}
5564
return encryptedEmails;
5665
} catch (error) {
@@ -74,7 +83,8 @@ export async function decryptEmailHybrid(
7483
try {
7584
const aux = getAux(encryptedEmail.params);
7685
const encryptionKey = await decryptKeysHybrid(encryptedEmail.encryptedKey, senderPublicKeys, recipientPrivateKeys);
77-
const body = await decryptEmailSymmetrically(encryptedEmail.enc, encryptionKey, aux);
86+
const enc = base64ToUint8Array(encryptedEmail.enc);
87+
const body = await decryptEmailSymmetrically(enc, encryptionKey, aux);
7888
return { body, params: encryptedEmail.params, id: encryptedEmail.id };
7989
} catch (error) {
8090
throw new Error('Failed to decrypt email with hybrid encryption', { cause: error });

src/email-crypto/hybridEncyptedEmailAndSubject.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ export async function encryptEmailAndSubjectHybrid(
2929
aux,
3030
email.id,
3131
);
32+
const encryptedText = uint8ArrayToBase64(enc);
3233
const encSubjectStr = uint8ArrayToBase64(subjectEnc);
3334
const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient.publicKeys, senderPrivateKey);
3435
const params = { ...email.params, subject: encSubjectStr };
35-
return { enc, encryptedKey, recipientEmail: recipient.email, params, id: email.id };
36+
return { enc: encryptedText, encryptedKey, recipientEmail: recipient.email, params, id: email.id };
3637
} catch (error) {
3738
throw new Error('Failed to encrypt the email and its subject with hybrid encryption', { cause: error });
3839
}
@@ -60,12 +61,13 @@ export async function encryptEmailAndSubjectHybridForMultipleRecipients(
6061
email.id,
6162
);
6263
const encSubjectStr = uint8ArrayToBase64(subjectEnc);
64+
const encryptedText = uint8ArrayToBase64(enc);
6365

6466
const encryptedEmails: HybridEncryptedEmail[] = [];
6567
for (const recipient of recipients) {
6668
const encryptedKey = await encryptKeysHybrid(encryptionKey, recipient.publicKeys, senderPrivateKey);
6769
const params = { ...email.params, subject: encSubjectStr };
68-
encryptedEmails.push({ enc, encryptedKey, recipientEmail: recipient.email, params, id: email.id });
70+
encryptedEmails.push({ enc: encryptedText, encryptedKey, recipientEmail: recipient.email, params, id: email.id });
6971
}
7072
return encryptedEmails;
7173
} catch (error) {
@@ -92,12 +94,8 @@ export async function decryptEmailAndSubjectHybrid(
9294
const aux = getAuxWithoutSubject(encryptedEmail.params);
9395
const encryptionKey = await decryptKeysHybrid(encryptedEmail.encryptedKey, senderPublicKeys, recipientPrivateKeys);
9496
const encSubject = base64ToUint8Array(encryptedEmail.params.subject);
95-
const { body, subject } = await decryptEmailAndSubjectSymmetrically(
96-
encryptedEmail.enc,
97-
encSubject,
98-
encryptionKey,
99-
aux,
100-
);
97+
const enc = base64ToUint8Array(encryptedEmail.enc);
98+
const { body, subject } = await decryptEmailAndSubjectSymmetrically(enc, encSubject, encryptionKey, aux);
10199
const params = { ...encryptedEmail.params, subject };
102100
return { body, params, id: encryptedEmail.id };
103101
} catch (error) {

src/email-crypto/pwdProtectedEmail.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { PwdProtectedEmail, Email } from '../types';
2+
import { base64ToUint8Array, uint8ArrayToBase64 } from '../utils';
23
import {
34
encryptEmailContentSymmetrically,
45
decryptEmailSymmetrically,
@@ -21,8 +22,9 @@ export async function createPwdProtectedEmail(email: Email, password: string): P
2122
}
2223
const aux = getAux(email.params);
2324
const { enc, encryptionKey } = await encryptEmailContentSymmetrically(email.body, aux, email.id);
25+
const encryptedText = uint8ArrayToBase64(enc);
2426
const encryptedKey = await passwordProtectKey(encryptionKey, password);
25-
return { enc, encryptedKey, params: email.params, id: email.id };
27+
return { enc: encryptedText, encryptedKey, params: email.params, id: email.id };
2628
} catch (error) {
2729
throw new Error('Failed to password-protect email', { cause: error });
2830
}
@@ -39,7 +41,8 @@ export async function decryptPwdProtectedEmail(encryptedEmail: PwdProtectedEmail
3941
try {
4042
const aux = getAux(encryptedEmail.params);
4143
const encryptionKey = await removePasswordProtection(encryptedEmail.encryptedKey, password);
42-
const body = await decryptEmailSymmetrically(encryptedEmail.enc, encryptionKey, aux);
44+
const enc = base64ToUint8Array(encryptedEmail.enc);
45+
const body = await decryptEmailSymmetrically(enc, encryptionKey, aux);
4346
return { body, params: encryptedEmail.params, id: encryptedEmail.id };
4447
} catch (error) {
4548
throw new Error('Failed to decrypt password-protect email', { cause: error });

0 commit comments

Comments
 (0)