@libp2p/keychain 3.0.7 → 3.0.8-0b4a2ee79

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/src/index.ts CHANGED
@@ -22,7 +22,7 @@
22
22
  *
23
23
  * The **key id** is the SHA-256 [multihash](https://github.com/multiformats/multihash) of its public key.
24
24
  *
25
- * The *public key* is a [protobuf encoding](https://github.com/libp2p/js-libp2p-crypto/blob/master/src/keys/keys.proto.js) containing a type and the [DER encoding](https://en.wikipedia.org/wiki/X.690) of the PKCS [SubjectPublicKeyInfo](https://www.ietf.org/rfc/rfc3279.txt).
25
+ * The *public key* is a [protobuf encoding](https://github.com/libp2p/js-libp2p/blob/main/packages/crypto/src/keys/keys.proto.js) containing a type and the [DER encoding](https://en.wikipedia.org/wiki/X.690) of the PKCS [SubjectPublicKeyInfo](https://www.ietf.org/rfc/rfc3279.txt).
26
26
  *
27
27
  * ## Private key storage
28
28
  *
@@ -50,25 +50,12 @@
50
50
  * A key benefit is that now the key chain can be used in browser with the [js-datastore-level](https://github.com/ipfs/js-datastore-level) implementation.
51
51
  */
52
52
 
53
- /* eslint max-nested-callbacks: ["error", 5] */
54
-
55
- import { pbkdf2, randomBytes } from '@libp2p/crypto'
56
- import { generateKeyPair, importKey, unmarshalPrivateKey } from '@libp2p/crypto/keys'
57
- import { CodeError } from '@libp2p/interface/errors'
58
- import { logger } from '@libp2p/logger'
59
- import { peerIdFromKeys } from '@libp2p/peer-id'
60
- import { Key } from 'interface-datastore/key'
61
- import mergeOptions from 'merge-options'
62
- import sanitize from 'sanitize-filename'
63
- import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
64
- import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
65
- import { codes } from './errors.js'
66
- import type { KeyChain, KeyInfo } from '@libp2p/interface/keychain'
53
+ import { DefaultKeychain } from './keychain.js'
54
+ import type { ComponentLogger } from '@libp2p/interface'
67
55
  import type { KeyType } from '@libp2p/interface/keys'
68
56
  import type { PeerId } from '@libp2p/interface/peer-id'
69
57
  import type { Datastore } from 'interface-datastore'
70
-
71
- const log = logger('libp2p:keychain')
58
+ import type { Multibase } from 'multiformats/bases/interface.js'
72
59
 
73
60
  export interface DEKConfig {
74
61
  hash: string
@@ -77,556 +64,160 @@ export interface DEKConfig {
77
64
  keyLength: number
78
65
  }
79
66
 
80
- export interface KeyChainInit {
67
+ export interface KeychainInit {
81
68
  pass?: string
82
69
  dek?: DEKConfig
83
70
  }
84
71
 
85
- const keyPrefix = '/pkcs8/'
86
- const infoPrefix = '/info/'
87
- const privates = new WeakMap<object, { dek: string }>()
88
-
89
- // NIST SP 800-132
90
- const NIST = {
91
- minKeyLength: 112 / 8,
92
- minSaltLength: 128 / 8,
93
- minIterationCount: 1000
94
- }
95
-
96
- const defaultOptions = {
97
- // See https://cryptosense.com/parametesr-choice-for-pbkdf2/
98
- dek: {
99
- keyLength: 512 / 8,
100
- iterationCount: 10000,
101
- salt: 'you should override this value with a crypto secure random number',
102
- hash: 'sha2-512'
103
- }
104
- }
105
-
106
- function validateKeyName (name: string): boolean {
107
- if (name == null) {
108
- return false
109
- }
110
- if (typeof name !== 'string') {
111
- return false
112
- }
113
- return name === sanitize(name.trim()) && name.length > 0
114
- }
115
-
116
- /**
117
- * Throws an error after a delay
118
- *
119
- * This assumes than an error indicates that the keychain is under attack. Delay returning an
120
- * error to make brute force attacks harder.
121
- */
122
- async function randomDelay (): Promise<void> {
123
- const min = 200
124
- const max = 1000
125
- const delay = Math.random() * (max - min) + min
126
-
127
- await new Promise(resolve => setTimeout(resolve, delay))
128
- }
129
-
130
- /**
131
- * Converts a key name into a datastore name
132
- */
133
- function DsName (name: string): Key {
134
- return new Key(keyPrefix + name)
135
- }
136
-
137
- /**
138
- * Converts a key name into a datastore info name
139
- */
140
- function DsInfoName (name: string): Key {
141
- return new Key(infoPrefix + name)
142
- }
143
-
144
- export interface KeyChainComponents {
72
+ export interface KeychainComponents {
145
73
  datastore: Datastore
74
+ logger: ComponentLogger
146
75
  }
147
76
 
148
- /**
149
- * Manages the lifecycle of a key. Keys are encrypted at rest using PKCS #8.
150
- *
151
- * A key in the store has two entries
152
- * - '/info/*key-name*', contains the KeyInfo for the key
153
- * - '/pkcs8/*key-name*', contains the PKCS #8 for the key
154
- *
155
- */
156
- export class DefaultKeyChain implements KeyChain {
157
- private readonly components: KeyChainComponents
158
- private readonly init: KeyChainInit
159
-
77
+ export interface KeyInfo {
160
78
  /**
161
- * Creates a new instance of a key chain
79
+ * The universally unique key id
162
80
  */
163
- constructor (components: KeyChainComponents, init: KeyChainInit) {
164
- this.components = components
165
- this.init = mergeOptions(defaultOptions, init)
166
-
167
- // Enforce NIST SP 800-132
168
- if (this.init.pass != null && this.init.pass?.length < 20) {
169
- throw new Error('pass must be least 20 characters')
170
- }
171
- if (this.init.dek?.keyLength != null && this.init.dek.keyLength < NIST.minKeyLength) {
172
- throw new Error(`dek.keyLength must be least ${NIST.minKeyLength} bytes`)
173
- }
174
- if (this.init.dek?.salt?.length != null && this.init.dek.salt.length < NIST.minSaltLength) {
175
- throw new Error(`dek.saltLength must be least ${NIST.minSaltLength} bytes`)
176
- }
177
- if (this.init.dek?.iterationCount != null && this.init.dek.iterationCount < NIST.minIterationCount) {
178
- throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`)
179
- }
180
-
181
- const dek = this.init.pass != null && this.init.dek?.salt != null
182
- ? pbkdf2(
183
- this.init.pass,
184
- this.init.dek?.salt,
185
- this.init.dek?.iterationCount,
186
- this.init.dek?.keyLength,
187
- this.init.dek?.hash)
188
- : ''
189
-
190
- privates.set(this, { dek })
191
- }
81
+ id: string
192
82
 
193
83
  /**
194
- * Generates the options for a keychain. A random salt is produced.
195
- *
196
- * @returns {object}
84
+ * The local key name
197
85
  */
198
- static generateOptions (): KeyChainInit {
199
- const options = Object.assign({}, defaultOptions)
200
- const saltLength = Math.ceil(NIST.minSaltLength / 3) * 3 // no base64 padding
201
- options.dek.salt = uint8ArrayToString(randomBytes(saltLength), 'base64')
202
- return options
203
- }
86
+ name: string
87
+ }
204
88
 
89
+ export interface Keychain {
205
90
  /**
206
- * Gets an object that can encrypt/decrypt protected data.
207
- * The default options for a keychain.
91
+ * Export an existing key as a PEM encrypted PKCS #8 string.
208
92
  *
209
- * @returns {object}
210
- */
211
- static get options (): typeof defaultOptions {
212
- return defaultOptions
213
- }
214
-
215
- /**
216
- * Create a new key.
93
+ * @example
217
94
  *
218
- * @param {string} name - The local key name; cannot already exist.
219
- * @param {string} type - One of the key types; 'rsa'.
220
- * @param {number} [size = 2048] - The key size in bits. Used for rsa keys only
95
+ * ```js
96
+ * await libp2p.keychain.createKey('keyTest', 'RSA', 4096)
97
+ * const pemKey = await libp2p.keychain.exportKey('keyTest', 'password123')
98
+ * ```
221
99
  */
222
- async createKey (name: string, type: KeyType, size = 2048): Promise<KeyInfo> {
223
- if (!validateKeyName(name) || name === 'self') {
224
- await randomDelay()
225
- throw new CodeError('Invalid key name', codes.ERR_INVALID_KEY_NAME)
226
- }
227
-
228
- if (typeof type !== 'string') {
229
- await randomDelay()
230
- throw new CodeError('Invalid key type', codes.ERR_INVALID_KEY_TYPE)
231
- }
232
-
233
- const dsname = DsName(name)
234
- const exists = await this.components.datastore.has(dsname)
235
- if (exists) {
236
- await randomDelay()
237
- throw new CodeError('Key name already exists', codes.ERR_KEY_ALREADY_EXISTS)
238
- }
239
-
240
- switch (type.toLowerCase()) {
241
- case 'rsa':
242
- if (!Number.isSafeInteger(size) || size < 2048) {
243
- await randomDelay()
244
- throw new CodeError('Invalid RSA key size', codes.ERR_INVALID_KEY_SIZE)
245
- }
246
- break
247
- default:
248
- break
249
- }
250
-
251
- let keyInfo
252
- try {
253
- const keypair = await generateKeyPair(type, size)
254
- const kid = await keypair.id()
255
- const cached = privates.get(this)
256
-
257
- if (cached == null) {
258
- throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS)
259
- }
260
-
261
- const dek = cached.dek
262
- const pem = await keypair.export(dek)
263
- keyInfo = {
264
- name,
265
- id: kid
266
- }
267
- const batch = this.components.datastore.batch()
268
- batch.put(dsname, uint8ArrayFromString(pem))
269
- batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo)))
270
-
271
- await batch.commit()
272
- } catch (err: any) {
273
- await randomDelay()
274
- throw err
275
- }
276
-
277
- return keyInfo
278
- }
100
+ exportKey(name: string, password: string): Promise<Multibase<'m'>>
279
101
 
280
102
  /**
281
- * List all the keys.
103
+ * Import a new key from a PEM encoded PKCS #8 string.
282
104
  *
283
- * @returns {Promise<KeyInfo[]>}
284
- */
285
- async listKeys (): Promise<KeyInfo[]> {
286
- const query = {
287
- prefix: infoPrefix
288
- }
289
-
290
- const info = []
291
- for await (const value of this.components.datastore.query(query)) {
292
- info.push(JSON.parse(uint8ArrayToString(value.value)))
293
- }
294
-
295
- return info
296
- }
297
-
298
- /**
299
- * Find a key by it's id
105
+ * @example
106
+ *
107
+ * ```js
108
+ * await libp2p.keychain.createKey('keyTest', 'RSA', 4096)
109
+ * const pemKey = await libp2p.keychain.exportKey('keyTest', 'password123')
110
+ * const keyInfo = await libp2p.keychain.importKey('keyTestImport', pemKey, 'password123')
111
+ * ```
300
112
  */
301
- async findKeyById (id: string): Promise<KeyInfo> {
302
- try {
303
- const keys = await this.listKeys()
304
- const key = keys.find((k) => k.id === id)
305
-
306
- if (key == null) {
307
- throw new CodeError(`Key with id '${id}' does not exist.`, codes.ERR_KEY_NOT_FOUND)
308
- }
309
-
310
- return key
311
- } catch (err: any) {
312
- await randomDelay()
313
- throw err
314
- }
315
- }
113
+ importKey(name: string, pem: string, password: string): Promise<KeyInfo>
316
114
 
317
115
  /**
318
- * Find a key by it's name.
116
+ * Import a new key from a PeerId with a private key component
117
+ *
118
+ * @example
319
119
  *
320
- * @param {string} name - The local key name.
321
- * @returns {Promise<KeyInfo>}
120
+ * ```js
121
+ * const keyInfo = await libp2p.keychain.importPeer('keyTestImport', peerIdFromString('12D3Foo...'))
122
+ * ```
322
123
  */
323
- async findKeyByName (name: string): Promise<KeyInfo> {
324
- if (!validateKeyName(name)) {
325
- await randomDelay()
326
- throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME)
327
- }
328
-
329
- const dsname = DsInfoName(name)
330
- try {
331
- const res = await this.components.datastore.get(dsname)
332
- return JSON.parse(uint8ArrayToString(res))
333
- } catch (err: any) {
334
- await randomDelay()
335
- log.error(err)
336
- throw new CodeError(`Key '${name}' does not exist.`, codes.ERR_KEY_NOT_FOUND)
337
- }
338
- }
124
+ importPeer(name: string, peerId: PeerId): Promise<KeyInfo>
339
125
 
340
126
  /**
341
- * Remove an existing key.
127
+ * Export an existing key as a PeerId
128
+ *
129
+ * @example
342
130
  *
343
- * @param {string} name - The local key name; must already exist.
344
- * @returns {Promise<KeyInfo>}
131
+ * ```js
132
+ * const peerId = await libp2p.keychain.exportPeerId('key-name')
133
+ * ```
345
134
  */
346
- async removeKey (name: string): Promise<KeyInfo> {
347
- if (!validateKeyName(name) || name === 'self') {
348
- await randomDelay()
349
- throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME)
350
- }
351
- const dsname = DsName(name)
352
- const keyInfo = await this.findKeyByName(name)
353
- const batch = this.components.datastore.batch()
354
- batch.delete(dsname)
355
- batch.delete(DsInfoName(name))
356
- await batch.commit()
357
- return keyInfo
358
- }
135
+ exportPeerId(name: string): Promise<PeerId>
359
136
 
360
137
  /**
361
- * Rename a key
138
+ * Create a key in the keychain.
139
+ *
140
+ * @example
362
141
  *
363
- * @param {string} oldName - The old local key name; must already exist.
364
- * @param {string} newName - The new local key name; must not already exist.
365
- * @returns {Promise<KeyInfo>}
142
+ * ```js
143
+ * const keyInfo = await libp2p.keychain.createKey('keyTest', 'RSA', 4096)
144
+ * ```
366
145
  */
367
- async renameKey (oldName: string, newName: string): Promise<KeyInfo> {
368
- if (!validateKeyName(oldName) || oldName === 'self') {
369
- await randomDelay()
370
- throw new CodeError(`Invalid old key name '${oldName}'`, codes.ERR_OLD_KEY_NAME_INVALID)
371
- }
372
- if (!validateKeyName(newName) || newName === 'self') {
373
- await randomDelay()
374
- throw new CodeError(`Invalid new key name '${newName}'`, codes.ERR_NEW_KEY_NAME_INVALID)
375
- }
376
- const oldDsname = DsName(oldName)
377
- const newDsname = DsName(newName)
378
- const oldInfoName = DsInfoName(oldName)
379
- const newInfoName = DsInfoName(newName)
380
-
381
- const exists = await this.components.datastore.has(newDsname)
382
- if (exists) {
383
- await randomDelay()
384
- throw new CodeError(`Key '${newName}' already exists`, codes.ERR_KEY_ALREADY_EXISTS)
385
- }
386
-
387
- try {
388
- const pem = await this.components.datastore.get(oldDsname)
389
- const res = await this.components.datastore.get(oldInfoName)
390
-
391
- const keyInfo = JSON.parse(uint8ArrayToString(res))
392
- keyInfo.name = newName
393
- const batch = this.components.datastore.batch()
394
- batch.put(newDsname, pem)
395
- batch.put(newInfoName, uint8ArrayFromString(JSON.stringify(keyInfo)))
396
- batch.delete(oldDsname)
397
- batch.delete(oldInfoName)
398
- await batch.commit()
399
- return keyInfo
400
- } catch (err: any) {
401
- await randomDelay()
402
- throw err
403
- }
404
- }
146
+ createKey(name: string, type: KeyType, size?: number): Promise<KeyInfo>
405
147
 
406
148
  /**
407
- * Export an existing key as a PEM encrypted PKCS #8 string
149
+ * List all the keys.
150
+ *
151
+ * @example
152
+ *
153
+ * ```js
154
+ * const keyInfos = await libp2p.keychain.listKeys()
155
+ * ```
408
156
  */
409
- async exportKey (name: string, password: string): Promise<string> {
410
- if (!validateKeyName(name)) {
411
- await randomDelay()
412
- throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME)
413
- }
414
- if (password == null) {
415
- await randomDelay()
416
- throw new CodeError('Password is required', codes.ERR_PASSWORD_REQUIRED)
417
- }
418
-
419
- const dsname = DsName(name)
420
- try {
421
- const res = await this.components.datastore.get(dsname)
422
- const pem = uint8ArrayToString(res)
423
- const cached = privates.get(this)
424
-
425
- if (cached == null) {
426
- throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS)
427
- }
428
-
429
- const dek = cached.dek
430
- const privateKey = await importKey(pem, dek)
431
- const keyString = await privateKey.export(password)
432
-
433
- return keyString
434
- } catch (err: any) {
435
- await randomDelay()
436
- throw err
437
- }
438
- }
157
+ listKeys(): Promise<KeyInfo[]>
439
158
 
440
159
  /**
441
- * Export an existing key as a PeerId
160
+ * Removes a key from the keychain.
161
+ *
162
+ * @example
163
+ *
164
+ * ```js
165
+ * await libp2p.keychain.createKey('keyTest', 'RSA', 4096)
166
+ * const keyInfo = await libp2p.keychain.removeKey('keyTest')
167
+ * ```
442
168
  */
443
- async exportPeerId (name: string): Promise<PeerId> {
444
- const password = 'temporary-password'
445
- const pem = await this.exportKey(name, password)
446
- const privateKey = await importKey(pem, password)
447
-
448
- return peerIdFromKeys(privateKey.public.bytes, privateKey.bytes)
449
- }
169
+ removeKey(name: string): Promise<KeyInfo>
450
170
 
451
171
  /**
452
- * Import a new key from a PEM encoded PKCS #8 string
172
+ * Rename a key in the keychain.
453
173
  *
454
- * @param {string} name - The local key name; must not already exist.
455
- * @param {string} pem - The PEM encoded PKCS #8 string
456
- * @param {string} password - The password.
457
- * @returns {Promise<KeyInfo>}
174
+ * @example
175
+ *
176
+ * ```js
177
+ * await libp2p.keychain.createKey('keyTest', 'RSA', 4096)
178
+ * const keyInfo = await libp2p.keychain.renameKey('keyTest', 'keyNewNtest')
179
+ * ```
458
180
  */
459
- async importKey (name: string, pem: string, password: string): Promise<KeyInfo> {
460
- if (!validateKeyName(name) || name === 'self') {
461
- await randomDelay()
462
- throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME)
463
- }
464
- if (pem == null) {
465
- await randomDelay()
466
- throw new CodeError('PEM encoded key is required', codes.ERR_PEM_REQUIRED)
467
- }
468
- const dsname = DsName(name)
469
- const exists = await this.components.datastore.has(dsname)
470
- if (exists) {
471
- await randomDelay()
472
- throw new CodeError(`Key '${name}' already exists`, codes.ERR_KEY_ALREADY_EXISTS)
473
- }
474
-
475
- let privateKey
476
- try {
477
- privateKey = await importKey(pem, password)
478
- } catch (err: any) {
479
- await randomDelay()
480
- throw new CodeError('Cannot read the key, most likely the password is wrong', codes.ERR_CANNOT_READ_KEY)
481
- }
482
-
483
- let kid
484
- try {
485
- kid = await privateKey.id()
486
- const cached = privates.get(this)
487
-
488
- if (cached == null) {
489
- throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS)
490
- }
491
-
492
- const dek = cached.dek
493
- pem = await privateKey.export(dek)
494
- } catch (err: any) {
495
- await randomDelay()
496
- throw err
497
- }
498
-
499
- const keyInfo = {
500
- name,
501
- id: kid
502
- }
503
- const batch = this.components.datastore.batch()
504
- batch.put(dsname, uint8ArrayFromString(pem))
505
- batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo)))
506
- await batch.commit()
507
-
508
- return keyInfo
509
- }
181
+ renameKey(oldName: string, newName: string): Promise<KeyInfo>
510
182
 
511
183
  /**
512
- * Import a peer key
184
+ * Find a key by it's id.
185
+ *
186
+ * @example
187
+ *
188
+ * ```js
189
+ * const keyInfo = await libp2p.keychain.createKey('keyTest', 'RSA', 4096)
190
+ * const keyInfo2 = await libp2p.keychain.findKeyById(keyInfo.id)
191
+ * ```
513
192
  */
514
- async importPeer (name: string, peer: PeerId): Promise<KeyInfo> {
515
- try {
516
- if (!validateKeyName(name)) {
517
- throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME)
518
- }
519
- if (peer == null) {
520
- throw new CodeError('PeerId is required', codes.ERR_MISSING_PRIVATE_KEY)
521
- }
522
- if (peer.privateKey == null) {
523
- throw new CodeError('PeerId.privKey is required', codes.ERR_MISSING_PRIVATE_KEY)
524
- }
525
-
526
- const privateKey = await unmarshalPrivateKey(peer.privateKey)
527
-
528
- const dsname = DsName(name)
529
- const exists = await this.components.datastore.has(dsname)
530
- if (exists) {
531
- await randomDelay()
532
- throw new CodeError(`Key '${name}' already exists`, codes.ERR_KEY_ALREADY_EXISTS)
533
- }
534
-
535
- const cached = privates.get(this)
536
-
537
- if (cached == null) {
538
- throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS)
539
- }
540
-
541
- const dek = cached.dek
542
- const pem = await privateKey.export(dek)
543
- const keyInfo: KeyInfo = {
544
- name,
545
- id: peer.toString()
546
- }
547
- const batch = this.components.datastore.batch()
548
- batch.put(dsname, uint8ArrayFromString(pem))
549
- batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo)))
550
- await batch.commit()
551
- return keyInfo
552
- } catch (err: any) {
553
- await randomDelay()
554
- throw err
555
- }
556
- }
193
+ findKeyById(id: string): Promise<KeyInfo>
557
194
 
558
195
  /**
559
- * Gets the private key as PEM encoded PKCS #8 string
196
+ * Find a key by it's name.
197
+ *
198
+ * @example
199
+ *
200
+ * ```js
201
+ * const keyInfo = await libp2p.keychain.createKey('keyTest', 'RSA', 4096)
202
+ * const keyInfo2 = await libp2p.keychain.findKeyByName('keyTest')
203
+ * ```
560
204
  */
561
- async getPrivateKey (name: string): Promise<string> {
562
- if (!validateKeyName(name)) {
563
- await randomDelay()
564
- throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME)
565
- }
566
-
567
- try {
568
- const dsname = DsName(name)
569
- const res = await this.components.datastore.get(dsname)
570
- return uint8ArrayToString(res)
571
- } catch (err: any) {
572
- await randomDelay()
573
- log.error(err)
574
- throw new CodeError(`Key '${name}' does not exist.`, codes.ERR_KEY_NOT_FOUND)
575
- }
576
- }
205
+ findKeyByName(name: string): Promise<KeyInfo>
577
206
 
578
207
  /**
579
208
  * Rotate keychain password and re-encrypt all associated keys
209
+ *
210
+ * @example
211
+ *
212
+ * ```js
213
+ * await libp2p.keychain.rotateKeychainPass('oldPassword', 'newPassword')
214
+ * ```
580
215
  */
581
- async rotateKeychainPass (oldPass: string, newPass: string): Promise<void> {
582
- if (typeof oldPass !== 'string') {
583
- await randomDelay()
584
- throw new CodeError(`Invalid old pass type '${typeof oldPass}'`, codes.ERR_INVALID_OLD_PASS_TYPE)
585
- }
586
- if (typeof newPass !== 'string') {
587
- await randomDelay()
588
- throw new CodeError(`Invalid new pass type '${typeof newPass}'`, codes.ERR_INVALID_NEW_PASS_TYPE)
589
- }
590
- if (newPass.length < 20) {
591
- await randomDelay()
592
- throw new CodeError(`Invalid pass length ${newPass.length}`, codes.ERR_INVALID_PASS_LENGTH)
593
- }
594
- log('recreating keychain')
595
- const cached = privates.get(this)
596
-
597
- if (cached == null) {
598
- throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS)
599
- }
600
-
601
- const oldDek = cached.dek
602
- this.init.pass = newPass
603
- const newDek = newPass != null && this.init.dek?.salt != null
604
- ? pbkdf2(
605
- newPass,
606
- this.init.dek.salt,
607
- this.init.dek?.iterationCount,
608
- this.init.dek?.keyLength,
609
- this.init.dek?.hash)
610
- : ''
611
- privates.set(this, { dek: newDek })
612
- const keys = await this.listKeys()
613
- for (const key of keys) {
614
- const res = await this.components.datastore.get(DsName(key.name))
615
- const pem = uint8ArrayToString(res)
616
- const privateKey = await importKey(pem, oldDek)
617
- const password = newDek.toString()
618
- const keyAsPEM = await privateKey.export(password)
216
+ rotateKeychainPass(oldPass: string, newPass: string): Promise<void>
217
+ }
619
218
 
620
- // Update stored key
621
- const batch = this.components.datastore.batch()
622
- const keyInfo = {
623
- name: key.name,
624
- id: key.id
625
- }
626
- batch.put(DsName(key.name), uint8ArrayFromString(keyAsPEM))
627
- batch.put(DsInfoName(key.name), uint8ArrayFromString(JSON.stringify(keyInfo)))
628
- await batch.commit()
629
- }
630
- log('keychain reconstructed')
219
+ export function keychain (init: KeychainInit = {}): (components: KeychainComponents) => Keychain {
220
+ return (components: KeychainComponents) => {
221
+ return new DefaultKeychain(components, init)
631
222
  }
632
223
  }