Skip to content

Commit 10d4b18

Browse files
committed
Add support for shortlived public ip address certificates
1 parent a770ad6 commit 10d4b18

4 files changed

Lines changed: 68 additions & 9 deletions

File tree

acme/acme.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,11 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
185185
Nonce string `json:"newNonce"`
186186
KeyChange string `json:"keyChange"`
187187
Meta struct {
188-
Terms string `json:"termsOfService"`
189-
Website string `json:"website"`
190-
CAA []string `json:"caaIdentities"`
191-
ExternalAcct bool `json:"externalAccountRequired"`
188+
Terms string `json:"termsOfService"`
189+
Website string `json:"website"`
190+
CAA []string `json:"caaIdentities"`
191+
ExternalAcct bool `json:"externalAccountRequired"`
192+
Profiles map[string]string `json:"profiles"`
192193
}
193194
}
194195
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
@@ -208,6 +209,7 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) {
208209
Website: v.Meta.Website,
209210
CAA: v.Meta.CAA,
210211
ExternalAccountRequired: v.Meta.ExternalAcct,
212+
Profiles: v.Meta.Profiles,
211213
}
212214
return *c.dir, nil
213215
}
@@ -219,6 +221,20 @@ func (c *Client) directoryURL() string {
219221
return LetsEncryptURL
220222
}
221223

224+
func (c *Client) validProfile(name string) bool {
225+
// profile names are optional, so empty string ("") is valid
226+
if name == "" {
227+
return true
228+
}
229+
if len(c.dir.Profiles) == 0 {
230+
// no profiles are supported so only valid name is empty string ("")
231+
// which is caught above
232+
return false
233+
}
234+
_, has := c.dir.Profiles[name]
235+
return has
236+
}
237+
222238
// CreateCert was part of the old version of ACME. It is incompatible with RFC 8555.
223239
//
224240
// Deprecated: this was for the pre-RFC 8555 version of ACME. Callers should use CreateOrderCert.

