Java Buildpack Client Certificate Mapper
June 16, 2026 · View on GitHub
| Workflow | Status |
|---|---|
| CI | |
| Release |
The java-buildpack-client-certificate-mapper is a Servlet filter that maps the X-Forwarded-Client-Cert header to the javax.servlet.request.X509Certificate (javax) or jakarta.servlet.request.X509Certificate (jakarta) Servlet attribute. Both base64-encoded DER and URL-encoded PEM certificates, as well as Envoy XFCC format, are supported.
Download
Pre-built jars are available on the Releases page:
- Releases — tagged versions (e.g.
v2.0.2) - Snapshot — latest build from
main(pre-release, updated on every push)
Development
The project requires Java 8. To build and test from source:
$ ./mvnw clean package
XFCC Header Format
The filter supports base64-encoded DER certificates, URL-encoded PEM certificates, and the Envoy XFCC format. In XFCC format, the header contains key-value fields such as Hash=, Cert=, and Subject=. Field names are matched case-insensitively. Multiple header values and the RFC 9110 comma-delimited equivalent are both supported.
The Hash= field (a SHA-256 fingerprint of the leaf certificate, set by the router) is recognised for format detection and optionally sanity-checked, but it cannot be mapped to an X509Certificate without a Cert= field.
XFCC detection and fallback behaviour
An entry is detected as XFCC format when it structurally begins with a short (≤ 20 characters) all-letter key followed by =, and contains at least one of Hash=, Cert=, or Chain=. JSON format is not supported.
If an entry passes the structural check but contains none of the recognised cert-related fields (e.g. only unknown future fields), it is treated as a raw certificate value; parsing will fail and a warning is logged. This preserves the same external behaviour as the raw-cert fallback path.
CF Gorouter XFCC fields
CF Gorouter emits only Hash= and Subject= in the XFCC header. The Cert= field is emitted only when the router is configured to forward the full client certificate. By= may also be present as the proxy identity, but its value is not a Subject Alternative Name (SAN) — CF app identity certs do not carry URI SANs or DNS SANs, so URI= and DNS= are not emitted and are not recognised by this library.
Request attributes set from XFCC fields
When the header is in XFCC format, the filter sets the following request attributes (first entry that contains the field wins for multi-entry headers):
| Attribute | Source | Value |
|---|---|---|
org.cloudfoundry.router.xfcc.hash | Hash= | SHA-256 fingerprint of the client certificate |
org.cloudfoundry.router.xfcc.subject | Subject= | Full Subject DN of the client certificate |
org.cloudfoundry.router.xfcc.app.guid | Subject= OU=app:<guid> | CF app GUID parsed from the Subject DN |
org.cloudfoundry.router.xfcc.space.guid | Subject= OU=space:<guid> | CF space GUID parsed from the Subject DN |
org.cloudfoundry.router.xfcc.org.guid | Subject= OU=organization:<guid> | CF organization GUID parsed from the Subject DN |
org.cloudfoundry.router.xfcc.instance.guid | Subject= CN=<guid> | CF app instance GUID parsed from the Subject DN |
The CF Subject DN format is: CN=<instance-guid>,OU=app:<app-guid>,OU=space:<space-guid>,OU=organization:<org-guid>.
These attributes are set regardless of whether a Cert= field is present, so applications can identify the caller even when only a Hash= and Subject= are forwarded by the router.
Unknown fields are silently skipped and logged at FINE level.
Specifications:
- Envoy
x-forwarded-client-certheader — XFCC field definitions (Hash=,Cert=,Chain=,Subject=) - RFC 9110 §5.3 — HTTP header comma-delimited field values
- Jakarta Servlet 6.0 specification —
jakarta.servlet.request.X509Certificateattribute
Error Handling
Certificate parse failures (invalid Base64, invalid CertificateFactory input) are caught inside doFilter(), logged as a WARNING, and the request is passed to the next filter without setting the certificate attribute. This behaviour is unchanged from earlier versions.
Behaviour change (this version): previously, a malformed URL-encoded value in the header (e.g. a %GG sequence that URLDecoder cannot decode) caused an IllegalArgumentException to propagate uncaught out of doFilter(), typically resulting in a 500 response. From this version that exception is caught alongside CertificateException and the same log-and-continue behaviour applies.
Debug Logging
The filter uses Java Util Logging (JUL). To enable debug output, set the logger level for org.cloudfoundry.router to FINE. When enabled, the filter logs the XFCC field names present in each header (e.g. Hash, Cert, Subject). Certificate values are never logged.
CI / Workflows
| Workflow | Trigger | Description |
|---|---|---|
| CI | push to main, pull requests, manual | Builds and runs all tests. On push to main (after tests pass) also publishes the jar to the rolling snapshot release. |
| Release | manual (workflow_dispatch) | Bumps to release version, tags vX.Y.Z, creates a GitHub Release with the jar attached, then advances to the next SNAPSHOT version. |
All workflows can be triggered manually from Actions → select workflow → Run workflow in the GitHub UI.
License
This project is released under version 2.0 of the Apache License.