Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: move keychain #633

Merged
merged 97 commits into from
May 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
4c8d147
Initial commit
daviddias Dec 6, 2017
49e6c47
chore: setup repo
daviddias Dec 6, 2017
1a96ae8
feat: move bits from https://github.com/richardschneider/ipfs-encryption
richardschneider Dec 6, 2017
658a4d7
docs: install and links
richardschneider Dec 6, 2017
409a999
fix: linting
richardschneider Dec 6, 2017
7c44c91
fix: more linting
richardschneider Dec 6, 2017
98ba68a
test: needs more time to generate RSA key
richardschneider Dec 6, 2017
569f963
test: temporarily disable webworker tests #3
richardschneider Dec 6, 2017
358c8c2
test: disable webworker
richardschneider Dec 6, 2017
99780ab
chore: ci coverage
richardschneider Dec 6, 2017
cfdd2f4
chore: publish coverage report
richardschneider Dec 6, 2017
643bcd4
Add syntax highlighting to README
mkg20001 Dec 6, 2017
506e1d7
Merge pull request #4 from libp2p/mkg20001-patch-1
richardschneider Dec 6, 2017
f49e753
fix: return info on removed key #10
richardschneider Dec 8, 2017
8305d20
fix: error message
richardschneider Dec 8, 2017
3b8d05a
docs(keychain): add API documentation
richardschneider Dec 9, 2017
f71d3a6
fix: maps an IPFS hash name to its forge equivalent
richardschneider Dec 10, 2017
ff4f656
fix: lint errors
richardschneider Dec 10, 2017
06917f7
fix: lint errors
richardschneider Dec 10, 2017
2dd069b
test: importing openssl keys
richardschneider Dec 10, 2017
1b2664a
refactor: keep the key info in the store
richardschneider Dec 11, 2017
ee9dbeb
Updating CI files
victorb Dec 14, 2017
b4518e0
Merge pull request #15 from libp2p/automatic-ci-script-update
richardschneider Dec 16, 2017
9129d20
docs: correct hash name
richardschneider Dec 16, 2017
97bf98f
Merge pull request #13 from libp2p/ds-keyinfo
richardschneider Dec 16, 2017
e78b248
test: key name comparision
richardschneider Dec 16, 2017
3b7c691
test(openssl): verify key id
richardschneider Dec 17, 2017
605d290
Merge pull request #17 from libp2p/filenames
richardschneider Dec 19, 2017
c1627a9
feat: use libp2p-crypto (#18)
richardschneider Dec 20, 2017
5343b0f
chore: update deps
daviddias Dec 20, 2017
21611e4
chore: update contributors
daviddias Dec 20, 2017
de15d12
chore: release version v0.2.0
daviddias Dec 20, 2017
89a451c
feat: generate unique options for a key chain (#20)
richardschneider Dec 28, 2017
849a7c7
chore: update contributors
daviddias Dec 28, 2017
6a84873
chore: release version v0.2.1
daviddias Dec 28, 2017
1e276f6
chore: update deps
daviddias Jan 29, 2018
2ce4444
fix: deepmerge 2.0.1 fails in browser, stay with 1.5.2
richardschneider Jan 29, 2018
3816b82
chore: update contributors
daviddias Jan 29, 2018
acf48a8
chore: release version v0.3.0
daviddias Jan 29, 2018
5560669
CMS - PKCS #7 (#19)
richardschneider Jan 29, 2018
486e54b
chore: update contributors
daviddias Jan 29, 2018
ee978a5
chore: release version v0.3.1
daviddias Jan 29, 2018
974c507
docs: add lead-maintainer
vasco-santos Jun 25, 2018
0065b0a
chore: fix out of date npms (#21)
camelmasa Jun 29, 2018
73d4530
chore: update deps
daviddias Jun 30, 2018
f95fef4
chore: use lodash main dependency
vasco-santos Jul 3, 2018
8dfaab1
fix: validate createKey params properly (#26)
Sep 18, 2018
65129bf
chore: update contributors
vasco-santos Sep 18, 2018
5d3f489
chore: release version v0.3.2
vasco-santos Sep 18, 2018
24d4374
chore: upgrade dependencies (#27)
vasco-santos Oct 25, 2018
571c81a
chore: update contributors
vasco-santos Oct 25, 2018
251e0b8
chore: release version v0.3.3
vasco-santos Oct 25, 2018
17268d5
chore: update dependencies (#29)
vasco-santos Jan 4, 2019
a753b1c
chore: update contributors
vasco-santos Jan 4, 2019
4b895cf
chore: release version v0.3.4
vasco-santos Jan 4, 2019
7eeed87
fix: reduce bundle size (#28)
hugomrdias Jan 10, 2019
5cbded5
chore: update contributors
vasco-santos Jan 10, 2019
4dd2ad3
chore: release version v0.3.5
vasco-santos Jan 10, 2019
18357e6
Merge branch 'master' of github.com:libp2p/js-libp2p-keychain
vasco-santos Jan 10, 2019
eaf6a88
chore: update contributors
vasco-santos Jan 10, 2019
aa5a6cb
chore: release version v0.3.6
vasco-santos Jan 10, 2019
3779bd0
chore: use travis (#32)
vasco-santos Feb 18, 2019
9eb11f4
feat: adds support for ed25199 and secp256k1 (#31)
AlbertoElias Feb 25, 2019
217cfd3
chore: update libp2p-crypto (#33)
AlbertoElias Feb 26, 2019
267002f
chore: update contributors
vasco-santos Feb 26, 2019
e30330e
chore: release version v0.4.0
vasco-santos Feb 26, 2019
f71a6bb
Revert "feat: adds support for ed25199 and secp256k1 (#31)"
vasco-santos Mar 14, 2019
4e4d3d4
chore: update contributors
vasco-santos Mar 14, 2019
a5fd967
chore: release version v0.4.1
vasco-santos Mar 14, 2019
ef47374
chore: add discourse badge (#34)
vasco-santos Apr 11, 2019
7051b9c
fix: throw errors with correct stack trace (#35)
achingbrain Jun 13, 2019
74cb4d4
chore: update contributors
vasco-santos Jun 13, 2019
717112b
chore: release version v0.4.2
vasco-santos Jun 13, 2019
dda315a
refactor: use async/await instead of callbacks (#37)
jacobheun Aug 16, 2019
e375c2f
chore: update contributors
vasco-santos Aug 16, 2019
ad37817
chore: release version v0.5.0
vasco-santos Aug 16, 2019
893a2c9
chore: downgrade peer-id to same version used by libp2p (#38)
achingbrain Sep 25, 2019
b9eb9d7
chore: update contributors
vasco-santos Sep 25, 2019
ce8c412
chore: release version v0.5.1
vasco-santos Sep 25, 2019
8de9681
chore: update node-forge dependency (#39)
Dec 2, 2019
ff6bd50
chore: update contributors
vasco-santos Dec 2, 2019
163edbb
chore: release version v0.5.2
vasco-santos Dec 2, 2019
b6d5313
chore: update deps (#40)
Dec 18, 2019
8ff68d1
chore: update contributors
vasco-santos Dec 18, 2019
be63323
chore: release version v0.5.3
vasco-santos Dec 18, 2019
6b9516c
Revert "chore: update deps (#40)"
vasco-santos Dec 18, 2019
66c1fb3
chore: update contributors
vasco-santos Dec 18, 2019
0d13a8b
chore: release version v0.5.4
vasco-santos Dec 18, 2019
464fcbe
chore: update deps (#40)
Dec 18, 2019
24e10f3
chore: update contributors
vasco-santos Dec 18, 2019
44a1e7c
chore: release version v0.6.0
vasco-santos Dec 18, 2019
55fb5d5
chore(deps-dev): bump aegir from 20.6.1 to 21.2.0 (#44)
dependabot-preview[bot] Feb 20, 2020
fd618b6
chore(deps-dev): bump datastore-level from 0.14.1 to 1.0.0
dependabot-preview[bot] Apr 28, 2020
e72a5b0
chore(deps-dev): bump datastore-level from 0.14.1 to 1.0.0
vasco-santos Apr 29, 2020
9e96dbc
chore(deps-dev): bump datastore-fs from 0.9.1 to 1.0.0 (#46)
dependabot-preview[bot] Apr 29, 2020
d9fd726
chore: move to libp2p
vasco-santos Apr 29, 2020
476a3b7
Merge branch 'master' of ../js-libp2p-keychain into chore/move-keychain
vasco-santos May 11, 2020
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
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,14 @@
"multiaddr": "^7.4.3",
"multistream-select": "^0.15.0",
"mutable-proxy": "^1.0.0",
"node-forge": "^0.9.1",
"p-any": "^3.0.0",
"p-fifo": "^1.0.0",
"p-settle": "^4.0.1",
"peer-id": "^0.13.11",
"protons": "^1.0.1",
"retimer": "^2.0.0",
"sanitize-filename": "^1.6.3",
"streaming-iterables": "^4.1.0",
"timeout-abort-controller": "^1.0.0",
"xsalsa20": "^1.0.2"
Expand All @@ -84,31 +86,38 @@
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-bytes": "^0.1.2",
"chai-string": "^1.5.0",
"cids": "^0.8.0",
"datastore-fs": "^1.1.0",
"datastore-level": "^1.1.0",
"delay": "^4.3.0",
"dirty-chai": "^2.0.1",
"interop-libp2p": "libp2p/interop#chore/update-libp2p-daemon-with-peerstore",
"ipfs-http-client": "^44.0.0",
"it-concat": "^1.0.0",
"it-pair": "^1.0.0",
"it-pushable": "^1.4.0",
"level": "^6.0.1",
"libp2p-bootstrap": "^0.11.0",
"libp2p-delegated-content-routing": "^0.5.0",
"libp2p-delegated-peer-routing": "^0.5.0",
"libp2p-floodsub": "^0.21.0",
"libp2p-gossipsub": "^0.4.0",
"libp2p-kad-dht": "^0.19.1",
"libp2p-mdns": "^0.14.1",
"libp2p-noise": "^1.1.0",
"libp2p-mplex": "^0.9.5",
"libp2p-noise": "^1.1.0",
"libp2p-secio": "^0.12.4",
"libp2p-tcp": "^0.14.1",
"libp2p-webrtc-star": "^0.18.0",
"libp2p-websockets": "^0.13.1",
"multihashes": "^0.4.19",
"nock": "^12.0.3",
"p-defer": "^3.0.0",
"p-times": "^3.0.0",
"p-wait-for": "^3.1.0",
"promisify-es6": "^1.0.3",
"rimraf": "^3.0.2",
"sinon": "^9.0.2"
},
"contributors": [
Expand Down
123 changes: 123 additions & 0 deletions src/keychain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# js-libp2p-keychain

[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai)
[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/)
[![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p)
[![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io)
[![](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-keychain.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-keychain)
[![](https://img.shields.io/travis/libp2p/js-libp2p-keychain.svg?style=flat-square)](https://travis-ci.com/libp2p/js-libp2p-keychain)
[![Dependency Status](https://david-dm.org/libp2p/js-libp2p-keychain.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-keychain)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)

> A secure key chain for libp2p in JavaScript

## Lead Maintainer

[Vasco Santos](https://github.com/vasco-santos).

## Features

- Manages the lifecycle of a key
- Keys are encrypted at rest
- Enforces the use of safe key names
- Uses encrypted PKCS 8 for key storage
- Uses PBKDF2 for a "stetched" key encryption key
- Enforces NIST SP 800-131A and NIST SP 800-132
- Uses PKCS 7: CMS (aka RFC 5652) to provide cryptographically protected messages
- Delays reporting errors to slow down brute force attacks

## Table of Contents

## Install

```sh
npm install --save libp2p-keychain
```

### Usage

```js
const Keychain = require('libp2p-keychain')
const FsStore = require('datastore-fs')

const datastore = new FsStore('./a-keystore')
const opts = {
passPhrase: 'some long easily remembered phrase'
}
const keychain = new Keychain(datastore, opts)
```

## API

Managing a key

- `async createKey (name, type, size)`
- `async renameKey (oldName, newName)`
- `async removeKey (name)`
- `async exportKey (name, password)`
- `async importKey (name, pem, password)`
- `async importPeer (name, peer)`

A naming service for a key

- `async listKeys ()`
- `async findKeyById (id)`
- `async findKeyByName (name)`

Cryptographically protected messages

- `async cms.encrypt (name, plain)`
- `async cms.decrypt (cmsData)`

### KeyInfo

The key management and naming service API all return a `KeyInfo` object. The `id` is a universally unique identifier for the key. The `name` is local to the key chain.

```js
{
name: 'rsa-key',
id: 'QmYWYSUZ4PV6MRFYpdtEDJBiGs4UrmE6g8wmAWSePekXVW'
}
```

The **key id** is the SHA-256 [multihash](https://github.com/multiformats/multihash) of its public key. The *public key* is a [protobuf encoding](https://github.com/libp2p/js-libp2p-crypto/blob/master/src/keys/keys.proto.js) containing a type and the [DER encoding](https://en.wikipedia.org/wiki/X.690) of the PKCS [SubjectPublicKeyInfo](https://www.ietf.org/rfc/rfc3279.txt).

### Private key storage

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. This, along with the passPhrase, is the input to a `PBKDF2` function.

```js
const defaultOptions = {
//See https://cryptosense.com/parameter-choice-for-pbkdf2/
dek: {
keyLength: 512 / 8,
iterationCount: 1000,
salt: 'at least 16 characters long',
hash: 'sha2-512'
}
}
```

![key storage](./doc/private-key.png?raw=true)

### Physical storage

The actual physical storage of an encrypted key is left to implementations of [interface-datastore](https://github.com/ipfs/interface-datastore/). A key benifit is that now the key chain can be used in browser with the [js-datastore-level](https://github.com/ipfs/js-datastore-level) implementation.

### Cryptographic Message Syntax (CMS)

CMS, aka [PKCS #7](https://en.wikipedia.org/wiki/PKCS) and [RFC 5652](https://tools.ietf.org/html/rfc5652), describes an encapsulation syntax for data protection. It is used to digitally sign, digest, authenticate, or encrypt arbitrary message content. Basically, `cms.encrypt` creates a DER message that can be only be read by someone holding the private key.

## Contribute

Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/js-libp2p-keychain/issues)!

This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).

[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md)

## License

[MIT](LICENSE)
122 changes: 122 additions & 0 deletions src/keychain/cms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
'use strict'

require('node-forge/lib/pkcs7')
require('node-forge/lib/pbe')
const forge = require('node-forge/lib/forge')
const { certificateForKey, findAsync } = require('./util')
const errcode = require('err-code')

/**
* Cryptographic Message Syntax (aka PKCS #7)
*
* CMS describes an encapsulation syntax for data protection. It
* is used to digitally sign, digest, authenticate, or encrypt
* arbitrary message content.
*
* See RFC 5652 for all the details.
*/
class CMS {
/**
* Creates a new instance with a keychain
*
* @param {Keychain} keychain - the available keys
*/
constructor (keychain) {
if (!keychain) {
throw errcode(new Error('keychain is required'), 'ERR_KEYCHAIN_REQUIRED')
}

this.keychain = keychain
}

/**
* Creates some protected data.
*
* The output Buffer contains the PKCS #7 message in DER.
*
* @param {string} name - The local key name.
* @param {Buffer} plain - The data to encrypt.
* @returns {undefined}
*/
async encrypt (name, plain) {
if (!Buffer.isBuffer(plain)) {
throw errcode(new Error('Plain data must be a Buffer'), 'ERR_INVALID_PARAMS')
}

const key = await this.keychain.findKeyByName(name)
const pem = await this.keychain._getPrivateKey(name)
const privateKey = forge.pki.decryptRsaPrivateKey(pem, this.keychain._())
const certificate = await certificateForKey(key, privateKey)

// 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()
return Buffer.from(der, 'binary')
}

/**
* Reads some protected data.
*
* The keychain must contain one of the keys used to encrypt the data. If none of the keys
* exists, an Error is returned with the property 'missingKeys'. It is array of key ids.
*
* @param {Buffer} cmsData - The CMS encrypted data to decrypt.
* @returns {undefined}
*/
async decrypt (cmsData) {
if (!Buffer.isBuffer(cmsData)) {
throw errcode(new Error('CMS data is required'), 'ERR_INVALID_PARAMS')
}

let cms
try {
const buf = forge.util.createBuffer(cmsData.toString('binary'))
const obj = forge.asn1.fromDer(buf)
cms = forge.pkcs7.messageFromAsn1(obj)
} catch (err) {
throw errcode(new Error('Invalid CMS: ' + err.message), 'ERR_INVALID_CMS')
}

// 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
}
})

const r = await findAsync(recipients, async (recipient) => {
try {
const key = await this.keychain.findKeyById(recipient.keyId)
if (key) return true
} catch (err) {
return false
}
return false
})

if (!r) {
const missingKeys = recipients.map(r => r.keyId)
throw errcode(new Error('Decryption needs one of the key(s): ' + missingKeys.join(', ')), 'ERR_MISSING_KEYS', {
missingKeys
})
}

const key = await this.keychain.findKeyById(r.keyId)
const pem = await this.keychain._getPrivateKey(key.name)
const privateKey = forge.pki.decryptRsaPrivateKey(pem, this.keychain._())
cms.decrypt(r.recipient, privateKey)
return Buffer.from(cms.content.getBytes(), 'binary')
}
}

module.exports = CMS
Binary file added src/keychain/doc/private-key.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/keychain/doc/private-key.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<mxfile userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36" version="7.8.2" editor="www.draw.io"><diagram id="a8b2919f-aefc-d24c-c550-ea0bf34e92af" name="Page-1">7VlNb6MwEP01HLfCGBJ6bNJ2V9pdqVIP2x4dcMAKYGScJumvXxNsvkw+SmgSVe2hMs9mbL839swQA07j9U+G0vAv9XFkWKa/NuC9YVmua4n/ObApAOjCAggY8QsIVMAzeccSNCW6JD7OGgM5pREnaRP0aJJgjzcwxBhdNYfNadScNUUB1oBnD0U6+o/4PJTbssYV/guTIFQzg9Ft0TND3iJgdJnI+QwLzrd/RXeMlC250SxEPl3VIPhgwCmjlBeteD3FUU6toq1473FHb7luhhN+zAtSpzcULeXWU5RluYmQoQzLRfKNIobjtbA7CXkcCQCIZsYZXeApjSgTSEITMXIyJ1HUglBEgkQ8emJlWOCTN8w4EZTfyY6Y+H4+zWQVEo6fU+Tlc66EfwlsSynOF22KJ7loYQCvd24clHQKL8U0xpxtxBDlolIA6aBgJJ9Xldy2hMKa0ko3JB0sKA1XJIuG5Lmbc6hx/jT5ff9oaWQL50jzZsqoh4Uq3dTUtBiAF9AmxtaJAVYHM6MBmLE1Zny8EABNOaFJ9nW9sfQryfr4fN7oaJxrNOPEv8sv1ZyvSFwPxGuSLjbJNi85GzcmGCvgdQvAUQk8YUbE8nK6a7xhX7uKD7JWo8XpoEVhDEeIk7em+S6u5AxPlIiJq6PQEgWMraaJjC6Zh+Vb9Uu2bUiFw12GOGIB5pqhrXTlto9SczSomk5Dyw9IJsL1dku1C+9SKpYHR5Fvmj1VhE1D2ukbTkX3WlQsuGmErbqw4KLnE5oHBDlWWbt10K22i+xQVgiANrVhaT4g271g22xfKI3kTDQKi33d5rY7fB4Mmgxn5B3NtgNy/5D7EKOdieHcfyhcRmiGo0mZBauwW+XBe+KlzOblSoxSz7pjunvj6A8RgcpaY9Mw3tfZ1BA6n2f41IOt6puaRAucrz/AiSbUNaR/Fjxj+geAxk668PJqRLiPexX8QPuS/OjVmo84yjhleqV2CXac9o18Vnb06uEm3e01PvWW8XZfh4iZFdn+n9mQTLWSCQhcjanRntB5ElF6yl9cQl++zGpfbo7unp9VZgE9M2dJoFFdbRmc5cRarRMLLd0P3S5KnAEoGWuUaHwcTHPXhL/U2q/NjPdF+k6tIHV6J8AqeF9PBtzyZxu2HLVvaQPdlqHhShswaG0zmLQdVWsRbb+lPV5avf44Qdpm2Vo/67JLnfb+oo86RDeNKxLdHkr0208TXcXGz/pW0S066C+61SG6/S36x0TXC7VTRP9SH43VLahyzHZpc/xHY7DfUG85xWP1A2MxvPoRFz78Bw==</diagram></mxfile>
3 changes: 3 additions & 0 deletions src/keychain/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict'

module.exports = require('./keychain')
Loading