Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/tomcat-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ jobs:
test -f "$JAVA_HOME/lib/security/cacerts.bcfks"
grep -q "^security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider" "$JAVA_HOME/conf/security/java.security.fips"
grep -q "^security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider" "$JAVA_HOME/conf/security/java.security.fips"
# TLS defaults. SunX509 is a SunJSSE-only algorithm name and BCFKS
# is a BCFIPS-only store type, so leaving either default in place
# breaks TLS at first use rather than at startup. Exactly one line
# each - a stale JDK default left behind would still match a plain
# grep and win, since the last assignment is the one that applies.
sec_fips="$JAVA_HOME/conf/security/java.security.fips"
test "$(grep -c "^ssl.KeyManagerFactory.algorithm=" "$sec_fips")" = 1
test "$(grep -c "^ssl.TrustManagerFactory.algorithm=" "$sec_fips")" = 1
grep -q "^ssl.KeyManagerFactory.algorithm=PKIX$" "$sec_fips"
grep -q "^ssl.TrustManagerFactory.algorithm=PKIX$" "$sec_fips"
grep -q "javax.net.ssl.trustStoreProvider=BCFIPS" "$TOMCAT_HOME/bin/setenv.sh"
grep -q "approved_only=true" "$TOMCAT_HOME/bin/setenv.sh"
grep -q "java.security.properties==" "$TOMCAT_HOME/bin/setenv.sh"
# the JDK default must stay usable or Maven, keytool and the
Expand All @@ -118,6 +129,7 @@ jobs:
test ! -e "$JAVA_HOME/conf/security/java.security.fips"
! grep -q "BouncyCastleFipsProvider" "$JAVA_HOME/conf/security/java.security"
! grep -q "approved_only" "$TOMCAT_HOME/bin/setenv.sh"
! grep -q "trustStoreProvider" "$TOMCAT_HOME/bin/setenv.sh"
echo "standard variant verified clean"
fi
'
Expand Down
25 changes: 24 additions & 1 deletion docker/Dockerfile.tomcat
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,28 @@ RUN if [ "$FIPS_ENABLED" != "true" ]; then echo "FIPS_ENABLED=$FIPS_ENABLED - sk
# fine for compliance - the approved DRBG is still BCFIPS's. Consequence:
# SUN also serves MD5, so approved-only mode cannot block MD5 for
# application code. That has to be caught by code review.
sed -E '/^security\.provider\.[0-9]+=/d' ${JAVA_HOME}/conf/security/java.security \
#
# The ssl.*ManagerFactory.algorithm defaults are rewritten to PKIX in the
# same pass. The JDK ships ssl.KeyManagerFactory.algorithm=SunX509, an
# algorithm name only SunJSSE implements - BCJSSE registers PKIX (and its
# X.509 alias) and nothing else. With SunJSSE dropped from the provider
# list, KeyManagerFactory.getDefaultAlgorithm() still returns SunX509 and
# every default-constructed SSLContext dies with NoSuchAlgorithmException,
# typically surfacing as "Default SSLContext not available" on the first
# outbound HTTPS call rather than at startup.
#
# The existing lines are deleted rather than substituted in place: a
# substitution silently no-ops if the JDK ever stops shipping the property,
# which would put us back on the broken default with a green build.
sed -E -e '/^security\.provider\.[0-9]+=/d' -e '/^ssl\.(Key|Trust)ManagerFactory\.algorithm=/d' \
${JAVA_HOME}/conf/security/java.security \
> ${JAVA_HOME}/conf/security/java.security.fips; \
printf '%s\n' \
'security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider' \
'security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS' \
'security.provider.3=SUN' \
'ssl.KeyManagerFactory.algorithm=PKIX' \
'ssl.TrustManagerFactory.algorithm=PKIX' \
>> ${JAVA_HOME}/conf/security/java.security.fips; \
\
# 4. Providers named in java.security load from the SYSTEM classloader, so the
Expand All @@ -216,6 +232,13 @@ RUN if [ "$FIPS_ENABLED" != "true" ]; then echo "FIPS_ENABLED=$FIPS_ENABLED - sk
echo 'CATALINA_OPTS="$CATALINA_OPTS -Dorg.bouncycastle.fips.approved_only=true"'; \
echo 'CATALINA_OPTS="$CATALINA_OPTS -Djavax.net.ssl.trustStore=$JAVA_HOME/lib/security/cacerts.bcfks"'; \
echo 'CATALINA_OPTS="$CATALINA_OPTS -Djavax.net.ssl.trustStoreType=BCFKS"'; \
echo '# BCFKS is registered by BCFIPS only. Naming the provider explicitly'; \
echo '# keeps TrustStoreManager off the "try every provider in order" path,'; \
echo '# where SUN is asked for BCFKS first and the resulting'; \
echo '# KeyStoreException is swallowed into an empty trust anchor set - TLS'; \
echo '# then fails at handshake time with PKIX path building failed instead'; \
echo '# of at load time.'; \
echo 'CATALINA_OPTS="$CATALINA_OPTS -Djavax.net.ssl.trustStoreProvider=BCFIPS"'; \
echo 'CATALINA_OPTS="$CATALINA_OPTS -Djavax.net.ssl.trustStorePassword=changeit"'; \
echo '# Session id entropy. conf/context.xml reads these; without them Tomcat'; \
echo '# defaults to SHA1PRNG, which BCFIPS does not implement, so JSESSIONID'; \
Expand Down
36 changes: 34 additions & 2 deletions docker/FIPS.README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,36 @@ approved-only mode is in force keytool can no longer read the source truststore.
Hashes are pinned in the Dockerfile rather than fetched next to the jars.
Downloading an artifact and its checksum from the same source verifies nothing.

