Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,17 @@ This library is supported for Java LTS versions 8, 11, 17 and 21. For issues on
| RS256 | RSA256 | RSASSA-PKCS1-v1_5 with SHA-256 |
| RS384 | RSA384 | RSASSA-PKCS1-v1_5 with SHA-384 |
| RS512 | RSA512 | RSASSA-PKCS1-v1_5 with SHA-512 |
| PS256 | RSA256PSS | RSASSA-PSS with SHA-256 |
| PS384 | RSA384PSS | RSASSA-PSS with SHA-384 |
| PS512 | RSA512PSS | RSASSA-PSS with SHA-512 |
| ES256 | ECDSA256 | ECDSA with curve P-256 and SHA-256 |
| ES384 | ECDSA384 | ECDSA with curve P-384 and SHA-384 |
| ES512 | ECDSA512 | ECDSA with curve P-521 and SHA-512 |

> Note - Support for ECDSA with curve secp256k1 and SHA-256 (ES256K) has been dropped since it has been [disabled in Java 15](https://www.oracle.com/java/technologies/javase/15-relnote-issues.html#JDK-8237219)

> Note - The RSASSA-PSS algorithms (PS256, PS384, PS512) rely on the JVM for RSASSA-PSS support, which is available natively from Java 11 onwards via the `SunRsaSign` provider ([JDK-8146293](https://bugs.openjdk.org/browse/JDK-8146293)). On Java 8, a security provider that implements `RSASSA-PSS` (such as [BouncyCastle](https://www.bouncycastle.org/)) must be registered on the classpath; no additional provider is bundled by this library.

> :warning: **Important security note:** JVM has a critical vulnerability for ECDSA Algorithms - [CVE-2022-21449](https://nvd.nist.gov/vuln/detail/CVE-2022-21449). Please review the details of the vulnerability and update your environment.
### Installation

Expand Down
128 changes: 128 additions & 0 deletions lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import com.auth0.jwt.interfaces.RSAKeyProvider;

import java.security.interfaces.*;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;

/**
* The Algorithm class represents an algorithm to be used in the Signing or Verification process of a Token.
Expand Down Expand Up @@ -127,6 +129,132 @@
return RSA512(publicKey, privateKey);
}

/**
* Creates a new Algorithm instance using RSASSA-PSS with SHA-256. Tokens specify this as "PS256".
* <p>
* On Java 8 runtimes the built-in providers do not implement RSASSA-PSS; a provider that supports it
* (such as BouncyCastle) must be registered on the classpath. Java 11 and above support it natively.
*
* @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance.
* @return a valid RSASSA-PSS 256 Algorithm.
* @throws IllegalArgumentException if the Key Provider is null.
*/
public static Algorithm RSA256PSS(RSAKeyProvider keyProvider) throws IllegalArgumentException {
return new RSAAlgorithm("PS256", "RSASSA-PSS", pssParams("SHA-256", MGF1ParameterSpec.SHA256, 32), keyProvider);
}

/**
* Creates a new Algorithm instance using RSASSA-PSS with SHA-256. Tokens specify this as "PS256".
*
* @param publicKey the key to use in the verify instance.
* @param privateKey the key to use in the signing instance.
* @return a valid RSASSA-PSS 256 Algorithm.
* @throws IllegalArgumentException if both provided Keys are null.
*/
public static Algorithm RSA256PSS(RSAPublicKey publicKey, RSAPrivateKey privateKey)
throws IllegalArgumentException {
return RSA256PSS(RSAAlgorithm.providerForKeys(publicKey, privateKey));
}

/**
* Creates a new Algorithm instance using RSASSA-PSS with SHA-256. Tokens specify this as "PS256".
*
* @param key the key to use in the verify or signing instance.
* @return a valid RSASSA-PSS 256 Algorithm.
* @throws IllegalArgumentException if the provided Key is null.
*/
public static Algorithm RSA256PSS(RSAKey key) throws IllegalArgumentException {
RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey) key : null;
RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey) key : null;
return RSA256PSS(publicKey, privateKey);
}

/**
* Creates a new Algorithm instance using RSASSA-PSS with SHA-384. Tokens specify this as "PS384".
* <p>
* On Java 8 runtimes the built-in providers do not implement RSASSA-PSS; a provider that supports it
* (such as BouncyCastle) must be registered on the classpath. Java 11 and above support it natively.
*
* @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance.
* @return a valid RSASSA-PSS 384 Algorithm.
* @throws IllegalArgumentException if the Key Provider is null.
*/
public static Algorithm RSA384PSS(RSAKeyProvider keyProvider) throws IllegalArgumentException {
return new RSAAlgorithm("PS384", "RSASSA-PSS", pssParams("SHA-384", MGF1ParameterSpec.SHA384, 48), keyProvider);
}

/**
* Creates a new Algorithm instance using RSASSA-PSS with SHA-384. Tokens specify this as "PS384".
*
* @param publicKey the key to use in the verify instance.
* @param privateKey the key to use in the signing instance.
* @return a valid RSASSA-PSS 384 Algorithm.
* @throws IllegalArgumentException if both provided Keys are null.
*/
public static Algorithm RSA384PSS(RSAPublicKey publicKey, RSAPrivateKey privateKey)
throws IllegalArgumentException {
return RSA384PSS(RSAAlgorithm.providerForKeys(publicKey, privateKey));
}

/**
* Creates a new Algorithm instance using RSASSA-PSS with SHA-384. Tokens specify this as "PS384".
*
* @param key the key to use in the verify or signing instance.
* @return a valid RSASSA-PSS 384 Algorithm.
* @throws IllegalArgumentException if the provided Key is null.
*/
public static Algorithm RSA384PSS(RSAKey key) throws IllegalArgumentException {
RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey) key : null;
RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey) key : null;
return RSA384PSS(publicKey, privateKey);
}

