refactor!: consolidate crypto architecture and security model

* make ZeroEchoSession the sole policy, audit, and runtime boundary
* replace combined key builders with operation-specific SPI and typed metadata
* remove obsolete pre-release compatibility APIs and global crypto operations
* finalize JCA agreement contexts and replace inheritance with composition
* harden secret lifecycle, key destruction, hybrid KEX, PBKDF2, and audit handling
* standardize PairSeq I/O and introduce immutable validated value types
* migrate app, ext, samples, and required pki integration points
* expand correctness, security, concurrency, and malformed-input coverage

BREAKING CHANGE: removes deprecated pre-release global configuration, legacy
context factories, combined key-builder contracts, String-based password APIs,
unchecked PairSeq writing, BlockGeometry public fields, and other compatibility
facades.
This commit is contained in:
2026-07-28 19:20:30 +02:00
parent 7319aca0db
commit 49dc080c65
298 changed files with 12802 additions and 8763 deletions

View File

@@ -0,0 +1,202 @@
/*******************************************************************************
* Copyright (C) 2026, Leo Galambos
* All rights reserved.
******************************************************************************/
package zeroecho.sdk;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.security.Key;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.security.auth.DestroyFailedException;
import javax.security.auth.Destroyable;
import org.junit.jupiter.api.Test;
import zeroecho.core.audit.AuditListener;
class ZeroEchoSessionDestroyKeyTest {
@Test
void strictDestroyDistinguishesEveryLifecycleOutcome() throws Exception {
System.out.print("ZeroEchoSession/destroy-strict...");
AtomicInteger audits = new AtomicInteger();
ZeroEchoSession session = new ZeroEchoSession().withAuditListener(new AuditListener() {
@Override
public void onKeyDestroyed(String id, String provider, Key key) {
audits.incrementAndGet();
}
});
TestKey success = new TestKey(Behavior.SUCCESS);
assertTrue(session.destroyKey("test", "provider", success));
assertFalse(session.destroyKey("test", "provider", success));
assertEquals(1, audits.get());
assertFalse(session.destroyKey("test", "provider", new PlainKey()));
assertThrows(NullPointerException.class, () -> session.destroyKey("test", "provider", null));
assertThrows(DestroyFailedException.class,
() -> session.destroyKey("test", "provider", new TestKey(Behavior.FAIL_CHECKED)));
assertThrows(DestroyFailedException.class,
() -> session.destroyKey("test", "provider", new TestKey(Behavior.NO_TRANSITION)));
assertThrows(IllegalStateException.class,
() -> session.destroyKey("test", "provider", new TestKey(Behavior.FAIL_RUNTIME)));
assertEquals(1, audits.get());
System.out.println("ok");
}
@Test
void concurrentStrictDestroyReportsAndAuditsOneTransition() throws Exception {
System.out.print("ZeroEchoSession/destroy-concurrent...");
AtomicInteger audits = new AtomicInteger();
ZeroEchoSession session = new ZeroEchoSession().withAuditListener(new AuditListener() {
@Override
public void onKeyDestroyed(String id, String provider, Key key) {
audits.incrementAndGet();
}
});
BlockingKey key = new BlockingKey();
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
Future<Boolean> first = executor.submit(() -> session.destroyKey("test", "provider", key));
key.destroyEntered.await();
Future<Boolean> second = executor.submit(() -> session.destroyKey("test", "provider", key));
key.allowDestroy.countDown();
boolean firstResult = first.get();
boolean secondResult = second.get();
assertTrue(firstResult ^ secondResult);
assertEquals(1, audits.get());
assertEquals(1, key.destroyCalls.get());
} finally {
executor.shutdownNow();
}
System.out.println("ok");
}
private enum Behavior {
SUCCESS,
FAIL_CHECKED,
FAIL_RUNTIME,
NO_TRANSITION
}
private static final class TestKey implements Key, Destroyable {
private static final long serialVersionUID = 1L;
private static final String SECRET = "secret-key-marker";
private final Behavior behavior;
private boolean destroyed;
private boolean encodedCalled;
private boolean toStringCalled;
private TestKey(Behavior behavior) {
this.behavior = behavior;
}
@Override
public void destroy() throws DestroyFailedException {
switch (behavior) {
case SUCCESS -> destroyed = true;
case FAIL_CHECKED -> throw new DestroyFailedException(SECRET);
case FAIL_RUNTIME -> throw new IllegalStateException(SECRET);
case NO_TRANSITION -> {
// Intentionally does not transition.
}
}
}
@Override
public boolean isDestroyed() {
return destroyed;
}
@Override
public String getAlgorithm() {
return "test";
}
@Override
public String getFormat() {
return "RAW";
}
@Override
public byte[] getEncoded() {
encodedCalled = true;
return SECRET.getBytes(java.nio.charset.StandardCharsets.UTF_8);
}
@Override
public String toString() {
toStringCalled = true;
return SECRET;
}
}
private static final class PlainKey implements Key {
private static final long serialVersionUID = 1L;
@Override
public String getAlgorithm() {
return "plain";
}
@Override
public String getFormat() {
return null;
}
@Override
public byte[] getEncoded() {
return null;
}
}
private static final class BlockingKey implements Key, Destroyable {
private static final long serialVersionUID = 1L;
private final CountDownLatch destroyEntered = new CountDownLatch(1);
private final CountDownLatch allowDestroy = new CountDownLatch(1);
private final AtomicInteger destroyCalls = new AtomicInteger();
private boolean destroyed;
@Override
public void destroy() throws DestroyFailedException {
destroyCalls.incrementAndGet();
destroyEntered.countDown();
try {
allowDestroy.await();
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
throw new DestroyFailedException("Interrupted while testing destruction");
}
destroyed = true;
}
@Override
public boolean isDestroyed() {
return destroyed;
}
@Override
public String getAlgorithm() {
return "test";
}
@Override
public String getFormat() {
return null;
}
@Override
public byte[] getEncoded() {
return null;
}
}
}