Skip to content

[improve][client] Seed MessageCryptoBc from the BC-FIPS DRBG when that provider is registered - #26154

Merged
merlimat merged 5 commits into
apache:masterfrom
david-streamlio:fips-qw-client-crypto
Aug 28, 2026
Merged

[improve][client] Seed MessageCryptoBc from the BC-FIPS DRBG when that provider is registered#26154
merlimat merged 5 commits into
apache:masterfrom
david-streamlio:fips-qw-client-crypto

Conversation

@david-streamlio

@david-streamlio david-streamlio commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Motivation

MessageCryptoBc pins SecureRandom.getInstance("NativePRNGNonBlocking") for data-key and GCM-IV generation. That algorithm does not exist in FIPS providers, and even when the fallback engages, randomness is sourced outside the FIPS-validated module. NIST SP 800-38D permits random 96-bit GCM IVs only when they come from an approved DRBG, so on a JVM running a restricted security configuration (e.g. BC-FIPS in approved-only mode) client-side encryption silently generates keys and IVs with non-approved randomness.

This is one of a set of small preparatory cleanups for FIPS-restricted deployments; the broader proposal is #26155.

Modifications

MessageCryptoBc: when a BCFIPS provider is registered, obtain SecureRandom.getInstance("DEFAULT", bcfips) — the BC-FIPS SP 800-90A DRBG — so key and IV generation stays within the FIPS-validated module. Only registered providers are consulted (Security.getProvider), deliberately avoiding BouncyCastle classpath resolution during class loading, consistent with the existing lazy BcProviderHolder design. Behaviour on standard JVMs is unchanged: NativePRNGNonBlocking, falling back to new SecureRandom().

This PR was originally two changes; the second is now obsolete. It also made KeyManagerProxy fall back from KeyStore.getInstance("JKS") to KeyStore.getDefaultType() on JVMs where the JKS type is unavailable. PIP-478 has since removed KeyManagerProxy along with the rest of the PIP-337 SSL machinery, and its replacement already covers the concern: the in-memory carrier keystore is built through JcaKeyStores, which selects BCFKS then PKCS12 under a pinned JCA provider and never asks for JKS. That half was dropped when master was merged in, so what remains is the one file above.

Verifying this change

  • Make sure that the change passes the CI checks.

Covered by existing tests: RawBatchMessageContainerImplTest exercises MessageCryptoBc encryption end to end. The BCFIPS branch cannot be exercised without a FIPS-registered JVM; it is compile-verified, and the DEFAULT SecureRandom service is confirmed present in bc-fips 2.0.1.

Does this pull request potentially affect one of the following parts:

  • Dependencies (add or upgrade a dependency)
  • The public API
  • The schema
  • The default values of configurations
  • The threading model
  • The binary protocol
  • The REST endpoints
  • The admin CLI options
  • The metrics
  • Anything that affects deployment

…ted JVMs

Two small fixes for JVMs running a restricted security configuration
(e.g. BC-FIPS in approved-only mode):

- MessageCryptoBc: when the BCFIPS provider is registered, source
  randomness for data-key and IV generation from its SP 800-90A DRBG
  instead of the SUN NativePRNGNonBlocking algorithm, which does not
  exist in FIPS providers. Only registered providers are consulted so
  class loading still avoids BouncyCastle classpath resolution.
