// Package bundler implements certificate bundling functionality for
// CFSSL.
package bundler

import (
	"bytes"
	"crypto"
	"crypto/ecdsa"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/pem"
	goerr "errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/cloudflare/cfssl/errors"
	"github.com/cloudflare/cfssl/helpers"
	"github.com/cloudflare/cfssl/log"
	"github.com/cloudflare/cfssl/ubiquity"
)

// IntermediateStash contains the path to the directory where
// downloaded intermediates should be saved.
// When unspecified, downloaded intermediates are not saved.
var IntermediateStash string

// BundleFlavor is named optimization strategy on certificate chain selection when bundling.
type BundleFlavor string

const (
	// Optimal means the shortest chain with newest intermediates and
	// the most advanced crypto.
	Optimal BundleFlavor = "optimal"

	// Ubiquitous is aimed to provide the chain which is accepted
	// by the most platforms.
	Ubiquitous BundleFlavor = "ubiquitous"

	// Force means the bundler only verfiies the input as a valid bundle, not optimization is done.
	Force BundleFlavor = "force"
)

const (
	sha2Warning          = "The bundle contains certificates signed with advanced hash functions such as SHA2, which are problematic for certain operating systems, e.g. Windows XP SP2."
	ecdsaWarning         = "The bundle contains ECDSA signatures, which are problematic for certain operating systems, e.g. Windows XP, Android 2.2 and Android 2.3."
	expiringWarningStub  = "The bundle is expiring within 30 days."
	untrustedWarningStub = "The bundle may not be trusted by the following platform(s):"
	ubiquityWarning      = "Unable to measure bundle ubiquity: No platform metadata present."
)

// A Bundler contains the certificate pools for producing certificate
// bundles. It contains any intermediates and root certificates that
// should be used.
type Bundler struct {
	RootPool         *x509.CertPool
	IntermediatePool *x509.CertPool
	KnownIssuers     map[string]bool
}

// NewBundler creates a new Bundler from the files passed in; these
// files should contain a list of valid root certificates and a list
// of valid intermediate certificates, respectively.
func NewBundler(caBundleFile, intBundleFile string) (*Bundler, error) {
	var caBundle, intBundle []byte
	var err error

	if caBundleFile != "" {
		log.Debug("Loading CA bundle: ", caBundleFile)
		caBundle, err = ioutil.ReadFile(caBundleFile)
		if err != nil {
			log.Errorf("root bundle failed to load: %v", err)
			return nil, errors.Wrap(errors.RootError, errors.ReadFailed, err)
		}
	}

	if intBundleFile != "" {
		log.Debug("Loading Intermediate bundle: ", intBundleFile)
		intBundle, err = ioutil.ReadFile(intBundleFile)
		if err != nil {
			log.Errorf("intermediate bundle failed to load: %v", err)
			return nil, errors.Wrap(errors.IntermediatesError, errors.ReadFailed, err)
		}
	}

	if IntermediateStash != "" {
		if _, err = os.Stat(IntermediateStash); err != nil && os.IsNotExist(err) {
			log.Infof("intermediate stash directory %s doesn't exist, creating", IntermediateStash)
			err = os.MkdirAll(IntermediateStash, 0755)
			if err != nil {
				log.Errorf("failed to create intermediate stash directory %s: %v",
					IntermediateStash, err)
				return nil, err
			}
			log.Infof("intermediate stash directory %s created", IntermediateStash)
		}
	}

	return NewBundlerFromPEM(caBundle, intBundle)

}

