Skip to content
This repository has been archived by the owner on Apr 8, 2020. It is now read-only.

Commit

Permalink
feat: adds key-composer to support import and export to pem
Browse files Browse the repository at this point in the history
  • Loading branch information
AlbertoElias committed Apr 23, 2019
1 parent ce22cf1 commit ec178c7
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 16 deletions.
44 changes: 44 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
language: node_js

cache: npm

stages:
- check
- test
- cov

node_js:
- '10'

os:
- linux
- osx
- windows

script: npx nyc -s npm run test:node -- --bail
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov

jobs:
include:
- stage: check
script:
- npx aegir commitlint --travis
- npx aegir dep-check
- npm run lint

- stage: test
name: chrome
addons:
chrome: stable
script:
- npx aegir test -t browser

- stage: test
name: firefox
addons:
firefox: latest
script:
- npx aegir test -t browser -- --browsers FirefoxHeadless

notifications:
email: false
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# js-libp2p-crypto-secp256k1

[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io)
[![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/)
[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs)
[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
[![](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-crypto-secp256k1.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-crypto-secp256k1)
[![](https://img.shields.io/travis/libp2p/js-libp2p-crypto-secp256k1.svg?style=flat-square)](https://travis-ci.com/libp2p/js-libp2p-crypto-secp256k1)
[![Dependency Status](https://david-dm.org/libp2p/js-libp2p-crypto-secp256k1.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-crypto-secp256k1)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)
![](https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square)
![](https://img.shields.io/badge/Node.js-%3E%3D4.0.0-orange.svg?style=flat-square)
Expand Down
2 changes: 0 additions & 2 deletions ci/Jenkinsfile

This file was deleted.

18 changes: 10 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
"scripts": {
"lint": "aegir lint",
"build": "aegir build",
"test": "npm run test:node && npm run test:browser",
"test:node": "aegir test --env node",
"test:browser": "aegir test --env browser",
"test": "aegir test -t node -t browser",
"test:node": "aegir test -t node",
"test:browser": "aegir test -t browser",
"release": "aegir release",
"release-minor": "aegir release --type minor",
"release-major": "aegir release --type major",
Expand All @@ -27,19 +27,21 @@
],
"license": "MIT",
"dependencies": {
"async": "^2.6.1",
"async": "^2.6.2",
"bs58": "^4.0.1",
"multihashing-async": "~0.5.1",
"crypto-key-composer": "~0.1.0",
"multihashing-async": "~0.6.0",
"nodeify": "^1.0.1",
"safe-buffer": "^5.1.2",
"secp256k1": "^3.6.1"
"secp256k1": "^3.6.2"
},
"devDependencies": {
"aegir": "^18.0.3",
"aegir": "^18.2.2",
"benchmark": "^2.1.4",
"chai": "^4.2.0",
"chai-string": "^1.5.0",
"dirty-chai": "^2.0.1",
"libp2p-crypto": "~0.16.0"
"libp2p-crypto": "~0.16.1"
},
"engines": {
"node": ">=6.0.0",
Expand Down
2 changes: 1 addition & 1 deletion src/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module.exports = (randomBytes) => {

let privateKey
do {
privateKey = randomBytes(32)
privateKey = randomBytes(privateKeyLength)
} while (!secp256k1.privateKeyVerify(privateKey))

done(null, privateKey)
Expand Down
75 changes: 74 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const bs58 = require('bs58')
const multihashing = require('multihashing-async')
const { composePrivateKey, decomposePrivateKey } = require('crypto-key-composer')

module.exports = (keysProtobuf, randomBytes, crypto) => {
crypto = crypto || require('./crypto')(randomBytes)
Expand Down Expand Up @@ -93,6 +94,63 @@ module.exports = (keysProtobuf, randomBytes, crypto) => {
callback(null, bs58.encode(hash))
})
}

/**
* Exports the key into a password protected PEM format
*
* @param {string} [format] - Defaults to 'pkcs-8'.
* @param {string} password - The password to read the encrypted PEM
* @param {function(Error, KeyInfo)} callback
* @returns {undefined}
*/
export (format, password, callback) {
if (typeof password === 'function') {
callback = password
password = format
format = 'pkcs-8'
}

ensure(callback)

let err = null
let pem = null

const decompressedPublicKey = typedArrayToUint8Array(crypto.decompressPublicKey(this.public._key))
try {
if (format === 'pkcs-8') {
pem = composePrivateKey({
format: 'pkcs8-pem',
keyAlgorithm: {
id: 'ec-public-key',
namedCurve: 'secp256k1'
},
keyData: {
d: typedArrayToUint8Array(this.marshal()),
// The public key concatenates the x and y values and adds an initial byte
x: decompressedPublicKey.slice(1, 33),
y: decompressedPublicKey.slice(33, 65)
},
encryptionAlgorithm: {
keyDerivationFunc: {
id: 'pbkdf2',
iterationCount: 10000, // The number of iterations
keyLength: 32, // Automatic, based on the `encryptionScheme`
prf: 'hmac-with-sha512' // The pseudo-random function
},
encryptionScheme: {
id: 'aes256-cbc'
}
}
}, { password })
} else {
err = new Error(`Unknown export format '${format}'`)
}
} catch (_err) {
err = _err
}

callback(err, pem)
}
}

function unmarshalSecp256k1PrivateKey (bytes, callback) {
Expand Down Expand Up @@ -122,17 +180,32 @@ module.exports = (keysProtobuf, randomBytes, crypto) => {
})
}

function importPEM (pem, password, callback) {
let privkey
try {
const decomposedPrivateKey = decomposePrivateKey(pem, { password })
privkey = new Secp256k1PrivateKey(Buffer.from(decomposedPrivateKey.keyData.d))
} catch (err) { return callback(err) }

callback(null, privkey)
}

function ensure (callback) {
if (typeof callback !== 'function') {
throw new Error('callback is required')
}
}

function typedArrayToUint8Array (typedArray) {
return new Uint8Array(typedArray.buffer.slice(typedArray.byteOffset, typedArray.byteOffset + typedArray.byteLength))
}

return {
Secp256k1PublicKey,
Secp256k1PrivateKey,
unmarshalSecp256k1PrivateKey,
unmarshalSecp256k1PublicKey,
generateKeyPair
generateKeyPair,
import: importPEM
}
}
41 changes: 41 additions & 0 deletions test/secp256k1.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
chai.use(require('chai-string'))

const Buffer = require('safe-buffer').Buffer

Expand Down Expand Up @@ -139,6 +140,46 @@ describe('secp256k1 keys', () => {
})
})
})

/* eslint-disable */
describe('import and export', () => {
it('password protected PKCS #8', (done) => {
key.export('pkcs-8', 'my secret', (err, pem) => {
expect(err).to.not.exist()
expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
secp256k1.import(pem, 'my secret', (err, clone) => {
expect(err).to.not.exist()
expect(clone).to.exist()
expect(key.equals(clone)).to.eql(true)
done()
})
})
})

it('defaults to PKCS #8', (done) => {
key.export('another secret', (err, pem) => {
expect(err).to.not.exist()
expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
secp256k1.import(pem, 'another secret', (err, clone) => {
expect(err).to.not.exist()
expect(clone).to.exist()
expect(key.equals(clone)).to.eql(true)
done()
})
})
})

it('needs correct password', (done) => {
key.export('another secret', (err, pem) => {
expect(err).to.not.exist()
secp256k1.import(pem, 'not the secret', (err, clone) => {
expect(err).to.exist()
done()
})
})
})
})
/* eslint-enable */
})

describe('key generation error', () => {
Expand Down

0 comments on commit ec178c7

Please sign in to comment.