mirror of
https://github.com/fluencelabs/js-libp2p-crypto
synced 2025-03-15 10:30:50 +00:00
feat(ecdh): use node core instead of webcrypto-ossl
This commit is contained in:
parent
ebe1cecdeb
commit
6d15450438
@ -7,6 +7,7 @@
|
||||
"node-webcrypto-ossl": false,
|
||||
"./src/crypto/webcrypto.js": "./src/crypto/webcrypto-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"
|
||||
},
|
||||
"scripts": {
|
||||
@ -67,4 +68,4 @@
|
||||
"greenkeeperio-bot <support@greenkeeper.io>",
|
||||
"nikuda <nikuda@gmail.com>"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
129
src/crypto/ecdh-browser.js
Normal file
129
src/crypto/ecdh-browser.js
Normal 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
|
||||
}
|
@ -1,101 +1,41 @@
|
||||
'use strict'
|
||||
|
||||
const crypto = require('./webcrypto')()
|
||||
const nodeify = require('nodeify')
|
||||
const BN = require('asn1.js').bignum
|
||||
const crypto = require('crypto')
|
||||
const setImmediate = require('async/setImmediate')
|
||||
|
||||
const util = require('./util')
|
||||
const toBase64 = util.toBase64
|
||||
const toBn = util.toBn
|
||||
const curves = {
|
||||
'P-256': 'prime256v1',
|
||||
'P-384': 'secp384r1',
|
||||
'P-521': 'secp521r1'
|
||||
}
|
||||
|
||||
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 (!curves[curve]) {
|
||||
return callback(new Error(`Unkown curve: ${curve}`))
|
||||
}
|
||||
const ecdh = crypto.createECDH(curves[curve])
|
||||
ecdh.generateKeys()
|
||||
|
||||
setImmediate(() => callback(null, {
|
||||
key: ecdh.getPublicKey(),
|
||||
genSharedKey (theirPub, forcePrivate, cb) {
|
||||
if (typeof forcePrivate === 'function') {
|
||||
cb = forcePrivate
|
||||
forcePrivate = undefined
|
||||
forcePrivate = null
|
||||
}
|
||||
|
||||
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)
|
||||
if (forcePrivate) {
|
||||
ecdh.setPrivateKey(forcePrivate.private)
|
||||
}
|
||||
|
||||
let secret
|
||||
try {
|
||||
secret = ecdh.computeSecret(theirPub)
|
||||
} catch (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
setImmediate(() => cb(null, secret))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
@ -14,6 +14,11 @@ const lengths = {
|
||||
'P-384': 97,
|
||||
'P-521': 133
|
||||
}
|
||||
const secretLengths = {
|
||||
'P-256': 32,
|
||||
'P-384': 48,
|
||||
'P-521': 66
|
||||
}
|
||||
|
||||
describe('generateEphemeralKeyPair', () => {
|
||||
curves.forEach((curve) => {
|
||||
@ -28,7 +33,7 @@ describe('generateEphemeralKeyPair', () => {
|
||||
|
||||
keys[0].genSharedKey(keys[1].key, (err, shared) => {
|
||||
expect(err).to.not.exist
|
||||
expect(shared).to.have.length(32)
|
||||
expect(shared).to.have.length(secretLengths[curve])
|
||||
done()
|
||||
})
|
||||
})
|
||||
@ -39,12 +44,29 @@ describe('generateEphemeralKeyPair', () => {
|
||||
it('generates a shared secret', (done) => {
|
||||
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
|
||||
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(s1).to.have.length(32)
|
||||
|
||||
expect(
|
||||
secrets[0]
|
||||
).to.be.eql(
|
||||
secrets[1]
|
||||
)
|
||||
|
||||
expect(secrets[0]).to.have.length(32)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
4
test/fixtures/go-elliptic-key.js
vendored
4
test/fixtures/go-elliptic-key.js
vendored
@ -3,9 +3,9 @@
|
||||
module.exports = {
|
||||
curve: 'P-256',
|
||||
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
|
||||
],
|
||||
]),
|
||||
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
|
||||
])
|
||||
|
Loading…
x
Reference in New Issue
Block a user