Java Spring STIG
This article provides guidance on ensuring that the embedded Tomcat server provided with the Spring Framework is configured in accordance with the Tomcat Secure Technical Implementation Guide STIG from DISA.
The following sections have been designed in an implementation-friendly, as opposed to an assessment, or testing-friendly, fashion. STIG identifiers are included where practical.
Long blocks of code have been collapsed, so please look out for sections like the following:
Important stuff here
Implementation Reference!
Assumptions
- A fairly stock gradle setup with a project generated by the Spring Initializr is in use.
- Where version numbers are specified, it is assumed that the user will use the latest available at the time of implementation
- Your application is using Spring Security.
Disclaimer
I am not a Spring expert!
The material provided in this document should be considered reference material.
No warranties for fitness to any particular purpose are expressed or implied.
Sources were referenced when possible. If you feel that your material was used without reference, please contact the author for corrections to the oversight.
Corrections, updates, or better ways to do things are welcome as a PR to the source repository
FIPS 140-2/3 - TCAT-AS-000750
Starting with FIPS-enablement removes what may be the largest hurdle first. If you find that this becomes excessively problematic due to other parts of your infrastructure not being FIPS compatible, you can skip this and come back to it later.
IMPORTANT: Using a FIPS-validated cryptographic library does not make your application FIPS-validated.
Obtain BouncyCastle FIPS
NOTE: Some platforms, such as Red Hat Enterprise Linux and derivatives, have FIPS support in the underlying system.
If you are using one of these systems, you simply need to ensure that it is configured for your system
java
installation.These instructions have been written assuming that you wish to use BouncyCastle for portability across systems.
Add the following to your build.gradle
in the dependencies
section:
implementation 'org.bouncycastle:bc-fips:1.0.2.3'
implementation 'org.bouncycastle:bctls-fips:1.0.13'
implementation 'org.bouncycastle:bcpkix-fips:1.0.6'
Relevant Documentation
- Legion of the Bouncy Castle Inc. - BC-FJA(Bouncy Castle FIPS Java API)
- The Bouncy Castle FIPS Java API in 100 Examples
Set up Custom Security Providers
Create a $projectDir/src/main/resources/java.security
file with the following content:
security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS
security.provider.3=com.sun.net.ssl.internal.ssl.Provider BCFIPS
security.provider.4=SUN
security.provider.5=
fips.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
fips.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS
fips.provider.3=com.sun.net.ssl.internal.ssl.Provider BCFIPS
fips.provider.4=SUN
fips.provider.5=
You may see a warning similar to
invalid entry for security.provider.5
which can be ignored.The empty line is required to keep the remaining internal security providers from loading.
Add the Necessary Options to build.gradle
plugins {
<your other plugins>
id 'application'
id 'distribution'
}
apply plugin: 'application'
apply plugin: 'distribution'
application {
applicationDefaultJvmArgs = [
// STIG:> TCAT-AS-000750
// STIG:> APSC-DV-001860
// STIG:> APSC-DV-002010
// STIG:> APSC-DV-002020
// STIG:> APSC-DV-002030
// STIG:> APSC-DV-002040
// STIG:> APSC-DV-002050
// STIG:> APSC-DV-003100
"-Djava.security.properties=src/main/resources/java.security",
"-Dorg.bouncycastle.fips.approved_only=true"
]
}
startScripts {
// Needed to fix the java.security path
doLast {
unixScript.text = unixScript.text.replace('src/main/dist', '\$APP_HOME')
windowsScript.text = windowsScript.text.replace('src/main/dist', '%~dp0..')
}
}
jar {
// This is the default
enabled = true
}
Building
To build your application, run gradle assembleDist
. The output will be located
in build/distributions/<AppName>.zip
.
Executing your application can be done by unzipping the artifact and executing
the appropriate script in the bin
directory.
How to verify your configuration
The following code can be used to verify the configuration:
import javax.crypto.Cipher;
import java.security.Provider;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import javax.crypto.Mac;
System.out.println("BC Approved Only Mode: " + CryptoServicesRegistrar.isInApprovedOnlyMode());
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
String algorithm = cipher.getAlgorithm();
Provider provider = cipher.getProvider();
int blockSize = cipher.getBlockSize();
System.out.println("Output using default Provider:");
System.out.println("Algorithm :"+algorithm);
System.out.println("Provider Name:"+provider.getName());
System.out.println("Block Size :"+blockSize);
}
catch(Exception e) {
System.out.println(e);
}
try {
Mac mac = Mac.getInstance("hmacMD5");
}
catch(Exception e) {
System.out.println(e);
}
The expected output is as follows:
BC Approved Only Mode: true
Output using default Provider:
Algorithm :AES/CBC/PKCS5Padding
Provider Name:BCFIPS
Block Size :16
java.security.NoSuchAlgorithmException: Algorithm hmacMD5 not available
Necessary Code
To prevent the accidental use of our application without being FIPS enabled, we need to add code to ensure that the application fails to start if the configuration is incorrect.
To accomplish this, we can add a configuration parameter stig.fips-mode
to
allow FIPS to be toggled for debugging.
Create a file in your applications config
directory called FipsCheck.java
with the following content:
FipsCheck.java
package my.app.starter.config;
import javax.crypto.Mac;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import lombok.extern.slf4j.Slf4j;
import my.app.starter.config.exceptions.FipsCheckException;
@Slf4j
@Configuration
@ConditionalOnWebApplication(
type = Type.SERVLET
)
public class FipsCheck implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>{
@Value("${stig.fips-mode:true}")
private boolean fipsMode;
@Autowired
ApplicationContext context;
public void customize(TomcatServletWebServerFactory factory) {
// Ideally, we will find a method for doing this without binding explicitly to Tomcat
factory.addContextCustomizers((context) -> {
Boolean fipsOK = false;
try {
Mac mac = Mac.getInstance("hmacMD5");
}
catch(Exception e) {
fipsOK = true;
}
if ( fipsMode && fipsOK ) {
log.info("FIPS Mode Correctly Enabled for Core Java");
}
else if ( !fipsMode ) {
log.warn("FIPS Mode Disabled Via Configuration for Core Java");
}
else {
throw new FipsCheckException("Core Java");
}
});
}
}
``` </details>
Next, create a directory called `config/exceptions` and a file in that directory named `FipsCheckException.java` with the following content:
```java
package my.app.starter.config.exceptions;
public class FipsCheckException extends RuntimeException {
public FipsCheckException(String target) {
super("FIPS Mode Not Enabled in " + target);
}
}
The Rest of the STIGs
Only the Tomcat Server May Be Used
Spring has the ability to use web servers other than Tomcat.
Users must ensure that no other webserver is selected for use since there is a STIG for Tomcat.
build.gradle
Updates
You will need to add the following to your build.gradle
.
Where items overlap with configuring the system for FIPS mode, simply append to the existing set.
implementation 'org.springframework.boot:spring-boot-starter-validation:2.6.7'
apply plugin: 'application'
application {
applicationDefaultJvmArgs = [
// STIG:> TCAT-AS-001660
// STRICT_SERVLET_COMPLIANCE must be set to true
"-Dorg.apache.catalina.STRICT_SERVLET_COMPLIANCE=true",
// STIG:> TCAT-AS-001680
// ALLOW_BACKSLASH must be set to false.
"-Dorg.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH=false",
// STIG:> TCAT-AS-001690
// ENFORCE_ENCODING_IN_GET_WRITER must be set to true
"-Dorg.apache.catalina.connector.Response.ENFORCE_ENCODING_IN_GET_WRITER=true",
// STIG:> TCAT-AS-001670
// RECYCLE_FACADES must be set to true
"-Dorg.apache.catalina.connector.RECYCLE_FACADES=true"
]
}
appplication.yaml
Settings
Ensure that the following settings appear in the application.yaml
file and not
in a profile.
application.yaml
# NOTE: Just because a STIG item is listed here does not mean that it is
# fully met by setting the value. This only focuses on what Spring and
# Tomcat can do natively.
## TOMCAT STIG ##
# STIG:> TCAT-AS-000020
# Secured connectors must be configured to use strong encryption ciphers.
# STIG:> TCAT-AS-000040
# TLS 1.2 must be used on secured HTTP connectors.
server.ssl.enabled-protocols=TLSv1.2
# STIG:> TCAT-AS-000070
# Cookies must have secure flag set.
server.servlet.session.cookie.secure: true
# STIG:> TCAT-AS-000080
# Cookies must have http-only flag set.
server.servlet.session.cookie.http-only: true
# STIG:> TCAT-AS-000100
# Connectors must be secured.
server.ssl.enabled: true
# STIG:> TCAT-AS-000170
# Tomcat servers behind a proxy or load balancer must log client IP.
server.tomcat.accesslog.enabled: true
server.tomcat.accesslog.request-attributes-enabled: true
server.tomcat.accesslog.directory: /dev
server.tomcat.accesslog.prefix: stdout
server.tomcat.accesslog.buffered: false
# The following two items must be empty!
server.tomcat.accesslog.suffix:
server.tomcat.accesslog.file-date-format:
# STIG:> TCAT-AS-000240
# Date and time of events must be logged.
# STIG:> TCAT-AS-000250
# Remote hostname must be logged.
# STIG:> TCAT-AS-000260
# HTTP status code must be logged.
# STIG:> TCAT-AS-000270
# The first line of request must be logged.
# STIG:> TCAT-AS-001080
# Application user name must be logged.
server.tomcat.accesslog.pattern: "%h %l %t %u \"%r\" %s %b"
# STIG:> TCAT-AS-000470
# Stack tracing must be disabled.
trace: false
server.error.include-stacktrace: never
server.error.whitelabel.enabled: false
# STIG:> TCAT-AS-000510
# DefaultServlet debug parameter must be disabled.
server.servlet.register-default-servlet: false
# STIG:> TCAT-AS-000550
# xpoweredBy attribute must be disabled.
# This should be empty!
server.server-header:
# STIG:> TCAT-AS-000610
# JMX authentication must be secured.
# STIG:> TCAT-AS-000630
# TLS must be enabled on JMX.
spring.jmx.enabled: false
# STIG:> TCAT-AS-000750
# Tomcat must use FIPS-validated ciphers on secured connectors
#
# You will need to change this to "off" if your underlying platform is not
# FIPS-enabled with the appropriate tomcat-native libraries installed
stig.spring.embedded-tomcat.fips-mode: "on"
## APPLICATION STIG ##
# APSC-DV-000447
# The application must not be subject to input handling vulnerabilities.
server.tomcat.reject-illegal-header: true
# APSC-DV-001480
# The application must prevent program execution in accordance with
# organization-defined policies regarding software program usage and
# restrictions, and/or rules authorizing the terms and conditions of
# software program usage. (Least Privilege)
management.server.port: -1
management.endpoints.enabled-by-default: false
management.endpoints.web.exposure.include: ""
management.endpoints.jmx.exposure.include: ""
# APSC-DV-001660
# Service-Oriented Applications handling non-releasable data must
# authenticate endpoint devices via mutual SSL/TLS.
server.ssl.client-auth: true
# APSC-DV-002270
# Applications must not use URL embedded session IDs.
server.servlet.session.tracking-modes: COOKIE
# APSC-DV-002310
# The application must fail to a secure state if system initialization
# fails, shutdown fails, or aborts fail.
server.shutdown: graceful
# APSC-DV-002500
# The application must protect from Cross-Site Request Forgery (CSRF)
# vulnerabilities.
server.servlet.session.cookie.same-site: Strict
Additional Application Code
The following items are necessary to meet STIG requirements that would usually be met via XML configuration files. Unfortunately, the embedded Tomcat does not allow for XML configuration so we need to do it under the hood.
Enable HSTS
TCAT-AS-000030 - HTTP Strict Transport Security (HSTS) must be enabled
Ensure that your application has HSTS enabled by adding the following to the
class that you have created that hooks @EnableWebSecurity
. This only shows
the HSTS portion of the configuration, access controls and other items outside
of the Tomcat STIG are left to the reader.
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
.headers()
.httpStrictTransportSecurity()
.maxAgeInSeconds(31536000)
.includeSubDomains(true)
.preload(false)
}
}
Tomcat STIG Error Valve
To ensure that no information is inadvertently leaked, a custom Error Valve
should be created in your config
directory as TomcatStigErrorValve.java
with the following content:
package my.app.starter.config;
import java.io.IOException;
import java.io.Writer;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ErrorReportValve;
import org.springframework.context.annotation.Configuration;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
public class TomcatStigErrorValve extends ErrorReportValve{
// Based on work in https://github.com/spring-projects/spring-boot/issues/21257#issuecomment-745565376
protected void report(Request request, Response response, Throwable throwable) {
if (!response.setErrorReported()) {
return;
}
log.error("{} Fatal error before getting to Spring.", response.getStatus());
try {
Writer writer = response.getReporter();
writer.write(Integer.toString(response.getStatus()));
writer.write(" Fatal error. Could not process request.");
response.finishResponse();
} catch (IOException e) {}
}
}
Tomcat STIG Class
Create TomcatStig.java
in your config
directory with the following content
to apply the remainder of the STIG items.
This could be done in many ways but has been shown here in the most compact form.
Ideally, the Spring community would pick this up and turn it into a plugin (hint, hint)!
TomcatStig.java
package my.app.starter.config;
import javax.validation.constraints.Pattern;
import org.apache.catalina.Container;
import org.apache.catalina.core.AprLifecycleListener;
import org.apache.catalina.core.StandardHost;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import my.app.starter.config.TomcatStigErrorValve;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
@ConditionalOnWebApplication(
type = Type.SERVLET
)
public class TomcatStig implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Value("${server.ssl.enabled:false}")
private boolean sslEnabled;
@Value("${stig.fips-mode:true}")
private boolean globalFipsMode;
@Pattern(regexp = "^(on|off)$")
@Value("${stig.spring.embedded-tomcat.fips-mode:on}")
private String fipsMode;
// Based on work from https://www.behindjava.com/run-a-fully-fips-compliant/
@Autowired
ApplicationContext context;
public void customize(TomcatServletWebServerFactory factory) {
// Set fipsMode to match the global if FIPS is disabled everywhere
if ( !globalFipsMode ) {
fipsMode = "off";
}
String pkg_name = this.getClass().getPackageName();
// STIG:> TCAT-AS-000750
// Tomcat must use FIPS-validated ciphers on secured connectors
AprLifecycleListener aprLifecycleListener = new AprLifecycleListener();
aprLifecycleListener.setFIPSMode(fipsMode);
if ( ! aprLifecycleListener.getFIPSMode().equals("on") ) {
log.warn("FIPS Mode Disabled in Tomcat");
}
factory.addContextLifecycleListeners(aprLifecycleListener);
// End TCAT-AS-000750
factory.addConnectorCustomizers((connector) -> {
// STIG:> TCAT-AS-000100
// Connectors must be secured
connector.setSecure(sslEnabled);
// End TCAT-AS-000100
// STIG:> TCAT-AS-000550
// xpoweredBy attribute must be disabled.
connector.setXpoweredBy(false);
// STIG:> TCAT-AS-000470
// Stack tracking must be disabled
connector.setAllowTrace(false);
// End TCAT-AS-000470
// STIG:> TCAT-AS-001670
// RECYCLE_FACADES must be set to true
connector.setDiscardFacades(true);
// End TCAT-AS-001670
// Start APSC-DV-002520
// The application must protect from canonical representation vulnerabilities
connector.setUseBodyEncodingForURI(true);
// End APSC-DV-002520
});
factory.addContextCustomizers((context) -> {
SecurityConstraint constraint = new SecurityConstraint();
SecurityCollection collection = new SecurityCollection();
// Apply to everything
collection.setName("restricted_methods");
collection.addPattern("/*");
// STIG:> TCAT-AS-000090
// DefaultServlet must be set to readonly for PUT and DELETE
collection.addMethod("PUT");
collection.addMethod("DELETE");
// End TCAT-AS-000090 - Readonly Defaults
// STIG:> TCAT-AS-000040
// TLS 1.2 must be used on secured HTTP connectors
if ( sslEnabled ) {
collection.setName("https_only");
constraint.setUserConstraint("CONFIDENTIAL");
}
// STIG:> TCAT-AS-000040
constraint.addCollection(collection);
// Start APSC-DV-000460
// The application must enforce approved authorizations for logical access
// to information and system resources in accordance with applicable
// access control policies.
constraint.setAuthConstraint(true);
// End APSC-DV-000460
context.addConstraint(constraint);
});
// STIG:> TCAT-AS-000470
// Stack tracing must be disabled
//
// STIG:> TCAT-AS-000920
// ErrorReportValve showServerInfo must be set to false
//
// STIG:> TCAT-AS-000940
// ErrorReportValve showReport must be set to false
//
// This fully disables the embedded Tomcat stack trace and only presents the contents of the TomcatStigErrorValve.
// See: https://github.com/spring-projects/spring-boot/issues/21257#issuecomment-745565376
factory.addContextCustomizers((context) -> {
Container parent = context.getParent();
if ( parent instanceof StandardHost) {
((StandardHost) parent).setErrorReportValveClass(pkg_name + ".TomcatStigErrorValve");
}
});
// End TCAT-AS-000940
// End TCAT-AS-000920
// End TCAT-AS-000470
// STIG:> TCAT-AS-000590
// Applications in privileged mode must be approved by the ISSO
factory.addContextCustomizers((context) -> {
context.setPrivileged(false);
});
// End TCAT-AS-000590
}
}
References
- Spring Boot - Embedded Tomcat Configuration
- Spring Embedded Web Servers Guide
- Spring Common Application Properties
- Apache Tomcat System Properties
- TomcatServletWebServerFactory
- Spring and FIPS
- Oryx Tomcat Configuration
- OWASP Secure Headers Project
Troubleshooting
Once FIPS mode is enabled, you may run into various issues that have to be remedied.
Something is randomly breaking
If you find that your application is randomly crashing, the first thing to try is to disble FIPS mode to see if it has to do with your cryptography setup.
For testing, you can set stig.fips-mode: false
in your application.yml
but
DO NOT MAKE THIS THE DEFAULT.
The underlying system isn’t FIPS compliant
You can individually disable FIPS for Tomcat (but leave it on for BouncyCastle)
by setting the following in application.yaml
:
stig.spring.embedded-tomcat.fips-mode: "off"
- Note: The quotes are important around
off
due to YAML switching it to a Boolean natively - Different operating systems have different methods for enabling FIPS in
tomcat
so you will need to consult your systems documentation for details. That said, it is HIGHLY recommended that you work inside a FIPS-enabled virtual machine for initial testing.- Ubuntu requires the
libtcnative-1
package - RHEL requires the
tomcat-native
package from EPEL - macOS needs to re-roll
libtcnative
using a FIPS-compliant OpenSSL- This is not the default
- Ubuntu requires the
- Note: The quotes are important around
Your passwords are not long enough
If you see an error like the following, then you need to make sure that your passwords meet the required length.
org.bouncycastle.crypto.fips.FipsUnapprovedOperationError:
password must be at least 112 bits
In this case, the password must be at least 14
characters (112 bits
).
The application hangs
You may discover that your application hangs during startup. This is because the
cryptography library has been configured to use SecureRandom
instead of the
regular random pool.
Modern systems should have a Trusted Platform Module (or CPU-bound equivalent) so you may neve rencounter this issue. However, if you do, you will need to install a Pseudo-Random Number Generator (PRNG) on your system to prevent hangs.
java.io.IOException: DER length more than 4 bytes: 109
If you see this error, you are trying to use a jks file that has not been created in a FIPS-compatible format.
The best solution is to use a p12 file if possible.
Converting jks to p12 with keytool
Use the following command to convert your Java Keystore to PKCS12 format.
Since we are using the Bouncy Castle FIPS libraries, we want to ensure that they used for the conversion so that we prevent any possible issues.
If you run into issues with the conversion, you may need to use a non-FIPS keytool for the initial conversion since not all
jks
files are FIPS-compatible.
keytool \
-importkeystore \
-srckeystore <keystore.jks> \
-srcstoretype JKS \
-srcstorepass <src_password> \
-destkeystore <keystore.p12> \
-deststoretype PKCS12 \
-deststorepass <dest_password>
Verifying your p12 file
To verify that your keystore works with the FIPS-enabled system, you need to use keytool with the BCFIPS provider enabled as follows.
keytool \
-list \
-keystore <keystore.p12> \
-storetype PKCS12 \
-provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvder \
-providerpath </path/to>/bc-fips-<version>.jar \
-storepass <password>
This work is derived from work in the performance of Federal Government Contract Number W52P1J-21-F-0245.
Any copyright in this work is subject to the Government's Unlimited Rights license as defined in DFARS 252.227-7013 and/or DFARS 252.227-7014.
The reproduction of this work for commercial purposes is strictly prohibited.
Nongovernmental users may copy and distribute this document in any medium, either commercially or noncommercially, provided that this copyright notice is reproduced in all copies.
Nongovernmental users may not use technical measures to obstruct or control the reading or further copying of the copies they make or distribute.
Nongovernmental users may not accept compensation of any manner in exchange for copies.
All other rights reserved.