Skip to content

Make Bouncy Castle optional in phase4-lib - #389

Open
gregjotau wants to merge 1 commit into
phax:masterfrom
gregjotau:contribution/make-ph-bc-optional
Open

Make Bouncy Castle optional in phase4-lib#389
gregjotau wants to merge 1 commit into
phax:masterfrom
gregjotau:contribution/make-ph-bc-optional

Conversation

@gregjotau

@gregjotau gregjotau commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

This replaces #386. I accidentally opened that pull request from the wrong GitHub account; this is the same focused change from my personal account, rebased onto current master.

Why this is desirable

phase4-lib currently makes ph-bc mandatory because ECryptoAlgorithmCrypt and ICryptoAlgorithmCrypt expose ASN1ObjectIdentifier and initialize seven algorithm identifiers from CMSAlgorithm. Those identifiers are standards-defined metadata; phase4-lib does not otherwise invoke a Bouncy Castle implementation from production code.

This means a consumer inherits a crypto-provider dependency merely to initialize/read algorithm metadata, even when its actual AS4 security path is supplied through WSS4J/JCA or when it manages providers explicitly. It also makes basic enum, configuration, and AOT analysis dependent on a provider-specific library.

This is dependency-boundary cleanup, not a claim that AS4 encryption no longer needs cryptographic implementations. In particular, our Peppol application still receives Bouncy Castle independently through peppol-commons and WSS4J/Cryptacular, so this does not reduce that application today. The benefit is that phase4-lib no longer imposes an independent BC edge on every consumer and profiles can state their real requirements explicitly.

Alternative used

No alternative crypto provider is introduced. The seven stable OIDs are stored in their canonical dot-decimal form, and a provider-neutral getOIDString API exposes them. Actual signing/encryption remains unchanged and continues through the existing WSS4J/JCA code.

For compatibility, the existing getOID method remains with the same ASN1ObjectIdentifier return descriptor, is deprecated, and constructs the value lazily. Calling that legacy method still requires ph-bc. Reflection/AOT tooling that eagerly resolves every method descriptor may also still require it until a future major release can remove the BC-typed method.

ph-bc remains explicitly declared in the BDEW production profile, which directly uses BC APIs, and in module tests that directly exercise BC behavior.

Verification

  • isolated classloader test loads every ECryptoAlgorithmCrypt value and reads all OIDs while hiding every org.bouncycastle class
  • targeted seven-module reactor on current master: 204 tests, 0 failures, 0 errors, 1 existing skip
  • Maven verify run on JDK 26
  • upstream CI on the original identical change passed Java 17, 21, and 25

This changes only the direct phase4-lib to ph-bc dependency edge. The independent WSS4J/Cryptacular dependency path is intentionally untouched.

Keep standards-defined encryption OIDs as provider-neutral strings so phase4-lib does not require ph-bc merely for algorithm metadata. Preserve the BC-typed OID method lazily for compatibility and keep ph-bc explicit in modules with real BC usage.
@gregjotau
gregjotau force-pushed the contribution/make-ph-bc-optional branch from 56055d4 to bfdda5f Compare August 11, 2026 08:22
@phax

phax commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Thanks for the updated PR. As WSS4J also has some partial dependency on BC, I need to dig a bit further into this one. Also some stuff is loaded purely by the presence in the classpath (SPI). Therefore it's not contained in the last minor version update

@phax

phax commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Following up on my previous comment — I dug into the indirect BC paths in phase4-lib so we have a shared factual basis. All of the below is against current master (345ade0, 4.6.1-SNAPSHOT, ph-commons 12.3.5, bcprov 1.85.2 / bcpkix + bcutil 1.85), i.e. without this PR applied.

Which classpath jars actually reference BC bytecode

Of the 70 jars on phase4-lib's resolved runtime classpath, only 4 contain references to org.bouncycastle:

Jar distinct BC refs Where
org.cryptacular:cryptacular:1.2.7 191 throughout
com.helger.commons:ph-security:12.3.5 16 CertificateHelper, CRLHelper, EOCSPResponseStatus
org.apache.wss4j:wss4j-ws-security-common:4.0.1 6 only BouncyCastleUtils
com.helger.commons:ph-bc:12.3.5 2 PBCProvider

