Verifica Green Pass API

ecco, l’avevo ipotizzato :rofl: :rofl: :rofl:

1 Mi Piace

Tra l’altro sembra che non ci troviamo nel caso di dipendenti che hanno abusato dei frontend ufficiali. Ad essere imputati sono alcuni frontend (pare macedoni) in cui era stato installato il server di prova, che genera i GP validi a scopo di test solo in “anteprima”, che sono stati lasciati accessibili dall’esterno pubblicamente.
Sono stati trovati, e usati. Se fosse confermato è quasi peggio di avere qualche mela marcia in qualche ufficio, in quanto in quel caso trovato l’ente emettitore si poteva pensare di invalidare le chiavi ad esso associate (come fa la Francia pare, da quando hanno avuto il leak del GP di Macron). Invece con i frontend di test, al momento, l’unica possibilità è back-listare i GP da esso generati man mano che si identificano.

Dei fenomeni quindi, ambienti di test con chiavi di produzione… in macedonia, in vietnam (vietnam?) :woman_facepalming:

1 Mi Piace

Robe da “rutti”.
comunque, giusto per la cronaca, a me quei greenpass che mostrano nei tweet già li da invalidi.
Penso abbiano già fatto piazza pulita.
Sarà da capire l’entità del danno e se non sono state “offerte” (perché mettere robe di test pubbliche con dati di prod è offrire) altre chiavi.
Per altro nelle issue ne trovo una proprio di due giorni fa:

1 Mi Piace

Ho scritto un app javascript che mi permette di leggere tutti i dati che sono scritti nel mio qrcode/greenpass:

L’app ignora completamente firme e certificati perchè non mi interessano… cioè non mi interessavano, ora con tutta questa baraonda che è uscita fuori sui green pass di Mickey Mouse, mi è venuta la curiosità di capire con quale chiave pubblica è stato firmato il mio certificato, ma non sono ancora riuscito a districarmi bene negli algoritmi di decodifica…

Aggiungendo qualche riga di codice al mio script, sono riuscito per ora solo a tirare fuori il KID (che manco so cosa sia…), che è contenuto nell’header del payload; ma la parte “signature” del payload mi rimane ancora oscura.

Praticamente sono arrivato a questo punto:

[protected_header, unprotected_header, cbor_data, signature] = CBOR.decode(unzipped);

In cbor_data ci sono i dati JSON del greenpass, e vabbè.
unprotected_header è vuoto.
Ho scoperto che in protected_header c’è il KID.
In signature, invece, ho trovato diversa “roba” che non so proprio come leggere.

Questo è il codice che ho trovato per estrarre il KID:

protected_headerArr = typedArrayToBuffer(protected_header);
protected_headerData  = CBOR.decode(protected_headerArr);
let kid  = protected_headerData['4'];
kidReduced = kid.reduce ( (str, v) => str + String.fromCharCode(v), "")
kidFinal = btoa(kidReduced);

Gli unici greenpass di cui conosco a priori il KID sono quelli citati in questo thread: Possible leak of private keys · Discussion #105 · ehn-dcc-development/hcert-spec · GitHub

Ho fatto la prova con quello di A. H.:
HC1:6BFOX141AZPOPS30OU TSUO9-Q1CO9IIE *V%DTBQ98PTSAJ+3R. G9UFNUDSTESCSA24/E02BNT84/L8PW1F-9ZPU.C4YVA3JK3/NITT9ES2:0P04R-T-P77SB*Z9C5O R2V/FFPKYJIBM98TG+APC:E:*BAETT75XE36N4.PF30PC3LU+KYLNKZ2E7DZXG0CPL+8/N36FHRICSGEKP1X9GSQ79CGCTVYRCLQ7Q.K.J2%8VSR9YA8 16.24W79O44AA2X$IRC5EV42C4GVGX621A0YTUNXKPPOSUKHJAB32L5AP67RVOXTNBUTPZQJ9B1H71+4KUVKLLZ+64DMO964C4XIMBZG.OJZHB60QYU3:.J5SI%U0GFRYRRXKDLP9/GRM$58SE+VGI1N-BI%0Q%PNUO9A00854P%Q3X2-S5XLQ2X3JQUOEV:9W8DRGNUHBR:.NZENY*3JC0.O6 65O.R4%5R03K+A0HCD-S-IF-V7D$VD-N$CS9OU7FP4ZF-07DUSRLQ9LT:8DWUE

