#!/bin/bash -feu ###################################################################### # shc-decode.sh - decode the Ontario SMART Health Card # proof-of-vaccination QR code shc:/5676290952... thingies # (C) 2021 "Nosey" Nick Waterman, # All wrongs righted, all rights reserved # This work is licensed under a Creative Commons # Attribution-ShareAlike 4.0 International License. usage () { echo "USAGE: $0 shc:/5676290952..." echo " or: $0 FILE" echo "decodes the JWT-ish content of an Ontario SMART Health Card" echo "shc:/5676290952... QR code" exit 1 } banner () { echo "######################################################################" echo "# $1:"; shift echo "$@" } case "${1:-}" in "") usage ;; "shc:/"[0-9]*) SHC="$1" ;; *) if [ -f "$1" ] then SHC="$(< "$1")" else usage fi ;; esac case "$SHC" in "shc:/"[0-9]*) true ;; *) echo "Expected shc:/5676290952..." echo "OR a file containing shc:/5676290952..." exit 9 ;; esac banner "SHC" "$SHC" # NOTE / + instead of _ - and many deliberately invalid ? chars in # this not-quite-ASCII table for base64 JWTs: ASCII='+./0123456789???????ABCDEFGHIJKLMNOPQRSTUVWXYZ????/?abcdefghijklmnopqrstuvwxyz' # Take Pairs of digits, convert to ASCII-ish+45 JWT-ish thing... JWT=$( for X in $(echo "${SHC:5}" | sed -e 's/[0-9][0-9]/ &/g; s/ 0/ /g'); do echo -n "${ASCII:$X:1}" done ) banner "JWT" "$JWT" # Split the JWT into HEAD, PAYLOAD, SIG IFS=. read -r HEAD PAYLD SIG <<< "$JWT" banner "B64 HEADER" "$HEAD" banner "B64 PAYLOAD" "$PAYLD" banner "B64 SIG" "$SIG" b64dec () { # Add back the trailing == and base64-decode: LEN="${#1}" case "$((LEN % 4))" in 0) echo "$1" ;; 1) echo "LEN %4 == 1 should never happen??" 2>&1; exit 9;; 2) echo "$1==" ;; 3) echo "$1=" ;; esac | base64 -d } # Decode the base64(ish) chunks: HEAD=$( b64dec "$HEAD") banner "HEADER" "$HEAD" case "$HEAD" in *'"zip":"DEF"'*) # Zip DEFlated payload: PAYLD=$({ b64dec H4sIAAAAAAAAAw # gzip DEFLATE header b64dec "$PAYLD" # can't add CRC32/Len footer - ignore "unexpected end of file" error: } | zcat 2>/dev/null || true) ;; *) # Not zipped, just regular base64: PAYLD=$(b64dec "$PAYLD") ;; esac banner "PAYLOAD" "$PAYLD" SIG=$(b64dec "$SIG" | od -tx1) # ++++++++++ verify? banner "SIG (unchecked)" "$SIG" NICE="" # unless we find... command -v json_pp >/dev/null && NICE="json_pp" command -v jq >/dev/null && NICE="jq" if [ "$NICE" ]; then banner "PRETTY HEADER" "$(echo "$HEAD" | $NICE)" banner "PRETTY PAYLOAD" "$(echo "$PAYLD" | $NICE)" fi