Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions docs/msi_v2/linux-attested-story.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Linux KMPP Managed Identity Key Provider

## Audience and purpose

This document is for MSAL maintainers and SDK engineers working on MSI V2
Managed Identity support. It supplements the
[MSI V2 `/issuecredential` design](msi_with_credential_design.md) by documenting
the Linux-specific key-provider architecture.

It answers the following questions:

- What Linux functionality exists today?
- How does Linux key handling differ from the Windows implementation?
- Which security properties does KMPP provide?
- What remains before Linux has an end-to-end MSI V2 mTLS-PoP flow?
- Which behaviors must not yet be treated as production contracts?

> [!IMPORTANT]
> Linux support is currently a proof of concept. The implementation creates a
> protected key and builds the certificate signing request (CSR). Linux key
> attestation and the complete MSI V2 mTLS-PoP token acquisition flow remain
> future work.

## Overview

The Linux Managed Identity key provider uses KMPP (KeyIso with OP-TEE and
TrustZone) to create a non-exportable RSA-PSS key. MSAL accesses KMPP through
`libkmpp.so`; private-key operations remain inside the trusted execution
environment.

KMPP is the Linux key-provider technology. It should not be described as Linux
Credential Guard or Linux KeyGuard.

## Current proof-of-concept scope

The current implementation covers:

- Native interoperability with `libkmpp.so`.
- Creating and opening an enclave-backed KMPP key.
- Reading the public key from the KMPP-generated self-signed certificate.
- Performing RSA-PSS/SHA-256 signing inside the enclave.
- Rejecting private-key export.
- Building the MSI V2 CSR with the KMPP-backed key.

The following work is not yet complete:

- Attesting the KMPP key.
- Sending the Linux attestation evidence to Microsoft Azure Attestation.
- Completing the `/issuecredential` and regional ESTS token-acquisition flow.
- Defining production fallback, recovery, and key-lifecycle behavior.

## Key-provider architecture

```mermaid
sequenceDiagram
participant MSAL
participant KMPP as libkmpp.so
participant TA as KMPP Trusted Application

MSAL->>KMPP: Create or open key
KMPP->>TA: Generate non-exportable RSA-PSS key
TA-->>KMPP: Key identifier and self-signed certificate
KMPP-->>MSAL: Key handle and public certificate

MSAL->>KMPP: Sign CSR data
KMPP->>TA: RSA-PSS/SHA-256 sign
TA-->>KMPP: Signature
KMPP-->>MSAL: Signature

MSAL->>MSAL: Build CSR using the public key and signature
```

## Windows and Linux comparison

| Aspect | Windows | Linux |
|---|---|---|
| Key provider | KeyGuard KSP | KMPP/KeyIso through `libkmpp.so` |
| Protection boundary | Credential Guard and the Windows key-storage provider | OP-TEE/TrustZone trusted execution environment |
| Key properties | Non-exportable, hardware-backed RSA key | Non-exportable RSA-PSS key |
| Native integration | Windows CNG/KSP APIs | P/Invoke into `libkmpp.so` |
| Public-key source | Windows key provider | KMPP-generated self-signed certificate |
| Private-key operations | Performed by the Windows key provider | Performed inside the KMPP trusted application |
| CSR ownership | MSAL builds and signs the CSR using the protected key | MSAL builds and signs the CSR using the protected key |
| Key attestation | Implemented through Microsoft Azure Attestation | Not yet implemented |
| End-to-end token flow | Implemented | Not yet implemented |
| E2E validation | Existing Windows flow | No Linux E2E flow yet |
| Current status | Supported implementation | POC: key creation and CSR generation only |

The existence of the KMPP key provider does not indicate that Linux MSI V2
mTLS-PoP is ready for production use. Linux support should be advertised only
after attestation and the remaining token-acquisition stages are implemented
and validated.
72 changes: 32 additions & 40 deletions docs/msi_v2/msi_with_credential_design.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The primary objective is to enable seamless token acquisition in MSI V2 for VM/V

- Define the **MSI V2 token acquisition process**.
- Describe how MSAL interacts with the `/issuecredential` and the ESTS regional token endpoint.
- Ensure compatibility with **Windows and Linux** VMs and VMSS.
- Support the Windows flow and define the planned Linux KMPP integration.

## Token Acquisition Process

Expand Down Expand Up @@ -51,28 +51,26 @@ sequenceDiagram
MSAL ->> IMDS: GET /metadata/identity/getPlatformMetadata
IMDS -->> MSAL: client_id, tenant_id, cuid, maa_endpoint

