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

     1  package csr
     2  
     3  import (
     4  	"context"
     5  	"crypto"
     6  	"crypto/x509"
     7  	"errors"
     8  	"net/netip"
     9  	"strings"
    10  
    11  	"github.com/letsencrypt/boulder/core"
    12  	berrors "github.com/letsencrypt/boulder/errors"
    13  	"github.com/letsencrypt/boulder/goodkey"
    14  	"github.com/letsencrypt/boulder/identifier"
    15  )
    16  
    17  // maxCNLength is the maximum length allowed for the common name as specified in RFC 5280
    18  const maxCNLength = 64
    19  
    20  // This map is used to decide which CSR signing algorithms we consider
    21  // strong enough to use. Significantly the missing algorithms are:
    22  // * No algorithms using MD2, MD5, or SHA-1
    23  // * No DSA algorithms
    24  var goodSignatureAlgorithms = map[x509.SignatureAlgorithm]bool{
    25  	x509.SHA256WithRSA:   true,
    26  	x509.SHA384WithRSA:   true,
    27  	x509.SHA512WithRSA:   true,
    28  	x509.ECDSAWithSHA256: true,
    29  	x509.ECDSAWithSHA384: true,
    30  	x509.ECDSAWithSHA512: true,
    31  }
    32  
    33  var (
    34  	invalidPubKey       = berrors.BadCSRError("invalid public key in CSR")
    35  	unsupportedSigAlg   = berrors.BadCSRError("signature algorithm not supported")
    36  	invalidSig          = berrors.BadCSRError("invalid signature on CSR")
    37  	invalidEmailPresent = berrors.BadCSRError("CSR contains one or more email address fields")
    38  	invalidURIPresent   = berrors.BadCSRError("CSR contains one or more URI fields")
    39  	invalidNoIdent      = berrors.BadCSRError("at least one identifier is required")
    40  	invalidIPCN         = berrors.BadCSRError("CSR contains IP address in Common Name")
    41  )
    42  
    43  // VerifyCSR checks the validity of a x509.CertificateRequest. It uses
    44  // identifier.FromCSR to normalize the DNS names before checking whether we'll
    45  // issue for them.
    46  func VerifyCSR(ctx context.Context, csr *x509.CertificateRequest, maxNames int, keyPolicy *goodkey.KeyPolicy, pa core.PolicyAuthority) error {
    47  	key, ok := csr.PublicKey.(crypto.PublicKey)
    48  	if !ok {
    49  		return invalidPubKey
    50  	}
    51  	err := keyPolicy.GoodKey(ctx, key)
    52  	if err != nil {
    53  		if errors.Is(err, goodkey.ErrBadKey) {
    54  			return berrors.BadCSRError("invalid public key in CSR: %s", err)
    55  		}
    56  		return berrors.InternalServerError("error checking key validity: %s", err)
    57  	}
    58  	if !goodSignatureAlgorithms[csr.SignatureAlgorithm] {
    59  		return unsupportedSigAlg
    60  	}
    61  
    62  	err = csr.CheckSignature()
    63  	if err != nil {
    64  		return invalidSig
    65  	}
    66  	if len(csr.EmailAddresses) > 0 {
    67  		return invalidEmailPresent
    68  	}
    69  	if len(csr.URIs) > 0 {
    70  		return invalidURIPresent
    71  	}
    72  
    73  	// Reject all CSRs which have an IP address in the CN. We want to get rid of
    74  	// CNs entirely anyway, and IP addresses are a new feature, so don't let
    75  	// clients get in the habit of including them in the CN. We don't use
    76  	// CNFromCSR here because that also filters out IP address CNs, for defense
    77  	// in depth.
    78  	_, err = netip.ParseAddr(csr.Subject.CommonName)
    79  	if err == nil { // Inverted! Successful parsing is a bad thing in this case.
    80  		return invalidIPCN
    81  	}
    82  
    83  	// FromCSR also performs normalization, returning values that may not match
    84  	// the literal CSR contents.
    85  	idents := identifier.FromCSR(csr)
    86  	if len(idents) == 0 {
    87  		return invalidNoIdent
    88  	}
    89  	if len(idents) > maxNames {
    90  		return berrors.BadCSRError("CSR contains more than %d identifiers", maxNames)
    91  	}
    92  
    93  	err = pa.WillingToIssue(idents)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	return nil
    98  }
    99  
   100  // CNFromCSR returns the lower-cased Subject Common Name from the CSR, if a
   101  // short enough CN was provided. If it was too long or appears to be an IP,
   102  // there will be no CN. If none was provided, the CN will be the first SAN that
   103  // is short enough, which is done only for backwards compatibility with prior
   104  // Let's Encrypt behaviour.
   105  func CNFromCSR(csr *x509.CertificateRequest) string {
   106  	if len(csr.Subject.CommonName) > maxCNLength {
   107  		return ""
   108  	}
   109  
   110  	if csr.Subject.CommonName != "" {
   111  		_, err := netip.ParseAddr(csr.Subject.CommonName)
   112  		if err == nil { // Inverted! Successful parsing is a bad thing in this case.
   113  			return ""
   114  		}
   115  
   116  		return strings.ToLower(csr.Subject.CommonName)
   117  	}
   118  
   119  	// If there's no CN already, but we want to set one, promote the first dnsName
   120  	// SAN which is shorter than the maximum acceptable CN length (if any). We
   121  	// will never promote an ipAddress SAN to the CN.
   122  	for _, name := range csr.DNSNames {
   123  		if len(name) <= maxCNLength {
   124  			return strings.ToLower(name)
   125  		}
   126  	}
   127  
   128  	return ""
   129  }