Con il codice di cui sopra, ottengo questo:

image

Dall’elemento ‘4’ ricavo il KID e viene HhkeqvrtQ0U= , quindi funziona, ma X e Y cosa sono e come le ricavo dalla stringa HC1?
La chiave pubblica quale sarebbe, e come faccio a sapere se è la stessa usata per il mio greenpass? (So che non lo è, ma immagino che la storia non finisca qui e presto succederà un casino anche con le chiavi italiane…)

Applicando il suddetto algoritmo a signature viene fuori questo:

image

Ora cosa ci faccio con questa roba?!?

Creado che tu abbia sbagliato dal principio, il greenpass non viene firmato con la chiave pubblica.
Ma con la chiave privata, quella pubblica viene usata per “verificare” che sia stato firmato.
la voce signature (non ho approfondito)ma dovrebbe essere la chiave pubblica con cui decodificare.
Se avessero messo dentro il greenpass la chiave privata tutto sto macello sarebbe stato inutile :smiley:
Con quella puoi verificare con quale chiave pubblica puoi decodificare e se è presente in
https://get.dgc.gov.it/v1/dgc/signercertificate/status
In maniera da capire se è una chiave pubblica “presente”

1 Mi Piace

Sì ma ci sarà pure “qualcosa” che viene confrontato con “qualcos’altro” per capire se il greenpass è genuino.
Cosa viene confrontato con cosa?
Forse proprio il KID? Dove trovo documentazione tecnica? L’ho trovata solo sul formato del payload JSON.

Per la doc non so, ma per quello che vuoi verificare tu, in JS puoi guardare qua dentro, trovi tutto:

Alla fine questa è una SDK non “legalizzata”

Quanto meno l’ambaradan green pass diffonde un po’ di cultura sulle firme elettroniche anche fra gli addetti ai lavori (sviluppatori).
Il futuro potrebbe essere roseo, speriamo di essere in grado di selezionare bene e far restare solo le cose buone.

Almeno adesso una cosa in più l’ho capita: il “KID” identifica l’ente emittente del greenpass; qui c’è una lista non ufficiale, una ufficiale non saprei dove prenderla:

Il tizio li chiama “certificati” ma non mi pare siano certificati, mah.

Invece quest’altro tizio ha publicato il codice per decodificare la firma:

Solo che nel repository questo file non esiste:

assets/it_dgc_public_keys.json

Forse dovrebbe contenere l’elenco dei certificati scaricabili “a ripetizione” dal link https://get.dgc.gov.it/v1/dgc/signercertificate/update ?

Tutte le cose ufficiali le trovi qua:
https://dgcg.covidbevis.se/tp/
Clicca su inspect TRUSTLIST e hai la lista completa. Credo sia questo che stai cercando.

Permane lo stesso problema: non capisco cosa devo cofrontare con cosa.

ciao

il KID identifica il singolo certificato.
Per scaricare i certificati validi devi “ciclare” sul valore dell’header X-FORWARDED-FOR, come faccio qui:

come vedi quando scarichi un certificato ottieni anche il suo KID quindi puoi usare questa informazione (come fa l’app e l’sdk) per validare la firma del DGC:

  • dal DGC leggi il KID con cui è stato firmato
  • prendi il certificato con tale KID
  • validi la firma del DGC con tale certificato

bye

2 Mi Piace

Il server è permaloso per il CORS, non accetta connessioni da browser…
Ho provato anche con un mio proxy php, ma non mi restituisce gli header corretti.
Come posso accedere ai certificati senza usare node.js ma solo il browser?

E poi resta il problema della firma: non riesco a capire come elaborare il “signature” di questo output:

[protected_header, unprotected_header, cbor_data, signature] = CBOR.decode(unzipped);

