Skip to content

Commit

Permalink
Add 2D-DOC Support
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexPresso authored Aug 24, 2021
2 parents 208156c + 62831b6 commit 79e9913
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 136 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Golang implementation of the covid certificates. At the moment it only includes

## Features
- Decode signed DCC (European QRCodes) data ✅
- Decode 2D-DOC data ❌ (planned)
- Decode 2D-DOC data
- Pretty-print decoded data as JSON ✅
- Download public-keys from european gateway ❌ (planned)
- Verify signature ❌ (planned)
Expand All @@ -15,10 +15,10 @@ Golang implementation of the covid certificates. At the moment it only includes
## Usage
`gocovidcertificate <flags>`

| Flag | Type | Description | Required | Default value |
| ------ | ------ | ------------------------ | -------- | ------------- |
| -code | string | QRCode string to decode | true | none |
| -print | bool | Prints the QRCode data to console | false | true |
| Flag | Type | Description | Required | Default value |
| ------ | ------ | --------------------------------------------------------- | -------- | ------------- |
| -code | string | QRCode data to decode (put it between double-quotes `""`) | true | none |
| -print | bool | Prints the QRCode data to console | false | true |

Example:
`gocovidcertificate -code "HC1:..." -print`
Expand Down
29 changes: 0 additions & 29 deletions dcc/dcc.go

This file was deleted.

86 changes: 86 additions & 0 deletions decoders/2ddoc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package decoders

import (
"encoding/json"
"github.com/alexpresso/gocovidcertificate/types"
"github.com/alexpresso/gocovidcertificate/utils"
"strconv"
"strings"
)

var TwoDPrefix = "DC"

func twoDDocDecode(input string) (certificate *types.Certificate, err error) {
header, remaining, err := decodeHeader(input)
if err != nil {
return nil, err
}

message, signature, err := decodeData(remaining)
if err != nil {
return nil, err
}

return &types.Certificate{
Type: types.TWODDOC,
Data: &types.TwoDDoc{
Header: header,
Message: message,
Signature: signature,
},
}, nil
}

func decodeHeader(input string) (header *types.TwoDDocHeader, remaining string, err error) {
length := 22
version, err := strconv.Atoi(utils.Substring(input, 2, 4))
if err != nil {
return nil, "", err
}

header = &types.TwoDDocHeader{
IDFlag: utils.Substring(input, 0, 2),
Version: version,
Issuer: utils.Substring(input, 4, 8),
CertID: utils.Substring(input, 8, 12),
DocumentDate: utils.Substring(input, 12, 16),
SignatureDate: utils.Substring(input, 16, 20),
DocumentTypeID: utils.Substring(input, 20, 22),
}

switch version {
case 3:
header.PerimeterID = utils.Substring(input, 22, 24)
length = 24
case 4:
header.PerimeterID = utils.Substring(input, 22, 24)
header.CountryID = utils.Substring(input, 24, 26)
length = 26
}

return header, utils.Substring(input, length, len(input)), err
}

func decodeData(input string) (message *types.TwoDDocMessage, signature string, err error) {
data := make(map[string]interface{})

units := strings.Split(input, string(rune(31)))
groups := strings.Split(units[0], string(rune(29)))
signature = units[1]

for _, group := range groups {
key, value := decodeField(group)
data[key] = value
}

jsonString, err := json.Marshal(data)
err = json.Unmarshal(jsonString, &message)

return
}

func decodeField(input string) (key string, value string) {
key = utils.Substring(input, 0, 2)
value = utils.Substring(input, 2, len(input))
return
}
40 changes: 40 additions & 0 deletions decoders/dcc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package decoders

import (
"github.com/alexpresso/gocovidcertificate/types"
"github.com/alexpresso/gocovidcertificate/utils"
"strings"
)

var DCCPrefixes = map[string]bool{"HC1": true, "LT1": true}

