2017-12-06 22:56:09 +13:00
|
|
|
'use strict'
|
|
|
|
|
2017-12-07 00:16:38 +13:00
|
|
|
const sanitize = require('sanitize-filename')
|
2017-12-06 22:56:09 +13:00
|
|
|
const forge = require('node-forge')
|
|
|
|
const deepmerge = require('deepmerge')
|
2017-12-07 00:16:38 +13:00
|
|
|
const crypto = require('libp2p-crypto')
|
2017-12-06 22:56:09 +13:00
|
|
|
const util = require('./util')
|
|
|
|
const CMS = require('./cms')
|
|
|
|
const DS = require('interface-datastore')
|
|
|
|
const pull = require('pull-stream')
|
|
|
|
|
2017-12-11 14:25:54 +13:00
|
|
|
const keyPrefix = '/pkcs8/'
|
|
|
|
const infoPrefix = '/info/'
|
2017-12-06 22:56:09 +13:00
|
|
|
|
|
|
|
// NIST SP 800-132
|
|
|
|
const NIST = {
|
|
|
|
minKeyLength: 112 / 8,
|
|
|
|
minSaltLength: 128 / 8,
|
|
|
|
minIterationCount: 1000
|
|
|
|
}
|
|
|
|
|
2017-12-10 17:19:20 +13:00
|
|
|
/**
|
|
|
|
* Maps an IPFS hash name to its forge equivalent.
|
|
|
|
*
|
|
|
|
* See https://github.com/multiformats/multihash/blob/master/hashtable.csv
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
const hashName2Forge = {
|
2017-12-10 17:37:16 +13:00
|
|
|
sha1: 'sha1',
|
2017-12-10 17:19:20 +13:00
|
|
|
'sha2-256': 'sha256',
|
2017-12-10 17:21:26 +13:00
|
|
|
'sha2-512': 'sha512'
|
2017-12-10 17:19:20 +13:00
|
|
|
}
|
2017-12-10 17:21:26 +13:00
|
|
|
|
2017-12-06 22:56:09 +13:00
|
|
|
const defaultOptions = {
|
|
|
|
// See https://cryptosense.com/parametesr-choice-for-pbkdf2/
|
|
|
|
dek: {
|
|
|
|
keyLength: 512 / 8,
|
|
|
|
iterationCount: 10000,
|
|
|
|
salt: 'you should override this value with a crypto secure random number',
|
2017-12-10 17:19:20 +13:00
|
|
|
hash: 'sha2-512'
|
2017-12-06 22:56:09 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function validateKeyName (name) {
|
|
|
|
if (!name) return false
|
|
|
|
return name === sanitize(name.trim())
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an error to the caller, after a delay
|
|
|
|
*
|
|
|
|
* This assumes than an error indicates that the keychain is under attack. Delay returning an
|
|
|
|
* error to make brute force attacks harder.
|
|
|
|
*
|
|
|
|
* @param {function(Error)} callback - The caller
|
|
|
|
* @param {string | Error} err - The error
|
2017-12-07 00:16:38 +13:00
|
|
|
* @returns {undefined}
|
2017-12-09 20:37:00 +13:00
|
|
|
* @private
|
2017-12-06 22:56:09 +13:00
|
|
|
*/
|
2017-12-07 00:16:38 +13:00
|
|
|
function _error (callback, err) {
|
2017-12-06 22:56:09 +13:00
|
|
|
const min = 200
|
|
|
|
const max = 1000
|
|
|
|
const delay = Math.random() * (max - min) + min
|
|
|
|
if (typeof err === 'string') err = new Error(err)
|
|
|
|
setTimeout(callback, delay, err, null)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts a key name into a datastore name.
|
2017-12-07 00:16:38 +13:00
|
|
|
*
|
|
|
|
* @param {string} name
|
|
|
|
* @returns {DS.Key}
|
2017-12-09 20:37:00 +13:00
|
|
|
* @private
|
2017-12-06 22:56:09 +13:00
|
|
|
*/
|
|
|
|
function DsName (name) {
|
2017-12-11 14:25:54 +13:00
|
|
|
return new DS.Key(keyPrefix + name)
|
2017-12-06 22:56:09 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-12-11 14:25:54 +13:00
|
|
|
* Converts a key name into a datastore info name.
|
2017-12-07 00:16:38 +13:00
|
|
|
*
|
2017-12-11 14:25:54 +13:00
|
|
|
* @param {string} name
|
|
|
|
* @returns {DS.Key}
|
2017-12-09 20:37:00 +13:00
|
|
|
* @private
|
2017-12-06 22:56:09 +13:00
|
|
|
*/
|
2017-12-11 14:25:54 +13:00
|
|
|
function DsInfoName (name) {
|
|
|
|
return new DS.Key(infoPrefix + name)
|
2017-12-06 22:56:09 +13:00
|
|
|
}
|
|
|
|
|
2017-12-09 20:37:00 +13:00
|
|
|
/**
|
|
|
|
* Information about a key.
|
|
|
|
*
|
|
|
|
* @typedef {Object} KeyInfo
|
|
|
|
*
|
|
|
|
* @property {string} id - The universally unique key id.
|
|
|
|
* @property {string} name - The local key name.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2017-12-11 14:25:54 +13:00
|
|
|
* Manages the lifecycle of a key. Keys are encrypted at rest using PKCS #8.
|
|
|
|
*
|
|
|
|
* A key in the store has two entries
|
|
|
|
* - '/info/key-name', contains the KeyInfo for the key
|
|
|
|
* - '/pkcs8/key-name', contains the PKCS #8 for the key
|
|
|
|
*
|
2017-12-09 20:37:00 +13:00
|
|
|
*/
|
2017-12-06 22:56:09 +13:00
|
|
|
class Keychain {
|
2017-12-09 20:37:00 +13:00
|
|
|
/**
|
|
|
|
* Creates a new instance of a key chain.
|
|
|
|
*
|
|
|
|
* @param {DS} store - where the key are.
|
|
|
|
* @param {object} options - ???
|
|
|
|
*/
|
2017-12-06 22:56:09 +13:00
|
|
|
constructor (store, options) {
|
|
|
|
if (!store) {
|
|
|
|
throw new Error('store is required')
|
|
|
|
}
|
|
|
|
this.store = store
|
|
|
|
|
|
|
|
const opts = deepmerge(defaultOptions, options)
|
|
|
|
|
|
|
|
// Enforce NIST SP 800-132
|
|
|
|
if (!opts.passPhrase || opts.passPhrase.length < 20) {
|
|
|
|
throw new Error('passPhrase must be least 20 characters')
|
|
|
|
}
|
|
|
|
if (opts.dek.keyLength < NIST.minKeyLength) {
|
|
|
|
throw new Error(`dek.keyLength must be least ${NIST.minKeyLength} bytes`)
|
|
|
|
}
|
|
|
|
if (opts.dek.salt.length < NIST.minSaltLength) {
|
|
|
|
throw new Error(`dek.saltLength must be least ${NIST.minSaltLength} bytes`)
|
|
|
|
}
|
|
|
|
if (opts.dek.iterationCount < NIST.minIterationCount) {
|
|
|
|
throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`)
|
|
|
|
}
|
|
|
|
this.dek = opts.dek
|
|
|
|
|
2017-12-10 17:19:20 +13:00
|
|
|
// Get the hashing alogorithm
|
|
|
|
const hashAlgorithm = hashName2Forge[opts.dek.hash]
|
2017-12-10 17:37:16 +13:00
|
|
|
if (!hashAlgorithm) {
|
2017-12-10 17:19:20 +13:00
|
|
|
throw new Error(`dek.hash '${opts.dek.hash}' is unknown or not supported`)
|
2017-12-10 17:37:16 +13:00
|
|
|
}
|
2017-12-10 17:19:20 +13:00
|
|
|
|
2017-12-06 22:56:09 +13:00
|
|
|
// Create the derived encrypting key
|
|
|
|
let dek = forge.pkcs5.pbkdf2(
|
|
|
|
opts.passPhrase,
|
|
|
|
opts.dek.salt,
|
|
|
|
opts.dek.iterationCount,
|
|
|
|
opts.dek.keyLength,
|
2017-12-10 17:19:20 +13:00
|
|
|
hashAlgorithm)
|
2017-12-06 22:56:09 +13:00
|
|
|
dek = forge.util.bytesToHex(dek)
|
|
|
|
Object.defineProperty(this, '_', { value: () => dek })
|
|
|
|
|
|
|
|
// Provide access to protected messages
|
|
|
|
this.cms = new CMS(this)
|
|
|
|
}
|
|
|
|
|
2017-12-09 20:37:00 +13:00
|
|
|
/**
|
|
|
|
* The default options for a keychain.
|
|
|
|
*
|
|
|
|
* @returns {object}
|
|
|
|
*/
|
2017-12-07 00:16:38 +13:00
|
|
|
static get options () {
|
2017-12-06 22:56:09 +13:00
|
|
|
return defaultOptions
|
|
|
|
}
|
|
|
|
|
2017-12-09 20:37:00 +13:00
|
|
|
/**
|
|
|
|
* Create a new key.
|
|
|
|
*
|
|
|
|
* @param {string} name - The local key name; cannot already exist.
|
|
|
|
* @param {string} type - One of the key types; 'rsa'.
|
|
|
|
* @param {int} size - The key size in bits.
|
|
|
|
* @param {function(Error, KeyInfo)} callback
|
|
|
|
* @returns {undefined}
|
|
|
|
*/
|
2017-12-06 22:56:09 +13:00
|
|
|
createKey (name, type, size, callback) {
|
|
|
|
const self = this
|
|
|
|
|
|
|
|
if (!validateKeyName(name) || name === 'self') {
|
|
|
|
return _error(callback, `Invalid key name '${name}'`)
|
|
|
|
}
|
|
|
|
const dsname = DsName(name)
|
|
|
|
self.store.has(dsname, (err, exists) => {
|
2017-12-07 00:16:38 +13:00
|
|
|
if (err) return _error(callback, err)
|
2017-12-08 14:46:38 +13:00
|
|
|
if (exists) return _error(callback, `Key '${name}' already exists`)
|
2017-12-06 22:56:09 +13:00
|
|
|
|
|
|
|
switch (type.toLowerCase()) {
|
|
|
|
case 'rsa':
|
|
|
|
if (size < 2048) {
|
|
|
|
return _error(callback, `Invalid RSA key size ${size}`)
|
|
|
|
}
|
|
|
|
forge.pki.rsa.generateKeyPair({bits: size, workers: -1}, (err, keypair) => {
|
|
|
|
if (err) return _error(callback, err)
|
2017-12-11 14:25:54 +13:00
|
|
|
util.keyId(keypair.privateKey, (err, kid) => {
|
2017-12-06 22:56:09 +13:00
|
|
|
if (err) return _error(callback, err)
|
|
|
|
|
2017-12-11 14:25:54 +13:00
|
|
|
const pem = forge.pki.encryptRsaPrivateKey(keypair.privateKey, this._())
|
|
|
|
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)
|
|
|
|
})
|
2017-12-06 22:56:09 +13:00
|
|
|
})
|
|
|
|
})
|
2017-12-07 00:16:38 +13:00
|
|
|
break
|
2017-12-06 22:56:09 +13:00
|
|
|
|
|
|
|
default:
|
|
|
|
return _error(callback, `Invalid key type '${type}'`)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-12-09 20:37:00 +13:00
|
|
|
/**
|
|
|
|
* List all the keys.
|
|
|
|
*
|
|
|
|
* @param {function(Error, KeyInfo[])} callback
|
|
|
|
* @returns {undefined}
|
|
|
|
*/
|
2017-12-06 22:56:09 +13:00
|
|
|
listKeys (callback) {
|
|
|
|
const self = this
|
|
|
|
const query = {
|
2017-12-11 14:25:54 +13:00
|
|
|
prefix: infoPrefix
|
2017-12-06 22:56:09 +13:00
|
|
|
}
|
|
|
|
pull(
|
|
|
|
self.store.query(query),
|
|
|
|
pull.collect((err, res) => {
|
|
|
|
if (err) return _error(callback, err)
|
|
|
|
|
2017-12-11 14:25:54 +13:00
|
|
|
const info = res.map(r => JSON.parse(r.value))
|
|
|
|
callback(null, info)
|
2017-12-06 22:56:09 +13:00
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2017-12-09 20:37:00 +13:00
|
|
|
/**
|
2017-12-11 14:25:54 +13:00
|
|
|
* Find a key by it's id.
|
2017-12-09 20:37:00 +13:00
|
|
|
*
|
|
|
|
* @param {string} id - The universally unique key identifier.
|
|
|
|
* @param {function(Error, KeyInfo)} callback
|
|
|
|
* @returns {undefined}
|
|
|
|
*/
|
2017-12-06 22:56:09 +13:00
|
|
|
findKeyById (id, callback) {
|
|
|
|
this.listKeys((err, keys) => {
|
|
|
|
if (err) return _error(callback, err)
|
|
|
|
|
|
|
|
const key = keys.find((k) => k.id === id)
|
|
|
|
callback(null, key)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-12-11 14:25:54 +13:00
|
|
|
/**
|
|
|
|
* Find a key by it's name.
|
|
|
|
*
|
|
|
|
* @param {string} name - The local key name.
|
|
|
|
* @param {function(Error, KeyInfo)} callback
|
|
|
|
* @returns {undefined}
|
|
|
|
*/
|
|
|
|
findKeyByName (name, callback) {
|
|
|
|
if (!validateKeyName(name)) {
|
|
|
|
return _error(callback, `Invalid key name '${name}'`)
|
|
|
|
}
|
|
|
|
|
|
|
|
const dsname = DsInfoName(name)
|
|
|
|
this.store.get(dsname, (err, res) => {
|
|
|
|
if (err) {
|
|
|
|
return _error(callback, `Key '${name}' does not exist. ${err.message}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null, JSON.parse(res.toString()))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-12-09 20:37:00 +13:00
|
|
|
/**
|
|
|
|
* Remove an existing key.
|
|
|
|
*
|
|
|
|
* @param {string} name - The local key name; must already exist.
|
|
|
|
* @param {function(Error, KeyInfo)} callback
|
|
|
|
* @returns {undefined}
|
|
|
|
*/
|
2017-12-06 22:56:09 +13:00
|
|
|
removeKey (name, callback) {
|
|
|
|
const self = this
|
|
|
|
if (!validateKeyName(name) || name === 'self') {
|
|
|
|
return _error(callback, `Invalid key name '${name}'`)
|
|
|
|
}
|
|
|
|
const dsname = DsName(name)
|
2017-12-11 14:25:54 +13:00
|
|
|
self.findKeyByName(name, (err, keyinfo) => {
|
2017-12-07 00:16:38 +13:00
|
|
|
if (err) return _error(callback, err)
|
2017-12-11 14:25:54 +13:00
|
|
|
const batch = self.store.batch()
|
|
|
|
batch.delete(dsname)
|
|
|
|
batch.delete(DsInfoName(name))
|
|
|
|
batch.commit((err) => {
|
2017-12-08 14:45:02 +13:00
|
|
|
if (err) return _error(callback, err)
|
|
|
|
callback(null, keyinfo)
|
|
|
|
})
|
2017-12-06 22:56:09 +13:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-12-09 20:37:00 +13:00
|
|
|
/**
|
|
|
|
* Rename a key
|
|
|
|
*
|
|
|
|
* @param {string} oldName - The old local key name; must already exist.
|
|
|
|
* @param {string} newName - The new local key name; must not already exist.
|
|
|
|
* @param {function(Error, KeyInfo)} callback
|
|
|
|
* @returns {undefined}
|
|
|
|
*/
|
2017-12-07 00:16:38 +13:00
|
|
|
renameKey (oldName, newName, callback) {
|
2017-12-06 22:56:09 +13:00
|
|
|
const self = this
|
|
|
|
if (!validateKeyName(oldName) || oldName === 'self') {
|
|
|
|
return _error(callback, `Invalid old key name '${oldName}'`)
|
|
|
|
}
|
|
|
|
if (!validateKeyName(newName) || newName === 'self') {
|
|
|
|
return _error(callback, `Invalid new key name '${newName}'`)
|
|
|
|
}
|
|
|
|
const oldDsname = DsName(oldName)
|
|
|
|
const newDsname = DsName(newName)
|
2017-12-11 14:25:54 +13:00
|
|
|
const oldInfoName = DsInfoName(oldName)
|
|
|
|
const newInfoName = DsInfoName(newName)
|
2017-12-06 22:56:09 +13:00
|
|
|
this.store.get(oldDsname, (err, res) => {
|
|
|
|
if (err) {
|
|
|
|
return _error(callback, `Key '${oldName}' does not exist. ${err.message}`)
|
|
|
|
}
|
|
|
|
const pem = res.toString()
|
|
|
|
self.store.has(newDsname, (err, exists) => {
|
2017-12-07 00:16:38 +13:00
|
|
|
if (err) return _error(callback, err)
|
2017-12-08 14:46:38 +13:00
|
|
|
if (exists) return _error(callback, `Key '${newName}' already exists`)
|
2017-12-06 22:56:09 +13:00
|
|
|
|
2017-12-11 14:25:54 +13:00
|
|
|
self.store.get(oldInfoName, (err, res) => {
|
2017-12-06 22:56:09 +13:00
|
|
|
if (err) return _error(callback, err)
|
2017-12-11 14:25:54 +13:00
|
|
|
|
|
|
|
const keyInfo = JSON.parse(res.toString())
|
|
|
|
keyInfo.name = newName
|
|
|
|
const batch = self.store.batch()
|
|
|
|
batch.put(newDsname, pem)
|
|
|
|
batch.put(newInfoName, JSON.stringify(keyInfo))
|
|
|
|
batch.delete(oldDsname)
|
|
|
|
batch.delete(oldInfoName)
|
|
|
|
batch.commit((err) => {
|
|
|
|
if (err) return _error(callback, err)
|
|
|
|
callback(null, keyInfo)
|
|
|
|
})
|
2017-12-06 22:56:09 +13:00
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-12-09 20:37:00 +13:00
|
|
|
/**
|
|
|
|
* Export an existing key as a PEM encrypted PKCS #8 string
|
|
|
|
*
|
|
|
|
* @param {string} name - The local key name; must already exist.
|
|
|
|
* @param {string} password - The password
|
|
|
|
* @param {function(Error, string)} callback
|
|
|
|
* @returns {undefined}
|
|
|
|
*/
|
2017-12-06 22:56:09 +13:00
|
|
|
exportKey (name, password, callback) {
|
|
|
|
if (!validateKeyName(name)) {
|
|
|
|
return _error(callback, `Invalid key name '${name}'`)
|
|
|
|
}
|
|
|
|
if (!password) {
|
|
|
|
return _error(callback, 'Password is required')
|
|
|
|
}
|
|
|
|
|
|
|
|
const dsname = DsName(name)
|
|
|
|
this.store.get(dsname, (err, res) => {
|
|
|
|
if (err) {
|
|
|
|
return _error(callback, `Key '${name}' does not exist. ${err.message}`)
|
|
|
|
}
|
|
|
|
const pem = res.toString()
|
|
|
|
try {
|
|
|
|
const options = {
|
|
|
|
algorithm: 'aes256',
|
|
|
|
count: this.dek.iterationCount,
|
|
|
|
saltSize: NIST.minSaltLength,
|
|
|
|
prfAlgorithm: 'sha512'
|
|
|
|
}
|
|
|
|
const privateKey = forge.pki.decryptRsaPrivateKey(pem, this._())
|
|
|
|
const res = forge.pki.encryptRsaPrivateKey(privateKey, password, options)
|
|
|
|
return callback(null, res)
|
|
|
|
} catch (e) {
|
|
|
|
_error(callback, e)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-12-09 20:37:00 +13:00
|
|
|
/**
|
|
|
|
* Import a new key from a PEM encoded PKCS #8 string
|
|
|
|
*
|
|
|
|
* @param {string} name - The local key name; must not already exist.
|
|
|
|
* @param {string} pem - The PEM encoded PKCS #8 string
|
|
|
|
* @param {string} password - The password.
|
|
|
|
* @param {function(Error, KeyInfo)} callback
|
|
|
|
* @returns {undefined}
|
|
|
|
*/
|
2017-12-07 00:16:38 +13:00
|
|
|
importKey (name, pem, password, callback) {
|
2017-12-06 22:56:09 +13:00
|
|
|
const self = this
|
|
|
|
if (!validateKeyName(name) || name === 'self') {
|
|
|
|
return _error(callback, `Invalid key name '${name}'`)
|
|
|
|
}
|
|
|
|
if (!pem) {
|
|
|
|
return _error(callback, 'PEM encoded key is required')
|
|
|
|
}
|
|
|
|
const dsname = DsName(name)
|
|
|
|
self.store.has(dsname, (err, exists) => {
|
2017-12-07 00:16:38 +13:00
|
|
|
if (err) return _error(callback, err)
|
2017-12-08 14:46:38 +13:00
|
|
|
if (exists) return _error(callback, `Key '${name}' already exists`)
|
2017-12-06 22:56:09 +13:00
|
|
|
try {
|
|
|
|
const privateKey = forge.pki.decryptRsaPrivateKey(pem, password)
|
|
|
|
if (privateKey === null) {
|
|
|
|
return _error(callback, 'Cannot read the key, most likely the password is wrong')
|
|
|
|
}
|
2017-12-07 00:16:38 +13:00
|
|
|
const newpem = forge.pki.encryptRsaPrivateKey(privateKey, this._())
|
2017-12-11 14:25:54 +13:00
|
|
|
util.keyId(privateKey, (err, kid) => {
|
2017-12-07 00:16:38 +13:00
|
|
|
if (err) return _error(callback, err)
|
2017-12-06 22:56:09 +13:00
|
|
|
|
2017-12-11 14:25:54 +13:00
|
|
|
const keyInfo = {
|
|
|
|
name: name,
|
|
|
|
id: kid
|
|
|
|
}
|
|
|
|
const batch = self.store.batch()
|
|
|
|
batch.put(dsname, newpem)
|
|
|
|
batch.put(DsInfoName(name), JSON.stringify(keyInfo))
|
|
|
|
batch.commit((err) => {
|
|
|
|
if (err) return _error(callback, err)
|
|
|
|
|
|
|
|
callback(null, keyInfo)
|
|
|
|
})
|
2017-12-06 22:56:09 +13:00
|
|
|
})
|
|
|
|
} catch (err) {
|
|
|
|
_error(callback, err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
importPeer (name, peer, callback) {
|
|
|
|
const self = this
|
|
|
|
if (!validateKeyName(name)) {
|
|
|
|
return _error(callback, `Invalid key name '${name}'`)
|
|
|
|
}
|
|
|
|
if (!peer || !peer.privKey) {
|
2017-12-07 00:16:38 +13:00
|
|
|
return _error(callback, 'Peer.privKey is required')
|
2017-12-06 22:56:09 +13:00
|
|
|
}
|
|
|
|
const dsname = DsName(name)
|
|
|
|
self.store.has(dsname, (err, exists) => {
|
2017-12-07 00:16:38 +13:00
|
|
|
if (err) return _error(callback, err)
|
2017-12-08 14:46:38 +13:00
|
|
|
if (exists) return _error(callback, `Key '${name}' already exists`)
|
2017-12-06 22:56:09 +13:00
|
|
|
|
|
|
|
const privateKeyProtobuf = peer.marshalPrivKey()
|
2017-12-07 00:16:38 +13:00
|
|
|
crypto.keys.unmarshalPrivateKey(privateKeyProtobuf, (err, key) => {
|
|
|
|
if (err) return _error(callback, err)
|
2017-12-06 22:56:09 +13:00
|
|
|
try {
|
|
|
|
const der = key.marshal()
|
2017-12-07 00:16:38 +13:00
|
|
|
const buf = forge.util.createBuffer(der.toString('binary'))
|
2017-12-06 22:56:09 +13:00
|
|
|
const obj = forge.asn1.fromDer(buf)
|
|
|
|
const privateKey = forge.pki.privateKeyFromAsn1(obj)
|
|
|
|
if (privateKey === null) {
|
|
|
|
return _error(callback, 'Cannot read the peer private key')
|
|
|
|
}
|
2017-12-07 00:16:38 +13:00
|
|
|
const pem = forge.pki.encryptRsaPrivateKey(privateKey, this._())
|
2017-12-11 14:25:54 +13:00
|
|
|
util.keyId(privateKey, (err, kid) => {
|
2017-12-06 22:56:09 +13:00
|
|
|
if (err) return _error(callback, err)
|
|
|
|
|
2017-12-11 14:25:54 +13:00
|
|
|
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)
|
|
|
|
})
|
2017-12-06 22:56:09 +13:00
|
|
|
})
|
|
|
|
} catch (err) {
|
|
|
|
_error(callback, err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-12-09 20:37:00 +13:00
|
|
|
* Gets the private key as PEM encoded PKCS #8 string.
|
2017-12-06 22:56:09 +13:00
|
|
|
*
|
|
|
|
* @param {string} name
|
|
|
|
* @param {function(Error, string)} callback
|
2017-12-07 00:16:38 +13:00
|
|
|
* @returns {undefined}
|
2017-12-11 14:25:54 +13:00
|
|
|
* @private
|
2017-12-06 22:56:09 +13:00
|
|
|
*/
|
|
|
|
_getPrivateKey (name, callback) {
|
|
|
|
if (!validateKeyName(name)) {
|
|
|
|
return _error(callback, `Invalid key name '${name}'`)
|
|
|
|
}
|
|
|
|
this.store.get(DsName(name), (err, res) => {
|
|
|
|
if (err) {
|
|
|
|
return _error(callback, `Key '${name}' does not exist. ${err.message}`)
|
|
|
|
}
|
|
|
|
callback(null, res.toString())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = Keychain
|