Node: drop asn1.js dependency in ECDSA

JWT is not an option for brainpool curves, at least for now.

For encoding the keys in DER, we switch to the much lighter eckey-utils
lib, which also allows to drop BN.js as a direct dependency.
This commit is contained in:
larabr 2024-02-05 17:46:43 +01:00
parent 933a51d4e4
commit b92c2e0114
4 changed files with 51 additions and 116 deletions

59
package-lock.json generated
View File

@ -8,9 +8,6 @@
"name": "openpgp",
"version": "6.0.0-alpha.0",
"license": "LGPL-3.0+",
"dependencies": {
"asn1.js": "^5.0.0"
},
"devDependencies": {
"@openpgp/asmcrypto.js": "^3.0.0",
"@openpgp/jsdoc": "^3.6.11",
@ -28,10 +25,10 @@
"@types/chai": "^4.2.14",
"argon2id": "^1.0.1",
"benchmark": "^2.1.4",
"bn.js": "^4.11.8",
"c8": "^8.0.1",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
"eckey-utils": "^0.7.14",
"eslint": "^8.34.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-base": "^15.0.0",
@ -1775,16 +1772,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/asn1.js": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.0.0.tgz",
"integrity": "sha512-Y+FKviD0uyIWWo/xE0XkUl0x1allKFhzEVJ+//2Dgqpy+n+B77MlPNqvyk7Vx50M9XyVzjnRhDqJAEAsyivlbA==",
"dependencies": {
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0"
}
},
"node_modules/assertion-error": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
@ -1882,7 +1869,8 @@
"node_modules/bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
"dev": true
},
"node_modules/body-parser": {
"version": "1.19.2",
@ -2683,6 +2671,12 @@
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
"dev": true
},
"node_modules/eckey-utils": {
"version": "0.7.14",
"resolved": "https://registry.npmjs.org/eckey-utils/-/eckey-utils-0.7.14.tgz",
"integrity": "sha512-s/mENS+mMnJjDSydy0muBQQHMTWJ1nPe8EiphANZrf+lv/1u35aP9WvWHTWqCBJ21blNIurGF7UoLjtaOpoCFw==",
"dev": true
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -4147,7 +4141,8 @@
"node_modules/inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"node_modules/internal-slot": {
"version": "1.0.3",
@ -5216,11 +5211,6 @@
"node": ">=4"
}
},
"node_modules/minimalistic-assert": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz",
"integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M="
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -8589,16 +8579,6 @@
"es-shim-unscopables": "^1.0.0"
}
},
"asn1.js": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.0.0.tgz",
"integrity": "sha512-Y+FKviD0uyIWWo/xE0XkUl0x1allKFhzEVJ+//2Dgqpy+n+B77MlPNqvyk7Vx50M9XyVzjnRhDqJAEAsyivlbA==",
"requires": {
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0"
}
},
"assertion-error": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
@ -8681,7 +8661,8 @@
"bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
"dev": true
},
"body-parser": {
"version": "1.19.2",
@ -9292,6 +9273,12 @@
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
"dev": true
},
"eckey-utils": {
"version": "0.7.14",
"resolved": "https://registry.npmjs.org/eckey-utils/-/eckey-utils-0.7.14.tgz",
"integrity": "sha512-s/mENS+mMnJjDSydy0muBQQHMTWJ1nPe8EiphANZrf+lv/1u35aP9WvWHTWqCBJ21blNIurGF7UoLjtaOpoCFw==",
"dev": true
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -10420,7 +10407,8 @@
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"internal-slot": {
"version": "1.0.3",
@ -11255,11 +11243,6 @@
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true
},
"minimalistic-assert": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz",
"integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M="
},
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",

View File

