Skip to content

Instantly share code, notes, and snippets.

@heatxsink
Last active March 9, 2024 18:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save heatxsink/db53ddeced344b4bde32172551a96e87 to your computer and use it in GitHub Desktop.
Save heatxsink/db53ddeced344b4bde32172551a96e87 to your computer and use it in GitHub Desktop.
SMART Health Card (COVID19 Vaccine) QR Code processing / decoding in golang
// Download your SMART Health Card QR code image somewhere on your filesystem.
// I'm in California, so I went here: https://myvaccinerecord.cdph.ca.gov/
//
// Some links I found useful when hacking this up ...
// - https://www.reddit.com/r/Quebec/comments/ndz2uz/how_the_covid_vaccination_qr_code_works_and_what/
// - https://github.com/dvci/health-cards-walkthrough/blob/main/SMART%20Health%20Cards.ipynb
// - https://smarthealth.cards
// - https://github.com/fproulx/shc-covid19-decoder
// - https://github.com/smart-on-fhir/health-cards/blob/main/docs/index.md#every-health-card-can-be-embedded-in-a-qr-code
// - https://github.com/smart-on-fhir/health-cards/blob/main/docs/index.md#encoding-chunks-as-qr-codes
package main
import (
"bytes"
"compress/flate"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"image"
_ "image/jpeg"
_ "image/png"
"io"
"io/ioutil"
"os"
"strconv"
"strings"
"github.com/makiuchi-d/gozxing"
"github.com/makiuchi-d/gozxing/qrcode"
"gopkg.in/square/go-jose.v2"
)
var (
filenameOption string
)
func init() {
flag.StringVar(&filenameOption, "f", "", "path to QR code filename")
flag.Usage = usage
flag.Parse()
}
func usage() {
flag.PrintDefaults()
}
// More info on SMART Health Cards over here -> https://smarthealth.cards
type HealthCard struct {
Iss string `json:"iss,omitempty"`
Nbf float64 `json:"nbf,omitempty"`
Vc struct {
CredentialSubject struct {
FhirBundle struct {
Entry []struct {
FullUrl string `json:"fullUrl,omitempty"`
Resource struct {
BirthDate string `json:"birthDate,omitempty"`
Name []struct {
Family string `json:"family,omitempty"`
Given []string `json:"given,omitempty"`
} `json:"name,omitempty"`
ResourceType string `json:"resourceType,omitempty"`
} `json:"resource,omitempty"`
} `json:"entry,omitempty"`
ResourceType string `json:"resourceType,omitempty"`
Type string `json:"type,omitempty"`
} `json:"fhirBundle,omitempty"`
FhirVersion string `json:"fhirVersion,omitempty"`
} `json:"credentialSubject,omitempty"`
Type []string `json:"type,omitempty"`
} `json:"vc,omitempty"`
}
func decodeQRCode(path string) (*gozxing.Result, error) {
d, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
img, _, err := image.Decode(bytes.NewReader(d))
if err != nil {
return nil, err
}
bmp, err := gozxing.NewBinaryBitmapFromImage(img)
if err != nil {
return nil, err
}
qrReader := qrcode.NewQRCodeReader()
return qrReader.Decode(bmp, nil)
}
// This helped me understand how to decodeJWS ...
// https://github.com/fproulx/shc-covid19-decoder/blob/main/src/shc.js
func decodeJWS(text string) ([]byte, error) {
n := strings.TrimPrefix(text, "shc:/")
var rr []rune
for i := 0; i < len(n); i = i + 2 {
nn, err := strconv.Atoi(fmt.Sprintf("%c%c", n[i], n[i+1]))
if err != nil {
return nil, err
}
rr = append(rr, rune(nn+45))
}
j, err := jose.ParseSigned(string(rr))
if err != nil {
return nil, err
}
x, err := j.CompactSerialize()
if err != nil {
return nil, err
}
tokens := strings.Split(x, ".")
payload, err := base64.RawURLEncoding.DecodeString(tokens[1])
if err != nil {
return nil, err
}
b := bytes.NewReader(payload)
f := flate.NewReader(b)
bb := new(bytes.Buffer)
_, err = io.Copy(bb, f)
if err != nil {
return nil, err
}
f.Close()
return bb.Bytes(), nil
}
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
func main() {
if filenameOption == "" {
fmt.Println("Filename is required.")
flag.Usage()
os.Exit(1)
}
if !fileExists(filenameOption) {
fmt.Printf("The filename '%s' does not exist.\n", filenameOption)
flag.Usage()
os.Exit(1)
}
r, err := decodeQRCode(filenameOption)
if err != nil {
fmt.Printf("%#v", err)
}
b, err := decodeJWS(r.GetText())
if err != nil {
fmt.Printf("%#v", err)
}
fmt.Println(string(b))
var hc HealthCard
err = json.Unmarshal(b, &hc)
if err != nil {
fmt.Printf("%#v", err)
}
fmt.Println(hc.Iss, hc.Nbf, hc.Vc.Type)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment