Bewijspakketten: een ondertekend dossier van een GNSS-vertrouwensgebeurtenis
Een pakket is een technisch dossier, geen juridisch oordeel. Het vermeldt wat de Kshana-engine heeft berekend uit een vermelde ontvangerlog onder een vermelde configuratie, en laat iedereen nagaan dat er achteraf niets in is gewijzigd. Het zegt niet wat een gebeurtenis veroorzaakte, wie verantwoordelijk was, of aan enige verplichting is voldaan, of dat de log toont wat de ontvanger werkelijk ontving. Het manifest en de samenvatting zeggen dit allebei.
Een bewijspakket is bedoeld voor een incidentrapport, een verzekeraar of de vraag van een toezichthouder: "wat zei uw systeem over het GNSS-signaal tussen deze twee tijdstippen, en kunnen wij dat dossier vertrouwen?"
Een pakket maken#
# 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
Voor een pakket dat is gemaakt uit een laboratoriumsessie legt --lab-attestation lab.toml de verklaring van de operator over de opstelling (geleid of afgeschermd, demping of behuizing, wat enige RF uitzond) vast in het ondertekende manifest; zie HARDWARE-SAFETY.md. Ze wordt vastgelegd zoals gegeven en nooit geverifieerd.
--from en --to zijn seconden sinds de eerste epoch van de log, of een ISO-8601 UTC-tijd wanneer de log zijn eigen starttijd vermeldt. --out mag nog geen bestanden bevatten. --created-utc neemt een echte UTC-tijd zoals 2026-01-02T03:04:05Z (alles anders wordt geweigerd) of none, waarmee de aanmaaktijd wordt weggelaten en het pakket byte voor byte reproduceerbaar wordt. De aanmaaktijd is wat de ondertekenaar vermeldt; niets controleert haar tegen een klok, en daar dient het optionele tijdstempeltoken voor.
Het sleutelbestand bevat een Ed25519-seed van 32 bytes als 64 hexadecimale cijfers; lever uw eigen sleutel (bijvoorbeeld een die in uw sleutelbeheersysteem wordt bewaard) of laat keygen er een maken. kshana waarschuwt als het bestand door andere gebruikers kan worden gelezen. .gitignore sluit *.evidence-key uit; noem uw sleutel zo als hij in een checkout staat.
Wat er in een pakket zit#
| Bestand | Inhoud |
|---|---|
log-slice.bin |
de ruwe bytes van de ontvangerlog voor het venster |
config.json |
het scenario zoals gedraaid (elke monitordrempel, gebeurtenis en vergelijkingsregel), de monitors die draaiden, de kalibratiebasislijn en de scenario-hash; voor een vaartuig ook de adviserende verklaring die elke uitvoer voor een vaartuig draagt (adviserende software, geen typegoedgekeurde navigatieapparatuur); vanaf 0,36 de geometry_model (de daadwerkelijk gebruikte tabel [geometry], het gewicht van de geometriemonitor, elke herkomst en de klasseregels). Mapnamen en inline-payloads zijn verwijderd. |
epochs.json |
elke epoch in het venster met de statistieken van de engine, de alarmen (de redenen) en de vertrouwenstoestand; vanaf 0,36 ook de satelliet-geometry (goed, gedegradeerd met redenen, of onbekend waar de log geen satellietgeometrie geeft) en een class bij elke afleiding (geometry, environment, inconsistency) |
summary.html |
een op zichzelf staande samenvatting zonder scripts: identificatie, hashes, resultaten, redenen, een geometriebaan (epochs per geometriestatus) gescheiden van de klassen van de afleidingen (0,36), elke drempel, de tabel per epoch en de beperkingen van het dossier |
manifest.json |
engineversie, venster, aanmaaktijd, het formaat en de naam van de log, de SHA-256 van de volledige log, het bereik en de SHA-256 van de slice, de publieke sleutel en vingerafdruk van de ondertekenaar, de grootte en SHA-256 van elk bestand, en een hashketen over de bestanden |
manifest.sig |
Ed25519-handtekening over de exacte bytes van manifest.json |
timestamp.tsr |
optioneel RFC 3161-tijdstempeltoken |
Logslice. De lezer meldt de bron-bytespan van elke epoch, dus de slice is het kleinste bytebereik dat elke epoch in het venster bevat (byte-range): NMEA, u-blox UBX, RINEX 3-waarnemingen en Android-logs. Ze wordt gehasht als ruwe bytes, nooit als gedecodeerde tekst. Voor een livefeed of RINEX 2 is er geen span; de hele log wordt dan meegeleverd en het manifest zegt whole-log (het venster beperkt welke epochs worden gemeld, niet welke bytes zijn opgenomen). De samenvatting vermeldt welke van toepassing is. Een slice is een dossier van de bytes van het venster, geen op zichzelf staande log: een RINEX-slice heeft bijvoorbeeld geen bestandskop.
Hashketen. Het manifest somt de bestanden op in een vaste volgorde. link₀ = SHA-256("kshana- evidence-chain/1"); voor elk bestand linkᵢ = SHA-256(linkᵢ₋₁ ‖ SHA-256(file) ‖ len(name) ‖ name) (lengte als 4 bytes, big-endian). De laatste schakel is chain_head. Het herordenen, verwijderen of vervangen van een bestand verandert de keten; de handtekening dekt de keten.
Een pakket verifiëren#
kshana evidence verify pack/ --pubkey signer.key.pub [--log session.nmea] \
[--require-timestamp] [--allow-unpinned] [--json]
Exitstatus 0 betekent geverifieerd; 1 betekent dat er iets is mislukt; 2 betekent een gebruiksfout; 3 betekent dat het pakket intact is maar de ondertekenaar niet was vastgepind (zie hieronder). Elke controle meldt geslaagd, mislukt of overgeslagen, en elke mislukking wordt benoemd:
| Foutcode | Betekenis |
|---|---|
manifest-missing, manifest-malformed, unsupported-format |
het manifest kan niet worden gelezen |
signature-missing, signature-malformed, signature-invalid |
geen handtekening, geen hexadecimale handtekening van 64 bytes in kleine letters, of ze verifieert niet over manifest.json |
public-key-malformed, public-key-mismatch |
de sleutel is ongeldig, of wijkt af van de sleutel die u hebt opgegeven |
file-missing, file-size-mismatch, file-hash-mismatch |
een vermeld bestand ontbreekt of wijkt af van het manifest; het bestand wordt genoemd |
unlisted-file |
een bestand zit in het pakket maar niet in het manifest |
chain-mismatch, chain-head-mismatch |
een schakel is niet wat zijn invoer oplevert; het bestand wordt genoemd |
artifact-list-malformed, slice-record-inconsistent, epoch-count-mismatch |
het manifest spreekt zichzelf of epochs.json tegen |
full-log-hash-mismatch, slice-not-from-full-log |
met --log: de log die u hebt is niet de vastgelegde, of de slice is niet dat bereik ervan |
timestamp-missing, timestamp-malformed, timestamp-imprint-mismatch |
zie hieronder |
Het wijzigen van één byte van een bestand laat de verificatie mislukken met de bijpassende reden: een gewijzigd artefact wordt gemeld als hashafwijking van dat bestand en niets anders; een gewijzigd manifest laat de handtekening mislukken. Het opnieuw berekenen van hashes en de keten na het bewerken van een bestand helpt niet zonder de ondertekeningssleutel; dan mislukt alleen de handtekening.
Pin de sleutel vast. Zonder --pubkey bewijst een pakket alleen dat het intact is ten opzichte van de sleutel die het zelf noemt, en die kan iedereen genereren. De verificatie eindigt dan dus met 3 en zegt "INTACT, BUT THE SIGNER IS NOT PINNED" in plaats van "VERIFIED"; --allow-unpinned accepteert de eigen sleutel van het pakket bewust en drukt "VERIFIED against the key the pack names itself (signer NOT pinned; pass --pubkey)" af. Haal de publieke sleutel van de ondertekenaar bij de ondertekenaar op via een route die u vertrouwt (niet uit het pakket) en geef hem mee met --pubkey: 64 hexadecimale cijfers of het bestand .pub. Per ongeluk de privésleutel meegeven wordt herkend en geweigerd. De vingerafdruk (de eerste 128 bits van de SHA-256 van de sleutel, 32 hexadecimale cijfers) wordt afgedrukt om met het oog te vergelijken; de volledige sleutel vastpinnen is de betrouwbare route.
Pakketformaat en verificatieprocedure (normatief)#
Deze sectie is de specificatie waaruit een onafhankelijke verificateur wordt geschreven. Woorden in hoofdletters zijn eisen. Een pakket is een map (of een willekeurige afbeelding van bestandsnaam naar bytes) met bestanden die precies zo heten als hieronder; een verificateur werkt op de bytes van elk bestand zoals opgeslagen en serialiseert nooit iets opnieuw.
Bestanden. De artefactbestanden zijn, in deze vaste volgorde, log-slice.bin, config.json, epochs.json, summary.html. Een pakket heeft ook manifest.json en manifest.sig, en MAG timestamp.tsr hebben. Elke andere naam is een niet-vermeld bestand.
Hex. Elke hexadecimale tekenreeks bestaat uitsluitend uit kleine letters 0-9a-f, zonder witruimte; hoofdletters worden niet geaccepteerd. SHA-256-waarden zijn 64 cijfers, de publieke sleutel is 64 cijfers, de handtekening is 128 cijfers.
manifest.sig. De 128-cijferige hexadecimale notatie in kleine letters van de Ed25519-handtekening van 64 bytes (RFC 8032, het pure Ed25519-schema) over de exacte bytes van manifest.json, gevolgd door precies één \n en niets anders. De handtekening MOET strikt worden gecontroleerd: een handtekening waarvan de S-waarde niet kleiner is dan de grouporde L is ongeldig.
manifest.json. Een UTF-8 JSON-object. De leden ervan (een verificateur MOET elk lid vinden met het vermelde JSON-type; extra leden worden genegeerd):
| lid | type | betekenis |
|---|---|---|
format |
tekenreeks | MOET gelijk zijn aan kshana-evidence/1 |
title, engine_version, disclaimer |
tekenreeks | beschrijvend |
created_utc |
tekenreeks of null | aanmaaktijd, beschrijvend |
window |
object {from_s, to_s} |
getallen, seconden |
epochs_in_window |
niet-negatief geheel getal | het aantal items van de JSON-array in epochs.json |
log |
object | format tekenreeks, file_name tekenreeks, full_sha256 hex, full_bytes niet-negatief geheel getal, start_label tekenreeks of null, en slice |
log.slice |
object | kind (precies byte-range of whole-log; elke andere waarde maakt het manifest misvormd), start en end niet-negatieve gehele getallen (byteoffsets, einde exclusief), sha256 hex |
signer |
object | algorithm tekenreeks (ed25519), public_key hex (32 bytes), fingerprint tekenreeks |
artifacts |
array van objecten | één per artefactbestand, in de vaste volgorde: name tekenreeks, role tekenreeks, bytes niet-negatief geheel getal, sha256 hex, link hex |
chain_head |
hex | de laatste schakel |
lab_attestation |
object, optioneel | alleen aanwezig in een pakket dat is gemaakt uit een laboratoriumsessie (--lab-attestation): de verklaring van de operator operator, organisation, date, setup (conducted of shielded), attenuation_db of enclosure, emitter, statement en optioneel signature, vastgelegd zoals gegeven. Ze valt onder de handtekening van het manifest en wordt nooit geverifieerd; een verificateur meldt haar als "attested by the named operator; not verified by Kshana". Anders afwezig, en een pakket zonder haar blijft ongewijzigd |
signer.fingerprint zijn de eerste 32 hexadecimale cijfers van de SHA-256 over de 32 bytes van de publieke sleutel.
Hashketen. link0 is de SHA-256 van de 23 ASCII-bytes kshana-evidence-chain/1. Voor elk artefact in volgorde: link = SHA-256( previous_link (32 bytes) ‖ SHA-256(file) (32 bytes) ‖ len(name) as 4 bytes big-endian ‖ name as UTF-8 ), waarbij SHA-256(file) wordt genomen uit de vastgelegde sha256 van het manifest voor dat artefact (zodat de keten een eigenschap van het manifest is en daartegen wordt gecontroleerd, niet tegen de bestanden).
Procedure. Een verificateur voert deze stappen in volgorde uit en verzamelt elke mislukking; het resultaat is de verzameling mislukkingen, elk een code en, waar vermeld, het bestand name. Hij MAG NIET bij de eerste mislukking stoppen, behalve waar vermeld. pin is de publieke sleutel die de verificateur is opgedragen te vertrouwen, indien er een is.
- Geen
manifest.json: mislukkingmanifest-missing; stop. - Als
manifest.jsongeen JSON-object is met elk lid hierboven in de vermelde typen (inclusiefartifacts-items met elk lid, enlog.slice): mislukkingmanifest-malformed. Anders, alsformatniet de bovenstaande waarde is:unsupported-format. - Handtekeningfase. Deze fase wordt uitgevoerd of het manifest nu leesbaar was of niet. Als het manifest leesbaar was, is de genoemde sleutel
signer.public_key; als die niet uit 64 hexadecimale cijfers in kleine letters bestaat, mislukkingpublic-key-malformed(geen genoemde sleutel); anders, alssigner.fingerprintniet de vingerafdruk ervan is, mislukkingpublic-key-malformed(de sleutel blijft bruikbaar). Als er zowel eenpinals een genoemde sleutel bestaat en ze verschillen: mislukkingpublic-key-mismatch. De controlesleutel ispinals er een is opgegeven, anders de genoemde sleutel als die er is; met geen van beide is er geen controlesleutel (dit omvat elk geval waarin het manifest niet leesbaar was en geenpinis opgegeven). Als er geenmanifest.sigis:signature-missing. Anders, als het bestand niet precies 128 hexadecimale cijfers in kleine letters plus één\nis:signature-malformed. Anders, als er een controlesleutel is die geen geldige Ed25519-publiekesleutelcodering is:public-key-malformed. Anders, als er een controlesleutel is en de handtekening niet strikt verifieert over de bytes vanmanifest.json(wat ze ook zijn):signature-invalid. (Zonder controlesleutel wordt de handtekening niet gecontroleerd en wordt daarvoor geen mislukking gemeld.) - Als het manifest niet leesbaar was (stap 2 gaf
manifest-malformed): stop hier. - Artefacten. Als de namen in
artifactsniet precies de vier hierboven zijn, in volgorde:artifact-list-malformed. Daarna, voor elk item van het manifest, in volgorde: als het pakket geen bestand van die naam heeft,file-missing(naam); anders, als de lengte afwijkt vanbytes,file-size-mismatch(naam); en, onafhankelijk daarvan, als de SHA-256 van het bestand afwijkt vansha256,file-hash-mismatch(naam). - Niet-vermelde bestanden: voor elk bestand in het pakket behalve
manifest.json,manifest.sig,timestamp.tsren de namen inartifactsvan het manifest:unlisted-file(naam). - Keten. Doorloop de
artifactsvan het manifest in volgorde vanaflink0. Als desha256van een item niet uit 64 hexadecimale cijfers in kleine letters bestaat:manifest-malformeden de keten wordt verlaten (geen controle van het hoofd). Bereken anders de schakel; als die afwijkt van delinkvan het item:chain-mismatch(naam). Na de doorloop, als de keten niet is verlaten en de laatste schakel afwijkt vanchain_head:chain-head-mismatch. - Slice en epochs. Meld
slice-record-inconsistent(ten hoogste één keer per reden, en een verificateur meldt de code één keer, ongeacht hoeveel redenen gelden) als een van deze geldt:start > end;end > full_bytes; desha256van het itemlog-slice.binwijkt af vanslice.sha256; debytesvan dat item wijkt af vanend - start; ofkindiswhole-logen een vanstart != 0,end != full_bytes,slice.sha256 != full_sha256geldt. Alsepochs.jsonbestaat en als JSON-array wordt geparseerd waarvan de lengte afwijkt vanepochs_in_window:epoch-count-mismatch. - Volledige log. Alleen als de verificateur de oorspronkelijke logbytes heeft gekregen: als hun SHA-256 afwijkt van
log.full_sha256,full-log-hash-mismatch; anders, als het bereikstart..endniet binnen de log valt of de bytes van de log daar afwijken vanlog-slice.bin,slice-not-from-full-log. - Tijdstempel. Zonder
timestamp.tsr: als de verificateur is opgedragen er een te eisen,timestamp-missing; anders niets. Met een: als het niet kan worden gelezen als RFC 3161-token of -respons (zie hieronder),timestamp-malformed; anders, als de message imprint niet de hash is, in het eigen algoritme van de imprint (SHA-256, SHA-384 of SHA-512), van de bytes vanmanifest.json:timestamp-imprint-mismatch.
Het pakket is precies dan geverifieerd wanneer de verzameling mislukkingen leeg is. Of de ondertekenaar was vastgepind, wordt apart gemeld en verandert de verzameling mislukkingen niet.
Tijdstempeltoken. Een token is een DER-ContentInfo waarvan het inhoudstype CMS SignedData is (1.2.840.113549.1.7.2) en waarvan het ingekapselde inhoudstype id-ct-TSTInfo is (1.2.840.113549.1.9.16.1.4); een respons is een DER-TimeStampResp (een PKIStatusInfo gevolgd door het token) waarvan de status 0 of 1 is. Het message-imprintalgoritme van de TSTInfo moet SHA-256, SHA-384 of SHA-512 zijn met een digest van de lengte van dat algoritme, en de genTime ervan een echte UTC-kalendertijd. BER met onbepaalde lengte wordt niet geaccepteerd. De handtekening en de certificaatketen van de autoriteit worden alleen gecontroleerd wanneer vertrouwde ankers zijn opgegeven (--tsa-ca, hieronder).
Het bewijslabel (0,40)#
Een pakket dat door 0,40 of later is gebouwd draagt, binnen config.json, een vermelding evidence (het niveau, VALIDATED, MODELLED of PARTNER, het zwakste van de rijen ervan, en de id's en eisen van de rijen van de verificatiematrix waarop de inhoud van het pakket berust: de rij voor pakketintegriteit, de rijen voor receiver-trust, en voor die pakketten de rijen voor jammertest-scoring of round-trip) en config_version "1.1". Het staat binnen config.json, dus de bestandshash van het manifest, de hashketen en de handtekening dekken het: het wijzigen ervan laat de verificatie falen. kshana evidence verify meldt het niveau en de rij-id's.
De wijziging is additief en alleen voorwaarts. De format van het manifest blijft kshana-evidence/1: de normatieve tekst hierboven zegt dat een verificatieprogramma die waarde MOET vinden, en het clean-room-verificatieprogramma in scripts/ doet dat. De minorversie wordt daarom gedragen door config_version (afwezig bij een pakket van vóór 0,40, wat 1,0 betekent), niet door een nieuwe string format. Een pakket van vóór 0,40 heeft geen label en wordt ongewijzigd geverifieerd; kshana evidence verify zegt "no evidence label (pre-0.40 pack)". De tests zijn tests/evidence_pack_label.rs (inclusief de vastgelegde pakketten van vóór 0.40 met een onafhankelijke referentie) en tests/evidence_labels_doc_sync.rs.
RFC 3161-tijdstempel (optioneel)#
Een tijdstempeltoken van een tijdstempelautoriteit toont dat het manifest bestond op een tijdstip dat de autoriteit bevestigt. Het token gaat over manifest.json, dus het wordt aangevraagd nadat het pakket is gemaakt (er is geen manier om er een mee te geven wanneer het pakket wordt gemaakt, en create_bundle weigert een token dat niet over precies dat manifest gaat):
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 leest het token (een kaal token of een volledige respons), controleert dat het is verleend en dat zijn message imprint gelijk is aan de hash van manifest.json, en drukt het tijdstip af dat het vermeldt. Zonder vertrouwde ankers voegt het "timestamp authority signature not verified by Kshana" toe en meldt authority_signature_verified: false; voer voor dat deel de regel openssl ts -verify hierboven uit. Een token waarvan de imprint afwijkt, of dat niet parseert, is een mislukking. De inhoudstypen, de imprintlengte en de generatietijd van het token worden op zinnigheid gecontroleerd.
Met --tsa-ca authority-ca.pem (een of meer PEM-certificaten, allemaal vertrouwd) controleert verify ook de autoriteit, offline, zoals openssl ts -verify dat doet: de message digest van de ondertekende attributen, de handtekening (RSA PKCS#1 v1.5, RSA-PSS, ECDSA P-256 of P-384), de ESS-binding met het ondertekeningscertificaat, het kritieke extended key usage timeStamping van de ondertekenaar, en de keten naar een zelfuitgegeven anker, waarbij elk certificaat geldig is op de genTime van het token. Een mislukking wordt gemeld als timestamp-authority-invalid met de eerste controle die mislukte, en authority_signature_verified is alleen true wanneer alles slaagde. Intrekking (CRL of OCSP) wordt niet gecontroleerd. Deze verificateur komt overeen met OpenSSL 3.0.13 op 27 zelfgegenereerde gevallen (tests/fixtures/evidence_tsa/PREREGISTRATION.md).
Het token staat naast het ondertekende manifest, niet erin. Niets in de handtekening of de keten dekt het, dus een token kan worden verwijderd, of een ander geldig token kan worden vervangen, zonder dat de handtekening mislukt. verify meldt daarom wanneer een pakket er geen heeft, en --require-timestamp maakt een ontbrekend token tot een mislukking (timestamp-missing); een vervangen token wordt alleen opgemerkt als het niet bij het manifest past. attach-timestamp weigert een bestaand token te overschrijven tenzij u --replace meegeeft. Als een tijdstempel voor u van belang is, bewaar dan het token en het verificatieresultaat bij uw eigen gegevens, en eis het.
Vanuit code#
kshana::evidence::build_receiver_trust_pack is het ene toegangspunt dat de opdrachtregel en elk ander oppervlak aanroepen om een pakket te maken: een scenario, de bytes van de log (en die van het navigatiebestand, voor RINEX), het venster als tekst, een titel en een optionele aanmaaktijd, plus de ondertekeningsseed gaan erin; de bestanden, het aantal epochs, de slice, de manifesthash en de vingerafdruk van de ondertekenaar komen eruit. Het leest geen bestanden en geen klok, dus dezelfde invoer geeft dezelfde bytes. Daaronder zijn create_bundle en verify_bundle zuivere functies: bytes erin (EvidenceInput, een seed van 32 bytes, een BTreeMap<String, Vec<u8>> van bestanden), serialiseerbare resultaten eruit (Files, VerifyReport). Ze gebruiken geen bestanden, klok of processtatus en bouwen voor wasm32, zodat de Python-, WebAssembly- en serveroppervlakken ze ongewijzigd aanroepen. De opdrachtregel is een dunne laag eromheen.
Wat een pakket niet doet#
- Het draait de beoordeling niet opnieuw. Iedereen met de volledige log en
config.jsonkankshana receiver-trustopnieuw draaien en vergelijken metepochs.json; het pakket bewijst wat is vastgelegd, niet dat de engine gelijk had. - Het bewijst niet dat de log echt of volledig is. De hash van de volledige log koppelt het dossier aan één bestand; waar dat bestand vandaan kwam, moet de operator vaststellen.
- Het bevat geen bewering over prestaties bij het detecteren of afwijzen van interferentie.
- Het is een technisch dossier en geen juridisch oordeel en geen certificering.