@ -78,10 +78,10 @@
"@types/chai": "^4.2.14",
"argon2id": "^1.0.1",
"benchmark": "^2.1.4",
"bn.js": "^4.11.8",
"c8": "^8.0.1",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
"eckey-utils": "^0.7.14",
"eslint": "^8.34.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-base": "^15.0.0",
@ -106,9 +106,6 @@
"typescript": "^5.3.3",
"web-streams-polyfill": "^3.2.0"
},
"dependencies": {
"asn1.js": "^5.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/openpgpjs/openpgpjs"

View File

@ -14,7 +14,7 @@ import { wasm } from '@rollup/plugin-wasm';
// import pkg from './package.json' assert { type: 'json' };
const pkg = JSON.parse(readFileSync('./package.json'));
const nodeDependencies = Object.keys(pkg.dependencies);
const nodeDependencies = Object.keys(pkg.dependencies || {});
const nodeBuiltinModules = builtinModules.concat(['module']);
const wasmOptions = {

View File

@ -24,7 +24,7 @@ import enums from '../../../enums';
import util from '../../../util';
import { getRandomBytes } from '../../random';
import hash from '../../hash';
import { CurveWithOID, webCurves, privateToJWK, rawPublicToJWK, validateStandardParams } from './oid_curves';
import { CurveWithOID, webCurves, privateToJWK, rawPublicToJWK, validateStandardParams, nodeCurves } from './oid_curves';
const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto();
@ -63,13 +63,8 @@ export async function sign(oid, hashAlgo, message, publicKey, privateKey, hashed
util.printDebugError('Browser did not support signing: ' + err.message);
}
break;
case 'node': {
const signature = await nodeSign(curve, hashAlgo, message, keyPair);
return {
r: signature.r.toArrayLike(Uint8Array),
s: signature.s.toArrayLike(Uint8Array)
};
}
case 'node':
return nodeSign(curve, hashAlgo, message, privateKey);
}
}
@ -247,85 +242,45 @@ async function webVerify(curve, hashAlgo, { r, s }, message, publicKey) {
);
}
async function nodeSign(curve, hashAlgo, message, keyPair) {
async function nodeSign(curve, hashAlgo, message, privateKey) {
// JWT encoding cannot be used for now, as Brainpool curves are not supported
const ecKeyUtils = util.nodeRequire('eckey-utils');
const nodeBuffer = util.getNodeBuffer();
const { privateKey: derPrivateKey } = ecKeyUtils.generateDer({
curveName: nodeCurves[curve.name],
privateKey: nodeBuffer.from(privateKey)
});
const sign = nodeCrypto.createSign(enums.read(enums.hash, hashAlgo));
sign.write(message);
sign.end();
const key = ECPrivateKey.encode({
version: 1,
parameters: curve.oid,
privateKey: Array.from(keyPair.privateKey),
publicKey: { unused: 0, data: Array.from(keyPair.publicKey) }
}, 'pem', {
label: 'EC PRIVATE KEY'
});
return ECDSASignature.decode(sign.sign(key), 'der');
const signature = new Uint8Array(sign.sign({ key: derPrivateKey, format: 'der', type: 'sec1', dsaEncoding: 'ieee-p1363' }));
const len = curve.payloadSize;
return {
r: signature.slice(0, len),
s: signature.slice(len, len << 1)
};
}
async function nodeVerify(curve, hashAlgo, { r, s }, message, publicKey) {
const { default: BN } = await import('bn.js');
const ecKeyUtils = util.nodeRequire('eckey-utils');
const nodeBuffer = util.getNodeBuffer();
const { publicKey: derPublicKey } = ecKeyUtils.generateDer({
curveName: nodeCurves[curve.name],
publicKey: nodeBuffer.from(publicKey)
});
const verify = nodeCrypto.createVerify(enums.read(enums.hash, hashAlgo));
verify.write(message);
verify.end();
const key = SubjectPublicKeyInfo.encode({
algorithm: {
algorithm: [1, 2, 840, 10045, 2, 1],
parameters: curve.oid
},
subjectPublicKey: { unused: 0, data: Array.from(publicKey) }
}, 'pem', {
label: 'PUBLIC KEY'
});
const signature = ECDSASignature.encode({
r: new BN(r), s: new BN(s)
}, 'der');
const signature = util.concatUint8Array([r, s]);
try {
return verify.verify(key, signature);
return verify.verify({ key: derPublicKey, format: 'der', type: 'spki', dsaEncoding: 'ieee-p1363' }, signature);
} catch (err) {
return false;
}
}
// Originally written by Owen Smith https://github.com/omsmith
// Adapted on Feb 2018 from https://github.com/Brightspace/node-jwk-to-pem/
/* eslint-disable no-invalid-this */
const asn1 = nodeCrypto ? util.nodeRequire('asn1.js') : undefined;
const ECDSASignature = nodeCrypto ?
asn1.define('ECDSASignature', function() {
this.seq().obj(
this.key('r').int(),
this.key('s').int()
);
}) : undefined;
const ECPrivateKey = nodeCrypto ?
asn1.define('ECPrivateKey', function() {
this.seq().obj(
this.key('version').int(),
this.key('privateKey').octstr(),
this.key('parameters').explicit(0).optional().any(),
this.key('publicKey').explicit(1).optional().bitstr()
);
}) : undefined;
const AlgorithmIdentifier = nodeCrypto ?
asn1.define('AlgorithmIdentifier', function() {
this.seq().obj(
this.key('algorithm').objid(),
this.key('parameters').optional().any()
);
}) : undefined;
const SubjectPublicKeyInfo = nodeCrypto ?
asn1.define('SubjectPublicKeyInfo', function() {
this.seq().obj(
this.key('algorithm').use(AlgorithmIdentifier),
this.key('subjectPublicKey').bitstr()
);
}) : undefined;