mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-06-12 00:56:41 +00:00
Node: drop asn1.js dependency (#1722)
asn1.js is a fairly large lib and was simply needed to handle DER encodings in some NodeCrypto operations. This change replaces the dependency by moving to: - JWT encoding for RSA (support added in Node v15) - a much lighter dependency (eckey-utils) for ECDSA, where JWT cannot be used for now, as Node has yet to add decoding support for Brainpool curves. The change also allows us to drop BN.js as a direct dependency, optimising the BigInteger-related chunking in the lightweight build.
This commit is contained in:
parent
a6283e64cc
commit
151f15e282
59
package-lock.json
generated
59
package-lock.json
generated
@ -8,9 +8,6 @@
|
|||||||
"name": "openpgp",
|
"name": "openpgp",
|
||||||
"version": "6.0.0-alpha.0",
|
"version": "6.0.0-alpha.0",
|
||||||
"license": "LGPL-3.0+",
|
"license": "LGPL-3.0+",
|
||||||
"dependencies": {
|
|
||||||
"asn1.js": "^5.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@openpgp/asmcrypto.js": "^3.1.0",
|
"@openpgp/asmcrypto.js": "^3.1.0",
|
||||||
"@openpgp/jsdoc": "^3.6.11",
|
"@openpgp/jsdoc": "^3.6.11",
|
||||||
@ -28,10 +25,10 @@
|
|||||||
"@types/chai": "^4.2.14",
|
"@types/chai": "^4.2.14",
|
||||||
"argon2id": "^1.0.1",
|
"argon2id": "^1.0.1",
|
||||||
"benchmark": "^2.1.4",
|
"benchmark": "^2.1.4",
|
||||||
"bn.js": "^4.11.8",
|
|
||||||
"c8": "^8.0.1",
|
"c8": "^8.0.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
|
"eckey-utils": "^0.7.14",
|
||||||
"eslint": "^8.34.0",
|
"eslint": "^8.34.0",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
@ -1775,16 +1772,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/assertion-error": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
||||||
@ -1882,7 +1869,8 @@
|
|||||||
"node_modules/bn.js": {
|
"node_modules/bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.8",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"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": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.19.2",
|
"version": "1.19.2",
|
||||||
@ -2683,6 +2671,12 @@
|
|||||||
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
|
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@ -4147,7 +4141,8 @@
|
|||||||
"node_modules/inherits": {
|
"node_modules/inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
"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": {
|
"node_modules/internal-slot": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
@ -5216,11 +5211,6 @@
|
|||||||
"node": ">=4"
|
"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": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
@ -8589,16 +8579,6 @@
|
|||||||
"es-shim-unscopables": "^1.0.0"
|
"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": {
|
"assertion-error": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
||||||
@ -8681,7 +8661,8 @@
|
|||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.8",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"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": {
|
"body-parser": {
|
||||||
"version": "1.19.2",
|
"version": "1.19.2",
|
||||||
@ -9292,6 +9273,12 @@
|
|||||||
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
|
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
|
||||||
"dev": true
|
"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": {
|
"ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@ -10420,7 +10407,8 @@
|
|||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"internal-slot": {
|
"internal-slot": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
@ -11255,11 +11243,6 @@
|
|||||||
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
|
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
|
||||||
"dev": true
|
"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": {
|
"minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
@ -78,10 +78,10 @@
|
|||||||
"@types/chai": "^4.2.14",
|
"@types/chai": "^4.2.14",
|
||||||
"argon2id": "^1.0.1",
|
"argon2id": "^1.0.1",
|
||||||
"benchmark": "^2.1.4",
|
"benchmark": "^2.1.4",
|
||||||
"bn.js": "^4.11.8",
|
|
||||||
"c8": "^8.0.1",
|
"c8": "^8.0.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
|
"eckey-utils": "^0.7.14",
|
||||||
"eslint": "^8.34.0",
|
"eslint": "^8.34.0",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
@ -106,9 +106,6 @@
|
|||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"web-streams-polyfill": "^3.2.0"
|
"web-streams-polyfill": "^3.2.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
|
||||||
"asn1.js": "^5.0.0"
|
|
||||||
},
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/openpgpjs/openpgpjs"
|
"url": "https://github.com/openpgpjs/openpgpjs"
|
||||||
|
@ -14,7 +14,7 @@ import { wasm } from '@rollup/plugin-wasm';
|
|||||||
// import pkg from './package.json' assert { type: 'json' };
|
// import pkg from './package.json' assert { type: 'json' };
|
||||||
const pkg = JSON.parse(readFileSync('./package.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 nodeBuiltinModules = builtinModules.concat(['module']);
|
||||||
|
|
||||||
const wasmOptions = {
|
const wasmOptions = {
|
||||||
|
@ -24,7 +24,7 @@ import enums from '../../../enums';
|
|||||||
import util from '../../../util';
|
import util from '../../../util';
|
||||||
import { getRandomBytes } from '../../random';
|
import { getRandomBytes } from '../../random';
|
||||||
import hash from '../../hash';
|
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 webCrypto = util.getWebCrypto();
|
||||||
const nodeCrypto = util.getNodeCrypto();
|
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);
|
util.printDebugError('Browser did not support signing: ' + err.message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'node': {
|
case 'node':
|
||||||
const signature = await nodeSign(curve, hashAlgo, message, keyPair);
|
return nodeSign(curve, hashAlgo, message, privateKey);
|
||||||
return {
|
|
||||||
r: signature.r.toArrayLike(Uint8Array),
|
|
||||||
s: signature.s.toArrayLike(Uint8Array)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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));
|
const sign = nodeCrypto.createSign(enums.read(enums.hash, hashAlgo));
|
||||||
sign.write(message);
|
sign.write(message);
|
||||||
sign.end();
|
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.subarray(0, len),
|
||||||
|
s: signature.subarray(len, len << 1)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function nodeVerify(curve, hashAlgo, { r, s }, message, publicKey) {
|
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));
|
const verify = nodeCrypto.createVerify(enums.read(enums.hash, hashAlgo));
|
||||||
verify.write(message);
|
verify.write(message);
|
||||||
verify.end();
|
verify.end();
|
||||||
const key = SubjectPublicKeyInfo.encode({
|
|
||||||
algorithm: {
|
const signature = util.concatUint8Array([r, s]);
|
||||||
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');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return verify.verify(key, signature);
|
return verify.verify({ key: derPublicKey, format: 'der', type: 'spki', dsaEncoding: 'ieee-p1363' }, signature);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return false;
|
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;
|
|
||||||
|
@ -28,30 +28,6 @@ import enums from '../../enums';
|
|||||||
|
|
||||||
const webCrypto = util.getWebCrypto();
|
const webCrypto = util.getWebCrypto();
|
||||||
const nodeCrypto = util.getNodeCrypto();
|
const nodeCrypto = util.getNodeCrypto();
|
||||||
const asn1 = nodeCrypto ? util.nodeRequire('asn1.js') : undefined;
|
|
||||||
|
|
||||||
/* eslint-disable no-invalid-this */
|
|
||||||
const RSAPrivateKey = nodeCrypto ? asn1.define('RSAPrivateKey', function () {
|
|
||||||
this.seq().obj( // used for native NodeJS crypto
|
|
||||||
this.key('version').int(), // 0
|
|
||||||
this.key('modulus').int(), // n
|
|
||||||
this.key('publicExponent').int(), // e
|
|
||||||
this.key('privateExponent').int(), // d
|
|
||||||
this.key('prime1').int(), // p
|
|
||||||
this.key('prime2').int(), // q
|
|
||||||
this.key('exponent1').int(), // dp
|
|
||||||
this.key('exponent2').int(), // dq
|
|
||||||
this.key('coefficient').int() // u
|
|
||||||
);
|
|
||||||
}) : undefined;
|
|
||||||
|
|
||||||
const RSAPublicKey = nodeCrypto ? asn1.define('RSAPubliceKey', function () {
|
|
||||||
this.seq().obj( // used for native NodeJS crypto
|
|
||||||
this.key('modulus').int(), // n
|
|
||||||
this.key('publicExponent').int() // e
|
|
||||||
);
|
|
||||||
}) : undefined;
|
|
||||||
/* eslint-enable no-invalid-this */
|
|
||||||
|
|
||||||
/** Create signature
|
/** Create signature
|
||||||
* @param {module:enums.hash} hashAlgo - Hash algorithm
|
* @param {module:enums.hash} hashAlgo - Hash algorithm
|
||||||
@ -178,47 +154,24 @@ export async function generate(bits, e) {
|
|||||||
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-33
|
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-33
|
||||||
const jwk = await webCrypto.exportKey('jwk', keyPair.privateKey);
|
const jwk = await webCrypto.exportKey('jwk', keyPair.privateKey);
|
||||||
// map JWK parameters to corresponding OpenPGP names
|
// map JWK parameters to corresponding OpenPGP names
|
||||||
return {
|
return jwkToPrivate(jwk, e);
|
||||||
n: b64ToUint8Array(jwk.n),
|
} else if (util.getNodeCrypto()) {
|
||||||
e: e.toUint8Array(),
|
|
||||||
d: b64ToUint8Array(jwk.d),
|
|
||||||
// switch p and q
|
|
||||||
p: b64ToUint8Array(jwk.q),
|
|
||||||
q: b64ToUint8Array(jwk.p),
|
|
||||||
// Since p and q are switched in places, u is the inverse of jwk.q
|
|
||||||
u: b64ToUint8Array(jwk.qi)
|
|
||||||
};
|
|
||||||
} else if (util.getNodeCrypto() && nodeCrypto.generateKeyPair && RSAPrivateKey) {
|
|
||||||
const opts = {
|
const opts = {
|
||||||
modulusLength: bits,
|
modulusLength: bits,
|
||||||
publicExponent: e.toNumber(),
|
publicExponent: e.toNumber(),
|
||||||
publicKeyEncoding: { type: 'pkcs1', format: 'der' },
|
publicKeyEncoding: { type: 'pkcs1', format: 'jwk' },
|
||||||
privateKeyEncoding: { type: 'pkcs1', format: 'der' }
|
privateKeyEncoding: { type: 'pkcs1', format: 'jwk' }
|
||||||
};
|
};
|
||||||
const prv = await new Promise((resolve, reject) => {
|
const jwk = await new Promise((resolve, reject) => {
|
||||||
nodeCrypto.generateKeyPair('rsa', opts, (err, _, der) => {
|
nodeCrypto.generateKeyPair('rsa', opts, (err, _, jwkPrivateKey) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
resolve(RSAPrivateKey.decode(der, 'der'));
|
resolve(jwkPrivateKey);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
/**
|
return jwkToPrivate(jwk, e);
|
||||||
* OpenPGP spec differs from DER spec, DER: `u = (inverse of q) mod p`, OpenPGP: `u = (inverse of p) mod q`.
|
|
||||||
* @link https://tools.ietf.org/html/rfc3447#section-3.2
|
|
||||||
* @link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-08#section-5.6.1
|
|
||||||
*/
|
|
||||||
return {
|
|
||||||
n: prv.modulus.toArrayLike(Uint8Array),
|
|
||||||
e: prv.publicExponent.toArrayLike(Uint8Array),
|
|
||||||
d: prv.privateExponent.toArrayLike(Uint8Array),
|
|
||||||
// switch p and q
|
|
||||||
p: prv.prime2.toArrayLike(Uint8Array),
|
|
||||||
q: prv.prime1.toArrayLike(Uint8Array),
|
|
||||||
// Since p and q are switched in places, we can keep u as defined by DER
|
|
||||||
u: prv.coefficient.toArrayLike(Uint8Array)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSA keygen fallback using 40 iterations of the Miller-Rabin test
|
// RSA keygen fallback using 40 iterations of the Miller-Rabin test
|
||||||
@ -332,36 +285,12 @@ async function webSign(hashName, data, n, e, d, p, q, u) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function nodeSign(hashAlgo, data, n, e, d, p, q, u) {
|
async function nodeSign(hashAlgo, data, n, e, d, p, q, u) {
|
||||||
const { default: BN } = await import('bn.js');
|
|
||||||
const pBNum = new BN(p);
|
|
||||||
const qBNum = new BN(q);
|
|
||||||
const dBNum = new BN(d);
|
|
||||||
const dq = dBNum.mod(qBNum.subn(1)); // d mod (q-1)
|
|
||||||
const dp = dBNum.mod(pBNum.subn(1)); // d mod (p-1)
|
|
||||||
const sign = nodeCrypto.createSign(enums.read(enums.hash, hashAlgo));
|
const sign = nodeCrypto.createSign(enums.read(enums.hash, hashAlgo));
|
||||||
sign.write(data);
|
sign.write(data);
|
||||||
sign.end();
|
sign.end();
|
||||||
const keyObject = {
|
|
||||||
version: 0,
|
const jwk = await privateToJWK(n, e, d, p, q, u);
|
||||||
modulus: new BN(n),
|
return new Uint8Array(sign.sign({ key: jwk, format: 'jwk', type: 'pkcs1' }));
|
||||||
publicExponent: new BN(e),
|
|
||||||
privateExponent: new BN(d),
|
|
||||||
// switch p and q
|
|
||||||
prime1: new BN(q),
|
|
||||||
prime2: new BN(p),
|
|
||||||
// switch dp and dq
|
|
||||||
exponent1: dq,
|
|
||||||
exponent2: dp,
|
|
||||||
coefficient: new BN(u)
|
|
||||||
};
|
|
||||||
if (typeof nodeCrypto.createPrivateKey !== 'undefined') { //from version 11.6.0 Node supports der encoded key objects
|
|
||||||
const der = RSAPrivateKey.encode(keyObject, 'der');
|
|
||||||
return new Uint8Array(sign.sign({ key: der, format: 'der', type: 'pkcs1' }));
|
|
||||||
}
|
|
||||||
const pem = RSAPrivateKey.encode(keyObject, 'pem', {
|
|
||||||
label: 'RSA PRIVATE KEY'
|
|
||||||
});
|
|
||||||
return new Uint8Array(sign.sign(pem));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function bnVerify(hashAlgo, s, n, e, hashed) {
|
async function bnVerify(hashAlgo, s, n, e, hashed) {
|
||||||
@ -388,24 +317,13 @@ async function webVerify(hashName, data, s, n, e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function nodeVerify(hashAlgo, data, s, n, e) {
|
async function nodeVerify(hashAlgo, data, s, n, e) {
|
||||||
const { default: BN } = await import('bn.js');
|
const jwk = publicToJWK(n, e);
|
||||||
|
const key = { key: jwk, format: 'jwk', type: 'pkcs1' };
|
||||||
|
|
||||||
const verify = nodeCrypto.createVerify(enums.read(enums.hash, hashAlgo));
|
const verify = nodeCrypto.createVerify(enums.read(enums.hash, hashAlgo));
|
||||||
verify.write(data);
|
verify.write(data);
|
||||||
verify.end();
|
verify.end();
|
||||||
const keyObject = {
|
|
||||||
modulus: new BN(n),
|
|
||||||
publicExponent: new BN(e)
|
|
||||||
};
|
|
||||||
let key;
|
|
||||||
if (typeof nodeCrypto.createPrivateKey !== 'undefined') { //from version 11.6.0 Node supports der encoded key objects
|
|
||||||
const der = RSAPublicKey.encode(keyObject, 'der');
|
|
||||||
key = { key: der, format: 'der', type: 'pkcs1' };
|
|
||||||
} else {
|
|
||||||
key = RSAPublicKey.encode(keyObject, 'pem', {
|
|
||||||
label: 'RSA PUBLIC KEY'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
return await verify.verify(key, s);
|
return await verify.verify(key, s);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -414,22 +332,9 @@ async function nodeVerify(hashAlgo, data, s, n, e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function nodeEncrypt(data, n, e) {
|
async function nodeEncrypt(data, n, e) {
|
||||||
const { default: BN } = await import('bn.js');
|
const jwk = publicToJWK(n, e);
|
||||||
|
const key = { key: jwk, format: 'jwk', type: 'pkcs1', padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
||||||
|
|
||||||
const keyObject = {
|
|
||||||
modulus: new BN(n),
|
|
||||||
publicExponent: new BN(e)
|
|
||||||
};
|
|
||||||
let key;
|
|
||||||
if (typeof nodeCrypto.createPrivateKey !== 'undefined') {
|
|
||||||
const der = RSAPublicKey.encode(keyObject, 'der');
|
|
||||||
key = { key: der, format: 'der', type: 'pkcs1', padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
|
||||||
} else {
|
|
||||||
const pem = RSAPublicKey.encode(keyObject, 'pem', {
|
|
||||||
label: 'RSA PUBLIC KEY'
|
|
||||||
});
|
|
||||||
key = { key: pem, padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
|
||||||
}
|
|
||||||
return new Uint8Array(nodeCrypto.publicEncrypt(key, data));
|
return new Uint8Array(nodeCrypto.publicEncrypt(key, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,36 +351,9 @@ async function bnEncrypt(data, n, e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function nodeDecrypt(data, n, e, d, p, q, u, randomPayload) {
|
async function nodeDecrypt(data, n, e, d, p, q, u, randomPayload) {
|
||||||
const { default: BN } = await import('bn.js');
|
const jwk = await privateToJWK(n, e, d, p, q, u);
|
||||||
|
const key = { key: jwk, format: 'jwk' , type: 'pkcs1', padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
||||||
|
|
||||||
const pBNum = new BN(p);
|
|
||||||
const qBNum = new BN(q);
|
|
||||||
const dBNum = new BN(d);
|
|
||||||
const dq = dBNum.mod(qBNum.subn(1)); // d mod (q-1)
|
|
||||||
const dp = dBNum.mod(pBNum.subn(1)); // d mod (p-1)
|
|
||||||
const keyObject = {
|
|
||||||
version: 0,
|
|
||||||
modulus: new BN(n),
|
|
||||||
publicExponent: new BN(e),
|
|
||||||
privateExponent: new BN(d),
|
|
||||||
// switch p and q
|
|
||||||
prime1: new BN(q),
|
|
||||||
prime2: new BN(p),
|
|
||||||
// switch dp and dq
|
|
||||||
exponent1: dq,
|
|
||||||
exponent2: dp,
|
|
||||||
coefficient: new BN(u)
|
|
||||||
};
|
|
||||||
let key;
|
|
||||||
if (typeof nodeCrypto.createPrivateKey !== 'undefined') {
|
|
||||||
const der = RSAPrivateKey.encode(keyObject, 'der');
|
|
||||||
key = { key: der, format: 'der' , type: 'pkcs1', padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
|
||||||
} else {
|
|
||||||
const pem = RSAPrivateKey.encode(keyObject, 'pem', {
|
|
||||||
label: 'RSA PRIVATE KEY'
|
|
||||||
});
|
|
||||||
key = { key: pem, padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
return new Uint8Array(nodeCrypto.privateDecrypt(key, data));
|
return new Uint8Array(nodeCrypto.privateDecrypt(key, data));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -570,3 +448,17 @@ function publicToJWK(n, e) {
|
|||||||
ext: true
|
ext: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Convert JWK private key to OpenPGP private key params */
|
||||||
|
function jwkToPrivate(jwk, e) {
|
||||||
|
return {
|
||||||
|
n: b64ToUint8Array(jwk.n),
|
||||||
|
e: e.toUint8Array(),
|
||||||
|
d: b64ToUint8Array(jwk.d),
|
||||||
|
// switch p and q
|
||||||
|
p: b64ToUint8Array(jwk.q),
|
||||||
|
q: b64ToUint8Array(jwk.p),
|
||||||
|
// Since p and q are switched in places, u is the inverse of jwk.q
|
||||||
|
u: b64ToUint8Array(jwk.qi)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import BN from 'bn.js';
|
|
||||||
import { use as chaiUse, expect } from 'chai';
|
import { use as chaiUse, expect } from 'chai';
|
||||||
import chaiAsPromised from 'chai-as-promised'; // eslint-disable-line import/newline-after-import
|
import chaiAsPromised from 'chai-as-promised'; // eslint-disable-line import/newline-after-import
|
||||||
chaiUse(chaiAsPromised);
|
chaiUse(chaiAsPromised);
|
||||||
|
|
||||||
import openpgp from '../initOpenpgp.js';
|
import openpgp from '../initOpenpgp.js';
|
||||||
|
import util from '../../src/util.js';
|
||||||
|
|
||||||
const armoredDSAKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
const armoredDSAKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
@ -387,15 +387,16 @@ export default () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('detect g with small order', async function() {
|
it('detect g with small order', async function() {
|
||||||
const keyPacket = await cloneKeyPacket(egKey);
|
const BigInteger = await util.getBigInteger();
|
||||||
const p = keyPacket.publicParams.p;
|
|
||||||
const g = keyPacket.publicParams.g;
|
|
||||||
|
|
||||||
const pBN = new BN(p);
|
const keyPacket = await cloneKeyPacket(egKey);
|
||||||
const gModP = new BN(g).toRed(new BN.red(pBN));
|
const { p, g } = keyPacket.publicParams;
|
||||||
|
|
||||||
|
const pBN = new BigInteger(p);
|
||||||
|
const gBN = new BigInteger(g);
|
||||||
// g**(p-1)/2 has order 2
|
// g**(p-1)/2 has order 2
|
||||||
const gOrd2 = gModP.redPow(pBN.subn(1).shrn(1));
|
const gOrd2 = gBN.modExp(pBN.dec().irightShift(new BigInteger(1)), pBN);
|
||||||
keyPacket.publicParams.g = gOrd2.toArrayLike(Uint8Array, 'be');
|
keyPacket.publicParams.g = gOrd2.toUint8Array();
|
||||||
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user