vendredi 25 juin 2021

Open Source, Standards at the Rescue to Allow Travels in Those COVID-19 Times

 In June 2021, the European Union (EU) has announced the "Digital Green Certificate", also known as "corona pass" or "Digital COVID Certificate (DCC)") to certify that a European resident has been vaccinated (once or twice), got recently tested, or had recovered from the COVID-19. The intent is of course to facilitate travels within EU and perhaps (depending on Member States) entrance in some large indoor events.

This certificate is actually a QR code such as this one (provided as an example by the Belgian Federal Govt):



But, what is inside this QR code ? What about privacy ? What about security ? How is it constructed ?

I was curious and found a wealth of public information and even source code ! And, even more interesting, the majority of the components rely on open and free standards from the Internet Engineering Task Force (IETF). So, based on the open source code in the EU github, I extended it to show more information,  learn more about base45, CBOR, and COSE ;-) 

There is a web front-end where you can decode your own EU Green Certificate if you want at https://ehealth.vyncke.org/ .

And, here are some explanations for the QR-code above.

After analyzing the uploaded image, the QR code is (left-hand column is the hexadecimal/computer format the right-hand column is the ASCII/human format):

48 43 31 3A 4E 43 46 4F 58 4E 25 54 53 4D 41 48    H C 1 : N C F O X N % T S M A H 
4E 2D 48 25 4F 43 48 4F 53 38 30 4A 53 33 4E 4C    N - H % O C H O S 8 0 J S 3 N L 
37 33 3A 44 34 2B 4F 56 2D 33 36 48 44 37 41 4F    7 3 : D 4 + O V - 3 6 H D 7 A O 
4D 4F 57 34 53 32 53 2A 2A 4A 34 47 35 57 2F 4A    M O W 4 S 2 S * * J 4 G 5 W / J 
54 33 46 46 2F 38 58 2A 47 33 4D 39 42 4D 39 5A    T 3 F F / 8 X * G 3 M 9 B M 9 Z 
30 42 5A 57 34 56 2F 41 59 37 33 33 4A 37 25 32    0 B Z W 4 V / A Y 7 3 3 J 7 % 2 
48 56 37 37 41 44 46 59 52 56 4E 44 46 2E 39 33    H V 7 7 A D F Y R V N D F . 9 3 
24 50 4E 2D 2A 30 58 33 37 2A 30 39 30 47 56 56    $ P N - * 0 X 3 7 * 0 9 0 G V V 
4E 4E 47 4D 35 56 2E 34 39 39 54 50 2B 4D 35 2A    N N G M 5 V . 4 9 9 T P + M 5 * 
4B 2A 55 33 2A 39 36 38 34 36 41 24 51 20 37 36    K * U 3 * 9 6 8 4 6 A $ Q   7 6 
55 57 36 32 55 31 30 25 4D 50 46 36 35 5A 4D 4E    U W 6 2 U 1 0 % M P F 6 5 Z M N 
48 36 4C 4B 39 32 52 35 51 56 31 4F 32 52 30 4E    H 6 L K 9 2 R 5 Q V 1 O 2 R 0 N 
4C 44 2B 39 20 42 4C 58 45 36 55 43 36 35 5A 4D    L D + 9   B L X E 6 U C 6 5 Z M 
31 37 36 4E 46 36 37 35 49 50 46 35 24 35 51 41    1 7 6 N F 6 7 5 I P F 5 $ 5 Q A 
34 36 2F 51 36 35 37 36 50 52 36 50 46 35 52 42    4 6 / Q 6 5 7 6 P R 6 P F 5 R B 
51 37 34 36 42 34 36 4F 31 4E 36 34 36 52 4D 39    Q 7 4 6 B 4 6 O 1 N 6 4 6 R M 9 
58 43 35 2E 51 36 39 4C 36 2D 39 36 51 57 36 55    X C 5 . Q 6 9 L 6 - 9 6 Q W 6 U 
34 36 25 45 35 20 4E 50 43 37 31 41 4C 36 5A 4F    4 6 % E 5   N P C 7 1 A L 6 Z O 
36 36 58 36 39 2F 39 2D 33 41 4B 49 36 33 5A 4D    6 6 X 6 9 / 9 - 3 A K I 6 3 Z M 
4C 45 51 5A 37 36 55 57 36 2A 45 39 39 51 39 45    L E Q Z 7 6 U W 6 * E 9 9 Q 9 E 
24 42 44 5A 49 45 39 4A 2F 4D 4A 46 5A 49 2A 49    $ B D Z I E 9 J / M J F Z I * I 
42 2A 4E 49 5A 30 4B 41 34 32 42 4B 42 54 4B 42    B * N I Z 0 K A 4 2 B K B T K B 
41 34 32 32 39 42 43 57 4B 58 53 4A 47 5A 49 38    A 4 2 2 9 B C W K X S J G Z I 8 
44 4A 43 30 4A 2A 50 49 54 51 54 41 2E 53 47 44    D J C 0 J * P I T Q T A . S G D 
33 32 4F 49 5A 30 4B 25 47 41 2B 45 53 43 51 53    3 2 O I Z 0 K % G A + E S C Q S 
45 54 43 25 45 53 49 53 54 52 20 53 52 36 33 2B    E T C % E S I S T R   S R 6 3 + 
4E 54 57 56 42 44 4B 42 59 4C 44 4E 34 44 45 31    N T W V B D K B Y L D N 4 D E 1 
44 2D 4E 53 4C 46 55 4B 51 39 42 2E 55 50 2D 31    D - N S L F U K Q 9 B . U P - 1 
41 5A 4A 53 39 4A 45 36 46 2A 5A 4A 4B 45 37 2B    A Z J S 9 J E 6 F * Z J K E 7 + 
33 47 33 55 55 53 2E 37 37 53 55 31 51 55 42 35    3 G 3 U U S . 7 7 S U 1 Q U B 5 
4A 50 4E 32 52 2A 4F 35 35 4F 4F 51 43 2A 33 4A    J P N 2 R * O 5 5 O O Q C * 3 J 
53 48 35 33 53 46 4E 2A 34 36 50 42 4D 5A 4C 2B    S H 5 3 S F N * 4 6 P B M Z L + 
48 32 25 2D 54 24 4C 56 56 56 31 59 3A 44 33 54    H 2 % - T $ L V V V 1 Y : D 3 T 
33 41 50 37 42 46 50 49 37 53 59 4D 30 2F 4B 4F    3 A P 7 B F P I 7 S Y M 0 / K O 
2B 44 47                                           + D G 

