Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
91 changes: 90 additions & 1 deletion LibsAndSamples.sln
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "all", "all", "{C44AADC0-1E9
.editorconfig = .editorconfig
.gitattributes = .gitattributes
.gitignore = .gitignore
CHANGELOG.md = CHANGELOG.md
build\CodeCoverage.runsettings = build\CodeCoverage.runsettings
build\credscan-exclusion.json = build\credscan-exclusion.json
Directory.Build.props = Directory.Build.props
Expand Down Expand Up @@ -196,6 +195,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MacMauiAppWithBroker", "tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MacConsoleAppWithBroker", "tests\devapps\MacConsoleAppWithBroker\MacConsoleAppWithBroker.csproj", "{DBD18BC8-72E4-47D4-BD79-8DEBD9F2C0D0}"
EndProject
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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeyGuardAttestation", "tests\devapps\Managed Identity apps\KeyGuardAttestationApp\KeyGuardAttestation\KeyGuardAttestation.csproj", "{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug + MobileApps|Any CPU = Debug + MobileApps|Any CPU
Expand Down Expand Up @@ -2022,6 +2025,90 @@ Global
{DBD18BC8-72E4-47D4-BD79-8DEBD9F2C0D0}.Release|x64.Build.0 = Release|Any CPU
{DBD18BC8-72E4-47D4-BD79-8DEBD9F2C0D0}.Release|x86.ActiveCfg = Release|Any CPU
{DBD18BC8-72E4-47D4-BD79-8DEBD9F2C0D0}.Release|x86.Build.0 = Release|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|Any CPU.ActiveCfg = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|Any CPU.Build.0 = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|ARM.ActiveCfg = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|ARM.Build.0 = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|ARM64.ActiveCfg = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|ARM64.Build.0 = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|iPhone.ActiveCfg = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|iPhone.Build.0 = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|iPhoneSimulator.Build.0 = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|x64.ActiveCfg = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|x64.Build.0 = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|x86.ActiveCfg = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug + MobileApps|x86.Build.0 = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|ARM.ActiveCfg = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|ARM.Build.0 = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|ARM64.Build.0 = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|iPhone.Build.0 = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|x64.ActiveCfg = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|x64.Build.0 = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|x86.ActiveCfg = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Debug|x86.Build.0 = Debug|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|Any CPU.Build.0 = Release|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|ARM.ActiveCfg = Release|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|ARM.Build.0 = Release|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|ARM64.ActiveCfg = Release|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|ARM64.Build.0 = Release|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|iPhone.ActiveCfg = Release|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|iPhone.Build.0 = Release|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|x64.ActiveCfg = Release|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|x64.Build.0 = Release|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|x86.ActiveCfg = Release|Any CPU
{269A7A67-E48E-49A6-936B-60F1BE51F0CD}.Release|x86.Build.0 = Release|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug + MobileApps|Any CPU.ActiveCfg = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug + MobileApps|Any CPU.Build.0 = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug + MobileApps|ARM.ActiveCfg = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug + MobileApps|ARM.Build.0 = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug + MobileApps|ARM64.ActiveCfg = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug + MobileApps|ARM64.Build.0 = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug + MobileApps|iPhone.ActiveCfg = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug + MobileApps|iPhone.Build.0 = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug + MobileApps|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug + MobileApps|iPhoneSimulator.Build.0 = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug + MobileApps|x64.ActiveCfg = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug + MobileApps|x64.Build.0 = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug + MobileApps|x86.ActiveCfg = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug + MobileApps|x86.Build.0 = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug|Any CPU.Build.0 = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug|ARM.ActiveCfg = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug|ARM.Build.0 = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug|ARM64.Build.0 = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug|iPhone.Build.0 = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug|x64.ActiveCfg = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug|x64.Build.0 = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug|x86.ActiveCfg = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Debug|x86.Build.0 = Debug|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Release|Any CPU.ActiveCfg = Release|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Release|Any CPU.Build.0 = Release|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Release|ARM.ActiveCfg = Release|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Release|ARM.Build.0 = Release|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Release|ARM64.ActiveCfg = Release|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Release|ARM64.Build.0 = Release|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Release|iPhone.ActiveCfg = Release|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Release|iPhone.Build.0 = Release|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Release|x64.ActiveCfg = Release|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Release|x64.Build.0 = Release|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Release|x86.ActiveCfg = Release|Any CPU
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -2081,6 +2168,8 @@ Global
{97995B86-AA0F-3AF9-DA40-85A6263E4391} = {9B0B5396-4D95-4C15-82ED-DC22B5A3123F}
{AEF6BB00-931F-4638-955D-24D735625C34} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB}
{DBD18BC8-72E4-47D4-BD79-8DEBD9F2C0D0} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB}
{269A7A67-E48E-49A6-936B-60F1BE51F0CD} = {1A37FD75-94E9-4D6F-953A-0DABBD7B49E9}
{701B8CD0-6D28-4B06-B8B4-9C47AF6E0793} = {BCAEE9AE-8D3E-4C77-A2E4-134E1552D5F8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {020399A9-DC27-4B82-9CAA-EF488665AC27}
Expand Down
7 changes: 7 additions & 0 deletions build/template-pack-and-sign-all-nugets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ steps:
ProjectRootPath: '$(Build.SourcesDirectory)\$(MsalSourceDir)src\client'
AssemblyName: 'Microsoft.Identity.Client.Extensions.Msal'

# Sign binary and pack Microsoft.Identity.Client.MtlsPop
- template: template-pack-and-sign-nuget.yaml
parameters:
BuildConfiguration: ${{ parameters.BuildConfiguration }}
ProjectRootPath: '$(Build.SourcesDirectory)\$(MsalSourceDir)src\client'
AssemblyName: 'Microsoft.Identity.Client.MtlsPop'

# Copy all packages out to staging
- task: CopyFiles@2
displayName: 'Copy Files to: $(Build.ArtifactStagingDirectory)\packages'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace Microsoft.Identity.Client.MtlsPop.Attestation
{
/// <summary>
/// Managed façade for <c>AttestationClientLib.dll</c>. Holds initialization state,
/// does ref-count hygiene on <see cref="SafeNCryptKeyHandle"/>, and returns a JWT.
/// </summary>
public sealed class AttestationClient : IDisposable
{
private bool _initialized;

/// <summary>
/// AttestationClient constructor. Pro-actively verifies the native DLL.
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
public AttestationClient()
{
/* step 0 ── ensure the resolver probes all valid locations
(env override → app base → System32/SysWOW64 → PATH) */
NativeDllResolver.EnsureLoaded();

/* step 1 ── optional proactive verification (non-fatal)
Keep the probe for diagnostics, but do NOT throw here; if the DLL
is truly unavailable/mismatched, InitAttestationLib will fail. */
string dllError = NativeDiagnostics.ProbeNativeDll();
// intentionally not throwing on dllError to avoid path-specific false negatives

/* step 2 ── load & initialize (logger is required by native lib) */
var info = new AttestationClientLib.AttestationLogInfo
{
Log = AttestationLogger.ConsoleLogger, // minimal rooted delegate; works on netstandard2.0 & net8.0
Ctx = IntPtr.Zero
};

_initialized = AttestationClientLib.InitAttestationLib(ref info) == 0;
if (!_initialized)
throw new InvalidOperationException("Failed to initialize AttestationClientLib.");
}

/// <summary>
/// Calls the native <c>AttestKeyGuardImportKey</c> and returns a structured result.
/// </summary>
public AttestationResult Attest(string endpoint,
SafeNCryptKeyHandle keyHandle,
string clientId)
{
if (!_initialized)
return new(AttestationStatus.NotInitialized, null, -1,
"Native library not initialized.");

IntPtr buf = IntPtr.Zero;
bool addRef = false;

try
{
keyHandle.DangerousAddRef(ref addRef);

int rc = AttestationClientLib.AttestKeyGuardImportKey(
endpoint, null, null, keyHandle, out buf, clientId);

if (rc != 0)
return new(AttestationStatus.NativeError, null, rc, null);

if (buf == IntPtr.Zero)
return new(AttestationStatus.TokenEmpty, null, 0,
"rc==0 but token buffer was null.");

string jwt = Marshal.PtrToStringAnsi(buf)!;
return new(AttestationStatus.Success, jwt, 0, null);
}
catch (DllNotFoundException ex)
{
return new(AttestationStatus.Exception, null, -1,
$"Native DLL not found: {ex.Message}");
}
catch (BadImageFormatException ex)
{
return new(AttestationStatus.Exception, null, -1,
$"Architecture mismatch (x86/x64) or corrupted DLL: {ex.Message}");
}
catch (SEHException ex)
{
return new(AttestationStatus.Exception, null, -1,
$"Native library raised SEHException: {ex.Message}");
}
catch (Exception ex)
{
return new(AttestationStatus.Exception, null, -1, ex.Message);
}
finally
{
if (buf != IntPtr.Zero)
AttestationClientLib.FreeAttestationToken(buf);
if (addRef)
keyHandle.DangerousRelease();
}
}

/// <summary>
/// Disposes the client, releasing any resources and un-initializing the native library.
/// </summary>
public void Dispose()
{
if (_initialized)
{
AttestationClientLib.UninitAttestationLib();
_initialized = false;
}
GC.SuppressFinalize(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Win32.SafeHandles;
using System;
using System.IO;
using System.Runtime.InteropServices;

namespace Microsoft.Identity.Client.MtlsPop.Attestation
{
internal static class AttestationClientLib
{
internal enum LogLevel { Error, Warn, Info, Debug }

internal delegate void LogFunc(
IntPtr ctx, string tag, LogLevel lvl, string func, int line, string msg);

[StructLayout(LayoutKind.Sequential)]
internal struct AttestationLogInfo
{
public LogFunc Log;
public IntPtr Ctx;
}

[DllImport("AttestationClientLib.dll", CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi)]
internal static extern int InitAttestationLib(ref AttestationLogInfo info);

[DllImport("AttestationClientLib.dll", CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi)]
internal static extern int AttestKeyGuardImportKey(
string endpoint,
string authToken,
string clientPayload,
SafeNCryptKeyHandle keyHandle,
out IntPtr token,
string clientId);

[DllImport("AttestationClientLib.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern void FreeAttestationToken(IntPtr token);

[DllImport("AttestationClientLib.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern void UninitAttestationLib();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Text;

namespace Microsoft.Identity.Client.MtlsPop.Attestation
{
internal static class AttestationErrors
{
internal static string Describe(AttestationResultErrorCode rc) => rc switch
{
AttestationResultErrorCode.ERRORCURLINITIALIZATION
=> "libcurl failed to initialize (DLL missing or version mismatch).",
AttestationResultErrorCode.ERRORHTTPREQUESTFAILED
=> "Could not reach the attestation service (network / proxy?).",
AttestationResultErrorCode.ERRORATTESTATIONFAILED
=> "The enclave rejected the evidence (key type / PCR policy).",
AttestationResultErrorCode.ERRORJWTDECRYPTIONFAILED
=> "The JWT returned by the service could not be decrypted.",
AttestationResultErrorCode.ERRORLOGGERINITIALIZATION
=> "Native logger setup failed (rare).",
_ => rc.ToString() // default: enum name
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Microsoft.Identity.Client.MtlsPop.Attestation
{
internal static class AttestationLogger
{
/// <summary>
/// Attestation Logger
/// </summary>
internal static readonly AttestationClientLib.LogFunc ConsoleLogger = (ctx, tag, lvl, func, line, msg) =>
{
try
{
string sTag = ToText(tag);
string sFunc = ToText(func);
string sMsg = ToText(msg);

var lineText = $"[MtlsPop][{lvl}] {sTag} {sFunc}:{line} {sMsg}";

// Default: Trace (respects listeners; safe for all app types)
Trace.WriteLine(lineText);

// Opt-in console mirroring for local debugging
if (Environment.GetEnvironmentVariable("MSAL_MTLSPOP_LOG_TO_CONSOLE") == "1")
{
Console.WriteLine(lineText);
}
}
catch
{
}
};

// Converts either string or IntPtr (char*) to text. Works with any LogFunc variant.
private static string ToText(object value)
{
if (value is IntPtr p && p != IntPtr.Zero)
{
try
{ return Marshal.PtrToStringAnsi(p) ?? string.Empty; }
catch { return string.Empty; }
}
return value?.ToString() ?? string.Empty;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Identity.Client.MtlsPop.Attestation
{
/// <summary>
/// AttestationResult is the result of an attestation operation.
/// </summary>
/// <param name="Status"></param>
/// <param name="Jwt"></param>
/// <param name="NativeErrorCode"></param>
/// <param name="ErrorMessage"></param>
public sealed record AttestationResult(
AttestationStatus Status,
string Jwt,
int NativeErrorCode,
string ErrorMessage);
}
Loading