[acme db interface] wip
parent
491c188a5e
commit
80a6640103
@ -0,0 +1,164 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/smallstep/certificates/acme"
|
||||
)
|
||||
|
||||
// NewLinker returns a new Directory type.
|
||||
func NewLinker(dns, prefix string) *Linker {
|
||||
return &Linker{Prefix: prefix, DNS: dns}
|
||||
}
|
||||
|
||||
// Linker generates ACME links.
|
||||
type Linker struct {
|
||||
Prefix string
|
||||
DNS string
|
||||
}
|
||||
|
||||
// GetLink is a helper for GetLinkExplicit
|
||||
func (l *Linker) GetLink(ctx context.Context, typ LinkType, abs bool, inputs ...string) string {
|
||||
var provName string
|
||||
if p, err := provisionerFromContext(ctx); err == nil && p != nil {
|
||||
provName = p.GetName()
|
||||
}
|
||||
return l.GetLinkExplicit(typ, provName, abs, baseURLFromContext(ctx), inputs...)
|
||||
}
|
||||
|
||||
// GetLinkExplicit returns an absolute or partial path to the given resource and a base
|
||||
// URL dynamically obtained from the request for which the link is being
|
||||
// calculated.
|
||||
func (l *Linker) GetLinkExplicit(typ LinkType, provisionerName string, abs bool, baseURL *url.URL, inputs ...string) string {
|
||||
var link string
|
||||
switch typ {
|
||||
case NewNonceLinkType, NewAccountLinkType, NewOrderLinkType, NewAuthzLinkType, DirectoryLinkType, KeyChangeLinkType, RevokeCertLinkType:
|
||||
link = fmt.Sprintf("/%s/%s", provisionerName, typ)
|
||||
case AccountLinkType, OrderLinkType, AuthzLinkType, CertificateLinkType:
|
||||
link = fmt.Sprintf("/%s/%s/%s", provisionerName, typ, inputs[0])
|
||||
case ChallengeLinkType:
|
||||
link = fmt.Sprintf("/%s/%s/%s/%s", provisionerName, typ, inputs[0], inputs[1])
|
||||
case OrdersByAccountLinkType:
|
||||
link = fmt.Sprintf("/%s/%s/%s/orders", provisionerName, AccountLinkType, inputs[0])
|
||||
case FinalizeLinkType:
|
||||
link = fmt.Sprintf("/%s/%s/%s/finalize", provisionerName, OrderLinkType, inputs[0])
|
||||
}
|
||||
|
||||
if abs {
|
||||
// Copy the baseURL value from the pointer. https://github.com/golang/go/issues/38351
|
||||
u := url.URL{}
|
||||
if baseURL != nil {
|
||||
u = *baseURL
|
||||
}
|
||||
|
||||
// If no Scheme is set, then default to https.
|
||||
if u.Scheme == "" {
|
||||
u.Scheme = "https"
|
||||
}
|
||||
|
||||
// If no Host is set, then use the default (first DNS attr in the ca.json).
|
||||
if u.Host == "" {
|
||||
u.Host = l.DNS
|
||||
}
|
||||
|
||||
u.Path = l.Prefix + link
|
||||
return u.String()
|
||||
}
|
||||
return link
|
||||
}
|
||||
|
||||
// LinkType captures the link type.
|
||||
type LinkType int
|
||||
|
||||
const (
|
||||
// NewNonceLinkType new-nonce
|
||||
NewNonceLinkType LinkType = iota
|
||||
// NewAccountLinkType new-account
|
||||
NewAccountLinkType
|
||||
// AccountLinkType account
|
||||
AccountLinkType
|
||||
// OrderLinkType order
|
||||
OrderLinkType
|
||||
// NewOrderLinkType new-order
|
||||
NewOrderLinkType
|
||||
// OrdersByAccountLinkType list of orders owned by account
|
||||
OrdersByAccountLinkType
|
||||
// FinalizeLinkType finalize order
|
||||
FinalizeLinkType
|
||||
// NewAuthzLinkType authz
|
||||
NewAuthzLinkType
|
||||
// AuthzLinkType new-authz
|
||||
AuthzLinkType
|
||||
// ChallengeLinkType challenge
|
||||
ChallengeLinkType
|
||||
// CertificateLinkType certificate
|
||||
CertificateLinkType
|
||||
// DirectoryLinkType directory
|
||||
DirectoryLinkType
|
||||
// RevokeCertLinkType revoke certificate
|
||||
RevokeCertLinkType
|
||||
// KeyChangeLinkType key rollover
|
||||
KeyChangeLinkType
|
||||
)
|
||||
|
||||
func (l LinkType) String() string {
|
||||
switch l {
|
||||
case NewNonceLinkType:
|
||||
return "new-nonce"
|
||||
case NewAccountLinkType:
|
||||
return "new-account"
|
||||
case AccountLinkType:
|
||||
return "account"
|
||||
case NewOrderLinkType:
|
||||
return "new-order"
|
||||
case OrderLinkType:
|
||||
return "order"
|
||||
case NewAuthzLinkType:
|
||||
return "new-authz"
|
||||
case AuthzLinkType:
|
||||
return "authz"
|
||||
case ChallengeLinkType:
|
||||
return "challenge"
|
||||
case CertificateLinkType:
|
||||
return "certificate"
|
||||
case DirectoryLinkType:
|
||||
return "directory"
|
||||
case RevokeCertLinkType:
|
||||
return "revoke-cert"
|
||||
case KeyChangeLinkType:
|
||||
return "key-change"
|
||||
default:
|
||||
return fmt.Sprintf("unexpected LinkType '%d'", int(l))
|
||||
}
|
||||
}
|
||||
|
||||
// LinkOrder sets the ACME links required by an ACME order.
|
||||
func (l *Linker) LinkOrder(ctx context.Context, o *acme.Order) {
|
||||
o.azURLs = make([]string, len(o.AuthorizationIDs))
|
||||
for i, azID := range o.AutohrizationIDs {
|
||||
o.azURLs[i] = l.GetLink(ctx, AuthzLinkType, true, azID)
|
||||
}
|
||||
o.FinalizeURL = l.GetLink(ctx, FinalizeLinkType, true, o.ID)
|
||||
if o.CertificateID != "" {
|
||||
o.CertificateURL = l.GetLink(ctx, CertificateLinkType, true, o.CertificateID)
|
||||
}
|
||||
}
|
||||
|
||||
// LinkAccount sets the ACME links required by an ACME account.
|
||||
func (l *Linker) LinkAccount(ctx context.Context, acc *acme.Account) {
|
||||
a.Orders = l.GetLink(ctx, OrdersByAccountLinkType, true, acc.ID)
|
||||
}
|
||||
|
||||
// LinkChallenge sets the ACME links required by an ACME account.
|
||||
func (l *Linker) LinkChallenge(ctx context.Context, ch *acme.Challenge) {
|
||||
a.URL = l.GetLink(ctx, ChallengeLinkType, true, ch.AuthzID, ch.ID)
|
||||
}
|
||||
|
||||
// LinkAuthorization sets the ACME links required by an ACME account.
|
||||
func (l *Linker) LinkAuthorization(ctx context.Context, az *acme.Authorization) {
|
||||
for _, ch := range az.Challenges {
|
||||
l.LinkChallenge(ctx, ch)
|
||||
}
|
||||
}
|
@ -1,420 +0,0 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/randutil"
|
||||
)
|
||||
|
||||
// Interface is the acme authority interface.
|
||||
type Interface interface {
|
||||
GetDirectory(ctx context.Context) (*Directory, error)
|
||||
NewNonce() (string, error)
|
||||
UseNonce(string) error
|
||||
|
||||
DeactivateAccount(ctx context.Context, accID string) (*Account, error)
|
||||
GetAccount(ctx context.Context, accID string) (*Account, error)
|
||||
GetAccountByKey(ctx context.Context, key *jose.JSONWebKey) (*Account, error)
|
||||
NewAccount(ctx context.Context, acc *Account) (*Account, error)
|
||||
UpdateAccount(ctx context.Context, acc *Account) (*Account, error)
|
||||
|
||||
GetAuthz(ctx context.Context, accID string, authzID string) (*Authorization, error)
|
||||
ValidateChallenge(ctx context.Context, accID string, chID string, key *jose.JSONWebKey) (*Challenge, error)
|
||||
|
||||
FinalizeOrder(ctx context.Context, accID string, orderID string, csr *x509.CertificateRequest) (*Order, error)
|
||||
GetOrder(ctx context.Context, accID string, orderID string) (*Order, error)
|
||||
GetOrdersByAccount(ctx context.Context, accID string) ([]string, error)
|
||||
NewOrder(ctx context.Context, o *Order) (*Order, error)
|
||||
|
||||
GetCertificate(string, string) ([]byte, error)
|
||||
|
||||
LoadProvisionerByID(string) (provisioner.Interface, error)
|
||||
GetLink(ctx context.Context, linkType Link, absoluteLink bool, inputs ...string) string
|
||||
GetLinkExplicit(linkType Link, provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string
|
||||
}
|
||||
|
||||
// Authority is the layer that handles all ACME interactions.
|
||||
type Authority struct {
|
||||
backdate provisioner.Duration
|
||||
db DB
|
||||
dir *directory
|
||||
signAuth SignAuthority
|
||||
}
|
||||
|
||||
// AuthorityOptions required to create a new ACME Authority.
|
||||
type AuthorityOptions struct {
|
||||
Backdate provisioner.Duration
|
||||
// DB storage backend that impements the acme.DB interface.
|
||||
DB DB
|
||||
// DNS the host used to generate accurate ACME links. By default the authority
|
||||
// will use the Host from the request, so this value will only be used if
|
||||
// request.Host is empty.
|
||||
DNS string
|
||||
// Prefix is a URL path prefix under which the ACME api is served. This
|
||||
// prefix is required to generate accurate ACME links.
|
||||
// E.g. https://ca.smallstep.com/acme/my-acme-provisioner/new-account --
|
||||
// "acme" is the prefix from which the ACME api is accessed.
|
||||
Prefix string
|
||||
}
|
||||
|
||||
// NewAuthority returns a new Authority that implements the ACME interface.
|
||||
//
|
||||
// Deprecated: NewAuthority exists for hitorical compatibility and should not
|
||||
// be used. Use acme.New() instead.
|
||||
func NewAuthority(db DB, dns, prefix string, signAuth SignAuthority) (*Authority, error) {
|
||||
return New(signAuth, AuthorityOptions{
|
||||
DB: db,
|
||||
DNS: dns,
|
||||
Prefix: prefix,
|
||||
})
|
||||
}
|
||||
|
||||
// New returns a new Authority that implements the ACME interface.
|
||||
func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) {
|
||||
return &Authority{
|
||||
backdate: ops.Backdate, db: ops.DB, dir: newDirectory(ops.DNS, ops.Prefix), signAuth: signAuth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetLink returns the requested link from the directory.
|
||||
func (a *Authority) GetLink(ctx context.Context, typ Link, abs bool, inputs ...string) string {
|
||||
return a.dir.getLink(ctx, typ, abs, inputs...)
|
||||
}
|
||||
|
||||
// GetLinkExplicit returns the requested link from the directory.
|
||||
func (a *Authority) GetLinkExplicit(typ Link, provName string, abs bool, baseURL *url.URL, inputs ...string) string {
|
||||
return a.dir.getLinkExplicit(typ, provName, abs, baseURL, inputs...)
|
||||
}
|
||||
|
||||
// GetDirectory returns the ACME directory object.
|
||||
func (a *Authority) GetDirectory(ctx context.Context) (*Directory, error) {
|
||||
return &Directory{
|
||||
NewNonce: a.dir.getLink(ctx, NewNonceLink, true),
|
||||
NewAccount: a.dir.getLink(ctx, NewAccountLink, true),
|
||||
NewOrder: a.dir.getLink(ctx, NewOrderLink, true),
|
||||
RevokeCert: a.dir.getLink(ctx, RevokeCertLink, true),
|
||||
KeyChange: a.dir.getLink(ctx, KeyChangeLink, true),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LoadProvisionerByID calls out to the SignAuthority interface to load a
|
||||
// provisioner by ID.
|
||||
func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) {
|
||||
return a.signAuth.LoadProvisionerByID(id)
|
||||
}
|
||||
|
||||
// NewNonce generates, stores, and returns a new ACME nonce.
|
||||
func (a *Authority) NewNonce(ctx context.Context) (Nonce, error) {
|
||||
return a.db.CreateNonce(ctx)
|
||||
}
|
||||
|
||||
// UseNonce consumes the given nonce if it is valid, returns error otherwise.
|
||||
func (a *Authority) UseNonce(ctx context.Context, nonce string) error {
|
||||
return a.db.DeleteNonce(ctx, Nonce(nonce))
|
||||
}
|
||||
|
||||
// NewAccount creates, stores, and returns a new ACME account.
|
||||
func (a *Authority) NewAccount(ctx context.Context, acc *Account) error {
|
||||
if err := a.db.CreateAccount(ctx, acc); err != nil {
|
||||
return ErrorISEWrap(err, "error creating account")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateAccount updates an ACME account.
|
||||
func (a *Authority) UpdateAccount(ctx context.Context, acc *Account) (*Account, error) {
|
||||
/*
|
||||
acc.Contact = auo.Contact
|
||||
acc.Status = auo.Status
|
||||
*/
|
||||
if err := a.db.UpdateAccount(ctx, acc); err != nil {
|
||||
return nil, ErrorISEWrap(err, "error updating account")
|
||||
}
|
||||
return acc, nil
|
||||
}
|
||||
|
||||
// GetAccount returns an ACME account.
|
||||
func (a *Authority) GetAccount(ctx context.Context, id string) (*Account, error) {
|
||||
acc, err := a.db.GetAccount(ctx, id)
|
||||
if err != nil {
|
||||
return nil, ErrorISEWrap(err, "error retrieving account")
|
||||
}
|
||||
return acc, nil
|
||||
}
|
||||
|
||||
// GetAccountByKey returns the ACME associated with the jwk id.
|
||||
func (a *Authority) GetAccountByKey(ctx context.Context, jwk *jose.JSONWebKey) (*Account, error) {
|
||||
kid, err := KeyToID(jwk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
acc, err := a.db.GetAccountByKeyID(ctx, kid)
|
||||
return acc, err
|
||||
}
|
||||
|
||||
// GetOrder returns an ACME order.
|
||||
func (a *Authority) GetOrder(ctx context.Context, accID, orderID string) (*Order, error) {
|
||||
prov, err := ProvisionerFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o, err := a.db.GetOrder(ctx, orderID)
|
||||
if err != nil {
|
||||
return nil, ErrorISEWrap(err, "error retrieving order")
|
||||
}
|
||||
if accID != o.AccountID {
|
||||
log.Printf("account-id from request ('%s') does not match order account-id ('%s')", accID, o.AccountID)
|
||||
return nil, NewError(ErrorUnauthorizedType, "account does not own order")
|
||||
}
|
||||
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, NewError(ErrorUnauthorizedType, "provisioner does not own order")
|
||||
}
|
||||
if err = o.UpdateStatus(ctx, a.db); err != nil {
|
||||
return nil, ErrorISEWrap(err, "error updating order")
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
/*
|
||||
// GetOrdersByAccount returns the list of order urls owned by the account.
|
||||
func (a *Authority) GetOrdersByAccount(ctx context.Context, id string) ([]string, error) {
|
||||
ordersByAccountMux.Lock()
|
||||
defer ordersByAccountMux.Unlock()
|
||||
|
||||
var oiba = orderIDsByAccount{}
|
||||
oids, err := oiba.unsafeGetOrderIDsByAccount(a.db, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ret = []string{}
|
||||
for _, oid := range oids {
|
||||
ret = append(ret, a.dir.getLink(ctx, OrderLink, true, oid))
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
*/
|
||||
|
||||
// NewOrder generates, stores, and returns a new ACME order.
|
||||
func (a *Authority) NewOrder(ctx context.Context, o *Order) error {
|
||||
if len(o.AccountID) == 0 {
|
||||
return NewErrorISE("account-id cannot be empty")
|
||||
}
|
||||
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.Backdate = a.backdate.Duration
|
||||
o.ProvisionerID = prov.GetID()
|
||||
|
||||
if err = a.db.CreateOrder(ctx, o); err != nil {
|
||||
return nil, ErrorWrap(ErrorServerInternalType, err, "error creating order")
|
||||
}
|
||||
return o, nil
|
||||
*/
|
||||
}
|
||||
|
||||
// FinalizeOrder attempts to finalize an order and generate a new certificate.
|
||||
func (a *Authority) FinalizeOrder(ctx context.Context, accID, orderID string, csr *x509.CertificateRequest) (*Order, error) {
|
||||
prov, err := ProvisionerFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o, err := a.db.GetOrder(ctx, orderID)
|
||||
if err != nil {
|
||||
return nil, ErrorISEWrap(err, "error retrieving order")
|
||||
}
|
||||
if accID != o.AccountID {
|
||||
log.Printf("account-id from request ('%s') does not match order account-id ('%s')", accID, o.AccountID)
|
||||
return nil, NewError(ErrorUnauthorizedType, "account does not own order")
|
||||
}
|
||||
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, NewError(ErrorUnauthorizedType, "provisioner does not own order")
|
||||
}
|
||||
if err = o.Finalize(ctx, a.db, csr, a.signAuth, prov); err != nil {
|
||||
return nil, ErrorISEWrap(err, "error finalizing order")
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (a *Authority) GetAuthorization(ctx context.Context, accID, authzID string) (*Authorization, error) {
|
||||
az, err := a.db.GetAuthorization(ctx, authzID)
|
||||
if err != nil {
|
||||
return nil, ErrorISEWrap(err, "error retrieving authorization")
|
||||
}
|
||||
if 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")
|
||||
}
|
||||
if err = az.UpdateStatus(ctx, a.db); err != nil {
|
||||
return nil, ErrorISEWrap(err, "error updating authorization status")
|
||||
}
|
||||
return az, nil
|
||||
}
|
||||
|
||||
// NewChallenge generates and stores an ACME challenge and associated resources.
|
||||
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")
|
||||
if err != nil {
|
||||
return nil, ErrorISEWrap(err, "error retrieving challenge")
|
||||
}
|
||||
if accID != ch.AccountID {
|
||||
log.Printf("account-id from request ('%s') does not match challenge account-id ('%s')", accID, ch.AccountID)
|
||||
return nil, NewError(ErrorUnauthorizedType, "account does not own order")
|
||||
}
|
||||
client := http.Client{
|
||||
Timeout: time.Duration(30 * time.Second),
|
||||
}
|
||||
dialer := &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
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)
|
||||
},
|
||||
}); err != nil {
|
||||
return nil, ErrorISEWrap(err, "error validating challenge")
|
||||
}
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// GetCertificate retrieves the Certificate by ID.
|
||||
func (a *Authority) GetCertificate(ctx context.Context, accID, certID string) ([]byte, error) {
|
||||
cert, err := a.db.GetCertificate(ctx, certID)
|
||||
if err != nil {
|
||||
return nil, ErrorISEWrap(err, "error retrieving certificate")
|
||||
}
|
||||
if cert.AccountID != accID {
|
||||
log.Printf("account-id from request ('%s') does not match challenge account-id ('%s')", accID, cert.AccountID)
|
||||
return nil, NewError(ErrorUnauthorizedType, "account does not own order")
|
||||
}
|
||||
return cert.ToACME(ctx)
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Directory represents an ACME directory for configuring clients.
|
||||
type Directory struct {
|
||||
NewNonce string `json:"newNonce,omitempty"`
|
||||
NewAccount string `json:"newAccount,omitempty"`
|
||||
NewOrder string `json:"newOrder,omitempty"`
|
||||
NewAuthz string `json:"newAuthz,omitempty"`
|
||||
RevokeCert string `json:"revokeCert,omitempty"`
|
||||
KeyChange string `json:"keyChange,omitempty"`
|
||||
}
|
||||
|
||||
// ToLog enables response logging for the Directory type.
|
||||
func (d *Directory) ToLog() (interface{}, error) {
|
||||
b, err := json.Marshal(d)
|
||||
if err != nil {
|
||||
return nil, ErrorISEWrap(err, "error marshaling directory for logging")
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
type directory struct {
|
||||
prefix, dns string
|
||||
}
|
||||
|
||||
// newDirectory returns a new Directory type.
|
||||
func newDirectory(dns, prefix string) *directory {
|
||||
return &directory{prefix: prefix, dns: dns}
|
||||
}
|
||||
|
||||
// Link captures the link type.
|
||||
type Link int
|
||||
|
||||
const (
|
||||
// NewNonceLink new-nonce
|
||||
NewNonceLink Link = iota
|
||||
// NewAccountLink new-account
|
||||
NewAccountLink
|
||||
// AccountLink account
|
||||
AccountLink
|
||||
// OrderLink order
|
||||
OrderLink
|
||||
// NewOrderLink new-order
|
||||
NewOrderLink
|
||||
// OrdersByAccountLink list of orders owned by account
|
||||
OrdersByAccountLink
|
||||
// FinalizeLink finalize order
|
||||
FinalizeLink
|
||||
// NewAuthzLink authz
|
||||
NewAuthzLink
|
||||
// AuthzLink new-authz
|
||||
AuthzLink
|
||||
// ChallengeLink challenge
|
||||
ChallengeLink
|
||||
// CertificateLink certificate
|
||||
CertificateLink
|
||||
// DirectoryLink directory
|
||||
DirectoryLink
|
||||
// RevokeCertLink revoke certificate
|
||||
RevokeCertLink
|
||||
// KeyChangeLink key rollover
|
||||
KeyChangeLink
|
||||
)
|
||||
|
||||
func (l Link) String() string {
|
||||
switch l {
|
||||
case NewNonceLink:
|
||||
return "new-nonce"
|
||||
case NewAccountLink:
|
||||
return "new-account"
|
||||
case AccountLink:
|
||||
return "account"
|
||||
case NewOrderLink:
|
||||
return "new-order"
|
||||
case OrderLink:
|
||||
return "order"
|
||||
case NewAuthzLink:
|
||||
return "new-authz"
|
||||
case AuthzLink:
|
||||
return "authz"
|
||||
case ChallengeLink:
|
||||
return "challenge"
|
||||
case CertificateLink:
|
||||
return "certificate"
|
||||
case DirectoryLink:
|
||||
return "directory"
|
||||
case RevokeCertLink:
|
||||
return "revoke-cert"
|
||||
case KeyChangeLink:
|
||||
return "key-change"
|
||||
default:
|
||||
return "unexpected"
|
||||
}
|
||||
}
|
||||
|
||||
func (d *directory) getLink(ctx context.Context, typ Link, abs bool, inputs ...string) string {
|
||||
var provName string
|
||||
if p, err := ProvisionerFromContext(ctx); err == nil && p != nil {
|
||||
provName = p.GetName()
|
||||
}
|
||||
return d.getLinkExplicit(typ, provName, abs, BaseURLFromContext(ctx), inputs...)
|
||||
}
|
||||
|
||||
// getLinkExplicit returns an absolute or partial path to the given resource and a base
|
||||
// URL dynamically obtained from the request for which the link is being
|
||||
// calculated.
|
||||
func (d *directory) getLinkExplicit(typ Link, provisionerName string, abs bool, baseURL *url.URL, inputs ...string) string {
|
||||
var link string
|
||||
switch typ {
|
||||
case NewNonceLink, NewAccountLink, NewOrderLink, NewAuthzLink, DirectoryLink, KeyChangeLink, RevokeCertLink:
|
||||
link = fmt.Sprintf("/%s/%s", provisionerName, typ.String())
|
||||
case AccountLink, OrderLink, AuthzLink, ChallengeLink, CertificateLink:
|
||||
link = fmt.Sprintf("/%s/%s/%s", provisionerName, typ.String(), inputs[0])
|
||||
case OrdersByAccountLink:
|
||||
link = fmt.Sprintf("/%s/%s/%s/orders", provisionerName, AccountLink.String(), inputs[0])
|
||||
case FinalizeLink:
|
||||
link = fmt.Sprintf("/%s/%s/%s/finalize", provisionerName, OrderLink.String(), inputs[0])
|
||||
}
|
||||
|
||||
if abs {
|
||||
// Copy the baseURL value from the pointer. https://github.com/golang/go/issues/38351
|
||||
u := url.URL{}
|
||||
if baseURL != nil {
|
||||
u = *baseURL
|
||||
}
|
||||
|
||||
// If no Scheme is set, then default to https.
|
||||
if u.Scheme == "" {
|
||||
u.Scheme = "https"
|
||||
}
|
||||
|
||||
// If no Host is set, then use the default (first DNS attr in the ca.json).
|
||||
if u.Host == "" {
|
||||
u.Host = d.dns
|
||||
}
|
||||
|
||||
u.Path = d.prefix + link
|
||||
return u.String()
|
||||
}
|
||||
return link
|
||||
}
|
@ -1,3 +1,9 @@
|
||||
package acme
|
||||
|
||||
// Nonce represents an ACME nonce type.
|
||||
type Nonce string
|
||||
|
||||
// String implements the ToString interface.
|
||||
func (n Nonce) String() string {
|
||||
return string(n)
|
||||
}
|
||||
|
Loading…
Reference in New Issue