alt Attestable CU
MSAL ->> MAA: POST /attest/keyguard (attestation info)
MAA -->> MSAL: attestation_token
else Unattestable CU
Note over MSAL: Skip attestation
end

MSAL ->> IMDS: POST /metadata/identity/issuecredential (CSR + attestation_token?)
MSAL ->> MAA: POST key attestation info
MAA -->> MSAL: attestation_token

MSAL ->> IMDS: POST /metadata/identity/issuecredential (CSR + attestation_token)
IMDS -->> MSAL: client_credential (x509), regional_token_url

MSAL ->> ESTS: POST /oauth2/v2.0/token (mTLS)
ESTS -->> MSAL: access_token (bearer|mtls_pop **for attested CUs**, bearer **only** for unattested CUs)
ESTS -->> MSAL: access_token (mtls_pop)
MSAL -->> App : AuthenticationResult (access_token + mtls_certificate)
```

### The new CSR + Attested flow

- MSAL needs to create a CSR (Certificate Signing Request) with a sourced key.
- Azure RP will add a new `PlatformMetadata` endpoint which will provide the info needed to create the CSR.
- MSAL will source a key from CredentialGuard (where available) or in-memory to form the CSR
- For keys from CredentialGuard, MSAL will perform a key attestation with MAA (Microsoft Attestation Service) - POP only
- MSAL will then send the CSR and the attestation token (where applicable) to the MIRP
- On Windows, MSAL sources a non-exportable key from the KeyGuard KSP.
- The planned Linux implementation sources a non-exportable RSA-PSS key from KMPP through `libkmpp.so`.
- MSAL builds the CSR using the platform-backed key.
- MSAL obtains key attestation evidence from MAA and sends the CSR and attestation token to the MIRP.
- Linux KMPP key creation and CSR generation are currently a proof of concept; Linux attestation and the complete token flow are not yet implemented.

### Certificate Retrieval from `/issuecredential` Endpoint

Expand All @@ -86,7 +84,7 @@ sequenceDiagram
- The client (MSAL) presents the **certificate** to **ESTS** over **MTLS**.
- ESTS validates the certificate and issues an **access token**.
- The access token is then used to authenticate with Azure services.
- The access token can be bearer / pop depending upon the key that was sourced.
- ESTS issues an mTLS PoP access token bound to the certificate.
- MSAL will then return the access token and the certificate obtained from MIRP back to the caller.
- The caller will need to use the access token to call the target resource over mTLS using the cert.

Expand Down Expand Up @@ -151,21 +149,21 @@ Response supplies the UAID/client_id, tenant_id, CUID, and (for attestable VMs)

MSAL will load a pre-exisitng key, if none available MSAL will try creating one in KSP (Key Storage Provider)

| OS | Key Store | Persistence |
|------------------------|------------------|-------------------|
| Windows (attested) | KeyGuard KSP | Durable |
| Windows (unattested) | Software KSP | Durable |
| Linux | In-memory → KMPP (future) | Process-lifetime |
| OS | Key provider | Key properties | Status |
|---|---|---|---|
| Windows | KeyGuard KSP | Non-exportable, hardware-backed RSA key | Implemented |
| Linux | KMPP/KeyIso through `libkmpp.so` | Non-exportable RSA-PSS key held in an OP-TEE/TrustZone enclave | POC: key creation and CSR generation only |

#### CSR requirements

- **Subject:** `CN={client_id}, DC={tenant_id}`
- **Attribute OID 1.2.840.113549.1.9.7:** `PrintableString = {CUID}`
- **Signed with:** new key `RSA 2048`

#### (Optionally) Attest Key
#### Attest Key

If they Key is from KeyGuard, MSAL will then get an attestation token for this key.
MSAL obtains an attestation token for the platform-backed key. Windows KeyGuard
attestation is implemented. Linux KMPP attestation remains future work.

#### Request Certificate

Expand All @@ -175,7 +173,7 @@ Content-Type: application/json

{
"csr": "<Base64 CSR>",
"attestation_token": "<jwt>" // Optional on unattested compute units
"attestation_token": "<jwt>"
}
```

