package caddytls import ( "crypto/tls" "crypto/x509" "fmt" "io/ioutil" "net/url" "strings" "github.com/mholt/caddy" "github.com/xenolf/lego/acme" ) // Config describes how TLS should be configured and used. type Config struct { // The hostname or class of hostnames this config is // designated for; can contain wildcard characters // according to RFC 6125 ยง6.4.3 - this field MUST // be set in order for things to work as expected Hostname string // Whether TLS is enabled Enabled bool // Minimum and maximum protocol versions to allow ProtocolMinVersion uint16 ProtocolMaxVersion uint16 // The list of cipher suites; first should be // TLS_FALLBACK_SCSV to prevent degrade attacks Ciphers []uint16 // Whether to prefer server cipher suites PreferServerCipherSuites bool // The list of preferred curves CurvePreferences []tls.CurveID // Client authentication policy ClientAuth tls.ClientAuthType // List of client CA certificates to allow, if // client authentication is enabled ClientCerts []string // Manual means user provides own certs and keys Manual bool // Managed means config qualifies for implicit, // automatic, managed TLS; as opposed to the user // providing and managing the certificate manually Managed bool // OnDemand means the class of hostnames this // config applies to may obtain and manage // certificates at handshake-time (as opposed // to pre-loaded at startup); OnDemand certs // will be managed the same way as preloaded // ones, however, if an OnDemand cert fails to // renew, it is removed from the in-memory // cache; if this is true, Managed must // necessarily be true OnDemand bool // SelfSigned means that this hostname is // served with a self-signed certificate // that we generated in memory for convenience SelfSigned bool // The endpoint of the directory for the ACME // CA we are to use CAUrl string // The host (ONLY the host, not port) to listen // on if necessary to start a listener to solve // an ACME challenge ListenHost string // The alternate port (ONLY port, not host) // to use for the ACME HTTP challenge; this // port will be used if we proxy challenges // coming in on port 80 to this alternate port AltHTTPPort string // The alternate port (ONLY port, not host) // to use for the ACME TLS-SNI challenge. // The system must forward the standard port // for the TLS-SNI challenge to this port. AltTLSSNIPort string // The string identifier of the DNS provider // to use when solving the ACME DNS challenge DNSProvider string // The email address to use when creating or // using an ACME account (fun fact: if this // is set to "off" then this config will not // qualify for managed TLS) ACMEEmail string // The type of key to use when generating // certificates KeyType acme.KeyType // The storage creator; use StorageFor() to get a guaranteed // non-nil Storage instance. Note, Caddy may call this frequently // so implementors are encouraged to cache any heavy instantiations. StorageProvider string // The state needed to operate on-demand TLS OnDemandState OnDemandState // Add the must staple TLS extension to the CSR generated by lego/acme MustStaple bool // The list of protocols to choose from for Application Layer // Protocol Negotiation (ALPN). ALPN []string tlsConfig *tls.Config // the final tls.Config created with buildStandardTLSConfig() } // OnDemandState contains some state relevant for providing // on-demand TLS. type OnDemandState struct { // The number of certificates that have been issued on-demand // by this config. It is only safe to modify this count atomically. // If it reaches MaxObtain, on-demand issuances must fail. ObtainedCount int32 // Set from max_certs in tls config, it specifies the // maximum number of certificates that can be issued. MaxObtain int32 } // ObtainCert obtains a certificate for name using c, as long // as a certificate does not already exist in storage for that // name. The name must qualify and c must be flagged as Managed. // This function is a no-op if storage already has a certificate // for name. // // It only obtains and stores certificates (and their keys), // it does not load them into memory. If allowPrompts is true, // the user may be shown a prompt. func (c *Config) ObtainCert(name string, allowPrompts bool) error { if !c.Managed || !HostQualifies(name) { return nil } storage, err := c.StorageFor(c.CAUrl) if err != nil { return err } siteExists, err := storage.SiteExists(name) if err != nil { return err } if siteExists { return nil } if c.ACMEEmail == "" { c.ACMEEmail = getEmail(storage, allowPrompts) } client, err := newACMEClient(c, allowPrompts) if err != nil { return err } return client.Obtain(name) } // RenewCert renews the certificate for name using c. It stows the // renewed certificate and its assets in storage if successful. func (c *Config) RenewCert(name string, allowPrompts bool) error { client, err := newACMEClient(c, allowPrompts) if err != nil { return err } return client.Renew(name) } // StorageFor obtains a TLS Storage instance for the given CA URL which should // be unique for every different ACME CA. If a StorageCreator is set on this // Config, it will be used. Otherwise the default file storage implementation // is used. When the error is nil, this is guaranteed to return a non-nil // Storage instance. func (c *Config) StorageFor(caURL string) (Storage, error) { // Validate CA URL if caURL == "" { caURL = DefaultCAUrl } if caURL == "" { return nil, fmt.Errorf("cannot create storage without CA URL") } caURL = strings.ToLower(caURL) // scheme required or host will be parsed as path (as of Go 1.6) if !strings.Contains(caURL, "://") { caURL = "https://" + caURL } u, err := url.Parse(caURL) if err != nil { return nil, fmt.Errorf("%s: unable to parse CA URL: %v", caURL, err) } if u.Host == "" { return nil, fmt.Errorf("%s: no host in CA URL", caURL) } // Create the storage based on the URL var s Storage if c.StorageProvider == "" { c.StorageProvider = "file" } creator, ok := storageProviders[c.StorageProvider] if !ok { return nil, fmt.Errorf("%s: Unknown storage: %v", caURL, c.StorageProvider) } s, err = creator(u) if err != nil { return nil, fmt.Errorf("%s: unable to create custom storage '%v': %v", caURL, c.StorageProvider, err) } return s, nil } // buildStandardTLSConfig converts cfg (*caddytls.Config) to a *tls.Config // and stores it in cfg so it can be used in servers. If TLS is disabled, // no tls.Config is created. func (cfg *Config) buildStandardTLSConfig() error { if !cfg.Enabled { return nil } config := new(tls.Config) ciphersAdded := make(map[uint16]struct{}) curvesAdded := make(map[tls.CurveID]struct{}) // add cipher suites for _, ciph := range cfg.Ciphers { if _, ok := ciphersAdded[ciph]; !ok { ciphersAdded[ciph] = struct{}{} config.CipherSuites = append(config.CipherSuites, ciph) } } config.PreferServerCipherSuites = cfg.PreferServerCipherSuites // add curve preferences for _, curv := range cfg.CurvePreferences { if _, ok := curvesAdded[curv]; !ok { curvesAdded[curv] = struct{}{} config.CurvePreferences = append(config.CurvePreferences, curv) } } config.MinVersion = cfg.ProtocolMinVersion config.MaxVersion = cfg.ProtocolMaxVersion config.ClientAuth = cfg.ClientAuth config.NextProtos = cfg.ALPN config.GetCertificate = cfg.GetCertificate // set up client authentication if enabled if config.ClientAuth != tls.NoClientCert { pool := x509.NewCertPool() clientCertsAdded := make(map[string]struct{}) for _, caFile := range cfg.ClientCerts { // don't add cert to pool more than once if _, ok := clientCertsAdded[caFile]; ok { continue } clientCertsAdded[caFile] = struct{}{} // Any client with a certificate from this CA will be allowed to connect caCrt, err := ioutil.ReadFile(caFile) if err != nil { return err } if !pool.AppendCertsFromPEM(caCrt) { return fmt.Errorf("error loading client certificate '%s': no certificates were successfully parsed", caFile) } } config.ClientCAs = pool } // default cipher suites if len(config.CipherSuites) == 0 { config.CipherSuites = defaultCiphers } // for security, ensure TLS_FALLBACK_SCSV is always included first if len(config.CipherSuites) == 0 || config.CipherSuites[0] != tls.TLS_FALLBACK_SCSV { config.CipherSuites = append([]uint16{tls.TLS_FALLBACK_SCSV}, config.CipherSuites...) } // store the resulting new tls.Config cfg.tlsConfig = config return nil } // MakeTLSConfig makes a tls.Config from configs. The returned // tls.Config is programmed to load the matching caddytls.Config // based on the hostname in SNI, but that's all. func MakeTLSConfig(configs []*Config) (*tls.Config, error) { if len(configs) == 0 { return nil, nil } configMap := make(configGroup) for i, cfg := range configs { if cfg == nil { // avoid nil pointer dereference below this loop configs[i] = new(Config) continue } // can't serve TLS and non-TLS on same port if i > 0 && cfg.Enabled != configs[i-1].Enabled { thisConfProto, lastConfProto := "not TLS", "not TLS" if cfg.Enabled { thisConfProto = "TLS" } if configs[i-1].Enabled { lastConfProto = "TLS" } return nil, fmt.Errorf("cannot multiplex %s (%s) and %s (%s) on same listener", configs[i-1].Hostname, lastConfProto, cfg.Hostname, thisConfProto) } // convert each caddytls.Config into a tls.Config if err := cfg.buildStandardTLSConfig(); err != nil { return nil, err } // Key this config by its hostname (overwriting // configs with the same hostname pattern); during // TLS handshakes, configs are loaded based on // the hostname pattern, according to client's SNI. configMap[cfg.Hostname] = cfg } // Is TLS disabled? By now, we know that all // configs agree whether it is or not, so we // can just look at the first one. If so, // we're done here. if len(configs) == 0 || !configs[0].Enabled { return nil, nil } return &tls.Config{ GetConfigForClient: configMap.GetConfigForClient, }, nil } // ConfigGetter gets a Config keyed by key. type ConfigGetter func(c *caddy.Controller) *Config var configGetters = make(map[string]ConfigGetter) // RegisterConfigGetter registers fn as the way to get a // Config for server type serverType. func RegisterConfigGetter(serverType string, fn ConfigGetter) { configGetters[serverType] = fn } // SetDefaultTLSParams sets the default TLS cipher suites, protocol versions, // and server preferences of a server.Config if they were not previously set // (it does not overwrite; only fills in missing values). func SetDefaultTLSParams(config *Config) { // If no ciphers provided, use default list if len(config.Ciphers) == 0 { config.Ciphers = defaultCiphers } // Not a cipher suite, but still important for mitigating protocol downgrade attacks // (prepend since having it at end breaks http2 due to non-h2-approved suites before it) config.Ciphers = append([]uint16{tls.TLS_FALLBACK_SCSV}, config.Ciphers...) // If no curves provided, use default list if len(config.CurvePreferences) == 0 { config.CurvePreferences = defaultCurves } // Set default protocol min and max versions - must balance compatibility and security if config.ProtocolMinVersion == 0 { config.ProtocolMinVersion = tls.VersionTLS11 } if config.ProtocolMaxVersion == 0 { config.ProtocolMaxVersion = tls.VersionTLS12 } // Prefer server cipher suites config.PreferServerCipherSuites = true } // Map of supported key types var supportedKeyTypes = map[string]acme.KeyType{ "P384": acme.EC384, "P256": acme.EC256, "RSA8192": acme.RSA8192, "RSA4096": acme.RSA4096, "RSA2048": acme.RSA2048, } // Map of supported protocols. // HTTP/2 only supports TLS 1.2 and higher. var supportedProtocols = map[string]uint16{ "tls1.0": tls.VersionTLS10, "tls1.1": tls.VersionTLS11, "tls1.2": tls.VersionTLS12, } // Map of supported ciphers, used only for parsing config. // // Note that, at time of writing, HTTP/2 blacklists 276 cipher suites, // including all but four of the suites below (the four GCM suites). // See https://http2.github.io/http2-spec/#BadCipherSuites // // TLS_FALLBACK_SCSV is not in this list because we manually ensure // it is always added (even though it is not technically a cipher suite). // // This map, like any map, is NOT ORDERED. Do not range over this map. var supportedCiphersMap = map[string]uint16{ "ECDHE-RSA-AES256-GCM-SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "ECDHE-ECDSA-AES256-GCM-SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "ECDHE-RSA-AES128-GCM-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "ECDHE-ECDSA-AES128-GCM-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "ECDHE-RSA-WITH-CHACHA20-POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, "ECDHE-ECDSA-WITH-CHACHA20-POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, "ECDHE-RSA-AES128-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, "ECDHE-RSA-AES256-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, "ECDHE-ECDSA-AES256-CBC-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, "ECDHE-ECDSA-AES128-CBC-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, "RSA-AES128-CBC-SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA, "RSA-AES256-CBC-SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA, "ECDHE-RSA-3DES-EDE-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, "RSA-3DES-EDE-CBC-SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, } // List of all the ciphers we want to use by default var defaultCiphers = []uint16{ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, } // Map of supported curves // https://golang.org/pkg/crypto/tls/#CurveID var supportedCurvesMap = map[string]tls.CurveID{ "X25519": tls.X25519, "P256": tls.CurveP256, "P384": tls.CurveP384, "P521": tls.CurveP521, } // List of all the curves we want to use by default // // This list should only include curves which are fast by design (e.g. X25519) // and those for which an optimized assembly implementation exists (e.g. P256). // The latter ones can be found here: https://github.com/golang/go/tree/master/src/crypto/elliptic var defaultCurves = []tls.CurveID{ tls.X25519, tls.CurveP256, } const ( // HTTPChallengePort is the officially designated port for // the HTTP challenge according to the ACME spec. HTTPChallengePort = "80" // TLSSNIChallengePort is the officially designated port for // the TLS-SNI challenge according to the ACME spec. TLSSNIChallengePort = "443" // DefaultHTTPAlternatePort is the port on which the ACME // client will open a listener and solve the HTTP challenge. // If this alternate port is used instead of the default // port, then whatever is listening on the default port must // be capable of proxying or forwarding the request to this // alternate port. DefaultHTTPAlternatePort = "5033" )