func dccDecode(input string) (*types.Certificate, error) {
var err error
var bytes []byte

for prefix := range DCCPrefixes {
input = strings.TrimPrefix(input, prefix+":")
}

bytes, err = utils.Base45decode(input)
if err != nil {
return nil, err
}

if bytes[0] == 0x78 {
bytes, err = utils.ZlibDecompress(bytes)
if err != nil {
return nil, err
}
}

cose, err := utils.DecodeCOSE(bytes)
if err != nil {
return nil, err
}

return &types.Certificate{
Type: types.DCC,
Data: cose,
}, nil
}
17 changes: 17 additions & 0 deletions decoders/decoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package decoders

import (
"errors"
"github.com/alexpresso/gocovidcertificate/types"
"strings"
)

func Decode(input string) (*types.Certificate, error) {
if split := strings.Split(input, ":")[0]; DCCPrefixes[split] {
return dccDecode(input)
} else if strings.HasPrefix(input, TwoDPrefix) {
return twoDDocDecode(input)
}

return nil, errors.New("unsupported code format")
}
11 changes: 5 additions & 6 deletions gocovidcertificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import (
"encoding/json"
"flag"
"fmt"
"github.com/alexpresso/gocovidcertificate/dcc"
"github.com/alexpresso/gocovidcertificate/decoders"
"log"
)


