@ajna-inc/vaults 0.1.0
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 +202 -0
- package/build/VaultsApi.js +263 -0
- package/build/VaultsEvents.js +19 -0
- package/build/VaultsModule.js +71 -0
- package/build/crypto/wasm/VaultCrypto.js +70 -0
- package/build/errors/BadSuiteError.js +34 -0
- package/build/errors/DecryptAeadError.js +34 -0
- package/build/errors/DecryptKemError.js +34 -0
- package/build/errors/PolicyError.js +34 -0
- package/build/errors/VaultError.js +77 -0
- package/build/errors/index.js +16 -0
- package/build/index.js +119 -0
- package/build/messages/CreateVaultMessage.js +126 -0
- package/build/messages/DeleteVaultMessage.js +114 -0
- package/build/messages/DenyAccessMessage.js +114 -0
- package/build/messages/DenyShareMessage.js +120 -0
- package/build/messages/GrantAccessMessage.js +126 -0
- package/build/messages/ProvideShareMessage.js +126 -0
- package/build/messages/RequestAccessMessage.js +120 -0
- package/build/messages/RequestShareMessage.js +120 -0
- package/build/messages/RetrieveVaultMessage.js +108 -0
- package/build/messages/StoreVaultMessage.js +114 -0
- package/build/messages/UpdateVaultMessage.js +120 -0
- package/build/messages/VaultCreatedAckMessage.js +108 -0
- package/build/messages/VaultDataMessage.js +121 -0
- package/build/messages/VaultProblemReportMessage.js +124 -0
- package/build/messages/VaultStoredAckMessage.js +115 -0
- package/build/messages/index.js +36 -0
- package/build/models/ThresholdSession.js +24 -0
- package/build/models/VaultDocument.js +28 -0
- package/build/models/VaultHeader.js +31 -0
- package/build/models/VaultPolicy.js +29 -0
- package/build/models/index.js +20 -0
- package/build/repository/ThresholdSessionRecord.js +117 -0
- package/build/repository/ThresholdSessionRepository.js +216 -0
- package/build/repository/VaultRecord.js +128 -0
- package/build/repository/VaultRepository.js +200 -0
- package/build/repository/index.js +13 -0
- package/build/services/VaultEncryptionService.js +613 -0
- package/build/services/VaultService.js +398 -0
- package/build/services/index.js +8 -0
- package/package.json +45 -0
- package/wasm/README.md +166 -0
- package/wasm/package.json +16 -0
- package/wasm/vault_crypto.d.ts +526 -0
- package/wasm/vault_crypto.js +2137 -0
- package/wasm/vault_crypto_bg.wasm +0 -0
- package/wasm/vault_crypto_bg.wasm.d.ts +66 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
3
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
4
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
5
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
6
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
7
|
+
var _, done = false;
|
|
8
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
9
|
+
var context = {};
|
|
10
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
11
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
12
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
13
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
14
|
+
if (kind === "accessor") {
|
|
15
|
+
if (result === void 0) continue;
|
|
16
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
17
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
18
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
19
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
20
|
+
}
|
|
21
|
+
else if (_ = accept(result)) {
|
|
22
|
+
if (kind === "field") initializers.unshift(_);
|
|
23
|
+
else descriptor[key] = _;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
27
|
+
done = true;
|
|
28
|
+
};
|
|
29
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
30
|
+
var useValue = arguments.length > 2;
|
|
31
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
32
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
33
|
+
}
|
|
34
|
+
return useValue ? value : void 0;
|
|
35
|
+
};
|
|
36
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
37
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
38
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
39
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
40
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
41
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
42
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
46
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
47
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
48
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
49
|
+
function step(op) {
|
|
50
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
51
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
52
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
53
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
54
|
+
switch (op[0]) {
|
|
55
|
+
case 0: case 1: t = op; break;
|
|
56
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
57
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
58
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
59
|
+
default:
|
|
60
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
61
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
62
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
63
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
64
|
+
if (t[2]) _.ops.pop();
|
|
65
|
+
_.trys.pop(); continue;
|
|
66
|
+
}
|
|
67
|
+
op = body.call(thisArg, _);
|
|
68
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
69
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
|
|
73
|
+
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
74
|
+
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
75
|
+
};
|
|
76
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
77
|
+
exports.VaultEncryptionService = void 0;
|
|
78
|
+
var core_1 = require("@credo-ts/core");
|
|
79
|
+
var hash_wasm_1 = require("hash-wasm");
|
|
80
|
+
var VaultCrypto_1 = require("../crypto/wasm/VaultCrypto");
|
|
81
|
+
var errors_1 = require("../errors");
|
|
82
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
83
|
+
// Service
|
|
84
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
85
|
+
/**
|
|
86
|
+
* Vault Encryption Service
|
|
87
|
+
*
|
|
88
|
+
* Provides client-side encryption/decryption for all vault policy modes:
|
|
89
|
+
* - S3 suite: Passphrase-based (Argon2id → AES-256-GCM)
|
|
90
|
+
* - P1 suite: Post-quantum (ML-KEM-768 → AES-256-GCM)
|
|
91
|
+
*
|
|
92
|
+
* All encryption and decryption happens client-side per ZK-Vault spec.
|
|
93
|
+
*/
|
|
94
|
+
var VaultEncryptionService = function () {
|
|
95
|
+
var _classDecorators = [(0, core_1.injectable)()];
|
|
96
|
+
var _classDescriptor;
|
|
97
|
+
var _classExtraInitializers = [];
|
|
98
|
+
var _classThis;
|
|
99
|
+
var VaultEncryptionService = _classThis = /** @class */ (function () {
|
|
100
|
+
function VaultEncryptionService_1() {
|
|
101
|
+
}
|
|
102
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
103
|
+
// S3 SUITE (PASSPHRASE) - For Local Storage
|
|
104
|
+
// Spec §4.1: CEK = Argon2id(passphrase, salt, m=64-128MiB, t=2-3)
|
|
105
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
106
|
+
/**
|
|
107
|
+
* Encrypt data with passphrase (S3 suite)
|
|
108
|
+
* All encryption happens CLIENT-SIDE
|
|
109
|
+
*
|
|
110
|
+
* @param plaintext - Data to encrypt
|
|
111
|
+
* @param passphrase - User passphrase
|
|
112
|
+
* @param options - Encryption options
|
|
113
|
+
* @returns Encrypted vault document
|
|
114
|
+
*/
|
|
115
|
+
VaultEncryptionService_1.prototype.encryptWithPassphrase = function (plaintext, passphrase, options) {
|
|
116
|
+
var _a, _b, _c, _d;
|
|
117
|
+
if (options === void 0) { options = {}; }
|
|
118
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
119
|
+
var salt, argon2Params, cek, nonce, docId, vaultId, epoch, aadFields, aad, encResult, ciphertext, kcmp, header;
|
|
120
|
+
return __generator(this, function (_e) {
|
|
121
|
+
switch (_e.label) {
|
|
122
|
+
case 0:
|
|
123
|
+
salt = (0, VaultCrypto_1.randomBytes)(32);
|
|
124
|
+
argon2Params = {
|
|
125
|
+
memory: (_a = options.memory) !== null && _a !== void 0 ? _a : 65536, // 64 MiB
|
|
126
|
+
iterations: (_b = options.iterations) !== null && _b !== void 0 ? _b : 3,
|
|
127
|
+
parallelism: 1,
|
|
128
|
+
};
|
|
129
|
+
return [4 /*yield*/, this.deriveKeyArgon2(passphrase, salt, argon2Params)
|
|
130
|
+
// 3. Generate nonce (12 bytes for AES-GCM)
|
|
131
|
+
];
|
|
132
|
+
case 1:
|
|
133
|
+
cek = _e.sent();
|
|
134
|
+
nonce = (0, VaultCrypto_1.generateNonceAesGcm)();
|
|
135
|
+
docId = (_c = options.docId) !== null && _c !== void 0 ? _c : (0, VaultCrypto_1.generateUuid)();
|
|
136
|
+
vaultId = (_d = options.vaultId) !== null && _d !== void 0 ? _d : docId;
|
|
137
|
+
epoch = 1;
|
|
138
|
+
aadFields = {
|
|
139
|
+
v: 1,
|
|
140
|
+
suite: 'S3',
|
|
141
|
+
aead: 'AES-256-GCM',
|
|
142
|
+
docId: docId,
|
|
143
|
+
vaultId: vaultId,
|
|
144
|
+
epoch: epoch,
|
|
145
|
+
policy: { mode: 'passphrase' },
|
|
146
|
+
};
|
|
147
|
+
aad = (0, VaultCrypto_1.canonicalAad)(aadFields);
|
|
148
|
+
encResult = (0, VaultCrypto_1.aesGcmEncrypt)(cek, nonce, plaintext, aad);
|
|
149
|
+
ciphertext = encResult.ciphertext();
|
|
150
|
+
kcmp = (0, VaultCrypto_1.keyCommitment)(cek);
|
|
151
|
+
header = {
|
|
152
|
+
v: 1,
|
|
153
|
+
suite: 'S3',
|
|
154
|
+
aead: 'AES-256-GCM',
|
|
155
|
+
docId: docId,
|
|
156
|
+
vaultId: vaultId,
|
|
157
|
+
epoch: epoch,
|
|
158
|
+
nonce: (0, VaultCrypto_1.toBase64Url)(nonce),
|
|
159
|
+
kcmp: (0, VaultCrypto_1.toBase64Url)(kcmp),
|
|
160
|
+
salt: (0, VaultCrypto_1.toBase64Url)(salt),
|
|
161
|
+
argon2: argon2Params,
|
|
162
|
+
policy: { mode: 'passphrase' },
|
|
163
|
+
};
|
|
164
|
+
// 9. Zero out CEK from memory (security best practice)
|
|
165
|
+
this.zeroize(cek);
|
|
166
|
+
return [2 /*return*/, { header: header, ciphertext: ciphertext }];
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Decrypt passphrase-protected vault (S3 suite)
|
|
173
|
+
* All decryption happens CLIENT-SIDE
|
|
174
|
+
*
|
|
175
|
+
* @param vault - Encrypted vault document
|
|
176
|
+
* @param passphrase - User passphrase
|
|
177
|
+
* @returns Decrypted plaintext
|
|
178
|
+
*/
|
|
179
|
+
VaultEncryptionService_1.prototype.decryptWithPassphrase = function (vault, passphrase) {
|
|
180
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
181
|
+
var header, ciphertext, salt, cek, expectedKcmp, aad, nonce, plaintext;
|
|
182
|
+
return __generator(this, function (_a) {
|
|
183
|
+
switch (_a.label) {
|
|
184
|
+
case 0:
|
|
185
|
+
header = vault.header, ciphertext = vault.ciphertext;
|
|
186
|
+
// 1. Validate suite
|
|
187
|
+
if (header.suite !== 'S3' && header.suite !== 'S1') {
|
|
188
|
+
throw new errors_1.BadSuiteError("Expected S1/S3 suite for passphrase vault, got ".concat(header.suite));
|
|
189
|
+
}
|
|
190
|
+
if (!header.salt || !header.argon2) {
|
|
191
|
+
throw new errors_1.PolicyError('Missing salt or argon2 params for passphrase vault');
|
|
192
|
+
}
|
|
193
|
+
salt = (0, VaultCrypto_1.fromBase64Url)(header.salt);
|
|
194
|
+
return [4 /*yield*/, this.deriveKeyArgon2(passphrase, salt, header.argon2)
|
|
195
|
+
// 3. Verify key commitment (spec §9.2 step 3)
|
|
196
|
+
];
|
|
197
|
+
case 1:
|
|
198
|
+
cek = _a.sent();
|
|
199
|
+
expectedKcmp = (0, VaultCrypto_1.fromBase64Url)(header.kcmp);
|
|
200
|
+
if (!(0, VaultCrypto_1.verifyKeyCommitment)(cek, expectedKcmp)) {
|
|
201
|
+
this.zeroize(cek);
|
|
202
|
+
throw new errors_1.DecryptAeadError('Key commitment verification failed - wrong passphrase');
|
|
203
|
+
}
|
|
204
|
+
aad = (0, VaultCrypto_1.canonicalAad)({
|
|
205
|
+
v: header.v,
|
|
206
|
+
suite: header.suite,
|
|
207
|
+
aead: header.aead,
|
|
208
|
+
docId: header.docId,
|
|
209
|
+
vaultId: header.vaultId,
|
|
210
|
+
epoch: header.epoch,
|
|
211
|
+
policy: header.policy,
|
|
212
|
+
});
|
|
213
|
+
nonce = (0, VaultCrypto_1.fromBase64Url)(header.nonce);
|
|
214
|
+
try {
|
|
215
|
+
plaintext = (0, VaultCrypto_1.aesGcmDecrypt)(cek, nonce, ciphertext, aad);
|
|
216
|
+
this.zeroize(cek);
|
|
217
|
+
return [2 /*return*/, plaintext];
|
|
218
|
+
}
|
|
219
|
+
catch (e) {
|
|
220
|
+
this.zeroize(cek);
|
|
221
|
+
throw new errors_1.DecryptAeadError('AEAD decryption failed - data may be corrupted or tampered');
|
|
222
|
+
}
|
|
223
|
+
return [2 /*return*/];
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
};
|
|
228
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
229
|
+
// P1 SUITE (POST-QUANTUM) - For Multi-Party Sharing
|
|
230
|
+
// Spec §4.1: KEM = ML-KEM-768, AEAD = AES-256-GCM
|
|
231
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
232
|
+
/**
|
|
233
|
+
* Encrypt with any-of policy (spec §6.1)
|
|
234
|
+
* Each recipient gets their own KEM-wrapped CEK
|
|
235
|
+
*
|
|
236
|
+
* @param plaintext - Data to encrypt
|
|
237
|
+
* @param recipients - Array of recipient public keys
|
|
238
|
+
* @param options - Encryption options
|
|
239
|
+
* @returns Encrypted vault document
|
|
240
|
+
*/
|
|
241
|
+
VaultEncryptionService_1.prototype.encryptAnyOf = function (plaintext, recipients, options) {
|
|
242
|
+
var _a, _b;
|
|
243
|
+
if (options === void 0) { options = {}; }
|
|
244
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
245
|
+
var cek, nonce, docId, vaultId, epoch, aad, encResult, ciphertext, recipientWraps, kcmp, header;
|
|
246
|
+
return __generator(this, function (_c) {
|
|
247
|
+
if (recipients.length === 0) {
|
|
248
|
+
throw new errors_1.PolicyError('At least one recipient required for any-of policy');
|
|
249
|
+
}
|
|
250
|
+
cek = (0, VaultCrypto_1.generateCek)();
|
|
251
|
+
nonce = (0, VaultCrypto_1.generateNonceAesGcm)();
|
|
252
|
+
docId = (_a = options.docId) !== null && _a !== void 0 ? _a : (0, VaultCrypto_1.generateUuid)();
|
|
253
|
+
vaultId = (_b = options.vaultId) !== null && _b !== void 0 ? _b : docId;
|
|
254
|
+
epoch = 1;
|
|
255
|
+
aad = (0, VaultCrypto_1.canonicalAad)({
|
|
256
|
+
v: 1,
|
|
257
|
+
suite: 'P1',
|
|
258
|
+
aead: 'AES-256-GCM',
|
|
259
|
+
docId: docId,
|
|
260
|
+
vaultId: vaultId,
|
|
261
|
+
epoch: epoch,
|
|
262
|
+
policy: { mode: 'any-of' },
|
|
263
|
+
});
|
|
264
|
+
encResult = (0, VaultCrypto_1.aesGcmEncrypt)(cek, nonce, plaintext, aad);
|
|
265
|
+
ciphertext = encResult.ciphertext();
|
|
266
|
+
recipientWraps = recipients.map(function (r) { return ({
|
|
267
|
+
kid: r.kid,
|
|
268
|
+
kem: 'ML-KEM-768',
|
|
269
|
+
ct: (0, VaultCrypto_1.toBase64Url)((0, VaultCrypto_1.kemWrapCek)(cek, r.publicKey)),
|
|
270
|
+
}); });
|
|
271
|
+
kcmp = (0, VaultCrypto_1.keyCommitment)(cek);
|
|
272
|
+
header = {
|
|
273
|
+
v: 1,
|
|
274
|
+
suite: 'P1',
|
|
275
|
+
aead: 'AES-256-GCM',
|
|
276
|
+
docId: docId,
|
|
277
|
+
vaultId: vaultId,
|
|
278
|
+
epoch: epoch,
|
|
279
|
+
nonce: (0, VaultCrypto_1.toBase64Url)(nonce),
|
|
280
|
+
kcmp: (0, VaultCrypto_1.toBase64Url)(kcmp),
|
|
281
|
+
policy: { mode: 'any-of' },
|
|
282
|
+
recipients: recipientWraps,
|
|
283
|
+
};
|
|
284
|
+
this.zeroize(cek);
|
|
285
|
+
return [2 /*return*/, { header: header, ciphertext: ciphertext }];
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* Decrypt any-of vault (spec §9.2)
|
|
291
|
+
* Try the specified recipient's wrap
|
|
292
|
+
*
|
|
293
|
+
* @param vault - Encrypted vault document
|
|
294
|
+
* @param recipientSecretKey - Recipient's ML-KEM secret key
|
|
295
|
+
* @param recipientKid - Recipient's key identifier
|
|
296
|
+
* @returns Decrypted plaintext
|
|
297
|
+
*/
|
|
298
|
+
VaultEncryptionService_1.prototype.decryptAnyOf = function (vault, recipientSecretKey, recipientKid) {
|
|
299
|
+
var _a;
|
|
300
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
301
|
+
var header, ciphertext, recipient, wrappedCek, cek, aad, nonce, plaintext;
|
|
302
|
+
return __generator(this, function (_b) {
|
|
303
|
+
header = vault.header, ciphertext = vault.ciphertext;
|
|
304
|
+
if (((_a = header.policy) === null || _a === void 0 ? void 0 : _a.mode) !== 'any-of' || !header.recipients) {
|
|
305
|
+
throw new errors_1.PolicyError('Not an any-of policy vault');
|
|
306
|
+
}
|
|
307
|
+
recipient = header.recipients.find(function (r) { return r.kid === recipientKid; });
|
|
308
|
+
if (!recipient) {
|
|
309
|
+
throw new errors_1.DecryptKemError("No recipient wrap found for kid: ".concat(recipientKid));
|
|
310
|
+
}
|
|
311
|
+
wrappedCek = (0, VaultCrypto_1.fromBase64Url)(recipient.ct);
|
|
312
|
+
try {
|
|
313
|
+
cek = (0, VaultCrypto_1.kemUnwrapCek)(wrappedCek, recipientSecretKey);
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
throw new errors_1.DecryptKemError('Failed to unwrap CEK - invalid key or corrupted wrap');
|
|
317
|
+
}
|
|
318
|
+
// Verify key commitment
|
|
319
|
+
if (!(0, VaultCrypto_1.verifyKeyCommitment)(cek, (0, VaultCrypto_1.fromBase64Url)(header.kcmp))) {
|
|
320
|
+
this.zeroize(cek);
|
|
321
|
+
throw new errors_1.DecryptAeadError('Key commitment verification failed');
|
|
322
|
+
}
|
|
323
|
+
aad = (0, VaultCrypto_1.canonicalAad)({
|
|
324
|
+
v: header.v,
|
|
325
|
+
suite: header.suite,
|
|
326
|
+
aead: header.aead,
|
|
327
|
+
docId: header.docId,
|
|
328
|
+
vaultId: header.vaultId,
|
|
329
|
+
epoch: header.epoch,
|
|
330
|
+
policy: header.policy,
|
|
331
|
+
});
|
|
332
|
+
nonce = (0, VaultCrypto_1.fromBase64Url)(header.nonce);
|
|
333
|
+
try {
|
|
334
|
+
plaintext = (0, VaultCrypto_1.aesGcmDecrypt)(cek, nonce, ciphertext, aad);
|
|
335
|
+
this.zeroize(cek);
|
|
336
|
+
return [2 /*return*/, plaintext];
|
|
337
|
+
}
|
|
338
|
+
catch (e) {
|
|
339
|
+
this.zeroize(cek);
|
|
340
|
+
throw new errors_1.DecryptAeadError('AEAD decryption failed');
|
|
341
|
+
}
|
|
342
|
+
return [2 /*return*/];
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
};
|
|
346
|
+
/**
|
|
347
|
+
* Encrypt with all-of policy (spec §6.2)
|
|
348
|
+
* Requires ALL listed keys to decrypt via HKDF-join
|
|
349
|
+
*
|
|
350
|
+
* @param plaintext - Data to encrypt
|
|
351
|
+
* @param participants - Array of participant public keys
|
|
352
|
+
* @param options - Encryption options
|
|
353
|
+
* @returns Encrypted vault document
|
|
354
|
+
*/
|
|
355
|
+
VaultEncryptionService_1.prototype.encryptAllOf = function (plaintext, participants, options) {
|
|
356
|
+
var _a, _b;
|
|
357
|
+
if (options === void 0) { options = {}; }
|
|
358
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
359
|
+
var cek, nonce, wrapNonce, docId, vaultId, epoch, keyMaterials, context, wrapKey, wrappedCekResult, aad, encResult, ciphertext, header;
|
|
360
|
+
return __generator(this, function (_c) {
|
|
361
|
+
if (participants.length < 2) {
|
|
362
|
+
throw new errors_1.PolicyError('At least 2 participants required for all-of policy');
|
|
363
|
+
}
|
|
364
|
+
cek = (0, VaultCrypto_1.generateCek)();
|
|
365
|
+
nonce = (0, VaultCrypto_1.generateNonceAesGcm)();
|
|
366
|
+
wrapNonce = (0, VaultCrypto_1.generateNonceAesGcm)();
|
|
367
|
+
docId = (_a = options.docId) !== null && _a !== void 0 ? _a : (0, VaultCrypto_1.generateUuid)();
|
|
368
|
+
vaultId = (_b = options.vaultId) !== null && _b !== void 0 ? _b : docId;
|
|
369
|
+
epoch = 1;
|
|
370
|
+
keyMaterials = participants.map(function (p) { return ({
|
|
371
|
+
kid: p.kid,
|
|
372
|
+
key: p.publicKey,
|
|
373
|
+
}); });
|
|
374
|
+
context = "wrap:doc:".concat(docId, ":epoch:").concat(epoch);
|
|
375
|
+
wrapKey = (0, VaultCrypto_1.hkdfJoin)(keyMaterials, context);
|
|
376
|
+
wrappedCekResult = (0, VaultCrypto_1.aesGcmEncrypt)(wrapKey, wrapNonce, cek, new Uint8Array(0));
|
|
377
|
+
aad = (0, VaultCrypto_1.canonicalAad)({
|
|
378
|
+
v: 1,
|
|
379
|
+
suite: 'P1',
|
|
380
|
+
aead: 'AES-256-GCM',
|
|
381
|
+
docId: docId,
|
|
382
|
+
vaultId: vaultId,
|
|
383
|
+
epoch: epoch,
|
|
384
|
+
policy: { mode: 'all-of' },
|
|
385
|
+
});
|
|
386
|
+
encResult = (0, VaultCrypto_1.aesGcmEncrypt)(cek, nonce, plaintext, aad);
|
|
387
|
+
ciphertext = encResult.ciphertext();
|
|
388
|
+
header = {
|
|
389
|
+
v: 1,
|
|
390
|
+
suite: 'P1',
|
|
391
|
+
aead: 'AES-256-GCM',
|
|
392
|
+
docId: docId,
|
|
393
|
+
vaultId: vaultId,
|
|
394
|
+
epoch: epoch,
|
|
395
|
+
nonce: (0, VaultCrypto_1.toBase64Url)(nonce),
|
|
396
|
+
kcmp: (0, VaultCrypto_1.toBase64Url)((0, VaultCrypto_1.keyCommitment)(cek)),
|
|
397
|
+
policy: { mode: 'all-of' },
|
|
398
|
+
wrap_all: {
|
|
399
|
+
alg: 'HKDF-join',
|
|
400
|
+
kids: participants.map(function (p) { return p.kid; }),
|
|
401
|
+
ct: (0, VaultCrypto_1.toBase64Url)(wrappedCekResult.ciphertext()),
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
this.zeroize(cek);
|
|
405
|
+
this.zeroize(wrapKey);
|
|
406
|
+
return [2 /*return*/, { header: header, ciphertext: ciphertext }];
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
};
|
|
410
|
+
/**
|
|
411
|
+
* Encrypt with threshold policy (spec §6.3)
|
|
412
|
+
* Any t-of-n participants can decrypt
|
|
413
|
+
*
|
|
414
|
+
* @param plaintext - Data to encrypt
|
|
415
|
+
* @param threshold - Minimum shares required (t)
|
|
416
|
+
* @param participants - Array of participant public keys (n)
|
|
417
|
+
* @param options - Encryption options
|
|
418
|
+
* @returns Encrypted vault document
|
|
419
|
+
*/
|
|
420
|
+
VaultEncryptionService_1.prototype.encryptThreshold = function (plaintext, threshold, participants, options) {
|
|
421
|
+
var _a, _b;
|
|
422
|
+
if (options === void 0) { options = {}; }
|
|
423
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
424
|
+
var n, cek, nonce, docId, vaultId, epoch, shares, thresholdShares, aad, encResult, ciphertext, header, _i, shares_1, share;
|
|
425
|
+
return __generator(this, function (_c) {
|
|
426
|
+
n = participants.length;
|
|
427
|
+
if (threshold < 1 || threshold > n) {
|
|
428
|
+
throw new errors_1.PolicyError("Invalid threshold: ".concat(threshold, " of ").concat(n));
|
|
429
|
+
}
|
|
430
|
+
cek = (0, VaultCrypto_1.generateCek)();
|
|
431
|
+
nonce = (0, VaultCrypto_1.generateNonceAesGcm)();
|
|
432
|
+
docId = (_a = options.docId) !== null && _a !== void 0 ? _a : (0, VaultCrypto_1.generateUuid)();
|
|
433
|
+
vaultId = (_b = options.vaultId) !== null && _b !== void 0 ? _b : docId;
|
|
434
|
+
epoch = 1;
|
|
435
|
+
shares = (0, VaultCrypto_1.shamirSplit)(cek, threshold, n);
|
|
436
|
+
thresholdShares = participants.map(function (p, i) { return ({
|
|
437
|
+
kid: p.kid,
|
|
438
|
+
enc_share: {
|
|
439
|
+
kem: 'ML-KEM-768',
|
|
440
|
+
ct: (0, VaultCrypto_1.toBase64Url)((0, VaultCrypto_1.kemWrapCek)(shares[i], p.publicKey)),
|
|
441
|
+
},
|
|
442
|
+
}); });
|
|
443
|
+
aad = (0, VaultCrypto_1.canonicalAad)({
|
|
444
|
+
v: 1,
|
|
445
|
+
suite: 'P1',
|
|
446
|
+
aead: 'AES-256-GCM',
|
|
447
|
+
docId: docId,
|
|
448
|
+
vaultId: vaultId,
|
|
449
|
+
epoch: epoch,
|
|
450
|
+
policy: { mode: 'threshold', t: threshold, n: n },
|
|
451
|
+
});
|
|
452
|
+
encResult = (0, VaultCrypto_1.aesGcmEncrypt)(cek, nonce, plaintext, aad);
|
|
453
|
+
ciphertext = encResult.ciphertext();
|
|
454
|
+
header = {
|
|
455
|
+
v: 1,
|
|
456
|
+
suite: 'P1',
|
|
457
|
+
aead: 'AES-256-GCM',
|
|
458
|
+
docId: docId,
|
|
459
|
+
vaultId: vaultId,
|
|
460
|
+
epoch: epoch,
|
|
461
|
+
nonce: (0, VaultCrypto_1.toBase64Url)(nonce),
|
|
462
|
+
kcmp: (0, VaultCrypto_1.toBase64Url)((0, VaultCrypto_1.keyCommitment)(cek)),
|
|
463
|
+
policy: { mode: 'threshold', t: threshold, n: n },
|
|
464
|
+
shares: thresholdShares,
|
|
465
|
+
};
|
|
466
|
+
this.zeroize(cek);
|
|
467
|
+
// Zero out shares
|
|
468
|
+
for (_i = 0, shares_1 = shares; _i < shares_1.length; _i++) {
|
|
469
|
+
share = shares_1[_i];
|
|
470
|
+
this.zeroize(share);
|
|
471
|
+
}
|
|
472
|
+
return [2 /*return*/, { header: header, ciphertext: ciphertext }];
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
};
|
|
476
|
+
/**
|
|
477
|
+
* Decrypt threshold vault with collected shares
|
|
478
|
+
*
|
|
479
|
+
* @param vault - Encrypted vault document
|
|
480
|
+
* @param decryptedShares - Array of decrypted shares (at least t shares)
|
|
481
|
+
* @returns Decrypted plaintext
|
|
482
|
+
*/
|
|
483
|
+
VaultEncryptionService_1.prototype.decryptThreshold = function (vault, decryptedShares) {
|
|
484
|
+
var _a, _b;
|
|
485
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
486
|
+
var header, ciphertext, threshold, shareData, cek, aad, nonce, plaintext;
|
|
487
|
+
return __generator(this, function (_c) {
|
|
488
|
+
header = vault.header, ciphertext = vault.ciphertext;
|
|
489
|
+
if (((_a = header.policy) === null || _a === void 0 ? void 0 : _a.mode) !== 'threshold') {
|
|
490
|
+
throw new errors_1.PolicyError('Not a threshold policy vault');
|
|
491
|
+
}
|
|
492
|
+
threshold = (_b = header.policy.t) !== null && _b !== void 0 ? _b : 1;
|
|
493
|
+
if (decryptedShares.length < threshold) {
|
|
494
|
+
throw new errors_1.PolicyError("Need ".concat(threshold, " shares, got ").concat(decryptedShares.length));
|
|
495
|
+
}
|
|
496
|
+
shareData = decryptedShares.map(function (s) { return ({
|
|
497
|
+
index: s.index,
|
|
498
|
+
data: s.share,
|
|
499
|
+
}); });
|
|
500
|
+
cek = (0, VaultCrypto_1.shamirReconstruct)(shareData);
|
|
501
|
+
// Verify key commitment
|
|
502
|
+
if (!(0, VaultCrypto_1.verifyKeyCommitment)(cek, (0, VaultCrypto_1.fromBase64Url)(header.kcmp))) {
|
|
503
|
+
this.zeroize(cek);
|
|
504
|
+
throw new errors_1.DecryptAeadError('Key commitment verification failed');
|
|
505
|
+
}
|
|
506
|
+
aad = (0, VaultCrypto_1.canonicalAad)({
|
|
507
|
+
v: header.v,
|
|
508
|
+
suite: header.suite,
|
|
509
|
+
aead: header.aead,
|
|
510
|
+
docId: header.docId,
|
|
511
|
+
vaultId: header.vaultId,
|
|
512
|
+
epoch: header.epoch,
|
|
513
|
+
policy: header.policy,
|
|
514
|
+
});
|
|
515
|
+
nonce = (0, VaultCrypto_1.fromBase64Url)(header.nonce);
|
|
516
|
+
try {
|
|
517
|
+
plaintext = (0, VaultCrypto_1.aesGcmDecrypt)(cek, nonce, ciphertext, aad);
|
|
518
|
+
this.zeroize(cek);
|
|
519
|
+
return [2 /*return*/, plaintext];
|
|
520
|
+
}
|
|
521
|
+
catch (e) {
|
|
522
|
+
this.zeroize(cek);
|
|
523
|
+
throw new errors_1.DecryptAeadError('AEAD decryption failed');
|
|
524
|
+
}
|
|
525
|
+
return [2 /*return*/];
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
};
|
|
529
|
+
/**
|
|
530
|
+
* Unwrap a single threshold share for a participant
|
|
531
|
+
*
|
|
532
|
+
* @param vault - Encrypted vault document
|
|
533
|
+
* @param participantSecretKey - Participant's ML-KEM secret key
|
|
534
|
+
* @param participantKid - Participant's key identifier
|
|
535
|
+
* @returns Decrypted share with its index
|
|
536
|
+
*/
|
|
537
|
+
VaultEncryptionService_1.prototype.unwrapThresholdShare = function (vault, participantSecretKey, participantKid) {
|
|
538
|
+
var _a;
|
|
539
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
540
|
+
var header, shareIndex, shareEntry, wrappedShare, share;
|
|
541
|
+
return __generator(this, function (_b) {
|
|
542
|
+
header = vault.header;
|
|
543
|
+
if (((_a = header.policy) === null || _a === void 0 ? void 0 : _a.mode) !== 'threshold' || !header.shares) {
|
|
544
|
+
throw new errors_1.PolicyError('Not a threshold policy vault');
|
|
545
|
+
}
|
|
546
|
+
shareIndex = header.shares.findIndex(function (s) { return s.kid === participantKid; });
|
|
547
|
+
if (shareIndex === -1) {
|
|
548
|
+
throw new errors_1.DecryptKemError("No share found for kid: ".concat(participantKid));
|
|
549
|
+
}
|
|
550
|
+
shareEntry = header.shares[shareIndex];
|
|
551
|
+
wrappedShare = (0, VaultCrypto_1.fromBase64Url)(shareEntry.enc_share.ct);
|
|
552
|
+
try {
|
|
553
|
+
share = (0, VaultCrypto_1.kemUnwrapCek)(wrappedShare, participantSecretKey);
|
|
554
|
+
}
|
|
555
|
+
catch (e) {
|
|
556
|
+
throw new errors_1.DecryptKemError('Failed to unwrap share - invalid key');
|
|
557
|
+
}
|
|
558
|
+
// Return with 1-based index (Shamir convention)
|
|
559
|
+
return [2 /*return*/, {
|
|
560
|
+
index: shareIndex + 1,
|
|
561
|
+
share: share,
|
|
562
|
+
}];
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
};
|
|
566
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
567
|
+
// HELPER METHODS
|
|
568
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
569
|
+
/**
|
|
570
|
+
* Derive key from passphrase using Argon2id
|
|
571
|
+
* Spec §4.1 S1/S3 suite
|
|
572
|
+
*/
|
|
573
|
+
VaultEncryptionService_1.prototype.deriveKeyArgon2 = function (passphrase, salt, params) {
|
|
574
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
575
|
+
var hash;
|
|
576
|
+
return __generator(this, function (_a) {
|
|
577
|
+
switch (_a.label) {
|
|
578
|
+
case 0: return [4 /*yield*/, (0, hash_wasm_1.argon2id)({
|
|
579
|
+
password: passphrase,
|
|
580
|
+
salt: salt,
|
|
581
|
+
parallelism: params.parallelism,
|
|
582
|
+
iterations: params.iterations,
|
|
583
|
+
memorySize: params.memory,
|
|
584
|
+
hashLength: 32,
|
|
585
|
+
outputType: 'binary',
|
|
586
|
+
})];
|
|
587
|
+
case 1:
|
|
588
|
+
hash = _a.sent();
|
|
589
|
+
return [2 /*return*/, new Uint8Array(hash)];
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
};
|
|
594
|
+
/**
|
|
595
|
+
* Zero out sensitive data from memory
|
|
596
|
+
* Security best practice to minimize exposure window
|
|
597
|
+
*/
|
|
598
|
+
VaultEncryptionService_1.prototype.zeroize = function (data) {
|
|
599
|
+
data.fill(0);
|
|
600
|
+
};
|
|
601
|
+
return VaultEncryptionService_1;
|
|
602
|
+
}());
|
|
603
|
+
__setFunctionName(_classThis, "VaultEncryptionService");
|
|
604
|
+
(function () {
|
|
605
|
+
var _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
606
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
607
|
+
VaultEncryptionService = _classThis = _classDescriptor.value;
|
|
608
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
609
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
610
|
+
})();
|
|
611
|
+
return VaultEncryptionService = _classThis;
|
|
612
|
+
}();
|
|
613
|
+
exports.VaultEncryptionService = VaultEncryptionService;
|