summaryrefslogtreecommitdiff
path: root/modules/caddytls
diff options
context:
space:
mode:
authorMatthew Holt <mholt@users.noreply.github.com>2019-04-25 13:54:48 -0600
committerMatthew Holt <mholt@users.noreply.github.com>2019-04-25 13:54:48 -0600
commit2d056fbe66849f041a233a0d961639fae3835cbb (patch)
treedc78505933861e01f615470ffc1dd56a852da0b8 /modules/caddytls
parent545f28008e0175491af030f8689cab2112fda9ed (diff)
Initial commit of Storage, TLS, and automatic HTTPS implementations
Diffstat (limited to 'modules/caddytls')
-rw-r--r--modules/caddytls/acmemanager.go84
-rw-r--r--modules/caddytls/connpolicy.go149
-rw-r--r--modules/caddytls/fileloader.go61
-rw-r--r--modules/caddytls/folderloader.go122
-rw-r--r--modules/caddytls/matchers.go79
-rw-r--r--modules/caddytls/tls.go359
6 files changed, 854 insertions, 0 deletions
diff --git a/modules/caddytls/acmemanager.go b/modules/caddytls/acmemanager.go
new file mode 100644
index 0000000..a7a460a
--- /dev/null
+++ b/modules/caddytls/acmemanager.go
@@ -0,0 +1,84 @@
+package caddytls
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/go-acme/lego/certcrypto"
+
+ "bitbucket.org/lightcodelabs/caddy2"
+ "github.com/go-acme/lego/challenge"
+ "github.com/mholt/certmagic"
+)
+
+func init() {
+ caddy2.RegisterModule(caddy2.Module{
+ Name: "tls.management.acme",
+ New: func() (interface{}, error) { return new(acmeManagerMaker), nil },
+ })
+}
+
+// ManagerMaker TODO: WIP...
+type ManagerMaker interface {
+ newManager(interactive bool) (certmagic.Manager, error)
+}
+
+// acmeManagerMaker makes an ACME manager
+// for managinig certificates using ACME.
+type acmeManagerMaker struct {
+ CA string `json:"ca,omitempty"`
+ Email string `json:"email,omitempty"`
+ RenewAhead caddy2.Duration `json:"renew_ahead,omitempty"`
+ KeyType string `json:"key_type,omitempty"`
+ ACMETimeout caddy2.Duration `json:"acme_timeout,omitempty"`
+ MustStaple bool `json:"must_staple,omitempty"`
+ Challenges ChallengesConfig `json:"challenges"`
+ OnDemand *OnDemandConfig `json:"on_demand,omitempty"`
+ Storage json.RawMessage `json:"storage,omitempty"`
+
+ storage certmagic.Storage
+ keyType certcrypto.KeyType
+}
+
+func (m *acmeManagerMaker) Provision() error {
+ m.setDefaults()
+
+ // DNS providers
+ if m.Challenges.DNS != nil {
+ val, err := caddy2.LoadModuleInline("provider", "tls.dns", m.Challenges.DNS)
+ if err != nil {
+ return fmt.Errorf("loading TLS storage module: %s", err)
+ }
+ m.Challenges.dns = val.(challenge.Provider)
+ m.Challenges.DNS = nil // allow GC to deallocate - TODO: Does this help?
+ }
+
+ // policy-specific storage implementation
+ if m.Storage != nil {
+ val, err := caddy2.LoadModuleInline("system", "caddy.storage", m.Storage)
+ if err != nil {
+ return fmt.Errorf("loading TLS storage module: %s", err)
+ }
+ cmStorage, err := val.(caddy2.StorageConverter).CertMagicStorage()
+ if err != nil {
+ return fmt.Errorf("creating TLS storage configuration: %v", err)
+ }
+ m.storage = cmStorage
+ m.Storage = nil // allow GC to deallocate - TODO: Does this help?
+ }
+
+ return nil
+}
+
+// setDefaults indiscriminately sets all the default values in m.
+func (m *acmeManagerMaker) setDefaults() {
+ m.CA = certmagic.LetsEncryptStagingCA // certmagic.Default.CA // TODO: When not testing, switch to production CA
+ m.Email = certmagic.Default.Email
+ m.RenewAhead = caddy2.Duration(certmagic.Default.RenewDurationBefore)
+ m.keyType = certmagic.Default.KeyType
+ m.storage = certmagic.Default.Storage
+}
+
+func (m *acmeManagerMaker) newManager(interactive bool) (certmagic.Manager, error) {
+ return nil, nil
+}
diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go
new file mode 100644
index 0000000..9400034
--- /dev/null
+++ b/modules/caddytls/connpolicy.go
@@ -0,0 +1,149 @@
+package caddytls
+
+import (
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+
+ "bitbucket.org/lightcodelabs/caddy2"
+ "github.com/go-acme/lego/challenge/tlsalpn01"
+ "github.com/mholt/certmagic"
+)
+
+// ConnectionPolicies is an ordered group of connection policies;
+// the first matching policy will be used to configure TLS
+// connections at handshake-time.
+type ConnectionPolicies []*ConnectionPolicy
+
+// TLSConfig converts the group of policies to a standard-lib-compatible
+// TLS configuration which selects the first matching policy based on
+// the ClientHello.
+func (cp ConnectionPolicies) TLSConfig(handle caddy2.Handle) (*tls.Config, error) {
+ // connection policy matchers
+ for i, pol := range cp {
+ for modName, rawMsg := range pol.MatchersRaw {
+ val, err := caddy2.LoadModule("tls.handshake_match."+modName, rawMsg)
+ if err != nil {
+ return nil, fmt.Errorf("loading handshake matcher module '%s': %s", modName, err)
+ }
+ cp[i].Matchers = append(cp[i].Matchers, val.(ConnectionMatcher))
+ }
+ cp[i].MatchersRaw = nil // allow GC to deallocate - TODO: Does this help?
+ }
+
+ // pre-build standard TLS configs so we don't have to at handshake-time
+ for i := range cp {
+ err := cp[i].buildStandardTLSConfig(handle)
+ if err != nil {
+ return nil, fmt.Errorf("connection policy %d: building standard TLS config: %s", i, err)
+ }
+ }
+
+ return &tls.Config{
+ GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
+ policyLoop:
+ for _, pol := range cp {
+ for _, matcher := range pol.Matchers {
+ if !matcher.Match(hello) {
+ continue policyLoop
+ }
+ }
+ return pol.stdTLSConfig, nil
+ }
+ return nil, fmt.Errorf("no server TLS configuration available for ClientHello: %+v", hello)
+ },
+ }, nil
+}
+
+// ConnectionPolicy specifies the logic for handling a TLS handshake.
+type ConnectionPolicy struct {
+ MatchersRaw map[string]json.RawMessage `json:"match,omitempty"`
+
+ CipherSuites []string `json:"cipher_suites,omitempty"`
+ Curves []string `json:"curves,omitempty"`
+ ALPN []string `json:"alpn,omitempty"`
+ ProtocolMin string `json:"protocol_min,omitempty"`
+ ProtocolMax string `json:"protocol_max,omitempty"`
+
+ // TODO: Client auth
+
+ // TODO: see if starlark could be useful here - enterprise only
+ StarlarkHandshake string `json:"starlark_handshake,omitempty"`
+
+ Matchers []ConnectionMatcher
+ stdTLSConfig *tls.Config
+}
+
+func (cp *ConnectionPolicy) buildStandardTLSConfig(handle caddy2.Handle) error {
+ tlsApp := handle.App("tls").(*TLS)
+
+ cfg := &tls.Config{
+ NextProtos: cp.ALPN,
+ PreferServerCipherSuites: true,
+ GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ cfgTpl, err := tlsApp.getConfigForName(hello.ServerName)
+ if err != nil {
+ return nil, fmt.Errorf("getting config for name %s: %v", hello.ServerName, err)
+ }
+ newCfg := certmagic.New(tlsApp.certCache, cfgTpl)
+ return newCfg.GetCertificate(hello)
+ },
+ MinVersion: tls.VersionTLS12,
+ MaxVersion: tls.VersionTLS13,
+ // TODO: Session ticket key rotation (use Storage)
+ }
+
+ // add all the cipher suites in order, without duplicates
+ cipherSuitesAdded := make(map[uint16]struct{})
+ for _, csName := range cp.CipherSuites {
+ csID := supportedCipherSuites[csName]
+ if _, ok := cipherSuitesAdded[csID]; !ok {
+ cipherSuitesAdded[csID] = struct{}{}
+ cfg.CipherSuites = append(cfg.CipherSuites, csID)
+ }
+ }
+
+ // add all the curve preferences in order, without duplicates
+ curvesAdded := make(map[tls.CurveID]struct{})
+ for _, curveName := range cp.Curves {
+ curveID := supportedCurves[curveName]
+ if _, ok := curvesAdded[curveID]; !ok {
+ curvesAdded[curveID] = struct{}{}
+ cfg.CurvePreferences = append(cfg.CurvePreferences, curveID)
+ }
+ }
+
+ // ensure ALPN includes the ACME TLS-ALPN protocol
+ var alpnFound bool
+ for _, a := range cp.ALPN {
+ if a == tlsalpn01.ACMETLS1Protocol {
+ alpnFound = true
+ break
+ }
+ }
+ if !alpnFound {
+ cfg.NextProtos = append(cfg.NextProtos, tlsalpn01.ACMETLS1Protocol)
+ }
+
+ // min and max protocol versions
+ if cp.ProtocolMin != "" {
+ cfg.MinVersion = supportedProtocols[cp.ProtocolMin]
+ }
+ if cp.ProtocolMax != "" {
+ cfg.MaxVersion = supportedProtocols[cp.ProtocolMax]
+ }
+ if cp.ProtocolMin > cp.ProtocolMax {
+ return fmt.Errorf("protocol min (%x) cannot be greater than protocol max (%x)", cp.ProtocolMin, cp.ProtocolMax)
+ }
+
+ // TODO: client auth, and other fields
+
+ cp.stdTLSConfig = cfg
+
+ return nil
+}
+
+// ConnectionMatcher is a type which matches TLS handshakes.
+type ConnectionMatcher interface {
+ Match(*tls.ClientHelloInfo) bool
+}
diff --git a/modules/caddytls/fileloader.go b/modules/caddytls/fileloader.go
new file mode 100644
index 0000000..fae2275
--- /dev/null
+++ b/modules/caddytls/fileloader.go
@@ -0,0 +1,61 @@
+package caddytls
+
+import (
+ "crypto/tls"
+ "fmt"
+ "io/ioutil"
+
+ "bitbucket.org/lightcodelabs/caddy2"
+)
+
+func init() {
+ caddy2.RegisterModule(caddy2.Module{
+ Name: "tls.certificates.load_files",
+ New: func() (interface{}, error) { return fileLoader{}, nil },
+ })
+}
+
+// fileLoader loads certificates and their associated keys from disk.
+type fileLoader []CertKeyFilePair
+
+// CertKeyFilePair pairs certificate and key file names along with their
+// encoding format so that they can be loaded from disk.
+type CertKeyFilePair struct {
+ Certificate string `json:"certificate"`
+ Key string `json:"key"`
+ Format string `json:"format,omitempty"` // "pem" is default
+}
+
+// LoadCertificates returns the certificates to be loaded by fl.
+func (fl fileLoader) LoadCertificates() ([]tls.Certificate, error) {
+ var certs []tls.Certificate
+ for _, pair := range fl {
+ certData, err := ioutil.ReadFile(pair.Certificate)
+ if err != nil {
+ return nil, err
+ }
+ keyData, err := ioutil.ReadFile(pair.Key)
+ if err != nil {
+ return nil, err
+ }
+
+ var cert tls.Certificate
+ switch pair.Format {
+ case "":
+ fallthrough
+ case "pem":
+ cert, err = tls.X509KeyPair(certData, keyData)
+ default:
+ return nil, fmt.Errorf("unrecognized certificate/key encoding format: %s", pair.Format)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ certs = append(certs, cert)
+ }
+ return certs, nil
+}
+
+// Interface guard
+var _ CertificateLoader = (fileLoader)(nil)
diff --git a/modules/caddytls/folderloader.go b/modules/caddytls/folderloader.go
new file mode 100644
index 0000000..9d46502
--- /dev/null
+++ b/modules/caddytls/folderloader.go
@@ -0,0 +1,122 @@
+package caddytls
+
+import (
+ "bytes"
+ "crypto/tls"
+ "encoding/pem"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "bitbucket.org/lightcodelabs/caddy2"
+)
+
+func init() {
+ caddy2.RegisterModule(caddy2.Module{
+ Name: "tls.certificates.load_folders",
+ New: func() (interface{}, error) { return folderLoader{}, nil },
+ })
+}
+
+// folderLoader loads certificates and their associated keys from disk
+// by recursively walking the specified directories, looking for PEM
+// files which contain both a certificate and a key.
+type folderLoader []string
+
+// LoadCertificates loads all the certificates+keys in the directories
+// listed in fl from all files ending with .pem. This method of loading
+// certificates expects the certificate and key to be bundled into the
+// same file.
+func (fl folderLoader) LoadCertificates() ([]tls.Certificate, error) {
+ var certs []tls.Certificate
+ for _, dir := range fl {
+ err := filepath.Walk(dir, func(fpath string, info os.FileInfo, err error) error {
+ if err != nil {
+ return fmt.Errorf("unable to traverse into path: %s", fpath)
+ }
+ if info.IsDir() {
+ return nil
+ }
+ if !strings.HasSuffix(strings.ToLower(info.Name()), ".pem") {
+ return nil
+ }
+
+ cert, err := x509CertFromCertAndKeyPEMFile(fpath)
+ if err != nil {
+ return err
+ }
+
+ certs = append(certs, cert)
+
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ }
+ return certs, nil
+}
+
+func x509CertFromCertAndKeyPEMFile(fpath string) (tls.Certificate, error) {
+ bundle, err := ioutil.ReadFile(fpath)
+ if err != nil {
+ return tls.Certificate{}, err
+ }
+
+ certBuilder, keyBuilder := new(bytes.Buffer), new(bytes.Buffer)
+ var foundKey bool // use only the first key in the file
+
+ for {
+ // Decode next block so we can see what type it is
+ var derBlock *pem.Block
+ derBlock, bundle = pem.Decode(bundle)
+ if derBlock == nil {
+ break
+ }
+
+ if derBlock.Type == "CERTIFICATE" {
+ // Re-encode certificate as PEM, appending to certificate chain
+ pem.Encode(certBuilder, derBlock)
+ } else if derBlock.Type == "EC PARAMETERS" {
+ // EC keys generated from openssl can be composed of two blocks:
+ // parameters and key (parameter block should come first)
+ if !foundKey {
+ // Encode parameters
+ pem.Encode(keyBuilder, derBlock)
+
+ // Key must immediately follow
+ derBlock, bundle = pem.Decode(bundle)
+ if derBlock == nil || derBlock.Type != "EC PRIVATE KEY" {
+ return tls.Certificate{}, fmt.Errorf("%s: expected elliptic private key to immediately follow EC parameters", fpath)
+ }
+ pem.Encode(keyBuilder, derBlock)
+ foundKey = true
+ }
+ } else if derBlock.Type == "PRIVATE KEY" || strings.HasSuffix(derBlock.Type, " PRIVATE KEY") {
+ // RSA key
+ if !foundKey {
+ pem.Encode(keyBuilder, derBlock)
+ foundKey = true
+ }
+ } else {
+ return tls.Certificate{}, fmt.Errorf("%s: unrecognized PEM block type: %s", fpath, derBlock.Type)
+ }
+ }
+
+ certPEMBytes, keyPEMBytes := certBuilder.Bytes(), keyBuilder.Bytes()
+ if len(certPEMBytes) == 0 {
+ return tls.Certificate{}, fmt.Errorf("%s: failed to parse PEM data", fpath)
+ }
+ if len(keyPEMBytes) == 0 {
+ return tls.Certificate{}, fmt.Errorf("%s: no private key block found", fpath)
+ }
+
+ cert, err := tls.X509KeyPair(certPEMBytes, keyPEMBytes)
+ if err != nil {
+ return tls.Certificate{}, fmt.Errorf("%s: making X509 key pair: %v", fpath, err)
+ }
+
+ return cert, nil
+}
diff --git a/modules/caddytls/matchers.go b/modules/caddytls/matchers.go
new file mode 100644
index 0000000..c376f87
--- /dev/null
+++ b/modules/caddytls/matchers.go
@@ -0,0 +1,79 @@
+package caddytls
+
+import (
+ "crypto/tls"
+
+ "bitbucket.org/lightcodelabs/caddy2"
+)
+
+type (
+ MatchServerName []string
+
+ // TODO: these others should be enterprise-only, probably
+ MatchProtocol []string // TODO: version or protocol?
+ MatchClientCert struct{} // TODO: client certificate options
+ MatchRemote []string
+ MatchStarlark string
+)
+
+func init() {
+ caddy2.RegisterModule(caddy2.Module{
+ Name: "tls.handshake_match.host",
+ New: func() (interface{}, error) { return MatchServerName{}, nil },
+ })
+ caddy2.RegisterModule(caddy2.Module{
+ Name: "tls.handshake_match.protocol",
+ New: func() (interface{}, error) { return MatchProtocol{}, nil },
+ })
+ caddy2.RegisterModule(caddy2.Module{
+ Name: "tls.handshake_match.client_cert",
+ New: func() (interface{}, error) { return MatchClientCert{}, nil },
+ })
+ caddy2.RegisterModule(caddy2.Module{
+ Name: "tls.handshake_match.remote",
+ New: func() (interface{}, error) { return MatchRemote{}, nil },
+ })
+ caddy2.RegisterModule(caddy2.Module{
+ Name: "tls.handshake_match.starlark",
+ New: func() (interface{}, error) { return new(MatchStarlark), nil },
+ })
+}
+
+func (m MatchServerName) Match(hello *tls.ClientHelloInfo) bool {
+ for _, name := range m {
+ // TODO: support wildcards (and regex?)
+ if hello.ServerName == name {
+ return true
+ }
+ }
+ return false
+}
+
+func (m MatchProtocol) Match(hello *tls.ClientHelloInfo) bool {
+ // TODO: not implemented
+ return false
+}
+
+func (m MatchClientCert) Match(hello *tls.ClientHelloInfo) bool {
+ // TODO: not implemented
+ return false
+}
+
+func (m MatchRemote) Match(hello *tls.ClientHelloInfo) bool {
+ // TODO: not implemented
+ return false
+}
+
+func (m MatchStarlark) Match(hello *tls.ClientHelloInfo) bool {
+ // TODO: not implemented
+ return false
+}
+
+// Interface guards
+var (
+ _ ConnectionMatcher = MatchServerName{}
+ _ ConnectionMatcher = MatchProtocol{}
+ _ ConnectionMatcher = MatchClientCert{}
+ _ ConnectionMatcher = MatchRemote{}
+ _ ConnectionMatcher = new(MatchStarlark)
+)
diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go
new file mode 100644
index 0000000..43ad957
--- /dev/null
+++ b/modules/caddytls/tls.go
@@ -0,0 +1,359 @@
+package caddytls
+
+import (
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "time"
+
+ "bitbucket.org/lightcodelabs/caddy2"
+ "github.com/go-acme/lego/certcrypto"
+ "github.com/go-acme/lego/challenge"
+ "github.com/klauspost/cpuid"
+ "github.com/mholt/certmagic"
+)
+
+func init() {
+ caddy2.RegisterModule(caddy2.Module{
+ Name: "tls",
+ New: func() (interface{}, error) { return new(TLS), nil },
+ })
+}
+
+// TLS represents a process-wide TLS configuration.
+type TLS struct {
+ Certificates map[string]json.RawMessage `json:"certificates"`
+ Automation AutomationConfig `json:"automation"`
+
+ certificateLoaders []CertificateLoader
+ certCache *certmagic.Cache
+}
+
+// TODO: Finish stubbing out this two-phase setup process: prepare, then start...
+
+func (t *TLS) Provision() error {
+ // set up the certificate cache
+ // TODO: this makes a new cache every time; better to only make a new
+ // cache (or even better, add/remove only what is necessary) if the
+ // certificates config has been updated
+ t.certCache = certmagic.NewCache(certmagic.CacheOptions{
+ GetConfigForCert: func(cert certmagic.Certificate) (certmagic.Config, error) {
+ return t.getConfigForName(cert.Names[0])
+ },
+ })
+
+ for i, ap := range t.Automation.Policies {
+ val, err := caddy2.LoadModuleInline("module", "tls.management", ap.Management)
+ if err != nil {
+ return fmt.Errorf("loading TLS automation management module: %s", err)
+ }
+ t.Automation.Policies[i].management = val.(ManagerMaker)
+ t.Automation.Policies[i].Management = nil // allow GC to deallocate - TODO: Does this help?
+ }
+
+ // certificate loaders
+ for modName, rawMsg := range t.Certificates {
+ if modName == automateKey {
+ continue // special case; these will be loaded in later
+ }
+ val, err := caddy2.LoadModule("tls.certificates."+modName, rawMsg)
+ if err != nil {
+ return fmt.Errorf("loading certificate module '%s': %s", modName, err)
+ }
+ t.certificateLoaders = append(t.certificateLoaders, val.(CertificateLoader))
+ }
+
+ return nil
+}
+
+// Start activates the TLS module.
+func (t *TLS) Start(handle caddy2.Handle) error {
+ // load manual/static (unmanaged) certificates
+ for _, loader := range t.certificateLoaders {
+ certs, err := loader.LoadCertificates()
+ if err != nil {
+ return fmt.Errorf("loading certificates: %v", err)
+ }
+ magic := certmagic.New(t.certCache, certmagic.Config{
+ Storage: caddy2.GetStorage(),
+ })
+ for _, cert := range certs {
+ err := magic.CacheUnmanagedTLSCertificate(cert)
+ if err != nil {
+ return fmt.Errorf("caching unmanaged certificate: %v", err)
+ }
+ }
+ }
+
+ // load automated (managed) certificates
+ if automatedRawMsg, ok := t.Certificates[automateKey]; ok {
+ var names []string
+ err := json.Unmarshal(automatedRawMsg, &names)
+ if err != nil {
+ return fmt.Errorf("automate: decoding names: %v", err)
+ }
+ err = t.Manage(names)
+ if err != nil {
+ return fmt.Errorf("automate: managing %v: %v", names, err)
+ }
+ // for _, name := range names {
+ // t.Manage([]string{name)
+ // ap := t.getAutomationPolicyForName(name)
+ // magic := certmagic.New(t.certCache, ap.makeCertMagicConfig())
+ // err := magic.Manage([]string{name})
+ // if err != nil {
+ // return fmt.Errorf("automate: manage %s: %v", name, err)
+ // }
+ // }
+ }
+ t.Certificates = nil // allow GC to deallocate - TODO: Does this help?
+
+ return nil
+}
+
+// Stop stops the TLS module and cleans up any allocations.
+func (t *TLS) Stop() error {
+ if t.certCache != nil {
+ // TODO: ensure locks are cleaned up too... maybe in certmagic though
+ t.certCache.Stop()
+ }
+ return nil
+}
+
+// Manage immediately begins managing names according to the
+// matching automation policy.
+func (t *TLS) Manage(names []string) error {
+ for _, name := range names {
+ ap := t.getAutomationPolicyForName(name)
+ magic := certmagic.New(t.certCache, ap.makeCertMagicConfig())
+ err := magic.Manage([]string{name})
+ if err != nil {
+ return fmt.Errorf("automate: manage %s: %v", name, err)
+ }
+ }
+ return nil
+}
+
+// HandleHTTPChallenge ensures that the HTTP challenge is handled for the
+// certificate named by r.Host, if it is an HTTP challenge request.
+func (t *TLS) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool {
+ if !certmagic.LooksLikeHTTPChallenge(r) {
+ return false
+ }
+ ap := t.getAutomationPolicyForName(r.Host)
+ magic := certmagic.New(t.certCache, ap.makeCertMagicConfig())
+ return magic.HandleHTTPChallenge(w, r)
+}
+
+func (t *TLS) getConfigForName(name string) (certmagic.Config, error) {
+ ap := t.getAutomationPolicyForName(name)
+ return ap.makeCertMagicConfig(), nil
+}
+
+func (t *TLS) getAutomationPolicyForName(name string) AutomationPolicy {
+ for _, ap := range t.Automation.Policies {
+ if len(ap.Hosts) == 0 {
+ // no host filter is an automatic match
+ return ap
+ }
+ for _, h := range ap.Hosts {
+ if h == name {
+ return ap
+ }
+ }
+ }
+
+ // default automation policy
+ mgmt := new(acmeManagerMaker)
+ mgmt.setDefaults()
+ return AutomationPolicy{management: mgmt}
+}
+
+// CertificateLoader is a type that can load certificates.
+type CertificateLoader interface {
+ LoadCertificates() ([]tls.Certificate, error)
+}
+
+// AutomationConfig designates configuration for the
+// construction and use of ACME clients.
+type AutomationConfig struct {
+ Policies []AutomationPolicy `json:"policies,omitempty"`
+}
+
+// AutomationPolicy designates the policy for automating the
+// management of managed TLS certificates.
+type AutomationPolicy struct {
+ Hosts []string `json:"hosts,omitempty"`
+ Management json.RawMessage `json:"management"`
+
+ management ManagerMaker
+}
+
+func (ap AutomationPolicy) makeCertMagicConfig() certmagic.Config {
+ if acmeMgmt, ok := ap.management.(*acmeManagerMaker); ok {
+ // default, which is management via ACME
+
+ storage := acmeMgmt.storage
+ if storage == nil {
+ storage = caddy2.GetStorage()
+ }
+
+ var ond *certmagic.OnDemandConfig
+ if acmeMgmt.OnDemand != nil {
+ ond = &certmagic.OnDemandConfig{
+ // TODO: fill this out
+ }
+ }
+
+ return certmagic.Config{
+ CA: certmagic.LetsEncryptStagingCA, //ap.CA, // TODO: Restore true value
+ Email: acmeMgmt.Email,
+ Agreed: true,
+ DisableHTTPChallenge: acmeMgmt.Challenges.HTTP.Disabled,
+ DisableTLSALPNChallenge: acmeMgmt.Challenges.TLSALPN.Disabled,
+ RenewDurationBefore: time.Duration(acmeMgmt.RenewAhead),
+ AltHTTPPort: acmeMgmt.Challenges.HTTP.AlternatePort,
+ AltTLSALPNPort: acmeMgmt.Challenges.TLSALPN.AlternatePort,
+ DNSProvider: acmeMgmt.Challenges.dns,
+ KeyType: supportedCertKeyTypes[acmeMgmt.KeyType],
+ CertObtainTimeout: time.Duration(acmeMgmt.ACMETimeout),
+ OnDemand: ond,
+ MustStaple: acmeMgmt.MustStaple,
+ Storage: storage,
+ // TODO: listenHost
+ }
+ }
+
+ return certmagic.Config{
+ NewManager: ap.management.newManager,
+ }
+}
+
+// ChallengesConfig configures the ACME challenges.
+type ChallengesConfig struct {
+ HTTP HTTPChallengeConfig `json:"http"`
+ TLSALPN TLSALPNChallengeConfig `json:"tls-alpn"`
+ DNS json.RawMessage `json:"dns,omitempty"`
+
+ dns challenge.Provider
+}
+
+// HTTPChallengeConfig configures the ACME HTTP challenge.
+type HTTPChallengeConfig struct {
+ Disabled bool `json:"disabled,omitempty"`
+ AlternatePort int `json:"alternate_port,omitempty"`
+}
+
+// TLSALPNChallengeConfig configures the ACME TLS-ALPN challenge.
+type TLSALPNChallengeConfig struct {
+ Disabled bool `json:"disabled,omitempty"`
+ AlternatePort int `json:"alternate_port,omitempty"`
+}
+
+// OnDemandConfig configures on-demand TLS, for obtaining
+// needed certificates at handshake-time.
+type OnDemandConfig struct {
+ // TODO: MaxCertificates state might not endure reloads...
+ // MaxCertificates int `json:"max_certificates,omitempty"`
+ AskURL string `json:"ask_url,omitempty"`
+ AskStarlark string `json:"ask_starlark,omitempty"`
+}
+
+// supportedCertKeyTypes is all the key types that are supported
+// for certificates that are obtained through ACME.
+var supportedCertKeyTypes = map[string]certcrypto.KeyType{
+ "RSA2048": certcrypto.RSA2048,
+ "RSA4096": certcrypto.RSA4096,
+ "P256": certcrypto.EC256,
+ "P384": certcrypto.EC384,
+}
+
+// supportedCipherSuites is the unordered map of cipher suite
+// string names to their definition in crypto/tls.
+// TODO: might not be needed much longer, see:
+// https://github.com/golang/go/issues/30325
+var supportedCipherSuites = map[string]uint16{
+ "ECDHE_ECDSA_AES256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ "ECDHE_RSA_AES256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ "ECDHE_ECDSA_AES128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ "ECDHE_RSA_AES128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ "ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
+ "ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
+ "ECDHE_RSA_AES256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ "ECDHE_RSA_AES128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_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_AES256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+ "RSA_AES128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_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,
+}
+
+// defaultCipherSuites is the ordered list of all the cipher
+// suites we want to support by default, assuming AES-NI
+// (hardware acceleration for AES).
+var defaultCipherSuitesWithAESNI = []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,
+}
+
+// defaultCipherSuites is the ordered list of all the cipher
+// suites we want to support by default, assuming lack of
+// AES-NI (NO hardware acceleration for AES).
+var defaultCipherSuitesWithoutAESNI = []uint16{
+ tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
+ tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
+ 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,
+}
+
+// getOptimalDefaultCipherSuites returns an appropriate cipher
+// suite to use depending on the hardware support for AES.
+//
+// See https://github.com/mholt/caddy/issues/1674
+func getOptimalDefaultCipherSuites() []uint16 {
+ if cpuid.CPU.AesNi() {
+ return defaultCipherSuitesWithAESNI
+ }
+ return defaultCipherSuitesWithoutAESNI
+}
+
+// supportedCurves is the unordered map of supported curves.
+// https://golang.org/pkg/crypto/tls/#CurveID
+var supportedCurves = map[string]tls.CurveID{
+ "X25519": tls.X25519,
+ "P256": tls.CurveP256,
+ "P384": tls.CurveP384,
+ "P521": tls.CurveP521,
+}
+
+// defaultCurves is the list of only the curves we want to use
+// by default, in descending order of preference.
+//
+// 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,
+}
+
+// supportedProtocols is a 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,
+ "tls1.3": tls.VersionTLS13,
+}
+
+const automateKey = "automate"