Merge pull request #12 from libp2p/webcrypto

Async Crypto Endeavour
This commit is contained in:
David Dias 2016-11-03 07:42:05 +00:00 committed by GitHub
commit 12630dc514
36 changed files with 1401 additions and 29917 deletions

View File

@ -1,13 +0,0 @@
'use strict'
const path = require('path')
module.exports = {
webpack: {
resolve: {
alias: {
'node-forge': path.resolve(__dirname, 'vendor/forge.bundle.js')
}
}
}
}

1
.gitignore vendored
View File

@ -31,5 +31,4 @@ build
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
lib
dist

View File

@ -1,9 +1,16 @@
sudo: false
language: node_js
node_js:
- 4
- 5
- stable
matrix:
include:
- node_js: 4
env: CXX=g++-4.8
- node_js: 6
env:
- SAUCE=true
- CXX=g++-4.8
- node_js: stable
env: CXX=g++-4.8
# Make sure we have new NPM.
before_install:
@ -22,9 +29,6 @@ before_script:
after_success:
- npm run coverage-publish
env:
- CXX=g++-4.8
addons:
firefox: 'latest'
apt:

View File

@ -9,6 +9,11 @@
[![Circle CI](https://circleci.com/gh/libp2p/js-libp2p-crypto.svg?style=svg)](https://circleci.com/gh/libp2p/js-libp2p-crypto)
[![Dependency Status](https://david-dm.org/libp2p/js-libp2p-crypto.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-crypto)
[![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)
[![Sauce Test Status](https://saucelabs.com/browser-matrix/ipfs-js-libp2p-crypto.svg)](https://saucelabs.com/u/ipfs-js-
libp2p-crypto)
> Crypto primitives for libp2p in JavaScript
@ -21,13 +26,22 @@ needed for libp2p. This is based on this [go implementation](https://github.com/
- [Usage](#usage)
- [Example](#example)
- [API](#api)
- [`generateKeyPair(type, bits)`](#generatekeypairtype-bits)
- [`generateEphemeralKeyPair(curve)`](#generateephemeralkeypaircurve)
- [`keyStretcher(cipherType, hashType, secret)`](#keystretcherciphertype-hashtype-secret)
- [`marshalPublicKey(key[, type])`](#marshalpublickeykey-type)
- [`hmac`](#hmac)
- [`create(hash, secret, callback)`](#createhash-secret-callback)
- [`digest(data, callback)`](#digestdata-callback)
- [`aes`](#aes)
- [`create(key, iv, callback)`](#createkey-iv-callback)
- [`encrypt(data, callback)`](#encryptdata-callback)
- [`encrypt(data, callback)`](#encryptdata-callback)
- [`webcrypto`](#webcrypto)
- [`keys`](#keys)
- [`generateKeyPair(type, bits, callback)`](#generatekeypairtype-bits-callback)
- [`generateEphemeralKeyPair(curve, callback)`](#generateephemeralkeypaircurve-callback)
- [`keyStretcher(cipherType, hashType, secret, callback)`](#keystretcherciphertype-hashtype-secret-callback)
- [`marshalPublicKey(key[, type], callback)`](#marshalpublickeykey-type-callback)
- [`unmarshalPublicKey(buf)`](#unmarshalpublickeybuf)
- [`marshalPrivateKey(key[, type])`](#marshalprivatekeykey-type)
- [`unmarshalPrivateKey(buf)`](#unmarshalprivatekeybuf)
- [`unmarshalPrivateKey(buf, callback)`](#unmarshalprivatekeybuf-callback)
- [Contribute](#contribute)
- [License](#license)
@ -44,27 +58,74 @@ npm install --save libp2p-crypto
```js
const crypto = require('libp2p-crypto')
var keyPair = crypto.generateKeyPair('RSA', 2048)
crypto.generateKeyPair('RSA', 2048, (err, key) => {
})
```
## API
### `generateKeyPair(type, bits)`
### `hmac`
Exposes an interface to the Keyed-Hash Message Authentication Code (HMAC) as defined in U.S. Federal Information Processing Standards Publication 198. An HMAC is a cryptographic hash that uses a key to sign a message. The receiver verifies the hash by recomputing it using the same key.
#### `create(hash, secret, callback)`
- `hash: String`
- `secret: Buffer`
- `callback: Function`
##### `digest(data, callback)`
- `data: Buffer`
- `callback: Function`
### `aes`
Expoes an interface to AES encryption (formerly Rijndael), as defined in U.S. Federal Information Processing Standards Publication 197.
This uses `CTR` mode.
#### `create(key, iv, callback)`
- `key: Buffer` The key, if length `16` then `AES 128` is used. For length `32`, `AES 256` is used.
- `iv: Buffer` Must have length `16`.
- `callback: Function`
##### `encrypt(data, callback)`
- `data: Buffer`
- `callback: Function`
##### `encrypt(data, callback)`
- `data: Buffer`
- `callback: Function`
### `webcrypto`
Depending on the environment this is either an instance of [node-webcrypto-ossl](https://github.com/PeculiarVentures/node-webcrypto-ossl) or the result of `window.crypto`.
### `keys`
### `generateKeyPair(type, bits, callback)`
- `type: String`, only `'RSA'` is currently supported
- `bits: Number`
- `bits: Number` Minimum of 1024
- `callback: Function`
Generates a keypair of the given type and bitsize.
### `generateEphemeralKeyPair(curve)`
### `generateEphemeralKeyPair(curve, callback)`
- `curve: String`, one of `'P-256'`, `'P-384'`, `'P-521'` is currently supported
- `callback: Function`
Generates an ephemeral public key and returns a function that will compute the shared secret key.
Focuses only on ECDH now, but can be made more general in the future.
Returns an object of the form
Calls back with an object of the form
```js
{
key: Buffer,
@ -72,15 +133,16 @@ Returns an object of the form
}
```
### `keyStretcher(cipherType, hashType, secret)`
### `keyStretcher(cipherType, hashType, secret, callback)`
- `cipherType: String`, one of `'AES-128'`, `'AES-256'`, `'Blowfish'`
- `hashType: String`, one of `'SHA1'`, `SHA256`, `SHA512`
- `secret: Buffer`
- `callback: Function`
Generates a set of keys for each party by stretching the shared key.
Returns an object of the form
Calls back with an object of the form
```js
{
k1: {
@ -95,7 +157,8 @@ Returns an object of the form
}
}
```
### `marshalPublicKey(key[, type])`
### `marshalPublicKey(key[, type], callback)`
- `key: crypto.rsa.RsaPublicKey`
- `type: String`, only `'RSA'` is currently supported
@ -115,11 +178,13 @@ Converts a protobuf serialized public key into its representative object.
Converts a private key object into a protobuf serialized private key.
### `unmarshalPrivateKey(buf)`
### `unmarshalPrivateKey(buf, callback)`
- `buf: Buffer`
- `callback: Function`
Converts a protobuf serialized private key into its representative object.
Converts a protobuf serialized private key into its representative object.
## Contribute

View File

@ -0,0 +1,37 @@
'use strict'
const Benchmark = require('benchmark')
const crypto = require('../src')
const suite = new Benchmark.Suite('ephemeral-keys')
const secrets = []
const curves = ['P-256', 'P-384', 'P-521']
curves.forEach((curve) => {
suite.add(`ephemeral key with secrect ${curve}`, (d) => {
crypto.generateEphemeralKeyPair('P-256', (err, res) => {
if (err) {
throw err
}
res.genSharedKey(res.key, (err, secret) => {
if (err) {
throw err
}
secrets.push(secret)
d.resolve()
})
})
}, {
defer: true
})
})
suite
.on('cycle', (event) => {
console.log(String(event.target))
})
.run({
'async': true
})

View File

@ -0,0 +1,47 @@
'use strict'
const Benchmark = require('benchmark')
const crypto = require('../src')
const suite = new Benchmark.Suite('key-stretcher')
const keys = []
const ciphers = ['AES-128', 'AES-256', 'Blowfish']
const hashes = ['SHA1', 'SHA256', 'SHA512']
crypto.generateEphemeralKeyPair('P-256', (err, res) => {
if (err) {
throw err
}
res.genSharedKey(res.key, (err, secret) => {
if (err) {
throw err
}
ciphers.forEach((cipher) => {
hashes.forEach((hash) => {
suite.add(`keyStretcher ${cipher} ${hash}`, (d) => {
crypto.keyStretcher(cipher, hash, secret, (err, k) => {
if (err) {
throw err
}
keys.push(k)
d.resolve()
})
}, {
defer: true
})
})
})
})
})
suite
.on('cycle', (event) => {
console.log(String(event.target))
})
.run({
'async': true
})

52
benchmarks/rsa.js Normal file
View File

@ -0,0 +1,52 @@
'use strict'
const Benchmark = require('benchmark')
const crypto = require('../src')
const suite = new Benchmark.Suite('rsa')
const keys = []
const bits = [1024, 2048, 4096]
bits.forEach((bit) => {
suite.add(`generateKeyPair ${bit}bits`, (d) => {
crypto.generateKeyPair('RSA', bit, (err, key) => {
if (err) throw err
keys.push(key)
d.resolve()
})
}, {
defer: true
})
})
suite.add('sign and verify', (d) => {
const key = keys[0]
const text = key.genSecret()
key.sign(text, (err, sig) => {
if (err) {
throw err
}
key.public.verify(text, sig, (err, res) => {
if (err) {
throw err
}
if (res !== true) {
throw new Error('failed to verify')
}
d.resolve()
})
})
}, {
defer: true
})
suite
.on('cycle', (event) => {
console.log(String(event.target))
})
.run({
'async': true
})

View File

@ -2,8 +2,13 @@
"name": "libp2p-crypto",
"version": "0.6.1",
"description": "Crypto primitives for libp2p",
"main": "lib/index.js",
"jsnext:main": "src/index.js",
"main": "src/index.js",
"browser": {
"node-webcrypto-ossl": false,
"./src/crypto/webcrypto.js": "./src/crypto/webcrypto-browser.js",
"./src/crypto/hmac.js": "./src/crypto/hmac-browser.js",
"./src/crypto/aes.js": "./src/crypto/aes-browser.js"
},
"scripts": {
"lint": "aegir-lint",
"build": "aegir-build",
@ -25,13 +30,17 @@
"author": "Friedel Ziegelmayer <dignifiedqurie@gmail.com>",
"license": "MIT",
"dependencies": {
"elliptic": "^6.3.2",
"multihashing": "^0.2.1",
"node-forge": "^0.6.39",
"protocol-buffers": "^3.1.6"
"asn1.js": "^4.8.1",
"async": "^2.1.2",
"multihashing-async": "^0.1.0",
"node-webcrypto-ossl": "^1.0.7",
"nodeify": "^1.0.0",
"protocol-buffers": "^3.1.6",
"webcrypto-shim": "github:dignifiedquire/webcrypto-shim#master"
},
"devDependencies": {
"aegir": "^8.0.0",
"aegir": "^9.0.1",
"benchmark": "^2.1.2",
"chai": "^3.5.0",
"pre-commit": "^1.1.3"
},
@ -40,7 +49,7 @@
"test"
],
"engines": {
"node": "^4.0.0"
"node": ">=4.0.0"
},
"repository": {
"type": "git",

7
src/crypto.js Normal file
View File

@ -0,0 +1,7 @@
'use strict'
exports.webcrypto = require('./crypto/webcrypto')()
exports.hmac = require('./crypto/hmac')
exports.ecdh = require('./crypto/ecdh')
exports.aes = require('./crypto/aes')
exports.rsa = require('./crypto/rsa')

View File

@ -1,13 +0,0 @@
enum KeyType {
RSA = 0;
}
message PublicKey {
required KeyType Type = 1;
required bytes Data = 2;
}
message PrivateKey {
required KeyType Type = 1;
required bytes Data = 2;
}

15
src/crypto.proto.js Normal file
View File

@ -0,0 +1,15 @@
'use strict'
module.exports = `enum KeyType {
RSA = 0;
}
message PublicKey {
required KeyType Type = 1;
required bytes Data = 2;
}
message PrivateKey {
required KeyType Type = 1;
required bytes Data = 2;
}`

52
src/crypto/aes-browser.js Normal file
View File

@ -0,0 +1,52 @@
'use strict'
const nodeify = require('nodeify')
const crypto = require('./webcrypto')()
exports.create = function (key, iv, callback) {
nodeify(crypto.subtle.importKey(
'raw',
key,
{
name: 'AES-CTR'
},
false,
['encrypt', 'decrypt']
).then((key) => {
const counter = copy(iv)
return {
encrypt (data, cb) {
nodeify(crypto.subtle.encrypt(
{
name: 'AES-CTR',
counter: counter,
length: 128
},
key,
data
).then((raw) => Buffer.from(raw)), cb)
},
decrypt (data, cb) {
nodeify(crypto.subtle.decrypt(
{
name: 'AES-CTR',
counter: counter,
length: 128
},
key,
data
).then((raw) => Buffer.from(raw)), cb)
}
}
}), callback)
}
function copy (buf) {
const fresh = new Buffer(buf.length)
buf.copy(fresh)
return fresh
}

30
src/crypto/aes.js Normal file
View File

@ -0,0 +1,30 @@
'use strict'
const crypto = require('crypto')
const ciphers = {
16: 'aes-128-ctr',
32: 'aes-256-ctr'
}
exports.create = function (key, iv, callback) {
const name = ciphers[key.length]
if (!name) {
return callback(new Error('Invalid key length'))
}
const cipher = crypto.createCipheriv(name, key, iv)
const decipher = crypto.createDecipheriv(name, key, iv)
const res = {
encrypt (data, cb) {
cb(null, cipher.update(data))
},
decrypt (data, cb) {
cb(null, decipher.update(data))
}
}
callback(null, res)
}

101
src/crypto/ecdh.js Normal file
View File

@ -0,0 +1,101 @@
'use strict'
const crypto = require('./webcrypto')()
const nodeify = require('nodeify')
const BN = require('asn1.js').bignum
const util = require('./util')
const toBase64 = util.toBase64
const toBn = util.toBn
exports.generateEphmeralKeyPair = function (curve, callback) {
nodeify(crypto.subtle.generateKey(
{
name: 'ECDH',
namedCurve: curve
},
true,
['deriveBits']
).then((pair) => {
// forcePrivate is used for testing only
const genSharedKey = (theirPub, forcePrivate, cb) => {
if (typeof forcePrivate === 'function') {
cb = forcePrivate
forcePrivate = undefined
}
const privateKey = forcePrivate || pair.privateKey
nodeify(crypto.subtle.importKey(
'jwk',
unmarshalPublicKey(curve, theirPub),
{
name: 'ECDH',
namedCurve: curve
},
false,
[]
).then((publicKey) => {
return crypto.subtle.deriveBits(
{
name: 'ECDH',
namedCurve: curve,
public: publicKey
},
privateKey,
256
)
}).then((bits) => {
// return p.derive(pub.getPublic()).toBuffer('be')
return Buffer.from(bits)
}), cb)
}
return crypto.subtle.exportKey(
'jwk',
pair.publicKey
).then((publicKey) => {
return {
key: marshalPublicKey(publicKey),
genSharedKey
}
})
}), callback)
}
const curveLengths = {
'P-256': 32,
'P-384': 48,
'P-521': 66
}
// Marshal converts a jwk encodec ECDH public key into the
// form specified in section 4.3.6 of ANSI X9.62. (This is the format
// go-ipfs uses)
function marshalPublicKey (jwk) {
const byteLen = curveLengths[jwk.crv]
return Buffer.concat([
Buffer([4]), // uncompressed point
toBn(jwk.x).toBuffer('be', byteLen),
toBn(jwk.y).toBuffer('be', byteLen)
], 1 + byteLen * 2)
}
// Unmarshal converts a point, serialized by Marshal, into an jwk encoded key
function unmarshalPublicKey (curve, key) {
const byteLen = curveLengths[curve]
if (!key.slice(0, 1).equals(Buffer([4]))) {
throw new Error('Invalid key format')
}
const x = new BN(key.slice(1, byteLen + 1))
const y = new BN(key.slice(1 + byteLen))
return {
kty: 'EC',
crv: curve,
x: toBase64(x),
y: toBase64(y),
ext: true
}
}

View File

@ -0,0 +1,38 @@
'use strict'
const nodeify = require('nodeify')
const crypto = require('./webcrypto')()
const lengths = require('./hmac-lengths')
const hashTypes = {
SHA1: 'SHA-1',
SHA256: 'SHA-256',
SHA512: 'SHA-512'
}
exports.create = function (hashType, secret, callback) {
const hash = hashTypes[hashType]
nodeify(crypto.subtle.importKey(
'raw',
secret,
{
name: 'HMAC',
hash: {name: hash}
},
false,
['sign']
).then((key) => {
return {
digest (data, cb) {
nodeify(crypto.subtle.sign(
{name: 'HMAC'},
key,
data
).then((raw) => Buffer.from(raw)), cb)
},
length: lengths[hashType]
}
}), callback)
}

View File

@ -0,0 +1,7 @@
'use strict'
module.exports = {
SHA1: 20,
SHA256: 32,
SHA512: 64
}

24
src/crypto/hmac.js Normal file
View File

@ -0,0 +1,24 @@
'use strict'
const crypto = require('crypto')
const lengths = require('./hmac-lengths')
exports.create = function (hash, secret, callback) {
const res = {
digest (data, cb) {
const hmac = genFresh()
hmac.update(data)
setImmediate(() => {
cb(null, hmac.digest())
})
},
length: lengths[hash]
}
function genFresh () {
return crypto.createHmac(hash.toLowerCase(), secret)
}
callback(null, res)
}

228
src/crypto/rsa.js Normal file
View File

@ -0,0 +1,228 @@
'use strict'
const nodeify = require('nodeify')
const asn1 = require('asn1.js')
const util = require('./util')
const toBase64 = util.toBase64
const toBn = util.toBn
const crypto = require('./webcrypto')()
exports.generateKey = function (bits, callback) {
nodeify(crypto.subtle.generateKey(
{
name: 'RSASSA-PKCS1-v1_5',
modulusLength: bits,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: 'SHA-256'}
},
true,
['sign', 'verify']
)
.then(exportKey)
.then((keys) => ({
privateKey: keys[0],
publicKey: keys[1]
})), callback)
}
// Takes a jwk key
exports.unmarshalPrivateKey = function (key, callback) {
const privateKey = crypto.subtle.importKey(
'jwk',
key,
{
name: 'RSASSA-PKCS1-v1_5',
hash: {name: 'SHA-256'}
},
true,
['sign']
)
nodeify(Promise.all([
privateKey,
derivePublicFromPrivate(key)
]).then((keys) => exportKey({
privateKey: keys[0],
publicKey: keys[1]
})).then((keys) => ({
privateKey: keys[0],
publicKey: keys[1]
})), callback)
}
exports.getRandomValues = function (arr) {
return Buffer.from(crypto.getRandomValues(arr))
}
exports.hashAndSign = function (key, msg, callback) {
nodeify(crypto.subtle.importKey(
'jwk',
key,
{
name: 'RSASSA-PKCS1-v1_5',
hash: {name: 'SHA-256'}
},
false,
['sign']
).then((privateKey) => {
return crypto.subtle.sign(
{name: 'RSASSA-PKCS1-v1_5'},
privateKey,
Uint8Array.from(msg)
)
}).then((sig) => Buffer.from(sig)), callback)
}
exports.hashAndVerify = function (key, sig, msg, callback) {
nodeify(crypto.subtle.importKey(
'jwk',
key,
{
name: 'RSASSA-PKCS1-v1_5',
hash: {name: 'SHA-256'}
},
false,
['verify']
).then((publicKey) => {
return crypto.subtle.verify(
{name: 'RSASSA-PKCS1-v1_5'},
publicKey,
sig,
msg
)
}), callback)
}
function exportKey (pair) {
return Promise.all([
crypto.subtle.exportKey('jwk', pair.privateKey),
crypto.subtle.exportKey('jwk', pair.publicKey)
])
}
function derivePublicFromPrivate (jwKey) {
return crypto.subtle.importKey(
'jwk',
{
kty: jwKey.kty,
n: jwKey.n,
e: jwKey.e,
alg: jwKey.alg,
kid: jwKey.kid
},
{
name: 'RSASSA-PKCS1-v1_5',
hash: {name: 'SHA-256'}
},
true,
['verify']
)
}
const RSAPrivateKey = asn1.define('RSAPrivateKey', function () {
this.seq().obj(
this.key('version').int(),
this.key('modulus').int(),
this.key('publicExponent').int(),
this.key('privateExponent').int(),
this.key('prime1').int(),
this.key('prime2').int(),
this.key('exponent1').int(),
this.key('exponent2').int(),
this.key('coefficient').int()
)
})
const AlgorithmIdentifier = asn1.define('AlgorithmIdentifier', function () {
this.seq().obj(
this.key('algorithm').objid({
'1.2.840.113549.1.1.1': 'rsa'
}),
this.key('none').optional().null_(),
this.key('curve').optional().objid(),
this.key('params').optional().seq().obj(
this.key('p').int(),
this.key('q').int(),
this.key('g').int()
)
)
})
const PublicKey = asn1.define('RSAPublicKey', function () {
this.seq().obj(
this.key('algorithm').use(AlgorithmIdentifier),
this.key('subjectPublicKey').bitstr()
)
})
const RSAPublicKey = asn1.define('RSAPublicKey', function () {
this.seq().obj(
this.key('modulus').int(),
this.key('publicExponent').int()
)
})
// Convert a PKCS#1 in ASN1 DER format to a JWK key
exports.pkcs1ToJwk = function (bytes) {
const asn1 = RSAPrivateKey.decode(bytes, 'der')
return {
kty: 'RSA',
n: toBase64(asn1.modulus),
e: toBase64(asn1.publicExponent),
d: toBase64(asn1.privateExponent),
p: toBase64(asn1.prime1),
q: toBase64(asn1.prime2),
dp: toBase64(asn1.exponent1),
dq: toBase64(asn1.exponent2),
qi: toBase64(asn1.coefficient),
alg: 'RS256',
kid: '2011-04-29'
}
}
// Convert a JWK key into PKCS#1 in ASN1 DER format
exports.jwkToPkcs1 = function (jwk) {
return RSAPrivateKey.encode({
version: 0,
modulus: toBn(jwk.n),
publicExponent: toBn(jwk.e),
privateExponent: toBn(jwk.d),
prime1: toBn(jwk.p),
prime2: toBn(jwk.q),
exponent1: toBn(jwk.dp),
exponent2: toBn(jwk.dq),
coefficient: toBn(jwk.qi)
}, 'der')
}
// Convert a PKCIX in ASN1 DER format to a JWK key
exports.pkixToJwk = function (bytes) {
const ndata = PublicKey.decode(bytes, 'der')
const asn1 = RSAPublicKey.decode(ndata.subjectPublicKey.data, 'der')
return {
kty: 'RSA',
n: toBase64(asn1.modulus),
e: toBase64(asn1.publicExponent),
alg: 'RS256',
kid: '2011-04-29'
}
}
// Convert a JWK key to PKCIX in ASN1 DER format
exports.jwkToPkix = function (jwk) {
return PublicKey.encode({
algorithm: {
algorithm: 'rsa',
none: null
},
subjectPublicKey: {
data: RSAPublicKey.encode({
modulus: toBn(jwk.n),
publicExponent: toBn(jwk.e)
}, 'der')
}
}, 'der')
}

19
src/crypto/util.js Normal file
View File

@ -0,0 +1,19 @@
'use strict'
const BN = require('asn1.js').bignum
// Convert a BN.js instance to a base64 encoded string without padding
// Adapted from https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#appendix-C
exports.toBase64 = function toBase64 (bn) {
let s = bn.toBuffer('be').toString('base64')
return s
.replace(/(=*)$/, '') // Remove any trailing '='s
.replace(/\+/g, '-') // 62nd char of encoding
.replace(/\//g, '_') // 63rd char of encoding
}
// Convert a base64 encoded string to a BN.js instance
exports.toBn = function toBn (str) {
return new BN(Buffer.from(str, 'base64'))
}

View File

@ -0,0 +1,14 @@
'use strict'
module.exports = function getWebCrypto () {
if (typeof window !== 'undefined') {
// This is only a shim for interfaces, not for functionality
require('webcrypto-shim')(window)
if (window.crypto) {
return window.crypto
}
}
throw new Error('Please use an environment with crypto support')
}

7
src/crypto/webcrypto.js Normal file
View File

@ -0,0 +1,7 @@
'use strict'
module.exports = function getWebCrypto () {
const WebCrypto = require('node-webcrypto-ossl')
const webCrypto = new WebCrypto()
return webCrypto
}

View File

@ -1,36 +1,11 @@
'use strict'
const EC = require('elliptic').ec
const curveMap = {
'P-256': 'p256',
'P-384': 'p384',
'P-521': 'p521'
}
const crypto = require('./crypto')
// Generates an ephemeral public key and returns a function that will compute
// the shared secret key.
//
// Focuses only on ECDH now, but can be made more general in the future.
module.exports = (curveName) => {
const curve = curveMap[curveName]
if (!curve) {
throw new Error('unsupported curve passed')
}
const ec = new EC(curve)
const priv = ec.genKeyPair()
// forcePrivate is used for testing only
const genSharedKey = (theirPub, forcePrivate) => {
const pub = ec.keyFromPublic(theirPub, 'hex')
const p = forcePrivate || priv
return p.derive(pub.getPublic()).toBuffer('be')
}
return {
key: new Buffer(priv.getPublic('hex'), 'hex'),
genSharedKey
}
module.exports = (curve, callback) => {
crypto.ecdh.generateEphmeralKeyPair(curve, callback)
}

View File

@ -1,24 +1,25 @@
'use strict'
const protobuf = require('protocol-buffers')
const fs = require('fs')
const path = require('path')
const pbm = protobuf(fs.readFileSync(path.join(__dirname, './crypto.proto')))
const pbm = protobuf(require('./crypto.proto'))
const c = require('./crypto')
exports.hmac = c.hmac
exports.aes = c.aes
exports.webcrypto = c.webcrypto
exports.utils = require('./utils')
const keys = exports.keys = require('./keys')
exports.keyStretcher = require('./key-stretcher')
exports.generateEphemeralKeyPair = require('./ephemeral-keys')
// Generates a keypair of the given type and bitsize
exports.generateKeyPair = (type, bits) => {
exports.generateKeyPair = (type, bits, cb) => {
let key = keys[type.toLowerCase()]
if (!key) {
throw new Error('invalid or unsupported key type')
return cb(new Error('invalid or unsupported key type'))
}
return key.generateKeyPair(bits)
key.generateKeyPair(bits, cb)
}
// Converts a protobuf serialized public key into its
@ -43,22 +44,19 @@ exports.marshalPublicKey = (key, type) => {
throw new Error('invalid or unsupported key type')
}
return pbm.PublicKey.encode({
Type: pbm.KeyType.RSA,
Data: key.marshal()
})
return key.bytes
}
// Converts a protobuf serialized private key into its
// representative object
exports.unmarshalPrivateKey = (buf) => {
exports.unmarshalPrivateKey = (buf, callback) => {
const decoded = pbm.PrivateKey.decode(buf)
switch (decoded.Type) {
case pbm.KeyType.RSA:
return keys.rsa.unmarshalRsaPrivateKey(decoded.Data)
return keys.rsa.unmarshalRsaPrivateKey(decoded.Data, callback)
default:
throw new Error('invalid or unsupported key type')
callback(new Error('invalid or unsupported key type'))
}
}
@ -71,8 +69,5 @@ exports.marshalPrivateKey = (key, type) => {
throw new Error('invalid or unsupported key type')
}
return pbm.PrivateKey.encode({
Type: pbm.KeyType.RSA,
Data: key.marshal()
})
return key.bytes
}

View File

@ -1,7 +1,7 @@
'use strict'
const forge = require('node-forge')
const createBuffer = forge.util.createBuffer
const crypto = require('./crypto')
const whilst = require('async/whilst')
const cipherMap = {
'AES-128': {
@ -18,78 +18,91 @@ const cipherMap = {
}
}
const hashMap = {
SHA1: 'sha1',
SHA256: 'sha256',
// workaround for https://github.com/digitalbazaar/forge/issues/401
SHA512: forge.md.sha512.create()
}
// Generates a set of keys for each party by stretching the shared key.
// (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey)
module.exports = (cipherType, hashType, secret) => {
module.exports = (cipherType, hash, secret, callback) => {
const cipher = cipherMap[cipherType]
const hash = hashMap[hashType]
if (!cipher) {
throw new Error('unkown cipherType passed')
return callback(new Error('unkown cipherType passed'))
}
if (!hash) {
throw new Error('unkown hashType passed')
}
if (Buffer.isBuffer(secret)) {
secret = createBuffer(secret.toString('binary'))
return callback(new Error('unkown hashType passed'))
}
const cipherKeySize = cipher.keySize
const ivSize = cipher.ivSize
const hmacKeySize = 20
const seed = 'key expansion'
const seed = Buffer.from('key expansion')
const resultLength = 2 * (ivSize + cipherKeySize + hmacKeySize)
const m = forge.hmac.create()
m.start(hash, secret)
m.update(seed)
let a = m.digest().bytes()
const result = createBuffer()
let j = 0
for (; j < resultLength;) {
m.start(hash, secret)
m.update(a)
m.update(seed)
const b = createBuffer(m.digest(), 'raw')
let todo = b.length()
if (j + todo > resultLength) {
todo = resultLength - j
crypto.hmac.create(hash, secret, (err, m) => {
if (err) {
return callback(err)
}
result.putBytes(b.getBytes(todo))
m.digest(seed, (err, a) => {
if (err) {
return callback(err)
}
j += todo
let result = []
let j = 0
m.start(hash, secret)
m.update(a)
a = m.digest().bytes()
}
whilst(
() => j < resultLength,
stretch,
finish
)
const half = resultLength / 2
const r1 = createBuffer(result.getBytes(half))
const r2 = createBuffer(result.getBytes())
function stretch (cb) {
m.digest(Buffer.concat([a, seed]), (err, b) => {
if (err) {
return cb(err)
}
const createKey = (res) => ({
iv: new Buffer(res.getBytes(ivSize), 'binary'),
cipherKey: new Buffer(res.getBytes(cipherKeySize), 'binary'),
macKey: new Buffer(res.getBytes(), 'binary')
let todo = b.length
if (j + todo > resultLength) {
todo = resultLength - j
}
result.push(b)
j += todo
m.digest(a, (err, _a) => {
if (err) {
return cb(err)
}
a = _a
cb()
})
})
}
function finish (err) {
if (err) {
return callback(err)
}
const half = resultLength / 2
const resultBuffer = Buffer.concat(result)
const r1 = resultBuffer.slice(0, half)
const r2 = resultBuffer.slice(half, resultLength)
const createKey = (res) => ({
iv: res.slice(0, ivSize),
cipherKey: res.slice(ivSize, ivSize + cipherKeySize),
macKey: res.slice(ivSize + cipherKeySize)
})
callback(null, {
k1: createKey(r1),
k2: createKey(r2)
})
}
})
})
return {
k1: createKey(r1),
k2: createKey(r2)
}
}

View File

@ -1,35 +1,23 @@
'use strict'
const forge = require('node-forge')
const multihashing = require('multihashing-async')
const protobuf = require('protocol-buffers')
const fs = require('fs')
const path = require('path')
const utils = require('../utils')
const pki = forge.pki
const rsa = pki.rsa
const pbm = protobuf(fs.readFileSync(path.join(__dirname, '../crypto.proto')))
const crypto = require('../crypto').rsa
const pbm = protobuf(require('../crypto.proto'))
class RsaPublicKey {
constructor (k) {
this._key = k
constructor (key) {
this._key = key
}
verify (data, sig) {
const md = forge.md.sha256.create()
if (Buffer.isBuffer(data)) {
md.update(data.toString('binary'), 'binary')
} else {
md.update(data)
}
return this._key.verify(md.digest().bytes(), sig)
verify (data, sig, callback) {
ensure(callback)
crypto.hashAndVerify(this._key, sig, data, callback)
}
marshal () {
return new Buffer(forge.asn1.toDer(pki.publicKeyToAsn1(this._key)).bytes(), 'binary')
return crypto.jwkToPkix(this._key)
}
get bytes () {
@ -47,34 +35,27 @@ class RsaPublicKey {
return this.bytes.equals(key.bytes)
}
hash () {
return utils.keyHash(this.bytes)
hash (callback) {
ensure(callback)
multihashing(this.bytes, 'sha2-256', callback)
}
}
class RsaPrivateKey {
constructor (privKey, pubKey) {
this._privateKey = privKey
if (pubKey) {
this._publicKey = pubKey
} else {
this._publicKey = forge.pki.setRsaPublicKey(privKey.n, privKey.e)
}
// key - Object of the jwk format
// publicKey - Buffer of the spki format
constructor (key, publicKey) {
this._key = key
this._publicKey = publicKey
}
genSecret () {
return forge.random.getBytesSync(16)
return crypto.getRandomValues(new Uint8Array(16))
}
sign (message) {
const md = forge.md.sha256.create()
if (Buffer.isBuffer(message)) {
md.update(message.toString('binary'), 'binary')
} else {
md.update(message)
}
const raw = this._privateKey.sign(md, 'RSASSA-PKCS1-V1_5')
return new Buffer(raw, 'binary')
sign (message, callback) {
ensure(callback)
crypto.hashAndSign(this._key, message, callback)
}
get public () {
@ -85,12 +66,12 @@ class RsaPrivateKey {
return new RsaPublicKey(this._publicKey)
}
decrypt (bytes) {
return this._privateKey.decrypt(bytes, 'RSAES-PKCS1-V1_5')
decrypt (msg, callback) {
crypto.decrypt(this._key, msg, callback)
}
marshal () {
return new Buffer(forge.asn1.toDer(pki.privateKeyToAsn1(this._privateKey)).bytes(), 'binary')
return crypto.jwkToPkcs1(this._key)
}
get bytes () {
@ -104,32 +85,43 @@ class RsaPrivateKey {
return this.bytes.equals(key.bytes)
}
hash () {
return utils.keyHash(this.bytes)
hash (callback) {
ensure(callback)
multihashing(this.bytes, 'sha2-256', callback)
}
}
function unmarshalRsaPrivateKey (bytes) {
if (Buffer.isBuffer(bytes)) {
bytes = forge.util.createBuffer(bytes.toString('binary'))
}
const key = pki.privateKeyFromAsn1(forge.asn1.fromDer(bytes))
function unmarshalRsaPrivateKey (bytes, callback) {
const jwk = crypto.pkcs1ToJwk(bytes)
crypto.unmarshalPrivateKey(jwk, (err, keys) => {
if (err) {
return callback(err)
}
return new RsaPrivateKey(key)
callback(null, new RsaPrivateKey(keys.privateKey, keys.publicKey))
})
}
function unmarshalRsaPublicKey (bytes) {
if (Buffer.isBuffer(bytes)) {
bytes = forge.util.createBuffer(bytes.toString('binary'))
}
const key = pki.publicKeyFromAsn1(forge.asn1.fromDer(bytes))
const jwk = crypto.pkixToJwk(bytes)
return new RsaPublicKey(key)
return new RsaPublicKey(jwk)
}
function generateKeyPair (bits) {
const p = rsa.generateKeyPair({bits})
return new RsaPrivateKey(p.privateKey, p.publicKey)
function generateKeyPair (bits, cb) {
crypto.generateKey(bits, (err, keys) => {
if (err) {
return cb(err)
}
cb(null, new RsaPrivateKey(keys.privateKey, keys.publicKey))
})
}
function ensure (cb) {
if (typeof cb !== 'function') {
throw new Error('callback is required')
}
}
module.exports = {

View File

@ -1,8 +0,0 @@
'use strict'
const multihashing = require('multihashing')
// Hashes a key
exports.keyHash = (bytes) => {
return multihashing(bytes, 'sha2-256')
}

153
stats.md Normal file
View File

@ -0,0 +1,153 @@
# Stats
## Size
| | non-minified | minified |
|-------|--------------|----------|
|before | `1.8M` | `949K` |
|after | `606K` | `382K` |
## Performance
### RSA
#### Before
##### Node `6.6.0`
```
generateKeyPair 1024bits x 3.51 ops/sec ±29.45% (22 runs sampled)
generateKeyPair 2048bits x 0.17 ops/sec ±145.40% (5 runs sampled)
generateKeyPair 4096bits x 0.02 ops/sec ±96.53% (5 runs sampled)
sign and verify x 95.98 ops/sec ±1.51% (71 runs sampled)
```
##### Browser (Chrome `53.0.2785.116`)
```
generateKeyPair 1024bits x 3.56 ops/sec ±27.16% (23 runs sampled)
generateKeyPair 2048bits x 0.49 ops/sec ±69.32% (8 runs sampled)
generateKeyPair 4096bits x 0.03 ops/sec ±77.11% (5 runs sampled)
sign and verify x 109 ops/sec ±2.00% (53 runs sampled)
```
#### After
##### Node `6.6.0`
```
generateKeyPair 1024bits x 42.45 ops/sec ±9.87% (52 runs sampled)
generateKeyPair 2048bits x 7.46 ops/sec ±23.80% (16 runs sampled)
generateKeyPair 4096bits x 1.50 ops/sec ±58.59% (13 runs sampled)
sign and verify x 1,080 ops/sec ±2.23% (74 runs sampled)
```
##### Browser (Chrome `53.0.2785.116`)
```
generateKeyPair 1024bits x 5.89 ops/sec ±18.94% (19 runs sampled)
generateKeyPair 2048bits x 1.32 ops/sec ±36.84% (10 runs sampled)
generateKeyPair 4096bits x 0.20 ops/sec ±62.49% (5 runs sampled)
sign and verify x 608 ops/sec ±6.75% (56 runs sampled)
```
### Key Stretcher
#### Before
##### Node `6.6.0`
```
keyStretcher AES-128 SHA1 x 3,863 ops/sec ±3.80% (70 runs sampled)
keyStretcher AES-128 SHA256 x 3,862 ops/sec ±5.33% (64 runs sampled)
keyStretcher AES-128 SHA512 x 3,369 ops/sec ±1.73% (73 runs sampled)
keyStretcher AES-256 SHA1 x 3,008 ops/sec ±4.81% (67 runs sampled)
keyStretcher AES-256 SHA256 x 2,900 ops/sec ±7.01% (64 runs sampled)
keyStretcher AES-256 SHA512 x 2,553 ops/sec ±4.45% (73 runs sampled)
keyStretcher Blowfish SHA1 x 28,045 ops/sec ±7.32% (61 runs sampled)
keyStretcher Blowfish SHA256 x 18,860 ops/sec ±5.36% (67 runs sampled)
keyStretcher Blowfish SHA512 x 12,142 ops/sec ±12.44% (72 runs sampled)
```
##### Browser (Chrome `53.0.2785.116`)
```
keyStretcher AES-128 SHA1 x 4,168 ops/sec ±4.08% (49 runs sampled)
keyStretcher AES-128 SHA256 x 4,239 ops/sec ±6.36% (48 runs sampled)
keyStretcher AES-128 SHA512 x 3,600 ops/sec ±5.15% (51 runs sampled)
keyStretcher AES-256 SHA1 x 3,009 ops/sec ±6.82% (48 runs sampled)
keyStretcher AES-256 SHA256 x 3,086 ops/sec ±9.56% (19 runs sampled)
keyStretcher AES-256 SHA512 x 2,470 ops/sec ±2.22% (54 runs sampled)
keyStretcher Blowfish SHA1 x 7,143 ops/sec ±15.17% (9 runs sampled)
keyStretcher Blowfish SHA256 x 17,846 ops/sec ±4.74% (46 runs sampled)
keyStretcher Blowfish SHA512 x 7,726 ops/sec ±1.81% (50 runs sampled)
```
#### After
##### Node `6.6.0`
```
keyStretcher AES-128 SHA1 x 6,680 ops/sec ±3.62% (65 runs sampled)
keyStretcher AES-128 SHA256 x 8,124 ops/sec ±4.37% (66 runs sampled)
keyStretcher AES-128 SHA512 x 11,683 ops/sec ±4.56% (66 runs sampled)
keyStretcher AES-256 SHA1 x 5,531 ops/sec ±4.69% (68 runs sampled)
keyStretcher AES-256 SHA256 x 6,725 ops/sec ±4.87% (66 runs sampled)
keyStretcher AES-256 SHA512 x 9,042 ops/sec ±3.87% (64 runs sampled)
keyStretcher Blowfish SHA1 x 40,757 ops/sec ±5.38% (60 runs sampled)
keyStretcher Blowfish SHA256 x 41,845 ops/sec ±4.89% (64 runs sampled)
keyStretcher Blowfish SHA512 x 42,345 ops/sec ±4.86% (63 runs sampled)
```
##### Browser (Chrome `53.0.2785.116`)
```
keyStretcher AES-128 SHA1 x 479 ops/sec ±2.12% (54 runs sampled)
keyStretcher AES-128 SHA256 x 668 ops/sec ±2.02% (53 runs sampled)
keyStretcher AES-128 SHA512 x 1,112 ops/sec ±1.61% (54 runs sampled)
keyStretcher AES-256 SHA1 x 460 ops/sec ±1.37% (54 runs sampled)
keyStretcher AES-256 SHA256 x 596 ops/sec ±1.56% (54 runs sampled)
keyStretcher AES-256 SHA512 x 808 ops/sec ±3.27% (52 runs sampled)
keyStretcher Blowfish SHA1 x 3,015 ops/sec ±3.51% (52 runs sampled)
keyStretcher Blowfish SHA256 x 2,755 ops/sec ±3.82% (53 runs sampled)
keyStretcher Blowfish SHA512 x 2,955 ops/sec ±5.35% (51 runs sampled)
```
### Ephemeral Keys
#### Before
##### Node `6.6.0`
```
ephemeral key with secrect P-256 x 89.93 ops/sec ±39.45% (72 runs sampled)
ephemeral key with secrect P-384 x 110 ops/sec ±1.28% (71 runs sampled)
ephemeral key with secrect P-521 x 112 ops/sec ±1.70% (72 runs sampled)
```
##### Browser (Chrome `53.0.2785.116`)
```
ephemeral key with secrect P-256 x 6.27 ops/sec ±15.89% (35 runs sampled)
ephemeral key with secrect P-384 x 6.84 ops/sec ±1.21% (35 runs sampled)
ephemeral key with secrect P-521 x 6.60 ops/sec ±1.84% (34 runs sampled)
```
#### After
##### Node `6.6.0`
```
ephemeral key with secrect P-256 x 555 ops/sec ±1.61% (75 runs sampled)
ephemeral key with secrect P-384 x 547 ops/sec ±4.40% (68 runs sampled)
ephemeral key with secrect P-521 x 583 ops/sec ±4.84% (72 runs sampled)
```
##### Browser (Chrome `53.0.2785.116`)
```
ephemeral key with secrect P-256 x 796 ops/sec ±2.36% (53 runs sampled)
ephemeral key with secrect P-384 x 788 ops/sec ±2.66% (53 runs sampled)
ephemeral key with secrect P-521 x 808 ops/sec ±1.83% (54 runs sampled)
```

37
test/aes.spec.js Normal file
View File

@ -0,0 +1,37 @@
/* eslint max-nested-callbacks: ["error", 8] */
/* eslint-env mocha */
'use strict'
const expect = require('chai').expect
const crypto = require('../src')
const bytes = {
16: 'AES-128',
32: 'AES-256'
}
describe('AES-CTR', () => {
Object.keys(bytes).forEach((byte) => {
it(`${bytes[byte]} - encrypt and decrypt`, (done) => {
const key = new Buffer(parseInt(byte, 10))
key.fill(5)
const iv = new Buffer(16)
iv.fill(1)
crypto.aes.create(key, iv, (err, cipher) => {
expect(err).to.not.exist
cipher.encrypt(new Buffer('hello'), (err, res) => {
expect(err).to.not.exist
cipher.decrypt(res, (err, res) => {
expect(err).to.not.exist
expect(res).to.be.eql(new Buffer('hello'))
done()
})
})
})
})
})
})

View File

@ -1,44 +1,53 @@
/* eslint max-nested-callbacks: ["error", 8] */
/* eslint-env mocha */
'use strict'
const expect = require('chai').expect
const EC = require('elliptic').ec
const parallel = require('async/parallel')
const crypto = require('../src')
const fixtures = require('./fixtures/go-elliptic-key')
const crypto = require('../src')
const curves = ['P-256', 'P-384'] // 'P-521' fails in tests :( no clue why
const lengths = {
'P-256': 65,
'P-384': 97,
'P-521': 133
}
describe('generateEphemeralKeyPair', () => {
it('returns a function that generates a shared secret', () => {
const res = crypto.generateEphemeralKeyPair('P-256')
const ourPublic = '044374add0df35706db7dade25f3959fc051d2ef5166f8a6a0aa632d0ab41cdb4d30e1a064e121ac56155235a6b8d4c5d8fe35e019f507f4e2ff1445e229d7af43'
curves.forEach((curve) => {
it(`generate and shared key ${curve}`, (done) => {
parallel([
(cb) => crypto.generateEphemeralKeyPair(curve, cb),
(cb) => crypto.generateEphemeralKeyPair(curve, cb)
], (err, keys) => {
expect(err).to.not.exist
expect(keys[0].key).to.have.length(lengths[curve])
expect(keys[1].key).to.have.length(lengths[curve])
expect(
res.genSharedKey(ourPublic)
).to.have.length(32)
expect(
res.key
).to.exist
keys[0].genSharedKey(keys[1].key, (err, shared) => {
expect(err).to.not.exist
expect(shared).to.have.length(32)
done()
})
})
})
})
describe('go interop', () => {
it('generates a shared secret', () => {
it('generates a shared secret', (done) => {
const curve = fixtures.curve
const ec = new EC(fixtures.curveJs)
const bobPrivate = ec.keyFromPrivate(fixtures.bob.private, 'binary')
const alice = crypto.generateEphemeralKeyPair(curve)
const bob = {
key: fixtures.bob.public,
// this is using bobs private key from go ipfs
// instead of alices
genSharedKey: (key) => alice.genSharedKey(key, bobPrivate)
}
crypto.generateEphemeralKeyPair(curve, (err, alice) => {
expect(err).to.not.exist
const s1 = alice.genSharedKey(bob.key)
const s2 = bob.genSharedKey(alice.key)
expect(s1.equals(s2)).to.be.eql(true)
alice.genSharedKey(fixtures.bob.public, (err, s1) => {
expect(err).to.not.exist
expect(s1).to.have.length(32)
done()
})
})
})
})
})

View File

@ -2,13 +2,12 @@
module.exports = {
curve: 'P-256',
curveJs: 'p256',
bob: {
private: [
231, 236, 69, 16, 13, 92, 76, 83, 75, 40, 32, 71, 235, 187, 29, 214, 98, 231, 42, 5, 80, 89, 58, 175, 8, 95, 86, 50, 44, 214, 4, 172
181, 217, 162, 151, 225, 36, 53, 253, 107, 66, 27, 27, 232, 72, 0, 0, 103, 167, 84, 62, 203, 91, 97, 137, 131, 193, 230, 126, 98, 242, 216, 170
],
public: new Buffer([
4, 160, 169, 215, 85, 152, 11, 209, 69, 105, 17, 51, 49, 83, 214, 171, 157, 73, 165, 85, 28, 196, 161, 234, 87, 149, 139, 76, 123, 37, 174, 194, 67, 167, 18, 34, 164, 35, 171, 164, 238, 141, 199, 206, 86, 130, 183, 88, 63, 121, 110, 150, 229, 10, 213, 176, 181, 1, 98, 20, 246, 85, 212, 200, 229
4, 53, 59, 128, 56, 162, 250, 72, 141, 206, 117, 232, 57, 96, 39, 39, 247, 7, 27, 57, 251, 232, 120, 186, 21, 239, 176, 139, 195, 129, 125, 85, 11, 188, 191, 32, 227, 0, 6, 163, 101, 68, 208, 1, 43, 131, 124, 112, 102, 91, 104, 79, 16, 119, 152, 208, 4, 147, 155, 83, 20, 146, 104, 55, 90
])
}
}

View File

@ -16,5 +16,16 @@ module.exports = {
key: new Buffer([
8, 0, 18, 94, 48, 92, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 75, 0, 48, 72, 2, 65, 0, 230, 157, 160, 242, 74, 222, 87, 0, 77, 180, 91, 175, 217, 166, 2, 95, 193, 239, 195, 140, 224, 57, 84, 207, 46, 172, 113, 196, 20, 133, 117, 205, 45, 7, 224, 41, 40, 195, 254, 124, 14, 84, 223, 147, 67, 198, 48, 36, 53, 161, 112, 46, 153, 90, 19, 123, 94, 247, 5, 116, 1, 238, 32, 15, 2, 3, 1, 0, 1
])
},
verify: {
signature: new Buffer([
3, 116, 81, 57, 91, 194, 7, 1, 230, 236, 229, 142, 36, 209, 208, 107, 47, 52, 164, 236, 139, 35, 155, 97, 43, 64, 145, 91, 19, 218, 149, 63, 99, 164, 191, 110, 145, 37, 18, 7, 98, 112, 144, 35, 29, 186, 169, 150, 165, 88, 145, 170, 197, 110, 42, 163, 188, 10, 42, 63, 34, 93, 91, 94, 199, 110, 10, 82, 238, 80, 93, 93, 77, 130, 22, 216, 229, 172, 36, 229, 82, 162, 20, 78, 19, 46, 82, 243, 43, 80, 115, 125, 145, 231, 194, 224, 30, 187, 55, 228, 74, 52, 203, 191, 254, 148, 136, 218, 62, 147, 171, 130, 251, 181, 105, 29, 238, 207, 197, 249, 61, 105, 202, 172, 160, 174, 43, 124, 115, 130, 169, 30, 76, 41, 52, 200, 2, 26, 53, 190, 43, 20, 203, 10, 217, 250, 47, 102, 92, 103, 197, 22, 108, 184, 74, 218, 82, 202, 180, 98, 13, 114, 12, 92, 1, 139, 150, 170, 8, 92, 32, 116, 168, 219, 157, 162, 28, 77, 29, 29, 74, 136, 144, 49, 173, 245, 253, 76, 167, 200, 169, 163, 7, 49, 133, 120, 99, 191, 53, 10, 66, 26, 234, 240, 139, 235, 134, 30, 55, 248, 150, 100, 242, 150, 159, 198, 44, 78, 150, 7, 133, 139, 59, 76, 3, 225, 94, 13, 89, 122, 34, 95, 95, 107, 74, 169, 171, 169, 222, 25, 191, 182, 148, 116, 66, 67, 102, 12, 193, 217, 247, 243, 148, 233, 161, 157
]),
data: new Buffer([
10, 16, 27, 128, 228, 220, 147, 176, 53, 105, 175, 171, 32, 213, 35, 236, 203, 60, 18, 171, 2, 8, 0, 18, 166, 2, 48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 181, 113, 138, 108, 208, 103, 166, 102, 37, 36, 204, 250, 228, 165, 44, 64, 176, 210, 205, 141, 241, 55, 200, 110, 98, 68, 85, 199, 254, 19, 86, 204, 63, 250, 167, 38, 59, 249, 146, 228, 73, 171, 63, 18, 96, 104, 191, 137, 186, 244, 255, 90, 16, 119, 195, 52, 177, 213, 254, 187, 174, 84, 174, 173, 12, 236, 53, 234, 3, 209, 82, 37, 78, 111, 214, 135, 76, 195, 9, 242, 134, 188, 153, 84, 139, 231, 51, 146, 177, 60, 12, 25, 158, 91, 215, 152, 7, 0, 84, 35, 36, 230, 227, 67, 198, 72, 50, 110, 37, 209, 98, 193, 65, 93, 173, 199, 4, 198, 102, 99, 148, 144, 224, 217, 114, 53, 144, 245, 251, 114, 211, 20, 82, 163, 123, 75, 16, 192, 106, 213, 128, 2, 11, 200, 203, 84, 41, 199, 224, 155, 171, 217, 64, 109, 116, 188, 151, 183, 173, 52, 205, 164, 93, 13, 251, 65, 182, 160, 154, 185, 239, 33, 184, 84, 159, 105, 101, 173, 194, 251, 123, 84, 92, 66, 61, 180, 45, 104, 162, 224, 214, 233, 64, 220, 165, 2, 104, 116, 150, 2, 234, 203, 112, 21, 124, 23, 48, 66, 30, 63, 30, 36, 246, 135, 203, 218, 115, 22, 189, 39, 39, 125, 205, 65, 222, 220, 77, 18, 84, 121, 161, 153, 125, 25, 139, 137, 170, 239, 150, 106, 119, 168, 216, 140, 113, 121, 26, 53, 118, 110, 53, 192, 244, 252, 145, 85, 2, 3, 1, 0, 1, 26, 17, 80, 45, 50, 53, 54, 44, 80, 45, 51, 56, 52, 44, 80, 45, 53, 50, 49, 34, 24, 65, 69, 83, 45, 50, 53, 54, 44, 65, 69, 83, 45, 49, 50, 56, 44, 66, 108, 111, 119, 102, 105, 115, 104, 42, 13, 83, 72, 65, 50, 53, 54, 44, 83, 72, 65, 53, 49, 50, 10, 16, 220, 83, 240, 105, 6, 203, 78, 83, 210, 115, 6, 106, 98, 82, 1, 161, 18, 171, 2, 8, 0, 18, 166, 2, 48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 185, 234, 19, 191, 164, 33, 65, 94, 87, 42, 74, 83, 224, 25, 142, 44, 26, 7, 92, 242, 189, 42, 170, 197, 178, 92, 45, 240, 107, 141, 128, 59, 122, 252, 48, 140, 4, 85, 85, 203, 3, 197, 8, 127, 120, 98, 44, 169, 135, 196, 70, 137, 117, 180, 177, 134, 170, 35, 165, 88, 105, 30, 114, 138, 11, 96, 68, 99, 18, 149, 223, 166, 105, 12, 176, 77, 48, 214, 22, 236, 17, 154, 213, 209, 158, 169, 202, 5, 100, 210, 83, 90, 201, 38, 205, 246, 231, 106, 63, 86, 222, 143, 157, 173, 62, 4, 85, 232, 20, 188, 6, 209, 186, 132, 192, 117, 146, 181, 233, 26, 0, 240, 138, 206, 91, 170, 114, 137, 217, 132, 139, 242, 144, 213, 103, 101, 190, 146, 188, 250, 188, 134, 255, 70, 125, 78, 65, 136, 239, 190, 206, 139, 155, 140, 163, 233, 170, 247, 205, 87, 209, 19, 29, 173, 10, 147, 43, 28, 90, 46, 6, 197, 217, 186, 66, 68, 126, 76, 64, 184, 8, 170, 23, 79, 243, 223, 119, 133, 118, 50, 226, 44, 246, 176, 10, 161, 219, 83, 54, 68, 248, 5, 14, 177, 114, 54, 63, 11, 71, 136, 142, 56, 151, 123, 230, 61, 80, 15, 180, 42, 49, 220, 148, 99, 231, 20, 230, 220, 85, 207, 187, 37, 210, 137, 171, 125, 71, 14, 53, 100, 91, 83, 209, 50, 132, 165, 253, 25, 161, 5, 97, 164, 163, 83, 95, 53, 2, 3, 1, 0, 1, 26, 17, 80, 45, 50, 53, 54, 44, 80, 45, 51, 56, 52, 44, 80, 45, 53, 50, 49, 34, 15, 65, 69, 83, 45, 50, 53, 54, 44, 65, 69, 83, 45, 49, 50, 56, 42, 13, 83, 72, 65, 50, 53, 54, 44, 83, 72, 65, 53, 49, 50, 4, 97, 54, 203, 112, 136, 34, 231, 162, 19, 154, 131, 27, 105, 26, 121, 238, 120, 25, 203, 66, 232, 53, 198, 20, 19, 96, 119, 218, 90, 64, 170, 3, 132, 116, 1, 87, 116, 232, 165, 161, 198, 117, 167, 60, 145, 1, 253, 108, 50, 150, 117, 8, 140, 133, 48, 30, 236, 36, 84, 186, 22, 144, 87, 101
]),
publicKey: new Buffer([
8, 0, 18, 166, 2, 48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 181, 113, 138, 108, 208, 103, 166, 102, 37, 36, 204, 250, 228, 165, 44, 64, 176, 210, 205, 141, 241, 55, 200, 110, 98, 68, 85, 199, 254, 19, 86, 204, 63, 250, 167, 38, 59, 249, 146, 228, 73, 171, 63, 18, 96, 104, 191, 137, 186, 244, 255, 90, 16, 119, 195, 52, 177, 213, 254, 187, 174, 84, 174, 173, 12, 236, 53, 234, 3, 209, 82, 37, 78, 111, 214, 135, 76, 195, 9, 242, 134, 188, 153, 84, 139, 231, 51, 146, 177, 60, 12, 25, 158, 91, 215, 152, 7, 0, 84, 35, 36, 230, 227, 67, 198, 72, 50, 110, 37, 209, 98, 193, 65, 93, 173, 199, 4, 198, 102, 99, 148, 144, 224, 217, 114, 53, 144, 245, 251, 114, 211, 20, 82, 163, 123, 75, 16, 192, 106, 213, 128, 2, 11, 200, 203, 84, 41, 199, 224, 155, 171, 217, 64, 109, 116, 188, 151, 183, 173, 52, 205, 164, 93, 13, 251, 65, 182, 160, 154, 185, 239, 33, 184, 84, 159, 105, 101, 173, 194, 251, 123, 84, 92, 66, 61, 180, 45, 104, 162, 224, 214, 233, 64, 220, 165, 2, 104, 116, 150, 2, 234, 203, 112, 21, 124, 23, 48, 66, 30, 63, 30, 36, 246, 135, 203, 218, 115, 22, 189, 39, 39, 125, 205, 65, 222, 220, 77, 18, 84, 121, 161, 153, 125, 25, 139, 137, 170, 239, 150, 106, 119, 168, 216, 140, 113, 121, 26, 53, 118, 110, 53, 192, 244, 252, 145, 85, 2, 3, 1, 0, 1
])
}
}

24
test/hmac.spec.js Normal file
View File

@ -0,0 +1,24 @@
/* eslint max-nested-callbacks: ["error", 8] */
/* eslint-env mocha */
'use strict'
const expect = require('chai').expect
const crypto = require('../src')
const hashes = ['SHA1', 'SHA256', 'SHA512']
describe('HMAC', () => {
hashes.forEach((hash) => {
it(`${hash} - sign and verify`, (done) => {
crypto.hmac.create(hash, new Buffer('secret'), (err, hmac) => {
expect(err).to.not.exist
hmac.digest(new Buffer('hello world'), (err, sig) => {
expect(err).to.not.exist
expect(sig).to.have.length(hmac.length)
done()
})
})
})
})
})

View File

@ -1,3 +1,4 @@
/* eslint max-nested-callbacks: ["error", 8] */
/* eslint-env mocha */
'use strict'
@ -8,8 +9,14 @@ const fixtures = require('./fixtures/go-key-rsa')
describe('libp2p-crypto', () => {
let key
before(() => {
key = crypto.generateKeyPair('RSA', 2048)
before((done) => {
crypto.generateKeyPair('RSA', 2048, (err, _key) => {
if (err) {
return done(err)
}
key = _key
done()
})
})
it('marshalPublicKey and unmarshalPublicKey', () => {
@ -18,43 +25,67 @@ describe('libp2p-crypto', () => {
expect(key2.equals(key.public)).to.be.eql(true)
})
it('marshalPrivateKey and unmarshalPrivateKey', () => {
const key2 = crypto.unmarshalPrivateKey(crypto.marshalPrivateKey(key))
it('marshalPrivateKey and unmarshalPrivateKey', (done) => {
crypto.unmarshalPrivateKey(crypto.marshalPrivateKey(key), (err, key2) => {
if (err) {
return done(err)
}
expect(key2.equals(key)).to.be.eql(true)
expect(key2.equals(key)).to.be.eql(true)
expect(key2.public.equals(key.public)).to.be.eql(true)
done()
})
})
// marshalled keys seem to be slightly different
// unsure as to if this is just a difference in encoding
// or a bug
describe('go interop', () => {
it('unmarshals private key', () => {
const key = crypto.unmarshalPrivateKey(fixtures.private.key)
const hash = fixtures.private.hash
it('unmarshals private key', (done) => {
crypto.unmarshalPrivateKey(fixtures.private.key, (err, key) => {
if (err) {
return done(err)
}
const hash = fixtures.private.hash
expect(fixtures.private.key).to.be.eql(key.bytes)
expect(
key.hash()
).to.be.eql(
hash
)
key.hash((err, digest) => {
if (err) {
return done(err)
}
expect(digest).to.be.eql(hash)
done()
})
})
})
it('unmarshals public key', () => {
it('unmarshals public key', (done) => {
const key = crypto.unmarshalPublicKey(fixtures.public.key)
const hash = fixtures.public.hash
expect(
key.hash()
).to.be.eql(
hash
)
expect(crypto.marshalPublicKey(key)).to.be.eql(fixtures.public.key)
key.hash((err, digest) => {
if (err) {
return done(err)
}
expect(digest).to.be.eql(hash)
done()
})
})
it('unmarshal -> marshal, private key', () => {
const key = crypto.unmarshalPrivateKey(fixtures.private.key)
const marshalled = crypto.marshalPrivateKey(key)
expect(
fixtures.private.key.equals(marshalled)
).to.be.eql(
true
)
it('unmarshal -> marshal, private key', (done) => {
crypto.unmarshalPrivateKey(fixtures.private.key, (err, key) => {
if (err) {
return done(err)
}
const marshalled = crypto.marshalPrivateKey(key)
expect(marshalled).to.be.eql(fixtures.private.key)
done()
})
})
it('unmarshal -> marshal, public key', () => {

View File

@ -11,15 +11,38 @@ describe('keyStretcher', () => {
describe('generate', () => {
const ciphers = ['AES-128', 'AES-256', 'Blowfish']
const hashes = ['SHA1', 'SHA256', 'SHA512']
const res = crypto.generateEphemeralKeyPair('P-256')
const secret = res.genSharedKey(res.key)
let res
let secret
before((done) => {
crypto.generateEphemeralKeyPair('P-256', (err, _res) => {
if (err) {
return done(err)
}
res = _res
res.genSharedKey(res.key, (err, _secret) => {
if (err) {
return done(err)
}
secret = _secret
done()
})
})
})
ciphers.forEach((cipher) => {
hashes.forEach((hash) => {
it(`${cipher} - ${hash}`, () => {
const keys = crypto.keyStretcher(cipher, hash, secret)
expect(keys.k1).to.exist
expect(keys.k2).to.exist
it(`${cipher} - ${hash}`, (done) => {
crypto.keyStretcher(cipher, hash, secret, (err, keys) => {
if (err) {
return done(err)
}
expect(keys.k1).to.exist
expect(keys.k2).to.exist
done()
})
})
})
})
@ -27,19 +50,24 @@ describe('keyStretcher', () => {
describe('go interop', () => {
fixtures.forEach((test) => {
it(`${test.cipher} - ${test.hash}`, () => {
it(`${test.cipher} - ${test.hash}`, (done) => {
const cipher = test.cipher
const hash = test.hash
const secret = test.secret
const keys = crypto.keyStretcher(cipher, hash, secret)
crypto.keyStretcher(cipher, hash, secret, (err, keys) => {
if (err) {
return done(err)
}
expect(keys.k1.iv).to.be.eql(test.k1.iv)
expect(keys.k1.cipherKey).to.be.eql(test.k1.cipherKey)
expect(keys.k1.macKey).to.be.eql(test.k1.macKey)
expect(keys.k1.iv).to.be.eql(test.k1.iv)
expect(keys.k1.cipherKey).to.be.eql(test.k1.cipherKey)
expect(keys.k1.macKey).to.be.eql(test.k1.macKey)
expect(keys.k2.iv).to.be.eql(test.k2.iv)
expect(keys.k2.cipherKey).to.be.eql(test.k2.cipherKey)
expect(keys.k2.macKey).to.be.eql(test.k2.macKey)
expect(keys.k2.iv).to.be.eql(test.k2.iv)
expect(keys.k2.cipherKey).to.be.eql(test.k2.cipherKey)
expect(keys.k2.macKey).to.be.eql(test.k2.macKey)
done()
})
})
})
})

View File

@ -5,60 +5,80 @@ const expect = require('chai').expect
const crypto = require('../src')
const rsa = crypto.keys.rsa
const fixtures = require('./fixtures/go-key-rsa')
describe('RSA', () => {
let key
before(() => {
key = crypto.generateKeyPair('RSA', 2048)
before((done) => {
crypto.generateKeyPair('RSA', 2048, (err, _key) => {
if (err) return done(err)
key = _key
done()
})
})
it('generates a valid key', () => {
it('generates a valid key', (done) => {
expect(
key
).to.be.an.instanceof(
rsa.RsaPrivateKey
)
expect(
key.hash()
).to.have.length(
34
)
key.hash((err, digest) => {
if (err) {
return done(err)
}
expect(digest).to.have.length(34)
done()
})
})
it('signs', () => {
const pk = key.public
it('signs', (done) => {
const text = key.genSecret()
const sig = key.sign(text)
expect(
pk.verify(text, sig)
).to.be.eql(
true
)
key.sign(text, (err, sig) => {
if (err) {
return done(err)
}
key.public.verify(text, sig, (err, res) => {
if (err) {
return done(err)
}
expect(res).to.be.eql(true)
done()
})
})
})
it('encoding', () => {
it('encoding', (done) => {
const keyMarshal = key.marshal()
const key2 = rsa.unmarshalRsaPrivateKey(keyMarshal)
const keyMarshal2 = key2.marshal()
rsa.unmarshalRsaPrivateKey(keyMarshal, (err, key2) => {
if (err) {
return done(err)
}
const keyMarshal2 = key2.marshal()
expect(
keyMarshal
).to.be.eql(
keyMarshal2
)
expect(
keyMarshal
).to.be.eql(
keyMarshal2
)
const pk = key.public
const pkMarshal = pk.marshal()
const pk2 = rsa.unmarshalRsaPublicKey(pkMarshal)
const pkMarshal2 = pk2.marshal()
const pk = key.public
const pkMarshal = pk.marshal()
const pk2 = rsa.unmarshalRsaPublicKey(pkMarshal)
const pkMarshal2 = pk2.marshal()
expect(
pkMarshal
).to.be.eql(
pkMarshal2
)
expect(
pkMarshal
).to.be.eql(
pkMarshal2
)
done()
})
})
describe('key equals', () => {
@ -76,54 +96,82 @@ describe('RSA', () => {
)
})
it('not equals other key', () => {
const key2 = crypto.generateKeyPair('RSA', 2048)
it('not equals other key', (done) => {
crypto.generateKeyPair('RSA', 2048, (err, key2) => {
if (err) return done(err)
expect(
key.equals(key2)
).to.be.eql(
false
)
expect(
key.equals(key2)
).to.be.eql(
false
)
expect(
key2.equals(key)
).to.be.eql(
false
)
expect(
key2.equals(key)
).to.be.eql(
false
)
expect(
key.public.equals(key2.public)
).to.be.eql(
false
)
expect(
key.public.equals(key2.public)
).to.be.eql(
false
)
expect(
key2.public.equals(key.public)
).to.be.eql(
false
)
expect(
key2.public.equals(key.public)
).to.be.eql(
false
)
done()
})
})
})
it('sign and verify', () => {
it('sign and verify', (done) => {
const data = new Buffer('hello world')
const sig = key.sign(data)
key.sign(data, (err, sig) => {
if (err) {
return done(err)
}
expect(
key.public.verify(data, sig)
).to.be.eql(
true
)
key.public.verify(data, sig, (err, valid) => {
if (err) {
return done(err)
}
expect(valid).to.be.eql(true)
done()
})
})
})
it('does fails to verify for different data', () => {
it('fails to verify for different data', (done) => {
const data = new Buffer('hello world')
const sig = key.sign(data)
key.sign(data, (err, sig) => {
if (err) {
return done(err)
}
expect(
key.public.verify(new Buffer('hello'), sig)
).to.be.eql(
false
)
key.public.verify(new Buffer('hello'), sig, (err, valid) => {
if (err) {
return done(err)
}
expect(valid).to.be.eql(false)
done()
})
})
})
describe('go interop', () => {
it('verifies with data from go', (done) => {
const key = crypto.unmarshalPublicKey(fixtures.verify.publicKey)
key.verify(fixtures.verify.data, fixtures.verify.signature, (err, ok) => {
if (err) throw err
expect(err).to.not.exist
expect(ok).to.be.eql(true)
done()
})
})
})
})

29552
vendor/forge.bundle.js vendored

File diff suppressed because it is too large Load Diff