mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-10-31 16:13:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			164 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			164 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package trustpinning
 | |
| 
 | |
| import (
 | |
| 	"crypto/x509"
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/sirupsen/logrus"
 | |
| 	"github.com/theupdateframework/notary/tuf/data"
 | |
| 	"github.com/theupdateframework/notary/tuf/utils"
 | |
| )
 | |
| 
 | |
| // TrustPinConfig represents the configuration under the trust_pinning section of the config file
 | |
| // This struct represents the preferred way to bootstrap trust for this repository
 | |
| // This is fully optional. If left at the default, uninitialized value Notary will use TOFU over
 | |
| // HTTPS.
 | |
| // You can use this to provide certificates or a CA to pin to as a root of trust for a GUN.
 | |
| // These are used with the following precedence:
 | |
| //
 | |
| // 1. Certs
 | |
| // 2. CA
 | |
| // 3. TOFUS (TOFU over HTTPS)
 | |
| //
 | |
| // Only one trust pinning option will be used to validate a particular GUN.
 | |
| type TrustPinConfig struct {
 | |
| 	// CA maps a GUN prefix to file paths containing the root CA.
 | |
| 	// This file can contain multiple root certificates, bundled in separate PEM blocks.
 | |
| 	CA map[string]string
 | |
| 	// Certs maps a GUN to a list of certificate IDs
 | |
| 	Certs map[string][]string
 | |
| 	// DisableTOFU, when true, disables "Trust On First Use" of new key data
 | |
| 	// This is false by default, which means new key data will always be trusted the first time it is seen.
 | |
| 	DisableTOFU bool
 | |
| }
 | |
| 
 | |
| type trustPinChecker struct {
 | |
| 	gun           data.GUN
 | |
| 	config        TrustPinConfig
 | |
| 	pinnedCAPool  *x509.CertPool
 | |
| 	pinnedCertIDs []string
 | |
| }
 | |
| 
 | |
| // CertChecker is a function type that will be used to check leaf certs against pinned trust
 | |
| type CertChecker func(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool
 | |
| 
 | |
| // NewTrustPinChecker returns a new certChecker function from a TrustPinConfig for a GUN
 | |
| func NewTrustPinChecker(trustPinConfig TrustPinConfig, gun data.GUN, firstBootstrap bool) (CertChecker, error) {
 | |
| 	t := trustPinChecker{gun: gun, config: trustPinConfig}
 | |
| 	// Determine the mode, and if it's even valid
 | |
| 	if pinnedCerts, ok := trustPinConfig.Certs[gun.String()]; ok {
 | |
| 		logrus.Debugf("trust-pinning using Cert IDs")
 | |
| 		t.pinnedCertIDs = pinnedCerts
 | |
| 		return t.certsCheck, nil
 | |
| 	}
 | |
| 	var ok bool
 | |
| 	t.pinnedCertIDs, ok = wildcardMatch(gun, trustPinConfig.Certs)
 | |
| 	if ok {
 | |
| 		return t.certsCheck, nil
 | |
| 	}
 | |
| 
 | |
| 	if caFilepath, err := getPinnedCAFilepathByPrefix(gun, trustPinConfig); err == nil {
 | |
| 		logrus.Debugf("trust-pinning using root CA bundle at: %s", caFilepath)
 | |
| 
 | |
| 		// Try to add the CA certs from its bundle file to our certificate store,
 | |
| 		// and use it to validate certs in the root.json later
 | |
| 		caCerts, err := utils.LoadCertBundleFromFile(caFilepath)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("could not load root cert from CA path")
 | |
| 		}
 | |
| 		// Now only consider certificates that are direct children from this CA cert chain
 | |
| 		caRootPool := x509.NewCertPool()
 | |
| 		for _, caCert := range caCerts {
 | |
| 			if err = utils.ValidateCertificate(caCert, true); err != nil {
 | |
| 				logrus.Debugf("ignoring root CA certificate with CN %s in bundle: %s", caCert.Subject.CommonName, err)
 | |
| 				continue
 | |
| 			}
 | |
| 			caRootPool.AddCert(caCert)
 | |
| 		}
 | |
| 		// If we didn't have any valid CA certs, error out
 | |
| 		if len(caRootPool.Subjects()) == 0 {
 | |
| 			return nil, fmt.Errorf("invalid CA certs provided")
 | |
| 		}
 | |
| 		t.pinnedCAPool = caRootPool
 | |
| 		return t.caCheck, nil
 | |
| 	}
 | |
| 
 | |
| 	// If TOFUs is disabled and we don't have any previous trusted root data for this GUN, we error out
 | |
| 	if trustPinConfig.DisableTOFU && firstBootstrap {
 | |
| 		return nil, fmt.Errorf("invalid trust pinning specified")
 | |
| 
 | |
| 	}
 | |
| 	return t.tofusCheck, nil
 | |
| }
 | |
