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 }