// NewBundlerFromPEM creates a new Bundler from PEM-encoded root certificates and
// intermediate certificates.
// If caBundlePEM is nil, the resulting Bundler can only do "Force" bundle.
func NewBundlerFromPEM(caBundlePEM, intBundlePEM []byte) (*Bundler, error) {
	log.Debug("parsing root certificates from PEM")
	roots, err := helpers.ParseCertificatesPEM(caBundlePEM)
	if err != nil {
		log.Errorf("failed to parse root bundle: %v", err)
		return nil, errors.New(errors.RootError, errors.ParseFailed)
	}

	log.Debug("parse intermediate certificates from PEM")
	intermediates, err := helpers.ParseCertificatesPEM(intBundlePEM)
	if err != nil {
		log.Errorf("failed to parse intermediate bundle: %v", err)
		return nil, errors.New(errors.IntermediatesError, errors.ParseFailed)
	}

	b := &Bundler{
		KnownIssuers:     map[string]bool{},
		IntermediatePool: x509.NewCertPool(),
	}

	log.Debug("building certificate pools")

	// RootPool will be nil if caBundlePEM is nil, also
	// that translates to caBundleFile is "".
	// Systems root store will be used.
	if caBundlePEM != nil {
		b.RootPool = x509.NewCertPool()
	}

	for _, c := range roots {
		b.RootPool.AddCert(c)
		b.KnownIssuers[string(c.Signature)] = true
	}

	for _, c := range intermediates {
		b.IntermediatePool.AddCert(c)
		b.KnownIssuers[string(c.Signature)] = true
	}

	log.Debug("bundler set up")
	return b, nil
}

// VerifyOptions generates an x509 VerifyOptions structure that can be
// used for verifying certificates.
func (b *Bundler) VerifyOptions() x509.VerifyOptions {
	return x509.VerifyOptions{
		Roots:         b.RootPool,
		Intermediates: b.IntermediatePool,
		KeyUsages: []x509.ExtKeyUsage{
			x509.ExtKeyUsageServerAuth,
			x509.ExtKeyUsageMicrosoftServerGatedCrypto,
			x509.ExtKeyUsageNetscapeServerGatedCrypto,
		},
	}
}

// BundleFromFile takes a set of files containing the PEM-encoded leaf certificate
// (optionally along with some intermediate certs), the PEM-encoded private key
// and returns the bundle built from that key and the certificate(s).
func (b *Bundler) BundleFromFile(bundleFile, keyFile string, flavor BundleFlavor, password string) (*Bundle, error) {
	log.Debug("Loading Certificate: ", bundleFile)
	certsRaw, err := ioutil.ReadFile(bundleFile)
	if err != nil {
		return nil, errors.Wrap(errors.CertificateError, errors.ReadFailed, err)
	}

	var keyPEM []byte
	// Load private key PEM only if a file is given
	if keyFile != "" {
		log.Debug("Loading private key: ", keyFile)
		keyPEM, err = ioutil.ReadFile(keyFile)
		if err != nil {
			log.Debugf("failed to read private key: ", err)
			return nil, errors.Wrap(errors.PrivateKeyError, errors.ReadFailed, err)
		}
		if len(keyPEM) == 0 {
			log.Debug("key is empty")
			return nil, errors.Wrap(errors.PrivateKeyError, errors.DecodeFailed, err)
		}
	}

	return b.BundleFromPEMorDER(certsRaw, keyPEM, flavor, password)
}

// BundleFromPEMorDER builds a certificate bundle from the set of byte
// slices containing the PEM or DER-encoded certificate(s), private key.
func (b *Bundler) BundleFromPEMorDER(certsRaw, keyPEM []byte, flavor BundleFlavor, password string) (*Bundle, error) {
	log.Debug("bundling from PEM files")
	var key crypto.Signer
	var err error
	if len(keyPEM) != 0 {
		key, err = helpers.ParsePrivateKeyPEM(keyPEM)
		if err != nil {
			log.Debugf("failed to parse private key: %v", err)
			return nil, err
		}
	}

	certs, err := helpers.ParseCertificatesPEM(certsRaw)
	if err != nil {
		// If PEM doesn't work try DER
		var keyDER crypto.Signer
		var errDER error
		certs, keyDER, errDER = helpers.ParseCertificatesDER(certsRaw, password)
		// Only use DER key if no key read from file
		if key == nil && keyDER != nil {
			key = keyDER
		}
		if errDER != nil {
			log.Debugf("failed to parse certificates: %v", err)
			// If neither parser works pass along PEM error
			return nil, err
		}

	}
	if len(certs) == 0 {
		log.Debugf("no certificates found")
		return nil, errors.New(errors.CertificateError, errors.DecodeFailed)
	}

	log.Debugf("bundle ready")
	return b.Bundle(certs, key, flavor)
}

