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:
@@ -5,9 +5,20 @@ plugins {
|
|||||||
|
|
||||||
group='org.egothor'
|
group='org.egothor'
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
mockitoAgent
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api 'org.bouncycastle:bcpkix-jdk18on'
|
api 'org.bouncycastle:bcpkix-jdk18on'
|
||||||
implementation 'org.egothor:conflux'
|
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.links("https://www.egothor.org/javadoc/conflux")
|
||||||
// options.overview = file("src/main/javadoc/overview.html")
|
// options.overview = file("src/main/javadoc/overview.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
jvmArgs("-javaagent:${configurations.mockitoAgent.singleFile}")
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import javax.crypto.SecretKey;
|
|||||||
|
|
||||||
import zeroecho.core.audit.AuditListener;
|
import zeroecho.core.audit.AuditListener;
|
||||||
import zeroecho.core.audit.AuditedContexts;
|
import zeroecho.core.audit.AuditedContexts;
|
||||||
|
import zeroecho.core.context.AgreementContext;
|
||||||
import zeroecho.core.context.CryptoContext;
|
import zeroecho.core.context.CryptoContext;
|
||||||
import zeroecho.core.context.DigestContext;
|
import zeroecho.core.context.DigestContext;
|
||||||
import zeroecho.core.context.EncryptionContext;
|
import zeroecho.core.context.EncryptionContext;
|
||||||
@@ -335,33 +336,39 @@ public final class CryptoAlgorithms {
|
|||||||
|
|
||||||
if (AUDIT_MODE == AuditMode.WRAP) {
|
if (AUDIT_MODE == AuditMode.WRAP) {
|
||||||
final AuditListener listener = AUDIT; // pass through the global listener
|
final AuditListener listener = AUDIT; // pass through the global listener
|
||||||
if (ctx instanceof SignatureContext) {
|
return switch (ctx) {
|
||||||
@SuppressWarnings("unchecked")
|
case SignatureContext signatureContext -> wrapForAudit(signatureContext, listener, role);
|
||||||
C out = (C) AuditedContexts.wrap(ctx, listener, role);
|
case EncryptionContext encryptionContext -> wrapForAudit(encryptionContext, listener, role);
|
||||||
return out;
|
case KemContext kemContext -> wrapForAudit(kemContext, listener, role);
|
||||||
} else if (ctx instanceof EncryptionContext) {
|
case DigestContext digestContext -> wrapForAudit(digestContext, listener, role);
|
||||||
@SuppressWarnings("unchecked")
|
case MacContext macContext -> wrapForAudit(macContext, listener, role);
|
||||||
C out = (C) AuditedContexts.wrap(ctx, listener, role);
|
case AgreementContext agreementContext -> wrapForAudit(agreementContext, 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 ctx;
|
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 algorithm’s default spec for the
|
* Creates a {@link CryptoContext} using the algorithm’s default spec for the
|
||||||
* role.
|
* role.
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user