fix: replace CryptoAlgorithms audit wrap instanceof chain with Java 21

switch

Replace the AUDIT_MODE == WRAP dispatch in
zeroecho.core.CryptoAlgorithms#create(...) with an exhaustive Java 21
pattern switch over the sealed CryptoContext hierarchy. This removes the
repeated instanceof chain, keeps unchecked casts localized in a single
internal helper, and closes the missing audit-wrap gap for
AgreementContext.

Add focused JUnit 5 coverage for audited proxy wrapping using
Mockito-based tests for representative context interfaces and wrapper
lifecycle delegation.

Closes #20
Time-Spent: 45m
This commit is contained in:
2026-04-05 22:17:14 +02:00
parent a4b9eeffe1
commit 14fbf31989
3 changed files with 156 additions and 22 deletions

View File

@@ -5,9 +5,20 @@ plugins {
group='org.egothor'
configurations {
mockitoAgent
}
dependencies {
api 'org.bouncycastle:bcpkix-jdk18on'
implementation 'org.egothor:conflux'
testImplementation "org.mockito:mockito-core:5.23.0"
testImplementation "org.mockito:mockito-inline:5.2.0"
mockitoAgent("org.mockito:mockito-core:5.23.0") {
transitive = false
}
}
@@ -53,3 +64,8 @@ javadoc {
options.links("https://www.egothor.org/javadoc/conflux")
// options.overview = file("src/main/javadoc/overview.html")
}
test {
useJUnitPlatform()
jvmArgs("-javaagent:${configurations.mockitoAgent.singleFile}")
}

View File

@@ -49,6 +49,7 @@ import javax.crypto.SecretKey;
import zeroecho.core.audit.AuditListener;
import zeroecho.core.audit.AuditedContexts;
import zeroecho.core.context.AgreementContext;
import zeroecho.core.context.CryptoContext;
import zeroecho.core.context.DigestContext;
import zeroecho.core.context.EncryptionContext;
@@ -335,33 +336,39 @@ public final class CryptoAlgorithms {
if (AUDIT_MODE == AuditMode.WRAP) {
final AuditListener listener = AUDIT; // pass through the global listener
if (ctx instanceof SignatureContext) {
@SuppressWarnings("unchecked")
C out = (C) AuditedContexts.wrap(ctx, listener, role);
return out;
} else if (ctx instanceof EncryptionContext) {
@SuppressWarnings("unchecked")
C out = (C) AuditedContexts.wrap(ctx, listener, role);
return out;
} else if (ctx instanceof KemContext) {
@SuppressWarnings("unchecked")
C out = (C) AuditedContexts.wrap(ctx, listener, role);
return out;
} else if (ctx instanceof DigestContext) {
@SuppressWarnings("unchecked")
C out = (C) AuditedContexts.wrap(ctx, listener, role);
return out;
} else if (ctx instanceof MacContext) {
@SuppressWarnings("unchecked")
C out = (C) AuditedContexts.wrap(ctx, listener, role);
return out;
}
// Unknown context type: return as-is (no wrapping).
return switch (ctx) {
case SignatureContext signatureContext -> wrapForAudit(signatureContext, listener, role);
case EncryptionContext encryptionContext -> wrapForAudit(encryptionContext, listener, role);
case KemContext kemContext -> wrapForAudit(kemContext, listener, role);
case DigestContext digestContext -> wrapForAudit(digestContext, listener, role);
case MacContext macContext -> wrapForAudit(macContext, listener, role);
case AgreementContext agreementContext -> wrapForAudit(agreementContext, listener, role);
};
}
return ctx;
}
/**
* Returns the audited wrapper for the supplied context.
*
* <p>
* The returned context remains owned by the caller of the factory method. This
* helper does not acquire an additional resource requiring local cleanup.
* </p>
*
* @param <C> context type
* @param context source context
* @param listener audit listener
* @param role key usage role
* @return audited wrapper
*/
@SuppressWarnings("unchecked")
/* default */ static <C extends CryptoContext> C wrapForAudit(CryptoContext context, AuditListener listener,
KeyUsage role) {
return (C) AuditedContexts.wrap(context, listener, role);
}
/**
* Creates a {@link CryptoContext} using the algorithms default spec for the
* role.

View File

@@ -0,0 +1,111 @@
/*******************************************************************************
* Copyright (C) 2026, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.core;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.lang.reflect.Proxy;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import zeroecho.core.CryptoAlgorithms.AuditMode;
import zeroecho.core.audit.AuditListener;
import zeroecho.core.context.AgreementContext;
import zeroecho.core.context.CryptoContext;
import zeroecho.core.context.DigestContext;
/**
* Verifies audited proxy wrapping for representative {@link CryptoContext}
* contract types.
*
* <p>
* These tests focus on the internal audit wrapping path used by
* {@link CryptoAlgorithms#wrapForAudit(CryptoContext, zeroecho.core.audit.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 {
@AfterEach
void restoreAuditConfiguration() {
CryptoAlgorithms.setAuditListener(AuditListener.noop());
CryptoAlgorithms.setAuditMode(AuditMode.OFF);
}
@Test
void wrapForAuditDigestContextReturnsProxy() {
System.out.println("wrapForAuditDigestContextReturnsProxy");
DigestContext context = mock(DigestContext.class);
DigestContext wrapped = CryptoAlgorithms.wrapForAudit(context, AuditListener.noop(), KeyUsage.DIGEST);
System.out.println("...ctxClass=" + wrapped.getClass().getName());
assertTrue(Proxy.isProxyClass(wrapped.getClass()), "Digest context should be wrapped as JDK proxy");
System.out.println("wrapForAuditDigestContextReturnsProxy...ok");
}
@Test
void wrapForAuditAgreementContextReturnsProxy() {
System.out.println("wrapForAuditAgreementContextReturnsProxy");
AgreementContext context = mock(AgreementContext.class);
AgreementContext wrapped = CryptoAlgorithms.wrapForAudit(context, AuditListener.noop(), KeyUsage.AGREEMENT);
System.out.println("...ctxClass=" + wrapped.getClass().getName());
assertTrue(Proxy.isProxyClass(wrapped.getClass()), "Agreement context should be wrapped as JDK proxy");
System.out.println("wrapForAuditAgreementContextReturnsProxy...ok");
}
@Test
void wrapForAuditDigestContextCloseDelegatesToWrappedContext() throws Exception {
System.out.println("wrapForAuditDigestContextCloseDelegatesToWrappedContext");
DigestContext context = mock(DigestContext.class);
DigestContext wrapped = CryptoAlgorithms.wrapForAudit(context, AuditListener.noop(), KeyUsage.DIGEST);
wrapped.close();
verify(context).close();
System.out.println("...wrappedCloseDelegated=true");
System.out.println("wrapForAuditDigestContextCloseDelegatesToWrappedContext...ok");
}
}