Zum Inhalt springen

Unabhängiges F&E-Projekt · Köln

So funktioniert die Verifizierung

Diese Seite verwendet den Live-Demonstrationspass, kein Mock-up. Der erste Teil sind fünf Schritte in einfacher Sprache: was jeder tut und was er nicht belegt. Der zweite Teil ist dieselbe Prüfung als Befehle, für alle, die sie lieber ausführen als darüber lesen.

Die fünf Schritte

  1. Die Credential herunterladen

    Jede Passseite bietet ihre Credential als signiertes Token an, eine einzige Textzeile. Laden Sie sie herunter. Alles Weitere arbeitet mit dieser Datei, nie mit der Seite, die sie anzeigt.

    Das belegt, dass Sie dasselbe Artefakt halten, das der Aussteller veröffentlicht hat. Es belegt nicht, dass irgendetwas darin wahr ist.

  2. Die Signatur des Ausstellers prüfen

    Das Token trägt eine Signatur über seinen Inhalt. Ein Verifizierer rechnet die Prüfung mit dem öffentlichen Schlüssel des Ausstellers nach; ändern Sie ein Zeichen des Inhalts, und die Prüfung schlägt fehl.

    Das belegt, dass der Inhalt sich nicht verändert hat, seit der Inhaber dieses Schlüssels ihn signiert hat. Es belegt nicht, wer den Schlüssel hält: Das ist der nächste Schritt.

  3. Die Identität des Ausstellers prüfen

    Der Aussteller heißt did:web:anydpp.eu, was sich zu einem öffentlichen Identifikationsdokument unter anydpp.eu auflöst. Der in Schritt zwei verwendete Schlüssel muss dort gelistet sein, unter dem eigenen Namen und der eigenen Domain des Ausstellers.

    Das belegt, dass der Schlüssel dem gehört, der diese Domain kontrolliert. Es belegt nicht, dass der Inhaber der Domain in der realen Welt der ist, der er zu sein behauptet.

  4. Die Statusliste prüfen

    Die Credential nennt eine Statusliste, veröffentlicht und signiert von demselben Aussteller, und ihre eigene Position darin. Lesen Sie das Bit an dieser Position: 0 heißt nicht widerrufen, 1 heißt widerrufen.

    Das belegt, ob der Aussteller die Credential seit der Ausstellung zurückgezogen hat. Es belegt nicht, warum, und es prüft nichts nach, was die Credential aussagt.

  5. Die Nachweisverweise prüfen

    Die Credential listet die Quelldokumente, aus denen sie gebaut wurde, jedes mit Kennung und mit einem Digest des Datensatzes, und nennt zu jedem Attribut das Quelldokument und die Konfidenzstufe. Die Dokumente selbst enthält sie nicht.

    Das belegt, welche Dokumente der Aussteller gelesen zu haben angibt, und erlaubt dem Inhaber eines Dokuments zu zeigen, dass es dasselbe ist. Es belegt nicht, dass diese Dokumente echt oder ihre Inhalte wahr sind.

Demonstrationspass öffnen

So funktioniert die Prüfung

Vom Quelldokument bis zu einer Signatur, die ein Dritter prüfen kann. Die Methode, so weit wir sie veröffentlichen.

Dieser Film: KI-gestützte Produktion, von Menschen geprüfte Inhalte. Synthetische Sprachausgabe auf Englisch, Untertitel in fünf Sprachen. Barrierefreiheit und KI-Transparenz

Was sich geändert hat

Die ersten Demonstrations-Credentials, ausgestellt im Juli 2026, trugen einen als Ed25519Signature2020 bezeichneten Beweis, dessen signierte Bytes nicht der kanonischen Form dieser Suite entsprachen. Eine echte Signatur, aber eine, die kein standardbasierter Verifizierer akzeptierte, sodass die einzige verfügbare Prüfung die von AnyLAI selbst war. Dieses Beweisformat ist veraltet: Die vier Pässe, die es tragen, bleiben als historischer Nachweis lesbar, so gekennzeichnet, ohne Prüfschaltfläche.

Die aktuellen Demonstrations-Credentials werden als signiertes Token in der Form ausgestellt, die der W3C (World Wide Web Consortium)-Standard für Verifiable Credentials für umhüllende Beweise definiert, mit dem unter did:web:anydpp.eu veröffentlichten Ausstellerschlüssel und einer signierten Statusliste für den Widerruf. Die Schritte oben und die Befehle unten verwenden nur dieses Token, diesen Schlüssel und diese Liste.

Was eine Verifizierung nicht belegt

Eine Verifizierung, die jeden der obigen Schritte besteht, beantwortet eine Frage: Ist dies die Credential, die der Aussteller veröffentlicht hat, und steht der Aussteller noch dahinter. Diese Fragen beantwortet sie nicht:

