blob: d1ec52f4d021701e421e23d46d2afbadf7969ad0 [file] [log] [blame]
Alex Vaghin33b41822016-08-22 21:27:50 +02001// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package autocert
6
7import (
8 "crypto/ecdsa"
9 "crypto/elliptic"
10 "crypto/rand"
11 "crypto/tls"
12 "crypto/x509"
13 "encoding/base64"
14 "fmt"
15 "net/http"
16 "net/http/httptest"
17 "testing"
18 "time"
19
Alex Vaghindfc9fd32016-08-31 22:02:02 +020020 "golang.org/x/crypto/acme"
Alex Vaghin33b41822016-08-22 21:27:50 +020021)
22
23func TestRenewalNext(t *testing.T) {
24 now := time.Now()
Alex Vaghin05d11b22016-09-03 09:54:54 +020025 timeNow = func() time.Time { return now }
26 defer func() { timeNow = time.Now }()
Alex Vaghin33b41822016-08-22 21:27:50 +020027
28 man := &Manager{RenewBefore: 7 * 24 * time.Hour}
Alex Vaghin05d11b22016-09-03 09:54:54 +020029 defer man.stopRenew()
Alex Vaghin33b41822016-08-22 21:27:50 +020030 tt := []struct {
31 expiry time.Time
32 min, max time.Duration
33 }{
34 {now.Add(90 * 24 * time.Hour), 83*24*time.Hour - maxRandRenew, 83 * 24 * time.Hour},
35 {now.Add(time.Hour), 0, 1},
36 {now, 0, 1},
37 {now.Add(-time.Hour), 0, 1},
38 }
39
40 dr := &domainRenewal{m: man}
41 for i, test := range tt {
42 next := dr.next(test.expiry)
43 if next < test.min || test.max < next {
44 t.Errorf("%d: next = %v; want between %v and %v", i, next, test.min, test.max)
45 }
46 }
47}
48
49func TestRenewFromCache(t *testing.T) {
50 const domain = "example.org"
51
52 // ACME CA server stub
53 var ca *httptest.Server
54 ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
55 w.Header().Set("replay-nonce", "nonce")
56 if r.Method == "HEAD" {
57 // a nonce request
58 return
59 }
60
61 switch r.URL.Path {
62 // discovery
63 case "/":
64 if err := discoTmpl.Execute(w, ca.URL); err != nil {
65 t.Fatalf("discoTmpl: %v", err)
66 }
67 // client key registration
68 case "/new-reg":
69 w.Write([]byte("{}"))
70 // domain authorization
71 case "/new-authz":
72 w.Header().Set("location", ca.URL+"/authz/1")
73 w.WriteHeader(http.StatusCreated)
74 w.Write([]byte(`{"status": "valid"}`))
75 // cert request
76 case "/new-cert":
77 var req struct {
78 CSR string `json:"csr"`
79 }
80 decodePayload(&req, r.Body)
81 b, _ := base64.RawURLEncoding.DecodeString(req.CSR)
82 csr, err := x509.ParseCertificateRequest(b)
83 if err != nil {
84 t.Fatalf("new-cert: CSR: %v", err)
85 }
86 der, err := dummyCert(csr.PublicKey, domain)
87 if err != nil {
88 t.Fatalf("new-cert: dummyCert: %v", err)
89 }
90 chainUp := fmt.Sprintf("<%s/ca-cert>; rel=up", ca.URL)
91 w.Header().Set("link", chainUp)
92 w.WriteHeader(http.StatusCreated)
93 w.Write(der)
94 // CA chain cert
95 case "/ca-cert":
96 der, err := dummyCert(nil, "ca")
97 if err != nil {
98 t.Fatalf("ca-cert: dummyCert: %v", err)
99 }
100 w.Write(der)
101 default:
102 t.Errorf("unrecognized r.URL.Path: %s", r.URL.Path)
103 }
104 }))
105 defer ca.Close()
106
107 // use EC key to run faster on 386
108 key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
109 if err != nil {
110 t.Fatal(err)
111 }
112 man := &Manager{
113 Prompt: AcceptTOS,
114 Cache: make(memCache),
115 RenewBefore: 24 * time.Hour,
116 Client: &acme.Client{
117 Key: key,
118 DirectoryURL: ca.URL,
119 },
120 }
Alex Vaghin05d11b22016-09-03 09:54:54 +0200121 defer man.stopRenew()
Alex Vaghin33b41822016-08-22 21:27:50 +0200122
123 // cache an almost expired cert
124 now := time.Now()
125 cert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), domain)
126 if err != nil {
127 t.Fatal(err)
128 }
129 tlscert := &tls.Certificate{PrivateKey: key, Certificate: [][]byte{cert}}
130 if err := man.cachePut(domain, tlscert); err != nil {
131 t.Fatal(err)
132 }
133
134 // veriy the renewal happened
135 defer func() {
136 testDidRenewLoop = func(next time.Duration, err error) {}
137 }()
138 done := make(chan struct{})
139 testDidRenewLoop = func(next time.Duration, err error) {
140 defer close(done)
141 if err != nil {
142 t.Errorf("testDidRenewLoop: %v", err)
143 }
144 // Next should be about 90 days:
145 // dummyCert creates 90days expiry + account for man.RenewBefore.
146 // Previous expiration was within 1 min.
147 future := 88 * 24 * time.Hour
148 if next < future {
149 t.Errorf("testDidRenewLoop: next = %v; want >= %v", next, future)
150 }
151
152 // ensure the new cert is cached
153 after := time.Now().Add(future)
154 tlscert, err := man.cacheGet(domain)
155 if err != nil {
156 t.Fatalf("man.cacheGet: %v", err)
157 }
158 if !tlscert.Leaf.NotAfter.After(after) {
159 t.Errorf("cache leaf.NotAfter = %v; want > %v", tlscert.Leaf.NotAfter, after)
160 }
161
162 // verify the old cert is also replaced in memory
163 man.stateMu.Lock()
164 defer man.stateMu.Unlock()
165 s := man.state[domain]
166 if s == nil {
167 t.Fatalf("m.state[%q] is nil", domain)
168 }
169 tlscert, err = s.tlscert()
170 if err != nil {
171 t.Fatalf("s.tlscert: %v", err)
172 }
173 if !tlscert.Leaf.NotAfter.After(after) {
174 t.Errorf("state leaf.NotAfter = %v; want > %v", tlscert.Leaf.NotAfter, after)
175 }
176 }
177
178 // trigger renew
179 hello := &tls.ClientHelloInfo{ServerName: domain}
180 if _, err := man.GetCertificate(hello); err != nil {
181 t.Fatal(err)
182 }
183
184 // wait for renew loop
185 select {
186 case <-time.After(10 * time.Second):
187 t.Fatal("renew took too long to occur")
188 case <-done:
189 }
190}