EU Digital Covid Certificate

Qualche settimana fa ho scaricato il mio EU Digital Covid Certificate e mi sono un pochino incuriosito.

Ma cosa diavolo è contenuto dentro quel QR Code?

Vediamo di dare una occhiata. La prima cosa da fare è decodificare il QRCode e vedere che cosa contiene. Per qualche strana ragione la mia webcam oggi non ne voleva sapere di funzionare con OpenCV. Un peccato perché con un paio di righe codice avrei semplicemente decodificato il QRCode. Nessun problema. Online esistono una quantità enorme di lettori di QRCode. Ho usato uno di questi ed in pochi secondi mi sono trovato con la stringa contenente i dati.

Dato che non credo sia una buona idea condividere con voi tutti i miei dati personali, usiamo dei dati di test per dare una idea.

Il contenuto del QRCode è questo:

HC1: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

Bellino, non è vero? Ad ogni modo, se vi interessa, i dati di test li ho presi qui: https://github.com/eu-digital-green-certificates/dgc-testdata/tree/main/IT/2DCode/raw

Bene. ora abbiamo i dati del QRCode ma che cavolo c’è dentro.

Vediamo innanzitutto il processo che viene usato per creare il QRCode:

Direi che è sufficientemente chiaro. Se volete saperne un pochino di più, leggete qui.

A questo punto noi dobbiamo percorrere la catena a ritroso. In realtà quello che ci interessa è il contenuto originale del JSON Document, il secondo elemento della catena, perché non è nostra intenzione verificare l’autenticità della firma digitale ma solo esplorare il contenuto.

Primo passo. Levarci di torno i primi caratteri ed ottenere una bella stringhetta Base45. Eccola qui:

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

Uno sforzone… abbiamo solo cancellato i primi quattro caratteri della stringa di prima.

Ora decodifichiamo i dati in Base45. Otteniamo questo:

b'x\x9c\xbb\xd4\xe2\xbb\x88\xc5\xc3\xd2@<\xe3\xec-VaF\xb5\x05\x91\x8c\x8cKX\xa4\x12\xa7\xbc\x98\xc1&\x95\xb0\xbc\xa7\x831\xc93\xc4\x92\x91y!\xe3\x92\xc4\xb2\xc6UI)yLI\xb9\x89\xb9\xfeA\xee\xba\x86\x06\x06\x06\xc6\x06F\x86\xa6Ie\x05Y\x86\x86\x86\x96\xc6&\x96\x06\x06\xe6I)%YF@a]\x03\x13\xa0\x92\xa4\xe4|\xa0\x01I\xc9\x99\x15j\x06\x86\x9e!\xae\xe6\xc6\x06\x06\xae\x86\x8eNF\x8e\x16&\xce\xe6\x86@\r&n\x86\x06\xc6.\xceN\x86n\xe6\x06\x8e\xcafI\xb9\x059\xae\xa1\xfa\x86\xfaF\x06\xfa\x86\xa6F\x16I\x99\xc5 \x03\x8aS\x98\x92J\xd23-L\x0cL\x8d\x81\x9a\xcc\x92\xf3\x12s\x97$\xa7\xe5\x95d\xbax\xda8;\x06\x04y\xfa\'\xa5\xe5e\xbad*8\'\x16\x14e\xe6\'\xa7\xe7\x95\xe4\xfa:\x06y\xfa\x84\xda\x84\xb8\x06\xb9\x06;&\xa5\xe7\xe5\xf9&\x16e\xe6\x1c\xde\xa9\x10\x92Z\x94Z\x9c\x98\\\x06\xa4\x0c\xf5\x0c\xf4\x0c\x92S\xf2\x93\xb2\x0c-\xcd\xcdu\r\xcct\r\xcd"\x1c\x96\xbc\x9b vp\xb9\xcf\xf99\xabv\xb2z\xeaf\xf6gNZ\xf1\xcb\xe0\x10\xef6\t~6\x16\x1e\x8f\x82\x15\xae\xbb\xbd\xadd\x1e\x7fy\x1b4\xe7x_\x9a\x91\xaa\xfb55\xf3\xe3^\x1b+%\x0f\xac\nZ&R\x197\x0f\x00l\x94r\xca'

Dalla immagine che abbiamo utilizzato prima sappiamo che questi dati sono compressi con zlib. Decomprimiamoli ed otteniamo questo:

