Maximilian Fixl
Maximilian Fixl

Reputation: 688

How to create a key.pem with RSA-PSS parameters in openssl that meet the PS256 requirements?

Expected behavior of the correctly running application

I want to create a certificate, a key and a public key with openssl (3.2.1 30 Jan 2024). I need to sign this key and use it as a JWT token to get another access token via https request.

Environment

The application is written in TypeScript, which limits the choice of libraries for signing. I currently use jose and jsonwebtoken packages, with the aim of signing the key correctly.

Problem

It seems that the message of the verify method of the jwt-package says that the key does not have the correct RSA-PSS parameters for the PS256 algorithm.

Requirements

The following requirements are placed on the creation of a certificate:


To create the certificate, I executed the following commands with openssl to create cert.pem, key.pem and pubkey.pem.

Creating the certificate and key
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
Extracting the public key
openssl x509 -pubkey -noout -in cert.pem > pubkey.pem

Imported via the file system, I sign the private key

Here it seems to work at first glance, but generates a token that does not appear to have a valid signature according to the jwt.io debugger, as the test is not passed. This raises the question of whether client side rendering could be a problem. In any case, the results remain the same in Chrome and Firefox.

const PS256 = 'PS256'
/**
 * Other const for SignJWT declared
 * ...
 */
const getSignedJWTToken = async () => {
    const privateKeyString = readFileSync(privateKeyPath, 'utf-8')
    const privateKey = await importPKCS8(privateKeyString, PS256)
    const signedJwt = await new SignJWT()
        .setProtectedHeader({ alg: PS256 })
        .setIssuer(clientId)
        .setSubject(clientId)
        .setAudience(audienceUrl)
        .setIssuedAt(currentTime)
        .setExpirationTime(expirationTime)
        .setJti(uuid)
        .setNotBefore(notValidBeforeTime)
        .sign(privateKey)

    return signedJwt
}
Verify the signed token with jsonwebtoken verify method

Since jose gives no indication of a problem, I use the jsonwebtoken verify method to check whether the signed key can be verified with the key. The verification methods of jose provide a lot of information, but no indication of a successful signing.

const logVerifcationOfSignedKey = async (signedJwt: string) => {
    const bufferKey: Buffer = fs.readFileSync(`${certPath}/key.pem`)
    jwt.verify(signedJwt, bufferKey, { algorithms: [PS256] }, (err, decoded) => {
        if (err)
            console.error('JWT verification failed:', err)
        else
            console.log('JWT verified successfully:', decoded)
    })
}
// Output: Error: Invalid key for this operation, its RSA-PSS parameters do not meet the
// requirements of "alg" PS256.

This is where the problem lies, which can apparently only be solved by using a different procedure when creating the key.

I was able to find a hint here (accepted answer) regarding the -keyout option parameter. This should apparently create the key independently of the certificate. The alternative of using only the -out parameter was not a solution.

Does anyone have a clue or a solution that can help with this problem, or does anyone actually recognize the problem?

Upvotes: 1

Views: 950

Answers (1)

Filip Skokan
Filip Skokan

Reputation: 211

The jsonwebtoken library does not implement the correct RSASSA-PSS parameters validation. It is missing a condition here for when there are no explicit parameters.

This condition is accounted for in the jose library here, that's why in Node.js you can sign and verify using your RSASSA-PSS key with jose but not with jsonwebtoken. A bug in jsonwebtoken.

As far as jwt.io goes, it uses jose under the covers but browser runtimes use Web Cryptography API and it does not support the SPKI/PKCS#8 id-RSASSA-PSS (1.2.840.113549.1.1.10) structure. This affordance was removed in 2022 to align the spec with what's interoperable between the different Web Cryptography API implementations.

For the best interoperability you should use the JSON Web Key format for everything, alternatively if you insist on using PEM serialized keys, use the plain old rsaEncryption (1.2.840.113549.1.1.1) OID instead of id-RSASSA-PSS (1.2.840.113549.1.1.10) for RSA JWA algorithms.

Upvotes: 3

Related Questions