/**
* Creates a new Algorithm instance using RSASSA-PSS with SHA-512. Tokens specify this as "PS512".
* <p>
* On Java 8 runtimes the built-in providers do not implement RSASSA-PSS; a provider that supports it
* (such as BouncyCastle) must be registered on the classpath. Java 11 and above support it natively.
*
* @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance.
* @return a valid RSASSA-PSS 512 Algorithm.
* @throws IllegalArgumentException if the Key Provider is null.
*/
public static Algorithm RSA512PSS(RSAKeyProvider keyProvider) throws IllegalArgumentException {
return new RSAAlgorithm("PS512", "RSASSA-PSS", pssParams("SHA-512", MGF1ParameterSpec.SHA512, 64), keyProvider);
}

/**
* Creates a new Algorithm instance using RSASSA-PSS with SHA-512. Tokens specify this as "PS512".
*
* @param publicKey the key to use in the verify instance.
* @param privateKey the key to use in the signing instance.
* @return a valid RSASSA-PSS 512 Algorithm.
* @throws IllegalArgumentException if both provided Keys are null.
*/
public static Algorithm RSA512PSS(RSAPublicKey publicKey, RSAPrivateKey privateKey)
throws IllegalArgumentException {
return RSA512PSS(RSAAlgorithm.providerForKeys(publicKey, privateKey));
}

/**
* Creates a new Algorithm instance using RSASSA-PSS with SHA-512. Tokens specify this as "PS512".
*
* @param key the key to use in the verify or signing instance.
* @return a valid RSASSA-PSS 512 Algorithm.
* @throws IllegalArgumentException if the provided Key is null.
*/
public static Algorithm RSA512PSS(RSAKey key) throws IllegalArgumentException {
RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey) key : null;
RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey) key : null;
return RSA512PSS(publicKey, privateKey);
}

private static PSSParameterSpec pssParams(String hashAlgorithm, MGF1ParameterSpec mgf1Spec, int saltLength) {
// JWA (RFC 7518 §3.5) mandates MGF1 with the same hash as the digest and a salt length equal to
// the hash output size (32/48/64 bytes for SHA-256/384/512). Trailer field is the fixed value 1.
return new PSSParameterSpec(hashAlgorithm, "MGF1", mgf1Spec, saltLength, 1);
}

