// 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"
	"log"
	"net"
	"strings"
	"time"

	"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.
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

	// whether this certificate is under our management
	managed bool
}

// NeedsRenewal returns true if the certificate is
// expiring soon or has expired.
func (c Certificate) NeedsRenewal() bool {
	if c.NotAfter.IsZero() {
		return false
	}
	timeLeft := c.NotAfter.UTC().Sub(time.Now().UTC())
	renewDurationBefore := DefaultRenewDurationBefore
	if len(c.configs) > 0 && c.configs[0].RenewDurationBefore > 0 {
		renewDurationBefore = c.configs[0].RenewDurationBefore
	}
	return timeLeft < renewDurationBefore
}

// 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) {
	certRes, err := cfg.loadCertResource(domain)
	if err != nil {
		return Certificate{}, err
	}
	cert, err := cfg.makeCertificateWithOCSP(certRes.Certificate, certRes.PrivateKey)
	if err != nil {
		return cert, err
	}
	cert.managed = true
	if cfg.OnEvent != nil {
		cfg.OnEvent("cached_managed_cert", cert.Names)
	}
	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 method is safe for concurrent use.
func (cfg *Config) CacheUnmanagedCertificatePEMFile(certFile, keyFile string) error {
	cert, err := cfg.makeCertificateFromDiskWithOCSP(certFile, keyFile)
	if err != nil {
		return err
	}
	cfg.cacheCertificate(cert)
	if cfg.OnEvent != nil {
		cfg.OnEvent("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) error {
	var cert Certificate
	err := fillCertFromLeaf(&cert, tlsCert)
	if err != nil {
		return err
	}
	err = cfg.certCache.stapleOCSP(&cert, nil)
	if err != nil {
		log.Printf("[WARNING] Stapling OCSP: %v", err)
	}
	if cfg.OnEvent != nil {
		cfg.OnEvent("cached_unmanaged_cert", cert.Names)
	}
	cfg.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) error {
	cert, err := cfg.makeCertificateWithOCSP(certBytes, keyBytes)
	if err != nil {
		return err
	}
	cfg.cacheCertificate(cert)
	if cfg.OnEvent != nil {
		cfg.OnEvent("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(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)
}

// 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 (*Config) 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 (cfg *Config) makeCertificateWithOCSP(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
	cert, err := cfg.makeCertificate(certPEMBlock, keyPEMBlock)
	if err != nil {
		return cert, err
	}
	err = cfg.certCache.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 fmt.Errorf("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 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)
	cert.NotAfter = leaf.NotAfter

	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 managedCertInStorageExpiresSoon(cert Certificate) (bool, error) {
	if len(cert.configs) == 0 {
		return false, fmt.Errorf("no configs for certificate")
	}
	cfg := cert.configs[0]
	certRes, err := cfg.loadCertResource(cert.Names[0])
	if err != nil {
		return false, err
	}
	tlsCert, err := tls.X509KeyPair(certRes.Certificate, certRes.PrivateKey)
	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 < cfg.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.mu.Lock()
	defer cfg.certCache.mu.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
}

// HostQualifies returns true if the hostname alone
// appears eligible for automagic TLS. 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
}
