-
Notifications
You must be signed in to change notification settings - Fork 378
MSI V2 client side keys #5448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Robbie-Microsoft
merged 19 commits into
rginsburg/msiv2_feature_branch
from
gladjohn/msi_vs_keys
Sep 19, 2025
Merged
MSI V2 client side keys #5448
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
18b51d3
draft
gladjohn c813ed0
updated
gladjohn df59d37
MSI V2 client-side keys: add e2e + unit tests, fixes, hardware KSP up…
gladjohn 59a5998
Merge branch 'main' into gladjohn/msi_vs_keys
gladjohn 04cad71
Merge branch 'main' into gladjohn/msi_vs_keys
Robbie-Microsoft 9292e24
draft
gladjohn f7f4081
updated
gladjohn 287c2aa
MSI V2 client-side keys: add e2e + unit tests, fixes, hardware KSP up…
gladjohn b3bf762
fixed rebase error
Robbie-Microsoft c435bd7
Merge branch 'gladjohn/msi_vs_keys' of https://github.com/AzureAD/mic…
Robbie-Microsoft 6e7544c
Fixed build error
Robbie-Microsoft c9df653
Fixed rebase error
Robbie-Microsoft 34eb19d
Implemented feedback and added XML docs
Robbie-Microsoft 09564a4
All existing ImdsV2 unit tests run in both net48 and net8.0
Robbie-Microsoft 4c0bb4e
added a comment
Robbie-Microsoft 290470b
Cleaned up InMemoryManagedIdentityKeyProviderTests
Robbie-Microsoft 357098f
Implemented some feedback
Robbie-Microsoft fd3ba4c
Fixed logging
Robbie-Microsoft afa1d62
Merge branch 'rginsburg/msiv2_feature_branch' into gladjohn/msi_vs_keys
Robbie-Microsoft File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
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
33 changes: 33 additions & 0 deletions
33
src/client/Microsoft.Identity.Client/ManagedIdentity/IManagedIdentityKeyProvider.cs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.Identity.Client.Core; | ||
|
|
||
| namespace Microsoft.Identity.Client.ManagedIdentity | ||
| { | ||
| /// <summary> | ||
| /// Provides managed identity keys for authentication scenarios. | ||
| /// Implementations of this interface are responsible for obtaining or creating | ||
| /// the best available key type (KeyGuard, Hardware, or InMemory) for managed identity authentication. | ||
| /// </summary> | ||
| internal interface IManagedIdentityKeyProvider | ||
| { | ||
| /// <summary> | ||
| /// Gets an existing managed identity key or creates a new one if none exists. | ||
| /// The method returns the best available key type based on the provider's capabilities | ||
| /// and the current environment. | ||
| /// </summary> | ||
| /// <param name="logger">Logger adapter for recording operations and diagnostics.</param> | ||
| /// <param name="ct">Cancellation token to observe while waiting for the task to complete.</param> | ||
| /// <returns> | ||
| /// A task that represents the asynchronous operation. The task result contains | ||
| /// a <see cref="ManagedIdentityKeyInfo"/> object with the key, its type, and provider message. | ||
| /// </returns> | ||
| /// <exception cref="System.OperationCanceledException"> | ||
| /// Thrown when the operation is canceled via the cancellation token. | ||
| /// </exception> | ||
| Task<ManagedIdentityKeyInfo> GetOrCreateKeyAsync(ILoggerAdapter logger, CancellationToken ct); | ||
Robbie-Microsoft marked this conversation as resolved.
Show resolved
Hide resolved
Robbie-Microsoft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
159 changes: 159 additions & 0 deletions
159
...rosoft.Identity.Client/ManagedIdentity/KeyProviders/InMemoryManagedIdentityKeyProvider.cs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System; | ||
| using System.Security.Cryptography; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.Identity.Client.Core; | ||
| using Microsoft.Identity.Client.Internal; | ||
| using System.Runtime.InteropServices; | ||
|
|
||
| namespace Microsoft.Identity.Client.ManagedIdentity.KeyProviders | ||
| { | ||
| /// <summary> | ||
| /// In-memory RSA key provider for managed identity authentication. | ||
| /// </summary> | ||
| internal sealed class InMemoryManagedIdentityKeyProvider : IManagedIdentityKeyProvider | ||
| { | ||
| private static readonly SemaphoreSlim s_once = new (1, 1); | ||
| private volatile ManagedIdentityKeyInfo _cachedKey; | ||
|
|
||
| /// <summary> | ||
| /// Asynchronously retrieves or creates an RSA key pair for managed identity authentication. | ||
| /// Uses thread-safe caching to ensure only one key is created per provider instance. | ||
| /// </summary> | ||
| /// <param name="logger">Logger adapter for recording key creation operations and diagnostics.</param> | ||
| /// <param name="ct">Cancellation token to support cooperative cancellation of the key creation process.</param> | ||
| /// <returns> | ||
| /// A task that represents the asynchronous operation. The task result contains a | ||
| /// <see cref="ManagedIdentityKeyInfo"/> with the RSA key, key type, and provider message. | ||
| /// </returns> | ||
| public async Task<ManagedIdentityKeyInfo> GetOrCreateKeyAsync( | ||
| ILoggerAdapter logger, | ||
| CancellationToken ct) | ||
| { | ||
| // Return cached if available | ||
| if (_cachedKey is not null) | ||
| { | ||
| logger?.Info("[MI][InMemoryKeyProvider] Returning cached key."); | ||
| return _cachedKey; | ||
| } | ||
|
|
||
| // Ensure only one creation at a time | ||
| logger?.Info(() => "[MI][InMemoryKeyProvider] Waiting on creation semaphore."); | ||
| await s_once.WaitAsync(ct).ConfigureAwait(false); | ||
|
|
||
| try | ||
| { | ||
| if (_cachedKey is not null) | ||
| { | ||
| logger?.Info(() => "[MI][InMemoryKeyProvider] Cached key created while waiting; returning it."); | ||
| return _cachedKey; | ||
| } | ||
|
|
||
| if (ct.IsCancellationRequested) | ||
| { | ||
| logger?.Info(() => "[MI][InMemoryKeyProvider] Cancellation requested after entering critical section."); | ||
| ct.ThrowIfCancellationRequested(); | ||
| } | ||
|
|
||
| logger?.Info(() => "[MI][InMemoryKeyProvider] Starting RSA key creation."); | ||
| RSA rsa = null; | ||
| string message; | ||
|
|
||
| try | ||
| { | ||
| rsa = CreateRsaKeyPair(); | ||
| message = "In-memory RSA key created for Managed Identity authentication."; | ||
| logger?.Info("[MI][InMemoryKeyProvider] RSA key created (2048)."); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| message = $"Failed to create in-memory RSA key: {ex.GetType().Name} - {ex.Message}"; | ||
Robbie-Microsoft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| logger?.WarningPii( | ||
Robbie-Microsoft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| $"[MI][InMemoryKeyProvider] Exception during RSA creation: {ex}", | ||
| $"[MI][InMemoryKeyProvider] Exception during RSA creation: {ex.GetType().Name}"); | ||
| } | ||
|
|
||
| _cachedKey = new ManagedIdentityKeyInfo(rsa, ManagedIdentityKeyType.InMemory, message); | ||
|
|
||
| logger?.Info(() => | ||
| $"[MI][InMemoryKeyProvider] Caching key. Success={(rsa != null)}. HasMessage={!string.IsNullOrEmpty(message)}."); | ||
|
|
||
| return _cachedKey; | ||
| } | ||
| finally | ||
| { | ||
| s_once.Release(); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates a new RSA key pair with 2048-bit key size for cryptographic operations. | ||
| /// Uses platform-specific RSA implementations: RSACng on .NET Framework and RSA.Create() on other platforms. | ||
| /// </summary> | ||
| /// <returns> | ||
| /// An <see cref="RSA"/> instance configured with a 2048-bit key size. | ||
| /// On .NET Framework, returns <see cref="RSACng"/>; on other platforms, returns the default RSA implementation. | ||
| /// </returns> | ||
| /// <remarks> | ||
| /// This method is public instead of private because it is used in unit tests | ||
| /// </remarks> | ||
| public static RSA CreateRsaKeyPair() | ||
| { | ||
| #if NET462 || NET472 || NET8_0 | ||
Robbie-Microsoft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Windows-only TFMs (Framework or -windows TFMs): compile CNG path | ||
| return CreateWindowsPersistedRsa(); | ||
|
|
||
| #else | ||
| // netstandard2.0 can run anywhere; pick at runtime | ||
| if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | ||
| { | ||
| return CreateWindowsPersistedRsa(); // requires CNG package in csproj | ||
| } | ||
| return CreatePortableRsa(); | ||
|
|
||
| #endif | ||
| } | ||
|
|
||
| private static RSA CreatePortableRsa() | ||
| { | ||
| var rsa = RSA.Create(); | ||
| if (rsa.KeySize < Constants.RsaKeySize) | ||
| rsa.KeySize = Constants.RsaKeySize; | ||
| return rsa; | ||
| } | ||
|
|
||
| private static RSA CreateWindowsPersistedRsa() | ||
| { | ||
| // Persisted CNG key (non-ephemeral) so Schannel can use it for TLS client auth | ||
| var creation = new CngKeyCreationParameters | ||
| { | ||
| ExportPolicy = CngExportPolicies.AllowExport, | ||
| KeyCreationOptions = CngKeyCreationOptions.MachineKey, // try machine store first | ||
| Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider | ||
| }; | ||
|
|
||
| // Persist key length with the key | ||
| creation.Parameters.Add( | ||
| new CngProperty("Length", BitConverter.GetBytes(Constants.RsaKeySize), CngPropertyOptions.Persist)); | ||
|
|
||
| // Non-null name => persisted; null would be ephemeral (bad for Schannel) | ||
| string keyName = "MSAL-MTLS-" + Guid.NewGuid().ToString("N"); | ||
|
|
||
| try | ||
| { | ||
| var key = CngKey.Create(CngAlgorithm.Rsa, keyName, creation); | ||
| return new RSACng(key); | ||
| } | ||
| catch (CryptographicException) | ||
| { | ||
| // Some environments disallow MachineKey. Fall back to user profile. | ||
| creation.KeyCreationOptions = CngKeyCreationOptions.None; | ||
| var key = CngKey.Create(CngAlgorithm.Rsa, keyName, creation); | ||
| return new RSACng(key); | ||
| } | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.