From 96db7afc776f53a32242c462c2f88fe89555b4e5 Mon Sep 17 00:00:00 2001 From: Pushkar Kulkarni Date: Tue, 12 May 2026 16:01:13 +0200 Subject: [PATCH] Miscellaneous security hardening fixes --- Makefile | 16 +- pom.xml | 2 +- .../openssl/cipher/OpenSSLCipher.java | 342 +++++++++++++++--- .../canonical/openssl/drbg/OpenSSLDrbg.java | 19 +- .../canonical/openssl/kdf/OpenSSLPBKDF2.java | 105 +++++- .../canonical/openssl/key/KeyConverter.java | 7 +- .../openssl/keyagreement/DHKeyAgreement.java | 10 +- .../keyagreement/ECDHKeyAgreement.java | 10 +- .../keyagreement/OpenSSLKeyAgreement.java | 40 +- .../keyencapsulation/OpenSSLKEMRSA.java | 6 +- .../keypairgenerator/DHKeyPairGenerator.java | 43 +++ .../keypairgenerator/ECKeyPairGenerator.java | 69 ++++ .../keypairgenerator/EncodedPrivateKey.java | 71 ++++ .../keypairgenerator/EncodedPublicKey.java | 46 +++ .../OpenSSLKeyPairGenerator.java | 97 +++++ .../openssl/mac/CMACwithAes256CBC.java | 8 +- .../openssl/mac/GMACWithAes128GCM.java | 32 +- .../canonical/openssl/mac/HMACwithSHA1.java | 10 +- .../openssl/mac/HMACwithSHA3_512.java | 8 +- .../com/canonical/openssl/mac/KMAC128.java | 10 +- .../com/canonical/openssl/mac/KMAC256.java | 8 +- .../com/canonical/openssl/mac/OpenSSLMAC.java | 40 +- .../com/canonical/openssl/md/OpenSSLMD.java | 8 +- .../openssl/provider/OpenSSLFIPSProvider.java | 4 + .../openssl/signature/OpenSSLSignature.java | 74 +++- .../openssl/util/NativeMemoryCleaner.java | 2 +- src/main/native/c/KeyConverter.c | 6 +- src/main/native/c/OpenSSLCipher.c | 150 +++++--- src/main/native/c/OpenSSLDrbg.c | 104 ++++-- src/main/native/c/OpenSSLKeyAgreement.c | 47 ++- src/main/native/c/OpenSSLKeyPairGenerator.c | 156 ++++++++ src/main/native/c/OpenSSLMAC.c | 98 +++-- src/main/native/c/OpenSSLMD.c | 14 +- src/main/native/c/OpenSSLPBKDF2.c | 34 +- src/main/native/c/OpenSSLSignature.c | 66 +++- src/main/native/c/RSAKEMDecapsulator.c | 15 +- src/main/native/c/RSAKEMEncapsulator.c | 11 +- src/main/native/c/cipher.c | 84 +++-- src/main/native/c/drbg.c | 71 ++-- src/main/native/c/evp_utils.c | 6 +- src/main/native/c/init.c | 60 +-- src/main/native/c/jni_utils.c | 25 +- src/main/native/c/kdf.c | 16 +- src/main/native/c/keyagreement.c | 6 +- src/main/native/c/mac.c | 19 +- src/main/native/c/md.c | 7 +- src/main/native/c/signature.c | 45 ++- src/main/native/include/drbg.h | 8 +- .../include/jni/OpenSSLKeyPairGenerator.h | 37 ++ src/main/native/include/jni/OpenSSLPBKDF2.h | 17 +- src/main/native/include/jni_utils.h | 2 + src/main/native/include/jssl.h | 1 + src/main/native/include/mac.h | 3 +- src/main/native/include/signature.h | 3 +- src/test/consumer-snap/snapcraft.yaml | 2 +- src/test/java/CipherTest.java | 83 +++++ src/test/java/KeyAgreementTest.java | 4 +- src/test/java/KeyConverterTest.java | 35 ++ src/test/java/MacTest.java | 35 +- src/test/java/SecretKeyFactoryTest.java | 117 ++++++ src/test/native/drbg_test.c | 52 +-- src/test/native/mac.c | 16 +- 62 files changed, 2070 insertions(+), 472 deletions(-) create mode 100644 src/main/java/com/canonical/openssl/keypairgenerator/DHKeyPairGenerator.java create mode 100644 src/main/java/com/canonical/openssl/keypairgenerator/ECKeyPairGenerator.java create mode 100644 src/main/java/com/canonical/openssl/keypairgenerator/EncodedPrivateKey.java create mode 100644 src/main/java/com/canonical/openssl/keypairgenerator/EncodedPublicKey.java create mode 100644 src/main/java/com/canonical/openssl/keypairgenerator/OpenSSLKeyPairGenerator.java create mode 100644 src/main/native/c/OpenSSLKeyPairGenerator.c create mode 100644 src/main/native/include/jni/OpenSSLKeyPairGenerator.h 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); }