119 lines
3.5 KiB
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 ""
|
|
}
|