|
1 | 1 | package api
|
2 | 2 |
|
3 | 3 | import (
|
4 |
| - "context" |
5 | 4 | "encoding/json"
|
6 | 5 | "net/http"
|
7 | 6 |
|
8 | 7 | "github.com/go-chi/chi"
|
9 | 8 | "github.com/smallstep/certificates/acme"
|
10 | 9 | "github.com/smallstep/certificates/api"
|
11 | 10 | "github.com/smallstep/certificates/logging"
|
12 |
| - "go.step.sm/crypto/jose" |
13 | 11 | )
|
14 | 12 |
|
15 |
| -// ExternalAccountBinding represents the ACME externalAccountBinding JWS |
16 |
| -type ExternalAccountBinding struct { |
17 |
| - Protected string `json:"protected"` |
18 |
| - Payload string `json:"payload"` |
19 |
| - Sig string `json:"signature"` |
20 |
| -} |
21 |
| - |
22 | 13 | // NewAccountRequest represents the payload for a new account request.
|
23 | 14 | type NewAccountRequest struct {
|
24 | 15 | Contact []string `json:"contact"`
|
@@ -241,142 +232,3 @@ func (h *Handler) GetOrdersByAccountID(w http.ResponseWriter, r *http.Request) {
|
241 | 232 | api.JSON(w, orders)
|
242 | 233 | logOrdersByAccount(w, orders)
|
243 | 234 | }
|
244 |
| - |
245 |
| -// validateExternalAccountBinding validates the externalAccountBinding property in a call to new-account. |
246 |
| -func (h *Handler) validateExternalAccountBinding(ctx context.Context, nar *NewAccountRequest) (*acme.ExternalAccountKey, error) { |
247 |
| - acmeProv, err := acmeProvisionerFromContext(ctx) |
248 |
| - if err != nil { |
249 |
| - return nil, acme.WrapErrorISE(err, "could not load ACME provisioner from context") |
250 |
| - } |
251 |
| - |
252 |
| - if !acmeProv.RequireEAB { |
253 |
| - return nil, nil |
254 |
| - } |
255 |
| - |
256 |
| - if nar.ExternalAccountBinding == nil { |
257 |
| - return nil, acme.NewError(acme.ErrorExternalAccountRequiredType, "no external account binding provided") |
258 |
| - } |
259 |
| - |
260 |
| - eabJSONBytes, err := json.Marshal(nar.ExternalAccountBinding) |
261 |
| - if err != nil { |
262 |
| - return nil, acme.WrapErrorISE(err, "error marshaling externalAccountBinding into bytes") |
263 |
| - } |
264 |
| - |
265 |
| - eabJWS, err := jose.ParseJWS(string(eabJSONBytes)) |
266 |
| - if err != nil { |
267 |
| - return nil, acme.WrapErrorISE(err, "error parsing externalAccountBinding jws") |
268 |
| - } |
269 |
| - |
270 |
| - // TODO(hs): implement strategy pattern to allow for different ways of verification (i.e. webhook call) based on configuration? |
271 |
| - |
272 |
| - keyID, acmeErr := validateEABJWS(ctx, eabJWS) |
273 |
| - if acmeErr != nil { |
274 |
| - return nil, acmeErr |
275 |
| - } |
276 |
| - |
277 |
| - externalAccountKey, err := h.db.GetExternalAccountKey(ctx, acmeProv.ID, keyID) |
278 |
| - if err != nil { |
279 |
| - if _, ok := err.(*acme.Error); ok { |
280 |
| - return nil, acme.WrapError(acme.ErrorUnauthorizedType, err, "the field 'kid' references an unknown key") |
281 |
| - } |
282 |
| - return nil, acme.WrapErrorISE(err, "error retrieving external account key") |
283 |
| - } |
284 |
| - |
285 |
| - if externalAccountKey.AlreadyBound() { |
286 |
| - return nil, acme.NewError(acme.ErrorUnauthorizedType, "external account binding key with id '%s' was already bound to account '%s' on %s", keyID, externalAccountKey.AccountID, externalAccountKey.BoundAt) |
287 |
| - } |
288 |
| - |
289 |
| - payload, err := eabJWS.Verify(externalAccountKey.KeyBytes) |
290 |
| - if err != nil { |
291 |
| - return nil, acme.WrapErrorISE(err, "error verifying externalAccountBinding signature") |
292 |
| - } |
293 |
| - |
294 |
| - jwk, err := jwkFromContext(ctx) |
295 |
| - if err != nil { |
296 |
| - return nil, err |
297 |
| - } |
298 |
| - |
299 |
| - var payloadJWK *jose.JSONWebKey |
300 |
| - if err = json.Unmarshal(payload, &payloadJWK); err != nil { |
301 |
| - return nil, acme.WrapError(acme.ErrorMalformedType, err, "error unmarshaling payload into jwk") |
302 |
| - } |
303 |
| - |
304 |
| - if !keysAreEqual(jwk, payloadJWK) { |
305 |
| - return nil, acme.NewError(acme.ErrorUnauthorizedType, "keys in jws and eab payload do not match") |
306 |
| - } |
307 |
| - |
308 |
| - return externalAccountKey, nil |
309 |
| -} |
310 |
| - |
311 |
| -// keysAreEqual performs an equality check on two JWKs by comparing |
312 |
| -// the (base64 encoding) of the Key IDs. |
313 |
| -func keysAreEqual(x, y *jose.JSONWebKey) bool { |
314 |
| - if x == nil || y == nil { |
315 |
| - return false |
316 |
| - } |
317 |
| - digestX, errX := acme.KeyToID(x) |
318 |
| - digestY, errY := acme.KeyToID(y) |
319 |
| - if errX != nil || errY != nil { |
320 |
| - return false |
321 |
| - } |
322 |
| - return digestX == digestY |
323 |
| -} |
324 |
| - |
325 |
| -// validateEABJWS verifies the contents of the External Account Binding JWS. |
326 |
| -// The protected header of the JWS MUST meet the following criteria: |
327 |
| -// o The "alg" field MUST indicate a MAC-based algorithm |
328 |
| -// o The "kid" field MUST contain the key identifier provided by the CA |
329 |
| -// o The "nonce" field MUST NOT be present |
330 |
| -// o The "url" field MUST be set to the same value as the outer JWS |
331 |
| -func validateEABJWS(ctx context.Context, jws *jose.JSONWebSignature) (string, *acme.Error) { |
332 |
| - |
333 |
| - if jws == nil { |
334 |
| - return "", acme.NewErrorISE("no JWS provided") |
335 |
| - } |
336 |
| - |
337 |
| - if len(jws.Signatures) != 1 { |
338 |
| - return "", acme.NewError(acme.ErrorMalformedType, "JWS must have one signature") |
339 |
| - } |
340 |
| - |
341 |
| - header := jws.Signatures[0].Protected |
342 |
| - algorithm := header.Algorithm |
343 |
| - keyID := header.KeyID |
344 |
| - nonce := header.Nonce |
345 |
| - |
346 |
| - if !(algorithm == jose.HS256 || algorithm == jose.HS384 || algorithm == jose.HS512) { |
347 |
| - return "", acme.NewError(acme.ErrorMalformedType, "'alg' field set to invalid algorithm '%s'", algorithm) |
348 |
| - } |
349 |
| - |
350 |
| - if keyID == "" { |
351 |
| - return "", acme.NewError(acme.ErrorMalformedType, "'kid' field is required") |
352 |
| - } |
353 |
| - |
354 |
| - if nonce != "" { |
355 |
| - return "", acme.NewError(acme.ErrorMalformedType, "'nonce' must not be present") |
356 |
| - } |
357 |
| - |
358 |
| - jwsURL, ok := header.ExtraHeaders["url"] |
359 |
| - if !ok { |
360 |
| - return "", acme.NewError(acme.ErrorMalformedType, "'url' field is required") |
361 |
| - } |
362 |
| - |
363 |
| - outerJWS, err := jwsFromContext(ctx) |
364 |
| - if err != nil { |
365 |
| - return "", acme.WrapErrorISE(err, "could not retrieve outer JWS from context") |
366 |
| - } |
367 |
| - |
368 |
| - if len(outerJWS.Signatures) != 1 { |
369 |
| - return "", acme.NewError(acme.ErrorMalformedType, "outer JWS must have one signature") |
370 |
| - } |
371 |
| - |
372 |
| - outerJWSURL, ok := outerJWS.Signatures[0].Protected.ExtraHeaders["url"] |
373 |
| - if !ok { |
374 |
| - return "", acme.NewError(acme.ErrorMalformedType, "'url' field must be set in outer JWS") |
375 |
| - } |
376 |
| - |
377 |
| - if jwsURL != outerJWSURL { |
378 |
| - return "", acme.NewError(acme.ErrorMalformedType, "'url' field is not the same value as the outer JWS") |
379 |
| - } |
380 |
| - |
381 |
| - return keyID, nil |
382 |
| -} |
0 commit comments