1// 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	"context"
9	"crypto/ecdsa"
10	"crypto/elliptic"
11	"crypto/rand"
12	"crypto/tls"
13	"crypto/x509"
14	"encoding/base64"
15	"fmt"
16	"net/http"
17	"net/http/httptest"
18	"testing"
19	"time"
20
21	"golang.org/x/crypto/acme"
22)
23
24func TestRenewalNext(t *testing.T) {
25	now := time.Now()
26	man := &Manager{
27		RenewBefore: 7 * 24 * time.Hour,
28		nowFunc:     func() time.Time { return now },
29	}
30	defer man.stopRenew()
31	tt := []struct {
32		expiry   time.Time
33		min, max time.Duration
34	}{
35		{now.Add(90 * 24 * time.Hour), 83*24*time.Hour - renewJitter, 83 * 24 * time.Hour},
36		{now.Add(time.Hour), 0, 1},
37		{now, 0, 1},
38		{now.Add(-time.Hour), 0, 1},
39	}
40
41	dr := &domainRenewal{m: man}
42	for i, test := range tt {
43		next := dr.next(test.expiry)
44		if next < test.min || test.max < next {
45			t.Errorf("%d: next = %v; want between %v and %v", i, next, test.min, test.max)
46		}
47	}
48}
49
50func TestRenewFromCache(t *testing.T) {
51	// ACME CA server stub
52	var ca *httptest.Server
53	ca = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
54		w.Header().Set("Replay-Nonce", "nonce")
55		if r.Method == "HEAD" {
56			// a nonce request
57			return
58		}
59
60		switch r.URL.Path {
61		// discovery
62		case "/":
63			if err := discoTmpl.Execute(w, ca.URL); err != nil {
64				t.Fatalf("discoTmpl: %v", err)
65			}
66		// client key registration
67		case "/new-reg":
68			w.Write([]byte("{}"))
69		// domain authorization
70		case "/new-authz":
71			w.Header().Set("Location", ca.URL+"/authz/1")
72			w.WriteHeader(http.StatusCreated)
73			w.Write([]byte(`{"status": "valid"}`))
74		// authorization status request done by Manager's revokePendingAuthz.
75		case "/authz/1":
76			w.Write([]byte(`{"status": "valid"}`))
77		// cert request
78		case "/new-cert":
79			var req struct {
80				CSR string `json:"csr"`
81			}
82			decodePayload(&req, r.Body)
83			b, _ := base64.RawURLEncoding.DecodeString(req.CSR)
84			csr, err := x509.ParseCertificateRequest(b)
85			if err != nil {
86				t.Fatalf("new-cert: CSR: %v", err)
87			}
88			der, err := dummyCert(csr.PublicKey, exampleDomain)
89			if err != nil {
90				t.Fatalf("new-cert: dummyCert: %v", err)
91			}
92			chainUp := fmt.Sprintf("<%s/ca-cert>; rel=up", ca.URL)
93			w.Header().Set("Link", chainUp)
94			w.WriteHeader(http.StatusCreated)
95			w.Write(der)
96		// CA chain cert
97		case "/ca-cert":
98			der, err := dummyCert(nil, "ca")
99			if err != nil {
100				t.Fatalf("ca-cert: dummyCert: %v", err)
101			}
102			w.Write(der)
103		default:
104			t.Errorf("unrecognized r.URL.Path: %s", r.URL.Path)
105		}
106	}))
107	defer ca.Close()
108
109	man := &Manager{
110		Prompt:      AcceptTOS,
111		Cache:       newMemCache(t),
112		RenewBefore: 24 * time.Hour,
113		Client: &acme.Client{
114			DirectoryURL: ca.URL,
115		},
116	}
117	defer man.stopRenew()
118
119	// cache an almost expired cert
120	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
121	if err != nil {
122		t.Fatal(err)
123	}
124	now := time.Now()
125	cert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), exampleDomain)
126	if err != nil {
127		t.Fatal(err)
128	}
129	tlscert := &tls.Certificate{PrivateKey: key, Certificate: [][]byte{cert}}
130	if err := man.cachePut(context.Background(), exampleCertKey, 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(context.Background(), exampleCertKey)
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[exampleCertKey]
166		if s == nil {
167			t.Fatalf("m.state[%q] is nil", exampleCertKey)
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 := clientHelloInfo(exampleDomain, algECDSA)
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}
191
192func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
193	man := &Manager{
194		Prompt:      AcceptTOS,
195		Cache:       newMemCache(t),
196		RenewBefore: 24 * time.Hour,
197		Client: &acme.Client{
198			DirectoryURL: "invalid",
199		},
200	}
201	defer man.stopRenew()
202
203	// cache a recently renewed cert with a different private key
204	newKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
205	if err != nil {
206		t.Fatal(err)
207	}
208	now := time.Now()
209	newCert, err := dateDummyCert(newKey.Public(), now.Add(-2*time.Hour), now.Add(time.Hour*24*90), exampleDomain)
210	if err != nil {
211		t.Fatal(err)
212	}
213	newLeaf, err := validCert(exampleCertKey, [][]byte{newCert}, newKey, now)
214	if err != nil {
215		t.Fatal(err)
216	}
217	newTLSCert := &tls.Certificate{PrivateKey: newKey, Certificate: [][]byte{newCert}, Leaf: newLeaf}
218	if err := man.cachePut(context.Background(), exampleCertKey, newTLSCert); err != nil {
219		t.Fatal(err)
220	}
221
222	// set internal state to an almost expired cert
223	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
224	if err != nil {
225		t.Fatal(err)
226	}
227	oldCert, err := dateDummyCert(key.Public(), now.Add(-2*time.Hour), now.Add(time.Minute), exampleDomain)
228	if err != nil {
229		t.Fatal(err)
230	}
231	oldLeaf, err := validCert(exampleCertKey, [][]byte{oldCert}, key, now)
232	if err != nil {
233		t.Fatal(err)
234	}
235	man.stateMu.Lock()
236	if man.state == nil {
237		man.state = make(map[certKey]*certState)
238	}
239	s := &certState{
240		key:  key,
241		cert: [][]byte{oldCert},
242		leaf: oldLeaf,
243	}
244	man.state[exampleCertKey] = s
245	man.stateMu.Unlock()
246
247	// veriy the renewal accepted the newer cached cert
248	defer func() {
249		testDidRenewLoop = func(next time.Duration, err error) {}
250	}()
251	done := make(chan struct{})
252	testDidRenewLoop = func(next time.Duration, err error) {
253		defer close(done)
254		if err != nil {
255			t.Errorf("testDidRenewLoop: %v", err)
256		}
257		// Next should be about 90 days
258		// Previous expiration was within 1 min.
259		future := 88 * 24 * time.Hour
260		if next < future {
261			t.Errorf("testDidRenewLoop: next = %v; want >= %v", next, future)
262		}
263
264		// ensure the cached cert was not modified
265		tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
266		if err != nil {
267			t.Fatalf("man.cacheGet: %v", err)
268		}
269		if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) {
270			t.Errorf("cache leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter)
271		}
272
273		// verify the old cert is also replaced in memory
274		man.stateMu.Lock()
275		defer man.stateMu.Unlock()
276		s := man.state[exampleCertKey]
277		if s == nil {
278			t.Fatalf("m.state[%q] is nil", exampleCertKey)
279		}
280		stateKey := s.key.Public().(*ecdsa.PublicKey)
281		if stateKey.X.Cmp(newKey.X) != 0 || stateKey.Y.Cmp(newKey.Y) != 0 {
282			t.Fatalf("state key was not updated from cache x: %v y: %v; want x: %v y: %v", stateKey.X, stateKey.Y, newKey.X, newKey.Y)
283		}
284		tlscert, err = s.tlscert()
285		if err != nil {
286			t.Fatalf("s.tlscert: %v", err)
287		}
288		if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) {
289			t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter)
290		}
291
292		// verify the private key is replaced in the renewal state
293		r := man.renewal[exampleCertKey]
294		if r == nil {
295			t.Fatalf("m.renewal[%q] is nil", exampleCertKey)
296		}
297		renewalKey := r.key.Public().(*ecdsa.PublicKey)
298		if renewalKey.X.Cmp(newKey.X) != 0 || renewalKey.Y.Cmp(newKey.Y) != 0 {
299			t.Fatalf("renewal private key was not updated from cache x: %v y: %v; want x: %v y: %v", renewalKey.X, renewalKey.Y, newKey.X, newKey.Y)
300		}
301
302	}
303
304	// assert the expiring cert is returned from state
305	hello := clientHelloInfo(exampleDomain, algECDSA)
306	tlscert, err := man.GetCertificate(hello)
307	if err != nil {
308		t.Fatal(err)
309	}
310	if !oldLeaf.NotAfter.Equal(tlscert.Leaf.NotAfter) {
311		t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, oldLeaf.NotAfter)
312	}
313
314	// trigger renew
315	go man.renew(exampleCertKey, s.key, s.leaf.NotAfter)
316
317	// wait for renew loop
318	select {
319	case <-time.After(10 * time.Second):
320		t.Fatal("renew took too long to occur")
321	case <-done:
322		// assert the new cert is returned from state after renew
323		hello := clientHelloInfo(exampleDomain, algECDSA)
324		tlscert, err := man.GetCertificate(hello)
325		if err != nil {
326			t.Fatal(err)
327		}
328		if !newTLSCert.Leaf.NotAfter.Equal(tlscert.Leaf.NotAfter) {
329			t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newTLSCert.Leaf.NotAfter)
330		}
331	}
332}
333