@push.rocks/smartsecret 1.5.0 → 1.6.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/.smartconfig.json +0 -1
- package/dist_rust/smartsecret-kernel_linux_amd64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/smartsecret.kernel.error.d.ts +1 -1
- package/dist_ts/smartsecret.kernel.error.js +2 -1
- package/dist_ts/smartsecret.kernel.protocol.d.ts +21 -1
- package/dist_ts/smartsecret.kernel.protocol.js +16 -1
- package/dist_ts/smartsecret.kernelstore.d.ts +3 -2
- package/dist_ts/smartsecret.kernelstore.js +23 -2
- package/dist_ts/smartsecret.sealedfile.d.ts +12 -1
- package/dist_ts/smartsecret.sealedfile.js +484 -15
- package/package.json +1 -1
- package/readme.hints.md +6 -0
- package/readme.md +24 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/smartsecret.kernel.error.ts +2 -0
- package/ts/smartsecret.kernel.protocol.ts +40 -1
- package/ts/smartsecret.kernelstore.ts +38 -0
- package/ts/smartsecret.sealedfile.ts +650 -11
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
normalizeSmartSecretKernelAccount,
|
|
7
7
|
normalizeSmartSecretKernelService,
|
|
8
8
|
type ISmartSecretKernelOperationOptions,
|
|
9
|
+
type TSmartSecretKernelMoveStatus,
|
|
9
10
|
} from './smartsecret.kernel.protocol.js';
|
|
10
11
|
import {
|
|
11
12
|
SmartSecretSealedFileStoreError,
|
|
@@ -17,6 +18,7 @@ export const smartSecretSealedFileMaximumEntryBytes = 512 * 1_024;
|
|
|
17
18
|
const schemaVersion = 1 as const;
|
|
18
19
|
const profile = 'smartsecret-kernel-aes-256-gcm-file-v1' as const;
|
|
19
20
|
const manifestName = 'manifest.json';
|
|
21
|
+
const relocationReceiptName = '.smartsecret-relocation.json';
|
|
20
22
|
const masterKeyBytes = 32;
|
|
21
23
|
const maximumEnvelopeBytes = 768 * 1_024;
|
|
22
24
|
const operationTimeoutMs = 60_000;
|
|
@@ -43,12 +45,34 @@ export interface ISmartSecretSealedFileKernelStore {
|
|
|
43
45
|
): Promise<boolean>;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
export interface ISmartSecretSealedFileRelocationKernelStore
|
|
49
|
+
extends ISmartSecretSealedFileKernelStore {
|
|
50
|
+
moveEntry(
|
|
51
|
+
sourceAccountArg: string,
|
|
52
|
+
destinationAccountArg: string,
|
|
53
|
+
optionsArg?: ISmartSecretKernelOperationOptions,
|
|
54
|
+
): Promise<TSmartSecretKernelMoveStatus>;
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
export interface ISmartSecretSealedFileStoreOptions {
|
|
47
58
|
kernelStore: ISmartSecretSealedFileKernelStore;
|
|
48
59
|
storeId: string;
|
|
49
60
|
directoryPath: string;
|
|
50
61
|
}
|
|
51
62
|
|
|
63
|
+
export interface ISmartSecretSealedFileStoreRelocationOptions {
|
|
64
|
+
kernelStore: ISmartSecretSealedFileRelocationKernelStore;
|
|
65
|
+
storeId: string;
|
|
66
|
+
sourceDirectoryPath: string;
|
|
67
|
+
destinationDirectoryPath: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface INormalizedRelocationOptions {
|
|
71
|
+
kernelStore: ISmartSecretSealedFileRelocationKernelStore;
|
|
72
|
+
source: INormalizedOptions;
|
|
73
|
+
destination: INormalizedOptions;
|
|
74
|
+
}
|
|
75
|
+
|
|
52
76
|
interface INormalizedOptions {
|
|
53
77
|
kernelStore: ISmartSecretSealedFileKernelStore;
|
|
54
78
|
service: string;
|
|
@@ -76,6 +100,33 @@ interface IEnvelopeV1 {
|
|
|
76
100
|
tag: string;
|
|
77
101
|
}
|
|
78
102
|
|
|
103
|
+
interface IDirectoryIdentity {
|
|
104
|
+
device: string;
|
|
105
|
+
inode: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface IRelocationReceiptV1 extends IDirectoryIdentity {
|
|
109
|
+
schemaVersion: 1;
|
|
110
|
+
kind: 'relocation';
|
|
111
|
+
profile: typeof profile;
|
|
112
|
+
serviceDigest: string;
|
|
113
|
+
storeId: string;
|
|
114
|
+
sourcePathDigest: string;
|
|
115
|
+
destinationPathDigest: string;
|
|
116
|
+
masterKeyFingerprint: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interface IRelocationDirectoryInspection {
|
|
120
|
+
manifest: IManifestV1;
|
|
121
|
+
receiptValue?: unknown;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface IRelocationMasterKeyState {
|
|
125
|
+
key: Uint8Array;
|
|
126
|
+
sourcePresent: boolean;
|
|
127
|
+
destinationPresent: boolean;
|
|
128
|
+
}
|
|
129
|
+
|
|
79
130
|
type TLifecycleState = 'ready' | 'closing' | 'closed';
|
|
80
131
|
type TBigIntStats = plugins.fs.BigIntStats;
|
|
81
132
|
|
|
@@ -195,6 +246,71 @@ const normalizeOptions = (optionsArg: ISmartSecretSealedFileStoreOptions): INorm
|
|
|
195
246
|
}
|
|
196
247
|
};
|
|
197
248
|
|
|
249
|
+
const normalizeRelocationOptions = (
|
|
250
|
+
optionsArg: ISmartSecretSealedFileStoreRelocationOptions,
|
|
251
|
+
): INormalizedRelocationOptions => {
|
|
252
|
+
try {
|
|
253
|
+
if (!optionsArg || typeof optionsArg !== 'object' || Array.isArray(optionsArg)) {
|
|
254
|
+
throw createSmartSecretSealedFileStoreError('INVALID_ARGUMENT');
|
|
255
|
+
}
|
|
256
|
+
const expectedKeys = [
|
|
257
|
+
'kernelStore',
|
|
258
|
+
'storeId',
|
|
259
|
+
'sourceDirectoryPath',
|
|
260
|
+
'destinationDirectoryPath',
|
|
261
|
+
];
|
|
262
|
+
const keys = Reflect.ownKeys(optionsArg);
|
|
263
|
+
if (
|
|
264
|
+
keys.length !== expectedKeys.length
|
|
265
|
+
|| keys.some((keyArg) => typeof keyArg !== 'string')
|
|
266
|
+
|| expectedKeys.some((keyArg) => !keys.includes(keyArg))
|
|
267
|
+
) throw createSmartSecretSealedFileStoreError('INVALID_ARGUMENT');
|
|
268
|
+
const values: Record<string, unknown> = Object.create(null);
|
|
269
|
+
for (const key of keys as string[]) {
|
|
270
|
+
const descriptor = Object.getOwnPropertyDescriptor(optionsArg, key);
|
|
271
|
+
if (!descriptor?.enumerable || !('value' in descriptor)) {
|
|
272
|
+
throw createSmartSecretSealedFileStoreError('INVALID_ARGUMENT');
|
|
273
|
+
}
|
|
274
|
+
values[key] = descriptor.value;
|
|
275
|
+
}
|
|
276
|
+
const kernelStoreValue = values.kernelStore;
|
|
277
|
+
if (!kernelStoreValue || typeof kernelStoreValue !== 'object') {
|
|
278
|
+
throw createSmartSecretSealedFileStoreError('INVALID_ARGUMENT');
|
|
279
|
+
}
|
|
280
|
+
const moveEntry = dataPropertyValue(kernelStoreValue, 'moveEntry');
|
|
281
|
+
if (typeof moveEntry !== 'function') {
|
|
282
|
+
throw createSmartSecretSealedFileStoreError('INVALID_ARGUMENT');
|
|
283
|
+
}
|
|
284
|
+
const source = normalizeOptions({
|
|
285
|
+
kernelStore: kernelStoreValue as ISmartSecretSealedFileKernelStore,
|
|
286
|
+
storeId: values.storeId as string,
|
|
287
|
+
directoryPath: values.sourceDirectoryPath as string,
|
|
288
|
+
});
|
|
289
|
+
const destination = normalizeOptions({
|
|
290
|
+
kernelStore: kernelStoreValue as ISmartSecretSealedFileKernelStore,
|
|
291
|
+
storeId: values.storeId as string,
|
|
292
|
+
directoryPath: values.destinationDirectoryPath as string,
|
|
293
|
+
});
|
|
294
|
+
if (source.directoryPath === destination.directoryPath) {
|
|
295
|
+
throw createSmartSecretSealedFileStoreError('INVALID_ARGUMENT');
|
|
296
|
+
}
|
|
297
|
+
const kernelStore: ISmartSecretSealedFileRelocationKernelStore = {
|
|
298
|
+
...source.kernelStore,
|
|
299
|
+
moveEntry: (sourceAccountArg, destinationAccountArg, operationOptionsArg) => Reflect.apply(
|
|
300
|
+
moveEntry,
|
|
301
|
+
kernelStoreValue,
|
|
302
|
+
[sourceAccountArg, destinationAccountArg, operationOptionsArg],
|
|
303
|
+
) as Promise<TSmartSecretKernelMoveStatus>,
|
|
304
|
+
};
|
|
305
|
+
source.kernelStore = kernelStore;
|
|
306
|
+
destination.kernelStore = kernelStore;
|
|
307
|
+
return { kernelStore, source, destination };
|
|
308
|
+
} catch (errorArg) {
|
|
309
|
+
if (errorArg instanceof SmartSecretSealedFileStoreError) throw errorArg;
|
|
310
|
+
throw createSmartSecretSealedFileStoreError('INVALID_ARGUMENT');
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
198
314
|
const exactObject = (
|
|
199
315
|
valueArg: unknown,
|
|
200
316
|
keysArg: readonly string[],
|
|
@@ -349,6 +465,66 @@ const manifestFor = (optionsArg: INormalizedOptions, fingerprintArg: string): IM
|
|
|
349
465
|
masterKeyFingerprint: fingerprintArg,
|
|
350
466
|
});
|
|
351
467
|
|
|
468
|
+
const directoryIdentity = (statsArg: TBigIntStats): IDirectoryIdentity => ({
|
|
469
|
+
device: statsArg.dev.toString(10),
|
|
470
|
+
inode: statsArg.ino.toString(10),
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
const directoryIdentitiesEqual = (
|
|
474
|
+
leftArg: IDirectoryIdentity,
|
|
475
|
+
rightArg: IDirectoryIdentity,
|
|
476
|
+
): boolean => leftArg.device === rightArg.device && leftArg.inode === rightArg.inode;
|
|
477
|
+
|
|
478
|
+
const relocationReceiptFor = (
|
|
479
|
+
optionsArg: INormalizedRelocationOptions,
|
|
480
|
+
fingerprintArg: string,
|
|
481
|
+
identityArg: IDirectoryIdentity,
|
|
482
|
+
): IRelocationReceiptV1 => ({
|
|
483
|
+
schemaVersion,
|
|
484
|
+
kind: 'relocation',
|
|
485
|
+
profile,
|
|
486
|
+
serviceDigest: digest(encoder.encode(optionsArg.source.service)),
|
|
487
|
+
storeId: optionsArg.source.storeId,
|
|
488
|
+
sourcePathDigest: digest(encoder.encode(optionsArg.source.directoryPath)),
|
|
489
|
+
destinationPathDigest: digest(encoder.encode(optionsArg.destination.directoryPath)),
|
|
490
|
+
masterKeyFingerprint: fingerprintArg,
|
|
491
|
+
...identityArg,
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
const parseRelocationReceipt = (
|
|
495
|
+
valueArg: unknown,
|
|
496
|
+
optionsArg: INormalizedRelocationOptions,
|
|
497
|
+
fingerprintArg: string,
|
|
498
|
+
identityArg: IDirectoryIdentity,
|
|
499
|
+
): IRelocationReceiptV1 => {
|
|
500
|
+
const value = exactObject(valueArg, [
|
|
501
|
+
'schemaVersion',
|
|
502
|
+
'kind',
|
|
503
|
+
'profile',
|
|
504
|
+
'serviceDigest',
|
|
505
|
+
'storeId',
|
|
506
|
+
'sourcePathDigest',
|
|
507
|
+
'destinationPathDigest',
|
|
508
|
+
'masterKeyFingerprint',
|
|
509
|
+
'device',
|
|
510
|
+
'inode',
|
|
511
|
+
], 'FILESYSTEM_FAILED');
|
|
512
|
+
const expected = relocationReceiptFor(optionsArg, fingerprintArg, identityArg);
|
|
513
|
+
if (
|
|
514
|
+
value.schemaVersion !== expected.schemaVersion
|
|
515
|
+
|| value.kind !== expected.kind
|
|
516
|
+
|| value.profile !== expected.profile
|
|
517
|
+
|| value.serviceDigest !== expected.serviceDigest
|
|
518
|
+
|| value.storeId !== expected.storeId
|
|
519
|
+
|| value.sourcePathDigest !== expected.sourcePathDigest
|
|
520
|
+
|| value.destinationPathDigest !== expected.destinationPathDigest
|
|
521
|
+
|| value.masterKeyFingerprint !== expected.masterKeyFingerprint
|
|
522
|
+
|| value.device !== expected.device
|
|
523
|
+
|| value.inode !== expected.inode
|
|
524
|
+
) throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
525
|
+
return value as unknown as IRelocationReceiptV1;
|
|
526
|
+
};
|
|
527
|
+
|
|
352
528
|
const parseManifest = (valueArg: unknown, optionsArg: INormalizedOptions): IManifestV1 => {
|
|
353
529
|
const value = exactObject(valueArg, [
|
|
354
530
|
'schemaVersion',
|
|
@@ -455,7 +631,10 @@ const validateDirectory = (
|
|
|
455
631
|
) throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
456
632
|
};
|
|
457
633
|
|
|
458
|
-
const
|
|
634
|
+
const openDirectory = async (
|
|
635
|
+
directoryPathArg: string,
|
|
636
|
+
createArg: boolean,
|
|
637
|
+
): Promise<plugins.fs.promises.FileHandle> => {
|
|
459
638
|
let currentHandle: plugins.fs.promises.FileHandle | undefined;
|
|
460
639
|
try {
|
|
461
640
|
const effectiveUid = process.geteuid?.();
|
|
@@ -475,10 +654,12 @@ const ensureDirectory = async (directoryPathArg: string): Promise<plugins.fs.pro
|
|
|
475
654
|
validateDirectory(await currentHandle.stat({ bigint: true }), BigInt(effectiveUid!), false);
|
|
476
655
|
for (let index = 0; index < components.length; index++) {
|
|
477
656
|
const componentPath = directoryHandlePath(currentHandle, components[index]);
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
657
|
+
if (createArg) {
|
|
658
|
+
try {
|
|
659
|
+
await plugins.fs.promises.mkdir(componentPath, { mode: 0o700 });
|
|
660
|
+
} catch (errorArg) {
|
|
661
|
+
if ((errorArg as NodeJS.ErrnoException).code !== 'EEXIST') throw errorArg;
|
|
662
|
+
}
|
|
482
663
|
}
|
|
483
664
|
let nextHandle: plugins.fs.promises.FileHandle | undefined;
|
|
484
665
|
try {
|
|
@@ -514,6 +695,47 @@ const ensureDirectory = async (directoryPathArg: string): Promise<plugins.fs.pro
|
|
|
514
695
|
}
|
|
515
696
|
};
|
|
516
697
|
|
|
698
|
+
const ensureDirectory = (directoryPathArg: string): Promise<plugins.fs.promises.FileHandle> =>
|
|
699
|
+
openDirectory(directoryPathArg, true);
|
|
700
|
+
|
|
701
|
+
const openExistingDirectory = (
|
|
702
|
+
directoryPathArg: string,
|
|
703
|
+
): Promise<plugins.fs.promises.FileHandle> => openDirectory(directoryPathArg, false);
|
|
704
|
+
|
|
705
|
+
const openChildDirectoryIfPresent = async (
|
|
706
|
+
parentHandleArg: plugins.fs.promises.FileHandle,
|
|
707
|
+
nameArg: string,
|
|
708
|
+
): Promise<plugins.fs.promises.FileHandle | undefined> => {
|
|
709
|
+
let handle: plugins.fs.promises.FileHandle | undefined;
|
|
710
|
+
try {
|
|
711
|
+
try {
|
|
712
|
+
handle = await plugins.fs.promises.open(
|
|
713
|
+
directoryHandlePath(parentHandleArg, nameArg),
|
|
714
|
+
plugins.fs.constants.O_RDONLY
|
|
715
|
+
| plugins.fs.constants.O_DIRECTORY
|
|
716
|
+
| plugins.fs.constants.O_NOFOLLOW,
|
|
717
|
+
);
|
|
718
|
+
} catch (errorArg) {
|
|
719
|
+
if ((errorArg as NodeJS.ErrnoException).code === 'ENOENT') return undefined;
|
|
720
|
+
throw errorArg;
|
|
721
|
+
}
|
|
722
|
+
const effectiveUid = process.geteuid?.();
|
|
723
|
+
if (!Number.isSafeInteger(effectiveUid) || effectiveUid! < 0) {
|
|
724
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
725
|
+
}
|
|
726
|
+
validateDirectory(await handle.stat({ bigint: true }), BigInt(effectiveUid!), true);
|
|
727
|
+
const result = handle;
|
|
728
|
+
handle = undefined;
|
|
729
|
+
return result;
|
|
730
|
+
} catch (errorArg) {
|
|
731
|
+
let operationError: unknown = errorArg instanceof SmartSecretSealedFileStoreError
|
|
732
|
+
? errorArg
|
|
733
|
+
: createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
734
|
+
if (handle) operationError = await closeFileHandleWithError(handle, operationError);
|
|
735
|
+
throw operationError;
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
|
|
517
739
|
const readStrictJsonFile = async (
|
|
518
740
|
directoryHandleArg: plugins.fs.promises.FileHandle,
|
|
519
741
|
fileNameArg: string,
|
|
@@ -655,11 +877,13 @@ const removeAndSync = async (
|
|
|
655
877
|
|
|
656
878
|
const listOwnedFiles = async (
|
|
657
879
|
directoryHandleArg: plugins.fs.promises.FileHandle,
|
|
880
|
+
allowRelocationReceiptArg = false,
|
|
658
881
|
): Promise<string[]> => {
|
|
659
882
|
try {
|
|
660
883
|
const names = await plugins.fs.promises.readdir(directoryHandlePath(directoryHandleArg));
|
|
661
884
|
if (names.some((nameArg) => (
|
|
662
885
|
nameArg !== manifestName
|
|
886
|
+
&& !(allowRelocationReceiptArg && nameArg === relocationReceiptName)
|
|
663
887
|
&& !entryFilePattern.test(nameArg)
|
|
664
888
|
&& !temporaryFilePattern.test(nameArg)
|
|
665
889
|
))) throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
@@ -721,6 +945,185 @@ const readMasterKey = async (optionsArg: INormalizedOptions): Promise<Uint8Array
|
|
|
721
945
|
}
|
|
722
946
|
};
|
|
723
947
|
|
|
948
|
+
const mapRelocationKernelError = (errorArg: unknown): SmartSecretSealedFileStoreError => {
|
|
949
|
+
if (!(errorArg instanceof SmartSecretKernelStoreError)) {
|
|
950
|
+
return createSmartSecretSealedFileStoreError('KERNEL_UNAVAILABLE');
|
|
951
|
+
}
|
|
952
|
+
if (errorArg.code === 'MUTATION_OUTCOME_UNKNOWN') {
|
|
953
|
+
return createSmartSecretSealedFileStoreError('MUTATION_OUTCOME_UNKNOWN');
|
|
954
|
+
}
|
|
955
|
+
if (errorArg.code === 'TARGET_CONFLICT' || errorArg.code === 'SOURCE_CHANGED') {
|
|
956
|
+
return createSmartSecretSealedFileStoreError('MASTER_KEY_CONFLICT');
|
|
957
|
+
}
|
|
958
|
+
if (errorArg.code === 'INVALID_ARGUMENT') {
|
|
959
|
+
return createSmartSecretSealedFileStoreError('INVALID_ARGUMENT');
|
|
960
|
+
}
|
|
961
|
+
return createSmartSecretSealedFileStoreError('KERNEL_UNAVAILABLE');
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
const acquireRelocationLeases = async (
|
|
965
|
+
optionsArg: INormalizedRelocationOptions,
|
|
966
|
+
): Promise<plugins.smartipc.NamedMutexLease[]> => {
|
|
967
|
+
const deadline = performance.now() + operationTimeoutMs;
|
|
968
|
+
const ordered = [optionsArg.source, optionsArg.destination]
|
|
969
|
+
.sort((leftArg, rightArg) => leftArg.mutexNamespace < rightArg.mutexNamespace
|
|
970
|
+
? -1
|
|
971
|
+
: leftArg.mutexNamespace > rightArg.mutexNamespace
|
|
972
|
+
? 1
|
|
973
|
+
: 0);
|
|
974
|
+
const leases: plugins.smartipc.NamedMutexLease[] = [];
|
|
975
|
+
try {
|
|
976
|
+
for (const options of ordered) leases.push(await acquireLease(options, deadline));
|
|
977
|
+
return leases;
|
|
978
|
+
} catch (errorArg) {
|
|
979
|
+
let operationError: unknown = errorArg;
|
|
980
|
+
for (const lease of leases.reverse()) {
|
|
981
|
+
try {
|
|
982
|
+
await lease.release();
|
|
983
|
+
} catch {
|
|
984
|
+
operationError = preferCleanupError(
|
|
985
|
+
operationError,
|
|
986
|
+
createSmartSecretSealedFileStoreError('MUTEX_FAILED'),
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
throw operationError;
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
const inspectRelocationDirectory = async (
|
|
995
|
+
directoryHandleArg: plugins.fs.promises.FileHandle,
|
|
996
|
+
optionsArg: INormalizedOptions,
|
|
997
|
+
): Promise<IRelocationDirectoryInspection> => {
|
|
998
|
+
const names = await listOwnedFiles(directoryHandleArg, true);
|
|
999
|
+
if (!names.includes(manifestName)) {
|
|
1000
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1001
|
+
}
|
|
1002
|
+
const manifestValue = await readStrictJsonFile(
|
|
1003
|
+
directoryHandleArg,
|
|
1004
|
+
manifestName,
|
|
1005
|
+
4 * 1_024,
|
|
1006
|
+
);
|
|
1007
|
+
if (manifestValue === undefined) {
|
|
1008
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1009
|
+
}
|
|
1010
|
+
const receiptValue = names.includes(relocationReceiptName)
|
|
1011
|
+
? await readStrictJsonFile(directoryHandleArg, relocationReceiptName, 4 * 1_024)
|
|
1012
|
+
: undefined;
|
|
1013
|
+
if (names.includes(relocationReceiptName) && receiptValue === undefined) {
|
|
1014
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1015
|
+
}
|
|
1016
|
+
return {
|
|
1017
|
+
manifest: parseManifest(manifestValue, optionsArg),
|
|
1018
|
+
...(receiptValue === undefined ? {} : { receiptValue }),
|
|
1019
|
+
};
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
const readRelocationMasterKey = async (
|
|
1023
|
+
sourceArg: INormalizedOptions,
|
|
1024
|
+
destinationArg: INormalizedOptions,
|
|
1025
|
+
expectedFingerprintArg: string,
|
|
1026
|
+
): Promise<IRelocationMasterKeyState> => {
|
|
1027
|
+
let sourceKey: Uint8Array | null = null;
|
|
1028
|
+
let destinationKey: Uint8Array | null = null;
|
|
1029
|
+
try {
|
|
1030
|
+
sourceKey = await readMasterKey(sourceArg);
|
|
1031
|
+
destinationKey = await readMasterKey(destinationArg);
|
|
1032
|
+
if (!sourceKey && !destinationKey) {
|
|
1033
|
+
throw createSmartSecretSealedFileStoreError('MASTER_KEY_UNAVAILABLE');
|
|
1034
|
+
}
|
|
1035
|
+
if (
|
|
1036
|
+
(sourceKey && fingerprint(sourceKey) !== expectedFingerprintArg)
|
|
1037
|
+
|| (destinationKey && fingerprint(destinationKey) !== expectedFingerprintArg)
|
|
1038
|
+
|| (
|
|
1039
|
+
sourceKey
|
|
1040
|
+
&& destinationKey
|
|
1041
|
+
&& !plugins.crypto.timingSafeEqual(sourceKey, destinationKey)
|
|
1042
|
+
)
|
|
1043
|
+
) throw createSmartSecretSealedFileStoreError('MASTER_KEY_CONFLICT');
|
|
1044
|
+
return {
|
|
1045
|
+
key: new Uint8Array(sourceKey ?? destinationKey!),
|
|
1046
|
+
sourcePresent: sourceKey !== null,
|
|
1047
|
+
destinationPresent: destinationKey !== null,
|
|
1048
|
+
};
|
|
1049
|
+
} finally {
|
|
1050
|
+
sourceKey?.fill(0);
|
|
1051
|
+
destinationKey?.fill(0);
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
const syncRelocationParents = async (
|
|
1056
|
+
sourceParentHandleArg: plugins.fs.promises.FileHandle,
|
|
1057
|
+
sourceParentPathArg: string,
|
|
1058
|
+
destinationParentHandleArg: plugins.fs.promises.FileHandle,
|
|
1059
|
+
destinationParentPathArg: string,
|
|
1060
|
+
): Promise<void> => {
|
|
1061
|
+
try {
|
|
1062
|
+
await sourceParentHandleArg.sync();
|
|
1063
|
+
if (sourceParentPathArg !== destinationParentPathArg) {
|
|
1064
|
+
await destinationParentHandleArg.sync();
|
|
1065
|
+
}
|
|
1066
|
+
} catch {
|
|
1067
|
+
throw createSmartSecretSealedFileStoreError('MUTATION_OUTCOME_UNKNOWN');
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
const assertOpenChildIdentity = async (
|
|
1072
|
+
parentHandleArg: plugins.fs.promises.FileHandle,
|
|
1073
|
+
nameArg: string,
|
|
1074
|
+
childHandleArg: plugins.fs.promises.FileHandle,
|
|
1075
|
+
): Promise<IDirectoryIdentity> => {
|
|
1076
|
+
try {
|
|
1077
|
+
const [pathStats, handleStats] = await Promise.all([
|
|
1078
|
+
plugins.fs.promises.lstat(directoryHandlePath(parentHandleArg, nameArg), { bigint: true }),
|
|
1079
|
+
childHandleArg.stat({ bigint: true }),
|
|
1080
|
+
]);
|
|
1081
|
+
if (
|
|
1082
|
+
pathStats.isSymbolicLink()
|
|
1083
|
+
|| !pathStats.isDirectory()
|
|
1084
|
+
|| pathStats.dev !== handleStats.dev
|
|
1085
|
+
|| pathStats.ino !== handleStats.ino
|
|
1086
|
+
) throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1087
|
+
return directoryIdentity(handleStats);
|
|
1088
|
+
} catch (errorArg) {
|
|
1089
|
+
if (errorArg instanceof SmartSecretSealedFileStoreError) throw errorArg;
|
|
1090
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
const readChildIdentity = async (
|
|
1095
|
+
parentHandleArg: plugins.fs.promises.FileHandle,
|
|
1096
|
+
nameArg: string,
|
|
1097
|
+
): Promise<IDirectoryIdentity | undefined> => {
|
|
1098
|
+
try {
|
|
1099
|
+
const stats = await plugins.fs.promises.lstat(
|
|
1100
|
+
directoryHandlePath(parentHandleArg, nameArg),
|
|
1101
|
+
{ bigint: true },
|
|
1102
|
+
);
|
|
1103
|
+
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
1104
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1105
|
+
}
|
|
1106
|
+
return directoryIdentity(stats);
|
|
1107
|
+
} catch (errorArg) {
|
|
1108
|
+
if ((errorArg as NodeJS.ErrnoException).code === 'ENOENT') return undefined;
|
|
1109
|
+
if (errorArg instanceof SmartSecretSealedFileStoreError) throw errorArg;
|
|
1110
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
const assertChildAbsent = async (
|
|
1115
|
+
parentHandleArg: plugins.fs.promises.FileHandle,
|
|
1116
|
+
nameArg: string,
|
|
1117
|
+
): Promise<void> => {
|
|
1118
|
+
try {
|
|
1119
|
+
await plugins.fs.promises.lstat(directoryHandlePath(parentHandleArg, nameArg));
|
|
1120
|
+
} catch (errorArg) {
|
|
1121
|
+
if ((errorArg as NodeJS.ErrnoException).code === 'ENOENT') return;
|
|
1122
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1123
|
+
}
|
|
1124
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1125
|
+
};
|
|
1126
|
+
|
|
724
1127
|
const createMasterKey = async (optionsArg: INormalizedOptions): Promise<Uint8Array> => {
|
|
725
1128
|
const generated = plugins.smartcrypto.generateAes256GcmKey();
|
|
726
1129
|
let written = false;
|
|
@@ -754,14 +1157,26 @@ const createMasterKey = async (optionsArg: INormalizedOptions): Promise<Uint8Arr
|
|
|
754
1157
|
}
|
|
755
1158
|
};
|
|
756
1159
|
|
|
757
|
-
const bootstrapUnderLease = async (
|
|
758
|
-
|
|
1160
|
+
const bootstrapUnderLease = async (
|
|
1161
|
+
optionsArg: INormalizedOptions,
|
|
1162
|
+
existingIdentityArg?: IDirectoryIdentity,
|
|
1163
|
+
allowRelocationReceiptArg = false,
|
|
1164
|
+
): Promise<Uint8Array> => {
|
|
1165
|
+
const directoryHandle = existingIdentityArg
|
|
1166
|
+
? await openExistingDirectory(optionsArg.directoryPath)
|
|
1167
|
+
: await ensureDirectory(optionsArg.directoryPath);
|
|
759
1168
|
let key: Uint8Array | null = null;
|
|
760
1169
|
let result: Uint8Array | undefined;
|
|
761
1170
|
let createdMasterKey = false;
|
|
762
1171
|
let operationError: unknown;
|
|
763
1172
|
try {
|
|
764
|
-
|
|
1173
|
+
if (existingIdentityArg) {
|
|
1174
|
+
const currentIdentity = directoryIdentity(await directoryHandle.stat({ bigint: true }));
|
|
1175
|
+
if (!directoryIdentitiesEqual(currentIdentity, existingIdentityArg)) {
|
|
1176
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
const names = await listOwnedFiles(directoryHandle, allowRelocationReceiptArg);
|
|
765
1180
|
const manifestValue = await readStrictJsonFile(
|
|
766
1181
|
directoryHandle,
|
|
767
1182
|
manifestName,
|
|
@@ -873,6 +1288,230 @@ export class SmartSecretSealedFileStore {
|
|
|
873
1288
|
return store;
|
|
874
1289
|
}
|
|
875
1290
|
|
|
1291
|
+
/** Moves an initialized store and rebinds its exact kernel master key. */
|
|
1292
|
+
public static async relocate(
|
|
1293
|
+
optionsArg: ISmartSecretSealedFileStoreRelocationOptions,
|
|
1294
|
+
): Promise<SmartSecretSealedFileStore> {
|
|
1295
|
+
const options = normalizeRelocationOptions(optionsArg);
|
|
1296
|
+
if (process.platform !== 'linux' || typeof process.geteuid !== 'function') {
|
|
1297
|
+
throw createSmartSecretSealedFileStoreError('KERNEL_UNAVAILABLE');
|
|
1298
|
+
}
|
|
1299
|
+
const leases = await acquireRelocationLeases(options);
|
|
1300
|
+
let sourceParentHandle: plugins.fs.promises.FileHandle | undefined;
|
|
1301
|
+
let destinationParentHandle: plugins.fs.promises.FileHandle | undefined;
|
|
1302
|
+
let sourceDirectoryHandle: plugins.fs.promises.FileHandle | undefined;
|
|
1303
|
+
let destinationDirectoryHandle: plugins.fs.promises.FileHandle | undefined;
|
|
1304
|
+
let expectedKey: Uint8Array | undefined;
|
|
1305
|
+
let expectedIdentity: IDirectoryIdentity | undefined;
|
|
1306
|
+
let resultKey: Uint8Array | undefined;
|
|
1307
|
+
let mutationPossible = false;
|
|
1308
|
+
let operationError: unknown;
|
|
1309
|
+
const sourceParentPath = plugins.path.dirname(options.source.directoryPath);
|
|
1310
|
+
const destinationParentPath = plugins.path.dirname(options.destination.directoryPath);
|
|
1311
|
+
const sourceName = plugins.path.basename(options.source.directoryPath);
|
|
1312
|
+
const destinationName = plugins.path.basename(options.destination.directoryPath);
|
|
1313
|
+
try {
|
|
1314
|
+
sourceParentHandle = await openExistingDirectory(sourceParentPath);
|
|
1315
|
+
destinationParentHandle = await openExistingDirectory(destinationParentPath);
|
|
1316
|
+
const [sourceParentStats, destinationParentStats] = await Promise.all([
|
|
1317
|
+
sourceParentHandle.stat({ bigint: true }),
|
|
1318
|
+
destinationParentHandle.stat({ bigint: true }),
|
|
1319
|
+
]);
|
|
1320
|
+
if (sourceParentStats.dev !== destinationParentStats.dev) {
|
|
1321
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1322
|
+
}
|
|
1323
|
+
sourceDirectoryHandle = await openChildDirectoryIfPresent(sourceParentHandle, sourceName);
|
|
1324
|
+
destinationDirectoryHandle = await openChildDirectoryIfPresent(
|
|
1325
|
+
destinationParentHandle,
|
|
1326
|
+
destinationName,
|
|
1327
|
+
);
|
|
1328
|
+
if (Boolean(sourceDirectoryHandle) === Boolean(destinationDirectoryHandle)) {
|
|
1329
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1330
|
+
}
|
|
1331
|
+
const storeAtSource = sourceDirectoryHandle !== undefined;
|
|
1332
|
+
const currentOptions = storeAtSource ? options.source : options.destination;
|
|
1333
|
+
const currentHandle = sourceDirectoryHandle ?? destinationDirectoryHandle!;
|
|
1334
|
+
expectedIdentity = await assertOpenChildIdentity(
|
|
1335
|
+
storeAtSource ? sourceParentHandle : destinationParentHandle,
|
|
1336
|
+
storeAtSource ? sourceName : destinationName,
|
|
1337
|
+
currentHandle,
|
|
1338
|
+
);
|
|
1339
|
+
if (expectedIdentity.device !== sourceParentStats.dev.toString(10)) {
|
|
1340
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1341
|
+
}
|
|
1342
|
+
const inspection = await inspectRelocationDirectory(currentHandle, currentOptions);
|
|
1343
|
+
const keyState = await readRelocationMasterKey(
|
|
1344
|
+
options.source,
|
|
1345
|
+
options.destination,
|
|
1346
|
+
inspection.manifest.masterKeyFingerprint,
|
|
1347
|
+
);
|
|
1348
|
+
expectedKey = keyState.key;
|
|
1349
|
+
let receipt = inspection.receiptValue === undefined
|
|
1350
|
+
? undefined
|
|
1351
|
+
: parseRelocationReceipt(
|
|
1352
|
+
inspection.receiptValue,
|
|
1353
|
+
options,
|
|
1354
|
+
inspection.manifest.masterKeyFingerprint,
|
|
1355
|
+
expectedIdentity,
|
|
1356
|
+
);
|
|
1357
|
+
if (storeAtSource) {
|
|
1358
|
+
if (!keyState.sourcePresent) {
|
|
1359
|
+
throw createSmartSecretSealedFileStoreError('MASTER_KEY_UNAVAILABLE');
|
|
1360
|
+
}
|
|
1361
|
+
if (!receipt) {
|
|
1362
|
+
await atomicWriteJson(
|
|
1363
|
+
currentHandle,
|
|
1364
|
+
relocationReceiptName,
|
|
1365
|
+
relocationReceiptFor(
|
|
1366
|
+
options,
|
|
1367
|
+
inspection.manifest.masterKeyFingerprint,
|
|
1368
|
+
expectedIdentity,
|
|
1369
|
+
),
|
|
1370
|
+
);
|
|
1371
|
+
mutationPossible = true;
|
|
1372
|
+
const receiptValue = await readStrictJsonFile(
|
|
1373
|
+
currentHandle,
|
|
1374
|
+
relocationReceiptName,
|
|
1375
|
+
4 * 1_024,
|
|
1376
|
+
);
|
|
1377
|
+
if (receiptValue === undefined) {
|
|
1378
|
+
throw createSmartSecretSealedFileStoreError('MUTATION_OUTCOME_UNKNOWN');
|
|
1379
|
+
}
|
|
1380
|
+
receipt = parseRelocationReceipt(
|
|
1381
|
+
receiptValue,
|
|
1382
|
+
options,
|
|
1383
|
+
inspection.manifest.masterKeyFingerprint,
|
|
1384
|
+
expectedIdentity,
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
await assertOpenChildIdentity(sourceParentHandle, sourceName, sourceDirectoryHandle!);
|
|
1388
|
+
await assertChildAbsent(destinationParentHandle, destinationName);
|
|
1389
|
+
try {
|
|
1390
|
+
await plugins.fs.promises.rename(
|
|
1391
|
+
directoryHandlePath(sourceParentHandle, sourceName),
|
|
1392
|
+
directoryHandlePath(destinationParentHandle, destinationName),
|
|
1393
|
+
);
|
|
1394
|
+
mutationPossible = true;
|
|
1395
|
+
} catch {
|
|
1396
|
+
const [sourceAfter, destinationAfter] = await Promise.all([
|
|
1397
|
+
readChildIdentity(sourceParentHandle, sourceName),
|
|
1398
|
+
readChildIdentity(destinationParentHandle, destinationName),
|
|
1399
|
+
]);
|
|
1400
|
+
if (
|
|
1401
|
+
sourceAfter
|
|
1402
|
+
&& directoryIdentitiesEqual(sourceAfter, expectedIdentity)
|
|
1403
|
+
&& destinationAfter === undefined
|
|
1404
|
+
) {
|
|
1405
|
+
try {
|
|
1406
|
+
if (!await removeAndSync(currentHandle, relocationReceiptName)) {
|
|
1407
|
+
throw createSmartSecretSealedFileStoreError('MUTATION_OUTCOME_UNKNOWN');
|
|
1408
|
+
}
|
|
1409
|
+
} catch (errorArg) {
|
|
1410
|
+
if (mutationOutcomeUnknown(errorArg)) throw errorArg;
|
|
1411
|
+
throw createSmartSecretSealedFileStoreError('MUTATION_OUTCOME_UNKNOWN');
|
|
1412
|
+
}
|
|
1413
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1414
|
+
}
|
|
1415
|
+
if (
|
|
1416
|
+
sourceAfter === undefined
|
|
1417
|
+
&& destinationAfter
|
|
1418
|
+
&& directoryIdentitiesEqual(destinationAfter, expectedIdentity)
|
|
1419
|
+
) {
|
|
1420
|
+
mutationPossible = true;
|
|
1421
|
+
} else {
|
|
1422
|
+
throw createSmartSecretSealedFileStoreError('MUTATION_OUTCOME_UNKNOWN');
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
} else {
|
|
1426
|
+
if (!receipt && (keyState.sourcePresent || !keyState.destinationPresent)) {
|
|
1427
|
+
throw createSmartSecretSealedFileStoreError('FILESYSTEM_FAILED');
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
await syncRelocationParents(
|
|
1431
|
+
sourceParentHandle,
|
|
1432
|
+
sourceParentPath,
|
|
1433
|
+
destinationParentHandle,
|
|
1434
|
+
destinationParentPath,
|
|
1435
|
+
);
|
|
1436
|
+
mutationPossible = true;
|
|
1437
|
+
let moveStatus: TSmartSecretKernelMoveStatus;
|
|
1438
|
+
try {
|
|
1439
|
+
moveStatus = await options.kernelStore.moveEntry(
|
|
1440
|
+
options.source.masterKeyAccount,
|
|
1441
|
+
options.destination.masterKeyAccount,
|
|
1442
|
+
);
|
|
1443
|
+
} catch (errorArg) {
|
|
1444
|
+
throw mapRelocationKernelError(errorArg);
|
|
1445
|
+
}
|
|
1446
|
+
if (moveStatus === 'sourceAbsent') {
|
|
1447
|
+
throw createSmartSecretSealedFileStoreError('MASTER_KEY_UNAVAILABLE');
|
|
1448
|
+
}
|
|
1449
|
+
const destinationKey = await readMasterKey(options.destination);
|
|
1450
|
+
try {
|
|
1451
|
+
if (!destinationKey || !plugins.crypto.timingSafeEqual(destinationKey, expectedKey)) {
|
|
1452
|
+
throw createSmartSecretSealedFileStoreError('MASTER_KEY_CONFLICT');
|
|
1453
|
+
}
|
|
1454
|
+
} finally {
|
|
1455
|
+
destinationKey?.fill(0);
|
|
1456
|
+
}
|
|
1457
|
+
resultKey = await bootstrapUnderLease(options.destination, expectedIdentity, Boolean(receipt));
|
|
1458
|
+
if (!plugins.crypto.timingSafeEqual(resultKey, expectedKey)) {
|
|
1459
|
+
throw createSmartSecretSealedFileStoreError('MASTER_KEY_CONFLICT');
|
|
1460
|
+
}
|
|
1461
|
+
if (receipt) {
|
|
1462
|
+
try {
|
|
1463
|
+
if (!await removeAndSync(currentHandle, relocationReceiptName)) {
|
|
1464
|
+
throw createSmartSecretSealedFileStoreError('MUTATION_OUTCOME_UNKNOWN');
|
|
1465
|
+
}
|
|
1466
|
+
} catch (errorArg) {
|
|
1467
|
+
if (mutationOutcomeUnknown(errorArg)) throw errorArg;
|
|
1468
|
+
throw createSmartSecretSealedFileStoreError('MUTATION_OUTCOME_UNKNOWN');
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
} catch (errorArg) {
|
|
1472
|
+
operationError = errorArg;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
for (const handle of [
|
|
1476
|
+
sourceDirectoryHandle,
|
|
1477
|
+
destinationDirectoryHandle,
|
|
1478
|
+
sourceParentHandle,
|
|
1479
|
+
destinationParentHandle,
|
|
1480
|
+
]) {
|
|
1481
|
+
if (!handle) continue;
|
|
1482
|
+
try {
|
|
1483
|
+
await closeFileHandle(handle);
|
|
1484
|
+
} catch (errorArg) {
|
|
1485
|
+
operationError = preferCleanupError(
|
|
1486
|
+
operationError,
|
|
1487
|
+
mutationPossible
|
|
1488
|
+
? createSmartSecretSealedFileStoreError('MUTATION_OUTCOME_UNKNOWN')
|
|
1489
|
+
: errorArg,
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
for (const lease of leases.reverse()) {
|
|
1494
|
+
try {
|
|
1495
|
+
await lease.release();
|
|
1496
|
+
} catch {
|
|
1497
|
+
operationError = preferCleanupError(
|
|
1498
|
+
operationError,
|
|
1499
|
+
createSmartSecretSealedFileStoreError(
|
|
1500
|
+
mutationPossible ? 'MUTATION_OUTCOME_UNKNOWN' : 'MUTEX_FAILED',
|
|
1501
|
+
),
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
expectedKey?.fill(0);
|
|
1506
|
+
if (operationError) {
|
|
1507
|
+
resultKey?.fill(0);
|
|
1508
|
+
throw operationError;
|
|
1509
|
+
}
|
|
1510
|
+
const store = new SmartSecretSealedFileStore(options.destination, resultKey!);
|
|
1511
|
+
resultKey = undefined;
|
|
1512
|
+
return store;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
876
1515
|
/** Explicitly discards this store's ciphertext and kernel master key. */
|
|
877
1516
|
public static async reset(
|
|
878
1517
|
optionsArg: ISmartSecretSealedFileStoreOptions,
|
|
@@ -948,7 +1587,7 @@ export class SmartSecretSealedFileStore {
|
|
|
948
1587
|
deadlineArg,
|
|
949
1588
|
markAcquiredArg,
|
|
950
1589
|
async () => {
|
|
951
|
-
const directoryHandle = await
|
|
1590
|
+
const directoryHandle = await openExistingDirectory(this.directoryPath);
|
|
952
1591
|
const id = entryId(this.options, account);
|
|
953
1592
|
let value: unknown | undefined;
|
|
954
1593
|
let operationError: unknown;
|
|
@@ -1017,7 +1656,7 @@ export class SmartSecretSealedFileStore {
|
|
|
1017
1656
|
deadlineArg,
|
|
1018
1657
|
markAcquiredArg,
|
|
1019
1658
|
async () => {
|
|
1020
|
-
const directoryHandle = await
|
|
1659
|
+
const directoryHandle = await openExistingDirectory(this.directoryPath);
|
|
1021
1660
|
let encrypted: plugins.smartcrypto.IAesGcmCiphertext | undefined;
|
|
1022
1661
|
const id = entryId(this.options, account);
|
|
1023
1662
|
const keyFingerprint = fingerprint(this.masterKey);
|
|
@@ -1078,7 +1717,7 @@ export class SmartSecretSealedFileStore {
|
|
|
1078
1717
|
deadlineArg,
|
|
1079
1718
|
markAcquiredArg,
|
|
1080
1719
|
async () => {
|
|
1081
|
-
const directoryHandle = await
|
|
1720
|
+
const directoryHandle = await openExistingDirectory(this.directoryPath);
|
|
1082
1721
|
let result: boolean | undefined;
|
|
1083
1722
|
let operationError: unknown;
|
|
1084
1723
|
try {
|