1 package va
2
3 import (
4 "bytes"
5 "context"
6 "encoding/base64"
7 "errors"
8 "fmt"
9 mrand "math/rand"
10 "net"
11 "net/http"
12 "net/http/httptest"
13 "net/url"
14 "regexp"
15 "strconv"
16 "strings"
17 "time"
18 "unicode/utf8"
19
20 "github.com/letsencrypt/boulder/bdns"
21 "github.com/letsencrypt/boulder/core"
22 berrors "github.com/letsencrypt/boulder/errors"
23 "github.com/letsencrypt/boulder/identifier"
24 "github.com/letsencrypt/boulder/must"
25 "github.com/letsencrypt/boulder/probs"
26 "github.com/letsencrypt/boulder/test"
27 "github.com/miekg/dns"
28
29 "testing"
30 )
31
32 func httpChallenge() core.Challenge {
33 return createChallenge(core.ChallengeTypeHTTP01)
34 }
35
36
37
38 func TestDialerMismatchError(t *testing.T) {
39 d := preresolvedDialer{
40 ip: net.ParseIP("127.0.0.1"),
41 port: 1337,
42 hostname: "letsencrypt.org",
43 }
44
45 expectedErr := dialerMismatchError{
46 dialerHost: d.hostname,
47 dialerIP: d.ip.String(),
48 dialerPort: d.port,
49 host: "lettuceencrypt.org",
50 }
51
52 _, err := d.DialContext(
53 context.Background(),
54 "tincan-and-string",
55 "lettuceencrypt.org:80")
56 test.AssertEquals(t, err.Error(), expectedErr.Error())
57 }
58
59
60
61
62 func TestPreresolvedDialerTimeout(t *testing.T) {
63 va, _ := setup(nil, 0, "", nil)
64
65 va.singleDialTimeout = 50 * time.Millisecond
66
67
68 ctxTimeout := 500 * time.Millisecond
69 ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout)
70 defer cancel()
71
72 va.dnsClient = dnsMockReturnsUnroutable{&bdns.MockClient{}}
73
74
75
76
77 var prob *probs.ProblemDetails
78 var took time.Duration
79 for i := 0; i < 20; i++ {
80 started := time.Now()
81 _, _, prob = va.fetchHTTP(ctx, "unroutable.invalid", "/.well-known/acme-challenge/whatever")
82 took = time.Since(started)
83 if prob != nil && strings.Contains(prob.Detail, "Network unreachable") {
84 continue
85 } else {
86 break
87 }
88 }
89 if prob == nil {
90 t.Fatalf("Connection should've timed out")
91 }
92
93
94
95 if took < va.singleDialTimeout {
96 t.Fatalf("fetch returned before %s (took: %s) with %#v", va.singleDialTimeout, took, prob)
97 }
98 if took > 2*va.singleDialTimeout {
99 t.Fatalf("fetch didn't timeout after %s (took: %s)", va.singleDialTimeout, took)
100 }
101 test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
102 expectMatch := regexp.MustCompile(
103 "Fetching http://unroutable.invalid/.well-known/acme-challenge/.*: Timeout during connect")
104 if !expectMatch.MatchString(prob.Detail) {
105 t.Errorf("Problem details incorrect. Got %q, expected to match %q",
106 prob.Detail, expectMatch)
107 }
108 }
109
110 func TestHTTPTransport(t *testing.T) {
111 dummyDialerFunc := func(_ context.Context, _, _ string) (net.Conn, error) {
112 return nil, nil
113 }
114 transport := httpTransport(dummyDialerFunc)
115
116
117 test.AssertEquals(t, transport.TLSClientConfig.InsecureSkipVerify, true)
118
119 test.AssertEquals(t, transport.DisableKeepAlives, true)
120 test.AssertEquals(t, transport.MaxIdleConns, 1)
121 test.AssertEquals(t, transport.IdleConnTimeout.String(), "1s")
122 test.AssertEquals(t, transport.TLSHandshakeTimeout.String(), "10s")
123 }
124
125 func TestHTTPValidationTarget(t *testing.T) {
126
127
128 testCases := []struct {
129 Name string
130 Host string
131 ExpectedError error
132 ExpectedIPs []string
133 }{
134 {
135 Name: "No IPs for host",
136 Host: "always.invalid",
137 ExpectedError: berrors.DNSError("No valid IP addresses found for always.invalid"),
138 },
139 {
140 Name: "Only IPv4 addrs for host",
141 Host: "some.example.com",
142 ExpectedIPs: []string{"127.0.0.1"},
143 },
144 {
145 Name: "Only IPv6 addrs for host",
146 Host: "ipv6.localhost",
147 ExpectedIPs: []string{"::1"},
148 },
149 {
150 Name: "Both IPv6 and IPv4 addrs for host",
151 Host: "ipv4.and.ipv6.localhost",
152
153 ExpectedIPs: []string{"::1", "127.0.0.1"},
154 },
155 }
156
157 const (
158 examplePort = 1234
159 examplePath = "/.well-known/path/i/took"
160 exampleQuery = "my-path=was&my=own"
161 )
162
163 va, _ := setup(nil, 0, "", nil)
164 for _, tc := range testCases {
165 t.Run(tc.Name, func(t *testing.T) {
166 target, err := va.newHTTPValidationTarget(
167 context.Background(),
168 tc.Host,
169 examplePort,
170 examplePath,
171 exampleQuery)
172 if err != nil && tc.ExpectedError == nil {
173 t.Fatalf("Unexpected error from NewHTTPValidationTarget: %v", err)
174 } else if err != nil && tc.ExpectedError != nil {
175 test.AssertMarshaledEquals(t, err, tc.ExpectedError)
176 } else if err == nil {
177
178 test.AssertNotEquals(t, target.host, "")
179 test.AssertNotEquals(t, target.port, 0)
180 test.AssertNotEquals(t, target.path, "")
181
182
183 for i, expectedIP := range tc.ExpectedIPs {
184 gotIP := target.ip()
185 if gotIP == nil {
186 t.Errorf("Expected IP %d to be %s got nil", i, expectedIP)
187 } else {
188 test.AssertEquals(t, gotIP.String(), expectedIP)
189 }
190
191 _ = target.nextIP()
192 }
193 }
194 })
195 }
196 }
197
198 func TestExtractRequestTarget(t *testing.T) {
199 mustURL := func(rawURL string) *url.URL {
200 return must.Do(url.Parse(rawURL))
201 }
202
203 testCases := []struct {
204 Name string
205 Req *http.Request
206 ExpectedError error
207 ExpectedHost string
208 ExpectedPort int
209 }{
210 {
211 Name: "nil input req",
212 ExpectedError: fmt.Errorf("redirect HTTP request was nil"),
213 },
214 {
215 Name: "invalid protocol scheme",
216 Req: &http.Request{
217 URL: mustURL("gopher://letsencrypt.org"),
218 },
219 ExpectedError: fmt.Errorf("Invalid protocol scheme in redirect target. " +
220 `Only "http" and "https" protocol schemes are supported, ` +
221 `not "gopher"`),
222 },
223 {
224 Name: "invalid explicit port",
225 Req: &http.Request{
226 URL: mustURL("https://weird.port.letsencrypt.org:9999"),
227 },
228 ExpectedError: fmt.Errorf("Invalid port in redirect target. Only ports 80 " +
229 "and 443 are supported, not 9999"),
230 },
231 {
232 Name: "invalid empty hostname",
233 Req: &http.Request{
234 URL: mustURL("https:///who/needs/a/hostname?not=me"),
235 },
236 ExpectedError: errors.New("Invalid empty hostname in redirect target"),
237 },
238 {
239 Name: "invalid .well-known hostname",
240 Req: &http.Request{
241 URL: mustURL("https://my.webserver.is.misconfigured.well-known/acme-challenge/xxx"),
242 },
243 ExpectedError: errors.New(`Invalid host in redirect target "my.webserver.is.misconfigured.well-known". Check webserver config for missing '/' in redirect target.`),
244 },
245 {
246 Name: "invalid non-iana hostname",
247 Req: &http.Request{
248 URL: mustURL("https://my.tld.is.cpu/pretty/cool/right?yeah=Ithoughtsotoo"),
249 },
250 ExpectedError: errors.New("Invalid hostname in redirect target, must end in IANA registered TLD"),
251 },
252 {
253 Name: "bare IP",
254 Req: &http.Request{
255 URL: mustURL("https://10.10.10.10"),
256 },
257 ExpectedError: fmt.Errorf(`Invalid host in redirect target "10.10.10.10". ` +
258 "Only domain names are supported, not IP addresses"),
259 },
260 {
261 Name: "valid HTTP redirect, explicit port",
262 Req: &http.Request{
263 URL: mustURL("http://cpu.letsencrypt.org:80"),
264 },
265 ExpectedHost: "cpu.letsencrypt.org",
266 ExpectedPort: 80,
267 },
268 {
269 Name: "valid HTTP redirect, implicit port",
270 Req: &http.Request{
271 URL: mustURL("http://cpu.letsencrypt.org"),
272 },
273 ExpectedHost: "cpu.letsencrypt.org",
274 ExpectedPort: 80,
275 },
276 {
277 Name: "valid HTTPS redirect, explicit port",
278 Req: &http.Request{
279 URL: mustURL("https://cpu.letsencrypt.org:443/hello.world"),
280 },
281 ExpectedHost: "cpu.letsencrypt.org",
282 ExpectedPort: 443,
283 },
284 {
285 Name: "valid HTTPS redirect, implicit port",
286 Req: &http.Request{
287 URL: mustURL("https://cpu.letsencrypt.org/hello.world"),
288 },
289 ExpectedHost: "cpu.letsencrypt.org",
290 ExpectedPort: 443,
291 },
292 }
293
294 va, _ := setup(nil, 0, "", nil)
295 for _, tc := range testCases {
296 t.Run(tc.Name, func(t *testing.T) {
297 host, port, err := va.extractRequestTarget(tc.Req)
298 if err != nil && tc.ExpectedError == nil {
299 t.Errorf("Expected nil err got %v", err)
300 } else if err != nil && tc.ExpectedError != nil {
301 test.AssertEquals(t, err.Error(), tc.ExpectedError.Error())
302 } else if err == nil && tc.ExpectedError != nil {
303 t.Errorf("Expected err %v, got nil", tc.ExpectedError)
304 } else {
305 test.AssertEquals(t, host, tc.ExpectedHost)
306 test.AssertEquals(t, port, tc.ExpectedPort)
307 }
308 })
309 }
310 }
311
312
313
314
315 func TestHTTPValidationDNSError(t *testing.T) {
316 va, mockLog := setup(nil, 0, "", nil)
317
318 _, _, prob := va.fetchHTTP(ctx, "always.error", "/.well-known/acme-challenge/whatever")
319 test.AssertError(t, prob, "Expected validation fetch to fail")
320 matchingLines := mockLog.GetAllMatching(`read udp: some net error`)
321 if len(matchingLines) != 1 {
322 t.Errorf("Didn't see expected DNS error logged. Instead, got:\n%s",
323 strings.Join(mockLog.GetAllMatching(`.*`), "\n"))
324 }
325 }
326
327
328
329
330
331 func TestHTTPValidationDNSIdMismatchError(t *testing.T) {
332 va, mockLog := setup(nil, 0, "", nil)
333
334 _, _, prob := va.fetchHTTP(ctx, "id.mismatch", "/.well-known/acme-challenge/whatever")
335 test.AssertError(t, prob, "Expected validation fetch to fail")
336 matchingLines := mockLog.GetAllMatching(`logDNSError ID mismatch`)
337 if len(matchingLines) != 1 {
338 t.Errorf("Didn't see expected DNS error logged. Instead, got:\n%s",
339 strings.Join(mockLog.GetAllMatching(`.*`), "\n"))
340 }
341 expectedRegex := regexp.MustCompile(
342 `INFO: logDNSError ID mismatch ` +
343 `chosenServer=\[mock.server\] ` +
344 `hostname=\[id\.mismatch\] ` +
345 `respHostname=\[id\.mismatch\.\] ` +
346 `queryType=\[A\] ` +
347 `msg=\[([A-Za-z0-9+=/\=]+)\] ` +
348 `resp=\[([A-Za-z0-9+=/\=]+)\] ` +
349 `err\=\[dns: id mismatch\]`,
350 )
351
352 matches := expectedRegex.FindAllStringSubmatch(matchingLines[0], -1)
353 test.AssertEquals(t, len(matches), 1)
354 submatches := matches[0]
355 test.AssertEquals(t, len(submatches), 3)
356
357 msgBytes, err := base64.StdEncoding.DecodeString(submatches[1])
358 test.AssertNotError(t, err, "bad base64 encoded query msg")
359 msg := new(dns.Msg)
360 err = msg.Unpack(msgBytes)
361 test.AssertNotError(t, err, "bad packed query msg")
362
363 respBytes, err := base64.StdEncoding.DecodeString(submatches[2])
364 test.AssertNotError(t, err, "bad base64 encoded resp msg")
365 resp := new(dns.Msg)
366 err = resp.Unpack(respBytes)
367 test.AssertNotError(t, err, "bad packed response msg")
368 }
369
370 func TestSetupHTTPValidation(t *testing.T) {
371 va, _ := setup(nil, 0, "", nil)
372
373 mustTarget := func(t *testing.T, host string, port int, path string) *httpValidationTarget {
374 target, err := va.newHTTPValidationTarget(
375 context.Background(),
376 host,
377 port,
378 path,
379 "")
380 if err != nil {
381 t.Fatalf("Failed to construct httpValidationTarget for %q", host)
382 return nil
383 }
384 return target
385 }
386
387 httpInputURL := "http://ipv4.and.ipv6.localhost/yellow/brick/road"
388 httpsInputURL := "https://ipv4.and.ipv6.localhost/yellow/brick/road"
389
390 testCases := []struct {
391 Name string
392 InputURL string
393 InputTarget *httpValidationTarget
394 ExpectedRecord core.ValidationRecord
395 ExpectedDialer *preresolvedDialer
396 ExpectedError error
397 }{
398 {
399 Name: "nil target",
400 InputURL: httpInputURL,
401 ExpectedError: fmt.Errorf("httpValidationTarget can not be nil"),
402 },
403 {
404 Name: "empty input URL",
405 InputTarget: &httpValidationTarget{},
406 ExpectedError: fmt.Errorf("reqURL can not be nil"),
407 },
408 {
409 Name: "target with no IPs",
410 InputURL: httpInputURL,
411 InputTarget: &httpValidationTarget{
412 host: "ipv4.and.ipv6.localhost",
413 port: va.httpPort,
414 path: "idk",
415 },
416 ExpectedRecord: core.ValidationRecord{
417 URL: "http://ipv4.and.ipv6.localhost/yellow/brick/road",
418 Hostname: "ipv4.and.ipv6.localhost",
419 Port: strconv.Itoa(va.httpPort),
420 },
421 ExpectedError: fmt.Errorf(`host "ipv4.and.ipv6.localhost" has no IP addresses remaining to use`),
422 },
423 {
424 Name: "HTTP input req",
425 InputTarget: mustTarget(t, "ipv4.and.ipv6.localhost", va.httpPort, "/yellow/brick/road"),
426 InputURL: httpInputURL,
427 ExpectedRecord: core.ValidationRecord{
428 Hostname: "ipv4.and.ipv6.localhost",
429 Port: strconv.Itoa(va.httpPort),
430 URL: "http://ipv4.and.ipv6.localhost/yellow/brick/road",
431 AddressesResolved: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")},
432 AddressUsed: net.ParseIP("::1"),
433 },
434 ExpectedDialer: &preresolvedDialer{
435 ip: net.ParseIP("::1"),
436 port: va.httpPort,
437 timeout: va.singleDialTimeout,
438 },
439 },
440 {
441 Name: "HTTPS input req",
442 InputTarget: mustTarget(t, "ipv4.and.ipv6.localhost", va.httpsPort, "/yellow/brick/road"),
443 InputURL: httpsInputURL,
444 ExpectedRecord: core.ValidationRecord{
445 Hostname: "ipv4.and.ipv6.localhost",
446 Port: strconv.Itoa(va.httpsPort),
447 URL: "https://ipv4.and.ipv6.localhost/yellow/brick/road",
448 AddressesResolved: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")},
449 AddressUsed: net.ParseIP("::1"),
450 },
451 ExpectedDialer: &preresolvedDialer{
452 ip: net.ParseIP("::1"),
453 port: va.httpsPort,
454 timeout: va.singleDialTimeout,
455 },
456 },
457 }
458
459 for _, tc := range testCases {
460 t.Run(tc.Name, func(t *testing.T) {
461 outDialer, outRecord, err := va.setupHTTPValidation(tc.InputURL, tc.InputTarget)
462 if err != nil && tc.ExpectedError == nil {
463 t.Errorf("Expected nil error, got %v", err)
464 } else if err == nil && tc.ExpectedError != nil {
465 t.Errorf("Expected %v error, got nil", tc.ExpectedError)
466 } else if err != nil && tc.ExpectedError != nil {
467 test.AssertEquals(t, err.Error(), tc.ExpectedError.Error())
468 }
469 if tc.ExpectedDialer == nil && outDialer != nil {
470 t.Errorf("Expected nil dialer, got %v", outDialer)
471 } else if tc.ExpectedDialer != nil {
472 test.AssertMarshaledEquals(t, outDialer, tc.ExpectedDialer)
473 }
474
475 test.AssertMarshaledEquals(t, outRecord, tc.ExpectedRecord)
476 })
477 }
478 }
479
480
481 func httpTestSrv(t *testing.T) *httptest.Server {
482 t.Helper()
483 mux := http.NewServeMux()
484 server := httptest.NewUnstartedServer(mux)
485
486 server.Start()
487 httpPort := getPort(server)
488
489
490 mux.HandleFunc("/ok", func(resp http.ResponseWriter, req *http.Request) {
491 resp.WriteHeader(http.StatusOK)
492 fmt.Fprint(resp, "ok")
493 })
494
495
496
497 mux.HandleFunc("/timeout", func(resp http.ResponseWriter, req *http.Request) {
498 time.Sleep(time.Second)
499 resp.WriteHeader(http.StatusOK)
500 fmt.Fprint(resp, "sorry, I'm a slow server")
501 })
502
503
504
505 mux.HandleFunc("/loop", func(resp http.ResponseWriter, req *http.Request) {
506 http.Redirect(
507 resp,
508 req,
509 fmt.Sprintf("http://example.com:%d/loop", httpPort),
510 http.StatusMovedPermanently)
511 })
512
513
514
515
516 for i := 0; i <= maxRedirect+1; i++ {
517
518 i := i
519 mux.HandleFunc(fmt.Sprintf("/max-redirect/%d", i),
520 func(resp http.ResponseWriter, req *http.Request) {
521 http.Redirect(
522 resp,
523 req,
524 fmt.Sprintf("http://example.com:%d/max-redirect/%d", httpPort, i+1),
525 http.StatusMovedPermanently,
526 )
527 })
528 }
529
530
531 mux.HandleFunc("/redir-bad-proto", func(resp http.ResponseWriter, req *http.Request) {
532 http.Redirect(
533 resp,
534 req,
535 "gopher://example.com",
536 http.StatusMovedPermanently,
537 )
538 })
539
540
541
542 mux.HandleFunc("/redir-bad-port", func(resp http.ResponseWriter, req *http.Request) {
543 http.Redirect(
544 resp,
545 req,
546 "https://example.com:1987",
547 http.StatusMovedPermanently,
548 )
549 })
550
551
552 mux.HandleFunc("/redir-bad-host", func(resp http.ResponseWriter, req *http.Request) {
553 http.Redirect(
554 resp,
555 req,
556 "https://127.0.0.1",
557 http.StatusMovedPermanently,
558 )
559 })
560
561 mux.HandleFunc("/bad-status-code", func(resp http.ResponseWriter, req *http.Request) {
562 resp.WriteHeader(http.StatusGone)
563 fmt.Fprint(resp, "sorry, I'm gone")
564 })
565
566
567 mux.HandleFunc("/303-see-other", func(resp http.ResponseWriter, req *http.Request) {
568 http.Redirect(
569 resp,
570 req,
571 "http://example.org/303-see-other",
572 http.StatusSeeOther,
573 )
574 })
575
576 tooLargeBuf := bytes.NewBuffer([]byte{})
577 for i := 0; i < maxResponseSize+10; i++ {
578 tooLargeBuf.WriteByte(byte(97))
579 }
580 mux.HandleFunc("/resp-too-big", func(resp http.ResponseWriter, req *http.Request) {
581 resp.WriteHeader(http.StatusOK)
582 fmt.Fprint(resp, tooLargeBuf)
583 })
584
585
586
587 tooLargeInvalidUTF8 := bytes.NewBuffer([]byte{})
588 tooLargeInvalidUTF8.WriteString("f\xffoo")
589 tooLargeInvalidUTF8.Write(tooLargeBuf.Bytes())
590
591
592
593
594 mux.HandleFunc("/invalid-utf8-body", func(resp http.ResponseWriter, req *http.Request) {
595 resp.WriteHeader(http.StatusOK)
596 fmt.Fprint(resp, tooLargeInvalidUTF8)
597 })
598
599 mux.HandleFunc("/redir-path-too-long", func(resp http.ResponseWriter, req *http.Request) {
600 http.Redirect(
601 resp,
602 req,
603 "https://example.com/this-is-too-long-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
604 http.StatusMovedPermanently)
605 })
606
607
608 mux.HandleFunc("/redir-uppercase-publicsuffix", func(resp http.ResponseWriter, req *http.Request) {
609 http.Redirect(
610 resp,
611 req,
612 "http://example.COM/ok",
613 http.StatusMovedPermanently)
614 })
615
616
617 mux.HandleFunc("/printf-verbs", func(resp http.ResponseWriter, req *http.Request) {
618 resp.WriteHeader(http.StatusOK)
619 fmt.Fprint(resp, "%"+"2F.well-known%"+"2F"+tooLargeBuf.String())
620 })
621
622 return server
623 }
624
625 type testNetErr struct{}
626
627 func (e *testNetErr) Error() string {
628 return "testNetErr"
629 }
630
631 func (e *testNetErr) Temporary() bool {
632 return false
633 }
634
635 func (e *testNetErr) Timeout() bool {
636 return false
637 }
638
639 func TestFallbackErr(t *testing.T) {
640 untypedErr := errors.New("the least interesting kind of error")
641 berr := berrors.InternalServerError("code violet: class neptune")
642 netOpErr := &net.OpError{
643 Op: "siphon",
644 Err: fmt.Errorf("port was clogged. please empty packets"),
645 }
646 netDialOpErr := &net.OpError{
647 Op: "dial",
648 Err: fmt.Errorf("your call is important to us - please stay on the line"),
649 }
650 netErr := &testNetErr{}
651
652 testCases := []struct {
653 Name string
654 Err error
655 ExpectFallback bool
656 }{
657 {
658 Name: "Nil error",
659 Err: nil,
660 },
661 {
662 Name: "Standard untyped error",
663 Err: untypedErr,
664 },
665 {
666 Name: "A Boulder error instance",
667 Err: berr,
668 },
669 {
670 Name: "A non-dial net.OpError instance",
671 Err: netOpErr,
672 },
673 {
674 Name: "A dial net.OpError instance",
675 Err: netDialOpErr,
676 ExpectFallback: true,
677 },
678 {
679 Name: "A generic net.Error instance",
680 Err: netErr,
681 },
682 {
683 Name: "A URL error wrapping a standard error",
684 Err: &url.Error{
685 Op: "ivy",
686 URL: "https://en.wikipedia.org/wiki/Operation_Ivy_(band)",
687 Err: errors.New("take warning"),
688 },
689 },
690 {
691 Name: "A URL error wrapping a nil error",
692 Err: &url.Error{
693 Err: nil,
694 },
695 },
696 {
697 Name: "A URL error wrapping a Boulder error instance",
698 Err: &url.Error{
699 Err: berr,
700 },
701 },
702 {
703 Name: "A URL error wrapping a non-dial net OpError",
704 Err: &url.Error{
705 Err: netOpErr,
706 },
707 },
708 {
709 Name: "A URL error wrapping a dial net.OpError",
710 Err: &url.Error{
711 Err: netDialOpErr,
712 },
713 ExpectFallback: true,
714 },
715 {
716 Name: "A URL error wrapping a generic net Error",
717 Err: &url.Error{
718 Err: netErr,
719 },
720 },
721 }
722
723 for _, tc := range testCases {
724 t.Run(tc.Name, func(t *testing.T) {
725 if isFallback := fallbackErr(tc.Err); isFallback != tc.ExpectFallback {
726 t.Errorf(
727 "Expected fallbackErr for %t to be %v was %v\n",
728 tc.Err, tc.ExpectFallback, isFallback)
729 }
730 })
731 }
732 }
733
734 func TestFetchHTTP(t *testing.T) {
735
736 testSrv := httpTestSrv(t)
737 defer testSrv.Close()
738
739
740
741 va, _ := setup(testSrv, 0, "", nil)
742
743
744 httpPort := getPort(testSrv)
745
746
747
748
749
750
751
752
753 expectedLoopRecords := []core.ValidationRecord{}
754 for i := 0; i < 2; i++ {
755
756 url := "http://example.com/loop"
757 if i != 0 {
758 url = fmt.Sprintf("http://example.com:%d/loop", httpPort)
759 }
760 expectedLoopRecords = append(expectedLoopRecords,
761 core.ValidationRecord{
762 Hostname: "example.com",
763 Port: strconv.Itoa(httpPort),
764 URL: url,
765 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
766 AddressUsed: net.ParseIP("127.0.0.1"),
767 })
768 }
769
770
771
772
773 expectedTooManyRedirRecords := []core.ValidationRecord{}
774 for i := 0; i <= maxRedirect+1; i++ {
775
776 url := "http://example.com/max-redirect/0"
777 if i != 0 {
778 url = fmt.Sprintf("http://example.com:%d/max-redirect/%d", httpPort, i)
779 }
780 expectedTooManyRedirRecords = append(expectedTooManyRedirRecords,
781 core.ValidationRecord{
782 Hostname: "example.com",
783 Port: strconv.Itoa(httpPort),
784 URL: url,
785 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
786 AddressUsed: net.ParseIP("127.0.0.1"),
787 })
788 }
789
790 expectedTruncatedResp := bytes.NewBuffer([]byte{})
791 for i := 0; i < maxResponseSize; i++ {
792 expectedTruncatedResp.WriteByte(byte(97))
793 }
794
795 testCases := []struct {
796 Name string
797 Host string
798 Path string
799 ExpectedBody string
800 ExpectedRecords []core.ValidationRecord
801 ExpectedProblem *probs.ProblemDetails
802 }{
803 {
804 Name: "No IPs for host",
805 Host: "always.invalid",
806 Path: "/.well-known/whatever",
807 ExpectedProblem: probs.DNS(
808 "No valid IP addresses found for always.invalid"),
809
810
811 ExpectedRecords: nil,
812 },
813 {
814 Name: "Timeout for host with standard ACME allowed port",
815 Host: "example.com",
816 Path: "/timeout",
817 ExpectedProblem: probs.Connection(
818 "127.0.0.1: Fetching http://example.com/timeout: " +
819 "Timeout after connect (your server may be slow or overloaded)"),
820 ExpectedRecords: []core.ValidationRecord{
821 {
822 Hostname: "example.com",
823 Port: strconv.Itoa(httpPort),
824 URL: "http://example.com/timeout",
825 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
826 AddressUsed: net.ParseIP("127.0.0.1"),
827 },
828 },
829 },
830 {
831 Name: "Connecting to bad port",
832 Host: "example.com:" + strconv.Itoa(httpPort),
833 Path: "/timeout",
834 ExpectedProblem: probs.Connection(
835 "127.0.0.1: Fetching http://example.com:" + strconv.Itoa(httpPort) + "/timeout: " +
836 "Error getting validation data"),
837 ExpectedRecords: []core.ValidationRecord{
838 {
839 Hostname: "example.com:" + strconv.Itoa(httpPort),
840 Port: strconv.Itoa(httpPort),
841 URL: "http://example.com:" + strconv.Itoa(httpPort) + "/timeout",
842 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
843 AddressUsed: net.ParseIP("127.0.0.1"),
844 },
845 },
846 },
847 {
848 Name: "Redirect loop",
849 Host: "example.com",
850 Path: "/loop",
851 ExpectedProblem: probs.Connection(fmt.Sprintf(
852 "127.0.0.1: Fetching http://example.com:%d/loop: Redirect loop detected", httpPort)),
853 ExpectedRecords: expectedLoopRecords,
854 },
855 {
856 Name: "Too many redirects",
857 Host: "example.com",
858 Path: "/max-redirect/0",
859 ExpectedProblem: probs.Connection(fmt.Sprintf(
860 "127.0.0.1: Fetching http://example.com:%d/max-redirect/12: Too many redirects", httpPort)),
861 ExpectedRecords: expectedTooManyRedirRecords,
862 },
863 {
864 Name: "Redirect to bad protocol",
865 Host: "example.com",
866 Path: "/redir-bad-proto",
867 ExpectedProblem: probs.Connection(
868 "127.0.0.1: Fetching gopher://example.com: Invalid protocol scheme in " +
869 `redirect target. Only "http" and "https" protocol schemes ` +
870 `are supported, not "gopher"`),
871 ExpectedRecords: []core.ValidationRecord{
872 {
873 Hostname: "example.com",
874 Port: strconv.Itoa(httpPort),
875 URL: "http://example.com/redir-bad-proto",
876 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
877 AddressUsed: net.ParseIP("127.0.0.1"),
878 },
879 },
880 },
881 {
882 Name: "Redirect to bad port",
883 Host: "example.com",
884 Path: "/redir-bad-port",
885 ExpectedProblem: probs.Connection(fmt.Sprintf(
886 "127.0.0.1: Fetching https://example.com:1987: Invalid port in redirect target. "+
887 "Only ports %d and 443 are supported, not 1987", httpPort)),
888 ExpectedRecords: []core.ValidationRecord{
889 {
890 Hostname: "example.com",
891 Port: strconv.Itoa(httpPort),
892 URL: "http://example.com/redir-bad-port",
893 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
894 AddressUsed: net.ParseIP("127.0.0.1"),
895 },
896 },
897 },
898 {
899 Name: "Redirect to bad host (bare IP address)",
900 Host: "example.com",
901 Path: "/redir-bad-host",
902 ExpectedProblem: probs.Connection(
903 "127.0.0.1: Fetching https://127.0.0.1: Invalid host in redirect target " +
904 `"127.0.0.1". Only domain names are supported, not IP addresses`),
905 ExpectedRecords: []core.ValidationRecord{
906 {
907 Hostname: "example.com",
908 Port: strconv.Itoa(httpPort),
909 URL: "http://example.com/redir-bad-host",
910 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
911 AddressUsed: net.ParseIP("127.0.0.1"),
912 },
913 },
914 },
915 {
916 Name: "Redirect to long path",
917 Host: "example.com",
918 Path: "/redir-path-too-long",
919 ExpectedProblem: probs.Connection(
920 "127.0.0.1: Fetching https://example.com/this-is-too-long-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789: Redirect target too long"),
921 ExpectedRecords: []core.ValidationRecord{
922 {
923 Hostname: "example.com",
924 Port: strconv.Itoa(httpPort),
925 URL: "http://example.com/redir-path-too-long",
926 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
927 AddressUsed: net.ParseIP("127.0.0.1"),
928 },
929 },
930 },
931 {
932 Name: "Wrong HTTP status code",
933 Host: "example.com",
934 Path: "/bad-status-code",
935 ExpectedProblem: probs.Unauthorized(
936 "127.0.0.1: Invalid response from http://example.com/bad-status-code: 410"),
937 ExpectedRecords: []core.ValidationRecord{
938 {
939 Hostname: "example.com",
940 Port: strconv.Itoa(httpPort),
941 URL: "http://example.com/bad-status-code",
942 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
943 AddressUsed: net.ParseIP("127.0.0.1"),
944 },
945 },
946 },
947 {
948 Name: "HTTP status code 303 redirect",
949 Host: "example.com",
950 Path: "/303-see-other",
951 ExpectedProblem: probs.Connection(
952 "127.0.0.1: Fetching http://example.org/303-see-other: received disallowed redirect status code"),
953 ExpectedRecords: []core.ValidationRecord{
954 {
955 Hostname: "example.com",
956 Port: strconv.Itoa(httpPort),
957 URL: "http://example.com/303-see-other",
958 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
959 AddressUsed: net.ParseIP("127.0.0.1"),
960 },
961 },
962 },
963 {
964 Name: "Response too large",
965 Host: "example.com",
966 Path: "/resp-too-big",
967 ExpectedProblem: probs.Unauthorized(fmt.Sprintf(
968 "127.0.0.1: Invalid response from http://example.com/resp-too-big: %q", expectedTruncatedResp.String(),
969 )),
970 ExpectedRecords: []core.ValidationRecord{
971 {
972 Hostname: "example.com",
973 Port: strconv.Itoa(httpPort),
974 URL: "http://example.com/resp-too-big",
975 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
976 AddressUsed: net.ParseIP("127.0.0.1"),
977 },
978 },
979 },
980 {
981 Name: "Broken IPv6 only",
982 Host: "ipv6.localhost",
983 Path: "/ok",
984 ExpectedProblem: probs.Connection(
985 "::1: Fetching http://ipv6.localhost/ok: Error getting validation data"),
986 ExpectedRecords: []core.ValidationRecord{
987 {
988 Hostname: "ipv6.localhost",
989 Port: strconv.Itoa(httpPort),
990 URL: "http://ipv6.localhost/ok",
991 AddressesResolved: []net.IP{net.ParseIP("::1")},
992 AddressUsed: net.ParseIP("::1"),
993 },
994 },
995 },
996 {
997 Name: "Dual homed w/ broken IPv6, working IPv4",
998 Host: "ipv4.and.ipv6.localhost",
999 Path: "/ok",
1000 ExpectedBody: "ok",
1001 ExpectedRecords: []core.ValidationRecord{
1002 {
1003 Hostname: "ipv4.and.ipv6.localhost",
1004 Port: strconv.Itoa(httpPort),
1005 URL: "http://ipv4.and.ipv6.localhost/ok",
1006 AddressesResolved: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")},
1007
1008 AddressUsed: net.ParseIP("::1"),
1009 },
1010 {
1011 Hostname: "ipv4.and.ipv6.localhost",
1012 Port: strconv.Itoa(httpPort),
1013 URL: "http://ipv4.and.ipv6.localhost/ok",
1014 AddressesResolved: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")},
1015
1016 AddressUsed: net.ParseIP("127.0.0.1"),
1017 },
1018 },
1019 },
1020 {
1021 Name: "Working IPv4 only",
1022 Host: "example.com",
1023 Path: "/ok",
1024 ExpectedBody: "ok",
1025 ExpectedRecords: []core.ValidationRecord{
1026 {
1027 Hostname: "example.com",
1028 Port: strconv.Itoa(httpPort),
1029 URL: "http://example.com/ok",
1030 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
1031 AddressUsed: net.ParseIP("127.0.0.1"),
1032 },
1033 },
1034 },
1035 {
1036 Name: "Redirect to uppercase Public Suffix",
1037 Host: "example.com",
1038 Path: "/redir-uppercase-publicsuffix",
1039 ExpectedBody: "ok",
1040 ExpectedRecords: []core.ValidationRecord{
1041 {
1042 Hostname: "example.com",
1043 Port: strconv.Itoa(httpPort),
1044 URL: "http://example.com/redir-uppercase-publicsuffix",
1045 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
1046 AddressUsed: net.ParseIP("127.0.0.1"),
1047 },
1048 {
1049 Hostname: "example.com",
1050 Port: strconv.Itoa(httpPort),
1051 URL: "http://example.com/ok",
1052 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
1053 AddressUsed: net.ParseIP("127.0.0.1"),
1054 },
1055 },
1056 },
1057 {
1058 Name: "Reflected response body containing printf verbs",
1059 Host: "example.com",
1060 Path: "/printf-verbs",
1061 ExpectedProblem: &probs.ProblemDetails{
1062 Type: probs.UnauthorizedProblem,
1063 Detail: fmt.Sprintf("127.0.0.1: Invalid response from http://example.com/printf-verbs: %q",
1064 ("%2F.well-known%2F" + expectedTruncatedResp.String())[:maxResponseSize]),
1065 HTTPStatus: http.StatusForbidden,
1066 },
1067 ExpectedRecords: []core.ValidationRecord{
1068 {
1069 Hostname: "example.com",
1070 Port: strconv.Itoa(httpPort),
1071 URL: "http://example.com/printf-verbs",
1072 AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
1073 AddressUsed: net.ParseIP("127.0.0.1"),
1074 },
1075 },
1076 },
1077 }
1078
1079 for _, tc := range testCases {
1080 t.Run(tc.Name, func(t *testing.T) {
1081 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
1082 defer cancel()
1083 body, records, prob := va.fetchHTTP(ctx, tc.Host, tc.Path)
1084 if prob != nil && tc.ExpectedProblem == nil {
1085 t.Errorf("expected nil prob, got %#v\n", prob)
1086 } else if prob == nil && tc.ExpectedProblem != nil {
1087 t.Errorf("expected %#v prob, got nil", tc.ExpectedProblem)
1088 } else if prob != nil && tc.ExpectedProblem != nil {
1089 test.AssertMarshaledEquals(t, prob, tc.ExpectedProblem)
1090 } else {
1091 test.AssertEquals(t, string(body), tc.ExpectedBody)
1092 }
1093
1094 test.AssertMarshaledEquals(t, records, tc.ExpectedRecords)
1095 })
1096 }
1097 }
1098
1099
1100 const pathWrongToken = "i6lNAC4lOOLYCl-A08VJt9z_tKYvVk63Dumo8icsBjQ"
1101 const path404 = "404"
1102 const path500 = "500"
1103 const pathFound = "GBq8SwWq3JsbREFdCamk5IX3KLsxW5ULeGs98Ajl_UM"
1104 const pathMoved = "5J4FIMrWNfmvHZo-QpKZngmuhqZGwRm21-oEgUDstJM"
1105 const pathRedirectInvalidPort = "port-redirect"
1106 const pathWait = "wait"
1107 const pathWaitLong = "wait-long"
1108 const pathReLookup = "7e-P57coLM7D3woNTp_xbJrtlkDYy6PWf3mSSbLwCr4"
1109 const pathReLookupInvalid = "re-lookup-invalid"
1110 const pathRedirectToFailingURL = "re-to-failing-url"
1111 const pathLooper = "looper"
1112 const pathValid = "valid"
1113 const rejectUserAgent = "rejectMe"
1114
1115 func httpSrv(t *testing.T, token string) *httptest.Server {
1116 m := http.NewServeMux()
1117
1118 server := httptest.NewUnstartedServer(m)
1119
1120 defaultToken := token
1121 currentToken := defaultToken
1122
1123 m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
1124 if strings.HasSuffix(r.URL.Path, path404) {
1125 t.Logf("HTTPSRV: Got a 404 req\n")
1126 http.NotFound(w, r)
1127 } else if strings.HasSuffix(r.URL.Path, path500) {
1128 t.Logf("HTTPSRV: Got a 500 req\n")
1129 http.Error(w, "Internal Server Error", http.StatusInternalServerError)
1130 } else if strings.HasSuffix(r.URL.Path, pathMoved) {
1131 t.Logf("HTTPSRV: Got a http.StatusMovedPermanently redirect req\n")
1132 if currentToken == defaultToken {
1133 currentToken = pathMoved
1134 }
1135 http.Redirect(w, r, pathValid, http.StatusMovedPermanently)
1136 } else if strings.HasSuffix(r.URL.Path, pathFound) {
1137 t.Logf("HTTPSRV: Got a http.StatusFound redirect req\n")
1138 if currentToken == defaultToken {
1139 currentToken = pathFound
1140 }
1141 http.Redirect(w, r, pathMoved, http.StatusFound)
1142 } else if strings.HasSuffix(r.URL.Path, pathWait) {
1143 t.Logf("HTTPSRV: Got a wait req\n")
1144 time.Sleep(time.Second * 3)
1145 } else if strings.HasSuffix(r.URL.Path, pathWaitLong) {
1146 t.Logf("HTTPSRV: Got a wait-long req\n")
1147 time.Sleep(time.Second * 10)
1148 } else if strings.HasSuffix(r.URL.Path, pathReLookup) {
1149 t.Logf("HTTPSRV: Got a redirect req to a valid hostname\n")
1150 if currentToken == defaultToken {
1151 currentToken = pathReLookup
1152 }
1153 port := getPort(server)
1154 http.Redirect(w, r, fmt.Sprintf("http://other.valid.com:%d/path", port), http.StatusFound)
1155 } else if strings.HasSuffix(r.URL.Path, pathReLookupInvalid) {
1156 t.Logf("HTTPSRV: Got a redirect req to an invalid hostname\n")
1157 http.Redirect(w, r, "http://invalid.invalid/path", http.StatusFound)
1158 } else if strings.HasSuffix(r.URL.Path, pathRedirectToFailingURL) {
1159 t.Logf("HTTPSRV: Redirecting to a URL that will fail\n")
1160 port := getPort(server)
1161 http.Redirect(w, r, fmt.Sprintf("http://other.valid.com:%d/%s", port, path500), http.StatusMovedPermanently)
1162 } else if strings.HasSuffix(r.URL.Path, pathLooper) {
1163 t.Logf("HTTPSRV: Got a loop req\n")
1164 http.Redirect(w, r, r.URL.String(), http.StatusMovedPermanently)
1165 } else if strings.HasSuffix(r.URL.Path, pathRedirectInvalidPort) {
1166 t.Logf("HTTPSRV: Got a port redirect req\n")
1167
1168 http.Redirect(w, r, "http://other.valid.com:8080/path", http.StatusFound)
1169 } else if r.Header.Get("User-Agent") == rejectUserAgent {
1170 w.WriteHeader(http.StatusBadRequest)
1171 w.Write([]byte("found trap User-Agent"))
1172 } else {
1173 t.Logf("HTTPSRV: Got a valid req\n")
1174 t.Logf("HTTPSRV: Path = %s\n", r.URL.Path)
1175
1176 ch := core.Challenge{Token: currentToken}
1177 keyAuthz, _ := ch.ExpectedKeyAuthorization(accountKey)
1178 t.Logf("HTTPSRV: Key Authz = '%s%s'\n", keyAuthz, "\\n\\r \\t")
1179
1180 fmt.Fprint(w, keyAuthz, "\n\r \t")
1181 currentToken = defaultToken
1182 }
1183 })
1184
1185 server.Start()
1186 return server
1187 }
1188
1189 func TestHTTPBadPort(t *testing.T) {
1190 hs := httpSrv(t, expectedToken)
1191 defer hs.Close()
1192
1193 va, _ := setup(hs, 0, "", nil)
1194
1195
1196
1197
1198 badPort := 40000 + mrand.Intn(25000)
1199 va.httpPort = badPort
1200
1201 _, prob := va.validateHTTP01(ctx, dnsi("localhost"), httpChallenge())
1202 if prob == nil {
1203 t.Fatalf("Server's down; expected refusal. Where did we connect?")
1204 }
1205 test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
1206 if !strings.Contains(prob.Detail, "Connection refused") {
1207 t.Errorf("Expected a connection refused error, got %q", prob.Detail)
1208 }
1209 }
1210
1211 func TestHTTPKeyAuthorizationFileMismatch(t *testing.T) {
1212 m := http.NewServeMux()
1213 hs := httptest.NewUnstartedServer(m)
1214 m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
1215 w.Write([]byte("\xef\xffAABBCC"))
1216 })
1217 hs.Start()
1218
1219 va, _ := setup(hs, 0, "", nil)
1220 _, prob := va.validateHTTP01(ctx, dnsi("localhost.com"), httpChallenge())
1221
1222 if prob == nil {
1223 t.Fatalf("Expected validation to fail when file mismatched.")
1224 }
1225 expected := `The key authorization file from the server did not match this challenge. Expected "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0.9jg46WB3rR_AHD-EBXdN7cBkH1WOu0tA3M9fm21mqTI" (got "\xef\xffAABBCC")`
1226 if prob.Detail != expected {
1227 t.Errorf("validation failed with %s, expected %s", prob.Detail, expected)
1228 }
1229 }
1230
1231 func TestHTTP(t *testing.T) {
1232
1233
1234
1235
1236
1237
1238
1239
1240 hs := httpSrv(t, expectedToken)
1241
1242 va, log := setup(hs, 0, "", nil)
1243
1244 chall := httpChallenge()
1245 t.Logf("Trying to validate: %+v\n", chall)
1246 _, prob := va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
1247 if prob != nil {
1248 t.Errorf("Unexpected failure in HTTP validation: %s", prob)
1249 }
1250 test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1)
1251
1252 log.Clear()
1253 setChallengeToken(&chall, path404)
1254 _, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
1255 if prob == nil {
1256 t.Fatalf("Should have found a 404 for the challenge.")
1257 }
1258 test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
1259 test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1)
1260
1261 log.Clear()
1262 setChallengeToken(&chall, pathWrongToken)
1263
1264
1265 _, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
1266 if prob == nil {
1267 t.Fatalf("Should have found the wrong token value.")
1268 }
1269 test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
1270 test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1)
1271
1272 log.Clear()
1273 setChallengeToken(&chall, pathMoved)
1274 _, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
1275 if prob != nil {
1276 t.Fatalf("Failed to follow http.StatusMovedPermanently redirect")
1277 }
1278 redirectValid := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathValid + `"`
1279 matchedValidRedirect := log.GetAllMatching(redirectValid)
1280 test.AssertEquals(t, len(matchedValidRedirect), 1)
1281
1282 log.Clear()
1283 setChallengeToken(&chall, pathFound)
1284 _, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
1285 if prob != nil {
1286 t.Fatalf("Failed to follow http.StatusFound redirect")
1287 }
1288 redirectMoved := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathMoved + `"`
1289 matchedMovedRedirect := log.GetAllMatching(redirectMoved)
1290 test.AssertEquals(t, len(matchedValidRedirect), 1)
1291 test.AssertEquals(t, len(matchedMovedRedirect), 1)
1292
1293 ipIdentifier := identifier.ACMEIdentifier{Type: identifier.IdentifierType("ip"), Value: "127.0.0.1"}
1294 _, prob = va.validateHTTP01(ctx, ipIdentifier, chall)
1295 if prob == nil {
1296 t.Fatalf("IdentifierType IP shouldn't have worked.")
1297 }
1298 test.AssertEquals(t, prob.Type, probs.MalformedProblem)
1299
1300 _, prob = va.validateHTTP01(ctx, identifier.ACMEIdentifier{Type: identifier.DNS, Value: "always.invalid"}, chall)
1301 if prob == nil {
1302 t.Fatalf("Domain name is invalid.")
1303 }
1304 test.AssertEquals(t, prob.Type, probs.DNSProblem)
1305 }
1306
1307 func TestHTTPTimeout(t *testing.T) {
1308 hs := httpSrv(t, expectedToken)
1309
1310
1311 va, _ := setup(hs, 0, "", nil)
1312
1313 chall := httpChallenge()
1314 setChallengeToken(&chall, pathWaitLong)
1315
1316 started := time.Now()
1317 timeout := 250 * time.Millisecond
1318 ctx, cancel := context.WithTimeout(context.Background(), timeout)
1319 defer cancel()
1320 _, prob := va.validateHTTP01(ctx, dnsi("localhost"), chall)
1321 if prob == nil {
1322 t.Fatalf("Connection should've timed out")
1323 }
1324
1325 took := time.Since(started)
1326
1327
1328 if took < timeout-200*time.Millisecond {
1329 t.Fatalf("HTTP timed out before %s: %s with %s", timeout, took, prob)
1330 }
1331 if took > 2*timeout {
1332 t.Fatalf("HTTP connection didn't timeout after %s", timeout)
1333 }
1334 test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
1335 test.AssertEquals(t, prob.Detail, "127.0.0.1: Fetching http://localhost/.well-known/acme-challenge/wait-long: Timeout after connect (your server may be slow or overloaded)")
1336 }
1337
1338
1339
1340
1341 type dnsMockReturnsUnroutable struct {
1342 *bdns.MockClient
1343 }
1344
1345 func (mock dnsMockReturnsUnroutable) LookupHost(_ context.Context, hostname string) ([]net.IP, error) {
1346 return []net.IP{net.ParseIP("198.51.100.1")}, nil
1347 }
1348
1349
1350
1351
1352 func TestHTTPDialTimeout(t *testing.T) {
1353 va, _ := setup(nil, 0, "", nil)
1354
1355 started := time.Now()
1356 timeout := 250 * time.Millisecond
1357 ctx, cancel := context.WithTimeout(context.Background(), timeout)
1358 defer cancel()
1359
1360 va.dnsClient = dnsMockReturnsUnroutable{&bdns.MockClient{}}
1361
1362
1363
1364
1365 var prob *probs.ProblemDetails
1366 for i := 0; i < 20; i++ {
1367 _, prob = va.validateHTTP01(ctx, dnsi("unroutable.invalid"), httpChallenge())
1368 if prob != nil && strings.Contains(prob.Detail, "Network unreachable") {
1369 continue
1370 } else {
1371 break
1372 }
1373 }
1374 if prob == nil {
1375 t.Fatalf("Connection should've timed out")
1376 }
1377 took := time.Since(started)
1378
1379
1380 if took < (timeout-200*time.Millisecond)/2 {
1381 t.Fatalf("HTTP returned before %s (%s) with %#v", timeout, took, prob)
1382 }
1383 if took > 2*timeout {
1384 t.Fatalf("HTTP connection didn't timeout after %s seconds", timeout)
1385 }
1386 test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
1387 expectMatch := regexp.MustCompile(
1388 "Fetching http://unroutable.invalid/.well-known/acme-challenge/.*: Timeout during connect")
1389 if !expectMatch.MatchString(prob.Detail) {
1390 t.Errorf("Problem details incorrect. Got %q, expected to match %q",
1391 prob.Detail, expectMatch)
1392 }
1393 }
1394
1395 func TestHTTPRedirectLookup(t *testing.T) {
1396 hs := httpSrv(t, expectedToken)
1397 defer hs.Close()
1398 va, log := setup(hs, 0, "", nil)
1399
1400 chall := httpChallenge()
1401 setChallengeToken(&chall, pathMoved)
1402 _, prob := va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
1403 if prob != nil {
1404 t.Fatalf("Unexpected failure in redirect (%s): %s", pathMoved, prob)
1405 }
1406 redirectValid := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathValid + `"`
1407 matchedValidRedirect := log.GetAllMatching(redirectValid)
1408 test.AssertEquals(t, len(matchedValidRedirect), 1)
1409 test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 2)
1410
1411 log.Clear()
1412 setChallengeToken(&chall, pathFound)
1413 _, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
1414 if prob != nil {
1415 t.Fatalf("Unexpected failure in redirect (%s): %s", pathFound, prob)
1416 }
1417 redirectMoved := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathMoved + `"`
1418 matchedMovedRedirect := log.GetAllMatching(redirectMoved)
1419 test.AssertEquals(t, len(matchedMovedRedirect), 1)
1420 test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 3)
1421
1422 log.Clear()
1423 setChallengeToken(&chall, pathReLookupInvalid)
1424 _, err := va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
1425 test.AssertError(t, err, chall.Token)
1426 test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 1)
1427 test.AssertDeepEquals(t, err, probs.Connection(`127.0.0.1: Fetching http://invalid.invalid/path: Invalid hostname in redirect target, must end in IANA registered TLD`))
1428
1429 log.Clear()
1430 setChallengeToken(&chall, pathReLookup)
1431 _, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
1432 if prob != nil {
1433 t.Fatalf("Unexpected error in redirect (%s): %s", pathReLookup, prob)
1434 }
1435 redirectPattern := `following redirect to host "" url "http://other.valid.com:\d+/path"`
1436 test.AssertEquals(t, len(log.GetAllMatching(redirectPattern)), 1)
1437 test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 1)
1438 test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid.com: \[127.0.0.1\]`)), 1)
1439
1440 log.Clear()
1441 setChallengeToken(&chall, pathRedirectInvalidPort)
1442 _, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
1443 test.AssertNotNil(t, prob, "Problem details for pathRedirectInvalidPort should not be nil")
1444 test.AssertEquals(t, prob.Detail, fmt.Sprintf(
1445 "127.0.0.1: Fetching http://other.valid.com:8080/path: Invalid port in redirect target. "+
1446 "Only ports %d and %d are supported, not 8080", va.httpPort, va.httpsPort))
1447
1448
1449
1450
1451 log.Clear()
1452 setChallengeToken(&chall, pathRedirectToFailingURL)
1453 _, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
1454 test.AssertNotNil(t, prob, "Problem Details should not be nil")
1455 test.AssertDeepEquals(t, prob,
1456 probs.Unauthorized(
1457 fmt.Sprintf("127.0.0.1: Invalid response from http://other.valid.com:%d/500: 500",
1458 va.httpPort)))
1459 }
1460
1461 func TestHTTPRedirectLoop(t *testing.T) {
1462 hs := httpSrv(t, expectedToken)
1463 defer hs.Close()
1464 va, _ := setup(hs, 0, "", nil)
1465
1466 chall := httpChallenge()
1467 setChallengeToken(&chall, "looper")
1468 _, prob := va.validateHTTP01(ctx, dnsi("localhost"), chall)
1469 if prob == nil {
1470 t.Fatalf("Challenge should have failed for %s", chall.Token)
1471 }
1472 }
1473
1474 func TestHTTPRedirectUserAgent(t *testing.T) {
1475 hs := httpSrv(t, expectedToken)
1476 defer hs.Close()
1477 va, _ := setup(hs, 0, "", nil)
1478 va.userAgent = rejectUserAgent
1479
1480 chall := httpChallenge()
1481 setChallengeToken(&chall, pathMoved)
1482 _, prob := va.validateHTTP01(ctx, dnsi("localhost"), chall)
1483 if prob == nil {
1484 t.Fatalf("Challenge with rejectUserAgent should have failed (%s).", pathMoved)
1485 }
1486
1487 setChallengeToken(&chall, pathFound)
1488 _, prob = va.validateHTTP01(ctx, dnsi("localhost"), chall)
1489 if prob == nil {
1490 t.Fatalf("Challenge with rejectUserAgent should have failed (%s).", pathFound)
1491 }
1492 }
1493
1494 func getPort(hs *httptest.Server) int {
1495 url, err := url.Parse(hs.URL)
1496 if err != nil {
1497 panic(fmt.Sprintf("Failed to parse hs URL: %q - %s", hs.URL, err.Error()))
1498 }
1499 _, portString, err := net.SplitHostPort(url.Host)
1500 if err != nil {
1501 panic(fmt.Sprintf("Failed to split hs URL host: %q - %s", url.Host, err.Error()))
1502 }
1503 port, err := strconv.ParseInt(portString, 10, 64)
1504 if err != nil {
1505 panic(fmt.Sprintf("Failed to parse hs URL port: %q - %s", portString, err.Error()))
1506 }
1507 return int(port)
1508 }
1509
1510 func TestValidateHTTP(t *testing.T) {
1511 chall := core.HTTPChallenge01("")
1512 setChallengeToken(&chall, core.NewToken())
1513
1514 hs := httpSrv(t, chall.Token)
1515 defer hs.Close()
1516
1517 va, _ := setup(hs, 0, "", nil)
1518
1519 _, prob := va.validateChallenge(ctx, dnsi("localhost"), chall)
1520 test.Assert(t, prob == nil, "validation failed")
1521 }
1522
1523 func TestLimitedReader(t *testing.T) {
1524 chall := core.HTTPChallenge01("")
1525 setChallengeToken(&chall, core.NewToken())
1526
1527 hs := httpSrv(t, "012345\xff67890123456789012345678901234567890123456789012345678901234567890123456789")
1528 va, _ := setup(hs, 0, "", nil)
1529 defer hs.Close()
1530
1531 _, prob := va.validateChallenge(ctx, dnsi("localhost"), chall)
1532
1533 test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
1534 test.Assert(t, strings.HasPrefix(prob.Detail, "127.0.0.1: Invalid response from "),
1535 "Expected failure due to truncation")
1536
1537 if !utf8.ValidString(prob.Detail) {
1538 t.Errorf("Problem Detail contained an invalid UTF-8 string")
1539 }
1540 }
1541
View as plain text