From b87d9cf2c9d905c15f4c957d42361b1a72974edf Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Mon, 26 Feb 2024 21:52:40 +0000 Subject: [PATCH] 8325506: Ensure randomness is only read from provided SecureRandom object Reviewed-by: kdriver, valeriep --- .../sun/security/util/SignatureUtil.java | 5 +- .../security/provider/all/Deterministic.java | 290 ++++++++++++++++++ test/lib-test/jdk/test/lib/AssertsTest.java | 68 +++- .../lib/security/SeededSecureRandomTest.java | 63 ++++ test/lib/jdk/test/lib/Asserts.java | 62 +++- .../test/lib/security/SeededSecureRandom.java | 68 ++++ 6 files changed, 551 insertions(+), 5 deletions(-) create mode 100644 test/jdk/sun/security/provider/all/Deterministic.java create mode 100644 test/lib-test/jdk/test/lib/security/SeededSecureRandomTest.java create mode 100644 test/lib/jdk/test/lib/security/SeededSecureRandom.java diff --git a/src/java.base/share/classes/sun/security/util/SignatureUtil.java b/src/java.base/share/classes/sun/security/util/SignatureUtil.java index 3d415814ba4..155adc198d0 100644 --- a/src/java.base/share/classes/sun/security/util/SignatureUtil.java +++ b/src/java.base/share/classes/sun/security/util/SignatureUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -297,6 +297,9 @@ public class SignatureUtil { } else { keyAlgorithm = signatureAlgorithm.substring(with + 4); } + if (keyAlgorithm.endsWith("INP1363FORMAT")) { + keyAlgorithm = keyAlgorithm.substring(0, keyAlgorithm.length() - 13); + } if (keyAlgorithm.equalsIgnoreCase("ECDSA")) { keyAlgorithm = "EC"; } diff --git a/test/jdk/sun/security/provider/all/Deterministic.java b/test/jdk/sun/security/provider/all/Deterministic.java new file mode 100644 index 00000000000..66d674da9bf --- /dev/null +++ b/test/jdk/sun/security/provider/all/Deterministic.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8325506 + * @library /test/lib + * @modules java.base/sun.security.util + * @run main/othervm Deterministic + * @summary confirm the output of random calculations are determined + * by the SecureRandom parameters + */ + +import jdk.test.lib.Asserts; +import jdk.test.lib.security.SeededSecureRandom; +import sun.security.util.SignatureUtil; + +import javax.crypto.Cipher; +import javax.crypto.KEM; +import javax.crypto.KeyAgreement; +import javax.crypto.KeyGenerator; +import javax.crypto.spec.ChaCha20ParameterSpec; +import javax.crypto.spec.DHParameterSpec; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.DSAParameterSpec; +import java.security.spec.PSSParameterSpec; +import java.util.Arrays; +import java.util.Objects; + +public class Deterministic { + + private static final long SEED = SeededSecureRandom.seed(); + private static int hash = 0; + + public static void main(String[] args) throws Exception { + + for (var p : Security.getProviders()) { + var name = p.getName(); + if (name.equals("SunMSCAPI") || name.startsWith("SunPKCS11")) { + System.out.println("Skipped native provider " + name); + continue; + } + for (var s : p.getServices()) { + switch (s.getType()) { + case "KeyPairGenerator" -> testKeyPairGenerator(s); + case "KeyGenerator" -> testKeyGenerator(s); + case "Signature" -> testSignature(s); + case "KEM" -> testKEM(s); + case "KeyAgreement" -> testKeyAgreement(s); + case "Cipher" -> testCipher(s); + case "AlgorithmParameterGenerator" -> testAlgorithmParameterGenerator(s); + } + } + } + // Run twice and this value should be the same for the same SEED + System.out.println("Final hash: " + hash); + } + + static void testCipher(Provider.Service s) throws Exception { + var alg = s.getAlgorithm(); + System.out.println(s.getProvider().getName() + + " " + s.getType() + "." + alg); + if (alg.contains("Wrap") || alg.contains("KW")) { + System.out.println(" Ignored"); + return; + } + Key key; + AlgorithmParameterSpec spec; + if (alg.startsWith("PBE")) { + key = new SecretKeySpec("isthisakey".getBytes(StandardCharsets.UTF_8), "PBE"); + // Some cipher requires salt to be 8 byte long + spec = new PBEParameterSpec("saltsalt".getBytes(StandardCharsets.UTF_8), 100); + } else { + key = generateKey(alg.split("/")[0], s.getProvider()); + if (!alg.contains("/") || alg.contains("/ECB/")) { + spec = null; + } else { + if (alg.contains("/GCM/")) { + spec = new GCMParameterSpec(128, new SeededSecureRandom(SEED + 1).generateSeed(16)); + } else if (alg.equals("ChaCha20")) { + spec = new ChaCha20ParameterSpec(new SeededSecureRandom(SEED + 2).generateSeed(12), 128); + } else if (alg.contains("ChaCha20")) { + spec = new IvParameterSpec(new SeededSecureRandom(SEED + 3).generateSeed(12)); + } else { + spec = new IvParameterSpec(new SeededSecureRandom(SEED + 4).generateSeed(16)); + } + } + } + var c = Cipher.getInstance(alg, s.getProvider()); + c.init(Cipher.ENCRYPT_MODE, key, spec, new SeededSecureRandom(SEED)); + // Some cipher requires plaintext to be 16 byte long + var ct1 = c.doFinal("asimpleplaintext".getBytes(StandardCharsets.UTF_8)); + // Some cipher requires IV to be different, so re-instantiate a cipher + c = Cipher.getInstance(alg, s.getProvider()); + c.init(Cipher.ENCRYPT_MODE, key, spec, new SeededSecureRandom(SEED)); + var ct2 = c.doFinal("asimpleplaintext".getBytes(StandardCharsets.UTF_8)); + Asserts.assertEqualsByteArray(ct1, ct2); + hash = Objects.hash(hash, Arrays.hashCode(ct1)); + System.out.println(" Passed"); + } + + static void testAlgorithmParameterGenerator(Provider.Service s) throws Exception { + System.out.println(s.getProvider().getName() + + " " + s.getType() + "." + s.getAlgorithm()); + var apg = AlgorithmParameterGenerator.getInstance(s.getAlgorithm(), s.getProvider()); + apg.init(1024, new SeededSecureRandom(SEED)); + var p1 = apg.generateParameters().getParameterSpec(AlgorithmParameterSpec.class); + apg.init(1024, new SeededSecureRandom(SEED)); + var p2 = apg.generateParameters().getParameterSpec(AlgorithmParameterSpec.class); + if (p1 instanceof DSAParameterSpec d1 && p2 instanceof DSAParameterSpec d2) { + Asserts.assertEQ(d1.getG(), d2.getG()); + Asserts.assertEQ(d1.getP(), d2.getP()); + Asserts.assertEQ(d1.getQ(), d2.getQ()); + hash = Objects.hash(hash, d1.getG(), d1.getP(), d1.getQ()); + } else if (p1 instanceof DHParameterSpec d1 && p2 instanceof DHParameterSpec d2){ + Asserts.assertEQ(d1.getG(), d2.getG()); + Asserts.assertEQ(d1.getP(), d2.getP()); + Asserts.assertEQ(d1.getL(), d2.getL()); + hash = Objects.hash(hash, d1.getG(), d1.getP(), d1.getL()); + } else { + Asserts.assertEQ(p1, p2); + hash = Objects.hash(hash, p1); + } + System.out.println(" Passed"); + } + + private static void testSignature(Provider.Service s) throws Exception { + System.out.println(s.getProvider().getName() + + " " + s.getType() + "." + s.getAlgorithm()); + String keyAlg = SignatureUtil.extractKeyAlgFromDwithE(s.getAlgorithm()); + if (keyAlg == null) { + if (s.getAlgorithm().equals("HSS/LMS")) { + // We don't support HSS/LMS key generation and signing + System.out.println(" Ignored: HSS/LMS"); + return; + } else { + keyAlg = s.getAlgorithm(); // EdDSA etc + } + } + var sk = generateKeyPair(keyAlg, 0).getPrivate(); + var sig = Signature.getInstance(s.getAlgorithm(), s.getProvider()); + try { + if (keyAlg.equals("RSASSA-PSS")) { + sig.setParameter(PSSParameterSpec.DEFAULT); + } + sig.initSign(sk, new SeededSecureRandom(SEED)); + sig.update(new byte[20]); + var s1 = sig.sign(); + sig.initSign(sk, new SeededSecureRandom(SEED)); + sig.update(new byte[20]); + var s2 = sig.sign(); + Asserts.assertEqualsByteArray(s1, s2); + hash = Objects.hash(hash, Arrays.hashCode(s1)); + System.out.println(" Passed"); + } catch (InvalidKeyException ike) { + System.out.println(" Ignored: " + ike.getMessage()); + } + } + + static void testKeyPairGenerator(Provider.Service s) throws Exception { + System.out.println(s.getProvider().getName() + + " " + s.getType() + "." + s.getAlgorithm()); + var kp1 = generateKeyPair(s.getAlgorithm(), 0); + var kp2 = generateKeyPair(s.getAlgorithm(), 0); + Asserts.assertEqualsByteArray( + kp1.getPrivate().getEncoded(), kp2.getPrivate().getEncoded()); + Asserts.assertEqualsByteArray( + kp1.getPublic().getEncoded(), kp2.getPublic().getEncoded()); + hash = Objects.hash(hash, + Arrays.hashCode(kp1.getPrivate().getEncoded()), + Arrays.hashCode(kp1.getPublic().getEncoded())); + System.out.println(" Passed"); + } + + static KeyPair generateKeyPair(String alg, int offset) throws Exception { + var g = KeyPairGenerator.getInstance(alg); + var size = switch (g.getAlgorithm()) { + case "RSA", "RSASSA-PSS", "DSA", "DiffieHellman" -> 1024; + case "EC" -> 256; + case "EdDSA", "Ed25519", "XDH", "X25519" -> 255; + case "Ed448", "X448" -> 448; + default -> throw new UnsupportedOperationException(alg); + }; + g.initialize(size, new SeededSecureRandom(SEED + offset)); + return g.generateKeyPair(); + } + + static void testKeyGenerator(Provider.Service s) throws Exception { + System.out.println(s.getProvider().getName() + + " " + s.getType() + "." + s.getAlgorithm()); + if (s.getAlgorithm().startsWith("SunTls")) { + System.out.println(" Ignored"); + return; + } + var k1 = generateKey(s.getAlgorithm(), s.getProvider()); + var k2 = generateKey(s.getAlgorithm(), s.getProvider()); + Asserts.assertEqualsByteArray(k1.getEncoded(), k2.getEncoded()); + hash = Objects.hash(hash, + Arrays.hashCode(k1.getEncoded())); + System.out.println(" Passed"); + } + + static Key generateKey(String s, Provider p) throws Exception { + if (s.startsWith("AES_")) { + var g = KeyGenerator.getInstance("AES", p); + g.init(Integer.parseInt(s.substring(4)), new SeededSecureRandom(SEED + 1)); + return g.generateKey(); + } if (s.startsWith("ChaCha")) { + var g = KeyGenerator.getInstance("ChaCha20", p); + g.init(new SeededSecureRandom(SEED + 2)); + return g.generateKey(); + } if (s.equals("RSA")) { + return generateKeyPair("RSA", 3).getPublic(); + } else { + var g = KeyGenerator.getInstance(s, p); + g.init(new SeededSecureRandom(SEED + 4)); + return g.generateKey(); + } + } + + static void testKEM(Provider.Service s) throws Exception { + System.out.println(s.getProvider().getName() + + " " + s.getType() + "." + s.getAlgorithm()); + String keyAlg = getKeyAlgFromKEM(s.getAlgorithm()); + var kp = generateKeyPair(keyAlg, 10); + var kem = KEM.getInstance(s.getAlgorithm(), s.getProvider()); + var e1 = kem.newEncapsulator(kp.getPublic(), null, new SeededSecureRandom(SEED)); + var enc1 = e1.encapsulate(); + var e2 = kem.newEncapsulator(kp.getPublic(), null, new SeededSecureRandom(SEED)); + var enc2 = e2.encapsulate(); + Asserts.assertEqualsByteArray(enc1.encapsulation(), enc2.encapsulation()); + Asserts.assertEqualsByteArray(enc1.key().getEncoded(), enc2.key().getEncoded()); + hash = Objects.hash(hash, Arrays.hashCode(enc1.encapsulation()), + Arrays.hashCode(enc1.key().getEncoded())); + System.out.println(" Passed"); + } + + static void testKeyAgreement(Provider.Service s) throws Exception { + System.out.println(s.getProvider().getName() + + " " + s.getType() + "." + s.getAlgorithm()); + String keyAlg = getKeyAlgFromKEM(s.getAlgorithm()); + var kpS = generateKeyPair(keyAlg, 11); + var kpR = generateKeyPair(keyAlg, 12); + var ka = KeyAgreement.getInstance(s.getAlgorithm(), s.getProvider()); + ka.init(kpS.getPrivate(), new SeededSecureRandom(SEED)); + ka.doPhase(kpR.getPublic(), true); + var sc1 = ka.generateSecret(); + ka.init(kpS.getPrivate(), new SeededSecureRandom(SEED)); + ka.doPhase(kpR.getPublic(), true); + var sc2 = ka.generateSecret(); + + Asserts.assertEqualsByteArray(sc1, sc2); + hash = Objects.hash(hash, Arrays.hashCode(sc1)); + System.out.println(" Passed"); + } + + static String getKeyAlgFromKEM(String algorithm) { + return switch (algorithm) { + case "DHKEM" -> "X25519"; + case "ECDH" -> "EC"; + default -> algorithm; + }; + } +} diff --git a/test/lib-test/jdk/test/lib/AssertsTest.java b/test/lib-test/jdk/test/lib/AssertsTest.java index a4c739d64ba..9acb6be98c5 100644 --- a/test/lib-test/jdk/test/lib/AssertsTest.java +++ b/test/lib-test/jdk/test/lib/AssertsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,7 +21,11 @@ * questions. */ +import jdk.test.lib.Asserts; + import java.lang.SuppressWarnings; +import java.util.Arrays; +import java.util.HexFormat; import static jdk.test.lib.Asserts.*; @@ -49,6 +53,7 @@ public class AssertsTest { testLessThan(); testLessThanOrEqual(); testEquals(); + testEqualsByteArray(); testGreaterThanOrEqual(); testGreaterThan(); testNotEquals(); @@ -90,6 +95,26 @@ public class AssertsTest { expectFail(Assertion.LTE, 2, null); } + private static void testEqualsByteArray() throws Exception { + byte[] b1 = new byte[1]; + byte[] b11 = new byte[1]; + byte[] b2 = new byte[2]; + + expectPass(Assertion.EQBA, b1, b1); + expectPass(Assertion.EQBA, b1, b11); + expectPass(Assertion.EQBA, (byte[])null, (byte[])null); + expectPass(Assertion.NEQBA, b1, b2); + expectPass(Assertion.NEQBA, b1, (byte[])null); + expectPass(Assertion.NEQBA, (byte[])null, b1); + + expectFail(Assertion.EQBA, b1, b2); + expectFail(Assertion.EQBA, (byte[])null, b1); + expectFail(Assertion.EQBA, b1, (byte[])null); + expectFail(Assertion.NEQBA, b1, b1); + expectFail(Assertion.NEQBA, b1, b11); + expectFail(Assertion.NEQBA, (byte[])null, (byte[])null); + } + private static void testGreaterThanOrEqual() throws Exception { expectPass(Assertion.GTE, 1, 1); expectPass(Assertion.GTE, 2, 1); @@ -191,10 +216,47 @@ public class AssertsTest { " to throw a RuntimeException"); } + private static void expectPass(Assertion assertion, byte[] b1, byte[] b2) + throws Exception { + if (assertion == Assertion.EQBA) { + String msg = "Expected " + Assertion.asString("assertEqualsByteArray", + Arrays.toString(b1), Arrays.toString(b2)) + " to pass"; + Asserts.assertEqualsByteArray(b1, b2, msg); + } else { + String msg = "Expected " + Assertion.asString("assertNotEqualsByteArray", + Arrays.toString(b1), Arrays.toString(b2)) + " to pass"; + Asserts.assertNotEqualsByteArray(b1, b2, msg); + } + } + + private static void expectFail(Assertion assertion, byte[] b1, byte[] b2) + throws Exception { + if (assertion == Assertion.EQBA) { + try { + Asserts.assertEqualsByteArray(b1, b2); + } catch (RuntimeException e) { + return; + } + throw new Exception("Expected " + + Assertion.asString("assertEqualsByteArray", + Arrays.toString(b1), Arrays.toString(b2)) + + " to throw a RuntimeException"); + } else { + try { + Asserts.assertNotEqualsByteArray(b1, b2); + } catch (RuntimeException e) { + return; + } + throw new Exception("Expected " + + Assertion.asString("assertNotEqualsByteArray", + Arrays.toString(b1), Arrays.toString(b2)) + + " to throw a RuntimeException"); + } + } } enum Assertion { - LT, LTE, EQ, GTE, GT, NE, NULL, NOTNULL, FALSE, TRUE; + LT, LTE, EQ, EQBA, NEQBA, GTE, GT, NE, NULL, NOTNULL, FALSE, TRUE; @SuppressWarnings("unchecked") public static > void run(Assertion assertion, T ... args) { @@ -262,7 +324,7 @@ enum Assertion { } } - private static String asString(String assertion, Object ... args) { + public static String asString(String assertion, Object ... args) { if (args == null) { return String.format("%s(null)", assertion); } diff --git a/test/lib-test/jdk/test/lib/security/SeededSecureRandomTest.java b/test/lib-test/jdk/test/lib/security/SeededSecureRandomTest.java new file mode 100644 index 00000000000..f5f74a16f72 --- /dev/null +++ b/test/lib-test/jdk/test/lib/security/SeededSecureRandomTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.test.lib.security; + +import jdk.test.lib.Asserts; + +/* + * @test + * @library /test/lib + * @run main/othervm jdk.test.lib.security.SeededSecureRandomTest + */ +public class SeededSecureRandomTest { + + private static final String PROP = "secure.random.seed"; + + public static void main(String[] args) throws Exception { + try { + System.clearProperty(PROP); + Asserts.assertNE(get(), get()); // random seed (different) + Asserts.assertEQ(get(1L), get(1L)); // same seed + Asserts.assertEQ(get(10L), get(10L)); // same seed + Asserts.assertNE(get(1L), get(10L)); // different seed + + System.setProperty(PROP, "10"); + Asserts.assertEQ(get(), get()); // seed set by system property + Asserts.assertNE(get(), get(1L)); // seed set not system property + Asserts.assertEQ(get(), get(10L)); // seed set same as system property + Asserts.assertEQ(get(1L), get(1L)); // same seed + Asserts.assertEQ(get(10L), get(10L)); // same seed + Asserts.assertNE(get(1L), get(10L)); // different seed + } finally { + System.clearProperty(PROP); + } + } + + static int get() { + return new SeededSecureRandom(SeededSecureRandom.seed()).nextInt(); + } + + static int get(long seed) { + return new SeededSecureRandom(seed).nextInt(); + } +} diff --git a/test/lib/jdk/test/lib/Asserts.java b/test/lib/jdk/test/lib/Asserts.java index d503ea8e544..2febd00e975 100644 --- a/test/lib/jdk/test/lib/Asserts.java +++ b/test/lib/jdk/test/lib/Asserts.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,8 @@ package jdk.test.lib; +import java.util.Arrays; +import java.util.HexFormat; import java.util.Objects; /** @@ -234,6 +236,64 @@ public class Asserts { } } + /** + * Asserts that {@code lhs} is the same byte array as {@code rhs}. + * + * @param lhs The left hand side of the comparison. + * @param rhs The right hand side of the comparison. + * @throws RuntimeException if the assertion is not true. + * @see #assertEqualsByteArray(byte[], byte[], String) + */ + public static void assertEqualsByteArray(byte[] lhs, byte[] rhs) { + assertEqualsByteArray(lhs, rhs, null); + } + + /** + * Asserts that {@code lhs} is not the same byte array as {@code rhs}. + * + * @param lhs The left hand side of the comparison. + * @param rhs The right hand side of the comparison. + * @throws RuntimeException if the assertion is not true. + * @see #assertNotEqualsByteArray(byte[], byte[], String) + */ + public static void assertNotEqualsByteArray(byte[] lhs, byte[] rhs) { + assertNotEqualsByteArray(lhs, rhs, null); + } + + /** + * Asserts that {@code lhs} is the same byte array as {@code rhs}. + * + * @param lhs The left hand side of the comparison. + * @param rhs The right hand side of the comparison. + * @param msg A description of the assumption; {@code null} for a default message. + * @throws RuntimeException if the assertion is not true. + */ + public static void assertEqualsByteArray(byte[] lhs, byte[] rhs, String msg) { + if (!Arrays.equals(lhs, rhs)) { + msg = Objects.toString(msg, "assertEqualsByteArray") + + ": expected " + HexFormat.of().formatHex(lhs) + + " to equal " + HexFormat.of().formatHex(rhs); + fail(msg); + } + } + + /** + * Asserts that {@code lhs} is not the same byte array as {@code rhs}. + * + * @param lhs The left hand side of the comparison. + * @param rhs The right hand side of the comparison. + * @param msg A description of the assumption; {@code null} for a default message. + * @throws RuntimeException if the assertion is not true. + */ + public static void assertNotEqualsByteArray(byte[] lhs, byte[] rhs, String msg) { + if (Arrays.equals(lhs, rhs)) { + msg = Objects.toString(msg, "assertNotEqualsByteArray") + + ": expected " + HexFormat.of().formatHex(lhs) + + " to not equal " + HexFormat.of().formatHex(rhs); + fail(msg); + } + } + /** * Shorthand for {@link #assertGreaterThanOrEqual(Comparable, Comparable)}. * diff --git a/test/lib/jdk/test/lib/security/SeededSecureRandom.java b/test/lib/jdk/test/lib/security/SeededSecureRandom.java new file mode 100644 index 00000000000..f897677390e --- /dev/null +++ b/test/lib/jdk/test/lib/security/SeededSecureRandom.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.test.lib.security; + +import java.security.SecureRandom; +import java.util.Random; + +/** + * A deterministic SecureRandom with a seed. + *

+ * Users can provide the seed with the system property "secure.random.seed". + * Otherwise, it's a random value. Usually, a test runs without this system + * property and the random seed is printed out. When it fails, set the + * system property to this recorded seed to reproduce the failure. + */ +public class SeededSecureRandom extends SecureRandom { + + private final Random rnd; + + public static long seed() { + String value = System.getProperty("secure.random.seed"); + long seed = value != null + ? Long.parseLong(value) + : new Random().nextLong(); + System.out.println("SeededSecureRandom: seed = " + seed); + return seed; + } + + public SeededSecureRandom(long seed) { + rnd = new Random(seed); + } + + public static SeededSecureRandom one() { + return new SeededSecureRandom(seed()); + } + + @Override + public void nextBytes(byte[] bytes) { + rnd.nextBytes(bytes); + } + + @Override + public byte[] generateSeed(int numBytes) { + var out = new byte[numBytes]; + rnd.nextBytes(out); + return out; + } +}