diff --git a/doc/API.md b/doc/API.md index 908fff23..14fddbce 100644 --- a/doc/API.md +++ b/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. diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index df5e689a..fb550561 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -494,8 +494,6 @@ const node = await createLibp2p({ datastore: dsInstant, } }) - -await node.loadKeychain() ``` #### Configuring Dialing diff --git a/src/index.ts b/src/index.ts index d64bb224..e76b7939 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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 { pubsub?: PubSub dht?: DualDHT - /** - * Load keychain keys from the datastore. - * Imports the private key as 'self', if needed. - */ - loadKeychain: () => Promise - /** * Get a deduplicated list of peer advertising multiaddrs by concatenating * the listen addresses used by transports with any configured diff --git a/src/keychain/cms.ts b/src/keychain/cms.ts index 8a26c338..51da640b 100644 --- a/src/keychain/cms.ts +++ b/src/keychain/cms.ts @@ -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() * 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) } diff --git a/src/keychain/index.ts b/src/keychain/index.ts index b2b99126..8c58819a 100644 --- a/src/keychain/index.ts +++ b/src/keychain/index.ts @@ -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 } /** diff --git a/src/libp2p.ts b/src/libp2p.ts index 62f412bf..ade4602e 100644 --- a/src/libp2p.ts +++ b/src/libp2p.ts @@ -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 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 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 } diff --git a/test/keychain/cms-interop.spec.ts b/test/keychain/cms-interop.spec.ts index 5a3494c7..716cd960 100644 --- a/test/keychain/cms-interop.spec.ts +++ b/test/keychain/cms-interop.spec.ts @@ -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') diff --git a/test/keychain/keychain.spec.ts b/test/keychain/keychain.spec.ts index fc7feb79..5fefa065 100644 --- a/test/keychain/keychain.spec.ts +++ b/test/keychain/keychain.spec.ts @@ -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()