|
16 | 16 | package io.jsonwebtoken.impl.security;
|
17 | 17 |
|
18 | 18 | import io.jsonwebtoken.JweHeader;
|
| 19 | +import io.jsonwebtoken.UnsupportedJwtException; |
19 | 20 | import io.jsonwebtoken.impl.DefaultJweHeader;
|
20 | 21 | import io.jsonwebtoken.impl.lang.Bytes;
|
21 | 22 | import io.jsonwebtoken.impl.lang.CheckedFunction;
|
| 23 | +import io.jsonwebtoken.impl.lang.Parameter; |
22 | 24 | import io.jsonwebtoken.impl.lang.ParameterReadable;
|
23 | 25 | import io.jsonwebtoken.impl.lang.RequiredParameterReader;
|
24 | 26 | import io.jsonwebtoken.lang.Assert;
|
@@ -50,11 +52,13 @@ public class Pbes2HsAkwAlgorithm extends CryptoAlgorithm implements KeyAlgorithm
|
50 | 52 | "[JWA RFC 7518, Section 4.8.1.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.2) " +
|
51 | 53 | "recommends password-based-encryption iterations be greater than or equal to " +
|
52 | 54 | MIN_RECOMMENDED_ITERATIONS + ". Provided: ";
|
| 55 | + private static final double MAX_ITERATIONS_FACTOR = 2.5; |
53 | 56 |
|
54 | 57 | private final int HASH_BYTE_LENGTH;
|
55 | 58 | private final int DERIVED_KEY_BIT_LENGTH;
|
56 | 59 | private final byte[] SALT_PREFIX;
|
57 | 60 | private final int DEFAULT_ITERATIONS;
|
| 61 | + private final int MAX_ITERATIONS; |
58 | 62 | private final KeyAlgorithm<SecretKey, SecretKey> wrapAlg;
|
59 | 63 |
|
60 | 64 | private static byte[] toRfcSaltPrefix(byte[] bytes) {
|
@@ -106,6 +110,7 @@ protected Pbes2HsAkwAlgorithm(int hashBitLength, KeyAlgorithm<SecretKey, SecretK
|
106 | 110 | } else {
|
107 | 111 | DEFAULT_ITERATIONS = DEFAULT_SHA256_ITERATIONS;
|
108 | 112 | }
|
| 113 | + MAX_ITERATIONS = (int) (DEFAULT_ITERATIONS * MAX_ITERATIONS_FACTOR); // upper bound to help mitigate DoS attacks |
109 | 114 |
|
110 | 115 | // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8, 2nd paragraph, last sentence:
|
111 | 116 | // "Their derived-key lengths respectively are 16, 24, and 32 octets." :
|
@@ -184,7 +189,16 @@ public SecretKey getDecryptionKey(DecryptionKeyRequest<Password> request) throws
|
184 | 189 | final Password key = Assert.notNull(request.getKey(), "Decryption Password cannot be null.");
|
185 | 190 | ParameterReadable reader = new RequiredParameterReader(header);
|
186 | 191 | final byte[] inputSalt = reader.get(DefaultJweHeader.P2S);
|
187 |
| - final int iterations = reader.get(DefaultJweHeader.P2C); |
| 192 | + |
| 193 | + Parameter<Integer> param = DefaultJweHeader.P2C; |
| 194 | + final int iterations = reader.get(param); |
| 195 | + if (iterations > MAX_ITERATIONS) { |
| 196 | + String msg = "JWE Header " + param + " value " + iterations + " exceeds " + getId() + " maximum " + |
| 197 | + "allowed value " + MAX_ITERATIONS + ". The larger value is rejected to help mitigate " + |
| 198 | + "potential Denial of Service attacks."; |
| 199 | + throw new UnsupportedJwtException(msg); |
| 200 | + } |
| 201 | + |
188 | 202 | final byte[] rfcSalt = Bytes.concat(SALT_PREFIX, inputSalt);
|
189 | 203 | final char[] password = key.toCharArray(); // password will be safely cleaned/zeroed in deriveKey next:
|
190 | 204 | final SecretKey derivedKek = deriveKey(request, password, rfcSalt, iterations);
|
|
0 commit comments