{"id":507,"date":"2021-07-11T13:20:53","date_gmt":"2021-07-11T11:20:53","guid":{"rendered":"https:\/\/lendl.priv.at\/blog\/?p=507"},"modified":"2026-01-26T12:12:31","modified_gmt":"2026-01-26T11:12:31","slug":"parsing-the-eu-digital-covid-certificate","status":"publish","type":"post","link":"https:\/\/lendl.priv.at\/blog\/2021\/07\/11\/parsing-the-eu-digital-covid-certificate\/","title":{"rendered":"Parsing the EU Digital COVID Certificate"},"content":{"rendered":"\n<p>I recently go my first Covid-19 shot, and shortly after that I received my QR code that is supposed to prove to anybody that I&#8217;m vaccinated.<\/p>\n\n\n\n<p>Of course, I was interested in the actual technical implementation of the thing so I started tinkering around. Initially, I wasn&#8217;t sure whether the QR code just links to a webpage or is  a standalone document, but reading the <a href=\"https:\/\/ec.europa.eu\/info\/live-work-travel-eu\/coronavirus-response\/safe-covid-19-vaccines-europeans\/eu-digital-covid-certificate_en\">documentation<\/a> quickly made clear that it simply contains a signed document.<\/p>\n\n\n\n<p>There is a python snippet floating around that does all the decoding (decode base45, decompress, decode cbor, ignore signature, cbor decode), but I was wondering whether I can just use standard Linux command-line tools to do the same.<\/p>\n\n\n\n<p>To my big surprise, I could not find a simple <a href=\"https:\/\/datatracker.ietf.org\/doc\/draft-faltstrom-base45\/\">base45<\/a> decoder to be used on the command line. Neither the <a href=\"https:\/\/www.gnu.org\/software\/coreutils\/\">GNU coreutils<\/a> (which supports a <a href=\"https:\/\/www.gnu.org\/software\/coreutils\/manual\/html_node\/basenc-invocation.html#basenc-invocation\">bunch of encodings<\/a>) or <a href=\"http:\/\/www.quarkline.net\/basez\/\">basez<\/a> have implemented base45 yet.  For a quick coding exercise, getting code into the coreutils was a bit too ambitious, so I <a href=\"https:\/\/github.com\/otmarlendl\/base45\">rolled my own<\/a> in a few minutes. I haven&#8217;t done coding in C for quite some time, so this was fun. (while probably not necessary, this should be doable as a one-liner in Perl)<\/p>\n\n\n\n<p>Next step: The libz decompression. Here, too, I was surprised that my Debian box did not have a simple program ready to do it. After some googling, I found that <br><b>zlib-flate -uncompress<\/b><br>does the trick. zlib-flate is included in the qpdf package. <\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p><strong>Update: <\/strong>The debian package zlib1g-dev contains a few example programs, zpipe.c does exactly what I need. Just copy the file from \/usr\/share\/doc\/zlib1g-dev\/examples\/ , compile with &#8220;cc -o zpipe zpipe.c -lz&#8221;.<\/p><\/blockquote>\n\n\n\n<p>Then <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc8949\">cbor<\/a>: at first this looked easy, there is a libcbor0 in Debian and appropriate modules for Perl and Python. But a command-line tool? That&#8217;s not included by default.<\/p>\n\n\n\n<p>Initially, I tried using <br><strong>python3 -m cbor2.tool<\/strong><br>and then continue with jq to select the right element (<strong>jq  &#8216;.&#8221;CBORTag:18&#8243;[2]&#8217; <\/strong>seems to do the trick) but I ran into encoding issues: I did not manage to extract the element in binary form, I always had escape sequences like &#8216;\\x04&#8217; or &#8216;\\u0012&#8217; in there which are not suitable for next step of cbor decoding.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p><strong>Update:<\/strong> I&#8217;ve now read a bit more about the cbor standard and on the things it can do that JSON can&#8217;t, is handling raw binary data. JSON knows strings, yes, but they are character strings encoded in utf-8, not sequences of octets. Back in the old days of latin1 that might not have mattered that much, but these days you really shouldn&#8217;t mix those two.<\/p><\/blockquote>\n\n\n\n<p>So this is still work in progress, watch this space for updates.<\/p>\n\n\n\n<p><strong>Updates:<\/strong> <\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>I&#8217;m not the only one who tried this, see also this <a href=\"http:\/\/www.corentindupont.info\/blog\/posts\/Programming\/2021-08-13-GreenPass.html\">blogpost by&nbsp;Corentin Dupont<\/a>.<\/li><li>Based on troubles with faked certificates, I had a closer look at the key distribtion and updates of the Austrian green pass system. I documented the results in a <a href=\"https:\/\/cert.at\/de\/blog\/2021\/10\/eu-digital-green-certificate-was-gilt-eigentlich-bei-uns\">blogpost at CERT.at<\/a>.<\/li><\/ul>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I recently go my first Covid-19 shot, and shortly after that I received my QR code that is supposed to prove to anybody that I&#8217;m vaccinated. Of course, I was interested in the actual technical implementation of the thing so I started tinkering around. Initially, I wasn&#8217;t sure whether the QR code just links to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11],"tags":[],"class_list":["post-507","post","type-post","status-publish","format-standard","hentry","category-system-administration"],"_links":{"self":[{"href":"https:\/\/lendl.priv.at\/blog\/wp-json\/wp\/v2\/posts\/507","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lendl.priv.at\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/lendl.priv.at\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/lendl.priv.at\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/lendl.priv.at\/blog\/wp-json\/wp\/v2\/comments?post=507"}],"version-history":[{"count":1,"href":"https:\/\/lendl.priv.at\/blog\/wp-json\/wp\/v2\/posts\/507\/revisions"}],"predecessor-version":[{"id":841,"href":"https:\/\/lendl.priv.at\/blog\/wp-json\/wp\/v2\/posts\/507\/revisions\/841"}],"wp:attachment":[{"href":"https:\/\/lendl.priv.at\/blog\/wp-json\/wp\/v2\/media?parent=507"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/lendl.priv.at\/blog\/wp-json\/wp\/v2\/categories?post=507"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lendl.priv.at\/blog\/wp-json\/wp\/v2\/tags?post=507"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}