chore(text): source code format

This commit is contained in:
2026-07-30 20:28:43 +02:00
parent bc68e0433c
commit dedd16f584
262 changed files with 4867 additions and 5717 deletions

View File

@@ -74,9 +74,8 @@ class CapabilityValueSemanticsTest {
void nullAndIncompatibleDefaultsAreRejectedAtConstruction() {
System.out.println("nullAndIncompatibleDefaultsAreRejectedAtConstruction");
assertThrows(NullPointerException.class, () -> capability(() -> null));
assertThrows(IllegalArgumentException.class,
() -> new Capability("DIGEST", AlgorithmFamily.DIGEST, KeyUsage.DIGEST, DigestContext.class,
NullKey.class, TestSpec.class, new OtherSpec()));
assertThrows(IllegalArgumentException.class, () -> new Capability("DIGEST", AlgorithmFamily.DIGEST,
KeyUsage.DIGEST, DigestContext.class, NullKey.class, TestSpec.class, new OtherSpec()));
System.out.println("...invalidDefaultsRejected=true");
System.out.println("nullAndIncompatibleDefaultsAreRejectedAtConstruction...ok");
@@ -121,8 +120,7 @@ class CapabilityValueSemanticsTest {
(key, spec) -> {
runtimeSpecs.add(spec);
return mock(DigestContext.class);
},
() -> new TestSpec(Integer.toString(evaluations.incrementAndGet())));
}, () -> new TestSpec(Integer.toString(evaluations.incrementAndGet())));
}
}
}

View File

