Files
kubernetes/vendor/github.com/cloudflare/cfssl/bundler/bundler.go
2018-08-08 21:01:29 -07:00

844 lines
26 KiB
Go

// 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
opts options
}
type options struct {
keyUsages []x509.ExtKeyUsage
}
var defaultOptions = options{
keyUsages: []x509.ExtKeyUsage{
x509.ExtKeyUsageAny,
},
}
// An Option sets options such as allowed key usages, etc.
type Option func(*options)
// WithKeyUsages lets you set which Extended Key Usage values are acceptable. By
// default x509.ExtKeyUsageAny will be used.
func WithKeyUsages(usages ...x509.ExtKeyUsage) Option {
return func(o *options) {
o.keyUsages = usages
}
}
// 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, opt ...Option) (*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, opt...)
}
// 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, opt ...Option) (*Bundler, error) {
opts := defaultOptions
for _, o := range opt {
o(&opts)
}
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(),
opts: opts,
}
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: b.opts.keyUsages,
}
}
// 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")
searchErr := b.fetchIntermediates(certs)
if searchErr != nil {
log.Debugf("search failed: %v", searchErr)
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)
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)
bundle.Expires = helpers.ExpiryTime(bundle.Chain)
bundle.LeafExpires = bundle.Chain[0].NotAfter
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
}