// BundleFromRemote fetches the certificate served by the server at
// serverName (or ip, if the ip argument is not the empty string). It
// is expected that the method will be able to make a connection at
// port 443. The certificate used by the server in this connection is
// used to build the bundle, which will necessarily be keyless.
func (b *Bundler) BundleFromRemote(serverName, ip string, flavor BundleFlavor) (*Bundle, error) {
	config := &tls.Config{
		RootCAs:    b.RootPool,
		ServerName: serverName,
	}

	// Dial by IP if present
	var dialName string
	if ip != "" {
		dialName = ip + ":443"
	} else {
		dialName = serverName + ":443"
	}

	log.Debugf("bundling from remote %s", dialName)

	dialer := &net.Dialer{Timeout: time.Duration(5) * time.Second}
	conn, err := tls.DialWithDialer(dialer, "tcp", dialName, config)
	var dialError string
	// If there's an error in tls.Dial, try again with
	// InsecureSkipVerify to fetch the remote bundle to (re-)bundle
	// with. If the bundle is indeed not usable (expired, mismatched
	// hostnames, etc.), report the error.  Otherwise, create a
	// working bundle and insert the tls error in the bundle.Status.
	if err != nil {
		log.Debugf("dial failed: %v", err)
		// record the error msg
		dialError = fmt.Sprintf("Failed rigid TLS handshake with %s: %v", dialName, err)
		// dial again with InsecureSkipVerify
		log.Debugf("try again with InsecureSkipVerify.")
		config.InsecureSkipVerify = true
		conn, err = tls.DialWithDialer(dialer, "tcp", dialName, config)
		if err != nil {
			log.Debugf("dial with InsecureSkipVerify failed: %v", err)
			return nil, errors.Wrap(errors.DialError, errors.Unknown, err)
		}
	}

	connState := conn.ConnectionState()

	certs := connState.PeerCertificates

	err = conn.VerifyHostname(serverName)
	if err != nil {
		log.Debugf("failed to verify hostname: %v", err)
		return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
	}

	// Bundle with remote certs. Inject the initial dial error, if any, to the status reporting.
	bundle, err := b.Bundle(certs, nil, flavor)
	if err != nil {
		return nil, err
	} else if dialError != "" {
		bundle.Status.Messages = append(bundle.Status.Messages, dialError)
	}
	return bundle, err
}

type fetchedIntermediate struct {
	Cert *x509.Certificate
	Name string
}

// fetchRemoteCertificate retrieves a single URL pointing to a certificate
// and attempts to first parse it as a DER-encoded certificate; if
// this fails, it attempts to decode it as a PEM-encoded certificate.
func fetchRemoteCertificate(certURL string) (fi *fetchedIntermediate, err error) {
	log.Debugf("fetching remote certificate: %s", certURL)
	var resp *http.Response
	resp, err = http.Get(certURL)
	if err != nil {
		log.Debugf("failed HTTP get: %v", err)
		return
	}

	defer resp.Body.Close()
	var certData []byte
	certData, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Debugf("failed to read response body: %v", err)
		return
	}

	log.Debugf("attempting to parse certificate as DER")
	crt, err := x509.ParseCertificate(certData)
	if err != nil {
		log.Debugf("attempting to parse certificate as PEM")
		crt, err = helpers.ParseCertificatePEM(certData)
		if err != nil {
			log.Debugf("failed to parse certificate: %v", err)
			return
		}
	}

	log.Debugf("certificate fetch succeeds")
	fi = &fetchedIntermediate{Cert: crt, Name: constructCertFileName(crt)}
	return
}

func reverse(certs []*x509.Certificate) []*x509.Certificate {
	n := len(certs)
	if n == 0 {
		return certs
	}
	rcerts := []*x509.Certificate{}
	for i := n - 1; i >= 0; i-- {
		rcerts = append(rcerts, certs[i])
	}
	return rcerts
}

