feat: use webcrypto in favor of node-forge

BREAKING CHANGE: generateKeyPair is now async
This commit is contained in:
Friedel Ziegelmayer 2016-09-13 13:23:11 +02:00
parent 73a5258876
commit 08c5df5e79
32 changed files with 2728 additions and 334 deletions

View File

@ -8,6 +8,9 @@ module.exports = {
alias: { alias: {
'node-forge': path.resolve(__dirname, 'vendor/forge.bundle.js') 'node-forge': path.resolve(__dirname, 'vendor/forge.bundle.js')
} }
},
externals: {
ursa: '{}'
} }
} }
} }

View File

@ -21,7 +21,7 @@ needed for libp2p. This is based on this [go implementation](https://github.com/
- [Usage](#usage) - [Usage](#usage)
- [Example](#example) - [Example](#example)
- [API](#api) - [API](#api)
- [`generateKeyPair(type, bits)`](#generatekeypairtype-bits) - [`generateKeyPair(type, bits, cb)`](#generatekeypairtype-bits-cb)
- [`generateEphemeralKeyPair(curve)`](#generateephemeralkeypaircurve) - [`generateEphemeralKeyPair(curve)`](#generateephemeralkeypaircurve)
- [`keyStretcher(cipherType, hashType, secret)`](#keystretcherciphertype-hashtype-secret) - [`keyStretcher(cipherType, hashType, secret)`](#keystretcherciphertype-hashtype-secret)
- [`marshalPublicKey(key[, type])`](#marshalpublickeykey-type) - [`marshalPublicKey(key[, type])`](#marshalpublickeykey-type)
@ -44,15 +44,17 @@ npm install --save libp2p-crypto
```js ```js
const crypto = require('libp2p-crypto') const crypto = require('libp2p-crypto')
var keyPair = crypto.generateKeyPair('RSA', 2048) crypto.generateKeyPair('RSA', 2048, (err, key) => {
})
``` ```
## API ## API
### `generateKeyPair(type, bits)` ### `generateKeyPair(type, bits, cb)`
- `type: String`, only `'RSA'` is currently supported - `type: String`, only `'RSA'` is currently supported
- `bits: Number` - `bits: Number`
- `cb: Function`
Generates a keypair of the given type and bitsize. Generates a keypair of the given type and bitsize.

View File

@ -0,0 +1,33 @@
'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,43 @@
'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
})
})
})

47
benchmarks/rsa.js Normal file
View File

@ -0,0 +1,47 @@
'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,17 +2,26 @@
"name": "libp2p-crypto", "name": "libp2p-crypto",
"version": "0.6.1", "version": "0.6.1",
"description": "Crypto primitives for libp2p", "description": "Crypto primitives for libp2p",
"main": "lib/index.js", "main": "src/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",
"browser": {
"node-webcrypto-ossl": false,
"./src/crypto/webcrypto.js": "./src/crypto/webcrypto-browser.js",
"./lib/crypto/webcrypto.js": "./lib/crypto/webcrypto-browser.js",
"./src/crypto/hmac.js": "./src/crypto/hmac-browser.js",
"./lib/crypto/hmac.js": "./lib/crypto/hmac-browser.js",
"./src/crypto/aes.js": "./src/crypto/aes-browser.js",
"./lib/crypto/aes.js": "./lib/crypto/aes-browser.js"
},
"scripts": { "scripts": {
"lint": "aegir-lint", "lint": "aegir-lint",
"build": "aegir-build", "build": "aegir-build",
"test": "aegir-test", "test": "PHANTOM=off aegir-test",
"test:node": "aegir-test --env node", "test:node": "aegir-test --env node",
"test:browser": "aegir-test --env browser", "test:browser": "PHANTOM=off aegir-test --env browser",
"release": "aegir-release", "release": "PHANTOM=off aegir-release",
"release-minor": "aegir-release --type minor", "release-minor": "PHANTOM=off aegir-release --type minor",
"release-major": "aegir-release --type major", "release-major": "PHANTOM=off aegir-release --type major",
"coverage": "aegir-coverage", "coverage": "aegir-coverage",
"coverage-publish": "aegir-coverage publish" "coverage-publish": "aegir-coverage publish"
}, },
@ -25,13 +34,18 @@
"author": "Friedel Ziegelmayer <dignifiedqurie@gmail.com>", "author": "Friedel Ziegelmayer <dignifiedqurie@gmail.com>",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"elliptic": "^6.3.2", "asn1.js": "^4.8.1",
"async": "^2.0.1",
"bn.js": "^4.11.6",
"multihashing": "^0.2.1", "multihashing": "^0.2.1",
"node-forge": "^0.6.39", "node-webcrypto-ossl": "^1.0.7",
"protocol-buffers": "^3.1.6" "nodeify": "^1.0.0",
"protocol-buffers": "^3.1.6",
"webcrypto-shim": "^0.1.1"
}, },
"devDependencies": { "devDependencies": {
"aegir": "^8.0.0", "aegir": "^8.0.0",
"benchmark": "^2.1.1",
"chai": "^3.5.0", "chai": "^3.5.0",
"pre-commit": "^1.1.3" "pre-commit": "^1.1.3"
}, },
@ -56,4 +70,4 @@
"Richard Littauer <richard.littauer@gmail.com>", "Richard Littauer <richard.littauer@gmail.com>",
"greenkeeperio-bot <support@greenkeeper.io>" "greenkeeperio-bot <support@greenkeeper.io>"
] ]
} }

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;
}

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

