diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptRandom.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptRandom.java index 1a56a8de..a11aa80e 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptRandom.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptRandom.java @@ -44,10 +44,7 @@ public final class WolfCryptRandom extends SecureRandomSpi { * Create new WolfCryptRandom object */ public WolfCryptRandom() { - - this.rng = new Rng(); - this.rng.init(); - + checkRngInitialized(); log("initialized new object"); } @@ -69,19 +66,45 @@ protected synchronized byte[] engineGenerateSeed(int numBytes) Rng.RNG_MAX_BLOCK_LEN); } + checkRngInitialized(); + return rng.generateBlock(numBytes); } @Override protected synchronized void engineNextBytes(byte[] bytes) { + if (bytes == null) { + throw new NullPointerException("Input byte[] should not be null"); + } + + checkRngInitialized(); + rng.generateBlock(bytes); } @Override protected synchronized void engineSetSeed(byte[] seed) { + + if (seed == null) { + throw new NullPointerException("Input seed[] should not be null"); + } + /* wolfCrypt reseeds internally automatically */ log("setSeed() not supported by wolfJCE"); + + } + + /** + * Initialize the RNG if needed (null). This handles cases where the object + * was created through deserialization, reflection, etc. and the + * constructor was not called. + */ + private void checkRngInitialized() { + if (this.rng == null) { + this.rng = new Rng(); + this.rng.init(); + } } private void log(String msg) { @@ -138,12 +161,25 @@ private synchronized void writeObject(ObjectOutputStream out) private synchronized void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - if (rng == null) { - this.rng = new Rng(); - this.rng.init(); - } - in.defaultReadObject(); + + checkRngInitialized(); + } + + @Override + public String toString() { + /* Native wolfCrypt DRBG details: + * Hash_DRBG = DRBG implementation + * SHA-256 = hash function used in Hash_DRBG implementation + * 128 = security strength in bits + * reseed_only = NIST implementation default, prediction resistance + * not enabled for every generate call, only when explicitly + * reseeded. + * + * This output format matches other JCE providers, some callers + * may expect this format. + */ + return "Hash_DRBG,SHA-256,128,reseed_only"; } } diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptRandomTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptRandomTest.java index 2a7e264a..46871cb2 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptRandomTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptRandomTest.java @@ -36,6 +36,12 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + import java.security.Security; import java.security.Provider; import java.security.SecureRandom; @@ -345,5 +351,92 @@ public void testGenerateSeedWithZeroArgument() assertNotNull(seed); assertEquals(0, seed.length); } + + @Test + public void testSerializationDeserialization() + throws NoSuchProviderException, NoSuchAlgorithmException, + IOException, ClassNotFoundException { + + /* Create original SecureRandom instance */ + SecureRandom orig = SecureRandom.getInstance("HashDRBG", "wolfJCE"); + assertNotNull(orig); + + /* Serialize the SecureRandom instance */ + byte[] serialized = serialize(orig); + assertNotNull(serialized); + assertTrue(serialized.length > 0); + + /* Deserialize the SecureRandom instance */ + SecureRandom copy = deserialize(serialized); + assertNotNull(copy); + + /* Test that both original and deserialized instances can generate + * random numbers without throwing NullPointerException. + * This test would fail without the lazy loading fix in + * WolfCryptRandom.engineNextBytes() and engineGenerateSeed() */ + byte[] origBytes = new byte[32]; + byte[] copyBytes = new byte[32]; + + /* This should not throw NullPointerException */ + orig.nextBytes(origBytes); + copy.nextBytes(copyBytes); + + /* Also test generateSeed() to ensure it works after deserialization */ + byte[] origSeed = orig.generateSeed(16); + byte[] copySeed = copy.generateSeed(16); + + assertNotNull(origSeed); + assertNotNull(copySeed); + assertEquals(16, origSeed.length); + assertEquals(16, copySeed.length); + + /* Verify we got actual random data */ + assertNotNull(origBytes); + assertNotNull(copyBytes); + assertEquals(32, origBytes.length); + assertEquals(32, copyBytes.length); + + /* Random bytes should not be all zeros */ + boolean origHasNonZero = false; + boolean copyHasNonZero = false; + for (int i = 0; i < 32; i++) { + if (origBytes[i] != 0) { + origHasNonZero = true; + } + if (copyBytes[i] != 0) { + copyHasNonZero = true; + } + } + assertTrue("Original random bytes should not be all zeros", + origHasNonZero); + assertTrue("Deserialized random bytes should not be all zeros", + copyHasNonZero); + + /* The two instances should generate different random sequences */ + assertFalse("Original and deserialized instances should generate " + + "different random sequences", Arrays.equals(origBytes, copyBytes)); + } + + /* + * Serialize a SecureRandom object to byte array + */ + private byte[] serialize(SecureRandom sr) throws IOException { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos)) { + oos.writeObject(sr); + return bos.toByteArray(); + } + } + + /* + * Deserialize a SecureRandom object from byte array + */ + private SecureRandom deserialize(byte[] serialized) + throws IOException, ClassNotFoundException { + try (ByteArrayInputStream bis = new ByteArrayInputStream(serialized); + ObjectInputStream ois = new ObjectInputStream(bis)) { + return (SecureRandom) ois.readObject(); + } + } }