mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-05-28 09:51:19 +00:00
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:
parent
605d290525
commit
c1627a99e7
@ -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'
|
||||
}
|
||||
|
12
package.json
12
package.json
@ -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"
|
||||
}
|
||||
|
96
src/cms.js
96
src/cms.js
@ -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
|
190
src/keychain.js
190
src/keychain.js
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
86
src/util.js
86
src/util.js
@ -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)
|
||||
})
|
||||
}
|
@ -23,6 +23,5 @@ describe('browser', () => {
|
||||
})
|
||||
|
||||
require('./keychain.spec')(datastore1, datastore2)
|
||||
require('./openssl')(datastore1)
|
||||
require('./peerid')
|
||||
})
|
||||
|
@ -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()
|
||||
|
@ -30,6 +30,5 @@ describe('node', () => {
|
||||
})
|
||||
|
||||
require('./keychain.spec')(datastore1, datastore2)
|
||||
require('./openssl')(datastore1)
|
||||
require('./peerid')
|
||||
})
|
||||
|
154
test/openssl.js
154
test/openssl.js
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user