{"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\/en\/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\">The key to your PC.<\/p>\n      <h2 class=\"tbsl-hero__title\">SystemLocker<\/h2>\n      <p class=\"tbsl-hero__lead\">Passwordless Windows login and data protection via a physical USB dongle. When the dongle is plugged in, the PC automatically logs in to the correct user account, without entering a password. If the dongle is missing, the account and work data remain encrypted and inaccessible.<\/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>Passwordless &amp; convenient<\/strong><br>The dongle replaces password entry and selects the correct profile.<\/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>Fully encrypted<\/strong><br>System and work data protected with BitLocker, no boot password required.<\/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>Role-based<\/strong><br>Different dongles open different accounts and data areas, cryptographically separated.<\/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>Securely secured<\/strong><br>Central recovery of the dongles via this REST API. Copies for emergencies.<\/div>\n    <\/div>\n  <\/section>\n\n  <section class=\"tbsl-workflow\">\n    <h3>This is how it works in 3 steps<\/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 from the running application<\/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>Assistant: Welcome<\/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>Assistant: Dongle management with Finance role, provisioned<\/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>Simulated start with dongle, login successful<\/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>Simulated start without dongle, disguised error message<\/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>Assistant: Summary<\/figcaption>\n        <\/figure>\n          <\/div>\n    <p class=\"tbsl-shots__note\">All visible values (role, account <code>TB\\tboehme<\/code>, serial) are demo data, not real secrets.<\/p>\n  <\/section>\n\n  <hr class=\"tbsl-hr\">\n\n  <h2 class=\"tbsl-api-title\">REST API for the wizard<\/h2>\n  <p class=\"tbsl-docs__intro\">\n    Central REST API for the <strong>SystemLocker WPF wizard<\/strong>. Stores dongle material\n    (client-side AES-256-GCM encrypted), PCs, metrics, audit log in the Linkwerk database.\n    Namespace: <code>systemlocker\/v1<\/code>. Base 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 signature per request<\/span>\n    <span class=\"tbsl-badge tbsl-badge--warn\">Master key server-side, encrypted escrow<\/span>\n  <\/div>\n\n  <h2>. Auth model<\/h2>\n  <p>Two auth classes:<\/p>\n  <ol>\n    <li><strong>Wizard (HMAC)<\/strong>: each request is signed with the installation's own\n        <code>apiKey<\/code> . No token, no 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>: either <em>WP-Admin-Login<\/em> with <code>manage_options<\/code>-right (Cookies) or header <code>X-SL-Admin-Secret<\/code> with the value from\n        <code>SL_ADMIN_SECRET<\/code> in <code>wp-config.php<\/code>.<\/li>\n  <\/ol>\n\n  <h3>.1 calculate HMAC signature (two formats supported in parallel)<\/h3>\n  <p>The server accepts both formats. New wizards use <strong>v2<\/strong>, older <strong>v1<\/strong>.<\/p>\n\n  <h4>v2 \u2014 Spec 2.0 (recommended, corresponds to <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 verification:<\/p>\n  <ul>\n    <li><code>|now - timestamp| \u2264 300s<\/code> (Clock drift tolerance)<\/li>\n    <li>Nonce not in replay cache (transient, 5-6 min)<\/li>\n    <li><code>hash_equals(expected, signature)<\/code><\/li>\n  <\/ul>\n\n  <h2>. Endpoint overview<\/h2>\n  <table class=\"tbsl-docs__table\">\n    <thead><tr><th>Method<\/th><th>Path<\/th><th>Auth<\/th><th>Purpose<\/th><\/tr><\/thead>\n    <tbody>\n      <tr><td>GET   <\/td><td><code>\/version<\/code>                                              <\/td><td>\u2014     <\/td><td>Server version, HMAC formats, feature flags, known metric types<\/td><\/tr>\n      <tr class=\"tbsl-adm\"><td>POST  <\/td><td><code>\/installations<\/code>                                        <\/td><td>Admin <\/td><td>Create new installation (gives <em>apiKey<\/em> + <em>restorePassword<\/em> ONCE back)<\/td><\/tr>\n      <tr class=\"tbsl-adm\"><td>GET   <\/td><td><code>\/installations<\/code>                                        <\/td><td>Admin <\/td><td>List all installations (without secrets)<\/td><\/tr>\n      <tr class=\"tbsl-adm\"><td>DELETE<\/td><td><code>\/installations\/{id}<\/code>                                   <\/td><td>Admin <\/td><td>Delete installation (Audit)<\/td><\/tr>\n      <tr class=\"tbsl-adm\"><td>GET   <\/td><td><code>\/installations\/{id}\/restore-password<\/code>                  <\/td><td>Admin <\/td><td>Retrieve plaintext password (Audit + Reveal warning)<\/td><\/tr>\n      <tr><td>POST  <\/td><td><code>\/installations\/{id}\/restore-password\/verify<\/code>           <\/td><td>HMAC  <\/td><td>Verify restore password without revealing it<\/td><\/tr>\n      <tr class=\"tbsl-adm\"><td>POST  <\/td><td><code>\/installations\/{id}\/rotate-api-key<\/code>                    <\/td><td>Admin <\/td><td>Generate new apiKey, invalidate old one<\/td><\/tr>\n      <tr><td>POST  <\/td><td><code>\/pcs<\/code>                                                  <\/td><td>HMAC  <\/td><td>Register\/update PC (pcId = SHA-256 of hardwareId)<\/td><\/tr>\n      <tr><td>POST  <\/td><td><code>\/dongles<\/code>                                              <\/td><td>HMAC  <\/td><td>Save dongle record (material already encrypted)<\/td><\/tr>\n      <tr><td>GET   <\/td><td><code>\/dongles<\/code>                                              <\/td><td>HMAC  <\/td><td>Dongles of own installation (Query: pcId, role, withMaterial)<\/td><\/tr>\n      <tr><td>GET   <\/td><td><code>\/dongles\/{dongleId}<\/code>                                   <\/td><td>HMAC  <\/td><td>Load single dongle including material ciphertext<\/td><\/tr>\n      <tr class=\"tbsl-adm\"><td>DELETE<\/td><td><code>\/dongles\/{dongleId}<\/code>                                   <\/td><td>Admin <\/td><td>Delete dongle record (audit)<\/td><\/tr>\n      <tr><td>POST  <\/td><td><code>\/metrics<\/code>                                              <\/td><td>HMAC  <\/td><td>Ingest metric or batch<\/td><\/tr>\n      <tr><td>GET   <\/td><td><code>\/metrics<\/code>                                              <\/td><td>HMAC\/Admin<\/td><td>Query metrics (pcId, type, fromUtc, toUtc)<\/td><\/tr>\n      <tr class=\"tbsl-adm\"><td>GET   <\/td><td><code>\/audit<\/code>                                                <\/td><td>Admin <\/td><td>Read audit log (filter: installationId)<\/td><\/tr>\n    <\/tbody>\n  <\/table>\n\n  <h2>. Data models<\/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>Additionally in the response when creating (ONCE, never retrievable in plain text):<\/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>Important:<\/strong> <code>K_d<\/code>, <code>S<\/code> and the Windows password is ONLY stored in the <code>material.ciphertext<\/code>, encrypted with the restore key from\n    <code>Argon2id(restorePassword, salt)<\/code>. The server never sees them in plain text.<\/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>. Examples<\/h2>\n\n  <h3>.1 Create installation (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>.2 Wizard: Save dongle (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# -&gt; 201 {\"dongleId\":\"d1a2...\"}<\/code><\/pre>\n\n  <h3>.3 Wizard: Verify restore password (no plain text 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 Retrieve restore password (Admin, with 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>. C# Client (WPF Wizard)<\/h2>\n  <p>Copy-paste-ready reference implementation for .NET 6+\/WPF Wizard.\n     HMAC via <code>System.Security.Cryptography.HMACSHA256<\/code>, JSON via\n     <code>System.Text.Json<\/code>, no third-party Nugets required.<\/p>\n\n  <h3>.1 HMAC client class<\/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>.2 Register PC + save dongle<\/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>.3 Verify restore password (no plaintext 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>.4 Ingest metrics (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 Error handling<\/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>. Security model (summary)<\/h2>\n  <ul>\n    <li>The server sees <strong>Metadata<\/strong> in plain text (customer, PC, role, timestamp).<\/li>\n    <li>The server sees <strong>Dongle material ONLY as ciphertext<\/strong> under the client-side restore key.<\/li>\n    <li>The server sees the <strong>restore password<\/strong> in plain text during creation (it generated it)  \n        and afterwards only encrypted with <code>SL_MASTER_KEY<\/code>. The master key is located in\n        <code>wp-config.php<\/code> outside the DB.<\/li>\n    <li><strong>Vendor-Escrow (Variant A)<\/strong>Admin can restore restore passwords.\n        Each output is audited (<code>tb_sl_audit<\/code>).<\/li>\n    <li>Sensitive actions run via <code>X-SL-Admin-Secret<\/code> OR WP-Admin session.<\/li>\n    <li>All Wizard requests are HMAC-signed. Replay protection via Nonce transient + 5-min window.<\/li>\n  <\/ul>\n\n  <h2>. Error format<\/h2>\n<pre><code>{ \"error\": { \"code\": \"unauthorized\", \"message\": \"bad_signature\" } }<\/code><\/pre>\n  <p>HTTP codes: <code>200<\/code> OK \u00b7 <code>201<\/code> Created \u00b7\n     <code>202<\/code> Accepted \u00b7 <code>204<\/code> No Content \u00b7\n     <code>400<\/code> Bad Request \u00b7 <code>401<\/code> Unauthorized \u00b7\n     <code>403<\/code> Forbidden \u00b7 <code>404<\/code> Not Found \u00b7\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}]}}