The 'HC1:' signature is found in the first characters, 'HC1' stands for Health Certificate version 1. Let's remove it...

QR-code only allows for 45 different characters... not enough for the Health Certificate as it is in binary (required 256 characters per octet). So, this binary information is 'encoded' in base45 (thanks to my friend Patrik's IETF draft draft-faltstrom-base45).

Base45 decoding... The decoded message is now (many more binary characters represented as '.' on the right-hand column and also less octets):

78 DA BB D4 E2 BB 88 51 8D C5 63 4A E1 C5 96 53    x . . . . . . Q . . c J . . . S 
B6 92 19  B 22 19 F9 97 30 26 39 B9 B2 48 25 5C    . . . . " . . . 0 & 9 . . H % \ 
DD F2 9D 4D 2A 61 9D FA 77 4B 46 E6 85 8C 4B 12    . . . M * a . . w K F . . . K . 
4B 1A 57 26 25 67 56 C8 19 18 3A B9 86 F9 38 B9    K . W & % g V . . . : . . . 8 . 
78 FA FA 18 79  7 19 7A BA 79  7  4 38  7 47 58    x . . . y . . z . y . . 8 . G X 
F8  7 BA 19 2B 7B 27 25 E7  3 B5 27 A5 14 1D 28    . . . . + { ' % . . . ' . . . ( 
31 32 30 32 D4 35 30 D5 35 34  D 31 B4 B4 32 32    1 2 0 2 . 5 0 . 5 4 . 1 . . 2 2 
B4 32 32 8A 4A CA 2C 4E  D 76 D6  5 AA 28 4E 46    . 2 2 . J . , N . v . . . ( N F 
A8 30 32  D 31 B0 B4 32 30 B2 32 30 8F 4A 2A 49    . 0 2 . 1 . . 2 0 . 2 0 . J * I 
CE B0 30 34 34 33 30 B6 34 4E 2A 49 CF B4 30 31    . . 0 4 4 3 0 . 4 N * I . . 0 1 
30 35 B6 34 30 30 4B 2A 29 CA 34 32 33 30 31 34    0 5 . 4 0 0 K * ) . 4 2 3 0 1 4 
35 30 30 48 2A 29 C9 F0  9 30 33 31 33 D1 35 49    5 0 0 H * ) . . . 0 3 1 3 . 5 I 
4E C9 4F CA 32 B4 B4 30 D0 35 30 D4 35 34 49 CE    N . O . 2 . . 0 . 5 0 . 5 4 I . 
4B CC 5D 92 94 96 97 EE 9A 54 94 98 5A 54 92 94    K . ] . . . . . . T . . Z T . . 
9E 57 10 90 5A 92 5A A4 10 90 58 9A A3 E0 9B 58    . W . . Z . Z . . . X . . . . X 
94 99 98 9C 96 57 92 EE EA 14 E4 E8 1A 14 92 9C    . . . . . W . . . . . . . . . . 
9E 57 52 10 E0 1A E2 1A 64 13 E0 18 EA 63 E3 EB    . W R . . . . . d . . . . c . . 
18 E4 E9 98 5C 96 5A 94 6A A8 67 A0 67 10 E1 B0    . . . . \ . Z . j . g . g . . . 
F0  6 4B D7 F4 BB  F 37 9C 7C 97 FC 77 C3 9C 99    . . K . . . . 7 . | . . w . . . 
39 E9 7F 3F F2 97 3E DD F2 41 F1 E1 97 37 13 F6    9 .  ? . . > . . A . . . 7 . . 
C4 CE BE BE 63 96 96 F9 2A A6 7B 96 26 96 5B 6E    . . . . c . . . * . { . & . [ n 
AC 5A 12 F7 EC C0 F9  D  D 7B 6E B3 1C D7 3B CE    . Z . . . . . . . { n . . . ; . 
C8 6F DE C9  4  0 C1 87 81  1                      . o . . . . . . . . 

First octet is 0x78, which is a sign for ZLIB compression. After decompression, the length went from 362 to 357 octets.

Obviously, the compression was rather useless as the 'compressed' length is larger than the 'uncompressed' one... Compression efficiency usually depends on the data, sometimes it compresses better than other times.

D2 84 4D A2  1 26  4 48 94 71 D1 84 CA 3D 19 68    . . M . . & . H . q . . . = . h 
A0 59  1  F A4  1 62 42 45  4 1A 60 D5 B4 F7  6    . Y . . . . b B E . . ` . . . . 
1A 60 AE 27 F7 39  1  3 A1  1 A4 61 74 81 A9 62    . ` . ' . 9 . . . . . a t . . b 
63 69 78 1E 30 31 42 45 56 4C 42 44 49 4D 4C 32    c i x . 0 1 B E V L B D I M L 2 
4B 52 31 49 46 4B 50 50 43 53 58 38 4F 51 46 33    K R 1 I F K P P C S X 8 O Q F 3 
23 4B 62 63 6F 62 42 45 62 64 72 C0 74 32 30 32    # K b c o b B E b d r . t 2 0 2 
31 2D 30 35 2D 31 35 54 31 39 3A 32 31 3A 32 32    1 - 0 5 - 1 5 T 1 9 : 2 1 : 2 2 
5A 62 69 73 65 53 43 2D 42 45 62 73 63 C0 74 32    Z b i s e S C - B E b s c . t 2 
30 32 31 2D 30 35 2D 32 35 54 30 39 3A 30 32 3A    0 2 1 - 0 5 - 2 5 T 0 9 : 0 2 : 
30 37 5A 62 74 63 68 38 31 31 36 30 33 39 33 62    0 7 Z b t c h 8 1 1 6 0 3 9 3 b 
74 67 69 38 34 30 35 33 39 30 30 36 62 74 72 69    t g i 8 4 0 5 3 9 0 0 6 b t r i 
32 36 30 34 31 35 30 30 30 62 74 74 68 4C 50 36    2 6 0 4 1 5 0 0 0 b t t h L P 6 
34 36 34 2D 34 63 64 6F 62 6A 31 39 38 30 2D 30    4 6 4 - 4 c d o b j 1 9 8 0 - 0 
31 2D 31 34 63 6E 61 6D A4 62 66 6E 67 45 62 72    1 - 1 4 c n a m . b f n g E b r 
61 65 72 74 62 67 6E 70 50 65 74 65 72 20 50 61    a e r t b g n p P e t e r   P a 
75 6C 20 4D 61 72 69 61 63 66 6E 74 67 45 42 52    u l   M a r i a c f n t g E B R 
41 45 52 54 63 67 6E 74 70 50 45 54 45 52 3C 50    A E R T c g n t p P E T E R < P 
41 55 4C 3C 4D 41 52 49 41 63 76 65 72 65 31 2E    A U L < M A R I A c v e r e 1 . 
30 2E 30 58 40 A1 D8  4 8A 97 DD E1 B0 C9 EE 63    0 . 0 X @ . . . . . . . . . . c 
FD B0 9C 99 6C 67 FD F1  F 75 E5 B4 F0 21 E1 F4    . . . . l g . . . u . . . ! . . 
EC 90 BC 5D 9B D7 B8 9A 2A 37 AA  2 DE 39 34 39    . . . ] . . . . * 7 . . . 9 4 9 
B4 D8 AA A4 5E E6 C0 CF B0 80 BC DB  4 C7 2E C7    . . . . ^ . . . . . . . . . . . 
 1  F 37 89  2                                     . . 7 . . 

Interpreting the message as Concise Binary Object Representation (CBOR), another IETF standards by my friends Carsten and Paul RFC 7049... CBOR tag is 18 (see IANA registry), hence it is a CBOR Object Signing and Encryption (COSE) Single Signer Data Object message, another IETF standards by late Jim Schaad RFC 8152.

Checking the COSE structure (ignoring the signature) of the CBOR Web Token (yet another IETF standards RFC 8392)...

	COSE Key Id(KID): 0x9471D184CA3D1968
	COSE Algorithm: Es256

The COSE message beside the crypto signatures contains "claims", which is the part that is protected/signed by the CBOR Web Token: in this case what is certified valid by a EU Member State).

 The CBOR-encoded claims payload is (and now we can recognized some human-readable content such as dates and names):

A4  1 62 42 45  4 1A 60 D5 B4 F7  6 1A 60 AE 27    . . b B E . . ` . . . . . ` . ' 
F7 39  1  3 A1  1 A4 61 74 81 A9 62 63 69 78 1E    . 9 . . . . . a t . . b c i x . 
30 31 42 45 56 4C 42 44 49 4D 4C 32 4B 52 31 49    0 1 B E V L B D I M L 2 K R 1 I 
46 4B 50 50 43 53 58 38 4F 51 46 33 23 4B 62 63    F K P P C S X 8 O Q F 3 # K b c 
6F 62 42 45 62 64 72 C0 74 32 30 32 31 2D 30 35    o b B E b d r . t 2 0 2 1 - 0 5 
2D 31 35 54 31 39 3A 32 31 3A 32 32 5A 62 69 73    - 1 5 T 1 9 : 2 1 : 2 2 Z b i s 
65 53 43 2D 42 45 62 73 63 C0 74 32 30 32 31 2D    e S C - B E b s c . t 2 0 2 1 - 
30 35 2D 32 35 54 30 39 3A 30 32 3A 30 37 5A 62    0 5 - 2 5 T 0 9 : 0 2 : 0 7 Z b 
74 63 68 38 31 31 36 30 33 39 33 62 74 67 69 38    t c h 8 1 1 6 0 3 9 3 b t g i 8 
34 30 35 33 39 30 30 36 62 74 72 69 32 36 30 34    4 0 5 3 9 0 0 6 b t r i 2 6 0 4 
31 35 30 30 30 62 74 74 68 4C 50 36 34 36 34 2D    1 5 0 0 0 b t t h L P 6 4 6 4 - 
34 63 64 6F 62 6A 31 39 38 30 2D 30 31 2D 31 34    4 c d o b j 1 9 8 0 - 0 1 - 1 4 
63 6E 61 6D A4 62 66 6E 67 45 62 72 61 65 72 74    c n a m . b f n g E b r a e r t 
62 67 6E 70 50 65 74 65 72 20 50 61 75 6C 20 4D    b g n p P e t e r   P a u l   M 
61 72 69 61 63 66 6E 74 67 45 42 52 41 45 52 54    a r i a c f n t g E B R A E R T 
63 67 6E 74 70 50 45 54 45 52 3C 50 41 55 4C 3C    c g n t p P E T E R < P A U L < 
4D 41 52 49 41 63 76 65 72 65 31 2E 30 2E 30       M A R I A c v e r e 1 . 0 . 0 

After decoding the COSE claims:

	Issuer              : BE
	Expiration time     : 2021-06-25
	Issued At           : 2021-05-26
	Health payload JSON : {
    "dob": "1980-01-14",
    "nam": {
        "fn": "Ebraert",
        "fnt": "EBRAERT",
        "gn": "Peter Paul Maria",
        "gnt": "PETER<PAUL<MARIA"
    },
    "t": [
        {
            "ci": "01BEVLBDIML2KR1IFKPPCSX8OQF3#K",
            "co": "BE",
            "dr": "2021-05-15T19:21:22+00:00",
            "is": "SC-BE",
            "sc": "2021-05-25T09:02:07+00:00",
            "tc": "81160393",
            "tg": "840539006",
            "tr": "260415000",
            "tt": "LP6464-4"
        }
    ],
    "ver": "1.0.0"
}

Health Certificate

Using the EU JSON specification, we can then display a fully human-readable certificate content from the JSON part (in italic above).

Last name: Ebraert
First name: Peter Paul Maria
Name as in passport: EBRAERT<<PETER<PAUL<MARIA
Birth date: 1980-01-14

Test for COVID-19 / Nucleic acid amplification with probe detection
 Test taken on: 2021-05-25 09:02:07+00:00 by 81160393 in Belgium
 Test result: Not detected

What about YOUR QR-code ?

Feel free to try and decode on-line your own QR-code at https://ehealth.vyncke.org/index.php (I do not keep anything from your test, the QR and the decoded information is deleted from my server once it has been displayed on your browser, your IP address will be logged though by the web server).

mardi 23 février 2021

Attirail du geek, partie 10: la framboise volante

Attirail du geek, partie 10: la framboise volante  

Encore une combinaison de mes hobbies 'geek':

  • informatique (beaucoup)
  • électronique (un peu)
  • aviation (un peu)

Le but: suivre les avions (position, vitesse) via télémétrie et radio pour moins de 100 EUR et sans installer un radar à la maison!

Comment: via un micro-computer de 50 EUR, une radio programmable (en anglais "Software Defined Radio" -- SDR), une petite antenne (idéalement de 68,5 mm de long), et des logiciels en open source. 

Les avions ont pour la plupart un transpondeur en mode-S qui transmet sur 1090 MHz leur code transpondeur sur 24 bits (4 chiffres de 0 à 7 donc 4 chiffres exprimés sur 3 bits) et leur altitude que tout le monde peut recevoir via une bonne antenne en vue directe (donc sans obstacles entre l'antenne et l'avion). Les avions commerciaux (compagnies aériennes notamment) ont en plus un système ADS-B out qui transmet sur la même fréquence leurs positions. Rien n'est chiffré, donc, tout le monde peut décoder les messages.

Le matériel

Un simple micro-computer de type Raspberry Pi 3 B+: format carte de crédit, avec 1 GByte de mémoire vive, sans clavier ni écran mais avec Wi-Fi, un port Ethernet, 4 portes USB et une porte HDMI. Le tout alimenté par micro-USB comme tous les appareils nomades. Le mémoire de masse est une micro-carte flash; ce qui permet d'initialiser facilement le système de fichier via Linux, Mac OS, ou Windows.

Ajoutons-y un petit boîtier en plastique et un chargeur micro-USB et cela devient un vrai ordi!

Rester à ajouter une clé USB bleue pour la radio SDR (40 EUR) et connecter la petite antenne à la clé USB et le tour est joué au niveau matériel.




Notes sur l'antenne

Dans un premier temps, 1090 MHz cela correspond à une longueur d'onde dans l'air de 275 mm (il faut diviser la vitesse


de la lumière par la fréquence), ça je comprends. Puis, c'est la partie qui m'a toujours un peu échappée lors de mes études d'ingénieurs... qui dit que l'antenne doit exactement faire le quart de la longueur d'onde, dans ce cas-ci 68,5 mm hélas celle que j'ai reçue fait 80 mm donc le rendement ne sera pas exceptionnel... en plus, je l'ai placée collée à l'intérieur sur une vitre et pas sur un mat extérieur. Quelques jours après, je place cette antenne à l'extérieur (de l'autre côté de la vitre) et je suis ébahi: la portée passe de 40 nm (+/- 70 km) à plus de 150 nm (+/- 270 km) et donc le nombre d'avions reçus passe de 10 en pointe à plus de 20 au minimum !

Le logiciel

Tout ordinateur doit avoir son système opératoire (OS) ainsi que ses applications. Dans mon cas:

  • Système opératoire: Raspberry Pi OS, une variante de Linux Debian pour le processeur ARM qui équipe le micro-ordinateur
  • Applications: le pilote de la radio et diverses applications analysant la sortie de la radio pour envoyer à plusieurs sites d'agrégation ('feeder' en anglais) de ces données (car nous sommes des milliers à faire ce 'truc') par exemple pour https://flightaware.com

Pour l'OS, il suffit de télécharger une image brute depuis le site de Raspberry, de la copier sur la micro-flash, et d'insérer la micro-flash dans le Raspberry Pi. L'absence d'écran et de clavier (sauf si connexion par les ports USB et HDMI évidemment) complique un peu les choses (le mot de passe par défaut est 'raspberry' que j'ai évidemment immédiatement changé!).

Le pilote de la radio

Le pilote de la radio, 'dump1090', se connecte au port USB de la radio, effectue le décodage des messages et les offre à plusieurs clients/consommateurs via une connexion TCP/IP. Ce qui est assez élégant car via une seule radio, il est possible d'envoyer les données sur plusieurs applications locales ou distantes via la porte TCP 30005.

En fait, il y a plusieurs version de ce pilote qui ont hérité l'une de l'autre en ajoutant des fonctionnalités:
  1. antirez (version de départ plus maintenue en dehors d'un patch mineur en février 2020)
  2. MalcomRobb sur base d'antirez (aucune mise à jour depuis octobre 2014)
  3. mutability sur base de MalcomRobb (aucune mise à jour depuis 2019)
  4. flightaware sur base de mutability (encore mis à jour en  octobre 2020, avec une belle interface web et surtout le support d'IPv6)

Comme il ne peut y avoir qu'un seul pilote pour la radio, autant installer la dernière version via la commande Linux 'apt-get install dump1090-fa' (après avoir mis à jour les sources apt voir le point 2 de ce lien). Ce qui est fait rapidement et inutile de redémarrer évidemment même si la doc dit le contraire!

Cela fonctionne immédiatement et une connexion sur la porte TCP 8080 genre http://[2001:db8::1]:8080/ (remplacer par l'adresse IPv6 voire IPv4 du Raspberry) et donne immédiatement une vue très sympa des avions dans le coin! Le coin dans le cas de ma simple antenne est tout de même plus de 100 km !

Les autres applications

Par la suite, il est possible d'installer d'autres applications qui vont partager les données sur les quelques avions que le système reçoit avec des sites d'agrégation de ces milliers de systèmes. En général, il y a une incentive qui donne un contrat 'gold' ou 'business' gratuitement sur ces sites; donc, parfois un grand historique ou un accès à une interface de programmation (API). Pour avoir ces bénéfices, il faut évidemment créer sur chaque site un compte et lier son Raspberry à ce compte (soit via l'adresse IP soit via un code à copier/coller).

Voici la liste des applications que j'ai installées et qui fonctionnent donc toutes en même temps (charge CPU très très légère un Raspberry PI Zero devrait suffire):

Tout cela est bien sympa, mais pour l'instant ne fonctionne que pour ADS-B donc pas pour les petits avions belges dont ceux de mon club...

Explications demain ou un autre jour ;-)