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

use libp2p-crypto #18

Merged
merged 6 commits into from
Dec 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:/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:/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