Ma poi possibile che i certificati siano solo ad accesso seriale?!? Se a ognuno è associato un KID, cioè un indice, probabilmente c’è un modo per chiedere solo il certificato associato a quel KID. Sennò a che serve il KID? tanto varrebbe usare al posto del KID il nome per esteso dell’ente emittente, con spazi, virgole, apostrofi e compagnia bella.

E poi resta il problema della firma: non riesco a capire come elaborare il “signature”

dai una occhiata al metodo doVerify della libreria cose-js, usata ad esempio da dcc-utils appunto per la validazione della firma:

per quanto riguarda l’accesso ai certificati, penso di aver capito cosa vorresti fare (una soluzione completamente “browser-based” di validazione): non credo che l’endpoint ministeriale sia pensato per tale scopo e che consenta un download “selettivo” (ma se trovi il modo felice di essermi sbagliato!). L’app, l’sdk etc infatti scaricano periodicamente i certificati da tale endpoint e li memorizzano localmente indicizzandoli appunto per il loro KID (che trovi come header di risposta alla chiamata). Le validazioni sono fatte quindi “offline”, prendendo il KID del GP e usandolo per recuperare dalla cache locale il relativo certificato. Infine tale certificato è usato per la validazione della firma.

Nel tuo caso quindi dovresti realizzare una parte “server-side” che ti fa da repository dei certificati o - a questo punto - direttamente da validatore (come ho pensato di fare nel mio progetto con browserClient che accede alla camera e fa la lettura del GP e quindi effettua una chiamata http al validatorServer)

In realtà per ora mi accontenteri anche di fare il controllo a mano solo tra il mio greenpass e il certificato del Ministero della Salute, giusto per capire.
Però mi manca proprio l’inizio; va bene, per verificare la firma bisogna chiamare:

doVerify (SigStructure, verifier, alg, sig)

Ma cosa ci scrivo dentro a SigStructure, verifier, alg, e sig ?!?
Tutto quello che ho per ora è questo comando javascript:

[protected_header, unprotected_header, cbor_data, signature] = CBOR.decode(unzipped);

Come “traduco” questo “signature” in “SigStructure”? O devo metterlo in “sig”?
“Alg” l’ho trovato, è il primo elemento dell’array “protected_header”, se vale “-7” vuol dire che l’algoritmo è ES256:


              +-------+-------+---------+------------------+
              | Name  | Value | Hash    | Description      |
              +-------+-------+---------+------------------+
              | ES256 | -7    | SHA-256 | ECDSA w/ SHA-256 |
              | ES384 | -35   | SHA-384 | ECDSA w/ SHA-384 |
              | ES512 | -36   | SHA-512 | ECDSA w/ SHA-512 |
              +-------+-------+---------+------------------+

                      Table 5: ECDSA Algorithm Values

https://www.tech-invite.com/y80/tinv-ietf-rfc-8152-2.html

In “verifier” vedo che ci devono essere vari dati, ma dove li prendo?

verifier.externalAAD
verifier.key.kid
verifier.key.x
verifier.key.y

Qui ho trovato la lista completa dei certificati; è vecchia e amatoriale, ma per studiare va bene:
https://raw.githubusercontent.com/lovasoa/sanipasse/master/src/assets/Digital_Green_Certificate_Signing_Keys.json

Sono riuscito almeno a trovare un paio di siti per decodificare online una stringa CBOR, così almeno posso fare prove eliminando l’incognita “il mio codice è giusto?”…

http://cbor.me/
https://geraintluff.github.io/cbor-debug/

Il primo mi pare migliore.
Bisogna mettere nel pannello di desrta l’array unzippato derivante dalla decodifica di un QR code, ad esempio questo di prova disponibile online:

