Skip to content

[POC] Linux Managed Identity key provider (KMPP/OP-TEE non-exportable RSA-PSS) - #6150

Draft
Gladwin Johnson (gladjohn) wants to merge 1 commit into
mainfrom
gladjohn/linux-kmpp-key-provider
Draft

[POC] Linux Managed Identity key provider (KMPP/OP-TEE non-exportable RSA-PSS)#6150
Gladwin Johnson (gladjohn) wants to merge 1 commit into
mainfrom
gladjohn/linux-kmpp-key-provider

Conversation

@gladjohn

Copy link
Copy Markdown
Contributor

Summary (POC / draft)

Adds a Linux Managed Identity key provider that sources a non-exportable RSA-PSS key from the KMPP (KeyIso / OP-TEE / TrustZone) enclave via libkmpp.so — the Linux equivalent of a Windows CredentialGuard / KeyGuard key.

This is a proof of concept for the MSI v2 mTLS-PoP story on Linux. It gets the flow to where MSAL itself creates the non-exportable key and builds the CSR; attestation is the remaining gap (below).

Changes

  • ManagedIdentity/KeyProviders/KmppNativeMethods.cs — P/Invoke into libkmpp.so (keygen, open, in-enclave sign, public cert).
  • ManagedIdentity/KeyProviders/KmppRsa.csRSA whose private key stays in the enclave. Public key read from the enclave self-signed cert; RSA-PSS/SHA-256 signing runs in-enclave; private-key export throws (non-exportable).
  • ManagedIdentity/KeyProviders/LinuxManagedIdentityKeyProvider.cs — creates the KMPP key (KeyIso_create_self_sign_pfx_to_key_id), reports it as KeyGuard, falls back to in-memory RSA when the enclave is unavailable.
  • ManagedIdentityKeyProviderFactory.cs — selects the Linux provider on net8.0+.
  • ManagedIdentityPopExtensions.cs — allows MI mTLS PoP on Linux (net8.0+), previously Windows-only.
  • Microsoft.Identity.Client.csproj — excludes the net8-only KMPP files from net462/net472/netstandard2.0.

Validation

Built (all TFMs) and run E2E on an Azure Linux 3 (LVBS "shielded secrets") Trusted Launch VM:

[MI][KeyProviderFactory] Linux detected - using KMPP/OP-TEE managed identity key provider.
GET .../metadata/identity/getplatformmetadata?cred-api-version=2.0
[MI][LinuxKeyProvider] KMPP enclave key created (2048-bit, non-exportable).
MsalClientException: credential_guard_requires_cng -
  [ImdsV2] Credential Guard attestation currently supports only RSA CNG keys on Windows.

A standalone probe confirmed the native path: keygen -> public key -> in-enclave RSA-PSS sign -> verify = VALID; private key non-exportable (only the public cert is extractable).

Known gap / not in scope

  • Attestation on Linux — fails at ImdsV2ManagedIdentitySource.GetAttestationJwtAsync (MSAL only attests Windows CNG/CredentialGuard keys). A Linux attestation path (e.g. KMPP KeyIso_create_key_claim -> MAA) is the follow-up.
  • No unit tests yet (POC). KMPP requires OP-TEE initialized on the host.

Draft for discussion — feedback welcome on the provider seam, the net8.0+ gating, and the attestation follow-up.

Copilot AI review requested due to automatic review settings July 30, 2026 02:54

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

Adds a proof-of-concept Linux Managed Identity mTLS-PoP key provider backed by KMPP/OP-TEE (non-exportable RSA-PSS), enables MI mTLS-PoP on Linux for net8.0+, and changes KeyGuard/Credential Guard attestation to fail closed (surface MsalServiceException("attestation_failed")) while bridging native MAA logs into MSAL logging.

Changes:

  • Introduces Linux KMPP-backed key provider (KmppNativeMethods, KmppRsa, LinuxManagedIdentityKeyProvider) and selects it on Linux for net8.0+ with TFM gating in the main csproj.
  • Changes IMDSv2 attestation behavior to throw attestation_failed when a configured provider yields no token / errors (and preserves cancellation).
  • Adds/updates unit tests to validate the new attestation failure semantics and native-log-to-MSAL logger bridging.

