Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow certain keychain operations without a password #726

Merged
merged 3 commits into from
Aug 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class Libp2p extends EventEmitter {
}

// Create keychain
if (this._options.keychain && this._options.keychain.pass && this._options.keychain.datastore) {
if (this._options.keychain && this._options.keychain.datastore) {
log('creating keychain')

const keychainOpts = Keychain.generateOptions()
Expand Down
8 changes: 4 additions & 4 deletions src/keychain/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class Keychain {
this.opts = mergeOptions(defaultOptions, options)

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

// Create the derived encrypting key
const dek = crypto.pbkdf2(
const dek = this.opts.passPhrase ? crypto.pbkdf2(
this.opts.passPhrase,
this.opts.dek.salt,
this.opts.dek.iterationCount,
this.opts.dek.keyLength,
this.opts.dek.hash)
this.opts.dek.hash) : ''

Object.defineProperty(this, '_', { value: () => dek })
}

Expand Down
64 changes: 57 additions & 7 deletions test/keychain/keychain.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
/* eslint-env mocha */
'use strict'

const chai = require('chai')
const { expect } = chai
const { chai, expect } = require('aegir/utils/chai')
const fail = expect.fail
chai.use(require('dirty-chai'))
chai.use(require('chai-string'))

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

it('needs a pass phrase to encrypt a key', () => {
expect(() => new Keychain(datastore2)).to.throw()
it('can start without a password', () => {
expect(() => new Keychain(datastore2)).to.not.throw()
})

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

it('needs a supported hashing alorithm', () => {
it('supports supported hashing alorithms', () => {
const ok = new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'sha2-256' } })
expect(ok).to.exist()
})

it('does not support unsupported hashing alorithms', () => {
expect(() => new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'my-hash' } })).to.throw()
})

it('can list keys without a password', async () => {
const keychain = new Keychain(datastore2)

expect(await keychain.listKeys()).to.have.lengthOf(0)
})

it('can find a key without a password', async () => {
const keychain = new Keychain(datastore2)
const keychainWithPassword = new Keychain(datastore2, { passPhrase: `hello-${Date.now()}-${Date.now()}` })
const id = `key-${Math.random()}`

await keychainWithPassword.createKey(id, 'rsa', 2048)

await expect(keychain.findKeyById(id)).to.eventually.be.ok()
})

it('can remove a key without a password', async () => {
const keychainWithoutPassword = new Keychain(datastore2)
const keychainWithPassword = new Keychain(datastore2, { passPhrase: `hello-${Date.now()}-${Date.now()}` })
const name = `key-${Math.random()}`

expect(await keychainWithPassword.createKey(name, 'rsa', 2048)).to.have.property('name', name)
expect(await keychainWithoutPassword.findKeyByName(name)).to.have.property('name', name)
await keychainWithoutPassword.removeKey(name)
await expect(keychainWithoutPassword.findKeyByName(name)).to.be.rejectedWith(/does not exist/)
})

it('requires a key to create a password', async () => {
const keychain = new Keychain(datastore2)

await expect(keychain.createKey('derp')).to.be.rejected()
})

it('can generate options', () => {
const options = Keychain.generateOptions()
options.passPhrase = passPhrase
Expand Down Expand Up @@ -475,7 +509,7 @@ describe('libp2p.keychain', () => {
throw new Error('should throw an error using the keychain if no passphrase provided')
})

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

it('does not require a keychain passphrase', async () => {
const [libp2p] = await peerUtils.createPeer({
started: false,
config: {
keychain: {
datastore: new MemoryDatastore()
}
}
})

await libp2p.loadKeychain()

const kInfo = await libp2p.keychain.createKey('keyName', 'ed25519')
expect(kInfo).to.exist()
})

it('can reload keys', async () => {
const datastore = new MemoryDatastore()
const [libp2p] = await peerUtils.createPeer({
Expand Down