mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-05-29 18:21:23 +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**.
|
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
|
```js
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
createIfNeeded: true,
|
|
||||||
|
|
||||||
//See https://cryptosense.com/parameter-choice-for-pbkdf2/
|
//See https://cryptosense.com/parameter-choice-for-pbkdf2/
|
||||||
dek: {
|
dek: {
|
||||||
keyLength: 512 / 8,
|
keyLength: 512 / 8,
|
||||||
iterationCount: 10000,
|
iterationCount: 1000,
|
||||||
salt: 'at least 16 characters long',
|
salt: 'at least 16 characters long',
|
||||||
hash: 'sha2-512'
|
hash: 'sha2-512'
|
||||||
}
|
}
|
||||||
|
12
package.json
12
package.json
@ -45,22 +45,20 @@
|
|||||||
"async": "^2.6.0",
|
"async": "^2.6.0",
|
||||||
"deepmerge": "^1.5.2",
|
"deepmerge": "^1.5.2",
|
||||||
"interface-datastore": "~0.4.1",
|
"interface-datastore": "~0.4.1",
|
||||||
"libp2p-crypto": "~0.10.3",
|
"libp2p-crypto": "~0.11.0",
|
||||||
"multihashes": "~0.4.12",
|
|
||||||
"node-forge": "~0.7.1",
|
|
||||||
"pull-stream": "^3.6.1",
|
"pull-stream": "^3.6.1",
|
||||||
"sanitize-filename": "^1.6.1"
|
"sanitize-filename": "^1.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"aegir": "^12.2.0",
|
"aegir": "^12.3.0",
|
||||||
"chai": "^4.1.2",
|
"chai": "^4.1.2",
|
||||||
"chai-string": "^1.4.0",
|
"chai-string": "^1.4.0",
|
||||||
"datastore-fs": "^0.4.1",
|
"datastore-fs": "~0.4.1",
|
||||||
"datastore-level": "^0.7.0",
|
"datastore-level": "~0.7.0",
|
||||||
"dirty-chai": "^2.0.1",
|
"dirty-chai": "^2.0.1",
|
||||||
"level-js": "^2.2.4",
|
"level-js": "^2.2.4",
|
||||||
"mocha": "^4.0.1",
|
"mocha": "^4.0.1",
|
||||||
"peer-id": "^0.10.2",
|
"peer-id": "~0.10.4",
|
||||||
"pre-commit": "^1.2.2",
|
"pre-commit": "^1.2.2",
|
||||||
"rimraf": "^2.6.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'
|
'use strict'
|
||||||
|
|
||||||
const sanitize = require('sanitize-filename')
|
const sanitize = require('sanitize-filename')
|
||||||
const forge = require('node-forge')
|
|
||||||
const deepmerge = require('deepmerge')
|
const deepmerge = require('deepmerge')
|
||||||
const crypto = require('libp2p-crypto')
|
const crypto = require('libp2p-crypto')
|
||||||
const util = require('./util')
|
|
||||||
const CMS = require('./cms')
|
|
||||||
const DS = require('interface-datastore')
|
const DS = require('interface-datastore')
|
||||||
const pull = require('pull-stream')
|
const pull = require('pull-stream')
|
||||||
|
|
||||||
@ -19,24 +17,11 @@ const NIST = {
|
|||||||
minIterationCount: 1000
|
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 = {
|
const defaultOptions = {
|
||||||
// See https://cryptosense.com/parametesr-choice-for-pbkdf2/
|
// See https://cryptosense.com/parametesr-choice-for-pbkdf2/
|
||||||
dek: {
|
dek: {
|
||||||
keyLength: 512 / 8,
|
keyLength: 512 / 8,
|
||||||
iterationCount: 10000,
|
iterationCount: 1000,
|
||||||
salt: 'you should override this value with a crypto secure random number',
|
salt: 'you should override this value with a crypto secure random number',
|
||||||
hash: 'sha2-512'
|
hash: 'sha2-512'
|
||||||
}
|
}
|
||||||
@ -133,26 +118,15 @@ class Keychain {
|
|||||||
if (opts.dek.iterationCount < NIST.minIterationCount) {
|
if (opts.dek.iterationCount < NIST.minIterationCount) {
|
||||||
throw new Error(`dek.iterationCount must be least ${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
|
// Create the derived encrypting key
|
||||||
let dek = forge.pkcs5.pbkdf2(
|
const dek = crypto.pbkdf2(
|
||||||
opts.passPhrase,
|
opts.passPhrase,
|
||||||
opts.dek.salt,
|
opts.dek.salt,
|
||||||
opts.dek.iterationCount,
|
opts.dek.iterationCount,
|
||||||
opts.dek.keyLength,
|
opts.dek.keyLength,
|
||||||
hashAlgorithm)
|
opts.dek.hash)
|
||||||
dek = forge.util.bytesToHex(dek)
|
|
||||||
Object.defineProperty(this, '_', { value: () => dek })
|
Object.defineProperty(this, '_', { value: () => dek })
|
||||||
|
|
||||||
// Provide access to protected messages
|
|
||||||
this.cms = new CMS(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -189,31 +163,32 @@ class Keychain {
|
|||||||
if (size < 2048) {
|
if (size < 2048) {
|
||||||
return _error(callback, `Invalid RSA key size ${size}`)
|
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)
|
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)
|
if (err) return _error(callback, err)
|
||||||
|
|
||||||
const pem = forge.pki.encryptRsaPrivateKey(keypair.privateKey, this._())
|
callback(null, keyInfo)
|
||||||
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)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
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}`)
|
return _error(callback, `Key '${name}' does not exist. ${err.message}`)
|
||||||
}
|
}
|
||||||
const pem = res.toString()
|
const pem = res.toString()
|
||||||
try {
|
crypto.keys.import(pem, this._(), (err, privateKey) => {
|
||||||
const options = {
|
if (err) return _error(callback, err)
|
||||||
algorithm: 'aes256',
|
privateKey.export(password, callback)
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,62 +375,12 @@ class Keychain {
|
|||||||
self.store.has(dsname, (err, exists) => {
|
self.store.has(dsname, (err, exists) => {
|
||||||
if (err) return _error(callback, err)
|
if (err) return _error(callback, err)
|
||||||
if (exists) return _error(callback, `Key '${name}' already exists`)
|
if (exists) return _error(callback, `Key '${name}' already exists`)
|
||||||
try {
|
crypto.keys.import(pem, password, (err, privateKey) => {
|
||||||
const privateKey = forge.pki.decryptRsaPrivateKey(pem, password)
|
if (err) return _error(callback, 'Cannot read the key, most likely the password is wrong')
|
||||||
if (privateKey === null) {
|
privateKey.id((err, kid) => {
|
||||||
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) => {
|
|
||||||
if (err) return _error(callback, err)
|
if (err) return _error(callback, err)
|
||||||
|
privateKey.export(this._(), (err, pem) => {
|
||||||
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)
|
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 = {
|
const keyInfo = {
|
||||||
name: name,
|
name: name,
|
||||||
id: kid
|
id: kid
|
||||||
@ -478,9 +394,43 @@ class Keychain {
|
|||||||
callback(null, keyInfo)
|
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('./keychain.spec')(datastore1, datastore2)
|
||||||
require('./openssl')(datastore1)
|
|
||||||
require('./peerid')
|
require('./peerid')
|
||||||
})
|
})
|
||||||
|
@ -16,11 +16,10 @@ module.exports = (datastore1, datastore2) => {
|
|||||||
const rsaKeyName = 'tajné jméno'
|
const rsaKeyName = 'tajné jméno'
|
||||||
const renamedRsaKeyName = 'ชื่อลับ'
|
const renamedRsaKeyName = 'ชื่อลับ'
|
||||||
let rsaKeyInfo
|
let rsaKeyInfo
|
||||||
let emptyKeystore
|
// let emptyKeystore
|
||||||
let ks
|
let ks
|
||||||
|
|
||||||
before((done) => {
|
before((done) => {
|
||||||
emptyKeystore = new Keychain(datastore1, { passPhrase: passPhrase })
|
|
||||||
ks = new Keychain(datastore2, { passPhrase: passPhrase })
|
ks = new Keychain(datastore2, { passPhrase: passPhrase })
|
||||||
done()
|
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', () => {
|
describe('exported key', () => {
|
||||||
let pemKey
|
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) => {
|
ks.findKeyByName('alice', (err, key) => {
|
||||||
expect(err).to.not.exist()
|
expect(err).to.not.exist()
|
||||||
expect(key).to.exist()
|
expect(key).to.exist()
|
||||||
|
@ -30,6 +30,5 @@ describe('node', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
require('./keychain.spec')(datastore1, datastore2)
|
require('./keychain.spec')(datastore1, datastore2)
|
||||||
require('./openssl')(datastore1)
|
|
||||||
require('./peerid')
|
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-----
|
|
||||||
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
|
|
||||||
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