refactor: use async/await instead of callbacks (#37)

BREAKING CHANGE: The api now uses async/await instead of callbacks.

Co-Authored-By: Vasco Santos <vasco.santos@moxy.studio>
This commit is contained in:
Jacob Heun 2019-08-16 13:12:47 +02:00 committed by Vasco Santos
parent 717112bdf8
commit dda315a9c8
12 changed files with 532 additions and 691 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ logs
*.log *.log
coverage coverage
.nyc_output
# Runtime data # Runtime data
pids pids

View File

@ -7,6 +7,7 @@ stages:
node_js: node_js:
- '10' - '10'
- '12'
os: os:
- linux - linux
@ -20,8 +21,7 @@ jobs:
include: include:
- stage: check - stage: check
script: script:
- npx aegir commitlint --travis - npx aegir dep-check
- npx aegir dep-check -- -i wrtc -i electron-webrtc
- npm run lint - npm run lint
- stage: test - stage: test
@ -29,14 +29,14 @@ jobs:
addons: addons:
chrome: stable chrome: stable
script: script:
- npx aegir test -t browser - npx aegir test -t browser -t webworker
- stage: test - stage: test
name: firefox name: firefox
addons: addons:
firefox: latest firefox: latest
script: script:
- npx aegir test -t browser -- --browsers FirefoxHeadless - npx aegir test -t browser -t webworker -- --browsers FirefoxHeadless
notifications: notifications:
email: false email: false

View File

@ -1,17 +1,13 @@
# js-libp2p-keychain # js-libp2p-keychain
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai)
[![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/) [![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/)
[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) [![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p)
[![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io) [![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io)
[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) [![](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-keychain.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-keychain)
[![Coverage Status](https://coveralls.io/repos/github/libp2p/js-libp2p-keychain/badge.svg?branch=master)](https://coveralls.io/github/libp2p/js-libp2p-keychain?branch=master) [![](https://img.shields.io/travis/libp2p/js-libp2p-keychain.svg?style=flat-square)](https://travis-ci.com/libp2p/js-libp2p-keychain)
[![Travis CI](https://travis-ci.org/libp2p/js-libp2p-keychain.svg?branch=master)](https://travis-ci.org/libp2p/js-libp2p-keychain)
[![Circle CI](https://circleci.com/gh/libp2p/js-libp2p-keychain.svg?style=svg)](https://circleci.com/gh/libp2p/js-libp2p-keychain)
[![Dependency Status](https://david-dm.org/libp2p/js-libp2p-keychain.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-keychain) [![Dependency Status](https://david-dm.org/libp2p/js-libp2p-keychain.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-keychain)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)
![](https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square)
![](https://img.shields.io/badge/Node.js-%3E%3D6.0.0-orange.svg?style=flat-square)
> A secure key chain for libp2p in JavaScript > A secure key chain for libp2p in JavaScript
@ -55,23 +51,23 @@ const keychain = new Keychain(datastore, opts)
Managing a key Managing a key
- `createKey (name, type, size, callback)` - `async createKey (name, type, size)`
- `renameKey (oldName, newName, callback)` - `async renameKey (oldName, newName)`
- `removeKey (name, callback)` - `async removeKey (name)`
- `exportKey (name, password, callback)` - `async exportKey (name, password)`
- `importKey (name, pem, password, callback)` - `async importKey (name, pem, password)`
- `importPeer (name, peer, callback)` - `async importPeer (name, peer)`
A naming service for a key A naming service for a key
- `listKeys (callback)` - `async listKeys ()`
- `findKeyById (id, callback)` - `async findKeyById (id)`
- `findKeyByName (name, callback)` - `async findKeyByName (name)`
Cryptographically protected messages Cryptographically protected messages
- `cms.encrypt (name, plain, callback)` - `async cms.encrypt (name, plain)`
- `cms.decrypt (cmsData, callback)` - `async cms.decrypt (cmsData)`
### KeyInfo ### KeyInfo
@ -116,11 +112,11 @@ CMS, aka [PKCS #7](https://en.wikipedia.org/wiki/PKCS) and [RFC 5652](https://to
## Contribute ## Contribute
Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/js-libp2p-crypto/issues)! Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/js-libp2p-keychain/issues)!
This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).
[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md) [![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)
## License ## License

View File

@ -7,21 +7,19 @@
"scripts": { "scripts": {
"lint": "aegir lint", "lint": "aegir lint",
"build": "aegir build", "build": "aegir build",
"coverage": "nyc --reporter=text --reporter=lcov npm run test:node",
"test": "aegir test -t node -t browser", "test": "aegir test -t node -t browser",
"test:node": "aegir test -t node", "test:node": "aegir test -t node",
"test:browser": "aegir test -t browser", "test:browser": "aegir test -t browser",
"release": "aegir release", "release": "aegir release",
"release-minor": "aegir release --type minor", "release-minor": "aegir release --type minor",
"release-major": "aegir release --type major", "release-major": "aegir release --type major"
"coverage": "aegir coverage",
"coverage-publish": "aegir coverage publish"
}, },
"pre-push": [ "pre-push": [
"lint", "lint"
"test"
], ],
"engines": { "engines": {
"node": ">=6.0.0", "node": ">=10.0.0",
"npm": ">=3.0.0" "npm": ">=3.0.0"
}, },
"repository": { "repository": {
@ -42,26 +40,24 @@
}, },
"homepage": "https://github.com/libp2p/js-libp2p-keychain#readme", "homepage": "https://github.com/libp2p/js-libp2p-keychain#readme",
"dependencies": { "dependencies": {
"async": "^2.6.2", "err-code": "^2.0.0",
"err-code": "^1.1.2", "interface-datastore": "^0.7.0",
"interface-datastore": "~0.6.0", "libp2p-crypto": "^0.17.0",
"libp2p-crypto": "~0.16.1",
"merge-options": "^1.0.1", "merge-options": "^1.0.1",
"node-forge": "~0.7.6", "node-forge": "^0.8.5",
"pull-stream": "^3.6.9",
"sanitize-filename": "^1.6.1" "sanitize-filename": "^1.6.1"
}, },
"devDependencies": { "devDependencies": {
"aegir": "^18.2.1", "aegir": "^20.0.0",
"chai": "^4.2.0", "chai": "^4.2.0",
"chai-string": "^1.5.0", "chai-string": "^1.5.0",
"datastore-fs": "~0.8.0", "datastore-fs": "^0.9.0",
"datastore-level": "~0.10.0", "datastore-level": "^0.12.1",
"dirty-chai": "^2.0.1", "dirty-chai": "^2.0.1",
"level-js": "^4.0.1", "level": "^5.0.1",
"mocha": "^5.2.0", "multihashes": "^0.4.15",
"multihashes": "~0.4.14", "peer-id": "^0.13.2",
"peer-id": "~0.12.2", "promisify-es6": "^1.0.3",
"rimraf": "^2.6.3" "rimraf": "^2.6.3"
}, },
"contributors": [ "contributors": [

View File

@ -1,13 +1,9 @@
'use strict' 'use strict'
const setImmediate = require('async/setImmediate')
const series = require('async/series')
const detect = require('async/detect')
const waterfall = require('async/waterfall')
require('node-forge/lib/pkcs7') require('node-forge/lib/pkcs7')
require('node-forge/lib/pbe') require('node-forge/lib/pbe')
const forge = require('node-forge/lib/forge') const forge = require('node-forge/lib/forge')
const util = require('./util') const { certificateForKey, findAsync } = require('./util')
const errcode = require('err-code') const errcode = require('err-code')
/** /**
@ -40,44 +36,27 @@ class CMS {
* *
* @param {string} name - The local key name. * @param {string} name - The local key name.
* @param {Buffer} plain - The data to encrypt. * @param {Buffer} plain - The data to encrypt.
* @param {function(Error, Buffer)} callback
* @returns {undefined} * @returns {undefined}
*/ */
encrypt (name, plain, callback) { async encrypt (name, plain) {
const self = this
const done = (err, result) => setImmediate(() => callback(err, result))
if (!Buffer.isBuffer(plain)) { if (!Buffer.isBuffer(plain)) {
return done(errcode(new Error('Plain data must be a Buffer'), 'ERR_INVALID_PARAMS')) throw errcode(new Error('Plain data must be a Buffer'), 'ERR_INVALID_PARAMS')
} }
series([ const key = await this.keychain.findKeyByName(name)
(cb) => self.keychain.findKeyByName(name, cb), const pem = await this.keychain._getPrivateKey(name)
(cb) => self.keychain._getPrivateKey(name, cb) const privateKey = forge.pki.decryptRsaPrivateKey(pem, this.keychain._())
], (err, results) => { const certificate = await certificateForKey(key, privateKey)
if (err) return done(err)
let key = results[0] // create a p7 enveloped message
let pem = results[1] const p7 = forge.pkcs7.createEnvelopedData()
try { p7.addRecipient(certificate)
const privateKey = forge.pki.decryptRsaPrivateKey(pem, self.keychain._()) p7.content = forge.util.createBuffer(plain)
util.certificateForKey(key, privateKey, (err, certificate) => { p7.encrypt()
if (err) return callback(err)
// create a p7 enveloped message // convert message to DER
const p7 = forge.pkcs7.createEnvelopedData() const der = forge.asn1.toDer(p7.toAsn1()).getBytes()
p7.addRecipient(certificate) return Buffer.from(der, 'binary')
p7.content = forge.util.createBuffer(plain)
p7.encrypt()
// convert message to DER
const der = forge.asn1.toDer(p7.toAsn1()).getBytes()
done(null, Buffer.from(der, 'binary'))
})
} catch (err) {
done(err)
}
})
} }
/** /**
@ -87,24 +66,20 @@ class CMS {
* exists, an Error is returned with the property 'missingKeys'. It is array of key ids. * exists, an Error is returned with the property 'missingKeys'. It is array of key ids.
* *
* @param {Buffer} cmsData - The CMS encrypted data to decrypt. * @param {Buffer} cmsData - The CMS encrypted data to decrypt.
* @param {function(Error, Buffer)} callback
* @returns {undefined} * @returns {undefined}
*/ */
decrypt (cmsData, callback) { async decrypt (cmsData) {
const done = (err, result) => setImmediate(() => callback(err, result))
if (!Buffer.isBuffer(cmsData)) { if (!Buffer.isBuffer(cmsData)) {
return done(errcode(new Error('CMS data is required'), 'ERR_INVALID_PARAMS')) throw errcode(new Error('CMS data is required'), 'ERR_INVALID_PARAMS')
} }
const self = this
let cms let cms
try { try {
const buf = forge.util.createBuffer(cmsData.toString('binary')) const buf = forge.util.createBuffer(cmsData.toString('binary'))
const obj = forge.asn1.fromDer(buf) const obj = forge.asn1.fromDer(buf)
cms = forge.pkcs7.messageFromAsn1(obj) cms = forge.pkcs7.messageFromAsn1(obj)
} catch (err) { } catch (err) {
return done(errcode(new Error('Invalid CMS: ' + err.message), 'ERR_INVALID_CMS')) throw errcode(new Error('Invalid CMS: ' + err.message), 'ERR_INVALID_CMS')
} }
// Find a recipient whose key we hold. We only deal with recipient certs // Find a recipient whose key we hold. We only deal with recipient certs
@ -118,31 +93,29 @@ class CMS {
keyId: r.issuer.find(a => a.shortName === 'CN').value keyId: r.issuer.find(a => a.shortName === 'CN').value
} }
}) })
detect(
recipients,
(r, cb) => self.keychain.findKeyById(r.keyId, (err, info) => cb(null, !err && info)),
(err, r) => {
if (err) return done(err)
if (!r) {
const missingKeys = recipients.map(r => r.keyId)
err = errcode(new Error('Decryption needs one of the key(s): ' + missingKeys.join(', ')), 'ERR_MISSING_KEYS', {
missingKeys
})
return done(err)
}
waterfall([ const r = await findAsync(recipients, async (recipient) => {
(cb) => self.keychain.findKeyById(r.keyId, cb), try {
(key, cb) => self.keychain._getPrivateKey(key.name, cb) const key = await this.keychain.findKeyById(recipient.keyId)
], (err, pem) => { if (key) return true
if (err) return done(err) } catch (err) {
return false
const privateKey = forge.pki.decryptRsaPrivateKey(pem, self.keychain._())
cms.decrypt(r.recipient, privateKey)
done(null, Buffer.from(cms.content.getBytes(), 'binary'))
})
} }
) return false
})
if (!r) {
const missingKeys = recipients.map(r => r.keyId)
throw errcode(new Error('Decryption needs one of the key(s): ' + missingKeys.join(', ')), 'ERR_MISSING_KEYS', {
missingKeys
})
}
const key = await this.keychain.findKeyById(r.keyId)
const pem = await this.keychain._getPrivateKey(key.name)
const privateKey = forge.pki.decryptRsaPrivateKey(pem, this.keychain._())
cms.decrypt(r.recipient, privateKey)
return Buffer.from(cms.content.getBytes(), 'binary')
} }
} }

View File

@ -5,8 +5,6 @@ const sanitize = require('sanitize-filename')
const mergeOptions = require('merge-options') const mergeOptions = require('merge-options')
const crypto = require('libp2p-crypto') const crypto = require('libp2p-crypto')
const DS = require('interface-datastore') const DS = require('interface-datastore')
const collect = require('pull-stream/sinks/collect')
const pull = require('pull-stream/pull')
const CMS = require('./cms') const CMS = require('./cms')
const errcode = require('err-code') const errcode = require('err-code')
@ -37,22 +35,21 @@ function validateKeyName (name) {
} }
/** /**
* Returns an error to the caller, after a delay * Throws an error after a delay
* *
* This assumes than an error indicates that the keychain is under attack. Delay returning an * This assumes than an error indicates that the keychain is under attack. Delay returning an
* error to make brute force attacks harder. * error to make brute force attacks harder.
* *
* @param {function(Error)} callback - The caller
* @param {string | Error} err - The error * @param {string | Error} err - The error
* @returns {undefined}
* @private * @private
*/ */
function _error (callback, err) { async function throwDelayed (err) {
const min = 200 const min = 200
const max = 1000 const max = 1000
const delay = Math.random() * (max - min) + min const delay = Math.random() * (max - min) + min
setTimeout(callback, delay, err, null) await new Promise(resolve => setTimeout(resolve, delay))
throw err
} }
/** /**
@ -175,146 +172,131 @@ class Keychain {
* @param {string} name - The local key name; cannot already exist. * @param {string} name - The local key name; cannot already exist.
* @param {string} type - One of the key types; 'rsa'. * @param {string} type - One of the key types; 'rsa'.
* @param {int} size - The key size in bits. * @param {int} size - The key size in bits.
* @param {function(Error, KeyInfo)} callback * @returns {KeyInfo}
* @returns {undefined}
*/ */
createKey (name, type, size, callback) { async createKey (name, type, size) {
const self = this const self = this
if (!validateKeyName(name) || name === 'self') { if (!validateKeyName(name) || name === 'self') {
return _error(callback, errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME')) return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
} }
if (typeof type !== 'string') { if (typeof type !== 'string') {
return _error(callback, errcode(new Error(`Invalid key type '${type}'`), 'ERR_INVALID_KEY_TYPE')) return throwDelayed(errcode(new Error(`Invalid key type '${type}'`), 'ERR_INVALID_KEY_TYPE'))
} }
if (!Number.isSafeInteger(size)) { if (!Number.isSafeInteger(size)) {
return _error(callback, errcode(new Error(`Invalid key size '${size}'`), 'ERR_INVALID_KEY_SIZE')) return throwDelayed(errcode(new Error(`Invalid key size '${size}'`), 'ERR_INVALID_KEY_SIZE'))
} }
const dsname = DsName(name) const dsname = DsName(name)
self.store.has(dsname, (err, exists) => { const exists = await self.store.has(dsname)
if (err) return _error(callback, err) if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
if (exists) return _error(callback, errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
switch (type.toLowerCase()) { switch (type.toLowerCase()) {
case 'rsa': case 'rsa':
if (size < 2048) { if (size < 2048) {
return _error(callback, errcode(new Error(`Invalid RSA key size ${size}`), 'ERR_INVALID_KEY_SIZE')) return throwDelayed(errcode(new Error(`Invalid RSA key size ${size}`), 'ERR_INVALID_KEY_SIZE'))
} }
break break
default: default:
break break
}
let keyInfo
try {
const keypair = await crypto.keys.generateKeyPair(type, size)
const kid = await keypair.id()
const pem = await keypair.export(this._())
keyInfo = {
name: name,
id: kid
} }
const batch = self.store.batch()
batch.put(dsname, pem)
batch.put(DsInfoName(name), JSON.stringify(keyInfo))
crypto.keys.generateKeyPair(type, size, (err, keypair) => { await batch.commit()
if (err) return _error(callback, err) } catch (err) {
keypair.id((err, kid) => { return throwDelayed(err)
if (err) return _error(callback, err) }
keypair.export(this._(), (err, pem) => {
if (err) return _error(callback, err)
const keyInfo = {
name: name,
id: kid
}
const batch = self.store.batch()
batch.put(dsname, pem)
batch.put(DsInfoName(name), JSON.stringify(keyInfo))
batch.commit((err) => {
if (err) return _error(callback, err)
callback(null, keyInfo) return keyInfo
})
})
})
})
})
} }
/** /**
* List all the keys. * List all the keys.
* *
* @param {function(Error, KeyInfo[])} callback * @returns {KeyInfo[]}
* @returns {undefined}
*/ */
listKeys (callback) { async listKeys () {
const self = this const self = this
const query = { const query = {
prefix: infoPrefix prefix: infoPrefix
} }
pull(
self.store.query(query),
collect((err, res) => {
if (err) return _error(callback, err)
const info = res.map(r => JSON.parse(r.value)) const info = []
callback(null, info) for await (const value of self.store.query(query)) {
}) info.push(JSON.parse(value.value))
) }
return info
} }
/** /**
* Find a key by it's id. * Find a key by it's id.
* *
* @param {string} id - The universally unique key identifier. * @param {string} id - The universally unique key identifier.
* @param {function(Error, KeyInfo)} callback * @returns {KeyInfo}
* @returns {undefined}
*/ */
findKeyById (id, callback) { async findKeyById (id) {
this.listKeys((err, keys) => { try {
if (err) return _error(callback, err) const keys = await this.listKeys()
return keys.find((k) => k.id === id)
const key = keys.find((k) => k.id === id) } catch (err) {
callback(null, key) return throwDelayed(err)
}) }
} }
/** /**
* Find a key by it's name. * Find a key by it's name.
* *
* @param {string} name - The local key name. * @param {string} name - The local key name.
* @param {function(Error, KeyInfo)} callback * @returns {KeyInfo}
* @returns {undefined}
*/ */
findKeyByName (name, callback) { async findKeyByName (name) {
if (!validateKeyName(name)) { if (!validateKeyName(name)) {
return _error(callback, errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME')) return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
} }
const dsname = DsInfoName(name) const dsname = DsInfoName(name)
this.store.get(dsname, (err, res) => { try {
if (err) { const res = await this.store.get(dsname)
return _error(callback, errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND')) return JSON.parse(res.toString())
} } catch (err) {
return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
callback(null, JSON.parse(res.toString())) }
})
} }
/** /**
* Remove an existing key. * Remove an existing key.
* *
* @param {string} name - The local key name; must already exist. * @param {string} name - The local key name; must already exist.
* @param {function(Error, KeyInfo)} callback * @returns {KeyInfo}
* @returns {undefined}
*/ */
removeKey (name, callback) { async removeKey (name) {
const self = this const self = this
if (!validateKeyName(name) || name === 'self') { if (!validateKeyName(name) || name === 'self') {
return _error(callback, errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME')) return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
} }
const dsname = DsName(name) const dsname = DsName(name)
self.findKeyByName(name, (err, keyinfo) => { const keyInfo = await self.findKeyByName(name)
if (err) return _error(callback, err) const batch = self.store.batch()
const batch = self.store.batch() batch.delete(dsname)
batch.delete(dsname) batch.delete(DsInfoName(name))
batch.delete(DsInfoName(name)) await batch.commit()
batch.commit((err) => { return keyInfo
if (err) return _error(callback, err)
callback(null, keyinfo)
})
})
} }
/** /**
@ -322,47 +304,41 @@ class Keychain {
* *
* @param {string} oldName - The old local key name; must already exist. * @param {string} oldName - The old local key name; must already exist.
* @param {string} newName - The new local key name; must not already exist. * @param {string} newName - The new local key name; must not already exist.
* @param {function(Error, KeyInfo)} callback * @returns {KeyInfo}
* @returns {undefined}
*/ */
renameKey (oldName, newName, callback) { async renameKey (oldName, newName) {
const self = this const self = this
if (!validateKeyName(oldName) || oldName === 'self') { if (!validateKeyName(oldName) || oldName === 'self') {
return _error(callback, errcode(new Error(`Invalid old key name '${oldName}'`), 'ERR_OLD_KEY_NAME_INVALID')) return throwDelayed(errcode(new Error(`Invalid old key name '${oldName}'`), 'ERR_OLD_KEY_NAME_INVALID'))
} }
if (!validateKeyName(newName) || newName === 'self') { if (!validateKeyName(newName) || newName === 'self') {
return _error(callback, errcode(new Error(`Invalid new key name '${newName}'`), 'ERR_NEW_KEY_NAME_INVALID')) return throwDelayed(errcode(new Error(`Invalid new key name '${newName}'`), 'ERR_NEW_KEY_NAME_INVALID'))
} }
const oldDsname = DsName(oldName) const oldDsname = DsName(oldName)
const newDsname = DsName(newName) const newDsname = DsName(newName)
const oldInfoName = DsInfoName(oldName) const oldInfoName = DsInfoName(oldName)
const newInfoName = DsInfoName(newName) const newInfoName = DsInfoName(newName)
this.store.get(oldDsname, (err, res) => {
if (err) { const exists = await self.store.has(newDsname)
return _error(callback, errcode(new Error(`Key '${oldName}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND')) if (exists) return throwDelayed(errcode(new Error(`Key '${newName}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
}
try {
let res = await this.store.get(oldDsname)
const pem = res.toString() const pem = res.toString()
self.store.has(newDsname, (err, exists) => { res = await self.store.get(oldInfoName)
if (err) return _error(callback, err)
if (exists) return _error(callback, errcode(new Error(`Key '${newName}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
self.store.get(oldInfoName, (err, res) => { const keyInfo = JSON.parse(res.toString())
if (err) return _error(callback, err) keyInfo.name = newName
const batch = self.store.batch()
const keyInfo = JSON.parse(res.toString()) batch.put(newDsname, pem)
keyInfo.name = newName batch.put(newInfoName, JSON.stringify(keyInfo))
const batch = self.store.batch() batch.delete(oldDsname)
batch.put(newDsname, pem) batch.delete(oldInfoName)
batch.put(newInfoName, JSON.stringify(keyInfo)) await batch.commit()
batch.delete(oldDsname) return keyInfo
batch.delete(oldInfoName) } catch (err) {
batch.commit((err) => { return throwDelayed(err)
if (err) return _error(callback, err) }
callback(null, keyInfo)
})
})
})
})
} }
/** /**
@ -370,28 +346,25 @@ class Keychain {
* *
* @param {string} name - The local key name; must already exist. * @param {string} name - The local key name; must already exist.
* @param {string} password - The password * @param {string} password - The password
* @param {function(Error, string)} callback * @returns {string}
* @returns {undefined}
*/ */
exportKey (name, password, callback) { async exportKey (name, password) {
if (!validateKeyName(name)) { if (!validateKeyName(name)) {
return _error(callback, errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME')) return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
} }
if (!password) { if (!password) {
return _error(callback, errcode(new Error('Password is required'), 'ERR_PASSWORD_REQUIRED')) return throwDelayed(errcode(new Error('Password is required'), 'ERR_PASSWORD_REQUIRED'))
} }
const dsname = DsName(name) const dsname = DsName(name)
this.store.get(dsname, (err, res) => { try {
if (err) { const res = await this.store.get(dsname)
return _error(callback, errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
}
const pem = res.toString() const pem = res.toString()
crypto.keys.import(pem, this._(), (err, privateKey) => { const privateKey = await crypto.keys.import(pem, this._())
if (err) return _error(callback, err) return privateKey.export(password)
privateKey.export(password, callback) } catch (err) {
}) return throwDelayed(err)
}) }
} }
/** /**
@ -400,99 +373,97 @@ class Keychain {
* @param {string} name - The local key name; must not already exist. * @param {string} name - The local key name; must not already exist.
* @param {string} pem - The PEM encoded PKCS #8 string * @param {string} pem - The PEM encoded PKCS #8 string
* @param {string} password - The password. * @param {string} password - The password.
* @param {function(Error, KeyInfo)} callback * @returns {KeyInfo}
* @returns {undefined}
*/ */
importKey (name, pem, password, callback) { async importKey (name, pem, password) {
const self = this const self = this
if (!validateKeyName(name) || name === 'self') { if (!validateKeyName(name) || name === 'self') {
return _error(callback, errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME')) return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
} }
if (!pem) { if (!pem) {
return _error(callback, 'PEM encoded key is required') return throwDelayed(errcode(new Error('PEM encoded key is required'), 'ERR_PEM_REQUIRED'))
} }
const dsname = DsName(name) const dsname = DsName(name)
self.store.has(dsname, (err, exists) => { const exists = await self.store.has(dsname)
if (err) return _error(callback, err) if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
if (exists) return _error(callback, errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
crypto.keys.import(pem, password, (err, privateKey) => {
if (err) return _error(callback, errcode(new Error('Cannot read the key, most likely the password is wrong'), 'ERR_CANNOT_READ_KEY'))
privateKey.id((err, kid) => {
if (err) return _error(callback, err)
privateKey.export(this._(), (err, pem) => {
if (err) return _error(callback, err)
const keyInfo = {
name: name,
id: kid
}
const batch = self.store.batch()
batch.put(dsname, pem)
batch.put(DsInfoName(name), JSON.stringify(keyInfo))
batch.commit((err) => {
if (err) return _error(callback, err)
callback(null, keyInfo) let privateKey
}) try {
}) privateKey = await crypto.keys.import(pem, password)
}) } catch (err) {
}) return throwDelayed(errcode(new Error('Cannot read the key, most likely the password is wrong'), 'ERR_CANNOT_READ_KEY'))
}) }
let kid
try {
kid = await privateKey.id()
pem = await privateKey.export(this._())
} catch (err) {
return throwDelayed(err)
}
const keyInfo = {
name: name,
id: kid
}
const batch = self.store.batch()
batch.put(dsname, pem)
batch.put(DsInfoName(name), JSON.stringify(keyInfo))
await batch.commit()
return keyInfo
} }
importPeer (name, peer, callback) { async importPeer (name, peer) {
const self = this const self = this
if (!validateKeyName(name)) { if (!validateKeyName(name)) {
return _error(callback, errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME')) return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
} }
if (!peer || !peer.privKey) { if (!peer || !peer.privKey) {
return _error(callback, errcode(new Error('Peer.privKey is required'), 'ERR_MISSING_PRIVATE_KEY')) return throwDelayed(errcode(new Error('Peer.privKey is required'), 'ERR_MISSING_PRIVATE_KEY'))
} }
const privateKey = peer.privKey const privateKey = peer.privKey
const dsname = DsName(name) const dsname = DsName(name)
self.store.has(dsname, (err, exists) => { const exists = await self.store.has(dsname)
if (err) return _error(callback, err) if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
if (exists) return _error(callback, errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
privateKey.id((err, kid) => { try {
if (err) return _error(callback, err) const kid = await privateKey.id()
privateKey.export(this._(), (err, pem) => { const pem = await privateKey.export(this._())
if (err) return _error(callback, err) const keyInfo = {
const keyInfo = { name: name,
name: name, id: kid
id: kid }
} const batch = self.store.batch()
const batch = self.store.batch() batch.put(dsname, pem)
batch.put(dsname, pem) batch.put(DsInfoName(name), JSON.stringify(keyInfo))
batch.put(DsInfoName(name), JSON.stringify(keyInfo)) await batch.commit()
batch.commit((err) => { return keyInfo
if (err) return _error(callback, err) } catch (err) {
return throwDelayed(err)
callback(null, keyInfo) }
})
})
})
})
} }
/** /**
* Gets the private key as PEM encoded PKCS #8 string. * Gets the private key as PEM encoded PKCS #8 string.
* *
* @param {string} name * @param {string} name
* @param {function(Error, string)} callback * @returns {string}
* @returns {undefined}
* @private * @private
*/ */
_getPrivateKey (name, callback) { async _getPrivateKey (name) {
if (!validateKeyName(name)) { if (!validateKeyName(name)) {
return _error(callback, errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME')) return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
try {
const dsname = DsName(name)
const res = await this.store.get(dsname)
return res.toString()
} catch (err) {
return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
} }
this.store.get(DsName(name), (err, res) => {
if (err) {
return _error(callback, errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
}
callback(null, res.toString())
})
} }
} }

View File

@ -14,10 +14,9 @@ exports = module.exports
* *
* @param {KeyInfo} key - The id and name of the key * @param {KeyInfo} key - The id and name of the key
* @param {RsaPrivateKey} privateKey - The naked key * @param {RsaPrivateKey} privateKey - The naked key
* @param {function(Error, Certificate)} callback
* @returns {undefined} * @returns {undefined}
*/ */
exports.certificateForKey = (key, privateKey, callback) => { exports.certificateForKey = (key, privateKey) => {
const publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e) const publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e)
const cert = pki.createCertificate() const cert = pki.createCertificate()
cert.publicKey = publicKey cert.publicKey = publicKey
@ -67,5 +66,24 @@ exports.certificateForKey = (key, privateKey, callback) => {
// self-sign certificate // self-sign certificate
cert.sign(privateKey) cert.sign(privateKey)
return callback(null, cert) return cert
} }
/**
* Finds the first item in a collection that is matched in the
* `asyncCompare` function.
*
* `asyncCompare` is an async function that must
* resolve to either `true` or `false`.
*
* @param {Array} array
* @param {function(*)} asyncCompare An async function that returns a boolean
*/
async function findAsync (array, asyncCompare) {
const promises = array.map(asyncCompare)
const results = await Promise.all(promises)
const index = results.findIndex(result => result)
return array[index]
}
module.exports.findAsync = findAsync

View File

@ -1,25 +1,24 @@
/* eslint-env mocha */ /* eslint-env mocha */
'use strict' 'use strict'
const series = require('async/series')
const LevelStore = require('datastore-level') const LevelStore = require('datastore-level')
describe('browser', () => { describe('browser', () => {
const datastore1 = new LevelStore('test-keystore-1', { db: require('level-js') }) const datastore1 = new LevelStore('test-keystore-1', { db: require('level') })
const datastore2 = new LevelStore('test-keystore-2', { db: require('level-js') }) const datastore2 = new LevelStore('test-keystore-2', { db: require('level') })
before((done) => { before(() => {
series([ return Promise.all([
(cb) => datastore1.open(cb), datastore1.open(),
(cb) => datastore2.open(cb) datastore2.open()
], done) ])
}) })
after((done) => { after(() => {
series([ return Promise.all([
(cb) => datastore1.close(cb), datastore1.close(),
(cb) => datastore2.close(cb) datastore2.close()
], done) ])
}) })
require('./keychain.spec')(datastore1, datastore2) require('./keychain.spec')(datastore1, datastore2)

View File

@ -15,14 +15,13 @@ module.exports = (datastore) => {
const aliceKeyName = 'cms-interop-alice' const aliceKeyName = 'cms-interop-alice'
let ks let ks
before((done) => { before(() => {
ks = new Keychain(datastore, { passPhrase: passPhrase }) ks = new Keychain(datastore, { passPhrase: passPhrase })
done()
}) })
const plainData = Buffer.from('This is a message from Alice to Bob') const plainData = Buffer.from('This is a message from Alice to Bob')
it('imports openssl key', function (done) { it('imports openssl key', async function () {
this.timeout(10 * 1000) this.timeout(10 * 1000)
const aliceKid = 'QmNzBqPwp42HZJccsLtc4ok6LjZAspckgs2du5tTmjPfFA' const aliceKid = 'QmNzBqPwp42HZJccsLtc4ok6LjZAspckgs2du5tTmjPfFA'
const alice = `-----BEGIN ENCRYPTED PRIVATE KEY----- const alice = `-----BEGIN ENCRYPTED PRIVATE KEY-----
@ -43,15 +42,12 @@ igg5jozKCW82JsuWSiW9tu0F/6DuvYiZwHS3OLiJP0CuLfbOaRw8Jia1RTvXEH7m
cn4oisOvxCprs4aM9UVjtZTCjfyNpX8UWwT1W3rySV+KQNhxuMy3RzmL cn4oisOvxCprs4aM9UVjtZTCjfyNpX8UWwT1W3rySV+KQNhxuMy3RzmL
-----END ENCRYPTED PRIVATE KEY----- -----END ENCRYPTED PRIVATE KEY-----
` `
ks.importKey(aliceKeyName, alice, 'mypassword', (err, key) => { const key = await ks.importKey(aliceKeyName, alice, 'mypassword')
expect(err).to.not.exist() expect(key.name).to.equal(aliceKeyName)
expect(key.name).to.equal(aliceKeyName) expect(key.id).to.equal(aliceKid)
expect(key.id).to.equal(aliceKid)
done()
})
}) })
it('decrypts node-forge example', (done) => { it('decrypts node-forge example', async () => {
const example = ` const example = `
MIIBcwYJKoZIhvcNAQcDoIIBZDCCAWACAQAxgfowgfcCAQAwYDBbMQ0wCwYDVQQK MIIBcwYJKoZIhvcNAQcDoIIBZDCCAWACAQAxgfowgfcCAQAwYDBbMQ0wCwYDVQQK
EwRpcGZzMREwDwYDVQQLEwhrZXlzdG9yZTE3MDUGA1UEAxMuUW1OekJxUHdwNDJI EwRpcGZzMREwDwYDVQQLEwhrZXlzdG9yZTE3MDUGA1UEAxMuUW1OekJxUHdwNDJI
@ -62,12 +58,9 @@ knU1yykWGkdlbclCuu0NaAfmb8o0OX50CbEKZB7xmsv8tnqn0H0jMF4GCSqGSIb3
DQEHATAdBglghkgBZQMEASoEEP/PW1JWehQx6/dsLkp/Mf+gMgQwFM9liLTqC56B DQEHATAdBglghkgBZQMEASoEEP/PW1JWehQx6/dsLkp/Mf+gMgQwFM9liLTqC56B
nHILFmhac/+a/StQOKuf9dx5qXeGvt9LnwKuGGSfNX4g+dTkoa6N nHILFmhac/+a/StQOKuf9dx5qXeGvt9LnwKuGGSfNX4g+dTkoa6N
` `
ks.cms.decrypt(Buffer.from(example, 'base64'), (err, plain) => { const plain = await ks.cms.decrypt(Buffer.from(example, 'base64'))
expect(err).to.not.exist() expect(plain).to.exist()
expect(plain).to.exist() expect(plain.toString()).to.equal(plainData.toString())
expect(plain.toString()).to.equal(plainData.toString())
done()
})
}) })
}) })
} }

View File

@ -3,11 +3,11 @@
'use strict' 'use strict'
const chai = require('chai') const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect const expect = chai.expect
chai.use(dirtyChai) const fail = expect.fail
chai.use(require('dirty-chai'))
chai.use(require('chai-string')) chai.use(require('chai-string'))
const Keychain = require('..') const Keychain = require('../')
const PeerId = require('peer-id') const PeerId = require('peer-id')
module.exports = (datastore1, datastore2) => { module.exports = (datastore1, datastore2) => {
@ -55,148 +55,111 @@ module.exports = (datastore1, datastore2) => {
}) })
describe('key name', () => { describe('key name', () => {
it('is a valid filename and non-ASCII', () => { it('is a valid filename and non-ASCII', async () => {
ks.removeKey('../../nasty', (err) => { const errors = await Promise.all([
expect(err).to.exist() ks.removeKey('../../nasty').then(fail, err => err),
expect(err).to.have.property('message', 'Invalid key name \'../../nasty\'') ks.removeKey('').then(fail, err => err),
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME') ks.removeKey(' ').then(fail, err => err),
}) ks.removeKey(null).then(fail, err => err),
ks.removeKey('', (err) => { ks.removeKey(undefined).then(fail, err => err)
expect(err).to.exist() ])
expect(err).to.have.property('message', 'Invalid key name \'\'')
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME') expect(errors).to.have.length(5)
}) errors.forEach(error => {
ks.removeKey(' ', (err) => { expect(error).to.have.property('code', 'ERR_INVALID_KEY_NAME')
expect(err).to.exist()
expect(err).to.have.property('message', 'Invalid key name \' \'')
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
ks.removeKey(null, (err) => {
expect(err).to.exist()
expect(err).to.have.property('message', 'Invalid key name \'null\'')
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
})
ks.removeKey(undefined, (err) => {
expect(err).to.exist()
expect(err).to.have.property('message', 'Invalid key name \'undefined\'')
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
}) })
}) })
}) })
describe('key', () => { describe('key', () => {
it('can be an RSA key', function (done) { it('can be an RSA key', async () => {
this.timeout(50 * 1000) rsaKeyInfo = await ks.createKey(rsaKeyName, 'rsa', 2048)
ks.createKey(rsaKeyName, 'rsa', 2048, (err, info) => { expect(rsaKeyInfo).to.exist()
expect(err).to.not.exist()
expect(info).exist()
rsaKeyInfo = info
done()
})
})
it('has a name and id', () => {
expect(rsaKeyInfo).to.have.property('name', rsaKeyName) expect(rsaKeyInfo).to.have.property('name', rsaKeyName)
expect(rsaKeyInfo).to.have.property('id') expect(rsaKeyInfo).to.have.property('id')
}) })
it('is encrypted PEM encoded PKCS #8', (done) => { it('is encrypted PEM encoded PKCS #8', async () => {
ks._getPrivateKey(rsaKeyName, (err, pem) => { const pem = await ks._getPrivateKey(rsaKeyName)
expect(err).to.not.exist() return expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
done()
})
}) })
it('does not overwrite existing key', (done) => { it('throws if an invalid private key name is given', async () => {
ks.createKey(rsaKeyName, 'rsa', 2048, (err) => { const err = await ks._getPrivateKey(undefined).then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS') expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
done()
})
}) })
it('cannot create the "self" key', (done) => { it('throws if a private key cant be found', async () => {
ks.createKey('self', 'rsa', 2048, (err) => { const err = await ks._getPrivateKey('not real').then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
done() expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND')
})
}) })
it('should validate name is string', (done) => { it('does not overwrite existing key', async () => {
ks.createKey(5, 'rsa', 2048, (err) => { const err = await ks.createKey(rsaKeyName, 'rsa', 2048).then(fail, err => err)
expect(err).to.exist() expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
expect(err.message).to.contain('Invalid key name')
done()
})
}) })
it('should validate type is string', (done) => { it('cannot create the "self" key', async () => {
ks.createKey('TEST' + Date.now(), null, 2048, (err) => { const err = await ks.createKey('self', 'rsa', 2048).then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
expect(err.message).to.contain('Invalid key type') expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
done()
})
}) })
it('should validate size is integer', (done) => { it('should validate name is string', async () => {
ks.createKey('TEST' + Date.now(), 'rsa', 'string', (err) => { const err = await ks.createKey(5, 'rsa', 2048).then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
expect(err.message).to.contain('Invalid key size') expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
done() })
})
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', () => { describe('implements NIST SP 800-131A', () => {
it('disallows RSA length < 2048', (done) => { it('disallows RSA length < 2048', async () => {
ks.createKey('bad-nist-rsa', 'rsa', 1024, (err) => { const err = await ks.createKey('bad-nist-rsa', 'rsa', 1024).then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
expect(err).to.have.property('message', 'Invalid RSA key size 1024') expect(err).to.have.property('code', 'ERR_INVALID_KEY_SIZE')
expect(err).to.have.property('code', 'ERR_INVALID_KEY_SIZE')
done()
})
}) })
}) })
}) })
describe('query', () => { describe('query', () => {
it('finds all existing keys', (done) => { it('finds all existing keys', async () => {
ks.listKeys((err, keys) => { const keys = await ks.listKeys()
expect(err).to.not.exist() expect(keys).to.exist()
expect(keys).to.exist() const mykey = keys.find((k) => k.name.normalize() === rsaKeyName.normalize())
const mykey = keys.find((k) => k.name.normalize() === rsaKeyName.normalize()) expect(mykey).to.exist()
expect(mykey).to.exist()
done()
})
}) })
it('finds a key by name', (done) => { it('finds a key by name', async () => {
ks.findKeyByName(rsaKeyName, (err, key) => { const key = await ks.findKeyByName(rsaKeyName)
expect(err).to.not.exist() expect(key).to.exist()
expect(key).to.exist() expect(key).to.deep.equal(rsaKeyInfo)
expect(key).to.deep.equal(rsaKeyInfo)
done()
})
}) })
it('finds a key by id', (done) => { it('finds a key by id', async () => {
ks.findKeyById(rsaKeyInfo.id, (err, key) => { const key = await ks.findKeyById(rsaKeyInfo.id)
expect(err).to.not.exist() expect(key).to.exist()
expect(key).to.exist() expect(key).to.deep.equal(rsaKeyInfo)
expect(key).to.deep.equal(rsaKeyInfo)
done()
})
}) })
it('returns the key\'s name and id', (done) => { it('returns the key\'s name and id', async () => {
ks.listKeys((err, keys) => { const keys = await ks.listKeys()
expect(err).to.not.exist() expect(keys).to.exist()
expect(keys).to.exist() keys.forEach((key) => {
keys.forEach((key) => { expect(key).to.have.property('name')
expect(key).to.have.property('name') expect(key).to.have.property('id')
expect(key).to.have.property('id')
})
done()
}) })
}) })
}) })
@ -205,103 +168,97 @@ module.exports = (datastore1, datastore2) => {
const plainData = Buffer.from('This is a message from Alice to Bob') const plainData = Buffer.from('This is a message from Alice to Bob')
let cms let cms
it('service is available', (done) => { it('service is available', () => {
expect(ks).to.have.property('cms') expect(ks).to.have.property('cms')
done()
}) })
it('requires a key', (done) => { it('requires a key', async () => {
ks.cms.encrypt('no-key', plainData, (err, msg) => { const err = await ks.cms.encrypt('no-key', plainData).then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
done() expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND')
})
}) })
it('requires plain data as a Buffer', (done) => { it('requires plain data as a Buffer', async () => {
ks.cms.encrypt(rsaKeyName, 'plain data', (err, msg) => { const err = await ks.cms.encrypt(rsaKeyName, 'plain data').then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
done() expect(err).to.have.property('code', 'ERR_INVALID_PARAMS')
})
}) })
it('encrypts', (done) => { it('encrypts', async () => {
ks.cms.encrypt(rsaKeyName, plainData, (err, msg) => { cms = await ks.cms.encrypt(rsaKeyName, plainData)
expect(err).to.not.exist() expect(cms).to.exist()
expect(msg).to.exist() expect(cms).to.be.instanceOf(Buffer)
expect(msg).to.be.instanceOf(Buffer)
cms = msg
done()
})
}) })
it('is a PKCS #7 message', (done) => { it('is a PKCS #7 message', async () => {
ks.cms.decrypt('not CMS', (err) => { const err = await ks.cms.decrypt('not CMS').then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
done() expect(err).to.have.property('code', 'ERR_INVALID_PARAMS')
})
}) })
it('is a PKCS #7 binary message', (done) => { it('is a PKCS #7 binary message', async () => {
ks.cms.decrypt(plainData, (err) => { const err = await ks.cms.decrypt(plainData).then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
done() expect(err).to.have.property('code', 'ERR_INVALID_CMS')
})
}) })
it('cannot be read without the key', (done) => { it('cannot be read without the key', async () => {
emptyKeystore.cms.decrypt(cms, (err, plain) => { const err = await emptyKeystore.cms.decrypt(cms).then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
expect(err).to.have.property('missingKeys') expect(err).to.have.property('missingKeys')
expect(err.missingKeys).to.eql([rsaKeyInfo.id]) expect(err.missingKeys).to.eql([rsaKeyInfo.id])
expect(err).to.have.property('code', 'ERR_MISSING_KEYS') expect(err).to.have.property('code', 'ERR_MISSING_KEYS')
done()
})
}) })
it('can be read with the key', (done) => { it('can be read with the key', async () => {
ks.cms.decrypt(cms, (err, plain) => { const plain = await ks.cms.decrypt(cms)
expect(err).to.not.exist() expect(plain).to.exist()
expect(plain).to.exist() expect(plain.toString()).to.equal(plainData.toString())
expect(plain.toString()).to.equal(plainData.toString())
done()
})
}) })
}) })
describe('exported key', () => { describe('exported key', () => {
let pemKey let pemKey
it('is a PKCS #8 encrypted pem', (done) => { it('requires the password', async () => {
ks.exportKey(rsaKeyName, 'password', (err, pem) => { const err = await ks.exportKey(rsaKeyName).then(fail, err => err)
expect(err).to.not.exist() expect(err).to.exist()
expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----') expect(err).to.have.property('code', 'ERR_PASSWORD_REQUIRED')
pemKey = pem
done()
})
}) })
it('can be imported', (done) => { it('requires the key name', async () => {
ks.importKey('imported-key', pemKey, 'password', (err, key) => { const err = await ks.exportKey(undefined, 'password').then(fail, err => err)
expect(err).to.not.exist() expect(err).to.exist()
expect(key.name).to.equal('imported-key') expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
expect(key.id).to.equal(rsaKeyInfo.id)
done()
})
}) })
it('cannot be imported as an existing key name', (done) => { it('is a PKCS #8 encrypted pem', async () => {
ks.importKey(rsaKeyName, pemKey, 'password', (err, key) => { pemKey = await ks.exportKey(rsaKeyName, 'password')
expect(err).to.exist() expect(pemKey).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
done()
})
}) })
it('cannot be imported with the wrong password', function (done) { it('can be imported', async () => {
this.timeout(5 * 1000) const key = await ks.importKey('imported-key', pemKey, 'password')
ks.importKey('a-new-name-for-import', pemKey, 'not the password', (err, key) => { expect(key.name).to.equal('imported-key')
expect(err).to.exist() expect(key.id).to.equal(rsaKeyInfo.id)
done() })
})
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')
}) })
}) })
@ -309,136 +266,117 @@ module.exports = (datastore1, datastore2) => {
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==' 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 let alice
before(function (done) { before(async function () {
const encoded = Buffer.from(alicePrivKey, 'base64') const encoded = Buffer.from(alicePrivKey, 'base64')
PeerId.createFromPrivKey(encoded, (err, id) => { alice = await PeerId.createFromPrivKey(encoded)
expect(err).to.not.exist()
alice = id
done()
})
}) })
it('private key can be imported', (done) => { it('private key can be imported', async () => {
ks.importPeer('alice', alice, (err, key) => { const key = await ks.importPeer('alice', alice)
expect(err).to.not.exist() expect(key.name).to.equal('alice')
expect(key.name).to.equal('alice') expect(key.id).to.equal(alice.toB58String())
expect(key.id).to.equal(alice.toB58String())
done()
})
}) })
it('key id exists', (done) => { it('private key import requires a valid name', async () => {
ks.findKeyById(alice.toB58String(), (err, key) => { const err = await ks.importPeer(undefined, alice).then(fail, err => err)
expect(err).to.not.exist() expect(err).to.exist()
expect(key).to.exist() expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
expect(key).to.have.property('name', 'alice')
expect(key).to.have.property('id', alice.toB58String())
done()
})
}) })
it('key name exists', (done) => { it('private key import requires the peer', async () => {
ks.findKeyByName('alice', (err, key) => { const err = await ks.importPeer('alice').then(fail, err => err)
expect(err).to.not.exist() expect(err).to.exist()
expect(key).to.exist() expect(err).to.have.property('code', 'ERR_MISSING_PRIVATE_KEY')
expect(key).to.have.property('name', 'alice') })
expect(key).to.have.property('id', alice.toB58String())
done() 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', () => { describe('rename', () => {
it('requires an existing key name', (done) => { it('requires an existing key name', async () => {
ks.renameKey('not-there', renamedRsaKeyName, (err) => { const err = await ks.renameKey('not-there', renamedRsaKeyName).then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND') expect(err).to.have.property('code', 'ERR_NOT_FOUND')
done()
})
}) })
it('requires a valid new key name', (done) => { it('requires a valid new key name', async () => {
ks.renameKey(rsaKeyName, '..\not-valid', (err) => { const err = await ks.renameKey(rsaKeyName, '..\not-valid').then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_NEW_KEY_NAME_INVALID') expect(err).to.have.property('code', 'ERR_NEW_KEY_NAME_INVALID')
done()
})
}) })
it('does not overwrite existing key', (done) => { it('does not overwrite existing key', async () => {
ks.renameKey(rsaKeyName, rsaKeyName, (err) => { const err = await ks.renameKey(rsaKeyName, rsaKeyName).then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS') expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
done()
})
}) })
it('cannot create the "self" key', (done) => { it('cannot create the "self" key', async () => {
ks.renameKey(rsaKeyName, 'self', (err) => { const err = await ks.renameKey(rsaKeyName, 'self').then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_NEW_KEY_NAME_INVALID') expect(err).to.have.property('code', 'ERR_NEW_KEY_NAME_INVALID')
done()
})
}) })
it('removes the existing key name', (done) => { it('removes the existing key name', async () => {
ks.renameKey(rsaKeyName, renamedRsaKeyName, (err, key) => { const key = await ks.renameKey(rsaKeyName, renamedRsaKeyName)
expect(err).to.not.exist() expect(key).to.exist()
expect(key).to.exist() expect(key).to.have.property('name', renamedRsaKeyName)
expect(key).to.have.property('name', renamedRsaKeyName) expect(key).to.have.property('id', rsaKeyInfo.id)
expect(key).to.have.property('id', rsaKeyInfo.id) // Try to find the changed key
ks.findKeyByName(rsaKeyName, (err, key) => { const err = await ks.findKeyByName(rsaKeyName).then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
done()
})
})
}) })
it('creates the new key name', (done) => { it('creates the new key name', async () => {
ks.findKeyByName(renamedRsaKeyName, (err, key) => { const key = await ks.findKeyByName(renamedRsaKeyName)
expect(err).to.not.exist() expect(key).to.exist()
expect(key).to.exist() expect(key).to.have.property('name', renamedRsaKeyName)
expect(key).to.have.property('name', renamedRsaKeyName)
done()
})
}) })
it('does not change the key ID', (done) => { it('does not change the key ID', async () => {
ks.findKeyByName(renamedRsaKeyName, (err, key) => { const key = await ks.findKeyByName(renamedRsaKeyName)
expect(err).to.not.exist() expect(key).to.exist()
expect(key).to.exist() expect(key).to.have.property('name', renamedRsaKeyName)
expect(key).to.have.property('name', renamedRsaKeyName) expect(key).to.have.property('id', rsaKeyInfo.id)
expect(key).to.have.property('id', rsaKeyInfo.id) })
done()
}) 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', () => { describe('key removal', () => {
it('cannot remove the "self" key', (done) => { it('cannot remove the "self" key', async () => {
ks.removeKey('self', (err) => { const err = await ks.removeKey('self').then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME') expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
done()
})
}) })
it('cannot remove an unknown key', (done) => { it('cannot remove an unknown key', async () => {
ks.removeKey('not-there', (err) => { const err = await ks.removeKey('not-there').then(fail, err => err)
expect(err).to.exist() expect(err).to.exist()
expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND') expect(err).to.have.property('code', 'ERR_KEY_NOT_FOUND')
done()
})
}) })
it('can remove a known key', (done) => { it('can remove a known key', async () => {
ks.removeKey(renamedRsaKeyName, (err, key) => { const key = await ks.removeKey(renamedRsaKeyName)
expect(err).to.not.exist() expect(key).to.exist()
expect(key).to.exist() expect(key).to.have.property('name', renamedRsaKeyName)
expect(key).to.have.property('name', renamedRsaKeyName) expect(key).to.have.property('id', rsaKeyInfo.id)
expect(key).to.have.property('id', rsaKeyInfo.id)
done()
})
}) })
}) })
}) })

View File

@ -3,8 +3,8 @@
const os = require('os') const os = require('os')
const path = require('path') const path = require('path')
const rimraf = require('rimraf') const promisify = require('promisify-es6')
const series = require('async/series') const rimraf = promisify(require('rimraf'))
const FsStore = require('datastore-fs') const FsStore = require('datastore-fs')
describe('node', () => { describe('node', () => {
@ -13,20 +13,16 @@ describe('node', () => {
const datastore1 = new FsStore(store1) const datastore1 = new FsStore(store1)
const datastore2 = new FsStore(store2) const datastore2 = new FsStore(store2)
before((done) => { before(async () => {
series([ await datastore1.open()
(cb) => datastore1.open(cb), await datastore2.open()
(cb) => datastore2.open(cb)
], done)
}) })
after((done) => { after(async () => {
series([ await datastore1.close()
(cb) => datastore1.close(cb), await datastore2.close()
(cb) => datastore2.close(cb), await rimraf(store1)
(cb) => rimraf(store1, cb), await rimraf(store2)
(cb) => rimraf(store2, cb)
], done)
}) })
require('./keychain.spec')(datastore1, datastore2) require('./keychain.spec')(datastore1, datastore2)

View File

@ -21,55 +21,32 @@ describe('peer ID', () => {
let peer let peer
let publicKeyDer // a buffer let publicKeyDer // a buffer
before(function (done) { before(async () => {
const encoded = Buffer.from(sample.privKey, 'base64') const encoded = Buffer.from(sample.privKey, 'base64')
PeerId.createFromPrivKey(encoded, (err, id) => { peer = await PeerId.createFromPrivKey(encoded)
expect(err).to.not.exist()
peer = id
done()
})
}) })
it('decoded public key', (done) => { it('decoded public key', () => {
// console.log('peer id', peer.toJSON())
// console.log('id', peer.toB58String())
// console.log('id decoded', multihash.decode(peer.id))
// get protobuf version of the public key // get protobuf version of the public key
const publicKeyProtobuf = peer.marshalPubKey() const publicKeyProtobuf = peer.marshalPubKey()
const publicKey = crypto.keys.unmarshalPublicKey(publicKeyProtobuf) const publicKey = crypto.keys.unmarshalPublicKey(publicKeyProtobuf)
// console.log('public key', publicKey)
publicKeyDer = publicKey.marshal() publicKeyDer = publicKey.marshal()
// console.log('public key der', publicKeyDer.toString('base64'))
// get protobuf version of the private key // get protobuf version of the private key
const privateKeyProtobuf = peer.marshalPrivKey() const privateKeyProtobuf = peer.marshalPrivKey()
crypto.keys.unmarshalPrivateKey(privateKeyProtobuf, (err, key) => { const key = crypto.keys.unmarshalPrivateKey(privateKeyProtobuf)
expect(err).to.not.exist() expect(key).to.exist()
// console.log('private key', key)
// console.log('\nprivate key der', key.marshal().toString('base64'))
done()
})
}) })
it('encoded public key with DER', (done) => { it('encoded public key with DER', async () => {
const jwk = rsaUtils.pkixToJwk(publicKeyDer) const jwk = rsaUtils.pkixToJwk(publicKeyDer)
// console.log('jwk', jwk)
const rsa = new rsaClass.RsaPublicKey(jwk) const rsa = new rsaClass.RsaPublicKey(jwk)
// console.log('rsa', rsa) const keyId = await rsa.hash()
rsa.hash((err, keyId) => { const kids = multihash.toB58String(keyId)
expect(err).to.not.exist() expect(kids).to.equal(peer.toB58String())
// console.log('err', err)
// console.log('keyId', keyId)
// console.log('id decoded', multihash.decode(keyId))
const kids = multihash.toB58String(keyId)
// console.log('id', kids)
expect(kids).to.equal(peer.toB58String())
done()
})
}) })
it('encoded public key with JWT', (done) => { it('encoded public key with JWT', async () => {
const jwk = { const jwk = {
kty: 'RSA', kty: 'RSA',
n: 'tkiqPxzBWXgZpdQBd14o868a30F3Sc43jwWQG3caikdTHOo7kR14o-h12D45QJNNQYRdUty5eC8ItHAB4YIH-Oe7DIOeVFsnhinlL9LnILwqQcJUeXENNtItDIM4z1ji1qta7b0mzXAItmRFZ-vkNhHB6N8FL1kbS3is_g2UmX8NjxAwvgxjyT5e3_IO85eemMpppsx_ZYmSza84P6onaJFL-btaXRq3KS7jzXkzg5NHKigfjlG7io_RkoWBAghI2smyQ5fdu-qGpS_YIQbUnhL9tJLoGrU72MufdMBZSZJL8pfpz8SB9BBGDCivV0VpbvV2J6En26IsHL_DN0pbIw', n: 'tkiqPxzBWXgZpdQBd14o868a30F3Sc43jwWQG3caikdTHOo7kR14o-h12D45QJNNQYRdUty5eC8ItHAB4YIH-Oe7DIOeVFsnhinlL9LnILwqQcJUeXENNtItDIM4z1ji1qta7b0mzXAItmRFZ-vkNhHB6N8FL1kbS3is_g2UmX8NjxAwvgxjyT5e3_IO85eemMpppsx_ZYmSza84P6onaJFL-btaXRq3KS7jzXkzg5NHKigfjlG7io_RkoWBAghI2smyQ5fdu-qGpS_YIQbUnhL9tJLoGrU72MufdMBZSZJL8pfpz8SB9BBGDCivV0VpbvV2J6En26IsHL_DN0pbIw',
@ -77,33 +54,16 @@ describe('peer ID', () => {
alg: 'RS256', alg: 'RS256',
kid: '2011-04-29' kid: '2011-04-29'
} }
// console.log('jwk', jwk)
const rsa = new rsaClass.RsaPublicKey(jwk) const rsa = new rsaClass.RsaPublicKey(jwk)
// console.log('rsa', rsa) const keyId = await rsa.hash()
rsa.hash((err, keyId) => { const kids = multihash.toB58String(keyId)
expect(err).to.not.exist() expect(kids).to.equal(peer.toB58String())
// console.log('err', err)
// console.log('keyId', keyId)
// console.log('id decoded', multihash.decode(keyId))
const kids = multihash.toB58String(keyId)
// console.log('id', kids)
expect(kids).to.equal(peer.toB58String())
done()
})
}) })
it('decoded private key', (done) => { it('decoded private key', async () => {
// console.log('peer id', peer.toJSON())
// console.log('id', peer.toB58String())
// console.log('id decoded', multihash.decode(peer.id))
// get protobuf version of the private key // get protobuf version of the private key
const privateKeyProtobuf = peer.marshalPrivKey() const privateKeyProtobuf = peer.marshalPrivKey()
crypto.keys.unmarshalPrivateKey(privateKeyProtobuf, (err, key) => { const key = await crypto.keys.unmarshalPrivateKey(privateKeyProtobuf)
expect(err).to.not.exist() expect(key).to.exist()
// console.log('private key', key)
// console.log('\nprivate key der', key.marshal().toString('base64'))
done()
})
}) })
}) })