mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-03 19:17:29 -05:00 
			
		
		
		
	Extract most of caddytls core code into external CertMagic package
All code relating to a caddytls.Config and setting it up from the Caddyfile is still intact; only the certificate management-related code was removed into a separate package. I don't expect this to build in CI successfully; updating dependencies and vendor is coming next. I've also removed the ad-hoc, half-baked storage plugins that we need to finish making first-class Caddy plugins (they were never documented anyway). The new certmagic package has a much better storage interface, and we can finally move toward making a new storage plugin type, but it shouldn't be configurable in the Caddyfile, I think, since it doesn't make sense for a Caddy instance to use more than one storage config... We also have the option of eliminating DNS provider plugins and just shipping all of lego's DNS providers by using a lego package (the caddytls/setup.go file has a comment describing how) -- but it doubles Caddy's binary size by 100% from about 19 MB to around 40 MB...!
This commit is contained in:
		
							parent
							
								
									8f583dcf36
								
							
						
					
					
						commit
						e0f1a02c37
					
				
							
								
								
									
										62
									
								
								caddy.go
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								caddy.go
									
									
									
									
									
								
							@ -108,12 +108,12 @@ type Instance struct {
 | 
			
		||||
	servers []ServerListener
 | 
			
		||||
 | 
			
		||||
	// these callbacks execute when certain events occur
 | 
			
		||||
	onFirstStartup  []func() error // starting, not as part of a restart
 | 
			
		||||
	onStartup       []func() error // starting, even as part of a restart
 | 
			
		||||
	onRestart       []func() error // before restart commences
 | 
			
		||||
	onRestartFailed []func() error // if restart failed
 | 
			
		||||
	onShutdown      []func() error // stopping, even as part of a restart
 | 
			
		||||
	onFinalShutdown []func() error // stopping, not as part of a restart
 | 
			
		||||
	OnFirstStartup  []func() error // starting, not as part of a restart
 | 
			
		||||
	OnStartup       []func() error // starting, even as part of a restart
 | 
			
		||||
	OnRestart       []func() error // before restart commences
 | 
			
		||||
	OnRestartFailed []func() error // if restart failed
 | 
			
		||||
	OnShutdown      []func() error // stopping, even as part of a restart
 | 
			
		||||
	OnFinalShutdown []func() error // stopping, not as part of a restart
 | 
			
		||||
 | 
			
		||||
	// storing values on an instance is preferable to
 | 
			
		||||
	// global state because these will get garbage-
 | 
			
		||||
@ -163,13 +163,13 @@ func (i *Instance) Stop() error {
 | 
			
		||||
// the rest. All the non-nil errors will be returned.
 | 
			
		||||
func (i *Instance) ShutdownCallbacks() []error {
 | 
			
		||||
	var errs []error
 | 
			
		||||
	for _, shutdownFunc := range i.onShutdown {
 | 
			
		||||
	for _, shutdownFunc := range i.OnShutdown {
 | 
			
		||||
		err := shutdownFunc()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, finalShutdownFunc := range i.onFinalShutdown {
 | 
			
		||||
	for _, finalShutdownFunc := range i.OnFinalShutdown {
 | 
			
		||||
		err := finalShutdownFunc()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
@ -192,7 +192,7 @@ func (i *Instance) Restart(newCaddyfile Input) (*Instance, error) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		r := recover()
 | 
			
		||||
		if err != nil || r != nil {
 | 
			
		||||
			for _, fn := range i.onRestartFailed {
 | 
			
		||||
			for _, fn := range i.OnRestartFailed {
 | 
			
		||||
				err = fn()
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Printf("[ERROR] restart failed: %v", err)
 | 
			
		||||
@ -205,7 +205,7 @@ func (i *Instance) Restart(newCaddyfile Input) (*Instance, error) {
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// run restart callbacks
 | 
			
		||||
	for _, fn := range i.onRestart {
 | 
			
		||||
	for _, fn := range i.OnRestart {
 | 
			
		||||
		err = fn()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return i, err
 | 
			
		||||
@ -252,7 +252,7 @@ func (i *Instance) Restart(newCaddyfile Input) (*Instance, error) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return i, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, shutdownFunc := range i.onShutdown {
 | 
			
		||||
	for _, shutdownFunc := range i.OnShutdown {
 | 
			
		||||
		err = shutdownFunc()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return i, err
 | 
			
		||||
@ -274,42 +274,6 @@ func (i *Instance) SaveServer(s Server, ln net.Listener) {
 | 
			
		||||
	i.servers = append(i.servers, ServerListener{server: s, listener: ln})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HasListenerWithAddress returns whether this package is
 | 
			
		||||
// tracking a server using a listener with the address
 | 
			
		||||
// addr.
 | 
			
		||||
func HasListenerWithAddress(addr string) bool {
 | 
			
		||||
	instancesMu.Lock()
 | 
			
		||||
	defer instancesMu.Unlock()
 | 
			
		||||
	for _, inst := range instances {
 | 
			
		||||
		for _, sln := range inst.servers {
 | 
			
		||||
			if listenerAddrEqual(sln.listener, addr) {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// listenerAddrEqual compares a listener's address with
 | 
			
		||||
// addr. Extra care is taken to match addresses with an
 | 
			
		||||
// empty hostname portion, as listeners tend to report
 | 
			
		||||
// [::]:80, for example, when the matching address that
 | 
			
		||||
// created the listener might be simply :80.
 | 
			
		||||
func listenerAddrEqual(ln net.Listener, addr string) bool {
 | 
			
		||||
	lnAddr := ln.Addr().String()
 | 
			
		||||
	hostname, port, err := net.SplitHostPort(addr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return lnAddr == addr
 | 
			
		||||
	}
 | 
			
		||||
	if lnAddr == net.JoinHostPort("::", port) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if lnAddr == net.JoinHostPort("0.0.0.0", port) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return hostname != "" && lnAddr == addr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TCPServer is a type that can listen and serve connections.
 | 
			
		||||
// A TCPServer must associate with exactly zero or one net.Listeners.
 | 
			
		||||
type TCPServer interface {
 | 
			
		||||
@ -551,14 +515,14 @@ func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string]r
 | 
			
		||||
	// run startup callbacks
 | 
			
		||||
	if !IsUpgrade() && restartFds == nil {
 | 
			
		||||
		// first startup means not a restart or upgrade
 | 
			
		||||
		for _, firstStartupFunc := range inst.onFirstStartup {
 | 
			
		||||
		for _, firstStartupFunc := range inst.OnFirstStartup {
 | 
			
		||||
			err = firstStartupFunc()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, startupFunc := range inst.onStartup {
 | 
			
		||||
	for _, startupFunc := range inst.OnStartup {
 | 
			
		||||
		err = startupFunc()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
 | 
			
		||||
@ -33,8 +33,8 @@ import (
 | 
			
		||||
	"github.com/mholt/caddy"
 | 
			
		||||
	"github.com/mholt/caddy/caddytls"
 | 
			
		||||
	"github.com/mholt/caddy/telemetry"
 | 
			
		||||
	"github.com/xenolf/lego/acme"
 | 
			
		||||
	"gopkg.in/natefinch/lumberjack.v2"
 | 
			
		||||
	"github.com/mholt/certmagic"
 | 
			
		||||
	lumberjack "gopkg.in/natefinch/lumberjack.v2"
 | 
			
		||||
 | 
			
		||||
	_ "github.com/mholt/caddy/caddyhttp" // plug in the HTTP server type
 | 
			
		||||
	// This is where other plugins get plugged in (imported)
 | 
			
		||||
@ -44,17 +44,17 @@ func init() {
 | 
			
		||||
	caddy.TrapSignals()
 | 
			
		||||
	setVersion()
 | 
			
		||||
 | 
			
		||||
	flag.BoolVar(&caddytls.Agreed, "agree", false, "Agree to the CA's Subscriber Agreement")
 | 
			
		||||
	flag.StringVar(&caddytls.DefaultCAUrl, "ca", "https://acme-v02.api.letsencrypt.org/directory", "URL to certificate authority's ACME server directory")
 | 
			
		||||
	flag.BoolVar(&caddytls.DisableHTTPChallenge, "disable-http-challenge", caddytls.DisableHTTPChallenge, "Disable the ACME HTTP challenge")
 | 
			
		||||
	flag.BoolVar(&caddytls.DisableTLSALPNChallenge, "disable-tls-alpn-challenge", caddytls.DisableTLSALPNChallenge, "Disable the ACME TLS-ALPN challenge")
 | 
			
		||||
	flag.BoolVar(&certmagic.Agreed, "agree", false, "Agree to the CA's Subscriber Agreement")
 | 
			
		||||
	flag.StringVar(&certmagic.CA, "ca", certmagic.CA, "URL to certificate authority's ACME server directory")
 | 
			
		||||
	flag.BoolVar(&certmagic.DisableHTTPChallenge, "disable-http-challenge", certmagic.DisableHTTPChallenge, "Disable the ACME HTTP challenge")
 | 
			
		||||
	flag.BoolVar(&certmagic.DisableTLSALPNChallenge, "disable-tls-alpn-challenge", certmagic.DisableTLSALPNChallenge, "Disable the ACME TLS-ALPN challenge")
 | 
			
		||||
	flag.StringVar(&disabledMetrics, "disabled-metrics", "", "Comma-separated list of telemetry metrics to disable")
 | 
			
		||||
	flag.StringVar(&conf, "conf", "", "Caddyfile to load (default \""+caddy.DefaultConfigFile+"\")")
 | 
			
		||||
	flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
 | 
			
		||||
	flag.StringVar(&envFile, "env", "", "Path to file with environment variables to load in KEY=VALUE format")
 | 
			
		||||
	flag.BoolVar(&plugins, "plugins", false, "List installed plugins")
 | 
			
		||||
	flag.StringVar(&caddytls.DefaultEmail, "email", "", "Default ACME CA account email address")
 | 
			
		||||
	flag.DurationVar(&acme.HTTPClient.Timeout, "catimeout", acme.HTTPClient.Timeout, "Default ACME CA HTTP timeout")
 | 
			
		||||
	flag.StringVar(&certmagic.Email, "email", "", "Default ACME CA account email address")
 | 
			
		||||
	flag.DurationVar(&certmagic.HTTPTimeout, "catimeout", certmagic.HTTPTimeout, "Default ACME CA HTTP timeout")
 | 
			
		||||
	flag.StringVar(&logfile, "log", "", "Process log file")
 | 
			
		||||
	flag.StringVar(&caddy.PidFile, "pidfile", "", "Path to write pid file")
 | 
			
		||||
	flag.BoolVar(&caddy.Quiet, "quiet", false, "Quiet mode (no initialization output)")
 | 
			
		||||
@ -73,7 +73,7 @@ func Run() {
 | 
			
		||||
 | 
			
		||||
	caddy.AppName = appName
 | 
			
		||||
	caddy.AppVersion = appVersion
 | 
			
		||||
	acme.UserAgent = appName + "/" + appVersion
 | 
			
		||||
	certmagic.UserAgent = appName + "/" + appVersion
 | 
			
		||||
 | 
			
		||||
	// Set up process log before anything bad happens
 | 
			
		||||
	switch logfile {
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ func setupBind(c *caddy.Controller) error {
 | 
			
		||||
		if !c.Args(&config.ListenHost) {
 | 
			
		||||
			return c.ArgErr()
 | 
			
		||||
		}
 | 
			
		||||
		config.TLS.ListenHost = config.ListenHost // necessary for ACME challenges, see issue #309
 | 
			
		||||
		config.TLS.Manager.ListenHost = config.ListenHost // necessary for ACME challenges, see issue #309
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ func TestSetupBind(t *testing.T) {
 | 
			
		||||
	if got, want := cfg.ListenHost, "1.2.3.4"; got != want {
 | 
			
		||||
		t.Errorf("Expected the config's ListenHost to be %s, was %s", want, got)
 | 
			
		||||
	}
 | 
			
		||||
	if got, want := cfg.TLS.ListenHost, "1.2.3.4"; got != want {
 | 
			
		||||
	if got, want := cfg.TLS.Manager.ListenHost, "1.2.3.4"; got != want {
 | 
			
		||||
		t.Errorf("Expected the TLS config's ListenHost to be %s, was %s", want, got)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy"
 | 
			
		||||
	"github.com/mholt/caddy/caddytls"
 | 
			
		||||
	"github.com/mholt/certmagic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func activateHTTPS(cctx caddy.Context) error {
 | 
			
		||||
@ -37,10 +38,10 @@ func activateHTTPS(cctx caddy.Context) error {
 | 
			
		||||
 | 
			
		||||
	// place certificates and keys on disk
 | 
			
		||||
	for _, c := range ctx.siteConfigs {
 | 
			
		||||
		if c.TLS.OnDemand {
 | 
			
		||||
		if c.TLS.Manager.OnDemand != nil {
 | 
			
		||||
			continue // obtain these certificates on-demand instead
 | 
			
		||||
		}
 | 
			
		||||
		err := c.TLS.ObtainCert(c.TLS.Hostname, operatorPresent)
 | 
			
		||||
		err := c.TLS.Manager.ObtainCert(c.TLS.Hostname, operatorPresent)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
@ -62,9 +63,14 @@ func activateHTTPS(cctx caddy.Context) error {
 | 
			
		||||
	// on the ports we'd need to do ACME before we finish starting; parent process
 | 
			
		||||
	// already running renewal ticker, so renewal won't be missed anyway.)
 | 
			
		||||
	if !caddy.IsUpgrade() {
 | 
			
		||||
		err = caddytls.RenewManagedCertificates(true)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		ctx.instance.StorageMu.RLock()
 | 
			
		||||
		certCache, ok := ctx.instance.Storage[caddytls.CertCacheInstStorageKey].(*certmagic.Cache)
 | 
			
		||||
		ctx.instance.StorageMu.RUnlock()
 | 
			
		||||
		if ok && certCache != nil {
 | 
			
		||||
			err = certCache.RenewManagedCertificates(operatorPresent)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -95,13 +101,13 @@ func markQualifiedForAutoHTTPS(configs []*SiteConfig) {
 | 
			
		||||
// value will always be nil.
 | 
			
		||||
func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error {
 | 
			
		||||
	for _, cfg := range configs {
 | 
			
		||||
		if cfg == nil || cfg.TLS == nil || !cfg.TLS.Managed || cfg.TLS.OnDemand {
 | 
			
		||||
		if cfg == nil || cfg.TLS == nil || !cfg.TLS.Managed || cfg.TLS.Manager.OnDemand != nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		cfg.TLS.Enabled = true
 | 
			
		||||
		cfg.Addr.Scheme = "https"
 | 
			
		||||
		if loadCertificates && caddytls.HostQualifies(cfg.TLS.Hostname) {
 | 
			
		||||
			_, err := cfg.TLS.CacheManagedCertificate(cfg.TLS.Hostname)
 | 
			
		||||
		if loadCertificates && certmagic.HostQualifies(cfg.TLS.Hostname) {
 | 
			
		||||
			_, err := cfg.TLS.Manager.CacheManagedCertificate(cfg.TLS.Hostname)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
@ -113,7 +119,7 @@ func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error {
 | 
			
		||||
		// Set default port of 443 if not explicitly set
 | 
			
		||||
		if cfg.Addr.Port == "" &&
 | 
			
		||||
			cfg.TLS.Enabled &&
 | 
			
		||||
			(!cfg.TLS.Manual || cfg.TLS.OnDemand) &&
 | 
			
		||||
			(!cfg.TLS.Manual || cfg.TLS.Manager.OnDemand != nil) &&
 | 
			
		||||
			cfg.Addr.Host != "localhost" {
 | 
			
		||||
			cfg.Addr.Port = HTTPSPort
 | 
			
		||||
		}
 | 
			
		||||
@ -207,7 +213,7 @@ func redirPlaintextHost(cfg *SiteConfig) *SiteConfig {
 | 
			
		||||
		Addr:       Address{Original: addr, Host: host, Port: port},
 | 
			
		||||
		ListenHost: cfg.ListenHost,
 | 
			
		||||
		middleware: []Middleware{redirMiddleware},
 | 
			
		||||
		TLS:        &caddytls.Config{AltHTTPPort: cfg.TLS.AltHTTPPort, AltTLSALPNPort: cfg.TLS.AltTLSALPNPort},
 | 
			
		||||
		TLS:        &caddytls.Config{Manager: cfg.TLS.Manager},
 | 
			
		||||
		Timeouts:   cfg.Timeouts,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,7 @@ import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy/caddytls"
 | 
			
		||||
	"github.com/mholt/certmagic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestRedirPlaintextHost(t *testing.T) {
 | 
			
		||||
@ -150,18 +151,18 @@ func TestHostHasOtherPort(t *testing.T) {
 | 
			
		||||
func TestMakePlaintextRedirects(t *testing.T) {
 | 
			
		||||
	configs := []*SiteConfig{
 | 
			
		||||
		// Happy path = standard redirect from 80 to 443
 | 
			
		||||
		{Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{Managed: true}},
 | 
			
		||||
		{Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{Manager: &certmagic.Config{Managed: true}}},
 | 
			
		||||
 | 
			
		||||
		// Host on port 80 already defined; don't change it (no redirect)
 | 
			
		||||
		{Addr: Address{Host: "sub1.example.com", Port: "80", Scheme: "http"}, TLS: new(caddytls.Config)},
 | 
			
		||||
		{Addr: Address{Host: "sub1.example.com"}, TLS: &caddytls.Config{Managed: true}},
 | 
			
		||||
		{Addr: Address{Host: "sub1.example.com"}, TLS: &caddytls.Config{Manager: &certmagic.Config{Managed: true}}},
 | 
			
		||||
 | 
			
		||||
		// Redirect from port 80 to port 5000 in this case
 | 
			
		||||
		{Addr: Address{Host: "sub2.example.com", Port: "5000"}, TLS: &caddytls.Config{Managed: true}},
 | 
			
		||||
		{Addr: Address{Host: "sub2.example.com", Port: "5000"}, TLS: &caddytls.Config{Manager: &certmagic.Config{Managed: true}}},
 | 
			
		||||
 | 
			
		||||
		// Can redirect from 80 to either 443 or 5001, but choose 443
 | 
			
		||||
		{Addr: Address{Host: "sub3.example.com", Port: "443"}, TLS: &caddytls.Config{Managed: true}},
 | 
			
		||||
		{Addr: Address{Host: "sub3.example.com", Port: "5001", Scheme: "https"}, TLS: &caddytls.Config{Managed: true}},
 | 
			
		||||
		{Addr: Address{Host: "sub3.example.com", Port: "443"}, TLS: &caddytls.Config{Manager: &certmagic.Config{Managed: true}}},
 | 
			
		||||
		{Addr: Address{Host: "sub3.example.com", Port: "5001", Scheme: "https"}, TLS: &caddytls.Config{Manager: &certmagic.Config{Managed: true}}},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result := makePlaintextRedirects(configs)
 | 
			
		||||
@ -175,7 +176,7 @@ func TestMakePlaintextRedirects(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestEnableAutoHTTPS(t *testing.T) {
 | 
			
		||||
	configs := []*SiteConfig{
 | 
			
		||||
		{Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{Managed: true}},
 | 
			
		||||
		{Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{Manager: &certmagic.Config{Managed: true}}},
 | 
			
		||||
		{}, // not managed - no changes!
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -215,7 +216,7 @@ func TestMarkQualifiedForAutoHTTPS(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	count := 0
 | 
			
		||||
	for _, cfg := range configs {
 | 
			
		||||
		if cfg.TLS.Managed {
 | 
			
		||||
		if cfg.TLS.Manager.Managed {
 | 
			
		||||
			count++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,7 @@ import (
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
@ -31,6 +32,7 @@ import (
 | 
			
		||||
	"github.com/mholt/caddy/caddyhttp/staticfiles"
 | 
			
		||||
	"github.com/mholt/caddy/caddytls"
 | 
			
		||||
	"github.com/mholt/caddy/telemetry"
 | 
			
		||||
	"github.com/mholt/certmagic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const serverType = "http"
 | 
			
		||||
@ -169,12 +171,20 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd
 | 
			
		||||
 | 
			
		||||
			// If default HTTP or HTTPS ports have been customized,
 | 
			
		||||
			// make sure the ACME challenge ports match
 | 
			
		||||
			var altHTTPPort, altTLSALPNPort string
 | 
			
		||||
			var altHTTPPort, altTLSALPNPort int
 | 
			
		||||
			if HTTPPort != DefaultHTTPPort {
 | 
			
		||||
				altHTTPPort = HTTPPort
 | 
			
		||||
				portInt, err := strconv.Atoi(HTTPPort)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
				}
 | 
			
		||||
				altHTTPPort = portInt
 | 
			
		||||
			}
 | 
			
		||||
			if HTTPSPort != DefaultHTTPSPort {
 | 
			
		||||
				altTLSALPNPort = HTTPSPort
 | 
			
		||||
				portInt, err := strconv.Atoi(HTTPSPort)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
				}
 | 
			
		||||
				altTLSALPNPort = portInt
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Make our caddytls.Config, which has a pointer to the
 | 
			
		||||
@ -182,8 +192,8 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd
 | 
			
		||||
			// to use automatic HTTPS when the time comes
 | 
			
		||||
			caddytlsConfig := caddytls.NewConfig(h.instance)
 | 
			
		||||
			caddytlsConfig.Hostname = addr.Host
 | 
			
		||||
			caddytlsConfig.AltHTTPPort = altHTTPPort
 | 
			
		||||
			caddytlsConfig.AltTLSALPNPort = altTLSALPNPort
 | 
			
		||||
			caddytlsConfig.Manager.AltHTTPPort = altHTTPPort
 | 
			
		||||
			caddytlsConfig.Manager.AltTLSALPNPort = altTLSALPNPort
 | 
			
		||||
 | 
			
		||||
			// Save the config to our master list, and key it for lookups
 | 
			
		||||
			cfg := &SiteConfig{
 | 
			
		||||
@ -221,7 +231,7 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) {
 | 
			
		||||
	// trusted CA (obviously not a perfect hueristic)
 | 
			
		||||
	var looksLikeProductionCA bool
 | 
			
		||||
	for _, publicCAEndpoint := range caddytls.KnownACMECAs {
 | 
			
		||||
		if strings.Contains(caddytls.DefaultCAUrl, publicCAEndpoint) {
 | 
			
		||||
		if strings.Contains(certmagic.CA, publicCAEndpoint) {
 | 
			
		||||
			looksLikeProductionCA = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
@ -243,7 +253,7 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) {
 | 
			
		||||
			if !caddy.IsLoopback(cfg.Addr.Host) &&
 | 
			
		||||
				!caddy.IsLoopback(cfg.ListenHost) &&
 | 
			
		||||
				(caddytls.QualifiesForManagedTLS(cfg) ||
 | 
			
		||||
					caddytls.HostQualifies(cfg.Addr.Host)) {
 | 
			
		||||
					certmagic.HostQualifies(cfg.Addr.Host)) {
 | 
			
		||||
				atLeastOneSiteLooksLikeProduction = true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@ -264,7 +274,7 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) {
 | 
			
		||||
			// is incorrect for this site.
 | 
			
		||||
			cfg.Addr.Scheme = "https"
 | 
			
		||||
		}
 | 
			
		||||
		if cfg.Addr.Port == "" && ((!cfg.TLS.Manual && !cfg.TLS.SelfSigned) || cfg.TLS.OnDemand) {
 | 
			
		||||
		if cfg.Addr.Port == "" && ((!cfg.TLS.Manual && !cfg.TLS.SelfSigned) || cfg.TLS.Manager.OnDemand != nil) {
 | 
			
		||||
			// this is vital, otherwise the function call below that
 | 
			
		||||
			// sets the listener address will use the default port
 | 
			
		||||
			// instead of 443 because it doesn't know about TLS.
 | 
			
		||||
 | 
			
		||||
@ -402,24 +402,26 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) (int, error)
 | 
			
		||||
 | 
			
		||||
	if vhost == nil {
 | 
			
		||||
		// check for ACME challenge even if vhost is nil;
 | 
			
		||||
		// could be a new host coming online soon
 | 
			
		||||
		if caddytls.HTTPChallengeHandler(w, r, "localhost") {
 | 
			
		||||
		// could be a new host coming online soon - choose any
 | 
			
		||||
		// vhost's cert manager configuration, I guess
 | 
			
		||||
		if len(s.sites) > 0 && s.sites[0].TLS.Manager.HandleHTTPChallenge(w, r) {
 | 
			
		||||
			return 0, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// otherwise, log the error and write a message to the client
 | 
			
		||||
		remoteHost, _, err := net.SplitHostPort(r.RemoteAddr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			remoteHost = r.RemoteAddr
 | 
			
		||||
		}
 | 
			
		||||
		WriteSiteNotFound(w, r) // don't add headers outside of this function
 | 
			
		||||
		WriteSiteNotFound(w, r) // don't add headers outside of this function (http.forwardproxy)
 | 
			
		||||
		log.Printf("[INFO] %s - No such site at %s (Remote: %s, Referer: %s)",
 | 
			
		||||
			hostname, s.Server.Addr, remoteHost, r.Header.Get("Referer"))
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// we still check for ACME challenge if the vhost exists,
 | 
			
		||||
	// because we must apply its HTTP challenge config settings
 | 
			
		||||
	if caddytls.HTTPChallengeHandler(w, r, vhost.ListenHost) {
 | 
			
		||||
	// because the HTTP challenge might be disabled by its config
 | 
			
		||||
	if vhost.TLS.Manager.HandleHTTPChallenge(w, r) {
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,390 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy/telemetry"
 | 
			
		||||
	"golang.org/x/crypto/ocsp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// certificateCache is to be an instance-wide cache of certs
 | 
			
		||||
// that site-specific TLS configs can refer to. Using a
 | 
			
		||||
// central map like this avoids duplication of certs in
 | 
			
		||||
// memory when the cert is used by multiple sites, and makes
 | 
			
		||||
// maintenance easier. Because these are not to be global,
 | 
			
		||||
// the cache will get garbage collected after a config reload
 | 
			
		||||
// (a new instance will take its place).
 | 
			
		||||
type certificateCache struct {
 | 
			
		||||
	sync.RWMutex
 | 
			
		||||
	cache map[string]Certificate // keyed by certificate hash
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// replaceCertificate replaces oldCert with newCert in the cache, and
 | 
			
		||||
// updates all configs that are pointing to the old certificate to
 | 
			
		||||
// point to the new one instead. newCert must already be loaded into
 | 
			
		||||
// the cache (this method does NOT load it into the cache).
 | 
			
		||||
//
 | 
			
		||||
// Note that all the names on the old certificate will be deleted
 | 
			
		||||
// from the name lookup maps of each config, then all the names on
 | 
			
		||||
// the new certificate will be added to the lookup maps as long as
 | 
			
		||||
// they do not overwrite any entries.
 | 
			
		||||
//
 | 
			
		||||
// The newCert may be modified and its cache entry updated.
 | 
			
		||||
//
 | 
			
		||||
// This method is safe for concurrent use.
 | 
			
		||||
func (certCache *certificateCache) replaceCertificate(oldCert, newCert Certificate) error {
 | 
			
		||||
	certCache.Lock()
 | 
			
		||||
	defer certCache.Unlock()
 | 
			
		||||
 | 
			
		||||
	// have all the configs that are pointing to the old
 | 
			
		||||
	// certificate point to the new certificate instead
 | 
			
		||||
	for _, cfg := range oldCert.configs {
 | 
			
		||||
		// first delete all the name lookup entries that
 | 
			
		||||
		// pointed to the old certificate
 | 
			
		||||
		for name, certKey := range cfg.Certificates {
 | 
			
		||||
			if certKey == oldCert.Hash {
 | 
			
		||||
				delete(cfg.Certificates, name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// then add name lookup entries for the names
 | 
			
		||||
		// on the new certificate, but don't overwrite
 | 
			
		||||
		// entries that may already exist, not only as
 | 
			
		||||
		// a courtesy, but importantly: because if we
 | 
			
		||||
		// overwrote a value here, and this config no
 | 
			
		||||
		// longer pointed to a certain certificate in
 | 
			
		||||
		// the cache, that certificate's list of configs
 | 
			
		||||
		// referring to it would be incorrect; so just
 | 
			
		||||
		// insert entries, don't overwrite any
 | 
			
		||||
		for _, name := range newCert.Names {
 | 
			
		||||
			if _, ok := cfg.Certificates[name]; !ok {
 | 
			
		||||
				cfg.Certificates[name] = newCert.Hash
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// since caching a new certificate attaches only the config
 | 
			
		||||
	// that loaded it, the new certificate needs to be given the
 | 
			
		||||
	// list of all the configs that use it, so copy the list
 | 
			
		||||
	// over from the old certificate to the new certificate
 | 
			
		||||
	// in the cache
 | 
			
		||||
	newCert.configs = oldCert.configs
 | 
			
		||||
	certCache.cache[newCert.Hash] = newCert
 | 
			
		||||
 | 
			
		||||
	// finally, delete the old certificate from the cache
 | 
			
		||||
	delete(certCache.cache, oldCert.Hash)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// reloadManagedCertificate reloads the certificate corresponding to the name(s)
 | 
			
		||||
// on oldCert into the cache, from storage. This also replaces the old certificate
 | 
			
		||||
// with the new one, so that all configurations that used the old cert now point
 | 
			
		||||
// to the new cert.
 | 
			
		||||
func (certCache *certificateCache) reloadManagedCertificate(oldCert Certificate) error {
 | 
			
		||||
	// get the certificate from storage and cache it
 | 
			
		||||
	newCert, err := oldCert.configs[0].CacheManagedCertificate(oldCert.Names[0])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("unable to reload certificate for %v into cache: %v", oldCert.Names, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// and replace the old certificate with the new one
 | 
			
		||||
	err = certCache.replaceCertificate(oldCert, newCert)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("replacing certificate %v: %v", oldCert.Names, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Certificate is a tls.Certificate with associated metadata tacked on.
 | 
			
		||||
// Even if the metadata can be obtained by parsing the certificate,
 | 
			
		||||
// we are more efficient by extracting the metadata onto this struct.
 | 
			
		||||
type Certificate struct {
 | 
			
		||||
	tls.Certificate
 | 
			
		||||
 | 
			
		||||
	// Names is the list of names this certificate is written for.
 | 
			
		||||
	// The first is the CommonName (if any), the rest are SAN.
 | 
			
		||||
	Names []string
 | 
			
		||||
 | 
			
		||||
	// NotAfter is when the certificate expires.
 | 
			
		||||
	NotAfter time.Time
 | 
			
		||||
 | 
			
		||||
	// OCSP contains the certificate's parsed OCSP response.
 | 
			
		||||
	OCSP *ocsp.Response
 | 
			
		||||
 | 
			
		||||
	// The hex-encoded hash of this cert's chain's bytes.
 | 
			
		||||
	Hash string
 | 
			
		||||
 | 
			
		||||
	// configs is the list of configs that use or refer to
 | 
			
		||||
	// The first one is assumed to be the config that is
 | 
			
		||||
	// "in charge" of this certificate (i.e. determines
 | 
			
		||||
	// whether it is managed, how it is managed, etc).
 | 
			
		||||
	// This field will be populated by cacheCertificate.
 | 
			
		||||
	// Only meddle with it if you know what you're doing!
 | 
			
		||||
	configs []*Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CacheManagedCertificate loads the certificate for domain into the
 | 
			
		||||
// cache, from the TLS storage for managed certificates. It returns a
 | 
			
		||||
// copy of the Certificate that was put into the cache.
 | 
			
		||||
//
 | 
			
		||||
// This method is safe for concurrent use.
 | 
			
		||||
func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) {
 | 
			
		||||
	storage, err := cfg.StorageFor(cfg.CAUrl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Certificate{}, err
 | 
			
		||||
	}
 | 
			
		||||
	siteData, err := storage.LoadSite(domain)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Certificate{}, err
 | 
			
		||||
	}
 | 
			
		||||
	cert, err := makeCertificateWithOCSP(siteData.Cert, siteData.Key)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return cert, err
 | 
			
		||||
	}
 | 
			
		||||
	telemetry.Increment("tls_managed_cert_count")
 | 
			
		||||
	return cfg.cacheCertificate(cert), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// cacheUnmanagedCertificatePEMFile loads a certificate for host using certFile
 | 
			
		||||
// and keyFile, which must be in PEM format. It stores the certificate in
 | 
			
		||||
// the in-memory cache.
 | 
			
		||||
//
 | 
			
		||||
// This function is safe for concurrent use.
 | 
			
		||||
func (cfg *Config) cacheUnmanagedCertificatePEMFile(certFile, keyFile string) error {
 | 
			
		||||
	cert, err := makeCertificateFromDiskWithOCSP(certFile, keyFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	cfg.cacheCertificate(cert)
 | 
			
		||||
	telemetry.Increment("tls_manual_cert_count")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// cacheUnmanagedCertificatePEMBytes makes a certificate out of the PEM bytes
 | 
			
		||||
// of the certificate and key, then caches it in memory.
 | 
			
		||||
//
 | 
			
		||||
// This function is safe for concurrent use.
 | 
			
		||||
func (cfg *Config) cacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte) error {
 | 
			
		||||
	cert, err := makeCertificateWithOCSP(certBytes, keyBytes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	cfg.cacheCertificate(cert)
 | 
			
		||||
	telemetry.Increment("tls_manual_cert_count")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// makeCertificateFromDiskWithOCSP makes a Certificate by loading the
 | 
			
		||||
// certificate and key files. It fills out all the fields in
 | 
			
		||||
// the certificate except for the Managed and OnDemand flags.
 | 
			
		||||
// (It is up to the caller to set those.) It staples OCSP.
 | 
			
		||||
func makeCertificateFromDiskWithOCSP(certFile, keyFile string) (Certificate, error) {
 | 
			
		||||
	certPEMBlock, err := ioutil.ReadFile(certFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Certificate{}, err
 | 
			
		||||
	}
 | 
			
		||||
	keyPEMBlock, err := ioutil.ReadFile(keyFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Certificate{}, err
 | 
			
		||||
	}
 | 
			
		||||
	return makeCertificateWithOCSP(certPEMBlock, keyPEMBlock)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// makeCertificate turns a certificate PEM bundle and a key PEM block into
 | 
			
		||||
// a Certificate with necessary metadata from parsing its bytes filled into
 | 
			
		||||
// its struct fields for convenience (except for the OnDemand and Managed
 | 
			
		||||
// flags; it is up to the caller to set those properties!). This function
 | 
			
		||||
// does NOT staple OCSP.
 | 
			
		||||
func makeCertificate(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
 | 
			
		||||
	var cert Certificate
 | 
			
		||||
 | 
			
		||||
	// Convert to a tls.Certificate
 | 
			
		||||
	tlsCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return cert, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Extract necessary metadata
 | 
			
		||||
	err = fillCertFromLeaf(&cert, tlsCert)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return cert, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cert, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// makeCertificateWithOCSP is the same as makeCertificate except that it also
 | 
			
		||||
// staples OCSP to the certificate.
 | 
			
		||||
func makeCertificateWithOCSP(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
 | 
			
		||||
	cert, err := makeCertificate(certPEMBlock, keyPEMBlock)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return cert, err
 | 
			
		||||
	}
 | 
			
		||||
	err = stapleOCSP(&cert, certPEMBlock)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Printf("[WARNING] Stapling OCSP: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	return cert, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// fillCertFromLeaf populates metadata fields on cert from tlsCert.
 | 
			
		||||
func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error {
 | 
			
		||||
	if len(tlsCert.Certificate) == 0 {
 | 
			
		||||
		return errors.New("certificate is empty")
 | 
			
		||||
	}
 | 
			
		||||
	cert.Certificate = tlsCert
 | 
			
		||||
 | 
			
		||||
	// the leaf cert should be the one for the site; it has what we need
 | 
			
		||||
	leaf, err := x509.ParseCertificate(tlsCert.Certificate[0])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if leaf.Subject.CommonName != "" { // TODO: CommonName is deprecated
 | 
			
		||||
		cert.Names = []string{strings.ToLower(leaf.Subject.CommonName)}
 | 
			
		||||
	}
 | 
			
		||||
	for _, name := range leaf.DNSNames {
 | 
			
		||||
		if name != leaf.Subject.CommonName { // TODO: CommonName is deprecated
 | 
			
		||||
			cert.Names = append(cert.Names, strings.ToLower(name))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, ip := range leaf.IPAddresses {
 | 
			
		||||
		if ipStr := ip.String(); ipStr != leaf.Subject.CommonName { // TODO: CommonName is deprecated
 | 
			
		||||
			cert.Names = append(cert.Names, strings.ToLower(ipStr))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, email := range leaf.EmailAddresses {
 | 
			
		||||
		if email != leaf.Subject.CommonName { // TODO: CommonName is deprecated
 | 
			
		||||
			cert.Names = append(cert.Names, strings.ToLower(email))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(cert.Names) == 0 {
 | 
			
		||||
		return errors.New("certificate has no names")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// save the hash of this certificate (chain) and
 | 
			
		||||
	// expiration date, for necessity and efficiency
 | 
			
		||||
	cert.Hash = hashCertificateChain(cert.Certificate.Certificate)
 | 
			
		||||
	cert.NotAfter = leaf.NotAfter
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// hashCertificateChain computes the unique hash of certChain,
 | 
			
		||||
// which is the chain of DER-encoded bytes. It returns the
 | 
			
		||||
// hex encoding of the hash.
 | 
			
		||||
func hashCertificateChain(certChain [][]byte) string {
 | 
			
		||||
	h := sha256.New()
 | 
			
		||||
	for _, certInChain := range certChain {
 | 
			
		||||
		h.Write(certInChain)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%x", h.Sum(nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// managedCertInStorageExpiresSoon returns true if cert (being a
 | 
			
		||||
// managed certificate) is expiring within RenewDurationBefore.
 | 
			
		||||
// It returns false if there was an error checking the expiration
 | 
			
		||||
// of the certificate as found in storage, or if the certificate
 | 
			
		||||
// in storage is NOT expiring soon. A certificate that is expiring
 | 
			
		||||
// soon in our cache but is not expiring soon in storage probably
 | 
			
		||||
// means that another instance renewed the certificate in the
 | 
			
		||||
// meantime, and it would be a good idea to simply load the cert
 | 
			
		||||
// into our cache rather than repeating the renewal process again.
 | 
			
		||||
func managedCertInStorageExpiresSoon(cert Certificate) (bool, error) {
 | 
			
		||||
	if len(cert.configs) == 0 {
 | 
			
		||||
		return false, fmt.Errorf("no configs for certificate")
 | 
			
		||||
	}
 | 
			
		||||
	storage, err := cert.configs[0].StorageFor(cert.configs[0].CAUrl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	siteData, err := storage.LoadSite(cert.Names[0])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	tlsCert, err := tls.X509KeyPair(siteData.Cert, siteData.Key)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	leaf, err := x509.ParseCertificate(tlsCert.Certificate[0])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	timeLeft := leaf.NotAfter.Sub(time.Now().UTC())
 | 
			
		||||
	return timeLeft < RenewDurationBefore, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// cacheCertificate adds cert to the in-memory cache. If a certificate
 | 
			
		||||
// with the same hash is already cached, it is NOT overwritten; instead,
 | 
			
		||||
// cfg is added to the existing certificate's list of configs if not
 | 
			
		||||
// already in the list. Then all the names on cert are used to add
 | 
			
		||||
// entries to cfg.Certificates (the config's name lookup map).
 | 
			
		||||
// Then the certificate is stored/updated in the cache. It returns
 | 
			
		||||
// a copy of the certificate that ends up being stored in the cache.
 | 
			
		||||
//
 | 
			
		||||
// It is VERY important, even for some test cases, that the Hash field
 | 
			
		||||
// of the cert be set properly.
 | 
			
		||||
//
 | 
			
		||||
// This function is safe for concurrent use.
 | 
			
		||||
func (cfg *Config) cacheCertificate(cert Certificate) Certificate {
 | 
			
		||||
	cfg.certCache.Lock()
 | 
			
		||||
	defer cfg.certCache.Unlock()
 | 
			
		||||
 | 
			
		||||
	// if this certificate already exists in the cache,
 | 
			
		||||
	// use it instead of overwriting it -- very important!
 | 
			
		||||
	if existingCert, ok := cfg.certCache.cache[cert.Hash]; ok {
 | 
			
		||||
		cert = existingCert
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// attach this config to the certificate so we know which
 | 
			
		||||
	// configs are referencing/using the certificate, but don't
 | 
			
		||||
	// duplicate entries
 | 
			
		||||
	var found bool
 | 
			
		||||
	for _, c := range cert.configs {
 | 
			
		||||
		if c == cfg {
 | 
			
		||||
			found = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		cert.configs = append(cert.configs, cfg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// key the certificate by all its names for this config only,
 | 
			
		||||
	// this is how we find the certificate during handshakes
 | 
			
		||||
	// (yes, if certs overlap in the names they serve, one will
 | 
			
		||||
	// overwrite another here, but that's just how it goes)
 | 
			
		||||
	for _, name := range cert.Names {
 | 
			
		||||
		cfg.Certificates[name] = cert.Hash
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// store the certificate
 | 
			
		||||
	cfg.certCache.cache[cert.Hash] = cert
 | 
			
		||||
 | 
			
		||||
	return cert
 | 
			
		||||
}
 | 
			
		||||
@ -1,82 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 caddytls
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestUnexportedGetCertificate(t *testing.T) {
 | 
			
		||||
	certCache := &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
	cfg := &Config{Certificates: make(map[string]string), certCache: certCache}
 | 
			
		||||
 | 
			
		||||
	// When cache is empty
 | 
			
		||||
	if _, matched, defaulted := cfg.getCertificate("example.com"); matched || defaulted {
 | 
			
		||||
		t.Errorf("Got a certificate when cache was empty; matched=%v, defaulted=%v", matched, defaulted)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// When cache has one certificate in it
 | 
			
		||||
	firstCert := Certificate{Names: []string{"example.com"}}
 | 
			
		||||
	certCache.cache["0xdeadbeef"] = firstCert
 | 
			
		||||
	cfg.Certificates["example.com"] = "0xdeadbeef"
 | 
			
		||||
	if cert, matched, defaulted := cfg.getCertificate("Example.com"); !matched || defaulted || cert.Names[0] != "example.com" {
 | 
			
		||||
		t.Errorf("Didn't get a cert for 'Example.com' or got the wrong one: %v, matched=%v, defaulted=%v", cert, matched, defaulted)
 | 
			
		||||
	}
 | 
			
		||||
	if cert, matched, defaulted := cfg.getCertificate("example.com"); !matched || defaulted || cert.Names[0] != "example.com" {
 | 
			
		||||
		t.Errorf("Didn't get a cert for 'example.com' or got the wrong one: %v, matched=%v, defaulted=%v", cert, matched, defaulted)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// When retrieving wildcard certificate
 | 
			
		||||
	certCache.cache["0xb01dface"] = Certificate{Names: []string{"*.example.com"}}
 | 
			
		||||
	cfg.Certificates["*.example.com"] = "0xb01dface"
 | 
			
		||||
	if cert, matched, defaulted := cfg.getCertificate("sub.example.com"); !matched || defaulted || cert.Names[0] != "*.example.com" {
 | 
			
		||||
		t.Errorf("Didn't get wildcard cert for 'sub.example.com' or got the wrong one: %v, matched=%v, defaulted=%v", cert, matched, defaulted)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// When no certificate matches and SNI is provided, return no certificate (should be TLS alert)
 | 
			
		||||
	if cert, matched, defaulted := cfg.getCertificate("nomatch"); matched || defaulted {
 | 
			
		||||
		t.Errorf("Expected matched=false, defaulted=false; but got matched=%v, defaulted=%v (cert: %v)", matched, defaulted, cert)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCacheCertificate(t *testing.T) {
 | 
			
		||||
	certCache := &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
	cfg := &Config{Certificates: make(map[string]string), certCache: certCache}
 | 
			
		||||
 | 
			
		||||
	cfg.cacheCertificate(Certificate{Names: []string{"example.com", "sub.example.com"}, Hash: "foobar"})
 | 
			
		||||
	if len(certCache.cache) != 1 {
 | 
			
		||||
		t.Errorf("Expected length of certificate cache to be 1")
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := certCache.cache["foobar"]; !ok {
 | 
			
		||||
		t.Error("Expected first cert to be cached by key 'foobar', but it wasn't")
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := cfg.Certificates["example.com"]; !ok {
 | 
			
		||||
		t.Error("Expected first cert to be keyed by 'example.com', but it wasn't")
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := cfg.Certificates["sub.example.com"]; !ok {
 | 
			
		||||
		t.Error("Expected first cert to be keyed by 'sub.example.com', but it wasn't")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// different config, but using same cache; and has cert with overlapping name,
 | 
			
		||||
	// but different hash
 | 
			
		||||
	cfg2 := &Config{Certificates: make(map[string]string), certCache: certCache}
 | 
			
		||||
	cfg2.cacheCertificate(Certificate{Names: []string{"example.com"}, Hash: "barbaz"})
 | 
			
		||||
	if _, ok := certCache.cache["barbaz"]; !ok {
 | 
			
		||||
		t.Error("Expected second cert to be cached by key 'barbaz.com', but it wasn't")
 | 
			
		||||
	}
 | 
			
		||||
	if hash, ok := cfg2.Certificates["example.com"]; !ok {
 | 
			
		||||
		t.Error("Expected second cert to be keyed by 'example.com', but it wasn't")
 | 
			
		||||
	} else if hash != "barbaz" {
 | 
			
		||||
		t.Errorf("Expected second cert to map to 'barbaz' but it was %s instead", hash)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -1,429 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy"
 | 
			
		||||
	"github.com/mholt/caddy/telemetry"
 | 
			
		||||
	"github.com/xenolf/lego/acme"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// acmeMu ensures that only one ACME challenge occurs at a time.
 | 
			
		||||
var acmeMu sync.Mutex
 | 
			
		||||
 | 
			
		||||
// ACMEClient is a wrapper over acme.Client with
 | 
			
		||||
// some custom state attached. It is used to obtain,
 | 
			
		||||
// renew, and revoke certificates with ACME.
 | 
			
		||||
type ACMEClient struct {
 | 
			
		||||
	AllowPrompts bool
 | 
			
		||||
	config       *Config
 | 
			
		||||
	acmeClient   *acme.Client
 | 
			
		||||
	storage      Storage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// newACMEClient creates a new ACMEClient given an email and whether
 | 
			
		||||
// prompting the user is allowed. It's a variable so we can mock in tests.
 | 
			
		||||
var newACMEClient = func(config *Config, allowPrompts bool) (*ACMEClient, error) {
 | 
			
		||||
	storage, err := config.StorageFor(config.CAUrl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Look up or create the LE user account
 | 
			
		||||
	leUser, err := getUser(storage, config.ACMEEmail)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// ensure key type is set
 | 
			
		||||
	keyType := DefaultKeyType
 | 
			
		||||
	if config.KeyType != "" {
 | 
			
		||||
		keyType = config.KeyType
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// ensure CA URL (directory endpoint) is set
 | 
			
		||||
	caURL := DefaultCAUrl
 | 
			
		||||
	if config.CAUrl != "" {
 | 
			
		||||
		caURL = config.CAUrl
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// ensure endpoint is secure (assume HTTPS if scheme is missing)
 | 
			
		||||
	if !strings.Contains(caURL, "://") {
 | 
			
		||||
		caURL = "https://" + caURL
 | 
			
		||||
	}
 | 
			
		||||
	u, err := url.Parse(caURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if u.Scheme != "https" && !caddy.IsLoopback(u.Host) && !caddy.IsInternal(u.Host) {
 | 
			
		||||
		return nil, fmt.Errorf("%s: insecure CA URL (HTTPS required)", caURL)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// The client facilitates our communication with the CA server.
 | 
			
		||||
	client, err := acme.NewClient(caURL, &leUser, keyType)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If not registered, the user must register an account with the CA
 | 
			
		||||
	// and agree to terms
 | 
			
		||||
	if leUser.Registration == nil {
 | 
			
		||||
		if allowPrompts { // can't prompt a user who isn't there
 | 
			
		||||
			termsURL := client.GetToSURL()
 | 
			
		||||
			if !Agreed && termsURL != "" {
 | 
			
		||||
				Agreed = askUserAgreement(client.GetToSURL())
 | 
			
		||||
			}
 | 
			
		||||
			if !Agreed && termsURL != "" {
 | 
			
		||||
				return nil, errors.New("user must agree to CA terms (use -agree flag)")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		reg, err := client.Register(Agreed)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.New("registration error: " + err.Error())
 | 
			
		||||
		}
 | 
			
		||||
		leUser.Registration = reg
 | 
			
		||||
 | 
			
		||||
		// save user to the file system
 | 
			
		||||
		err = saveUser(storage, leUser)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.New("could not save user: " + err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c := &ACMEClient{
 | 
			
		||||
		AllowPrompts: allowPrompts,
 | 
			
		||||
		config:       config,
 | 
			
		||||
		acmeClient:   client,
 | 
			
		||||
		storage:      storage,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config.DNSProvider == "" {
 | 
			
		||||
		// Use HTTP and TLS-ALPN challenges by default
 | 
			
		||||
 | 
			
		||||
		// figure out which ports we'll be serving the challenges on
 | 
			
		||||
		useHTTPPort := HTTPChallengePort
 | 
			
		||||
		useTLSALPNPort := TLSALPNChallengePort
 | 
			
		||||
		if config.AltHTTPPort != "" {
 | 
			
		||||
			useHTTPPort = config.AltHTTPPort
 | 
			
		||||
		}
 | 
			
		||||
		if config.AltTLSALPNPort != "" {
 | 
			
		||||
			useTLSALPNPort = config.AltTLSALPNPort
 | 
			
		||||
		}
 | 
			
		||||
		if caddy.HasListenerWithAddress(net.JoinHostPort(config.ListenHost, useHTTPPort)) {
 | 
			
		||||
			useHTTPPort = DefaultHTTPAlternatePort
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// if using file storage, we can distribute the HTTP or TLS-ALPN challenge
 | 
			
		||||
		// across all instances sharing the acme folder; either way, we must still
 | 
			
		||||
		// set the address for the default provider server
 | 
			
		||||
		var useDistributedSolver bool
 | 
			
		||||
		if storage, err := c.config.StorageFor(c.config.CAUrl); err == nil {
 | 
			
		||||
			if _, ok := storage.(*FileStorage); ok {
 | 
			
		||||
				useDistributedSolver = true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if useDistributedSolver {
 | 
			
		||||
			// ... being careful to respect user's listener bind preferences
 | 
			
		||||
			c.acmeClient.SetChallengeProvider(acme.HTTP01, distributedSolver{
 | 
			
		||||
				providerServer: acme.NewHTTPProviderServer(config.ListenHost, useHTTPPort),
 | 
			
		||||
			})
 | 
			
		||||
			c.acmeClient.SetChallengeProvider(acme.TLSALPN01, distributedSolver{
 | 
			
		||||
				providerServer: acme.NewTLSALPNProviderServer(config.ListenHost, useTLSALPNPort),
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			// Always respect user's bind preferences by using config.ListenHost.
 | 
			
		||||
			// NOTE(Nov'18): At time of writing, SetHTTPAddress() and SetTLSAddress()
 | 
			
		||||
			// reset the challenge provider back to the default one, overriding
 | 
			
		||||
			// anything set by SetChalllengeProvider(). Calling them mutually
 | 
			
		||||
			// excuslively is safe, as is calling Set*Address() before SetChallengeProvider().
 | 
			
		||||
			err := c.acmeClient.SetHTTPAddress(net.JoinHostPort(config.ListenHost, useHTTPPort))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			err = c.acmeClient.SetTLSAddress(net.JoinHostPort(config.ListenHost, useTLSALPNPort))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// if this server is already listening on the TLS-ALPN port we're supposed to use,
 | 
			
		||||
		// then wire up this config's ACME client to use our own facilities for solving
 | 
			
		||||
		// the challenge: our own certificate cache, since we already have a listener
 | 
			
		||||
		if caddy.HasListenerWithAddress(net.JoinHostPort(config.ListenHost, useTLSALPNPort)) {
 | 
			
		||||
			c.acmeClient.SetChallengeProvider(acme.TLSALPN01, tlsALPNSolver{certCache: config.certCache})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Disable any challenges that should not be used
 | 
			
		||||
		var disabledChallenges []acme.Challenge
 | 
			
		||||
		if DisableHTTPChallenge {
 | 
			
		||||
			disabledChallenges = append(disabledChallenges, acme.HTTP01)
 | 
			
		||||
		}
 | 
			
		||||
		if DisableTLSALPNChallenge {
 | 
			
		||||
			disabledChallenges = append(disabledChallenges, acme.TLSALPN01)
 | 
			
		||||
		}
 | 
			
		||||
		if len(disabledChallenges) > 0 {
 | 
			
		||||
			c.acmeClient.ExcludeChallenges(disabledChallenges)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// Otherwise, use DNS challenge exclusively
 | 
			
		||||
 | 
			
		||||
		// Load provider constructor function
 | 
			
		||||
		provFn, ok := dnsProviders[config.DNSProvider]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil, errors.New("unknown DNS provider by name '" + config.DNSProvider + "'")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// We could pass credentials to create the provider, but for now
 | 
			
		||||
		// just let the solver package get them from the environment
 | 
			
		||||
		prov, err := provFn()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Use the DNS challenge exclusively
 | 
			
		||||
		c.acmeClient.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSALPN01})
 | 
			
		||||
		c.acmeClient.SetChallengeProvider(acme.DNS01, prov)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return c, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Obtain obtains a single certificate for name. It stores the certificate
 | 
			
		||||
// on the disk if successful. This function is safe for concurrent use.
 | 
			
		||||
//
 | 
			
		||||
// Right now our storage mechanism only supports one name per certificate,
 | 
			
		||||
// so this function (along with Renew and Revoke) only accepts one domain
 | 
			
		||||
// as input. It can be easily modified to support SAN certificates if our
 | 
			
		||||
// storage mechanism is upgraded later.
 | 
			
		||||
//
 | 
			
		||||
// Callers who have access to a Config value should use the ObtainCert
 | 
			
		||||
// method on that instead of this lower-level method.
 | 
			
		||||
func (c *ACMEClient) Obtain(name string) error {
 | 
			
		||||
	waiter, err := c.storage.TryLock(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if waiter != nil {
 | 
			
		||||
		log.Printf("[INFO] Certificate for %s is already being obtained elsewhere and stored; waiting", name)
 | 
			
		||||
		waiter.Wait()
 | 
			
		||||
		return nil // we assume the process with the lock succeeded, rather than hammering this execution path again
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err := c.storage.Unlock(name); err != nil {
 | 
			
		||||
			log.Printf("[ERROR] Unable to unlock obtain call for %s: %v", name, err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	for attempts := 0; attempts < 2; attempts++ {
 | 
			
		||||
		namesObtaining.Add([]string{name})
 | 
			
		||||
		acmeMu.Lock()
 | 
			
		||||
		certificate, err := c.acmeClient.ObtainCertificate([]string{name}, true, nil, c.config.MustStaple)
 | 
			
		||||
		acmeMu.Unlock()
 | 
			
		||||
		namesObtaining.Remove([]string{name})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// for a certain kind of error, we can enumerate the error per-domain
 | 
			
		||||
			if failures, ok := err.(acme.ObtainError); ok && len(failures) > 0 {
 | 
			
		||||
				var errMsg string // combine all the failures into a single error message
 | 
			
		||||
				for errDomain, obtainErr := range failures {
 | 
			
		||||
					if obtainErr == nil {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
					errMsg += fmt.Sprintf("[%s] failed to get certificate: %v\n", errDomain, obtainErr)
 | 
			
		||||
				}
 | 
			
		||||
				return errors.New(errMsg)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return fmt.Errorf("[%s] failed to obtain certificate: %v", name, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// double-check that we actually got a certificate, in case there's a bug upstream (see issue #2121)
 | 
			
		||||
		if certificate.Domain == "" || certificate.Certificate == nil {
 | 
			
		||||
			return errors.New("returned certificate was empty; probably an unchecked error obtaining it")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Success - immediately save the certificate resource
 | 
			
		||||
		err = saveCertResource(c.storage, certificate)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("error saving assets for %v: %v", name, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		break
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go telemetry.Increment("tls_acme_certs_obtained")
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Renew renews the managed certificate for name. It puts the renewed
 | 
			
		||||
// certificate into storage (not the cache). This function is safe for
 | 
			
		||||
// concurrent use.
 | 
			
		||||
//
 | 
			
		||||
// Callers who have access to a Config value should use the RenewCert
 | 
			
		||||
// method on that instead of this lower-level method.
 | 
			
		||||
func (c *ACMEClient) Renew(name string) error {
 | 
			
		||||
	waiter, err := c.storage.TryLock(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if waiter != nil {
 | 
			
		||||
		log.Printf("[INFO] Certificate for %s is already being renewed elsewhere and stored; waiting", name)
 | 
			
		||||
		waiter.Wait()
 | 
			
		||||
		return nil // assume that the worker that renewed the cert succeeded; avoid hammering this path over and over
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err := c.storage.Unlock(name); err != nil {
 | 
			
		||||
			log.Printf("[ERROR] Unable to unlock renew call for %s: %v", name, err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// Prepare for renewal (load PEM cert, key, and meta)
 | 
			
		||||
	siteData, err := c.storage.LoadSite(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	var certMeta acme.CertificateResource
 | 
			
		||||
	err = json.Unmarshal(siteData.Meta, &certMeta)
 | 
			
		||||
	certMeta.Certificate = siteData.Cert
 | 
			
		||||
	certMeta.PrivateKey = siteData.Key
 | 
			
		||||
 | 
			
		||||
	// Perform renewal and retry if necessary, but not too many times.
 | 
			
		||||
	var newCertMeta *acme.CertificateResource
 | 
			
		||||
	var success bool
 | 
			
		||||
	for attempts := 0; attempts < 2; attempts++ {
 | 
			
		||||
		namesObtaining.Add([]string{name})
 | 
			
		||||
		acmeMu.Lock()
 | 
			
		||||
		newCertMeta, err = c.acmeClient.RenewCertificate(certMeta, true, c.config.MustStaple)
 | 
			
		||||
		acmeMu.Unlock()
 | 
			
		||||
		namesObtaining.Remove([]string{name})
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			// double-check that we actually got a certificate; check a couple fields, just in case
 | 
			
		||||
			if newCertMeta == nil || newCertMeta.Domain == "" || newCertMeta.Certificate == nil {
 | 
			
		||||
				err = errors.New("returned certificate was empty; probably an unchecked error renewing it")
 | 
			
		||||
			} else {
 | 
			
		||||
				success = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// wait a little bit and try again
 | 
			
		||||
		wait := 10 * time.Second
 | 
			
		||||
		log.Printf("[ERROR] Renewing [%v]: %v; trying again in %s", name, err, wait)
 | 
			
		||||
		time.Sleep(wait)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !success {
 | 
			
		||||
		return errors.New("too many renewal attempts; last error: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	caddy.EmitEvent(caddy.CertRenewEvent, name)
 | 
			
		||||
	go telemetry.Increment("tls_acme_certs_renewed")
 | 
			
		||||
 | 
			
		||||
	return saveCertResource(c.storage, newCertMeta)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Revoke revokes the certificate for name and deletes
 | 
			
		||||
// it from storage.
 | 
			
		||||
func (c *ACMEClient) Revoke(name string) error {
 | 
			
		||||
	siteExists, err := c.storage.SiteExists(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !siteExists {
 | 
			
		||||
		return errors.New("no certificate and key for " + name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	siteData, err := c.storage.LoadSite(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = c.acmeClient.RevokeCertificate(siteData.Cert)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go telemetry.Increment("tls_acme_certs_revoked")
 | 
			
		||||
 | 
			
		||||
	err = c.storage.DeleteSite(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New("certificate revoked, but unable to delete certificate file: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// namesObtaining is a set of hostnames with thread-safe
 | 
			
		||||
// methods. A name should be in this set only while this
 | 
			
		||||
// package is in the process of obtaining a certificate
 | 
			
		||||
// for the name. ACME challenges that are received for
 | 
			
		||||
// names which are not in this set were not initiated by
 | 
			
		||||
// this package and probably should not be handled by
 | 
			
		||||
// this package.
 | 
			
		||||
var namesObtaining = nameCoordinator{names: make(map[string]struct{})}
 | 
			
		||||
 | 
			
		||||
type nameCoordinator struct {
 | 
			
		||||
	names map[string]struct{}
 | 
			
		||||
	mu    sync.RWMutex
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Add adds names to c. It is safe for concurrent use.
 | 
			
		||||
func (c *nameCoordinator) Add(names []string) {
 | 
			
		||||
	c.mu.Lock()
 | 
			
		||||
	for _, name := range names {
 | 
			
		||||
		c.names[strings.ToLower(name)] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	c.mu.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Remove removes names from c. It is safe for concurrent use.
 | 
			
		||||
func (c *nameCoordinator) Remove(names []string) {
 | 
			
		||||
	c.mu.Lock()
 | 
			
		||||
	for _, name := range names {
 | 
			
		||||
		delete(c.names, strings.ToLower(name))
 | 
			
		||||
	}
 | 
			
		||||
	c.mu.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Has returns true if c has name. It is safe for concurrent use.
 | 
			
		||||
func (c *nameCoordinator) Has(name string) bool {
 | 
			
		||||
	hostname, _, err := net.SplitHostPort(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		hostname = name
 | 
			
		||||
	}
 | 
			
		||||
	c.mu.RLock()
 | 
			
		||||
	_, ok := c.names[strings.ToLower(hostname)]
 | 
			
		||||
	c.mu.RUnlock()
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// KnownACMECAs is a list of ACME directory endpoints of
 | 
			
		||||
// known, public, and trusted ACME-compatible certificate
 | 
			
		||||
// authorities.
 | 
			
		||||
var KnownACMECAs = []string{
 | 
			
		||||
	"https://acme-v02.api.letsencrypt.org/directory",
 | 
			
		||||
}
 | 
			
		||||
@ -1,17 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 caddytls
 | 
			
		||||
 | 
			
		||||
// TODO
 | 
			
		||||
@ -17,16 +17,15 @@ package caddytls
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"github.com/xenolf/lego/challenge/tlsalpn01"
 | 
			
		||||
 | 
			
		||||
	"github.com/klauspost/cpuid"
 | 
			
		||||
	"github.com/mholt/caddy"
 | 
			
		||||
	"github.com/xenolf/lego/acme"
 | 
			
		||||
	"github.com/mholt/certmagic"
 | 
			
		||||
	"github.com/xenolf/lego/certcrypto"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Config describes how TLS should be configured and used.
 | 
			
		||||
@ -64,102 +63,31 @@ type Config struct {
 | 
			
		||||
	// 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 means this config should be managed
 | 
			
		||||
	// by the CertMagic Config (Manager field)
 | 
			
		||||
	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
 | 
			
		||||
	// Manager is how certificates are managed
 | 
			
		||||
	Manager *certmagic.Config
 | 
			
		||||
 | 
			
		||||
	// 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; if non-empty,
 | 
			
		||||
	// this port will be used instead of
 | 
			
		||||
	// HTTPChallengePort to spin up a listener for
 | 
			
		||||
	// the HTTP challenge
 | 
			
		||||
	AltHTTPPort string
 | 
			
		||||
 | 
			
		||||
	// The alternate port (ONLY port, not host)
 | 
			
		||||
	// to use for the ACME TLS-ALPN challenge;
 | 
			
		||||
	// the system must forward TLSALPNChallengePort
 | 
			
		||||
	// to this port for challenge to succeed
 | 
			
		||||
	AltTLSALPNPort 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
 | 
			
		||||
 | 
			
		||||
	// The map of hostname to certificate hash. This is used to complete
 | 
			
		||||
	// handshakes and serve the right certificate given the SNI.
 | 
			
		||||
	Certificates map[string]string
 | 
			
		||||
 | 
			
		||||
	certCache *certificateCache // pointer to the Instance's certificate store
 | 
			
		||||
	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
 | 
			
		||||
 | 
			
		||||
	// The url to call to check if an on-demand tls certificate should
 | 
			
		||||
	// be issued. If a request to the URL fails or returns a non 2xx
 | 
			
		||||
	// status on-demand issuances must fail.
 | 
			
		||||
	AskURL *url.URL
 | 
			
		||||
	// The final tls.Config created with
 | 
			
		||||
	// buildStandardTLSConfig()
 | 
			
		||||
	tlsConfig *tls.Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewConfig returns a new Config with a pointer to the instance's
 | 
			
		||||
@ -167,149 +95,21 @@ type OnDemandState struct {
 | 
			
		||||
// the returned Config for successful practical use.
 | 
			
		||||
func NewConfig(inst *caddy.Instance) *Config {
 | 
			
		||||
	inst.StorageMu.RLock()
 | 
			
		||||
	certCache, ok := inst.Storage[CertCacheInstStorageKey].(*certificateCache)
 | 
			
		||||
	certCache, ok := inst.Storage[CertCacheInstStorageKey].(*certmagic.Cache)
 | 
			
		||||
	inst.StorageMu.RUnlock()
 | 
			
		||||
	if !ok || certCache == nil {
 | 
			
		||||
		certCache = &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
		certCache = certmagic.NewCache(certmagic.FileStorage{Path: caddy.AssetsPath()})
 | 
			
		||||
		inst.OnShutdown = append(inst.OnShutdown, func() error {
 | 
			
		||||
			certCache.Stop()
 | 
			
		||||
			return nil
 | 
			
		||||
		})
 | 
			
		||||
		inst.StorageMu.Lock()
 | 
			
		||||
		inst.Storage[CertCacheInstStorageKey] = certCache
 | 
			
		||||
		inst.StorageMu.Unlock()
 | 
			
		||||
	}
 | 
			
		||||
	cfg := new(Config)
 | 
			
		||||
	cfg.Certificates = make(map[string]string)
 | 
			
		||||
	cfg.certCache = certCache
 | 
			
		||||
	return cfg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 {
 | 
			
		||||
	skip, err := c.preObtainOrRenewChecks(name, allowPrompts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	return &Config{
 | 
			
		||||
		Manager: certmagic.NewWithCache(certCache, certmagic.Config{}), // TODO
 | 
			
		||||
	}
 | 
			
		||||
	if skip {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// we expect this to be a new (non-existent) site
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
	skip, err := c.preObtainOrRenewChecks(name, allowPrompts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if skip {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := newACMEClient(c, allowPrompts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return client.Renew(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// preObtainOrRenewChecks perform a few simple checks before
 | 
			
		||||
// obtaining or renewing a certificate with ACME, and returns
 | 
			
		||||
// whether this name should be skipped (like if it's not
 | 
			
		||||
// managed TLS) as well as any error. It ensures that the
 | 
			
		||||
// config is Managed, that the name qualifies for a certificate,
 | 
			
		||||
// and that an email address is available.
 | 
			
		||||
func (c *Config) preObtainOrRenewChecks(name string, allowPrompts bool) (bool, error) {
 | 
			
		||||
	if !c.Managed || !HostQualifies(name) {
 | 
			
		||||
		return true, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// wildcard certificates require DNS challenge (as of March 2018)
 | 
			
		||||
	if strings.Contains(name, "*") && c.DNSProvider == "" {
 | 
			
		||||
		return false, fmt.Errorf("wildcard domain name (%s) requires DNS challenge; use dns subdirective to configure it", name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.ACMEEmail == "" {
 | 
			
		||||
		var err error
 | 
			
		||||
		c.ACMEEmail, err = getEmail(c, allowPrompts)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
@ -346,20 +146,20 @@ func (c *Config) buildStandardTLSConfig() error {
 | 
			
		||||
	// ensure ALPN includes the ACME TLS-ALPN protocol
 | 
			
		||||
	var alpnFound bool
 | 
			
		||||
	for _, a := range c.ALPN {
 | 
			
		||||
		if a == acme.ACMETLS1Protocol {
 | 
			
		||||
		if a == tlsalpn01.ACMETLS1Protocol {
 | 
			
		||||
			alpnFound = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !alpnFound {
 | 
			
		||||
		c.ALPN = append(c.ALPN, acme.ACMETLS1Protocol)
 | 
			
		||||
		c.ALPN = append(c.ALPN, tlsalpn01.ACMETLS1Protocol)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	config.MinVersion = c.ProtocolMinVersion
 | 
			
		||||
	config.MaxVersion = c.ProtocolMaxVersion
 | 
			
		||||
	config.ClientAuth = c.ClientAuth
 | 
			
		||||
	config.NextProtos = c.ALPN
 | 
			
		||||
	config.GetCertificate = c.GetCertificate
 | 
			
		||||
	config.GetCertificate = c.Manager.GetCertificate
 | 
			
		||||
 | 
			
		||||
	// set up client authentication if enabled
 | 
			
		||||
	if config.ClientAuth != tls.NoClientCert {
 | 
			
		||||
@ -580,12 +380,12 @@ func SetDefaultTLSParams(config *Config) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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,
 | 
			
		||||
var supportedKeyTypes = map[string]certcrypto.KeyType{
 | 
			
		||||
	"P384":    certcrypto.EC384,
 | 
			
		||||
	"P256":    certcrypto.EC256,
 | 
			
		||||
	"RSA8192": certcrypto.RSA8192,
 | 
			
		||||
	"RSA4096": certcrypto.RSA4096,
 | 
			
		||||
	"RSA2048": certcrypto.RSA2048,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SupportedProtocols is a map of supported protocols.
 | 
			
		||||
@ -605,7 +405,7 @@ func GetSupportedProtocolName(protocol uint16) (string, error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "", errors.New("name: unsuported protocol")
 | 
			
		||||
	return "", fmt.Errorf("name: unsuported protocol")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SupportedCiphersMap has supported ciphers, used only for parsing config.
 | 
			
		||||
@ -643,7 +443,7 @@ func GetSupportedCipherName(cipher uint16) (string, error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "", errors.New("name: unsuported cipher")
 | 
			
		||||
	return "", fmt.Errorf("name: unsuported cipher")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List of all the ciphers we want to use by default
 | 
			
		||||
@ -706,24 +506,6 @@ var defaultCurves = []tls.CurveID{
 | 
			
		||||
	tls.CurveP256,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
 | 
			
		||||
	// 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"
 | 
			
		||||
 | 
			
		||||
	// CertCacheInstStorageKey is the name of the key for
 | 
			
		||||
	// accessing the certificate storage on the *caddy.Instance.
 | 
			
		||||
	CertCacheInstStorageKey = "tls_cert_cache"
 | 
			
		||||
)
 | 
			
		||||
// CertCacheInstStorageKey is the name of the key for
 | 
			
		||||
// accessing the certificate storage on the *caddy.Instance.
 | 
			
		||||
const CertCacheInstStorageKey = "tls_cert_cache"
 | 
			
		||||
 | 
			
		||||
@ -16,8 +16,6 @@ package caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
@ -110,120 +108,3 @@ func TestGetPreferredDefaultCiphers(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageForNoURL(t *testing.T) {
 | 
			
		||||
	c := &Config{}
 | 
			
		||||
	if _, err := c.StorageFor(""); err == nil {
 | 
			
		||||
		t.Fatal("Expected error on empty URL")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageForLowercasesAndPrefixesScheme(t *testing.T) {
 | 
			
		||||
	resultStr := ""
 | 
			
		||||
	RegisterStorageProvider("fake-TestStorageForLowercasesAndPrefixesScheme", func(caURL *url.URL) (Storage, error) {
 | 
			
		||||
		resultStr = caURL.String()
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	})
 | 
			
		||||
	c := &Config{
 | 
			
		||||
		StorageProvider: "fake-TestStorageForLowercasesAndPrefixesScheme",
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := c.StorageFor("EXAMPLE.COM/BLAH"); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if resultStr != "https://example.com/blah" {
 | 
			
		||||
		t.Fatalf("Unexpected CA URL string: %v", resultStr)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageForBadURL(t *testing.T) {
 | 
			
		||||
	c := &Config{}
 | 
			
		||||
	if _, err := c.StorageFor("http://192.168.0.%31/"); err == nil {
 | 
			
		||||
		t.Fatal("Expected error for bad URL")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageForDefault(t *testing.T) {
 | 
			
		||||
	c := &Config{}
 | 
			
		||||
	s, err := c.StorageFor("example.com")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := s.(*FileStorage); !ok {
 | 
			
		||||
		t.Fatalf("Unexpected storage type: %#v", s)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageForCustom(t *testing.T) {
 | 
			
		||||
	storage := fakeStorage("fake-TestStorageForCustom")
 | 
			
		||||
	RegisterStorageProvider("fake-TestStorageForCustom", func(caURL *url.URL) (Storage, error) { return storage, nil })
 | 
			
		||||
	c := &Config{
 | 
			
		||||
		StorageProvider: "fake-TestStorageForCustom",
 | 
			
		||||
	}
 | 
			
		||||
	s, err := c.StorageFor("example.com")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if s != storage {
 | 
			
		||||
		t.Fatal("Unexpected storage")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageForCustomError(t *testing.T) {
 | 
			
		||||
	RegisterStorageProvider("fake-TestStorageForCustomError", func(caURL *url.URL) (Storage, error) { return nil, errors.New("some error") })
 | 
			
		||||
	c := &Config{
 | 
			
		||||
		StorageProvider: "fake-TestStorageForCustomError",
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := c.StorageFor("example.com"); err == nil {
 | 
			
		||||
		t.Fatal("Expecting error")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorageForCustomNil(t *testing.T) {
 | 
			
		||||
	// Should fall through to the default
 | 
			
		||||
	c := &Config{StorageProvider: ""}
 | 
			
		||||
	s, err := c.StorageFor("example.com")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := s.(*FileStorage); !ok {
 | 
			
		||||
		t.Fatalf("Unexpected storage type: %#v", s)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type fakeStorage string
 | 
			
		||||
 | 
			
		||||
func (s fakeStorage) SiteExists(domain string) (bool, error) {
 | 
			
		||||
	panic("no impl")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s fakeStorage) LoadSite(domain string) (*SiteData, error) {
 | 
			
		||||
	panic("no impl")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s fakeStorage) StoreSite(domain string, data *SiteData) error {
 | 
			
		||||
	panic("no impl")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s fakeStorage) DeleteSite(domain string) error {
 | 
			
		||||
	panic("no impl")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s fakeStorage) TryLock(domain string) (Waiter, error) {
 | 
			
		||||
	panic("no impl")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s fakeStorage) Unlock(domain string) error {
 | 
			
		||||
	panic("no impl")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s fakeStorage) LoadUser(email string) (*UserData, error) {
 | 
			
		||||
	panic("no impl")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s fakeStorage) StoreUser(email string, data *UserData) error {
 | 
			
		||||
	panic("no impl")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s fakeStorage) MostRecentUserEmail() string {
 | 
			
		||||
	panic("no impl")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -15,265 +15,20 @@
 | 
			
		||||
package caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto"
 | 
			
		||||
	"crypto/ecdsa"
 | 
			
		||||
	"crypto/elliptic"
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"crypto/rsa"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"crypto/x509/pkix"
 | 
			
		||||
	"encoding/pem"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"hash/fnv"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"math/big"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/crypto/ocsp"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy"
 | 
			
		||||
	"github.com/xenolf/lego/acme"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// loadPrivateKey loads a PEM-encoded ECC/RSA private key from an array of bytes.
 | 
			
		||||
func loadPrivateKey(keyBytes []byte) (crypto.PrivateKey, error) {
 | 
			
		||||
	keyBlock, _ := pem.Decode(keyBytes)
 | 
			
		||||
 | 
			
		||||
	switch keyBlock.Type {
 | 
			
		||||
	case "RSA PRIVATE KEY":
 | 
			
		||||
		return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
 | 
			
		||||
	case "EC PRIVATE KEY":
 | 
			
		||||
		return x509.ParseECPrivateKey(keyBlock.Bytes)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil, errors.New("unknown private key type")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// savePrivateKey saves a PEM-encoded ECC/RSA private key to an array of bytes.
 | 
			
		||||
func savePrivateKey(key crypto.PrivateKey) ([]byte, error) {
 | 
			
		||||
	var pemType string
 | 
			
		||||
	var keyBytes []byte
 | 
			
		||||
	switch key := key.(type) {
 | 
			
		||||
	case *ecdsa.PrivateKey:
 | 
			
		||||
		var err error
 | 
			
		||||
		pemType = "EC"
 | 
			
		||||
		keyBytes, err = x509.MarshalECPrivateKey(key)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	case *rsa.PrivateKey:
 | 
			
		||||
		pemType = "RSA"
 | 
			
		||||
		keyBytes = x509.MarshalPKCS1PrivateKey(key)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pemKey := pem.Block{Type: pemType + " PRIVATE KEY", Bytes: keyBytes}
 | 
			
		||||
	return pem.EncodeToMemory(&pemKey), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// stapleOCSP staples OCSP information to cert for hostname name.
 | 
			
		||||
// If you have it handy, you should pass in the PEM-encoded certificate
 | 
			
		||||
// bundle; otherwise the DER-encoded cert will have to be PEM-encoded.
 | 
			
		||||
// If you don't have the PEM blocks already, just pass in nil.
 | 
			
		||||
//
 | 
			
		||||
// Errors here are not necessarily fatal, it could just be that the
 | 
			
		||||
// certificate doesn't have an issuer URL.
 | 
			
		||||
func stapleOCSP(cert *Certificate, pemBundle []byte) error {
 | 
			
		||||
	if pemBundle == nil {
 | 
			
		||||
		// The function in the acme package that gets OCSP requires a PEM-encoded cert
 | 
			
		||||
		bundle := new(bytes.Buffer)
 | 
			
		||||
		for _, derBytes := range cert.Certificate.Certificate {
 | 
			
		||||
			pem.Encode(bundle, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
 | 
			
		||||
		}
 | 
			
		||||
		pemBundle = bundle.Bytes()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var ocspBytes []byte
 | 
			
		||||
	var ocspResp *ocsp.Response
 | 
			
		||||
	var ocspErr error
 | 
			
		||||
	var gotNewOCSP bool
 | 
			
		||||
 | 
			
		||||
	// First try to load OCSP staple from storage and see if
 | 
			
		||||
	// we can still use it.
 | 
			
		||||
	// TODO: Use Storage interface instead of disk directly
 | 
			
		||||
	var ocspFileNamePrefix string
 | 
			
		||||
	if len(cert.Names) > 0 {
 | 
			
		||||
		firstName := strings.Replace(cert.Names[0], "*", "wildcard_", -1)
 | 
			
		||||
		ocspFileNamePrefix = firstName + "-"
 | 
			
		||||
	}
 | 
			
		||||
	ocspFileName := ocspFileNamePrefix + fastHash(pemBundle)
 | 
			
		||||
	ocspCachePath := filepath.Join(ocspFolder, ocspFileName)
 | 
			
		||||
	cachedOCSP, err := ioutil.ReadFile(ocspCachePath)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		resp, err := ocsp.ParseResponse(cachedOCSP, nil)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			if freshOCSP(resp) {
 | 
			
		||||
				// staple is still fresh; use it
 | 
			
		||||
				ocspBytes = cachedOCSP
 | 
			
		||||
				ocspResp = resp
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// invalid contents; delete the file
 | 
			
		||||
			// (we do this independently of the maintenance routine because
 | 
			
		||||
			// in this case we know for sure this should be a staple file
 | 
			
		||||
			// because we loaded it by name, whereas the maintenance routine
 | 
			
		||||
			// just iterates the list of files, even if somehow a non-staple
 | 
			
		||||
			// file gets in the folder. in this case we are sure it is corrupt.)
 | 
			
		||||
			err := os.Remove(ocspCachePath)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Printf("[WARNING] Unable to delete invalid OCSP staple file: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If we couldn't get a fresh staple by reading the cache,
 | 
			
		||||
	// then we need to request it from the OCSP responder
 | 
			
		||||
	if ocspResp == nil || len(ocspBytes) == 0 {
 | 
			
		||||
		ocspBytes, ocspResp, ocspErr = acme.GetOCSPForCert(pemBundle)
 | 
			
		||||
		if ocspErr != nil {
 | 
			
		||||
			// An error here is not a problem because a certificate may simply
 | 
			
		||||
			// not contain a link to an OCSP server. But we should log it anyway.
 | 
			
		||||
			// There's nothing else we can do to get OCSP for this certificate,
 | 
			
		||||
			// so we can return here with the error.
 | 
			
		||||
			return fmt.Errorf("no OCSP stapling for %v: %v", cert.Names, ocspErr)
 | 
			
		||||
		}
 | 
			
		||||
		gotNewOCSP = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// By now, we should have a response. If good, staple it to
 | 
			
		||||
	// the certificate. If the OCSP response was not loaded from
 | 
			
		||||
	// storage, we persist it for next time.
 | 
			
		||||
	if ocspResp.Status == ocsp.Good {
 | 
			
		||||
		if ocspResp.NextUpdate.After(cert.NotAfter) {
 | 
			
		||||
			// uh oh, this OCSP response expires AFTER the certificate does, that's kinda bogus.
 | 
			
		||||
			// it was the reason a lot of Symantec-validated sites (not Caddy) went down
 | 
			
		||||
			// in October 2017. https://twitter.com/mattiasgeniar/status/919432824708648961
 | 
			
		||||
			return fmt.Errorf("invalid: OCSP response for %v valid after certificate expiration (%s)",
 | 
			
		||||
				cert.Names, cert.NotAfter.Sub(ocspResp.NextUpdate))
 | 
			
		||||
		}
 | 
			
		||||
		cert.Certificate.OCSPStaple = ocspBytes
 | 
			
		||||
		cert.OCSP = ocspResp
 | 
			
		||||
		if gotNewOCSP {
 | 
			
		||||
			err := os.MkdirAll(filepath.Join(caddy.AssetsPath(), "ocsp"), 0700)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("unable to make OCSP staple path for %v: %v", cert.Names, err)
 | 
			
		||||
			}
 | 
			
		||||
			err = ioutil.WriteFile(ocspCachePath, ocspBytes, 0644)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("unable to write OCSP staple file for %v: %v", cert.Names, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func makeSelfSignedCertWithCustomSAN(sans []string, config *Config) (Certificate, error) {
 | 
			
		||||
	// start by generating private key
 | 
			
		||||
	var privKey interface{}
 | 
			
		||||
	var err error
 | 
			
		||||
	switch config.KeyType {
 | 
			
		||||
	case "", acme.EC256:
 | 
			
		||||
		privKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 | 
			
		||||
	case acme.EC384:
 | 
			
		||||
		privKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
 | 
			
		||||
	case acme.RSA2048:
 | 
			
		||||
		privKey, err = rsa.GenerateKey(rand.Reader, 2048)
 | 
			
		||||
	case acme.RSA4096:
 | 
			
		||||
		privKey, err = rsa.GenerateKey(rand.Reader, 4096)
 | 
			
		||||
	case acme.RSA8192:
 | 
			
		||||
		privKey, err = rsa.GenerateKey(rand.Reader, 8192)
 | 
			
		||||
	default:
 | 
			
		||||
		return Certificate{}, fmt.Errorf("cannot generate private key; unknown key type %v", config.KeyType)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Certificate{}, fmt.Errorf("failed to generate private key: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// create certificate structure with proper values
 | 
			
		||||
	notBefore := time.Now()
 | 
			
		||||
	notAfter := notBefore.Add(24 * time.Hour * 7)
 | 
			
		||||
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
 | 
			
		||||
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Certificate{}, fmt.Errorf("failed to generate serial number: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	cert := &x509.Certificate{
 | 
			
		||||
		SerialNumber: serialNumber,
 | 
			
		||||
		Subject:      pkix.Name{Organization: []string{"Caddy Self-Signed"}},
 | 
			
		||||
		NotBefore:    notBefore,
 | 
			
		||||
		NotAfter:     notAfter,
 | 
			
		||||
		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
 | 
			
		||||
		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
 | 
			
		||||
	}
 | 
			
		||||
	if len(sans) == 0 {
 | 
			
		||||
		sans = []string{""}
 | 
			
		||||
	}
 | 
			
		||||
	var names []string
 | 
			
		||||
	for _, san := range sans {
 | 
			
		||||
		if ip := net.ParseIP(san); ip != nil {
 | 
			
		||||
			names = append(names, strings.ToLower(ip.String()))
 | 
			
		||||
			cert.IPAddresses = append(cert.IPAddresses, ip)
 | 
			
		||||
		} else {
 | 
			
		||||
			names = append(names, strings.ToLower(san))
 | 
			
		||||
			cert.DNSNames = append(cert.DNSNames, strings.ToLower(san))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	publicKey := func(privKey interface{}) interface{} {
 | 
			
		||||
		switch k := privKey.(type) {
 | 
			
		||||
		case *rsa.PrivateKey:
 | 
			
		||||
			return &k.PublicKey
 | 
			
		||||
		case *ecdsa.PrivateKey:
 | 
			
		||||
			return &k.PublicKey
 | 
			
		||||
		default:
 | 
			
		||||
			return errors.New("unknown key type")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	derBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, publicKey(privKey), privKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Certificate{}, fmt.Errorf("could not create certificate: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	chain := [][]byte{derBytes}
 | 
			
		||||
 | 
			
		||||
	return Certificate{
 | 
			
		||||
		Certificate: tls.Certificate{
 | 
			
		||||
			Certificate: chain,
 | 
			
		||||
			PrivateKey:  privKey,
 | 
			
		||||
			Leaf:        cert,
 | 
			
		||||
		},
 | 
			
		||||
		Names:    names,
 | 
			
		||||
		NotAfter: cert.NotAfter,
 | 
			
		||||
		Hash:     hashCertificateChain(chain),
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// makeSelfSignedCertForConfig makes a self-signed certificate according
 | 
			
		||||
// to the parameters in config and caches the new cert in config directly.
 | 
			
		||||
func makeSelfSignedCertForConfig(config *Config) error {
 | 
			
		||||
	cert, err := makeSelfSignedCertWithCustomSAN([]string{config.Hostname}, config)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	config.cacheCertificate(cert)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RotateSessionTicketKeys rotates the TLS session ticket keys
 | 
			
		||||
// on cfg every TicketRotateInterval. It spawns a new goroutine so
 | 
			
		||||
// this function does NOT block. It returns a channel you should
 | 
			
		||||
// close when you are ready to stop the key rotation, like when the
 | 
			
		||||
// server using cfg is no longer running.
 | 
			
		||||
//
 | 
			
		||||
// TODO: See about moving this into CertMagic and using its Storage
 | 
			
		||||
func RotateSessionTicketKeys(cfg *tls.Config) chan struct{} {
 | 
			
		||||
	ch := make(chan struct{})
 | 
			
		||||
	ticker := time.NewTicker(TicketRotateInterval)
 | 
			
		||||
@ -347,15 +102,6 @@ func standaloneTLSTicketKeyRotation(c *tls.Config, ticker *time.Ticker, exitChan
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// fastHash hashes input using a hashing algorithm that
 | 
			
		||||
// is fast, and returns the hash as a hex-encoded string.
 | 
			
		||||
// Do not use this for cryptographic purposes.
 | 
			
		||||
func fastHash(input []byte) string {
 | 
			
		||||
	h := fnv.New32a()
 | 
			
		||||
	h.Write(input)
 | 
			
		||||
	return fmt.Sprintf("%x", h.Sum32())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// NumTickets is how many tickets to hold and consider
 | 
			
		||||
	// to decrypt TLS sessions.
 | 
			
		||||
 | 
			
		||||
@ -15,83 +15,11 @@
 | 
			
		||||
package caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto"
 | 
			
		||||
	"crypto/ecdsa"
 | 
			
		||||
	"crypto/elliptic"
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"crypto/rsa"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSaveAndLoadRSAPrivateKey(t *testing.T) {
 | 
			
		||||
	privateKey, err := rsa.GenerateKey(rand.Reader, 128) // make tests faster; small key size OK for testing
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// test save
 | 
			
		||||
	savedBytes, err := savePrivateKey(privateKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal("error saving private key:", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// test load
 | 
			
		||||
	loadedKey, err := loadPrivateKey(savedBytes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error("error loading private key:", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// verify loaded key is correct
 | 
			
		||||
	if !PrivateKeysSame(privateKey, loadedKey) {
 | 
			
		||||
		t.Error("Expected key bytes to be the same, but they weren't")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSaveAndLoadECCPrivateKey(t *testing.T) {
 | 
			
		||||
	privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// test save
 | 
			
		||||
	savedBytes, err := savePrivateKey(privateKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal("error saving private key:", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// test load
 | 
			
		||||
	loadedKey, err := loadPrivateKey(savedBytes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error("error loading private key:", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// verify loaded key is correct
 | 
			
		||||
	if !PrivateKeysSame(privateKey, loadedKey) {
 | 
			
		||||
		t.Error("Expected key bytes to be the same, but they weren't")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PrivateKeysSame compares the bytes of a and b and returns true if they are the same.
 | 
			
		||||
func PrivateKeysSame(a, b crypto.PrivateKey) bool {
 | 
			
		||||
	return bytes.Equal(PrivateKeyBytes(a), PrivateKeyBytes(b))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PrivateKeyBytes returns the bytes of DER-encoded key.
 | 
			
		||||
func PrivateKeyBytes(key crypto.PrivateKey) []byte {
 | 
			
		||||
	var keyBytes []byte
 | 
			
		||||
	switch key := key.(type) {
 | 
			
		||||
	case *rsa.PrivateKey:
 | 
			
		||||
		keyBytes = x509.MarshalPKCS1PrivateKey(key)
 | 
			
		||||
	case *ecdsa.PrivateKey:
 | 
			
		||||
		keyBytes, _ = x509.MarshalECPrivateKey(key)
 | 
			
		||||
	}
 | 
			
		||||
	return keyBytes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStandaloneTLSTicketKeyRotation(t *testing.T) {
 | 
			
		||||
	type syncPkt struct {
 | 
			
		||||
		ticketKey [32]byte
 | 
			
		||||
 | 
			
		||||
@ -1,305 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	RegisterStorageProvider("file", NewFileStorage)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewFileStorage is a StorageConstructor function that creates a new
 | 
			
		||||
// Storage instance backed by the local disk. The resulting Storage
 | 
			
		||||
// instance is guaranteed to be non-nil if there is no error.
 | 
			
		||||
func NewFileStorage(caURL *url.URL) (Storage, error) {
 | 
			
		||||
	// storageBasePath is the root path in which all TLS/ACME assets are
 | 
			
		||||
	// stored. Do not change this value during the lifetime of the program.
 | 
			
		||||
	storageBasePath := filepath.Join(caddy.AssetsPath(), "acme")
 | 
			
		||||
 | 
			
		||||
	storage := &FileStorage{Path: filepath.Join(storageBasePath, caURL.Host)}
 | 
			
		||||
	storage.Locker = &fileStorageLock{caURL: caURL.Host, storage: storage}
 | 
			
		||||
	return storage, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FileStorage facilitates forming file paths derived from a root
 | 
			
		||||
// directory. It is used to get file paths in a consistent,
 | 
			
		||||
// cross-platform way or persisting ACME assets on the file system.
 | 
			
		||||
type FileStorage struct {
 | 
			
		||||
	Path string
 | 
			
		||||
	Locker
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sites gets the directory that stores site certificate and keys.
 | 
			
		||||
func (s *FileStorage) sites() string {
 | 
			
		||||
	return filepath.Join(s.Path, "sites")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// site returns the path to the folder containing assets for domain.
 | 
			
		||||
func (s *FileStorage) site(domain string) string {
 | 
			
		||||
	domain = fileSafe(domain)
 | 
			
		||||
	return filepath.Join(s.sites(), domain)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// siteCertFile returns the path to the certificate file for domain.
 | 
			
		||||
func (s *FileStorage) siteCertFile(domain string) string {
 | 
			
		||||
	domain = fileSafe(domain)
 | 
			
		||||
	return filepath.Join(s.site(domain), domain+".crt")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// siteKeyFile returns the path to domain's private key file.
 | 
			
		||||
func (s *FileStorage) siteKeyFile(domain string) string {
 | 
			
		||||
	domain = fileSafe(domain)
 | 
			
		||||
	return filepath.Join(s.site(domain), domain+".key")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// siteMetaFile returns the path to the domain's asset metadata file.
 | 
			
		||||
func (s *FileStorage) siteMetaFile(domain string) string {
 | 
			
		||||
	domain = fileSafe(domain)
 | 
			
		||||
	return filepath.Join(s.site(domain), domain+".json")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// users gets the directory that stores account folders.
 | 
			
		||||
func (s *FileStorage) users() string {
 | 
			
		||||
	return filepath.Join(s.Path, "users")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// user gets the account folder for the user with email
 | 
			
		||||
func (s *FileStorage) user(email string) string {
 | 
			
		||||
	if email == "" {
 | 
			
		||||
		email = emptyEmail
 | 
			
		||||
	}
 | 
			
		||||
	email = fileSafe(email)
 | 
			
		||||
	return filepath.Join(s.users(), email)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// emailUsername returns the username portion of an email address (part before
 | 
			
		||||
// '@') or the original input if it can't find the "@" symbol.
 | 
			
		||||
func emailUsername(email string) string {
 | 
			
		||||
	at := strings.Index(email, "@")
 | 
			
		||||
	if at == -1 {
 | 
			
		||||
		return email
 | 
			
		||||
	} else if at == 0 {
 | 
			
		||||
		return email[1:]
 | 
			
		||||
	}
 | 
			
		||||
	return email[:at]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// userRegFile gets the path to the registration file for the user with the
 | 
			
		||||
// given email address.
 | 
			
		||||
func (s *FileStorage) userRegFile(email string) string {
 | 
			
		||||
	if email == "" {
 | 
			
		||||
		email = emptyEmail
 | 
			
		||||
	}
 | 
			
		||||
	email = strings.ToLower(email)
 | 
			
		||||
	fileName := emailUsername(email)
 | 
			
		||||
	if fileName == "" {
 | 
			
		||||
		fileName = "registration"
 | 
			
		||||
	}
 | 
			
		||||
	fileName = fileSafe(fileName)
 | 
			
		||||
	return filepath.Join(s.user(email), fileName+".json")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// userKeyFile gets the path to the private key file for the user with the
 | 
			
		||||
// given email address.
 | 
			
		||||
func (s *FileStorage) userKeyFile(email string) string {
 | 
			
		||||
	if email == "" {
 | 
			
		||||
		email = emptyEmail
 | 
			
		||||
	}
 | 
			
		||||
	email = strings.ToLower(email)
 | 
			
		||||
	fileName := emailUsername(email)
 | 
			
		||||
	if fileName == "" {
 | 
			
		||||
		fileName = "private"
 | 
			
		||||
	}
 | 
			
		||||
	fileName = fileSafe(fileName)
 | 
			
		||||
	return filepath.Join(s.user(email), fileName+".key")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// readFile abstracts a simple ioutil.ReadFile, making sure to return an
 | 
			
		||||
// ErrNotExist instance when the file is not found.
 | 
			
		||||
func (s *FileStorage) readFile(file string) ([]byte, error) {
 | 
			
		||||
	b, err := ioutil.ReadFile(file)
 | 
			
		||||
	if os.IsNotExist(err) {
 | 
			
		||||
		return nil, ErrNotExist(err)
 | 
			
		||||
	}
 | 
			
		||||
	return b, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SiteExists implements Storage.SiteExists by checking for the presence of
 | 
			
		||||
// cert and key files.
 | 
			
		||||
func (s *FileStorage) SiteExists(domain string) (bool, error) {
 | 
			
		||||
	_, err := os.Stat(s.siteCertFile(domain))
 | 
			
		||||
	if os.IsNotExist(err) {
 | 
			
		||||
		return false, nil
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = os.Stat(s.siteKeyFile(domain))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadSite implements Storage.LoadSite by loading it from disk. If it is not
 | 
			
		||||
// present, an instance of ErrNotExist is returned.
 | 
			
		||||
func (s *FileStorage) LoadSite(domain string) (*SiteData, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	siteData := new(SiteData)
 | 
			
		||||
	siteData.Cert, err = s.readFile(s.siteCertFile(domain))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	siteData.Key, err = s.readFile(s.siteKeyFile(domain))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	siteData.Meta, err = s.readFile(s.siteMetaFile(domain))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return siteData, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StoreSite implements Storage.StoreSite by writing it to disk. The base
 | 
			
		||||
// directories needed for the file are automatically created as needed.
 | 
			
		||||
func (s *FileStorage) StoreSite(domain string, data *SiteData) error {
 | 
			
		||||
	err := os.MkdirAll(s.site(domain), 0700)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("making site directory: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	err = ioutil.WriteFile(s.siteCertFile(domain), data.Cert, 0600)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("writing certificate file: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	err = ioutil.WriteFile(s.siteKeyFile(domain), data.Key, 0600)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("writing key file: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	err = ioutil.WriteFile(s.siteMetaFile(domain), data.Meta, 0600)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("writing cert meta file: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	log.Printf("[INFO][%v] Certificate written to disk: %v", domain, s.siteCertFile(domain))
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteSite implements Storage.DeleteSite by deleting just the cert from
 | 
			
		||||
// disk. If it is not present, an instance of ErrNotExist is returned.
 | 
			
		||||
func (s *FileStorage) DeleteSite(domain string) error {
 | 
			
		||||
	err := os.Remove(s.siteCertFile(domain))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return ErrNotExist(err)
 | 
			
		||||
		}
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadUser implements Storage.LoadUser by loading it from disk. If it is not
 | 
			
		||||
// present, an instance of ErrNotExist is returned.
 | 
			
		||||
func (s *FileStorage) LoadUser(email string) (*UserData, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	userData := new(UserData)
 | 
			
		||||
	userData.Reg, err = s.readFile(s.userRegFile(email))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	userData.Key, err = s.readFile(s.userKeyFile(email))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return userData, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StoreUser implements Storage.StoreUser by writing it to disk. The base
 | 
			
		||||
// directories needed for the file are automatically created as needed.
 | 
			
		||||
func (s *FileStorage) StoreUser(email string, data *UserData) error {
 | 
			
		||||
	err := os.MkdirAll(s.user(email), 0700)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("making user directory: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	err = ioutil.WriteFile(s.userRegFile(email), data.Reg, 0600)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("writing user registration file: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	err = ioutil.WriteFile(s.userKeyFile(email), data.Key, 0600)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("writing user key file: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MostRecentUserEmail implements Storage.MostRecentUserEmail by finding the
 | 
			
		||||
// most recently written sub directory in the users' directory. It is named
 | 
			
		||||
// after the email address. This corresponds to the most recent call to
 | 
			
		||||
// StoreUser.
 | 
			
		||||
func (s *FileStorage) MostRecentUserEmail() string {
 | 
			
		||||
	userDirs, err := ioutil.ReadDir(s.users())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	var mostRecent os.FileInfo
 | 
			
		||||
	for _, dir := range userDirs {
 | 
			
		||||
		if !dir.IsDir() {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if mostRecent == nil || dir.ModTime().After(mostRecent.ModTime()) {
 | 
			
		||||
			mostRecent = dir
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if mostRecent != nil {
 | 
			
		||||
		return mostRecent.Name()
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// fileSafe standardizes and sanitizes str for use in a file path.
 | 
			
		||||
func fileSafe(str string) string {
 | 
			
		||||
	str = strings.ToLower(str)
 | 
			
		||||
	str = strings.TrimSpace(str)
 | 
			
		||||
	repl := strings.NewReplacer(
 | 
			
		||||
		"..", "",
 | 
			
		||||
		"/", "",
 | 
			
		||||
		"\\", "",
 | 
			
		||||
		// TODO: Consider also replacing "@" with "_at_" (but migrate existing accounts...)
 | 
			
		||||
		"+", "_plus_",
 | 
			
		||||
		"*", "wildcard_",
 | 
			
		||||
		"%", "",
 | 
			
		||||
		"$", "",
 | 
			
		||||
		"`", "",
 | 
			
		||||
		"~", "",
 | 
			
		||||
		":", "",
 | 
			
		||||
		";", "",
 | 
			
		||||
		"=", "",
 | 
			
		||||
		"!", "",
 | 
			
		||||
		"#", "",
 | 
			
		||||
		"&", "",
 | 
			
		||||
		"|", "",
 | 
			
		||||
		`"`, "",
 | 
			
		||||
		"'", "")
 | 
			
		||||
	return repl.Replace(str)
 | 
			
		||||
}
 | 
			
		||||
@ -1,84 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// *********************************** NOTE ********************************
 | 
			
		||||
// Due to circular package dependencies with the storagetest sub package and
 | 
			
		||||
// the fact that we want to use that harness to test file storage, most of
 | 
			
		||||
// the tests for file storage are done in the storagetest package.
 | 
			
		||||
 | 
			
		||||
func TestPathBuilders(t *testing.T) {
 | 
			
		||||
	fs := FileStorage{Path: "test"}
 | 
			
		||||
 | 
			
		||||
	for i, testcase := range []struct {
 | 
			
		||||
		in, folder, certFile, keyFile, metaFile string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in:       "example.com",
 | 
			
		||||
			folder:   filepath.Join("test", "sites", "example.com"),
 | 
			
		||||
			certFile: filepath.Join("test", "sites", "example.com", "example.com.crt"),
 | 
			
		||||
			keyFile:  filepath.Join("test", "sites", "example.com", "example.com.key"),
 | 
			
		||||
			metaFile: filepath.Join("test", "sites", "example.com", "example.com.json"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:       "*.example.com",
 | 
			
		||||
			folder:   filepath.Join("test", "sites", "wildcard_.example.com"),
 | 
			
		||||
			certFile: filepath.Join("test", "sites", "wildcard_.example.com", "wildcard_.example.com.crt"),
 | 
			
		||||
			keyFile:  filepath.Join("test", "sites", "wildcard_.example.com", "wildcard_.example.com.key"),
 | 
			
		||||
			metaFile: filepath.Join("test", "sites", "wildcard_.example.com", "wildcard_.example.com.json"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// prevent directory traversal! very important, esp. with on-demand TLS
 | 
			
		||||
			// see issue #2092
 | 
			
		||||
			in:       "a/../../../foo",
 | 
			
		||||
			folder:   filepath.Join("test", "sites", "afoo"),
 | 
			
		||||
			certFile: filepath.Join("test", "sites", "afoo", "afoo.crt"),
 | 
			
		||||
			keyFile:  filepath.Join("test", "sites", "afoo", "afoo.key"),
 | 
			
		||||
			metaFile: filepath.Join("test", "sites", "afoo", "afoo.json"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:       "b\\..\\..\\..\\foo",
 | 
			
		||||
			folder:   filepath.Join("test", "sites", "bfoo"),
 | 
			
		||||
			certFile: filepath.Join("test", "sites", "bfoo", "bfoo.crt"),
 | 
			
		||||
			keyFile:  filepath.Join("test", "sites", "bfoo", "bfoo.key"),
 | 
			
		||||
			metaFile: filepath.Join("test", "sites", "bfoo", "bfoo.json"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:       "c/foo",
 | 
			
		||||
			folder:   filepath.Join("test", "sites", "cfoo"),
 | 
			
		||||
			certFile: filepath.Join("test", "sites", "cfoo", "cfoo.crt"),
 | 
			
		||||
			keyFile:  filepath.Join("test", "sites", "cfoo", "cfoo.key"),
 | 
			
		||||
			metaFile: filepath.Join("test", "sites", "cfoo", "cfoo.json"),
 | 
			
		||||
		},
 | 
			
		||||
	} {
 | 
			
		||||
		if actual := fs.site(testcase.in); actual != testcase.folder {
 | 
			
		||||
			t.Errorf("Test %d: site folder: Expected '%s' but got '%s'", i, testcase.folder, actual)
 | 
			
		||||
		}
 | 
			
		||||
		if actual := fs.siteCertFile(testcase.in); actual != testcase.certFile {
 | 
			
		||||
			t.Errorf("Test %d: site cert file: Expected '%s' but got '%s'", i, testcase.certFile, actual)
 | 
			
		||||
		}
 | 
			
		||||
		if actual := fs.siteKeyFile(testcase.in); actual != testcase.keyFile {
 | 
			
		||||
			t.Errorf("Test %d: site key file: Expected '%s' but got '%s'", i, testcase.keyFile, actual)
 | 
			
		||||
		}
 | 
			
		||||
		if actual := fs.siteMetaFile(testcase.in); actual != testcase.metaFile {
 | 
			
		||||
			t.Errorf("Test %d: site meta file: Expected '%s' but got '%s'", i, testcase.metaFile, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -1,140 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	// be sure to remove lock files when exiting the process!
 | 
			
		||||
	caddy.OnProcessExit = append(caddy.OnProcessExit, func() {
 | 
			
		||||
		fileStorageNameLocksMu.Lock()
 | 
			
		||||
		defer fileStorageNameLocksMu.Unlock()
 | 
			
		||||
		for key, fw := range fileStorageNameLocks {
 | 
			
		||||
			os.Remove(fw.filename)
 | 
			
		||||
			delete(fileStorageNameLocks, key)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// fileStorageLock facilitates ACME-related locking by using
 | 
			
		||||
// the associated FileStorage, so multiple processes can coordinate
 | 
			
		||||
// renewals on the certificates on a shared file system.
 | 
			
		||||
type fileStorageLock struct {
 | 
			
		||||
	caURL   string
 | 
			
		||||
	storage *FileStorage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TryLock attempts to get a lock for name, otherwise it returns
 | 
			
		||||
// a Waiter value to wait until the other process is finished.
 | 
			
		||||
func (s *fileStorageLock) TryLock(name string) (Waiter, error) {
 | 
			
		||||
	fileStorageNameLocksMu.Lock()
 | 
			
		||||
	defer fileStorageNameLocksMu.Unlock()
 | 
			
		||||
 | 
			
		||||
	// see if lock already exists within this process
 | 
			
		||||
	fw, ok := fileStorageNameLocks[s.caURL+name]
 | 
			
		||||
	if ok {
 | 
			
		||||
		// lock already created within process, let caller wait on it
 | 
			
		||||
		return fw, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// attempt to persist lock to disk by creating lock file
 | 
			
		||||
	fw = &fileWaiter{
 | 
			
		||||
		filename: s.storage.siteCertFile(name) + ".lock",
 | 
			
		||||
		wg:       new(sync.WaitGroup),
 | 
			
		||||
	}
 | 
			
		||||
	// parent dir must exist
 | 
			
		||||
	if err := os.MkdirAll(s.storage.site(name), 0700); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	lf, err := os.OpenFile(fw.filename, os.O_CREATE|os.O_EXCL, 0644)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if os.IsExist(err) {
 | 
			
		||||
			// another process has the lock; use it to wait
 | 
			
		||||
			return fw, nil
 | 
			
		||||
		}
 | 
			
		||||
		// otherwise, this was some unexpected error
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	lf.Close()
 | 
			
		||||
 | 
			
		||||
	// looks like we get the lock
 | 
			
		||||
	fw.wg.Add(1)
 | 
			
		||||
	fileStorageNameLocks[s.caURL+name] = fw
 | 
			
		||||
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Unlock unlocks name.
 | 
			
		||||
func (s *fileStorageLock) Unlock(name string) error {
 | 
			
		||||
	fileStorageNameLocksMu.Lock()
 | 
			
		||||
	defer fileStorageNameLocksMu.Unlock()
 | 
			
		||||
	fw, ok := fileStorageNameLocks[s.caURL+name]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf("FileStorage: no lock to release for %s", name)
 | 
			
		||||
	}
 | 
			
		||||
	// remove lock file
 | 
			
		||||
	os.Remove(fw.filename)
 | 
			
		||||
 | 
			
		||||
	// if parent folder is now empty, remove it too to keep it tidy
 | 
			
		||||
	lockParentFolder := s.storage.site(name)
 | 
			
		||||
	dir, err := os.Open(lockParentFolder)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		items, _ := dir.Readdirnames(3) // OK to ignore error here
 | 
			
		||||
		if len(items) == 0 {
 | 
			
		||||
			os.Remove(lockParentFolder)
 | 
			
		||||
		}
 | 
			
		||||
		dir.Close()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fw.wg.Done()
 | 
			
		||||
	delete(fileStorageNameLocks, s.caURL+name)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// fileWaiter waits for a file to disappear; it polls
 | 
			
		||||
// the file system to check for the existence of a file.
 | 
			
		||||
// It also has a WaitGroup which will be faster than
 | 
			
		||||
// polling, for when locking need only happen within this
 | 
			
		||||
// process.
 | 
			
		||||
type fileWaiter struct {
 | 
			
		||||
	filename string
 | 
			
		||||
	wg       *sync.WaitGroup
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Wait waits until the lock is released.
 | 
			
		||||
func (fw *fileWaiter) Wait() {
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	fw.wg.Wait()
 | 
			
		||||
	for time.Since(start) < 1*time.Hour {
 | 
			
		||||
		_, err := os.Stat(fw.filename)
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		time.Sleep(1 * time.Second)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var fileStorageNameLocks = make(map[string]*fileWaiter) // keyed by CA + name
 | 
			
		||||
var fileStorageNameLocksMu sync.Mutex
 | 
			
		||||
 | 
			
		||||
var _ Locker = &fileStorageLock{}
 | 
			
		||||
var _ Waiter = &fileWaiter{}
 | 
			
		||||
@ -16,20 +16,10 @@ package caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy/telemetry"
 | 
			
		||||
	"github.com/xenolf/lego/acme"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// configGroup is a type that keys configs by their hostname
 | 
			
		||||
@ -89,451 +79,6 @@ func (cg configGroup) GetConfigForClient(clientHello *tls.ClientHelloInfo) (*tls
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetCertificate gets a certificate to satisfy clientHello. In getting
 | 
			
		||||
// the certificate, it abides the rules and settings defined in the
 | 
			
		||||
// Config that matches clientHello.ServerName. It first checks the in-
 | 
			
		||||
// memory cache, then, if the config enables "OnDemand", it accesses
 | 
			
		||||
// disk, then accesses the network if it must obtain a new certificate
 | 
			
		||||
// via ACME.
 | 
			
		||||
//
 | 
			
		||||
// This method is safe for use as a tls.Config.GetCertificate callback.
 | 
			
		||||
func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
 | 
			
		||||
	if ClientHelloTelemetry && len(clientHello.SupportedVersions) > 0 {
 | 
			
		||||
		// If no other plugin (such as the HTTP server type) is implementing ClientHello telemetry, we do it.
 | 
			
		||||
		// NOTE: The values in the Go standard lib's ClientHelloInfo aren't guaranteed to be in order.
 | 
			
		||||
		info := ClientHelloInfo{
 | 
			
		||||
			Version:                   clientHello.SupportedVersions[0], // report the highest
 | 
			
		||||
			CipherSuites:              clientHello.CipherSuites,
 | 
			
		||||
			ExtensionsUnknown:         true, // no extension info... :(
 | 
			
		||||
			CompressionMethodsUnknown: true, // no compression methods... :(
 | 
			
		||||
			Curves:                    clientHello.SupportedCurves,
 | 
			
		||||
			Points:                    clientHello.SupportedPoints,
 | 
			
		||||
			// We also have, but do not yet use: SignatureSchemes, ServerName, and SupportedProtos (ALPN)
 | 
			
		||||
			// because the standard lib parses some extensions, but our MITM detector generally doesn't.
 | 
			
		||||
		}
 | 
			
		||||
		go telemetry.SetNested("tls_client_hello", info.Key(), info)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// special case: serve up the certificate for a TLS-ALPN ACME challenge
 | 
			
		||||
	// (https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05)
 | 
			
		||||
	for _, proto := range clientHello.SupportedProtos {
 | 
			
		||||
		if proto == acme.ACMETLS1Protocol {
 | 
			
		||||
			cfg.certCache.RLock()
 | 
			
		||||
			challengeCert, ok := cfg.certCache.cache[tlsALPNCertKeyName(clientHello.ServerName)]
 | 
			
		||||
			cfg.certCache.RUnlock()
 | 
			
		||||
			if !ok {
 | 
			
		||||
				// see if this challenge was started in a cluster; try distributed challenge solver
 | 
			
		||||
				// (note that the tls.Config's ALPN settings must include the ACME TLS-ALPN challenge
 | 
			
		||||
				// protocol string, otherwise a valid certificate will not solve the challenge; we
 | 
			
		||||
				// should already have taken care of that when we made the tls.Config)
 | 
			
		||||
				challengeCert, ok, err := cfg.tryDistributedChallengeSolver(clientHello)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Printf("[ERROR][%s] TLS-ALPN: %v", clientHello.ServerName, err)
 | 
			
		||||
				}
 | 
			
		||||
				if ok {
 | 
			
		||||
					return &challengeCert.Certificate, nil
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return nil, fmt.Errorf("no certificate to complete TLS-ALPN challenge for SNI name: %s", clientHello.ServerName)
 | 
			
		||||
			}
 | 
			
		||||
			return &challengeCert.Certificate, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get the certificate and serve it up
 | 
			
		||||
	cert, err := cfg.getCertDuringHandshake(strings.ToLower(clientHello.ServerName), true, true)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		go telemetry.Increment("tls_handshake_count") // TODO: This is a "best guess" for now, we need something listener-level
 | 
			
		||||
	}
 | 
			
		||||
	return &cert.Certificate, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getCertificate gets a certificate that matches name (a server name)
 | 
			
		||||
// from the in-memory cache, according to the lookup table associated with
 | 
			
		||||
// cfg. The lookup then points to a certificate in the Instance certificate
 | 
			
		||||
// cache.
 | 
			
		||||
//
 | 
			
		||||
// If there is no exact match for name, it will be checked against names of
 | 
			
		||||
// the form '*.example.com' (wildcard certificates) according to RFC 6125.
 | 
			
		||||
// If a match is found, matched will be true. If no matches are found, matched
 | 
			
		||||
// will be false and a "default" certificate will be returned with defaulted
 | 
			
		||||
// set to true. If defaulted is false, then no certificates were available.
 | 
			
		||||
//
 | 
			
		||||
// The logic in this function is adapted from the Go standard library,
 | 
			
		||||
// which is by the Go Authors.
 | 
			
		||||
//
 | 
			
		||||
// This function is safe for concurrent use.
 | 
			
		||||
func (cfg *Config) getCertificate(name string) (cert Certificate, matched, defaulted bool) {
 | 
			
		||||
	var certKey string
 | 
			
		||||
	var ok bool
 | 
			
		||||
 | 
			
		||||
	// Not going to trim trailing dots here since RFC 3546 says,
 | 
			
		||||
	// "The hostname is represented ... without a trailing dot."
 | 
			
		||||
	// Just normalize to lowercase.
 | 
			
		||||
	name = strings.ToLower(name)
 | 
			
		||||
 | 
			
		||||
	cfg.certCache.RLock()
 | 
			
		||||
	defer cfg.certCache.RUnlock()
 | 
			
		||||
 | 
			
		||||
	// exact match? great, let's use it
 | 
			
		||||
	if certKey, ok = cfg.Certificates[name]; ok {
 | 
			
		||||
		cert = cfg.certCache.cache[certKey]
 | 
			
		||||
		matched = true
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// try replacing labels in the name with wildcards until we get a match
 | 
			
		||||
	labels := strings.Split(name, ".")
 | 
			
		||||
	for i := range labels {
 | 
			
		||||
		labels[i] = "*"
 | 
			
		||||
		candidate := strings.Join(labels, ".")
 | 
			
		||||
		if certKey, ok = cfg.Certificates[candidate]; ok {
 | 
			
		||||
			cert = cfg.certCache.cache[certKey]
 | 
			
		||||
			matched = true
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check the certCache directly to see if the SNI name is
 | 
			
		||||
	// already the key of the certificate it wants; this implies
 | 
			
		||||
	// that the SNI can contain the hash of a specific cert
 | 
			
		||||
	// (chain) it wants and we will still be able to serveit up
 | 
			
		||||
	// (this behavior, by the way, could be controversial as to
 | 
			
		||||
	// whether it complies with RFC 6066 about SNI, but I think
 | 
			
		||||
	// it does, soooo...)
 | 
			
		||||
	if directCert, ok := cfg.certCache.cache[name]; ok {
 | 
			
		||||
		cert = directCert
 | 
			
		||||
		matched = true
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// if nothing matches, use a "default" certificate
 | 
			
		||||
	// (See issues 2035 and 1303; any change to this behavior
 | 
			
		||||
	// must account for hosts defined like ":443" or
 | 
			
		||||
	// "0.0.0.0:443" where the hostname is empty or a catch-all
 | 
			
		||||
	// IP or something.)
 | 
			
		||||
	if certKey, ok := cfg.Certificates[""]; ok {
 | 
			
		||||
		cert = cfg.certCache.cache[certKey]
 | 
			
		||||
		defaulted = true
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getCertDuringHandshake will get a certificate for name. It first tries
 | 
			
		||||
// the in-memory cache. If no certificate for name is in the cache, the
 | 
			
		||||
// config most closely corresponding to name will be loaded. If that config
 | 
			
		||||
// allows it (OnDemand==true) and if loadIfNecessary == true, it goes to disk
 | 
			
		||||
// to load it into the cache and serve it. If it's not on disk and if
 | 
			
		||||
// obtainIfNecessary == true, the certificate will be obtained from the CA,
 | 
			
		||||
// cached, and served. If obtainIfNecessary is true, then loadIfNecessary
 | 
			
		||||
// must also be set to true. An error will be returned if and only if no
 | 
			
		||||
// certificate is available.
 | 
			
		||||
//
 | 
			
		||||
// This function is safe for concurrent use.
 | 
			
		||||
func (cfg *Config) getCertDuringHandshake(name string, loadIfNecessary, obtainIfNecessary bool) (Certificate, error) {
 | 
			
		||||
	// First check our in-memory cache to see if we've already loaded it
 | 
			
		||||
	cert, matched, defaulted := cfg.getCertificate(name)
 | 
			
		||||
	if matched {
 | 
			
		||||
		return cert, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If OnDemand is enabled, then we might be able to load or
 | 
			
		||||
	// obtain a needed certificate
 | 
			
		||||
	if cfg.OnDemand && loadIfNecessary {
 | 
			
		||||
		// Then check to see if we have one on disk
 | 
			
		||||
		loadedCert, err := cfg.CacheManagedCertificate(name)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			loadedCert, err = cfg.handshakeMaintenance(name, loadedCert)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Printf("[ERROR] Maintaining newly-loaded certificate for %s: %v", name, err)
 | 
			
		||||
			}
 | 
			
		||||
			return loadedCert, nil
 | 
			
		||||
		}
 | 
			
		||||
		if obtainIfNecessary {
 | 
			
		||||
			// By this point, we need to ask the CA for a certificate
 | 
			
		||||
 | 
			
		||||
			name = strings.ToLower(name)
 | 
			
		||||
 | 
			
		||||
			// Make sure the certificate should be obtained based on config
 | 
			
		||||
			err := cfg.checkIfCertShouldBeObtained(name)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return Certificate{}, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Name has to qualify for a certificate
 | 
			
		||||
			if !HostQualifies(name) {
 | 
			
		||||
				return cert, errors.New("hostname '" + name + "' does not qualify for certificate")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Obtain certificate from the CA
 | 
			
		||||
			return cfg.obtainOnDemandCertificate(name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Fall back to the default certificate if there is one
 | 
			
		||||
	if defaulted {
 | 
			
		||||
		return cert, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return Certificate{}, fmt.Errorf("no certificate available for %s", name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkIfCertShouldBeObtained checks to see if an on-demand tls certificate
 | 
			
		||||
// should be obtained for a given domain based upon the config settings.  If
 | 
			
		||||
// a non-nil error is returned, do not issue a new certificate for name.
 | 
			
		||||
func (cfg *Config) checkIfCertShouldBeObtained(name string) error {
 | 
			
		||||
	// If the "ask" URL is defined in the config, use to determine if a
 | 
			
		||||
	// cert should obtained
 | 
			
		||||
	if cfg.OnDemandState.AskURL != nil {
 | 
			
		||||
		return cfg.checkURLForObtainingNewCerts(name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Otherwise use the limit defined by the "max_certs" setting
 | 
			
		||||
	return cfg.checkLimitsForObtainingNewCerts(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cfg *Config) checkURLForObtainingNewCerts(name string) error {
 | 
			
		||||
	client := http.Client{
 | 
			
		||||
		Timeout: 10 * time.Second,
 | 
			
		||||
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
 | 
			
		||||
			return errors.New("following http redirects is not allowed")
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Copy the URL from the config in order to modify it for this request
 | 
			
		||||
	askURL := new(url.URL)
 | 
			
		||||
	*askURL = *cfg.OnDemandState.AskURL
 | 
			
		||||
 | 
			
		||||
	query := askURL.Query()
 | 
			
		||||
	query.Set("domain", name)
 | 
			
		||||
	askURL.RawQuery = query.Encode()
 | 
			
		||||
 | 
			
		||||
	resp, err := client.Get(askURL.String())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error checking %v to deterine if certificate for hostname '%s' should be allowed: %v", cfg.OnDemandState.AskURL, name, err)
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	if resp.StatusCode < 200 || resp.StatusCode > 299 {
 | 
			
		||||
		return fmt.Errorf("certificate for hostname '%s' not allowed, non-2xx status code %d returned from %v", name, resp.StatusCode, cfg.OnDemandState.AskURL)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkLimitsForObtainingNewCerts checks to see if name can be issued right
 | 
			
		||||
// now according the maximum count defined in the configuration. If a non-nil
 | 
			
		||||
// error is returned, do not issue a new certificate for name.
 | 
			
		||||
func (cfg *Config) checkLimitsForObtainingNewCerts(name string) error {
 | 
			
		||||
	// User can set hard limit for number of certs for the process to issue
 | 
			
		||||
	if cfg.OnDemandState.MaxObtain > 0 &&
 | 
			
		||||
		atomic.LoadInt32(&cfg.OnDemandState.ObtainedCount) >= cfg.OnDemandState.MaxObtain {
 | 
			
		||||
		return fmt.Errorf("%s: maximum certificates issued (%d)", name, cfg.OnDemandState.MaxObtain)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make sure name hasn't failed a challenge recently
 | 
			
		||||
	failedIssuanceMu.RLock()
 | 
			
		||||
	when, ok := failedIssuance[name]
 | 
			
		||||
	failedIssuanceMu.RUnlock()
 | 
			
		||||
	if ok {
 | 
			
		||||
		return fmt.Errorf("%s: throttled; refusing to issue cert since last attempt on %s failed", name, when.String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make sure, if we've issued a few certificates already, that we haven't
 | 
			
		||||
	// issued any recently
 | 
			
		||||
	lastIssueTimeMu.Lock()
 | 
			
		||||
	since := time.Since(lastIssueTime)
 | 
			
		||||
	lastIssueTimeMu.Unlock()
 | 
			
		||||
	if atomic.LoadInt32(&cfg.OnDemandState.ObtainedCount) >= 10 && since < 10*time.Minute {
 | 
			
		||||
		return fmt.Errorf("%s: throttled; last certificate was obtained %v ago", name, since)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Good to go 👍
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// obtainOnDemandCertificate obtains a certificate for name for the given
 | 
			
		||||
// name. If another goroutine has already started obtaining a cert for
 | 
			
		||||
// name, it will wait and use what the other goroutine obtained.
 | 
			
		||||
//
 | 
			
		||||
// This function is safe for use by multiple concurrent goroutines.
 | 
			
		||||
func (cfg *Config) obtainOnDemandCertificate(name string) (Certificate, error) {
 | 
			
		||||
	// We must protect this process from happening concurrently, so synchronize.
 | 
			
		||||
	obtainCertWaitChansMu.Lock()
 | 
			
		||||
	wait, ok := obtainCertWaitChans[name]
 | 
			
		||||
	if ok {
 | 
			
		||||
		// lucky us -- another goroutine is already obtaining the certificate.
 | 
			
		||||
		// wait for it to finish obtaining the cert and then we'll use it.
 | 
			
		||||
		obtainCertWaitChansMu.Unlock()
 | 
			
		||||
		<-wait
 | 
			
		||||
		return cfg.getCertDuringHandshake(name, true, false)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// looks like it's up to us to do all the work and obtain the cert.
 | 
			
		||||
	// make a chan others can wait on if needed
 | 
			
		||||
	wait = make(chan struct{})
 | 
			
		||||
	obtainCertWaitChans[name] = wait
 | 
			
		||||
	obtainCertWaitChansMu.Unlock()
 | 
			
		||||
 | 
			
		||||
	// obtain the certificate
 | 
			
		||||
	log.Printf("[INFO] Obtaining new certificate for %s", name)
 | 
			
		||||
	err := cfg.ObtainCert(name, false)
 | 
			
		||||
 | 
			
		||||
	// immediately unblock anyone waiting for it; doing this in
 | 
			
		||||
	// a defer would risk deadlock because of the recursive call
 | 
			
		||||
	// to getCertDuringHandshake below when we return!
 | 
			
		||||
	obtainCertWaitChansMu.Lock()
 | 
			
		||||
	close(wait)
 | 
			
		||||
	delete(obtainCertWaitChans, name)
 | 
			
		||||
	obtainCertWaitChansMu.Unlock()
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// Failed to solve challenge, so don't allow another on-demand
 | 
			
		||||
		// issue for this name to be attempted for a little while.
 | 
			
		||||
		failedIssuanceMu.Lock()
 | 
			
		||||
		failedIssuance[name] = time.Now()
 | 
			
		||||
		go func(name string) {
 | 
			
		||||
			time.Sleep(5 * time.Minute)
 | 
			
		||||
			failedIssuanceMu.Lock()
 | 
			
		||||
			delete(failedIssuance, name)
 | 
			
		||||
			failedIssuanceMu.Unlock()
 | 
			
		||||
		}(name)
 | 
			
		||||
		failedIssuanceMu.Unlock()
 | 
			
		||||
		return Certificate{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Success - update counters and stuff
 | 
			
		||||
	atomic.AddInt32(&cfg.OnDemandState.ObtainedCount, 1)
 | 
			
		||||
	lastIssueTimeMu.Lock()
 | 
			
		||||
	lastIssueTime = time.Now()
 | 
			
		||||
	lastIssueTimeMu.Unlock()
 | 
			
		||||
 | 
			
		||||
	// certificate is already on disk; now just start over to load it and serve it
 | 
			
		||||
	return cfg.getCertDuringHandshake(name, true, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handshakeMaintenance performs a check on cert for expiration and OCSP
 | 
			
		||||
// validity.
 | 
			
		||||
//
 | 
			
		||||
// This function is safe for use by multiple concurrent goroutines.
 | 
			
		||||
func (cfg *Config) handshakeMaintenance(name string, cert Certificate) (Certificate, error) {
 | 
			
		||||
	// Check cert expiration
 | 
			
		||||
	timeLeft := cert.NotAfter.Sub(time.Now().UTC())
 | 
			
		||||
	if timeLeft < RenewDurationBefore {
 | 
			
		||||
		log.Printf("[INFO] Certificate for %v expires in %v; attempting renewal", cert.Names, timeLeft)
 | 
			
		||||
		return cfg.renewDynamicCertificate(name, cert)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check OCSP staple validity
 | 
			
		||||
	if cert.OCSP != nil {
 | 
			
		||||
		refreshTime := cert.OCSP.ThisUpdate.Add(cert.OCSP.NextUpdate.Sub(cert.OCSP.ThisUpdate) / 2)
 | 
			
		||||
		if time.Now().After(refreshTime) {
 | 
			
		||||
			err := stapleOCSP(&cert, nil)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				// An error with OCSP stapling is not the end of the world, and in fact, is
 | 
			
		||||
				// quite common considering not all certs have issuer URLs that support it.
 | 
			
		||||
				log.Printf("[ERROR] Getting OCSP for %s: %v", name, err)
 | 
			
		||||
			}
 | 
			
		||||
			cfg.certCache.Lock()
 | 
			
		||||
			cfg.certCache.cache[cert.Hash] = cert
 | 
			
		||||
			cfg.certCache.Unlock()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cert, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// renewDynamicCertificate renews the certificate for name using cfg. It returns the
 | 
			
		||||
// certificate to use and an error, if any. name should already be lower-cased before
 | 
			
		||||
// calling this function. name is the name obtained directly from the handshake's
 | 
			
		||||
// ClientHello.
 | 
			
		||||
//
 | 
			
		||||
// This function is safe for use by multiple concurrent goroutines.
 | 
			
		||||
func (cfg *Config) renewDynamicCertificate(name string, currentCert Certificate) (Certificate, error) {
 | 
			
		||||
	obtainCertWaitChansMu.Lock()
 | 
			
		||||
	wait, ok := obtainCertWaitChans[name]
 | 
			
		||||
	if ok {
 | 
			
		||||
		// lucky us -- another goroutine is already renewing the certificate.
 | 
			
		||||
		// wait for it to finish, then we'll use the new one.
 | 
			
		||||
		obtainCertWaitChansMu.Unlock()
 | 
			
		||||
		<-wait
 | 
			
		||||
		return cfg.getCertDuringHandshake(name, true, false)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// looks like it's up to us to do all the work and renew the cert
 | 
			
		||||
	wait = make(chan struct{})
 | 
			
		||||
	obtainCertWaitChans[name] = wait
 | 
			
		||||
	obtainCertWaitChansMu.Unlock()
 | 
			
		||||
 | 
			
		||||
	// renew and reload the certificate
 | 
			
		||||
	log.Printf("[INFO] Renewing certificate for %s", name)
 | 
			
		||||
	err := cfg.RenewCert(name, false)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// even though the recursive nature of the dynamic cert loading
 | 
			
		||||
		// would just call this function anyway, we do it here to
 | 
			
		||||
		// make the replacement as atomic as possible.
 | 
			
		||||
		newCert, err := currentCert.configs[0].CacheManagedCertificate(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Printf("[ERROR] loading renewed certificate for %s: %v", name, err)
 | 
			
		||||
		} else {
 | 
			
		||||
			// replace the old certificate with the new one
 | 
			
		||||
			err = cfg.certCache.replaceCertificate(currentCert, newCert)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Printf("[ERROR] Replacing certificate for %s: %v", name, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// immediately unblock anyone waiting for it; doing this in
 | 
			
		||||
	// a defer would risk deadlock because of the recursive call
 | 
			
		||||
	// to getCertDuringHandshake below when we return!
 | 
			
		||||
	obtainCertWaitChansMu.Lock()
 | 
			
		||||
	close(wait)
 | 
			
		||||
	delete(obtainCertWaitChans, name)
 | 
			
		||||
	obtainCertWaitChansMu.Unlock()
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Certificate{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cfg.getCertDuringHandshake(name, true, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// tryDistributedChallengeSolver is to be called when the clientHello pertains to
 | 
			
		||||
// a TLS-ALPN challenge and a certificate is required to solve it. This method
 | 
			
		||||
// checks the distributed store of challenge info files and, if a matching ServerName
 | 
			
		||||
// is present, it makes a certificate to solve this challenge and returns it.
 | 
			
		||||
// A boolean true is returned if a valid certificate is returned.
 | 
			
		||||
func (cfg *Config) tryDistributedChallengeSolver(clientHello *tls.ClientHelloInfo) (Certificate, bool, error) {
 | 
			
		||||
	filePath := distributedSolver{}.challengeTokensPath(clientHello.ServerName)
 | 
			
		||||
	f, err := os.Open(filePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return Certificate{}, false, nil
 | 
			
		||||
		}
 | 
			
		||||
		return Certificate{}, false, fmt.Errorf("opening distributed challenge token file %s: %v", filePath, err)
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
 | 
			
		||||
	var chalInfo challengeInfo
 | 
			
		||||
	err = json.NewDecoder(f).Decode(&chalInfo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Certificate{}, false, fmt.Errorf("decoding challenge token file %s (corrupted?): %v", filePath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cert, err := acme.TLSALPNChallengeCert(chalInfo.Domain, chalInfo.KeyAuth)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Certificate{}, false, fmt.Errorf("making TLS-ALPN challenge certificate: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if cert == nil {
 | 
			
		||||
		return Certificate{}, false, fmt.Errorf("got nil TLS-ALPN challenge certificate but no error")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return Certificate{Certificate: *cert}, true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ClientHelloInfo is our own version of the standard lib's
 | 
			
		||||
// tls.ClientHelloInfo. As of May 2018, any fields populated
 | 
			
		||||
// by the Go standard library are not guaranteed to have their
 | 
			
		||||
@ -570,21 +115,6 @@ func (info ClientHelloInfo) Key() string {
 | 
			
		||||
		compressionMethods, info.Curves, info.Points)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// obtainCertWaitChans is used to coordinate obtaining certs for each hostname.
 | 
			
		||||
var obtainCertWaitChans = make(map[string]chan struct{})
 | 
			
		||||
var obtainCertWaitChansMu sync.Mutex
 | 
			
		||||
 | 
			
		||||
// failedIssuance is a set of names that we recently failed to get a
 | 
			
		||||
// certificate for from the ACME CA. They are removed after some time.
 | 
			
		||||
// When a name is in this map, do not issue a certificate for it on-demand.
 | 
			
		||||
var failedIssuance = make(map[string]time.Time)
 | 
			
		||||
var failedIssuanceMu sync.RWMutex
 | 
			
		||||
 | 
			
		||||
// lastIssueTime records when we last obtained a certificate successfully.
 | 
			
		||||
// If this value is recent, do not make any on-demand certificate requests.
 | 
			
		||||
var lastIssueTime time.Time
 | 
			
		||||
var lastIssueTimeMu sync.Mutex
 | 
			
		||||
 | 
			
		||||
// ClientHelloTelemetry determines whether to report
 | 
			
		||||
// TLS ClientHellos to telemetry. Disable if doing
 | 
			
		||||
// it from a different package.
 | 
			
		||||
 | 
			
		||||
@ -1,76 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetCertificate(t *testing.T) {
 | 
			
		||||
	certCache := &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
	cfg := &Config{Certificates: make(map[string]string), certCache: certCache}
 | 
			
		||||
 | 
			
		||||
	hello := &tls.ClientHelloInfo{ServerName: "example.com"}
 | 
			
		||||
	helloSub := &tls.ClientHelloInfo{ServerName: "sub.example.com"}
 | 
			
		||||
	helloNoSNI := &tls.ClientHelloInfo{}
 | 
			
		||||
	helloNoMatch := &tls.ClientHelloInfo{ServerName: "nomatch"} // TODO (see below)
 | 
			
		||||
 | 
			
		||||
	// When cache is empty
 | 
			
		||||
	if cert, err := cfg.GetCertificate(hello); err == nil {
 | 
			
		||||
		t.Errorf("GetCertificate should return error when cache is empty, got: %v", cert)
 | 
			
		||||
	}
 | 
			
		||||
	if cert, err := cfg.GetCertificate(helloNoSNI); err == nil {
 | 
			
		||||
		t.Errorf("GetCertificate should return error when cache is empty even if server name is blank, got: %v", cert)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// When cache has one certificate in it
 | 
			
		||||
	firstCert := Certificate{Names: []string{"example.com"}, Certificate: tls.Certificate{Leaf: &x509.Certificate{DNSNames: []string{"example.com"}}}}
 | 
			
		||||
	cfg.cacheCertificate(firstCert)
 | 
			
		||||
	if cert, err := cfg.GetCertificate(hello); err != nil {
 | 
			
		||||
		t.Errorf("Got an error but shouldn't have, when cert exists in cache: %v", err)
 | 
			
		||||
	} else if cert.Leaf.DNSNames[0] != "example.com" {
 | 
			
		||||
		t.Errorf("Got wrong certificate with exact match; expected 'example.com', got: %v", cert)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := cfg.GetCertificate(helloNoSNI); err != nil {
 | 
			
		||||
		t.Errorf("Got an error with no SNI but shouldn't have, when cert exists in cache: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// When retrieving wildcard certificate
 | 
			
		||||
	wildcardCert := Certificate{
 | 
			
		||||
		Names:       []string{"*.example.com"},
 | 
			
		||||
		Certificate: tls.Certificate{Leaf: &x509.Certificate{DNSNames: []string{"*.example.com"}}},
 | 
			
		||||
		Hash:        "(don't overwrite the first one)",
 | 
			
		||||
	}
 | 
			
		||||
	cfg.cacheCertificate(wildcardCert)
 | 
			
		||||
	if cert, err := cfg.GetCertificate(helloSub); err != nil {
 | 
			
		||||
		t.Errorf("Didn't get wildcard cert, got: cert=%v, err=%v ", cert, err)
 | 
			
		||||
	} else if cert.Leaf.DNSNames[0] != "*.example.com" {
 | 
			
		||||
		t.Errorf("Got wrong certificate, expected wildcard: %v", cert)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// When cache is NOT empty but there's no SNI
 | 
			
		||||
	if cert, err := cfg.GetCertificate(helloNoSNI); err != nil {
 | 
			
		||||
		t.Errorf("Expected random certificate with no error when no SNI, got err: %v", err)
 | 
			
		||||
	} else if cert == nil || len(cert.Leaf.DNSNames) == 0 {
 | 
			
		||||
		t.Errorf("Expected random cert with no matches, got: %v", cert)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// When no certificate matches, raise an alert
 | 
			
		||||
	if _, err := cfg.GetCertificate(helloNoMatch); err == nil {
 | 
			
		||||
		t.Errorf("Expected an error when no certificate matched the SNI, got: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -1,120 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httputil"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/xenolf/lego/acme"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const challengeBasePath = "/.well-known/acme-challenge"
 | 
			
		||||
 | 
			
		||||
// HTTPChallengeHandler proxies challenge requests to ACME client if the
 | 
			
		||||
// request path starts with challengeBasePath, if the HTTP challenge is not
 | 
			
		||||
// disabled, and if we are known to be obtaining a certificate for the name.
 | 
			
		||||
// It returns true if it handled the request and no more needs to be done;
 | 
			
		||||
// it returns false if this call was a no-op and the request still needs handling.
 | 
			
		||||
func HTTPChallengeHandler(w http.ResponseWriter, r *http.Request, listenHost string) bool {
 | 
			
		||||
	if !strings.HasPrefix(r.URL.Path, challengeBasePath) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if DisableHTTPChallenge {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// see if another instance started the HTTP challenge for this name
 | 
			
		||||
	if tryDistributedChallengeSolver(w, r) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// otherwise, if we aren't getting the name, then ignore this challenge
 | 
			
		||||
	if !namesObtaining.Has(r.Host) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scheme := "http"
 | 
			
		||||
	if r.TLS != nil {
 | 
			
		||||
		scheme = "https"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if listenHost == "" {
 | 
			
		||||
		listenHost = "localhost"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// always proxy to the DefaultHTTPAlternatePort because obviously the
 | 
			
		||||
	// ACME challenge request already got into one of our HTTP handlers, so
 | 
			
		||||
	// it means we must have started a HTTP listener on the alternate
 | 
			
		||||
	// port instead; which is only accessible via listenHost
 | 
			
		||||
	upstream, err := url.Parse(fmt.Sprintf("%s://%s:%s", scheme, listenHost, DefaultHTTPAlternatePort))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		w.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
		log.Printf("[ERROR] ACME proxy handler: %v", err)
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	proxy := httputil.NewSingleHostReverseProxy(upstream)
 | 
			
		||||
	proxy.Transport = &http.Transport{
 | 
			
		||||
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
 | 
			
		||||
	}
 | 
			
		||||
	proxy.ServeHTTP(w, r)
 | 
			
		||||
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// tryDistributedChallengeSolver checks to see if this challenge
 | 
			
		||||
// request was initiated by another instance that shares file
 | 
			
		||||
// storage, and attempts to complete the challenge for it. It
 | 
			
		||||
// returns true if the challenge was handled; false otherwise.
 | 
			
		||||
func tryDistributedChallengeSolver(w http.ResponseWriter, r *http.Request) bool {
 | 
			
		||||
	filePath := distributedSolver{}.challengeTokensPath(r.Host)
 | 
			
		||||
	f, err := os.Open(filePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			log.Printf("[ERROR][%s] Opening distributed challenge token file: %v", r.Host, err)
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
 | 
			
		||||
	var chalInfo challengeInfo
 | 
			
		||||
	err = json.NewDecoder(f).Decode(&chalInfo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Printf("[ERROR][%s] Decoding challenge token file %s (corrupted?): %v", r.Host, filePath, err)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// this part borrowed from xenolf/lego's built-in HTTP-01 challenge solver (March 2018)
 | 
			
		||||
	challengeReqPath := acme.HTTP01ChallengePath(chalInfo.Token)
 | 
			
		||||
	if r.URL.Path == challengeReqPath &&
 | 
			
		||||
		strings.HasPrefix(r.Host, chalInfo.Domain) &&
 | 
			
		||||
		r.Method == "GET" {
 | 
			
		||||
		w.Header().Add("Content-Type", "text/plain")
 | 
			
		||||
		w.Write([]byte(chalInfo.KeyAuth))
 | 
			
		||||
		r.Close = true
 | 
			
		||||
		log.Printf("[INFO][%s] Served key authentication (distributed)", chalInfo.Domain)
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
@ -1,84 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestHTTPChallengeHandlerNoOp(t *testing.T) {
 | 
			
		||||
	namesObtaining.Add([]string{"localhost"})
 | 
			
		||||
 | 
			
		||||
	// try base paths and host names that aren't
 | 
			
		||||
	// handled by this handler
 | 
			
		||||
	for _, url := range []string{
 | 
			
		||||
		"http://localhost/",
 | 
			
		||||
		"http://localhost/foo.html",
 | 
			
		||||
		"http://localhost/.git",
 | 
			
		||||
		"http://localhost/.well-known/",
 | 
			
		||||
		"http://localhost/.well-known/acme-challenging",
 | 
			
		||||
		"http://other/.well-known/acme-challenge/foo",
 | 
			
		||||
	} {
 | 
			
		||||
		req, err := http.NewRequest("GET", url, nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatalf("Could not craft request, got error: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		rw := httptest.NewRecorder()
 | 
			
		||||
		if HTTPChallengeHandler(rw, req, "") {
 | 
			
		||||
			t.Errorf("Got true with this URL, but shouldn't have: %s", url)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestHTTPChallengeHandlerSuccess(t *testing.T) {
 | 
			
		||||
	expectedPath := challengeBasePath + "/asdf"
 | 
			
		||||
 | 
			
		||||
	// Set up fake acme handler backend to make sure proxying succeeds
 | 
			
		||||
	var proxySuccess bool
 | 
			
		||||
	ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		proxySuccess = true
 | 
			
		||||
		if r.URL.Path != expectedPath {
 | 
			
		||||
			t.Errorf("Expected path '%s' but got '%s' instead", expectedPath, r.URL.Path)
 | 
			
		||||
		}
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
	// Custom listener that uses the port we expect
 | 
			
		||||
	ln, err := net.Listen("tcp", "127.0.0.1:"+DefaultHTTPAlternatePort)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unable to start test server listener: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	ts.Listener = ln
 | 
			
		||||
 | 
			
		||||
	// Tell this package that we are handling a challenge for 127.0.0.1
 | 
			
		||||
	namesObtaining.Add([]string{"127.0.0.1"})
 | 
			
		||||
 | 
			
		||||
	// Start our engines and run the test
 | 
			
		||||
	ts.Start()
 | 
			
		||||
	defer ts.Close()
 | 
			
		||||
	req, err := http.NewRequest("GET", "http://127.0.0.1:"+DefaultHTTPAlternatePort+expectedPath, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Could not craft request, got error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	rw := httptest.NewRecorder()
 | 
			
		||||
 | 
			
		||||
	HTTPChallengeHandler(rw, req, "")
 | 
			
		||||
 | 
			
		||||
	if !proxySuccess {
 | 
			
		||||
		t.Fatal("Expected request to be proxied, but it wasn't")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -1,365 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/crypto/ocsp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	// maintain assets while this package is imported, which is
 | 
			
		||||
	// always. we don't ever stop it, since we need it running.
 | 
			
		||||
	go maintainAssets(make(chan struct{}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// RenewInterval is how often to check certificates for renewal.
 | 
			
		||||
	RenewInterval = 12 * time.Hour
 | 
			
		||||
 | 
			
		||||
	// RenewDurationBefore is how long before expiration to renew certificates.
 | 
			
		||||
	RenewDurationBefore = (24 * time.Hour) * 30
 | 
			
		||||
 | 
			
		||||
	// RenewDurationBeforeAtStartup is how long before expiration to require
 | 
			
		||||
	// a renewed certificate when the process is first starting up (see #1680).
 | 
			
		||||
	// A wider window between RenewDurationBefore and this value will allow
 | 
			
		||||
	// Caddy to start under duress but hopefully this duration will give it
 | 
			
		||||
	// enough time for the blockage to be relieved.
 | 
			
		||||
	RenewDurationBeforeAtStartup = (24 * time.Hour) * 7
 | 
			
		||||
 | 
			
		||||
	// OCSPInterval is how often to check if OCSP stapling needs updating.
 | 
			
		||||
	OCSPInterval = 1 * time.Hour
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// maintainAssets is a permanently-blocking function
 | 
			
		||||
// that loops indefinitely and, on a regular schedule, checks
 | 
			
		||||
// certificates for expiration and initiates a renewal of certs
 | 
			
		||||
// that are expiring soon. It also updates OCSP stapling and
 | 
			
		||||
// performs other maintenance of assets. It should only be
 | 
			
		||||
// called once per process.
 | 
			
		||||
//
 | 
			
		||||
// You must pass in the channel which you'll close when
 | 
			
		||||
// maintenance should stop, to allow this goroutine to clean up
 | 
			
		||||
// after itself and unblock. (Not that you HAVE to stop it...)
 | 
			
		||||
func maintainAssets(stopChan chan struct{}) {
 | 
			
		||||
	renewalTicker := time.NewTicker(RenewInterval)
 | 
			
		||||
	ocspTicker := time.NewTicker(OCSPInterval)
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-renewalTicker.C:
 | 
			
		||||
			log.Println("[INFO] Scanning for expiring certificates")
 | 
			
		||||
			RenewManagedCertificates(false)
 | 
			
		||||
			log.Println("[INFO] Done checking certificates")
 | 
			
		||||
		case <-ocspTicker.C:
 | 
			
		||||
			log.Println("[INFO] Scanning for stale OCSP staples")
 | 
			
		||||
			UpdateOCSPStaples()
 | 
			
		||||
			DeleteOldStapleFiles()
 | 
			
		||||
			log.Println("[INFO] Done checking OCSP staples")
 | 
			
		||||
		case <-stopChan:
 | 
			
		||||
			renewalTicker.Stop()
 | 
			
		||||
			ocspTicker.Stop()
 | 
			
		||||
			log.Println("[INFO] Stopped background maintenance routine")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RenewManagedCertificates renews managed certificates,
 | 
			
		||||
// including ones loaded on-demand.
 | 
			
		||||
func RenewManagedCertificates(allowPrompts bool) (err error) {
 | 
			
		||||
	for _, inst := range caddy.Instances() {
 | 
			
		||||
		inst.StorageMu.RLock()
 | 
			
		||||
		certCache, ok := inst.Storage[CertCacheInstStorageKey].(*certificateCache)
 | 
			
		||||
		inst.StorageMu.RUnlock()
 | 
			
		||||
		if !ok || certCache == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// we use the queues for a very important reason: to do any and all
 | 
			
		||||
		// operations that could require an exclusive write lock outside
 | 
			
		||||
		// of the read lock! otherwise we get a deadlock, yikes. in other
 | 
			
		||||
		// words, our first iteration through the certificate cache does NOT
 | 
			
		||||
		// perform any operations--only queues them--so that more fine-grained
 | 
			
		||||
		// write locks may be obtained during the actual operations.
 | 
			
		||||
		var renewQueue, reloadQueue, deleteQueue []Certificate
 | 
			
		||||
 | 
			
		||||
		certCache.RLock()
 | 
			
		||||
		for certKey, cert := range certCache.cache {
 | 
			
		||||
			if len(cert.configs) == 0 {
 | 
			
		||||
				// this is bad if this happens, probably a programmer error (oops)
 | 
			
		||||
				log.Printf("[ERROR] No associated TLS config for certificate with names %v; unable to manage", cert.Names)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if !cert.configs[0].Managed || cert.configs[0].SelfSigned {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// the list of names on this cert should never be empty... programmer error?
 | 
			
		||||
			if cert.Names == nil || len(cert.Names) == 0 {
 | 
			
		||||
				log.Printf("[WARNING] Certificate keyed by '%s' has no names: %v - removing from cache", certKey, cert.Names)
 | 
			
		||||
				deleteQueue = append(deleteQueue, cert)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// if time is up or expires soon, we need to try to renew it
 | 
			
		||||
			timeLeft := cert.NotAfter.Sub(time.Now().UTC())
 | 
			
		||||
			if timeLeft < RenewDurationBefore {
 | 
			
		||||
				// see if the certificate in storage has already been renewed, possibly by another
 | 
			
		||||
				// instance of Caddy that didn't coordinate with this one; if so, just load it (this
 | 
			
		||||
				// might happen if another instance already renewed it - kinda sloppy but checking disk
 | 
			
		||||
				// first is a simple way to possibly drastically reduce rate limit problems)
 | 
			
		||||
				storedCertExpiring, err := managedCertInStorageExpiresSoon(cert)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					// hmm, weird, but not a big deal, maybe it was deleted or something
 | 
			
		||||
					log.Printf("[NOTICE] Error while checking if certificate for %v in storage is also expiring soon: %v",
 | 
			
		||||
						cert.Names, err)
 | 
			
		||||
				} else if !storedCertExpiring {
 | 
			
		||||
					// if the certificate is NOT expiring soon and there was no error, then we
 | 
			
		||||
					// are good to just reload the certificate from storage instead of repeating
 | 
			
		||||
					// a likely-unnecessary renewal procedure
 | 
			
		||||
					reloadQueue = append(reloadQueue, cert)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// the certificate in storage has not been renewed yet, so we will do it
 | 
			
		||||
				// NOTE 1: This is not correct 100% of the time, if multiple Caddy instances
 | 
			
		||||
				// happen to run their maintenance checks at approximately the same times;
 | 
			
		||||
				// both might start renewal at about the same time and do two renewals and one
 | 
			
		||||
				// will overwrite the other. Hence TLS storage plugins. This is sort of a TODO.
 | 
			
		||||
				// NOTE 2: It is super-important to note that the TLS-ALPN challenge requires
 | 
			
		||||
				// a write lock on the cache in order to complete its challenge, so it is extra
 | 
			
		||||
				// vital that this renew operation does not happen inside our read lock!
 | 
			
		||||
				renewQueue = append(renewQueue, cert)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		certCache.RUnlock()
 | 
			
		||||
 | 
			
		||||
		// Reload certificates that merely need to be updated in memory
 | 
			
		||||
		for _, oldCert := range reloadQueue {
 | 
			
		||||
			timeLeft := oldCert.NotAfter.Sub(time.Now().UTC())
 | 
			
		||||
			log.Printf("[INFO] Certificate for %v expires in %v, but is already renewed in storage; reloading stored certificate",
 | 
			
		||||
				oldCert.Names, timeLeft)
 | 
			
		||||
 | 
			
		||||
			err = certCache.reloadManagedCertificate(oldCert)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if allowPrompts {
 | 
			
		||||
					return err // operator is present, so report error immediately
 | 
			
		||||
				}
 | 
			
		||||
				log.Printf("[ERROR] Loading renewed certificate: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Renewal queue
 | 
			
		||||
		for _, oldCert := range renewQueue {
 | 
			
		||||
			timeLeft := oldCert.NotAfter.Sub(time.Now().UTC())
 | 
			
		||||
			log.Printf("[INFO] Certificate for %v expires in %v; attempting renewal", oldCert.Names, timeLeft)
 | 
			
		||||
 | 
			
		||||
			// Get the name which we should use to renew this certificate;
 | 
			
		||||
			// we only support managing certificates with one name per cert,
 | 
			
		||||
			// so this should be easy. We can't rely on cert.Config.Hostname
 | 
			
		||||
			// because it may be a wildcard value from the Caddyfile (e.g.
 | 
			
		||||
			// *.something.com) which, as of Jan. 2017, is not supported by ACME.
 | 
			
		||||
			// TODO: ^ ^ ^ (wildcards)
 | 
			
		||||
			renewName := oldCert.Names[0]
 | 
			
		||||
 | 
			
		||||
			// perform renewal
 | 
			
		||||
			err := oldCert.configs[0].RenewCert(renewName, allowPrompts)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if allowPrompts {
 | 
			
		||||
					// Certificate renewal failed and the operator is present. See a discussion
 | 
			
		||||
					// about this in issue 642. For a while, we only stopped if the certificate
 | 
			
		||||
					// was expired, but in reality, there is no difference between reporting
 | 
			
		||||
					// it now versus later, except that there's somebody present to deal with
 | 
			
		||||
					// it right now. Follow-up: See issue 1680. Only fail in this case if the
 | 
			
		||||
					// certificate is dangerously close to expiration.
 | 
			
		||||
					timeLeft := oldCert.NotAfter.Sub(time.Now().UTC())
 | 
			
		||||
					if timeLeft < RenewDurationBeforeAtStartup {
 | 
			
		||||
						return err
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				log.Printf("[ERROR] %v", err)
 | 
			
		||||
				if oldCert.configs[0].OnDemand {
 | 
			
		||||
					// loaded dynamically, remove dynamically
 | 
			
		||||
					deleteQueue = append(deleteQueue, oldCert)
 | 
			
		||||
				}
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// successful renewal, so update in-memory cache by loading
 | 
			
		||||
			// renewed certificate so it will be used with handshakes
 | 
			
		||||
			err = certCache.reloadManagedCertificate(oldCert)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if allowPrompts {
 | 
			
		||||
					return err // operator is present, so report error immediately
 | 
			
		||||
				}
 | 
			
		||||
				log.Printf("[ERROR] %v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Deletion queue
 | 
			
		||||
		for _, cert := range deleteQueue {
 | 
			
		||||
			certCache.Lock()
 | 
			
		||||
			// remove any pointers to this certificate from Configs
 | 
			
		||||
			for _, cfg := range cert.configs {
 | 
			
		||||
				for name, certKey := range cfg.Certificates {
 | 
			
		||||
					if certKey == cert.Hash {
 | 
			
		||||
						delete(cfg.Certificates, name)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			// then delete the certificate from the cache
 | 
			
		||||
			delete(certCache.cache, cert.Hash)
 | 
			
		||||
			certCache.Unlock()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateOCSPStaples updates the OCSP stapling in all
 | 
			
		||||
// eligible, cached certificates.
 | 
			
		||||
//
 | 
			
		||||
// OCSP maintenance strives to abide the relevant points on
 | 
			
		||||
// Ryan Sleevi's recommendations for good OCSP support:
 | 
			
		||||
// https://gist.github.com/sleevi/5efe9ef98961ecfb4da8
 | 
			
		||||
func UpdateOCSPStaples() {
 | 
			
		||||
	for _, inst := range caddy.Instances() {
 | 
			
		||||
		inst.StorageMu.RLock()
 | 
			
		||||
		certCache, ok := inst.Storage[CertCacheInstStorageKey].(*certificateCache)
 | 
			
		||||
		inst.StorageMu.RUnlock()
 | 
			
		||||
		if !ok || certCache == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Create a temporary place to store updates
 | 
			
		||||
		// until we release the potentially long-lived
 | 
			
		||||
		// read lock and use a short-lived write lock
 | 
			
		||||
		// on the certificate cache.
 | 
			
		||||
		type ocspUpdate struct {
 | 
			
		||||
			rawBytes []byte
 | 
			
		||||
			parsed   *ocsp.Response
 | 
			
		||||
		}
 | 
			
		||||
		updated := make(map[string]ocspUpdate)
 | 
			
		||||
 | 
			
		||||
		certCache.RLock()
 | 
			
		||||
		for certHash, cert := range certCache.cache {
 | 
			
		||||
			// no point in updating OCSP for expired certificates
 | 
			
		||||
			if time.Now().After(cert.NotAfter) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var lastNextUpdate time.Time
 | 
			
		||||
			if cert.OCSP != nil {
 | 
			
		||||
				lastNextUpdate = cert.OCSP.NextUpdate
 | 
			
		||||
				if freshOCSP(cert.OCSP) {
 | 
			
		||||
					continue // no need to update staple if ours is still fresh
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			err := stapleOCSP(&cert, nil)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if cert.OCSP != nil {
 | 
			
		||||
					// if there was no staple before, that's fine; otherwise we should log the error
 | 
			
		||||
					log.Printf("[ERROR] Checking OCSP: %v", err)
 | 
			
		||||
				}
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// By this point, we've obtained the latest OCSP response.
 | 
			
		||||
			// If there was no staple before, or if the response is updated, make
 | 
			
		||||
			// sure we apply the update to all names on the certificate.
 | 
			
		||||
			if cert.OCSP != nil && (lastNextUpdate.IsZero() || lastNextUpdate != cert.OCSP.NextUpdate) {
 | 
			
		||||
				log.Printf("[INFO] Advancing OCSP staple for %v from %s to %s",
 | 
			
		||||
					cert.Names, lastNextUpdate, cert.OCSP.NextUpdate)
 | 
			
		||||
				updated[certHash] = ocspUpdate{rawBytes: cert.Certificate.OCSPStaple, parsed: cert.OCSP}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		certCache.RUnlock()
 | 
			
		||||
 | 
			
		||||
		// These write locks should be brief since we have all the info we need now.
 | 
			
		||||
		for certKey, update := range updated {
 | 
			
		||||
			certCache.Lock()
 | 
			
		||||
			cert := certCache.cache[certKey]
 | 
			
		||||
			cert.OCSP = update.parsed
 | 
			
		||||
			cert.Certificate.OCSPStaple = update.rawBytes
 | 
			
		||||
			certCache.cache[certKey] = cert
 | 
			
		||||
			certCache.Unlock()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteOldStapleFiles deletes cached OCSP staples that have expired.
 | 
			
		||||
// TODO: Should we do this for certificates too?
 | 
			
		||||
func DeleteOldStapleFiles() {
 | 
			
		||||
	// TODO: Upgrade caddytls.Storage to support OCSP operations too
 | 
			
		||||
	files, err := ioutil.ReadDir(ocspFolder)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// maybe just hasn't been created yet; no big deal
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, file := range files {
 | 
			
		||||
		if file.IsDir() {
 | 
			
		||||
			// weird, what's a folder doing inside the OCSP cache?
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		stapleFile := filepath.Join(ocspFolder, file.Name())
 | 
			
		||||
		ocspBytes, err := ioutil.ReadFile(stapleFile)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		resp, err := ocsp.ParseResponse(ocspBytes, nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// contents are invalid; delete it
 | 
			
		||||
			err = os.Remove(stapleFile)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Printf("[ERROR] Purging corrupt staple file %s: %v", stapleFile, err)
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if time.Now().After(resp.NextUpdate) {
 | 
			
		||||
			// response has expired; delete it
 | 
			
		||||
			err = os.Remove(stapleFile)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Printf("[ERROR] Purging expired staple file %s: %v", stapleFile, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// freshOCSP returns true if resp is still fresh,
 | 
			
		||||
// meaning that it is not expedient to get an
 | 
			
		||||
// updated response from the OCSP server.
 | 
			
		||||
func freshOCSP(resp *ocsp.Response) bool {
 | 
			
		||||
	nextUpdate := resp.NextUpdate
 | 
			
		||||
	// If there is an OCSP responder certificate, and it expires before the
 | 
			
		||||
	// OCSP response, use its expiration date as the end of the OCSP
 | 
			
		||||
	// response's validity period.
 | 
			
		||||
	if resp.Certificate != nil && resp.Certificate.NotAfter.Before(nextUpdate) {
 | 
			
		||||
		nextUpdate = resp.Certificate.NotAfter
 | 
			
		||||
	}
 | 
			
		||||
	// start checking OCSP staple about halfway through validity period for good measure
 | 
			
		||||
	refreshTime := resp.ThisUpdate.Add(nextUpdate.Sub(resp.ThisUpdate) / 2)
 | 
			
		||||
	return time.Now().Before(refreshTime)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var ocspFolder = filepath.Join(caddy.AssetsPath(), "ocsp")
 | 
			
		||||
							
								
								
									
										106
									
								
								caddytls/selfsigned.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								caddytls/selfsigned.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,106 @@
 | 
			
		||||
package caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/ecdsa"
 | 
			
		||||
	"crypto/elliptic"
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"crypto/rsa"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"crypto/x509/pkix"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"math/big"
 | 
			
		||||
	"net"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/xenolf/lego/certcrypto"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// newSelfSignedCertificate returns a new self-signed certificate.
 | 
			
		||||
func newSelfSignedCertificate(ssconfig selfSignedConfig) (tls.Certificate, error) {
 | 
			
		||||
	// start by generating private key
 | 
			
		||||
	var privKey interface{}
 | 
			
		||||
	var err error
 | 
			
		||||
	switch ssconfig.KeyType {
 | 
			
		||||
	case "", certcrypto.EC256:
 | 
			
		||||
		privKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 | 
			
		||||
	case certcrypto.EC384:
 | 
			
		||||
		privKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
 | 
			
		||||
	case certcrypto.RSA2048:
 | 
			
		||||
		privKey, err = rsa.GenerateKey(rand.Reader, 2048)
 | 
			
		||||
	case certcrypto.RSA4096:
 | 
			
		||||
		privKey, err = rsa.GenerateKey(rand.Reader, 4096)
 | 
			
		||||
	case certcrypto.RSA8192:
 | 
			
		||||
		privKey, err = rsa.GenerateKey(rand.Reader, 8192)
 | 
			
		||||
	default:
 | 
			
		||||
		return tls.Certificate{}, fmt.Errorf("cannot generate private key; unknown key type %v", ssconfig.KeyType)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return tls.Certificate{}, fmt.Errorf("failed to generate private key: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// create certificate structure with proper values
 | 
			
		||||
	notBefore := time.Now()
 | 
			
		||||
	notAfter := ssconfig.Expire
 | 
			
		||||
	if notAfter.IsZero() || notAfter.Before(notBefore) {
 | 
			
		||||
		notAfter = notBefore.Add(24 * time.Hour * 7)
 | 
			
		||||
	}
 | 
			
		||||
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
 | 
			
		||||
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return tls.Certificate{}, fmt.Errorf("failed to generate serial number: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	cert := &x509.Certificate{
 | 
			
		||||
		SerialNumber: serialNumber,
 | 
			
		||||
		Subject:      pkix.Name{Organization: []string{"Caddy Self-Signed"}},
 | 
			
		||||
		NotBefore:    notBefore,
 | 
			
		||||
		NotAfter:     notAfter,
 | 
			
		||||
		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
 | 
			
		||||
		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
 | 
			
		||||
	}
 | 
			
		||||
	if len(ssconfig.SAN) == 0 {
 | 
			
		||||
		ssconfig.SAN = []string{""}
 | 
			
		||||
	}
 | 
			
		||||
	var names []string
 | 
			
		||||
	for _, san := range ssconfig.SAN {
 | 
			
		||||
		if ip := net.ParseIP(san); ip != nil {
 | 
			
		||||
			names = append(names, strings.ToLower(ip.String()))
 | 
			
		||||
			cert.IPAddresses = append(cert.IPAddresses, ip)
 | 
			
		||||
		} else {
 | 
			
		||||
			names = append(names, strings.ToLower(san))
 | 
			
		||||
			cert.DNSNames = append(cert.DNSNames, strings.ToLower(san))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// generate the associated public key
 | 
			
		||||
	publicKey := func(privKey interface{}) interface{} {
 | 
			
		||||
		switch k := privKey.(type) {
 | 
			
		||||
		case *rsa.PrivateKey:
 | 
			
		||||
			return &k.PublicKey
 | 
			
		||||
		case *ecdsa.PrivateKey:
 | 
			
		||||
			return &k.PublicKey
 | 
			
		||||
		default:
 | 
			
		||||
			return fmt.Errorf("unknown key type")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	derBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, publicKey(privKey), privKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return tls.Certificate{}, fmt.Errorf("could not create certificate: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	chain := [][]byte{derBytes}
 | 
			
		||||
 | 
			
		||||
	return tls.Certificate{
 | 
			
		||||
		Certificate: chain,
 | 
			
		||||
		PrivateKey:  privKey,
 | 
			
		||||
		Leaf:        cert,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// selfSignedConfig configures a self-signed certificate.
 | 
			
		||||
type selfSignedConfig struct {
 | 
			
		||||
	SAN     []string
 | 
			
		||||
	KeyType certcrypto.KeyType
 | 
			
		||||
	Expire  time.Time
 | 
			
		||||
}
 | 
			
		||||
@ -29,17 +29,20 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy"
 | 
			
		||||
	"github.com/mholt/caddy/telemetry"
 | 
			
		||||
	"github.com/mholt/certmagic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	caddy.RegisterPlugin("tls", caddy.Plugin{Action: setupTLS})
 | 
			
		||||
 | 
			
		||||
	// ensure TLS assets are stored and accessed from the CADDYPATH
 | 
			
		||||
	certmagic.DefaultStorage = certmagic.FileStorage{Path: caddy.AssetsPath()}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// setupTLS sets up the TLS configuration and installs certificates that
 | 
			
		||||
// are specified by the user in the config file. All the automatic HTTPS
 | 
			
		||||
// stuff comes later outside of this function.
 | 
			
		||||
func setupTLS(c *caddy.Controller) error {
 | 
			
		||||
	// obtain the configGetter, which loads the config we're, uh, configuring
 | 
			
		||||
	configGetter, ok := configGetters[c.ServerType()]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf("no caddytls.ConfigGetter for %s server type; must call RegisterConfigGetter", c.ServerType())
 | 
			
		||||
@ -49,18 +52,68 @@ func setupTLS(c *caddy.Controller) error {
 | 
			
		||||
		return fmt.Errorf("no caddytls.Config to set up for %s", c.Key)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// the certificate cache is tied to the current caddy.Instance; get a pointer to it
 | 
			
		||||
	certCache, ok := c.Get(CertCacheInstStorageKey).(*certificateCache)
 | 
			
		||||
	config.Enabled = true
 | 
			
		||||
 | 
			
		||||
	// a single certificate cache is used by the whole caddy.Instance; get a pointer to it
 | 
			
		||||
	certCache, ok := c.Get(CertCacheInstStorageKey).(*certmagic.Cache)
 | 
			
		||||
	if !ok || certCache == nil {
 | 
			
		||||
		certCache = &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
		certCache = certmagic.NewCache(certmagic.FileStorage{Path: caddy.AssetsPath()})
 | 
			
		||||
		c.OnShutdown(func() error {
 | 
			
		||||
			certCache.Stop()
 | 
			
		||||
			return nil
 | 
			
		||||
		})
 | 
			
		||||
		c.Set(CertCacheInstStorageKey, certCache)
 | 
			
		||||
	}
 | 
			
		||||
	config.certCache = certCache
 | 
			
		||||
	config.Manager = certmagic.NewWithCache(certCache, certmagic.Config{})
 | 
			
		||||
 | 
			
		||||
	config.Enabled = true
 | 
			
		||||
	// we use certmagic events to collect metrics for telemetry
 | 
			
		||||
	config.Manager.OnEvent = func(event string, data interface{}) {
 | 
			
		||||
		switch event {
 | 
			
		||||
		case "tls_handshake_started":
 | 
			
		||||
			clientHello := data.(*tls.ClientHelloInfo)
 | 
			
		||||
			if ClientHelloTelemetry && len(clientHello.SupportedVersions) > 0 {
 | 
			
		||||
				// If no other plugin (such as the HTTP server type) is implementing ClientHello telemetry, we do it.
 | 
			
		||||
				// NOTE: The values in the Go standard lib's ClientHelloInfo aren't guaranteed to be in order.
 | 
			
		||||
				info := ClientHelloInfo{
 | 
			
		||||
					Version:                   clientHello.SupportedVersions[0], // report the highest
 | 
			
		||||
					CipherSuites:              clientHello.CipherSuites,
 | 
			
		||||
					ExtensionsUnknown:         true, // no extension info... :(
 | 
			
		||||
					CompressionMethodsUnknown: true, // no compression methods... :(
 | 
			
		||||
					Curves:                    clientHello.SupportedCurves,
 | 
			
		||||
					Points:                    clientHello.SupportedPoints,
 | 
			
		||||
					// We also have, but do not yet use: SignatureSchemes, ServerName, and SupportedProtos (ALPN)
 | 
			
		||||
					// because the standard lib parses some extensions, but our MITM detector generally doesn't.
 | 
			
		||||
				}
 | 
			
		||||
				go telemetry.SetNested("tls_client_hello", info.Key(), info)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case "tls_handshake_completed":
 | 
			
		||||
			// TODO: This is a "best guess" for now - at this point, we only gave a
 | 
			
		||||
			// certificate to the client; we need something listener-level to be sure
 | 
			
		||||
			go telemetry.Increment("tls_handshake_count")
 | 
			
		||||
 | 
			
		||||
		case "acme_cert_obtained":
 | 
			
		||||
			go telemetry.Increment("tls_acme_certs_obtained")
 | 
			
		||||
 | 
			
		||||
		case "acme_cert_renewed":
 | 
			
		||||
			name := data.(string)
 | 
			
		||||
			caddy.EmitEvent(caddy.CertRenewEvent, name)
 | 
			
		||||
			go telemetry.Increment("tls_acme_certs_renewed")
 | 
			
		||||
 | 
			
		||||
		case "acme_cert_revoked":
 | 
			
		||||
			telemetry.Increment("acme_certs_revoked")
 | 
			
		||||
 | 
			
		||||
		case "cached_managed_cert":
 | 
			
		||||
			telemetry.Increment("tls_managed_cert_count")
 | 
			
		||||
 | 
			
		||||
		case "cached_unmanaged_cert":
 | 
			
		||||
			telemetry.Increment("tls_unmanaged_cert_count")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for c.Next() {
 | 
			
		||||
		var certificateFile, keyFile, loadDir, maxCerts, askURL string
 | 
			
		||||
		var onDemand bool
 | 
			
		||||
 | 
			
		||||
		args := c.RemainingArgs()
 | 
			
		||||
		switch len(args) {
 | 
			
		||||
@ -96,14 +149,14 @@ func setupTLS(c *caddy.Controller) error {
 | 
			
		||||
				if len(arg) != 1 {
 | 
			
		||||
					return c.ArgErr()
 | 
			
		||||
				}
 | 
			
		||||
				config.CAUrl = arg[0]
 | 
			
		||||
				config.Manager.CA = arg[0]
 | 
			
		||||
			case "key_type":
 | 
			
		||||
				arg := c.RemainingArgs()
 | 
			
		||||
				value, ok := supportedKeyTypes[strings.ToUpper(arg[0])]
 | 
			
		||||
				if !ok {
 | 
			
		||||
					return c.Errf("Wrong key type name or key type not supported: '%s'", c.Val())
 | 
			
		||||
				}
 | 
			
		||||
				config.KeyType = value
 | 
			
		||||
				config.Manager.KeyType = value
 | 
			
		||||
			case "protocols":
 | 
			
		||||
				args := c.RemainingArgs()
 | 
			
		||||
				if len(args) == 1 {
 | 
			
		||||
@ -111,7 +164,6 @@ func setupTLS(c *caddy.Controller) error {
 | 
			
		||||
					if !ok {
 | 
			
		||||
						return c.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					config.ProtocolMinVersion, config.ProtocolMaxVersion = value, value
 | 
			
		||||
				} else {
 | 
			
		||||
					value, ok := SupportedProtocols[strings.ToLower(args[0])]
 | 
			
		||||
@ -174,32 +226,44 @@ func setupTLS(c *caddy.Controller) error {
 | 
			
		||||
				config.Manual = true
 | 
			
		||||
			case "max_certs":
 | 
			
		||||
				c.Args(&maxCerts)
 | 
			
		||||
				config.OnDemand = true
 | 
			
		||||
				telemetry.Increment("tls_on_demand_count")
 | 
			
		||||
				onDemand = true
 | 
			
		||||
			case "ask":
 | 
			
		||||
				c.Args(&askURL)
 | 
			
		||||
				config.OnDemand = true
 | 
			
		||||
				telemetry.Increment("tls_on_demand_count")
 | 
			
		||||
				onDemand = true
 | 
			
		||||
			case "dns":
 | 
			
		||||
				args := c.RemainingArgs()
 | 
			
		||||
				if len(args) != 1 {
 | 
			
		||||
					return c.ArgErr()
 | 
			
		||||
				}
 | 
			
		||||
				// TODO: we can get rid of DNS provider plugins with this one line
 | 
			
		||||
				// of code; however, currently (Dec. 2018) this adds about 20 MB
 | 
			
		||||
				// of bloat to the Caddy binary, doubling its size to ~40 MB...!
 | 
			
		||||
				// dnsProv, err := dns.NewDNSChallengeProviderByName(args[0])
 | 
			
		||||
				// if err != nil {
 | 
			
		||||
				// 	return c.Errf("Configuring DNS provider '%s': %v", args[0], err)
 | 
			
		||||
				// }
 | 
			
		||||
				dnsProvName := args[0]
 | 
			
		||||
				if _, ok := dnsProviders[dnsProvName]; !ok {
 | 
			
		||||
					return c.Errf("Unsupported DNS provider '%s'", args[0])
 | 
			
		||||
				dnsProvConstructor, ok := dnsProviders[dnsProvName]
 | 
			
		||||
				if !ok {
 | 
			
		||||
					return c.Errf("Unknown DNS provider by name '%s'", dnsProvName)
 | 
			
		||||
				}
 | 
			
		||||
				config.DNSProvider = args[0]
 | 
			
		||||
			case "storage":
 | 
			
		||||
				args := c.RemainingArgs()
 | 
			
		||||
				if len(args) != 1 {
 | 
			
		||||
					return c.ArgErr()
 | 
			
		||||
				dnsProv, err := dnsProvConstructor()
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return c.Errf("Setting up DNS provider '%s': %v", dnsProvName, err)
 | 
			
		||||
				}
 | 
			
		||||
				storageProvName := args[0]
 | 
			
		||||
				if _, ok := storageProviders[storageProvName]; !ok {
 | 
			
		||||
					return c.Errf("Unsupported Storage provider '%s'", args[0])
 | 
			
		||||
				}
 | 
			
		||||
				config.StorageProvider = args[0]
 | 
			
		||||
				config.Manager.DNSProvider = dnsProv
 | 
			
		||||
			// TODO
 | 
			
		||||
			// case "storage":
 | 
			
		||||
			// 	args := c.RemainingArgs()
 | 
			
		||||
			// 	if len(args) != 1 {
 | 
			
		||||
			// 		return c.ArgErr()
 | 
			
		||||
			// 	}
 | 
			
		||||
			// 	storageProvName := args[0]
 | 
			
		||||
			// 	storageProvConstr, ok := storageProviders[storageProvName]
 | 
			
		||||
			// 	if !ok {
 | 
			
		||||
			// 		return c.Errf("Unsupported Storage provider '%s'", args[0])
 | 
			
		||||
			// 	}
 | 
			
		||||
			// 	config.Manager.Storage = storageProvConstr
 | 
			
		||||
			case "alpn":
 | 
			
		||||
				args := c.RemainingArgs()
 | 
			
		||||
				if len(args) == 0 {
 | 
			
		||||
@ -209,9 +273,9 @@ func setupTLS(c *caddy.Controller) error {
 | 
			
		||||
					config.ALPN = append(config.ALPN, arg)
 | 
			
		||||
				}
 | 
			
		||||
			case "must_staple":
 | 
			
		||||
				config.MustStaple = true
 | 
			
		||||
				config.Manager.MustStaple = true
 | 
			
		||||
			case "wildcard":
 | 
			
		||||
				if !HostQualifies(config.Hostname) {
 | 
			
		||||
				if !certmagic.HostQualifies(config.Hostname) {
 | 
			
		||||
					return c.Errf("Hostname '%s' does not qualify for managed TLS, so cannot manage wildcard certificate for it", config.Hostname)
 | 
			
		||||
				}
 | 
			
		||||
				if strings.Contains(config.Hostname, "*") {
 | 
			
		||||
@ -233,26 +297,26 @@ func setupTLS(c *caddy.Controller) error {
 | 
			
		||||
			return c.ArgErr()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// set certificate limit if on-demand TLS is enabled
 | 
			
		||||
		if maxCerts != "" {
 | 
			
		||||
			maxCertsNum, err := strconv.Atoi(maxCerts)
 | 
			
		||||
			if err != nil || maxCertsNum < 1 {
 | 
			
		||||
				return c.Err("max_certs must be a positive integer")
 | 
			
		||||
		// configure on-demand TLS, if enabled
 | 
			
		||||
		if onDemand {
 | 
			
		||||
			config.Manager.OnDemand = new(certmagic.OnDemandConfig)
 | 
			
		||||
			if maxCerts != "" {
 | 
			
		||||
				maxCertsNum, err := strconv.Atoi(maxCerts)
 | 
			
		||||
				if err != nil || maxCertsNum < 1 {
 | 
			
		||||
					return c.Err("max_certs must be a positive integer")
 | 
			
		||||
				}
 | 
			
		||||
				config.Manager.OnDemand.MaxObtain = int32(maxCertsNum)
 | 
			
		||||
			}
 | 
			
		||||
			config.OnDemandState.MaxObtain = int32(maxCertsNum)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if askURL != "" {
 | 
			
		||||
			parsedURL, err := url.Parse(askURL)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return c.Err("ask must be a valid url")
 | 
			
		||||
			if askURL != "" {
 | 
			
		||||
				parsedURL, err := url.Parse(askURL)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return c.Err("ask must be a valid url")
 | 
			
		||||
				}
 | 
			
		||||
				if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
 | 
			
		||||
					return c.Err("ask URL must use http or https")
 | 
			
		||||
				}
 | 
			
		||||
				config.Manager.OnDemand.AskURL = parsedURL
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
 | 
			
		||||
				return c.Err("ask URL must use http or https")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			config.OnDemandState.AskURL = parsedURL
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// don't try to load certificates unless we're supposed to
 | 
			
		||||
@ -262,7 +326,7 @@ func setupTLS(c *caddy.Controller) error {
 | 
			
		||||
 | 
			
		||||
		// load a single certificate and key, if specified
 | 
			
		||||
		if certificateFile != "" && keyFile != "" {
 | 
			
		||||
			err := config.cacheUnmanagedCertificatePEMFile(certificateFile, keyFile)
 | 
			
		||||
			err := config.Manager.CacheUnmanagedCertificatePEMFile(certificateFile, keyFile)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return c.Errf("Unable to load certificate and key files for '%s': %v", c.Key, err)
 | 
			
		||||
			}
 | 
			
		||||
@ -282,7 +346,14 @@ func setupTLS(c *caddy.Controller) error {
 | 
			
		||||
 | 
			
		||||
	// generate self-signed cert if needed
 | 
			
		||||
	if config.SelfSigned {
 | 
			
		||||
		err := makeSelfSignedCertForConfig(config)
 | 
			
		||||
		ssCert, err := newSelfSignedCertificate(selfSignedConfig{
 | 
			
		||||
			SAN:     []string{config.Hostname},
 | 
			
		||||
			KeyType: config.Manager.KeyType,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("self-signed certificate generation: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		err = config.Manager.CacheUnmanagedTLSCertificate(ssCert)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("self-signed: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
@ -362,7 +433,7 @@ func loadCertsInDir(cfg *Config, c *caddy.Controller, dir string) error {
 | 
			
		||||
				return c.Errf("%s: no private key block found", path)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			err = cfg.cacheUnmanagedCertificatePEMBytes(certPEMBytes, keyPEMBytes)
 | 
			
		||||
			err = cfg.Manager.CacheUnmanagedCertificatePEMBytes(certPEMBytes, keyPEMBytes)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return c.Errf("%s: failed to load cert and key for '%s': %v", path, c.Key, err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,8 @@ import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy"
 | 
			
		||||
	"github.com/xenolf/lego/acme"
 | 
			
		||||
	"github.com/mholt/certmagic"
 | 
			
		||||
	"github.com/xenolf/lego/certcrypto"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestMain(m *testing.M) {
 | 
			
		||||
@ -46,8 +47,7 @@ func TestMain(m *testing.M) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetupParseBasic(t *testing.T) {
 | 
			
		||||
	certCache := &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
	cfg := &Config{Certificates: make(map[string]string), certCache: certCache}
 | 
			
		||||
	cfg, certCache := testConfigForTLSSetup()
 | 
			
		||||
 | 
			
		||||
	RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg })
 | 
			
		||||
	c := caddy.NewTestController("", `tls `+certFile+` `+keyFile+``)
 | 
			
		||||
@ -127,8 +127,7 @@ func TestSetupParseWithOptionalParams(t *testing.T) {
 | 
			
		||||
            must_staple
 | 
			
		||||
            alpn http/1.1
 | 
			
		||||
        }`
 | 
			
		||||
	certCache := &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
	cfg := &Config{Certificates: make(map[string]string), certCache: certCache}
 | 
			
		||||
	cfg, certCache := testConfigForTLSSetup()
 | 
			
		||||
 | 
			
		||||
	RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg })
 | 
			
		||||
	c := caddy.NewTestController("", params)
 | 
			
		||||
@ -151,7 +150,7 @@ func TestSetupParseWithOptionalParams(t *testing.T) {
 | 
			
		||||
		t.Errorf("Expected 3 Ciphers (not including TLS_FALLBACK_SCSV), got %v", len(cfg.Ciphers)-1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !cfg.MustStaple {
 | 
			
		||||
	if !cfg.Manager.MustStaple {
 | 
			
		||||
		t.Error("Expected must staple to be true")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -164,8 +163,7 @@ func TestSetupDefaultWithOptionalParams(t *testing.T) {
 | 
			
		||||
	params := `tls {
 | 
			
		||||
            ciphers RSA-3DES-EDE-CBC-SHA
 | 
			
		||||
        }`
 | 
			
		||||
	certCache := &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
	cfg := &Config{Certificates: make(map[string]string), certCache: certCache}
 | 
			
		||||
	cfg, certCache := testConfigForTLSSetup()
 | 
			
		||||
	RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg })
 | 
			
		||||
	c := caddy.NewTestController("", params)
 | 
			
		||||
	c.Set(CertCacheInstStorageKey, certCache)
 | 
			
		||||
@ -184,8 +182,7 @@ func TestSetupParseWithWrongOptionalParams(t *testing.T) {
 | 
			
		||||
	params := `tls ` + certFile + ` ` + keyFile + ` {
 | 
			
		||||
			protocols ssl tls
 | 
			
		||||
		}`
 | 
			
		||||
	certCache := &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
	cfg := &Config{Certificates: make(map[string]string), certCache: certCache}
 | 
			
		||||
	cfg, certCache := testConfigForTLSSetup()
 | 
			
		||||
	RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg })
 | 
			
		||||
	c := caddy.NewTestController("", params)
 | 
			
		||||
	c.Set(CertCacheInstStorageKey, certCache)
 | 
			
		||||
@ -239,8 +236,7 @@ func TestSetupParseWithClientAuth(t *testing.T) {
 | 
			
		||||
	params := `tls ` + certFile + ` ` + keyFile + ` {
 | 
			
		||||
			clients
 | 
			
		||||
		}`
 | 
			
		||||
	certCache := &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
	cfg := &Config{Certificates: make(map[string]string), certCache: certCache}
 | 
			
		||||
	cfg, _ := testConfigForTLSSetup()
 | 
			
		||||
	RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg })
 | 
			
		||||
	c := caddy.NewTestController("", params)
 | 
			
		||||
	err := setupTLS(c)
 | 
			
		||||
@ -273,8 +269,8 @@ func TestSetupParseWithClientAuth(t *testing.T) {
 | 
			
		||||
			clients verify_if_given
 | 
			
		||||
		}`, tls.VerifyClientCertIfGiven, true, noCAs},
 | 
			
		||||
	} {
 | 
			
		||||
		certCache := &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
		cfg := &Config{Certificates: make(map[string]string), certCache: certCache}
 | 
			
		||||
		certCache := certmagic.NewCache(certmagic.DefaultStorage)
 | 
			
		||||
		cfg := &Config{Manager: certmagic.NewWithCache(certCache, certmagic.Config{})}
 | 
			
		||||
		RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg })
 | 
			
		||||
		c := caddy.NewTestController("", caseData.params)
 | 
			
		||||
		c.Set(CertCacheInstStorageKey, certCache)
 | 
			
		||||
@ -327,8 +323,8 @@ func TestSetupParseWithCAUrl(t *testing.T) {
 | 
			
		||||
				ca 1 2
 | 
			
		||||
			}`, true, ""},
 | 
			
		||||
	} {
 | 
			
		||||
		certCache := &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
		cfg := &Config{Certificates: make(map[string]string), certCache: certCache}
 | 
			
		||||
		certCache := certmagic.NewCache(certmagic.DefaultStorage)
 | 
			
		||||
		cfg := &Config{Manager: certmagic.NewWithCache(certCache, certmagic.Config{})}
 | 
			
		||||
		RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg })
 | 
			
		||||
		c := caddy.NewTestController("", caseData.params)
 | 
			
		||||
		c.Set(CertCacheInstStorageKey, certCache)
 | 
			
		||||
@ -343,8 +339,8 @@ func TestSetupParseWithCAUrl(t *testing.T) {
 | 
			
		||||
			t.Errorf("In case %d: Expected no errors, got: %v", caseNumber, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if cfg.CAUrl != caseData.expectedCAUrl {
 | 
			
		||||
			t.Errorf("Expected '%v' as CAUrl, got %#v", caseData.expectedCAUrl, cfg.CAUrl)
 | 
			
		||||
		if cfg.Manager.CA != caseData.expectedCAUrl {
 | 
			
		||||
			t.Errorf("Expected '%v' as CAUrl, got %#v", caseData.expectedCAUrl, cfg.Manager.CA)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -353,8 +349,7 @@ func TestSetupParseWithKeyType(t *testing.T) {
 | 
			
		||||
	params := `tls {
 | 
			
		||||
            key_type p384
 | 
			
		||||
        }`
 | 
			
		||||
	certCache := &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
	cfg := &Config{Certificates: make(map[string]string), certCache: certCache}
 | 
			
		||||
	cfg, certCache := testConfigForTLSSetup()
 | 
			
		||||
	RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg })
 | 
			
		||||
	c := caddy.NewTestController("", params)
 | 
			
		||||
	c.Set(CertCacheInstStorageKey, certCache)
 | 
			
		||||
@ -364,8 +359,8 @@ func TestSetupParseWithKeyType(t *testing.T) {
 | 
			
		||||
		t.Errorf("Expected no errors, got: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cfg.KeyType != acme.EC384 {
 | 
			
		||||
		t.Errorf("Expected 'P384' as KeyType, got %#v", cfg.KeyType)
 | 
			
		||||
	if cfg.Manager.KeyType != certcrypto.EC384 {
 | 
			
		||||
		t.Errorf("Expected 'P384' as KeyType, got %#v", cfg.Manager.KeyType)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -373,8 +368,7 @@ func TestSetupParseWithCurves(t *testing.T) {
 | 
			
		||||
	params := `tls {
 | 
			
		||||
            curves x25519 p256 p384 p521
 | 
			
		||||
        }`
 | 
			
		||||
	certCache := &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
	cfg := &Config{Certificates: make(map[string]string), certCache: certCache}
 | 
			
		||||
	cfg, certCache := testConfigForTLSSetup()
 | 
			
		||||
	RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg })
 | 
			
		||||
	c := caddy.NewTestController("", params)
 | 
			
		||||
	c.Set(CertCacheInstStorageKey, certCache)
 | 
			
		||||
@ -402,8 +396,7 @@ func TestSetupParseWithOneTLSProtocol(t *testing.T) {
 | 
			
		||||
	params := `tls {
 | 
			
		||||
            protocols tls1.2
 | 
			
		||||
        }`
 | 
			
		||||
	certCache := &certificateCache{cache: make(map[string]Certificate)}
 | 
			
		||||
	cfg := &Config{Certificates: make(map[string]string), certCache: certCache}
 | 
			
		||||
	cfg, certCache := testConfigForTLSSetup()
 | 
			
		||||
	RegisterConfigGetter("", func(c *caddy.Controller) *Config { return cfg })
 | 
			
		||||
	c := caddy.NewTestController("", params)
 | 
			
		||||
	c.Set(CertCacheInstStorageKey, certCache)
 | 
			
		||||
@ -422,6 +415,14 @@ func TestSetupParseWithOneTLSProtocol(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testConfigForTLSSetup() (*Config, *certmagic.Cache) {
 | 
			
		||||
	certCache := certmagic.NewCache(nil)
 | 
			
		||||
	certCache.Stop()
 | 
			
		||||
	return &Config{
 | 
			
		||||
		Manager: certmagic.NewWithCache(certCache, certmagic.Config{}),
 | 
			
		||||
	}, certCache
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	certFile = "test_cert.pem"
 | 
			
		||||
	keyFile  = "test_key.pem"
 | 
			
		||||
 | 
			
		||||
@ -1,127 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 caddytls
 | 
			
		||||
 | 
			
		||||
import "net/url"
 | 
			
		||||
 | 
			
		||||
// StorageConstructor is a function type that is used in the Config to
 | 
			
		||||
// instantiate a new Storage instance. This function can return a nil
 | 
			
		||||
// Storage even without an error.
 | 
			
		||||
type StorageConstructor func(caURL *url.URL) (Storage, error)
 | 
			
		||||
 | 
			
		||||
// SiteData contains persisted items pertaining to an individual site.
 | 
			
		||||
type SiteData struct {
 | 
			
		||||
	// Cert is the public cert byte array.
 | 
			
		||||
	Cert []byte
 | 
			
		||||
	// Key is the private key byte array.
 | 
			
		||||
	Key []byte
 | 
			
		||||
	// Meta is metadata about the site used by Caddy.
 | 
			
		||||
	Meta []byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UserData contains persisted items pertaining to a user.
 | 
			
		||||
type UserData struct {
 | 
			
		||||
	// Reg is the user registration byte array.
 | 
			
		||||
	Reg []byte
 | 
			
		||||
	// Key is the user key byte array.
 | 
			
		||||
	Key []byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Locker provides support for mutual exclusion
 | 
			
		||||
type Locker interface {
 | 
			
		||||
	// TryLock will return immediatedly with or without acquiring the lock.
 | 
			
		||||
	// If a lock could be obtained, (nil, nil) is returned and you may
 | 
			
		||||
	// continue normally. If not (meaning another process is already
 | 
			
		||||
	// working on that name), a Waiter value will be returned upon
 | 
			
		||||
	// which you can Wait() until it is finished, and then return
 | 
			
		||||
	// when it unblocks. If waiting, do not unlock!
 | 
			
		||||
	//
 | 
			
		||||
	// To prevent deadlocks, all implementations (where this concern
 | 
			
		||||
	// is relevant) should put a reasonable expiration on the lock in
 | 
			
		||||
	// case Unlock is unable to be called due to some sort of storage
 | 
			
		||||
	// system failure or crash.
 | 
			
		||||
	TryLock(name string) (Waiter, error)
 | 
			
		||||
 | 
			
		||||
	// Unlock unlocks the mutex for name. Only callers of TryLock who
 | 
			
		||||
	// successfully obtained the lock (no Waiter value was returned)
 | 
			
		||||
	// should call this method, and it should be called only after
 | 
			
		||||
	// the obtain/renew and store are finished, even if there was
 | 
			
		||||
	// an error (or a timeout). Unlock should also clean up any
 | 
			
		||||
	// unused resources allocated during TryLock.
 | 
			
		||||
	Unlock(name string) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Storage is an interface abstracting all storage used by Caddy's TLS
 | 
			
		||||
// subsystem. Implementations of this interface store both site and
 | 
			
		||||
// user data.
 | 
			
		||||
type Storage interface {
 | 
			
		||||
	// SiteExists returns true if this site exists in storage.
 | 
			
		||||
	// Site data is considered present when StoreSite has been called
 | 
			
		||||
	// successfully (without DeleteSite having been called, of course).
 | 
			
		||||
	SiteExists(domain string) (bool, error)
 | 
			
		||||
 | 
			
		||||
	// LoadSite obtains the site data from storage for the given domain and
 | 
			
		||||
	// returns it. If data for the domain does not exist, an error value
 | 
			
		||||
	// of type ErrNotExist is returned. For multi-server storage, care
 | 
			
		||||
	// should be taken to make this load atomic to prevent race conditions
 | 
			
		||||
	// that happen with multiple data loads.
 | 
			
		||||
	LoadSite(domain string) (*SiteData, error)
 | 
			
		||||
 | 
			
		||||
	// StoreSite persists the given site data for the given domain in
 | 
			
		||||
	// storage. For multi-server storage, care should be taken to make this
 | 
			
		||||
	// call atomic to prevent half-written data on failure of an internal
 | 
			
		||||
	// intermediate storage step. Implementers can trust that at runtime
 | 
			
		||||
	// this function will only be invoked after LockRegister and before
 | 
			
		||||
	// UnlockRegister of the same domain.
 | 
			
		||||
	StoreSite(domain string, data *SiteData) error
 | 
			
		||||
 | 
			
		||||
	// DeleteSite deletes the site for the given domain from storage.
 | 
			
		||||
	// Multi-server implementations should attempt to make this atomic. If
 | 
			
		||||
	// the site does not exist, an error value of type ErrNotExist is returned.
 | 
			
		||||
	DeleteSite(domain string) error
 | 
			
		||||
 | 
			
		||||
	// LoadUser obtains user data from storage for the given email and
 | 
			
		||||
	// returns it. If data for the email does not exist, an error value
 | 
			
		||||
	// of type ErrNotExist is returned. Multi-server implementations
 | 
			
		||||
	// should take care to make this operation atomic for all loaded
 | 
			
		||||
	// data items.
 | 
			
		||||
	LoadUser(email string) (*UserData, error)
 | 
			
		||||
 | 
			
		||||
	// StoreUser persists the given user data for the given email in
 | 
			
		||||
	// storage. Multi-server implementations should take care to make this
 | 
			
		||||
	// operation atomic for all stored data items.
 | 
			
		||||
	StoreUser(email string, data *UserData) error
 | 
			
		||||
 | 
			
		||||
	// MostRecentUserEmail provides the most recently used email parameter
 | 
			
		||||
	// in StoreUser. The result is an empty string if there are no
 | 
			
		||||
	// persisted users in storage.
 | 
			
		||||
	MostRecentUserEmail() string
 | 
			
		||||
 | 
			
		||||
	// Locker is necessary because synchronizing certificate maintenance
 | 
			
		||||
	// depends on how storage is implemented.
 | 
			
		||||
	Locker
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrNotExist is returned by Storage implementations when
 | 
			
		||||
// a resource is not found. It is similar to os.ErrNotExist
 | 
			
		||||
// except this is a type, not a variable.
 | 
			
		||||
type ErrNotExist interface {
 | 
			
		||||
	error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Waiter is a type that can block until a storage lock is released.
 | 
			
		||||
type Waiter interface {
 | 
			
		||||
	Wait()
 | 
			
		||||
}
 | 
			
		||||
@ -1,148 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 storagetest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy/caddytls"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// memoryMutex is a mutex used to control access to memoryStoragesByCAURL.
 | 
			
		||||
var memoryMutex sync.Mutex
 | 
			
		||||
 | 
			
		||||
// memoryStoragesByCAURL is a map keyed by a CA URL string with values of
 | 
			
		||||
// instantiated memory stores. Do not access this directly, it is used by
 | 
			
		||||
// InMemoryStorageCreator.
 | 
			
		||||
var memoryStoragesByCAURL = make(map[string]*InMemoryStorage)
 | 
			
		||||
 | 
			
		||||
// InMemoryStorageCreator is a caddytls.Storage.StorageCreator to create
 | 
			
		||||
// InMemoryStorage instances for testing.
 | 
			
		||||
func InMemoryStorageCreator(caURL *url.URL) (caddytls.Storage, error) {
 | 
			
		||||
	urlStr := caURL.String()
 | 
			
		||||
	memoryMutex.Lock()
 | 
			
		||||
	defer memoryMutex.Unlock()
 | 
			
		||||
	storage := memoryStoragesByCAURL[urlStr]
 | 
			
		||||
	if storage == nil {
 | 
			
		||||
		storage = NewInMemoryStorage()
 | 
			
		||||
		memoryStoragesByCAURL[urlStr] = storage
 | 
			
		||||
	}
 | 
			
		||||
	return storage, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InMemoryStorage is a caddytls.Storage implementation for use in testing.
 | 
			
		||||
// It simply stores information in runtime memory.
 | 
			
		||||
type InMemoryStorage struct {
 | 
			
		||||
	// Sites are exposed for testing purposes.
 | 
			
		||||
	Sites map[string]*caddytls.SiteData
 | 
			
		||||
	// Users are exposed for testing purposes.
 | 
			
		||||
	Users map[string]*caddytls.UserData
 | 
			
		||||
	// LastUserEmail is exposed for testing purposes.
 | 
			
		||||
	LastUserEmail string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewInMemoryStorage constructs an InMemoryStorage instance. For use with
 | 
			
		||||
// caddytls, the InMemoryStorageCreator should be used instead.
 | 
			
		||||
func NewInMemoryStorage() *InMemoryStorage {
 | 
			
		||||
	return &InMemoryStorage{
 | 
			
		||||
		Sites: make(map[string]*caddytls.SiteData),
 | 
			
		||||
		Users: make(map[string]*caddytls.UserData),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SiteExists implements caddytls.Storage.SiteExists in memory.
 | 
			
		||||
func (s *InMemoryStorage) SiteExists(domain string) (bool, error) {
 | 
			
		||||
	_, siteExists := s.Sites[domain]
 | 
			
		||||
	return siteExists, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Clear completely clears all values associated with this storage.
 | 
			
		||||
func (s *InMemoryStorage) Clear() {
 | 
			
		||||
	s.Sites = make(map[string]*caddytls.SiteData)
 | 
			
		||||
	s.Users = make(map[string]*caddytls.UserData)
 | 
			
		||||
	s.LastUserEmail = ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadSite implements caddytls.Storage.LoadSite in memory.
 | 
			
		||||
func (s *InMemoryStorage) LoadSite(domain string) (*caddytls.SiteData, error) {
 | 
			
		||||
	siteData, ok := s.Sites[domain]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, caddytls.ErrNotExist(errors.New("not found"))
 | 
			
		||||
	}
 | 
			
		||||
	return siteData, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyBytes(from []byte) []byte {
 | 
			
		||||
	copiedBytes := make([]byte, len(from))
 | 
			
		||||
	copy(copiedBytes, from)
 | 
			
		||||
	return copiedBytes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StoreSite implements caddytls.Storage.StoreSite in memory.
 | 
			
		||||
func (s *InMemoryStorage) StoreSite(domain string, data *caddytls.SiteData) error {
 | 
			
		||||
	copiedData := new(caddytls.SiteData)
 | 
			
		||||
	copiedData.Cert = copyBytes(data.Cert)
 | 
			
		||||
	copiedData.Key = copyBytes(data.Key)
 | 
			
		||||
	copiedData.Meta = copyBytes(data.Meta)
 | 
			
		||||
	s.Sites[domain] = copiedData
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteSite implements caddytls.Storage.DeleteSite in memory.
 | 
			
		||||
func (s *InMemoryStorage) DeleteSite(domain string) error {
 | 
			
		||||
	if _, ok := s.Sites[domain]; !ok {
 | 
			
		||||
		return caddytls.ErrNotExist(errors.New("not found"))
 | 
			
		||||
	}
 | 
			
		||||
	delete(s.Sites, domain)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TryLock implements Storage.TryLock by returning nil values because it
 | 
			
		||||
// is not a multi-server storage implementation.
 | 
			
		||||
func (s *InMemoryStorage) TryLock(domain string) (caddytls.Waiter, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Unlock implements Storage.Unlock as a no-op because it is
 | 
			
		||||
// not a multi-server storage implementation.
 | 
			
		||||
func (s *InMemoryStorage) Unlock(domain string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadUser implements caddytls.Storage.LoadUser in memory.
 | 
			
		||||
func (s *InMemoryStorage) LoadUser(email string) (*caddytls.UserData, error) {
 | 
			
		||||
	userData, ok := s.Users[email]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, caddytls.ErrNotExist(errors.New("not found"))
 | 
			
		||||
	}
 | 
			
		||||
	return userData, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StoreUser implements caddytls.Storage.StoreUser in memory.
 | 
			
		||||
func (s *InMemoryStorage) StoreUser(email string, data *caddytls.UserData) error {
 | 
			
		||||
	copiedData := new(caddytls.UserData)
 | 
			
		||||
	copiedData.Reg = copyBytes(data.Reg)
 | 
			
		||||
	copiedData.Key = copyBytes(data.Key)
 | 
			
		||||
	s.Users[email] = copiedData
 | 
			
		||||
	s.LastUserEmail = email
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MostRecentUserEmail implements caddytls.Storage.MostRecentUserEmail in memory.
 | 
			
		||||
func (s *InMemoryStorage) MostRecentUserEmail() string {
 | 
			
		||||
	return s.LastUserEmail
 | 
			
		||||
}
 | 
			
		||||
@ -1,26 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 storagetest
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestMemoryStorage(t *testing.T) {
 | 
			
		||||
	storage := NewInMemoryStorage()
 | 
			
		||||
	storageTest := &StorageTest{
 | 
			
		||||
		Storage:  storage,
 | 
			
		||||
		PostTest: storage.Clear,
 | 
			
		||||
	}
 | 
			
		||||
	storageTest.Test(t, false)
 | 
			
		||||
}
 | 
			
		||||
@ -1,306 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 storagetest provides utilities to assist in testing caddytls.Storage
 | 
			
		||||
// implementations.
 | 
			
		||||
package storagetest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy/caddytls"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// StorageTest is a test harness that contains tests to execute all exposed
 | 
			
		||||
// parts of a Storage implementation.
 | 
			
		||||
type StorageTest struct {
 | 
			
		||||
	// Storage is the implementation to use during tests. This must be
 | 
			
		||||
	// present.
 | 
			
		||||
	caddytls.Storage
 | 
			
		||||
 | 
			
		||||
	// PreTest, if present, is called before every test. Any error returned
 | 
			
		||||
	// is returned from the test and the test does not continue.
 | 
			
		||||
	PreTest func() error
 | 
			
		||||
 | 
			
		||||
	// PostTest, if present, is executed after every test via defer which
 | 
			
		||||
	// means it executes even on failure of the test (but not on failure of
 | 
			
		||||
	// PreTest).
 | 
			
		||||
	PostTest func()
 | 
			
		||||
 | 
			
		||||
	// AfterUserEmailStore, if present, is invoked during
 | 
			
		||||
	// TestMostRecentUserEmail after each storage just in case anything
 | 
			
		||||
	// needs to be mocked.
 | 
			
		||||
	AfterUserEmailStore func(email string) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestFunc holds information about a test.
 | 
			
		||||
type TestFunc struct {
 | 
			
		||||
	// Name is the friendly name of the test.
 | 
			
		||||
	Name string
 | 
			
		||||
 | 
			
		||||
	// Fn is the function that is invoked for the test.
 | 
			
		||||
	Fn func() error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// runPreTest runs the PreTest function if present.
 | 
			
		||||
func (s *StorageTest) runPreTest() error {
 | 
			
		||||
	if s.PreTest != nil {
 | 
			
		||||
		return s.PreTest()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// runPostTest runs the PostTest function if present.
 | 
			
		||||
func (s *StorageTest) runPostTest() {
 | 
			
		||||
	if s.PostTest != nil {
 | 
			
		||||
		s.PostTest()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AllFuncs returns all test functions that are part of this harness.
 | 
			
		||||
func (s *StorageTest) AllFuncs() []TestFunc {
 | 
			
		||||
	return []TestFunc{
 | 
			
		||||
		{"TestSiteInfoExists", s.TestSiteExists},
 | 
			
		||||
		{"TestSite", s.TestSite},
 | 
			
		||||
		{"TestUser", s.TestUser},
 | 
			
		||||
		{"TestMostRecentUserEmail", s.TestMostRecentUserEmail},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Test executes the entire harness using the testing package. Failures are
 | 
			
		||||
// reported via T.Fatal. If eagerFail is true, the first failure causes all
 | 
			
		||||
// testing to stop immediately.
 | 
			
		||||
func (s *StorageTest) Test(t *testing.T, eagerFail bool) {
 | 
			
		||||
	if errs := s.TestAll(eagerFail); len(errs) > 0 {
 | 
			
		||||
		ifaces := make([]interface{}, len(errs))
 | 
			
		||||
		for i, err := range errs {
 | 
			
		||||
			ifaces[i] = err
 | 
			
		||||
		}
 | 
			
		||||
		t.Fatal(ifaces...)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestAll executes the entire harness and returns the results as an array of
 | 
			
		||||
// errors. If eagerFail is true, the first failure causes all testing to stop
 | 
			
		||||
// immediately.
 | 
			
		||||
func (s *StorageTest) TestAll(eagerFail bool) (errs []error) {
 | 
			
		||||
	for _, fn := range s.AllFuncs() {
 | 
			
		||||
		if err := fn.Fn(); err != nil {
 | 
			
		||||
			errs = append(errs, fmt.Errorf("%v failed: %v", fn.Name, err))
 | 
			
		||||
			if eagerFail {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var simpleSiteData = &caddytls.SiteData{
 | 
			
		||||
	Cert: []byte("foo"),
 | 
			
		||||
	Key:  []byte("bar"),
 | 
			
		||||
	Meta: []byte("baz"),
 | 
			
		||||
}
 | 
			
		||||
var simpleSiteDataAlt = &caddytls.SiteData{
 | 
			
		||||
	Cert: []byte("qux"),
 | 
			
		||||
	Key:  []byte("quux"),
 | 
			
		||||
	Meta: []byte("corge"),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestSiteExists tests Storage.SiteExists.
 | 
			
		||||
func (s *StorageTest) TestSiteExists() error {
 | 
			
		||||
	if err := s.runPreTest(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer s.runPostTest()
 | 
			
		||||
 | 
			
		||||
	// Should not exist at first
 | 
			
		||||
	siteExists, err := s.SiteExists("example.com")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if siteExists {
 | 
			
		||||
		return errors.New("Site should not exist")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Should exist after we store it
 | 
			
		||||
	if err := s.StoreSite("example.com", simpleSiteData); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	siteExists, err = s.SiteExists("example.com")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !siteExists {
 | 
			
		||||
		return errors.New("Expected site to exist")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Site should no longer exist after we delete it
 | 
			
		||||
	if err := s.DeleteSite("example.com"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	siteExists, err = s.SiteExists("example.com")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if siteExists {
 | 
			
		||||
		return errors.New("Site should not exist after delete")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestSite tests Storage.LoadSite, Storage.StoreSite, and Storage.DeleteSite.
 | 
			
		||||
func (s *StorageTest) TestSite() error {
 | 
			
		||||
	if err := s.runPreTest(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer s.runPostTest()
 | 
			
		||||
 | 
			
		||||
	// Should be a not-found error at first
 | 
			
		||||
	_, err := s.LoadSite("example.com")
 | 
			
		||||
	if _, ok := err.(caddytls.ErrNotExist); !ok {
 | 
			
		||||
		return fmt.Errorf("Expected caddytls.ErrNotExist from load, got %T: %v", err, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Delete should also be a not-found error at first
 | 
			
		||||
	err = s.DeleteSite("example.com")
 | 
			
		||||
	if _, ok := err.(caddytls.ErrNotExist); !ok {
 | 
			
		||||
		return fmt.Errorf("Expected ErrNotExist from delete, got: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Should store successfully and then load just fine
 | 
			
		||||
	if err := s.StoreSite("example.com", simpleSiteData); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if siteData, err := s.LoadSite("example.com"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	} else if !bytes.Equal(siteData.Cert, simpleSiteData.Cert) {
 | 
			
		||||
		return errors.New("Unexpected cert returned after store")
 | 
			
		||||
	} else if !bytes.Equal(siteData.Key, simpleSiteData.Key) {
 | 
			
		||||
		return errors.New("Unexpected key returned after store")
 | 
			
		||||
	} else if !bytes.Equal(siteData.Meta, simpleSiteData.Meta) {
 | 
			
		||||
		return errors.New("Unexpected meta returned after store")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Overwrite should work just fine
 | 
			
		||||
	if err := s.StoreSite("example.com", simpleSiteDataAlt); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if siteData, err := s.LoadSite("example.com"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	} else if !bytes.Equal(siteData.Cert, simpleSiteDataAlt.Cert) {
 | 
			
		||||
		return errors.New("Unexpected cert returned after overwrite")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// It should delete fine and then not be there
 | 
			
		||||
	if err := s.DeleteSite("example.com"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	_, err = s.LoadSite("example.com")
 | 
			
		||||
	if _, ok := err.(caddytls.ErrNotExist); !ok {
 | 
			
		||||
		return fmt.Errorf("Expected caddytls.ErrNotExist after delete, got %T: %v", err, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var simpleUserData = &caddytls.UserData{
 | 
			
		||||
	Reg: []byte("foo"),
 | 
			
		||||
	Key: []byte("bar"),
 | 
			
		||||
}
 | 
			
		||||
var simpleUserDataAlt = &caddytls.UserData{
 | 
			
		||||
	Reg: []byte("baz"),
 | 
			
		||||
	Key: []byte("qux"),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestUser tests Storage.LoadUser and Storage.StoreUser.
 | 
			
		||||
func (s *StorageTest) TestUser() error {
 | 
			
		||||
	if err := s.runPreTest(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer s.runPostTest()
 | 
			
		||||
 | 
			
		||||
	// Should be a not-found error at first
 | 
			
		||||
	_, err := s.LoadUser("foo@example.com")
 | 
			
		||||
	if _, ok := err.(caddytls.ErrNotExist); !ok {
 | 
			
		||||
		return fmt.Errorf("Expected caddytls.ErrNotExist from load, got %T: %v", err, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Should store successfully and then load just fine
 | 
			
		||||
	if err := s.StoreUser("foo@example.com", simpleUserData); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if userData, err := s.LoadUser("foo@example.com"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	} else if !bytes.Equal(userData.Reg, simpleUserData.Reg) {
 | 
			
		||||
		return errors.New("Unexpected reg returned after store")
 | 
			
		||||
	} else if !bytes.Equal(userData.Key, simpleUserData.Key) {
 | 
			
		||||
		return errors.New("Unexpected key returned after store")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Overwrite should work just fine
 | 
			
		||||
	if err := s.StoreUser("foo@example.com", simpleUserDataAlt); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if userData, err := s.LoadUser("foo@example.com"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	} else if !bytes.Equal(userData.Reg, simpleUserDataAlt.Reg) {
 | 
			
		||||
		return errors.New("Unexpected reg returned after overwrite")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestMostRecentUserEmail tests Storage.MostRecentUserEmail.
 | 
			
		||||
func (s *StorageTest) TestMostRecentUserEmail() error {
 | 
			
		||||
	if err := s.runPreTest(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer s.runPostTest()
 | 
			
		||||
 | 
			
		||||
	// Should be empty on first run
 | 
			
		||||
	if e := s.MostRecentUserEmail(); e != "" {
 | 
			
		||||
		return fmt.Errorf("Expected empty most recent user on first run, got: %v", e)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If we store user, then that one should be returned
 | 
			
		||||
	if err := s.StoreUser("foo1@example.com", simpleUserData); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if s.AfterUserEmailStore != nil {
 | 
			
		||||
		s.AfterUserEmailStore("foo1@example.com")
 | 
			
		||||
	}
 | 
			
		||||
	if e := s.MostRecentUserEmail(); e != "foo1@example.com" {
 | 
			
		||||
		return fmt.Errorf("Unexpected most recent email after first store: %v", e)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If we store another user, then that one should be returned
 | 
			
		||||
	if err := s.StoreUser("foo2@example.com", simpleUserDataAlt); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if s.AfterUserEmailStore != nil {
 | 
			
		||||
		s.AfterUserEmailStore("foo2@example.com")
 | 
			
		||||
	}
 | 
			
		||||
	if e := s.MostRecentUserEmail(); e != "foo2@example.com" {
 | 
			
		||||
		return fmt.Errorf("Unexpected most recent email after user key: %v", e)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@ -1,54 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 storagetest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy/caddytls"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TestFileStorage tests the file storage set with the test harness in this
 | 
			
		||||
// package.
 | 
			
		||||
func TestFileStorage(t *testing.T) {
 | 
			
		||||
	emailCounter := 0
 | 
			
		||||
	storageTest := &StorageTest{
 | 
			
		||||
		Storage:  &caddytls.FileStorage{Path: "./testdata"}, // nameLocks isn't made here, but it's okay because the tests don't call TryLock or Unlock
 | 
			
		||||
		PostTest: func() { os.RemoveAll("./testdata") },
 | 
			
		||||
		AfterUserEmailStore: func(email string) error {
 | 
			
		||||
			// We need to change the dir mod time to show a
 | 
			
		||||
			// that certain dirs are newer.
 | 
			
		||||
			emailCounter++
 | 
			
		||||
			fp := filepath.Join("./testdata", "users", email)
 | 
			
		||||
 | 
			
		||||
			// What we will do is subtract 10 days from today and
 | 
			
		||||
			// then add counter * seconds to make the later
 | 
			
		||||
			// counters newer. We accept that this isn't exactly
 | 
			
		||||
			// how the file storage works because it only changes
 | 
			
		||||
			// timestamps on *newly seen* users, but it achieves
 | 
			
		||||
			// the result that the harness expects.
 | 
			
		||||
			chTime := time.Now().AddDate(0, 0, -10).Add(time.Duration(emailCounter) * time.Second)
 | 
			
		||||
			if err := os.Chtimes(fp, chTime, chTime); err != nil {
 | 
			
		||||
				return fmt.Errorf("Unable to change file time for %v: %v", fp, err)
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	storageTest.Test(t, false)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										251
									
								
								caddytls/tls.go
									
									
									
									
									
								
							
							
						
						
									
										251
									
								
								caddytls/tls.go
									
									
									
									
									
								
							@ -15,7 +15,7 @@
 | 
			
		||||
// Package caddytls facilitates the management of TLS assets and integrates
 | 
			
		||||
// Let's Encrypt functionality into Caddy with first-class support for
 | 
			
		||||
// creating and renewing certificates automatically. It also implements
 | 
			
		||||
// the tls directive.
 | 
			
		||||
// the tls directive. It's mostly powered by the CertMagic package.
 | 
			
		||||
//
 | 
			
		||||
// This package is meant to be used by Caddy server types. To use the
 | 
			
		||||
// tls directive, a server type must import this package and call
 | 
			
		||||
@ -29,196 +29,11 @@
 | 
			
		||||
package caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/mholt/caddy"
 | 
			
		||||
	"github.com/xenolf/lego/acme"
 | 
			
		||||
	"github.com/mholt/certmagic"
 | 
			
		||||
	"github.com/xenolf/lego/challenge"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HostQualifies returns true if the hostname alone
 | 
			
		||||
// appears eligible for automatic HTTPS. For example:
 | 
			
		||||
// localhost, empty hostname, and IP addresses are
 | 
			
		||||
// not eligible because we cannot obtain certificates
 | 
			
		||||
// for those names. Wildcard names are allowed, as long
 | 
			
		||||
// as they conform to CABF requirements (only one wildcard
 | 
			
		||||
// label, and it must be the left-most label).
 | 
			
		||||
func HostQualifies(hostname string) bool {
 | 
			
		||||
	return hostname != "localhost" && // localhost is ineligible
 | 
			
		||||
 | 
			
		||||
		// hostname must not be empty
 | 
			
		||||
		strings.TrimSpace(hostname) != "" &&
 | 
			
		||||
 | 
			
		||||
		// only one wildcard label allowed, and it must be left-most
 | 
			
		||||
		(!strings.Contains(hostname, "*") ||
 | 
			
		||||
			(strings.Count(hostname, "*") == 1 &&
 | 
			
		||||
				strings.HasPrefix(hostname, "*."))) &&
 | 
			
		||||
 | 
			
		||||
		// must not start or end with a dot
 | 
			
		||||
		!strings.HasPrefix(hostname, ".") &&
 | 
			
		||||
		!strings.HasSuffix(hostname, ".") &&
 | 
			
		||||
 | 
			
		||||
		// cannot be an IP address, see
 | 
			
		||||
		// https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt
 | 
			
		||||
		net.ParseIP(hostname) == nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// saveCertResource saves the certificate resource to disk. This
 | 
			
		||||
// includes the certificate file itself, the private key, and the
 | 
			
		||||
// metadata file.
 | 
			
		||||
func saveCertResource(storage Storage, cert *acme.CertificateResource) error {
 | 
			
		||||
	// Save cert, private key, and metadata
 | 
			
		||||
	siteData := &SiteData{
 | 
			
		||||
		Cert: cert.Certificate,
 | 
			
		||||
		Key:  cert.PrivateKey,
 | 
			
		||||
	}
 | 
			
		||||
	var err error
 | 
			
		||||
	siteData.Meta, err = json.MarshalIndent(&cert, "", "\t")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		err = storage.StoreSite(cert.Domain, siteData)
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Revoke revokes the certificate for host via ACME protocol.
 | 
			
		||||
// It assumes the certificate was obtained from the
 | 
			
		||||
// CA at DefaultCAUrl.
 | 
			
		||||
func Revoke(host string) error {
 | 
			
		||||
	client, err := newACMEClient(new(Config), true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return client.Revoke(host)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// tlsALPNSolver is a type that can solve TLS-ALPN challenges using
 | 
			
		||||
// an existing listener and our custom, in-memory certificate cache.
 | 
			
		||||
type tlsALPNSolver struct {
 | 
			
		||||
	certCache *certificateCache
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Present adds the challenge certificate to the cache.
 | 
			
		||||
func (s tlsALPNSolver) Present(domain, token, keyAuth string) error {
 | 
			
		||||
	cert, err := acme.TLSALPNChallengeCert(domain, keyAuth)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	certHash := hashCertificateChain(cert.Certificate)
 | 
			
		||||
	s.certCache.Lock()
 | 
			
		||||
	s.certCache.cache[tlsALPNCertKeyName(domain)] = Certificate{
 | 
			
		||||
		Certificate: *cert,
 | 
			
		||||
		Names:       []string{domain},
 | 
			
		||||
		Hash:        certHash, // perhaps not necesssary
 | 
			
		||||
	}
 | 
			
		||||
	s.certCache.Unlock()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CleanUp removes the challenge certificate from the cache.
 | 
			
		||||
func (s tlsALPNSolver) CleanUp(domain, token, keyAuth string) error {
 | 
			
		||||
	s.certCache.Lock()
 | 
			
		||||
	delete(s.certCache.cache, domain)
 | 
			
		||||
	s.certCache.Unlock()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// tlsALPNCertKeyName returns the key to use when caching a cert
 | 
			
		||||
// for use with the TLS-ALPN ACME challenge. It is simply to help
 | 
			
		||||
// avoid conflicts (although at time of writing, there shouldn't
 | 
			
		||||
// be, since the cert cache is keyed by hash of certificate chain).
 | 
			
		||||
func tlsALPNCertKeyName(sniName string) string {
 | 
			
		||||
	return sniName + ":acme-tls-alpn"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// distributedSolver allows the ACME HTTP-01 and TLS-ALPN challenges
 | 
			
		||||
// to be solved by an instance other than the one which initiated it.
 | 
			
		||||
// This is useful behind load balancers or in other cluster/fleet
 | 
			
		||||
// configurations. The only requirement is that this (the initiating)
 | 
			
		||||
// instance share the $CADDYPATH/acme folder with the instance that
 | 
			
		||||
// will complete the challenge. Mounting the folder locally should be
 | 
			
		||||
// sufficient.
 | 
			
		||||
//
 | 
			
		||||
// Obviously, the instance which completes the challenge must be
 | 
			
		||||
// serving on the HTTPChallengePort for the HTTP-01 challenge or the
 | 
			
		||||
// TLSALPNChallengePort for the TLS-ALPN-01 challenge (or have all
 | 
			
		||||
// the packets port-forwarded) to receive and handle the request. The
 | 
			
		||||
// server which receives the challenge must handle it by checking to
 | 
			
		||||
// see if a file exists, e.g.:
 | 
			
		||||
// $CADDYPATH/acme/challenge_tokens/example.com.json
 | 
			
		||||
// and if so, decode it and use it to serve up the correct response.
 | 
			
		||||
// Caddy's HTTP server does this by default (for HTTP-01) and so does
 | 
			
		||||
// its TLS package (for TLS-ALPN-01).
 | 
			
		||||
//
 | 
			
		||||
// So as long as the folder is shared, this will just work. There are
 | 
			
		||||
// no other requirements. The instances may be on other machines or
 | 
			
		||||
// even other networks, as long as they share the folder as part of
 | 
			
		||||
// the local file system.
 | 
			
		||||
//
 | 
			
		||||
// This solver works by persisting the token and keyauth information
 | 
			
		||||
// to disk in the shared folder when the authorization is presented,
 | 
			
		||||
// and then deletes it when it is cleaned up.
 | 
			
		||||
type distributedSolver struct {
 | 
			
		||||
	// As the distributedSolver is only a wrapper over the actual
 | 
			
		||||
	// solver, place the actual solver here
 | 
			
		||||
	providerServer ChallengeProvider
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Present adds the challenge certificate to the cache.
 | 
			
		||||
func (dhs distributedSolver) Present(domain, token, keyAuth string) error {
 | 
			
		||||
	if dhs.providerServer != nil {
 | 
			
		||||
		err := dhs.providerServer.Present(domain, token, keyAuth)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("presenting with standard provider server: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := os.MkdirAll(dhs.challengeTokensBasePath(), 0755)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	infoBytes, err := json.Marshal(challengeInfo{
 | 
			
		||||
		Domain:  domain,
 | 
			
		||||
		Token:   token,
 | 
			
		||||
		KeyAuth: keyAuth,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ioutil.WriteFile(dhs.challengeTokensPath(domain), infoBytes, 0644)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CleanUp removes the challenge certificate from the cache.
 | 
			
		||||
func (dhs distributedSolver) CleanUp(domain, token, keyAuth string) error {
 | 
			
		||||
	if dhs.providerServer != nil {
 | 
			
		||||
		err := dhs.providerServer.CleanUp(domain, token, keyAuth)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Printf("[ERROR] Cleaning up standard provider server: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return os.Remove(dhs.challengeTokensPath(domain))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dhs distributedSolver) challengeTokensPath(domain string) string {
 | 
			
		||||
	domainFile := fileSafe(domain)
 | 
			
		||||
	return filepath.Join(dhs.challengeTokensBasePath(), domainFile+".json")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dhs distributedSolver) challengeTokensBasePath() string {
 | 
			
		||||
	return filepath.Join(caddy.AssetsPath(), "acme", "challenge_tokens")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type challengeInfo struct {
 | 
			
		||||
	Domain, Token, KeyAuth string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConfigHolder is any type that has a Config; it presumably is
 | 
			
		||||
// connected to a hostname and port on which it is serving.
 | 
			
		||||
type ConfigHolder interface {
 | 
			
		||||
@ -240,11 +55,12 @@ func QualifiesForManagedTLS(c ConfigHolder) bool {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	tlsConfig := c.TLSConfig()
 | 
			
		||||
	if tlsConfig == nil {
 | 
			
		||||
	if tlsConfig == nil || tlsConfig.Manager == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	onDemand := tlsConfig.Manager.OnDemand != nil
 | 
			
		||||
 | 
			
		||||
	return (!tlsConfig.Manual || tlsConfig.OnDemand) && // user might provide own cert and key
 | 
			
		||||
	return (!tlsConfig.Manual || onDemand) && // user might provide own cert and key
 | 
			
		||||
 | 
			
		||||
		// if self-signed, we've already generated one to use
 | 
			
		||||
		!tlsConfig.SelfSigned &&
 | 
			
		||||
@ -255,17 +71,30 @@ func QualifiesForManagedTLS(c ConfigHolder) bool {
 | 
			
		||||
 | 
			
		||||
		// we get can't certs for some kinds of hostnames, but
 | 
			
		||||
		// on-demand TLS allows empty hostnames at startup
 | 
			
		||||
		(HostQualifies(c.Host()) || tlsConfig.OnDemand)
 | 
			
		||||
		(certmagic.HostQualifies(c.Host()) || onDemand)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Revoke revokes the certificate fro host via the ACME protocol.
 | 
			
		||||
// It assumes the certificate was obtained from certmagic.CA.
 | 
			
		||||
func Revoke(domainName string) error {
 | 
			
		||||
	return certmagic.NewDefault().RevokeCert(domainName, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// KnownACMECAs is a list of ACME directory endpoints of
 | 
			
		||||
// known, public, and trusted ACME-compatible certificate
 | 
			
		||||
// authorities.
 | 
			
		||||
var KnownACMECAs = []string{
 | 
			
		||||
	"https://acme-v02.api.letsencrypt.org/directory",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ChallengeProvider defines an own type that should be used in Caddy plugins
 | 
			
		||||
// over acme.ChallengeProvider. Using acme.ChallengeProvider causes version mismatches
 | 
			
		||||
// over challenge.Provider. Using challenge.Provider causes version mismatches
 | 
			
		||||
// with vendored dependencies (see https://github.com/mattfarina/golang-broken-vendor)
 | 
			
		||||
//
 | 
			
		||||
// acme.ChallengeProvider is an interface that allows the implementation of custom
 | 
			
		||||
// challenge.Provider is an interface that allows the implementation of custom
 | 
			
		||||
// challenge providers. For more details, see:
 | 
			
		||||
// https://godoc.org/github.com/xenolf/lego/acme#ChallengeProvider
 | 
			
		||||
type ChallengeProvider acme.ChallengeProvider
 | 
			
		||||
type ChallengeProvider challenge.Provider
 | 
			
		||||
 | 
			
		||||
// DNSProviderConstructor is a function that takes credentials and
 | 
			
		||||
// returns a type that can solve the ACME DNS challenges.
 | 
			
		||||
@ -280,32 +109,12 @@ func RegisterDNSProvider(name string, provider DNSProviderConstructor) {
 | 
			
		||||
	caddy.RegisterPlugin("tls.dns."+name, caddy.Plugin{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// DefaultEmail represents the Let's Encrypt account email to use if none provided.
 | 
			
		||||
	DefaultEmail string
 | 
			
		||||
// TODO...
 | 
			
		||||
 | 
			
		||||
	// Agreed indicates whether user has agreed to the Let's Encrypt SA.
 | 
			
		||||
	Agreed bool
 | 
			
		||||
// var storageProviders = make(map[string]StorageConstructor)
 | 
			
		||||
 | 
			
		||||
	// DefaultCAUrl is the default URL to the CA's ACME directory endpoint.
 | 
			
		||||
	// It's very important to set this unless you set it in every Config.
 | 
			
		||||
	DefaultCAUrl string
 | 
			
		||||
 | 
			
		||||
	// DefaultKeyType is used as the type of key for new certificates
 | 
			
		||||
	// when no other key type is specified.
 | 
			
		||||
	DefaultKeyType = acme.RSA2048
 | 
			
		||||
 | 
			
		||||
	// DisableHTTPChallenge will disable all HTTP challenges.
 | 
			
		||||
	DisableHTTPChallenge bool
 | 
			
		||||
 | 
			
		||||
	// DisableTLSALPNChallenge will disable all TLS-ALPN challenges.
 | 
			
		||||
	DisableTLSALPNChallenge bool
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var storageProviders = make(map[string]StorageConstructor)
 | 
			
		||||
 | 
			
		||||
// RegisterStorageProvider registers provider by name for storing tls data
 | 
			
		||||
func RegisterStorageProvider(name string, provider StorageConstructor) {
 | 
			
		||||
	storageProviders[name] = provider
 | 
			
		||||
	caddy.RegisterPlugin("tls.storage."+name, caddy.Plugin{})
 | 
			
		||||
}
 | 
			
		||||
// // RegisterStorageProvider registers provider by name for storing tls data
 | 
			
		||||
// func RegisterStorageProvider(name string, provider StorageConstructor) {
 | 
			
		||||
// 	storageProviders[name] = provider
 | 
			
		||||
// 	caddy.RegisterPlugin("tls.storage."+name, caddy.Plugin{})
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
@ -15,49 +15,11 @@
 | 
			
		||||
package caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/xenolf/lego/acme"
 | 
			
		||||
	"github.com/mholt/certmagic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestHostQualifies(t *testing.T) {
 | 
			
		||||
	for i, test := range []struct {
 | 
			
		||||
		host   string
 | 
			
		||||
		expect bool
 | 
			
		||||
	}{
 | 
			
		||||
		{"example.com", true},
 | 
			
		||||
		{"sub.example.com", true},
 | 
			
		||||
		{"Sub.Example.COM", true},
 | 
			
		||||
		{"127.0.0.1", false},
 | 
			
		||||
		{"127.0.1.5", false},
 | 
			
		||||
		{"69.123.43.94", false},
 | 
			
		||||
		{"::1", false},
 | 
			
		||||
		{"::", false},
 | 
			
		||||
		{"0.0.0.0", false},
 | 
			
		||||
		{"", false},
 | 
			
		||||
		{" ", false},
 | 
			
		||||
		{"*.example.com", true},
 | 
			
		||||
		{"*.*.example.com", false},
 | 
			
		||||
		{"sub.*.example.com", false},
 | 
			
		||||
		{"*sub.example.com", false},
 | 
			
		||||
		{".com", false},
 | 
			
		||||
		{"example.com.", false},
 | 
			
		||||
		{"localhost", false},
 | 
			
		||||
		{"local", true},
 | 
			
		||||
		{"devsite", true},
 | 
			
		||||
		{"192.168.1.3", false},
 | 
			
		||||
		{"10.0.2.1", false},
 | 
			
		||||
		{"169.112.53.4", false},
 | 
			
		||||
	} {
 | 
			
		||||
		actual := HostQualifies(test.host)
 | 
			
		||||
		if actual != test.expect {
 | 
			
		||||
			t.Errorf("Test %d: Expected HostQualifies(%s)=%v, but got %v",
 | 
			
		||||
				i, test.host, test.expect, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type holder struct {
 | 
			
		||||
	host, port string
 | 
			
		||||
	cfg        *Config
 | 
			
		||||
@ -79,17 +41,17 @@ func TestQualifiesForManagedTLS(t *testing.T) {
 | 
			
		||||
		{holder{host: "", cfg: new(Config)}, false},
 | 
			
		||||
		{holder{host: "localhost", cfg: new(Config)}, false},
 | 
			
		||||
		{holder{host: "123.44.3.21", cfg: new(Config)}, false},
 | 
			
		||||
		{holder{host: "example.com", cfg: new(Config)}, true},
 | 
			
		||||
		{holder{host: "*.example.com", cfg: new(Config)}, true},
 | 
			
		||||
		{holder{host: "example.com", cfg: &Config{Manager: &certmagic.Config{}}}, true},
 | 
			
		||||
		{holder{host: "*.example.com", cfg: &Config{Manager: &certmagic.Config{}}}, true},
 | 
			
		||||
		{holder{host: "*.*.example.com", cfg: new(Config)}, false},
 | 
			
		||||
		{holder{host: "*sub.example.com", cfg: new(Config)}, false},
 | 
			
		||||
		{holder{host: "sub.*.example.com", cfg: new(Config)}, false},
 | 
			
		||||
		{holder{host: "example.com", cfg: &Config{Manual: true}}, false},
 | 
			
		||||
		{holder{host: "example.com", cfg: &Config{ACMEEmail: "off"}}, false},
 | 
			
		||||
		{holder{host: "example.com", cfg: &Config{ACMEEmail: "foo@bar.com"}}, true},
 | 
			
		||||
		{holder{host: "example.com", cfg: &Config{Manager: &certmagic.Config{}, Manual: true}}, false},
 | 
			
		||||
		{holder{host: "example.com", cfg: &Config{Manager: &certmagic.Config{}, ACMEEmail: "off"}}, false},
 | 
			
		||||
		{holder{host: "example.com", cfg: &Config{Manager: &certmagic.Config{}, ACMEEmail: "foo@bar.com"}}, true},
 | 
			
		||||
		{holder{host: "example.com", port: "80"}, false},
 | 
			
		||||
		{holder{host: "example.com", port: "1234", cfg: new(Config)}, true},
 | 
			
		||||
		{holder{host: "example.com", port: "443", cfg: new(Config)}, true},
 | 
			
		||||
		{holder{host: "example.com", port: "1234", cfg: &Config{Manager: &certmagic.Config{}}}, true},
 | 
			
		||||
		{holder{host: "example.com", port: "443", cfg: &Config{Manager: &certmagic.Config{}}}, true},
 | 
			
		||||
		{holder{host: "example.com", port: "80"}, false},
 | 
			
		||||
	} {
 | 
			
		||||
		if got, want := QualifiesForManagedTLS(test.cfg), test.expect; got != want {
 | 
			
		||||
@ -97,88 +59,3 @@ func TestQualifiesForManagedTLS(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSaveCertResource(t *testing.T) {
 | 
			
		||||
	storage := &FileStorage{Path: "./le_test_save"}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		err := os.RemoveAll(storage.Path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatalf("Could not remove temporary storage directory (%s): %v", storage.Path, err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	domain := "example.com"
 | 
			
		||||
	certContents := "certificate"
 | 
			
		||||
	keyContents := "private key"
 | 
			
		||||
	metaContents := `{
 | 
			
		||||
	"domain": "example.com",
 | 
			
		||||
	"certUrl": "https://example.com/cert",
 | 
			
		||||
	"certStableUrl": "https://example.com/cert/stable"
 | 
			
		||||
}`
 | 
			
		||||
 | 
			
		||||
	cert := &acme.CertificateResource{
 | 
			
		||||
		Domain:        domain,
 | 
			
		||||
		CertURL:       "https://example.com/cert",
 | 
			
		||||
		CertStableURL: "https://example.com/cert/stable",
 | 
			
		||||
		PrivateKey:    []byte(keyContents),
 | 
			
		||||
		Certificate:   []byte(certContents),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := saveCertResource(storage, cert)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Expected no error, got: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	siteData, err := storage.LoadSite(domain)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Expected no error reading site, got: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if string(siteData.Cert) != certContents {
 | 
			
		||||
		t.Errorf("Expected certificate file to contain '%s', got '%s'", certContents, string(siteData.Cert))
 | 
			
		||||
	}
 | 
			
		||||
	if string(siteData.Key) != keyContents {
 | 
			
		||||
		t.Errorf("Expected private key file to contain '%s', got '%s'", keyContents, string(siteData.Key))
 | 
			
		||||
	}
 | 
			
		||||
	if string(siteData.Meta) != metaContents {
 | 
			
		||||
		t.Errorf("Expected meta file to contain '%s', got '%s'", metaContents, string(siteData.Meta))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExistingCertAndKey(t *testing.T) {
 | 
			
		||||
	storage := &FileStorage{Path: "./le_test_existing"}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		err := os.RemoveAll(storage.Path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatalf("Could not remove temporary storage directory (%s): %v", storage.Path, err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	domain := "example.com"
 | 
			
		||||
 | 
			
		||||
	siteExists, err := storage.SiteExists(domain)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Could not determine whether site exists: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if siteExists {
 | 
			
		||||
		t.Errorf("Did NOT expect %v to have existing cert or key, but it did", domain)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = saveCertResource(storage, &acme.CertificateResource{
 | 
			
		||||
		Domain:      domain,
 | 
			
		||||
		PrivateKey:  []byte("key"),
 | 
			
		||||
		Certificate: []byte("cert"),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Expected no error, got: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	siteExists, err = storage.SiteExists(domain)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Could not determine whether site exists: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !siteExists {
 | 
			
		||||
		t.Errorf("Expected %v to have existing cert and key, but it did NOT", domain)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										233
									
								
								caddytls/user.go
									
									
									
									
									
								
							
							
						
						
									
										233
									
								
								caddytls/user.go
									
									
									
									
									
								
							@ -1,233 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"crypto"
 | 
			
		||||
	"crypto/ecdsa"
 | 
			
		||||
	"crypto/elliptic"
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/xenolf/lego/acme"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// User represents a Let's Encrypt user account.
 | 
			
		||||
type User struct {
 | 
			
		||||
	Email        string
 | 
			
		||||
	Registration *acme.RegistrationResource
 | 
			
		||||
	key          crypto.PrivateKey
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetEmail gets u's email.
 | 
			
		||||
func (u User) GetEmail() string {
 | 
			
		||||
	return u.Email
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetRegistration gets u's registration resource.
 | 
			
		||||
func (u User) GetRegistration() *acme.RegistrationResource {
 | 
			
		||||
	return u.Registration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPrivateKey gets u's private key.
 | 
			
		||||
func (u User) GetPrivateKey() crypto.PrivateKey {
 | 
			
		||||
	return u.key
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// newUser creates a new User for the given email address
 | 
			
		||||
// with a new private key. This function does NOT save the
 | 
			
		||||
// user to disk or register it via ACME. If you want to use
 | 
			
		||||
// a user account that might already exist, call getUser
 | 
			
		||||
// instead. It does NOT prompt the user.
 | 
			
		||||
func newUser(email string) (User, error) {
 | 
			
		||||
	user := User{Email: email}
 | 
			
		||||
	privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return user, errors.New("error generating private key: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	user.key = privateKey
 | 
			
		||||
	return user, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getEmail does everything it can to obtain an email address
 | 
			
		||||
// from the user within the scope of memory and storage to use
 | 
			
		||||
// for ACME TLS. If it cannot get an email address, it returns
 | 
			
		||||
// empty string. (If user is present, it will warn the user of
 | 
			
		||||
// the consequences of an empty email.) This function MAY prompt
 | 
			
		||||
// the user for input. If userPresent is false, the operator
 | 
			
		||||
// will NOT be prompted and an empty email may be returned.
 | 
			
		||||
// If the user is prompted, a new User will be created and
 | 
			
		||||
// stored in storage according to the email address they
 | 
			
		||||
// provided (which might be blank).
 | 
			
		||||
func getEmail(cfg *Config, userPresent bool) (string, error) {
 | 
			
		||||
	storage, err := cfg.StorageFor(cfg.CAUrl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// First try memory (command line flag or typed by user previously)
 | 
			
		||||
	leEmail := DefaultEmail
 | 
			
		||||
 | 
			
		||||
	// Then try to get most recent user email from storage
 | 
			
		||||
	if leEmail == "" {
 | 
			
		||||
		leEmail = storage.MostRecentUserEmail()
 | 
			
		||||
		DefaultEmail = leEmail // save for next time
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Looks like there is no email address readily available,
 | 
			
		||||
	// so we will have to ask the user if we can.
 | 
			
		||||
	if leEmail == "" && userPresent {
 | 
			
		||||
		// evidently, no User data was present in storage;
 | 
			
		||||
		// thus we must make a new User so that we can get
 | 
			
		||||
		// the Terms of Service URL via our ACME client, phew!
 | 
			
		||||
		user, err := newUser("")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// get the agreement URL
 | 
			
		||||
		agreementURL := agreementTestURL
 | 
			
		||||
		if agreementURL == "" {
 | 
			
		||||
			// we call acme.NewClient directly because newACMEClient
 | 
			
		||||
			// would require that we already know the user's email
 | 
			
		||||
			caURL := DefaultCAUrl
 | 
			
		||||
			if cfg.CAUrl != "" {
 | 
			
		||||
				caURL = cfg.CAUrl
 | 
			
		||||
			}
 | 
			
		||||
			tempClient, err := acme.NewClient(caURL, user, "")
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", fmt.Errorf("making ACME client to get ToS URL: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			agreementURL = tempClient.GetToSURL()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// prompt the user for an email address and terms agreement
 | 
			
		||||
		reader := bufio.NewReader(stdin)
 | 
			
		||||
		promptUserAgreement(agreementURL)
 | 
			
		||||
		fmt.Println("Please enter your email address to signify agreement and to be notified")
 | 
			
		||||
		fmt.Println("in case of issues. You can leave it blank, but we don't recommend it.")
 | 
			
		||||
		fmt.Print("  Email address: ")
 | 
			
		||||
		leEmail, err = reader.ReadString('\n')
 | 
			
		||||
		if err != nil && err != io.EOF {
 | 
			
		||||
			return "", fmt.Errorf("reading email address: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		leEmail = strings.TrimSpace(leEmail)
 | 
			
		||||
		DefaultEmail = leEmail
 | 
			
		||||
		Agreed = true
 | 
			
		||||
 | 
			
		||||
		// save the new user to preserve this for next time
 | 
			
		||||
		user.Email = leEmail
 | 
			
		||||
		err = saveUser(storage, user)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// lower-casing the email is important for consistency
 | 
			
		||||
	return strings.ToLower(leEmail), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getUser loads the user with the given email from disk
 | 
			
		||||
// using the provided storage. If the user does not exist,
 | 
			
		||||
// it will create a new one, but it does NOT save new
 | 
			
		||||
// users to the disk or register them via ACME. It does
 | 
			
		||||
// NOT prompt the user.
 | 
			
		||||
func getUser(storage Storage, email string) (User, error) {
 | 
			
		||||
	var user User
 | 
			
		||||
 | 
			
		||||
	// open user reg
 | 
			
		||||
	userData, err := storage.LoadUser(email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if _, ok := err.(ErrNotExist); ok {
 | 
			
		||||
			// create a new user
 | 
			
		||||
			return newUser(email)
 | 
			
		||||
		}
 | 
			
		||||
		return user, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// load user information
 | 
			
		||||
	err = json.Unmarshal(userData.Reg, &user)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return user, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// load their private key
 | 
			
		||||
	user.key, err = loadPrivateKey(userData.Key)
 | 
			
		||||
	return user, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// saveUser persists a user's key and account registration
 | 
			
		||||
// to the file system. It does NOT register the user via ACME
 | 
			
		||||
// or prompt the user. You must also pass in the storage
 | 
			
		||||
// wherein the user should be saved. It should be the storage
 | 
			
		||||
// for the CA with which user has an account.
 | 
			
		||||
func saveUser(storage Storage, user User) error {
 | 
			
		||||
	// Save the private key and registration
 | 
			
		||||
	userData := new(UserData)
 | 
			
		||||
	var err error
 | 
			
		||||
	userData.Key, err = savePrivateKey(user.key)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		userData.Reg, err = json.MarshalIndent(&user, "", "\t")
 | 
			
		||||
	}
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		err = storage.StoreUser(user.Email, userData)
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// promptUserAgreement simply outputs the standard user
 | 
			
		||||
// agreement prompt with the given agreement URL.
 | 
			
		||||
// It outputs a newline after the message.
 | 
			
		||||
func promptUserAgreement(agreementURL string) {
 | 
			
		||||
	const userAgreementPrompt = `Your sites will be served over HTTPS automatically using Let's Encrypt.
 | 
			
		||||
By continuing, you agree to the Let's Encrypt Subscriber Agreement at:`
 | 
			
		||||
	fmt.Printf("\n\n%s\n  %s\n", userAgreementPrompt, agreementURL)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// askUserAgreement prompts the user to agree to the agreement
 | 
			
		||||
// at the given agreement URL via stdin. It returns whether the
 | 
			
		||||
// user agreed or not.
 | 
			
		||||
func askUserAgreement(agreementURL string) bool {
 | 
			
		||||
	promptUserAgreement(agreementURL)
 | 
			
		||||
	fmt.Print("Do you agree to the terms? (y/n): ")
 | 
			
		||||
 | 
			
		||||
	reader := bufio.NewReader(stdin)
 | 
			
		||||
	answer, err := reader.ReadString('\n')
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	answer = strings.ToLower(strings.TrimSpace(answer))
 | 
			
		||||
 | 
			
		||||
	return answer == "y" || answer == "yes"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// agreementTestURL is set during tests to skip requiring
 | 
			
		||||
// setting up an entire ACME CA endpoint.
 | 
			
		||||
var agreementTestURL string
 | 
			
		||||
 | 
			
		||||
// stdin is used to read the user's input if prompted;
 | 
			
		||||
// this is changed by tests during tests.
 | 
			
		||||
var stdin = io.ReadWriter(os.Stdin)
 | 
			
		||||
 | 
			
		||||
// The name of the folder for accounts where the email
 | 
			
		||||
// address was not provided; default 'username' if you will,
 | 
			
		||||
// but only for local/storage use, not with the CA.
 | 
			
		||||
const emptyEmail = "default"
 | 
			
		||||
@ -1,221 +0,0 @@
 | 
			
		||||
// Copyright 2015 Light Code Labs, LLC
 | 
			
		||||
//
 | 
			
		||||
// 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 caddytls
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/ecdsa"
 | 
			
		||||
	"crypto/elliptic"
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"io"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/xenolf/lego/acme"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestUser(t *testing.T) {
 | 
			
		||||
	defer testStorage.clean()
 | 
			
		||||
 | 
			
		||||
	privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Could not generate test private key: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	u := User{
 | 
			
		||||
		Email:        "me@mine.com",
 | 
			
		||||
		Registration: new(acme.RegistrationResource),
 | 
			
		||||
		key:          privateKey,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if expected, actual := "me@mine.com", u.GetEmail(); actual != expected {
 | 
			
		||||
		t.Errorf("Expected email '%s' but got '%s'", expected, actual)
 | 
			
		||||
	}
 | 
			
		||||
	if u.GetRegistration() == nil {
 | 
			
		||||
		t.Error("Expected a registration resource, but got nil")
 | 
			
		||||
	}
 | 
			
		||||
	if expected, actual := privateKey, u.GetPrivateKey(); actual != expected {
 | 
			
		||||
		t.Errorf("Expected the private key at address %p but got one at %p instead ", expected, actual)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewUser(t *testing.T) {
 | 
			
		||||
	email := "me@foobar.com"
 | 
			
		||||
	user, err := newUser(email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Error creating user: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if user.key == nil {
 | 
			
		||||
		t.Error("Private key is nil")
 | 
			
		||||
	}
 | 
			
		||||
	if user.Email != email {
 | 
			
		||||
		t.Errorf("Expected email to be %s, but was %s", email, user.Email)
 | 
			
		||||
	}
 | 
			
		||||
	if user.Registration != nil {
 | 
			
		||||
		t.Error("New user already has a registration resource; it shouldn't")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSaveUser(t *testing.T) {
 | 
			
		||||
	defer testStorage.clean()
 | 
			
		||||
 | 
			
		||||
	email := "me@foobar.com"
 | 
			
		||||
	user, err := newUser(email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Error creating user: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = saveUser(testStorage, user)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Error saving user: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	_, err = testStorage.LoadUser(email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Cannot access user data, error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetUserDoesNotAlreadyExist(t *testing.T) {
 | 
			
		||||
	defer testStorage.clean()
 | 
			
		||||
 | 
			
		||||
	user, err := getUser(testStorage, "user_does_not_exist@foobar.com")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Error getting user: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if user.key == nil {
 | 
			
		||||
		t.Error("Expected user to have a private key, but it was nil")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetUserAlreadyExists(t *testing.T) {
 | 
			
		||||
	defer testStorage.clean()
 | 
			
		||||
 | 
			
		||||
	email := "me@foobar.com"
 | 
			
		||||
 | 
			
		||||
	// Set up test
 | 
			
		||||
	user, err := newUser(email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Error creating user: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	err = saveUser(testStorage, user)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Error saving user: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Expect to load user from disk
 | 
			
		||||
	user2, err := getUser(testStorage, email)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Error getting user: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Assert keys are the same
 | 
			
		||||
	if !PrivateKeysSame(user.key, user2.key) {
 | 
			
		||||
		t.Error("Expected private key to be the same after loading, but it wasn't")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Assert emails are the same
 | 
			
		||||
	if user.Email != user2.Email {
 | 
			
		||||
		t.Errorf("Expected emails to be equal, but was '%s' before and '%s' after loading", user.Email, user2.Email)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetEmail(t *testing.T) {
 | 
			
		||||
	// ensure storage (via StorageFor) uses the local testdata folder that we delete later
 | 
			
		||||
	origCaddypath := os.Getenv("CADDYPATH")
 | 
			
		||||
	os.Setenv("CADDYPATH", "./testdata")
 | 
			
		||||
	defer os.Setenv("CADDYPATH", origCaddypath)
 | 
			
		||||
 | 
			
		||||
	agreementTestURL = "(none - testing)"
 | 
			
		||||
	defer func() { agreementTestURL = "" }()
 | 
			
		||||
 | 
			
		||||
	// let's not clutter up the output
 | 
			
		||||
	origStdout := os.Stdout
 | 
			
		||||
	os.Stdout = nil
 | 
			
		||||
	defer func() { os.Stdout = origStdout }()
 | 
			
		||||
 | 
			
		||||
	defer testStorage.clean()
 | 
			
		||||
	DefaultEmail = "test2@foo.com"
 | 
			
		||||
 | 
			
		||||
	// Test1: Use default email from flag (or user previously typing it)
 | 
			
		||||
	actual, err := getEmail(testConfig, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("getEmail (1) error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if actual != DefaultEmail {
 | 
			
		||||
		t.Errorf("Did not get correct email from memory; expected '%s' but got '%s'", DefaultEmail, actual)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Test2: Get input from user
 | 
			
		||||
	DefaultEmail = ""
 | 
			
		||||
	stdin = new(bytes.Buffer)
 | 
			
		||||
	_, err = io.Copy(stdin, strings.NewReader("test3@foo.com\n"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Could not simulate user input, error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	actual, err = getEmail(testConfig, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("getEmail (2) error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if actual != "test3@foo.com" {
 | 
			
		||||
		t.Errorf("Did not get correct email from user input prompt; expected '%s' but got '%s'", "test3@foo.com", actual)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Test3: Get most recent email from before (in storage)
 | 
			
		||||
	DefaultEmail = ""
 | 
			
		||||
	for i, eml := range []string{
 | 
			
		||||
		"test4-1@foo.com",
 | 
			
		||||
		"test4-2@foo.com",
 | 
			
		||||
		"TEST4-3@foo.com", // test case insensitivity
 | 
			
		||||
	} {
 | 
			
		||||
		u, err := newUser(eml)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatalf("Error creating user %d: %v", i, err)
 | 
			
		||||
		}
 | 
			
		||||
		err = saveUser(testStorage, u)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatalf("Error saving user %d: %v", i, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Change modified time so they're all different and the test becomes more deterministic
 | 
			
		||||
		f, err := os.Stat(testStorage.user(eml))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatalf("Could not access user folder for '%s': %v", eml, err)
 | 
			
		||||
		}
 | 
			
		||||
		chTime := f.ModTime().Add(time.Duration(i) * time.Hour) // 1 second isn't always enough space!
 | 
			
		||||
		if err := os.Chtimes(testStorage.user(eml), chTime, chTime); err != nil {
 | 
			
		||||
			t.Fatalf("Could not change user folder mod time for '%s': %v", eml, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	actual, err = getEmail(testConfig, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("getEmail (3) error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if actual != "test4-3@foo.com" {
 | 
			
		||||
		t.Errorf("Did not get correct email from storage; expected '%s' but got '%s'", "test4-3@foo.com", actual)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	testStorageBase = "./testdata" // ephemeral folder that gets deleted after tests finish
 | 
			
		||||
	testCAHost      = "localhost"
 | 
			
		||||
	testConfig      = &Config{CAUrl: "http://" + testCAHost + "/directory", StorageProvider: "file"}
 | 
			
		||||
	testStorage     = &FileStorage{Path: filepath.Join(testStorageBase, "acme", testCAHost)}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (s *FileStorage) clean() error { return os.RemoveAll(testStorageBase) }
 | 
			
		||||
@ -71,37 +71,37 @@ func (c *Controller) ServerType() string {
 | 
			
		||||
// OnFirstStartup adds fn to the list of callback functions to execute
 | 
			
		||||
// when the server is about to be started NOT as part of a restart.
 | 
			
		||||
func (c *Controller) OnFirstStartup(fn func() error) {
 | 
			
		||||
	c.instance.onFirstStartup = append(c.instance.onFirstStartup, fn)
 | 
			
		||||
	c.instance.OnFirstStartup = append(c.instance.OnFirstStartup, fn)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnStartup adds fn to the list of callback functions to execute
 | 
			
		||||
// when the server is about to be started (including restarts).
 | 
			
		||||
func (c *Controller) OnStartup(fn func() error) {
 | 
			
		||||
	c.instance.onStartup = append(c.instance.onStartup, fn)
 | 
			
		||||
	c.instance.OnStartup = append(c.instance.OnStartup, fn)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnRestart adds fn to the list of callback functions to execute
 | 
			
		||||
// when the server is about to be restarted.
 | 
			
		||||
func (c *Controller) OnRestart(fn func() error) {
 | 
			
		||||
	c.instance.onRestart = append(c.instance.onRestart, fn)
 | 
			
		||||
	c.instance.OnRestart = append(c.instance.OnRestart, fn)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnRestartFailed adds fn to the list of callback functions to execute
 | 
			
		||||
// if the server failed to restart.
 | 
			
		||||
func (c *Controller) OnRestartFailed(fn func() error) {
 | 
			
		||||
	c.instance.onRestartFailed = append(c.instance.onRestartFailed, fn)
 | 
			
		||||
	c.instance.OnRestartFailed = append(c.instance.OnRestartFailed, fn)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnShutdown adds fn to the list of callback functions to execute
 | 
			
		||||
// when the server is about to be shut down (including restarts).
 | 
			
		||||
func (c *Controller) OnShutdown(fn func() error) {
 | 
			
		||||
	c.instance.onShutdown = append(c.instance.onShutdown, fn)
 | 
			
		||||
	c.instance.OnShutdown = append(c.instance.OnShutdown, fn)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnFinalShutdown adds fn to the list of callback functions to execute
 | 
			
		||||
// when the server is about to be shut down NOT as part of a restart.
 | 
			
		||||
func (c *Controller) OnFinalShutdown(fn func() error) {
 | 
			
		||||
	c.instance.onFinalShutdown = append(c.instance.onFinalShutdown, fn)
 | 
			
		||||
	c.instance.OnFinalShutdown = append(c.instance.OnFinalShutdown, fn)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Context gets the context associated with the instance associated with c.
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user