diff --git a/java/wolfssl-openjdk-fips-root/test-images/spring-boot-tests/Dockerfile b/java/wolfssl-openjdk-fips-root/test-images/spring-boot-tests/Dockerfile new file mode 100644 index 0000000..0b33808 --- /dev/null +++ b/java/wolfssl-openjdk-fips-root/test-images/spring-boot-tests/Dockerfile @@ -0,0 +1,388 @@ +# syntax=docker/dockerfile:1 +# Spring Boot SSL Tests with wolfJSSE FIPS +# Build: docker build -t spring-boot-wolfjsse-fips . + +ARG FIPS_BASE_IMAGE=wolfssl-openjdk-fips-root:latest +ARG SPRING_BOOT_TAG=v3.4.1 + +# Stage 1: Build Spring Boot with FIPS patches +FROM rootpublic/openjdk:19-jdk-bookworm-slim AS builder +RUN apt-get update && apt-get install -y build-essential git perl && rm -rf /var/lib/apt/lists/* +WORKDIR /app/spring-boot +ARG SPRING_BOOT_TAG +RUN git clone --depth 1 --branch ${SPRING_BOOT_TAG} https://github.com/spring-projects/spring-boot.git . +COPY apply_spring_fips_fixes.sh /tmp/ +RUN chmod +x /tmp/apply_spring_fips_fixes.sh && /tmp/apply_spring_fips_fixes.sh /app/spring-boot +# Pre-compile test sources to warm the Gradle cache. Failures are tolerated because +# they do not affect the runtime image, but a warning is logged if this step fails. +RUN if ! ./gradlew :spring-boot-project:spring-boot:compileTestJava \ + :spring-boot-project:spring-boot-autoconfigure:compileTestJava \ + :spring-boot-project:spring-boot-actuator:compileTestJava \ + --no-daemon -x checkstyleMain -x checkstyleTest -x checkFormat; then \ + echo "WARNING: Gradle compileTestJava tasks failed; continuing Docker build because tests are not required for the runtime image."; \ + fi +# Resolve test runtime classpath dependencies. Failures are tolerated but logged, +# since these dependencies are only needed for test execution, not for running the image. +RUN if ! ./gradlew :spring-boot-project:spring-boot:dependencies --configuration testRuntimeClasspath --no-daemon; then \ + echo "WARNING: Gradle dependencies (testRuntimeClasspath) resolution failed; continuing Docker build because tests are not required for the runtime image."; \ + fi + +# Stage 2: Runtime with FIPS base +FROM ${FIPS_BASE_IMAGE} +RUN apt-get update && apt-get install -y git openjdk-17-jdk-headless && rm -rf /var/lib/apt/lists/* +ENV GRADLE_JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 +COPY --from=builder /app/spring-boot /app/spring-boot +COPY --from=builder /root/.gradle /root/.gradle + +# Get wolfSSL certs and generate test certs with localhost SAN +RUN git clone --depth 1 --filter=blob:none --sparse https://github.com/wolfSSL/wolfssl.git /tmp/wolfssl && \ + cd /tmp/wolfssl && git sparse-checkout set certs && cp -r certs /app/certs && rm -rf /tmp/wolfssl && \ + for f in /app/certs/*.pem; do [ -f "$f" ] && grep -q "BEGIN" "$f" && sed -n '/-----BEGIN/,/-----END/p' "$f" > "$f.tmp" && mv "$f.tmp" "$f"; done + +RUN mkdir -p /app/certs/test + +WORKDIR /app/spring-boot +ENV FIPS_CHECK=true GRADLE_OPTS="-Xmx4g -XX:MaxMetaspaceSize=512m" +ENV WOLFJSSE_TEST_OPTS="-Xbootclasspath/a:/usr/share/java/wolfcrypt-jni.jar:/usr/share/java/wolfssl-jsse.jar:/usr/share/java/filtered-providers.jar \ + -Djava.library.path=/usr/lib/jni:/usr/local/lib \ + -Djavax.net.ssl.trustStore=/app/spring-boot/spring-boot-project/spring-boot/src/test/resources/truststore.wks \ + -Djavax.net.ssl.trustStorePassword=wolfSSLFIPSPwd2024 -Djavax.net.ssl.trustStoreType=WKS \ + --add-modules=jdk.crypto.ec \ + --add-exports=jdk.crypto.ec/sun.security.ec=ALL-UNNAMED \ + --add-opens=jdk.crypto.ec/sun.security.ec=ALL-UNNAMED \ + --add-opens=java.base/java.security=ALL-UNNAMED \ + --add-opens=java.base/sun.security.provider=ALL-UNNAMED \ + --add-opens=java.base/sun.security.util=ALL-UNNAMED \ + --add-opens=java.base/sun.security.rsa=ALL-UNNAMED" + +RUN ln -sf /usr/lib/jni/libwolfssljni.so /usr/local/openjdk-19/lib/ && \ + ln -sf /usr/lib/jni/libwolfcryptjni.so /usr/local/openjdk-19/lib/ && \ + ln -sf /usr/local/lib/libwolfssl.so /usr/local/openjdk-19/lib/ + +# Gradle security config: Uses standard SUN providers (not wolfSSL) because Gradle +# needs MD5 checksums for dependency verification, which is not FIPS-approved. +# This config is ONLY used by the Gradle daemon JVM, not by test JVMs. +# The test JVMs use the FIPS base image's java.security which already has +# WolfCryptProvider and WolfSSLProvider registered at high priority. +# WKS is set as default keystore type for compatibility with our converted keystores. +RUN cat > /usr/local/openjdk-19/conf/security/java.security.gradle <<'EOF' +security.provider.1=SUN +security.provider.2=SunRsaSign +security.provider.3=SunEC +security.provider.4=SunJSSE +security.provider.5=SunJCE +keystore.type=WKS +keystore.type.compat=true +EOF + +# Gradle init for wolfJSSE test configuration +RUN mkdir -p /root/.gradle/init.d && cat > /root/.gradle/init.d/wolfjsse.gradle <<'EOF' +allprojects { + // Skip buildSrc tests - they fail due to FIPS environment (MD5 checksums, etc.) + if (project.name == 'buildSrc' || project.path.startsWith(':buildSrc')) { + tasks.withType(Test) { enabled = false } + } + tasks.withType(Test) { + executable = '/usr/local/openjdk-19/bin/java' + def opts = System.getenv('WOLFJSSE_TEST_OPTS') + if (opts) jvmArgs opts.split(/\s+/).findAll { it.trim() } + systemProperty 'junit.jupiter.execution.timeout.default', '120s' + testLogging { events "passed", "skipped", "failed"; showExceptions = true } + } +} +EOF + +# WKS keystore utilities +RUN mkdir -p /app/util && cat > /app/util/WksUtil.java <<'JAVA' +import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.security.*; import java.security.cert.Certificate; import java.security.spec.*; import java.util.*; +public class WksUtil { + static { + // Register WolfCryptProvider if on classpath but not yet in security providers + // (needed when running under java-17 with -Xbootclasspath/a) + try { + if (Security.getProvider("wolfJCE") == null) { + Security.addProvider((Provider) Class.forName( + "com.wolfssl.provider.jce.WolfCryptProvider").getConstructor().newInstance()); + } + } catch (Exception e) { /* not on classpath, skip */ } + } + static final char[] FIPS_PWD = "wolfSSLFIPSPwd2024".toCharArray(); + public static void main(String[] a) throws Exception { + if ("combined".equals(a[0])) combined(a[1],a[2],a[3],a[4],a[5],a[6].toCharArray()); + else if ("selfsigned".equals(a[0])) selfsigned(a[1],a[2],a[3],a[4],a[5].toCharArray()); + else if ("trust".equals(a[0])) trust(a[1],a[2],a[3],a[4].toCharArray()); + else if ("convert".equals(a[0])) convert(a[1],a[2].toCharArray(),a[3],a[4].toCharArray()); + else if ("addca".equals(a[0])) addca(a[1],a[2],a[3],a[4]); + else if ("exportpem".equals(a[0])) exportpem(a[1],a[2].toCharArray(),a[3],a[4],a[5],a.length > 6 ? a[6] : null); + } + static void combined(String cert, String key, String ca, String out, String alias, char[] pwd) throws Exception { + KeyStore ks = KeyStore.getInstance("WKS"); ks.load(null, pwd); + ks.setKeyEntry(alias, loadKey(key), pwd, new java.security.cert.Certificate[]{loadCert(cert), loadCert(ca)}); + ks.setCertificateEntry("ca", loadCert(ca)); + try (FileOutputStream f = new FileOutputStream(out)) { ks.store(f, pwd); } + System.out.println("Created combined keystore+truststore: " + out); + } + static void selfsigned(String cert, String key, String out, String alias, char[] pwd) throws Exception { + KeyStore ks = KeyStore.getInstance("WKS"); ks.load(null, pwd); + ks.setKeyEntry(alias, loadKey(key), pwd, new java.security.cert.Certificate[]{loadCert(cert)}); + try (FileOutputStream f = new FileOutputStream(out)) { ks.store(f, pwd); } + System.out.println("Created self-signed keystore: " + out); + } + static void trust(String cert, String out, String alias, char[] pwd) throws Exception { + KeyStore ks = KeyStore.getInstance("WKS"); ks.load(null, pwd); + ks.setCertificateEntry(alias, loadCert(cert)); + try (FileOutputStream f = new FileOutputStream(out)) { ks.store(f, pwd); } + System.out.println("Created truststore: " + out); + } + static void convert(String in, char[] srcPwd, String out, char[] dstPwd) throws Exception { + String type = in.toLowerCase().endsWith(".p12") ? "PKCS12" : "JKS"; + KeyStore src = KeyStore.getInstance(type); + try (FileInputStream f = new FileInputStream(in)) { src.load(f, srcPwd); } + KeyStore dst = KeyStore.getInstance("WKS"); dst.load(null, dstPwd); + for (Enumeration e = src.aliases(); e.hasMoreElements();) { + String a = e.nextElement(); + if (src.isKeyEntry(a)) dst.setKeyEntry(a, src.getKey(a, srcPwd), dstPwd, src.getCertificateChain(a)); + else dst.setCertificateEntry(a, src.getCertificate(a)); + } + try (FileOutputStream f = new FileOutputStream(out)) { dst.store(f, dstPwd); } + System.out.println("Converted: " + in + " -> " + out); + } + static void addca(String ksPath, String caPath, String pwd, String alias) throws Exception { + KeyStore ks = KeyStore.getInstance("WKS"); + try (FileInputStream f = new FileInputStream(ksPath)) { ks.load(f, pwd.toCharArray()); } + if (!ks.containsAlias(alias)) { + ks.setCertificateEntry(alias, loadCert(caPath)); + try (FileOutputStream f = new FileOutputStream(ksPath)) { ks.store(f, pwd.toCharArray()); } + System.out.println(" Added test CA to cacerts as: " + alias); + } + System.out.println("Done. cacerts now has " + ks.size() + " entries"); + } + static void exportpem(String in, char[] srcPwd, String alias, String certOut, String keyOut, String chainOut) throws Exception { + KeyStore src = KeyStore.getInstance(storeTypeFor(in)); + try (FileInputStream f = new FileInputStream(in)) { src.load(f, srcPwd); } + Key key = src.getKey(alias, srcPwd); + if (!(key instanceof PrivateKey)) throw new IllegalArgumentException("Alias has no private key: " + alias); + Certificate cert = src.getCertificate(alias); + Certificate[] chain = src.getCertificateChain(alias); + writePem("PRIVATE KEY", key.getEncoded(), keyOut); + if (cert != null) writePem("CERTIFICATE", cert.getEncoded(), certOut); + if (chainOut != null && chain != null && chain.length > 0) { + try (Writer w = new OutputStreamWriter(new FileOutputStream(chainOut), StandardCharsets.US_ASCII)) { + for (Certificate c : chain) writePem(w, "CERTIFICATE", c.getEncoded()); + } + } + System.out.println("Exported PEM key/cert for alias " + alias + " from " + in); + } + static String storeTypeFor(String path) { + String p = path.toLowerCase(); + if (p.endsWith(".p12") || p.endsWith(".pfx")) return "PKCS12"; + if (p.endsWith(".wks")) return "WKS"; + return "JKS"; + } + static void writePem(String type, byte[] der, String out) throws Exception { + try (Writer w = new OutputStreamWriter(new FileOutputStream(out), StandardCharsets.US_ASCII)) { + writePem(w, type, der); + } + } + static void writePem(Writer w, String type, byte[] der) throws Exception { + w.write("-----BEGIN " + type + "-----\n"); + String b64 = Base64.getMimeEncoder(64, new byte[]{'\n'}).encodeToString(der); + w.write(b64); + if (!b64.endsWith("\n")) w.write("\n"); + w.write("-----END " + type + "-----\n"); + } + static java.security.cert.Certificate loadCert(String f) throws Exception { + try (FileInputStream in = new FileInputStream(f)) { + return java.security.cert.CertificateFactory.getInstance("X.509", "FilteredSun").generateCertificate(in); + } + } + static PrivateKey loadKey(String f) throws Exception { + String pem = new String(Files.readAllBytes(Paths.get(f))) + .replace("-----BEGIN PRIVATE KEY-----","").replace("-----END PRIVATE KEY-----","").replaceAll("\\s",""); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(pem)); + try { return KeyFactory.getInstance("RSA").generatePrivate(spec); } + catch (Exception e) { return KeyFactory.getInstance("EC").generatePrivate(spec); } + } +} +JAVA +RUN cd /app/util && /usr/local/openjdk-19/bin/javac --release 17 -cp /usr/share/java/wolfcrypt-jni.jar:/usr/share/java/wolfssl-jsse.jar WksUtil.java + +# Generate CA-signed test certificates with localhost SAN (openssl-free) +RUN cd /app/certs/test && \ + KT=/usr/lib/jvm/java-17-openjdk-amd64/bin/keytool && \ + JRUN17="/usr/lib/jvm/java-17-openjdk-amd64/bin/java -cp /app/util" && \ + P=wolfSSLFIPSPwd2024 && \ + $KT -genkeypair -alias ca -keyalg RSA -keysize 2048 -sigalg SHA256withRSA \ + -storetype PKCS12 -keystore ca.p12 -storepass "$P" -keypass "$P" \ + -dname "CN=Test CA" -validity 3650 \ + -ext bc:c=ca:true -ext ku:c=keyCertSign,cRLSign && \ + $KT -exportcert -rfc -alias ca -keystore ca.p12 -storetype PKCS12 \ + -storepass "$P" -file ca-cert.pem && \ + for alias in server client; do \ + CN=$([ "$alias" = server ] && echo localhost || echo client) && \ + $KT -genkeypair -alias "$alias" -keyalg RSA -keysize 2048 -sigalg SHA256withRSA \ + -storetype PKCS12 -keystore "$alias.p12" -storepass "$P" -keypass "$P" \ + -dname "CN=$CN" -validity 3650 -ext san=dns:localhost,ip:127.0.0.1 && \ + $KT -certreq -alias "$alias" -keystore "$alias.p12" -storetype PKCS12 \ + -storepass "$P" -file "$alias.csr" && \ + $KT -gencert -alias ca -keystore ca.p12 -storetype PKCS12 -storepass "$P" \ + -infile "$alias.csr" -rfc -outfile "$alias-signed.pem" -validity 3650 \ + -ext bc=ca:false -ext ku:c=digitalSignature,keyEncipherment \ + -ext eku=serverAuth,clientAuth -ext san=dns:localhost,ip:127.0.0.1 && \ + $KT -importcert -noprompt -alias ca -keystore "$alias.p12" -storetype PKCS12 \ + -storepass "$P" -file ca-cert.pem && \ + $KT -importcert -noprompt -alias "$alias" -keystore "$alias.p12" -storetype PKCS12 \ + -storepass "$P" -file "$alias-signed.pem" && \ + $JRUN17 WksUtil exportpem "$alias.p12" "$P" "$alias" "$alias-cert.pem" "$alias-key.pem"; \ + done && \ + rm -f *.csr *-signed.pem ca.p12 server.p12 client.p12 + +# Create WKS keystores script +RUN cat > /app/create-wks.sh <<'WKSEOF' && chmod +x /app/create-wks.sh +#!/bin/bash +FIPS_PWD="wolfSSLFIPSPwd2024"; CERTS="/app/certs/test"; SP="/app/spring-boot/spring-boot-project" +run() { /usr/local/openjdk-19/bin/java -cp /app/util:/usr/share/java/wolfcrypt-jni.jar:/usr/share/java/wolfssl-jsse.jar:/usr/share/java/filtered-providers.jar -Djava.library.path=/usr/lib/jni:/usr/local/lib WksUtil "$@"; } +conv() { /usr/lib/jvm/java-17-openjdk-amd64/bin/java -Xbootclasspath/a:/usr/share/java/wolfcrypt-jni.jar:/usr/share/java/wolfssl-jsse.jar -Djava.library.path=/usr/lib/jni:/usr/local/lib -cp /app/util:/usr/share/java/wolfcrypt-jni.jar:/usr/share/java/wolfssl-jsse.jar WksUtil "$@"; } + +echo "=== Converting JKS/P12 to WKS ===" +find "$SP" \( -name "*.jks" -o -name "*.p12" \) 2>/dev/null | while read -r f; do + w="${f%.*}.wks"; for p in password secret changeit; do conv convert "$f" "$p" "$w" "$FIPS_PWD" 2>/dev/null && break; done +done +# Delete old JKS/P12 files after conversion to avoid loading wrong format +echo "=== Deleting old JKS/P12 files ===" +find "$SP" -name "*.jks" -delete 2>/dev/null +find "$SP" -name "*.p12" -delete 2>/dev/null + +echo "=== Creating WKS keystores ===" +for d in "$SP/spring-boot/src/test/resources" "$SP/spring-boot-autoconfigure/src/test/resources" "$SP/spring-boot-actuator/src/test/resources"; do + [ -d "$d" ] && for n in test restricted test-expired test-not-yet-valid; do + [ ! -f "$d/$n.wks" ] && run combined "$CERTS/server-cert.pem" "$CERTS/server-key.pem" "$CERTS/ca-cert.pem" "$d/$n.wks" "test-alias" "$FIPS_PWD" 2>/dev/null + done && run trust "$CERTS/ca-cert.pem" "$d/truststore.wks" "ca" "$FIPS_PWD" 2>/dev/null +done + +for sub in rsocket amqp ssl cassandra data/redis data/mongo mongo mail; do + d="$SP/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/$sub" + mkdir -p "$d" 2>/dev/null; [ -d "$d" ] && { + [ ! -f "$d/test.wks" ] && run combined "$CERTS/server-cert.pem" "$CERTS/server-key.pem" "$CERTS/ca-cert.pem" "$d/test.wks" "test-alias" "$FIPS_PWD" 2>/dev/null + [ ! -f "$d/keystore.wks" ] && run combined "$CERTS/server-cert.pem" "$CERTS/server-key.pem" "$CERTS/ca-cert.pem" "$d/keystore.wks" "test-alias" "$FIPS_PWD" 2>/dev/null + [ ! -f "$d/truststore.wks" ] && run trust "$CERTS/ca-cert.pem" "$d/truststore.wks" "ca" "$FIPS_PWD" 2>/dev/null + } +done + +for d in "$SP/spring-boot/src/test/resources" "$SP/spring-boot-autoconfigure/src/test/resources"; do + [ -d "$d" ] && { + [ ! -f "$d/client.wks" ] && run combined "$CERTS/client-cert.pem" "$CERTS/client-key.pem" "$CERTS/ca-cert.pem" "$d/client.wks" "client" "$FIPS_PWD" 2>/dev/null + [ ! -f "$d/server-with-client-trust.wks" ] && run combined "$CERTS/server-cert.pem" "$CERTS/server-key.pem" "$CERTS/ca-cert.pem" "$d/server-with-client-trust.wks" "server" "$FIPS_PWD" 2>/dev/null + } +done +# Create WKS at direct classpath resource paths (not full package paths) +# Some tests reference e.g. "classpath:rsocket/test.wks" which maps to +# src/test/resources/rsocket/test.wks, not the org/springframework/... path. +AUTOCONFIG_RES="$SP/spring-boot-autoconfigure/src/test/resources" +for sub in rsocket amqp ssl; do + mkdir -p "$AUTOCONFIG_RES/$sub" 2>/dev/null + [ ! -f "$AUTOCONFIG_RES/$sub/test.wks" ] && run combined "$CERTS/server-cert.pem" "$CERTS/server-key.pem" "$CERTS/ca-cert.pem" "$AUTOCONFIG_RES/$sub/test.wks" "test-alias" "$FIPS_PWD" 2>/dev/null +done + +echo "=== WKS setup complete: $(find "$SP" -name "*.wks" 2>/dev/null | wc -l) files ===" +WKSEOF +RUN /app/create-wks.sh + +# Add test CA to all truststores +RUN cat > /app/add-ca.sh <<'ADDEOF' && chmod +x /app/add-ca.sh +#!/bin/bash +FIPS_PWD="wolfSSLFIPSPwd2024"; CA="/app/certs/test/ca-cert.pem"; SP="/app/spring-boot/spring-boot-project" +run() { /usr/local/openjdk-19/bin/java -cp /app/util:/usr/share/java/wolfcrypt-jni.jar:/usr/share/java/wolfssl-jsse.jar:/usr/share/java/filtered-providers.jar -Djava.library.path=/usr/lib/jni:/usr/local/lib WksUtil addca "$1" "$CA" "$FIPS_PWD" "test-ca" 2>/dev/null; } +echo "Adding test CA to all test truststores..." +find "$SP" \( -name "truststore.wks" -o -name "test.wks" \) 2>/dev/null | while read -r f; do echo " Checking: $f"; run "$f"; done +echo "Done adding test CA to truststores" +ADDEOF +RUN /app/add-ca.sh || true + +# Replace test PEM files with CA-signed certificates +# This is critical because Spring Boot tests that load PEM directly need CA-signed certs +RUN CERTS="/app/certs/test" && SP="/app/spring-boot/spring-boot-project" && \ + # Create certificate chain (server cert + CA cert) + cat "$CERTS/server-cert.pem" "$CERTS/ca-cert.pem" > /tmp/test-cert-chain.pem && \ + # Replace PEM files in all test resource directories + for d in "$SP/spring-boot/src/test/resources" \ + "$SP/spring-boot-autoconfigure/src/test/resources" \ + "$SP/spring-boot-actuator/src/test/resources"; do \ + [ -d "$d" ] && { \ + cp /tmp/test-cert-chain.pem "$d/test-cert.pem" 2>/dev/null || true; \ + cp "$CERTS/server-key.pem" "$d/test-key.pem" 2>/dev/null || true; \ + cp /tmp/test-cert-chain.pem "$d/test-cert-chain.pem" 2>/dev/null || true; \ + echo "Replaced PEM files in $d"; \ + }; \ + done && rm -f /tmp/test-cert-chain.pem && \ + echo "=== PEM file replacement complete ===" + +# Generate RSA certs for SSL reload test (replacing ED25519 originals which aren't FIPS-approved) +RUN cd /app/certs/test && \ + KT=/usr/lib/jvm/java-17-openjdk-amd64/bin/keytool && \ + JRUN17="/usr/lib/jvm/java-17-openjdk-amd64/bin/java -cp /app/util" && \ + P=wolfSSLFIPSPwd2024 && \ + for CN in 1 2; do \ + $KT -genkeypair -alias "reload${CN}" -keyalg RSA -keysize 2048 -sigalg SHA256withRSA \ + -storetype PKCS12 -keystore "reload${CN}.p12" -storepass "$P" -keypass "$P" \ + -dname "CN=${CN}" -validity 3650 && \ + $JRUN17 WksUtil exportpem "reload${CN}.p12" "$P" "reload${CN}" "reload${CN}-cert.pem" "reload${CN}-key.pem"; \ + done && \ + rm -f reload1.p12 reload2.p12 && \ + SP="/app/spring-boot/spring-boot-project" && \ + TOMCAT_RES="$SP/spring-boot/src/test/resources/org/springframework/boot/web/embedded/tomcat" && \ + cp reload1-cert.pem "$TOMCAT_RES/1.crt" && cp reload1-key.pem "$TOMCAT_RES/1.key" && \ + cp reload2-cert.pem "$TOMCAT_RES/2.crt" && cp reload2-key.pem "$TOMCAT_RES/2.key" && \ + echo "=== SSL reload test certs replaced (ED25519 -> RSA) ===" + +# Generate SslInfoTests-specific WKS keystores with self-signed certs and exact validity dates. +# Uses JDK keytool start dates (openssl/faketime not required). +RUN mkdir -p /app/certs/sslinfo && cd /app/certs/sslinfo && \ + KT=/usr/lib/jvm/java-17-openjdk-amd64/bin/keytool && \ + JRUN17="/usr/lib/jvm/java-17-openjdk-amd64/bin/java -cp /app/util" && \ + JRUN19="/usr/local/openjdk-19/bin/java -cp /app/util:/usr/share/java/wolfcrypt-jni.jar:/usr/share/java/wolfssl-jsse.jar:/usr/share/java/filtered-providers.jar -Djava.library.path=/usr/lib/jni:/usr/local/lib" && \ + P=wolfSSLFIPSPwd2024 && \ + DNAME="CN=localhost, OU=Spring, O=VMware, L=Palo Alto, ST=California, C=US" && \ + for name in valid expired not-yet-valid soon-to-expire; do \ + case "$name" in \ + valid) SD="" V=3650 ;; \ + expired) SD="-startdate 2020/01/01" V=30 ;; \ + not-yet-valid) SD="-startdate 2030/01/01" V=3650 ;; \ + soon-to-expire) SD="" V=5 ;; \ + esac && \ + $KT -genkeypair -alias spring-boot -keyalg RSA -keysize 2048 -sigalg SHA256withRSA \ + -storetype PKCS12 -keystore "${name}.p12" -storepass "$P" -keypass "$P" \ + -dname "$DNAME" $SD -validity $V && \ + $JRUN17 WksUtil exportpem "${name}.p12" "$P" "spring-boot" "${name}-cert.pem" "${name}-key.pem" && \ + $JRUN19 WksUtil selfsigned "${name}-cert.pem" "${name}-key.pem" "/app/certs/sslinfo/${name}.wks" "spring-boot" "$P"; \ + done && \ + rm -f *.p12 *.pem && \ + echo "=== SslInfoTests WKS keystores created ===" + +# Test runner +RUN cat > /app/run-tests.sh <<'EOF' && chmod +x /app/run-tests.sh +#!/bin/bash +cd /app/spring-boot; export JAVA_HOME="${GRADLE_JAVA_HOME}"; unset JAVA_TOOL_OPTIONS +RUNS=() +run() { local name="$1"; shift; RUNS+=("$name"); echo -e "\n=== Running: $name ==="; "$@" 2>&1 | tee "/tmp/${name}.log"; } +summary() { + for n in "${RUNS[@]}"; do + local p f s t + p=$(grep -c ' PASSED$' "/tmp/$n.log" 2>/dev/null) || p=0 + f=$(grep -c ') FAILED$' "/tmp/$n.log" 2>/dev/null) || f=0 + s=$(grep -c ' SKIPPED$' "/tmp/$n.log" 2>/dev/null) || s=0 + t=$((p + f + s)) + [ "$t" -gt 0 ] && echo "- $n: $t tests: $p passed, $f failed, $s skipped" + done +} + +./gradlew :spring-boot-project:spring-boot:clean :spring-boot-project:spring-boot-autoconfigure:clean :spring-boot-project:spring-boot-actuator:clean --no-daemon 2>/dev/null || true + +run spring-boot ./gradlew :spring-boot-project:spring-boot:test --tests "*Ssl*" --tests "*ssl*" --tests "*Pem*" --tests "*Jks*" --tests "*Keystore*" --tests "*KeyStore*" --no-daemon -x checkstyleMain -x checkstyleTest -x checkFormat -x :buildSrc:test -x :buildSrc:check --continue || true +run spring-boot-autoconfigure ./gradlew :spring-boot-project:spring-boot-autoconfigure:test --tests "*Ssl*" --tests "*ssl*" --no-daemon -x checkstyleMain -x checkstyleTest -x checkFormat -x :buildSrc:test -x :buildSrc:check --continue || true +run spring-boot-actuator ./gradlew :spring-boot-project:spring-boot-actuator:test --tests "*Ssl*" --tests "*ssl*" --no-daemon -x checkstyleMain -x checkstyleTest -x checkFormat -x :buildSrc:test -x :buildSrc:check --continue || true + +echo -e "\n=== TEST SUMMARY ==="; summary +EOF + +CMD ["/app/run-tests.sh"] diff --git a/java/wolfssl-openjdk-fips-root/test-images/spring-boot-tests/README.md b/java/wolfssl-openjdk-fips-root/test-images/spring-boot-tests/README.md new file mode 100644 index 0000000..71a62c1 --- /dev/null +++ b/java/wolfssl-openjdk-fips-root/test-images/spring-boot-tests/README.md @@ -0,0 +1,66 @@ +# Spring Boot SSL Tests with wolfJSSE FIPS + +This directory contains a Docker image for running Spring Boot SSL/TLS test suites using wolfJSSE and wolfJCE in FIPS 140-3 mode. + +## Overview + +This Docker image builds and runs Spring Boot's SSL-related test suites with wolfSSL's FIPS 140-3 validated cryptographic library (Certificate #4718), replacing all non-FIPS compliant Java cryptography providers with wolfJCE and wolfJSSE. + +## Prerequisites + +- The wolfssl-openjdk-fips-root base image built and available + +## Building the Image + +```bash +docker build -t spring-boot-wolfjsse-fips . +``` + +You can specify a different Spring Boot version using build args: + +```bash +docker build --build-arg SPRING_BOOT_TAG=v3.4.1 -t spring-boot-wolfjsse-fips . +``` + +## Running Tests + +To run the SSL test suite: + +```bash +docker run --rm spring-boot-wolfjsse-fips +``` + +The container will automatically: +1. Apply FIPS-compatibility patches to Spring Boot source +2. Convert JKS/PKCS12 keystores to WKS format +3. Generate CA-signed test certificates +4. Run SSL-related tests from spring-boot, spring-boot-autoconfigure, and spring-boot-actuator + +## FIPS Modifications + +The image includes several modifications for FIPS compliance: + +### Keystore Format +- All JKS and PKCS12 keystores are converted to WKS (wolfSSL KeyStore) format +- WKS is the only keystore format that works with wolfJSSE in FIPS mode + +### Password Requirements +- All keystore passwords are changed to meet FIPS requirements (minimum 14 characters) +- Default password: `wolfSSLFIPSPwd2024` + +### Certificate Requirements +- All test certificates are CA-signed (self-signed certificates fail native wolfSSL validation) +- Certificates include proper Subject Alternative Names (SAN) for localhost + +### Test Exclusions +Some tests are disabled due to FIPS incompatibilities: +- Tests using non-FIPS algorithms (DSA, EdDSA, PBES2) +- Tests requiring JKS format with PBEWithMD5AndTripleDES +- Netty/Reactor SSL tests (InsecureTrustManagerFactory incompatible) + +## Test Results + +The container outputs a summary showing: +- Number of tests run per module +- Pass/fail/skip counts +- Detailed logs are saved in `/tmp/*.log` within the container diff --git a/java/wolfssl-openjdk-fips-root/test-images/spring-boot-tests/apply_spring_fips_fixes.sh b/java/wolfssl-openjdk-fips-root/test-images/spring-boot-tests/apply_spring_fips_fixes.sh new file mode 100755 index 0000000..3bc0181 --- /dev/null +++ b/java/wolfssl-openjdk-fips-root/test-images/spring-boot-tests/apply_spring_fips_fixes.sh @@ -0,0 +1,668 @@ +#!/bin/bash +# ============================================================================== +# Spring Boot FIPS Compatibility Fixes for wolfJSSE +# ============================================================================== +# Simplified version based on working non-FIPS approach, with FIPS-specific additions. +# +# FIPS Requirements: +# 1. WKS keystore format (not JKS/PKCS12) +# 2. FIPS-compliant passwords (min 14 chars for HMAC PBKDF2) +# 3. CA-signed certificates (self-signed fail native wolfSSL validation) +# ============================================================================== + +# Don't use set -e due to subshell/pipeline interactions + +SPRING_BOOT_DIR="${1:-/app/spring-boot}" +BOOT_MAIN="${SPRING_BOOT_DIR}/spring-boot-project/spring-boot/src/main/java/org/springframework/boot" +BOOT_TEST="${SPRING_BOOT_DIR}/spring-boot-project/spring-boot/src/test/java/org/springframework/boot" +AUTOCONFIG_TEST="${SPRING_BOOT_DIR}/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure" + +echo "=== Applying Spring Boot FIPS Fixes ===" + +# FIPS-compliant password (minimum 14 characters for HMAC PBKDF2) +FIPS_PASSWORD="wolfSSLFIPSPwd2024" + +# ============================================================================== +# SECTION 1: Replace short passwords with FIPS-compliant ones +# ============================================================================== +echo "" +echo "=== SECTION 1: Password replacements for FIPS compliance ===" + +# Replace common short passwords in SSL-related test files +find "${SPRING_BOOT_DIR}/spring-boot-project" -name "*.java" -type f 2>/dev/null | while IFS= read -r file; do + # Check basename for SSL-related names OR file contents for SSL patterns + if echo "$(basename "$file")" | grep -qE 'Ssl|ssl|Pem|Jks|KeyStore|TrustStore|WebServer|Servlet|Reactive|Redis|Mongo|Mail|Cassandra|Couchbase|RSocket|Elasticsearch|Rabbit|Kafka' 2>/dev/null || \ + grep -qE 'getSsl|setSsl|SslBundle|ssl\.bundle|\.wks|\.jks|\.p12|keystore\.password|key\.password' "$file" 2>/dev/null; then + modified=false + for pwd in "secret" "password" "changeit" "changeme" "testpass" "storepass" "keypass"; do + # Replace "password" format (Java strings) + if grep -q "\"${pwd}\"" "$file" 2>/dev/null; then + sed -i "s/\"${pwd}\"/\"${FIPS_PASSWORD}\"/g" "$file" + modified=true + fi + # Replace :password" format (property strings like keystore.password:secret") + if grep -q ":${pwd}\"" "$file" 2>/dev/null; then + sed -i "s/:${pwd}\"/:${FIPS_PASSWORD}\"/g" "$file" + modified=true + fi + # Replace =password" format (property strings like keystore.password=secret") + if grep -q "=${pwd}\"" "$file" 2>/dev/null; then + sed -i "s/=${pwd}\"/=${FIPS_PASSWORD}\"/g" "$file" + modified=true + fi + done + [ "$modified" = true ] && echo " Updated passwords in $(basename "$file")" + fi +done + +# ============================================================================== +# SECTION 2: Fix null password handling in PemSslStoreBundle +# ============================================================================== +echo "" +echo "=== SECTION 2: Null password fixes ===" + +PEM_BUNDLE="${BOOT_MAIN}/ssl/pem/PemSslStoreBundle.java" +if [ -f "$PEM_BUNDLE" ] && ! grep -q "FIPS_DEFAULT_PASSWORD" "$PEM_BUNDLE"; then + sed -i '/^public class PemSslStoreBundle/a\ +\ + // Default password for wolfJSSE WKS keystore (requires non-null, min 14 chars for FIPS HMAC)\ + private static final char[] FIPS_DEFAULT_PASSWORD = "wolfSSLFIPSPwd2024".toCharArray();' "$PEM_BUNDLE" + # Fix setKeyEntry: replace null fallback with FIPS default password + sed -i 's/(keyPassword != null) ? keyPassword\.toCharArray() : null/(keyPassword != null) ? keyPassword.toCharArray() : FIPS_DEFAULT_PASSWORD/g' "$PEM_BUNDLE" + # Fix store.load(null) -> store.load(null, FIPS_DEFAULT_PASSWORD) for WKS compatibility + sed -i 's/store\.load(null);/store.load(null, FIPS_DEFAULT_PASSWORD);/' "$PEM_BUNDLE" + # Fix getKeyStorePassword() to return FIPS password instead of null. + # When PemSslStoreBundle stores a key with FIPS_DEFAULT_PASSWORD, Tomcat's + # SslConnectorCustomizer calls getKeyStorePassword() to retrieve the key. + # If it returns null, KeyStore.getKey(alias, null) throws UnrecoverableKeyException. + sed -i '/getKeyStorePassword/,/return null;/{s/return null;/return "wolfSSLFIPSPwd2024";/}' "$PEM_BUNDLE" + echo " Fixed PemSslStoreBundle.java null password handling" +fi + +# Fix store.load(null, null) patterns +find "${SPRING_BOOT_DIR}/spring-boot-project" -name "*.java" -type f 2>/dev/null | while IFS= read -r file; do + if grep -q 'store\.load(null, null)' "$file" 2>/dev/null; then + sed -i 's/store\.load(null, null)/store.load(null, "'"${FIPS_PASSWORD}"'".toCharArray())/g' "$file" + echo " Fixed store.load(null,null) in $(basename "$file")" + fi +done + +# ============================================================================== +# SECTION 3: Change keystore type from JKS/PKCS12 to WKS +# ============================================================================== +echo "" +echo "=== SECTION 3: Keystore type changes (JKS/PKCS12 -> WKS) ===" + +find "${SPRING_BOOT_DIR}/spring-boot-project" -name "*.java" -type f 2>/dev/null | while IFS= read -r file; do + modified=false + if grep -q 'KeyStore\.getInstance("JKS")' "$file" 2>/dev/null; then + sed -i 's/KeyStore\.getInstance("JKS")/KeyStore.getInstance("WKS")/g' "$file" + modified=true + fi + if grep -q 'KeyStore\.getInstance("PKCS12")' "$file" 2>/dev/null; then + sed -i 's/KeyStore\.getInstance("PKCS12")/KeyStore.getInstance("WKS")/g' "$file" + modified=true + fi + if grep -q 'KeyStore\.getInstance("pkcs12")' "$file" 2>/dev/null; then + sed -i 's/KeyStore\.getInstance("pkcs12")/KeyStore.getInstance("WKS")/g' "$file" + modified=true + fi + if grep -q 'KeyStore\.getInstance("jks")' "$file" 2>/dev/null; then + sed -i 's/KeyStore\.getInstance("jks")/KeyStore.getInstance("WKS")/g' "$file" + modified=true + fi + [ "$modified" = true ] && echo " Changed keystore type in $(basename "$file")" +done + +# Fix JksSslStoreBundle to default to WKS type +JKS_BUNDLE="${BOOT_MAIN}/ssl/jks/JksSslStoreBundle.java" +if [ -f "$JKS_BUNDLE" ]; then + # Replace KeyStore.getDefaultType() with "WKS" for FIPS compliance + if grep -q 'KeyStore\.getDefaultType()' "$JKS_BUNDLE"; then + sed -i 's/KeyStore\.getDefaultType()/"WKS"/g' "$JKS_BUNDLE" + echo " Changed KeyStore.getDefaultType() to WKS in JksSslStoreBundle.java" + fi + # Also change any type == null checks that default to JKS + if grep -q '"JKS"' "$JKS_BUNDLE"; then + sed -i 's/"JKS"/"WKS"/g' "$JKS_BUNDLE" + echo " Changed JKS string literals to WKS in JksSslStoreBundle.java" + fi +fi + +# Fix JksSslStoreDetails default type +JKS_DETAILS="${BOOT_MAIN}/ssl/jks/JksSslStoreDetails.java" +if [ -f "$JKS_DETAILS" ]; then + if grep -q 'KeyStore\.getDefaultType()' "$JKS_DETAILS"; then + sed -i 's/KeyStore\.getDefaultType()/"WKS"/g' "$JKS_DETAILS" + echo " Changed KeyStore.getDefaultType() to WKS in JksSslStoreDetails.java" + fi +fi + +# ============================================================================== +# SECTION 3.5: Replace keystore TYPE string values in test files +# ============================================================================== +echo "" +echo "=== SECTION 3.5: Keystore type string replacements ===" + +# Replace setKeyStoreType("JKS") and similar patterns in test files +find "${SPRING_BOOT_DIR}/spring-boot-project" -name "*.java" -type f 2>/dev/null | while IFS= read -r file; do + modified=false + # setKeyStoreType("JKS") -> setKeyStoreType("WKS") + if grep -q 'setKeyStoreType("JKS")' "$file" 2>/dev/null; then + sed -i 's/setKeyStoreType("JKS")/setKeyStoreType("WKS")/g' "$file" + modified=true + fi + if grep -q 'setKeyStoreType("PKCS12")' "$file" 2>/dev/null; then + sed -i 's/setKeyStoreType("PKCS12")/setKeyStoreType("WKS")/g' "$file" + modified=true + fi + if grep -q 'setTrustStoreType("JKS")' "$file" 2>/dev/null; then + sed -i 's/setTrustStoreType("JKS")/setTrustStoreType("WKS")/g' "$file" + modified=true + fi + if grep -q 'setTrustStoreType("PKCS12")' "$file" 2>/dev/null; then + sed -i 's/setTrustStoreType("PKCS12")/setTrustStoreType("WKS")/g' "$file" + modified=true + fi + # Also fix .type("JKS") and .type("PKCS12") builder patterns + if grep -q '\.type("JKS")' "$file" 2>/dev/null; then + sed -i 's/\.type("JKS")/.type("WKS")/g' "$file" + modified=true + fi + if grep -q '\.type("PKCS12")' "$file" 2>/dev/null; then + sed -i 's/\.type("PKCS12")/.type("WKS")/g' "$file" + modified=true + fi + # Also fix setType("JKS/PKCS12") setter patterns + if grep -q 'setType("JKS")' "$file" 2>/dev/null; then + sed -i 's/setType("JKS")/setType("WKS")/g' "$file" + modified=true + fi + if grep -q 'setType("PKCS12")' "$file" 2>/dev/null; then + sed -i 's/setType("PKCS12")/setType("WKS")/g' "$file" + modified=true + fi + # Also fix Spring property strings like key-store-type=jks or trust-store-type=pkcs12 + for old_type in jks pkcs12 PKCS12 JKS; do + if grep -q "store-type=${old_type}\"" "$file" 2>/dev/null; then + sed -i "s/store-type=${old_type}\"/store-type=WKS\"/g" "$file" + modified=true + fi + done + # Fix Spring config property .type=PKCS12 and .type=JKS patterns + for old_type in PKCS12 JKS pkcs12 jks; do + if grep -q "\.type=${old_type}\"" "$file" 2>/dev/null; then + sed -i "s/\.type=${old_type}\"/\.type=WKS\"/g" "$file" + modified=true + fi + done + [ "$modified" = true ] && echo " Changed keystore type properties in $(basename "$file")" +done + +# ============================================================================== +# SECTION 4: Replace .jks/.p12 file references with .wks +# ============================================================================== +echo "" +echo "=== SECTION 4: File extension changes (.jks/.p12 -> .wks) ===" + +find "${SPRING_BOOT_DIR}/spring-boot-project" -name "*.java" -type f 2>/dev/null | while IFS= read -r file; do + modified=false + if grep -q '\.jks"' "$file" 2>/dev/null; then + sed -i 's/\.jks"/.wks"/g' "$file" + modified=true + fi + if grep -q '\.p12"' "$file" 2>/dev/null; then + sed -i 's/\.p12"/.wks"/g' "$file" + modified=true + fi + [ "$modified" = true ] && echo " Changed file extensions in $(basename "$file")" +done + +# ============================================================================== +# SECTION 4.5: Fix getStoreType method in test files +# ============================================================================== +echo "" +echo "=== SECTION 4.5: Fix getStoreType methods ===" + +# After section 4 changes .p12 to .wks, the getStoreType method still returns "pkcs12" +# We need to change it to return "WKS" for .wks files +SERVLET_TESTS="${BOOT_TEST}/web/servlet/server/AbstractServletWebServerFactoryTests.java" +if [ -f "$SERVLET_TESTS" ]; then + # Fix the getStoreType method that returns wrong type for .wks files + if grep -q 'endsWith(".wks") ? "pkcs12"' "$SERVLET_TESTS" 2>/dev/null; then + sed -i 's/endsWith(".wks") ? "pkcs12"/endsWith(".wks") ? "WKS"/g' "$SERVLET_TESTS" + echo " Fixed getStoreType in AbstractServletWebServerFactoryTests.java" + fi +fi + +# ============================================================================== +# SECTION 5: Disable incompatible test classes +# ============================================================================== +echo "" +echo "=== SECTION 5: Disabling incompatible test classes ===" + +disable_test_class() { + local file="$1" + local reason="$2" + [ ! -f "$file" ] && return 0 + + local class_line=$(grep -n -E '^(public )?(abstract )?class ' "$file" | head -1 | cut -d: -f1) + [ -z "$class_line" ] && return 0 + + # Skip if already disabled + local check_start=$((class_line - 3)) + [ $check_start -lt 1 ] && check_start=1 + sed -n "${check_start},${class_line}p" "$file" | grep -q "@Disabled" && return 0 + + # Add import and annotation + grep -q "import org.junit.jupiter.api.Disabled;" "$file" || \ + sed -i '/^package /a\import org.junit.jupiter.api.Disabled;' "$file" + class_line=$(grep -n -E '^(public )?(abstract )?class ' "$file" | head -1 | cut -d: -f1) + sed -i "${class_line}i\\@Disabled(\"${reason}\")" "$file" + echo " Disabled: $(basename "$file")" +} + +disable_test_method() { + local file="$1" + local method="$2" + local reason="$3" + [ ! -f "$file" ] && return 0 + + # Find method - handle both parameterized and regular tests + local method_line=$(grep -n "void ${method}(" "$file" | head -1 | cut -d: -f1) + [ -z "$method_line" ] && return 0 + + # Check if already disabled + local check_start=$((method_line - 5)) + [ $check_start -lt 1 ] && check_start=1 + sed -n "${check_start},${method_line}p" "$file" | grep -q "@Disabled" && return 0 + + # Add import if needed + grep -q "import org.junit.jupiter.api.Disabled;" "$file" || \ + sed -i '/^package /a\import org.junit.jupiter.api.Disabled;' "$file" + + # Find @Test annotation before method and add @Disabled after it + method_line=$(grep -n "void ${method}(" "$file" | head -1 | cut -d: -f1) + local search_start=$((method_line - 10)) + [ $search_start -lt 1 ] && search_start=1 + for i in $(seq $method_line -1 $search_start); do + if sed -n "${i}p" "$file" | grep -qE '^\s*@(Test|ParameterizedTest)'; then + sed -i "${i}a\\ @Disabled(\"${reason}\")" "$file" + echo " Disabled: $method" + break + fi + done +} + +# PemPrivateKeyParserTests: Most methods are parser/format tests and can run in FIPS. +# Patch/disable only the unsupported pieces: +# - DSA row in shouldParseTraditionalPkcs8 +# - EC curve toString() assertions (provider formatting differs) +# - EdDSA parsing (not available in FIPS) +# - Encrypted PKCS#8 success path (PBES2 encrypted key parsing unsupported in FIPS) +PEM_PRIV_KEY_TESTS="${BOOT_TEST}/ssl/pem/PemPrivateKeyParserTests.java" +if [ -f "$PEM_PRIV_KEY_TESTS" ]; then + # Remove the DSA success row; RSA row remains. + sed -i '/"dsa\.key,.*DSA"/d' "$PEM_PRIV_KEY_TESTS" + + # wolfJCE parses rsa-pss PKCS#8 private key but reports algorithm "RSA" + # (provider naming difference vs Spring's expected "RSASSA-PSS"). + sed -i '/rsa-pss\.key.*RSASSA-PSS/s/RSASSA-PSS/RSA/' "$PEM_PRIV_KEY_TESTS" + + # Relax provider-specific ECParameterSpec.toString() formatting assertions + sed -i 's/assertThat(ecPrivateKey.getParams().toString()).contains(curveName).contains(oid);/assertThat(ecPrivateKey.getParams()).isNotNull();/' "$PEM_PRIV_KEY_TESTS" + + echo " Patched PemPrivateKeyParserTests.java (DSA row removed, EC assertion relaxed)" +fi +disable_test_method "${BOOT_TEST}/ssl/pem/PemPrivateKeyParserTests.java" \ + "shouldParseEdDsaPkcs8" "wolfJCE FIPS: EdDSA not available" +disable_test_method "${BOOT_TEST}/ssl/pem/PemPrivateKeyParserTests.java" \ + "shouldParseXdhPkcs8" "wolfJCE FIPS: XDH not available" +disable_test_method "${BOOT_TEST}/ssl/pem/PemPrivateKeyParserTests.java" \ + "shouldParseEncryptedPkcs8" "wolfJCE FIPS: PBES2 encrypted PKCS#8 parsing unsupported" + +# PemContentTests: switch the DSA-specific parser sanity check to RSA so the +# test still validates PKCS#8 private key loading in FIPS mode. +PEM_CONTENT_TESTS="${BOOT_TEST}/ssl/pem/PemContentTests.java" +if [ -f "$PEM_CONTENT_TESTS" ]; then + sed -i '/void getPrivateKeyReturnsPrivateKey/,/^\t}/ s#/pkcs8/dsa\.key#/pkcs8/rsa.key#' "$PEM_CONTENT_TESTS" + sed -i '/void getPrivateKeyReturnsPrivateKey/,/^\t}/ s/isEqualTo(\"DSA\")/isEqualTo(\"RSA\")/' "$PEM_CONTENT_TESTS" + echo " Patched PemContentTests.java (DSA fixture -> RSA for FIPS)" +fi + +# CertificateMatcherTests: parameter source includes DSA/EdDSA algorithms that may +# be unavailable in FIPS. Patch the source to skip unsupported algorithms instead +# of disabling the whole class (RSA/EC cases still validate matching behavior). +CERT_MATCH_SRC="${AUTOCONFIG_TEST}/ssl/CertificateMatchingTestSource.java" +if [ -f "$CERT_MATCH_SRC" ]; then + # Patch the single key generation line directly (more robust than matching the full loop). + perl -0777 -i -pe ' + s~keyPairs\.put\(algorithm, algorithm\.generateKeyPair\(\)\);~ +\t\t\ttry { +\t\t\t\tkeyPairs.put(algorithm, algorithm.generateKeyPair()); +\t\t\t} +\t\t\tcatch (NoSuchAlgorithmException | InvalidAlgorithmParameterException ex) { +\t\t\t\t// wolfJSSE FIPS test image: skip unsupported algorithms (e.g. DSA/EdDSA) +\t\t\t}~s + ' "$CERT_MATCH_SRC" + + # Fallback for upstream formatting changes: remove unsupported algorithms from the list. + if ! grep -q 'skip unsupported algorithms' "$CERT_MATCH_SRC"; then + sed -i 's/Stream.of("RSA", "DSA", "ed25519", "ed448")/Stream.of("RSA")/' "$CERT_MATCH_SRC" + echo " Patched CertificateMatchingTestSource.java (fallback: removed unsupported algorithms)" + else + echo " Patched CertificateMatchingTestSource.java to skip unsupported algorithms" + fi +fi + +# PemSslStoreBundleTests: Most tests work with RSA PEM from classpath. Patch explicit +# PKCS12/short-password cases for WKS+FIPS, and disable only the remaining incompatible tests. +PEM_STORE_TESTS="${BOOT_TEST}/ssl/pem/PemSslStoreBundleTests.java" +if [ -f "$PEM_STORE_TESTS" ]; then + # createWithDetailsWhenHasStoreType: explicit PKCS12 is not available in FIPS image. + # Keep the test intent (explicit store type honored) but use WKS. + sed -i '/createWithDetailsWhenHasStoreType/,/^\t}/ s/"PKCS12"/"WKS"/g' "$PEM_STORE_TESTS" + + # createWithDetailsWhenHasKeyStoreDetailsAndTrustStoreDetailsAndKeyPassword: + # Short passwords ("kss"/"tss") violate FIPS minimum. Use the standard FIPS test password. + sed -i '/createWithDetailsWhenHasKeyStoreDetailsAndTrustStoreDetailsAndKeyPassword/,/^\t}/ s/withPassword(\"kss\")/withPassword(\"'"${FIPS_PASSWORD}"'\")/g' "$PEM_STORE_TESTS" + sed -i '/createWithDetailsWhenHasKeyStoreDetailsAndTrustStoreDetailsAndKeyPassword/,/^\t}/ s/withPassword(\"tss\")/withPassword(\"'"${FIPS_PASSWORD}"'\")/g' "$PEM_STORE_TESTS" + # Update assertion passwords too (WKS key retrieval requires the actual entry password). + sed -i '/createWithDetailsWhenHasKeyStoreDetailsAndTrustStoreDetailsAndKeyPassword/,/^\t}/ s/"kss"\\.toCharArray()/"'"${FIPS_PASSWORD}"'".toCharArray()/g' "$PEM_STORE_TESTS" + sed -i '/createWithDetailsWhenHasKeyStoreDetailsAndTrustStoreDetailsAndKeyPassword/,/^\t}/ s/"tss"\\.toCharArray()/"'"${FIPS_PASSWORD}"'".toCharArray()/g' "$PEM_STORE_TESTS" + + # Fallback if sed did not match due to source formatting changes. + perl -0777 -i -pe ' + s|(void createWithDetailsWhenHasKeyStoreDetailsAndTrustStoreDetailsAndKeyPassword\(\) \{.*?assertThat\(bundle\.getKeyStore\(\)\)\.satisfies\(storeContainingCertAndKey\("ksa", )"kss"\.toCharArray\(\)(\)\);)|$1"'"${FIPS_PASSWORD}"'".toCharArray()$2|s; + s|(void createWithDetailsWhenHasKeyStoreDetailsAndTrustStoreDetailsAndKeyPassword\(\) \{.*?assertThat\(bundle\.getTrustStore\(\)\)\.satisfies\(storeContainingCertAndKey\("tsa", )"tss"\.toCharArray\(\)(\)\);)|$1"'"${FIPS_PASSWORD}"'".toCharArray()$2|s; + ' "$PEM_STORE_TESTS" + + # Null/empty store cases: WKS/FIPS returns a non-null keystore password because + # WKS always uses password-based protection. Preserve test intent (no stores). + sed -i '/createWithDetailsWhenNullStores/,/^\t}/ s/assertThat(bundle.getKeyStorePassword()).isNull();/assertThat(bundle.getKeyStorePassword()).isNotNull();/' "$PEM_STORE_TESTS" + sed -i '/createWithDetailsWhenStoresHaveNoValues/,/^\t}/ s/assertThat(bundle.getKeyStorePassword()).isNull();/assertThat(bundle.getKeyStorePassword()).isNotNull();/' "$PEM_STORE_TESTS" + + # createWithDetailsWhenHasKeyStoreDetailsAndTrustStoreDetailsWithoutKey: + # WKS may assign non-JKS alias names and include multiple cert-only aliases. + # Preserve test intent by asserting the trust store contains only cert entries + # (no private keys) and at least one certificate, without alias-specific checks. + perl -0777 -i -pe ' + s|assertThat\(bundle\.getTrustStore\(\)\)\.satisfies\(storeContainingCert\("ssl"\)\);| +\t\tassertThat(bundle.getTrustStore()).satisfies(ThrowingConsumer.of((keyStore) -> { +\t\t\tassertThat(keyStore).isNotNull(); +\t\t\tassertThat(keyStore.getType()).isEqualTo(KeyStore.getDefaultType()); +\t\t\tassertThat(keyStore.size()).isGreaterThanOrEqualTo(1); +\t\t\tboolean sawCert = false; +\t\t\tjava.util.Enumeration aliases = keyStore.aliases(); +\t\t\twhile (aliases.hasMoreElements()) { +\t\t\t\tString alias = aliases.nextElement(); +\t\t\t\tif (keyStore.getCertificate(alias) != null) { +\t\t\t\t\tsawCert = true; +\t\t\t\t} +\t\t\t\tassertThat(keyStore.getKey(alias, EMPTY_KEY_PASSWORD)).isNull(); +\t\t\t} +\t\t\tassertThat(sawCert).isTrue(); +\t\t}));|s + ' "$PEM_STORE_TESTS" +fi +disable_test_method "${BOOT_TEST}/ssl/pem/PemSslStoreBundleTests.java" \ + "createWithDetailsWhenHasKeyStoreDetailsCertAndEncryptedKey" "wolfJCE FIPS: PBES2 encrypted key" + +# JKS tests use PBEWithMD5AndTripleDES (not FIPS-approved) +disable_test_class "${BOOT_TEST}/ssl/jks/JksSslStoreBundleTests.java" \ + "FIPS: JKS keystore format uses non-approved algorithms" + +# ============================================================================== +# SECTION 6: Disable incompatible test methods and classes +# ============================================================================== +echo "" +echo "=== SECTION 6: Disabling incompatible test methods ===" + +# SslInfoTests: Patch to use FIPS-compatible WKS keystores with self-signed certs. +# The Dockerfile creates keystores in /app/certs/sslinfo/ with exact validity dates +# (valid, expired, not-yet-valid, soon-to-expire) using keytool start dates + WksUtil. +# We patch: classpath references -> absolute file paths, createKeyStore -> copy WKS, +# validCertificatesShouldProvideSslInfo assertions (1 chain not 4), cert count in multipleBundles. +SSLINFO_FILE="${BOOT_TEST}/info/SslInfoTests.java" +if [ -f "$SSLINFO_FILE" ]; then + # Simple string replacements (classpath -> absolute paths, cert count) + sed -i \ + -e 's|"classpath:test.wks"|"/app/certs/sslinfo/valid.wks"|g' \ + -e 's|"classpath:test-expired.wks"|"/app/certs/sslinfo/expired.wks"|g' \ + -e 's|"classpath:test-not-yet-valid.wks"|"/app/certs/sslinfo/not-yet-valid.wks"|g' \ + -e 's|assertThat(certs).hasSize(5)|assertThat(certs).hasSize(4)|g' \ + "$SSLINFO_FILE" + + # Multiline replacements using perl (already installed in builder stage): + # 1. Rewrite validCertificatesShouldProvideSslInfo method body + # 2. Replace createKeyStore to copy pre-built WKS instead of running keytool + # 3. Remove createProcessBuilder method (no longer needed) + perl -0777 -i -pe ' + # Rewrite validCertificatesShouldProvideSslInfo: our valid.wks has 1 key entry + # (alias "spring-boot") instead of the original JKS 2 key + 2 trusted cert entries + s|\tvoid validCertificatesShouldProvideSslInfo\(\) \{.*?\n\t\}| +\tvoid validCertificatesShouldProvideSslInfo() { +\t\tSslInfo sslInfo = createSslInfo("/app/certs/sslinfo/valid.wks"); +\t\tassertThat(sslInfo.getBundles()).hasSize(1); +\t\tBundleInfo bundle = sslInfo.getBundles().get(0); +\t\tassertThat(bundle.getName()).isEqualTo("test-0"); +\t\tassertThat(bundle.getCertificateChains()).hasSize(1); +\t\tCertificateChainInfo chain = bundle.getCertificateChains().get(0); +\t\tassertThat(chain.getAlias()).isEqualTo("spring-boot"); +\t\tassertThat(chain.getCertificates()).hasSize(1); +\t\tCertificateInfo cert = chain.getCertificates().get(0); +\t\tassertThat(cert.getSubject()).isEqualTo("CN=localhost,OU=Spring,O=VMware,L=Palo Alto,ST=California,C=US"); +\t\tassertThat(cert.getIssuer()).isEqualTo(cert.getSubject()); +\t\tassertThat(cert.getSerialNumber()).isNotEmpty(); +\t\tassertThat(cert.getVersion()).isEqualTo("V3"); +\t\tassertThat(cert.getSignatureAlgorithmName()).isEqualTo("SHA256withRSA"); +\t\tassertThat(cert.getValidityStarts()).isInThePast(); +\t\tassertThat(cert.getValidityEnds()).isInTheFuture(); +\t\tassertThat(cert.getValidity()).isNotNull(); +\t\tassertThat(cert.getValidity().getStatus()).isSameAs(Status.VALID); +\t\tassertThat(cert.getValidity().getMessage()).isNull(); +\t}|s; + + # Replace createKeyStore: copy pre-built WKS instead of running keytool + s|\tprivate Path createKeyStore\(Path directory\).*?\n\t\}| +\tprivate Path createKeyStore(Path directory) throws IOException, InterruptedException { +\t\tPath keyStore = directory.resolve("test.wks"); +\t\tjava.nio.file.Files.copy(java.nio.file.Path.of("/app/certs/sslinfo/soon-to-expire.wks"), keyStore); +\t\treturn keyStore; +\t}|s; + + # Remove createProcessBuilder method (no longer needed) + s|\n\tprivate ProcessBuilder createProcessBuilder\(Path keystore\).*?\n\t\}\n|\n|s; + ' "$SSLINFO_FILE" + echo " Patched SslInfoTests.java for FIPS WKS keystores" +fi + +# sslWithPemCertificates: Client uses Reactor Netty's InsecureTrustManagerFactory +# through compiled jars. Native wolfSSL verification rejects the cert before the +# Java TrustManager callback fires, causing SSLHandshakeException. +disable_test_method "${BOOT_TEST}/web/reactive/server/AbstractReactiveWebServerFactoryTests.java" \ + "sslWithPemCertificates" "Reactor Netty client InsecureTMF in compiled jars" + +# TLSv1.1 disabled by JDK security policy +disable_test_method "${BOOT_TEST}/web/embedded/tomcat/SslConnectorCustomizerTests.java" \ + "sslEnabledMultipleProtocolsConfiguration" "TLSv1.1 disabled by JDK policy" + +# wolfSSL doesn't support static RSA cipher suites (no forward secrecy) +disable_test_method "${BOOT_TEST}/web/embedded/undertow/UndertowServletWebServerFactoryTests.java" \ + "sslRestrictedProtocolsRSATLS12Success" "wolfSSL: Static RSA ciphers not supported" + +# PEM getSsl() (2-arg version) doesn't set keyPassword. WKS requires the correct +# password to retrieve private keys. Add keyPassword to match FIPS_DEFAULT_PASSWORD. +SERVLET_TESTS="${BOOT_TEST}/web/servlet/server/AbstractServletWebServerFactoryTests.java" +if [ -f "$SERVLET_TESTS" ] && ! grep -A1 'ssl\.setTrustCertificate(cert);' "$SERVLET_TESTS" | grep -q 'setKeyPassword'; then + sed -i '/ssl\.setTrustCertificate(cert);/{ +a\ ssl.setKeyPassword("'"${FIPS_PASSWORD}"'"); +}' "$SERVLET_TESTS" + echo " Added keyPassword to getSsl(cert,key) in AbstractServletWebServerFactoryTests.java" +fi + +# JKS/SUN provider tests +disable_test_method "${AUTOCONFIG_TEST}/ssl/PropertiesSslBundleTests.java" \ + "jksPropertiesAreMappedToSslBundle" "FIPS: JKS requires SUN provider" +disable_test_method "${AUTOCONFIG_TEST}/ssl/PropertiesSslBundleTests.java" \ + "getWithResourceLoader" "FIPS: JKS requires SUN provider" +# pemPropertiesAreMappedToSslBundle: Replace ed25519 with rsa (not FIPS-approved) +PROPS_BUNDLE="${AUTOCONFIG_TEST}/ssl/PropertiesSslBundleTests.java" +if [ -f "$PROPS_BUNDLE" ]; then + sed -i 's/ed25519-cert\.pem/rsa-cert.pem/g; s/ed25519-key\.pem/rsa-key.pem/g' "$PROPS_BUNDLE" + echo " Replaced ed25519 with rsa in PropertiesSslBundleTests.java" +fi +# WKS requires the correct password to retrieve keys (unlike JKS which allows empty). +PROPS_BUNDLE_TESTS="${AUTOCONFIG_TEST}/ssl/PropertiesSslBundleTests.java" +if [ -f "$PROPS_BUNDLE_TESTS" ]; then + if grep -q 'EMPTY_KEY_PASSWORD = new char\[\] {}' "$PROPS_BUNDLE_TESTS"; then + sed -i 's/EMPTY_KEY_PASSWORD = new char\[\] {}/EMPTY_KEY_PASSWORD = "'"${FIPS_PASSWORD}"'".toCharArray()/' "$PROPS_BUNDLE_TESTS" + echo " Fixed EMPTY_KEY_PASSWORD in PropertiesSslBundleTests.java" + fi +fi + +# Fix EMPTY_KEY_PASSWORD in PemSslStoreBundleTests too (same issue) +PEM_STORE_TESTS="${BOOT_TEST}/ssl/pem/PemSslStoreBundleTests.java" +if [ -f "$PEM_STORE_TESTS" ]; then + if grep -q 'EMPTY_KEY_PASSWORD = new char\[\] {}' "$PEM_STORE_TESTS"; then + sed -i 's/EMPTY_KEY_PASSWORD = new char\[\] {}/EMPTY_KEY_PASSWORD = "'"${FIPS_PASSWORD}"'".toCharArray()/' "$PEM_STORE_TESTS" + echo " Fixed EMPTY_KEY_PASSWORD in PemSslStoreBundleTests.java" + fi +fi + +# SslAutoConfigurationTests: Replace ed25519 with rsa (not FIPS-approved) +# and fix short passwords in property strings. +SSL_AUTO_TESTS="${AUTOCONFIG_TEST}/ssl/SslAutoConfigurationTests.java" +if [ -f "$SSL_AUTO_TESTS" ]; then + if grep -q "ed25519" "$SSL_AUTO_TESTS"; then + sed -i 's/ed25519-cert\.pem/rsa-cert.pem/g; s/ed25519-key\.pem/rsa-key.pem/g' "$SSL_AUTO_TESTS" + echo " Replaced ed25519 with rsa in SslAutoConfigurationTests.java" + fi + # Fix short passwords that don't meet FIPS HMAC minimum + sed -i 's/password=secret1"/password='"${FIPS_PASSWORD}"'"/g; s/password=secret2"/password='"${FIPS_PASSWORD}"'"/g' "$SSL_AUTO_TESTS" + # Fix assertion strings to match patched values + sed -i 's/isEqualTo("PKCS12")/isEqualTo("WKS")/g' "$SSL_AUTO_TESTS" + sed -i 's/isEqualTo("secret1")/isEqualTo("'"${FIPS_PASSWORD}"'")/g' "$SSL_AUTO_TESTS" + sed -i 's/isEqualTo("secret2")/isEqualTo("'"${FIPS_PASSWORD}"'")/g' "$SSL_AUTO_TESTS" + echo " Fixed type and password assertions in SslAutoConfigurationTests.java" +fi +disable_test_method "${AUTOCONFIG_TEST}/ssl/SslPropertiesBundleRegistrarTests.java" \ + "shouldUseResourceLoader" "FIPS: JKS requires SUN provider" + +# RSocket shouldUseSslWhenRocketServerSslIsConfigured: Test only provides keyPassword +# but WKS requires keyStorePassword to load. Inject the missing property. +RSOCKET_TEST="${AUTOCONFIG_TEST}/rsocket/RSocketServerAutoConfigurationTests.java" +if [ -f "$RSOCKET_TEST" ]; then + if grep -q 'shouldUseSslWhenRocketServerSslIsConfigured' "$RSOCKET_TEST" && \ + ! grep -q 'keyStorePassword' "$RSOCKET_TEST"; then + # Insert keyStorePassword property before port=0 in the withPropertyValues() call + sed -i 's|"spring.rsocket.server.ssl.keyPassword=[^"]*", "spring.rsocket.server.port=0"|"spring.rsocket.server.ssl.keyPassword='"${FIPS_PASSWORD}"'", "spring.rsocket.server.ssl.keyStorePassword='"${FIPS_PASSWORD}"'", "spring.rsocket.server.port=0"|' "$RSOCKET_TEST" + echo " Patched RSocketServerAutoConfigurationTests: added keyStorePassword property" + fi +fi + +# RabbitMQ SSL tests: Most pass with WKS/password fixes and JKS/PKCS12->WKS patching. +# NonExisting keystore/truststore error-path tests: Spring AMQP's compiled +# RabbitConnectionFactoryBean calls KeyStore.getInstance("PKCS12") which fails in FIPS +# (PKCS12 not available). Error is "PKCS12 not found" instead of "foo does not exist". +# Cannot patch compiled Spring AMQP jars. +disable_test_method "${AUTOCONFIG_TEST}/amqp/RabbitAutoConfigurationTests.java" \ + "enableSslWithNonExistingKeystoreShouldFail" \ + "Spring AMQP compiled jar uses KeyStore.getInstance(PKCS12) - not available in FIPS" +disable_test_method "${AUTOCONFIG_TEST}/amqp/RabbitAutoConfigurationTests.java" \ + "enableSslWithNonExistingTrustStoreShouldFail" \ + "Spring AMQP compiled jar uses KeyStore.getInstance(PKCS12) - not available in FIPS" + +# ============================================================================== +# SECTION 7: Patch HTTP Client Tests for broader exception handling +# ============================================================================== +echo "" +echo "=== SECTION 7: HTTP Client test exception handling ===" + +# The tests expect SSLHandshakeException specifically, but wolfJSSE may throw +# different exception types. Patch to accept IOException (parent of SSL exceptions). +HTTP_CLIENT_TEST="${BOOT_TEST}/http/client/AbstractClientHttpRequestFactoryBuilderTests.java" +if [ -f "$HTTP_CLIENT_TEST" ]; then + # Change SSLHandshakeException.class to IOException.class for the insecure request test + if grep -q "assertThatExceptionOfType(SSLHandshakeException.class)" "$HTTP_CLIENT_TEST"; then + sed -i 's/assertThatExceptionOfType(SSLHandshakeException.class)/assertThatExceptionOfType(IOException.class)/g' "$HTTP_CLIENT_TEST" + echo " Patched exception type in AbstractClientHttpRequestFactoryBuilderTests.java" + fi +fi + +WEB_CLIENT_TEST="${BOOT_TEST}/web/client/AbstractClientHttpRequestFactoriesTests.java" +if [ -f "$WEB_CLIENT_TEST" ]; then + if grep -q "assertThatExceptionOfType(SSLHandshakeException.class)" "$WEB_CLIENT_TEST"; then + sed -i 's/assertThatExceptionOfType(SSLHandshakeException.class)/assertThatExceptionOfType(IOException.class)/g' "$WEB_CLIENT_TEST" + echo " Patched exception type in AbstractClientHttpRequestFactoriesTests.java" + fi +fi + +# PemCertificateParserTests: test-cert.pem now contains the full chain (server cert +# + CA cert) so client-auth tests have the CA cert in the trust material. The +# parseCertificate() test expects hasSize(1) but the chain has 2 certs. +PEM_PARSER_TESTS="${BOOT_TEST}/ssl/pem/PemCertificateParserTests.java" +if [ -f "$PEM_PARSER_TESTS" ]; then + sed -i '/parseCertificate/,/hasSize(1)/{s/hasSize(1)/hasSize(2)/}' "$PEM_PARSER_TESTS" + echo " Patched PemCertificateParserTests: test-cert.pem now has chain (2 certs)" +fi + +# ============================================================================== +# SECTION 8: Patch TrustSelfSignedStrategy -> TrustAllStrategy +# ============================================================================== +echo "" +echo "=== SECTION 8: Trust strategy patches for CA-signed certs ===" + +# We use CA-signed certs; TrustSelfSignedStrategy only works when Issuer==Subject +patch_trust_strategy() { + local file="$1" + [ ! -f "$file" ] && return 0 + if grep -q "TrustSelfSignedStrategy" "$file"; then + grep -q "TrustAllStrategy" "$file" || \ + sed -i '/^package /a\import org.apache.hc.client5.http.ssl.TrustAllStrategy;' "$file" + sed -i 's/new TrustSelfSignedStrategy()/TrustAllStrategy.INSTANCE/g' "$file" + echo " Patched: $(basename "$file")" + fi +} + +patch_trust_strategy "${BOOT_TEST}/web/servlet/server/AbstractServletWebServerFactoryTests.java" +patch_trust_strategy "${BOOT_TEST}/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java" +patch_trust_strategy "${BOOT_TEST}/web/embedded/jetty/JettyServletWebServerFactoryTests.java" +patch_trust_strategy "${BOOT_TEST}/web/embedded/undertow/UndertowServletWebServerFactoryTests.java" +patch_trust_strategy "${BOOT_TEST}/web/reactive/server/AbstractReactiveWebServerFactoryTests.java" + +# ============================================================================== +# SECTION 9: Disable Netty/Reactor SSL tests (InsecureTrustManagerFactory) +# ============================================================================== +echo "" +echo "=== SECTION 9: Disabling Netty/Reactor SSL tests ===" + +# InsecureTrustManagerFactory fails through compiled Reactor Netty jars because +# native wolfSSL verification rejects the cert before the Java TrustManager +# callback fires. Cannot patch compiled jars to use explicit trust material. + +# NettyRSocketServerFactory: PEM certificate tests fail (client InsecureTMF), +# but WKS keystore/bundle tests pass. Disable only the failing PEM cert methods. +for method in tcpTransportBasicSslCertificateFromClassPath \ + tcpTransportBasicSslCertificateFromFileSystem \ + websocketTransportBasicSslCertificateFromClassPath \ + websocketTransportBasicSslCertificateFromFileSystem; do + disable_test_method "${BOOT_TEST}/rsocket/netty/NettyRSocketServerFactoryTests.java" \ + "$method" "Reactor Netty client InsecureTMF in compiled jars" +done + +# ClientHttpRequestFactoriesReactor: both connectWithSslBundle tests fail +disable_test_class "${BOOT_TEST}/web/client/ClientHttpRequestFactoriesReactorTests.java" \ + "Reactor Netty client InsecureTMF path in compiled jars (cannot patch here)" + +# ReactorClientHttpRequestFactoryBuilder: uses Reactor Netty InsecureTMF +disable_test_class "${BOOT_TEST}/http/client/ReactorClientHttpRequestFactoryBuilderTests.java" \ + "Reactor Netty client InsecureTMF path in compiled jars (cannot patch here)" + +# NettyReactiveWebServerFactory: most tests PASS (basicSsl, clientAuth, etc.), +# only the SSL bundle reload test fails with IllegalStateException. +disable_test_method "${BOOT_TEST}/web/embedded/netty/NettyReactiveWebServerFactoryTests.java" \ + "whenSslBundleIsUpdatedThenSslIsReloaded" "Reactor Netty SSL bundle reload not supported with wolfJSSE" + +echo "" +echo "=== Spring Boot FIPS Fixes Applied ===" diff --git a/java/wolfssl-openjdk-fips-root/test-images/spring-boot-tests/build.sh b/java/wolfssl-openjdk-fips-root/test-images/spring-boot-tests/build.sh new file mode 100755 index 0000000..7fa1ae8 --- /dev/null +++ b/java/wolfssl-openjdk-fips-root/test-images/spring-boot-tests/build.sh @@ -0,0 +1,180 @@ +#!/bin/bash + +# Spring Boot SSL Tests Docker Image Build Script +# Builds a Spring Boot test container that runs SSL tests with wolfJSSE in FIPS mode + +set -e + +# Enable BuildKit (required for heredoc syntax in Dockerfile) +export DOCKER_BUILDKIT=1 + +# Default values +IMAGE_NAME="spring-boot-wolfjsse" +TAG="latest" +VERBOSE=false +NO_CACHE=false +BASE_IMAGE="wolfssl-openjdk-fips-root:latest" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Print usage information +usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Build the Spring Boot SSL tests Docker image with wolfJSSE FIPS support" + echo "" + echo "OPTIONS:" + echo " -n, --name NAME Set image name (default: spring-boot-wolfjsse)" + echo " -t, --tag TAG Set image tag (default: latest)" + echo " -b, --base BASE Set base image (default: wolfssl-openjdk-fips-root:latest)" + echo " -c, --no-cache Build without using Docker cache" + echo " -v, --verbose Enable verbose output" + echo " -h, --help Show this help message" + echo "" + echo "Examples:" + echo " $0 # Basic build" + echo " $0 -n myspringboot -t v1.0 # Custom name and tag" + echo " $0 -b wolfssl-openjdk-fips-root:custom -v # Use custom base image with verbose output" + echo "" +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -n|--name) + IMAGE_NAME="$2" + shift 2 + ;; + -t|--tag) + TAG="$2" + shift 2 + ;; + -b|--base) + BASE_IMAGE="$2" + shift 2 + ;; + -c|--no-cache) + NO_CACHE=true + shift + ;; + -v|--verbose) + VERBOSE=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo -e "${RED}Error: Unknown option $1${NC}" + usage + exit 1 + ;; + esac +done + +# Print build configuration +echo -e "${GREEN}=== Spring Boot SSL Tests Build (wolfJSSE FIPS) ===${NC}" +echo -e "${BLUE}Image Name:${NC} ${IMAGE_NAME}:${TAG}" +echo -e "${BLUE}Base Image:${NC} ${BASE_IMAGE}" +echo -e "${BLUE}Build Context:${NC} $(pwd)" +echo "" + +# Check if base image exists +echo -e "${YELLOW}Checking base image availability...${NC}" +if ! docker image inspect "${BASE_IMAGE}" > /dev/null 2>&1; then + echo -e "${RED}Error: Base image '${BASE_IMAGE}' not found!${NC}" + echo -e "${YELLOW}Please build the base wolfSSL OpenJDK FIPS image first:${NC}" + echo " cd ../../" + echo " ./build.sh -p YOUR_WOLFSSL_PASSWORD --wolfcrypt-jni ./wolfcrypt-jni --wolfssl-jni ./wolfssljni" + exit 1 +fi +echo -e "${GREEN}Base image found${NC}" + +# Check if Docker is running +if ! docker info > /dev/null 2>&1; then + echo -e "${RED}Error: Docker is not running or not accessible${NC}" + exit 1 +fi + +# Verify required files exist +echo -e "${YELLOW}Verifying required files...${NC}" +REQUIRED_FILES=( + "Dockerfile" + "apply_spring_fips_fixes.sh" +) +MISSING_FILES=() +for file in "${REQUIRED_FILES[@]}"; do + if [[ ! -e "$file" ]]; then + MISSING_FILES+=("$file") + fi +done + +if [[ ${#MISSING_FILES[@]} -gt 0 ]]; then + echo -e "${RED}Error: Missing required files:${NC}" + for file in "${MISSING_FILES[@]}"; do + echo " - $file" + done + exit 1 +fi +echo -e "${GREEN}All required files found${NC}" + +# Build Docker arguments +DOCKER_ARGS=() +if [[ "${NO_CACHE}" == "true" ]]; then + DOCKER_ARGS+=(--no-cache) +fi +if [[ "${VERBOSE}" == "true" ]]; then + DOCKER_ARGS+=(--progress=plain) +fi + +# Add build args +DOCKER_ARGS+=(--build-arg FIPS_BASE_IMAGE="${BASE_IMAGE}") + +echo -e "${YELLOW}Starting Docker build...${NC}" +echo "" + +# Build the Docker image +if docker build "${DOCKER_ARGS[@]}" -t "${IMAGE_NAME}:${TAG}" .; then + echo "" + echo -e "${GREEN}=== Build Successful ===${NC}" + echo -e "${GREEN}Image:${NC} ${IMAGE_NAME}:${TAG}" + + # Show image details + echo "" + echo -e "${BLUE}Image Details:${NC}" + docker image inspect "${IMAGE_NAME}:${TAG}" --format "Size: {{.Size}} bytes" 2>/dev/null || true + docker image inspect "${IMAGE_NAME}:${TAG}" --format "Created: {{.Created}}" 2>/dev/null || true + + echo "" + echo -e "${YELLOW}=== Usage ===${NC}" + echo "" + echo -e "${BLUE}Run all SSL tests:${NC}" + echo " docker run --rm ${IMAGE_NAME}:${TAG}" + echo "" + echo -e "${BLUE}Test modules:${NC}" + echo " - spring-boot: Core SSL bundle tests" + echo " - spring-boot-autoconfigure: SSL auto-configuration tests" + echo " - spring-boot-actuator: Actuator SSL tests" + echo "" + echo -e "${BLUE}Run specific test class:${NC}" + echo " docker run --rm ${IMAGE_NAME}:${TAG} ./gradlew :spring-boot-project:spring-boot:test --tests 'SslBundleTest' --no-daemon" + echo "" + echo -e "${BLUE}Run specific module tests:${NC}" + echo " docker run --rm ${IMAGE_NAME}:${TAG} ./gradlew :spring-boot-project:spring-boot:test --tests '*Ssl*' --no-daemon" + echo "" + echo -e "${BLUE}Interactive shell:${NC}" + echo " docker run --rm -it ${IMAGE_NAME}:${TAG} bash" + echo "" + +else + echo "" + echo -e "${RED}=== Build Failed ===${NC}" + exit 1 +fi +