chore: move keychain to libp2p

This commit is contained in:
Vasco Santos 2020-02-03 16:49:00 +01:00
parent 5ad5d3706a
commit 6973449809
18 changed files with 438 additions and 719 deletions

2
.gitattributes vendored
View File

@ -1,2 +0,0 @@
*.png binary
* crlf=input

View File

@ -1,147 +0,0 @@
<a name="0.6.0"></a>
# [0.6.0](https://github.com/libp2p/js-libp2p-keychain/compare/v0.5.4...v0.6.0) (2019-12-18)
<a name="0.5.4"></a>
## [0.5.4](https://github.com/libp2p/js-libp2p-keychain/compare/v0.5.3...v0.5.4) (2019-12-18)
<a name="0.5.3"></a>
## [0.5.3](https://github.com/libp2p/js-libp2p-keychain/compare/v0.5.2...v0.5.3) (2019-12-18)
<a name="0.5.2"></a>
## [0.5.2](https://github.com/libp2p/js-libp2p-keychain/compare/v0.5.1...v0.5.2) (2019-12-02)
<a name="0.5.1"></a>
## [0.5.1](https://github.com/libp2p/js-libp2p-keychain/compare/v0.5.0...v0.5.1) (2019-09-25)
<a name="0.5.0"></a>
# [0.5.0](https://github.com/libp2p/js-libp2p-keychain/compare/v0.4.2...v0.5.0) (2019-08-16)
* refactor: use async/await instead of callbacks (#37) ([dda315a](https://github.com/libp2p/js-libp2p-keychain/commit/dda315a)), closes [#37](https://github.com/libp2p/js-libp2p-keychain/issues/37)
### BREAKING CHANGES
* The api now uses async/await instead of callbacks.
Co-Authored-By: Vasco Santos <vasco.santos@moxy.studio>
<a name="0.4.2"></a>
## [0.4.2](https://github.com/libp2p/js-libp2p-keychain/compare/v0.4.1...v0.4.2) (2019-06-13)
### Bug Fixes
* throw errors with correct stack trace ([#35](https://github.com/libp2p/js-libp2p-keychain/issues/35)) ([7051b9c](https://github.com/libp2p/js-libp2p-keychain/commit/7051b9c))
<a name="0.4.1"></a>
## [0.4.1](https://github.com/libp2p/js-libp2p-keychain/compare/v0.4.0...v0.4.1) (2019-03-14)
<a name="0.4.0"></a>
# [0.4.0](https://github.com/libp2p/js-libp2p-keychain/compare/v0.3.6...v0.4.0) (2019-02-26)
### Features
* adds support for ed25199 and secp256k1 ([#31](https://github.com/libp2p/js-libp2p-keychain/issues/31)) ([9eb11f4](https://github.com/libp2p/js-libp2p-keychain/commit/9eb11f4))
<a name="0.3.6"></a>
## [0.3.6](https://github.com/libp2p/js-libp2p-keychain/compare/v0.3.5...v0.3.6) (2019-01-10)
### Bug Fixes
* reduce bundle size ([#28](https://github.com/libp2p/js-libp2p-keychain/issues/28)) ([7eeed87](https://github.com/libp2p/js-libp2p-keychain/commit/7eeed87))
<a name="0.3.5"></a>
## [0.3.5](https://github.com/libp2p/js-libp2p-keychain/compare/v0.3.4...v0.3.5) (2019-01-10)
<a name="0.3.4"></a>
## [0.3.4](https://github.com/libp2p/js-libp2p-keychain/compare/v0.3.3...v0.3.4) (2019-01-04)
<a name="0.3.3"></a>
## [0.3.3](https://github.com/libp2p/js-libp2p-keychain/compare/v0.3.2...v0.3.3) (2018-10-25)
<a name="0.3.2"></a>
## [0.3.2](https://github.com/libp2p/js-libp2p-keychain/compare/v0.3.1...v0.3.2) (2018-09-18)
### Bug Fixes
* validate createKey params properly ([#26](https://github.com/libp2p/js-libp2p-keychain/issues/26)) ([8dfaab1](https://github.com/libp2p/js-libp2p-keychain/commit/8dfaab1))
<a name="0.3.1"></a>
## [0.3.1](https://github.com/libp2p/js-libp2p-keychain/compare/v0.3.0...v0.3.1) (2018-01-29)
<a name="0.3.0"></a>
# [0.3.0](https://github.com/libp2p/js-libp2p-keychain/compare/v0.2.1...v0.3.0) (2018-01-29)
### Bug Fixes
* deepmerge 2.0.1 fails in browser, stay with 1.5.2 ([2ce4444](https://github.com/libp2p/js-libp2p-keychain/commit/2ce4444))
<a name="0.2.1"></a>
## [0.2.1](https://github.com/libp2p/js-libp2p-keychain/compare/v0.2.0...v0.2.1) (2017-12-28)
### Features
* generate unique options for a key chain ([#20](https://github.com/libp2p/js-libp2p-keychain/issues/20)) ([89a451c](https://github.com/libp2p/js-libp2p-keychain/commit/89a451c))
<a name="0.2.0"></a>
# 0.2.0 (2017-12-20)
### Bug Fixes
* error message ([8305d20](https://github.com/libp2p/js-libp2p-keychain/commit/8305d20))
* lint errors ([06917f7](https://github.com/libp2p/js-libp2p-keychain/commit/06917f7))
* lint errors ([ff4f656](https://github.com/libp2p/js-libp2p-keychain/commit/ff4f656))
* linting ([409a999](https://github.com/libp2p/js-libp2p-keychain/commit/409a999))
* maps an IPFS hash name to its forge equivalent ([f71d3a6](https://github.com/libp2p/js-libp2p-keychain/commit/f71d3a6)), closes [#12](https://github.com/libp2p/js-libp2p-keychain/issues/12)
* more linting ([7c44c91](https://github.com/libp2p/js-libp2p-keychain/commit/7c44c91))
* return info on removed key [#10](https://github.com/libp2p/js-libp2p-keychain/issues/10) ([f49e753](https://github.com/libp2p/js-libp2p-keychain/commit/f49e753))
### Features
* move bits from https://github.com/richardschneider/ipfs-encryption ([1a96ae8](https://github.com/libp2p/js-libp2p-keychain/commit/1a96ae8))
* use libp2p-crypto ([#18](https://github.com/libp2p/js-libp2p-keychain/issues/18)) ([c1627a9](https://github.com/libp2p/js-libp2p-keychain/commit/c1627a9))

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 libp2p
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,77 +0,0 @@
{
"name": "libp2p-keychain",
"version": "0.6.0",
"description": "Key management and cryptographically protected messages",
"leadMaintainer": "Vasco Santos <vasco.santos@moxy.studio>",
"main": "src/index.js",
"scripts": {
"lint": "aegir lint",
"build": "aegir build",
"coverage": "nyc --reporter=text --reporter=lcov npm run test:node",
"test": "aegir test -t node -t browser",
"test:node": "aegir test -t node",
"test:browser": "aegir test -t browser",
"release": "aegir release",
"release-minor": "aegir release --type minor",
"release-major": "aegir release --type major"
},
"pre-push": [
"lint"
],
"engines": {
"node": ">=10.0.0",
"npm": ">=3.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/libp2p/js-libp2p-keychain.git"
},
"keywords": [
"IPFS",
"libp2p",
"keys",
"encryption",
"secure",
"crypto"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/libp2p/js-libp2p-keychain/issues"
},
"homepage": "https://github.com/libp2p/js-libp2p-keychain#readme",
"dependencies": {
"err-code": "^2.0.0",
"interface-datastore": "^0.8.0",
"libp2p-crypto": "^0.17.1",
"merge-options": "^2.0.0",
"node-forge": "^0.9.1",
"sanitize-filename": "^1.6.1"
},
"devDependencies": {
"aegir": "^20.0.0",
"chai": "^4.2.0",
"chai-string": "^1.5.0",
"datastore-fs": "^0.9.0",
"datastore-level": "^0.14.0",
"dirty-chai": "^2.0.1",
"level": "^6.0.0",
"multihashes": "^0.4.15",
"peer-id": "^0.13.5",
"promisify-es6": "^1.0.3",
"rimraf": "^3.0.0"
},
"contributors": [
"Alan Shaw <alan.shaw@protocol.ai>",
"Alberto Elias <hi@albertoelias.me>",
"Alex Potsides <alex@achingbrain.net>",
"David Dias <daviddias.p@gmail.com>",
"Hugo Dias <mail@hugodias.me>",
"Jacob Heun <jacobheun@gmail.com>",
"Maciej Krüger <mkg20001@gmail.com>",
"Masahiro Saito <camelmasa@gmail.com>",
"Richard Schneider <makaretu@gmail.com>",
"Vasco Santos <vasco.santos@moxy.studio>",
"Vasco Santos <vasco.santos@ua.pt>",
"Victor Bjelkholm <victorbjelkholm@gmail.com>"
]
}

View File

@ -1,27 +0,0 @@
/* eslint-env mocha */
'use strict'
const LevelStore = require('datastore-level')
describe('browser', () => {
const datastore1 = new LevelStore('test-keystore-1', { db: require('level') })
const datastore2 = new LevelStore('test-keystore-2', { db: require('level') })
before(() => {
return Promise.all([
datastore1.open(),
datastore2.open()
])
})
after(() => {
return Promise.all([
datastore1.close(),
datastore2.close()
])
})
require('./keychain.spec')(datastore1, datastore2)
require('./cms-interop')(datastore2)
require('./peerid')
})

View File

@ -1,383 +0,0 @@
/* eslint max-nested-callbacks: ["error", 8] */
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const expect = chai.expect
const fail = expect.fail
chai.use(require('dirty-chai'))
chai.use(require('chai-string'))
const Keychain = require('../')
const PeerId = require('peer-id')
module.exports = (datastore1, datastore2) => {
describe('keychain', () => {
const passPhrase = 'this is not a secure phrase'
const rsaKeyName = 'tajné jméno'
const renamedRsaKeyName = 'ชื่อลับ'
let rsaKeyInfo
let emptyKeystore
let ks
before((done) => {
ks = new Keychain(datastore2, { passPhrase: passPhrase })
emptyKeystore = new Keychain(datastore1, { passPhrase: passPhrase })
done()
})
it('needs a pass phrase to encrypt a key', () => {
expect(() => new Keychain(datastore2)).to.throw()
})
it('needs a NIST SP 800-132 non-weak pass phrase', () => {
expect(() => new Keychain(datastore2, { passPhrase: '< 20 character' })).to.throw()
})
it('needs a store to persist a key', () => {
expect(() => new Keychain(null, { passPhrase: passPhrase })).to.throw()
})
it('has default options', () => {
expect(Keychain.options).to.exist()
})
it('needs a supported hashing alorithm', () => {
const ok = new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'sha2-256' } })
expect(ok).to.exist()
expect(() => new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'my-hash' } })).to.throw()
})
it('can generate options', () => {
const options = Keychain.generateOptions()
options.passPhrase = passPhrase
const chain = new Keychain(datastore2, options)
expect(chain).to.exist()
})
describe('key name', () => {
it('is a valid filename and non-ASCII', async () => {
const errors = await Promise.all([
ks.removeKey('../../nasty').then(fail, err => err),
ks.removeKey('').then(fail, err => err),
ks.removeKey(' ').then(fail, err => err),
ks.removeKey(null).then(fail, err => err),
ks.removeKey(undefined).then(fail, err => err)
])
expect(errors).to.have.length(5)
errors.forEach(error => {
expect(error).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
})
})
describe('key', () => {
it('can be an RSA key', async () => {
rsaKeyInfo = await ks.createKey(rsaKeyName, 'rsa', 2048)
expect(rsaKeyInfo).to.exist()
expect(rsaKeyInfo).to.have.property('name', rsaKeyName)
expect(rsaKeyInfo).to.have.property('id')
})
it('is encrypted PEM encoded PKCS #8', async () => {
const pem = await ks._getPrivateKey(rsaKeyName)
return expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
})
it('throws if an invalid private key name is given', async () => {
const err = await ks._getPrivateKey(undefined).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
it('throws if a private key cant be found', async () => {
const err = await ks._getPrivateKey('not real').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND')
})
it('does not overwrite existing key', async () => {
const err = await ks.createKey(rsaKeyName, 'rsa', 2048).then(fail, err => err)
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
})
it('cannot create the "self" key', async () => {
const err = await ks.createKey('self', 'rsa', 2048).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
it('should validate name is string', async () => {
const err = await ks.createKey(5, 'rsa', 2048).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
it('should validate type is string', async () => {
const err = await ks.createKey('TEST' + Date.now(), null, 2048).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_TYPE')
})
it('should validate size is integer', async () => {
const err = await ks.createKey('TEST' + Date.now(), 'rsa', 'string').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_SIZE')
})
describe('implements NIST SP 800-131A', () => {
it('disallows RSA length < 2048', async () => {
const err = await ks.createKey('bad-nist-rsa', 'rsa', 1024).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_SIZE')
})
})
})
describe('query', () => {
it('finds all existing keys', async () => {
const keys = await ks.listKeys()
expect(keys).to.exist()
const mykey = keys.find((k) => k.name.normalize() === rsaKeyName.normalize())
expect(mykey).to.exist()
})
it('finds a key by name', async () => {
const key = await ks.findKeyByName(rsaKeyName)
expect(key).to.exist()
expect(key).to.deep.equal(rsaKeyInfo)
})
it('finds a key by id', async () => {
const key = await ks.findKeyById(rsaKeyInfo.id)
expect(key).to.exist()
expect(key).to.deep.equal(rsaKeyInfo)
})
it('returns the key\'s name and id', async () => {
const keys = await ks.listKeys()
expect(keys).to.exist()
keys.forEach((key) => {
expect(key).to.have.property('name')
expect(key).to.have.property('id')
})
})
})
describe('CMS protected data', () => {
const plainData = Buffer.from('This is a message from Alice to Bob')
let cms
it('service is available', () => {
expect(ks).to.have.property('cms')
})
it('requires a key', async () => {
const err = await ks.cms.encrypt('no-key', plainData).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND')
})
it('requires plain data as a Buffer', async () => {
const err = await ks.cms.encrypt(rsaKeyName, 'plain data').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_PARAMS')
})
it('encrypts', async () => {
cms = await ks.cms.encrypt(rsaKeyName, plainData)
expect(cms).to.exist()
expect(cms).to.be.instanceOf(Buffer)
})
it('is a PKCS #7 message', async () => {
const err = await ks.cms.decrypt('not CMS').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_PARAMS')
})
it('is a PKCS #7 binary message', async () => {
const err = await ks.cms.decrypt(plainData).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_CMS')
})
it('cannot be read without the key', async () => {
const err = await emptyKeystore.cms.decrypt(cms).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('missingKeys')
expect(err.missingKeys).to.eql([rsaKeyInfo.id])
expect(err).to.have.property('code', 'ERR_MISSING_KEYS')
})
it('can be read with the key', async () => {
const plain = await ks.cms.decrypt(cms)
expect(plain).to.exist()
expect(plain.toString()).to.equal(plainData.toString())
})
})
describe('exported key', () => {
let pemKey
it('requires the password', async () => {
const err = await ks.exportKey(rsaKeyName).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_PASSWORD_REQUIRED')
})
it('requires the key name', async () => {
const err = await ks.exportKey(undefined, 'password').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
it('is a PKCS #8 encrypted pem', async () => {
pemKey = await ks.exportKey(rsaKeyName, 'password')
expect(pemKey).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
})
it('can be imported', async () => {
const key = await ks.importKey('imported-key', pemKey, 'password')
expect(key.name).to.equal('imported-key')
expect(key.id).to.equal(rsaKeyInfo.id)
})
it('requires the pem', async () => {
const err = await ks.importKey('imported-key', undefined, 'password').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_PEM_REQUIRED')
})
it('cannot be imported as an existing key name', async () => {
const err = await ks.importKey(rsaKeyName, pemKey, 'password').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
})
it('cannot be imported with the wrong password', async () => {
const err = await ks.importKey('a-new-name-for-import', pemKey, 'not the password').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_CANNOT_READ_KEY')
})
})
describe('peer id', () => {
const alicePrivKey = 'CAASpgkwggSiAgEAAoIBAQC2SKo/HMFZeBml1AF3XijzrxrfQXdJzjePBZAbdxqKR1Mc6juRHXij6HXYPjlAk01BhF1S3Ll4Lwi0cAHhggf457sMg55UWyeGKeUv0ucgvCpBwlR5cQ020i0MgzjPWOLWq1rtvSbNcAi2ZEVn6+Q2EcHo3wUvWRtLeKz+DZSZfw2PEDC+DGPJPl7f8g7zl56YymmmzH9liZLNrzg/qidokUv5u1pdGrcpLuPNeTODk0cqKB+OUbuKj9GShYECCEjaybJDl9276oalL9ghBtSeEv20kugatTvYy590wFlJkkvyl+nPxIH0EEYMKK9XRWlu9XYnoSfboiwcv8M3SlsjAgMBAAECggEAZtju/bcKvKFPz0mkHiaJcpycy9STKphorpCT83srBVQi59CdFU6Mj+aL/xt0kCPMVigJw8P3/YCEJ9J+rS8BsoWE+xWUEsJvtXoT7vzPHaAtM3ci1HZd302Mz1+GgS8Epdx+7F5p80XAFLDUnELzOzKftvWGZmWfSeDnslwVONkL/1VAzwKy7Ce6hk4SxRE7l2NE2OklSHOzCGU1f78ZzVYKSnS5Ag9YrGjOAmTOXDbKNKN/qIorAQ1bovzGoCwx3iGIatQKFOxyVCyO1PsJYT7JO+kZbhBWRRE+L7l+ppPER9bdLFxs1t5CrKc078h+wuUr05S1P1JjXk68pk3+kQKBgQDeK8AR11373Mzib6uzpjGzgNRMzdYNuExWjxyxAzz53NAR7zrPHvXvfIqjDScLJ4NcRO2TddhXAfZoOPVH5k4PJHKLBPKuXZpWlookCAyENY7+Pd55S8r+a+MusrMagYNljb5WbVTgN8cgdpim9lbbIFlpN6SZaVjLQL3J8TWH6wKBgQDSChzItkqWX11CNstJ9zJyUE20I7LrpyBJNgG1gtvz3ZMUQCn3PxxHtQzN9n1P0mSSYs+jBKPuoSyYLt1wwe10/lpgL4rkKWU3/m1Myt0tveJ9WcqHh6tzcAbb/fXpUFT/o4SWDimWkPkuCb+8j//2yiXk0a/T2f36zKMuZvujqQKBgC6B7BAQDG2H2B/ijofp12ejJU36nL98gAZyqOfpLJ+FeMz4TlBDQ+phIMhnHXA5UkdDapQ+zA3SrFk+6yGk9Vw4Hf46B+82SvOrSbmnMa+PYqKYIvUzR4gg34rL/7AhwnbEyD5hXq4dHwMNsIDq+l2elPjwm/U9V0gdAl2+r50HAoGALtsKqMvhv8HucAMBPrLikhXP/8um8mMKFMrzfqZ+otxfHzlhI0L08Bo3jQrb0Z7ByNY6M8epOmbCKADsbWcVre/AAY0ZkuSZK/CaOXNX/AhMKmKJh8qAOPRY02LIJRBCpfS4czEdnfUhYV/TYiFNnKRj57PPYZdTzUsxa/yVTmECgYBr7slQEjb5Onn5mZnGDh+72BxLNdgwBkhO0OCdpdISqk0F0Pxby22DFOKXZEpiyI9XYP1C8wPiJsShGm2yEwBPWXnrrZNWczaVuCbXHrZkWQogBDG3HGXNdU4MAWCyiYlyinIBpPpoAJZSzpGLmWbMWh28+RJS6AQX6KHrK1o2uw=='
let alice
before(async function () {
const encoded = Buffer.from(alicePrivKey, 'base64')
alice = await PeerId.createFromPrivKey(encoded)
})
it('private key can be imported', async () => {
const key = await ks.importPeer('alice', alice)
expect(key.name).to.equal('alice')
expect(key.id).to.equal(alice.toB58String())
})
it('private key import requires a valid name', async () => {
const err = await ks.importPeer(undefined, alice).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
it('private key import requires the peer', async () => {
const err = await ks.importPeer('alice').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_MISSING_PRIVATE_KEY')
})
it('key id exists', async () => {
const key = await ks.findKeyById(alice.toB58String())
expect(key).to.exist()
expect(key).to.have.property('name', 'alice')
expect(key).to.have.property('id', alice.toB58String())
})
it('key name exists', async () => {
const key = await ks.findKeyByName('alice')
expect(key).to.exist()
expect(key).to.have.property('name', 'alice')
expect(key).to.have.property('id', alice.toB58String())
})
})
describe('rename', () => {
it('requires an existing key name', async () => {
const err = await ks.renameKey('not-there', renamedRsaKeyName).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_NOT_FOUND')
})
it('requires a valid new key name', async () => {
const err = await ks.renameKey(rsaKeyName, '..\not-valid').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_NEW_KEY_NAME_INVALID')
})
it('does not overwrite existing key', async () => {
const err = await ks.renameKey(rsaKeyName, rsaKeyName).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
})
it('cannot create the "self" key', async () => {
const err = await ks.renameKey(rsaKeyName, 'self').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_NEW_KEY_NAME_INVALID')
})
it('removes the existing key name', async () => {
const key = await ks.renameKey(rsaKeyName, renamedRsaKeyName)
expect(key).to.exist()
expect(key).to.have.property('name', renamedRsaKeyName)
expect(key).to.have.property('id', rsaKeyInfo.id)
// Try to find the changed key
const err = await ks.findKeyByName(rsaKeyName).then(fail, err => err)
expect(err).to.exist()
})
it('creates the new key name', async () => {
const key = await ks.findKeyByName(renamedRsaKeyName)
expect(key).to.exist()
expect(key).to.have.property('name', renamedRsaKeyName)
})
it('does not change the key ID', async () => {
const key = await ks.findKeyByName(renamedRsaKeyName)
expect(key).to.exist()
expect(key).to.have.property('name', renamedRsaKeyName)
expect(key).to.have.property('id', rsaKeyInfo.id)
})
it('throws with invalid key names', async () => {
const err = await ks.findKeyByName(undefined).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
})
describe('key removal', () => {
it('cannot remove the "self" key', async () => {
const err = await ks.removeKey('self').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
it('cannot remove an unknown key', async () => {
const err = await ks.removeKey('not-there').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND')
})
it('can remove a known key', async () => {
const key = await ks.removeKey(renamedRsaKeyName)
expect(key).to.exist()
expect(key).to.have.property('name', renamedRsaKeyName)
expect(key).to.have.property('id', rsaKeyInfo.id)
})
})
})
}

View File

@ -1,31 +0,0 @@
/* eslint-env mocha */
'use strict'
const os = require('os')
const path = require('path')
const promisify = require('promisify-es6')
const rimraf = promisify(require('rimraf'))
const FsStore = require('datastore-fs')
describe('node', () => {
const store1 = path.join(os.tmpdir(), 'test-keystore-1-' + Date.now())
const store2 = path.join(os.tmpdir(), 'test-keystore-2-' + Date.now())
const datastore1 = new FsStore(store1)
const datastore2 = new FsStore(store2)
before(async () => {
await datastore1.open()
await datastore2.open()
})
after(async () => {
await datastore1.close()
await datastore2.close()
await rimraf(store1)
await rimraf(store2)
})
require('./keychain.spec')(datastore1, datastore2)
require('./cms-interop')(datastore2)
require('./peerid')
})

View File

@ -49,6 +49,7 @@
"debug": "^4.1.1",
"err-code": "^1.1.2",
"hashlru": "^2.3.0",
"interface-datastore": "^0.8.0",
"it-all": "^1.0.1",
"it-buffer": "^0.1.1",
"it-handshake": "^1.0.1",
@ -64,6 +65,7 @@
"multiaddr": "^7.2.1",
"multistream-select": "^0.15.0",
"mutable-proxy": "^1.0.0",
"node-forge": "^0.9.1",
"p-any": "^2.1.0",
"p-fifo": "^1.0.0",
"p-settle": "^3.1.0",
@ -71,6 +73,7 @@
"peer-info": "^0.17.0",
"protons": "^1.0.1",
"retimer": "^2.0.0",
"sanitize-filename": "^1.6.3",
"timeout-abort-controller": "^1.0.0",
"xsalsa20": "^1.0.2"
},
@ -80,12 +83,17 @@
"aegir": "^20.5.1",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-string": "^1.5.0",
"cids": "^0.7.1",
"datastore-fs": "^0.9.1",
"datastore-level": "^0.14.0",
"delay": "^4.3.0",
"dirty-chai": "^2.0.1",
"is-browser": "^2.1.0",
"it-concat": "^1.0.0",
"it-pair": "^1.0.0",
"it-pushable": "^1.4.0",
"level": "^6.0.0",
"libp2p-bootstrap": "^0.10.3",
"libp2p-delegated-content-routing": "^0.4.1",
"libp2p-delegated-peer-routing": "^0.4.0",
@ -98,10 +106,13 @@
"libp2p-tcp": "^0.14.1",
"libp2p-webrtc-star": "^0.17.0",
"libp2p-websockets": "^0.13.1",
"multihashes": "^0.4.15",
"nock": "^10.0.6",
"p-defer": "^3.0.0",
"p-times": "^2.1.0",
"p-wait-for": "^3.1.0",
"promisify-es6": "^1.0.3",
"rimraf": "^3.0.0",
"sinon": "^8.1.0",
"streaming-iterables": "^4.1.0",
"wrtc": "^0.4.1"

View File

@ -28,12 +28,6 @@
## Table of Contents
## Install
```sh
npm install --save libp2p-keychain
```
### Usage
```js

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -7,24 +7,33 @@ const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
chai.use(require('chai-string'))
const Keychain = require('..')
module.exports = (datastore) => {
describe('cms interop', () => {
const passPhrase = 'this is not a secure phrase'
const aliceKeyName = 'cms-interop-alice'
let ks
const os = require('os')
const path = require('path')
const isBrowser = require('is-browser')
const FsStore = require('datastore-fs')
const LevelStore = require('datastore-level')
before(() => {
ks = new Keychain(datastore, { passPhrase: passPhrase })
})
const Keychain = require('../../src/keychain')
const plainData = Buffer.from('This is a message from Alice to Bob')
describe('cms interop', () => {
const passPhrase = 'this is not a secure phrase'
const aliceKeyName = 'cms-interop-alice'
let ks
it('imports openssl key', async function () {
this.timeout(10 * 1000)
const aliceKid = 'QmNzBqPwp42HZJccsLtc4ok6LjZAspckgs2du5tTmjPfFA'
const alice = `-----BEGIN ENCRYPTED PRIVATE KEY-----
before(() => {
const datastore = isBrowser
? new LevelStore('test-keystore-1', { db: require('level') })
: new FsStore(path.join(os.tmpdir(), 'test-keystore-1-' + Date.now()))
ks = new Keychain(datastore, { passPhrase: passPhrase })
})
const plainData = Buffer.from('This is a message from Alice to Bob')
it('imports openssl key', async function () {
this.timeout(10 * 1000)
const aliceKid = 'QmNzBqPwp42HZJccsLtc4ok6LjZAspckgs2du5tTmjPfFA'
const alice = `-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIMhYqiVoLJMICAggA
MBQGCCqGSIb3DQMHBAhU7J9bcJPLDQSCAoDzi0dP6z97wJBs3jK2hDvZYdoScknG
QMPOnpG1LO3IZ7nFha1dta5liWX+xRFV04nmVYkkNTJAPS0xjJOG9B5Hm7wm8uTd
@ -42,13 +51,13 @@ igg5jozKCW82JsuWSiW9tu0F/6DuvYiZwHS3OLiJP0CuLfbOaRw8Jia1RTvXEH7m
cn4oisOvxCprs4aM9UVjtZTCjfyNpX8UWwT1W3rySV+KQNhxuMy3RzmL
-----END ENCRYPTED PRIVATE KEY-----
`
const key = await ks.importKey(aliceKeyName, alice, 'mypassword')
expect(key.name).to.equal(aliceKeyName)
expect(key.id).to.equal(aliceKid)
})
const key = await ks.importKey(aliceKeyName, alice, 'mypassword')
expect(key.name).to.equal(aliceKeyName)
expect(key.id).to.equal(aliceKid)
})
it('decrypts node-forge example', async () => {
const example = `
it('decrypts node-forge example', async () => {
const example = `
MIIBcwYJKoZIhvcNAQcDoIIBZDCCAWACAQAxgfowgfcCAQAwYDBbMQ0wCwYDVQQK
EwRpcGZzMREwDwYDVQQLEwhrZXlzdG9yZTE3MDUGA1UEAxMuUW1OekJxUHdwNDJI
WkpjY3NMdGM0b2s2TGpaQXNwY2tnczJkdTV0VG1qUGZGQQIBATANBgkqhkiG9w0B
@ -58,9 +67,8 @@ knU1yykWGkdlbclCuu0NaAfmb8o0OX50CbEKZB7xmsv8tnqn0H0jMF4GCSqGSIb3
DQEHATAdBglghkgBZQMEASoEEP/PW1JWehQx6/dsLkp/Mf+gMgQwFM9liLTqC56B
nHILFmhac/+a/StQOKuf9dx5qXeGvt9LnwKuGGSfNX4g+dTkoa6N
`
const plain = await ks.cms.decrypt(Buffer.from(example, 'base64'))
expect(plain).to.exist()
expect(plain.toString()).to.equal(plainData.toString())
})
const plain = await ks.cms.decrypt(Buffer.from(example, 'base64'))
expect(plain).to.exist()
expect(plain.toString()).to.equal(plainData.toString())
})
}
})

View File

@ -0,0 +1,394 @@
/* eslint max-nested-callbacks: ["error", 8] */
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const expect = chai.expect
const fail = expect.fail
chai.use(require('dirty-chai'))
chai.use(require('chai-string'))
const os = require('os')
const path = require('path')
const isBrowser = require('is-browser')
const FsStore = require('datastore-fs')
const LevelStore = require('datastore-level')
const Keychain = require('../../src/keychain')
const PeerId = require('peer-id')
describe('keychain', () => {
const passPhrase = 'this is not a secure phrase'
const rsaKeyName = 'tajné jméno'
const renamedRsaKeyName = 'ชื่อลับ'
let rsaKeyInfo
let emptyKeystore
let ks
let datastore1, datastore2
before(() => {
datastore1 = isBrowser
? new LevelStore('test-keystore-1', { db: require('level') })
: new FsStore(path.join(os.tmpdir(), 'test-keystore-1-' + Date.now()))
datastore2 = isBrowser
? new LevelStore('test-keystore-2', { db: require('level') })
: new FsStore(path.join(os.tmpdir(), 'test-keystore-2-' + Date.now()))
ks = new Keychain(datastore2, { passPhrase: passPhrase })
emptyKeystore = new Keychain(datastore1, { passPhrase: passPhrase })
})
it('needs a pass phrase to encrypt a key', () => {
expect(() => new Keychain(datastore2)).to.throw()
})
it('needs a NIST SP 800-132 non-weak pass phrase', () => {
expect(() => new Keychain(datastore2, { passPhrase: '< 20 character' })).to.throw()
})
it('needs a store to persist a key', () => {
expect(() => new Keychain(null, { passPhrase: passPhrase })).to.throw()
})
it('has default options', () => {
expect(Keychain.options).to.exist()
})
it('needs a supported hashing alorithm', () => {
const ok = new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'sha2-256' } })
expect(ok).to.exist()
expect(() => new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'my-hash' } })).to.throw()
})
it('can generate options', () => {
const options = Keychain.generateOptions()
options.passPhrase = passPhrase
const chain = new Keychain(datastore2, options)
expect(chain).to.exist()
})
describe('key name', () => {
it('is a valid filename and non-ASCII', async () => {
const errors = await Promise.all([
ks.removeKey('../../nasty').then(fail, err => err),
ks.removeKey('').then(fail, err => err),
ks.removeKey(' ').then(fail, err => err),
ks.removeKey(null).then(fail, err => err),
ks.removeKey(undefined).then(fail, err => err)
])
expect(errors).to.have.length(5)
errors.forEach(error => {
expect(error).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
})
})
describe('key', () => {
it('can be an RSA key', async () => {
rsaKeyInfo = await ks.createKey(rsaKeyName, 'rsa', 2048)
expect(rsaKeyInfo).to.exist()
expect(rsaKeyInfo).to.have.property('name', rsaKeyName)
expect(rsaKeyInfo).to.have.property('id')
})
it('is encrypted PEM encoded PKCS #8', async () => {
const pem = await ks._getPrivateKey(rsaKeyName)
return expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
})
it('throws if an invalid private key name is given', async () => {
const err = await ks._getPrivateKey(undefined).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
it('throws if a private key cant be found', async () => {
const err = await ks._getPrivateKey('not real').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND')
})
it('does not overwrite existing key', async () => {
const err = await ks.createKey(rsaKeyName, 'rsa', 2048).then(fail, err => err)
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
})
it('cannot create the "self" key', async () => {
const err = await ks.createKey('self', 'rsa', 2048).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
it('should validate name is string', async () => {
const err = await ks.createKey(5, 'rsa', 2048).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
it('should validate type is string', async () => {
const err = await ks.createKey('TEST' + Date.now(), null, 2048).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_TYPE')
})
it('should validate size is integer', async () => {
const err = await ks.createKey('TEST' + Date.now(), 'rsa', 'string').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_SIZE')
})
describe('implements NIST SP 800-131A', () => {
it('disallows RSA length < 2048', async () => {
const err = await ks.createKey('bad-nist-rsa', 'rsa', 1024).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_SIZE')
})
})
})
describe('query', () => {
it('finds all existing keys', async () => {
const keys = await ks.listKeys()
expect(keys).to.exist()
const mykey = keys.find((k) => k.name.normalize() === rsaKeyName.normalize())
expect(mykey).to.exist()
})
it('finds a key by name', async () => {
const key = await ks.findKeyByName(rsaKeyName)
expect(key).to.exist()
expect(key).to.deep.equal(rsaKeyInfo)
})
it('finds a key by id', async () => {
const key = await ks.findKeyById(rsaKeyInfo.id)
expect(key).to.exist()
expect(key).to.deep.equal(rsaKeyInfo)
})
it('returns the key\'s name and id', async () => {
const keys = await ks.listKeys()
expect(keys).to.exist()
keys.forEach((key) => {
expect(key).to.have.property('name')
expect(key).to.have.property('id')
})
})
})
describe('CMS protected data', () => {
const plainData = Buffer.from('This is a message from Alice to Bob')
let cms
it('service is available', () => {
expect(ks).to.have.property('cms')
})
it('requires a key', async () => {
const err = await ks.cms.encrypt('no-key', plainData).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND')
})
it('requires plain data as a Buffer', async () => {
const err = await ks.cms.encrypt(rsaKeyName, 'plain data').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_PARAMS')
})
it('encrypts', async () => {
cms = await ks.cms.encrypt(rsaKeyName, plainData)
expect(cms).to.exist()
expect(cms).to.be.instanceOf(Buffer)
})
it('is a PKCS #7 message', async () => {
const err = await ks.cms.decrypt('not CMS').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_PARAMS')
})
it('is a PKCS #7 binary message', async () => {
const err = await ks.cms.decrypt(plainData).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_CMS')
})
it('cannot be read without the key', async () => {
const err = await emptyKeystore.cms.decrypt(cms).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('missingKeys')
expect(err.missingKeys).to.eql([rsaKeyInfo.id])
expect(err).to.have.property('code', 'ERR_MISSING_KEYS')
})
it('can be read with the key', async () => {
const plain = await ks.cms.decrypt(cms)
expect(plain).to.exist()
expect(plain.toString()).to.equal(plainData.toString())
})
})
describe('exported key', () => {
let pemKey
it('requires the password', async () => {
const err = await ks.exportKey(rsaKeyName).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_PASSWORD_REQUIRED')
})
it('requires the key name', async () => {
const err = await ks.exportKey(undefined, 'password').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
it('is a PKCS #8 encrypted pem', async () => {
pemKey = await ks.exportKey(rsaKeyName, 'password')
expect(pemKey).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
})
it('can be imported', async () => {
const key = await ks.importKey('imported-key', pemKey, 'password')
expect(key.name).to.equal('imported-key')
expect(key.id).to.equal(rsaKeyInfo.id)
})
it('requires the pem', async () => {
const err = await ks.importKey('imported-key', undefined, 'password').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_PEM_REQUIRED')
})
it('cannot be imported as an existing key name', async () => {
const err = await ks.importKey(rsaKeyName, pemKey, 'password').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
})
it('cannot be imported with the wrong password', async () => {
const err = await ks.importKey('a-new-name-for-import', pemKey, 'not the password').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_CANNOT_READ_KEY')
})
})
describe('peer id', () => {
const alicePrivKey = 'CAASpgkwggSiAgEAAoIBAQC2SKo/HMFZeBml1AF3XijzrxrfQXdJzjePBZAbdxqKR1Mc6juRHXij6HXYPjlAk01BhF1S3Ll4Lwi0cAHhggf457sMg55UWyeGKeUv0ucgvCpBwlR5cQ020i0MgzjPWOLWq1rtvSbNcAi2ZEVn6+Q2EcHo3wUvWRtLeKz+DZSZfw2PEDC+DGPJPl7f8g7zl56YymmmzH9liZLNrzg/qidokUv5u1pdGrcpLuPNeTODk0cqKB+OUbuKj9GShYECCEjaybJDl9276oalL9ghBtSeEv20kugatTvYy590wFlJkkvyl+nPxIH0EEYMKK9XRWlu9XYnoSfboiwcv8M3SlsjAgMBAAECggEAZtju/bcKvKFPz0mkHiaJcpycy9STKphorpCT83srBVQi59CdFU6Mj+aL/xt0kCPMVigJw8P3/YCEJ9J+rS8BsoWE+xWUEsJvtXoT7vzPHaAtM3ci1HZd302Mz1+GgS8Epdx+7F5p80XAFLDUnELzOzKftvWGZmWfSeDnslwVONkL/1VAzwKy7Ce6hk4SxRE7l2NE2OklSHOzCGU1f78ZzVYKSnS5Ag9YrGjOAmTOXDbKNKN/qIorAQ1bovzGoCwx3iGIatQKFOxyVCyO1PsJYT7JO+kZbhBWRRE+L7l+ppPER9bdLFxs1t5CrKc078h+wuUr05S1P1JjXk68pk3+kQKBgQDeK8AR11373Mzib6uzpjGzgNRMzdYNuExWjxyxAzz53NAR7zrPHvXvfIqjDScLJ4NcRO2TddhXAfZoOPVH5k4PJHKLBPKuXZpWlookCAyENY7+Pd55S8r+a+MusrMagYNljb5WbVTgN8cgdpim9lbbIFlpN6SZaVjLQL3J8TWH6wKBgQDSChzItkqWX11CNstJ9zJyUE20I7LrpyBJNgG1gtvz3ZMUQCn3PxxHtQzN9n1P0mSSYs+jBKPuoSyYLt1wwe10/lpgL4rkKWU3/m1Myt0tveJ9WcqHh6tzcAbb/fXpUFT/o4SWDimWkPkuCb+8j//2yiXk0a/T2f36zKMuZvujqQKBgC6B7BAQDG2H2B/ijofp12ejJU36nL98gAZyqOfpLJ+FeMz4TlBDQ+phIMhnHXA5UkdDapQ+zA3SrFk+6yGk9Vw4Hf46B+82SvOrSbmnMa+PYqKYIvUzR4gg34rL/7AhwnbEyD5hXq4dHwMNsIDq+l2elPjwm/U9V0gdAl2+r50HAoGALtsKqMvhv8HucAMBPrLikhXP/8um8mMKFMrzfqZ+otxfHzlhI0L08Bo3jQrb0Z7ByNY6M8epOmbCKADsbWcVre/AAY0ZkuSZK/CaOXNX/AhMKmKJh8qAOPRY02LIJRBCpfS4czEdnfUhYV/TYiFNnKRj57PPYZdTzUsxa/yVTmECgYBr7slQEjb5Onn5mZnGDh+72BxLNdgwBkhO0OCdpdISqk0F0Pxby22DFOKXZEpiyI9XYP1C8wPiJsShGm2yEwBPWXnrrZNWczaVuCbXHrZkWQogBDG3HGXNdU4MAWCyiYlyinIBpPpoAJZSzpGLmWbMWh28+RJS6AQX6KHrK1o2uw=='
let alice
before(async function () {
const encoded = Buffer.from(alicePrivKey, 'base64')
alice = await PeerId.createFromPrivKey(encoded)
})
it('private key can be imported', async () => {
const key = await ks.importPeer('alice', alice)
expect(key.name).to.equal('alice')
expect(key.id).to.equal(alice.toB58String())
})
it('private key import requires a valid name', async () => {
const err = await ks.importPeer(undefined, alice).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
it('private key import requires the peer', async () => {
const err = await ks.importPeer('alice').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_MISSING_PRIVATE_KEY')
})
it('key id exists', async () => {
const key = await ks.findKeyById(alice.toB58String())
expect(key).to.exist()
expect(key).to.have.property('name', 'alice')
expect(key).to.have.property('id', alice.toB58String())
})
it('key name exists', async () => {
const key = await ks.findKeyByName('alice')
expect(key).to.exist()
expect(key).to.have.property('name', 'alice')
expect(key).to.have.property('id', alice.toB58String())
})
})
describe('rename', () => {
it('requires an existing key name', async () => {
const err = await ks.renameKey('not-there', renamedRsaKeyName).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_NOT_FOUND')
})
it('requires a valid new key name', async () => {
const err = await ks.renameKey(rsaKeyName, '..\not-valid').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_NEW_KEY_NAME_INVALID')
})
it('does not overwrite existing key', async () => {
const err = await ks.renameKey(rsaKeyName, rsaKeyName).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
})
it('cannot create the "self" key', async () => {
const err = await ks.renameKey(rsaKeyName, 'self').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_NEW_KEY_NAME_INVALID')
})
it('removes the existing key name', async () => {
const key = await ks.renameKey(rsaKeyName, renamedRsaKeyName)
expect(key).to.exist()
expect(key).to.have.property('name', renamedRsaKeyName)
expect(key).to.have.property('id', rsaKeyInfo.id)
// Try to find the changed key
const err = await ks.findKeyByName(rsaKeyName).then(fail, err => err)
expect(err).to.exist()
})
it('creates the new key name', async () => {
const key = await ks.findKeyByName(renamedRsaKeyName)
expect(key).to.exist()
expect(key).to.have.property('name', renamedRsaKeyName)
})
it('does not change the key ID', async () => {
const key = await ks.findKeyByName(renamedRsaKeyName)
expect(key).to.exist()
expect(key).to.have.property('name', renamedRsaKeyName)
expect(key).to.have.property('id', rsaKeyInfo.id)
})
it('throws with invalid key names', async () => {
const err = await ks.findKeyByName(undefined).then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
})
describe('key removal', () => {
it('cannot remove the "self" key', async () => {
const err = await ks.removeKey('self').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
it('cannot remove an unknown key', async () => {
const err = await ks.removeKey('not-there').then(fail, err => err)
expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND')
})
it('can remove a known key', async () => {
const key = await ks.removeKey(renamedRsaKeyName)
expect(key).to.exist()
expect(key).to.have.property('name', renamedRsaKeyName)
expect(key).to.have.property('id', rsaKeyInfo.id)
})
})
})