@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/README.md +5 -5
- package/dist/index.min.js +11 -11
- package/dist/src/index.d.ts +99 -74
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -494
- package/dist/src/index.js.map +1 -1
- package/dist/src/keychain.d.ts +112 -0
- package/dist/src/keychain.d.ts.map +1 -0
- package/dist/src/keychain.js +495 -0
- package/dist/src/keychain.js.map +1 -0
- package/package.json +8 -8
- package/src/index.ts +105 -514
- package/src/keychain.ts +563 -0
- package/dist/typedoc-urls.json +0 -10
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
|
|
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
|
-
|
|
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
|
|
67
|
+
export interface KeychainInit {
|
|
81
68
|
pass?: string
|
|
82
69
|
dek?: DEKConfig
|
|
83
70
|
}
|
|
84
71
|
|
|
85
|
-
|
|
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
|
-
*
|
|
79
|
+
* The universally unique key id
|
|
162
80
|
*/
|
|
163
|
-
|
|
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
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* @returns {object}
|
|
84
|
+
* The local key name
|
|
197
85
|
*/
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
*
|
|
207
|
-
* The default options for a keychain.
|
|
91
|
+
* Export an existing key as a PEM encrypted PKCS #8 string.
|
|
208
92
|
*
|
|
209
|
-
* @
|
|
210
|
-
*/
|
|
211
|
-
static get options (): typeof defaultOptions {
|
|
212
|
-
return defaultOptions
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Create a new key.
|
|
93
|
+
* @example
|
|
217
94
|
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
95
|
+
* ```js
|
|
96
|
+
* await libp2p.keychain.createKey('keyTest', 'RSA', 4096)
|
|
97
|
+
* const pemKey = await libp2p.keychain.exportKey('keyTest', 'password123')
|
|
98
|
+
* ```
|
|
221
99
|
*/
|
|
222
|
-
|
|
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
|
-
*
|
|
103
|
+
* Import a new key from a PEM encoded PKCS #8 string.
|
|
282
104
|
*
|
|
283
|
-
* @
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
116
|
+
* Import a new key from a PeerId with a private key component
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
319
119
|
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
120
|
+
* ```js
|
|
121
|
+
* const keyInfo = await libp2p.keychain.importPeer('keyTestImport', peerIdFromString('12D3Foo...'))
|
|
122
|
+
* ```
|
|
322
123
|
*/
|
|
323
|
-
|
|
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
|
-
*
|
|
127
|
+
* Export an existing key as a PeerId
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
342
130
|
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
131
|
+
* ```js
|
|
132
|
+
* const peerId = await libp2p.keychain.exportPeerId('key-name')
|
|
133
|
+
* ```
|
|
345
134
|
*/
|
|
346
|
-
|
|
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
|
-
*
|
|
138
|
+
* Create a key in the keychain.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
362
141
|
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
142
|
+
* ```js
|
|
143
|
+
* const keyInfo = await libp2p.keychain.createKey('keyTest', 'RSA', 4096)
|
|
144
|
+
* ```
|
|
366
145
|
*/
|
|
367
|
-
|
|
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
|
-
*
|
|
149
|
+
* List all the keys.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
*
|
|
153
|
+
* ```js
|
|
154
|
+
* const keyInfos = await libp2p.keychain.listKeys()
|
|
155
|
+
* ```
|
|
408
156
|
*/
|
|
409
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
172
|
+
* Rename a key in the keychain.
|
|
453
173
|
*
|
|
454
|
-
* @
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
582
|
-
|
|
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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
}
|