SystemLocker, REST-API

Der Schlüssel zu Ihrem PC.

SystemLocker

Passwortloser Windows-Login und Datenschutz über einen physischen USB-Dongle. Steckt der Dongle, meldet der PC automatisch das richtige Benutzerkonto an, ganz ohne Passworteingabe. Fehlt der Dongle, bleiben Konto und Arbeitsdaten verschlüsselt und unzugänglich.

SystemLocker Hero
Passwortlos & komfortabel
Der Dongle ersetzt die Passworteingabe und wählt das richtige Profil.
Rundum verschlüsselt
System und Arbeitsdaten mit BitLocker geschützt, kein Boot-Passwort nötig.
Rollenbasiert
Verschiedene Dongles öffnen verschiedene Konten und Datenbereiche, kryptografisch getrennt.
Sicher gesichert
Zentrale Wiederherstellung der Dongles über diese REST-API. Kopien für den Notfall.

So funktioniert es in 3 Schritten

Workflow: Dongle einstecken, automatisch anmelden, arbeiten

Screenshots aus der laufenden Anwendung

Assistent: Willkommen
Assistent: Willkommen
Assistent: Dongle-Verwaltung mit Rolle Finanzen, provisioniert
Assistent: Dongle-Verwaltung mit Rolle Finanzen, provisioniert
Simulierter Start mit Dongle, Anmeldung erfolgreich
Simulierter Start mit Dongle, Anmeldung erfolgreich
Simulierter Start ohne Dongle, getarnte Fehlermeldung
Simulierter Start ohne Dongle, getarnte Fehlermeldung
Assistent: Zusammenfassung
Assistent: Zusammenfassung

Alle sichtbaren Werte (Rolle, Konto TB\tboehme, Serial) sind Demo-Daten, keine echten Geheimnisse.


REST-API für den Wizard

Zentrale REST-API für den SystemLocker-WPF-Wizard. Speichert Dongle-Material (clientseitig AES-256-GCM verschlüsselt), PCs, Metriken, Audit-Log in der Linkwerk-Datenbank. Namespace: systemlocker/v1. Basis-URL: https://www.tb-software.ch/ai/wp-json/systemlocker/v1/.

JSON UTF-8 HTTPS only HMAC-Signatur pro Request Master-Key server-seitig, verschlüsseltes Escrow

1. Auth-Modell

Zwei Auth-Klassen:

  1. Wizard (HMAC): jeder Request wird mit dem installations-eigenen apiKey signiert. Kein Token, keine Session. Header: X-SL-Installation, X-SL-Timestamp, X-SL-Nonce, X-SL-Signature.
  2. Admin: entweder WP-Admin-Login mit manage_options-Recht (Cookies) oder Header X-SL-Admin-Secret mit dem Wert aus SL_ADMIN_SECRET in wp-config.php.

1.1 HMAC-Signatur berechnen (zwei Formate parallel unterstützt)

Der Server akzeptiert beide Formate. Neue Wizards nutzen v2, ältere v1.

v2 — Spec 2.0 (empfohlen, entspricht HmacRequestSigner.cs)

timestamp = ISO-8601 UTC ("2026-07-11T00:00:00Z")
nonce     = zufaellig, mind. 16 Byte
path      = "/dongles"                             # ab Namespace
body_hex  = hex(SHA256(body))                      # leerer Body: SHA256("")
canonical = f"{METHOD}\n{path}\n{timestamp}\n{nonce}\n{body_hex}"
signature = base64(HMAC-SHA256(key=apiKey, msg=canonical))

Headers:
  X-SL-Installation : <installationId>
  X-SL-Timestamp    : <timestamp ISO-8601>
  X-SL-Nonce        : <nonce>
  X-SL-Signature    : <signature base64>

v1 — Legacy (dot-format)

timestamp = int(Unix-Sekunden UTC)
nonce     = hex(random_bytes(16))
canonical = f"{timestamp}.{nonce}.{METHOD}.{fullpath}.{body}"
# fullpath = "/systemlocker/v1/dongles"  (mit Namespace-Prefix)
# body     = raw request body (leerer String bei GET)
signature = HMAC-SHA256(key=apiKey, msg=canonical).hex()

