Skip to content

Commit fd9845e

Browse files
committed
Add cursor and limit to ACME EAB DB interface
1 parent c3f2fd8 commit fd9845e

File tree

11 files changed

+1313
-1295
lines changed

11 files changed

+1313
-1295
lines changed

acme/api/account.go

Lines changed: 0 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
11
package api
22

33
import (
4-
"context"
54
"encoding/json"
65
"net/http"
76

87
"github.com/go-chi/chi"
98
"github.com/smallstep/certificates/acme"
109
"github.com/smallstep/certificates/api"
1110
"github.com/smallstep/certificates/logging"
12-
"go.step.sm/crypto/jose"
1311
)
1412

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-
2213
// NewAccountRequest represents the payload for a new account request.
2314
type NewAccountRequest struct {
2415
Contact []string `json:"contact"`
@@ -241,142 +232,3 @@ func (h *Handler) GetOrdersByAccountID(w http.ResponseWriter, r *http.Request) {
241232
api.JSON(w, orders)
242233
logOrdersByAccount(w, orders)
243234
}
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

Comments
 (0)