boulder/csr/csr.go

119 lines
3.5 KiB
Go

package csr
import (
"context"
"crypto"
"crypto/x509"
"errors"
"net/netip"
"strings"
"github.com/letsencrypt/boulder/core"
berrors "github.com/letsencrypt/boulder/errors"
"github.com/letsencrypt/boulder/goodkey"
"github.com/letsencrypt/boulder/identifier"
)
// maxCNLength is the maximum length allowed for the common name as specified in RFC 5280
const maxCNLength = 64
// This map is used to decide which CSR signing algorithms we consider
// strong enough to use. Significantly the missing algorithms are:
// * No algorithms using MD2, MD5, or SHA-1
// * No DSA algorithms
var goodSignatureAlgorithms = map[x509.SignatureAlgorithm]bool{
x509.SHA256WithRSA: true,
x509.SHA384WithRSA: true,
x509.SHA512WithRSA: true,
x509.ECDSAWithSHA256: true,
x509.ECDSAWithSHA384: true,
x509.ECDSAWithSHA512: true,
}
var (
invalidPubKey = berrors.BadCSRError("invalid public key in CSR")
unsupportedSigAlg = berrors.BadCSRError("signature algorithm not supported")
invalidSig = berrors.BadCSRError("invalid signature on CSR")
invalidEmailPresent = berrors.BadCSRError("CSR contains one or more email address fields")
invalidURIPresent = berrors.BadCSRError("CSR contains one or more URI fields")
invalidNoIdent = berrors.BadCSRError("at least one identifier is required")
)
// VerifyCSR checks the validity of a x509.CertificateRequest. It uses
// identifier.FromCSR to normalize the DNS names before checking whether we'll
// issue for them.
func VerifyCSR(ctx context.Context, csr *x509.CertificateRequest, maxNames int, keyPolicy *goodkey.KeyPolicy, pa core.PolicyAuthority) error {
key, ok := csr.PublicKey.(crypto.PublicKey)
if !ok {
return invalidPubKey
}
err := keyPolicy.GoodKey(ctx, key)
if err != nil {
if errors.Is(err, goodkey.ErrBadKey) {
return berrors.BadCSRError("invalid public key in CSR: %s", err)
}
return berrors.InternalServerError("error checking key validity: %s", err)
}
if !goodSignatureAlgorithms[csr.SignatureAlgorithm] {
return unsupportedSigAlg
}
err = csr.CheckSignature()
if err != nil {
return invalidSig
}
if len(csr.EmailAddresses) > 0 {
return invalidEmailPresent
}
if len(csr.URIs) > 0 {
return invalidURIPresent
}
// FromCSR also performs normalization, returning values that may not match
// the literal CSR contents.
idents := identifier.FromCSR(csr)
if len(idents) == 0 {
return invalidNoIdent
}
if len(idents) > maxNames {
return berrors.BadCSRError("CSR contains more than %d identifiers", maxNames)
}
err = pa.WillingToIssue(idents)
if err != nil {
return err
}
return nil
}
// CNFromCSR returns the lower-cased Subject Common Name from the CSR, if a
// short enough CN was provided. If it was too long or appears to be an IP,
// there will be no CN. If none was provided, the CN will be the first SAN that
// is short enough, which is done only for backwards compatibility with prior
// Let's Encrypt behaviour.
func CNFromCSR(csr *x509.CertificateRequest) string {
if len(csr.Subject.CommonName) > maxCNLength {
return ""
}
if csr.Subject.CommonName != "" {
_, err := netip.ParseAddr(csr.Subject.CommonName)
if err == nil { // inverted; we're looking for successful parsing here
return ""
}
return strings.ToLower(csr.Subject.CommonName)
}
// If there's no CN already, but we want to set one, promote the first dnsName
// SAN which is shorter than the maximum acceptable CN length (if any). We
// will never promote an ipAddress SAN to the CN.
for _, name := range csr.DNSNames {
if len(name) <= maxCNLength {
return strings.ToLower(name)
}
}
return ""
}