.NET
Managed C# over HttpClient, .NET 8 LTS and 9. Auth is attached per request, so an injected HttpClient is never mutated.
In development
This SDK is implemented but not yet published to its package registry. Until it ships, collect an opaque evidence blob on the device and appraise it server-side with @rootherald/node (or any available server SDK). The API shown below is the planned surface and may change before release.
terminalbash
dotnet add package RootHerald.AspNetCoreThe calls
new RootHeraldClient(secretKey, baseUrl?, httpClient?)IssueChallengeAsync(new ChallengeOptions { Ask = [Ask.Identity, Ask.Posture], Policy, KeyPurpose })→RootHeraldChallenge(ChallengeId, Nonce, ExpiresAt, Challenge).VerifyAsync(evidence, new AttestOptions { ChallengeId, Policy })→AttestResult:Verdict,IsAllowed,DeviceId,VerdictData,EnrollmentRequired,AssuranceClaimsMet,Key.RelayEnrollAsync(blob, challengeId)/RelayActivateAsync(activation).RootHeraldClient.VerifyKeySignature(jwk, message, signature)—System.Security.Cryptography, raw or DER, never throws.- Exceptions:
InvalidSecretKeyException,ChallengeException,InvalidEvidenceException,UnknownPolicyException,PolicyDowngradeException,AdmissionRefusedException,QuotaExceededException; baseRootHeraldApiException.
Example
Program.cscsharp
using System.Text.Json.Nodes;
using RootHerald.AspNetCore;
var rh = new RootHeraldClient(Environment.GetEnvironmentVariable("RH_SECRET_KEY")!);
app.MapPost("/api/challenge", async () =>
{
var c = await rh.IssueChallengeAsync(new ChallengeOptions
{
Ask = [Ask.Identity, Ask.Posture],
Policy = "rootherald:builtin:strict-hardware",
});
return Results.Json(new { c.ChallengeId, c.Challenge }); // relay Challenge verbatim
});
app.MapPost("/api/verify", async (JsonObject body) =>
{
AttestResult result;
try
{
result = await rh.VerifyAsync(body["evidence"]!, new AttestOptions { ChallengeId = body["challengeId"]!.GetValue<string>() });
}
catch (QuotaExceededException) { return Results.StatusCode(429); }
if (result.EnrollmentRequired) return Results.Json(new { error = "enrollment_required" }, statusCode: 409);
if (!result.IsAllowed) return Results.Json(new { error = "device_rejected" }, statusCode: 403);
if (result.Key is { } key) await keys.PutAsync(result.DeviceId!, key.Jwk); // only for a key ask, only on pass
return Results.Json(new { deviceId = result.DeviceId });
});A runnable sample is the RootHerald.AspNetCore.Sample project in sdk-dotnet.