feat(ecdh): use node core instead of webcrypto-ossl

This commit is contained in:
Friedel Ziegelmayer 2016-11-29 16:36:56 +01:00
parent ebe1cecdeb
commit 6d15450438
5 changed files with 189 additions and 97 deletions

View File

@ -7,6 +7,7 @@
"node-webcrypto-ossl": false, "node-webcrypto-ossl": false,
"./src/crypto/webcrypto.js": "./src/crypto/webcrypto-browser.js", "./src/crypto/webcrypto.js": "./src/crypto/webcrypto-browser.js",
"./src/crypto/hmac.js": "./src/crypto/hmac-browser.js", "./src/crypto/hmac.js": "./src/crypto/hmac-browser.js",
"./src/crypto/ecdh.js": "./src/crypto/ecdh-browser.js",
"./src/crypto/ciphers.js": "./src/crypto/ciphers-browser.js" "./src/crypto/ciphers.js": "./src/crypto/ciphers-browser.js"
}, },
"scripts": { "scripts": {
@ -67,4 +68,4 @@
"greenkeeperio-bot <support@greenkeeper.io>", "greenkeeperio-bot <support@greenkeeper.io>",
"nikuda <nikuda@gmail.com>" "nikuda <nikuda@gmail.com>"
] ]
} }

129
src/crypto/ecdh-browser.js Normal file
View File

@ -0,0 +1,129 @@
'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
const bits = {
'P-256': 256,
'P-384': 384,
'P-521': 521
}
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
}
let privateKey
if (forcePrivate) {
privateKey = crypto.subtle.importKey(
'jwk',
unmarshalPrivateKey(curve, forcePrivate),
{
name: 'ECDH',
namedCurve: curve
},
false,
['deriveBits']
)
} else {
privateKey = Promise.resolve(pair.privateKey)
}
const keys = Promise.all([
crypto.subtle.importKey(
'jwk',
unmarshalPublicKey(curve, theirPub),
{
name: 'ECDH',
namedCurve: curve
},
false,
[]
),
privateKey
])
nodeify(keys.then((keys) => crypto.subtle.deriveBits(
{
name: 'ECDH',
namedCurve: curve,
public: keys[0]
},
keys[1],
bits[curve]
)).then((bits) => 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
}
}
function unmarshalPrivateKey (curve, key) {
const result = unmarshalPublicKey(curve, key.public)
result.d = toBase64(new BN(key.private))
return result
}

View File

@ -1,101 +1,41 @@
'use strict' 'use strict'
const crypto = require('./webcrypto')() const crypto = require('crypto')
const nodeify = require('nodeify') const setImmediate = require('async/setImmediate')
const BN = require('asn1.js').bignum
const util = require('./util') const curves = {
const toBase64 = util.toBase64 'P-256': 'prime256v1',
const toBn = util.toBn 'P-384': 'secp384r1',
'P-521': 'secp521r1'
}
exports.generateEphmeralKeyPair = function (curve, callback) { exports.generateEphmeralKeyPair = function (curve, callback) {
nodeify(crypto.subtle.generateKey( if (!curves[curve]) {
{ return callback(new Error(`Unkown curve: ${curve}`))
name: 'ECDH', }
namedCurve: curve const ecdh = crypto.createECDH(curves[curve])
}, ecdh.generateKeys()
true,
['deriveBits'] setImmediate(() => callback(null, {
).then((pair) => { key: ecdh.getPublicKey(),
// forcePrivate is used for testing only genSharedKey (theirPub, forcePrivate, cb) {
const genSharedKey = (theirPub, forcePrivate, cb) => {
if (typeof forcePrivate === 'function') { if (typeof forcePrivate === 'function') {
cb = forcePrivate cb = forcePrivate
forcePrivate = undefined forcePrivate = null
} }
const privateKey = forcePrivate || pair.privateKey if (forcePrivate) {
nodeify(crypto.subtle.importKey( ecdh.setPrivateKey(forcePrivate.private)
'jwk', }
unmarshalPublicKey(curve, theirPub),
{ let secret
name: 'ECDH', try {
namedCurve: curve secret = ecdh.computeSecret(theirPub)
}, } catch (err) {
false, return cb(err)
[] }
).then((publicKey) => {
return crypto.subtle.deriveBits( setImmediate(() => cb(null, secret))
{
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

@ -14,6 +14,11 @@ const lengths = {
'P-384': 97, 'P-384': 97,
'P-521': 133 'P-521': 133
} }
const secretLengths = {
'P-256': 32,
'P-384': 48,
'P-521': 66
}
describe('generateEphemeralKeyPair', () => { describe('generateEphemeralKeyPair', () => {
curves.forEach((curve) => { curves.forEach((curve) => {
@ -28,7 +33,7 @@ describe('generateEphemeralKeyPair', () => {
keys[0].genSharedKey(keys[1].key, (err, shared) => { keys[0].genSharedKey(keys[1].key, (err, shared) => {
expect(err).to.not.exist expect(err).to.not.exist
expect(shared).to.have.length(32) expect(shared).to.have.length(secretLengths[curve])
done() done()
}) })
}) })
@ -39,12 +44,29 @@ describe('generateEphemeralKeyPair', () => {
it('generates a shared secret', (done) => { it('generates a shared secret', (done) => {
const curve = fixtures.curve const curve = fixtures.curve
crypto.generateEphemeralKeyPair(curve, (err, alice) => { parallel([
(cb) => crypto.generateEphemeralKeyPair(curve, cb),
(cb) => crypto.generateEphemeralKeyPair(curve, cb)
], (err, res) => {
expect(err).to.not.exist expect(err).to.not.exist
const alice = res[0]
const bob = res[1]
bob.key = fixtures.bob.public
alice.genSharedKey(fixtures.bob.public, (err, s1) => { parallel([
(cb) => alice.genSharedKey(bob.key, cb),
(cb) => bob.genSharedKey(alice.key, fixtures.bob, cb)
], (err, secrets) => {
expect(err).to.not.exist expect(err).to.not.exist
expect(s1).to.have.length(32)
expect(
secrets[0]
).to.be.eql(
secrets[1]
)
expect(secrets[0]).to.have.length(32)
done() done()
}) })
}) })

View File

@ -3,9 +3,9 @@
module.exports = { module.exports = {
curve: 'P-256', curve: 'P-256',
bob: { bob: {
private: [ private: new Buffer([
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 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, 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 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
]) ])