Balíčky důkazů: podepsaný záznam události důvěry v GNSS
Balíček je technický záznam, nikoli právní stanovisko. Uvádí, co engine Kshana vypočítal z uvedeného záznamu přijímače při uvedené konfiguraci, a umožňuje komukoli ověřit, že se v něm nic dodatečně nezměnilo. Neříká, co událost způsobilo, kdo za ni nesl odpovědnost, zda byla splněna nějaká povinnost, ani že záznam ukazuje to, co přijímač skutečně přijal. Manifest i shrnutí to uvádějí.
Balíček důkazů slouží pro zprávu o incidentu, pojišťovnu nebo dotaz regulátora: „co váš systém řekl o signálu GNSS mezi těmito dvěma časy a můžeme tomuto záznamu věřit?“
Vytvoření balíčku#
# once: a signing key. The private key stays with you; never commit it.
kshana evidence keygen --out signer.key # writes signer.key (0600) and signer.key.pub
kshana receiver-trust evidence session.toml \
--from 100 --to 260 \ # seconds since the first epoch, or ISO-8601 UTC
--key signer.key --out pack/ \
--title "Berth 4, 14 June" # optional
U balíčku vytvořeného z laboratorní relace --lab-attestation lab.toml zaznamenává do podepsaného manifestu prohlášení operátora o uspořádání (vedené nebo stíněné, útlum nebo kryt, co vysílalo jakékoli RF); viz HARDWARE-SAFETY.md. Zaznamenává se tak, jak je uvedeno, a nikdy se neověřuje.
--from a --to jsou sekundy od první epochy záznamu, nebo čas UTC podle ISO-8601, když záznam uvádí vlastní čas začátku. --out nesmí již obsahovat soubory. --created-utc přijímá skutečný čas UTC, například 2026-01-02T03:04:05Z (cokoli jiného se odmítne), nebo none, což čas vytvoření vynechá a učiní balíček byte po bytu reprodukovatelným. Čas vytvoření je takový, jaký uvede podepisující; nic jej nekontroluje proti hodinám, k čemuž slouží volitelný token časového razítka.
Soubor s klíčem obsahuje 32bajtový seed Ed25519 jako 64 hexadecimálních číslic; dodejte vlastní (například klíč uchovávaný ve vašem systému správy klíčů) nebo nechte keygen vytvořit jeden. kshana varuje, pokud je soubor čitelný pro jiné uživatele. .gitignore vylučuje *.evidence-key; pojmenujte svůj klíč tak, pokud leží v checkoutu.
Co je v balíčku#
| Soubor | Obsah |
|---|---|
log-slice.bin |
surové bajty záznamu přijímače pro dané okno |
config.json |
scénář tak, jak byl spuštěn (každý práh monitoru, událost a pravidlo porovnání), monitory, které běžely, kalibrační základ a hash scénáře; u plavidla také poradní prohlášení, které nese každý výstup pro plavidlo (software pouze poradního charakteru, nikoli typově schválené navigační zařízení); od 0,36 též geometry_model (tabulka [geometry] skutečně použitá, váha monitoru geometrie, každý původ a pravidla tříd). Názvy adresářů a vložená data jsou odstraněny. |
epochs.json |
každá epocha v okně se statistikami enginu, poplachy (důvody) a stavem důvěry; od 0,36 také geometry družic (dobrá, zhoršená s důvody nebo neznámá, kde záznam neuvádí geometrii družic) a class u každého závěru (geometry, environment, inconsistency) |
summary.html |
samostatné shrnutí bez skriptů: identifikace, hashe, výsledky, důvody, dráha geometrie (epochy podle stavu geometrie) oddělená od tříd závěrů (0,36), každý práh, tabulka po epochách a meze záznamu |
manifest.json |
verze enginu, okno, čas vytvoření, formát a název záznamu, SHA-256 úplného záznamu, rozsah a SHA-256 výřezu, veřejný klíč a otisk podepisujícího, velikost a SHA-256 každého souboru a řetězec hashů přes soubory |
manifest.sig |
podpis Ed25519 přes přesné bajty manifest.json |
timestamp.tsr |
volitelný token časového razítka podle RFC 3161 |
Výřez záznamu. Čtečka uvádí zdrojový bajtový rozsah každé epochy, takže výřez je nejmenší bajtový rozsah, který obsahuje každou epochu v okně (byte-range): NMEA, u-blox UBX, pozorování RINEX 3 a záznamy z Androidu. Hashuje se jako surové bajty, nikdy jako dekódovaný text. U živého přenosu nebo RINEX 2 rozsah neexistuje; pak se přibalí celý záznam a manifest uvádí whole-log (okno omezuje, které epochy se uvádějí, nikoli které bajty se zahrnou). Shrnutí uvádí, který případ platí. Výřez je záznam bajtů okna, nikoli samostatný záznam: například výřez RINEX nemá hlavičku souboru.
Řetězec hashů. Manifest uvádí soubory v pevném pořadí. link₀ = SHA-256("kshana- evidence-chain/1"); pro každý soubor linkᵢ = SHA-256(linkᵢ₋₁ ‖ SHA-256(file) ‖ len(name) ‖ name) (délka jako 4 bajty, big-endian). Poslední článek je chain_head. Přeřazení, odstranění nebo nahrazení souboru změní řetězec; podpis pokrývá řetězec.
Ověření balíčku#
kshana evidence verify pack/ --pubkey signer.key.pub [--log session.nmea] \
[--require-timestamp] [--allow-unpinned] [--json]
Návratový kód 0 znamená ověřeno; 1 znamená, že něco selhalo; 2 znamená chybu použití; 3 znamená, že balíček je neporušený, ale jeho podepisující nebyl připnut (viz níže). Každá kontrola hlásí splněno, selhalo nebo přeskočeno a každé selhání je pojmenováno:
| Kód selhání | Význam |
|---|---|
manifest-missing, manifest-malformed, unsupported-format |
manifest nelze přečíst |
signature-missing, signature-malformed, signature-invalid |
žádný podpis, není to 64bajtový podpis v hexadecimálních malých písmenech, nebo se neověří přes manifest.json |
public-key-malformed, public-key-mismatch |
klíč není platný nebo se liší od toho, který jste dodali |
file-missing, file-size-mismatch, file-hash-mismatch |
uvedený soubor chybí nebo se liší od manifestu; soubor je pojmenován |
unlisted-file |
soubor je v balíčku, ale není v manifestu |
chain-mismatch, chain-head-mismatch |
článek neodpovídá tomu, co dávají jeho vstupy; soubor je pojmenován |
artifact-list-malformed, slice-record-inconsistent, epoch-count-mismatch |
manifest si odporuje nebo odporuje epochs.json |
full-log-hash-mismatch, slice-not-from-full-log |
s --log: záznam, který máte, není ten zaznamenaný, nebo výřez není tímto rozsahem z něj |
timestamp-missing, timestamp-malformed, timestamp-imprint-mismatch |
viz níže |
Změna jediného bajtu kteréhokoli souboru způsobí selhání ověření s odpovídajícím důvodem: změněný artefakt se hlásí jako neshoda hashe tohoto souboru a nic jiného; změněný manifest způsobí selhání podpisu. Přepočet hashů a řetězce po úpravě souboru bez podpisového klíče nepomůže; selže pak pouze podpis.
Připněte klíč. Bez --pubkey balíček dokazuje jen to, že je neporušený vůči klíči, který sám uvádí a který může vygenerovat kdokoli. Ověření tedy končí kódem 3 a hlásí „INTACT, BUT THE SIGNER IS NOT PINNED“ místo „VERIFIED“; --allow-unpinned přijme vlastní klíč balíčku vědomě a vypíše „VERIFIED against the key the pack names itself (signer NOT pinned; pass --pubkey)“. Veřejný klíč podepisujícího získejte od podepisujícího cestou, které věříte (nikoli z balíčku), a předejte jej pomocí --pubkey: 64 hexadecimálních číslic nebo soubor .pub. Omylem předaný soukromý klíč se rozpozná a odmítne. Otisk (prvních 128 bitů SHA-256 klíče, 32 hexadecimálních číslic) se vypisuje k porovnání pohledem; spolehlivou cestou je připnutí celého klíče.
Formát balíčku a postup ověření (normativní)#
Tento oddíl je specifikace, podle níž se píše nezávislý ověřovač. Slova psaná velkými písmeny jsou požadavky. Balíček je adresář (nebo libovolné zobrazení názvu souboru na bajty) souborů pojmenovaných přesně jako níže; ověřovač pracuje s bajty každého souboru tak, jak je uložen, a nikdy nic znovu neserializuje.
Soubory. Soubory artefaktů jsou v tomto pevném pořadí log-slice.bin, config.json, epochs.json, summary.html. Balíček má také manifest.json a manifest.sig a MŮŽE mít timestamp.tsr. Jakýkoli jiný název je nevyjmenovaný soubor.
Hex. Každý hexadecimální řetězec je pouze malými písmeny 0-9a-f, bez bílých znaků; velká písmena se nepřijímají. Hodnoty SHA-256 mají 64 číslic, veřejný klíč má 64 číslic, podpis má 128 číslic.
manifest.sig. Hexadecimální zápis malými písmeny o 128 číslicích 64bajtového podpisu Ed25519 (RFC 8032, čisté schéma Ed25519) přes přesné bajty manifest.json, následovaný právě jedním \n a ničím jiným. Podpis MUSÍ být kontrolován striktně: podpis, jehož hodnota S není menší než řád grupy L, je neplatný.
manifest.json. Objekt JSON v UTF-8. Jeho členové (ověřovač MUSÍ najít každého s uvedeným typem JSON; další členové se ignorují):
| člen | typ | význam |
|---|---|---|
format |
řetězec | MUSÍ se rovnat kshana-evidence/1 |
title, engine_version, disclaimer |
řetězec | popisný |
created_utc |
řetězec nebo null | čas vytvoření, popisný |
window |
objekt {from_s, to_s} |
čísla, sekundy |
epochs_in_window |
nezáporné celé číslo | počet položek pole JSON v epochs.json |
log |
objekt | format řetězec, file_name řetězec, full_sha256 hex, full_bytes nezáporné celé číslo, start_label řetězec nebo null a slice |
log.slice |
objekt | kind (přesně byte-range nebo whole-log; jakákoli jiná hodnota činí manifest chybně utvořeným), start a end nezáporná celá čísla (bajtové posuny, konec je vyloučen), sha256 hex |
signer |
objekt | algorithm řetězec (ed25519), public_key hex (32 bajtů), fingerprint řetězec |
artifacts |
pole objektů | jeden pro každý soubor artefaktu, v pevném pořadí: name řetězec, role řetězec, bytes nezáporné celé číslo, sha256 hex, link hex |
chain_head |
hex | poslední článek |
lab_attestation |
objekt, volitelný | přítomen pouze v balíčku vytvořeném z laboratorní relace (--lab-attestation): prohlášení operátora operator, organisation, date, setup (conducted nebo shielded), attenuation_db nebo enclosure, emitter, statement a volitelně signature, zaznamenané tak, jak jsou uvedeny. Je pokryto podpisem manifestu a nikdy se neověřuje; ověřovač je hlásí jako „attested by the named operator; not verified by Kshana“ (potvrzeno jmenovaným operátorem; Kshana neověřuje). Jinak chybí a balíček bez něj se nemění |
signer.fingerprint je prvních 32 hexadecimálních číslic SHA-256 přes 32 bajtů veřejného klíče.
Řetězec hashů. link0 je SHA-256 z 23 bajtů ASCII kshana-evidence-chain/1. Pro každý artefakt v pořadí link = SHA-256( previous_link (32 bytes) ‖ SHA-256(file) (32 bytes) ‖ len(name) as 4 bytes big-endian ‖ name as UTF-8 ), kde SHA-256(file) se bere ze zaznamenaného sha256 manifestu pro daný artefakt (takže řetězec je vlastností manifestu a kontroluje se proti němu, nikoli proti souborům).
Postup. Ověřovač provádí tyto kroky v pořadí a shromažďuje každé selhání; výsledkem je množina selhání, každé je code a tam, kde je to uvedeno, soubor name. NESMÍ se zastavit u prvního selhání, kromě uvedených případů. pin je veřejný klíč, kterému má ověřovač důvěřovat, pokud nějaký byl zadán.
- Žádný
manifest.json: selhánímanifest-missing; zastavit. - Pokud
manifest.jsonnení objekt JSON se všemi výše uvedenými členy v uvedených typech (včetně položekartifactsse všemi členy alog.slice): selhánímanifest-malformed. Jinak, pokudformatnení výše uvedenou hodnotou:unsupported-format. - Fáze podpisu. Tato fáze se provádí bez ohledu na to, zda byl manifest čitelný. Pokud byl manifest čitelný, uvedeným klíčem je
signer.public_key; pokud to není 64 hexadecimálních číslic malými písmeny, selhánípublic-key-malformed(žádný uvedený klíč); jinak, pokudsigner.fingerprintnení jeho otiskem, selhánípublic-key-malformed(klíč zůstává použitelný). Pokud existujepini uvedený klíč a liší se: selhánípublic-key-mismatch. Kontrolním klíčem jepin, pokud byl zadán, jinak uvedený klíč, pokud existuje; pokud není ani jeden, kontrolní klíč neexistuje (to zahrnuje každý případ, kdy manifest nebyl čitelný a nebyl zadán žádnýpin). Pokud neexistujemanifest.sig:signature-missing. Jinak, pokud soubor není přesně 128 hexadecimálních číslic malými písmeny plus jedno\n:signature-malformed. Jinak, pokud existuje kontrolní klíč, který není platným kódováním veřejného klíče Ed25519:public-key-malformed. Jinak, pokud existuje kontrolní klíč a podpis se striktně neověří přes bajtymanifest.json(ať jsou jakékoli):signature-invalid. (Bez kontrolního klíče se podpis nekontroluje a pro to se nevyvolává žádné selhání.) - Pokud manifest nebyl čitelný (krok 2 dal
manifest-malformed): zde se zastavit. - Artefakty. Pokud názvy
artifactsnejsou přesně čtyři výše uvedené v pořadí:artifact-list-malformed. Poté pro každou položku manifestu v pořadí: pokud balíček nemá soubor tohoto názvu,file-missing(název); jinak, pokud se jeho délka liší odbytes,file-size-mismatch(název); a nezávisle, pokud se SHA-256 souboru liší odsha256,file-hash-mismatch(název). - Nevyjmenované soubory: pro každý soubor v balíčku jiný než
manifest.json,manifest.sig,timestamp.tsra názvy vartifactsmanifestu:unlisted-file(název). - Řetězec. Projděte
artifactsmanifestu v pořadí odlink0. Pokudsha256položky není 64 hexadecimálních číslic malými písmeny:manifest-malformeda řetězec se opustí (žádná kontrola hlavy). Jinak vypočtěte článek; pokud se liší odlinkpoložky:chain-mismatch(název). Po průchodu, pokud řetězec nebyl opuštěn a poslední článek se liší odchain_head:chain-head-mismatch. - Výřez a epochy. Vyvolejte
slice-record-inconsistent(nejvýše jednou za důvod a ověřovač uvádí kód jednou, bez ohledu na to, kolik důvodů platí), pokud platí cokoli z:start > end;end > full_bytes;sha256položkylog-slice.binse liší odslice.sha256;bytestéto položky se liší odend - start; nebokindjewhole-loga platí cokoli zstart != 0,end != full_bytes,slice.sha256 != full_sha256. Pokudepochs.jsonexistuje a je zpracováno jako pole JSON, jehož délka se liší odepochs_in_window:epoch-count-mismatch. - Úplný záznam. Pouze pokud ověřovač dostal původní bajty záznamu: pokud se jejich SHA-256 liší od
log.full_sha256,full-log-hash-mismatch; jinak, pokud rozsahstart..endnení uvnitř záznamu nebo se bajty záznamu tam liší odlog-slice.bin,slice-not-from-full-log. - Časové razítko. Bez
timestamp.tsr: pokud byl ověřovač instruován, aby je vyžadoval,timestamp-missing; jinak nic. S ním: pokud jej nelze přečíst jako token nebo odpověď podle RFC 3161 (viz níže),timestamp-malformed; jinak, pokud jeho otisk zprávy není hashem, v algoritmu vlastním tomuto otisku (SHA-256, SHA-384 nebo SHA-512), bajtůmanifest.json:timestamp-imprint-mismatch.
Balíček je ověřen právě tehdy, když je množina selhání prázdná. Zda byl podepisující připnut, se hlásí zvlášť a množinu selhání to nemění.
Token časového razítka. Token je DER ContentInfo, jehož typ obsahu je CMS SignedData (1.2.840.113549.1.7.2) a jehož zapouzdřený typ obsahu je id-ct-TSTInfo (1.2.840.113549.1.9.16.1.4); odpověď je DER TimeStampResp (PKIStatusInfo následovaná tokenem), jejíž stav je 0 nebo 1. Algoritmus otisku zprávy v TSTInfo musí být SHA-256, SHA-384 nebo SHA-512 s otiskem délky odpovídající tomuto algoritmu a jeho genTime skutečný kalendářní čas UTC. BER s neurčenou délkou se nepřijímá. Podpis autority a řetězec certifikátů se kontrolují, pouze když jsou zadány důvěryhodné kotvy (--tsa-ca, níže).
Štítek důkazů (0,40)#
Balíček vytvořený verzí 0,40 nebo novější nese uvnitř config.json položku evidence (úroveň, VALIDATED, MODELLED nebo PARTNER, což je nejslabší stav mezi jeho řádky, a id a požadavky řádků verifikační matice, na nichž obsah balíčku stojí: řádek integrity balíčku, řádky receiver-trust a řádky jammertest-scoring nebo round-trip pro tyto balíčky) a config_version "1.1". Je uvnitř config.json, takže hash souboru z manifestu, hashový řetězec a podpis jej pokrývají: jeho změna ověření nesplní. kshana evidence verify uvádí úroveň a id řádků.
Změna je aditivní a jen dopředná. format manifestu zůstává kshana-evidence/1: normativní text výše říká, že ověřovač MUSÍ najít tuto hodnotu, a ověřovač clean-room v scripts/ ji najde. Podverze je proto nesena v config_version (u balíčku vytvořeného před 0,40 chybí, což znamená 1,0), nikoli novým řetězcem format. Balíček vytvořený před 0,40 nemá štítek a ověřuje se beze změny; kshana evidence verify říká „no evidence label (pre-0.40 pack)“. Testy jsou tests/evidence_pack_label.rs (včetně odevzdaných balíčků orákul před 0.40) a tests/evidence_labels_doc_sync.rs.
Časové razítko podle RFC 3161 (volitelné)#
Token časového razítka od autority časových razítek ukazuje, že manifest existoval v čase, který autorita potvrzuje. Token je přes manifest.json, takže se žádá až po vytvoření balíčku (neexistuje způsob, jak jej dodat při vytvoření balíčku, a create_bundle odmítne token, který není přesně přes tento manifest):
openssl ts -query -data pack/manifest.json -sha256 -cert -out req.tsq
# send req.tsq to your authority, get the response, then:
kshana evidence attach-timestamp pack/ response.tsr
openssl ts -verify -in response.tsr -data pack/manifest.json -CAfile authority-ca.pem
kshana evidence verify přečte token (holý token nebo úplnou odpověď), zkontroluje, že byl udělen a že jeho otisk zprávy se rovná hashi manifest.json, a vypíše čas, který uvádí. Bez důvěryhodných kotev přidá „timestamp authority signature not verified by Kshana“ (podpis autority časových razítek Kshana neověřila) a hlásí authority_signature_verified: false; pro tuto část spusťte výše uvedený řádek openssl ts -verify. Token, jehož otisk se liší, nebo který nelze zpracovat, je selhání. Typy obsahu tokenu, délka otisku a čas vytvoření se kontrolují z hlediska smyslu.
S --tsa-ca authority-ca.pem (jeden nebo více certifikátů PEM, všechny důvěryhodné) verify kontroluje také autoritu, offline, tak jako openssl ts -verify: otisk zprávy v podepsaných atributech, podpis (RSA PKCS#1 v1.5, RSA-PSS, ECDSA P-256 nebo P-384), vazbu na podpisový certifikát ESS, kritické rozšířené použití klíče timeStamping u podepisujícího a řetězec k samovydané kotvě, přičemž každý certifikát platí v genTime tokenu. Selhání se hlásí jako timestamp-authority-invalid s první kontrolou, která selhala, a authority_signature_verified je pravda pouze tehdy, když prošlo vše. Odvolání (CRL nebo OCSP) se nekontroluje. Tento ověřovač se shoduje s OpenSSL 3.0.13 na 27 vlastních vygenerovaných případech (tests/fixtures/evidence_tsa/PREREGISTRATION.md).
Token leží vedle podepsaného manifestu, nikoli uvnitř něj. Nic v podpisu ani řetězci jej nepokrývá, takže token lze odstranit nebo nahradit jiným platným tokenem, aniž by podpis selhal. verify proto upozorní, když jej balíček nemá, a --require-timestamp činí chybějící token selháním (timestamp-missing); nahrazený token se zachytí jen tehdy, když neodpovídá manifestu. attach-timestamp odmítne přepsat existující token, pokud nepředáte --replace. Pokud na časovém razítku záleží, uchovejte token a výsledek ověření ve vlastních záznamech a vyžadujte jej.
Z kódu#
kshana::evidence::build_receiver_trust_pack je jediný vstupní bod, který příkazový řádek i každý další povrch volá k vytvoření balíčku: dovnitř jde scénář, bajty záznamu (a navigačního souboru, u RINEX), okno jako text, název a volitelný čas vytvoření plus podpisový seed; ven jdou soubory, počet epoch, výřez, hash manifestu a otisk podepisujícího. Nečte žádné soubory ani hodiny, takže stejné vstupy dávají stejné bajty. Pod ním jsou create_bundle a verify_bundle čisté funkce: bajty dovnitř (EvidenceInput, 32bajtový seed, BTreeMap<String, Vec<u8>> souborů), serializovatelné výsledky ven (Files, VerifyReport). Nepoužívají soubory, hodiny ani stav procesu a sestavují se pro wasm32, takže je povrchy Python, WebAssembly a serveru volají beze změny. Příkazový řádek je jejich tenký obal.
Co balíček nedělá#
- Neprovádí znovu vyhodnocení. Kdokoli s úplným záznamem a
config.jsonmůže znovu spustitkshana receiver-trusta porovnat sepochs.json; balíček dokazuje, co bylo zaznamenáno, nikoli že engine měl pravdu. - Nedokazuje, že záznam je pravý nebo úplný. Hash úplného záznamu váže záznam k jednomu souboru; odkud tento soubor pochází, musí prokázat operátor.
- Nenese žádné tvrzení o výkonu při detekci nebo odmítnutí interference.
- Je to technický záznam, nikoli právní stanovisko ani certifikace.