/**
* Creates a new Algorithm instance using HmacSHA256. Tokens specify this as "HS256".
*
Expand Down Expand Up @@ -309,11 +437,11 @@
}


public static Algorithm none() {

Check warning on line 440 in lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java

View workflow job for this annotation

GitHub Actions / gradle

no comment
return new NoneAlgorithm();
}

protected Algorithm(String name, String description) {

Check warning on line 444 in lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java

View workflow job for this annotation

GitHub Actions / gradle

no comment
this.name = name;
this.description = description;
}
Expand Down
115 changes: 115 additions & 0 deletions lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.AlgorithmParameterSpec;

/**
* Class used to perform the signature hash calculations.
Expand Down Expand Up @@ -85,6 +86,31 @@ boolean verifySignatureFor(
payload.getBytes(StandardCharsets.UTF_8), signatureBytes);
}

/**
* Verify signature for JWT header and payload using a public key and algorithm parameters.
*
* @param algorithm algorithm name.
* @param publicKey the public key to use for verification.
* @param params the algorithm parameters, or null to use the algorithm defaults.
* @param header JWT header.
* @param payload JWT payload.
* @param signatureBytes JWT signature.
* @return true if signature is valid.
* @throws NoSuchAlgorithmException if the algorithm is not supported.
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
*/
boolean verifySignatureFor(
String algorithm,
PublicKey publicKey,
AlgorithmParameterSpec params,
String header,
String payload,
byte[] signatureBytes
) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
return verifySignatureFor(algorithm, publicKey, params, header.getBytes(StandardCharsets.UTF_8),
payload.getBytes(StandardCharsets.UTF_8), signatureBytes);
}

/**
* Verify signature for JWT header and payload using a public key.
*
Expand All @@ -103,8 +129,33 @@ boolean verifySignatureFor(
byte[] headerBytes,
byte[] payloadBytes,
byte[] signatureBytes
) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
return verifySignatureFor(algorithm, publicKey, null, headerBytes, payloadBytes, signatureBytes);
}

/**
* Verify signature for JWT header and payload using a public key and algorithm parameters.
*
* @param algorithm algorithm name.
* @param publicKey the public key to use for verification.
* @param params the algorithm parameters, or null to use the algorithm defaults.
* @param headerBytes JWT header.
* @param payloadBytes JWT payload.
* @param signatureBytes JWT signature.
* @return true if signature is valid.
* @throws NoSuchAlgorithmException if the algorithm is not supported.
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
*/
boolean verifySignatureFor(
String algorithm,
PublicKey publicKey,
AlgorithmParameterSpec params,
byte[] headerBytes,
byte[] payloadBytes,
byte[] signatureBytes
) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
final Signature s = Signature.getInstance(algorithm);
setParameterIfPresent(s, params);
s.initVerify(publicKey);
s.update(headerBytes);
s.update(JWT_PART_SEPARATOR);
Expand All @@ -130,8 +181,33 @@ byte[] createSignatureFor(
PrivateKey privateKey,
byte[] headerBytes,
byte[] payloadBytes
) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
return createSignatureFor(algorithm, privateKey, null, headerBytes, payloadBytes);
}

/**
* Create signature for JWT header and payload using a private key and algorithm parameters.
*
* @param algorithm algorithm name.
* @param privateKey the private key to use for signing.
* @param params the algorithm parameters, or null to use the algorithm defaults.
* @param headerBytes JWT header.
* @param payloadBytes JWT payload.
* @return the signature bytes.
* @throws NoSuchAlgorithmException if the algorithm is not supported.
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
* @throws SignatureException if this signature object is not initialized properly
* or if this signature algorithm is unable to process the input data provided.
*/
byte[] createSignatureFor(
String algorithm,
PrivateKey privateKey,
AlgorithmParameterSpec params,
byte[] headerBytes,
byte[] payloadBytes
) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
final Signature s = Signature.getInstance(algorithm);
setParameterIfPresent(s, params);
s.initSign(privateKey);
s.update(headerBytes);
s.update(JWT_PART_SEPARATOR);
Expand Down Expand Up @@ -199,10 +275,49 @@ byte[] createSignatureFor(
String algorithm,
PrivateKey privateKey,
byte[] contentBytes
) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
return createSignatureFor(algorithm, privateKey, (AlgorithmParameterSpec) null, contentBytes);
}