Server-Verifikation:

  • |now - timestamp| ≤ 300s (Clock-Drift-Toleranz)
  • Nonce nicht in Replay-Cache (transient, 5-6 min)
  • hash_equals(expected, signature)

2. Endpoint-Übersicht

MethodePfadAuthZweck
GET /version Server-Version, HMAC-Formate, Feature-Flags, bekannte Metric-Types
POST /installations Admin Neue Installation anlegen (gibt apiKey + restorePassword EINMALIG zurück)
GET /installations Admin Alle Installationen listen (ohne Secrets)
DELETE/installations/{id} Admin Installation löschen (Audit)
GET /installations/{id}/restore-password Admin Klartext-Passwort abrufen (Audit + Reveal-Warnung)
POST /installations/{id}/restore-password/verify HMAC Restore-Passwort verifizieren ohne es preiszugeben
POST /installations/{id}/rotate-api-key Admin Neuen apiKey generieren, alten invalidieren
POST /pcs HMAC PC registrieren/updaten (pcId = SHA-256 der hardwareId)
POST /dongles HMAC Dongle-Record speichern (material bereits verschlüsselt)
GET /dongles HMAC Dongles der eigenen Installation (Query: pcId, role, withMaterial)
GET /dongles/{dongleId} HMAC Einzeldongle inkl. material-Chiffrat laden
DELETE/dongles/{dongleId} Admin Dongle-Record löschen (Audit)
POST /metrics HMAC Metric oder Batch ingesten
GET /metrics HMAC/AdminMetriken abfragen (pcId, type, fromUtc, toUtc)
GET /audit Admin Audit-Log lesen (Filter: installationId)

3. Datenmodelle

3.1 Installation

{
  "installationId": "b3f1a2c4-...",
  "customerName":   "Muster AG",
  "createdUtc":     "2026-07-09T10:00:00Z"
}

Beim Create zusaetzlich in der Antwort (EINMALIG, nie wieder abrufbar in Klartext):

{
  "apiKey":          "sl_...",     // Wizard-Secret fuer HMAC
  "restorePassword": "A7fH3...",   // 32 Zeichen, wird gebraucht wenn kein DPAPI-Cache
}

3.2 PC

Request POST /pcs:
{ "hostname": "TBS-PC01", "os": "Windows 10 19045",
  "machineGuid": "...", "hardwareId": "..." }

Response:
{ "pcId": "sha256-..." }

3.3 DongleRecord

{
  "dongleId":     "d1a2...",           // Server-vergeben
  "installationId":"b3f1...",
  "role":         "Main | Backup1 | ...",
  "serialNumber": 1364915896,
  "account":      { "userName": "timo", "domain": ".", "profileId": "S-1-5-21-..." },
  "pcId":         "sha256-...",
  "sessionId":    "...",
  "material": {
    "algo":       "AES-256-GCM",
    "kdf":        "Argon2id",
    "salt":       "base64",
    "nonce":      "base64",
    "tag":        "base64",
    "ciphertext": "base64"
  },
  "createdUtc":   "2026-07-09T10:06:00Z",
  "version":      1
}

Wichtig: K_d, S und das Windows-Passwort stecken NUR im material.ciphertext, verschlüsselt mit dem Restore-Key aus Argon2id(restorePassword, salt). Der Server sieht sie NIE im Klartext.

3.4 Metric

{
  "type":         "login_success | login_denied | dongle_removed | heartbeat | custom",
  "value":        1,
  "pcId":         "sha256-...",
  "dongleId":     "d1a2-... | null",
  "data":         { "beliebig": "json" },
  "timestampUtc": "2026-07-09T10:07:00Z"
}

4. Beispiele

4.1 Installation anlegen (Admin, curl)

curl -X POST "https://www.tb-software.ch/ai/wp-json/systemlocker/v1/installations" \
     -H "X-SL-Admin-Secret: $SL_ADMIN_SECRET" \
     -H "Content-Type: application/json" \
     -d '{"customerName":"Muster AG"}'

{
  "installationId": "b3f1a2c4-...",
  "customerName":   "Muster AG",
  "apiKey":         "sl_a1b2c3...",     <- unbedingt sichern
  "restorePassword":"A7fHmKq2X8...",    <- unbedingt sichern
  "createdUtc":     "2026-07-09T10:00:00Z"
}

