17
17
package jose
18
18
19
19
import (
20
+ "bytes"
20
21
"crypto"
21
22
"crypto/ecdsa"
22
23
"crypto/elliptic"
23
24
"crypto/rsa"
25
+ "crypto/sha1"
26
+ "crypto/sha256"
24
27
"crypto/x509"
25
28
"encoding/base64"
26
29
"errors"
27
30
"fmt"
28
31
"math/big"
32
+ "net/url"
29
33
"reflect"
30
34
"strings"
31
35
@@ -57,16 +61,31 @@ type rawJSONWebKey struct {
57
61
Dq * byteBuffer `json:"dq,omitempty"`
58
62
Qi * byteBuffer `json:"qi,omitempty"`
59
63
// Certificates
60
- X5c []string `json:"x5c,omitempty"`
64
+ X5c []string `json:"x5c,omitempty"`
65
+ X5u * url.URL `json:"x5u,omitempty"`
66
+ X5tSHA1 * byteBuffer `json:"x5t,omitempty"`
67
+ X5tSHA256 * byteBuffer `json:"x5t#S256,omitempty"`
61
68
}
62
69
63
70
// JSONWebKey represents a public or private key in JWK format.
64
71
type JSONWebKey struct {
65
- Key interface {}
72
+ // Cryptographic key, can be a symmetric or asymmetric key.
73
+ Key interface {}
74
+ // Key identifier, parsed from `kid` header.
75
+ KeyID string
76
+ // Key algorithm, parsed from `alg` header.
77
+ Algorithm string
78
+ // Key use, parsed from `use` header.
79
+ Use string
80
+
81
+ // X.509 certificate chain, parsed from `x5c` header.
66
82
Certificates []* x509.Certificate
67
- KeyID string
68
- Algorithm string
69
- Use string
83
+ // X.509 certificate URL, parsed from `x5u` header.
84
+ CertificatesURL * url.URL
85
+ // X.509 certificate thumbprint (SHA-1), parsed from `x5t` header.
86
+ CertificateThumbprintSHA1 []byte
87
+ // X.509 certificate thumbprint (SHA-256), parsed from `x5t#S256` header.
88
+ CertificateThumbprintSHA256 []byte
70
89
}
71
90
72
91
// MarshalJSON serializes the given key to its JSON representation.
@@ -105,6 +124,36 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
105
124
raw .X5c = append (raw .X5c , base64 .StdEncoding .EncodeToString (cert .Raw ))
106
125
}
107
126
127
+ x5tSHA1Len := len (k .CertificateThumbprintSHA1 )
128
+ x5tSHA256Len := len (k .CertificateThumbprintSHA256 )
129
+ if x5tSHA1Len > 0 {
130
+ if x5tSHA1Len != sha1 .Size {
131
+ return nil , fmt .Errorf ("square/go-jose: invalid SHA-1 thumbprint (must be %d bytes, not %d)" , sha1 .Size , x5tSHA1Len )
132
+ }
133
+ raw .X5tSHA1 = newFixedSizeBuffer (k .CertificateThumbprintSHA1 , sha1 .Size )
134
+ }
135
+ if x5tSHA256Len > 0 {
136
+ if x5tSHA256Len != sha256 .Size {
137
+ return nil , fmt .Errorf ("square/go-jose: invalid SHA-256 thumbprint (must be %d bytes, not %d)" , sha256 .Size , x5tSHA256Len )
138
+ }
139
+ raw .X5tSHA256 = newFixedSizeBuffer (k .CertificateThumbprintSHA256 , sha256 .Size )
140
+ }
141
+
142
+ // If cert chain is attached (as opposed to being behind a URL), check the
143
+ // keys thumbprints to make sure they match what is expected. This is to
144
+ // ensure we don't accidentally produce a JWK with semantically inconsistent
145
+ // data in the headers.
146
+ if len (k .Certificates ) > 0 {
147
+ expectedSHA1 := sha1 .Sum (k .Certificates [0 ].Raw )
148
+ expectedSHA256 := sha256 .Sum256 (k .Certificates [0 ].Raw )
149
+ if ! bytes .Equal (k .CertificateThumbprintSHA1 , expectedSHA1 [:]) ||
150
+ ! bytes .Equal (k .CertificateThumbprintSHA256 , expectedSHA256 [:]) {
151
+ return nil , errors .New ("square/go-jose: invalid SHA-1 or SHA-256 thumbprint, does not match cert chain" )
152
+ }
153
+ }
154
+
155
+ raw .X5u = k .CertificatesURL
156
+
108
157
return json .Marshal (raw )
109
158
}
110
159
@@ -116,28 +165,61 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
116
165
return err
117
166
}
118
167
168
+ certs , err := parseCertificateChain (raw .X5c )
169
+ if err != nil {
170
+ return fmt .Errorf ("square/go-jose: failed to unmarshal x5c field: %s" , err )
171
+ }
172
+
119
173
var key interface {}
174
+ var certPub interface {}
175
+ var keyPub interface {}
176
+
177
+ if len (certs ) > 0 {
178
+ // We need to check that leaf public key matches the key embedded in this
179
+ // JWK, as required by the standard (see RFC 7517, Section 4.7). Otherwise
180
+ // the JWK parsed could be semantically invalid. Technically, should also
181
+ // check key usage fields and other extensions on the cert here, but the
182
+ // standard doesn't exactly explain how they're supposed to map from the
183
+ // JWK representation to the X.509 extensions.
184
+ certPub = certs [0 ].PublicKey
185
+ }
186
+
120
187
switch raw .Kty {
121
188
case "EC" :
122
189
if raw .D != nil {
123
190
key , err = raw .ecPrivateKey ()
191
+ if err == nil {
192
+ keyPub = key .(* ecdsa.PrivateKey ).Public ()
193
+ }
124
194
} else {
125
195
key , err = raw .ecPublicKey ()
196
+ keyPub = key
126
197
}
127
198
case "RSA" :
128
199
if raw .D != nil {
129
200
key , err = raw .rsaPrivateKey ()
201
+ if err == nil {
202
+ keyPub = key .(* rsa.PrivateKey ).Public ()
203
+ }
130
204
} else {
131
205
key , err = raw .rsaPublicKey ()
206
+ keyPub = key
132
207
}
133
208
case "oct" :
209
+ if certPub != nil {
210
+ return errors .New ("square/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain" )
211
+ }
134
212
key , err = raw .symmetricKey ()
135
213
case "OKP" :
136
214
if raw .Crv == "Ed25519" && raw .X != nil {
137
215
if raw .D != nil {
138
216
key , err = raw .edPrivateKey ()
217
+ if err == nil {
218
+ keyPub = key .(ed25519.PrivateKey ).Public ()
219
+ }
139
220
} else {
140
221
key , err = raw .edPublicKey ()
222
+ keyPub = key
141
223
}
142
224
} else {
143
225
err = fmt .Errorf ("square/go-jose: unknown curve %s'" , raw .Crv )
@@ -146,12 +228,43 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
146
228
err = fmt .Errorf ("square/go-jose: unknown json web key type '%s'" , raw .Kty )
147
229
}
148
230
149
- if err == nil {
150
- * k = JSONWebKey {Key : key , KeyID : raw .Kid , Algorithm : raw .Alg , Use : raw .Use }
231
+ if err != nil {
232
+ return
233
+ }
234
+
235
+ if certPub != nil && keyPub != nil {
236
+ if ! reflect .DeepEqual (certPub , keyPub ) {
237
+ return errors .New ("square/go-jose: invalid JWK, public keys in key and x5c fields to not match" )
238
+ }
239
+ }
240
+
241
+ * k = JSONWebKey {Key : key , KeyID : raw .Kid , Algorithm : raw .Alg , Use : raw .Use , Certificates : certs }
242
+
243
+ k .CertificatesURL = raw .X5u
244
+ k .CertificateThumbprintSHA1 = raw .X5tSHA1 .bytes ()
245
+ k .CertificateThumbprintSHA256 = raw .X5tSHA256 .bytes ()
246
+
247
+ x5tSHA1Len := len (k .CertificateThumbprintSHA1 )
248
+ x5tSHA256Len := len (k .CertificateThumbprintSHA256 )
249
+ if x5tSHA1Len > 0 && x5tSHA1Len != sha1 .Size {
250
+ return errors .New ("square/go-jose: invalid JWK, x5t header is of incorrect size" )
251
+ }
252
+ if x5tSHA256Len > 0 && x5tSHA256Len != sha256 .Size {
253
+ return errors .New ("square/go-jose: invalid JWK, x5t header is of incorrect size" )
254
+ }
255
+
256
+ // If certificate chain *and* thumbprints are set, verify correctness.
257
+ if len (k .Certificates ) > 0 {
258
+ leaf := k .Certificates [0 ]
259
+ sha1sum := sha1 .Sum (leaf .Raw )
260
+ sha256sum := sha256 .Sum256 (leaf .Raw )
261
+
262
+ if len (k .CertificateThumbprintSHA1 ) > 0 && ! bytes .Equal (sha1sum [:], k .CertificateThumbprintSHA1 ) {
263
+ return errors .New ("square/go-jose: invalid JWK, x5c thumbprint does not match x5t value" )
264
+ }
151
265
152
- k .Certificates , err = parseCertificateChain (raw .X5c )
153
- if err != nil {
154
- return fmt .Errorf ("failed to unmarshal x5c field: %s" , err )
266
+ if len (k .CertificateThumbprintSHA256 ) > 0 && ! bytes .Equal (sha256sum [:], k .CertificateThumbprintSHA256 ) {
267
+ return errors .New ("square/go-jose: invalid JWK, x5c thumbprint does not match x5t#S256 value" )
155
268
}
156
269
}
157
270
0 commit comments