Skip to content
This repository has been archived by the owner on Jun 15, 2023. It is now read-only.

Commit

Permalink
feat: use libp2p-crypto (#18)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
richardschneider authored and daviddias committed Dec 20, 2017
1 parent 605d290 commit c1627a9
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 511 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
Expand Down
12 changes: 5 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
96 changes: 0 additions & 96 deletions src/cms.js

This file was deleted.

168 changes: 59 additions & 109 deletions src/keychain.js
Original file line number Diff line number Diff line change
@@ -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')

Expand All @@ -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'
}
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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}'`)
}
})
})
})
}

Expand Down Expand Up @@ -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)
})
})
}

Expand All @@ -409,31 +375,27 @@ 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)
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)
})
})
})
} catch (err) {
_error(callback, err)
}
})
})
}

Expand All @@ -445,42 +407,30 @@ class Keychain {
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`)

const privateKeyProtobuf = peer.marshalPrivKey()
crypto.keys.unmarshalPrivateKey(privateKeyProtobuf, (err, key) => {
privateKey.id((err, kid) => {
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')
privateKey.export(this._(), (err, pem) => {
if (err) return _error(callback, err)
const keyInfo = {
name: name,
id: kid
}
const pem = forge.pki.encryptRsaPrivateKey(privateKey, this._())
util.keyId(privateKey, (err, 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 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)
})
} catch (err) {
_error(callback, err)
}
})
})
})
}
Expand Down
Loading

0 comments on commit c1627a9

Please sign in to comment.