github.com/letsencrypt/boulder@v0.20251208.0/test/integration/validation_test.go (about) 1 //go:build integration 2 3 package integration 4 5 import ( 6 "crypto/ecdsa" 7 "crypto/elliptic" 8 "crypto/rand" 9 "database/sql" 10 "fmt" 11 "slices" 12 "strings" 13 "testing" 14 "time" 15 16 "github.com/eggsampler/acme/v3" 17 "github.com/miekg/dns" 18 19 challtestsrvclient "github.com/letsencrypt/boulder/test/chall-test-srv-client" 20 "github.com/letsencrypt/boulder/test/vars" 21 ) 22 23 var expectedUserAgents = []string{"boulder", "remoteva-a", "remoteva-b", "remoteva-c"} 24 25 func collectUserAgentsFromDNSRequests(requests []challtestsrvclient.DNSRequest) []string { 26 userAgents := make([]string, len(requests)) 27 for i, request := range requests { 28 userAgents[i] = request.UserAgent 29 } 30 return userAgents 31 } 32 33 func assertUserAgentsLength(t *testing.T, got []string, checkType string) { 34 t.Helper() 35 36 if len(got) != 4 { 37 t.Errorf("During %s, expected 4 User-Agents, got %d", checkType, len(got)) 38 } 39 } 40 41 func assertExpectedUserAgents(t *testing.T, got []string, checkType string) { 42 t.Helper() 43 44 for _, ua := range expectedUserAgents { 45 if !slices.Contains(got, ua) { 46 t.Errorf("During %s, expected User-Agent %q in %s (got %v)", checkType, ua, expectedUserAgents, got) 47 } 48 } 49 } 50 51 func TestMPICTLSALPN01(t *testing.T) { 52 t.Parallel() 53 54 client, err := makeClient() 55 if err != nil { 56 t.Fatalf("creating acme client: %s", err) 57 } 58 59 domain := randomDomain(t) 60 61 order, err := client.Client.NewOrder(client.Account, []acme.Identifier{{Type: "dns", Value: domain}}) 62 if err != nil { 63 t.Fatalf("creating order: %s", err) 64 } 65 66 authz, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0]) 67 if err != nil { 68 t.Fatalf("fetching authorization: %s", err) 69 } 70 71 chal, ok := authz.ChallengeMap[acme.ChallengeTypeTLSALPN01] 72 if !ok { 73 t.Fatalf("no TLS-ALPN-01 challenge found in %#v", authz) 74 } 75 76 _, err = testSrvClient.AddARecord(domain, []string{"64.112.117.134"}) 77 if err != nil { 78 t.Fatalf("adding A record: %s", err) 79 } 80 defer func() { 81 testSrvClient.RemoveARecord(domain) 82 }() 83 84 _, err = testSrvClient.AddTLSALPN01Response(domain, chal.KeyAuthorization) 85 if err != nil { 86 t.Fatal(err) 87 } 88 defer func() { 89 _, err = testSrvClient.RemoveTLSALPN01Response(domain) 90 if err != nil { 91 t.Fatal(err) 92 } 93 }() 94 95 chal, err = client.Client.UpdateChallenge(client.Account, chal) 96 if err != nil { 97 t.Fatalf("completing TLS-ALPN-01 validation: %s", err) 98 } 99 100 validationEvents, err := testSrvClient.TLSALPN01RequestHistory(domain) 101 if err != nil { 102 t.Fatal(err) 103 } 104 if len(validationEvents) != 4 { 105 t.Errorf("expected 4 validation events got %d", len(validationEvents)) 106 } 107 108 dnsEvents, err := testSrvClient.DNSRequestHistory(domain) 109 if err != nil { 110 t.Fatal(err) 111 } 112 113 var caaEvents []challtestsrvclient.DNSRequest 114 for _, event := range dnsEvents { 115 if event.Question.Qtype == dns.TypeCAA { 116 caaEvents = append(caaEvents, event) 117 } 118 } 119 assertUserAgentsLength(t, collectUserAgentsFromDNSRequests(caaEvents), "CAA check") 120 assertExpectedUserAgents(t, collectUserAgentsFromDNSRequests(caaEvents), "CAA check") 121 } 122 123 func TestMPICDNS01(t *testing.T) { 124 t.Parallel() 125 126 client, err := makeClient() 127 if err != nil { 128 t.Fatalf("creating acme client: %s", err) 129 } 130 131 domain := randomDomain(t) 132 133 order, err := client.Client.NewOrder(client.Account, []acme.Identifier{{Type: "dns", Value: domain}}) 134 if err != nil { 135 t.Fatalf("creating order: %s", err) 136 } 137 138 authz, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0]) 139 if err != nil { 140 t.Fatalf("fetching authorization: %s", err) 141 } 142 143 chal, ok := authz.ChallengeMap[acme.ChallengeTypeDNS01] 144 if !ok { 145 t.Fatalf("no DNS challenge found in %#v", authz) 146 } 147 148 _, err = testSrvClient.AddDNS01Response(domain, chal.KeyAuthorization) 149 if err != nil { 150 t.Fatal(err) 151 } 152 defer func() { 153 _, err = testSrvClient.RemoveDNS01Response(domain) 154 if err != nil { 155 t.Fatal(err) 156 } 157 }() 158 159 chal, err = client.Client.UpdateChallenge(client.Account, chal) 160 if err != nil { 161 t.Fatalf("completing DNS-01 validation: %s", err) 162 } 163 164 challDomainDNSEvents, err := testSrvClient.DNSRequestHistory("_acme-challenge." + domain) 165 if err != nil { 166 t.Fatal(err) 167 } 168 169 var validationEvents []challtestsrvclient.DNSRequest 170 for _, event := range challDomainDNSEvents { 171 if event.Question.Qtype == dns.TypeTXT && event.Question.Name == "_acme-challenge."+domain+"." { 172 validationEvents = append(validationEvents, event) 173 } 174 } 175 assertUserAgentsLength(t, collectUserAgentsFromDNSRequests(validationEvents), "DNS-01 validation") 176 assertExpectedUserAgents(t, collectUserAgentsFromDNSRequests(validationEvents), "DNS-01 validation") 177 178 domainDNSEvents, err := testSrvClient.DNSRequestHistory(domain) 179 if err != nil { 180 t.Fatal(err) 181 } 182 183 var caaEvents []challtestsrvclient.DNSRequest 184 for _, event := range domainDNSEvents { 185 if event.Question.Qtype == dns.TypeCAA { 186 caaEvents = append(caaEvents, event) 187 } 188 } 189 assertUserAgentsLength(t, collectUserAgentsFromDNSRequests(caaEvents), "CAA check") 190 assertExpectedUserAgents(t, collectUserAgentsFromDNSRequests(caaEvents), "CAA check") 191 } 192 193 func TestMPICHTTP01(t *testing.T) { 194 t.Parallel() 195 196 client, err := makeClient() 197 if err != nil { 198 t.Fatalf("creating acme client: %s", err) 199 } 200 201 domain := randomDomain(t) 202 203 order, err := client.Client.NewOrder(client.Account, []acme.Identifier{{Type: "dns", Value: domain}}) 204 if err != nil { 205 t.Fatalf("creating order: %s", err) 206 } 207 208 authz, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0]) 209 if err != nil { 210 t.Fatalf("fetching authorization: %s", err) 211 } 212 213 chal, ok := authz.ChallengeMap[acme.ChallengeTypeHTTP01] 214 if !ok { 215 t.Fatalf("no HTTP challenge found in %#v", authz) 216 } 217 218 _, err = testSrvClient.AddHTTP01Response(chal.Token, chal.KeyAuthorization) 219 if err != nil { 220 t.Fatal(err) 221 } 222 defer func() { 223 _, err = testSrvClient.RemoveHTTP01Response(chal.Token) 224 if err != nil { 225 t.Fatal(err) 226 } 227 }() 228 229 chal, err = client.Client.UpdateChallenge(client.Account, chal) 230 if err != nil { 231 t.Fatalf("completing HTTP-01 validation: %s", err) 232 } 233 234 validationEvents, err := testSrvClient.HTTPRequestHistory(domain) 235 if err != nil { 236 t.Fatal(err) 237 } 238 239 var validationUAs []string 240 for _, event := range validationEvents { 241 if event.URL == "/.well-known/acme-challenge/"+chal.Token { 242 validationUAs = append(validationUAs, event.UserAgent) 243 } 244 } 245 assertUserAgentsLength(t, validationUAs, "HTTP-01 validation") 246 assertExpectedUserAgents(t, validationUAs, "HTTP-01 validation") 247 248 dnsEvents, err := testSrvClient.DNSRequestHistory(domain) 249 if err != nil { 250 t.Fatal(err) 251 } 252 253 var caaEvents []challtestsrvclient.DNSRequest 254 for _, event := range dnsEvents { 255 if event.Question.Qtype == dns.TypeCAA { 256 caaEvents = append(caaEvents, event) 257 } 258 } 259 260 assertUserAgentsLength(t, collectUserAgentsFromDNSRequests(caaEvents), "CAA check") 261 assertExpectedUserAgents(t, collectUserAgentsFromDNSRequests(caaEvents), "CAA check") 262 } 263 264 func TestCAARechecking(t *testing.T) { 265 t.Parallel() 266 267 domain := randomDomain(t) 268 idents := []acme.Identifier{{Type: "dns", Value: domain}} 269 270 // Create an order and authorization, and fulfill the associated challenge. 271 // This should put the authz into the "valid" state, since CAA checks passed. 272 client, err := makeClient() 273 if err != nil { 274 t.Fatalf("creating acme client: %s", err) 275 } 276 277 order, err := client.Client.NewOrder(client.Account, idents) 278 if err != nil { 279 t.Fatalf("creating order: %s", err) 280 } 281 282 authz, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0]) 283 if err != nil { 284 t.Fatalf("fetching authorization: %s", err) 285 } 286 287 chal, ok := authz.ChallengeMap[acme.ChallengeTypeHTTP01] 288 if !ok { 289 t.Fatalf("no HTTP challenge found in %#v", authz) 290 } 291 292 _, err = testSrvClient.AddHTTP01Response(chal.Token, chal.KeyAuthorization) 293 if err != nil { 294 t.Fatal(err) 295 } 296 defer func() { 297 _, err = testSrvClient.RemoveHTTP01Response(chal.Token) 298 if err != nil { 299 t.Fatal(err) 300 } 301 }() 302 303 chal, err = client.Client.UpdateChallenge(client.Account, chal) 304 if err != nil { 305 t.Fatalf("completing HTTP-01 validation: %s", err) 306 } 307 308 // Manipulate the database so that it looks like the authz was validated 309 // more than 8 hours ago. 310 db, err := sql.Open("mysql", vars.DBConnSAIntegrationFullPerms) 311 if err != nil { 312 t.Fatalf("sql.Open: %s", err) 313 } 314 315 _, err = db.Exec(`UPDATE authz2 SET attemptedAt = ? WHERE identifierValue = ?`, time.Now().Add(-24*time.Hour).Format(time.DateTime), domain) 316 if err != nil { 317 t.Fatalf("updating authz attemptedAt timestamp: %s", err) 318 } 319 320 // Change the CAA record to now forbid issuance. 321 _, err = testSrvClient.AddCAAIssue(domain, ";") 322 if err != nil { 323 t.Fatal(err) 324 } 325 326 // Try to finalize the order created above. Due to our db manipulation, this 327 // should trigger a CAA recheck. And due to our challtestsrv manipulation, 328 // that CAA recheck should fail. Therefore the whole finalize should fail. 329 key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 330 if err != nil { 331 t.Fatalf("generating cert key: %s", err) 332 } 333 334 csr, err := makeCSR(key, idents, false) 335 if err != nil { 336 t.Fatalf("generating finalize csr: %s", err) 337 } 338 339 _, err = client.Client.FinalizeOrder(client.Account, order, csr) 340 if err == nil { 341 t.Errorf("expected finalize to fail, but got success") 342 } 343 if !strings.Contains(err.Error(), "CAA") { 344 t.Errorf("expected finalize to fail due to CAA, but got: %s", err) 345 } 346 } 347 348 func TestCAAAccountURI(t *testing.T) { 349 t.Parallel() 350 client, err := makeClient() 351 if err != nil { 352 t.Fatalf("creating acme client: %s", err) 353 } 354 355 domain := random_domain() 356 idents := []acme.Identifier{{Type: "dns", Value: domain}} 357 record := fmt.Sprintf("happy-hacker-ca.invalid; accounturi=%s", client.Account.URL) 358 _, err = testSrvClient.AddCAAIssue(domain, record) 359 if err != nil { 360 t.Fatal(err) 361 } 362 _, err = authAndIssue(client, nil, idents, true, "") 363 if err != nil { 364 t.Fatalf("authAndIssue: %s", err) 365 } 366 367 newClient, err := makeClient() 368 if err != nil { 369 t.Fatalf("creating acme client: %s", err) 370 } 371 372 // New domain to avoid rate limiting; same old CAA record to trigger CAA error. 373 domain = random_domain() 374 idents = []acme.Identifier{{Type: "dns", Value: domain}} 375 _, err = testSrvClient.AddCAAIssue(domain, record) 376 if err != nil { 377 t.Fatal(err) 378 } 379 _, err = authAndIssue(newClient, nil, idents, true, "") 380 if err == nil { 381 t.Errorf("expected error with mismatched CAA account URI, but got success") 382 } 383 if err != nil && !strings.Contains(err.Error(), "CAA") { 384 t.Errorf("expected CAA error with mismatched CAA account URI, but got: %s", err) 385 } 386 } 387 388 func TestCAAValidationMethods(t *testing.T) { 389 t.Parallel() 390 391 testCases := []struct { 392 caa string 393 challType string 394 expectError bool 395 }{ 396 {"dns-01", acme.ChallengeTypeDNS01, false}, 397 {"dns-01", acme.ChallengeTypeHTTP01, true}, 398 {"http-01", acme.ChallengeTypeHTTP01, false}, 399 {"http-01", acme.ChallengeTypeDNS01, true}, 400 {"dns-01,http-01", acme.ChallengeTypeDNS01, false}, 401 {"dns-01,http-01", acme.ChallengeTypeHTTP01, false}, 402 } 403 404 client, err := makeClient() 405 if err != nil { 406 t.Fatalf("creating acme client: %s", err) 407 } 408 409 for _, tc := range testCases { 410 t.Run(fmt.Sprintf("%s-with-caa-allowing-%s", tc.challType, tc.caa), func(t *testing.T) { 411 domain := random_domain() 412 record := fmt.Sprintf("happy-hacker-ca.invalid; validationmethods=%s", tc.caa) 413 _, err = testSrvClient.AddCAAIssue(domain, record) 414 if err != nil { 415 t.Fatal(err) 416 } 417 418 idents := []acme.Identifier{{Type: "dns", Value: domain}} 419 order, err := client.Client.NewOrder(client.Account, idents) 420 if err != nil { 421 t.Fatalf("creating order: %s", err) 422 } 423 424 authz, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0]) 425 if err != nil { 426 t.Fatalf("fetching authorization: %s", err) 427 } 428 429 var chal acme.Challenge 430 var ok bool 431 switch tc.challType { 432 case acme.ChallengeTypeDNS01: 433 chal, ok = authz.ChallengeMap[acme.ChallengeTypeDNS01] 434 if !ok { 435 t.Fatalf("no DNS challenge found in %#v", authz) 436 } 437 _, err = testSrvClient.AddDNS01Response(domain, chal.KeyAuthorization) 438 if err != nil { 439 t.Fatalf("adding DNS-01 response: %s", err) 440 } 441 case acme.ChallengeTypeHTTP01: 442 chal, ok = authz.ChallengeMap[acme.ChallengeTypeHTTP01] 443 if !ok { 444 t.Fatalf("no HTTP challenge found in %#v", authz) 445 } 446 _, err = testSrvClient.AddHTTP01Response(chal.Token, chal.KeyAuthorization) 447 if err != nil { 448 t.Fatalf("adding HTTP-01 response: %s", err) 449 } 450 default: 451 t.Fatalf("unknown challenge type: %q", tc.challType) 452 } 453 454 chal, err = client.Client.UpdateChallenge(client.Account, chal) 455 if err != nil { 456 if tc.expectError && !strings.Contains(err.Error(), "CAA") { 457 t.Errorf("expected validation to fail due to CAA, but got: %s", err) 458 } else if !tc.expectError { 459 t.Errorf("issuing with challenge type %q and CAA %q failed: %s", tc.challType, tc.caa, err) 460 } 461 } 462 if err == nil && tc.expectError { 463 t.Errorf("issuing with challenge type %q and CAA %q succeeded, but should have failed", tc.challType, tc.caa) 464 } 465 }) 466 } 467 }