Improve tests for already bound EAB keys

This commit is contained in:
Herman Slatman 2021-10-08 13:18:23 +02:00
parent 9d4cafc4bd
commit 0afea2e957
No known key found for this signature in database
GPG key ID: F4D8A44EA0A75A4F
4 changed files with 96 additions and 14 deletions

View file

@ -43,6 +43,7 @@ func KeyToID(jwk *jose.JSONWebKey) (string, error) {
return base64.RawURLEncoding.EncodeToString(kid), nil return base64.RawURLEncoding.EncodeToString(kid), nil
} }
// ExternalAccountKey is an ACME External Account Binding key.
type ExternalAccountKey struct { type ExternalAccountKey struct {
ID string `json:"id"` ID string `json:"id"`
Provisioner string `json:"provisioner"` Provisioner string `json:"provisioner"`
@ -53,12 +54,20 @@ type ExternalAccountKey struct {
BoundAt time.Time `json:"boundAt,omitempty"` BoundAt time.Time `json:"boundAt,omitempty"`
} }
// AlreadyBound returns whether this EAK is already bound to
// an ACME Account or not.
func (eak *ExternalAccountKey) AlreadyBound() bool { func (eak *ExternalAccountKey) AlreadyBound() bool {
return !eak.BoundAt.IsZero() return !eak.BoundAt.IsZero()
} }
func (eak *ExternalAccountKey) BindTo(account *Account) { // BindTo binds the EAK to an Account.
// It returns an error if it's already bound.
func (eak *ExternalAccountKey) BindTo(account *Account) error {
if eak.AlreadyBound() {
return NewError(ErrorUnauthorizedType, "external account binding key with id '%s' was already bound to account '%s' on %s", eak.ID, eak.AccountID, eak.BoundAt)
}
eak.AccountID = account.ID eak.AccountID = account.ID
eak.BoundAt = time.Now() eak.BoundAt = time.Now()
eak.KeyBytes = []byte{} // clearing the key bytes; can only be used once eak.KeyBytes = []byte{} // clearing the key bytes; can only be used once
return nil
} }

View file

@ -4,6 +4,7 @@ import (
"crypto" "crypto"
"encoding/base64" "encoding/base64"
"testing" "testing"
"time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
@ -79,3 +80,67 @@ func TestAccount_IsValid(t *testing.T) {
}) })
} }
} }
func TestExternalAccountKey_BindTo(t *testing.T) {
boundAt := time.Now()
tests := []struct {
name string
eak *ExternalAccountKey
acct *Account
err *Error
}{
{
name: "ok",
eak: &ExternalAccountKey{
ID: "eakID",
Provisioner: "prov",
Reference: "ref",
KeyBytes: []byte{1, 3, 3, 7},
},
acct: &Account{
ID: "accountID",
},
err: nil,
},
{
name: "fail/already-bound",
eak: &ExternalAccountKey{
ID: "eakID",
Provisioner: "prov",
Reference: "ref",
KeyBytes: []byte{1, 3, 3, 7},
AccountID: "someAccountID",
BoundAt: boundAt,
},
acct: &Account{
ID: "accountID",
},
err: NewError(ErrorUnauthorizedType, "external account binding key with id '%s' was already bound to account '%s' on %s", "eakID", "someAccountID", boundAt),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
eak := tt.eak
acct := tt.acct
err := eak.BindTo(acct)
wantErr := tt.err != nil
gotErr := err != nil
if wantErr != gotErr {
t.Errorf("ExternalAccountKey.BindTo() error = %v, wantErr %v", err, tt.err)
}
if wantErr {
assert.NotNil(t, err)
assert.Type(t, &Error{}, err)
ae, _ := err.(*Error)
assert.Equals(t, ae.Type, tt.err.Type)
assert.Equals(t, ae.Detail, tt.err.Detail)
assert.Equals(t, ae.Identifier, tt.err.Identifier)
assert.Equals(t, ae.Subproblems, tt.err.Subproblems)
} else {
assert.Equals(t, eak.AccountID, acct.ID)
assert.Equals(t, eak.KeyBytes, []byte{})
assert.NotNil(t, eak.BoundAt)
}
})
}
}

View file

