[acme db interface] wip
This commit is contained in:
parent
03ba229bcb
commit
1135ae04fc
12 changed files with 251 additions and 222 deletions
|
@ -21,7 +21,7 @@ type NewAccountRequest struct {
|
||||||
func validateContacts(cs []string) error {
|
func validateContacts(cs []string) error {
|
||||||
for _, c := range cs {
|
for _, c := range cs {
|
||||||
if len(c) == 0 {
|
if len(c) == 0 {
|
||||||
return acme.MalformedErr(errors.New("contact cannot be empty string"))
|
return acme.NewError(acme.ErrorMalformedType, "contact cannot be empty string")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -30,7 +30,7 @@ func validateContacts(cs []string) error {
|
||||||
// Validate validates a new-account request body.
|
// Validate validates a new-account request body.
|
||||||
func (n *NewAccountRequest) Validate() error {
|
func (n *NewAccountRequest) Validate() error {
|
||||||
if n.OnlyReturnExisting && len(n.Contact) > 0 {
|
if n.OnlyReturnExisting && len(n.Contact) > 0 {
|
||||||
return acme.MalformedErr(errors.New("incompatible input; onlyReturnExisting must be alone"))
|
return acme.NewError(acme.ErrorMalformedType, "incompatible input; onlyReturnExisting must be alone")
|
||||||
}
|
}
|
||||||
return validateContacts(n.Contact)
|
return validateContacts(n.Contact)
|
||||||
}
|
}
|
||||||
|
@ -51,8 +51,8 @@ func (u *UpdateAccountRequest) IsDeactivateRequest() bool {
|
||||||
func (u *UpdateAccountRequest) Validate() error {
|
func (u *UpdateAccountRequest) Validate() error {
|
||||||
switch {
|
switch {
|
||||||
case len(u.Status) > 0 && len(u.Contact) > 0:
|
case len(u.Status) > 0 && len(u.Contact) > 0:
|
||||||
return acme.MalformedErr(errors.New("incompatible input; contact and " +
|
return acme.NewError(acme.ErrorMalformedType, "incompatible input; contact and "+
|
||||||
"status updates are mutually exclusive"))
|
"status updates are mutually exclusive")
|
||||||
case len(u.Contact) > 0:
|
case len(u.Contact) > 0:
|
||||||
if err := validateContacts(u.Contact); err != nil {
|
if err := validateContacts(u.Contact); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -60,8 +60,8 @@ func (u *UpdateAccountRequest) Validate() error {
|
||||||
return nil
|
return nil
|
||||||
case len(u.Status) > 0:
|
case len(u.Status) > 0:
|
||||||
if u.Status != string(acme.StatusDeactivated) {
|
if u.Status != string(acme.StatusDeactivated) {
|
||||||
return acme.MalformedErr(errors.Errorf("cannot update account "+
|
return acme.NewError(acme.ErrorMalformedType, "cannot update account "+
|
||||||
"status to %s, only deactivated", u.Status))
|
"status to %s, only deactivated", u.Status)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
|
@ -80,8 +80,8 @@ func (h *Handler) NewAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
var nar NewAccountRequest
|
var nar NewAccountRequest
|
||||||
if err := json.Unmarshal(payload.value, &nar); err != nil {
|
if err := json.Unmarshal(payload.value, &nar); err != nil {
|
||||||
api.WriteError(w, acme.MalformedErr(errors.Wrap(err,
|
api.WriteError(w, acme.ErrorWrap(acme.ErrorMalformedType, err,
|
||||||
"failed to unmarshal new-account request payload")))
|
"failed to unmarshal new-account request payload"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := nar.Validate(); err != nil {
|
if err := nar.Validate(); err != nil {
|
||||||
|
@ -101,7 +101,8 @@ func (h *Handler) NewAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Account does not exist //
|
// Account does not exist //
|
||||||
if nar.OnlyReturnExisting {
|
if nar.OnlyReturnExisting {
|
||||||
api.WriteError(w, acme.AccountDoesNotExistErr(nil))
|
api.WriteError(w, acme.NewError(acme.ErrorAccountDoesNotExistType,
|
||||||
|
"account does not exist"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jwk, err := acme.JwkFromContext(r.Context())
|
jwk, err := acme.JwkFromContext(r.Context())
|
||||||
|
@ -146,7 +147,8 @@ func (h *Handler) GetUpdateAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
if !payload.isPostAsGet {
|
if !payload.isPostAsGet {
|
||||||
var uar UpdateAccountRequest
|
var uar UpdateAccountRequest
|
||||||
if err := json.Unmarshal(payload.value, &uar); err != nil {
|
if err := json.Unmarshal(payload.value, &uar); err != nil {
|
||||||
api.WriteError(w, acme.MalformedErr(errors.Wrap(err, "failed to unmarshal new-account request payload")))
|
api.WriteError(w, acme.ErrorWrap(acme.ErrorMalformedType, err,
|
||||||
|
"failed to unmarshal new-account request payload"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := uar.Validate(); err != nil {
|
if err := uar.Validate(); err != nil {
|
||||||
|
|
|
@ -23,11 +23,11 @@ type NewOrderRequest struct {
|
||||||
// Validate validates a new-order request body.
|
// Validate validates a new-order request body.
|
||||||
func (n *NewOrderRequest) Validate() error {
|
func (n *NewOrderRequest) Validate() error {
|
||||||
if len(n.Identifiers) == 0 {
|
if len(n.Identifiers) == 0 {
|
||||||
return acme.MalformedErr(errors.Errorf("identifiers list cannot be empty"))
|
return acme.NewError(ErrorMalformedType, "identifiers list cannot be empty")
|
||||||
}
|
}
|
||||||
for _, id := range n.Identifiers {
|
for _, id := range n.Identifiers {
|
||||||
if id.Type != "dns" {
|
if id.Type != "dns" {
|
||||||
return acme.MalformedErr(errors.Errorf("identifier type unsupported: %s", id.Type))
|
return acme.NewError(ErrorMalformedType, "identifier type unsupported: %s", id.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -8,10 +8,12 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
"go.step.sm/crypto/jose"
|
"go.step.sm/crypto/jose"
|
||||||
|
"go.step.sm/crypto/randutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Interface is the acme authority interface.
|
// Interface is the acme authority interface.
|
||||||
|
@ -124,7 +126,7 @@ func (a *Authority) UseNonce(ctx context.Context, nonce string) error {
|
||||||
// NewAccount creates, stores, and returns a new ACME account.
|
// NewAccount creates, stores, and returns a new ACME account.
|
||||||
func (a *Authority) NewAccount(ctx context.Context, acc *Account) error {
|
func (a *Authority) NewAccount(ctx context.Context, acc *Account) error {
|
||||||
if err := a.db.CreateAccount(ctx, acc); err != nil {
|
if err := a.db.CreateAccount(ctx, acc); err != nil {
|
||||||
return ErrorWrap(ErrorServerInternalType, err, "error creating account")
|
return ErrorISEWrap(err, "error creating account")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -136,7 +138,7 @@ func (a *Authority) UpdateAccount(ctx context.Context, acc *Account) (*Account,
|
||||||
acc.Status = auo.Status
|
acc.Status = auo.Status
|
||||||
*/
|
*/
|
||||||
if err := a.db.UpdateAccount(ctx, acc); err != nil {
|
if err := a.db.UpdateAccount(ctx, acc); err != nil {
|
||||||
return nil, ErrorWrap(ErrorServerInternalType, err, "error updating account")
|
return nil, ErrorISEWrap(err, "error updating account")
|
||||||
}
|
}
|
||||||
return acc, nil
|
return acc, nil
|
||||||
}
|
}
|
||||||
|
@ -145,7 +147,7 @@ func (a *Authority) UpdateAccount(ctx context.Context, acc *Account) (*Account,
|
||||||
func (a *Authority) GetAccount(ctx context.Context, id string) (*Account, error) {
|
func (a *Authority) GetAccount(ctx context.Context, id string) (*Account, error) {
|
||||||
acc, err := a.db.GetAccount(ctx, id)
|
acc, err := a.db.GetAccount(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrorWrap(ErrorServerInternalType, err, "error retrieving account")
|
return nil, ErrorISEWrap(err, "error retrieving account")
|
||||||
}
|
}
|
||||||
return acc, nil
|
return acc, nil
|
||||||
}
|
}
|
||||||
|
@ -168,7 +170,7 @@ func (a *Authority) GetOrder(ctx context.Context, accID, orderID string) (*Order
|
||||||
}
|
}
|
||||||
o, err := a.db.GetOrder(ctx, orderID)
|
o, err := a.db.GetOrder(ctx, orderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrorWrap(ErrorServerInternalType, err, "error retrieving order")
|
return nil, ErrorISEWrap(err, "error retrieving order")
|
||||||
}
|
}
|
||||||
if accID != o.AccountID {
|
if accID != o.AccountID {
|
||||||
log.Printf("account-id from request ('%s') does not match order account-id ('%s')", accID, o.AccountID)
|
log.Printf("account-id from request ('%s') does not match order account-id ('%s')", accID, o.AccountID)
|
||||||
|
@ -179,7 +181,7 @@ func (a *Authority) GetOrder(ctx context.Context, accID, orderID string) (*Order
|
||||||
return nil, NewError(ErrorUnauthorizedType, "provisioner does not own order")
|
return nil, NewError(ErrorUnauthorizedType, "provisioner does not own order")
|
||||||
}
|
}
|
||||||
if err = o.UpdateStatus(ctx, a.db); err != nil {
|
if err = o.UpdateStatus(ctx, a.db); err != nil {
|
||||||
return nil, ErrorWrap(ErrorServerInternalType, err, "error updating order")
|
return nil, ErrorISEWrap(err, "error updating order")
|
||||||
}
|
}
|
||||||
return o, nil
|
return o, nil
|
||||||
}
|
}
|
||||||
|
@ -205,11 +207,45 @@ func (a *Authority) GetOrdersByAccount(ctx context.Context, id string) ([]string
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// NewOrder generates, stores, and returns a new ACME order.
|
// NewOrder generates, stores, and returns a new ACME order.
|
||||||
func (a *Authority) NewOrder(ctx context.Context, o *Order) (*Order, error) {
|
func (a *Authority) NewOrder(ctx context.Context, o *Order) error {
|
||||||
prov, err := ProvisionerFromContext(ctx)
|
if len(o.AccountID) == 0 {
|
||||||
if err != nil {
|
return NewErrorISE("account-id cannot be empty")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
if len(o.ProvisionerID) == 0 {
|
||||||
|
return NewErrorISE("provisioner-id cannot be empty")
|
||||||
|
}
|
||||||
|
if len(o.Identifiers) == 0 {
|
||||||
|
return NewErrorISE("identifiers cannot be empty")
|
||||||
|
}
|
||||||
|
if o.DefaultDuration == 0 {
|
||||||
|
return NewErrorISE("default-duration cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
o.AuthorizationIDs = make([]string, len(o.Identifiers))
|
||||||
|
for i, identifier := range o.Identifiers {
|
||||||
|
az := &Authorization{
|
||||||
|
AccountID: o.AccountID,
|
||||||
|
Identifier: identifier,
|
||||||
|
}
|
||||||
|
if err := a.NewAuthorization(ctx, az); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.AuthorizationIDs[i] = az.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
now := clock.Now()
|
||||||
|
if o.NotBefore.IsZero() {
|
||||||
|
o.NotBefore = now
|
||||||
|
}
|
||||||
|
if o.NotAfter.IsZero() {
|
||||||
|
o.NotAfter = o.NotBefore.Add(o.DefaultDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.db.CreateOrder(ctx, o); err != nil {
|
||||||
|
return ErrorISEWrap(err, "error creating order")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
/*
|
||||||
o.DefaultDuration = prov.DefaultTLSCertDuration()
|
o.DefaultDuration = prov.DefaultTLSCertDuration()
|
||||||
o.Backdate = a.backdate.Duration
|
o.Backdate = a.backdate.Duration
|
||||||
o.ProvisionerID = prov.GetID()
|
o.ProvisionerID = prov.GetID()
|
||||||
|
@ -218,6 +254,7 @@ func (a *Authority) NewOrder(ctx context.Context, o *Order) (*Order, error) {
|
||||||
return nil, ErrorWrap(ErrorServerInternalType, err, "error creating order")
|
return nil, ErrorWrap(ErrorServerInternalType, err, "error creating order")
|
||||||
}
|
}
|
||||||
return o, nil
|
return o, nil
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// FinalizeOrder attempts to finalize an order and generate a new certificate.
|
// FinalizeOrder attempts to finalize an order and generate a new certificate.
|
||||||
|
@ -228,7 +265,7 @@ func (a *Authority) FinalizeOrder(ctx context.Context, accID, orderID string, cs
|
||||||
}
|
}
|
||||||
o, err := a.db.GetOrder(ctx, orderID)
|
o, err := a.db.GetOrder(ctx, orderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrorWrap(ErrorServerInternalType, err, "error retrieving order")
|
return nil, ErrorISEWrap(err, "error retrieving order")
|
||||||
}
|
}
|
||||||
if accID != o.AccountID {
|
if accID != o.AccountID {
|
||||||
log.Printf("account-id from request ('%s') does not match order account-id ('%s')", accID, o.AccountID)
|
log.Printf("account-id from request ('%s') does not match order account-id ('%s')", accID, o.AccountID)
|
||||||
|
@ -239,33 +276,113 @@ func (a *Authority) FinalizeOrder(ctx context.Context, accID, orderID string, cs
|
||||||
return nil, NewError(ErrorUnauthorizedType, "provisioner does not own order")
|
return nil, NewError(ErrorUnauthorizedType, "provisioner does not own order")
|
||||||
}
|
}
|
||||||
if err = o.Finalize(ctx, a.db, csr, a.signAuth, prov); err != nil {
|
if err = o.Finalize(ctx, a.db, csr, a.signAuth, prov); err != nil {
|
||||||
return nil, ErrorWrap(ErrorServerInternalType, err, "error finalizing order")
|
return nil, ErrorISEWrap(err, "error finalizing order")
|
||||||
}
|
}
|
||||||
return o, nil
|
return o, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAuthz retrieves and attempts to update the status on an ACME authz
|
// NewAuthorization generates and stores an ACME Authorization type along with
|
||||||
|
// any associated resources.
|
||||||
|
func (a *Authority) NewAuthorization(ctx context.Context, az *Authorization) error {
|
||||||
|
if len(az.AccountID) == 0 {
|
||||||
|
return NewErrorISE("account-id cannot be empty")
|
||||||
|
}
|
||||||
|
if len(az.Identifier.Value) == 0 {
|
||||||
|
return NewErrorISE("identifier cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(az.Identifier.Value, "*.") {
|
||||||
|
az.Wildcard = true
|
||||||
|
az.Identifier = Identifier{
|
||||||
|
Value: strings.TrimPrefix(az.Identifier.Value, "*."),
|
||||||
|
Type: az.Identifier.Type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
chTypes = []string{"dns-01"}
|
||||||
|
)
|
||||||
|
// HTTP and TLS challenges can only be used for identifiers without wildcards.
|
||||||
|
if !az.Wildcard {
|
||||||
|
chTypes = append(chTypes, []string{"http-01", "tls-alpn-01"}...)
|
||||||
|
}
|
||||||
|
|
||||||
|
az.Token, err = randutil.Alphanumeric(32)
|
||||||
|
if err != nil {
|
||||||
|
return ErrorISEWrap(err, "error generating random alphanumeric ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
az.Challenges = make([]*Challenge, len(chTypes))
|
||||||
|
for i, typ := range chTypes {
|
||||||
|
ch := &Challenge{
|
||||||
|
AccountID: az.AccountID,
|
||||||
|
AuthzID: az.ID,
|
||||||
|
Value: az.Identifier.Value,
|
||||||
|
Type: typ,
|
||||||
|
Token: az.Token,
|
||||||
|
}
|
||||||
|
if err := a.NewChallenge(ctx, ch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
az.Challenges[i] = ch
|
||||||
|
}
|
||||||
|
if err = a.db.CreateAuthorization(ctx, az); err != nil {
|
||||||
|
return ErrorISEWrap(err, "error creating authorization")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthorization retrieves and attempts to update the status on an ACME authz
|
||||||
// before returning.
|
// before returning.
|
||||||
func (a *Authority) GetAuthz(ctx context.Context, accID, authzID string) (*Authorization, error) {
|
func (a *Authority) GetAuthorization(ctx context.Context, accID, authzID string) (*Authorization, error) {
|
||||||
az, err := a.db.GetAuthorization(ctx, authzID)
|
az, err := a.db.GetAuthorization(ctx, authzID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrorWrap(ErrorServerInternalType, err, "error retrieving authorization")
|
return nil, ErrorISEWrap(err, "error retrieving authorization")
|
||||||
}
|
}
|
||||||
if accID != az.AccountID {
|
if accID != az.AccountID {
|
||||||
log.Printf("account-id from request ('%s') does not match authz account-id ('%s')", accID, az.AccountID)
|
log.Printf("account-id from request ('%s') does not match authz account-id ('%s')", accID, az.AccountID)
|
||||||
return nil, NewError(ErrorUnauthorizedType, "account does not own order")
|
return nil, NewError(ErrorUnauthorizedType, "account does not own order")
|
||||||
}
|
}
|
||||||
if err = az.UpdateStatus(ctx, a.db); err != nil {
|
if err = az.UpdateStatus(ctx, a.db); err != nil {
|
||||||
return nil, ErrorWrap(ErrorServerInternalType, err, "error updating authorization status")
|
return nil, ErrorISEWrap(err, "error updating authorization status")
|
||||||
}
|
}
|
||||||
return az, nil
|
return az, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateChallenge attempts to validate the challenge.
|
// NewChallenge generates and stores an ACME challenge and associated resources.
|
||||||
func (a *Authority) ValidateChallenge(ctx context.Context, accID, chID string, jwk *jose.JSONWebKey) (*Challenge, error) {
|
func (a *Authority) NewChallenge(ctx context.Context, ch *Challenge) error {
|
||||||
|
if len(ch.AccountID) == 0 {
|
||||||
|
return NewErrorISE("account-id cannot be empty")
|
||||||
|
}
|
||||||
|
if len(ch.AuthzID) == 0 {
|
||||||
|
return NewErrorISE("authz-id cannot be empty")
|
||||||
|
}
|
||||||
|
if len(ch.Token) == 0 {
|
||||||
|
return NewErrorISE("token cannot be empty")
|
||||||
|
}
|
||||||
|
if len(ch.Value) == 0 {
|
||||||
|
return NewErrorISE("value cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ch.Type {
|
||||||
|
case "dns-01", "http-01", "tls-alpn-01":
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return NewErrorISE("unexpected error type '%s'", ch.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.db.CreateChallenge(ctx, ch); err != nil {
|
||||||
|
return ErrorISEWrap(err, "error creating challenge")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValidateChallenge attempts to validate the challenge.
|
||||||
|
func (a *Authority) GetValidateChallenge(ctx context.Context, accID, chID, azID string, jwk *jose.JSONWebKey) (*Challenge, error) {
|
||||||
ch, err := a.db.GetChallenge(ctx, chID, "todo")
|
ch, err := a.db.GetChallenge(ctx, chID, "todo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrorWrap(ErrorServerInternalType, err, "error retrieving challenge")
|
return nil, ErrorISEWrap(err, "error retrieving challenge")
|
||||||
}
|
}
|
||||||
if accID != ch.AccountID {
|
if accID != ch.AccountID {
|
||||||
log.Printf("account-id from request ('%s') does not match challenge account-id ('%s')", accID, ch.AccountID)
|
log.Printf("account-id from request ('%s') does not match challenge account-id ('%s')", accID, ch.AccountID)
|
||||||
|
@ -284,7 +401,7 @@ func (a *Authority) ValidateChallenge(ctx context.Context, accID, chID string, j
|
||||||
return tls.DialWithDialer(dialer, network, addr, config)
|
return tls.DialWithDialer(dialer, network, addr, config)
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, ErrorWrap(ErrorServerInternalType, err, "error validating challenge")
|
return nil, ErrorISEWrap(err, "error validating challenge")
|
||||||
}
|
}
|
||||||
return ch, nil
|
return ch, nil
|
||||||
}
|
}
|
||||||
|
@ -293,7 +410,7 @@ func (a *Authority) ValidateChallenge(ctx context.Context, accID, chID string, j
|
||||||
func (a *Authority) GetCertificate(ctx context.Context, accID, certID string) ([]byte, error) {
|
func (a *Authority) GetCertificate(ctx context.Context, accID, certID string) ([]byte, error) {
|
||||||
cert, err := a.db.GetCertificate(ctx, certID)
|
cert, err := a.db.GetCertificate(ctx, certID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrorWrap(ErrorServerInternalType, err, "error retrieving certificate")
|
return nil, ErrorISEWrap(err, "error retrieving certificate")
|
||||||
}
|
}
|
||||||
if cert.AccountID != accID {
|
if cert.AccountID != accID {
|
||||||
log.Printf("account-id from request ('%s') does not match challenge account-id ('%s')", accID, cert.AccountID)
|
log.Printf("account-id from request ('%s') does not match challenge account-id ('%s')", accID, cert.AccountID)
|
||||||
|
|
|
@ -8,20 +8,22 @@ import (
|
||||||
|
|
||||||
// Authorization representst an ACME Authorization.
|
// Authorization representst an ACME Authorization.
|
||||||
type Authorization struct {
|
type Authorization struct {
|
||||||
Identifier *Identifier `json:"identifier"`
|
Identifier Identifier `json:"identifier"`
|
||||||
Status Status `json:"status"`
|
Status Status `json:"status"`
|
||||||
Expires string `json:"expires"`
|
Expires string `json:"expires"`
|
||||||
Challenges []*Challenge `json:"challenges"`
|
Challenges []*Challenge `json:"challenges"`
|
||||||
|
ChallengeIDs string `json::"-"`
|
||||||
Wildcard bool `json:"wildcard"`
|
Wildcard bool `json:"wildcard"`
|
||||||
ID string `json:"-"`
|
ID string `json:"-"`
|
||||||
AccountID string `json:"-"`
|
AccountID string `json:"-"`
|
||||||
|
Token string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToLog enables response logging.
|
// ToLog enables response logging.
|
||||||
func (az *Authorization) ToLog() (interface{}, error) {
|
func (az *Authorization) ToLog() (interface{}, error) {
|
||||||
b, err := json.Marshal(az)
|
b, err := json.Marshal(az)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrorInternalServerWrap(err, "error marshaling authz for logging")
|
return nil, ErrorISEWrap(err, "error marshaling authz for logging")
|
||||||
}
|
}
|
||||||
return string(b), nil
|
return string(b), nil
|
||||||
}
|
}
|
||||||
|
@ -32,7 +34,7 @@ func (az *Authorization) UpdateStatus(ctx context.Context, db DB) error {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
expiry, err := time.Parse(time.RFC3339, az.Expires)
|
expiry, err := time.Parse(time.RFC3339, az.Expires)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorInternalServerWrap(err, "error converting expiry string to time")
|
return ErrorISEWrap(err, "error converting expiry string to time")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch az.Status {
|
switch az.Status {
|
||||||
|
@ -64,7 +66,7 @@ func (az *Authorization) UpdateStatus(ctx context.Context, db DB) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = db.UpdateAuthorization(ctx, az); err != nil {
|
if err = db.UpdateAuthorization(ctx, az); err != nil {
|
||||||
return ErrorInternalServerWrap(err, "error updating authorization")
|
return ErrorISEWrap(err, "error updating authorization")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ type Challenge struct {
|
||||||
func (ch *Challenge) ToLog() (interface{}, error) {
|
func (ch *Challenge) ToLog() (interface{}, error) {
|
||||||
b, err := json.Marshal(ch)
|
b, err := json.Marshal(ch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrorInternalServerWrap(err, "error marshaling challenge for logging")
|
return nil, ErrorISEWrap(err, "error marshaling challenge for logging")
|
||||||
}
|
}
|
||||||
return string(b), nil
|
return string(b), nil
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorInternalServerWrap(err, "error reading "+
|
return ErrorISEWrap(err, "error reading "+
|
||||||
"response body for url %s", url)
|
"response body for url %s", url)
|
||||||
}
|
}
|
||||||
keyAuth := strings.Trim(string(body), "\r\n")
|
keyAuth := strings.Trim(string(body), "\r\n")
|
||||||
|
@ -100,7 +100,7 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb
|
||||||
ch.Validated = clock.Now().Format(time.RFC3339)
|
ch.Validated = clock.Now().Format(time.RFC3339)
|
||||||
|
|
||||||
if err = db.UpdateChallenge(ctx, ch); err != nil {
|
if err = db.UpdateChallenge(ctx, ch); err != nil {
|
||||||
return ErrorInternalServerWrap(err, "error updating challenge")
|
return ErrorISEWrap(err, "error updating challenge")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -178,7 +178,7 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON
|
||||||
ch.Validated = clock.Now().Format(time.RFC3339)
|
ch.Validated = clock.Now().Format(time.RFC3339)
|
||||||
|
|
||||||
if err = db.UpdateChallenge(ctx, ch); err != nil {
|
if err = db.UpdateChallenge(ctx, ch); err != nil {
|
||||||
return ErrorInternalServerWrap(err, "tlsalpn01ValidateChallenge - error updating challenge")
|
return ErrorISEWrap(err, "tlsalpn01ValidateChallenge - error updating challenge")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,7 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK
|
||||||
ch.Validated = clock.Now().UTC().Format(time.RFC3339)
|
ch.Validated = clock.Now().UTC().Format(time.RFC3339)
|
||||||
|
|
||||||
if err = db.UpdateChallenge(ctx, ch); err != nil {
|
if err = db.UpdateChallenge(ctx, ch); err != nil {
|
||||||
return ErrorInternalServerWrap(err, "error updating challenge")
|
return ErrorISEWrap(err, "error updating challenge")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -244,7 +244,7 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK
|
||||||
func KeyAuthorization(token string, jwk *jose.JSONWebKey) (string, error) {
|
func KeyAuthorization(token string, jwk *jose.JSONWebKey) (string, error) {
|
||||||
thumbprint, err := jwk.Thumbprint(crypto.SHA256)
|
thumbprint, err := jwk.Thumbprint(crypto.SHA256)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", ErrorInternalServerWrap(err, "error generating JWK thumbprint")
|
return "", ErrorISEWrap(err, "error generating JWK thumbprint")
|
||||||
}
|
}
|
||||||
encPrint := base64.RawURLEncoding.EncodeToString(thumbprint)
|
encPrint := base64.RawURLEncoding.EncodeToString(thumbprint)
|
||||||
return fmt.Sprintf("%s.%s", token, encPrint), nil
|
return fmt.Sprintf("%s.%s", token, encPrint), nil
|
||||||
|
@ -254,7 +254,7 @@ func KeyAuthorization(token string, jwk *jose.JSONWebKey) (string, error) {
|
||||||
func storeError(ctx context.Context, ch *Challenge, db DB, err *Error) error {
|
func storeError(ctx context.Context, ch *Challenge, db DB, err *Error) error {
|
||||||
ch.Error = err
|
ch.Error = err
|
||||||
if err := db.UpdateChallenge(ctx, ch); err != nil {
|
if err := db.UpdateChallenge(ctx, ch); err != nil {
|
||||||
return ErrorInternalServerWrap(err, "failure saving error to acme challenge")
|
return ErrorISEWrap(err, "failure saving error to acme challenge")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package nosql
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -75,18 +74,17 @@ func (db *DB) GetAuthorization(ctx context.Context, id string) (*acme.Authorizat
|
||||||
// CreateAuthorization creates an entry in the database for the Authorization.
|
// CreateAuthorization creates an entry in the database for the Authorization.
|
||||||
// Implements the acme.DB.CreateAuthorization interface.
|
// Implements the acme.DB.CreateAuthorization interface.
|
||||||
func (db *DB) CreateAuthorization(ctx context.Context, az *acme.Authorization) error {
|
func (db *DB) CreateAuthorization(ctx context.Context, az *acme.Authorization) error {
|
||||||
if len(az.AccountID) == 0 {
|
|
||||||
return errors.New("account-id cannot be empty")
|
|
||||||
}
|
|
||||||
if az.Identifier == nil {
|
|
||||||
return errors.New("identifier cannot be nil")
|
|
||||||
}
|
|
||||||
var err error
|
var err error
|
||||||
az.ID, err = randID()
|
az.ID, err = randID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chIDs := make([]string, len(az.Challenges))
|
||||||
|
for i, ch := range az.Challenges {
|
||||||
|
chIDs[i] = ch.ID
|
||||||
|
}
|
||||||
|
|
||||||
now := clock.Now()
|
now := clock.Now()
|
||||||
dbaz := &dbAuthz{
|
dbaz := &dbAuthz{
|
||||||
ID: az.ID,
|
ID: az.ID,
|
||||||
|
@ -95,38 +93,10 @@ func (db *DB) CreateAuthorization(ctx context.Context, az *acme.Authorization) e
|
||||||
Created: now,
|
Created: now,
|
||||||
Expires: now.Add(defaultExpiryDuration),
|
Expires: now.Add(defaultExpiryDuration),
|
||||||
Identifier: az.Identifier,
|
Identifier: az.Identifier,
|
||||||
|
Challenges: chIDs,
|
||||||
|
Wildcard: az.Wildcard,
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(az.Identifier.Value, "*.") {
|
|
||||||
dbaz.Wildcard = true
|
|
||||||
dbaz.Identifier = &acme.Identifier{
|
|
||||||
Value: strings.TrimPrefix(az.Identifier.Value, "*."),
|
|
||||||
Type: az.Identifier.Type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chIDs := []string{}
|
|
||||||
chTypes := []string{"dns-01"}
|
|
||||||
// HTTP and TLS challenges can only be used for identifiers without wildcards.
|
|
||||||
if !dbaz.Wildcard {
|
|
||||||
chTypes = append(chTypes, []string{"http-01", "tls-alpn-01"}...)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, typ := range chTypes {
|
|
||||||
ch := &acme.Challenge{
|
|
||||||
AccountID: az.AccountID,
|
|
||||||
AuthzID: az.ID,
|
|
||||||
Value: az.Identifier.Value,
|
|
||||||
Type: typ,
|
|
||||||
}
|
|
||||||
if err = db.CreateChallenge(ctx, ch); err != nil {
|
|
||||||
return errors.Wrapf(err, "error creating challenge")
|
|
||||||
}
|
|
||||||
|
|
||||||
chIDs = append(chIDs, ch.ID)
|
|
||||||
}
|
|
||||||
dbaz.Challenges = chIDs
|
|
||||||
|
|
||||||
return db.save(ctx, az.ID, dbaz, nil, "authz", authzTable)
|
return db.save(ctx, az.ID, dbaz, nil, "authz", authzTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,29 +47,11 @@ func (db *DB) getDBChallenge(ctx context.Context, id string) (*dbChallenge, erro
|
||||||
// CreateChallenge creates a new ACME challenge data structure in the database.
|
// CreateChallenge creates a new ACME challenge data structure in the database.
|
||||||
// Implements acme.DB.CreateChallenge interface.
|
// Implements acme.DB.CreateChallenge interface.
|
||||||
func (db *DB) CreateChallenge(ctx context.Context, ch *acme.Challenge) error {
|
func (db *DB) CreateChallenge(ctx context.Context, ch *acme.Challenge) error {
|
||||||
if len(ch.AuthzID) == 0 {
|
|
||||||
return errors.New("AuthzID cannot be empty")
|
|
||||||
}
|
|
||||||
if len(ch.AccountID) == 0 {
|
|
||||||
return errors.New("AccountID cannot be empty")
|
|
||||||
}
|
|
||||||
if len(ch.Value) == 0 {
|
|
||||||
return errors.New("AccountID cannot be empty")
|
|
||||||
}
|
|
||||||
// TODO: verify that challenge type is set and is one of expected types.
|
|
||||||
if len(ch.Type) == 0 {
|
|
||||||
return errors.New("Type cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
ch.ID, err = randID()
|
ch.ID, err = randID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "error generating random id for ACME challenge")
|
return errors.Wrap(err, "error generating random id for ACME challenge")
|
||||||
}
|
}
|
||||||
ch.Token, err = randID()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "error generating token for ACME challenge")
|
|
||||||
}
|
|
||||||
|
|
||||||
dbch := &dbChallenge{
|
dbch := &dbChallenge{
|
||||||
ID: ch.ID,
|
ID: ch.ID,
|
||||||
|
|
|
@ -74,48 +74,12 @@ func (db *DB) GetOrder(ctx context.Context, id string) (*acme.Order, error) {
|
||||||
|
|
||||||
// CreateOrder creates ACME Order resources and saves them to the DB.
|
// CreateOrder creates ACME Order resources and saves them to the DB.
|
||||||
func (db *DB) CreateOrder(ctx context.Context, o *acme.Order) error {
|
func (db *DB) CreateOrder(ctx context.Context, o *acme.Order) error {
|
||||||
if len(o.AccountID) == 0 {
|
|
||||||
return ServerInternalErr(errors.New("account-id cannot be empty"))
|
|
||||||
}
|
|
||||||
if len(o.ProvisionerID) == 0 {
|
|
||||||
return ServerInternalErr(errors.New("provisioner-id cannot be empty"))
|
|
||||||
}
|
|
||||||
if len(o.Identifiers) == 0 {
|
|
||||||
return ServerInternalErr(errors.New("identifiers cannot be empty"))
|
|
||||||
}
|
|
||||||
if o.DefaultDuration == 0 {
|
|
||||||
return ServerInternalErr(errors.New("default-duration cannot be empty"))
|
|
||||||
}
|
|
||||||
|
|
||||||
o.ID, err = randID()
|
o.ID, err = randID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
azIDs := make([]string, len(ops.Identifiers))
|
|
||||||
for i, identifier := range ops.Identifiers {
|
|
||||||
az, err = db.CreateAuthorzation(&types.Authorization{
|
|
||||||
AccountID: o.AccountID,
|
|
||||||
Identifier: o.Identifier,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
azIDs[i] = az.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
now := clock.Now()
|
now := clock.Now()
|
||||||
var backdate time.Duration
|
|
||||||
nbf := o.NotBefore
|
|
||||||
if nbf.IsZero() {
|
|
||||||
nbf = now
|
|
||||||
backdate = -1 * o.Backdate
|
|
||||||
}
|
|
||||||
naf := o.NotAfter
|
|
||||||
if naf.IsZero() {
|
|
||||||
naf = nbf.Add(o.DefaultDuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
dbo := &dbOrder{
|
dbo := &dbOrder{
|
||||||
ID: o.ID,
|
ID: o.ID,
|
||||||
AccountID: o.AccountID,
|
AccountID: o.AccountID,
|
||||||
|
@ -123,10 +87,10 @@ func (db *DB) CreateOrder(ctx context.Context, o *acme.Order) error {
|
||||||
Created: now,
|
Created: now,
|
||||||
Status: StatusPending,
|
Status: StatusPending,
|
||||||
Expires: now.Add(defaultOrderExpiry),
|
Expires: now.Add(defaultOrderExpiry),
|
||||||
Identifiers: ops.Identifiers,
|
Identifiers: o.Identifiers,
|
||||||
NotBefore: nbf.Add(backdate),
|
NotBefore: o.NotBefore,
|
||||||
NotAfter: naf,
|
NotAfter: o.NotBefore,
|
||||||
Authorizations: azIDs,
|
Authorizations: o.AuthorizationIDs,
|
||||||
}
|
}
|
||||||
if err := db.save(ctx, o.ID, dbo, nil, orderTable); err != nil {
|
if err := db.save(ctx, o.ID, dbo, nil, orderTable); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -21,7 +21,7 @@ type Directory struct {
|
||||||
func (d *Directory) ToLog() (interface{}, error) {
|
func (d *Directory) ToLog() (interface{}, error) {
|
||||||
b, err := json.Marshal(d)
|
b, err := json.Marshal(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrorInternalServerWrap(err, "error marshaling directory for logging")
|
return nil, ErrorISEWrap(err, "error marshaling directory for logging")
|
||||||
}
|
}
|
||||||
return string(b), nil
|
return string(b), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// Error represents an ACME
|
|
||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -11,55 +10,55 @@ import (
|
||||||
type ProblemType int
|
type ProblemType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// The request specified an account that does not exist
|
// ErrorAccountDoesNotExistType request specified an account that does not exist
|
||||||
ErrorAccountDoesNotExistType ProblemType = iota
|
ErrorAccountDoesNotExistType ProblemType = iota
|
||||||
// The request specified a certificate to be revoked that has already been revoked
|
// ErrorAlreadyRevokedType request specified a certificate to be revoked that has already been revoked
|
||||||
ErrorAlreadyRevokedType
|
ErrorAlreadyRevokedType
|
||||||
// The CSR is unacceptable (e.g., due to a short key)
|
// ErrorBadCSRType CSR is unacceptable (e.g., due to a short key)
|
||||||
ErrorBadCSRType
|
ErrorBadCSRType
|
||||||
// The client sent an unacceptable anti-replay nonce
|
// ErrorBadNonceType client sent an unacceptable anti-replay nonce
|
||||||
ErrorBadNonceType
|
ErrorBadNonceType
|
||||||
// The JWS was signed by a public key the server does not support
|
// ErrorBadPublicKeyType JWS was signed by a public key the server does not support
|
||||||
ErrorBadPublicKeyType
|
ErrorBadPublicKeyType
|
||||||
// The revocation reason provided is not allowed by the server
|
// ErrorBadRevocationReasonType revocation reason provided is not allowed by the server
|
||||||
ErrorBadRevocationReasonType
|
ErrorBadRevocationReasonType
|
||||||
// The JWS was signed with an algorithm the server does not support
|
// ErrorBadSignatureAlgorithmType JWS was signed with an algorithm the server does not support
|
||||||
ErrorBadSignatureAlgorithmType
|
ErrorBadSignatureAlgorithmType
|
||||||
// Certification Authority Authorization (CAA) records forbid the CA from issuing a certificate
|
// ErrorCaaType Authority Authorization (CAA) records forbid the CA from issuing a certificate
|
||||||
ErrorCaaType
|
ErrorCaaType
|
||||||
// Specific error conditions are indicated in the “subproblems” array.
|
// ErrorCompoundType error conditions are indicated in the “subproblems” array.
|
||||||
ErrorCompoundType
|
ErrorCompoundType
|
||||||
// The server could not connect to validation target
|
// ErrorConnectionType server could not connect to validation target
|
||||||
ErrorConnectionType
|
ErrorConnectionType
|
||||||
// There was a problem with a DNS query during identifier validation
|
// ErrorDNSType was a problem with a DNS query during identifier validation
|
||||||
ErrorDNSType
|
ErrorDNSType
|
||||||
// The request must include a value for the “externalAccountBinding” field
|
// ErrorExternalAccountRequiredType request must include a value for the “externalAccountBinding” field
|
||||||
ErrorExternalAccountRequiredType
|
ErrorExternalAccountRequiredType
|
||||||
// Response received didn’t match the challenge’s requirements
|
// ErrorIncorrectResponseType received didn’t match the challenge’s requirements
|
||||||
ErrorIncorrectResponseType
|
ErrorIncorrectResponseType
|
||||||
// A contact URL for an account was invalid
|
// ErrorInvalidContactType URL for an account was invalid
|
||||||
ErrorInvalidContactType
|
ErrorInvalidContactType
|
||||||
// The request message was malformed
|
// ErrorMalformedType request message was malformed
|
||||||
ErrorMalformedType
|
ErrorMalformedType
|
||||||
// The request attempted to finalize an order that is not ready to be finalized
|
// ErrorOrderNotReadyType request attempted to finalize an order that is not ready to be finalized
|
||||||
ErrorOrderNotReadyType
|
ErrorOrderNotReadyType
|
||||||
// The request exceeds a rate limit
|
// ErrorRateLimitedType request exceeds a rate limit
|
||||||
ErrorRateLimitedType
|
ErrorRateLimitedType
|
||||||
// The server will not issue certificates for the identifier
|
// ErrorRejectedIdentifierType server will not issue certificates for the identifier
|
||||||
ErrorRejectedIdentifierType
|
ErrorRejectedIdentifierType
|
||||||
// The server experienced an internal error
|
// ErrorServerInternalType server experienced an internal error
|
||||||
ErrorServerInternalType
|
ErrorServerInternalType
|
||||||
// The server received a TLS error during validation
|
// ErrorTLSType server received a TLS error during validation
|
||||||
ErrorTLSType
|
ErrorTLSType
|
||||||
// The client lacks sufficient authorization
|
// ErrorUnauthorizedType client lacks sufficient authorization
|
||||||
ErrorUnauthorizedType
|
ErrorUnauthorizedType
|
||||||
// A contact URL for an account used an unsupported protocol scheme
|
// ErrorUnsupportedContactType URL for an account used an unsupported protocol scheme
|
||||||
ErrorUnsupportedContactType
|
ErrorUnsupportedContactType
|
||||||
// An identifier is of an unsupported type
|
// ErrorUnsupportedIdentifierType identifier is of an unsupported type
|
||||||
ErrorUnsupportedIdentifierType
|
ErrorUnsupportedIdentifierType
|
||||||
// Visit the “instance” URL and take actions specified there
|
// ErrorUserActionRequiredType the “instance” URL and take actions specified there
|
||||||
ErrorUserActionRequiredType
|
ErrorUserActionRequiredType
|
||||||
// The operation is not implemented
|
// ErrorNotImplementedType operation is not implemented
|
||||||
ErrorNotImplementedType
|
ErrorNotImplementedType
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -116,7 +115,7 @@ func (ap ProblemType) String() string {
|
||||||
case ErrorNotImplementedType:
|
case ErrorNotImplementedType:
|
||||||
return "notImplemented"
|
return "notImplemented"
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("unsupported type ACME error type %v", ap)
|
return fmt.Sprintf("unsupported type ACME error type '%d'", int(ap))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,6 +269,7 @@ type Error struct {
|
||||||
Status int `json:"-"`
|
Status int `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewError creates a new Error type.
|
||||||
func NewError(pt ProblemType, msg string, args ...interface{}) *Error {
|
func NewError(pt ProblemType, msg string, args ...interface{}) *Error {
|
||||||
meta, ok := errorMap[pt]
|
meta, ok := errorMap[pt]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -290,6 +290,11 @@ func NewError(pt ProblemType, msg string, args ...interface{}) *Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewErrorISE creates a new ErrorServerInternalType Error.
|
||||||
|
func NewErrorISE(msg string, args ...interface{}) *Error {
|
||||||
|
return NewError(ErrorServerInternalType, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
// ErrorWrap attempts to wrap the internal error.
|
// ErrorWrap attempts to wrap the internal error.
|
||||||
func ErrorWrap(typ ProblemType, err error, msg string, args ...interface{}) *Error {
|
func ErrorWrap(typ ProblemType, err error, msg string, args ...interface{}) *Error {
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
|
@ -307,8 +312,8 @@ func ErrorWrap(typ ProblemType, err error, msg string, args ...interface{}) *Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorInternalServerWrap shortcut to wrap an internal server error type.
|
// ErrorISEWrap shortcut to wrap an internal server error type.
|
||||||
func ErrorInternalServerWrap(err error, msg string, args ...interface{}) *Error {
|
func ErrorISEWrap(err error, msg string, args ...interface{}) *Error {
|
||||||
return ErrorWrap(ErrorServerInternalType, err, msg, args...)
|
return ErrorWrap(ErrorServerInternalType, err, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,12 +21,13 @@ type Identifier struct {
|
||||||
// Order contains order metadata for the ACME protocol order type.
|
// Order contains order metadata for the ACME protocol order type.
|
||||||
type Order struct {
|
type Order struct {
|
||||||
Status Status `json:"status"`
|
Status Status `json:"status"`
|
||||||
Expires string `json:"expires,omitempty"`
|
Expires time.Time `json:"expires,omitempty"`
|
||||||
Identifiers []Identifier `json:"identifiers"`
|
Identifiers []Identifier `json:"identifiers"`
|
||||||
NotBefore string `json:"notBefore,omitempty"`
|
NotBefore time.Time `json:"notBefore,omitempty"`
|
||||||
NotAfter string `json:"notAfter,omitempty"`
|
NotAfter time.Time `json:"notAfter,omitempty"`
|
||||||
Error interface{} `json:"error,omitempty"`
|
Error interface{} `json:"error,omitempty"`
|
||||||
Authorizations []string `json:"authorizations"`
|
AuthorizationURLs []string `json:"authorizations"`
|
||||||
|
AuthorizationIDs []string `json:"-"`
|
||||||
FinalizeURL string `json:"finalize"`
|
FinalizeURL string `json:"finalize"`
|
||||||
Certificate string `json:"certificate,omitempty"`
|
Certificate string `json:"certificate,omitempty"`
|
||||||
ID string `json:"-"`
|
ID string `json:"-"`
|
||||||
|
@ -40,7 +41,7 @@ type Order struct {
|
||||||
func (o *Order) ToLog() (interface{}, error) {
|
func (o *Order) ToLog() (interface{}, error) {
|
||||||
b, err := json.Marshal(o)
|
b, err := json.Marshal(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrorInternalServerWrap(err, "error marshaling order for logging")
|
return nil, ErrorISEWrap(err, "error marshaling order for logging")
|
||||||
}
|
}
|
||||||
return string(b), nil
|
return string(b), nil
|
||||||
}
|
}
|
||||||
|
@ -49,10 +50,6 @@ func (o *Order) ToLog() (interface{}, error) {
|
||||||
// Changes to the order are saved using the database interface.
|
// Changes to the order are saved using the database interface.
|
||||||
func (o *Order) UpdateStatus(ctx context.Context, db DB) error {
|
func (o *Order) UpdateStatus(ctx context.Context, db DB) error {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
expiry, err := time.Parse(time.RFC3339, o.Expires)
|
|
||||||
if err != nil {
|
|
||||||
return ErrorInternalServerWrap(err, "order.UpdateStatus - error converting expiry string to time")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch o.Status {
|
switch o.Status {
|
||||||
case StatusInvalid:
|
case StatusInvalid:
|
||||||
|
@ -61,7 +58,7 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error {
|
||||||
return nil
|
return nil
|
||||||
case StatusReady:
|
case StatusReady:
|
||||||
// Check expiry
|
// Check expiry
|
||||||
if now.After(expiry) {
|
if now.After(o.Expires) {
|
||||||
o.Status = StatusInvalid
|
o.Status = StatusInvalid
|
||||||
o.Error = NewError(ErrorMalformedType, "order has expired")
|
o.Error = NewError(ErrorMalformedType, "order has expired")
|
||||||
break
|
break
|
||||||
|
@ -69,7 +66,7 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error {
|
||||||
return nil
|
return nil
|
||||||
case StatusPending:
|
case StatusPending:
|
||||||
// Check expiry
|
// Check expiry
|
||||||
if now.After(expiry) {
|
if now.After(o.Expires) {
|
||||||
o.Status = StatusInvalid
|
o.Status = StatusInvalid
|
||||||
o.Error = NewError(ErrorMalformedType, "order has expired")
|
o.Error = NewError(ErrorMalformedType, "order has expired")
|
||||||
break
|
break
|
||||||
|
@ -80,7 +77,7 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error {
|
||||||
StatusInvalid: 0,
|
StatusInvalid: 0,
|
||||||
StatusPending: 0,
|
StatusPending: 0,
|
||||||
}
|
}
|
||||||
for _, azID := range o.Authorizations {
|
for _, azID := range o.AuthorizationIDs {
|
||||||
az, err := db.GetAuthorization(ctx, azID)
|
az, err := db.GetAuthorization(ctx, azID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -100,14 +97,14 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error {
|
||||||
case count[StatusPending] > 0:
|
case count[StatusPending] > 0:
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case count[StatusValid] == len(o.Authorizations):
|
case count[StatusValid] == len(o.AuthorizationIDs):
|
||||||
o.Status = StatusReady
|
o.Status = StatusReady
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return NewError(ErrorServerInternalType, "unexpected authz status")
|
return NewErrorISE("unexpected authz status")
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return NewError(ErrorServerInternalType, "unrecognized order status: %s", o.Status)
|
return NewErrorISE("unrecognized order status: %s", o.Status)
|
||||||
}
|
}
|
||||||
return db.UpdateOrder(ctx, o)
|
return db.UpdateOrder(ctx, o)
|
||||||
}
|
}
|
||||||
|
@ -129,7 +126,7 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
|
||||||
case StatusReady:
|
case StatusReady:
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
return NewError(ErrorServerInternalType, "unexpected status %s for order %s", o.Status, o.ID)
|
return NewErrorISE("unexpected status %s for order %s", o.Status, o.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RFC8555: The CSR MUST indicate the exact same set of requested
|
// RFC8555: The CSR MUST indicate the exact same set of requested
|
||||||
|
@ -173,7 +170,7 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
|
||||||
ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod)
|
ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod)
|
||||||
signOps, err := p.AuthorizeSign(ctx, "")
|
signOps, err := p.AuthorizeSign(ctx, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorInternalServerWrap(err, "error retrieving authorization options from ACME provisioner")
|
return ErrorISEWrap(err, "error retrieving authorization options from ACME provisioner")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Template data
|
// Template data
|
||||||
|
@ -183,26 +180,17 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
|
||||||
|
|
||||||
templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data)
|
templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorInternalServerWrap(err, "error creating template options from ACME provisioner")
|
return ErrorISEWrap(err, "error creating template options from ACME provisioner")
|
||||||
}
|
}
|
||||||
signOps = append(signOps, templateOptions)
|
signOps = append(signOps, templateOptions)
|
||||||
|
|
||||||
nbf, err := time.Parse(time.RFC3339, o.NotBefore)
|
|
||||||
if err != nil {
|
|
||||||
return ErrorInternalServerWrap(err, "error parsing order NotBefore")
|
|
||||||
}
|
|
||||||
naf, err := time.Parse(time.RFC3339, o.NotAfter)
|
|
||||||
if err != nil {
|
|
||||||
return ErrorInternalServerWrap(err, "error parsing order NotAfter")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign a new certificate.
|
// Sign a new certificate.
|
||||||
certChain, err := auth.Sign(csr, provisioner.SignOptions{
|
certChain, err := auth.Sign(csr, provisioner.SignOptions{
|
||||||
NotBefore: provisioner.NewTimeDuration(nbf),
|
NotBefore: provisioner.NewTimeDuration(o.NotBefore),
|
||||||
NotAfter: provisioner.NewTimeDuration(naf),
|
NotAfter: provisioner.NewTimeDuration(o.NotAfter),
|
||||||
}, signOps...)
|
}, signOps...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorInternalServerWrap(err, "error signing certificate for order %s", o.ID)
|
return ErrorISEWrap(err, "error signing certificate for order %s", o.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
cert := &Certificate{
|
cert := &Certificate{
|
||||||
|
|
|
@ -14,10 +14,9 @@ import (
|
||||||
|
|
||||||
// WriteError writes to w a JSON representation of the given error.
|
// WriteError writes to w a JSON representation of the given error.
|
||||||
func WriteError(w http.ResponseWriter, err error) {
|
func WriteError(w http.ResponseWriter, err error) {
|
||||||
switch k := err.(type) {
|
switch err.(type) {
|
||||||
case *acme.Error:
|
case *acme.Error:
|
||||||
w.Header().Set("Content-Type", "application/problem+json")
|
w.Header().Set("Content-Type", "application/problem+json")
|
||||||
err = k.ToACME()
|
|
||||||
default:
|
default:
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue