mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-04-25 02:22:14 +00:00
chore: move keychain to libp2p
This commit is contained in:
parent
5ad5d3706a
commit
6973449809
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1,2 +0,0 @@
|
|||||||
*.png binary
|
|
||||||
* crlf=input
|
|
@ -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))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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.
|
|
@ -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>"
|
|
||||||
]
|
|
||||||
}
|
|
@ -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')
|
|
||||||
})
|
|
@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
@ -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')
|
|
||||||
})
|
|
11
package.json
11
package.json
@ -49,6 +49,7 @@
|
|||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"err-code": "^1.1.2",
|
"err-code": "^1.1.2",
|
||||||
"hashlru": "^2.3.0",
|
"hashlru": "^2.3.0",
|
||||||
|
"interface-datastore": "^0.8.0",
|
||||||
"it-all": "^1.0.1",
|
"it-all": "^1.0.1",
|
||||||
"it-buffer": "^0.1.1",
|
"it-buffer": "^0.1.1",
|
||||||
"it-handshake": "^1.0.1",
|
"it-handshake": "^1.0.1",
|
||||||
@ -64,6 +65,7 @@
|
|||||||
"multiaddr": "^7.2.1",
|
"multiaddr": "^7.2.1",
|
||||||
"multistream-select": "^0.15.0",
|
"multistream-select": "^0.15.0",
|
||||||
"mutable-proxy": "^1.0.0",
|
"mutable-proxy": "^1.0.0",
|
||||||
|
"node-forge": "^0.9.1",
|
||||||
"p-any": "^2.1.0",
|
"p-any": "^2.1.0",
|
||||||
"p-fifo": "^1.0.0",
|
"p-fifo": "^1.0.0",
|
||||||
"p-settle": "^3.1.0",
|
"p-settle": "^3.1.0",
|
||||||
@ -71,6 +73,7 @@
|
|||||||
"peer-info": "^0.17.0",
|
"peer-info": "^0.17.0",
|
||||||
"protons": "^1.0.1",
|
"protons": "^1.0.1",
|
||||||
"retimer": "^2.0.0",
|
"retimer": "^2.0.0",
|
||||||
|
"sanitize-filename": "^1.6.3",
|
||||||
"timeout-abort-controller": "^1.0.0",
|
"timeout-abort-controller": "^1.0.0",
|
||||||
"xsalsa20": "^1.0.2"
|
"xsalsa20": "^1.0.2"
|
||||||
},
|
},
|
||||||
@ -80,12 +83,17 @@
|
|||||||
"aegir": "^20.5.1",
|
"aegir": "^20.5.1",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
|
"chai-string": "^1.5.0",
|
||||||
"cids": "^0.7.1",
|
"cids": "^0.7.1",
|
||||||
|
"datastore-fs": "^0.9.1",
|
||||||
|
"datastore-level": "^0.14.0",
|
||||||
"delay": "^4.3.0",
|
"delay": "^4.3.0",
|
||||||
"dirty-chai": "^2.0.1",
|
"dirty-chai": "^2.0.1",
|
||||||
|
"is-browser": "^2.1.0",
|
||||||
"it-concat": "^1.0.0",
|
"it-concat": "^1.0.0",
|
||||||
"it-pair": "^1.0.0",
|
"it-pair": "^1.0.0",
|
||||||
"it-pushable": "^1.4.0",
|
"it-pushable": "^1.4.0",
|
||||||
|
"level": "^6.0.0",
|
||||||
"libp2p-bootstrap": "^0.10.3",
|
"libp2p-bootstrap": "^0.10.3",
|
||||||
"libp2p-delegated-content-routing": "^0.4.1",
|
"libp2p-delegated-content-routing": "^0.4.1",
|
||||||
"libp2p-delegated-peer-routing": "^0.4.0",
|
"libp2p-delegated-peer-routing": "^0.4.0",
|
||||||
@ -98,10 +106,13 @@
|
|||||||
"libp2p-tcp": "^0.14.1",
|
"libp2p-tcp": "^0.14.1",
|
||||||
"libp2p-webrtc-star": "^0.17.0",
|
"libp2p-webrtc-star": "^0.17.0",
|
||||||
"libp2p-websockets": "^0.13.1",
|
"libp2p-websockets": "^0.13.1",
|
||||||
|
"multihashes": "^0.4.15",
|
||||||
"nock": "^10.0.6",
|
"nock": "^10.0.6",
|
||||||
"p-defer": "^3.0.0",
|
"p-defer": "^3.0.0",
|
||||||
"p-times": "^2.1.0",
|
"p-times": "^2.1.0",
|
||||||
"p-wait-for": "^3.1.0",
|
"p-wait-for": "^3.1.0",
|
||||||
|
"promisify-es6": "^1.0.3",
|
||||||
|
"rimraf": "^3.0.0",
|
||||||
"sinon": "^8.1.0",
|
"sinon": "^8.1.0",
|
||||||
"streaming-iterables": "^4.1.0",
|
"streaming-iterables": "^4.1.0",
|
||||||
"wrtc": "^0.4.1"
|
"wrtc": "^0.4.1"
|
||||||
|
@ -28,12 +28,6 @@
|
|||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install --save libp2p-keychain
|
|
||||||
```
|
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
```js
|
```js
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@ -7,24 +7,33 @@ const dirtyChai = require('dirty-chai')
|
|||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
chai.use(dirtyChai)
|
chai.use(dirtyChai)
|
||||||
chai.use(require('chai-string'))
|
chai.use(require('chai-string'))
|
||||||
const Keychain = require('..')
|
|
||||||
|
|
||||||
module.exports = (datastore) => {
|
const os = require('os')
|
||||||
describe('cms interop', () => {
|
const path = require('path')
|
||||||
const passPhrase = 'this is not a secure phrase'
|
const isBrowser = require('is-browser')
|
||||||
const aliceKeyName = 'cms-interop-alice'
|
const FsStore = require('datastore-fs')
|
||||||
let ks
|
const LevelStore = require('datastore-level')
|
||||||
|
|
||||||
before(() => {
|
const Keychain = require('../../src/keychain')
|
||||||
ks = new Keychain(datastore, { passPhrase: passPhrase })
|
|
||||||
})
|
|
||||||
|
|
||||||
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 () {
|
before(() => {
|
||||||
this.timeout(10 * 1000)
|
const datastore = isBrowser
|
||||||
const aliceKid = 'QmNzBqPwp42HZJccsLtc4ok6LjZAspckgs2du5tTmjPfFA'
|
? new LevelStore('test-keystore-1', { db: require('level') })
|
||||||
const alice = `-----BEGIN ENCRYPTED PRIVATE KEY-----
|
: 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
|
MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIMhYqiVoLJMICAggA
|
||||||
MBQGCCqGSIb3DQMHBAhU7J9bcJPLDQSCAoDzi0dP6z97wJBs3jK2hDvZYdoScknG
|
MBQGCCqGSIb3DQMHBAhU7J9bcJPLDQSCAoDzi0dP6z97wJBs3jK2hDvZYdoScknG
|
||||||
QMPOnpG1LO3IZ7nFha1dta5liWX+xRFV04nmVYkkNTJAPS0xjJOG9B5Hm7wm8uTd
|
QMPOnpG1LO3IZ7nFha1dta5liWX+xRFV04nmVYkkNTJAPS0xjJOG9B5Hm7wm8uTd
|
||||||
@ -42,13 +51,13 @@ igg5jozKCW82JsuWSiW9tu0F/6DuvYiZwHS3OLiJP0CuLfbOaRw8Jia1RTvXEH7m
|
|||||||
cn4oisOvxCprs4aM9UVjtZTCjfyNpX8UWwT1W3rySV+KQNhxuMy3RzmL
|
cn4oisOvxCprs4aM9UVjtZTCjfyNpX8UWwT1W3rySV+KQNhxuMy3RzmL
|
||||||
-----END ENCRYPTED PRIVATE KEY-----
|
-----END ENCRYPTED PRIVATE KEY-----
|
||||||
`
|
`
|
||||||
const key = await ks.importKey(aliceKeyName, alice, 'mypassword')
|
const key = await ks.importKey(aliceKeyName, alice, 'mypassword')
|
||||||
expect(key.name).to.equal(aliceKeyName)
|
expect(key.name).to.equal(aliceKeyName)
|
||||||
expect(key.id).to.equal(aliceKid)
|
expect(key.id).to.equal(aliceKid)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('decrypts node-forge example', async () => {
|
it('decrypts node-forge example', async () => {
|
||||||
const example = `
|
const example = `
|
||||||
MIIBcwYJKoZIhvcNAQcDoIIBZDCCAWACAQAxgfowgfcCAQAwYDBbMQ0wCwYDVQQK
|
MIIBcwYJKoZIhvcNAQcDoIIBZDCCAWACAQAxgfowgfcCAQAwYDBbMQ0wCwYDVQQK
|
||||||
EwRpcGZzMREwDwYDVQQLEwhrZXlzdG9yZTE3MDUGA1UEAxMuUW1OekJxUHdwNDJI
|
EwRpcGZzMREwDwYDVQQLEwhrZXlzdG9yZTE3MDUGA1UEAxMuUW1OekJxUHdwNDJI
|
||||||
WkpjY3NMdGM0b2s2TGpaQXNwY2tnczJkdTV0VG1qUGZGQQIBATANBgkqhkiG9w0B
|
WkpjY3NMdGM0b2s2TGpaQXNwY2tnczJkdTV0VG1qUGZGQQIBATANBgkqhkiG9w0B
|
||||||
@ -58,9 +67,8 @@ knU1yykWGkdlbclCuu0NaAfmb8o0OX50CbEKZB7xmsv8tnqn0H0jMF4GCSqGSIb3
|
|||||||
DQEHATAdBglghkgBZQMEASoEEP/PW1JWehQx6/dsLkp/Mf+gMgQwFM9liLTqC56B
|
DQEHATAdBglghkgBZQMEASoEEP/PW1JWehQx6/dsLkp/Mf+gMgQwFM9liLTqC56B
|
||||||
nHILFmhac/+a/StQOKuf9dx5qXeGvt9LnwKuGGSfNX4g+dTkoa6N
|
nHILFmhac/+a/StQOKuf9dx5qXeGvt9LnwKuGGSfNX4g+dTkoa6N
|
||||||
`
|
`
|
||||||
const plain = await ks.cms.decrypt(Buffer.from(example, 'base64'))
|
const plain = await ks.cms.decrypt(Buffer.from(example, 'base64'))
|
||||||
expect(plain).to.exist()
|
expect(plain).to.exist()
|
||||||
expect(plain.toString()).to.equal(plainData.toString())
|
expect(plain.toString()).to.equal(plainData.toString())
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
})
|
394
test/keychain/keychain.spec.js
Normal file
394
test/keychain/keychain.spec.js
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user