mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-04-25 10:32:14 +00:00
fix: load keychain on startup
Renames KeyChain to DefaultKeyChain and makes it implement the Startable interface. Loads the current peer id into the keychain on startup instead of the user having to do it manually.
This commit is contained in:
parent
1b9bab68ed
commit
82330ac4e6
30
doc/API.md
30
doc/API.md
@ -181,36 +181,6 @@ Required keys in the `options` object:
|
||||
|
||||
## Libp2p Instance Methods
|
||||
|
||||
### loadKeychain
|
||||
|
||||
Load keychain keys from the datastore, importing the private key as 'self', if needed.
|
||||
|
||||
`libp2p.loadKeychain()`
|
||||
|
||||
#### Returns
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `Promise` | Promise resolves when the keychain is ready |
|
||||
|
||||
#### Example
|
||||
|
||||
```js
|
||||
import { createLibp2p } from 'libp2p'
|
||||
|
||||
// ...
|
||||
|
||||
const libp2p = await createLibp2p({
|
||||
// ...
|
||||
keychain: {
|
||||
pass: '0123456789pass1234567890'
|
||||
}
|
||||
})
|
||||
|
||||
// load keychain
|
||||
await libp2p.loadKeychain()
|
||||
```
|
||||
|
||||
### start
|
||||
|
||||
Starts the libp2p node.
|
||||
|
@ -494,8 +494,6 @@ const node = await createLibp2p({
|
||||
datastore: dsInstant,
|
||||
}
|
||||
})
|
||||
|
||||
await node.loadKeychain()
|
||||
```
|
||||
|
||||
#### Configuring Dialing
|
||||
|
@ -20,7 +20,7 @@ import type { ConnectionManager, Registrar, StreamHandler } from '@libp2p/interf
|
||||
import type { Metrics, MetricsInit } from '@libp2p/interfaces/metrics'
|
||||
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
|
||||
import type { DialerInit } from '@libp2p/interfaces/dialer'
|
||||
import type { KeyChain } from './keychain/index.js'
|
||||
import type { KeyChain } from '@libp2p/interfaces/keychain'
|
||||
|
||||
export interface PersistentPeerStoreOptions {
|
||||
threshold?: number
|
||||
@ -158,12 +158,6 @@ export interface Libp2p extends Startable, EventEmitter<Libp2pEvents> {
|
||||
pubsub?: PubSub
|
||||
dht?: DualDHT
|
||||
|
||||
/**
|
||||
* Load keychain keys from the datastore.
|
||||
* Imports the private key as 'self', if needed.
|
||||
*/
|
||||
loadKeychain: () => Promise<void>
|
||||
|
||||
/**
|
||||
* Get a deduplicated list of peer advertising multiaddrs by concatenating
|
||||
* the listen addresses used by transports with any configured
|
||||
|
@ -8,7 +8,7 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||
import { codes } from '../errors.js'
|
||||
import { logger } from '@libp2p/logger'
|
||||
import type { KeyChain } from './index.js'
|
||||
import type { DefaultKeyChain } from './index.js'
|
||||
|
||||
const log = logger('libp2p:keychain:cms')
|
||||
|
||||
@ -24,12 +24,12 @@ const privates = new WeakMap<object, { dek: string }>()
|
||||
* See RFC 5652 for all the details.
|
||||
*/
|
||||
export class CMS {
|
||||
private readonly keychain: KeyChain
|
||||
private readonly keychain: DefaultKeyChain
|
||||
|
||||
/**
|
||||
* Creates a new instance with a keychain
|
||||
*/
|
||||
constructor (keychain: KeyChain, dek: string) {
|
||||
constructor (keychain: DefaultKeyChain, dek: string) {
|
||||
if (keychain == null) {
|
||||
throw errCode(new Error('keychain is required'), codes.ERR_KEYCHAIN_REQUIRED)
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ import { generateKeyPair, importKey, unmarshalPrivateKey } from '@libp2p/crypto/
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
import { pbkdf2, randomBytes } from '@libp2p/crypto'
|
||||
import type { KeyChain } from '@libp2p/interfaces/keychain'
|
||||
import type { Startable } from '@libp2p/interfaces'
|
||||
|
||||
const log = logger('libp2p:keychain')
|
||||
|
||||
@ -111,9 +113,10 @@ function DsInfoName (name: string) {
|
||||
* - '/pkcs8/*key-name*', contains the PKCS #8 for the key
|
||||
*
|
||||
*/
|
||||
export class KeyChain {
|
||||
export class DefaultKeyChain implements KeyChain, Startable {
|
||||
private readonly components: Components
|
||||
private init: KeyChainInit
|
||||
private started: boolean
|
||||
|
||||
/**
|
||||
* Creates a new instance of a key chain
|
||||
@ -146,6 +149,27 @@ export class KeyChain {
|
||||
: ''
|
||||
|
||||
privates.set(this, { dek })
|
||||
this.started = false
|
||||
}
|
||||
|
||||
isStarted () {
|
||||
return this.started
|
||||
}
|
||||
|
||||
async start () {
|
||||
// Load keychain keys from the datastore.
|
||||
// Imports the private key as 'self', if needed.
|
||||
try {
|
||||
await this.findKeyByName('self')
|
||||
} catch (err: any) {
|
||||
await this.importPeer('self', this.components.getPeerId())
|
||||
}
|
||||
|
||||
this.started = true
|
||||
}
|
||||
|
||||
async stop () {
|
||||
this.started = false
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,7 +12,7 @@ import { AutoDialler } from './connection-manager/auto-dialler.js'
|
||||
import { Circuit } from './circuit/transport.js'
|
||||
import { Relay } from './circuit/index.js'
|
||||
import { DefaultDialer } from './dialer/index.js'
|
||||
import { KeyChain } from './keychain/index.js'
|
||||
import { DefaultKeyChain } from './keychain/index.js'
|
||||
import { DefaultMetrics } from './metrics/index.js'
|
||||
import { DefaultTransportManager } from './transport-manager.js'
|
||||
import { DefaultUpgrader } from './upgrader.js'
|
||||
@ -44,6 +44,7 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||
import errCode from 'err-code'
|
||||
import { unmarshalPublicKey } from '@libp2p/crypto/keys'
|
||||
import type { Metrics } from '@libp2p/interfaces/metrics'
|
||||
import type { KeyChain } from '@libp2p/interfaces/keychain'
|
||||
|
||||
const log = logger('libp2p')
|
||||
|
||||
@ -140,8 +141,8 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
||||
}))
|
||||
|
||||
// Create keychain
|
||||
const keychainOpts = KeyChain.generateOptions()
|
||||
this.keychain = this.configureComponent(new KeyChain(this.components, {
|
||||
const keychainOpts = DefaultKeyChain.generateOptions()
|
||||
this.keychain = this.configureComponent(new DefaultKeyChain(this.components, {
|
||||
...keychainOpts,
|
||||
...init.keychain
|
||||
}))
|
||||
@ -352,22 +353,6 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
||||
log('libp2p has stopped')
|
||||
}
|
||||
|
||||
/**
|
||||
* Load keychain keys from the datastore.
|
||||
* Imports the private key as 'self', if needed.
|
||||
*/
|
||||
async loadKeychain () {
|
||||
if (this.keychain == null) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await this.keychain.findKeyByName('self')
|
||||
} catch (err: any) {
|
||||
await this.keychain.importPeer('self', this.peerId)
|
||||
}
|
||||
}
|
||||
|
||||
isStarted () {
|
||||
return this.started
|
||||
}
|
||||
|
@ -5,17 +5,17 @@ import { expect } from 'aegir/chai'
|
||||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||
import { MemoryDatastore } from 'datastore-core/memory'
|
||||
import { KeyChain } from '../../src/keychain/index.js'
|
||||
import { DefaultKeyChain } from '../../src/keychain/index.js'
|
||||
import { Components } from '@libp2p/interfaces/components'
|
||||
|
||||
describe('cms interop', () => {
|
||||
const passPhrase = 'this is not a secure phrase'
|
||||
const aliceKeyName = 'cms-interop-alice'
|
||||
let ks: KeyChain
|
||||
let ks: DefaultKeyChain
|
||||
|
||||
before(() => {
|
||||
const datastore = new MemoryDatastore()
|
||||
ks = new KeyChain(new Components({ datastore }), { pass: passPhrase })
|
||||
ks = new DefaultKeyChain(new Components({ datastore }), { pass: passPhrase })
|
||||
})
|
||||
|
||||
const plainData = uint8ArrayFromString('This is a message from Alice to Bob')
|
||||
|
@ -7,7 +7,7 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||
import { createNode } from '../utils/creators/peer.js'
|
||||
import { Key } from 'interface-datastore/key'
|
||||
import { MemoryDatastore } from 'datastore-core/memory'
|
||||
import { KeyChain, KeyChainInit, KeyInfo } from '../../src/keychain/index.js'
|
||||
import { DefaultKeyChain, KeyChainInit, KeyInfo } from '../../src/keychain/index.js'
|
||||
import { pbkdf2 } from '@libp2p/crypto'
|
||||
import { Components } from '@libp2p/interfaces/components'
|
||||
import type { Datastore } from 'interface-datastore'
|
||||
@ -20,16 +20,16 @@ describe('keychain', () => {
|
||||
const rsaKeyName = 'tajné jméno'
|
||||
const renamedRsaKeyName = 'ชื่อลับ'
|
||||
let rsaKeyInfo: KeyInfo
|
||||
let emptyKeystore: KeyChain
|
||||
let ks: KeyChain
|
||||
let emptyKeystore: DefaultKeyChain
|
||||
let ks: DefaultKeyChain
|
||||
let datastore1: Datastore, datastore2: Datastore
|
||||
|
||||
before(async () => {
|
||||
datastore1 = new MemoryDatastore()
|
||||
datastore2 = new MemoryDatastore()
|
||||
|
||||
ks = new KeyChain(new Components({ datastore: datastore2 }), { pass: passPhrase })
|
||||
emptyKeystore = new KeyChain(new Components({ datastore: datastore1 }), { pass: passPhrase })
|
||||
ks = new DefaultKeyChain(new Components({ datastore: datastore2 }), { pass: passPhrase })
|
||||
emptyKeystore = new DefaultKeyChain(new Components({ datastore: datastore1 }), { pass: passPhrase })
|
||||
|
||||
await datastore1.open()
|
||||
await datastore2.open()
|
||||
@ -41,35 +41,35 @@ describe('keychain', () => {
|
||||
})
|
||||
|
||||
it('can start without a password', () => {
|
||||
expect(() => new KeyChain(new Components({ datastore: datastore2 }), {})).to.not.throw()
|
||||
expect(() => new DefaultKeyChain(new Components({ datastore: datastore2 }), {})).to.not.throw()
|
||||
})
|
||||
|
||||
it('needs a NIST SP 800-132 non-weak pass phrase', () => {
|
||||
expect(() => new KeyChain(new Components({ datastore: datastore2 }), { pass: '< 20 character' })).to.throw()
|
||||
expect(() => new DefaultKeyChain(new Components({ datastore: datastore2 }), { pass: '< 20 character' })).to.throw()
|
||||
})
|
||||
|
||||
it('has default options', () => {
|
||||
expect(KeyChain.options).to.exist()
|
||||
expect(DefaultKeyChain.options).to.exist()
|
||||
})
|
||||
|
||||
it('supports supported hashing alorithms', () => {
|
||||
const ok = new KeyChain(new Components({ datastore: datastore2 }), { pass: passPhrase, dek: { hash: 'sha2-256', salt: 'salt-salt-salt-salt', iterationCount: 1000, keyLength: 14 } })
|
||||
const ok = new DefaultKeyChain(new Components({ datastore: datastore2 }), { pass: passPhrase, dek: { hash: 'sha2-256', salt: 'salt-salt-salt-salt', iterationCount: 1000, keyLength: 14 } })
|
||||
expect(ok).to.exist()
|
||||
})
|
||||
|
||||
it('does not support unsupported hashing alorithms', () => {
|
||||
expect(() => new KeyChain(new Components({ datastore: datastore2 }), { pass: passPhrase, dek: { hash: 'my-hash', salt: 'salt-salt-salt-salt', iterationCount: 1000, keyLength: 14 } })).to.throw()
|
||||
expect(() => new DefaultKeyChain(new Components({ datastore: datastore2 }), { pass: passPhrase, dek: { hash: 'my-hash', salt: 'salt-salt-salt-salt', iterationCount: 1000, keyLength: 14 } })).to.throw()
|
||||
})
|
||||
|
||||
it('can list keys without a password', async () => {
|
||||
const keychain = new KeyChain(new Components({ datastore: datastore2 }), {})
|
||||
const keychain = new DefaultKeyChain(new Components({ datastore: datastore2 }), {})
|
||||
|
||||
expect(await keychain.listKeys()).to.have.lengthOf(0)
|
||||
})
|
||||
|
||||
it('can find a key without a password', async () => {
|
||||
const keychain = new KeyChain(new Components({ datastore: datastore2 }), {})
|
||||
const keychainWithPassword = new KeyChain(new Components({ datastore: datastore2 }), { pass: `hello-${Date.now()}-${Date.now()}` })
|
||||
const keychain = new DefaultKeyChain(new Components({ datastore: datastore2 }), {})
|
||||
const keychainWithPassword = new DefaultKeyChain(new Components({ datastore: datastore2 }), { pass: `hello-${Date.now()}-${Date.now()}` })
|
||||
const name = `key-${Math.random()}`
|
||||
|
||||
const { id } = await keychainWithPassword.createKey(name, 'Ed25519')
|
||||
@ -78,8 +78,8 @@ describe('keychain', () => {
|
||||
})
|
||||
|
||||
it('can remove a key without a password', async () => {
|
||||
const keychainWithoutPassword = new KeyChain(new Components({ datastore: datastore2 }), {})
|
||||
const keychainWithPassword = new KeyChain(new Components({ datastore: datastore2 }), { pass: `hello-${Date.now()}-${Date.now()}` })
|
||||
const keychainWithoutPassword = new DefaultKeyChain(new Components({ datastore: datastore2 }), {})
|
||||
const keychainWithPassword = new DefaultKeyChain(new Components({ datastore: datastore2 }), { pass: `hello-${Date.now()}-${Date.now()}` })
|
||||
const name = `key-${Math.random()}`
|
||||
|
||||
expect(await keychainWithPassword.createKey(name, 'Ed25519')).to.have.property('name', name)
|
||||
@ -89,16 +89,16 @@ describe('keychain', () => {
|
||||
})
|
||||
|
||||
it('requires a name to create a password', async () => {
|
||||
const keychain = new KeyChain(new Components({ datastore: datastore2 }), {})
|
||||
const keychain = new DefaultKeyChain(new Components({ datastore: datastore2 }), {})
|
||||
|
||||
// @ts-expect-error invalid parameters
|
||||
await expect(keychain.createKey(undefined, 'derp')).to.be.rejected()
|
||||
})
|
||||
|
||||
it('can generate options', () => {
|
||||
const options = KeyChain.generateOptions()
|
||||
const options = DefaultKeyChain.generateOptions()
|
||||
options.pass = passPhrase
|
||||
const chain = new KeyChain(new Components({ datastore: datastore2 }), options)
|
||||
const chain = new DefaultKeyChain(new Components({ datastore: datastore2 }), options)
|
||||
expect(chain).to.exist()
|
||||
})
|
||||
|
||||
@ -418,7 +418,7 @@ describe('keychain', () => {
|
||||
|
||||
describe('rotate keychain passphrase', () => {
|
||||
let oldPass: string
|
||||
let kc: KeyChain
|
||||
let kc: DefaultKeyChain
|
||||
let options: KeyChainInit
|
||||
let ds: Datastore
|
||||
before(async () => {
|
||||
@ -433,7 +433,7 @@ describe('keychain', () => {
|
||||
hash: 'sha2-512'
|
||||
}
|
||||
}
|
||||
kc = new KeyChain(new Components({ datastore: ds }), options)
|
||||
kc = new DefaultKeyChain(new Components({ datastore: ds }), options)
|
||||
await ds.open()
|
||||
})
|
||||
|
||||
@ -512,8 +512,6 @@ describe('libp2p.keychain', () => {
|
||||
}
|
||||
})
|
||||
|
||||
await libp2p.loadKeychain()
|
||||
|
||||
const kInfo = await libp2p.keychain.createKey('keyName', 'Ed25519')
|
||||
expect(kInfo).to.exist()
|
||||
})
|
||||
@ -526,8 +524,6 @@ describe('libp2p.keychain', () => {
|
||||
}
|
||||
})
|
||||
|
||||
await libp2p.loadKeychain()
|
||||
|
||||
const kInfo = await libp2p.keychain.createKey('keyName', 'Ed25519')
|
||||
expect(kInfo).to.exist()
|
||||
})
|
||||
@ -543,7 +539,6 @@ describe('libp2p.keychain', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
await libp2p.loadKeychain()
|
||||
|
||||
const kInfo = await libp2p.keychain.createKey('keyName', 'Ed25519')
|
||||
expect(kInfo).to.exist()
|
||||
@ -558,7 +553,6 @@ describe('libp2p.keychain', () => {
|
||||
}
|
||||
})
|
||||
|
||||
await libp2p2.loadKeychain()
|
||||
const key = await libp2p2.keychain.findKeyByName('keyName')
|
||||
|
||||
expect(key).to.exist()
|
||||
|
Loading…
x
Reference in New Issue
Block a user