Getting your digital Covid-19 🦠 Vaccination Certificate into your Apple Wallet and use it with your Apple Watch ⌚️
I've always been sort of reluctant of installing or using auxiliary apps in order to apply to this one special use case. Wouldn't it be better to use the stuff already there and giving far more 'ins' and 'outs' from a process perspective?
This is especially true if you already own a digital wallet like Apple Wallet or whatever the competitors like Google and such offer which you already use for Coupons, Shopping Cards and Credit Cards.
As soon as I got the Covid Vaccination Certificate paper (I'm living in Germany thus one gets an official document with a QR Code applied to it) I was keen to see what's in this code and what the vaccination certificate fuss is all about.
Turned out all is pretty simple and straightforward:
The barcode data is a JSON document put in a CBOR binary document, packed in a COSE signed document, zLib compressed and Base45 (no typo!) encoded.
[Source: https://github.com/ehn-dcc-development/hcert-spec]
The actual data is just a JSON payload with some useful information like your name, DOB, vaccination details and such. You can learn all about it here eHealth Network - Technical Specifications for EU Digital COVID Certificates. I also recommend reading the post by Harrison Sand linked at the end of this post which is giving you more details.
Wiring up a quick Jupyter Notebook shouldn't be that hard.
Reading QR Code
Let's import all the libraries needed first:
import base45
import zlib
import cbor2
import json
from cose.messages import CoseMessage
Now let's see what's in there. Do not mess around with getting the barcode data. Load an arbitrary universal barcode reader app. It will show the contents of the code. It usually starts with HC1 if you got the code in the European Union. Other countries might have another prefix. Just copy and paste it to the notebook in order to have a sample data set to play around with.
qr_data = "HC1:6BFY7...."
b45_data = qr_data[4:]
zlib_data = base45.b45decode(b45_data)
cose = zlib.decompress(zlib_data)
decoded_cose = CoseMessage.decode(cose)
payload = cbor2.loads(decoded_cose.payload)
Payload now holds the actual JSON document. Pretty print it!
print(json.dumps(payload, indent=4, sort_keys=True))
Excellent!
Re-generate the QR Code
At this point, let's walk all the way back to the QR Code itself by generating one from your initial qr_data:
import qrcode
import matplotlib.pyplot as plt
from PIL import Image
img = qrcode.make(qr_data)
imgplot = plt.imshow(img, cmap="gray")
plt.show()
But wait: if you now try to validate this QR Code with one of the Covid-19 Vaccination Certificate Validation apps (like CovPass Check) it most likely won't be able to verify the newly generated one. Turned out this is just an encoding issue:
Forcing the barcode data into a latin-1 encoding should do the trick:
qr_data.encode(encoding='latin-1',errors='strict')
Repeat the generation process above.
Et voilà!
From Vaccination Certificate to Apple Wallet
Now wouldn't it be pretty cool to just roll up jour sleeve and presenting your vaccination certificate using your Apple Watch (at least in case you own one)?
Thought so 🙄
There is a nice little Python Library called wallet-py3k which comes in handy for this.
from wallet.models import Pass, Barcode, Generic, BarcodeFormat
I do encourage you to use pyenv or Conda as this will give a nice self contained environment for development. I had some issues getting swig and m2crypto working on Mac OS X Big Sur so do not expect this be a walk in the park.
You will need to set up a system for automatically signing and compressing passes. You'll need an Apple Developer Account in order to set this up properly. See the Useful Links section at the end of this post. There are a couple of parameters needed in order to create the Wallet Pass file. You'll get what needs to be provided during the creation of your Apple Pass Identifier.
pass_type_identifier = "pass.de.xyz.c19"
organization_name = "XYZ"
team_identifier = "WAB1X34T123J" # Your Apple team ID
cert_pem = "certificate.pem"
key_pem = "key.pem"
wwdr_pem = "AppleWWDRCA.pem"
key_pem_password = "the key.pem password"
There is some limitation to the look and feel of a Wallet Pass as you can read here Wallet Developers Guide - Pass Design and Creation.
We'll go for a generic pass with some basic information and a nice little logo. At this point this is just a proof of concept, so I omit the variable extraction from the JSON document. That should be a no brainer.
cardInfo = Generic()
cardInfo.addHeaderField('dovh',dov,'Date Of Vaccination')
cardInfo.addPrimaryField('name', firstname+ " "+lastname, 'Name')
cardInfo.addSecondaryField('dob',dob,'Date Of Birth')
cardInfo.addSecondaryField('dov', dov, 'Date Of Vaccination')
cardInfo.addAuxiliaryField('issuer', issuer+" ("+country+")", 'Certificate Issuer')
cardInfo.addAuxiliaryField('mp',mp,'Product')
cardInfo.addAuxiliaryField('dnr', str(dnr)+"/"+str(overalldoses), 'Dose Number')
cardInfo.addBackField('ci', ci, 'Unique Certificate Identifier')
cardInfo.addBackField('tg', tg, 'Disease/Agent targeted')
cardInfo.addBackField('vp', vp, 'Vaccine/Prophylaxis')
With all the 'visuals' applied let's create the Pass itself:
walletpass = Pass(cardInfo,
passTypeIdentifier=pass_type_identifier,
organizationName=organization_name,
teamIdentifier=team_identifier)
Apple wants to make sure a Wallet Pass doesn't look to potato-ish, so apply some further attributes to this pass as well as an icon and a nice background:
walletpass.serialNumber = ci
walletpass.description = "EU VACCINATION CERTIFICATE"
walletpass.logoText = "COVID-19"
walletpass.foregroundColor = "rgb(65,71,100)"
walletpass.addFile("icon.png", open("eu.png", "rb"))
walletpass.addFile("icon@2x.png", open("eu.png", "rb"))
walletpass.addFile("logo.png", open("eu.png", "rb"))
walletpass.addFile("logo@2x.png", open("eu.png", "rb"))
And most important generate a barcode which displays on the pass itself as well as fullscreen on the Apple Watch later on:
qr_data.encode(encoding='latin-1',errors='strict')
walletpass.barcode = Barcode(message = qr_data, format=BarcodeFormat.QR)
Finally create the Pass file (.pkpass) and store it:
_ = walletpass.create(cert_pem,
key_pem,
wwdr_pem,
key_pem_password, lastname.upper()+"_"+firstname.upper()+"_"+dob+"_"+ci[-4:]+
".pkpass")
The resulting .pkass file can be added to your wallet by double clicking it, emailing it, etc.
Lights, Camera, Action
Once you got this up and running this could be tweaked into Flask in order to make it a self service web client:
Enable camera 🤳, take a picture 📸 of the QR Code, stream the .pkpass file 🎫 to the user 👝.
Useful Links
As usual there is a whole lot of browser tabs logging the journey throughout your whole research. These are the ones I found most useful circumventing one or another obstacle:
https://gist.github.com/andrisasuke/91eccace11366e626d14c5c249054e20
https://fluffy.es/keychain-cant-export-to-p12/
https://gist.github.com/jcward/d08b33fc3e6c5f90c18437956e5ccc35
https://harrisonsand.com/posts/covid-certificates/
https://github.com/corona-warn-app
https://betterprogramming.pub/how-to-generate-and-decode-qr-codes-in-python-a933bce56fd0
* Header picture courtesy of European Union, 2021