Drei verschiedene Dinge werden leicht als eines gelesen: die kryptografische Echtheit, die die obigen Schritte belegen; die Wahrheit der Quellen, die nur die Dokumente und die Menschen dahinter belegen können; und die regulatorische Hinlänglichkeit, über die der Marktteilnehmer, der die Waren in Verkehr bringt, und die zuständige Behörde befinden.

  • Die Wahrheit jeder Aussage in einem Quelldokument. Ein Digest beweist, dass ein Dokument dasjenige ist, das gelesen wurde, nicht, dass sein Inhalt zutrifft.
  • Die Einhaltung des Rechts. Die Credential hält Nachweise fest und fällt kein Urteil darüber, ob Waren auf irgendeinem Markt in Verkehr gebracht werden dürfen.
  • Die Vollständigkeit der Sorgfaltspflicht. Sie listet die gelesenen Dokumente, nicht die Dokumente, die die eigenen Pflichten eines Marktteilnehmers noch verlangen können.
  • Eine Zertifizierung irgendeiner Art. Niemand hat Erzeuger, Parzelle oder Sendung auf Grundlage dieser Credential auditiert.
  • Eine behördliche Zulassung. Keine zuständige Behörde hat die Credential, den Aussteller oder die Methode geprüft oder bestätigt.

Für Ingenieure: dieselbe Prüfung als Befehle

Alles Folgende läuft ohne Repository und ohne Konto: drei Downloads, ein Verifizierer von etwa dreißig Zeilen in Node oder Python, ein Manipulationstest und die veröffentlichten Werte zum Vergleich. Die Befehle sind nicht übersetzt; ein übersetzter Befehl funktioniert nicht mehr.

