TracePass
Riferimento

Verificare la credenziale di un passaporto

Scaricate la credenziale W3C firmata di un passaporto, risolvete la chiave did:web e verificate la firma ES256 da soli, con curl, Node o Python.

Ogni versione di un passaporto TracePass pubblicato è emessa come verifiable credential W3C firmata nel formato del passaporto digitale di prodotto UNTP 0.7.0. Potete verificare che i dati di un passaporto provengono da TracePass e non sono stati modificati, senza account e senza chiederci nulla. Questa pagina lo mostra sul passaporto demo pubblico.

1. Scaricate la credenziale

bash
curl --http2 -H "Accept: application/vc+jwt" \
  https://id.tracepass.eu/01/99999999999997/21/6B27A0000001 \
  -o credential.jwt

L'URL stesso del passaporto restituisce la credenziale quando chiedete application/vc+jwt. Gli URL dei passaporti richiedono HTTP/2 (EN 18216), quindi gli strumenti che parlano solo HTTP/1.1 ricevono 505. L'header Link della risposta, il linkset del passaporto e l'intestazione della pagina HTML segnalano la credenziale. Un passaporto senza una versione corrente firmata risponde 406, e un passaporto sospeso, scaduto o archiviato non ne serve nessuna.

2. Leggete l'intestazione

La credenziale è un JWS compatto. La sua intestazione protetta indica come è stata firmata:

json
{
  "alg": "ES256",
  "typ": "vc+jwt",
  "cty": "vc",
  "kid": "did:web:www.tracepass.eu#key-1"
}

Il payload è la credenziale stessa (VC-JOSE-COSE), non un claim vc di VC 1.1. kid indica l'emittente e la chiave con cui è stata firmata.

3. Risolvete la chiave dell'emittente

L'emittente è did:web:www.tracepass.eu. Un identificatore did:web si risolve in un documento su quel dominio, qui https://www.tracepass.eu/.well-known/did.json, che pubblica la chiave pubblica con il kid dell'intestazione. La firma lega la credenziale al controllo del dominio.

4. Verificate la firma

Entrambi gli esempi recuperano la chiave e verificano la firma ES256:

javascript
// npm install jose
import { readFileSync } from "node:fs";
import { decodeProtectedHeader, importJWK, jwtVerify } from "jose";

const jws = readFileSync("credential.jwt", "utf8").trim();
const { kid } = decodeProtectedHeader(jws); // did:web:www.tracepass.eu#key-1
const did = kid.split("#")[0];
// did:web:<host> resolves to https://<host>/.well-known/did.json
const doc = await (await fetch(`https://${did.slice("did:web:".length)}/.well-known/did.json`)).json();
const method = doc.verificationMethod.find((m) => m.id === kid);
const { payload } = await jwtVerify(jws, await importJWK(method.publicKeyJwk, "ES256"), { typ: "vc+jwt" });
console.log("Verified. Issuer:", payload.issuer.id, "Product:", payload.credentialSubject.id);
python
# pip install "pyjwt[crypto]" requests
import jwt, requests

token = open("credential.jwt").read().strip()
kid = jwt.get_unverified_header(token)["kid"]  # did:web:www.tracepass.eu#key-1
did = kid.split("#")[0]
# did:web:<host> resolves to https://<host>/.well-known/did.json
doc = requests.get(f"https://{did.removeprefix('did:web:')}/.well-known/did.json").json()
jwk = next(m["publicKeyJwk"] for m in doc["verificationMethod"] if m["id"] == kid)
claims = jwt.decode(token, jwt.PyJWK(jwk).key, algorithms=["ES256"])
print("Verified. Issuer:", claims["issuer"]["id"], "Product:", claims["credentialSubject"]["id"])

Una credenziale modificata dopo la firma, anche di un solo carattere, fallisce con un errore di firma.

5. Controllate il formato (facoltativo)

Per confermare che la credenziale segue UNTP 0.7.0, validatela rispetto allo schema ufficiale UNTP:

python
# pip install jsonschema requests "pyjwt[crypto]"
import jwt, requests
from jsonschema import Draft202012Validator

token = open("credential.jwt").read().strip()
credential = jwt.decode(token, options={"verify_signature": False})  # verify first, as above
for claim in ("iss", "sub", "iat"):
    credential.pop(claim, None)  # JWT claims, not part of the credential
schema = requests.get("https://untp.unece.org/artefacts/schema/v0.7.0/dpp/DigitalProductPassport.json").json()
errors = [e.message for e in Draft202012Validator(schema).iter_errors(credential)]
print("UNTP 0.7.0 schema errors:", errors or "none")

UNTP richiede inoltre l'identificativo dello stabilimento di produzione e il paese di produzione. La credenziale li contiene quando il passaporto li registra e altrimenti li omette, quindi è pienamente valida rispetto allo schema UNTP solo per quei passaporti. Il passaporto demo si valida senza errori.

Che cosa dimostra una firma valida

Che TracePass ha emesso esattamente questi dati per questa versione del passaporto e che nessuno li ha modificati da allora. È una firma did:web: dimostra il controllo di tracepass.eu, con la stessa forza di HTTPS. Non è un sigillo elettronico qualificato eIDAS.

Copre solo la vista pubblica del passaporto, mai i campi ad accesso riservato o per le autorità, quindi non è il registro legale completo. Non esiste ancora una lista di revoca: una copia già in possesso di qualcuno continua a risultare valida anche dopo la sospensione del passaporto, quindi verificate lo stato attuale nel passaporto pubblicato.