4.2 Wizard: Dongle speichern (HMAC)

import time, secrets, hmac, hashlib, json, urllib.request

installation = "b3f1a2c4-..."
api_key      = "sl_a1b2c3..."
body_obj     = { "role":"Main", "serialNumber":1364915896, "account":{...},
                 "pcId":"sha256-...", "sessionId":"...", "material":{...},
                 "version":1 }
body = json.dumps(body_obj, separators=(",",":"))
ts   = str(int(time.time()))
nonce= secrets.token_hex(16)
path = "/systemlocker/v1/dongles"
canonical = f"{ts}.{nonce}.POST.{path}.{body}"
sig  = hmac.new(api_key.encode(), canonical.encode(), hashlib.sha256).hexdigest()

req = urllib.request.Request(
    "https://www.tb-software.ch/ai/wp-json/systemlocker/v1/dongles",
    data=body.encode(),
    headers={
      "X-SL-Installation": installation,
      "X-SL-Timestamp":    ts,
      "X-SL-Nonce":        nonce,
      "X-SL-Signature":    sig,
      "Content-Type":      "application/json",
    },
    method="POST",
)
print(urllib.request.urlopen(req).read())
# -> 201 {"dongleId":"d1a2..."}

4.3 Wizard: Restore-Passwort verifizieren (kein Klartext-Reveal)

POST https://www.tb-software.ch/ai/wp-json/systemlocker/v1/installations/{id}/restore-password/verify
Headers: X-SL-*
Body:    { "restorePassword":"A7fHmKq2X8..." }
Response: 200 { "valid": true|false }

4.4 Restore-Passwort abrufen (Admin, mit Audit)

curl "https://www.tb-software.ch/ai/wp-json/systemlocker/v1/installations/{id}/restore-password" \
     -H "X-SL-Admin-Secret: $SL_ADMIN_SECRET"

{
  "installationId":  "b3f1...",
  "restorePassword": "A7fHmKq2X8...",
  "issuedUtc":       "2026-07-09T10:22:00Z",
  "auditId":         "..."
}

5. C#-Client (WPF-Wizard)

Copy-paste-fertige Referenzimplementierung für den .NET-6+/WPF-Wizard. HMAC via System.Security.Cryptography.HMACSHA256, JSON via System.Text.Json, keine Fremd-Nugets noetig.

5.1 HMAC-Client-Klasse

using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

public sealed class SystemLockerClient
{
    private readonly HttpClient _http = new();
    private readonly string _base;      // "https://tb-software.ch/ai/wp-json/systemlocker/v1"
    private readonly string _installationId;
    private readonly string _apiKey;

    public SystemLockerClient(string baseUrl, string installationId, string apiKey)
    {
        _base = baseUrl.TrimEnd('/');
        _installationId = installationId;
        _apiKey = apiKey;
    }