6BFOXN%TS3DH0YOJ58S S-W5HDC *M0II5XHC9B5G2+$N IOP-IA%NFQGRJPC%OQHIZC4.OI1RM8ZA.A5:S9MKN4NN3F85QNCY0O%0VZ001HOC9JU0D0HT0HB2PL/IB*09B9LW4T*8+DCMH0LDK2%K:XFE70*LP$V25$0Q:J:4MO1P0%0L0HD+9E/HY+4J6TH48S%4K.GJ2PT3QY:GQ3TE2I+-CPHN6D7LLK*2HG%89UV-0LZ 2ZJJ524-LH/CJTK96L6SR9MU9DHGZ%P WUQRENS431T1XCNCF+47AY0-IFO0500TGPN8F5G.41Q2E4T8ALW.INSV$ 07UV5SR+BNQHNML7 /KD3TU 4V*CAT3ZGLQMI/XI%ZJNSBBXK2:UG%UJMI:TU+MMPZ5$/PMX19UE:-PSR3/$NU44CBE6DQ3D7B0FBOFX0DV2DGMB$YPF62I$60/F$Z2I6IFX21XNI-LM%3/DF/U6Z9FEOJVRLVW6K$UG+BKK57:1+D10%4K83F+1VWD1NE

Cliccando sulla freccia per convertire, nel pannello di sinistra compare:

18([h'A2044839301768CDDA05130126', {}, h'A4041A6194E898061A60A78C8801624954390103A101A4617681AA62646E02626D616D4F52472D3130303033303231356276706A313131393334393030376264746A323032312D30342D313062636F62495462636978263031495445373330304531414232413834433731393030344631303344434231463730412336626D706C45552F312F32302F313532386269736249546273640262746769383430353339303036636E616DA463666E746944493C43415052494F62666E6944692043617072696F63676E746D4D4152494C553C54455245534162676E6E4D6172696CC3B9205465726573616376657265312E302E3063646F626A313937372D30362D3136', h'A4EE9016C1A74CCF9CAAB905492D698F6992A8FA30C20DB6180F06040C4870A845BB4B3A1CE3F4ED529CC78E66322547D62637C74AB17919C0AA52A614795E9E'])

Che sono i dati grezzi da estrarre ed elaborare ulteriormente, mentre in quello di destra compare una spiegazione e, se possibile, una conversione in forma leggibile:

D2                                      # tag(18)
   84                                   # array(4)
      4D                                # bytes(13)
         A2044839301768CDDA05130126     # "\xA2\x04H90\x17h\xCD\xDA\x05\x13\x01&"
      A0                                # map(0)
      59 0101                           # bytes(257)
[snip]
  58 40                             # bytes(64)

Quello che ho capito:
Il valore D2 è la somma di C0 e 12:
C0 vuol dire che il valore che segue è la “tag CBOR”
12 è il valore hex della tag, cikoè 18, che dovrebbe equivalere all’algoritmo di cifratura COSE chiamato “Sign1Tag” (piuttosto che il SignTag che avrebbe valore 98), usato nel sorgente della funzione di verifica di DCC-UTIL in questo modo:

let verified = await dcc.checkSignature(verifier);

checkSignature chiama a sua volta cose.sign.verify() in sign.js, che a sua volta chiama verifyInternal(), sempre in sign.js, che a sua volta decide cosa fare in base al valore della tag, SignTag (98) o Sign1Tag (18).

84 potrebbe essere la somma di 80 (=array) e 4 (numero di elementi nell’array CBOR)

4D potrebbe essere la somma di 40 (= segue elenco di bytes) e D (=13 bytes)

Questi 13 byte vanno decodificati per tirare fuori il KID, ma non ho ancora capito come funziona l’algoritmo :

function kidDecoder(kid) {
 //   	kidReduced = kid.reduce ( (str, v) => str + String.fromCharCode(v), "")
	try {
		reducer = (str, v) => str + String.fromCharCode(v)
   	kidReduced = kid.reduce ( reducer , "")
		kidFinal = btoa(kidReduced);
		console.log("KID source:",  kid);
		console.log("KID result:",  kidFinal);
		return kidFinal;
	} catch (error) {
		console.log("Error in decoding kid:", error, kid)
		return "KID error";
	}

}

A0 dovrebbe indicare che il secondo elemento dell’array, unprotected_header, è di tipo map (A0 ?) ma è vuoto (A0+0).

59 dovrebbe voler dire che dopo c’è una sequenza di byte, che sono 257 (hex 0101), anche se dopo però c’è un 58 a indicare che segue una sequenza di byte (64, hex40), che sarebbe la firma.

