From b82311f8ac4e7c24ace71e8f1a19199970c19d8f Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Fri, 20 Mar 2026 17:02:02 +0100 Subject: [PATCH 1/2] update internxt-crypto --- package.json | 2 +- src/mail/index.ts | 102 +++++++++++++++---------------- test/mail/index.test.ts | 130 +++++++++++++++------------------------- yarn.lock | 11 ++-- 4 files changed, 102 insertions(+), 143 deletions(-) diff --git a/package.json b/package.json index 59c8390..e30fa08 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ }, "dependencies": { "axios": "1.13.6", - "internxt-crypto": "0.0.14" + "internxt-crypto": "1.0.0" }, "lint-staged": { "*.{js,jsx,tsx,ts}": [ diff --git a/src/mail/index.ts b/src/mail/index.ts index bad73d4..2a796f4 100644 --- a/src/mail/index.ts +++ b/src/mail/index.ts @@ -4,23 +4,23 @@ import { HttpClient } from '../shared/http/client'; import { EncryptedKeystore, KeystoreType, - PublicKeysBase64, - PrivateKeys, HybridEncryptedEmail, PwdProtectedEmail, - base64ToPublicKey, - EmailKeys, + HybridKeyPair, Email, + EmailBody, createEncryptionAndRecoveryKeystores, encryptEmailHybrid, - User, openEncryptionKeystore, - UserWithPublicKeys, + RecipientWithPublicKey, encryptEmailHybridForMultipleRecipients, decryptEmailHybrid, createPwdProtectedEmail, decryptPwdProtectedEmail, openRecoveryKeystore, + UTF8ToUint8, + base64ToUint8Array, + EmailPublicParameters, } from 'internxt-crypto'; export class Mail { @@ -82,9 +82,9 @@ export class Mail { * * @param userEmail - The email of the user * @param baseKey - The secret key of the user - * @returns The email keys of the user + * @returns The hybrid keys of the user */ - async getUserEmailKeys(userEmail: string, baseKey: Uint8Array): Promise { + async getUserEmailKeys(userEmail: string, baseKey: Uint8Array): Promise { const keystore = await this.downloadKeystoreFromServer(userEmail, KeystoreType.ENCRYPTION); return openEncryptionKeystore(keystore, baseKey); } @@ -94,9 +94,9 @@ export class Mail { * * @param userEmail - The email of the user * @param recoveryCodes - The recovery codes of the user - * @returns The email keys of the user + * @returns The hybrid keys of the user */ - async recoverUserEmailKeys(userEmail: string, recoveryCodes: string): Promise { + async recoverUserEmailKeys(userEmail: string, recoveryCodes: string): Promise { const keystore = await this.downloadKeystoreFromServer(userEmail, KeystoreType.RECOVERY); return openRecoveryKeystore(recoveryCodes, keystore); } @@ -107,16 +107,16 @@ export class Mail { * @param userEmail - The email of the user * @returns User with corresponding public keys */ - async getUserWithPublicKeys(userEmail: string): Promise { - const response = await this.client.post<{ publicKeys: PublicKeysBase64; user: User }[]>( + async getUserWithPublicKeys(userEmail: string): Promise { + const response = await this.client.post<{ publicKey: string; email: string }[]>( `${this.apiUrl}/users/public-keys`, { emails: [userEmail] }, this.headers(), ); if (!response[0]) throw new Error(`No public keys found for ${userEmail}`); const singleResponse = response[0]; - const publicKeys = await base64ToPublicKey(singleResponse.publicKeys); - const result = { ...singleResponse.user, publicKeys }; + const publicHybridKey = base64ToUint8Array(singleResponse.publicKey); + const result = { email: singleResponse.email, publicHybridKey }; return result; } @@ -126,8 +126,8 @@ export class Mail { * @param emails - The emails of the users * @returns Users with corresponding public keys */ - async getSeveralUsersWithPublicKeys(emails: string[]): Promise { - const response = await this.client.post<{ publicKeys: PublicKeysBase64; user: User }[]>( + async getSeveralUsersWithPublicKeys(emails: string[]): Promise { + const response = await this.client.post<{ publicKey: string; email: string }[]>( `${this.apiUrl}/users/public-keys`, { emails }, this.headers(), @@ -135,8 +135,8 @@ export class Mail { const result = await Promise.all( response.map(async (item) => { - const publicKeys = await base64ToPublicKey(item.publicKeys); - return { ...item.user, publicKeys }; + const publicHybridKey = base64ToUint8Array(item.publicKey); + return { email: item.email, publicHybridKey }; }), ); @@ -147,38 +147,39 @@ export class Mail { * Sends the encrypted email to the server * * @param email - The encrypted email + * @param params - The public parameters of the email (sender, recipients, replyTo, etc.) * @returns Server response */ - async sendEncryptedEmail(email: HybridEncryptedEmail): Promise { - return this.client.post(`${this.apiUrl}/emails`, { emails: [email] }, this.headers()); + async sendEncryptedEmail(email: HybridEncryptedEmail, params: EmailPublicParameters): Promise { + return this.client.post(`${this.apiUrl}/emails`, { emails: [email], params }, this.headers()); } /** * Encrypts email and sends it to the server * * @param email - The message to encrypt - * @param senderPrivateKeys - The private keys of the sender - * @param isSubjectEncrypted - Indicates if the subject field should be encrypted + * @param aux - An optional string that can be used as additional input for the encryption * @returns Server response */ - async encryptAndSendEmail( - email: Email, - senderPrivateKeys: PrivateKeys, - isSubjectEncrypted: boolean = false, - ): Promise { + async encryptAndSendEmail(email: Email, aux?: string): Promise { const recipient = await this.getUserWithPublicKeys(email.params.recipient.email); - const encEmail = await encryptEmailHybrid(email, recipient, senderPrivateKeys, isSubjectEncrypted); - return this.sendEncryptedEmail(encEmail); + const auxArray = aux ? UTF8ToUint8(aux) : new Uint8Array(); + const encEmail = await encryptEmailHybrid(email.body, recipient, auxArray); + return this.sendEncryptedEmail(encEmail, email.params); } /** * Sends the encrypted emails for multiple recipients to the server * * @param emails - The encrypted emails + * @param params - The public parameters of the email (sender, recipients, replyTo, etc.) * @returns Server response */ - async sendEncryptedEmailToMultipleRecipients(emails: HybridEncryptedEmail[]): Promise { - return this.client.post(`${this.apiUrl}/emails`, { emails }, this.headers()); + async sendEncryptedEmailToMultipleRecipients( + emails: HybridEncryptedEmail[], + params: EmailPublicParameters, + ): Promise { + return this.client.post(`${this.apiUrl}/emails`, { emails, params }, this.headers()); } /** @@ -189,23 +190,15 @@ export class Mail { * @param isSubjectEncrypted - Indicates if the subject field should be encrypted * @returns Server response */ - async encryptAndSendEmailToMultipleRecipients( - email: Email, - senderPrivateKeys: PrivateKeys, - isSubjectEncrypted: boolean = false, - ): Promise { + async encryptAndSendEmailToMultipleRecipients(email: Email, aux?: string): Promise { const recipientEmails = email.params.recipients ? email.params.recipients.map((user) => user.email) : [email.params.recipient.email]; const recipients = await this.getSeveralUsersWithPublicKeys(recipientEmails); - const encEmails = await encryptEmailHybridForMultipleRecipients( - email, - recipients, - senderPrivateKeys, - isSubjectEncrypted, - ); - return this.sendEncryptedEmailToMultipleRecipients(encEmails); + const auxArray = aux ? UTF8ToUint8(aux) : new Uint8Array(); + const encEmails = await encryptEmailHybridForMultipleRecipients(email.body, recipients, auxArray); + return this.sendEncryptedEmailToMultipleRecipients(encEmails, email.params); } /** @@ -214,8 +207,8 @@ export class Mail { * @param email - The password-protected email * @returns Server response */ - async sendPasswordProtectedEmail(email: PwdProtectedEmail): Promise { - return this.client.post(`${this.apiUrl}/emails`, { email }, this.headers()); + async sendPasswordProtectedEmail(email: PwdProtectedEmail, params: EmailPublicParameters): Promise { + return this.client.post(`${this.apiUrl}/emails`, { email, params }, this.headers()); } /** @@ -226,9 +219,10 @@ export class Mail { * @param isSubjectEncrypted - Indicates if the subject field should be encrypted * @returns Server response */ - async passwordProtectAndSendEmail(email: Email, pwd: string, isSubjectEncrypted: boolean = false): Promise { - const encEmail = await createPwdProtectedEmail(email, pwd, isSubjectEncrypted); - return this.sendPasswordProtectedEmail(encEmail); + async passwordProtectAndSendEmail(email: Email, pwd: string, aux?: string): Promise { + const auxArray = aux ? UTF8ToUint8(aux) : new Uint8Array(); + const encEmail = await createPwdProtectedEmail(email.body, pwd, auxArray); + return this.sendPasswordProtectedEmail(encEmail, email.params); } /** @@ -236,9 +230,9 @@ export class Mail { * * @param email - The password-protected email * @param pwd - The shared password - * @returns The decrypted email + * @returns The decrypted email body */ - async openPasswordProtectedEmail(email: PwdProtectedEmail, pwd: string): Promise { + async openPasswordProtectedEmail(email: PwdProtectedEmail, pwd: string): Promise { return decryptPwdProtectedEmail(email, pwd); } @@ -247,12 +241,10 @@ export class Mail { * * @param email - The encrypted email * @param recipientPrivateKeys - The private keys of the email recipient - * @returns The decrypted email + * @returns The decrypted email body */ - async decryptEmail(email: HybridEncryptedEmail, recipientPrivateKeys: PrivateKeys): Promise { - const senderEmail = email.params.sender.email; - const sender = await this.getUserWithPublicKeys(senderEmail); - return decryptEmailHybrid(email, sender.publicKeys, recipientPrivateKeys); + async decryptEmail(email: HybridEncryptedEmail, recipientPrivateKeys: Uint8Array): Promise { + return decryptEmailHybrid(email, recipientPrivateKeys); } /** diff --git a/test/mail/index.test.ts b/test/mail/index.test.ts index 26c9578..a98b85e 100644 --- a/test/mail/index.test.ts +++ b/test/mail/index.test.ts @@ -7,11 +7,11 @@ import { genSymmetricKey, KeystoreType, generateEmailKeys, - publicKeyToBase64, Email, generateUuid, createPwdProtectedEmail, encryptEmailHybrid, + uint8ArrayToBase64, } from 'internxt-crypto'; import { describe, it, expect, beforeEach, vi } from 'vitest'; @@ -53,13 +53,8 @@ describe('Mail service tests', () => { encryptedKeystore: { userEmail: email, type: KeystoreType.ENCRYPTION, - encryptedKeys: expect.objectContaining({ - publicKeys: { eccPublicKeyBase64: expect.any(String), kyberPublicKeyBase64: expect.any(String) }, - privateKeys: { - eccPrivateKeyBase64: expect.any(String), - kyberPrivateKeyBase64: expect.any(String), - }, - }), + privateKeyEncrypted: expect.any(String), + publicKey: expect.any(String), }, }, headers, @@ -71,13 +66,8 @@ describe('Mail service tests', () => { encryptedKeystore: { userEmail: email, type: KeystoreType.RECOVERY, - encryptedKeys: expect.objectContaining({ - publicKeys: { eccPublicKeyBase64: expect.any(String), kyberPublicKeyBase64: expect.any(String) }, - privateKeys: { - eccPrivateKeyBase64: expect.any(String), - kyberPrivateKeyBase64: expect.any(String), - }, - }), + privateKeyEncrypted: expect.any(String), + publicKey: expect.any(String), }, }, headers, @@ -133,64 +123,55 @@ describe('Mail service tests', () => { }); describe('test public keys call methods', async () => { - const userA = { - email: 'user A email', - name: 'user A name', - }; - const userB = { - email: 'user B email', - name: 'user B name', - }; - const userC = { - email: 'user C email', - name: 'user C name', - }; + const userA = 'user A email'; + const userB = 'user B email'; + const userC = 'user C email'; const emailKeysA = await generateEmailKeys(); const emailKeysB = await generateEmailKeys(); const emailKeysC = await generateEmailKeys(); - const userAwithKeys = { ...userA, publicKeys: emailKeysA.publicKeys }; - const userBwithKeys = { ...userB, publicKeys: emailKeysB.publicKeys }; - const userCwithKeys = { ...userC, publicKeys: emailKeysC.publicKeys }; - - const emailKeysABase64 = await publicKeyToBase64(emailKeysA.publicKeys); - const emailKeysBBase64 = await publicKeyToBase64(emailKeysB.publicKeys); - const emailKeysCBase64 = await publicKeyToBase64(emailKeysC.publicKeys); + const publicKeyA = uint8ArrayToBase64(emailKeysA.publicKey); + const publicKeyB = uint8ArrayToBase64(emailKeysB.publicKey); + const publicKeyC = uint8ArrayToBase64(emailKeysC.publicKey); it('When user email public keys are requested, then it should successfully get them', async () => { const { client, headers } = clientAndHeadersWithToken(); const postCall = vi .spyOn(HttpClient.prototype, 'post') - .mockResolvedValue([{ publicKeys: emailKeysABase64, user: userA }]); - const result = await client.getUserWithPublicKeys(userA.email); + .mockResolvedValue([{ publicKey: publicKeyA, email: userA }]); + const result = await client.getUserWithPublicKeys(userA); expect(postCall.mock.calls[0]).toEqual([ '/users/public-keys', { - emails: [userA.email], + emails: [userA], }, headers, ]); - expect(result).toStrictEqual(userAwithKeys); + expect(result).toStrictEqual({ email: userA, publicHybridKey: emailKeysA.publicKey }); }); it('When public keys are requested for several users, then it should successfully get all of them', async () => { const { client, headers } = clientAndHeadersWithToken(); const postCall = vi.spyOn(HttpClient.prototype, 'post').mockResolvedValue([ - { publicKeys: emailKeysABase64, user: userA }, - { publicKeys: emailKeysBBase64, user: userB }, - { publicKeys: emailKeysCBase64, user: userC }, + { publicKey: publicKeyA, email: userA }, + { publicKey: publicKeyB, email: userB }, + { publicKey: publicKeyC, email: userC }, ]); - const result = await client.getSeveralUsersWithPublicKeys([userA.email, userB.email, userC.email]); + const result = await client.getSeveralUsersWithPublicKeys([userA, userB, userC]); expect(postCall.mock.calls[0]).toEqual([ '/users/public-keys', { - emails: [userA.email, userB.email, userC.email], + emails: [userA, userB, userC], }, headers, ]); - expect(result).toEqual([userAwithKeys, userBwithKeys, userCwithKeys]); + expect(result).toEqual([ + { email: userA, publicHybridKey: emailKeysA.publicKey }, + { email: userB, publicHybridKey: emailKeysB.publicKey }, + { email: userC, publicHybridKey: emailKeysC.publicKey }, + ]); }); }); @@ -206,19 +187,17 @@ describe('Mail service tests', () => { }; const uuid = generateUuid(); - const emailKeysA = await generateEmailKeys(); const emailKeysB = await generateEmailKeys(); - const emailKeysABase64 = await publicKeyToBase64(emailKeysA.publicKeys); - const emailKeysBBase64 = await publicKeyToBase64(emailKeysB.publicKeys); + const publicKeyB = uint8ArrayToBase64(emailKeysB.publicKey); const email: Email = { id: uuid, body: { text: 'Email text', + subject: 'Email subject', attachments: ['Email attachment'], }, params: { - subject: 'Email subject', createdAt: '2026-01-21T15:11:22.000Z', sender: userA, recipient: userB, @@ -234,9 +213,9 @@ describe('Mail service tests', () => { const { client, headers } = clientAndHeadersWithToken(); const postCall = vi .spyOn(HttpClient.prototype, 'post') - .mockResolvedValueOnce([{ publicKeys: emailKeysBBase64, user: userB }]) + .mockResolvedValueOnce([{ publicKey: publicKeyB, email: userB.email }]) .mockResolvedValueOnce({}); - await client.encryptAndSendEmail(email, emailKeysA.privateKeys, false); + await client.encryptAndSendEmail(email); expect(postCall.mock.calls[0]).toEqual([ '/users/public-keys', @@ -251,20 +230,19 @@ describe('Mail service tests', () => { { emails: [ { - encryptedKey: { - kyberCiphertext: expect.any(String), - encryptedKey: expect.any(String), - }, - enc: { + encEmailBody: { encText: expect.any(String), + encSubject: expect.any(String), encAttachments: [expect.any(String)], }, - recipientEmail: userB.email, - params: email.params, - id: email.id, - isSubjectEncrypted: false, + encryptedKey: { + encryptedKey: expect.any(String), + hybridCiphertext: expect.any(String), + encryptedForEmail: userB.email, + }, }, ], + params: email.params, }, headers, ]); @@ -273,7 +251,7 @@ describe('Mail service tests', () => { it('When user request password protect email, then it should successfully protect and send an email', async () => { const { client, headers } = clientAndHeadersWithToken(); const postCall = vi.spyOn(HttpClient.prototype, 'post').mockResolvedValue({}); - await client.passwordProtectAndSendEmail(email, pwd, false); + await client.passwordProtectAndSendEmail(email, pwd); expect(postCall.mock.calls[0]).toEqual([ '/emails', @@ -283,14 +261,13 @@ describe('Mail service tests', () => { encryptedKey: expect.any(String), salt: expect.any(String), }, - enc: { + encEmailBody: { encText: expect.any(String), + encSubject: expect.any(String), encAttachments: [expect.any(String)], }, - params: email.params, - id: email.id, - isSubjectEncrypted: false, }, + params: email.params, }, headers, ]); @@ -298,30 +275,19 @@ describe('Mail service tests', () => { it('When user request opening a password protect email, then it should successfully open it', async () => { const { client } = clientAndHeadersWithToken(); - const encEmail = await createPwdProtectedEmail(email, pwd, true); + const encEmail = await createPwdProtectedEmail(email.body, pwd); const result = await client.openPasswordProtectedEmail(encEmail, pwd); - expect(result).toEqual(email); + expect(result).toEqual(email.body); }); it('When user request decrypting an encrypted email, then it should successfully decrypt it', async () => { - const { client, headers } = clientAndHeadersWithToken(); - const recipient = { ...userB, publicKeys: emailKeysB.publicKeys }; - const postCall = vi - .spyOn(HttpClient.prototype, 'post') - .mockResolvedValue([{ publicKeys: emailKeysABase64, user: userA }]); - const encEmail = await encryptEmailHybrid(email, recipient, emailKeysA.privateKeys, true); - const result = await client.decryptEmail(encEmail, emailKeysB.privateKeys); - - expect(postCall.mock.calls[0]).toEqual([ - '/users/public-keys', - { - emails: [userA.email], - }, - headers, - ]); + const { client } = clientAndHeadersWithToken(); + const recipient = { email: userB.email, publicHybridKey: emailKeysB.publicKey }; + const encEmail = await encryptEmailHybrid(email.body, recipient); + const result = await client.decryptEmail(encEmail, emailKeysB.secretKey); - expect(result).toEqual(email); + expect(result).toEqual(email.body); }); }); }); diff --git a/yarn.lock b/yarn.lock index dfa0f6b..9e694e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -306,7 +306,7 @@ resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-2.1.1.tgz#c8c74fcda8c3d1f88797d0ecda24f9fc8b92b052" integrity sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw== -"@noble/curves@~2.0.0": +"@noble/curves@^2.0.1", "@noble/curves@~2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-2.0.1.tgz#64ba8bd5e8564a02942655602515646df1cdb3ad" integrity sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw== @@ -1270,12 +1270,13 @@ index-to-position@^1.1.0: resolved "https://registry.yarnpkg.com/index-to-position/-/index-to-position-1.2.0.tgz#c800eb34dacf4dbf96b9b06c7eb78d5f704138b4" integrity sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw== -internxt-crypto@0.0.14: - version "0.0.14" - resolved "https://registry.yarnpkg.com/internxt-crypto/-/internxt-crypto-0.0.14.tgz#1290b2a70190c23d25b83483de8200d9eafae00f" - integrity sha512-gIvqgou0r86kSk6x2t6pxAh9dJiob/sQ1Y3TdGnAF4Qq2RD++4Aq1b6NY2UqfUYV4vPhWsd2BkFS71jAyVrXpA== +internxt-crypto@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/internxt-crypto/-/internxt-crypto-1.0.0.tgz#6761bbb0602971aae1abcc3d56d197d0ec3ad896" + integrity sha512-NTlMq+AenZH8jUcjxErzeVnM/JvwYdzoS4tdjHY2Sl9SzJsu4nc+5/62aN41iMZ7GLPJ8egLgeO1cZQC4rTK8w== dependencies: "@noble/ciphers" "^2.1.1" + "@noble/curves" "^2.0.1" "@noble/hashes" "^2.0.1" "@noble/post-quantum" "^0.5.2" "@scure/bip39" "^2.0.1" From 4d590bdb2937125f30629e471592b465046cd47e Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Mon, 23 Mar 2026 17:57:21 +0100 Subject: [PATCH 2/2] split crypto and api, use 1.0.2 version --- package.json | 2 +- src/mail/create.ts | 122 ++++++++++++++++++++++++++++++++++++++++ src/mail/index.ts | 109 ++++++++--------------------------- test/mail/index.test.ts | 8 +-- yarn.lock | 8 +-- 5 files changed, 153 insertions(+), 96 deletions(-) create mode 100644 src/mail/create.ts diff --git a/package.json b/package.json index e30fa08..f8caada 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ }, "dependencies": { "axios": "1.13.6", - "internxt-crypto": "1.0.0" + "internxt-crypto": "1.0.2" }, "lint-staged": { "*.{js,jsx,tsx,ts}": [ diff --git a/src/mail/create.ts b/src/mail/create.ts new file mode 100644 index 0000000..c48f387 --- /dev/null +++ b/src/mail/create.ts @@ -0,0 +1,122 @@ +import { + EncryptedKeystore, + HybridEncryptedEmail, + PwdProtectedEmail, + HybridKeyPair, + Email, + EmailBody, + createEncryptionAndRecoveryKeystores, + openEncryptionKeystore, + RecipientWithPublicKey, + encryptEmailHybridForMultipleRecipients, + decryptEmailHybrid, + createPwdProtectedEmail, + decryptPwdProtectedEmail, + openRecoveryKeystore, + UTF8ToUint8, +} from 'internxt-crypto'; + +/** + * Creates recovery and encryption keystores for a user + * + * @param userEmail - The email of the user + * @param baseKey - The secret key of the user + * @returns The created keystores, keys and recovery codes for opening recovery keystore + */ +export async function createKeystores( + userEmail: string, + baseKey: Uint8Array, +): Promise<{ + encryptionKeystore: EncryptedKeystore; + recoveryKeystore: EncryptedKeystore; + recoveryCodes: string; + keys: HybridKeyPair; +}> { + return createEncryptionAndRecoveryKeystores(userEmail, baseKey); +} + +/** + * Opens user's keystore and returns the keys + * + * @param keystore - The encrypted keystore + * @param baseKey - The secret key of the user + * @returns The keys of the user + */ +export async function openKeystore(keystore: EncryptedKeystore, baseKey: Uint8Array): Promise { + return openEncryptionKeystore(keystore, baseKey); +} + +/** + * Recovery of user's keys using recovery keystore + * + * @param keystore - The recovery keystore + * @param recoveryCodes - The recovery codes of the user + * @returns The keys of the user + */ +export async function recoverKeys(keystore: EncryptedKeystore, recoveryCodes: string): Promise { + return openRecoveryKeystore(recoveryCodes, keystore); +} + +/** + * Encrypts the email + * + * @param email - The email to encrypt + * @param recipients - The recipients of the email + * @param aux - The optional auxilary data to encrypt together with the email (e.g. email sender) + * @returns The encrypted emails for each recipient + */ +export async function encryptEmail( + email: Email, + recipients: RecipientWithPublicKey[], + aux?: string, +): Promise { + const auxArray = aux ? UTF8ToUint8(aux) : new Uint8Array(); + return encryptEmailHybridForMultipleRecipients(email.body, recipients, auxArray); +} + +/** + * Password-protects the email + * + * @param email - The email to password-protect + * @param pwd - The password to protect the email with + * @param aux - The optional auxilary data to encrypt together with the email (e.g. email sender) + * @returns The password-protected email + */ +export async function passwordProtectAndSendEmail(email: Email, pwd: string, aux?: string): Promise { + const auxArray = aux ? UTF8ToUint8(aux) : new Uint8Array(); + return createPwdProtectedEmail(email.body, pwd, auxArray); +} + +/** + * Opens the password-protected email + * + * @param email - The password-protected email + * @param pwd - The shared password + * @param aux - The optional auxilary data that was encrypted together with the email (e.g. email sender) + * @returns The decrypted email body + */ +export async function openPasswordProtectedEmail( + email: PwdProtectedEmail, + pwd: string, + aux?: string, +): Promise { + const auxArray = aux ? UTF8ToUint8(aux) : new Uint8Array(); + return decryptPwdProtectedEmail(email, pwd, auxArray); +} + +/** + * Decrypt the email + * + * @param email - The encrypted email + * @param recipientPrivateKeys - The private keys of the email recipient + * @param aux - The optional auxilary data that was encrypted together with the email (e.g. email sender) + * @returns The decrypted email body + */ +export async function decryptEmail( + email: HybridEncryptedEmail, + recipientPrivateKeys: Uint8Array, + aux?: string, +): Promise { + const auxArray = aux ? UTF8ToUint8(aux) : new Uint8Array(); + return decryptEmailHybrid(email, recipientPrivateKeys, auxArray); +} diff --git a/src/mail/index.ts b/src/mail/index.ts index 2a796f4..fa4c202 100644 --- a/src/mail/index.ts +++ b/src/mail/index.ts @@ -8,21 +8,13 @@ import { PwdProtectedEmail, HybridKeyPair, Email, - EmailBody, - createEncryptionAndRecoveryKeystores, - encryptEmailHybrid, - openEncryptionKeystore, RecipientWithPublicKey, - encryptEmailHybridForMultipleRecipients, - decryptEmailHybrid, - createPwdProtectedEmail, - decryptPwdProtectedEmail, - openRecoveryKeystore, - UTF8ToUint8, base64ToUint8Array, EmailPublicParameters, } from 'internxt-crypto'; +import { createKeystores, encryptEmail, passwordProtectAndSendEmail, openKeystore, recoverKeys } from './create'; + export class Mail { private readonly client: HttpClient; private readonly appDetails: AppDetails; @@ -55,15 +47,15 @@ export class Mail { * * @param userEmail - The email of the user * @param baseKey - The secret key of the user - * @returns The recovery codes for opening recovery keystore + * @returns The recovery codes and keys of the user */ - async createAndUploadKeystores(userEmail: string, baseKey: Uint8Array): Promise { - const { encryptionKeystore, recoveryKeystore, recoveryCodes } = await createEncryptionAndRecoveryKeystores( - userEmail, - baseKey, - ); + async createAndUploadKeystores( + userEmail: string, + baseKey: Uint8Array, + ): Promise<{ recoveryCodes: string; keys: HybridKeyPair }> { + const { encryptionKeystore, recoveryKeystore, recoveryCodes, keys } = await createKeystores(userEmail, baseKey); await Promise.all([this.uploadKeystoreToServer(encryptionKeystore), this.uploadKeystoreToServer(recoveryKeystore)]); - return recoveryCodes; + return { recoveryCodes, keys }; } /** @@ -86,7 +78,7 @@ export class Mail { */ async getUserEmailKeys(userEmail: string, baseKey: Uint8Array): Promise { const keystore = await this.downloadKeystoreFromServer(userEmail, KeystoreType.ENCRYPTION); - return openEncryptionKeystore(keystore, baseKey); + return openKeystore(keystore, baseKey); } /** @@ -98,7 +90,7 @@ export class Mail { */ async recoverUserEmailKeys(userEmail: string, recoveryCodes: string): Promise { const keystore = await this.downloadKeystoreFromServer(userEmail, KeystoreType.RECOVERY); - return openRecoveryKeystore(recoveryCodes, keystore); + return recoverKeys(keystore, recoveryCodes); } /** @@ -144,61 +136,29 @@ export class Mail { } /** - * Sends the encrypted email to the server - * - * @param email - The encrypted email - * @param params - The public parameters of the email (sender, recipients, replyTo, etc.) - * @returns Server response - */ - async sendEncryptedEmail(email: HybridEncryptedEmail, params: EmailPublicParameters): Promise { - return this.client.post(`${this.apiUrl}/emails`, { emails: [email], params }, this.headers()); - } - - /** - * Encrypts email and sends it to the server - * - * @param email - The message to encrypt - * @param aux - An optional string that can be used as additional input for the encryption - * @returns Server response - */ - async encryptAndSendEmail(email: Email, aux?: string): Promise { - const recipient = await this.getUserWithPublicKeys(email.params.recipient.email); - const auxArray = aux ? UTF8ToUint8(aux) : new Uint8Array(); - const encEmail = await encryptEmailHybrid(email.body, recipient, auxArray); - return this.sendEncryptedEmail(encEmail, email.params); - } - - /** - * Sends the encrypted emails for multiple recipients to the server + * Sends the encrypted emails to the server * * @param emails - The encrypted emails - * @param params - The public parameters of the email (sender, recipients, replyTo, etc.) + * @param params - The public parameters of the email (sender, recipients, CCs, BCCs, etc.) * @returns Server response */ - async sendEncryptedEmailToMultipleRecipients( - emails: HybridEncryptedEmail[], - params: EmailPublicParameters, - ): Promise { + async sendEncryptedEmail(emails: HybridEncryptedEmail[], params: EmailPublicParameters): Promise { return this.client.post(`${this.apiUrl}/emails`, { emails, params }, this.headers()); } /** - * Encrypts emails for multiple recipients and sends emails to the server + * Encrypts and sends email(s) to the server * * @param email - The message to encrypt - * @param senderPrivateKeys - The private keys of the sender - * @param isSubjectEncrypted - Indicates if the subject field should be encrypted + * @param aux - The optional auxilary data to encrypt together with the email (e.g. email sender) * @returns Server response */ - async encryptAndSendEmailToMultipleRecipients(email: Email, aux?: string): Promise { - const recipientEmails = email.params.recipients - ? email.params.recipients.map((user) => user.email) - : [email.params.recipient.email]; - + async encryptAndSendEmail(email: Email, aux?: string): Promise { + const recipientEmails = email.params.recipients.map((user) => user.email); const recipients = await this.getSeveralUsersWithPublicKeys(recipientEmails); - const auxArray = aux ? UTF8ToUint8(aux) : new Uint8Array(); - const encEmails = await encryptEmailHybridForMultipleRecipients(email.body, recipients, auxArray); - return this.sendEncryptedEmailToMultipleRecipients(encEmails, email.params); + + const encEmails = await encryptEmail(email, recipients, aux); + return this.sendEncryptedEmail(encEmails, email.params); } /** @@ -216,37 +176,14 @@ export class Mail { * * @param email - The email * @param pwd - The password - * @param isSubjectEncrypted - Indicates if the subject field should be encrypted + * @param aux - The optional auxilary data to encrypt together with the email (e.g. email sender) * @returns Server response */ async passwordProtectAndSendEmail(email: Email, pwd: string, aux?: string): Promise { - const auxArray = aux ? UTF8ToUint8(aux) : new Uint8Array(); - const encEmail = await createPwdProtectedEmail(email.body, pwd, auxArray); + const encEmail = await passwordProtectAndSendEmail(email, pwd, aux); return this.sendPasswordProtectedEmail(encEmail, email.params); } - /** - * Opens the password-protected email - * - * @param email - The password-protected email - * @param pwd - The shared password - * @returns The decrypted email body - */ - async openPasswordProtectedEmail(email: PwdProtectedEmail, pwd: string): Promise { - return decryptPwdProtectedEmail(email, pwd); - } - - /** - * Decrypt the email - * - * @param email - The encrypted email - * @param recipientPrivateKeys - The private keys of the email recipient - * @returns The decrypted email body - */ - async decryptEmail(email: HybridEncryptedEmail, recipientPrivateKeys: Uint8Array): Promise { - return decryptEmailHybrid(email, recipientPrivateKeys); - } - /** * Returns the needed headers for the module requests * @private diff --git a/test/mail/index.test.ts b/test/mail/index.test.ts index a98b85e..77fa04f 100644 --- a/test/mail/index.test.ts +++ b/test/mail/index.test.ts @@ -14,6 +14,7 @@ import { uint8ArrayToBase64, } from 'internxt-crypto'; import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { decryptEmail, openPasswordProtectedEmail } from '../../src/mail/create'; describe('Mail service tests', () => { beforeEach(() => { @@ -200,7 +201,6 @@ describe('Mail service tests', () => { params: { createdAt: '2026-01-21T15:11:22.000Z', sender: userA, - recipient: userB, recipients: [userB], replyToEmailID: uuid, labels: ['inbox', 'test'], @@ -274,18 +274,16 @@ describe('Mail service tests', () => { }); it('When user request opening a password protect email, then it should successfully open it', async () => { - const { client } = clientAndHeadersWithToken(); const encEmail = await createPwdProtectedEmail(email.body, pwd); - const result = await client.openPasswordProtectedEmail(encEmail, pwd); + const result = await openPasswordProtectedEmail(encEmail, pwd); expect(result).toEqual(email.body); }); it('When user request decrypting an encrypted email, then it should successfully decrypt it', async () => { - const { client } = clientAndHeadersWithToken(); const recipient = { email: userB.email, publicHybridKey: emailKeysB.publicKey }; const encEmail = await encryptEmailHybrid(email.body, recipient); - const result = await client.decryptEmail(encEmail, emailKeysB.secretKey); + const result = await decryptEmail(encEmail, emailKeysB.secretKey); expect(result).toEqual(email.body); }); diff --git a/yarn.lock b/yarn.lock index 9e694e6..ce92077 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1270,10 +1270,10 @@ index-to-position@^1.1.0: resolved "https://registry.yarnpkg.com/index-to-position/-/index-to-position-1.2.0.tgz#c800eb34dacf4dbf96b9b06c7eb78d5f704138b4" integrity sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw== -internxt-crypto@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/internxt-crypto/-/internxt-crypto-1.0.0.tgz#6761bbb0602971aae1abcc3d56d197d0ec3ad896" - integrity sha512-NTlMq+AenZH8jUcjxErzeVnM/JvwYdzoS4tdjHY2Sl9SzJsu4nc+5/62aN41iMZ7GLPJ8egLgeO1cZQC4rTK8w== +internxt-crypto@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/internxt-crypto/-/internxt-crypto-1.0.2.tgz#983fe991dfbb00a453e93070bb34049a88a94707" + integrity sha512-F9PuXci0eU1wlgDwqEbGR7hVDNS0MX8VNh/W+pdpR4ZsEsjRDBrOD2g1DvViR2woCxPiu1AW9Wwekpw2YVKfnA== dependencies: "@noble/ciphers" "^2.1.1" "@noble/curves" "^2.0.1"