2

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.loged 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.

milanHrabos
  • 2,010
  • 3
  • 11
  • 45
  • 1
    Your private key is invalid, which you can see e.g. with `openssl rsa -check -in -noout`. – Topaco Jul 20 '22 at 10:01
  • Why do you use the WebCrypto API under NodeJS for key generation and PEM export instead of the more convenient [crypto](https://nodejs.org/api/crypto.html) module? – Topaco Jul 20 '22 at 10:10
  • Because I need n, e, d variables as to to public n and e for OIDC `jwks` endpoint. And subtle library can provide me these variables – milanHrabos Jul 20 '22 at 10:12
  • (1) mathematically RSA privatekey can be (n,d) but _PEM format_ "RSA PRIVATE KEY" is CRT-form defined in [RFC8017 A.1.2 et pred](https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.2) which your code doesn't even remotely do. (2) Since nodejs v15.9, builtin-crypto.keyObject.export supports 'jwk' format which gives what you actually want, but didn't ask about. – dave_thompson_085 Jul 20 '22 at 10:29
  • 1
    The *node-rsa-pem-from-mod-exp* library you are using seems to support only public keys, but not the more complex private keys. How about the following alternative: Create the keys and export them as PEM (for signing/verifying with *jsonwebtoken*). Convert PEM to JWK (to extract n,d,e), or export again as JWK as mentioned in the prevoius comment. – Topaco Jul 20 '22 at 10:29

1 Answers1

1

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).

Topaco
  • 40,594
  • 4
  • 35
  • 62