...

Source file src/github.com/in-toto/in-toto-golang/in_toto/certconstraint.go

Documentation: github.com/in-toto/in-toto-golang/in_toto

     1  package in_toto
     2  
     3  import (
     4  	"crypto/x509"
     5  	"fmt"
     6  	"net/url"
     7  )
     8  
     9  const (
    10  	AllowAllConstraint = "*"
    11  )
    12  
    13  // CertificateConstraint defines the attributes a certificate must have to act as a functionary.
    14  // A wildcard `*` allows any value in the specified attribute, where as an empty array or value
    15  // asserts that the certificate must have nothing for that attribute. A certificate must have
    16  // every value defined in a constraint to match.
    17  type CertificateConstraint struct {
    18  	CommonName    string   `json:"common_name"`
    19  	DNSNames      []string `json:"dns_names"`
    20  	Emails        []string `json:"emails"`
    21  	Organizations []string `json:"organizations"`
    22  	Roots         []string `json:"roots"`
    23  	URIs          []string `json:"uris"`
    24  }
    25  
    26  // checkResult is a data structure used to hold
    27  // certificate constraint errors
    28  type checkResult struct {
    29  	errors []error
    30  }
    31  
    32  // newCheckResult initializes a new checkResult
    33  func newCheckResult() *checkResult {
    34  	return &checkResult{
    35  		errors: make([]error, 0),
    36  	}
    37  }
    38  
    39  // evaluate runs a constraint check on a certificate
    40  func (cr *checkResult) evaluate(cert *x509.Certificate, constraintCheck func(*x509.Certificate) error) *checkResult {
    41  	err := constraintCheck(cert)
    42  	if err != nil {
    43  		cr.errors = append(cr.errors, err)
    44  	}
    45  	return cr
    46  }
    47  
    48  // error reduces all of the errors into one error with a
    49  // combined error message. If there are no errors, nil
    50  // will be returned.
    51  func (cr *checkResult) error() error {
    52  	if len(cr.errors) == 0 {
    53  		return nil
    54  	}
    55  	return fmt.Errorf("cert failed constraints check: %+q", cr.errors)
    56  }
    57  
    58  // Check tests the provided certificate against the constraint. An error is returned if the certificate
    59  // fails any of the constraints. nil is returned if the certificate passes all of the constraints.
    60  func (cc CertificateConstraint) Check(cert *x509.Certificate, rootCAIDs []string, rootCertPool, intermediateCertPool *x509.CertPool) error {
    61  	return newCheckResult().
    62  		evaluate(cert, cc.checkCommonName).
    63  		evaluate(cert, cc.checkDNSNames).
    64  		evaluate(cert, cc.checkEmails).
    65  		evaluate(cert, cc.checkOrganizations).
    66  		evaluate(cert, cc.checkRoots(rootCAIDs, rootCertPool, intermediateCertPool)).
    67  		evaluate(cert, cc.checkURIs).
    68  		error()
    69  }
    70  
    71  // checkCommonName verifies that the certificate's common name matches the constraint.
    72  func (cc CertificateConstraint) checkCommonName(cert *x509.Certificate) error {
    73  	return checkCertConstraint("common name", []string{cc.CommonName}, []string{cert.Subject.CommonName})
    74  }
    75  
    76  // checkDNSNames verifies that the certificate's dns names matches the constraint.
    77  func (cc CertificateConstraint) checkDNSNames(cert *x509.Certificate) error {
    78  	return checkCertConstraint("dns name", cc.DNSNames, cert.DNSNames)
    79  }
    80  
    81  // checkEmails verifies that the certificate's emails matches the constraint.
    82  func (cc CertificateConstraint) checkEmails(cert *x509.Certificate) error {
    83  	return checkCertConstraint("email", cc.Emails, cert.EmailAddresses)
    84  }
    85  
    86  // checkOrganizations verifies that the certificate's organizations matches the constraint.
    87  func (cc CertificateConstraint) checkOrganizations(cert *x509.Certificate) error {
    88  	return checkCertConstraint("organization", cc.Organizations, cert.Subject.Organization)
    89  }
    90  
    91  // checkRoots verifies that the certificate's roots matches the constraint.
    92  // The certificates trust chain must also be verified.
    93  func (cc CertificateConstraint) checkRoots(rootCAIDs []string, rootCertPool, intermediateCertPool *x509.CertPool) func(*x509.Certificate) error {
    94  	return func(cert *x509.Certificate) error {
    95  		_, err := VerifyCertificateTrust(cert, rootCertPool, intermediateCertPool)
    96  		if err != nil {
    97  			return fmt.Errorf("failed to verify roots: %w", err)
    98  		}
    99  		return checkCertConstraint("root", cc.Roots, rootCAIDs)
   100  	}
   101  }
   102  
   103  // checkURIs verifies that the certificate's URIs matches the constraint.
   104  func (cc CertificateConstraint) checkURIs(cert *x509.Certificate) error {
   105  	return checkCertConstraint("uri", cc.URIs, urisToStrings(cert.URIs))
   106  }
   107  
   108  // urisToStrings is a helper that converts a list of URL objects to the string that represents them
   109  func urisToStrings(uris []*url.URL) []string {
   110  	res := make([]string, 0, len(uris))
   111  	for _, uri := range uris {
   112  		res = append(res, uri.String())
   113  	}
   114  
   115  	return res
   116  }
   117  
   118  // checkCertConstraint tests that the provided test values match the allowed values of the constraint.
   119  // All allowed values must be met one-to-one to be considered a successful match.
   120  func checkCertConstraint(attributeName string, constraints, values []string) error {
   121  	// If the only constraint is to allow all, the check succeeds
   122  	if len(constraints) == 1 && constraints[0] == AllowAllConstraint {
   123  		return nil
   124  	}
   125  
   126  	if len(constraints) == 1 && constraints[0] == "" {
   127  		constraints = []string{}
   128  	}
   129  
   130  	if len(values) == 1 && values[0] == "" {
   131  		values = []string{}
   132  	}
   133  
   134  	// If no constraints are specified, but the certificate has values for the attribute, then the check fails
   135  	if len(constraints) == 0 && len(values) > 0 {
   136  		return fmt.Errorf("not expecting any %s(s), but cert has %d %s(s)", attributeName, len(values), attributeName)
   137  	}
   138  
   139  	unmet := NewSet(constraints...)
   140  	for _, v := range values {
   141  		// if the cert has a value we didn't expect, fail early
   142  		if !unmet.Has(v) {
   143  			return fmt.Errorf("cert has an unexpected %s %s given constraints %+q", attributeName, v, constraints)
   144  		}
   145  
   146  		// consider the constraint met
   147  		unmet.Remove(v)
   148  	}
   149  
   150  	// if we have any unmet left after going through each test value, fail.
   151  	if len(unmet) > 0 {
   152  		return fmt.Errorf("cert with %s(s) %+q did not pass all constraints %+q", attributeName, values, constraints)
   153  	}
   154  
   155  	return nil
   156  }
   157  

View as plain text