105 lines
3.5 KiB
Go
105 lines
3.5 KiB
Go
package csr
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/letsencrypt/boulder/core"
|
|
"github.com/letsencrypt/boulder/goodkey"
|
|
)
|
|
|
|
// maxCNLength is the maximum length allowed for the common name as specified in RFC 5280
|
|
const maxCNLength = 64
|
|
|
|
// This map is used to detect algorithms in crypto/x509 that
|
|
// are no longer considered sufficiently strong.
|
|
// * No MD2, MD5, or SHA-1
|
|
// * No DSA
|
|
//
|
|
// SHA1WithRSA is allowed because there's still a fair bit of it
|
|
// out there, but we should try to remove it soon.
|
|
var badSignatureAlgorithms = map[x509.SignatureAlgorithm]bool{
|
|
x509.UnknownSignatureAlgorithm: true,
|
|
x509.MD2WithRSA: true,
|
|
x509.MD5WithRSA: true,
|
|
x509.DSAWithSHA1: true,
|
|
x509.DSAWithSHA256: true,
|
|
x509.ECDSAWithSHA1: true,
|
|
}
|
|
|
|
var (
|
|
invalidPubKey = errors.New("invalid public key in CSR")
|
|
unsupportedSigAlg = errors.New("signature algorithm not supported")
|
|
invalidSig = errors.New("invalid signature on CSR")
|
|
invalidEmailPresent = errors.New("CSR contains one or more email address fields")
|
|
invalidIPPresent = errors.New("CSR contains one or more IP address fields")
|
|
invalidNoDNS = errors.New("at least one DNS name is required")
|
|
)
|
|
|
|
// VerifyCSR checks the validity of a x509.CertificateRequest. Before doing checks it normalizes
|
|
// the CSR which lowers the case of DNS names and subject CN, and if forceCNFromSAN is true it
|
|
// will hoist a DNS name into the CN if it is empty.
|
|
func VerifyCSR(csr *x509.CertificateRequest, maxNames int, keyPolicy *goodkey.KeyPolicy, pa core.PolicyAuthority, forceCNFromSAN bool, regID int64) error {
|
|
normalizeCSR(csr, forceCNFromSAN)
|
|
key, ok := csr.PublicKey.(crypto.PublicKey)
|
|
if !ok {
|
|
return invalidPubKey
|
|
}
|
|
if err := keyPolicy.GoodKey(key); err != nil {
|
|
return fmt.Errorf("invalid public key in CSR: %s", err)
|
|
}
|
|
if badSignatureAlgorithms[csr.SignatureAlgorithm] {
|
|
// go1.6 provides a stringer for x509.SignatureAlgorithm but 1.5.x
|
|
// does not
|
|
return unsupportedSigAlg
|
|
}
|
|
if err := csr.CheckSignature(); err != nil {
|
|
return invalidSig
|
|
}
|
|
if len(csr.EmailAddresses) > 0 {
|
|
return invalidEmailPresent
|
|
}
|
|
if len(csr.IPAddresses) > 0 {
|
|
return invalidIPPresent
|
|
}
|
|
if len(csr.DNSNames) == 0 && csr.Subject.CommonName == "" {
|
|
return invalidNoDNS
|
|
}
|
|
if len(csr.Subject.CommonName) > maxCNLength {
|
|
return fmt.Errorf("CN was longer than %d bytes", maxCNLength)
|
|
}
|
|
if maxNames > 0 && len(csr.DNSNames) > maxNames {
|
|
return fmt.Errorf("CSR contains more than %d DNS names", maxNames)
|
|
}
|
|
badNames := []string{}
|
|
for _, name := range csr.DNSNames {
|
|
if err := pa.WillingToIssue(core.AcmeIdentifier{
|
|
Type: core.IdentifierDNS,
|
|
Value: name,
|
|
}); err != nil {
|
|
badNames = append(badNames, fmt.Sprintf("%q", name))
|
|
}
|
|
}
|
|
if len(badNames) > 0 {
|
|
return fmt.Errorf("policy forbids issuing for: %s", strings.Join(badNames, ", "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// normalizeCSR deduplicates and lowers the case of dNSNames and the subject CN.
|
|
// If forceCNFromSAN is true it will also hoist a dNSName into the CN if it is empty.
|
|
func normalizeCSR(csr *x509.CertificateRequest, forceCNFromSAN bool) {
|
|
if forceCNFromSAN && csr.Subject.CommonName == "" {
|
|
if len(csr.DNSNames) > 0 {
|
|
csr.Subject.CommonName = csr.DNSNames[0]
|
|
}
|
|
} else if csr.Subject.CommonName != "" {
|
|
csr.DNSNames = append(csr.DNSNames, csr.Subject.CommonName)
|
|
}
|
|
csr.Subject.CommonName = strings.ToLower(csr.Subject.CommonName)
|
|
csr.DNSNames = core.UniqueLowerNames(csr.DNSNames)
|
|
}
|