internxt-crypto 0.0.7-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +240 -0
- package/dist/__vite-browser-external-Dyvby5gX.js +2 -0
- package/dist/__vite-browser-external-Dyvby5gX.js.map +1 -0
- package/dist/__vite-browser-external-l0sNRNKZ.mjs +2 -0
- package/dist/__vite-browser-external-l0sNRNKZ.mjs.map +1 -0
- package/dist/index.js +2055 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +8558 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Internxt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Mail cryptographic library
|
|
2
|
+
|
|
3
|
+
[](https://sonarcloud.io/summary/new_code?id=internxt_crypto)
|
|
4
|
+
[](https://sonarcloud.io/summary/new_code?id=internxt_crypto)
|
|
5
|
+
[](https://sonarcloud.io/summary/new_code?id=internxt_crypto)
|
|
6
|
+
[](https://sonarcloud.io/summary/new_code?id=internxt_crypto)
|
|
7
|
+
[](https://sonarcloud.io/summary/new_code?id=internxt_crypto)
|
|
8
|
+
[](https://sonarcloud.io/summary/new_code?id=internxt_crypto)
|
|
9
|
+
[](https://sonarcloud.io/summary/new_code?id=internxt_crypto)
|
|
10
|
+
|
|
11
|
+
# Project Manteinance
|
|
12
|
+
|
|
13
|
+
We aim to have:
|
|
14
|
+
|
|
15
|
+
- An 'A' score on Maintainability Rating
|
|
16
|
+
- An 'A' score on Security Rating
|
|
17
|
+
- Less than 3% duplicated lines
|
|
18
|
+
- A 90% tests coverage
|
|
19
|
+
|
|
20
|
+
## Scripts
|
|
21
|
+
|
|
22
|
+
### `yarn run lint` (`yarn run lint:ts`)
|
|
23
|
+
|
|
24
|
+
- Runs .ts linter
|
|
25
|
+
|
|
26
|
+
### `yarn test` (`vitest run`)
|
|
27
|
+
|
|
28
|
+
- Runs unit tests with [Vitest](https://vitest.dev/)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### `yarn build`
|
|
32
|
+
|
|
33
|
+
Builds the app for production to the `build` folder.
|
|
34
|
+
|
|
35
|
+
## Project Structure
|
|
36
|
+
|
|
37
|
+
### Core Cryptography Modules
|
|
38
|
+
|
|
39
|
+
- **`asymmetric-crypto`** - Asymmetric elliptic curves cryptography (curve P-521) for generating keys and deriving a shared secret between two users
|
|
40
|
+
- **`symmetric-crypto`** - Symmetric encryption operations (AES-GCM) for data encryption and decryption
|
|
41
|
+
- **`post-quantum-crypto`** - Post-quantum cryptographic algorithms (MLKEMs) for generating keys and deriving a shared secret between two users
|
|
42
|
+
- **`hash`** - Cryptographic hashing functions (BLAKE3) for data integrity, commitments and secret extensions
|
|
43
|
+
|
|
44
|
+
### Key Management
|
|
45
|
+
|
|
46
|
+
- **`derive-key`** - Key derivation functions for deriving cryptographic keys from passwords (ARGON2) and base key (BLAKE3 in KDF mode)
|
|
47
|
+
- **`key-wrapper`** - Key wrapping and unwrapping functions for secure symmetric key storage and transport
|
|
48
|
+
- **`keystore-crypto`** - Keystore cryptographic operations for securing user's keys
|
|
49
|
+
- **`keystore-service`** - Keystore management service for communicating with the server
|
|
50
|
+
|
|
51
|
+
### Email Security
|
|
52
|
+
|
|
53
|
+
- **`email-crypto`** - End-to-end email encryption and decryption using hybrid cryptography and password-protection
|
|
54
|
+
- **`email-search`** - Email indexing on the client side to enable search while preserving privacy
|
|
55
|
+
- **`email-service`** - Email management service for communicating with the server
|
|
56
|
+
|
|
57
|
+
### Infrastructure
|
|
58
|
+
|
|
59
|
+
- **`storage-service`** - Abstraction layer for accessing Local Storage and Session Storage
|
|
60
|
+
- **`utils`** - Type converter functions and access to enviromental variables
|
|
61
|
+
- **`types`** - TypeScript type definitions for all library interfaces and data structures
|
|
62
|
+
- **`constants`** - Cryptographic constants, algorithm identifiers, and configuration values
|
|
63
|
+
|
|
64
|
+
## Usage Example
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import {
|
|
68
|
+
asymmetric,
|
|
69
|
+
symmetric,
|
|
70
|
+
utils,
|
|
71
|
+
emailCrypto,
|
|
72
|
+
pq,
|
|
73
|
+
keystoreService,
|
|
74
|
+
deriveKey,
|
|
75
|
+
hash,
|
|
76
|
+
SymmetricCiphertext
|
|
77
|
+
} from 'internxt-crypto';
|
|
78
|
+
|
|
79
|
+
// Asymmetric encryption
|
|
80
|
+
const keysAlice = await asymmetric.generateEccKeys();
|
|
81
|
+
const keysBob = await asymmetric.generateEccKeys();
|
|
82
|
+
const resultAlice = await asymmetric.deriveSecretKey(keysBob.publicKey, keysAlice.privateKey);
|
|
83
|
+
const resultBob = await asymmetric.deriveSecretKey(keysAlice.publicKey, keysBob.privateKey);
|
|
84
|
+
expect(resultAlice).toStrictEqual(resultBob);
|
|
85
|
+
|
|
86
|
+
// Symmetric encryption
|
|
87
|
+
const data = utils.UTF8ToUint8('Sensitive information to encrypt'); // convert to Uint8Array
|
|
88
|
+
const additionalData = 'Additional non-secret data';
|
|
89
|
+
const key = await symmetric.genSymmetricCryptoKey(); // CryptoKey
|
|
90
|
+
const ciphertext: SymmetricCiphertext = await symmetric.encryptSymmetrically(key, data, additionalData);
|
|
91
|
+
const plainText = await symmetric.decryptSymmetrically(encryptionKey, ciphertext, additionalData);
|
|
92
|
+
expect(data).toStrictEqual(plainText);
|
|
93
|
+
|
|
94
|
+
// Post qunatum cryptography
|
|
95
|
+
const keys = pq.generateKyberKeys();
|
|
96
|
+
const { cipherText, sharedSecret } = pq.encapsulateKyber(keys.publicKey);
|
|
97
|
+
const result = pq.decapsulateKyber(cipherText, keys.secretKey);
|
|
98
|
+
expect(result).toStrictEqual(sharedSecret);
|
|
99
|
+
|
|
100
|
+
// Hash
|
|
101
|
+
const result = await hash.hashData(['']);
|
|
102
|
+
const expectedResult = 'af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262';
|
|
103
|
+
expect(result).toStrictEqual(expectedResult);
|
|
104
|
+
|
|
105
|
+
// Key derivation
|
|
106
|
+
const context = 'BLAKE3 2019-12-27 16:29:52 test vectors context';
|
|
107
|
+
const baseKey = symmetric.genSymmetricKey(); // Uint8Array
|
|
108
|
+
const key = await deriveKey.deriveSymmetricCryptoKeyFromContext(context, baseKey);
|
|
109
|
+
expect(key).instanceOf(CryptoKey);
|
|
110
|
+
|
|
111
|
+
const password = 'your password';
|
|
112
|
+
const { keyHex, saltHex } = await deriveKey.getKeyFromPasswordHex(password);
|
|
113
|
+
const result = await deriveKey.verifyKeyFromPasswordHex(password, saltHex, keyHex);
|
|
114
|
+
expect(result).toBe(true);
|
|
115
|
+
|
|
116
|
+
// Hybrid email encryption
|
|
117
|
+
|
|
118
|
+
const emailBody: EmailBody = {
|
|
119
|
+
text: 'email text',
|
|
120
|
+
createdAt: '2025-06-14T08:11:22.000Z',
|
|
121
|
+
labels: ['label 1', 'label2'],
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const userAlice = {
|
|
125
|
+
email: 'alice email',
|
|
126
|
+
name: 'alice',
|
|
127
|
+
id: '1',
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const userBob = {
|
|
131
|
+
email: 'bob email',
|
|
132
|
+
name: 'bob',
|
|
133
|
+
id: '2',
|
|
134
|
+
};
|
|
135
|
+
const { privateKeys: alicePrivateKeys, publicKeys: alicePublicKeys } = await emailCrypto.generateEmailKeys();
|
|
136
|
+
const { privateKeys: bobPrivateKeys, publicKeys: bobPublicKeys } = await emailCrypto.generateEmailKeys();
|
|
137
|
+
|
|
138
|
+
const emailBody: EmailBody = {
|
|
139
|
+
text: 'email body',
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const emailParams: EmailPublicParameters = {
|
|
143
|
+
labels: ['label 1', 'label2'],
|
|
144
|
+
createdAt: '2025-06-14T08:11:22.000Z',
|
|
145
|
+
subject: 'email subject',
|
|
146
|
+
sender: userAlice,
|
|
147
|
+
recipient: userBob,
|
|
148
|
+
replyToEmailID: 2,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const email: Email = {
|
|
152
|
+
id: 'email id',
|
|
153
|
+
params: emailParams,
|
|
154
|
+
body: emailBody,
|
|
155
|
+
};
|
|
156
|
+
const encryptedEmail = await emailCrypto.encryptEmailHybrid(email, bobPublicKeys, alicePrivateKeys);
|
|
157
|
+
const decryptedEmail = await emailCrypto.decryptEmailHybrid(encryptedEmail, alicePublicKeys, bobPrivateKeys);
|
|
158
|
+
expect(decryptedEmail).toStrictEqual(email);
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
// password-protected email
|
|
162
|
+
const sharedSecret = 'secret shared between Alice and Bob';
|
|
163
|
+
const encryptedEmail = await emailCrypto.createPwdProtectedEmail(email, sharedSecret);
|
|
164
|
+
const decryptedEmail = await emailCrypto.decryptPwdProtectedEmail(encryptedEmail, sharedSecret);
|
|
165
|
+
expect(decryptedEmail).toStrictEqual(email);
|
|
166
|
+
|
|
167
|
+
// keystore
|
|
168
|
+
|
|
169
|
+
// For this to work, session storage must have UserID and baseKey
|
|
170
|
+
const { encryptionKeystore, recoveryKeystore, recoveryCodes } = await createEncryptionAndRecoveryKeystores();
|
|
171
|
+
const result_enc = await keystoreService.openEncryptionKeystore(encryptionKeystore);
|
|
172
|
+
const result_rec = await keystoreService.openRecoveryKeystore(recoveryCodes, recoveryKeystore);
|
|
173
|
+
expect(result_enc).toStrictEqual(result_rec);
|
|
174
|
+
|
|
175
|
+
// Email storage and search
|
|
176
|
+
|
|
177
|
+
// Between sessions emails are stored encrypted in IndexedDB. The encryption key is derived from user's baseKey
|
|
178
|
+
// During the session, all emails are decrypted and stored in the cache (up to 600 MB, if excides - we delete oldests emails)
|
|
179
|
+
// For search, we build a search index from cache, then use Flexsearch for the search.
|
|
180
|
+
// The search is doen separately for email content, subject, sender and recivers.
|
|
181
|
+
|
|
182
|
+
// Open IndexedDB database
|
|
183
|
+
const userID = 'user ID';
|
|
184
|
+
const db = await openDatabase(userID);
|
|
185
|
+
|
|
186
|
+
// Derive database key
|
|
187
|
+
const key = await deriveIndexKey(baseKey);
|
|
188
|
+
|
|
189
|
+
// Encrypt and store one or several emails
|
|
190
|
+
await encryptAndStoreEmail(email, key, db);
|
|
191
|
+
await encryptAndStoreManyEmail(emails, key, db);
|
|
192
|
+
|
|
193
|
+
// Delete given email by its ID
|
|
194
|
+
await deleteEmail(emailID, db);
|
|
195
|
+
|
|
196
|
+
// Delete oldests emails
|
|
197
|
+
const number = 5;
|
|
198
|
+
await deleteOldestEmails(number, db);
|
|
199
|
+
|
|
200
|
+
// Get all emails with or without sorting
|
|
201
|
+
const allEmails = await getAndDecryptAllEmails(key, db);
|
|
202
|
+
const newestFirst = await getAllEmailsSortedNewestFirst(db, key);
|
|
203
|
+
const oldestFirst = await getAllEmailsSortedOldestFirst(db, key);
|
|
204
|
+
|
|
205
|
+
// Get the number of stored emails
|
|
206
|
+
const count = await getEmailCount(db);
|
|
207
|
+
|
|
208
|
+
// Close IndexedDB database
|
|
209
|
+
closeDatabase(db);
|
|
210
|
+
|
|
211
|
+
// Delete IndexedDB database
|
|
212
|
+
await deleteDatabase(userID);
|
|
213
|
+
|
|
214
|
+
// Create email cache
|
|
215
|
+
const esCache = await createCacheFromDB(key, db);
|
|
216
|
+
|
|
217
|
+
// Add one or multiple emails to cache
|
|
218
|
+
const result = addEmailToCache(email, esCache);
|
|
219
|
+
expect(result.success).toBe(true);
|
|
220
|
+
|
|
221
|
+
const result = addEmailsToCache(emails, esCache);
|
|
222
|
+
expect(result.success).toBe(true);
|
|
223
|
+
|
|
224
|
+
// Get email from cache by its ID
|
|
225
|
+
const email = await getEmailFromCache(emailID, esCache);
|
|
226
|
+
|
|
227
|
+
// Delete email from cache by its ID
|
|
228
|
+
await deleteEmailFromCache(emailID, esCache);
|
|
229
|
+
|
|
230
|
+
// Create search index and search by query
|
|
231
|
+
const searchIndex = await buildSearchIndexFromCache(esCache);
|
|
232
|
+
const query = 'keywords to search';
|
|
233
|
+
const options = {
|
|
234
|
+
fields: ['subject'], // in which fields to search, all by deafult (subject, body, from, to)
|
|
235
|
+
limit: 5, // result limit, 50 by default
|
|
236
|
+
boost: { subject: 3, body: 1, from: 2, to: 2 }, // custom waights for matches in different email parts
|
|
237
|
+
};
|
|
238
|
+
const result: EmailSearchResult = await searchEmails(query, esCache, searchIndex);
|
|
239
|
+
|
|
240
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"__vite-browser-external-Dyvby5gX.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"__vite-browser-external-l0sNRNKZ.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|