-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdcc-generator.go
138 lines (112 loc) · 3.03 KB
/
dcc-generator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package dcc
import (
"bytes"
"compress/zlib"
"crypto"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"github.com/dasio/base45"
"github.com/fxamacker/cbor/v2"
"github.com/skip2/go-qrcode"
"github.com/veraison/go-cose"
)
// Generates Vaccine Passport with json data read from 'dataPath' and writes the Vaccine Pass as a QR code file at 'outputPath'
func GenerateQR(key crypto.Signer, kid, dataPath, outputPath string) error {
// Generate Vaccine Pass using given key and data
raw, err := Generate(key, kid, dataPath)
if err != nil {
return err
}
// Write raw Vaccine Pass to QR code file
err = qrcode.WriteFile(raw, qrcode.Medium, 256, outputPath)
if err != nil {
return err
}
return nil
}
// Generates Vaccine Passport with json data read from 'dataPath' and returns raw Vaccine Pass string as `HC1:...`
func Generate(key crypto.Signer, kid, dataPath string) (dccBase45 string, err error) {
conf, err := readRaw(dataPath)
if err != nil {
return
}
// Generate JSON eu-dcc structure
payload := generateDCCStruct(conf.Name, conf.Surname, conf.Dob, conf.IssuerCountry, conf.Issuer, conf.VaccinationDate, conf.Doses)
// JSON struct to CBOR
dccCBORBytes, err := cbor.Marshal(payload)
if err != nil {
return
}
// Sign CBOR with COSE
dccCOSESignMsg, err := coseSign(dccCBORBytes, key, kid)
if err != nil {
return
}
dccCOSE, err := dccCOSESignMsg.MarshalCBOR()
if err != nil {
return
}
// Compress Binary COSE Data with zlib
dccCOSEcompressed := zlibCompress(dccCOSE)
// Encode zlib compressed cose to base45
dccBase45 = base45.EncodeToString(dccCOSEcompressed)
// Prepend magic HC1 (Health Certificate Version 1)
dccBase45 = fmt.Sprintf("%s%s", dccPrefix, dccBase45)
return
}
// coseSign Signs the given CBOR payload with the given signer key
func coseSign(payload []byte, key crypto.Signer, kid string) (*cose.Sign1Message, error) {
// create a signer with a new private key
signer, err := cose.NewSigner(cose.AlgorithmES256, key)
if err != nil {
return nil, err
}
kidBytes, err := base64.StdEncoding.DecodeString(kid)
if err != nil {
return nil, err
}
msg := cose.NewSign1Message()
msg.Headers.Protected[cose.HeaderLabelKeyID] = kidBytes // KID is first 8 bytes of Signer Certificate
msg.Payload = payload
err = msg.Sign(rand.Reader, nil, signer)
if err != nil {
return nil, err
}
return msg, nil
}
func zlibCompress(data []byte) []byte {
var b bytes.Buffer
w := zlib.NewWriter(&b)
w.Write(data)
w.Close()
return b.Bytes()
}
func zlibDecompress(data []byte) ([]byte, error) {
b := bytes.NewReader(data)
z, err := zlib.NewReader(b)
if err != nil {
return nil, err
}
defer z.Close()
output, err := ioutil.ReadAll(z)
if err != nil {
return nil, err
}
return output, nil
}
// readRaw reads raw config to parse data for Payload of DCC/Greenpass
func readRaw(path string) (*Config, error) {
bytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
conf := &Config{}
err = json.Unmarshal(bytes, conf)
if err != nil {
return nil, err
}
return conf, nil
}