@push.rocks/smartsecret 1.1.0 → 1.2.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.
@@ -0,0 +1,657 @@
1
+ import * as plugins from './smartsecret.plugins.js';
2
+ import {
3
+ SmartSecretKernelStoreError,
4
+ createSmartSecretKernelStoreError,
5
+ } from './smartsecret.kernel.error.js';
6
+
7
+ export const smartSecretKernelDefaultTimeoutMs = 5_000;
8
+ export const smartSecretKernelMaximumTimeoutMs = 60_000;
9
+ export const smartSecretKernelMaximumEntryBytes = 1_024;
10
+ export const smartSecretKernelMaximumLegacyBytes = 16 * 1_024;
11
+ export const smartSecretKernelMaximumMessageBytes = 32 * 1_024;
12
+ export const smartSecretKernelLegacyService = 'global.idp.devidp';
13
+
14
+ const serviceDomain = Buffer.from(
15
+ '@push.rocks/smartsecret/kernel-service/v1\u0000',
16
+ 'utf8',
17
+ );
18
+ const entryDomain = Buffer.from(
19
+ '@push.rocks/smartsecret/kernel-entry/v1\u0000',
20
+ 'utf8',
21
+ );
22
+ const mutexDomain = Buffer.from(
23
+ '@push.rocks/smartsecret/kernel-mutex/v1\u0000',
24
+ 'utf8',
25
+ );
26
+ const canonicalDigestPattern = /^[A-Za-z0-9_-]{43}$/;
27
+ const legacyAccountPattern = /^v1:[a-f0-9]{64}$/;
28
+
29
+ export type TSmartSecretKernelDomainErrorCode =
30
+ | 'ENTRY_CORRUPT'
31
+ | 'INVALID_REQUEST'
32
+ | 'KERNEL_UNAVAILABLE'
33
+ | 'MUTATION_OUTCOME_UNKNOWN'
34
+ | 'ROOT_CONFLICT'
35
+ | 'SOURCE_CHANGED';
36
+
37
+ export interface ISmartSecretKernelDomainErrorResult {
38
+ outcome: 'domainError';
39
+ code: TSmartSecretKernelDomainErrorCode;
40
+ }
41
+
42
+ export interface ISmartSecretKernelOperationOptions {
43
+ timeoutMs?: number;
44
+ signal?: AbortSignal;
45
+ }
46
+
47
+ export interface ISmartSecretKernelStoreOptions {
48
+ service: string;
49
+ }
50
+
51
+ export interface INormalizedSmartSecretKernelOperationOptions {
52
+ timeoutMs: number;
53
+ signal?: AbortSignal;
54
+ }
55
+
56
+ export interface ISmartSecretKernelGetSuccessAbsent {
57
+ outcome: 'success';
58
+ found: false;
59
+ }
60
+
61
+ export interface ISmartSecretKernelGetSuccessPresent {
62
+ outcome: 'success';
63
+ found: true;
64
+ value: string;
65
+ }
66
+
67
+ export type TSmartSecretKernelGetResult =
68
+ | ISmartSecretKernelDomainErrorResult
69
+ | ISmartSecretKernelGetSuccessAbsent
70
+ | ISmartSecretKernelGetSuccessPresent;
71
+
72
+ export interface ISmartSecretKernelMutationSuccess {
73
+ outcome: 'success';
74
+ }
75
+
76
+ export type TSmartSecretKernelSetResult =
77
+ | ISmartSecretKernelDomainErrorResult
78
+ | ISmartSecretKernelMutationSuccess;
79
+
80
+ export interface ISmartSecretKernelDeleteSuccess {
81
+ outcome: 'success';
82
+ deleted: boolean;
83
+ }
84
+
85
+ export type TSmartSecretKernelDeleteResult =
86
+ | ISmartSecretKernelDomainErrorResult
87
+ | ISmartSecretKernelDeleteSuccess;
88
+
89
+ export interface ISmartSecretKernelLegacySuccessAbsent {
90
+ outcome: 'success';
91
+ found: false;
92
+ }
93
+
94
+ export interface ISmartSecretKernelLegacySuccessPresent {
95
+ outcome: 'success';
96
+ found: true;
97
+ value: string;
98
+ sourceSerial: number;
99
+ sessionRootSerial: number | null;
100
+ persistentRootSerial: number;
101
+ foundInSession: boolean;
102
+ foundInPersistent: boolean;
103
+ byteLength: number;
104
+ sha256: string;
105
+ }
106
+
107
+ export type TSmartSecretKernelLegacyReadResult =
108
+ | ISmartSecretKernelDomainErrorResult
109
+ | ISmartSecretKernelLegacySuccessAbsent
110
+ | ISmartSecretKernelLegacySuccessPresent;
111
+
112
+ export interface ISmartSecretKernelLegacyDeleteSuccess {
113
+ outcome: 'success';
114
+ status: 'deleted' | 'alreadyAbsent';
115
+ }
116
+
117
+ export type TSmartSecretKernelLegacyDeleteResult =
118
+ | ISmartSecretKernelDomainErrorResult
119
+ | ISmartSecretKernelLegacyDeleteSuccess;
120
+
121
+ export interface ISmartSecretKernelLegacyDeleteParams {
122
+ account: string;
123
+ sourceSerial: number;
124
+ sessionRootSerial: number | null;
125
+ persistentRootSerial: number;
126
+ foundInSession: boolean;
127
+ foundInPersistent: boolean;
128
+ byteLength: number;
129
+ sha256: string;
130
+ }
131
+
132
+ export type TSmartSecretKernelCommands = {
133
+ getEntry: {
134
+ params: { description: string };
135
+ result: TSmartSecretKernelGetResult;
136
+ };
137
+ setEntry: {
138
+ params: { description: string; value: string };
139
+ result: TSmartSecretKernelSetResult;
140
+ };
141
+ deleteEntry: {
142
+ params: { description: string };
143
+ result: TSmartSecretKernelDeleteResult;
144
+ };
145
+ readDevIdpV1Legacy: {
146
+ params: { account: string };
147
+ result: TSmartSecretKernelLegacyReadResult;
148
+ };
149
+ deleteDevIdpV1Legacy: {
150
+ params: ISmartSecretKernelLegacyDeleteParams;
151
+ result: TSmartSecretKernelLegacyDeleteResult;
152
+ };
153
+ };
154
+
155
+ const domainErrorCodes: ReadonlySet<string> = new Set([
156
+ 'ENTRY_CORRUPT',
157
+ 'INVALID_REQUEST',
158
+ 'KERNEL_UNAVAILABLE',
159
+ 'MUTATION_OUTCOME_UNKNOWN',
160
+ 'ROOT_CONFLICT',
161
+ 'SOURCE_CHANGED',
162
+ ]);
163
+
164
+ const isSmartSecretKernelDomainErrorCode = (
165
+ valueArg: string,
166
+ ): valueArg is TSmartSecretKernelDomainErrorCode => domainErrorCodes.has(valueArg);
167
+
168
+ const requireExactObject = (
169
+ valueArg: unknown,
170
+ keysArg: readonly string[],
171
+ inputArg: boolean,
172
+ ): Record<string, unknown> => {
173
+ const fail = (): never => {
174
+ throw createSmartSecretKernelStoreError(
175
+ inputArg ? 'INVALID_ARGUMENT' : 'WORKER_INTEGRITY_FAILED',
176
+ );
177
+ };
178
+ try {
179
+ if (valueArg === null || typeof valueArg !== 'object' || Array.isArray(valueArg)) {
180
+ return fail();
181
+ }
182
+ const prototype = Object.getPrototypeOf(valueArg);
183
+ if (prototype !== Object.prototype && prototype !== null) {
184
+ return fail();
185
+ }
186
+ const ownKeys = Reflect.ownKeys(valueArg);
187
+ if (
188
+ ownKeys.length !== keysArg.length
189
+ || ownKeys.some((keyArg) => typeof keyArg !== 'string' || !keysArg.includes(keyArg))
190
+ || keysArg.some((keyArg) => !ownKeys.includes(keyArg))
191
+ ) {
192
+ return fail();
193
+ }
194
+ const result: Record<string, unknown> = Object.create(null);
195
+ for (const key of keysArg) {
196
+ const descriptor = Object.getOwnPropertyDescriptor(valueArg, key);
197
+ if (!descriptor?.enumerable || !('value' in descriptor)) {
198
+ return fail();
199
+ }
200
+ result[key] = descriptor.value;
201
+ }
202
+ return result;
203
+ } catch {
204
+ return fail();
205
+ }
206
+ };
207
+
208
+ const requireAllowedInputObject = (
209
+ valueArg: unknown,
210
+ allowedKeysArg: readonly string[],
211
+ ): Record<string, unknown> => {
212
+ const fail = (): never => {
213
+ throw createSmartSecretKernelStoreError('INVALID_ARGUMENT');
214
+ };
215
+ try {
216
+ if (valueArg === null || typeof valueArg !== 'object' || Array.isArray(valueArg)) {
217
+ return fail();
218
+ }
219
+ const prototype = Object.getPrototypeOf(valueArg);
220
+ if (prototype !== Object.prototype && prototype !== null) {
221
+ return fail();
222
+ }
223
+ const ownKeys = Reflect.ownKeys(valueArg);
224
+ if (
225
+ ownKeys.some((keyArg) =>
226
+ typeof keyArg !== 'string' || !allowedKeysArg.includes(keyArg)
227
+ )
228
+ ) {
229
+ return fail();
230
+ }
231
+ const result: Record<string, unknown> = Object.create(null);
232
+ for (const key of ownKeys) {
233
+ if (typeof key !== 'string') return fail();
234
+ const descriptor = Object.getOwnPropertyDescriptor(valueArg, key);
235
+ if (!descriptor?.enumerable || !('value' in descriptor)) {
236
+ return fail();
237
+ }
238
+ result[key] = descriptor.value;
239
+ }
240
+ return result;
241
+ } catch (error) {
242
+ if (error instanceof SmartSecretKernelStoreError) throw error;
243
+ return fail();
244
+ }
245
+ };
246
+
247
+ const requireValidScalarString = (valueArg: unknown): string => {
248
+ if (typeof valueArg !== 'string') {
249
+ throw createSmartSecretKernelStoreError('INVALID_ARGUMENT');
250
+ }
251
+ for (let index = 0; index < valueArg.length; index++) {
252
+ const codeUnit = valueArg.charCodeAt(index);
253
+ if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
254
+ const next = valueArg.charCodeAt(index + 1);
255
+ if (index + 1 >= valueArg.length || next < 0xdc00 || next > 0xdfff) {
256
+ throw createSmartSecretKernelStoreError('INVALID_ARGUMENT');
257
+ }
258
+ index++;
259
+ } else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
260
+ throw createSmartSecretKernelStoreError('INVALID_ARGUMENT');
261
+ }
262
+ }
263
+ const byteLength = Buffer.byteLength(valueArg, 'utf8');
264
+ if (byteLength < 1 || byteLength > 1_024) {
265
+ throw createSmartSecretKernelStoreError('INVALID_ARGUMENT');
266
+ }
267
+ return valueArg;
268
+ };
269
+
270
+ const lengthPrefix = (lengthArg: number): Buffer => {
271
+ const prefix = Buffer.alloc(4);
272
+ prefix.writeUInt32BE(lengthArg, 0);
273
+ return prefix;
274
+ };
275
+
276
+ const digestDescription = (
277
+ prefixArg: string,
278
+ partsArg: readonly Uint8Array[],
279
+ ): string => {
280
+ const hash = plugins.crypto.createHash('sha256');
281
+ for (const part of partsArg) hash.update(part);
282
+ return `${prefixArg}${hash.digest('base64url')}`;
283
+ };
284
+
285
+ export const normalizeSmartSecretKernelService = (valueArg: unknown): string =>
286
+ requireValidScalarString(valueArg);
287
+
288
+ export const normalizeSmartSecretKernelStoreOptions = (
289
+ valueArg: unknown,
290
+ ): ISmartSecretKernelStoreOptions => {
291
+ const value = requireExactObject(valueArg, ['service'], true);
292
+ return { service: normalizeSmartSecretKernelService(value.service) };
293
+ };
294
+
295
+ export const normalizeSmartSecretKernelAccount = (valueArg: unknown): string =>
296
+ requireValidScalarString(valueArg);
297
+
298
+ export const normalizeDevIdpV1LegacyAccount = (valueArg: unknown): string => {
299
+ const account = requireValidScalarString(valueArg);
300
+ if (!legacyAccountPattern.test(account)) {
301
+ throw createSmartSecretKernelStoreError('INVALID_ARGUMENT');
302
+ }
303
+ return account;
304
+ };
305
+
306
+ export const deriveSmartSecretKernelServiceDescription = (serviceArg: string): string => {
307
+ const service = Buffer.from(normalizeSmartSecretKernelService(serviceArg), 'utf8');
308
+ return digestDescription('smartsecret-service:v1:', [
309
+ serviceDomain,
310
+ lengthPrefix(service.byteLength),
311
+ service,
312
+ ]);
313
+ };
314
+
315
+ export const deriveSmartSecretKernelEntryDescription = (
316
+ serviceArg: string,
317
+ accountArg: string,
318
+ ): string => {
319
+ const service = Buffer.from(normalizeSmartSecretKernelService(serviceArg), 'utf8');
320
+ const account = Buffer.from(normalizeSmartSecretKernelAccount(accountArg), 'utf8');
321
+ return digestDescription('smartsecret-entry:v1:', [
322
+ entryDomain,
323
+ lengthPrefix(service.byteLength),
324
+ service,
325
+ lengthPrefix(account.byteLength),
326
+ account,
327
+ ]);
328
+ };
329
+
330
+ export const deriveSmartSecretKernelMutexNamespace = (serviceArg: string): string => {
331
+ const service = Buffer.from(normalizeSmartSecretKernelService(serviceArg), 'utf8');
332
+ return digestDescription('smartsecret-kernel:v1:', [
333
+ mutexDomain,
334
+ lengthPrefix(service.byteLength),
335
+ service,
336
+ ]);
337
+ };
338
+
339
+ export const deriveDevIdpV1LegacyDescription = (accountArg: string): string =>
340
+ `keyring:${normalizeDevIdpV1LegacyAccount(accountArg)}@${smartSecretKernelLegacyService}`;
341
+
342
+ export const normalizeSmartSecretKernelValue = (valueArg: unknown): Uint8Array => {
343
+ try {
344
+ if (
345
+ !(valueArg instanceof Uint8Array)
346
+ || valueArg.byteLength > smartSecretKernelMaximumEntryBytes
347
+ ) {
348
+ throw createSmartSecretKernelStoreError('INVALID_ARGUMENT');
349
+ }
350
+ return new Uint8Array(valueArg);
351
+ } catch (error) {
352
+ if (error instanceof SmartSecretKernelStoreError) throw error;
353
+ throw createSmartSecretKernelStoreError('INVALID_ARGUMENT');
354
+ }
355
+ };
356
+
357
+ export const encodeCanonicalBase64Url = (valueArg: Uint8Array): string => {
358
+ const value = Buffer.from(valueArg);
359
+ try {
360
+ return value.toString('base64url');
361
+ } finally {
362
+ value.fill(0);
363
+ }
364
+ };
365
+
366
+ const decodeCanonicalBase64Url = (
367
+ valueArg: unknown,
368
+ maximumBytesArg: number,
369
+ minimumBytesArg = 0,
370
+ ): Uint8Array => {
371
+ const maximumEncodedLength = Math.ceil(maximumBytesArg * 4 / 3);
372
+ if (
373
+ typeof valueArg !== 'string'
374
+ || valueArg.length > maximumEncodedLength
375
+ || valueArg.length % 4 === 1
376
+ || !/^[A-Za-z0-9_-]*$/.test(valueArg)
377
+ ) {
378
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
379
+ }
380
+ const decoded = Buffer.from(valueArg, 'base64url');
381
+ if (
382
+ decoded.byteLength < minimumBytesArg
383
+ || decoded.byteLength > maximumBytesArg
384
+ || decoded.toString('base64url') !== valueArg
385
+ ) {
386
+ decoded.fill(0);
387
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
388
+ }
389
+ const result = new Uint8Array(decoded);
390
+ decoded.fill(0);
391
+ return result;
392
+ };
393
+
394
+ export const normalizeSmartSecretKernelOperationOptions = (
395
+ valueArg: ISmartSecretKernelOperationOptions | undefined,
396
+ ): INormalizedSmartSecretKernelOperationOptions => {
397
+ if (valueArg === undefined) return { timeoutMs: smartSecretKernelDefaultTimeoutMs };
398
+ const options = requireAllowedInputObject(valueArg, ['timeoutMs', 'signal']);
399
+ const timeoutMs = Object.hasOwn(options, 'timeoutMs')
400
+ ? options.timeoutMs
401
+ : smartSecretKernelDefaultTimeoutMs;
402
+ if (
403
+ typeof timeoutMs !== 'number'
404
+ || !Number.isSafeInteger(timeoutMs)
405
+ || timeoutMs < 1
406
+ || timeoutMs > smartSecretKernelMaximumTimeoutMs
407
+ ) {
408
+ throw createSmartSecretKernelStoreError('INVALID_ARGUMENT');
409
+ }
410
+ if (!Object.hasOwn(options, 'signal')) return { timeoutMs };
411
+ let signal: AbortSignal;
412
+ try {
413
+ if (
414
+ !(options.signal instanceof AbortSignal)
415
+ || Object.getPrototypeOf(options.signal) !== AbortSignal.prototype
416
+ ) {
417
+ throw createSmartSecretKernelStoreError('INVALID_ARGUMENT');
418
+ }
419
+ signal = options.signal;
420
+ void signal.aborted;
421
+ } catch (error) {
422
+ if (error instanceof SmartSecretKernelStoreError) throw error;
423
+ throw createSmartSecretKernelStoreError('INVALID_ARGUMENT');
424
+ }
425
+ return { timeoutMs, signal };
426
+ };
427
+
428
+ const parseDomainError = (
429
+ valueArg: Record<string, unknown>,
430
+ ): ISmartSecretKernelDomainErrorResult | undefined => {
431
+ if (valueArg.outcome !== 'domainError') return undefined;
432
+ const result = requireExactObject(valueArg, ['outcome', 'code'], false);
433
+ if (
434
+ typeof result.code !== 'string'
435
+ || !isSmartSecretKernelDomainErrorCode(result.code)
436
+ ) {
437
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
438
+ }
439
+ return {
440
+ outcome: 'domainError',
441
+ code: result.code,
442
+ };
443
+ };
444
+
445
+ const requireResultObject = (valueArg: unknown): Record<string, unknown> => {
446
+ try {
447
+ if (valueArg === null || typeof valueArg !== 'object' || Array.isArray(valueArg)) {
448
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
449
+ }
450
+ const prototype = Object.getPrototypeOf(valueArg);
451
+ if (prototype !== Object.prototype && prototype !== null) {
452
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
453
+ }
454
+ const descriptor = Object.getOwnPropertyDescriptor(valueArg, 'outcome');
455
+ if (!descriptor?.enumerable || !('value' in descriptor)) {
456
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
457
+ }
458
+ const result: Record<string, unknown> = Object.create(null);
459
+ for (const key of Reflect.ownKeys(valueArg)) {
460
+ if (typeof key !== 'string') {
461
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
462
+ }
463
+ const property = Object.getOwnPropertyDescriptor(valueArg, key);
464
+ if (!property?.enumerable || !('value' in property)) {
465
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
466
+ }
467
+ result[key] = property.value;
468
+ }
469
+ return result;
470
+ } catch (error) {
471
+ if (error instanceof SmartSecretKernelStoreError) throw error;
472
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
473
+ }
474
+ };
475
+
476
+ const requirePositiveSerial = (valueArg: unknown): number => {
477
+ if (
478
+ typeof valueArg !== 'number'
479
+ || !Number.isSafeInteger(valueArg)
480
+ || valueArg < 1
481
+ || valueArg > 2_147_483_647
482
+ ) {
483
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
484
+ }
485
+ return valueArg;
486
+ };
487
+
488
+ export const parseSmartSecretKernelGetResult = (
489
+ valueArg: unknown,
490
+ ):
491
+ | { kind: 'domainError'; domainError: ISmartSecretKernelDomainErrorResult }
492
+ | { kind: 'success'; found: false }
493
+ | { kind: 'success'; found: true; value: Uint8Array } => {
494
+ const value = requireResultObject(valueArg);
495
+ const domainError = parseDomainError(value);
496
+ if (domainError) return { kind: 'domainError', domainError };
497
+ if (value.outcome !== 'success' || typeof value.found !== 'boolean') {
498
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
499
+ }
500
+ if (!value.found) {
501
+ requireExactObject(value, ['outcome', 'found'], false);
502
+ return { kind: 'success', found: false };
503
+ }
504
+ const result = requireExactObject(value, ['outcome', 'found', 'value'], false);
505
+ return {
506
+ kind: 'success',
507
+ found: true,
508
+ value: decodeCanonicalBase64Url(result.value, smartSecretKernelMaximumEntryBytes),
509
+ };
510
+ };
511
+
512
+ export const parseSmartSecretKernelSetResult = (
513
+ valueArg: unknown,
514
+ ):
515
+ | { kind: 'domainError'; domainError: ISmartSecretKernelDomainErrorResult }
516
+ | { kind: 'success' } => {
517
+ const value = requireResultObject(valueArg);
518
+ const domainError = parseDomainError(value);
519
+ if (domainError) return { kind: 'domainError', domainError };
520
+ const result = requireExactObject(value, ['outcome'], false);
521
+ if (result.outcome !== 'success') {
522
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
523
+ }
524
+ return { kind: 'success' };
525
+ };
526
+
527
+ export const parseSmartSecretKernelDeleteResult = (
528
+ valueArg: unknown,
529
+ ):
530
+ | { kind: 'domainError'; domainError: ISmartSecretKernelDomainErrorResult }
531
+ | { kind: 'success'; deleted: boolean } => {
532
+ const value = requireResultObject(valueArg);
533
+ const domainError = parseDomainError(value);
534
+ if (domainError) return { kind: 'domainError', domainError };
535
+ const result = requireExactObject(value, ['outcome', 'deleted'], false);
536
+ if (result.outcome !== 'success' || typeof result.deleted !== 'boolean') {
537
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
538
+ }
539
+ return { kind: 'success', deleted: result.deleted };
540
+ };
541
+
542
+ export type TParsedSmartSecretKernelLegacyRead =
543
+ | { kind: 'domainError'; domainError: ISmartSecretKernelDomainErrorResult }
544
+ | { kind: 'success'; found: false }
545
+ | {
546
+ kind: 'success';
547
+ found: true;
548
+ value: Uint8Array;
549
+ receiptParams: ISmartSecretKernelLegacyDeleteParams;
550
+ };
551
+
552
+ export const parseSmartSecretKernelLegacyReadResult = (
553
+ valueArg: unknown,
554
+ accountArg: string,
555
+ ): TParsedSmartSecretKernelLegacyRead => {
556
+ const account = normalizeDevIdpV1LegacyAccount(accountArg);
557
+ const value = requireResultObject(valueArg);
558
+ const domainError = parseDomainError(value);
559
+ if (domainError) return { kind: 'domainError', domainError };
560
+ if (value.outcome !== 'success' || typeof value.found !== 'boolean') {
561
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
562
+ }
563
+ if (!value.found) {
564
+ requireExactObject(value, ['outcome', 'found'], false);
565
+ return { kind: 'success', found: false };
566
+ }
567
+ const result = requireExactObject(value, [
568
+ 'outcome',
569
+ 'found',
570
+ 'value',
571
+ 'sourceSerial',
572
+ 'sessionRootSerial',
573
+ 'persistentRootSerial',
574
+ 'foundInSession',
575
+ 'foundInPersistent',
576
+ 'byteLength',
577
+ 'sha256',
578
+ ], false);
579
+ const decoded = decodeCanonicalBase64Url(
580
+ result.value,
581
+ smartSecretKernelMaximumLegacyBytes,
582
+ 1,
583
+ );
584
+ let actualDigest: Buffer | undefined;
585
+ let claimedDigest: Buffer | undefined;
586
+ let didSucceed = false;
587
+ try {
588
+ if (
589
+ typeof result.byteLength !== 'number'
590
+ || !Number.isSafeInteger(result.byteLength)
591
+ || result.byteLength !== decoded.byteLength
592
+ || typeof result.foundInSession !== 'boolean'
593
+ || typeof result.foundInPersistent !== 'boolean'
594
+ || (!result.foundInSession && !result.foundInPersistent)
595
+ || typeof result.sha256 !== 'string'
596
+ || !canonicalDigestPattern.test(result.sha256)
597
+ ) {
598
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
599
+ }
600
+ const sourceSerial = requirePositiveSerial(result.sourceSerial);
601
+ const persistentRootSerial = requirePositiveSerial(result.persistentRootSerial);
602
+ const sessionRootSerial = result.sessionRootSerial === null
603
+ ? null
604
+ : requirePositiveSerial(result.sessionRootSerial);
605
+ if (result.foundInSession && sessionRootSerial === null) {
606
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
607
+ }
608
+ actualDigest = plugins.crypto.createHash('sha256').update(decoded).digest();
609
+ claimedDigest = Buffer.from(result.sha256, 'base64url');
610
+ if (
611
+ claimedDigest.byteLength !== 32
612
+ || claimedDigest.toString('base64url') !== result.sha256
613
+ || !plugins.crypto.timingSafeEqual(actualDigest, claimedDigest)
614
+ ) {
615
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
616
+ }
617
+ const parsed: TParsedSmartSecretKernelLegacyRead = {
618
+ kind: 'success',
619
+ found: true,
620
+ value: decoded,
621
+ receiptParams: {
622
+ account,
623
+ sourceSerial,
624
+ sessionRootSerial,
625
+ persistentRootSerial,
626
+ foundInSession: result.foundInSession,
627
+ foundInPersistent: result.foundInPersistent,
628
+ byteLength: result.byteLength,
629
+ sha256: result.sha256,
630
+ },
631
+ };
632
+ didSucceed = true;
633
+ return parsed;
634
+ } finally {
635
+ actualDigest?.fill(0);
636
+ claimedDigest?.fill(0);
637
+ if (!didSucceed) decoded.fill(0);
638
+ }
639
+ };
640
+
641
+ export const parseSmartSecretKernelLegacyDeleteResult = (
642
+ valueArg: unknown,
643
+ ):
644
+ | { kind: 'domainError'; domainError: ISmartSecretKernelDomainErrorResult }
645
+ | { kind: 'success'; status: 'deleted' | 'alreadyAbsent' } => {
646
+ const value = requireResultObject(valueArg);
647
+ const domainError = parseDomainError(value);
648
+ if (domainError) return { kind: 'domainError', domainError };
649
+ const result = requireExactObject(value, ['outcome', 'status'], false);
650
+ if (
651
+ result.outcome !== 'success'
652
+ || (result.status !== 'deleted' && result.status !== 'alreadyAbsent')
653
+ ) {
654
+ throw createSmartSecretKernelStoreError('WORKER_INTEGRITY_FAILED');
655
+ }
656
+ return { kind: 'success', status: result.status };
657
+ };