mirror of
https://github.com/fluencelabs/js-libp2p-crypto
synced 2025-03-15 19:50:58 +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,
|
"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
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'
|
'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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
4
test/fixtures/go-elliptic-key.js
vendored
4
test/fixtures/go-elliptic-key.js
vendored
@ -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
|
||||||
])
|
])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user