1 package redis
2
3 import (
4 "context"
5 "errors"
6 "math/big"
7 "testing"
8 "time"
9
10 "github.com/jmhodges/clock"
11 "github.com/letsencrypt/boulder/core"
12 "github.com/letsencrypt/boulder/log"
13 "github.com/letsencrypt/boulder/metrics"
14 "github.com/letsencrypt/boulder/ocsp/responder"
15 ocsp_test "github.com/letsencrypt/boulder/ocsp/test"
16 "github.com/letsencrypt/boulder/rocsp"
17 "github.com/letsencrypt/boulder/test"
18 "github.com/prometheus/client_golang/prometheus"
19 "golang.org/x/crypto/ocsp"
20 )
21
22
23
24
25
26
27
28
29 type notFoundRedis struct {
30 serialStored chan *big.Int
31 }
32
33 func (nfr *notFoundRedis) GetResponse(ctx context.Context, serial string) ([]byte, error) {
34 return nil, rocsp.ErrRedisNotFound
35 }
36
37 func (nfr *notFoundRedis) StoreResponse(ctx context.Context, resp *ocsp.Response) error {
38 nfr.serialStored <- resp.SerialNumber
39 return nil
40 }
41
42 type recordingSigner struct {
43 serialRequested *big.Int
44 }
45
46 func (rs *recordingSigner) Response(ctx context.Context, req *ocsp.Request) (*responder.Response, error) {
47 if rs.serialRequested != nil {
48 panic("signed twice")
49 }
50 rs.serialRequested = req.SerialNumber
51
52
53 return &responder.Response{Response: &ocsp.Response{
54 SerialNumber: req.SerialNumber,
55 }}, nil
56 }
57
58 func TestNotFound(t *testing.T) {
59 recordingSigner := recordingSigner{}
60 src, err := NewRedisSource(nil, &recordingSigner, time.Second, clock.NewFake(), metrics.NoopRegisterer, log.NewMock(), 1)
61 test.AssertNotError(t, err, "making source")
62 notFoundRedis := ¬FoundRedis{make(chan *big.Int)}
63 src.client = notFoundRedis
64
65 serial := big.NewInt(987654321)
66 _, err = src.Response(context.Background(), &ocsp.Request{
67 SerialNumber: serial,
68 })
69 test.AssertNotError(t, err, "signing response when not found")
70 if recordingSigner.serialRequested.Cmp(serial) != 0 {
71 t.Errorf("issued signing request for serial %x; expected %x", recordingSigner.serialRequested, serial)
72 }
73 stored := <-notFoundRedis.serialStored
74 if stored == nil {
75 t.Fatalf("response was never stored")
76 }
77 if stored.Cmp(serial) != 0 {
78 t.Errorf("stored response for serial %x; expected %x", notFoundRedis.serialStored, serial)
79 }
80 }
81
82 type panicSource struct{}
83
84 func (ps panicSource) Response(ctx context.Context, req *ocsp.Request) (*responder.Response, error) {
85 panic("shouldn't happen")
86 }
87
88 type errorRedis struct{}
89
90 func (er errorRedis) GetResponse(ctx context.Context, serial string) ([]byte, error) {
91 return nil, errors.New("the enzabulators florbled")
92 }
93
94 func (er errorRedis) StoreResponse(ctx context.Context, resp *ocsp.Response) error {
95 return nil
96 }
97
98
99
100 func TestQueryError(t *testing.T) {
101 serial := big.NewInt(314159)
102 thisUpdate := time.Now().Truncate(time.Second).UTC()
103 resp, _, err := ocsp_test.FakeResponse(ocsp.Response{
104 SerialNumber: serial,
105 Status: ocsp.Good,
106 ThisUpdate: thisUpdate,
107 })
108 test.AssertNotError(t, err, "making fake response")
109 source := echoSource{resp: resp}
110
111 src, err := NewRedisSource(nil, source, time.Second, clock.NewFake(), metrics.NoopRegisterer, log.NewMock(), 1)
112 test.AssertNotError(t, err, "making source")
113 src.client = errorRedis{}
114
115 receivedResp, err := src.Response(context.Background(), &ocsp.Request{
116 SerialNumber: serial,
117 })
118 test.AssertNotError(t, err, "expected no error when Redis errored")
119 test.AssertDeepEquals(t, resp.Raw, receivedResp.Raw)
120 test.AssertMetricWithLabelsEquals(t, src.counter, prometheus.Labels{"result": "lookup_error"}, 1)
121 }
122
123 type garbleRedis struct{}
124
125 func (er garbleRedis) GetResponse(ctx context.Context, serial string) ([]byte, error) {
126 return []byte("not a valid OCSP response, I can tell by the pixels"), nil
127 }
128
129 func (er garbleRedis) StoreResponse(ctx context.Context, resp *ocsp.Response) error {
130 panic("shouldn't happen")
131 }
132
133 func TestParseError(t *testing.T) {
134 src, err := NewRedisSource(nil, panicSource{}, time.Second, clock.NewFake(), metrics.NoopRegisterer, log.NewMock(), 1)
135 test.AssertNotError(t, err, "making source")
136 src.client = garbleRedis{}
137
138 _, err = src.Response(context.Background(), &ocsp.Request{
139 SerialNumber: big.NewInt(314159),
140 })
141 test.AssertError(t, err, "expected error when Redis returned junk")
142 if errors.Is(err, rocsp.ErrRedisNotFound) {
143 t.Errorf("incorrect error value ErrRedisNotFound; expected general error")
144 }
145 }
146
147 func TestSignError(t *testing.T) {
148 src, err := NewRedisSource(nil, errorSource{}, time.Second, clock.NewFake(), metrics.NoopRegisterer, log.NewMock(), 1)
149 test.AssertNotError(t, err, "making source")
150 src.client = ¬FoundRedis{nil}
151
152 _, err = src.Response(context.Background(), &ocsp.Request{
153 SerialNumber: big.NewInt(2718),
154 })
155 test.AssertError(t, err, "Expected error when signer errored")
156 }
157
158
159
160
161
162
163 type staleRedis struct {
164 serialStored chan *big.Int
165 thisUpdate time.Time
166 }
167
168 func (sr *staleRedis) GetResponse(ctx context.Context, serial string) ([]byte, error) {
169 serInt, err := core.StringToSerial(serial)
170 if err != nil {
171 return nil, err
172 }
173 resp, _, err := ocsp_test.FakeResponse(ocsp.Response{
174 SerialNumber: serInt,
175 ThisUpdate: sr.thisUpdate,
176 })
177 if err != nil {
178 return nil, err
179 }
180 return resp.Raw, nil
181 }
182
183 func (sr *staleRedis) StoreResponse(ctx context.Context, resp *ocsp.Response) error {
184 sr.serialStored <- resp.SerialNumber
185 return nil
186 }
187
188 func TestStale(t *testing.T) {
189 recordingSigner := recordingSigner{}
190 clk := clock.NewFake()
191 src, err := NewRedisSource(nil, &recordingSigner, time.Second, clk, metrics.NoopRegisterer, log.NewMock(), 1)
192 test.AssertNotError(t, err, "making source")
193 staleRedis := &staleRedis{
194 serialStored: make(chan *big.Int),
195 thisUpdate: clk.Now().Add(-time.Hour),
196 }
197 src.client = staleRedis
198
199 serial := big.NewInt(8675309)
200 _, err = src.Response(context.Background(), &ocsp.Request{
201 SerialNumber: serial,
202 })
203 test.AssertNotError(t, err, "signing response when not found")
204 if recordingSigner.serialRequested == nil {
205 t.Fatalf("signing source was never called")
206 }
207 if recordingSigner.serialRequested.Cmp(serial) != 0 {
208 t.Errorf("issued signing request for serial %x; expected %x", recordingSigner.serialRequested, serial)
209 }
210 stored := <-staleRedis.serialStored
211 if stored == nil {
212 t.Fatalf("response was never stored")
213 }
214 if stored.Cmp(serial) != 0 {
215 t.Errorf("stored response for serial %x; expected %x", staleRedis.serialStored, serial)
216 }
217 }
218
219
220 type notFoundSigner struct{}
221
222 func (nfs notFoundSigner) Response(ctx context.Context, req *ocsp.Request) (*responder.Response, error) {
223 return nil, responder.ErrNotFound
224 }
225
226 func TestCertificateNotFound(t *testing.T) {
227 src, err := NewRedisSource(nil, notFoundSigner{}, time.Second, clock.NewFake(), metrics.NoopRegisterer, log.NewMock(), 1)
228 test.AssertNotError(t, err, "making source")
229 notFoundRedis := ¬FoundRedis{nil}
230 src.client = notFoundRedis
231
232 _, err = src.Response(context.Background(), &ocsp.Request{
233 SerialNumber: big.NewInt(777777777),
234 })
235 if !errors.Is(err, responder.ErrNotFound) {
236 t.Errorf("expected NotFound error, got %s", err)
237 }
238 }
239
240 func TestNoServeStale(t *testing.T) {
241 clk := clock.NewFake()
242 src, err := NewRedisSource(nil, errorSource{}, time.Second, clk, metrics.NoopRegisterer, log.NewMock(), 1)
243 test.AssertNotError(t, err, "making source")
244 staleRedis := &staleRedis{
245 serialStored: nil,
246 thisUpdate: clk.Now().Add(-time.Hour),
247 }
248 src.client = staleRedis
249
250 serial := big.NewInt(111111)
251 _, err = src.Response(context.Background(), &ocsp.Request{
252 SerialNumber: serial,
253 })
254 test.AssertError(t, err, "expected to error when signer was down")
255 }
256
View as plain text