{"id":13034,"date":"2026-08-28T13:25:09","date_gmt":"2026-08-28T11:25:09","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/narzedzia\/web-bot-auth-signature-inspector\/"},"modified":"2026-08-28T13:25:09","modified_gmt":"2026-08-28T11:25:09","slug":"web-bot-auth-signature-inspector","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/web-bot-auth-signature-inspector\/","title":{"rendered":"Inspektor podpis\u00f3w Web Bot Auth i kalkulator zakres\u00f3w adres\u00f3w"},"content":{"rendered":"<div class=\"gtm-analyser-container\" style=\"background: var(--bg-panel, #1e1e24); padding: 25px; border-radius: 8px; border: 1px solid var(--border, #2a2a35);\">\n<p style=\"color: var(--text-secondary, #a0a0b0); margin-bottom: 20px;\">Przegl\u0105darka agentowa wygl\u0105da w raporcie jak cz\u0142owiek, bo jest prawdziwym silnikiem przegl\u0105darki. Na pytanie, kto naprawd\u0119 pyta, odpowiadaj\u0105 dwa mechanizmy, a ich utrzymanie kosztuje skrajnie r\u00f3\u017cnie. Pierwszy to podpis, kt\u00f3ry agent przynosi ze sob\u0105; tutaj odtwarzane s\u0105 dok\u0142adnie te bajty, kt\u00f3re zosta\u0142y podpisane, odcisk klucza por\u00f3wnywany z keyid, a podpis sprawdzany. Drugi to lista adres\u00f3w publikowana przez operatora; tutaj liczone jest, co taka lista obejmuje, i sprawdzane, czy adres do niej wpada.<\/p>\n<div style=\"margin-bottom: 14px;\">\n        <label for=\"wb-header\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Nag\u0142\u00f3wki: Signature-Agent, Signature-Input, Signature<\/label><br \/>\n        <textarea id=\"wb-header\" rows=\"7\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace; font-size: 0.82rem;\"><\/textarea>\n    <\/div>\n<div style=\"display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; margin-bottom: 14px;\">\n<div>\n            <label for=\"wb-authority\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Authority, do kt\u00f3rego posz\u0142o \u017c\u0105danie<\/label><br \/>\n            <input id=\"wb-authority\" type=\"text\" value=\"www.example.com\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace;\">\n        <\/div>\n<div>\n            <label for=\"wb-adresse\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Adres do sprawdzenia<\/label><br \/>\n            <input id=\"wb-adresse\" type=\"text\" value=\"192.0.2.45\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace;\">\n        <\/div>\n<\/p><\/div>\n<div style=\"display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; margin-bottom: 14px;\">\n<div>\n            <label for=\"wb-jwk\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Klucz publiczny jako JWK lub JWKS<\/label><br \/>\n            <textarea id=\"wb-jwk\" rows=\"5\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace; font-size: 0.82rem;\"><\/textarea>\n        <\/div>\n<div>\n            <label for=\"wb-praefixe\" style=\"color: var(--text-secondary, #a0a0b0); display: block; font-size: 0.85rem; margin-bottom: 5px;\">Opublikowane prefiksy, po jednym w wierszu<\/label><br \/>\n            <textarea id=\"wb-praefixe\" rows=\"5\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); color: var(--text-primary, #e8e8ee); border-radius: 6px; box-sizing: border-box; font-family: monospace; font-size: 0.82rem;\"><\/textarea>\n        <\/div>\n<\/p><\/div>\n<p style=\"color: var(--text-secondary, #a0a0b0); font-size: 0.82rem; margin-bottom: 16px;\">Wszystko liczy si\u0119 w przegl\u0105darce; \u017caden nag\u0142\u00f3wek, klucz ani adres nie jest przesy\u0142any ani zapisywany. Przyk\u0142ad nosi sta\u0142e znaczniki czasu, aby jego podpis pozosta\u0142 sprawdzalny &#8211; dlatego zg\u0142asza zarazem, \u017ce wygas\u0142. W\u0142a\u015bnie ten rozdzia\u0142 jest tu u\u017cyteczny: poprawny kryptograficznie i wa\u017cny obecnie to dwa odr\u0119bne ustalenia.<\/p>\n<p>    <button id=\"wb-btn\" class=\"button\" style=\"background: var(--accent, #7ee787); color: var(--on-accent, #0b1114); border: none; padding: 12px 24px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Sprawd\u017a<\/button><\/p>\n<div id=\"wb-ausgabe\" style=\"margin-top: 22px;\"><\/div>\n<\/div>\n<p><script>\n(function () {\n    'use strict';<\/p>\n<p>    const T = {\"einleitung\":\"Przegl\u0105darka agentowa wygl\u0105da w raporcie jak cz\u0142owiek, bo jest prawdziwym silnikiem przegl\u0105darki. Na pytanie, kto naprawd\u0119 pyta, odpowiadaj\u0105 dwa mechanizmy, a ich utrzymanie kosztuje skrajnie r\u00f3\u017cnie. Pierwszy to podpis, kt\u00f3ry agent przynosi ze sob\u0105; tutaj odtwarzane s\u0105 dok\u0142adnie te bajty, kt\u00f3re zosta\u0142y podpisane, odcisk klucza por\u00f3wnywany z keyid, a podpis sprawdzany. Drugi to lista adres\u00f3w publikowana przez operatora; tutaj liczone jest, co taka lista obejmuje, i sprawdzane, czy adres do niej wpada.\",\"label_header\":\"Nag\u0142\u00f3wki: Signature-Agent, Signature-Input, Signature\",\"label_authority\":\"Authority, do kt\u00f3rego posz\u0142o \u017c\u0105danie\",\"label_adresse\":\"Adres do sprawdzenia\",\"label_jwk\":\"Klucz publiczny jako JWK lub JWKS\",\"label_praefixe\":\"Opublikowane prefiksy, po jednym w wierszu\",\"datenschutz\":\"Wszystko liczy si\u0119 w przegl\u0105darce; \u017caden nag\u0142\u00f3wek, klucz ani adres nie jest przesy\u0142any ani zapisywany. Przyk\u0142ad nosi sta\u0142e znaczniki czasu, aby jego podpis pozosta\u0142 sprawdzalny - dlatego zg\u0142asza zarazem, \u017ce wygas\u0142. W\u0142a\u015bnie ten rozdzia\u0142 jest tu u\u017cyteczny: poprawny kryptograficznie i wa\u017cny obecnie to dwa odr\u0119bne ustalenia.\",\"knopf\":\"Sprawd\u017a\",\"beispiel_header\":\"Signature-Agent: \\u0022https:\\\/\\\/signer.example.com\\u0022\\nSignature-Input: sig1=(\\u0022@authority\\u0022 \\u0022signature-agent\\u0022);created=1787000000;expires=1787003600;keyid=\\u0022ulMr0bd_FrTSdSiYtpOpPZ0UrGNldXBNi9wDof-iKpc\\u0022;alg=\\u0022ed25519\\u0022;tag=\\u0022web-bot-auth\\u0022;nonce=\\u0022bHctYmVpc3BpZWwtbm9uY2U\\u0022\\nSignature: sig1=:HPpAewTOKvc4nY\\\/dONz6R848m3D8Zf8poPQMu6bNGByXPzztOSHT7tqjx5\\\/THiWk7r\\\/u0lKzn7wPvdPsK3NaAg==:\",\"beispiel_jwk\":\"{\\u0022crv\\u0022:\\u0022Ed25519\\u0022,\\u0022kty\\u0022:\\u0022OKP\\u0022,\\u0022x\\u0022:\\u0022AYQxRK7FjZW-qXcFpYAer7j-nC9VPsKM-5oCT7Vb3kw\\u0022}\",\"beispiel_praefixe\":\"192.0.2.0\\\/24\\n198.51.100.0\\\/24\\n203.0.113.0\\\/25\\n203.0.113.128\\\/25\\n2001:db8::\\\/32\",\"h_agent\":\"Kto twierdzi, \u017ce pyta\",\"zeile_agent\":\"Host z Signature-Agent\",\"zeile_verzeichnis\":\"Gdzie powinien le\u017ce\u0107 jego zestaw kluczy\",\"wert_keiner\":\"brak\",\"fehler_kein_signature_input\":\"Nie znaleziono nag\u0142\u00f3wka Signature-Input. Bez niego nie ma czego odtwarza\u0107 - obj\u0119te sk\u0142adniki i parametry stoj\u0105 wy\u0142\u0105cznie tam.\",\"fehler_kein_signature\":\"Nie znaleziono nag\u0142\u00f3wka Signature. Baz\u0119 da si\u0119 jeszcze odtworzy\u0107, ale nie da si\u0119 nic sprawdzi\u0107.\",\"fehler_signature_input_unlesbar\":\"Nag\u0142\u00f3wka Signature-Input nie da si\u0119 odczyta\u0107 jako s\u0142ownika p\u00f3l strukturalnych.\",\"fehler_signature_unlesbar\":\"Nag\u0142\u00f3wka Signature nie da si\u0119 odczyta\u0107 jako s\u0142ownika p\u00f3l strukturalnych.\",\"h_signatur\":\"Podpis\",\"zeile_komponenten\":\"Obj\u0119te sk\u0142adniki\",\"zeile_keyid\":\"keyid\",\"zeile_alg\":\"alg\",\"zeile_tag\":\"tag\",\"zeile_lebensdauer\":\"Okno wa\u017cno\u015bci\",\"zeile_signaturlaenge\":\"D\u0142ugo\u015b\u0107 podpisu\",\"einheit_sekunden\":\"sekund\",\"einheit_bytes\":\"bajt\u00f3w\",\"h_basis\":\"Baza podpisu, odtworzona\",\"basis_fehlend\":\"Nie uda\u0142o si\u0119 ustali\u0107 warto\u015bci dla:\",\"pruefung_laeuft\":\"Klucz jest sprawdzany...\",\"zeile_abdruck\":\"Odcisk klucza, RFC 7638\",\"zeile_abdruck_urteil\":\"Wobec keyid\",\"abdruck_kein_jwk\":\"nie podano klucza\",\"abdruck_kein_okp\":\"to nie jest klucz OKP z crv i x\",\"abdruck_kein_subtle\":\"ta przegl\u0105darka nie oferuje WebCrypto\",\"abdruck_passt\":\"zgadza si\u0119 - to ten klucz, kt\u00f3ry nazywa podpis\",\"abdruck_passt_nicht\":\"nie zgadza si\u0119 z keyid: inny klucz\",\"abdruck_ohne_keyid\":\"podpis nie podaje keyid do por\u00f3wnania\",\"zeile_signaturpruefung\":\"Sprawdzenie podpisu\",\"signatur_gueltig\":\"wa\u017cny dla tej bazy i tego klucza\",\"signatur_ungueltig\":\"nie zgadza si\u0119\",\"pruefung_kein_subtle\":\"ta przegl\u0105darka nie oferuje WebCrypto\",\"pruefung_kein_ed25519\":\"ta przegl\u0105darka nie ma Ed25519 w WebCrypto; odcisk powy\u017cej mimo to sprawdzono\",\"pruefung_unvollstaendig\":\"brakuje klucza albo podpisu\",\"h_befunde\":\"Ustalenia\",\"befund_kein_tag\":\"Tag nie brzmi web-bot-auth. Podpis bez tego tagu mo\u017ce by\u0107 podpisem wiadomo\u015bci HTTP do czego\u015b zupe\u0142nie innego.\",\"befund_keyid_fehlt\":\"Brak keyid. Bez niego strona sprawdzaj\u0105ca nie wie, kt\u00f3ry klucz z zestawu pobra\u0107.\",\"befund_alg_fremd\":\"Alg podaje co\u015b innego ni\u017c ed25519. Web Bot Auth jest okre\u015blone wok\u00f3\u0142 Ed25519.\",\"befund_created_fehlt\":\"Brak parametru created, wi\u0119c wieku podpisu nie da si\u0119 ustali\u0107.\",\"befund_kein_ablauf\":\"Brak parametru expires. Podpis bez ko\u0144ca da si\u0119 odtwarza\u0107 tak d\u0142ugo, jak \u017cyje klucz.\",\"befund_abgelaufen\":\"Okno wa\u017cno\u015bci min\u0119\u0142o. Podpis mo\u017ce mimo to zgadza\u0107 si\u0119 kryptograficznie - to inne pytanie ni\u017c to, czy nale\u017cy go teraz przyj\u0105\u0107.\",\"befund_noch_nicht_gueltig\":\"Znacznik created le\u017cy w przysz\u0142o\u015bci, co zwykle wskazuje na \u017ale chodz\u0105cy zegar, a nie na atak.\",\"befund_fenster_weit\":\"Okno wa\u017cno\u015bci jest d\u0142u\u017csze ni\u017c godzina. Im szersze, tym d\u0142u\u017cej przechwycony podpis pozostaje u\u017cyteczny.\",\"befund_authority_ungedeckt\":\"@authority nie jest obj\u0119te. Ten sam podpis by\u0142by wtedy wa\u017cny wobec innego hosta - a w\u0142a\u015bnie temu zbi\u00f3r obj\u0119tych sk\u0142adnik\u00f3w ma zapobiega\u0107.\",\"befund_agent_ungedeckt\":\"Nag\u0142\u00f3wek Signature-Agent jest obecny, ale nieobj\u0119ty podpisem. Da si\u0119 go podmieni\u0107 bez \u0142amania czegokolwiek - deklarowane pochodzenie klucza pozostaje wi\u0119c niechronione.\",\"befund_agent_fehlt\":\"Brak nag\u0142\u00f3wka Signature-Agent. Zestaw kluczy trzeba wtedy znale\u017a\u0107 inaczej.\",\"befund_nonce_fehlt\":\"Brak nonce. Powt\u00f3rzenie w oknie wa\u017cno\u015bci trzeba wtedy wychwyci\u0107 gdzie indziej.\",\"befund_laenge_ungewoehnlich\":\"Podpis nie ma 64 bajt\u00f3w. Podpis Ed25519 ma zawsze dok\u0142adnie tyle.\",\"befund_signatur_fehlt\":\"Dla tej etykiety nie znaleziono w nag\u0142\u00f3wku Signature \u017cadnych bajt\u00f3w podpisu.\",\"befund_basis_unvollstaendig\":\"Co najmniej jednego obj\u0119tego sk\u0142adnika nie da\u0142o si\u0119 ustali\u0107 z danych wej\u015bciowych; baza powy\u017cej jest przez to niepe\u0142na, a sprawdzenie wobec niej nie powiod\u0142oby si\u0119 ju\u017c z tego powodu.\",\"h_netz\":\"Dow\u00f3d adresowy\",\"zeile_praefixe\":\"Odczytane prefiksy\",\"zeile_v4adressen\":\"Obj\u0119te adresy IPv4\",\"zeile_v6\":\"Obj\u0119te adresy IPv6\",\"zeile_ungueltig\":\"Nieczytelne jako prefiks\",\"zeile_treffer\":\"Adres w li\u015bcie\",\"treffer_ja\":\"tak, w\",\"treffer_nein\":\"nie\",\"treffer_unlesbar\":\"adresu nie da si\u0119 odczyta\u0107\",\"h_vergleich\":\"Co r\u00f3\u017cni oba dowody\",\"vergleich_pflege\":\"List\u0119 adres\u00f3w trzeba pobra\u0107 na nowo, gdy tylko operator j\u0105 zmieni, a operator potrafi zwielokrotni\u0107 swoj\u0105 pul\u0119 z dnia na dzie\u0144. Dow\u00f3d kluczowy takiej piel\u0119gnacji nie wymaga: zestaw kluczy pobiera si\u0119 raz i trzyma wobec keyid.\",\"vergleich_beweis\":\"Adres \u015bwiadczy o tym, \u017ce \u017c\u0105danie przysz\u0142o z sieci, kt\u00f3r\u0105 operator dysponuje. Podpis \u015bwiadczy o tym, \u017ce ktokolwiek trzyma klucz prywatny, wytworzy\u0142 dok\u0142adnie to \u017c\u0105danie. Drugie jest twierdzeniem o \u017c\u0105daniu, pierwsze tylko o jego drodze.\",\"vergleich_absicht\":\"\u017baden z nich nie \u015bwiadczy o zamiarze. Sprawdzony podpis m\u00f3wi, kto pyta, a nie co stanie si\u0119 z odpowiedzi\u0105 - i w tym miejscu mechanizm si\u0119 ko\u0144czy.\",\"tausender\":\" \"};<\/p>\n<p>    \/\/ =========================================================================\n    \/\/ Strukturierte Felder, RFC 8941\n    \/\/ =========================================================================\n    \/\/\n    \/\/ Gebraucht wird nur der Ausschnitt, den RFC 9421 benutzt: ein Woerterbuch,\n    \/\/ dessen Werte innere Listen aus Zeichenketten oder einzelne Bytefolgen\n    \/\/ sind, jeweils mit Parametern. Ein vollstaendiger Parser waere hier\n    \/\/ Ballast; was fehlt, wird als Fehler gemeldet statt stillschweigend\n    \/\/ falsch gedeutet.<\/p>\n<p>    function Leser(text) {\n        this.s = String(text == null ? '' : text);\n        this.i = 0;\n    }\n    Leser.prototype.rest = function () { return this.s.slice(this.i); };\n    Leser.prototype.ende = function () { return this.i >= this.s.length; };\n    Leser.prototype.zeichen = function () { return this.s.charAt(this.i); };\n    Leser.prototype.ows = function () {\n        while (!this.ende() && (this.zeichen() === ' ' || this.zeichen() === '\\t')) { this.i++; }\n    };<\/p>\n<p>    function istZiffer(c) { return c >= '0' && c <= '9'; }\n    function istBuchstabe(c) {\n        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');\n    }\n\n    \/** Zeichenkette mit Anfuehrungszeichen, Fluchtzeichen nur vor \" und Rueckstrich. *\/\n    function leseZeichenkette(l) {\n        if (l.zeichen() !== '\"') { throw new Error('erwartet: Zeichenkette'); }\n        l.i++;\n        var raus = '';\n        while (!l.ende()) {\n            var c = l.s.charAt(l.i++);\n            if (c === '\\\\') {\n                if (l.ende()) { throw new Error('Fluchtzeichen am Ende'); }\n                var n = l.s.charAt(l.i++);\n                if (n !== '\"' &#038;&#038; n !== '\\\\') { throw new Error('unerlaubtes Fluchtzeichen'); }\n                raus += n;\n            } else if (c === '\"') {\n                return raus;\n            } else {\n                raus += c;\n            }\n        }\n        throw new Error('Zeichenkette nicht geschlossen');\n    }\n\n    function leseToken(l) {\n        var start = l.i;\n        while (!l.ende() &#038;&#038; \/[A-Za-z0-9:\\\/!#$%&#038;'*+\\-.^_`|~]\/.test(l.zeichen())) { l.i++; }\n        if (l.i === start) { throw new Error('erwartet: Token'); }\n        return l.s.slice(start, l.i);\n    }\n\n    function leseZahl(l) {\n        var start = l.i;\n        if (l.zeichen() === '-') { l.i++; }\n        while (!l.ende() &#038;&#038; (istZiffer(l.zeichen()) || l.zeichen() === '.')) { l.i++; }\n        var roh = l.s.slice(start, l.i);\n        if (!\/^-?\\d+(\\.\\d+)?$\/.test(roh)) { throw new Error('erwartet: Zahl'); }\n        return Number(roh);\n    }\n\n    function leseBytefolge(l) {\n        if (l.zeichen() !== ':') { throw new Error('erwartet: Bytefolge'); }\n        l.i++;\n        var start = l.i;\n        while (!l.ende() &#038;&#038; l.zeichen() !== ':') { l.i++; }\n        if (l.ende()) { throw new Error('Bytefolge nicht geschlossen'); }\n        var roh = l.s.slice(start, l.i);\n        l.i++;\n        return { typ: 'bytes', wert: roh };\n    }\n\n    function leseWert(l) {\n        var c = l.zeichen();\n        if (c === '\"') { return { typ: 'text', wert: leseZeichenkette(l) }; }\n        if (c === ':') { return leseBytefolge(l); }\n        if (c === '?') {\n            l.i++;\n            var b = l.s.charAt(l.i++);\n            if (b !== '0' &#038;&#038; b !== '1') { throw new Error('erwartet: Wahrheitswert'); }\n            return { typ: 'bool', wert: b === '1' };\n        }\n        if (istZiffer(c) || c === '-') { return { typ: 'zahl', wert: leseZahl(l) }; }\n        return { typ: 'token', wert: leseToken(l) };\n    }\n\n    \/** Parameter: ;name=wert oder ;name (dann true). Reihenfolge bleibt erhalten. *\/\n    function leseParameter(l) {\n        var liste = [];\n        while (!l.ende() &#038;&#038; l.zeichen() === ';') {\n            l.i++;\n            l.ows();\n            var name = leseSchluessel(l);\n            var wert = { typ: 'bool', wert: true };\n            if (!l.ende() &#038;&#038; l.zeichen() === '=') {\n                l.i++;\n                wert = leseWert(l);\n            }\n            liste.push({ name: name, wert: wert });\n        }\n        return liste;\n    }\n\n    function leseSchluessel(l) {\n        var start = l.i;\n        while (!l.ende() &#038;&#038; \/[a-z0-9_\\-.*]\/.test(l.zeichen())) { l.i++; }\n        if (l.i === start) { throw new Error('erwartet: Schluessel'); }\n        return l.s.slice(start, l.i);\n    }\n\n    function leseInnereListe(l) {\n        if (l.zeichen() !== '(') { throw new Error('erwartet: innere Liste'); }\n        l.i++;\n        var eintraege = [];\n        for (;;) {\n            l.ows();\n            if (l.ende()) { throw new Error('innere Liste nicht geschlossen'); }\n            if (l.zeichen() === ')') { l.i++; break; }\n            var w = leseWert(l);\n            var p = leseParameter(l);\n            eintraege.push({ wert: w, parameter: p });\n        }\n        return eintraege;\n    }\n\n    \/** Woerterbuch: schluessel=wert, durch Komma getrennt. *\/\n    function leseWoerterbuch(text) {\n        var l = new Leser(text);\n        var raus = [];\n        l.ows();\n        while (!l.ende()) {\n            var name = leseSchluessel(l);\n            var eintrag = { name: name, liste: null, wert: null, parameter: [] };\n            if (!l.ende() &#038;&#038; l.zeichen() === '=') {\n                l.i++;\n                if (l.zeichen() === '(') {\n                    eintrag.liste = leseInnereListe(l);\n                } else {\n                    eintrag.wert = leseWert(l);\n                }\n            } else {\n                eintrag.wert = { typ: 'bool', wert: true };\n            }\n            eintrag.parameter = leseParameter(l);\n            raus.push(eintrag);\n            l.ows();\n            if (l.ende()) { break; }\n            if (l.zeichen() !== ',') { throw new Error('erwartet: Komma'); }\n            l.i++;\n            l.ows();\n        }\n        return raus;\n    }\n\n    \/\/ --- Zurueckschreiben in die kanonische Form -----------------------------\n\n    function schreibeWert(w) {\n        if (w.typ === 'text') { return '\"' + String(w.wert).replace(\/\\\\\/g, '\\\\\\\\').replace(\/\"\/g, '\\\\\"') + '\"'; }\n        if (w.typ === 'bytes') { return ':' + w.wert + ':'; }\n        if (w.typ === 'bool') { return w.wert ? '?1' : '?0'; }\n        if (w.typ === 'zahl') { return String(w.wert); }\n        return String(w.wert);\n    }\n\n    function schreibeParameter(liste) {\n        return (liste || []).map(function (p) {\n            if (p.wert.typ === 'bool' &#038;&#038; p.wert.wert === true) { return ';' + p.name; }\n            return ';' + p.name + '=' + schreibeWert(p.wert);\n        }).join('');\n    }\n\n    function schreibeInnereListe(eintraege, parameter) {\n        var kern = '(' + (eintraege || []).map(function (e) {\n            return schreibeWert(e.wert) + schreibeParameter(e.parameter);\n        }).join(' ') + ')';\n        return kern + schreibeParameter(parameter);\n    }\n\n    \/\/ =========================================================================\n    \/\/ Kopfzeilen und Signaturbasis\n    \/\/ =========================================================================\n\n    \/** Zerlegt einen eingefuegten Kopfzeilenblock. Fortsetzungszeilen zaehlen dazu. *\/\n    function kopfzeilen(text) {\n        var raus = [];\n        var zeilen = String(text == null ? '' : text).split(\/\\r\\n|\\r|\\n\/);\n        for (var i = 0; i < zeilen.length; i++) {\n            var z = zeilen[i];\n            if (!z.replace(\/[ \\t]\/g, '').length) { continue; }\n            if (\/^[ \\t]\/.test(z) &#038;&#038; raus.length) {\n                raus[raus.length - 1].wert += ' ' + z.replace(\/^[ \\t]+\/, '').replace(\/[ \\t]+$\/, '');\n                continue;\n            }\n            var p = z.indexOf(':');\n            if (p < 1) { continue; }\n            raus.push({\n                name: z.slice(0, p).trim().toLowerCase(),\n                wert: z.slice(p + 1).replace(\/^[ \\t]+\/, '').replace(\/[ \\t]+$\/, '')\n            });\n        }\n        return raus;\n    }\n\n    function feld(liste, name) {\n        var treffer = liste.filter(function (k) { return k.name === name; });\n        if (!treffer.length) { return null; }\n        return treffer.map(function (k) { return k.wert; }).join(', ');\n    }\n\n    \/**\n     * Baut die Signaturbasis nach RFC 9421 Abschnitt 2.5.\n     * Jede Komponentenzeile endet mit einem Zeilenvorschub, die\n     * @signature-params-Zeile steht ohne abschliessenden Vorschub am Ende.\n     *\/\n    function basisBauen(eintraege, parameter, kopf, authority) {\n        var zeilen = [];\n        var fehlend = [];\n        (eintraege || []).forEach(function (e) {\n            var name = String(e.wert.wert).toLowerCase();\n            var kennung = '\"' + name + '\"' + schreibeParameter(e.parameter);\n            var wert = null;\n            if (name === '@authority') {\n                wert = authority ? String(authority).toLowerCase() : null;\n            } else if (name.charAt(0) === '@') {\n                wert = null; \/\/ andere abgeleitete Bestandteile sind hier nicht aufloesbar\n            } else {\n                wert = feld(kopf, name);\n            }\n            if (wert === null || wert === '') {\n                fehlend.push(name);\n                zeilen.push(kennung + ': ');\n            } else {\n                zeilen.push(kennung + ': ' + wert);\n            }\n        });\n        zeilen.push('\"@signature-params\": ' + schreibeInnereListe(eintraege, parameter));\n        return { basis: zeilen.join('\\n'), fehlend: fehlend };\n    }\n\n    \/\/ =========================================================================\n    \/\/ Auswertung der Signatur\n    \/\/ =========================================================================\n\n    function parameterKarte(liste) {\n        var k = {};\n        (liste || []).forEach(function (p) { k[p.name] = p.wert.wert; });\n        return k;\n    }\n\n    var FENSTER_LANG = 3600; \/\/ Sekunden; darueber ist ein Signaturfenster ungewoehnlich weit\n\n    function zerlegen(eingabe, jetzt) {\n        var ein = eingabe || {};\n        var zeit = typeof jetzt === 'number' ? jetzt : Math.floor(Date.now() \/ 1000);\n        var kopf = kopfzeilen(ein.header);\n        var fehler = [];\n\n        var agentRoh = feld(kopf, 'signature-agent');\n        var agent = null;\n        if (agentRoh) {\n            var host = null;\n            try {\n                var l = new Leser(agentRoh);\n                var w = leseWert(l);\n                var adr = String(w.wert);\n                host = adr.replace(\/^https?:\\\/\\\/\/, '').replace(\/\\\/.*$\/, '') || null;\n            } catch (e) {\n                host = agentRoh.replace(\/^\"|\"$\/g, '').replace(\/^https?:\\\/\\\/\/, '').replace(\/\\\/.*$\/, '') || null;\n            }\n            agent = {\n                roh: agentRoh,\n                host: host,\n                verzeichnis: host ? 'https:\/\/' + host + '\/.well-known\/http-message-signatures-directory' : null\n            };\n        }\n\n        var eingabeRoh = feld(kopf, 'signature-input');\n        var signaturRoh = feld(kopf, 'signature');\n        if (!eingabeRoh) { fehler.push('kein_signature_input'); }\n        if (!signaturRoh) { fehler.push('kein_signature'); }\n\n        var eingabeDict = [];\n        var signaturDict = [];\n        if (eingabeRoh) {\n            try { eingabeDict = leseWoerterbuch(eingabeRoh); }\n            catch (e) { fehler.push('signature_input_unlesbar'); }\n        }\n        if (signaturRoh) {\n            try { signaturDict = leseWoerterbuch(signaturRoh); }\n            catch (e) { fehler.push('signature_unlesbar'); }\n        }\n\n        var signaturen = eingabeDict.map(function (eintrag) {\n            var params = parameterKarte(eintrag.parameter);\n            var komponenten = (eintrag.liste || []).map(function (e) { return String(e.wert.wert).toLowerCase(); });\n            var gebaut = basisBauen(eintrag.liste, eintrag.parameter, kopf, ein.authority);\n\n            var passend = signaturDict.filter(function (s) { return s.name === eintrag.name; })[0];\n            var bytesRoh = (passend &#038;&#038; passend.wert &#038;&#038; passend.wert.typ === 'bytes') ? passend.wert.wert : null;\n            var bytes = null;\n            if (bytesRoh !== null) {\n                try { bytes = base64ZuBytes(bytesRoh); } catch (e) { bytes = null; }\n            }\n\n            var befunde = [];\n            if (params.tag !== 'web-bot-auth') { befunde.push('kein_tag'); }\n            if (!params.keyid) { befunde.push('keyid_fehlt'); }\n            if (params.alg &#038;&#038; String(params.alg).toLowerCase() !== 'ed25519') { befunde.push('alg_fremd'); }\n            if (typeof params.created !== 'number') { befunde.push('created_fehlt'); }\n            if (typeof params.expires !== 'number') { befunde.push('kein_ablauf'); }\n            if (typeof params.expires === 'number' &#038;&#038; params.expires < zeit) { befunde.push('abgelaufen'); }\n            if (typeof params.created === 'number' &#038;&#038; params.created > zeit) { befunde.push('noch_nicht_gueltig'); }\n            if (typeof params.created === 'number' && typeof params.expires === 'number'\n                && params.expires - params.created > FENSTER_LANG) { befunde.push('fenster_weit'); }\n            if (komponenten.indexOf('@authority') === -1) { befunde.push('authority_ungedeckt'); }\n            if (agentRoh && komponenten.indexOf('signature-agent') === -1) { befunde.push('agent_ungedeckt'); }\n            if (!agentRoh) { befunde.push('agent_fehlt'); }\n            if (!params.nonce) { befunde.push('nonce_fehlt'); }\n            if (bytes && bytes.length !== 64) { befunde.push('laenge_ungewoehnlich'); }\n            if (!bytes) { befunde.push('signatur_fehlt'); }\n            if (gebaut.fehlend.length) { befunde.push('basis_unvollstaendig'); }<\/p>\n<p>            return {\n                label: eintrag.name,\n                komponenten: komponenten,\n                parameter: params,\n                basis: gebaut.basis,\n                fehlend: gebaut.fehlend,\n                signaturBytes: bytes ? bytes.length : 0,\n                signatur: bytes,\n                lebensdauer: (typeof params.created === 'number' && typeof params.expires === 'number')\n                    ? params.expires - params.created : null,\n                befunde: befunde\n            };\n        });<\/p>\n<p>        return { agent: agent, signaturen: signaturen, kopfzeilen: kopf, fehler: fehler, zeit: zeit };\n    }<\/p>\n<p>    \/\/ =========================================================================\n    \/\/ Base64 und WebCrypto\n    \/\/ =========================================================================<\/p>\n<p>    function base64ZuBytes(s) {\n        var t = String(s).replace(\/-\/g, '+').replace(\/_\/g, '\/').replace(\/\\s+\/g, '');\n        while (t.length % 4) { t += '='; }\n        var roh = atob(t);\n        var b = new Uint8Array(roh.length);\n        for (var i = 0; i < roh.length; i++) { b[i] = roh.charCodeAt(i); }\n        return b;\n    }\n\n    function bytesZuBase64Url(b) {\n        var s = '';\n        for (var i = 0; i < b.length; i++) { s += String.fromCharCode(b[i]); }\n        return btoa(s).replace(\/\\+\/g, '-').replace(\/\\\/\/g, '_').replace(\/=+$\/, '');\n    }\n\n    function textZuBytes(t) {\n        if (typeof TextEncoder !== 'undefined') { return new TextEncoder().encode(t); }\n        var s = unescape(encodeURIComponent(String(t)));\n        var b = new Uint8Array(s.length);\n        for (var i = 0; i < s.length; i++) { b[i] = s.charCodeAt(i); }\n        return b;\n    }\n\n    \/**\n     * Daumenabdruck nach RFC 7638: die Pflichtfelder in lexikografischer\n     * Ordnung, ohne Leerraum, durch SHA-256, base64url. Bei Web Bot Auth ist\n     * genau das die keyid - der Abgleich braucht also kein Ed25519.\n     *\/\n    function daumenabdruck(jwk) {\n        if (!jwk || jwk.kty !== 'OKP' || !jwk.crv || !jwk.x) {\n            return Promise.resolve({ moeglich: false, grund: 'kein_okp' });\n        }\n        var kanonisch = '{\"crv\":\"' + jwk.crv + '\",\"kty\":\"' + jwk.kty + '\",\"x\":\"' + jwk.x + '\"}';\n        if (!window.crypto || !window.crypto.subtle) {\n            return Promise.resolve({ moeglich: false, grund: 'kein_subtle' });\n        }\n        return window.crypto.subtle.digest('SHA-256', textZuBytes(kanonisch)).then(function (h) {\n            return { moeglich: true, abdruck: bytesZuBase64Url(new Uint8Array(h)) };\n        }, function () {\n            return { moeglich: false, grund: 'kein_subtle' };\n        });\n    }\n\n    \/** Ed25519 gegen die Signaturbasis. Fehlt der Algorithmus im Browser, wird das gesagt. *\/\n    function verifizieren(basis, signatur, jwk) {\n        if (!signatur || !jwk) { return Promise.resolve({ moeglich: false, grund: 'unvollstaendig' }); }\n        if (!window.crypto || !window.crypto.subtle || !window.crypto.subtle.importKey) {\n            return Promise.resolve({ moeglich: false, grund: 'kein_subtle' });\n        }\n        return window.crypto.subtle.importKey('jwk', {\n            kty: jwk.kty, crv: jwk.crv, x: jwk.x, ext: true\n        }, { name: 'Ed25519' }, false, ['verify']).then(function (key) {\n            return window.crypto.subtle.verify({ name: 'Ed25519' }, key, signatur, textZuBytes(basis))\n                .then(function (ok) { return { moeglich: true, gueltig: !!ok }; });\n        }).catch(function () {\n            return { moeglich: false, grund: 'kein_ed25519' };\n        });\n    }\n\n    \/\/ =========================================================================\n    \/\/ Adressbeweis: Praefixliste\n    \/\/ =========================================================================\n\n    function ipv4ZuZahl(a) {\n        var t = String(a).split('.');\n        if (t.length !== 4) { return null; }\n        var n = 0;\n        for (var i = 0; i < 4; i++) {\n            if (!\/^\\d{1,3}$\/.test(t[i])) { return null; }\n            var o = Number(t[i]);\n            if (o > 255) { return null; }\n            n = n * 256 + o;\n        }\n        return n;\n    }<\/p>\n<p>    \/** IPv6 als 32 Hexziffern, damit sich Praefixe rein textlich vergleichen lassen. *\/\n    function ipv6ZuHex(a) {\n        var s = String(a).trim();\n        if (s.indexOf(':') === -1) { return null; }\n        var teile = s.split('::');\n        if (teile.length > 2) { return null; }\n        function gruppen(t) { return t ? t.split(':').filter(function (x) { return x.length; }) : []; }\n        var links = gruppen(teile[0]);\n        var rechts = teile.length === 2 ? gruppen(teile[1]) : [];\n        var luecke = 8 - links.length - rechts.length;\n        if (teile.length === 1) {\n            if (links.length !== 8) { return null; }\n            luecke = 0;\n        } else if (luecke < 0) {\n            return null;\n        }\n        var nullen = [];\n        for (var k = 0; k < luecke; k++) { nullen.push('0'); }\n        var alle = links.concat(nullen).concat(rechts);\n        if (alle.length !== 8) { return null; }\n        var hex = '';\n        for (var i = 0; i < 8; i++) {\n            if (!\/^[0-9a-fA-F]{1,4}$\/.test(alle[i])) { return null; }\n            hex += ('000' + alle[i].toLowerCase()).slice(-4);\n        }\n        return hex;\n    }\n\n    function hexZuBits(hex, anzahl) {\n        var bits = '';\n        for (var i = 0; i < hex.length &#038;&#038; bits.length < anzahl; i++) {\n            bits += ('000' + parseInt(hex.charAt(i), 16).toString(2)).slice(-4);\n        }\n        return bits.slice(0, anzahl);\n    }\n\n    function netzPruefen(text, adresse) {\n        var zeilen = String(text == null ? '' : text).split(\/[\\s,;]+\/).filter(function (z) { return z.length; });\n        var praefixe = [];\n        var ungueltig = [];\n        var summeV4 = 0;\n        var v6Exponenten = [];\n\n        zeilen.forEach(function (z) {\n            var t = z.split('\/');\n            var netz = t[0];\n            var laenge = t.length > 1 ? Number(t[1]) : null;\n            var v4 = ipv4ZuZahl(netz);\n            if (v4 !== null) {\n                var l4 = laenge === null ? 32 : laenge;\n                if (!(l4 >= 0 && l4 <= 32) || !\/^\\d+$\/.test(String(t.length > 1 ? t[1] : '32'))) { ungueltig.push(z); return; }\n                var anzahl = Math.pow(2, 32 - l4);\n                summeV4 += anzahl;\n                praefixe.push({ roh: z, version: 4, laenge: l4, netz: v4, adressen: anzahl });\n                return;\n            }\n            var v6 = ipv6ZuHex(netz);\n            if (v6 !== null) {\n                var l6 = laenge === null ? 128 : laenge;\n                if (!(l6 >= 0 && l6 <= 128)) { ungueltig.push(z); return; }\n                v6Exponenten.push(128 - l6);\n                praefixe.push({ roh: z, version: 6, laenge: l6, netz: v6, adressen: null, exponent: 128 - l6 });\n                return;\n            }\n            ungueltig.push(z);\n        });\n\n        \/\/ Treffer\n        var treffer = null;\n        var trefferPraefix = null;\n        var adr = String(adresse == null ? '' : adresse).trim();\n        if (adr.length) {\n            var a4 = ipv4ZuZahl(adr);\n            var a6 = a4 === null ? ipv6ZuHex(adr) : null;\n            if (a4 === null &#038;&#038; a6 === null) {\n                treffer = 'unlesbar';\n            } else {\n                treffer = 'nein';\n                for (var i = 0; i < praefixe.length; i++) {\n                    var p = praefixe[i];\n                    if (p.version === 4 &#038;&#038; a4 !== null) {\n                        if (p.laenge === 0) { treffer = 'ja'; trefferPraefix = p.roh; break; }\n                        var maske = p.laenge === 0 ? 0 : (-1 << (32 - p.laenge)) >>> 0;\n                        if (((a4 >>> 0) & maske) === ((p.netz >>> 0) & maske)) {\n                            treffer = 'ja'; trefferPraefix = p.roh; break;\n                        }\n                    } else if (p.version === 6 && a6 !== null) {\n                        if (hexZuBits(a6, p.laenge) === hexZuBits(p.netz, p.laenge)) {\n                            treffer = 'ja'; trefferPraefix = p.roh; break;\n                        }\n                    }\n                }\n            }\n        }<\/p>\n<p>        return {\n            praefixe: praefixe,\n            anzahlPraefixe: praefixe.length,\n            v4Adressen: summeV4,\n            v6Exponenten: v6Exponenten,\n            ungueltig: ungueltig,\n            treffer: treffer,\n            trefferPraefix: trefferPraefix\n        };\n    }<\/p>\n<p>    \/\/ --- Pruefpunkt ----------------------------------------------------------\n    window.LW_TEST = window.LW_TEST || {};\n    window.LW_TEST.WB = {\n        zerlegen: zerlegen,\n        basisBauen: basisBauen,\n        leseWoerterbuch: leseWoerterbuch,\n        schreibeInnereListe: schreibeInnereListe,\n        kopfzeilen: kopfzeilen,\n        netzPruefen: netzPruefen,\n        daumenabdruck: daumenabdruck,\n        verifizieren: verifizieren,\n        base64ZuBytes: base64ZuBytes,\n        bytesZuBase64Url: bytesZuBase64Url,\n        ipv6ZuHex: ipv6ZuHex,\n        FENSTER_LANG: FENSTER_LANG,\n        T: T\n    };<\/p>\n<p>    \/\/ =========================================================================\n    \/\/ Oberflaeche\n    \/\/ =========================================================================<\/p>\n<p>    var btn = document.getElementById('wb-btn');\n    var out = document.getElementById('wb-ausgabe');\n    if (!btn || !out) { return; }<\/p>\n<p>    \/\/ Beispiel vorbelegen, damit das Werkzeug ohne eigene Daten etwas zeigt.\n    var beispielHeader = document.getElementById('wb-header');\n    var beispielJwk = document.getElementById('wb-jwk');\n    var beispielPraefixe = document.getElementById('wb-praefixe');\n    if (beispielHeader && !beispielHeader.value) { beispielHeader.value = T.beispiel_header; }\n    if (beispielJwk && !beispielJwk.value) { beispielJwk.value = T.beispiel_jwk; }\n    if (beispielPraefixe && !beispielPraefixe.value) { beispielPraefixe.value = T.beispiel_praefixe; }<\/p>\n<p>    function el(tag, stil, text) {\n        var e = document.createElement(tag);\n        if (stil) { e.setAttribute('style', stil); }\n        if (text !== undefined) { e.textContent = text; }\n        return e;\n    }\n    function wert(id) { var e = document.getElementById(id); return e ? e.value : ''; }\n    function zahl(n) { return String(n).replace(\/\\B(?=(\\d{3})+(?!\\d))\/g, T.tausender); }<\/p>\n<p>    var UEBERSCHRIFT = 'font-family: \"Nunito Sans\", sans-serif; font-weight: 700; color: var(--text-primary, #e8e8ee); font-size: 0.92rem; margin: 18px 0 8px;';\n    var ZELLE = 'padding: 5px 12px 5px 0; color: var(--text-secondary, #a0a0b0); font-size: 0.87rem;';\n    var ZELLE_WERT = 'padding: 5px 12px 5px 0; color: var(--text-primary, #e8e8ee); font-size: 0.87rem; font-family: monospace; word-break: break-all;';\n    var VORFORM = 'background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-radius: 6px; padding: 10px; color: var(--text-primary, #e8e8ee); font-family: monospace; font-size: 0.78rem; white-space: pre-wrap; word-break: break-all; margin: 0;';\n    var GUT = ' color: #7ee787;';\n    var WARN = ' color: #ffa94d;';\n    var GRAU = ' color: #8a8a99;';<\/p>\n<p>    function gitter(spalten) {\n        return el('div', 'display: grid; grid-template-columns: ' + spalten + '; gap: 0 18px; align-items: baseline;');\n    }\n    function paar(g, name, w, stil) {\n        g.appendChild(el('div', ZELLE, name));\n        g.appendChild(el('div', ZELLE_WERT + (stil || ''), w));\n    }<\/p>\n<p>    btn.addEventListener('click', function () {\n        var erg = zerlegen({ header: wert('wb-header'), authority: wert('wb-authority') });\n        out.innerHTML = '';<\/p>\n<p>        \/\/ --- Absender ---\n        out.appendChild(el('div', UEBERSCHRIFT, T.h_agent));\n        var ga = gitter('auto auto');\n        if (erg.agent) {\n            paar(ga, T.zeile_agent, erg.agent.host || T.wert_keiner, erg.agent.host ? '' : WARN);\n            paar(ga, T.zeile_verzeichnis, erg.agent.verzeichnis || T.wert_keiner, GRAU);\n        } else {\n            paar(ga, T.zeile_agent, T.wert_keiner, WARN);\n        }\n        out.appendChild(ga);<\/p>\n<p>        if (erg.fehler.length) {\n            var fl = el('ul', 'margin: 8px 0 0 18px; padding: 0; color: #ffa94d; font-size: 0.87rem; line-height: 1.6;');\n            erg.fehler.forEach(function (f) { fl.appendChild(el('li', '', T['fehler_' + f] || f)); });\n            out.appendChild(fl);\n        }<\/p>\n<p>        \/\/ --- Signaturen ---\n        erg.signaturen.forEach(function (s) {\n            out.appendChild(el('div', UEBERSCHRIFT, T.h_signatur + ' ' + s.label));\n            var g = gitter('auto auto');\n            paar(g, T.zeile_komponenten, s.komponenten.length ? s.komponenten.join(', ') : T.wert_keiner);\n            paar(g, T.zeile_keyid, s.parameter.keyid || T.wert_keiner, s.parameter.keyid ? '' : WARN);\n            paar(g, T.zeile_alg, s.parameter.alg || T.wert_keiner, GRAU);\n            paar(g, T.zeile_tag, s.parameter.tag || T.wert_keiner, s.parameter.tag === 'web-bot-auth' ? GUT : WARN);\n            paar(g, T.zeile_lebensdauer,\n                s.lebensdauer === null ? T.wert_keiner : zahl(s.lebensdauer) + ' ' + T.einheit_sekunden,\n                s.lebensdauer === null ? WARN : (s.lebensdauer > FENSTER_LANG ? WARN : GUT));\n            paar(g, T.zeile_signaturlaenge,\n                s.signaturBytes ? zahl(s.signaturBytes) + ' ' + T.einheit_bytes : T.wert_keiner,\n                s.signaturBytes === 64 ? GUT : WARN);\n            out.appendChild(g);<\/p>\n<p>            out.appendChild(el('div', UEBERSCHRIFT, T.h_basis));\n            out.appendChild(el('pre', VORFORM, s.basis));\n            if (s.fehlend.length) {\n                out.appendChild(el('p', ZELLE + WARN + ' margin: 8px 0 0;',\n                    T.basis_fehlend + ' ' + s.fehlend.join(', ')));\n            }<\/p>\n<p>            var pruefZeile = el('div', ZELLE + GRAU + ' margin-top: 10px;', T.pruefung_laeuft);\n            out.appendChild(pruefZeile);<\/p>\n<p>            var jwk = null;\n            try { jwk = JSON.parse(wert('wb-jwk')); } catch (e) { jwk = null; }\n            if (jwk && jwk.keys && jwk.keys.length) { jwk = jwk.keys[0]; }<\/p>\n<p>            Promise.all([\n                daumenabdruck(jwk),\n                verifizieren(s.basis, s.signatur, jwk)\n            ]).then(function (r) {\n                var abdruck = r[0];\n                var pruef = r[1];\n                pruefZeile.textContent = '';\n                var gp = gitter('auto auto');\n                if (!jwk) {\n                    paar(gp, T.zeile_abdruck, T.abdruck_kein_jwk, GRAU);\n                } else if (!abdruck.moeglich) {\n                    paar(gp, T.zeile_abdruck, T['abdruck_' + abdruck.grund] || T.wert_keiner, GRAU);\n                } else {\n                    var passt = s.parameter.keyid && abdruck.abdruck === s.parameter.keyid;\n                    paar(gp, T.zeile_abdruck, abdruck.abdruck, passt ? GUT : WARN);\n                    paar(gp, T.zeile_abdruck_urteil,\n                        !s.parameter.keyid ? T.abdruck_ohne_keyid : (passt ? T.abdruck_passt : T.abdruck_passt_nicht),\n                        passt ? GUT : WARN);\n                }\n                if (pruef.moeglich) {\n                    paar(gp, T.zeile_signaturpruefung, pruef.gueltig ? T.signatur_gueltig : T.signatur_ungueltig,\n                        pruef.gueltig ? GUT : WARN);\n                } else {\n                    paar(gp, T.zeile_signaturpruefung, T['pruefung_' + pruef.grund] || T.wert_keiner, GRAU);\n                }\n                pruefZeile.appendChild(gp);\n            });<\/p>\n<p>            if (s.befunde.length) {\n                out.appendChild(el('div', UEBERSCHRIFT, T.h_befunde));\n                var bl = el('ul', 'margin: 0 0 0 18px; padding: 0; color: var(--text-secondary, #a0a0b0); font-size: 0.87rem; line-height: 1.65;');\n                s.befunde.forEach(function (b) {\n                    var text = T['befund_' + b];\n                    if (text) { bl.appendChild(el('li', '', text)); }\n                });\n                out.appendChild(bl);\n            }\n        });<\/p>\n<p>        \/\/ --- Adressbeweis ---\n        var netz = netzPruefen(wert('wb-praefixe'), wert('wb-adresse'));\n        out.appendChild(el('div', UEBERSCHRIFT, T.h_netz));\n        var gn = gitter('auto auto');\n        paar(gn, T.zeile_praefixe, zahl(netz.anzahlPraefixe));\n        paar(gn, T.zeile_v4adressen, zahl(netz.v4Adressen));\n        if (netz.v6Exponenten.length) {\n            paar(gn, T.zeile_v6, netz.v6Exponenten.map(function (e) { return '2^' + e; }).join(' + '), GRAU);\n        }\n        if (netz.ungueltig.length) {\n            paar(gn, T.zeile_ungueltig, netz.ungueltig.join(', '), WARN);\n        }\n        if (netz.treffer !== null) {\n            paar(gn, T.zeile_treffer,\n                netz.treffer === 'ja' ? (T.treffer_ja + ' ' + netz.trefferPraefix)\n                    : (netz.treffer === 'nein' ? T.treffer_nein : T.treffer_unlesbar),\n                netz.treffer === 'ja' ? GUT : WARN);\n        }\n        out.appendChild(gn);<\/p>\n<p>        out.appendChild(el('div', UEBERSCHRIFT, T.h_vergleich));\n        var vl = el('ul', 'margin: 0 0 0 18px; padding: 0; color: var(--text-secondary, #a0a0b0); font-size: 0.87rem; line-height: 1.65;');\n        ['vergleich_pflege', 'vergleich_beweis', 'vergleich_absicht'].forEach(function (k) {\n            vl.appendChild(el('li', '', T[k]));\n        });\n        out.appendChild(vl);\n    });\n})();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Zestawia obok siebie dwie drogi, kt\u00f3rymi strona mo\u017ce si\u0119 dowiedzie\u0107, kto pyta: podpis Ed25519 wed\u0142ug RFC 9421, odtworzony i sprawdzony wobec klucza publicznego, oraz list\u0119 opublikowanych zakres\u00f3w adres\u00f3w, policzon\u0105 i sprawdzon\u0105 pod k\u0105tem przynale\u017cno\u015bci.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":9284,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[92343,92044,92077],"class_list":["post-13034","page","type-page","status-publish","hentry","tag-artificial-intelligence-pl","tag-tracking-pl","tag-web-security-pl"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Inspektor podpis\u00f3w Web Bot Auth i kalkulator zakres\u00f3w adres\u00f3w - Lukas Wojcik - Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/web-bot-auth-signature-inspector\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Inspektor podpis\u00f3w Web Bot Auth i kalkulator zakres\u00f3w adres\u00f3w - Lukas Wojcik - Blog\" \/>\n<meta property=\"og:description\" content=\"Zestawia obok siebie dwie drogi, kt\u00f3rymi strona mo\u017ce si\u0119 dowiedzie\u0107, kto pyta: podpis Ed25519 wed\u0142ug RFC 9421, odtworzony i sprawdzony wobec klucza publicznego, oraz list\u0119 opublikowanych zakres\u00f3w adres\u00f3w, policzon\u0105 i sprawdzon\u0105 pod k\u0105tem przynale\u017cno\u015bci.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/web-bot-auth-signature-inspector\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/narzedzia\\\/web-bot-auth-signature-inspector\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/narzedzia\\\/web-bot-auth-signature-inspector\\\/\",\"name\":\"Inspektor podpis\u00f3w Web Bot Auth i kalkulator zakres\u00f3w adres\u00f3w - Lukas Wojcik - Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-08-28T11:25:09+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/narzedzia\\\/web-bot-auth-signature-inspector\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/narzedzia\\\/web-bot-auth-signature-inspector\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/narzedzia\\\/web-bot-auth-signature-inspector\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Toolbox\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/toolbox-3\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Inspektor podpis\u00f3w Web Bot Auth i kalkulator zakres\u00f3w adres\u00f3w\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"luky\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"luky\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Inspektor podpis\u00f3w Web Bot Auth i kalkulator zakres\u00f3w adres\u00f3w - Lukas Wojcik - Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/web-bot-auth-signature-inspector\/","og_locale":"en_US","og_type":"article","og_title":"Inspektor podpis\u00f3w Web Bot Auth i kalkulator zakres\u00f3w adres\u00f3w - Lukas Wojcik - Blog","og_description":"Zestawia obok siebie dwie drogi, kt\u00f3rymi strona mo\u017ce si\u0119 dowiedzie\u0107, kto pyta: podpis Ed25519 wed\u0142ug RFC 9421, odtworzony i sprawdzony wobec klucza publicznego, oraz list\u0119 opublikowanych zakres\u00f3w adres\u00f3w, policzon\u0105 i sprawdzon\u0105 pod k\u0105tem przynale\u017cno\u015bci.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/web-bot-auth-signature-inspector\/","og_site_name":"Lukas Wojcik - Blog","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/web-bot-auth-signature-inspector\/","url":"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/web-bot-auth-signature-inspector\/","name":"Inspektor podpis\u00f3w Web Bot Auth i kalkulator zakres\u00f3w adres\u00f3w - Lukas Wojcik - Blog","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-08-28T11:25:09+00:00","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/web-bot-auth-signature-inspector\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/web-bot-auth-signature-inspector\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/narzedzia\/web-bot-auth-signature-inspector\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Toolbox","item":"https:\/\/www.lukaswojcik.com\/blog\/toolbox-3\/"},{"@type":"ListItem","position":3,"name":"Inspektor podpis\u00f3w Web Bot Auth i kalkulator zakres\u00f3w adres\u00f3w"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"luky","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"luky"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/13034","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=13034"}],"version-history":[{"count":0,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/13034\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/9284"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=13034"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=13034"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}