- KeyManagerProxy: fall back to the platform default keystore type when
  the hardcoded JKS type is unavailable. JKS remains preferred because it
  accepts certificate lists that do not form a linked chain, which
  PKCS12 rejects (verified by KeyManagerProxyTest's multiple-CA case).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves Pulsar client-side TLS/crypto helper compatibility with FIPS-restricted JVM security configurations by avoiding hardcoded SUN-provider assumptions and selecting FIPS-appropriate primitives when the BC-FIPS provider is registered.

Changes:

  • MessageCryptoBc: Prefer SecureRandom from the registered BCFIPS provider (its DRBG) when available; otherwise keep existing NativePRNGNonBlocking behavior.
  • KeyManagerProxy: Prefer JKS for the in-memory keystore but fall back to KeyStore.getDefaultType() when JKS is unavailable (e.g., under FIPS restrictions).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
pulsar-common/src/main/java/org/apache/pulsar/common/util/KeyManagerProxy.java Adds JKS → default keystore-type fallback for the in-memory keystore used by the auto-refreshing PEM key manager.
pulsar-client-messagecrypto-bc/src/main/java/org/apache/pulsar/client/impl/crypto/MessageCryptoBc.java Switches SecureRandom selection to use BC-FIPS DRBG when BCFIPS is registered, preserving non-FIPS behavior otherwise.

Comment thread pulsar-common/src/main/java/org/apache/pulsar/common/util/KeyManagerProxy.java Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@david-streamlio
david-streamlio requested a review from merlimat July 9, 2026 14:55
…ypto

Resolves the KeyManagerProxy conflict by taking master's deletion. PIP-478
removed KeyManagerProxy along with the rest of the PIP-337 SSL machinery, and
its replacement already covers what this branch changed there: the in-memory
carrier keystore is now built through JcaKeyStores, which selects BCFKS then
PKCS12 under a pinned JCA provider and never asks for JKS. The JKS-to-default-
type fallback therefore has no site left, and nothing outside the deleted file
referenced the class.

The MessageCryptoBc change is unaffected and still applies: master's static
initializer still seeds from NativePRNGNonBlocking unconditionally.
@david-streamlio david-streamlio changed the title [improve][client] Make client TLS/crypto helpers work on FIPS-restricted JVMs [improve][client] Seed MessageCryptoBc from the BC-FIPS DRBG when that provider is registered Aug 26, 2026
david-streamlio added a commit to david-streamlio/pulsar that referenced this pull request Aug 26, 2026
PIP-478 has landed since this proposal was written, and it moved or closed several of the things
the Motivation section cites as current state. Reviewers who check the first claim and find it
already fixed will discount the rest, so the audit is done against master rather than left to them.

SecurityUtility is gone (apache#26322 deleted it with the rest of the PIP-337 TLS stack), and the PIP
leaned on it in six places -- the Background paragraph establishing that Pulsar is already
FIPS-capable at the JCA layer, the packaging jar-swap rationale, the EC curve allowlist, the
fipsMode validator's first assertion, the High Level Design, and the integration test. Each now
names JcaProviders, whose ResolvedBouncyCastleProvider(Provider, boolean fips) reports the flavour
as a value rather than a static predicate. Its registration-first resolution order is recorded too:
a provider an operator registered in java.security is preserved rather than shadowed by a
classpath-constructed one, which is the property a FIPS deployment depends on.

Motivation item 2 is closed. PulsarSslConfiguration, PulsarSslFactory and DefaultPulsarSslFactory
no longer exist; every listener builds from a PulsarTlsFactory, and the broker binary listener
carries engineProvider(TlsFactorySupport.engineProvider(conf.getTlsProvider())). Rewritten to what
survives -- an unset tlsProvider still resolves to the native engine, because tcnative remains an
unconditional pulsar-common dependency, so FIPS mode must pin the engine and drop the jar.

Motivation item 3 is stale in form: the declared defaults are now empty rather than the literal
Conscrypt, and unset *means* Conscrypt only through resolveWebJsseProvider, on the web listeners.
The concern survives; the citations did not.

Items 1, 4, 5 and 6 were re-verified and hold, with line numbers corrected. Item 1 gains the
version catalog's own statement of the gap (libs.versions.toml:62-63).

Adds the gap the PIP did not list: DefaultBrokerTlsFactory resolves its JSSE provider as
resolveJsseProvider(null, ...), so the broker's jsseProvider and brokerClientJsseProvider keys are
declared but ignored -- item 2's shape in a newer key. apache#26326 fixes it and adds the jcaProvider
configuration surface that design (a) assumes, so the dependency is now stated rather than implied.

Housekeeping: apache#26152 and apache#26153 are merged, not open; apache#26154 no longer claims the KeyManagerProxy
half, which PIP-478 obsoleted; and the Configuration table's four pre-PIP-478 provider rows become
two rows naming the keys fipsMode actually evaluates.
@david-streamlio

Copy link
Copy Markdown
Contributor Author

@lhotari tagging you here because PIP-478 changed the shape of this PR, and the surviving half sits next to your work.

It was two changes. The second made KeyManagerProxy fall back from KeyStore.getInstance("JKS") to the platform default type on JVMs where JKS is unavailable. #26322 deleted that class, and JcaKeyStores already covers the concern better — it selects BCFKS then PKCS12 under a pinned JCA provider and never asks for JKS at all. So I merged master and took the deletion rather than re-homing the hunk. Nothing outside the deleted file referenced the class, and the only surviving getInstance("JKS") in the tree is tests/certificate-authority/RemoveJksPassword.java.

What's left is one file, +10/-1: MessageCryptoBc's static initializer still seeds unconditionally from NativePRNGNonBlocking, so on a BC-FIPS JVM the data-key and GCM-IV randomness comes from outside the validated module. NIST SP 800-38D only permits random 96-bit GCM IVs from an approved DRBG. When BCFIPS is registered the change takes SecureRandom.getInstance("DEFAULT", bcfips) instead; behaviour on a standard JVM is byte-for-byte what it is today.

It reads the provider with Security.getProvider(...) rather than JcaProviders.bouncyCastleProvider() on purpose — this runs during class initialization, and the helper resolves reflectively from the class path. Happy to switch the literal to JcaProviders.BC_FIPS if you'd prefer the constant.

CI is 44/44 on e4d106f6ee.

… unavailable

The static initializer wrapped both the BCFIPS and the non-FIPS branch in one
try, so a NoSuchAlgorithmException from either fell back to new SecureRandom().
On a JVM where BCFIPS is registered but its DEFAULT SP 800-90A DRBG cannot be
obtained, that resolves by provider search order and can land outside the
validated module, with nothing at run time to indicate it.

NIST SP 800-38D only permits a random 96-bit GCM IV from an approved DRBG, so a
silent fallback there defeats the reason the branch exists.

Split the two paths:

- BCFIPS registered and its DRBG unavailable is now fatal. An IllegalStateException
  naming the provider and the reason, with the original exception as cause,
  surfaces as ExceptionInInitializerError. Registering BCFIPS is an operator
  asking for FIPS-approved randomness; refusing to start is safer than starting
  outside the module.
- Without BCFIPS the behaviour is unchanged: NativePRNGNonBlocking, falling back
  to the platform default where it is not available. No FIPS guarantee was being
  claimed on that path.

Addresses the review comment on the original change.
@david-streamlio

Copy link
Copy Markdown
Contributor Author

@merlimat @lhotari gentle ping on this one — it has been open a while and I think it is ready for a look.

It is small and self-contained now: one file, MessageCryptoBc.java, +27/-4. The scope narrowed to just the DRBG seeding after the earlier round of review.

What it does. When the BC-FIPS provider is registered, seed MessageCryptoBc from that provider's SP 800-90A DRBG rather than from new SecureRandom(). new SecureRandom() resolves by provider search order, so data-key and GCM-IV material could come from outside the validated module with nothing at run time to indicate it — and NIST SP 800-38D only permits a random 96-bit GCM IV from an approved DRBG. On a JVM without BCFIPS the behaviour is byte-for-byte unchanged (NativePRNGNonBlocking, falling back to the platform default), which was the point of keeping the two paths separate.

Copilot raised that the original version still fell back silently when BCFIPS was registered but its DRBG could not be obtained; that is fixed in 44d54794a7, which makes that case fail fast instead. Both review threads are resolved.

State: CI green on 44d54794a7 (full run, including the flaky suite), no conflicts with master, no rebase needed.

Happy to adjust the approach if you would rather this were handled differently — and no rush, just did not want it to go stale.

@merlimat
merlimat merged commit 367caff into apache:master Aug 28, 2026
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants