@wishknish/knishio-client-ts 1.0.0 → 1.2.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/README.md +13 -0
- package/dist/index.cjs +1304 -121
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +152 -18
- package/dist/index.d.ts +152 -18
- package/dist/index.iife.js +3044 -1049
- package/dist/index.iife.js.map +1 -1
- package/dist/index.js +1295 -122
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/exception/SecretStorageException.ts +9 -0
- package/src/index.ts +16 -2
- package/src/libraries/GraphQLClient.ts +18 -5
- package/src/storage/FileStorageBackend.ts +176 -0
- package/src/storage/MemorySecretStorageProvider.ts +75 -3
- package/src/storage/NonExtractableKeySecretStorageProvider.ts +547 -0
- package/src/storage/WebAuthnPrfSecretStorageProvider.ts +679 -0
- package/src/storage/WebCryptoSecretStorageProvider.ts +105 -134
- package/src/storage/WebStorageBackend.ts +102 -0
- package/src/storage/index.ts +26 -2
- package/src/storage/secretEnvelope.ts +200 -0
- package/src/types/storage.ts +22 -2
|
@@ -49,10 +49,17 @@ License: https://github.com/WishKnish/KnishIO-Client-TS/blob/master/LICENSE
|
|
|
49
49
|
import type {
|
|
50
50
|
ISecretStorageProvider,
|
|
51
51
|
SecretStorageMetadata,
|
|
52
|
-
EncryptedSecretPayload
|
|
52
|
+
EncryptedSecretPayload,
|
|
53
|
+
StorageOptions
|
|
53
54
|
} from '@/types/storage'
|
|
54
55
|
import SecretStorageException from '@/exception/SecretStorageException'
|
|
55
56
|
import { zeroizeBytes, withSecureBytes } from '@/libraries/secureMemory'
|
|
57
|
+
import {
|
|
58
|
+
sealEnvelope,
|
|
59
|
+
openEnvelope,
|
|
60
|
+
SECRET_KEY_PREFIX,
|
|
61
|
+
RECOVERY_KEY_PREFIX
|
|
62
|
+
} from './secretEnvelope'
|
|
56
63
|
|
|
57
64
|
/**
|
|
58
65
|
* Storage backend adapter interface (supports Memory, LocalStorage, IndexedDB, etc.)
|
|
@@ -87,64 +94,35 @@ export class MemoryStorageBackend implements IStorageBackend {
|
|
|
87
94
|
}
|
|
88
95
|
}
|
|
89
96
|
|
|
90
|
-
/**
|
|
91
|
-
* Helper to convert Uint8Array to base64
|
|
92
|
-
*/
|
|
93
|
-
function uint8ArrayToBase64(bytes: Uint8Array): string {
|
|
94
|
-
let binary = ''
|
|
95
|
-
const len = bytes.byteLength
|
|
96
|
-
for (let i = 0; i < len; i++) {
|
|
97
|
-
const byte = bytes[i]
|
|
98
|
-
if (byte !== undefined) {
|
|
99
|
-
binary += String.fromCharCode(byte)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return btoa(binary)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Helper to convert base64 to Uint8Array
|
|
107
|
-
*/
|
|
108
|
-
function base64ToUint8Array(base64: string): Uint8Array {
|
|
109
|
-
const binary = atob(base64)
|
|
110
|
-
const len = binary.length
|
|
111
|
-
const bytes = new Uint8Array(len)
|
|
112
|
-
for (let i = 0; i < len; i++) {
|
|
113
|
-
bytes[i] = binary.charCodeAt(i)
|
|
114
|
-
}
|
|
115
|
-
return bytes
|
|
116
|
-
}
|
|
117
97
|
|
|
118
|
-
const textEncoder = new TextEncoder()
|
|
119
98
|
const textDecoder = new TextDecoder()
|
|
120
|
-
const KEY_PREFIX =
|
|
121
|
-
const DEFAULT_ITERATIONS = 100000
|
|
122
|
-
|
|
99
|
+
const KEY_PREFIX = SECRET_KEY_PREFIX
|
|
123
100
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
101
|
+
* Software envelope-encryption secret storage provider: WebCrypto AES-256-GCM with PBKDF2-HMAC-SHA256.
|
|
102
|
+
* Writes the cross-SDK envelope format; never hardware-backed.
|
|
126
103
|
*/
|
|
127
104
|
export default class WebCryptoSecretStorageProvider implements ISecretStorageProvider {
|
|
128
105
|
public readonly providerType = 'webcrypto-aes-gcm'
|
|
129
106
|
private backend: IStorageBackend
|
|
130
107
|
private defaultPassphrase?: string
|
|
131
|
-
private hardwareBacked: boolean
|
|
132
108
|
|
|
133
109
|
constructor(options: {
|
|
134
110
|
backend?: IStorageBackend
|
|
135
111
|
defaultPassphrase?: string
|
|
136
|
-
hardwareBacked?: boolean
|
|
137
112
|
} = {}) {
|
|
138
113
|
this.backend = options.backend ?? new MemoryStorageBackend()
|
|
139
114
|
this.defaultPassphrase = options.defaultPassphrase
|
|
140
|
-
this.hardwareBacked = options.hardwareBacked ?? false
|
|
141
115
|
}
|
|
142
116
|
|
|
143
117
|
/**
|
|
144
|
-
*
|
|
118
|
+
* True only when this provider holds a non-exportable key inside platform-secure
|
|
119
|
+
* hardware (Android TEE/StrongBox, Secure Enclave, TPM) and learned that from the
|
|
120
|
+
* platform itself — never from a caller argument. Software envelope providers
|
|
121
|
+
* return false. The value is persisted as `metadata.hardwareBacked` in every
|
|
122
|
+
* envelope this provider writes.
|
|
145
123
|
*/
|
|
146
124
|
isHardwareBacked(): boolean {
|
|
147
|
-
return
|
|
125
|
+
return false
|
|
148
126
|
}
|
|
149
127
|
|
|
150
128
|
/**
|
|
@@ -157,48 +135,13 @@ export default class WebCryptoSecretStorageProvider implements ISecretStoragePro
|
|
|
157
135
|
)
|
|
158
136
|
}
|
|
159
137
|
|
|
160
|
-
/**
|
|
161
|
-
* Derive an AES-GCM CryptoKey from a passphrase and salt using PBKDF2
|
|
162
|
-
*/
|
|
163
|
-
private async deriveKey(passphrase: string, salt: Uint8Array, iterations = DEFAULT_ITERATIONS): Promise<CryptoKey> {
|
|
164
|
-
if (!await this.isAvailable()) {
|
|
165
|
-
throw SecretStorageException.unavailable(this.providerType, 'WebCrypto API is not available')
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const passphraseBytes = textEncoder.encode(passphrase)
|
|
169
|
-
try {
|
|
170
|
-
const baseKey = await globalThis.crypto.subtle.importKey(
|
|
171
|
-
'raw',
|
|
172
|
-
passphraseBytes,
|
|
173
|
-
'PBKDF2',
|
|
174
|
-
false,
|
|
175
|
-
['deriveKey']
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
return await globalThis.crypto.subtle.deriveKey(
|
|
179
|
-
{
|
|
180
|
-
name: 'PBKDF2',
|
|
181
|
-
salt: salt as BufferSource,
|
|
182
|
-
iterations,
|
|
183
|
-
hash: 'SHA-256'
|
|
184
|
-
},
|
|
185
|
-
baseKey,
|
|
186
|
-
{ name: 'AES-GCM', length: 256 },
|
|
187
|
-
false,
|
|
188
|
-
['encrypt', 'decrypt']
|
|
189
|
-
)
|
|
190
|
-
} finally {
|
|
191
|
-
zeroizeBytes(passphraseBytes)
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
138
|
/**
|
|
196
139
|
* Store and encrypt a master secret
|
|
197
140
|
*/
|
|
198
141
|
async storeSecret(
|
|
199
142
|
bundleHash: string,
|
|
200
143
|
secret: string,
|
|
201
|
-
options?:
|
|
144
|
+
options?: StorageOptions
|
|
202
145
|
): Promise<void> {
|
|
203
146
|
if (!bundleHash) {
|
|
204
147
|
throw new SecretStorageException('Bundle hash cannot be empty')
|
|
@@ -212,49 +155,39 @@ export default class WebCryptoSecretStorageProvider implements ISecretStoragePro
|
|
|
212
155
|
throw new SecretStorageException('Passphrase required for envelope encryption')
|
|
213
156
|
}
|
|
214
157
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
globalThis.crypto.getRandomValues(iv)
|
|
219
|
-
|
|
220
|
-
const key = await this.deriveKey(passphrase, salt, DEFAULT_ITERATIONS)
|
|
221
|
-
const secretBytes = textEncoder.encode(secret)
|
|
158
|
+
if (!await this.isAvailable()) {
|
|
159
|
+
throw SecretStorageException.unavailable(this.providerType, 'WebCrypto API is not available')
|
|
160
|
+
}
|
|
222
161
|
|
|
223
162
|
try {
|
|
224
|
-
const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
|
|
225
|
-
{
|
|
226
|
-
name: 'AES-GCM',
|
|
227
|
-
iv: iv as BufferSource
|
|
228
|
-
},
|
|
229
|
-
key,
|
|
230
|
-
secretBytes
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
const ciphertext = uint8ArrayToBase64(new Uint8Array(encryptedBuffer))
|
|
234
163
|
const metadata: SecretStorageMetadata = {
|
|
235
164
|
bundleHash,
|
|
236
165
|
label: options?.label,
|
|
237
166
|
createdAt: Date.now(),
|
|
238
|
-
hardwareBacked:
|
|
167
|
+
hardwareBacked: false,
|
|
239
168
|
providerType: this.providerType
|
|
240
169
|
}
|
|
241
170
|
|
|
242
|
-
const payload
|
|
243
|
-
version: 1,
|
|
244
|
-
ciphertext,
|
|
245
|
-
iv: uint8ArrayToBase64(iv),
|
|
246
|
-
salt: uint8ArrayToBase64(salt),
|
|
247
|
-
algorithm: 'AES-GCM',
|
|
248
|
-
iterations: DEFAULT_ITERATIONS,
|
|
249
|
-
metadata
|
|
250
|
-
}
|
|
251
|
-
|
|
171
|
+
const payload = await sealEnvelope(secret, passphrase, metadata)
|
|
252
172
|
await this.backend.setItem(`${KEY_PREFIX}${bundleHash}`, JSON.stringify(payload))
|
|
173
|
+
|
|
174
|
+
if (options?.recoveryPassphrase) {
|
|
175
|
+
const recoveryMetadata: SecretStorageMetadata = {
|
|
176
|
+
bundleHash,
|
|
177
|
+
label: options?.label,
|
|
178
|
+
createdAt: Date.now(),
|
|
179
|
+
hardwareBacked: false,
|
|
180
|
+
providerType: 'webcrypto-aes-gcm'
|
|
181
|
+
}
|
|
182
|
+
const recoveryPayload = await sealEnvelope(secret, options.recoveryPassphrase, recoveryMetadata)
|
|
183
|
+
await this.backend.setItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`, JSON.stringify(recoveryPayload))
|
|
184
|
+
}
|
|
253
185
|
} catch (err: unknown) {
|
|
186
|
+
if (err instanceof SecretStorageException) {
|
|
187
|
+
throw err
|
|
188
|
+
}
|
|
254
189
|
const msg = err instanceof Error ? err.message : String(err)
|
|
255
190
|
throw new SecretStorageException(`Encryption failed: ${msg}`)
|
|
256
|
-
} finally {
|
|
257
|
-
zeroizeBytes(secretBytes)
|
|
258
191
|
}
|
|
259
192
|
}
|
|
260
193
|
|
|
@@ -282,28 +215,21 @@ export default class WebCryptoSecretStorageProvider implements ISecretStoragePro
|
|
|
282
215
|
throw new SecretStorageException('Passphrase required for secret decryption')
|
|
283
216
|
}
|
|
284
217
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
218
|
+
if (!await this.isAvailable()) {
|
|
219
|
+
throw SecretStorageException.unavailable(this.providerType, 'WebCrypto API is not available')
|
|
220
|
+
}
|
|
288
221
|
|
|
289
222
|
try {
|
|
290
|
-
const
|
|
291
|
-
const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
|
|
292
|
-
{
|
|
293
|
-
name: 'AES-GCM',
|
|
294
|
-
iv: iv as BufferSource
|
|
295
|
-
},
|
|
296
|
-
key,
|
|
297
|
-
ciphertext as BufferSource
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
const decryptedBytes = new Uint8Array(decryptedBuffer)
|
|
223
|
+
const decryptedBytes = await openEnvelope(payload, passphrase)
|
|
301
224
|
try {
|
|
302
225
|
return textDecoder.decode(decryptedBytes)
|
|
303
226
|
} finally {
|
|
304
227
|
zeroizeBytes(decryptedBytes)
|
|
305
228
|
}
|
|
306
229
|
} catch (err: unknown) {
|
|
230
|
+
if (err instanceof SecretStorageException) {
|
|
231
|
+
throw err
|
|
232
|
+
}
|
|
307
233
|
const msg = err instanceof Error ? err.message : String(err)
|
|
308
234
|
throw SecretStorageException.decryptionFailed(msg)
|
|
309
235
|
}
|
|
@@ -314,7 +240,9 @@ export default class WebCryptoSecretStorageProvider implements ISecretStoragePro
|
|
|
314
240
|
*/
|
|
315
241
|
async deleteSecret(bundleHash: string): Promise<boolean> {
|
|
316
242
|
const key = `${KEY_PREFIX}${bundleHash}`
|
|
243
|
+
const recoveryKey = `${RECOVERY_KEY_PREFIX}${bundleHash}`
|
|
317
244
|
const result = await this.backend.removeItem(key)
|
|
245
|
+
await this.backend.removeItem(recoveryKey)
|
|
318
246
|
return result !== false
|
|
319
247
|
}
|
|
320
248
|
|
|
@@ -331,7 +259,7 @@ export default class WebCryptoSecretStorageProvider implements ISecretStoragePro
|
|
|
331
259
|
*/
|
|
332
260
|
async listSecrets(): Promise<SecretStorageMetadata[]> {
|
|
333
261
|
const keys = await this.backend.keys()
|
|
334
|
-
const matchingKeys = keys.filter(k => k.startsWith(KEY_PREFIX))
|
|
262
|
+
const matchingKeys = keys.filter(k => k.startsWith(KEY_PREFIX) && !k.startsWith(RECOVERY_KEY_PREFIX))
|
|
335
263
|
const results: SecretStorageMetadata[] = []
|
|
336
264
|
|
|
337
265
|
for (const key of matchingKeys) {
|
|
@@ -376,22 +304,12 @@ export default class WebCryptoSecretStorageProvider implements ISecretStoragePro
|
|
|
376
304
|
throw new SecretStorageException('Passphrase required for secret decryption')
|
|
377
305
|
}
|
|
378
306
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
307
|
+
if (!await this.isAvailable()) {
|
|
308
|
+
throw SecretStorageException.unavailable(this.providerType, 'WebCrypto API is not available')
|
|
309
|
+
}
|
|
382
310
|
|
|
383
311
|
try {
|
|
384
|
-
const
|
|
385
|
-
const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
|
|
386
|
-
{
|
|
387
|
-
name: 'AES-GCM',
|
|
388
|
-
iv: iv as BufferSource
|
|
389
|
-
},
|
|
390
|
-
key,
|
|
391
|
-
ciphertext as BufferSource
|
|
392
|
-
)
|
|
393
|
-
|
|
394
|
-
const decryptedBytes = new Uint8Array(decryptedBuffer)
|
|
312
|
+
const decryptedBytes = await openEnvelope(payload, passphrase)
|
|
395
313
|
return await withSecureBytes(decryptedBytes, async (bytes) => {
|
|
396
314
|
const secretString = textDecoder.decode(bytes)
|
|
397
315
|
return await fn(secretString)
|
|
@@ -404,4 +322,57 @@ export default class WebCryptoSecretStorageProvider implements ISecretStoragePro
|
|
|
404
322
|
throw SecretStorageException.decryptionFailed(msg)
|
|
405
323
|
}
|
|
406
324
|
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Recover a secret using its recovery envelope and re-enroll it
|
|
328
|
+
*/
|
|
329
|
+
async recoverSecret(
|
|
330
|
+
bundleHash: string,
|
|
331
|
+
recoveryPassphrase: string,
|
|
332
|
+
options?: { label?: string; passphrase?: string }
|
|
333
|
+
): Promise<void> {
|
|
334
|
+
if (!bundleHash) {
|
|
335
|
+
throw new SecretStorageException('Bundle hash cannot be empty')
|
|
336
|
+
}
|
|
337
|
+
if (!recoveryPassphrase) {
|
|
338
|
+
throw new SecretStorageException('Recovery passphrase cannot be empty')
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const raw = await this.backend.getItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`)
|
|
342
|
+
if (!raw) {
|
|
343
|
+
throw SecretStorageException.notFound(bundleHash)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
let payload: EncryptedSecretPayload
|
|
347
|
+
try {
|
|
348
|
+
payload = JSON.parse(raw)
|
|
349
|
+
} catch {
|
|
350
|
+
throw SecretStorageException.decryptionFailed('Corrupted recovery payload format')
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
let decryptedBytes: Uint8Array
|
|
354
|
+
try {
|
|
355
|
+
decryptedBytes = await openEnvelope(payload, recoveryPassphrase)
|
|
356
|
+
} catch (err: unknown) {
|
|
357
|
+
if (err instanceof SecretStorageException) {
|
|
358
|
+
throw err
|
|
359
|
+
}
|
|
360
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
361
|
+
throw SecretStorageException.decryptionFailed(msg)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
let secretStr: string
|
|
365
|
+
try {
|
|
366
|
+
secretStr = textDecoder.decode(decryptedBytes)
|
|
367
|
+
} finally {
|
|
368
|
+
zeroizeBytes(decryptedBytes)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const storePassphrase = options?.passphrase ?? this.defaultPassphrase ?? recoveryPassphrase
|
|
372
|
+
await this.storeSecret(bundleHash, secretStr, {
|
|
373
|
+
...options,
|
|
374
|
+
passphrase: storePassphrase,
|
|
375
|
+
recoveryPassphrase
|
|
376
|
+
})
|
|
377
|
+
}
|
|
407
378
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/*
|
|
2
|
+
(
|
|
3
|
+
(/(
|
|
4
|
+
(//(
|
|
5
|
+
(///(
|
|
6
|
+
(/////(
|
|
7
|
+
(//////( )
|
|
8
|
+
(////////( (/)
|
|
9
|
+
(////////( (///)
|
|
10
|
+
(//////////( (////)
|
|
11
|
+
(//////////( (//////)
|
|
12
|
+
(////////////( (///////)
|
|
13
|
+
(/////////////( (/////////)
|
|
14
|
+
(//////////////( (///////////)
|
|
15
|
+
(///////////////( (/////////////)
|
|
16
|
+
(////////////////( (//////////////)
|
|
17
|
+
((((((((((((((((((( (((((((((((((((
|
|
18
|
+
((((((((((((((((((( ((((((((((((((
|
|
19
|
+
((((((((((((((((((( ((((((((((((((
|
|
20
|
+
(((((((((((((((((((( (((((((((((((
|
|
21
|
+
(((((((((((((((((((( ((((((((((((
|
|
22
|
+
((((((((((((((((((( ((((((((((((
|
|
23
|
+
((((((((((((((((((( ((((((((((
|
|
24
|
+
((((((((((((((((((/ (((((((((
|
|
25
|
+
(((((((((((((((((( ((((((((
|
|
26
|
+
((((((((((((((((( (((((((
|
|
27
|
+
(((((((((((((((((( (((((
|
|
28
|
+
################# ##
|
|
29
|
+
################ #
|
|
30
|
+
################# ##
|
|
31
|
+
%################ ###
|
|
32
|
+
###############( ####
|
|
33
|
+
############### ####
|
|
34
|
+
############### ######
|
|
35
|
+
%#############( (#######
|
|
36
|
+
%############# #########
|
|
37
|
+
############( ##########
|
|
38
|
+
########### #############
|
|
39
|
+
######### ##############
|
|
40
|
+
%######
|
|
41
|
+
|
|
42
|
+
Powered by Knish.IO: Connecting a Decentralized World
|
|
43
|
+
|
|
44
|
+
Please visit https://github.com/WishKnish/KnishIO-Client-TS for information.
|
|
45
|
+
|
|
46
|
+
License: https://github.com/WishKnish/KnishIO-Client-TS/blob/master/LICENSE
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import type { IStorageBackend } from './WebCryptoSecretStorageProvider'
|
|
50
|
+
import SecretStorageException from '@/exception/SecretStorageException'
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Browser persistent storage backend wrapping Web Storage (localStorage or sessionStorage).
|
|
54
|
+
* Adapts the Web Storage API (length + key(i)) to IStorageBackend.keys(), filtering
|
|
55
|
+
* by a prefix (defaults to 'knishio:') so unrelated items are ignored.
|
|
56
|
+
*/
|
|
57
|
+
export default class WebStorageBackend implements IStorageBackend {
|
|
58
|
+
private readonly storage: Storage
|
|
59
|
+
readonly prefix: string
|
|
60
|
+
|
|
61
|
+
constructor(storage?: Storage, prefix = 'knishio:') {
|
|
62
|
+
if (storage) {
|
|
63
|
+
this.storage = storage
|
|
64
|
+
} else if (typeof globalThis !== 'undefined' && globalThis.localStorage) {
|
|
65
|
+
this.storage = globalThis.localStorage
|
|
66
|
+
} else {
|
|
67
|
+
throw SecretStorageException.unavailable(
|
|
68
|
+
'web-storage',
|
|
69
|
+
'WebStorageBackend requires a Storage object or global localStorage'
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
this.prefix = prefix
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getItem(key: string): string | null {
|
|
76
|
+
return this.storage.getItem(key)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
setItem(key: string, value: string): void {
|
|
80
|
+
this.storage.setItem(key, value)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
removeItem(key: string): boolean {
|
|
84
|
+
const existed = this.storage.getItem(key) !== null
|
|
85
|
+
this.storage.removeItem(key)
|
|
86
|
+
return existed
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
keys(): string[] {
|
|
90
|
+
const result: string[] = []
|
|
91
|
+
const len = this.storage.length
|
|
92
|
+
for (let i = 0; i < len; i++) {
|
|
93
|
+
const k = this.storage.key(i)
|
|
94
|
+
if (k !== null) {
|
|
95
|
+
if (!this.prefix || k.startsWith(this.prefix)) {
|
|
96
|
+
result.push(k)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return result
|
|
101
|
+
}
|
|
102
|
+
}
|
package/src/storage/index.ts
CHANGED
|
@@ -52,6 +52,32 @@ export {
|
|
|
52
52
|
MemoryStorageBackend,
|
|
53
53
|
type IStorageBackend
|
|
54
54
|
} from './WebCryptoSecretStorageProvider'
|
|
55
|
+
export { default as FileStorageBackend } from './FileStorageBackend'
|
|
56
|
+
export { default as WebStorageBackend } from './WebStorageBackend'
|
|
57
|
+
export {
|
|
58
|
+
default as WebAuthnPrfSecretStorageProvider,
|
|
59
|
+
type WebAuthnPrfSecretStorageOptions,
|
|
60
|
+
PRF_SALT_LABEL,
|
|
61
|
+
KEK_INFO
|
|
62
|
+
} from './WebAuthnPrfSecretStorageProvider'
|
|
63
|
+
export {
|
|
64
|
+
default as NonExtractableKeySecretStorageProvider,
|
|
65
|
+
type NonExtractableKeyStorageOptions,
|
|
66
|
+
type IKeyStore,
|
|
67
|
+
IndexedDbKeyStore,
|
|
68
|
+
MemoryKeyStore
|
|
69
|
+
} from './NonExtractableKeySecretStorageProvider'
|
|
70
|
+
export {
|
|
71
|
+
sealEnvelope,
|
|
72
|
+
openEnvelope,
|
|
73
|
+
uint8ArrayToBase64,
|
|
74
|
+
base64ToUint8Array,
|
|
75
|
+
deriveEnvelopeKey,
|
|
76
|
+
ENVELOPE_ALGORITHM,
|
|
77
|
+
DEFAULT_ITERATIONS,
|
|
78
|
+
SECRET_KEY_PREFIX,
|
|
79
|
+
RECOVERY_KEY_PREFIX
|
|
80
|
+
} from './secretEnvelope'
|
|
55
81
|
|
|
56
82
|
import type { ISecretStorageProvider } from '@/types/storage'
|
|
57
83
|
import MemorySecretStorageProvider from './MemorySecretStorageProvider'
|
|
@@ -61,7 +87,6 @@ export interface CreateSecretStorageOptions {
|
|
|
61
87
|
type?: 'webcrypto' | 'memory'
|
|
62
88
|
defaultPassphrase?: string
|
|
63
89
|
backend?: IStorageBackend
|
|
64
|
-
hardwareBacked?: boolean
|
|
65
90
|
}
|
|
66
91
|
|
|
67
92
|
/**
|
|
@@ -79,7 +104,6 @@ export function createDefaultSecretStorage(
|
|
|
79
104
|
return new WebCryptoSecretStorageProvider({
|
|
80
105
|
backend: options.backend,
|
|
81
106
|
defaultPassphrase: options.defaultPassphrase,
|
|
82
|
-
hardwareBacked: options.hardwareBacked
|
|
83
107
|
})
|
|
84
108
|
}
|
|
85
109
|
|