/
csr.go
120 lines (113 loc) · 3.97 KB
/
csr.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package csr
import (
"context"
"crypto"
"crypto/x509"
"errors"
"strings"
"github.com/letsencrypt/boulder/core"
berrors "github.com/letsencrypt/boulder/errors"
"github.com/letsencrypt/boulder/features"
"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
//
// SHA1WithRSA is allowed because there's still a fair bit of it
// out there, but we should try to remove it soon.
var goodSignatureAlgorithms = map[x509.SignatureAlgorithm]bool{
x509.SHA1WithRSA: true, // TODO(#2988): Remove support
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")
invalidIPPresent = berrors.BadCSRError("CSR contains one or more IP address fields")
invalidNoDNS = berrors.BadCSRError("at least one DNS name is required")
invalidAllSANTooLong = berrors.BadCSRError("CSR doesn't contain a SAN short enough to fit in CN")
)
// 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 hoist a DNS name into the CN
// if it is empty.
func VerifyCSR(ctx context.Context, csr *x509.CertificateRequest, maxNames int, keyPolicy *goodkey.KeyPolicy, pa core.PolicyAuthority) error {
normalizeCSR(csr)
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
}
if !features.Enabled(features.SHA1CSRs) && csr.SignatureAlgorithm == x509.SHA1WithRSA {
return unsupportedSigAlg
}
err = csr.CheckSignature()
if 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 csr.Subject.CommonName == "" {
return invalidAllSANTooLong
}
if len(csr.Subject.CommonName) > maxCNLength {
return berrors.BadCSRError("CN was longer than %d bytes", maxCNLength)
}
if len(csr.DNSNames) > maxNames {
return berrors.BadCSRError("CSR contains more than %d DNS names", maxNames)
}
idents := make([]identifier.ACMEIdentifier, len(csr.DNSNames))
for i, dnsName := range csr.DNSNames {
idents[i] = identifier.DNSIdentifier(dnsName)
}
err = pa.WillingToIssueWildcards(idents)
if err != nil {
return err
}
return nil
}
// normalizeCSR deduplicates and lowers the case of dNSNames and the subject CN.
// It will also hoist a dNSName into the CN if it is empty.
func normalizeCSR(csr *x509.CertificateRequest) {
if csr.Subject.CommonName == "" {
var forcedCN string
// Promote the first SAN that is less than maxCNLength (if any)
for _, name := range csr.DNSNames {
if len(name) <= maxCNLength {
forcedCN = name
break
}
}
csr.Subject.CommonName = forcedCN
} 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)
}