From 1afaa1e174b31cd4461fd56750c499c139ca39f0 Mon Sep 17 00:00:00 2001
From: Brian Campbell <brian.d.campbell@gmail.com>
Date: Tue, 5 Dec 2023 14:18:26 -0700
Subject: [PATCH] Add the PBES2 algorithms to JWE's default blocked
 AlgorithmConstraints and put a max on the iteration count to fix Issue #212

---
 .../org/jose4j/jwe/JsonWebEncryption.java     | 10 ++-
 .../Pbes2HmacShaWithAesKeyWrapAlgorithm.java  | 17 +++++
 .../org/jose4j/cookbook/JoseCookbookTest.java |  2 +
 .../jose4j/jwe/JweCryptoPrimitiveTest.java    |  5 ++
 ...ncryptedRSAPrivateKeyJwkAppendixCTest.java |  3 +
 ...es2HmacShaWithAesKeyWrapAlgorithmTest.java | 76 ++++++++++++++++++-
 .../jose4j/jwt/consumer/JwtConsumerTest.java  |  2 +
 7 files changed, 110 insertions(+), 5 deletions(-)

Index: jose4j-0.5.1/src/main/java/org/jose4j/jwe/JsonWebEncryption.java
===================================================================
--- jose4j-0.5.1.orig/src/main/java/org/jose4j/jwe/JsonWebEncryption.java
+++ jose4j-0.5.1/src/main/java/org/jose4j/jwe/JsonWebEncryption.java
@@ -38,6 +38,13 @@ import java.security.Key;
 public class JsonWebEncryption extends JsonWebStructure
 {
 	public static final short COMPACT_SERIALIZATION_PARTS = 5;
+
+    private static final AlgorithmConstraints DEFAULT_BLOCKED =
+            new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.BLACKLIST,
+                    KeyManagementAlgorithmIdentifiers.RSA1_5,
+                    KeyManagementAlgorithmIdentifiers.PBES2_HS256_A128KW,
+                    KeyManagementAlgorithmIdentifiers.PBES2_HS384_A192KW,
+                    KeyManagementAlgorithmIdentifiers.PBES2_HS512_A256KW);
 	
     private Base64Url base64url = new Base64Url();
     
@@ -52,6 +59,11 @@ public class JsonWebEncryption extends J
 
     private AlgorithmConstraints contentEncryptionAlgorithmConstraints = AlgorithmConstraints.NO_CONSTRAINTS;
 