/**
* Create signature using a private key and algorithm parameters.
* To get the correct JWT Signature, ensure the content is in the format {HEADER}.{PAYLOAD}
*
* @param algorithm algorithm name.
* @param privateKey the private key to use for signing.
* @param params the algorithm parameters, or null to use the algorithm defaults.
* @param contentBytes the content to be signed.
* @return the signature bytes.
* @throws NoSuchAlgorithmException if the algorithm is not supported.
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
* @throws SignatureException if this signature object is not initialized properly
* or if this signature algorithm is unable to process the input data provided.
*/
byte[] createSignatureFor(
String algorithm,
PrivateKey privateKey,
AlgorithmParameterSpec params,
byte[] contentBytes
) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
final Signature s = Signature.getInstance(algorithm);
setParameterIfPresent(s, params);
s.initSign(privateKey);
s.update(contentBytes);
return s.sign();
}

private static void setParameterIfPresent(Signature s, AlgorithmParameterSpec params)
throws NoSuchAlgorithmException {
if (params != null) {
try {
s.setParameter(params);
} catch (InvalidAlgorithmParameterException e) {
// Remapped to NoSuchAlgorithmException to keep the throws clause unchanged. The params
// are library-controlled (see Algorithm#pssParams), so this branch is effectively
// unreachable; it only fires if a provider rejects the fixed PSS spec entirely.
throw new NoSuchAlgorithmException(
"The algorithm parameters are invalid for the signature algorithm.", e);
}
}
}
}
26 changes: 20 additions & 6 deletions lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.security.SignatureException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Base64;

/**
Expand All @@ -21,20 +22,33 @@ class RSAAlgorithm extends Algorithm {

private final RSAKeyProvider keyProvider;
private final CryptoHelper crypto;
private final AlgorithmParameterSpec params;

//Visible for testing
RSAAlgorithm(CryptoHelper crypto, String id, String algorithm, RSAKeyProvider keyProvider)
throws IllegalArgumentException {
RSAAlgorithm(CryptoHelper crypto, String id, String algorithm, AlgorithmParameterSpec params,
RSAKeyProvider keyProvider) throws IllegalArgumentException {
super(id, algorithm);
if (keyProvider == null) {
throw new IllegalArgumentException("The Key Provider cannot be null.");
}
this.keyProvider = keyProvider;
this.crypto = crypto;
this.params = params;
}

//Visible for testing
RSAAlgorithm(CryptoHelper crypto, String id, String algorithm, RSAKeyProvider keyProvider)
throws IllegalArgumentException {
this(crypto, id, algorithm, null, keyProvider);
}

RSAAlgorithm(String id, String algorithm, AlgorithmParameterSpec params, RSAKeyProvider keyProvider)
throws IllegalArgumentException {
this(new CryptoHelper(), id, algorithm, params, keyProvider);
}

RSAAlgorithm(String id, String algorithm, RSAKeyProvider keyProvider) throws IllegalArgumentException {
this(new CryptoHelper(), id, algorithm, keyProvider);
this(new CryptoHelper(), id, algorithm, null, keyProvider);
}

@Override
Expand All @@ -46,7 +60,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException {
throw new IllegalStateException("The given Public Key is null.");
}
boolean valid = crypto.verifySignatureFor(
getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), signatureBytes);
getDescription(), publicKey, params, jwt.getHeader(), jwt.getPayload(), signatureBytes);
if (!valid) {
throw new SignatureVerificationException(this);
}
Expand All @@ -63,7 +77,7 @@ public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGene
if (privateKey == null) {
throw new IllegalStateException("The given Private Key is null.");
}
return crypto.createSignatureFor(getDescription(), privateKey, headerBytes, payloadBytes);
return crypto.createSignatureFor(getDescription(), privateKey, params, headerBytes, payloadBytes);
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) {
throw new SignatureGenerationException(this, e);
}
Expand All @@ -76,7 +90,7 @@ public byte[] sign(byte[] contentBytes) throws SignatureGenerationException {
if (privateKey == null) {
throw new IllegalStateException("The given Private Key is null.");
}
return crypto.createSignatureFor(getDescription(), privateKey, contentBytes);
return crypto.createSignatureFor(getDescription(), privateKey, params, contentBytes);
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) {
throw new SignatureGenerationException(this, e);
}
Expand Down
Loading
Loading