|
- // Copyright 2015 Matthew Holt
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- // Package certmagic automates the obtaining and renewal of TLS certificates,
- // including TLS & HTTPS best practices such as robust OCSP stapling, caching,
- // HTTP->HTTPS redirects, and more.
- //
- // Its high-level API serves your HTTP handlers over HTTPS if you simply give
- // the domain name(s) and the http.Handler; CertMagic will create and run
- // the HTTPS server for you, fully managing certificates during the lifetime
- // of the server. Similarly, it can be used to start TLS listeners or return
- // a ready-to-use tls.Config -- whatever layer you need TLS for, CertMagic
- // makes it easy. See the HTTPS, Listen, and TLS functions for that.
- //
- // If you need more control, create a Cache using NewCache() and then make
- // a Config using New(). You can then call Manage() on the config. But if
- // you use this lower-level API, you'll have to be sure to solve the HTTP
- // and TLS-ALPN challenges yourself (unless you disabled them or use the
- // DNS challenge) by using the provided Config.GetCertificate function
- // in your tls.Config and/or Config.HTTPChallangeHandler in your HTTP
- // handler.
- //
- // See the package's README for more instruction.
- package certmagic
-
- import (
- "context"
- "crypto"
- "crypto/tls"
- "crypto/x509"
- "fmt"
- "log"
- "net"
- "net/http"
- "sort"
- "strings"
- "sync"
- "time"
- )
-
- // HTTPS serves mux for all domainNames using the HTTP
- // and HTTPS ports, redirecting all HTTP requests to HTTPS.
- // It uses the Default config.
- //
- // This high-level convenience function is opinionated and
- // applies sane defaults for production use, including
- // timeouts for HTTP requests and responses. To allow very
- // long-lived connections, you should make your own
- // http.Server values and use this package's Listen(), TLS(),
- // or Config.TLSConfig() functions to customize to your needs.
- // For example, servers which need to support large uploads or
- // downloads with slow clients may need to use longer timeouts,
- // thus this function is not suitable.
- //
- // Calling this function signifies your acceptance to
- // the CA's Subscriber Agreement and/or Terms of Service.
- func HTTPS(domainNames []string, mux http.Handler) error {
- if mux == nil {
- mux = http.DefaultServeMux
- }
-
- DefaultACME.Agreed = true
- cfg := NewDefault()
-
- err := cfg.ManageSync(domainNames)
- if err != nil {
- return err
- }
-
- httpWg.Add(1)
- defer httpWg.Done()
-
- // if we haven't made listeners yet, do so now,
- // and clean them up when all servers are done
- lnMu.Lock()
- if httpLn == nil && httpsLn == nil {
- httpLn, err = net.Listen("tcp", fmt.Sprintf(":%d", HTTPPort))
- if err != nil {
- lnMu.Unlock()
- return err
- }
-
- tlsConfig := cfg.TLSConfig()
- tlsConfig.NextProtos = append([]string{"h2", "http/1.1"}, tlsConfig.NextProtos...)
-
- httpsLn, err = tls.Listen("tcp", fmt.Sprintf(":%d", HTTPSPort), tlsConfig)
- if err != nil {
- httpLn.Close()
- httpLn = nil
- lnMu.Unlock()
- return err
- }
-
- go func() {
- httpWg.Wait()
- lnMu.Lock()
- httpLn.Close()
- httpsLn.Close()
- lnMu.Unlock()
- }()
- }
- hln, hsln := httpLn, httpsLn
- lnMu.Unlock()
-
- // create HTTP/S servers that are configured
- // with sane default timeouts and appropriate
- // handlers (the HTTP server solves the HTTP
- // challenge and issues redirects to HTTPS,
- // while the HTTPS server simply serves the
- // user's handler)
- httpServer := &http.Server{
- ReadHeaderTimeout: 5 * time.Second,
- ReadTimeout: 5 * time.Second,
- WriteTimeout: 5 * time.Second,
- IdleTimeout: 5 * time.Second,
- }
- if am, ok := cfg.Issuer.(*ACMEManager); ok {
- httpServer.Handler = am.HTTPChallengeHandler(http.HandlerFunc(httpRedirectHandler))
- }
- httpsServer := &http.Server{
- ReadHeaderTimeout: 10 * time.Second,
- ReadTimeout: 30 * time.Second,
- WriteTimeout: 2 * time.Minute,
- IdleTimeout: 5 * time.Minute,
- Handler: mux,
- }
-
- log.Printf("%v Serving HTTP->HTTPS on %s and %s",
- domainNames, hln.Addr(), hsln.Addr())
-
- go httpServer.Serve(hln)
- return httpsServer.Serve(hsln)
- }
-
- func httpRedirectHandler(w http.ResponseWriter, r *http.Request) {
- toURL := "https://"
-
- // since we redirect to the standard HTTPS port, we
- // do not need to include it in the redirect URL
- requestHost := hostOnly(r.Host)
-
- toURL += requestHost
- toURL += r.URL.RequestURI()
-
- // get rid of this disgusting unencrypted HTTP connection 🤢
- w.Header().Set("Connection", "close")
-
- http.Redirect(w, r, toURL, http.StatusMovedPermanently)
- }
-
- // TLS enables management of certificates for domainNames
- // and returns a valid tls.Config. It uses the Default
- // config.
- //
- // Because this is a convenience function that returns
- // only a tls.Config, it does not assume HTTP is being
- // served on the HTTP port, so the HTTP challenge is
- // disabled (no HTTPChallengeHandler is necessary). The
- // package variable Default is modified so that the
- // HTTP challenge is disabled.
- //
- // Calling this function signifies your acceptance to
- // the CA's Subscriber Agreement and/or Terms of Service.
- func TLS(domainNames []string) (*tls.Config, error) {
- DefaultACME.Agreed = true
- DefaultACME.DisableHTTPChallenge = true
- cfg := NewDefault()
- return cfg.TLSConfig(), cfg.ManageSync(domainNames)
- }
-
- // Listen manages certificates for domainName and returns a
- // TLS listener. It uses the Default config.
- //
- // Because this convenience function returns only a TLS-enabled
- // listener and does not presume HTTP is also being served,
- // the HTTP challenge will be disabled. The package variable
- // Default is modified so that the HTTP challenge is disabled.
- //
- // Calling this function signifies your acceptance to
- // the CA's Subscriber Agreement and/or Terms of Service.
- func Listen(domainNames []string) (net.Listener, error) {
- DefaultACME.Agreed = true
- DefaultACME.DisableHTTPChallenge = true
- cfg := NewDefault()
- err := cfg.ManageSync(domainNames)
- if err != nil {
- return nil, err
- }
- return tls.Listen("tcp", fmt.Sprintf(":%d", HTTPSPort), cfg.TLSConfig())
- }
-
- // ManageSync obtains certificates for domainNames and keeps them
- // renewed using the Default config.
- //
- // This is a slightly lower-level function; you will need to
- // wire up support for the ACME challenges yourself. You can
- // obtain a Config to help you do that by calling NewDefault().
- //
- // You will need to ensure that you use a TLS config that gets
- // certificates from this Config and that the HTTP and TLS-ALPN
- // challenges can be solved. The easiest way to do this is to
- // use NewDefault().TLSConfig() as your TLS config and to wrap
- // your HTTP handler with NewDefault().HTTPChallengeHandler().
- // If you don't have an HTTP server, you will need to disable
- // the HTTP challenge.
- //
- // If you already have a TLS config you want to use, you can
- // simply set its GetCertificate field to
- // NewDefault().GetCertificate.
- //
- // Calling this function signifies your acceptance to
- // the CA's Subscriber Agreement and/or Terms of Service.
- func ManageSync(domainNames []string) error {
- DefaultACME.Agreed = true
- return NewDefault().ManageSync(domainNames)
- }
-
- // ManageAsync is the same as ManageSync, except that
- // certificates are managed asynchronously. This means
- // that the function will return before certificates
- // are ready, and errors that occur during certificate
- // obtain or renew operations are only logged. It is
- // vital that you monitor the logs if using this method,
- // which is only recommended for automated/non-interactive
- // environments.
- func ManageAsync(ctx context.Context, domainNames []string) error {
- DefaultACME.Agreed = true
- return NewDefault().ManageAsync(ctx, domainNames)
- }
-
- // OnDemandConfig configures on-demand TLS (certificate
- // operations as-needed, like during TLS handshakes,
- // rather than immediately).
- //
- // When this package's high-level convenience functions
- // are used (HTTPS, Manage, etc., where the Default
- // config is used as a template), this struct regulates
- // certificate operations using an implicit whitelist
- // containing the names passed into those functions if
- // no DecisionFunc is set. This ensures some degree of
- // control by default to avoid certificate operations for
- // aribtrary domain names. To override this whitelist,
- // manually specify a DecisionFunc. To impose rate limits,
- // specify your own DecisionFunc.
- type OnDemandConfig struct {
- // If set, this function will be called to determine
- // whether a certificate can be obtained or renewed
- // for the given name. If an error is returned, the
- // request will be denied.
- DecisionFunc func(name string) error
-
- // List of whitelisted hostnames (SNI values) for
- // deferred (on-demand) obtaining of certificates.
- // Used only by higher-level functions in this
- // package to persist the list of hostnames that
- // the config is supposed to manage. This is done
- // because it seems reasonable that if you say
- // "Manage [domain names...]", then only those
- // domain names should be able to have certs;
- // we don't NEED this feature, but it makes sense
- // for higher-level convenience functions to be
- // able to retain their convenience (alternative
- // is: the user manually creates a DecisionFunc
- // that whitelists the same names it already
- // passed into Manage) and without letting clients
- // have their run of any domain names they want.
- // Only enforced if len > 0.
- hostWhitelist []string
- }
-
- func (o *OnDemandConfig) whitelistContains(name string) bool {
- for _, n := range o.hostWhitelist {
- if strings.EqualFold(n, name) {
- return true
- }
- }
- return false
- }
-
- // isLoopback returns true if the hostname of addr looks
- // explicitly like a common local hostname. addr must only
- // be a host or a host:port combination.
- func isLoopback(addr string) bool {
- host := hostOnly(addr)
- return host == "localhost" ||
- strings.Trim(host, "[]") == "::1" ||
- strings.HasPrefix(host, "127.")
- }
-
- // isInternal returns true if the IP of addr
- // belongs to a private network IP range. addr
- // must only be an IP or an IP:port combination.
- // Loopback addresses are considered false.
- func isInternal(addr string) bool {
- privateNetworks := []string{
- "10.0.0.0/8",
- "172.16.0.0/12",
- "192.168.0.0/16",
- "fc00::/7",
- }
- host := hostOnly(addr)
- ip := net.ParseIP(host)
- if ip == nil {
- return false
- }
- for _, privateNetwork := range privateNetworks {
- _, ipnet, _ := net.ParseCIDR(privateNetwork)
- if ipnet.Contains(ip) {
- return true
- }
- }
- return false
- }
-
- // hostOnly returns only the host portion of hostport.
- // If there is no port or if there is an error splitting
- // the port off, the whole input string is returned.
- func hostOnly(hostport string) string {
- host, _, err := net.SplitHostPort(hostport)
- if err != nil {
- return hostport // OK; probably had no port to begin with
- }
- return host
- }
-
- // PreChecker is an interface that can be optionally implemented by
- // Issuers. Pre-checks are performed before each call (or batch of
- // identical calls) to Issue(), giving the issuer the option to ensure
- // it has all the necessary information/state.
- type PreChecker interface {
- PreCheck(ctx context.Context, names []string, interactive bool) error
- }
-
- // Issuer is a type that can issue certificates.
- type Issuer interface {
- // Issue obtains a certificate for the given CSR. It
- // must honor context cancellation if it is long-running.
- // It can also use the context to find out if the current
- // call is part of a retry, via AttemptsCtxKey.
- Issue(ctx context.Context, request *x509.CertificateRequest) (*IssuedCertificate, error)
-
- // IssuerKey must return a string that uniquely identifies
- // this particular configuration of the Issuer such that
- // any certificates obtained by this Issuer will be treated
- // as identical if they have the same SANs.
- //
- // Certificates obtained from Issuers with the same IssuerKey
- // will overwrite others with the same SANs. For example, an
- // Issuer might be able to obtain certificates from different
- // CAs, say A and B. It is likely that the CAs have different
- // use cases and purposes (e.g. testing and production), so
- // their respective certificates should not overwrite eaach
- // other.
- IssuerKey() string
- }
-
- // Revoker can revoke certificates. Reason codes are defined
- // by RFC 5280 §5.3.1: https://tools.ietf.org/html/rfc5280#section-5.3.1
- // and are available as constants in our ACME library.
- type Revoker interface {
- Revoke(ctx context.Context, cert CertificateResource, reason int) error
- }
-
- // KeyGenerator can generate a private key.
- type KeyGenerator interface {
- // GenerateKey generates a private key. The returned
- // PrivateKey must be able to expose its associated
- // public key.
- GenerateKey() (crypto.PrivateKey, error)
- }
-
- // IssuedCertificate represents a certificate that was just issued.
- type IssuedCertificate struct {
- // The PEM-encoding of DER-encoded ASN.1 data.
- Certificate []byte
-
- // Any extra information to serialize alongside the
- // certificate in storage.
- Metadata interface{}
- }
-
- // CertificateResource associates a certificate with its private
- // key and other useful information, for use in maintaining the
- // certificate.
- type CertificateResource struct {
- // The list of names on the certificate;
- // for convenience only.
- SANs []string `json:"sans,omitempty"`
-
- // The PEM-encoding of DER-encoded ASN.1 data
- // for the cert or chain.
- CertificatePEM []byte `json:"-"`
-
- // The PEM-encoding of the certificate's private key.
- PrivateKeyPEM []byte `json:"-"`
-
- // Any extra information associated with the certificate,
- // usually provided by the issuer implementation.
- IssuerData interface{} `json:"issuer_data,omitempty"`
- }
-
- // NamesKey returns the list of SANs as a single string,
- // truncated to some ridiculously long size limit. It
- // can act as a key for the set of names on the resource.
- func (cr *CertificateResource) NamesKey() string {
- sort.Strings(cr.SANs)
- result := strings.Join(cr.SANs, ",")
- if len(result) > 1024 {
- const trunc = "_trunc"
- result = result[:1024-len(trunc)] + trunc
- }
- return result
- }
-
- // Default contains the package defaults for the
- // various Config fields. This is used as a template
- // when creating your own Configs with New(), and it
- // is also used as the Config by all the high-level
- // functions in this package.
- //
- // The fields of this value will be used for Config
- // fields which are unset. Feel free to modify these
- // defaults, but do not use this Config by itself: it
- // is only a template. Valid configurations can be
- // obtained by calling New() (if you have your own
- // certificate cache) or NewDefault() (if you only
- // need a single config and want to use the default
- // cache). This is the only Config which can access
- // the default certificate cache.
- var Default = Config{
- RenewalWindowRatio: DefaultRenewalWindowRatio,
- Storage: defaultFileStorage,
- KeySource: DefaultKeyGenerator,
- }
-
- const (
- // HTTPChallengePort is the officially-designated port for
- // the HTTP challenge according to the ACME spec.
- HTTPChallengePort = 80
-
- // TLSALPNChallengePort is the officially-designated port for
- // the TLS-ALPN challenge according to the ACME spec.
- TLSALPNChallengePort = 443
- )
-
- // Port variables must remain their defaults unless you
- // forward packets from the defaults to whatever these
- // are set to; otherwise ACME challenges will fail.
- var (
- // HTTPPort is the port on which to serve HTTP
- // and, by extension, the HTTP challenge (unless
- // Default.AltHTTPPort is set).
- HTTPPort = 80
-
- // HTTPSPort is the port on which to serve HTTPS
- // and, by extension, the TLS-ALPN challenge
- // (unless Default.AltTLSALPNPort is set).
- HTTPSPort = 443
- )
-
- // Variables for conveniently serving HTTPS.
- var (
- httpLn, httpsLn net.Listener
- lnMu sync.Mutex
- httpWg sync.WaitGroup
- )
-
- // Maximum size for the stack trace when recovering from panics.
- const stackTraceBufferSize = 1024 * 128
|