| 
 | |
| func (t trustPinChecker) certsCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
 | |
| 	// reconstruct the leaf + intermediate cert chain, which is bundled as {leaf, intermediates...},
 | |
| 	// in order to get the matching id in the root file
 | |
| 	key, err := utils.CertBundleToKey(leafCert, intCerts)
 | |
| 	if err != nil {
 | |
| 		logrus.Debug("error creating cert bundle: ", err.Error())
 | |
| 		return false
 | |
| 	}
 | |
| 	return utils.StrSliceContains(t.pinnedCertIDs, key.ID())
 | |
| }
 | |
| 
 | |
| func (t trustPinChecker) caCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
 | |
| 	// Use intermediate certificates included in the root TUF metadata for our validation
 | |
| 	caIntPool := x509.NewCertPool()
 | |
| 	for _, intCert := range intCerts {
 | |
| 		caIntPool.AddCert(intCert)
 | |
| 	}
 | |
| 	// Attempt to find a valid certificate chain from the leaf cert to CA root
 | |
| 	// Use this certificate if such a valid chain exists (possibly using intermediates)
 | |
| 	var err error
 | |
| 	if _, err = leafCert.Verify(x509.VerifyOptions{Roots: t.pinnedCAPool, Intermediates: caIntPool}); err == nil {
 | |
| 		return true
 | |
| 	}
 | |
| 	logrus.Debugf("unable to find a valid certificate chain from leaf cert to CA root: %s", err)
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (t trustPinChecker) tofusCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // Will return the CA filepath corresponding to the most specific (longest) entry in the map that is still a prefix
 | |
| // of the provided gun.  Returns an error if no entry matches this GUN as a prefix.
 | |
| func getPinnedCAFilepathByPrefix(gun data.GUN, t TrustPinConfig) (string, error) {
 | |
| 	specificGUN := ""
 | |
| 	specificCAFilepath := ""
 | |
| 	foundCA := false
 | |
| 	for gunPrefix, caFilepath := range t.CA {
 | |
| 		if strings.HasPrefix(gun.String(), gunPrefix) && len(gunPrefix) >= len(specificGUN) {
 | |
| 			specificGUN = gunPrefix
 | |
| 			specificCAFilepath = caFilepath
 | |
| 			foundCA = true
 | |
| 		}
 | |
| 	}
 | |
| 	if !foundCA {
 | |
| 		return "", fmt.Errorf("could not find pinned CA for GUN: %s", gun)
 | |
| 	}
 | |
| 	return specificCAFilepath, nil
 | |
| }
 | |
| 
 | |
| // wildcardMatch will attempt to match the most specific (longest prefix) wildcarded
 | |
| // trustpinning option for key IDs. Given the simple globbing and the use of maps,
 | |
| // it is impossible to have two different prefixes of equal length.
 | |
| // This logic also solves the issue of Go's randomization of map iteration.
 | |
| func wildcardMatch(gun data.GUN, certs map[string][]string) ([]string, bool) {
 | |
| 	var (
 | |
| 		longest = ""
 | |
| 		ids     []string
 | |
| 	)
 | |
| 	for gunPrefix, keyIDs := range certs {
 | |
| 		if strings.HasSuffix(gunPrefix, "*") {
 | |
| 			if strings.HasPrefix(gun.String(), gunPrefix[:len(gunPrefix)-1]) && len(gunPrefix) > len(longest) {
 | |
| 				longest = gunPrefix
 | |
| 				ids = keyIDs
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return ids, ids != nil
 | |
| }
 | 
