Skip to content

Commit 74d2bae

Browse files
committed
feat: Add PS256 Support
1 parent 46e8373 commit 74d2bae

7 files changed

Lines changed: 538 additions & 12 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,17 @@ This library is supported for Java LTS versions 8, 11, 17 and 21. For issues on
3636
| RS256 | RSA256 | RSASSA-PKCS1-v1_5 with SHA-256 |
3737
| RS384 | RSA384 | RSASSA-PKCS1-v1_5 with SHA-384 |
3838
| RS512 | RSA512 | RSASSA-PKCS1-v1_5 with SHA-512 |
39+
| PS256 | RSA256PSS | RSASSA-PSS with SHA-256 |
40+
| PS384 | RSA384PSS | RSASSA-PSS with SHA-384 |
41+
| PS512 | RSA512PSS | RSASSA-PSS with SHA-512 |
3942
| ES256 | ECDSA256 | ECDSA with curve P-256 and SHA-256 |
4043
| ES384 | ECDSA384 | ECDSA with curve P-384 and SHA-384 |
4144
| ES512 | ECDSA512 | ECDSA with curve P-521 and SHA-512 |
4245

4346
> 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)
4447
48+
> Note - The RSASSA-PSS algorithms (PS256, PS384, PS512) rely on the JVM for RSASSA-PSS support, which is available natively from Java 11 onwards. 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.
49+
4550
> :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.
4651
### Installation
4752

lib/src/main/java/com/auth0/jwt/algorithms/Algorithm.java

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import com.auth0.jwt.interfaces.RSAKeyProvider;
88

99
import java.security.interfaces.*;
10+
import java.security.spec.MGF1ParameterSpec;
11+
import java.security.spec.PSSParameterSpec;
1012

