wolfronix-sdk 1.2.0 → 1.3.1
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/README.md +69 -27
- package/dist/index.d.mts +27 -3
- package/dist/index.d.ts +27 -3
- package/dist/index.js +174 -15
- package/dist/index.mjs +174 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# wolfronix-sdk
|
|
2
2
|
|
|
3
3
|
Official JavaScript/TypeScript SDK for Wolfronix - Zero-knowledge encryption made simple.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/wolfronix-sdk)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
10
|
- 🔐 **Zero-Knowledge Encryption** - Keys generated client-side, never leave your device
|
|
11
11
|
- 🏢 **Enterprise Ready** - Seamless integration with your existing storage
|
|
12
|
-
|
|
13
12
|
- 🚀 **Simple API** - Encrypt files in 2 lines of code
|
|
14
13
|
- 📦 **TypeScript Native** - Full type definitions included
|
|
15
|
-
- 🌐 **Universal** - Works in Node.js and browsers
|
|
16
|
-
- ⚡ **Streaming** - Handle large files with progress tracking
|
|
14
|
+
- 🌐 **Universal** - Works in Node.js 16+ and modern browsers
|
|
17
15
|
- 🔄 **Auto Retry** - Built-in retry logic with exponential backoff
|
|
18
16
|
|
|
19
17
|
## Backend Integration (Enterprise Mode)
|
|
@@ -30,17 +28,17 @@ Wolfronix handles all encryption/decryption keys and logic; you only handle the
|
|
|
30
28
|
## Installation
|
|
31
29
|
|
|
32
30
|
```bash
|
|
33
|
-
npm install
|
|
31
|
+
npm install wolfronix-sdk
|
|
34
32
|
# or
|
|
35
|
-
yarn add
|
|
33
|
+
yarn add wolfronix-sdk
|
|
36
34
|
# or
|
|
37
|
-
pnpm add
|
|
35
|
+
pnpm add wolfronix-sdk
|
|
38
36
|
```
|
|
39
37
|
|
|
40
38
|
## Quick Start
|
|
41
39
|
|
|
42
40
|
```typescript
|
|
43
|
-
import Wolfronix from '
|
|
41
|
+
import Wolfronix from 'wolfronix-sdk';
|
|
44
42
|
|
|
45
43
|
// Initialize client
|
|
46
44
|
const wfx = new Wolfronix({
|
|
@@ -67,7 +65,7 @@ const decrypted = await wfx.decrypt(result.file_id);
|
|
|
67
65
|
### Browser (React, Vue, Angular, etc.)
|
|
68
66
|
|
|
69
67
|
```typescript
|
|
70
|
-
import Wolfronix from '
|
|
68
|
+
import Wolfronix from 'wolfronix-sdk';
|
|
71
69
|
|
|
72
70
|
const wfx = new Wolfronix('https://wolfronix-server:5002');
|
|
73
71
|
|
|
@@ -77,7 +75,6 @@ const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
77
75
|
if (!file) return;
|
|
78
76
|
|
|
79
77
|
try {
|
|
80
|
-
// Keys are automatically handled by the SDK
|
|
81
78
|
const { file_id } = await wfx.encrypt(file);
|
|
82
79
|
console.log('File encrypted with your private key:', file_id);
|
|
83
80
|
} catch (error) {
|
|
@@ -102,7 +99,7 @@ const handleDownload = async (fileId: string, filename: string) => {
|
|
|
102
99
|
### Node.js
|
|
103
100
|
|
|
104
101
|
```typescript
|
|
105
|
-
import Wolfronix from '
|
|
102
|
+
import Wolfronix from 'wolfronix-sdk';
|
|
106
103
|
import * as fs from 'fs';
|
|
107
104
|
|
|
108
105
|
const wfx = new Wolfronix({
|
|
@@ -129,26 +126,50 @@ async function main() {
|
|
|
129
126
|
fs.writeFileSync('decrypted.pdf', Buffer.from(decrypted));
|
|
130
127
|
}
|
|
131
128
|
|
|
129
|
+
main();
|
|
130
|
+
// Decrypt and save
|
|
131
|
+
const decrypted = await wfx.decryptToBuffer(file_id);
|
|
132
|
+
fs.writeFileSync('decrypted.pdf', Buffer.from(decrypted));
|
|
133
|
+
}
|
|
134
|
+
|
|
132
135
|
main();
|
|
133
136
|
```
|
|
134
137
|
|
|
135
|
-
###
|
|
138
|
+
### 💬 E2E Encrypted Chat Integration
|
|
139
|
+
|
|
140
|
+
Turn any chat app into a secure, end-to-end encrypted messenger in minutes.
|
|
136
141
|
|
|
142
|
+
**Sender (Alice):**
|
|
137
143
|
```typescript
|
|
138
|
-
|
|
144
|
+
// 1. Get Bob's Public Key & Encrypt Message
|
|
145
|
+
const securePacket = await wfx.encryptMessage("Secret details at 5 PM", "bob_user_id");
|
|
139
146
|
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
// 2. Send 'securePacket' string via your normal chat API (Socket.io, Firebase, etc.)
|
|
148
|
+
chatSocket.emit('message', {
|
|
149
|
+
to: 'bob',
|
|
150
|
+
text: securePacket // Valid JSON string
|
|
144
151
|
});
|
|
152
|
+
```
|
|
145
153
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
154
|
+
**Recipient (Bob):**
|
|
155
|
+
```typescript
|
|
156
|
+
// 1. Receive message from chat server
|
|
157
|
+
chatSocket.on('message', async (msg) => {
|
|
158
|
+
try {
|
|
159
|
+
// 2. Decrypt locally with Bob's Private Key
|
|
160
|
+
const plainText = await wfx.decryptMessage(msg.text);
|
|
161
|
+
console.log("Decrypted:", plainText);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.error("Could not decrypt message");
|
|
164
|
+
}
|
|
149
165
|
});
|
|
150
166
|
```
|
|
151
167
|
|
|
168
|
+
**Features:**
|
|
169
|
+
- **Hybrid Encryption:** Uses AES-256 for messages + RSA-2048 for key exchange (Fast & Secure).
|
|
170
|
+
- **Zero-Knowledge:** Your chat server only sees encrypted packets.
|
|
171
|
+
- **Universal:** Works with any backend (Socket.io, Firebase, PostgreSQL, etc).
|
|
172
|
+
|
|
152
173
|
## API Reference
|
|
153
174
|
|
|
154
175
|
### Constructor
|
|
@@ -181,13 +202,19 @@ new Wolfronix(config: WolfronixConfig | string)
|
|
|
181
202
|
| Method | Description |
|
|
182
203
|
|--------|-------------|
|
|
183
204
|
| `encrypt(file, filename?)` | Encrypt and store file |
|
|
184
|
-
| `encryptStream(file, onProgress?)` | Encrypt large file with progress |
|
|
185
205
|
| `decrypt(fileId)` | Decrypt file (returns Blob) |
|
|
186
206
|
| `decryptToBuffer(fileId)` | Decrypt file (returns ArrayBuffer) |
|
|
187
|
-
| `decryptStream(fileId, onProgress?)` | Decrypt large file with progress |
|
|
188
207
|
| `listFiles()` | List user's encrypted files |
|
|
189
208
|
| `deleteFile(fileId)` | Delete encrypted file |
|
|
190
209
|
|
|
210
|
+
### E2E Chat Encryption
|
|
211
|
+
|
|
212
|
+
| Method | Description |
|
|
213
|
+
|--------|-------------|
|
|
214
|
+
| `getPublicKey(userId)` | Fetch a user's RSA public key |
|
|
215
|
+
| `encryptMessage(text, recipientId)` | Encrypt text for a recipient (returns packet string) |
|
|
216
|
+
| `decryptMessage(packetString)` | Decrypt a received message packet |
|
|
217
|
+
|
|
191
218
|
### Utility
|
|
192
219
|
|
|
193
220
|
| Method | Description |
|
|
@@ -207,7 +234,7 @@ import Wolfronix, {
|
|
|
207
234
|
PermissionDeniedError,
|
|
208
235
|
NetworkError,
|
|
209
236
|
ValidationError
|
|
210
|
-
} from '
|
|
237
|
+
} from 'wolfronix-sdk';
|
|
211
238
|
|
|
212
239
|
try {
|
|
213
240
|
await wfx.encrypt(file);
|
|
@@ -246,7 +273,7 @@ import Wolfronix, {
|
|
|
246
273
|
FileInfo,
|
|
247
274
|
ListFilesResponse,
|
|
248
275
|
MetricsResponse
|
|
249
|
-
} from '
|
|
276
|
+
} from 'wolfronix-sdk';
|
|
250
277
|
|
|
251
278
|
// All methods are fully typed
|
|
252
279
|
const config: WolfronixConfig = {
|
|
@@ -263,7 +290,7 @@ const response: EncryptResponse = await wfx.encrypt(file);
|
|
|
263
290
|
```typescript
|
|
264
291
|
// useWolfronix.ts
|
|
265
292
|
import { useState, useCallback, useMemo } from 'react';
|
|
266
|
-
import Wolfronix, { FileInfo as WolfronixFile } from '
|
|
293
|
+
import Wolfronix, { FileInfo as WolfronixFile } from 'wolfronix-sdk';
|
|
267
294
|
|
|
268
295
|
export function useWolfronix(baseUrl: string, clientId?: string) {
|
|
269
296
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -351,10 +378,25 @@ function FileManager() {
|
|
|
351
378
|
}
|
|
352
379
|
```
|
|
353
380
|
|
|
381
|
+
## Real-World Use Cases
|
|
382
|
+
|
|
383
|
+
Wolfronix can be integrated into **any application** that handles sensitive data:
|
|
384
|
+
|
|
385
|
+
| Industry | Application | How Wolfronix Helps |
|
|
386
|
+
|----------|------------|---------------------|
|
|
387
|
+
| 🏥 **Healthcare** | Patient records, lab reports | HIPAA-compliant encryption at rest |
|
|
388
|
+
| 🏦 **Finance** | Invoices, tax docs, receipts | End-to-end encrypted banking documents |
|
|
389
|
+
| ⚖️ **Legal** | Contracts, case files | Zero-knowledge confidential storage |
|
|
390
|
+
| ☁️ **Cloud Storage** | Drive/Dropbox alternatives | Encrypted file vault with user-owned keys |
|
|
391
|
+
| 🏢 **Enterprise** | HR records, internal docs | Per-employee encryption isolation |
|
|
392
|
+
| 🎓 **Education** | Exam papers, student data | Tamper-proof academic records |
|
|
393
|
+
| 💬 **Messaging** | File attachments | Encrypted file sharing in chat apps |
|
|
394
|
+
| 🛒 **E-commerce** | Order docs, payment receipts | PCI-compliant document storage |
|
|
395
|
+
|
|
354
396
|
## Requirements
|
|
355
397
|
|
|
356
398
|
- Node.js 16+ (for Node.js usage)
|
|
357
|
-
- Modern browser with
|
|
399
|
+
- Modern browser with Web Crypto API support
|
|
358
400
|
|
|
359
401
|
## License
|
|
360
402
|
|
package/dist/index.d.mts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Zero-knowledge encryption made simple
|
|
4
4
|
*
|
|
5
5
|
* @package @wolfronix/sdk
|
|
6
|
-
* @version 1.
|
|
6
|
+
* @version 1.3.0
|
|
7
7
|
*/
|
|
8
8
|
interface WolfronixConfig {
|
|
9
9
|
/** Wolfronix server base URL */
|
|
@@ -25,7 +25,7 @@ interface AuthResponse {
|
|
|
25
25
|
}
|
|
26
26
|
interface EncryptResponse {
|
|
27
27
|
status: string;
|
|
28
|
-
file_id:
|
|
28
|
+
file_id: string;
|
|
29
29
|
file_size: number;
|
|
30
30
|
enc_time_ms: number;
|
|
31
31
|
}
|
|
@@ -51,6 +51,11 @@ interface MetricsResponse {
|
|
|
51
51
|
total_bytes_encrypted: number;
|
|
52
52
|
total_bytes_decrypted: number;
|
|
53
53
|
}
|
|
54
|
+
interface EncryptMessagePacket {
|
|
55
|
+
key: string;
|
|
56
|
+
iv: string;
|
|
57
|
+
msg: string;
|
|
58
|
+
}
|
|
54
59
|
declare class WolfronixError extends Error {
|
|
55
60
|
readonly code: string;
|
|
56
61
|
readonly statusCode?: number;
|
|
@@ -188,6 +193,25 @@ declare class Wolfronix {
|
|
|
188
193
|
* ```
|
|
189
194
|
*/
|
|
190
195
|
deleteFile(fileId: string): Promise<DeleteResponse>;
|
|
196
|
+
/**
|
|
197
|
+
* Get another user's public key (for E2E encryption)
|
|
198
|
+
* @param userId The ID of the recipient
|
|
199
|
+
*/
|
|
200
|
+
getPublicKey(userId: string): Promise<string>;
|
|
201
|
+
/**
|
|
202
|
+
* Encrypt a short text message for a recipient (Hybrid Encryption: RSA + AES)
|
|
203
|
+
* Returns a secure JSON string (packet) to send via chat
|
|
204
|
+
*
|
|
205
|
+
* @param text The plain text message
|
|
206
|
+
* @param recipientId The recipient's user ID
|
|
207
|
+
*/
|
|
208
|
+
encryptMessage(text: string, recipientId: string): Promise<string>;
|
|
209
|
+
/**
|
|
210
|
+
* Decrypt a message packet received from chat
|
|
211
|
+
*
|
|
212
|
+
* @param packetJson The secure JSON string packet
|
|
213
|
+
*/
|
|
214
|
+
decryptMessage(packetJson: string): Promise<string>;
|
|
191
215
|
/**
|
|
192
216
|
* Get encryption/decryption metrics
|
|
193
217
|
*
|
|
@@ -218,4 +242,4 @@ declare class Wolfronix {
|
|
|
218
242
|
*/
|
|
219
243
|
declare function createClient(config: WolfronixConfig | string): Wolfronix;
|
|
220
244
|
|
|
221
|
-
export { type AuthResponse, AuthenticationError, type DeleteResponse, type EncryptResponse, type FileInfo, FileNotFoundError, type ListFilesResponse, type MetricsResponse, NetworkError, PermissionDeniedError, ValidationError, Wolfronix, type WolfronixConfig, WolfronixError, createClient, Wolfronix as default };
|
|
245
|
+
export { type AuthResponse, AuthenticationError, type DeleteResponse, type EncryptMessagePacket, type EncryptResponse, type FileInfo, FileNotFoundError, type ListFilesResponse, type MetricsResponse, NetworkError, PermissionDeniedError, ValidationError, Wolfronix, type WolfronixConfig, WolfronixError, createClient, Wolfronix as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Zero-knowledge encryption made simple
|
|
4
4
|
*
|
|
5
5
|
* @package @wolfronix/sdk
|
|
6
|
-
* @version 1.
|
|
6
|
+
* @version 1.3.0
|
|
7
7
|
*/
|
|
8
8
|
interface WolfronixConfig {
|
|
9
9
|
/** Wolfronix server base URL */
|
|
@@ -25,7 +25,7 @@ interface AuthResponse {
|
|
|
25
25
|
}
|
|
26
26
|
interface EncryptResponse {
|
|
27
27
|
status: string;
|
|
28
|
-
file_id:
|
|
28
|
+
file_id: string;
|
|
29
29
|
file_size: number;
|
|
30
30
|
enc_time_ms: number;
|
|
31
31
|
}
|
|
@@ -51,6 +51,11 @@ interface MetricsResponse {
|
|
|
51
51
|
total_bytes_encrypted: number;
|
|
52
52
|
total_bytes_decrypted: number;
|
|
53
53
|
}
|
|
54
|
+
interface EncryptMessagePacket {
|
|
55
|
+
key: string;
|
|
56
|
+
iv: string;
|
|
57
|
+
msg: string;
|
|
58
|
+
}
|
|
54
59
|
declare class WolfronixError extends Error {
|
|
55
60
|
readonly code: string;
|
|
56
61
|
readonly statusCode?: number;
|
|
@@ -188,6 +193,25 @@ declare class Wolfronix {
|
|
|
188
193
|
* ```
|
|
189
194
|
*/
|
|
190
195
|
deleteFile(fileId: string): Promise<DeleteResponse>;
|
|
196
|
+
/**
|
|
197
|
+
* Get another user's public key (for E2E encryption)
|
|
198
|
+
* @param userId The ID of the recipient
|
|
199
|
+
*/
|
|
200
|
+
getPublicKey(userId: string): Promise<string>;
|
|
201
|
+
/**
|
|
202
|
+
* Encrypt a short text message for a recipient (Hybrid Encryption: RSA + AES)
|
|
203
|
+
* Returns a secure JSON string (packet) to send via chat
|
|
204
|
+
*
|
|
205
|
+
* @param text The plain text message
|
|
206
|
+
* @param recipientId The recipient's user ID
|
|
207
|
+
*/
|
|
208
|
+
encryptMessage(text: string, recipientId: string): Promise<string>;
|
|
209
|
+
/**
|
|
210
|
+
* Decrypt a message packet received from chat
|
|
211
|
+
*
|
|
212
|
+
* @param packetJson The secure JSON string packet
|
|
213
|
+
*/
|
|
214
|
+
decryptMessage(packetJson: string): Promise<string>;
|
|
191
215
|
/**
|
|
192
216
|
* Get encryption/decryption metrics
|
|
193
217
|
*
|
|
@@ -218,4 +242,4 @@ declare class Wolfronix {
|
|
|
218
242
|
*/
|
|
219
243
|
declare function createClient(config: WolfronixConfig | string): Wolfronix;
|
|
220
244
|
|
|
221
|
-
export { type AuthResponse, AuthenticationError, type DeleteResponse, type EncryptResponse, type FileInfo, FileNotFoundError, type ListFilesResponse, type MetricsResponse, NetworkError, PermissionDeniedError, ValidationError, Wolfronix, type WolfronixConfig, WolfronixError, createClient, Wolfronix as default };
|
|
245
|
+
export { type AuthResponse, AuthenticationError, type DeleteResponse, type EncryptMessagePacket, type EncryptResponse, type FileInfo, FileNotFoundError, type ListFilesResponse, type MetricsResponse, NetworkError, PermissionDeniedError, ValidationError, Wolfronix, type WolfronixConfig, WolfronixError, createClient, Wolfronix as default };
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,14 @@ __export(index_exports, {
|
|
|
33
33
|
module.exports = __toCommonJS(index_exports);
|
|
34
34
|
|
|
35
35
|
// src/crypto.ts
|
|
36
|
+
var getCrypto = () => {
|
|
37
|
+
if (typeof globalThis.crypto !== "undefined") {
|
|
38
|
+
return globalThis.crypto;
|
|
39
|
+
}
|
|
40
|
+
throw new Error(
|
|
41
|
+
"Web Crypto API not available. Requires a modern browser or Node.js 16+."
|
|
42
|
+
);
|
|
43
|
+
};
|
|
36
44
|
var RSA_ALG = {
|
|
37
45
|
name: "RSA-OAEP",
|
|
38
46
|
modulusLength: 2048,
|
|
@@ -40,9 +48,10 @@ var RSA_ALG = {
|
|
|
40
48
|
hash: "SHA-256"
|
|
41
49
|
};
|
|
42
50
|
var WRAP_ALG = "AES-GCM";
|
|
51
|
+
var SESSION_ALG = "AES-GCM";
|
|
43
52
|
var PBKDF2_ITERATIONS = 1e5;
|
|
44
53
|
async function generateKeyPair() {
|
|
45
|
-
return await
|
|
54
|
+
return await getCrypto().subtle.generateKey(
|
|
46
55
|
RSA_ALG,
|
|
47
56
|
true,
|
|
48
57
|
// extractable
|
|
@@ -51,7 +60,7 @@ async function generateKeyPair() {
|
|
|
51
60
|
}
|
|
52
61
|
async function exportKeyToPEM(key, type) {
|
|
53
62
|
const format = type === "public" ? "spki" : "pkcs8";
|
|
54
|
-
const exported = await
|
|
63
|
+
const exported = await getCrypto().subtle.exportKey(format, key);
|
|
55
64
|
const exportedAsBase64 = arrayBufferToBase64(exported);
|
|
56
65
|
const pemHeader = type === "public" ? "-----BEGIN PUBLIC KEY-----" : "-----BEGIN PRIVATE KEY-----";
|
|
57
66
|
const pemFooter = type === "public" ? "-----END PUBLIC KEY-----" : "-----END PRIVATE KEY-----";
|
|
@@ -66,7 +75,7 @@ async function importKeyFromPEM(pem, type) {
|
|
|
66
75
|
const binaryDer = base64ToArrayBuffer(pemContents);
|
|
67
76
|
const format = type === "public" ? "spki" : "pkcs8";
|
|
68
77
|
const usage = type === "public" ? ["encrypt", "wrapKey"] : ["decrypt", "unwrapKey"];
|
|
69
|
-
return await
|
|
78
|
+
return await getCrypto().subtle.importKey(
|
|
70
79
|
format,
|
|
71
80
|
binaryDer,
|
|
72
81
|
RSA_ALG,
|
|
@@ -76,7 +85,7 @@ async function importKeyFromPEM(pem, type) {
|
|
|
76
85
|
}
|
|
77
86
|
async function deriveWrappingKey(password, saltHex) {
|
|
78
87
|
const enc = new TextEncoder();
|
|
79
|
-
const passwordKey = await
|
|
88
|
+
const passwordKey = await getCrypto().subtle.importKey(
|
|
80
89
|
"raw",
|
|
81
90
|
enc.encode(password),
|
|
82
91
|
"PBKDF2",
|
|
@@ -84,7 +93,7 @@ async function deriveWrappingKey(password, saltHex) {
|
|
|
84
93
|
["deriveKey"]
|
|
85
94
|
);
|
|
86
95
|
const salt = hexToArrayBuffer(saltHex);
|
|
87
|
-
return await
|
|
96
|
+
return await getCrypto().subtle.deriveKey(
|
|
88
97
|
{
|
|
89
98
|
name: "PBKDF2",
|
|
90
99
|
salt,
|
|
@@ -98,12 +107,13 @@ async function deriveWrappingKey(password, saltHex) {
|
|
|
98
107
|
);
|
|
99
108
|
}
|
|
100
109
|
async function wrapPrivateKey(privateKey, password) {
|
|
101
|
-
const
|
|
110
|
+
const crypto = getCrypto();
|
|
111
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
102
112
|
const saltHex = arrayBufferToHex(salt.buffer);
|
|
103
113
|
const wrappingKey = await deriveWrappingKey(password, saltHex);
|
|
104
|
-
const exportedKey = await
|
|
105
|
-
const iv =
|
|
106
|
-
const encryptedContent = await
|
|
114
|
+
const exportedKey = await crypto.subtle.exportKey("pkcs8", privateKey);
|
|
115
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
116
|
+
const encryptedContent = await crypto.subtle.encrypt(
|
|
107
117
|
{
|
|
108
118
|
name: WRAP_ALG,
|
|
109
119
|
iv
|
|
@@ -125,7 +135,7 @@ async function unwrapPrivateKey(encryptedKeyBase64, password, saltHex) {
|
|
|
125
135
|
const iv = combinedArray.slice(0, 12);
|
|
126
136
|
const data = combinedArray.slice(12);
|
|
127
137
|
const wrappingKey = await deriveWrappingKey(password, saltHex);
|
|
128
|
-
const decryptedKeyData = await
|
|
138
|
+
const decryptedKeyData = await getCrypto().subtle.decrypt(
|
|
129
139
|
{
|
|
130
140
|
name: WRAP_ALG,
|
|
131
141
|
iv
|
|
@@ -133,7 +143,7 @@ async function unwrapPrivateKey(encryptedKeyBase64, password, saltHex) {
|
|
|
133
143
|
wrappingKey,
|
|
134
144
|
data
|
|
135
145
|
);
|
|
136
|
-
return await
|
|
146
|
+
return await getCrypto().subtle.importKey(
|
|
137
147
|
"pkcs8",
|
|
138
148
|
decryptedKeyData,
|
|
139
149
|
RSA_ALG,
|
|
@@ -141,17 +151,98 @@ async function unwrapPrivateKey(encryptedKeyBase64, password, saltHex) {
|
|
|
141
151
|
["decrypt", "unwrapKey"]
|
|
142
152
|
);
|
|
143
153
|
}
|
|
154
|
+
async function generateSessionKey() {
|
|
155
|
+
return await getCrypto().subtle.generateKey(
|
|
156
|
+
{
|
|
157
|
+
name: SESSION_ALG,
|
|
158
|
+
length: 256
|
|
159
|
+
},
|
|
160
|
+
true,
|
|
161
|
+
["encrypt", "decrypt"]
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
async function encryptData(data, key) {
|
|
165
|
+
const enc = new TextEncoder();
|
|
166
|
+
const encodedData = enc.encode(data);
|
|
167
|
+
const iv = getCrypto().getRandomValues(new Uint8Array(12));
|
|
168
|
+
const encryptedContent = await getCrypto().subtle.encrypt(
|
|
169
|
+
{
|
|
170
|
+
name: SESSION_ALG,
|
|
171
|
+
iv
|
|
172
|
+
},
|
|
173
|
+
key,
|
|
174
|
+
encodedData
|
|
175
|
+
);
|
|
176
|
+
return {
|
|
177
|
+
encrypted: arrayBufferToBase64(encryptedContent),
|
|
178
|
+
iv: arrayBufferToBase64(iv.buffer)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
async function decryptData(encryptedBase64, ivBase64, key) {
|
|
182
|
+
const encryptedData = base64ToArrayBuffer(encryptedBase64);
|
|
183
|
+
const iv = base64ToArrayBuffer(ivBase64);
|
|
184
|
+
const decryptedContent = await getCrypto().subtle.decrypt(
|
|
185
|
+
{
|
|
186
|
+
name: SESSION_ALG,
|
|
187
|
+
iv
|
|
188
|
+
},
|
|
189
|
+
key,
|
|
190
|
+
encryptedData
|
|
191
|
+
);
|
|
192
|
+
const dec = new TextDecoder();
|
|
193
|
+
return dec.decode(decryptedContent);
|
|
194
|
+
}
|
|
195
|
+
async function rsaEncrypt(data, publicKey) {
|
|
196
|
+
const encrypted = await getCrypto().subtle.encrypt(
|
|
197
|
+
{
|
|
198
|
+
name: "RSA-OAEP"
|
|
199
|
+
},
|
|
200
|
+
publicKey,
|
|
201
|
+
data
|
|
202
|
+
);
|
|
203
|
+
return arrayBufferToBase64(encrypted);
|
|
204
|
+
}
|
|
205
|
+
async function rsaDecrypt(encryptedBase64, privateKey) {
|
|
206
|
+
const data = base64ToArrayBuffer(encryptedBase64);
|
|
207
|
+
const decrypted = await getCrypto().subtle.decrypt(
|
|
208
|
+
{
|
|
209
|
+
name: "RSA-OAEP"
|
|
210
|
+
},
|
|
211
|
+
privateKey,
|
|
212
|
+
data
|
|
213
|
+
);
|
|
214
|
+
return decrypted;
|
|
215
|
+
}
|
|
216
|
+
async function exportSessionKey(key) {
|
|
217
|
+
return await getCrypto().subtle.exportKey("raw", key);
|
|
218
|
+
}
|
|
219
|
+
async function importSessionKey(raw) {
|
|
220
|
+
return await getCrypto().subtle.importKey(
|
|
221
|
+
"raw",
|
|
222
|
+
raw,
|
|
223
|
+
SESSION_ALG,
|
|
224
|
+
true,
|
|
225
|
+
["encrypt", "decrypt"]
|
|
226
|
+
);
|
|
227
|
+
}
|
|
144
228
|
function arrayBufferToBase64(buffer) {
|
|
145
|
-
let binary = "";
|
|
146
229
|
const bytes = new Uint8Array(buffer);
|
|
230
|
+
if (typeof Buffer !== "undefined") {
|
|
231
|
+
return Buffer.from(bytes).toString("base64");
|
|
232
|
+
}
|
|
233
|
+
let binary = "";
|
|
147
234
|
const len = bytes.byteLength;
|
|
148
235
|
for (let i = 0; i < len; i++) {
|
|
149
236
|
binary += String.fromCharCode(bytes[i]);
|
|
150
237
|
}
|
|
151
|
-
return
|
|
238
|
+
return btoa(binary);
|
|
152
239
|
}
|
|
153
240
|
function base64ToArrayBuffer(base64) {
|
|
154
|
-
|
|
241
|
+
if (typeof Buffer !== "undefined") {
|
|
242
|
+
const buf = Buffer.from(base64, "base64");
|
|
243
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
244
|
+
}
|
|
245
|
+
const binary_string = atob(base64);
|
|
155
246
|
const len = binary_string.length;
|
|
156
247
|
const bytes = new Uint8Array(len);
|
|
157
248
|
for (let i = 0; i < len; i++) {
|
|
@@ -492,9 +583,15 @@ var Wolfronix = class {
|
|
|
492
583
|
throw new Error("Public key not available. Is user logged in?");
|
|
493
584
|
}
|
|
494
585
|
formData.append("client_public_key", this.publicKeyPEM);
|
|
495
|
-
|
|
586
|
+
const response = await this.request("POST", "/api/v1/encrypt", {
|
|
496
587
|
formData
|
|
497
588
|
});
|
|
589
|
+
return {
|
|
590
|
+
status: response.status,
|
|
591
|
+
file_id: String(response.file_id),
|
|
592
|
+
file_size: response.file_size,
|
|
593
|
+
enc_time_ms: response.enc_time_ms
|
|
594
|
+
};
|
|
498
595
|
}
|
|
499
596
|
/**
|
|
500
597
|
* Decrypt and retrieve a file
|
|
@@ -585,6 +682,68 @@ var Wolfronix = class {
|
|
|
585
682
|
}
|
|
586
683
|
return this.request("DELETE", `/api/v1/files/${fileId}`);
|
|
587
684
|
}
|
|
685
|
+
// ============================================================================
|
|
686
|
+
// E2E Chat Encryption Methods
|
|
687
|
+
// ============================================================================
|
|
688
|
+
/**
|
|
689
|
+
* Get another user's public key (for E2E encryption)
|
|
690
|
+
* @param userId The ID of the recipient
|
|
691
|
+
*/
|
|
692
|
+
async getPublicKey(userId) {
|
|
693
|
+
this.ensureAuthenticated();
|
|
694
|
+
const result = await this.request("GET", `/api/v1/keys/${userId}`);
|
|
695
|
+
return result.public_key;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Encrypt a short text message for a recipient (Hybrid Encryption: RSA + AES)
|
|
699
|
+
* Returns a secure JSON string (packet) to send via chat
|
|
700
|
+
*
|
|
701
|
+
* @param text The plain text message
|
|
702
|
+
* @param recipientId The recipient's user ID
|
|
703
|
+
*/
|
|
704
|
+
async encryptMessage(text, recipientId) {
|
|
705
|
+
this.ensureAuthenticated();
|
|
706
|
+
const recipientPubKeyPEM = await this.getPublicKey(recipientId);
|
|
707
|
+
const recipientPubKey = await importKeyFromPEM(recipientPubKeyPEM, "public");
|
|
708
|
+
const sessionKey = await generateSessionKey();
|
|
709
|
+
const { encrypted: encryptedMsg, iv } = await encryptData(text, sessionKey);
|
|
710
|
+
const rawSessionKey = await exportSessionKey(sessionKey);
|
|
711
|
+
const encryptedSessionKey = await rsaEncrypt(rawSessionKey, recipientPubKey);
|
|
712
|
+
const packet = {
|
|
713
|
+
key: encryptedSessionKey,
|
|
714
|
+
iv,
|
|
715
|
+
msg: encryptedMsg
|
|
716
|
+
};
|
|
717
|
+
return JSON.stringify(packet);
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Decrypt a message packet received from chat
|
|
721
|
+
*
|
|
722
|
+
* @param packetJson The secure JSON string packet
|
|
723
|
+
*/
|
|
724
|
+
async decryptMessage(packetJson) {
|
|
725
|
+
this.ensureAuthenticated();
|
|
726
|
+
if (!this.privateKey) {
|
|
727
|
+
throw new Error("Private key not available. Is user logged in?");
|
|
728
|
+
}
|
|
729
|
+
let packet;
|
|
730
|
+
try {
|
|
731
|
+
packet = JSON.parse(packetJson);
|
|
732
|
+
} catch (e) {
|
|
733
|
+
throw new ValidationError("Invalid message packet format");
|
|
734
|
+
}
|
|
735
|
+
if (!packet.key || !packet.iv || !packet.msg) {
|
|
736
|
+
throw new ValidationError("Invalid message packet structure");
|
|
737
|
+
}
|
|
738
|
+
try {
|
|
739
|
+
const rawSessionKey = await rsaDecrypt(packet.key, this.privateKey);
|
|
740
|
+
const sessionKey = await importSessionKey(rawSessionKey);
|
|
741
|
+
const plainText = await decryptData(packet.msg, packet.iv, sessionKey);
|
|
742
|
+
return plainText;
|
|
743
|
+
} catch (error) {
|
|
744
|
+
throw new Error("Decryption failed. You may not be the intended recipient.");
|
|
745
|
+
}
|
|
746
|
+
}
|
|
588
747
|
// ==========================================================================
|
|
589
748
|
// Metrics & Status
|
|
590
749
|
// ==========================================================================
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
// src/crypto.ts
|
|
2
|
+
var getCrypto = () => {
|
|
3
|
+
if (typeof globalThis.crypto !== "undefined") {
|
|
4
|
+
return globalThis.crypto;
|
|
5
|
+
}
|
|
6
|
+
throw new Error(
|
|
7
|
+
"Web Crypto API not available. Requires a modern browser or Node.js 16+."
|
|
8
|
+
);
|
|
9
|
+
};
|
|
2
10
|
var RSA_ALG = {
|
|
3
11
|
name: "RSA-OAEP",
|
|
4
12
|
modulusLength: 2048,
|
|
@@ -6,9 +14,10 @@ var RSA_ALG = {
|
|
|
6
14
|
hash: "SHA-256"
|
|
7
15
|
};
|
|
8
16
|
var WRAP_ALG = "AES-GCM";
|
|
17
|
+
var SESSION_ALG = "AES-GCM";
|
|
9
18
|
var PBKDF2_ITERATIONS = 1e5;
|
|
10
19
|
async function generateKeyPair() {
|
|
11
|
-
return await
|
|
20
|
+
return await getCrypto().subtle.generateKey(
|
|
12
21
|
RSA_ALG,
|
|
13
22
|
true,
|
|
14
23
|
// extractable
|
|
@@ -17,7 +26,7 @@ async function generateKeyPair() {
|
|
|
17
26
|
}
|
|
18
27
|
async function exportKeyToPEM(key, type) {
|
|
19
28
|
const format = type === "public" ? "spki" : "pkcs8";
|
|
20
|
-
const exported = await
|
|
29
|
+
const exported = await getCrypto().subtle.exportKey(format, key);
|
|
21
30
|
const exportedAsBase64 = arrayBufferToBase64(exported);
|
|
22
31
|
const pemHeader = type === "public" ? "-----BEGIN PUBLIC KEY-----" : "-----BEGIN PRIVATE KEY-----";
|
|
23
32
|
const pemFooter = type === "public" ? "-----END PUBLIC KEY-----" : "-----END PRIVATE KEY-----";
|
|
@@ -32,7 +41,7 @@ async function importKeyFromPEM(pem, type) {
|
|
|
32
41
|
const binaryDer = base64ToArrayBuffer(pemContents);
|
|
33
42
|
const format = type === "public" ? "spki" : "pkcs8";
|
|
34
43
|
const usage = type === "public" ? ["encrypt", "wrapKey"] : ["decrypt", "unwrapKey"];
|
|
35
|
-
return await
|
|
44
|
+
return await getCrypto().subtle.importKey(
|
|
36
45
|
format,
|
|
37
46
|
binaryDer,
|
|
38
47
|
RSA_ALG,
|
|
@@ -42,7 +51,7 @@ async function importKeyFromPEM(pem, type) {
|
|
|
42
51
|
}
|
|
43
52
|
async function deriveWrappingKey(password, saltHex) {
|
|
44
53
|
const enc = new TextEncoder();
|
|
45
|
-
const passwordKey = await
|
|
54
|
+
const passwordKey = await getCrypto().subtle.importKey(
|
|
46
55
|
"raw",
|
|
47
56
|
enc.encode(password),
|
|
48
57
|
"PBKDF2",
|
|
@@ -50,7 +59,7 @@ async function deriveWrappingKey(password, saltHex) {
|
|
|
50
59
|
["deriveKey"]
|
|
51
60
|
);
|
|
52
61
|
const salt = hexToArrayBuffer(saltHex);
|
|
53
|
-
return await
|
|
62
|
+
return await getCrypto().subtle.deriveKey(
|
|
54
63
|
{
|
|
55
64
|
name: "PBKDF2",
|
|
56
65
|
salt,
|
|
@@ -64,12 +73,13 @@ async function deriveWrappingKey(password, saltHex) {
|
|
|
64
73
|
);
|
|
65
74
|
}
|
|
66
75
|
async function wrapPrivateKey(privateKey, password) {
|
|
67
|
-
const
|
|
76
|
+
const crypto = getCrypto();
|
|
77
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
68
78
|
const saltHex = arrayBufferToHex(salt.buffer);
|
|
69
79
|
const wrappingKey = await deriveWrappingKey(password, saltHex);
|
|
70
|
-
const exportedKey = await
|
|
71
|
-
const iv =
|
|
72
|
-
const encryptedContent = await
|
|
80
|
+
const exportedKey = await crypto.subtle.exportKey("pkcs8", privateKey);
|
|
81
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
82
|
+
const encryptedContent = await crypto.subtle.encrypt(
|
|
73
83
|
{
|
|
74
84
|
name: WRAP_ALG,
|
|
75
85
|
iv
|
|
@@ -91,7 +101,7 @@ async function unwrapPrivateKey(encryptedKeyBase64, password, saltHex) {
|
|
|
91
101
|
const iv = combinedArray.slice(0, 12);
|
|
92
102
|
const data = combinedArray.slice(12);
|
|
93
103
|
const wrappingKey = await deriveWrappingKey(password, saltHex);
|
|
94
|
-
const decryptedKeyData = await
|
|
104
|
+
const decryptedKeyData = await getCrypto().subtle.decrypt(
|
|
95
105
|
{
|
|
96
106
|
name: WRAP_ALG,
|
|
97
107
|
iv
|
|
@@ -99,7 +109,7 @@ async function unwrapPrivateKey(encryptedKeyBase64, password, saltHex) {
|
|
|
99
109
|
wrappingKey,
|
|
100
110
|
data
|
|
101
111
|
);
|
|
102
|
-
return await
|
|
112
|
+
return await getCrypto().subtle.importKey(
|
|
103
113
|
"pkcs8",
|
|
104
114
|
decryptedKeyData,
|
|
105
115
|
RSA_ALG,
|
|
@@ -107,17 +117,98 @@ async function unwrapPrivateKey(encryptedKeyBase64, password, saltHex) {
|
|
|
107
117
|
["decrypt", "unwrapKey"]
|
|
108
118
|
);
|
|
109
119
|
}
|
|
120
|
+
async function generateSessionKey() {
|
|
121
|
+
return await getCrypto().subtle.generateKey(
|
|
122
|
+
{
|
|
123
|
+
name: SESSION_ALG,
|
|
124
|
+
length: 256
|
|
125
|
+
},
|
|
126
|
+
true,
|
|
127
|
+
["encrypt", "decrypt"]
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
async function encryptData(data, key) {
|
|
131
|
+
const enc = new TextEncoder();
|
|
132
|
+
const encodedData = enc.encode(data);
|
|
133
|
+
const iv = getCrypto().getRandomValues(new Uint8Array(12));
|
|
134
|
+
const encryptedContent = await getCrypto().subtle.encrypt(
|
|
135
|
+
{
|
|
136
|
+
name: SESSION_ALG,
|
|
137
|
+
iv
|
|
138
|
+
},
|
|
139
|
+
key,
|
|
140
|
+
encodedData
|
|
141
|
+
);
|
|
142
|
+
return {
|
|
143
|
+
encrypted: arrayBufferToBase64(encryptedContent),
|
|
144
|
+
iv: arrayBufferToBase64(iv.buffer)
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async function decryptData(encryptedBase64, ivBase64, key) {
|
|
148
|
+
const encryptedData = base64ToArrayBuffer(encryptedBase64);
|
|
149
|
+
const iv = base64ToArrayBuffer(ivBase64);
|
|
150
|
+
const decryptedContent = await getCrypto().subtle.decrypt(
|
|
151
|
+
{
|
|
152
|
+
name: SESSION_ALG,
|
|
153
|
+
iv
|
|
154
|
+
},
|
|
155
|
+
key,
|
|
156
|
+
encryptedData
|
|
157
|
+
);
|
|
158
|
+
const dec = new TextDecoder();
|
|
159
|
+
return dec.decode(decryptedContent);
|
|
160
|
+
}
|
|
161
|
+
async function rsaEncrypt(data, publicKey) {
|
|
162
|
+
const encrypted = await getCrypto().subtle.encrypt(
|
|
163
|
+
{
|
|
164
|
+
name: "RSA-OAEP"
|
|
165
|
+
},
|
|
166
|
+
publicKey,
|
|
167
|
+
data
|
|
168
|
+
);
|
|
169
|
+
return arrayBufferToBase64(encrypted);
|
|
170
|
+
}
|
|
171
|
+
async function rsaDecrypt(encryptedBase64, privateKey) {
|
|
172
|
+
const data = base64ToArrayBuffer(encryptedBase64);
|
|
173
|
+
const decrypted = await getCrypto().subtle.decrypt(
|
|
174
|
+
{
|
|
175
|
+
name: "RSA-OAEP"
|
|
176
|
+
},
|
|
177
|
+
privateKey,
|
|
178
|
+
data
|
|
179
|
+
);
|
|
180
|
+
return decrypted;
|
|
181
|
+
}
|
|
182
|
+
async function exportSessionKey(key) {
|
|
183
|
+
return await getCrypto().subtle.exportKey("raw", key);
|
|
184
|
+
}
|
|
185
|
+
async function importSessionKey(raw) {
|
|
186
|
+
return await getCrypto().subtle.importKey(
|
|
187
|
+
"raw",
|
|
188
|
+
raw,
|
|
189
|
+
SESSION_ALG,
|
|
190
|
+
true,
|
|
191
|
+
["encrypt", "decrypt"]
|
|
192
|
+
);
|
|
193
|
+
}
|
|
110
194
|
function arrayBufferToBase64(buffer) {
|
|
111
|
-
let binary = "";
|
|
112
195
|
const bytes = new Uint8Array(buffer);
|
|
196
|
+
if (typeof Buffer !== "undefined") {
|
|
197
|
+
return Buffer.from(bytes).toString("base64");
|
|
198
|
+
}
|
|
199
|
+
let binary = "";
|
|
113
200
|
const len = bytes.byteLength;
|
|
114
201
|
for (let i = 0; i < len; i++) {
|
|
115
202
|
binary += String.fromCharCode(bytes[i]);
|
|
116
203
|
}
|
|
117
|
-
return
|
|
204
|
+
return btoa(binary);
|
|
118
205
|
}
|
|
119
206
|
function base64ToArrayBuffer(base64) {
|
|
120
|
-
|
|
207
|
+
if (typeof Buffer !== "undefined") {
|
|
208
|
+
const buf = Buffer.from(base64, "base64");
|
|
209
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
210
|
+
}
|
|
211
|
+
const binary_string = atob(base64);
|
|
121
212
|
const len = binary_string.length;
|
|
122
213
|
const bytes = new Uint8Array(len);
|
|
123
214
|
for (let i = 0; i < len; i++) {
|
|
@@ -458,9 +549,15 @@ var Wolfronix = class {
|
|
|
458
549
|
throw new Error("Public key not available. Is user logged in?");
|
|
459
550
|
}
|
|
460
551
|
formData.append("client_public_key", this.publicKeyPEM);
|
|
461
|
-
|
|
552
|
+
const response = await this.request("POST", "/api/v1/encrypt", {
|
|
462
553
|
formData
|
|
463
554
|
});
|
|
555
|
+
return {
|
|
556
|
+
status: response.status,
|
|
557
|
+
file_id: String(response.file_id),
|
|
558
|
+
file_size: response.file_size,
|
|
559
|
+
enc_time_ms: response.enc_time_ms
|
|
560
|
+
};
|
|
464
561
|
}
|
|
465
562
|
/**
|
|
466
563
|
* Decrypt and retrieve a file
|
|
@@ -551,6 +648,68 @@ var Wolfronix = class {
|
|
|
551
648
|
}
|
|
552
649
|
return this.request("DELETE", `/api/v1/files/${fileId}`);
|
|
553
650
|
}
|
|
651
|
+
// ============================================================================
|
|
652
|
+
// E2E Chat Encryption Methods
|
|
653
|
+
// ============================================================================
|
|
654
|
+
/**
|
|
655
|
+
* Get another user's public key (for E2E encryption)
|
|
656
|
+
* @param userId The ID of the recipient
|
|
657
|
+
*/
|
|
658
|
+
async getPublicKey(userId) {
|
|
659
|
+
this.ensureAuthenticated();
|
|
660
|
+
const result = await this.request("GET", `/api/v1/keys/${userId}`);
|
|
661
|
+
return result.public_key;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Encrypt a short text message for a recipient (Hybrid Encryption: RSA + AES)
|
|
665
|
+
* Returns a secure JSON string (packet) to send via chat
|
|
666
|
+
*
|
|
667
|
+
* @param text The plain text message
|
|
668
|
+
* @param recipientId The recipient's user ID
|
|
669
|
+
*/
|
|
670
|
+
async encryptMessage(text, recipientId) {
|
|
671
|
+
this.ensureAuthenticated();
|
|
672
|
+
const recipientPubKeyPEM = await this.getPublicKey(recipientId);
|
|
673
|
+
const recipientPubKey = await importKeyFromPEM(recipientPubKeyPEM, "public");
|
|
674
|
+
const sessionKey = await generateSessionKey();
|
|
675
|
+
const { encrypted: encryptedMsg, iv } = await encryptData(text, sessionKey);
|
|
676
|
+
const rawSessionKey = await exportSessionKey(sessionKey);
|
|
677
|
+
const encryptedSessionKey = await rsaEncrypt(rawSessionKey, recipientPubKey);
|
|
678
|
+
const packet = {
|
|
679
|
+
key: encryptedSessionKey,
|
|
680
|
+
iv,
|
|
681
|
+
msg: encryptedMsg
|
|
682
|
+
};
|
|
683
|
+
return JSON.stringify(packet);
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Decrypt a message packet received from chat
|
|
687
|
+
*
|
|
688
|
+
* @param packetJson The secure JSON string packet
|
|
689
|
+
*/
|
|
690
|
+
async decryptMessage(packetJson) {
|
|
691
|
+
this.ensureAuthenticated();
|
|
692
|
+
if (!this.privateKey) {
|
|
693
|
+
throw new Error("Private key not available. Is user logged in?");
|
|
694
|
+
}
|
|
695
|
+
let packet;
|
|
696
|
+
try {
|
|
697
|
+
packet = JSON.parse(packetJson);
|
|
698
|
+
} catch (e) {
|
|
699
|
+
throw new ValidationError("Invalid message packet format");
|
|
700
|
+
}
|
|
701
|
+
if (!packet.key || !packet.iv || !packet.msg) {
|
|
702
|
+
throw new ValidationError("Invalid message packet structure");
|
|
703
|
+
}
|
|
704
|
+
try {
|
|
705
|
+
const rawSessionKey = await rsaDecrypt(packet.key, this.privateKey);
|
|
706
|
+
const sessionKey = await importSessionKey(rawSessionKey);
|
|
707
|
+
const plainText = await decryptData(packet.msg, packet.iv, sessionKey);
|
|
708
|
+
return plainText;
|
|
709
|
+
} catch (error) {
|
|
710
|
+
throw new Error("Decryption failed. You may not be the intended recipient.");
|
|
711
|
+
}
|
|
712
|
+
}
|
|
554
713
|
// ==========================================================================
|
|
555
714
|
// Metrics & Status
|
|
556
715
|
// ==========================================================================
|