Expand All @@ -189,7 +187,7 @@ MSAL will then call ESTS-R to get an access token
grant_type=client_credentials
client_id=<UAID>
scope=https://management.azure.com/.default
token_type=mtls_pop // **Only valid for attested CUs**; omit (default bearer) for unattested
token_type=mtls_pop
```
Response identical to AAD v2 but may include token_type":"mtls_pop".

Expand All @@ -211,7 +209,6 @@ Response identical to AAD v2 but may include token_type":"mtls_pop".

.PARAMETERS
-Scope Resource scope (default: ARM).
-Unattested Skip attestation token; forces bearer token.
-Mock Use mocked responses (auto‑fallback if IMDS is not reachable).

.NOTES
Expand All @@ -220,7 +217,6 @@ Response identical to AAD v2 but may include token_type":"mtls_pop".

param(
[string]$Scope = "https://management.azure.com/.default",
[switch]$Unattested,
[switch]$Mock
)

Expand Down Expand Up @@ -255,7 +251,7 @@ if ($Mock) {
client_id = [guid]::NewGuid().Guid
tenant_id = [guid]::NewGuid().Guid
CUID = [guid]::NewGuid().Guid
attestation_endpoint = if ($Unattested) { $null } else { "https://mock.maa.contoso.com" }
attestation_endpoint = "https://mock.maa.contoso.com"
}
Info "Generated mock platform metadata."
}
Expand Down Expand Up @@ -299,15 +295,13 @@ Info "CSR ready (len=$($csrBytes.Length))"
# 4. Attestation token (mock or real)
# ------------------------------------------------------------
$attToken = $null
if (-not $Unattested) {
if ($Mock) {
$attToken = "eyMock.Token"; Info "Mock attestation token generated."
} elseif ($maaEp) {
Info "Requesting attestation token from $maaEp …"
$maaResp = Invoke-WebRequest -Uri "$maaEp/attest/keyguard?api-version=2023-04-01-preview" -Method POST -Body (@{ AttestationInfo = '<bin>' } | ConvertTo-Json -Compress) -ContentType 'application/json'
Throw-IfError $maaResp 'attest/keyguard'
$attToken = ($maaResp.Content | ConvertFrom-Json).token
}
if ($Mock) {
$attToken = "eyMock.Token"; Info "Mock attestation token generated."
} elseif ($maaEp) {
Info "Requesting attestation token from $maaEp …"
$maaResp = Invoke-WebRequest -Uri "$maaEp/attest/keyguard?api-version=2023-04-01-preview" -Method POST -Body (@{ AttestationInfo = '<bin>' } | ConvertTo-Json -Compress) -ContentType 'application/json'
Throw-IfError $maaResp 'attest/keyguard'
$attToken = ($maaResp.Content | ConvertFrom-Json).token
}

# ------------------------------------------------------------
Expand All @@ -320,7 +314,7 @@ if ($Mock) {
} else {
$issueUri = "http://169.254.169.254/metadata/identity/issuecredential?api-version=2025-05-01&cid=$cuid&uaid=$clientId"
$issueHdr = @{ Metadata = 'true'; 'X-ms-Client-Request-id' = [guid]::NewGuid() }
$bodyObj = @{ csr = $csrBase64 }; if ($attToken) { $bodyObj.attestation_token = $attToken }
$bodyObj = @{ csr = $csrBase64; attestation_token = $attToken }
$issueResp = Invoke-WebRequest -Uri $issueUri -Method POST -Headers $issueHdr -Body ($bodyObj | ConvertTo-Json -Compress) -ContentType 'application/json'
Throw-IfError $issueResp 'issuecredential'
$cred = $issueResp.Content | ConvertFrom-Json
Expand All @@ -343,15 +337,13 @@ if ($Mock) {
Info "Mock access token generated."
} else {
$regional = "$regionalTokenUrl/$tenantId/oauth2/v2.0/token"
$bodyStr = "grant_type=client_credentials&scope=$([Uri]::EscapeDataString($Scope))&client_id=$clientId"
if (-not $Unattested) { $bodyStr += "&token_type=mtls_pop" }
$bodyStr = "grant_type=client_credentials&scope=$([Uri]::EscapeDataString($Scope))&client_id=$clientId&token_type=mtls_pop"
$tokResp = Invoke-WebRequest -Uri $regional -Method POST -Body $bodyStr -Headers @{ 'Accept'='application/json'; 'Content-Type'='application/x-www-form-urlencoded' } -Certificate $miCert
Throw-IfError $tokResp 'token'
$tokenJson = $tokResp.Content | ConvertFrom-Json
}

$kind = if ($Unattested) { 'Bearer' } else { 'mTLS-PoP' }
Info "$kind token acquired – expires_in = $($tokenJson.expires_in)s"
Info "mTLS-PoP token acquired – expires_in = $($tokenJson.expires_in)s"
```

## Summary of New APIs on Managed Identity Builder
Expand Down
Loading