CVE-2017-12972: Insufficient Verification of Data Authenticity (CWE-345)
value: In Nimbus JOSE+JWT before 4.39, there is no integer-overflow check when converting length values from bytes to bits, which allows attackers to conduct HMAC bypass attacks by shifting Additional Authenticated Data (AAD) and ciphertext so that different plaintext is obtained for the same HMAC.
CWE description: The product does not sufficiently verify the origin or authenticity of data, in a way that causes it to accept invalid data.
CVSS v3 base score: 7.5
Published: 2017-08-20T16:29Z
Repository: https://bitbucket.org/connect2id/nimbus-jose-jwt
Language: Java
Files changed: src/main/java/com/nimbusds/jose/crypto/AAD.java, src/main/java/com/nimbusds/jose/crypto/AESEncrypter.java, src/main/java/com/nimbusds/jose/crypto/AESGCM.java, src/main/java/com/nimbusds/jose/crypto/AESGCMKW.java, src/main/java/com/nimbusds/jose/crypto/ConcatKDF.java, src/main/java/com/nimbusds/jose/crypto/ContentCryptoProvider.java, src/main/java/com/nimbusds/jose/crypto/DirectEncrypter.java, src/main/java/com/nimbusds/jose/crypto/RSA1_5.java, src/main/java/com/nimbusds/jose/jwk/OctetSequenceKey.java, src/main/java/com/nimbusds/jose/jwk/RSAKey.java, src/main/java/com/nimbusds/jose/util/ByteUtils.java, src/main/java/com/nimbusds/jose/util/IntegerOverflowException.java, src/test/java/com/nimbusds/jose/crypto/AADTest.java, src/test/java/com/nimbusds/jose/crypto/AESCBCTest.java, src/test/java/com/nimbusds/jose/util/ByteUtilsTest.java
Commit date: 2017-06-01 23:21:05 +0300
Commit message: Switches to safe bit computation in crypto (iss #224)

Diff: diff --git a/src/main/java/com/nimbusds/jose/crypto/AAD.java b/src/main/java/com/nimbusds/jose/crypto/AAD.java
index 8cb3018..bfcf2ad 100644
--- a/src/main/java/com/nimbusds/jose/crypto/AAD.java
+++ b/src/main/java/com/nimbusds/jose/crypto/AAD.java
@@ -22,19 +22,20 @@ import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 
 import com.nimbusds.jose.JWEHeader;
 import com.nimbusds.jose.util.Base64URL;
 import com.nimbusds.jose.util.ByteUtils;
+import com.nimbusds.jose.util.IntegerOverflowException;
 
 
 /**
  * Additional authenticated data (AAD).
  *
  * <p>See RFC 7518 (JWA), section 5.1, point 14.
  *
  * @author Vladimir Dzhuvinov
- * @version 2015-05-14
+ * @version 2017-06-01
  */
 class AAD {
 
 
 	/**
@@ -73,12 +74,15 @@ class AAD {
 	 * @param aad The Additional Authenticated Data (AAD). Must not be
 	 *            {@code null}.
 	 *
 	 * @return The computed AAD bit length, as a 64 bit big-endian
 	 *         representation (8 byte array).
+	 *
+	 * @throws IntegerOverflowException On a integer overflow.
 	 */
-	public static byte[] computeLength(final byte[] aad) {
+	public static byte[] computeLength(final byte[] aad)
+		throws IntegerOverflowException {
 
-		final int bitLength = ByteUtils.bitLength(aad);
+		final int bitLength = ByteUtils.safeBitLength(aad);
 		return ByteBuffer.allocate(8).putLong(bitLength).array();
 	}
 }
diff --git a/src/main/java/com/nimbusds/jose/crypto/AESEncrypter.java b/src/main/java/com/nimbusds/jose/crypto/AESEncrypter.java
index 8c286fb..16fcf7d 100644
--- a/src/main/java/com/nimbusds/jose/crypto/AESEncrypter.java
+++ b/src/main/java/com/nimbusds/jose/crypto/AESEncrypter.java
@@ -19,17 +19,16 @@ package com.nimbusds.jose.crypto;
 
 
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
 
-import net.jcip.annotations.ThreadSafe;
-
 import com.nimbusds.jose.*;
 import com.nimbusds.jose.jwk.OctetSequenceKey;
 import com.nimbusds.jose.util.Base64URL;
 import com.nimbusds.jose.util.ByteUtils;
 import com.nimbusds.jose.util.Container;
+import net.jcip.annotations.ThreadSafe;
 
 
 /**
  * AES and AES GCM key wrap encrypter of {@link com.nimbusds.jose.JWEObject JWE
  * objects}. Expects an AES key.
@@ -69,11 +68,11 @@ import com.nimbusds.jose.util.Container;
  * </ul>
  *
  * @author Melisa Halsband
  * @author Vladimir Dzhuvinov
  * @author Dimitar A. Stoikov
- * @version 2016-10-13
+ * @version 2017-06-01
  */
 @ThreadSafe
 public class AESEncrypter extends AESCryptoProvider implements JWEEncrypter {
 
 
@@ -143,46 +142,46 @@ public class AESEncrypter extends AESCryptoProvider implements JWEEncrypter {
 		// Check the AES key size and determine the algorithm family
 		final AlgFamily algFamily;
 
 		if (alg.equals(JWEAlgorithm.A128KW)) {
 
-			if(ByteUtils.bitLength(getKey().getEncoded()) != 128){
+			if(ByteUtils.safeBitLength(getKey().getEncoded()) != 128){
 				throw new KeyLengthException("The Key Encryption Key (KEK) length must be 128 bits for A128KW encryption");
 			}
 			algFamily = AlgFamily.AESKW;
 
 		} else if (alg.equals(JWEAlgorithm.A192KW)) {
 
-			if(ByteUtils.bitLength(getKey().getEncoded()) != 192){
+			if(ByteUtils.safeBitLength(getKey().getEncoded()) != 192){
 				throw new KeyLengthException("The Key Encryption Key (KEK) length must be 192 bits for A192KW encryption");
 			}
 			algFamily = AlgFamily.AESKW;
 
 		} else if (alg.equals(JWEAlgorithm.A256KW)) {
 
-			if (ByteUtils.bitLength(getKey().getEncoded()) != 256) {
+			if (ByteUtils.safeBitLength(getKey().getEncoded()) != 256) {
 				throw new KeyLengthException("The Key Encryption Key (KEK) length must be 256 bits for A256KW encryption");
 			}
 			algFamily = AlgFamily.AESKW;
 
 		} else if (alg.equals(JWEAlgorithm.A128GCMKW)) {
 
-			if(ByteUtils.bitLength(getKey().getEncoded()) != 128){
+			if(ByteUtils.safeBitLength(getKey().getEncoded()) != 128){
 				throw new KeyLengthException("The Key Encryption Key (KEK) length must be 128 bits for A128GCMKW encryption");
 			}
 			algFamily = AlgFamily.AESGCMKW;
 
 		} else if (alg.equals(JWEAlgorithm.A192GCMKW)) {
 
-			if(ByteUtils.bitLength(getKey().getEncoded()) != 192){
+			if(ByteUtils.safeBitLength(getKey().getEncoded()) != 192){
 				throw new KeyLengthException("The Key Encryption Key (KEK) length must be 192 bits for A192GCMKW encryption");
 			}
 			algFamily = AlgFamily.AESGCMKW;
 
 		} else if (alg.equals(JWEAlgorithm.A256GCMKW)) {
 
-			if(ByteUtils.bitLength(getKey().getEncoded()) != 256){
+			if(ByteUtils.safeBitLength(getKey().getEncoded()) != 256){
 				throw new KeyLengthException("The Key Encryption Key (KEK) length must be 256 bits for A256GCMKW encryption");
 			}
 			algFamily = AlgFamily.AESGCMKW;
 
 		} else {
diff --git a/src/main/java/com/nimbusds/jose/crypto/AESGCM.java b/src/main/java/com/nimbusds/jose/crypto/AESGCM.java
index 20d78b9..ce69b58 100644
--- a/src/main/java/com/nimbusds/jose/crypto/AESGCM.java
+++ b/src/main/java/com/nimbusds/jose/crypto/AESGCM.java
@@ -18,19 +18,17 @@
 package com.nimbusds.jose.crypto;
 
 
 import java.security.*;
 import java.security.spec.InvalidParameterSpecException;
-
 import javax.crypto.*;
 import javax.crypto.spec.GCMParameterSpec;
 
-import net.jcip.annotations.ThreadSafe;
-
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.util.ByteUtils;
 import com.nimbusds.jose.util.Container;
+import net.jcip.annotations.ThreadSafe;
 
 
 /**
  * AES/GSM/NoPadding encryption and decryption methods. Falls back to the
  * BouncyCastle.org provider on Java 6. This class is thread-safe.
@@ -38,11 +36,11 @@ import com.nimbusds.jose.util.Container;
  * <p>See RFC 7518 (JWA), section 5.1 and appendix 3.
  *
  * @author Vladimir Dzhuvinov
  * @author Axel Nennker
  * @author Dimitar A. Stoikov
- * @version 2016-10-13
+ * @version 2017-06-01
  */
 @ThreadSafe
 class AESGCM {
 
 
@@ -197,12 +195,12 @@ class AESGCM {
 	 * @see #AUTH_TAG_BIT_LENGTH
 	 */
 	private static void validate(final byte[] iv, final int authTagLength)
 		throws JOSEException {
 		
-		if (ByteUtils.bitLength(iv) != IV_BIT_LENGTH) {
-			throw new JOSEException(String.format("IV length of %d bits is required, got %d", IV_BIT_LENGTH, ByteUtils.bitLength(iv)));
+		if (ByteUtils.safeBitLength(iv) != IV_BIT_LENGTH) {
+			throw new JOSEException(String.format("IV length of %d bits is required, got %d", IV_BIT_LENGTH, ByteUtils.safeBitLength(iv)));
 		}
 
 		if (authTagLength != AUTH_TAG_BIT_LENGTH) {
 			throw new JOSEException(String.format("Authentication tag length of %d bits is required, got %d", AUTH_TAG_BIT_LENGTH, authTagLength));
 		}
diff --git a/src/main/java/com/nimbusds/jose/crypto/AESGCMKW.java b/src/main/java/com/nimbusds/jose/crypto/AESGCMKW.java
index 5dd397b..e58c167 100644
--- a/src/main/java/com/nimbusds/jose/crypto/AESGCMKW.java
+++ b/src/main/java/com/nimbusds/jose/crypto/AESGCMKW.java
@@ -36,11 +36,11 @@ import com.nimbusds.jose.util.Container;
  *
  * <p>See RFC 7518 (JWA), section 4.7.
  *
  * @author Melisa Halsband
  * @author Vladimir Dzhuvinov
- * @version 2016-10-13
+ * @version 2017-06-01
  */
 @ThreadSafe
 class AESGCMKW {
 
 
@@ -95,13 +95,13 @@ class AESGCMKW {
 					   final Provider provider)
 		throws JOSEException {
 
 		byte[] keyBytes = AESGCM.decrypt(kek, iv, authEncrCEK.getCipherText(), new byte[0], authEncrCEK.getAuthenticationTag(), provider);
 
-		if (ByteUtils.bitLength(keyBytes) != keyLength) {
+		if (ByteUtils.safeBitLength(keyBytes) != keyLength) {
 
-			throw new KeyLengthException("CEK key length mismatch: " + ByteUtils.bitLength(keyBytes) + " != " + keyLength);
+			throw new KeyLengthException("CEK key length mismatch: " + ByteUtils.safeBitLength(keyBytes) + " != " + keyLength);
 		}
 
 		return new SecretKeySpec(keyBytes, "AES");
 	}
 
diff --git a/src/main/java/com/nimbusds/jose/crypto/ConcatKDF.java b/src/main/java/com/nimbusds/jose/crypto/ConcatKDF.java
index a5ef5e0..c08766c 100644
--- a/src/main/java/com/nimbusds/jose/crypto/ConcatKDF.java
+++ b/src/main/java/com/nimbusds/jose/crypto/ConcatKDF.java
@@ -40,11 +40,11 @@ import net.jcip.annotations.ThreadSafe;
  * Concatenation Key Derivation Function (KDF). This class is thread-safe.
  *
  * <p>See NIST.800-56A.
  *
  * @author Vladimir Dzhuvinov
- * @version 2017-05-05
+ * @version 2017-06-01
  */
 @ThreadSafe
 class ConcatKDF implements JCAAware<JCAContext> {
 
 
@@ -113,11 +113,11 @@ class ConcatKDF implements JCAAware<JCAContext> {
 
 		ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
 		final MessageDigest md = getMessageDigest();
 
-		for (int i=1; i <= computeDigestCycles(ByteUtils.bitLength(md.getDigestLength()), keyLengthBits); i++) {
+		for (int i=1; i <= computeDigestCycles(ByteUtils.safeBitLength(md.getDigestLength()), keyLengthBits); i++) {
 
 			byte[] counterBytes = IntegerUtils.toBytes(i);
 
 			md.update(counterBytes);
 			md.update(sharedSecret.getEncoded());
diff --git a/src/main/java/com/nimbusds/jose/crypto/ContentCryptoProvider.java b/src/main/java/com/nimbusds/jose/crypto/ContentCryptoProvider.java
index a29b702..630a6b5 100644
--- a/src/main/java/com/nimbusds/jose/crypto/ContentCryptoProvider.java
+++ b/src/main/java/com/nimbusds/jose/crypto/ContentCryptoProvider.java
@@ -26,17 +26,18 @@ import javax.crypto.spec.SecretKeySpec;
 import com.nimbusds.jose.*;
 import com.nimbusds.jose.jca.JWEJCAContext;
 import com.nimbusds.jose.util.Base64URL;
 import com.nimbusds.jose.util.ByteUtils;
 import com.nimbusds.jose.util.Container;
+import com.nimbusds.jose.util.IntegerOverflowException;
 
 
 /**
  * JWE content encryption / decryption provider.
  *
  * @author Vladimir Dzhuvinov
- * @version 2016-10-13
+ * @version 2017-06-01
  */
 class ContentCryptoProvider {
 
 
 	/**
@@ -124,12 +125,16 @@ class ContentCryptoProvider {
 	 *                            encryption method.
 	 */
 	private static void checkCEKLength(final SecretKey cek, final EncryptionMethod enc)
 		throws KeyLengthException {
 
-		if (enc.cekBitLength() != ByteUtils.bitLength(cek.getEncoded())) {
-			throw new KeyLengthException("The Content Encryption Key (CEK) length for " + enc + " must be " + enc.cekBitLength() + " bits");
+		try {
+			if (enc.cekBitLength() != ByteUtils.safeBitLength(cek.getEncoded())) {
+				throw new KeyLengthException("The Content Encryption Key (CEK) length for " + enc + " must be " + enc.cekBitLength() + " bits");
+			}
+		} catch (IntegerOverflowException e) {
+			throw new KeyLengthException("The Content Encryption Key (CEK) is too long: " + e.getMessage());
 		}
 	}
 
 
 	/**
diff --git a/src/main/java/com/nimbusds/jose/crypto/DirectEncrypter.java b/src/main/java/com/nimbusds/jose/crypto/DirectEncrypter.java
index 256430e..ca8d81b 100644
--- a/src/main/java/com/nimbusds/jose/crypto/DirectEncrypter.java
+++ b/src/main/java/com/nimbusds/jose/crypto/DirectEncrypter.java
@@ -57,11 +57,11 @@ import com.nimbusds.jose.util.ByteUtils;
  *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED} (requires 256 bit key)
  *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED} (requires 512 bit key)
  * </ul>
  *
  * @author Vladimir Dzhuvinov
- * @version 2014-06-29
+ * @version 2017-06-01
  */
 @ThreadSafe
 public class DirectEncrypter extends DirectCryptoProvider implements JWEEncrypter {
 
 
@@ -130,11 +130,11 @@ public class DirectEncrypter extends DirectCryptoProvider implements JWEEncrypte
 		}
 
 		// Check key length matches encryption method
 		EncryptionMethod enc = header.getEncryptionMethod();
 
-		if (enc.cekBitLength() != ByteUtils.bitLength(getKey().getEncoded())) {
+		if (enc.cekBitLength() != ByteUtils.safeBitLength(getKey().getEncoded())) {
 			throw new KeyLengthException(enc.cekBitLength(), enc);
 		}
 
 		final Base64URL encryptedKey = null; // The second JWE part
 
diff --git a/src/main/java/com/nimbusds/jose/crypto/RSA1_5.java b/src/main/java/com/nimbusds/jose/crypto/RSA1_5.java
index 88508a7..27f7df2 100644
--- a/src/main/java/com/nimbusds/jose/crypto/RSA1_5.java
+++ b/src/main/java/com/nimbusds/jose/crypto/RSA1_5.java
@@ -34,11 +34,11 @@ import net.jcip.annotations.ThreadSafe;
 /**
  * RSAES-PKCS1-V1_5 methods for Content Encryption Key (CEK) encryption and
  * decryption. This class is thread-safe.
  *
  * @author Vladimir Dzhuvinov
- * @version 2016-12-04
+ * @version 2017-06-01
  */
 @ThreadSafe
 class RSA1_5 {
 
 
@@ -96,11 +96,11 @@ class RSA1_5 {
 		try {
 			Cipher cipher = CipherHelper.getInstance("RSA/ECB/PKCS1Padding", provider);
 			cipher.init(Cipher.DECRYPT_MODE, priv);
 			byte[] secretKeyBytes = cipher.doFinal(encryptedCEK);
 
-			if (ByteUtils.bitLength(secretKeyBytes) != keyLength) {
+			if (ByteUtils.safeBitLength(secretKeyBytes) != keyLength) {
 				// CEK key length mismatch
 				return null;
 			}
 
 			return new SecretKeySpec(secretKeyBytes, "AES");
diff --git a/src/main/java/com/nimbusds/jose/jwk/OctetSequenceKey.java b/src/main/java/com/nimbusds/jose/jwk/OctetSequenceKey.java
index 9d10204..3932ab1 100644
--- a/src/main/java/com/nimbusds/jose/jwk/OctetSequenceKey.java
+++ b/src/main/java/com/nimbusds/jose/jwk/OctetSequenceKey.java
@@ -27,14 +27,11 @@ import java.util.Set;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
 
 import com.nimbusds.jose.Algorithm;
 import com.nimbusds.jose.JOSEException;
-import com.nimbusds.jose.util.Base64;
-import com.nimbusds.jose.util.Base64URL;
-import com.nimbusds.jose.util.ByteUtils;
-import com.nimbusds.jose.util.JSONObjectUtils;
+import com.nimbusds.jose.util.*;
 import net.jcip.annotations.Immutable;
 import net.minidev.json.JSONObject;
 
 
 /**
@@ -63,11 +60,11 @@ import net.minidev.json.JSONObject;
  * 	.build();
  * </pre>
  * 
  * @author Justin Richer
  * @author Vladimir Dzhuvinov
- * @version 2017-04-08
+ * @version 2017-06-01
  */
 @Immutable
 public final class OctetSequenceKey extends JWK implements SecretJWK {
 
 
@@ -537,11 +534,15 @@ public final class OctetSequenceKey extends JWK implements SecretJWK {
 
 
 	@Override
 	public int size() {
 
-		return ByteUtils.bitLength(k.decode());
+		try {
+			return ByteUtils.safeBitLength(k.decode());
+		} catch (IntegerOverflowException e) {
+			throw new ArithmeticException(e.getMessage());
+		}
 	}
 
 
 	@Override
 	public JSONObject toJSONObject() {
diff --git a/src/main/java/com/nimbusds/jose/jwk/RSAKey.java b/src/main/java/com/nimbusds/jose/jwk/RSAKey.java
index 9755801..f1db66e 100644
--- a/src/main/java/com/nimbusds/jose/jwk/RSAKey.java
+++ b/src/main/java/com/nimbusds/jose/jwk/RSAKey.java
@@ -33,13 +33,11 @@ import java.text.ParseException;
 import java.util.*;
 
 import com.nimbusds.jose.Algorithm;
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.util.Base64;
-import com.nimbusds.jose.util.Base64URL;
-import com.nimbusds.jose.util.ByteUtils;
-import com.nimbusds.jose.util.JSONObjectUtils;
+import com.nimbusds.jose.util.*;
 import net.jcip.annotations.Immutable;
 import net.minidev.json.JSONArray;
 import net.minidev.json.JSONObject;
 
 
@@ -131,11 +129,11 @@ import net.minidev.json.JSONObject;
  * <p>See http://en.wikipedia.org/wiki/RSA_%28algorithm%29
  *
  * @author Vladimir Dzhuvinov
  * @author Justin Richer
  * @author Cedric Staub
- * @version 2017-04-08
+ * @version 2017-06-01
  */
 @Immutable
 public final class RSAKey extends JWK implements AssymetricJWK {
 
 
@@ -1931,11 +1929,15 @@ public final class RSAKey extends JWK implements AssymetricJWK {
 
 
 	@Override
 	public int size() {
 
-		return ByteUtils.bitLength(n.decode());
+		try {
+			return ByteUtils.safeBitLength(n.decode());
+		} catch (IntegerOverflowException e) {
+			throw new ArithmeticException(e.getMessage());
+		}
 	}
 
 
 	/**
 	 * Returns a copy of this RSA JWK with any private values removed.
diff --git a/src/main/java/com/nimbusds/jose/util/ByteUtils.java b/src/main/java/com/nimbusds/jose/util/ByteUtils.java
index b38d6ac..f76b8ec 100644
--- a/src/main/java/com/nimbusds/jose/util/ByteUtils.java
+++ b/src/main/java/com/nimbusds/jose/util/ByteUtils.java
@@ -24,11 +24,11 @@ import java.io.IOException;
 
 /**
  * Byte utilities.
  *
  * @author Vladimir Dzhuvinov
- * @version 2015-05-12
+ * @version 2017-06-01
  */
 public class ByteUtils {
 
 
 	/**
@@ -90,10 +90,32 @@ public class ByteUtils {
 
 		return byteLength * 8;
 	}
 
 
+	/**
+	 * Returns the bit length of the specified byte length, preventing
+	 * integer overflow.
+	 *
+	 * @param byteLength The byte length.
+	 *
+	 * @return The bit length.
+	 *
+	 * @throws IntegerOverflowException On a integer overflow.
+	 */
+	public static int safeBitLength(final int byteLength)
+		throws IntegerOverflowException {
+		
+		long longResult = (long)byteLength * (long)8;
+		if((long)((int)longResult) != longResult) {
+			throw new IntegerOverflowException();
+		} else {
+			return (int)longResult;
+		}
+	}
+
+
 	/**
 	 * Returns the byte length of the specified byte array.
 	 *
 	 * @param byteArray The byte array. May be {@code null}.
 	 *
@@ -107,10 +129,31 @@ public class ByteUtils {
 			return bitLength(byteArray.length);
 		}
 	}
 
 
+	/**
+	 * Returns the byte length of the specified byte array, preventing
+	 * integer overflow.
+	 *
+	 * @param byteArray The byte array. May be {@code null}.
+	 *
+	 * @return The bite length, zero if the array is {@code null}.
+	 *
+	 * @throws IntegerOverflowException On a integer overflow.
+	 */
+	public static int safeBitLength(final byte[] byteArray)
+		throws IntegerOverflowException {
+
+		if (byteArray == null) {
+			return 0;
+		} else {
+			return safeBitLength(byteArray.length);
+		}
+	}
+
+
 	/**
 	 * Returns the byte length of the specified bit length.
 	 *
 	 * @param bitLength The bit length.
 	 *
diff --git a/src/main/java/com/nimbusds/jose/util/IntegerOverflowException.java b/src/main/java/com/nimbusds/jose/util/IntegerOverflowException.java
new file mode 100644
index 0000000..168232b
--- /dev/null
+++ b/src/main/java/com/nimbusds/jose/util/IntegerOverflowException.java
@@ -0,0 +1,36 @@
+/*
+ * nimbus-jose-jwt
+ *
+ * Copyright 2012-2016, Connect2id Ltd and contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+ * this file except in compliance with the License. You may obtain a copy of the
+ * License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package com.nimbusds.jose.util;
+
+
+import com.nimbusds.jose.JOSEException;
+
+
+/**
+ * Integer overflow exception.
+ */
+public class IntegerOverflowException extends JOSEException {
+	
+	
+	/**
+	 * Creates a new integer overflow exception.
+	 */
+	public IntegerOverflowException() {
+		super("Integer overflow");
+	}
+}
diff --git a/src/test/java/com/nimbusds/jose/crypto/AADTest.java b/src/test/java/com/nimbusds/jose/crypto/AADTest.java
index c48e63a..44d5ab9 100644
--- a/src/test/java/com/nimbusds/jose/crypto/AADTest.java
+++ b/src/test/java/com/nimbusds/jose/crypto/AADTest.java
@@ -19,23 +19,23 @@ package com.nimbusds.jose.crypto;
 
 
 import java.nio.charset.Charset;
 import java.util.Arrays;
 
-import com.nimbusds.jose.util.Base64URL;
-import junit.framework.TestCase;
-
 import com.nimbusds.jose.EncryptionMethod;
 import com.nimbusds.jose.JWEAlgorithm;
 import com.nimbusds.jose.JWEHeader;
+import com.nimbusds.jose.util.Base64URL;
+import com.nimbusds.jose.util.IntegerOverflowException;
+import junit.framework.TestCase;
 
 
 /**
  * Tests the Additional Authenticated Data (AAD) functions.
  *
  * @author Vladimir Dzhuvinov
- * @version 2015-05-17
+ * @version 2017-06-01
  */
 public class AADTest extends TestCase {
 
 
 	public void testComputeForJWEHeader() {
@@ -56,11 +56,12 @@ public class AADTest extends TestCase {
 
 		assertTrue(Arrays.equals(expected, AAD.compute(base64URL)));
 	}
 
 
-	public void testComputeLength() {
+	public void testComputeLength()
+		throws IntegerOverflowException {
 
 		byte[] aad = new byte[]{0, 1, 2, 3}; // 32 bits
 
 		byte[] expectedBitLength = new byte[]{0, 0, 0, 0, 0, 0, 0, 32};
 
diff --git a/src/test/java/com/nimbusds/jose/crypto/AESCBCTest.java b/src/test/java/com/nimbusds/jose/crypto/AESCBCTest.java
index 004ff26..e6543b9 100644
--- a/src/test/java/com/nimbusds/jose/crypto/AESCBCTest.java
+++ b/src/test/java/com/nimbusds/jose/crypto/AESCBCTest.java
@@ -16,29 +16,30 @@
  */
 
 package com.nimbusds.jose.crypto;
 
 
+import java.util.Arrays;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
 
 import com.nimbusds.jose.EncryptionMethod;
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JWEAlgorithm;
 import com.nimbusds.jose.JWEHeader;
 import com.nimbusds.jose.util.Base64URL;
 import junit.framework.TestCase;
-
 import org.junit.Assert;
 
 
 /**
  * Tests the authenticated AES/CBC encryption and decryption methods. Uses test 
  * vectors from draft-ietf-jose-json-web-algorithms-10, appendix C.
  *
  * @author Vladimir Dzhuvinov
- * @version 2017-05-30
+ * @author Quan Nguyen
+ * @version 2017-06-01
  */
 public class AESCBCTest extends TestCase {
 
 
 	private static final byte[] INPUT_KEY_256 = 
@@ -148,11 +149,12 @@ public class AESCBCTest extends TestCase {
 		  (byte)0x21, (byte)0x68, (byte)0x39, (byte)0x64, (byte)0x5b, (byte)0x20, (byte)0x12, (byte)0xbf,
 		  (byte)0x2e, (byte)0x62, (byte)0x69, (byte)0xa8, (byte)0xc5, (byte)0x6a, (byte)0x81, (byte)0x6d, 
 		  (byte)0xbc, (byte)0x1b, (byte)0x26, (byte)0x77, (byte)0x61, (byte)0x95, (byte)0x5b, (byte)0xc5  };
 
 
-	public void testAADLengthComputation() {
+	public void testAADLengthComputation()
+		throws JOSEException {
 
 		Assert.assertArrayEquals(AAD_LENGTH, com.nimbusds.jose.crypto.AAD.computeLength(AAD));
 	}
 
 
@@ -241,6 +243,65 @@ public class AESCBCTest extends TestCase {
 			);
 		} catch (JOSEException e) {
 			assertEquals("MAC check failed", e.getMessage());
 		}
 	}
+	
+	
+	public void testIntegerOverflowHmacBypass()
+		throws Exception {
+		
+		SecretKey inputKey = new SecretKeySpec(INPUT_KEY_256, "AES");
+		
+		Assert.assertArrayEquals("Input key", INPUT_KEY_256, inputKey.getEncoded());
+		byte[] iv = new byte[16];
+		byte[] aad = new byte[8];
+		byte[] plaintext = new byte[536870928];
+		for (int i = 0; i < plaintext.length; i++ ){
+			// Doesn't matter what value is, but rand is too expensive for large array
+			plaintext[i] = (byte) i;
+		}
+		
+		AuthenticatedCipherText act;
+		
+		try {
+			act = AESCBC.encryptAuthenticated(inputKey, iv, plaintext, aad, null, null);
+		} catch (OutOfMemoryError e) {
+			System.out.println("Test not run due to " + e);
+			return;
+		}
+		
+		byte[] ciphertext = act.getCipherText();
+		byte[] authTag = act.getAuthenticationTag();
+		
+		// Now shift aad and ciphertext around so that HMAC doesn't change,
+		// but the plaintext will change.
+		int n = 0;
+		byte[] buffer = new byte[aad.length + iv.length + ciphertext.length];
+		System.arraycopy(aad, 0, buffer, n, aad.length);
+		n += aad.length;
+		System.arraycopy(iv, 0, buffer, n, iv.length);
+		n += iv.length;
+		System.arraycopy(ciphertext, 0, buffer, n, ciphertext.length);
+		// Note that due to integer overflow :536870920 * 8 = 64
+		int newAadSize = 536870920;
+		byte[] newAad = Arrays.copyOfRange(buffer, 0, newAadSize);
+		byte[] newIv = Arrays.copyOfRange(buffer, newAadSize, newAadSize + 16);
+		byte[] newCiphertext = Arrays.copyOfRange(buffer, newAadSize + 16,
+			buffer.length);
+		
+		try {
+			byte[] decrypted = AESCBC.decryptAuthenticated(inputKey, newIv,
+				newCiphertext, newAad,
+				authTag, // Note that the authTag does NOT change.
+				null, null);
+			// Reaching this point means that the HMac check is
+			// bypassed although the decrypted data is different
+			// from plaintext.
+			// Assert.assertArrayEquals(decrypted, plaintext);
+			fail();
+		} catch (JOSEException ignored) {
+			// ok
+		}
+	}
+	
 }
\ No newline at end of file
diff --git a/src/test/java/com/nimbusds/jose/util/ByteUtilsTest.java b/src/test/java/com/nimbusds/jose/util/ByteUtilsTest.java
index 3c0c133..bdb98e4 100644
--- a/src/test/java/com/nimbusds/jose/util/ByteUtilsTest.java
+++ b/src/test/java/com/nimbusds/jose/util/ByteUtilsTest.java
@@ -20,10 +20,11 @@ package com.nimbusds.jose.util;
 
 import java.nio.charset.Charset;
 import java.security.MessageDigest;
 import java.util.Arrays;
 
+import com.nimbusds.jose.JOSEException;
 import junit.framework.TestCase;
 
 
 /**
  * Tests the byte utilities.
@@ -64,6 +65,50 @@ public class ByteUtilsTest extends TestCase {
 		
 		byte[] concat = ByteUtils.concat(firstHalf, secondHalf);
 		
 		assertTrue(Base64URL.encode(hash).equals(Base64URL.encode(concat)));
 	}
+	
+	
+	public void testSafeBitLength_OK()
+		throws JOSEException {
+		
+		assertEquals(8, ByteUtils.bitLength(1));
+		assertEquals(16, ByteUtils.bitLength(2));
+		assertEquals(32, ByteUtils.bitLength(4));
+		assertEquals(64, ByteUtils.bitLength(8));
+	}
+	
+	
+	public void testSafeBitLength_IntegerOverflow() {
+		
+		try {
+			ByteUtils.safeBitLength(Integer.MAX_VALUE);
+			fail();
+		} catch (IntegerOverflowException e) {
+			assertEquals("Integer overflow", e.getMessage());
+		}
+	}
+	
+	
+	public void testArraySafeBitLength_OK()
+		throws JOSEException {
+		
+		assertEquals( 8, ByteUtils.bitLength(new byte[1]));
+		assertEquals(16, ByteUtils.bitLength(new byte[2]));
+		assertEquals(32, ByteUtils.bitLength(new byte[4]));
+		assertEquals(64, ByteUtils.bitLength(new byte[8]));
+	}
+	
+	
+	public void testArraySafeBitLength_IntegerOverflow() {
+		
+		try {
+			ByteUtils.safeBitLength(new byte[Integer.MAX_VALUE]);
+			fail();
+		} catch (OutOfMemoryError e) {
+			System.out.println("Test not run due to " + e);
+		} catch (IntegerOverflowException e) {
+			assertEquals("Integer overflow", e.getMessage());
+		}
+	}
 }

Vulnerable code:  * @version 2015-05-14
	public static byte[] computeLength(final byte[] aad) {
		final int bitLength = ByteUtils.bitLength(aad);
import net.jcip.annotations.ThreadSafe;

 * @version 2016-10-13
			if(ByteUtils.bitLength(getKey().getEncoded()) != 128){
			if(ByteUtils.bitLength(getKey().getEncoded()) != 192){
			if (ByteUtils.bitLength(getKey().getEncoded()) != 256) {
			if(ByteUtils.bitLength(getKey().getEncoded()) != 128){
			if(ByteUtils.bitLength(getKey().getEncoded()) != 192){
			if(ByteUtils.bitLength(getKey().getEncoded()) != 256){

import net.jcip.annotations.ThreadSafe;

 * @version 2016-10-13
		if (ByteUtils.bitLength(iv) != IV_BIT_LENGTH) {
			throw new JOSEException(String.format("IV length of %d bits is required, got %d", IV_BIT_LENGTH, ByteUtils.bitLength(iv)));
 * @version 2016-10-13
		if (ByteUtils.bitLength(keyBytes) != keyLength) {
			throw new KeyLengthException("CEK key length mismatch: " + ByteUtils.bitLength(keyBytes) + " != " + keyLength);
 * @version 2017-05-05
		for (int i=1; i <= computeDigestCycles(ByteUtils.bitLength(md.getDigestLength()), keyLengthBits); i++) {
 * @version 2016-10-13
		if (enc.cekBitLength() != ByteUtils.bitLength(cek.getEncoded())) {
			throw new KeyLengthException("The Content Encryption Key (CEK) length for " + enc + " must be " + enc.cekBitLength() + " bits");
 * @version 2014-06-29
		if (enc.cekBitLength() != ByteUtils.bitLength(getKey().getEncoded())) {
 * @version 2016-12-04
			if (ByteUtils.bitLength(secretKeyBytes) != keyLength) {
import com.nimbusds.jose.util.Base64;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jose.util.ByteUtils;
import com.nimbusds.jose.util.JSONObjectUtils;
 * @version 2017-04-08
		return ByteUtils.bitLength(k.decode());
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jose.util.ByteUtils;
import com.nimbusds.jose.util.JSONObjectUtils;
 * @version 2017-04-08
		return ByteUtils.bitLength(n.decode());
 * @version 2015-05-12
import com.nimbusds.jose.util.Base64URL;
import junit.framework.TestCase;

 * @version 2015-05-17
	public void testComputeLength() {

 * @version 2017-05-30
	public void testAADLengthComputation() {
Fixed code: import com.nimbusds.jose.util.IntegerOverflowException;
 * @version 2017-06-01
	 *
	 * @throws IntegerOverflowException On a integer overflow.
	public static byte[] computeLength(final byte[] aad)
		throws IntegerOverflowException {
		final int bitLength = ByteUtils.safeBitLength(aad);
import net.jcip.annotations.ThreadSafe;
 * @version 2017-06-01
			if(ByteUtils.safeBitLength(getKey().getEncoded()) != 128){
			if(ByteUtils.safeBitLength(getKey().getEncoded()) != 192){
			if (ByteUtils.safeBitLength(getKey().getEncoded()) != 256) {
			if(ByteUtils.safeBitLength(getKey().getEncoded()) != 128){
			if(ByteUtils.safeBitLength(getKey().getEncoded()) != 192){
			if(ByteUtils.safeBitLength(getKey().getEncoded()) != 256){
import net.jcip.annotations.ThreadSafe;
 * @version 2017-06-01
		if (ByteUtils.safeBitLength(iv) != IV_BIT_LENGTH) {
			throw new JOSEException(String.format("IV length of %d bits is required, got %d", IV_BIT_LENGTH, ByteUtils.safeBitLength(iv)));
 * @version 2017-06-01
		if (ByteUtils.safeBitLength(keyBytes) != keyLength) {
			throw new KeyLengthException("CEK key length mismatch: " + ByteUtils.safeBitLength(keyBytes) + " != " + keyLength);
 * @version 2017-06-01
		for (int i=1; i <= computeDigestCycles(ByteUtils.safeBitLength(md.getDigestLength()), keyLengthBits); i++) {
import com.nimbusds.jose.util.IntegerOverflowException;
 * @version 2017-06-01
		try {
			if (enc.cekBitLength() != ByteUtils.safeBitLength(cek.getEncoded())) {
				throw new KeyLengthException("The Content Encryption Key (CEK) length for " + enc + " must be " + enc.cekBitLength() + " bits");
			}
		} catch (IntegerOverflowException e) {
			throw new KeyLengthException("The Content Encryption Key (CEK) is too long: " + e.getMessage());
 * @version 2017-06-01
		if (enc.cekBitLength() != ByteUtils.safeBitLength(getKey().getEncoded())) {
 * @version 2017-06-01
			if (ByteUtils.safeBitLength(secretKeyBytes) != keyLength) {
import com.nimbusds.jose.util.*;
 * @version 2017-06-01
		try {
			return ByteUtils.safeBitLength(k.decode());
		} catch (IntegerOverflowException e) {
			throw new ArithmeticException(e.getMessage());
		}
import com.nimbusds.jose.util.*;
 * @version 2017-06-01
		try {
			return ByteUtils.safeBitLength(n.decode());
		} catch (IntegerOverflowException e) {
			throw new ArithmeticException(e.getMessage());
		}
 * @version 2017-06-01
	/**
	 * Returns the bit length of the specified byte length, preventing
	 * integer overflow.
	 *
	 * @param byteLength The byte length.
	 *
	 * @return The bit length.
	 *
	 * @throws IntegerOverflowException On a integer overflow.
	 */
	public static int safeBitLength(final int byteLength)
		throws IntegerOverflowException {
		
		long longResult = (long)byteLength * (long)8;
		if((long)((int)longResult) != longResult) {
			throw new IntegerOverflowException();
		} else {
			return (int)longResult;
		}
	}


	/**
	 * Returns the byte length of the specified byte array, preventing
	 * integer overflow.
	 *
	 * @param byteArray The byte array. May be {@code null}.
	 *
	 * @return The bite length, zero if the array is {@code null}.
	 *
	 * @throws IntegerOverflowException On a integer overflow.
	 */
	public static int safeBitLength(final byte[] byteArray)
		throws IntegerOverflowException {

		if (byteArray == null) {
			return 0;
		} else {
			return safeBitLength(byteArray.length);
		}
	}


/*
 * nimbus-jose-jwt
 *
 * Copyright 2012-2016, Connect2id Ltd and contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the
 * License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

package com.nimbusds.jose.util;


import com.nimbusds.jose.JOSEException;


/**
 * Integer overflow exception.
 */
public class IntegerOverflowException extends JOSEException {
	
	
	/**
	 * Creates a new integer overflow exception.
	 */
	public IntegerOverflowException() {
		super("Integer overflow");
	}
}
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jose.util.IntegerOverflowException;
import junit.framework.TestCase;
 * @version 2017-06-01
	public void testComputeLength()
		throws IntegerOverflowException {
import java.util.Arrays;
 * @author Quan Nguyen
 * @version 2017-06-01
	public void testAADLengthComputation()
		throws JOSEException {
	
	
	public void testIntegerOverflowHmacBypass()
		throws Exception {
		
		SecretKey inputKey = new SecretKeySpec(INPUT_KEY_256, "AES");
		
		Assert.assertArrayEquals("Input key", INPUT_KEY_256, inputKey.getEncoded());
		byte[] iv = new byte[16];
		byte[] aad = new byte[8];
		byte[] plaintext = new byte[536870928];
		for (int i = 0; i < plaintext.length; i++ ){
			// Doesn't matter what value is, but rand is too expensive for large array
			plaintext[i] = (byte) i;
		}
		
		AuthenticatedCipherText act;
		
		try {
			act = AESCBC.encryptAuthenticated(inputKey, iv, plaintext, aad, null, null);
		} catch (OutOfMemoryError e) {
			System.out.println("Test not run due to " + e);
			return;
		}
		
		byte[] ciphertext = act.getCipherText();
		byte[] authTag = act.getAuthenticationTag();
		
		// Now shift aad and ciphertext around so that HMAC doesn't change,
		// but the plaintext will change.
		int n = 0;
		byte[] buffer = new byte[aad.length + iv.length + ciphertext.length];
		System.arraycopy(aad, 0, buffer, n, aad.length);
		n += aad.length;
		System.arraycopy(iv, 0, buffer, n, iv.length);
		n += iv.length;
		System.arraycopy(ciphertext, 0, buffer, n, ciphertext.length);
		// Note that due to integer overflow :536870920 * 8 = 64
		int newAadSize = 536870920;
		byte[] newAad = Arrays.copyOfRange(buffer, 0, newAadSize);
		byte[] newIv = Arrays.copyOfRange(buffer, newAadSize, newAadSize + 16);
		byte[] newCiphertext = Arrays.copyOfRange(buffer, newAadSize + 16,
			buffer.length);
		
		try {
			byte[] decrypted = AESCBC.decryptAuthenticated(inputKey, newIv,
				newCiphertext, newAad,
				authTag, // Note that the authTag does NOT change.
				null, null);
			// Reaching this point means that the HMac check is
			// bypassed although the decrypted data is different
			// from plaintext.
			// Assert.assertArrayEquals(decrypted, plaintext);
			fail();
		} catch (JOSEException ignored) {
			// ok
		}
	}
	
import com.nimbusds.jose.JOSEException;
	
	
	public void testSafeBitLength_OK()
		throws JOSEException {
		
		assertEquals(8, ByteUtils.bitLength(1));
		assertEquals(16, ByteUtils.bitLength(2));
		assertEquals(32, ByteUtils.bitLength(4));
		assertEquals(64, ByteUtils.bitLength(8));
	}
	
	
	public void testSafeBitLength_IntegerOverflow() {
		
		try {
			ByteUtils.safeBitLength(Integer.MAX_VALUE);
			fail();
		} catch (IntegerOverflowException e) {
			assertEquals("Integer overflow", e.getMessage());
		}
	}
	
	
	public void testArraySafeBitLength_OK()
		throws JOSEException {
		
		assertEquals( 8, ByteUtils.bitLength(new byte[1]));
		assertEquals(16, ByteUtils.bitLength(new byte[2]));
		assertEquals(32, ByteUtils.bitLength(new byte[4]));
		assertEquals(64, ByteUtils.bitLength(new byte[8]));
	}
	
	
	public void testArraySafeBitLength_IntegerOverflow() {
		
		try {
			ByteUtils.safeBitLength(new byte[Integer.MAX_VALUE]);
			fail();
		} catch (OutOfMemoryError e) {
			System.out.println("Test not run due to " + e);
		} catch (IntegerOverflowException e) {
			assertEquals("Integer overflow", e.getMessage());
		}
	}