8342442: Static ACVP sample tests

Reviewed-by: mullan, bperez
This commit is contained in:
Weijun Wang 2024-11-09 23:11:33 +00:00
parent 325a2c3f76
commit f400896822
7 changed files with 669 additions and 0 deletions

View File

@ -0,0 +1,162 @@
/*
* 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.
*/
import jdk.test.lib.json.JSONValue;
import jtreg.SkippedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Provider;
import java.security.Security;
/*
* @test
* @bug 8342442
* @library /test/lib
*/
/// This test runs on `internalProjection.json`-style files generated
/// by NIST's ACVP Server. See [https://github.com/usnistgov/ACVP-Server].
///
/// The files are either put into the `data` directory or another
/// directory specified by the `test.acvp.data` test property.
/// The test walks through the directory recursively and looks for
/// file names equal to or ending with `internalProjection.json` and
/// runs tests on them.
///
/// Set the `test.acvp.alg` test property to only test the specified algorithm.
///
/// Sample files can be downloaded from
/// [https://github.com/usnistgov/ACVP-Server/tree/master/gen-val/json-files].
///
/// By default, the test uses system-preferred implementations.
/// If you want to test a specific provider, set the
/// `test.acvp.provider` test property. The provider must be
/// registered.
///
/// Tests for each algorithm must be compliant to its specification linked from
/// [https://github.com/usnistgov/ACVP?tab=readme-ov-file#supported-algorithms].
///
/// Example:
/// ```
/// jtreg -Dtest.acvp.provider=SunJCE \
/// -Dtest.acvp.alg=ML-KEM \
/// -Dtest.acvp.data=/path/to/json-files/ \
/// -jdk:/path/to/jdk Launcher.java
/// ```
public class Launcher {
private static final String ONLY_ALG
= System.getProperty("test.acvp.alg");
private static final Provider PROVIDER;
private static int count = 0;
private static int invalidTest = 0;
private static int unsupportedTest = 0;
static {
var provProp = System.getProperty("test.acvp.provider");
if (provProp != null) {
var p = Security.getProvider(provProp);
if (p == null) {
System.err.println(provProp + " is not a registered provider name");
throw new RuntimeException(provProp + " is not a registered provider name");
}
PROVIDER = p;
} else {
PROVIDER = null;
}
}
public static void main(String[] args) throws Exception {
var testDataProp = System.getProperty("test.acvp.data");
Path dataPath = testDataProp != null
? Path.of(testDataProp)
: Path.of(System.getProperty("test.src"), "data");
System.out.println("Data path: " + dataPath);
if (PROVIDER != null) {
System.out.println("Provider: " + PROVIDER.getName());
}
if (ONLY_ALG != null) {
System.out.println("Algorithm: " + ONLY_ALG);
}
try (var stream = Files.walk(dataPath)) {
stream.filter(Files::isRegularFile)
.filter(p -> p.getFileName().toString()
.endsWith("internalProjection.json"))
.forEach(Launcher::run);
}
if (count > 0) {
System.out.println();
System.out.println("Test completed: " + count);
System.out.println("Invalid tests: " + invalidTest);
System.out.println("Unsupported tests: " + unsupportedTest);
} else {
throw new SkippedException("No supported test found");
}
}
static void run(Path test) {
try {
JSONValue kat;
try {
kat = JSONValue.parse(Files.readString(test));
} catch (Exception e) {
System.out.println("Warning: cannot parse " + test + ". Skipped");
invalidTest++;
return;
}
var alg = kat.get("algorithm").asString();
if (ONLY_ALG != null && !alg.equals(ONLY_ALG)) {
return;
}
System.out.println(">>> Testing " + test + "...");
switch (alg) {
case "ML-DSA" -> {
ML_DSA_Test.run(kat, PROVIDER);
count++;
}
case "ML-KEM" -> {
ML_KEM_Test.run(kat, PROVIDER);
count++;
}
case "SHA2-256", "SHA2-224", "SHA3-256", "SHA3-224" -> {
SHA_Test.run(kat, PROVIDER);
count++;
}
default -> {
System.out.println("Skipped unsupported algorithm: " + alg);
unsupportedTest++;
}
}
} catch (RuntimeException re) {
throw re;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,126 @@
/*
* 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.
*/
import jdk.test.lib.Asserts;
import jdk.test.lib.json.JSONValue;
import jdk.test.lib.security.FixedSecureRandom;
import java.security.*;
import java.security.spec.EncodedKeySpec;
import java.security.spec.NamedParameterSpec;
import static jdk.test.lib.Utils.toByteArray;
// JSON spec at https://pages.nist.gov/ACVP/draft-celi-acvp-ml-dsa.html
public class ML_DSA_Test {
public static void run(JSONValue kat, Provider provider) throws Exception {
var mode = kat.get("mode").asString();
switch (mode) {
case "keyGen" -> keyGenTest(kat, provider);
case "sigGen" -> sigGenTest(kat, provider);
case "sigVer" -> sigVerTest(kat, provider);
default -> throw new UnsupportedOperationException("Unknown mode: " + mode);
}
}
static void keyGenTest(JSONValue kat, Provider p) throws Exception {
var g = p == null
? KeyPairGenerator.getInstance("ML-DSA")
: KeyPairGenerator.getInstance("ML-DSA", p);
var f = p == null
? KeyFactory.getInstance("ML-DSA")
: KeyFactory.getInstance("ML-DSA", p);
for (var t : kat.get("testGroups").asArray()) {
var pname = t.get("parameterSet").asString();
var np = new NamedParameterSpec(pname);
System.out.println(">> " + pname);
for (var c : t.get("tests").asArray()) {
System.out.print(c.get("tcId").asString() + " ");
g.initialize(np, new FixedSecureRandom(toByteArray(c.get("seed").asString())));
var kp = g.generateKeyPair();
var pk = f.getKeySpec(kp.getPublic(), EncodedKeySpec.class).getEncoded();
var sk = f.getKeySpec(kp.getPrivate(), EncodedKeySpec.class).getEncoded();
Asserts.assertEqualsByteArray(pk, toByteArray(c.get("pk").asString()));
Asserts.assertEqualsByteArray(sk, toByteArray(c.get("sk").asString()));
}
System.out.println();
}
}
static void sigGenTest(JSONValue kat, Provider p) throws Exception {
var s = p == null
? Signature.getInstance("ML-DSA")
: Signature.getInstance("ML-DSA", p);
for (var t : kat.get("testGroups").asArray()) {
var pname = t.get("parameterSet").asString();
var det = Boolean.parseBoolean(t.get("deterministic").asString());
System.out.println(">> " + pname + " sign");
for (var c : t.get("tests").asArray()) {
System.out.print(Integer.parseInt(c.get("tcId").asString()) + " ");
var sk = new PrivateKey() {
public String getAlgorithm() { return pname; }
public String getFormat() { return "RAW"; }
public byte[] getEncoded() { return toByteArray(c.get("sk").asString()); }
};
var sr = new FixedSecureRandom(
det ? new byte[32] : toByteArray(c.get("rnd").asString()));
s.initSign(sk, sr);
s.update(toByteArray(c.get("message").asString()));
var sig = s.sign();
Asserts.assertEqualsByteArray(
sig, toByteArray(c.get("signature").asString()));
}
System.out.println();
}
}
static void sigVerTest(JSONValue kat, Provider p) throws Exception {
var s = p == null
? Signature.getInstance("ML-DSA")
: Signature.getInstance("ML-DSA", p);
for (var t : kat.get("testGroups").asArray()) {
var pname = t.get("parameterSet").asString();
var pk = new PublicKey() {
public String getAlgorithm() { return pname; }
public String getFormat() { return "RAW"; }
public byte[] getEncoded() { return toByteArray(t.get("pk").asString()); }
};
System.out.println(">> " + pname + " verify");
for (var c : t.get("tests").asArray()) {
System.out.print(c.get("tcId").asString() + " ");
// Only ML-DSA sigVer has negative tests
var expected = Boolean.parseBoolean(c.get("testPassed").asString());
var actual = true;
try {
s.initVerify(pk);
s.update(toByteArray(c.get("message").asString()));
actual = s.verify(toByteArray(c.get("signature").asString()));
} catch (InvalidKeyException | SignatureException e) {
actual = false;
}
Asserts.assertEQ(expected, actual);
}
System.out.println();
}
}
}

View File

@ -0,0 +1,112 @@
/*
* 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.
*/
import jdk.test.lib.Asserts;
import jdk.test.lib.json.JSONValue;
import jdk.test.lib.security.FixedSecureRandom;
import javax.crypto.KEM;
import java.security.*;
import java.security.spec.EncodedKeySpec;
import java.security.spec.NamedParameterSpec;
import static jdk.test.lib.Utils.toByteArray;
// JSON spec at https://pages.nist.gov/ACVP/draft-celi-acvp-ml-kem.html
public class ML_KEM_Test {
public static void run(JSONValue kat, Provider provider) throws Exception {
var mode = kat.get("mode").asString();
switch (mode) {
case "keyGen" -> keyGenTest(kat, provider);
case "encapDecap" -> encapDecapTest(kat, provider);
default -> throw new UnsupportedOperationException("Unknown mode: " + mode);
}
}
static void keyGenTest(JSONValue kat, Provider p) throws Exception {
var g = p == null
? KeyPairGenerator.getInstance("ML-KEM")
: KeyPairGenerator.getInstance("ML-KEM", p);
var f = p == null
? KeyFactory.getInstance("ML-KEM")
: KeyFactory.getInstance("ML-KEM", p);
for (var t : kat.get("testGroups").asArray()) {
var pname = t.get("parameterSet").asString();
var np = new NamedParameterSpec(pname);
System.out.println(">> " + pname);
for (var c : t.get("tests").asArray()) {
System.out.print(c.get("tcId").asString() + " ");
g.initialize(np, new FixedSecureRandom(
toByteArray(c.get("d").asString()), toByteArray(c.get("z").asString())));
var kp = g.generateKeyPair();
var pk = f.getKeySpec(kp.getPublic(), EncodedKeySpec.class).getEncoded();
var sk = f.getKeySpec(kp.getPrivate(), EncodedKeySpec.class).getEncoded();
Asserts.assertEqualsByteArray(pk, toByteArray(c.get("ek").asString()));
Asserts.assertEqualsByteArray(sk, toByteArray(c.get("dk").asString()));
}
System.out.println();
}
}
static void encapDecapTest(JSONValue kat, Provider p) throws Exception {
var g = p == null
? KEM.getInstance("ML-KEM")
: KEM.getInstance("ML-KEM", p);
for (var t : kat.get("testGroups").asArray()) {
var pname = t.get("parameterSet").asString();
var function = t.get("function").asString();
System.out.println(">> " + pname + " " + function);
if (function.equals("encapsulation")) {
for (var c : t.get("tests").asArray()) {
System.out.print(c.get("tcId").asString() + " ");
var ek = new PublicKey() {
public String getAlgorithm() { return pname; }
public String getFormat() { return "RAW"; }
public byte[] getEncoded() { return toByteArray(c.get("ek").asString()); }
};
var e = g.newEncapsulator(
ek, new FixedSecureRandom(toByteArray(c.get("m").asString())));
var enc = e.encapsulate();
Asserts.assertEqualsByteArray(
enc.encapsulation(), toByteArray(c.get("c").asString()));
Asserts.assertEqualsByteArray(
enc.key().getEncoded(), toByteArray(c.get("k").asString()));
}
System.out.println();
} else if (function.equals("decapsulation")) {
var dk = new PrivateKey() {
public String getAlgorithm() { return pname; }
public String getFormat() { return "RAW"; }
public byte[] getEncoded() { return toByteArray(t.get("dk").asString()); }
};
for (var c : t.get("tests").asArray()) {
System.out.print(c.get("tcId").asString() + " ");
var d = g.newDecapsulator(dk);
var k = d.decapsulate(toByteArray(c.get("c").asString()));
Asserts.assertEqualsByteArray(k.getEncoded(), toByteArray(c.get("k").asString()));
}
System.out.println();
}
}
}
}

View File

@ -0,0 +1,139 @@
/*
* 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.
*/
import jdk.test.lib.Asserts;
import jdk.test.lib.json.JSONValue;
import java.security.*;
import java.util.Arrays;
import static jdk.test.lib.Utils.toByteArray;
// JSON spec at https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html
// and https://pages.nist.gov/ACVP/draft-celi-acvp-sha3.html
public class SHA_Test {
public static void run(JSONValue kat, Provider provider) throws Exception {
var alg = kat.get("algorithm").asString();
if (alg.startsWith("SHA2-")) alg = "SHA-" + alg.substring(5);
var md = provider == null ? MessageDigest.getInstance(alg)
: MessageDigest.getInstance(alg, provider);
for (var t : kat.get("testGroups").asArray()) {
var testType = t.get("testType").asString();
switch (testType) {
case "AFT" -> {
for (var c : t.get("tests").asArray()) {
System.out.print(c.get("tcId").asString() + " ");
var msg = toByteArray(c.get("msg").asString());
var len = Integer.parseInt(c.get("len").asString());
if (msg.length * 8 == len) {
Asserts.assertEqualsByteArray(md.digest(msg),
toByteArray(c.get("md").asString()));
} else {
System.out.print("bits ");
}
}
}
case "MCT" -> {
var mctVersion = t.get("mctVersion").asString();
var trunc = mctVersion.equals("alternate");
for (var c : t.get("tests").asArray()) {
System.out.print(c.get("tcId").asString() + " ");
var SEED = toByteArray(c.get("msg").asString());
var INITIAL_SEED_LENGTH = Integer.parseInt(c.get("len").asString());
if (SEED.length * 8 == INITIAL_SEED_LENGTH) {
for (var r : c.get("resultsArray").asArray()) {
if (alg.startsWith("SHA3-")) {
var MD = SEED;
for (var i = 0; i < 1000; i++) {
if (trunc) {
MD = Arrays.copyOf(MD, INITIAL_SEED_LENGTH / 8);
}
MD = md.digest(MD);
}
Asserts.assertEqualsByteArray(MD,
toByteArray(r.get("md").asString()));
SEED = MD;
} else {
var A = SEED;
var B = SEED;
var C = SEED;
byte[] MD = null;
for (var i = 0; i < 1000; i++) {
var MSG = concat(A, B, C);
if (trunc) {
MSG = Arrays.copyOf(MSG, INITIAL_SEED_LENGTH / 8);
}
MD = md.digest(MSG);
A = B;
B = C;
C = MD;
}
Asserts.assertEqualsByteArray(MD,
toByteArray(r.get("md").asString()));
SEED = MD;
}
}
} else {
System.out.print("bits ");
}
}
}
case "LDT" -> {
for (var c : t.get("tests").asArray()) {
System.out.print(c.get("tcId").asString() + " ");
var lm = c.get("largeMsg");
var ct = toByteArray(lm.get("content").asString());
var flen = Long.parseLong(lm.get("fullLength").asString());
var clen = Long.parseLong(lm.get("contentLength").asString());
var cc = 0L;
while (cc < flen) {
md.update(ct);
cc += clen;
}
Asserts.assertEqualsByteArray(md.digest(),
toByteArray(c.get("md").asString()));
}
}
default -> throw new UnsupportedOperationException(
"Unknown testType: " + testType);
}
System.out.println();
}
}
/////////////
static byte[] concat(byte[]... input) {
var sum = 0;
for (var i : input) {
sum += i.length;
}
var out = new byte[sum];
sum = 0;
for (var i : input) {
System.arraycopy(i, 0, out, sum, i.length);
sum += i.length;
}
return out;
}
}

View File

@ -0,0 +1,9 @@
# Automated Cryptographic Validation Test System Sample JSON files v1.1.0.36
## License
NIST-developed software is provided by NIST as a public service. You may use, copy, and distribute copies of the software in any medium, provided that you keep intact this entire notice. You may improve, modify, and create derivative works of the software or any portion of the software, and you may copy and distribute such modifications or works. Modified works should carry a notice stating that you changed the software and should note the date and nature of any such change. Please explicitly acknowledge the National Institute of Standards and Technology as the source of the software.
NIST-developed software is expressly provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED, IN FACT, OR ARISING BY OPERATION OF LAW, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND DATA ACCURACY. NIST NEITHER REPRESENTS NOR WARRANTS THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT ANY DEFECTS WILL BE CORRECTED. NIST DOES NOT WARRANT OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF THE SOFTWARE OR THE RESULTS THEREOF, INCLUDING BUT NOT LIMITED TO THE CORRECTNESS, ACCURACY, RELIABILITY, OR USEFULNESS OF THE SOFTWARE.
You are solely responsible for determining the appropriateness of using and distributing the software and you assume all risks associated with its use, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and the unavailability or interruption of operation. This software is not intended to be used in any situation where a failure could cause risk of injury or damage to property. The software developed by NIST employees is not subject to copyright protection within the United States.

View File

@ -0,0 +1,49 @@
/*
* 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.
*/
import jdk.test.lib.Asserts;
import jdk.test.lib.Utils;
import jdk.test.lib.security.FixedSecureRandom;
/*
* @test
* @library /test/lib
* @summary ensure FixedSecureRandom works as expected
*/
public class FixedSecureRandomTest {
public static void main(String[] args) throws Exception {
var fsr = new FixedSecureRandom(new byte[] {1, 2, 3},
new byte[] {4, 5, 6});
var b1 = new byte[2];
fsr.nextBytes(b1);
Asserts.assertEqualsByteArray(b1, new byte[] {1, 2});
Asserts.assertTrue(fsr.hasRemaining());
fsr.nextBytes(b1);
Asserts.assertEqualsByteArray(b1, new byte[] {3, 4});
Asserts.assertTrue(fsr.hasRemaining());
fsr.nextBytes(b1);
Asserts.assertEqualsByteArray(b1, new byte[] {5, 6});
Asserts.assertFalse(fsr.hasRemaining());
Utils.runAndCheckException(() -> fsr.nextBytes(b1),
IllegalStateException.class);
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.io.ByteArrayOutputStream;
import java.security.SecureRandom;
/// A custom implementation of `SecureRandom` that outputs a
/// predefined sequence of bytes.
///
/// The `FixedSecureRandom` class is designed for testing and
/// controlled environments where predictable output is required.
/// Upon creation, the class is initialized with a fixed byte array.
/// Each call to `nextBytes()` will return these bytes in sequence,
/// ensuring that the output matches the provided input exactly.
/// An `IllegalStateException` will be thrown when the predefined
/// bytes are exhausted.
public class FixedSecureRandom extends SecureRandom {
private byte[] buffer;
private int offset;
// Multiple segments of ordered predefined bytes can be
// provided for convenience. For example, ML-KEM.KeyGen
// requires 2 blocks of 32-byte random data.
public FixedSecureRandom(byte[]... data) {
var os = new ByteArrayOutputStream();
for (byte[] b : data) {
os.writeBytes(b);
}
buffer = os.toByteArray();
offset = 0;
}
@Override
public void nextBytes(byte[] bytes) {
if (bytes.length > buffer.length - offset) {
throw new IllegalStateException("Not enough bytes");
}
System.arraycopy(buffer, offset, bytes, 0, bytes.length);
offset += bytes.length;
}
/// {@return whether there are remaining used bytes}
///
/// This method is useful to detect whether an algorithm
/// implementation has indeed consumed the required number
/// of bytes correctly.
public boolean hasRemaining() {
return offset != buffer.length;
}
}