1113
/**
1214
* The Algorithm class represents an algorithm to be used in the Signing or Verification process of a Token.
@@ -127,6 +129,130 @@ public static Algorithm RSA512(RSAKey key) throws IllegalArgumentException {
127129
return RSA512(publicKey, privateKey);
128130
}
129131

132+
/**
133+
* Creates a new Algorithm instance using RSASSA-PSS with SHA-256. Tokens specify this as "PS256".
134+
* <p>
135+
* On Java 8 runtimes the built-in providers do not implement RSASSA-PSS; a provider that supports it
136+
* (such as BouncyCastle) must be registered on the classpath. Java 11 and above support it natively.
137+
*
138+
* @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance.
139+
* @return a valid RSASSA-PSS 256 Algorithm.
140+
* @throws IllegalArgumentException if the Key Provider is null.
141+
*/
142+
public static Algorithm RSA256PSS(RSAKeyProvider keyProvider) throws IllegalArgumentException {
143+
return new RSAAlgorithm("PS256", "RSASSA-PSS", pssParams("SHA-256", MGF1ParameterSpec.SHA256, 32), keyProvider);
144+
}
145+
146+
/**
147+
* Creates a new Algorithm instance using RSASSA-PSS with SHA-256. Tokens specify this as "PS256".
148+
*
149+
* @param publicKey the key to use in the verify instance.
150+
* @param privateKey the key to use in the signing instance.
151+
* @return a valid RSASSA-PSS 256 Algorithm.
152+
* @throws IllegalArgumentException if both provided Keys are null.
153+
*/
154+
public static Algorithm RSA256PSS(RSAPublicKey publicKey, RSAPrivateKey privateKey)
155+
throws IllegalArgumentException {
156+
return RSA256PSS(RSAAlgorithm.providerForKeys(publicKey, privateKey));
157+
}
158+
159+
/**
160+
* Creates a new Algorithm instance using RSASSA-PSS with SHA-256. Tokens specify this as "PS256".
161+
*
162+
* @param key the key to use in the verify or signing instance.
163+
* @return a valid RSASSA-PSS 256 Algorithm.
164+
* @throws IllegalArgumentException if the provided Key is null.
165+
*/
166+
public static Algorithm RSA256PSS(RSAKey key) throws IllegalArgumentException {
167+
RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey) key : null;
168+
RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey) key : null;
169+
return RSA256PSS(publicKey, privateKey);
170+
}
171+
172+
/**
173+
* Creates a new Algorithm instance using RSASSA-PSS with SHA-384. Tokens specify this as "PS384".
174+
* <p>
175+
* On Java 8 runtimes the built-in providers do not implement RSASSA-PSS; a provider that supports it
176+
* (such as BouncyCastle) must be registered on the classpath. Java 11 and above support it natively.
177+
*
178+
* @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance.
179+
* @return a valid RSASSA-PSS 384 Algorithm.
180+
* @throws IllegalArgumentException if the Key Provider is null.
181+
*/
182+
public static Algorithm RSA384PSS(RSAKeyProvider keyProvider) throws IllegalArgumentException {
183+
return new RSAAlgorithm("PS384", "RSASSA-PSS", pssParams("SHA-384", MGF1ParameterSpec.SHA384, 48), keyProvider);
184+
}
185+
186+
/**
187+
* Creates a new Algorithm instance using RSASSA-PSS with SHA-384. Tokens specify this as "PS384".
188+
*
189+
* @param publicKey the key to use in the verify instance.
190+
* @param privateKey the key to use in the signing instance.
191+
* @return a valid RSASSA-PSS 384 Algorithm.
192+
* @throws IllegalArgumentException if both provided Keys are null.
193+
*/
194+
public static Algorithm RSA384PSS(RSAPublicKey publicKey, RSAPrivateKey privateKey)
195+
throws IllegalArgumentException {
196+
return RSA384PSS(RSAAlgorithm.providerForKeys(publicKey, privateKey));
197+
}
198+
199+
/**
200+
* Creates a new Algorithm instance using RSASSA-PSS with SHA-384. Tokens specify this as "PS384".
201+
*
202+
* @param key the key to use in the verify or signing instance.
203+
* @return a valid RSASSA-PSS 384 Algorithm.
204+
* @throws IllegalArgumentException if the provided Key is null.
205+
*/
206+
public static Algorithm RSA384PSS(RSAKey key) throws IllegalArgumentException {
207+
RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey) key : null;
208+
RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey) key : null;
209+
return RSA384PSS(publicKey, privateKey);
210+
}
211+
212+
/**
213+
* Creates a new Algorithm instance using RSASSA-PSS with SHA-512. Tokens specify this as "PS512".
214+
* <p>
215+
* On Java 8 runtimes the built-in providers do not implement RSASSA-PSS; a provider that supports it
216+
* (such as BouncyCastle) must be registered on the classpath. Java 11 and above support it natively.
217+
*
218+
* @param keyProvider the provider of the Public Key and Private Key for the verify and signing instance.
219+
* @return a valid RSASSA-PSS 512 Algorithm.
220+
* @throws IllegalArgumentException if the Key Provider is null.
221+
*/
222+
public static Algorithm RSA512PSS(RSAKeyProvider keyProvider) throws IllegalArgumentException {
223+
return new RSAAlgorithm("PS512", "RSASSA-PSS", pssParams("SHA-512", MGF1ParameterSpec.SHA512, 64), keyProvider);
224+
}
225+
226+
/**
227+
* Creates a new Algorithm instance using RSASSA-PSS with SHA-512. Tokens specify this as "PS512".
228+
*
229+
* @param publicKey the key to use in the verify instance.
230+
* @param privateKey the key to use in the signing instance.
231+
* @return a valid RSASSA-PSS 512 Algorithm.
232+
* @throws IllegalArgumentException if both provided Keys are null.
233+
*/
234+
public static Algorithm RSA512PSS(RSAPublicKey publicKey, RSAPrivateKey privateKey)
235+
throws IllegalArgumentException {
236+
return RSA512PSS(RSAAlgorithm.providerForKeys(publicKey, privateKey));
237+
}
238+
239+
/**
240+
* Creates a new Algorithm instance using RSASSA-PSS with SHA-512. Tokens specify this as "PS512".
241+
*
242+
* @param key the key to use in the verify or signing instance.
243+
* @return a valid RSASSA-PSS 512 Algorithm.
244+
* @throws IllegalArgumentException if the provided Key is null.
245+
*/
246+
public static Algorithm RSA512PSS(RSAKey key) throws IllegalArgumentException {
247+
RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey) key : null;
248+
RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey) key : null;
249+
return RSA512PSS(publicKey, privateKey);
250+
}
251+
252+
private static PSSParameterSpec pssParams(String hashAlgorithm, MGF1ParameterSpec mgf1Spec, int saltLength) {
253+
return new PSSParameterSpec(hashAlgorithm, "MGF1", mgf1Spec, saltLength, 1);
254+
}
255+
130256
/**
131257
* Creates a new Algorithm instance using HmacSHA256. Tokens specify this as "HS256".
132258
*

lib/src/main/java/com/auth0/jwt/algorithms/CryptoHelper.java

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import javax.crypto.spec.SecretKeySpec;
55
import java.nio.charset.StandardCharsets;
66
import java.security.*;
7+
import java.security.spec.AlgorithmParameterSpec;
78

89
/**
910
* Class used to perform the signature hash calculations.
@@ -85,6 +86,31 @@ boolean verifySignatureFor(
8586
payload.getBytes(StandardCharsets.UTF_8), signatureBytes);
8687
}
8788

89+
/**
90+
* Verify signature for JWT header and payload using a public key and algorithm parameters.
91+
*
92+
* @param algorithm algorithm name.
93+
* @param publicKey the public key to use for verification.
94+
* @param params the algorithm parameters, or null to use the algorithm defaults.
95+
* @param header JWT header.
96+
* @param payload JWT payload.
97+
* @param signatureBytes JWT signature.
98+
* @return true if signature is valid.
99+
* @throws NoSuchAlgorithmException if the algorithm is not supported.
100+
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
101+
*/
102+
boolean verifySignatureFor(
103+
String algorithm,
104+
PublicKey publicKey,
105+
AlgorithmParameterSpec params,
106+
String header,
107+
String payload,
108+
byte[] signatureBytes
109+
) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
110+
return verifySignatureFor(algorithm, publicKey, params, header.getBytes(StandardCharsets.UTF_8),
111+
payload.getBytes(StandardCharsets.UTF_8), signatureBytes);
112+
}
113+
88114
/**
89115
* Verify signature for JWT header and payload using a public key.
90116
*
@@ -103,8 +129,33 @@ boolean verifySignatureFor(
103129
byte[] headerBytes,
104130
byte[] payloadBytes,
105131
byte[] signatureBytes
132+
) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
133+
return verifySignatureFor(algorithm, publicKey, null, headerBytes, payloadBytes, signatureBytes);
134+
}
135+
136+
/**
137+
* Verify signature for JWT header and payload using a public key and algorithm parameters.
138+
*
139+
* @param algorithm algorithm name.
140+
* @param publicKey the public key to use for verification.
141+
* @param params the algorithm parameters, or null to use the algorithm defaults.
142+
* @param headerBytes JWT header.
143+
* @param payloadBytes JWT payload.
144+
* @param signatureBytes JWT signature.
145+
* @return true if signature is valid.
146+
* @throws NoSuchAlgorithmException if the algorithm is not supported.
147+
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
148+
*/
149+
boolean verifySignatureFor(
150+
String algorithm,
151+
PublicKey publicKey,
152+
AlgorithmParameterSpec params,
153+
byte[] headerBytes,
154+
byte[] payloadBytes,
155+
byte[] signatureBytes
106156
) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
107157
final Signature s = Signature.getInstance(algorithm);
158+
setParameterIfPresent(s, params);
108159
s.initVerify(publicKey);
109160
s.update(headerBytes);
110161
s.update(JWT_PART_SEPARATOR);
@@ -130,8 +181,33 @@ byte[] createSignatureFor(
130181
PrivateKey privateKey,
131182
byte[] headerBytes,
132183
byte[] payloadBytes
184+
) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
185+
return createSignatureFor(algorithm, privateKey, null, headerBytes, payloadBytes);
186+
}
187+
188+
/**
189+
* Create signature for JWT header and payload using a private key and algorithm parameters.
190+
*
191+
* @param algorithm algorithm name.
192+
* @param privateKey the private key to use for signing.
193+
* @param params the algorithm parameters, or null to use the algorithm defaults.
194+
* @param headerBytes JWT header.
195+
* @param payloadBytes JWT payload.
196+
* @return the signature bytes.
197+
* @throws NoSuchAlgorithmException if the algorithm is not supported.
198+
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
199+
* @throws SignatureException if this signature object is not initialized properly
200+
* or if this signature algorithm is unable to process the input data provided.
201+
*/
202+
byte[] createSignatureFor(
203+
String algorithm,
204+
PrivateKey privateKey,
205+
AlgorithmParameterSpec params,
206+
byte[] headerBytes,
207+
byte[] payloadBytes
133208
) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
134209
final Signature s = Signature.getInstance(algorithm);
210+
setParameterIfPresent(s, params);
135211
s.initSign(privateKey);
136212
s.update(headerBytes);
137213
s.update(JWT_PART_SEPARATOR);
@@ -199,10 +275,46 @@ byte[] createSignatureFor(
199275
String algorithm,
200276
PrivateKey privateKey,
201277
byte[] contentBytes
278+
) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
279+
return createSignatureFor(algorithm, privateKey, (AlgorithmParameterSpec) null, contentBytes);
280+
}
281+
282+
/**
283+
* Create signature using a private key and algorithm parameters.
284+
* To get the correct JWT Signature, ensure the content is in the format {HEADER}.{PAYLOAD}
285+
*
286+
* @param algorithm algorithm name.
287+
* @param privateKey the private key to use for signing.
288+
* @param params the algorithm parameters, or null to use the algorithm defaults.
289+
* @param contentBytes the content to be signed.
290+
* @return the signature bytes.
291+
* @throws NoSuchAlgorithmException if the algorithm is not supported.
292+
* @throws InvalidKeyException if the given key is inappropriate for initializing the specified algorithm.
293+
* @throws SignatureException if this signature object is not initialized properly
294+
* or if this signature algorithm is unable to process the input data provided.
295+
*/
296+
byte[] createSignatureFor(
297+
String algorithm,
298+
PrivateKey privateKey,
299+
AlgorithmParameterSpec params,
300+
byte[] contentBytes
202301
) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
203302
final Signature s = Signature.getInstance(algorithm);
303+
setParameterIfPresent(s, params);
204304
s.initSign(privateKey);
205305
s.update(contentBytes);
206306
return s.sign();
207307
}
308+
309+
private static void setParameterIfPresent(Signature s, AlgorithmParameterSpec params)
310+
throws NoSuchAlgorithmException {
311+
if (params != null) {
312+
try {
313+
s.setParameter(params);
314+
} catch (InvalidAlgorithmParameterException e) {
315+
throw new NoSuchAlgorithmException(
316+
"The algorithm parameters are invalid for the signature algorithm.", e);
317+
}
318+
}
319+
}
208320
}

lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.security.SignatureException;
1111
import java.security.interfaces.RSAPrivateKey;
1212
import java.security.interfaces.RSAPublicKey;
13+
import java.security.spec.AlgorithmParameterSpec;
1314
import java.util.Base64;
1415

1516
/**
@@ -21,20 +22,33 @@ class RSAAlgorithm extends Algorithm {
2122

2223
private final RSAKeyProvider keyProvider;
2324
private final CryptoHelper crypto;
25+
private final AlgorithmParameterSpec params;
2426

2527
//Visible for testing
26-
RSAAlgorithm(CryptoHelper crypto, String id, String algorithm, RSAKeyProvider keyProvider)
27-
throws IllegalArgumentException {
28+
RSAAlgorithm(CryptoHelper crypto, String id, String algorithm, AlgorithmParameterSpec params,
29+
RSAKeyProvider keyProvider) throws IllegalArgumentException {
2830
super(id, algorithm);
2931
if (keyProvider == null) {
3032
throw new IllegalArgumentException("The Key Provider cannot be null.");
3133
}
3234
this.keyProvider = keyProvider;
3335
this.crypto = crypto;
36+
this.params = params;
37+
}
38+
39+
//Visible for testing
40+
RSAAlgorithm(CryptoHelper crypto, String id, String algorithm, RSAKeyProvider keyProvider)
41+
throws IllegalArgumentException {
42+
this(crypto, id, algorithm, null, keyProvider);
43+
}
44+
45+
RSAAlgorithm(String id, String algorithm, AlgorithmParameterSpec params, RSAKeyProvider keyProvider)
46+
throws IllegalArgumentException {
47+
this(new CryptoHelper(), id, algorithm, params, keyProvider);
3448
}
3549

3650
RSAAlgorithm(String id, String algorithm, RSAKeyProvider keyProvider) throws IllegalArgumentException {
37-
this(new CryptoHelper(), id, algorithm, keyProvider);
51+
this(new CryptoHelper(), id, algorithm, null, keyProvider);
3852
}
3953

4054
@Override
@@ -46,7 +60,7 @@ public void verify(DecodedJWT jwt) throws SignatureVerificationException {
4660
throw new IllegalStateException("The given Public Key is null.");
4761
}
4862
boolean valid = crypto.verifySignatureFor(
49-
getDescription(), publicKey, jwt.getHeader(), jwt.getPayload(), signatureBytes);
63+
getDescription(), publicKey, params, jwt.getHeader(), jwt.getPayload(), signatureBytes);
5064
if (!valid) {
5165
throw new SignatureVerificationException(this);
5266
}
@@ -63,7 +77,7 @@ public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGene
6377
if (privateKey == null) {
6478
throw new IllegalStateException("The given Private Key is null.");
6579
}
66-
return crypto.createSignatureFor(getDescription(), privateKey, headerBytes, payloadBytes);
80+
return crypto.createSignatureFor(getDescription(), privateKey, params, headerBytes, payloadBytes);
6781
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) {
6882
throw new SignatureGenerationException(this, e);
6983
}
@@ -76,7 +90,7 @@ public byte[] sign(byte[] contentBytes) throws SignatureGenerationException {
7690
if (privateKey == null) {
7791
throw new IllegalStateException("The given Private Key is null.");
7892
}
79-
return crypto.createSignatureFor(getDescription(), privateKey, contentBytes);
93+
return crypto.createSignatureFor(getDescription(), privateKey, params, contentBytes);
8094
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException | IllegalStateException e) {
8195
throw new SignatureGenerationException(this, e);
8296
}

0 commit comments

Comments
 (0)