Skip to content

Commit b2444f8

Browse files
committed
test
1 parent e5a8900 commit b2444f8

File tree

2 files changed

+122
-0
lines changed

2 files changed

+122
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Identity.Client.MtlsPop.Attestation;
5+
using Microsoft.Identity.Test.Common.Core.Helpers;
6+
using Microsoft.VisualStudio.TestTools.UnitTesting;
7+
using System;
8+
using System.Runtime.InteropServices;
9+
using System.Security.Cryptography;
10+
11+
namespace Microsoft.Identity.Test.E2E
12+
{
13+
[TestClass]
14+
public class KeyGuardAttestationTests
15+
{
16+
// Create a KeyGuard-protected RSA key, using the same provider + flags we use elsewhere.
17+
// Provider name + flags mirror the KeyGuard helper in #5448:
18+
// - Provider: "Microsoft Software Key Storage Provider"
19+
// - Flags: Virtual Isolation (0x00020000) + Per-Boot Key (0x00040000)
20+
// See PR #5448 KeyGuardHelper.cs. :contentReference[oaicite:0]{index=0}
21+
private static CngKey CreateKeyGuardKey(string keyName)
22+
{
23+
const string ProviderName = "Microsoft Software Key Storage Provider";
24+
const int NCRYPT_USE_VIRTUAL_ISOLATION_FLAG = 0x00020000;
25+
const int NCRYPT_USE_PER_BOOT_KEY_FLAG = 0x00040000;
26+
27+
var p = new CngKeyCreationParameters
28+
{
29+
Provider = new CngProvider(ProviderName),
30+
ExportPolicy = CngExportPolicies.None,
31+
KeyUsage = CngKeyUsages.AllUsages,
32+
KeyCreationOptions =
33+
CngKeyCreationOptions.OverwriteExistingKey |
34+
(CngKeyCreationOptions)NCRYPT_USE_VIRTUAL_ISOLATION_FLAG |
35+
(CngKeyCreationOptions)NCRYPT_USE_PER_BOOT_KEY_FLAG,
36+
};
37+
38+
// Set 2048-bit RSA length
39+
p.Parameters.Add(new CngProperty(
40+
"Length",
41+
BitConverter.GetBytes(2048),
42+
CngPropertyOptions.None));
43+
44+
return CngKey.Create(CngAlgorithm.Rsa, keyName, p);
45+
}
46+
47+
private static bool IsKeyGuardProtected(CngKey key)
48+
{
49+
try
50+
{
51+
// KeyGuard exposes a "Virtual Iso" property that is non-zero when protected.
52+
// Same check used in #5448. :contentReference[oaicite:1]{index=1}
53+
var prop = key.GetProperty("Virtual Iso", CngPropertyOptions.None);
54+
var bytes = prop.GetValue();
55+
return bytes != null && bytes.Length >= 4 && BitConverter.ToInt32(bytes, 0) != 0;
56+
}
57+
catch
58+
{
59+
return false;
60+
}
61+
}
62+
63+
[TestCategory("MI_E2E_AzureArc")]
64+
[RunOnAzureDevOps]
65+
[TestMethod]
66+
public void Attest_KeyGuardKey_OnAzureArc_Succeeds()
67+
{
68+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
69+
{
70+
Assert.Inconclusive("KeyGuard attestation test runs only on Windows.");
71+
}
72+
73+
// Attestation endpoint must be injected via pipeline variables on the Arc agent.
74+
var endpoint = Environment.GetEnvironmentVariable("TOKEN_ATTESTATION_ENDPOINT");
75+
if (string.IsNullOrWhiteSpace(endpoint))
76+
{
77+
Assert.Inconclusive($"Set {"TOKEN_ATTESTATION_ENDPOINT"} on the Azure Arc agent to run this test.");
78+
}
79+
80+
// UAMI client id (optional). For SAMI it's typically unset.
81+
var clientId = Environment.GetEnvironmentVariable("MSI_CLIENT_ID") ?? string.Empty;
82+
83+
string keyName = "MsalE2E_Keyguard_" + Guid.NewGuid().ToString("N");
84+
CngKey key = null;
85+
try
86+
{
87+
key = CreateKeyGuardKey(keyName);
88+
89+
if (!IsKeyGuardProtected(key))
90+
{
91+
Assert.Inconclusive("Key was created but not KeyGuard-protected. Is KeyGuard/VBS enabled on this machine?");
92+
}
93+
94+
// Use the new public AttestationClient from the MtlsPop package. :contentReference[oaicite:2]{index=2}
95+
using var client = new AttestationClient();
96+
var result = client.Attest(endpoint, key.Handle, clientId);
97+
98+
// Validate success + JWT shape (3 parts).
99+
Assert.AreEqual(AttestationStatus.Success, result.Status,
100+
$"Attestation failed: status={result.Status}, nativeRc={result.NativeErrorCode}, msg={result.ErrorMessage}");
101+
Assert.IsFalse(string.IsNullOrEmpty(result.Jwt), "Expected a non-empty attestation JWT.");
102+
103+
var parts = result.Jwt.Split('.');
104+
Assert.AreEqual(3, parts.Length, "Expected a JWT (3 parts).");
105+
}
106+
catch (CryptographicException ex)
107+
{
108+
Assert.Inconclusive("CNG/KeyGuard is not available or access is denied on this machine: " + ex.Message);
109+
}
110+
catch (InvalidOperationException ex)
111+
{
112+
// Thrown by AttestationClient when the native DLL cannot be found/initialized.
113+
Assert.Inconclusive("Attestation native lib not available on this runner: " + ex.Message);
114+
}
115+
finally
116+
{
117+
try { key?.Delete(); } catch { /* best-effort cleanup */ }
118+
}
119+
}
120+
}
121+
}

tests/Microsoft.Identity.Test.E2e/Microsoft.Identity.Test.E2E.MSI.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10+
<ProjectReference Include="..\..\src\client\Microsoft.Identity.Client.MtlsPop\Microsoft.Identity.Client.MtlsPop.csproj" />
1011
<ProjectReference Include="..\..\src\client\Microsoft.Identity.Client\Microsoft.Identity.Client.csproj" />
1112
<ProjectReference Include="..\Microsoft.Identity.Test.Common\Microsoft.Identity.Test.Common.csproj" />
1213

0 commit comments

Comments
 (0)