### 4.1 TLS defaults

Swapping the provider list is not enough on its own - three JDK defaults name
SunJSSE machinery that is no longer registered, and each one fails late rather
than at startup:

| Setting | JDK default | FIPS value | Set in |
|---------|-------------|------------|--------|
| `ssl.KeyManagerFactory.algorithm` | `SunX509` | `PKIX` | `java.security.fips` |
| `ssl.TrustManagerFactory.algorithm` | `PKIX` | `PKIX` (pinned) | `java.security.fips` |
| `javax.net.ssl.trustStoreProvider` | unset | `BCFIPS` | `setenv.sh` |

- **`SunX509` is a SunJSSE-only algorithm name.** BCJSSE registers `PKIX` and its
`X.509` alias, nothing else. Leave the default in place and
`KeyManagerFactory.getDefaultAlgorithm()` still returns `SunX509`, so every
default-constructed `SSLContext` throws `NoSuchAlgorithmException` - usually
seen as `Default SSLContext not available` on the first outbound HTTPS call.
- `ssl.TrustManagerFactory.algorithm` already defaults to `PKIX`, but it is
written explicitly so a JDK default change cannot move it.
- **`BCFKS` is registered by BCFIPS only.** Without
`javax.net.ssl.trustStoreProvider`, `TrustStoreManager` walks the provider list
in order, asks SUN for `BCFKS` first, and swallows the resulting
`KeyStoreException` into an empty trust anchor set. The failure then shows up
at handshake time as `PKIX path building failed`, not at load time.

Both `java.security.fips` lines are written by deleting the JDK entries and
appending ours, never by in-place substitution - a substitution silently no-ops
if the property ever stops shipping, which puts the image back on the broken
default with a green build.

`tomcat-builder.yml` crosses `arch` with `fips` (4 build jobs, 2 merge jobs) and
asserts each variant is genuinely what it claims - including that the standard
image contains **no** FIPS artifacts, which catches `FIPS_ENABLED` leaking.
Expand Down Expand Up @@ -211,8 +241,10 @@ than reading config, and exits non-zero on failure:

It checks provider order, that SecureRandom and SHA-256 come from BCFIPS,
AES-256-GCM, PBKDF2, that a sub-112-bit password is rejected, that the BCFKS
truststore loads, and that `context.xml` resolves session IDs to BCFIPS through
the same `IntrospectionUtils` path the Tomcat Digester uses.
truststore loads through the named provider, that the TLS defaults from section
4.1 resolve to BCJSSE and `SSLContext.getDefault()` actually builds, and that
`context.xml` resolves session IDs to BCFIPS through the same
`IntrospectionUtils` path the Tomcat Digester uses.

It also reports which provider serves MD5, as a standing reminder of section 8.

Expand Down
Loading