Skip to content

Commit d8264b1

Browse files
committed
Add new mtls pop package for MSI flows
1 parent ac5219e commit d8264b1

26 files changed

+901
-6
lines changed

LibsAndSamples.sln

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MacMauiAppWithBroker", "tes
196196
EndProject
197197
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MacConsoleAppWithBroker", "tests\devapps\MacConsoleAppWithBroker\MacConsoleAppWithBroker.csproj", "{DBD18BC8-72E4-47D4-BD79-8DEBD9F2C0D0}"
198198
EndProject
199+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Identity.Client.MtlsPop", "src\client\Microsoft.Identity.Client.MtlsPop\Microsoft.Identity.Client.MtlsPop.csproj", "{269A7A67-E48E-49A6-936B-60F1BE51F0CD}"
200+
EndProject
199201
Global
200202
GlobalSection(SolutionConfigurationPlatforms) = preSolution
201203
Debug + MobileApps|Any CPU = Debug + MobileApps|Any CPU
@@ -2022,6 +2024,48 @@ Global
20222024
{DBD18BC8-72E4-47D4-BD79-8DEBD9F2C0D0}.Release|x64.Build.0 = Release|Any CPU
20232025
{DBD18BC8-72E4-47D4-BD79-8DEBD9F2C0D0}.Release|x86.ActiveCfg = Release|Any CPU
20242026
{DBD18BC8-72E4-47D4-BD79-8DEBD9F2C0D0}.Release|x86.Build.0 = Release|Any CPU
2027+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|Any CPU.ActiveCfg = Debug|Any CPU
2028+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|Any CPU.Build.0 = Debug|Any CPU
2029+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|ARM.ActiveCfg = Debug|Any CPU
2030+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|ARM.Build.0 = Debug|Any CPU
2031+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|ARM64.ActiveCfg = Debug|Any CPU
2032+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|ARM64.Build.0 = Debug|Any CPU
2033+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|iPhone.ActiveCfg = Debug|Any CPU
2034+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|iPhone.Build.0 = Debug|Any CPU
2035+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|iPhoneSimulator.ActiveCfg = Debug|Any CPU
2036+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|iPhoneSimulator.Build.0 = Debug|Any CPU
2037+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|x64.ActiveCfg = Debug|Any CPU
2038+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|x64.Build.0 = Debug|Any CPU
2039+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|x86.ActiveCfg = Debug|Any CPU
2040+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|x86.Build.0 = Debug|Any CPU
2041+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
2042+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
2043+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|ARM.ActiveCfg = Debug|Any CPU
2044+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|ARM.Build.0 = Debug|Any CPU
2045+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|ARM64.ActiveCfg = Debug|Any CPU
2046+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|ARM64.Build.0 = Debug|Any CPU
2047+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|iPhone.ActiveCfg = Debug|Any CPU
2048+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|iPhone.Build.0 = Debug|Any CPU
2049+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
2050+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
2051+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|x64.ActiveCfg = Debug|Any CPU
2052+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|x64.Build.0 = Debug|Any CPU
2053+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|x86.ActiveCfg = Debug|Any CPU
2054+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|x86.Build.0 = Debug|Any CPU
2055+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
2056+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|Any CPU.Build.0 = Release|Any CPU
2057+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|ARM.ActiveCfg = Release|Any CPU
2058+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|ARM.Build.0 = Release|Any CPU
2059+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|ARM64.ActiveCfg = Release|Any CPU
2060+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|ARM64.Build.0 = Release|Any CPU
2061+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|iPhone.ActiveCfg = Release|Any CPU
2062+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|iPhone.Build.0 = Release|Any CPU
2063+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
2064+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
2065+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|x64.ActiveCfg = Release|Any CPU
2066+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|x64.Build.0 = Release|Any CPU
2067+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|x86.ActiveCfg = Release|Any CPU
2068+
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|x86.Build.0 = Release|Any CPU
20252069
EndGlobalSection
20262070
GlobalSection(SolutionProperties) = preSolution
20272071
HideSolutionNode = FALSE
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Runtime.InteropServices;
7+
using System.Text;
8+
using Microsoft.Win32.SafeHandles;
9+
10+
namespace Microsoft.Identity.Client.MtlsPop.Attestation
11+
{
12+
/// <summary>
13+
/// Managed façade for <c>AttestationClientLib.dll</c>. Holds initialization state,
14+
/// does ref-count hygiene on <see cref="SafeNCryptKeyHandle"/>, and returns a JWT.
15+
/// </summary>
16+
public sealed class AttestationClient : IDisposable
17+
{
18+
private bool _initialized;
19+
20+
/// <summary>
21+
/// AttestationClient constructor. Pro-actively verifies the native DLL,
22+
/// </summary>
23+
/// <exception cref="InvalidOperationException"></exception>
24+
public AttestationClient()
25+
{
26+
/* step 0 ── pro-actively verify the native DLL */
27+
string dllError = NativeDiagnostics.ProbeNativeDll();
28+
if (dllError is not null)
29+
throw new InvalidOperationException(dllError);
30+
31+
/* step 1 ── load & initialize */
32+
NativeDllResolver.EnsureLoaded();
33+
34+
var info = new AttestationClientLib.AttestationLogInfo
35+
{
36+
Log = AttestationLogger.ConsoleLogger,
37+
Ctx = IntPtr.Zero
38+
};
39+
40+
_initialized = AttestationClientLib.InitAttestationLib(ref info) == 0;
41+
if (!_initialized)
42+
throw new InvalidOperationException("Failed to initialize AttestationClientLib.");
43+
}
44+
45+
/// <summary>
46+
/// Calls the native <c>AttestKeyGuardImportKey</c> and returns a structured result.
47+
/// </summary>
48+
public AttestationResult Attest(string endpoint,
49+
SafeNCryptKeyHandle keyHandle,
50+
string clientId)
51+
{
52+
if (!_initialized)
53+
return new(AttestationStatus.NotInitialized, null, -1,
54+
"Native library not initialized.");
55+
56+
IntPtr buf = IntPtr.Zero;
57+
bool addRef = false;
58+
59+
try
60+
{
61+
keyHandle.DangerousAddRef(ref addRef);
62+
63+
int rc = AttestationClientLib.AttestKeyGuardImportKey(
64+
endpoint, null, null, keyHandle, out buf, clientId);
65+
66+
if (rc != 0)
67+
return new(AttestationStatus.NativeError, null, rc, null);
68+
69+
if (buf == IntPtr.Zero)
70+
return new(AttestationStatus.TokenEmpty, null, 0,
71+
"rc==0 but token buffer was null.");
72+
73+
string jwt = Marshal.PtrToStringAnsi(buf)!;
74+
return new(AttestationStatus.Success, jwt, 0, null);
75+
}
76+
catch (DllNotFoundException ex)
77+
{
78+
return new(AttestationStatus.Exception, null, -1,
79+
$"Native DLL not found: {ex.Message}");
80+
}
81+
catch (BadImageFormatException ex)
82+
{
83+
return new(AttestationStatus.Exception, null, -1,
84+
$"Architecture mismatch (x86/x64) or corrupted DLL: {ex.Message}");
85+
}
86+
catch (SEHException ex)
87+
{
88+
return new(AttestationStatus.Exception, null, -1,
89+
$"Native library raised SEHException: {ex.Message}");
90+
}
91+
catch (Exception ex)
92+
{
93+
return new(AttestationStatus.Exception, null, -1, ex.Message);
94+
}
95+
finally
96+
{
97+
if (buf != IntPtr.Zero)
98+
AttestationClientLib.FreeAttestationToken(buf);
99+
if (addRef)
100+
keyHandle.DangerousRelease();
101+
}
102+
}
103+
104+
/// <summary>
105+
/// Disposes the client, releasing any resources and un-initializing the native library.
106+
/// </summary>
107+
public void Dispose()
108+
{
109+
if (_initialized)
110+
{
111+
AttestationClientLib.UninitAttestationLib();
112+
_initialized = false;
113+
}
114+
GC.SuppressFinalize(this);
115+
}
116+
}
117+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Win32.SafeHandles;
5+
using System;
6+
using System.IO;
7+
using System.Runtime.InteropServices;
8+
9+
namespace Microsoft.Identity.Client.MtlsPop.Attestation
10+
{
11+
internal static class AttestationClientLib
12+
{
13+
internal enum LogLevel { Error, Warn, Info, Debug }
14+
15+
internal delegate void LogFunc(
16+
IntPtr ctx, string tag, LogLevel lvl, string func, int line, string msg);
17+
18+
[StructLayout(LayoutKind.Sequential)]
19+
internal struct AttestationLogInfo
20+
{
21+
public LogFunc Log;
22+
public IntPtr Ctx;
23+
}
24+
25+
[DllImport("AttestationClientLib.dll", CallingConvention = CallingConvention.Cdecl,
26+
CharSet = CharSet.Ansi)]
27+
internal static extern int InitAttestationLib(ref AttestationLogInfo info);
28+
29+
[DllImport("AttestationClientLib.dll", CallingConvention = CallingConvention.Cdecl,
30+
CharSet = CharSet.Ansi)]
31+
internal static extern int AttestKeyGuardImportKey(
32+
string endpoint,
33+
string authToken,
34+
string clientPayload,
35+
SafeNCryptKeyHandle keyHandle,
36+
out IntPtr token,
37+
string clientId);
38+
39+
[DllImport("AttestationClientLib.dll", CallingConvention = CallingConvention.Cdecl)]
40+
internal static extern void FreeAttestationToken(IntPtr token);
41+
42+
[DllImport("AttestationClientLib.dll", CallingConvention = CallingConvention.Cdecl)]
43+
internal static extern void UninitAttestationLib();
44+
}
45+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Text;
7+
8+
namespace Microsoft.Identity.Client.MtlsPop.Attestation
9+
{
10+
internal static class AttestationErrors
11+
{
12+
internal static string Describe(AttestationResultErrorCode rc) => rc switch
13+
{
14+
AttestationResultErrorCode.ERRORCURLINITIALIZATION
15+
=> "libcurl failed to initialize (DLL missing or version mismatch).",
16+
AttestationResultErrorCode.ERRORHTTPREQUESTFAILED
17+
=> "Could not reach the attestation service (network / proxy?).",
18+
AttestationResultErrorCode.ERRORATTESTATIONFAILED
19+
=> "The enclave rejected the evidence (key type / PCR policy).",
20+
AttestationResultErrorCode.ERRORJWTDECRYPTIONFAILED
21+
=> "The JWT returned by the service could not be decrypted.",
22+
AttestationResultErrorCode.ERRORLOGGERINITIALIZATION
23+
=> "Native logger setup failed (rare).",
24+
_ => rc.ToString() // default: enum name
25+
};
26+
}
27+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
6+
namespace Microsoft.Identity.Client.MtlsPop.Attestation
7+
{
8+
internal static class AttestationLogger
9+
{
10+
/// <summary>Default logger that pipes native messages to <c>Console.WriteLine</c>.</summary>
11+
internal static readonly AttestationClientLib.LogFunc ConsoleLogger = (_,
12+
tag, lvl, func, line, msg) =>
13+
Console.WriteLine($"[{lvl}] {tag} {func}:{line} {msg}");
14+
}
15+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
namespace Microsoft.Identity.Client.MtlsPop.Attestation
5+
{
6+
/// <summary>
7+
/// AttestationResult is the result of an attestation operation.
8+
/// </summary>
9+
/// <param name="Status"></param>
10+
/// <param name="Jwt"></param>
11+
/// <param name="NativeErrorCode"></param>
12+
/// <param name="ErrorMessage"></param>
13+
public sealed record AttestationResult(
14+
AttestationStatus Status,
15+
string Jwt,
16+
int NativeErrorCode,
17+
string ErrorMessage);
18+
}

0 commit comments

Comments
 (0)