A look inside the
European Covid Green Certificate
Luciano Mammino (@loige)
2021-12-01
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/8545853/1BhMtjb9CskspHYBTi_lSw95DqPkuyRzBls5kbEwe_XWed96Z.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/8545852/17y5aHEQ8tg07zxHC89ea_gygExweZH5pQuEZnxrP-MbfdwuA.png)
Get these slides!
Let me introduce myself first...
๐ I'm Lucianoย (๐ฎ๐น๐๐๐ค)
๐จโ๐ป Senior Architect @ fourTheorem (Dublin ๐ฎ๐ช)
๐ Co-Author of Node.js Design Patternsย ๐
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/8956193/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/8956194/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/8370252/pasted-from-clipboard.png)
We are business focused technologists that deliver.
Accelerated Serverlessย | AI as a Serviceย | Platform Modernisation
We are hiring: do you want to work with us?
Disclaimers
๐ค I am not involved with the DGC working group
ย
๐ข COVID has been tough on everyone,
ย ย ย we'll try to focus only on the tech here!
Agenda + Goals
1. ๐ฉโ๐ซ Needs and principles
2. ๐ Cryptographic model
3. ๐ฆ The data
4. ๐ง Layers of encoding
5. ๐ Decoding in Rust
๐คจ Learn some cool technologies
๐ง Learn a tiny bit of Rust
๐ค Be nerdy and have fun!
The need for a digital certificate in the COVID age
The need for a digital certificate in the COVID age
๐ท We need a system to quickly provide a proof against COVID
ย ย ย (Vaccination, negative test, proof of recovery)ย
๐โโ๏ธ It needs to be personal, easy to carry around (digital),
ย ย ย ย easy to issue and to validate
๐ It needs to be secure against forgery and work across countries
Electronic Health Certificates (HCERT)
Requirements & Guiding Principles
โ๏ธ Signed data with machine readable content
๐ Use compact encoding
๐คฒ Based on open standards
Asymmetric cryptographic signatures
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128293/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128294/pasted-from-clipboard.png)
๐คซ Private Key
๐ข Public Key
๐
101010101000101010010...
0101010101010101010101...
Asymmetric cryptographic signatures
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128293/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128294/pasted-from-clipboard.png)
๐คซ Private Key
๐ข Public Key
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128300/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128312/pasted-from-clipboard.png)
101010101000101010010...
0101010101010101010101...
The owner of the private key signs the document
Anyone can validate the signature using the public key
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128316/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128317/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128319/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128331/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128357/pasted-from-clipboard.png)
What's inside a certificate?
Cryptographic header (Key Id, Algorithm)
DGC container
Header (Issuer, Issue date, expiry date)
Certificates list
Cryptographic Signature
vaccine, test, or recovery data
Personal data (name, surname, DoB)
An example
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9078837/temp.png)
An example
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9078837/temp.png)
Personal info
Vaccine info
{
"1": "DK",
"4": 1625054000,
"6": 1622462000,
"-260": {
"ver":"1.0.0",
"nam":{
"fn":"Klaus",
"fnt":"KLAUS",
"gn":"Jรธrgensen",
"gnt":"JOERGENSEN"
},
"dob":"1983-01-06",
"v":[
{
"tg":"840539006",
"vp":"1119349007",
"mp":"EU/1/20/1528",
"ma":"ORG-100030215",
"dn":2,
"sd":2,
"dt":"2021-05-03",
"co":"DK",
"is":"Danish Health Data Authority",
"ci":"URN:UVCI:01:DK:B986830007345F99AE898FB82C6C61F2#A"
}
]
}
}
DGC Header
Layered encoding
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9078837/temp.png)
โQRCODE ASCII mode
Prefix + Base45 encoding
Zlib compression
CWT signed data (COSE)
CBOR document
๐ง
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9078837/temp.png)
HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-36
5KN-TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z
*AK.GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7U
J5PNDIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9
UKPSH9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.
GUQ$9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9
R8Q02-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI
4OIMEDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6
ZC0JBY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0
TB9FNDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QD
SWNG4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR
$F-75NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63
ET-D1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$H
K00XWPD2
QRCode content
"HC1:" (prefix)
Binary data encoded in Base45
Base45
Allows to encode binary data in text format (ASCII)
Like Base64, but it uses 45 characters instead:
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9092602/Screenshot_2021-11-09_at_09.28.46.png)
Base45
Base45
01001001 00100000 01100111 01101111 01110100 00100000 01101101 01111001 00100000 01110011 01101000 01101111 01110100 01110011 00100000 11011000 00111101
UTF8 (17 bytes)
I got my shots ๐
Hex (38 bytes)
49 20 67 6f 74 20 6d 79 20 73 68 6f 74 73 20 f0 9f 92 89
Base64 (28 bytes)
SSBnb3QgbXkgc2hvdHMg8J+SiQ==
Base45 (29 bytes)
0B9J3DSUEZ$DR4459DLWEH74Z7K23
Some binary data
"A QR-code is used to encode text as a graphical image. [...] QR-codes cannot be used to encode arbitrary binary data directly.ย [...] Compared to already established Base64, Base32 and Base16 encoding schemes [...], the Base45 scheme described in this document offer a more compact QR-code encoding"
Base45
Base45
NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-
TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z*AK.
GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7UJ5PN
DIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9UKPS
H9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.GUQ$
9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9R8Q0
2-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI4OIM
EDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6ZC0J
BY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0TB9F
NDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QDSWNG
4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR$F-7
5NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63ET-D
1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$HK00X
WPD2
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9094319/Screenshot_2021-11-09_at_18.35.18.png)
Base45
Decode
Compressed binary data
Zlib compression
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9094383/pasted-from-clipboard.png)
Zlib compression
"zlib is designed to be a free, general-purpose, legally unencumbered -- that is, not covered by any patents -- lossless data-compression library for use on virtually any computer hardware and operating system"
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9094383/pasted-from-clipboard.png)
Zlib compression
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9094319/Screenshot_2021-11-09_at_18.35.18.png)
Zlib inflate
Compressed binary data
Decompressed binary data
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9106525/Screenshot_2021-11-13_at_17.40.40.png)
Zlib compression
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9106525/Screenshot_2021-11-13_at_17.40.40.png)
๐ We start to see some "readable" information!
COSE / CWT
CBOR Object Signing and Encryption / CBOR Web Token
CBOR !? ๐ค
But wait, what the heck is
โ
CBOR
TLDR; Like JSON but in binary!
JSON
A schema-less data format where a value can be:
Null
Boolean
Number
String
Array
Object
null
true
-17.34
"A programmer walks into a bar..."
["foo", 1.23, null, false, [22]]
{"foo": "bar", "manyvals": [1,2,3], "nested": {}}
CBOR
A schema-less binaryย data format where a value can be:
Null
Boolean
Number
String Text
Array
Object Map
F6
F5
fbc031570a3d70a3d7
7820412070726f6772616d6d65722077616c6b7320696e746f2061206261722e2e2e
8563666f6ffb3ff3ae147ae147aef6f48116
a363666f6f63626172686d616e7976616c7383010203666e6573746564a0
CBOR
It also supports:
Byte String (a sequence of raw bytes)
Tags (annotations that allow to create new types)
CBOR
An example:
A3 63666F6F 63626172 686D616E7976616C73 83 01 02 03 666E6573746564 A0
{
"foo":
"bar",
"manyvals":
[
}
],
"nested":
{}
1,
2,
3
COSE / CWT
CBOR Object Signing and Encryption / CBOR Web Token
ok... again ๐
COSE
CBOR Object Signing and Encryption
COSE (inspired by JOSE) defines CBOR-based protocols for:
Encrypted data
Cryptographic signed data
MACed data
CWT
Like JWT but for CBOR
Defines a protocol for transferring claims between parties
CBOR Web Token
Claims are digitally signed for authenticity
CWT
A CWT is made of 4 parts:
1๏ธโฃ Protected header
CBOR Web Token
2๏ธโฃ Non protected header
3๏ธโฃ Payload
4๏ธโฃ Signature
CWT
A CWT is encoded as a (tagged) CBOR array with 4 values:
1๏ธโฃ Protected header (binary string)
CBOR Web Token
2๏ธโฃ Non protected headerย (map)
3๏ธโฃ Payloadย (binary string)
4๏ธโฃ Signatureย (binary string)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9106525/Screenshot_2021-11-13_at_17.40.40.png)
CWT tag
Array (length 4)
Unprotected header
Map (length 0)
Protected header
Binary String
Payload
Binary String
Signature
Binary String
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9106525/Screenshot_2021-11-13_at_17.40.40.png)
CWT payload
{
"1": "DK",
"4": 1625054000,
"6": 1622462000,
"-260": {
"ver":"1.0.0",
"nam":{
"fn":"Klaus",
"fnt":"KLAUS",
"gn":"Jรธrgensen",
"gnt":"JOERGENSEN"
},
"dob":"1983-01-06",
"v":[
{
"tg":"840539006",
"vp":"1119349007",
"mp":"EU/1/20/1528",
"ma":"ORG-100030215",
"dn":2,
"sd":2,
"dt":"2021-05-03",
"co":"DK",
"is":"Danish Health Data Authority",
"ci":"URN:UVCI:01:DK:B986830007345F99AE898FB82C6C61F2#A"
}
]
}
}
CBOR decode
ย ย (to JSON)
Binary String follows (CBOR Encoded)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128276/pasted-from-clipboard.png)
How to decodeย - quick recap ๐โโ๏ธ
1. Remove "HC1:" prefix
2. Base45 decode
3. Zlib decompress
4. Parse CWT
5. Parse CWT Payload as CBOR
6. Party hard! ๐ฅณ
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128301/pasted-from-clipboard.png)
Hey, let's implement this...
in Rust!
cargo new dgc-decode
Project bootstrap
// src/main.rs
fn main() {
let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z*AK.GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7UJ5PNDIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9UKPSH9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.GUQ$9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9R8Q02-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI4OIMEDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6ZC0JBY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0TB9FNDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QDSWNG4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR$F-75NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63ET-D1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$HK00XWPD2";
todo!()
// 1. Remove "HC1:" prefix
// 2. Base45 decode
// 3. Zlib decompress
// 4. Parse CWT
// 5. Parse CWT Payload as CBOR
}
fn main() {
let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z*AK.GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7UJ5PNDIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9UKPSH9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.GUQ$9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9R8Q02-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI4OIMEDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6ZC0JBY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0TB9FNDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QDSWNG4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR$F-75NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63ET-D1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$HK00XWPD2";
let no_prefix = remove_prefix(cert_data);
todo!();
// 2. Base45 decode
// 3. Zlib decompress
// 4. Parse CWT
// 5. Parse CWT Payload as CBOR
}
fn remove_prefix(data: &str) -> &str {
todo!()
// remove "HC1:" prefix and return remaining string
}
fn main() {
let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z*AK.GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7UJ5PNDIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9UKPSH9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.GUQ$9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9R8Q02-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI4OIMEDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6ZC0JBY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0TB9FNDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QDSWNG4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR$F-75NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63ET-D1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$HK00XWPD2";
let no_prefix = remove_prefix(cert_data);
todo!();
// 2. Base45 decode
// 3. Zlib decompress
// 4. Parse CWT
// 5. Parse CWT Payload as CBOR
}
fn remove_prefix(data: &str) -> &str {
if data.len() < 4 || !data.starts_with("HC1:") {
panic!("Invalid prefix"); // IRL use a Result!
}
&data[4..]
}
fn main() {
let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z*AK.GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7UJ5PNDIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9UKPSH9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.GUQ$9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9R8Q02-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI4OIMEDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6ZC0JBY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0TB9FNDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QDSWNG4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR$F-75NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63ET-D1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$HK00XWPD2";
let no_prefix = remove_prefix(cert_data);
let decoded = decode_base45(no_prefix);
todo!()
// 3. Zlib decompress
// 4. Parse CWT
// 5. Parse CWT Payload as CBOR
}
fn decode_base45(data: &str) -> Vec<u8> {
todo!()
// parse the string as base45 encoded and return the
// resulting raw bytes
}
// ...
cargo add base45
# Cargo.toml
# ...
[dependencies]
base45 = "3.0.0"
fn main() {
let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z*AK.GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7UJ5PNDIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9UKPSH9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.GUQ$9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9R8Q02-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI4OIMEDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6ZC0JBY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0TB9FNDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QDSWNG4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR$F-75NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63ET-D1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$HK00XWPD2";
let no_prefix = remove_prefix(cert_data);
let decoded = decode_base45(no_prefix);
todo!()
// 3. Zlib decompress
// 4. Parse CWT
// 5. Parse CWT Payload as CBOR
}
fn decode_base45(data: &str) -> Vec<u8> {
base45::decode(data).unwrap() // IRL use a Result!
}
// ...
fn main() {
let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z*AK.GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7UJ5PNDIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9UKPSH9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.GUQ$9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9R8Q02-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI4OIMEDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6ZC0JBY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0TB9FNDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QDSWNG4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR$F-75NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63ET-D1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$HK00XWPD2";
let no_prefix = remove_prefix(cert_data);
let decoded = decode_base45(no_prefix);
let decompressed = decompress(decoded);
todo!()
// 4. Parse CWT
// 5. Parse CWT Payload as CBOR
}
fn decompress(data: Vec<u8>) -> Vec<u8> {
todo!()
// decompress using zlib inflate
}
// ...
cargo add inflate
# Cargo.toml
# ...
[dependencies]
base45 = "3.0.0"
inflate = "0.4.5"
fn main() {
let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z*AK.GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7UJ5PNDIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9UKPSH9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.GUQ$9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9R8Q02-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI4OIMEDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6ZC0JBY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0TB9FNDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QDSWNG4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR$F-75NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63ET-D1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$HK00XWPD2";
let no_prefix = remove_prefix(cert_data);
let decoded = decode_base45(no_prefix);
let decompressed = decompress(decoded);
todo!()
// 4. Parse CWT
// 5. Parse CWT Payload as CBOR
}
fn decompress(data: Vec<u8>) -> Vec<u8> {
inflate::inflate_bytes_zlib(data.as_slice()).unwrap()
// IRL use a Result!
}
// ...
Are we on the right track?
fn main() {
let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z*AK.GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7UJ5PNDIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9UKPSH9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.GUQ$9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9R8Q02-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI4OIMEDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6ZC0JBY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0TB9FNDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QDSWNG4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR$F-75NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63ET-D1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$HK00XWPD2";
let no_prefix = remove_prefix(cert_data);
let decoded = decode_base45(no_prefix);
let decompressed = decompress(decoded);
println!("{}", String::from_utf8_lossy(&decompressed));
}
// ...
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9129395/Screenshot_2021-11-22_at_09.24.26.png)
Are we on the right track?
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9129395/Screenshot_2021-11-22_at_09.24.26.png)
We are starting to see some readable info! ๐คฉ
fn main() {
let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z*AK.GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7UJ5PNDIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9UKPSH9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.GUQ$9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9R8Q02-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI4OIMEDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6ZC0JBY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0TB9FNDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QDSWNG4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR$F-75NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63ET-D1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$HK00XWPD2";
let no_prefix = remove_prefix(cert_data);
let decoded = decode_base45(no_prefix);
let decompressed = decompress(decoded);
let cwt_payload = get_cwt_payload(decompressed);
todo!()
// 5. Parse CWT Payload as CBOR
}
fn get_cwt_payload(data: Vec<u8>) -> Vec<u8> {
todo!()
// Parse raw bytes as CBOR.
// Extract and return the raw bytes representing
// the CWT payload
}
// ...
cargo add ciborium
# Cargo.toml
# ...
[dependencies]
base45 = "3.0.0"
ciborium = "0.2.0"
inflate = "0.4.5"
use ciborium::{de::from_reader, value::Value};
fn main() {
let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z*AK.GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7UJ5PNDIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9UKPSH9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.GUQ$9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9R8Q02-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI4OIMEDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6ZC0JBY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0TB9FNDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QDSWNG4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR$F-75NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63ET-D1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$HK00XWPD2";
let no_prefix = remove_prefix(cert_data);
let decoded = decode_base45(no_prefix);
let decompressed = decompress(decoded);
let cwt_payload = get_cwt_payload(decompressed);
todo!()
// 5. Parse CWT Payload as CBOR
}
fn get_cwt_payload(data: Vec<u8>) -> Vec<u8> {
let parsed: Value = from_reader(data.as_slice()).unwrap();
println!("{:?}", parsed);
todo!()
}
// ...
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9130002/Screenshot_2021-11-22_at_13.14.17.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9130002/Screenshot_2021-11-22_at_13.14.17.png)
Payloadย โ
Non protected header
Protected header
The content is an array
CWT Tag (18)
Signature
// ...
fn get_cwt_payload(data: Vec<u8>) -> Vec<u8> {
// IRL avoid .unwrap() like hell and propagate errors correctly!
let parsed: Value = from_reader(data.as_slice()).unwrap();
let (tag, arr) = parsed.as_tag().unwrap();
assert_eq!(tag, 18);
let arr = arr.as_array().unwrap();
let payload = arr[2].as_bytes().unwrap();
payload.clone()
}
// ...
use ciborium::{de::from_reader, value::Value};
fn main() {
let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z*AK.GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7UJ5PNDIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9UKPSH9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.GUQ$9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9R8Q02-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI4OIMEDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6ZC0JBY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0TB9FNDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QDSWNG4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR$F-75NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63ET-D1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$HK00XWPD2";
let no_prefix = remove_prefix(cert_data);
let decoded = decode_base45(no_prefix);
let decompressed = decompress(decoded);
let cwt_payload = get_cwt_payload(decompressed);
let parsed_payload = parse_cwt_payload(cwt_payload);
}
fn parse_cwt_payload(data: Vec<u8>) -> Value {
// parse the binary data as CBOR
todo!()
}
// ...
use ciborium::{de::from_reader, value::Value};
fn main() {
let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z*AK.GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7UJ5PNDIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9UKPSH9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.GUQ$9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9R8Q02-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI4OIMEDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6ZC0JBY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0TB9FNDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QDSWNG4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR$F-75NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63ET-D1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$HK00XWPD2";
let no_prefix = remove_prefix(cert_data);
let decoded = decode_base45(no_prefix);
let decompressed = decompress(decoded);
let cwt_payload = get_cwt_payload(decompressed);
let parsed_payload = parse_cwt_payload(cwt_payload);
println!("{:#?}", parsed_payload);
}
fn parse_cwt_payload(data: Vec<u8>) -> Value {
from_reader(data.as_slice()).unwrap()
}
// ...
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9130084/ezgif.com-gif-maker__1_.gif)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9130087/pasted-from-clipboard.png)
Ok, let's make it more readable... ๐
cargo add serde_json
use ciborium::{de::from_reader, value::Value};
use serde_json::to_string_pretty;
fn main() {
let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4.P7*ZOP-IMJTI94F/8X*G3M9JUPY0BZW4Z*AK.GNNVR*G0C7PHBO335KN/NBEDBVBJ623323EAJ7UJ5PNDIB6PNS7B1DN%BBWC7WC7GB3683ML7SZ4ZI00T9UKPSH9WC5PF6846A$Q 76QW6A/98T5WBI$E9$UPV3Q.GUQ$9WC5R7ACB97C968ELZ5$DP6PP5IL*PP:+P*.1D9R8Q02-DE%QHOJ+PB/VSQOL9DLKWCZ3EBKDYGIZ J$XI4OIMEDTJCJKDLEDL9CZTAKBI/8D:8DKTDL+SQ05.$S6ZC0JBY63-C3F+LBQ99Q9E$BDZIA9JJ-JS7BYZJ92KG0TB9FNDA5KD9FED.B4JB3E9B9NNPCV9E6LFSD9C8J-QDSWNG4C-TLNKE$JDVPLW1KD0KCZGBKQCJE%RH5WAMSSR$F-75NXONQ84QV9/7/-LT1AIYBZGD$9RCLV-PTZ-K63ET-D1757H3GF9MV2N7WNQSY1SBZT-:81JJLHFQ-VG$HK00XWPD2";
let no_prefix = remove_prefix(cert_data);
let decoded = decode_base45(no_prefix);
let decompressed = decompress(decoded);
let cwt_payload = get_cwt_payload(decompressed);
let parsed_payload = parse_cwt_payload(cwt_payload);
println!("{}", to_string_pretty(&parsed_payload).unwrap());
}
// ...
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9130087/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9134318/Screenshot_2021-11-23_at_16.54.06.png)
All the code:
loige.link/green-code
A better (& more complete) implementation
as a Rust library
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128386/pasted-from-clipboard.png)
Exercise for the viewer:
Try to validate the signature
๐ You can get the Public Key from the certificate here: loige.link/green-examples
๐ Here you can find more about how the CoseSign1 protocol works: loige.link/cose-sign-verif
๐ฆ You could use a crate like ring for crypto!
(Spoiler: I implemented some of this stuff in my library!)
Is all this stuff legal? ๐ฐ
๐ You can certainly look into your certificate (and the test certificates!)
๐ฃ Looking into other people's certificate will disclose a lot of privacy-sensitive info (thread carefully)
๐ฒ Building a validator app? Check your country's regulation (especially if you need to store data!)
Cover Picture by FPVmat A on Unsplash
โค๏ธ ย Huge thanks to rust-italia for some precius review sessions and many pull requests!
โค๏ธ Thanks to @gbinside, @88_eugen, @AlleviTommaso, @npmccallum, @pelgerย for reviews and suggestions.
โ๏ธ nodejsdp.link
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/9128301/pasted-from-clipboard.png)
THANK YOU!
โค๏ธ