@ -0,0 +1,17 @@
'use strict'
module.exports = new Buffer(`
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)
}

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

@ -0,0 +1,58 @@
'use strict'
const crypto = require('./webcrypto')()
const nodeify = require('nodeify')
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(
'spki',
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(
'spki',
pair.publicKey
).then((publicKey) => {
return {
key: Buffer.from(publicKey),
genSharedKey
}
})
}), callback)
}

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

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

@ -0,0 +1,205 @@
'use strict'
const multihashing = require('multihashing')
const nodeify = require('nodeify')
const BN = require('bn.js')
const asn1 = require('asn1.js')
const crypto = require('./webcrypto')()
const sha2256 = multihashing.createHash('sha2-256')
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) => {
return {
privateKey: keys[0],
publicKey: Buffer.from(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(privateKey)
]).then((keys) => {
return exportKey({
privateKey: keys[0],
publicKey: keys[1]
})
}).then((keys) => {
return {
privateKey: keys[0],
publicKey: Buffer.from(keys[1])
}
}), callback)
}
exports.getRandomValues = function (arr) {
return Buffer.from(crypto.getRandomValues(arr))
}
exports.hashAndSign = function (key, msg, callback) {
sha2256(msg, (err, digest) => {
if (err) {
return callback(err)
}
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(digest)
)
}).then((sig) => Buffer.from(sig)), callback)
})
}
exports.hashAndVerify = function (key, sig, msg, callback) {
sha2256(msg, (err, digest) => {
if (err) {
return callback(err)
}
nodeify(crypto.subtle.importKey(
'spki',
Uint8Array.from(key),
{
name: 'RSASSA-PKCS1-v1_5',
hash: {name: 'SHA-256'}
},
false,
['verify']
).then((publicKey) => {
return crypto.subtle.verify(
{name: 'RSASSA-PKCS1-v1_5'},
publicKey,
Uint8Array.from(sig),
Uint8Array.from(digest)
)
}), callback)
})
}
function exportKey (pair) {
return Promise.all([
crypto.subtle.exportKey('jwk', pair.privateKey),
crypto.subtle.exportKey('spki', pair.publicKey)
])
}
function derivePublicFromPrivate (privatePromise) {
return privatePromise.then((privateKey) => {
return crypto.subtle.exportKey('jwk', privateKey)
}).then((jwKey) => 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()
)
})
// 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'
}
}
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 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
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
function toBn (str) {
return new BN(Buffer.from(str, 'base64'))
}

View File

@ -0,0 +1,13 @@
'use strict'
module.exports = function getWebCrypto () {
if (typeof window !== 'undefined') {
require('webcrypto-shim')
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' 'use strict'
const EC = require('elliptic').ec const crypto = require('./crypto')
const curveMap = {
'P-256': 'p256',
'P-384': 'p384',
'P-521': 'p521'
}
// Generates an ephemeral public key and returns a function that will compute // Generates an ephemeral public key and returns a function that will compute
// the shared secret key. // the shared secret key.
// //
// Focuses only on ECDH now, but can be made more general in the future. // Focuses only on ECDH now, but can be made more general in the future.
module.exports = (curveName) => { module.exports = (curve, callback) => {
const curve = curveMap[curveName] crypto.ecdh.generateEphmeralKeyPair(curve, callback)
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
}
} }

View File

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

View File

@ -1,7 +1,7 @@
'use strict' 'use strict'
const forge = require('node-forge') const crypto = require('./crypto')
const createBuffer = forge.util.createBuffer const whilst = require('async/whilst')
const cipherMap = { const cipherMap = {
'AES-128': { '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. // Generates a set of keys for each party by stretching the shared key.
// (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey) // (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey)
module.exports = (cipherType, hashType, secret) => { module.exports = (cipherType, hash, secret, callback) => {
const cipher = cipherMap[cipherType] const cipher = cipherMap[cipherType]
const hash = hashMap[hashType]
if (!cipher) { if (!cipher) {
throw new Error('unkown cipherType passed') return callback(new Error('unkown cipherType passed'))
} }
if (!hash) { if (!hash) {
throw new Error('unkown hashType passed') return callback(new Error('unkown hashType passed'))
}
if (Buffer.isBuffer(secret)) {
secret = createBuffer(secret.toString('binary'))
} }
const cipherKeySize = cipher.keySize const cipherKeySize = cipher.keySize
const ivSize = cipher.ivSize const ivSize = cipher.ivSize
const hmacKeySize = 20 const hmacKeySize = 20
const seed = 'key expansion' const seed = Buffer.from('key expansion')
const resultLength = 2 * (ivSize + cipherKeySize + hmacKeySize) const resultLength = 2 * (ivSize + cipherKeySize + hmacKeySize)
const m = forge.hmac.create() crypto.hmac.create(hash, secret, (err, m) => {
m.start(hash, secret) if (err) {
m.update(seed) return callback(err)
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
} }
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) whilst(
m.update(a) () => j < resultLength,
a = m.digest().bytes() stretch,
} finish
)
const half = resultLength / 2 function stretch (cb) {
const r1 = createBuffer(result.getBytes(half)) m.digest(Buffer.concat([a, seed]), (err, b) => {
const r2 = createBuffer(result.getBytes()) if (err) {
return cb(err)
}
const createKey = (res) => ({ let todo = b.length
iv: new Buffer(res.getBytes(ivSize), 'binary'),
cipherKey: new Buffer(res.getBytes(cipherKeySize), 'binary'), if (j + todo > resultLength) {
macKey: new Buffer(res.getBytes(), 'binary') 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' 'use strict'
const forge = require('node-forge') const multihashing = require('multihashing')
const protobuf = require('protocol-buffers') const protobuf = require('protocol-buffers')
const fs = require('fs')
const path = require('path')
const utils = require('../utils') const crypto = require('../crypto').rsa
const pbm = protobuf(require('../crypto.proto'))
const pki = forge.pki
const rsa = pki.rsa
const pbm = protobuf(fs.readFileSync(path.join(__dirname, '../crypto.proto')))
class RsaPublicKey { class RsaPublicKey {
constructor (k) { constructor (key) {
this._key = k this._key = key
} }
verify (data, sig) { verify (data, sig, callback) {
const md = forge.md.sha256.create() ensure(callback)
if (Buffer.isBuffer(data)) { crypto.hashAndVerify(this._key, sig, data, callback)
md.update(data.toString('binary'), 'binary')
} else {
md.update(data)
}
return this._key.verify(md.digest().bytes(), sig)
} }
marshal () { marshal () {
return new Buffer(forge.asn1.toDer(pki.publicKeyToAsn1(this._key)).bytes(), 'binary') return this._key
} }
get bytes () { get bytes () {
@ -47,34 +35,27 @@ class RsaPublicKey {
return this.bytes.equals(key.bytes) return this.bytes.equals(key.bytes)
} }
hash () { hash (callback) {
return utils.keyHash(this.bytes) ensure(callback)
multihashing(this.bytes, 'sha2-256', callback)
} }
} }
class RsaPrivateKey { class RsaPrivateKey {
constructor (privKey, pubKey) { // key - Object of the jwk format
this._privateKey = privKey // publicKey - Buffer of the spki format
if (pubKey) { constructor (key, publicKey) {
this._publicKey = pubKey this._key = key
} else { this._publicKey = publicKey
this._publicKey = forge.pki.setRsaPublicKey(privKey.n, privKey.e)
}
} }
genSecret () { genSecret () {
return forge.random.getBytesSync(16) return crypto.getRandomValues(new Uint8Array(16))
} }
sign (message) { sign (message, callback) {
const md = forge.md.sha256.create() ensure(callback)
if (Buffer.isBuffer(message)) { crypto.hashAndSign(this._key, message, callback)
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')
} }
get public () { get public () {
@ -85,12 +66,12 @@ class RsaPrivateKey {
return new RsaPublicKey(this._publicKey) return new RsaPublicKey(this._publicKey)
} }
decrypt (bytes) { decrypt (msg, callback) {
return this._privateKey.decrypt(bytes, 'RSAES-PKCS1-V1_5') crypto.decrypt(this._key, msg, callback)
} }
marshal () { marshal () {
return new Buffer(forge.asn1.toDer(pki.privateKeyToAsn1(this._privateKey)).bytes(), 'binary') return crypto.jwkToPkcs1(this._key)
} }
get bytes () { get bytes () {
@ -104,32 +85,41 @@ class RsaPrivateKey {
return this.bytes.equals(key.bytes) return this.bytes.equals(key.bytes)
} }
hash () { hash (callback) {
return utils.keyHash(this.bytes) ensure(callback)
multihashing(this.bytes, 'sha2-256', callback)
} }
} }
function unmarshalRsaPrivateKey (bytes) { function unmarshalRsaPrivateKey (bytes, callback) {
if (Buffer.isBuffer(bytes)) { const jwk = crypto.pkcs1ToJwk(bytes)
bytes = forge.util.createBuffer(bytes.toString('binary')) crypto.unmarshalPrivateKey(jwk, (err, keys) => {
} if (err) {
const key = pki.privateKeyFromAsn1(forge.asn1.fromDer(bytes)) return callback(err)
}
return new RsaPrivateKey(key) callback(null, new RsaPrivateKey(keys.privateKey, keys.publicKey))
})
} }
function unmarshalRsaPublicKey (bytes) { function unmarshalRsaPublicKey (bytes) {
if (Buffer.isBuffer(bytes)) { return new RsaPublicKey(bytes)
bytes = forge.util.createBuffer(bytes.toString('binary'))
}
const key = pki.publicKeyFromAsn1(forge.asn1.fromDer(bytes))
return new RsaPublicKey(key)
} }
function generateKeyPair (bits) { function generateKeyPair (bits, cb) {
const p = rsa.generateKeyPair({bits}) crypto.generateKey(bits, (err, keys) => {
return new RsaPrivateKey(p.privateKey, p.publicKey) 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 = { 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.7M` | `949K` |
|after | `461K` | `291K` |
## 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 44.42 ops/sec ±10.21% (43 runs sampled)
generateKeyPair 2048bits x 7.46 ops/sec ±22.60% (27 runs sampled)
generateKeyPair 4096bits x 1.64 ops/sec ±30.16% (13 runs sampled)
sign and verify x 900 ops/sec ±4.03% (68 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,95 @@
/* eslint max-nested-callbacks: ["error", 8] */
/* eslint-env mocha */ /* eslint-env mocha */
'use strict' 'use strict'
const expect = require('chai').expect const expect = require('chai').expect
const EC = require('elliptic').ec
const crypto = require('../src') const crypto = require('../src')
const fixtures = require('./fixtures/go-elliptic-key') const fixtures = require('./fixtures/go-elliptic-key')
const curves = ['P-256', 'P-384', 'P-521']
// const lengths = {
// 'P-256': 32,
// 'P-384': 48,
// 'P-521': 65
// }
describe('generateEphemeralKeyPair', () => { describe('generateEphemeralKeyPair', () => {
it('returns a function that generates a shared secret', () => { curves.forEach((curve) => {
const res = crypto.generateEphemeralKeyPair('P-256') it(`generate and shared key ${curve}`, (done) => {
const ourPublic = '044374add0df35706db7dade25f3959fc051d2ef5166f8a6a0aa632d0ab41cdb4d30e1a064e121ac56155235a6b8d4c5d8fe35e019f507f4e2ff1445e229d7af43' crypto.generateEphemeralKeyPair(curve, (err, ours) => {
if (err) {
return done(err)
}
expect( crypto.generateEphemeralKeyPair(curve, (err, theirs) => {
res.genSharedKey(ourPublic) if (err) {
).to.have.length(32) return done(err)
}
expect( ours.genSharedKey(theirs.key, (err, shared) => {
res.key if (err) {
).to.exist return done(err)
}
expect(shared).to.exist
// expect(shared).to.have.length(lengths[curve])
expect(ours.key).to.exist
done()
})
})
})
})
}) })
describe('go interop', () => { describe.skip('go interop', () => {
it('generates a shared secret', () => { it('generates a shared secret', (done) => {
const curve = fixtures.curve const curve = fixtures.curve
const ec = new EC(fixtures.curveJs) console.log('start', curve)
const bobPrivate = ec.keyFromPrivate(fixtures.bob.private, 'binary') // crypto.webcrypto.subtle.importKey(
// 'pkcs8',
// Uint8Array.from(fixtures.bob.private),
// {
// name: 'ECDH',
// namedCurve: curve
// },
// false,
// ['deriveBits']
// ).then((bobPrivate) => {
// console.log('imported bobs key')
// checkKeys(bobPrivate)
// }).catch((err) => {
// done(err)
// })
checkKeys()
function checkKeys (bobPrivate) {
crypto.generateEphemeralKeyPair(curve, (err, alice) => {
if (err) {
return done(err)
}
console.log('genreated ephem pair')
const bob = {
key: fixtures.bob.public,
// this is using bobs private key from go ipfs
// instead of alices
genSharedKey: (key, cb) => alice.genSharedKey(key, bobPrivate, cb)
}
const alice = crypto.generateEphemeralKeyPair(curve) alice.genSharedKey(bob.key, (err, s1) => {
const bob = { if (err) {
key: fixtures.bob.public, return done(err)
// this is using bobs private key from go ipfs }
// instead of alices console.log('genshared alice')
genSharedKey: (key) => alice.genSharedKey(key, bobPrivate) bob.genSharedKey(alice.key, (err, s2) => {
if (err) {
return done(err)
}
console.log('genshared bob')
expect(s1.equals(s2)).to.be.eql(true)
done()
})
})
})
} }
const s1 = alice.genSharedKey(bob.key)
const s2 = bob.genSharedKey(alice.key)
expect(s1.equals(s2)).to.be.eql(true)
}) })
}) })
}) })

View File

@ -2,13 +2,12 @@
module.exports = { module.exports = {
curve: 'P-256', curve: 'P-256',
curveJs: 'p256',
bob: { bob: {
private: [ 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([ 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
]) ])
} }
} }

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 */ /* eslint-env mocha */
'use strict' 'use strict'
@ -8,8 +9,12 @@ const fixtures = require('./fixtures/go-key-rsa')
describe('libp2p-crypto', () => { describe('libp2p-crypto', () => {
let key let key
before(() => { before((done) => {
key = crypto.generateKeyPair('RSA', 2048) crypto.generateKeyPair('RSA', 2048, (err, _key) => {
if (err) return done(err)
key = _key
done()
})
}) })
it('marshalPublicKey and unmarshalPublicKey', () => { it('marshalPublicKey and unmarshalPublicKey', () => {
@ -18,43 +23,67 @@ describe('libp2p-crypto', () => {
expect(key2.equals(key.public)).to.be.eql(true) expect(key2.equals(key.public)).to.be.eql(true)
}) })
it('marshalPrivateKey and unmarshalPrivateKey', () => { it('marshalPrivateKey and unmarshalPrivateKey', (done) => {
const key2 = crypto.unmarshalPrivateKey(crypto.marshalPrivateKey(key)) 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', () => { describe('go interop', () => {
it('unmarshals private key', () => { it('unmarshals private key', (done) => {
const key = crypto.unmarshalPrivateKey(fixtures.private.key) crypto.unmarshalPrivateKey(fixtures.private.key, (err, key) => {
const hash = fixtures.private.hash if (err) {
return done(err)
}
const hash = fixtures.private.hash
expect(fixtures.private.key).to.be.eql(key.bytes)
expect( key.hash((err, digest) => {
key.hash() if (err) {
).to.be.eql( return done(err)
hash }
)
expect(digest).to.be.eql(hash)
done()
})
})
}) })
it('unmarshals public key', () => { it('unmarshals public key', (done) => {
const key = crypto.unmarshalPublicKey(fixtures.public.key) const key = crypto.unmarshalPublicKey(fixtures.public.key)
const hash = fixtures.public.hash const hash = fixtures.public.hash
expect( expect(crypto.marshalPublicKey(key)).to.be.eql(fixtures.public.key)
key.hash()
).to.be.eql( key.hash((err, digest) => {
hash if (err) {
) return done(err)
}
expect(digest).to.be.eql(hash)
done()
})
}) })
it('unmarshal -> marshal, private key', () => { it('unmarshal -> marshal, private key', (done) => {
const key = crypto.unmarshalPrivateKey(fixtures.private.key) crypto.unmarshalPrivateKey(fixtures.private.key, (err, key) => {
const marshalled = crypto.marshalPrivateKey(key) if (err) {
expect( return done(err)
fixtures.private.key.equals(marshalled) }
).to.be.eql(
true const marshalled = crypto.marshalPrivateKey(key)
) expect(marshalled).to.be.eql(fixtures.private.key)
done()
})
}) })
it('unmarshal -> marshal, public key', () => { it('unmarshal -> marshal, public key', () => {

View File

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

View File

@ -8,57 +8,76 @@ const rsa = crypto.keys.rsa
describe('RSA', () => { describe('RSA', () => {
let key let key
before(() => { before((done) => {
key = crypto.generateKeyPair('RSA', 2048) 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( expect(
key key
).to.be.an.instanceof( ).to.be.an.instanceof(
rsa.RsaPrivateKey rsa.RsaPrivateKey
) )
expect( key.hash((err, digest) => {
key.hash() if (err) {
).to.have.length( return done(err)
34 }
)
expect(digest).to.have.length(34)
done()
})
}) })
it('signs', () => { it('signs', (done) => {
const pk = key.public
const text = key.genSecret() const text = key.genSecret()
const sig = key.sign(text)
expect( key.sign(text, (err, sig) => {
pk.verify(text, sig) if (err) {
).to.be.eql( return done(err)
true }
)
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 keyMarshal = key.marshal()
const key2 = rsa.unmarshalRsaPrivateKey(keyMarshal) rsa.unmarshalRsaPrivateKey(keyMarshal, (err, key2) => {
const keyMarshal2 = key2.marshal() if (err) {
return done(err)
}
const keyMarshal2 = key2.marshal()
expect( expect(
keyMarshal keyMarshal
).to.be.eql( ).to.be.eql(
keyMarshal2 keyMarshal2
) )
const pk = key.public const pk = key.public
const pkMarshal = pk.marshal() const pkMarshal = pk.marshal()
const pk2 = rsa.unmarshalRsaPublicKey(pkMarshal) const pk2 = rsa.unmarshalRsaPublicKey(pkMarshal)
const pkMarshal2 = pk2.marshal() const pkMarshal2 = pk2.marshal()
expect( expect(
pkMarshal pkMarshal
).to.be.eql( ).to.be.eql(
pkMarshal2 pkMarshal2
) )
done()
})
}) })
describe('key equals', () => { describe('key equals', () => {
@ -76,54 +95,69 @@ describe('RSA', () => {
) )
}) })
it('not equals other key', () => { it('not equals other key', (done) => {
const key2 = crypto.generateKeyPair('RSA', 2048) crypto.generateKeyPair('RSA', 2048, (err, key2) => {
if (err) return done(err)
expect( expect(
key.equals(key2) key.equals(key2)
).to.be.eql( ).to.be.eql(
false false
) )
expect( expect(
key2.equals(key) key2.equals(key)
).to.be.eql( ).to.be.eql(
false false
) )
expect( expect(
key.public.equals(key2.public) key.public.equals(key2.public)
).to.be.eql( ).to.be.eql(
false false
) )
expect( expect(
key2.public.equals(key.public) key2.public.equals(key.public)
).to.be.eql( ).to.be.eql(
false false
) )
done()
})
}) })
}) })
it('sign and verify', () => { it('sign and verify', (done) => {
const data = new Buffer('hello world') 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, (err, valid) => {
key.public.verify(data, sig) if (err) {
).to.be.eql( return done(err)
true }
) 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 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, (err, valid) => {
key.public.verify(new Buffer('hello'), sig) if (err) {
).to.be.eql( return done(err)
false }
) expect(valid).to.be.eql(false)
done()
})
})
}) })
}) })

1486
vendor/prime.worker.js vendored Normal file

File diff suppressed because it is too large Load Diff