github.com/letsencrypt/boulder@v0.20251208.0/issuance/crl.go (about)

     1  package issuance
     2  
     3  import (
     4  	"crypto/rand"
     5  	"crypto/x509"
     6  	"fmt"
     7  	"math/big"
     8  	"time"
     9  
    10  	"github.com/zmap/zlint/v3/lint"
    11  
    12  	"github.com/letsencrypt/boulder/config"
    13  	"github.com/letsencrypt/boulder/crl/idp"
    14  	"github.com/letsencrypt/boulder/linter"
    15  )
    16  
    17  type CRLProfileConfig struct {
    18  	ValidityInterval config.Duration
    19  	MaxBackdate      config.Duration
    20  
    21  	// LintConfig is a path to a zlint config file, which can be used to control
    22  	// the behavior of zlint's "customizable lints".
    23  	LintConfig string
    24  	// IgnoredLints is a list of lint names that we know will fail for this
    25  	// profile, and which we know it is safe to ignore.
    26  	IgnoredLints []string
    27  }
    28  
    29  type CRLProfile struct {
    30  	validityInterval time.Duration
    31  	maxBackdate      time.Duration
    32  
    33  	lints lint.Registry
    34  }
    35  
    36  func NewCRLProfile(config CRLProfileConfig) (*CRLProfile, error) {
    37  	lifetime := config.ValidityInterval.Duration
    38  	if lifetime >= 10*24*time.Hour {
    39  		return nil, fmt.Errorf("crl lifetime cannot be more than 10 days, got %q", lifetime)
    40  	} else if lifetime <= 0*time.Hour {
    41  		return nil, fmt.Errorf("crl lifetime must be positive, got %q", lifetime)
    42  	}
    43  
    44  	if config.MaxBackdate.Duration < 0 {
    45  		return nil, fmt.Errorf("crl max backdate must be non-negative, got %q", config.MaxBackdate)
    46  	}
    47  
    48  	reg, err := linter.NewRegistry(config.IgnoredLints)
    49  	if err != nil {
    50  		return nil, fmt.Errorf("creating lint registry: %w", err)
    51  	}
    52  	if config.LintConfig != "" {
    53  		lintconfig, err := lint.NewConfigFromFile(config.LintConfig)
    54  		if err != nil {
    55  			return nil, fmt.Errorf("loading zlint config file: %w", err)
    56  		}
    57  		reg.SetConfiguration(lintconfig)
    58  	}
    59  
    60  	return &CRLProfile{
    61  		validityInterval: config.ValidityInterval.Duration,
    62  		maxBackdate:      config.MaxBackdate.Duration,
    63  		lints:            reg,
    64  	}, nil
    65  }
    66  
    67  type CRLRequest struct {
    68  	Number *big.Int
    69  	Shard  int64
    70  
    71  	ThisUpdate time.Time
    72  
    73  	Entries []x509.RevocationListEntry
    74  }
    75  
    76  // crlURL combines the CRL URL base with a shard, and adds a suffix.
    77  func (i *Issuer) crlURL(shard int) string {
    78  	return fmt.Sprintf("%s%d.crl", i.crlURLBase, shard)
    79  }
    80  
    81  func (i *Issuer) IssueCRL(prof *CRLProfile, req *CRLRequest) ([]byte, error) {
    82  	backdatedBy := i.clk.Now().Sub(req.ThisUpdate)
    83  	if backdatedBy > prof.maxBackdate {
    84  		return nil, fmt.Errorf("ThisUpdate is too far in the past (%s>%s)", backdatedBy, prof.maxBackdate)
    85  	}
    86  	if backdatedBy < 0 {
    87  		return nil, fmt.Errorf("ThisUpdate is in the future (%s>%s)", req.ThisUpdate, i.clk.Now())
    88  	}
    89  
    90  	template := &x509.RevocationList{
    91  		RevokedCertificateEntries: req.Entries,
    92  		Number:                    req.Number,
    93  		ThisUpdate:                req.ThisUpdate,
    94  		NextUpdate:                req.ThisUpdate.Add(-time.Second).Add(prof.validityInterval),
    95  	}
    96  
    97  	if i.crlURLBase == "" {
    98  		return nil, fmt.Errorf("CRL must contain an issuingDistributionPoint")
    99  	}
   100  
   101  	// Concat the base with the shard directly, since we require that the base
   102  	// end with a single trailing slash.
   103  	idp, err := idp.MakeUserCertsExt([]string{
   104  		i.crlURL(int(req.Shard)),
   105  	})
   106  	if err != nil {
   107  		return nil, fmt.Errorf("creating IDP extension: %w", err)
   108  	}
   109  	template.ExtraExtensions = append(template.ExtraExtensions, idp)
   110  
   111  	err = i.Linter.CheckCRL(template, prof.lints)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	crlBytes, err := x509.CreateRevocationList(
   117  		rand.Reader,
   118  		template,
   119  		i.Cert.Certificate,
   120  		i.Signer,
   121  	)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	return crlBytes, nil
   127  }