Skip to content

Commit

Permalink
[acme db interface] wip
Browse files Browse the repository at this point in the history
  • Loading branch information
dopey committed Mar 25, 2021
1 parent 0368957 commit 461bad3
Show file tree
Hide file tree
Showing 19 changed files with 565 additions and 1,249 deletions.
File renamed without changes.
83 changes: 40 additions & 43 deletions acme/authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"log"
"net"
"net/http"
"net/url"
Expand Down Expand Up @@ -69,17 +70,6 @@ type AuthorityOptions struct {
Prefix string
}

var (
accountTable = []byte("acme_accounts")
accountByKeyIDTable = []byte("acme_keyID_accountID_index")
authzTable = []byte("acme_authzs")
challengeTable = []byte("acme_challenges")
nonceTable = []byte("nonces")
orderTable = []byte("acme_orders")
ordersByAccountIDTable = []byte("acme_account_orders_index")
certTable = []byte("acme_certs")
)

// NewAuthority returns a new Authority that implements the ACME interface.
//
// Deprecated: NewAuthority exists for hitorical compatibility and should not
Expand Down Expand Up @@ -197,14 +187,23 @@ func (a *Authority) GetAccountByKey(ctx context.Context, jwk *jose.JSONWebKey) (

// GetOrder returns an ACME order.
func (a *Authority) GetOrder(ctx context.Context, accID, orderID string) (*Order, error) {
o, err := getOrder(a.db, orderID)
prov, err := ProvisionerFromContext(ctx)
if err != nil {
return nil, err
}
o, err := a.db.GetOrder(ctx, orderID)
if err != nil {
return nil, ServerInternalErr(err)
}
if accID != o.AccountID {
log.Printf("account-id from request ('%s') does not match order account-id ('%s')", accID, o.AccountID)
return nil, UnauthorizedErr(errors.New("account does not own order"))
}
if o, err = o.updateStatus(a.db); err != nil {
if prov.GetID() != o.ProvisionerID {
log.Printf("provisioner-id from request ('%s') does not match order provisioner-id ('%s')", prov.GetID(), o.ProvisionerID)
return nil, UnauthorizedErr(errors.New("provisioner does not own order"))
}
if err = a.updateOrderStatus(ctx, o); err != nil {
return nil, err
}
return o.toACME(ctx, a.db, a.dir)
Expand Down Expand Up @@ -234,13 +233,15 @@ func (a *Authority) NewOrder(ctx context.Context, ops OrderOptions) (*Order, err
if err != nil {
return nil, err
}
ops.backdate = a.backdate.Duration
ops.defaultDuration = prov.DefaultTLSCertDuration()
order, err := newOrder(a.db, ops)
if err != nil {
return nil, Wrap(err, "error creating order")
}
return order.toACME(ctx, a.db, a.dir)
return db.CreateOrder(ctx, &Order{
AccountID: ops.AccountID,
ProvisionerID: prov.GetID(),
Backdate: a.backdate.Duration,
DefaultDuration: prov.DefaultTLSCertDuration(),
Identifiers: ops.Identifiers,
NotBefore: ops.NotBefore,
NotAfter: ops.NotAfter,
})
}

// FinalizeOrder attempts to finalize an order and generate a new certificate.
Expand All @@ -249,44 +250,51 @@ func (a *Authority) FinalizeOrder(ctx context.Context, accID, orderID string, cs
if err != nil {
return nil, err
}
o, err := getOrder(a.db, orderID)
o, err := a.db.GetOrder(ctx, orderID)
if err != nil {
return nil, err
}
if accID != o.AccountID {
log.Printf("account-id from request ('%s') does not match order account-id ('%s')", accID, o.AccountID)
return nil, UnauthorizedErr(errors.New("account does not own order"))
}
o, err = o.finalize(a.db, csr, a.signAuth, prov)
if prov.GetID() != o.ProvisionerID {
log.Printf("provisioner-id from request ('%s') does not match order provisioner-id ('%s')", prov.GetID(), o.ProvisionerID)
return nil, UnauthorizedErr(errors.New("provisioner does not own order"))
}
o, err = o.Finalize(ctx, a.db, csr, a.signAuth, prov)
if err != nil {
return nil, Wrap(err, "error finalizing order")
}
return o.toACME(ctx, a.db, a.dir)
return o, nil
}

// GetAuthz retrieves and attempts to update the status on an ACME authz
// before returning.
func (a *Authority) GetAuthz(ctx context.Context, accID, authzID string) (*Authz, error) {
az, err := getAuthz(a.db, authzID)
az, err := a.db.GetAuthorization(ctx, authzID)
if err != nil {
return nil, err
}
if accID != az.getAccountID() {
if accID != az.AccountID {
log.Printf("account-id from request ('%s') does not match authz account-id ('%s')", accID, az.AccountID)
return nil, UnauthorizedErr(errors.New("account does not own authz"))
}
az, err = az.updateStatus(a.db)
az, err = az.UpdateStatus(ctx, a.db)
if err != nil {
return nil, Wrap(err, "error updating authz status")
}
return az.toACME(ctx, a.db, a.dir)
return az, nil
}

// ValidateChallenge attempts to validate the challenge.
func (a *Authority) ValidateChallenge(ctx context.Context, accID, chID string, jwk *jose.JSONWebKey) (*Challenge, error) {
ch, err := getChallenge(a.db, chID)
ch, err := a.db.GetChallenge(ctx, chID, "todo")
if err != nil {
return nil, err
}
if accID != ch.getAccountID() {
if accID != ch.AccountID {
log.Printf("account-id from request ('%s') does not match challenge account-id ('%s')", accID, ch.AccountID)
return nil, UnauthorizedErr(errors.New("account does not own challenge"))
}
client := http.Client{
Expand All @@ -295,17 +303,16 @@ func (a *Authority) ValidateChallenge(ctx context.Context, accID, chID string, j
dialer := &net.Dialer{
Timeout: 30 * time.Second,
}
ch, err = ch.validate(a.db, jwk, validateOptions{
if err = ch.Validate(ctx, a.db, jwk, validateOptions{
httpGet: client.Get,
lookupTxt: net.LookupTXT,
tlsDial: func(network, addr string, config *tls.Config) (*tls.Conn, error) {
return tls.DialWithDialer(dialer, network, addr, config)
},
})
if err != nil {
}); err != nil {
return nil, Wrap(err, "error attempting challenge validation")
}
return ch.toACME(ctx, a.db, a.dir)
return ch, nil
}

// GetCertificate retrieves the Certificate by ID.
Expand All @@ -319,13 +326,3 @@ func (a *Authority) GetCertificate(accID, certID string) ([]byte, error) {
}
return cert.toACME(a.db, a.dir)
}

type httpGetter func(string) (*http.Response, error)
type lookupTxt func(string) ([]string, error)
type tlsDialer func(network, addr string, config *tls.Config) (*tls.Conn, error)

type validateOptions struct {
httpGet httpGetter
lookupTxt lookupTxt
tlsDial tlsDialer
}
75 changes: 75 additions & 0 deletions acme/authorization.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package types

import (
"context"
"encoding/json"
"time"

"github.com/pkg/errors"
)

// Authorization representst an ACME Authorization.
type Authorization struct {
Identifier *Identifier `json:"identifier"`
Status string `json:"status"`
Expires string `json:"expires"`
Challenges []*Challenge `json:"challenges"`
Wildcard bool `json:"wildcard"`
ID string `json:"-"`
AccountID string `json:"-"`
}

// ToLog enables response logging.
func (az *Authorization) ToLog() (interface{}, error) {
b, err := json.Marshal(az)
if err != nil {
return nil, ServerInternalErr(errors.Wrap(err, "error marshaling authz for logging"))
}
return string(b), nil
}

// UpdateStatus updates the ACME Authorization Status if necessary.
// Changes to the Authorization are saved using the database interface.
func (az *Authorization) UpdateStatus(ctx context.Context, db DB) error {
now := time.Now().UTC()
expiry, err := time.Parse(time.RFC3339, az.Expires)
if err != nil {
return ServerInternalErr(errors.Wrap("error converting expiry string to time"))
}

switch az.Status {
case StatusInvalid:
return nil
case StatusValid:
return nil
case StatusPending:
// check expiry
if now.After(expiry) {
az.Status = StatusInvalid
az.Error = MalformedErr(errors.New("authz has expired"))
break
}

var isValid = false
for _, chID := range ba.Challenges {
ch, err := db.GetChallenge(ctx, chID, az.ID)
if err != nil {
return ServerInternalErr(err)
}
if ch.Status == StatusValid {
isValid = true
break
}
}

if !isValid {
return nil
}
az.Status = StatusValid
az.Error = nil
default:
return nil, ServerInternalErr(errors.Errorf("unrecognized authz status: %s", ba.Status))
}

return ServerInternalErr(db.UpdateAuthorization(ctx, az))
}

0 comments on commit 461bad3

Please sign in to comment.