Having this code:
const { subtle } = require("crypto");
const getPem = require("rsa-pem-from-mod-exp");
const Token = require("jsonwebtoken");
async function createKeys() {
const keyPair = await subtle.generateKey({
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: "SHA-256"
}, true, ["sign", "verify"]);
const privateKey = await subtle.exportKey("jwk", keyPair.privateKey);
const priv = getPem(privateKey.n, privateKey.d).replace(/public/gi, "PRIVATE")
const pub = getPem(privateKey.n, privateKey.e);
return { priv, pub };
}
// createKeys().then(k => console.log(`priv:\n\n${k.priv}\n\npub:\n\n${k.pub}\n\n`));
const payload = { id: 1 };
createKeys().then(k => {
const signed = Token.sign(payload, k.priv, { algorithm: "RS256", keyid: "1" });
console.log(signed);
const verified = Token.verify(signed, k.pub, { algorithms: "RS256", complete: true });
console.log(verified);
})
gives:
node:internal/crypto/sig:131
const ret = this[kHandle].sign(data, format, type, passphrase, rsaPadding,
^
Error: error:1E08010C:DECODER routines::unsupported
at Sign.sign (node:internal/crypto/sig:131:29)
at Object.sign (/home/shepherd/Desktop/test/express/node_modules/jwa/index.js:152:45)
at Object.jwsSign [as sign] (/home/shepherd/Desktop/test/express/node_modules/jws/lib/sign-stream.js:32:24)
at module.exports [as sign] (/home/shepherd/Desktop/test/express/node_modules/jsonwebtoken/sign.js:204:16)
at /home/shepherd/Desktop/test/express/crypto/c.js:21:24 {
opensslErrorStack: [
'error:0688010A:asn1 encoding routines::nested asn1 error',
'error:0688010A:asn1 encoding routines::nested asn1 error',
'error:068000A8:asn1 encoding routines::wrong tag',
'error:0688010A:asn1 encoding routines::nested asn1 error',
'error:068000DF:asn1 encoding routines::too large'
],
library: 'DECODER routines',
reason: 'unsupported',
code: 'ERR_OSSL_UNSUPPORTED'
}
Node.js v18.6.0
But the keys seems find:
priv:
-----BEGIN RSA PRIVATE KEY-----
MIICCQKCAQEAq8G7V/OWIpwfM9wKailp9uB7GgYEywq5I5mW9vMfB7vndD1gzIWt
EyADeS/Dx5Yxq9LNSgvsltO5ZaQITuNBSVEJFZ6/aT9nG5zrH9QpqGXKNipcLjMc
SVYyc0ybXWHNbawZe0pWOqSVz5wja+H+9+JN8U//DsZZTe61wzc1tdRooMf4eQEh
Q49dwErrMunBinF35vDcJgkY4nzND3CxnALEdRYf34aYkuCLi5G11UUrHUoGN4di
9I+NVGIhj4Do9d6BvnsHDCSN5BTAFribe0y7AvqTgCD9JMb2OIqd+z3m+tjk+V64
yOOaHQEMKeRr5eDIxa2QD6FfK3Gpqmu5pQKCAQBLrP7J6DHoyuf2lgdish+Vnl+u
3iMDgRSEqnn5EbLE2hZHQXnicy2IRS0ymoiE6li1T5qS+wEBjYTc0zKz625LCvDZ
PUox6bUY1gFE01qNb1fymKRn2K4oY9mzsner49k67r6Fc4HdscGuKSn0MS2Bc40K
+0eyb1NOwpQEUNGR7KzmpP+c2WDoXd+vW5sSs9lEVGLzcNTcKiV/t2AMiJ7g6F7R
q7Kh/by0Q+jd5WhRPrcRrhMnwYhXhN8cMU4jC7o2ayR6Di1NiSVuHPF5Uowe31OB
1dbqYmYCIcIYb335PJSp1NQFD4dgyiTsRE9DB88ybTVTNdWeieQ5aeq1xQA7
-----END RSA PRIVATE KEY-----
pub:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAq8G7V/OWIpwfM9wKailp9uB7GgYEywq5I5mW9vMfB7vndD1gzIWt
EyADeS/Dx5Yxq9LNSgvsltO5ZaQITuNBSVEJFZ6/aT9nG5zrH9QpqGXKNipcLjMc
SVYyc0ybXWHNbawZe0pWOqSVz5wja+H+9+JN8U//DsZZTe61wzc1tdRooMf4eQEh
Q49dwErrMunBinF35vDcJgkY4nzND3CxnALEdRYf34aYkuCLi5G11UUrHUoGN4di
9I+NVGIhj4Do9d6BvnsHDCSN5BTAFribe0y7AvqTgCD9JMb2OIqd+z3m+tjk+V64
yOOaHQEMKeRr5eDIxa2QD6FfK3Gpqmu5pQIDAQAB
-----END RSA PUBLIC KEY-----
Those key console.log
ed from above by uncommenting it. So if the keys seems to be ok, whats the problem? The RSA routine says error:1E08010C:DECODER routines::unsupported
, but why?
PS: I need n, e variables to publish in OIDC discovery jwks_uri
. So i need library which provides these variables. Subtle library does.
As already mentioned in my comment, the posted private key is invalid. This can be checked e.g. with
openssl rsa -check -in <path to pem> -noout
The reason for this is that the private key is incorrectly converted with getPem()
, since the rsa-pem-from-mod-exp library only supports public keys.
An alternative is to generate PEM encoded keys with crypto, which can be used for signing and verifying with jsonwebtoken:
var crypto = require("crypto");
var Token = require("jsonwebtoken");
// 1. crypto: Generate keypair, PKCS#1 format, PEM encoding
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa',
{
modulusLength: 2048,
publicKeyEncoding: {type: 'pkcs1',format: 'pem'},
privateKeyEncoding: {type: 'pkcs1', format: 'pem'}
});
// 2. Sign verify using the generated PEM keys
var payload = "some payload";
const signed = Token.sign(payload, privateKey, { algorithm: "RS256", keyid: "1" });
const verified = Token.verify(signed, publicKey, { algorithms: "RS256", complete: true });
console.log(verified.payload);
Of course, this would also be achievable with WebCrypto, but crypto is less cumbersome and more comfortable overall. Besides, WebCrypto still has the status Experimental under NodeJS, s. here.
For extracting n, e, d the keys can be imported and exported as JWK:
// 3. Derive n,e,d
// crypto: Import PEM, export as JWK (the latter is only possible as of v15.9.0)
var pubJwk = crypto.createPublicKey(publicKey).export({format: 'jwk'});
console.log(pubJwk.e);
console.log(pubJwk.n);
var privJwk = crypto.createPrivateKey(privateKey).export({format: 'jwk'});
console.log(privJwk.d);
NodeJS supports JWKs only from v15.9.0. For earlier versions, you can apply e.g. pem-jwk:
// for earlier versions than v15.9.0 apply e.g. pem-jwk for conversion
var pem2jwk = require('pem-jwk').pem2jwk
var pubJwk = pem2jwk(publicKey)
console.log(pubJwk.e)
console.log(pubJwk.n)
var privJwk = pem2jwk(privateKey)
console.log(privJwk.d)
With the pem-jwk library it would also be possible to use the WebCrypto code by converting the JWKs to PEM keys (pem-jwk supports conversion in both directions). However, due to the experimental status of the WebCrypto API for NodeJS and the easier handling of the crypto module, this seems to me the less favorable option (although this is of course a matter of opinion).