diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index ef41d4fa..00000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -*.png binary -* crlf=input diff --git a/keychain/CHANGELOG.md b/keychain/CHANGELOG.md deleted file mode 100644 index f661d419..00000000 --- a/keychain/CHANGELOG.md +++ /dev/null @@ -1,147 +0,0 @@ - -# [0.6.0](https://github.com/libp2p/js-libp2p-keychain/compare/v0.5.4...v0.6.0) (2019-12-18) - - - - -## [0.5.4](https://github.com/libp2p/js-libp2p-keychain/compare/v0.5.3...v0.5.4) (2019-12-18) - - - - -## [0.5.3](https://github.com/libp2p/js-libp2p-keychain/compare/v0.5.2...v0.5.3) (2019-12-18) - - - - -## [0.5.2](https://github.com/libp2p/js-libp2p-keychain/compare/v0.5.1...v0.5.2) (2019-12-02) - - - - -## [0.5.1](https://github.com/libp2p/js-libp2p-keychain/compare/v0.5.0...v0.5.1) (2019-09-25) - - - - -# [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 - - - - -## [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)) - - - - -## [0.4.1](https://github.com/libp2p/js-libp2p-keychain/compare/v0.4.0...v0.4.1) (2019-03-14) - - - - -# [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)) - - - - -## [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)) - - - - -## [0.3.5](https://github.com/libp2p/js-libp2p-keychain/compare/v0.3.4...v0.3.5) (2019-01-10) - - - - -## [0.3.4](https://github.com/libp2p/js-libp2p-keychain/compare/v0.3.3...v0.3.4) (2019-01-04) - - - - -## [0.3.3](https://github.com/libp2p/js-libp2p-keychain/compare/v0.3.2...v0.3.3) (2018-10-25) - - - - -## [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)) - - - - -## [0.3.1](https://github.com/libp2p/js-libp2p-keychain/compare/v0.3.0...v0.3.1) (2018-01-29) - - - - -# [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)) - - - - -## [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)) - - - - -# 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)) - - - diff --git a/keychain/LICENSE b/keychain/LICENSE deleted file mode 100644 index bbfffbf9..00000000 --- a/keychain/LICENSE +++ /dev/null @@ -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. diff --git a/keychain/package.json b/keychain/package.json deleted file mode 100644 index 29b8873a..00000000 --- a/keychain/package.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "name": "libp2p-keychain", - "version": "0.6.0", - "description": "Key management and cryptographically protected messages", - "leadMaintainer": "Vasco Santos ", - "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 ", - "Alberto Elias ", - "Alex Potsides ", - "David Dias ", - "Hugo Dias ", - "Jacob Heun ", - "Maciej Krüger ", - "Masahiro Saito ", - "Richard Schneider ", - "Vasco Santos ", - "Vasco Santos ", - "Victor Bjelkholm " - ] -} diff --git a/keychain/test/browser.js b/keychain/test/browser.js deleted file mode 100644 index 02222fb3..00000000 --- a/keychain/test/browser.js +++ /dev/null @@ -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') -}) diff --git a/keychain/test/keychain.spec.js b/keychain/test/keychain.spec.js deleted file mode 100644 index c455f2d7..00000000 --- a/keychain/test/keychain.spec.js +++ /dev/null @@ -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) - }) - }) - }) -} diff --git a/keychain/test/node.js b/keychain/test/node.js deleted file mode 100644 index bbb25089..00000000 --- a/keychain/test/node.js +++ /dev/null @@ -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') -}) diff --git a/package.json b/package.json index 0a1a2995..4b86ae3a 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/keychain/README.md b/src/keychain/README.md similarity index 98% rename from keychain/README.md rename to src/keychain/README.md index 37829b48..1d1e08a8 100644 --- a/keychain/README.md +++ b/src/keychain/README.md @@ -28,12 +28,6 @@ ## Table of Contents -## Install - -```sh -npm install --save libp2p-keychain -``` - ### Usage ```js diff --git a/keychain/src/cms.js b/src/keychain/cms.js similarity index 100% rename from keychain/src/cms.js rename to src/keychain/cms.js diff --git a/keychain/doc/private-key.png b/src/keychain/doc/private-key.png similarity index 100% rename from keychain/doc/private-key.png rename to src/keychain/doc/private-key.png diff --git a/keychain/doc/private-key.xml b/src/keychain/doc/private-key.xml similarity index 100% rename from keychain/doc/private-key.xml rename to src/keychain/doc/private-key.xml diff --git a/keychain/src/index.js b/src/keychain/index.js similarity index 100% rename from keychain/src/index.js rename to src/keychain/index.js diff --git a/keychain/src/keychain.js b/src/keychain/keychain.js similarity index 100% rename from keychain/src/keychain.js rename to src/keychain/keychain.js diff --git a/keychain/src/util.js b/src/keychain/util.js similarity index 100% rename from keychain/src/util.js rename to src/keychain/util.js diff --git a/keychain/test/cms-interop.js b/test/keychain/cms-interop.spec.js similarity index 57% rename from keychain/test/cms-interop.js rename to test/keychain/cms-interop.spec.js index 06eb6312..3ffba04a 100644 --- a/keychain/test/cms-interop.js +++ b/test/keychain/cms-interop.spec.js @@ -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()) }) -} +}) diff --git a/test/keychain/keychain.spec.js b/test/keychain/keychain.spec.js new file mode 100644 index 00000000..c7f8d625 --- /dev/null +++ b/test/keychain/keychain.spec.js @@ -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) + }) + }) +}) diff --git a/keychain/test/peerid.js b/test/keychain/peerid.spec.js similarity index 100% rename from keychain/test/peerid.js rename to test/keychain/peerid.spec.js