+    public JsonWebEncryption()
+    {
+        setAlgorithmConstraints(DEFAULT_BLOCKED);
+    }
+
     public void setPlainTextCharEncoding(String plaintextCharEncoding)
     {
         this.plaintextCharEncoding = plaintextCharEncoding;
Index: jose4j-0.5.1/src/main/java/org/jose4j/jwe/Pbes2HmacShaWithAesKeyWrapAlgorithm.java
===================================================================
--- jose4j-0.5.1.orig/src/main/java/org/jose4j/jwe/Pbes2HmacShaWithAesKeyWrapAlgorithm.java
+++ jose4j-0.5.1/src/main/java/org/jose4j/jwe/Pbes2HmacShaWithAesKeyWrapAlgorithm.java
@@ -51,6 +51,8 @@ public class Pbes2HmacShaWithAesKeyWrapA
     private long defaultIterationCount = 8192L * 8;
     private int defaultSaltByteLength = 12;
 
+    private long maxIterationCount = 2499999L;
+
     public Pbes2HmacShaWithAesKeyWrapAlgorithm(String alg, String hmacAlg, AesKeyWrapManagementAlgorithm keyWrapAlg)
     {
         setAlgorithmIdentifier(alg);
@@ -111,6 +113,11 @@ public class Pbes2HmacShaWithAesKeyWrapA
     public Key manageForDecrypt(Key managementKey, byte[] encryptedKey, ContentEncryptionKeyDescriptor cekDesc, Headers headers, ProviderContext providerContext) throws JoseException
     {
         Long iterationCount = headers.getLongHeaderValue(HeaderParameterNames.PBES2_ITERATION_COUNT);
+        if (iterationCount > maxIterationCount)
+        {
+            throw new JoseException("PBES2 iteration count ("+HeaderParameterNames.PBES2_ITERATION_COUNT+"="+
+                    iterationCount+") cannot be more than "+ maxIterationCount+" to avoid excessive resource utilization.");
+        }
         String saltInputString = headers.getStringHeaderValue(HeaderParameterNames.PBES2_SALT_INPUT);
         Base64Url base64Url = new Base64Url();
         byte[] saltInput = base64Url.base64UrlDecode(saltInputString);
@@ -170,6 +177,16 @@ public class Pbes2HmacShaWithAesKeyWrapA
         this.defaultSaltByteLength = defaultSaltByteLength;
     }
 
+    public long getMaxIterationCount()
+    {
+        return maxIterationCount;
+    }
+
+    public void setMaxIterationCount(long maxIterationCount)
+    {
+        this.maxIterationCount = maxIterationCount;
+    }
+
     public static class HmacSha256Aes128 extends Pbes2HmacShaWithAesKeyWrapAlgorithm
     {
         public HmacSha256Aes128()
Index: jose4j-0.5.1/src/test/java/org/jose4j/cookbook/JoseCookbookTest.java
===================================================================
--- jose4j-0.5.1.orig/src/test/java/org/jose4j/cookbook/JoseCookbookTest.java
+++ jose4j-0.5.1/src/test/java/org/jose4j/cookbook/JoseCookbookTest.java
@@ -897,6 +897,7 @@ public class JoseCookbookTest
         jwe.setKey(new PbkdfKey(password));
 
         jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.PBES2_HS512_A256KW);
+        jwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, KeyManagementAlgorithmIdentifiers.PBES2_HS512_A256KW));
         Headers headers = jwe.getHeaders();
         headers.setStringHeaderValue(HeaderParameterNames.PBES2_SALT_INPUT, "8Q1SzinasR3xchYz6ZZcHA");
         headers.setObjectHeaderValue(HeaderParameterNames.PBES2_ITERATION_COUNT, 8192L);
@@ -957,6 +958,7 @@ public class JoseCookbookTest
         jwe.setKey(new PbkdfKey(password));
 
         jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.PBES2_HS256_A128KW);
+        jwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, KeyManagementAlgorithmIdentifiers.PBES2_HS256_A128KW));
         Headers headers = jwe.getHeaders();
         headers.setStringHeaderValue(HeaderParameterNames.PBES2_SALT_INPUT, "8Q1SzinasR3xchYz6ZZcHA");
         headers.setObjectHeaderValue(HeaderParameterNames.PBES2_ITERATION_COUNT, 8192L);
Index: jose4j-0.5.1/src/test/java/org/jose4j/jwe/Pbes2ExampleEncryptedRSAPrivateKeyJwkAppendixCTest.java
===================================================================
--- jose4j-0.5.1.orig/src/test/java/org/jose4j/jwe/Pbes2ExampleEncryptedRSAPrivateKeyJwkAppendixCTest.java
+++ jose4j-0.5.1/src/test/java/org/jose4j/jwe/Pbes2ExampleEncryptedRSAPrivateKeyJwkAppendixCTest.java
@@ -17,6 +17,7 @@ package org.jose4j.jwe;
 
 import org.jose4j.base64url.Base64Url;
 import org.jose4j.jca.ProviderContextTest;
+import org.jose4j.jwa.AlgorithmConstraints;
 import org.jose4j.jwk.PublicJsonWebKey;
 import org.jose4j.jwk.RsaJsonWebKey;
 import org.jose4j.jwk.Use;
@@ -33,6 +34,7 @@ import java.security.Key;
 
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
+import static org.jose4j.jwa.AlgorithmConstraints.ConstraintType.PERMIT;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertThat;
 
@@ -90,6 +92,7 @@ public class Pbes2ExampleEncryptedRSAPri
     {
         PbkdfKey key = new PbkdfKey(PASSWORD);
         JsonWebEncryption jwe = new JsonWebEncryption();
+        jwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, KeyManagementAlgorithmIdentifiers.PBES2_HS256_A128KW));
         jwe.setCompactSerialization(CS);
         jwe.setKey(key);
         String payload = jwe.getPayload();