@ -138,7 +138,11 @@ func (h *Handler) NewAccount(w http.ResponseWriter, r *http.Request) {
return return
} }
if eak != nil { // means that we have a (valid) External Account Binding key that should be bound, updated and sent in the response if eak != nil { // means that we have a (valid) External Account Binding key that should be bound, updated and sent in the response
eak.BindTo(acc) err := eak.BindTo(acc)
if err != nil {
api.WriteError(w, err)
return
}
if err := h.db.UpdateExternalAccountKey(ctx, prov.Name, eak); err != nil { if err := h.db.UpdateExternalAccountKey(ctx, prov.Name, eak); err != nil {
api.WriteError(w, acme.WrapErrorISE(err, "error updating external account binding key")) api.WriteError(w, acme.WrapErrorISE(err, "error updating external account binding key"))
return return

View file

@ -1055,6 +1055,7 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) {
ctx := context.WithValue(context.Background(), jwkContextKey, jwk) ctx := context.WithValue(context.Background(), jwkContextKey, jwk)
ctx = context.WithValue(ctx, baseURLContextKey, baseURL) ctx = context.WithValue(ctx, baseURLContextKey, baseURL)
ctx = context.WithValue(ctx, provisionerContextKey, prov) ctx = context.WithValue(ctx, provisionerContextKey, prov)
createdAt := time.Now()
return test{ return test{
db: &acme.MockDB{ db: &acme.MockDB{
MockGetExternalAccountKey: func(ctx context.Context, provisionerName string, keyID string) (*acme.ExternalAccountKey, error) { MockGetExternalAccountKey: func(ctx context.Context, provisionerName string, keyID string) (*acme.ExternalAccountKey, error) {
@ -1063,7 +1064,7 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) {
Provisioner: escProvName, Provisioner: escProvName,
Reference: "testeak", Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7}, KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: time.Now(), CreatedAt: createdAt,
}, nil }, nil
}, },
}, },
@ -1072,7 +1073,13 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) {
Contact: []string{"foo", "bar"}, Contact: []string{"foo", "bar"},
ExternalAccountBinding: eab, ExternalAccountBinding: eab,
}, },
eak: &acme.ExternalAccountKey{}, eak: &acme.ExternalAccountKey{
ID: "eakID",
Provisioner: escProvName,
Reference: "testeak",
KeyBytes: []byte{1, 3, 3, 7},
CreatedAt: createdAt,
},
err: nil, err: nil,
} }
}, },
@ -1299,8 +1306,6 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) {
wantErr := tc.err != nil wantErr := tc.err != nil
gotErr := err != nil gotErr := err != nil
if wantErr != gotErr { if wantErr != gotErr {
// fmt.Println(got)
// fmt.Println(fmt.Sprintf("%#+v", got))
t.Errorf("Handler.validateExternalAccountBinding() error = %v, want %v", err, tc.err) t.Errorf("Handler.validateExternalAccountBinding() error = %v, want %v", err, tc.err)
} }
if wantErr { if wantErr {
@ -1311,20 +1316,19 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) {
assert.Equals(t, ae.Detail, tc.err.Detail) assert.Equals(t, ae.Detail, tc.err.Detail)
assert.Equals(t, ae.Identifier, tc.err.Identifier) assert.Equals(t, ae.Identifier, tc.err.Identifier)
assert.Equals(t, ae.Subproblems, tc.err.Subproblems) assert.Equals(t, ae.Subproblems, tc.err.Subproblems)
// fmt.Println(fmt.Sprintf("%#+v", ae))
// fmt.Println(fmt.Sprintf("%#+v", tc.err))
//t.Fail()
} else { } else {
if got == nil { if got == nil {
assert.Nil(t, tc.eak) assert.Nil(t, tc.eak)
} else { } else {
// TODO: equality check on certain fields?
assert.NotNil(t, tc.eak) assert.NotNil(t, tc.eak)
assert.Equals(t, got.ID, tc.eak.ID)
assert.Equals(t, got.KeyBytes, tc.eak.KeyBytes)
assert.Equals(t, got.Provisioner, tc.eak.Provisioner)
assert.Equals(t, got.Reference, tc.eak.Reference)
assert.Equals(t, got.CreatedAt, tc.eak.CreatedAt)
assert.Equals(t, got.AccountID, tc.eak.AccountID)
assert.Equals(t, got.BoundAt, tc.eak.BoundAt)
} }
//assert.Equals(t, tc.eak, got)
//assert.NotNil(t, got)
} }
}) })
} }