CVE-2017-12972 (published 2017-08-20T16:29Z)

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.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 7.5, HIGH (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-345
cwe_name: Insufficient Verification of Data Authenticity
description: The product does not sufficiently verify the origin or authenticity of data, in a way that causes it to accept invalid data.
extended_description: 
            
         
         

Fix information:
repo_url: https://bitbucket.org/connect2id/nimbus-jose-jwt
commit_details: repo_url: https://bitbucket.org/connect2id/nimbus-jose-jwt
author: Vladimir Dzhuvinov
author_date: 2017-06-01 23:21:05+03:00
msg: Switches to safe bit computation in crypto (iss #224)
repository: repo_url: https://bitbucket.org/connect2id/nimbus-jose-jwt
repo_name: visit repo url
description: visit repo url
repo_language: visit repo url
owner: connect2id
file_changes: filename: AAD.java
change_type: ModificationType.MODIFY
diff: @@ -24,6 +24,7 @@ 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;
 
 
 /**
@@ -32,7 +33,7 @@ import com.nimbusds.jose.util.ByteUtils;
  * <p>See RFC 7518 (JWA), section 5.1, point 14.
  *
  * @author Vladimir Dzhuvinov
- * @version 2015-05-14
+ * @version 2017-06-01
  */
 class AAD {
 
@@ -75,10 +76,13 @@ class AAD {
 	 *
 	 * @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();
 	}
 }

programming_language: Java
filename: AESEncrypter.java
change_type: ModificationType.MODIFY
diff: @@ -21,13 +21,12 @@ 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;
 
 
 /**
@@ -71,7 +70,7 @@ import com.nimbusds.jose.util.Container;
  * @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 {
@@ -145,42 +144,42 @@ public class AESEncrypter extends AESCryptoProvider implements JWEEncrypter {
 
 		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;

programming_language: Java
filename: AESGCM.java
change_type: ModificationType.MODIFY
diff: @@ -20,15 +20,13 @@ 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;
 
 
 /**
@@ -40,7 +38,7 @@ import com.nimbusds.jose.util.Container;
  * @author Vladimir Dzhuvinov
  * @author Axel Nennker
  * @author Dimitar A. Stoikov
- * @version 2016-10-13
+ * @version 2017-06-01
  */
 @ThreadSafe
 class AESGCM {
@@ -199,8 +197,8 @@ class AESGCM {
 	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) {

programming_language: Java
filename: AESGCMKW.java
change_type: ModificationType.MODIFY
diff: @@ -38,7 +38,7 @@ import com.nimbusds.jose.util.Container;
  *
  * @author Melisa Halsband
  * @author Vladimir Dzhuvinov
- * @version 2016-10-13
+ * @version 2017-06-01
  */
 @ThreadSafe
 class AESGCMKW {
@@ -97,9 +97,9 @@ class AESGCMKW {
 
 		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");

programming_language: Java
filename: ConcatKDF.java
change_type: ModificationType.MODIFY
diff: @@ -42,7 +42,7 @@ import net.jcip.annotations.ThreadSafe;
  * <p>See NIST.800-56A.
  *
  * @author Vladimir Dzhuvinov
- * @version 2017-05-05
+ * @version 2017-06-01
  */
 @ThreadSafe
 class ConcatKDF implements JCAAware<JCAContext> {
@@ -115,7 +115,7 @@ class ConcatKDF implements JCAAware<JCAContext> {
 
 		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);
 

programming_language: Java
filename: ContentCryptoProvider.java
change_type: ModificationType.MODIFY
diff: @@ -28,13 +28,14 @@ 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 {
 
@@ -126,8 +127,12 @@ class ContentCryptoProvider {
 	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());
 		}
 	}
 

programming_language: Java
filename: DirectEncrypter.java
change_type: ModificationType.MODIFY
diff: @@ -59,7 +59,7 @@ import com.nimbusds.jose.util.ByteUtils;
  * </ul>
  *
  * @author Vladimir Dzhuvinov
- * @version 2014-06-29
+ * @version 2017-06-01
  */
 @ThreadSafe
 public class DirectEncrypter extends DirectCryptoProvider implements JWEEncrypter {
@@ -132,7 +132,7 @@ 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);
 		}
 

programming_language: Java
filename: RSA1_5.java
change_type: ModificationType.MODIFY
diff: @@ -36,7 +36,7 @@ import net.jcip.annotations.ThreadSafe;
  * decryption. This class is thread-safe.
  *
  * @author Vladimir Dzhuvinov
- * @version 2016-12-04
+ * @version 2017-06-01
  */
 @ThreadSafe
 class RSA1_5 {
@@ -98,7 +98,7 @@ class RSA1_5 {
 			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;
 			}

programming_language: Java
filename: OctetSequenceKey.java
change_type: ModificationType.MODIFY
diff: @@ -29,10 +29,7 @@ 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;
 
@@ -65,7 +62,7 @@ import net.minidev.json.JSONObject;
  * 
  * @author Justin Richer
  * @author Vladimir Dzhuvinov
- * @version 2017-04-08
+ * @version 2017-06-01
  */
 @Immutable
 public final class OctetSequenceKey extends JWK implements SecretJWK {
@@ -539,7 +536,11 @@ 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());
+		}
 	}
 
 

programming_language: Java
filename: RSAKey.java
change_type: ModificationType.MODIFY
diff: @@ -35,9 +35,7 @@ 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;
@@ -133,7 +131,7 @@ import net.minidev.json.JSONObject;
  * @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 {
@@ -1933,7 +1931,11 @@ 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());
+		}
 	}
 
 

programming_language: Java
filename: ByteUtils.java
change_type: ModificationType.MODIFY
diff: @@ -26,7 +26,7 @@ import java.io.IOException;
  * Byte utilities.
  *
  * @author Vladimir Dzhuvinov
- * @version 2015-05-12
+ * @version 2017-06-01
  */
 public class ByteUtils {
 
@@ -92,6 +92,28 @@ public class ByteUtils {
 	}
 
 
+	/**
+	 * 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.
 	 *
@@ -109,6 +131,27 @@ public class ByteUtils {
 	}
 
 
+	/**
+	 * 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.
 	 *

programming_language: Java
filename: IntegerOverflowException.java
change_type: ModificationType.ADD
diff: @@ -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");
+	}
+}

programming_language: Java
filename: AADTest.java
change_type: ModificationType.MODIFY
diff: @@ -21,19 +21,19 @@ 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 {
 
@@ -58,7 +58,8 @@ public class AADTest extends TestCase {
 	}
 
 
-	public void testComputeLength() {
+	public void testComputeLength()
+		throws IntegerOverflowException {
 
 		byte[] aad = new byte[]{0, 1, 2, 3}; // 32 bits
 

programming_language: Java
filename: AESCBCTest.java
change_type: ModificationType.MODIFY
diff: @@ -18,6 +18,7 @@
 package com.nimbusds.jose.crypto;
 
 
+import java.util.Arrays;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
 
@@ -27,7 +28,6 @@ import com.nimbusds.jose.JWEAlgorithm;
 import com.nimbusds.jose.JWEHeader;
 import com.nimbusds.jose.util.Base64URL;
 import junit.framework.TestCase;
-
 import org.junit.Assert;
 
 
@@ -36,7 +36,8 @@ import org.junit.Assert;
  * 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 {
 
@@ -150,7 +151,8 @@ public class AESCBCTest extends TestCase {
 		  (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));
 	}
@@ -243,4 +245,63 @@ public class AESCBCTest extends TestCase {
 			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

programming_language: Java
filename: ByteUtilsTest.java
change_type: ModificationType.MODIFY
diff: @@ -22,6 +22,7 @@ import java.nio.charset.Charset;
 import java.security.MessageDigest;
 import java.util.Arrays;
 
+import com.nimbusds.jose.JOSEException;
 import junit.framework.TestCase;
 
 
@@ -66,4 +67,48 @@ public class ByteUtilsTest extends TestCase {
 		
 		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());
+		}
+	}
 }

programming_language: Java