mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-11-24 06:25:50 +00:00
NodeCrypto: use JWK encoding over DER in RSA
This commit is contained in:
parent
3320eaccb2
commit
933a51d4e4
@ -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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user