Skip to content

Commit

Permalink
Issuer policies that can randomize issuer used (#238)
Browse files Browse the repository at this point in the history
  • Loading branch information
mholt committed Jun 14, 2023
1 parent 232fade commit 65d3af6
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 5 deletions.
11 changes: 7 additions & 4 deletions README.md
Expand Up @@ -75,10 +75,11 @@ CertMagic - Automatic HTTPS using Let's Encrypt
## Features

- Fully automated certificate management including issuance and renewal
- One-liner, fully managed HTTPS servers
- One-line, fully managed HTTPS servers
- Full control over almost every aspect of the system
- HTTP->HTTPS redirects
- Solves all 3 ACME challenges: HTTP, TLS-ALPN, and DNS
- Multiple issuers supported: get certificates from multiple sources/CAs for redundancy and resiliency
- Solves all 3 common ACME challenges: HTTP, TLS-ALPN, and DNS (and capable of others)
- Most robust error handling of _any_ ACME client
- Challenges are randomized to avoid accidental dependence
- Challenges are rotated to overcome certain network blockages
Expand All @@ -88,7 +89,8 @@ CertMagic - Automatic HTTPS using Let's Encrypt
- Written in Go, a language with memory-safety guarantees
- Powered by [ACMEz](https://github.com/mholt/acmez), _the_ premier ACME client library for Go
- All [libdns](https://github.com/libdns) DNS providers work out-of-the-box
- Pluggable storage implementations (default: file system)
- Pluggable storage backends (default: file system)
- Pluggable key sources
- Wildcard certificates
- Automatic OCSP stapling ([done right](https://gist.github.com/sleevi/5efe9ef98961ecfb4da8#gistcomment-2336055)) [keeps your sites online!](https://twitter.com/caddyserver/status/1234874273724084226)
- Will [automatically attempt](https://twitter.com/mholt6/status/1235577699541762048) to replace [revoked certificates](https://community.letsencrypt.org/t/2020-02-29-caa-rechecking-bug/114591/3?u=mholt)!
Expand All @@ -101,7 +103,8 @@ CertMagic - Automatic HTTPS using Let's Encrypt
- Caddy / CertMagic pioneered this technology
- Custom decision functions to regulate and throttle on-demand behavior
- Optional event hooks for observation
- Works with any certificate authority (CA) compliant with the ACME specification
- One-time private keys by default (new key for each cert) to discourage pinning and reduce scope of key compromise
- Works with any certificate authority (CA) compliant with the ACME specification RFC 8555
- Certificate revocation (please, only if private key is compromised)
- Must-Staple (optional; not default)
- Cross-platform support! Mac, Windows, Linux, BSD, Android...
Expand Down
17 changes: 17 additions & 0 deletions certmagic.go
Expand Up @@ -411,6 +411,23 @@ type KeyGenerator interface {
GenerateKey() (crypto.PrivateKey, error)
}

// IssuerPolicy is a type that enumerates how to
// choose which issuer to use. EXPERIMENTAL and
// subject to change.
type IssuerPolicy string

// Supported issuer policies. These are subject to change.
const (
// UseFirstIssuer uses the first issuer that
// successfully returns a certificate.
UseFirstIssuer = "first"

// UseFirstRandomIssuer shuffles the list of
// configured issuers, then uses the first one
// that successfully returns a certificate.
UseFirstRandomIssuer = "first_random"
)

// IssuedCertificate represents a certificate that was just issued.
type IssuedCertificate struct {
// The PEM-encoding of DER-encoded ASN.1 data.
Expand Down
16 changes: 15 additions & 1 deletion config.go
Expand Up @@ -95,6 +95,10 @@ type Config struct {
// turn until one succeeds.
Issuers []Issuer

// How to select which issuer to use.
// Default: UseFirstIssuer (subject to change).
IssuerPolicy IssuerPolicy

// If true, private keys already existing in storage
// will be reused. Otherwise, a new key will be
// created for every new certificate to mitigate
Expand Down Expand Up @@ -533,7 +537,9 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
return fmt.Errorf("obtaining certificate aborted by event handler: %w", err)
}

// if storage has a private key already, use it; otherwise we'll generate our own
// If storage has a private key already, use it; otherwise we'll generate our own.
// Also create the slice of issuers we will try using according to any issuer
// selection policy (it must be a copy of the slice so we don't mutate original).
var privKey crypto.PrivateKey
var privKeyPEM []byte
var issuers []Issuer
Expand All @@ -542,6 +548,14 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
if err != nil {
return err
}
} else {
issuers = make([]Issuer, len(cfg.Issuers))
copy(issuers, cfg.Issuers)
}
if cfg.IssuerPolicy == UseFirstRandomIssuer {
weakrand.Shuffle(len(issuers), func(i, j int) {
issuers[i], issuers[j] = issuers[j], issuers[i]
})
}
if privKey == nil {
privKey, err = cfg.KeySource.GenerateKey()
Expand Down

0 comments on commit 65d3af6

Please sign in to comment.