Maximilian Fixl
Maximilian Fixl

Reputation: 688

How can I sign a JWT with RS256 using PS256 private key in node.js

I have the following keys created with openssl (the algorithms etc. are specified by the counterpart):

openssl req -newkey rsa-pss -new -nodes -x509 -days 3650 -pkeyopt rsa_keygen_bits:4096 -sigopt rsa_pss_saltlen:32 -keyout key.pem -out cert.pem
openssl x509 -pubkey -noout -in cert.pem > pubkey.pem

I have the following working Java code:

private static String createJwtToken() {
    try {
        Path privateKeyPath = Path.of("/Users/abc/bzstcerts/key.pem");
        Path publicKeyPath = Path.of("/Users/abc/bzstcerts/pubkey.pem");

        RSAPrivateKey privateKey = (RSAPrivateKey)readPrivateKey(privateKeyPath);
        RSAPublicKey publicKey =(RSAPublicKey) readPublicKey(publicKeyPath);

        Algorithm algorithm = Algorithm.RSA256( publicKey,  privateKey);

        return JWT.create()
                .withIssuer("iss")
                .withSubject("subject")
                .withAudience("audience")
                .withIssuedAt(new Date())
                .withExpiresAt(new Date(System.currentTimeMillis() + (5 * 60 * 1000L)))
                .withJWTId(UUID.randomUUID().toString())
                .withNotBefore(new Date(System.currentTimeMillis() - 60 * 1000L))
                .sign(algorithm);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
 }

private static PrivateKey readPrivateKey(Path privateKeyPath) throws Exception {
    byte[] keyBytes = Files.readAllBytes(privateKeyPath);
    keyBytes = Base64.getDecoder().decode(keyBytes);
    PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSASSA-PSS");
    return keyFactory.generatePrivate(spec);
}

private static PublicKey readPublicKey(Path publicKeyPath) throws Exception {
    byte[] keyBytes = Files.readAllBytes(publicKeyPath);
    keyBytes = Base64.getDecoder().decode(keyBytes);
    X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSASSA-PSS");
    return keyFactory.generatePublic(spec);
}

Here the private and public keys are loaded using RSASSA-PSS and casted to RSAPrivateKey and RSAPublicKey respectively.

I try to achieve the same with jose in node but I have no success.

import * as fs from 'fs'
import * as jose from 'jose'
import * as uuid from 'uuid'
import path from 'node:path'

const jwtUuid = uuid.v4()
const currentTime = Math.floor(Date.now() / 1000)
const expirationTime = currentTime + 300
const notValidBeforeTime = currentTime - 60
const clientId = process.env.CLIENT_ID
const audienceUrl = process.env.AUDIENCE_URL

const getSignedJWTToken = async () => {
    const signOptions: jose.JWTPayload = {
        iss: clientId,
        sub: clientId,
        aud: audienceUrl,
        iat: currentTime,
        exp: expirationTime,
        jti: jwtUuid,
        nbf: notValidBeforeTime,
    }
    const privateKey = await getPrivateKey()

    const signedJwt = await new jose.SignJWT(signOptions)
        .setProtectedHeader({ alg: 'PS256' })
        .setIssuer(clientId)
        .setSubject(clientId)
        .setAudience(audienceUrl)
        .setIssuedAt(currentTime)
        .setExpirationTime(expirationTime)
        .setJti(jwtUuid)
        .setNotBefore(notValidBeforeTime)
        .sign(privateKey)
    console.log(`\nsignedJwt in generateRequestToken:\n${signedJwt}`)
    return signedJwt
}

const certPath = path.resolve('./assets/cert')
const privateKeyName = 'key.pem'
const getPrivateKey = async () => {
    const privateKeyPath = `${certPath}/${privateKeyName}`
    const privateKeyString = fs.readFileSync(privateKeyPath, 'utf-8')
    return await jose.importPKCS8(privateKeyString, 'PS256')
}

const publicKeyName = 'pubkey.pem'
const getPublicKey = async () => {
    const publicKeyPath = `${certPath}/${publicKeyName}`
    const publicKeyString = fs.readFileSync(publicKeyPath, 'utf-8')
    console.log(`\npublicKeyString from readFileSync:\n${publicKeyString}`)
    return await jose.importSPKI(publicKeyString, 'PS256')
}

I can load the keys and sign the jwt using PS256 algorithm, but I can't load the keys with PS256 algorithm, convert them to RS256 algorithm and sign the jwt. How can I achieve this? Thanks a lot for your help in advance!

Upvotes: 0

Views: 774

Answers (2)

Daniel Röder
Daniel Röder

Reputation: 1

i am working on a very similar problem. The German Ministry of Finance has the requirement that the certificate has to have rsa-pss. But every implementation of generating JWT has a problem using such a key generated with rsa-pss to sign. It may validate locally but it never does on jwt.io or at the API. Generally you can sign with openssl using a command like: openssl dgst -sha256 -binary -sign /path/to/key.pem And it works if you use a certificate that wasn't created with "rsa-pss", jwt.io says they are valid, but not having rsa-pss is not an option here.

Upvotes: 0

Filip Skokan
Filip Skokan

Reputation: 211

I can load the keys and sign the jwt using PS256 algorithm, but I can't load the keys with PS256 algorithm, convert them to RS256 algorithm and sign the jwt. How can I achieve this? Thanks a lot for your help in advance!

Here the private and public keys are loaded using RSASSA-PSS and casted to RSAPrivateKey and RSAPublicKey respectively.

You've generated an RSASSA-PSS key, it cannot be used for RS256, at least not in Node.js, there's no manipulation in the crypto module you could do to drop/change the key's parameters.

If you want a key that's usable for both RS256 and PS256 in Node.js use this

openssl req -newkey rsa -new -nodes -x509 -days 3650 -pkeyopt rsa_keygen_bits:4096 -keyout key.pem -out cert.pem
openssl x509 -pubkey -noout -in cert.pem > pubkey.pem

Upvotes: 1

Related Questions