From 933a51d4e417a3a8e75c644fb9ee3bef383ef10c Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Mon, 5 Feb 2024 15:33:35 +0100 Subject: [PATCH] NodeCrypto: use JWK encoding over DER in RSA --- src/crypto/public_key/rsa.js | 172 +++++++---------------------------- 1 file changed, 32 insertions(+), 140 deletions(-) diff --git a/src/crypto/public_key/rsa.js b/src/crypto/public_key/rsa.js index 6387bc25..79826f23 100644 --- a/src/crypto/public_key/rsa.js +++ b/src/crypto/public_key/rsa.js @@ -28,30 +28,6 @@ import enums from '../../enums'; const webCrypto = util.getWebCrypto(); 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 * @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 const jwk = await webCrypto.exportKey('jwk', keyPair.privateKey); // map JWK parameters to corresponding OpenPGP names - 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) - }; - } else if (util.getNodeCrypto() && nodeCrypto.generateKeyPair && RSAPrivateKey) { + return jwkToPrivate(jwk, e); + } else if (util.getNodeCrypto()) { const opts = { modulusLength: bits, publicExponent: e.toNumber(), - publicKeyEncoding: { type: 'pkcs1', format: 'der' }, - privateKeyEncoding: { type: 'pkcs1', format: 'der' } + publicKeyEncoding: { type: 'pkcs1', format: 'jwk' }, + privateKeyEncoding: { type: 'pkcs1', format: 'jwk' } }; - const prv = await new Promise((resolve, reject) => { - nodeCrypto.generateKeyPair('rsa', opts, (err, _, der) => { + const jwk = await new Promise((resolve, reject) => { + nodeCrypto.generateKeyPair('rsa', opts, (err, _, jwkPrivateKey) => { if (err) { reject(err); } else { - resolve(RSAPrivateKey.decode(der, 'der')); + resolve(jwkPrivateKey); } }); }); - /** - * 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) - }; + return jwkToPrivate(jwk, e); } // 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) { - 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)); sign.write(data); sign.end(); - 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) - }; - 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)); + + const jwk = await privateToJWK(n, e, d, p, q, u); + return new Uint8Array(sign.sign({ key: jwk, format: 'jwk', type: 'pkcs1' })); } 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) { - 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)); verify.write(data); 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 { return await verify.verify(key, s); } catch (err) { @@ -414,22 +332,9 @@ async function nodeVerify(hashAlgo, data, s, 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)); } @@ -446,36 +351,9 @@ async function bnEncrypt(data, n, e) { } 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 { return new Uint8Array(nodeCrypto.privateDecrypt(key, data)); } catch (err) { @@ -570,3 +448,17 @@ function publicToJWK(n, e) { 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) + }; +}