Skip to content

Commit bbaa1bf

Browse files
MAA POC (#5339)
* init * pr comments * pr comments * typos --------- Co-authored-by: Gladwin Johnson <[email protected]>
1 parent 39b3f69 commit bbaa1bf

14 files changed

+821
-0
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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.Runtime.InteropServices;
7+
using static KeyGuard.Attestation.AttestationErrors;
8+
9+
namespace KeyGuard.Attestation
10+
{
11+
/// <summary>
12+
/// Managed façade for <c>AttestationClientLib.dll</c>. Holds initialization state,
13+
/// does ref-count hygiene on <see cref="SafeNCryptKeyHandle"/>, and returns a JWT.
14+
/// </summary>
15+
public sealed class AttestationClient : IDisposable
16+
{
17+
private bool _initialized;
18+
19+
/// <summary>
20+
/// AttestationClient constructor. Pro-actively verifies the native DLL,
21+
/// </summary>
22+
/// <exception cref="InvalidOperationException"></exception>
23+
public AttestationClient()
24+
{
25+
/* step 0 ── pro-actively verify the native DLL */
26+
string? dllError = NativeDiagnostics.ProbeNativeDll();
27+
if (dllError is not null)
28+
throw new InvalidOperationException(dllError);
29+
30+
/* step 1 ── load & initialize */
31+
NativeDllResolver.EnsureLoaded();
32+
33+
var info = new NativeMethods.AttestationLogInfo
34+
{
35+
Log = AttestationLogger.ConsoleLogger,
36+
Ctx = IntPtr.Zero
37+
};
38+
39+
_initialized = NativeMethods.InitAttestationLib(ref info) == 0;
40+
if (!_initialized)
41+
throw new InvalidOperationException("Failed to initialize AttestationClientLib.");
42+
}
43+
44+
/// <summary>
45+
/// Calls the native <c>AttestKeyGuardImportKey</c> and returns a structured result.
46+
/// </summary>
47+
public AttestationResult Attest(string endpoint,
48+
SafeNCryptKeyHandle keyHandle,
49+
string clientId = "kg-sample-client")
50+
{
51+
if (!_initialized)
52+
return new(AttestationStatus.NotInitialized, null, -1,
53+
"Native library not initialized.");
54+
55+
IntPtr buf = IntPtr.Zero;
56+
bool addRef = false;
57+
58+
try
59+
{
60+
keyHandle.DangerousAddRef(ref addRef);
61+
62+
int rc = NativeMethods.AttestKeyGuardImportKey(
63+
endpoint, null, null, keyHandle, out buf, clientId);
64+
65+
if (rc != 0)
66+
return new(AttestationStatus.NativeError, null, rc, null);
67+
68+
if (buf == IntPtr.Zero)
69+
return new(AttestationStatus.TokenEmpty, null, 0,
70+
"rc==0 but token buffer was null.");
71+
72+
string jwt = Marshal.PtrToStringAnsi(buf)!;
73+
return new(AttestationStatus.Success, jwt, 0, null);
74+
}
75+
catch (DllNotFoundException ex)
76+
{
77+
return new(AttestationStatus.Exception, null, -1,
78+
$"Native DLL not found: {ex.Message}");
79+
}
80+
catch (BadImageFormatException ex)
81+
{
82+
return new(AttestationStatus.Exception, null, -1,
83+
$"Architecture mismatch (x86/x64) or corrupted DLL: {ex.Message}");
84+
}
85+
catch (SEHException ex)
86+
{
87+
return new(AttestationStatus.Exception, null, -1,
88+
$"Native library raised SEHException: {ex.Message}");
89+
}
90+
catch (Exception ex)
91+
{
92+
return new(AttestationStatus.Exception, null, -1, ex.Message);
93+
}
94+
finally
95+
{
96+
if (buf != IntPtr.Zero)
97+
NativeMethods.FreeAttestationToken(buf);
98+
if (addRef)
99+
keyHandle.DangerousRelease();
100+
}
101+
}
102+
103+
/// <summary>
104+
/// Disposes the client, releasing any resources and un-initializing the native library.
105+
/// </summary>
106+
public void Dispose()
107+
{
108+
if (_initialized)
109+
{
110+
NativeMethods.UninitAttestationLib();
111+
_initialized = false;
112+
}
113+
GC.SuppressFinalize(this);
114+
}
115+
}
116+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
namespace KeyGuard.Attestation
5+
{
6+
internal static class AttestationErrors
7+
{
8+
internal static string Describe(AttestationResultErrorCode rc) => rc switch
9+
{
10+
AttestationResultErrorCode.ERRORCURLINITIALIZATION
11+
=> "libcurl failed to initialize (DLL missing or version mismatch).",
12+
AttestationResultErrorCode.ERRORHTTPREQUESTFAILED
13+
=> "Could not reach the attestation service (network / proxy?).",
14+
AttestationResultErrorCode.ERRORATTESTATIONFAILED
15+
=> "The enclave rejected the evidence (key type / PCR policy).",
16+
AttestationResultErrorCode.ERRORJWTDECRYPTIONFAILED
17+
=> "The JWT returned by the service could not be decrypted.",
18+
AttestationResultErrorCode.ERRORLOGGERINITIALIZATION
19+
=> "Native logger setup failed (rare).",
20+
_ => rc.ToString() // default: enum name
21+
};
22+
}
23+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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 KeyGuard.Attestation
10+
{
11+
/// <summary>P/Invoke signatures – nothing managed should leak beyond this class.</summary>
12+
internal static class NativeMethods
13+
{
14+
internal enum LogLevel { Error, Warn, Info, Debug }
15+
16+
internal delegate void LogFunc(
17+
IntPtr ctx, string tag, LogLevel lvl, string func, int line, string msg);
18+
19+
[StructLayout(LayoutKind.Sequential)]
20+
internal struct AttestationLogInfo
21+
{
22+
public LogFunc Log;
23+
public IntPtr Ctx;
24+
}
25+
26+
[DllImport("AttestationClientLib.dll", CallingConvention = CallingConvention.Cdecl,
27+
CharSet = CharSet.Ansi)]
28+
internal static extern int InitAttestationLib(ref AttestationLogInfo info);
29+
30+
[DllImport("AttestationClientLib.dll", CallingConvention = CallingConvention.Cdecl,
31+
CharSet = CharSet.Ansi)]
32+
internal static extern int AttestKeyGuardImportKey(
33+
string? endpoint,
34+
string? authToken,
35+
string? clientPayload,
36+
SafeNCryptKeyHandle keyHandle,
37+
out IntPtr token,
38+
string clientId);
39+
40+
[DllImport("AttestationClientLib.dll", CallingConvention = CallingConvention.Cdecl)]
41+
internal static extern void FreeAttestationToken(IntPtr token);
42+
43+
[DllImport("AttestationClientLib.dll", CallingConvention = CallingConvention.Cdecl)]
44+
internal static extern void UninitAttestationLib();
45+
}
46+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
namespace KeyGuard.Attestation
5+
{
6+
internal static class AttestationLogger
7+
{
8+
/// <summary>Default logger that pipes native messages to <c>Console.WriteLine</c>.</summary>
9+
internal static readonly NativeMethods.LogFunc ConsoleLogger = (_,
10+
tag, lvl, func, line, msg) =>
11+
Console.WriteLine($"[{lvl}] {tag} {func}:{line} {msg}");
12+
}
13+
}
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+
namespace KeyGuard.Attestation
5+
{
6+
/// <summary>
7+
/// Error codes returned by <c>AttestationClientLib.dll</c>.
8+
/// A value of <see cref="SUCCESS"/> (0) indicates success; all other
9+
/// values are negative and represent specific failure categories.
10+
/// </summary>
11+
public enum AttestationResultErrorCode
12+
{
13+
/// <summary>The operation completed successfully.</summary>
14+
SUCCESS = 0,
15+
16+
/// <summary>libcurl could not be initialized inside the native library.</summary>
17+
ERRORCURLINITIALIZATION = -1,
18+
19+
/// <summary>The HTTP response body could not be parsed (malformed JSON, invalid JWT, etc.).</summary>
20+
ERRORRESPONSEPARSING = -2,
21+
22+
/// <summary>Managed-Identity (MSI) access token could not be obtained.</summary>
23+
ERRORMSITOKENNOTFOUND = -3,
24+
25+
/// <summary>The HTTP request exceeded the maximum retry count configured by the native client.</summary>
26+
ERRORHTTPREQUESTEXCEEDEDRETRIES = -4,
27+
28+
/// <summary>An HTTP request to the attestation service failed (network error, non-200 status, timeout, etc.).</summary>
29+
ERRORHTTPREQUESTFAILED = -5,
30+
31+
/// <summary>The attestation enclave rejected the supplied evidence (policy or signature failure).</summary>
32+
ERRORATTESTATIONFAILED = -6,
33+
34+
/// <summary>libcurl reported “couldn’t send” (DNS resolution, TLS handshake, or socket error).</summary>
35+
ERRORSENDINGCURLREQUESTFAILED = -7,
36+
37+
/// <summary>One or more input parameters passed to the native API were invalid or null.</summary>
38+
ERRORINVALIDINPUTPARAMETER = -8,
39+
40+
/// <summary>Validation of the attestation parameters failed on the client side.</summary>
41+
ERRORATTESTATIONPARAMETERSVALIDATIONFAILED = -9,
42+
43+
/// <summary>Native client failed to allocate heap memory.</summary>
44+
ERRORFAILEDMEMORYALLOCATION = -10,
45+
46+
/// <summary>Could not retrieve OS build / version information required for the attestation payload.</summary>
47+
ERRORFAILEDTOGETOSINFO = -11,
48+
49+
/// <summary>Internal TPM failure while gathering quotes or PCR values.</summary>
50+
ERRORTPMINTERNALFAILURE = -12,
51+
52+
/// <summary>TPM operation (e.g., signing the quote) failed.</summary>
53+
ERRORTPMOPERATIONFAILURE = -13,
54+
55+
/// <summary>The returned JWT could not be decrypted on the client.</summary>
56+
ERRORJWTDECRYPTIONFAILED = -14,
57+
58+
/// <summary>JWT decryption failed due to a TPM error.</summary>
59+
ERRORJWTDECRYPTIONTPMERROR = -15,
60+
61+
/// <summary>JSON in the service response was invalid or lacked required fields.</summary>
62+
ERRORINVALIDJSONRESPONSE = -16,
63+
64+
/// <summary>The VCEK certificate blob returned from the service was empty.</summary>
65+
ERROREMPTYVCEKCERT = -17,
66+
67+
/// <summary>The service response body was empty.</summary>
68+
ERROREMPTYRESPONSE = -18,
69+
70+
/// <summary>The HTTP request body generated by the client was empty.</summary>
71+
ERROREMPTYREQUESTBODY = -19,
72+
73+
/// <summary>Failed to parse the host-configuration-level (HCL) report.</summary>
74+
ERRORHCLREPORTPARSINGFAILURE = -20,
75+
76+
/// <summary>The retrieved HCL report was empty.</summary>
77+
ERRORHCLREPORTEMPTY = -21,
78+
79+
/// <summary>Could not extract JWK information from the attestation evidence.</summary>
80+
ERROREXTRACTINGJWKINFO = -22,
81+
82+
/// <summary>Failed converting a JWK structure to an RSA public key.</summary>
83+
ERRORCONVERTINGJWKTORSAPUB = -23,
84+
85+
/// <summary>EVP initialization for RSA encryption failed (OpenSSL).</summary>
86+
ERROREVPPKEYENCRYPTINITFAILED = -24,
87+
88+
/// <summary>EVP encryption failed when building the attestation claim.</summary>
89+
ERROREVPPKEYENCRYPTFAILED = -25,
90+
91+
/// <summary>Failed to decrypt data due to a TPM error.</summary>
92+
ERRORDATADECRYPTIONTPMERROR = -26,
93+
94+
/// <summary>Parsing DNS information for the attestation service endpoint failed.</summary>
95+
ERRORPARSINGDNSINFO = -27,
96+
97+
/// <summary>Failed to parse the attestation response envelope.</summary>
98+
ERRORPARSINGATTESTATIONRESPONSE = -28,
99+
100+
/// <summary>Provisioning of the Attestation Key (AK) certificate failed.</summary>
101+
ERRORAKCERTPROVISIONINGFAILED = -29,
102+
103+
/// <summary>Initialising the native attestation client failed.</summary>
104+
ERRORCLIENTINITFAILED = -30,
105+
106+
/// <summary>The service returned an empty JWT.</summary>
107+
ERROREMPTYJWTRESPONSE = -31,
108+
109+
/// <summary>Creating the KeyGuard attestation report failed on the client.</summary>
110+
ERRORCREATEKGATTESTATIONREPORT = -32,
111+
112+
/// <summary>Failed to extract the public key from the import-only key.</summary>
113+
ERROREXTRACTIMPORTKEYPUB = -33,
114+
115+
/// <summary>An unexpected C++ exception occurred inside the native client.</summary>
116+
ERRORUNEXPECTEDEXCEPTION = -34,
117+
118+
/// <summary>Initialising the native logger failed (file I/O / permissions / path issues).</summary>
119+
ERRORLOGGERINITIALIZATION = -35
120+
}
121+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
namespace KeyGuard.Attestation;
5+
6+
/// <summary>
7+
/// High-level outcome categories returned by <see cref="AttestationClient.Attest"/>.
8+
/// </summary>
9+
public enum AttestationStatus
10+
{
11+
/// <summary>Everything succeeded; <see cref="AttestationResult.Jwt"/> is populated.</summary>
12+
Success = 0,
13+
14+
/// <summary>Native library returned a non-zero <c>AttestationResultErrorCode</c>.</summary>
15+
NativeError = 1,
16+
17+
/// <summary>rc == 0 but the token buffer was null/empty.</summary>
18+
TokenEmpty = 2,
19+
20+
/// <summary><see cref="AttestationClient"/> could not initialize the native DLL.</summary>
21+
NotInitialized = 3,
22+
23+
/// <summary>Any managed exception thrown while attempting the call.</summary>
24+
Exception = 4
25+
}
26+
27+
/// <summary>
28+
/// Unified result returned by <see cref="AttestationClient.Attest"/>.
29+
/// </summary>
30+
/// <param name="Status">High-level category.</param>
31+
/// <param name="Jwt">JWT when <see cref="AttestationStatus.Success"/>; otherwise null.</param>
32+
/// <param name="NativeCode">
33+
/// The raw integer code returned by <c>AttestKeyGuardImportKey</c>
34+
/// (cast to <see cref="AttestationResultErrorCode"/> for readability).
35+
/// Zero when <see cref="Status"/> is not <see cref="AttestationStatus.NativeError"/>.
36+
/// </param>
37+
/// <param name="Message">Optional descriptive text for non-success cases.</param>
38+
public record AttestationResult(
39+
AttestationStatus Status,
40+
string? Jwt,
41+
int NativeCode,
42+
string? Message);

0 commit comments

Comments
 (0)