func main() {
var (
err error
err error
mustPrint = flag.Bool("print", true, "Prints content of the decoded certificate")
code = flag.String("code", "", "The DCC code you want to process")
code = flag.String("code", "", "The DCC code you want to process")
)

flag.Parse()
Expand All @@ -22,13 +21,13 @@ func main() {
log.Fatal("Missing -code flag")
}

cose, err := dcc.Decode(*code)
data, err := decoders.Decode(*code)
if err != nil {
log.Fatal(err)
}

if *mustPrint {
indent, err := json.MarshalIndent(cose, "", " ")
indent, err := json.MarshalIndent(data, "", " ")
if err != nil {
log.Fatal(err)
}
Expand Down
32 changes: 32 additions & 0 deletions types/2dcertificate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package types

type TwoDDoc struct {
Header *TwoDDocHeader
Message *TwoDDocMessage
Signature string
}

type TwoDDocHeader struct {
IDFlag string
Version int
Issuer string
CertID string
DocumentDate string
SignatureDate string
DocumentTypeID string
PerimeterID string
CountryID string
}

type TwoDDocMessage struct {
Lastname string `json:"L0"`
Firstname string `json:"L1"`
DateOfBirth string `json:"L2"`
TargetedAgent string `json:"L3"`
VaccineATC string `json:"L4"`
Dose1Manufacturer string `json:"L5"`
Dose2Manufacturer string `json:"L6"`
Dose int `json:"L7,string"`
RequiredDoses int `json:"L8,string"`
Date int `json:"L9,string"`
}
60 changes: 8 additions & 52 deletions types/certificate.go
Original file line number Diff line number Diff line change
@@ -1,57 +1,13 @@
package types

// DccRoot see https://github.com/ehn-dcc-development/ehn-dcc-schema
type DccRoot struct {
DCC covidCertificate `cbor:"1,keyasint"`
}

type covidCertificate struct {
Version string `cbor:"ver" json:"version"`
DateOfBirth string `cbor:"dob" json:"dateOfBirth"`
Name name `cbor:"nam" json:"name"`
Vaccines []vaccineEntry `cbor:"v" json:"vaccines"`
Tests []testEntry `cbor:"t" json:"tests"`
Recoveries []recoveryEntry `cbor:"r" json:"recoveries"`
}

type name struct {
Surname string `cbor:"fn" json:"surname"`
StdSurname string `cbor:"fnt" json:"stdSurname"`
Forename string `cbor:"gn" json:"forename"`
StdForename string `cbor:"gnt" json:"stdForename"`
}

type vaccineEntry struct {
TargetedAgent string `cbor:"tg" json:"targetedAgent"`
Vaccine string `cbor:"vp" json:"vaccine"`
Product string `cbor:"mp" json:"product"`
Manufacturer string `cbor:"ma" json:"manufacturer"`
Dose int64 `cbor:"dn" json:"doseNumber"`
DoseSeries int64 `cbor:"sd" json:"doseSeries"`
Date string `cbor:"dt" json:"date"`
Country string `cbor:"co" json:"country"`
Issuer string `cbor:"is" json:"issuer"`
CertificateID string `cbor:"ci" json:"certificateID"`
}
const (
DCC CertificateType = "DCC"
TWODDOC CertificateType = "2DDOC"
)

type testEntry struct {
TargetedAgent string `cbor:"tg" json:"targetedAgent"`
TestType string `cbor:"tt" json:"testType"`
Name string `cbor:"nm" json:"name"`
Manufacturer string `cbor:"ma" json:"manufacturer"`
SampleDatetime string `cbor:"sc" json:"sampleDatetime"`
TestResult string `cbor:"tr" json:"testResult"`
TestingCentre string `cbor:"tc" json:"testingCentre"`
Country string `cbor:"co" json:"country"`
Issuer string `cbor:"is" json:"issuer"`
CertificateID string `cbor:"ci" json:"certificateID"`
}
type CertificateType string

type recoveryEntry struct {
TargetedAgent string `cbor:"tg" json:"targetedAgent"`
Country string `cbor:"co" json:"country"`
Issuer string `cbor:"is" json:"issuer"`
ValidFrom string `cbor:"df" json:"validFrom"`
ValidUntil string `cbor:"du" json:"validUntil"`
CertificateID string `cbor:"ci" json:"certificateID"`
type Certificate struct {
Type CertificateType
Data interface{}
}
40 changes: 20 additions & 20 deletions types/cose.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,35 @@ package types

// Signed see https://datatracker.ietf.org/doc/html/rfc8152#section-2
type Signed struct {
_ struct{} `cbor:",toarray"`
Protected []byte
Unprotected map[interface{}]interface{}
Content []byte
Signature []byte
_ struct{} `cbor:",toarray"`
Protected []byte
Unprotected map[interface{}]interface{}
Content []byte
Signature []byte
}

// Header see https://datatracker.ietf.org/doc/html/rfc8152#section-3.1 & https://www.iana.org/assignments/cose/cose.xhtml
type Header struct {
Alg int `cbor:"1,keyasint,omitempty"`
Kid []byte `cbor:"4,keyasint,omitempty"`
IV []byte `cbor:"5,keyasint,omitempty"`
Alg int `cbor:"1,keyasint,omitempty"`
Kid []byte `cbor:"4,keyasint,omitempty"`
IV []byte `cbor:"5,keyasint,omitempty"`
}

// Claims see https://datatracker.ietf.org/doc/html/draft-ietf-ace-cbor-web-token-08#section-3.1
type Claims struct {
Iss string `cbor:"1,keyasint"`
Sub string `cbor:"2,keyasint"`
Aud string `cbor:"3,keyasint"`
Exp int64 `cbor:"4,keyasint"`
Nbf int `cbor:"5,keyasint"`
Iat int64 `cbor:"6,keyasint"`
Cti []byte `cbor:"7,keyasint"`
HCData DccRoot `cbor:"-260,keyasint"`
LCData DccRoot `cbor:"-250,keyasint"`
Iss string `cbor:"1,keyasint"`
Sub string `cbor:"2,keyasint"`
Aud string `cbor:"3,keyasint"`
Exp int64 `cbor:"4,keyasint"`
Nbf int `cbor:"5,keyasint"`
Iat int64 `cbor:"6,keyasint"`
Cti []byte `cbor:"7,keyasint"`
HCData DccRoot `cbor:"-260,keyasint"`
LCData DccRoot `cbor:"-250,keyasint"`
}

type COSE struct {
Signed Signed `json:"-"`
Header Header
Claims Claims
Signed Signed `json:"-"`
Header Header
Claims Claims
}
Loading

0 comments on commit 79e9913

Please sign in to comment.