Zum Hauptinhalt springen
Gilt für:
.NET 6+EF CoreDuoKey Cockpit

Voraussetzungen

  • .NET 6+ (the reference client is BCL-only: System.Net.Http, System.Text.Json, AesGcm).
  • A CAP API key (dke_cap_…) for the calling tenant — see Getting Started.
  • An active tenant policy (bootstrap one from a template).

Install​

The .NET client is a single self-contained class, DuoKey.Cap.CapClient, downloaded from your Cockpit. It has no NuGet dependencies — it uses only the base class library. The download endpoint returns a JSON envelope (language, filename, content), so extract the content field — for example with jq — rather than saving the response body directly:

DownloadBASH
curl -H "Authorization: Bearer $COCKPIT_SESSION_JWT" \
"https://cockpit.example.com/api/cap/sdk/download/dotnet" \
| jq -r '.content' > Cap/CapClient.cs

The client surface​

MemberPurpose
new CapClient(string baseUrl, string capKey)Create a client. baseUrl is the Cockpit API root (…/api); capKey is the dke_cap_… key.
new CapClient(baseUrl, capKey, byte[]? dataKey)Optional: supply a 32-byte key for the local demo Protect/Reveal (see caveat below).
Task<CapDecision> ResolveAsync(string dataClass, string purpose)Resolve an intent to a decision.
Task ObserveAsync(string dataClass, string purpose, string observedAlgorithm, string source)Report the algorithm that actually ran (drift detection).
Task<string> ProtectAsync(string value, string dataClass, string purpose)Convenience: resolve + apply (demo helper — see caveat).
string Reveal(string tagged)Reverse a value produced by Protect (demo helper).

CapDecision is a record: Algorithm, Posture, QuantumResistant, Backend.

CapDecision fieldsCSHARP
CapDecision d = await cap.ResolveAsync("pan", "storage");
d.Algorithm;        // e.g. "aes256-gcm"
d.Posture;          // "classical" | "hybrid" | "pqc_only"
d.QuantumResistant; // false for classical, true otherwise
d.Backend;          // "software" | "fips1403_hsm" | "pqc_capable" | "local_tokenizer"

Register with dependency injection​

appsettings.jsonJSON
{
"Cap": {
  "BaseUrl": "https://cockpit.example.com/api"
}
}
Program.csCSHARP
builder.Services.AddSingleton(sp =>
{
  var cfg = sp.GetRequiredService<IConfiguration>();
  var baseUrl = cfg["Cap:BaseUrl"]!;
  var capKey  = Environment.GetEnvironmentVariable("CAP_KEY")!;
  return new DuoKey.Cap.CapClient(baseUrl, capKey);
});
Keep the key out of source

Bind the key from an environment variable (CAP_KEY) or your secret manager. Never commit a dke_cap_… key.

Resolve and run a signature​

SignatureService.csCSHARP
public class SignatureService
{
  private readonly CapClient _cap;
  public SignatureService(CapClient cap) => _cap = cap;

  public async Task<byte[]> SignAsync(byte[] payload)
  {
      // 1) Resolve WHAT (pii + sign) -> HOW
      CapDecision d = await _cap.ResolveAsync("pii", "sign");

      // 2) Run the signature with d.Algorithm using the referenced key.
      string algorithm = d.Algorithm;   // e.g. "hybrid-ml-dsa65-ecdsa-p256"
      byte[] signature = _signer.Sign(payload, algorithm);

      // 3) Close the loop: report what actually ran.
      await _cap.ObserveAsync("pii", "sign", algorithm, "payments-svc");
      return signature;
  }
}

Protect fields with EF Core​

A common pattern is to protect a sensitive column with an EF Core value converter that delegates to CAP. The intent (data class + purpose) is fixed per column; the algorithm is resolved by policy.

Customer.cs + converterCSHARP
public class Customer
{
  public int Id { get; set; }
  // The plane resolves HOW; this column just declares it is a PAN to be tokenized.
  public string Pan { get; set; } = default!;
}

// A converter that tokenizes on save and reveals on read via CAP.
public sealed class CapTokenizeConverter : ValueConverter<string, string>
{
  public CapTokenizeConverter(CapClient cap)
      : base(v => cap.ProtectAsync(v, "pan", "tokenize").GetAwaiter().GetResult(),
             v => cap.Reveal(v)) { }
}

protected override void OnModelCreating(ModelBuilder b) =>
  b.Entity<Customer>().Property(c => c.Pan).HasConversion(new CapTokenizeConverter(_cap));
The reference helpers are illustrative

In the downloaded reference client, ProtectAsync/Reveal run aes256-gcm with a local demo key and a stand-in tokenizer — for wiring and testing, not production. Production tokenization and vault-backed encryption run server-side against the policy's key_ref. Do not ship the demo helpers as real tokenization.

Observe and drift​

ObserveAsync(...) reports the algorithm that actually executed. If it is weaker than the resolved decision, CAP records a drift finding (warning if still quantum-resistant, critical if it dropped to classical), reviewable in the Cockpit.

Drift-honest telemetryCSHARP
await _cap.ObserveAsync("pii", "sign", "ecdsa-p256", "legacy-batch");
// -> CAP flags a drift finding: declared hybrid, observed classical

The posture flip — zero code change​

1

Today

ResolveAsync("pii","sign") returns hybrid-ml-dsa65-ecdsa-p256 under a hybrid policy.

2

Security raises the floor

A new policy version sets a pqc_only posture (or bootstraps cnsa_2_0).

3

Next resolve

The same code now receives ml-dsa65 — no recompile, no redeploy.

Error handling​

SituationWhat you getWhat to do
No active policy for the tenantresolved: false, reason "no active CAP policy…"Bootstrap a policy from a template.
Posture floor cannot be metresolved: false with a reasonFix the policy rule or the requested residency.
Unknown data class / purposeValidation errorUse a valid data class and one of storage / transport / tokenize / sign.
Invalid or revoked keyHTTP 401/403 from /cap/sdk/*Mint a new dke_cap_ key.
cap.enabled feature offAccess deniedThe tenant needs the Enterprise or Free Trial edition.