b'\xd2\x84M\xa2\x04H90\x17h\xcd\xda\x05\x13\x01&\xa0Y\x01\x01\xa4\x04\x1aa\x94\xe8\x98\x06\x1a`\xa7\x8c\x88\x01bIT9\x01\x03\xa1\x01\xa4av\x81\xaabdn\x02bmamORG-100030215bvpj1119349007bdtj2021-04-10bcobITbcix&01ITE7300E1AB2A84C719004F103DCB1F70A#6bmplEU/1/20/1528bisbITbsd\x02btgi840539006cnam\xa4cfntiDI<CAPRIObfniDi CapriocgntmMARILU<TERESAbgnnMaril\xc3\xb9 Teresacvere1.0.0cdobj1977-06-16X@\xa4\xee\x90\x16\xc1\xa7L\xcf\x9c\xaa\xb9\x05I-i\x8fi\x92\xa8\xfa0\xc2\r\xb6\x18\x0f\x06\x04\x0cHp\xa8E\xbbK:\x1c\xe3\xf4\xedR\x9c\xc7\x8ef2%G\xd6&7\xc7J\xb1y\x19\xc0\xaaR\xa6\x14y^\x9e'

Cominciamo a vedere qualcosa di intellegibile. Sempre usando come riferimento l’immagine sappiamo che la rappresentazione di questi dati è COSE: CBOR Object Signing and Encryption.

Da questo documento capiamo che ci sono due header, un payload e la firma digitale. Facciamo un altro giro di decodifica ed otteniamo questo:

{4: 1637148824, 6: 1621593224, 1: 'IT', -260: {1: {'v': [{'dn': 2, 'ma': 'ORG-100030215', 'vp': '1119349007', 'dt': '2021-04-10', 'co': 'IT', 'ci': '01ITE7300E1AB2A84C719004F103DCB1F70A#6', 'mp': 'EU/1/20/1528', 'is': 'IT', 'sd': 2, 'tg': '840539006'}], 'nam': {'fnt': 'DI<CAPRIO', 'fn': 'Di Caprio', 'gnt': 'MARILU<TERESA', 'gn': 'Marilù Teresa'}, 'ver': '1.0.0', 'dob': '1977-06-16'}}}

Direi che siamo quasi alla fine. E’ il JSON Document che stavamo cercando. Facciamo un pretty print del file e finalmente abbiamo il risultato che cercavamo:

{
    "4": 1637148824,
    "6": 1621593224,
    "1": "IT",
    "-260": {
        "1": {
            "v": [
                {
                    "dn": 2,
                    "ma": "ORG-100030215",
                    "vp": "1119349007",
                    "dt": "2021-04-10",
                    "co": "IT",
                    "ci": "01ITE7300E1AB2A84C719004F103DCB1F70A#6",
                    "mp": "EU/1/20/1528",
                    "is": "IT",
                    "sd": 2,
                    "tg": "840539006"
                }
            ],
            "nam": {
                "fnt": "DI<CAPRIO",
                "fn": "Di Caprio",
                "gnt": "MARILU<TERESA",
                "gn": "Maril\u00f9 Teresa"
            },
            "ver": "1.0.0",
            "dob": "1977-06-16"
        }
    }
}

Lo schema del file JSON è specificato qui.

E quindi non c’è niente di magico all’interno del QRCode. Sono quasi deluso :-). Diciamo che, comunque, Marilu è un bel nome per dei dati di test. Chissà se è davvero casuale o se è la fidanzata, o la mamma, di qualcuno che ha contribuito a scrivere le specifiche od il codice.

Se proprio volete ecco il codice che ho usato per fare tutto questo. Quick and dirty, ovviamente:

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
from base45 import b45decode
import cv2
import flynn
import zlib
import json

data = "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"
print(f"Raw Data: <{data}>")
decoded_data = b45decode(data)
print(f"Decoded Data: <{decoded_data}>")
decompressed_data = zlib.decompress(decoded_data)
print(f"Decompressed Data: <{decompressed_data}>")
(_, (h1, h2, payload, signature)) = flynn.decoder.loads(decompressed_data)
data = flynn.decoder.loads(payload)
print(f"Certificate Data: <{data}>")
print(f"JSON Data:")
print(json.dumps(data, indent=4))
print(f"Header 1: <{h1}>")
print(f"Header 2: <{h2}>")
print(f"Signature: <{signature}>")

Ovviamente in Python 🙂

Il contenuto del file “vc.txt”, utilizzato dal codice, è questo:

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
5 1 vote
Article Rating
Subscribe
Notify of
guest

6 Commenti
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Alessandro

Ottimo articolo!
Piuttosto, se volessi fare l’operazione inversa come si procede?

Grazie

paolo

Grazie quindi con a logica, con questo controllo, un falso non funzionerebbe.
Cioè non è che bisogna prendere le chiavi pubbliche e fare la verifica?

Immagina che una malintenzionato crea tutto con la logica corretta e la sua firma…. senza controllo tu leggi i dati lo stesso…?

Grazie ancora

paolo

ciao ma il controllo della firma non lo fai? Potrebbe esserci il caso in cui viene generato un falso e firmato in altro modo? GRAAZIE