1 package va
2
3 import (
4 "bytes"
5 "context"
6 "crypto/sha256"
7 "crypto/subtle"
8 "crypto/tls"
9 "crypto/x509"
10 "crypto/x509/pkix"
11 "encoding/asn1"
12 "encoding/hex"
13 "errors"
14 "fmt"
15 "net"
16 "strconv"
17 "strings"
18
19 "github.com/letsencrypt/boulder/core"
20 "github.com/letsencrypt/boulder/identifier"
21 "github.com/letsencrypt/boulder/probs"
22 )
23
24 const (
25
26
27 ACMETLS1Protocol = "acme-tls/1"
28 )
29
30 var (
31
32
33 IdPeAcmeIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
34
35
36 IdCeSubjectAltName = asn1.ObjectIdentifier{2, 5, 29, 17}
37 )
38
39
40
41
42 func certAltNames(cert *x509.Certificate) []string {
43 var names []string
44 if cert.Subject.CommonName != "" {
45 names = append(names, cert.Subject.CommonName)
46 }
47 names = append(names, cert.DNSNames...)
48 names = append(names, cert.EmailAddresses...)
49 for _, id := range cert.IPAddresses {
50 names = append(names, id.String())
51 }
52 for _, id := range cert.URIs {
53 names = append(names, id.String())
54 }
55 names = core.UniqueLowerNames(names)
56 return names
57 }
58
59 func (va *ValidationAuthorityImpl) tryGetChallengeCert(ctx context.Context,
60 identifier identifier.ACMEIdentifier, challenge core.Challenge,
61 tlsConfig *tls.Config) (*x509.Certificate, *tls.ConnectionState, []core.ValidationRecord, *probs.ProblemDetails) {
62
63 allAddrs, err := va.getAddrs(ctx, identifier.Value)
64 validationRecords := []core.ValidationRecord{
65 {
66 Hostname: identifier.Value,
67 AddressesResolved: allAddrs,
68 Port: strconv.Itoa(va.tlsPort),
69 },
70 }
71 if err != nil {
72 return nil, nil, validationRecords, detailedError(err)
73 }
74 thisRecord := &validationRecords[0]
75
76
77 v4, v6 := availableAddresses(allAddrs)
78 addresses := append(v4, v6...)
79
80
81 if len(addresses) < 1 {
82 return nil, nil, validationRecords, probs.Malformed("no IP addresses found for %q", identifier.Value)
83 }
84
85
86 if len(v6) > 0 {
87 address := net.JoinHostPort(v6[0].String(), thisRecord.Port)
88 thisRecord.AddressUsed = v6[0]
89
90 cert, cs, prob := va.getChallengeCert(ctx, address, identifier, challenge, tlsConfig)
91
92
93 if err == nil {
94 return cert, cs, validationRecords, prob
95 }
96
97
98 thisRecord.AddressesTried = append(thisRecord.AddressesTried, thisRecord.AddressUsed)
99 va.metrics.ipv4FallbackCounter.Inc()
100 }
101
102
103
104 if len(v4) == 0 && len(thisRecord.AddressesTried) > 0 {
105 return nil, nil, validationRecords, probs.Malformed("Unable to contact %q at %q, no IPv4 addresses to try as fallback",
106 thisRecord.Hostname, thisRecord.AddressesTried[0])
107 } else if len(v4) == 0 && len(thisRecord.AddressesTried) == 0 {
108
109
110 return nil, nil, validationRecords, probs.Malformed("No IP addresses found for %q", thisRecord.Hostname)
111 }
112
113
114
115 thisRecord.AddressUsed = v4[0]
116 cert, cs, prob := va.getChallengeCert(ctx, net.JoinHostPort(v4[0].String(), thisRecord.Port),
117 identifier, challenge, tlsConfig)
118 return cert, cs, validationRecords, prob
119 }
120
121 func (va *ValidationAuthorityImpl) getChallengeCert(
122 ctx context.Context,
123 hostPort string,
124 identifier identifier.ACMEIdentifier,
125 challenge core.Challenge,
126 config *tls.Config,
127 ) (*x509.Certificate, *tls.ConnectionState, *probs.ProblemDetails) {
128 va.log.Info(fmt.Sprintf("%s [%s] Attempting to validate for %s %s", challenge.Type, identifier, hostPort, config.ServerName))
129
130 config.InsecureSkipVerify = true
131
132 dialCtx, cancel := context.WithTimeout(ctx, va.singleDialTimeout)
133 defer cancel()
134
135 dialer := &tls.Dialer{Config: config}
136 conn, err := dialer.DialContext(dialCtx, "tcp", hostPort)
137 if err != nil {
138 va.log.Infof("%s connection failure for %s. err=[%#v] errStr=[%s]", challenge.Type, identifier, err, err)
139 host, _, splitErr := net.SplitHostPort(hostPort)
140 if splitErr == nil && net.ParseIP(host) != nil {
141
142
143
144 return nil, nil, detailedError(ipError{net.ParseIP(host), err})
145 }
146 return nil, nil, detailedError(err)
147 }
148 defer conn.Close()
149
150
151 cs := conn.(*tls.Conn).ConnectionState()
152 certs := cs.PeerCertificates
153 if len(certs) == 0 {
154 va.log.Infof("%s challenge for %s resulted in no certificates", challenge.Type, identifier.Value)
155 return nil, nil, probs.Unauthorized(fmt.Sprintf("No certs presented for %s challenge", challenge.Type))
156 }
157 for i, cert := range certs {
158 va.log.AuditInfof("%s challenge for %s received certificate (%d of %d): cert=[%s]",
159 challenge.Type, identifier.Value, i+1, len(certs), hex.EncodeToString(cert.Raw))
160 }
161 return certs[0], &cs, nil
162 }
163
164 func checkExpectedSAN(cert *x509.Certificate, name identifier.ACMEIdentifier) error {
165 if len(cert.DNSNames) != 1 {
166 return errors.New("wrong number of dNSNames")
167 }
168
169 for _, ext := range cert.Extensions {
170 if IdCeSubjectAltName.Equal(ext.Id) {
171 expectedSANs, err := asn1.Marshal([]asn1.RawValue{
172 {Tag: 2, Class: 2, Bytes: []byte(cert.DNSNames[0])},
173 })
174 if err != nil || !bytes.Equal(expectedSANs, ext.Value) {
175 return errors.New("SAN extension does not match expected bytes")
176 }
177 }
178 }
179
180 if !strings.EqualFold(cert.DNSNames[0], name.Value) {
181 return errors.New("dNSName does not match expected identifier")
182 }
183
184 return nil
185 }
186
187
188
189
190 func checkAcceptableExtensions(exts []pkix.Extension, requiredOIDs []asn1.ObjectIdentifier) error {
191 oidSeen := make(map[string]bool)
192
193 for _, ext := range exts {
194 if oidSeen[ext.Id.String()] {
195 return fmt.Errorf("Extension OID %s seen twice", ext.Id)
196 }
197 oidSeen[ext.Id.String()] = true
198 }
199
200 for _, required := range requiredOIDs {
201 if !oidSeen[required.String()] {
202 return fmt.Errorf("Required extension OID %s is not present", required)
203 }
204 }
205
206 return nil
207 }
208
209 func (va *ValidationAuthorityImpl) validateTLSALPN01(ctx context.Context, identifier identifier.ACMEIdentifier, challenge core.Challenge) ([]core.ValidationRecord, *probs.ProblemDetails) {
210 if identifier.Type != "dns" {
211 va.log.Info(fmt.Sprintf("Identifier type for TLS-ALPN-01 was not DNS: %s", identifier))
212 return nil, probs.Malformed("Identifier type for TLS-ALPN-01 was not DNS")
213 }
214
215 cert, cs, validationRecords, problem := va.tryGetChallengeCert(ctx, identifier, challenge, &tls.Config{
216 MinVersion: tls.VersionTLS12,
217 NextProtos: []string{ACMETLS1Protocol},
218 ServerName: identifier.Value,
219 })
220 if problem != nil {
221 return validationRecords, problem
222 }
223
224 if cs.NegotiatedProtocol != ACMETLS1Protocol {
225 errText := fmt.Sprintf(
226 "Cannot negotiate ALPN protocol %q for %s challenge",
227 ACMETLS1Protocol,
228 core.ChallengeTypeTLSALPN01,
229 )
230 return validationRecords, probs.Unauthorized(errText)
231 }
232
233 badCertErr := func(msg string) *probs.ProblemDetails {
234 hostPort := net.JoinHostPort(validationRecords[0].AddressUsed.String(), validationRecords[0].Port)
235
236 return probs.Unauthorized(fmt.Sprintf(
237 "Incorrect validation certificate for %s challenge. "+
238 "Requested %s from %s. "+
239 "%s",
240 challenge.Type, identifier.Value, hostPort, msg,
241 ))
242 }
243
244
245 err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature)
246 if err != nil || !bytes.Equal(cert.RawSubject, cert.RawIssuer) {
247 return validationRecords, badCertErr(
248 "Received certificate which is not self-signed.")
249 }
250
251
252
253 allowedOIDs := []asn1.ObjectIdentifier{
254 IdPeAcmeIdentifier, IdCeSubjectAltName,
255 }
256 err = checkAcceptableExtensions(cert.Extensions, allowedOIDs)
257 if err != nil {
258 return validationRecords, badCertErr(
259 fmt.Sprintf("Received certificate with unexpected extensions: %q", err))
260 }
261
262
263
264 err = checkExpectedSAN(cert, identifier)
265 if err != nil {
266 names := strings.Join(certAltNames(cert), ", ")
267 return validationRecords, badCertErr(
268 fmt.Sprintf("Received certificate with unexpected identifiers (%q): %q", names, err))
269 }
270
271
272 h := sha256.Sum256([]byte(challenge.ProvidedKeyAuthorization))
273 for _, ext := range cert.Extensions {
274 if IdPeAcmeIdentifier.Equal(ext.Id) {
275 va.metrics.tlsALPNOIDCounter.WithLabelValues(IdPeAcmeIdentifier.String()).Inc()
276 if !ext.Critical {
277 return validationRecords, badCertErr(
278 "Received certificate with acmeValidationV1 extension that is not Critical.")
279 }
280 var extValue []byte
281 rest, err := asn1.Unmarshal(ext.Value, &extValue)
282 if err != nil || len(rest) > 0 || len(h) != len(extValue) {
283 return validationRecords, badCertErr(
284 "Received certificate with malformed acmeValidationV1 extension value.")
285 }
286 if subtle.ConstantTimeCompare(h[:], extValue) != 1 {
287 return validationRecords, badCertErr(fmt.Sprintf(
288 "Received certificate with acmeValidationV1 extension value %s but expected %s.",
289 hex.EncodeToString(extValue),
290 hex.EncodeToString(h[:]),
291 ))
292 }
293 return validationRecords, nil
294 }
295 }
296
297 return validationRecords, badCertErr(
298 "Received certificate with no acmeValidationV1 extension.")
299 }
300
View as plain text