pomerium/internal/cryptutil/tls.go
Bobby DeSimone bf9a6f5e97
cryptutil: add automatic certificate management (#644)
Signed-off-by: Bobby DeSimone <bobbydesimone@gmail.com>
2020-05-05 12:50:19 -07:00

103 lines
3.6 KiB
Go

package cryptutil
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"github.com/caddyserver/certmagic"
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
)
// NewAutocert automatically retrieves public certificates from the free
// certificate authority Let's Encrypt using HTTP-01 and TLS-ALPN-01 challenges.
// To complete the challenges, the server must be accessible from the internet
// by port 80 or 443 .
//
// https://letsencrypt.org/docs/challenge-types/#http-01-challenge
// https://letsencrypt.org/docs/challenge-types/#tls-alpn-01
func NewAutocert(tlsConfig *tls.Config, hostnames []string, useStaging bool, path string) (*tls.Config, func(h http.Handler) http.Handler, error) {
certmagic.DefaultACME.Agreed = true
if useStaging {
certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA
}
cm := certmagic.NewDefault()
tlsConfig = newTLSConfigIfEmpty(tlsConfig)
// add existing certs to the cache, and staple OCSP
for _, cert := range tlsConfig.Certificates {
if err := cm.CacheUnmanagedTLSCertificate(cert, nil); err != nil {
return nil, nil, fmt.Errorf("cryptutil: failed caching cert: %w", err)
}
}
cm.Storage = &certmagic.FileStorage{Path: path}
acmeConfig := certmagic.NewACMEManager(cm, certmagic.DefaultACME)
cm.Issuer = acmeConfig
// todo(bdd) : add cancellation context?
if err := cm.ManageAsync(context.TODO(), hostnames); err != nil {
return nil, nil, fmt.Errorf("cryptutil: sync failed: %w", err)
}
tlsConfig.GetCertificate = cm.GetCertificate
tlsConfig.NextProtos = append(tlsConfig.NextProtos, tlsalpn01.ACMETLS1Protocol)
tlsConfig.BuildNameToCertificate()
return tlsConfig, acmeConfig.HTTPChallengeHandler, nil
}
// TLSConfigFromBase64 returns an tls configuration from a base64 encoded blob.
func TLSConfigFromBase64(tlsConfig *tls.Config, cert, key string) (*tls.Config, error) {
tlsConfig = newTLSConfigIfEmpty(tlsConfig)
c, err := CertifcateFromBase64(cert, key)
if err != nil {
return nil, err
}
tlsConfig.Certificates = append(tlsConfig.Certificates, *c)
tlsConfig.BuildNameToCertificate()
return tlsConfig, nil
}
// TLSConfigFromFile returns an tls configuration from a certificate and
// key file .
func TLSConfigFromFile(tlsConfig *tls.Config, cert, key string) (*tls.Config, error) {
tlsConfig = newTLSConfigIfEmpty(tlsConfig)
c, err := CertificateFromFile(cert, key)
if err != nil {
return nil, err
}
tlsConfig.Certificates = append(tlsConfig.Certificates, *c)
tlsConfig.BuildNameToCertificate()
return tlsConfig, nil
}
// newTLSConfigIfEmpty returns an opinionated TLS configuration if config is nil.
// See :
// https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations
// https://blog.cloudflare.com/exposing-go-on-the-internet/
// https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices
// https://github.com/golang/go/blob/df91b8044dbe790c69c16058330f545be069cc1f/src/crypto/tls/common.go#L919
func newTLSConfigIfEmpty(tlsConfig *tls.Config) *tls.Config {
if tlsConfig != nil {
return tlsConfig
}
return &tls.Config{
MinVersion: tls.VersionTLS12,
// Prioritize cipher suites sped up by AES-NI (AES-GCM)
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},
PreferServerCipherSuites: true,
// Use curves which have assembly implementations
CurvePreferences: []tls.CurveID{
tls.X25519,
tls.CurveP256,
},
// HTTP/2 must be enabled manually when using http.Serve
NextProtos: []string{"h2", "http/1.1"},
}
}