-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathvalidator.go
208 lines (187 loc) · 6.13 KB
/
validator.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package nzcpv
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"errors"
"fmt"
"math/big"
"time"
)
var (
defaultKeys = map[string]*ecdsa.PublicKey{
"did:web:nzcp.identity.health.nz#z12Kf7UQ": &ecdsa.PublicKey{
Curve: elliptic.P256(),
X: big.NewInt(0).SetBytes([]byte{
0x0d, 0x00, 0x8a, 0x26, 0xeb, 0x2a, 0x32, 0xc4,
0xf4, 0xbb, 0xb0, 0xa3, 0xa6, 0x68, 0x63, 0x54,
0x69, 0x07, 0x96, 0x7d, 0xc0, 0xdd, 0xf4, 0xbe,
0x6b, 0x27, 0x87, 0xe0, 0xdb, 0xb9, 0xda, 0xd7,
}),
Y: big.NewInt(0).SetBytes([]byte{
0x97, 0x18, 0x16, 0xce, 0xc2, 0xed, 0x54, 0x8f,
0x1f, 0xa9, 0x99, 0x93, 0x3c, 0xfa, 0x3d, 0x9d,
0x9f, 0xa4, 0xcc, 0x6b, 0x3b, 0xc3, 0xb5, 0xce,
0xf3, 0xea, 0xd4, 0x53, 0xaf, 0x0e, 0xc6, 0x62,
}),
},
}
defaultTrustedIssuers = map[string]struct{}{
"did:web:nzcp.identity.health.nz": struct{}{},
}
)
var (
defaultValidator = NewValidator()
)
var (
ErrBadSignature = errors.New("Bad signature")
ErrInvalidSigningAlgorithm = errors.New("Invalid signing algorithm")
ErrUntrustedIssuer = errors.New("Untrusted issuer")
ErrUnknownPublicKey = errors.New("Unknown public key")
ErrTokenNotActive = errors.New("Token not yet active")
ErrTokenExpired = errors.New("Token has expired")
ErrInvalidCTI = errors.New("Invalid CTI")
ErrInvalidClaimsContext = errors.New("Claims context is invalid")
ErrInvalidClaimsType = errors.New("Claims type is invalid")
ErrInvalidTokenVersion = errors.New("Token version is invalid")
)
// Validator is a struct that holds a list of trusted issuers and keys for
// validating tokens. The zero-value is NOT usable. Use NewValidator() instead.
type Validator struct {
keys map[string]*ecdsa.PublicKey
trustedIssuers map[string]struct{}
}
// NewValidator creates a token validator to which non-trusted issuers and
// public keys can be added. This is intended for testing purposes only. To
// ensure compliance to the specification, the default validator should be
// used instead via the ValidateToken() function.
func NewValidator() *Validator {
v := &Validator{
keys: map[string]*ecdsa.PublicKey{},
trustedIssuers: map[string]struct{}{},
}
for id, key := range defaultKeys {
v.keys[id] = key
}
for iss := range defaultTrustedIssuers {
v.trustedIssuers[iss] = struct{}{}
}
return v
}
// ValidateToken validates token t only accepting the trusted issuers in the
// official specification. If the token is invalid, a slice of all validation
// errors is returned. Otherwise, nil is returned.
func ValidateToken(t *Token) []error {
return defaultValidator.validateTokenV1(t)
}
// ValidateToken validates token t according to the configuration of the
// Validator. If the token is invalid, a slice of all validation errors is
// returned. Otherwise, nil is returned.
func (v *Validator) ValidateToken(t *Token) []error {
return v.validateTokenV1(t)
}
func (v *Validator) validateTokenV1(t *Token) (errs []error) {
if t.Algorithm != -7 {
errs = append(errs,
fmt.Errorf("%w: expected -7, got %d",
ErrInvalidSigningAlgorithm, t.Algorithm))
}
if _, ok := v.trustedIssuers[t.Issuer]; !ok {
errs = append(errs,
fmt.Errorf("%w: got '%s'", ErrUntrustedIssuer, t.Issuer))
}
// verify signature
keyID := t.Issuer + "#" + t.KeyID
key, ok := v.keys[keyID]
if !ok {
// TODO retrieve?
errs = append(errs,
fmt.Errorf("%w: got '%s'", ErrUnknownPublicKey, keyID))
} else if key == nil {
errs = append(errs,
fmt.Errorf("%w: key '%s' is nil", ErrUnknownPublicKey, keyID))
} else if len(t.Signature) != 64 {
errs = append(errs,
fmt.Errorf("%w: incorrect length", ErrBadSignature))
} else {
if !ecdsa.Verify(
key,
t.digest,
big.NewInt(0).SetBytes(t.Signature[:32]),
big.NewInt(0).SetBytes(t.Signature[32:])) {
errs = append(errs,
fmt.Errorf("%w: failed verification", ErrBadSignature))
}
}
// timestamps
now := time.Now()
if now.Before(t.NotBefore) {
errs = append(errs,
fmt.Errorf("%w (nbf: %v)", ErrTokenNotActive, t.NotBefore))
}
if now.After(t.Expires) {
errs = append(errs,
fmt.Errorf("%w (exp: %v)", ErrTokenExpired, t.Expires))
}
// cti/jti: any non-zero uuid is valid
nilUUID := [16]byte{}
if len(t.cti) != 16 || bytes.Equal(t.cti, nilUUID[:]) {
errs = append(errs,
fmt.Errorf("%w: got '%x'", ErrInvalidCTI, t.cti))
}
// claims
vcContext := "https://www.w3.org/2018/credentials/v1"
if len(t.VerifiableCredential.Context) < 1 ||
t.VerifiableCredential.Context[0] != vcContext {
errs = append(errs,
fmt.Errorf("%w: @context[0] must be '%s' (got: %s)",
ErrInvalidClaimsContext, vcContext,
t.VerifiableCredential.Context[0]))
}
nzcpContext := "https://nzcp.covid19.health.nz/contexts/v1"
containsNZCPContext := false
for _, c := range t.VerifiableCredential.Context {
if c == nzcpContext {
containsNZCPContext = true
break
}
}
if !containsNZCPContext {
errs = append(errs,
fmt.Errorf("%w: missing NZCP context '%s'",
ErrInvalidClaimsContext, nzcpContext))
}
// pass type
if len(t.VerifiableCredential.Type) != 2 ||
t.VerifiableCredential.Type[0] != "VerifiableCredential" ||
t.VerifiableCredential.Type[1] != "PublicCovidPass" {
errs = append(errs,
fmt.Errorf("%w: type must be %v (got: %v)",
ErrInvalidClaimsType,
[]string{"VerifiableCredential", "PublicCovidPass"},
t.VerifiableCredential.Type))
}
// version
if t.VerifiableCredential.Version != "1.0.0" {
errs = append(errs,
fmt.Errorf("%w: token version must be 1.0.0 (got: '%s')",
ErrInvalidTokenVersion,
t.VerifiableCredential.Version))
}
return errs
}
// RegisterIssuer instructs the validator to treat iss as a valid issuer for
// NZCPs. This is intended for testing purposes only.
func (v *Validator) RegisterIssuer(iss string) {
v.trustedIssuers[iss] = struct{}{}
}
// RegisterPublicKey instructs the validator to treat id and its associated
// public key as valid for NZCPs. This is intended for testing purposes only.
func (v *Validator) RegisterPublicKey(id string, pub *ecdsa.PublicKey) error {
if _, ok := v.keys[id]; ok {
return errors.New("Cannot overwrite existing public key; " +
"use a new instance instead.")
}
v.keys[id] = pub
return nil
}