Builds FIPS 140 validated provider module for Debian containers running OpenSSL 3+ and demonstrates how the module can be integrated onto various Debian-based images.
Build the image:
docker build -t debian-openssl-fipsmodule:3.0.8 .
I have not published this image to any public registries. Even if I did, you should build it yourself since there are no guarantees I did not tamper with the cryptographic provider in the image I published.
This approach is demonstrated using Docker, but could be used on a regular
virtual machines as well (i.e. build the FIPS provider module, copy it to the
correct location on a target VM, run openssl fipsinstall
).
You could build images based off of the image in this repository. This is not
recommended. Alternatively (and recommended), you can configure many
Debian-based images to use the binaries in this image instead. The basic idea
is to copy in the FIPS validated cryptographic module, run its self-tests, and
set the OpenSSL configuration so that the module can be used. In each of the
examples, I assume that FIPS should be enabled by default; see OpenSSL's
manpage for
alternative configurations. In each example, note that the self-tests are
re-run rather than copying fipsmodule.cnf
(see OpenSSL's FIPS
README
for details.
Verification that a process is running in a FIPS-compliant mode typically follows several steps:
- Directly interrogate the process (if possible)
- Confirm that a FIPS-compliant algorithm (typically SHA256) is available through the cryptographic module
- Confirm that a non-FIPS-compliant algorithm (typically MD5) is not available through the cryptographic module. In some cases, the process will provide a fallback implementation.
- Confirm that the available ciphers only contain FIPS-compliant ciphers
(e.g. no
chacha20_poly1305
).
Node.js requires the environment variable OPENSSL_MODULES
be set and the
flags --openssl-shared-config
and --enable-fips
be passed to the node
command.
Sample Dockerfile:
FROM node:18-bookworm
ENV OPENSSL_MODULES=/usr/lib/x86_64-linux-gnu/ossl-modules
COPY --from=debian-openssl-fipsmodule:3.0.8 $OPENSSL_MODULES/fips.so $OPENSSL_MODULES/fips.so
RUN openssl fipsinstall -module $OPENSSL_MODULES/fips.so -out /usr/lib/ssl/fipsmodule.cnf
COPY --from=debian-openssl-fipsmodule:3.0.8 /etc/ssl/openssl.cnf /etc/ssl/openssl.cnf
CMD [ "node", "--openssl-shared-config", "--enable-fips" ]
Build the image and run:
docker run -it --rm $(docker build -q .)
Confirm that node is running in FIPS mode:
> crypto.fips;
1
Confirm that SHA256 (a FIPS-compliant algorithm) is available:
> crypto.createHash('sha256');
Hash {
_options: undefined,
[Symbol(kHandle)]: Hash {},
[Symbol(kState)]: { [Symbol(kFinalized)]: false }
}
Confirm that MD5 (a non-FIPS-compliant algorithm) is unavailable:
> crypto.createHash('md5')
Uncaught Error: error:0308010C:digital envelope routines::unsupported
at new Hash (node:internal/crypto/hash:69:19)
at Object.createHash (node:crypto:133:10) {
opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ],
library: 'digital envelope routines',
reason: 'unsupported',
code: 'ERR_OSSL_EVP_UNSUPPORTED'
}
Confirm that ecdhe-rsa-chacha20-poly1305
(a non-FIPS-compliant cipher) is unavailable:
> tls.getCiphers().includes('ecdhe-rsa-chacha20-poly1305');
false
Note that tls_chacha20_poly1305_sha256
appears in tls.getCiphers()
because
all of the TLSv1.3 ciphers are hard-coded
in the list.
Sample Dockerfile:
FROM python:3.12-bookworm
ARG OPENSSL_MODULES=/usr/lib/x86_64-linux-gnu/ossl-modules
COPY --from=debian-openssl-fipsmodule:3.0.8 $OPENSSL_MODULES/fips.so $OPENSSL_MODULES/fips.so
RUN openssl fipsinstall -module $OPENSSL_MODULES/fips.so -out /usr/lib/ssl/fipsmodule.cnf
COPY --from=debian-openssl-fipsmodule:3.0.8 /etc/ssl/openssl.cnf /etc/ssl/openssl.cnf
Build the image and run:
docker run -it --rm $(docker build -q .)
Confirm that SHA256 (a FIPS-compliant algorithm) is available:
>>> import hashlib
>>> hashlib.new('sha256')
<sha256 _hashlib.HASH object @ 0x7fc4c806b7f0>
Python 3.12+ includes a fallback implementation of MD5:
For any of the MD5, SHA1, SHA2, or SHA3 algorithms that the linked OpenSSL does not provide we fall back to a verified implementation from the HACL* project.
Previous versions with throw an exception, 3.12+ will return an object from a
different module than hashlib
:
>>> import hashlib
>>> hashlib.new('md5')
<_md5.md5 object at 0x7fc4c8026590>
Confirm that TLS_CHACHA20_POLY1305_SHA256
is unavailable:
>>> import ssl
>>> ctx = ssl.create_default_context()
>>> 'TLS_CHACHA20_POLY1305_SHA256' in [cipher['name'] for cipher in ctx.get_ciphers()]
False
Sample Dockerfile:
FROM ruby:3.2-bookworm
ARG OPENSSL_MODULES=/usr/lib/x86_64-linux-gnu/ossl-modules
COPY --from=debian-openssl-fipsmodule:3.0.8 $OPENSSL_MODULES/fips.so $OPENSSL_MODULES/fips.so
RUN openssl fipsinstall -module $OPENSSL_MODULES/fips.so -out /usr/lib/ssl/fipsmodule.cnf
COPY --from=debian-openssl-fipsmodule:3.0.8 /etc/ssl/openssl.cnf /etc/ssl/openssl.cnf
Ruby 3.2 uses Ruby/OpenSSL 3.1.0, which doesn't support the concept of FIPS providers (added
in Ruby/OpenSSL 3.2.0). As a result OpenSSL.fips
doesn't work.
Confirm that SHA256 is available through OpenSSL's digest:
irb(main):002:0> OpenSSL::Digest::SHA256.new
=> #<OpenSSL::Digest::SHA256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855>
Confirm that MD5 is unavailable throught OpenSSL's digest:
irb(main):001:0> require "openssl"
=> true
irb(main):002:0> OpenSSL::Digest::MD5.new
/usr/local/lib/ruby/3.2.0/openssl/digest.rb:35:in `initialize': Digest initialization failed: initialization error (OpenSSL::Digest::DigestError)
from /usr/local/lib/ruby/3.2.0/openssl/digest.rb:35:in `block (3 levels) in <class:Digest>'
from (irb):2:in `new'
from (irb):2:in `<main>'
from /usr/local/lib/ruby/gems/3.2.0/gems/irb-1.6.2/exe/irb:11:in `<top (required)>'
from /usr/local/bin/irb:25:in `load'
from /usr/local/bin/irb:25:in `<main>'
Ruby's Digest
also doesn't use OpenSSL (it contains its own C implementation
), so Digest::MD5
will continue to work.
Confirm that TLS_AES_256_GCM_SHA384
is available, but
TLS_CHACHA20_POLY1305_SHA256
is unavailable:
irb(main):001:0> require "openssl"
=> true
irb(main):002:0> ctx = OpenSSL::SSL::SSLContext.new
=> #<OpenSSL::SSL::SSLContext:0x00007ffb5d296430 @verify_hostname=false, @verify_mode=0>
irb(main):003:0> ctx.ciphers.any? { |name, version, bits, alg_bits| name == "TLS_AES_256_GCM_SHA384" }
=> true
irb(main):004:0> ctx.ciphers.any? { |name, version, bits, alg_bits| name == "TLS_CHACHA20_POLY1305_SHA256" }
=> false
Create a custom httpd.conf
file, ensure that it contains SSLFIPS on
.
Sample Dockerfile:
FROM httpd:2.4-bookworm
ARG OPENSSL_MODULES=/usr/lib/x86_64-linux-gnu/ossl-modules
COPY --from=debian-openssl-fipsmodule:3.0.8 $OPENSSL_MODULES/fips.so $OPENSSL_MODULES/fips.so
RUN openssl fipsinstall -module $OPENSSL_MODULES/fips.so -out /usr/lib/ssl/fipsmodule.cnf
COPY --from=debian-openssl-fipsmodule:3.0.8 /etc/ssl/openssl.cnf /etc/ssl/openssl.cnf
COPY httpd.conf /usr/local/apache2/conf/httpd.conf
EXPOSE 443
Generate localhost keys for testing:
openssl req -x509 -out server.crt -keyout server.key \
-newkey rsa:2048 -nodes -sha256 \
-subj '/CN=localhost' -extensions EXT -config <( \
printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
Build the image and run:
docker run --security-opt label=disable \
--mount type=bind,src=server.crt,target=/usr/local/apache2/conf/server.crt \
--mount type=bind,src=server.key,target=/usr/local/apache2/conf/server.key \
-it --rm -p 8443:443 $(docker build -q .)
Observe a log message of the form AH01884: OpenSSL has FIPS mode enabled
. Confirm that the site returns data by visiting https://localhost:443.
Confirm that the site connects with a FIPS cipher TLS_AES_256_GCM_SHA384
:
echo | openssl s_client -connect localhost:8443 -ciphersuites TLS_AES_256_GCM_SHA384
Confirm that the site does not connect with a non-FIPS cipher TLS_CHACHA20_POLY1305_SHA256
:
echo | openssl s_client -connect localhost:8443 -ciphersuites TLS_CHACHA20_POLY1305_SHA256