...

Source file src/github.com/letsencrypt/boulder/linter/linter.go

Documentation: github.com/letsencrypt/boulder/linter

     1  package linter
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/ecdsa"
     6  	"crypto/rand"
     7  	"crypto/rsa"
     8  	"crypto/x509"
     9  	"fmt"
    10  	"strings"
    11  
    12  	zlintx509 "github.com/zmap/zcrypto/x509"
    13  	"github.com/zmap/zlint/v3"
    14  	"github.com/zmap/zlint/v3/lint"
    15  
    16  	"github.com/letsencrypt/boulder/core"
    17  
    18  	_ "github.com/letsencrypt/boulder/linter/lints/cabf_br"
    19  	_ "github.com/letsencrypt/boulder/linter/lints/chrome"
    20  	_ "github.com/letsencrypt/boulder/linter/lints/cpcps"
    21  	_ "github.com/letsencrypt/boulder/linter/lints/rfc"
    22  )
    23  
    24  var ErrLinting = fmt.Errorf("failed lint(s)")
    25  
    26  // Check accomplishes the entire process of linting: it generates a throwaway
    27  // signing key, uses that to create a linting cert, and runs a default set of
    28  // lints (everything except for the ETSI and EV lints) against it. If the
    29  // subjectPubKey and realSigner indicate that this is a self-signed cert, the
    30  // cert will have its pubkey replaced to also be self-signed. This is the
    31  // primary public interface of this package, but it can be inefficient; creating
    32  // a new signer and a new lint registry are expensive operations which
    33  // performance-sensitive clients may want to cache via linter.New().
    34  func Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey, realIssuer *x509.Certificate, realSigner crypto.Signer, skipLints []string) ([]byte, error) {
    35  	linter, err := New(realIssuer, realSigner, skipLints)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	lintCertBytes, err := linter.Check(tbs, subjectPubKey)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	return lintCertBytes, nil
    46  }
    47  
    48  // CheckCRL is like Check, but for CRLs.
    49  func CheckCRL(tbs *x509.RevocationList, realIssuer *x509.Certificate, realSigner crypto.Signer, skipLints []string) error {
    50  	linter, err := New(realIssuer, realSigner, skipLints)
    51  	if err != nil {
    52  		return err
    53  	}
    54  	return linter.CheckCRL(tbs)
    55  }
    56  
    57  // Linter is capable of linting a to-be-signed (TBS) certificate. It does so by
    58  // signing that certificate with a throwaway private key and a fake issuer whose
    59  // public key matches the throwaway private key, and then running the resulting
    60  // certificate through a registry of zlint lints.
    61  type Linter struct {
    62  	issuer     *x509.Certificate
    63  	signer     crypto.Signer
    64  	registry   lint.Registry
    65  	realPubKey crypto.PublicKey
    66  }
    67  
    68  // New constructs a Linter. It uses the provided real certificate and signer
    69  // (private key) to generate a matching fake keypair and issuer cert that will
    70  // be used to sign the lint certificate. It uses the provided list of lint names
    71  // to skip to filter the zlint global registry to only those lints which should
    72  // be run.
    73  func New(realIssuer *x509.Certificate, realSigner crypto.Signer, skipLints []string) (*Linter, error) {
    74  	lintSigner, err := makeSigner(realSigner)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	lintIssuer, err := makeIssuer(realIssuer, lintSigner)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	reg, err := makeRegistry(skipLints)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	return &Linter{lintIssuer, lintSigner, reg, realSigner.Public()}, nil
    87  }
    88  
    89  // Check signs the given TBS certificate using the Linter's fake issuer cert and
    90  // private key, then runs the resulting certificate through all non-filtered
    91  // lints. If the subjectPubKey is identical to the public key of the real signer
    92  // used to create this linter, then the throwaway cert will have its pubkey
    93  // replaced with the linter's pubkey so that it appears self-signed. It returns
    94  // an error if any lint fails. On success it also returns the DER bytes of the
    95  // linting certificate.
    96  func (l Linter) Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey) ([]byte, error) {
    97  	lintPubKey := subjectPubKey
    98  	selfSigned, err := core.PublicKeysEqual(subjectPubKey, l.realPubKey)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	if selfSigned {
   103  		lintPubKey = l.signer.Public()
   104  	}
   105  
   106  	lintCertBytes, cert, err := makeLintCert(tbs, lintPubKey, l.issuer, l.signer)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	lintRes := zlint.LintCertificateEx(cert, l.registry)
   112  	err = ProcessResultSet(lintRes)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	return lintCertBytes, nil
   118  }
   119  
   120  // CheckCRL signs the given RevocationList template using the Linter's fake
   121  // issuer cert and private key, then runs the resulting CRL through our suite
   122  // of CRL checks. It returns an error if any check fails.
   123  func (l Linter) CheckCRL(tbs *x509.RevocationList) error {
   124  	crl, err := makeLintCRL(tbs, l.issuer, l.signer)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	lintRes := zlint.LintRevocationListEx(crl, l.registry)
   129  	return ProcessResultSet(lintRes)
   130  }
   131  
   132  func makeSigner(realSigner crypto.Signer) (crypto.Signer, error) {
   133  	var lintSigner crypto.Signer
   134  	var err error
   135  	switch k := realSigner.Public().(type) {
   136  	case *rsa.PublicKey:
   137  		lintSigner, err = rsa.GenerateKey(rand.Reader, k.Size()*8)
   138  		if err != nil {
   139  			return nil, fmt.Errorf("failed to create RSA lint signer: %w", err)
   140  		}
   141  	case *ecdsa.PublicKey:
   142  		lintSigner, err = ecdsa.GenerateKey(k.Curve, rand.Reader)
   143  		if err != nil {
   144  			return nil, fmt.Errorf("failed to create ECDSA lint signer: %w", err)
   145  		}
   146  	default:
   147  		return nil, fmt.Errorf("unsupported lint signer type: %T", k)
   148  	}
   149  	return lintSigner, nil
   150  }
   151  
   152  func makeIssuer(realIssuer *x509.Certificate, lintSigner crypto.Signer) (*x509.Certificate, error) {
   153  	lintIssuerTBS := &x509.Certificate{
   154  		// This is nearly the full list of attributes that
   155  		// x509.CreateCertificate() says it carries over from the template.
   156  		// Constructing this TBS certificate in this way ensures that the
   157  		// resulting lint issuer is as identical to the real issuer as we can
   158  		// get, without sharing a public key.
   159  		//
   160  		// We do not copy the SignatureAlgorithm field while constructing the
   161  		// lintIssuer because the lintIssuer is self-signed. Depending on the
   162  		// realIssuer, which could be either an intermediate or cross-signed
   163  		// intermediate, the SignatureAlgorithm of that certificate may differ
   164  		// from the root certificate that had signed it.
   165  		AuthorityKeyId:              realIssuer.AuthorityKeyId,
   166  		BasicConstraintsValid:       realIssuer.BasicConstraintsValid,
   167  		CRLDistributionPoints:       realIssuer.CRLDistributionPoints,
   168  		DNSNames:                    realIssuer.DNSNames,
   169  		EmailAddresses:              realIssuer.EmailAddresses,
   170  		ExcludedDNSDomains:          realIssuer.ExcludedDNSDomains,
   171  		ExcludedEmailAddresses:      realIssuer.ExcludedEmailAddresses,
   172  		ExcludedIPRanges:            realIssuer.ExcludedIPRanges,
   173  		ExcludedURIDomains:          realIssuer.ExcludedURIDomains,
   174  		ExtKeyUsage:                 realIssuer.ExtKeyUsage,
   175  		ExtraExtensions:             realIssuer.ExtraExtensions,
   176  		IPAddresses:                 realIssuer.IPAddresses,
   177  		IsCA:                        realIssuer.IsCA,
   178  		IssuingCertificateURL:       realIssuer.IssuingCertificateURL,
   179  		KeyUsage:                    realIssuer.KeyUsage,
   180  		MaxPathLen:                  realIssuer.MaxPathLen,
   181  		MaxPathLenZero:              realIssuer.MaxPathLenZero,
   182  		NotAfter:                    realIssuer.NotAfter,
   183  		NotBefore:                   realIssuer.NotBefore,
   184  		OCSPServer:                  realIssuer.OCSPServer,
   185  		PermittedDNSDomains:         realIssuer.PermittedDNSDomains,
   186  		PermittedDNSDomainsCritical: realIssuer.PermittedDNSDomainsCritical,
   187  		PermittedEmailAddresses:     realIssuer.PermittedEmailAddresses,
   188  		PermittedIPRanges:           realIssuer.PermittedIPRanges,
   189  		PermittedURIDomains:         realIssuer.PermittedURIDomains,
   190  		PolicyIdentifiers:           realIssuer.PolicyIdentifiers,
   191  		SerialNumber:                realIssuer.SerialNumber,
   192  		Subject:                     realIssuer.Subject,
   193  		SubjectKeyId:                realIssuer.SubjectKeyId,
   194  		URIs:                        realIssuer.URIs,
   195  		UnknownExtKeyUsage:          realIssuer.UnknownExtKeyUsage,
   196  	}
   197  	lintIssuerBytes, err := x509.CreateCertificate(rand.Reader, lintIssuerTBS, lintIssuerTBS, lintSigner.Public(), lintSigner)
   198  	if err != nil {
   199  		return nil, fmt.Errorf("failed to create lint issuer: %w", err)
   200  	}
   201  	lintIssuer, err := x509.ParseCertificate(lintIssuerBytes)
   202  	if err != nil {
   203  		return nil, fmt.Errorf("failed to parse lint issuer: %w", err)
   204  	}
   205  	return lintIssuer, nil
   206  }
   207  
   208  func makeRegistry(skipLints []string) (lint.Registry, error) {
   209  	reg, err := lint.GlobalRegistry().Filter(lint.FilterOptions{
   210  		ExcludeNames: skipLints,
   211  		ExcludeSources: []lint.LintSource{
   212  			// Excluded because Boulder does not issue EV certs.
   213  			lint.CABFEVGuidelines,
   214  			// Excluded because Boulder does not use the
   215  			// ETSI EN 319 412-5 qcStatements extension.
   216  			lint.EtsiEsi,
   217  		},
   218  	})
   219  	if err != nil {
   220  		return nil, fmt.Errorf("failed to create lint registry: %w", err)
   221  	}
   222  	return reg, nil
   223  }
   224  
   225  func makeLintCert(tbs *x509.Certificate, subjectPubKey crypto.PublicKey, issuer *x509.Certificate, signer crypto.Signer) ([]byte, *zlintx509.Certificate, error) {
   226  	lintCertBytes, err := x509.CreateCertificate(rand.Reader, tbs, issuer, subjectPubKey, signer)
   227  	if err != nil {
   228  		return nil, nil, fmt.Errorf("failed to create lint certificate: %w", err)
   229  	}
   230  	lintCert, err := zlintx509.ParseCertificate(lintCertBytes)
   231  	if err != nil {
   232  		return nil, nil, fmt.Errorf("failed to parse lint certificate: %w", err)
   233  	}
   234  	return lintCertBytes, lintCert, nil
   235  }
   236  
   237  func ProcessResultSet(lintRes *zlint.ResultSet) error {
   238  	if lintRes.NoticesPresent || lintRes.WarningsPresent || lintRes.ErrorsPresent || lintRes.FatalsPresent {
   239  		var failedLints []string
   240  		for lintName, result := range lintRes.Results {
   241  			if result.Status > lint.Pass {
   242  				failedLints = append(failedLints, fmt.Sprintf("%s (%s)", lintName, result.Details))
   243  			}
   244  		}
   245  		return fmt.Errorf("%w: %s", ErrLinting, strings.Join(failedLints, ", "))
   246  	}
   247  	return nil
   248  }
   249  
   250  func makeLintCRL(tbs *x509.RevocationList, issuer *x509.Certificate, signer crypto.Signer) (*zlintx509.RevocationList, error) {
   251  	lintCRLBytes, err := x509.CreateRevocationList(rand.Reader, tbs, issuer, signer)
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  	lintCRL, err := zlintx509.ParseRevocationList(lintCRLBytes)
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  	return lintCRL, nil
   260  }
   261  

View as plain text