Skip to content

Add SN/I certificate support over mTLS Proof-of-Possession (PoP)#1040

Draft
Robbie-Microsoft wants to merge 1 commit into
devfrom
rginsburg/sni-mtls-pop
Draft

Add SN/I certificate support over mTLS Proof-of-Possession (PoP)#1040
Robbie-Microsoft wants to merge 1 commit into
devfrom
rginsburg/sni-mtls-pop

Conversation

@Robbie-Microsoft

Copy link
Copy Markdown
Contributor

Summary

Adds support for using a Subject-Name/Issuer (SN/I) certificate as the first-leg credential over mTLS Proof-of-Possession (PoP).

Today an SN/I cert is used to sign a private_key_jwt (x5c) client assertion, which yields a Bearer token (SNI + Bearer — unchanged). This PR lets a confidential-client app present that same certificate as the client TLS certificate in the mutual-TLS handshake to the token endpoint, so Entra ID (ESTS) returns a token that is cryptographically bound to the cert (token_type=mtls_pop, cnf/x5t#S256). The credential is identical; only the mechanism changes (assertion-signer → TLS client cert).

Also covers the 2-leg Federated Identity Credential (FIC) over mTLS PoP, where both legs are cert-bound and the final token is bound to the Leg-1 certificate thumbprint.

Public API

// Direct SN/I cert -> mTLS-bound PoP token
IAuthenticationResult result = cca.acquireToken(
        ClientCredentialParameters.builder(Set.of("https://vault.azure.net/.default"))
                .mtlsProofOfPossession()          // opt in
                .build())
        .get();

result.metadata().tokenType();            // TokenType.MTLS_POP
result.metadata().bindingCertificate();   // public material only: x5c chain + x5t#S256
// FIC Leg 2: assertion-authenticated app that must still bind to a cert
ConfidentialClientApplication.builder(clientId, ClientCredentialFactory.createFromClientAssertion(t1))
        .mtlsBindingCertificate(sniCert)
        .build();
  • ClientCredentialParameters.Builder.mtlsProofOfPossession()
  • ConfidentialClientApplication.Builder.mtlsBindingCertificate(IClientCertificate)
  • New public types: TokenType (BEARER / MTLS_POP), BindingCertificate (public material only), plus AuthenticationResultMetadata.tokenType() / bindingCertificate().

Internal changes

  • New: TokenType, BindingCertificate, MtlsClientCertificateHelper (mTLS socket factory, thumbprint, binding-cert resolution), MtlsEndpointHelper (login.*mtlsauth.* host rewrite; region optional → global mtlsauth.microsoft.com; tenanted-authority validation; sovereign-cloud fail-fast).
  • Request building (TokenRequestExecutor): direct cert path omits client_assertion; FIC Leg 2 sends client_assertion_type=...:jwt-pop; result populates tokenType/bindingCertificate.
  • Transport: mTLS socket factory threaded through OAuthHttpRequest / HttpHelper / IHttpHelper via an injected mTLS DefaultHttpClient (MSAL owns the mTLS transport).
  • Cache isolation: access-token cache keyed on {token_type + cert KeyId} so Bearer and PoP (and different certs) never alias.
  • Telemetry: isolated TokenType.telemetryValue() (BEARER=2, MTLS_POP=6) for cross-SDK parity; the locked v5 wire header is intentionally left unchanged.

Backward compatibility

The existing SNI + Bearer (assertion) and broker SHR-PoP flows are unchanged and remain byte-for-byte identical — validated by the full existing unit suite.

Testing

  • Unit: 3 new classes (MtlsEndpointHelperTest, MtlsClientCertificateHelperTest, MtlsProofOfPossessionTest) + a hermetic PKCS12 resource. Full suite: 410 tests, 0 failures, 0 errors, 0 skipped.
  • E2E: MtlsPopIT — direct SNI→PoP (global + regional), Bearer/PoP cache isolation, and 2-leg FIC (both legs bound). CI-only: requires the lab SN/I cert (non-CNG) + an ESTS allow-listed resource; ESTS gates mTLS PoP on the final resource audience.

Docs / samples

  • New sample confidential-client/ClientCredentialMtlsProofOfPossession.java
  • changelog.txt and .github/copilot-instructions.md updated
  • Javadoc on all new public API

Out of scope / follow-ups

  • Managed-identity first-leg mTLS variant (separate follow-up).
  • User-scoped (user_fic) FIC over mTLS PoP.
  • US Gov / China clouds (fail-fast today).

Risks / open questions

  • mTLS E2E only passes in CI (lab creds + allow-listed resource).
  • The telemetry wire header intentionally does not yet carry token type (would require an ESTS-coordinated schema bump).

Allow a confidential-client app configured with a Subject-Name/Issuer (SN/I) certificate to obtain an mTLS-bound PoP access token from Entra ID by presenting that same certificate as the client TLS certificate in the mutual-TLS handshake to the token endpoint, instead of signing a private_key_jwt (x5c) client assertion.

Opt in via ClientCredentialParameters.builder(...).mtlsProofOfPossession(). Adds TokenType, BindingCertificate, MtlsClientCertificateHelper, and MtlsEndpointHelper; threads the mTLS socket factory through the HTTP layer; derives the mtlsauth.* endpoint (region optional); builds the mtls_pop request (direct cert -> no assertion; FIC Leg 2 -> jwt-pop with mtlsBindingCertificate); parses token_type and surfaces the public binding certificate; and isolates the cache on {token_type + cert KeyId}. Also covers the 2-leg FIC over mTLS PoP where both legs are cert-bound.

The existing SNI+Bearer (assertion) and broker SHR-PoP flows are unchanged. Includes unit tests, an E2E integration test (MtlsPopIT), a sample, and docs/changelog updates.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@Robbie-Microsoft Robbie-Microsoft requested a review from a team as a code owner July 1, 2026 19:41
@Robbie-Microsoft Robbie-Microsoft marked this pull request as draft July 1, 2026 20:51
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.

1 participant