Reputation: 688
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.
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.
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.
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.
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
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
}
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
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