xmlsec:4.0.4, wss4j-ws-security-dom:4.0.1 and jasypt:1.9.3 reference none.

The WSS4J path (the "partial dependency" I mentioned)

It narrows to exactly one method pair: BouncyCastleUtils.getSubjectKeyIdentifierBytes() / getAuthorityKeyIdentifierBytes(). Callers:

  • DOMX509SKI — both constructors
  • MerlinAKI — not reachable, since AS4CryptoFactoryConfiguration only supports Merlin

DOMX509SKI is reached from two places, and this is the part that matters:

  1. OutboundWSSecEncryptedKey.createEncryptedKeyElement(), guarded by keyIdentifierType == 16, i.e. WSConstants.X509_SKI, which we expose as ECryptoKeyIdentifierType.X509_SKI_KEY_IDENTIFIER. It is a conditional branch, so BC is only touched when a user selects that type; the BST_DIRECT_REFERENCE default never reaches it. Note that SKI_KEY_IDENTIFIER (= 4) does not go through DOMX509SKI — that path uses Merlin.getSKIBytesFromCert, which is BC-free.
  2. InboundEncryptedKeyProcessor, branching on the incoming ds:X509SKI element. This one is not under our control: any remote sender that references its key that way makes BC mandatory for decryption, regardless of local configuration.

So with ph-bc optional, BC becomes conditionally required at runtime, and one of those conditions is chosen by the peer, not by us. Any profile that can encounter this needs a hard ph-bc dependency, or we need a defined failure mode — otherwise it is a NoClassDefFoundError during inbound message processing.

cryptacular

cryptacular is declared by wss4j-ws-security-common and pulls in bcprov + bcpkix on a second, independent edge (1.78.1, managed up to 1.85.x here). However, no class in either wss4j-ws-security-common or wss4j-ws-security-dom references org.cryptacular — only META-INF/DEPENDENCIES and the pom mention it. It looks like a transitive dependency that is unused on this classpath (presumably needed by wss4j-ws-security-stax, which we do not use). Worth verifying before excluding it, but if it can be excluded, that removes the second BC edge.

ph-security

Only CertificateHelper.isCA() uses BC among the classes we touch, and phase4-lib does not call it — we only use CertificateHelper.getPEMEncodedCertificate() in AS4IncomingHelper, which is BC-free. CRLHelper and EOCSPResponseStatus are unreachable from phase4-lib. Relevant detail: ph-security declares ph-bc as <optional>true</optional>, so phase4-lib's explicit declaration is currently what makes it resolvable at all.

On the SPI / classpath-presence point

Two things are loaded purely by presence:

  • bcprov-jdk18on ships META-INF/services/java.security.Provider listing org.bouncycastle.jce.provider.BouncyCastleProvider and org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider.
  • ph-bc ships META-INF/services/com.helger.base.thirdparty.IThirdPartyModuleProviderSPIThirdPartyModuleProvider_ph_bc. If ph-bc is absent from a deployment, BC stops appearing in the third-party module listing. Cosmetic, but it changes the generated dependency list.

For the record, phase4-lib itself never calls Security.addProvider and never registers BouncyCastleProvider: WSSConfigManager only installs WSS4J's own STRTransform / AttachmentContentSignatureTransform / AttachmentCompleteSignatureTransform providers. Provider injection is entirely up to the caller via AS4SigningParams.setSecurityProvider* and AS4CryptParams.setSecurityProvider*. Additionally, EKeyStoreType.BKS and BCFKS are selectable from configuration and need a registered BC provider at runtime — JKS is the default.

On getOID()

Confirming your premise: ICryptoAlgorithmCrypt.getOID() has no consumer anywhere in this repository apart from the assertNotNull in ECryptoAlgorithmCryptTest. It really is the only compile-time reason phase4-lib needs BC.

Method

Bytecode scan (grep -a for org/bouncycastle over every class of every jar on the resolved runtime classpath, then javap -c on the hits to identify the calling methods and branch conditions). Reflective usage would not show up this way, so the cryptacular conclusion in particular is "no static references", not "provably unused".

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.

2 participants