summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/caddyserver/certmagic/certificates.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/caddyserver/certmagic/certificates.go')
-rw-r--r--vendor/github.com/caddyserver/certmagic/certificates.go404
1 files changed, 404 insertions, 0 deletions
diff --git a/vendor/github.com/caddyserver/certmagic/certificates.go b/vendor/github.com/caddyserver/certmagic/certificates.go
new file mode 100644
index 0000000000..ebdb61832d
--- /dev/null
+++ b/vendor/github.com/caddyserver/certmagic/certificates.go
@@ -0,0 +1,404 @@
+// Copyright 2015 Matthew Holt
+//
+// 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 certmagic
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "strings"
+ "time"
+
+ "go.uber.org/zap"
+ "golang.org/x/crypto/ocsp"
+)
+
+// 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,
+// but at the cost of slightly higher memory use.
+type Certificate struct {
+ tls.Certificate
+
+ // Names is the list of subject names this
+ // certificate is signed for.
+ Names []string
+
+ // Optional; user-provided, and arbitrary.
+ Tags []string
+
+ // OCSP contains the certificate's parsed OCSP response.
+ ocsp *ocsp.Response
+
+ // The hex-encoded hash of this cert's chain's bytes.
+ hash string
+
+ // Whether this certificate is under our management
+ managed bool
+}
+
+// NeedsRenewal returns true if the certificate is
+// expiring soon (according to cfg) or has expired.
+func (cert Certificate) NeedsRenewal(cfg *Config) bool {
+ return currentlyInRenewalWindow(cert.Leaf.NotBefore, cert.Leaf.NotAfter, cfg.RenewalWindowRatio)
+}
+
+// Expired returns true if the certificate has expired.
+func (cert Certificate) Expired() bool {
+ if cert.Leaf == nil {
+ // ideally cert.Leaf would never be nil, but this can happen for
+ // "synthetic" certs like those made to solve the TLS-ALPN challenge
+ // which adds a special cert directly to the cache, since
+ // tls.X509KeyPair() discards the leaf; oh well
+ return false
+ }
+ return time.Now().After(cert.Leaf.NotAfter)
+}
+
+// currentlyInRenewalWindow returns true if the current time is
+// within the renewal window, according to the given start/end
+// dates and the ratio of the renewal window. If true is returned,
+// the certificate being considered is due for renewal.
+func currentlyInRenewalWindow(notBefore, notAfter time.Time, renewalWindowRatio float64) bool {
+ if notAfter.IsZero() {
+ return false
+ }
+ lifetime := notAfter.Sub(notBefore)
+ if renewalWindowRatio == 0 {
+ renewalWindowRatio = DefaultRenewalWindowRatio
+ }
+ renewalWindow := time.Duration(float64(lifetime) * renewalWindowRatio)
+ renewalWindowStart := notAfter.Add(-renewalWindow)
+ return time.Now().After(renewalWindowStart)
+}
+
+// HasTag returns true if cert.Tags has tag.
+func (cert Certificate) HasTag(tag string) bool {
+ for _, t := range cert.Tags {
+ if t == tag {
+ return true
+ }
+ }
+ return false
+}
+
+// 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 is a lower-level method; normally you'll call Manage() instead.
+//
+// This method is safe for concurrent use.
+func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) {
+ cert, err := cfg.loadManagedCertificate(domain)
+ if err != nil {
+ return cert, err
+ }
+ cfg.certCache.cacheCertificate(cert)
+ cfg.emit("cached_managed_cert", cert.Names)
+ return cert, nil
+}
+
+// loadManagedCertificate loads the managed certificate for domain,
+// but it does not add it to the cache. It just loads from storage.
+func (cfg *Config) loadManagedCertificate(domain string) (Certificate, error) {
+ certRes, err := cfg.loadCertResource(domain)
+ if err != nil {
+ return Certificate{}, err
+ }
+ cert, err := cfg.makeCertificateWithOCSP(certRes.CertificatePEM, certRes.PrivateKeyPEM)
+ if err != nil {
+ return cert, err
+ }
+ cert.managed = true
+ return 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 method is safe for concurrent use.
+func (cfg *Config) CacheUnmanagedCertificatePEMFile(certFile, keyFile string, tags []string) error {
+ cert, err := cfg.makeCertificateFromDiskWithOCSP(cfg.Storage, certFile, keyFile)
+ if err != nil {
+ return err
+ }
+ cert.Tags = tags
+ cfg.certCache.cacheCertificate(cert)
+ cfg.emit("cached_unmanaged_cert", cert.Names)
+ return nil
+}
+
+// CacheUnmanagedTLSCertificate adds tlsCert to the certificate cache.
+// It staples OCSP if possible.
+//
+// This method is safe for concurrent use.
+func (cfg *Config) CacheUnmanagedTLSCertificate(tlsCert tls.Certificate, tags []string) error {
+ var cert Certificate
+ err := fillCertFromLeaf(&cert, tlsCert)
+ if err != nil {
+ return err
+ }
+ _, err = stapleOCSP(cfg.Storage, &cert, nil)
+ if err != nil && cfg.Logger != nil {
+ cfg.Logger.Warn("stapling OCSP", zap.Error(err))
+ }
+ cfg.emit("cached_unmanaged_cert", cert.Names)
+ cert.Tags = tags
+ cfg.certCache.cacheCertificate(cert)
+ return nil
+}
+
+// CacheUnmanagedCertificatePEMBytes makes a certificate out of the PEM bytes
+// of the certificate and key, then caches it in memory.
+//
+// This method is safe for concurrent use.
+func (cfg *Config) CacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte, tags []string) error {
+ cert, err := cfg.makeCertificateWithOCSP(certBytes, keyBytes)
+ if err != nil {
+ return err
+ }
+ cert.Tags = tags
+ cfg.certCache.cacheCertificate(cert)
+ cfg.emit("cached_unmanaged_cert", cert.Names)
+ 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 (cfg Config) makeCertificateFromDiskWithOCSP(storage Storage, 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 cfg.makeCertificateWithOCSP(certPEMBlock, keyPEMBlock)
+}
+
+// makeCertificateWithOCSP is the same as makeCertificate except that it also
+// staples OCSP to the certificate.
+func (cfg Config) makeCertificateWithOCSP(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
+ cert, err := makeCertificate(certPEMBlock, keyPEMBlock)
+ if err != nil {
+ return cert, err
+ }
+ _, err = stapleOCSP(cfg.Storage, &cert, certPEMBlock)
+ if err != nil && cfg.Logger != nil {
+ cfg.Logger.Warn("stapling OCSP", zap.Error(err))
+ }
+ return cert, nil
+}
+
+// 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
+}
+
+// fillCertFromLeaf populates cert from tlsCert. If it succeeds, it
+// guarantees that cert.Leaf is non-nil.
+func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error {
+ if len(tlsCert.Certificate) == 0 {
+ return fmt.Errorf("certificate is empty")
+ }
+ cert.Certificate = tlsCert
+
+ // the leaf cert should be the one for the site; we must set
+ // the tls.Certificate.Leaf field so that TLS handshakes are
+ // more efficient
+ leaf, err := x509.ParseCertificate(tlsCert.Certificate[0])
+ if err != nil {
+ return err
+ }
+ cert.Certificate.Leaf = leaf
+
+ // for convenience, we do want to assemble all the
+ // subjects on the certificate into one list
+ 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))
+ }
+ }
+ for _, u := range leaf.URIs {
+ if u.String() != leaf.Subject.CommonName { // TODO: CommonName is deprecated
+ cert.Names = append(cert.Names, u.String())
+ }
+ }
+ if len(cert.Names) == 0 {
+ return fmt.Errorf("certificate has no names")
+ }
+
+ // save the hash of this certificate (chain) and
+ // expiration date, for necessity and efficiency
+ cert.hash = hashCertificateChain(cert.Certificate.Certificate)
+
+ return 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 (cfg *Config) managedCertInStorageExpiresSoon(cert Certificate) (bool, error) {
+ certRes, err := cfg.loadCertResource(cert.Names[0])
+ if err != nil {
+ return false, err
+ }
+ tlsCert, err := tls.X509KeyPair(certRes.CertificatePEM, certRes.PrivateKeyPEM)
+ if err != nil {
+ return false, err
+ }
+ leaf, err := x509.ParseCertificate(tlsCert.Certificate[0])
+ if err != nil {
+ return false, err
+ }
+ return currentlyInRenewalWindow(leaf.NotBefore, leaf.NotAfter, cfg.RenewalWindowRatio), 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. It assumes that the new certificate for oldCert.Names[0] is
+// already in storage.
+func (cfg *Config) reloadManagedCertificate(oldCert Certificate) error {
+ if cfg.Logger != nil {
+ cfg.Logger.Info("reloading managed certificate", zap.Strings("identifiers", oldCert.Names))
+ }
+ newCert, err := cfg.loadManagedCertificate(oldCert.Names[0])
+ if err != nil {
+ return fmt.Errorf("loading managed certificate for %v from storage: %v", oldCert.Names, err)
+ }
+ cfg.certCache.replaceCertificate(oldCert, newCert)
+ return nil
+}
+
+// SubjectQualifiesForCert returns true if subj is a name which,
+// as a quick sanity check, looks like it could be the subject
+// of a certificate. Requirements are:
+// - must not be empty
+// - must not start or end with a dot (RFC 1034)
+// - must not contain common accidental special characters
+func SubjectQualifiesForCert(subj string) bool {
+ // must not be empty
+ return strings.TrimSpace(subj) != "" &&
+
+ // must not start or end with a dot
+ !strings.HasPrefix(subj, ".") &&
+ !strings.HasSuffix(subj, ".") &&
+
+ // if it has a wildcard, must be a left-most label
+ (!strings.Contains(subj, "*") || strings.HasPrefix(subj, "*.")) &&
+
+ // must not contain other common special characters
+ !strings.ContainsAny(subj, "()[]{}<> \t\n\"\\!@#$%^&|;'+=")
+}
+
+// SubjectQualifiesForPublicCert returns true if the subject
+// name appears eligible for automagic TLS with a public
+// CA such as Let's Encrypt. For example: localhost and IP
+// addresses are not eligible because we cannot obtain certs
+// for those names with a public CA. 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 SubjectQualifiesForPublicCert(subj string) bool {
+ // must at least qualify for certificate
+ return SubjectQualifiesForCert(subj) &&
+
+ // localhost is ineligible
+ subj != "localhost" &&
+
+ // .localhost TLD is ineligible
+ !strings.HasSuffix(subj, ".localhost") &&
+
+ // .local TLD is ineligible
+ !strings.HasSuffix(subj, ".local") &&
+
+ // only one wildcard label allowed, and it must be left-most
+ (!strings.Contains(subj, "*") ||
+ (strings.Count(subj, "*") == 1 &&
+ len(subj) > 2 &&
+ strings.HasPrefix(subj, "*."))) &&
+
+ // cannot be an IP address (as of yet), see
+ // https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt
+ net.ParseIP(subj) == nil
+}
+
+// MatchWildcard returns true if subject (a candidate DNS name)
+// matches wildcard (a reference DNS name), mostly according to
+// RFC6125-compliant wildcard rules.
+func MatchWildcard(subject, wildcard string) bool {
+ if subject == wildcard {
+ return true
+ }
+ if !strings.Contains(wildcard, "*") {
+ return false
+ }
+ labels := strings.Split(subject, ".")
+ for i := range labels {
+ if labels[i] == "" {
+ continue // invalid label
+ }
+ labels[i] = "*"
+ candidate := strings.Join(labels, ".")
+ if candidate == wildcard {
+ return true
+ }
+ }
+ return false
+}