Index: jose4j-0.5.1/src/test/java/org/jose4j/jwe/Pbes2HmacShaWithAesKeyWrapAlgorithmTest.java
===================================================================
--- jose4j-0.5.1.orig/src/test/java/org/jose4j/jwe/Pbes2HmacShaWithAesKeyWrapAlgorithmTest.java
+++ jose4j-0.5.1/src/test/java/org/jose4j/jwe/Pbes2HmacShaWithAesKeyWrapAlgorithmTest.java
@@ -18,20 +18,34 @@ package org.jose4j.jwe;
 
 import org.jose4j.base64url.Base64Url;
 import org.jose4j.jca.ProviderContextTest;
+import org.jose4j.jwa.AlgorithmConstraints;
+import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.MalformedClaimException;
+import org.jose4j.jwt.NumericDate;
+import org.jose4j.jwt.consumer.InvalidJwtException;
+import org.jose4j.jwt.consumer.JwtConsumer;
+import org.jose4j.jwt.consumer.JwtConsumerBuilder;
+import org.jose4j.jwt.consumer.JwtContext;
+import org.jose4j.jwt.consumer.SimpleJwtConsumerTestHelp;
 import org.jose4j.jwx.HeaderParameterNames;
 import org.jose4j.jwx.Headers;
+import org.jose4j.keys.AesKey;
+import org.jose4j.keys.ExampleRsaJwksFromJwe;
 import org.jose4j.keys.PbkdfKey;
 import org.jose4j.lang.ByteUtil;
 import org.jose4j.lang.InvalidKeyException;
 import org.jose4j.lang.JoseException;
+import org.junit.Assert;
 import org.junit.Test;
 
 import java.security.Key;
 
 import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.jose4j.jwa.AlgorithmConstraints.ConstraintType.PERMIT;
 import static org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers.*;