@@ -105,9 +105,10 @@ public class CatalogContractTest {
Logger jul = Logger.getLogger("zeroecho.audit");
jul.setLevel(Level.FINE); // see PROGRESS at FINE
session = new ZeroEchoSession().withAuditListener(JulAuditListenerStd.builder().logger(jul)
.infoLevel(Level.INFO).warnLevel(Level.WARNING).progressLevel(Level.FINE)
.includeStackTraces(true).build()).withAuditMode(AuditMode.WRAP);
session = new ZeroEchoSession()
.withAuditListener(JulAuditListenerStd.builder().logger(jul).infoLevel(Level.INFO)
.warnLevel(Level.WARNING).progressLevel(Level.FINE).includeStackTraces(true).build())
.withAuditMode(AuditMode.WRAP);
dump("");
dump("zeroecho.core.audit");
@@ -178,8 +179,7 @@ public class CatalogContractTest {
.anyMatch(info -> info.operation() == KeyOperation.SYMMETRIC_GENERATE);
// SIGN/VERIFY (asymmetric)
if (alg.roles().contains(KeyUsage.SIGN) && alg.roles().contains(KeyUsage.VERIFY)
&& hasAsym) {
if (alg.roles().contains(KeyUsage.SIGN) && alg.roles().contains(KeyUsage.VERIFY) && hasAsym) {
trySignVerify(id, msg);
System.out.println();
}
@@ -193,8 +193,7 @@ public class CatalogContractTest {
}
// KEM
if (alg.roles().contains(KeyUsage.ENCAPSULATE) && alg.roles().contains(KeyUsage.DECAPSULATE)
&& hasAsym) {
if (alg.roles().contains(KeyUsage.ENCAPSULATE) && alg.roles().contains(KeyUsage.DECAPSULATE) && hasAsym) {
tryKem(id, msg);
System.out.println();
}

View File

@@ -74,10 +74,10 @@ import zeroecho.core.tag.TagEngine;
*
* <p>
* These tests focus on the internal audit wrapping path used by
* {@link AuditedContexts#wrap(CryptoContext, AuditListener, KeyUsage)}.
* They verify that representative context types are wrapped as audited JDK
* proxies and that the resulting wrapper preserves the expected basic
* delegation behavior.
* {@link AuditedContexts#wrap(CryptoContext, AuditListener, KeyUsage)}. They
* verify that representative context types are wrapped as audited JDK proxies
* and that the resulting wrapper preserves the expected basic delegation
* behavior.
* </p>
*/
class CryptoAlgorithmsAuditWrapTest {

View File

@@ -27,7 +27,8 @@ import zeroecho.core.context.DigestContext;
import zeroecho.sdk.ZeroEchoSession;
/**
* Verifies authoritative registry ownership and explicitly scoped runtime state.
* Verifies authoritative registry ownership and explicitly scoped runtime
* state.
*/
class CryptoArchitectureTest {
@@ -51,8 +52,8 @@ class CryptoArchitectureTest {
System.out.println("explicitPolicyOrder");
List<String> events = new ArrayList<>();
AuditListener listener = policyOrderListener(events);
ZeroEchoSession allowed = new ZeroEchoSession().withAuditListener(listener).withPolicy(
(id, role, key, spec) -> events.add("policy"));
ZeroEchoSession allowed = new ZeroEchoSession().withAuditListener(listener)
.withPolicy((id, role, key, spec) -> events.add("policy"));
try (DigestContext context = allowed.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE)) {
assertSame(CryptoAlgorithms.require("DIGEST"), context.algorithm());
}
@@ -60,19 +61,18 @@ class CryptoArchitectureTest {
events.clear();
IllegalArgumentException denial = new IllegalArgumentException("controlled denial");
ZeroEchoSession denied = new ZeroEchoSession().withAuditListener(listener).withPolicy(
(id, role, key, spec) -> {
events.add("policy");
throw denial;
});
ZeroEchoSession denied = new ZeroEchoSession().withAuditListener(listener).withPolicy((id, role, key, spec) -> {
events.add("policy");
throw denial;
});
assertSame(denial, assertThrows(IllegalArgumentException.class,
() -> denied.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE)));
assertEquals(List.of("policy"), events);
events.clear();
IllegalStateException failure = new IllegalStateException("controlled policy failure");
ZeroEchoSession failing = new ZeroEchoSession().withAuditListener(listener).withPolicy(
(id, role, key, spec) -> {
ZeroEchoSession failing = new ZeroEchoSession().withAuditListener(listener)
.withPolicy((id, role, key, spec) -> {
events.add("policy");
throw failure;
});
@@ -91,8 +91,7 @@ class CryptoArchitectureTest {
AtomicInteger secondEvents = new AtomicInteger();
AuditListener firstListener = contextListener(firstEvents);
AuditListener secondListener = contextListener(secondEvents);
ZeroEchoSession wrapped = new ZeroEchoSession().withAuditListener(firstListener)
.withAuditMode(AuditMode.WRAP);
ZeroEchoSession wrapped = new ZeroEchoSession().withAuditListener(firstListener).withAuditMode(AuditMode.WRAP);
ZeroEchoSession direct = new ZeroEchoSession().withAuditListener(secondListener);
try (DigestContext wrappedContext = wrapped.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE)) {

View File

@@ -58,15 +58,12 @@ class SecretSpecLifecycleTest {
new SpecCase("zeroecho.core.alg.kyber.KyberPrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR),
new SpecCase("zeroecho.core.alg.mldsa.MldsaPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR),
new SpecCase("zeroecho.core.alg.ntru.NtruPrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR),
new SpecCase("zeroecho.core.alg.ntruprime.NtrulPrimePrivateKeySpec", "pkcs8", 8,
Factory.CONSTRUCTOR),
new SpecCase("zeroecho.core.alg.ntruprime.SntruPrimePrivateKeySpec", "pkcs8", 8,
Factory.CONSTRUCTOR),
new SpecCase("zeroecho.core.alg.ntruprime.NtrulPrimePrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR),
new SpecCase("zeroecho.core.alg.ntruprime.SntruPrimePrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR),
new SpecCase("zeroecho.core.alg.rsa.RsaPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR),
new SpecCase("zeroecho.core.alg.saber.SaberPrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR),
new SpecCase("zeroecho.core.alg.slhdsa.SlhDsaPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR),
new SpecCase("zeroecho.core.alg.sphincsplus.SphincsPlusPrivateKeySpec", "encoded", 8,
Factory.CONSTRUCTOR),
new SpecCase("zeroecho.core.alg.sphincsplus.SphincsPlusPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR),
new SpecCase("zeroecho.core.alg.xdh.XdhPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR));
@Test
@@ -131,14 +128,14 @@ class SecretSpecLifecycleTest {
() -> AesKeyImportSpec.unmarshal(PairSeq.of("k.b64", aesKey, "k.b64", "%")));
assertThrows(IllegalArgumentException.class,
() -> ChaChaKeyImportSpec.unmarshal(PairSeq.of("k.b64", chachaKey, "k.b64", "%")));
assertThrows(IllegalArgumentException.class, () -> HmacKeyImportSpec.unmarshal(
PairSeq.of("mac", "HmacSHA256", "k.b64", encodedPrivateKey, "k.b64", "%")));
assertThrows(IllegalArgumentException.class, () -> MldsaPrivateKeySpec.unmarshal(
PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%")));
assertThrows(IllegalArgumentException.class, () -> SlhDsaPrivateKeySpec.unmarshal(
PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%")));
assertThrows(IllegalArgumentException.class, () -> SphincsPlusPrivateKeySpec.unmarshal(
PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%")));
assertThrows(IllegalArgumentException.class, () -> HmacKeyImportSpec
.unmarshal(PairSeq.of("mac", "HmacSHA256", "k.b64", encodedPrivateKey, "k.b64", "%")));
assertThrows(IllegalArgumentException.class,
() -> MldsaPrivateKeySpec.unmarshal(PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%")));
assertThrows(IllegalArgumentException.class,
() -> SlhDsaPrivateKeySpec.unmarshal(PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%")));
assertThrows(IllegalArgumentException.class, () -> SphincsPlusPrivateKeySpec
.unmarshal(PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%")));
System.out.println("...cases=6");
System.out.println("unmarshalCleansUpWhenMalformedDataFollowsValidSecretMaterial...ok");
}
@@ -195,7 +192,8 @@ class SecretSpecLifecycleTest {
generator.initialize(2048);
KeyPair pair = generator.generateKeyPair();
RsaPrivateKeySpec rsaSpec = new RsaPrivateKeySpec(pair.getPrivate().getEncoded());
PrivateKey imported = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().importPrivate("RSA", rsaSpec);
PrivateKey imported = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().importPrivate("RSA",
rsaSpec);
assertArrayEquals(pair.getPrivate().getEncoded(), imported.getEncoded());
assertFalse(rsaSpec.isDestroyed());
assertArrayEquals(pair.getPrivate().getEncoded(), rsaSpec.encoded());
@@ -221,9 +219,7 @@ class SecretSpecLifecycleTest {
}
private enum Factory {
CONSTRUCTOR,
STATIC_RAW,
HMAC
CONSTRUCTOR, STATIC_RAW, HMAC
}
private record SpecCase(String className, String accessor, int length, Factory factory) {

View File

@@ -60,12 +60,9 @@ class TargetArchitectureTest {
@Test
void obsoleteArchitectureTypesAreAbsent() {
String name = start("obsoleteArchitectureTypesAreAbsent");
assertThrows(ClassNotFoundException.class,
() -> Class.forName("zeroecho.core.spi.ContextConstructorKS"));
assertThrows(ClassNotFoundException.class,
() -> Class.forName("zeroecho.core.spi.SymmetricKeyBuilder"));
assertThrows(ClassNotFoundException.class,
() -> Class.forName("zeroecho.core.spi.AsymmetricKeyBuilder"));
assertThrows(ClassNotFoundException.class, () -> Class.forName("zeroecho.core.spi.ContextConstructorKS"));
assertThrows(ClassNotFoundException.class, () -> Class.forName("zeroecho.core.spi.SymmetricKeyBuilder"));
assertThrows(ClassNotFoundException.class, () -> Class.forName("zeroecho.core.spi.AsymmetricKeyBuilder"));
for (Method method : CryptoAlgorithms.class.getDeclaredMethods()) {
if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) {
@@ -76,8 +73,8 @@ class TargetArchitectureTest {
for (Field field : CryptoAlgorithms.class.getDeclaredFields()) {
assertTrue(Modifier.isFinal(field.getModifiers()), field.toString());
}
Set<String> removedConveniences = Set.of("generateSecret", "importSecret", "generateKeyPair",
"importPublic", "importPrivate");
Set<String> removedConveniences = Set.of("generateSecret", "importSecret", "generateKeyPair", "importPublic",
"importPrivate");
for (Method method : CryptoAlgorithm.class.getDeclaredMethods()) {
assertFalse(removedConveniences.contains(method.getName()), method.toString());
}
@@ -98,14 +95,13 @@ class TargetArchitectureTest {
String name = start("auditListenerFailureCannotChangeOperationOutcome");
AuditListener failing = new AuditListener() {
@Override
public void onContextCreatedMeta(String contextId, String algorithmId, String provider,
KeyUsage role, String keyFingerprint, Map<String, Object> metadata) {
public void onContextCreatedMeta(String contextId, String algorithmId, String provider, KeyUsage role,
String keyFingerprint, Map<String, Object> metadata) {
throw new AssertionError("controlled listener failure");
}
@Override
public void onContextClosed(String contextId, long bodyBytes, long trailerBytes,
long durationMillis) {
public void onContextClosed(String contextId, long bodyBytes, long trailerBytes, long durationMillis) {
throw new AssertionError("controlled listener failure");
}
};
@@ -127,14 +123,10 @@ class TargetArchitectureTest {
switch (info.operation()) {
case ASYMMETRIC_KEY_PAIR_GENERATE ->
assertDoesNotThrow(() -> algorithm.asymmetricKeyPairGenerator(specType));
case ASYMMETRIC_PUBLIC_IMPORT ->
assertDoesNotThrow(() -> algorithm.publicKeyImporter(specType));
case ASYMMETRIC_PRIVATE_IMPORT ->
assertDoesNotThrow(() -> algorithm.privateKeyImporter(specType));
case SYMMETRIC_GENERATE ->
assertDoesNotThrow(() -> algorithm.symmetricKeyGenerator(specType));
case SYMMETRIC_IMPORT ->
assertDoesNotThrow(() -> algorithm.symmetricKeyImporter(specType));
case ASYMMETRIC_PUBLIC_IMPORT -> assertDoesNotThrow(() -> algorithm.publicKeyImporter(specType));
case ASYMMETRIC_PRIVATE_IMPORT -> assertDoesNotThrow(() -> algorithm.privateKeyImporter(specType));
case SYMMETRIC_GENERATE -> assertDoesNotThrow(() -> algorithm.symmetricKeyGenerator(specType));
case SYMMETRIC_IMPORT -> assertDoesNotThrow(() -> algorithm.symmetricKeyImporter(specType));
}
}

View File

@@ -67,16 +67,16 @@ class ZeroEchoSessionWrapIntegrationTest {
SecretKey aesKey = new SecretKeySpec(new byte[16], "AES");
CtxInterface aesContext = Ctx.INSTANCE.getContext("wrap-integration-" + CONTEXT_IDS.incrementAndGet());
byte[] ciphertext;
try (EncryptionContext encryption = session.createContext(
"AES", KeyUsage.ENCRYPT, aesKey, AesSpec.gcm128(null))) {
try (EncryptionContext encryption = session.createContext("AES", KeyUsage.ENCRYPT, aesKey,
AesSpec.gcm128(null))) {
assertProxy(encryption);
((ContextAware) encryption).setContext(aesContext);
try (InputStream input = encryption.attach(new ByteArrayInputStream(message))) {
ciphertext = input.readAllBytes();
}
}
try (EncryptionContext decryption = session.createContext(
"AES", KeyUsage.DECRYPT, aesKey, AesSpec.gcm128(null))) {
try (EncryptionContext decryption = session.createContext("AES", KeyUsage.DECRYPT, aesKey,
AesSpec.gcm128(null))) {
assertProxy(decryption);
((ContextAware) decryption).setContext(aesContext);
try (InputStream input = decryption.attach(new ByteArrayInputStream(ciphertext))) {
@@ -84,16 +84,14 @@ class ZeroEchoSessionWrapIntegrationTest {
}
}
KeyPair signingKeys = session.keyBuilders().asymmetric()
.generateKeyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec());
KeyPair signingKeys = session.keyBuilders().asymmetric().generateKeyPair("Ed25519",
Ed25519KeyGenSpec.defaultSpec());
TaggedBody signature;
try (SignatureContext signer = session.createContext(
"Ed25519", KeyUsage.SIGN, signingKeys.getPrivate())) {
try (SignatureContext signer = session.createContext("Ed25519", KeyUsage.SIGN, signingKeys.getPrivate())) {
assertProxy(signer);
signature = produceTag(signer, message);
}
try (SignatureContext verifier = session.createContext(
"Ed25519", KeyUsage.VERIFY, signingKeys.getPublic())) {
try (SignatureContext verifier = session.createContext("Ed25519", KeyUsage.VERIFY, signingKeys.getPublic())) {
assertProxy(verifier);
verifier.setExpectedTag(signature.tag());
try (InputStream input = verifier.wrap(new ByteArrayInputStream(message))) {
@@ -111,9 +109,7 @@ class ZeroEchoSessionWrapIntegrationTest {
assertArrayEquals(message, produceTag(digest, message).body());
}
assertEquals(List.of(
"create:ENCRYPT", "create:DECRYPT",
"create:SIGN", "tag", "create:VERIFY", "verify:true",
assertEquals(List.of("create:ENCRYPT", "create:DECRYPT", "create:SIGN", "tag", "create:VERIFY", "verify:true",
"create:MAC", "tag", "create:DIGEST", "tag"), listener.events);
System.out.println("...contexts=6...events=" + listener.events.size());
System.out.println("registeredStreamContexts...ok");
@@ -127,10 +123,10 @@ class ZeroEchoSessionWrapIntegrationTest {
KeyPair aliceKeys = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobKeys = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
try (AgreementContext alice = session.createContext(
"Xdh", KeyUsage.AGREEMENT, aliceKeys.getPrivate(), XdhSpec.X25519);
AgreementContext bob = session.createContext(
"Xdh", KeyUsage.AGREEMENT, bobKeys.getPrivate(), XdhSpec.X25519)) {
try (AgreementContext alice = session.createContext("Xdh", KeyUsage.AGREEMENT, aliceKeys.getPrivate(),
XdhSpec.X25519);
AgreementContext bob = session.createContext("Xdh", KeyUsage.AGREEMENT, bobKeys.getPrivate(),
XdhSpec.X25519)) {
assertProxy(alice);
assertProxy(bob);
alice.setPeerPublic(bobKeys.getPublic());
@@ -138,10 +134,10 @@ class ZeroEchoSessionWrapIntegrationTest {
assertArrayEquals(alice.deriveSecret(), bob.deriveSecret());
}
try (MessageAgreementContext alice = session.createContext(
"Xdh", KeyUsage.AGREEMENT, new KeyPairKey(aliceKeys), XdhSpec.X25519);
MessageAgreementContext bob = session.createContext(
"Xdh", KeyUsage.AGREEMENT, new KeyPairKey(bobKeys), XdhSpec.X25519)) {
try (MessageAgreementContext alice = session.createContext("Xdh", KeyUsage.AGREEMENT, new KeyPairKey(aliceKeys),
XdhSpec.X25519);
MessageAgreementContext bob = session.createContext("Xdh", KeyUsage.AGREEMENT, new KeyPairKey(bobKeys),
XdhSpec.X25519)) {
assertProxy(alice);
assertProxy(bob);
byte[] aliceMessage = alice.getPeerMessage();
@@ -151,10 +147,9 @@ class ZeroEchoSessionWrapIntegrationTest {
assertArrayEquals(alice.deriveSecret(), bob.deriveSecret());
}
assertEquals(List.of(
"create:AGREEMENT", "create:AGREEMENT", "peer", "peer", "derived", "derived",
"create:AGREEMENT", "create:AGREEMENT", "message-get", "message-get",
"message-set", "message-set", "derived", "derived"), listener.events);
assertEquals(List.of("create:AGREEMENT", "create:AGREEMENT", "peer", "peer", "derived", "derived",
"create:AGREEMENT", "create:AGREEMENT", "message-get", "message-get", "message-set", "message-set",
"derived", "derived"), listener.events);
System.out.println("...contexts=4...events=" + listener.events.size());
System.out.println("registeredAgreementContexts...ok");
}
@@ -164,32 +159,30 @@ class ZeroEchoSessionWrapIntegrationTest {
System.out.println("registeredKemContexts");
RecordingListener listener = new RecordingListener();
ZeroEchoSession session = wrappedSession(listener);
KeyPair recipient = session.keyBuilders().asymmetric()
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512());
KeyPair recipient = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512());
KemContext.KemResult encapsulated;
try (KemContext encapsulator = session.createContext(
"ML-KEM", KeyUsage.ENCAPSULATE, recipient.getPublic(), VoidSpec.INSTANCE);
KemContext decapsulator = session.createContext(
"ML-KEM", KeyUsage.DECAPSULATE, recipient.getPrivate(), VoidSpec.INSTANCE)) {
try (KemContext encapsulator = session.createContext("ML-KEM", KeyUsage.ENCAPSULATE, recipient.getPublic(),
VoidSpec.INSTANCE);
KemContext decapsulator = session.createContext("ML-KEM", KeyUsage.DECAPSULATE, recipient.getPrivate(),
VoidSpec.INSTANCE)) {
assertProxy(encapsulator);
assertProxy(decapsulator);
encapsulated = encapsulator.encapsulate();
assertArrayEquals(encapsulated.sharedSecret(), decapsulator.decapsulate(encapsulated.ciphertext()));
}
try (MessageAgreementContext initiator = session.createContext(
"ML-KEM", KeyUsage.AGREEMENT, recipient.getPublic(), VoidSpec.INSTANCE);
MessageAgreementContext responder = session.createContext(
"ML-KEM", KeyUsage.AGREEMENT, recipient.getPrivate(), VoidSpec.INSTANCE)) {
try (MessageAgreementContext initiator = session.createContext("ML-KEM", KeyUsage.AGREEMENT,
recipient.getPublic(), VoidSpec.INSTANCE);
MessageAgreementContext responder = session.createContext("ML-KEM", KeyUsage.AGREEMENT,
recipient.getPrivate(), VoidSpec.INSTANCE)) {
assertProxy(initiator);
assertProxy(responder);
responder.setPeerMessage(initiator.getPeerMessage());
assertArrayEquals(initiator.deriveSecret(), responder.deriveSecret());
}
assertEquals(List.of(
"create:ENCAPSULATE", "create:DECAPSULATE", "encapsulated", "decapsulated",
assertEquals(List.of("create:ENCAPSULATE", "create:DECAPSULATE", "encapsulated", "decapsulated",
"create:AGREEMENT", "create:AGREEMENT", "message-get", "message-set", "derived", "derived"),
listener.events);
System.out.println("...contexts=4...events=" + listener.events.size());
@@ -204,8 +197,8 @@ class ZeroEchoSessionWrapIntegrationTest {
byte[][] tag = new byte[1][];
int tagLength = engine.tagLength();
byte[] emittedBody;
try (InputStream input = new TailStrippingInputStream(
engine.wrap(new ByteArrayInputStream(body)), tagLength, 128) {
try (InputStream input = new TailStrippingInputStream(engine.wrap(new ByteArrayInputStream(body)), tagLength,
128) {
@Override
protected void processTail(byte[] tail) {
tag[0] = tail.clone();
@@ -220,7 +213,8 @@ class ZeroEchoSessionWrapIntegrationTest {
assertTrue(Proxy.isProxyClass(context.getClass()));
}
private record TaggedBody(byte[] body, byte[] tag) {}
private record TaggedBody(byte[] body, byte[] tag) {
}
private static final class RecordingListener implements AuditListener {
private final List<String> events = new ArrayList<>();

View File

@@ -41,8 +41,8 @@ class AesDecryptionSecurityTest {
byte[] plaintext = repeated((byte) 0x5a, 96);
Encrypted encrypted = encrypt(AesSpec.gcm128(null), plaintext);
FailureResult modifiedBody = failedDecryption(
encrypted.spec, KEY, encrypted.iv, changed(encrypted.ciphertext, 0));
FailureResult modifiedBody = failedDecryption(encrypted.spec, KEY, encrypted.iv,
changed(encrypted.ciphertext, 0));
FailureResult modifiedTag = failedDecryption(encrypted.spec, KEY, encrypted.iv,
changed(encrypted.ciphertext, encrypted.ciphertext.length - 1));
FailureResult truncatedBody = failedDecryption(encrypted.spec, KEY, encrypted.iv,
@@ -52,19 +52,17 @@ class AesDecryptionSecurityTest {
FailureResult trailing = failedDecryption(encrypted.spec, KEY, encrypted.iv,
Arrays.copyOf(encrypted.ciphertext, encrypted.ciphertext.length + 1));
FailureResult wrongKey = failedDecryption(encrypted.spec, WRONG_KEY, encrypted.iv, encrypted.ciphertext);
FailureResult wrongIv = failedDecryption(
encrypted.spec, KEY, changed(encrypted.iv, 0), encrypted.ciphertext);
FailureResult wrongIv = failedDecryption(encrypted.spec, KEY, changed(encrypted.iv, 0), encrypted.ciphertext);
FailureResult empty = failedDecryption(encrypted.spec, KEY, encrypted.iv, new byte[0]);
for (FailureResult result : List.of(
modifiedBody, modifiedTag, truncatedBody, truncatedTag, trailing, wrongKey, wrongIv, empty)) {
for (FailureResult result : List.of(modifiedBody, modifiedTag, truncatedBody, truncatedTag, trailing, wrongKey,
wrongIv, empty)) {
assertEquals(0, result.outputBytes());
assertAuthenticationFailure(result.failure());
}
assertThrows(IOException.class, () -> decrypt(encrypted.spec, KEY,
Arrays.copyOf(encrypted.iv, encrypted.iv.length - 1), encrypted.ciphertext));
assertThrows(IOException.class,
() -> decrypt(encrypted.spec, KEY, null, encrypted.ciphertext));
assertThrows(IOException.class, () -> decrypt(encrypted.spec, KEY, null, encrypted.ciphertext));
System.out.println("...cases=10");
System.out.println("gcmTamperMatrix...ok");
}
@@ -102,18 +100,15 @@ class AesDecryptionSecurityTest {
byte[] plaintext = repeated((byte) 0x44, 32);
Encrypted encrypted = encrypt(spec, plaintext);
assertFalse(Arrays.equals(plaintext,
decrypt(spec, KEY, changed(encrypted.iv, 0), encrypted.ciphertext)));
assertFalse(Arrays.equals(plaintext,
decrypt(spec, WRONG_KEY, encrypted.iv, encrypted.ciphertext)));
assertFalse(Arrays.equals(plaintext,
decrypt(spec, KEY, encrypted.iv, changed(encrypted.ciphertext, 0))));
assertFalse(Arrays.equals(plaintext, decrypt(spec, KEY, changed(encrypted.iv, 0), encrypted.ciphertext)));
assertFalse(Arrays.equals(plaintext, decrypt(spec, WRONG_KEY, encrypted.iv, encrypted.ciphertext)));
assertFalse(Arrays.equals(plaintext, decrypt(spec, KEY, encrypted.iv, changed(encrypted.ciphertext, 0))));
assertThrows(IOException.class, () -> decrypt(spec, KEY, encrypted.iv,
Arrays.copyOf(encrypted.ciphertext, encrypted.ciphertext.length - 1)));
assertThrows(IOException.class, () -> decrypt(spec, KEY, encrypted.iv,
Arrays.copyOf(encrypted.ciphertext, encrypted.ciphertext.length + 1)));
assertThrows(IOException.class, () -> decrypt(spec, KEY,
Arrays.copyOf(encrypted.iv, encrypted.iv.length - 1), encrypted.ciphertext));
assertThrows(IOException.class,
() -> decrypt(spec, KEY, Arrays.copyOf(encrypted.iv, encrypted.iv.length - 1), encrypted.ciphertext));
assertThrows(IOException.class, () -> decrypt(spec, KEY, null, encrypted.ciphertext));
System.out.println("...cases=7");
System.out.println("cbcNoPaddingMatrix...ok");
@@ -132,8 +127,8 @@ class AesDecryptionSecurityTest {
// authentication.
assertThrows(IOException.class, () -> decrypt(spec, KEY, encrypted.iv,
changed(encrypted.ciphertext, encrypted.ciphertext.length - 17)));
assertThrows(IOException.class, () -> decrypt(spec, KEY,
Arrays.copyOf(encrypted.iv, encrypted.iv.length - 1), encrypted.ciphertext));
assertThrows(IOException.class,
() -> decrypt(spec, KEY, Arrays.copyOf(encrypted.iv, encrypted.iv.length - 1), encrypted.ciphertext));
assertThrows(IOException.class, () -> decrypt(spec, KEY, null, encrypted.ciphertext));
System.out.println("...cases=4");
System.out.println("cbcPkcsPaddingMatrix...ok");
@@ -141,7 +136,8 @@ class AesDecryptionSecurityTest {
private static Encrypted encrypt(AesSpec spec, byte[] plaintext) throws Exception {
CtxInterface context = newContext("aes-security-enc-");
EncryptionContext encryption = new zeroecho.sdk.ZeroEchoSession().createContext("AES", KeyUsage.ENCRYPT, KEY, spec);
EncryptionContext encryption = new zeroecho.sdk.ZeroEchoSession().createContext("AES", KeyUsage.ENCRYPT, KEY,
spec);
((ContextAware) encryption).setContext(context);
byte[] ciphertext;
try (InputStream stream = encryption.attach(new ByteArrayInputStream(plaintext))) {
@@ -157,7 +153,8 @@ class AesDecryptionSecurityTest {
if (iv != null) {
context.put(ConfluxKeys.iv("AES"), iv);
}
EncryptionContext decryption = new zeroecho.sdk.ZeroEchoSession().createContext("AES", KeyUsage.DECRYPT, key, spec);
EncryptionContext decryption = new zeroecho.sdk.ZeroEchoSession().createContext("AES", KeyUsage.DECRYPT, key,
spec);
((ContextAware) decryption).setContext(context);
try (InputStream stream = decryption.attach(new ByteArrayInputStream(ciphertext))) {
return stream.readAllBytes();
@@ -170,7 +167,8 @@ class AesDecryptionSecurityTest {
throws Exception {
CtxInterface context = newContext("aes-security-fail-");
context.put(ConfluxKeys.iv("AES"), iv);
EncryptionContext decryption = new zeroecho.sdk.ZeroEchoSession().createContext("AES", KeyUsage.DECRYPT, key, spec);
EncryptionContext decryption = new zeroecho.sdk.ZeroEchoSession().createContext("AES", KeyUsage.DECRYPT, key,
spec);
((ContextAware) decryption).setContext(context);
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (InputStream stream = decryption.attach(new ByteArrayInputStream(ciphertext))) {
@@ -226,7 +224,9 @@ class AesDecryptionSecurityTest {
return result;
}
private record Encrypted(AesSpec spec, byte[] iv, byte[] ciphertext) {}
private record Encrypted(AesSpec spec, byte[] iv, byte[] ciphertext) {
}
private record FailureResult(int outputBytes, IOException failure) {}
private record FailureResult(int outputBytes, IOException failure) {
}
}

View File

@@ -198,7 +198,8 @@ class AesRandomSupportTest {
@Test
void publicBuilderExposesOnlyDecryptionIvConfiguration() throws Exception {
System.out.print("AesRandomSupport/publicBuilderExposesOnlyDecryptionIvConfiguration...");
List<String> methodNames = Arrays.stream(AesDataContentBuilder.class.getMethods()).map(Method::getName).toList();
List<String> methodNames = Arrays.stream(AesDataContentBuilder.class.getMethods()).map(Method::getName)
.toList();
assertFalse(methodNames.contains("withIv"));
assertTrue(methodNames.contains("withDecryptionIv"));

View File

@@ -116,17 +116,20 @@ public class ChaChaLargeDataTest {
byte[] msg = randomBytes(SIZE);
CryptoAlgorithm chacha = CryptoAlgorithms.require("CHACHA20");
SecretKey key = chacha.symmetricKeyGenerator(ChaChaKeyGenSpec.class).generateSecret(ChaChaKeyGenSpec.chacha256());
SecretKey key = chacha.symmetricKeyGenerator(ChaChaKeyGenSpec.class)
.generateSecret(ChaChaKeyGenSpec.chacha256());
CtxInterface session = Ctx.INSTANCE.getContext("chacha-ctx-" + System.nanoTime());
ChaChaSpec spec = ChaChaSpec.builder().initialCounter(1).header(null).build();
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.ENCRYPT, key, spec);
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.ENCRYPT, key,
spec);
((ContextAware) enc).setContext(session);
byte[] ct = readAll(enc.attach(new ByteArrayInputStream(msg)));
enc.close();
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key, spec);
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key,
spec);
((ContextAware) dec).setContext(session);
byte[] pt = readAll(dec.attach(new ByteArrayInputStream(ct)));
dec.close();
@@ -143,17 +146,20 @@ public class ChaChaLargeDataTest {
byte[] msg = randomBytes(SIZE);
CryptoAlgorithm chacha = CryptoAlgorithms.require("CHACHA20");
SecretKey key = chacha.symmetricKeyGenerator(ChaChaKeyGenSpec.class).generateSecret(ChaChaKeyGenSpec.chacha256());
SecretKey key = chacha.symmetricKeyGenerator(ChaChaKeyGenSpec.class)
.generateSecret(ChaChaKeyGenSpec.chacha256());
CtxInterface session = Ctx.INSTANCE.getContext("chacha-hdr-" + System.nanoTime());
ChaChaSpec spec = ChaChaSpec.builder().initialCounter(1).header(new ChaChaHeaderCodec()).build();
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.ENCRYPT, key, spec);
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.ENCRYPT, key,
spec);
((ContextAware) enc).setContext(session);
byte[] ct = readAll(enc.attach(new ByteArrayInputStream(msg)));
enc.close();
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key, spec);
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key,
spec);
((ContextAware) dec).setContext(session);
byte[] pt = readAll(dec.attach(new ByteArrayInputStream(ct)));
dec.close();
@@ -174,14 +180,16 @@ public class ChaChaLargeDataTest {
byte[] msg = randomBytes(SIZE);
CryptoAlgorithm chacha = CryptoAlgorithms.require("CHACHA20");
SecretKey key = chacha.symmetricKeyGenerator(ChaChaKeyGenSpec.class).generateSecret(ChaChaKeyGenSpec.chacha256());
SecretKey key = chacha.symmetricKeyGenerator(ChaChaKeyGenSpec.class)
.generateSecret(ChaChaKeyGenSpec.chacha256());
// Encrypt with explicit counter=7 (in ctx), headerless (ctx-only).
CtxInterface encCtx = Ctx.INSTANCE.getContext("chacha-ctr-enc-" + System.nanoTime());
encCtx.put(ConfluxKeys.tagBits("CHACHA20"), 7);
ChaChaSpec spec = ChaChaSpec.builder().initialCounter(1).header(null).build();
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.ENCRYPT, key, spec);
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.ENCRYPT, key,
spec);
((ContextAware) enc).setContext(encCtx);
byte[] ct = readAll(enc.attach(new ByteArrayInputStream(msg)));
enc.close();
@@ -194,7 +202,8 @@ public class ChaChaLargeDataTest {
decCtxOk.put(ConfluxKeys.iv("CHACHA20"), nonce);
decCtxOk.put(ConfluxKeys.tagBits("CHACHA20"), 7);
EncryptionContext decOk = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key, spec);
EncryptionContext decOk = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key,
spec);
((ContextAware) decOk).setContext(decCtxOk);
byte[] ptOk = readAll(decOk.attach(new ByteArrayInputStream(ct)));
decOk.close();
@@ -205,7 +214,8 @@ public class ChaChaLargeDataTest {
decCtxBad.put(ConfluxKeys.iv("CHACHA20"), nonce);
decCtxBad.put(ConfluxKeys.tagBits("CHACHA20"), 8);
EncryptionContext decBad = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key, spec);
EncryptionContext decBad = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key,
spec);
((ContextAware) decBad).setContext(decCtxBad);
byte[] ptBad = readAll(decBad.attach(new ByteArrayInputStream(ct)));
decBad.close();
@@ -235,12 +245,14 @@ public class ChaChaLargeDataTest {
ChaCha20Poly1305Spec spec = ChaCha20Poly1305Spec.builder().header(null).build();
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.ENCRYPT, key, spec);
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.ENCRYPT,
key, spec);
((ContextAware) enc).setContext(session);
byte[] ct = readAll(enc.attach(new ByteArrayInputStream(msg)));
enc.close();
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.DECRYPT, key, spec);
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.DECRYPT,
key, spec);
((ContextAware) dec).setContext(session);
byte[] pt = readAll(dec.attach(new ByteArrayInputStream(ct)));
dec.close();
@@ -266,12 +278,14 @@ public class ChaChaLargeDataTest {
ChaCha20Poly1305Spec spec = ChaCha20Poly1305Spec.builder().header(new ChaCha20Poly1305HeaderCodec()).build();
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.ENCRYPT, key, spec);
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.ENCRYPT,
key, spec);
((ContextAware) enc).setContext(session);
byte[] ct = readAll(enc.attach(new ByteArrayInputStream(msg)));
enc.close();
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.DECRYPT, key, spec);
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.DECRYPT,
key, spec);
((ContextAware) dec).setContext(session);
byte[] pt = readAll(dec.attach(new ByteArrayInputStream(ct)));
dec.close();
@@ -302,7 +316,8 @@ public class ChaChaLargeDataTest {
ChaCha20Poly1305Spec spec = ChaCha20Poly1305Spec.builder().header(null).build();
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.ENCRYPT, key, spec);
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.ENCRYPT,
key, spec);
((ContextAware) enc).setContext(encCtx);
byte[] ct = readAll(enc.attach(new ByteArrayInputStream(msg)));
enc.close();
@@ -314,7 +329,8 @@ public class ChaChaLargeDataTest {
byte[] nonce = encCtx.get(ConfluxKeys.iv("CHACHA20-POLY1305"));
decCtx.put(ConfluxKeys.iv("CHACHA20-POLY1305"), nonce);
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.DECRYPT, key, spec);
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.DECRYPT,
key, spec);
((ContextAware) dec).setContext(decCtx);
assertThrows(IOException.class, () -> {

View File

@@ -177,8 +177,8 @@ class ChaChaNonceLifecycleTest {
private static ChaCha20Poly1305CipherContext encryptingContext(ChaCha20Poly1305Spec spec, SecureRandom random,
CtxInterface operation) {
ChaCha20Poly1305CipherContext context = new ChaCha20Poly1305CipherContext(
new ChaCha20Poly1305Algorithm(), KEY, true, spec, random);
ChaCha20Poly1305CipherContext context = new ChaCha20Poly1305CipherContext(new ChaCha20Poly1305Algorithm(), KEY,
true, spec, random);
((ContextAware) context).setContext(operation);
return context;
}
@@ -190,8 +190,8 @@ class ChaChaNonceLifecycleTest {
}
private static byte[] decryptHeader(byte[] ciphertext) throws Exception {
ChaCha20Poly1305CipherContext context = new ChaCha20Poly1305CipherContext(
new ChaCha20Poly1305Algorithm(), KEY, false, HEADER_SPEC, null);
ChaCha20Poly1305CipherContext context = new ChaCha20Poly1305CipherContext(new ChaCha20Poly1305Algorithm(), KEY,
false, HEADER_SPEC, null);
((ContextAware) context).setContext(Ctx.INSTANCE.getContext("chacha-header-decrypt-" + System.nanoTime()));
try (InputStream stream = context.attach(new ByteArrayInputStream(ciphertext))) {
return stream.readAllBytes();

View File

@@ -206,8 +206,10 @@ public class AgreementAlgorithmsRoundTripTest {
MessageAgreementContext bCtx = null;
try {
aCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, aliceKey, spec);
bCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, bobKey, spec);
aCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, aliceKey,
spec);
bCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, bobKey,
spec);
byte[] aMsg = aCtx.getPeerMessage();
byte[] bMsg = bCtx.getPeerMessage();
@@ -274,12 +276,12 @@ public class AgreementAlgorithmsRoundTripTest {
try {
// Alice (initiator): has Bob's public key
aliceCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, bob.getPublic(),
VoidSpec.INSTANCE);
aliceCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT,
bob.getPublic(), VoidSpec.INSTANCE);
// Bob (responder): has his private key
bobCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, bob.getPrivate(),
VoidSpec.INSTANCE);
bobCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT,
bob.getPrivate(), VoidSpec.INSTANCE);
// Initiator produces encapsulation message (ciphertext) to send
byte[] enc = aliceCtx.getPeerMessage();
@@ -362,8 +364,10 @@ public class AgreementAlgorithmsRoundTripTest {
AgreementContext bCtx = null;
try {
aCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, alice.getPrivate(), spec);
bCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, bob.getPrivate(), spec);
aCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT,
alice.getPrivate(), spec);
bCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT,
bob.getPrivate(), spec);
aCtx.setPeerPublic(bob.getPublic());
bCtx.setPeerPublic(alice.getPublic());

View File

@@ -135,7 +135,8 @@ public class EcdsaLargeDataTest {
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ECDSA", spec);
// SIGN (streaming): emits [body][signature]; capture trailer
SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("ECDSA", KeyUsage.SIGN, kp.getPrivate(), spec);
SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("ECDSA", KeyUsage.SIGN,
kp.getPrivate(), spec);
final byte[][] sigHolder = new byte[1][];
final int sigLen = signer.tagLength(); // should equal spec.signFixedLength()
@@ -160,7 +161,8 @@ public class EcdsaLargeDataTest {
System.out.println("...signature size: " + ourSig.length + " (expected " + spec.signFixedLength() + ")");
// VERIFY with our streaming verifier
SignatureContext verifier = new zeroecho.sdk.ZeroEchoSession().createContext("ECDSA", KeyUsage.VERIFY, kp.getPublic(), spec);
SignatureContext verifier = new zeroecho.sdk.ZeroEchoSession().createContext("ECDSA", KeyUsage.VERIFY,
kp.getPublic(), spec);
verifier.setExpectedTag(ourSig);
byte[] sink2;
try (InputStream verIn = verifier.wrap(new ByteArrayInputStream(msg))) {
@@ -181,7 +183,8 @@ public class EcdsaLargeDataTest {
// Extra symmetry check (optional): our verifier must accept a JCA signature
// (different bytes)
byte[] jcaSig = jcaEcdsaSign(spec.jcaFactory(), kp.getPrivate(), msg);
SignatureContext verifier2 = new zeroecho.sdk.ZeroEchoSession().createContext("ECDSA", KeyUsage.VERIFY, kp.getPublic(), spec);
SignatureContext verifier2 = new zeroecho.sdk.ZeroEchoSession().createContext("ECDSA", KeyUsage.VERIFY,
kp.getPublic(), spec);
verifier2.setExpectedTag(jcaSig);
try (InputStream verIn2 = verifier2.wrap(new ByteArrayInputStream(msg))) {
byte[] passthrough = readAll(verIn2);

View File

@@ -132,11 +132,13 @@ public class Ed25519LargeDataTest {
return;
}
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec());
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed25519",
Ed25519KeyGenSpec.defaultSpec());
// SIGN: context emits [body][signature] — capture trailer via
// TailStrippingInputStream
SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("Ed25519", KeyUsage.SIGN, kp.getPrivate(), null);
SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("Ed25519", KeyUsage.SIGN,
kp.getPrivate(), null);
final byte[][] sigHolder = new byte[1][];
final int sigLen = signer.tagLength();
@@ -166,7 +168,8 @@ public class Ed25519LargeDataTest {
assertArrayEquals(refSig, ourSig, "signature mismatch vs JCA reference");
// VERIFY: supply expected tag and drain (throws on mismatch)
SignatureContext verifier = new zeroecho.sdk.ZeroEchoSession().createContext("Ed25519", KeyUsage.VERIFY, kp.getPublic(), null);
SignatureContext verifier = new zeroecho.sdk.ZeroEchoSession().createContext("Ed25519", KeyUsage.VERIFY,
kp.getPublic(), null);
verifier.setExpectedTag(ourSig);
byte[] sink2;

View File

@@ -132,11 +132,13 @@ public class Ed448LargeDataTest {
return;
}
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed448", Ed448KeyGenSpec.defaultSpec());
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed448",
Ed448KeyGenSpec.defaultSpec());
// SIGN: context emits [body][signature] — capture trailer via
// TailStrippingInputStream
SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("Ed448", KeyUsage.SIGN, kp.getPrivate(), null);
SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("Ed448", KeyUsage.SIGN,
kp.getPrivate(), null);
final byte[][] sigHolder = new byte[1][];
final int sigLen = signer.tagLength();
@@ -166,7 +168,8 @@ public class Ed448LargeDataTest {
assertArrayEquals(refSig, ourSig, "signature mismatch vs JCA reference");
// VERIFY: supply expected tag and drain (throws on mismatch)
SignatureContext verifier = new zeroecho.sdk.ZeroEchoSession().createContext("Ed448", KeyUsage.VERIFY, kp.getPublic(), null);
SignatureContext verifier = new zeroecho.sdk.ZeroEchoSession().createContext("Ed448", KeyUsage.VERIFY,
kp.getPublic(), null);
verifier.setExpectedTag(ourSig);
byte[] sink2;

View File

@@ -92,16 +92,19 @@ public class ElgamalLargeDataTest {
byte[] msg = randomBytes(SIZE);
System.out.printf("...input: %d bytes%n", msg.length);
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048());
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal",
ElgamalParamSpec.ffdhe2048());
ElgamalEncSpec spec = ElgamalEncSpec.pkcs1();
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, kp.getPublic(), spec);
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT,
kp.getPublic(), spec);
InputStream ctIn = enc.attach(new ByteArrayInputStream(msg));
byte[] ct = ctIn.readAllBytes();
enc.close();
System.out.printf("...encrypted: %d bytes%n", ct.length);
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.DECRYPT, kp.getPrivate(), spec);
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.DECRYPT,
kp.getPrivate(), spec);
InputStream ptIn = dec.attach(new ByteArrayInputStream(ct));
byte[] pt = ptIn.readAllBytes();
dec.close();
@@ -120,16 +123,19 @@ public class ElgamalLargeDataTest {
byte[] msg = randomBytes(SIZE);
System.out.printf("...input: %d bytes%n", msg.length);
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048());
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal",
ElgamalParamSpec.ffdhe2048());
ElgamalEncSpec spec = ElgamalEncSpec.noPadding();
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, kp.getPublic(), spec);
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT,
kp.getPublic(), spec);
InputStream ctIn = enc.attach(new ByteArrayInputStream(msg));
byte[] ct = ctIn.readAllBytes();
enc.close();
System.out.printf("...encrypted: %d bytes%n", ct.length);
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.DECRYPT, kp.getPrivate(), spec);
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.DECRYPT,
kp.getPrivate(), spec);
InputStream ptIn = dec.attach(new ByteArrayInputStream(ct));
byte[] pt = ptIn.readAllBytes();
dec.close();
@@ -148,10 +154,12 @@ public class ElgamalLargeDataTest {
byte[] msg = randomBytes(SIZE);
System.out.printf("...input: %d bytes%n", msg.length);
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048());
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal",
ElgamalParamSpec.ffdhe2048());
ElgamalEncSpec spec = ElgamalEncSpec.noPadding();
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, kp.getPublic(), spec);
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT,
kp.getPublic(), spec);
InputStream ctIn = enc.attach(new ByteArrayInputStream(msg));
assertThrowsExactly(IllegalStateException.class, () -> ctIn.readAllBytes(),
"No-padding cipher streams cannot processes incomplete blocks: 3 instead of 255");

View File

@@ -127,7 +127,8 @@ public class HmacLargeDataTest {
CryptoAlgorithm algo = CryptoAlgorithms.require(ALG_ID);
// Generate a key (macName must match)
SecretKey key = algo.symmetricKeyGenerator(HmacKeyGenSpec.class).generateSecret(new HmacKeyGenSpec(JCA_MAC, 256));
SecretKey key = algo.symmetricKeyGenerator(HmacKeyGenSpec.class)
.generateSecret(new HmacKeyGenSpec(JCA_MAC, 256));
// --- MAC (produce): engine emits [body][tag]; capture trailer ---
HmacSpec spec = HmacSpec.sha256();

View File

@@ -119,11 +119,13 @@ public final class MldsaLargeDataTest {
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-DSA", spec);
SignatureContext mldsaVerifier = new zeroecho.sdk.ZeroEchoSession().createContext("ML-DSA", KeyUsage.VERIFY, kp.getPublic());
SignatureContext mldsaVerifier = new zeroecho.sdk.ZeroEchoSession().createContext("ML-DSA", KeyUsage.VERIFY,
kp.getPublic());
int expectedSigLen = mldsaVerifier.tagLength();
System.out.println(INDENT + " expectedSigLen=" + expectedSigLen);
SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("ML-DSA", KeyUsage.SIGN, kp.getPrivate());
SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("ML-DSA", KeyUsage.SIGN,
kp.getPrivate());
final byte[][] sigHolder = new byte[1][];
byte[] passthrough;
@@ -178,7 +180,8 @@ public final class MldsaLargeDataTest {
byte[] badSig = Arrays.copyOf(signature, signature.length);
badSig[0] = (byte) (badSig[0] ^ 0x01);
SignatureContext badVerifier = new zeroecho.sdk.ZeroEchoSession().createContext("ML-DSA", KeyUsage.VERIFY, kp.getPublic());
SignatureContext badVerifier = new zeroecho.sdk.ZeroEchoSession().createContext("ML-DSA", KeyUsage.VERIFY,
kp.getPublic());
try {
badVerifier.setExpectedTag(badSig);

View File

@@ -47,8 +47,7 @@ class BlockGeometryTest {
assertEquals(2, first.inChunkSize());
assertEquals(3, first.outChunkSize());
assertEquals(0, first.finalizationOutputChunks());
assertEquals("BlockGeometry[inChunkSize=2, outChunkSize=3, finalizationOutputChunks=0]",
first.toString());
assertEquals("BlockGeometry[inChunkSize=2, outChunkSize=3, finalizationOutputChunks=0]", first.toString());
System.out.println("...legacyFields=true");
System.out.println("valueSemantics...ok");
}
@@ -56,7 +55,8 @@ class BlockGeometryTest {
@Test
void actualRsaPath() throws Exception {
System.out.println("actualRsaPath");
KeyPair keyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048());
KeyPair keyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA",
RsaKeyGenSpec.rsa2048());
RsaEncSpec spec = RsaEncSpec.oaep(RsaEncSpec.Hash.SHA256);
BlockGeometry encryptGeometry = BlockGeometry.forRsa(spec, keyPair.getPublic(), true);
BlockGeometry decryptGeometry = BlockGeometry.forRsa(spec, keyPair.getPrivate(), false);
@@ -66,7 +66,8 @@ class BlockGeometryTest {
assertEquals(256, decryptGeometry.inChunkSize());
assertEquals(256, decryptGeometry.outChunkSize());
EncryptionContext context = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, keyPair.getPublic(), spec);
EncryptionContext context = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT,
keyPair.getPublic(), spec);
try (InputStream stream = context.attach(new ByteArrayInputStream(new byte[] { 1, 2, 3 }))) {
assertEquals(256, stream.readAllBytes().length);
} finally {

View File

@@ -88,16 +88,19 @@ public class RsaLargeDataTest {
byte[] msg = randomBytes(SIZE);
System.out.printf("...input: %d bytes%n", msg.length);
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048());
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA",
RsaKeyGenSpec.rsa2048());
RsaEncSpec spec = RsaEncSpec.oaep(RsaEncSpec.Hash.SHA256);
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, kp.getPublic(), spec);
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT,
kp.getPublic(), spec);
InputStream ctIn = enc.attach(new ByteArrayInputStream(msg));
byte[] ct = ctIn.readAllBytes();
enc.close();
System.out.printf("...encrypted: %d bytes%n", ct.length);
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.DECRYPT, kp.getPrivate(), spec);
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.DECRYPT,
kp.getPrivate(), spec);
InputStream ptIn = dec.attach(new ByteArrayInputStream(ct));
byte[] pt = ptIn.readAllBytes();
dec.close();
@@ -116,16 +119,19 @@ public class RsaLargeDataTest {
byte[] msg = randomBytes(SIZE);
System.out.printf("...input: %d bytes%n", msg.length);
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048());
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA",
RsaKeyGenSpec.rsa2048());
RsaEncSpec spec = RsaEncSpec.pkcs1v15();
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, kp.getPublic(), spec);
EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT,
kp.getPublic(), spec);
InputStream ctIn = enc.attach(new ByteArrayInputStream(msg));
byte[] ct = ctIn.readAllBytes();
enc.close();
System.out.printf("...encrypted: %d bytes%n", ct.length);
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.DECRYPT, kp.getPrivate(), spec);
EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.DECRYPT,
kp.getPrivate(), spec);
InputStream ptIn = dec.attach(new ByteArrayInputStream(ct));
byte[] pt = ptIn.readAllBytes();
dec.close();

View File

@@ -126,14 +126,16 @@ public final class SlhDsaLargeDataTest {
// Create verifier FIRST to obtain tag length via
// SlhDsaSignatureContext.sigLenFromPublicKey.
SignatureContext verifierCtx = new zeroecho.sdk.ZeroEchoSession().createContext("SLH-DSA", KeyUsage.VERIFY, kp.getPublic());
SignatureContext verifierCtx = new zeroecho.sdk.ZeroEchoSession().createContext("SLH-DSA", KeyUsage.VERIFY,
kp.getPublic());
int expectedSigLen = verifierCtx.tagLength();
System.out.println(INDENT + " expectedSigLen=" + expectedSigLen);
// Now sign and strip trailer using the expected length from verifier (not from
// signer).
SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("SLH-DSA", KeyUsage.SIGN, kp.getPrivate());
SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("SLH-DSA", KeyUsage.SIGN,
kp.getPrivate());
final byte[][] sigHolder = new byte[1][];
byte[] passthrough;
@@ -188,7 +190,8 @@ public final class SlhDsaLargeDataTest {
byte[] badSig = Arrays.copyOf(signature, signature.length);
badSig[0] = (byte) (badSig[0] ^ 0x01);
SignatureContext badVerifier = new zeroecho.sdk.ZeroEchoSession().createContext("SLH-DSA", KeyUsage.VERIFY, kp.getPublic());
SignatureContext badVerifier = new zeroecho.sdk.ZeroEchoSession().createContext("SLH-DSA", KeyUsage.VERIFY,
kp.getPublic());
try {
badVerifier.setExpectedTag(badSig);

View File

@@ -48,9 +48,8 @@ class AuditedContextsRegressionTest {
DigestContext target = mock(DigestContext.class);
when(target.tagLength()).thenReturn(2);
when(target.wrap(any(InputStream.class))).thenAnswer(invocation -> invocation.getArgument(0));
DigestContext unrelated = (DigestContext) Proxy.newProxyInstance(
DigestContext.class.getClassLoader(), new Class<?>[] { DigestContext.class },
(proxy, method, arguments) -> {
DigestContext unrelated = (DigestContext) Proxy.newProxyInstance(DigestContext.class.getClassLoader(),
new Class<?>[] { DigestContext.class }, (proxy, method, arguments) -> {
try {
return method.invoke(target, arguments);
} catch (InvocationTargetException exception) {
@@ -62,7 +61,8 @@ class AuditedContextsRegressionTest {
assertNotSame(unrelated, wrapped);
assertTrue(Proxy.isProxyClass(wrapped.getClass()));
assertArrayEquals(new byte[] { 1, 2 }, wrapped.wrap(new ByteArrayInputStream(new byte[] { 1, 2 })).readAllBytes());
assertArrayEquals(new byte[] { 1, 2 },
wrapped.wrap(new ByteArrayInputStream(new byte[] { 1, 2 })).readAllBytes());
assertSame(wrapped, AuditedContexts.wrap(wrapped, AuditListener.noop(), KeyUsage.DIGEST));
System.out.println("...nestedProxy=true");
System.out.println("unrelatedProxyGetsWrapped...ok");
@@ -179,8 +179,8 @@ class AuditedContextsRegressionTest {
AgreementContext agreement = mock(AgreementContext.class);
doThrow(peerFailure).when(agreement).setPeerPublic(any(PublicKey.class));
when(agreement.deriveSecret()).thenThrow(deriveFailure);
AgreementContext wrappedAgreement = (AgreementContext) AuditedContexts.wrap(
agreement, listener, KeyUsage.AGREEMENT);
AgreementContext wrappedAgreement = (AgreementContext) AuditedContexts.wrap(agreement, listener,
KeyUsage.AGREEMENT);
assertSame(peerFailure, assertThrows(IllegalArgumentException.class,
() -> wrappedAgreement.setPeerPublic(mock(PublicKey.class))));
@@ -193,14 +193,13 @@ class AuditedContextsRegressionTest {
doThrow(setMessageFailure).when(messageAgreement).setPeerMessage(any(byte[].class));
when(messageAgreement.getPeerMessage()).thenThrow(getMessageFailure);
when(messageAgreement.deriveSecret()).thenThrow(messageDeriveFailure);
MessageAgreementContext wrappedMessage = (MessageAgreementContext) AuditedContexts.wrap(
messageAgreement, listener, KeyUsage.AGREEMENT);
MessageAgreementContext wrappedMessage = (MessageAgreementContext) AuditedContexts.wrap(messageAgreement,
listener, KeyUsage.AGREEMENT);
assertSame(setMessageFailure, assertThrows(IllegalArgumentException.class,
() -> wrappedMessage.setPeerMessage(new byte[] { 1 })));
assertSame(setMessageFailure,
assertThrows(IllegalArgumentException.class, () -> wrappedMessage.setPeerMessage(new byte[] { 1 })));
assertSame(getMessageFailure, assertThrows(IllegalStateException.class, wrappedMessage::getPeerMessage));
assertSame(messageDeriveFailure,
assertThrows(IllegalStateException.class, wrappedMessage::deriveSecret));
assertSame(messageDeriveFailure, assertThrows(IllegalStateException.class, wrappedMessage::deriveSecret));
assertEquals(List.of(peerFailure, deriveFailure, setMessageFailure, getMessageFailure, messageDeriveFailure),
listener.failures);

View File

@@ -65,8 +65,8 @@ class JulAuditListenerStdSecurityTest {
}
private static JulAuditListenerStd listener(RecordingHandler handler) {
Logger logger = Logger.getLogger(
JulAuditListenerStdSecurityTest.class.getName() + "." + LOGGER_IDS.incrementAndGet());
Logger logger = Logger
.getLogger(JulAuditListenerStdSecurityTest.class.getName() + "." + LOGGER_IDS.incrementAndGet());
logger.setUseParentHandlers(false);
logger.setLevel(Level.ALL);
handler.setLevel(Level.ALL);

View File

@@ -25,10 +25,8 @@ class CipherTransformInputStreamBuilderTest {
assertThrows(IllegalArgumentException.class, () -> new TestStream(2, 1, 0, 0));
assertThrows(IllegalArgumentException.class, () -> new TestStream(2, 1, 1, -1));
assertThrows(IllegalArgumentException.class, () -> new TestStream(Integer.MAX_VALUE, 1, 2, 0));
assertThrows(IllegalArgumentException.class,
() -> new TestStream(2, 1, Integer.MAX_VALUE, 1));
assertThrows(IllegalArgumentException.class,
() -> new TestStream(2, Integer.MAX_VALUE, 1, 1));
assertThrows(IllegalArgumentException.class, () -> new TestStream(2, 1, Integer.MAX_VALUE, 1));
assertThrows(IllegalArgumentException.class, () -> new TestStream(2, Integer.MAX_VALUE, 1, 1));
new TestStream(2, 1, 1, 0);
new TestStream(2, 1, 1, 1);
@@ -57,16 +55,14 @@ class CipherTransformInputStreamBuilderTest {
System.out.print("CipherBuilder/algorithm-guard...");
CountingInputStream gcmInput = new CountingInputStream();
Cipher gcm = Cipher.getInstance("AES/GCM/NoPadding");
assertThrows(IllegalArgumentException.class,
() -> CipherTransformInputStreamBuilder.builder().withUpstream(gcmInput).withCipher(gcm)
.withIndependentBlocks().build());
assertThrows(IllegalArgumentException.class, () -> CipherTransformInputStreamBuilder.builder()
.withUpstream(gcmInput).withCipher(gcm).withIndependentBlocks().build());
assertEquals(0, gcmInput.reads);
CountingInputStream cbcInput = new CountingInputStream();
Cipher cbc = Cipher.getInstance("AES/CBC/PKCS5Padding");
assertThrows(IllegalArgumentException.class,
() -> CipherTransformInputStreamBuilder.builder().withUpstream(cbcInput).withCipher(cbc)
.withLeftZeroPadding(true).withIndependentBlocks().build());
assertThrows(IllegalArgumentException.class, () -> CipherTransformInputStreamBuilder.builder()
.withUpstream(cbcInput).withCipher(cbc).withLeftZeroPadding(true).withIndependentBlocks().build());
assertEquals(0, cbcInput.reads);
System.out.println("ok");
}

View File

@@ -44,8 +44,8 @@ class PairSeqTest {
assertEquals("pair 0 value must not be null",
assertThrows(IllegalArgumentException.class, () -> PairSeq.of("key", null)).getMessage());
assertEquals("pair 1 key must not be null",
assertThrows(IllegalArgumentException.class,
() -> PairSeq.of("first", "value", null, "other")).getMessage());
assertThrows(IllegalArgumentException.class, () -> PairSeq.of("first", "value", null, "other"))
.getMessage());
String[] source = { "key", "value" };
PairSeq sequence = PairSeq.of(source);
@@ -66,8 +66,7 @@ class PairSeqTest {
IOException partial = new IOException("partial");
FailingAppendable partialOutput = new FailingAppendable(2, partial);
IOException partialActual = assertThrows(IOException.class,
() -> PairSeq.of("a", "b").writeTo(partialOutput));
IOException partialActual = assertThrows(IOException.class, () -> PairSeq.of("a", "b").writeTo(partialOutput));
assertSame(partial, partialActual);
assertEquals("a=", partialOutput.output.toString());

View File

@@ -50,8 +50,7 @@ import zeroecho.sdk.util.BouncyCastleActivator;
* then proves that the reconstructed key remains operational.
*/
class KeyringAlgorithmCoverageTest {
private static final byte[] MESSAGE = "keyring-algorithm-matrix".getBytes(
java.nio.charset.StandardCharsets.UTF_8);
private static final byte[] MESSAGE = "keyring-algorithm-matrix".getBytes(java.nio.charset.StandardCharsets.UTF_8);
@TempDir
Path temporaryDirectory;
@@ -62,8 +61,7 @@ class KeyringAlgorithmCoverageTest {
@Test
void persistentImporterUniverseHasAcceptedCardinality() {
List<KeyringImportRegistry.PersistentMapping> mappings =
KeyringImportRegistry.mappings();
List<KeyringImportRegistry.PersistentMapping> mappings = KeyringImportRegistry.mappings();
assertEquals(19, count(mappings, KeyringStore.Kind.PUBLIC_KEY));
assertEquals(19, count(mappings, KeyringStore.Kind.PRIVATE_KEY));
assertEquals(6, count(mappings, KeyringStore.Kind.SECRET_KEY));
@@ -72,49 +70,39 @@ class KeyringAlgorithmCoverageTest {
@Test
void publicApiRequiresUnlockMaterialAndExposesNoPlaintextStorePath() {
List<Method> publicMethods = Arrays.stream(KeyringStore.class.getDeclaredMethods())
.filter(method -> Modifier.isPublic(method.getModifiers()))
.toList();
publicMethods.stream()
.filter(method -> "create".equals(method.getName())
|| "open".equals(method.getName()))
.filter(method -> Modifier.isPublic(method.getModifiers())).toList();
publicMethods.stream().filter(method -> "create".equals(method.getName()) || "open".equals(method.getName()))
.forEach(method -> {
assertTrue(Arrays.asList(method.getParameterTypes())
.contains(KeyringPassword.class));
assertTrue(Arrays.stream(method.getParameterTypes())
.noneMatch(String.class::equals));
assertTrue(Arrays.asList(method.getParameterTypes()).contains(KeyringPassword.class));
assertTrue(Arrays.stream(method.getParameterTypes()).noneMatch(String.class::equals));
});
assertTrue(publicMethods.stream().noneMatch(method -> switch (method.getName()) {
case "exportText", "importText", "save", "load" -> true;
default -> false;
}));
assertTrue(Arrays.stream(KeyringStore.class.getFields())
.noneMatch(field -> field.getType() == byte[].class));
assertTrue(Arrays.stream(KeyringStore.class.getFields()).noneMatch(field -> field.getType() == byte[].class));
}
@TestFactory
Stream<DynamicTest> asymmetricEncryptedStoreRoundTrips() {
List<String> algorithms = KeyringImportRegistry.mappings().stream()
.filter(mapping -> mapping.kind() == KeyringStore.Kind.PUBLIC_KEY)
.map(KeyringImportRegistry.PersistentMapping::algorithmId)
.toList();
return algorithms.stream().map(algorithmId -> DynamicTest.dynamicTest(
algorithmId + " PUBLIC/X.509 + PRIVATE/PKCS#8",
() -> roundTripAsymmetric(algorithmId)));
.map(KeyringImportRegistry.PersistentMapping::algorithmId).toList();
return algorithms.stream().map(algorithmId -> DynamicTest
.dynamicTest(algorithmId + " PUBLIC/X.509 + PRIVATE/PKCS#8", () -> roundTripAsymmetric(algorithmId)));
}
@TestFactory
Stream<DynamicTest> secretEncryptedStoreRoundTrips() {
return KeyringImportRegistry.mappings().stream()
.filter(mapping -> mapping.kind() == KeyringStore.Kind.SECRET_KEY)
.map(mapping -> DynamicTest.dynamicTest(secretDisplayName(mapping),
() -> roundTripSecret(mapping)));
.map(mapping -> DynamicTest.dynamicTest(secretDisplayName(mapping), () -> roundTripSecret(mapping)));
}
private void roundTripAsymmetric(String algorithmId) throws Exception {
KeyPair original = generatePair(algorithmId);
Path path = temporaryDirectory.resolve("asymmetric-" + safeName(algorithmId) + ".zek");
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(path, password)) {
try (KeyringPassword password = password(); KeyringStore store = KeyringStore.create(path, password)) {
store.putPublic("matrix", algorithmId, original.getPublic());
store.putPrivate("matrix", algorithmId, original.getPrivate());
}
@@ -123,8 +111,7 @@ class KeyringAlgorithmCoverageTest {
PrivateKey reconstructedPrivate = null;
byte[] originalPublic = null;
byte[] reconstructedPublicBytes = null;
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.open(path, password)) {
try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) {
KeyringStore.PublicWithId publicWithId = store.getPublicWithId("matrix");
KeyringStore.PrivateWithId privateWithId = store.getPrivateWithId("matrix");
assertEquals(algorithmId, publicWithId.algorithm());
@@ -134,8 +121,7 @@ class KeyringAlgorithmCoverageTest {
originalPublic = original.getPublic().getEncoded();
reconstructedPublicBytes = reconstructedPublic.getEncoded();
assertArrayEquals(originalPublic, reconstructedPublicBytes);
proveAsymmetricOperation(algorithmId, reconstructedPublic,
reconstructedPrivate);
proveAsymmetricOperation(algorithmId, reconstructedPublic, reconstructedPrivate);
} finally {
wipe(originalPublic);
wipe(reconstructedPublicBytes);
@@ -146,23 +132,19 @@ class KeyringAlgorithmCoverageTest {
}
}
private void roundTripSecret(KeyringImportRegistry.PersistentMapping mapping)
throws Exception {
private void roundTripSecret(KeyringImportRegistry.PersistentMapping mapping) throws Exception {
byte[] material = new byte[32];
Arrays.fill(material, secretFill(mapping));
String jcaName = secretJcaName(mapping);
SecretKey original = new SecretKeySpec(material, jcaName);
Path path = temporaryDirectory.resolve("secret-" + safeName(secretDisplayName(mapping))
+ ".zek");
Path path = temporaryDirectory.resolve("secret-" + safeName(secretDisplayName(mapping)) + ".zek");
SecretKey reconstructed = null;
byte[] reconstructedBytes = null;
try {
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(path, password)) {
try (KeyringPassword password = password(); KeyringStore store = KeyringStore.create(path, password)) {
store.putSecret("matrix", mapping.algorithmId(), original);
}
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.open(path, password)) {
try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) {
KeyringStore.SecretWithId withId = store.getSecretWithId("matrix");
assertEquals(mapping.algorithmId(), withId.algorithm());
reconstructed = withId.key();
@@ -179,11 +161,10 @@ class KeyringAlgorithmCoverageTest {
}
}
private static void proveAsymmetricOperation(String algorithmId,
PublicKey publicKey, PrivateKey privateKey) throws Exception {
private static void proveAsymmetricOperation(String algorithmId, PublicKey publicKey, PrivateKey privateKey)
throws Exception {
CryptoAlgorithm algorithm = CryptoAlgorithms.require(algorithmId);
if (algorithm.roles().contains(KeyUsage.SIGN)
&& algorithm.roles().contains(KeyUsage.VERIFY)) {
if (algorithm.roles().contains(KeyUsage.SIGN) && algorithm.roles().contains(KeyUsage.VERIFY)) {
proveSignature(algorithmId, publicKey, privateKey);
} else if (algorithm.roles().contains(KeyUsage.ENCAPSULATE)
&& algorithm.roles().contains(KeyUsage.DECAPSULATE)) {
@@ -191,21 +172,18 @@ class KeyringAlgorithmCoverageTest {
} else if (algorithm.roles().contains(KeyUsage.AGREEMENT)) {
proveAgreement(algorithmId, publicKey, privateKey);
} else {
assertTrue(algorithm.roles().contains(KeyUsage.ENCRYPT)
&& algorithm.roles().contains(KeyUsage.DECRYPT));
assertTrue(algorithm.roles().contains(KeyUsage.ENCRYPT) && algorithm.roles().contains(KeyUsage.DECRYPT));
proveEncryption(algorithmId, publicKey, privateKey);
}
}
private static void proveSignature(String algorithmId, PublicKey publicKey,
PrivateKey privateKey) throws Exception {
private static void proveSignature(String algorithmId, PublicKey publicKey, PrivateKey privateKey)
throws Exception {
ZeroEchoSession session = new ZeroEchoSession();
AtomicReference<byte[]> signatureHolder = new AtomicReference<>();
byte[] signature = null;
try (SignatureContext signer = session.createContext(
algorithmId, KeyUsage.SIGN, privateKey);
InputStream signed = new TailStrippingInputStream(
signer.wrap(new ByteArrayInputStream(MESSAGE)),
try (SignatureContext signer = session.createContext(algorithmId, KeyUsage.SIGN, privateKey);
InputStream signed = new TailStrippingInputStream(signer.wrap(new ByteArrayInputStream(MESSAGE)),
signer.tagLength(), 512) {
@Override
protected void processTail(byte[] tail) {
@@ -217,8 +195,7 @@ class KeyringAlgorithmCoverageTest {
}
try {
assertTrue(signature != null && signature.length > 0);
try (SignatureContext verifier = session.createContext(
algorithmId, KeyUsage.VERIFY, publicKey);
try (SignatureContext verifier = session.createContext(algorithmId, KeyUsage.VERIFY, publicKey);
InputStream verified = verificationStream(verifier, signature)) {
assertArrayEquals(MESSAGE, verified.readAllBytes());
}
@@ -227,23 +204,19 @@ class KeyringAlgorithmCoverageTest {
}
}
private static InputStream verificationStream(SignatureContext verifier,
byte[] signature) throws IOException {
private static InputStream verificationStream(SignatureContext verifier, byte[] signature) throws IOException {
verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch());
verifier.setExpectedTag(signature);
return verifier.wrap(new ByteArrayInputStream(MESSAGE));
}
private static void proveKem(String algorithmId, PublicKey publicKey,
PrivateKey privateKey) throws Exception {
private static void proveKem(String algorithmId, PublicKey publicKey, PrivateKey privateKey) throws Exception {
ZeroEchoSession session = new ZeroEchoSession();
byte[] encapsulated = null;
byte[] senderSecret = null;
byte[] recipientSecret = null;
try (KemContext sender = session.createContext(
algorithmId, KeyUsage.ENCAPSULATE, publicKey);
KemContext recipient = session.createContext(
algorithmId, KeyUsage.DECAPSULATE, privateKey)) {
try (KemContext sender = session.createContext(algorithmId, KeyUsage.ENCAPSULATE, publicKey);
KemContext recipient = session.createContext(algorithmId, KeyUsage.DECAPSULATE, privateKey)) {
KemContext.KemResult result = sender.encapsulate();
encapsulated = result.ciphertext();
senderSecret = result.sharedSecret();
@@ -256,16 +229,14 @@ class KeyringAlgorithmCoverageTest {
}
}
private static void proveAgreement(String algorithmId, PublicKey publicKey,
PrivateKey privateKey) throws Exception {
private static void proveAgreement(String algorithmId, PublicKey publicKey, PrivateKey privateKey)
throws Exception {
KeyPair peer = generatePair(algorithmId);
ZeroEchoSession session = new ZeroEchoSession();
byte[] firstSecret = null;
byte[] secondSecret = null;
try (AgreementContext first = session.createContext(
algorithmId, KeyUsage.AGREEMENT, privateKey);
AgreementContext second = session.createContext(
algorithmId, KeyUsage.AGREEMENT, peer.getPrivate())) {
try (AgreementContext first = session.createContext(algorithmId, KeyUsage.AGREEMENT, privateKey);
AgreementContext second = session.createContext(algorithmId, KeyUsage.AGREEMENT, peer.getPrivate())) {
first.setPeerPublic(peer.getPublic());
second.setPeerPublic(publicKey);
firstSecret = first.deriveSecret();
@@ -279,32 +250,26 @@ class KeyringAlgorithmCoverageTest {
}
}
private static void proveEncryption(String algorithmId, Key encryptionKey,
Key decryptionKey) throws Exception {
private static void proveEncryption(String algorithmId, Key encryptionKey, Key decryptionKey) throws Exception {
ZeroEchoSession session = new ZeroEchoSession();
conflux.CtxInterface operationContext =
conflux.Ctx.INSTANCE.getContext("keyring-matrix-" + algorithmId);
conflux.CtxInterface operationContext = conflux.Ctx.INSTANCE.getContext("keyring-matrix-" + algorithmId);
byte[] ciphertext = null;
byte[] plaintext = null;
try {
try (EncryptionContext encryption = session.createContext(
algorithmId, KeyUsage.ENCRYPT, encryptionKey)) {
try (EncryptionContext encryption = session.createContext(algorithmId, KeyUsage.ENCRYPT, encryptionKey)) {
if (encryption instanceof zeroecho.core.spi.ContextAware contextAware) {
contextAware.setContext(operationContext);
}
try (InputStream encrypted = encryption.attach(
new ByteArrayInputStream(MESSAGE))) {
try (InputStream encrypted = encryption.attach(new ByteArrayInputStream(MESSAGE))) {
ciphertext = encrypted.readAllBytes();
}
}
try (EncryptionContext decryption = session.createContext(
algorithmId, KeyUsage.DECRYPT, decryptionKey)) {
try (EncryptionContext decryption = session.createContext(algorithmId, KeyUsage.DECRYPT, decryptionKey)) {
if (decryption instanceof zeroecho.core.spi.ContextAware contextAware) {
contextAware.setContext(operationContext);
}
try (InputStream decrypted = decryption.attach(
new ByteArrayInputStream(ciphertext))) {
plaintext = decrypted.readAllBytes();
try (InputStream decrypted = decryption.attach(new ByteArrayInputStream(ciphertext))) {
plaintext = decrypted.readAllBytes();
}
}
assertArrayEquals(MESSAGE, plaintext);
@@ -314,8 +279,7 @@ class KeyringAlgorithmCoverageTest {
}
}
private static void proveSecretOperation(
KeyringImportRegistry.PersistentMapping mapping, SecretKey key)
private static void proveSecretOperation(KeyringImportRegistry.PersistentMapping mapping, SecretKey key)
throws Exception {
if ("HMAC".equals(mapping.algorithmId())) {
proveMac(key);
@@ -345,30 +309,22 @@ class KeyringAlgorithmCoverageTest {
private static KeyPair generatePair(String algorithmId) throws Exception {
CryptoAlgorithm algorithm = CryptoAlgorithms.require(algorithmId);
KeyOperationInfo generation = algorithm.keyOperations().stream()
.filter(info -> info.operation()
== KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE)
.filter(info -> info.defaultSpec() != null)
.findFirst()
.orElseThrow(() -> new AssertionError(
"No default key-pair generation mapping for " + algorithmId));
AsymmetricKeyPairGenerator generator =
algorithm.asymmetricKeyPairGenerator(generation.specType());
.filter(info -> info.operation() == KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE)
.filter(info -> info.defaultSpec() != null).findFirst()
.orElseThrow(() -> new AssertionError("No default key-pair generation mapping for " + algorithmId));
AsymmetricKeyPairGenerator generator = algorithm.asymmetricKeyPairGenerator(generation.specType());
return generator.generateKeyPair((AlgorithmKeySpec) generation.defaultSpec());
}
private static long count(List<KeyringImportRegistry.PersistentMapping> mappings,
KeyringStore.Kind kind) {
private static long count(List<KeyringImportRegistry.PersistentMapping> mappings, KeyringStore.Kind kind) {
return mappings.stream().filter(mapping -> mapping.kind() == kind).count();
}
private static String secretDisplayName(
KeyringImportRegistry.PersistentMapping mapping) {
return mapping.algorithmId() + "/" + mapping.hmacVariant().name()
+ " SECRET/RAW";
private static String secretDisplayName(KeyringImportRegistry.PersistentMapping mapping) {
return mapping.algorithmId() + "/" + mapping.hmacVariant().name() + " SECRET/RAW";
}
private static String secretJcaName(
KeyringImportRegistry.PersistentMapping mapping) {
private static String secretJcaName(KeyringImportRegistry.PersistentMapping mapping) {
return switch (mapping.algorithmId()) {
case "AES" -> "AES";
case "CHACHA20", "CHACHA20-POLY1305" -> "ChaCha20";
@@ -377,8 +333,7 @@ class KeyringAlgorithmCoverageTest {
};
}
private static byte secretFill(
KeyringImportRegistry.PersistentMapping mapping) {
private static byte secretFill(KeyringImportRegistry.PersistentMapping mapping) {
return (byte) (mapping.hmacVariant().code() + mapping.algorithmId().length() + 1);
}

View File

@@ -32,8 +32,8 @@ class KeyringAtomicPersistenceTest {
private static final char[] PASSWORD = { 'a', 't', 'o', 'm', 'i', 'c' };
private static final byte[] ENTRY_A = material((byte) 0x31);
private static final byte[] ENTRY_B = material((byte) 0x72);
private static final Set<PosixFilePermission> FILE_PERMISSIONS = Set.of(
PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE);
private static final Set<PosixFilePermission> FILE_PERMISSIONS = Set.of(PosixFilePermission.OWNER_READ,
PosixFilePermission.OWNER_WRITE);
@TempDir
Path temporaryDirectory;
@@ -41,20 +41,16 @@ class KeyringAtomicPersistenceTest {
@Test
void mainImagePreCommitFailuresPreserveAuthoritativeState() throws Exception {
start("mainImagePreCommitFailuresPreserveAuthoritativeState");
for (FailureStage stage : List.of(FailureStage.CREATE_TEMP,
FailureStage.WRITE_TEMP, FailureStage.FORCE_TEMP,
for (FailureStage stage : List.of(FailureStage.CREATE_TEMP, FailureStage.WRITE_TEMP, FailureStage.FORCE_TEMP,
FailureStage.ATOMIC_MOVE)) {
Path path = initialized("main-" + stage + ".zek");
byte[] before = Files.readAllBytes(path);
FailingFileOperations operations =
new FailingFileOperations(Target.MAIN_IMAGE, stage, false);
FailingFileOperations operations = new FailingFileOperations(Target.MAIN_IMAGE, stage, false);
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.open(path, password,
KeyringProtection.standard(), deterministicRandom(50),
operations)) {
KeyringStore store = KeyringStore.open(path, password, KeyringProtection.standard(),
deterministicRandom(50), operations)) {
long highWater = longField(store, "nonceHighWater");
KeyringException failure = assertThrows(KeyringException.class,
() -> put(store, "B", ENTRY_B));
KeyringException failure = assertThrows(KeyringException.class, () -> put(store, "B", ENTRY_B));
assertSafe(failure, KeyringException.Code.KEYRING_IO_FAILED);
assertTrue(store.contains("A"));
assertFalse(store.contains("B"));
@@ -76,16 +72,15 @@ class KeyringAtomicPersistenceTest {
void mainImageDirectoryForceFailurePoisonsUntilReopen() throws Exception {
start("mainImageDirectoryForceFailurePoisonsUntilReopen");
Path path = initialized("main-directory.zek");
FailingFileOperations operations = new FailingFileOperations(
Target.MAIN_IMAGE, FailureStage.FORCE_DIRECTORY, false);
FailingFileOperations operations = new FailingFileOperations(Target.MAIN_IMAGE, FailureStage.FORCE_DIRECTORY,
false);
KeyringStore store;
try (KeyringPassword password = password()) {
store = KeyringStore.open(path, password, KeyringProtection.standard(),
deterministicRandom(70), operations);
store = KeyringStore.open(path, password, KeyringProtection.standard(), deterministicRandom(70),
operations);
}
try {
KeyringException failure = assertThrows(KeyringException.class,
() -> put(store, "B", ENTRY_B));
KeyringException failure = assertThrows(KeyringException.class, () -> put(store, "B", ENTRY_B));
assertSafe(failure, KeyringException.Code.KEYRING_DURABILITY_UNCONFIRMED);
assertTrue(store.isDestroyed());
assertThrows(IllegalStateException.class, () -> store.contains("A"));
@@ -101,21 +96,17 @@ class KeyringAtomicPersistenceTest {
@Test
void sidecarPreCommitFailuresIssueNoUncommittedNonce() throws Exception {
start("sidecarPreCommitFailuresIssueNoUncommittedNonce");
for (FailureStage stage : List.of(FailureStage.CREATE_TEMP,
FailureStage.WRITE_TEMP, FailureStage.FORCE_TEMP,
for (FailureStage stage : List.of(FailureStage.CREATE_TEMP, FailureStage.WRITE_TEMP, FailureStage.FORCE_TEMP,
FailureStage.ATOMIC_MOVE)) {
Path path = initialized("sidecar-" + stage + ".zek");
byte[] mainBefore = Files.readAllBytes(path);
byte[] sidecarBefore = Files.readAllBytes(sidecar(path));
FailingFileOperations operations =
new FailingFileOperations(Target.NONCE_RESERVATION, stage, false);
FailingFileOperations operations = new FailingFileOperations(Target.NONCE_RESERVATION, stage, false);
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.open(path, password,
KeyringProtection.standard(), deterministicRandom(90),
operations)) {
KeyringStore store = KeyringStore.open(path, password, KeyringProtection.standard(),
deterministicRandom(90), operations)) {
long highWater = longField(store, "nonceHighWater");
KeyringException failure = assertThrows(KeyringException.class,
() -> put(store, "B", ENTRY_B));
KeyringException failure = assertThrows(KeyringException.class, () -> put(store, "B", ENTRY_B));
assertSafe(failure, KeyringException.Code.KEYRING_IO_FAILED);
assertEquals(highWater, longField(store, "nonceHighWater"));
assertFalse(store.contains("B"));
@@ -138,17 +129,16 @@ class KeyringAtomicPersistenceTest {
start("sidecarDirectoryForceFailureIssuesNothingUntilReopen");
Path path = initialized("sidecar-directory.zek");
long initialHighWater;
FailingFileOperations operations = new FailingFileOperations(
Target.NONCE_RESERVATION, FailureStage.FORCE_DIRECTORY, false);
FailingFileOperations operations = new FailingFileOperations(Target.NONCE_RESERVATION,
FailureStage.FORCE_DIRECTORY, false);
KeyringStore store;
try (KeyringPassword password = password()) {
store = KeyringStore.open(path, password, KeyringProtection.standard(),
deterministicRandom(110), operations);
store = KeyringStore.open(path, password, KeyringProtection.standard(), deterministicRandom(110),
operations);
}
try {
initialHighWater = longField(store, "nonceHighWater");
KeyringException failure = assertThrows(KeyringException.class,
() -> put(store, "B", ENTRY_B));
KeyringException failure = assertThrows(KeyringException.class, () -> put(store, "B", ENTRY_B));
assertSafe(failure, KeyringException.Code.KEYRING_DURABILITY_UNCONFIRMED);
assertFalse(store.isDestroyed());
assertThrows(IllegalStateException.class, () -> store.contains("A"));
@@ -157,8 +147,8 @@ class KeyringAtomicPersistenceTest {
store.close();
}
try (KeyringPassword password = password();
KeyringStore reopened = KeyringStore.open(path, password,
KeyringProtection.standard(), deterministicRandom(130))) {
KeyringStore reopened = KeyringStore.open(path, password, KeyringProtection.standard(),
deterministicRandom(130))) {
assertTrue(reopened.contains("A"));
assertFalse(reopened.contains("B"));
assertEquals(initialHighWater + 1, longField(reopened, "nonceHighWater"));
@@ -179,18 +169,14 @@ class KeyringAtomicPersistenceTest {
private void assertCleanupFailure(Target target, String file) throws Exception {
Path path = initialized(file);
FailingFileOperations operations = new FailingFileOperations(
target, FailureStage.WRITE_TEMP, true);
FailingFileOperations operations = new FailingFileOperations(target, FailureStage.WRITE_TEMP, true);
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.open(path, password,
KeyringProtection.standard(), deterministicRandom(150),
operations)) {
KeyringException failure = assertThrows(KeyringException.class,
() -> put(store, "B", ENTRY_B));
KeyringStore store = KeyringStore.open(path, password, KeyringProtection.standard(),
deterministicRandom(150), operations)) {
KeyringException failure = assertThrows(KeyringException.class, () -> put(store, "B", ENTRY_B));
assertSafe(failure, KeyringException.Code.KEYRING_IO_FAILED);
assertEquals(1, failure.getSuppressed().length);
assertSafe((KeyringException) failure.getSuppressed()[0],
KeyringException.Code.KEYRING_IO_FAILED);
assertSafe((KeyringException) failure.getSuppressed()[0], KeyringException.Code.KEYRING_IO_FAILED);
assertTrue(store.contains("A"));
assertFalse(store.contains("B"));
Path residual = operations.firstTemporary();
@@ -207,21 +193,19 @@ class KeyringAtomicPersistenceTest {
private Path initialized(String file) throws Exception {
Path path = temporaryDirectory.resolve(file);
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(path, password,
KeyringProtection.standard(), deterministicRandom(1))) {
KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(),
deterministicRandom(1))) {
put(store, "A", ENTRY_A);
}
return path;
}
private static void put(KeyringStore store, String alias, byte[] material)
throws Exception {
private static void put(KeyringStore store, String alias, byte[] material) throws Exception {
store.putSecret(alias, "AES", new SecretKeySpec(material, "AES"));
}
private static void assertReopened(Path path, boolean hasB) throws Exception {
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.open(path, password)) {
try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) {
assertTrue(store.contains("A"));
assertEquals(hasB, store.contains("B"));
assertSecretEquals(ENTRY_A, store.getSecret("A"));
@@ -242,21 +226,18 @@ class KeyringAtomicPersistenceTest {
private static void assertAlreadyOpen(Path path) throws Exception {
try (KeyringPassword password = password()) {
KeyringException failure = assertThrows(KeyringException.class,
() -> KeyringStore.open(path, password));
KeyringException failure = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password));
assertSafe(failure, KeyringException.Code.KEYRING_ALREADY_OPEN);
}
}
private static void assertSafe(KeyringException failure,
KeyringException.Code expected) {
private static void assertSafe(KeyringException failure, KeyringException.Code expected) {
assertEquals(expected, failure.code());
assertEquals(expected.name(), failure.getMessage());
assertNull(failure.getCause());
}
private static void assertSentinelAbsent(Path directory, byte[] sentinel)
throws IOException {
private static void assertSentinelAbsent(Path directory, byte[] sentinel) throws IOException {
try (java.util.stream.Stream<Path> paths = Files.list(directory)) {
for (Path current : paths.toList()) {
if (Files.isRegularFile(current)) {
@@ -272,8 +253,7 @@ class KeyringAtomicPersistenceTest {
}
private static int indexOf(byte[] haystack, byte[] needle) {
outer:
for (int index = 0; index <= haystack.length - needle.length; index++) {
outer: for (int index = 0; index <= haystack.length - needle.length; index++) {
for (int offset = 0; offset < needle.length; offset++) {
if (haystack[index + offset] != needle[offset]) {
continue outer;
@@ -329,12 +309,7 @@ class KeyringAtomicPersistenceTest {
}
private enum FailureStage {
CREATE_TEMP,
WRITE_TEMP,
FORCE_TEMP,
ATOMIC_MOVE,
FORCE_DIRECTORY,
DELETE_TEMP
CREATE_TEMP, WRITE_TEMP, FORCE_TEMP, ATOMIC_MOVE, FORCE_DIRECTORY, DELETE_TEMP
}
private static final class FailingFileOperations implements KeyringFileOperations {
@@ -345,20 +320,17 @@ class KeyringAtomicPersistenceTest {
private boolean primaryFailed;
private boolean cleanupFailed;
private FailingFileOperations(Target target, FailureStage primaryStage,
boolean failCleanup) {
private FailingFileOperations(Target target, FailureStage primaryStage, boolean failCleanup) {
this.target = target;
this.primaryStage = primaryStage;
this.failCleanup = failCleanup;
}
@Override
public Path createTemporary(Target actualTarget, Path parent, String prefix,
String suffix, FileAttribute<Set<PosixFilePermission>> permissions)
throws IOException {
public Path createTemporary(Target actualTarget, Path parent, String prefix, String suffix,
FileAttribute<Set<PosixFilePermission>> permissions) throws IOException {
failBefore(actualTarget, FailureStage.CREATE_TEMP);
Path temporary = NIO.createTemporary(actualTarget, parent, prefix, suffix,
permissions);
Path temporary = NIO.createTemporary(actualTarget, parent, prefix, suffix, permissions);
if (actualTarget == target) {
temporaryPaths.add(temporary);
}
@@ -366,22 +338,19 @@ class KeyringAtomicPersistenceTest {
}
@Override
public void writeTemporary(Target actualTarget, Path temporary, byte[] image)
throws IOException {
public void writeTemporary(Target actualTarget, Path temporary, byte[] image) throws IOException {
NIO.writeTemporary(actualTarget, temporary, image);
failAfter(actualTarget, FailureStage.WRITE_TEMP);
}
@Override
public void forceTemporary(Target actualTarget, Path temporary)
throws IOException {
public void forceTemporary(Target actualTarget, Path temporary) throws IOException {
NIO.forceTemporary(actualTarget, temporary);
failAfter(actualTarget, FailureStage.FORCE_TEMP);
}
@Override
public void atomicReplace(Target actualTarget, Path temporary, Path destination)
throws IOException {
public void atomicReplace(Target actualTarget, Path temporary, Path destination) throws IOException {
failBefore(actualTarget, FailureStage.ATOMIC_MOVE);
NIO.atomicReplace(actualTarget, temporary, destination);
}
@@ -393,8 +362,7 @@ class KeyringAtomicPersistenceTest {
}
@Override
public void deleteTemporary(Target actualTarget, Path temporary)
throws IOException {
public void deleteTemporary(Target actualTarget, Path temporary) throws IOException {
if (actualTarget == target && failCleanup && !cleanupFailed) {
cleanupFailed = true;
throw new IOException("DELETE_TEMP_SENTINEL");
@@ -402,16 +370,14 @@ class KeyringAtomicPersistenceTest {
NIO.deleteTemporary(actualTarget, temporary);
}
private void failBefore(Target actualTarget, FailureStage stage)
throws IOException {
private void failBefore(Target actualTarget, FailureStage stage) throws IOException {
if (actualTarget == target && primaryStage == stage && !primaryFailed) {
primaryFailed = true;
throw new IOException(stage.name() + "_SENTINEL");
}
}
private void failAfter(Target actualTarget, FailureStage stage)
throws IOException {
private void failAfter(Target actualTarget, FailureStage stage) throws IOException {
failBefore(actualTarget, stage);
}

View File

@@ -56,62 +56,57 @@ class KeyringCryptographicFormatTest {
Path source = createEmpty("master-source.zek", 1);
byte[] image = Files.readAllBytes(source);
try {
assertUniformUnlockFailure(source,
new char[] { 'w', 'r', 'o', 'n', 'g' });
assertUniformUnlockFailure(source, new char[] { 'w', 'r', 'o', 'n', 'g' });
assertUniformUnlockFailure(copyWithMutation(source, image, "wrapped-cipher",
value -> value[WRAPPED_OFFSET] ^= 1), PASSWORD);
assertUniformUnlockFailure(copyWithMutation(source, image, "wrapped-tag",
value -> value[WRAPPED_OFFSET + 47] ^= 1), PASSWORD);
assertUniformUnlockFailure(copyWithMutation(source, image, "salt",
value -> value[SALT_OFFSET] ^= 1), PASSWORD);
assertUniformUnlockFailure(
copyWithMutation(source, image, "wrapped-cipher", value -> value[WRAPPED_OFFSET] ^= 1), PASSWORD);
assertUniformUnlockFailure(
copyWithMutation(source, image, "wrapped-tag", value -> value[WRAPPED_OFFSET + 47] ^= 1), PASSWORD);
assertUniformUnlockFailure(copyWithMutation(source, image, "salt", value -> value[SALT_OFFSET] ^= 1),
PASSWORD);
assertUniformUnlockFailure(copyWithMutation(source, image, "iterations-auth",
value -> putInt(value, ITERATIONS_OFFSET, 600_001)), PASSWORD);
assertUniformUnlockFailure(copyWithMutation(source, image, "wrap-nonce-auth",
value -> value[WRAP_NONCE_OFFSET + 1] ^= 1), PASSWORD);
assertUniformUnlockFailure(
copyWithMutation(source, image, "wrap-nonce-auth", value -> value[WRAP_NONCE_OFFSET + 1] ^= 1),
PASSWORD);
assertRejected(copyWithMutation(source, image, "iterations-low",
value -> putInt(value, ITERATIONS_OFFSET, 599_999)),
assertRejected(
copyWithMutation(source, image, "iterations-low",
value -> putInt(value, ITERATIONS_OFFSET, 599_999)),
KeyringException.Code.KEYRING_LIMIT_EXCEEDED);
assertRejected(copyWithMutation(source, image, "iterations-operational",
value -> putInt(value, ITERATIONS_OFFSET, 1_000_001)),
assertRejected(
copyWithMutation(source, image, "iterations-operational",
value -> putInt(value, ITERATIONS_OFFSET, 1_000_001)),
KeyringException.Code.KEYRING_LIMIT_EXCEEDED);
assertRejected(copyWithMutation(source, image, "iterations-absolute",
value -> putInt(value, ITERATIONS_OFFSET, 10_000_001)),
assertRejected(
copyWithMutation(source, image, "iterations-absolute",
value -> putInt(value, ITERATIONS_OFFSET, 10_000_001)),
KeyringException.Code.KEYRING_LIMIT_EXCEEDED);
assertRejected(copyWithMutation(source, image, "unknown-kdf",
value -> value[KDF_OFFSET] = 99),
assertRejected(copyWithMutation(source, image, "unknown-kdf", value -> value[KDF_OFFSET] = 99),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyWithMutation(source, image, "unknown-aead",
value -> value[AEAD_OFFSET] = 99),
assertRejected(copyWithMutation(source, image, "unknown-aead", value -> value[AEAD_OFFSET] = 99),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyWithMutation(source, image, "kek-length",
value -> putInt(value, KEK_LENGTH_OFFSET, 31)),
assertRejected(copyWithMutation(source, image, "kek-length", value -> putInt(value, KEK_LENGTH_OFFSET, 31)),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyWithMutation(source, image, "wrap-domain",
value -> value[WRAP_NONCE_OFFSET] = 2),
assertRejected(copyWithMutation(source, image, "wrap-domain", value -> value[WRAP_NONCE_OFFSET] = 2),
KeyringException.Code.KEYRING_FORMAT_INVALID);
for (int wrappedLength : new int[] { -1, 47, 49 }) {
assertRejected(copyWithMutation(source, image,
"wrapped-length-" + wrappedLength,
value -> putInt(value, WRAPPED_LENGTH_OFFSET, wrappedLength)),
wrappedLength == 47
? KeyringException.Code.KEYRING_FORMAT_INVALID
assertRejected(
copyWithMutation(source, image, "wrapped-length-" + wrappedLength,
value -> putInt(value, WRAPPED_LENGTH_OFFSET, wrappedLength)),
wrappedLength == 47 ? KeyringException.Code.KEYRING_FORMAT_INVALID
: KeyringException.Code.KEYRING_LIMIT_EXCEEDED);
}
int[] boundaries = { 1, VERSION_OFFSET, STORE_ID_OFFSET, KDF_OFFSET,
ITERATIONS_OFFSET, SALT_OFFSET, KEK_LENGTH_OFFSET, AEAD_OFFSET,
WRAP_NONCE_OFFSET, WRAPPED_LENGTH_OFFSET, WRAPPED_OFFSET,
int[] boundaries = { 1, VERSION_OFFSET, STORE_ID_OFFSET, KDF_OFFSET, ITERATIONS_OFFSET, SALT_OFFSET,
KEK_LENGTH_OFFSET, AEAD_OFFSET, WRAP_NONCE_OFFSET, WRAPPED_LENGTH_OFFSET, WRAPPED_OFFSET,
WRAPPED_OFFSET + 47, ENTRY_COUNT_OFFSET };
for (int boundary : boundaries) {
Path truncated = copyImage(source, Arrays.copyOf(image, boundary),
"truncated-" + boundary);
Path truncated = copyImage(source, Arrays.copyOf(image, boundary), "truncated-" + boundary);
assertSafeRejected(truncated);
}
byte[] trailing = Arrays.copyOf(image, image.length + 1);
assertRejected(copyImage(source, trailing, "trailing"),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyImage(source, trailing, "trailing"), KeyringException.Code.KEYRING_FORMAT_INVALID);
} finally {
wipe(image);
}
@@ -125,44 +120,47 @@ class KeyringCryptographicFormatTest {
byte[] image = Files.readAllBytes(source);
ImageLayout layout = layout(image);
try {
assertRejected(copyWithMutation(source, image, "entry-cipher",
value -> value[layout.entries.get(0).ciphertextOffset] ^= 1),
assertRejected(
copyWithMutation(source, image, "entry-cipher",
value -> value[layout.entries.get(0).ciphertextOffset] ^= 1),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyWithMutation(source, image, "entry-tag",
value -> value[layout.entries.get(0).endOffset - 1] ^= 1),
assertRejected(
copyWithMutation(source, image, "entry-tag",
value -> value[layout.entries.get(0).endOffset - 1] ^= 1),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyWithMutation(source, image, "entry-nonce",
value -> value[layout.entries.get(0).nonceOffset] ^= 1),
assertRejected(
copyWithMutation(source, image, "entry-nonce",
value -> value[layout.entries.get(0).nonceOffset] ^= 1),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyWithMutation(source, image, "entry-id",
value -> value[layout.entries.get(0).entryIdOffset] ^= 1),
assertRejected(
copyWithMutation(source, image, "entry-id",
value -> value[layout.entries.get(0).entryIdOffset] ^= 1),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyWithMutation(source, image, "manifest-nonce",
value -> value[layout.manifestNonceOffset] ^= 1),
assertRejected(
copyWithMutation(source, image, "manifest-nonce", value -> value[layout.manifestNonceOffset] ^= 1),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyWithMutation(source, image, "manifest-cipher",
value -> value[layout.manifestCipherOffset] ^= 1),
assertRejected(
copyWithMutation(source, image, "manifest-cipher",
value -> value[layout.manifestCipherOffset] ^= 1),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyWithMutation(source, image, "manifest-tag",
value -> value[image.length - 1] ^= 1),
assertRejected(copyWithMutation(source, image, "manifest-tag", value -> value[image.length - 1] ^= 1),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyImage(source, reorderEntries(image, layout),
"entry-reorder"), KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyImage(source, duplicateFirstEntry(image, layout),
"entry-duplicate"), KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyImage(source, deleteFirstEntry(image, layout),
"entry-delete"), KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyImage(source, insertUnauthenticatedEntry(image, layout),
"entry-insert"), KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyImage(source, reorderEntries(image, layout), "entry-reorder"),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyImage(source, duplicateFirstEntry(image, layout), "entry-duplicate"),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyImage(source, deleteFirstEntry(image, layout), "entry-delete"),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyImage(source, insertUnauthenticatedEntry(image, layout), "entry-insert"),
KeyringException.Code.KEYRING_FORMAT_INVALID);
Path other = createTwoEntries("outer-other.zek", 107);
byte[] otherImage = Files.readAllBytes(other);
try {
ImageLayout otherLayout = layout(otherImage);
byte[] copied = replaceEntry(otherImage, otherLayout.entries.get(0),
slice(image, layout.entries.get(0).entryIdOffset,
layout.entries.get(0).endOffset));
slice(image, layout.entries.get(0).entryIdOffset, layout.entries.get(0).endOffset));
assertRejected(copyImage(other, copied, "entry-cross-store"),
KeyringException.Code.KEYRING_FORMAT_INVALID);
wipe(copied);
@@ -192,19 +190,15 @@ class KeyringCryptographicFormatTest {
value -> value[descriptor(value, 0).nonceOffset] ^= 1);
assertManifestMutationRejected(fixture.path, image, masterKey, "cipher-length",
value -> putInt(value, descriptor(value, 0).ciphertextLengthOffset,
getInt(value,
descriptor(value, 0).ciphertextLengthOffset) + 1));
getInt(value, descriptor(value, 0).ciphertextLengthOffset) + 1));
assertManifestMutationRejected(fixture.path, image, masterKey, "cipher-digest",
value -> value[descriptor(value, 0).digestOffset] ^= 1);
assertManifestMutationRejected(fixture.path, image, masterKey, "duplicate-id",
value -> copyField(value, descriptor(value, 0).entryIdOffset,
descriptor(value, 1).entryIdOffset, KeyringStore.UUID_BYTES));
assertManifestMutationRejected(fixture.path, image, masterKey, "duplicate-nonce",
value -> copyField(value, descriptor(value, 0).nonceOffset,
descriptor(value, 1).nonceOffset, KeyringStore.NONCE_BYTES));
assertManifestMutationRejected(fixture.path, image, masterKey, "duplicate-id", value -> copyField(value,
descriptor(value, 0).entryIdOffset, descriptor(value, 1).entryIdOffset, KeyringStore.UUID_BYTES));
assertManifestMutationRejected(fixture.path, image, masterKey, "duplicate-nonce", value -> copyField(value,
descriptor(value, 0).nonceOffset, descriptor(value, 1).nonceOffset, KeyringStore.NONCE_BYTES));
assertManifestMutationRejected(fixture.path, image, masterKey, "duplicate-alias",
value -> copyField(value, descriptor(value, 0).aliasOffset,
descriptor(value, 1).aliasOffset,
value -> copyField(value, descriptor(value, 0).aliasOffset, descriptor(value, 1).aliasOffset,
descriptor(value, 0).aliasLength));
assertManifestMutationRejected(fixture.path, image, masterKey, "unknown-algorithm",
value -> overwriteAscii(value, descriptor(value, 0).algorithmOffset, "BAD"),
@@ -222,33 +216,29 @@ class KeyringCryptographicFormatTest {
value -> putInt(value, 16, 1));
assertManifestMutationRejected(fixture.path, image, masterKey, "manifest-reorder",
KeyringCryptographicFormatTest::swapManifestDescriptors);
assertManifestReplacementRejected(fixture.path, image, masterKey,
"manifest-truncated", value -> Arrays.copyOf(value, value.length - 1));
assertManifestReplacementRejected(fixture.path, image, masterKey,
"manifest-trailing", value -> Arrays.copyOf(value, value.length + 1));
assertManifestReplacementRejected(fixture.path, image, masterKey, "manifest-truncated",
value -> Arrays.copyOf(value, value.length - 1));
assertManifestReplacementRejected(fixture.path, image, masterKey, "manifest-trailing",
value -> Arrays.copyOf(value, value.length + 1));
assertEntryMutationRejected(fixture.path, image, masterKey, "entry-cipher-auth",
value -> value[0] ^= 1, false);
assertEntryMutationRejected(fixture.path, image, masterKey, "entry-cipher-auth", value -> value[0] ^= 1,
false);
assertEntryMutationRejected(fixture.path, image, masterKey, "entry-tag-auth",
value -> value[value.length - 1] ^= 1, false);
assertEntryMutationRejected(fixture.path, image, masterKey, "entry-version",
value -> putInt(value, 0, 2), true);
assertEntryMutationRejected(fixture.path, image, masterKey, "entry-version", value -> putInt(value, 0, 2),
true);
assertEntryMutationRejected(fixture.path, image, masterKey, "alias-substitution",
value -> overwriteAscii(value, Integer.BYTES * 2, "xxx"), true);
assertEntryMutationRejected(fixture.path, image, masterKey,
"algorithm-substitution",
assertEntryMutationRejected(fixture.path, image, masterKey, "algorithm-substitution",
value -> overwriteAscii(value, Integer.BYTES * 3 + 3, "BAD"), true);
assertEntryMutationRejected(fixture.path, image, masterKey, "kind-substitution",
value -> value[Integer.BYTES * 4 + 2] = 1, true);
assertEntryMutationRejected(fixture.path, image, masterKey,
"encoding-substitution",
assertEntryMutationRejected(fixture.path, image, masterKey, "encoding-substitution",
value -> value[Integer.BYTES * 4 + 3] = 1, true);
assertEntryMutationRejected(fixture.path, image, masterKey, "hmac-substitution",
value -> value[Integer.BYTES * 4 + 4] = 1, true);
assertCoherentIdentityMutationRejected(fixture.path, image, masterKey,
"coherent-entry-id", true);
assertCoherentIdentityMutationRejected(fixture.path, image, masterKey,
"coherent-entry-nonce", false);
assertCoherentIdentityMutationRejected(fixture.path, image, masterKey, "coherent-entry-id", true);
assertCoherentIdentityMutationRejected(fixture.path, image, masterKey, "coherent-entry-nonce", false);
} finally {
wipe(image);
wipe(masterKey);
@@ -259,35 +249,24 @@ class KeyringCryptographicFormatTest {
@Test
void decodedEntryAndManifestSchemasRejectTypeConfusionAndBounds() throws Exception {
start("decodedEntryAndManifestSchemasRejectTypeConfusionAndBounds");
byte[] validEntry = entryPlaintext(1, "a", "AES", 3, 3, 0,
new byte[32], false);
byte[] validEntry = entryPlaintext(1, "a", "AES", 3, 3, 0, new byte[32], false);
try {
invokeDecodeEntry(validEntry);
assertDecodeEntryRejected(entryPlaintext(2, "a", "AES", 3, 3, 0,
new byte[32], false));
assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 99, 3, 0,
new byte[32], false));
assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 3, 99, 0,
new byte[32], false));
assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 3, 3, 99,
new byte[32], false));
assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 3, 3, 0,
new byte[32], true));
assertDecodeEntryRejected(entryPlaintext(2, "a", "AES", 3, 3, 0, new byte[32], false));
assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 99, 3, 0, new byte[32], false));
assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 3, 99, 0, new byte[32], false));
assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 3, 3, 99, new byte[32], false));
assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 3, 3, 0, new byte[32], true));
assertDecodeEntryRejected(lengthOnlyEntry(-1, "AES"));
assertDecodeEntryRejected(lengthOnlyEntry(KeyringStore.MAX_ALIAS_BYTES + 1,
"AES"));
assertDecodeEntryRejected(lengthOnlyEntry(KeyringStore.MAX_ALIAS_BYTES + 1, "AES"));
assertDecodeEntryRejected(metadataLengthEntry(-1));
assertDecodeEntryRejected(metadataLengthEntry(
KeyringStore.MAX_METADATA_BYTES + 1));
assertDecodeEntryRejected(metadataLengthEntry(KeyringStore.MAX_METADATA_BYTES + 1));
assertDecodeEntryRejected(encodedLengthEntry(-1));
assertDecodeEntryRejected(encodedLengthEntry(
KeyringStore.MAX_ENTRY_CIPHERTEXT_BYTES));
assertDecodeEntryRejected(encodedLengthEntry(KeyringStore.MAX_ENTRY_CIPHERTEXT_BYTES));
byte[] maximumAlias = entryPlaintext(1,
"a".repeat(KeyringStore.MAX_ALIAS_BYTES), "AES", 3, 3, 0,
byte[] maximumAlias = entryPlaintext(1, "a".repeat(KeyringStore.MAX_ALIAS_BYTES), "AES", 3, 3, 0,
new byte[1], false);
byte[] maximumMetadata = entryPlaintext(1, "a",
"A".repeat(KeyringStore.MAX_METADATA_BYTES), 3, 3, 0,
byte[] maximumMetadata = entryPlaintext(1, "a", "A".repeat(KeyringStore.MAX_METADATA_BYTES), 3, 3, 0,
new byte[1], false);
try {
invokeDecodeEntry(maximumAlias);
@@ -320,26 +299,26 @@ class KeyringCryptographicFormatTest {
Path source = createEmpty("bounds-source.zek", 31);
byte[] image = Files.readAllBytes(source);
try {
assertRejected(copyWithMutation(source, image, "negative-count",
value -> putInt(value, ENTRY_COUNT_OFFSET, -1)),
assertRejected(
copyWithMutation(source, image, "negative-count", value -> putInt(value, ENTRY_COUNT_OFFSET, -1)),
KeyringException.Code.KEYRING_LIMIT_EXCEEDED);
assertRejected(copyWithMutation(source, image, "oversized-count",
value -> putInt(value, ENTRY_COUNT_OFFSET,
KeyringStore.MAX_ENTRY_COUNT + 1)),
assertRejected(
copyWithMutation(source, image, "oversized-count",
value -> putInt(value, ENTRY_COUNT_OFFSET, KeyringStore.MAX_ENTRY_COUNT + 1)),
KeyringException.Code.KEYRING_LIMIT_EXCEEDED);
assertRejected(copyWithMutation(source, image, "old-main-version",
value -> putInt(value, VERSION_OFFSET, 1)),
assertRejected(
copyWithMutation(source, image, "old-main-version", value -> putInt(value, VERSION_OFFSET, 1)),
KeyringException.Code.KEYRING_FORMAT_INVALID);
assertRejected(copyWithMutation(source, image, "draft-main-version",
value -> putInt(value, VERSION_OFFSET, 3)),
assertRejected(
copyWithMutation(source, image, "draft-main-version", value -> putInt(value, VERSION_OFFSET, 3)),
KeyringException.Code.KEYRING_FORMAT_INVALID);
} finally {
wipe(image);
}
Path oversized = temporaryDirectory.resolve("oversized.zek");
try (java.nio.channels.FileChannel channel = java.nio.channels.FileChannel.open(
oversized, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
try (java.nio.channels.FileChannel channel = java.nio.channels.FileChannel.open(oversized,
StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
channel.position(KeyringStore.MAX_FILE_BYTES);
channel.write(ByteBuffer.wrap(new byte[] { 0 }));
}
@@ -351,8 +330,7 @@ class KeyringCryptographicFormatTest {
}
Path plaintext = temporaryDirectory.resolve("plaintext-v1.zek");
Files.writeString(plaintext, "# KeyringStore v1\njava.io.File\nHmacSHA1\n",
StandardCharsets.UTF_8);
Files.writeString(plaintext, "# KeyringStore v1\njava.io.File\nHmacSHA1\n", StandardCharsets.UTF_8);
ownerOnly(plaintext);
assertRejected(plaintext, KeyringException.Code.KEYRING_FORMAT_INVALID);
@@ -363,10 +341,9 @@ class KeyringCryptographicFormatTest {
root.addHandler(handler);
try {
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(protectedStore, password,
KeyringProtection.standard(), deterministicRandom(61))) {
store.putSecret("sentinel", "AES",
new SecretKeySpec(Arrays.copyOf(sentinel, 32), "AES"));
KeyringStore store = KeyringStore.create(protectedStore, password, KeyringProtection.standard(),
deterministicRandom(61))) {
store.putSecret("sentinel", "AES", new SecretKeySpec(Arrays.copyOf(sentinel, 32), "AES"));
}
assertAbsent(Files.readAllBytes(protectedStore), sentinel);
assertAbsent(Files.readAllBytes(sidecar(protectedStore)), sentinel);
@@ -397,8 +374,8 @@ class KeyringCryptographicFormatTest {
private Path createEmpty(String name, int seed) throws Exception {
Path path = temporaryDirectory.resolve(name);
try (KeyringPassword password = password();
KeyringStore ignored = KeyringStore.create(path, password,
KeyringProtection.standard(), deterministicRandom(seed))) {
KeyringStore ignored = KeyringStore.create(path, password, KeyringProtection.standard(),
deterministicRandom(seed))) {
// Current empty image and reservation sidecar are durable.
}
return path;
@@ -418,8 +395,8 @@ class KeyringCryptographicFormatTest {
Arrays.fill(first, (byte) 0x31);
Arrays.fill(second, (byte) 0x42);
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(path, password,
KeyringProtection.standard(), deterministicRandom(seed))) {
KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(),
deterministicRandom(seed))) {
store.putSecret("one", "AES", new SecretKeySpec(first, "AES"));
store.putSecret("two", "AES", new SecretKeySpec(second, "AES"));
masterKey = bytesField(store, "masterKey").clone();
@@ -430,29 +407,28 @@ class KeyringCryptographicFormatTest {
return new StoreFixture(path, masterKey);
}
private void assertManifestMutationRejected(Path source, byte[] image, byte[] masterKey,
String name, Mutation mutation) throws Exception {
private void assertManifestMutationRejected(Path source, byte[] image, byte[] masterKey, String name,
Mutation mutation) throws Exception {
assertManifestMutationRejected(source, image, masterKey, name, mutation,
KeyringException.Code.KEYRING_FORMAT_INVALID);
}
private void assertManifestMutationRejected(Path source, byte[] image, byte[] masterKey,
String name, Mutation mutation, KeyringException.Code code) throws Exception {
private void assertManifestMutationRejected(Path source, byte[] image, byte[] masterKey, String name,
Mutation mutation, KeyringException.Code code) throws Exception {
assertManifestReplacementRejected(source, image, masterKey, name, value -> {
mutation.apply(value);
return value;
}, code);
}
private void assertManifestReplacementRejected(Path source, byte[] image,
byte[] masterKey, String name, Replacement mutation) throws Exception {
private void assertManifestReplacementRejected(Path source, byte[] image, byte[] masterKey, String name,
Replacement mutation) throws Exception {
assertManifestReplacementRejected(source, image, masterKey, name, mutation,
KeyringException.Code.KEYRING_FORMAT_INVALID);
}
private void assertManifestReplacementRejected(Path source, byte[] image,
byte[] masterKey, String name, Replacement mutation,
KeyringException.Code code) throws Exception {
private void assertManifestReplacementRejected(Path source, byte[] image, byte[] masterKey, String name,
Replacement mutation, KeyringException.Code code) throws Exception {
byte[] plaintext = decryptManifest(image, masterKey);
byte[] replacement = null;
byte[] rewritten = null;
@@ -469,8 +445,8 @@ class KeyringCryptographicFormatTest {
}
}
private void assertEntryMutationRejected(Path source, byte[] image, byte[] masterKey,
String name, Mutation mutation, boolean plaintextMutation) throws Exception {
private void assertEntryMutationRejected(Path source, byte[] image, byte[] masterKey, String name,
Mutation mutation, boolean plaintextMutation) throws Exception {
ImageLayout imageLayout = layout(image);
WireEntry entry = imageLayout.entries.get(0);
byte[] ciphertext = slice(image, entry.ciphertextOffset, entry.endOffset);
@@ -478,8 +454,7 @@ class KeyringCryptographicFormatTest {
byte[] rewritten = null;
try {
if (plaintextMutation) {
byte[] nonce = slice(image, entry.nonceOffset,
entry.nonceOffset + KeyringStore.NONCE_BYTES);
byte[] nonce = slice(image, entry.nonceOffset, entry.nonceOffset + KeyringStore.NONCE_BYTES);
byte[] aad = entryAad(image, entry, 0);
byte[] plaintext = null;
try {
@@ -497,13 +472,10 @@ class KeyringCryptographicFormatTest {
}
rewritten = installFirstCiphertext(image, masterKey, changed);
Path path = copyImage(source, rewritten, name);
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.open(path, password)) {
KeyringException failure = assertThrows(KeyringException.class,
() -> store.getSecret("one"));
try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) {
KeyringException failure = assertThrows(KeyringException.class, () -> store.getSecret("one"));
assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID, failure.code());
assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID.name(),
failure.getMessage());
assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID.name(), failure.getMessage());
}
} finally {
wipe(ciphertext);
@@ -512,8 +484,8 @@ class KeyringCryptographicFormatTest {
}
}
private void assertCoherentIdentityMutationRejected(Path source, byte[] image,
byte[] masterKey, String name, boolean entryId) throws Exception {
private void assertCoherentIdentityMutationRejected(Path source, byte[] image, byte[] masterKey, String name,
boolean entryId) throws Exception {
byte[] rewritten = image.clone();
byte[] manifest = decryptManifest(image, masterKey);
try {
@@ -531,10 +503,8 @@ class KeyringCryptographicFormatTest {
wipe(rewritten);
rewritten = withManifest;
Path path = copyImage(source, rewritten, name);
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.open(path, password)) {
KeyringException failure = assertThrows(KeyringException.class,
() -> store.getSecret("one"));
try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) {
KeyringException failure = assertThrows(KeyringException.class, () -> store.getSecret("one"));
assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID, failure.code());
}
} finally {
@@ -543,8 +513,7 @@ class KeyringCryptographicFormatTest {
}
}
private static byte[] installFirstCiphertext(byte[] image, byte[] masterKey,
byte[] ciphertext) throws Exception {
private static byte[] installFirstCiphertext(byte[] image, byte[] masterKey, byte[] ciphertext) throws Exception {
byte[] result = image.clone();
ImageLayout imageLayout = layout(result);
WireEntry entry = imageLayout.entries.get(0);
@@ -581,8 +550,7 @@ class KeyringCryptographicFormatTest {
}
}
private static byte[] replaceManifest(byte[] image, byte[] masterKey,
byte[] plaintext) throws Exception {
private static byte[] replaceManifest(byte[] image, byte[] masterKey, byte[] plaintext) throws Exception {
ImageLayout imageLayout = layout(image);
byte[] nonce = slice(image, imageLayout.manifestNonceOffset,
imageLayout.manifestNonceOffset + KeyringStore.NONCE_BYTES);
@@ -591,11 +559,9 @@ class KeyringCryptographicFormatTest {
try {
ciphertext = crypt(Cipher.ENCRYPT_MODE, masterKey, nonce, aad, plaintext);
int lengthOffset = imageLayout.manifestCipherOffset - Integer.BYTES;
byte[] result = Arrays.copyOf(image, imageLayout.manifestCipherOffset
+ ciphertext.length);
byte[] result = Arrays.copyOf(image, imageLayout.manifestCipherOffset + ciphertext.length);
putInt(result, lengthOffset, ciphertext.length);
System.arraycopy(ciphertext, 0, result, imageLayout.manifestCipherOffset,
ciphertext.length);
System.arraycopy(ciphertext, 0, result, imageLayout.manifestCipherOffset, ciphertext.length);
return result;
} finally {
wipe(nonce);
@@ -605,8 +571,7 @@ class KeyringCryptographicFormatTest {
}
private static byte[] manifestAad(byte[] image) {
ByteBuffer buffer = ByteBuffer.allocate(8 + Integer.BYTES
+ KeyringStore.UUID_BYTES + Integer.BYTES * 2);
ByteBuffer buffer = ByteBuffer.allocate(8 + Integer.BYTES + KeyringStore.UUID_BYTES + Integer.BYTES * 2);
buffer.put(KeyringStore.MAGIC);
buffer.putInt(KeyringStore.FORMAT_VERSION);
buffer.put(image, STORE_ID_OFFSET, KeyringStore.UUID_BYTES);
@@ -616,8 +581,7 @@ class KeyringCryptographicFormatTest {
}
private static byte[] entryAad(byte[] image, WireEntry entry, int position) {
ByteBuffer buffer = ByteBuffer.allocate(8 + Integer.BYTES
+ KeyringStore.UUID_BYTES * 2 + Integer.BYTES * 2);
ByteBuffer buffer = ByteBuffer.allocate(8 + Integer.BYTES + KeyringStore.UUID_BYTES * 2 + Integer.BYTES * 2);
buffer.put(KeyringStore.MAGIC);
buffer.putInt(KeyringStore.FORMAT_VERSION);
buffer.put(image, STORE_ID_OFFSET, KeyringStore.UUID_BYTES);
@@ -627,11 +591,9 @@ class KeyringCryptographicFormatTest {
return buffer.array();
}
private static byte[] crypt(int mode, byte[] key, byte[] nonce, byte[] aad,
byte[] input) throws Exception {
private static byte[] crypt(int mode, byte[] key, byte[] nonce, byte[] aad, byte[] input) throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(mode, new SecretKeySpec(key, "AES"),
new GCMParameterSpec(KeyringStore.GCM_TAG_BITS, nonce));
cipher.init(mode, new SecretKeySpec(key, "AES"), new GCMParameterSpec(KeyringStore.GCM_TAG_BITS, nonce));
cipher.updateAAD(aad);
return cipher.doFinal(input);
}
@@ -664,9 +626,8 @@ class KeyringCryptographicFormatTest {
int digest = buffer.position();
buffer.position(digest + KeyringStore.SHA256_BYTES);
if (index == target) {
return new ManifestDescriptor(start, buffer.position(), entryId, position,
alias, aliasLength, algorithm, kind, encoding,
hmac, nonce, ciphertextLength, digest);
return new ManifestDescriptor(start, buffer.position(), entryId, position, alias, aliasLength,
algorithm, kind, encoding, hmac, nonce, ciphertextLength, digest);
}
}
throw new IllegalArgumentException("descriptor index");
@@ -712,11 +673,9 @@ class KeyringCryptographicFormatTest {
private void assertUniformUnlockFailure(Path source, char[] candidate) throws Exception {
try (KeyringPassword password = new KeyringPassword(candidate)) {
KeyringException failure = assertThrows(KeyringException.class,
() -> KeyringStore.open(source, password));
KeyringException failure = assertThrows(KeyringException.class, () -> KeyringStore.open(source, password));
assertEquals(KeyringException.Code.KEYRING_UNLOCK_FAILED, failure.code());
assertEquals(KeyringException.Code.KEYRING_UNLOCK_FAILED.name(),
failure.getMessage());
assertEquals(KeyringException.Code.KEYRING_UNLOCK_FAILED.name(), failure.getMessage());
assertNull(failure.getCause());
assertEquals(0, failure.getSuppressed().length);
}
@@ -724,8 +683,7 @@ class KeyringCryptographicFormatTest {
private void assertRejected(Path path, KeyringException.Code code) throws Exception {
try (KeyringPassword password = password()) {
KeyringException failure = assertThrows(KeyringException.class,
() -> KeyringStore.open(path, password));
KeyringException failure = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password));
assertEquals(code, failure.code());
assertEquals(code.name(), failure.getMessage());
assertNull(failure.getCause());
@@ -734,18 +692,16 @@ class KeyringCryptographicFormatTest {
private void assertSafeRejected(Path path) throws Exception {
try (KeyringPassword password = password()) {
KeyringException failure = assertThrows(KeyringException.class,
() -> KeyringStore.open(path, password));
assertTrue(Set.of(KeyringException.Code.KEYRING_FORMAT_INVALID,
KeyringException.Code.KEYRING_LIMIT_EXCEEDED,
KeyringException.Code.KEYRING_UNLOCK_FAILED).contains(failure.code()));
KeyringException failure = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password));
assertTrue(
Set.of(KeyringException.Code.KEYRING_FORMAT_INVALID, KeyringException.Code.KEYRING_LIMIT_EXCEEDED,
KeyringException.Code.KEYRING_UNLOCK_FAILED).contains(failure.code()));
assertEquals(failure.code().name(), failure.getMessage());
assertNull(failure.getCause());
}
}
private Path copyWithMutation(Path source, byte[] image, String name,
Mutation mutation) throws Exception {
private Path copyWithMutation(Path source, byte[] image, String name, Mutation mutation) throws Exception {
byte[] copy = image.clone();
try {
mutation.apply(copy);
@@ -765,8 +721,7 @@ class KeyringCryptographicFormatTest {
}
private static List<String> openAliases(Path path) throws Exception {
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.open(path, password)) {
try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) {
return store.aliases();
}
}
@@ -784,8 +739,7 @@ class KeyringCryptographicFormatTest {
int length = buffer.getInt();
int ciphertext = buffer.position();
buffer.position(ciphertext + length);
entries.add(new WireEntry(entryId, nonce, ciphertext,
buffer.position()));
entries.add(new WireEntry(entryId, nonce, ciphertext, buffer.position()));
}
int manifestNonce = buffer.position();
buffer.position(manifestNonce + KeyringStore.NONCE_BYTES);
@@ -812,8 +766,7 @@ class KeyringCryptographicFormatTest {
private static byte[] duplicateFirstEntry(byte[] image, ImageLayout layout) {
WireEntry first = layout.entries.get(0);
WireEntry second = layout.entries.get(1);
return replaceEntry(image, second,
slice(image, first.entryIdOffset, first.endOffset));
return replaceEntry(image, second, slice(image, first.entryIdOffset, first.endOffset));
}
private static byte[] deleteFirstEntry(byte[] image, ImageLayout layout) {
@@ -833,8 +786,7 @@ class KeyringCryptographicFormatTest {
System.arraycopy(image, 0, result, 0, layout.manifestNonceOffset);
putInt(result, ENTRY_COUNT_OFFSET, 3);
System.arraycopy(encoded, 0, result, layout.manifestNonceOffset, encoded.length);
System.arraycopy(image, layout.manifestNonceOffset, result,
layout.manifestNonceOffset + encoded.length,
System.arraycopy(image, layout.manifestNonceOffset, result, layout.manifestNonceOffset + encoded.length,
image.length - layout.manifestNonceOffset);
wipe(encoded);
return result;
@@ -845,14 +797,14 @@ class KeyringCryptographicFormatTest {
byte[] result = new byte[image.length - currentLength + replacement.length];
System.arraycopy(image, 0, result, 0, target.entryIdOffset);
System.arraycopy(replacement, 0, result, target.entryIdOffset, replacement.length);
System.arraycopy(image, target.endOffset, result,
target.entryIdOffset + replacement.length, image.length - target.endOffset);
System.arraycopy(image, target.endOffset, result, target.entryIdOffset + replacement.length,
image.length - target.endOffset);
wipe(replacement);
return result;
}
private static byte[] entryPlaintext(int version, String alias, String algorithm,
int kind, int encoding, int hmac, byte[] key, boolean trailing) throws Exception {
private static byte[] entryPlaintext(int version, String alias, String algorithm, int kind, int encoding, int hmac,
byte[] key, boolean trailing) throws Exception {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (DataOutputStream out = new DataOutputStream(bytes)) {
out.writeInt(version);
@@ -870,8 +822,7 @@ class KeyringCryptographicFormatTest {
return bytes.toByteArray();
}
private static byte[] lengthOnlyEntry(int aliasLength, String algorithm)
throws Exception {
private static byte[] lengthOnlyEntry(int aliasLength, String algorithm) throws Exception {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (DataOutputStream out = new DataOutputStream(bytes)) {
out.writeInt(1);
@@ -905,10 +856,8 @@ class KeyringCryptographicFormatTest {
return bytes.toByteArray();
}
private static byte[] emptyManifest(int version, long highWater, int count,
boolean trailing) {
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES * 3 + Long.BYTES
+ (trailing ? 1 : 0));
private static byte[] emptyManifest(int version, long highWater, int count, boolean trailing) {
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES * 3 + Long.BYTES + (trailing ? 1 : 0));
buffer.putInt(version).putInt(0x010203).putLong(highWater).putInt(count);
if (trailing) {
buffer.put((byte) 0);
@@ -917,8 +866,7 @@ class KeyringCryptographicFormatTest {
}
private static void invokeDecodeEntry(byte[] input) throws Exception {
Method method = KeyringStore.class.getDeclaredMethod("decodeEntryPlaintext",
byte[].class);
Method method = KeyringStore.class.getDeclaredMethod("decodeEntryPlaintext", byte[].class);
method.setAccessible(true);
method.invoke(null, (Object) input);
}
@@ -933,16 +881,13 @@ class KeyringCryptographicFormatTest {
}
}
private static void invokeDecodeManifest(byte[] input, int expectedCount)
throws Exception {
Method method = KeyringStore.class.getDeclaredMethod("decodeManifest",
byte[].class, int.class);
private static void invokeDecodeManifest(byte[] input, int expectedCount) throws Exception {
Method method = KeyringStore.class.getDeclaredMethod("decodeManifest", byte[].class, int.class);
method.setAccessible(true);
method.invoke(null, input, expectedCount);
}
private static void assertDecodeManifestRejected(byte[] input, int expectedCount)
throws Exception {
private static void assertDecodeManifestRejected(byte[] input, int expectedCount) throws Exception {
try {
InvocationTargetException failure = assertThrows(InvocationTargetException.class,
() -> invokeDecodeManifest(input, expectedCount));
@@ -971,8 +916,7 @@ class KeyringCryptographicFormatTest {
}
private static int indexOf(byte[] haystack, byte[] needle) {
outer:
for (int index = 0; index <= haystack.length - needle.length; index++) {
outer: for (int index = 0; index <= haystack.length - needle.length; index++) {
for (int offset = 0; offset < needle.length; offset++) {
if (haystack[index + offset] != needle[offset]) {
continue outer;
@@ -992,8 +936,7 @@ class KeyringCryptographicFormatTest {
}
private static void ownerOnly(Path path) throws Exception {
Files.setPosixFilePermissions(path, Set.of(
java.nio.file.attribute.PosixFilePermission.OWNER_READ,
Files.setPosixFilePermissions(path, Set.of(java.nio.file.attribute.PosixFilePermission.OWNER_READ,
java.nio.file.attribute.PosixFilePermission.OWNER_WRITE));
}
@@ -1039,18 +982,15 @@ class KeyringCryptographicFormatTest {
byte[] apply(byte[] value);
}
private record WireEntry(int entryIdOffset, int nonceOffset,
int ciphertextOffset, int endOffset) {
private record WireEntry(int entryIdOffset, int nonceOffset, int ciphertextOffset, int endOffset) {
}
private record ImageLayout(List<WireEntry> entries, int manifestNonceOffset,
int manifestCipherOffset) {
private record ImageLayout(List<WireEntry> entries, int manifestNonceOffset, int manifestCipherOffset) {
}
private record ManifestDescriptor(int startOffset, int endOffset,
int entryIdOffset, int positionOffset, int aliasOffset, int aliasLength,
int algorithmOffset, int kindOffset, int encodingOffset, int hmacOffset, int nonceOffset,
int ciphertextLengthOffset, int digestOffset) {
private record ManifestDescriptor(int startOffset, int endOffset, int entryIdOffset, int positionOffset,
int aliasOffset, int aliasLength, int algorithmOffset, int kindOffset, int encodingOffset, int hmacOffset,
int nonceOffset, int ciphertextLengthOffset, int digestOffset) {
}
private record StoreFixture(Path path, byte[] masterKey) {

View File

@@ -54,12 +54,9 @@ class KeyringFilesystemSecurityTest {
private static final char[] PASSWORD = { 'f', 'i', 'l', 'e', 's', 'y', 's' };
private static final byte[] CHILD_PASSWORD = { 'f', 'i', 'l', 'e', 's', 'y', 's' };
private static final long TIMEOUT_SECONDS = 15L;
private static final Set<PosixFilePermission> DIRECTORY_PERMISSIONS = Set.of(
PosixFilePermission.OWNER_READ,
PosixFilePermission.OWNER_WRITE,
PosixFilePermission.OWNER_EXECUTE);
private static final Set<PosixFilePermission> FILE_PERMISSIONS = Set.of(
PosixFilePermission.OWNER_READ,
private static final Set<PosixFilePermission> DIRECTORY_PERMISSIONS = Set.of(PosixFilePermission.OWNER_READ,
PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE);
private static final Set<PosixFilePermission> FILE_PERMISSIONS = Set.of(PosixFilePermission.OWNER_READ,
PosixFilePermission.OWNER_WRITE);
@TempDir
@@ -80,20 +77,15 @@ class KeyringFilesystemSecurityTest {
}
assertOpenSucceeds(path);
for (PosixFilePermission unsafe : List.of(
PosixFilePermission.GROUP_READ,
PosixFilePermission.GROUP_WRITE,
PosixFilePermission.OTHERS_READ,
PosixFilePermission.OTHERS_WRITE)) {
for (PosixFilePermission unsafe : List.of(PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_WRITE,
PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_WRITE)) {
Path unsafeParent = temporaryDirectory.resolve("parent-" + unsafe.name());
Path unsafeStore = unsafeParent.resolve("keys.zek");
createPopulated(unsafeStore, unsafe.ordinal() + 10);
Set<PosixFilePermission> permissions = new java.util.HashSet<>(
DIRECTORY_PERMISSIONS);
Set<PosixFilePermission> permissions = new java.util.HashSet<>(DIRECTORY_PERMISSIONS);
permissions.add(unsafe);
Files.setPosixFilePermissions(unsafeParent, permissions);
assertRedactedFailure(unsafeStore,
KeyringException.Code.KEYRING_FILESYSTEM_UNSUPPORTED);
assertRedactedFailure(unsafeStore, KeyringException.Code.KEYRING_FILESYSTEM_UNSUPPORTED);
Files.setPosixFilePermissions(unsafeParent, DIRECTORY_PERMISSIONS);
}
@@ -119,8 +111,8 @@ class KeyringFilesystemSecurityTest {
Path path = temporaryDirectory.resolve("owner-mismatch.zek");
createPopulated(path, 31);
Method validator = KeyringStore.class.getDeclaredMethod("validateExistingFile",
Path.class, UserPrincipal.class);
Method validator = KeyringStore.class.getDeclaredMethod("validateExistingFile", Path.class,
UserPrincipal.class);
validator.setAccessible(true);
UserPrincipal other = () -> "controlled-other-owner";
InvocationTargetException failure = assertThrows(InvocationTargetException.class,
@@ -151,15 +143,14 @@ class KeyringFilesystemSecurityTest {
Path path = temporaryDirectory.resolve("shared.zek");
byte[] material = material((byte) 0x41);
try (KeyringPassword password = password();
KeyringStore first = KeyringStore.create(path, password,
KeyringProtection.standard(), deterministicRandom(41));
KeyringStore first = KeyringStore.create(path, password, KeyringProtection.standard(),
deterministicRandom(41));
KeyringPassword secondPassword = password()) {
first.putSecret("shared", "AES", new SecretKeySpec(material, "AES"));
KeyringException contention = assertThrows(KeyringException.class,
() -> KeyringStore.open(path, secondPassword));
assertEquals(KeyringException.Code.KEYRING_ALREADY_OPEN, contention.code());
assertEquals(KeyringException.Code.KEYRING_ALREADY_OPEN.name(),
contention.getMessage());
assertEquals(KeyringException.Code.KEYRING_ALREADY_OPEN.name(), contention.getMessage());
assertNull(contention.getCause());
assertArrayEquals(material, first.getSecret("shared").getEncoded());
} finally {
@@ -216,8 +207,8 @@ class KeyringFilesystemSecurityTest {
byte[] first = material((byte) 0x12);
byte[] second = material((byte) 0x34);
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(path, password,
KeyringProtection.standard(), deterministicRandom(71))) {
KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(),
deterministicRandom(71))) {
store.putSecret("one", "AES", new SecretKeySpec(first, "AES"));
store.putSecret("two", "AES", new SecretKeySpec(second, "AES"));
runConcurrentReaders(store, first, second);
@@ -273,8 +264,7 @@ class KeyringFilesystemSecurityTest {
BlockingRandom random = new BlockingRandom(81);
KeyringStore store;
try (KeyringPassword password = password()) {
store = KeyringStore.create(path, password,
KeyringProtection.standard(), random);
store = KeyringStore.create(path, password, KeyringProtection.standard(), random);
}
byte[] material = material((byte) 0x5c);
AtomicReference<Throwable> putFailure = new AtomicReference<>();
@@ -282,8 +272,7 @@ class KeyringFilesystemSecurityTest {
random.arm();
Thread mutation = new Thread(() -> {
try {
store.putSecret("admitted", "AES",
new SecretKeySpec(material, "AES"));
store.putSecret("admitted", "AES", new SecretKeySpec(material, "AES"));
} catch (Throwable throwable) {
putFailure.set(throwable);
}
@@ -305,11 +294,8 @@ class KeyringFilesystemSecurityTest {
assertNull(putFailure.get());
assertNull(closeFailure.get());
assertTrue(store.isDestroyed());
assertThrows(KeyringException.class,
() -> store.putSecret("late", "AES",
new SecretKeySpec(material, "AES")));
try (KeyringPassword password = password();
KeyringStore reopened = KeyringStore.open(path, password)) {
assertThrows(KeyringException.class, () -> store.putSecret("late", "AES", new SecretKeySpec(material, "AES")));
try (KeyringPassword password = password(); KeyringStore reopened = KeyringStore.open(path, password)) {
assertArrayEquals(material, reopened.getSecret("admitted").getEncoded());
} finally {
wipe(material);
@@ -367,8 +353,7 @@ class KeyringFilesystemSecurityTest {
createPopulated(path, 111);
Path link = temporaryDirectory.resolve("linked-parent");
Files.createSymbolicLink(link, real.getFileName());
assertRedactedFailure(link.resolve("keys.zek"),
KeyringException.Code.KEYRING_FILESYSTEM_UNSUPPORTED);
assertRedactedFailure(link.resolve("keys.zek"), KeyringException.Code.KEYRING_FILESYSTEM_UNSUPPORTED);
}
private void assertArtifactHardLinkRejected(Artifact artifact) throws Exception {
@@ -394,8 +379,7 @@ class KeyringFilesystemSecurityTest {
Path malicious = temporaryDirectory.resolve(".temp-race.zek.precreated.tmp");
Files.createSymbolicLink(malicious, target.getFileName());
byte[] material = material((byte) 0x63);
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.open(path, password)) {
try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) {
store.putSecret("safe", "AES", new SecretKeySpec(material, "AES"));
assertArrayEquals(before, Files.readAllBytes(target));
} finally {
@@ -404,8 +388,7 @@ class KeyringFilesystemSecurityTest {
}
}
private void runConcurrentReaders(KeyringStore store, byte[] first, byte[] second)
throws Exception {
private void runConcurrentReaders(KeyringStore store, byte[] first, byte[] second) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(8);
CountDownLatch start = new CountDownLatch(1);
List<Future<Boolean>> results = new ArrayList<>();
@@ -415,8 +398,7 @@ class KeyringFilesystemSecurityTest {
results.add(executor.submit(() -> {
start.await();
if (selected == 7) {
assertThrows(IllegalArgumentException.class,
() -> store.getSecret("missing"));
assertThrows(IllegalArgumentException.class, () -> store.getSecret("missing"));
return true;
}
String alias = selected % 2 == 0 ? "one" : "two";
@@ -472,8 +454,8 @@ class KeyringFilesystemSecurityTest {
private void createPopulated(Path path, int seed) throws Exception {
byte[] material = material((byte) seed);
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(path, password,
KeyringProtection.standard(), deterministicRandom(seed))) {
KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(),
deterministicRandom(seed))) {
store.putSecret("child", "AES", new SecretKeySpec(material, "AES"));
} finally {
wipe(material);
@@ -481,8 +463,7 @@ class KeyringFilesystemSecurityTest {
}
private static void assertOpenSucceeds(Path path) throws Exception {
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.open(path, password)) {
try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) {
assertFalse(store.isDestroyed());
store.aliases();
}
@@ -490,19 +471,16 @@ class KeyringFilesystemSecurityTest {
private static void assertAlreadyOpen(Path path) throws Exception {
try (KeyringPassword password = password()) {
KeyringException failure = assertThrows(KeyringException.class,
() -> KeyringStore.open(path, password));
KeyringException failure = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password));
assertEquals(KeyringException.Code.KEYRING_ALREADY_OPEN, failure.code());
assertEquals(failure.code().name(), failure.getMessage());
assertNull(failure.getCause());
}
}
private static void assertRedactedFailure(Path path, KeyringException.Code code)
throws Exception {
private static void assertRedactedFailure(Path path, KeyringException.Code code) throws Exception {
try (KeyringPassword password = password()) {
KeyringException failure = assertThrows(KeyringException.class,
() -> KeyringStore.open(path, password));
KeyringException failure = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password));
assertEquals(code, failure.code());
assertEquals(code.name(), failure.getMessage());
assertNull(failure.getCause());
@@ -511,8 +489,8 @@ class KeyringFilesystemSecurityTest {
}
}
private static Thread closeThread(KeyringStore store, CountDownLatch started,
AtomicReference<Throwable> failure, String name) {
private static Thread closeThread(KeyringStore store, CountDownLatch started, AtomicReference<Throwable> failure,
String name) {
return new Thread(() -> {
started.countDown();
try {
@@ -523,8 +501,7 @@ class KeyringFilesystemSecurityTest {
}, name);
}
private static void awaitQueued(ReentrantReadWriteLock lock, int minimum)
throws Exception {
private static void awaitQueued(ReentrantReadWriteLock lock, int minimum) throws Exception {
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(TIMEOUT_SECONDS);
while (lock.getQueueLength() < minimum && System.nanoTime() < deadline) {
Thread.onSpinWait();
@@ -674,19 +651,17 @@ class KeyringFilesystemSecurityTest {
private ChildOwner(Process process) {
this.process = process;
output = new BufferedReader(new InputStreamReader(process.getInputStream(),
StandardCharsets.UTF_8));
output = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));
input = process.getOutputStream();
}
private static ChildOwner start(Path path) throws Exception {
String executable = System.getProperty("os.name", "")
.toLowerCase(java.util.Locale.ROOT).contains("win")
? "java.exe" : "java";
String executable = System.getProperty("os.name", "").toLowerCase(java.util.Locale.ROOT).contains("win")
? "java.exe"
: "java";
Path java = Path.of(System.getProperty("java.home"), "bin", executable);
Process process = new ProcessBuilder(java.toString(), "-cp", childClasspath(),
KeyringStoreLockProcess.class.getName(), path.toString())
.redirectErrorStream(true).start();
KeyringStoreLockProcess.class.getName(), path.toString()).redirectErrorStream(true).start();
return new ChildOwner(process);
}
@@ -777,8 +752,7 @@ class KeyringFilesystemSecurityTest {
}
private static String codeSource(Class<?> type) throws Exception {
return Path.of(type.getProtectionDomain().getCodeSource().getLocation().toURI())
.toString();
return Path.of(type.getProtectionDomain().getCodeSource().getLocation().toURI()).toString();
}
}
@@ -802,8 +776,8 @@ final class KeyringStoreLockProcess {
}
try (KeyringPassword password = new KeyringPassword(passwordChars);
KeyringStore store = KeyringStore.open(Path.of(args[0]), password);
BufferedReader control = new BufferedReader(new InputStreamReader(
System.in, StandardCharsets.US_ASCII))) {
BufferedReader control = new BufferedReader(
new InputStreamReader(System.in, StandardCharsets.US_ASCII))) {
Arrays.fill(passwordChars, '\0');
Arrays.fill(passwordBytes, (byte) 0);
System.out.println("KEYRING_OPEN");

View File

@@ -36,34 +36,31 @@ class KeyringImportRegistryTest {
@Test
void persistentImporterMatrixIsClosedUniqueAndExecutable() throws Exception {
start("persistentImporterMatrixIsClosedUniqueAndExecutable");
List<KeyringImportRegistry.PersistentMapping> mappings =
KeyringImportRegistry.mappings();
List<KeyringImportRegistry.PersistentMapping> mappings = KeyringImportRegistry.mappings();
assertEquals(19, count(mappings, KeyringStore.Kind.PUBLIC_KEY));
assertEquals(19, count(mappings, KeyringStore.Kind.PRIVATE_KEY));
assertEquals(6, count(mappings, KeyringStore.Kind.SECRET_KEY));
assertEquals(mappings.size(), mappings.stream().distinct().count());
for (KeyringImportRegistry.PersistentMapping mapping : mappings) {
KeyringImportRegistry.validateMapping(mapping.algorithmId(), mapping.kind(),
mapping.encoding(), mapping.hmacVariant());
KeyringImportRegistry.validateMapping(mapping.algorithmId(), mapping.kind(), mapping.encoding(),
mapping.hmacVariant());
}
roundTripAsymmetricMappings(mappings);
roundTripSecretMappings(mappings);
assertEquals(KeyringException.Code.KEYRING_IMPORT_MAPPING_INVALID,
assertThrows(KeyringException.class,
() -> KeyringImportRegistry.validateMapping("RSA",
KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW,
KeyringImportRegistry.HmacVariant.NONE)).code());
() -> KeyringImportRegistry.validateMapping("RSA", KeyringStore.Kind.SECRET_KEY,
KeyringStore.Encoding.RAW, KeyringImportRegistry.HmacVariant.NONE))
.code());
System.out.println("...mapping-count=" + mappings.size());
ok();
}
@Test
void alternateProviderStandardEncodingUsesCanonicalImporter(
@TempDir Path temporaryDirectory) throws Exception {
void alternateProviderStandardEncodingUsesCanonicalImporter(@TempDir Path temporaryDirectory) throws Exception {
start("alternateProviderStandardEncodingUsesCanonicalImporter");
java.security.KeyPairGenerator generator =
java.security.KeyPairGenerator.getInstance("RSA", "BC");
java.security.KeyPairGenerator generator = java.security.KeyPairGenerator.getInstance("RSA", "BC");
generator.initialize(2048);
KeyPair pair = generator.generateKeyPair();
byte[] encoded = pair.getPublic().getEncoded();
@@ -72,20 +69,17 @@ class KeyringImportRegistryTest {
byte[] importedEncoding = null;
byte[] reopenedEncoding = null;
try {
imported = KeyringImportRegistry.importKey("RSA",
KeyringStore.Kind.PUBLIC_KEY, KeyringStore.Encoding.X509,
imported = KeyringImportRegistry.importKey("RSA", KeyringStore.Kind.PUBLIC_KEY, KeyringStore.Encoding.X509,
KeyringImportRegistry.HmacVariant.NONE, encoded);
importedEncoding = imported.getEncoded();
assertArrayEquals(encoded, importedEncoding);
assertEquals("RSA", imported.getAlgorithm());
assertNotEquals(pair.getPublic().getClass(), imported.getClass());
Path path = temporaryDirectory.resolve("alternate-provider.zek");
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(path, password)) {
try (KeyringPassword password = password(); KeyringStore store = KeyringStore.create(path, password)) {
store.putPublic("alternate", "RSA", pair.getPublic());
}
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.open(path, password)) {
try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) {
reopened = store.getPublic("alternate");
reopenedEncoding = reopened.getEncoded();
assertArrayEquals(encoded, reopenedEncoding);
@@ -100,28 +94,23 @@ class KeyringImportRegistryTest {
ok();
}
private static long count(List<KeyringImportRegistry.PersistentMapping> mappings,
KeyringStore.Kind kind) {
private static long count(List<KeyringImportRegistry.PersistentMapping> mappings, KeyringStore.Kind kind) {
return mappings.stream().filter(mapping -> mapping.kind() == kind).count();
}
private static void roundTripAsymmetricMappings(
List<KeyringImportRegistry.PersistentMapping> mappings) throws Exception {
List<String> algorithms = mappings.stream()
.filter(mapping -> mapping.kind() == KeyringStore.Kind.PUBLIC_KEY)
.map(KeyringImportRegistry.PersistentMapping::algorithmId)
.toList();
private static void roundTripAsymmetricMappings(List<KeyringImportRegistry.PersistentMapping> mappings)
throws Exception {
List<String> algorithms = mappings.stream().filter(mapping -> mapping.kind() == KeyringStore.Kind.PUBLIC_KEY)
.map(KeyringImportRegistry.PersistentMapping::algorithmId).toList();
for (String algorithmId : algorithms) {
KeyPair pair = generatePair(algorithmId);
roundTrip(mapping(mappings, algorithmId, KeyringStore.Kind.PUBLIC_KEY),
pair.getPublic());
roundTrip(mapping(mappings, algorithmId, KeyringStore.Kind.PRIVATE_KEY),
pair.getPrivate());
roundTrip(mapping(mappings, algorithmId, KeyringStore.Kind.PUBLIC_KEY), pair.getPublic());
roundTrip(mapping(mappings, algorithmId, KeyringStore.Kind.PRIVATE_KEY), pair.getPrivate());
}
}
private static void roundTripSecretMappings(
List<KeyringImportRegistry.PersistentMapping> mappings) throws Exception {
private static void roundTripSecretMappings(List<KeyringImportRegistry.PersistentMapping> mappings)
throws Exception {
for (KeyringImportRegistry.PersistentMapping mapping : mappings) {
if (mapping.kind() != KeyringStore.Kind.SECRET_KEY) {
continue;
@@ -147,33 +136,26 @@ class KeyringImportRegistryTest {
CryptoAlgorithm algorithm = CryptoAlgorithms.require(algorithmId);
KeyOperationInfo generation = algorithm.keyOperations().stream()
.filter(info -> info.operation() == KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE)
.filter(info -> info.defaultSpec() != null)
.findFirst()
.orElseThrow(() -> new AssertionError(
"No default key-pair generation mapping for " + algorithmId));
AsymmetricKeyPairGenerator generator =
algorithm.asymmetricKeyPairGenerator(generation.specType());
.filter(info -> info.defaultSpec() != null).findFirst()
.orElseThrow(() -> new AssertionError("No default key-pair generation mapping for " + algorithmId));
AsymmetricKeyPairGenerator generator = algorithm.asymmetricKeyPairGenerator(generation.specType());
return generator.generateKeyPair((AlgorithmKeySpec) generation.defaultSpec());
}
private static KeyringImportRegistry.PersistentMapping mapping(
List<KeyringImportRegistry.PersistentMapping> mappings, String algorithmId,
KeyringStore.Kind kind) {
List<KeyringImportRegistry.PersistentMapping> mappings, String algorithmId, KeyringStore.Kind kind) {
return mappings.stream()
.filter(candidate -> candidate.algorithmId().equals(algorithmId)
&& candidate.kind() == kind)
.findFirst()
.orElseThrow();
.filter(candidate -> candidate.algorithmId().equals(algorithmId) && candidate.kind() == kind)
.findFirst().orElseThrow();
}
private static void roundTrip(KeyringImportRegistry.PersistentMapping mapping,
Key source) throws Exception {
private static void roundTrip(KeyringImportRegistry.PersistentMapping mapping, Key source) throws Exception {
byte[] encoded = source.getEncoded();
Key imported = null;
byte[] reconstructed = null;
try {
imported = KeyringImportRegistry.importKey(mapping.algorithmId(), mapping.kind(),
mapping.encoding(), mapping.hmacVariant(), encoded);
imported = KeyringImportRegistry.importKey(mapping.algorithmId(), mapping.kind(), mapping.encoding(),
mapping.hmacVariant(), encoded);
reconstructed = imported.getEncoded();
assertArrayEquals(encoded, reconstructed);
} finally {

View File

@@ -27,8 +27,7 @@ import org.junit.jupiter.api.io.TempDir;
class KeyringNonceReservationTest {
private static final int MAGIC_BYTES = 8;
private static final int VERSION_OFFSET = MAGIC_BYTES;
private static final int HIGH_WATER_OFFSET =
MAGIC_BYTES + Integer.BYTES + KeyringStore.UUID_BYTES + Integer.BYTES;
private static final int HIGH_WATER_OFFSET = MAGIC_BYTES + Integer.BYTES + KeyringStore.UUID_BYTES + Integer.BYTES;
private static final int TAG_BYTES = 32;
private static final int CURRENT_SIDECAR_VERSION = 2;
private static final char[] PASSWORD = { 'n', 'o', 'n', 'c', 'e' };
@@ -47,8 +46,7 @@ class KeyringNonceReservationTest {
for (int index = 0; index < storeId.length; index++) {
storeId[index] = (byte) (0xa0 + index);
}
byte[] expected = HexFormat.of().parseHex(
"54e0e054749745a3ef5e2cc5a5c16bafed6f39df9daa4ff412bac74d56bd27b9");
byte[] expected = HexFormat.of().parseHex("54e0e054749745a3ef5e2cc5a5c16bafed6f39df9daa4ff412bac74d56bd27b9");
byte[] first = null;
byte[] second = null;
byte[] changedMaster = null;
@@ -88,13 +86,12 @@ class KeyringNonceReservationTest {
byte[] retainedMacKey;
byte[] macKeyCopy;
try (KeyringPassword password = password()) {
KeyringStore store = KeyringStore.create(path, password,
KeyringProtection.standard(), deterministicRandom());
KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(),
deterministicRandom());
retainedMacKey = field(store, "nonceReservationMacKey");
macKeyCopy = retainedMacKey.clone();
assertEquals(CURRENT_SIDECAR_VERSION,
ByteBuffer.wrap(Files.readAllBytes(sidecar(path)),
VERSION_OFFSET, Integer.BYTES).getInt());
ByteBuffer.wrap(Files.readAllBytes(sidecar(path)), VERSION_OFFSET, Integer.BYTES).getInt());
store.close();
store.close();
assertTrue(allZero(retainedMacKey));
@@ -109,8 +106,7 @@ class KeyringNonceReservationTest {
wipe(reservation);
wipe(macKeyCopy);
}
try (KeyringPassword password = password();
KeyringStore reopened = KeyringStore.open(path, password)) {
try (KeyringPassword password = password(); KeyringStore reopened = KeyringStore.open(path, password)) {
assertTrue(reopened.aliases().isEmpty());
}
ok();
@@ -153,14 +149,13 @@ class KeyringNonceReservationTest {
Arrays.fill(material, (byte) 0x4a);
try {
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(path, password,
KeyringProtection.standard(), deterministicRandom())) {
KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(),
deterministicRandom())) {
assertEquals(1L, longField(store, "nonceHighWater"));
Files.move(path, saved);
Files.createDirectory(path);
assertThrows(IOException.class,
() -> store.putSecret("failed", "AES",
new SecretKeySpec(material, "AES")));
() -> store.putSecret("failed", "AES", new SecretKeySpec(material, "AES")));
assertEquals(3L, sidecarHighWater(sidecar(path)));
assertEquals(3L, longField(store, "nonceHighWater"));
Files.delete(path);
@@ -168,17 +163,15 @@ class KeyringNonceReservationTest {
}
try (KeyringPassword password = password();
KeyringStore reopened = KeyringStore.open(path, password,
KeyringProtection.standard(), deterministicRandom(51))) {
KeyringStore reopened = KeyringStore.open(path, password, KeyringProtection.standard(),
deterministicRandom(51))) {
assertEquals(3L, longField(reopened, "nonceHighWater"));
assertTrue(reopened.aliases().isEmpty());
reopened.putSecret("accepted", "AES",
new SecretKeySpec(material, "AES"));
reopened.putSecret("accepted", "AES", new SecretKeySpec(material, "AES"));
assertEquals(5L, longField(reopened, "nonceHighWater"));
assertEquals(5L, sidecarHighWater(sidecar(path)));
}
try (KeyringPassword password = password();
KeyringStore reopened = KeyringStore.open(path, password)) {
try (KeyringPassword password = password(); KeyringStore reopened = KeyringStore.open(path, password)) {
assertEquals(5L, longField(reopened, "nonceHighWater"));
assertArrayEquals(material, reopened.getSecret("accepted").getEncoded());
}
@@ -198,24 +191,21 @@ class KeyringNonceReservationTest {
Arrays.fill(material, (byte) 0x35);
RecordingRandom random = new RecordingRandom(1);
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(path, password,
KeyringProtection.standard(), random)) {
KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(), random)) {
byte[] mainBefore = Files.readAllBytes(path);
try {
assertEquals(List.of(32, 32, 16, 11, 3), random.requests());
Files.move(reservation, saved);
Files.createDirectory(reservation);
assertThrows(IOException.class,
() -> store.putSecret("failed", "AES",
new SecretKeySpec(material, "AES")));
() -> store.putSecret("failed", "AES", new SecretKeySpec(material, "AES")));
assertEquals(1L, longField(store, "nonceHighWater"));
assertArrayEquals(mainBefore, Files.readAllBytes(path));
assertEquals(List.of(32, 32, 16, 11, 3, 16), random.requests());
Files.delete(reservation);
Files.move(saved, reservation);
store.putSecret("accepted", "AES",
new SecretKeySpec(material, "AES"));
store.putSecret("accepted", "AES", new SecretKeySpec(material, "AES"));
assertEquals(3L, longField(store, "nonceHighWater"));
assertEquals(List.of(32, 32, 16, 11, 3, 16, 16), random.requests());
} finally {
@@ -237,8 +227,7 @@ class KeyringNonceReservationTest {
Arrays.fill(second, (byte) 0x22);
RecordingRandom random = new RecordingRandom(7);
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(path, password,
KeyringProtection.standard(), random)) {
KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(), random)) {
assertEquals(1L, longField(store, "nonceHighWater"));
assertEquals(List.of(32, 32, 16, 11, 3), random.requests());
store.putSecret("one", "AES", new SecretKeySpec(first, "AES"));
@@ -261,8 +250,8 @@ class KeyringNonceReservationTest {
byte[] image;
byte[] master;
try (KeyringPassword password = password()) {
KeyringStore store = KeyringStore.create(path, password,
KeyringProtection.standard(), deterministicRandom());
KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(),
deterministicRandom());
image = Files.readAllBytes(sidecar(path));
master = field(store, "masterKey").clone();
store.close();
@@ -304,14 +293,13 @@ class KeyringNonceReservationTest {
}
}
private Path sidecarWithVersion(String file, int version, boolean useMasterKey)
throws Exception {
private Path sidecarWithVersion(String file, int version, boolean useMasterKey) throws Exception {
Path path = temporaryDirectory.resolve(file);
byte[] image;
byte[] key;
try (KeyringPassword password = password()) {
KeyringStore store = KeyringStore.create(path, password,
KeyringProtection.standard(), deterministicRandom());
KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(),
deterministicRandom());
image = Files.readAllBytes(sidecar(path));
key = field(store, useMasterKey ? "masterKey" : "nonceReservationMacKey").clone();
store.close();
@@ -343,8 +331,7 @@ class KeyringNonceReservationTest {
private Path sidecarWithTrailingByte(String file) throws Exception {
Path path = createAndClose(file);
Files.write(sidecar(path), new byte[] { 0 },
java.nio.file.StandardOpenOption.APPEND);
Files.write(sidecar(path), new byte[] { 0 }, java.nio.file.StandardOpenOption.APPEND);
return path;
}
@@ -356,11 +343,9 @@ class KeyringNonceReservationTest {
byte[] mainBefore = Files.readAllBytes(path);
byte[] sidecarBefore = Files.readAllBytes(sidecar(path));
try (KeyringPassword password = password()) {
KeyringException exception = assertThrows(KeyringException.class,
() -> KeyringStore.open(path, password));
KeyringException exception = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password));
assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID, exception.code());
assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID.name(),
exception.getMessage());
assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID.name(), exception.getMessage());
assertArrayEquals(mainBefore, Files.readAllBytes(path));
assertArrayEquals(sidecarBefore, Files.readAllBytes(sidecar(path)));
} finally {
@@ -376,8 +361,8 @@ class KeyringNonceReservationTest {
private Path createAndClose(String file, int randomSeed) throws Exception {
Path path = temporaryDirectory.resolve(file);
try (KeyringPassword password = password();
KeyringStore ignored = KeyringStore.create(path, password,
KeyringProtection.standard(), deterministicRandom(randomSeed))) {
KeyringStore ignored = KeyringStore.create(path, password, KeyringProtection.standard(),
deterministicRandom(randomSeed))) {
// Creation writes the initial durable reservation.
}
return path;
@@ -454,8 +439,7 @@ class KeyringNonceReservationTest {
}
private static int indexOf(byte[] haystack, byte[] needle) {
outer:
for (int index = 0; index <= haystack.length - needle.length; index++) {
outer: for (int index = 0; index <= haystack.length - needle.length; index++) {
for (int offset = 0; offset < needle.length; offset++) {
if (haystack[index + offset] != needle[offset]) {
continue outer;

View File

@@ -39,8 +39,8 @@ class KeyringStoreTest {
KeyPair pair = generator.generateKeyPair();
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(path, password,
KeyringProtection.standard(), deterministicRandom())) {
KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(),
deterministicRandom())) {
store.putPublic("rsa", "RSA", pair.getPublic());
store.putPrivate("rsa", "RSA", pair.getPrivate());
store.putSecret("aes", "AES", aes);
@@ -64,13 +64,11 @@ class KeyringStoreTest {
void wrongPasswordAndCorruptionFailUniformly() throws Exception {
start("wrongPasswordAndCorruptionFailUniformly");
Path path = temporaryDirectory.resolve("wrong.zek");
try (KeyringPassword password = password();
KeyringStore ignored = KeyringStore.create(path, password)) {
try (KeyringPassword password = password(); KeyringStore ignored = KeyringStore.create(path, password)) {
// empty current-format store
}
try (KeyringPassword wrong = new KeyringPassword(new char[] { 'w', 'r', 'o', 'n', 'g' })) {
KeyringException exception = assertThrows(KeyringException.class,
() -> KeyringStore.open(path, wrong));
KeyringException exception = assertThrows(KeyringException.class, () -> KeyringStore.open(path, wrong));
assertEquals(KeyringException.Code.KEYRING_UNLOCK_FAILED, exception.code());
}
byte[] bytes = Files.readAllBytes(path);
@@ -78,8 +76,7 @@ class KeyringStoreTest {
Files.write(path, bytes);
Arrays.fill(bytes, (byte) 0);
try (KeyringPassword password = password()) {
KeyringException exception = assertThrows(KeyringException.class,
() -> KeyringStore.open(path, password));
KeyringException exception = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password));
assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID, exception.code());
}
ok();
@@ -90,13 +87,11 @@ class KeyringStoreTest {
start("plaintextV1AndDuplicateOwnerAreRejected");
Path old = temporaryDirectory.resolve("old.txt");
Files.writeString(old, "# KeyringStore v1\n", StandardCharsets.UTF_8);
Files.setPosixFilePermissions(old, java.util.Set.of(
java.nio.file.attribute.PosixFilePermission.OWNER_READ,
Files.setPosixFilePermissions(old, java.util.Set.of(java.nio.file.attribute.PosixFilePermission.OWNER_READ,
java.nio.file.attribute.PosixFilePermission.OWNER_WRITE));
try (KeyringPassword password = password()) {
assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID,
assertThrows(KeyringException.class,
() -> KeyringStore.open(old, password)).code());
assertThrows(KeyringException.class, () -> KeyringStore.open(old, password)).code());
}
Path path = temporaryDirectory.resolve("owned.zek");
@@ -104,12 +99,10 @@ class KeyringStoreTest {
KeyringStore first = KeyringStore.create(path, password);
KeyringPassword secondPassword = password()) {
assertEquals(KeyringException.Code.KEYRING_ALREADY_OPEN,
assertThrows(KeyringException.class,
() -> KeyringStore.open(path, secondPassword)).code());
assertThrows(KeyringException.class, () -> KeyringStore.open(path, secondPassword)).code());
assertTrue(first.aliases().isEmpty());
}
try (KeyringPassword password = password();
KeyringStore reopened = KeyringStore.open(path, password)) {
try (KeyringPassword password = password(); KeyringStore reopened = KeyringStore.open(path, password)) {
assertTrue(reopened.aliases().isEmpty());
}
ok();
@@ -134,8 +127,7 @@ class KeyringStoreTest {
void trailingDataAndNonExportableKeysAreRejected() throws Exception {
start("trailingDataAndNonExportableKeysAreRejected");
Path path = temporaryDirectory.resolve("strict.zek");
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(path, password)) {
try (KeyringPassword password = password(); KeyringStore store = KeyringStore.create(path, password)) {
SecretKey nonExportable = new SecretKey() {
private static final long serialVersionUID = 1L;
@@ -162,8 +154,7 @@ class KeyringStoreTest {
Files.write(path, new byte[] { 1 }, java.nio.file.StandardOpenOption.APPEND);
try (KeyringPassword password = password()) {
KeyringException exception = assertThrows(KeyringException.class,
() -> KeyringStore.open(path, password));
KeyringException exception = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password));
assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID, exception.code());
}
ok();
@@ -175,8 +166,8 @@ class KeyringStoreTest {
Path path = temporaryDirectory.resolve("hmac.zek");
List<String> accepted = List.of("HmacSHA256", "HmacSHA384", "HmacSHA512");
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(path, password,
KeyringProtection.standard(), deterministicRandom())) {
KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(),
deterministicRandom())) {
for (String variant : accepted) {
byte[] material = new byte[64];
Arrays.fill(material, (byte) variant.length());
@@ -190,23 +181,19 @@ class KeyringStoreTest {
}
byte[] before = Files.readAllBytes(path);
try {
for (String rejected : List.of("HmacMD5", "HmacSHA1", "HmacSHA224",
"hmacsha256", "HmacSha384", " HmacSHA512", "HmacSHA512 ",
"BC:HmacSHA256", "", "X".repeat(4097))) {
for (String rejected : List.of("HmacMD5", "HmacSHA1", "HmacSHA224", "hmacsha256", "HmacSha384",
" HmacSHA512", "HmacSHA512 ", "BC:HmacSHA256", "", "X".repeat(4097))) {
SecretKey key = controlledSecret(rejected, new byte[32]);
KeyringException exception = assertThrows(KeyringException.class,
() -> store.putSecret("rejected", "HMAC", key));
assertEquals(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID,
exception.code());
assertEquals(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID.name(),
exception.getMessage());
assertEquals(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID, exception.code());
assertEquals(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID.name(), exception.getMessage());
assertArrayEquals(before, Files.readAllBytes(path));
assertFalse(store.contains("rejected"));
}
SecretKey mismatched = controlledSecret("HmacSHA256", new byte[32]);
assertEquals(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE,
assertThrows(KeyringException.class,
() -> store.putSecret("wrong", "AES", mismatched)).code());
assertThrows(KeyringException.class, () -> store.putSecret("wrong", "AES", mismatched)).code());
assertArrayEquals(before, Files.readAllBytes(path));
} finally {
Arrays.fill(before, (byte) 0);
@@ -219,19 +206,16 @@ class KeyringStoreTest {
void providerDraftAndUnknownHmacVariantAreRejectedStructurally() throws Exception {
start("providerDraftAndUnknownHmacVariantAreRejectedStructurally");
assertEquals(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID,
assertThrows(KeyringException.class,
() -> KeyringImportRegistry.HmacVariant.fromCode(99)).code());
assertThrows(KeyringException.class, () -> KeyringImportRegistry.HmacVariant.fromCode(99)).code());
byte[] staleDraft = staleProviderEntryPlaintext();
try {
java.lang.reflect.Method decoder = KeyringStore.class.getDeclaredMethod(
"decodeEntryPlaintext", byte[].class);
java.lang.reflect.Method decoder = KeyringStore.class.getDeclaredMethod("decodeEntryPlaintext",
byte[].class);
decoder.setAccessible(true);
java.lang.reflect.InvocationTargetException failure =
assertThrows(java.lang.reflect.InvocationTargetException.class,
() -> decoder.invoke(null, (Object) staleDraft));
java.lang.reflect.InvocationTargetException failure = assertThrows(
java.lang.reflect.InvocationTargetException.class, () -> decoder.invoke(null, (Object) staleDraft));
assertTrue(failure.getCause() instanceof KeyringException);
assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID,
((KeyringException) failure.getCause()).code());
assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID, ((KeyringException) failure.getCause()).code());
} finally {
Arrays.fill(staleDraft, (byte) 0);
}
@@ -242,15 +226,13 @@ class KeyringStoreTest {
void providerBoundOrNoncanonicalKeysFailBeforeMutation() throws Exception {
start("providerBoundOrNoncanonicalKeysFailBeforeMutation");
Path path = temporaryDirectory.resolve("provider-bound.zek");
try (KeyringPassword password = password();
KeyringStore store = KeyringStore.create(path, password)) {
try (KeyringPassword password = password(); KeyringStore store = KeyringStore.create(path, password)) {
byte[] before = Files.readAllBytes(path);
try {
SecretKey unsupported = controlledSecret("ProviderAES", new byte[32]);
KeyringException failure = assertThrows(KeyringException.class,
() -> store.putSecret("bad", "AES", unsupported));
assertEquals(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE,
failure.code());
assertEquals(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE, failure.code());
assertArrayEquals(before, Files.readAllBytes(path));
assertFalse(store.contains("bad"));
} finally {
@@ -310,8 +292,7 @@ class KeyringStoreTest {
return bytes.toByteArray();
}
private static void writeString(java.io.DataOutputStream out, String value)
throws java.io.IOException {
private static void writeString(java.io.DataOutputStream out, String value) throws java.io.IOException {
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
try {
out.writeInt(bytes.length);
@@ -322,8 +303,7 @@ class KeyringStoreTest {
}
private static int indexOf(byte[] haystack, byte[] needle) {
outer:
for (int index = 0; index <= haystack.length - needle.length; index++) {
outer: for (int index = 0; index <= haystack.length - needle.length; index++) {
for (int offset = 0; offset < needle.length; offset++) {
if (haystack[index + offset] != needle[offset]) {
continue outer;

View File

@@ -81,10 +81,7 @@ class ZeroEchoSessionDestroyKeyTest {
}
private enum Behavior {
SUCCESS,
FAIL_CHECKED,
FAIL_RUNTIME,
NO_TRANSITION
SUCCESS, FAIL_CHECKED, FAIL_RUNTIME, NO_TRANSITION
}
private static final class TestKey implements Key, Destroyable {

View File

@@ -91,22 +91,26 @@ class HybridKexBuilderTest {
HybridKexTranscript transcript = new HybridKexTranscript().addUtf8("suite", "X25519+ML-KEM-768").addUtf8("role",
"builder-test");
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
HybridKexContext alice = null;
HybridKexContext bob = null;
try {
alice = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcript).classicAgreement()
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate())
alice = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).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(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcript).classicAgreement().algorithm("Xdh")
.spec(XdhSpec.X25519).privateKey(bobClassic.getPrivate()).peerPublic(aliceClassic.getPublic())
.pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate()).buildResponder();
bob = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).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));
@@ -137,21 +141,24 @@ class HybridKexBuilderTest {
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
HybridKexContext alice = null;
HybridKexContext bob = null;
try {
alice = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicPairMessage().algorithm("Xdh")
.spec(XdhSpec.X25519).keyPair(new KeyPairKey(aliceClassic)).pqcKem().algorithm("ML-KEM")
.peerPublic(bobPqc.getPublic()).buildInitiator();
alice = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicPairMessage()
.algorithm("Xdh").spec(XdhSpec.X25519).keyPair(new KeyPairKey(aliceClassic)).pqcKem()
.algorithm("ML-KEM").peerPublic(bobPqc.getPublic()).buildInitiator();
bob = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicPairMessage().algorithm("Xdh").spec(XdhSpec.X25519)
.keyPair(new KeyPairKey(bobClassic)).pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate())
.buildResponder();
bob = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).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));
@@ -183,14 +190,17 @@ class HybridKexBuilderTest {
void buildInitiatorWithoutProfileFails() throws Exception {
System.out.println("buildInitiatorWithoutProfileFails");
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519)
.privateKey(aliceClassic.getPrivate()).peerPublic(bobClassic.getPublic()).pqcKem()
.algorithm("ML-KEM").peerPublic(bobPqc.getPublic()).buildInitiator();
HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).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());
@@ -204,11 +214,12 @@ class HybridKexBuilderTest {
System.out.println("buildInitiatorWithoutClassicModeFails");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic())
.buildInitiator();
HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).pqcKem().algorithm("ML-KEM")
.peerPublic(bobPqc.getPublic()).buildInitiator();
});
System.out.println("...exception=" + exception.getMessage());
@@ -222,13 +233,15 @@ class HybridKexBuilderTest {
System.out.println("buildInitiatorClassicAgreementWithoutPeerPublicFails");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519)
.privateKey(aliceClassic.getPrivate()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic())
.buildInitiator();
HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).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());
@@ -242,11 +255,13 @@ class HybridKexBuilderTest {
System.out.println("buildResponderPairMessageWithoutKeyPairFails");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicPairMessage().algorithm("Xdh").spec(XdhSpec.X25519)
.pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate()).buildResponder();
HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicPairMessage()
.algorithm("Xdh").spec(XdhSpec.X25519).pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate())
.buildResponder();
});
System.out.println("...exception=" + exception.getMessage());
@@ -260,13 +275,15 @@ class HybridKexBuilderTest {
System.out.println("buildInitiatorWithoutPqcPeerPublicFails");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519)
.privateKey(aliceClassic.getPrivate()).peerPublic(bobClassic.getPublic()).pqcKem()
.algorithm("ML-KEM").buildInitiator();
HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).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());
@@ -280,13 +297,15 @@ class HybridKexBuilderTest {
System.out.println("buildResponderWithoutPqcPrivateFails");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519)
.privateKey(bobClassic.getPrivate()).peerPublic(aliceClassic.getPublic()).pqcKem()
.algorithm("ML-KEM").buildResponder();
HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).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());
@@ -302,14 +321,18 @@ class HybridKexBuilderTest {
HybridKexProfile profile = HybridKexProfile.defaultProfile(16);
HybridKexPolicy policy = new HybridKexPolicy(0, 0, 32);
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).policy(policy).classicAgreement().algorithm("Xdh")
.spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate()).peerPublic(bobClassic.getPublic())
.pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic()).buildInitiator();
HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).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());
@@ -328,21 +351,18 @@ class HybridKexBuilderTest {
closedContexts.incrementAndGet();
}
};
ZeroEchoSession session = new ZeroEchoSession()
.withAuditListener(listener)
.withAuditMode(AuditMode.WRAP);
ZeroEchoSession session = new ZeroEchoSession().withAuditListener(listener).withAuditMode(AuditMode.WRAP);
HybridKexProfile profile = HybridKexProfile.defaultProfile(16);
HybridKexPolicy policy = new HybridKexPolicy(0, 0, 32);
KeyPair aliceClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
KeyPair bobPqc = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
assertThrows(IllegalArgumentException.class,
() -> HybridKexBuilder.builder(session).profile(profile).policy(policy).classicAgreement()
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate())
.peerPublic(bobClassic.getPublic()).pqcKem().algorithm("ML-KEM")
.peerPublic(bobPqc.getPublic()).buildInitiator());
.peerPublic(bobClassic.getPublic()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic())
.buildInitiator());
assertEquals(2, closedContexts.get());
System.out.println("...closedContexts=" + closedContexts.get());
@@ -362,21 +382,18 @@ class HybridKexBuilderTest {
ZeroEchoSession keySession = new ZeroEchoSession();
KeyPair aliceClassic = keySession.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = keySession.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = keySession.keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
ZeroEchoSession operationSession = new ZeroEchoSession()
.withAuditListener(listener)
.withAuditMode(AuditMode.WRAP)
.withPolicy((id, role, key, spec) -> {
KeyPair bobPqc = keySession.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
ZeroEchoSession operationSession = new ZeroEchoSession().withAuditListener(listener)
.withAuditMode(AuditMode.WRAP).withPolicy((id, role, key, spec) -> {
if ("ML-KEM".equals(id)) {
throw new IllegalArgumentException("controlled PQ policy denial");
}
});
assertThrows(IllegalArgumentException.class,
() -> HybridKexContexts.initiator(operationSession, HybridKexProfile.defaultProfile(32),
"Xdh", aliceClassic.getPrivate(), bobClassic.getPublic(), XdhSpec.X25519,
"ML-KEM", bobPqc.getPublic(), null));
() -> HybridKexContexts.initiator(operationSession, HybridKexProfile.defaultProfile(32), "Xdh",
aliceClassic.getPrivate(), bobClassic.getPublic(), XdhSpec.X25519, "ML-KEM", bobPqc.getPublic(),
null));
assertEquals(1, closedContexts.get());
System.out.println("...closedContexts=" + closedContexts.get());
@@ -390,21 +407,17 @@ class HybridKexBuilderTest {
AuditListener listener = new AuditListener() {
@Override
public void onContextCreatedMeta(String contextId, String algorithmId, String provider,
zeroecho.core.KeyUsage role, String keyFingerprint,
java.util.Map<String, Object> specMeta) {
zeroecho.core.KeyUsage role, String keyFingerprint, java.util.Map<String, Object> specMeta) {
createdContexts.incrementAndGet();
}
};
ZeroEchoSession session = new ZeroEchoSession()
.withAuditListener(listener)
.withAuditMode(AuditMode.WRAP);
ZeroEchoSession session = new ZeroEchoSession().withAuditListener(listener).withAuditMode(AuditMode.WRAP);
KeyPair aliceClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
assertThrows(NullPointerException.class,
() -> HybridKexContexts.initiator(session, HybridKexProfile.defaultProfile(32),
"Xdh", aliceClassic.getPrivate(), bobClassic.getPublic(), XdhSpec.X25519,
"ML-KEM", null, null));
() -> HybridKexContexts.initiator(session, HybridKexProfile.defaultProfile(32), "Xdh",
aliceClassic.getPrivate(), bobClassic.getPublic(), XdhSpec.X25519, "ML-KEM", null, null));
assertEquals(0, createdContexts.get());
System.out.println("...createdContexts=0");
@@ -416,9 +429,12 @@ class HybridKexBuilderTest {
System.out.println("switchingClassicModeClearsConflictingStateAndBuildsPairMessage");
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair agreementKeyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair pairMessageKeyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair agreementKeyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair pairMessageKeyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric()
.generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
HybridKexContext context = null;
try {
@@ -449,13 +465,19 @@ class HybridKexBuilderTest {
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
KeyPair aliceClassicA = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassicA = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqcA = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair aliceClassicA = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobClassicA = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobPqcA = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
KeyPair aliceClassicB = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassicB = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobPqcB = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair aliceClassicB = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobClassicB = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobPqcB = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
HybridKexTranscript transcriptA = new HybridKexTranscript().addUtf8("context", "A");
HybridKexTranscript transcriptB = new HybridKexTranscript().addUtf8("context", "B");
@@ -469,13 +491,13 @@ class HybridKexBuilderTest {
HybridKexContext bobB = null;
try {
aliceA = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcriptA).classicAgreement()
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassicA.getPrivate())
.peerPublic(bobClassicA.getPublic()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqcA.getPublic())
.buildInitiator();
aliceA = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).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(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcriptA).classicAgreement()
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(bobClassicA.getPrivate())
bobA = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcriptA)
.classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519).privateKey(bobClassicA.getPrivate())
.peerPublic(aliceClassicA.getPublic()).pqcKem().algorithm("ML-KEM").privateKey(bobPqcA.getPrivate())
.buildResponder();
@@ -486,13 +508,13 @@ class HybridKexBuilderTest {
System.out.println("...responderA=" + hex(responderA));
assertArrayEquals(secretA, responderA);
aliceB = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcriptB).classicAgreement()
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassicB.getPrivate())
.peerPublic(bobClassicB.getPublic()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqcB.getPublic())
.buildInitiator();
aliceB = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).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(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcriptB).classicAgreement()
.algorithm("Xdh").spec(XdhSpec.X25519).privateKey(bobClassicB.getPrivate())
bobB = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcriptB)
.classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519).privateKey(bobClassicB.getPrivate())
.peerPublic(aliceClassicB.getPublic()).pqcKem().algorithm("ML-KEM").privateKey(bobPqcB.getPrivate())
.buildResponder();

View File

@@ -179,22 +179,27 @@ public class TagTrailerDataContentBuilderTest {
.generateSecret(AesKeyGenSpec.aes256());
// Ed25519 keys (JCA)
KeyPair ed = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec());
KeyPair ed = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed25519",
Ed25519KeyGenSpec.defaultSpec());
TagEngine<Signature> tagEnc = TagEngineBuilder.ed25519Sign(new zeroecho.sdk.ZeroEchoSession(), ed.getPrivate()).get();
TagEngine<Signature> tagDec = TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic()).get();
TagEngine<Signature> tagEnc = TagEngineBuilder.ed25519Sign(new zeroecho.sdk.ZeroEchoSession(), ed.getPrivate())
.get();
TagEngine<Signature> tagDec = TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic())
.get();
// ENCRYPT: body -> [body||signature] -> AES-GCM
DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg))
.add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192))
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()).build();
.add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192)).add(AesDataContentBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader())
.build();
byte[] ct = readAll(enc.getStream());
System.out.println("...ct=" + ct.length + " bytes");
// DECRYPT: AES-GCM -> strip trailer -> verify Ed25519 at EOF
DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ct))
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader())
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128)
.withHeader())
.add(new TagTrailerDataContentBuilder<>(tagDec).bufferSize(8192).throwOnMismatch()).build();
byte[] pt = readAll(dec.getStream());
@@ -217,23 +222,28 @@ public class TagTrailerDataContentBuilderTest {
// SPHINCS+ key pair via registry (uses default param set from
// SphincsPlusKeyGenSpec)
KeyPair spx = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("SPHINCS+", SphincsPlusKeyGenSpec.defaultSpec());
KeyPair spx = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("SPHINCS+",
SphincsPlusKeyGenSpec.defaultSpec());
// Tag engines (SPHINCS+)
TagEngine<Signature> tagEnc = TagEngineBuilder.sphincsPlusSign(new zeroecho.sdk.ZeroEchoSession(), spx.getPrivate()).get();
TagEngine<Signature> tagDec = TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic()).get();
TagEngine<Signature> tagEnc = TagEngineBuilder
.sphincsPlusSign(new zeroecho.sdk.ZeroEchoSession(), spx.getPrivate()).get();
TagEngine<Signature> tagDec = TagEngineBuilder
.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic()).get();
// ENCRYPT: body -> [body||spxSig] -> AES-GCM
DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg))
.add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192))
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()).build();
.add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192)).add(AesDataContentBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader())
.build();
byte[] ct = readAll(enc.getStream());
System.out.println("...ct=" + ct.length + " bytes");
// DECRYPT: AES-GCM -> strip trailer -> verify SPHINCS+ at EOF
DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ct))
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader())
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128)
.withHeader())
.add(new TagTrailerDataContentBuilder<>(tagDec).bufferSize(8192).throwOnMismatch()).build();
byte[] pt = readAll(dec.getStream());
@@ -255,24 +265,29 @@ public class TagTrailerDataContentBuilderTest {
.generateSecret(AesKeyGenSpec.aes256());
// RSA-2048 keys (use registry for convenience)
KeyPair rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048());
KeyPair rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA",
RsaKeyGenSpec.rsa2048());
// Tag engines (SHA-256, saltLen=32)
RsaSigSpec pss = RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32);
TagEngine<Signature> tagEnc = TagEngineBuilder.rsaSign(new zeroecho.sdk.ZeroEchoSession(), rsa.getPrivate(), pss).get();
TagEngine<Signature> tagDec = TagEngineBuilder.rsaVerify(new zeroecho.sdk.ZeroEchoSession(), rsa.getPublic(), pss).get();
TagEngine<Signature> tagEnc = TagEngineBuilder
.rsaSign(new zeroecho.sdk.ZeroEchoSession(), rsa.getPrivate(), pss).get();
TagEngine<Signature> tagDec = TagEngineBuilder
.rsaVerify(new zeroecho.sdk.ZeroEchoSession(), rsa.getPublic(), pss).get();
// ENCRYPT: body -> [body||pssSig] -> AES-GCM
DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg))
.add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192))
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()).build();
.add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192)).add(AesDataContentBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader())
.build();
byte[] ct = readAll(enc.getStream());
System.out.println("...ct=" + ct.length + " bytes");
// DECRYPT: AES-GCM -> strip trailer -> verify PSS at EOF
DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ct))
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader())
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128)
.withHeader())
.add(new TagTrailerDataContentBuilder<>(tagDec).bufferSize(8192).throwOnMismatch()).build();
byte[] pt = readAll(dec.getStream());
@@ -297,9 +312,12 @@ public class TagTrailerDataContentBuilderTest {
// ENCRYPT: [source] -> [tag trailer] -> [aes gcm]
DataContent encChain = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg))
.add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192))
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()) // writes IV/AAD headers
// into stream
.add(new TagTrailerDataContentBuilder<>(
TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256()))
.bufferSize(8192))
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128)
.withHeader()) // writes IV/AAD headers
// into stream
.build();
byte[] ciphertext = readAll(encChain.getStream());
@@ -307,10 +325,12 @@ public class TagTrailerDataContentBuilderTest {
// DECRYPT: [source(ct)] -> [aes gcm] -> [tag trailer verify]
DataContent decChain = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext))
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()) // reads IV/AAD headers
// back
.add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192)
.throwOnMismatch())
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128)
.withHeader()) // reads IV/AAD headers
// back
.add(new TagTrailerDataContentBuilder<>(
TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256()))
.bufferSize(8192).throwOnMismatch())
.build();
byte[] plain = readAll(decChain.getStream());
@@ -330,12 +350,16 @@ public class TagTrailerDataContentBuilderTest {
msg = Arrays.copyOf(msg, SIZE); // pad deterministic length for the test
System.out.println("...msg=" + msg.length + " bytes");
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048());
KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA",
RsaKeyGenSpec.rsa2048());
// ENCRYPT: [source] -> [tag trailer] -> [rsa/oaep]
DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg))
.add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192))
.add(RsaEncDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).oaep(RsaEncSpec.Hash.SHA256).withPublicKey(kp.getPublic()))
.add(new TagTrailerDataContentBuilder<>(
TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256()))
.bufferSize(8192))
.add(RsaEncDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).oaep(RsaEncSpec.Hash.SHA256)
.withPublicKey(kp.getPublic()))
.build();
byte[] ct = readAll(enc.getStream());
@@ -343,9 +367,11 @@ public class TagTrailerDataContentBuilderTest {
// DECRYPT: [source(ct)] -> [rsa/oaep] -> [tag verify]
DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ct))
.add(RsaEncDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).oaep(RsaEncSpec.Hash.SHA256).withPrivateKey(kp.getPrivate()))
.add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192)
.throwOnMismatch())
.add(RsaEncDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).oaep(RsaEncSpec.Hash.SHA256)
.withPrivateKey(kp.getPrivate()))
.add(new TagTrailerDataContentBuilder<>(
TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256()))
.bufferSize(8192).throwOnMismatch())
.build();
byte[] pt = readAll(dec.getStream());
@@ -379,17 +405,21 @@ public class TagTrailerDataContentBuilderTest {
System.out.println("...msg=" + msg.length + " bytes");
// ENCRYPT: [source] -> [tag trailer] -> [KEM envelope with AES/GCM payload]
AesDataContentBuilder aesEnc = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128) // 128-bit tag
AesDataContentBuilder aesEnc = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128) // 128-bit
// tag
.withHeader(); // carry IV etc.
DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg))
.add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192))
.add(KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId).recipientPublic(kemKeys.getPublic()).derivedKeyBytes(32) // AES-256
// key
// derived
// from
// KEM
// secret
.add(new TagTrailerDataContentBuilder<>(
TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256()))
.bufferSize(8192))
.add(KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId)
.recipientPublic(kemKeys.getPublic()).derivedKeyBytes(32) // AES-256
// key
// derived
// from
// KEM
// secret
.hkdfSha256("KEM-tag-demo".getBytes(java.nio.charset.StandardCharsets.US_ASCII))
.withAes(aesEnc))
.build();
@@ -398,15 +428,17 @@ public class TagTrailerDataContentBuilderTest {
System.out.println("...envelope=" + envelope.length + " bytes");
// DECRYPT: [source(envelope)] -> [KEM] -> [tag verify]
AesDataContentBuilder aesDec = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader();
AesDataContentBuilder aesDec = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128)
.withHeader();
DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(envelope))
.add(KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId).recipientPrivate(kemKeys.getPrivate())
.derivedKeyBytes(32)
.add(KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId)
.recipientPrivate(kemKeys.getPrivate()).derivedKeyBytes(32)
.hkdfSha256("KEM-tag-demo".getBytes(java.nio.charset.StandardCharsets.US_ASCII))
.withAes(aesDec))
.add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192)
.throwOnMismatch())
.add(new TagTrailerDataContentBuilder<>(
TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256()))
.bufferSize(8192).throwOnMismatch())
.build();
byte[] pt = readAll(dec.getStream());
@@ -430,26 +462,34 @@ public class TagTrailerDataContentBuilderTest {
// --- recipients ---
// RSA
KeyPair rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048());
KeyPair rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA",
RsaKeyGenSpec.rsa2048());
// ML-KEM (Kyber768 as a good mid-level)
KeyPair kem = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair kem = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
// --- symmetric payload (AES-256/GCM, tag 128) ---
// IV length is handled internally (12 bytes for GCM) and persisted via header.
AesDataContentBuilder aesEnc = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader(); // write
// IV/tagBits/AAD-hash
// header for decrypt
// side
AesDataContentBuilder aesEnc = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128)
.withHeader(); // write
// IV/tagBits/AAD-hash
// header for decrypt
// side
// --- tag trailer (SHA-256 digest as a trailer) ---
TagTrailerDataContentBuilder<byte[]> tagEnc = new TagTrailerDataContentBuilder<>(
TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192);
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic());
KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, kem.getPublic());
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT,
rsa.getPublic());
KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE,
kem.getPublic());
// --- envelope (ENCRYPT) with 3 recipients ---
MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesEnc)
MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesEnc)
// .addRsaOaepRecipient(rsa.getPublic()) // RSA-OAEP with SHA-256 MGF1
// .addKemRecipient("ML-KEM", kem.getPublic(), 32 /* kekBytes */, 16 /*
// hkdfSaltLen */)
@@ -467,36 +507,48 @@ public class TagTrailerDataContentBuilderTest {
// -------------- Decrypt three ways on the same ciphertext --------------
// a) by RSA private key
AesDataContentBuilder aesDecRsa = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader(); // read header to
// recover
// IV/tagBits
MultiRecipientDataSourceBuilder envDecRsa = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesDecRsa)
.unlockWith(new UnlockMaterial.Private(rsa.getPrivate()));
AesDataContentBuilder aesDecRsa = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128)
.withHeader(); // read header to
// recover
// IV/tagBits
MultiRecipientDataSourceBuilder envDecRsa = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesDecRsa).unlockWith(new UnlockMaterial.Private(rsa.getPrivate()));
byte[] ptRsa = readAll(DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(encrypted)).add(envDecRsa)
.add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192)
.throwOnMismatch())
.add(new TagTrailerDataContentBuilder<>(
TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256()))
.bufferSize(8192).throwOnMismatch())
.build().getStream());
System.out.println("...decrypted(RSA)=" + ptRsa.length);
assertArrayEquals(msg, ptRsa, "RSA path failed to recover the original");
// b) by KEM private key
AesDataContentBuilder aesDecKem = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader();
MultiRecipientDataSourceBuilder envDecKem = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesDecKem)
.unlockWith(new UnlockMaterial.Private(kem.getPrivate()));
AesDataContentBuilder aesDecKem = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128)
.withHeader();
MultiRecipientDataSourceBuilder envDecKem = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesDecKem).unlockWith(new UnlockMaterial.Private(kem.getPrivate()));
byte[] ptKem = readAll(DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(encrypted)).add(envDecKem)
.add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192)
.throwOnMismatch())
.add(new TagTrailerDataContentBuilder<>(
TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256()))
.bufferSize(8192).throwOnMismatch())
.build().getStream());
System.out.println("...decrypted(KEM)=" + ptKem.length);
assertArrayEquals(msg, ptKem, "KEM path failed to recover the original");
// c) by password
AesDataContentBuilder aesDecPwd = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader();
MultiRecipientDataSourceBuilder envDecPwd = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesDecPwd)
.unlockWith(new UnlockMaterial.Password(PASSWORD));
AesDataContentBuilder aesDecPwd = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128)
.withHeader();
MultiRecipientDataSourceBuilder envDecPwd = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesDecPwd).unlockWith(new UnlockMaterial.Password(PASSWORD));
byte[] ptPwd = readAll(DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(encrypted)).add(envDecPwd)
.add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192)
.throwOnMismatch())
.add(new TagTrailerDataContentBuilder<>(
TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256()))
.bufferSize(8192).throwOnMismatch())
.build().getStream());
System.out.println("...decrypted(PASSWORD)=" + ptPwd.length);
assertArrayEquals(msg, ptPwd, "Password path failed to recover the original");
@@ -516,10 +568,12 @@ public class TagTrailerDataContentBuilderTest {
byte[] msg = random(SIZE);
System.out.println("...input=" + msg.length);
KeyPair rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048());
KeyPair rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA",
RsaKeyGenSpec.rsa2048());
// AES-256/CBC with header so IV/params are serialized by the AES stage
AesDataContentBuilder aesCbc = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5().withHeader();
AesDataContentBuilder aesCbc = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5()
.withHeader();
TagTrailerDataContentBuilder<byte[]> tagEnc = new TagTrailerDataContentBuilder<>(
TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192);
@@ -529,10 +583,14 @@ public class TagTrailerDataContentBuilderTest {
// explicit for clarity
.throwOnMismatch();
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic());
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT,
rsa.getPublic());
// Envelope: recipient table (RSA-OAEP) + AES payload (CBC/PKCS7 with header)
MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc)
MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesCbc)
// CEK length for AES-256
.payloadKeyBytes(32)
// .addRsaOaepRecipient(rsa.getPublic()); old API
@@ -545,7 +603,9 @@ public class TagTrailerDataContentBuilderTest {
byte[] encrypted = readAll(encTail.getStream());
System.out.println("...encrypted=" + encrypted.length);
MultiRecipientDataSourceBuilder envDec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
MultiRecipientDataSourceBuilder envDec = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5()
// must match encrypt side
.withHeader())
@@ -577,23 +637,28 @@ public class TagTrailerDataContentBuilderTest {
.generateSecret(AesKeyGenSpec.aes256());
// ECDSA P-256 keys (via your unified ECDSA algorithm)
KeyPair ecdsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ECDSA", zeroecho.core.alg.ecdsa.EcdsaCurveSpec.P256);
KeyPair ecdsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ECDSA",
zeroecho.core.alg.ecdsa.EcdsaCurveSpec.P256);
// Tag engines (ECDSA/P-256 using P1363 format, fixed 64-byte tag)
TagEngine<Signature> tagEnc = TagEngineBuilder.ecdsaP256Sign(new zeroecho.sdk.ZeroEchoSession(), ecdsa.getPrivate()).get();
TagEngine<Signature> tagDec = TagEngineBuilder.ecdsaP256Verify(new zeroecho.sdk.ZeroEchoSession(), ecdsa.getPublic()).get();
TagEngine<Signature> tagEnc = TagEngineBuilder
.ecdsaP256Sign(new zeroecho.sdk.ZeroEchoSession(), ecdsa.getPrivate()).get();
TagEngine<Signature> tagDec = TagEngineBuilder
.ecdsaP256Verify(new zeroecho.sdk.ZeroEchoSession(), ecdsa.getPublic()).get();
// ENCRYPT: body -> [body||ecdsaSig] -> AES-GCM
DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg))
.add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192))
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()).build();
.add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192)).add(AesDataContentBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader())
.build();
byte[] ct = readAll(enc.getStream());
System.out.println("...ct=" + ct.length + " bytes");
// DECRYPT: AES-GCM -> strip trailer -> verify ECDSA at EOF
DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ct))
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader())
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128)
.withHeader())
.add(new TagTrailerDataContentBuilder<>(tagDec).bufferSize(8192).throwOnMismatch()).build();
byte[] pt = readAll(dec.getStream());

View File

@@ -243,25 +243,29 @@ class KemHybridRoundTripTest {
}
private static DataContent encryptStage(String kemId, KeyPair kp, String mode) {
KemDataContentBuilder kem = KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId).recipientPublic(kp.getPublic())
.derivedKeyBytes(32); // AES-256 or ChaCha20 key
KemDataContentBuilder kem = KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId)
.recipientPublic(kp.getPublic()).derivedKeyBytes(32); // AES-256 or ChaCha20 key
switch (mode) {
case "GCM": {
AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader().withAad(AAD);
AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeGcm(128).withHeader().withAad(AAD);
return kem.withAes(aes).build(true);
}
case "CBC": {
AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5().withHeader();
AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeCbcPkcs5().withHeader();
return kem.withAes(aes).build(true);
}
case "CTR": {
AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCtr().withHeader();
AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCtr()
.withHeader();
return kem.withAes(aes).build(true);
}
case "CHACHA20-POLY1305": {
ChaChaDataContentBuilder ch = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withAad(AAD) // non-empty → AEAD
// variant
ChaChaDataContentBuilder ch = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.withAad(AAD) // non-empty → AEAD
// variant
.withHeader(); // carry nonce
return kem.withChaCha(ch).build(true);
}
@@ -271,24 +275,28 @@ class KemHybridRoundTripTest {
}
private static DataContent decryptStage(String kemId, KeyPair kp, String mode) {
KemDataContentBuilder kem = KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId).recipientPrivate(kp.getPrivate())
.derivedKeyBytes(32);
KemDataContentBuilder kem = KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId)
.recipientPrivate(kp.getPrivate()).derivedKeyBytes(32);
switch (mode) {
case "GCM": {
AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader().withAad(AAD);
AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeGcm(128).withHeader().withAad(AAD);
return kem.withAes(aes).build(false);
}
case "CBC": {
AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5().withHeader();
AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeCbcPkcs5().withHeader();
return kem.withAes(aes).build(false);
}
case "CTR": {
AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCtr().withHeader();
AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCtr()
.withHeader();
return kem.withAes(aes).build(false);
}
case "CHACHA20-POLY1305": {
ChaChaDataContentBuilder ch = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withAad(AAD).withHeader();
ChaChaDataContentBuilder ch = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.withAad(AAD).withHeader();
return kem.withChaCha(ch).build(false);
}
default:

View File

@@ -42,15 +42,12 @@ class SessionBoundBuilderTest {
void policyDenialOccursBeforeContextCreation() {
System.out.println("policyDenialOccursBeforeContextCreation");
RecordingListener listener = new RecordingListener();
ZeroEchoSession session = new ZeroEchoSession()
.withAuditListener(listener)
ZeroEchoSession session = new ZeroEchoSession().withAuditListener(listener)
.withPolicy((id, role, key, spec) -> {
throw new IllegalArgumentException("controlled policy denial");
});
DataContent encryption = AesDataContentBuilder.builder(session)
.withKey(new SecretKeySpec(new byte[16], "AES"))
.modeGcm(128)
.build(true);
DataContent encryption = AesDataContentBuilder.builder(session).withKey(new SecretKeySpec(new byte[16], "AES"))
.modeGcm(128).build(true);
encryption.setInput(new PlainBytes(new byte[] { 1 }));
assertThrows(IllegalArgumentException.class, encryption::getStream);
@@ -63,13 +60,9 @@ class SessionBoundBuilderTest {
void wrappedContextUsesBuilderSessionAuditConfiguration() throws Exception {
System.out.println("wrappedContextUsesBuilderSessionAuditConfiguration");
RecordingListener listener = new RecordingListener();
ZeroEchoSession session = new ZeroEchoSession()
.withAuditListener(listener)
.withAuditMode(AuditMode.WRAP);
DataContent encryption = AesDataContentBuilder.builder(session)
.withKey(new SecretKeySpec(new byte[16], "AES"))
.modeGcm(128)
.build(true);
ZeroEchoSession session = new ZeroEchoSession().withAuditListener(listener).withAuditMode(AuditMode.WRAP);
DataContent encryption = AesDataContentBuilder.builder(session).withKey(new SecretKeySpec(new byte[16], "AES"))
.modeGcm(128).build(true);
encryption.setInput(new PlainBytes(new byte[] { 1, 2, 3 }));
try (InputStream input = encryption.getStream()) {

View File

@@ -58,8 +58,8 @@ class DecryptorCekCleanupTest {
System.out.println("acceptedCekIsClearedWhenPayloadSetupFails");
byte[] accepted = new byte[16];
RecipientOpener opener = (entryId, entryBlob, material) -> accepted;
Decryptor decryptor = new Decryptor(List.of(opener), new UnlockMaterial.Password(new char[] { 'p' }),
null, null, 16, 4, 128);
Decryptor decryptor = new Decryptor(List.of(opener), new UnlockMaterial.Password(new char[] { 'p' }), null,
null, 16, 4, 128);
decryptor.setInput(() -> new ByteArrayInputStream(envelopeHeader()));
assertThrows(NullPointerException.class, decryptor::getStream);
@@ -104,8 +104,7 @@ class DecryptorCekCleanupTest {
private static Decryptor decryptor(RecipientOpener opener) {
AesDataContentBuilder aes = AesDataContentBuilder.builder(new ZeroEchoSession()).withHeader().modeGcm(128);
return new Decryptor(List.of(opener), new UnlockMaterial.Password(new char[] { 'p' }),
aes, null, 16, 4, 128);
return new Decryptor(List.of(opener), new UnlockMaterial.Password(new char[] { 'p' }), aes, null, 16, 4, 128);
}
private static byte[] envelopeHeader() throws IOException {

View File

@@ -83,8 +83,8 @@ class EncryptorCekAllocationTest {
void recipientLimitIsCheckedBeforeCekGeneration() {
System.out.print("EncryptorCekAllocation/recipientLimitIsCheckedBeforeCekGeneration...");
RecordingRandomFactory randomFactory = new RecordingRandomFactory();
Encryptor encryptor = newEncryptor(List.of(new CapturingRecipient(false), new CapturingRecipient(false)),
1, randomFactory);
Encryptor encryptor = newEncryptor(List.of(new CapturingRecipient(false), new CapturingRecipient(false)), 1,
randomFactory);
encryptor.setInput(() -> new ByteArrayInputStream(new byte[0]));
assertThrows(IOException.class, encryptor::getStream);
@@ -97,8 +97,8 @@ class EncryptorCekAllocationTest {
void recipientFailureClearsGenuineAndDecoyCeks() {
System.out.print("EncryptorCekAllocation/recipientFailureClearsGenuineAndDecoyCeks...");
RecordingRandomFactory randomFactory = new RecordingRandomFactory();
Encryptor encryptor = newEncryptor(List.of(new CapturingRecipient(false), new FailingRecipient(true)),
8, randomFactory);
Encryptor encryptor = newEncryptor(List.of(new CapturingRecipient(false), new FailingRecipient(true)), 8,
randomFactory);
encryptor.setInput(() -> new ByteArrayInputStream(new byte[0]));
assertThrows(IOException.class, encryptor::getStream);
@@ -124,8 +124,8 @@ class EncryptorCekAllocationTest {
void failedRecipientProcessingDestroysOwnedPassword() {
System.out.print("EncryptorCekAllocation/failedRecipientProcessingDestroysOwnedPassword...");
PasswordRecipient passwordRecipient = passwordRecipient();
Encryptor encryptor = newEncryptor(List.of(passwordRecipient, new FailingRecipient(false)),
8, new RecordingRandomFactory());
Encryptor encryptor = newEncryptor(List.of(passwordRecipient, new FailingRecipient(false)), 8,
new RecordingRandomFactory());
encryptor.setInput(() -> new ByteArrayInputStream(new byte[0]));
assertThrows(IOException.class, encryptor::getStream);
@@ -143,8 +143,8 @@ class EncryptorCekAllocationTest {
private static Encryptor newEncryptor(List<Recipient> recipients, int maxRecipients,
IntFunction<byte[]> randomFactory) {
return new Encryptor(recipients, AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128), null, KEY_BYTES,
maxRecipients, 1024, randomFactory);
return new Encryptor(recipients, AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128),
null, KEY_BYTES, maxRecipients, 1024, randomFactory);
}
private static byte[] repeated(byte value) {

View File

@@ -49,27 +49,23 @@ class KemRecipientLifecycleTest {
void constructorsAndBuilderRejectUnsupportedKekBeforeOwnershipTransfer() throws Exception {
System.out.println("constructorsAndBuilderRejectUnsupportedKekBeforeOwnershipTransfer");
int[] invalidValues = { -1, 0, 1, 15, 17, 24, 31, 33, Integer.MAX_VALUE };
ZeroEchoSession session = new ZeroEchoSession()
.withPbkdf2Limits(new Pbkdf2Limits(20_000, 30_000));
ZeroEchoSession session = new ZeroEchoSession().withPbkdf2Limits(new Pbkdf2Limits(20_000, 30_000));
MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session)
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader());
for (int kekBytes : invalidValues) {
ControlledKemContext direct = new ControlledKemContext(filled(32, (byte) 0x11));
assertThrows(IllegalArgumentException.class,
() -> new KemCtxRecipient(direct, kekBytes, 16));
assertThrows(IllegalArgumentException.class, () -> new KemCtxRecipient(direct, kekBytes, 16));
assertFalse(direct.closed);
direct.close();
ControlledKemContext normal = new ControlledKemContext(filled(32, (byte) 0x22));
assertThrows(IllegalArgumentException.class,
() -> builder.addRecipient(normal, kekBytes, 16));
assertThrows(IllegalArgumentException.class, () -> builder.addRecipient(normal, kekBytes, 16));
assertEquals(0, recipients(builder).size());
assertFalse(normal.closed);
normal.close();
ControlledKemContext decoy = new ControlledKemContext(filled(32, (byte) 0x33));
assertThrows(IllegalArgumentException.class,
() -> builder.addRecipientDecoy(decoy, kekBytes, 16));
assertThrows(IllegalArgumentException.class, () -> builder.addRecipientDecoy(decoy, kekBytes, 16));
assertEquals(0, recipients(builder).size());
assertFalse(decoy.closed);
decoy.close();

View File

@@ -103,11 +103,14 @@ public class MultiRecipientEnvelopeTest {
final char[] password = "CorrectHorseBatteryStaple".toCharArray();
// AES-256-GCM with header so IV/tag are persisted in-band
Supplier<AesDataContentBuilder> aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader();
Supplier<AesDataContentBuilder> aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeGcm(128).withHeader();
// Encrypt
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get())
.payloadKeyBytes(32)
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32)
.addPasswordRecipient(password, /* iterations */ 10000, /* saltLen */ 16, /* kekBytes */ 32);
DataContent encryptor = enc.build(true);
@@ -120,8 +123,10 @@ public class MultiRecipientEnvelopeTest {
// Decrypt
UnlockMaterial.Password unlockMaterial = new UnlockMaterial.Password(password);
MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get())
.payloadKeyBytes(32).unlockWith(unlockMaterial);
MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32).unlockWith(unlockMaterial);
DataContent decryptor = dec.build(false);
decryptor.setInput(new BytesContent(encrypted));
@@ -144,15 +149,12 @@ public class MultiRecipientEnvelopeTest {
System.out.println("passwordRecipientWithAes128KekRoundTrips");
byte[] input = randomInput(257);
char[] password = "controlled-password".toCharArray();
ZeroEchoSession session = new ZeroEchoSession()
.withPbkdf2Limits(new Pbkdf2Limits(1_000_000, 1_000_000));
ZeroEchoSession session = new ZeroEchoSession().withPbkdf2Limits(new Pbkdf2Limits(1_000_000, 1_000_000));
byte[] encrypted;
try (MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session)
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader())
.payloadKeyBytes(32)
.addPasswordRecipient(password, 10_000, 16, 16);
MultiRecipientContent content = builder.build(true)) {
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()).payloadKeyBytes(32)
.addPasswordRecipient(password, 10_000, 16, 16); MultiRecipientContent content = builder.build(true)) {
content.setInput(new BytesContent(input));
try (InputStream stream = content.getStream()) {
encrypted = stream.readAllBytes();
@@ -162,10 +164,8 @@ public class MultiRecipientEnvelopeTest {
UnlockMaterial.Password unlock = new UnlockMaterial.Password(password);
byte[] decrypted;
try (MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session)
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader())
.payloadKeyBytes(32)
.unlockWith(unlock);
MultiRecipientContent content = builder.build(false)) {
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()).payloadKeyBytes(32)
.unlockWith(unlock); MultiRecipientContent content = builder.build(false)) {
content.setInput(new BytesContent(encrypted));
try (InputStream stream = content.getStream()) {
decrypted = stream.readAllBytes();
@@ -192,10 +192,13 @@ public class MultiRecipientEnvelopeTest {
final char[] password = "Tr0ub4dor&3".toCharArray();
// AES-256-CBC with PKCS7 padding, header persists IV
Supplier<AesDataContentBuilder> aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5().withHeader();
Supplier<AesDataContentBuilder> aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeCbcPkcs5().withHeader();
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc.get())
.payloadKeyBytes(32)
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesCbc.get()).payloadKeyBytes(32)
.addPasswordRecipient(password, /* iterations */ 10000, /* saltLen */ 16, /* kekBytes */ 32);
DataContent encryptor = enc.build(true);
@@ -206,8 +209,10 @@ public class MultiRecipientEnvelopeTest {
encrypted = readAllBytesAndPrint(es, "... encrypted size");
}
MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc.get())
.payloadKeyBytes(32).unlockWith(new UnlockMaterial.Password(password));
MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesCbc.get()).payloadKeyBytes(32).unlockWith(new UnlockMaterial.Password(password));
DataContent decryptor = dec.build(false);
decryptor.setInput(new BytesContent(encrypted));
@@ -234,13 +239,18 @@ public class MultiRecipientEnvelopeTest {
final byte[] input = randomInput(128 * 1024 + 7);
System.out.println("... input size: " + input.length);
KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048());
EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, elg.getPublic());
KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal",
ElgamalParamSpec.ffdhe2048());
EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT,
elg.getPublic());
Supplier<AesDataContentBuilder> aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader();
Supplier<AesDataContentBuilder> aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeGcm(128).withHeader();
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get())
.payloadKeyBytes(32).addRecipient(elgEnc);
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32).addRecipient(elgEnc);
DataContent encryptor = enc.build(true);
encryptor.setInput(new BytesContent(input));
@@ -250,8 +260,10 @@ public class MultiRecipientEnvelopeTest {
encrypted = readAllBytesAndPrint(es, "... encrypted size");
}
MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get())
.payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(elg.getPrivate()));
MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(elg.getPrivate()));
DataContent decryptor = dec.build(false);
decryptor.setInput(new BytesContent(encrypted));
@@ -274,13 +286,18 @@ public class MultiRecipientEnvelopeTest {
final byte[] input = randomInput(128 * 1024 + 13); // cross blocks
System.out.println("... input size: " + input.length);
KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048());
EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, elg.getPublic());
KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal",
ElgamalParamSpec.ffdhe2048());
EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT,
elg.getPublic());
Supplier<AesDataContentBuilder> aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5().withHeader();
Supplier<AesDataContentBuilder> aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeCbcPkcs5().withHeader();
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc.get())
.payloadKeyBytes(32).addRecipient(elgEnc);
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesCbc.get()).payloadKeyBytes(32).addRecipient(elgEnc);
DataContent encryptor = enc.build(true);
encryptor.setInput(new BytesContent(input));
@@ -290,8 +307,10 @@ public class MultiRecipientEnvelopeTest {
encrypted = readAllBytesAndPrint(es, "... encrypted size");
}
MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc.get())
.payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(elg.getPrivate()));
MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesCbc.get()).payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(elg.getPrivate()));
DataContent decryptor = dec.build(false);
decryptor.setInput(new BytesContent(encrypted));
@@ -322,12 +341,16 @@ public class MultiRecipientEnvelopeTest {
kpg.initialize(3072, new SecureRandom());
KeyPair rsa = kpg.generateKeyPair();
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic());
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT,
rsa.getPublic());
Supplier<AesDataContentBuilder> aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader();
Supplier<AesDataContentBuilder> aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeGcm(128).withHeader();
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get())
.payloadKeyBytes(32).addRecipient(rsaEnc);
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32).addRecipient(rsaEnc);
DataContent encryptor = enc.build(true);
encryptor.setInput(new BytesContent(input));
@@ -337,8 +360,10 @@ public class MultiRecipientEnvelopeTest {
encrypted = readAllBytesAndPrint(es, "... encrypted size");
}
MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get())
.payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(rsa.getPrivate()));
MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(rsa.getPrivate()));
DataContent decryptor = dec.build(false);
decryptor.setInput(new BytesContent(encrypted));
@@ -365,12 +390,16 @@ public class MultiRecipientEnvelopeTest {
kpg.initialize(3072, new SecureRandom());
KeyPair rsa = kpg.generateKeyPair();
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic());
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT,
rsa.getPublic());
Supplier<AesDataContentBuilder> aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5().withHeader();
Supplier<AesDataContentBuilder> aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeCbcPkcs5().withHeader();
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc.get())
.payloadKeyBytes(32).addRecipient(rsaEnc);
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesCbc.get()).payloadKeyBytes(32).addRecipient(rsaEnc);
DataContent encryptor = enc.build(true);
encryptor.setInput(new BytesContent(input));
@@ -380,8 +409,10 @@ public class MultiRecipientEnvelopeTest {
encrypted = readAllBytesAndPrint(es, "... encrypted size");
}
MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc.get())
.payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(rsa.getPrivate()));
MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesCbc.get()).payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(rsa.getPrivate()));
DataContent decryptor = dec.build(false);
decryptor.setInput(new BytesContent(encrypted));
@@ -403,12 +434,12 @@ public class MultiRecipientEnvelopeTest {
void testKemRecipientWith128BitKekRoundTrip() throws Exception {
System.out.println("testKemRecipientWith128BitKekRoundTrip");
byte[] input = randomInput(257);
KeyPair keyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric()
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512());
KeyPair keyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber512());
byte[] encrypted = encryptKemRecipients(input, new KeyPair[] { keyPair }, new int[] { 16 });
decryptAndAssert("...128-bit KEK", aesGcmSupplier(), new UnlockMaterial.Private(keyPair.getPrivate()),
input, encrypted);
decryptAndAssert("...128-bit KEK", aesGcmSupplier(), new UnlockMaterial.Private(keyPair.getPrivate()), input,
encrypted);
System.out.println("...encryptedLength=" + encrypted.length);
System.out.println("testKemRecipientWith128BitKekRoundTrip...ok");
@@ -418,17 +449,15 @@ public class MultiRecipientEnvelopeTest {
void testMixedKemRecipientKekSizesRoundTrip() throws Exception {
System.out.println("testMixedKemRecipientKekSizesRoundTrip");
zeroecho.sdk.ZeroEchoSession session = new zeroecho.sdk.ZeroEchoSession();
KeyPair first = session.keyBuilders().asymmetric()
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512());
KeyPair second = session.keyBuilders().asymmetric()
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512());
KeyPair first = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512());
KeyPair second = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512());
byte[] input = randomInput(513);
byte[] encrypted = encryptKemRecipients(input, new KeyPair[] { first, second }, new int[] { 16, 32 });
decryptAndAssert("...mixed 16-byte KEK", aesGcmSupplier(),
new UnlockMaterial.Private(first.getPrivate()), input, encrypted);
decryptAndAssert("...mixed 32-byte KEK", aesGcmSupplier(),
new UnlockMaterial.Private(second.getPrivate()), input, encrypted);
decryptAndAssert("...mixed 16-byte KEK", aesGcmSupplier(), new UnlockMaterial.Private(first.getPrivate()),
input, encrypted);
decryptAndAssert("...mixed 32-byte KEK", aesGcmSupplier(), new UnlockMaterial.Private(second.getPrivate()),
input, encrypted);
System.out.println("...recipientCount=2");
System.out.println("testMixedKemRecipientKekSizesRoundTrip...ok");
@@ -438,22 +467,16 @@ public class MultiRecipientEnvelopeTest {
void defaultKemOpenerContinuesAfterSameAlgorithmDecoy() throws Exception {
System.out.println("defaultKemOpenerContinuesAfterSameAlgorithmDecoy");
ZeroEchoSession session = new ZeroEchoSession();
KeyPair decoy = session.keyBuilders().asymmetric()
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512());
KeyPair legitimate = session.keyBuilders().asymmetric()
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512());
KeyPair decoy = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512());
KeyPair legitimate = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512());
byte[] input = randomInput(385);
byte[] encrypted;
KemContext decoyContext =
session.createContext("ML-KEM", KeyUsage.ENCAPSULATE, decoy.getPublic());
KemContext legitimateContext =
session.createContext("ML-KEM", KeyUsage.ENCAPSULATE, legitimate.getPublic());
KemContext decoyContext = session.createContext("ML-KEM", KeyUsage.ENCAPSULATE, decoy.getPublic());
KemContext legitimateContext = session.createContext("ML-KEM", KeyUsage.ENCAPSULATE, legitimate.getPublic());
try (MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session)
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader())
.payloadKeyBytes(32)
.addRecipient(decoyContext, 32, 16)
.addRecipient(legitimateContext, 32, 16);
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()).payloadKeyBytes(32)
.addRecipient(decoyContext, 32, 16).addRecipient(legitimateContext, 32, 16);
MultiRecipientContent content = builder.build(true)) {
content.setInput(new BytesContent(input));
try (InputStream stream = content.getStream()) {
@@ -463,8 +486,7 @@ public class MultiRecipientEnvelopeTest {
byte[] decrypted;
try (MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session)
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader())
.payloadKeyBytes(32)
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()).payloadKeyBytes(32)
.unlockWith(new UnlockMaterial.Private(legitimate.getPrivate()));
MultiRecipientContent content = builder.build(false)) {
content.setInput(new BytesContent(encrypted));
@@ -482,22 +504,16 @@ public class MultiRecipientEnvelopeTest {
void defaultEncryptionOpenerContinuesAfterSameAlgorithmDecoy() throws Exception {
System.out.println("defaultEncryptionOpenerContinuesAfterSameAlgorithmDecoy");
ZeroEchoSession session = new ZeroEchoSession();
KeyPair decoy = session.keyBuilders().asymmetric()
.generateKeyPair("RSA", RsaKeyGenSpec.rsa2048());
KeyPair legitimate = session.keyBuilders().asymmetric()
.generateKeyPair("RSA", RsaKeyGenSpec.rsa2048());
KeyPair decoy = session.keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048());
KeyPair legitimate = session.keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048());
byte[] input = randomInput(385);
byte[] encrypted;
EncryptionContext decoyContext =
session.createContext("RSA", KeyUsage.ENCRYPT, decoy.getPublic());
EncryptionContext legitimateContext =
session.createContext("RSA", KeyUsage.ENCRYPT, legitimate.getPublic());
EncryptionContext decoyContext = session.createContext("RSA", KeyUsage.ENCRYPT, decoy.getPublic());
EncryptionContext legitimateContext = session.createContext("RSA", KeyUsage.ENCRYPT, legitimate.getPublic());
try (MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session)
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader())
.payloadKeyBytes(32)
.addRecipient(decoyContext)
.addRecipient(legitimateContext);
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()).payloadKeyBytes(32)
.addRecipient(decoyContext).addRecipient(legitimateContext);
MultiRecipientContent content = builder.build(true)) {
content.setInput(new BytesContent(input));
try (InputStream stream = content.getStream()) {
@@ -507,8 +523,7 @@ public class MultiRecipientEnvelopeTest {
byte[] decrypted;
try (MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session)
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader())
.payloadKeyBytes(32)
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()).payloadKeyBytes(32)
.unlockWith(new UnlockMaterial.Private(legitimate.getPrivate()));
MultiRecipientContent content = builder.build(false)) {
content.setInput(new BytesContent(encrypted));
@@ -537,17 +552,25 @@ public class MultiRecipientEnvelopeTest {
kpg.initialize(3072, new SecureRandom());
KeyPair rsa = kpg.generateKeyPair();
KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512());
KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048());
KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber512());
KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal",
ElgamalParamSpec.ffdhe2048());
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic());
EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, elg.getPublic());
KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, kyber.getPublic());
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT,
rsa.getPublic());
EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT,
elg.getPublic());
KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE,
kyber.getPublic());
Supplier<AesDataContentBuilder> aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader();
Supplier<AesDataContentBuilder> aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeGcm(128).withHeader();
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get())
.payloadKeyBytes(32)
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32)
.addPasswordRecipient(password, /* iterations */ 10000, /* saltLen */ 16, /* kekBytes */ 32)
.addRecipient(rsaEnc).addRecipient(elgEnc).addRecipient(kybKem, /* kekBytes */ 32, /* saltLen */ 32);
@@ -585,18 +608,27 @@ public class MultiRecipientEnvelopeTest {
kpg.initialize(3072, new SecureRandom());
KeyPair rsa = kpg.generateKeyPair();
rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048());
KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048());
rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA",
RsaKeyGenSpec.rsa2048());
KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal",
ElgamalParamSpec.ffdhe2048());
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic());
EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, elg.getPublic());
KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, kyber.getPublic());
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT,
rsa.getPublic());
EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT,
elg.getPublic());
KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE,
kyber.getPublic());
Supplier<AesDataContentBuilder> aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5().withHeader();
Supplier<AesDataContentBuilder> aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeCbcPkcs5().withHeader();
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc.get())
.payloadKeyBytes(32)
MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesCbc.get()).payloadKeyBytes(32)
.addPasswordRecipient(password, /* iterations */ 10000, /* saltLen */ 16, /* kekBytes */ 32)
.addRecipient(rsaEnc).addRecipient(elgEnc).addRecipient(kybKem, /* kekBytes */ 32, /* saltLen */ 32);
@@ -636,23 +668,32 @@ public class MultiRecipientEnvelopeTest {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(3072, new SecureRandom());
KeyPair rsa = kpg.generateKeyPair();
KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048());
KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal",
ElgamalParamSpec.ffdhe2048());
// Sender signature keys (Ed25519)
KeyPair ed = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec());
KeyPair ed = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed25519",
Ed25519KeyGenSpec.defaultSpec());
// AES-256-GCM payload builder
Supplier<AesDataContentBuilder> aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader();
Supplier<AesDataContentBuilder> aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeGcm(128).withHeader();
// Context recipients
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic());
EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, elg.getPublic());
KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, kyber.getPublic());
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT,
rsa.getPublic());
EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT,
elg.getPublic());
KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE,
kyber.getPublic());
// Envelope (encrypt)
MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get())
.payloadKeyBytes(32).addRecipient(rsaEnc).addRecipient(elgEnc)
MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32).addRecipient(rsaEnc).addRecipient(elgEnc)
.addRecipient(kybKem, /* kekBytes */ 32, /* saltLen */ 16)
.addPasswordRecipient(password, /* iterations */ 100_000, /* saltLen */ 16, /* kekBytes */ 32);
@@ -673,10 +714,13 @@ public class MultiRecipientEnvelopeTest {
TagTrailerDataContentBuilder<Signature> verifyTrailer;
// via Password
verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic()))
.bufferSize(8192).throwOnMismatch();
DataContent decPwd = DataContentChainBuilder
.decrypt().add(BytesSourceBuilder.of(ciphertext)).add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
verifyTrailer = new TagTrailerDataContentBuilder<>(
TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic())).bufferSize(8192)
.throwOnMismatch();
DataContent decPwd = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext))
.add(MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32).unlockWith(new UnlockMaterial.Password(password)))
.add(verifyTrailer).build();
byte[] ptPwd;
@@ -686,10 +730,14 @@ public class MultiRecipientEnvelopeTest {
assertArrayEquals(msg, ptPwd);
// via RSA
verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic()))
.bufferSize(8192).throwOnMismatch();
verifyTrailer = new TagTrailerDataContentBuilder<>(
TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic())).bufferSize(8192)
.throwOnMismatch();
DataContent decRsa = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext))
.add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()).payloadKeyBytes(32)
.add(MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32)
.unlockWith(new UnlockMaterial.Private(rsa.getPrivate())))
.add(verifyTrailer).build();
byte[] ptRsa;
@@ -699,10 +747,14 @@ public class MultiRecipientEnvelopeTest {
assertArrayEquals(msg, ptRsa);
// via ElGamal
verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic()))
.bufferSize(8192).throwOnMismatch();
verifyTrailer = new TagTrailerDataContentBuilder<>(
TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic())).bufferSize(8192)
.throwOnMismatch();
DataContent decElgamal = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext))
.add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()).payloadKeyBytes(32)
.add(MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32)
.unlockWith(new UnlockMaterial.Private(elg.getPrivate())))
.add(verifyTrailer).build();
byte[] ptElgamal;
@@ -712,10 +764,14 @@ public class MultiRecipientEnvelopeTest {
assertArrayEquals(msg, ptElgamal);
// via ML-KEM
verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic()))
.bufferSize(8192).throwOnMismatch();
verifyTrailer = new TagTrailerDataContentBuilder<>(
TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic())).bufferSize(8192)
.throwOnMismatch();
DataContent decKem = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext))
.add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()).payloadKeyBytes(32)
.add(MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32)
.unlockWith(new UnlockMaterial.Private(kyber.getPrivate())))
.add(verifyTrailer).build();
byte[] ptKem;
@@ -741,28 +797,38 @@ public class MultiRecipientEnvelopeTest {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(3072, new SecureRandom());
KeyPair rsa = kpg.generateKeyPair();
KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512());
KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048());
KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber512());
KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal",
ElgamalParamSpec.ffdhe2048());
// Sender signature keys (SPHINCS+, default/best)
KeyPair spx = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("SPHINCS+", SphincsPlusKeyGenSpec.defaultSpec());
KeyPair spx = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("SPHINCS+",
SphincsPlusKeyGenSpec.defaultSpec());
Supplier<AesDataContentBuilder> aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader();
Supplier<AesDataContentBuilder> aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeGcm(128).withHeader();
// Context recipients
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic());
EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, elg.getPublic());
KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, kyber.getPublic());
EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT,
rsa.getPublic());
EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT,
elg.getPublic());
KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE,
kyber.getPublic());
// Envelope recipients
MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get())
.payloadKeyBytes(32).addRecipient(rsaEnc).addRecipient(elgEnc)
MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32).addRecipient(rsaEnc).addRecipient(elgEnc)
.addRecipient(kybKem, /* kekBytes */ 32, /* saltLen */ 16)
.addPasswordRecipient(password, /* iterations */ 120_000, /* saltLen */ 16, /* kekBytes */ 32);
// Tag trailer for SIGNING (SPHINCS+)
TagTrailerDataContentBuilder<Signature> signTrailer = new TagTrailerDataContentBuilder<>(
TagEngineBuilder.sphincsPlusSign(new zeroecho.sdk.ZeroEchoSession(), spx.getPrivate())).bufferSize(8192);
TagEngineBuilder.sphincsPlusSign(new zeroecho.sdk.ZeroEchoSession(), spx.getPrivate()))
.bufferSize(8192);
// Encrypt chain
DataContent encryptChain = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg)).add(signTrailer)
@@ -776,10 +842,13 @@ public class MultiRecipientEnvelopeTest {
TagTrailerDataContentBuilder<Signature> verifyTrailer;
// via Password
verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic()))
verifyTrailer = new TagTrailerDataContentBuilder<>(
TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic()))
.bufferSize(8192).throwOnMismatch();
DataContent decPwd = DataContentChainBuilder
.decrypt().add(BytesSourceBuilder.of(ciphertext)).add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
DataContent decPwd = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext))
.add(MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32).unlockWith(new UnlockMaterial.Password(password)))
.add(verifyTrailer).build();
byte[] ptPwd;
@@ -789,10 +858,14 @@ public class MultiRecipientEnvelopeTest {
assertArrayEquals(msg, ptPwd);
// via RSA
verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic()))
verifyTrailer = new TagTrailerDataContentBuilder<>(
TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic()))
.bufferSize(8192).throwOnMismatch();
DataContent decRsa = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext))
.add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()).payloadKeyBytes(32)
.add(MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32)
.unlockWith(new UnlockMaterial.Private(rsa.getPrivate())))
.add(verifyTrailer).build();
byte[] ptRsa;
@@ -802,10 +875,14 @@ public class MultiRecipientEnvelopeTest {
assertArrayEquals(msg, ptRsa);
// via ElGamal
verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic()))
verifyTrailer = new TagTrailerDataContentBuilder<>(
TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic()))
.bufferSize(8192).throwOnMismatch();
DataContent decElgamal = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext))
.add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()).payloadKeyBytes(32)
.add(MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32)
.unlockWith(new UnlockMaterial.Private(elg.getPrivate())))
.add(verifyTrailer).build();
byte[] ptElgamal;
@@ -815,10 +892,14 @@ public class MultiRecipientEnvelopeTest {
assertArrayEquals(msg, ptElgamal);
// via ML-KEM
verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic()))
verifyTrailer = new TagTrailerDataContentBuilder<>(
TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic()))
.bufferSize(8192).throwOnMismatch();
DataContent decKem = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext))
.add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()).payloadKeyBytes(32)
.add(MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesGcm.get()).payloadKeyBytes(32)
.unlockWith(new UnlockMaterial.Private(kyber.getPrivate())))
.add(verifyTrailer).build();
byte[] ptKem;
@@ -834,14 +915,12 @@ public class MultiRecipientEnvelopeTest {
// Helpers
// ------------------------------------------------------------------------------------
private static byte[] encryptKemRecipients(byte[] input, KeyPair[] keyPairs, int[] kekSizes)
throws Exception {
private static byte[] encryptKemRecipients(byte[] input, KeyPair[] keyPairs, int[] kekSizes) throws Exception {
zeroecho.sdk.ZeroEchoSession session = new zeroecho.sdk.ZeroEchoSession();
MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session)
.withAes(aesGcmSupplier().get()).payloadKeyBytes(32);
for (int index = 0; index < keyPairs.length; index++) {
KemContext context = session.createContext("ML-KEM", KeyUsage.ENCAPSULATE,
keyPairs[index].getPublic());
KemContext context = session.createContext("ML-KEM", KeyUsage.ENCAPSULATE, keyPairs[index].getPublic());
builder.addRecipient(context, kekSizes[index], 16);
}
DataContent encryptor = builder.build(true);
@@ -852,8 +931,7 @@ public class MultiRecipientEnvelopeTest {
}
private static Supplier<AesDataContentBuilder> aesGcmSupplier() {
return () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.modeGcm(128).withHeader();
return () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader();
}
/** Minimal source builder so we can compose pull-style chains. */
@@ -885,8 +963,10 @@ public class MultiRecipientEnvelopeTest {
private static void decryptAndAssert(String banner, Supplier<AesDataContentBuilder> aesFactory,
UnlockMaterial material, byte[] original, byte[] encrypted) throws IOException {
MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesFactory.get())
.payloadKeyBytes(32).unlockWith(material);
MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder
.builder(new zeroecho.sdk.ZeroEchoSession()
.withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000)))
.withAes(aesFactory.get()).payloadKeyBytes(32).unlockWith(material);
DataContent decryptor = dec.build(false);
decryptor.setInput(new BytesContent(encrypted));

View File

@@ -75,10 +75,8 @@ class PasswordRecipientTest {
() -> new PasswordRecipient(new char[] { 'p' }, 10_000, 16, kekBytes, false, LIMITS));
}
PasswordRecipient aes128 =
new PasswordRecipient(new char[] { 'p' }, 10_000, 16, 16, false, LIMITS);
PasswordRecipient aes256 =
new PasswordRecipient(new char[] { 'p' }, 10_000, 16, 32, false, LIMITS);
PasswordRecipient aes128 = new PasswordRecipient(new char[] { 'p' }, 10_000, 16, 16, false, LIMITS);
PasswordRecipient aes256 = new PasswordRecipient(new char[] { 'p' }, 10_000, 16, 32, false, LIMITS);
aes128.close();
aes256.close();
System.out.println("...acceptedKekBytes=16,32");
@@ -131,9 +129,8 @@ class PasswordRecipientTest {
ByteArrayOutputStream blob = new ByteArrayOutputStream();
Util.writePack7I(blob, 9_999);
assertThrows(IOException.class, () -> new PasswordOpener(LIMITS).tryOpen(
"PWD:PBKDF2-SHA256:GCM-WRAP", blob.toByteArray(),
new UnlockMaterial.Password(new char[] { 'p' })));
assertThrows(IOException.class, () -> new PasswordOpener(LIMITS).tryOpen("PWD:PBKDF2-SHA256:GCM-WRAP",
blob.toByteArray(), new UnlockMaterial.Password(new char[] { 'p' })));
System.out.println("ok");
}
@@ -143,11 +140,9 @@ class PasswordRecipientTest {
PasswordOpener opener = new PasswordOpener(LIMITS);
UnlockMaterial.Password material = new UnlockMaterial.Password(new char[] { 'p' });
assertThrows(IOException.class,
() -> opener.tryOpen("PWD:PBKDF2-SHA256:GCM-WRAP", new byte[] { 1 }, material));
assertThrows(IOException.class,
() -> opener.tryOpen("PWD:PBKDF2-SHA256:GCM-WRAP",
new byte[] { 1, 1, 1, 1, 1, (byte) 0x80 }, material));
assertThrows(IOException.class, () -> opener.tryOpen("PWD:PBKDF2-SHA256:GCM-WRAP", new byte[] { 1 }, material));
assertThrows(IOException.class, () -> opener.tryOpen("PWD:PBKDF2-SHA256:GCM-WRAP",
new byte[] { 1, 1, 1, 1, 1, (byte) 0x80 }, material));
System.out.println("...malformedCases=2");
System.out.println("ok");
}
@@ -201,8 +196,7 @@ class PasswordRecipientTest {
assertTrue(content.isDestroyed());
assertTrue(recipient.isDestroyed());
assertThrows(IllegalStateException.class,
() -> content.setInput(() -> new ByteArrayInputStream(new byte[0])));
assertThrows(IllegalStateException.class, () -> content.setInput(() -> new ByteArrayInputStream(new byte[0])));
assertThrows(IllegalStateException.class, content::getStream);
System.out.println("...contentDestroyed=true");
System.out.println("abandonedBuiltContentOwnsAndDestroysTransferredRecipient...ok");
@@ -218,14 +212,13 @@ class PasswordRecipientTest {
DataContent limited = limitedBuilder.build(true);
limited.setInput(() -> new ByteArrayInputStream(new byte[0]));
assertThrows(IOException.class, limited::getStream);
assertTrue(limitedRecipients.stream()
.map(PasswordRecipient.class::cast).allMatch(PasswordRecipient::isDestroyed));
assertTrue(
limitedRecipients.stream().map(PasswordRecipient.class::cast).allMatch(PasswordRecipient::isDestroyed));
PasswordRecipient randomRecipient =
new PasswordRecipient(new char[] { 'c' }, 10_000, 16, 32, false, LIMITS);
PasswordRecipient randomRecipient = new PasswordRecipient(new char[] { 'c' }, 10_000, 16, 32, false, LIMITS);
Encryptor randomFailure = new Encryptor(List.of(randomRecipient),
AesDataContentBuilder.builder(new ZeroEchoSession()).modeGcm(128).withHeader(),
null, 32, 4, 1024, ignored -> {
AesDataContentBuilder.builder(new ZeroEchoSession()).modeGcm(128).withHeader(), null, 32, 4, 1024,
ignored -> {
throw new IllegalStateException("controlled random failure");
});
randomFailure.setInput(() -> new ByteArrayInputStream(new byte[0]));

View File

@@ -32,8 +32,7 @@ class SessionRecipientOpenerContractTest {
assertSessionOnlyConstructor(EncCtxOpener.class);
List<Method> addOpenerMethods = Arrays.stream(MultiRecipientDataSourceBuilder.class.getMethods())
.filter(method -> method.getName().equals("addOpener"))
.toList();
.filter(method -> method.getName().equals("addOpener")).toList();
assertEquals(1, addOpenerMethods.size());
assertEquals(RecipientOpener.class, addOpenerMethods.get(0).getParameterTypes()[0]);
@@ -47,10 +46,8 @@ class SessionRecipientOpenerContractTest {
ZeroEchoSession session = new ZeroEchoSession();
UnlockMaterial.Password material = new UnlockMaterial.Password(new char[] { 'p' });
try {
assertNull(new KemCtxOpener(session).tryOpen(
"CTX-ENC:RSA", new byte[0], material));
assertNull(new EncCtxOpener(session).tryOpen(
"KEM:ML-KEM:GCM-WRAP", new byte[0], material));
assertNull(new KemCtxOpener(session).tryOpen("CTX-ENC:RSA", new byte[0], material));
assertNull(new EncCtxOpener(session).tryOpen("KEM:ML-KEM:GCM-WRAP", new byte[0], material));
} finally {
material.destroy();
}
@@ -61,15 +58,12 @@ class SessionRecipientOpenerContractTest {
@Test
void customReusableOpenerScansEveryEntryAndClosesOnce() throws Exception {
System.out.println("customReusableOpenerScansEveryEntryAndClosesOnce");
ZeroEchoSession session = new ZeroEchoSession()
.withPbkdf2Limits(new Pbkdf2Limits(20_000, 30_000));
ZeroEchoSession session = new ZeroEchoSession().withPbkdf2Limits(new Pbkdf2Limits(20_000, 30_000));
TrackingOpener opener = new TrackingOpener();
UnlockMaterial.Password material = new UnlockMaterial.Password(new char[] { 'p' });
try (MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session)
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader())
.unlockWith(material)
.addOpener(opener);
MultiRecipientContent content = builder.build(false)) {
.withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()).unlockWith(material)
.addOpener(opener); MultiRecipientContent content = builder.build(false)) {
content.setInput(() -> new ByteArrayInputStream(twoEntryHeader()));
assertThrows(IOException.class, content::getStream);
} finally {

View File

@@ -76,7 +76,8 @@ public class HybridDerivedTest {
byte[] aad = "aad".getBytes(StandardCharsets.UTF_8);
byte[] msg = fixedBytes(1024, (byte) 0x5A);
AesDataContentBuilder encAes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader().modeGcm(128);
AesDataContentBuilder encAes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader()
.modeGcm(128);
AesDataContentBuilder returnedEnc = HybridDerived.from(exporter).label("app/enc/aes").transcript(transcript)
.aad(aad).applyToAesGcm(encAes, 256);
@@ -88,10 +89,10 @@ public class HybridDerivedTest {
System.out.println("...ciphertextLen=" + ciphertext.length);
System.out.println("...ciphertextPrefix=" + shortHex(ciphertext, 32));
AesDataContentBuilder decAes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader().modeGcm(128);
AesDataContentBuilder decAes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader()
.modeGcm(128);
HybridDerived.from(exporter).label("app/enc/aes").transcript(transcript).aad(aad)
.applyToAesGcm(decAes, 256);
HybridDerived.from(exporter).label("app/enc/aes").transcript(transcript).aad(aad).applyToAesGcm(decAes, 256);
byte[] out = runDecrypt(decAes, ciphertext);
System.out.println("...outPrefix=" + shortHex(out, 32));
@@ -109,15 +110,16 @@ public class HybridDerivedTest {
byte[] aad = "aad".getBytes(StandardCharsets.UTF_8);
byte[] msg = fixedBytes(256, (byte) 0x1C);
AesDataContentBuilder encAes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader().modeGcm(128);
AesDataContentBuilder encAes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader()
.modeGcm(128);
HybridDerived.from(exporter).label("app/enc/aes").transcript(transcript).aad(aad)
.applyToAesGcm(encAes, 256);
HybridDerived.from(exporter).label("app/enc/aes").transcript(transcript).aad(aad).applyToAesGcm(encAes, 256);
byte[] ciphertext = runEncrypt(encAes, msg);
System.out.println("...ciphertextLen=" + ciphertext.length);
AesDataContentBuilder decAesWrong = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader().modeGcm(128);
AesDataContentBuilder decAesWrong = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.withHeader().modeGcm(128);
// ...label mismatch -> wrong key/iv/aad -> decryption must fail
HybridDerived.from(exporter).label("app/enc/aes_WRONG").transcript(transcript).aad(aad)
@@ -137,7 +139,8 @@ public class HybridDerivedTest {
byte[] aad = "aad".getBytes(StandardCharsets.UTF_8);
byte[] msg = fixedBytes(777, (byte) 0x33);
ChaChaDataContentBuilder encChaCha = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader();
ChaChaDataContentBuilder encChaCha = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.withHeader();
ChaChaDataContentBuilder returnedEnc = HybridDerived.from(exporter).label("app/enc/chacha")
.transcript(transcript).aad(aad).applyToChaChaAead(encChaCha, 256);
@@ -149,7 +152,8 @@ public class HybridDerivedTest {
System.out.println("...ciphertextLen=" + ciphertext.length);
System.out.println("...ciphertextPrefix=" + shortHex(ciphertext, 32));
ChaChaDataContentBuilder decChaCha = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader();
ChaChaDataContentBuilder decChaCha = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.withHeader();
HybridDerived.from(exporter).label("app/enc/chacha").transcript(transcript).aad(aad)
.applyToChaChaAead(decChaCha, 256);
@@ -174,7 +178,8 @@ public class HybridDerivedTest {
// recommended bits
// --------------------
HmacDataContentBuilder macBuilder = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).sha256().emitHexTag();
HmacDataContentBuilder macBuilder = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).sha256()
.emitHexTag();
int recommendedBits = macBuilder.recommendedKeyBits();
System.out.println("...recommendedBits=" + recommendedBits);
@@ -184,8 +189,8 @@ public class HybridDerivedTest {
String tagHex = runHmacHex(macBuilder, msg);
System.out.println("...tagHexPrefix=" + shortText(tagHex, 64));
HmacDataContentBuilder verifyBuilder = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).sha256().expectedTagHex(tagHex)
.emitVerificationBoolean();
HmacDataContentBuilder verifyBuilder = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.sha256().expectedTagHex(tagHex).emitVerificationBoolean();
HybridDerived.from(exporter).label("app/mac/hmac-default").transcript(transcript).applyToHmac(verifyBuilder);
@@ -197,7 +202,8 @@ public class HybridDerivedTest {
// Override key size path: applyToHmac(hmac, keyBits)
// --------------------
HmacDataContentBuilder macBuilderOv = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).sha256().emitHexTag();
HmacDataContentBuilder macBuilderOv = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.sha256().emitHexTag();
// ...override to 512-bit keying material (still valid for HMAC; explicit expert
// choice)
@@ -207,8 +213,8 @@ public class HybridDerivedTest {
String tagHexOv = runHmacHex(macBuilderOv, msg);
System.out.println("...tagHexOvPrefix=" + shortText(tagHexOv, 64));
HmacDataContentBuilder verifyBuilderOv = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).sha256().expectedTagHex(tagHexOv)
.emitVerificationBoolean();
HmacDataContentBuilder verifyBuilderOv = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
.sha256().expectedTagHex(tagHexOv).emitVerificationBoolean();
HybridDerived.from(exporter).label("app/mac/hmac-override").transcript(transcript).applyToHmac(verifyBuilderOv,
512);

View File

@@ -67,17 +67,9 @@ class HybridKexFrameCodecTest {
@Test
void rejectsMalformedFrames() {
String name = start("rejectsMalformedFrames");
byte[][] malformed = {
new byte[7],
ints(-1, 0),
ints(0, -1),
ints(1, 0),
ints(0, 1),
append(ints(0, 0), (byte) 0x7f),
new byte[HybridKexContext.MAX_FRAME_BYTES + 1],
ints(0, HybridKexContext.MAX_FRAME_BYTES),
ints(Integer.MAX_VALUE, 0)
};
byte[][] malformed = { new byte[7], ints(-1, 0), ints(0, -1), ints(1, 0), ints(0, 1),
append(ints(0, 0), (byte) 0x7f), new byte[HybridKexContext.MAX_FRAME_BYTES + 1],
ints(0, HybridKexContext.MAX_FRAME_BYTES), ints(Integer.MAX_VALUE, 0) };
for (byte[] frame : malformed) {
assertThrows(IOException.class, () -> HybridKexContext.decode(frame));

View File

@@ -128,11 +128,14 @@ public class HybridKexTest {
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
// Classic: X25519 key pairs (Xdh + XdhSpec.X25519)
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
// PQC: ML-KEM key pair (Kyber variant)
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
HybridKexContext alice = null;
HybridKexContext bob = null;
@@ -140,13 +143,15 @@ public class HybridKexTest {
try {
// Initiator: classic uses Alice private + Bob classic public; PQC uses Bob PQC
// public
alice = HybridKexContexts.initiator(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh", aliceClassic.getPrivate(), bobClassic.getPublic(),
XdhSpec.X25519, "ML-KEM", bobPqc.getPublic(), null);
alice = HybridKexContexts.initiator(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh",
aliceClassic.getPrivate(), bobClassic.getPublic(), XdhSpec.X25519, "ML-KEM", bobPqc.getPublic(),
null);
// Responder: classic uses Bob private + Alice classic public; PQC uses Bob PQC
// private
bob = HybridKexContexts.responder(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh", bobClassic.getPrivate(), aliceClassic.getPublic(),
XdhSpec.X25519, "ML-KEM", bobPqc.getPrivate(), null);
bob = HybridKexContexts.responder(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh",
bobClassic.getPrivate(), aliceClassic.getPublic(), XdhSpec.X25519, "ML-KEM", bobPqc.getPrivate(),
null);
// Alice produces message (contains PQC ciphertext; classic part is empty here)
byte[] aliceMsg = alice.getPeerMessage();
@@ -189,11 +194,14 @@ public class HybridKexTest {
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
// Classic: X25519 key pairs (Xdh + XdhSpec.X25519)
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh",
XdhSpec.X25519);
// PQC: ML-KEM key pair (recipient/responder)
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM",
KyberKeyGenSpec.kyber768());
HybridKexContext alice = null;
HybridKexContext bob = null;
@@ -203,11 +211,11 @@ public class HybridKexTest {
// KeyPairKey + ContextSpec).
// PQC leg is KEM-style: initiator uses recipient public key; responder uses
// recipient private key.
alice = HybridKexContexts.initiatorPairMessage(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh", new KeyPairKey(aliceClassic), XdhSpec.X25519,
"ML-KEM", bobPqc.getPublic(), null);
alice = HybridKexContexts.initiatorPairMessage(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh",
new KeyPairKey(aliceClassic), XdhSpec.X25519, "ML-KEM", bobPqc.getPublic(), null);
bob = HybridKexContexts.responderPairMessage(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh", new KeyPairKey(bobClassic), XdhSpec.X25519,
"ML-KEM", bobPqc.getPrivate(), null);
bob = HybridKexContexts.responderPairMessage(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh",
new KeyPairKey(bobClassic), XdhSpec.X25519, "ML-KEM", bobPqc.getPrivate(), null);
// Step 1: Alice -> Bob (classic SPKI + PQC ciphertext)
byte[] msgA = alice.getPeerMessage();

View File

@@ -242,8 +242,7 @@ public class HybridSignatureTest {
System.out.println("...msg=" + msg.length + " bytes");
ZeroEchoSession session = new ZeroEchoSession();
KeyPair ed = session.keyBuilders().asymmetric().generateKeyPair("Ed25519",
Ed25519KeyGenSpec.defaultSpec());
KeyPair ed = session.keyBuilders().asymmetric().generateKeyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec());
KeyPair spx = session.keyBuilders().asymmetric().generateKeyPair("SPHINCS+",
SphincsPlusKeyGenSpec.defaultSpec());
@@ -256,15 +255,15 @@ public class HybridSignatureTest {
HybridSignatureProfile.VerifyRule.AND);
byte[] sigAnd;
try (SignatureContext signer = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), andProfile, ed.getPrivate(), spx.getPrivate(),
2 * 1024 * 1024)) {
try (SignatureContext signer = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), andProfile,
ed.getPrivate(), spx.getPrivate(), 2 * 1024 * 1024)) {
sigAnd = signTrailer(signer, msg);
}
System.out.println("...sig(AND).len=" + sigAnd.length + ", head=" + hexShort(sigAnd));
// verify OK
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), andProfile, ed.getPublic(), spx.getPublic(),
2 * 1024 * 1024)) {
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), andProfile,
ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) {
verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch());
verifier.setExpectedTag(sigAnd);
try (InputStream in = verifier.wrap(new ByteArrayInputStream(msg))) {
@@ -275,8 +274,8 @@ public class HybridSignatureTest {
// corrupt classic => must fail
byte[] badClassic = concat(flipOneBit(sub(sigAnd, 0, edLen), 0), sub(sigAnd, edLen, spxLen));
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), andProfile, ed.getPublic(), spx.getPublic(),
2 * 1024 * 1024)) {
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), andProfile,
ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) {
verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch());
verifier.setExpectedTag(badClassic);
assertThrows(java.io.IOException.class, () -> {
@@ -289,8 +288,8 @@ public class HybridSignatureTest {
// corrupt pqc => must fail
byte[] badPqc = concat(sub(sigAnd, 0, edLen), flipOneBit(sub(sigAnd, edLen, spxLen), 0));
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), andProfile, ed.getPublic(), spx.getPublic(),
2 * 1024 * 1024)) {
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), andProfile,
ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) {
verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch());
verifier.setExpectedTag(badPqc);
assertThrows(java.io.IOException.class, () -> {
@@ -306,16 +305,16 @@ public class HybridSignatureTest {
HybridSignatureProfile.VerifyRule.OR);
byte[] sigOr;
try (SignatureContext signer = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), orProfile, ed.getPrivate(), spx.getPrivate(),
2 * 1024 * 1024)) {
try (SignatureContext signer = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), orProfile,
ed.getPrivate(), spx.getPrivate(), 2 * 1024 * 1024)) {
sigOr = signTrailer(signer, msg);
}
System.out.println("...sig(OR).len=" + sigOr.length + ", head=" + hexShort(sigOr));
// corrupt classic => OR must pass
byte[] orBadClassic = concat(flipOneBit(sub(sigOr, 0, edLen), 0), sub(sigOr, edLen, spxLen));
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), orProfile, ed.getPublic(), spx.getPublic(),
2 * 1024 * 1024)) {
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), orProfile,
ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) {
verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch());
verifier.setExpectedTag(orBadClassic);
try (InputStream in = verifier.wrap(new ByteArrayInputStream(msg))) {
@@ -326,8 +325,8 @@ public class HybridSignatureTest {
// corrupt pqc => OR must pass
byte[] orBadPqc = concat(sub(sigOr, 0, edLen), flipOneBit(sub(sigOr, edLen, spxLen), 0));
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), orProfile, ed.getPublic(), spx.getPublic(),
2 * 1024 * 1024)) {
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), orProfile,
ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) {
verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch());
verifier.setExpectedTag(orBadPqc);
try (InputStream in = verifier.wrap(new ByteArrayInputStream(msg))) {
@@ -338,8 +337,8 @@ public class HybridSignatureTest {
// corrupt both => OR must fail
byte[] orBadBoth = concat(flipOneBit(sub(sigOr, 0, edLen), 0), flipOneBit(sub(sigOr, edLen, spxLen), 0));
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), orProfile, ed.getPublic(), spx.getPublic(),
2 * 1024 * 1024)) {
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), orProfile,
ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) {
verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch());
verifier.setExpectedTag(orBadBoth);
assertThrows(java.io.IOException.class, () -> {
@@ -377,14 +376,14 @@ public class HybridSignatureTest {
HybridSignatureProfile.VerifyRule.AND);
byte[] sig;
try (SignatureContext signer = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, rsa.getPrivate(), spx.getPrivate(),
2 * 1024 * 1024)) {
try (SignatureContext signer = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile,
rsa.getPrivate(), spx.getPrivate(), 2 * 1024 * 1024)) {
sig = signTrailer(signer, msg);
}
System.out.println("...sig.len=" + sig.length + ", head=" + hexShort(sig));
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, rsa.getPublic(), spx.getPublic(),
2 * 1024 * 1024)) {
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile,
rsa.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) {
verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch());
verifier.setExpectedTag(sig);
try (InputStream in = verifier.wrap(new ByteArrayInputStream(msg))) {
@@ -395,8 +394,8 @@ public class HybridSignatureTest {
// negative sanity: corrupt classic => must fail (AND)
byte[] badClassic = concat(flipOneBit(sub(sig, 0, rsaLen), 0), sub(sig, rsaLen, spxLen));
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, rsa.getPublic(), spx.getPublic(),
2 * 1024 * 1024)) {
try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile,
rsa.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) {
verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch());
verifier.setExpectedTag(badClassic);
assertThrows(java.io.IOException.class, () -> {
@@ -426,8 +425,7 @@ public class HybridSignatureTest {
System.out.println("...msg=" + msg.length + " bytes");
ZeroEchoSession session = new ZeroEchoSession();
KeyPair ed = session.keyBuilders().asymmetric().generateKeyPair("Ed25519",
Ed25519KeyGenSpec.defaultSpec());
KeyPair ed = session.keyBuilders().asymmetric().generateKeyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec());
KeyPair spx = session.keyBuilders().asymmetric().generateKeyPair("SPHINCS+",
SphincsPlusKeyGenSpec.defaultSpec());
@@ -437,8 +435,8 @@ public class HybridSignatureTest {
byte[] out;
int tagLen;
try (SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPrivate(), spx.getPrivate(),
2 * 1024 * 1024)) {
try (SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile,
ed.getPrivate(), spx.getPrivate(), 2 * 1024 * 1024)) {
DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg))
.add(new TagTrailerDataContentBuilder<Signature>(tagEnc).bufferSize(8192)).build();
@@ -449,8 +447,8 @@ public class HybridSignatureTest {
System.out.println("...out=" + out.length + " bytes");
try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPublic(), spx.getPublic(),
2 * 1024 * 1024)) {
try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile,
ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) {
tagDec.setVerificationApproach(tagDec.getVerificationCore().getThrowOnMismatch());
// IMPORTANT: TagTrailerDataContentBuilder supplies expectedTag internally
@@ -481,8 +479,7 @@ public class HybridSignatureTest {
System.out.println("...msg=" + msg.length + " bytes");
ZeroEchoSession session = new ZeroEchoSession();
KeyPair ed = session.keyBuilders().asymmetric().generateKeyPair("Ed25519",
Ed25519KeyGenSpec.defaultSpec());
KeyPair ed = session.keyBuilders().asymmetric().generateKeyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec());
KeyPair spx = session.keyBuilders().asymmetric().generateKeyPair("SPHINCS+",
SphincsPlusKeyGenSpec.defaultSpec());
@@ -496,8 +493,8 @@ public class HybridSignatureTest {
byte[] out;
int tagLen;
try (SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPrivate(), spx.getPrivate(),
2 * 1024 * 1024)) {
try (SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile,
ed.getPrivate(), spx.getPrivate(), 2 * 1024 * 1024)) {
DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg))
.add(new TagTrailerDataContentBuilder<Signature>(tagEnc).bufferSize(8192)).build();
@@ -515,8 +512,8 @@ public class HybridSignatureTest {
byte[] badClassic = concat(flipOneBit(sub(tag, 0, edLen), 0), sub(tag, edLen, spxLen));
byte[] outBadClassic = concat(body, badClassic);
try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPublic(), spx.getPublic(),
2 * 1024 * 1024)) {
try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile,
ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) {
tagDec.setVerificationApproach(tagDec.getVerificationCore().getThrowOnMismatch());
DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(outBadClassic))
@@ -533,8 +530,8 @@ public class HybridSignatureTest {
byte[] badPqc = concat(sub(tag, 0, edLen), flipOneBit(sub(tag, edLen, spxLen), 0));
byte[] outBadPqc = concat(body, badPqc);
try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPublic(), spx.getPublic(),
2 * 1024 * 1024)) {
try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile,
ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) {
tagDec.setVerificationApproach(tagDec.getVerificationCore().getThrowOnMismatch());
DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(outBadPqc))
@@ -551,8 +548,8 @@ public class HybridSignatureTest {
byte[] badBoth = concat(flipOneBit(sub(tag, 0, edLen), 0), flipOneBit(sub(tag, edLen, spxLen), 0));
byte[] outBadBoth = concat(body, badBoth);
try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPublic(), spx.getPublic(),
2 * 1024 * 1024)) {
try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile,
ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) {
tagDec.setVerificationApproach(tagDec.getVerificationCore().getThrowOnMismatch());
DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(outBadBoth))
@@ -589,8 +586,8 @@ public class HybridSignatureTest {
byte[] out;
try (SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, rsa.getPrivate(), spx.getPrivate(),
2 * 1024 * 1024)) {
try (SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile,
rsa.getPrivate(), spx.getPrivate(), 2 * 1024 * 1024)) {
DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg))
.add(new TagTrailerDataContentBuilder<Signature>(tagEnc).bufferSize(8192)).build();
@@ -600,8 +597,8 @@ public class HybridSignatureTest {
System.out.println("...out=" + out.length + " bytes");
try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, rsa.getPublic(), spx.getPublic(),
2 * 1024 * 1024)) {
try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile,
rsa.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) {
tagDec.setVerificationApproach(tagDec.getVerificationCore().getThrowOnMismatch());
DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(out))