diff --git a/pom.xml b/pom.xml
index 89bda67..eeca792 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
com.canonical.openssl
openssl-fips-java
- 0.7.0
+ 0.7.1
17
diff --git a/src/main/java/com/canonical/openssl/cipher/OpenSSLCipher.java b/src/main/java/com/canonical/openssl/cipher/OpenSSLCipher.java
index ef3176e..6bcbd21 100644
--- a/src/main/java/com/canonical/openssl/cipher/OpenSSLCipher.java
+++ b/src/main/java/com/canonical/openssl/cipher/OpenSSLCipher.java
@@ -30,6 +30,7 @@
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.GCMParameterSpec;
import java.security.InvalidAlgorithmParameterException;
+import java.security.MessageDigest;
import java.security.SecureRandom;
import javax.crypto.ShortBufferException;
import javax.crypto.IllegalBlockSizeException;
@@ -74,6 +75,10 @@ abstract public class OpenSSLCipher extends CipherSpi {
boolean firstUpdate = true;
private ClearableBuffer aeadDecryptBuffer;
+ // Last (key, IV) latched for AEAD encryption on this instance, used to reject GCM/CCM nonce reuse.
+ private byte[] lastEncKey;
+ private byte[] lastEncIv;
+
private static final class ClearableBuffer {
private byte[] buf = new byte[256];
private int count;
@@ -151,6 +156,9 @@ protected OpenSSLCipher(String nameKeySizeAndMode, String padding) {
this.mode = parts[2];
this.padding = padding;
this.cipherContext = createContext0(nameKeySizeAndMode, padding);
+ if (this.cipherContext == 0) {
+ throw new ProviderException("Failed to create cipher context for " + nameKeySizeAndMode);
+ }
this.cipherState = new CipherState(this.cipherContext);
cleanable = cleaner.register(this, cipherState);
}
@@ -271,10 +279,30 @@ protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, Se
if (newKeyBytes == null) {
throw new InvalidKeyException("Key does not support encoding");
}
+ boolean encrypting = (opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE);
+ boolean isAEAD = isModeGCM() || isModeCCM();
+ // Reject reuse of the same key+IV for AEAD encryption: GCM/CCM nonce reuse is catastrophic.
+ if (encrypting && isAEAD && lastEncIv != null && lastEncKey != null
+ && MessageDigest.isEqual(specIv, lastEncIv)
+ && MessageDigest.isEqual(newKeyBytes, lastEncKey)) {
+ Arrays.fill(newKeyBytes, (byte) 0);
+ throw new InvalidAlgorithmParameterException(
+ "Cannot reuse the same key and IV for " + mode + " encryption (nonce reuse)");
+ }
resetStateForInit(opmode);
this.keyBytes = newKeyBytes;
this.iv = specIv;
cipherState.setIV(this.iv);
+ if (encrypting && isAEAD) {
+ if (lastEncKey != null) {
+ Arrays.fill(lastEncKey, (byte) 0);
+ }
+ if (lastEncIv != null) {
+ Arrays.fill(lastEncIv, (byte) 0);
+ }
+ lastEncKey = newKeyBytes.clone();
+ lastEncIv = specIv.clone();
+ }
if (!isModeCCM()) {
doInit0(null, 0, 0, keyBytes, iv, this.opmode);
Arrays.fill(keyBytes, (byte)0);
diff --git a/src/main/java/com/canonical/openssl/kdf/OpenSSLPBKDF2.java b/src/main/java/com/canonical/openssl/kdf/OpenSSLPBKDF2.java
index 006423a..e64e3a1 100644
--- a/src/main/java/com/canonical/openssl/kdf/OpenSSLPBKDF2.java
+++ b/src/main/java/com/canonical/openssl/kdf/OpenSSLPBKDF2.java
@@ -17,6 +17,9 @@
package com.canonical.openssl.kdf;
import com.canonical.openssl.util.NativeLibraryLoader;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
import java.security.spec.KeySpec;
import java.util.Arrays;
import javax.crypto.SecretKey;
@@ -142,10 +145,18 @@ protected SecretKey engineGenerateSecret(KeySpec keyspec) throws InvalidKeySpecE
+ " (FIPS SP 800-132)");
}
int keyLengthBytes = resolveKeyLengthBytes(pbeKeySpec.getKeyLength());
- PBKDF2SecretKey secretKey = new PBKDF2SecretKey(pbeKeySpec.getPassword(),
+ char[] password = pbeKeySpec.getPassword();
+ PBKDF2SecretKey secretKey = new PBKDF2SecretKey(password,
pbeKeySpec.getSalt(), pbeKeySpec.getIterationCount());
- byte[] secretBytes = generateSecret0(pbeKeySpec.getPassword(), pbeKeySpec.getSalt(),
+ byte[] passwordBytes = encodePassword(password);
+ byte[] secretBytes;
+ try {
+ secretBytes = generateSecret0(passwordBytes, pbeKeySpec.getSalt(),
pbeKeySpec.getIterationCount(), keyLengthBytes);
+ } finally {
+ Arrays.fill(passwordBytes, (byte) 0);
+ Arrays.fill(password, '\0');
+ }
if (secretBytes == null) {
throw new InvalidKeySpecException("PBKDF2 derivation failed");
}
@@ -222,10 +233,18 @@ protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException
"Key length " + (keyLengthBytes * 8) + " bits exceeds the maximum supported "
+ (MAX_KEY_LENGTH_BYTES * 8) + " bits");
}
- PBKDF2SecretKey secretKey = new PBKDF2SecretKey(pbeKey.getPassword(), pbeKey.getSalt(),
+ char[] password = pbeKey.getPassword();
+ PBKDF2SecretKey secretKey = new PBKDF2SecretKey(password, pbeKey.getSalt(),
pbeKey.getIterationCount());
- byte[] secretBytes = generateSecret0(pbeKey.getPassword(), pbeKey.getSalt(),
+ byte[] passwordBytes = encodePassword(password);
+ byte[] secretBytes;
+ try {
+ secretBytes = generateSecret0(passwordBytes, pbeKey.getSalt(),
pbeKey.getIterationCount(), keyLengthBytes);
+ } finally {
+ Arrays.fill(passwordBytes, (byte) 0);
+ Arrays.fill(password, '\0');
+ }
if (secretBytes == null) {
throw new InvalidKeyException("PBKDF2 derivation failed");
}
@@ -237,6 +256,17 @@ protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException
return secretKey;
}
- private native byte[] generateSecret0(char[] password, byte[] salt, int iterationCount, int keyLength);
+ // UTF-8 encode the password for portability
+ private static byte[] encodePassword(char[] password) {
+ ByteBuffer bb = StandardCharsets.UTF_8.encode(CharBuffer.wrap(password));
+ byte[] bytes = new byte[bb.remaining()];
+ bb.get(bytes);
+ if (bb.hasArray()) {
+ Arrays.fill(bb.array(), (byte) 0);
+ }
+ return bytes;
+ }
+
+ private native byte[] generateSecret0(byte[] password, byte[] salt, int iterationCount, int keyLength);
private static native int getMaxKeyLengthBytes0();
}
diff --git a/src/main/java/com/canonical/openssl/keyagreement/OpenSSLKeyAgreement.java b/src/main/java/com/canonical/openssl/keyagreement/OpenSSLKeyAgreement.java
index c1c1733..21252e6 100644
--- a/src/main/java/com/canonical/openssl/keyagreement/OpenSSLKeyAgreement.java
+++ b/src/main/java/com/canonical/openssl/keyagreement/OpenSSLKeyAgreement.java
@@ -129,6 +129,9 @@ protected void engineInit(Key key, SecureRandom random) throws InvalidKeyExcepti
cleanable.clean();
}
nativeHandle = initialize(key);
+ if (nativeHandle == 0) {
+ throw new InvalidKeyException("Failed to initialize key agreement");
+ }
cleanable = cleaner.register(this, new KeyAgreementState(nativeHandle));
state = State.INITIALIZED;
}
diff --git a/src/main/java/com/canonical/openssl/keyencapsulation/OpenSSLKEMRSA.java b/src/main/java/com/canonical/openssl/keyencapsulation/OpenSSLKEMRSA.java
index 4e2116a..36647e6 100644
--- a/src/main/java/com/canonical/openssl/keyencapsulation/OpenSSLKEMRSA.java
+++ b/src/main/java/com/canonical/openssl/keyencapsulation/OpenSSLKEMRSA.java
@@ -97,15 +97,18 @@ public RSAKEMEncapsulator(PublicKey key) throws InvalidKeyException {
throw new InvalidKeyException("Key does not support encoding");
}
nativeHandle = encapsulatorInit0(encoded);
- cleanable = cleaner.register(this, new EncapsulatorState(nativeHandle));
if (nativeHandle == 0) {
throw new InvalidKeyException("Failed to initialize RSA-KEM encapsulator");
}
+ cleanable = cleaner.register(this, new EncapsulatorState(nativeHandle));
}
public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm) {
- // TODO: ignoring from, to in the prototype
int secretSize = engineSecretSize();
+ if (from < 0 || from > to || to > secretSize) {
+ throw new IndexOutOfBoundsException(
+ "Invalid range [" + from + ", " + to + ") for secret of size " + secretSize);
+ }
byte[] secretBytes = new byte[secretSize];
int encapsulationSize = engineEncapsulationSize();
@@ -113,7 +116,8 @@ public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm) {
engineEncapsulate0(secretBytes, encapsulatedBytes);
try {
- SecretKey secretKey = new SecretKeySpec(secretBytes, algorithm);
+ SecretKey secretKey =
+ new SecretKeySpec(secretBytes, from, to - from, algorithm);
return new KEM.Encapsulated(secretKey, encapsulatedBytes, null);
} finally {
Arrays.fill(secretBytes, (byte)0);
@@ -169,17 +173,24 @@ public RSAKEMDecapsulator(PrivateKey key) throws InvalidKeyException {
} finally {
Arrays.fill(encoded, (byte) 0);
}
- cleanable = cleaner.register(this, new DecapsulatorState(nativeHandle));
if (nativeHandle == 0) {
throw new InvalidKeyException("Failed to initialize RSA-KEM decapsulator");
}
+ cleanable = cleaner.register(this, new DecapsulatorState(nativeHandle));
}
public SecretKey engineDecapsulate(byte[] encapsulation, int from, int to, String algorithm)
throws DecapsulateException {
+ // The secret size is only known once the encapsulation has been
+ // unwrapped, so decapsulate first and then validate the range.
byte[] secretBytes = engineDecapsulate0(encapsulation);
try {
- return new SecretKeySpec(secretBytes, algorithm);
+ if (from < 0 || from > to || to > secretBytes.length) {
+ throw new IndexOutOfBoundsException(
+ "Invalid range [" + from + ", " + to + ") for secret of size "
+ + secretBytes.length);
+ }
+ return new SecretKeySpec(secretBytes, from, to - from, algorithm);
} finally {
Arrays.fill(secretBytes, (byte)0);
}
diff --git a/src/main/java/com/canonical/openssl/mac/OpenSSLMAC.java b/src/main/java/com/canonical/openssl/mac/OpenSSLMAC.java
index b3c16c3..48045c1 100644
--- a/src/main/java/com/canonical/openssl/mac/OpenSSLMAC.java
+++ b/src/main/java/com/canonical/openssl/mac/OpenSSLMAC.java
@@ -23,6 +23,7 @@
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.Key;
+import java.security.ProviderException;
import java.util.Arrays;
import javax.crypto.MacSpi;
import java.security.InvalidAlgorithmParameterException;
@@ -122,6 +123,11 @@ protected void engineInit(Key key, AlgorithmParameterSpec spec) throws InvalidKe
this.keyBytes = newKeyBytes;
this.cachedIV = iv;
nativeHandle = doInit0(getAlgorithm(), getCipherType(), getDigestType(), iv, outputLength, keyBytes);
+ if (nativeHandle == 0) {
+ Arrays.fill(keyBytes, (byte) 0);
+ this.keyBytes = null;
+ throw new InvalidKeyException("Failed to initialize MAC");
+ }
macState = new MACState(nativeHandle);
macState.setKeyBytes(keyBytes);
cleanable = cleaner.register(this, macState);
@@ -138,6 +144,9 @@ protected void engineReset() {
cleanable.clean();
}
nativeHandle = doInit0(getAlgorithm(), getCipherType(), getDigestType(), this.cachedIV, this.outputLength, keyBytes);
+ if (nativeHandle == 0) {
+ throw new ProviderException("Failed to reset MAC");
+ }
macState = new MACState(nativeHandle);
macState.setKeyBytes(keyBytes);
cleanable = cleaner.register(this, macState);
diff --git a/src/main/java/com/canonical/openssl/md/OpenSSLMD.java b/src/main/java/com/canonical/openssl/md/OpenSSLMD.java
index ca1fcde..1266446 100644
--- a/src/main/java/com/canonical/openssl/md/OpenSSLMD.java
+++ b/src/main/java/com/canonical/openssl/md/OpenSSLMD.java
@@ -24,6 +24,7 @@
import java.nio.ByteBuffer;
import java.security.DigestException;
import java.security.MessageDigestSpi;
+import java.security.ProviderException;
import java.util.Arrays;
/* This implementation will be exercised by the user through the
@@ -65,6 +66,9 @@ protected OpenSSLMD(String algorithm) {
private void ensureInitialized() {
if (!initialized) {
nativeHandle = doInit0(mdName);
+ if (nativeHandle == 0) {
+ throw new ProviderException("Failed to initialize message digest " + mdName);
+ }
cleanable = cleaner.register(this, new MDState(nativeHandle));
initialized = true;
}
@@ -94,6 +98,9 @@ protected void engineReset() {
cleanable.clean();
}
nativeHandle = doInit0(mdName);
+ if (nativeHandle == 0) {
+ throw new ProviderException("Failed to initialize message digest " + mdName);
+ }
cleanable = cleaner.register(this, new MDState(nativeHandle));
initialized = true;
}
diff --git a/src/main/java/com/canonical/openssl/signature/OpenSSLSignature.java b/src/main/java/com/canonical/openssl/signature/OpenSSLSignature.java
index b083026..0b5c751 100644
--- a/src/main/java/com/canonical/openssl/signature/OpenSSLSignature.java
+++ b/src/main/java/com/canonical/openssl/signature/OpenSSLSignature.java
@@ -137,6 +137,9 @@ protected void engineInitSign(PrivateKey key) throws InvalidKeyException {
cleanable.clean();
}
nativeHandle = engineInitSign0(getSignatureName(), privKey, params);
+ if (nativeHandle == 0) {
+ throw new InvalidKeyException("Failed to initialize signature for signing");
+ }
cleanable = cleaner.register(this, new SignatureState(nativeHandle));
} else {
throw new InvalidKeyException ("Supplied PrivateKey is of type: " + key.getClass());
@@ -159,6 +162,9 @@ protected void engineInitVerify(PublicKey key) throws InvalidKeyException {
cleanable.clean();
}
nativeHandle = engineInitVerify0(getSignatureName(), pubKey, params);
+ if (nativeHandle == 0) {
+ throw new InvalidKeyException("Failed to initialize signature for verification");
+ }
cleanable = cleaner.register(this, new SignatureState(nativeHandle));
} else {
throw new InvalidKeyException ("Supplied PublicKey is not OpenSSL-based");
diff --git a/src/main/native/c/OpenSSLPBKDF2.c b/src/main/native/c/OpenSSLPBKDF2.c
index 681d3d0..369b88f 100644
--- a/src/main/native/c/OpenSSLPBKDF2.c
+++ b/src/main/native/c/OpenSSLPBKDF2.c
@@ -23,34 +23,34 @@
/*
* Class: com_canonical_openssl_kdf_OpenSSLPBKDF2
* Method: generateSecret0
- * Signature: ([C[BII)[B
+ * Signature: ([B[BII)[B
*/
JNIEXPORT jbyteArray JNICALL Java_com_canonical_openssl_kdf_OpenSSLPBKDF2_generateSecret0
- (JNIEnv *env, jobject this, jcharArray password, jbyteArray salt, jint iteration_count, jint key_length) {
+ (JNIEnv *env, jobject this, jbyteArray password, jbyteArray salt, jint iteration_count, jint key_length) {
if (key_length <= 0 || key_length > MAX_KEY_SIZE) {
throwProviderException(env, "Invalid PBKDF2 key length");
return NULL;
}
- int password_length = (*env)->GetArrayLength(env, password);
+ int password_length = array_length(env, password);
int salt_length = array_length(env, salt);
byte output[MAX_KEY_SIZE] = {0};
jbyteArray result = NULL;
- jchar *password_chars = (*env)->GetCharArrayElements(env, password, NULL);
- if (password_chars == NULL) {
+ jbyte *password_bytes = (*env)->GetByteArrayElements(env, password, NULL);
+ if (password_bytes == NULL) {
return NULL;
}
jbyte *salt_bytes = (*env)->GetByteArrayElements(env, salt, NULL);
if (salt_bytes == NULL) {
- (*env)->ReleaseCharArrayElements(env, password, password_chars, JNI_ABORT);
+ (*env)->ReleaseByteArrayElements(env, password, password_bytes, JNI_ABORT);
return NULL;
}
- kdf_spec *spec = create_pbkdf_spec((byte *)password_chars, password_length * sizeof(jchar),
+ kdf_spec *spec = create_pbkdf_spec((byte *)password_bytes, password_length,
(byte *)salt_bytes, salt_length, iteration_count);
- (*env)->ReleaseCharArrayElements(env, password, password_chars, JNI_ABORT);
+ (*env)->ReleaseByteArrayElements(env, password, password_bytes, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, salt, salt_bytes, JNI_ABORT);
if (spec == NULL) {
diff --git a/src/main/native/c/init.c b/src/main/native/c/init.c
index 9f69039..6fe8ef2 100644
--- a/src/main/native/c/init.c
+++ b/src/main/native/c/init.c
@@ -100,6 +100,26 @@ static void unload_global_libctx() {
unload_libctx(ctx);
}
+/*
+ * Note on OPENSSL_CUSTOM_CONF and the config file:
+ *
+ * When the FIPS provider is not already available by default (i.e. outside the
+ * Ubuntu Pro auto-FIPS setup), we load OpenSSL's configuration from the file
+ * named by the OPENSSL_CUSTOM_CONF environment variable, or from
+ * /usr/local/ssl/openssl.cnf if that variable is not set. That config decides
+ * which provider gets loaded as "fips".
+ *
+ * Both the environment variable and the config file it points to are TRUSTED
+ * inputs. Anyone who can change either of them can choose which OpenSSL provider
+ * module is loaded into this process. We use secure_getenv, so the variable is
+ * ignored when the process is running with elevated privileges (setuid/setgid),
+ * but in every other case the caller is responsible for protecting these inputs.
+ *
+ * In deployments where FIPS compliance is required, make sure the config file
+ * (and the directory containing it) is owned by root and not writable by
+ * untrusted users, so that "fips" cannot be redirected to a non-validated
+ * module.
+ */
int JNI_OnLoad(JavaVM* vm, void *reserved) {
const char *default_cnf = "/usr/local/ssl/openssl.cnf";
const char *custom_cnf = secure_getenv("OPENSSL_CUSTOM_CONF");
diff --git a/src/main/native/include/jni/OpenSSLPBKDF2.h b/src/main/native/include/jni/OpenSSLPBKDF2.h
index 670ddf8..e8ccb36 100644
--- a/src/main/native/include/jni/OpenSSLPBKDF2.h
+++ b/src/main/native/include/jni/OpenSSLPBKDF2.h
@@ -26,10 +26,10 @@ extern "C" {
/*
* Class: com_canonical_openssl_kdf_OpenSSLPBKDF2
* Method: generateSecret0
- * Signature: ([C[BII)[B
+ * Signature: ([B[BII)[B
*/
JNIEXPORT jbyteArray JNICALL Java_com_canonical_openssl_kdf_OpenSSLPBKDF2_generateSecret0
- (JNIEnv *, jobject, jcharArray, jbyteArray, jint, jint);
+ (JNIEnv *, jobject, jbyteArray, jbyteArray, jint, jint);
JNIEXPORT jint JNICALL Java_com_canonical_openssl_kdf_OpenSSLPBKDF2_getMaxKeyLengthBytes0
(JNIEnv *, jclass);
diff --git a/src/test/consumer-snap/snapcraft.yaml b/src/test/consumer-snap/snapcraft.yaml
index 808075a..82894ec 100644
--- a/src/test/consumer-snap/snapcraft.yaml
+++ b/src/test/consumer-snap/snapcraft.yaml
@@ -51,7 +51,7 @@ parts:
apps:
kem-test:
- command: /usr/lib/jvm/java-21-openjdk-amd64/bin/java -cp $SNAP/bin/KEMTest.jar:$SNAP/imported-libs/jar/openssl-fips-java-0.7.0.jar KEMTest
+ command: /usr/lib/jvm/java-21-openjdk-amd64/bin/java -cp $SNAP/bin/KEMTest.jar:$SNAP/imported-libs/jar/openssl-fips-java-0.7.1.jar KEMTest
environment:
OPENSSL_MODULES: $SNAP/usr/local/lib64/ossl-modules/
OPENSSL_CUSTOM_CONF: $SNAP/usr/local/ssl/openssl.cnf
diff --git a/src/test/java/CipherTest.java b/src/test/java/CipherTest.java
index fb3d144..69c4e41 100644
--- a/src/test/java/CipherTest.java
+++ b/src/test/java/CipherTest.java
@@ -22,6 +22,7 @@
import java.security.SecureRandom;
import javax.crypto.spec.IvParameterSpec;
import java.security.spec.AlgorithmParameterSpec;
+import java.security.InvalidAlgorithmParameterException;
import com.canonical.openssl.provider.OpenSSLFIPSProvider;
import org.junit.Test;
@@ -443,6 +444,53 @@ private void runTestAEADDecryptMultipleUpdates(String nameKeySizeAndMode, String
assertArrayEquals("AEAD multi-update decrypt failed for " + cipherName, plaintext, result);
}
+ @Test
+ public void testGCMNonceReuseRejected() throws Exception {
+ for (String cipher : new String[]{"AES128/GCM", "AES192/GCM", "AES256/GCM"}) {
+ runTestGCMNonceReuseRejected(cipher);
+ }
+ }
+
+ private void runTestGCMNonceReuseRejected(String nameKeySizeAndMode) throws Exception {
+ String cipherName = nameKeySizeAndMode + "/NONE";
+ SecureRandom sr = SecureRandom.getInstance("NativePRNG");
+
+ int keyBytes = nameKeySizeAndMode.contains("256") ? 32
+ : nameKeySizeAndMode.contains("192") ? 24 : 16;
+ byte[] key = new byte[keyBytes];
+ sr.nextBytes(key);
+ SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
+
+ byte[] iv = new byte[12];
+ sr.nextBytes(iv);
+ GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
+
+ byte[] plaintext = new byte[32];
+ sr.nextBytes(plaintext);
+
+ Cipher enc = Cipher.getInstance(cipherName, "OpenSSLFIPSProvider");
+ enc.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec, sr);
+ enc.doFinal(plaintext);
+
+ // Re-initializing the same instance for encryption with the same key+IV must be rejected.
+ try {
+ enc.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec, sr);
+ fail("GCM nonce reuse for encryption must be rejected for " + cipherName);
+ } catch (InvalidAlgorithmParameterException expected) {
+ // expected
+ }
+
+ // A fresh IV under the same key must be accepted.
+ byte[] iv2 = new byte[12];
+ sr.nextBytes(iv2);
+ enc.init(Cipher.ENCRYPT_MODE, keySpec, new GCMParameterSpec(128, iv2), sr);
+
+ // Decryption must never be blocked from reusing an IV.
+ Cipher dec = Cipher.getInstance(cipherName, "OpenSSLFIPSProvider");
+ dec.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec, sr);
+ dec.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec, sr);
+ }
+
@Test
public void testDoFinalNoInputAfterUpdate() throws Exception {
SecureRandom sr = SecureRandom.getInstance("NativePRNG");
diff --git a/src/test/java/KeyEncapsulationTest.java b/src/test/java/KeyEncapsulationTest.java
index 6df09aa..c403808 100644
--- a/src/test/java/KeyEncapsulationTest.java
+++ b/src/test/java/KeyEncapsulationTest.java
@@ -61,4 +61,37 @@ public void testKEMRSA() throws Exception {
assertTrue("Key Encapsulation with RSA test failed", aliceSecret.equals(bobSecret));
}
+
+ @Test
+ public void testKEMRSAPartialRange() throws Exception {
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+ kpg.initialize(4096);
+
+ KeyPair aliceKeys = kpg.generateKeyPair();
+ PublicKey alicePublicKey = aliceKeys.getPublic();
+ PrivateKey alicePrivateKey = aliceKeys.getPrivate();
+
+ // Bob encapsulates only a sub-range of the shared secret
+ KEM bobKem = KEM.getInstance("RSA", "OpenSSLFIPSProvider");
+ Encapsulator encapsulator = bobKem.newEncapsulator(alicePublicKey, null, null);
+ int secretSize = encapsulator.secretSize();
+ int from = 8;
+ int to = secretSize / 2;
+ KEM.Encapsulated encapsulated = encapsulator.encapsulate(from, to, "AES");
+ SecretKey bobSecret = encapsulated.key();
+
+ // The key must only contain the requested slice of the secret
+ assertTrue("Encapsulated key has wrong length for partial range",
+ bobSecret.getEncoded().length == to - from);
+
+ // Alice decapsulates the same sub-range and must recover the same key
+ KEM aliceKem = KEM.getInstance("RSA", "OpenSSLFIPSProvider");
+ Decapsulator decapsulator = aliceKem.newDecapsulator(alicePrivateKey, null);
+ byte[] encapsulationBytes = encapsulated.encapsulation();
+ SecretKey aliceSecret = decapsulator.decapsulate(encapsulationBytes, from, to, "AES");
+
+ assertTrue("Decapsulated key has wrong length for partial range",
+ aliceSecret.getEncoded().length == to - from);
+ assertTrue("Partial range KEM with RSA test failed", aliceSecret.equals(bobSecret));
+ }
}