Quindi risulta:
Protected header: A2044839301768CDDA05130126
Unprotected header: vuoto
Greenpass (JSON):
A4041A6194E898061A60A78C8801624954390103A101A4617681AA62646E02626D616D4F52472D3130303033303231356276706A313131393334393030376264746A323032312D30342D313062636F62495462636978263031495445373330304531414232413834433731393030344631303344434231463730412336626D706C45552F312F32302F313532386269736249546273640262746769383430353339303036636E616DA463666E746944493C43415052494F62666E6944692043617072696F63676E746D4D4152494C553C54455245534162676E6E4D6172696CC3B9205465726573616376657265312E302E3063646F626A313937372D30362D3136

Signature: A4EE9016C1A74CCF9CAAB905492D698F6992A8FA30C20DB6180F06040C4870A845BB4B3A1CE3F4ED529CC78E66322547D62637C74AB17919C0AA52A614795E9E

Sia il protected header che il greenpass sono altre stringhe CBOR da convertire; signature invece no, resta qualcosa di misterioso.

Protected header risulta in:

A2                     # map(2)
   04                  # unsigned(4)
   48                  # bytes(8)
      39301768CDDA0513 # "90\x17h\xCD\xDA\x05\x13"
   01                  # unsigned(1)
   26                  # negative(6)

Come fa la sequenza 39301768CDDA0513 a diventare OTAXaM3aBRM= non lo so proprio.

Gli ultimi due valori sembrano anche un errore, perchè dovrebbero indicare l’algoritmo tramite il numero “-7”

Ciao

premetto che non mi sono interessato del funzionamento interno della libreria cose-js.
Il metodo verify = function (payload, verifier, options) esportato dalla libreria ha come parametri il payload (ovvero l’intero messaggio COSE) e il verifier, ovvero la chiave.

Questo secondo parametro richiede un po’ di lavoro esterno perché, a seconda che la chiave sia RSA o EC deve essere passata in formato diverso. Ti consiglio di prendere spunto da come lo chiama la libreria dcc-utils per capire bene questo passaggio:

Infine, il valore di KID che trovi nel protected header deve essere semplicemente convertito in base64 per confrontarlo con quello indicato dall’endpoint del Ministero, anche qui prendi spunto da come funziona l’SDK ufficiale:

Mi arrendo…
Seguendo le tracce della verifica della firma, ho scoperto partendo da questa riga…

const verified = await dcc.checkSignatureWithCertificate(crt);

si va a finire nel file sign.js della libreria COSE:

SIGN.js → verify() → verifyInternal() → doVerify()

Ma da qui poi si va a finre nel file RSA.js

RSA.js → key.verify() → keypair.verify() → this.signingScheme.verify.apply()

L’ultima chiamata non sono riuscito a capire a che funzione corrisponda, ma a quel punto mi sono fermato, sono rimasto impantanato negli algoritmi RSA, da cui non riuscirei mai a districarmi…

Vabbè mi rassegno, per la verifica della firma si va “in automatico” e “a fiducia”, senza avere la più pallida idea di cosa succeda sotto il cofano.

Comunque nel frattempo ho almeno trovato un modo per scaricare tutti i certificati in una volta sola, anche se non ho capito se sono link ufficiali o ufficiosi, trovati qui:

DE: https://de.dscg.ubirch.com/trustList/DSC/
SE: Just nu är det många som försöker hämta ett Covidbevis | Covidbevis
NL: https://verifier-api.coronacheck.nl/v4/verifier/public_keys

“Ovviamente” ogni ente usa un formato diverso…

Ciao a tutti!

solo per segnalare che tra i parametri utilizzati dall’app:
https://get.dgc.gov.it/v1/dgc/settings

è comparsa anche una blacklist, in cui sono presenti gli UVCI dei GP “falsificati” che sono girati nei giorni scorsi.

contestualmente ho trovato traccia di alcuni commit che la referenziano in un branch dell’SDK:

immagino quindi che si stiano preparando ad aggiungere questa feature per consentire la revoca di un singolo GP…

2 Mi Piace