// Check if the certs form a partial cert chain: every cert verifies
// the signature of the one in front of it.
func partialVerify(certs []*x509.Certificate) bool {
	n := len(certs)
	if n == 0 {
		return false
	}
	for i := 0; i < n-1; i++ {
		if certs[i].CheckSignatureFrom(certs[i+1]) != nil {
			return false
		}
	}
	return true
}

func isSelfSigned(cert *x509.Certificate) bool {
	return cert.CheckSignatureFrom(cert) == nil
}

func isChainRootNode(cert *x509.Certificate) bool {
	if isSelfSigned(cert) {
		return true
	}
	return false
}

func (b *Bundler) verifyChain(chain []*fetchedIntermediate) bool {
	// This process will verify if the root of the (partial) chain is in our root pool,
	// and will fail otherwise.
	log.Debugf("verifying chain")
	for vchain := chain[:]; len(vchain) > 0; vchain = vchain[1:] {
		cert := vchain[0]
		// If this is a certificate in one of the pools, skip it.
		if b.KnownIssuers[string(cert.Cert.Signature)] {
			log.Debugf("certificate is known")
			continue
		}

		_, err := cert.Cert.Verify(b.VerifyOptions())
		if err != nil {
			log.Debugf("certificate failed verification: %v", err)
			return false
		} else if len(chain) == len(vchain) && isChainRootNode(cert.Cert) {
			// The first certificate in the chain is a root; it shouldn't be stored.
			log.Debug("looking at root certificate, will not store")
			continue
		}

		// leaf cert has an empty name, don't store leaf cert.
		if cert.Name == "" {
			continue
		}

		log.Debug("add certificate to intermediate pool:", cert.Name)
		b.IntermediatePool.AddCert(cert.Cert)
		b.KnownIssuers[string(cert.Cert.Signature)] = true

		if IntermediateStash != "" {
			fileName := filepath.Join(IntermediateStash, cert.Name)

			var block = pem.Block{Type: "CERTIFICATE", Bytes: cert.Cert.Raw}

			log.Debugf("write intermediate to stash directory: %s", fileName)
			// If the write fails, verification should not fail.
			err = ioutil.WriteFile(fileName, pem.EncodeToMemory(&block), 0644)
			if err != nil {
				log.Errorf("failed to write new intermediate: %v", err)
			} else {
				log.Info("stashed new intermediate ", cert.Name)
			}
		}
	}
	return true
}

// constructCertFileName returns a uniquely identifying file name for a certificate
func constructCertFileName(cert *x509.Certificate) string {
	// construct the filename as the CN with no period and space
	name := strings.Replace(cert.Subject.CommonName, ".", "", -1)
	name = strings.Replace(name, " ", "", -1)

	// add SKI and serial number as extra identifier
	name += fmt.Sprintf("_%x", cert.SubjectKeyId)
	name += fmt.Sprintf("_%x", cert.SerialNumber.Bytes())

	name += ".crt"
	return name
}

