mirror of
https://github.com/fluencelabs/js-libp2p-crypto
synced 2025-03-15 09:41:03 +00:00
perf: remove asn1.js and use node-forge (#166)
* perf: remove asn1.js from rsa * fix: tweaks * fix: it works, but I do not know 100% why * chore: remove asn1.js * fix: ensure jwk params encoded as uint * fix: util tests * fix: zero pad base64urlToBuffer * fix: more zero pad * test: add round trip test * test: base64url to Buffer with padding
This commit is contained in:
parent
0f4c533dfa
commit
00477e3bcb
@ -38,7 +38,6 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asmcrypto.js": "^2.3.2",
|
"asmcrypto.js": "^2.3.2",
|
||||||
"asn1.js": "^5.2.0",
|
|
||||||
"bn.js": "^5.0.0",
|
"bn.js": "^5.0.0",
|
||||||
"browserify-aes": "^1.2.0",
|
"browserify-aes": "^1.2.0",
|
||||||
"bs58": "^4.0.1",
|
"bs58": "^4.0.1",
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const errcode = require('err-code')
|
const errcode = require('err-code')
|
||||||
|
const { Buffer } = require('buffer')
|
||||||
const webcrypto = require('../webcrypto')
|
const webcrypto = require('../webcrypto')
|
||||||
const BN = require('asn1.js').bignum
|
const { bufferToBase64url, base64urlToBuffer } = require('../util')
|
||||||
const { toBase64, toBn } = require('../util')
|
|
||||||
const validateCurveType = require('./validate-curve-type')
|
const validateCurveType = require('./validate-curve-type')
|
||||||
|
|
||||||
const bits = {
|
const bits = {
|
||||||
@ -89,8 +89,8 @@ function marshalPublicKey (jwk) {
|
|||||||
|
|
||||||
return Buffer.concat([
|
return Buffer.concat([
|
||||||
Buffer.from([4]), // uncompressed point
|
Buffer.from([4]), // uncompressed point
|
||||||
toBn(jwk.x).toArrayLike(Buffer, 'be', byteLen),
|
base64urlToBuffer(jwk.x, byteLen),
|
||||||
toBn(jwk.y).toArrayLike(Buffer, 'be', byteLen)
|
base64urlToBuffer(jwk.y, byteLen)
|
||||||
], 1 + byteLen * 2)
|
], 1 + byteLen * 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,20 +101,17 @@ function unmarshalPublicKey (curve, key) {
|
|||||||
if (!key.slice(0, 1).equals(Buffer.from([4]))) {
|
if (!key.slice(0, 1).equals(Buffer.from([4]))) {
|
||||||
throw errcode(new Error('Cannot unmarshal public key - invalid key format'), 'ERR_INVALID_KEY_FORMAT')
|
throw errcode(new Error('Cannot unmarshal public key - invalid key format'), 'ERR_INVALID_KEY_FORMAT')
|
||||||
}
|
}
|
||||||
const x = new BN(key.slice(1, byteLen + 1))
|
|
||||||
const y = new BN(key.slice(1 + byteLen))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kty: 'EC',
|
kty: 'EC',
|
||||||
crv: curve,
|
crv: curve,
|
||||||
x: toBase64(x, byteLen),
|
x: bufferToBase64url(key.slice(1, byteLen + 1), byteLen),
|
||||||
y: toBase64(y, byteLen),
|
y: bufferToBase64url(key.slice(1 + byteLen), byteLen),
|
||||||
ext: true
|
ext: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unmarshalPrivateKey (curve, key) {
|
const unmarshalPrivateKey = (curve, key) => ({
|
||||||
const result = unmarshalPublicKey(curve, key.public)
|
...unmarshalPublicKey(curve, key.public),
|
||||||
result.d = toBase64(new BN(key.private))
|
d: bufferToBase64url(key.private)
|
||||||
return result
|
})
|
||||||
}
|
|
||||||
|
@ -1,17 +1,8 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
require('node-forge/lib/rsa')
|
require('node-forge/lib/rsa')
|
||||||
require('node-forge/lib/util')
|
|
||||||
require('node-forge/lib/jsbn')
|
|
||||||
const forge = require('node-forge/lib/forge')
|
const forge = require('node-forge/lib/forge')
|
||||||
|
const { base64urlToBigInteger } = require('../util')
|
||||||
function base64urlToBigInteger (str) {
|
|
||||||
var bytes = forge.util.decode64(
|
|
||||||
(str + '==='.slice((str.length + 3) % 4))
|
|
||||||
.replace(/-/g, '+')
|
|
||||||
.replace(/_/g, '/'))
|
|
||||||
return new forge.jsbn.BigInteger(forge.util.bytesToHex(bytes), 16)
|
|
||||||
}
|
|
||||||
|
|
||||||
function convert (key, types) {
|
function convert (key, types) {
|
||||||
return types.map(t => base64urlToBigInteger(key[t]))
|
return types.map(t => base64urlToBigInteger(key[t]))
|
||||||
|
@ -1,68 +1,27 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const asn1 = require('asn1.js')
|
const { Buffer } = require('buffer')
|
||||||
|
require('node-forge/lib/asn1')
|
||||||
const util = require('./../util')
|
require('node-forge/lib/rsa')
|
||||||
const toBase64 = util.toBase64
|
const forge = require('node-forge/lib/forge')
|
||||||
const toBn = util.toBn
|
const { bigIntegerToUintBase64url, base64urlToBigInteger } = require('./../util')
|
||||||
|
|
||||||
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
|
// Convert a PKCS#1 in ASN1 DER format to a JWK key
|
||||||
exports.pkcs1ToJwk = function (bytes) {
|
exports.pkcs1ToJwk = function (bytes) {
|
||||||
const asn1 = RSAPrivateKey.decode(bytes, 'der')
|
const asn1 = forge.asn1.fromDer(bytes.toString('binary'))
|
||||||
|
const privateKey = forge.pki.privateKeyFromAsn1(asn1)
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc7518#section-6.3.1
|
||||||
return {
|
return {
|
||||||
kty: 'RSA',
|
kty: 'RSA',
|
||||||
n: toBase64(asn1.modulus),
|
n: bigIntegerToUintBase64url(privateKey.n),
|
||||||
e: toBase64(asn1.publicExponent),
|
e: bigIntegerToUintBase64url(privateKey.e),
|
||||||
d: toBase64(asn1.privateExponent),
|
d: bigIntegerToUintBase64url(privateKey.d),
|
||||||
p: toBase64(asn1.prime1),
|
p: bigIntegerToUintBase64url(privateKey.p),
|
||||||
q: toBase64(asn1.prime2),
|
q: bigIntegerToUintBase64url(privateKey.q),
|
||||||
dp: toBase64(asn1.exponent1),
|
dp: bigIntegerToUintBase64url(privateKey.dP),
|
||||||
dq: toBase64(asn1.exponent2),
|
dq: bigIntegerToUintBase64url(privateKey.dQ),
|
||||||
qi: toBase64(asn1.coefficient),
|
qi: bigIntegerToUintBase64url(privateKey.qInv),
|
||||||
alg: 'RS256',
|
alg: 'RS256',
|
||||||
kid: '2011-04-29'
|
kid: '2011-04-29'
|
||||||
}
|
}
|
||||||
@ -70,28 +29,29 @@ exports.pkcs1ToJwk = function (bytes) {
|
|||||||
|
|
||||||
// Convert a JWK key into PKCS#1 in ASN1 DER format
|
// Convert a JWK key into PKCS#1 in ASN1 DER format
|
||||||
exports.jwkToPkcs1 = function (jwk) {
|
exports.jwkToPkcs1 = function (jwk) {
|
||||||
return RSAPrivateKey.encode({
|
const asn1 = forge.pki.privateKeyToAsn1({
|
||||||
version: 0,
|
n: base64urlToBigInteger(jwk.n),
|
||||||
modulus: toBn(jwk.n),
|
e: base64urlToBigInteger(jwk.e),
|
||||||
publicExponent: toBn(jwk.e),
|
d: base64urlToBigInteger(jwk.d),
|
||||||
privateExponent: toBn(jwk.d),
|
p: base64urlToBigInteger(jwk.p),
|
||||||
prime1: toBn(jwk.p),
|
q: base64urlToBigInteger(jwk.q),
|
||||||
prime2: toBn(jwk.q),
|
dP: base64urlToBigInteger(jwk.dp),
|
||||||
exponent1: toBn(jwk.dp),
|
dQ: base64urlToBigInteger(jwk.dq),
|
||||||
exponent2: toBn(jwk.dq),
|
qInv: base64urlToBigInteger(jwk.qi)
|
||||||
coefficient: toBn(jwk.qi)
|
})
|
||||||
}, 'der')
|
|
||||||
|
return Buffer.from(forge.asn1.toDer(asn1).getBytes(), 'binary')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a PKCIX in ASN1 DER format to a JWK key
|
// Convert a PKCIX in ASN1 DER format to a JWK key
|
||||||
exports.pkixToJwk = function (bytes) {
|
exports.pkixToJwk = function (bytes) {
|
||||||
const ndata = PublicKey.decode(bytes, 'der')
|
const asn1 = forge.asn1.fromDer(bytes.toString('binary'))
|
||||||
const asn1 = RSAPublicKey.decode(ndata.subjectPublicKey.data, 'der')
|
const publicKey = forge.pki.publicKeyFromAsn1(asn1)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kty: 'RSA',
|
kty: 'RSA',
|
||||||
n: toBase64(asn1.modulus),
|
n: bigIntegerToUintBase64url(publicKey.n),
|
||||||
e: toBase64(asn1.publicExponent),
|
e: bigIntegerToUintBase64url(publicKey.e),
|
||||||
alg: 'RS256',
|
alg: 'RS256',
|
||||||
kid: '2011-04-29'
|
kid: '2011-04-29'
|
||||||
}
|
}
|
||||||
@ -99,16 +59,10 @@ exports.pkixToJwk = function (bytes) {
|
|||||||
|
|
||||||
// Convert a JWK key to PKCIX in ASN1 DER format
|
// Convert a JWK key to PKCIX in ASN1 DER format
|
||||||
exports.jwkToPkix = function (jwk) {
|
exports.jwkToPkix = function (jwk) {
|
||||||
return PublicKey.encode({
|
const asn1 = forge.pki.publicKeyToAsn1({
|
||||||
algorithm: {
|
n: base64urlToBigInteger(jwk.n),
|
||||||
algorithm: 'rsa',
|
e: base64urlToBigInteger(jwk.e)
|
||||||
none: null
|
})
|
||||||
},
|
|
||||||
subjectPublicKey: {
|
return Buffer.from(forge.asn1.toDer(asn1).getBytes(), 'binary')
|
||||||
data: RSAPublicKey.encode({
|
|
||||||
modulus: toBn(jwk.n),
|
|
||||||
publicExponent: toBn(jwk.e)
|
|
||||||
}, 'der')
|
|
||||||
}
|
|
||||||
}, 'der')
|
|
||||||
}
|
}
|
||||||
|
57
src/util.js
57
src/util.js
@ -1,20 +1,55 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const BN = require('asn1.js').bignum
|
const { Buffer } = require('buffer')
|
||||||
|
require('node-forge/lib/util')
|
||||||
|
require('node-forge/lib/jsbn')
|
||||||
|
const forge = require('node-forge/lib/forge')
|
||||||
|
|
||||||
// Convert a BN.js instance to a base64 encoded string without padding
|
exports.bigIntegerToUintBase64url = (num, len) => {
|
||||||
|
// Call `.abs()` to convert to unsigned
|
||||||
|
let buf = Buffer.from(num.abs().toByteArray()) // toByteArray converts to big endian
|
||||||
|
|
||||||
|
// toByteArray() gives us back a signed array, which will include a leading 0
|
||||||
|
// byte if the most significant bit of the number is 1:
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-integer
|
||||||
|
// Our number will always be positive so we should remove the leading padding.
|
||||||
|
buf = buf[0] === 0 ? buf.slice(1) : buf
|
||||||
|
|
||||||
|
if (len != null) {
|
||||||
|
if (buf.length > len) throw new Error('byte array longer than desired length')
|
||||||
|
buf = Buffer.concat([Buffer.alloc(len - buf.length), buf])
|
||||||
|
}
|
||||||
|
|
||||||
|
return exports.bufferToBase64url(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a Buffer to a base64 encoded string without padding
|
||||||
// Adapted from https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#appendix-C
|
// Adapted from https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#appendix-C
|
||||||
exports.toBase64 = function toBase64 (bn, len) {
|
exports.bufferToBase64url = buf => {
|
||||||
// if len is defined then the bytes are leading-0 padded to the length
|
return buf
|
||||||
const s = bn.toArrayLike(Buffer, 'be', len).toString('base64')
|
.toString('base64')
|
||||||
|
.split('=')[0] // Remove any trailing '='s
|
||||||
return s
|
|
||||||
.replace(/(=*)$/, '') // Remove any trailing '='s
|
|
||||||
.replace(/\+/g, '-') // 62nd char of encoding
|
.replace(/\+/g, '-') // 62nd char of encoding
|
||||||
.replace(/\//g, '_') // 63rd char of encoding
|
.replace(/\//g, '_') // 63rd char of encoding
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a base64 encoded string to a BN.js instance
|
// Convert a base64url encoded string to a BigInteger
|
||||||
exports.toBn = function toBn (str) {
|
exports.base64urlToBigInteger = str => {
|
||||||
return new BN(Buffer.from(str, 'base64'))
|
const buf = exports.base64urlToBuffer(str)
|
||||||
|
return new forge.jsbn.BigInteger(buf.toString('hex'), 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.base64urlToBuffer = (str, len) => {
|
||||||
|
str = (str + '==='.slice((str.length + 3) % 4))
|
||||||
|
.replace(/-/g, '+')
|
||||||
|
.replace(/_/g, '/')
|
||||||
|
|
||||||
|
let buf = Buffer.from(str, 'base64')
|
||||||
|
|
||||||
|
if (len != null) {
|
||||||
|
if (buf.length > len) throw new Error('byte array longer than desired length')
|
||||||
|
buf = Buffer.concat([Buffer.alloc(len - buf.length), buf])
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf
|
||||||
}
|
}
|
||||||
|
@ -6,26 +6,33 @@ const chai = require('chai')
|
|||||||
const dirtyChai = require('dirty-chai')
|
const dirtyChai = require('dirty-chai')
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
chai.use(dirtyChai)
|
chai.use(dirtyChai)
|
||||||
|
require('node-forge/lib/jsbn')
|
||||||
|
const forge = require('node-forge/lib/forge')
|
||||||
const util = require('../src/util')
|
const util = require('../src/util')
|
||||||
const BN = require('bn.js')
|
|
||||||
|
|
||||||
describe('Util', () => {
|
describe('Util', () => {
|
||||||
let bn
|
let bn
|
||||||
|
|
||||||
before((done) => {
|
before(() => {
|
||||||
bn = new BN('dead', 16)
|
bn = new forge.jsbn.BigInteger('dead', 16)
|
||||||
done()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('toBase64', (done) => {
|
it('should convert BigInteger to a uint base64url encoded string', () => {
|
||||||
expect(util.toBase64(bn)).to.eql('3q0')
|
expect(util.bigIntegerToUintBase64url(bn)).to.eql('3q0')
|
||||||
done()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('toBase64 zero padding', (done) => {
|
it('should convert BigInteger to a uint base64url encoded string with padding', () => {
|
||||||
const bnpad = new BN('ff', 16)
|
const bnpad = new forge.jsbn.BigInteger('ff', 16)
|
||||||
expect(util.toBase64(bnpad, 2)).to.eql('AP8')
|
expect(util.bigIntegerToUintBase64url(bnpad, 2)).to.eql('AP8')
|
||||||
done()
|
})
|
||||||
|
|
||||||
|
it('should convert base64url encoded string to BigInteger', () => {
|
||||||
|
const num = util.base64urlToBigInteger('3q0')
|
||||||
|
expect(num.equals(bn)).to.be.true()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should convert base64url encoded string to Buffer with padding', () => {
|
||||||
|
const buf = util.base64urlToBuffer('AP8', 2)
|
||||||
|
expect(Buffer.from([0, 255])).to.eql(buf)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user