Copy and paste

  1. 1. Download the three artefacts

    # 1. the credential, as a compact JWS (application/vc+jwt)
    curl -s https://anylai.eu/api/corridor/passports/DEMO26-000001/credential.jwt -o DEMO26-000001.jwt
    
    # 2. the issuer's DID document (the public key lives HERE, not in the credential)
    curl -s https://anydpp.eu/.well-known/did.json -o did.json
    
    # 3. the status list credential (an EnvelopedVerifiableCredential around its own JWS)
    curl -s https://anylai.eu/api/corridor/status-list/1 -o status-list-1.json
    
    # what you downloaded
    sha256sum DEMO26-000001.jwt

    The credential is one line of text: header, payload and signature, base64url-encoded and dot-separated. The DID document is fetched from the issuer's own domain, never taken from inside the credential: a forged credential must not be allowed to vouch for itself.

  2. 2. Verify it in Node (one file, one dependency)

    // verify.mjs  --  npm i jose  --  node verify.mjs
    import { compactVerify, importJWK } from "jose";
    import { gunzipSync } from "node:zlib";
    
    const b64 = (s) => Buffer.from(s, "base64url");
    const jws = (await (await fetch("https://anylai.eu/api/corridor/passports/DEMO26-000001/credential.jwt")).text()).trim();
    const header = JSON.parse(b64(jws.split(".")[0]));
    if (header.typ !== "vc+jwt" || header.cty !== "vc") throw new Error("not a vc+jwt");
    
    const did = header.kid.split("#")[0];
    const didDoc = await (await fetch(`https://${did.replace("did:web:", "")}/.well-known/did.json`)).json();
    const method = didDoc.verificationMethod.find((m) => m.id === header.kid);
    if (!method || !didDoc.assertionMethod.includes(header.kid)) throw new Error("kid not under assertionMethod");
    
    const key = await importJWK(method.publicKeyJwk, header.alg);
    const { payload } = await compactVerify(jws, key); // throws JWSSignatureVerificationFailed on a bad signature
    const vc = JSON.parse(Buffer.from(payload).toString("utf8"));
    if (vc.issuer.id !== method.controller) throw new Error("key controller is not the issuer");
    console.log("signature: valid | issuer:", vc.issuer.id, "| kid:", header.kid);
    
    const wrapper = await (await fetch(vc.credentialStatus.statusListCredential)).json();
    const listJws = wrapper.id.replace("data:application/vc+jwt,", "");
    const listHeader = JSON.parse(b64(listJws.split(".")[0]));
    const listMethod = didDoc.verificationMethod.find((m) => m.id === listHeader.kid);
    const listKey = await importJWK(listMethod.publicKeyJwk, listHeader.alg);
    const list = JSON.parse(Buffer.from((await compactVerify(listJws, listKey)).payload).toString("utf8"));
    const encoded = list.credentialSubject.encodedList;
    if (!encoded.startsWith("u")) throw new Error("encodedList is not multibase base64url");
    const bits = gunzipSync(b64(encoded.slice(1)));
    const index = Number(vc.credentialStatus.statusListIndex);
    const revoked = (bits[index >> 3] >> (7 - (index & 7))) & 1;
    console.log("status:", revoked ? "revoked" : "active", "| status list signature: valid");

    Steps two to four of the page above, as code. It throws on a bad signature, on a key that is not listed under the issuer's assertionMethod, on a controller that is not the issuer, and on a status list that is not signed by the same key.

  3. 3. Or in Python

    # verify.py  --  pip install pyjwt cryptography requests  --  python verify.py
    import base64, gzip, json, jwt, requests
    
    jws = requests.get("https://anylai.eu/api/corridor/passports/DEMO26-000001/credential.jwt").text.strip()
    header = jwt.get_unverified_header(jws)
    assert header["typ"] == "vc+jwt" and header["cty"] == "vc"
    
    did = header["kid"].split("#")[0]
    did_doc = requests.get(f"https://{did.removeprefix('did:web:')}/.well-known/did.json").json()
    method = next(m for m in did_doc["verificationMethod"] if m["id"] == header["kid"])
    assert header["kid"] in did_doc["assertionMethod"]
    
    key = jwt.algorithms.OKPAlgorithm.from_jwk(json.dumps(method["publicKeyJwk"]))
    vc = jwt.decode(jws, key=key, algorithms=["EdDSA"])  # raises InvalidSignatureError on a bad signature
    assert vc["issuer"]["id"] == method["controller"], "key controller is not the issuer"
    print("signature: valid | issuer:", vc["issuer"]["id"], "| kid:", header["kid"])
    
    wrapper = requests.get(vc["credentialStatus"]["statusListCredential"]).json()
    list_jws = wrapper["id"].removeprefix("data:application/vc+jwt,")
    list_header = jwt.get_unverified_header(list_jws)
    list_method = next(m for m in did_doc["verificationMethod"] if m["id"] == list_header["kid"])
    list_key = jwt.algorithms.OKPAlgorithm.from_jwk(json.dumps(list_method["publicKeyJwk"]))
    status_list = jwt.decode(list_jws, key=list_key, algorithms=["EdDSA"])
    encoded = status_list["credentialSubject"]["encodedList"]
    assert encoded.startswith("u"), "encodedList is not multibase base64url"
    body = encoded[1:]
    bits = gzip.decompress(base64.urlsafe_b64decode(body + "=" * (-len(body) % 4)))
    index = int(vc["credentialStatus"]["statusListIndex"])
    print("status:", "revoked" if bits[index // 8] >> (7 - index % 8) & 1 else "active", "| status list signature: valid")

    The same check with PyJWT and cryptography. Both verifiers are the reader's own code calling an open-source library; neither calls anything of AnyLAI's except the three public downloads.

  4. 4. Prove the check has teeth

    // tamper.mjs -- change ONE character of the payload segment and the signature check must fail
    import { compactVerify, importJWK } from "jose";
    const jws = (await (await fetch("https://anylai.eu/api/corridor/passports/DEMO26-000001/credential.jwt")).text()).trim();
    const [h, p, s] = jws.split(".");
    const flipped = p.slice(0, 10) + (p[10] === "A" ? "B" : "A") + p.slice(11);
    const didDoc = await (await fetch("https://anydpp.eu/.well-known/did.json")).json();
    const key = await importJWK(didDoc.verificationMethod[0].publicKeyJwk, "EdDSA");
    await compactVerify(`${h}.${flipped}.${s}`, key)
      .then(() => { throw new Error("TAMPERED CREDENTIAL VERIFIED: stop trusting this check"); })
      .catch((e) => console.log("tampered credential rejected:", e.code ?? e.message));

    Feed a credential with one character of its payload changed to the same verifier. If it still passes, the verification is not doing anything and you should stop trusting it. That is the test worth running first.

Published values

Published values for the demonstration credential
Credential (compact JWS)https://anylai.eu/api/corridor/passports/DEMO26-000001/credential.jwt
Credential (JSON-LD envelope)https://anylai.eu/api/corridor/passports/DEMO26-000001/credential.jsonld
DID documenthttps://anydpp.eu/.well-known/did.json
Status listhttps://anylai.eu/api/corridor/status-list/1
kiddid:web:anydpp.eu#key-1
alg / typ / ctyEdDSA / vc+jwt / vc
Expected outcomesignature valid; kid under assertionMethod; controller = issuer; status active
SHA-256 of DEMO26-000001.jwtpublished in the release record

The digest is read from the live passport API when this page is viewed, so it names the artefact actually being served; compare it with the output of sha256sum above.

Das Regulierungsradar, einmal im Monat

Was sich bei EU-Entwaldungs- und Produktpass-Regeln tatsächlich geändert hat, mit Quelle je Punkt und sonst nichts im Umschlag.

Double Opt-in: Sie bestätigen über einen Link in Ihrem Postfach und können sich aus jeder Ausgabe mit einem Klick abmelden.

Machen Sie es selbst

Der Demonstrationspass ist öffentlich. Seine Credential lässt sich mit den Befehlen auf dieser Seite gegen den unter did:web:anydpp.eu veröffentlichten Ausstellerschlüssel prüfen, ohne Konto.