forked from TrueCloudLab/certificates
Add new directory structure
This commit is contained in:
parent
088432150d
commit
34859551ef
11 changed files with 354 additions and 0 deletions
31
acme/db/db.go
Normal file
31
acme/db/db.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// DB is the DB interface expected by the step-ca ACME API.
|
||||||
|
type DB interface {
|
||||||
|
CreateAccount(ctx context.Context, acc *Account) (*Account, error)
|
||||||
|
GetAccount(ctx context.Context, id string) (*Account, error)
|
||||||
|
GetAccountByKeyID(ctx context.Context) (*Account, error)
|
||||||
|
UpdateAccount(ctx context.Context, acc *Account) error
|
||||||
|
|
||||||
|
CreateNonce(ctx context.Context) (Nonce, error)
|
||||||
|
DeleteNonce(ctx context.Context, nonce Nonce) error
|
||||||
|
|
||||||
|
CreateAuthorization(ctx context.Context, authz *Authorization) error
|
||||||
|
GetAuthorization(ctx context.Context, id string) (*Authorization, error)
|
||||||
|
UpdateAuthorization(ctx context.Context, authz *Authorization) error
|
||||||
|
|
||||||
|
CreateCertificate(ctx context.Context, cert *Certificate) error
|
||||||
|
GetCertificate(ctx context.Context, id string) (*Certificate, error)
|
||||||
|
|
||||||
|
CreateChallenge(ctx context.Context, ch *Challenge) error
|
||||||
|
GetChallenge(ctx context.Context, id string) (*Challenge, error)
|
||||||
|
UpdateChallenge(ctx context.Context, ch *Challenge) error
|
||||||
|
|
||||||
|
CreateOrder(ctx context.Context, o *Order) error
|
||||||
|
DeleteOrder(ctx context.Context, id string) error
|
||||||
|
GetOrder(ctx context.Context, id string) (*Order, error)
|
||||||
|
GetOrdersByAccountID(ctx context.Context, accountID string) error
|
||||||
|
UpdateOrder(ctx context.Context, o *Order) error
|
||||||
|
}
|
138
acme/db/nosql/account.go
Normal file
138
acme/db/nosql/account.go
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
package nosql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
nosqlDB "github.com/smallstep/nosql"
|
||||||
|
"go.step.sm/crypto/jose"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dbAccount represents an ACME account.
|
||||||
|
type dbAccount 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) saveAccount(nu *dbAccount, old *dbAccount) 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(*nu)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error marshaling new account object")
|
||||||
|
}
|
||||||
|
// Set the Account
|
||||||
|
_, swapped, err := db.CmpAndSwap(accountTable, []byte(nu.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccount imlements the AcmeDB.CreateAccount interface.
|
||||||
|
func (db *DB) CreateAccount(ctx context.Context, acc *Account) error {
|
||||||
|
id, err := randID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dba := &dbAccount{
|
||||||
|
ID: id,
|
||||||
|
Key: acc.Key,
|
||||||
|
Contact: acc.Contact,
|
||||||
|
Status: acc.Valid,
|
||||||
|
Created: clock.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
kid, err := keyToID(dba.Key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
kidB := []byte(kid)
|
||||||
|
|
||||||
|
// Set the jwkID -> acme account ID index
|
||||||
|
_, swapped, err := db.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 = db.saveAccount(dba, nil); err != nil {
|
||||||
|
db.db.Del(accountByKeyIDTable, kidB)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccount imlements the AcmeDB.UpdateAccount interface.
|
||||||
|
func (db *DB) UpdateAccount(ctx context.Context, acc *Account) error {
|
||||||
|
kid, err := keyToID(dba.Key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dba, err := db.db.getAccountByKeyID(ctx, kid)
|
||||||
|
|
||||||
|
newdba := *dba
|
||||||
|
newdba.Contact = acc.contact
|
||||||
|
newdba.Status = acc.Status
|
||||||
|
|
||||||
|
// If the status has changed to 'deactivated', then set deactivatedAt timestamp.
|
||||||
|
if acc.Status == types.StatusDeactivated && dba.Status != types.Status.Deactivated {
|
||||||
|
newdba.Deactivated = clock.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.saveAccount(newdba, dba)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAccountByID retrieves the account with the given ID.
|
||||||
|
func (db *DB) getAccountByID(ctx context.Context, id string) (*dbAccount, error) {
|
||||||
|
ab, err := db.db.Get(accountTable, []byte(id))
|
||||||
|
if err != nil {
|
||||||
|
if nosqlDB.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 (db *DB) getAccountByKeyID(ctx context.Context, kid string) (*dbAccount, error) {
|
||||||
|
id, err := db.db.Get(accountByKeyIDTable, []byte(kid))
|
||||||
|
if err != nil {
|
||||||
|
if nosqlDB.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))
|
||||||
|
}
|
0
acme/db/nosql/authz.go
Normal file
0
acme/db/nosql/authz.go
Normal file
0
acme/db/nosql/certificate.go
Normal file
0
acme/db/nosql/certificate.go
Normal file
0
acme/db/nosql/challenge.go
Normal file
0
acme/db/nosql/challenge.go
Normal file
74
acme/db/nosql/nonce.go
Normal file
74
acme/db/nosql/nonce.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package nosql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
nosqlDB "github.com/smallstep/nosql"
|
||||||
|
"github.com/smallstep/nosql/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dbNonce contains nonce metadata used in the ACME protocol.
|
||||||
|
type dbNonce struct {
|
||||||
|
ID string
|
||||||
|
Created time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNonce creates, stores, and returns an ACME replay-nonce.
|
||||||
|
// Implements the acme.DB interface.
|
||||||
|
func (db *DB) CreateNonce() (Nonce, error) {
|
||||||
|
_id, err := randID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id := base64.RawURLEncoding.EncodeToString([]byte(_id))
|
||||||
|
n := &dbNonce{
|
||||||
|
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 Nonce(id), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteNonce 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 (db *DB) DeleteNonce(nonce string) error {
|
||||||
|
err := db.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 nosqlDB.IsErrNotFound(err):
|
||||||
|
return BadNonceErr(nil)
|
||||||
|
case err != nil:
|
||||||
|
return ServerInternalErr(errors.Wrapf(err, "error deleting nonce %s", nonce))
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
10
acme/db/nosql/nosql.go
Normal file
10
acme/db/nosql/nosql.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package nosql
|
||||||
|
|
||||||
|
import (
|
||||||
|
nosqlDB "github.com/smallstep/nosql"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DB is a struct that implements the AcmeDB interface.
|
||||||
|
type DB struct {
|
||||||
|
db nosqlDB.DB
|
||||||
|
}
|
0
acme/db/nosql/order.go
Normal file
0
acme/db/nosql/order.go
Normal file
66
acme/types/account.go
Normal file
66
acme/types/account.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountUpdateOptions are the options needed to update an existing ACME account.
|
||||||
|
type AccountUpdateOptions struct {
|
||||||
|
Contact []string
|
||||||
|
Status types.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
//}
|
32
acme/types/authz.go
Normal file
32
acme/types/authz.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Authz is a subset of the Authz type containing only those attributes
|
||||||
|
// required for responses in the ACME protocol.
|
||||||
|
type Authz struct {
|
||||||
|
Identifier Identifier `json:"identifier"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Expires string `json:"expires"`
|
||||||
|
Challenges []*Challenge `json:"challenges"`
|
||||||
|
Wildcard bool `json:"wildcard"`
|
||||||
|
ID string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToLog enables response logging.
|
||||||
|
func (a *Authz) ToLog() (interface{}, error) {
|
||||||
|
b, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ServerInternalErr(errors.Wrap(err, "error marshaling authz for logging"))
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetID returns the Authz ID.
|
||||||
|
func (a *Authz) GetID() string {
|
||||||
|
return a.ID
|
||||||
|
}
|
3
acme/types/nonce.go
Normal file
3
acme/types/nonce.go
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package acme
|
||||||
|
|
||||||
|
type Nonce string
|
Loading…
Reference in a new issue