feat: use libp2p-crypto (#18)

* test: openssl interop is now the responsibility of libp2p-crypto

* feat: use libp2p-crypto, not node-forge, for key management

* fix: use libp2p-crypto.pbkdf, not node-forge

* fix: do not ship CMS

This removes all depencies on node-forge

* test: update dependencies

* test: remove dead code
This commit is contained in:
Richard Schneider 2017-12-21 02:43:54 +13:00 committed by David Dias
parent 605d290525
commit c1627a99e7
9 changed files with 90 additions and 522 deletions

View File

@ -85,15 +85,14 @@ The **key id** is the SHA-256 [multihash](https://github.com/multiformats/multih
A private key is stored as an encrypted PKCS 8 structure in the PEM format. It is protected by a key generated from the key chain's *passPhrase* using **PBKDF2**.
The default options for generating the derived encryption key are in the `dek` object
The default options for generating the derived encryption key are in the `dek` object. This, along with the passPhrase, is the input to a `PBKDF2` function.
```js
const defaultOptions = {
createIfNeeded: true,
//See https://cryptosense.com/parameter-choice-for-pbkdf2/
dek: {
keyLength: 512 / 8,
iterationCount: 10000,
iterationCount: 1000,
salt: 'at least 16 characters long',
hash: 'sha2-512'
}

View File

@ -45,22 +45,20 @@
"async": "^2.6.0",
"deepmerge": "^1.5.2",
"interface-datastore": "~0.4.1",
"libp2p-crypto": "~0.10.3",
"multihashes": "~0.4.12",
"node-forge": "~0.7.1",
"libp2p-crypto": "~0.11.0",
"pull-stream": "^3.6.1",
"sanitize-filename": "^1.6.1"
},
"devDependencies": {
"aegir": "^12.2.0",
"aegir": "^12.3.0",
"chai": "^4.1.2",
"chai-string": "^1.4.0",
"datastore-fs": "^0.4.1",
"datastore-level": "^0.7.0",
"datastore-fs": "~0.4.1",
"datastore-level": "~0.7.0",
"dirty-chai": "^2.0.1",
"level-js": "^2.2.4",
"mocha": "^4.0.1",
"peer-id": "^0.10.2",
"peer-id": "~0.10.4",
"pre-commit": "^1.2.2",
"rimraf": "^2.6.2"
}

View File

@ -1,96 +0,0 @@
'use strict'
const async = require('async')
const forge = require('node-forge')
const util = require('./util')
class CMS {
constructor (keystore) {
if (!keystore) {
throw new Error('keystore is required')
}
this.keystore = keystore
}
createAnonymousEncryptedData (name, plain, callback) {
const self = this
if (!Buffer.isBuffer(plain)) {
return callback(new Error('Data is required'))
}
self.keystore._getPrivateKey(name, (err, key) => {
if (err) {
return callback(err)
}
try {
const privateKey = forge.pki.decryptRsaPrivateKey(key, self.keystore._())
util.certificateForKey(privateKey, (err, certificate) => {
if (err) return callback(err)
// create a p7 enveloped message
const p7 = forge.pkcs7.createEnvelopedData()
p7.addRecipient(certificate)
p7.content = forge.util.createBuffer(plain)
p7.encrypt()
// convert message to DER
const der = forge.asn1.toDer(p7.toAsn1()).getBytes()
callback(null, Buffer.from(der, 'binary'))
})
} catch (err) {
callback(err)
}
})
}
readData (cmsData, callback) {
if (!Buffer.isBuffer(cmsData)) {
return callback(new Error('CMS data is required'))
}
const self = this
let cms
try {
const buf = forge.util.createBuffer(cmsData.toString('binary'))
const obj = forge.asn1.fromDer(buf)
cms = forge.pkcs7.messageFromAsn1(obj)
} catch (err) {
return callback(new Error('Invalid CMS: ' + err.message))
}
// Find a recipient whose key we hold. We only deal with recipient certs
// issued by ipfs (O=ipfs).
const recipients = cms.recipients
.filter(r => r.issuer.find(a => a.shortName === 'O' && a.value === 'ipfs'))
.filter(r => r.issuer.find(a => a.shortName === 'CN'))
.map(r => {
return {
recipient: r,
keyId: r.issuer.find(a => a.shortName === 'CN').value
}
})
async.detect(
recipients,
(r, cb) => self.keystore.findKeyById(r.keyId, (err, info) => cb(null, !err && info)),
(err, r) => {
if (err) return callback(err)
if (!r) return callback(new Error('No key found for decryption'))
async.waterfall([
(cb) => self.keystore.findKeyById(r.keyId, cb),
(key, cb) => self.keystore._getPrivateKey(key.name, cb)
], (err, pem) => {
if (err) return callback(err)
const privateKey = forge.pki.decryptRsaPrivateKey(pem, self.keystore._())
cms.decrypt(r.recipient, privateKey)
async.setImmediate(() => callback(null, Buffer.from(cms.content.getBytes(), 'binary')))
})
}
)
}
}
module.exports = CMS

View File

@ -1,11 +1,9 @@
/* eslint max-nested-callbacks: ["error", 5] */
'use strict'
const sanitize = require('sanitize-filename')
const forge = require('node-forge')
const deepmerge = require('deepmerge')
const crypto = require('libp2p-crypto')
const util = require('./util')
const CMS = require('./cms')
const DS = require('interface-datastore')
const pull = require('pull-stream')
@ -19,24 +17,11 @@ const NIST = {
minIterationCount: 1000
}
/**
* Maps an IPFS hash name to its forge equivalent.
*
* See https://github.com/multiformats/multihash/blob/master/hashtable.csv
*
* @private
*/
const hashName2Forge = {
sha1: 'sha1',
'sha2-256': 'sha256',
'sha2-512': 'sha512'
}
const defaultOptions = {
// See https://cryptosense.com/parametesr-choice-for-pbkdf2/
dek: {
keyLength: 512 / 8,
iterationCount: 10000,
iterationCount: 1000,
salt: 'you should override this value with a crypto secure random number',
hash: 'sha2-512'
}
@ -133,26 +118,15 @@ class Keychain {
if (opts.dek.iterationCount < NIST.minIterationCount) {
throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`)
}
this.dek = opts.dek
// Get the hashing alogorithm
const hashAlgorithm = hashName2Forge[opts.dek.hash]
if (!hashAlgorithm) {
throw new Error(`dek.hash '${opts.dek.hash}' is unknown or not supported`)
}
// Create the derived encrypting key
let dek = forge.pkcs5.pbkdf2(
const dek = crypto.pbkdf2(
opts.passPhrase,
opts.dek.salt,
opts.dek.iterationCount,
opts.dek.keyLength,
hashAlgorithm)
dek = forge.util.bytesToHex(dek)
opts.dek.hash)
Object.defineProperty(this, '_', { value: () => dek })
// Provide access to protected messages
this.cms = new CMS(this)
}
/**
@ -189,31 +163,32 @@ class Keychain {
if (size < 2048) {
return _error(callback, `Invalid RSA key size ${size}`)
}
forge.pki.rsa.generateKeyPair({bits: size, workers: -1}, (err, keypair) => {
break
default:
break
}
crypto.keys.generateKeyPair(type, size, (err, keypair) => {
if (err) return _error(callback, err)
keypair.id((err, kid) => {
if (err) return _error(callback, err)
keypair.export(this._(), (err, pem) => {
if (err) return _error(callback, err)
util.keyId(keypair.privateKey, (err, kid) => {
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)
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)
})
callback(null, keyInfo)
})
})
break
default:
return _error(callback, `Invalid key type '${type}'`)
}
})
})
})
}
@ -372,19 +347,10 @@ class Keychain {
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)
}
crypto.keys.import(pem, this._(), (err, privateKey) => {
if (err) return _error(callback, err)
privateKey.export(password, callback)
})
})
}
@ -409,62 +375,12 @@ class Keychain {
self.store.has(dsname, (err, exists) => {
if (err) return _error(callback, err)
if (exists) return _error(callback, `Key '${name}' already exists`)
try {
const privateKey = forge.pki.decryptRsaPrivateKey(pem, password)
if (privateKey === null) {
return _error(callback, 'Cannot read the key, most likely the password is wrong')
}
const newpem = forge.pki.encryptRsaPrivateKey(privateKey, this._())
util.keyId(privateKey, (err, kid) => {
crypto.keys.import(pem, password, (err, privateKey) => {
if (err) return _error(callback, 'Cannot read the key, most likely the password is wrong')
privateKey.id((err, kid) => {
if (err) return _error(callback, err)
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) => {
privateKey.export(this._(), (err, pem) => {
if (err) return _error(callback, err)
callback(null, keyInfo)
})
})
} 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) {
return _error(callback, 'Peer.privKey is required')
}
const dsname = DsName(name)
self.store.has(dsname, (err, exists) => {
if (err) return _error(callback, err)
if (exists) return _error(callback, `Key '${name}' already exists`)
const privateKeyProtobuf = peer.marshalPrivKey()
crypto.keys.unmarshalPrivateKey(privateKeyProtobuf, (err, key) => {
if (err) return _error(callback, err)
try {
const der = key.marshal()
const buf = forge.util.createBuffer(der.toString('binary'))
const obj = forge.asn1.fromDer(buf)
const privateKey = forge.pki.privateKeyFromAsn1(obj)
if (privateKey === null) {
return _error(callback, 'Cannot read the peer private key')
}
const pem = forge.pki.encryptRsaPrivateKey(privateKey, this._())
util.keyId(privateKey, (err, kid) => {
if (err) return _error(callback, err)
const keyInfo = {
name: name,
id: kid
@ -478,9 +394,43 @@ class Keychain {
callback(null, keyInfo)
})
})
} 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) {
return _error(callback, 'Peer.privKey is required')
}
const privateKey = peer.privKey
const dsname = DsName(name)
self.store.has(dsname, (err, exists) => {
if (err) return _error(callback, err)
if (exists) return _error(callback, `Key '${name}' already exists`)
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)
})
})
})
})
}

View File

@ -1,86 +0,0 @@
'use strict'
const forge = require('node-forge')
const pki = forge.pki
const multihash = require('multihashes')
const rsaUtils = require('libp2p-crypto/src/keys/rsa-utils')
const rsaClass = require('libp2p-crypto/src/keys/rsa-class')
exports = module.exports
// Create an IPFS key id; the SHA-256 multihash of a public key.
// See https://github.com/richardschneider/ipfs-encryption/issues/16
exports.keyId = (privateKey, callback) => {
try {
const publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e)
const spki = pki.publicKeyToSubjectPublicKeyInfo(publicKey)
const der = Buffer.from(forge.asn1.toDer(spki).getBytes(), 'binary')
const jwk = rsaUtils.pkixToJwk(der)
const rsa = new rsaClass.RsaPublicKey(jwk)
rsa.hash((err, kid) => {
if (err) return callback(err)
const kids = multihash.toB58String(kid)
return callback(null, kids)
})
} catch (err) {
callback(err)
}
}
exports.certificateForKey = (privateKey, callback) => {
exports.keyId(privateKey, (err, kid) => {
if (err) return callback(err)
const publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e)
const cert = pki.createCertificate()
cert.publicKey = publicKey
cert.serialNumber = '01'
cert.validity.notBefore = new Date()
cert.validity.notAfter = new Date()
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10)
var attrs = [{
name: 'organizationName',
value: 'ipfs'
}, {
shortName: 'OU',
value: 'keystore'
}, {
name: 'commonName',
value: kid
}]
cert.setSubject(attrs)
cert.setIssuer(attrs)
cert.setExtensions([{
name: 'basicConstraints',
cA: true
}, {
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true
}, {
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true,
emailProtection: true,
timeStamping: true
}, {
name: 'nsCertType',
client: true,
server: true,
email: true,
objsign: true,
sslCA: true,
emailCA: true,
objCA: true
}])
// self-sign certificate
cert.sign(privateKey)
return callback(null, cert)
})
}

View File

@ -23,6 +23,5 @@ describe('browser', () => {
})
require('./keychain.spec')(datastore1, datastore2)
require('./openssl')(datastore1)
require('./peerid')
})

View File

@ -16,11 +16,10 @@ module.exports = (datastore1, datastore2) => {
const rsaKeyName = 'tajné jméno'
const renamedRsaKeyName = 'ชื่อลับ'
let rsaKeyInfo
let emptyKeystore
// let emptyKeystore
let ks
before((done) => {
emptyKeystore = new Keychain(datastore1, { passPhrase: passPhrase })
ks = new Keychain(datastore2, { passPhrase: passPhrase })
done()
})
@ -163,56 +162,6 @@ module.exports = (datastore1, datastore2) => {
})
})
describe('CMS protected data', () => {
const plainData = Buffer.from('This is a message from Alice to Bob')
let cms
it('service is available', (done) => {
expect(ks).to.have.property('cms')
done()
})
it('is anonymous', (done) => {
ks.cms.createAnonymousEncryptedData(rsaKeyName, plainData, (err, msg) => {
expect(err).to.not.exist()
expect(msg).to.exist()
expect(msg).to.be.instanceOf(Buffer)
cms = msg
done()
})
})
it('is a PKCS #7 message', (done) => {
ks.cms.readData('not CMS', (err) => {
expect(err).to.exist()
done()
})
})
it('is a PKCS #7 binary message', (done) => {
ks.cms.readData(plainData, (err) => {
expect(err).to.exist()
done()
})
})
it('cannot be read without the key', (done) => {
emptyKeystore.cms.readData(cms, (err, plain) => {
expect(err).to.exist()
done()
})
})
it('can be read with the key', (done) => {
ks.cms.readData(cms, (err, plain) => {
expect(err).to.not.exist()
expect(plain).to.exist()
expect(plain.toString()).to.equal(plainData.toString())
done()
})
})
})
describe('exported key', () => {
let pemKey
@ -272,7 +221,17 @@ module.exports = (datastore1, datastore2) => {
})
})
it('key exists', (done) => {
it('key id exists', (done) => {
ks.findKeyById(alice.toB58String(), (err, key) => {
expect(err).to.not.exist()
expect(key).to.exist()
expect(key).to.have.property('name', 'alice')
expect(key).to.have.property('id', alice.toB58String())
done()
})
})
it('key name exists', (done) => {
ks.findKeyByName('alice', (err, key) => {
expect(err).to.not.exist()
expect(key).to.exist()

View File

@ -30,6 +30,5 @@ describe('node', () => {
})
require('./keychain.spec')(datastore1, datastore2)
require('./openssl')(datastore1)
require('./peerid')
})

View File

@ -1,154 +0,0 @@
/* eslint max-nested-callbacks: ["error", 8] */
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
const Keychain = require('..')
module.exports = (datastore1) => {
describe('interop with openssl', () => {
const passPhrase = 'this is not a secure phrase'
const keyName = 'openssl-key'
let ks
before((done) => {
ks = new Keychain(datastore1, { passPhrase: passPhrase })
done()
})
it('can read a private key', (done) => {
/*
* Generated with
* openssl genpkey -algorithm RSA
* -pkeyopt rsa_keygen_bits:3072
* -pkeyopt rsa_keygen_pubexp:65537
*/
const pem = `-----BEGIN PRIVATE KEY-----
MIIG/wIBADANBgkqhkiG9w0BAQEFAASCBukwggblAgEAAoIBgQDp0Whyqa8KmdvK
0MsQGJEBzDAEHAZc0C6cr0rkb6Xwo+yB5kjZBRDORk0UXtYGE1pYt4JhUTmMzcWO
v2xTIsdbVMQlNtput2U8kIqS1cSTkX5HxOJtCiIzntMzuR/bGPSOexkyFQ8nCUqb
ROS7cln/ixprra2KMAKldCApN3ue2jo/JI1gyoS8sekhOASAa0ufMPpC+f70sc75
Y53VLnGBNM43iM/2lsK+GI2a13d6rRy86CEM/ygnh/EDlyNDxo+SQmy6GmSv/lmR
xgWQE2dIfK504KIxFTOphPAQAr9AsmcNnCQLhbz7YTsBz8WcytHGQ0Z5pnBQJ9AV
CX9E6DFHetvs0CNLVw1iEO06QStzHulmNEI/3P8I1TIxViuESJxSu3pSNwG1bSJZ
+Qee24vvlz/slBzK5gZWHvdm46v7vl5z7SA+whncEtjrswd8vkJk9fI/YTUbgOC0
HWMdc2t/LTZDZ+LUSZ/b2n5trvdJSsOKTjEfuf0wICC08pUUk8MCAwEAAQKCAYEA
ywve+DQCneIezHGk5cVvp2/6ApeTruXalJZlIxsRr3eq2uNwP4X2oirKpPX2RjBo
NMKnpnsyzuOiu+Pf3hJFrTpfWzHXXm5Eq+OZcwnQO5YNY6XGO4qhSNKT9ka9Mzbo
qRKdPrCrB+s5rryVJXKYVSInP3sDSQ2IPsYpZ6GW6Mv56PuFCpjTzElzejV7M0n5
0bRmn+MZVMVUR54KYiaCywFgUzmr3yfs1cfcsKqMRywt2J58lRy/chTLZ6LILQMv
4V01neVJiRkTmUfIWvc1ENIFM9QJlky9AvA5ASvwTTRz8yOnxoOXE/y4OVyOePjT
cz9eumu9N5dPuUIMmsYlXmRNaeGZPD9bIgKY5zOlfhlfZSuOLNH6EHBNr6JAgfwL
pdP43sbg2SSNKpBZ0iSMvpyTpbigbe3OyhnFH/TyhcC2Wdf62S9/FRsvjlRPbakW
YhKAA2kmJoydcUDO5ccEga8b7NxCdhRiczbiU2cj70pMIuOhDlGAznyxsYbtyxaB
AoHBAPy6Cbt6y1AmuId/HYfvms6i8B+/frD1CKyn+sUDkPf81xSHV7RcNrJi1S1c
V55I0y96HulsR+GmcAW1DF3qivWkdsd/b4mVkizd/zJm3/Dm8p8QOnNTtdWvYoEB
VzfAhBGaR/xflSLxZh2WE8ZHQ3IcRCXV9ZFgJ7PMeTprBJXzl0lTptvrHyo9QK1v
obLrL/KuXWS0ql1uSnJr1vtDI5uW8WU4GDENeU5b/CJHpKpjVxlGg+7pmLknxlBl
oBnZnQKBwQDs2Ky29qZ69qnPWowKceMJ53Z6uoUeSffRZ7xuBjowpkylasEROjuL
nyAihIYB7fd7R74CnRVYLI+O2qXfNKJ8HN+TgcWv8LudkRcnZDSvoyPEJAPyZGfr
olRCXD3caqtarlZO7vXSAl09C6HcL2KZ8FuPIEsuO0Aw25nESMg9eVMaIC6s2eSU
NUt6xfZw1JC0c+f0LrGuFSjxT2Dr5WKND9ageI6afuauMuosjrrOMl2g0dMcSnVz
KrtYa7Wi1N8CgcBFnuJreUplDCWtfgEen40f+5b2yAQYr4fyOFxGxdK73jVJ/HbW
wsh2n+9mDZg9jIZQ/+1gFGpA6V7W06dSf/hD70ihcKPDXSbloUpaEikC7jxMQWY4
uwjOkwAp1bq3Kxu21a+bAKHO/H1LDTrpVlxoJQ1I9wYtRDXrvBpxU2XyASbeFmNT
FhSByFn27Ve4OD3/NrWXtoVwM5/ioX6ZvUcj55McdTWE3ddbFNACiYX9QlyOI/TY
bhWafDCPmU9fj6kCgcEAjyQEfi9jPj2FM0RODqH1zS6OdG31tfCOTYicYQJyeKSI
/hAezwKaqi9phHMDancfcupQ89Nr6vZDbNrIFLYC3W+1z7hGeabMPNZLYAs3rE60
dv4tRHlaNRbORazp1iTBmvRyRRI2js3O++3jzOb2eILDUyT5St+UU/LkY7R5EG4a
w1df3idx9gCftXufDWHqcqT6MqFl0QgIzo5izS68+PPxitpRlR3M3Mr4rCU20Rev
blphdF+rzAavYyj1hYuRAoHBANmxwbq+QqsJ19SmeGMvfhXj+T7fNZQFh2F0xwb2
rMlf4Ejsnx97KpCLUkoydqAs2q0Ws9Nkx2VEVx5KfUD7fWhgbpdnEPnQkfeXv9sD
vZTuAoqInN1+vj1TME6EKR/6D4OtQygSNpecv23EuqEvyXWqRVsRt9Qd2B0H4k7h
gnjREs10u7zyqBIZH7KYVgyh27WxLr859ap8cKAH6Fb+UOPtZo3sUeeume60aebn
4pMwXeXP+LO8NIfRXV8mgrm86g==
-----END PRIVATE KEY-----
`
ks.importKey(keyName, pem, '', (err, key) => {
expect(err).to.not.exist()
expect(key).to.exist()
expect(key).to.have.property('name', keyName)
expect(key).to.have.property('id')
ks.removeKey(keyName, done)
})
})
// TODO: net.forge can not cope with this
// Uncaught AssertionError: expected [Error: Cannot read encrypted PBE data block. Unsupported OID.] to not exist
it.skip('can read a private encrypted key (v1)', (done) => {
/*
* Generated with
* openssl genpkey -algorithm RSA
* -pkeyopt rsa_keygen_bits:1024
* -pkeyopt rsa_keygen_pubexp:65537
* -out foo.pem
* openssl pkcs8 -in foo.pem -topk8 -passout pass:mypassword
*/
const pem = `-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICoTAbBgkqhkiG9w0BBQMwDgQI2563Jugj/KkCAggABIICgPxHkKtUUE8EWevq
eX9nTjqpbsv0QoXQMhegfxDELJLU8tj6V0bWNt7QDdfQ1n6FRgnNvNGick6gyqHH
yH9qC2oXwkDFP7OrHp2NEZd7DHQLLc+L4KJ/0dzsiZ1U9no7XzQMUay9Bc918ADE
pN2/EqigWkaG4gNjkAeKWr6+BNRevDXlSvls7YDboNcTiACi5zJkthivB9g3vT1m
gPdN6Gf/mmqtBTDHeqj5QsmXYqeCyo5b26JgYsziABVZDHph4ekPUsTvudRpE9Ex
baXwdYEAZxVpSbTvQ3A5qysjSZeM9ttfRTSSwL391q7dViz4+aujpk0Vj7piH+1B
CkfO8/XudRdRlnOe+KjMidktKCsMGCIOW92IlfMvIQ/Zn1GTYj9bRXONFNJ2WPND
UmCKnL7cmworwg/weRorrGKBWIGspU+tDASOPSvIGKo6Hoxm4CN1TpDRY7DAGlgm
Y3TEbMYfpXyzkPjvAhJDt03D3J9PrTO6uM5d7YUaaTmJ2TQFQVF2Lc3Uz8lDJLs0
ZYtfQ/4H+YY2RrX7ua7t6ArUcYXZtv0J4lRYWjwV8fGPUVc0d8xLJU0Yjf4BD7K8
rsavHo9b5YvBUX7SgUyxAEembEOe3SjQ+gPu2U5wovcjUuC9eItEEsXGrx30BQ0E
8BtK2+hp0eMkW5/BYckJkH+Yl8ypbzRGRRIZzLgeI4JveSx/mNhewfgTr+ORPThZ
mBdkD5r+ixWF174naw53L8U9wF8kiK7pIE1N9TR4USEeovLwX6Ni/2MMDZedOfof
2f77eUdLsK19/5/lcgAAYaXauXWhy2d2r3SayFrC9woy0lh2VLKRMBjcx1oWb7dp
0uxzo5Y=
-----END ENCRYPTED PRIVATE KEY-----
`
ks.importKey(keyName, pem, 'mypassword', (err, key) => {
expect(err).to.not.exist()
expect(key).to.exist()
expect(key).to.have.property('name', keyName)
expect(key).to.have.property('id')
ks.removeKey(keyName, done)
})
})
it('can read a private encrypted key (v2)', (done) => {
/*
* Generated with
* openssl genpkey -algorithm RSA
* -pkeyopt rsa_keygen_bits:1024
* -pkeyopt rsa_keygen_pubexp:65537
* -out foo.pem
* openssl pkcs8 -in foo.pem -topk8 -v2 aes-256-cbc -passout pass:mypassword
*/
const pem = `-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIhuL894loRucCAggA
MB0GCWCGSAFlAwQBKgQQEoEtsjW3iC9/u0uGvkxX7wSCAoAsX3l6JoR2OGbT8CkY
YT3RQFqquOgItYOHw6E3tir2YrmxEAo99nxoL8pdto37KSC32eAGnfv5R1zmHHSx
0M3/y2AWiCBTX95EEzdtGC1hK3PBa/qpp/xEmcrsjYN6NXxMAkhC0hMP/HdvqMAg
ee7upvaYJsJcl8QLFNayAWr8b8cZA/RBhGEIRl59Eyj6nNtxDt3bCrfe06o1CPCV
50/fRZEwFOi/C6GYvPN6MrPZO3ALBWgopLT2yQqycTKtfxYWIdOsMBkAjKf2D6Pk
u2mqBsaP4b71jIIeT4euSJLsoJV+O39s8YHXtW8GtOqp7V5kIlnm90lZ9wzeLTZ7
HJsD/jEdYto5J3YWm2wwEDccraffJSm7UDtJBvQdIx832kxeFCcGQjW38Zl1qqkg
iTH1PLTypxj2ZuviS2EkXVFb/kVU6leWwOt6fqWFC58UvJKeCk/6veazz3PDnTWM
92ClUqFd+CZn9VT4CIaJaAc6v5NLpPp+T9sRX9AtequPm7FyTeevY9bElfyk9gW9
JDKgKxs6DGWDa16RL5vzwtU+G3o6w6IU+mEwa6/c+hN+pRFs/KBNLLSP9OHBx7BJ
X/32Ft+VFhJaK+lQ+f+hve7od/bgKnz4c/Vtp7Dh51DgWgCpBgb8p0vqu02vTnxD
BXtDv3h75l5PhvdWfVIzpMWRYFvPR+vJi066FjAz2sjYc0NMLSYtZWyWoIInjhoX
Dp5CQujCtw/ZSSlwde1DKEWAW4SeDZAOQNvuz0rU3eosNUJxEmh3aSrcrRtDpw+Y
mBUuWAZMpz7njBi7h+JDfmSW/GAaMwrVFC2gef5375R0TejAh+COAjItyoeYEvv8
DQd8
-----END ENCRYPTED PRIVATE KEY-----
`
ks.importKey(keyName, pem, 'mypassword', (err, key) => {
expect(err).to.not.exist()
expect(key).to.exist()
expect(key).to.have.property('name', keyName)
expect(key).to.have.property('id', 'QmeMWBbuyw8KycYhZVxMzVHK3zLH1mp2DT84X2NApqiXgn')
ks.removeKey(keyName, done)
})
})
})
}