// fetchIntermediates goes through each of the URLs in the AIA "Issuing
// CA" extensions and fetches those certificates. If those
// certificates are not present in either the root pool or
// intermediate pool, the certificate is saved to file and added to
// the list of intermediates to be used for verification. This will
// not add any new certificates to the root pool; if the ultimate
// issuer is not trusted, fetching the certicate here will not change
// that.
func (b *Bundler) fetchIntermediates(certs []*x509.Certificate) (err error) {
	if IntermediateStash != "" {
		log.Debugf("searching intermediates")
		if _, err := os.Stat(IntermediateStash); err != nil && os.IsNotExist(err) {
			log.Infof("intermediate stash directory %s doesn't exist, creating", IntermediateStash)
			err = os.MkdirAll(IntermediateStash, 0755)
			if err != nil {
				log.Errorf("failed to create intermediate stash directory %s: %v", IntermediateStash, err)
				return err
			}
			log.Infof("intermediate stash directory %s created", IntermediateStash)
		}
	}
	// stores URLs and certificate signatures that have been seen
	seen := map[string]bool{}
	var foundChains int

	// Construct a verify chain as a reversed partial bundle,
	// such that the certs are ordered by promxity to the root CAs.
	var chain []*fetchedIntermediate
	for i, cert := range certs {
		var name string

		// Only construct filenames for non-leaf intermediate certs
		// so they will be saved to disk if necessary.
		// Leaf cert gets a empty name and will be skipped.
		if i > 0 {
			name = constructCertFileName(cert)
		}

		chain = append([]*fetchedIntermediate{{cert, name}}, chain...)
		seen[string(cert.Signature)] = true
	}

	// Verify the chain and store valid intermediates in the chain.
	// If it doesn't verify, fetch the intermediates and extend the chain
	// in a DFS manner and verify each time we hit a root.
	for {
		if len(chain) == 0 {
			log.Debugf("search complete")
			if foundChains == 0 {
				return x509.UnknownAuthorityError{}
			}
			return nil
		}

		current := chain[0]
		var advanced bool
		if b.verifyChain(chain) {
			foundChains++
		}
		log.Debugf("walk AIA issuers")
		for _, url := range current.Cert.IssuingCertificateURL {
			if seen[url] {
				log.Debugf("url %s has been seen", url)
				continue
			}
			crt, err := fetchRemoteCertificate(url)
			if err != nil {
				continue
			} else if seen[string(crt.Cert.Signature)] {
				log.Debugf("fetched certificate is known")
				continue
			}
			seen[url] = true
			seen[string(crt.Cert.Signature)] = true
			chain = append([]*fetchedIntermediate{crt}, chain...)
			advanced = true
			break
		}

		if !advanced {
			log.Debugf("didn't advance, stepping back")
			chain = chain[1:]
		}
	}
}

