chore(text): source code format
This commit is contained in:
@@ -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())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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, () -> {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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]));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user