@happyvertical/encryption 0.74.8
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/AGENT.md +33 -0
- package/LICENSE +7 -0
- package/README.md +478 -0
- package/dist/adapters/nacl.d.ts +75 -0
- package/dist/adapters/nacl.d.ts.map +1 -0
- package/dist/adapters/node.d.ts +96 -0
- package/dist/adapters/node.d.ts.map +1 -0
- package/dist/adapters/pgp.d.ts +79 -0
- package/dist/adapters/pgp.d.ts.map +1 -0
- package/dist/chunks/nacl-CoiIhzki.js +454 -0
- package/dist/chunks/nacl-CoiIhzki.js.map +1 -0
- package/dist/chunks/node-nfBpcQQH.js +551 -0
- package/dist/chunks/node-nfBpcQQH.js.map +1 -0
- package/dist/chunks/pgp-BIhtvrNo.js +916 -0
- package/dist/chunks/pgp-BIhtvrNo.js.map +1 -0
- package/dist/cli/claude-context.d.ts +3 -0
- package/dist/cli/claude-context.d.ts.map +1 -0
- package/dist/cli/claude-context.js +21 -0
- package/dist/cli/claude-context.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +356 -0
- package/dist/index.js.map +1 -0
- package/dist/shared/base.d.ts +61 -0
- package/dist/shared/base.d.ts.map +1 -0
- package/dist/shared/errors.d.ts +79 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/factory.d.ts +42 -0
- package/dist/shared/factory.d.ts.map +1 -0
- package/dist/shared/types.d.ts +310 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/metadata.json +34 -0
- package/package.json +67 -0
|
@@ -0,0 +1,916 @@
|
|
|
1
|
+
import * as openpgp from "openpgp";
|
|
2
|
+
import { BaseEncryption, KeyError, EncryptError, InvalidKeyError, VerificationError, DecryptError, PassphraseError, SignatureError } from "../index.js";
|
|
3
|
+
class PGPEncryption extends BaseEncryption {
|
|
4
|
+
options;
|
|
5
|
+
publicKeys;
|
|
6
|
+
privateKeys;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
super("pgp", options.debug);
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Initialize keys from options
|
|
13
|
+
*/
|
|
14
|
+
async initializeKeys() {
|
|
15
|
+
if (this.publicKeys || this.privateKeys) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
if (this.options.publicKey) {
|
|
20
|
+
const key = await openpgp.readKey({
|
|
21
|
+
armoredKey: this.options.publicKey
|
|
22
|
+
});
|
|
23
|
+
this.publicKeys = [key];
|
|
24
|
+
} else if (this.options.publicKeys) {
|
|
25
|
+
this.publicKeys = await Promise.all(
|
|
26
|
+
this.options.publicKeys.map(
|
|
27
|
+
(k) => openpgp.readKey({ armoredKey: k })
|
|
28
|
+
)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
if (this.options.privateKey) {
|
|
32
|
+
const key = await openpgp.readPrivateKey({
|
|
33
|
+
armoredKey: this.options.privateKey
|
|
34
|
+
});
|
|
35
|
+
if (this.options.passphrase) {
|
|
36
|
+
const decryptedKey = await openpgp.decryptKey({
|
|
37
|
+
privateKey: key,
|
|
38
|
+
passphrase: this.options.passphrase
|
|
39
|
+
});
|
|
40
|
+
this.privateKeys = [decryptedKey];
|
|
41
|
+
} else {
|
|
42
|
+
this.privateKeys = [key];
|
|
43
|
+
}
|
|
44
|
+
} else if (this.options.privateKeys) {
|
|
45
|
+
this.privateKeys = await Promise.all(
|
|
46
|
+
this.options.privateKeys.map(async (k) => {
|
|
47
|
+
const key = await openpgp.readPrivateKey({ armoredKey: k });
|
|
48
|
+
if (this.options.passphrase) {
|
|
49
|
+
return openpgp.decryptKey({
|
|
50
|
+
privateKey: key,
|
|
51
|
+
passphrase: this.options.passphrase
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return key;
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throw new KeyError(
|
|
60
|
+
`Failed to initialize PGP keys: ${error.message}`,
|
|
61
|
+
this.adapterType,
|
|
62
|
+
error
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get public keys for encryption
|
|
68
|
+
*/
|
|
69
|
+
async getPublicKeys(options) {
|
|
70
|
+
await this.initializeKeys();
|
|
71
|
+
if (options?.publicKeys) {
|
|
72
|
+
return Promise.all(
|
|
73
|
+
options.publicKeys.map((k) => openpgp.readKey({ armoredKey: k }))
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
if (!this.publicKeys || this.publicKeys.length === 0) {
|
|
77
|
+
throw new KeyError(
|
|
78
|
+
"No public keys available for encryption",
|
|
79
|
+
this.adapterType
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return this.publicKeys;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get private keys for decryption
|
|
86
|
+
*/
|
|
87
|
+
async getPrivateKeys(options) {
|
|
88
|
+
await this.initializeKeys();
|
|
89
|
+
if (options?.privateKey) {
|
|
90
|
+
const key = await openpgp.readPrivateKey({
|
|
91
|
+
armoredKey: options.privateKey
|
|
92
|
+
});
|
|
93
|
+
if (options.passphrase) {
|
|
94
|
+
const decryptedKey = await openpgp.decryptKey({
|
|
95
|
+
privateKey: key,
|
|
96
|
+
passphrase: options.passphrase
|
|
97
|
+
});
|
|
98
|
+
return [decryptedKey];
|
|
99
|
+
}
|
|
100
|
+
return [key];
|
|
101
|
+
}
|
|
102
|
+
if (!this.privateKeys || this.privateKeys.length === 0) {
|
|
103
|
+
throw new KeyError(
|
|
104
|
+
"No private keys available for decryption",
|
|
105
|
+
this.adapterType
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
return this.privateKeys;
|
|
109
|
+
}
|
|
110
|
+
async encryptText(text, options) {
|
|
111
|
+
try {
|
|
112
|
+
this.log("Encrypting text", { length: text.length });
|
|
113
|
+
this.validateEncryptOptions(options);
|
|
114
|
+
const publicKeys = await this.getPublicKeys(options);
|
|
115
|
+
const format = options?.armor !== false ? "armored" : "binary";
|
|
116
|
+
const encryptConfig = {
|
|
117
|
+
message: await openpgp.createMessage({ text }),
|
|
118
|
+
encryptionKeys: publicKeys,
|
|
119
|
+
format
|
|
120
|
+
};
|
|
121
|
+
if (options?.sign && options?.privateKey) {
|
|
122
|
+
const signingKey = await openpgp.readPrivateKey({
|
|
123
|
+
armoredKey: options.privateKey
|
|
124
|
+
});
|
|
125
|
+
if (this.options.passphrase) {
|
|
126
|
+
encryptConfig.signingKeys = await openpgp.decryptKey({
|
|
127
|
+
privateKey: signingKey,
|
|
128
|
+
passphrase: this.options.passphrase
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
encryptConfig.signingKeys = signingKey;
|
|
132
|
+
}
|
|
133
|
+
} else if (options?.sign && this.privateKeys) {
|
|
134
|
+
encryptConfig.signingKeys = this.privateKeys;
|
|
135
|
+
}
|
|
136
|
+
const encrypted = await openpgp.encrypt(encryptConfig);
|
|
137
|
+
this.log("Text encrypted successfully");
|
|
138
|
+
if (format === "binary" && encrypted instanceof Uint8Array) {
|
|
139
|
+
return Buffer.from(encrypted).toString("base64");
|
|
140
|
+
}
|
|
141
|
+
return encrypted;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
throw new EncryptError(
|
|
144
|
+
`Failed to encrypt text: ${error.message}`,
|
|
145
|
+
this.adapterType,
|
|
146
|
+
error
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async decryptText(encrypted, options) {
|
|
151
|
+
try {
|
|
152
|
+
this.log("Decrypting text", { length: encrypted.length });
|
|
153
|
+
this.validateDecryptOptions(options);
|
|
154
|
+
const privateKeys = await this.getPrivateKeys(options);
|
|
155
|
+
let message;
|
|
156
|
+
try {
|
|
157
|
+
message = await openpgp.readMessage({
|
|
158
|
+
armoredMessage: encrypted
|
|
159
|
+
});
|
|
160
|
+
} catch {
|
|
161
|
+
try {
|
|
162
|
+
message = await openpgp.readMessage({
|
|
163
|
+
binaryMessage: Buffer.from(encrypted, "base64")
|
|
164
|
+
});
|
|
165
|
+
} catch {
|
|
166
|
+
message = await openpgp.readMessage({
|
|
167
|
+
binaryMessage: Buffer.from(encrypted, "binary")
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const decryptConfig = {
|
|
172
|
+
message,
|
|
173
|
+
decryptionKeys: privateKeys,
|
|
174
|
+
format: "utf8"
|
|
175
|
+
};
|
|
176
|
+
if (options?.verify && options?.publicKey) {
|
|
177
|
+
if (typeof options.publicKey === "string") {
|
|
178
|
+
decryptConfig.verificationKeys = await openpgp.readKey({
|
|
179
|
+
armoredKey: options.publicKey
|
|
180
|
+
});
|
|
181
|
+
} else {
|
|
182
|
+
throw new InvalidKeyError(
|
|
183
|
+
"PGP verification requires armored public key string",
|
|
184
|
+
this.adapterType
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const result = await openpgp.decrypt(decryptConfig);
|
|
189
|
+
if (options?.verify && result.signatures) {
|
|
190
|
+
try {
|
|
191
|
+
await result.signatures[0].verified;
|
|
192
|
+
this.log("Signature verified successfully");
|
|
193
|
+
} catch (error) {
|
|
194
|
+
throw new VerificationError(
|
|
195
|
+
"Signature verification failed",
|
|
196
|
+
this.adapterType,
|
|
197
|
+
error
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
this.log("Text decrypted successfully");
|
|
202
|
+
return result.data;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (error instanceof VerificationError) {
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
throw new DecryptError(
|
|
208
|
+
`Failed to decrypt text: ${error.message}`,
|
|
209
|
+
this.adapterType,
|
|
210
|
+
error
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async encryptBuffer(buffer, options) {
|
|
215
|
+
try {
|
|
216
|
+
this.log("Encrypting buffer", { size: buffer.length });
|
|
217
|
+
this.validateEncryptOptions(options);
|
|
218
|
+
const publicKeys = await this.getPublicKeys(options);
|
|
219
|
+
const format = options?.armor === true ? "armored" : "binary";
|
|
220
|
+
const encryptConfig = {
|
|
221
|
+
message: await openpgp.createMessage({ binary: buffer }),
|
|
222
|
+
encryptionKeys: publicKeys,
|
|
223
|
+
format
|
|
224
|
+
};
|
|
225
|
+
if (options?.sign && options?.privateKey) {
|
|
226
|
+
const signingKey = await openpgp.readPrivateKey({
|
|
227
|
+
armoredKey: options.privateKey
|
|
228
|
+
});
|
|
229
|
+
if (this.options.passphrase) {
|
|
230
|
+
encryptConfig.signingKeys = await openpgp.decryptKey({
|
|
231
|
+
privateKey: signingKey,
|
|
232
|
+
passphrase: this.options.passphrase
|
|
233
|
+
});
|
|
234
|
+
} else {
|
|
235
|
+
encryptConfig.signingKeys = signingKey;
|
|
236
|
+
}
|
|
237
|
+
} else if (options?.sign && this.privateKeys) {
|
|
238
|
+
encryptConfig.signingKeys = this.privateKeys;
|
|
239
|
+
}
|
|
240
|
+
const encrypted = await openpgp.encrypt(encryptConfig);
|
|
241
|
+
this.log("Buffer encrypted successfully");
|
|
242
|
+
if (typeof encrypted === "string") {
|
|
243
|
+
return Buffer.from(encrypted);
|
|
244
|
+
}
|
|
245
|
+
if (encrypted instanceof Uint8Array) {
|
|
246
|
+
return Buffer.from(encrypted);
|
|
247
|
+
}
|
|
248
|
+
return Buffer.from(encrypted);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
throw new EncryptError(
|
|
251
|
+
`Failed to encrypt buffer: ${error.message}`,
|
|
252
|
+
this.adapterType,
|
|
253
|
+
error
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async decryptBuffer(buffer, options) {
|
|
258
|
+
try {
|
|
259
|
+
this.log("Decrypting buffer", { size: buffer.length });
|
|
260
|
+
this.validateDecryptOptions(options);
|
|
261
|
+
const privateKeys = await this.getPrivateKeys(options);
|
|
262
|
+
let message;
|
|
263
|
+
try {
|
|
264
|
+
message = await openpgp.readMessage({
|
|
265
|
+
armoredMessage: buffer.toString("utf8")
|
|
266
|
+
});
|
|
267
|
+
} catch {
|
|
268
|
+
message = await openpgp.readMessage({
|
|
269
|
+
binaryMessage: buffer
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
const decryptConfig = {
|
|
273
|
+
message,
|
|
274
|
+
decryptionKeys: privateKeys,
|
|
275
|
+
format: "binary"
|
|
276
|
+
};
|
|
277
|
+
if (options?.verify && options?.publicKey) {
|
|
278
|
+
if (typeof options.publicKey === "string") {
|
|
279
|
+
decryptConfig.verificationKeys = await openpgp.readKey({
|
|
280
|
+
armoredKey: options.publicKey
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
throw new InvalidKeyError(
|
|
284
|
+
"PGP verification requires armored public key string",
|
|
285
|
+
this.adapterType
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const result = await openpgp.decrypt(decryptConfig);
|
|
290
|
+
if (options?.verify && result.signatures) {
|
|
291
|
+
try {
|
|
292
|
+
await result.signatures[0].verified;
|
|
293
|
+
this.log("Signature verified successfully");
|
|
294
|
+
} catch (error) {
|
|
295
|
+
throw new VerificationError(
|
|
296
|
+
"Signature verification failed",
|
|
297
|
+
this.adapterType,
|
|
298
|
+
error
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
this.log("Buffer decrypted successfully");
|
|
303
|
+
return Buffer.from(result.data);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
if (error instanceof VerificationError) {
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
throw new DecryptError(
|
|
309
|
+
`Failed to decrypt buffer: ${error.message}`,
|
|
310
|
+
this.adapterType,
|
|
311
|
+
error
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async generateKeyPair(options) {
|
|
316
|
+
try {
|
|
317
|
+
this.log("Generating PGP key pair", options);
|
|
318
|
+
const keyType = options?.type || "rsa";
|
|
319
|
+
const keySize = options?.keySize || 4096;
|
|
320
|
+
let generateOptions;
|
|
321
|
+
if (keyType === "rsa") {
|
|
322
|
+
generateOptions = {
|
|
323
|
+
type: "rsa",
|
|
324
|
+
rsaBits: keySize,
|
|
325
|
+
userIDs: [
|
|
326
|
+
{
|
|
327
|
+
name: options?.name || "",
|
|
328
|
+
email: options?.email || ""
|
|
329
|
+
}
|
|
330
|
+
],
|
|
331
|
+
passphrase: options?.passphrase,
|
|
332
|
+
format: "object"
|
|
333
|
+
};
|
|
334
|
+
} else if (keyType === "ecc" || keyType === "ecdh") {
|
|
335
|
+
generateOptions = {
|
|
336
|
+
type: "ecc",
|
|
337
|
+
curve: options?.curve || "curve25519",
|
|
338
|
+
userIDs: [
|
|
339
|
+
{
|
|
340
|
+
name: options?.name || "",
|
|
341
|
+
email: options?.email || ""
|
|
342
|
+
}
|
|
343
|
+
],
|
|
344
|
+
passphrase: options?.passphrase,
|
|
345
|
+
format: "object"
|
|
346
|
+
};
|
|
347
|
+
} else {
|
|
348
|
+
throw new KeyError(
|
|
349
|
+
`Unsupported key type for PGP: ${keyType}`,
|
|
350
|
+
this.adapterType
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
const { privateKey: privKeyObj, publicKey: pubKeyObj } = await openpgp.generateKey(generateOptions);
|
|
354
|
+
const publicKey = pubKeyObj.armor();
|
|
355
|
+
const privateKey = privKeyObj.armor();
|
|
356
|
+
const fingerprint = pubKeyObj.getFingerprint();
|
|
357
|
+
const keyId = pubKeyObj.getKeyID().toHex();
|
|
358
|
+
this.log("Key pair generated successfully", { keyId, fingerprint });
|
|
359
|
+
return {
|
|
360
|
+
publicKey,
|
|
361
|
+
privateKey,
|
|
362
|
+
fingerprint,
|
|
363
|
+
keyId
|
|
364
|
+
};
|
|
365
|
+
} catch (error) {
|
|
366
|
+
throw new KeyError(
|
|
367
|
+
`Failed to generate PGP key pair: ${error.message}`,
|
|
368
|
+
this.adapterType,
|
|
369
|
+
error
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async importKey(key, options) {
|
|
374
|
+
try {
|
|
375
|
+
this.log("Importing PGP key");
|
|
376
|
+
const armoredKey = typeof key === "string" ? key : key.toString("utf8");
|
|
377
|
+
const keyType = options?.type || "public";
|
|
378
|
+
if (keyType === "public") {
|
|
379
|
+
const pubKey = await openpgp.readKey({ armoredKey });
|
|
380
|
+
const expirationTime2 = await pubKey.getExpirationTime();
|
|
381
|
+
return {
|
|
382
|
+
type: "public",
|
|
383
|
+
format: "armored",
|
|
384
|
+
data: armoredKey,
|
|
385
|
+
fingerprint: pubKey.getFingerprint(),
|
|
386
|
+
keyId: pubKey.getKeyID().toHex(),
|
|
387
|
+
algorithm: pubKey.getAlgorithmInfo().algorithm,
|
|
388
|
+
created: pubKey.getCreationTime(),
|
|
389
|
+
expires: expirationTime2 instanceof Date ? expirationTime2 : void 0,
|
|
390
|
+
userIds: pubKey.getUserIDs().map((uid) => {
|
|
391
|
+
const match = uid.match(/^(.*?)\s*<(.+?)>$/);
|
|
392
|
+
if (match) {
|
|
393
|
+
return { name: match[1], email: match[2] };
|
|
394
|
+
}
|
|
395
|
+
return { email: uid };
|
|
396
|
+
})
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const privKey = await openpgp.readPrivateKey({ armoredKey });
|
|
400
|
+
let decryptedKey = privKey;
|
|
401
|
+
if (options?.passphrase) {
|
|
402
|
+
try {
|
|
403
|
+
decryptedKey = await openpgp.decryptKey({
|
|
404
|
+
privateKey: privKey,
|
|
405
|
+
passphrase: options.passphrase
|
|
406
|
+
});
|
|
407
|
+
} catch (error) {
|
|
408
|
+
throw new PassphraseError(
|
|
409
|
+
"Invalid passphrase for private key",
|
|
410
|
+
this.adapterType,
|
|
411
|
+
error
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
this.log("Key imported successfully");
|
|
416
|
+
const expirationTime = await decryptedKey.getExpirationTime();
|
|
417
|
+
return {
|
|
418
|
+
type: "private",
|
|
419
|
+
format: "armored",
|
|
420
|
+
data: armoredKey,
|
|
421
|
+
fingerprint: decryptedKey.getFingerprint(),
|
|
422
|
+
keyId: decryptedKey.getKeyID().toHex(),
|
|
423
|
+
algorithm: decryptedKey.getAlgorithmInfo().algorithm,
|
|
424
|
+
created: decryptedKey.getCreationTime(),
|
|
425
|
+
expires: expirationTime instanceof Date ? expirationTime : void 0,
|
|
426
|
+
userIds: decryptedKey.getUserIDs().map((uid) => {
|
|
427
|
+
const match = uid.match(/^(.*?)\s*<(.+?)>$/);
|
|
428
|
+
if (match) {
|
|
429
|
+
return { name: match[1], email: match[2] };
|
|
430
|
+
}
|
|
431
|
+
return { email: uid };
|
|
432
|
+
})
|
|
433
|
+
};
|
|
434
|
+
} catch (error) {
|
|
435
|
+
if (error instanceof PassphraseError) {
|
|
436
|
+
throw error;
|
|
437
|
+
}
|
|
438
|
+
throw new InvalidKeyError(
|
|
439
|
+
`Failed to import PGP key: ${error.message}`,
|
|
440
|
+
this.adapterType,
|
|
441
|
+
error
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async exportKey(key, options) {
|
|
446
|
+
try {
|
|
447
|
+
this.log("Exporting PGP key");
|
|
448
|
+
if (typeof key.data === "string") {
|
|
449
|
+
if (options?.format === "binary") {
|
|
450
|
+
return Buffer.from(key.data);
|
|
451
|
+
}
|
|
452
|
+
return key.data;
|
|
453
|
+
}
|
|
454
|
+
if (Buffer.isBuffer(key.data)) {
|
|
455
|
+
if (options?.format === "armored" || options?.armor) {
|
|
456
|
+
return key.data.toString("utf8");
|
|
457
|
+
}
|
|
458
|
+
return key.data;
|
|
459
|
+
}
|
|
460
|
+
throw new InvalidKeyError(
|
|
461
|
+
"Invalid key data format for export",
|
|
462
|
+
this.adapterType
|
|
463
|
+
);
|
|
464
|
+
} catch (error) {
|
|
465
|
+
throw new KeyError(
|
|
466
|
+
`Failed to export PGP key: ${error.message}`,
|
|
467
|
+
this.adapterType,
|
|
468
|
+
error
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Sign data with private key
|
|
474
|
+
*/
|
|
475
|
+
async sign(data, options) {
|
|
476
|
+
try {
|
|
477
|
+
this.log("Signing data");
|
|
478
|
+
await this.initializeKeys();
|
|
479
|
+
let signingKey;
|
|
480
|
+
if (options?.privateKey) {
|
|
481
|
+
signingKey = await openpgp.readPrivateKey({
|
|
482
|
+
armoredKey: options.privateKey
|
|
483
|
+
});
|
|
484
|
+
if (options.passphrase) {
|
|
485
|
+
signingKey = await openpgp.decryptKey({
|
|
486
|
+
privateKey: signingKey,
|
|
487
|
+
passphrase: options.passphrase
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
} else if (this.privateKeys && this.privateKeys.length > 0) {
|
|
491
|
+
signingKey = this.privateKeys[0];
|
|
492
|
+
} else {
|
|
493
|
+
throw new KeyError(
|
|
494
|
+
"No private key available for signing",
|
|
495
|
+
this.adapterType
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
const message = typeof data === "string" ? await openpgp.createMessage({ text: data }) : await openpgp.createMessage({ binary: data });
|
|
499
|
+
const format = options?.armor !== false ? "armored" : "binary";
|
|
500
|
+
const signed = await openpgp.sign({
|
|
501
|
+
message,
|
|
502
|
+
signingKeys: signingKey,
|
|
503
|
+
detached: options?.detached,
|
|
504
|
+
format
|
|
505
|
+
});
|
|
506
|
+
this.log("Data signed successfully");
|
|
507
|
+
if (format === "armored") {
|
|
508
|
+
return signed;
|
|
509
|
+
} else {
|
|
510
|
+
if (signed instanceof Uint8Array) {
|
|
511
|
+
return Buffer.from(signed);
|
|
512
|
+
}
|
|
513
|
+
return Buffer.from(signed);
|
|
514
|
+
}
|
|
515
|
+
} catch (error) {
|
|
516
|
+
throw new SignatureError(
|
|
517
|
+
`Failed to sign data: ${error.message}`,
|
|
518
|
+
this.adapterType,
|
|
519
|
+
error
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Verify signature
|
|
525
|
+
*/
|
|
526
|
+
async verify(data, signature, options) {
|
|
527
|
+
try {
|
|
528
|
+
this.log("Verifying signature");
|
|
529
|
+
if (!options?.publicKey) {
|
|
530
|
+
throw new KeyError(
|
|
531
|
+
"Public key required for signature verification",
|
|
532
|
+
this.adapterType
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
const verificationKey = await openpgp.readKey({
|
|
536
|
+
armoredKey: options.publicKey
|
|
537
|
+
});
|
|
538
|
+
const signatureStr = typeof signature === "string" ? signature : signature.toString("utf8");
|
|
539
|
+
const isDetached = signatureStr.includes("-----BEGIN PGP SIGNATURE-----");
|
|
540
|
+
if (isDetached) {
|
|
541
|
+
const message = typeof data === "string" ? await openpgp.createMessage({ text: data }) : await openpgp.createMessage({ binary: data });
|
|
542
|
+
const sig = await openpgp.readSignature({
|
|
543
|
+
armoredSignature: signatureStr
|
|
544
|
+
});
|
|
545
|
+
const result = await openpgp.verify({
|
|
546
|
+
message,
|
|
547
|
+
signature: sig,
|
|
548
|
+
verificationKeys: verificationKey
|
|
549
|
+
});
|
|
550
|
+
await result.signatures[0].verified;
|
|
551
|
+
} else {
|
|
552
|
+
let message;
|
|
553
|
+
if (signatureStr.includes("-----BEGIN PGP SIGNED MESSAGE-----")) {
|
|
554
|
+
message = await openpgp.readCleartextMessage({
|
|
555
|
+
cleartextMessage: signatureStr
|
|
556
|
+
});
|
|
557
|
+
} else {
|
|
558
|
+
try {
|
|
559
|
+
message = await openpgp.readMessage({
|
|
560
|
+
armoredMessage: signatureStr
|
|
561
|
+
});
|
|
562
|
+
} catch {
|
|
563
|
+
message = await openpgp.readMessage({
|
|
564
|
+
binaryMessage: typeof signature === "string" ? Buffer.from(signature, "binary") : signature
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
const result = await openpgp.verify({
|
|
569
|
+
message,
|
|
570
|
+
verificationKeys: verificationKey
|
|
571
|
+
});
|
|
572
|
+
await result.signatures[0].verified;
|
|
573
|
+
}
|
|
574
|
+
this.log("Signature verified successfully");
|
|
575
|
+
return true;
|
|
576
|
+
} catch (error) {
|
|
577
|
+
if (error instanceof KeyError) {
|
|
578
|
+
throw error;
|
|
579
|
+
}
|
|
580
|
+
this.log("Signature verification failed", error);
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Encrypt email message in PGP/MIME format
|
|
586
|
+
*/
|
|
587
|
+
async encryptEmail(message, options) {
|
|
588
|
+
try {
|
|
589
|
+
this.log("Encrypting email message");
|
|
590
|
+
const emailContent = this.serializeEmailContent(message, options);
|
|
591
|
+
const publicKeys = await this.getPublicKeys({
|
|
592
|
+
publicKeys: options?.publicKeys
|
|
593
|
+
});
|
|
594
|
+
let signingKey;
|
|
595
|
+
if (options?.sign) {
|
|
596
|
+
await this.initializeKeys();
|
|
597
|
+
if (options.privateKey) {
|
|
598
|
+
const key = await openpgp.readPrivateKey({
|
|
599
|
+
armoredKey: options.privateKey
|
|
600
|
+
});
|
|
601
|
+
if (options.passphrase) {
|
|
602
|
+
signingKey = await openpgp.decryptKey({
|
|
603
|
+
privateKey: key,
|
|
604
|
+
passphrase: options.passphrase
|
|
605
|
+
});
|
|
606
|
+
} else {
|
|
607
|
+
signingKey = key;
|
|
608
|
+
}
|
|
609
|
+
} else if (this.privateKeys && this.privateKeys.length > 0) {
|
|
610
|
+
signingKey = this.privateKeys[0];
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const encryptConfig = {
|
|
614
|
+
message: await openpgp.createMessage({ text: emailContent }),
|
|
615
|
+
encryptionKeys: publicKeys,
|
|
616
|
+
format: options?.armor !== false ? "armored" : "binary"
|
|
617
|
+
};
|
|
618
|
+
if (signingKey) {
|
|
619
|
+
encryptConfig.signingKeys = signingKey;
|
|
620
|
+
}
|
|
621
|
+
const encrypted = await openpgp.encrypt(encryptConfig);
|
|
622
|
+
this.log("Email encrypted successfully");
|
|
623
|
+
return {
|
|
624
|
+
...message,
|
|
625
|
+
text: encrypted,
|
|
626
|
+
html: void 0,
|
|
627
|
+
// Clear HTML when encrypted
|
|
628
|
+
contentType: 'multipart/encrypted; protocol="application/pgp-encrypted"',
|
|
629
|
+
headers: {
|
|
630
|
+
...message.headers || {},
|
|
631
|
+
"Content-Type": 'multipart/encrypted; protocol="application/pgp-encrypted"'
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
} catch (error) {
|
|
635
|
+
throw new EncryptError(
|
|
636
|
+
`Failed to encrypt email: ${error.message}`,
|
|
637
|
+
this.adapterType,
|
|
638
|
+
error
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Decrypt PGP/MIME email message
|
|
644
|
+
*/
|
|
645
|
+
async decryptEmail(message, options) {
|
|
646
|
+
try {
|
|
647
|
+
this.log("Decrypting email message");
|
|
648
|
+
if (!message.text) {
|
|
649
|
+
throw new DecryptError(
|
|
650
|
+
"Email message has no text content to decrypt",
|
|
651
|
+
this.adapterType
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
const privateKeys = await this.getPrivateKeys({
|
|
655
|
+
privateKey: options?.privateKey,
|
|
656
|
+
passphrase: options?.passphrase
|
|
657
|
+
});
|
|
658
|
+
let encryptedMessage;
|
|
659
|
+
try {
|
|
660
|
+
encryptedMessage = await openpgp.readMessage({
|
|
661
|
+
armoredMessage: message.text
|
|
662
|
+
});
|
|
663
|
+
} catch {
|
|
664
|
+
encryptedMessage = await openpgp.readMessage({
|
|
665
|
+
binaryMessage: Buffer.from(message.text, "base64")
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
const decryptConfig = {
|
|
669
|
+
message: encryptedMessage,
|
|
670
|
+
decryptionKeys: privateKeys,
|
|
671
|
+
format: "utf8"
|
|
672
|
+
};
|
|
673
|
+
if (options?.verify) {
|
|
674
|
+
const verificationKey = options.publicKey || this.options.publicKey;
|
|
675
|
+
if (verificationKey) {
|
|
676
|
+
decryptConfig.verificationKeys = await openpgp.readKey({
|
|
677
|
+
armoredKey: verificationKey
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const result = await openpgp.decrypt(decryptConfig);
|
|
682
|
+
const decryptedContent = result.data;
|
|
683
|
+
let verified = false;
|
|
684
|
+
let verificationError;
|
|
685
|
+
let signerKeyId;
|
|
686
|
+
let signerFingerprint;
|
|
687
|
+
if (options?.verify) {
|
|
688
|
+
const verificationKey = options.publicKey || this.options.publicKey;
|
|
689
|
+
if (!verificationKey) {
|
|
690
|
+
verificationError = "Public key required for signature verification";
|
|
691
|
+
this.log("Email signature verification skipped: no public key");
|
|
692
|
+
} else if (!result.signatures || result.signatures.length === 0) {
|
|
693
|
+
verificationError = "No signature found in message";
|
|
694
|
+
this.log("Email signature verification skipped: no signatures");
|
|
695
|
+
} else {
|
|
696
|
+
try {
|
|
697
|
+
await result.signatures[0].verified;
|
|
698
|
+
verified = true;
|
|
699
|
+
signerKeyId = result.signatures[0].keyID.toHex();
|
|
700
|
+
const pubKey = await openpgp.readKey({
|
|
701
|
+
armoredKey: verificationKey
|
|
702
|
+
});
|
|
703
|
+
signerFingerprint = pubKey.getFingerprint();
|
|
704
|
+
this.log("Email signature verified successfully");
|
|
705
|
+
} catch (error) {
|
|
706
|
+
verificationError = error instanceof Error ? error.message : String(error);
|
|
707
|
+
this.log("Email signature verification failed", error);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const deserializedMessage = this.deserializeEmailContent(
|
|
712
|
+
decryptedContent,
|
|
713
|
+
message
|
|
714
|
+
);
|
|
715
|
+
this.log("Email decrypted successfully");
|
|
716
|
+
return {
|
|
717
|
+
...deserializedMessage,
|
|
718
|
+
encrypted: true,
|
|
719
|
+
signed: result.signatures && result.signatures.length > 0,
|
|
720
|
+
verified: options?.verify ? verified : void 0,
|
|
721
|
+
verificationError,
|
|
722
|
+
signerKeyId,
|
|
723
|
+
signerFingerprint,
|
|
724
|
+
encryptionAlgorithm: "pgp"
|
|
725
|
+
};
|
|
726
|
+
} catch (error) {
|
|
727
|
+
if (error instanceof DecryptError) {
|
|
728
|
+
throw error;
|
|
729
|
+
}
|
|
730
|
+
throw new DecryptError(
|
|
731
|
+
`Failed to decrypt email: ${error.message}`,
|
|
732
|
+
this.adapterType,
|
|
733
|
+
error
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Sign email message
|
|
739
|
+
*/
|
|
740
|
+
async signEmail(message, options) {
|
|
741
|
+
try {
|
|
742
|
+
this.log("Signing email message");
|
|
743
|
+
const emailContent = this.serializeEmailContent(message);
|
|
744
|
+
await this.initializeKeys();
|
|
745
|
+
let signingKey;
|
|
746
|
+
if (options?.privateKey) {
|
|
747
|
+
const key = await openpgp.readPrivateKey({
|
|
748
|
+
armoredKey: options.privateKey
|
|
749
|
+
});
|
|
750
|
+
if (options.passphrase) {
|
|
751
|
+
signingKey = await openpgp.decryptKey({
|
|
752
|
+
privateKey: key,
|
|
753
|
+
passphrase: options.passphrase
|
|
754
|
+
});
|
|
755
|
+
} else {
|
|
756
|
+
signingKey = key;
|
|
757
|
+
}
|
|
758
|
+
} else if (this.privateKeys && this.privateKeys.length > 0) {
|
|
759
|
+
signingKey = this.privateKeys[0];
|
|
760
|
+
} else {
|
|
761
|
+
throw new KeyError(
|
|
762
|
+
"No private key available for signing email",
|
|
763
|
+
this.adapterType
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
const signedMessageObj = await openpgp.sign({
|
|
767
|
+
message: await openpgp.createCleartextMessage({ text: emailContent }),
|
|
768
|
+
signingKeys: signingKey,
|
|
769
|
+
format: "object"
|
|
770
|
+
});
|
|
771
|
+
const signedMessage = signedMessageObj.armor();
|
|
772
|
+
this.log("Email signed successfully");
|
|
773
|
+
return {
|
|
774
|
+
...message,
|
|
775
|
+
text: signedMessage,
|
|
776
|
+
contentType: "text/plain; charset=utf-8",
|
|
777
|
+
headers: {
|
|
778
|
+
...message.headers || {},
|
|
779
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
} catch (error) {
|
|
783
|
+
throw new SignatureError(
|
|
784
|
+
`Failed to sign email: ${error.message}`,
|
|
785
|
+
this.adapterType,
|
|
786
|
+
error
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Verify email signature
|
|
792
|
+
*/
|
|
793
|
+
async verifyEmail(message, options) {
|
|
794
|
+
try {
|
|
795
|
+
this.log("Verifying email signature");
|
|
796
|
+
if (!message.text) {
|
|
797
|
+
return {
|
|
798
|
+
valid: false,
|
|
799
|
+
message: "Email message has no text content to verify"
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
if (!options?.publicKey) {
|
|
803
|
+
return {
|
|
804
|
+
valid: false,
|
|
805
|
+
message: "Public key required for email signature verification"
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
const verificationKey = await openpgp.readKey({
|
|
809
|
+
armoredKey: options.publicKey
|
|
810
|
+
});
|
|
811
|
+
const signedMessage = await openpgp.readCleartextMessage({
|
|
812
|
+
cleartextMessage: message.text
|
|
813
|
+
});
|
|
814
|
+
const result = await openpgp.verify({
|
|
815
|
+
message: signedMessage,
|
|
816
|
+
verificationKeys: verificationKey
|
|
817
|
+
});
|
|
818
|
+
await result.signatures[0].verified;
|
|
819
|
+
const keyId = result.signatures[0].keyID.toHex();
|
|
820
|
+
const fingerprint = verificationKey.getFingerprint();
|
|
821
|
+
this.log("Email signature verified successfully");
|
|
822
|
+
return {
|
|
823
|
+
valid: true,
|
|
824
|
+
keyId,
|
|
825
|
+
keyFingerprint: fingerprint,
|
|
826
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
827
|
+
algorithm: "pgp"
|
|
828
|
+
};
|
|
829
|
+
} catch (error) {
|
|
830
|
+
this.log("Email signature verification failed", error);
|
|
831
|
+
return {
|
|
832
|
+
valid: false,
|
|
833
|
+
message: error.message
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Serialize email content for encryption/signing
|
|
839
|
+
*/
|
|
840
|
+
serializeEmailContent(message, options) {
|
|
841
|
+
const parts = [];
|
|
842
|
+
if (options?.encryptSubject) {
|
|
843
|
+
parts.push(`Subject: ${message.subject}`);
|
|
844
|
+
}
|
|
845
|
+
if (message.text) {
|
|
846
|
+
parts.push(`
|
|
847
|
+
${message.text}`);
|
|
848
|
+
}
|
|
849
|
+
if (message.html) {
|
|
850
|
+
parts.push(`
|
|
851
|
+
--- HTML Content ---
|
|
852
|
+
${message.html}`);
|
|
853
|
+
}
|
|
854
|
+
if (message.attachments && message.attachments.length > 0) {
|
|
855
|
+
parts.push("\n--- Attachments ---");
|
|
856
|
+
for (const attachment of message.attachments) {
|
|
857
|
+
parts.push(
|
|
858
|
+
`- ${attachment.filename || "unnamed"} (${attachment.contentType}, ${attachment.size} bytes)`
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return parts.join("\n");
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Deserialize email content after decryption
|
|
866
|
+
*/
|
|
867
|
+
deserializeEmailContent(content, originalMessage) {
|
|
868
|
+
const lines = content.split("\n");
|
|
869
|
+
let subject = originalMessage.subject;
|
|
870
|
+
let text = "";
|
|
871
|
+
let html;
|
|
872
|
+
let currentSection = "text";
|
|
873
|
+
for (const line of lines) {
|
|
874
|
+
if (line.startsWith("Subject: ")) {
|
|
875
|
+
subject = line.substring(9);
|
|
876
|
+
} else if (line === "--- HTML Content ---") {
|
|
877
|
+
currentSection = "html";
|
|
878
|
+
} else if (line === "--- Attachments ---") {
|
|
879
|
+
break;
|
|
880
|
+
} else if (currentSection === "text" && line.trim()) {
|
|
881
|
+
text += `${line}
|
|
882
|
+
`;
|
|
883
|
+
} else if (currentSection === "html") {
|
|
884
|
+
if (!html) html = "";
|
|
885
|
+
html += `${line}
|
|
886
|
+
`;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
return {
|
|
890
|
+
...originalMessage,
|
|
891
|
+
subject,
|
|
892
|
+
text: text.trim(),
|
|
893
|
+
html: html?.trim()
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
async getCapabilities() {
|
|
897
|
+
return {
|
|
898
|
+
textEncryption: true,
|
|
899
|
+
fileEncryption: true,
|
|
900
|
+
bufferEncryption: true,
|
|
901
|
+
streamEncryption: true,
|
|
902
|
+
emailEncryption: true,
|
|
903
|
+
signing: true,
|
|
904
|
+
verification: true,
|
|
905
|
+
keyGeneration: true,
|
|
906
|
+
keyManagement: true,
|
|
907
|
+
multipleRecipients: true,
|
|
908
|
+
symmetricEncryption: false,
|
|
909
|
+
asymmetricEncryption: true
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
export {
|
|
914
|
+
PGPEncryption
|
|
915
|
+
};
|
|
916
|
+
//# sourceMappingURL=pgp-BIhtvrNo.js.map
|