certificates/acme/nonce.go
2019-09-13 15:48:33 -07:00

73 lines
1.6 KiB
Go

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
}
}