POC 2: MSI v2 mTLS PoP — WindowsCertificate + SchannelSession#931
Open
gladjohn wants to merge 3 commits into
Open
POC 2: MSI v2 mTLS PoP — WindowsCertificate + SchannelSession#931gladjohn wants to merge 3 commits into
gladjohn wants to merge 3 commits into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a Windows-only end-to-end MSI v2 flow for Managed Identity mTLS Proof-of-Possession (PoP), including a handle-backed certificate wrapper (WindowsCertificate), the MSI v2 implementation (msal.msi_v2), and companion packages for KeyGuard attestation and WinHTTP/SChannel downstream mTLS calls.
Changes:
- Add MSI v2 (IMDSv2) token acquisition over WinHTTP/SChannel with KeyGuard key management, CSR issuance, optional MAA attestation, and in-memory certificate caching.
- Add
WindowsCertificate(platform key-handle backed) and expose newManagedIdentityClient.acquire_token_for_client()opt-in flags for MSI v2 gating. - Add new tests/docs/samples and two new sub-packages:
msal-key-attestationandmsal-schannel-transport.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
tests/test_msi_v2.py |
Unit coverage for cnf binding, cache behavior, and MI client gating. |
tests/test_e2e_mtls_pop.py |
Windows-only E2E test wiring MSAL MSI v2 token + downstream Schannel mTLS call. |
sample/msi_v2_sample.py |
Sample script demonstrating MSI v2 token acquisition and (non-mTLS) request header usage. |
sample/MSI_V2_GUIDE.md |
Setup/usage guide for MSI v2 + attestation. |
sample/devapp_msi_v2_mtls/app.py |
Dev app showing public-API-only flow + downstream SchannelSession call. |
msal/windows_certificate.py |
New handle-backed certificate wrapper with store lookup and CERT_CONTEXT creation. |
msal/msi_v2.py |
New MSI v2 implementation: KeyGuard key, CSR, IMDS calls, cert binding, WinHTTP mTLS token acquisition, cert cache. |
msal/managed_identity.py |
Adds MsiV2Error and new opt-in flags to route MSI v2 flow from public API. |
msal/__init__.py |
Exposes WindowsCertificate at package root (public API surface). |
msal-schannel-transport/pyproject.toml |
Defines new transport package metadata/build config. |
msal-schannel-transport/msal_schannel_transport/session.py |
Implements WinHTTP/SChannel session for downstream mTLS calls. |
msal-schannel-transport/msal_schannel_transport/__init__.py |
Transport package exports + usage docstring. |
msal-key-attestation/pyproject.toml |
Defines new attestation package metadata/build config and dependency on msal. |
msal-key-attestation/msal_key_attestation/attestation.py |
Implements AttestationClientLib.dll bindings + JWT caching and provider factory. |
msal-key-attestation/MANIFEST.in |
Packaging include/exclude rules for the attestation package. |
msal-key-attestation/LICENSE |
License file for the attestation package. |
docs/mTLS-PoP-Architecture-Decision.md |
Architecture decision record explaining WinHTTP/SChannel approach and separation of packages. |
docs/MSI_V2_API.md |
API reference documentation for MSI v2 public API usage and behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
0c3db4d to
00539ca
Compare
00539ca to
3df2f04
Compare
…roof This POC implements end-to-end mTLS Proof-of-Possession for Managed Identity v2 on Windows Azure VMs with Credential Guard / KeyGuard. - msal/windows_certificate.py: Python equivalent of .NET X509Certificate2 - Wraps NCRYPT_KEY_HANDLE (non-exportable private key) - Thread-safe CERT_CONTEXT creation for mTLS transports - Lifecycle management (close/context-manager) - msal/msi_v2.py: obtain_token() now returns 'binding_certificate' in result - msal/managed_identity.py: Public API wired (acquire_token_for_client) - msal/__init__.py: WindowsCertificate exported - msal-schannel-transport/: WinHTTP/SChannel HTTP client for downstream mTLS - Bypasses OpenSSL (which can't use non-exportable keys) - Uses NCRYPT_KEY_HANDLE via SChannel for TLS CertificateVerify - Self-contained Win32 bindings (no MSAL internal imports) - msal-key-attestation/: MAA attestation wrapper with in-memory JWT caching - sample/devapp_msi_v2_mtls/app.py: Uses ONLY public MSAL API - docs/mTLS-PoP-Architecture-Decision.md: Why WinHTTP (OpenSSL CNG gap) - docs/MSI_V2_API.md: API reference - tests/: Unit tests (52 pass) + E2E test framework - Token: mtls_pop, 86399s ✓ - Binding: cnf.x5t#S256 MATCH ✓ - Downstream: tokenbinding.vault.azure.net HTTP 200, secret returned ✓ - MSAL acquires token + returns cert handle (never makes downstream calls) - App developer uses SchannelSession for downstream mTLS - Same pattern as .NET: MSAL → HttpClient becomes MSAL → SchannelSession - AttestationClientLib.dll (5.3MB) not included in this PR (sourced from NuGet: Microsoft.Azure.Security.KeyGuardAttestation v1.1.5) - Production packaging TBD (platform wheel vs bundled) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- mtls_proof_of_possession=True now ALWAYS routes to MSI v2 (no silent fallback to Bearer/v1 — matches .NET's MtlsPopTokenNotSupportedinImdsV1 hard-fail behavior) - with_attestation_support controls binding strength tier: True = KeyGuard (attested) False = Software (non-attested mTLS, still v2) - Fix architecture doc: acknowledge third-party OpenSSL 3 CNG provider exists, clarify why it's insufficient for Python - Add x-ms-tokenboundauth header to E2E Key Vault test - Update unit test to verify non-attested v2 path Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add MsiV2Error to msal/__init__.py exports
- Add MsiV2Error class definition in managed_identity.py
- Add mtls_proof_of_possession and with_attestation_support params
- Fix base64url padding: (4 - len % 4) -> ((4 - len % 4) % 4)
- Fix cert cache threshold: 24h -> 1h (matches IMDS ~8h certs)
- Fix authorization_header references -> token_type + access_token
- Fix --run-e2e docs -> RUN_E2E_TESTS env var
- Fix Python version: >=3.8 -> >=3.9 (matches msal setup.cfg)
- Fix license format: 'MIT' -> {text = 'MIT'} (PEP 621)
- Add msal-key-attestation/__init__.py (fixes import + __version__)
- Add README.md to both sub-packages (fixes pyproject.toml refs)
- Add binding_certificate to MSI_V2_API.md return value docs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
3df2f04 to
f1ab3e0
Compare
gladjohn
added a commit
that referenced
this pull request
Jun 21, 2026
- Add prerequisite note: APIs come from PR #931, not yet on dev - Note this is standalone docs (not Sphinx-rendered) - Fix rstrip('/.default') -> removesuffix('/.default') - Fix AccessToken usage: store token_type on credential (not AccessToken) - Fix auth policy: read token_type from credential, not token object Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
End-to-end mTLS Proof-of-Possession for Managed Identity v2 on Windows Azure VMs with Credential Guard / KeyGuard.
E2E proven on MSIV2 VM (June 2026): Token acquired → cert binding verified → downstream mTLS call to
tokenbinding.vault.azure.netreturned HTTP 200 with secret value.What's included
Core (
msalpackage)msal/windows_certificate.py— Python equivalent of .NETX509Certificate2. WrapsNCRYPT_KEY_HANDLE, thread-safeCERT_CONTEXTcreation, lifecycle management.msal/msi_v2.py— Full MSI v2 flow: KeyGuard key mgmt, CSR, MAA attestation, issuecredential, WinHTTP/SChannel mTLS token acquisition. Returnsbinding_certificatein result.msal/managed_identity.py— Public API:acquire_token_for_client(mtls_proof_of_possession=True, with_attestation_support=True)Separate packages
msal-schannel-transport/— WinHTTP/SChannel HTTP client for downstream mTLS (bypasses OpenSSL which cannot use non-exportable keys). Self-contained Win32 bindings.msal-key-attestation/— Python wrapper for MAA attestation. Native DLL sourced from NuGet:Microsoft.Azure.Security.KeyGuardAttestation v1.1.5(not bundled in this PR).Dev app + docs
sample/devapp_msi_v2_mtls/app.py— Uses ONLY public MSAL APIdocs/mTLS-PoP-Architecture-Decision.md— Why WinHTTP/SChannel (OpenSSL CNG provider gap research)App developer usage
E2E Results (MSIV2 VM)
mtls_pop, 86399sNotes
AttestationClientLib.dll(5.3MB) not included — sourced from NuGet at build time