diff --git a/Makefile b/Makefile
index 96fd92b..00a477f 100644
--- a/Makefile
+++ b/Makefile
@@ -22,7 +22,7 @@ BUILD := ${TOPDIR}/build
JAVA_SRC := src/main/java/com/canonical/openssl
JAVA_SRC_DIRS := ${JAVA_SRC} ${JAVA_SRC}/drbg ${JAVA_SRC}/keyagreement ${JAVA_SRC}/keyencapsulation ${JAVA_SRC}/mac
JAVA_SRC_DIRS += ${JAVA_SRC}/kdf ${JAVA_SRC}/md ${JAVA_SRC}/signature ${JAVA_SRC}/key ${JAVA_SRC}/cipher
-JAVA_SRC_DIRS += ${JAVA_SRC}/provider ${JAVA_SRC}/util
+JAVA_SRC_DIRS += ${JAVA_SRC}/provider ${JAVA_SRC}/util ${JAVA_SRC}/keypairgenerator
JAVA_FILES = $(wildcard $(addsuffix /*.java, $(JAVA_SRC_DIRS)))
# Vars for compiling the C sources
@@ -36,8 +36,18 @@ NATFILES := $(foreach dir,$(NATDIR),$(wildcard $(dir)/*.c))
OBJS := $(patsubst $(NATDIR)/%.c, $(BUILD)/bin/%.o, $(NATFILES))
-CCFLAGS := ${INCLUDE_HEADERS} -c -fPIC -g
-LDFLAGS := -shared -fPIC -Wl,-soname,libjssl.so
+DEBUG ?= 0
+ifeq ($(DEBUG),1)
+ DEBUG_CFLAGS := -g
+else
+ DEBUG_CFLAGS :=
+endif
+
+HARDENING_CFLAGS := -O2 -fstack-protector-strong -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -Wall -Wformat -Wformat-security
+HARDENING_LDFLAGS := -Wl,-z,relro,-z,now
+
+CCFLAGS := ${INCLUDE_HEADERS} -c -fPIC ${HARDENING_CFLAGS} ${DEBUG_CFLAGS}
+LDFLAGS := -shared -fPIC ${HARDENING_LDFLAGS} -Wl,-soname,libjssl.so
SOLIB := $(BUILD)/bin/libjssl.so
# Vars for compiling the test sources
diff --git a/pom.xml b/pom.xml
index 583f761..a75ee49 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
com.canonical.openssl
openssl-fips-java
- 0.6.0
+ 0.7.0
17
diff --git a/src/main/java/com/canonical/openssl/cipher/OpenSSLCipher.java b/src/main/java/com/canonical/openssl/cipher/OpenSSLCipher.java
index 3fdd552..ef3176e 100644
--- a/src/main/java/com/canonical/openssl/cipher/OpenSSLCipher.java
+++ b/src/main/java/com/canonical/openssl/cipher/OpenSSLCipher.java
@@ -28,14 +28,21 @@
import java.security.AlgorithmParameters;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.GCMParameterSpec;
+import java.security.InvalidAlgorithmParameterException;
import java.security.SecureRandom;
import javax.crypto.ShortBufferException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.BadPaddingException;
import java.security.InvalidKeyException;
+import java.security.KeyFactory;
import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
+import java.security.ProviderException;
+import java.security.spec.InvalidParameterSpecException;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.SecretKeyFactory;
@@ -65,6 +72,45 @@ abstract public class OpenSSLCipher extends CipherSpi {
int outputSize;
int opmode = UNDECIDED;
boolean firstUpdate = true;
+ private ClearableBuffer aeadDecryptBuffer;
+
+ private static final class ClearableBuffer {
+ private byte[] buf = new byte[256];
+ private int count;
+
+ void write(byte[] src, int offset, int len) {
+ ensure(count + len);
+ System.arraycopy(src, offset, buf, count, len);
+ count += len;
+ }
+
+ private void ensure(int min) {
+ if (min <= buf.length) {
+ return;
+ }
+ int newCap = buf.length;
+ while (newCap < min) {
+ newCap <<= 1;
+ }
+ byte[] old = buf;
+ buf = new byte[newCap];
+ System.arraycopy(old, 0, buf, 0, count);
+ Arrays.fill(old, (byte) 0);
+ }
+
+ byte[] takeAndClear() {
+ byte[] out = new byte[count];
+ System.arraycopy(buf, 0, out, 0, count);
+ Arrays.fill(buf, 0, count, (byte) 0);
+ count = 0;
+ return out;
+ }
+
+ void clear() {
+ Arrays.fill(buf, 0, count, (byte) 0);
+ count = 0;
+ }
+ }
private static class CipherState implements Runnable {
private final AtomicLong nativeHandle;
@@ -125,35 +171,109 @@ private boolean isAADSupported() {
}
@Override
- protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) {
- // Note: AlgorithmParameters support not yet implemented
- engineInit(opmode, key, random);
+ protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+ if (params == null) {
+ engineInit(opmode, key, random);
+ return;
+ }
+ AlgorithmParameterSpec spec;
+ try {
+ if (isModeGCM() || isModeCCM()) {
+ spec = params.getParameterSpec(GCMParameterSpec.class);
+ } else {
+ spec = params.getParameterSpec(IvParameterSpec.class);
+ }
+ } catch (InvalidParameterSpecException e) {
+ throw new InvalidAlgorithmParameterException("Could not decode AlgorithmParameters for mode " + mode, e);
+ }
+ engineInit(opmode, key, spec, random);
}
@Override
- protected void engineInit(int opmode, Key key, SecureRandom random) {
- throw new UnsupportedOperationException ("The prototype supports only symmetric-key encrypt/decrypt with IVs");
- }
+ protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
+ if (key == null) {
+ throw new InvalidKeyException("Key must not be null");
+ }
- @Override
- protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) {
- if (!(params instanceof IvParameterSpec)) {
- throw new UnsupportedOperationException ("The prototype supports only symmetric-key encrypt/decrypt with an IV");
+ if ("ECB".equals(mode)) {
+ byte[] newKeyBytes = key.getEncoded();
+ if (newKeyBytes == null) {
+ throw new InvalidKeyException("Key does not support encoding");
+ }
+ resetStateForInit(opmode);
+ this.keyBytes = newKeyBytes;
+ cipherState.setIV(null);
+ doInit0(null, 0, 0, keyBytes, null, this.opmode);
+ Arrays.fill(keyBytes, (byte)0);
+ this.keyBytes = null;
+ return;
+ }
+
+ if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE) {
+ throw new InvalidKeyException("Mode " + mode + " requires an IV for decrypt/unwrap; use init with an IvParameterSpec");
+ }
+
+ byte[] generatedIv = new byte[ivLengthForMode()];
+ SecureRandom rng = (random != null) ? random : new SecureRandom();
+ rng.nextBytes(generatedIv);
+ try {
+ engineInit(opmode, key, new IvParameterSpec(generatedIv), random);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new InvalidKeyException("Failed to initialize with generated IV", e);
}
+ }
+
+ private void resetStateForInit(int opmode) {
this.firstUpdate = true;
this.inputSize = this.outputSize = 0;
- this.opmode = ((opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE)? ENCRYPT : DECRYPT);
- // Zero any key/IV material left over from a previous init before overwriting
+ if (this.aeadDecryptBuffer != null) {
+ this.aeadDecryptBuffer.clear();
+ this.aeadDecryptBuffer = null;
+ }
+ this.opmode = ((opmode == Cipher.ENCRYPT_MODE || opmode == Cipher.WRAP_MODE) ? ENCRYPT : DECRYPT);
if (this.keyBytes != null) {
- Arrays.fill(this.keyBytes, (byte)0);
+ Arrays.fill(this.keyBytes, (byte) 0);
this.keyBytes = null;
}
if (this.iv != null) {
- Arrays.fill(this.iv, (byte)0);
+ Arrays.fill(this.iv, (byte) 0);
this.iv = null;
}
- this.keyBytes = key.getEncoded();
- this.iv = ((IvParameterSpec)params).getIV();
+ }
+
+ private int ivLengthForMode() {
+ switch (mode) {
+ case "GCM":
+ case "CCM":
+ return 12;
+ default:
+ return 16;
+ }
+ }
+
+ @Override
+ protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+ if (key == null) {
+ throw new InvalidKeyException("Key must not be null");
+ }
+ byte[] specIv;
+ if (params instanceof IvParameterSpec ivSpec) {
+ specIv = ivSpec.getIV();
+ } else if (params instanceof GCMParameterSpec gcmSpec) {
+ if (gcmSpec.getTLen() != GCM_TAG_LEN * 8) {
+ throw new InvalidAlgorithmParameterException("Only " + (GCM_TAG_LEN * 8) + "-bit GCM tag length is supported, got: " + gcmSpec.getTLen());
+ }
+ specIv = gcmSpec.getIV();
+ } else {
+ throw new InvalidAlgorithmParameterException("Unsupported AlgorithmParameterSpec: " + (params == null ? "null" : params.getClass().getName()));
+ }
+ byte[] newKeyBytes = key.getEncoded();
+ if (newKeyBytes == null) {
+ throw new InvalidKeyException("Key does not support encoding");
+ }
+ resetStateForInit(opmode);
+ this.keyBytes = newKeyBytes;
+ this.iv = specIv;
cipherState.setIV(this.iv);
if (!isModeCCM()) {
doInit0(null, 0, 0, keyBytes, iv, this.opmode);
@@ -174,16 +294,27 @@ protected void engineSetPadding(String padding) {
@Override
protected byte[] engineUpdate(byte[] bytes, int offset, int length) {
- if (offset + length > bytes.length) {
+ if (bytes == null) {
+ throw new NullPointerException("input array must not be null");
+ }
+ if (offset < 0 || length < 0 || offset > bytes.length - length) {
throw new IllegalArgumentException("Invalid offset and/or length");
}
+ if (isAADSupported() && opmode == DECRYPT) {
+ if (aeadDecryptBuffer == null) {
+ aeadDecryptBuffer = new ClearableBuffer();
+ }
+ aeadDecryptBuffer.write(bytes, offset, length);
+ inputSize += length;
+ return new byte[0];
+ }
if (isModeCCM() && firstUpdate) {
doInit0(bytes, offset, length, keyBytes, iv, opmode);
Arrays.fill(keyBytes, (byte)0);
this.keyBytes = null;
}
firstUpdate = false;
- inputSize += length;
+ inputSize += length;
byte[] ret = doUpdate0(bytes, offset, length);
outputSize += ret.length;
return ret;
@@ -191,17 +322,59 @@ protected byte[] engineUpdate(byte[] bytes, int offset, int length) {
@Override
protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException {
- throw new UnsupportedOperationException("Unimplemented");
+ byte[] result = engineUpdate(input, inputOffset, inputLen);
+ try {
+ if (result == null || result.length == 0) {
+ return 0;
+ }
+ if (output == null || output.length - outputOffset < result.length) {
+ throw new ShortBufferException("Output buffer too small: need " + result.length + " bytes");
+ }
+ System.arraycopy(result, 0, output, outputOffset, result.length);
+ return result.length;
+ } finally {
+ if (result != null) {
+ Arrays.fill(result, (byte) 0);
+ }
+ }
}
@Override
protected AlgorithmParameters engineGetParameters() {
- throw new UnsupportedOperationException("The prototype ignores AlgorithmParameters");
+ if (iv == null) {
+ return null;
+ }
+ String name;
+ AlgorithmParameterSpec spec;
+ if (isModeGCM()) {
+ name = "GCM";
+ spec = new GCMParameterSpec(GCM_TAG_LEN * 8, iv);
+ } else if (isModeCCM()) {
+ name = "CCM";
+ spec = new GCMParameterSpec(GCM_TAG_LEN * 8, iv);
+ } else {
+ name = "AES";
+ spec = new IvParameterSpec(iv);
+ }
+ AlgorithmParameters ap;
+ try {
+ ap = AlgorithmParameters.getInstance(name);
+ } catch (NoSuchAlgorithmException e) {
+ /* No JCA-registered AlgorithmParameters for this mode (e.g. CCM on stock JDK);
+ * SPI contract allows returning null. */
+ return null;
+ }
+ try {
+ ap.init(spec);
+ } catch (InvalidParameterSpecException e) {
+ throw new ProviderException("Could not encode AlgorithmParameters", e);
+ }
+ return ap;
}
@Override
protected byte[] engineGetIV() {
- return iv;
+ return iv == null ? null : iv.clone();
}
protected abstract int engineGetOutputSize(int inputLen);
@@ -209,26 +382,78 @@ protected byte[] engineGetIV() {
protected abstract int engineGetBlockSize();
@Override
- protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException,
+ protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException,
IllegalBlockSizeException, BadPaddingException {
- throw new UnsupportedOperationException("Not implmenented");
+ byte[] result = engineDoFinal(input, inputOffset, inputLen);
+ try {
+ if (result == null || result.length == 0) {
+ return 0;
+ }
+ if (output == null || output.length - outputOffset < result.length) {
+ throw new ShortBufferException("Output buffer too small: need " + result.length + " bytes");
+ }
+ System.arraycopy(result, 0, output, outputOffset, result.length);
+ return result.length;
+ } finally {
+ if (result != null) {
+ Arrays.fill(result, (byte) 0);
+ }
+ }
}
@Override
protected byte[] engineDoFinal(byte[] bytes, int offset, int length) throws IllegalBlockSizeException, BadPaddingException {
- if (isModeCCM() && firstUpdate) {
- firstUpdate = false;
- doInit0(bytes, offset, length, keyBytes, iv, opmode);
- Arrays.fill(keyBytes, (byte)0);
- this.keyBytes = null;
+ if (bytes == null && length == 0) {
+ bytes = new byte[0];
+ offset = 0;
+ }
+ if (bytes == null) {
+ throw new NullPointerException("input array must not be null");
}
- int ciphertextLen = length;
- if (isModeGCM() && opmode == DECRYPT) {
- ciphertextLen = length - GCM_TAG_LEN;
- setGCMTag0(bytes, offset + ciphertextLen, GCM_TAG_LEN);
+ if (offset < 0 || length < 0 || offset > bytes.length - length) {
+ throw new IllegalArgumentException("Invalid offset and/or length");
+ }
+ byte[] accumulated = null;
+ try {
+ if (isAADSupported() && opmode == DECRYPT && aeadDecryptBuffer != null) {
+ aeadDecryptBuffer.write(bytes, offset, length);
+ accumulated = aeadDecryptBuffer.takeAndClear();
+ aeadDecryptBuffer = null;
+ bytes = accumulated;
+ offset = 0;
+ length = accumulated.length;
+ }
+ if (isModeCCM() && firstUpdate) {
+ firstUpdate = false;
+ doInit0(bytes, offset, length, keyBytes, iv, opmode);
+ Arrays.fill(keyBytes, (byte)0);
+ this.keyBytes = null;
+ }
+ int ciphertextLen = length;
+ if (isModeGCM() && opmode == DECRYPT) {
+ if (length < GCM_TAG_LEN) {
+ throw new BadPaddingException("GCM ciphertext shorter than tag");
+ }
+ ciphertextLen = length - GCM_TAG_LEN;
+ setGCMTag0(bytes, offset + ciphertextLen, GCM_TAG_LEN);
+ }
+ byte[] transformed = doUpdate0(bytes, offset, ciphertextLen);
+ try {
+ return doFinal0(transformed, transformed.length);
+ } finally {
+ if (transformed != null) {
+ Arrays.fill(transformed, (byte) 0);
+ }
+ }
+ } finally {
+ if (accumulated != null) {
+ Arrays.fill(accumulated, (byte) 0);
+ }
+ if (aeadDecryptBuffer != null) {
+ aeadDecryptBuffer.clear();
+ aeadDecryptBuffer = null;
+ }
}
- byte[] transformed = doUpdate0(bytes, offset, ciphertextLen);
- return doFinal0(transformed, transformed.length);
}
@Override
@@ -264,6 +489,8 @@ protected byte[] engineWrap(Key key) throws IllegalBlockSizeException, InvalidKe
return engineDoFinal(encoded, 0, encoded.length);
} catch (BadPaddingException e) {
throw new InvalidKeyException("Wrapping failed", e);
+ } finally {
+ Arrays.fill(encoded, (byte)0);
}
}
@@ -271,10 +498,6 @@ protected byte[] engineWrap(Key key) throws IllegalBlockSizeException, InvalidKe
protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType)
throws InvalidKeyException, NoSuchAlgorithmException {
- if (wrappedKeyType == Cipher.PUBLIC_KEY || wrappedKeyType == Cipher.PRIVATE_KEY) {
- throw new UnsupportedOperationException("No KeyFactory for public/private key pairs in the provider yet");
- }
-
byte[] keyMaterial = null;
try {
@@ -283,13 +506,44 @@ protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wr
throw new InvalidKeyException("Unwrapping failed", e);
}
- return createKey(keyMaterial, wrappedKeyAlgorithm, wrappedKeyType);
+ try {
+ return createKey(keyMaterial, wrappedKeyAlgorithm, wrappedKeyType);
+ } finally {
+ if (keyMaterial != null) Arrays.fill(keyMaterial, (byte)0);
+ }
+ }
+
+ private static KeyFactory keyFactoryForAlgo(String algo) throws NoSuchAlgorithmException {
+ // TODO: OpenSSLFIPSProvider does not yet register any KeyFactory implementations,
+ // so PUBLIC_KEY / PRIVATE_KEY unwrapping currently always falls back to the
+ // system-default provider.
+ try {
+ return KeyFactory.getInstance(algo, "OpenSSLFIPSProvider");
+ } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
+ // Algorithm not registered in OpenSSLFIPSProvider; fall back to system-default provider.
+ return KeyFactory.getInstance(algo);
+ }
}
- // TODO: might need to move to its own helper class
- private Key createKey(byte[] keyMaterial, String algo, int keyType) throws NoSuchAlgorithmException {
- // TODO: keyType is only SECRET_KEY for now
- return new SecretKeySpec(keyMaterial, 0, keyMaterial.length, algo);
+ private Key createKey(byte[] keyMaterial, String algo, int keyType) throws NoSuchAlgorithmException, InvalidKeyException {
+ switch (keyType) {
+ case Cipher.SECRET_KEY:
+ return new SecretKeySpec(keyMaterial, 0, keyMaterial.length, algo);
+ case Cipher.PUBLIC_KEY:
+ try {
+ return keyFactoryForAlgo(algo).generatePublic(new X509EncodedKeySpec(keyMaterial));
+ } catch (InvalidKeySpecException e) {
+ throw new InvalidKeyException("Failed to decode public key for algorithm " + algo, e);
+ }
+ case Cipher.PRIVATE_KEY:
+ try {
+ return keyFactoryForAlgo(algo).generatePrivate(new PKCS8EncodedKeySpec(keyMaterial));
+ } catch (InvalidKeySpecException e) {
+ throw new InvalidKeyException("Failed to decode private key for algorithm " + algo, e);
+ }
+ default:
+ throw new InvalidKeyException("Unsupported wrapped key type: " + keyType);
+ }
}
private static void cleanupNativeMemory(long handle) {
@@ -301,7 +555,7 @@ private static void cleanupNativeMemory(long handle) {
native long createContext0(String nameAndMode, String padding);
native void doInit0(byte[] input, int offset, int length, byte[] key, byte[] iv, int opmode);
native byte[] doUpdate0(byte[] input, int offset, int length);
- native byte[] updateAAD0(byte[] aad, int offset, int len);
+ native void updateAAD0(byte[] aad, int offset, int len);
native byte[] doFinal0(byte[] output, int length);
native void setGCMTag0(byte[] tag, int offset, int len);
}
diff --git a/src/main/java/com/canonical/openssl/drbg/OpenSSLDrbg.java b/src/main/java/com/canonical/openssl/drbg/OpenSSLDrbg.java
index 0089da3..03a908d 100644
--- a/src/main/java/com/canonical/openssl/drbg/OpenSSLDrbg.java
+++ b/src/main/java/com/canonical/openssl/drbg/OpenSSLDrbg.java
@@ -33,7 +33,7 @@
public class OpenSSLDrbg extends SecureRandomSpi {
- public static int DEFAULT_STRENGTH = 128;
+ public static final int DEFAULT_STRENGTH = 128;
static {
NativeLibraryLoader.load();
}
@@ -66,6 +66,9 @@ private OpenSSLDrbg() { }
protected OpenSSLDrbg(String name) {
drbgContext = init(name, DEFAULT_STRENGTH, false, false, null);
+ if (drbgContext == 0) {
+ throw new ProviderException("Failed to initialize DRBG: " + name);
+ }
cleanable = cleaner.register(this, new NativeDRBG(drbgContext));
}
@@ -76,12 +79,15 @@ protected OpenSSLDrbg(String name, SecureRandomParameters params) throws Illegal
if (params != null) {
this.params = params;
- DrbgParameters.Instantiation ins = (DrbgParameters.Instantiation)params;
+ DrbgParameters.Instantiation ins = (DrbgParameters.Instantiation)params;
this.drbgContext = init(name, ins.getStrength(), ins.getCapability().supportsPredictionResistance(),
ins.getCapability().supportsReseeding(), ins.getPersonalizationString());
} else {
this.drbgContext = init(name, DEFAULT_STRENGTH, false, false, null);
}
+ if (drbgContext == 0) {
+ throw new ProviderException("Failed to initialize DRBG: " + name);
+ }
cleanable = cleaner.register(this, new NativeDRBG(drbgContext));
}
@@ -165,8 +171,15 @@ protected void engineReseed(SecureRandomParameters params) throws IllegalArgumen
}
protected void engineSetSeed(byte[] seed) {
+ if (!isInitialized()) {
+ throw new ProviderException("DRBG not initialized");
+ }
synchronized (LOCK) {
- reseed0(seed, false, null);
+ // FIPS DRBGs (SP 800-90A) require entropy_input from a validated
+ // entropy source; caller-supplied bytes are routed as additional_input
+ // so OpenSSL still pulls fresh entropy from the FIPS-approved source.
+ // This matches SecureRandom.setSeed's "supplements, not replaces" contract.
+ reseed0(null, false, seed);
}
}
diff --git a/src/main/java/com/canonical/openssl/kdf/OpenSSLPBKDF2.java b/src/main/java/com/canonical/openssl/kdf/OpenSSLPBKDF2.java
index ace0501..006423a 100644
--- a/src/main/java/com/canonical/openssl/kdf/OpenSSLPBKDF2.java
+++ b/src/main/java/com/canonical/openssl/kdf/OpenSSLPBKDF2.java
@@ -49,8 +49,13 @@
*/
public class OpenSSLPBKDF2 extends SecretKeyFactorySpi {
+ private static final int FIPS_MIN_ITERATIONS = 1000;
+ private static final int DEFAULT_KEY_LENGTH_BYTES = 64;
+ private static final int MAX_KEY_LENGTH_BYTES;
+
static {
NativeLibraryLoader.load();
+ MAX_KEY_LENGTH_BYTES = getMaxKeyLengthBytes0();
}
public class PBKDF2SecretKey implements PBEKey {
@@ -63,7 +68,7 @@ public class PBKDF2SecretKey implements PBEKey {
public PBKDF2SecretKey(char[] password, byte[] salt, int iterationCount) {
this.password = password.clone();
- this.salt = salt;
+ this.salt = salt == null ? null : salt.clone();
this.iterationCount = iterationCount;
}
@@ -85,17 +90,17 @@ public char[] getPassword() {
public byte[] getSalt() {
checkDestroyed();
- return salt;
+ return salt == null ? null : salt.clone();
}
public void setEncoded(byte[] keyBytes) {
checkDestroyed();
- this.keyBytes = keyBytes;
+ this.keyBytes = keyBytes == null ? null : keyBytes.clone();
}
public byte[] getEncoded() {
checkDestroyed();
- return keyBytes;
+ return keyBytes == null ? null : keyBytes.clone();
}
public String getFormat() {
@@ -114,6 +119,9 @@ public void destroy() {
if (password != null) {
Arrays.fill(password, '\0');
}
+ if (salt != null) {
+ Arrays.fill(salt, (byte) 0);
+ }
if (keyBytes != null) {
Arrays.fill(keyBytes, (byte) 0);
}
@@ -128,20 +136,46 @@ public boolean isDestroyed() {
protected SecretKey engineGenerateSecret(KeySpec keyspec) throws InvalidKeySpecException {
if (keyspec instanceof PBEKeySpec pbeKeySpec) {
+ if (pbeKeySpec.getIterationCount() < FIPS_MIN_ITERATIONS) {
+ throw new InvalidKeySpecException(
+ "PBKDF2 iteration count must be at least " + FIPS_MIN_ITERATIONS
+ + " (FIPS SP 800-132)");
+ }
+ int keyLengthBytes = resolveKeyLengthBytes(pbeKeySpec.getKeyLength());
PBKDF2SecretKey secretKey = new PBKDF2SecretKey(pbeKeySpec.getPassword(),
pbeKeySpec.getSalt(), pbeKeySpec.getIterationCount());
byte[] secretBytes = generateSecret0(pbeKeySpec.getPassword(), pbeKeySpec.getSalt(),
- pbeKeySpec.getIterationCount());
+ pbeKeySpec.getIterationCount(), keyLengthBytes);
if (secretBytes == null) {
throw new InvalidKeySpecException("PBKDF2 derivation failed");
}
- secretKey.setEncoded(secretBytes);
+ try {
+ secretKey.setEncoded(secretBytes);
+ } finally {
+ Arrays.fill(secretBytes, (byte) 0);
+ }
return secretKey;
} else {
throw new InvalidKeySpecException("Invalid KeySpec type, should be PBEKeySpec");
}
}
+ private static int resolveKeyLengthBytes(int keyLengthBits) throws InvalidKeySpecException {
+ if (keyLengthBits == 0) {
+ return DEFAULT_KEY_LENGTH_BYTES;
+ }
+ if (keyLengthBits < 0) {
+ throw new InvalidKeySpecException("Negative key length: " + keyLengthBits);
+ }
+ int bytes = (keyLengthBits + 7) / 8;
+ if (bytes > MAX_KEY_LENGTH_BYTES) {
+ throw new InvalidKeySpecException(
+ "Requested key length " + keyLengthBits + " bits exceeds the maximum supported "
+ + (MAX_KEY_LENGTH_BYTES * 8) + " bits");
+ }
+ return bytes;
+ }
+
protected KeySpec engineGetKeySpec(SecretKey key, Class> keySpec) throws InvalidKeySpecException {
// TODO: this is quite half-hearted :-/
if (keySpec.isAssignableFrom(PBEKeySpec.class) && key instanceof PBEKey pbeKey) {
@@ -151,19 +185,58 @@ protected KeySpec engineGetKeySpec(SecretKey key, Class> keySpec) throws Inval
}
protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException {
- if (key instanceof PBEKey pbeKey) {
- PBKDF2SecretKey secretKey = new PBKDF2SecretKey(pbeKey.getPassword(), pbeKey.getSalt(),
- pbeKey.getIterationCount());
- byte[] secretBytes = generateSecret0(pbeKey.getPassword(), pbeKey.getSalt(), pbeKey.getIterationCount());
- if (secretBytes == null) {
- throw new InvalidKeyException("PBKDF2 derivation failed");
+ if (key == null) {
+ throw new InvalidKeyException("Key must not be null");
+ }
+ if (key instanceof PBKDF2SecretKey) {
+ return key;
+ }
+ if (!(key instanceof PBEKey pbeKey)) {
+ throw new InvalidKeyException("A key of type PBEKey is expected, given " + key.getClass());
+ }
+ String algorithm = pbeKey.getAlgorithm();
+ if (algorithm == null || !algorithm.regionMatches(true, 0, "PBKDF2", 0, 6)) {
+ throw new InvalidKeyException("Cannot translate non-PBKDF2 key, algorithm: " + algorithm);
+ }
+ if (!"RAW".equalsIgnoreCase(pbeKey.getFormat())) {
+ throw new InvalidKeyException("Cannot translate key with format: " + pbeKey.getFormat());
+ }
+ if (pbeKey.getIterationCount() < FIPS_MIN_ITERATIONS) {
+ throw new InvalidKeyException(
+ "PBKDF2 iteration count must be at least " + FIPS_MIN_ITERATIONS
+ + " (FIPS SP 800-132)");
+ }
+ // For RAW keys, getEncoded().length is the key length; preserve it.
+ byte[] existing = pbeKey.getEncoded();
+ int keyLengthBytes;
+ try {
+ keyLengthBytes = (existing != null && existing.length > 0)
+ ? existing.length : DEFAULT_KEY_LENGTH_BYTES;
+ } finally {
+ if (existing != null) {
+ Arrays.fill(existing, (byte) 0);
}
+ }
+ if (keyLengthBytes > MAX_KEY_LENGTH_BYTES) {
+ throw new InvalidKeyException(
+ "Key length " + (keyLengthBytes * 8) + " bits exceeds the maximum supported "
+ + (MAX_KEY_LENGTH_BYTES * 8) + " bits");
+ }
+ PBKDF2SecretKey secretKey = new PBKDF2SecretKey(pbeKey.getPassword(), pbeKey.getSalt(),
+ pbeKey.getIterationCount());
+ byte[] secretBytes = generateSecret0(pbeKey.getPassword(), pbeKey.getSalt(),
+ pbeKey.getIterationCount(), keyLengthBytes);
+ if (secretBytes == null) {
+ throw new InvalidKeyException("PBKDF2 derivation failed");
+ }
+ try {
secretKey.setEncoded(secretBytes);
- return secretKey;
- } else {
- throw new InvalidKeyException("A key of type PBEKey is expected, given " + key.getClass());
+ } finally {
+ Arrays.fill(secretBytes, (byte) 0);
}
+ return secretKey;
}
- private native byte[] generateSecret0(char[] password, byte[] salt, int iterationCount);
+ private native byte[] generateSecret0(char[] password, byte[] salt, int iterationCount, int keyLength);
+ private static native int getMaxKeyLengthBytes0();
}
diff --git a/src/main/java/com/canonical/openssl/key/KeyConverter.java b/src/main/java/com/canonical/openssl/key/KeyConverter.java
index 2b90859..00a7568 100644
--- a/src/main/java/com/canonical/openssl/key/KeyConverter.java
+++ b/src/main/java/com/canonical/openssl/key/KeyConverter.java
@@ -18,6 +18,7 @@
import com.canonical.openssl.util.NativeLibraryLoader;
import java.security.PrivateKey;
import java.security.PublicKey;
+import java.util.Arrays;
/**
* Utility class for converting Java Key objects to OpenSSL EVP_PKEY handles.
*
@@ -58,7 +59,11 @@ public static long privateKeyToEVPKey(PrivateKey key) {
if (encoded == null) {
throw new IllegalArgumentException("Key does not support encoding");
}
- return privateKeyToEVPKey0(encoded);
+ try {
+ return privateKeyToEVPKey0(encoded);
+ } finally {
+ Arrays.fill(encoded, (byte) 0);
+ }
}
/**
* Convert a Java PublicKey to an OpenSSL EVP_PKEY handle.
diff --git a/src/main/java/com/canonical/openssl/keyagreement/DHKeyAgreement.java b/src/main/java/com/canonical/openssl/keyagreement/DHKeyAgreement.java
index 67b8d5c..03c4b21 100644
--- a/src/main/java/com/canonical/openssl/keyagreement/DHKeyAgreement.java
+++ b/src/main/java/com/canonical/openssl/keyagreement/DHKeyAgreement.java
@@ -17,10 +17,18 @@
package com.canonical.openssl.keyagreement;
import java.security.Key;
+import java.util.Arrays;
public final class DHKeyAgreement extends OpenSSLKeyAgreement {
protected long initialize(Key key) {
- return engineInit0(OpenSSLKeyAgreement.AGREEMENT_DH, key.getEncoded());
+ byte[] encoded = key.getEncoded();
+ try {
+ return engineInit0(OpenSSLKeyAgreement.AGREEMENT_DH, encoded);
+ } finally {
+ if (encoded != null) {
+ Arrays.fill(encoded, (byte) 0);
+ }
+ }
}
@Override
diff --git a/src/main/java/com/canonical/openssl/keyagreement/ECDHKeyAgreement.java b/src/main/java/com/canonical/openssl/keyagreement/ECDHKeyAgreement.java
index cd0274e..8789469 100644
--- a/src/main/java/com/canonical/openssl/keyagreement/ECDHKeyAgreement.java
+++ b/src/main/java/com/canonical/openssl/keyagreement/ECDHKeyAgreement.java
@@ -17,10 +17,18 @@
package com.canonical.openssl.keyagreement;
import java.security.Key;
+import java.util.Arrays;
public final class ECDHKeyAgreement extends OpenSSLKeyAgreement {
protected long initialize(Key key) {
- return engineInit0(OpenSSLKeyAgreement.AGREEMENT_ECDH, key.getEncoded());
+ byte[] encoded = key.getEncoded();
+ try {
+ return engineInit0(OpenSSLKeyAgreement.AGREEMENT_ECDH, encoded);
+ } finally {
+ if (encoded != null) {
+ Arrays.fill(encoded, (byte) 0);
+ }
+ }
}
@Override
diff --git a/src/main/java/com/canonical/openssl/keyagreement/OpenSSLKeyAgreement.java b/src/main/java/com/canonical/openssl/keyagreement/OpenSSLKeyAgreement.java
index 51eb1fc..c1c1733 100644
--- a/src/main/java/com/canonical/openssl/keyagreement/OpenSSLKeyAgreement.java
+++ b/src/main/java/com/canonical/openssl/keyagreement/OpenSSLKeyAgreement.java
@@ -20,6 +20,7 @@
import com.canonical.openssl.util.NativeLibraryLoader;
import java.lang.ref.Cleaner;
import java.util.concurrent.atomic.AtomicLong;
+import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
@@ -41,8 +42,8 @@ abstract public class OpenSSLKeyAgreement extends KeyAgreementSpi {
NativeLibraryLoader.load();
}
- public static int AGREEMENT_DH = 0;
- public static int AGREEMENT_ECDH = 1;
+ public static final int AGREEMENT_DH = 0;
+ public static final int AGREEMENT_ECDH = 1;
enum State { UNINITIALIZED, INITIALIZED, PEER_KEY_ADDED };
private State state = State.UNINITIALIZED;
@@ -66,15 +67,22 @@ public void run() {
private static Cleaner cleaner = NativeMemoryCleaner.cleaner;
private Cleaner.Cleanable cleanable;
- protected Key engineDoPhase(Key key, boolean lastPhase) {
+ protected Key engineDoPhase(Key key, boolean lastPhase) throws InvalidKeyException {
if (state == State.UNINITIALIZED) {
throw new IllegalStateException("The KeyAgreement is not initialized yet");
}
+ if (key == null) {
+ throw new InvalidKeyException("Key must not be null");
+ }
byte[] encoded = key.getEncoded();
if (encoded == null) {
- throw new IllegalArgumentException("Key does not support encoding");
+ throw new InvalidKeyException("Key does not support encoding");
+ }
+ try {
+ engineDoPhase0(encoded);
+ } finally {
+ Arrays.fill(encoded, (byte) 0);
}
- engineDoPhase0(encoded);
state = State.PEER_KEY_ADDED;
return null;
}
@@ -87,8 +95,12 @@ protected byte[] engineGenerateSecret() {
protected int engineGenerateSecret(byte[] sharedSecret, int offset) {
byte[] secret = engineGenerateSecret();
- System.arraycopy(secret, 0, sharedSecret, offset, secret.length);
- return secret.length;
+ try {
+ System.arraycopy(secret, 0, sharedSecret, offset, secret.length);
+ return secret.length;
+ } finally {
+ Arrays.fill(secret, (byte)0);
+ }
}
protected SecretKey engineGenerateSecret(String algorithm) throws NoSuchAlgorithmException, InvalidKeyException {
@@ -100,12 +112,18 @@ protected SecretKey engineGenerateSecret(String algorithm) throws NoSuchAlgorith
}
}
- protected void engineInit(Key key, AlgorithmParameterSpec params, SecureRandom random) {
- // TODO: ignore random for now, does DH or ECDH use any kind of randomness?
- throw new UnsupportedOperationException ("prototype: KeyAgreement.init() with AlgorithmParameterSpec is unsupported");
+ protected void engineInit(Key key, AlgorithmParameterSpec params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException(
+ "AlgorithmParameterSpec is not supported; the key's own parameters are used");
+ }
+ engineInit(key, random);
}
- protected void engineInit(Key key, SecureRandom random) {
+ protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException {
+ if (key == null) {
+ throw new InvalidKeyException("Key must not be null");
+ }
// This is needed if the KeyAgreement is reused.
if (cleanable != null) {
cleanable.clean();
diff --git a/src/main/java/com/canonical/openssl/keyencapsulation/OpenSSLKEMRSA.java b/src/main/java/com/canonical/openssl/keyencapsulation/OpenSSLKEMRSA.java
index a758232..4e2116a 100644
--- a/src/main/java/com/canonical/openssl/keyencapsulation/OpenSSLKEMRSA.java
+++ b/src/main/java/com/canonical/openssl/keyencapsulation/OpenSSLKEMRSA.java
@@ -164,7 +164,11 @@ public RSAKEMDecapsulator(PrivateKey key) throws InvalidKeyException {
if (encoded == null) {
throw new InvalidKeyException("Key does not support encoding");
}
- nativeHandle = decapsulatorInit0(encoded);
+ try {
+ nativeHandle = decapsulatorInit0(encoded);
+ } 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");
diff --git a/src/main/java/com/canonical/openssl/keypairgenerator/DHKeyPairGenerator.java b/src/main/java/com/canonical/openssl/keypairgenerator/DHKeyPairGenerator.java
new file mode 100644
index 0000000..1f0cafa
--- /dev/null
+++ b/src/main/java/com/canonical/openssl/keypairgenerator/DHKeyPairGenerator.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+package com.canonical.openssl.keypairgenerator;
+
+/* groupFromSpec is intentionally not overridden: the standard JCA DHParameterSpec
+ * carries explicit (p, g) primes, and accepting those would let callers bypass
+ * the FIPS-approved FFDHE named groups. Callers should use initialize(keysize). */
+public final class DHKeyPairGenerator extends OpenSSLKeyPairGenerator {
+ public DHKeyPairGenerator() {
+ super("ffdhe2048");
+ }
+
+ @Override
+ protected String getAlgorithmName() {
+ return "DH";
+ }
+
+ @Override
+ protected String mapKeysizeToGroup(int keysize) {
+ switch (keysize) {
+ case 2048: return "ffdhe2048";
+ case 3072: return "ffdhe3072";
+ case 4096: return "ffdhe4096";
+ case 6144: return "ffdhe6144";
+ case 8192: return "ffdhe8192";
+ default: return null;
+ }
+ }
+}
diff --git a/src/main/java/com/canonical/openssl/keypairgenerator/ECKeyPairGenerator.java b/src/main/java/com/canonical/openssl/keypairgenerator/ECKeyPairGenerator.java
new file mode 100644
index 0000000..83a2d49
--- /dev/null
+++ b/src/main/java/com/canonical/openssl/keypairgenerator/ECKeyPairGenerator.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+package com.canonical.openssl.keypairgenerator;
+
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECGenParameterSpec;
+
+public final class ECKeyPairGenerator extends OpenSSLKeyPairGenerator {
+ public ECKeyPairGenerator() {
+ super("prime256v1");
+ }
+
+ @Override
+ protected String getAlgorithmName() {
+ return "EC";
+ }
+
+ @Override
+ protected String mapKeysizeToGroup(int keysize) {
+ switch (keysize) {
+ case 256: return "prime256v1";
+ case 384: return "secp384r1";
+ case 521: return "secp521r1";
+ default: return null;
+ }
+ }
+
+ @Override
+ protected String groupFromSpec(AlgorithmParameterSpec params) {
+ if (!(params instanceof ECGenParameterSpec ec)) {
+ return null;
+ }
+ String name = ec.getName();
+ if (name == null) {
+ return null;
+ }
+ switch (name) {
+ case "prime256v1":
+ case "secp256r1":
+ case "P-256":
+ case "NIST P-256":
+ return "prime256v1";
+ case "secp384r1":
+ case "P-384":
+ case "NIST P-384":
+ return "secp384r1";
+ case "secp521r1":
+ case "P-521":
+ case "NIST P-521":
+ return "secp521r1";
+ default:
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/com/canonical/openssl/keypairgenerator/EncodedPrivateKey.java b/src/main/java/com/canonical/openssl/keypairgenerator/EncodedPrivateKey.java
new file mode 100644
index 0000000..427cc10
--- /dev/null
+++ b/src/main/java/com/canonical/openssl/keypairgenerator/EncodedPrivateKey.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+package com.canonical.openssl.keypairgenerator;
+
+import java.security.PrivateKey;
+import java.util.Arrays;
+import javax.security.auth.Destroyable;
+
+final class EncodedPrivateKey implements PrivateKey, Destroyable {
+ private static final long serialVersionUID = 1L;
+
+ private final String algorithm;
+ private final byte[] encoded;
+ private volatile boolean destroyed = false;
+
+ EncodedPrivateKey(String algorithm, byte[] encoded) {
+ this.algorithm = algorithm;
+ this.encoded = encoded.clone();
+ }
+
+ private void checkDestroyed() {
+ if (destroyed) {
+ throw new IllegalStateException("EncodedPrivateKey has been destroyed");
+ }
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return algorithm;
+ }
+
+ @Override
+ public String getFormat() {
+ checkDestroyed();
+ return "PKCS#8";
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ checkDestroyed();
+ return encoded.clone();
+ }
+
+ @Override
+ public void destroy() {
+ if (destroyed) {
+ return;
+ }
+ Arrays.fill(encoded, (byte) 0);
+ destroyed = true;
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return destroyed;
+ }
+}
diff --git a/src/main/java/com/canonical/openssl/keypairgenerator/EncodedPublicKey.java b/src/main/java/com/canonical/openssl/keypairgenerator/EncodedPublicKey.java
new file mode 100644
index 0000000..b1eb93b
--- /dev/null
+++ b/src/main/java/com/canonical/openssl/keypairgenerator/EncodedPublicKey.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+package com.canonical.openssl.keypairgenerator;
+
+import java.security.PublicKey;
+
+final class EncodedPublicKey implements PublicKey {
+ private static final long serialVersionUID = 1L;
+
+ private final String algorithm;
+ private final byte[] encoded;
+
+ EncodedPublicKey(String algorithm, byte[] encoded) {
+ this.algorithm = algorithm;
+ this.encoded = encoded.clone();
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return algorithm;
+ }
+
+ @Override
+ public String getFormat() {
+ return "X.509";
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ return encoded.clone();
+ }
+}
diff --git a/src/main/java/com/canonical/openssl/keypairgenerator/OpenSSLKeyPairGenerator.java b/src/main/java/com/canonical/openssl/keypairgenerator/OpenSSLKeyPairGenerator.java
new file mode 100644
index 0000000..315b9e1
--- /dev/null
+++ b/src/main/java/com/canonical/openssl/keypairgenerator/OpenSSLKeyPairGenerator.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+package com.canonical.openssl.keypairgenerator;
+
+import com.canonical.openssl.util.NativeLibraryLoader;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGeneratorSpi;
+import java.security.PrivateKey;
+import java.security.ProviderException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Arrays;
+
+abstract public class OpenSSLKeyPairGenerator extends KeyPairGeneratorSpi {
+ static {
+ NativeLibraryLoader.load();
+ }
+
+ private String group;
+
+ protected OpenSSLKeyPairGenerator(String defaultGroup) {
+ this.group = defaultGroup;
+ }
+
+ /**
+ * The {@code random} argument is ignored. Key generation uses OpenSSL's
+ * FIPS-validated DRBG; a caller-supplied {@link SecureRandom} cannot be
+ * substituted without violating the FIPS boundary.
+ */
+ @Override
+ public void initialize(int keysize, SecureRandom random) {
+ String mapped = mapKeysizeToGroup(keysize);
+ if (mapped == null) {
+ throw new IllegalArgumentException(
+ "Unsupported key size " + keysize + " for " + getAlgorithmName());
+ }
+ this.group = mapped;
+ }
+
+ /**
+ * Accepts only specs that resolve to a FIPS-approved named group. The
+ * {@code random} argument is ignored; see {@link #initialize(int, SecureRandom)}.
+ */
+ @Override
+ public void initialize(AlgorithmParameterSpec params, SecureRandom random)
+ throws InvalidAlgorithmParameterException {
+ String mapped = groupFromSpec(params);
+ if (mapped == null) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported AlgorithmParameterSpec for " + getAlgorithmName()
+ + "; use initialize(keysize) or a FIPS-approved named-group spec");
+ }
+ this.group = mapped;
+ }
+
+ @Override
+ public KeyPair generateKeyPair() {
+ byte[][] encoded = generateKeyPair0(getAlgorithmName(), group);
+ if (encoded == null || encoded.length != 2
+ || encoded[0] == null || encoded[1] == null) {
+ throw new ProviderException(
+ "Provider failed to generate " + getAlgorithmName() + " key pair");
+ }
+ PrivateKey priv = new EncodedPrivateKey(getAlgorithmName(), encoded[0]);
+ Arrays.fill(encoded[0], (byte) 0);
+ PublicKey pub = new EncodedPublicKey(getAlgorithmName(), encoded[1]);
+ return new KeyPair(pub, priv);
+ }
+
+ protected abstract String getAlgorithmName();
+
+ /** Returns the named group for the given keysize, or null if unsupported. */
+ protected abstract String mapKeysizeToGroup(int keysize);
+
+ /** Returns the named group for the given spec, or null if unsupported. */
+ protected String groupFromSpec(AlgorithmParameterSpec params) {
+ return null;
+ }
+
+ private static native byte[][] generateKeyPair0(String algorithm, String group);
+}
diff --git a/src/main/java/com/canonical/openssl/mac/CMACwithAes256CBC.java b/src/main/java/com/canonical/openssl/mac/CMACwithAes256CBC.java
index 451bc1a..650d5ba 100644
--- a/src/main/java/com/canonical/openssl/mac/CMACwithAes256CBC.java
+++ b/src/main/java/com/canonical/openssl/mac/CMACwithAes256CBC.java
@@ -16,6 +16,8 @@
*/
package com.canonical.openssl.mac;
+import java.security.spec.AlgorithmParameterSpec;
+
public final class CMACwithAes256CBC extends OpenSSLMAC {
protected String getAlgorithm() {
return "CMAC";
@@ -29,7 +31,11 @@ protected String getDigestType() {
return null;
}
- protected byte[] getIV() {
+ protected byte[] getIV(AlgorithmParameterSpec spec) {
return null;
}
+
+ protected int getDefaultMacLength() {
+ return 16;
+ }
}
diff --git a/src/main/java/com/canonical/openssl/mac/GMACWithAes128GCM.java b/src/main/java/com/canonical/openssl/mac/GMACWithAes128GCM.java
index 6aef10c..806cd80 100644
--- a/src/main/java/com/canonical/openssl/mac/GMACWithAes128GCM.java
+++ b/src/main/java/com/canonical/openssl/mac/GMACWithAes128GCM.java
@@ -16,7 +16,15 @@
*/
package com.canonical.openssl.mac;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.spec.AlgorithmParameterSpec;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.IvParameterSpec;
+
public final class GMACWithAes128GCM extends OpenSSLMAC {
+
+ private static final int NONCE_LEN = 12;
+
protected String getAlgorithm() {
return "GMAC";
}
@@ -29,10 +37,24 @@ protected String getDigestType() {
return null;
}
- // TODO: a random IV?
- protected byte[] getIV() {
- return new byte[] { (byte)0xe0, (byte)0xe0, (byte)0x0f, (byte)0x19,
- (byte)0xfe, (byte)0xd7, (byte)0xba, (byte)0x01,
- (byte)0x36, (byte)0xa7, (byte)0x97, (byte)0xf3 };
+ protected byte[] getIV(AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException {
+ byte[] iv;
+ if (spec instanceof IvParameterSpec ivSpec) {
+ iv = ivSpec.getIV();
+ } else if (spec instanceof GCMParameterSpec gcmSpec) {
+ iv = gcmSpec.getIV();
+ } else {
+ throw new InvalidAlgorithmParameterException(
+ "GMAC requires an IvParameterSpec or GCMParameterSpec carrying a " + NONCE_LEN + "-byte nonce");
+ }
+ if (iv == null || iv.length != NONCE_LEN) {
+ throw new InvalidAlgorithmParameterException(
+ "GMAC nonce must be exactly " + NONCE_LEN + " bytes");
+ }
+ return iv.clone();
+ }
+
+ protected int getDefaultMacLength() {
+ return 16;
}
}
diff --git a/src/main/java/com/canonical/openssl/mac/HMACwithSHA1.java b/src/main/java/com/canonical/openssl/mac/HMACwithSHA1.java
index 78a0e6e..0a36ef9 100644
--- a/src/main/java/com/canonical/openssl/mac/HMACwithSHA1.java
+++ b/src/main/java/com/canonical/openssl/mac/HMACwithSHA1.java
@@ -16,6 +16,8 @@
*/
package com.canonical.openssl.mac;
+import java.security.spec.AlgorithmParameterSpec;
+
public final class HMACwithSHA1 extends OpenSSLMAC {
protected String getAlgorithm() {
return "HMAC";
@@ -29,8 +31,12 @@ protected String getDigestType() {
return "SHA1";
}
- protected byte[] getIV() {
- return null;
+ protected byte[] getIV(AlgorithmParameterSpec spec) {
+ return null;
+ }
+
+ protected int getDefaultMacLength() {
+ return 20;
}
}
diff --git a/src/main/java/com/canonical/openssl/mac/HMACwithSHA3_512.java b/src/main/java/com/canonical/openssl/mac/HMACwithSHA3_512.java
index f829439..9c5b4cd 100644
--- a/src/main/java/com/canonical/openssl/mac/HMACwithSHA3_512.java
+++ b/src/main/java/com/canonical/openssl/mac/HMACwithSHA3_512.java
@@ -16,6 +16,8 @@
*/
package com.canonical.openssl.mac;
+import java.security.spec.AlgorithmParameterSpec;
+
public final class HMACwithSHA3_512 extends OpenSSLMAC {
protected String getAlgorithm() {
return "HMAC";
@@ -29,8 +31,12 @@ protected String getDigestType() {
return "SHA3-512";
}
- protected byte[] getIV() {
+ protected byte[] getIV(AlgorithmParameterSpec spec) {
return null;
}
+
+ protected int getDefaultMacLength() {
+ return 64;
+ }
}
diff --git a/src/main/java/com/canonical/openssl/mac/KMAC128.java b/src/main/java/com/canonical/openssl/mac/KMAC128.java
index 9c2f22f..4e42cff 100644
--- a/src/main/java/com/canonical/openssl/mac/KMAC128.java
+++ b/src/main/java/com/canonical/openssl/mac/KMAC128.java
@@ -16,6 +16,8 @@
*/
package com.canonical.openssl.mac;
+import java.security.spec.AlgorithmParameterSpec;
+
public final class KMAC128 extends OpenSSLMAC {
protected String getAlgorithm() {
return "KMAC-128";
@@ -29,8 +31,12 @@ protected String getDigestType() {
return null;
}
- protected byte[] getIV() {
- return null;
+ protected byte[] getIV(AlgorithmParameterSpec spec) {
+ return null;
+ }
+
+ protected int getDefaultMacLength() {
+ return 32;
}
}
diff --git a/src/main/java/com/canonical/openssl/mac/KMAC256.java b/src/main/java/com/canonical/openssl/mac/KMAC256.java
index 2d8b6db..07e13ed 100644
--- a/src/main/java/com/canonical/openssl/mac/KMAC256.java
+++ b/src/main/java/com/canonical/openssl/mac/KMAC256.java
@@ -16,6 +16,8 @@
*/
package com.canonical.openssl.mac;
+import java.security.spec.AlgorithmParameterSpec;
+
public final class KMAC256 extends OpenSSLMAC {
protected String getAlgorithm() {
return "KMAC-256";
@@ -29,8 +31,12 @@ protected String getDigestType() {
return null;
}
- protected byte[] getIV() {
+ protected byte[] getIV(AlgorithmParameterSpec spec) {
return null;
}
+
+ protected int getDefaultMacLength() {
+ return 64;
+ }
}
diff --git a/src/main/java/com/canonical/openssl/mac/OpenSSLMAC.java b/src/main/java/com/canonical/openssl/mac/OpenSSLMAC.java
index a4b9412..b3c16c3 100644
--- a/src/main/java/com/canonical/openssl/mac/OpenSSLMAC.java
+++ b/src/main/java/com/canonical/openssl/mac/OpenSSLMAC.java
@@ -21,9 +21,11 @@
import java.lang.ref.Cleaner;
import java.util.concurrent.atomic.AtomicLong;
import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
import java.security.Key;
import java.util.Arrays;
import javax.crypto.MacSpi;
+import java.security.InvalidAlgorithmParameterException;
import java.security.spec.AlgorithmParameterSpec;
import javax.xml.crypto.dsig.spec.HMACParameterSpec;
@@ -74,36 +76,52 @@ public void run() {
protected abstract String getAlgorithm();
protected abstract String getCipherType();
protected abstract String getDigestType();
- protected abstract byte[] getIV();
+ protected abstract byte[] getIV(AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException;
+ protected abstract int getDefaultMacLength();
private static Cleaner cleaner = NativeMemoryCleaner.cleaner;
private MACState macState;
private Cleaner.Cleanable cleanable;
private int outputLength = -1;
private byte[] keyBytes;
+ private byte[] cachedIV;
@Override
protected byte[] engineDoFinal() {
+ if (nativeHandle == 0) {
+ throw new IllegalStateException("MAC not initialized");
+ }
return doFinal0();
}
@Override
protected int engineGetMacLength() {
+ if (nativeHandle == 0) {
+ return outputLength > 0 ? outputLength : getDefaultMacLength();
+ }
return getMacLength();
}
@Override
- protected void engineInit(Key key, AlgorithmParameterSpec spec) {
+ protected void engineInit(Key key, AlgorithmParameterSpec spec) throws InvalidKeyException, InvalidAlgorithmParameterException {
+ if (key == null) {
+ throw new InvalidKeyException("Key must not be null");
+ }
if (spec != null && isHMAC(this) && spec instanceof HMACParameterSpec hmacSpec) {
this.outputLength = hmacSpec.getOutputLength();
}
byte[] newKeyBytes = key.getEncoded();
+ if (newKeyBytes == null) {
+ throw new InvalidKeyException("Key does not support encoding");
+ }
+ byte[] iv = getIV(spec);
// clean() zeros the old keyBytes array (held by the old MACState) and frees the old handle
if (cleanable != null) {
cleanable.clean();
}
this.keyBytes = newKeyBytes;
- nativeHandle = doInit0(getAlgorithm(), getCipherType(), getDigestType(), getIV(), outputLength, keyBytes);
+ this.cachedIV = iv;
+ nativeHandle = doInit0(getAlgorithm(), getCipherType(), getDigestType(), iv, outputLength, keyBytes);
macState = new MACState(nativeHandle);
macState.setKeyBytes(keyBytes);
cleanable = cleaner.register(this, macState);
@@ -111,12 +129,15 @@ protected void engineInit(Key key, AlgorithmParameterSpec spec) {
@Override
protected void engineReset() {
+ if (keyBytes == null) {
+ throw new IllegalStateException("MAC not initialized");
+ }
if (cleanable != null) {
// Suppress keyBytes zeroing: we still need them for the doInit0 call below
macState.setKeyBytes(null);
cleanable.clean();
}
- nativeHandle = doInit0(getAlgorithm(), getCipherType(), getDigestType(), getIV(), this.outputLength, keyBytes);
+ nativeHandle = doInit0(getAlgorithm(), getCipherType(), getDigestType(), this.cachedIV, this.outputLength, keyBytes);
macState = new MACState(nativeHandle);
macState.setKeyBytes(keyBytes);
cleanable = cleaner.register(this, macState);
@@ -134,10 +155,19 @@ protected void engineUpdate(byte[] input, int offset, int length) {
@Override
protected void engineUpdate(ByteBuffer buffer) {
- engineUpdate(buffer.array());
+ int remaining = buffer.remaining();
+ if (remaining <= 0) {
+ return;
+ }
+ byte[] chunk = new byte[remaining];
+ buffer.get(chunk);
+ engineUpdate(chunk);
}
private void engineUpdate(byte[] input) {
+ if (nativeHandle == 0) {
+ throw new IllegalStateException("MAC not initialized");
+ }
doUpdate0(input);
}
diff --git a/src/main/java/com/canonical/openssl/md/OpenSSLMD.java b/src/main/java/com/canonical/openssl/md/OpenSSLMD.java
index 27359cf..ca1fcde 100644
--- a/src/main/java/com/canonical/openssl/md/OpenSSLMD.java
+++ b/src/main/java/com/canonical/openssl/md/OpenSSLMD.java
@@ -110,7 +110,13 @@ protected void engineUpdate(byte []input, int offset, int len) {
@Override
protected void engineUpdate(ByteBuffer data) {
- engineUpdate(data.array());
+ int remaining = data.remaining();
+ if (remaining <= 0) {
+ return;
+ }
+ byte[] chunk = new byte[remaining];
+ data.get(chunk);
+ engineUpdate(chunk);
}
private void engineUpdate(byte[] data) {
diff --git a/src/main/java/com/canonical/openssl/provider/OpenSSLFIPSProvider.java b/src/main/java/com/canonical/openssl/provider/OpenSSLFIPSProvider.java
index eb14bd1..501090e 100644
--- a/src/main/java/com/canonical/openssl/provider/OpenSSLFIPSProvider.java
+++ b/src/main/java/com/canonical/openssl/provider/OpenSSLFIPSProvider.java
@@ -31,6 +31,10 @@ public OpenSSLFIPSProvider() {
put("KeyAgreement.DH", "com.canonical.openssl.keyagreement.DHKeyAgreement");
put("KeyAgreement.ECDH", "com.canonical.openssl.keyagreement.ECDHKeyAgreement");
+ // Key Pair Generators (FIPS-approved named groups only)
+ put("KeyPairGenerator.DH", "com.canonical.openssl.keypairgenerator.DHKeyPairGenerator");
+ put("KeyPairGenerator.EC", "com.canonical.openssl.keypairgenerator.ECKeyPairGenerator");
+
// Key Encapsulation
put("KEM.RSA", "com.canonical.openssl.keyencapsulation.OpenSSLKEMRSA");
diff --git a/src/main/java/com/canonical/openssl/signature/OpenSSLSignature.java b/src/main/java/com/canonical/openssl/signature/OpenSSLSignature.java
index 43a0f5e..b083026 100644
--- a/src/main/java/com/canonical/openssl/signature/OpenSSLSignature.java
+++ b/src/main/java/com/canonical/openssl/signature/OpenSSLSignature.java
@@ -24,8 +24,14 @@
import java.util.concurrent.atomic.AtomicLong;
import java.nio.ByteBuffer;
import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PSSParameterSpec;
import java.security.AlgorithmParameters;
+import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidParameterSpecException;
+import java.security.InvalidParameterException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.PublicKey;
@@ -108,8 +114,7 @@ protected OpenSSLSignature(Params params) {
@Override
protected Object engineGetParameter(String param) {
- // TODO
- throw new UnsupportedOperationException();
+ throw new InvalidParameterException("Legacy getParameter(String) is not supported; use getParameters()");
}
@Override
@@ -124,6 +129,9 @@ protected void engineSetParameter(String param, Object value) {
@Override
protected void engineInitSign(PrivateKey key) throws InvalidKeyException {
+ if (key == null) {
+ throw new InvalidKeyException("Key must not be null");
+ }
if (key instanceof OpenSSLPrivateKey privKey) {
if (cleanable != null) {
cleanable.clean();
@@ -143,6 +151,9 @@ protected void engineInitSign(PrivateKey key, SecureRandom random) throws Invali
@Override
protected void engineInitVerify(PublicKey key) throws InvalidKeyException {
+ if (key == null) {
+ throw new InvalidKeyException("Key must not be null");
+ }
if (key instanceof OpenSSLPublicKey pubKey) {
if (cleanable != null) {
cleanable.clean();
@@ -156,18 +167,49 @@ protected void engineInitVerify(PublicKey key) throws InvalidKeyException {
@Override
protected AlgorithmParameters engineGetParameters() {
- // TODO
- throw new UnsupportedOperationException();
+ if (params == null || params.padding != Params.PSS_PADDING) {
+ return null;
+ }
+ String mgf1Digest = (params.mgf1Digest != null) ? params.mgf1Digest : params.digest;
+ if (mgf1Digest == null) {
+ throw new ProviderException("PSS padding is set but no digest has been configured");
+ }
+ try {
+ AlgorithmParameters ap = AlgorithmParameters.getInstance("RSASSA-PSS");
+ ap.init(new PSSParameterSpec(params.digest, "MGF1", new MGF1ParameterSpec(mgf1Digest), params.saltLength, 1));
+ return ap;
+ } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) {
+ throw new ProviderException("Could not encode PSS AlgorithmParameters", e);
+ }
}
@Override
- protected void engineSetParameter(AlgorithmParameterSpec params) {
- // TODO
- throw new UnsupportedOperationException();
+ protected void engineSetParameter(AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException {
+ if (params == null) {
+ throw new InvalidAlgorithmParameterException("AlgorithmParameterSpec must not be null");
+ }
+ if (!(params instanceof PSSParameterSpec pss)) {
+ throw new InvalidAlgorithmParameterException("Only PSSParameterSpec is supported, got: " + params.getClass().getName());
+ }
+ if (!"MGF1".equalsIgnoreCase(pss.getMGFAlgorithm())) {
+ throw new InvalidAlgorithmParameterException("Only MGF1 is supported for PSS MGF, got: " + pss.getMGFAlgorithm());
+ }
+ if (pss.getTrailerField() != 1) {
+ throw new InvalidAlgorithmParameterException("Only PSS trailerField=1 is supported, got: " + pss.getTrailerField());
+ }
+ AlgorithmParameterSpec mgfSpec = pss.getMGFParameters();
+ if (mgfSpec != null && !(mgfSpec instanceof MGF1ParameterSpec)) {
+ throw new InvalidAlgorithmParameterException("Only MGF1 is supported for PSS MGF, got: " + mgfSpec.getClass().getName());
+ }
+ String mgf1Digest = (mgfSpec != null) ? ((MGF1ParameterSpec) mgfSpec).getDigestAlgorithm() : null;
+ this.params = new Params(pss.getDigestAlgorithm(), pss.getSaltLength(), Padding.PSS, mgf1Digest);
}
@Override
protected byte[] engineSign() {
+ if (nativeHandle == 0) {
+ throw new IllegalStateException("Signature not initialized");
+ }
return engineSign0();
}
@@ -188,6 +230,15 @@ protected void engineUpdate(byte b) throws SignatureException {
@Override
protected void engineUpdate(byte[] b, int off, int len) throws SignatureException {
+ if (nativeHandle == 0) {
+ throw new IllegalStateException("Signature not initialized");
+ }
+ if (b == null) {
+ throw new NullPointerException("input array must not be null");
+ }
+ if (off < 0 || len < 0 || off > b.length - len) {
+ throw new IllegalArgumentException("Invalid offset/length");
+ }
engineUpdate0(b, off, len);
}
@@ -217,6 +268,15 @@ protected boolean engineVerify(byte[] sigBytes) {
@Override
protected boolean engineVerify(byte[] sigBytes, int offset, int length) {
+ if (nativeHandle == 0) {
+ throw new IllegalStateException("Signature not initialized");
+ }
+ if (sigBytes == null) {
+ throw new NullPointerException("signature bytes must not be null");
+ }
+ if (offset < 0 || length < 0 || offset > sigBytes.length - length) {
+ throw new IllegalArgumentException("Invalid offset/length");
+ }
return engineVerify0(sigBytes, offset, length);
}
diff --git a/src/main/java/com/canonical/openssl/util/NativeMemoryCleaner.java b/src/main/java/com/canonical/openssl/util/NativeMemoryCleaner.java
index fef08b3..93381b0 100644
--- a/src/main/java/com/canonical/openssl/util/NativeMemoryCleaner.java
+++ b/src/main/java/com/canonical/openssl/util/NativeMemoryCleaner.java
@@ -23,5 +23,5 @@ public class NativeMemoryCleaner {
* up by this cleaner. We do not want to have multiple Cleaners since
* every cleaner spawns its own daemon thread.
*/
- public static Cleaner cleaner = Cleaner.create();
+ public static final Cleaner cleaner = Cleaner.create();
}
diff --git a/src/main/native/c/KeyConverter.c b/src/main/native/c/KeyConverter.c
index af06157..68e718e 100644
--- a/src/main/native/c/KeyConverter.c
+++ b/src/main/native/c/KeyConverter.c
@@ -20,8 +20,6 @@
#include "evp_utils.h"
#include "jni_utils.h"
-extern OSSL_LIB_CTX *global_libctx;
-
/*
* Class: com_canonical_openssl_key_KeyConverter
* Method: privateKeyToEVPKey0
@@ -45,7 +43,7 @@ JNIEXPORT jlong JNICALL Java_com_canonical_openssl_key_KeyConverter_privateKeyTo
}
/* Use FIPS-safe decoder to convert DER to EVP_PKEY */
- EVP_PKEY *pkey = decode_private_key_fips(bytes, (size_t)length, global_libctx);
+ EVP_PKEY *pkey = decode_private_key_fips(bytes, (size_t)length, jssl_libctx());
(*env)->ReleaseByteArrayElements(env, encodedKey, (jbyte*)bytes, JNI_ABORT);
@@ -75,7 +73,7 @@ JNIEXPORT jlong JNICALL Java_com_canonical_openssl_key_KeyConverter_publicKeyToE
}
/* Use FIPS-safe decoder to convert DER to EVP_PKEY */
- EVP_PKEY *pkey = decode_public_key_fips(bytes, (size_t)length, global_libctx);
+ EVP_PKEY *pkey = decode_public_key_fips(bytes, (size_t)length, jssl_libctx());
(*env)->ReleaseByteArrayElements(env, encodedKey, (jbyte*)bytes, JNI_ABORT);
diff --git a/src/main/native/c/OpenSSLCipher.c b/src/main/native/c/OpenSSLCipher.c
index 36d79e5..ce34e04 100644
--- a/src/main/native/c/OpenSSLCipher.c
+++ b/src/main/native/c/OpenSSLCipher.c
@@ -15,27 +15,44 @@
*
*/
#include
+#include
#include "jssl.h"
#include "cipher.h"
#include "jni_utils.h"
#include "OpenSSLCipher.h"
#define LARGE_SIZE 1024
-extern OSSL_LIB_CTX *global_libctx;
JNIEXPORT jlong JNICALL Java_com_canonical_openssl_cipher_OpenSSLCipher_createContext0
(JNIEnv *env, jobject this, jstring name, jstring padding) {
- const char *namestr = (*env)->GetStringUTFChars(env, name, 0);
- const char *paddingstr = (*env)->GetStringUTFChars(env, padding, 0);
- jlong handle = (jlong) create_cipher_context(global_libctx, namestr, paddingstr);
- (*env)->ReleaseStringUTFChars(env, name, namestr);
- (*env)->ReleaseStringUTFChars(env, padding, paddingstr);
+ const char *namestr = NULL;
+ const char *paddingstr = NULL;
+ jlong handle = 0;
+
+ namestr = (*env)->GetStringUTFChars(env, name, 0);
+ if (namestr == NULL) {
+ goto cleanup;
+ }
+ paddingstr = (*env)->GetStringUTFChars(env, padding, 0);
+ if (paddingstr == NULL) {
+ goto cleanup;
+ }
+ handle = (jlong) create_cipher_context(jssl_libctx(), namestr, paddingstr);
+
+cleanup:
+ if (namestr) (*env)->ReleaseStringUTFChars(env, name, namestr);
+ if (paddingstr) (*env)->ReleaseStringUTFChars(env, padding, paddingstr);
return handle;
}
JNIEXPORT void JNICALL Java_com_canonical_openssl_cipher_OpenSSLCipher_doInit0
(JNIEnv *env, jobject this, jbyteArray input, jint offset, jint length, jbyteArray key, jbyteArray iv, jint opmode) {
+ if (offset < 0 || length < 0) {
+ throwIllegalArgument(env, "offset and length must be non-negative");
+ return;
+ }
+
jclass clazz = (*env)->GetObjectClass(env, this);
jfieldID ctx_id = (*env)->GetFieldID(env, clazz, "cipherContext", "J");
jlong ctx_handle = (*env)->GetLongField(env, this, ctx_id);
@@ -60,6 +77,9 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_cipher_OpenSSLCipher_doInit0
key_length = (*env)->GetArrayLength(env, key);
key_bytes = (unsigned char *) (*env)->GetByteArrayElements(env, key, NULL);
+ if (key_bytes == NULL) {
+ goto cleanup;
+ }
key_copy = (unsigned char *) malloc(key_length);
if (key_copy == NULL) {
throwOOM(env, "Could not allocate memory for the key");
@@ -67,16 +87,21 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_cipher_OpenSSLCipher_doInit0
}
memcpy(key_copy, key_bytes, key_length);
- iv_length = (*env)->GetArrayLength(env, iv);
- iv_bytes = (unsigned char *) (*env)->GetByteArrayElements(env, iv, NULL);
- iv_copy = (unsigned char *) malloc(iv_length);
- if (iv_copy == NULL) {
- throwOOM(env, "Could not allocate memory for the initialization vector");
- goto cleanup;
+ if (iv != NULL) {
+ iv_length = (*env)->GetArrayLength(env, iv);
+ iv_bytes = (unsigned char *) (*env)->GetByteArrayElements(env, iv, NULL);
+ if (iv_bytes == NULL) {
+ goto cleanup;
+ }
+ iv_copy = (unsigned char *) malloc(iv_length);
+ if (iv_copy == NULL) {
+ throwOOM(env, "Could not allocate memory for the initialization vector");
+ goto cleanup;
+ }
+ memcpy(iv_copy, iv_bytes, iv_length);
}
- memcpy(iv_copy, iv_bytes, iv_length);
- rc = cipher_init((cipher_context*)ctx_handle, input_bytes, length, key_copy, key_length, iv_copy, iv_length, opmode);
+ rc = cipher_init((cipher_context*)ctx_handle, (byte *)input_bytes, length, key_copy, key_length, iv_copy, iv_length, opmode);
switch (rc) {
case FAIL_OOM:
@@ -99,90 +124,111 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_cipher_OpenSSLCipher_doInit0
JNIEXPORT jbyteArray JNICALL Java_com_canonical_openssl_cipher_OpenSSLCipher_doUpdate0
(JNIEnv *env, jobject this, jbyteArray input, jint offset, jint length) {
+ jbyte *input_bytes = NULL;
byte *output_bytes = NULL;
int output_length = 0;
+ jbyteArray ret_array = NULL;
+
+ if (length < 0 || length > INT_MAX - MAX_BLOCK_LENGTH) {
+ (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/IllegalArgumentException"),
+ "input length out of range");
+ return NULL;
+ }
jclass clazz = (*env)->GetObjectClass(env, this);
jfieldID ctx_id = (*env)->GetFieldID(env, clazz, "cipherContext", "J");
jlong ctx_handle = (*env)->GetLongField(env, this, ctx_id);
- jbyte *input_bytes = (jbyte *)malloc(length);
+ input_bytes = (jbyte *)malloc(length);
if (input_bytes == NULL) {
throwOOM(env, "Could not allocate input buffer");
- return NULL;
+ goto cleanup;
}
(*env)->GetByteArrayRegion(env, input, offset, length, input_bytes);
output_bytes = (byte *)malloc(length + MAX_BLOCK_LENGTH);
if (output_bytes == NULL) {
throwOOM(env, "Could not allocate output buffer");
- return NULL;
+ goto cleanup;
}
- jssl_status rc = cipher_update((cipher_context*)ctx_handle, output_bytes, &output_length, input_bytes, length);
+ jssl_status rc = cipher_update((cipher_context*)ctx_handle, output_bytes, &output_length, (byte *)input_bytes, length);
if (rc == FAIL_EVP) {
+ throwProviderException(env, "Cipher update failed");
+ goto cleanup;
+ }
+
+ ret_array = (*env)->NewByteArray(env, output_length);
+ if (ret_array == NULL) {
+ goto cleanup;
+ }
+ (*env)->SetByteArrayRegion(env, ret_array, 0, output_length, (const jbyte *)output_bytes);
+
+cleanup:
+ if (input_bytes) {
memset(input_bytes, 0, length);
- memset(output_bytes, 0, output_length);
free(input_bytes);
+ }
+ if (output_bytes) {
+ memset(output_bytes, 0, output_length);
free(output_bytes);
- throwProviderException(env, "Cipher update failed");
- return NULL;
}
-
- jbyteArray ret_array = (*env)->NewByteArray(env, output_length);
- (*env)->SetByteArrayRegion(env, ret_array, 0, output_length, output_bytes);
- memset(input_bytes, 0, length);
- memset(output_bytes, 0, output_length);
- free(input_bytes);
- free(output_bytes);
return ret_array;
}
JNIEXPORT jbyteArray JNICALL Java_com_canonical_openssl_cipher_OpenSSLCipher_doFinal0
(JNIEnv *env, jobject this, jbyteArray output, jint length) {
int templen = 0;
+ jbyte *out_bytes = NULL;
+ byte *final_output = NULL;
+ jbyteArray ret_array = NULL;
jclass clazz = (*env)->GetObjectClass(env, this);
jfieldID ctx_id = (*env)->GetFieldID(env, clazz, "cipherContext", "J");
jlong ctx_handle = (*env)->GetLongField(env, this, ctx_id);
- jboolean copy = JNI_FALSE;
- jbyte *out_bytes = (*env)->GetByteArrayElements(env, output, ©);
+ out_bytes = (*env)->GetByteArrayElements(env, output, NULL);
+ if (out_bytes == NULL) {
+ goto cleanup;
+ }
- byte *final_output = (byte *)malloc(length + GCM_TAG_LEN);
+ final_output = (byte *)malloc(length + GCM_TAG_LEN);
if (final_output == NULL) {
throwOOM(env, "Could not allocate output buffer");
- if (copy) {
- (*env)->ReleaseByteArrayElements(env, output, out_bytes, JNI_ABORT);
- }
- return NULL;
+ goto cleanup;
}
memcpy(final_output, out_bytes, length);
jssl_status rc = cipher_do_final((cipher_context*)ctx_handle, final_output + length, &templen);
if (rc == FAIL_EVP) {
- memset(final_output, 0, length + GCM_TAG_LEN);
- free(final_output);
- if (copy) {
- (*env)->ReleaseByteArrayElements(env, output, out_bytes, JNI_ABORT);
- }
throwProviderException(env, "Final update to cipher failed");
- return NULL;
+ goto cleanup;
}
- jbyteArray ret_array = (*env)->NewByteArray(env, length + templen);
- (*env)->SetByteArrayRegion(env, ret_array, 0, length + templen, final_output);
+ ret_array = (*env)->NewByteArray(env, length + templen);
+ if (ret_array == NULL) {
+ goto cleanup;
+ }
+ (*env)->SetByteArrayRegion(env, ret_array, 0, length + templen, (const jbyte *)final_output);
- memset(final_output, 0, length + GCM_TAG_LEN);
- free(final_output);
- if (copy) {
+cleanup:
+ if (final_output) {
+ memset(final_output, 0, length + GCM_TAG_LEN);
+ free(final_output);
+ }
+ if (out_bytes) {
(*env)->ReleaseByteArrayElements(env, output, out_bytes, JNI_ABORT);
}
-
return ret_array;
}
JNIEXPORT void JNICALL Java_com_canonical_openssl_cipher_OpenSSLCipher_setGCMTag0
(JNIEnv *env, jobject this, jbyteArray tag, jint offset, jint len) {
+ if (len < 0 || len > GCM_TAG_LEN) {
+ (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/IllegalArgumentException"),
+ "GCM tag length out of range");
+ return;
+ }
+
jclass clazz = (*env)->GetObjectClass(env, this);
jfieldID ctx_id = (*env)->GetFieldID(env, clazz, "cipherContext", "J");
jlong ctx_handle = (*env)->GetLongField(env, this, ctx_id);
@@ -193,6 +239,11 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_cipher_OpenSSLCipher_setGCMTag
JNIEXPORT void JNICALL Java_com_canonical_openssl_cipher_OpenSSLCipher_updateAAD0
(JNIEnv *env, jobject this, jbyteArray aad, jint offset, jint length) {
+ if (offset < 0 || length < 0) {
+ throwIllegalArgument(env, "offset and length must be non-negative");
+ return;
+ }
+
jclass clazz = (*env)->GetObjectClass(env, this);
jfieldID ctx_id = (*env)->GetFieldID(env, clazz, "cipherContext", "J");
jlong ctx_handle = (*env)->GetLongField(env, this, ctx_id);
@@ -205,7 +256,7 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_cipher_OpenSSLCipher_updateAAD
(*env)->GetByteArrayRegion(env, aad, offset, length, aad_bytes);
int len;
- jssl_status rc = cipher_update_aad((cipher_context*)ctx_handle, &len, aad_bytes, length);
+ jssl_status rc = cipher_update_aad((cipher_context*)ctx_handle, &len, (byte *)aad_bytes, length);
memset(aad_bytes, 0, length);
free(aad_bytes);
@@ -216,5 +267,6 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_cipher_OpenSSLCipher_updateAAD
JNIEXPORT void JNICALL Java_com_canonical_openssl_cipher_OpenSSLCipher_cleanupNativeMemory0
(JNIEnv *env, jclass clazz, jlong handle) {
- free_cipher((cipher_context**) &handle);
+ cipher_context *ctx = (cipher_context*)handle;
+ free_cipher(&ctx);
}
diff --git a/src/main/native/c/OpenSSLDrbg.c b/src/main/native/c/OpenSSLDrbg.c
index 8398551..fbd2df2 100644
--- a/src/main/native/c/OpenSSLDrbg.c
+++ b/src/main/native/c/OpenSSLDrbg.c
@@ -16,6 +16,7 @@
*/
#include "OpenSSLDrbg.h"
#include "drbg.h"
+#include "jssl.h"
#include "jni_utils.h"
/* TODOs
@@ -45,15 +46,23 @@ void populate_params(DRBGParams *params, int strength, int prediction_resistance
*/
JNIEXPORT jlong JNICALL Java_com_canonical_openssl_drbg_OpenSSLDrbg_init
(JNIEnv *env, jobject this, jstring name, jint strength, jboolean prediction_resistance, jboolean reseeding, jbyteArray personalization_string) {
- const char *name_string = (*env)->GetStringUTFChars(env, name, 0);
+ const char *name_string = NULL;
byte *ps_bytes_native = NULL;
DRBGParams *params = NULL;
DRBG *drbg = NULL;
jsize pstr_length = 0;
- if (personalization_string != NULL) {
+ name_string = (*env)->GetStringUTFChars(env, name, 0);
+ if (name_string == NULL) {
+ goto error;
+ }
+
+ if (personalization_string != NULL) {
pstr_length = (*env)->GetArrayLength(env, personalization_string);
- byte *pstr_bytes = (*env)->GetByteArrayElements(env, personalization_string, NULL);
+ jbyte *pstr_bytes = (*env)->GetByteArrayElements(env, personalization_string, NULL);
+ if (pstr_bytes == NULL) {
+ goto error;
+ }
ps_bytes_native = (byte *)malloc(pstr_length);
if (ps_bytes_native == NULL) {
(*env)->ReleaseByteArrayElements(env, personalization_string, pstr_bytes, JNI_ABORT);
@@ -73,22 +82,22 @@ JNIEXPORT jlong JNICALL Java_com_canonical_openssl_drbg_OpenSSLDrbg_init
ps_bytes_native = NULL; // ownership transferred to params; free_DRBGParams will release it
int evp_error = JNI_FALSE;
- drbg = create_DRBG_with_params(name_string, NULL, params, &evp_error);
- (*env)->ReleaseStringUTFChars(env, name, name_string);
+ drbg = create_DRBG_with_params(jssl_libctx(), name_string, NULL, params, &evp_error);
if (drbg == NULL) {
- throwOOM(env, "Failed to allocate memory for a DRBG");
- goto error;
- }
-
- if (evp_error) {
- throwProviderException(env, "Random instantation failed");
+ if (evp_error) {
+ throwProviderException(env, "DRBG instantiation failed");
+ } else {
+ throwOOM(env, "Failed to allocate memory for a DRBG");
+ }
goto error;
}
+ release_jstring(env, name, name_string);
return (jlong)drbg;
error:
+ release_jstring(env, name, name_string);
if (ps_bytes_native) {
memset(ps_bytes_native, 0, pstr_length);
free(ps_bytes_native);
@@ -101,7 +110,7 @@ JNIEXPORT jlong JNICALL Java_com_canonical_openssl_drbg_OpenSSLDrbg_init
if (drbg) {
free_DRBG(&drbg);
}
- return (jlong)0;
+ return (jlong)0;
}
/*
@@ -129,10 +138,14 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_drbg_OpenSSLDrbg_nextBytes0
if (additional_input != NULL) {
additional_input_length = (*env)->GetArrayLength(env, additional_input);
jbyte *additional_input_bytes = (*env)->GetByteArrayElements(env, additional_input, NULL);
+ if (additional_input_bytes == NULL) {
+ goto cleanup;
+ }
ai_bytes_native = (byte*)malloc(additional_input_length);
if (ai_bytes_native == NULL) {
+ (*env)->ReleaseByteArrayElements(env, additional_input, additional_input_bytes, JNI_ABORT);
throwOOM(env, "Could not allocate memory for additional input");
- goto cleanup;
+ goto cleanup;
}
memcpy(ai_bytes_native, additional_input_bytes, additional_input_length);
(*env)->ReleaseByteArrayElements(env, additional_input, additional_input_bytes, JNI_ABORT);
@@ -146,8 +159,11 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_drbg_OpenSSLDrbg_nextBytes0
populate_params(params, strength, prediction_resistance, 0, NULL, 0, ai_bytes_native, additional_input_length);
ai_bytes_native = NULL; // ownership transferred to params; free_DRBGParams will release it
- next_rand_with_params((DRBG *)drbg_handle, output_bytes, output_bytes_length, params);
- (*env)->SetByteArrayRegion(env, out_bytes, 0, output_bytes_length, output_bytes);
+ if (!next_rand_with_params((DRBG *)drbg_handle, output_bytes, output_bytes_length, params)) {
+ throwProviderException(env, "DRBG generate failed");
+ goto cleanup;
+ }
+ (*env)->SetByteArrayRegion(env, out_bytes, 0, output_bytes_length, (const jbyte *)output_bytes);
cleanup:
if (output_bytes) {
@@ -176,8 +192,10 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_drbg_OpenSSLDrbg_reseed0
byte *ai_bytes = NULL;
jsize ai_length = 0;
jsize input_length = 0;
- byte *input_bytes = NULL;
+ byte *input_copy = NULL;
+ jbyte *input_pinned = NULL;
jbyte *ai_pinned = NULL;
+ DRBGParams *params = NULL;
jclass clazz = (*env)->GetObjectClass(env, this);
jfieldID drbg_id = (*env)->GetFieldID(env, clazz, "drbgContext", "J");
@@ -185,12 +203,24 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_drbg_OpenSSLDrbg_reseed0
if (in_bytes != NULL) {
input_length = (*env)->GetArrayLength(env, in_bytes);
- input_bytes = (*env)->GetByteArrayElements(env, in_bytes, NULL);
+ input_pinned = (*env)->GetByteArrayElements(env, in_bytes, NULL);
+ if (input_pinned == NULL) {
+ goto cleanup;
+ }
+ input_copy = (byte *)malloc(input_length);
+ if (input_copy == NULL) {
+ throwOOM(env, "Could not allocate memory for seed");
+ goto cleanup;
+ }
+ memcpy(input_copy, input_pinned, input_length);
}
if (additional_input != NULL) {
ai_length = (*env)->GetArrayLength(env, additional_input);
ai_pinned = (*env)->GetByteArrayElements(env, additional_input, NULL);
+ if (ai_pinned == NULL) {
+ goto cleanup;
+ }
ai_bytes = (byte *)malloc(ai_length);
if (ai_bytes == NULL) {
throwOOM(env, "Could not allocate memory for additional data");
@@ -199,7 +229,7 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_drbg_OpenSSLDrbg_reseed0
memcpy(ai_bytes, ai_pinned, ai_length);
}
- DRBGParams *params = (DRBGParams *)malloc(sizeof(DRBGParams));
+ params = (DRBGParams *)malloc(sizeof(DRBGParams));
if (params == NULL) {
throwOOM(env, "Could not allocate memory for DRBG params");
goto cleanup;
@@ -208,25 +238,31 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_drbg_OpenSSLDrbg_reseed0
populate_params(params, -1, 0, reseeding, NULL, 0, (byte *)ai_bytes, ai_length);
ai_bytes = NULL; // ownership transferred to params; free_DRBGParams will release it
- if (input_bytes == NULL) {
- int status = reseed_with_params((DRBG*)drbg_handle, params);
- if (FAIL_GETRANDOM == status) {
- throwProviderException(env, "getrandom() failed to generate seed");
- }
+ /* Reseed entropy comes from OpenSSL's FIPS entropy chain, not getrandom(). */
+ jssl_status status;
+ if (input_copy == NULL) {
+ status = reseed_with_params((DRBG*)drbg_handle, params);
} else {
- reseed_with_seed_and_params((DRBG*)drbg_handle, input_bytes, input_length, params);
+ status = reseed_with_seed_and_params((DRBG*)drbg_handle, input_copy, input_length, params);
+ }
+ if (FAIL_EVP == status) {
+ throwProviderException(env, "DRBG reseed failed");
}
cleanup:
if (ai_bytes) {
- memset(ai_bytes, 0, ai_length);
+ OPENSSL_cleanse(ai_bytes, ai_length);
free(ai_bytes);
}
if (ai_pinned) {
(*env)->ReleaseByteArrayElements(env, additional_input, ai_pinned, JNI_ABORT);
}
- if (input_bytes) {
- (*env)->ReleaseByteArrayElements(env, in_bytes, input_bytes, JNI_ABORT);
+ if (input_copy) {
+ OPENSSL_cleanse(input_copy, input_length);
+ free(input_copy);
+ }
+ if (input_pinned) {
+ (*env)->ReleaseByteArrayElements(env, in_bytes, input_pinned, JNI_ABORT);
}
free_DRBGParams(¶ms);
}
@@ -256,15 +292,23 @@ JNIEXPORT jbyteArray JNICALL Java_com_canonical_openssl_drbg_OpenSSLDrbg_generat
byte output[MAX_SEED_BYTES];
- generate_seed((DRBG*)drbg_handle, output, num_bytes);
+ if (!generate_seed((DRBG*)drbg_handle, output, num_bytes)) {
+ OPENSSL_cleanse(output, sizeof(output));
+ throwProviderException(env, "Seed generation failed");
+ return NULL;
+ }
jbyteArray ret_array = (*env)->NewByteArray(env, num_bytes);
- (*env)->SetByteArrayRegion(env, ret_array, 0, num_bytes, output);
+ if (ret_array != NULL) {
+ (*env)->SetByteArrayRegion(env, ret_array, 0, num_bytes, (const jbyte *)output);
+ }
+ OPENSSL_cleanse(output, sizeof(output));
return ret_array;
}
JNIEXPORT void JNICALL Java_com_canonical_openssl_drbg_OpenSSLDrbg_cleanupNativeMemory0
(JNIEnv *env, jclass clazz, jlong handle) {
- free_DRBG((DRBG**)&handle);
+ DRBG *drbg = (DRBG*)handle;
+ free_DRBG(&drbg);
}
diff --git a/src/main/native/c/OpenSSLKeyAgreement.c b/src/main/native/c/OpenSSLKeyAgreement.c
index d41d22a..d392429 100644
--- a/src/main/native/c/OpenSSLKeyAgreement.c
+++ b/src/main/native/c/OpenSSLKeyAgreement.c
@@ -15,31 +15,49 @@
*
*/
#include
+#include
+#include
#include "jssl.h"
#include "keyagreement.h"
#include "OpenSSLKeyAgreement.h"
-#include "evp_utils.h"
#include "jni_utils.h"
-extern OSSL_LIB_CTX *global_libctx;
-
+static int evp_type_for(key_agreement_algorithm algo) {
+ switch (algo) {
+ case DIFFIE_HELLMAN: return EVP_PKEY_DH;
+ case ELLIPTIC_CURVE: return EVP_PKEY_EC;
+ default: return -1;
+ }
+}
/*
* Class: OpenSSLKeyAgreementSpi
* Method: engineInit0
* Signature: (I[B)J
*/
-JNIEXPORT long JNICALL Java_com_canonical_openssl_keyagreement_OpenSSLKeyAgreement_engineInit0
+JNIEXPORT jlong JNICALL Java_com_canonical_openssl_keyagreement_OpenSSLKeyAgreement_engineInit0
(JNIEnv *env, jobject this, jint algo, jbyteArray keyBytes) {
key_agreement_algorithm type = algo;
- key_agreement *agreement = init_key_agreement(type, global_libctx);
+ key_agreement *agreement = init_key_agreement(type, jssl_libctx());
if (agreement == NULL) return 0;
jsize key_length = (*env)->GetArrayLength(env, keyBytes);
jbyte *key_bytes = (*env)->GetByteArrayElements(env, keyBytes, NULL);
- EVP_PKEY *private_key = decode_private_key_fips((byte *)key_bytes, key_length, global_libctx);
+ if (key_bytes == NULL) {
+ free_key_agreement(&agreement);
+ return 0;
+ }
+ const byte *p = (const byte *)key_bytes;
+ EVP_PKEY *private_key = d2i_PrivateKey_ex(evp_type_for(type), NULL,
+ &p, key_length,
+ jssl_libctx(), NULL);
(*env)->ReleaseByteArrayElements(env, keyBytes, key_bytes, JNI_ABORT);
+ if (private_key == NULL) {
+ free_key_agreement(&agreement);
+ throwProviderException(env, "Failed to decode private key for key agreement");
+ return 0;
+ }
set_private_key(agreement, private_key);
- return (long)agreement;
+ return (jlong)agreement;
}
/*
@@ -52,8 +70,17 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_keyagreement_OpenSSLKeyAgreeme
key_agreement *agreement = (key_agreement *)get_long_field(env, this, "nativeHandle");
jsize key_length = (*env)->GetArrayLength(env, keyBytes);
jbyte *key_bytes = (*env)->GetByteArrayElements(env, keyBytes, NULL);
- EVP_PKEY *public_key = decode_public_key_fips((byte *)key_bytes, key_length, global_libctx);
+ if (key_bytes == NULL) {
+ return;
+ }
+ const byte *p = (const byte *)key_bytes;
+ EVP_PKEY *public_key = d2i_PUBKEY_ex(NULL, &p, key_length,
+ jssl_libctx(), NULL);
(*env)->ReleaseByteArrayElements(env, keyBytes, key_bytes, JNI_ABORT);
+ if (public_key == NULL) {
+ throwProviderException(env, "Failed to decode public key for key agreement");
+ return;
+ }
set_peer_key(agreement, public_key);
}
@@ -77,6 +104,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_canonical_openssl_keyagreement_OpenSSLKeyA
}
}
jbyteArray byteArray = byte_array_to_jbyteArray(env, secret->bytes, secret->length);
+ OPENSSL_cleanse(secret->bytes, secret->length);
return byteArray;
}
@@ -87,5 +115,6 @@ JNIEXPORT jbyteArray JNICALL Java_com_canonical_openssl_keyagreement_OpenSSLKeyA
*/
JNIEXPORT void JNICALL Java_com_canonical_openssl_keyagreement_OpenSSLKeyAgreement_cleanupNativeMemory0
(JNIEnv *env, jclass clazz, jlong handle) {
- free_key_agreement((key_agreement**) &handle);
+ key_agreement *agreement = (key_agreement*)handle;
+ free_key_agreement(&agreement);
}
diff --git a/src/main/native/c/OpenSSLKeyPairGenerator.c b/src/main/native/c/OpenSSLKeyPairGenerator.c
new file mode 100644
index 0000000..d15a1d4
--- /dev/null
+++ b/src/main/native/c/OpenSSLKeyPairGenerator.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+#include
+#include
+#include
+#include
+#include
+
+#include "jssl.h"
+#include "jni_utils.h"
+#include "OpenSSLKeyPairGenerator.h"
+
+static int encode_pkey(EVP_PKEY *pkey, int selection, const char *structure,
+ unsigned char **out, size_t *out_len) {
+ OSSL_ENCODER_CTX *ectx = OSSL_ENCODER_CTX_new_for_pkey(
+ pkey, selection, "DER", structure, NULL);
+ if (ectx == NULL) {
+ return 0;
+ }
+ *out = NULL;
+ *out_len = 0;
+ int ok = OSSL_ENCODER_to_data(ectx, out, out_len);
+ OSSL_ENCODER_CTX_free(ectx);
+ return ok;
+}
+
+JNIEXPORT jobjectArray JNICALL
+Java_com_canonical_openssl_keypairgenerator_OpenSSLKeyPairGenerator_generateKeyPair0
+ (JNIEnv *env, jclass clazz, jstring algorithm, jstring group) {
+
+ EVP_PKEY *pkey = NULL;
+ EVP_PKEY_CTX *pctx = NULL;
+ unsigned char *priv_der = NULL;
+ unsigned char *pub_der = NULL;
+ size_t priv_len = 0;
+ size_t pub_len = 0;
+ const char *algo_c = NULL;
+ const char *group_c = NULL;
+ jobjectArray result = NULL;
+ jbyteArray privArr = NULL;
+ jbyteArray pubArr = NULL;
+ OSSL_PARAM params[2];
+
+ if (algorithm == NULL || group == NULL) {
+ throwProviderException(env, "algorithm and group must not be null");
+ return NULL;
+ }
+
+ algo_c = (*env)->GetStringUTFChars(env, algorithm, NULL);
+ if (algo_c == NULL) {
+ goto cleanup;
+ }
+ group_c = (*env)->GetStringUTFChars(env, group, NULL);
+ if (group_c == NULL) {
+ goto cleanup;
+ }
+
+ pctx = EVP_PKEY_CTX_new_from_name(jssl_libctx(), algo_c, NULL);
+ if (pctx == NULL) {
+ throwProviderException(env, "EVP_PKEY_CTX_new_from_name failed");
+ goto cleanup;
+ }
+
+ if (EVP_PKEY_keygen_init(pctx) <= 0) {
+ throwProviderException(env, "EVP_PKEY_keygen_init failed");
+ goto cleanup;
+ }
+
+ params[0] = OSSL_PARAM_construct_utf8_string(
+ OSSL_PKEY_PARAM_GROUP_NAME, (char *)group_c, 0);
+ params[1] = OSSL_PARAM_construct_end();
+
+ if (EVP_PKEY_CTX_set_params(pctx, params) <= 0) {
+ throwProviderException(env, "EVP_PKEY_CTX_set_params failed");
+ goto cleanup;
+ }
+
+ if (EVP_PKEY_keygen(pctx, &pkey) <= 0 || pkey == NULL) {
+ throwProviderException(env, "EVP_PKEY_keygen failed");
+ goto cleanup;
+ }
+
+ if (!encode_pkey(pkey, EVP_PKEY_KEYPAIR, "PrivateKeyInfo",
+ &priv_der, &priv_len)) {
+ throwProviderException(env, "private key DER encoding failed");
+ goto cleanup;
+ }
+
+ if (!encode_pkey(pkey, EVP_PKEY_PUBLIC_KEY, "SubjectPublicKeyInfo",
+ &pub_der, &pub_len)) {
+ throwProviderException(env, "public key DER encoding failed");
+ goto cleanup;
+ }
+
+ {
+ jclass byteArrayClass = (*env)->FindClass(env, "[B");
+ if (byteArrayClass == NULL) {
+ goto cleanup;
+ }
+
+ result = (*env)->NewObjectArray(env, 2, byteArrayClass, NULL);
+ if (result == NULL) {
+ goto cleanup;
+ }
+
+ privArr = byte_array_to_jbyteArray(env, priv_der, (int)priv_len);
+ if (privArr == NULL) {
+ result = NULL;
+ goto cleanup;
+ }
+ (*env)->SetObjectArrayElement(env, result, 0, privArr);
+
+ pubArr = byte_array_to_jbyteArray(env, pub_der, (int)pub_len);
+ if (pubArr == NULL) {
+ result = NULL;
+ goto cleanup;
+ }
+ (*env)->SetObjectArrayElement(env, result, 1, pubArr);
+ }
+
+cleanup:
+ if (priv_der != NULL) {
+ OPENSSL_cleanse(priv_der, priv_len);
+ OPENSSL_free(priv_der);
+ }
+ if (pub_der != NULL) {
+ OPENSSL_free(pub_der);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ if (pctx != NULL) {
+ EVP_PKEY_CTX_free(pctx);
+ }
+ if (algo_c != NULL) {
+ (*env)->ReleaseStringUTFChars(env, algorithm, algo_c);
+ }
+ if (group_c != NULL) {
+ (*env)->ReleaseStringUTFChars(env, group, group_c);
+ }
+ return result;
+}
diff --git a/src/main/native/c/OpenSSLMAC.c b/src/main/native/c/OpenSSLMAC.c
index 491b3e0..d952a99 100644
--- a/src/main/native/c/OpenSSLMAC.c
+++ b/src/main/native/c/OpenSSLMAC.c
@@ -31,45 +31,84 @@
*/
JNIEXPORT jlong JNICALL Java_com_canonical_openssl_mac_OpenSSLMAC_doInit0
(JNIEnv *env, jobject this, jstring name, jstring cipher, jstring digest, jbyteArray iv, jint output_length, jbyteArray key) {
- const char *name_str = jstring_to_char_array(env, name);
- const char *cipher_str = jstring_to_char_array(env, cipher);
- const char *digest_str = jstring_to_char_array(env, digest);
- byte *iv_bytes = jbyteArray_to_byte_array(env, iv);
- int iv_len = array_length(env, iv);
- byte *key_bytes = jbyteArray_to_byte_array(env, key);
- int key_len = array_length(env, key);
-
- mac_params *params = init_mac_params((char *)cipher_str, (char *)digest_str,
- iv_bytes, iv_len, (size_t)output_length);
+ const char *name_str = NULL;
+ const char *cipher_str = NULL;
+ const char *digest_str = NULL;
+ jbyte *iv_pinned = NULL;
+ jbyte *key_pinned = NULL;
+ byte *iv_copy = NULL;
+ byte *key_copy = NULL;
+ int iv_len = 0;
+ int key_len = 0;
+ mac_params *params = NULL;
+ mac_context *ctx = NULL;
int oom = 0;
- mac_context *ctx = mac_init((char *)name_str, key_bytes, key_len, params, &oom);
- free(params);
+ jlong ret = 0;
- if (key_bytes) {
- OPENSSL_cleanse(key_bytes, key_len);
- (*env)->ReleaseByteArrayElements(env, key, (jbyte *)key_bytes, JNI_ABORT);
- }
- if (iv_bytes) {
- OPENSSL_cleanse(iv_bytes, iv_len);
- (*env)->ReleaseByteArrayElements(env, iv, (jbyte *)iv_bytes, JNI_ABORT);
+ name_str = jstring_to_char_array(env, name);
+ if (name != NULL && name_str == NULL) goto cleanup;
+ cipher_str = jstring_to_char_array(env, cipher);
+ if (cipher != NULL && cipher_str == NULL) goto cleanup;
+ digest_str = jstring_to_char_array(env, digest);
+ if (digest != NULL && digest_str == NULL) goto cleanup;
+
+ if (iv != NULL) {
+ iv_len = (*env)->GetArrayLength(env, iv);
+ iv_pinned = (*env)->GetByteArrayElements(env, iv, NULL);
+ if (iv_pinned == NULL) goto cleanup;
+ iv_copy = (byte *)malloc(iv_len);
+ if (iv_copy == NULL) {
+ throwOOM(env, "Could not allocate IV buffer");
+ goto cleanup;
+ }
+ memcpy(iv_copy, iv_pinned, iv_len);
}
- if (name_str)
- (*env)->ReleaseStringUTFChars(env, name, name_str);
- if (cipher_str)
- (*env)->ReleaseStringUTFChars(env, cipher, cipher_str);
+ if (key != NULL) {
+ key_len = (*env)->GetArrayLength(env, key);
+ key_pinned = (*env)->GetByteArrayElements(env, key, NULL);
+ if (key_pinned == NULL) goto cleanup;
+ key_copy = (byte *)malloc(key_len);
+ if (key_copy == NULL) {
+ throwOOM(env, "Could not allocate key buffer");
+ goto cleanup;
+ }
+ memcpy(key_copy, key_pinned, key_len);
+ }
- if (digest_str)
- (*env)->ReleaseStringUTFChars(env, digest, digest_str);
+ params = init_mac_params((char *)cipher_str, (char *)digest_str,
+ iv_copy, iv_len, (size_t)output_length);
+ ctx = mac_init(jssl_libctx(), (char *)name_str, key_copy, key_len, params, &oom);
if (ctx == NULL) {
if (oom)
throwOOM(env, "Out of memory initializing MAC");
else
throwProviderException(env, "Failed to initialize MAC");
- return 0;
+ goto cleanup;
+ }
+ ret = (jlong)ctx;
+
+cleanup:
+ free(params);
+ if (key_pinned) {
+ (*env)->ReleaseByteArrayElements(env, key, key_pinned, JNI_ABORT);
+ }
+ if (iv_pinned) {
+ (*env)->ReleaseByteArrayElements(env, iv, iv_pinned, JNI_ABORT);
}
- return (jlong)ctx;
+ if (key_copy) {
+ OPENSSL_cleanse(key_copy, key_len);
+ free(key_copy);
+ }
+ if (iv_copy) {
+ OPENSSL_cleanse(iv_copy, iv_len);
+ free(iv_copy);
+ }
+ release_jstring(env, name, name_str);
+ release_jstring(env, cipher, cipher_str);
+ release_jstring(env, digest, digest_str);
+ return ret;
}
/*
@@ -92,9 +131,12 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_mac_OpenSSLMAC_doUpdate0
(JNIEnv *env, jobject this, jbyteArray input) {
mac_context *ctx = (mac_context*)get_long_field(env, this, "nativeHandle");
byte *input_bytes = jbyteArray_to_byte_array(env, input);
+ if (input_bytes == NULL) {
+ return;
+ }
int input_len = array_length(env, input);
jssl_status rc = mac_update(ctx, input_bytes, input_len);
- (*env)->ReleaseByteArrayElements(env, input, (jbyte *)input_bytes, JNI_ABORT);
+ release_jbyteArray(env, input, input_bytes);
if (rc != SUCCESS) {
throwProviderException(env, "MAC update failed");
}
diff --git a/src/main/native/c/OpenSSLMD.c b/src/main/native/c/OpenSSLMD.c
index 1f25ff0..1c98695 100644
--- a/src/main/native/c/OpenSSLMD.c
+++ b/src/main/native/c/OpenSSLMD.c
@@ -20,8 +20,6 @@
#include "jni_utils.h"
#include
-extern OSSL_LIB_CTX *global_libctx;
-
/*
* Class: OpenSSLMD
* Method: doInit0
@@ -30,9 +28,12 @@ extern OSSL_LIB_CTX *global_libctx;
JNIEXPORT jlong JNICALL Java_com_canonical_openssl_md_OpenSSLMD_doInit0
(JNIEnv *env, jobject this, jstring algorithm) {
const char *algorithm_str = jstring_to_char_array(env, algorithm);
+ if (algorithm_str == NULL) {
+ return 0;
+ }
int oom = 0;
- md_context *ctx = md_init(global_libctx, algorithm_str, &oom);
- (*env)->ReleaseStringUTFChars(env, algorithm, algorithm_str);
+ md_context *ctx = md_init(jssl_libctx(), algorithm_str, &oom);
+ release_jstring(env, algorithm, algorithm_str);
if (ctx == NULL) {
if (oom)
throwOOM(env, "Out of memory initializing digest");
@@ -52,11 +53,14 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_md_OpenSSLMD_doUpdate0
(JNIEnv *env, jobject this, jbyteArray data) {
md_context *ctx = (md_context*) get_long_field(env, this, "nativeHandle");
byte *data_array = jbyteArray_to_byte_array(env, data);
+ if (data_array == NULL) {
+ return;
+ }
int length = array_length(env, data);
if (md_update(ctx, data_array, length) != SUCCESS) {
throwProviderException(env, "Digest update failed");
}
- (*env)->ReleaseByteArrayElements(env, data, (jbyte *)data_array, JNI_ABORT);
+ release_jbyteArray(env, data, data_array);
}
/*
diff --git a/src/main/native/c/OpenSSLPBKDF2.c b/src/main/native/c/OpenSSLPBKDF2.c
index 1b1e783..681d3d0 100644
--- a/src/main/native/c/OpenSSLPBKDF2.c
+++ b/src/main/native/c/OpenSSLPBKDF2.c
@@ -19,22 +19,33 @@
#include "OpenSSLPBKDF2.h"
#include
-#define MAX_KEY_SIZE 64
-extern OSSL_LIB_CTX *global_libctx;
+#define MAX_KEY_SIZE 256
/*
- * Class: OpenSSLPBKDF2Spi
- * Method: generateKey0
- * Signature: ([C[BI)LOpenSSLPBKDF2Spi/PBKDF2SecretKey;
+ * Class: com_canonical_openssl_kdf_OpenSSLPBKDF2
+ * Method: generateSecret0
+ * Signature: ([C[BII)[B
*/
JNIEXPORT jbyteArray JNICALL Java_com_canonical_openssl_kdf_OpenSSLPBKDF2_generateSecret0
- (JNIEnv *env, jobject this, jcharArray password, jbyteArray salt, jint iteration_count) {
+ (JNIEnv *env, jobject this, jcharArray 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 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) {
+ return NULL;
+ }
jbyte *salt_bytes = (*env)->GetByteArrayElements(env, salt, NULL);
+ if (salt_bytes == NULL) {
+ (*env)->ReleaseCharArrayElements(env, password, password_chars, JNI_ABORT);
+ return NULL;
+ }
kdf_spec *spec = create_pbkdf_spec((byte *)password_chars, password_length * sizeof(jchar),
(byte *)salt_bytes, salt_length, iteration_count);
@@ -54,12 +65,17 @@ JNIEXPORT jbyteArray JNICALL Java_com_canonical_openssl_kdf_OpenSSLPBKDF2_genera
return NULL;
}
- if (kdf_derive(global_libctx, spec, params, output, MAX_KEY_SIZE, PBKDF2) == SUCCESS) {
- result = byte_array_to_jbyteArray(env, output, MAX_KEY_SIZE);
+ if (kdf_derive(jssl_libctx(), spec, params, output, key_length, PBKDF2) == SUCCESS) {
+ result = byte_array_to_jbyteArray(env, output, key_length);
}
- OPENSSL_cleanse(output, MAX_KEY_SIZE);
+ OPENSSL_cleanse(output, sizeof(output));
free_kdf_spec(&spec, PBKDF2);
free_kdf_params(¶ms, PBKDF2);
return result;
}
+
+JNIEXPORT jint JNICALL Java_com_canonical_openssl_kdf_OpenSSLPBKDF2_getMaxKeyLengthBytes0
+ (JNIEnv *env, jclass cls) {
+ return MAX_KEY_SIZE;
+}
diff --git a/src/main/native/c/OpenSSLSignature.c b/src/main/native/c/OpenSSLSignature.c
index 3c8c3bd..cb2d8e2 100644
--- a/src/main/native/c/OpenSSLSignature.c
+++ b/src/main/native/c/OpenSSLSignature.c
@@ -21,8 +21,6 @@
#include "jni_utils.h"
#include
-extern OSSL_LIB_CTX *global_libctx;
-
sv_params *create_params(JNIEnv *env, jobject this, jobject params, int *oom) {
int salt_length = get_int_field(env, params, "saltLength");
jstring digest = get_string_field(env, params, "digest");
@@ -30,9 +28,9 @@ sv_params *create_params(JNIEnv *env, jobject this, jobject params, int *oom) {
jstring mgf1_digest = get_string_field(env, params, "mgf1Digest");
char *mgf1_digest_name = jstring_to_char_array(env, mgf1_digest);
int padding = get_int_field(env, params, "padding");
- sv_params *svp = sv_create_params(global_libctx, salt_length, padding == 0 ? NONE : PSS, digest_name, mgf1_digest_name, oom);
- (*env)->ReleaseStringUTFChars(env, digest, digest_name);
- (*env)->ReleaseStringUTFChars(env, mgf1_digest, mgf1_digest_name);
+ sv_params *svp = sv_create_params(jssl_libctx(), salt_length, padding == 0 ? NONE : PSS, digest_name, mgf1_digest_name, oom);
+ release_jstring(env, digest, digest_name);
+ release_jstring(env, mgf1_digest, mgf1_digest_name);
return svp;
}
@@ -45,37 +43,52 @@ sv_type svtype_from_str(char *str) {
jlong init_signature(JNIEnv *env, jobject this, jstring sig_name, jobject jkey, jobject params, sv_state state) {
int oom = 0;
- sv_params *svparams = create_params(env, this, params, &oom);
+ sv_params *svparams = NULL;
+ sv_key *key = NULL;
+ sv_context *svc = NULL;
+ char *sig_name_str = NULL;
+ jlong ret = 0;
+
+ svparams = create_params(env, this, params, &oom);
if (svparams == NULL) {
if (oom) throwOOM(env, "Out of memory creating signature params");
else throwProviderException(env, "Failed to create signature params");
- return 0;
+ goto cleanup;
}
EVP_PKEY* evpkey = CASTPTR(EVP_PKEY, invokeLongMethod(env, jkey, "getNativeKeyHandle", "()J"));
+ if ((*env)->ExceptionCheck(env)) {
+ goto cleanup;
+ }
oom = 0;
- sv_key *key = sv_init_key(global_libctx, evpkey, &oom);
+ key = sv_init_key(jssl_libctx(), evpkey, &oom);
if (key == NULL) {
- free_sv_params(&svparams);
if (oom) throwOOM(env, "Out of memory initializing signature key");
else throwProviderException(env, "Failed to initialize signature key");
- return 0;
+ goto cleanup;
}
- char *sig_name_str = jstring_to_char_array(env, sig_name);
+ sig_name_str = jstring_to_char_array(env, sig_name);
+ if (sig_name_str == NULL) {
+ goto cleanup;
+ }
sv_type type = svtype_from_str(sig_name_str);
- (*env)->ReleaseStringUTFChars(env, sig_name, sig_name_str);
oom = 0;
- sv_context *svc = sv_init(global_libctx, key, svparams, state, type, &oom);
- free_sv_params(&svparams);
+ svc = sv_init(jssl_libctx(), key, svparams, state, type, &oom);
if (svc == NULL) {
- free_sv_key(&key);
if (oom) throwOOM(env, "Out of memory initializing signature context");
else throwProviderException(env, "Failed to initialize signature context");
- return 0;
+ goto cleanup;
}
- return (jlong)svc;
+ ret = (jlong)svc;
+ key = NULL;
+
+cleanup:
+ release_jstring(env, sig_name, sig_name_str);
+ if (svparams) free_sv_params(&svparams);
+ if (key) free_sv_key(&key);
+ return ret;
}
/*
@@ -106,6 +119,10 @@ JNIEXPORT jlong JNICALL Java_com_canonical_openssl_signature_OpenSSLSignature_en
*/
JNIEXPORT void JNICALL Java_com_canonical_openssl_signature_OpenSSLSignature_engineUpdate0
(JNIEnv *env, jobject this, jbyteArray bytes, jint offset, jint length) {
+ if (offset < 0 || length < 0) {
+ throwIllegalArgument(env, "offset and length must be non-negative");
+ return;
+ }
sv_context *ctx = (sv_context*)get_long_field(env, this, "nativeHandle");
byte *to_update = (byte*)malloc(length);
if (to_update == NULL) {
@@ -116,6 +133,7 @@ JNIEXPORT void JNICALL Java_com_canonical_openssl_signature_OpenSSLSignature_eng
if (sv_update(ctx, to_update, length) <= 0) {
throwProviderException(env, "Signature update failed");
}
+ OPENSSL_cleanse(to_update, length);
free(to_update);
}
@@ -129,7 +147,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_canonical_openssl_signature_OpenSSLSignatu
sv_context *ctx = (sv_context*)get_long_field(env, this, "nativeHandle");
size_t sig_length = 0;
if (sv_sign(ctx, NULL, &sig_length) < 0) {
- free_sv_context(&ctx);
+ throwProviderException(env, "Signing failed");
return NULL;
}
@@ -158,6 +176,10 @@ JNIEXPORT jbyteArray JNICALL Java_com_canonical_openssl_signature_OpenSSLSignatu
*/
JNIEXPORT jboolean JNICALL Java_com_canonical_openssl_signature_OpenSSLSignature_engineVerify0
(JNIEnv *env, jobject this, jbyteArray sig_bytes, jint offset, jint length) {
+ if (offset < 0 || length < 0) {
+ throwIllegalArgument(env, "offset and length must be non-negative");
+ return JNI_FALSE;
+ }
sv_context *ctx = (sv_context*)get_long_field(env, this, "nativeHandle");
byte *signature = (byte*)malloc(length);
if (signature == NULL) {
@@ -167,7 +189,13 @@ JNIEXPORT jboolean JNICALL Java_com_canonical_openssl_signature_OpenSSLSignature
copy_byte_array_range(env, sig_bytes, offset, length, signature);
int rc = sv_verify(ctx, signature, length);
free(signature);
- return rc <= 0 ? JNI_FALSE : JNI_TRUE;
+ if (rc < 0) {
+ (*env)->ThrowNew(env,
+ (*env)->FindClass(env, "java/security/SignatureException"),
+ "Signature verification error");
+ return JNI_FALSE;
+ }
+ return rc == 1 ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT void JNICALL Java_com_canonical_openssl_signature_OpenSSLSignature_cleanupNativeMemory0
diff --git a/src/main/native/c/RSAKEMDecapsulator.c b/src/main/native/c/RSAKEMDecapsulator.c
index 21f3561..8e9d9bd 100644
--- a/src/main/native/c/RSAKEMDecapsulator.c
+++ b/src/main/native/c/RSAKEMDecapsulator.c
@@ -21,7 +21,6 @@
#include "evp_utils.h"
#include "jni_utils.h"
-extern OSSL_LIB_CTX *global_libctx;
/*
* Class: OpenSSLKEMRSA_RSAKEMDecapsulator
* Method: decapsulatorInit0
@@ -30,10 +29,13 @@ extern OSSL_LIB_CTX *global_libctx;
JNIEXPORT jlong JNICALL Java_com_canonical_openssl_keyencapsulation_OpenSSLKEMRSA_00024RSAKEMDecapsulator_decapsulatorInit0
(JNIEnv *env, jobject this, jbyteArray key) {
byte* bytes = jbyteArray_to_byte_array(env, key);
+ if (bytes == NULL) {
+ return 0;
+ }
int length = array_length(env, key);
- EVP_PKEY *private_key = decode_private_key_fips(bytes, length, global_libctx);
- (*env)->ReleaseByteArrayElements(env, key, (jbyte*)bytes, JNI_ABORT);
- kem_keyspec *spec = init_kem_keyspec_with_key(NULL, private_key, global_libctx);
+ EVP_PKEY *private_key = decode_private_key_fips(bytes, length, jssl_libctx());
+ release_jbyteArray(env, key, bytes);
+ kem_keyspec *spec = init_kem_keyspec_with_key(NULL, private_key, jssl_libctx());
if (spec == NULL) {
throwOOM(env, "Could not allocate KEM keyspec");
return 0;
@@ -50,10 +52,13 @@ JNIEXPORT jbyteArray JNICALL Java_com_canonical_openssl_keyencapsulation_OpenSSL
(JNIEnv *env, jobject this, jbyteArray encapsulated) {
kem_keyspec *spec = (kem_keyspec*)get_long_field(env, this, "nativeHandle");
byte* bytes = jbyteArray_to_byte_array(env, encapsulated);
+ if (bytes == NULL) {
+ return NULL;
+ }
int length = array_length(env, encapsulated);
set_wrapped_key(spec, bytes, length);
jssl_status rc = unwrap(spec);
- (*env)->ReleaseByteArrayElements(env, encapsulated, (jbyte*)bytes, JNI_ABORT);
+ release_jbyteArray(env, encapsulated, bytes);
spec->wrapped_key = NULL;
spec->wrapped_key_length = 0;
if (rc == FAIL_OOM) {
diff --git a/src/main/native/c/RSAKEMEncapsulator.c b/src/main/native/c/RSAKEMEncapsulator.c
index 6c06f63..27ff7bd 100644
--- a/src/main/native/c/RSAKEMEncapsulator.c
+++ b/src/main/native/c/RSAKEMEncapsulator.c
@@ -21,8 +21,6 @@
#include "evp_utils.h"
#include "jni_utils.h"
-extern OSSL_LIB_CTX *global_libctx;
-
/*
* Class: OpenSSLKEMRSA_RSAKEMEncapsulator
* Method: encapsulatorInit0
@@ -31,10 +29,13 @@ extern OSSL_LIB_CTX *global_libctx;
JNIEXPORT jlong JNICALL Java_com_canonical_openssl_keyencapsulation_OpenSSLKEMRSA_00024RSAKEMEncapsulator_encapsulatorInit0
(JNIEnv *env, jobject this, jbyteArray key) {
byte* bytes = jbyteArray_to_byte_array(env, key);
+ if (bytes == NULL) {
+ return 0;
+ }
int length = array_length(env, key);
- EVP_PKEY *public_key = decode_public_key_fips(bytes, length, global_libctx);
- (*env)->ReleaseByteArrayElements(env, key, (jbyte*)bytes, JNI_ABORT);
- kem_keyspec *spec = init_kem_keyspec_with_key(public_key, NULL, global_libctx);
+ EVP_PKEY *public_key = decode_public_key_fips(bytes, length, jssl_libctx());
+ release_jbyteArray(env, key, bytes);
+ kem_keyspec *spec = init_kem_keyspec_with_key(public_key, NULL, jssl_libctx());
if (spec == NULL) {
throwOOM(env, "Could not allocate KEM keyspec");
return 0;
diff --git a/src/main/native/c/cipher.c b/src/main/native/c/cipher.c
index 77e2152..de8b263 100644
--- a/src/main/native/c/cipher.c
+++ b/src/main/native/c/cipher.c
@@ -15,8 +15,6 @@
*
*/
#include "cipher.h"
-#include
-#include
static inline int is_mode_CCM(cipher_context *ctx) {
const char* suffix = NULL;
@@ -46,28 +44,8 @@ static inline int is_op_decrypt(cipher_context *ctx) {
return ctx != NULL && ctx->op_mode == OP_DECRYPT;
}
-#define MAX_CIPHER_TABLE_SIZE 256
#define TAG_LEN 16
-void print_byte_array(byte *array, int length) {
- printf("[ ");
- for (int i = 0; i < length; i++) {
- printf("%d", array[i]);
- if (i < length-1) {
- printf(", ");
- }
- }
- printf(" ]\n");
-}
-
-typedef struct name_cipher_map {
- const char *name;
- const EVP_CIPHER *cipher;
-} name_cipher_map;
-
-static name_cipher_map cipher_table[MAX_CIPHER_TABLE_SIZE];
-static int table_size;
-
int get_padding_code(const char *name) {
if (name == NULL || str_equal(name, "NONE")) {
return 0;
@@ -80,9 +58,7 @@ int get_padding_code(const char *name) {
} else if (str_equal(name, "ISO7816-4")) {
return EVP_PADDING_ISO7816_4;
} else {
- // TODO: handle an supported padding scheme
- // TEMP: disable padding :-(
- return 0;
+ return -1;
}
}
@@ -97,20 +73,22 @@ cipher_context* create_cipher_context(OSSL_LIB_CTX *libctx, const char *name, co
if (new_ctx == NULL) {
goto error;
}
+ new_context->context = new_ctx;
- EVP_CIPHER_CTX_init(new_ctx);
new_context->name = strdup(name);
if (new_context->name == NULL) {
goto error;
}
- new_context->context = new_ctx;
new_context->cipher = EVP_CIPHER_fetch(libctx, name, NULL);
- if (new_context->cipher == NULL || new_context->context == NULL) {
+ if (new_context->cipher == NULL) {
goto error;
}
new_context->op_mode = OP_UNDEFINED;
new_context->padding = get_padding_code(padding_name);
+ if (new_context->padding < 0) {
+ goto error;
+ }
memset(new_context->gcm_tag, 0, GCM_TAG_LEN);
new_context->key = NULL;
new_context->iv = NULL;
@@ -125,6 +103,27 @@ cipher_context* create_cipher_context(OSSL_LIB_CTX *libctx, const char *name, co
jssl_status cipher_init(cipher_context *ctx, byte in_buf[], int in_len, unsigned char *key, int key_len, unsigned char *iv, int iv_len, int op_mode) {
jssl_status ret = FAIL_OOM;
+ if (is_mode_CCM(ctx) && op_mode != OP_ENCRYPT && in_len < TAG_LEN) {
+ return FAIL_EVP;
+ }
+
+ if (ctx->key != NULL) {
+ OPENSSL_cleanse(ctx->key, ctx->key_len);
+ free(ctx->key);
+ ctx->key = NULL;
+ ctx->key_len = 0;
+ }
+ if (ctx->iv != NULL) {
+ OPENSSL_cleanse(ctx->iv, ctx->iv_len);
+ free(ctx->iv);
+ ctx->iv = NULL;
+ ctx->iv_len = 0;
+ }
+ if (ctx->initial_bytes != NULL) {
+ free(ctx->initial_bytes);
+ ctx->initial_bytes = NULL;
+ }
+
if (key != NULL) {
ctx->key = (unsigned char *) malloc(key_len);
if (ctx->key == NULL) goto error;
@@ -147,21 +146,25 @@ jssl_status cipher_init(cipher_context *ctx, byte in_buf[], int in_len, unsigned
ret = FAIL_EVP;
if (!EVP_CipherInit_ex(ctx->context, ctx->cipher, NULL, NULL, NULL, op_mode)) {
- ERR_print_errors_fp(stderr);
goto error;
}
ctx->op_mode = op_mode;
if (is_mode_CCM(ctx)) {
- EVP_CIPHER_CTX_ctrl(ctx->context, EVP_CTRL_CCM_SET_IVLEN, iv_len, 0);
- EVP_CIPHER_CTX_ctrl(ctx->context, EVP_CTRL_CCM_SET_TAG, TAG_LEN, op_mode == OP_ENCRYPT ? 0 : (in_buf + in_len - TAG_LEN));
+ if (EVP_CIPHER_CTX_ctrl(ctx->context, EVP_CTRL_CCM_SET_IVLEN, iv_len, 0) <= 0) {
+ goto error;
+ }
+ if (EVP_CIPHER_CTX_ctrl(ctx->context, EVP_CTRL_CCM_SET_TAG, TAG_LEN, op_mode == OP_ENCRYPT ? 0 : (in_buf + in_len - TAG_LEN)) <= 0) {
+ goto error;
+ }
}
if (!EVP_CipherInit_ex(ctx->context, NULL, NULL, ctx->key, ctx->iv, op_mode)) {
- ERR_print_errors_fp(stderr);
goto error;
}
- EVP_CIPHER_CTX_set_padding(ctx->context, ctx->padding);
+ if (EVP_CIPHER_CTX_set_padding(ctx->context, ctx->padding) <= 0) {
+ goto error;
+ }
return SUCCESS;
error:
@@ -192,14 +195,12 @@ jssl_status cipher_update_aad(cipher_context *ctx, int *out_len_ptr, byte aad_bu
jssl_status cipher_update(cipher_context *ctx, byte out_buf[], int *out_len_ptr, byte in_buf[], int in_len) {
if (is_mode_CCM(ctx)) {
if (!EVP_CipherUpdate(ctx->context, NULL, out_len_ptr, NULL, is_op_decrypt(ctx) ? in_len-TAG_LEN : in_len)) {
- ERR_print_errors_fp(stderr);
return FAIL_EVP;
}
}
if (!EVP_CipherUpdate(ctx->context, out_buf, out_len_ptr, in_buf,
(is_mode_CCM(ctx) && is_op_decrypt(ctx)) ? in_len-TAG_LEN : in_len)) {
- ERR_print_errors_fp(stderr);
return FAIL_EVP;
}
return SUCCESS;
@@ -207,20 +208,25 @@ jssl_status cipher_update(cipher_context *ctx, byte out_buf[], int *out_len_ptr,
jssl_status cipher_do_final(cipher_context *ctx, byte *out_buf, int *out_len_ptr) {
if (ctx->op_mode == OP_DECRYPT && is_mode_GCM(ctx)) {
- EVP_CIPHER_CTX_ctrl(ctx->context, EVP_CTRL_GCM_SET_TAG, TAG_LEN, ctx->gcm_tag);
+ if (EVP_CIPHER_CTX_ctrl(ctx->context, EVP_CTRL_GCM_SET_TAG, TAG_LEN, ctx->gcm_tag) <= 0) {
+ return FAIL_EVP;
+ }
}
if (!EVP_CipherFinal_ex(ctx->context, out_buf, out_len_ptr)) {
- ERR_print_errors_fp(stderr);
return FAIL_EVP;
}
if (ctx->op_mode == OP_ENCRYPT) {
if(is_mode_CCM(ctx)) {
*out_len_ptr = TAG_LEN;
- EVP_CIPHER_CTX_ctrl(ctx->context, EVP_CTRL_CCM_GET_TAG, TAG_LEN, out_buf);
+ if (EVP_CIPHER_CTX_ctrl(ctx->context, EVP_CTRL_CCM_GET_TAG, TAG_LEN, out_buf) <= 0) {
+ return FAIL_EVP;
+ }
} else if(is_mode_GCM(ctx)) {
- EVP_CIPHER_CTX_ctrl(ctx->context, EVP_CTRL_GCM_GET_TAG, TAG_LEN, out_buf + *out_len_ptr);
+ if (EVP_CIPHER_CTX_ctrl(ctx->context, EVP_CTRL_GCM_GET_TAG, TAG_LEN, out_buf + *out_len_ptr) <= 0) {
+ return FAIL_EVP;
+ }
*out_len_ptr += TAG_LEN;
}
}
diff --git a/src/main/native/c/drbg.c b/src/main/native/c/drbg.c
index 8234384..c841b3c 100644
--- a/src/main/native/c/drbg.c
+++ b/src/main/native/c/drbg.c
@@ -15,7 +15,7 @@
*
*/
#include
-#include
+#include
#include
#include
@@ -48,34 +48,34 @@ static int create_params(const char *name, OSSL_PARAM params[]) {
return param_count;
}
-DRBG* create_DRBG(const char* name, DRBG* parent) {
- return create_DRBG_with_params(name, parent, NULL, NULL);
+DRBG* create_DRBG(OSSL_LIB_CTX *libctx, const char* name, DRBG* parent) {
+ return create_DRBG_with_params(libctx, name, parent, NULL, NULL);
}
#define MIN_NUMBER_OF_PARAMS 2
#define MAX_NUMBER_OF_PARAMS 4
-DRBG* create_DRBG_with_params(const char* name, DRBG* parent, DRBGParams *drbg_params, int *evp_error) {
+DRBG* create_DRBG_with_params(OSSL_LIB_CTX *libctx, const char* name, DRBG* parent, DRBGParams *drbg_params, int *evp_error) {
EVP_RAND *rand = NULL;
EVP_RAND_CTX *context = NULL;
int rc = 1;
- rand = EVP_RAND_fetch(NULL, name, NULL);
+ rand = EVP_RAND_fetch(libctx, name, "provider=fips");
if (NULL == rand) {
- fprintf(stderr, "Couldn't allocate EVP_RAND: %s\n", name);
+ rc = 0;
goto error;
}
-
+
context = EVP_RAND_CTX_new(rand, parent == NULL ? NULL : parent->context);
if (NULL == context) {
- fprintf(stderr, "Couldn't allocate EVP_RAND_CTX\n");
+ rc = 0;
goto error;
}
OSSL_PARAM params[MAX_NUMBER_OF_PARAMS] = {0};
int n_params = create_params(name, params);
if (n_params < MIN_NUMBER_OF_PARAMS) {
- fprintf(stderr, "Couldn't create params");
+ rc = 0;
goto error;
}
@@ -89,7 +89,6 @@ DRBG* create_DRBG_with_params(const char* name, DRBG* parent, DRBGParams *drbg_p
if (!rc) goto error;
- const OSSL_PROVIDER *prov = EVP_RAND_get0_provider(EVP_RAND_CTX_get0_rand(context));
DRBG *newDRBG = (DRBG*) malloc(sizeof(DRBG));
if (newDRBG == NULL) goto error;
newDRBG->rand = rand;
@@ -172,34 +171,50 @@ int next_rand_int(DRBG *drbg, int num_bits) {
int generate_seed(DRBG* generator, byte output[], int n_bytes) {
DRBG *parent = generator->parent;
if (parent != NULL) {
- return next_rand(parent, output, n_bytes);
- } else {
- return getrandom(output, n_bytes, 0);
+ return next_rand(parent, output, n_bytes) > 0;
+ }
+ if (n_bytes < 0) {
+ return 0;
+ }
+ size_t remaining = (size_t)n_bytes;
+ byte *p = output;
+ while (remaining > 0) {
+ ssize_t got = getrandom(p, remaining, 0);
+ if (got < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return 0;
+ }
+ if (got == 0) {
+ return 0;
+ }
+ p += got;
+ remaining -= (size_t)got;
}
+ return 1;
}
/*void reseed(DRBG* generator) {
reseed_with_params(generator, &NO_PARAMS);
}*/
+/* entropy is NULL: in FIPS mode the DRBG must pull from its configured entropy chain. */
jssl_status reseed_with_params(DRBG *generator, DRBGParams *params) {
- byte seed[128]; // TODO: what should the default seed size be?
- size_t length = 128;
- ssize_t num = getrandom(seed, length, 0);
- if (num < length) {
- return FAIL_GETRANDOM;
- }
- EVP_RAND_reseed(generator->context, params->prediction_resistance, seed, length, params->additional_data, params->additional_data_length);
- OPENSSL_cleanse(seed, length);
- return SUCCESS;
+ int rc = EVP_RAND_reseed(generator->context, params->prediction_resistance,
+ NULL, 0,
+ params->additional_data, params->additional_data_length);
+ return rc > 0 ? SUCCESS : FAIL_EVP;
}
-void reseed_with_seed(DRBG* generator, byte seed[], int seed_length) {
- EVP_RAND_reseed(generator->context, 0, seed, seed_length, NULL, 0);
- OPENSSL_cleanse(seed, seed_length);
+jssl_status reseed_with_seed(DRBG* generator, byte seed[], int seed_length) {
+ int rc = EVP_RAND_reseed(generator->context, 0, seed, seed_length, NULL, 0);
+ return rc > 0 ? SUCCESS : FAIL_EVP;
}
-void reseed_with_seed_and_params(DRBG* generator, byte seed[], int seed_length, DRBGParams *params) {
- EVP_RAND_reseed(generator->context, params->prediction_resistance, seed, seed_length, params->additional_data, params->additional_data_length);
- OPENSSL_cleanse(seed, seed_length);
+jssl_status reseed_with_seed_and_params(DRBG* generator, byte seed[], int seed_length, DRBGParams *params) {
+ int rc = EVP_RAND_reseed(generator->context, params->prediction_resistance,
+ seed, seed_length,
+ params->additional_data, params->additional_data_length);
+ return rc > 0 ? SUCCESS : FAIL_EVP;
}
diff --git a/src/main/native/c/evp_utils.c b/src/main/native/c/evp_utils.c
index 23ebafc..5301204 100644
--- a/src/main/native/c/evp_utils.c
+++ b/src/main/native/c/evp_utils.c
@@ -19,8 +19,6 @@
#include "jssl.h"
-extern OSSL_LIB_CTX *global_libctx;
-
/* FIPS-safe decoder functions using OSSL_DECODER API
* These functions properly route through the FIPS provider and respect
* provider boundaries, avoiding direct key structure manipulation.
@@ -32,7 +30,7 @@ EVP_PKEY *decode_private_key_fips(byte* bytes, size_t length, OSSL_LIB_CTX *libc
size_t data_len = length;
if (libctx == NULL) {
- libctx = global_libctx;
+ libctx = jssl_libctx();
}
/* Create decoder context for private keys in DER format
@@ -63,7 +61,7 @@ EVP_PKEY *decode_public_key_fips(byte* bytes, size_t length, OSSL_LIB_CTX *libct
size_t data_len = length;
if (libctx == NULL) {
- libctx = global_libctx;
+ libctx = jssl_libctx();
}
/* Create decoder context for public keys in DER format */
diff --git a/src/main/native/c/init.c b/src/main/native/c/init.c
index a2fb7e0..9f69039 100644
--- a/src/main/native/c/init.c
+++ b/src/main/native/c/init.c
@@ -14,16 +14,25 @@
* along with this program. If not, see .
*
*/
-#include "jssl.h"
+#include "jssl.h"
#include
-#include
+#include
#include
#include
#include "jni.h"
-/* Global libctx handle. Will be initializaed in JNI_OnLoad */
-OSSL_LIB_CTX *global_libctx = NULL;
-OSSL_PROVIDER *pbase, *pfips;
+static OSSL_LIB_CTX *global_libctx = NULL;
+static OSSL_PROVIDER *pbase, *pfips;
+
+/* A NULL ctx here means JNI_OnLoad did not run; not reachable in normal use. */
+OSSL_LIB_CTX* jssl_libctx(void) {
+ OSSL_LIB_CTX *ctx = __atomic_load_n(&global_libctx, __ATOMIC_ACQUIRE);
+ if (ctx == NULL) {
+ fputs("jssl: global_libctx is NULL; JNI_OnLoad did not run or memory is corrupted\n", stderr);
+ abort();
+ }
+ return ctx;
+}
/* Loading the FIPS provider is often not enough to get openssl's full functionality.
We also should load the base provider. The base provider does not provide for
@@ -45,17 +54,14 @@ OSSL_LIB_CTX* load_openssl_provider(const char *name, const char* conf_file_path
}
if (!OSSL_LIB_CTX_load_config(libctx, conf_file_path)) {
- ERR_print_errors_fp(stderr);
- OSSL_LIB_CTX_free(libctx);
- return NULL;
+ OSSL_LIB_CTX_free(libctx);
+ return NULL;
}
OSSL_PROVIDER *prov = OSSL_PROVIDER_load(libctx, name);
if (NULL == prov) {
- fprintf(stderr, "Failed to load the %s provider:\n", name);
- ERR_print_errors_fp(stderr);
- OSSL_LIB_CTX_free(libctx);
- return NULL;
+ OSSL_LIB_CTX_free(libctx);
+ return NULL;
}
if (strcmp("fips", name) == 0) {
@@ -67,7 +73,15 @@ OSSL_LIB_CTX* load_openssl_provider(const char *name, const char* conf_file_path
}
OSSL_LIB_CTX* load_openssl_fips_provider(const char* conf_file_path) {
- return load_openssl_provider("fips", conf_file_path);
+ OSSL_LIB_CTX *libctx = load_openssl_provider("fips", conf_file_path);
+ if (libctx == NULL) {
+ return NULL;
+ }
+ if (!EVP_set_default_properties(libctx, "fips=yes")) {
+ unload_libctx(libctx);
+ return NULL;
+ }
+ return libctx;
}
OSSL_LIB_CTX* load_openssl_base_provider(const char* conf_file_path) {
@@ -82,28 +96,26 @@ void unload_libctx(OSSL_LIB_CTX *libctx) {
}
static void unload_global_libctx() {
- unload_libctx(global_libctx);
+ OSSL_LIB_CTX *ctx = __atomic_exchange_n(&global_libctx, NULL, __ATOMIC_ACQ_REL);
+ unload_libctx(ctx);
}
int JNI_OnLoad(JavaVM* vm, void *reserved) {
const char *default_cnf = "/usr/local/ssl/openssl.cnf";
- const char *custom_cnf = getenv("OPENSSL_CUSTOM_CONF");
+ const char *custom_cnf = secure_getenv("OPENSSL_CUSTOM_CONF");
const char *conf = custom_cnf != NULL ? custom_cnf : default_cnf;
- global_libctx = load_openssl_fips_provider(conf);
- if (global_libctx == NULL) {
- fprintf(stderr, "Failed to load FIPS provider from %s\n", conf);
+ OSSL_LIB_CTX *ctx = load_openssl_fips_provider(conf);
+ if (ctx == NULL) {
return JNI_ERR;
}
- if (!OSSL_PROVIDER_available(global_libctx, "base")) {
- pbase = OSSL_PROVIDER_load(global_libctx, "base");
+ if (!OSSL_PROVIDER_available(ctx, "base")) {
+ pbase = OSSL_PROVIDER_load(ctx, "base");
if (pbase == NULL) {
- fprintf(stderr, "Failed to load base provider\n");
- ERR_print_errors_fp(stderr);
- unload_global_libctx();
- global_libctx = NULL;
+ unload_libctx(ctx);
return JNI_ERR;
}
}
+ __atomic_store_n(&global_libctx, ctx, __ATOMIC_RELEASE);
return JNI_VERSION_10;
}
diff --git a/src/main/native/c/jni_utils.c b/src/main/native/c/jni_utils.c
index 12fd793..dce65f2 100644
--- a/src/main/native/c/jni_utils.c
+++ b/src/main/native/c/jni_utils.c
@@ -15,7 +15,6 @@
*
*/
#include
-#include
#include "jssl.h"
void throwOOM(JNIEnv *env, const char *message) {
@@ -30,6 +29,12 @@ void throwProviderException(JNIEnv *env, const char *message) {
message);
}
+void throwIllegalArgument(JNIEnv *env, const char *message) {
+ (*env)->ThrowNew(env,
+ (*env)->FindClass(env, "java/lang/IllegalArgumentException"),
+ message);
+}
+
char *jstring_to_char_array(JNIEnv *env, jstring string) {
if (string == NULL) {
return NULL;
@@ -65,7 +70,7 @@ char *jcharArray_to_char_array(JNIEnv *env, jcharArray chars) {
jbyteArray byte_array_to_jbyteArray(JNIEnv *env, byte *array, int length) {
jbyteArray ret_array = (*env)->NewByteArray(env, length);
if (ret_array == NULL) return NULL;
- (*env)->SetByteArrayRegion(env, ret_array, 0, length, array);
+ (*env)->SetByteArrayRegion(env, ret_array, 0, length, (const jbyte *)array);
return ret_array;
}
@@ -79,7 +84,6 @@ long get_long_field(JNIEnv *env, jobject this, const char *field_name) {
jclass clazz = (*env)->GetObjectClass(env, this);
jfieldID ctx_id = (*env)->GetFieldID(env, clazz, field_name, "J");
if (ctx_id == NULL) {
- fprintf(stderr, "JNI: field '%s' (J) not found\n", field_name);
return 0;
}
return (*env)->GetLongField(env, this, ctx_id);
@@ -89,7 +93,6 @@ int get_int_field(JNIEnv *env, jobject this, const char *field_name) {
jclass clazz = (*env)->GetObjectClass(env, this);
jfieldID id = (*env)->GetFieldID(env, clazz, field_name, "I");
if (id == NULL) {
- fprintf(stderr, "JNI: field '%s' (I) not found\n", field_name);
return 0;
}
return (*env)->GetIntField(env, this, id);
@@ -99,24 +102,23 @@ jstring get_string_field(JNIEnv *env, jobject this, const char *field_name) {
jclass clazz = (*env)->GetObjectClass(env, this);
jfieldID id = (*env)->GetFieldID(env, clazz, field_name, "Ljava/lang/String;");
if (id == NULL) {
- fprintf(stderr, "JNI: field '%s' (Ljava/lang/String;) not found\n", field_name);
return NULL;
}
return (jstring)((*env)->GetObjectField(env, this, id));
}
void copy_byte_array(JNIEnv *env, jbyteArray destination, byte *source, int length) {
- (*env)->SetByteArrayRegion(env, destination, 0, length, source);
+ (*env)->SetByteArrayRegion(env, destination, 0, length, (const jbyte *)source);
}
void copy_byte_array_range(JNIEnv *env, jbyteArray source, int offset, int length, byte *destination) {
- (*env)->GetByteArrayRegion(env, source, offset, length, destination);
+ (*env)->GetByteArrayRegion(env, source, offset, length, (jbyte *)destination);
}
jbyteArray new_byteArray(JNIEnv *env, byte *source, int length) {
jbyteArray retArray = (*env)->NewByteArray(env, length);
if (retArray == NULL) return NULL;
- (*env)->SetByteArrayRegion(env, retArray, 0, length, source);
+ (*env)->SetByteArrayRegion(env, retArray, 0, length, (const jbyte *)source);
return retArray;
}
@@ -124,8 +126,11 @@ jlong invokeLongMethod(JNIEnv *env, jobject this, const char *name, const char *
jclass class = (*env)->GetObjectClass(env, this);
jmethodID methodID = (*env)->GetMethodID(env, class, name, signature);
if (methodID == NULL) {
- fprintf(stderr, "JNI: method '%s%s' not found\n", name, signature);
return 0;
}
- return (*env)->CallLongMethod(env, this, methodID);
+ jlong result = (*env)->CallLongMethod(env, this, methodID);
+ if ((*env)->ExceptionCheck(env)) {
+ return 0;
+ }
+ return result;
}
diff --git a/src/main/native/c/kdf.c b/src/main/native/c/kdf.c
index 6a30170..700c88d 100644
--- a/src/main/native/c/kdf.c
+++ b/src/main/native/c/kdf.c
@@ -156,16 +156,16 @@ static void populate_hkdf_params(OSSL_PARAM *ossl_params, kdf_spec *spec, kdf_pa
ossl_params[nparams++] = OSSL_PARAM_construct_end();
}
-static void populate_params(OSSL_PARAM *ossl_params, kdf_spec *spec, kdf_params *params, kdf_type type) {
+static jssl_status populate_params(OSSL_PARAM *ossl_params, kdf_spec *spec, kdf_params *params, kdf_type type) {
switch (type) {
case PBKDF2:
- populate_pbkdf2_params(ossl_params, spec, params);
- break;
+ populate_pbkdf2_params(ossl_params, spec, params);
+ return SUCCESS;
case HKDF:
populate_hkdf_params(ossl_params, spec, params);
- break;
+ return SUCCESS;
default:
- printf("Not supported yet.\n");
+ return FAIL_OPERATION_UNSUPPORTED;
}
}
@@ -182,11 +182,13 @@ static char *get_kdf_name(kdf_type type) {
jssl_status kdf_derive(OSSL_LIB_CTX *libctx, kdf_spec *spec, kdf_params *params, byte *keydata, int keylength, kdf_type type) {
OSSL_PARAM ossl_params[8];
- populate_params(ossl_params, spec, params, type);
+ jssl_status ret = populate_params(ossl_params, spec, params, type);
+ if (ret != SUCCESS) {
+ return ret;
+ }
EVP_KDF *kdf = NULL;
EVP_KDF_CTX *kctx = NULL;
- jssl_status ret = SUCCESS;
kdf = EVP_KDF_fetch(libctx, get_kdf_name(type), NULL);
if (kdf == NULL) {
diff --git a/src/main/native/c/keyagreement.c b/src/main/native/c/keyagreement.c
index c1df983..5c1e6d6 100644
--- a/src/main/native/c/keyagreement.c
+++ b/src/main/native/c/keyagreement.c
@@ -50,7 +50,7 @@ shared_secret *generate_shared_secret(key_agreement *agreement, int *evp_error)
}
if (EVP_PKEY_derive_init(ctx) <= 0) {
- if (evp_error) *evp_error = 1;
+ if (evp_error) *evp_error = 1;
goto error;
}
@@ -119,7 +119,9 @@ EVP_PKEY *generate_key(key_agreement_algorithm algo, OSSL_LIB_CTX *libctx) {
goto error;
}
- EVP_PKEY_CTX_set_params(pctx, params);
+ if (EVP_PKEY_CTX_set_params(pctx, params) <= 0) {
+ goto error;
+ }
if(EVP_PKEY_keygen(pctx, &key) <= 0) {
goto error;
diff --git a/src/main/native/c/mac.c b/src/main/native/c/mac.c
index d506989..5f99b5b 100644
--- a/src/main/native/c/mac.c
+++ b/src/main/native/c/mac.c
@@ -17,7 +17,6 @@
#include "jssl.h"
#include "mac.h"
#include
-#include
#include
mac_params *init_mac_params(char *cipher, char *digest, byte *iv, size_t iv_length, size_t output_length) {
@@ -31,7 +30,7 @@ mac_params *init_mac_params(char *cipher, char *digest, byte *iv, size_t iv_leng
return new;
}
-static void set_params(EVP_MAC_CTX *ctx, mac_params *params) {
+static int set_params(EVP_MAC_CTX *ctx, mac_params *params) {
OSSL_PARAM _params[8];
int n_params = 0;
if (params->cipher_name != NULL) {
@@ -44,21 +43,18 @@ static void set_params(EVP_MAC_CTX *ctx, mac_params *params) {
_params[n_params++] = OSSL_PARAM_construct_octet_string("iv", params->iv, params->iv_length);
}
_params[n_params] = OSSL_PARAM_construct_end();
- if (0 == EVP_MAC_CTX_set_params(ctx, _params)) {
- ERR_print_errors_fp(stderr);
- }
+ return EVP_MAC_CTX_set_params(ctx, _params);
}
-mac_context *mac_init(char *algorithm, byte *key, size_t key_length, mac_params *params, int *oom) {
+mac_context *mac_init(OSSL_LIB_CTX *libctx, char *algorithm, byte *key, size_t key_length, mac_params *params, int *oom) {
mac_context *new_ctx = (mac_context *)malloc(sizeof(mac_context));
if (new_ctx == NULL) {
if (oom) *oom = 1;
return NULL;
}
- new_ctx->algorithm = algorithm;
new_ctx->ctx = NULL;
- EVP_MAC *mac = EVP_MAC_fetch(NULL, algorithm, NULL);
+ EVP_MAC *mac = EVP_MAC_fetch(libctx, algorithm, "provider=fips");
if (mac == NULL) {
goto error;
}
@@ -67,8 +63,8 @@ mac_context *mac_init(char *algorithm, byte *key, size_t key_length, mac_params
if (new_ctx->ctx == NULL) {
goto error;
}
- if (params != NULL) {
- set_params(new_ctx->ctx, params);
+ if (params != NULL && set_params(new_ctx->ctx, params) == 0) {
+ goto error;
}
if (0 == EVP_MAC_init(new_ctx->ctx, (const unsigned char*)key, key_length, NULL)) {
goto error;
@@ -76,14 +72,12 @@ mac_context *mac_init(char *algorithm, byte *key, size_t key_length, mac_params
return new_ctx;
error:
- ERR_print_errors_fp(stderr);
free_mac_context(&new_ctx);
return NULL;
}
jssl_status mac_update(mac_context *ctx, byte *input, size_t input_size) {
if (0 == EVP_MAC_update(ctx->ctx, input, input_size)) {
- ERR_print_errors_fp(stderr);
return FAIL_EVP;
}
return SUCCESS;
@@ -91,7 +85,6 @@ jssl_status mac_update(mac_context *ctx, byte *input, size_t input_size) {
jssl_status mac_final(mac_context *ctx, byte *output, size_t *bytes_written, size_t output_size) {
if (0 == EVP_MAC_final(ctx->ctx, output, bytes_written, output_size)) {
- ERR_print_errors_fp(stderr);
return FAIL_EVP;
}
return SUCCESS;
diff --git a/src/main/native/c/md.c b/src/main/native/c/md.c
index 11d47cc..1801dd6 100644
--- a/src/main/native/c/md.c
+++ b/src/main/native/c/md.c
@@ -15,7 +15,6 @@
*
*/
#include "md.h"
-#include
md_context *md_init(OSSL_LIB_CTX *libctx, const char *algorithm, int *oom) {
md_context *new = NULL;
@@ -57,17 +56,17 @@ md_context *md_init(OSSL_LIB_CTX *libctx, const char *algorithm, int *oom) {
jssl_status md_update(md_context *ctx, byte *input, size_t input_length) {
if (!EVP_DigestUpdate(ctx->ossl_ctx, input, input_length)) {
- ERR_print_errors_fp(stderr);
return FAIL_EVP;
}
return SUCCESS;
}
jssl_status md_digest(md_context *ctx, byte *output, int *output_length) {
- if (!EVP_DigestFinal_ex(ctx->ossl_ctx, output, output_length)) {
- ERR_print_errors_fp(stderr);
+ unsigned int len = 0;
+ if (!EVP_DigestFinal_ex(ctx->ossl_ctx, output, &len)) {
return FAIL_EVP;
}
+ *output_length = (int)len;
return SUCCESS;
}
diff --git a/src/main/native/c/signature.c b/src/main/native/c/signature.c
index c5c1ed2..3f70819 100644
--- a/src/main/native/c/signature.c
+++ b/src/main/native/c/signature.c
@@ -16,7 +16,7 @@
*/
#include "signature.h"
#include "jssl.h"
-#include
+#include
#include
#include
@@ -28,7 +28,6 @@ sv_key *sv_init_key(OSSL_LIB_CTX *libctx, EVP_PKEY *pkey, int *oom) {
}
key->ctx = EVP_PKEY_CTX_new_from_pkey(libctx, pkey, NULL);
if (key->ctx == NULL) {
- ERR_print_errors_fp(stderr);
free(key);
return NULL;
}
@@ -108,6 +107,7 @@ sv_context *sv_init(OSSL_LIB_CTX *libctx, sv_key *key, sv_params *params, sv_sta
new_context->key = key;
new_context->data = NULL;
new_context->length = 0;
+ new_context->capacity = 0;
new_context->mctx = md_ctx;
return new_context;
@@ -118,27 +118,40 @@ sv_context *sv_init(OSSL_LIB_CTX *libctx, sv_key *key, sv_params *params, sv_sta
int sv_update(sv_context *ctx, byte *data, size_t length) {
if (ctx->type == SV_ED25519 || ctx->type == SV_ED448) {
- // Ed25519/Ed448 are one-shot: the message is consumed at sv_sign /
- // sv_verify time, so the buffer must outlive this call. The caller's
- // `data` is freed as soon as this function returns, so take a copy.
- byte *buf = NULL;
- if (length > 0) {
- buf = (byte *)malloc(length);
- if (buf == NULL) return 0;
- memcpy(buf, data, length);
+ if (length == 0) {
+ return 1;
}
- free(ctx->data);
- ctx->data = buf;
- ctx->length = length;
+ if (length > SIZE_MAX - ctx->length) {
+ return 0;
+ }
+ size_t new_length = ctx->length + length;
+ if (new_length > ctx->capacity) {
+ size_t new_cap = ctx->capacity ? ctx->capacity : 256;
+ while (new_cap < new_length) {
+ if (new_cap > SIZE_MAX / 2) {
+ new_cap = new_length;
+ break;
+ }
+ new_cap <<= 1;
+ }
+ byte *buf = (byte *)realloc(ctx->data, new_cap);
+ if (buf == NULL) {
+ return 0;
+ }
+ ctx->data = buf;
+ ctx->capacity = new_cap;
+ }
+ memcpy(ctx->data + ctx->length, data, length);
+ ctx->length = new_length;
return 1;
}
- if (ctx->state == SIGN && (EVP_DigestSignUpdate(ctx->mctx, data, length) < 0)) {
+ if (ctx->state == SIGN && (EVP_DigestSignUpdate(ctx->mctx, data, length) <= 0)) {
return 0;
}
- if(ctx->state == VERIFY && (EVP_DigestVerifyUpdate(ctx->mctx, data, length) < 0)) {
- return 0;
+ if(ctx->state == VERIFY && (EVP_DigestVerifyUpdate(ctx->mctx, data, length) <= 0)) {
+ return 0;
}
return 1;
diff --git a/src/main/native/include/drbg.h b/src/main/native/include/drbg.h
index dbf3de2..2d4d120 100644
--- a/src/main/native/include/drbg.h
+++ b/src/main/native/include/drbg.h
@@ -48,9 +48,9 @@ typedef struct _DRBG {
} DRBG;
/* Fetch and initialize a DRBG instance */
-DRBG* create_DRBG(const char* name, DRBG* parent);
+DRBG* create_DRBG(OSSL_LIB_CTX *libctx, const char* name, DRBG* parent);
-DRBG* create_DRBG_with_params(const char *name, DRBG *parent, DRBGParams *params, int *evp_error);
+DRBG* create_DRBG_with_params(OSSL_LIB_CTX *libctx, const char *name, DRBG *parent, DRBGParams *params, int *evp_error);
/* Destroy the given DRBG */
void free_DRBG(DRBG **generator);
@@ -69,9 +69,9 @@ int generate_seed(DRBG* generator, byte output[], int n_bytes);
jssl_status reseed_with_params(DRBG *generator, DRBGParams *params);
-void reseed_with_seed(DRBG* generator, byte seed[], int seed_length);
+jssl_status reseed_with_seed(DRBG* generator, byte seed[], int seed_length);
-void reseed_with_seed_and_params(DRBG* generator, byte seed[], int seed_length, DRBGParams *params);
+jssl_status reseed_with_seed_and_params(DRBG* generator, byte seed[], int seed_length, DRBGParams *params);
/* Return the next random integer generated by the DRBG.
* num_bits is the size of the integer in bits
diff --git a/src/main/native/include/jni/OpenSSLKeyPairGenerator.h b/src/main/native/include/jni/OpenSSLKeyPairGenerator.h
new file mode 100644
index 0000000..425347b
--- /dev/null
+++ b/src/main/native/include/jni/OpenSSLKeyPairGenerator.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class com_canonical_openssl_keypairgenerator_OpenSSLKeyPairGenerator */
+
+#ifndef _Included_com_canonical_openssl_keypairgenerator_OpenSSLKeyPairGenerator
+#define _Included_com_canonical_openssl_keypairgenerator_OpenSSLKeyPairGenerator
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: com_canonical_openssl_keypairgenerator_OpenSSLKeyPairGenerator
+ * Method: generateKeyPair0
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)[[B
+ */
+JNIEXPORT jobjectArray JNICALL Java_com_canonical_openssl_keypairgenerator_OpenSSLKeyPairGenerator_generateKeyPair0
+ (JNIEnv *, jclass, jstring, jstring);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/main/native/include/jni/OpenSSLPBKDF2.h b/src/main/native/include/jni/OpenSSLPBKDF2.h
index 3d51fb5..670ddf8 100644
--- a/src/main/native/include/jni/OpenSSLPBKDF2.h
+++ b/src/main/native/include/jni/OpenSSLPBKDF2.h
@@ -16,20 +16,23 @@
*/
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
-/* Header for class OpenSSLPBKDF2Spi */
+/* Header for class com_canonical_openssl_kdf_OpenSSLPBKDF2 */
-#ifndef _Included_OpenSSLPBKDF2Spi
-#define _Included_OpenSSLPBKDF2Spi
+#ifndef _Included_com_canonical_openssl_kdf_OpenSSLPBKDF2
+#define _Included_com_canonical_openssl_kdf_OpenSSLPBKDF2
#ifdef __cplusplus
extern "C" {
#endif
/*
- * Class: OpenSSLPBKDF2Spi
- * Method: generateKey0
- * Signature: ([C[BI)LOpenSSLPBKDF2Spi/PBKDF2SecretKey;
+ * Class: com_canonical_openssl_kdf_OpenSSLPBKDF2
+ * Method: generateSecret0
+ * Signature: ([C[BII)[B
*/
JNIEXPORT jbyteArray JNICALL Java_com_canonical_openssl_kdf_OpenSSLPBKDF2_generateSecret0
- (JNIEnv *, jobject, jcharArray, jbyteArray, jint);
+ (JNIEnv *, jobject, jcharArray, jbyteArray, jint, jint);
+
+JNIEXPORT jint JNICALL Java_com_canonical_openssl_kdf_OpenSSLPBKDF2_getMaxKeyLengthBytes0
+ (JNIEnv *, jclass);
#ifdef __cplusplus
}
diff --git a/src/main/native/include/jni_utils.h b/src/main/native/include/jni_utils.h
index eba5e90..fab5e80 100644
--- a/src/main/native/include/jni_utils.h
+++ b/src/main/native/include/jni_utils.h
@@ -51,4 +51,6 @@ jlong invokeLongMethod(JNIEnv *env, jobject this, const char *name, const char *
void throwOOM(JNIEnv *env, const char *message);
void throwProviderException(JNIEnv *env, const char *message);
+
+void throwIllegalArgument(JNIEnv *env, const char *message);
#endif //_INCLUDE_JNI_UTILS_H
diff --git a/src/main/native/include/jssl.h b/src/main/native/include/jssl.h
index e1adaa9..01e576f 100644
--- a/src/main/native/include/jssl.h
+++ b/src/main/native/include/jssl.h
@@ -31,6 +31,7 @@ typedef enum {
OSSL_LIB_CTX* load_openssl_fips_provider(const char*);
void unload_libctx(OSSL_LIB_CTX *libctx);
+OSSL_LIB_CTX* jssl_libctx(void);
/* Utility function for string comparison */
static inline int str_equal(const char *str1, const char *str2) {
diff --git a/src/main/native/include/mac.h b/src/main/native/include/mac.h
index 316473e..86ba664 100644
--- a/src/main/native/include/mac.h
+++ b/src/main/native/include/mac.h
@@ -28,12 +28,11 @@ typedef struct mac_params {
} mac_params;
typedef struct mac_context {
- char *algorithm;
EVP_MAC_CTX *ctx;
} mac_context;
mac_params *init_mac_params(char *cipher, char *digest, byte *iv, size_t iv_length, size_t output_length);
-mac_context *mac_init(char *algorithm, byte *key, size_t key_length, mac_params *params, int *oom);
+mac_context *mac_init(OSSL_LIB_CTX *libctx, char *algorithm, byte *key, size_t key_length, mac_params *params, int *oom);
jssl_status mac_update(mac_context *ctx, byte *input, size_t input_size);
jssl_status mac_final_with_input(mac_context *ctx, byte *input, size_t input_size, byte *output, size_t *bytes_written, size_t output_size);
jssl_status mac_final(mac_context *ctx, byte *output, size_t *bytes_written, size_t output_size);
diff --git a/src/main/native/include/signature.h b/src/main/native/include/signature.h
index 056cdfd..4536c37 100644
--- a/src/main/native/include/signature.h
+++ b/src/main/native/include/signature.h
@@ -53,7 +53,8 @@ typedef struct sv_context {
sv_type type;
sv_key *key;
byte *data;
- int length;
+ size_t length;
+ size_t capacity;
EVP_MD_CTX *mctx;
} sv_context;
diff --git a/src/test/consumer-snap/snapcraft.yaml b/src/test/consumer-snap/snapcraft.yaml
index 6a76f3b..808075a 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.6.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.0.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 e137c16..fb3d144 100644
--- a/src/test/java/CipherTest.java
+++ b/src/test/java/CipherTest.java
@@ -17,6 +17,7 @@
import javax.crypto.Cipher;
import java.security.Key;
import java.security.Security;
+import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import javax.crypto.spec.IvParameterSpec;
@@ -393,4 +394,86 @@ public void runTestKeyWrapUnwrap(String nameKeySizeAndMode, String padding) thro
assertArrayEquals("Invalid secret key " + (nameKeySizeAndMode + "/" + padding), sk1.getEncoded(), sk2.getEncoded());
}
+
+ @Test
+ public void testAEADDecryptMultipleUpdates() throws Exception {
+ for (String cipher : new String[]{"AES128/GCM", "AES192/GCM", "AES256/GCM"}) {
+ for (String padding : paddings) {
+ runTestAEADDecryptMultipleUpdates(cipher, padding);
+ }
+ }
+ }
+
+ // Encrypts with a single doFinal, then decrypts by feeding the ciphertext
+ // in three chunks via update()/update()/doFinal() to exercise the AEAD
+ // decrypt accumulation buffer.
+ private void runTestAEADDecryptMultipleUpdates(String nameKeySizeAndMode, String padding)
+ throws Exception {
+ String cipherName = nameKeySizeAndMode + "/" + padding;
+ 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[48];
+ sr.nextBytes(plaintext);
+ String aad = "additional authentication data";
+
+ Cipher enc = Cipher.getInstance(cipherName, "OpenSSLFIPSProvider");
+ enc.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec, sr);
+ enc.updateAAD(aad.getBytes());
+ byte[] ciphertext = enc.doFinal(plaintext);
+
+ // Split ciphertext across two update() calls; remainder goes to doFinal().
+ int chunk = ciphertext.length / 3;
+ Cipher dec = Cipher.getInstance(cipherName, "OpenSSLFIPSProvider");
+ dec.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec, sr);
+ dec.updateAAD(aad.getBytes());
+ dec.update(ciphertext, 0, chunk);
+ dec.update(ciphertext, chunk, chunk);
+ byte[] result = dec.doFinal(ciphertext, 2 * chunk, ciphertext.length - 2 * chunk);
+
+ assertArrayEquals("AEAD multi-update decrypt failed for " + cipherName, plaintext, result);
+ }
+
+ @Test
+ public void testDoFinalNoInputAfterUpdate() throws Exception {
+ SecureRandom sr = SecureRandom.getInstance("NativePRNG");
+
+ byte[] key = new byte[16];
+ sr.nextBytes(key);
+ byte[] iv = new byte[16];
+ sr.nextBytes(iv);
+ AlgorithmParameterSpec spec = new IvParameterSpec(iv);
+
+ byte[] plaintext = new byte[64];
+ sr.nextBytes(plaintext);
+
+ Cipher enc = Cipher.getInstance("AES128/CBC/PKCS7", "OpenSSLFIPSProvider");
+ enc.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), spec, sr);
+ byte[] ciphertext = concat(enc.update(plaintext), enc.doFinal());
+
+ Cipher dec = Cipher.getInstance("AES128/CBC/PKCS7", "OpenSSLFIPSProvider");
+ dec.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), spec, sr);
+ byte[] result = concat(dec.update(ciphertext), dec.doFinal());
+
+ assertArrayEquals("doFinal() with no input after update must not throw NPE", plaintext, result);
+ }
+
+ private static byte[] concat(byte[] a, byte[] b) {
+ int la = a == null ? 0 : a.length;
+ int lb = b == null ? 0 : b.length;
+ byte[] out = new byte[la + lb];
+ if (la > 0) System.arraycopy(a, 0, out, 0, la);
+ if (lb > 0) System.arraycopy(b, 0, out, la, lb);
+ return out;
+ }
}
+
diff --git a/src/test/java/KeyAgreementTest.java b/src/test/java/KeyAgreementTest.java
index 3c1f477..12edbf6 100644
--- a/src/test/java/KeyAgreementTest.java
+++ b/src/test/java/KeyAgreementTest.java
@@ -52,13 +52,13 @@ private void runTest(KeyPairGenerator kpg, String algo) throws Exception {
@Test
public void testDH() throws Exception {
- KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "OpenSSLFIPSProvider");
runTest(kpg, "DH");
}
@Test
public void testECDH() throws Exception {
- KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "OpenSSLFIPSProvider");
runTest(kpg, "ECDH");
}
}
diff --git a/src/test/java/KeyConverterTest.java b/src/test/java/KeyConverterTest.java
index 6b1a1d6..8c52240 100644
--- a/src/test/java/KeyConverterTest.java
+++ b/src/test/java/KeyConverterTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.*;
import java.security.*;
+import java.security.spec.ECGenParameterSpec;
/**
* Test FIPS-safe key conversion from Java Key objects to OpenSSL EVP_PKEY handles.
@@ -125,5 +126,39 @@ public void testFreeEVPKeyWithZeroHandle() {
// Should not crash
KeyConverter.freeEVPKey(0);
}
+
+ @Test
+ public void testECKeyPairFromFIPSProviderConverts() throws Exception {
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "OpenSSLFIPSProvider");
+ for (String curve : new String[]{"P-256", "P-384", "P-521"}) {
+ kpg.initialize(new ECGenParameterSpec(curve));
+ KeyPair kp = kpg.generateKeyPair();
+
+ long privHandle = KeyConverter.privateKeyToEVPKey(kp.getPrivate());
+ assertTrue("EC private key handle must be non-zero for " + curve, privHandle != 0);
+
+ long pubHandle = KeyConverter.publicKeyToEVPKey(kp.getPublic());
+ assertTrue("EC public key handle must be non-zero for " + curve, pubHandle != 0);
+
+ KeyConverter.freeEVPKey(privHandle);
+ KeyConverter.freeEVPKey(pubHandle);
+ }
+ }
+
+ @Test
+ public void testDHKeyPairFromFIPSProviderConverts() throws Exception {
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH", "OpenSSLFIPSProvider");
+ kpg.initialize(2048);
+ KeyPair kp = kpg.generateKeyPair();
+
+ long privHandle = KeyConverter.privateKeyToEVPKey(kp.getPrivate());
+ assertTrue("DH private key handle must be non-zero", privHandle != 0);
+
+ long pubHandle = KeyConverter.publicKeyToEVPKey(kp.getPublic());
+ assertTrue("DH public key handle must be non-zero", pubHandle != 0);
+
+ KeyConverter.freeEVPKey(privHandle);
+ KeyConverter.freeEVPKey(pubHandle);
+ }
}
diff --git a/src/test/java/MacTest.java b/src/test/java/MacTest.java
index d6dda43..a6bec0b 100644
--- a/src/test/java/MacTest.java
+++ b/src/test/java/MacTest.java
@@ -17,6 +17,7 @@
import java.lang.FunctionalInterface;
import java.util.Arrays;
import java.util.function.*;
+import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.security.Security;
@@ -27,6 +28,7 @@
import org.junit.Test;
import org.junit.BeforeClass;
import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class MacTest {
@@ -195,7 +197,11 @@ interface TriFunction {
private TriFunction macCompute = (mac, keySpec, input) -> {
try {
- mac.init(keySpec, null);
+ if (isGMAC(mac.getAlgorithm())) {
+ mac.init(keySpec, freshGMACNonce());
+ } else {
+ mac.init(keySpec, null);
+ }
mac.update(input, 0, input.length);
return mac.doFinal();
} catch (Exception ike) {
@@ -203,6 +209,16 @@ interface TriFunction {
}
};
+ private static boolean isGMAC(String macName) {
+ return macName.startsWith("GMAC");
+ }
+
+ private static IvParameterSpec freshGMACNonce() {
+ byte[] nonce = new byte[12];
+ new SecureRandom().nextBytes(nonce);
+ return new IvParameterSpec(nonce);
+ }
+
private void runTest(String name, SecretKeySpec keySpec, String macName) throws Exception {
Mac mac1 = Mac.getInstance(macName, "OpenSSLFIPSProvider");
Mac mac2 = Mac.getInstance(macName, "OpenSSLFIPSProvider");
@@ -210,7 +226,13 @@ private void runTest(String name, SecretKeySpec keySpec, String macName) throws
byte[] output1 = macCompute.apply(mac1, keySpec, input);
byte[] output2 = macCompute.apply(mac2, keySpec, input);
byte[] output3 = macCompute.apply(mac3, keySpec, input1);
- assertArrayEquals("Test for mac " + name + " failed.", output1, output2);
+ if (isGMAC(macName)) {
+ // Each Mac instance is initialized with a fresh random nonce, so
+ // tags of the same input under the same key must differ.
+ assertFalse("Test for mac " + name + " failed (output1==output2).", Arrays.equals(output1, output2));
+ } else {
+ assertArrayEquals("Test for mac " + name + " failed.", output1, output2);
+ }
assertFalse("Test for mac " + name + " failed.", Arrays.equals(output2, output3));
}
@@ -221,7 +243,14 @@ private void runLargeTest(String name, SecretKeySpec keySpec, String macName) th
byte[][] outputs = new byte[12][];
Mac mac = Mac.getInstance(macName, "OpenSSLFIPSProvider");
- mac.init(keySpec, null);
+ int preInitLen = mac.getMacLength();
+ if (isGMAC(macName)) {
+ mac.init(keySpec, freshGMACNonce());
+ } else {
+ mac.init(keySpec, null);
+ }
+ assertEquals("getMacLength() before/after init disagree for " + name,
+ preInitLen, mac.getMacLength());
outputs[0] = macCompute1.apply(mac, input);
mac.reset();
diff --git a/src/test/java/SecretKeyFactoryTest.java b/src/test/java/SecretKeyFactoryTest.java
index 87cb9db..4329702 100644
--- a/src/test/java/SecretKeyFactoryTest.java
+++ b/src/test/java/SecretKeyFactoryTest.java
@@ -14,10 +14,12 @@
* along with this program. If not, see .
*
*/
+import java.security.InvalidKeyException;
import java.security.spec.KeySpec;
import java.security.Security;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
+import javax.crypto.interfaces.PBEKey;
import javax.crypto.spec.PBEKeySpec;
import com.canonical.openssl.provider.OpenSSLFIPSProvider;
@@ -27,6 +29,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
public class SecretKeyFactoryTest {
@@ -53,6 +56,120 @@ public void testPBKDF2() throws Exception {
assertArrayEquals("Returned KeySpec does not match original KeySpec", ((PBEKeySpec)spec).getSalt(), salt.getBytes());
}
+ @Test
+ public void testTranslateForeignPBEKeyIgnoresEncodedLength() throws Exception {
+ SecretKeyFactory pbkdf = SecretKeyFactory.getInstance("PBKDF2", "OpenSSLFIPSProvider");
+
+ final char[] password = "Zaq12wsXCde34rfV".toCharArray();
+ final byte[] salt = "NaClCommonSaltRockSaltSeaSalt".getBytes();
+ final byte[] foreignEncoded = new byte[17];
+
+ PBEKey foreign = new PBEKey() {
+ public char[] getPassword() { return password.clone(); }
+ public byte[] getSalt() { return salt.clone(); }
+ public int getIterationCount() { return 120000; }
+ public byte[] getEncoded() { return foreignEncoded.clone(); }
+ public String getAlgorithm() { return "PBEWithMD5AndDES"; }
+ public String getFormat() { return "RAW"; }
+ };
+
+ SecretKey translated;
+ try {
+ translated = pbkdf.translateKey(foreign);
+ } catch (InvalidKeyException expected) {
+ return;
+ }
+ assertNotEquals("Translated key length must not be inherited from a foreign encoded form",
+ foreignEncoded.length, translated.getEncoded().length);
+ }
+
+ @Test
+ public void testPBKDF2ExplicitKeyLength() throws Exception {
+ char[] password = "Zaq12wsXCde34rfV".toCharArray();
+ byte[] salt = "NaClCommonSaltRockSaltSeaSalt".getBytes();
+ int iterationCount = 120000;
+
+ SecretKeyFactory pbkdf = SecretKeyFactory.getInstance("PBKDF2", "OpenSSLFIPSProvider");
+
+ for (int keyLengthBits : new int[]{128, 256, 512}) {
+ PBEKeySpec spec = new PBEKeySpec(password, salt, iterationCount, keyLengthBits);
+ SecretKey key = pbkdf.generateSecret(spec);
+ assertEquals("Key length mismatch for " + keyLengthBits + " bits",
+ keyLengthBits / 8, key.getEncoded().length);
+ }
+ }
+
+ @Test
+ public void testPBKDF2DifferentKeyLengthsProduceDifferentKeys() throws Exception {
+ char[] password = "Zaq12wsXCde34rfV".toCharArray();
+ byte[] salt = "NaClCommonSaltRockSaltSeaSalt".getBytes();
+ int iterationCount = 120000;
+
+ SecretKeyFactory pbkdf = SecretKeyFactory.getInstance("PBKDF2", "OpenSSLFIPSProvider");
+
+ SecretKey key128 = pbkdf.generateSecret(new PBEKeySpec(password, salt, iterationCount, 128));
+ SecretKey key256 = pbkdf.generateSecret(new PBEKeySpec(password, salt, iterationCount, 256));
+
+ // The 128-bit key must be a prefix of the 256-bit key (same PRF, same inputs).
+ byte[] k128 = key128.getEncoded();
+ byte[] k256 = key256.getEncoded();
+ byte[] k256prefix = new byte[16];
+ System.arraycopy(k256, 0, k256prefix, 0, 16);
+ assertArrayEquals("Shorter key must be a prefix of the longer key", k128, k256prefix);
+ }
+
+ @Test
+ public void testPBKDF2RejectsBelowFIPSMinimumIterations() throws Exception {
+ SecretKeyFactory pbkdf = SecretKeyFactory.getInstance("PBKDF2", "OpenSSLFIPSProvider");
+ char[] password = "password".toCharArray();
+ byte[] salt = "NaClCommonSaltRockSaltSeaSalt".getBytes();
+
+ for (int iterations : new int[]{1, 500, 999}) {
+ try {
+ pbkdf.generateSecret(new PBEKeySpec(password, salt, iterations));
+ fail("Expected InvalidKeySpecException for iteration count " + iterations);
+ } catch (java.security.spec.InvalidKeySpecException expected) {
+ // correct
+ }
+ }
+ }
+
+ @Test
+ public void testPBKDF2AcceptsFIPSMinimumIterations() throws Exception {
+ SecretKeyFactory pbkdf = SecretKeyFactory.getInstance("PBKDF2", "OpenSSLFIPSProvider");
+ char[] password = "password".toCharArray();
+ byte[] salt = "NaClCommonSaltRockSaltSeaSalt".getBytes();
+
+ SecretKey key = pbkdf.generateSecret(new PBEKeySpec(password, salt, 1000));
+ assertNotEquals("Key at FIPS minimum iterations must be non-empty", 0, key.getEncoded().length);
+ }
+
+ @Test
+ public void testTranslateKeyRejectsBelowFIPSMinimumIterations() throws Exception {
+ SecretKeyFactory pbkdf = SecretKeyFactory.getInstance("PBKDF2", "OpenSSLFIPSProvider");
+
+ final char[] password = "password".toCharArray();
+ final byte[] salt = "NaClCommonSaltRockSaltSeaSalt".getBytes();
+
+ for (int iterations : new int[]{1, 500, 999}) {
+ final int it = iterations;
+ PBEKey foreign = new PBEKey() {
+ public char[] getPassword() { return password.clone(); }
+ public byte[] getSalt() { return salt.clone(); }
+ public int getIterationCount() { return it; }
+ public byte[] getEncoded() { return new byte[64]; }
+ public String getAlgorithm() { return "PBKDF2WithHmacSHA512"; }
+ public String getFormat() { return "RAW"; }
+ };
+ try {
+ pbkdf.translateKey(foreign);
+ fail("Expected InvalidKeyException for iteration count " + iterations);
+ } catch (InvalidKeyException expected) {
+ // correct
+ }
+ }
+ }
+
@BeforeClass
public static void addProvider() throws Exception {
Security.addProvider(new OpenSSLFIPSProvider());
diff --git a/src/test/native/drbg_test.c b/src/test/native/drbg_test.c
index 02e4698..b6ae436 100644
--- a/src/test/native/drbg_test.c
+++ b/src/test/native/drbg_test.c
@@ -15,11 +15,11 @@
*
*/
#include "jssl.h"
-#include
+#include "drbg.h"
int result = 0;
-void test_xxx_drbg(const char *test, const char *algo) {
- DRBG *drbg = create_DRBG(algo, NULL);
+void test_xxx_drbg(OSSL_LIB_CTX *libctx, const char *test, const char *algo) {
+ DRBG *drbg = create_DRBG(libctx, algo, NULL);
byte output1[10] = {0}, output2[10] = {0}, output3[10] = {0};
next_rand(drbg, output1, 10);
next_rand(drbg, output2, 10);
@@ -39,21 +39,21 @@ void test_xxx_drbg(const char *test, const char *algo) {
free_DRBG(&drbg);
}
-void test_basic_hmac_drbg() {
- test_xxx_drbg("test_basic_hmac_drbg", "HMAC-DRBG");
+void test_basic_hmac_drbg(OSSL_LIB_CTX *libctx) {
+ test_xxx_drbg(libctx, "test_basic_hmac_drbg", "HMAC-DRBG");
}
-void test_basic_hash_drbg() {
- test_xxx_drbg("test_basic_hash_drbg", "HASH-DRBG");
+void test_basic_hash_drbg(OSSL_LIB_CTX *libctx) {
+ test_xxx_drbg(libctx, "test_basic_hash_drbg", "HASH-DRBG");
}
-void test_basic_ctr_drbg() {
- test_xxx_drbg("test_basic_hash_drbg", "CTR-DRBG");
+void test_basic_ctr_drbg(OSSL_LIB_CTX *libctx) {
+ test_xxx_drbg(libctx, "test_basic_ctr_drbg", "CTR-DRBG");
}
-void test_xxx_drbg_fails(const char *test, const char *algo) {
+void test_xxx_drbg_fails(OSSL_LIB_CTX *libctx, const char *test, const char *algo) {
DRBG *drbg = NULL;
- if (NULL == (drbg = create_DRBG(algo, NULL))) {
+ if (NULL == (drbg = create_DRBG(libctx, algo, NULL))) {
printf("drbg_test/%s: PASS\n", test);
} else {
printf("drbg_test/%s: FAIL\n", test);
@@ -61,17 +61,17 @@ void test_xxx_drbg_fails(const char *test, const char *algo) {
}
}
-void test_seed_src_drbg_fails() {
- test_xxx_drbg_fails("test_seed_src_drbg_fails", "SEED-SRC");
+void test_seed_src_drbg_fails(OSSL_LIB_CTX *libctx) {
+ test_xxx_drbg_fails(libctx, "test_seed_src_drbg_fails", "SEED-SRC");
}
-void test_test_rand_drbg_fails() {
- test_xxx_drbg_fails("test_test_rand_drbg_fails", "TEST-RAND");
+void test_test_rand_drbg_fails(OSSL_LIB_CTX *libctx) {
+ test_xxx_drbg_fails(libctx, "test_test_rand_drbg_fails", "TEST-RAND");
}
-void test_rand_int_num_bits(const char *algo, int num_bits) {
+void test_rand_int_num_bits(OSSL_LIB_CTX *libctx, const char *algo, int num_bits) {
DRBG *drbg;
- if (NULL == (drbg = create_DRBG(algo, NULL))) {
+ if (NULL == (drbg = create_DRBG(libctx, algo, NULL))) {
printf("drbg_test/test_rand_int_num_bits: FAIL\n");
} else {
printf("next_rand_int(%d) = %x (PASS)\n", num_bits, next_rand_int(drbg, num_bits));
@@ -81,15 +81,15 @@ void test_rand_int_num_bits(const char *algo, int num_bits) {
int main(int argc, char ** argv) {
OSSL_LIB_CTX *libctx = load_openssl_fips_provider("/usr/local/ssl/openssl.cnf");
- test_basic_hmac_drbg();
- test_basic_hash_drbg();
- test_basic_ctr_drbg();
- test_seed_src_drbg_fails();
- test_test_rand_drbg_fails();
- test_rand_int_num_bits("CTR-DRBG", 1);
- test_rand_int_num_bits("HMAC-DRBG", 16);
- test_rand_int_num_bits("HASH-DRBG", 30);
- test_rand_int_num_bits("HASH-DRBG", 32);
+ test_basic_hmac_drbg(libctx);
+ test_basic_hash_drbg(libctx);
+ test_basic_ctr_drbg(libctx);
+ test_seed_src_drbg_fails(libctx);
+ test_test_rand_drbg_fails(libctx);
+ test_rand_int_num_bits(libctx, "CTR-DRBG", 1);
+ test_rand_int_num_bits(libctx, "HMAC-DRBG", 16);
+ test_rand_int_num_bits(libctx, "HASH-DRBG", 30);
+ test_rand_int_num_bits(libctx, "HASH-DRBG", 32);
unload_libctx(libctx);
return result;
}
diff --git a/src/test/native/mac.c b/src/test/native/mac.c
index 15ab526..2079a48 100644
--- a/src/test/native/mac.c
+++ b/src/test/native/mac.c
@@ -87,7 +87,7 @@ void run_test(mac_context *ctx) {
void test_cmac(OSSL_LIB_CTX *libctx) {
printf("Testing CMAC: ");
mac_params *params = init_mac_params("AES-256-CBC", NULL, NULL, 0, 0);
- mac_context *ctx = mac_init("CMAC", key, 32, params, NULL);
+ mac_context *ctx = mac_init(libctx, "CMAC", key, 32, params, NULL);
run_test(ctx);
free_mac_context(&ctx);
free(params);
@@ -95,8 +95,8 @@ void test_cmac(OSSL_LIB_CTX *libctx) {
void test_hmac_sha1(OSSL_LIB_CTX *libctx) {
printf("Testing HMAC with SHA-1: ");
- mac_params *params = init_mac_params(NULL, "SHA1", NULL, 0, 0);
- mac_context *ctx = mac_init("HMAC", key, 64, params, NULL);
+ mac_params *params = init_mac_params(NULL, "SHA1", NULL, 0, 0);
+ mac_context *ctx = mac_init(libctx, "HMAC", key, 64, params, NULL);
run_test(ctx);
free_mac_context(&ctx);
free(params);
@@ -105,7 +105,7 @@ void test_hmac_sha1(OSSL_LIB_CTX *libctx) {
void test_hmac_sha3(OSSL_LIB_CTX *libctx) {
printf("Testing HMAC with SHA3-512: ");
mac_params *params = init_mac_params(NULL, "SHA3-512", NULL, 0, 0);
- mac_context *ctx = mac_init("HMAC", key, 64, params, NULL);
+ mac_context *ctx = mac_init(libctx, "HMAC", key, 64, params, NULL);
run_test(ctx);
free_mac_context(&ctx);
free(params);
@@ -114,22 +114,22 @@ void test_hmac_sha3(OSSL_LIB_CTX *libctx) {
void test_gmac(OSSL_LIB_CTX *libctx) {
printf("Testing GMAC: ");
mac_params *params = init_mac_params("AES-128-GCM", NULL, iv, sizeof(iv), 0);
- mac_context *ctx = mac_init("GMAC", key, 16, params, NULL);
- run_test(ctx);
+ mac_context *ctx = mac_init(libctx, "GMAC", key, 16, params, NULL);
+ run_test(ctx);
free_mac_context(&ctx);
free(params);
}
void test_kmac128(OSSL_LIB_CTX *libctx) {
printf("Testing KMAC-128: ");
- mac_context *ctx = mac_init("KMAC-128", key, 16, NULL, NULL);
+ mac_context *ctx = mac_init(libctx, "KMAC-128", key, 16, NULL, NULL);
run_test(ctx);
free_mac_context(&ctx);
}
void test_kmac256(OSSL_LIB_CTX *libctx) {
printf("Testing KMAC-256: ");
- mac_context *ctx = mac_init("KMAC-256", key, 32, NULL, NULL);
+ mac_context *ctx = mac_init(libctx, "KMAC-256", key, 32, NULL, NULL);
run_test(ctx);
free_mac_context(&ctx);
}