Yesterday, California released their Digital Vaccine Record system for securely verifying residents' COVID-19 vaccination status. I took a look at it and thought I'd write up my findings here. At a high level, the DVR consists of a QR code which contains a cryptographically-signed assertion in JSON Web Token (JWT) format. I'll walk you through how to get one, how to decode it, and what it contains in the rest of this article.
Getting one of the tokens is pretty easy; you just go to the Digital Vaccine Record website and put in your name, date of birth, and the phone number or email address you gave when you got your vaccine. I got mine through CVS, and, for whatever reason, they only provided my phone number to the state, not my email address. The state will then send you an email or SMS containing a short-lived link to a website which will show a very large and information-dense QR Code.
You can scan this QR code with the QR code scanner of your choice and you should get back some text1 like the following:
shc:/56762909524320603460292437404460312229595326546034602925407728043360287028647167452228092861553055647103414321643241110839632106452403713377212638633677424237084126633945392929556404425627596537253338446120605736010645293353127074232853503870255550530362394304317059085244240537...
This very long string is a Smart Health Card token, which is the framework being used by several states. Let's decode it with a little bit of Python!
First, create a virtualenv somewhere handy with python3 -m virtualenv venv
. Next, we'll install my forked copy of pyjwt
(which adds gzip support, as required for SHC) with ./venv/bin/pip install -e 'git+https://github.com/Roguelazer/pyjwt.git@deflate#egg=pyjwt[crypto]'
2.
The following code will verify the signature on your SHC and print out the contents:
import jwt
import json
def decode(code):
if code.startswith('shc:/'):
code = code[5:]
# decode the numeric data into binary data
if len(code) % 2 != 0:
raise ValueError('code is not the right length')
d = []
for i in range(int(len(code) / 2)):
d.append(chr(int(code[i * 2] + code[(i * 2) + 1]) + 45))
d = ''.join(d)
# download the public keys from the CDPH website. Oddity: the .well-known/
# directory should be at the top-level, but it isn't!
jwks_client = jwt.PyJWKClient(
'https://myvaccinerecord.cdph.ca.gov/creds/.well-known/jwks.json'
)
# find the matching signing key based on the header
signing_key = jwks_client.get_signing_key_from_jwt(d)
data = jwt.decode(d, signing_key.key, algorithms=['ES256', 'RS256'])
return data
code = input('SHC code: ')
print(json.dumps(decode(code)))
If it works, you should get something like the following:
{
"iss": "https://myvaccinerecord.cdph.ca.gov/creds",
"nbf": 1624036526,
"vc": {
"type": [
"https://smarthealth.cards#health-card",
"https://smarthealth.cards#immunization",
"https://smarthealth.cards#covid19"
],
"credentialSubject": {
"fhirVersion": "4.0.1",
"fhirBundle": {
"resourceType": "Bundle",
"type": "collection",
"entry": [
{
"fullUrl": "resource:0",
"resource": {
"resourceType": "Patient",
"name": [
{
"family": "YOUR LAST NAME",
"given": [
"YOUR FIRST NAME"
]
}
],
"birthDate": "YOUR BIRTH DATE"
}
},
{
"fullUrl": "resource:1",
"resource": {
"resourceType": "Immunization",
"status": "completed",
"vaccineCode": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/cvx",
"code": "207"
}
]
},
"patient": {
"reference": "resource:0"
},
"occurrenceDateTime": "2021-04-19",
"performer": [
{
"actor": {
"display": "CVS CORPORATE"
}
}
],
"lotNumber": "036B21A"
}
},
{
"fullUrl": "resource:2",
"resource": {
"resourceType": "Immunization",
"status": "completed",
"vaccineCode": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/cvx",
"code": "207"
}
]
},
"patient": {
"reference": "resource:0"
},
"occurrenceDateTime": "2021-05-18",
"performer": [
{
"actor": {
"display": "CVS CORPORATE"
}
}
],
"lotNumber": " 025C21A"
}
}
]
}
}
}
}
This is pretty much what you might expect and is in line with, e.g., this very good article from the Mozilla blog. It contains the minimum amount of information required to identify someone (ideally paired with looking at their license or something), it can be easily printed or otherwise stored offline3, it's cryptographically verified using standard protocols and technologies. The only thing that worries me is that it doesn't seem to have a human-readable field to encode what kind of vaccine it was that I received4, so if it turns out that one of the vaccines is ineffective against a variant and folks with that shot need a booster in order to be considered "fully vaccinated", that will require some gymnastics. That's a pretty minor and hypothetical concern, though.
Good on California for coming up with a reasonable and efficient system5! I look forward for using it in the future to be able to go to crowded places and feel confident that I'm not putting my un-vaccinatable son at risk. Hooray!
Mine is more than 1600 digits!
This line says, basically, install the package at https://github.com/Roguelazer/pyjwt.git
from the
"deflate" branch, under the name "pyjwt", and with the optional feature "crypto" to enable cryptographically
validating tokens
Remember, kids: never give your unlocked phone to a law enforcement officer under any circumstances. You do not want to consent to a search.
I assume this is what the "vaccine.coding.system" and "vaccine.coding.code" fields are supposed to represent, but these are not human-friendly and http://hl7.org/fhir/sid/cvx is a dead link.
And, particularly, good on California for doing so without doing anything deeply stupid involving blockchains
Want to comment on this? How about we talk on Mastodon instead? Share on Mastodon