// Bundle takes an X509 certificate (already in the
// Certificate structure), a private key as crypto.Signer in one of the appropriate
// formats (i.e. *rsa.PrivateKey or *ecdsa.PrivateKey, or even a opaque key), using them to
// build a certificate bundle.
func (b *Bundler) Bundle(certs []*x509.Certificate, key crypto.Signer, flavor BundleFlavor) (*Bundle, error) {
	log.Infof("bundling certificate for %+v", certs[0].Subject)
	if len(certs) == 0 {
		return nil, nil
	}

	// Detect reverse ordering of the cert chain.
	if len(certs) > 1 && !partialVerify(certs) {
		rcerts := reverse(certs)
		if partialVerify(rcerts) {
			certs = rcerts
		}
	}

	var ok bool
	cert := certs[0]
	if key != nil {
		switch {
		case cert.PublicKeyAlgorithm == x509.RSA:

			var rsaPublicKey *rsa.PublicKey
			if rsaPublicKey, ok = key.Public().(*rsa.PublicKey); !ok {
				return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
			}
			if cert.PublicKey.(*rsa.PublicKey).N.Cmp(rsaPublicKey.N) != 0 {
				return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
			}
		case cert.PublicKeyAlgorithm == x509.ECDSA:
			var ecdsaPublicKey *ecdsa.PublicKey
			if ecdsaPublicKey, ok = key.Public().(*ecdsa.PublicKey); !ok {
				return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
			}
			if cert.PublicKey.(*ecdsa.PublicKey).X.Cmp(ecdsaPublicKey.X) != 0 {
				return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
			}
		default:
			return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECC)
		}
	} else {
		switch {
		case cert.PublicKeyAlgorithm == x509.RSA:
		case cert.PublicKeyAlgorithm == x509.ECDSA:
		default:
			return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECC)
		}
	}

	bundle := new(Bundle)
	bundle.Cert = cert
	bundle.Key = key
	bundle.Issuer = &cert.Issuer
	bundle.Subject = &cert.Subject

	bundle.buildHostnames()

	if flavor == Force {
		// force bundle checks the certificates
		// forms a verification chain.
		if !partialVerify(certs) {
			return nil,
				errors.Wrap(errors.CertificateError, errors.VerifyFailed,
					goerr.New("Unable to verify the certificate chain"))
		}
		bundle.Chain = certs
	} else {
		// disallow self-signed cert
		if cert.CheckSignatureFrom(cert) == nil {
			return nil, errors.New(errors.CertificateError, errors.SelfSigned)
		}

		chains, err := cert.Verify(b.VerifyOptions())
		if err != nil {
			log.Debugf("verification failed: %v", err)
			// If the error was an unknown authority, try to fetch
			// the intermediate specified in the AIA and add it to
			// the intermediates bundle.
			if _, ok := err.(x509.UnknownAuthorityError); !ok {
				return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
			}

			log.Debugf("searching for intermediates via AIA issuer")
			err = b.fetchIntermediates(certs)
			if err != nil {
				log.Debugf("search failed: %v", err)
				return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
			}

			log.Debugf("verifying new chain")
			chains, err = cert.Verify(b.VerifyOptions())
			if err != nil {
				log.Debugf("failed to verify chain: %v", err)
				return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
			}
			log.Debugf("verify ok")
		}
		var matchingChains [][]*x509.Certificate
		switch flavor {
		case Optimal:
			matchingChains = optimalChains(chains)
		case Ubiquitous:
			if len(ubiquity.Platforms) == 0 {
				log.Warning("No metadata, Ubiquitous falls back to Optimal.")
			}
			matchingChains = ubiquitousChains(chains)
		default:
			matchingChains = ubiquitousChains(chains)
		}

		bundle.Chain = matchingChains[0]
	}

	statusCode := int(errors.Success)
	var messages []string
	// Check if bundle is expiring.
	expiringCerts := checkExpiringCerts(bundle.Chain)
	bundle.Expires = helpers.ExpiryTime(bundle.Chain)
	if len(expiringCerts) > 0 {
		statusCode |= errors.BundleExpiringBit
		messages = append(messages, expirationWarning(expiringCerts))
	}
	// Check if bundle contains SHA2 certs.
	if ubiquity.ChainHashUbiquity(bundle.Chain) <= ubiquity.SHA2Ubiquity {
		statusCode |= errors.BundleNotUbiquitousBit
		messages = append(messages, sha2Warning)
	}
	// Check if bundle contains ECDSA signatures.
	if ubiquity.ChainKeyAlgoUbiquity(bundle.Chain) <= ubiquity.ECDSA256Ubiquity {
		statusCode |= errors.BundleNotUbiquitousBit
		messages = append(messages, ecdsaWarning)
	}

	// when forcing a bundle, bundle ubiquity doesn't matter
	// also we don't retrieve the anchoring root of the bundle
	var untrusted []string
	if flavor != Force {
		// Add root store presence info
		root := bundle.Chain[len(bundle.Chain)-1]
		bundle.Root = root
		log.Infof("the anchoring root is %v", root.Subject)
		// Check if there is any platform that doesn't trust the chain.
		// Also, an warning will be generated if ubiquity.Platforms is nil,
		untrusted = ubiquity.UntrustedPlatforms(root)
		untrustedMsg := untrustedPlatformsWarning(untrusted)
		if len(untrustedMsg) > 0 {
			log.Debug("Populate untrusted platform warning.")
			statusCode |= errors.BundleNotUbiquitousBit
			messages = append(messages, untrustedMsg)
		}
	}

	// Check if there is any platform that rejects the chain because of SHA1 deprecation.
	sha1Msgs := ubiquity.SHA1DeprecationMessages(bundle.Chain)
	if len(sha1Msgs) > 0 {
		log.Debug("Populate SHA1 deprecation warning.")
		statusCode |= errors.BundleNotUbiquitousBit
		messages = append(messages, sha1Msgs...)
	}

	bundle.Status = &BundleStatus{ExpiringSKIs: getSKIs(bundle.Chain, expiringCerts), Code: statusCode, Messages: messages, Untrusted: untrusted}

	// attempt to not to include the root certificate for optimization
	if flavor != Force {
		// Include at least one intermediate if the leaf has enabled OCSP and is not CA.
		if bundle.Cert.OCSPServer != nil && !bundle.Cert.IsCA && len(bundle.Chain) <= 2 {
			// No op. Return one intermediate if there is one.
		} else {
			// do not include the root.
			bundle.Chain = bundle.Chain[:len(bundle.Chain)-1]
		}
	}

	bundle.Status.IsRebundled = diff(bundle.Chain, certs)

	log.Debugf("bundle complete")
	return bundle, nil
}

