diff --git a/acme/account.go b/acme/account.go deleted file mode 100644 index 1c5870d5..00000000 --- a/acme/account.go +++ /dev/null @@ -1,197 +0,0 @@ -package acme - -import ( - "context" - "encoding/json" - "time" - - "github.com/pkg/errors" - "github.com/smallstep/nosql" - "go.step.sm/crypto/jose" -) - -// Account is a subset of the internal account type containing only those -// attributes required for responses in the ACME protocol. -type Account struct { - Contact []string `json:"contact,omitempty"` - Status string `json:"status"` - Orders string `json:"orders"` - ID string `json:"-"` - Key *jose.JSONWebKey `json:"-"` -} - -// ToLog enables response logging. -func (a *Account) ToLog() (interface{}, error) { - b, err := json.Marshal(a) - if err != nil { - return nil, ServerInternalErr(errors.Wrap(err, "error marshaling account for logging")) - } - return string(b), nil -} - -// GetID returns the account ID. -func (a *Account) GetID() string { - return a.ID -} - -// GetKey returns the JWK associated with the account. -func (a *Account) GetKey() *jose.JSONWebKey { - return a.Key -} - -// IsValid returns true if the Account is valid. -func (a *Account) IsValid() bool { - return a.Status == StatusValid -} - -// AccountOptions are the options needed to create a new ACME account. -type AccountOptions struct { - Key *jose.JSONWebKey - Contact []string -} - -// account represents an ACME account. -type account struct { - ID string `json:"id"` - Created time.Time `json:"created"` - Deactivated time.Time `json:"deactivated"` - Key *jose.JSONWebKey `json:"key"` - Contact []string `json:"contact,omitempty"` - Status string `json:"status"` -} - -// newAccount returns a new acme account type. -func newAccount(db nosql.DB, ops AccountOptions) (*account, error) { - id, err := randID() - if err != nil { - return nil, err - } - - a := &account{ - ID: id, - Key: ops.Key, - Contact: ops.Contact, - Status: "valid", - Created: clock.Now(), - } - return a, a.saveNew(db) -} - -// toACME converts the internal Account type into the public acmeAccount -// type for presentation in the ACME protocol. -func (a *account) toACME(ctx context.Context, db nosql.DB, dir *directory) (*Account, error) { - return &Account{ - Status: a.Status, - Contact: a.Contact, - Orders: dir.getLink(ctx, OrdersByAccountLink, true, a.ID), - Key: a.Key, - ID: a.ID, - }, nil -} - -// save writes the Account to the DB. -// If the account is new then the necessary indices will be created. -// Else, the account in the DB will be updated. -func (a *account) saveNew(db nosql.DB) error { - kid, err := keyToID(a.Key) - if err != nil { - return err - } - kidB := []byte(kid) - - // Set the jwkID -> acme account ID index - _, swapped, err := db.CmpAndSwap(accountByKeyIDTable, kidB, nil, []byte(a.ID)) - switch { - case err != nil: - return ServerInternalErr(errors.Wrap(err, "error setting key-id to account-id index")) - case !swapped: - return ServerInternalErr(errors.Errorf("key-id to account-id index already exists")) - default: - if err = a.save(db, nil); err != nil { - db.Del(accountByKeyIDTable, kidB) - return err - } - return nil - } -} - -func (a *account) save(db nosql.DB, old *account) error { - var ( - err error - oldB []byte - ) - if old == nil { - oldB = nil - } else { - if oldB, err = json.Marshal(old); err != nil { - return ServerInternalErr(errors.Wrap(err, "error marshaling old acme order")) - } - } - - b, err := json.Marshal(*a) - if err != nil { - return errors.Wrap(err, "error marshaling new account object") - } - // Set the Account - _, swapped, err := db.CmpAndSwap(accountTable, []byte(a.ID), oldB, b) - switch { - case err != nil: - return ServerInternalErr(errors.Wrap(err, "error storing account")) - case !swapped: - return ServerInternalErr(errors.New("error storing account; " + - "value has changed since last read")) - default: - return nil - } -} - -// update updates the acme account object stored in the database if, -// and only if, the account has not changed since the last read. -func (a *account) update(db nosql.DB, contact []string) (*account, error) { - b := *a - b.Contact = contact - if err := b.save(db, a); err != nil { - return nil, err - } - return &b, nil -} - -// deactivate deactivates the acme account. -func (a *account) deactivate(db nosql.DB) (*account, error) { - b := *a - b.Status = StatusDeactivated - b.Deactivated = clock.Now() - if err := b.save(db, a); err != nil { - return nil, err - } - return &b, nil -} - -// getAccountByID retrieves the account with the given ID. -func getAccountByID(db nosql.DB, id string) (*account, error) { - ab, err := db.Get(accountTable, []byte(id)) - if err != nil { - if nosql.IsErrNotFound(err) { - return nil, MalformedErr(errors.Wrapf(err, "account %s not found", id)) - } - return nil, ServerInternalErr(errors.Wrapf(err, "error loading account %s", id)) - } - - a := new(account) - if err = json.Unmarshal(ab, a); err != nil { - return nil, ServerInternalErr(errors.Wrap(err, "error unmarshaling account")) - } - return a, nil -} - -// getAccountByKeyID retrieves Id associated with the given Kid. -func getAccountByKeyID(db nosql.DB, kid string) (*account, error) { - id, err := db.Get(accountByKeyIDTable, []byte(kid)) - if err != nil { - if nosql.IsErrNotFound(err) { - return nil, MalformedErr(errors.Wrapf(err, "account with key id %s not found", kid)) - } - return nil, ServerInternalErr(errors.Wrapf(err, "error loading key-account index")) - } - return getAccountByID(db, string(id)) -} diff --git a/acme/authority.go b/acme/authority.go index 0f5f2c9f..6de3a5e1 100644 --- a/acme/authority.go +++ b/acme/authority.go @@ -92,7 +92,7 @@ func NewAuthority(db nosql.DB, dns, prefix string, signAuth SignAuthority) (*Aut }) } -// New returns a new Autohrity that implements the ACME interface. +// New returns a new Authority that implements the ACME interface. func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { if _, ok := ops.DB.(*database.SimpleDB); !ok { // If it's not a SimpleDB then go ahead and bootstrap the DB with the @@ -140,59 +140,41 @@ func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error } // NewNonce generates, stores, and returns a new ACME nonce. -func (a *Authority) NewNonce() (string, error) { - n, err := newNonce(a.db) - if err != nil { - return "", err - } - return n.ID, nil +func (a *Authority) NewNonce(ctx context.Context) (string, error) { + return a.db.CreateNonce(ctx) } // UseNonce consumes the given nonce if it is valid, returns error otherwise. -func (a *Authority) UseNonce(nonce string) error { - return useNonce(a.db, nonce) +func (a *Authority) UseNonce(ctx context.Context, nonce string) error { + return a.db.DeleteNonce(ctx, nonce) } // NewAccount creates, stores, and returns a new ACME account. func (a *Authority) NewAccount(ctx context.Context, ao AccountOptions) (*Account, error) { - acc, err := newAccount(a.db, ao) - if err != nil { - return nil, err + a := NewAccount(ao) + if err := a.db.CreateAccount(ctx, a); err != nil { + return ServerInternalErr(err) } - return acc.toACME(ctx, a.db, a.dir) + return a, nil } // UpdateAccount updates an ACME account. -func (a *Authority) UpdateAccount(ctx context.Context, id string, contact []string) (*Account, error) { - acc, err := getAccountByID(a.db, id) +func (a *Authority) UpdateAccount(ctx context.Context, auo AccountUpdateOptions) (*Account, error) { + acc, err := a.db.GetAccount(ctx, auo.ID) if err != nil { - return nil, ServerInternalErr(err) + return ServerInternalErr(err) } - if acc, err = acc.update(a.db, contact); err != nil { - return nil, err + acc.Contact = auo.Contact + acc.Status = auo.Status + if err = a.db.UpdateAccount(ctx, acc); err != nil { + return ServerInternalErr(err) } - return acc.toACME(ctx, a.db, a.dir) + return acc, nil } // GetAccount returns an ACME account. func (a *Authority) GetAccount(ctx context.Context, id string) (*Account, error) { - acc, err := getAccountByID(a.db, id) - if err != nil { - return nil, err - } - return acc.toACME(ctx, a.db, a.dir) -} - -// DeactivateAccount deactivates an ACME account. -func (a *Authority) DeactivateAccount(ctx context.Context, id string) (*Account, error) { - acc, err := getAccountByID(a.db, id) - if err != nil { - return nil, err - } - if acc, err = acc.deactivate(a.db); err != nil { - return nil, err - } - return acc.toACME(ctx, a.db, a.dir) + return a.db.GetAccount(ctx, id) } func keyToID(jwk *jose.JSONWebKey) (string, error) { @@ -209,11 +191,8 @@ func (a *Authority) GetAccountByKey(ctx context.Context, jwk *jose.JSONWebKey) ( if err != nil { return nil, err } - acc, err := getAccountByKeyID(a.db, kid) - if err != nil { - return nil, err - } - return acc.toACME(ctx, a.db, a.dir) + acc, err := a.db.GetAccountByKeyID(ctx, kid) + return acc, err } // GetOrder returns an ACME order. diff --git a/acme/nonce.go b/acme/nonce.go deleted file mode 100644 index db680f08..00000000 --- a/acme/nonce.go +++ /dev/null @@ -1,73 +0,0 @@ -package acme - -import ( - "encoding/base64" - "encoding/json" - "time" - - "github.com/pkg/errors" - "github.com/smallstep/nosql" - "github.com/smallstep/nosql/database" -) - -// nonce contains nonce metadata used in the ACME protocol. -type nonce struct { - ID string - Created time.Time -} - -// newNonce creates, stores, and returns an ACME replay-nonce. -func newNonce(db nosql.DB) (*nonce, error) { - _id, err := randID() - if err != nil { - return nil, err - } - - id := base64.RawURLEncoding.EncodeToString([]byte(_id)) - n := &nonce{ - ID: id, - Created: clock.Now(), - } - b, err := json.Marshal(n) - if err != nil { - return nil, ServerInternalErr(errors.Wrap(err, "error marshaling nonce")) - } - _, swapped, err := db.CmpAndSwap(nonceTable, []byte(id), nil, b) - switch { - case err != nil: - return nil, ServerInternalErr(errors.Wrap(err, "error storing nonce")) - case !swapped: - return nil, ServerInternalErr(errors.New("error storing nonce; " + - "value has changed since last read")) - default: - return n, nil - } -} - -// useNonce verifies that the nonce is valid (by checking if it exists), -// and if so, consumes the nonce resource by deleting it from the database. -func useNonce(db nosql.DB, nonce string) error { - err := db.Update(&database.Tx{ - Operations: []*database.TxEntry{ - { - Bucket: nonceTable, - Key: []byte(nonce), - Cmd: database.Get, - }, - { - Bucket: nonceTable, - Key: []byte(nonce), - Cmd: database.Delete, - }, - }, - }) - - switch { - case nosql.IsErrNotFound(err): - return BadNonceErr(nil) - case err != nil: - return ServerInternalErr(errors.Wrapf(err, "error deleting nonce %s", nonce)) - default: - return nil - } -}