Reviewed changes

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

Show a summary per file
File Description
tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs Expands test coverage for attestation failure/cancellation semantics and logger bridging.
src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj Excludes net8+ KMPP provider files from older TFMs.
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs Fails closed on missing/empty attestation token and surfaces attestation_failed.
src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityPopExtensions.cs Enables MI mTLS PoP on Linux for net8.0+.
src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityKeyProviderFactory.cs Selects Linux KMPP provider on net8.0+ Linux.
src/client/Microsoft.Identity.Client/ManagedIdentity/KeyProviders/LinuxManagedIdentityKeyProvider.cs New Linux provider that creates a KMPP enclave key and falls back to in-memory RSA.
src/client/Microsoft.Identity.Client/ManagedIdentity/KeyProviders/KmppRsa.cs New RSA implementation that signs in-enclave and prevents private key export.
src/client/Microsoft.Identity.Client/ManagedIdentity/KeyProviders/KmppNativeMethods.cs New P/Invoke surface to libkmpp.so functions used by the Linux provider.
src/client/Microsoft.Identity.Client.KeyAttestation/PopKeyAttestor.cs Passes MSAL logger into native attestation client for log bridging.
src/client/Microsoft.Identity.Client.KeyAttestation/ManagedIdentityAttestationExtensions.cs Makes WithAttestationSupport() throw attestation_failed when attestation does not produce a usable JWT.
src/client/Microsoft.Identity.Client.KeyAttestation/Attestation/AttestationLogger.cs Adds mapping/formatting + bridge of native attestation logs into MSAL ILoggerAdapter.
src/client/Microsoft.Identity.Client.KeyAttestation/Attestation/AttestationClient.cs Keeps a strong ref to native log callback and translates native error codes to readable reasons.
CHANGELOG.md Documents the attestation behavior change and logging bridge addition.

Comment on lines 4 to +7
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Identity.Client.Core;
Copilot AI review requested due to automatic review settings July 30, 2026 04:20
@gladjohn
Gladwin Johnson (gladjohn) force-pushed the gladjohn/linux-kmpp-key-provider branch from 9a9e886 to 29a15c1 Compare July 30, 2026 04:20

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 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityPopExtensions.cs:59

  • On net8.0+ this enables mTLS PoP on Linux, but the IMDSv2 attestation path currently hard-requires a Windows RSACng key handle (see ImdsV2ManagedIdentitySource.GetAttestationJwtAsync). Since KeyGuard scenarios are documented to require .WithAttestationSupport(), Linux callers will end up with an unavoidable runtime failure instead of a clear "not supported" error. Until a Linux attestation/token-provider contract exists, keep Linux excluded here (or gate behind an explicit experimental switch).
            bool isSupportedOs = DesktopOsHelper.IsWindows();
#if NET8_0_OR_GREATER
            isSupportedOs = isSupportedOs || DesktopOsHelper.IsLinux();
#endif
            if (!isSupportedOs)

src/client/Microsoft.Identity.Client/ManagedIdentity/KeyProviders/KmppRsa.cs:33

  • _publicLoaded is used as a publication flag for _publicParameters, but it's not volatile / not accessed via Volatile.Read/Write. Under concurrent use of the cached RSA instance, another thread can observe _publicLoaded == true while still seeing a stale/default _publicParameters. Marking the flag volatile provides the required memory barriers for safe publication.
        private readonly string _keyId;
        private RSAParameters _publicParameters;
        private bool _publicLoaded;

src/client/Microsoft.Identity.Client/ManagedIdentity/KeyProviders/KmppRsa.cs:165

  • KeyIso_CLIENT_pkey_rsa_sign returns the signature length. Even if RSA-2048 is expected to be 256 bytes, returning the entire buffer unconditionally can return trailing zeros if the native method ever returns a shorter length (or if key size changes). Return exactly the number of bytes reported by rc.
                    throw new CryptographicException($"KMPP enclave RSA-PSS sign failed (rc={rc}).");
                }

                // RSA-2048 PSS signatures are always 256 bytes; the enclave fills the full buffer.
                return signature;

