Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions acme/acme.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,11 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
Nonce string `json:"newNonce"`
KeyChange string `json:"keyChange"`
Meta struct {
Terms string `json:"termsOfService"`
Website string `json:"website"`
CAA []string `json:"caaIdentities"`
ExternalAcct bool `json:"externalAccountRequired"`
Terms string `json:"termsOfService"`
Website string `json:"website"`
CAA []string `json:"caaIdentities"`
ExternalAcct bool `json:"externalAccountRequired"`
Profiles map[string]string `json:"profiles"`
}
}
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
Expand All @@ -208,6 +209,7 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
Website: v.Meta.Website,
CAA: v.Meta.CAA,
ExternalAccountRequired: v.Meta.ExternalAcct,
Profiles: v.Meta.Profiles,
}
return *c.dir, nil
}
Expand All @@ -219,6 +221,20 @@ func (c *Client) directoryURL() string {
return LetsEncryptURL
}

func (c *Client) validProfile(name string) bool {
// profile names are optional, so empty string ("") is valid
if name == "" {
return true
}
if len(c.dir.Profiles) == 0 {
// no profiles are supported so only valid name is empty string ("")
// which is caught above
return false
}
_, has := c.dir.Profiles[name]
return has
}

// CreateCert was part of the old version of ACME. It is incompatible with RFC 8555.
//
// Deprecated: this was for the pre-RFC 8555 version of ACME. Callers should use CreateOrderCert.
Expand Down
26 changes: 23 additions & 3 deletions acme/autocert/autocert.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,14 @@ type Manager struct {
// See RFC 8555, Section 7.3.4 for more details.
ExternalAccountBinding *acme.ExternalAccountBinding

// Profile optional name of certificate profile to use when creating a new order
//
// available profiles are defined by the ACME server and listed in the
// ACME server's directory response.
//
// See RFC: https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/
Profile string

clientMu sync.Mutex
client *acme.Client // initialized by acmeClient method

Expand Down Expand Up @@ -697,9 +705,16 @@ func (m *Manager) verifyRFC(ctx context.Context, client *acme.Client, domain str
// it will most likely not work on another order's authorization either.
challengeTypes := m.supportedChallengeTypes()
nextTyp := 0 // challengeTypes index
authOpts := []acme.OrderOption{acme.WithOrderProfile(m.Profile)}
AuthorizeOrderLoop:
for {
o, err := client.AuthorizeOrder(ctx, acme.DomainIDs(domain))
var ids []acme.AuthzID
if ip := net.ParseIP(domain); ip != nil {
ids = acme.IPIDs(domain)
} else {
ids = acme.DomainIDs(domain)
}
o, err := client.AuthorizeOrder(ctx, ids, authOpts...)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1064,10 +1079,15 @@ func (s *certState) tlscert() (*tls.Certificate, error) {
// certRequest generates a CSR for the given common name.
func certRequest(key crypto.Signer, name string, ext []pkix.Extension) ([]byte, error) {
req := &x509.CertificateRequest{
Subject: pkix.Name{CommonName: name},
DNSNames: []string{name},
Subject: pkix.Name{},
ExtraExtensions: ext,
}
if ip := net.ParseIP(name); ip != nil {
req.IPAddresses = []net.IP{ip}
} else {
req.DNSNames = []string{name}
req.Subject.CommonName = name
}
return x509.CreateCertificateRequest(rand.Reader, req, key)
}

Expand Down
8 changes: 8 additions & 0 deletions acme/rfc8555.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderO
Identifiers []wireAuthzID `json:"identifiers"`
NotBefore string `json:"notBefore,omitempty"`
NotAfter string `json:"notAfter,omitempty"`
Profile string `json:"profile,omitempty"`
}{}
for _, v := range id {
req.Identifiers = append(req.Identifiers, wireAuthzID{
Expand All @@ -218,6 +219,11 @@ func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderO
req.NotBefore = time.Time(o).Format(time.RFC3339)
case orderNotAfterOpt:
req.NotAfter = time.Time(o).Format(time.RFC3339)
case orderProfileOpt:
req.Profile = string(o)
if !c.validProfile(req.Profile) {
return nil, fmt.Errorf("invalid acme profile: %s", req.Profile)
}
default:
// Package's fault if we let this happen.
panic(fmt.Sprintf("unsupported order option type %T", o))
Expand Down Expand Up @@ -305,6 +311,7 @@ func responseOrder(res *http.Response) (*Order, error) {
Authorizations []string
Finalize string
Certificate string
Profile string
}
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
return nil, fmt.Errorf("acme: error reading order: %v", err)
Expand All @@ -318,6 +325,7 @@ func responseOrder(res *http.Response) (*Order, error) {
AuthzURLs: v.Authorizations,
FinalizeURL: v.Finalize,
CertURL: v.Certificate,
Profile: v.Profile,
}
for _, id := range v.Identifiers {
o.Identifiers = append(o.Identifiers, AuthzID{Type: id.Type, Value: id.Value})
Expand Down
19 changes: 19 additions & 0 deletions acme/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,9 @@ type Directory struct {
// ExternalAccountRequired indicates that the CA requires for all account-related
// requests to include external account binding information.
ExternalAccountRequired bool

// Profiles the certificate profiles which are supported by the ACME server
Profiles map[string]string
}

// Order represents a client's request for a certificate.
Expand Down Expand Up @@ -345,6 +348,13 @@ type Order struct {
// NotAfter is the requested value of the notAfter field in the certificate.
NotAfter time.Time

// Profile the certificate profile to use with the order (optional)
// available profiles are defined by the ACME server and listed in the
// ACME server's directory response.
//
// RFC: https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/
Profile string

// AuthzURLs represents authorizations to complete before a certificate
// for identifiers specified in the order can be issued.
// It also contains unexpired authorizations that the client has completed
Expand Down Expand Up @@ -386,6 +396,11 @@ func WithOrderNotAfter(t time.Time) OrderOption {
return orderNotAfterOpt(t)
}

// WithOrderProfile sets tthe order's Profile field.
func WithOrderProfile(p string) OrderOption {
return orderProfileOpt(p)
}

type orderNotBeforeOpt time.Time

func (orderNotBeforeOpt) privateOrderOpt() {}
Expand All @@ -394,6 +409,10 @@ type orderNotAfterOpt time.Time

func (orderNotAfterOpt) privateOrderOpt() {}

type orderProfileOpt string

func (orderProfileOpt) privateOrderOpt() {}

// Authorization encodes an authorization response.
type Authorization struct {
// URI uniquely identifies a authorization.
Expand Down