Skip to content

Commit 8c56ec0

Browse files
fix: allow certain keychain operations without a password (#726)
* fix: allow certain keychain operations without a password Listing, removing, renaming etc keys do not require a password so the user should not be required to provide one. This means we don't have to prompt the user to create a password when they aren't going to do any operations that require a password. * fix: make keychain pass optional * fix: support libp2p creation without keychain pass Co-authored-by: Jacob Heun <jacobheun@gmail.com>
1 parent fa5ee87 commit 8c56ec0

File tree

3 files changed

+62
-12
lines changed

3 files changed

+62
-12
lines changed

src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class Libp2p extends EventEmitter {
8282
}
8383

8484
// Create keychain
85-
if (this._options.keychain && this._options.keychain.pass && this._options.keychain.datastore) {
85+
if (this._options.keychain && this._options.keychain.datastore) {
8686
log('creating keychain')
8787

8888
const keychainOpts = Keychain.generateOptions()

src/keychain/index.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class Keychain {
110110
this.opts = mergeOptions(defaultOptions, options)
111111

112112
// Enforce NIST SP 800-132
113-
if (!this.opts.passPhrase || this.opts.passPhrase.length < 20) {
113+
if (this.opts.passPhrase && this.opts.passPhrase.length < 20) {
114114
throw new Error('passPhrase must be least 20 characters')
115115
}
116116
if (this.opts.dek.keyLength < NIST.minKeyLength) {
@@ -123,13 +123,13 @@ class Keychain {
123123
throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`)
124124
}
125125

126-
// Create the derived encrypting key
127-
const dek = crypto.pbkdf2(
126+
const dek = this.opts.passPhrase ? crypto.pbkdf2(
128127
this.opts.passPhrase,
129128
this.opts.dek.salt,
130129
this.opts.dek.iterationCount,
131130
this.opts.dek.keyLength,
132-
this.opts.dek.hash)
131+
this.opts.dek.hash) : ''
132+
133133
Object.defineProperty(this, '_', { value: () => dek })
134134
}
135135

test/keychain/keychain.spec.js

+57-7
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
/* eslint-env mocha */
33
'use strict'
44

5-
const chai = require('chai')
6-
const { expect } = chai
5+
const { chai, expect } = require('aegir/utils/chai')
76
const fail = expect.fail
8-
chai.use(require('dirty-chai'))
97
chai.use(require('chai-string'))
108

119
const peerUtils = require('../utils/creators/peer')
@@ -40,8 +38,8 @@ describe('keychain', () => {
4038
emptyKeystore = new Keychain(datastore1, { passPhrase: passPhrase })
4139
})
4240

43-
it('needs a pass phrase to encrypt a key', () => {
44-
expect(() => new Keychain(datastore2)).to.throw()
41+
it('can start without a password', () => {
42+
expect(() => new Keychain(datastore2)).to.not.throw()
4543
})
4644

4745
it('needs a NIST SP 800-132 non-weak pass phrase', () => {
@@ -56,12 +54,48 @@ describe('keychain', () => {
5654
expect(Keychain.options).to.exist()
5755
})
5856

59-
it('needs a supported hashing alorithm', () => {
57+
it('supports supported hashing alorithms', () => {
6058
const ok = new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'sha2-256' } })
6159
expect(ok).to.exist()
60+
})
61+
62+
it('does not support unsupported hashing alorithms', () => {
6263
expect(() => new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'my-hash' } })).to.throw()
6364
})
6465

66+
it('can list keys without a password', async () => {
67+
const keychain = new Keychain(datastore2)
68+
69+
expect(await keychain.listKeys()).to.have.lengthOf(0)
70+
})
71+
72+
it('can find a key without a password', async () => {
73+
const keychain = new Keychain(datastore2)
74+
const keychainWithPassword = new Keychain(datastore2, { passPhrase: `hello-${Date.now()}-${Date.now()}` })
75+
const id = `key-${Math.random()}`
76+
77+
await keychainWithPassword.createKey(id, 'rsa', 2048)
78+
79+
await expect(keychain.findKeyById(id)).to.eventually.be.ok()
80+
})
81+
82+
it('can remove a key without a password', async () => {
83+
const keychainWithoutPassword = new Keychain(datastore2)
84+
const keychainWithPassword = new Keychain(datastore2, { passPhrase: `hello-${Date.now()}-${Date.now()}` })
85+
const name = `key-${Math.random()}`
86+
87+
expect(await keychainWithPassword.createKey(name, 'rsa', 2048)).to.have.property('name', name)
88+
expect(await keychainWithoutPassword.findKeyByName(name)).to.have.property('name', name)
89+
await keychainWithoutPassword.removeKey(name)
90+
await expect(keychainWithoutPassword.findKeyByName(name)).to.be.rejectedWith(/does not exist/)
91+
})
92+
93+
it('requires a key to create a password', async () => {
94+
const keychain = new Keychain(datastore2)
95+
96+
await expect(keychain.createKey('derp')).to.be.rejected()
97+
})
98+
6599
it('can generate options', () => {
66100
const options = Keychain.generateOptions()
67101
options.passPhrase = passPhrase
@@ -475,7 +509,7 @@ describe('libp2p.keychain', () => {
475509
throw new Error('should throw an error using the keychain if no passphrase provided')
476510
})
477511

478-
it('can be used if a passphrase is provided', async () => {
512+
it('can be used when a passphrase is provided', async () => {
479513
const [libp2p] = await peerUtils.createPeer({
480514
started: false,
481515
config: {
@@ -492,6 +526,22 @@ describe('libp2p.keychain', () => {
492526
expect(kInfo).to.exist()
493527
})
494528

529+
it('does not require a keychain passphrase', async () => {
530+
const [libp2p] = await peerUtils.createPeer({
531+
started: false,
532+
config: {
533+
keychain: {
534+
datastore: new MemoryDatastore()
535+
}
536+
}
537+
})
538+
539+
await libp2p.loadKeychain()
540+
541+
const kInfo = await libp2p.keychain.createKey('keyName', 'ed25519')
542+
expect(kInfo).to.exist()
543+
})
544+
495545
it('can reload keys', async () => {
496546
const datastore = new MemoryDatastore()
497547
const [libp2p] = await peerUtils.createPeer({

0 commit comments

Comments
 (0)