…able RSA-PSS)

Sources a non-exportable RSA-PSS key for Managed Identity mTLS PoP from the
Linux KMPP (KeyIso / OP-TEE / TrustZone) enclave via libkmpp.so - the Linux
equivalent of a Windows CredentialGuard / KeyGuard key.

- KmppNativeMethods: P/Invoke into libkmpp.so (keygen, open, in-enclave sign, public cert).
- KmppRsa: enclave-backed RSA. Public key is read from the enclave certificate;
  RSA-PSS/SHA-256 signing runs inside the enclave; private key is non-exportable.
- LinuxManagedIdentityKeyProvider: creates the KMPP key and reports it as KeyGuard;
  falls back to in-memory RSA when the enclave is unavailable.
- ManagedIdentityKeyProviderFactory: select the Linux provider on net8.0+.
- ManagedIdentityPopExtensions: allow MI mTLS PoP on Linux (net8.0+).
- Microsoft.Identity.Client.csproj: exclude the net8-only KMPP files from older TFMs.

POC / draft. Validated end-to-end on an Azure Linux 3 (LVBS) VM: MSAL creates the
non-exportable KMPP key and builds the CSR; attestation still fails on Linux because
MSAL only attests Windows CNG/CredentialGuard keys today.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ef5bb3cc-f7b6-4df1-8da7-8b1eb94e3527
Copilot AI review requested due to automatic review settings July 30, 2026 12:47
@gladjohn
Gladwin Johnson (gladjohn) force-pushed the gladjohn/linux-kmpp-key-provider branch from 29a15c1 to 0818532 Compare July 30, 2026 12:47

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 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityPopExtensions.cs:60

  • On NET8+, Linux is treated as a supported OS for managed-identity mTLS PoP, but the IMDSv2 attestation path currently only supports Windows RSA CNG keys (see ImdsV2ManagedIdentitySource.GetAttestationJwtAsync). As a result, calling .WithAttestationSupport() on Linux will deterministically fail at runtime with credential_guard_requires_cng. Consider gating Linux/KMPP support behind an explicit opt-in until a Linux attestation implementation exists, so the public API doesn’t advertise a broken path by default.
            bool isSupportedOs = DesktopOsHelper.IsWindows();
#if NET8_0_OR_GREATER
            isSupportedOs = isSupportedOs || DesktopOsHelper.IsLinux();
#endif
            if (!isSupportedOs)

src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityKeyProviderFactory.cs:118

  • The Linux provider is selected unconditionally on NET8_0_OR_GREATER. Given that IMDSv2 attestation currently enforces RSACng (Windows-only), selecting this provider by default can cause Linux hosts to be advertised as KeyGuard-capable and later fail in the attestation path. Consider making Linux/KMPP selection explicitly opt-in until Linux attestation is implemented (e.g., via an environment variable) to avoid changing default runtime behavior on Linux net8.
#if NET8_0_OR_GREATER
            if (DesktopOsHelper.IsLinux())
            {
                // Linux: source a non-exportable RSA-PSS key from the KMPP/OP-TEE enclave
                // (KeyGuard-equivalent), falling back to in-memory inside the provider when the

src/client/Microsoft.Identity.Client/ManagedIdentity/KeyProviders/LinuxManagedIdentityKeyProvider.cs:125

  • If KeyIso_create_self_sign_pfx_to_key_id returns rc==0 but still allocates a non-null keyIdPtr, the current early-return path leaks that native allocation because KeyIso_clear_free_string is only called on the success path. Free the pointer before returning false whenever keyIdPtr is non-zero.
            if (rc == 0 || keyIdPtr == IntPtr.Zero)
            {
                logger?.Info(() => $"[MI][LinuxKeyProvider] KMPP keygen (KeyIso_create_self_sign_pfx_to_key_id) returned rc={rc}.");
                return false;
            }

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