github.com/letsencrypt/boulder@v0.20251208.0/test/integration/errors_test.go (about) 1 //go:build integration 2 3 package integration 4 5 import ( 6 "bytes" 7 "crypto" 8 "crypto/ecdsa" 9 "crypto/elliptic" 10 "crypto/rand" 11 "encoding/base64" 12 "encoding/json" 13 "errors" 14 "fmt" 15 "io" 16 "net/http" 17 "slices" 18 "strings" 19 "testing" 20 21 "github.com/eggsampler/acme/v3" 22 "github.com/go-jose/go-jose/v4" 23 24 "github.com/letsencrypt/boulder/test" 25 ) 26 27 // TestTooBigOrderError tests that submitting an order with more than 100 28 // identifiers produces the expected problem result. 29 func TestTooBigOrderError(t *testing.T) { 30 t.Parallel() 31 32 var idents []acme.Identifier 33 for i := range 101 { 34 idents = append(idents, acme.Identifier{Type: "dns", Value: fmt.Sprintf("%d.example.com", i)}) 35 } 36 37 _, err := authAndIssue(nil, nil, idents, true, "") 38 test.AssertError(t, err, "authAndIssue failed") 39 40 var prob acme.Problem 41 test.AssertErrorWraps(t, err, &prob) 42 test.AssertEquals(t, prob.Type, "urn:ietf:params:acme:error:malformed") 43 test.AssertContains(t, prob.Detail, "Order cannot contain more than 100 identifiers") 44 } 45 46 // TestAccountEmailError tests that registering a new account, or updating an 47 // account, with invalid contact information produces the expected problem 48 // result to ACME clients. 49 func TestAccountEmailError(t *testing.T) { 50 t.Parallel() 51 52 testCases := []struct { 53 name string 54 contacts []string 55 expectedProbType string 56 expectedProbDetail string 57 }{ 58 { 59 name: "empty contact", 60 contacts: []string{"mailto:valid@valid.com", ""}, 61 expectedProbType: "urn:ietf:params:acme:error:invalidContact", 62 expectedProbDetail: `empty contact`, 63 }, 64 { 65 name: "empty proto", 66 contacts: []string{"mailto:valid@valid.com", " "}, 67 expectedProbType: "urn:ietf:params:acme:error:unsupportedContact", 68 expectedProbDetail: `only contact scheme 'mailto:' is supported`, 69 }, 70 { 71 name: "empty mailto", 72 contacts: []string{"mailto:valid@valid.com", "mailto:"}, 73 expectedProbType: "urn:ietf:params:acme:error:invalidContact", 74 expectedProbDetail: `unable to parse email address`, 75 }, 76 { 77 name: "non-ascii mailto", 78 contacts: []string{"mailto:valid@valid.com", "mailto:cpu@l̴etsencrypt.org"}, 79 expectedProbType: "urn:ietf:params:acme:error:invalidContact", 80 expectedProbDetail: `contact email contains non-ASCII characters`, 81 }, 82 { 83 name: "too many contacts", 84 contacts: slices.Repeat([]string{"mailto:lots@valid.com"}, 11), 85 expectedProbType: "urn:ietf:params:acme:error:malformed", 86 expectedProbDetail: `too many contacts provided`, 87 }, 88 { 89 name: "invalid contact", 90 contacts: []string{"mailto:valid@valid.com", "mailto:a@"}, 91 expectedProbType: "urn:ietf:params:acme:error:invalidContact", 92 expectedProbDetail: `unable to parse email address`, 93 }, 94 { 95 name: "forbidden contact domain", 96 contacts: []string{"mailto:valid@valid.com", "mailto:a@example.com"}, 97 expectedProbType: "urn:ietf:params:acme:error:invalidContact", 98 expectedProbDetail: "contact email has forbidden domain \"example.com\"", 99 }, 100 { 101 name: "contact domain invalid TLD", 102 contacts: []string{"mailto:valid@valid.com", "mailto:a@example.cpu"}, 103 expectedProbType: "urn:ietf:params:acme:error:invalidContact", 104 expectedProbDetail: `contact email has invalid domain: Domain name does not end with a valid public suffix (TLD)`, 105 }, 106 { 107 name: "contact domain invalid", 108 contacts: []string{"mailto:valid@valid.com", "mailto:a@example./.com"}, 109 expectedProbType: "urn:ietf:params:acme:error:invalidContact", 110 expectedProbDetail: "contact email has invalid domain: Domain name contains an invalid character", 111 }, 112 } 113 114 for _, tc := range testCases { 115 t.Run(tc.name, func(t *testing.T) { 116 var prob acme.Problem 117 _, err := makeClient(tc.contacts...) 118 if err != nil { 119 test.AssertErrorWraps(t, err, &prob) 120 test.AssertEquals(t, prob.Type, tc.expectedProbType) 121 test.AssertContains(t, prob.Detail, "Error validating contact(s)") 122 test.AssertContains(t, prob.Detail, tc.expectedProbDetail) 123 } else { 124 t.Errorf("expected %s type problem for %q, got nil", 125 tc.expectedProbType, strings.Join(tc.contacts, ",")) 126 } 127 }) 128 } 129 } 130 131 func TestRejectedIdentifier(t *testing.T) { 132 t.Parallel() 133 134 // When a single malformed name is provided, we correctly reject it. 135 idents := []acme.Identifier{ 136 {Type: "dns", Value: "яџ–Х6яяdь}"}, 137 } 138 _, err := authAndIssue(nil, nil, idents, true, "") 139 test.AssertError(t, err, "issuance should fail for one malformed name") 140 var prob acme.Problem 141 test.AssertErrorWraps(t, err, &prob) 142 test.AssertEquals(t, prob.Type, "urn:ietf:params:acme:error:rejectedIdentifier") 143 test.AssertContains(t, prob.Detail, "Domain name contains an invalid character") 144 145 // When multiple malformed names are provided, we correctly reject all of 146 // them and reflect this in suberrors. This test ensures that the way we 147 // encode these errors across the gRPC boundary is resilient to non-ascii 148 // characters. 149 idents = []acme.Identifier{ 150 {Type: "dns", Value: "o-"}, 151 {Type: "dns", Value: "ш№Ў"}, 152 {Type: "dns", Value: "р±y"}, 153 {Type: "dns", Value: "яџ–Х6яя"}, 154 {Type: "dns", Value: "яџ–Х6яя`ь"}, 155 } 156 _, err = authAndIssue(nil, nil, idents, true, "") 157 test.AssertError(t, err, "issuance should fail for multiple malformed names") 158 test.AssertErrorWraps(t, err, &prob) 159 test.AssertEquals(t, prob.Type, "urn:ietf:params:acme:error:rejectedIdentifier") 160 test.AssertContains(t, prob.Detail, "Domain name contains an invalid character") 161 test.AssertContains(t, prob.Detail, "and 4 more problems") 162 } 163 164 // TestBadSignatureAlgorithm tests that supplying an unacceptable value for the 165 // "alg" field of the JWS Protected Header results in a problem document with 166 // the set of acceptable "alg" values listed in a custom extension field named 167 // "algorithms". Creating a request with an unacceptable "alg" field requires 168 // us to do some shenanigans. 169 func TestBadSignatureAlgorithm(t *testing.T) { 170 t.Parallel() 171 172 client, err := makeClient() 173 if err != nil { 174 t.Fatal("creating test client") 175 } 176 177 header, err := json.Marshal(&struct { 178 Alg string `json:"alg"` 179 KID string `json:"kid"` 180 Nonce string `json:"nonce"` 181 URL string `json:"url"` 182 }{ 183 Alg: string(jose.RS512), // This is the important bit; RS512 is unacceptable. 184 KID: client.Account.URL, 185 Nonce: "deadbeef", // This nonce would fail, but that check comes after the alg check. 186 URL: client.Directory().NewAccount, 187 }) 188 if err != nil { 189 t.Fatalf("creating JWS protected header: %s", err) 190 } 191 protected := base64.RawURLEncoding.EncodeToString(header) 192 193 payload := base64.RawURLEncoding.EncodeToString([]byte(`{"onlyReturnExisting": true}`)) 194 hash := crypto.SHA512.New() 195 hash.Write([]byte(protected + "." + payload)) 196 sig, err := client.Account.PrivateKey.Sign(rand.Reader, hash.Sum(nil), crypto.SHA512) 197 if err != nil { 198 t.Fatalf("creating fake signature: %s", err) 199 } 200 201 data, err := json.Marshal(&struct { 202 Protected string `json:"protected"` 203 Payload string `json:"payload"` 204 Signature string `json:"signature"` 205 }{ 206 Protected: protected, 207 Payload: payload, 208 Signature: base64.RawURLEncoding.EncodeToString(sig), 209 }) 210 211 req, err := http.NewRequest(http.MethodPost, client.Directory().NewAccount, bytes.NewReader(data)) 212 if err != nil { 213 t.Fatalf("creating HTTP request: %s", err) 214 } 215 req.Header.Set("Content-Type", "application/jose+json") 216 217 resp, err := http.DefaultClient.Do(req) 218 if err != nil { 219 t.Fatalf("making HTTP request: %s", err) 220 } 221 defer resp.Body.Close() 222 223 body, err := io.ReadAll(resp.Body) 224 if err != nil { 225 t.Fatalf("reading HTTP response: %s", err) 226 } 227 228 var prob struct { 229 Type string `json:"type"` 230 Detail string `json:"detail"` 231 Status int `json:"status"` 232 Algorithms []jose.SignatureAlgorithm `json:"algorithms"` 233 } 234 err = json.Unmarshal(body, &prob) 235 if err != nil { 236 t.Fatalf("parsing HTTP response: %s", err) 237 } 238 239 if prob.Type != "urn:ietf:params:acme:error:badSignatureAlgorithm" { 240 t.Errorf("problem document has wrong type: want badSignatureAlgorithm, got %s", prob.Type) 241 } 242 if prob.Status != http.StatusBadRequest { 243 t.Errorf("problem document has wrong status: want 400, got %d", prob.Status) 244 } 245 if len(prob.Algorithms) == 0 { 246 t.Error("problem document MUST contain acceptable algorithms, got none") 247 } 248 } 249 250 // TestOrderFinalizeEarly tests that finalizing an order before it is fully 251 // authorized results in an orderNotReady error. 252 func TestOrderFinalizeEarly(t *testing.T) { 253 t.Parallel() 254 255 client, err := makeClient() 256 if err != nil { 257 t.Fatalf("creating acme client: %s", err) 258 } 259 260 idents := []acme.Identifier{{Type: "dns", Value: randomDomain(t)}} 261 262 order, err := client.Client.NewOrder(client.Account, idents) 263 if err != nil { 264 t.Fatalf("creating order: %s", err) 265 } 266 key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 267 if err != nil { 268 t.Fatalf("generating key: %s", err) 269 } 270 csr, err := makeCSR(key, idents, false) 271 if err != nil { 272 t.Fatalf("generating CSR: %s", err) 273 } 274 275 order, err = client.Client.FinalizeOrder(client.Account, order, csr) 276 if err == nil { 277 t.Fatal("expected finalize to fail, but got success") 278 } 279 var prob acme.Problem 280 ok := errors.As(err, &prob) 281 if !ok { 282 t.Fatalf("expected error to be of type acme.Problem, got: %T", err) 283 } 284 if prob.Type != "urn:ietf:params:acme:error:orderNotReady" { 285 t.Errorf("expected problem type 'urn:ietf:params:acme:error:orderNotReady', got: %s", prob.Type) 286 } 287 if order.Status != "pending" { 288 t.Errorf("expected order status to be pending, got: %s", order.Status) 289 } 290 }