    private HttpRequestMessage Sign(HttpMethod method, string routePath, string body = "")
    {
        // routePath MUSS mit Namespace-Prefix beginnen: "/systemlocker/v1/dongles"
        var ts    = ((long)(DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds).ToString();
        var nonce = Convert.ToHexString(RandomNumberGenerator.GetBytes(16)).ToLower();
        var canonical = $"{ts}.{nonce}.{method.Method}.{routePath}.{body}";
        using var h = new HMACSHA256(Encoding.UTF8.GetBytes(_apiKey));
        var sig = Convert.ToHexString(h.ComputeHash(Encoding.UTF8.GetBytes(canonical))).ToLower();

        var req = new HttpRequestMessage(method, _base + routePath.Substring("/systemlocker/v1".Length));
        req.Headers.Add("X-SL-Installation", _installationId);
        req.Headers.Add("X-SL-Timestamp",    ts);
        req.Headers.Add("X-SL-Nonce",        nonce);
        req.Headers.Add("X-SL-Signature",    sig);
        if (!string.IsNullOrEmpty(body))
            req.Content = new StringContent(body, Encoding.UTF8, "application/json");
        return req;
    }

    public async Task<JsonElement> PostAsync(string route, object payload)
    {
        var body = JsonSerializer.Serialize(payload, new JsonSerializerOptions{ WriteIndented = false });
        using var req = Sign(HttpMethod.Post, route, body);
        using var r   = await _http.SendAsync(req);
        var text = await r.Content.ReadAsStringAsync();
        return JsonDocument.Parse(text).RootElement.Clone();
    }

    public async Task<JsonElement> GetAsync(string route)
    {
        using var req = Sign(HttpMethod.Get, route);
        using var r   = await _http.SendAsync(req);
        var text = await r.Content.ReadAsStringAsync();
        return JsonDocument.Parse(text).RootElement.Clone();
    }
}

5.2 PC registrieren + Dongle speichern

var sl = new SystemLockerClient(
    "https://www.tb-software.ch/ai/wp-json/systemlocker/v1",
    installationId: "b3f1a2c4-...",
    apiKey:         "sl_a1b2c3...");

// PC upsert
var pc = await sl.PostAsync("/systemlocker/v1/pcs", new {
    hostname    = Environment.MachineName,
    os          = Environment.OSVersion.VersionString,
    machineGuid = ReadRegistryMachineGuid(),
    hardwareId  = ComputeHardwareFingerprint(),
});
string pcId = pc.GetProperty("pcId").GetString();

// Material clientseitig verschluesseln (Argon2id KDF, AES-256-GCM)
// -> siehe SystemLocker.Crypto.PackDongleMaterial(...)
var material = SystemLocker.Crypto.PackDongleMaterial(
    Kd, S, account, credentialBlob, restorePassword, installationId, dongleId);

// Dongle speichern (Server sieht Chiffrat, NIE Klartext)
var d = await sl.PostAsync("/systemlocker/v1/dongles", new {
    role         = "Main",
    serialNumber = donglePcsc.Serial,
    account      = new { userName = "timo", domain = ".", profileId = "S-1-5-..." },
    pcId         = pcId,
    sessionId    = Guid.NewGuid().ToString(),
    material     = material,   // { algo, kdf, salt, nonce, tag, ciphertext }
    version      = 1,
});
string dongleId = d.GetProperty("dongleId").GetString();

5.3 Restore-Passwort verifizieren (kein Klartext-Reveal)

var v = await sl.PostAsync(
    $"/systemlocker/v1/installations/{installationId}/restore-password/verify",
    new { restorePassword = enteredPassword });
bool valid = v.GetProperty("valid").GetBoolean();

5.4 Metrics ingesten (Batch)

await sl.PostAsync("/systemlocker/v1/metrics", new [] {
    new { type = "login_success", value = 1, pcId = pcId, timestampUtc = DateTime.UtcNow.ToString("O") },
    new { type = "heartbeat",     value = 1, pcId = pcId, timestampUtc = DateTime.UtcNow.ToString("O") },
});

5.5 Fehlerbehandlung

if (r.StatusCode == HttpStatusCode.Unauthorized) {
    var err = json.GetProperty("error").GetProperty("message").GetString();
    // Moegliche Werte: missing_hmac_headers, bad_timestamp, nonce_reused,
    //                  bad_signature, unknown_installation, api_key_decrypt_failed
    throw new SystemLockerAuthException(err);
}

6. Sicherheitsmodell (Zusammenfassung)

  • Der Server sieht Metadaten im Klartext (Kunde, PC, Rolle, Zeitstempel).
  • Der Server sieht Dongle-Material NUR als Chiffrat unter dem clientseitigen Restore-Key.
  • Der Server sieht das Restore-Passwort im Klartext beim Create (er hat es generiert) und danach nur noch verschlüsselt mit SL_MASTER_KEY. Der Master-Key liegt in wp-config.php ausserhalb der DB.
  • Vendor-Escrow (Variante A): Admin kann Restore-Passwörter wiederherstellen. Jede Ausgabe wird auditiert (tb_sl_audit).
  • Sensible Aktionen laufen über X-SL-Admin-Secret ODER WP-Admin-Session.
  • Alle Wizard-Requests werden HMAC-signiert. Replay-Protection via Nonce-Transient + 5-min-Window.

7. Fehlerformat

{ "error": { "code": "unauthorized", "message": "bad_signature" } }

HTTP-Codes: 200 OK · 201 Created · 202 Accepted · 204 No Content · 400 Bad Request · 401 Unauthorized · 403 Forbidden · 404 Not Found · 409 Conflict.