acme/autocert/autocert.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
mathrand "math/rand"
2727
"net"
2828
"net/http"
29-
"net/netip"
3029
"path"
3130
"strings"
3231
"sync"
@@ -177,6 +176,14 @@ type Manager struct {
177176
// See RFC 8555, Section 7.3.4 for more details.
178177
ExternalAccountBinding *acme.ExternalAccountBinding
179178

179+
// Profile optional name of certificate profile to use when creating a new order
180+
//
181+
// available profiles are defined by the ACME server and listed in the
182+
// ACME server's directory response.
183+
//
184+
// See RFC: https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/
185+
Profile string
186+
180187
clientMu sync.Mutex
181188
client *acme.Client // initialized by acmeClient method
182189

@@ -694,9 +701,16 @@ func (m *Manager) verifyRFC(ctx context.Context, client *acme.Client, domain str
694701
// it will most likely not work on another order's authorization either.
695702
challengeTypes := m.supportedChallengeTypes()
696703
nextTyp := 0 // challengeTypes index
704+
authOpts := []acme.OrderOption{acme.WithOrderProfile(m.Profile)}
697705
AuthorizeOrderLoop:
698706
for {
699-
o, err := client.AuthorizeOrder(ctx, acme.DomainIDs(domain))
707+
var ids []acme.AuthzID
708+
if ip := net.ParseIP(domain); ip != nil {
709+
ids = acme.IPIDs(domain)
710+
} else {
711+
ids = acme.DomainIDs(domain)
712+
}
713+
o, err := client.AuthorizeOrder(ctx, ids, authOpts...)
700714
if err != nil {
701715
return nil, err
702716
}
@@ -1061,12 +1075,14 @@ func (s *certState) tlscert() (*tls.Certificate, error) {
10611075
// certRequest generates a CSR for the given common name.
10621076
func certRequest(key crypto.Signer, name string, ext []pkix.Extension) ([]byte, error) {
10631077
req := &x509.CertificateRequest{
1064-
Subject: pkix.Name{CommonName: name},
1078+
Subject: pkix.Name{},
10651079
ExtraExtensions: ext,
10661080
}
1067-
// add name to DNSNames if name is not an IP address
1068-
if _, err := netip.ParseAddr(name); err != nil {
1081+
if ip := net.ParseIP(name); ip != nil {
1082+
req.IPAddresses = []net.IP{ip}
1083+
} else {
10691084
req.DNSNames = []string{name}
1085+
req.Subject.CommonName = name
10701086
}
10711087
return x509.CreateCertificateRequest(rand.Reader, req, key)
10721088
}

acme/rfc8555.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderO
208208
Identifiers []wireAuthzID `json:"identifiers"`
209209
NotBefore string `json:"notBefore,omitempty"`
210210
NotAfter string `json:"notAfter,omitempty"`
211+
Profile string `json:"profile,omitempty"`
211212
}{}
212213
for _, v := range id {
213214
req.Identifiers = append(req.Identifiers, wireAuthzID{
@@ -221,6 +222,11 @@ func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderO
221222
req.NotBefore = time.Time(o).Format(time.RFC3339)
222223
case orderNotAfterOpt:
223224
req.NotAfter = time.Time(o).Format(time.RFC3339)
225+
case orderProfileOpt:
226+
req.Profile = string(o)
227+
if !c.validProfile(req.Profile) {
228+
return nil, fmt.Errorf("invalid acme profile: %s", req.Profile)
229+
}
224230
default:
225231
// Package's fault if we let this happen.
226232
panic(fmt.Sprintf("unsupported order option type %T", o))
@@ -308,6 +314,7 @@ func responseOrder(res *http.Response) (*Order, error) {
308314
Authorizations []string
309315
Finalize string
310316
Certificate string
317+
Profile string
311318
}
312319
if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
313320
return nil, fmt.Errorf("acme: error reading order: %v", err)
@@ -321,6 +328,7 @@ func responseOrder(res *http.Response) (*Order, error) {
321328
AuthzURLs: v.Authorizations,
322329
FinalizeURL: v.Finalize,
323330
CertURL: v.Certificate,
331+
Profile: v.Profile,
324332
}
325333
for _, id := range v.Identifiers {
326334
o.Identifiers = append(o.Identifiers, AuthzID{Type: id.Type, Value: id.Value})

acme/types.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,9 @@ type Directory struct {
311311
// ExternalAccountRequired indicates that the CA requires for all account-related
312312
// requests to include external account binding information.
313313
ExternalAccountRequired bool
314+
315+
// Profiles the certificate profiles which are supported by the ACME server
316+
Profiles map[string]string
314317
}
315318

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

351+
// Profile the certificate profile to use with the order (optional)
352+
// available profiles are defined by the ACME server and listed in the
353+
// ACME server's directory response.
354+
//
355+
// RFC: https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/
356+
Profile string
357+
348358
// AuthzURLs represents authorizations to complete before a certificate
349359
// for identifiers specified in the order can be issued.
350360
// It also contains unexpired authorizations that the client has completed
@@ -386,6 +396,11 @@ func WithOrderNotAfter(t time.Time) OrderOption {
386396
return orderNotAfterOpt(t)
387397
}
388398

399+
// WithOrderProfile sets tthe order's Profile field.
400+
func WithOrderProfile(p string) OrderOption {
401+
return orderProfileOpt(p)
402+
}
403+
389404
type orderNotBeforeOpt time.Time
390405

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

395410
func (orderNotAfterOpt) privateOrderOpt() {}
396411

412+
type orderProfileOpt string
413+
414+
func (orderProfileOpt) privateOrderOpt() {}
415+
397416
// Authorization encodes an authorization response.
398417
type Authorization struct {
399418
// URI uniquely identifies a authorization.

0 commit comments

Comments
 (0)