@libp2p/keychain 5.1.4-b2124c2db → 5.1.4-d53ef170c
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/dist/index.min.js +3 -3
- package/dist/src/index.d.ts +28 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/keychain.d.ts +8 -1
- package/dist/src/keychain.d.ts.map +1 -1
- package/dist/src/keychain.js +103 -21
- package/dist/src/keychain.js.map +1 -1
- package/dist/src/utils/export.d.ts +5 -1
- package/dist/src/utils/export.d.ts.map +1 -1
- package/dist/src/utils/export.js +14 -0
- package/dist/src/utils/export.js.map +1 -1
- package/package.json +5 -4
- package/src/index.ts +33 -0
- package/src/keychain.ts +125 -22
- package/src/utils/export.ts +16 -1
package/src/keychain.ts
CHANGED
|
@@ -10,12 +10,13 @@ import { sha256 } from 'multiformats/hashes/sha2'
|
|
|
10
10
|
import sanitize from 'sanitize-filename'
|
|
11
11
|
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
12
12
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
13
|
-
import { exportPrivateKey } from './utils/export.js'
|
|
14
|
-
import { importPrivateKey } from './utils/import.js'
|
|
15
|
-
import type { KeychainComponents, KeychainInit, Keychain as KeychainInterface, KeyInfo } from './index.js'
|
|
13
|
+
import { exportPrivateKey, exporter } from './utils/export.js'
|
|
14
|
+
import { importPrivateKey, importer } from './utils/import.js'
|
|
15
|
+
import type { KeychainComponents, KeychainInit, Keychain as KeychainInterface, KeyInfo, X509Info } from './index.js'
|
|
16
16
|
import type { Logger, PrivateKey } from '@libp2p/interface'
|
|
17
17
|
|
|
18
18
|
const keyPrefix = '/pkcs8/'
|
|
19
|
+
const certPrefix = '/x509/'
|
|
19
20
|
const infoPrefix = '/info/'
|
|
20
21
|
const privates = new WeakMap<object, { dek: string }>()
|
|
21
22
|
|
|
@@ -63,8 +64,8 @@ async function randomDelay (): Promise<void> {
|
|
|
63
64
|
/**
|
|
64
65
|
* Converts a key name into a datastore name
|
|
65
66
|
*/
|
|
66
|
-
function DsName (name: string): Key {
|
|
67
|
-
return new Key(
|
|
67
|
+
function DsName (prefix: string, name: string): Key {
|
|
68
|
+
return new Key(prefix + name)
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/**
|
|
@@ -206,7 +207,7 @@ export class Keychain implements KeychainInterface {
|
|
|
206
207
|
await randomDelay()
|
|
207
208
|
throw new InvalidParametersError('Key is required')
|
|
208
209
|
}
|
|
209
|
-
const datastoreName = DsName(name)
|
|
210
|
+
const datastoreName = DsName(keyPrefix, name)
|
|
210
211
|
const exists = await this.components.datastore.has(datastoreName)
|
|
211
212
|
if (exists) {
|
|
212
213
|
await randomDelay()
|
|
@@ -248,7 +249,7 @@ export class Keychain implements KeychainInterface {
|
|
|
248
249
|
throw new InvalidParametersError(`Invalid key name '${name}'`)
|
|
249
250
|
}
|
|
250
251
|
|
|
251
|
-
const datastoreName = DsName(name)
|
|
252
|
+
const datastoreName = DsName(keyPrefix, name)
|
|
252
253
|
try {
|
|
253
254
|
const res = await this.components.datastore.get(datastoreName)
|
|
254
255
|
const pem = uint8ArrayToString(res)
|
|
@@ -273,7 +274,7 @@ export class Keychain implements KeychainInterface {
|
|
|
273
274
|
throw new InvalidParametersError(`Invalid key name '${name}'`)
|
|
274
275
|
}
|
|
275
276
|
|
|
276
|
-
const datastoreName = DsName(name)
|
|
277
|
+
const datastoreName = DsName(keyPrefix, name)
|
|
277
278
|
const keyInfo = await this.findKeyByName(name)
|
|
278
279
|
const batch = this.components.datastore.batch()
|
|
279
280
|
batch.delete(datastoreName)
|
|
@@ -317,8 +318,8 @@ export class Keychain implements KeychainInterface {
|
|
|
317
318
|
await randomDelay()
|
|
318
319
|
throw new InvalidParametersError(`Invalid new key name '${newName}'`)
|
|
319
320
|
}
|
|
320
|
-
const oldDatastoreName = DsName(oldName)
|
|
321
|
-
const newDatastoreName = DsName(newName)
|
|
321
|
+
const oldDatastoreName = DsName(keyPrefix, oldName)
|
|
322
|
+
const newDatastoreName = DsName(keyPrefix, newName)
|
|
322
323
|
const oldInfoName = DsInfoName(oldName)
|
|
323
324
|
const newInfoName = DsInfoName(newName)
|
|
324
325
|
|
|
@@ -347,6 +348,99 @@ export class Keychain implements KeychainInterface {
|
|
|
347
348
|
}
|
|
348
349
|
}
|
|
349
350
|
|
|
351
|
+
/**
|
|
352
|
+
* List all the certificates
|
|
353
|
+
*/
|
|
354
|
+
async listX509 (): Promise<X509Info[]> {
|
|
355
|
+
const query = {
|
|
356
|
+
prefix: certPrefix
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const info = []
|
|
360
|
+
for await (const value of this.components.datastore.query(query)) {
|
|
361
|
+
info.push({
|
|
362
|
+
name: value.key.toString().replace(certPrefix, '')
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return info
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async importX509 (name: string, pem: string): Promise<void> {
|
|
370
|
+
try {
|
|
371
|
+
if (!validateKeyName(name)) {
|
|
372
|
+
throw new InvalidParametersError(`Invalid certificate name '${name}'`)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (pem == null) {
|
|
376
|
+
throw new InvalidParametersError('PEM is required')
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (!pem.includes('-----BEGIN CERTIFICATE-----') && !pem.includes('-----END CERTIFICATE-----')) {
|
|
380
|
+
throw new InvalidParametersError('PEM was invalid')
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const datastoreName = DsName(certPrefix, name)
|
|
384
|
+
|
|
385
|
+
const exists = await this.components.datastore.has(datastoreName)
|
|
386
|
+
if (exists) {
|
|
387
|
+
throw new InvalidParametersError(`Certificate '${name}' already exists`)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const cached = privates.get(this)
|
|
391
|
+
|
|
392
|
+
if (cached == null) {
|
|
393
|
+
throw new InvalidParametersError('dek missing')
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const dek = cached.dek
|
|
397
|
+
const dsPem = await exporter(uint8ArrayFromString(pem), dek)
|
|
398
|
+
await this.components.datastore.put(datastoreName, uint8ArrayFromString(dsPem))
|
|
399
|
+
} catch (err) {
|
|
400
|
+
await randomDelay()
|
|
401
|
+
throw err
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async exportX509 (name: string): Promise<string> {
|
|
406
|
+
try {
|
|
407
|
+
if (!validateKeyName(name)) {
|
|
408
|
+
throw new InvalidParametersError(`Invalid key name '${name}'`)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const datastoreName = DsName(certPrefix, name)
|
|
412
|
+
const res = await this.components.datastore.get(datastoreName)
|
|
413
|
+
const encryptedPem = uint8ArrayToString(res)
|
|
414
|
+
const cached = privates.get(this)
|
|
415
|
+
|
|
416
|
+
if (cached == null) {
|
|
417
|
+
throw new InvalidParametersError('dek missing')
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const dek = cached.dek
|
|
421
|
+
const buf = await importer(encryptedPem, dek)
|
|
422
|
+
|
|
423
|
+
return uint8ArrayToString(buf)
|
|
424
|
+
} catch (err: any) {
|
|
425
|
+
await randomDelay()
|
|
426
|
+
throw err
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async removeX509 (name: string): Promise<void> {
|
|
431
|
+
try {
|
|
432
|
+
if (!validateKeyName(name) || name === this.self) {
|
|
433
|
+
throw new InvalidParametersError(`Invalid key name '${name}'`)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const datastoreName = DsName(certPrefix, name)
|
|
437
|
+
await this.components.datastore.delete(datastoreName)
|
|
438
|
+
} catch (err) {
|
|
439
|
+
await randomDelay()
|
|
440
|
+
throw err
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
350
444
|
/**
|
|
351
445
|
* Rotate keychain password and re-encrypt all associated keys
|
|
352
446
|
*/
|
|
@@ -381,24 +475,33 @@ export class Keychain implements KeychainInterface {
|
|
|
381
475
|
this.init.dek?.hash)
|
|
382
476
|
: ''
|
|
383
477
|
privates.set(this, { dek: newDek })
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
478
|
+
|
|
479
|
+
const batch = this.components.datastore.batch()
|
|
480
|
+
|
|
481
|
+
for (const key of await this.listKeys()) {
|
|
482
|
+
const res = await this.components.datastore.get(DsName(keyPrefix, key.name))
|
|
387
483
|
const pem = uint8ArrayToString(res)
|
|
388
484
|
const privateKey = await importPrivateKey(pem, oldDek)
|
|
389
485
|
const password = newDek.toString()
|
|
390
486
|
const keyAsPEM = await exportPrivateKey(privateKey, password, privateKey.type === 'RSA' ? 'pkcs-8' : 'libp2p-key')
|
|
391
487
|
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
const keyInfo = {
|
|
395
|
-
name: key.name,
|
|
396
|
-
id: key.id
|
|
397
|
-
}
|
|
398
|
-
batch.put(DsName(key.name), uint8ArrayFromString(keyAsPEM))
|
|
399
|
-
batch.put(DsInfoName(key.name), uint8ArrayFromString(JSON.stringify(keyInfo)))
|
|
400
|
-
await batch.commit()
|
|
488
|
+
// add to batch
|
|
489
|
+
batch.put(DsName(keyPrefix, key.name), uint8ArrayFromString(keyAsPEM))
|
|
401
490
|
}
|
|
491
|
+
|
|
492
|
+
for (const key of await this.listX509()) {
|
|
493
|
+
// decrypt using old password and encrypt using new
|
|
494
|
+
const res = await this.components.datastore.get(DsName(certPrefix, key.name))
|
|
495
|
+
const pem = uint8ArrayToString(res)
|
|
496
|
+
const decrypted = await importer(pem, oldDek)
|
|
497
|
+
const encrypted = await exporter(decrypted, newDek)
|
|
498
|
+
|
|
499
|
+
// add to batch
|
|
500
|
+
batch.put(DsName(certPrefix, key.name), uint8ArrayFromString(encrypted))
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
await batch.commit()
|
|
504
|
+
|
|
402
505
|
this.log('keychain reconstructed')
|
|
403
506
|
}
|
|
404
507
|
}
|
package/src/utils/export.ts
CHANGED
|
@@ -9,7 +9,7 @@ import * as asn1js from 'asn1js'
|
|
|
9
9
|
import { base64 } from 'multiformats/bases/base64'
|
|
10
10
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
11
11
|
import { ITERATIONS, KEY_SIZE, SALT_LENGTH } from './constants.js'
|
|
12
|
-
import type { Ed25519PrivateKey, PrivateKey, RSAPrivateKey, Secp256k1PrivateKey } from '@libp2p/interface'
|
|
12
|
+
import type { ECDSAPrivateKey, Ed25519PrivateKey, PrivateKey, RSAPrivateKey, Secp256k1PrivateKey } from '@libp2p/interface'
|
|
13
13
|
import type { Multibase } from 'multiformats/bases/interface'
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -43,6 +43,10 @@ export async function exportPrivateKey (key: PrivateKey, password: string, forma
|
|
|
43
43
|
return exportSecp256k1PrivateKey(key, password, format)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
if (key.type === 'ECDSA') {
|
|
47
|
+
return exportECDSAPrivateKey(key, password, format)
|
|
48
|
+
}
|
|
49
|
+
|
|
46
50
|
throw new UnsupportedKeyTypeError()
|
|
47
51
|
}
|
|
48
52
|
|
|
@@ -68,6 +72,17 @@ export async function exportSecp256k1PrivateKey (key: Secp256k1PrivateKey, passw
|
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Exports the key into a password protected `format`
|
|
77
|
+
*/
|
|
78
|
+
export async function exportECDSAPrivateKey (key: ECDSAPrivateKey, password: string, format: ExportFormat = 'libp2p-key'): Promise<Multibase<'m'>> {
|
|
79
|
+
if (format === 'libp2p-key') {
|
|
80
|
+
return exporter(privateKeyToProtobuf(key), password)
|
|
81
|
+
} else {
|
|
82
|
+
throw new InvalidParametersError(`export format '${format}' is not supported`)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
71
86
|
/**
|
|
72
87
|
* Exports the key as libp2p-key - a aes-gcm encrypted value with the key
|
|
73
88
|
* derived from the password.
|