1 package sa
2
3 import (
4 "context"
5 "crypto/rand"
6 "crypto/rsa"
7 "crypto/x509"
8 "crypto/x509/pkix"
9 "encoding/base64"
10 "fmt"
11 "math/big"
12 "net"
13 "testing"
14 "time"
15
16 "github.com/jmhodges/clock"
17 "github.com/letsencrypt/boulder/db"
18 "github.com/letsencrypt/boulder/grpc"
19 "github.com/letsencrypt/boulder/probs"
20 "github.com/letsencrypt/boulder/test/vars"
21 "google.golang.org/protobuf/types/known/timestamppb"
22
23 "github.com/letsencrypt/boulder/core"
24 corepb "github.com/letsencrypt/boulder/core/proto"
25 "github.com/letsencrypt/boulder/test"
26 )
27
28 func TestRegistrationModelToPb(t *testing.T) {
29 badCases := []struct {
30 name string
31 input regModel
32 }{
33 {
34 name: "No ID",
35 input: regModel{ID: 0, Key: []byte("foo"), InitialIP: []byte("foo")},
36 },
37 {
38 name: "No Key",
39 input: regModel{ID: 1, Key: nil, InitialIP: []byte("foo")},
40 },
41 {
42 name: "No IP",
43 input: regModel{ID: 1, Key: []byte("foo"), InitialIP: nil},
44 },
45 {
46 name: "Bad IP",
47 input: regModel{ID: 1, Key: []byte("foo"), InitialIP: []byte("foo")},
48 },
49 }
50 for _, tc := range badCases {
51 t.Run(tc.name, func(t *testing.T) {
52 _, err := registrationModelToPb(&tc.input)
53 test.AssertError(t, err, "Should fail")
54 })
55 }
56
57 _, err := registrationModelToPb(®Model{
58 ID: 1, Key: []byte("foo"), InitialIP: net.ParseIP("1.2.3.4"),
59 })
60 test.AssertNotError(t, err, "Should pass")
61 }
62
63 func TestRegistrationPbToModel(t *testing.T) {}
64
65 func TestAuthzModel(t *testing.T) {
66 clk := clock.New()
67 now := clk.Now()
68 expires := now.Add(24 * time.Hour)
69 authzPB := &corepb.Authorization{
70 Id: "1",
71 Identifier: "example.com",
72 RegistrationID: 1,
73 Status: string(core.StatusValid),
74 ExpiresNS: expires.UnixNano(),
75 Expires: timestamppb.New(expires),
76 Challenges: []*corepb.Challenge{
77 {
78 Type: string(core.ChallengeTypeHTTP01),
79 Status: string(core.StatusValid),
80 Token: "MTIz",
81 ValidatedNS: now.UnixNano(),
82 Validated: timestamppb.New(now),
83 Validationrecords: []*corepb.ValidationRecord{
84 {
85 AddressUsed: []byte("1.2.3.4"),
86 Url: "https://example.com",
87 Hostname: "example.com",
88 Port: "443",
89 AddressesResolved: [][]byte{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4}},
90 AddressesTried: [][]byte{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4}},
91 },
92 },
93 },
94 },
95 }
96
97 model, err := authzPBToModel(authzPB)
98 test.AssertNotError(t, err, "authzPBToModel failed")
99
100 authzPBOut, err := modelToAuthzPB(*model)
101 test.AssertNotError(t, err, "modelToAuthzPB failed")
102 if authzPB.Challenges[0].Validationrecords[0].Hostname != "" {
103 test.Assert(t, false, fmt.Sprintf("dehydrated http-01 validation record expected hostname field to be missing, but found %v", authzPB.Challenges[0].Validationrecords[0].Hostname))
104 }
105 if authzPB.Challenges[0].Validationrecords[0].Port != "" {
106 test.Assert(t, false, fmt.Sprintf("rehydrated http-01 validation record expected port field to be missing, but found %v", authzPB.Challenges[0].Validationrecords[0].Port))
107 }
108
109
110
111 authzPB.Challenges[0].Validationrecords[0].Hostname = "example.com"
112 authzPB.Challenges[0].Validationrecords[0].Port = "443"
113 test.AssertDeepEquals(t, authzPB.Challenges, authzPBOut.Challenges)
114
115 now = clk.Now()
116 expires = now.Add(24 * time.Hour)
117 authzPB = &corepb.Authorization{
118 Id: "1",
119 Identifier: "example.com",
120 RegistrationID: 1,
121 Status: string(core.StatusValid),
122 ExpiresNS: expires.UnixNano(),
123 Expires: timestamppb.New(expires),
124 Challenges: []*corepb.Challenge{
125 {
126 Type: string(core.ChallengeTypeHTTP01),
127 Status: string(core.StatusValid),
128 Token: "MTIz",
129 ValidatedNS: now.UnixNano(),
130 Validated: timestamppb.New(now),
131 Validationrecords: []*corepb.ValidationRecord{
132 {
133 AddressUsed: []byte("1.2.3.4"),
134 Url: "https://example.com",
135 Hostname: "example.com",
136 Port: "443",
137 AddressesResolved: [][]byte{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4}},
138 AddressesTried: [][]byte{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4}},
139 },
140 },
141 },
142 },
143 }
144
145 validationErr := probs.Connection("weewoo")
146
147 authzPB.Challenges[0].Status = string(core.StatusInvalid)
148 authzPB.Challenges[0].Error, err = grpc.ProblemDetailsToPB(validationErr)
149 test.AssertNotError(t, err, "grpc.ProblemDetailsToPB failed")
150 model, err = authzPBToModel(authzPB)
151 test.AssertNotError(t, err, "authzPBToModel failed")
152
153 authzPBOut, err = modelToAuthzPB(*model)
154 test.AssertNotError(t, err, "modelToAuthzPB failed")
155 if authzPB.Challenges[0].Validationrecords[0].Hostname != "" {
156 test.Assert(t, false, fmt.Sprintf("dehydrated http-01 validation record expected hostname field to be missing, but found %v", authzPB.Challenges[0].Validationrecords[0].Hostname))
157 }
158 if authzPB.Challenges[0].Validationrecords[0].Port != "" {
159 test.Assert(t, false, fmt.Sprintf("rehydrated http-01 validation record expected port field to be missing, but found %v", authzPB.Challenges[0].Validationrecords[0].Port))
160 }
161
162
163
164 authzPB.Challenges[0].Validationrecords[0].Hostname = "example.com"
165 authzPB.Challenges[0].Validationrecords[0].Port = "443"
166 test.AssertDeepEquals(t, authzPB.Challenges, authzPBOut.Challenges)
167
168 now = clk.Now()
169 expires = now.Add(24 * time.Hour)
170 authzPB = &corepb.Authorization{
171 Id: "1",
172 Identifier: "example.com",
173 RegistrationID: 1,
174 Status: string(core.StatusInvalid),
175 ExpiresNS: expires.UnixNano(),
176 Expires: timestamppb.New(expires),
177 Challenges: []*corepb.Challenge{
178 {
179 Type: string(core.ChallengeTypeHTTP01),
180 Status: string(core.StatusInvalid),
181 Token: "MTIz",
182 Validationrecords: []*corepb.ValidationRecord{
183 {
184 AddressUsed: []byte("1.2.3.4"),
185 Url: "url",
186 AddressesResolved: [][]byte{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4}},
187 AddressesTried: [][]byte{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4}},
188 },
189 },
190 },
191 {
192 Type: string(core.ChallengeTypeDNS01),
193 Status: string(core.StatusInvalid),
194 Token: "MTIz",
195 Validationrecords: []*corepb.ValidationRecord{
196 {
197 AddressUsed: []byte("1.2.3.4"),
198 Url: "url",
199 AddressesResolved: [][]byte{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4}},
200 AddressesTried: [][]byte{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4}},
201 },
202 },
203 },
204 },
205 }
206 _, err = authzPBToModel(authzPB)
207 test.AssertError(t, err, "authzPBToModel didn't fail with multiple non-pending challenges")
208
209
210 now = clk.Now()
211 expires = now.Add(24 * time.Hour)
212 authzPB = &corepb.Authorization{
213 Id: "1",
214 Identifier: "example.com",
215 RegistrationID: 1,
216 Status: string(core.StatusValid),
217 ExpiresNS: expires.UnixNano(),
218 Expires: timestamppb.New(expires),
219 Challenges: []*corepb.Challenge{
220 {
221 Type: string(core.ChallengeTypeHTTP01),
222 Status: string(core.StatusValid),
223 Token: "MTIz",
224 ValidatedNS: now.UnixNano(),
225 Validated: timestamppb.New(now),
226 Validationrecords: []*corepb.ValidationRecord{
227 {
228 AddressUsed: []byte("1.2.3.4"),
229 Url: "https://example.com",
230 AddressesResolved: [][]byte{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4}},
231 AddressesTried: [][]byte{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4}},
232 },
233 },
234 },
235 },
236 }
237
238 model, err = authzPBToModel(authzPB)
239 test.AssertNotError(t, err, "authzPBToModel failed")
240
241 authzPBOut, err = modelToAuthzPB(*model)
242 test.AssertNotError(t, err, "modelToAuthzPB failed")
243 if authzPBOut.Challenges[0].Validationrecords[0].Hostname != "example.com" {
244 test.Assert(t, false, fmt.Sprintf("rehydrated http-01 validation record expected hostname example.com but found %v", authzPBOut.Challenges[0].Validationrecords[0].Hostname))
245 }
246 if authzPBOut.Challenges[0].Validationrecords[0].Port != "443" {
247 test.Assert(t, false, fmt.Sprintf("rehydrated http-01 validation record expected port 443 but found %v", authzPBOut.Challenges[0].Validationrecords[0].Port))
248 }
249 }
250
251
252
253 func TestModelToOrderBadJSON(t *testing.T) {
254 badJSON := []byte(`{`)
255 _, err := modelToOrder(&orderModel{
256 Error: badJSON,
257 })
258 test.AssertError(t, err, "expected error from modelToOrder")
259 var badJSONErr errBadJSON
260 test.AssertErrorWraps(t, err, &badJSONErr)
261 test.AssertEquals(t, string(badJSONErr.json), string(badJSON))
262 }
263
264
265
266
267 func TestPopulateAttemptedFieldsBadJSON(t *testing.T) {
268 badJSON := []byte(`{`)
269
270 testCases := []struct {
271 Name string
272 Model *authzModel
273 }{
274 {
275 Name: "Bad validation error field",
276 Model: &authzModel{
277 ValidationError: badJSON,
278 },
279 },
280 {
281 Name: "Bad validation record field",
282 Model: &authzModel{
283 ValidationRecord: badJSON,
284 },
285 },
286 }
287 for _, tc := range testCases {
288 t.Run(tc.Name, func(t *testing.T) {
289 err := populateAttemptedFields(*tc.Model, &corepb.Challenge{})
290 test.AssertError(t, err, "expected error from populateAttemptedFields")
291 var badJSONErr errBadJSON
292 test.AssertErrorWraps(t, err, &badJSONErr)
293 test.AssertEquals(t, string(badJSONErr.json), string(badJSON))
294 })
295 }
296 }
297
298 func TestCertificatesTableContainsDuplicateSerials(t *testing.T) {
299 ctx := context.Background()
300
301 sa, fc, cleanUp := initSA(t)
302 defer cleanUp()
303
304 serialString := core.SerialToString(big.NewInt(1337))
305
306
307 err := insertCertificate(ctx, sa.dbMap, fc, "1337.com", "leet", 1337, 1)
308 test.AssertNotError(t, err, "couldn't insert valid certificate")
309
310
311 certA, err := SelectCertificate(ctx, sa.dbMap, serialString)
312 test.AssertNotError(t, err, "received an error for a valid query")
313
314
315
316 err = insertCertificate(ctx, sa.dbMap, fc, "1337.net", "leet", 1337, 1)
317 test.AssertNotError(t, err, "couldn't insert valid certificate")
318
319
320 certB, err := SelectCertificate(ctx, sa.dbMap, serialString)
321 test.AssertNotError(t, err, "received an error for a valid query")
322
323
324 test.AssertByteEquals(t, certA.DER, certB.DER)
325 }
326
327 func insertCertificate(ctx context.Context, dbMap *db.WrappedMap, fc clock.FakeClock, hostname, cn string, serial, regID int64) error {
328 serialBigInt := big.NewInt(serial)
329 serialString := core.SerialToString(serialBigInt)
330
331 template := x509.Certificate{
332 Subject: pkix.Name{
333 CommonName: cn,
334 },
335 NotAfter: fc.Now().Add(30 * 24 * time.Hour),
336 DNSNames: []string{hostname},
337 SerialNumber: serialBigInt,
338 }
339
340 testKey := makeKey()
341 certDer, _ := x509.CreateCertificate(rand.Reader, &template, &template, &testKey.PublicKey, &testKey)
342 cert := &core.Certificate{
343 RegistrationID: regID,
344 Serial: serialString,
345 Expires: template.NotAfter,
346 DER: certDer,
347 }
348 err := dbMap.Insert(ctx, cert)
349 if err != nil {
350 return err
351 }
352 return nil
353 }
354
355 func bigIntFromB64(b64 string) *big.Int {
356 bytes, _ := base64.URLEncoding.DecodeString(b64)
357 x := big.NewInt(0)
358 x.SetBytes(bytes)
359 return x
360 }
361
362 func makeKey() rsa.PrivateKey {
363 n := bigIntFromB64("n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw==")
364 e := int(bigIntFromB64("AQAB").Int64())
365 d := bigIntFromB64("bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78eiZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRldY7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-bMwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDjd18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOcOpBrQzwQ==")
366 p := bigIntFromB64("uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc=")
367 q := bigIntFromB64("uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc=")
368 return rsa.PrivateKey{PublicKey: rsa.PublicKey{N: n, E: e}, D: d, Primes: []*big.Int{p, q}}
369 }
370
371 func TestIncidentSerialModel(t *testing.T) {
372 ctx := context.Background()
373
374 testIncidentsDbMap, err := DBMapForTest(vars.DBConnIncidentsFullPerms)
375 test.AssertNotError(t, err, "Couldn't create test dbMap")
376 defer test.ResetIncidentsTestDatabase(t)
377
378
379 _, err = testIncidentsDbMap.ExecContext(ctx,
380 "INSERT INTO incident_foo (serial) VALUES (?)",
381 "1337",
382 )
383 test.AssertNotError(t, err, "inserting row with only serial")
384
385 var res1 incidentSerialModel
386 err = testIncidentsDbMap.SelectOne(
387 ctx,
388 &res1,
389 "SELECT * FROM incident_foo WHERE serial = ?",
390 "1337",
391 )
392 test.AssertNotError(t, err, "selecting row with only serial")
393
394 test.AssertEquals(t, res1.Serial, "1337")
395 test.AssertBoxedNil(t, res1.RegistrationID, "registrationID should be NULL")
396 test.AssertBoxedNil(t, res1.OrderID, "orderID should be NULL")
397 test.AssertBoxedNil(t, res1.LastNoticeSent, "lastNoticeSent should be NULL")
398
399
400 _, err = testIncidentsDbMap.ExecContext(ctx,
401 "INSERT INTO incident_foo (serial, registrationID, orderID, lastNoticeSent) VALUES (?, ?, ?, ?)",
402 "1338",
403 1,
404 2,
405 time.Date(2023, 06, 29, 16, 9, 00, 00, time.UTC),
406 )
407 test.AssertNotError(t, err, "inserting row with only serial")
408
409 var res2 incidentSerialModel
410 err = testIncidentsDbMap.SelectOne(
411 ctx,
412 &res2,
413 "SELECT * FROM incident_foo WHERE serial = ?",
414 "1338",
415 )
416 test.AssertNotError(t, err, "selecting row with only serial")
417
418 test.AssertEquals(t, res2.Serial, "1338")
419 test.AssertEquals(t, *res2.RegistrationID, int64(1))
420 test.AssertEquals(t, *res2.OrderID, int64(2))
421 test.AssertEquals(t, *res2.LastNoticeSent, time.Date(2023, 06, 29, 16, 9, 00, 00, time.UTC))
422 }
423
View as plain text