Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A look inside the European Covid Green Certificate (Codemotion 2021)

A look inside the European Covid Green Certificate (Codemotion 2021)

When I saw how dense the European Covid Green Pass QR code is, I got immediately curious: "WOW, there must be a lot of interesting data in here". So, I started to dig deeper and I found that there's really a great wealth of interesting encoding and verification technologies being used in it! In this talk, I will share what I learned! We will go on a journey where we will explore Base54 encoding, COSE tokens, CBOR serialization, elliptic curve crypto, and much more! Finally, I will also show you how to write a decoder for Green Pass certificates in the most hyped language ever: Rust!

Luciano Mammino

December 01, 2021
Tweet

More Decks by Luciano Mammino

Other Decks in Technology

Transcript

  1. A look inside the
    European Covid Green Certificate
    Luciano Mammino ( )
    @loige
    2021-12-01
    loige.link/green
    1

    View full-size slide

  2. Get these slides!
    loige
    loige.link/green
    2

    View full-size slide

  3. Let me introduce myself first...
    3

    View full-size slide

  4. Let me introduce myself first...
    👋 I'm Luciano (
    🍕🍝)
    3

    View full-size slide

  5. Let me introduce myself first...
    👋 I'm Luciano (
    🍕🍝)
    Senior Architect @ fourTheorem (Dublin )
    3

    View full-size slide

  6. Let me introduce myself first...
    👋 I'm Luciano (
    🍕🍝)
    Senior Architect @ fourTheorem (Dublin )
    nodejsdp.link
    📔 Co-Author of Node.js Design Patterns
    👉
    3

    View full-size slide

  7. Let me introduce myself first...
    👋 I'm Luciano (
    🍕🍝)
    Senior Architect @ fourTheorem (Dublin )
    nodejsdp.link
    📔 Co-Author of Node.js Design Patterns
    👉
    Let's connect!
    (blog)
    (twitter)
    (twitch)
    (github)
    loige.co
    @loige
    loige
    lmammino
    3

    View full-size slide

  8. 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
    loige 4

    View full-size slide

  9. Disclaimers
    loige 5

    View full-size slide

  10. Disclaimers
    loige
    🤓 I am not involved with the DGC working group
    5

    View full-size slide

  11. Disclaimers
    loige
    🤓 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!
    5

    View full-size slide

  12. Agenda + Goals
    loige 6

    View full-size slide

  13. Agenda + Goals
    loige
    1. Needs and principles
    2.
    🗝 Cryptographic model
    3.
    📦 The data
    4.
    🧅 Layers of encoding
    5.
    🛠 Decoding in Rust
    6

    View full-size slide

  14. Agenda + Goals
    loige
    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!
    6

    View full-size slide

  15. The need for a digital certificate in
    the COVID age
    loige 7

    View full-size slide

  16. The need for a digital certificate in
    the COVID age
    loige 8

    View full-size slide

  17. The need for a digital certificate in
    the COVID age
    loige
    😷 We need a system to quickly provide a proof against COVID
    (Vaccination, negative test, proof of recovery)
    8

    View full-size slide

  18. The need for a digital certificate in
    the COVID age
    loige
    😷 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
    8

    View full-size slide

  19. The need for a digital certificate in
    the COVID age
    loige
    😷 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
    8

    View full-size slide

  20. The EU Covid Green Pass
    a.k.a.
    Electronic Health Certificates
    (HCERT)
    loige.link/hcert-spec
    loige 9

    View full-size slide

  21. Electronic Health Certificates (HCERT)
    Requirements & Guiding Principles
    loige 10

    View full-size slide

  22. Electronic Health Certificates (HCERT)
    Requirements & Guiding Principles
    loige
    ✍ Signed data with machine readable content
    10

    View full-size slide

  23. Electronic Health Certificates (HCERT)
    Requirements & Guiding Principles
    loige
    ✍ Signed data with machine readable content
    📃 Use compact encoding
    10

    View full-size slide

  24. Electronic Health Certificates (HCERT)
    Requirements & Guiding Principles
    loige
    ✍ Signed data with machine readable content
    📃 Use compact encoding
    🤲 Based on open standards
    10

    View full-size slide

  25. Asymmetric cryptographic signatures
    loige 11

    View full-size slide

  26. Asymmetric cryptographic signatures
    loige
    🤫 Private Key
    101010101000101010010...
    11

    View full-size slide

  27. Asymmetric cryptographic signatures
    loige
    🤫 Private Key
    📢 Public Key
    101010101000101010010... 0101010101010101010101...
    11

    View full-size slide

  28. Asymmetric cryptographic signatures
    loige
    🤫 Private Key
    📢 Public Key
    🔗
    101010101000101010010... 0101010101010101010101...
    11

    View full-size slide

  29. Asymmetric cryptographic signatures
    loige
    🤫 Private Key
    📢 Public Key
    101010101000101010010... 0101010101010101010101...
    12

    View full-size slide

  30. Asymmetric cryptographic signatures
    loige
    🤫 Private Key
    📢 Public Key
    101010101000101010010... 0101010101010101010101...
    12

    View full-size slide

  31. Asymmetric cryptographic signatures
    loige
    🤫 Private Key
    📢 Public Key
    101010101000101010010... 0101010101010101010101...
    The owner of the private
    key signs the document
    12

    View full-size slide

  32. Asymmetric cryptographic signatures
    loige
    🤫 Private Key
    📢 Public Key
    101010101000101010010... 0101010101010101010101...
    The owner of the private
    key signs the document
    12

    View full-size slide

  33. Asymmetric cryptographic signatures
    loige
    🤫 Private Key
    📢 Public Key
    101010101000101010010... 0101010101010101010101...
    The owner of the private
    key signs the document
    Anyone can validate the
    signature using the public
    key
    12

    View full-size slide

  34. Asymmetric cryptographic signatures
    loige
    🤫 Private Key
    📢 Public Key
    101010101000101010010... 0101010101010101010101...
    The owner of the private
    key signs the document
    Anyone can validate the
    signature using the public
    key
    12

    View full-size slide

  35. What's inside a certificate?
    loige 13

    View full-size slide

  36. What's inside a certificate?
    loige 13

    View full-size slide

  37. What's inside a certificate?
    loige
    Cryptographic header (Key Id, Algorithm)
    13

    View full-size slide

  38. What's inside a certificate?
    loige
    DGC container
    Cryptographic header (Key Id, Algorithm)
    13

    View full-size slide

  39. What's inside a certificate?
    loige
    DGC container
    Cryptographic header (Key Id, Algorithm)
    Cryptographic Signature
    13

    View full-size slide

  40. What's inside a certificate?
    loige
    DGC container
    Cryptographic header (Key Id, Algorithm)
    Cryptographic Signature
    Header (Issuer, Issue date, expiry date)
    13

    View full-size slide

  41. What's inside a certificate?
    loige
    DGC container
    Cryptographic header (Key Id, Algorithm)
    Cryptographic Signature
    Header (Issuer, Issue date, expiry date)
    13
    Certificates list
    Personal data (name, surname, DoB)

    View full-size slide

  42. What's inside a certificate?
    loige
    DGC container
    Cryptographic header (Key Id, Algorithm)
    Cryptographic Signature
    Header (Issuer, Issue date, expiry date)
    13
    Certificates list
    vaccine, test, or recovery data
    Personal data (name, surname, DoB)

    View full-size slide

  43. An example
    loige loige.link/green-examples 14

    View full-size slide

  44. An example
    loige loige.link/green-examples 15

    View full-size slide

  45. {
    "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"
    }
    ]
    }
    }
    An example
    loige loige.link/green-examples 15

    View full-size slide

  46. {
    "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"
    }
    ]
    }
    }
    An example
    loige loige.link/green-examples
    DGC Header
    15

    View full-size slide

  47. {
    "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"
    }
    ]
    }
    }
    An example
    loige loige.link/green-examples
    Personal info
    DGC Header
    15

    View full-size slide

  48. {
    "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"
    }
    ]
    }
    }
    An example
    loige loige.link/green-examples
    Personal info
    Vaccine info
    DGC Header
    15

    View full-size slide

  49. Layered
    encoding
    loige 16
    🧅

    View full-size slide

  50. Layered
    encoding
    loige
    QRCODE ASCII mode
    16
    🧅

    View full-size slide

  51. Layered
    encoding
    loige
    QRCODE ASCII mode
    Prefix + Base45 encoding
    16
    🧅

    View full-size slide

  52. Layered
    encoding
    loige
    QRCODE ASCII mode
    Prefix + Base45 encoding
    Zlib compression
    16
    🧅

    View full-size slide

  53. Layered
    encoding
    loige
    QRCODE ASCII mode
    Prefix + Base45 encoding
    Zlib compression
    CWT signed data (COSE)
    16
    🧅

    View full-size slide

  54. Layered
    encoding
    loige
    QRCODE ASCII mode
    Prefix + Base45 encoding
    Zlib compression
    CWT signed data (COSE)
    CBOR document
    16
    🧅

    View full-size slide

  55. loige
    QRCode content
    17

    View full-size slide

  56. loige
    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
    17

    View full-size slide

  57. loige
    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)
    17

    View full-size slide

  58. loige
    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
    17

    View full-size slide

  59. Base45
    loige 18

    View full-size slide

  60. loige
    Base45
    19

    View full-size slide

  61. loige
    Allows to encode binary data in text format (ASCII)
    Base45
    19

    View full-size slide

  62. loige
    Allows to encode binary data in text format (ASCII)
    Like Base64, but it uses 45 characters instead:
    Base45
    19

    View full-size slide

  63. loige
    Allows to encode binary data in text format (ASCII)
    Like Base64, but it uses 45 characters instead:
    loige.link/base45-explained
    Base45
    19

    View full-size slide

  64. Base45
    loige 20

    View full-size slide

  65. Base45
    loige
    01001001 00100000 01100111
    01101111 01110100 00100000
    01101101 01111001 00100000
    01110011 01101000 01101111
    01110100 01110011 00100000
    11011000 00111101
    Some binary data
    20

    View full-size slide

  66. Base45
    loige
    01001001 00100000 01100111
    01101111 01110100 00100000
    01101101 01111001 00100000
    01110011 01101000 01101111
    01110100 01110011 00100000
    11011000 00111101
    Some binary data
    20

    View full-size slide

  67. Base45
    loige
    01001001 00100000 01100111
    01101111 01110100 00100000
    01101101 01111001 00100000
    01110011 01101000 01101111
    01110100 01110011 00100000
    11011000 00111101
    UTF8 (17 bytes)
    I got my shots
    💉
    Some binary data
    20

    View full-size slide

  68. Base45
    loige
    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
    Some binary data
    20

    View full-size slide

  69. Base45
    loige
    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==
    Some binary data
    20

    View full-size slide

  70. Base45
    loige
    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
    20

    View full-size slide

  71. loige
    loige.link/base45-rfc
    "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
    21

    View full-size slide

  72. loige
    Base45
    22

    View full-size slide

  73. loige
    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
    22

    View full-size slide

  74. loige
    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
    Base45
    Decode
    22

    View full-size slide

  75. loige
    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
    Base45
    Decode
    Compressed binary data
    22

    View full-size slide

  76. Zlib compression
    loige 23

    View full-size slide

  77. Zlib compression
    loige
    "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"
    zlib.net
    24

    View full-size slide

  78. Zlib compression
    loige 25

    View full-size slide

  79. Zlib compression
    loige
    Compressed binary data
    25

    View full-size slide

  80. Zlib compression
    loige
    Zlib inflate
    Compressed binary data
    25

    View full-size slide

  81. Zlib compression
    loige
    Zlib inflate
    Compressed binary data Decompressed binary data
    25

    View full-size slide

  82. Zlib compression
    loige 26

    View full-size slide

  83. Zlib compression
    loige 26

    View full-size slide

  84. Zlib compression
    loige
    👀 We start to see
    some "readable"
    information!
    26

    View full-size slide

  85. COSE / CWT
    loige
    CBOR Object Signing and Encryption / CBOR Web Token
    27

    View full-size slide

  86. CBOR !?
    🤔
    loige
    But wait, what the heck is

    28

    View full-size slide

  87. CBOR
    loige
    TLDR; Like JSON but in binary!
    loige.link/cbor-rfc
    29

    View full-size slide

  88. JSON
    loige
    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": {}}
    30

    View full-size slide

  89. CBOR
    loige
    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
    31

    View full-size slide

  90. CBOR
    loige
    It also supports:
    Byte String (a sequence of raw bytes)
    Tags (annotations that allow to create new types)
    32

    View full-size slide

  91. CBOR
    loige
    An example:
    A3 63666F6F 63626172 686D616E7976616C73 83 01 02 03 666E6573746564 A0
    33

    View full-size slide

  92. CBOR
    loige
    An example:
    A3 63666F6F 63626172 686D616E7976616C73 83 01 02 03 666E6573746564 A0
    {
    }
    33

    View full-size slide

  93. CBOR
    loige
    An example:
    A3 63666F6F 63626172 686D616E7976616C73 83 01 02 03 666E6573746564 A0
    {
    "foo":
    }
    33

    View full-size slide

  94. CBOR
    loige
    An example:
    A3 63666F6F 63626172 686D616E7976616C73 83 01 02 03 666E6573746564 A0
    {
    "foo": "bar",
    }
    33

    View full-size slide

  95. CBOR
    loige
    An example:
    A3 63666F6F 63626172 686D616E7976616C73 83 01 02 03 666E6573746564 A0
    {
    "foo": "bar",
    "manyvals":
    }
    33

    View full-size slide

  96. CBOR
    loige
    An example:
    A3 63666F6F 63626172 686D616E7976616C73 83 01 02 03 666E6573746564 A0
    {
    "foo": "bar",
    "manyvals": [
    }
    ],
    33

    View full-size slide

  97. CBOR
    loige
    An example:
    A3 63666F6F 63626172 686D616E7976616C73 83 01 02 03 666E6573746564 A0
    {
    "foo": "bar",
    "manyvals": [
    }
    ],
    1,
    33

    View full-size slide

  98. CBOR
    loige
    An example:
    A3 63666F6F 63626172 686D616E7976616C73 83 01 02 03 666E6573746564 A0
    {
    "foo": "bar",
    "manyvals": [
    }
    ],
    1, 2,
    33

    View full-size slide

  99. CBOR
    loige
    An example:
    A3 63666F6F 63626172 686D616E7976616C73 83 01 02 03 666E6573746564 A0
    {
    "foo": "bar",
    "manyvals": [
    }
    ],
    1, 2, 3
    33

    View full-size slide

  100. CBOR
    loige
    An example:
    A3 63666F6F 63626172 686D616E7976616C73 83 01 02 03 666E6573746564 A0
    {
    "foo": "bar",
    "manyvals": [
    }
    ],
    "nested":
    1, 2, 3
    33

    View full-size slide

  101. CBOR
    loige
    An example:
    A3 63666F6F 63626172 686D616E7976616C73 83 01 02 03 666E6573746564 A0
    {
    "foo": "bar",
    "manyvals": [
    }
    ],
    "nested": {}
    1, 2, 3
    33

    View full-size slide

  102. COSE / CWT
    loige
    CBOR Object Signing and Encryption / CBOR Web Token
    ok... again
    😅
    34

    View full-size slide

  103. COSE
    loige
    CBOR Object Signing and Encryption
    35

    View full-size slide

  104. COSE
    loige
    CBOR Object Signing and Encryption
    COSE (inspired by ) defines CBOR-based protocols for:
    JOSE
    35

    View full-size slide

  105. COSE
    loige
    CBOR Object Signing and Encryption
    COSE (inspired by ) defines CBOR-based protocols for:
    JOSE
    Encrypted data
    35

    View full-size slide

  106. COSE
    loige
    CBOR Object Signing and Encryption
    COSE (inspired by ) defines CBOR-based protocols for:
    JOSE
    Encrypted data
    Cryptographic signed data
    35

    View full-size slide

  107. COSE
    loige
    CBOR Object Signing and Encryption
    COSE (inspired by ) defines CBOR-based protocols for:
    JOSE
    Encrypted data
    Cryptographic signed data
    MACed data
    35

    View full-size slide

  108. COSE
    loige
    loige.link/cose-rfc
    CBOR Object Signing and Encryption
    COSE (inspired by ) defines CBOR-based protocols for:
    JOSE
    Encrypted data
    Cryptographic signed data
    MACed data
    35

    View full-size slide

  109. CWT
    loige
    CBOR Web Token
    36

    View full-size slide

  110. CWT
    loige
    Like but for CBOR
    JWT
    CBOR Web Token
    36

    View full-size slide

  111. CWT
    loige
    Like but for CBOR
    JWT
    Defines a protocol for transferring claims between parties
    CBOR Web Token
    36

    View full-size slide

  112. CWT
    loige
    Like but for CBOR
    JWT
    Defines a protocol for transferring claims between parties
    CBOR Web Token
    Claims are digitally signed for authenticity
    36

    View full-size slide

  113. CWT
    loige
    Like but for CBOR
    JWT
    loige.link/cwt-rfc
    Defines a protocol for transferring claims between parties
    CBOR Web Token
    Claims are digitally signed for authenticity
    36

    View full-size slide

  114. CWT
    loige
    CBOR Web Token
    37

    View full-size slide

  115. CWT
    loige
    A CWT is made of 4 parts:
    CBOR Web Token
    37

    View full-size slide

  116. CWT
    loige
    A CWT is made of 4 parts:
    Protected header
    CBOR Web Token
    37

    View full-size slide

  117. CWT
    loige
    A CWT is made of 4 parts:
    Protected header
    CBOR Web Token
    Non protected header
    37

    View full-size slide

  118. CWT
    loige
    A CWT is made of 4 parts:
    Protected header
    CBOR Web Token
    Non protected header
    Payload
    37

    View full-size slide

  119. CWT
    loige
    A CWT is made of 4 parts:
    Protected header
    CBOR Web Token
    Non protected header
    Payload
    Signature
    37

    View full-size slide

  120. CWT
    loige
    A CWT is encoded as a (tagged) CBOR array with 4 values:
    Protected header (binary string)
    CBOR Web Token
    Non protected header (map)
    Payload (binary string)
    Signature (binary string)
    38

    View full-size slide

  121. loige
    CWT tag
    39

    View full-size slide

  122. loige
    CWT tag
    39

    View full-size slide

  123. loige
    Array (length 4)
    CWT tag
    39

    View full-size slide

  124. loige
    Array (length 4)
    CWT tag
    39

    View full-size slide

  125. Protected
    header
    Binary String
    loige
    Array (length 4)
    CWT tag
    39

    View full-size slide

  126. Protected
    header
    Binary String
    loige
    Array (length 4)
    CWT tag
    39

    View full-size slide

  127. Protected
    header
    Binary String
    loige
    Array (length 4)
    CWT tag
    39
    Unprotected header
    Map (length 0)

    View full-size slide

  128. Protected
    header
    Binary String
    loige
    Array (length 4)
    CWT tag
    39
    Unprotected header
    Map (length 0)

    View full-size slide

  129. Protected
    header
    Binary String
    loige
    Array (length 4)
    CWT tag
    39
    Unprotected header
    Map (length 0)
    Payload
    Binary String

    View full-size slide

  130. Protected
    header
    Binary String
    loige
    Array (length 4)
    CWT tag
    39
    Unprotected header
    Map (length 0)
    Payload
    Binary String

    View full-size slide

  131. Protected
    header
    Binary String
    loige
    Array (length 4)
    CWT tag
    39
    Unprotected header
    Map (length 0)
    Payload
    Binary String
    Signature
    Binary String

    View full-size slide

  132. loige
    CWT payload
    40

    View full-size slide

  133. loige
    CWT payload
    40

    View full-size slide

  134. loige
    CWT payload
    Binary String follows (CBOR Encoded)
    40

    View full-size slide

  135. loige
    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"
    }
    ]
    }
    }
    Binary String follows (CBOR Encoded)
    CBOR decode
    (to JSON)
    40

    View full-size slide

  136. loige
    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"
    }
    ]
    }
    }
    Binary String follows (CBOR Encoded)
    CBOR decode
    (to JSON)
    40

    View full-size slide

  137. How to decode - quick recap
    loige 41

    View full-size slide

  138. How to decode - quick recap
    loige
    1. Remove "HC1:" prefix
    41

    View full-size slide

  139. How to decode - quick recap
    loige
    1. Remove "HC1:" prefix
    2. Base45 decode
    41

    View full-size slide

  140. How to decode - quick recap
    loige
    1. Remove "HC1:" prefix
    2. Base45 decode
    3. Zlib decompress
    41

    View full-size slide

  141. How to decode - quick recap
    loige
    1. Remove "HC1:" prefix
    2. Base45 decode
    3. Zlib decompress
    4. Parse CWT
    41

    View full-size slide

  142. How to decode - quick recap
    loige
    1. Remove "HC1:" prefix
    2. Base45 decode
    3. Zlib decompress
    4. Parse CWT
    5. Parse CWT Payload as CBOR
    41

    View full-size slide

  143. How to decode - quick recap
    loige
    1. Remove "HC1:" prefix
    2. Base45 decode
    3. Zlib decompress
    4. Parse CWT
    5. Parse CWT Payload as CBOR
    6. Party hard!
    🥳
    41

    View full-size slide

  144. How to decode - quick recap
    loige
    1. Remove "HC1:" prefix
    2. Base45 decode
    3. Zlib decompress
    4. Parse CWT
    5. Parse CWT Payload as CBOR
    6. Party hard!
    🥳
    41

    View full-size slide

  145. How to decode - quick recap
    loige
    1. Remove "HC1:" prefix
    2. Base45 decode
    3. Zlib decompress
    4. Parse CWT
    5. Parse CWT Payload as CBOR
    6. Party hard!
    🥳
    Hey, let's implement this...
    41

    View full-size slide

  146. How to decode - quick recap
    loige
    1. Remove "HC1:" prefix
    2. Base45 decode
    3. Zlib decompress
    4. Parse CWT
    5. Parse CWT Payload as CBOR
    6. Party hard!
    🥳
    Hey, let's implement this... in Rust!
    41

    View full-size slide

  147. loige
    cargo new dgc-decode
    Project bootstrap
    42

    View full-size slide

  148. // src/main.rs
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    todo!()
    // 1. Remove "HC1:" prefix
    // 2. Base45 decode
    // 3. Zlib decompress
    // 4. Parse CWT
    // 5. Parse CWT Payload as CBOR
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    loige 43

    View full-size slide

  149. // src/main.rs
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    todo!()
    // 1. Remove "HC1:" prefix
    // 2. Base45 decode
    // 3. Zlib decompress
    // 4. Parse CWT
    // 5. Parse CWT Payload as CBOR
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    // src/main.rs
    1
    2
    fn main() {
    3
    4
    todo!()
    5
    // 1. Remove "HC1:" prefix
    6
    // 2. Base45 decode
    7
    // 3. Zlib decompress
    8
    // 4. Parse CWT
    9
    // 5. Parse CWT Payload as CBOR
    10
    }
    11
    loige 43

    View full-size slide

  150. // src/main.rs
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    todo!()
    // 1. Remove "HC1:" prefix
    // 2. Base45 decode
    // 3. Zlib decompress
    // 4. Parse CWT
    // 5. Parse CWT Payload as CBOR
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    // src/main.rs
    1
    2
    fn main() {
    3
    4
    todo!()
    5
    // 1. Remove "HC1:" prefix
    6
    // 2. Base45 decode
    7
    // 3. Zlib decompress
    8
    // 4. Parse CWT
    9
    // 5. Parse CWT Payload as CBOR
    10
    }
    11
    todo!()
    // 1. Remove "HC1:" prefix
    // 2. Base45 decode
    // 3. Zlib decompress
    // 4. Parse CWT
    // 5. Parse CWT Payload as CBOR
    // src/main.rs
    1
    2
    fn main() {
    3
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    4
    5
    6
    7
    8
    9
    10
    }
    11
    loige 43

    View full-size slide

  151. // src/main.rs
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    todo!()
    // 1. Remove "HC1:" prefix
    // 2. Base45 decode
    // 3. Zlib decompress
    // 4. Parse CWT
    // 5. Parse CWT Payload as CBOR
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    // src/main.rs
    1
    2
    fn main() {
    3
    4
    todo!()
    5
    // 1. Remove "HC1:" prefix
    6
    // 2. Base45 decode
    7
    // 3. Zlib decompress
    8
    // 4. Parse CWT
    9
    // 5. Parse CWT Payload as CBOR
    10
    }
    11
    todo!()
    // 1. Remove "HC1:" prefix
    // 2. Base45 decode
    // 3. Zlib decompress
    // 4. Parse CWT
    // 5. Parse CWT Payload as CBOR
    // src/main.rs
    1
    2
    fn main() {
    3
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    4
    5
    6
    7
    8
    9
    10
    }
    11
    // src/main.rs
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    todo!()
    // 1. Remove "HC1:" prefix
    // 2. Base45 decode
    // 3. Zlib decompress
    // 4. Parse CWT
    // 5. Parse CWT Payload as CBOR
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    loige 43

    View full-size slide

  152. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    loige 44

    View full-size slide

  153. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let no_prefix = remove_prefix(cert_data);
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    3
    todo!();
    4
    // 2. Base45 decode
    5
    // 3. Zlib decompress
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn remove_prefix(data: &str) -> &str {
    11
    todo!()
    12
    // remove "HC1:" prefix and return remaining string
    13
    }
    14
    loige 44

    View full-size slide

  154. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let no_prefix = remove_prefix(cert_data);
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    3
    todo!();
    4
    // 2. Base45 decode
    5
    // 3. Zlib decompress
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn remove_prefix(data: &str) -> &str {
    11
    todo!()
    12
    // remove "HC1:" prefix and return remaining string
    13
    }
    14
    fn remove_prefix(data: &str) -> &str {
    todo!()
    // remove "HC1:" prefix and return remaining string
    }
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    todo!();
    4
    // 2. Base45 decode
    5
    // 3. Zlib decompress
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    11
    12
    13
    14
    loige 44

    View full-size slide

  155. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let no_prefix = remove_prefix(cert_data);
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    3
    todo!();
    4
    // 2. Base45 decode
    5
    // 3. Zlib decompress
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn remove_prefix(data: &str) -> &str {
    11
    todo!()
    12
    // remove "HC1:" prefix and return remaining string
    13
    }
    14
    fn remove_prefix(data: &str) -> &str {
    todo!()
    // remove "HC1:" prefix and return remaining string
    }
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    todo!();
    4
    // 2. Base45 decode
    5
    // 3. Zlib decompress
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    11
    12
    13
    14
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    loige 44

    View full-size slide

  156. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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..]
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    loige 45

    View full-size slide

  157. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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..]
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    if data.len() < 4 || !data.starts_with("HC1:") {
    panic!("Invalid prefix"); // IRL use a Result!
    }
    &data[4..]
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    todo!();
    4
    // 2. Base45 decode
    5
    // 3. Zlib decompress
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn remove_prefix(data: &str) -> &str {
    11
    12
    13
    14
    15
    16
    }
    17
    loige 45

    View full-size slide

  158. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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..]
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    if data.len() < 4 || !data.starts_with("HC1:") {
    panic!("Invalid prefix"); // IRL use a Result!
    }
    &data[4..]
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    todo!();
    4
    // 2. Base45 decode
    5
    // 3. Zlib decompress
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn remove_prefix(data: &str) -> &str {
    11
    12
    13
    14
    15
    16
    }
    17
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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..]
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    loige 45

    View full-size slide

  159. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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 {
    todo!()
    // parse the string as base45 encoded and return the
    // resulting raw bytes
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    loige 46

    View full-size slide

  160. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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 {
    todo!()
    // parse the string as base45 encoded and return the
    // resulting raw bytes
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    let decoded = decode_base45(no_prefix);
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    4
    todo!()
    5
    // 3. Zlib decompress
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn decode_base45(data: &str) -> Vec {
    11
    todo!()
    12
    // parse the string as base45 encoded and return the
    13
    // resulting raw bytes
    14
    }
    15
    16
    // ...
    17
    loige 46

    View full-size slide

  161. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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 {
    todo!()
    // parse the string as base45 encoded and return the
    // resulting raw bytes
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    let decoded = decode_base45(no_prefix);
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    4
    todo!()
    5
    // 3. Zlib decompress
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn decode_base45(data: &str) -> Vec {
    11
    todo!()
    12
    // parse the string as base45 encoded and return the
    13
    // resulting raw bytes
    14
    }
    15
    16
    // ...
    17
    fn decode_base45(data: &str) -> Vec {
    todo!()
    // parse the string as base45 encoded and return the
    // resulting raw bytes
    }
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    todo!()
    5
    // 3. Zlib decompress
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    11
    12
    13
    14
    15
    16
    // ...
    17
    loige 46

    View full-size slide

  162. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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 {
    todo!()
    // parse the string as base45 encoded and return the
    // resulting raw bytes
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    let decoded = decode_base45(no_prefix);
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    4
    todo!()
    5
    // 3. Zlib decompress
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn decode_base45(data: &str) -> Vec {
    11
    todo!()
    12
    // parse the string as base45 encoded and return the
    13
    // resulting raw bytes
    14
    }
    15
    16
    // ...
    17
    fn decode_base45(data: &str) -> Vec {
    todo!()
    // parse the string as base45 encoded and return the
    // resulting raw bytes
    }
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    todo!()
    5
    // 3. Zlib decompress
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    11
    12
    13
    14
    15
    16
    // ...
    17
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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 {
    todo!()
    // parse the string as base45 encoded and return the
    // resulting raw bytes
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    loige 46

    View full-size slide

  163. loige
    cargo add base45
    47

    View full-size slide

  164. loige
    cargo add base45
    # Cargo.toml
    # ...
    [dependencies]
    base45 = "3.0.0"
    47

    View full-size slide

  165. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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 {
    base45::decode(data).unwrap() // IRL use a Result!
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    loige 48

    View full-size slide

  166. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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 {
    base45::decode(data).unwrap() // IRL use a Result!
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    base45::decode(data).unwrap() // IRL use a Result!
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    todo!()
    5
    // 3. Zlib decompress
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn decode_base45(data: &str) -> Vec {
    11
    12
    }
    13
    14
    // ...
    15
    loige 48

    View full-size slide

  167. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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 {
    base45::decode(data).unwrap() // IRL use a Result!
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    base45::decode(data).unwrap() // IRL use a Result!
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    todo!()
    5
    // 3. Zlib decompress
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn decode_base45(data: &str) -> Vec {
    11
    12
    }
    13
    14
    // ...
    15
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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 {
    base45::decode(data).unwrap() // IRL use a Result!
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    loige 48

    View full-size slide

  168. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    todo!()
    // decompress using zlib inflate
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    loige 49

    View full-size slide

  169. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    todo!()
    // decompress using zlib inflate
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let decompressed = decompress(decoded);
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    5
    todo!()
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn decompress(data: Vec) -> Vec {
    11
    todo!()
    12
    // decompress using zlib inflate
    13
    }
    14
    15
    // ...
    16
    loige 49

    View full-size slide

  170. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    todo!()
    // decompress using zlib inflate
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let decompressed = decompress(decoded);
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    5
    todo!()
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn decompress(data: Vec) -> Vec {
    11
    todo!()
    12
    // decompress using zlib inflate
    13
    }
    14
    15
    // ...
    16
    fn decompress(data: Vec) -> Vec {
    todo!()
    // decompress using zlib inflate
    }
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    let decompressed = decompress(decoded);
    5
    todo!()
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    11
    12
    13
    14
    15
    // ...
    16
    loige 49

    View full-size slide

  171. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    todo!()
    // decompress using zlib inflate
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let decompressed = decompress(decoded);
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    5
    todo!()
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn decompress(data: Vec) -> Vec {
    11
    todo!()
    12
    // decompress using zlib inflate
    13
    }
    14
    15
    // ...
    16
    fn decompress(data: Vec) -> Vec {
    todo!()
    // decompress using zlib inflate
    }
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    let decompressed = decompress(decoded);
    5
    todo!()
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    11
    12
    13
    14
    15
    // ...
    16
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    todo!()
    // decompress using zlib inflate
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    loige 49

    View full-size slide

  172. loige
    cargo add inflate
    50

    View full-size slide

  173. loige
    cargo add inflate
    # Cargo.toml
    # ...
    [dependencies]
    base45 = "3.0.0"
    inflate = "0.4.5"
    50

    View full-size slide

  174. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    inflate::inflate_bytes_zlib(data.as_slice()).unwrap()
    // IRL use a Result!
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    loige 51

    View full-size slide

  175. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    inflate::inflate_bytes_zlib(data.as_slice()).unwrap()
    // IRL use a Result!
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    inflate::inflate_bytes_zlib(data.as_slice()).unwrap()
    // IRL use a Result!
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    let decompressed = decompress(decoded);
    5
    todo!()
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn decompress(data: Vec) -> Vec {
    11
    12
    13
    }
    14
    15
    // ...
    16
    loige 51

    View full-size slide

  176. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    inflate::inflate_bytes_zlib(data.as_slice()).unwrap()
    // IRL use a Result!
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    inflate::inflate_bytes_zlib(data.as_slice()).unwrap()
    // IRL use a Result!
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    let decompressed = decompress(decoded);
    5
    todo!()
    6
    // 4. Parse CWT
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn decompress(data: Vec) -> Vec {
    11
    12
    13
    }
    14
    15
    // ...
    16
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    inflate::inflate_bytes_zlib(data.as_slice()).unwrap()
    // IRL use a Result!
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    loige 51

    View full-size slide

  177. Are we on the right track?
    loige
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    let no_prefix = remove_prefix(cert_data);
    let decoded = decode_base45(no_prefix);
    let decompressed = decompress(decoded);
    println!("{}", String::from_utf8_lossy(&decompressed));
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    52

    View full-size slide

  178. Are we on the right track?
    loige
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    let no_prefix = remove_prefix(cert_data);
    let decoded = decode_base45(no_prefix);
    let decompressed = decompress(decoded);
    println!("{}", String::from_utf8_lossy(&decompressed));
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    println!("{}", String::from_utf8_lossy(&decompressed));
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    let decompressed = decompress(decoded);
    5
    6
    7
    }
    8
    9
    // ...
    10
    52

    View full-size slide

  179. Are we on the right track?
    loige 53

    View full-size slide

  180. Are we on the right track?
    loige
    We are starting to see some
    readable info!
    🤩
    53

    View full-size slide

  181. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    todo!()
    // Parse raw bytes as CBOR.
    // Extract and return the raw bytes representing
    // the CWT payload
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    loige 54

    View full-size slide

  182. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    todo!()
    // Parse raw bytes as CBOR.
    // Extract and return the raw bytes representing
    // the CWT payload
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    let cwt_payload = get_cwt_payload(decompressed);
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    let decompressed = decompress(decoded);
    5
    6
    todo!()
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn get_cwt_payload(data: Vec) -> Vec {
    11
    todo!()
    12
    // Parse raw bytes as CBOR.
    13
    // Extract and return the raw bytes representing
    14
    // the CWT payload
    15
    }
    16
    17
    // ...
    18
    loige 54

    View full-size slide

  183. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    todo!()
    // Parse raw bytes as CBOR.
    // Extract and return the raw bytes representing
    // the CWT payload
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    let cwt_payload = get_cwt_payload(decompressed);
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    let decompressed = decompress(decoded);
    5
    6
    todo!()
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn get_cwt_payload(data: Vec) -> Vec {
    11
    todo!()
    12
    // Parse raw bytes as CBOR.
    13
    // Extract and return the raw bytes representing
    14
    // the CWT payload
    15
    }
    16
    17
    // ...
    18
    fn get_cwt_payload(data: Vec) -> Vec {
    todo!()
    // Parse raw bytes as CBOR.
    // Extract and return the raw bytes representing
    // the CWT payload
    }
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    let decompressed = decompress(decoded);
    5
    let cwt_payload = get_cwt_payload(decompressed);
    6
    todo!()
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // ...
    18
    loige 54

    View full-size slide

  184. fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    todo!()
    // Parse raw bytes as CBOR.
    // Extract and return the raw bytes representing
    // the CWT payload
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    let cwt_payload = get_cwt_payload(decompressed);
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    let decompressed = decompress(decoded);
    5
    6
    todo!()
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    fn get_cwt_payload(data: Vec) -> Vec {
    11
    todo!()
    12
    // Parse raw bytes as CBOR.
    13
    // Extract and return the raw bytes representing
    14
    // the CWT payload
    15
    }
    16
    17
    // ...
    18
    fn get_cwt_payload(data: Vec) -> Vec {
    todo!()
    // Parse raw bytes as CBOR.
    // Extract and return the raw bytes representing
    // the CWT payload
    }
    fn main() {
    1
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    2
    let no_prefix = remove_prefix(cert_data);
    3
    let decoded = decode_base45(no_prefix);
    4
    let decompressed = decompress(decoded);
    5
    let cwt_payload = get_cwt_payload(decompressed);
    6
    todo!()
    7
    // 5. Parse CWT Payload as CBOR
    8
    }
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // ...
    18
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    todo!()
    // Parse raw bytes as CBOR.
    // Extract and return the raw bytes representing
    // the CWT payload
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    loige 54

    View full-size slide

  185. loige
    cargo add ciborium
    55

    View full-size slide

  186. loige
    cargo add ciborium
    # Cargo.toml
    # ...
    [dependencies]
    base45 = "3.0.0"
    ciborium = "0.2.0"
    inflate = "0.4.5"
    55

    View full-size slide

  187. use ciborium::{de::from_reader, value::Value};
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    println!("{:?}", parsed);
    todo!()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    loige 56

    View full-size slide

  188. use ciborium::{de::from_reader, value::Value};
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    println!("{:?}", parsed);
    todo!()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    use ciborium::{de::from_reader, value::Value};
    1
    2
    fn main() {
    3
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    4
    let no_prefix = remove_prefix(cert_data);
    5
    let decoded = decode_base45(no_prefix);
    6
    let decompressed = decompress(decoded);
    7
    let cwt_payload = get_cwt_payload(decompressed);
    8
    todo!()
    9
    // 5. Parse CWT Payload as CBOR
    10
    }
    11
    12
    fn get_cwt_payload(data: Vec) -> Vec {
    13
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    14
    println!("{:?}", parsed);
    15
    todo!()
    16
    }
    17
    18
    // ...
    19
    loige 56

    View full-size slide

  189. use ciborium::{de::from_reader, value::Value};
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Vec {
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    println!("{:?}", parsed);
    todo!()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    use ciborium::{de::from_reader, value::Value};
    1
    2
    fn main() {
    3
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    4
    let no_prefix = remove_prefix(cert_data);
    5
    let decoded = decode_base45(no_prefix);
    6
    let decompressed = decompress(decoded);
    7
    let cwt_payload = get_cwt_payload(decompressed);
    8
    todo!()
    9
    // 5. Parse CWT Payload as CBOR
    10
    }
    11
    12
    fn get_cwt_payload(data: Vec) -> Vec {
    13
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    14
    println!("{:?}", parsed);
    15
    todo!()
    16
    }
    17
    18
    // ...
    19
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    println!("{:?}", parsed);
    use ciborium::{de::from_reader, value::Value};
    1
    2
    fn main() {
    3
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    4
    let no_prefix = remove_prefix(cert_data);
    5
    let decoded = decode_base45(no_prefix);
    6
    let decompressed = decompress(decoded);
    7
    let cwt_payload = get_cwt_payload(decompressed);
    8
    todo!()
    9
    // 5. Parse CWT Payload as CBOR
    10
    }
    11
    12
    fn get_cwt_payload(data: Vec) -> Vec {
    13
    14
    15
    todo!()
    16
    }
    17
    18
    // ...
    19
    loige 56

    View full-size slide

  190. CWT Tag (18)
    57
    loige

    View full-size slide

  191. The content is an array
    57
    loige

    View full-size slide

  192. 57
    Protected header
    loige

    View full-size slide

  193. 57
    Non protected header
    loige

    View full-size slide

  194. 57
    Payload

    loige

    View full-size slide

  195. 57
    Signature
    loige

    View full-size slide

  196. // ...
    fn get_cwt_payload(data: Vec) -> Vec {
    // 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()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    loige 58

    View full-size slide

  197. // ...
    fn get_cwt_payload(data: Vec) -> Vec {
    // 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()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    // ...
    1
    2
    fn get_cwt_payload(data: Vec) -> Vec {
    3
    // IRL avoid .unwrap() like hell and propagate errors correctly!
    4
    5
    let (tag, arr) = parsed.as_tag().unwrap();
    6
    assert_eq!(tag, 18);
    7
    let arr = arr.as_array().unwrap();
    8
    let payload = arr[2].as_bytes().unwrap();
    9
    payload.clone()
    10
    }
    11
    12
    // ...
    13
    loige 58

    View full-size slide

  198. // ...
    fn get_cwt_payload(data: Vec) -> Vec {
    // 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()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    // ...
    1
    2
    fn get_cwt_payload(data: Vec) -> Vec {
    3
    // IRL avoid .unwrap() like hell and propagate errors correctly!
    4
    5
    let (tag, arr) = parsed.as_tag().unwrap();
    6
    assert_eq!(tag, 18);
    7
    let arr = arr.as_array().unwrap();
    8
    let payload = arr[2].as_bytes().unwrap();
    9
    payload.clone()
    10
    }
    11
    12
    // ...
    13
    let (tag, arr) = parsed.as_tag().unwrap();
    assert_eq!(tag, 18);
    // ...
    1
    2
    fn get_cwt_payload(data: Vec) -> Vec {
    3
    // IRL avoid .unwrap() like hell and propagate errors correctly!
    4
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    5
    6
    7
    let arr = arr.as_array().unwrap();
    8
    let payload = arr[2].as_bytes().unwrap();
    9
    payload.clone()
    10
    }
    11
    12
    // ...
    13
    loige 58

    View full-size slide

  199. // ...
    fn get_cwt_payload(data: Vec) -> Vec {
    // 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()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    // ...
    1
    2
    fn get_cwt_payload(data: Vec) -> Vec {
    3
    // IRL avoid .unwrap() like hell and propagate errors correctly!
    4
    5
    let (tag, arr) = parsed.as_tag().unwrap();
    6
    assert_eq!(tag, 18);
    7
    let arr = arr.as_array().unwrap();
    8
    let payload = arr[2].as_bytes().unwrap();
    9
    payload.clone()
    10
    }
    11
    12
    // ...
    13
    let (tag, arr) = parsed.as_tag().unwrap();
    assert_eq!(tag, 18);
    // ...
    1
    2
    fn get_cwt_payload(data: Vec) -> Vec {
    3
    // IRL avoid .unwrap() like hell and propagate errors correctly!
    4
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    5
    6
    7
    let arr = arr.as_array().unwrap();
    8
    let payload = arr[2].as_bytes().unwrap();
    9
    payload.clone()
    10
    }
    11
    12
    // ...
    13
    let arr = arr.as_array().unwrap();
    // ...
    1
    2
    fn get_cwt_payload(data: Vec) -> Vec {
    3
    // IRL avoid .unwrap() like hell and propagate errors correctly!
    4
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    5
    let (tag, arr) = parsed.as_tag().unwrap();
    6
    assert_eq!(tag, 18);
    7
    8
    let payload = arr[2].as_bytes().unwrap();
    9
    payload.clone()
    10
    }
    11
    12
    // ...
    13
    loige 58

    View full-size slide

  200. // ...
    fn get_cwt_payload(data: Vec) -> Vec {
    // 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()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    // ...
    1
    2
    fn get_cwt_payload(data: Vec) -> Vec {
    3
    // IRL avoid .unwrap() like hell and propagate errors correctly!
    4
    5
    let (tag, arr) = parsed.as_tag().unwrap();
    6
    assert_eq!(tag, 18);
    7
    let arr = arr.as_array().unwrap();
    8
    let payload = arr[2].as_bytes().unwrap();
    9
    payload.clone()
    10
    }
    11
    12
    // ...
    13
    let (tag, arr) = parsed.as_tag().unwrap();
    assert_eq!(tag, 18);
    // ...
    1
    2
    fn get_cwt_payload(data: Vec) -> Vec {
    3
    // IRL avoid .unwrap() like hell and propagate errors correctly!
    4
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    5
    6
    7
    let arr = arr.as_array().unwrap();
    8
    let payload = arr[2].as_bytes().unwrap();
    9
    payload.clone()
    10
    }
    11
    12
    // ...
    13
    let arr = arr.as_array().unwrap();
    // ...
    1
    2
    fn get_cwt_payload(data: Vec) -> Vec {
    3
    // IRL avoid .unwrap() like hell and propagate errors correctly!
    4
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    5
    let (tag, arr) = parsed.as_tag().unwrap();
    6
    assert_eq!(tag, 18);
    7
    8
    let payload = arr[2].as_bytes().unwrap();
    9
    payload.clone()
    10
    }
    11
    12
    // ...
    13
    let payload = arr[2].as_bytes().unwrap();
    payload.clone()
    // ...
    1
    2
    fn get_cwt_payload(data: Vec) -> Vec {
    3
    // IRL avoid .unwrap() like hell and propagate errors correctly!
    4
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    5
    let (tag, arr) = parsed.as_tag().unwrap();
    6
    assert_eq!(tag, 18);
    7
    let arr = arr.as_array().unwrap();
    8
    9
    10
    }
    11
    12
    // ...
    13
    loige 58

    View full-size slide

  201. // ...
    fn get_cwt_payload(data: Vec) -> Vec {
    // 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()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    // ...
    1
    2
    fn get_cwt_payload(data: Vec) -> Vec {
    3
    // IRL avoid .unwrap() like hell and propagate errors correctly!
    4
    5
    let (tag, arr) = parsed.as_tag().unwrap();
    6
    assert_eq!(tag, 18);
    7
    let arr = arr.as_array().unwrap();
    8
    let payload = arr[2].as_bytes().unwrap();
    9
    payload.clone()
    10
    }
    11
    12
    // ...
    13
    let (tag, arr) = parsed.as_tag().unwrap();
    assert_eq!(tag, 18);
    // ...
    1
    2
    fn get_cwt_payload(data: Vec) -> Vec {
    3
    // IRL avoid .unwrap() like hell and propagate errors correctly!
    4
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    5
    6
    7
    let arr = arr.as_array().unwrap();
    8
    let payload = arr[2].as_bytes().unwrap();
    9
    payload.clone()
    10
    }
    11
    12
    // ...
    13
    let arr = arr.as_array().unwrap();
    // ...
    1
    2
    fn get_cwt_payload(data: Vec) -> Vec {
    3
    // IRL avoid .unwrap() like hell and propagate errors correctly!
    4
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    5
    let (tag, arr) = parsed.as_tag().unwrap();
    6
    assert_eq!(tag, 18);
    7
    8
    let payload = arr[2].as_bytes().unwrap();
    9
    payload.clone()
    10
    }
    11
    12
    // ...
    13
    let payload = arr[2].as_bytes().unwrap();
    payload.clone()
    // ...
    1
    2
    fn get_cwt_payload(data: Vec) -> Vec {
    3
    // IRL avoid .unwrap() like hell and propagate errors correctly!
    4
    let parsed: Value = from_reader(data.as_slice()).unwrap();
    5
    let (tag, arr) = parsed.as_tag().unwrap();
    6
    assert_eq!(tag, 18);
    7
    let arr = arr.as_array().unwrap();
    8
    9
    10
    }
    11
    12
    // ...
    13
    // ...
    fn get_cwt_payload(data: Vec) -> Vec {
    // 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()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    loige 58

    View full-size slide

  202. use ciborium::{de::from_reader, value::Value};
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Value {
    // parse the binary data as CBOR
    todo!()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    loige 59

    View full-size slide

  203. use ciborium::{de::from_reader, value::Value};
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Value {
    // parse the binary data as CBOR
    todo!()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    let parsed_payload = parse_cwt_payload(cwt_payload);
    use ciborium::{de::from_reader, value::Value};
    1
    2
    fn main() {
    3
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    4
    let no_prefix = remove_prefix(cert_data);
    5
    let decoded = decode_base45(no_prefix);
    6
    let decompressed = decompress(decoded);
    7
    let cwt_payload = get_cwt_payload(decompressed);
    8
    9
    }
    10
    11
    fn parse_cwt_payload(data: Vec) -> Value {
    12
    // parse the binary data as CBOR
    13
    todo!()
    14
    }
    15
    16
    // ...
    17
    loige 59

    View full-size slide

  204. use ciborium::{de::from_reader, value::Value};
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Value {
    // parse the binary data as CBOR
    todo!()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    let parsed_payload = parse_cwt_payload(cwt_payload);
    use ciborium::{de::from_reader, value::Value};
    1
    2
    fn main() {
    3
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    4
    let no_prefix = remove_prefix(cert_data);
    5
    let decoded = decode_base45(no_prefix);
    6
    let decompressed = decompress(decoded);
    7
    let cwt_payload = get_cwt_payload(decompressed);
    8
    9
    }
    10
    11
    fn parse_cwt_payload(data: Vec) -> Value {
    12
    // parse the binary data as CBOR
    13
    todo!()
    14
    }
    15
    16
    // ...
    17
    fn parse_cwt_payload(data: Vec) -> Value {
    // parse the binary data as CBOR
    todo!()
    }
    use ciborium::{de::from_reader, value::Value};
    1
    2
    fn main() {
    3
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    4
    let no_prefix = remove_prefix(cert_data);
    5
    let decoded = decode_base45(no_prefix);
    6
    let decompressed = decompress(decoded);
    7
    let cwt_payload = get_cwt_payload(decompressed);
    8
    let parsed_payload = parse_cwt_payload(cwt_payload);
    9
    }
    10
    11
    12
    13
    14
    15
    16
    // ...
    17
    loige 59

    View full-size slide

  205. use ciborium::{de::from_reader, value::Value};
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Value {
    from_reader(data.as_slice()).unwrap()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    loige 60

    View full-size slide

  206. use ciborium::{de::from_reader, value::Value};
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Value {
    from_reader(data.as_slice()).unwrap()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    from_reader(data.as_slice()).unwrap()
    use ciborium::{de::from_reader, value::Value};
    1
    2
    fn main() {
    3
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    4
    let no_prefix = remove_prefix(cert_data);
    5
    let decoded = decode_base45(no_prefix);
    6
    let decompressed = decompress(decoded);
    7
    let cwt_payload = get_cwt_payload(decompressed);
    8
    let parsed_payload = parse_cwt_payload(cwt_payload);
    9
    println!("{:#?}", parsed_payload);
    10
    }
    11
    12
    fn parse_cwt_payload(data: Vec) -> Value {
    13
    14
    }
    15
    16
    // ...
    17
    loige 60

    View full-size slide

  207. use ciborium::{de::from_reader, value::Value};
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Value {
    from_reader(data.as_slice()).unwrap()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    from_reader(data.as_slice()).unwrap()
    use ciborium::{de::from_reader, value::Value};
    1
    2
    fn main() {
    3
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    4
    let no_prefix = remove_prefix(cert_data);
    5
    let decoded = decode_base45(no_prefix);
    6
    let decompressed = decompress(decoded);
    7
    let cwt_payload = get_cwt_payload(decompressed);
    8
    let parsed_payload = parse_cwt_payload(cwt_payload);
    9
    println!("{:#?}", parsed_payload);
    10
    }
    11
    12
    fn parse_cwt_payload(data: Vec) -> Value {
    13
    14
    }
    15
    16
    // ...
    17
    println!("{:#?}", parsed_payload);
    use ciborium::{de::from_reader, value::Value};
    1
    2
    fn main() {
    3
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    4
    let no_prefix = remove_prefix(cert_data);
    5
    let decoded = decode_base45(no_prefix);
    6
    let decompressed = decompress(decoded);
    7
    let cwt_payload = get_cwt_payload(decompressed);
    8
    let parsed_payload = parse_cwt_payload(cwt_payload);
    9
    10
    }
    11
    12
    fn parse_cwt_payload(data: Vec) -> Value {
    13
    from_reader(data.as_slice()).unwrap()
    14
    }
    15
    16
    // ...
    17
    loige 60

    View full-size slide

  208. use ciborium::{de::from_reader, value::Value};
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Value {
    from_reader(data.as_slice()).unwrap()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    from_reader(data.as_slice()).unwrap()
    use ciborium::{de::from_reader, value::Value};
    1
    2
    fn main() {
    3
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    4
    let no_prefix = remove_prefix(cert_data);
    5
    let decoded = decode_base45(no_prefix);
    6
    let decompressed = decompress(decoded);
    7
    let cwt_payload = get_cwt_payload(decompressed);
    8
    let parsed_payload = parse_cwt_payload(cwt_payload);
    9
    println!("{:#?}", parsed_payload);
    10
    }
    11
    12
    fn parse_cwt_payload(data: Vec) -> Value {
    13
    14
    }
    15
    16
    // ...
    17
    println!("{:#?}", parsed_payload);
    use ciborium::{de::from_reader, value::Value};
    1
    2
    fn main() {
    3
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    4
    let no_prefix = remove_prefix(cert_data);
    5
    let decoded = decode_base45(no_prefix);
    6
    let decompressed = decompress(decoded);
    7
    let cwt_payload = get_cwt_payload(decompressed);
    8
    let parsed_payload = parse_cwt_payload(cwt_payload);
    9
    10
    }
    11
    12
    fn parse_cwt_payload(data: Vec) -> Value {
    13
    from_reader(data.as_slice()).unwrap()
    14
    }
    15
    16
    // ...
    17
    use ciborium::{de::from_reader, value::Value};
    fn main() {
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    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) -> Value {
    from_reader(data.as_slice()).unwrap()
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    loige 60

    View full-size slide

  209. Ok, let's make it more readable...
    😅
    loige 62

    View full-size slide

  210. Ok, let's make it more readable...
    😅
    loige
    cargo add serde_json
    62

    View full-size slide

  211. 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
    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());
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    loige 63

    View full-size slide

  212. 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
    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());
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    use serde_json::to_string_pretty;
    use ciborium::{de::from_reader, value::Value};
    1
    2
    3
    fn main() {
    4
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    5
    let no_prefix = remove_prefix(cert_data);
    6
    let decoded = decode_base45(no_prefix);
    7
    let decompressed = decompress(decoded);
    8
    let cwt_payload = get_cwt_payload(decompressed);
    9
    let parsed_payload = parse_cwt_payload(cwt_payload);
    10
    println!("{}", to_string_pretty(&parsed_payload).unwrap());
    11
    }
    12
    13
    // ...
    14
    loige 63

    View full-size slide

  213. 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
    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());
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    use serde_json::to_string_pretty;
    use ciborium::{de::from_reader, value::Value};
    1
    2
    3
    fn main() {
    4
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    5
    let no_prefix = remove_prefix(cert_data);
    6
    let decoded = decode_base45(no_prefix);
    7
    let decompressed = decompress(decoded);
    8
    let cwt_payload = get_cwt_payload(decompressed);
    9
    let parsed_payload = parse_cwt_payload(cwt_payload);
    10
    println!("{}", to_string_pretty(&parsed_payload).unwrap());
    11
    }
    12
    13
    // ...
    14
    println!("{}", to_string_pretty(&parsed_payload).unwrap());
    use ciborium::{de::from_reader, value::Value};
    1
    use serde_json::to_string_pretty;
    2
    3
    fn main() {
    4
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    5
    let no_prefix = remove_prefix(cert_data);
    6
    let decoded = decode_base45(no_prefix);
    7
    let decompressed = decompress(decoded);
    8
    let cwt_payload = get_cwt_payload(decompressed);
    9
    let parsed_payload = parse_cwt_payload(cwt_payload);
    10
    11
    }
    12
    13
    // ...
    14
    loige 63

    View full-size slide

  214. 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
    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());
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    use serde_json::to_string_pretty;
    use ciborium::{de::from_reader, value::Value};
    1
    2
    3
    fn main() {
    4
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    5
    let no_prefix = remove_prefix(cert_data);
    6
    let decoded = decode_base45(no_prefix);
    7
    let decompressed = decompress(decoded);
    8
    let cwt_payload = get_cwt_payload(decompressed);
    9
    let parsed_payload = parse_cwt_payload(cwt_payload);
    10
    println!("{}", to_string_pretty(&parsed_payload).unwrap());
    11
    }
    12
    13
    // ...
    14
    println!("{}", to_string_pretty(&parsed_payload).unwrap());
    use ciborium::{de::from_reader, value::Value};
    1
    use serde_json::to_string_pretty;
    2
    3
    fn main() {
    4
    let cert_data = "HC1:NCFOXN%TSMAHN-H9QCGDSB5QPN9OO3:D4$X4-365KN-TMLV4
    5
    let no_prefix = remove_prefix(cert_data);
    6
    let decoded = decode_base45(no_prefix);
    7
    let decompressed = decompress(decoded);
    8
    let cwt_payload = get_cwt_payload(decompressed);
    9
    let parsed_payload = parse_cwt_payload(cwt_payload);
    10
    11
    }
    12
    13
    // ...
    14
    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
    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());
    }
    // ...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    loige 63

    View full-size slide

  215. All the code:
    loige.link/green-code
    loige 65

    View full-size slide

  216. A better (& more complete) implementation
    as a Rust library
    loige
    github.com/rust-italia/dgc
    66

    View full-size slide

  217. Exercise for the viewer:
    Try to validate the signature
    loige 67

    View full-size slide

  218. Exercise for the viewer:
    Try to validate the signature
    loige
    🔑 You can get the Public Key from the certificate
    here: loige.link/green-examples
    67

    View full-size slide

  219. Exercise for the viewer:
    Try to validate the signature
    loige
    🔑 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
    67

    View full-size slide

  220. Exercise for the viewer:
    Try to validate the signature
    loige
    🔑 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 for crypto!
    ring
    67

    View full-size slide

  221. Exercise for the viewer:
    Try to validate the signature
    loige
    🔑 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 for crypto!
    ring
    (Spoiler: I implemented some of this stuff in my library!)
    67

    View full-size slide

  222. Is all this stuff legal?
    😰
    loige 68

    View full-size slide

  223. Is all this stuff legal?
    😰
    loige
    👀 You can certainly look into your certificate (and the
    test certificates!)
    68

    View full-size slide

  224. Is all this stuff legal?
    😰
    loige
    👀 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)
    68

    View full-size slide

  225. Is all this stuff legal?
    😰
    loige
    👀 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!)
    68

    View full-size slide

  226. Cover Picture by on
    ❤ Huge thanks to for some precius review sessions and many pull requests!
    ❤ Thanks to , , , , for reviews and suggestions.
    FPVmat A Unsplash
    rust-italia
    @gbinside @88_eugen @AlleviTommaso @npmccallum @pelger
    loige
    ☝ nodejsdp.link
    loige.link/green
    THANK YOU!

    69

    View full-size slide