-
Notifications
You must be signed in to change notification settings - Fork 87
FIPS
FIPS 140-3 variant of jruby-openssl.
Ships the unmodified BC-FJA module (NIST cert) and a FIPS-aware loader,
emulates the Ruby OpenSSL native library on top of BC-FIPS through JCE.
jruby-opensslvariant is not itself FIPS (140-3) validated, what carries FIPS compliance is the unmodified validated cryptographic module (bundled with the gem)
gem is not a full drop-in replacement, the
OpenSSLAPI surface is unchanged, in approved-only mode only FIPS-approved algorithms and parameters are exposed, code that reaches for a non-approved primitive raises instead of proceeding
bundle add jruby-openssl --source "https://gem.coop/@jossl-fips"or
gem "jruby-openssl", source: "https://gem.coop/@jossl-fips"(J)Ruby's standard require 'openssl' than resolves to gem's loader, which:
- refuses to load if a non-FIPS BouncyCastle provider is already registered
- loads
bc-fips,bcutil-fips,bcpkix-fips,bctls-fipslibraries - delegates
OpenSSLcryptographic operations to the FIPS-variant provider
For NIST-validated operation, start the JVM with the BC-FIPS integrity self-test and approved-only mode, using the org.bouncycastle.fips.approved_only property:
JRUBY_OPTS='-J-Dorg.bouncycastle.fips.approved_only=true' ruby -ropenssl -e 'p OpenSSL.fips_mode'All the upstream jruby-openssl documentation
applies here as well - the OpenSSL API and supported configuration knobs are the same.
| Property | Default | Effect |
|---|---|---|
org.bouncycastle.fips.approved_only |
unset by default; should be true in production FIPS use
|
BC-FIPS approved-only mode |
OpenSSL::X509::Store#set_default_paths (used by SSLContext#set_params) runs when
openssl gets loaded and reads JDK's $JAVA_HOME/lib/security/cacerts by default.
That file is a (Java) keystore, so needs a provider offering its actual JKS or PKCS12
type - both keystore types come from the JDK's own (built-in SUN) security provider.
Reading the default JDK truststore involves no cryptographic operation: JOpenSSL opens
it with a null password, and both JKS and PKCS12 skip their integrity check when
there is no password. Only certificate decoding happens and leaving SUN provider registered
for this is therefore not an actual FIPS concern, but if you would rather not rely on it
(or have special requirements), use a PEM bundle or a custom BC store.
The format of cacerts depends on the JDK - JKS up to 17, PKCS12 since Java 18 and is
detected from the file itself, neither keystore.type nor keystore.type.compat has to be set
for it to be read.
| Trust source | needs a JDK provider |
|---|---|
PEM bundle via SSL_CERT_FILE / SSL_CERT_DIR
|
no |
JDK cacerts (JKS or PKCS12) |
SUN |
PKCS12 / JKS truststore via SSL_CERT_FILE
|
SUN |
BCFKS truststore via javax.net.ssl.trustStore*
|
BCFIPS |
Using a PEM bundle - no keystore, no JDK provider, no extra properties:
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt # or SSL_CERT_DIR=/etc/ssl/certsOnly .crt, .cer and .pem file names take this path; anything else is read as a keystore.
SSL_CERT_DIR expects OpenSSL's hashed layout (c_rehash).
Minimal java.security keeping the JDK's cacerts - SUN stays for the JKS and PKCS12
keystore types:
security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider C:HYBRID;ENABLE{ALL};
security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS
security.provider.3=SUNNOTE: on JDK 8 providers have to be listed by class name (security.provider.3=sun.security.provider.Sun),
the short SUN form is JDK 9+, and a wrong entry silently leaves the provider out, surfacing as JKS not found.
Explicit BCFKS truststore:
JRUBY_OPTS="-J-Djavax.net.ssl.trustStore=/path/to/truststore.bcfks \
-J-Djavax.net.ssl.trustStoreType=BCFKS \
-J-Djavax.net.ssl.trustStoreProvider=BCFIPS \
-J-Djavax.net.ssl.trustStorePassword=changeit" ruby -ropensslPKCS12 truststore via SSL_CERT_FILE: PKCS12 is a standard format (used by C OpenSSL)
rather than an approved algorithm, and this (auto-detected) path requires the certificates to be unprotected,
since JOpenSSL opens the file with a null password, build it the way the JDK builds its own cacerts:
keytool -importcert -noprompt -alias ca1 -file ca1.pem \
-keystore cert_truststore.p12 -storetype PKCS12 -storepass changeit \
-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE -J-Dkeystore.pkcs12.macAlgorithm=NONE
export SSL_CERT_FILE=/path/to/cert_truststore.p12-
Legacy ciphers e.g.
OpenSSL::Cipher.new('des-cbc'),'bf-cbc'(Blowfish),'rc4','rc2-cbc','cast5-cbc','seed-cbc'are refused -
MD5 / MD4 as digests -
OpenSSL::Digest.new('MD5')and MD5-based key derivation (Cipher#pkcs5_keyivgen) are not approved -
Legacy encrypted PEM - MD5-based
DEK-Infoformat (olderOpenSSL::PKey.read(pem, pass)keys) cannot be read or written - Undersized keys / parameters e.g. RSA below 2048-bit and HMAC keys shorter than 112 bits
-
PKCS12 round-trips -
OpenSSL::PKCS12.createwrites PBES2/PBKDF2/AES-256-CBC key and certificate bags with a PBMAC1 (RFC 9579) MAC, matching C OpenSSL FIPS behavior. Reading is done on the module and handles both PBMAC1 (which OpenSSL writes under FIPS) and the legacy PKCS12KDF MAC. PBMAC1 requires a PBKDF2 salt of at least 128 bits (openssl pkcs12 -export -macsaltlen 16), empty passwords are refused (due BC).
This gem ships the Bouncy Castle FIPS Java API (BC-FJA) along with its companion jars:
bc-fipsbcutil-fipsbcpkix-fipsbctls-fips
These are libraries Bouncy Castle publishes, shipped byte-for-byte as released: nothing is modified, repackaged or shaded - so the FIPS 140-3 HMAC-SHA2-256 integrity self-test built into the module is kept valid.
The BC-FIPS jars are Copyright (c) The Legion of the Bouncy Castle Inc. and are licensed under the Bouncy Castle Licence - see LICENSE.bouncycastle.
Further reading:
Bouncy Castle FIPS log via java.util.logging (JUL) JDK's built-in logging.
BC-JSSE (TLS implementation) is fairly chatty out of the box: it reports JDK-wide disabled-algorithm policy at first load and emits an INFO line per TLS handshake. Because JUL's default console handler prints INFO and above, these would show up on stderr in an otherwise quiet application.
By default, jruby-openssl adjusts the level on exactly those known-noisy loggers
(init-time reports to SEVERE and per-handshake logs to WARNING so genuine protocol
problems are still visible). This silencing is skipped when you have configured JUL
yourself e.g. using a java.util.logging.config.file and can also be turned off
explicitly with -Djruby.openssl.log.silence=false.
Any BC logger can then be tuned the usual JUL way, in your logging.properties:
org.bouncycastle.jsse.provider.ProvTlsClient.level = FINEjruby-openssl has its own small logger facade, that defaults to the process output
and is gated by setting OpenSSL.debug = true or -Djruby.openssl.debug=true.
This is convenient for quick debugging but isn't wired into any logging framework.
Since the FIPS module already logs through JUL, we recommend to switch jruby-openssl
logging to the JUL backend too, so both go to the same place with one configuration:
-Djruby.openssl.log.logger=jul
Messages are then emitted through java.util.logging under org.jruby.ext.openssl.*
logger names and are configured like any other JUL logger:
handlers = java.util.logging.ConsoleHandler
.level = INFO
# jruby-openssl internals
org.jruby.ext.openssl.level = FINE
java.util.logging.ConsoleHandler.level = FINEIt's the same OpenSSL API, only the cryptographic backend changes.
Regular jruby-openssl runs on the general-purpose Bouncy Castle (or the Java built-in)
provider, this variant runs on the FIPS-validated Bouncy Castle FIPS provider.
It ships as a separate gem, from a separate source, but under the same name
(jruby-openssl) so application code and require 'openssl' stay unchanged.
BC-FIPS is Bouncy Castle's FIPS 140-3 validated cryptographic module and Java API,
distinct from the regular BC provider jars.
The FIPS variant of the gem bundles the bc-fips, bcutil-fips, bcpkix-fips and
bctls-fips jars. Upstream releases live at downloads.bouncycastle.org/fips-java.
BC-FIPS is not the regular provider, it's a separate fork (branched from regular BC around 1.58), differences:
-
BouncyCastleFipsProvider("BC-FIPS") instead ofBouncyCastleProvider("BC"). -
No direct primitives - regular BC lets you instantiate engines/digests/signers from
org.bouncycastle.crypto.*, BC-FIPS replaces those with an approved-mode factory pattern where primitives can only be reached through approved services. - A validated module - "FIPS 140-3 validated" means an independent NIST-accredited lab tested the exact provider under NIST's CMVP: its algorithm implementations checked against NIST's known answer tests, key-management and self-test requirements, after which NIST issued certificate #4943.
- Divergent APIs (because of the fork and its requirement), e.g. ASN.1 and key classes differ in both names and types.
Standard (non-FIPS) jruby-openssl is built against regular BC, so it cannot simply load
the FIPS jars. This variant (and especially the parent) need to be aware of the subtle API
differences and potential illegal cryptographic operations and be adapted.
In approved-only mode BC-FIPS permits only FIPS-approved algorithms and parameters, so some
things (that work on regular jruby-openssl) are rejected:
- Legacy ciphers - DES/3DES-as-DES, Blowfish, CAST5/CAST6, RC2, RC4, SEED, and CFB-1 mode
-
MD5-based key derivation e.g.
Cipher#pkcs5_keyivgen(which defaults to MD5) - Undersized keys - RSA below 2048 bits for signing, HMAC keys shorter than 112 bits
An operation that BC-FIPS refuses surfaces as an OpenSSL error, not a silent downgrade — the
point of FIPS is that non-approved crypto fails closed.
No, jruby-openssl gem is not validated, the only validated artifact is the unmodified BC-FIPS module, responsible for all cryptographic operations. Using this gem in approved-only mode means your cryptography runs inside that validated module using approved algorithms: it does not certify your application, JVM, OS, or deployment. Overall FIPS claims remain your responsibility.
Gem is not published on rubygems.org, jruby-openssl name there belongs to the
non-FIPS artifact. The FIPS (open-source) variant is published to a separate source,
point your Gemfile at:
source 'https://rubygems.org'
gem 'jruby-openssl', source: 'https://gem.coop/@jossl-fips' # GPL-3.0 licensedThe commercial edition is distributed through a separate source, see COMMERCIAL for access and contact details.
Either way gem name stays jruby-openssl, it's a drop-in replacement for the default gem.
Use BCFKS for persisted private keys, secret keys and their certificates. It is the
BC-FJA approved keystore format - protected using PBKDF2 with HMAC-SHA512 and AES-CCM.
The BC-FJA user guide recommends BCFKS stores backed by the BCFIPS provider for
private client and server credentials e.g. when setting up TLS.
Trust stores are different e.g. TLS only reads certificate entries when building a trust chain.
You can continue leveraging JDK's (certificate-only) cacerts store, loaded using the built-in
security provider, or they can be an explicitly configured BCFKS truststore.
No, BC's minimal BCJSSE FIPS configuration registers BCFIPS, BCJSSE in
fips:BCFIPS mode, and SUN, it does not change keystore.type.
BCJSSE honors that security property as the default for otherwise unspecified stores,
but it does not convert $JAVA_HOME/lib/security/cacerts.
Setting keystore.type globally to BCFKS while cacerts remains JKS or PKCS12 makes
the BCFKS parser read the wrong file format.
Use the standard javax.net.ssl.keyStore* and javax.net.ssl.trustStore* system
properties to select a non-default file, type, provider and password explicitly.
These properties are documented by the BCJSSE user guide.
BC-FJA (2.x) can optionally expose a JKS implementation by setting
org.bouncycastle.jca.enable_jks=true but in approved operation it is restricted to
reading certificate-only stores - must not be used for JKS files containing private
or secret keys. Use explicit BCFKS for protected credentials instead.
Packaging and maintaining the FIPS variant - a bundled validated module, a separate build,
a second artifact, and keeping all of it in step with the parent - is a considerable amount
of ongoing work on top of jruby-openssl itself.
The dual GPL-3.0 + commercial model is how we are trying to gauge whether there is enough interest to sustain that work. Open-source and internal use are already covered by GPL-3.0 (see COMMERCIAL for what does not need a commercial license).
jruby-openssl is largely maintained on volunteer time, and FIPS support adds to that
on both sides of the split. A pure-Java OpenSSL is what lets JRuby ship without native
dependencies; the alternative, binding to C OpenSSL, would trade that away for packaging
and deployment friction. Commercial licensing is how the work of keeping the JVM route
viable can be funded, and interest in it is a clear signal that it's worth the effort.
BC-FIPS can run in two states. In the default "general mode" it still exposes non-FIPS
approved algorithms and parameters, so you could unknowingly use non-compliant crypto.
Approved-only mode (-Dorg.bouncycastle.fips.approved_only=true) restricts the runtime
to the FIPS-approved algorithm set and key sizes (the actual FIPS operational state).
This gem assumes an approved-only default, see Approved-only mode for the flag.
For the FIPS variant to be a genuine drop-in, application code and transitive dependencies
must resolve the same gem name. RubyGems identifies a gem solely by name + version +
platform and neither platform nor a metadata field can act as a "variant" selector.
Thus the only way to be a true drop-in is to reuse the name jruby-openssl which is why
this is not published as a separate jruby-openssl-fips name, and why the gem comes
from a separate source.
With the name fixed there's extra metadata to be able to distinguish the gems installed locally, there's also a versioning convention. The FIPS gem mirrors the parent release it is built from and appends a fourth segment as its own version (revision) number:
Normal (jruby-openssl) |
FIPS (jruby-openssl) |
|---|---|
0.20.0 |
0.20.0.1, 0.20.0.2, … |
0.20.1 |
0.20.1.1, … |
The revision starts at 1 (a .0 would compare equal to the bare parent version).
This gem's customizations are (re-)licensed under GPL-3.0, see LICENSE. Commercial (non-GPL) licensing is available for organizations embedding this gem in proprietary or non-GPL compatible products, see COMMERCIAL.
Generated content - edits made here will be overwritten.