// checkExpiringCerts returns indices of certs that are expiring within 30 days.
func checkExpiringCerts(chain []*x509.Certificate) (expiringIntermediates []int) {
	now := time.Now()
	for i, cert := range chain {
		if cert.NotAfter.Sub(now).Hours() < 720 {
			expiringIntermediates = append(expiringIntermediates, i)
		}
	}
	return
}

// getSKIs returns a list of cert subject key id  in the bundle chain with matched indices.
func getSKIs(chain []*x509.Certificate, indices []int) (skis []string) {
	for _, index := range indices {
		ski := fmt.Sprintf("%X", chain[index].SubjectKeyId)
		skis = append(skis, ski)
	}
	return
}

// expirationWarning generates a warning message with expiring certs.
func expirationWarning(expiringIntermediates []int) (ret string) {
	if len(expiringIntermediates) == 0 {
		return
	}

	ret = expiringWarningStub
	if len(expiringIntermediates) > 1 {
		ret = ret + "The expiring certs are"
	} else {
		ret = ret + "The expiring cert is"
	}
	for _, index := range expiringIntermediates {
		ret = ret + " #" + strconv.Itoa(index+1)
	}
	ret = ret + " in the chain."
	return
}

// untrustedPlatformsWarning generates a warning message with untrusted platform names.
func untrustedPlatformsWarning(platforms []string) string {
	if len(ubiquity.Platforms) == 0 {
		return ubiquityWarning
	}

	if len(platforms) == 0 {
		return ""
	}

	msg := untrustedWarningStub
	for i, platform := range platforms {
		if i > 0 {
			msg += ","
		}
		msg += " " + platform
	}
	msg += "."
	return msg
}

// Optimal chains are the shortest chains, with newest intermediates and most advanced crypto suite being the tie breaker.
func optimalChains(chains [][]*x509.Certificate) [][]*x509.Certificate {
	// Find shortest chains
	chains = ubiquity.Filter(chains, ubiquity.CompareChainLength)
	// Find the chains with longest expiry.
	chains = ubiquity.Filter(chains, ubiquity.CompareChainExpiry)
	// Find the chains with more advanced crypto suite
	chains = ubiquity.Filter(chains, ubiquity.CompareChainCryptoSuite)

	return chains
}

// Ubiquitous chains are the chains with highest platform coverage and break ties with the optimal strategy.
func ubiquitousChains(chains [][]*x509.Certificate) [][]*x509.Certificate {
	// Filter out chains with highest cross platform ubiquity.
	chains = ubiquity.Filter(chains, ubiquity.ComparePlatformUbiquity)
	// Prefer that all intermediates are SHA-2 certs if the leaf is a SHA-2 cert, in order to improve ubiquity.
	chains = ubiquity.Filter(chains, ubiquity.CompareSHA2Homogeneity)
	// Filter shortest chains
	chains = ubiquity.Filter(chains, ubiquity.CompareChainLength)
	// Filter chains with highest signature hash ubiquity.
	chains = ubiquity.Filter(chains, ubiquity.CompareChainHashUbiquity)
	// Filter chains with highest keyAlgo ubiquity.
	chains = ubiquity.Filter(chains, ubiquity.CompareChainKeyAlgoUbiquity)
	// Filter chains with intermediates that last longer.
	chains = ubiquity.Filter(chains, ubiquity.CompareExpiryUbiquity)
	// Use the optimal strategy as final tie breaker.
	return optimalChains(chains)
}

// diff checkes if two input cert chains are not identical
func diff(chain1, chain2 []*x509.Certificate) bool {
	// Check if bundled one is different from the input.
	diff := false
	if len(chain1) != len(chain2) {
		diff = true
	} else {
		for i := 0; i < len(chain1); i++ {
			cert1 := chain1[i]
			cert2 := chain2[i]
			// Use signature to differentiate.
			if !bytes.Equal(cert1.Signature, cert2.Signature) {
				diff = true
				break
			}
		}
	}
	return diff
}
