1 package wfe2
2
3 import (
4 "bytes"
5 "context"
6 "crypto"
7 "crypto/ecdsa"
8 "crypto/elliptic"
9 "crypto/rand"
10 "crypto/rsa"
11 "crypto/x509"
12 "crypto/x509/pkix"
13 "encoding/asn1"
14 "encoding/base64"
15 "encoding/json"
16 "encoding/pem"
17 "errors"
18 "fmt"
19 "io"
20 "math/big"
21 "net/http"
22 "net/http/httptest"
23 "net/url"
24 "os"
25 "sort"
26 "strconv"
27 "strings"
28 "testing"
29 "time"
30
31 "github.com/jmhodges/clock"
32 "github.com/prometheus/client_golang/prometheus"
33 "golang.org/x/crypto/ocsp"
34 "google.golang.org/grpc"
35 "google.golang.org/protobuf/types/known/emptypb"
36 "google.golang.org/protobuf/types/known/timestamppb"
37 "gopkg.in/go-jose/go-jose.v2"
38
39 capb "github.com/letsencrypt/boulder/ca/proto"
40 "github.com/letsencrypt/boulder/cmd"
41 "github.com/letsencrypt/boulder/core"
42 corepb "github.com/letsencrypt/boulder/core/proto"
43 berrors "github.com/letsencrypt/boulder/errors"
44 "github.com/letsencrypt/boulder/features"
45 "github.com/letsencrypt/boulder/goodkey"
46 bgrpc "github.com/letsencrypt/boulder/grpc"
47 "github.com/letsencrypt/boulder/identifier"
48 "github.com/letsencrypt/boulder/issuance"
49 blog "github.com/letsencrypt/boulder/log"
50 "github.com/letsencrypt/boulder/metrics"
51 "github.com/letsencrypt/boulder/mocks"
52 "github.com/letsencrypt/boulder/must"
53 "github.com/letsencrypt/boulder/nonce"
54 noncepb "github.com/letsencrypt/boulder/nonce/proto"
55 "github.com/letsencrypt/boulder/probs"
56 rapb "github.com/letsencrypt/boulder/ra/proto"
57 "github.com/letsencrypt/boulder/ratelimits"
58 bredis "github.com/letsencrypt/boulder/redis"
59 "github.com/letsencrypt/boulder/revocation"
60 sapb "github.com/letsencrypt/boulder/sa/proto"
61 "github.com/letsencrypt/boulder/test"
62 inmemnonce "github.com/letsencrypt/boulder/test/inmem/nonce"
63 "github.com/letsencrypt/boulder/web"
64 )
65
66 const (
67 agreementURL = "http://example.invalid/terms"
68
69 test1KeyPublicJSON = `
70 {
71 "kty":"RSA",
72 "n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ",
73 "e":"AQAB"
74 }`
75
76 test1KeyPrivatePEM = `
77 -----BEGIN RSA PRIVATE KEY-----
78 MIIEowIBAAKCAQEAyNWVhtYEKJR21y9xsHV+PD/bYwbXSeNuFal46xYxVfRL5mqh
79 a7vttvjB/vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K/klBYN8oYvTwwmeSkAz
80 6ut7ZxPv+nZaT5TJhGk0NT2kh/zSpdriEJ/3vW+mqxYbbBmpvHqsa1/zx9fSuHYc
81 tAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV+mzfMyboQjujPh7aNJxAWS
82 q4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF+w8hOTI3XXohUdu
83 29Se26k2B0PolDSuj0GIQU6+W9TdLXSjBb2SpQIDAQABAoIBAHw58SXYV/Yp72Cn
84 jjFSW+U0sqWMY7rmnP91NsBjl9zNIe3C41pagm39bTIjB2vkBNR8ZRG7pDEB/QAc
85 Cn9Keo094+lmTArjL407ien7Ld+koW7YS8TyKADYikZo0vAK3qOy14JfQNiFAF9r
86 Bw61hG5/E58cK5YwQZe+YcyBK6/erM8fLrJEyw4CV49wWdq/QqmNYU1dx4OExAkl
87 KMfvYXpjzpvyyTnZuS4RONfHsO8+JTyJVm+lUv2x+bTce6R4W++UhQY38HakJ0x3
88 XRfXooRv1Bletu5OFlpXfTSGz/5gqsfemLSr5UHncsCcFMgoFBsk2t/5BVukBgC7
89 PnHrAjkCgYEA887PRr7zu3OnaXKxylW5U5t4LzdMQLpslVW7cLPD4Y08Rye6fF5s
90 O/jK1DNFXIoUB7iS30qR7HtaOnveW6H8/kTmMv/YAhLO7PAbRPCKxxcKtniEmP1x
91 ADH0tF2g5uHB/zeZhCo9qJiF0QaJynvSyvSyJFmY6lLvYZsAW+C+PesCgYEA0uCi
92 Q8rXLzLpfH2NKlLwlJTi5JjE+xjbabgja0YySwsKzSlmvYJqdnE2Xk+FHj7TCnSK
93 KUzQKR7+rEk5flwEAf+aCCNh3W4+Hp9MmrdAcCn8ZsKmEW/o7oDzwiAkRCmLw/ck
94 RSFJZpvFoxEg15riT37EjOJ4LBZ6SwedsoGA/a8CgYEA2Ve4sdGSR73/NOKZGc23
95 q4/B4R2DrYRDPhEySnMGoPCeFrSU6z/lbsUIU4jtQWSaHJPu4n2AfncsZUx9WeSb
96 OzTCnh4zOw33R4N4W8mvfXHODAJ9+kCc1tax1YRN5uTEYzb2dLqPQtfNGxygA1DF
97 BkaC9CKnTeTnH3TlKgK8tUcCgYB7J1lcgh+9ntwhKinBKAL8ox8HJfkUM+YgDbwR
98 sEM69E3wl1c7IekPFvsLhSFXEpWpq3nsuMFw4nsVHwaGtzJYAHByhEdpTDLXK21P
99 heoKF1sioFbgJB1C/Ohe3OqRLDpFzhXOkawOUrbPjvdBM2Erz/r11GUeSlpNazs7
100 vsoYXQKBgFwFM1IHmqOf8a2wEFa/a++2y/WT7ZG9nNw1W36S3P04K4lGRNRS2Y/S
101 snYiqxD9nL7pVqQP2Qbqbn0yD6d3G5/7r86F7Wu2pihM8g6oyMZ3qZvvRIBvKfWo
102 eROL1ve1vmQF3kjrMPhhK2kr6qdWnTE5XlPllVSZFQenSTzj98AO
103 -----END RSA PRIVATE KEY-----
104 `
105
106 test2KeyPublicJSON = `{
107 "kty":"RSA",
108 "n":"qnARLrT7Xz4gRcKyLdydmCr-ey9OuPImX4X40thk3on26FkMznR3fRjs66eLK7mmPcBZ6uOJseURU6wAaZNmemoYx1dMvqvWWIyiQleHSD7Q8vBrhR6uIoO4jAzJZR-ChzZuSDt7iHN-3xUVspu5XGwXU_MVJZshTwp4TaFx5elHIT_ObnTvTOU3Xhish07AbgZKmWsVbXh5s-CrIicU4OexJPgunWZ_YJJueOKmTvnLlTV4MzKR2oZlBKZ27S0-SfdV_QDx_ydle5oMAyKVtlAV35cyPMIsYNwgUGBCdY_2Uzi5eX0lTc7MPRwz6qR1kip-i59VcGcUQgqHV6Fyqw",
109 "e":"AQAB"
110 }`
111
112 test2KeyPrivatePEM = `
113 -----BEGIN RSA PRIVATE KEY-----
114 MIIEpAIBAAKCAQEAqnARLrT7Xz4gRcKyLdydmCr+ey9OuPImX4X40thk3on26FkM
115 znR3fRjs66eLK7mmPcBZ6uOJseURU6wAaZNmemoYx1dMvqvWWIyiQleHSD7Q8vBr
116 hR6uIoO4jAzJZR+ChzZuSDt7iHN+3xUVspu5XGwXU/MVJZshTwp4TaFx5elHIT/O
117 bnTvTOU3Xhish07AbgZKmWsVbXh5s+CrIicU4OexJPgunWZ/YJJueOKmTvnLlTV4
118 MzKR2oZlBKZ27S0+SfdV/QDx/ydle5oMAyKVtlAV35cyPMIsYNwgUGBCdY/2Uzi5
119 eX0lTc7MPRwz6qR1kip+i59VcGcUQgqHV6FyqwIDAQABAoIBAG5m8Xpj2YC0aYtG
120 tsxmX9812mpJFqFOmfS+f5N0gMJ2c+3F4TnKz6vE/ZMYkFnehAT0GErC4WrOiw68
121 F/hLdtJM74gQ0LGh9dKeJmz67bKqngcAHWW5nerVkDGIBtzuMEsNwxofDcIxrjkr
122 G0b7AHMRwXqrt0MI3eapTYxby7+08Yxm40mxpSsW87FSaI61LDxUDpeVkn7kolSN
123 WifVat7CpZb/D2BfGAQDxiU79YzgztpKhbynPdGc/OyyU+CNgk9S5MgUX2m9Elh3
124 aXrWh2bT2xzF+3KgZdNkJQcdIYVoGq/YRBxlGXPYcG4Do3xKhBmH79Io2BizevZv
125 nHkbUGECgYEAydjb4rl7wYrElDqAYpoVwKDCZAgC6o3AKSGXfPX1Jd2CXgGR5Hkl
126 ywP0jdSLbn2v/jgKQSAdRbYuEiP7VdroMb5M6BkBhSY619cH8etoRoLzFo1GxcE8
127 Y7B598VXMq8TT+TQqw/XRvM18aL3YDZ3LSsR7Gl2jF/sl6VwQAaZToUCgYEA2Cn4
128 fG58ME+M4IzlZLgAIJ83PlLb9ip6MeHEhUq2Dd0In89nss7Acu0IVg8ES88glJZy
129 4SjDLGSiuQuoQVo9UBq/E5YghdMJFp5ovwVfEaJ+ruWqOeujvWzzzPVyIWSLXRQa
130 N4kedtfrlqldMIXywxVru66Q1NOGvhDHm/Q8+28CgYEAkhLCbn3VNed7A9qidrkT
131 7OdqRoIVujEDU8DfpKtK0jBP3EA+mJ2j4Bvoq4uZrEiBSPS9VwwqovyIstAfX66g
132 Qv95IK6YDwfvpawUL9sxB3ZU/YkYIp0JWwun+Mtzo1ZYH4V0DZfVL59q9of9hj9k
133 V+fHfNOF22jAC67KYUtlPxECgYEAwF6hj4L3rDqvQYrB/p8tJdrrW+B7dhgZRNkJ
134 fiGd4LqLGUWHoH4UkHJXT9bvWNPMx88YDz6qapBoq8svAnHfTLFwyGp7KP1FAkcZ
135 Kp4KG/SDTvx+QCtvPX1/fjAUUJlc2QmxxyiU3uiK9Tpl/2/FOk2O4aiZpX1VVUIz
136 kZuKxasCgYBiVRkEBk2W4Ia0B7dDkr2VBrz4m23Y7B9cQLpNAapiijz/0uHrrCl8
137 TkLlEeVOuQfxTadw05gzKX0jKkMC4igGxvEeilYc6NR6a4nvRulG84Q8VV9Sy9Ie
138 wk6Oiadty3eQqSBJv0HnpmiEdQVffIK5Pg4M8Dd+aOBnEkbopAJOuA==
139 -----END RSA PRIVATE KEY-----
140 `
141 test3KeyPrivatePEM = `
142 -----BEGIN RSA PRIVATE KEY-----
143 MIIEpAIBAAKCAQEAuTQER6vUA1RDixS8xsfCRiKUNGRzzyIK0MhbS2biClShbb0h
144 Sx2mPP7gBvis2lizZ9r+y9hL57kNQoYCKndOBg0FYsHzrQ3O9AcoV1z2Mq+XhHZb
145 FrVYaXI0M3oY9BJCWog0dyi3XC0x8AxC1npd1U61cToHx+3uSvgZOuQA5ffEn5L3
146 8Dz1Ti7OV3E4XahnRJvejadUmTkki7phLBUXm5MnnyFm0CPpf6ApV7zhLjN5W+nV
147 0WL17o7v8aDgV/t9nIdi1Y26c3PlCEtiVHZcebDH5F1Deta3oLLg9+g6rWnTqPbY
148 3knffhp4m0scLD6e33k8MtzxDX/D7vHsg0/X1wIDAQABAoIBAQCnFJpX3lhiuH5G
149 1uqHmmdVxpRVv9oKn/eJ63cRSzvZfgg0bE/A6Hq0xGtvXqDySttvck4zsGqqHnQr
150 86G4lfE53D1jnv4qvS5bUKnARwmFKIxU4EHE9s1QM8uMNTaV2nMqIX7TkVP6QHuw
151 yB70R2inq15dS7EBWVGFKNX6HwAAdj8pFuF6o2vIwmAfee20aFzpWWf81jOH9Ai6
152 hyJyV3NqrU1JzIwlXaeX67R1VroFdhN/lapp+2b0ZEcJJtFlcYFl99NjkQeVZyik
153 izNv0GZZNWizc57wU0/8cv+jQ2f26ltvyrPz3QNK61bFfzy+/tfMvLq7sdCmztKJ
154 tMxCBJOBAoGBAPKnIVQIS2nTvC/qZ8ajw1FP1rkvYblIiixegjgfFhM32HehQ+nu
155 3TELi3I3LngLYi9o6YSqtNBmdBJB+DUAzIXp0TdOihOweGiv5dAEWwY9rjCzMT5S
156 GP7dCWiJwoMUHrOs1Po3dwcjj/YsoAW+FC0jSvach2Ln2CvPgr5FP0ARAoGBAMNj
157 64qUCzgeXiSyPKK69bCCGtHlTYUndwHQAZmABjbmxAXZNYgp/kBezFpKOwmICE8R
158 kK8YALRrL0VWXl/yj85b0HAZGkquNFHPUDd1e6iiP5TrY+Hy4oqtlYApjH6f85CE
159 lWjQ1iyUL7aT6fcSgzq65ZWD2hUzvNtWbTt6zQFnAoGAWS/EuDY0QblpOdNWQVR/
160 vasyqO4ZZRiccKJsCmSioH2uOoozhBAfjJ9JqblOgyDr/bD546E6xD5j+zH0IMci
161 ZTYDh+h+J659Ez1Topl3O1wAYjX6q4VRWpuzkZDQxYznm/KydSVdwmn3x+uvBW1P
162 zSdjrjDqMhg1BCVJUNXy4YECgYEAjX1z+dwO68qB3gz7/9NnSzRL+6cTJdNYSIW6
163 QtAEsAkX9iw+qaXPKgn77X5HljVd3vQXU9QL3pqnloxetxhNrt+p5yMmeOIBnSSF
164 MEPxEkK7zDlRETPzfP0Kf86WoLNviz2XfFmOXqXIj2w5RuOvB/6DdmwOpr/aiPLj
165 EulwPw0CgYAMSzsWOt6vU+y/G5NyhUCHvY50TdnGOj2btBk9rYVwWGWxCpg2QF0R
166 pcKXgGzXEVZKFAqB8V1c/mmCo8ojPgmqGM+GzX2Bj4seVBW7PsTeZUjrHpADshjV
167 F7o5b7y92NlxO5kwQzRKEAhwS5PbKJdx90iCuG+JlI1YgWlA1VcJMw==
168 -----END RSA PRIVATE KEY-----
169 `
170
171 testE1KeyPrivatePEM = `
172 -----BEGIN EC PRIVATE KEY-----
173 MHcCAQEEIH+p32RUnqT/iICBEGKrLIWFcyButv0S0lU/BLPOyHn2oAoGCCqGSM49
174 AwEHoUQDQgAEFwvSZpu06i3frSk/mz9HcD9nETn4wf3mQ+zDtG21GapLytH7R1Zr
175 ycBzDV9u6cX9qNLc9Bn5DAumz7Zp2AuA+Q==
176 -----END EC PRIVATE KEY-----
177 `
178
179 testE2KeyPrivatePEM = `
180 -----BEGIN EC PRIVATE KEY-----
181 MHcCAQEEIFRcPxQ989AY6se2RyIoF1ll9O6gHev4oY15SWJ+Jf5eoAoGCCqGSM49
182 AwEHoUQDQgAES8FOmrZ3ywj4yyFqt0etAD90U+EnkNaOBSLfQmf7pNi8y+kPKoUN
183 EeMZ9nWyIM6bktLrE11HnFOnKhAYsM5fZA==
184 -----END EC PRIVATE KEY-----`
185 )
186
187 type MockRegistrationAuthority struct {
188 lastRevocationReason revocation.Reason
189 }
190
191 func (ra *MockRegistrationAuthority) NewRegistration(ctx context.Context, in *corepb.Registration, _ ...grpc.CallOption) (*corepb.Registration, error) {
192 in.Id = 1
193 created := time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
194 in.CreatedAtNS = created.UnixNano()
195 in.CreatedAt = timestamppb.New(created)
196 return in, nil
197 }
198
199 func (ra *MockRegistrationAuthority) UpdateRegistration(ctx context.Context, in *rapb.UpdateRegistrationRequest, _ ...grpc.CallOption) (*corepb.Registration, error) {
200 if !bytes.Equal(in.Base.Key, in.Update.Key) {
201 in.Base.Key = in.Update.Key
202 }
203 return in.Base, nil
204 }
205
206 func (ra *MockRegistrationAuthority) PerformValidation(context.Context, *rapb.PerformValidationRequest, ...grpc.CallOption) (*corepb.Authorization, error) {
207 return &corepb.Authorization{}, nil
208 }
209
210 func (ra *MockRegistrationAuthority) RevokeCertByApplicant(ctx context.Context, in *rapb.RevokeCertByApplicantRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
211 ra.lastRevocationReason = revocation.Reason(in.Code)
212 return &emptypb.Empty{}, nil
213 }
214
215 func (ra *MockRegistrationAuthority) RevokeCertByKey(ctx context.Context, in *rapb.RevokeCertByKeyRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
216 ra.lastRevocationReason = revocation.Reason(ocsp.KeyCompromise)
217 return &emptypb.Empty{}, nil
218 }
219
220 func (ra *MockRegistrationAuthority) GenerateOCSP(ctx context.Context, req *rapb.GenerateOCSPRequest, _ ...grpc.CallOption) (*capb.OCSPResponse, error) {
221 return nil, nil
222 }
223
224 func (ra *MockRegistrationAuthority) AdministrativelyRevokeCertificate(context.Context, *rapb.AdministrativelyRevokeCertificateRequest, ...grpc.CallOption) (*emptypb.Empty, error) {
225 return &emptypb.Empty{}, nil
226 }
227
228 func (ra *MockRegistrationAuthority) OnValidationUpdate(context.Context, core.Authorization, ...grpc.CallOption) error {
229 return nil
230 }
231
232 func (ra *MockRegistrationAuthority) DeactivateAuthorization(context.Context, *corepb.Authorization, ...grpc.CallOption) (*emptypb.Empty, error) {
233 return &emptypb.Empty{}, nil
234 }
235
236 func (ra *MockRegistrationAuthority) DeactivateRegistration(context.Context, *corepb.Registration, ...grpc.CallOption) (*emptypb.Empty, error) {
237 return &emptypb.Empty{}, nil
238 }
239
240 func (ra *MockRegistrationAuthority) NewOrder(ctx context.Context, in *rapb.NewOrderRequest, _ ...grpc.CallOption) (*corepb.Order, error) {
241 created := time.Date(2021, 1, 1, 1, 1, 1, 0, time.UTC)
242 expires := time.Date(2021, 2, 1, 1, 1, 1, 0, time.UTC)
243
244 return &corepb.Order{
245 Id: 1,
246 RegistrationID: in.RegistrationID,
247 CreatedNS: created.UnixNano(),
248 Created: timestamppb.New(created),
249 ExpiresNS: expires.UnixNano(),
250 Expires: timestamppb.New(expires),
251 Names: in.Names,
252 Status: string(core.StatusPending),
253 V2Authorizations: []int64{1},
254 }, nil
255 }
256
257 func (ra *MockRegistrationAuthority) FinalizeOrder(ctx context.Context, in *rapb.FinalizeOrderRequest, _ ...grpc.CallOption) (*corepb.Order, error) {
258 in.Order.Status = string(core.StatusProcessing)
259 return in.Order, nil
260 }
261
262 func makeBody(s string) io.ReadCloser {
263 return io.NopCloser(strings.NewReader(s))
264 }
265
266
267
268 func loadKey(t *testing.T, keyBytes []byte) crypto.Signer {
269
270
271
272 block, _ := pem.Decode(keyBytes)
273 if block == nil {
274 t.Fatal("Unable to decode private key PEM bytes")
275 }
276
277
278 if rsaKey, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
279 return rsaKey
280 }
281
282
283 if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
284
285 switch k := key.(type) {
286 case *rsa.PrivateKey:
287 return k
288 case *ecdsa.PrivateKey:
289 return k
290 }
291 }
292
293
294 if ecdsaKey, err := x509.ParseECPrivateKey(block.Bytes); err == nil {
295 return ecdsaKey
296 }
297
298
299 t.Fatalf("Unable to decode private key PEM bytes")
300
301 return nil
302 }
303
304 var testKeyPolicy = goodkey.KeyPolicy{
305 AllowRSA: true,
306 AllowECDSANISTP256: true,
307 AllowECDSANISTP384: true,
308 }
309
310 var ctx = context.Background()
311
312 func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock, requestSigner) {
313 features.Reset()
314
315 fc := clock.NewFake()
316 stats := metrics.NoopRegisterer
317
318 certChains := map[issuance.IssuerNameID][][]byte{}
319 issuerCertificates := map[issuance.IssuerNameID]*issuance.Certificate{}
320 for _, files := range [][]string{
321 {
322 "../test/hierarchy/int-r3.cert.pem",
323 "../test/hierarchy/root-x1.cert.pem",
324 },
325 {
326 "../test/hierarchy/int-r3-cross.cert.pem",
327 "../test/hierarchy/root-dst.cert.pem",
328 },
329 {
330 "../test/hierarchy/int-e1.cert.pem",
331 "../test/hierarchy/root-x2.cert.pem",
332 },
333 {
334 "../test/hierarchy/int-e1.cert.pem",
335 "../test/hierarchy/root-x2-cross.cert.pem",
336 "../test/hierarchy/root-x1-cross.cert.pem",
337 "../test/hierarchy/root-dst.cert.pem",
338 },
339 } {
340 certs, err := issuance.LoadChain(files)
341 test.AssertNotError(t, err, "Unable to load chain")
342 var buf bytes.Buffer
343 for _, cert := range certs {
344 buf.Write([]byte("\n"))
345 buf.Write(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}))
346 }
347 id := certs[0].NameID()
348 certChains[id] = append(certChains[id], buf.Bytes())
349 issuerCertificates[id] = certs[0]
350 }
351
352 mockSA := mocks.NewStorageAuthorityReadOnly(fc)
353
354 log := blog.NewMock()
355
356 var gnc nonce.Getter
357 var noncePrefixMap map[string]nonce.Redeemer
358 var rnc nonce.Redeemer
359 var rncKey string
360 var inmemNonceService *inmemnonce.Service
361 var limiter *ratelimits.Limiter
362 if strings.Contains(os.Getenv("BOULDER_CONFIG_DIR"), "test/config-next") {
363
364 noncePrefix := nonce.DerivePrefix("192.168.1.1:8080", "b8c758dd85e113ea340ce0b3a99f389d40a308548af94d1730a7692c1874f1f")
365 nonceService, err := nonce.NewNonceService(metrics.NoopRegisterer, 100, noncePrefix)
366 test.AssertNotError(t, err, "making nonceService")
367
368 inmemNonceService = &inmemnonce.Service{NonceService: nonceService}
369 gnc = inmemNonceService
370 rnc = inmemNonceService
371
372
373 rc := bredis.Config{
374 Username: "unittest-rw",
375 TLS: cmd.TLSConfig{
376 CACertFile: "../test/redis-tls/minica.pem",
377 CertFile: "../test/redis-tls/boulder/cert.pem",
378 KeyFile: "../test/redis-tls/boulder/key.pem",
379 },
380 Lookups: []cmd.ServiceDomain{
381 {
382 Service: "redisratelimits",
383 Domain: "service.consul",
384 },
385 },
386 LookupDNSAuthority: "consul.service.consul",
387 }
388 rc.PasswordConfig = cmd.PasswordConfig{
389 PasswordFile: "../test/secrets/ratelimits_redis_password",
390 }
391 ring, err := bredis.NewRingFromConfig(rc, stats, log)
392 test.AssertNotError(t, err, "making redis ring client")
393 source := ratelimits.NewRedisSource(ring.Ring, fc, stats)
394 test.AssertNotNil(t, source, "source should not be nil")
395 limiter, err = ratelimits.NewLimiter(fc, source, "../test/config-next/wfe2-ratelimit-defaults.yml", "", stats)
396 test.AssertNotError(t, err, "making limiter")
397 } else {
398
399 noncePrefix := "mlem"
400 nonceService, err := nonce.NewNonceService(metrics.NoopRegisterer, 100, noncePrefix)
401 test.AssertNotError(t, err, "making nonceService")
402
403 inmemNonceService = &inmemnonce.Service{NonceService: nonceService}
404 gnc = inmemNonceService
405 noncePrefixMap = map[string]nonce.Redeemer{noncePrefix: inmemNonceService}
406 rnc = inmemNonceService
407 }
408
409 wfe, err := NewWebFrontEndImpl(
410 stats,
411 fc,
412 testKeyPolicy,
413 certChains,
414 issuerCertificates,
415 blog.NewMock(),
416 10*time.Second,
417 10*time.Second,
418 30*24*time.Hour,
419 7*24*time.Hour,
420 &MockRegistrationAuthority{},
421 mockSA,
422 gnc,
423 noncePrefixMap,
424 rnc,
425 rncKey,
426 mockSA,
427 limiter)
428 test.AssertNotError(t, err, "Unable to create WFE")
429
430 wfe.SubscriberAgreementURL = agreementURL
431
432 return wfe, fc, requestSigner{t, inmemNonceService.AsSource()}
433 }
434
435
436
437
438 func makePostRequestWithPath(path string, body string) *http.Request {
439 request := &http.Request{
440 Method: "POST",
441 RemoteAddr: "1.1.1.1:7882",
442 Header: map[string][]string{
443 "Content-Length": {strconv.Itoa(len(body))},
444 "Content-Type": {expectedJWSContentType},
445 },
446 Body: makeBody(body),
447 Host: "localhost",
448 }
449 url := mustParseURL(path)
450 request.URL = url
451 request.RequestURI = url.Path
452 return request
453 }
454
455
456
457
458
459 func signAndPost(signer requestSigner, path, signedURL, payload string) *http.Request {
460 _, _, body := signer.byKeyID(1, nil, signedURL, payload)
461 return makePostRequestWithPath(path, body)
462 }
463
464 func mustParseURL(s string) *url.URL {
465 return must.Do(url.Parse(s))
466 }
467
468 func sortHeader(s string) string {
469 a := strings.Split(s, ", ")
470 sort.Strings(a)
471 return strings.Join(a, ", ")
472 }
473
474 func addHeadIfGet(s []string) []string {
475 for _, a := range s {
476 if a == "GET" {
477 return append(s, "HEAD")
478 }
479 }
480 return s
481 }
482
483 func TestHandleFunc(t *testing.T) {
484 wfe, _, _ := setupWFE(t)
485 var mux *http.ServeMux
486 var rw *httptest.ResponseRecorder
487 var stubCalled bool
488 runWrappedHandler := func(req *http.Request, pattern string, allowed ...string) {
489 mux = http.NewServeMux()
490 rw = httptest.NewRecorder()
491 stubCalled = false
492 wfe.HandleFunc(mux, pattern, func(context.Context, *web.RequestEvent, http.ResponseWriter, *http.Request) {
493 stubCalled = true
494 }, allowed...)
495 req.URL = mustParseURL(pattern)
496 mux.ServeHTTP(rw, req)
497 }
498
499
500 type testCase struct {
501 allowed []string
502 reqMethod string
503 shouldCallStub bool
504 shouldSucceed bool
505 pattern string
506 }
507 var lastNonce string
508 for _, c := range []testCase{
509 {[]string{"GET", "POST"}, "GET", true, true, "/test"},
510 {[]string{"GET", "POST"}, "GET", true, true, newNoncePath},
511 {[]string{"GET", "POST"}, "POST", true, true, "/test"},
512 {[]string{"GET"}, "", false, false, "/test"},
513 {[]string{"GET"}, "POST", false, false, "/test"},
514 {[]string{"GET"}, "OPTIONS", false, true, "/test"},
515 {[]string{"GET"}, "MAKE-COFFEE", false, false, "/test"},
516 {[]string{"GET"}, "GET", true, true, directoryPath},
517 } {
518 runWrappedHandler(&http.Request{Method: c.reqMethod}, c.pattern, c.allowed...)
519 test.AssertEquals(t, stubCalled, c.shouldCallStub)
520 if c.shouldSucceed {
521 test.AssertEquals(t, rw.Code, http.StatusOK)
522 } else {
523 test.AssertEquals(t, rw.Code, http.StatusMethodNotAllowed)
524 test.AssertEquals(t, sortHeader(rw.Header().Get("Allow")), sortHeader(strings.Join(addHeadIfGet(c.allowed), ", ")))
525 test.AssertUnmarshaledEquals(t,
526 rw.Body.String(),
527 `{"type":"`+probs.ErrorNS+`malformed","detail":"Method not allowed","status":405}`)
528 }
529 if c.reqMethod == "GET" && c.pattern != newNoncePath {
530 nonce := rw.Header().Get("Replay-Nonce")
531 test.AssertEquals(t, nonce, "")
532 } else {
533 nonce := rw.Header().Get("Replay-Nonce")
534 test.AssertNotEquals(t, nonce, lastNonce)
535 test.AssertNotEquals(t, nonce, "")
536 lastNonce = nonce
537 }
538 linkHeader := rw.Header().Get("Link")
539 if c.pattern != directoryPath {
540
541 test.AssertEquals(t, linkHeader, `<http://localhost/directory>;rel="index"`)
542 } else {
543
544 test.AssertEquals(t, linkHeader, "")
545 }
546 }
547
548
549 runWrappedHandler(&http.Request{Method: "PUT"}, "/test", "GET", "POST")
550 test.AssertEquals(t, rw.Header().Get("Content-Type"), "application/problem+json")
551 test.AssertUnmarshaledEquals(t, rw.Body.String(), `{"type":"`+probs.ErrorNS+`malformed","detail":"Method not allowed","status":405}`)
552 test.AssertEquals(t, sortHeader(rw.Header().Get("Allow")), "GET, HEAD, POST")
553
554
555 runWrappedHandler(&http.Request{Method: "HEAD"}, "/test", "GET", "POST")
556 test.AssertEquals(t, stubCalled, true)
557 test.AssertEquals(t, rw.Body.String(), "")
558
559
560 runWrappedHandler(&http.Request{Method: "HEAD"}, "/test", "POST")
561 test.AssertEquals(t, stubCalled, false)
562 test.AssertEquals(t, rw.Code, http.StatusMethodNotAllowed)
563 test.AssertEquals(t, rw.Header().Get("Content-Type"), "application/problem+json")
564 test.AssertEquals(t, rw.Header().Get("Allow"), "POST")
565 test.AssertUnmarshaledEquals(t, rw.Body.String(), `{"type":"`+probs.ErrorNS+`malformed","detail":"Method not allowed","status":405}`)
566
567 wfe.AllowOrigins = []string{"*"}
568 testOrigin := "https://example.com"
569
570
571 runWrappedHandler(&http.Request{
572 Method: "POST",
573 Header: map[string][]string{
574 "Origin": {testOrigin},
575 },
576 }, "/test", "GET")
577 test.AssertEquals(t, stubCalled, false)
578 test.AssertEquals(t, rw.Code, http.StatusMethodNotAllowed)
579
580
581 runWrappedHandler(&http.Request{
582 Method: "GET",
583 Header: map[string][]string{
584 "Origin": {testOrigin},
585 },
586 }, "/test", "GET", "POST")
587 test.AssertEquals(t, stubCalled, true)
588 test.AssertEquals(t, rw.Code, http.StatusOK)
589 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Methods"), "")
590 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Origin"), "*")
591 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Headers"), "Content-Type")
592 test.AssertEquals(t, sortHeader(rw.Header().Get("Access-Control-Expose-Headers")), "Link, Location, Replay-Nonce")
593
594
595 runWrappedHandler(&http.Request{
596 Method: "OPTIONS",
597 Header: map[string][]string{
598 "Origin": {testOrigin},
599 "Access-Control-Request-Method": {"POST"},
600 },
601 }, "/test", "GET")
602 test.AssertEquals(t, stubCalled, false)
603 test.AssertEquals(t, rw.Code, http.StatusOK)
604 test.AssertEquals(t, rw.Header().Get("Allow"), "GET, HEAD")
605 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Origin"), "")
606 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Headers"), "")
607
608
609 runWrappedHandler(&http.Request{
610 Method: "OPTIONS",
611 Header: map[string][]string{
612 "Origin": {testOrigin},
613 "Access-Control-Request-Method": {"POST"},
614 "Access-Control-Request-Headers": {"X-Accept-Header1, X-Accept-Header2", "X-Accept-Header3"},
615 },
616 }, "/test", "GET", "POST")
617 test.AssertEquals(t, rw.Code, http.StatusOK)
618 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Origin"), "*")
619 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Headers"), "Content-Type")
620 test.AssertEquals(t, rw.Header().Get("Access-Control-Max-Age"), "86400")
621 test.AssertEquals(t, sortHeader(rw.Header().Get("Access-Control-Allow-Methods")), "GET, HEAD, POST")
622 test.AssertEquals(t, sortHeader(rw.Header().Get("Access-Control-Expose-Headers")), "Link, Location, Replay-Nonce")
623
624
625
626 runWrappedHandler(&http.Request{
627 Method: "OPTIONS",
628 Header: map[string][]string{
629 "Access-Control-Request-Method": {"POST"},
630 },
631 }, "/test", "GET", "POST")
632 test.AssertEquals(t, rw.Code, http.StatusOK)
633 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Origin"), "")
634 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Headers"), "")
635 test.AssertEquals(t, sortHeader(rw.Header().Get("Allow")), "GET, HEAD, POST")
636
637
638
639 for _, allowedMethod := range []string{"GET", "POST"} {
640 runWrappedHandler(&http.Request{
641 Method: "OPTIONS",
642 Header: map[string][]string{
643 "Origin": {testOrigin},
644 },
645 }, "/test", allowedMethod)
646 test.AssertEquals(t, rw.Code, http.StatusOK)
647 if allowedMethod == "GET" {
648 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Origin"), "*")
649 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Headers"), "Content-Type")
650 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Methods"), "GET, HEAD")
651 } else {
652 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Origin"), "")
653 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Headers"), "")
654 }
655 }
656
657
658
659 for _, wfe.AllowOrigins = range [][]string{
660 {},
661 {"http://example.com", "https://other.example"},
662 {""},
663 } {
664 runWrappedHandler(&http.Request{
665 Method: "OPTIONS",
666 Header: map[string][]string{
667 "Origin": {testOrigin},
668 "Access-Control-Request-Method": {"POST"},
669 },
670 }, "/test", "POST")
671 test.AssertEquals(t, rw.Code, http.StatusOK)
672 for _, h := range []string{
673 "Access-Control-Allow-Methods",
674 "Access-Control-Allow-Origin",
675 "Access-Control-Allow-Headers",
676 "Access-Control-Expose-Headers",
677 "Access-Control-Request-Headers",
678 } {
679 test.AssertEquals(t, rw.Header().Get(h), "")
680 }
681 }
682
683
684
685 for _, wfe.AllowOrigins = range [][]string{
686 {testOrigin, "http://example.org", "*"},
687 {"", "http://example.org", testOrigin},
688 } {
689 runWrappedHandler(&http.Request{
690 Method: "OPTIONS",
691 Header: map[string][]string{
692 "Origin": {testOrigin},
693 "Access-Control-Request-Method": {"POST"},
694 },
695 }, "/test", "POST")
696 test.AssertEquals(t, rw.Code, http.StatusOK)
697 test.AssertEquals(t, rw.Header().Get("Access-Control-Allow-Origin"), testOrigin)
698
699 test.AssertEquals(t, rw.Header().Get("Vary"), "Origin")
700 }
701 }
702
703 func TestPOST404(t *testing.T) {
704 wfe, _, _ := setupWFE(t)
705 responseWriter := httptest.NewRecorder()
706 url, _ := url.Parse("/foobar")
707 wfe.Index(ctx, newRequestEvent(), responseWriter, &http.Request{
708 Method: "POST",
709 URL: url,
710 })
711 test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
712 }
713
714 func TestIndex(t *testing.T) {
715 wfe, _, _ := setupWFE(t)
716
717 responseWriter := httptest.NewRecorder()
718
719 url, _ := url.Parse("/")
720 wfe.Index(ctx, newRequestEvent(), responseWriter, &http.Request{
721 Method: "GET",
722 URL: url,
723 })
724 test.AssertEquals(t, responseWriter.Code, http.StatusOK)
725 test.AssertNotEquals(t, responseWriter.Body.String(), "404 page not found\n")
726 test.Assert(t, strings.Contains(responseWriter.Body.String(), directoryPath),
727 "directory path not found")
728 test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache")
729
730 responseWriter.Body.Reset()
731 responseWriter.Header().Del("Cache-Control")
732 url, _ = url.Parse("/foo")
733 wfe.Index(ctx, newRequestEvent(), responseWriter, &http.Request{
734 URL: url,
735 })
736
737 test.AssertEquals(t, responseWriter.Body.String(), "404 page not found\n")
738 test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "")
739 }
740
741
742
743
744 func randomDirectoryKeyPresent(t *testing.T, buf []byte) bool {
745 var dir map[string]interface{}
746 err := json.Unmarshal(buf, &dir)
747 if err != nil {
748 t.Errorf("Failed to unmarshal directory: %s", err)
749 }
750 for _, v := range dir {
751 if v == randomDirKeyExplanationLink {
752 return true
753 }
754 }
755 return false
756 }
757
758 type fakeRand struct{}
759
760 func (fr fakeRand) Read(p []byte) (int, error) {
761 return len(p), nil
762 }
763
764 func TestDirectory(t *testing.T) {
765 wfe, _, signer := setupWFE(t)
766 mux := wfe.Handler(metrics.NoopRegisterer)
767 core.RandReader = fakeRand{}
768 defer func() { core.RandReader = rand.Reader }()
769
770 dirURL, _ := url.Parse("/directory")
771
772 getReq := &http.Request{
773 Method: http.MethodGet,
774 URL: dirURL,
775 Host: "localhost:4300",
776 }
777
778 _, _, jwsBody := signer.byKeyID(1, nil, "http://localhost/directory", "")
779 postAsGetReq := makePostRequestWithPath("/directory", jwsBody)
780
781 testCases := []struct {
782 name string
783 caaIdent string
784 website string
785 expectedJSON string
786 request *http.Request
787 }{
788 {
789 name: "standard GET, no CAA ident/website meta",
790 request: getReq,
791 expectedJSON: `{
792 "keyChange": "http://localhost:4300/acme/key-change",
793 "meta": {
794 "termsOfService": "http://example.invalid/terms"
795 },
796 "newNonce": "http://localhost:4300/acme/new-nonce",
797 "newAccount": "http://localhost:4300/acme/new-acct",
798 "newOrder": "http://localhost:4300/acme/new-order",
799 "revokeCert": "http://localhost:4300/acme/revoke-cert",
800 "AAAAAAAAAAA": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417"
801 }`,
802 },
803 {
804 name: "standard GET, CAA ident/website meta",
805 caaIdent: "Radiant Lock",
806 website: "zombo.com",
807 request: getReq,
808 expectedJSON: `{
809 "AAAAAAAAAAA": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
810 "keyChange": "http://localhost:4300/acme/key-change",
811 "meta": {
812 "caaIdentities": [
813 "Radiant Lock"
814 ],
815 "termsOfService": "http://example.invalid/terms",
816 "website": "zombo.com"
817 },
818 "newAccount": "http://localhost:4300/acme/new-acct",
819 "newNonce": "http://localhost:4300/acme/new-nonce",
820 "newOrder": "http://localhost:4300/acme/new-order",
821 "revokeCert": "http://localhost:4300/acme/revoke-cert"
822 }`,
823 },
824 {
825 name: "POST-as-GET, CAA ident/website meta",
826 caaIdent: "Radiant Lock",
827 website: "zombo.com",
828 request: postAsGetReq,
829 expectedJSON: `{
830 "AAAAAAAAAAA": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
831 "keyChange": "http://localhost/acme/key-change",
832 "meta": {
833 "caaIdentities": [
834 "Radiant Lock"
835 ],
836 "termsOfService": "http://example.invalid/terms",
837 "website": "zombo.com"
838 },
839 "newAccount": "http://localhost/acme/new-acct",
840 "newNonce": "http://localhost/acme/new-nonce",
841 "newOrder": "http://localhost/acme/new-order",
842 "revokeCert": "http://localhost/acme/revoke-cert"
843 }`,
844 },
845 }
846
847 for _, tc := range testCases {
848 t.Run(tc.name, func(t *testing.T) {
849
850 wfe.DirectoryCAAIdentity = tc.caaIdent
851 wfe.DirectoryWebsite = tc.website
852 responseWriter := httptest.NewRecorder()
853
854 mux.ServeHTTP(responseWriter, tc.request)
855
856 test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/json")
857
858 test.AssertEquals(t, responseWriter.Code, http.StatusOK)
859
860 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tc.expectedJSON)
861
862 test.AssertEquals(t,
863 randomDirectoryKeyPresent(t, responseWriter.Body.Bytes()),
864 true)
865 })
866 }
867 }
868
869 func TestRelativeDirectory(t *testing.T) {
870 wfe, _, _ := setupWFE(t)
871 mux := wfe.Handler(metrics.NoopRegisterer)
872 core.RandReader = fakeRand{}
873 defer func() { core.RandReader = rand.Reader }()
874
875 expectedDirectory := func(hostname string) string {
876 expected := new(bytes.Buffer)
877
878 fmt.Fprintf(expected, "{")
879 fmt.Fprintf(expected, `"keyChange":"%s/acme/key-change",`, hostname)
880 fmt.Fprintf(expected, `"newNonce":"%s/acme/new-nonce",`, hostname)
881 fmt.Fprintf(expected, `"newAccount":"%s/acme/new-acct",`, hostname)
882 fmt.Fprintf(expected, `"newOrder":"%s/acme/new-order",`, hostname)
883 fmt.Fprintf(expected, `"revokeCert":"%s/acme/revoke-cert",`, hostname)
884 fmt.Fprintf(expected, `"AAAAAAAAAAA":"https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",`)
885 fmt.Fprintf(expected, `"meta":{"termsOfService":"http://example.invalid/terms"}`)
886 fmt.Fprintf(expected, "}")
887 return expected.String()
888 }
889
890 dirTests := []struct {
891 host string
892 protoHeader string
893 result string
894 }{
895
896 {"", "", expectedDirectory("http://localhost")},
897
898 {"localhost:4300", "", expectedDirectory("http://localhost:4300")},
899
900 {"127.0.0.1:4300", "", expectedDirectory("http://127.0.0.1:4300")},
901
902 {"localhost:4300", "http", expectedDirectory("http://localhost:4300")},
903
904 {"localhost:4300", "https", expectedDirectory("https://localhost:4300")},
905 }
906
907 for _, tt := range dirTests {
908 var headers map[string][]string
909 responseWriter := httptest.NewRecorder()
910
911 if tt.protoHeader != "" {
912 headers = map[string][]string{
913 "X-Forwarded-Proto": {tt.protoHeader},
914 }
915 }
916
917 mux.ServeHTTP(responseWriter, &http.Request{
918 Method: "GET",
919 Host: tt.host,
920 URL: mustParseURL(directoryPath),
921 Header: headers,
922 })
923 test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/json")
924 test.AssertEquals(t, responseWriter.Code, http.StatusOK)
925 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tt.result)
926 }
927 }
928
929
930 func TestNonceEndpoint(t *testing.T) {
931 wfe, _, signer := setupWFE(t)
932 mux := wfe.Handler(metrics.NoopRegisterer)
933
934 getReq := &http.Request{
935 Method: http.MethodGet,
936 URL: mustParseURL(newNoncePath),
937 }
938 headReq := &http.Request{
939 Method: http.MethodHead,
940 URL: mustParseURL(newNoncePath),
941 }
942
943 _, _, jwsBody := signer.byKeyID(1, nil, fmt.Sprintf("http://localhost%s", newNoncePath), "")
944 postAsGetReq := makePostRequestWithPath(newNoncePath, jwsBody)
945
946 testCases := []struct {
947 name string
948 request *http.Request
949 expectedStatus int
950 }{
951 {
952 name: "GET new-nonce request",
953 request: getReq,
954 expectedStatus: http.StatusNoContent,
955 },
956 {
957 name: "HEAD new-nonce request",
958 request: headReq,
959 expectedStatus: http.StatusOK,
960 },
961 {
962 name: "POST-as-GET new-nonce request",
963 request: postAsGetReq,
964 expectedStatus: http.StatusOK,
965 },
966 }
967
968 for _, tc := range testCases {
969 t.Run(tc.name, func(t *testing.T) {
970 responseWriter := httptest.NewRecorder()
971 mux.ServeHTTP(responseWriter, tc.request)
972
973 test.AssertEquals(t, responseWriter.Code, tc.expectedStatus)
974
975 nonce := responseWriter.Header().Get("Replay-Nonce")
976 redeemResp, err := wfe.rnc.Redeem(context.Background(), &noncepb.NonceMessage{Nonce: nonce})
977 test.AssertNotError(t, err, "redeeming nonce")
978 test.AssertEquals(t, redeemResp.Valid, true)
979
980
981
982 cacheControl := responseWriter.Header().Get("Cache-Control")
983 test.AssertEquals(t, cacheControl, "no-store")
984 })
985 }
986 }
987
988 func TestHTTPMethods(t *testing.T) {
989 wfe, _, _ := setupWFE(t)
990 mux := wfe.Handler(metrics.NoopRegisterer)
991
992
993
994 getOnly := map[string]bool{http.MethodGet: true, http.MethodHead: true}
995 postOnly := map[string]bool{http.MethodPost: true}
996 getOrPost := map[string]bool{http.MethodGet: true, http.MethodHead: true, http.MethodPost: true}
997
998 testCases := []struct {
999 Name string
1000 Path string
1001 Allowed map[string]bool
1002 }{
1003 {
1004 Name: "Index path should be GET only",
1005 Path: "/",
1006 Allowed: getOnly,
1007 },
1008 {
1009 Name: "Directory path should be GET or POST only",
1010 Path: directoryPath,
1011 Allowed: getOrPost,
1012 },
1013 {
1014 Name: "NewAcct path should be POST only",
1015 Path: newAcctPath,
1016 Allowed: postOnly,
1017 },
1018 {
1019 Name: "Acct path should be POST only",
1020 Path: acctPath,
1021 Allowed: postOnly,
1022 },
1023
1024 {
1025 Name: "Authz path should be GET or POST only",
1026 Path: authzPath,
1027 Allowed: getOrPost,
1028 },
1029
1030 {
1031 Name: "Challenge path should be GET or POST only",
1032 Path: challengePath,
1033 Allowed: getOrPost,
1034 },
1035
1036 {
1037 Name: "Certificate path should be GET or POST only",
1038 Path: certPath,
1039 Allowed: getOrPost,
1040 },
1041 {
1042 Name: "RevokeCert path should be POST only",
1043 Path: revokeCertPath,
1044 Allowed: postOnly,
1045 },
1046 {
1047 Name: "Build ID path should be GET only",
1048 Path: buildIDPath,
1049 Allowed: getOnly,
1050 },
1051 {
1052 Name: "Rollover path should be POST only",
1053 Path: rolloverPath,
1054 Allowed: postOnly,
1055 },
1056 {
1057 Name: "New order path should be POST only",
1058 Path: newOrderPath,
1059 Allowed: postOnly,
1060 },
1061
1062 {
1063 Name: "Order path should be GET or POST only",
1064 Path: orderPath,
1065 Allowed: getOrPost,
1066 },
1067 {
1068 Name: "Nonce path should be GET or POST only",
1069 Path: newNoncePath,
1070 Allowed: getOrPost,
1071 },
1072 }
1073
1074
1075
1076 allMethods := []string{
1077 http.MethodGet,
1078 http.MethodHead,
1079 http.MethodPost,
1080 http.MethodPut,
1081 http.MethodPatch,
1082 http.MethodDelete,
1083 http.MethodConnect,
1084 http.MethodTrace,
1085 }
1086
1087 responseWriter := httptest.NewRecorder()
1088
1089 for _, tc := range testCases {
1090 t.Run(tc.Name, func(t *testing.T) {
1091
1092
1093 for _, method := range allMethods {
1094 responseWriter.Body.Reset()
1095 mux.ServeHTTP(responseWriter, &http.Request{
1096 Method: method,
1097 URL: mustParseURL(tc.Path),
1098 })
1099
1100
1101 if _, ok := tc.Allowed[method]; !ok {
1102 var prob probs.ProblemDetails
1103
1104 body := responseWriter.Body.String()
1105 err := json.Unmarshal([]byte(body), &prob)
1106 test.AssertNotError(t, err, fmt.Sprintf("Error unmarshalling resp body: %q", body))
1107
1108
1109
1110
1111 test.AssertEquals(t, responseWriter.Code, http.StatusOK)
1112 test.AssertEquals(t, prob.HTTPStatus, http.StatusMethodNotAllowed)
1113 test.AssertEquals(t, prob.Detail, "Method not allowed")
1114 } else {
1115
1116
1117 test.AssertNotEquals(t, responseWriter.Code, http.StatusMethodNotAllowed)
1118 }
1119 }
1120 })
1121 }
1122 }
1123
1124 func TestGetChallenge(t *testing.T) {
1125 wfe, _, _ := setupWFE(t)
1126
1127 challengeURL := "http://localhost/acme/chall-v3/1/-ZfxEw"
1128
1129 for _, method := range []string{"GET", "HEAD"} {
1130 resp := httptest.NewRecorder()
1131
1132 req, err := http.NewRequest(method, challengeURL, nil)
1133 req.URL.Path = "1/-ZfxEw"
1134 test.AssertNotError(t, err, "Could not make NewRequest")
1135
1136 wfe.Challenge(ctx, newRequestEvent(), resp, req)
1137 test.AssertEquals(t,
1138 resp.Code,
1139 http.StatusOK)
1140 test.AssertEquals(t,
1141 resp.Header().Get("Location"),
1142 challengeURL)
1143 test.AssertEquals(t,
1144 resp.Header().Get("Content-Type"),
1145 "application/json")
1146 test.AssertEquals(t,
1147 resp.Header().Get("Link"),
1148 `<http://localhost/acme/authz-v3/1>;rel="up"`)
1149
1150
1151
1152 if method == "GET" {
1153 test.AssertUnmarshaledEquals(
1154 t, resp.Body.String(),
1155 `{"status": "pending", "type":"dns","token":"token","url":"http://localhost/acme/chall-v3/1/-ZfxEw"}`)
1156 }
1157 }
1158 }
1159
1160 func TestChallenge(t *testing.T) {
1161 wfe, _, signer := setupWFE(t)
1162
1163 post := func(path string) *http.Request {
1164 signedURL := fmt.Sprintf("http://localhost/%s", path)
1165 _, _, jwsBody := signer.byKeyID(1, nil, signedURL, `{}`)
1166 return makePostRequestWithPath(path, jwsBody)
1167 }
1168 postAsGet := func(keyID int64, path, body string) *http.Request {
1169 _, _, jwsBody := signer.byKeyID(keyID, nil, fmt.Sprintf("http://localhost/%s", path), body)
1170 return makePostRequestWithPath(path, jwsBody)
1171 }
1172
1173 testCases := []struct {
1174 Name string
1175 Request *http.Request
1176 ExpectedStatus int
1177 ExpectedHeaders map[string]string
1178 ExpectedBody string
1179 }{
1180 {
1181 Name: "Valid challenge",
1182 Request: post("1/-ZfxEw"),
1183 ExpectedStatus: http.StatusOK,
1184 ExpectedHeaders: map[string]string{
1185 "Location": "http://localhost/acme/chall-v3/1/-ZfxEw",
1186 "Link": `<http://localhost/acme/authz-v3/1>;rel="up"`,
1187 },
1188 ExpectedBody: `{"status": "pending", "type":"dns","token":"token","url":"http://localhost/acme/chall-v3/1/-ZfxEw"}`,
1189 },
1190 {
1191 Name: "Expired challenge",
1192 Request: post("3/-ZfxEw"),
1193 ExpectedStatus: http.StatusNotFound,
1194 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Expired authorization","status":404}`,
1195 },
1196 {
1197 Name: "Missing challenge",
1198 Request: post("1/"),
1199 ExpectedStatus: http.StatusNotFound,
1200 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"No such challenge","status":404}`,
1201 },
1202 {
1203 Name: "Unspecified database error",
1204 Request: post("4/-ZfxEw"),
1205 ExpectedStatus: http.StatusInternalServerError,
1206 ExpectedBody: `{"type":"` + probs.ErrorNS + `serverInternal","detail":"Problem getting authorization","status":500}`,
1207 },
1208 {
1209 Name: "POST-as-GET, wrong owner",
1210 Request: postAsGet(1, "5/-ZfxEw", ""),
1211 ExpectedStatus: http.StatusForbidden,
1212 ExpectedBody: `{"type":"` + probs.ErrorNS + `unauthorized","detail":"User account ID doesn't match account ID in authorization","status":403}`,
1213 },
1214 {
1215 Name: "Valid POST-as-GET",
1216 Request: postAsGet(1, "1/-ZfxEw", ""),
1217 ExpectedStatus: http.StatusOK,
1218 ExpectedBody: `{"status": "pending", "type":"dns", "token":"token", "url": "http://localhost/acme/chall-v3/1/-ZfxEw"}`,
1219 },
1220 }
1221
1222 for _, tc := range testCases {
1223 t.Run(tc.Name, func(t *testing.T) {
1224 responseWriter := httptest.NewRecorder()
1225 wfe.Challenge(ctx, newRequestEvent(), responseWriter, tc.Request)
1226
1227 headers := responseWriter.Header()
1228 body := responseWriter.Body.String()
1229 test.AssertEquals(t, responseWriter.Code, tc.ExpectedStatus)
1230 for h, v := range tc.ExpectedHeaders {
1231 test.AssertEquals(t, headers.Get(h), v)
1232 }
1233 test.AssertUnmarshaledEquals(t, body, tc.ExpectedBody)
1234 })
1235 }
1236 }
1237
1238
1239
1240 type MockRAPerformValidationError struct {
1241 MockRegistrationAuthority
1242 }
1243
1244 func (ra *MockRAPerformValidationError) PerformValidation(context.Context, *rapb.PerformValidationRequest, ...grpc.CallOption) (*corepb.Authorization, error) {
1245 return nil, errors.New("broken on purpose")
1246 }
1247
1248
1249
1250
1251 func TestUpdateChallengeFinalizedAuthz(t *testing.T) {
1252 wfe, _, signer := setupWFE(t)
1253 wfe.ra = &MockRAPerformValidationError{}
1254 responseWriter := httptest.NewRecorder()
1255
1256 signedURL := "http://localhost/1/-ZfxEw"
1257 _, _, jwsBody := signer.byKeyID(1, nil, signedURL, `{}`)
1258 request := makePostRequestWithPath("1/-ZfxEw", jwsBody)
1259 wfe.Challenge(ctx, newRequestEvent(), responseWriter, request)
1260
1261 body := responseWriter.Body.String()
1262 test.AssertUnmarshaledEquals(t, body, `{
1263 "status": "pending",
1264 "type": "dns",
1265 "token":"token",
1266 "url": "http://localhost/acme/chall-v3/1/-ZfxEw"
1267 }`)
1268 }
1269
1270
1271
1272
1273 func TestUpdateChallengeRAError(t *testing.T) {
1274 wfe, _, signer := setupWFE(t)
1275
1276 wfe.ra = &MockRAPerformValidationError{}
1277
1278
1279 signedURL := "http://localhost/2/-ZfxEw"
1280 _, _, jwsBody := signer.byKeyID(1, nil, signedURL, `{}`)
1281 responseWriter := httptest.NewRecorder()
1282 request := makePostRequestWithPath("2/-ZfxEw", jwsBody)
1283
1284 wfe.Challenge(ctx, newRequestEvent(), responseWriter, request)
1285
1286
1287 body := responseWriter.Body.String()
1288 test.AssertUnmarshaledEquals(t, body, `{
1289 "type": "urn:ietf:params:acme:error:serverInternal",
1290 "detail": "Unable to update challenge",
1291 "status": 500
1292 }`)
1293 }
1294
1295 func TestBadNonce(t *testing.T) {
1296 wfe, _, _ := setupWFE(t)
1297
1298 key := loadKey(t, []byte(test2KeyPrivatePEM))
1299 rsaKey, ok := key.(*rsa.PrivateKey)
1300 test.Assert(t, ok, "Couldn't load RSA key")
1301
1302
1303 noNonceSigner, err := jose.NewSigner(jose.SigningKey{
1304 Key: rsaKey,
1305 Algorithm: jose.RS256,
1306 }, &jose.SignerOptions{
1307 EmbedJWK: true,
1308 })
1309 test.AssertNotError(t, err, "Failed to make signer")
1310
1311 responseWriter := httptest.NewRecorder()
1312 result, err := noNonceSigner.Sign([]byte(`{"contact":["mailto:person@mail.com"]}`))
1313 test.AssertNotError(t, err, "Failed to sign body")
1314 wfe.NewAccount(ctx, newRequestEvent(), responseWriter,
1315 makePostRequestWithPath("nonce", result.FullSerialize()))
1316 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), `{"type":"`+probs.ErrorNS+`badNonce","detail":"JWS has no anti-replay nonce","status":400}`)
1317 }
1318
1319 func TestNewECDSAAccount(t *testing.T) {
1320 wfe, _, signer := setupWFE(t)
1321
1322
1323 key := loadKey(t, []byte(testE2KeyPrivatePEM))
1324 _, ok := key.(*ecdsa.PrivateKey)
1325 test.Assert(t, ok, "Couldn't load ECDSA key")
1326
1327 payload := `{"contact":["mailto:person@mail.com"],"termsOfServiceAgreed":true}`
1328 path := newAcctPath
1329 signedURL := fmt.Sprintf("http://localhost%s", path)
1330 _, _, body := signer.embeddedJWK(key, signedURL, payload)
1331 request := makePostRequestWithPath(path, body)
1332
1333 responseWriter := httptest.NewRecorder()
1334 wfe.NewAccount(ctx, newRequestEvent(), responseWriter, request)
1335
1336 var acct core.Registration
1337 responseBody := responseWriter.Body.String()
1338 err := json.Unmarshal([]byte(responseBody), &acct)
1339 test.AssertNotError(t, err, "Couldn't unmarshal returned account object")
1340 test.Assert(t, len(*acct.Contact) >= 1, "No contact field in account")
1341 test.AssertEquals(t, (*acct.Contact)[0], "mailto:person@mail.com")
1342 test.AssertEquals(t, acct.Agreement, "")
1343 test.AssertEquals(t, acct.InitialIP.String(), "1.1.1.1")
1344
1345 test.AssertEquals(t, responseWriter.Header().Get("Location"), "http://localhost/acme/acct/1")
1346
1347 key = loadKey(t, []byte(testE1KeyPrivatePEM))
1348 _, ok = key.(*ecdsa.PrivateKey)
1349 test.Assert(t, ok, "Couldn't load ECDSA key")
1350
1351 _, _, body = signer.embeddedJWK(key, signedURL, payload)
1352 request = makePostRequestWithPath(path, body)
1353
1354
1355 responseWriter = httptest.NewRecorder()
1356
1357 wfe.NewAccount(ctx, newRequestEvent(), responseWriter, request)
1358 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(),
1359 `{
1360 "key": {
1361 "kty": "EC",
1362 "crv": "P-256",
1363 "x": "FwvSZpu06i3frSk_mz9HcD9nETn4wf3mQ-zDtG21Gao",
1364 "y": "S8rR-0dWa8nAcw1fbunF_ajS3PQZ-QwLps-2adgLgPk"
1365 },
1366 "initialIp": "",
1367 "status": ""
1368 }`)
1369 test.AssertEquals(t, responseWriter.Header().Get("Location"), "http://localhost/acme/acct/3")
1370 test.AssertEquals(t, responseWriter.Code, 200)
1371
1372
1373 key = loadKey(t, []byte(test3KeyPrivatePEM))
1374 _, ok = key.(*rsa.PrivateKey)
1375 test.Assert(t, ok, "Couldn't load test3 key")
1376
1377
1378 responseWriter = httptest.NewRecorder()
1379
1380
1381 payload = `{}`
1382 path = "1"
1383 signedURL = "http://localhost/1"
1384 _, _, body = signer.embeddedJWK(key, signedURL, payload)
1385 request = makePostRequestWithPath(path, body)
1386 wfe.NewAccount(ctx, newRequestEvent(), responseWriter, request)
1387 test.AssertEquals(t, responseWriter.Code, http.StatusForbidden)
1388 }
1389
1390
1391
1392
1393
1394 func TestEmptyAccount(t *testing.T) {
1395 wfe, _, signer := setupWFE(t)
1396 responseWriter := httptest.NewRecorder()
1397
1398
1399
1400
1401 key := loadKey(t, []byte(test1KeyPrivatePEM))
1402 _, ok := key.(*rsa.PrivateKey)
1403 test.Assert(t, ok, "Couldn't load RSA key")
1404
1405 payload := `{}`
1406 path := "1"
1407 signedURL := "http://localhost/1"
1408 _, _, body := signer.byKeyID(1, key, signedURL, payload)
1409 request := makePostRequestWithPath(path, body)
1410
1411
1412 wfe.Account(
1413 ctx,
1414 newRequestEvent(),
1415 responseWriter,
1416 request)
1417
1418 responseBody := responseWriter.Body.String()
1419
1420 test.AssertNotContains(t, responseBody, probs.ErrorNS)
1421
1422
1423 var acct core.Registration
1424 err := json.Unmarshal([]byte(responseBody), &acct)
1425 test.AssertNotError(t, err, "Couldn't unmarshal returned account object")
1426 test.Assert(t, len(*acct.Contact) >= 1, "No contact field in account")
1427 test.AssertEquals(t, (*acct.Contact)[0], "mailto:person@mail.com")
1428 test.AssertEquals(t, acct.Agreement, "")
1429 responseWriter.Body.Reset()
1430 }
1431
1432 func TestNewAccount(t *testing.T) {
1433 wfe, _, signer := setupWFE(t)
1434 mux := wfe.Handler(metrics.NoopRegisterer)
1435 key := loadKey(t, []byte(test2KeyPrivatePEM))
1436 _, ok := key.(*rsa.PrivateKey)
1437 test.Assert(t, ok, "Couldn't load test2 key")
1438
1439 path := newAcctPath
1440 signedURL := fmt.Sprintf("http://localhost%s", path)
1441
1442 wrongAgreementAcct := `{"contact":["mailto:person@mail.com"],"termsOfServiceAgreed":false}`
1443
1444 _, _, wrongAgreementBody := signer.embeddedJWK(key, signedURL, wrongAgreementAcct)
1445
1446
1447 _, _, fooBody := signer.embeddedJWK(key, signedURL, `foo`)
1448
1449 type newAcctErrorTest struct {
1450 r *http.Request
1451 respBody string
1452 }
1453
1454 acctErrTests := []newAcctErrorTest{
1455
1456 {
1457 &http.Request{
1458 Method: "POST",
1459 URL: mustParseURL(newAcctPath),
1460 Header: map[string][]string{
1461 "Content-Length": {"0"},
1462 "Content-Type": {expectedJWSContentType},
1463 },
1464 },
1465 `{"type":"` + probs.ErrorNS + `malformed","detail":"No body on POST","status":400}`,
1466 },
1467
1468
1469 {
1470 makePostRequestWithPath(newAcctPath, "hi"),
1471 `{"type":"` + probs.ErrorNS + `malformed","detail":"Parse error reading JWS","status":400}`,
1472 },
1473
1474
1475 {
1476 makePostRequestWithPath(newAcctPath, fooBody),
1477 `{"type":"` + probs.ErrorNS + `malformed","detail":"Request payload did not parse as JSON","status":400}`,
1478 },
1479
1480
1481
1482 {
1483 makePostRequestWithPath(newAcctPath,
1484 `{"payload":"Zm9x","protected":"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoicW5BUkxyVDdYejRnUmNLeUxkeWRtQ3ItZXk5T3VQSW1YNFg0MHRoazNvbjI2RmtNem5SM2ZSanM2NmVMSzdtbVBjQlo2dU9Kc2VVUlU2d0FhWk5tZW1vWXgxZE12cXZXV0l5aVFsZUhTRDdROHZCcmhSNnVJb080akF6SlpSLUNoelp1U0R0N2lITi0zeFVWc3B1NVhHd1hVX01WSlpzaFR3cDRUYUZ4NWVsSElUX09iblR2VE9VM1hoaXNoMDdBYmdaS21Xc1ZiWGg1cy1DcklpY1U0T2V4SlBndW5XWl9ZSkp1ZU9LbVR2bkxsVFY0TXpLUjJvWmxCS1oyN1MwLVNmZFZfUUR4X3lkbGU1b01BeUtWdGxBVjM1Y3lQTUlzWU53Z1VHQkNkWV8yVXppNWVYMGxUYzdNUFJ3ejZxUjFraXAtaTU5VmNHY1VRZ3FIVjZGeXF3IiwiZSI6IkFRQUIifSwia2lkIjoiIiwibm9uY2UiOiJyNHpuenZQQUVwMDlDN1JwZUtYVHhvNkx3SGwxZVBVdmpGeXhOSE1hQnVvIiwidXJsIjoiaHR0cDovL2xvY2FsaG9zdC9hY21lL25ldy1yZWcifQ","signature":"jcTdxSygm_cvD7KbXqsxgnoPApCTSkV4jolToSOd2ciRkg5W7Yl0ZKEEKwOc-dYIbQiwGiDzisyPCicwWsOUA1WSqHylKvZ3nxSMc6KtwJCW2DaOqcf0EEjy5VjiZJUrOt2c-r6b07tbn8sfOJKwlF2lsOeGi4s-rtvvkeQpAU-AWauzl9G4bv2nDUeCviAZjHx_PoUC-f9GmZhYrbDzAvXZ859ktM6RmMeD0OqPN7bhAeju2j9Gl0lnryZMtq2m0J2m1ucenQBL1g4ZkP1JiJvzd2cAz5G7Ftl2YeJJyWhqNd3qq0GVOt1P11s8PTGNaSoM0iR9QfUxT9A6jxARtg"}`),
1485 `{"type":"` + probs.ErrorNS + `malformed","detail":"JWS verification error","status":400}`,
1486 },
1487 {
1488 makePostRequestWithPath(newAcctPath, wrongAgreementBody),
1489 `{"type":"` + probs.ErrorNS + `malformed","detail":"must agree to terms of service","status":400}`,
1490 },
1491 }
1492 for _, rt := range acctErrTests {
1493 responseWriter := httptest.NewRecorder()
1494 mux.ServeHTTP(responseWriter, rt.r)
1495 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), rt.respBody)
1496 }
1497
1498 responseWriter := httptest.NewRecorder()
1499
1500 payload := `{"contact":["mailto:person@mail.com"],"termsOfServiceAgreed":true}`
1501 _, _, body := signer.embeddedJWK(key, signedURL, payload)
1502 request := makePostRequestWithPath(path, body)
1503
1504 wfe.NewAccount(ctx, newRequestEvent(), responseWriter, request)
1505
1506 var acct core.Registration
1507 responseBody := responseWriter.Body.String()
1508 err := json.Unmarshal([]byte(responseBody), &acct)
1509 test.AssertNotError(t, err, "Couldn't unmarshal returned account object")
1510 test.Assert(t, len(*acct.Contact) >= 1, "No contact field in account")
1511 test.AssertEquals(t, (*acct.Contact)[0], "mailto:person@mail.com")
1512 test.AssertEquals(t, acct.InitialIP.String(), "1.1.1.1")
1513
1514 test.AssertEquals(t, acct.Agreement, "")
1515
1516 test.AssertEquals(
1517 t, responseWriter.Header().Get("Location"),
1518 "http://localhost/acme/acct/1")
1519
1520
1521 key = loadKey(t, []byte(test1KeyPrivatePEM))
1522 _, ok = key.(*rsa.PrivateKey)
1523 test.Assert(t, ok, "Couldn't load test1 key")
1524
1525
1526 responseWriter = httptest.NewRecorder()
1527
1528 _, _, body = signer.embeddedJWK(key, signedURL, payload)
1529 request = makePostRequestWithPath(path, body)
1530
1531 wfe.NewAccount(ctx, newRequestEvent(), responseWriter, request)
1532
1533 test.AssertEquals(
1534 t, responseWriter.Header().Get("Location"),
1535 "http://localhost/acme/acct/1")
1536 test.AssertEquals(t, responseWriter.Code, 200)
1537 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(),
1538 `{
1539 "key": {
1540 "kty": "RSA",
1541 "n": "yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ",
1542 "e": "AQAB"
1543 },
1544 "contact": [
1545 "mailto:person@mail.com"
1546 ],
1547 "initialIp": "",
1548 "status": "valid"
1549 }`)
1550 }
1551
1552 func TestNewAccountWhenAccountHasBeenDeactivated(t *testing.T) {
1553 wfe, _, signer := setupWFE(t)
1554 signedURL := fmt.Sprintf("http://localhost%s", newAcctPath)
1555
1556 k := loadKey(t, []byte(test3KeyPrivatePEM))
1557 _, ok := k.(*rsa.PrivateKey)
1558 test.Assert(t, ok, "Couldn't load test3 key")
1559
1560 payload := `{"contact":["mailto:person@mail.com"],"termsOfServiceAgreed":true}`
1561 _, _, body := signer.embeddedJWK(k, signedURL, payload)
1562 request := makePostRequestWithPath(newAcctPath, body)
1563
1564 responseWriter := httptest.NewRecorder()
1565 wfe.NewAccount(ctx, newRequestEvent(), responseWriter, request)
1566
1567 test.AssertEquals(t, responseWriter.Code, http.StatusForbidden)
1568 }
1569
1570 func TestNewAccountNoID(t *testing.T) {
1571 wfe, _, signer := setupWFE(t)
1572 key := loadKey(t, []byte(test2KeyPrivatePEM))
1573 _, ok := key.(*rsa.PrivateKey)
1574 test.Assert(t, ok, "Couldn't load test2 key")
1575 path := newAcctPath
1576 signedURL := fmt.Sprintf("http://localhost%s", path)
1577
1578 payload := `{"contact":["mailto:person@mail.com"],"termsOfServiceAgreed":true}`
1579 _, _, body := signer.embeddedJWK(key, signedURL, payload)
1580 request := makePostRequestWithPath(path, body)
1581
1582 responseWriter := httptest.NewRecorder()
1583 wfe.NewAccount(ctx, newRequestEvent(), responseWriter, request)
1584
1585 responseBody := responseWriter.Body.String()
1586 test.AssertUnmarshaledEquals(t, responseBody, `{
1587 "key": {
1588 "kty": "RSA",
1589 "n": "qnARLrT7Xz4gRcKyLdydmCr-ey9OuPImX4X40thk3on26FkMznR3fRjs66eLK7mmPcBZ6uOJseURU6wAaZNmemoYx1dMvqvWWIyiQleHSD7Q8vBrhR6uIoO4jAzJZR-ChzZuSDt7iHN-3xUVspu5XGwXU_MVJZshTwp4TaFx5elHIT_ObnTvTOU3Xhish07AbgZKmWsVbXh5s-CrIicU4OexJPgunWZ_YJJueOKmTvnLlTV4MzKR2oZlBKZ27S0-SfdV_QDx_ydle5oMAyKVtlAV35cyPMIsYNwgUGBCdY_2Uzi5eX0lTc7MPRwz6qR1kip-i59VcGcUQgqHV6Fyqw",
1590 "e": "AQAB"
1591 },
1592 "contact": [
1593 "mailto:person@mail.com"
1594 ],
1595 "initialIp": "1.1.1.1",
1596 "createdAt": "2021-01-01T00:00:00Z",
1597 "status": ""
1598 }`)
1599 }
1600
1601 func TestGetAuthorization(t *testing.T) {
1602 wfe, _, signer := setupWFE(t)
1603
1604
1605 authzURL := "3"
1606 responseWriter := httptest.NewRecorder()
1607 wfe.Authorization(ctx, newRequestEvent(), responseWriter, &http.Request{
1608 Method: "GET",
1609 URL: mustParseURL(authzURL),
1610 })
1611 test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
1612 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(),
1613 `{"type":"`+probs.ErrorNS+`malformed","detail":"Expired authorization","status":404}`)
1614 responseWriter.Body.Reset()
1615
1616
1617 wfe.Authorization(ctx, newRequestEvent(), responseWriter, &http.Request{
1618 URL: mustParseURL("1d"),
1619 Method: "GET",
1620 })
1621 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(),
1622 `{"type":"`+probs.ErrorNS+`malformed","detail":"Invalid authorization ID","status":400}`)
1623
1624 _, _, jwsBody := signer.byKeyID(1, nil, "http://localhost/1", "")
1625 postAsGet := makePostRequestWithPath("1", jwsBody)
1626
1627 responseWriter = httptest.NewRecorder()
1628
1629 wfe.Authorization(ctx, newRequestEvent(), responseWriter, postAsGet)
1630 test.AssertEquals(t, responseWriter.Code, http.StatusOK)
1631 body := responseWriter.Body.String()
1632 test.AssertUnmarshaledEquals(t, body, `
1633 {
1634 "identifier": {
1635 "type": "dns",
1636 "value": "not-an-example.com"
1637 },
1638 "status": "valid",
1639 "expires": "2070-01-01T00:00:00Z",
1640 "challenges": [
1641 {
1642 "status": "pending",
1643 "type": "dns",
1644 "token":"token",
1645 "url": "http://localhost/acme/chall-v3/1/-ZfxEw"
1646 }
1647 ]
1648 }`)
1649 }
1650
1651
1652
1653 func TestAuthorization500(t *testing.T) {
1654 wfe, _, _ := setupWFE(t)
1655
1656 responseWriter := httptest.NewRecorder()
1657 wfe.Authorization(ctx, newRequestEvent(), responseWriter, &http.Request{
1658 Method: "GET",
1659 URL: mustParseURL("4"),
1660 })
1661 expected := `{
1662 "type": "urn:ietf:params:acme:error:serverInternal",
1663 "detail": "Problem getting authorization",
1664 "status": 500
1665 }`
1666 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), expected)
1667 }
1668
1669
1670
1671
1672 type SAWithFailedChallenges struct {
1673 mocks.StorageAuthorityReadOnly
1674 Clk clock.FakeClock
1675 }
1676
1677 func (sa *SAWithFailedChallenges) GetAuthorization2(ctx context.Context, id *sapb.AuthorizationID2, _ ...grpc.CallOption) (*corepb.Authorization, error) {
1678 authz := core.Authorization{
1679 ID: "55",
1680 Status: core.StatusValid,
1681 RegistrationID: 1,
1682 Identifier: identifier.DNSIdentifier("not-an-example.com"),
1683 Challenges: []core.Challenge{
1684 {
1685 Status: core.StatusInvalid,
1686 Type: "dns",
1687 Token: "exampleToken",
1688 Error: &probs.ProblemDetails{
1689 Type: "things:are:whack",
1690 Detail: "whack attack",
1691 HTTPStatus: 555,
1692 },
1693 },
1694 },
1695 }
1696 exp := sa.Clk.Now().AddDate(100, 0, 0)
1697 authz.Expires = &exp
1698 return bgrpc.AuthzToPB(authz)
1699 }
1700
1701
1702
1703 func TestAuthorizationChallengeNamespace(t *testing.T) {
1704 wfe, clk, _ := setupWFE(t)
1705
1706 wfe.sa = &SAWithFailedChallenges{Clk: clk}
1707
1708 responseWriter := httptest.NewRecorder()
1709 wfe.Authorization(ctx, newRequestEvent(), responseWriter, &http.Request{
1710 Method: "GET",
1711 URL: mustParseURL("55"),
1712 })
1713
1714 var authz core.Authorization
1715 err := json.Unmarshal(responseWriter.Body.Bytes(), &authz)
1716 test.AssertNotError(t, err, "Couldn't unmarshal returned authorization object")
1717 test.AssertEquals(t, len(authz.Challenges), 1)
1718
1719 test.AssertEquals(t, string(authz.Challenges[0].Error.Type), probs.ErrorNS+"things:are:whack")
1720 responseWriter.Body.Reset()
1721 }
1722
1723 func contains(s []string, e string) bool {
1724 for _, a := range s {
1725 if a == e {
1726 return true
1727 }
1728 }
1729 return false
1730 }
1731
1732 func TestAccount(t *testing.T) {
1733 wfe, _, signer := setupWFE(t)
1734 mux := wfe.Handler(metrics.NoopRegisterer)
1735 responseWriter := httptest.NewRecorder()
1736
1737
1738 mux.ServeHTTP(responseWriter, &http.Request{
1739 Method: "GET",
1740 URL: mustParseURL(acctPath),
1741 })
1742 test.AssertUnmarshaledEquals(t,
1743 responseWriter.Body.String(),
1744 `{"type":"`+probs.ErrorNS+`malformed","detail":"Method not allowed","status":405}`)
1745 responseWriter.Body.Reset()
1746
1747
1748 wfe.Account(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath("2", "invalid"))
1749 test.AssertUnmarshaledEquals(t,
1750 responseWriter.Body.String(),
1751 `{"type":"`+probs.ErrorNS+`malformed","detail":"Parse error reading JWS","status":400}`)
1752 responseWriter.Body.Reset()
1753
1754 key := loadKey(t, []byte(test2KeyPrivatePEM))
1755 _, ok := key.(*rsa.PrivateKey)
1756 test.Assert(t, ok, "Couldn't load RSA key")
1757
1758 signedURL := fmt.Sprintf("http://localhost%s%d", acctPath, 102)
1759 path := fmt.Sprintf("%s%d", acctPath, 102)
1760 payload := `{}`
1761
1762 _, _, body := signer.byKeyID(102, nil, signedURL, payload)
1763 request := makePostRequestWithPath(path, body)
1764
1765
1766 wfe.Account(ctx, newRequestEvent(), responseWriter, request)
1767 test.AssertUnmarshaledEquals(t,
1768 responseWriter.Body.String(),
1769 `{"type":"`+probs.ErrorNS+`accountDoesNotExist","detail":"Account \"http://localhost/acme/acct/102\" not found","status":400}`)
1770 responseWriter.Body.Reset()
1771
1772 key = loadKey(t, []byte(test1KeyPrivatePEM))
1773 _, ok = key.(*rsa.PrivateKey)
1774 test.Assert(t, ok, "Couldn't load RSA key")
1775
1776
1777 payload = `{}`
1778 path = "1"
1779 signedURL = "http://localhost/1"
1780 _, _, body = signer.byKeyID(1, nil, signedURL, payload)
1781 request = makePostRequestWithPath(path, body)
1782
1783 wfe.Account(ctx, newRequestEvent(), responseWriter, request)
1784 test.AssertNotContains(t, responseWriter.Body.String(), probs.ErrorNS)
1785 links := responseWriter.Header()["Link"]
1786 test.AssertEquals(t, contains(links, "<"+agreementURL+">;rel=\"terms-of-service\""), true)
1787 responseWriter.Body.Reset()
1788
1789
1790 payload = `{}`
1791 signedURL = "http://localhost/a/bunch/of/garbage/1"
1792 _, _, body = signer.byKeyID(1, nil, signedURL, payload)
1793 request = makePostRequestWithPath("/a/bunch/of/garbage/1", body)
1794
1795 wfe.Account(ctx, newRequestEvent(), responseWriter, request)
1796 test.AssertContains(t, responseWriter.Body.String(), "400")
1797 test.AssertContains(t, responseWriter.Body.String(), probs.ErrorNS+"malformed")
1798 responseWriter.Body.Reset()
1799
1800
1801 responseWriter = httptest.NewRecorder()
1802 _, _, body = signer.byKeyID(1, nil, "http://localhost/1", "")
1803 request = makePostRequestWithPath("1", body)
1804 wfe.Account(ctx, newRequestEvent(), responseWriter, request)
1805
1806 test.AssertNotContains(t, responseWriter.Body.String(), probs.ErrorNS)
1807 test.AssertEquals(t, responseWriter.Code, http.StatusOK)
1808
1809 altKey := loadKey(t, []byte(test2KeyPrivatePEM))
1810 _, ok = altKey.(*rsa.PrivateKey)
1811 test.Assert(t, ok, "Couldn't load altKey RSA key")
1812
1813
1814 responseWriter = httptest.NewRecorder()
1815 _, _, body = signer.byKeyID(2, altKey, "http://localhost/1", "")
1816 request = makePostRequestWithPath("1", body)
1817 wfe.Account(ctx, newRequestEvent(), responseWriter, request)
1818
1819 test.AssertEquals(t, responseWriter.Code, http.StatusForbidden)
1820 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), `{
1821 "type": "urn:ietf:params:acme:error:unauthorized",
1822 "detail": "Request signing key did not match account key",
1823 "status": 403
1824 }`)
1825 }
1826
1827 type mockSAWithCert struct {
1828 sapb.StorageAuthorityReadOnlyClient
1829 cert *x509.Certificate
1830 status core.OCSPStatus
1831 }
1832
1833 func newMockSAWithCert(t *testing.T, sa sapb.StorageAuthorityReadOnlyClient) *mockSAWithCert {
1834 cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
1835 test.AssertNotError(t, err, "Failed to load test cert")
1836 return &mockSAWithCert{sa, cert, core.OCSPStatusGood}
1837 }
1838
1839
1840
1841 func (sa *mockSAWithCert) GetCertificate(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
1842 if req.Serial != core.SerialToString(sa.cert.SerialNumber) {
1843 return nil, berrors.NotFoundError("Certificate with serial %q not found", req.Serial)
1844 }
1845
1846 return &corepb.Certificate{
1847 RegistrationID: 1,
1848 Serial: core.SerialToString(sa.cert.SerialNumber),
1849 IssuedNS: sa.cert.NotBefore.UnixNano(),
1850 Issued: timestamppb.New(sa.cert.NotBefore),
1851 ExpiresNS: sa.cert.NotAfter.UnixNano(),
1852 Expires: timestamppb.New(sa.cert.NotAfter),
1853 Der: sa.cert.Raw,
1854 }, nil
1855 }
1856
1857
1858
1859 func (sa *mockSAWithCert) GetCertificateStatus(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.CertificateStatus, error) {
1860 if req.Serial != core.SerialToString(sa.cert.SerialNumber) {
1861 return nil, berrors.NotFoundError("Status for certificate with serial %q not found", req.Serial)
1862 }
1863
1864 return &corepb.CertificateStatus{
1865 Serial: core.SerialToString(sa.cert.SerialNumber),
1866 Status: string(sa.status),
1867 }, nil
1868 }
1869
1870 type mockSAWithIncident struct {
1871 sapb.StorageAuthorityReadOnlyClient
1872 incidents map[string]*sapb.Incidents
1873 }
1874
1875
1876
1877 func newMockSAWithIncident(sa sapb.StorageAuthorityReadOnlyClient, serial []string) *mockSAWithIncident {
1878 incidents := make(map[string]*sapb.Incidents)
1879 for _, s := range serial {
1880 incidents[s] = &sapb.Incidents{
1881 Incidents: []*sapb.Incident{
1882 {
1883 Id: 0,
1884 SerialTable: "incident_foo",
1885 Url: agreementURL,
1886 RenewByNS: 0,
1887 RenewBy: timestamppb.New(time.Time{}),
1888 Enabled: true,
1889 },
1890 },
1891 }
1892 }
1893 return &mockSAWithIncident{sa, incidents}
1894 }
1895
1896 func (sa *mockSAWithIncident) IncidentsForSerial(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*sapb.Incidents, error) {
1897 incidents, ok := sa.incidents[req.Serial]
1898 if ok {
1899 return incidents, nil
1900 }
1901 return &sapb.Incidents{}, nil
1902 }
1903
1904 func TestGetCertificate(t *testing.T) {
1905 wfe, _, signer := setupWFE(t)
1906 wfe.sa = newMockSAWithCert(t, wfe.sa)
1907 mux := wfe.Handler(metrics.NoopRegisterer)
1908
1909 makeGet := func(path string) *http.Request {
1910 return &http.Request{URL: &url.URL{Path: path}, Method: "GET"}
1911 }
1912
1913 makePost := func(keyID int64, key interface{}, path, body string) *http.Request {
1914 _, _, jwsBody := signer.byKeyID(keyID, key, fmt.Sprintf("http://localhost%s", path), body)
1915 return makePostRequestWithPath(path, jwsBody)
1916 }
1917
1918 altKey := loadKey(t, []byte(test2KeyPrivatePEM))
1919 _, ok := altKey.(*rsa.PrivateKey)
1920 test.Assert(t, ok, "Couldn't load RSA key")
1921
1922 certPemBytes, _ := os.ReadFile("../test/hierarchy/ee-r3.cert.pem")
1923 cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
1924 test.AssertNotError(t, err, "failed to load test certificate")
1925
1926 chainPemBytes, err := os.ReadFile("../test/hierarchy/int-r3.cert.pem")
1927 test.AssertNotError(t, err, "Error reading ../test/hierarchy/int-r3.cert.pem")
1928
1929 chainCrossPemBytes, err := os.ReadFile("../test/hierarchy/int-r3-cross.cert.pem")
1930 test.AssertNotError(t, err, "Error reading ../test/hierarchy/int-r3-cross.cert.pem")
1931
1932 reqPath := fmt.Sprintf("/acme/cert/%s", core.SerialToString(cert.SerialNumber))
1933 pkixContent := "application/pem-certificate-chain"
1934 noCache := "public, max-age=0, no-cache"
1935 notFound := `{"type":"` + probs.ErrorNS + `malformed","detail":"Certificate not found","status":404}`
1936
1937 testCases := []struct {
1938 Name string
1939 Request *http.Request
1940 ExpectedStatus int
1941 ExpectedHeaders map[string]string
1942 ExpectedLink string
1943 ExpectedBody string
1944 ExpectedCert []byte
1945 AnyCert bool
1946 }{
1947 {
1948 Name: "Valid serial",
1949 Request: makeGet(reqPath),
1950 ExpectedStatus: http.StatusOK,
1951 ExpectedHeaders: map[string]string{
1952 "Content-Type": pkixContent,
1953 },
1954 ExpectedCert: append(certPemBytes, append([]byte("\n"), chainPemBytes...)...),
1955 ExpectedLink: fmt.Sprintf(`<http://localhost%s/1>;rel="alternate"`, reqPath),
1956 },
1957 {
1958 Name: "Valid serial, POST-as-GET",
1959 Request: makePost(1, nil, reqPath, ""),
1960 ExpectedStatus: http.StatusOK,
1961 ExpectedHeaders: map[string]string{
1962 "Content-Type": pkixContent,
1963 },
1964 ExpectedCert: append(certPemBytes, append([]byte("\n"), chainPemBytes...)...),
1965 },
1966 {
1967 Name: "Valid serial, bad POST-as-GET",
1968 Request: makePost(1, nil, reqPath, "{}"),
1969 ExpectedStatus: http.StatusBadRequest,
1970 ExpectedBody: `{
1971 "type": "urn:ietf:params:acme:error:malformed",
1972 "status": 400,
1973 "detail": "POST-as-GET requests must have an empty payload"
1974 }`,
1975 },
1976 {
1977 Name: "Valid serial, POST-as-GET from wrong account",
1978 Request: makePost(2, altKey, reqPath, ""),
1979 ExpectedStatus: http.StatusForbidden,
1980 ExpectedBody: `{
1981 "type": "urn:ietf:params:acme:error:unauthorized",
1982 "status": 403,
1983 "detail": "Account in use did not issue specified certificate"
1984 }`,
1985 },
1986 {
1987 Name: "Unused serial, no cache",
1988 Request: makeGet("/acme/cert/000000000000000000000000000000000001"),
1989 ExpectedStatus: http.StatusNotFound,
1990 ExpectedBody: notFound,
1991 },
1992 {
1993 Name: "Invalid serial, no cache",
1994 Request: makeGet("/acme/cert/nothex"),
1995 ExpectedStatus: http.StatusNotFound,
1996 ExpectedBody: notFound,
1997 },
1998 {
1999 Name: "Another invalid serial, no cache",
2000 Request: makeGet("/acme/cert/00000000000000"),
2001 ExpectedStatus: http.StatusNotFound,
2002 ExpectedBody: notFound,
2003 },
2004 {
2005 Name: "Valid serial (explicit default chain)",
2006 Request: makeGet(reqPath + "/0"),
2007 ExpectedStatus: http.StatusOK,
2008 ExpectedHeaders: map[string]string{
2009 "Content-Type": pkixContent,
2010 },
2011 ExpectedLink: fmt.Sprintf(`<http://localhost%s/1>;rel="alternate"`, reqPath),
2012 ExpectedCert: append(certPemBytes, append([]byte("\n"), chainPemBytes...)...),
2013 },
2014 {
2015 Name: "Valid serial (explicit alternate chain)",
2016 Request: makeGet(reqPath + "/1"),
2017 ExpectedStatus: http.StatusOK,
2018 ExpectedHeaders: map[string]string{
2019 "Content-Type": pkixContent,
2020 },
2021 ExpectedLink: fmt.Sprintf(`<http://localhost%s/0>;rel="alternate"`, reqPath),
2022 ExpectedCert: append(certPemBytes, append([]byte("\n"), chainCrossPemBytes...)...),
2023 },
2024 {
2025 Name: "Valid serial (explicit non-existent alternate chain)",
2026 Request: makeGet(reqPath + "/2"),
2027 ExpectedStatus: http.StatusNotFound,
2028 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Unknown issuance chain","status":404}`,
2029 },
2030 {
2031 Name: "Valid serial (explicit negative alternate chain)",
2032 Request: makeGet(reqPath + "/-1"),
2033 ExpectedStatus: http.StatusBadRequest,
2034 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Chain ID must be a non-negative integer","status":400}`,
2035 },
2036 }
2037
2038 for _, tc := range testCases {
2039 t.Run(tc.Name, func(t *testing.T) {
2040 responseWriter := httptest.NewRecorder()
2041 mockLog := wfe.log.(*blog.Mock)
2042 mockLog.Clear()
2043
2044
2045 mux.ServeHTTP(responseWriter, tc.Request)
2046 headers := responseWriter.Header()
2047
2048
2049 test.AssertEquals(t, responseWriter.Code, tc.ExpectedStatus)
2050
2051
2052 test.AssertEquals(t, headers.Get("Cache-Control"), noCache)
2053
2054
2055 for h, v := range tc.ExpectedHeaders {
2056 test.AssertEquals(t, headers.Get(h), v)
2057 }
2058
2059 if tc.ExpectedLink != "" {
2060 found := false
2061 links := headers["Link"]
2062 for _, link := range links {
2063 if link == tc.ExpectedLink {
2064 found = true
2065 break
2066 }
2067 }
2068 if !found {
2069 t.Errorf("Expected link '%s', but did not find it in (%v)",
2070 tc.ExpectedLink, links)
2071 }
2072 }
2073
2074 if tc.AnyCert {
2075 return
2076 }
2077
2078 if len(tc.ExpectedCert) > 0 {
2079
2080 bodyBytes := responseWriter.Body.Bytes()
2081 test.Assert(t, bytes.Equal(bodyBytes, tc.ExpectedCert), "Certificates don't match")
2082
2083
2084 reqlogs := mockLog.GetAllMatching(`INFO: [^ ]+ [^ ]+ [^ ]+ 200 .*`)
2085 if len(reqlogs) != 1 {
2086 t.Errorf("Didn't find info logs with code 200. Instead got:\n%s\n",
2087 strings.Join(mockLog.GetAllMatching(`.*`), "\n"))
2088 }
2089 } else {
2090
2091 body := responseWriter.Body.String()
2092 test.AssertUnmarshaledEquals(t, body, tc.ExpectedBody)
2093
2094
2095 reqlogs := mockLog.GetAllMatching(fmt.Sprintf(`INFO: [^ ]+ [^ ]+ [^ ]+ %d .*`, tc.ExpectedStatus))
2096 if len(reqlogs) != 1 {
2097 t.Errorf("Didn't find info logs with code %d. Instead got:\n%s\n",
2098 tc.ExpectedStatus, strings.Join(mockLog.GetAllMatching(`.*`), "\n"))
2099 }
2100 }
2101 })
2102 }
2103 }
2104
2105 type mockSAWithNewCert struct {
2106 sapb.StorageAuthorityReadOnlyClient
2107 clk clock.Clock
2108 }
2109
2110 func (sa *mockSAWithNewCert) GetCertificate(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
2111 issuer, err := core.LoadCert("../test/hierarchy/int-e1.cert.pem")
2112 if err != nil {
2113 return nil, fmt.Errorf("failed to load test issuer cert: %w", err)
2114 }
2115
2116 issuerKeyPem, err := os.ReadFile("../test/hierarchy/int-e1.key.pem")
2117 if err != nil {
2118 return nil, fmt.Errorf("failed to load test issuer key: %w", err)
2119 }
2120 issuerKey := loadKey(&testing.T{}, issuerKeyPem)
2121
2122 newKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
2123 if err != nil {
2124 return nil, fmt.Errorf("failed to create test key: %w", err)
2125 }
2126
2127 sn, err := core.StringToSerial(req.Serial)
2128 if err != nil {
2129 return nil, fmt.Errorf("failed to parse test serial: %w", err)
2130 }
2131
2132 template := &x509.Certificate{
2133 SerialNumber: sn,
2134 DNSNames: []string{"new.ee.boulder.test"},
2135 }
2136
2137 certDER, err := x509.CreateCertificate(rand.Reader, template, issuer, &newKey.PublicKey, issuerKey)
2138 if err != nil {
2139 return nil, fmt.Errorf("failed to issue test cert: %w", err)
2140 }
2141
2142 cert, err := x509.ParseCertificate(certDER)
2143 if err != nil {
2144 return nil, fmt.Errorf("failed to parse test cert: %w", err)
2145 }
2146
2147 issued := sa.clk.Now().Add(-1 * time.Second)
2148
2149 return &corepb.Certificate{
2150 RegistrationID: 1,
2151 Serial: core.SerialToString(cert.SerialNumber),
2152 IssuedNS: issued.UnixNano(),
2153 Issued: timestamppb.New(issued),
2154 Der: cert.Raw,
2155 }, nil
2156 }
2157
2158
2159
2160
2161 func TestGetCertificateNew(t *testing.T) {
2162 wfe, fc, signer := setupWFE(t)
2163 wfe.sa = &mockSAWithNewCert{wfe.sa, fc}
2164 mux := wfe.Handler(metrics.NoopRegisterer)
2165
2166 makeGet := func(path string) *http.Request {
2167 return &http.Request{URL: &url.URL{Path: path}, Method: "GET"}
2168 }
2169
2170 makePost := func(keyID int64, key interface{}, path, body string) *http.Request {
2171 _, _, jwsBody := signer.byKeyID(keyID, key, fmt.Sprintf("http://localhost%s", path), body)
2172 return makePostRequestWithPath(path, jwsBody)
2173 }
2174
2175 altKey := loadKey(t, []byte(test2KeyPrivatePEM))
2176 _, ok := altKey.(*rsa.PrivateKey)
2177 test.Assert(t, ok, "Couldn't load RSA key")
2178
2179 pkixContent := "application/pem-certificate-chain"
2180 noCache := "public, max-age=0, no-cache"
2181
2182 testCases := []struct {
2183 Name string
2184 Request *http.Request
2185 ExpectedStatus int
2186 ExpectedHeaders map[string]string
2187 ExpectedBody string
2188 }{
2189 {
2190 Name: "Get",
2191 Request: makeGet("/get/cert/000000000000000000000000000000000001"),
2192 ExpectedStatus: http.StatusForbidden,
2193 ExpectedBody: `{
2194 "type": "` + probs.ErrorNS + `unauthorized",
2195 "detail": "Certificate is too new for GET API. You should only use this non-standard API to access resources created more than 10s ago",
2196 "status": 403
2197 }`,
2198 },
2199 {
2200 Name: "ACME Get",
2201 Request: makeGet("/acme/cert/000000000000000000000000000000000002"),
2202 ExpectedStatus: http.StatusOK,
2203 ExpectedHeaders: map[string]string{
2204 "Content-Type": pkixContent,
2205 },
2206 },
2207 {
2208 Name: "ACME POST-as-GET",
2209 Request: makePost(1, nil, "/acme/cert/000000000000000000000000000000000003", ""),
2210 ExpectedStatus: http.StatusOK,
2211 ExpectedHeaders: map[string]string{
2212 "Content-Type": pkixContent,
2213 },
2214 },
2215 }
2216
2217 for _, tc := range testCases {
2218 t.Run(tc.Name, func(t *testing.T) {
2219 responseWriter := httptest.NewRecorder()
2220 mockLog := wfe.log.(*blog.Mock)
2221 mockLog.Clear()
2222
2223
2224 mux.ServeHTTP(responseWriter, tc.Request)
2225 headers := responseWriter.Header()
2226
2227
2228 test.AssertEquals(t, responseWriter.Code, tc.ExpectedStatus)
2229
2230
2231 test.AssertEquals(t, headers.Get("Cache-Control"), noCache)
2232
2233
2234 for h, v := range tc.ExpectedHeaders {
2235 test.AssertEquals(t, headers.Get(h), v)
2236 }
2237
2238
2239 if tc.ExpectedBody != "" {
2240 body := responseWriter.Body.String()
2241 test.AssertUnmarshaledEquals(t, body, tc.ExpectedBody)
2242
2243
2244 reqlogs := mockLog.GetAllMatching(fmt.Sprintf(`INFO: [^ ]+ [^ ]+ [^ ]+ %d .*`, tc.ExpectedStatus))
2245 if len(reqlogs) != 1 {
2246 t.Errorf("Didn't find info logs with code %d. Instead got:\n%s\n",
2247 tc.ExpectedStatus, strings.Join(mockLog.GetAllMatching(`.*`), "\n"))
2248 }
2249 }
2250 })
2251 }
2252 }
2253
2254
2255
2256 func TestGetCertificateHEADHasCorrectBodyLength(t *testing.T) {
2257 wfe, _, _ := setupWFE(t)
2258 wfe.sa = newMockSAWithCert(t, wfe.sa)
2259
2260 certPemBytes, _ := os.ReadFile("../test/hierarchy/ee-r3.cert.pem")
2261 cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
2262 test.AssertNotError(t, err, "failed to load test certificate")
2263
2264 chainPemBytes, err := os.ReadFile("../test/hierarchy/int-r3.cert.pem")
2265 test.AssertNotError(t, err, "Error reading ../test/hierarchy/int-r3.cert.pem")
2266 chain := fmt.Sprintf("%s\n%s", string(certPemBytes), string(chainPemBytes))
2267 chainLen := strconv.Itoa(len(chain))
2268
2269 mockLog := wfe.log.(*blog.Mock)
2270 mockLog.Clear()
2271
2272 mux := wfe.Handler(metrics.NoopRegisterer)
2273 s := httptest.NewServer(mux)
2274 defer s.Close()
2275 req, _ := http.NewRequest(
2276 "HEAD", fmt.Sprintf("%s/acme/cert/%s", s.URL, core.SerialToString(cert.SerialNumber)), nil)
2277 resp, err := http.DefaultClient.Do(req)
2278 if err != nil {
2279 test.AssertNotError(t, err, "do error")
2280 }
2281 body, err := io.ReadAll(resp.Body)
2282 if err != nil {
2283 test.AssertNotEquals(t, err, "readall error")
2284 }
2285 err = resp.Body.Close()
2286 if err != nil {
2287 test.AssertNotEquals(t, err, "readall error")
2288 }
2289 test.AssertEquals(t, resp.StatusCode, 200)
2290 test.AssertEquals(t, chainLen, resp.Header.Get("Content-Length"))
2291 test.AssertEquals(t, 0, len(body))
2292 }
2293
2294 type mockSAWithError struct {
2295 sapb.StorageAuthorityReadOnlyClient
2296 }
2297
2298 func (sa *mockSAWithError) GetCertificate(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
2299 return nil, errors.New("Oops")
2300 }
2301
2302 func TestGetCertificateServerError(t *testing.T) {
2303
2304
2305 wfe, _, _ := setupWFE(t)
2306 wfe.sa = &mockSAWithError{wfe.sa}
2307 mux := wfe.Handler(metrics.NoopRegisterer)
2308
2309 cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
2310 test.AssertNotError(t, err, "failed to load test certificate")
2311
2312 reqPath := fmt.Sprintf("/acme/cert/%s", core.SerialToString(cert.SerialNumber))
2313 req := &http.Request{URL: &url.URL{Path: reqPath}, Method: "GET"}
2314
2315
2316 responseWriter := httptest.NewRecorder()
2317 mux.ServeHTTP(responseWriter, req)
2318
2319 test.AssertEquals(t, responseWriter.Code, http.StatusInternalServerError)
2320
2321 noCache := "public, max-age=0, no-cache"
2322 test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), noCache)
2323
2324 body := `{
2325 "type": "urn:ietf:params:acme:error:serverInternal",
2326 "status": 500,
2327 "detail": "Failed to retrieve certificate"
2328 }`
2329 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), body)
2330 }
2331
2332 func newRequestEvent() *web.RequestEvent {
2333 return &web.RequestEvent{Extra: make(map[string]interface{})}
2334 }
2335
2336 func TestHeaderBoulderRequester(t *testing.T) {
2337 wfe, _, signer := setupWFE(t)
2338 mux := wfe.Handler(metrics.NoopRegisterer)
2339 responseWriter := httptest.NewRecorder()
2340
2341 key := loadKey(t, []byte(test1KeyPrivatePEM))
2342 _, ok := key.(*rsa.PrivateKey)
2343 test.Assert(t, ok, "Failed to load test 1 RSA key")
2344
2345 payload := `{}`
2346 path := fmt.Sprintf("%s%d", acctPath, 1)
2347 signedURL := fmt.Sprintf("http://localhost%s", path)
2348 _, _, body := signer.byKeyID(1, nil, signedURL, payload)
2349 request := makePostRequestWithPath(path, body)
2350
2351 mux.ServeHTTP(responseWriter, request)
2352 test.AssertEquals(t, responseWriter.Header().Get("Boulder-Requester"), "1")
2353
2354
2355 payload = `{"agreement":"https://letsencrypt.org/im-bad"}`
2356 _, _, body = signer.byKeyID(1, nil, signedURL, payload)
2357 request = makePostRequestWithPath(path, body)
2358 mux.ServeHTTP(responseWriter, request)
2359 test.AssertEquals(t, responseWriter.Header().Get("Boulder-Requester"), "1")
2360 }
2361
2362 func TestDeactivateAuthorization(t *testing.T) {
2363 wfe, _, signer := setupWFE(t)
2364 responseWriter := httptest.NewRecorder()
2365
2366 responseWriter.Body.Reset()
2367
2368 payload := `{"status":""}`
2369 _, _, body := signer.byKeyID(1, nil, "http://localhost/1", payload)
2370 request := makePostRequestWithPath("1", body)
2371
2372 wfe.Authorization(ctx, newRequestEvent(), responseWriter, request)
2373 test.AssertUnmarshaledEquals(t,
2374 responseWriter.Body.String(),
2375 `{"type": "`+probs.ErrorNS+`malformed","detail": "Invalid status value","status": 400}`)
2376
2377 responseWriter.Body.Reset()
2378 payload = `{"status":"deactivated"}`
2379 _, _, body = signer.byKeyID(1, nil, "http://localhost/1", payload)
2380 request = makePostRequestWithPath("1", body)
2381
2382 wfe.Authorization(ctx, newRequestEvent(), responseWriter, request)
2383 test.AssertUnmarshaledEquals(t,
2384 responseWriter.Body.String(),
2385 `{
2386 "identifier": {
2387 "type": "dns",
2388 "value": "not-an-example.com"
2389 },
2390 "status": "deactivated",
2391 "expires": "2070-01-01T00:00:00Z",
2392 "challenges": [
2393 {
2394 "status": "pending",
2395 "type": "dns",
2396 "token":"token",
2397 "url": "http://localhost/acme/chall-v3/1/-ZfxEw"
2398 }
2399 ]
2400 }`)
2401 }
2402
2403 func TestDeactivateAccount(t *testing.T) {
2404 responseWriter := httptest.NewRecorder()
2405 wfe, _, signer := setupWFE(t)
2406
2407 responseWriter.Body.Reset()
2408 payload := `{"status":"asd"}`
2409 signedURL := "http://localhost/1"
2410 path := "1"
2411 _, _, body := signer.byKeyID(1, nil, signedURL, payload)
2412 request := makePostRequestWithPath(path, body)
2413
2414 wfe.Account(ctx, newRequestEvent(), responseWriter, request)
2415 test.AssertUnmarshaledEquals(t,
2416 responseWriter.Body.String(),
2417 `{"type": "`+probs.ErrorNS+`malformed","detail": "Invalid value provided for status field","status": 400}`)
2418
2419 responseWriter.Body.Reset()
2420 payload = `{"status":"deactivated"}`
2421 _, _, body = signer.byKeyID(1, nil, signedURL, payload)
2422 request = makePostRequestWithPath(path, body)
2423
2424 wfe.Account(ctx, newRequestEvent(), responseWriter, request)
2425 test.AssertUnmarshaledEquals(t,
2426 responseWriter.Body.String(),
2427 `{
2428 "key": {
2429 "kty": "RSA",
2430 "n": "yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ",
2431 "e": "AQAB"
2432 },
2433 "contact": [
2434 "mailto:person@mail.com"
2435 ],
2436 "initialIp": "",
2437 "status": "deactivated"
2438 }`)
2439
2440 responseWriter.Body.Reset()
2441 payload = `{"status":"deactivated", "contact":[]}`
2442 _, _, body = signer.byKeyID(1, nil, signedURL, payload)
2443 request = makePostRequestWithPath(path, body)
2444 wfe.Account(ctx, newRequestEvent(), responseWriter, request)
2445 test.AssertUnmarshaledEquals(t,
2446 responseWriter.Body.String(),
2447 `{
2448 "key": {
2449 "kty": "RSA",
2450 "n": "yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ",
2451 "e": "AQAB"
2452 },
2453 "contact": [
2454 "mailto:person@mail.com"
2455 ],
2456 "initialIp": "",
2457 "status": "deactivated"
2458 }`)
2459
2460 responseWriter.Body.Reset()
2461 key := loadKey(t, []byte(test3KeyPrivatePEM))
2462 _, ok := key.(*rsa.PrivateKey)
2463 test.Assert(t, ok, "Couldn't load test3 RSA key")
2464
2465 payload = `{"status":"deactivated"}`
2466 path = "3"
2467 signedURL = "http://localhost/3"
2468 _, _, body = signer.byKeyID(3, key, signedURL, payload)
2469 request = makePostRequestWithPath(path, body)
2470
2471 wfe.Account(ctx, newRequestEvent(), responseWriter, request)
2472
2473 test.AssertUnmarshaledEquals(t,
2474 responseWriter.Body.String(),
2475 `{
2476 "type": "`+probs.ErrorNS+`unauthorized",
2477 "detail": "Account is not valid, has status \"deactivated\"",
2478 "status": 403
2479 }`)
2480 }
2481
2482 func TestNewOrder(t *testing.T) {
2483 wfe, _, signer := setupWFE(t)
2484 responseWriter := httptest.NewRecorder()
2485
2486 targetHost := "localhost"
2487 targetPath := "new-order"
2488 signedURL := fmt.Sprintf("http://%s/%s", targetHost, targetPath)
2489
2490 nonDNSIdentifierBody := `
2491 {
2492 "Identifiers": [
2493 {"type": "dns", "value": "not-example.com"},
2494 {"type": "dns", "value": "www.not-example.com"},
2495 {"type": "fakeID", "value": "www.i-am-21.com"}
2496 ]
2497 }
2498 `
2499
2500 validOrderBody := `
2501 {
2502 "Identifiers": [
2503 {"type": "dns", "value": "not-example.com"},
2504 {"type": "dns", "value": "www.not-example.com"}
2505 ]
2506 }`
2507
2508
2509 tooLongCNBody := `
2510 {
2511 "Identifiers": [
2512 {
2513 "type": "dns",
2514 "value": "thisreallylongexampledomainisabytelongerthanthemaxcnbytelimit.com"
2515 }
2516 ]
2517 }`
2518
2519 oneLongOneShortCNBody := `
2520 {
2521 "Identifiers": [
2522 {
2523 "type": "dns",
2524 "value": "thisreallylongexampledomainisabytelongerthanthemaxcnbytelimit.com"
2525 },
2526 {
2527 "type": "dns",
2528 "value": "not-example.com"
2529 }
2530 ]
2531 }`
2532
2533 testCases := []struct {
2534 Name string
2535 Request *http.Request
2536 ExpectedBody string
2537 ExpectedHeaders map[string]string
2538 }{
2539 {
2540 Name: "POST, but no body",
2541 Request: &http.Request{
2542 Method: "POST",
2543 Header: map[string][]string{
2544 "Content-Length": {"0"},
2545 "Content-Type": {expectedJWSContentType},
2546 },
2547 },
2548 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"No body on POST","status":400}`,
2549 },
2550 {
2551 Name: "POST, with an invalid JWS body",
2552 Request: makePostRequestWithPath("hi", "hi"),
2553 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Parse error reading JWS","status":400}`,
2554 },
2555 {
2556 Name: "POST, properly signed JWS, payload isn't valid",
2557 Request: signAndPost(signer, targetPath, signedURL, "foo"),
2558 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Request payload did not parse as JSON","status":400}`,
2559 },
2560 {
2561 Name: "POST, empty domain name identifier",
2562 Request: signAndPost(signer, targetPath, signedURL, `{"identifiers":[{"type":"dns","value":""}]}`),
2563 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"NewOrder request included empty domain name","status":400}`,
2564 },
2565 {
2566 Name: "POST, no identifiers in payload",
2567 Request: signAndPost(signer, targetPath, signedURL, "{}"),
2568 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"NewOrder request did not specify any identifiers","status":400}`,
2569 },
2570 {
2571 Name: "POST, non-DNS identifier in payload",
2572 Request: signAndPost(signer, targetPath, signedURL, nonDNSIdentifierBody),
2573 ExpectedBody: `{"type":"` + probs.ErrorNS + `unsupportedIdentifier","detail":"NewOrder request included invalid non-DNS type identifier: type \"fakeID\", value \"www.i-am-21.com\"","status":400}`,
2574 },
2575 {
2576 Name: "POST, notAfter and notBefore in payload",
2577 Request: signAndPost(signer, targetPath, signedURL, `{"identifiers":[{"type": "dns", "value": "not-example.com"}], "notBefore":"now", "notAfter": "later"}`),
2578 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"NotBefore and NotAfter are not supported","status":400}`,
2579 },
2580 {
2581 Name: "POST, no potential CNs 64 bytes or smaller",
2582 Request: signAndPost(signer, targetPath, signedURL, tooLongCNBody),
2583 ExpectedBody: `{"type":"` + probs.ErrorNS + `rejectedIdentifier","detail":"NewOrder request did not include a SAN short enough to fit in CN","status":400}`,
2584 },
2585 {
2586 Name: "POST, good payload, one potential CNs less than 64 bytes and one longer",
2587 Request: signAndPost(signer, targetPath, signedURL, oneLongOneShortCNBody),
2588 ExpectedBody: `
2589 {
2590 "status": "pending",
2591 "expires": "2021-02-01T01:01:01Z",
2592 "identifiers": [
2593 { "type": "dns", "value": "thisreallylongexampledomainisabytelongerthanthemaxcnbytelimit.com"},
2594 { "type": "dns", "value": "not-example.com"}
2595 ],
2596 "authorizations": [
2597 "http://localhost/acme/authz-v3/1"
2598 ],
2599 "finalize": "http://localhost/acme/finalize/1/1"
2600 }`,
2601 },
2602 {
2603 Name: "POST, good payload",
2604 Request: signAndPost(signer, targetPath, signedURL, validOrderBody),
2605 ExpectedBody: `
2606 {
2607 "status": "pending",
2608 "expires": "2021-02-01T01:01:01Z",
2609 "identifiers": [
2610 { "type": "dns", "value": "not-example.com"},
2611 { "type": "dns", "value": "www.not-example.com"}
2612 ],
2613 "authorizations": [
2614 "http://localhost/acme/authz-v3/1"
2615 ],
2616 "finalize": "http://localhost/acme/finalize/1/1"
2617 }`,
2618 },
2619 }
2620
2621 for _, tc := range testCases {
2622 t.Run(tc.Name, func(t *testing.T) {
2623 responseWriter.Body.Reset()
2624
2625 wfe.NewOrder(ctx, newRequestEvent(), responseWriter, tc.Request)
2626 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tc.ExpectedBody)
2627
2628 headers := responseWriter.Header()
2629 for k, v := range tc.ExpectedHeaders {
2630 test.AssertEquals(t, headers.Get(k), v)
2631 }
2632 })
2633 }
2634
2635
2636 responseWriter.Body.Reset()
2637 request := signAndPost(signer, targetPath, signedURL, validOrderBody)
2638 requestEvent := newRequestEvent()
2639 wfe.NewOrder(ctx, requestEvent, responseWriter, request)
2640
2641 if requestEvent.Created != "1" {
2642 t.Errorf("Expected to log Created field when creating Order: %#v", requestEvent)
2643 }
2644 }
2645
2646 func TestFinalizeOrder(t *testing.T) {
2647 wfe, _, signer := setupWFE(t)
2648 responseWriter := httptest.NewRecorder()
2649
2650 targetHost := "localhost"
2651 targetPath := "1/1"
2652 signedURL := fmt.Sprintf("http://%s/%s", targetHost, targetPath)
2653
2654
2655
2656 goodCertCSRPayload := `{
2657 "csr": "MIICYjCCAUoCAQAwHTEbMBkGA1UEAwwSbm90LWFuLWV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmqs7nue5oFxKBk2WaFZJAma2nm1oFyPIq19gYEAdQN4mWvaJ8RjzHFkDMYUrlIrGxCYuFJDHFUk9dh19Na1MIY-NVLgcSbyNcOML3bLbLEwGmvXPbbEOflBA9mxUS9TLMgXW5ghf_qbt4vmSGKloIim41QXt55QFW6O-84s8Kd2OE6df0wTsEwLhZB3j5pDU-t7j5vTMv4Tc7EptaPkOdfQn-68viUJjlYM_4yIBVRhWCdexFdylCKVLg0obsghQEwULKYCUjdg6F0VJUI115DU49tzscXU_3FS3CyY8rchunuYszBNkdmgpAwViHNWuP7ESdEd_emrj1xuioSe6PwIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAE_T1nWU38XVYL28hNVSXU0rW5IBUKtbvr0qAkD4kda4HmQRTYkt-LNSuvxoZCC9lxijjgtJi-OJe_DCTdZZpYzewlVvcKToWSYHYQ6Wm1-fxxD_XzphvZOujpmBySchdiz7QSVWJmVZu34XD5RJbIcrmj_cjRt42J1hiTFjNMzQu9U6_HwIMmliDL-soFY2RTvvZf-dAFvOUQ-Wbxt97eM1PbbmxJNWRhbAmgEpe9PWDPTpqV5AK56VAa991cQ1P8ZVmPss5hvwGWhOtpnpTZVHN3toGNYFKqxWPboirqushQlfKiFqT9rpRgM3-mFjOHidGqsKEkTdmfSVlVEk3oo="
2658 }`
2659
2660 egUrl := mustParseURL("1/1")
2661
2662 testCases := []struct {
2663 Name string
2664 Request *http.Request
2665 ExpectedHeaders map[string]string
2666 ExpectedBody string
2667 }{
2668 {
2669 Name: "POST, but no body",
2670 Request: &http.Request{
2671 URL: egUrl,
2672 RequestURI: targetPath,
2673 Method: "POST",
2674 Header: map[string][]string{
2675 "Content-Length": {"0"},
2676 "Content-Type": {expectedJWSContentType},
2677 },
2678 },
2679 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"No body on POST","status":400}`,
2680 },
2681 {
2682 Name: "POST, with an invalid JWS body",
2683 Request: makePostRequestWithPath(targetPath, "hi"),
2684 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Parse error reading JWS","status":400}`,
2685 },
2686 {
2687 Name: "POST, properly signed JWS, payload isn't valid",
2688 Request: signAndPost(signer, targetPath, signedURL, "foo"),
2689 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Request payload did not parse as JSON","status":400}`,
2690 },
2691 {
2692 Name: "Invalid path",
2693 Request: signAndPost(signer, "1", "http://localhost/1", "{}"),
2694 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Invalid request path","status":404}`,
2695 },
2696 {
2697 Name: "Bad acct ID in path",
2698 Request: signAndPost(signer, "a/1", "http://localhost/a/1", "{}"),
2699 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Invalid account ID","status":400}`,
2700 },
2701 {
2702 Name: "Mismatched acct ID in path/JWS",
2703
2704
2705
2706
2707
2708 Request: signAndPost(signer, "2/1", "http://localhost/2/1", "{}"),
2709 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"No order found for account ID 2","status":404}`,
2710 },
2711 {
2712 Name: "Order ID is invalid",
2713 Request: signAndPost(signer, "1/okwhatever/finalize-order", "http://localhost/1/okwhatever/finalize-order", "{}"),
2714 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Invalid order ID","status":400}`,
2715 },
2716 {
2717 Name: "Order doesn't exist",
2718
2719 Request: signAndPost(signer, "1/2", "http://localhost/1/2", "{}"),
2720 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"No order for ID 2","status":404}`,
2721 },
2722 {
2723 Name: "Order is already finalized",
2724
2725 Request: signAndPost(signer, "1/1", "http://localhost/1/1", goodCertCSRPayload),
2726 ExpectedBody: `{"type":"` + probs.ErrorNS + `orderNotReady","detail":"Order's status (\"valid\") is not acceptable for finalization","status":403}`,
2727 },
2728 {
2729 Name: "Order is expired",
2730
2731 Request: signAndPost(signer, "1/7", "http://localhost/1/7", goodCertCSRPayload),
2732 ExpectedBody: `{"type":"` + probs.ErrorNS + `malformed","detail":"Order 7 is expired","status":404}`,
2733 },
2734 {
2735 Name: "Good CSR, Pending Order",
2736 Request: signAndPost(signer, "1/4", "http://localhost/1/4", goodCertCSRPayload),
2737 ExpectedBody: `{"type":"` + probs.ErrorNS + `orderNotReady","detail":"Order's status (\"pending\") is not acceptable for finalization","status":403}`,
2738 },
2739 {
2740 Name: "Good CSR, Ready Order",
2741 Request: signAndPost(signer, "1/8", "http://localhost/1/8", goodCertCSRPayload),
2742 ExpectedHeaders: map[string]string{
2743 "Location": "http://localhost/acme/order/1/8",
2744 "Retry-After": "3",
2745 },
2746 ExpectedBody: `
2747 {
2748 "status": "processing",
2749 "expires": "2000-01-01T00:00:00Z",
2750 "identifiers": [
2751 {"type":"dns","value":"example.com"}
2752 ],
2753 "authorizations": [
2754 "http://localhost/acme/authz-v3/1"
2755 ],
2756 "finalize": "http://localhost/acme/finalize/1/8"
2757 }`,
2758 },
2759 }
2760
2761 for _, tc := range testCases {
2762 t.Run(tc.Name, func(t *testing.T) {
2763 responseWriter.Body.Reset()
2764 wfe.FinalizeOrder(ctx, newRequestEvent(), responseWriter, tc.Request)
2765 for k, v := range tc.ExpectedHeaders {
2766 got := responseWriter.Header().Get(k)
2767 if v != got {
2768 t.Errorf("Header %q: Expected %q, got %q", k, v, got)
2769 }
2770 }
2771 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(),
2772 tc.ExpectedBody)
2773 })
2774 }
2775
2776
2777
2778
2779
2780 badCSRReq := signAndPost(signer, "1/8", "http://localhost/1/8", `{"CSR": "ABCD"}`)
2781 responseWriter.Body.Reset()
2782 wfe.FinalizeOrder(ctx, newRequestEvent(), responseWriter, badCSRReq)
2783 responseBody := responseWriter.Body.String()
2784 test.AssertContains(t, responseBody, "Error parsing certificate request")
2785 }
2786
2787 func TestKeyRollover(t *testing.T) {
2788 responseWriter := httptest.NewRecorder()
2789 wfe, _, signer := setupWFE(t)
2790
2791 existingKey, err := rsa.GenerateKey(rand.Reader, 2048)
2792 test.AssertNotError(t, err, "Error creating random 2048 RSA key")
2793
2794 newKeyBytes, err := os.ReadFile("../test/test-key-5.der")
2795 test.AssertNotError(t, err, "Failed to read ../test/test-key-5.der")
2796 newKeyPriv, err := x509.ParsePKCS1PrivateKey(newKeyBytes)
2797 test.AssertNotError(t, err, "Failed parsing private key")
2798 newJWKJSON, err := jose.JSONWebKey{Key: newKeyPriv.Public()}.MarshalJSON()
2799 test.AssertNotError(t, err, "Failed to marshal JWK JSON")
2800
2801 wfe.KeyRollover(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath("", "{}"))
2802 test.AssertUnmarshaledEquals(t,
2803 responseWriter.Body.String(),
2804 `{
2805 "type": "`+probs.ErrorNS+`malformed",
2806 "detail": "Parse error reading JWS",
2807 "status": 400
2808 }`)
2809
2810 testCases := []struct {
2811 Name string
2812 Payload string
2813 ExpectedResponse string
2814 NewKey crypto.Signer
2815 ErrorStatType string
2816 }{
2817 {
2818 Name: "Missing account URL",
2819 Payload: `{"oldKey":` + test1KeyPublicJSON + `}`,
2820 ExpectedResponse: `{
2821 "type": "` + probs.ErrorNS + `malformed",
2822 "detail": "Inner key rollover request specified Account \"\", but outer JWS has Key ID \"http://localhost/acme/acct/1\"",
2823 "status": 400
2824 }`,
2825 NewKey: newKeyPriv,
2826 ErrorStatType: "KeyRolloverMismatchedAccount",
2827 },
2828 {
2829 Name: "incorrect old key",
2830 Payload: `{"oldKey":` + string(newJWKJSON) + `,"account":"http://localhost/acme/acct/1"}`,
2831 ExpectedResponse: `{
2832 "type": "` + probs.ErrorNS + `malformed",
2833 "detail": "Inner JWS does not contain old key field matching current account key",
2834 "status": 400
2835 }`,
2836 NewKey: newKeyPriv,
2837 ErrorStatType: "KeyRolloverWrongOldKey",
2838 },
2839 {
2840 Name: "Valid key rollover request, key exists",
2841 Payload: `{"oldKey":` + test1KeyPublicJSON + `,"account":"http://localhost/acme/acct/1"}`,
2842 ExpectedResponse: `{
2843 "type": "urn:ietf:params:acme:error:malformed",
2844 "detail": "New key is already in use for a different account",
2845 "status": 409
2846 }`,
2847 NewKey: existingKey,
2848 },
2849 {
2850 Name: "Valid key rollover request",
2851 Payload: `{"oldKey":` + test1KeyPublicJSON + `,"account":"http://localhost/acme/acct/1"}`,
2852 ExpectedResponse: `{
2853 "key": ` + string(newJWKJSON) + `,
2854 "contact": [
2855 "mailto:person@mail.com"
2856 ],
2857 "initialIp": "",
2858 "status": "valid"
2859 }`,
2860 NewKey: newKeyPriv,
2861 },
2862 }
2863
2864 for _, tc := range testCases {
2865 t.Run(tc.Name, func(t *testing.T) {
2866 wfe.stats.joseErrorCount.Reset()
2867 responseWriter.Body.Reset()
2868 _, _, inner := signer.embeddedJWK(tc.NewKey, "http://localhost/key-change", tc.Payload)
2869 _, _, outer := signer.byKeyID(1, nil, "http://localhost/key-change", inner)
2870 wfe.KeyRollover(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath("key-change", outer))
2871 t.Log(responseWriter.Body.String())
2872 t.Log(tc.ExpectedResponse)
2873 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tc.ExpectedResponse)
2874 if tc.ErrorStatType != "" {
2875 test.AssertMetricWithLabelsEquals(
2876 t, wfe.stats.joseErrorCount, prometheus.Labels{"type": tc.ErrorStatType}, 1)
2877 }
2878 })
2879 }
2880 }
2881
2882 func TestKeyRolloverMismatchedJWSURLs(t *testing.T) {
2883 responseWriter := httptest.NewRecorder()
2884 wfe, _, signer := setupWFE(t)
2885
2886 newKeyBytes, err := os.ReadFile("../test/test-key-5.der")
2887 test.AssertNotError(t, err, "Failed to read ../test/test-key-5.der")
2888 newKeyPriv, err := x509.ParsePKCS1PrivateKey(newKeyBytes)
2889 test.AssertNotError(t, err, "Failed parsing private key")
2890
2891 _, _, inner := signer.embeddedJWK(newKeyPriv, "http://localhost/wrong-url", "{}")
2892 _, _, outer := signer.byKeyID(1, nil, "http://localhost/key-change", inner)
2893 wfe.KeyRollover(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath("key-change", outer))
2894 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), `
2895 {
2896 "type": "urn:ietf:params:acme:error:malformed",
2897 "detail": "Outer JWS 'url' value \"http://localhost/key-change\" does not match inner JWS 'url' value \"http://localhost/wrong-url\"",
2898 "status": 400
2899 }`)
2900 }
2901
2902 func TestGetOrder(t *testing.T) {
2903 wfe, _, signer := setupWFE(t)
2904
2905 makeGet := func(path string) *http.Request {
2906 return &http.Request{URL: &url.URL{Path: path}, Method: "GET"}
2907 }
2908
2909 makePost := func(keyID int64, path, body string) *http.Request {
2910 _, _, jwsBody := signer.byKeyID(keyID, nil, fmt.Sprintf("http://localhost/%s", path), body)
2911 return makePostRequestWithPath(path, jwsBody)
2912 }
2913
2914 testCases := []struct {
2915 Name string
2916 Request *http.Request
2917 Response string
2918 Headers map[string]string
2919 Endpoint string
2920 }{
2921 {
2922 Name: "Good request",
2923 Request: makeGet("1/1"),
2924 Response: `{"status": "valid","expires": "2000-01-01T00:00:00Z","identifiers":[{"type":"dns", "value":"example.com"}], "authorizations":["http://localhost/acme/authz-v3/1"],"finalize":"http://localhost/acme/finalize/1/1","certificate":"http://localhost/acme/cert/serial"}`,
2925 },
2926 {
2927 Name: "404 request",
2928 Request: makeGet("1/2"),
2929 Response: `{"type":"` + probs.ErrorNS + `malformed","detail":"No order for ID 2", "status":404}`,
2930 },
2931 {
2932 Name: "Invalid request path",
2933 Request: makeGet("asd"),
2934 Response: `{"type":"` + probs.ErrorNS + `malformed","detail":"Invalid request path","status":404}`,
2935 },
2936 {
2937 Name: "Invalid account ID",
2938 Request: makeGet("asd/asd"),
2939 Response: `{"type":"` + probs.ErrorNS + `malformed","detail":"Invalid account ID","status":400}`,
2940 },
2941 {
2942 Name: "Invalid order ID",
2943 Request: makeGet("1/asd"),
2944 Response: `{"type":"` + probs.ErrorNS + `malformed","detail":"Invalid order ID","status":400}`,
2945 },
2946 {
2947 Name: "Real request, wrong account",
2948 Request: makeGet("2/1"),
2949 Response: `{"type":"` + probs.ErrorNS + `malformed","detail":"No order found for account ID 2", "status":404}`,
2950 },
2951 {
2952 Name: "Internal error request",
2953 Request: makeGet("1/3"),
2954 Response: `{"type":"` + probs.ErrorNS + `serverInternal","detail":"Failed to retrieve order for ID 3","status":500}`,
2955 },
2956 {
2957 Name: "Invalid POST-as-GET",
2958 Request: makePost(1, "1/1", "{}"),
2959 Response: `{"type":"` + probs.ErrorNS + `malformed","detail":"POST-as-GET requests must have an empty payload", "status":400}`,
2960 },
2961 {
2962 Name: "Valid POST-as-GET, wrong account",
2963 Request: makePost(1, "2/1", ""),
2964 Response: `{"type":"` + probs.ErrorNS + `malformed","detail":"No order found for account ID 2", "status":404}`,
2965 },
2966 {
2967 Name: "Valid POST-as-GET",
2968 Request: makePost(1, "1/1", ""),
2969 Response: `{"status": "valid","expires": "2000-01-01T00:00:00Z","identifiers":[{"type":"dns", "value":"example.com"}], "authorizations":["http://localhost/acme/authz-v3/1"],"finalize":"http://localhost/acme/finalize/1/1","certificate":"http://localhost/acme/cert/serial"}`,
2970 },
2971 {
2972 Name: "GET new order",
2973 Request: makeGet("1/9"),
2974 Response: `{"type":"` + probs.ErrorNS + `unauthorized","detail":"Order is too new for GET API. You should only use this non-standard API to access resources created more than 10s ago","status":403}`,
2975 Endpoint: "/get/order/",
2976 },
2977 {
2978 Name: "GET new order from old endpoint",
2979 Request: makeGet("1/9"),
2980 Response: `{"status": "valid","expires": "2000-01-01T00:00:00Z","identifiers":[{"type":"dns", "value":"example.com"}], "authorizations":["http://localhost/acme/authz-v3/1"],"finalize":"http://localhost/acme/finalize/1/9","certificate":"http://localhost/acme/cert/serial"}`,
2981 },
2982 {
2983 Name: "POST-as-GET new order",
2984 Request: makePost(1, "1/9", ""),
2985 Response: `{"status": "valid","expires": "2000-01-01T00:00:00Z","identifiers":[{"type":"dns", "value":"example.com"}], "authorizations":["http://localhost/acme/authz-v3/1"],"finalize":"http://localhost/acme/finalize/1/9","certificate":"http://localhost/acme/cert/serial"}`,
2986 },
2987 {
2988 Name: "POST-as-GET processing order",
2989 Request: makePost(1, "1/10", ""),
2990 Response: `{"status": "processing","expires": "2000-01-01T00:00:00Z","identifiers":[{"type":"dns", "value":"example.com"}], "authorizations":["http://localhost/acme/authz-v3/1"],"finalize":"http://localhost/acme/finalize/1/10"}`,
2991 Headers: map[string]string{"Retry-After": "3"},
2992 },
2993 }
2994
2995 for _, tc := range testCases {
2996 t.Run(tc.Name, func(t *testing.T) {
2997 responseWriter := httptest.NewRecorder()
2998 if tc.Endpoint != "" {
2999 wfe.GetOrder(ctx, &web.RequestEvent{Extra: make(map[string]interface{}), Endpoint: tc.Endpoint}, responseWriter, tc.Request)
3000 } else {
3001 wfe.GetOrder(ctx, newRequestEvent(), responseWriter, tc.Request)
3002 }
3003 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tc.Response)
3004 for k, v := range tc.Headers {
3005 test.AssertEquals(t, responseWriter.Header().Get(k), v)
3006 }
3007 })
3008 }
3009 }
3010
3011 func makeRevokeRequestJSON(reason *revocation.Reason) ([]byte, error) {
3012 certPemBytes, err := os.ReadFile("../test/hierarchy/ee-r3.cert.pem")
3013 if err != nil {
3014 return nil, err
3015 }
3016 certBlock, _ := pem.Decode(certPemBytes)
3017 if err != nil {
3018 return nil, err
3019 }
3020 return makeRevokeRequestJSONForCert(certBlock.Bytes, reason)
3021 }
3022
3023 func makeRevokeRequestJSONForCert(der []byte, reason *revocation.Reason) ([]byte, error) {
3024 revokeRequest := struct {
3025 CertificateDER core.JSONBuffer `json:"certificate"`
3026 Reason *revocation.Reason `json:"reason"`
3027 }{
3028 CertificateDER: der,
3029 Reason: reason,
3030 }
3031 revokeRequestJSON, err := json.Marshal(revokeRequest)
3032 if err != nil {
3033 return nil, err
3034 }
3035 return revokeRequestJSON, nil
3036 }
3037
3038
3039
3040 func TestRevokeCertificateByApplicantValid(t *testing.T) {
3041 wfe, _, signer := setupWFE(t)
3042 wfe.sa = newMockSAWithCert(t, wfe.sa)
3043
3044 mockLog := wfe.log.(*blog.Mock)
3045 mockLog.Clear()
3046
3047 revokeRequestJSON, err := makeRevokeRequestJSON(nil)
3048 test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
3049 _, _, jwsBody := signer.byKeyID(1, nil, "http://localhost/revoke-cert", string(revokeRequestJSON))
3050
3051 responseWriter := httptest.NewRecorder()
3052 wfe.RevokeCertificate(ctx, newRequestEvent(), responseWriter,
3053 makePostRequestWithPath("revoke-cert", jwsBody))
3054
3055 test.AssertEquals(t, responseWriter.Code, 200)
3056 test.AssertEquals(t, responseWriter.Body.String(), "")
3057 test.AssertDeepEquals(t, mockLog.GetAllMatching("Authenticated revocation"), []string{
3058 `INFO: [AUDIT] Authenticated revocation JSON={"Serial":"000000000000000000001d72443db5189821","Reason":0,"RegID":1,"Method":"applicant"}`,
3059 })
3060 }
3061
3062
3063
3064 func TestRevokeCertificateByKeyValid(t *testing.T) {
3065 wfe, _, signer := setupWFE(t)
3066 wfe.sa = newMockSAWithCert(t, wfe.sa)
3067
3068 mockLog := wfe.log.(*blog.Mock)
3069 mockLog.Clear()
3070
3071 keyPemBytes, err := os.ReadFile("../test/hierarchy/ee-r3.key.pem")
3072 test.AssertNotError(t, err, "Failed to load key")
3073 key := loadKey(t, keyPemBytes)
3074
3075 revocationReason := revocation.Reason(ocsp.KeyCompromise)
3076 revokeRequestJSON, err := makeRevokeRequestJSON(&revocationReason)
3077 test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
3078 _, _, jwsBody := signer.embeddedJWK(key, "http://localhost/revoke-cert", string(revokeRequestJSON))
3079
3080 responseWriter := httptest.NewRecorder()
3081 wfe.RevokeCertificate(ctx, newRequestEvent(), responseWriter,
3082 makePostRequestWithPath("revoke-cert", jwsBody))
3083
3084 test.AssertEquals(t, responseWriter.Code, 200)
3085 test.AssertEquals(t, responseWriter.Body.String(), "")
3086 test.AssertDeepEquals(t, mockLog.GetAllMatching("Authenticated revocation"), []string{
3087 `INFO: [AUDIT] Authenticated revocation JSON={"Serial":"000000000000000000001d72443db5189821","Reason":1,"RegID":0,"Method":"privkey"}`,
3088 })
3089 }
3090
3091
3092
3093 func TestRevokeCertificateNotIssued(t *testing.T) {
3094 wfe, _, signer := setupWFE(t)
3095 wfe.sa = newMockSAWithCert(t, wfe.sa)
3096
3097
3098 k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
3099 test.AssertNotError(t, err, "unexpected error making random private key")
3100
3101
3102
3103
3104 knownCert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
3105 test.AssertNotError(t, err, "Unexpected error loading test cert")
3106 template := &x509.Certificate{
3107 SerialNumber: knownCert.SerialNumber,
3108 }
3109 certDER, err := x509.CreateCertificate(rand.Reader, template, template, k.Public(), k)
3110 test.AssertNotError(t, err, "Unexpected error creating self-signed junk cert")
3111
3112 keyPemBytes, err := os.ReadFile("../test/hierarchy/ee-r3.key.pem")
3113 test.AssertNotError(t, err, "Failed to load key")
3114 key := loadKey(t, keyPemBytes)
3115
3116 revokeRequestJSON, err := makeRevokeRequestJSONForCert(certDER, nil)
3117 test.AssertNotError(t, err, "Failed to make revokeRequestJSON for certDER")
3118 _, _, jwsBody := signer.embeddedJWK(key, "http://localhost/revoke-cert", string(revokeRequestJSON))
3119
3120 responseWriter := httptest.NewRecorder()
3121 wfe.RevokeCertificate(ctx, newRequestEvent(), responseWriter,
3122 makePostRequestWithPath("revoke-cert", jwsBody))
3123
3124 test.AssertEquals(t, responseWriter.Code, 404)
3125 test.AssertEquals(t, responseWriter.Body.String(), "{\n \"type\": \"urn:ietf:params:acme:error:malformed\",\n \"detail\": \"Certificate from unrecognized issuer\",\n \"status\": 404\n}")
3126 }
3127
3128 func TestRevokeCertificateExpired(t *testing.T) {
3129 wfe, fc, signer := setupWFE(t)
3130 wfe.sa = newMockSAWithCert(t, wfe.sa)
3131
3132 keyPemBytes, err := os.ReadFile("../test/hierarchy/ee-r3.key.pem")
3133 test.AssertNotError(t, err, "Failed to load key")
3134 key := loadKey(t, keyPemBytes)
3135
3136 revokeRequestJSON, err := makeRevokeRequestJSON(nil)
3137 test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
3138
3139 _, _, jwsBody := signer.embeddedJWK(key, "http://localhost/revoke-cert", string(revokeRequestJSON))
3140
3141 cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
3142 test.AssertNotError(t, err, "Failed to load test certificate")
3143
3144 fc.Set(cert.NotAfter.Add(time.Hour))
3145
3146 responseWriter := httptest.NewRecorder()
3147 wfe.RevokeCertificate(ctx, newRequestEvent(), responseWriter,
3148 makePostRequestWithPath("revoke-cert", jwsBody))
3149 test.AssertEquals(t, responseWriter.Code, 403)
3150 test.AssertEquals(t, responseWriter.Body.String(), "{\n \"type\": \"urn:ietf:params:acme:error:unauthorized\",\n \"detail\": \"Certificate is expired\",\n \"status\": 403\n}")
3151 }
3152
3153 func TestRevokeCertificateReasons(t *testing.T) {
3154 wfe, _, signer := setupWFE(t)
3155 wfe.sa = newMockSAWithCert(t, wfe.sa)
3156 ra := wfe.ra.(*MockRegistrationAuthority)
3157
3158 reason0 := revocation.Reason(ocsp.Unspecified)
3159 reason1 := revocation.Reason(ocsp.KeyCompromise)
3160 reason2 := revocation.Reason(ocsp.CACompromise)
3161 reason100 := revocation.Reason(100)
3162
3163 testCases := []struct {
3164 Name string
3165 Reason *revocation.Reason
3166 ExpectedHTTPCode int
3167 ExpectedBody string
3168 ExpectedReason *revocation.Reason
3169 }{
3170 {
3171 Name: "Valid reason",
3172 Reason: &reason1,
3173 ExpectedHTTPCode: http.StatusOK,
3174 ExpectedReason: &reason1,
3175 },
3176 {
3177 Name: "No reason",
3178 ExpectedHTTPCode: http.StatusOK,
3179 ExpectedReason: &reason0,
3180 },
3181 {
3182 Name: "Unsupported reason",
3183 Reason: &reason2,
3184 ExpectedHTTPCode: http.StatusBadRequest,
3185 ExpectedBody: `{"type":"` + probs.ErrorNS + `badRevocationReason","detail":"unsupported revocation reason code provided: cACompromise (2). Supported reasons: unspecified (0), keyCompromise (1), superseded (4), cessationOfOperation (5)","status":400}`,
3186 },
3187 {
3188 Name: "Non-existent reason",
3189 Reason: &reason100,
3190 ExpectedHTTPCode: http.StatusBadRequest,
3191 ExpectedBody: `{"type":"` + probs.ErrorNS + `badRevocationReason","detail":"unsupported revocation reason code provided: unknown (100). Supported reasons: unspecified (0), keyCompromise (1), superseded (4), cessationOfOperation (5)","status":400}`,
3192 },
3193 }
3194
3195 for _, tc := range testCases {
3196 t.Run(tc.Name, func(t *testing.T) {
3197 revokeRequestJSON, err := makeRevokeRequestJSON(tc.Reason)
3198 test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
3199 _, _, jwsBody := signer.byKeyID(1, nil, "http://localhost/revoke-cert", string(revokeRequestJSON))
3200
3201 responseWriter := httptest.NewRecorder()
3202 wfe.RevokeCertificate(ctx, newRequestEvent(), responseWriter,
3203 makePostRequestWithPath("revoke-cert", jwsBody))
3204
3205 test.AssertEquals(t, responseWriter.Code, tc.ExpectedHTTPCode)
3206 if tc.ExpectedBody != "" {
3207 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tc.ExpectedBody)
3208 } else {
3209 test.AssertEquals(t, responseWriter.Body.String(), tc.ExpectedBody)
3210 }
3211 if tc.ExpectedReason != nil {
3212 test.AssertEquals(t, ra.lastRevocationReason, *tc.ExpectedReason)
3213 }
3214 })
3215 }
3216 }
3217
3218
3219 func TestRevokeCertificateWrongCertificateKey(t *testing.T) {
3220 wfe, _, signer := setupWFE(t)
3221 wfe.sa = newMockSAWithCert(t, wfe.sa)
3222
3223 keyPemBytes, err := os.ReadFile("../test/hierarchy/ee-e1.key.pem")
3224 test.AssertNotError(t, err, "Failed to load key")
3225 key := loadKey(t, keyPemBytes)
3226
3227 revocationReason := revocation.Reason(ocsp.KeyCompromise)
3228 revokeRequestJSON, err := makeRevokeRequestJSON(&revocationReason)
3229 test.AssertNotError(t, err, "Failed to make revokeRequestJSON")
3230 _, _, jwsBody := signer.embeddedJWK(key, "http://localhost/revoke-cert", string(revokeRequestJSON))
3231
3232 responseWriter := httptest.NewRecorder()
3233 wfe.RevokeCertificate(ctx, newRequestEvent(), responseWriter,
3234 makePostRequestWithPath("revoke-cert", jwsBody))
3235 test.AssertEquals(t, responseWriter.Code, 403)
3236 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(),
3237 `{"type":"`+probs.ErrorNS+`unauthorized","detail":"JWK embedded in revocation request must be the same public key as the cert to be revoked","status":403}`)
3238 }
3239
3240 type mockSAGetRegByKeyFails struct {
3241 sapb.StorageAuthorityReadOnlyClient
3242 }
3243
3244 func (sa *mockSAGetRegByKeyFails) GetRegistrationByKey(_ context.Context, req *sapb.JSONWebKey, _ ...grpc.CallOption) (*corepb.Registration, error) {
3245 return nil, fmt.Errorf("whoops")
3246 }
3247
3248
3249
3250 func TestNewAccountWhenGetRegByKeyFails(t *testing.T) {
3251 wfe, _, signer := setupWFE(t)
3252 wfe.sa = &mockSAGetRegByKeyFails{wfe.sa}
3253 key := loadKey(t, []byte(testE2KeyPrivatePEM))
3254 _, ok := key.(*ecdsa.PrivateKey)
3255 test.Assert(t, ok, "Couldn't load ECDSA key")
3256 payload := `{"contact":["mailto:person@mail.com"],"agreement":"` + agreementURL + `"}`
3257 responseWriter := httptest.NewRecorder()
3258 _, _, body := signer.embeddedJWK(key, "http://localhost/new-account", payload)
3259 wfe.NewAccount(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath("/new-account", body))
3260 if responseWriter.Code != 500 {
3261 t.Fatalf("Wrong response code %d for NewAccount with failing GetRegByKey (wanted 500)", responseWriter.Code)
3262 }
3263 var prob probs.ProblemDetails
3264 err := json.Unmarshal(responseWriter.Body.Bytes(), &prob)
3265 test.AssertNotError(t, err, "unmarshalling response")
3266 if prob.Type != probs.ErrorNS+probs.ServerInternalProblem {
3267 t.Errorf("Wrong type for returned problem: %#v", prob.Type)
3268 }
3269 }
3270
3271 type mockSAGetRegByKeyNotFound struct {
3272 sapb.StorageAuthorityReadOnlyClient
3273 }
3274
3275 func (sa *mockSAGetRegByKeyNotFound) GetRegistrationByKey(_ context.Context, req *sapb.JSONWebKey, _ ...grpc.CallOption) (*corepb.Registration, error) {
3276 return nil, berrors.NotFoundError("not found")
3277 }
3278
3279 func TestNewAccountWhenGetRegByKeyNotFound(t *testing.T) {
3280 wfe, _, signer := setupWFE(t)
3281 wfe.sa = &mockSAGetRegByKeyNotFound{wfe.sa}
3282 key := loadKey(t, []byte(testE2KeyPrivatePEM))
3283 _, ok := key.(*ecdsa.PrivateKey)
3284 test.Assert(t, ok, "Couldn't load ECDSA key")
3285
3286
3287 payload := `{"contact":["mailto:person@mail.com"],"termsOfServiceAgreed":true}`
3288 signedURL := "http://localhost/new-account"
3289 responseWriter := httptest.NewRecorder()
3290 _, _, body := signer.embeddedJWK(key, signedURL, payload)
3291 wfe.NewAccount(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath("/new-account", body))
3292 if responseWriter.Code != http.StatusCreated {
3293 t.Errorf("Bad response to NewRegistration: %d, %s", responseWriter.Code, responseWriter.Body)
3294 }
3295
3296
3297
3298 payload = `{"contact":["mailto:person@mail.com"],"termsOfServiceAgreed":true,"onlyReturnExisting":true}`
3299 responseWriter = httptest.NewRecorder()
3300 _, _, body = signer.embeddedJWK(key, signedURL, payload)
3301
3302 wfe.NewAccount(ctx, newRequestEvent(), responseWriter, makePostRequestWithPath("/new-account", body))
3303 test.AssertEquals(t, responseWriter.Code, http.StatusBadRequest)
3304 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), `
3305 {
3306 "type": "urn:ietf:params:acme:error:accountDoesNotExist",
3307 "detail": "No account exists with the provided key",
3308 "status": 400
3309 }`)
3310 }
3311
3312 func TestPrepAuthzForDisplay(t *testing.T) {
3313 wfe, _, _ := setupWFE(t)
3314
3315
3316 authz := &core.Authorization{
3317 ID: "12345",
3318 Status: core.StatusPending,
3319 RegistrationID: 1,
3320 Identifier: identifier.DNSIdentifier("*.example.com"),
3321 Challenges: []core.Challenge{
3322 {
3323 Type: "dns",
3324 ProvidedKeyAuthorization: " 🔑",
3325 },
3326 },
3327 }
3328
3329
3330 wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz)
3331
3332
3333 test.AssertEquals(t, strings.HasPrefix(authz.Identifier.Value, "*."), false)
3334
3335 test.AssertEquals(t, authz.Wildcard, true)
3336
3337
3338 authz.ID = "12345"
3339 wfe.prepAuthorizationForDisplay(&http.Request{Host: "localhost"}, authz)
3340 chal := authz.Challenges[0]
3341 test.AssertEquals(t, chal.URL, "http://localhost/acme/chall-v3/12345/po1V2w")
3342 test.AssertEquals(t, chal.URI, "")
3343 test.AssertEquals(t, chal.ProvidedKeyAuthorization, "")
3344 }
3345
3346
3347 type noSCTMockRA struct {
3348 MockRegistrationAuthority
3349 }
3350
3351 func (ra *noSCTMockRA) FinalizeOrder(context.Context, *rapb.FinalizeOrderRequest, ...grpc.CallOption) (*corepb.Order, error) {
3352 return nil, berrors.MissingSCTsError("noSCTMockRA missing scts error")
3353 }
3354
3355 func TestFinalizeSCTError(t *testing.T) {
3356 wfe, _, signer := setupWFE(t)
3357
3358
3359
3360 wfe.ra = &noSCTMockRA{}
3361
3362
3363 responseWriter := httptest.NewRecorder()
3364
3365
3366
3367
3368 goodCertCSRPayload := `{
3369 "csr": "MIICYjCCAUoCAQAwHTEbMBkGA1UEAwwSbm90LWFuLWV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmqs7nue5oFxKBk2WaFZJAma2nm1oFyPIq19gYEAdQN4mWvaJ8RjzHFkDMYUrlIrGxCYuFJDHFUk9dh19Na1MIY-NVLgcSbyNcOML3bLbLEwGmvXPbbEOflBA9mxUS9TLMgXW5ghf_qbt4vmSGKloIim41QXt55QFW6O-84s8Kd2OE6df0wTsEwLhZB3j5pDU-t7j5vTMv4Tc7EptaPkOdfQn-68viUJjlYM_4yIBVRhWCdexFdylCKVLg0obsghQEwULKYCUjdg6F0VJUI115DU49tzscXU_3FS3CyY8rchunuYszBNkdmgpAwViHNWuP7ESdEd_emrj1xuioSe6PwIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAE_T1nWU38XVYL28hNVSXU0rW5IBUKtbvr0qAkD4kda4HmQRTYkt-LNSuvxoZCC9lxijjgtJi-OJe_DCTdZZpYzewlVvcKToWSYHYQ6Wm1-fxxD_XzphvZOujpmBySchdiz7QSVWJmVZu34XD5RJbIcrmj_cjRt42J1hiTFjNMzQu9U6_HwIMmliDL-soFY2RTvvZf-dAFvOUQ-Wbxt97eM1PbbmxJNWRhbAmgEpe9PWDPTpqV5AK56VAa991cQ1P8ZVmPss5hvwGWhOtpnpTZVHN3toGNYFKqxWPboirqushQlfKiFqT9rpRgM3-mFjOHidGqsKEkTdmfSVlVEk3oo="
3370 }`
3371
3372
3373 request := signAndPost(signer, "1/8", "http://localhost/1/8", goodCertCSRPayload)
3374
3375
3376 wfe.FinalizeOrder(ctx, newRequestEvent(), responseWriter, request)
3377
3378
3379
3380 test.AssertUnmarshaledEquals(t,
3381 responseWriter.Body.String(),
3382 `{"type":"`+probs.ErrorNS+`serverInternal","detail":"Error finalizing order :: Unable to meet CA SCT embedding requirements","status":500}`)
3383 }
3384
3385 func TestOrderToOrderJSONV2Authorizations(t *testing.T) {
3386 wfe, fc, _ := setupWFE(t)
3387 expires := fc.Now()
3388 orderJSON := wfe.orderToOrderJSON(&http.Request{}, &corepb.Order{
3389 Id: 1,
3390 RegistrationID: 1,
3391 Names: []string{"a"},
3392 Status: string(core.StatusPending),
3393 ExpiresNS: expires.UnixNano(),
3394 Expires: timestamppb.New(expires),
3395 V2Authorizations: []int64{1, 2},
3396 })
3397 test.AssertDeepEquals(t, orderJSON.Authorizations, []string{
3398 "http://localhost/acme/authz-v3/1",
3399 "http://localhost/acme/authz-v3/2",
3400 })
3401 }
3402
3403 func TestGetChallengeUpRel(t *testing.T) {
3404 wfe, _, _ := setupWFE(t)
3405
3406 challengeURL := "http://localhost/acme/chall-v3/1/-ZfxEw"
3407 resp := httptest.NewRecorder()
3408
3409 req, err := http.NewRequest("GET", challengeURL, nil)
3410 test.AssertNotError(t, err, "Could not make NewRequest")
3411 req.URL.Path = "1/-ZfxEw"
3412
3413 wfe.Challenge(ctx, newRequestEvent(), resp, req)
3414 test.AssertEquals(t,
3415 resp.Code,
3416 http.StatusOK)
3417 test.AssertEquals(t,
3418 resp.Header().Get("Link"),
3419 `<http://localhost/acme/authz-v3/1>;rel="up"`)
3420 }
3421
3422 func TestPrepAccountForDisplay(t *testing.T) {
3423 acct := &core.Registration{
3424 ID: 1987,
3425 Agreement: "disagreement",
3426 }
3427
3428
3429 prepAccountForDisplay(acct)
3430
3431
3432 test.AssertEquals(t, acct.Agreement, "")
3433
3434 test.AssertEquals(t, acct.ID, int64(0))
3435 }
3436
3437 func TestGETAPIAuthz(t *testing.T) {
3438 wfe, _, _ := setupWFE(t)
3439 makeGet := func(path, endpoint string) (*http.Request, *web.RequestEvent) {
3440 return &http.Request{URL: &url.URL{Path: path}, Method: "GET"},
3441 &web.RequestEvent{Endpoint: endpoint}
3442 }
3443
3444 testCases := []struct {
3445 name string
3446 path string
3447 expectTooFreshErr bool
3448 }{
3449 {
3450 name: "fresh authz",
3451 path: "1",
3452 expectTooFreshErr: true,
3453 },
3454 {
3455 name: "old authz",
3456 path: "2",
3457 expectTooFreshErr: false,
3458 },
3459 }
3460
3461 tooFreshErr := `{"type":"` + probs.ErrorNS + `unauthorized","detail":"Authorization is too new for GET API. You should only use this non-standard API to access resources created more than 10s ago","status":403}`
3462 for _, tc := range testCases {
3463 responseWriter := httptest.NewRecorder()
3464 req, logEvent := makeGet(tc.path, getAuthzPath)
3465 wfe.Authorization(context.Background(), logEvent, responseWriter, req)
3466
3467 if responseWriter.Code == http.StatusOK && tc.expectTooFreshErr {
3468 t.Errorf("expected too fresh error, got http.StatusOK")
3469 } else {
3470 test.AssertEquals(t, responseWriter.Code, http.StatusForbidden)
3471 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tooFreshErr)
3472 }
3473 }
3474 }
3475
3476 func TestGETAPIChallenge(t *testing.T) {
3477 wfe, _, _ := setupWFE(t)
3478 makeGet := func(path, endpoint string) (*http.Request, *web.RequestEvent) {
3479 return &http.Request{URL: &url.URL{Path: path}, Method: "GET"},
3480 &web.RequestEvent{Endpoint: endpoint}
3481 }
3482
3483 testCases := []struct {
3484 name string
3485 path string
3486 expectTooFreshErr bool
3487 }{
3488 {
3489 name: "fresh authz challenge",
3490 path: "1/-ZfxEw",
3491 expectTooFreshErr: true,
3492 },
3493 {
3494 name: "old authz challenge",
3495 path: "2/-ZfxEw",
3496 expectTooFreshErr: false,
3497 },
3498 }
3499
3500 tooFreshErr := `{"type":"` + probs.ErrorNS + `unauthorized","detail":"Authorization is too new for GET API. You should only use this non-standard API to access resources created more than 10s ago","status":403}`
3501 for _, tc := range testCases {
3502 responseWriter := httptest.NewRecorder()
3503 req, logEvent := makeGet(tc.path, getAuthzPath)
3504 wfe.Challenge(context.Background(), logEvent, responseWriter, req)
3505
3506 if responseWriter.Code == http.StatusOK && tc.expectTooFreshErr {
3507 t.Errorf("expected too fresh error, got http.StatusOK")
3508 } else {
3509 test.AssertEquals(t, responseWriter.Code, http.StatusForbidden)
3510 test.AssertUnmarshaledEquals(t, responseWriter.Body.String(), tooFreshErr)
3511 }
3512 }
3513 }
3514
3515
3516
3517
3518
3519 func TestIndexGet404(t *testing.T) {
3520
3521 wfe, _, _ := setupWFE(t)
3522 path := "/nopathhere/nope/nofilehere"
3523 req := &http.Request{URL: &url.URL{Path: path}, Method: "GET"}
3524 logEvent := &web.RequestEvent{}
3525 responseWriter := httptest.NewRecorder()
3526
3527
3528 wfe.Index(context.Background(), logEvent, responseWriter, req)
3529
3530
3531 test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
3532
3533 test.AssertEquals(t, logEvent.Endpoint, "/")
3534
3535 test.AssertEquals(t, logEvent.Slug, path[1:])
3536 }
3537
3538
3539
3540 func TestARI(t *testing.T) {
3541 wfe, _, _ := setupWFE(t)
3542 msa := newMockSAWithCert(t, wfe.sa)
3543 wfe.sa = msa
3544
3545 err := features.Set(map[string]bool{"ServeRenewalInfo": true})
3546 test.AssertNotError(t, err, "setting feature flag")
3547 defer features.Reset()
3548
3549 makeGet := func(path, endpoint string) (*http.Request, *web.RequestEvent) {
3550 return &http.Request{URL: &url.URL{Path: path}, Method: "GET"},
3551 &web.RequestEvent{Endpoint: endpoint, Extra: map[string]interface{}{}}
3552 }
3553
3554
3555 cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
3556 test.AssertNotError(t, err, "failed to load test certificate")
3557 issuer, err := core.LoadCert("../test/hierarchy/int-r3.cert.pem")
3558 test.AssertNotError(t, err, "failed to load test issuer")
3559
3560
3561 ocspReqBytes, err := ocsp.CreateRequest(cert, issuer, &ocsp.RequestOptions{Hash: crypto.SHA256})
3562 test.AssertNotError(t, err, "failed to create ocsp request")
3563 ocspReq, err := ocsp.ParseRequest(ocspReqBytes)
3564 test.AssertNotError(t, err, "failed to parse ocsp request")
3565
3566
3567 idBytes, err := asn1.Marshal(certID{
3568 pkix.AlgorithmIdentifier{
3569 Algorithm: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1},
3570 Parameters: asn1.RawValue{Tag: 5 },
3571 },
3572 ocspReq.IssuerNameHash,
3573 ocspReq.IssuerKeyHash,
3574 cert.SerialNumber,
3575 })
3576 test.AssertNotError(t, err, "failed to marshal certID")
3577 req, event := makeGet(base64.RawURLEncoding.EncodeToString(idBytes), renewalInfoPath)
3578 resp := httptest.NewRecorder()
3579 wfe.RenewalInfo(context.Background(), event, resp, req)
3580 test.AssertEquals(t, resp.Code, http.StatusOK)
3581 test.AssertEquals(t, resp.Header().Get("Retry-After"), "21600")
3582 var ri core.RenewalInfo
3583 err = json.Unmarshal(resp.Body.Bytes(), &ri)
3584 test.AssertNotError(t, err, "unmarshalling renewal info")
3585 test.Assert(t, ri.SuggestedWindow.Start.After(cert.NotBefore), "suggested window begins before cert issuance")
3586 test.Assert(t, ri.SuggestedWindow.End.Before(cert.NotAfter), "suggested window ends after cert expiry")
3587
3588
3589
3590 msa.status = core.OCSPStatusRevoked
3591 req, event = makeGet(base64.RawURLEncoding.EncodeToString(idBytes), renewalInfoPath)
3592 resp = httptest.NewRecorder()
3593 wfe.RenewalInfo(context.Background(), event, resp, req)
3594 test.AssertEquals(t, resp.Code, http.StatusOK)
3595 test.AssertEquals(t, resp.Header().Get("Retry-After"), "21600")
3596 err = json.Unmarshal(resp.Body.Bytes(), &ri)
3597 test.AssertNotError(t, err, "unmarshalling renewal info")
3598 test.Assert(t, ri.SuggestedWindow.End.Before(wfe.clk.Now()), "suggested window should end in the past")
3599 test.Assert(t, ri.SuggestedWindow.Start.Before(ri.SuggestedWindow.End), "suggested window should start before it ends")
3600
3601
3602 idBytes, err = asn1.Marshal(certID{
3603 pkix.AlgorithmIdentifier{
3604 Algorithm: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1},
3605 Parameters: asn1.RawValue{Tag: 5 },
3606 },
3607 ocspReq.IssuerNameHash,
3608 ocspReq.IssuerKeyHash,
3609 big.NewInt(0).Add(cert.SerialNumber, big.NewInt(1)),
3610 })
3611 test.AssertNotError(t, err, "failed to marshal certID")
3612 req, event = makeGet(base64.RawURLEncoding.EncodeToString(idBytes), renewalInfoPath)
3613 resp = httptest.NewRecorder()
3614 wfe.RenewalInfo(context.Background(), event, resp, req)
3615 test.AssertEquals(t, resp.Code, http.StatusNotFound)
3616 test.AssertEquals(t, resp.Header().Get("Retry-After"), "")
3617
3618
3619 idBytes, err = asn1.Marshal(certID{
3620 pkix.AlgorithmIdentifier{
3621 Algorithm: asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26},
3622 Parameters: asn1.RawValue{Tag: 5 },
3623 },
3624 ocspReq.IssuerNameHash,
3625 ocspReq.IssuerKeyHash,
3626 big.NewInt(0).Add(cert.SerialNumber, big.NewInt(1)),
3627 })
3628 test.AssertNotError(t, err, "failed to marshal certID")
3629 req, event = makeGet(base64.RawURLEncoding.EncodeToString(idBytes), renewalInfoPath)
3630 resp = httptest.NewRecorder()
3631 wfe.RenewalInfo(context.Background(), event, resp, req)
3632 test.AssertEquals(t, resp.Code, http.StatusBadRequest)
3633 test.AssertContains(t, resp.Body.String(), "Request used hash algorithm other than SHA-256")
3634
3635
3636 req, event = makeGet(base64.RawURLEncoding.EncodeToString(ocspReqBytes), renewalInfoPath)
3637 resp = httptest.NewRecorder()
3638 wfe.RenewalInfo(context.Background(), event, resp, req)
3639 test.AssertEquals(t, resp.Code, http.StatusBadRequest)
3640 test.AssertContains(t, resp.Body.String(), "Path was not a DER-encoded CertID sequence")
3641
3642
3643
3644 req, event = makeGet(
3645 fmt.Sprintf(
3646 "%s/%s/%s",
3647 base64.RawURLEncoding.EncodeToString(ocspReq.IssuerNameHash),
3648 base64.RawURLEncoding.EncodeToString(ocspReq.IssuerKeyHash),
3649 base64.RawURLEncoding.EncodeToString(cert.SerialNumber.Bytes()),
3650 ),
3651 renewalInfoPath)
3652 resp = httptest.NewRecorder()
3653 wfe.RenewalInfo(context.Background(), event, resp, req)
3654 test.AssertEquals(t, resp.Code, http.StatusBadRequest)
3655 test.AssertContains(t, resp.Body.String(), "Path was not base64url-encoded")
3656
3657
3658 req, event = makeGet("", renewalInfoPath)
3659 resp = httptest.NewRecorder()
3660 wfe.RenewalInfo(context.Background(), event, resp, req)
3661 test.AssertEquals(t, resp.Code, http.StatusNotFound)
3662 test.AssertContains(t, resp.Body.String(), "Must specify a request path")
3663 }
3664
3665
3666
3667
3668 func TestIncidentARI(t *testing.T) {
3669 wfe, _, _ := setupWFE(t)
3670 expectSerial := big.NewInt(12345)
3671 expectSerialString := core.SerialToString(big.NewInt(12345))
3672 wfe.sa = newMockSAWithIncident(wfe.sa, []string{expectSerialString})
3673
3674 err := features.Set(map[string]bool{"ServeRenewalInfo": true})
3675 test.AssertNotError(t, err, "setting feature flag")
3676 defer features.Reset()
3677
3678 makeGet := func(path, endpoint string) (*http.Request, *web.RequestEvent) {
3679 return &http.Request{URL: &url.URL{Path: path}, Method: "GET"},
3680 &web.RequestEvent{Endpoint: endpoint, Extra: map[string]interface{}{}}
3681 }
3682
3683 idBytes, err := asn1.Marshal(certID{
3684 pkix.AlgorithmIdentifier{
3685 Algorithm: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1},
3686 Parameters: asn1.RawValue{Tag: 5 },
3687 },
3688 []byte("foo"),
3689 []byte("baz"),
3690 expectSerial,
3691 })
3692 test.AssertNotError(t, err, "failed to marshal certID")
3693
3694 req, event := makeGet(base64.RawURLEncoding.EncodeToString(idBytes), renewalInfoPath)
3695 resp := httptest.NewRecorder()
3696 wfe.RenewalInfo(context.Background(), event, resp, req)
3697 test.AssertEquals(t, resp.Code, 200)
3698 test.AssertEquals(t, resp.Header().Get("Retry-After"), "21600")
3699 var ri core.RenewalInfo
3700 err = json.Unmarshal(resp.Body.Bytes(), &ri)
3701 test.AssertNotError(t, err, "unmarshalling renewal info")
3702
3703 test.AssertEquals(t, ri.SuggestedWindow.Start.Before(wfe.clk.Now()), true)
3704
3705 test.AssertEquals(t, ri.SuggestedWindow.End.After(ri.SuggestedWindow.Start), true)
3706
3707 test.AssertEquals(t, ri.SuggestedWindow.End.Before(wfe.clk.Now()), true)
3708 }
3709
3710 type mockSAWithSerialMetadata struct {
3711 sapb.StorageAuthorityReadOnlyClient
3712 serial string
3713 regID int64
3714 }
3715
3716
3717 func (sa *mockSAWithSerialMetadata) GetSerialMetadata(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*sapb.SerialMetadata, error) {
3718 if req.Serial != sa.serial {
3719 return nil, berrors.NotFoundError("metadata for certificate with serial %q not found", req.Serial)
3720 }
3721
3722 return &sapb.SerialMetadata{
3723 Serial: sa.serial,
3724 RegistrationID: sa.regID,
3725 }, nil
3726 }
3727
3728
3729
3730 func TestUpdateARI(t *testing.T) {
3731 wfe, _, signer := setupWFE(t)
3732
3733 err := features.Set(map[string]bool{"ServeRenewalInfo": true})
3734 test.AssertNotError(t, err, "setting feature flag")
3735 defer features.Reset()
3736
3737 makePost := func(regID int64, body string) *http.Request {
3738 signedURL := fmt.Sprintf("http://localhost%s", renewalInfoPath)
3739 _, _, jwsBody := signer.byKeyID(regID, nil, signedURL, body)
3740 return makePostRequestWithPath(renewalInfoPath, jwsBody)
3741 }
3742
3743 type jsonReq struct {
3744 CertID string `json:"certID"`
3745 Replaced bool `json:"replaced"`
3746 }
3747
3748
3749 cert, err := core.LoadCert("../test/hierarchy/ee-r3.cert.pem")
3750 test.AssertNotError(t, err, "failed to load test certificate")
3751 issuer, err := core.LoadCert("../test/hierarchy/int-r3.cert.pem")
3752 test.AssertNotError(t, err, "failed to load test issuer")
3753 ocspReqBytes, err := ocsp.CreateRequest(cert, issuer, &ocsp.RequestOptions{Hash: crypto.SHA256})
3754 test.AssertNotError(t, err, "failed to create ocsp request")
3755 ocspReq, err := ocsp.ParseRequest(ocspReqBytes)
3756 test.AssertNotError(t, err, "failed to parse ocsp request")
3757
3758
3759 msa := mockSAWithSerialMetadata{wfe.sa, core.SerialToString(cert.SerialNumber), 1}
3760 wfe.sa = &msa
3761
3762
3763 req := makePost(1, "")
3764 responseWriter := httptest.NewRecorder()
3765 wfe.UpdateRenewal(ctx, newRequestEvent(), responseWriter, req)
3766 test.AssertEquals(t, responseWriter.Code, http.StatusBadRequest)
3767
3768
3769 req = makePost(1, "aGVsbG8gd29ybGQK")
3770 responseWriter = httptest.NewRecorder()
3771 wfe.UpdateRenewal(ctx, newRequestEvent(), responseWriter, req)
3772 test.AssertEquals(t, responseWriter.Code, http.StatusBadRequest)
3773
3774
3775 idBytes, err := asn1.Marshal(certID{
3776 pkix.AlgorithmIdentifier{
3777 Algorithm: asn1.ObjectIdentifier{1, 2, 3, 4, 5},
3778 Parameters: asn1.RawValue{Tag: 5 },
3779 },
3780 ocspReq.IssuerNameHash,
3781 ocspReq.IssuerKeyHash,
3782 cert.SerialNumber,
3783 })
3784 test.AssertNotError(t, err, "failed to marshal certID")
3785 body, err := json.Marshal(jsonReq{
3786 CertID: base64.RawURLEncoding.EncodeToString(idBytes),
3787 Replaced: true,
3788 })
3789 test.AssertNotError(t, err, "failed to marshal request body")
3790 req = makePost(1, string(body))
3791 responseWriter = httptest.NewRecorder()
3792 wfe.UpdateRenewal(ctx, newRequestEvent(), responseWriter, req)
3793 test.AssertEquals(t, responseWriter.Code, http.StatusBadRequest)
3794
3795
3796 idBytes, err = asn1.Marshal(certID{
3797 pkix.AlgorithmIdentifier{
3798 Algorithm: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1},
3799 Parameters: asn1.RawValue{Tag: 5 },
3800 },
3801 ocspReq.IssuerNameHash,
3802 ocspReq.IssuerKeyHash,
3803 big.NewInt(12345),
3804 })
3805 test.AssertNotError(t, err, "failed to marshal certID")
3806 body, err = json.Marshal(jsonReq{
3807 CertID: base64.RawURLEncoding.EncodeToString(idBytes),
3808 Replaced: true,
3809 })
3810 test.AssertNotError(t, err, "failed to marshal request body")
3811 req = makePost(1, string(body))
3812 responseWriter = httptest.NewRecorder()
3813 wfe.UpdateRenewal(ctx, newRequestEvent(), responseWriter, req)
3814 test.AssertEquals(t, responseWriter.Code, http.StatusNotFound)
3815
3816
3817 msa.regID = 2
3818 idBytes, err = asn1.Marshal(certID{
3819 pkix.AlgorithmIdentifier{
3820 Algorithm: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1},
3821 Parameters: asn1.RawValue{Tag: 5 },
3822 },
3823 ocspReq.IssuerNameHash,
3824 ocspReq.IssuerKeyHash,
3825 cert.SerialNumber,
3826 })
3827 test.AssertNotError(t, err, "failed to marshal certID")
3828 body, err = json.Marshal(jsonReq{
3829 CertID: base64.RawURLEncoding.EncodeToString(idBytes),
3830 Replaced: true,
3831 })
3832 test.AssertNotError(t, err, "failed to marshal request body")
3833 req = makePost(1, string(body))
3834 responseWriter = httptest.NewRecorder()
3835 wfe.UpdateRenewal(ctx, newRequestEvent(), responseWriter, req)
3836 test.AssertEquals(t, responseWriter.Code, http.StatusForbidden)
3837
3838
3839 msa.regID = 1
3840 idBytes, err = asn1.Marshal(certID{
3841 pkix.AlgorithmIdentifier{
3842 Algorithm: asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1},
3843 Parameters: asn1.RawValue{Tag: 5 },
3844 },
3845 ocspReq.IssuerNameHash,
3846 ocspReq.IssuerKeyHash,
3847 cert.SerialNumber,
3848 })
3849 test.AssertNotError(t, err, "failed to marshal certID")
3850 body, err = json.Marshal(jsonReq{
3851 CertID: base64.RawURLEncoding.EncodeToString(idBytes),
3852 Replaced: true,
3853 })
3854 test.AssertNotError(t, err, "failed to marshal request body")
3855 req = makePost(1, string(body))
3856 responseWriter = httptest.NewRecorder()
3857 wfe.UpdateRenewal(ctx, newRequestEvent(), responseWriter, req)
3858 test.AssertEquals(t, responseWriter.Code, http.StatusOK)
3859 }
3860
3861 func TestOldTLSInbound(t *testing.T) {
3862 wfe, _, _ := setupWFE(t)
3863 req := &http.Request{
3864 URL: &url.URL{Path: "/directory"},
3865 Method: "GET",
3866 Header: http.Header(map[string][]string{
3867 http.CanonicalHeaderKey("TLS-Version"): {"TLSv1"},
3868 }),
3869 }
3870
3871 responseWriter := httptest.NewRecorder()
3872 wfe.Handler(metrics.NoopRegisterer).ServeHTTP(responseWriter, req)
3873 test.AssertEquals(t, responseWriter.Code, http.StatusBadRequest)
3874 }
3875
3876 func Test_sendError(t *testing.T) {
3877 features.Reset()
3878 wfe, _, _ := setupWFE(t)
3879 testResponse := httptest.NewRecorder()
3880
3881 testErr := berrors.RateLimitError(0, "test")
3882 wfe.sendError(testResponse, &web.RequestEvent{Endpoint: "test"}, probs.RateLimited("test"), testErr)
3883
3884 test.AssertEquals(t, testResponse.Header().Get("Retry-After"), "")
3885
3886 test.AssertEquals(t, testResponse.Header().Get("Link"), "")
3887
3888 testErr = berrors.RateLimitError(time.Millisecond*500, "test")
3889 wfe.sendError(testResponse, &web.RequestEvent{Endpoint: "test"}, probs.RateLimited("test"), testErr)
3890
3891 test.AssertEquals(t, testResponse.Header().Get("Retry-After"), "1")
3892
3893 test.AssertEquals(t, testResponse.Header().Get("Link"), "<https://letsencrypt.org/docs/rate-limits>;rel=\"help\"")
3894
3895
3896 testResponse.Header().Del("Retry-After")
3897 testResponse.Header().Del("Link")
3898
3899 testErr = berrors.RateLimitError(time.Millisecond*499, "test")
3900 wfe.sendError(testResponse, &web.RequestEvent{Endpoint: "test"}, probs.RateLimited("test"), testErr)
3901
3902 test.AssertEquals(t, testResponse.Header().Get("Retry-After"), "")
3903
3904 test.AssertEquals(t, testResponse.Header().Get("Link"), "")
3905 }
3906
View as plain text