chore: extract shared classic-leg wiring in HybridKexBuilder

Extract duplicated classic-leg construction from
HybridKexBuilder.buildInitiator() and buildResponder()
into a private buildClassicLeg() helper with JavaDoc.

This keeps classic mode validation and context creation
in one place, reduces asymmetry risk between initiator
and responder paths, and preserves existing behavior.

Closes #18 spent @30m
This commit is contained in:
2026-04-05 22:56:47 +02:00
parent 14fbf31989
commit e74e833c5b
2 changed files with 510 additions and 32 deletions

View File

@@ -232,6 +232,43 @@ public final class HybridKexBuilder {
return new PqcKem(this);
}
/**
* Builds the configured classic agreement leg for the current builder state.
*
* <p>
* This method validates the classic-leg inputs required by the selected
* {@link ClassicMode} and returns the resulting {@link AgreementContext} ready
* for inclusion into a {@link HybridKexContext}. For
* {@link ClassicMode#CLASSIC_AGREEMENT}, the returned context is also bound to
* the configured peer public key.
* </p>
*
* @return classic agreement context derived from the configured classic-leg
* state
* @throws IOException if underlying context creation fails
* @throws IllegalStateException if the selected classic mode is missing
* required state
*/
private AgreementContext buildClassicLeg() throws IOException {
if (classicMode == ClassicMode.CLASSIC_AGREEMENT) {
if (classicPrivate == null || classicPeerPublic == null) {
throw new IllegalStateException(
"classic private key and peer public must be set for CLASSIC_AGREEMENT");
}
AgreementContext classic = CryptoAlgorithms.create(classicAlgId, KeyUsage.AGREEMENT, classicPrivate,
classicSpec);
classic.setPeerPublic(classicPeerPublic);
return classic;
}
if (classicMode == ClassicMode.PAIR_MESSAGE) {
if (classicKeyPair == null) {
throw new IllegalStateException("classic key pair must be set for PAIR_MESSAGE");
}
return CryptoAlgorithms.create(classicAlgId, KeyUsage.AGREEMENT, classicKeyPair, classicSpec);
}
throw new IllegalStateException("classic mode must be selected");
}
/**
* Builds initiator-side context.
*
@@ -241,22 +278,7 @@ public final class HybridKexBuilder {
public HybridKexContext buildInitiator() throws IOException {
validateCommon();
AgreementContext classic;
if (classicMode == ClassicMode.CLASSIC_AGREEMENT) {
if (classicPrivate == null || classicPeerPublic == null) {
throw new IllegalStateException(
"classic private key and peer public must be set for CLASSIC_AGREEMENT");
}
classic = CryptoAlgorithms.create(classicAlgId, KeyUsage.AGREEMENT, classicPrivate, classicSpec);
classic.setPeerPublic(classicPeerPublic);
} else if (classicMode == ClassicMode.PAIR_MESSAGE) {
if (classicKeyPair == null) {
throw new IllegalStateException("classic key pair must be set for PAIR_MESSAGE");
}
classic = CryptoAlgorithms.create(classicAlgId, KeyUsage.AGREEMENT, classicKeyPair, classicSpec);
} else {
throw new IllegalStateException("classic mode must be selected");
}
AgreementContext classic = buildClassicLeg();
if (pqcPeerPublic == null) {
throw new IllegalStateException("pqc peer public must be set for initiator");
@@ -279,22 +301,7 @@ public final class HybridKexBuilder {
public HybridKexContext buildResponder() throws IOException {
validateCommon();
AgreementContext classic;
if (classicMode == ClassicMode.CLASSIC_AGREEMENT) {
if (classicPrivate == null || classicPeerPublic == null) {
throw new IllegalStateException(
"classic private key and peer public must be set for CLASSIC_AGREEMENT");
}
classic = CryptoAlgorithms.create(classicAlgId, KeyUsage.AGREEMENT, classicPrivate, classicSpec);
classic.setPeerPublic(classicPeerPublic);
} else if (classicMode == ClassicMode.PAIR_MESSAGE) {
if (classicKeyPair == null) {
throw new IllegalStateException("classic key pair must be set for PAIR_MESSAGE");
}
classic = CryptoAlgorithms.create(classicAlgId, KeyUsage.AGREEMENT, classicKeyPair, classicSpec);
} else {
throw new IllegalStateException("classic mode must be selected");
}
AgreementContext classic = buildClassicLeg();
if (pqcPrivate == null) {
throw new IllegalStateException("pqc private key must be set for responder");

View File

@@ -0,0 +1,471 @@
/*******************************************************************************
* Copyright (C) 2026, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.sdk.builders;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.security.KeyPair;
import java.util.Arrays;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import zeroecho.core.CryptoAlgorithms;
import zeroecho.core.alg.common.agreement.KeyPairKey;
import zeroecho.core.alg.kyber.KyberKeyGenSpec;
import zeroecho.core.alg.xdh.XdhSpec;
import zeroecho.sdk.hybrid.kex.HybridKexContext;
import zeroecho.sdk.hybrid.kex.HybridKexPolicy;
import zeroecho.sdk.hybrid.kex.HybridKexProfile;
import zeroecho.sdk.hybrid.kex.HybridKexTranscript;
import zeroecho.sdk.util.BouncyCastleActivator;
/**
* Professional unit and regression tests for {@link HybridKexBuilder}.
*
* <p>
* These tests verify both supported classic-leg construction modes,
* builder-side validation failures, policy enforcement, transcript binding, and
* mode-switch behavior. The builder is exercised strictly through its public
* fluent API so that validation and resulting hybrid context construction are
* covered together.
* </p>
*/
class HybridKexBuilderTest {
@BeforeAll
static void setup() {
try {
BouncyCastleActivator.init();
} catch (Throwable ignore) {
// Keep tests runnable even when BC is not present.
}
}
@Test
void buildInitiatorResponderClassicAgreementRoundTrip() throws Exception {
System.out.println("buildInitiatorResponderClassicAgreementRoundTrip");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
HybridKexTranscript transcript = new HybridKexTranscript().addUtf8("suite", "X25519+ML-KEM-768").addUtf8("role",
"builder-test");
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
HybridKexContext alice = null;
HybridKexContext bob = null;
try {
alice = HybridKexBuilder.builder().profile(profile).transcript(transcript).classicAgreement()
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate())
.peerPublic(bobClassic.getPublic()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic())
.buildInitiator();
bob = HybridKexBuilder.builder().profile(profile).transcript(transcript).classicAgreement().algorithm("Xdh")
.spec(XdhSpec.X25519).privateKey(bobClassic.getPrivate()).peerPublic(aliceClassic.getPublic())
.pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate()).buildResponder();
byte[] aliceMessage = alice.getPeerMessage();
System.out.println("...aliceMessage(" + lens(aliceMessage) + ")=" + hex(aliceMessage));
bob.setPeerMessage(aliceMessage);
byte[] secretAlice = alice.deriveSecret();
byte[] secretBob = bob.deriveSecret();
System.out.println("...secretAlice=" + hex(secretAlice));
System.out.println("...secretBob=" + hex(secretBob));
assertNotNull(secretAlice);
assertNotNull(secretBob);
assertArrayEquals(secretAlice, secretBob);
assertEquals(profile.outLenBytes(), secretAlice.length);
} finally {
closeQuietly(alice);
closeQuietly(bob);
}
System.out.println("buildInitiatorResponderClassicAgreementRoundTrip...ok");
}
@Test
void buildInitiatorResponderPairMessageRoundTrip() throws Exception {
System.out.println("buildInitiatorResponderPairMessageRoundTrip");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
HybridKexContext alice = null;
HybridKexContext bob = null;
try {
alice = HybridKexBuilder.builder().profile(profile).classicPairMessage().algorithm("Xdh")
.spec(XdhSpec.X25519).keyPair(new KeyPairKey(aliceClassic)).pqcKem().algorithm("ML-KEM")
.peerPublic(bobPqc.getPublic()).buildInitiator();
bob = HybridKexBuilder.builder().profile(profile).classicPairMessage().algorithm("Xdh").spec(XdhSpec.X25519)
.keyPair(new KeyPairKey(bobClassic)).pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate())
.buildResponder();
byte[] messageA = alice.getPeerMessage();
System.out.println("...messageA(" + lens(messageA) + ")=" + hex(messageA));
bob.setPeerMessage(messageA);
byte[] messageB = bob.getPeerMessage();
System.out.println("...messageB(" + lens(messageB) + ")=" + hex(messageB));
alice.setPeerMessage(messageB);
byte[] secretAlice = alice.deriveSecret();
byte[] secretBob = bob.deriveSecret();
System.out.println("...secretAlice=" + hex(secretAlice));
System.out.println("...secretBob=" + hex(secretBob));
assertNotNull(secretAlice);
assertNotNull(secretBob);
assertArrayEquals(secretAlice, secretBob);
assertEquals(profile.outLenBytes(), secretAlice.length);
} finally {
closeQuietly(alice);
closeQuietly(bob);
}
System.out.println("buildInitiatorResponderPairMessageRoundTrip...ok");
}
@Test
void buildInitiatorWithoutProfileFails() throws Exception {
System.out.println("buildInitiatorWithoutProfileFails");
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
HybridKexBuilder.builder().classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519)
.privateKey(aliceClassic.getPrivate()).peerPublic(bobClassic.getPublic()).pqcKem()
.algorithm("ML-KEM").peerPublic(bobPqc.getPublic()).buildInitiator();
});
System.out.println("...exception=" + exception.getMessage());
assertEquals("profile must be set", exception.getMessage());
System.out.println("buildInitiatorWithoutProfileFails...ok");
}
@Test
void buildInitiatorWithoutClassicModeFails() throws Exception {
System.out.println("buildInitiatorWithoutClassicModeFails");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
HybridKexBuilder.builder().profile(profile).pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic())
.buildInitiator();
});
System.out.println("...exception=" + exception.getMessage());
assertEquals("classic mode must be selected", exception.getMessage());
System.out.println("buildInitiatorWithoutClassicModeFails...ok");
}
@Test
void buildInitiatorClassicAgreementWithoutPeerPublicFails() throws Exception {
System.out.println("buildInitiatorClassicAgreementWithoutPeerPublicFails");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
HybridKexBuilder.builder().profile(profile).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519)
.privateKey(aliceClassic.getPrivate()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic())
.buildInitiator();
});
System.out.println("...exception=" + exception.getMessage());
assertEquals("classic private key and peer public must be set for CLASSIC_AGREEMENT", exception.getMessage());
System.out.println("buildInitiatorClassicAgreementWithoutPeerPublicFails...ok");
}
@Test
void buildResponderPairMessageWithoutKeyPairFails() throws Exception {
System.out.println("buildResponderPairMessageWithoutKeyPairFails");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
HybridKexBuilder.builder().profile(profile).classicPairMessage().algorithm("Xdh").spec(XdhSpec.X25519)
.pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate()).buildResponder();
});
System.out.println("...exception=" + exception.getMessage());
assertEquals("classic key pair must be set for PAIR_MESSAGE", exception.getMessage());
System.out.println("buildResponderPairMessageWithoutKeyPairFails...ok");
}
@Test
void buildInitiatorWithoutPqcPeerPublicFails() throws Exception {
System.out.println("buildInitiatorWithoutPqcPeerPublicFails");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
HybridKexBuilder.builder().profile(profile).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519)
.privateKey(aliceClassic.getPrivate()).peerPublic(bobClassic.getPublic()).pqcKem()
.algorithm("ML-KEM").buildInitiator();
});
System.out.println("...exception=" + exception.getMessage());
assertEquals("pqc peer public must be set for initiator", exception.getMessage());
System.out.println("buildInitiatorWithoutPqcPeerPublicFails...ok");
}
@Test
void buildResponderWithoutPqcPrivateFails() throws Exception {
System.out.println("buildResponderWithoutPqcPrivateFails");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
HybridKexBuilder.builder().profile(profile).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519)
.privateKey(bobClassic.getPrivate()).peerPublic(aliceClassic.getPublic()).pqcKem()
.algorithm("ML-KEM").buildResponder();
});
System.out.println("...exception=" + exception.getMessage());
assertEquals("pqc private key must be set for responder", exception.getMessage());
System.out.println("buildResponderWithoutPqcPrivateFails...ok");
}
@Test
void buildInitiatorRejectsPolicyWhenOkmTooShort() throws Exception {
System.out.println("buildInitiatorRejectsPolicyWhenOkmTooShort");
HybridKexProfile profile = HybridKexProfile.defaultProfile(16);
HybridKexPolicy policy = new HybridKexPolicy(0, 0, 32);
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
HybridKexBuilder.builder().profile(profile).policy(policy).classicAgreement().algorithm("Xdh")
.spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate()).peerPublic(bobClassic.getPublic())
.pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic()).buildInitiator();
});
System.out.println("...exception=" + exception.getMessage());
assertEquals("Hybrid OKM length too small: 16 < 32", exception.getMessage());
System.out.println("buildInitiatorRejectsPolicyWhenOkmTooShort...ok");
}
@Test
void switchingClassicModeClearsConflictingStateAndBuildsPairMessage() throws Exception {
System.out.println("switchingClassicModeClearsConflictingStateAndBuildsPairMessage");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair agreementKeyPair = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair pairMessageKeyPair = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
HybridKexContext context = null;
try {
HybridKexBuilder builder = HybridKexBuilder.builder().profile(profile);
builder.classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519).privateKey(agreementKeyPair.getPrivate())
.peerPublic(agreementKeyPair.getPublic());
context = builder.classicPairMessage().algorithm("Xdh").spec(XdhSpec.X25519)
.keyPair(new KeyPairKey(pairMessageKeyPair)).pqcKem().algorithm("ML-KEM")
.peerPublic(bobPqc.getPublic()).buildInitiator();
byte[] peerMessage = context.getPeerMessage();
System.out.println("...peerMessage(" + lens(peerMessage) + ")=" + hex(peerMessage));
assertNotNull(context);
assertNotNull(peerMessage);
} finally {
closeQuietly(context);
}
System.out.println("switchingClassicModeClearsConflictingStateAndBuildsPairMessage...ok");
}
@Test
void transcriptChangesDerivedSecret() throws Exception {
System.out.println("transcriptChangesDerivedSecret");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair aliceClassicA = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassicA = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqcA = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair aliceClassicB = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassicB = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqcB = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
HybridKexTranscript transcriptA = new HybridKexTranscript().addUtf8("context", "A");
HybridKexTranscript transcriptB = new HybridKexTranscript().addUtf8("context", "B");
byte[] secretA;
byte[] secretB;
HybridKexContext aliceA = null;
HybridKexContext bobA = null;
HybridKexContext aliceB = null;
HybridKexContext bobB = null;
try {
aliceA = HybridKexBuilder.builder().profile(profile).transcript(transcriptA).classicAgreement()
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassicA.getPrivate())
.peerPublic(bobClassicA.getPublic()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqcA.getPublic())
.buildInitiator();
bobA = HybridKexBuilder.builder().profile(profile).transcript(transcriptA).classicAgreement()
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(bobClassicA.getPrivate())
.peerPublic(aliceClassicA.getPublic()).pqcKem().algorithm("ML-KEM").privateKey(bobPqcA.getPrivate())
.buildResponder();
bobA.setPeerMessage(aliceA.getPeerMessage());
secretA = aliceA.deriveSecret();
byte[] responderA = bobA.deriveSecret();
System.out.println("...secretA=" + hex(secretA));
System.out.println("...responderA=" + hex(responderA));
assertArrayEquals(secretA, responderA);
aliceB = HybridKexBuilder.builder().profile(profile).transcript(transcriptB).classicAgreement()
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassicB.getPrivate())
.peerPublic(bobClassicB.getPublic()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqcB.getPublic())
.buildInitiator();
bobB = HybridKexBuilder.builder().profile(profile).transcript(transcriptB).classicAgreement()
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(bobClassicB.getPrivate())
.peerPublic(aliceClassicB.getPublic()).pqcKem().algorithm("ML-KEM").privateKey(bobPqcB.getPrivate())
.buildResponder();
bobB.setPeerMessage(aliceB.getPeerMessage());
secretB = aliceB.deriveSecret();
byte[] responderB = bobB.deriveSecret();
System.out.println("...secretB=" + hex(secretB));
System.out.println("...responderB=" + hex(responderB));
assertArrayEquals(secretB, responderB);
if (Arrays.equals(secretA, secretB)) {
throw new AssertionError("Transcript-bound secrets should differ for different transcripts");
}
} finally {
closeQuietly(aliceA);
closeQuietly(bobA);
closeQuietly(aliceB);
closeQuietly(bobB);
}
System.out.println("transcriptChangesDerivedSecret...ok");
}
private static void closeQuietly(HybridKexContext context) {
if (context == null) {
return;
}
try {
context.close();
} catch (Exception ignore) {
// Cleanup only.
}
}
private static String hex(byte[] value) {
if (value == null) {
return "null";
}
StringBuilder builder = new StringBuilder(value.length * 2);
for (int index = 0; index < value.length; index++) {
if (builder.length() >= 30) {
builder.append("...");
break;
}
int current = value[index] & 0xFF;
if (current < 16) {
builder.append('0');
}
builder.append(Integer.toHexString(current));
}
return builder.toString();
}
private static String lens(byte[] message) {
if (message == null || message.length < 8) {
return "classicLen=?, pqcLen=?";
}
try {
DataInputStream input = new DataInputStream(new ByteArrayInputStream(message));
int classicLength = input.readInt();
int pqcLength = 0;
if (message.length >= 8 + Math.max(0, classicLength)) {
if (classicLength > 0) {
input.skipBytes(classicLength);
}
pqcLength = input.readInt();
}
return "classicLen=" + classicLength + ", pqcLen=" + pqcLength;
} catch (Exception exception) {
return "classicLen=?, pqcLen=?";
}
}
}