-import static org.jose4j.jwe.KeyManagementAlgorithmIdentifiers.PBES2_HS256_A128KW;
-import static org.jose4j.jwe.KeyManagementAlgorithmIdentifiers.PBES2_HS384_A192KW;
+import static org.jose4j.jwe.KeyManagementAlgorithmIdentifiers.*;
+import static org.jose4j.jwe.KeyManagementAlgorithmIdentifiers.RSA1_5;
 import static org.junit.Assert.*;
 
 /**
@@ -60,6 +74,7 @@ public class Pbes2HmacShaWithAesKeyWrapA
             for (String enc : encs)
             {
                 JsonWebEncryption encryptingJwe  = new JsonWebEncryption();
+                encryptingJwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, algs));
                 encryptingJwe.setAlgorithmHeaderValue(alg);
                 encryptingJwe.setEncryptionMethodHeaderParameter(enc);
                 encryptingJwe.setPayload(plaintext);
@@ -67,6 +82,7 @@ public class Pbes2HmacShaWithAesKeyWrapA
                 String compactSerialization = encryptingJwe.getCompactSerialization();
 
                 JsonWebEncryption decryptingJwe = new JsonWebEncryption();
+                decryptingJwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, algs));
                 decryptingJwe.setCompactSerialization(compactSerialization);
                 decryptingJwe.setKey(new PbkdfKey(password));
                 assertThat(plaintext, equalTo(decryptingJwe.getPayload()));
@@ -79,6 +95,7 @@ public class Pbes2HmacShaWithAesKeyWrapA
     {
         JsonWebEncryption encryptingJwe  = new JsonWebEncryption();
         encryptingJwe.setAlgorithmHeaderValue(PBES2_HS256_A128KW);
+        encryptingJwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, KeyManagementAlgorithmIdentifiers.PBES2_HS256_A128KW));
         encryptingJwe.setEncryptionMethodHeaderParameter(AES_128_CBC_HMAC_SHA_256);
         encryptingJwe.setPayload("meh");
 
@@ -90,6 +107,7 @@ public class Pbes2HmacShaWithAesKeyWrapA
     {
         JsonWebEncryption encryptingJwe  = new JsonWebEncryption();
         encryptingJwe.setAlgorithmHeaderValue(PBES2_HS256_A128KW);
+        encryptingJwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, KeyManagementAlgorithmIdentifiers.PBES2_HS256_A128KW));
         encryptingJwe.setEncryptionMethodHeaderParameter(AES_128_CBC_HMAC_SHA_256);
         encryptingJwe.setPayload("meh");
         PbkdfKey key = new PbkdfKey("passtheword");
@@ -98,6 +116,7 @@ public class Pbes2HmacShaWithAesKeyWrapA
         System.out.println(compactSerialization);
 
         JsonWebEncryption decryptingJwe = new JsonWebEncryption();
+        decryptingJwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, KeyManagementAlgorithmIdentifiers.PBES2_HS256_A128KW));
         decryptingJwe.setCompactSerialization(compactSerialization);
         decryptingJwe.setKey(key);
         decryptingJwe.getPayload();
@@ -164,12 +183,14 @@ public class Pbes2HmacShaWithAesKeyWrapA
         encryptingJwe.getHeaders().setObjectHeaderValue(HeaderParameterNames.PBES2_ITERATION_COUNT, iterationCount);
 
         encryptingJwe.setAlgorithmHeaderValue(PBES2_HS384_A192KW);
+        encryptingJwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, KeyManagementAlgorithmIdentifiers.PBES2_HS384_A192KW));
         encryptingJwe.setEncryptionMethodHeaderParameter(AES_192_CBC_HMAC_SHA_384);
         encryptingJwe.setPayload(plaintext);
         encryptingJwe.setKey(new PbkdfKey(password));
         String compactSerialization = encryptingJwe.getCompactSerialization();
 
         JsonWebEncryption decryptingJwe = new JsonWebEncryption();
+        decryptingJwe.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, KeyManagementAlgorithmIdentifiers.PBES2_HS384_A192KW));
         decryptingJwe.setCompactSerialization(compactSerialization);
         decryptingJwe.setKey(new PbkdfKey(password));
         assertThat(plaintext, equalTo(decryptingJwe.getPayload()));
@@ -205,4 +226,55 @@ public class Pbes2HmacShaWithAesKeyWrapA
         encryptingJwe.setKey(new PbkdfKey("super secret word"));
         encryptingJwe.getCompactSerialization();
     }
+
+    @Test
+    public void p2cTooBig() throws InvalidJwtException, MalformedClaimException
+    {
+        // check that default protections are in place for the "Billion hashes attack" from
+        // https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens-whitepaper.pdf
+
+        // PBES2-HS256+A128KW
+        String jwe1 = "eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJwMnMiOiI4UTFTemluYXNSM3h" +
+                "jaFl6NlpaY0hBIiwicDJjIjoyMTQ3NDgzNjQ3LCJlbmMiOiJBMTI4Q0JDLUhTMj" +
+                "U2In0.YKbKLsEoyw_JoNvhtuHo9aaeRNSEhhAW2OVHcuF_HLqS0n6hA_fgCA.VB" +
+                "iCzVHNoLiR3F4V82uoTQ.23i-Tb1AV4n0WKVSSgcQrdg6GRqsUKxjruHXYsTHAJ" +
+                "LZ2nsnGIX86vMXqIi6IRsfywCRFzLxEcZBRnTvG3nhzPk0GDD7FMyXhUHpDjEYC" +
+                "NA_XOmzg8yZR9oyjo6lTF6si4q9FZ2EhzgFQCLO_6h5EVg3vR75_hkBsnuoqoM3" +
+                "dwejXBtIodN84PeqMb6asmas_dpSsz7H10fC5ni9xIz424givB1YLldF6exVmL9" +
+                "3R3fOoOJbmk2GBQZL_SEGllv2cQsBgeprARsaQ7Bq99tT80coH8ItBjgV08AtzX" +
+                "FFsx9qKvC982KLKdPQMTlVJKkqtV4Ru5LEVpBZXBnZrtViSOgyg6AiuwaS-rCrc" +
+                "D_ePOGSuxvgtrokAKYPqmXUeRdjFJwafkYEkiuDCV9vWGAi1DH2xTafhJwcmywI" +
+                "yzi4BqRpmdn_N-zl5tuJYyuvKhjKv6ihbsV_k1hJGPGAxJ6wUpmwC4PTQ2izEm0" +
+                "TuSE8oMKdTw8V3kobXZ77ulMwDs4p.ALTKwxvAefeL-32NY7eTAQ";
+
+        // PBES2-HS512+A256KW
+        String jwe2 = "eyJwMmMiOjI1MDAwMDAsImFsZyI6IlBCRVMyLUhTNTEyK0EyNTZLVyIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLC" +
+                "JwMnMiOiJGbVE0aDY1aUFlZEs0SUFyIn0._P2Mbn0nvqRZVCaEaLnKQkMFwGNmEVbm8Ffnb5uIas0iAt5wcWC3T7rdTw" +
+                "yliWW11YnhpaiXH0WRalAsIUyaVHC4Ku1j9bVP.Tg2KOblqWEF9iC71O-WgBw.OCjt9WYrTFMIst7XBZ8HeA.YRqs3_n" +
+                "MchYr39AJYquQs8-PrZa2NGuqshOvtLfWSvE";
+
+        // PBES2-HS384+A192KW
+        String jwe3 = "eyJwMmMiOjI1MDAwMDAsImFsZyI6IlBCRVMyLUhTMzg0K0ExOTJLVyIsImVuYyI6IkExOTJDQkMtSFMzODQiLC" +
+                "JwMnMiOiJneXBKYzJFVXNtbmNqTUtqIn0.RYXJhCW2m4Pa5XPUPVVQVJRg8z-jj-zyXoa-Q1JzdfjO2tvrELM7Ko3qhk" +
+                "v2WcUAw3ZagzIeNjY.FhNCr7zjUt0fA6KotCbdUw.DMYLybjcrOX9cfwdWaORLg.QCGY9clkv4sz1rZeexg2dUx4ViH-BeL7\n";
+
+        for (String jwt : new String[] {jwe1, jwe2, jwe3})
+        {
+            // PBE2 algs blocked by default
+            JwtConsumer c = new JwtConsumerBuilder()
+                    .setDecryptionKey(ExampleRsaJwksFromJwe.APPENDIX_A_2.getPrivateKey())
+                    .build();
+
+            SimpleJwtConsumerTestHelp.expectProcessingFailure(jwt, c);
+
+            // but also there's a max on the # of iterations
+            c = new JwtConsumerBuilder()
+                    .setDecryptionKey(new PbkdfKey("super secret word"))
+                    .setJweAlgorithmConstraints(AlgorithmConstraints.NO_CONSTRAINTS)
+                    .build();
+
+            SimpleJwtConsumerTestHelp.expectProcessingFailure(jwt, c);
+        }
+    }
+
 }
Index: jose4j-0.5.1/src/test/java/org/jose4j/jwt/consumer/JwtConsumerTest.java
===================================================================
--- jose4j-0.5.1.orig/src/test/java/org/jose4j/jwt/consumer/JwtConsumerTest.java
+++ jose4j-0.5.1/src/test/java/org/jose4j/jwt/consumer/JwtConsumerTest.java
@@ -983,6 +983,7 @@ public class JwtConsumerTest
 
         JwtConsumer firstPassConsumer = new JwtConsumerBuilder()
                 .setDecryptionKeyResolver(decryptionKeyResolver)
+                .setJweAlgorithmConstraints(AlgorithmConstraints.NO_CONSTRAINTS)
                 .setSkipAllValidators()
                 .setDisableRequireSignature()
                 .setSkipSignatureVerification()
@@ -991,6 +992,7 @@ public class JwtConsumerTest
 
         JwtConsumer consumer = new JwtConsumerBuilder()
                 .setDecryptionKeyResolver(decryptionKeyResolver)
+                .setJweAlgorithmConstraints(AlgorithmConstraints.NO_CONSTRAINTS)
                 .setVerificationKey(sigKey.getPublicKey())
                 .setEvaluationTime(NumericDate.fromSeconds(1420229816))
                 .setExpectedAudience("canada")
