// Package cert provides facilities for automatically updating in-memory // TLS certificates whenever the corresponding files change on disk. It // will generate an in-memory self-signed certificate if none is found on // disk. // // It is used as follows: // // certificate := cert.New("cert.pem", "key.pem") // // s := http.Server{ // Addr: ":8443", // TLSConfig: &tls.Config{ // GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { // return certificate.Get() // }, // }, // } // // err := s.ListenAndServeTLS("", "") // package cert import ( "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "errors" "math/big" "os" "sync" "sync/atomic" "time" ) type certInfo struct { certificate *tls.Certificate certTime, keyTime time.Time } // Type Certificate represents a TLS certificate loaded from on-disk files // whose value will change transparently to the application when the // on-disk files are modified. type Certificate struct { certFile, keyFile string mu sync.Mutex // protects against simultaneous generation info atomic.Value // of type certInfo } var errOnlyOne = errors.New("only one of cert.pem and key.pem exists") // generate generates a self-signed certficate func generate() (tls.Certificate, error) { priv, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return tls.Certificate{}, err } serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) now := time.Now() template := x509.Certificate{ SerialNumber: serialNumber, NotBefore: now, NotAfter: now.Add(365 * 24 * time.Hour), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } bytes, err := x509.CreateCertificate( rand.Reader, &template, &template, &priv.PublicKey, priv, ) if err != nil { return tls.Certificate{}, err } return tls.Certificate{ Certificate: [][]byte{bytes}, PrivateKey: priv, }, nil } func modTime(filename string) time.Time { fi, err := os.Stat(filename) if err != nil { return time.Time{} } return fi.ModTime() } // load returns the current certificate if it is still valid, nil otherwise. func (cert *Certificate) load(certTime, keyTime time.Time) *certInfo { info, ok := cert.info.Load().(*certInfo) if !ok { return nil } if !info.certTime.Equal(certTime) || !info.keyTime.Equal(keyTime) { return nil } return info } // store returns the current certificate if it is still valid, and either // reads or generates a new one otherwise. func (cert *Certificate) store(certTime, keyTime time.Time) (*certInfo, error) { cert.mu.Lock() defer cert.mu.Unlock() // the certificate may have been updated since we checked info := cert.load(certTime, keyTime) if info != nil { return info, nil } var certificate tls.Certificate var err error nocert := certTime.Equal(time.Time{}) nokey := keyTime.Equal(time.Time{}) if nocert != nokey { return nil, errOnlyOne } else if nokey { certificate, err = generate() if err != nil { return nil, err } } else { certificate, err = tls.LoadX509KeyPair(cert.certFile, cert.keyFile) if err != nil { return nil, err } } info = &certInfo{ certificate: &certificate, certTime: certTime, keyTime: keyTime, } cert.info.Store(info) return info, nil } // Get returns the tls.Certificate corresponding to cert. // If both the key file and the cert file indicated by cert exist and are // readable, Get returns a certificate built from these files. If neither // exists, it returns an autogenerated self-signed certificate. In other // situations, it returns an error. // Get caches its results, and is therefore fast enough to be called on // each HTTPS request. func (cert *Certificate) Get() (*tls.Certificate, error) { certTime := modTime(cert.certFile) keyTime := modTime(cert.keyFile) info := cert.load(certTime, keyTime) if info == nil { var err error info, err = cert.store(certTime, keyTime) if info == nil || err != nil { return nil, err } } return info.certificate, nil } // New creates a new dynamic certificate. The parameters keyFile and // certFile are like those of tls.LoadX509KeyPair. func New(certFile, keyFile string) *Certificate { return &Certificate{ certFile: certFile, keyFile: keyFile, } }