Skip to content

Add FIC Leg 2 over mTLS Proof-of-Possession (stacked on #1040) - #1041

Open
Robbie-Microsoft wants to merge 16 commits into
rginsburg/sni-mtls-popfrom
rginsburg/sni-mtls-pop-fic-leg2
Open

Add FIC Leg 2 over mTLS Proof-of-Possession (stacked on #1040)#1041
Robbie-Microsoft wants to merge 16 commits into
rginsburg/sni-mtls-popfrom
rginsburg/sni-mtls-pop-fic-leg2

Conversation

@Robbie-Microsoft

@Robbie-Microsoft Robbie-Microsoft commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #1040. Adds 2-leg Federated Identity Credential (FIC) over mTLS Proof-of-Possession (PoP): an app that authenticates with a federated client assertion can still bind the resulting token to a certificate. The assertion is sent with client_assertion_type=...:jwt-pop and the binding certificate is presented on the mTLS handshake, so the final token is bound to the Leg-1 certificate thumbprint.

Stacked on #1040 (base rginsburg/sni-mtls-pop), so the diff is exactly the Leg-2 additions on top of the core work. Retarget the base to dev after #1040 merges.

Public API

ConfidentialClientApplication.builder(clientId, ClientCredentialFactory.createFromClientAssertion(t1))
        .mtlsBindingCertificate(sniCert)   // binding cert presented on the TLS handshake
        .build();
  • ConfidentialClientApplication.Builder.mtlsBindingCertificate(IClientCertificate)

Internal changes

  • Request building (TokenRequestExecutor): under mTLS PoP with an assertion credential, the assertion is sent with client_assertion_type=...:jwt-pop and the binding cert is presented on the mutual-TLS handshake.
  • MtlsClientCertificateHelper.resolveBindingCertificate falls back to the configured mtlsBindingCertificate(...) only when the credential is an IClientAssertion (FIC Leg 2); a non-assertion credential (e.g. a client secret) with a stray binding cert fails with MTLS_POP_ERROR.
  • AssertionRequestOptions carries the PoP flag through to assertion construction; adds the ClientAssertion.ASSERTION_TYPE_JWT_POP constant.

Testing

  • Unit: ficLeg2_assertionWithBindingCert_usesJwtPopAssertionType (MtlsProofOfPossessionTest); resolveBindingCertificate positive (assertion+cert) and negative (secret+cert → MTLS_POP_ERROR) tests (MtlsClientCertificateHelperTest). Suite green (385 tests).
  • E2E: acquireTokenFic_TwoLeg_MtlsPop_BothLegsBound (MtlsPopIT, CI-only — needs the lab SN/I cert + an ESTS allow-listed resource).

Out of scope

Unchanged from #1040: user-scoped (user_fic) FIC over mTLS PoP, managed-identity first-leg mTLS, and the two legacy sovereign hosts.

Adds the 2-leg Federated Identity Credential (FIC) flow over mTLS PoP: an assertion-authenticated confidential client can bind to a certificate via ConfidentialClientApplication.Builder.mtlsBindingCertificate(...), presenting the binding certificate on the TLS handshake and authenticating the federated assertion with client_assertion_type=...:jwt-pop. Both legs are cert-bound and the final token is bound to the Leg-1 certificate thumbprint.

Stacked on #1040 (core direct SN/I -> mTLS PoP); retarget base to dev after #1040 merges.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

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 extends MSAL4J’s confidential-client mTLS Proof-of-Possession (PoP) support to cover 2-leg Federated Identity Credential (FIC) “Leg 2” scenarios, where the app authenticates using a client assertion while still binding the resulting token to a certificate presented on the mTLS handshake.

Changes:

  • Add ConfidentialClientApplication.Builder.mtlsBindingCertificate(IClientCertificate) to supply a binding certificate for assertion-authenticated mTLS PoP requests.
  • Update token request construction to use client_assertion_type=...:jwt-pop when mtlsProofOfPossession() is set with a client assertion credential.
  • Re-introduce unit/integration tests and expand the sample/docs to demonstrate the 2-leg FIC over mTLS PoP flow.

Reviewed changes

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

Show a summary per file
File Description
msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ConfidentialClientApplication.java Adds builder option + getter for mtlsBindingCertificate used to present a cert on the mTLS handshake for assertion-authenticated apps.
msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenRequestExecutor.java Switches client-assertion requests to jwt-pop assertion type under mTLS PoP and threads PoP context to assertion construction.
msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/MtlsClientCertificateHelper.java Extends binding-certificate resolution to fall back to mtlsBindingCertificate for assertion-authenticated scenarios.
msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ClientCredentialParameters.java Updates mTLS PoP Javadoc to mention mtlsBindingCertificate(...) for assertion-authenticated apps.
msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ClientAssertion.java Introduces ASSERTION_TYPE_JWT_POP constant for client_assertion_type.
msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AssertionRequestOptions.java Adds a PoP flag to the context passed to context-aware assertion providers.
msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MtlsProofOfPossessionTest.java Re-adds unit coverage for “FIC Leg 2” behavior (jwt-pop + binding cert).
msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MtlsClientCertificateHelperTest.java Adds unit test to ensure binding cert resolves correctly for assertion-authenticated apps.
msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/MtlsPopIT.java Re-adds CI-only E2E test covering the 2-leg FIC mTLS PoP flow.
msal4j-sdk/src/samples/confidential-client/ClientCredentialMtlsProofOfPossession.java Adds an end-to-end sample scenario showing developer-orchestrated 2-leg FIC over mTLS PoP.
changelog.txt Notes the new 2-leg FIC over mTLS PoP capability.
.github/copilot-instructions.md Updates internal repo guidance to mention mtlsBindingCertificate(...) for 2-leg FIC over mTLS PoP.

The Unit Tests build's Credential Scanner (Guardian) gate flags the checked-in PKCS12 test cert (mtls_test_cert.p12), failing the build. It is a self-signed, test-only certificate (CN=msal4j-mtls-test) with no production secret, so allowlist it in build/credscan-exclude.json.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

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

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment thread msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MtlsProofOfPossessionTest.java Outdated
Robbie-Microsoft and others added 8 commits July 7, 2026 18:25
Pulls in the mTLS PoP socket-factory JDK 8 fix and the ClientCredentialParameters thread-safety/comment fixes from #1040. Resolved copilot-instructions.md to keep the FIC Leg 2 sentence alongside the Key Classes markdown split.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The FIC Leg 2 mTLS PoP test now parses the url-encoded body and asserts exact decoded values for client_assertion, client_assertion_type (via the JWT_POP_ASSERTION_TYPE constant, previously unused), and token_type, instead of brittle substring checks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…-leg2

# Conflicts:
#	msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/MtlsClientCertificateHelper.java
…-leg2

# Conflicts:
#	msal4j-sdk/src/integrationtest/java/com/microsoft/aad/msal4j/MtlsPopIT.java
Pulls in the fail-closed mTLS PoP token_type validation. Resolves conflicts keeping both features: the FIC Leg 2 tests and the parent's downgrade handling. Updates the ficLeg2 unit test mock to return token_type=mtls_pop (now that the response type is validated), and routes both FIC e2e legs through acquireMtlsPopOrSkipOnDowngrade so a slice downgrade on either leg is treated as inconclusive rather than a hard failure.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

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

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

Robbie-Microsoft and others added 2 commits July 8, 2026 18:47
resolveBindingCertificate only falls back to the app's mtlsBindingCertificate
when the authenticating credential is an IClientAssertion (FIC Leg 2). A
non-assertion credential (e.g. client secret) with a stray binding cert now
fails with MTLS_POP_ERROR instead of silently binding. Also makes the
ConfidentialClientApplication.mtlsBindingCertificate field private (only read
via its getter). Adds a secret+bindingCert negative test.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Robbie-Microsoft added a commit that referenced this pull request Jul 30, 2026
…name the X509 matrix

Ports the rigor from the merged MSAL.NET reference (PR #6100) to the SNI mTLS PoP
integration tests and docs:

- Add reusable MtlsResourceCaller.callResourceWithMtlsToken(url, token, cert): presents
  the binding certificate on the TLS handshake (reusing createMtlsSocketFactory), sends
  Authorization: mtls_pop <token>, GETs the resource, returns the status; drains and never
  logs the external body. The headline PoP test now calls mtlstb.graph.microsoft.com and
  requires HTTP 200, proving the bound token is genuinely usable (not just well-formed).
- Fix the @BeforeAll escape hatch: assertNotNull on the lab cert hard-failed the whole
  class off-CI; use Assumptions.assumeTrue so it skips instead (missing lab cert is an
  environment condition, not a defect).
- Establish the Credential_X509_Output_<Pop|Bearer> naming: rename the PoP tests into the
  family and add a standalone Credential_X509_Output_Bearer (asserts BEARER + null binding
  cert) instead of relying only on the cache-isolation side effect.
- Remove a redundant assertNotNull(thumbprint) immediately before assertEquals.
- Add docs/mtls-pop.md documenting the correct public API (mtlsProofOfPossession(),
  result.metadata().tokenType()/bindingCertificate(), region optional/global fallback,
  tenanted authority, Bearer omission is mTLS-specific). Written fresh; not copied from the
  stale #1021 branch.
- Fix the sample's missing ClientCredentialFactory import.

The Fic E2E resource call reuses MtlsResourceCaller in the FIC follow-up (#1041).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Robbie-Microsoft and others added 4 commits July 30, 2026 16:43
Bring the parent SNI branch (rginsburg/sni-mtls-pop @ d22059a) into the FIC
branch with --no-ff. Resolve the shared MtlsPopIT.java conflict by adopting the
SNI hardening for the direct-SNI cells while preserving the FIC two-leg test and
its skip-on-downgrade hatch untouched:

 - Adopt: @BeforeAll Assumptions.assumeTrue env-absence skip; the
   Credential_X509_Output_{Pop,Bearer,Pop_CacheHit,Pop_And_Bearer_CacheIsolated}
   matrix; the MtlsResourceCaller resource-call assertEquals(200) on the headline
   Pop cell; Bearer on the regional endpoint; direct mtls_pop assertions (no skip).
 - Preserve: acquireTokenFic_TwoLeg_MtlsPop_BothLegsBound and the retained
   acquireMtlsPopOrSkipOnDowngrade helper it depends on (de-hatching FIC is Task 2).

New parent-only files (docs/mtls-pop.md, MtlsResourceCaller.java) and the sample
import merged cleanly. Integration-test sources compile.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bring the base-resynced parent (48fd92d: SN/I mTLS PoP + upstream dev through
56b9c6b) into the FIC Leg 2 branch. ClientCredentialParameters and
TokenRequestExecutor auto-merged: dev's ExtendedCacheKey refactor / clientClaims
support and this branch's FIC jwt-pop assertion path coexist. Only changelog.txt
conflicted; resolved by keeping the 2-leg FIC entry under the unreleased 1.25.1
section alongside the SN/I mTLS PoP and dev entries. MtlsPopIT.java took this
branch's version unchanged, so the FIC test (acquireTokenFic_TwoLeg_MtlsPop_
BothLegsBound) and its acquireMtlsPopOrSkipOnDowngrade hatch are preserved intact
(de-hatch is Task 2).

mvn -pl msal4j-sdk test = 405 green; integrationtest compiles.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…anch

Brings in MtlsResourceCaller error-body surfacing + Credential_X509_Output_Pop assertion
message. FIC test acquireTokenFic_TwoLeg_MtlsPop_BothLegsBound and its skip gate are untouched.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Pulls in MtlsResourceCaller Accept: application/json fix. FIC test
acquireTokenFic_TwoLeg_MtlsPop_BothLegsBound and its acquireMtlsPopOrSkipOnDowngrade hatch untouched.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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