{"id":485,"date":"2026-07-10T23:23:02","date_gmt":"2026-07-10T23:23:02","guid":{"rendered":"https:\/\/www.tb-software.ch\/ai\/systemlocker\/restapi\/"},"modified":"2026-07-10T23:23:02","modified_gmt":"2026-07-10T23:23:02","slug":"restapi","status":"publish","type":"page","link":"https:\/\/www.tb-software.ch\/ai\/systemlocker\/restapi\/","title":{"rendered":"SystemLocker, REST-API"},"content":{"rendered":"<article class=\"tbsl-docs\">\n\n  <!-- \u2500\u2500\u2500 Produkt-Hero (aus Website\/Produktbeschreibung.md) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n  <section class=\"tbsl-hero\">\n    <div class=\"tbsl-hero__text\">\n      <p class=\"tbsl-hero__slogan\">Der Schl\u00fcssel zu Ihrem PC.<\/p>\n      <h2 class=\"tbsl-hero__title\">SystemLocker<\/h2>\n      <p class=\"tbsl-hero__lead\">Passwortloser Windows-Login und Datenschutz \u00fcber 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\u00fcsselt und unzug\u00e4nglich.<\/p>\n    <\/div>\n    <div class=\"tbsl-hero__visual\">\n      <img decoding=\"async\" src=\"https:\/\/www.tb-software.ch\/ai\/wp-content\/uploads\/tb-systemlocker\/assets\/hero.svg\" alt=\"SystemLocker Hero\" loading=\"lazy\">\n    <\/div>\n  <\/section>\n\n  <section class=\"tbsl-features\">\n    <div class=\"tbsl-feature\">\n      <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.tb-software.ch\/ai\/wp-content\/uploads\/tb-systemlocker\/assets\/icon-passwortlos.svg\" alt=\"\" width=\"36\" height=\"36\">\n      <div><strong>Passwortlos &amp; komfortabel<\/strong><br>Der Dongle ersetzt die Passworteingabe und w\u00e4hlt das richtige Profil.<\/div>\n    <\/div>\n    <div class=\"tbsl-feature\">\n      <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.tb-software.ch\/ai\/wp-content\/uploads\/tb-systemlocker\/assets\/icon-bitlocker.svg\" alt=\"\" width=\"36\" height=\"36\">\n      <div><strong>Rundum verschl\u00fcsselt<\/strong><br>System und Arbeitsdaten mit BitLocker gesch\u00fctzt, kein Boot-Passwort n\u00f6tig.<\/div>\n    <\/div>\n    <div class=\"tbsl-feature\">\n      <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.tb-software.ch\/ai\/wp-content\/uploads\/tb-systemlocker\/assets\/icon-rollen.svg\" alt=\"\" width=\"36\" height=\"36\">\n      <div><strong>Rollenbasiert<\/strong><br>Verschiedene Dongles \u00f6ffnen verschiedene Konten und Datenbereiche, kryptografisch getrennt.<\/div>\n    <\/div>\n    <div class=\"tbsl-feature\">\n      <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.tb-software.ch\/ai\/wp-content\/uploads\/tb-systemlocker\/assets\/icon-cloud.svg\" alt=\"\" width=\"36\" height=\"36\">\n      <div><strong>Sicher gesichert<\/strong><br>Zentrale Wiederherstellung der Dongles \u00fcber diese REST-API. Kopien f\u00fcr den Notfall.<\/div>\n    <\/div>\n  <\/section>\n\n  <section class=\"tbsl-workflow\">\n    <h3>So funktioniert es in 3 Schritten<\/h3>\n    <img decoding=\"async\" src=\"https:\/\/www.tb-software.ch\/ai\/wp-content\/uploads\/tb-systemlocker\/assets\/workflow.svg\" alt=\"Workflow: Dongle einstecken, automatisch anmelden, arbeiten\" loading=\"lazy\">\n  <\/section>\n\n  <section class=\"tbsl-shots\">\n    <h3>Screenshots aus der laufenden Anwendung<\/h3>\n    <div class=\"tbsl-shots__grid\">\n              <figure class=\"tbsl-shots__item\">\n          <img decoding=\"async\" src=\"https:\/\/www.tb-software.ch\/ai\/wp-content\/uploads\/tb-systemlocker\/assets\/screenshots\/wizard-1-welcome.png\" alt=\"Assistent: Willkommen\" loading=\"lazy\">\n          <figcaption>Assistent: Willkommen<\/figcaption>\n        <\/figure>\n              <figure class=\"tbsl-shots__item\">\n          <img decoding=\"async\" src=\"https:\/\/www.tb-software.ch\/ai\/wp-content\/uploads\/tb-systemlocker\/assets\/screenshots\/wizard-2-dongle.png\" alt=\"Assistent: Dongle-Verwaltung mit Rolle Finanzen, provisioniert\" loading=\"lazy\">\n          <figcaption>Assistent: Dongle-Verwaltung mit Rolle Finanzen, provisioniert<\/figcaption>\n        <\/figure>\n              <figure class=\"tbsl-shots__item\">\n          <img decoding=\"async\" src=\"https:\/\/www.tb-software.ch\/ai\/wp-content\/uploads\/tb-systemlocker\/assets\/screenshots\/wizard-3-login.png\" alt=\"Simulierter Start mit Dongle, Anmeldung erfolgreich\" loading=\"lazy\">\n          <figcaption>Simulierter Start mit Dongle, Anmeldung erfolgreich<\/figcaption>\n        <\/figure>\n              <figure class=\"tbsl-shots__item\">\n          <img decoding=\"async\" src=\"https:\/\/www.tb-software.ch\/ai\/wp-content\/uploads\/tb-systemlocker\/assets\/screenshots\/wizard-4-bootfehler.png\" alt=\"Simulierter Start ohne Dongle, getarnte Fehlermeldung\" loading=\"lazy\">\n          <figcaption>Simulierter Start ohne Dongle, getarnte Fehlermeldung<\/figcaption>\n        <\/figure>\n              <figure class=\"tbsl-shots__item\">\n          <img decoding=\"async\" src=\"https:\/\/www.tb-software.ch\/ai\/wp-content\/uploads\/tb-systemlocker\/assets\/screenshots\/wizard-5-summary.png\" alt=\"Assistent: Zusammenfassung\" loading=\"lazy\">\n          <figcaption>Assistent: Zusammenfassung<\/figcaption>\n        <\/figure>\n          <\/div>\n    <p class=\"tbsl-shots__note\">Alle sichtbaren Werte (Rolle, Konto <code>TB\\tboehme<\/code>, Serial) sind Demo-Daten, keine echten Geheimnisse.<\/p>\n  <\/section>\n\n  <hr class=\"tbsl-hr\">\n\n  <h2 class=\"tbsl-api-title\">REST-API f\u00fcr den Wizard<\/h2>\n  <p class=\"tbsl-docs__intro\">\n    Zentrale REST-API f\u00fcr den <strong>SystemLocker-WPF-Wizard<\/strong>. Speichert Dongle-Material\n    (clientseitig AES-256-GCM verschl\u00fcsselt), PCs, Metriken, Audit-Log in der Linkwerk-Datenbank.\n    Namespace: <code>systemlocker\/v1<\/code>. Basis-URL:\n    <code>https:\/\/www.tb-software.ch\/ai\/wp-json\/systemlocker\/v1\/<\/code>.\n  <\/p>\n\n  <div class=\"tbsl-docs__badges\">\n    <span class=\"tbsl-badge tbsl-badge--ok\">JSON UTF-8<\/span>\n    <span class=\"tbsl-badge tbsl-badge--ok\">HTTPS only<\/span>\n    <span class=\"tbsl-badge tbsl-badge--warn\">HMAC-Signatur pro Request<\/span>\n    <span class=\"tbsl-badge tbsl-badge--warn\">Master-Key server-seitig, verschl\u00fcsseltes Escrow<\/span>\n  <\/div>\n\n  <h2>1. Auth-Modell<\/h2>\n  <p>Zwei Auth-Klassen:<\/p>\n  <ol>\n    <li><strong>Wizard (HMAC)<\/strong>: jeder Request wird mit dem installations-eigenen\n        <code>apiKey<\/code> signiert. Kein Token, keine Session. Header:\n        <code>X-SL-Installation<\/code>, <code>X-SL-Timestamp<\/code>, <code>X-SL-Nonce<\/code>,\n        <code>X-SL-Signature<\/code>.<\/li>\n    <li><strong>Admin<\/strong>: entweder <em>WP-Admin-Login<\/em> mit <code>manage_options<\/code>-Recht\n        (Cookies) oder Header <code>X-SL-Admin-Secret<\/code> mit dem Wert aus\n        <code>SL_ADMIN_SECRET<\/code> in <code>wp-config.php<\/code>.<\/li>\n  <\/ol>\n\n  <h3>1.1 HMAC-Signatur berechnen (zwei Formate parallel unterst\u00fctzt)<\/h3>\n  <p>Der Server akzeptiert beide Formate. Neue Wizards nutzen <strong>v2<\/strong>, \u00e4ltere <strong>v1<\/strong>.<\/p>\n\n  <h4>v2 \u2014 Spec 2.0 (empfohlen, entspricht <code>HmacRequestSigner.cs<\/code>)<\/h4>\n<pre><code>timestamp = ISO-8601 UTC (\"2026-07-11T00:00:00Z\")\nnonce     = zufaellig, mind. 16 Byte\npath      = \"\/dongles\"                             # ab Namespace\nbody_hex  = hex(SHA256(body))                      # leerer Body: SHA256(\"\")\ncanonical = f\"{METHOD}\\n{path}\\n{timestamp}\\n{nonce}\\n{body_hex}\"\nsignature = base64(HMAC-SHA256(key=apiKey, msg=canonical))\n\nHeaders:\n  X-SL-Installation : &lt;installationId&gt;\n  X-SL-Timestamp    : &lt;timestamp ISO-8601&gt;\n  X-SL-Nonce        : &lt;nonce&gt;\n  X-SL-Signature    : &lt;signature base64&gt;\n<\/code><\/pre>\n\n  <h4>v1 \u2014 Legacy (dot-format)<\/h4>\n<pre><code>timestamp = int(Unix-Sekunden UTC)\nnonce     = hex(random_bytes(16))\ncanonical = f\"{timestamp}.{nonce}.{METHOD}.{fullpath}.{body}\"\n# fullpath = \"\/systemlocker\/v1\/dongles\"  (mit Namespace-Prefix)\n# body     = raw request body (leerer String bei GET)\nsignature = HMAC-SHA256(key=apiKey, msg=canonical).hex()\n<\/code><\/pre>\n\n  <p>Server-Verifikation:<\/p>\n  <ul>\n    <li><code>|now - timestamp| &le; 300s<\/code> (Clock-Drift-Toleranz)<\/li>\n    <li>Nonce nicht in Replay-Cache (transient, 5-6 min)<\/li>\n    <li><code>hash_equals(expected, signature)<\/code><\/li>\n  <\/ul>\n\n  <h2>2. Endpoint-\u00dcbersicht<\/h2>\n  <table class=\"tbsl-docs__table\">\n    <thead><tr><th>Methode<\/th><th>Pfad<\/th><th>Auth<\/th><th>Zweck<\/th><\/tr><\/thead>\n    <tbody>\n      <tr><td>GET   <\/td><td><code>\/version<\/code>                                              <\/td><td>\u2014     <\/td><td>Server-Version, HMAC-Formate, Feature-Flags, bekannte Metric-Types<\/td><\/tr>\n      <tr class=\"tbsl-adm\"><td>POST  <\/td><td><code>\/installations<\/code>                                        <\/td><td>Admin <\/td><td>Neue Installation anlegen (gibt <em>apiKey<\/em> + <em>restorePassword<\/em> EINMALIG zur\u00fcck)<\/td><\/tr>\n      <tr class=\"tbsl-adm\"><td>GET   <\/td><td><code>\/installations<\/code>                                        <\/td><td>Admin <\/td><td>Alle Installationen listen (ohne Secrets)<\/td><\/tr>\n      <tr class=\"tbsl-adm\"><td>DELETE<\/td><td><code>\/installations\/{id}<\/code>                                   <\/td><td>Admin <\/td><td>Installation l\u00f6schen (Audit)<\/td><\/tr>\n      <tr class=\"tbsl-adm\"><td>GET   <\/td><td><code>\/installations\/{id}\/restore-password<\/code>                  <\/td><td>Admin <\/td><td>Klartext-Passwort abrufen (Audit + Reveal-Warnung)<\/td><\/tr>\n      <tr><td>POST  <\/td><td><code>\/installations\/{id}\/restore-password\/verify<\/code>           <\/td><td>HMAC  <\/td><td>Restore-Passwort verifizieren ohne es preiszugeben<\/td><\/tr>\n      <tr class=\"tbsl-adm\"><td>POST  <\/td><td><code>\/installations\/{id}\/rotate-api-key<\/code>                    <\/td><td>Admin <\/td><td>Neuen apiKey generieren, alten invalidieren<\/td><\/tr>\n      <tr><td>POST  <\/td><td><code>\/pcs<\/code>                                                  <\/td><td>HMAC  <\/td><td>PC registrieren\/updaten (pcId = SHA-256 der hardwareId)<\/td><\/tr>\n      <tr><td>POST  <\/td><td><code>\/dongles<\/code>                                              <\/td><td>HMAC  <\/td><td>Dongle-Record speichern (material bereits verschl\u00fcsselt)<\/td><\/tr>\n      <tr><td>GET   <\/td><td><code>\/dongles<\/code>                                              <\/td><td>HMAC  <\/td><td>Dongles der eigenen Installation (Query: pcId, role, withMaterial)<\/td><\/tr>\n      <tr><td>GET   <\/td><td><code>\/dongles\/{dongleId}<\/code>                                   <\/td><td>HMAC  <\/td><td>Einzeldongle inkl. material-Chiffrat laden<\/td><\/tr>\n      <tr class=\"tbsl-adm\"><td>DELETE<\/td><td><code>\/dongles\/{dongleId}<\/code>                                   <\/td><td>Admin <\/td><td>Dongle-Record l\u00f6schen (Audit)<\/td><\/tr>\n      <tr><td>POST  <\/td><td><code>\/metrics<\/code>                                              <\/td><td>HMAC  <\/td><td>Metric oder Batch ingesten<\/td><\/tr>\n      <tr><td>GET   <\/td><td><code>\/metrics<\/code>                                              <\/td><td>HMAC\/Admin<\/td><td>Metriken abfragen (pcId, type, fromUtc, toUtc)<\/td><\/tr>\n      <tr class=\"tbsl-adm\"><td>GET   <\/td><td><code>\/audit<\/code>                                                <\/td><td>Admin <\/td><td>Audit-Log lesen (Filter: installationId)<\/td><\/tr>\n    <\/tbody>\n  <\/table>\n\n  <h2>3. Datenmodelle<\/h2>\n\n  <h3>3.1 Installation<\/h3>\n<pre><code>{\n  \"installationId\": \"b3f1a2c4-...\",\n  \"customerName\":   \"Muster AG\",\n  \"createdUtc\":     \"2026-07-09T10:00:00Z\"\n}<\/code><\/pre>\n  <p>Beim Create zusaetzlich in der Antwort (EINMALIG, nie wieder abrufbar in Klartext):<\/p>\n<pre><code>{\n  \"apiKey\":          \"sl_...\",     \/\/ Wizard-Secret fuer HMAC\n  \"restorePassword\": \"A7fH3...\",   \/\/ 32 Zeichen, wird gebraucht wenn kein DPAPI-Cache\n}<\/code><\/pre>\n\n  <h3>3.2 PC<\/h3>\n<pre><code>Request POST \/pcs:\n{ \"hostname\": \"TBS-PC01\", \"os\": \"Windows 10 19045\",\n  \"machineGuid\": \"...\", \"hardwareId\": \"...\" }\n\nResponse:\n{ \"pcId\": \"sha256-...\" }<\/code><\/pre>\n\n  <h3>3.3 DongleRecord<\/h3>\n<pre><code>{\n  \"dongleId\":     \"d1a2...\",           \/\/ Server-vergeben\n  \"installationId\":\"b3f1...\",\n  \"role\":         \"Main | Backup1 | ...\",\n  \"serialNumber\": 1364915896,\n  \"account\":      { \"userName\": \"timo\", \"domain\": \".\", \"profileId\": \"S-1-5-21-...\" },\n  \"pcId\":         \"sha256-...\",\n  \"sessionId\":    \"...\",\n  \"material\": {\n    \"algo\":       \"AES-256-GCM\",\n    \"kdf\":        \"Argon2id\",\n    \"salt\":       \"base64\",\n    \"nonce\":      \"base64\",\n    \"tag\":        \"base64\",\n    \"ciphertext\": \"base64\"\n  },\n  \"createdUtc\":   \"2026-07-09T10:06:00Z\",\n  \"version\":      1\n}<\/code><\/pre>\n  <p><strong>Wichtig:<\/strong> <code>K_d<\/code>, <code>S<\/code> und das Windows-Passwort stecken NUR\n    im <code>material.ciphertext<\/code>, verschl\u00fcsselt mit dem Restore-Key aus\n    <code>Argon2id(restorePassword, salt)<\/code>. Der Server sieht sie NIE im Klartext.<\/p>\n\n  <h3>3.4 Metric<\/h3>\n<pre><code>{\n  \"type\":         \"login_success | login_denied | dongle_removed | heartbeat | custom\",\n  \"value\":        1,\n  \"pcId\":         \"sha256-...\",\n  \"dongleId\":     \"d1a2-... | null\",\n  \"data\":         { \"beliebig\": \"json\" },\n  \"timestampUtc\": \"2026-07-09T10:07:00Z\"\n}<\/code><\/pre>\n\n  <h2>4. Beispiele<\/h2>\n\n  <h3>4.1 Installation anlegen (Admin, curl)<\/h3>\n<pre><code>curl -X POST \"https:\/\/www.tb-software.ch\/ai\/wp-json\/systemlocker\/v1\/installations\" \\\n     -H \"X-SL-Admin-Secret: $SL_ADMIN_SECRET\" \\\n     -H \"Content-Type: application\/json\" \\\n     -d '{\"customerName\":\"Muster AG\"}'\n\n{\n  \"installationId\": \"b3f1a2c4-...\",\n  \"customerName\":   \"Muster AG\",\n  \"apiKey\":         \"sl_a1b2c3...\",     &lt;- unbedingt sichern\n  \"restorePassword\":\"A7fHmKq2X8...\",    &lt;- unbedingt sichern\n  \"createdUtc\":     \"2026-07-09T10:00:00Z\"\n}<\/code><\/pre>\n\n  <h3>4.2 Wizard: Dongle speichern (HMAC)<\/h3>\n<pre><code>import time, secrets, hmac, hashlib, json, urllib.request\n\ninstallation = \"b3f1a2c4-...\"\napi_key      = \"sl_a1b2c3...\"\nbody_obj     = { \"role\":\"Main\", \"serialNumber\":1364915896, \"account\":{...},\n                 \"pcId\":\"sha256-...\", \"sessionId\":\"...\", \"material\":{...},\n                 \"version\":1 }\nbody = json.dumps(body_obj, separators=(\",\",\":\"))\nts   = str(int(time.time()))\nnonce= secrets.token_hex(16)\npath = \"\/systemlocker\/v1\/dongles\"\ncanonical = f\"{ts}.{nonce}.POST.{path}.{body}\"\nsig  = hmac.new(api_key.encode(), canonical.encode(), hashlib.sha256).hexdigest()\n\nreq = urllib.request.Request(\n    \"https:\/\/www.tb-software.ch\/ai\/wp-json\/systemlocker\/v1\/dongles\",\n    data=body.encode(),\n    headers={\n      \"X-SL-Installation\": installation,\n      \"X-SL-Timestamp\":    ts,\n      \"X-SL-Nonce\":        nonce,\n      \"X-SL-Signature\":    sig,\n      \"Content-Type\":      \"application\/json\",\n    },\n    method=\"POST\",\n)\nprint(urllib.request.urlopen(req).read())\n# -> 201 {\"dongleId\":\"d1a2...\"}<\/code><\/pre>\n\n  <h3>4.3 Wizard: Restore-Passwort verifizieren (kein Klartext-Reveal)<\/h3>\n<pre><code>POST https:\/\/www.tb-software.ch\/ai\/wp-json\/systemlocker\/v1\/installations\/{id}\/restore-password\/verify\nHeaders: X-SL-*\nBody:    { \"restorePassword\":\"A7fHmKq2X8...\" }\nResponse: 200 { \"valid\": true|false }<\/code><\/pre>\n\n  <h3>4.4 Restore-Passwort abrufen (Admin, mit Audit)<\/h3>\n<pre><code>curl \"https:\/\/www.tb-software.ch\/ai\/wp-json\/systemlocker\/v1\/installations\/{id}\/restore-password\" \\\n     -H \"X-SL-Admin-Secret: $SL_ADMIN_SECRET\"\n\n{\n  \"installationId\":  \"b3f1...\",\n  \"restorePassword\": \"A7fHmKq2X8...\",\n  \"issuedUtc\":       \"2026-07-09T10:22:00Z\",\n  \"auditId\":         \"...\"\n}\n<\/code><\/pre>\n\n  <h2>5. C#-Client (WPF-Wizard)<\/h2>\n  <p>Copy-paste-fertige Referenzimplementierung f\u00fcr den .NET-6+\/WPF-Wizard.\n     HMAC via <code>System.Security.Cryptography.HMACSHA256<\/code>, JSON via\n     <code>System.Text.Json<\/code>, keine Fremd-Nugets noetig.<\/p>\n\n  <h3>5.1 HMAC-Client-Klasse<\/h3>\n<pre><code>using System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Security.Cryptography;\nusing System.Text;\nusing System.Text.Json;\n\npublic sealed class SystemLockerClient\n{\n    private readonly HttpClient _http = new();\n    private readonly string _base;      \/\/ \"https:\/\/tb-software.ch\/ai\/wp-json\/systemlocker\/v1\"\n    private readonly string _installationId;\n    private readonly string _apiKey;\n\n    public SystemLockerClient(string baseUrl, string installationId, string apiKey)\n    {\n        _base = baseUrl.TrimEnd('\/');\n        _installationId = installationId;\n        _apiKey = apiKey;\n    }\n\n    private HttpRequestMessage Sign(HttpMethod method, string routePath, string body = \"\")\n    {\n        \/\/ routePath MUSS mit Namespace-Prefix beginnen: \"\/systemlocker\/v1\/dongles\"\n        var ts    = ((long)(DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds).ToString();\n        var nonce = Convert.ToHexString(RandomNumberGenerator.GetBytes(16)).ToLower();\n        var canonical = $\"{ts}.{nonce}.{method.Method}.{routePath}.{body}\";\n        using var h = new HMACSHA256(Encoding.UTF8.GetBytes(_apiKey));\n        var sig = Convert.ToHexString(h.ComputeHash(Encoding.UTF8.GetBytes(canonical))).ToLower();\n\n        var req = new HttpRequestMessage(method, _base + routePath.Substring(\"\/systemlocker\/v1\".Length));\n        req.Headers.Add(\"X-SL-Installation\", _installationId);\n        req.Headers.Add(\"X-SL-Timestamp\",    ts);\n        req.Headers.Add(\"X-SL-Nonce\",        nonce);\n        req.Headers.Add(\"X-SL-Signature\",    sig);\n        if (!string.IsNullOrEmpty(body))\n            req.Content = new StringContent(body, Encoding.UTF8, \"application\/json\");\n        return req;\n    }\n\n    public async Task&lt;JsonElement&gt; PostAsync(string route, object payload)\n    {\n        var body = JsonSerializer.Serialize(payload, new JsonSerializerOptions{ WriteIndented = false });\n        using var req = Sign(HttpMethod.Post, route, body);\n        using var r   = await _http.SendAsync(req);\n        var text = await r.Content.ReadAsStringAsync();\n        return JsonDocument.Parse(text).RootElement.Clone();\n    }\n\n    public async Task&lt;JsonElement&gt; GetAsync(string route)\n    {\n        using var req = Sign(HttpMethod.Get, route);\n        using var r   = await _http.SendAsync(req);\n        var text = await r.Content.ReadAsStringAsync();\n        return JsonDocument.Parse(text).RootElement.Clone();\n    }\n}<\/code><\/pre>\n\n  <h3>5.2 PC registrieren + Dongle speichern<\/h3>\n<pre><code>var sl = new SystemLockerClient(\n    \"https:\/\/www.tb-software.ch\/ai\/wp-json\/systemlocker\/v1\",\n    installationId: \"b3f1a2c4-...\",\n    apiKey:         \"sl_a1b2c3...\");\n\n\/\/ PC upsert\nvar pc = await sl.PostAsync(\"\/systemlocker\/v1\/pcs\", new {\n    hostname    = Environment.MachineName,\n    os          = Environment.OSVersion.VersionString,\n    machineGuid = ReadRegistryMachineGuid(),\n    hardwareId  = ComputeHardwareFingerprint(),\n});\nstring pcId = pc.GetProperty(\"pcId\").GetString();\n\n\/\/ Material clientseitig verschluesseln (Argon2id KDF, AES-256-GCM)\n\/\/ -&gt; siehe SystemLocker.Crypto.PackDongleMaterial(...)\nvar material = SystemLocker.Crypto.PackDongleMaterial(\n    Kd, S, account, credentialBlob, restorePassword, installationId, dongleId);\n\n\/\/ Dongle speichern (Server sieht Chiffrat, NIE Klartext)\nvar d = await sl.PostAsync(\"\/systemlocker\/v1\/dongles\", new {\n    role         = \"Main\",\n    serialNumber = donglePcsc.Serial,\n    account      = new { userName = \"timo\", domain = \".\", profileId = \"S-1-5-...\" },\n    pcId         = pcId,\n    sessionId    = Guid.NewGuid().ToString(),\n    material     = material,   \/\/ { algo, kdf, salt, nonce, tag, ciphertext }\n    version      = 1,\n});\nstring dongleId = d.GetProperty(\"dongleId\").GetString();<\/code><\/pre>\n\n  <h3>5.3 Restore-Passwort verifizieren (kein Klartext-Reveal)<\/h3>\n<pre><code>var v = await sl.PostAsync(\n    $\"\/systemlocker\/v1\/installations\/{installationId}\/restore-password\/verify\",\n    new { restorePassword = enteredPassword });\nbool valid = v.GetProperty(\"valid\").GetBoolean();<\/code><\/pre>\n\n  <h3>5.4 Metrics ingesten (Batch)<\/h3>\n<pre><code>await sl.PostAsync(\"\/systemlocker\/v1\/metrics\", new [] {\n    new { type = \"login_success\", value = 1, pcId = pcId, timestampUtc = DateTime.UtcNow.ToString(\"O\") },\n    new { type = \"heartbeat\",     value = 1, pcId = pcId, timestampUtc = DateTime.UtcNow.ToString(\"O\") },\n});<\/code><\/pre>\n\n  <h3>5.5 Fehlerbehandlung<\/h3>\n<pre><code>if (r.StatusCode == HttpStatusCode.Unauthorized) {\n    var err = json.GetProperty(\"error\").GetProperty(\"message\").GetString();\n    \/\/ Moegliche Werte: missing_hmac_headers, bad_timestamp, nonce_reused,\n    \/\/                  bad_signature, unknown_installation, api_key_decrypt_failed\n    throw new SystemLockerAuthException(err);\n}<\/code><\/pre>\n\n  <h2>6. Sicherheitsmodell (Zusammenfassung)<\/h2>\n  <ul>\n    <li>Der Server sieht <strong>Metadaten<\/strong> im Klartext (Kunde, PC, Rolle, Zeitstempel).<\/li>\n    <li>Der Server sieht <strong>Dongle-Material NUR als Chiffrat<\/strong> unter dem clientseitigen Restore-Key.<\/li>\n    <li>Der Server sieht das <strong>Restore-Passwort<\/strong> im Klartext beim Create (er hat es generiert)\n        und danach nur noch verschl\u00fcsselt mit <code>SL_MASTER_KEY<\/code>. Der Master-Key liegt in\n        <code>wp-config.php<\/code> ausserhalb der DB.<\/li>\n    <li><strong>Vendor-Escrow (Variante A)<\/strong>: Admin kann Restore-Passw\u00f6rter wiederherstellen.\n        Jede Ausgabe wird auditiert (<code>tb_sl_audit<\/code>).<\/li>\n    <li>Sensible Aktionen laufen \u00fcber <code>X-SL-Admin-Secret<\/code> ODER WP-Admin-Session.<\/li>\n    <li>Alle Wizard-Requests werden HMAC-signiert. Replay-Protection via Nonce-Transient + 5-min-Window.<\/li>\n  <\/ul>\n\n  <h2>7. Fehlerformat<\/h2>\n<pre><code>{ \"error\": { \"code\": \"unauthorized\", \"message\": \"bad_signature\" } }<\/code><\/pre>\n  <p>HTTP-Codes: <code>200<\/code> OK &middot; <code>201<\/code> Created &middot;\n     <code>202<\/code> Accepted &middot; <code>204<\/code> No Content &middot;\n     <code>400<\/code> Bad Request &middot; <code>401<\/code> Unauthorized &middot;\n     <code>403<\/code> Forbidden &middot; <code>404<\/code> Not Found &middot;\n     <code>409<\/code> Conflict.<\/p>\n<\/article>\n    \n","protected":false},"excerpt":{"rendered":"","protected":false},"author":0,"featured_media":0,"parent":473,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-485","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.tb-software.ch\/ai\/index.php?rest_route=\/wp\/v2\/pages\/485","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tb-software.ch\/ai\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.tb-software.ch\/ai\/index.php?rest_route=\/wp\/v2\/types\/page"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tb-software.ch\/ai\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=485"}],"version-history":[{"count":0,"href":"https:\/\/www.tb-software.ch\/ai\/index.php?rest_route=\/wp\/v2\/pages\/485\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/www.tb-software.ch\/ai\/index.php?rest_route=\/wp\/v2\/pages\/473"}],"wp:attachment":[{"href":"https:\/\/www.tb-software.ch\/ai\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=485"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}