Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client.ApiConfig.Parameters;
Expand Down Expand Up @@ -197,6 +198,16 @@ private async Task<AuthenticationResult> SendTokenRequestForManagedIdentityAsync
ManagedIdentityClient managedIdentityClient =
new ManagedIdentityClient(AuthenticationRequestParameters.RequestContext);

var keyProvider = ServiceBundle.PlatformProxy.ManagedIdentityKeyProvider;

var mi = await keyProvider.GetOrCreateKeyAsync(cancellationToken).ConfigureAwait(false);
var rsa = mi.KeyInfo;

byte[] data = System.Text.Encoding.UTF8.GetBytes("ping");
byte[] sig = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
bool ok = rsa.VerifyData(data, sig, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
// ok should be true

ManagedIdentityResponse managedIdentityResponse =
await managedIdentityClient
.SendTokenRequestForManagedIdentityAsync(_managedIdentityParameters, cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// ManagedIdentity/Providers/InMemoryManagedIdentityKeyProvider.cs
using System;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Identity.Client.ManagedIdentity.Providers
{
internal sealed class InMemoryManagedIdentityKeyProvider : IManagedIdentityKeyProvider
{
private static readonly SemaphoreSlim s_once = new(1, 1);
private volatile MiKeyInfo _cached;

public async Task<MiKeyInfo> GetOrCreateKeyAsync(CancellationToken ct)
{
if (_cached is not null)
return _cached;

await s_once.WaitAsync(ct).ConfigureAwait(false);
try
{
if (_cached is not null)
return _cached;

var rsa = CreateRsaKeyPair();
_cached = new MiKeyInfo(rsa, MiKeyType.InMemory);
return _cached;
}
finally
{
s_once.Release();
}
}

private static RSA CreateRsaKeyPair()
{
RSA rsa;
#if NET462 || NET472
// .NET Framework (Windows): use RSACng
rsa = new RSACng();
#else
// Cross-platform (.NET Core/5+/Standard)
rsa = RSA.Create();
#endif
rsa.KeySize = 2048;
return rsa;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#if !NETSTANDARD2_0
using System;
using System.Security.Cryptography;

namespace Microsoft.Identity.Client.ManagedIdentity.KeyGuard
{
/// <summary>
/// Helper for creating and validating Key Guard–isolated RSA keys.
/// </summary>
internal static class KeyGuardKey
{
// Flags not exposed in the public enum; values from NCrypt.h
private const CngKeyCreationOptions NCRYPT_USE_VIRTUAL_ISOLATION_FLAG = (CngKeyCreationOptions)0x00020000;
private const CngKeyCreationOptions NCRYPT_USE_PER_BOOT_KEY_FLAG = (CngKeyCreationOptions)0x00040000;

/// <summary>
/// Create a fresh RSA-2048 key with Key Guard (VBS) isolation.
/// Overwrites any existing key with the same name.
/// </summary>
/// <param name="providerName">Typically "Microsoft Software Key Storage Provider".</param>
/// <param name="keyName">The CNG key container name to create.</param>
public static CngKey CreateFresh(string providerName, string keyName)
{
var parms = new CngKeyCreationParameters
{
Provider = new CngProvider(providerName),
KeyUsage = CngKeyUsages.AllUsages,
ExportPolicy = CngExportPolicies.None,
// Per-boot, VBS/KeyGuard isolation; overwrite if exists
KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey
| NCRYPT_USE_VIRTUAL_ISOLATION_FLAG
| NCRYPT_USE_PER_BOOT_KEY_FLAG
};

// Set length = 2048 bits
parms.Parameters.Add(new CngProperty(
"Length",
BitConverter.GetBytes(2048),
CngPropertyOptions.None));

try
{
return CngKey.Create(CngAlgorithm.Rsa, keyName, parms);
}
catch (CryptographicException ex)
{
if (IsVbsUnavailable(ex))
{
// Clear, actionable signal to callers; provider can fall back to TPM/in-memory
throw new PlatformNotSupportedException(
"Key Guard requires Windows Core Isolation (VBS).", ex);
}
throw;
}
}

/// <summary>
/// Returns true if the key has the Key Guard (VBS) isolation flag.
/// </summary>
public static bool IsKeyGuardProtected(CngKey key)
{
if (!key.HasProperty("Virtual Iso", CngPropertyOptions.None))
return false;

byte[] val = key.GetProperty("Virtual Iso", CngPropertyOptions.None).GetValue();
return val != null && val.Length > 0 && val[0] != 0;
}

// NTE_NOT_SUPPORTED or recognizable message => VBS isolation not available
private static bool IsVbsUnavailable(CryptographicException ex)
{
const int NTE_NOT_SUPPORTED = unchecked((int)0x80890014);
return ex.HResult == NTE_NOT_SUPPORTED
|| (ex.Message != null && ex.Message.IndexOf("VBS key isolation", StringComparison.OrdinalIgnoreCase) >= 0)
|| (ex.Message != null && ex.Message.IndexOf("Virtualization-based", StringComparison.OrdinalIgnoreCase) >= 0);
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// src/client/Microsoft.Identity.Client/ManagedIdentity/Providers/WindowsManagedIdentityKeyProvider.cs
#if !NETSTANDARD2_0
using System;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client.ManagedIdentity.KeyGuard;

namespace Microsoft.Identity.Client.ManagedIdentity.Providers
{
/// <summary>
/// Windows policy:
/// 1) KeyGuard (CVM/TVM) if available
/// 2) Hardware (TPM/KSP via Microsoft Platform Crypto Provider)
/// 3) In-memory fallback (delegates to InMemoryManagedIdentityKeyProvider)
/// No certs; no attestation; no long-lived handles kept.
/// </summary>
internal sealed class WindowsManagedIdentityKeyProvider : IManagedIdentityKeyProvider
{
private static readonly SemaphoreSlim s_once = new SemaphoreSlim(1, 1);
private volatile MiKeyInfo _cached;

private const string KgProviderName = "Microsoft Software Key Storage Provider";
private const string KgKeyName = "KeyGuardRSAKey";
private const string TpmProvider = "Microsoft Platform Crypto Provider";
private const string TpmKeyName = "MSAL_MI_PLATFORM_RSA";

public async Task<MiKeyInfo> GetOrCreateKeyAsync(CancellationToken ct)
{
if (_cached != null)
return _cached;

await s_once.WaitAsync(ct).ConfigureAwait(false);
try
{
if (_cached != null)
return _cached;

// 1) KeyGuard (RSA-2048 under VBS isolation)
RSA kgRsa;
if (TryGetOrCreateKeyGuard(out kgRsa))
{
_cached = new MiKeyInfo(kgRsa, MiKeyType.KeyGuard);
return _cached;
}

// 2) Hardware TPM/KSP (RSA-2048, non-exportable)
RSA hwRsa;
if (TryGetOrCreateHardwareRsa(out hwRsa))
{
_cached = new MiKeyInfo(hwRsa, MiKeyType.Hardware);
return _cached;
}

// 3) Delegate fallback to portable in-memory provider
var memProvider = new InMemoryManagedIdentityKeyProvider();
_cached = await memProvider.GetOrCreateKeyAsync(ct).ConfigureAwait(false);
return _cached;
}
finally
{
s_once.Release();
}
}

// --- KeyGuard path (RSA) ---
private static bool TryGetOrCreateKeyGuard(out RSA rsa)
{
rsa = default(RSA);

try
{
CngProvider provider = new CngProvider(KgProviderName);

CngKey key;
if (CngKey.Exists(KgKeyName, provider))
{
key = CngKey.Open(KgKeyName, provider);

// Ensure actually KeyGuard-protected; if not, recreate as KeyGuard.
if (!KeyGuardKey.IsKeyGuardProtected(key))
{
key.Dispose();
key = KeyGuardKey.CreateFresh(KgProviderName, KgKeyName);
}
}
else
{
key = KeyGuardKey.CreateFresh(KgProviderName, KgKeyName);
}

rsa = new RSACng(key);
if (rsa.KeySize < 2048)
{
try
{ rsa.KeySize = 2048; }
catch { }
}
return true;
}
catch (PlatformNotSupportedException)
{
// VBS/Core Isolation not available => KeyGuard unavailable
return false;
}
catch (CryptographicException)
{
return false;
}
}

// --- Hardware (TPM/KSP) path (RSA) ---
private static bool TryGetOrCreateHardwareRsa(out RSA rsa)
{
rsa = default(RSA);

try
{
CngProvider provider = new CngProvider(TpmProvider);
CngKeyOpenOptions openOpts = CngKeyOpenOptions.MachineKey;

CngKey key = CngKey.Exists(TpmKeyName, provider, openOpts)
? CngKey.Open(TpmKeyName, provider, openOpts)
: CngKey.Create(
CngAlgorithm.Rsa,
TpmKeyName,
new CngKeyCreationParameters
{
Provider = provider,
KeyUsage = CngKeyUsages.Signing,
ExportPolicy = CngExportPolicies.None, // non-exportable
KeyCreationOptions = CngKeyCreationOptions.MachineKey
});

rsa = new RSACng(key);
if (rsa.KeySize < 2048)
{
try
{ rsa.KeySize = 2048; }
catch { }
}
return true;
}
catch (CryptographicException)
{
return false;
}
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ internal class ManagedIdentityClient
private const string WindowsHimdsFilePath = "%Programfiles%\\AzureConnectedMachineAgent\\himds.exe";
private const string LinuxHimdsFilePath = "/opt/azcmagent/bin/himds";
private readonly AbstractManagedIdentity _identitySource;
private readonly IManagedIdentityKeyProvider _provider;

public ManagedIdentityClient(RequestContext requestContext)
{
using (requestContext.Logger.LogMethodDuration())
{

_provider = requestContext.ServiceBundle.PlatformProxy.ManagedIdentityKeyProvider;
_identitySource = SelectManagedIdentitySource(requestContext);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading;
using Microsoft.Identity.Client.ManagedIdentity.Providers;
using Microsoft.Identity.Client.PlatformsCommon.Shared;

namespace Microsoft.Identity.Client.ManagedIdentity
{
internal static class ManagedIdentityKeyProviderFactory
{
private static IManagedIdentityKeyProvider s_provider;

internal static IManagedIdentityKeyProvider GetOrCreateProvider()
{
var p = Volatile.Read(ref s_provider);
if (p != null)
return p;

IManagedIdentityKeyProvider created = CreateProviderCore();
Interlocked.CompareExchange(ref s_provider, created, null);
return s_provider!;
}

private static IManagedIdentityKeyProvider CreateProviderCore()
{
#if NETSTANDARD2_0
return new InMemoryManagedIdentityKeyProvider();
#else
if (DesktopOsHelper.IsWindows())
{
return new WindowsManagedIdentityKeyProvider();
}
else
{
return new InMemoryManagedIdentityKeyProvider();
}
#endif
}
}
}
Loading