Skip to content
kares edited this page Sep 2, 2026 · 7 revisions

JRuby-OpenSSL (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-openssl variant 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 OpenSSL API 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

Installation

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:

  1. refuses to load if a non-FIPS BouncyCastle provider is already registered
  2. loads bc-fips, bcutil-fips, bcpkix-fips, bctls-fips libraries
  3. delegates OpenSSL cryptographic operations to the FIPS-variant provider

Approved-only mode

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'

Supported configuration

All the upstream jruby-openssl documentation applies here as well - the OpenSSL API and supported configuration knobs are the same.

Specific Runtime / JVM properties

Property Default Effect
org.bouncycastle.fips.approved_only unset by default; should be true in production FIPS use BC-FIPS approved-only mode

Trusted certificates (DEFAULT_CERT_FILE)

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/certs

Only .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=SUN

NOTE: 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 -ropenssl

PKCS12 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

Features unavailable under FIPS

  • 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-Info format (older OpenSSL::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.create writes 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).

Bundled Bouncy Castle FIPS module

This gem ships the Bouncy Castle FIPS Java API (BC-FJA) along with its companion jars:

  • bc-fips
  • bcutil-fips
  • bcpkix-fips
  • bctls-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:

Logging

BC (FIPS) logging

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 = FINE

Routing jruby-openssl logging (recommended)

jruby-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 = FINE

FAQ

How does the FIPS variant differ from regular jruby-openssl?

It'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.

What is BC-FIPS and where do I get it?

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.

How is BC-FIPS different from regular BC provider?

BC-FIPS is not the regular provider, it's a separate fork (branched from regular BC around 1.58), differences:

  • BouncyCastleFipsProvider ("BC-FIPS") instead of BouncyCastleProvider ("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.

Which algorithms or key sizes are not available under FIPS?

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.

Does using this gem make my application FIPS validated?

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.

How do I obtain the FIPS variant of the gem?

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 licensed

The 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.

What keystore format should I use under FIPS?

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.

Should I change Java's default keystore.type to BCFKS?

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.

Can BCFIPS read JKS truststores?

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.

Why GPL-3.0 / commercial only, not permissively licensed like jruby-openssl?

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.

What is approved-only mode and why should I set it?

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.

Why the same gem name, and how does versioning work?

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).

Licence

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.