1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package ctfe
16
17 import (
18 "bufio"
19 "bytes"
20 "context"
21 "crypto"
22 "crypto/sha256"
23 "encoding/hex"
24 "encoding/json"
25 "encoding/pem"
26 "errors"
27 "fmt"
28 "io"
29 "net/http"
30 "net/http/httptest"
31 "strings"
32 "testing"
33 "time"
34
35 "github.com/golang/mock/gomock"
36 "github.com/google/certificate-transparency-go/tls"
37 "github.com/google/certificate-transparency-go/trillian/mockclient"
38 "github.com/google/certificate-transparency-go/trillian/testdata"
39 "github.com/google/certificate-transparency-go/trillian/util"
40 "github.com/google/certificate-transparency-go/x509"
41 "github.com/google/certificate-transparency-go/x509util"
42 "github.com/google/go-cmp/cmp"
43 "github.com/google/go-cmp/cmp/cmpopts"
44 "github.com/google/trillian"
45 "github.com/google/trillian/monitoring"
46 "github.com/google/trillian/types"
47 "github.com/kylelemons/godebug/pretty"
48 "google.golang.org/grpc/codes"
49 "google.golang.org/grpc/status"
50 "google.golang.org/protobuf/proto"
51 "k8s.io/klog/v2"
52
53 ct "github.com/google/certificate-transparency-go"
54 "github.com/google/certificate-transparency-go/trillian/ctfe/configpb"
55 cttestonly "github.com/google/certificate-transparency-go/trillian/ctfe/testonly"
56 )
57
58
59 var fakeTime = time.Date(2016, 7, 22, 11, 01, 13, 0, time.UTC)
60 var fakeTimeMillis = uint64(fakeTime.UnixNano() / millisPerNano)
61
62
63 var fakeDeadlineTime = time.Date(2016, 7, 22, 11, 01, 13, 500*1000*1000, time.UTC)
64 var fakeTimeSource = util.NewFixedTimeSource(fakeTime)
65
66 const caCertB64 string = `MIIC0DCCAjmgAwIBAgIBADANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk
67 MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX
68 YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw
69 MDAwMDBaMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu
70 c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGf
71 MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVimhTYhCicRmTbneDIRgcKkATxtB7
72 jHbrkVfT0PtLO1FuzsvRyY2RxS90P6tjXVUJnNE6uvMa5UFEJFGnTHgW8iQ8+EjP
73 KDHM5nugSlojgZ88ujfmJNnDvbKZuDnd/iYx0ss6hPx7srXFL8/BT/9Ab1zURmnL
74 svfP34b7arnRsQIDAQABo4GvMIGsMB0GA1UdDgQWBBRfnYgNyHPmVNT4DdjmsMEk
75 tEfDVTB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkG
76 A1UEBhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEO
77 MAwGA1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwDAYDVR0TBAUwAwEB
78 /zANBgkqhkiG9w0BAQUFAAOBgQAGCMxKbWTyIF4UbASydvkrDvqUpdryOvw4BmBt
79 OZDQoeojPUApV2lGOwRmYef6HReZFSCa6i4Kd1F2QRIn18ADB8dHDmFYT9czQiRy
80 f1HWkLxHqd81TbD26yWVXeGJPE3VICskovPkQNJ0tU4b03YmnKliibduyqQQkOFP
81 OwqULg==`
82
83 const intermediateCertB64 string = `MIIC3TCCAkagAwIBAgIBCTANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk
84 MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX
85 YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw
86 MDAwMDBaMGIxCzAJBgNVBAYTAkdCMTEwLwYDVQQKEyhDZXJ0aWZpY2F0ZSBUcmFu
87 c3BhcmVuY3kgSW50ZXJtZWRpYXRlIENBMQ4wDAYDVQQIEwVXYWxlczEQMA4GA1UE
88 BxMHRXJ3IFdlbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA12pnjRFvUi5V
89 /4IckGQlCLcHSxTXcRWQZPeSfv3tuHE1oTZe594Yy9XOhl+GDHj0M7TQ09NAdwLn
90 o+9UKx3+m7qnzflNxZdfxyn4bxBfOBskNTXPnIAPXKeAwdPIRADuZdFu6c9S24rf
91 /lD1xJM1CyGQv1DVvDbzysWo2q6SzYsCAwEAAaOBrzCBrDAdBgNVHQ4EFgQUllUI
92 BQJ4R56Hc3ZBMbwUOkfiKaswfQYDVR0jBHYwdIAUX52IDchz5lTU+A3Y5rDBJLRH
93 w1WhWaRXMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu
94 c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuggEA
95 MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAIgbascZrcdzglcP2qi73
96 LPd2G+er1/w5wxpM/hvZbWc0yoLyLd5aDIu73YJde28+dhKtjbMAp+IRaYhgIyYi
97 hMOqXSGR79oQv5I103s6KjQNWUGblKSFZvP6w82LU9Wk6YJw6tKXsHIQ+c5KITix
98 iBEUO5P6TnqH3TfhOF8sKQg=`
99
100 const caAndIntermediateCertsPEM = "-----BEGIN CERTIFICATE-----\n" +
101 caCertB64 +
102 "\n-----END CERTIFICATE-----\n" +
103 "\n-----BEGIN CERTIFICATE-----\n" +
104 intermediateCertB64 +
105 "\n-----END CERTIFICATE-----\n"
106
107 const remoteQuotaUser = "Moneybags"
108
109 type handlerTestInfo struct {
110 mockCtrl *gomock.Controller
111 roots *x509util.PEMCertPool
112 client *mockclient.MockTrillianLogClient
113 li *logInfo
114 }
115
116 const certQuotaPrefix = "CERT:"
117
118 func quotaUserForCert(c *x509.Certificate) string {
119 return fmt.Sprintf("%s %s", certQuotaPrefix, c.Subject.String())
120 }
121
122 func quotaUsersForIssuers(t *testing.T, pem ...string) []string {
123 t.Helper()
124 r := make([]string, 0)
125 for _, p := range pem {
126 c, err := x509util.CertificateFromPEM([]byte(p))
127 if x509.IsFatal(err) {
128 t.Fatalf("Failed to parse pem: %v", err)
129 }
130 r = append(r, quotaUserForCert(c))
131 }
132 return r
133 }
134
135 func (info *handlerTestInfo) setRemoteQuotaUser(u string) {
136 if len(u) > 0 {
137 info.li.instanceOpts.RemoteQuotaUser = func(_ *http.Request) string { return u }
138 } else {
139 info.li.instanceOpts.RemoteQuotaUser = nil
140 }
141 }
142
143 func (info *handlerTestInfo) enableCertQuota(e bool) {
144 if e {
145 info.li.instanceOpts.CertificateQuotaUser = quotaUserForCert
146 } else {
147 info.li.instanceOpts.CertificateQuotaUser = nil
148 }
149 }
150
151
152 func setupTest(t *testing.T, pemRoots []string, signer crypto.Signer) handlerTestInfo {
153 t.Helper()
154 info := handlerTestInfo{
155 mockCtrl: gomock.NewController(t),
156 roots: x509util.NewPEMCertPool(),
157 }
158
159 info.client = mockclient.NewMockTrillianLogClient(info.mockCtrl)
160 vOpts := CertValidationOpts{
161 trustedRoots: info.roots,
162 rejectExpired: false,
163 }
164
165 cfg := &configpb.LogConfig{LogId: 0x42, Prefix: "test", IsMirror: false}
166 vCfg := &ValidatedLogConfig{Config: cfg}
167 iOpts := InstanceOptions{Validated: vCfg, Client: info.client, Deadline: time.Millisecond * 500, MetricFactory: monitoring.InertMetricFactory{}, RequestLog: new(DefaultRequestLog)}
168 info.li = newLogInfo(iOpts, vOpts, signer, fakeTimeSource)
169
170 for _, pemRoot := range pemRoots {
171 if !info.roots.AppendCertsFromPEM([]byte(pemRoot)) {
172 klog.Fatal("failed to load cert pool")
173 }
174 }
175
176 return info
177 }
178
179 func (info handlerTestInfo) getHandlers() map[string]AppHandler {
180 return map[string]AppHandler{
181 "get-sth": {Info: info.li, Handler: getSTH, Name: "GetSTH", Method: http.MethodGet},
182 "get-sth-consistency": {Info: info.li, Handler: getSTHConsistency, Name: "GetSTHConsistency", Method: http.MethodGet},
183 "get-proof-by-hash": {Info: info.li, Handler: getProofByHash, Name: "GetProofByHash", Method: http.MethodGet},
184 "get-entries": {Info: info.li, Handler: getEntries, Name: "GetEntries", Method: http.MethodGet},
185 "get-roots": {Info: info.li, Handler: getRoots, Name: "GetRoots", Method: http.MethodGet},
186 "get-entry-and-proof": {Info: info.li, Handler: getEntryAndProof, Name: "GetEntryAndProof", Method: http.MethodGet},
187 }
188 }
189
190 func (info handlerTestInfo) postHandlers() map[string]AppHandler {
191 return map[string]AppHandler{
192 "add-chain": {Info: info.li, Handler: addChain, Name: "AddChain", Method: http.MethodPost},
193 "add-pre-chain": {Info: info.li, Handler: addPreChain, Name: "AddPreChain", Method: http.MethodPost},
194 }
195 }
196
197 func TestPostHandlersRejectGet(t *testing.T) {
198 info := setupTest(t, []string{cttestonly.FakeCACertPEM}, nil)
199 defer info.mockCtrl.Finish()
200
201
202 for path, handler := range info.postHandlers() {
203 t.Run(path, func(t *testing.T) {
204 s := httptest.NewServer(handler)
205 defer s.Close()
206
207 resp, err := http.Get(s.URL + "/ct/v1/" + path)
208 if err != nil {
209 t.Fatalf("http.Get(%s)=(_,%q); want (_,nil)", path, err)
210 }
211 if got, want := resp.StatusCode, http.StatusMethodNotAllowed; got != want {
212 t.Errorf("http.Get(%s)=(%d,nil); want (%d,nil)", path, got, want)
213 }
214 })
215 }
216 }
217
218 func TestGetHandlersRejectPost(t *testing.T) {
219 info := setupTest(t, []string{cttestonly.FakeCACertPEM}, nil)
220 defer info.mockCtrl.Finish()
221
222
223 for path, handler := range info.getHandlers() {
224 t.Run(path, func(t *testing.T) {
225 s := httptest.NewServer(handler)
226 defer s.Close()
227
228 resp, err := http.Post(s.URL+"/ct/v1/"+path, "application/json", nil)
229 if err != nil {
230 t.Fatalf("http.Post(%s)=(_,%q); want (_,nil)", path, err)
231 }
232 if got, want := resp.StatusCode, http.StatusMethodNotAllowed; got != want {
233 t.Errorf("http.Post(%s)=(%d,nil); want (%d,nil)", path, got, want)
234 }
235 })
236 }
237 }
238
239 func TestPostHandlersFailure(t *testing.T) {
240 var tests = []struct {
241 descr string
242 body io.Reader
243 want int
244 }{
245 {"nil", nil, http.StatusBadRequest},
246 {"''", strings.NewReader(""), http.StatusBadRequest},
247 {"malformed-json", strings.NewReader("{ !$%^& not valid json "), http.StatusBadRequest},
248 {"empty-chain", strings.NewReader(`{ "chain": [] }`), http.StatusBadRequest},
249 {"wrong-chain", strings.NewReader(`{ "chain": [ "test" ] }`), http.StatusBadRequest},
250 }
251
252 info := setupTest(t, []string{cttestonly.FakeCACertPEM}, nil)
253 defer info.mockCtrl.Finish()
254 for path, handler := range info.postHandlers() {
255 t.Run(path, func(t *testing.T) {
256 s := httptest.NewServer(handler)
257
258 for _, test := range tests {
259 resp, err := http.Post(s.URL+"/ct/v1/"+path, "application/json", test.body)
260 if err != nil {
261 t.Errorf("http.Post(%s,%s)=(_,%q); want (_,nil)", path, test.descr, err)
262 continue
263 }
264 if resp.StatusCode != test.want {
265 t.Errorf("http.Post(%s,%s)=(%d,nil); want (%d,nil)", path, test.descr, resp.StatusCode, test.want)
266 }
267 }
268 })
269 }
270 }
271
272 func TestHandlers(t *testing.T) {
273 path := "/test-prefix/ct/v1/add-chain"
274 info := setupTest(t, nil, nil)
275 defer info.mockCtrl.Finish()
276 for _, test := range []string{
277 "/test-prefix/",
278 "test-prefix/",
279 "/test-prefix",
280 "test-prefix",
281 } {
282 t.Run(test, func(t *testing.T) {
283 handlers := info.li.Handlers(test)
284 if h, ok := handlers[path]; !ok {
285 t.Errorf("Handlers(%s)[%q]=%+v; want _", test, path, h)
286 } else if h.Name != "AddChain" {
287 t.Errorf("Handlers(%s)[%q].Name=%q; want 'AddChain'", test, path, h.Name)
288 }
289
290 if got, want := len(handlers), len(Entrypoints); got != want {
291 t.Fatalf("len(Handlers(%s))=%d; want %d", test, got, want)
292 }
293
294
295 var hNames []EntrypointName
296 for _, v := range handlers {
297 hNames = append(hNames, v.Name)
298 }
299
300 if !cmp.Equal(Entrypoints, hNames, cmpopts.SortSlices(func(n1, n2 EntrypointName) bool {
301 return n1 < n2
302 })) {
303 t.Errorf("Handler names mismatch got: %v, want: %v", hNames, Entrypoints)
304 }
305 })
306 }
307 }
308
309 func TestGetRoots(t *testing.T) {
310 info := setupTest(t, []string{caAndIntermediateCertsPEM}, nil)
311 defer info.mockCtrl.Finish()
312 handler := AppHandler{Info: info.li, Handler: getRoots, Name: "GetRoots", Method: http.MethodGet}
313
314 req, err := http.NewRequest("GET", "http://example.com/ct/v1/get-roots", nil)
315 if err != nil {
316 t.Fatalf("Failed to create request: %v", err)
317 }
318 w := httptest.NewRecorder()
319 handler.ServeHTTP(w, req)
320 if got, want := w.Code, http.StatusOK; got != want {
321 t.Fatalf("http.Get(get-roots)=%d; want %d", got, want)
322 }
323
324 var parsedJSON map[string][]string
325 if err := json.Unmarshal(w.Body.Bytes(), &parsedJSON); err != nil {
326 t.Fatalf("json.Unmarshal(%q)=%q; want nil", w.Body.Bytes(), err)
327 }
328 if got := len(parsedJSON); got != 1 {
329 t.Errorf("len(json)=%d; want 1", got)
330 }
331 certs := parsedJSON[jsonMapKeyCertificates]
332 if got := len(certs); got != 2 {
333 t.Fatalf("len(%q)=%d; want 2", certs, got)
334 }
335 if got, want := certs[0], strings.Replace(caCertB64, "\n", "", -1); got != want {
336 t.Errorf("certs[0]=%s; want %s", got, want)
337 }
338 if got, want := certs[1], strings.Replace(intermediateCertB64, "\n", "", -1); got != want {
339 t.Errorf("certs[1]=%s; want %s", got, want)
340 }
341 }
342
343 func TestAddChainWhitespace(t *testing.T) {
344 signer, err := setupSigner(fakeSignature)
345 if err != nil {
346 t.Fatalf("Failed to create test signer: %v", err)
347 }
348
349 info := setupTest(t, []string{cttestonly.FakeCACertPEM}, signer)
350 defer info.mockCtrl.Finish()
351
352
353 pemChain := []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM}
354
355
356 intro := "{\"chain\""
357
358 chunk1a := "[\"MIIH6DCCBtCgAwIBAgIIQoIqW4Zvv+swDQYJKoZIhvcNAQELBQAwcjELMAkGA1UE"
359
360 chunk1b := "BhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYDVQQKDAZHb29nbGUxDDAKBgNVBAsMA0VuZzEiMCAGA1UEAwwZRmFrZUludGVybWVkaWF0ZUF1dGhvcml0eTAeFw0xNjA1MTMxNDI2NDRaFw0xOTA3MTIxNDI2NDRaMIIBWDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEzARBgNVBAoMCkdvb2dsZSBJbmMxFTATBgNVBAMMDCouZ29vZ2xlLmNvbTGBwzCBwAYDVQQEDIG4UkZDNTI4MCBzNC4yLjEuOSAnVGhlIHBhdGhMZW5Db25zdHJhaW50IGZpZWxkIC4uLiBnaXZlcyB0aGUgbWF4aW11bSBudW1iZXIgb2Ygbm9uLXNlbGYtaXNzdWVkIGludGVybWVkaWF0ZSBjZXJ0aWZpY2F0ZXMgdGhhdCBtYXkgZm9sbG93IHRoaXMgY2VydGlmaWNhdGUgaW4gYSB2YWxpZCBjZXJ0aWZpY2F0aW9uIHBhdGguJzEqMCgGA1UEKgwhSW50ZXJtZWRpYXRlIENBIGNlcnQgdXNlZCB0byBzaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExAk5hPUVjRJUsgKc+QHibTVH1A3QEWFmCTUdyxIUlbI//zW9Io5N/DhQLSLWmB7KoCOvpJZ+MtGCXzFX+yj/N6OCBGMwggRfMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjCCA0IGA1UdEQSCAzkwggM1ggwqLmdvb2dsZS5jb22CDSouYW5kcm9pZC5jb22CFiouYXBwZW5naW5lLmdvb2dsZS5jb22CEiouY2xvdWQuZ29vZ2xlLmNvbYIWKi5nb29nbGUtYW5hbHl0aWNzLmNvbYILKi5nb29nbGUuY2GCCyouZ29vZ2xlLmNsgg4qLmdvb2dsZS5jby5pboIOKi5nb29nbGUuY28uanCCDiouZ29vZ2xlLmNvLnVrgg8qLmdvb2dsZS5jb20uYXKCDyouZ29vZ2xlLmNvbS5hdYIPKi5nb29nbGUuY29tLmJygg8qLmdvb2dsZS5jb20uY2+CDyouZ29vZ2xlLmNvbS5teIIPKi5nb29nbGUuY29tLnRygg8qLmdvb2dsZS5jb20udm6CCyouZ29vZ2xlLmRlggsqLmdvb2dsZS5lc4ILKi5nb29nbGUuZnKCCyouZ29vZ2xlLmh1ggsqLmdvb2dsZS5pdIILKi5nb29nbGUubmyCCyouZ29vZ2xlLnBsggsqLmdvb2dsZS5wdIISKi5nb29nbGVhZGFwaXMuY29tgg8qLmdvb2dsZWFwaXMuY26CFCouZ29vZ2xlY29tbWVyY2UuY29tghEqLmdvb2dsZXZpZGVvLmNvbYIMKi5nc3RhdGljLmNugg0qLmdzdGF0aWMuY29tggoqLmd2dDEuY29tggoqLmd2dDIuY29tghQqLm1ldHJpYy5nc3RhdGljLmNvbYIMKi51cmNoaW4uY29tghAqLnVybC5nb29nbGUuY29tghYqLnlvdXR1YmUtbm9jb29raWUuY29tgg0qLnlvdXR1YmUuY29tghYqLnlvdXR1YmVlZHVjYXRpb24uY29tggsqLnl0aW1nLmNvbYIaYW5kcm9pZC5jbGllbnRzLmdvb2dsZS5jb22CC2FuZHJvaWQuY29tggRnLmNvggZnb28uZ2yCFGdvb2dsZS1hbmFseXRpY3MuY29tggpnb29nbGUuY29tghJnb29nbGVjb21tZXJjZS5jb22CCnVyY2hpbi5jb22CCHlvdXR1LmJlggt5b3V0dWJlLmNvbYIUeW91dHViZWVkdWNhdGlvbi5jb20wDAYDVR0PBAUDAweAADBoBggrBgEFBQcBAQRcMFowKwYIKwYBBQUHMAKGH2h0dHA6Ly9wa2kuZ29vZ2xlLmNvbS9HSUFHMi5jcnQwKwYIKwYBBQUHMAGGH2h0dHA6Ly9jbGllbnRzMS5nb29nbGUuY29tL29jc3AwHQYDVR0OBBYEFNv0bmPu4ty+vzhgT5gx0GRE8WPYMAwGA1UdEwEB/wQCMAAwIQYDVR0gBBowGDAMBgorBgEEAdZ5AgUBMAgGBmeBDAECAjAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vcGtpLmdvb2dsZS5jb20vR0lBRzIuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQAOpm95fThLYPDBdpxOkvUkzhI0cpSVjc8cDNZ4a+5mK1A2Inq+/yLH3ZMsQIMvoDcpj7uYIr+Oxmy0i4/pHg+9it/f9cmqeawA5sqmGnSOZ/lfCYI8+bRbMIULrijCuJwjfGpZZsqOvSBuIOSzRvgGVplcs0dituT2khCFrkblwa/BqIqztvP7LuEmVpjkqt4pC3HvD0XUxs5PIdZZGInfeqymk5feReWHBuPHpPIUObKxmQt+hcw6YsHE+0B84Xtx9BMe4qqUfrqmtWXn9unBwxqSYsCqxHQpQ+70pmuBxlB9s6LStIzE9syaDmUyjxRljKAwINV6z0j7hKQ6MPpE\""
361
362 chunk2 := "\"MIIDnTCCAoWgAwIBAgIIQoIqW4Zvv+swDQYJKoZIhvcNAQELBQAwcTELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYDVQQKDAZHb29nbGUxDDAKBgNVBAsMA0VuZzEhMB8GA1UEAwwYRmFrZUNlcnRpZmljYXRlQXV0aG9yaXR5MB4XDTE2MDUxMzE0MjY0NFoXDTE5MDcxMjE0MjY0NFowcjELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYDVQQKDAZHb29nbGUxDDAKBgNVBAsMA0VuZzEiMCAGA1UEAwwZRmFrZUludGVybWVkaWF0ZUF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMqkDHpt6SYi1GcZyClAxr3LRDnn+oQBHbMEFUg3+lXVmEsq/xQO1s4naynV6I05676XvlMh0qPyJ+9GaBxvhHeFtGh4etQ9UEmJj55rSs50wA/IaDh+roKukQxthyTESPPgjqg+DPjh6H+h3Sn00Os6sjh3DxpOphTEsdtb7fmk8J0e2KjQQCjW/GlECzc359b9KbBwNkcAiYFayVHPLaCAdvzYVyiHgXHkEEs5FlHyhe2gNEG/81Io8c3E3DH5JhT9tmVRL3bpgpT8Kr4aoFhU2LXe45YIB1A9DjUm5TrHZ+iNtvE0YfYMR9L9C1HPppmX1CahEhTdog7laE1198UCAwEAAaM4MDYwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8ECDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwDQYJKoZIhvcNAQELBQADggEBAAHiOgwAvEzhrNMQVAz8a+SsyMIABXQ5P8WbJeHjkIipE4+5ZpkrZVXq9p8wOdkYnOHx4WNi9PVGQbLG9Iufh9fpk8cyyRWDi+V20/CNNtawMq3ClV3dWC98Tj4WX/BXDCeY2jK4jYGV+ds43HYV0ToBmvvrccq/U7zYMGFcQiKBClz5bTE+GMvrZWcO5A/Lh38i2YSF1i8SfDVnAOBlAgZmllcheHpGsWfSnduIllUvTsRvEIsaaqfVLl5QpRXBOq8tbjK85/2g6ear1oxPhJ1w9hds+WTFXkmHkWvKJebY13t3OfSjAyhaRSt8hdzDzHTFwjPjHT8h6dU7/hMdkUg=\""
363 epilog := "]}\n"
364
365
366 pool := loadCertsIntoPoolOrDie(t, pemChain)
367 merkleLeaf, err := ct.MerkleTreeLeafFromChain(pool.RawCertificates(), ct.X509LogEntryType, fakeTimeMillis)
368 if err != nil {
369 t.Fatalf("Unexpected error signing SCT: %v", err)
370 }
371
372 fullChain := make([]*x509.Certificate, len(pemChain)+1)
373 copy(fullChain, pool.RawCertificates())
374 fullChain[len(pemChain)] = info.roots.RawCertificates()[0]
375 leaf := logLeafForCert(t, fullChain, merkleLeaf, false)
376 queuedLeaf := &trillian.QueuedLogLeaf{
377 Leaf: leaf,
378 Status: status.New(codes.OK, "ok").Proto(),
379 }
380 rsp := trillian.QueueLeafResponse{QueuedLeaf: queuedLeaf}
381 req := &trillian.QueueLeafRequest{LogId: 0x42, Leaf: leaf}
382
383 var tests = []struct {
384 descr string
385 body string
386 want int
387 }{
388 {
389 descr: "valid",
390 body: intro + ":" + chunk1a + chunk1b + "," + chunk2 + epilog,
391 want: http.StatusOK,
392 },
393 {
394 descr: "valid-space-between",
395 body: intro + " : " + chunk1a + chunk1b + " , " + chunk2 + epilog,
396 want: http.StatusOK,
397 },
398 {
399 descr: "valid-newline-between",
400 body: intro + " : " + chunk1a + chunk1b + ",\n" + chunk2 + epilog,
401 want: http.StatusOK,
402 },
403 {
404 descr: "invalid-raw-newline-in-string",
405 body: intro + ":" + chunk1a + "\n" + chunk1b + "," + chunk2 + epilog,
406 want: http.StatusBadRequest,
407 },
408 {
409 descr: "valid-escaped-newline-in-string",
410 body: intro + ":" + chunk1a + "\\n" + chunk1b + "," + chunk2 + epilog,
411 want: http.StatusOK,
412 },
413 }
414
415 for _, test := range tests {
416 t.Run(test.descr, func(t *testing.T) {
417 if test.want == http.StatusOK {
418 info.client.EXPECT().QueueLeaf(deadlineMatcher(), cmpMatcher{req}).Return(&rsp, nil)
419 }
420
421 recorder := httptest.NewRecorder()
422 handler := AppHandler{Info: info.li, Handler: addChain, Name: "AddChain", Method: http.MethodPost}
423 req, err := http.NewRequest("POST", "http://example.com/ct/v1/add-chain", strings.NewReader(test.body))
424 if err != nil {
425 t.Fatalf("Failed to create POST request: %v", err)
426 }
427 handler.ServeHTTP(recorder, req)
428
429 if recorder.Code != test.want {
430 t.Fatalf("addChain()=%d (body:%v); want %dv", recorder.Code, recorder.Body, test.want)
431 }
432 })
433 }
434 }
435
436 func TestAddChain(t *testing.T) {
437 var tests = []struct {
438 descr string
439 chain []string
440 toSign string
441 want int
442 err error
443 remoteQuotaUser string
444 enableCertQuota bool
445
446 wantQuotaUsers []string
447 }{
448 {
449 descr: "leaf-only",
450 chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM},
451 want: http.StatusBadRequest,
452 },
453 {
454 descr: "wrong-entry-type",
455 chain: []string{cttestonly.PrecertPEMValid},
456 want: http.StatusBadRequest,
457 },
458 {
459 descr: "backend-rpc-fail",
460 chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM},
461 toSign: "1337d72a403b6539f58896decba416d5d4b3603bfa03e1f94bb9b4e898af897d",
462 want: http.StatusInternalServerError,
463 err: status.Errorf(codes.Internal, "error"),
464 },
465 {
466 descr: "success-without-root",
467 chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM},
468 toSign: "1337d72a403b6539f58896decba416d5d4b3603bfa03e1f94bb9b4e898af897d",
469 want: http.StatusOK,
470 },
471 {
472 descr: "success",
473 chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM, cttestonly.FakeCACertPEM},
474 toSign: "1337d72a403b6539f58896decba416d5d4b3603bfa03e1f94bb9b4e898af897d",
475 want: http.StatusOK,
476 },
477 {
478 descr: "success-without-root with remote quota",
479 chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM},
480 toSign: "1337d72a403b6539f58896decba416d5d4b3603bfa03e1f94bb9b4e898af897d",
481 remoteQuotaUser: remoteQuotaUser,
482 want: http.StatusOK,
483 wantQuotaUsers: []string{remoteQuotaUser},
484 },
485 {
486 descr: "success with remote quota",
487 chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM, cttestonly.FakeCACertPEM},
488 toSign: "1337d72a403b6539f58896decba416d5d4b3603bfa03e1f94bb9b4e898af897d",
489 remoteQuotaUser: remoteQuotaUser,
490 want: http.StatusOK,
491 wantQuotaUsers: []string{remoteQuotaUser},
492 },
493 {
494 descr: "success with chain quota",
495 chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM, cttestonly.FakeCACertPEM},
496 toSign: "1337d72a403b6539f58896decba416d5d4b3603bfa03e1f94bb9b4e898af897d",
497 enableCertQuota: true,
498 want: http.StatusOK,
499 wantQuotaUsers: quotaUsersForIssuers(t, cttestonly.FakeIntermediateCertPEM, cttestonly.FakeCACertPEM),
500 },
501 {
502 descr: "success with remote and chain quota",
503 chain: []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM, cttestonly.FakeCACertPEM},
504 toSign: "1337d72a403b6539f58896decba416d5d4b3603bfa03e1f94bb9b4e898af897d",
505 remoteQuotaUser: remoteQuotaUser,
506 enableCertQuota: true,
507 want: http.StatusOK,
508 wantQuotaUsers: append([]string{remoteQuotaUser}, quotaUsersForIssuers(t, cttestonly.FakeIntermediateCertPEM, cttestonly.FakeCACertPEM)...),
509 },
510 }
511
512 signer, err := setupSigner(fakeSignature)
513 if err != nil {
514 t.Fatalf("Failed to create test signer: %v", err)
515 }
516
517 info := setupTest(t, []string{cttestonly.FakeCACertPEM}, signer)
518 defer info.mockCtrl.Finish()
519
520 for _, test := range tests {
521 t.Run(test.descr, func(t *testing.T) {
522 info.setRemoteQuotaUser(test.remoteQuotaUser)
523 info.enableCertQuota(test.enableCertQuota)
524 pool := loadCertsIntoPoolOrDie(t, test.chain)
525 chain := createJSONChain(t, *pool)
526 if len(test.toSign) > 0 {
527 root := info.roots.RawCertificates()[0]
528 merkleLeaf, err := ct.MerkleTreeLeafFromChain(pool.RawCertificates(), ct.X509LogEntryType, fakeTimeMillis)
529 if err != nil {
530 t.Fatalf("Unexpected error signing SCT: %v", err)
531 }
532 leafChain := pool.RawCertificates()
533 if !leafChain[len(leafChain)-1].Equal(root) {
534
535 fullChain := make([]*x509.Certificate, len(leafChain)+1)
536 copy(fullChain, leafChain)
537 fullChain[len(leafChain)] = root
538 leafChain = fullChain
539 }
540 leaf := logLeafForCert(t, leafChain, merkleLeaf, false)
541 queuedLeaf := &trillian.QueuedLogLeaf{
542 Leaf: leaf,
543 Status: status.New(codes.OK, "ok").Proto(),
544 }
545 rsp := trillian.QueueLeafResponse{QueuedLeaf: queuedLeaf}
546 req := &trillian.QueueLeafRequest{LogId: 0x42, Leaf: leaf}
547 if len(test.wantQuotaUsers) > 0 {
548 req.ChargeTo = &trillian.ChargeTo{User: test.wantQuotaUsers}
549 }
550 info.client.EXPECT().QueueLeaf(deadlineMatcher(), cmpMatcher{req}).Return(&rsp, test.err)
551 }
552
553 recorder := makeAddChainRequest(t, info.li, chain)
554 if recorder.Code != test.want {
555 t.Fatalf("addChain()=%d (body:%v); want %dv", recorder.Code, recorder.Body, test.want)
556 }
557 if test.want == http.StatusOK {
558 var resp ct.AddChainResponse
559 if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil {
560 t.Fatalf("json.Decode(%s)=%v; want nil", recorder.Body.Bytes(), err)
561 }
562
563 if got, want := ct.Version(resp.SCTVersion), ct.V1; got != want {
564 t.Errorf("resp.SCTVersion=%v; want %v", got, want)
565 }
566 if got, want := resp.ID, demoLogID[:]; !bytes.Equal(got, want) {
567 t.Errorf("resp.ID=%v; want %v", got, want)
568 }
569 if got, want := resp.Timestamp, uint64(1469185273000); got != want {
570 t.Errorf("resp.Timestamp=%d; want %d", got, want)
571 }
572 if got, want := hex.EncodeToString(resp.Signature), "040300067369676e6564"; got != want {
573 t.Errorf("resp.Signature=%s; want %s", got, want)
574 }
575 }
576 })
577 }
578 }
579
580 func TestAddPrechain(t *testing.T) {
581 var tests = []struct {
582 descr string
583 chain []string
584 root string
585 toSign string
586 err error
587 want int
588 wantQuotaUser string
589 }{
590 {
591 descr: "leaf-signed-by-different",
592 chain: []string{cttestonly.PrecertPEMValid, cttestonly.FakeIntermediateCertPEM},
593 want: http.StatusBadRequest,
594 },
595 {
596 descr: "wrong-entry-type",
597 chain: []string{cttestonly.TestCertPEM},
598 want: http.StatusBadRequest,
599 },
600 {
601 descr: "backend-rpc-fail",
602 chain: []string{cttestonly.PrecertPEMValid, cttestonly.CACertPEM},
603 toSign: "92ecae1a2dc67a6c5f9c96fa5cab4c2faf27c48505b696dad926f161b0ca675a",
604 err: status.Errorf(codes.Internal, "error"),
605 want: http.StatusInternalServerError,
606 },
607 {
608 descr: "success",
609 chain: []string{cttestonly.PrecertPEMValid, cttestonly.CACertPEM},
610 toSign: "92ecae1a2dc67a6c5f9c96fa5cab4c2faf27c48505b696dad926f161b0ca675a",
611 want: http.StatusOK,
612 },
613 {
614 descr: "success with quota",
615 chain: []string{cttestonly.PrecertPEMValid, cttestonly.CACertPEM},
616 toSign: "92ecae1a2dc67a6c5f9c96fa5cab4c2faf27c48505b696dad926f161b0ca675a",
617 want: http.StatusOK,
618 wantQuotaUser: remoteQuotaUser,
619 },
620 {
621 descr: "success-without-root",
622 chain: []string{cttestonly.PrecertPEMValid},
623 toSign: "92ecae1a2dc67a6c5f9c96fa5cab4c2faf27c48505b696dad926f161b0ca675a",
624 want: http.StatusOK,
625 },
626 {
627 descr: "success-without-root with quota",
628 chain: []string{cttestonly.PrecertPEMValid},
629 toSign: "92ecae1a2dc67a6c5f9c96fa5cab4c2faf27c48505b696dad926f161b0ca675a",
630 want: http.StatusOK,
631 wantQuotaUser: remoteQuotaUser,
632 },
633 }
634
635 signer, err := setupSigner(fakeSignature)
636 if err != nil {
637 t.Fatalf("Failed to create test signer: %v", err)
638 }
639
640 info := setupTest(t, []string{cttestonly.CACertPEM}, signer)
641 defer info.mockCtrl.Finish()
642
643 for _, test := range tests {
644 t.Run(test.descr, func(t *testing.T) {
645 info.setRemoteQuotaUser(test.wantQuotaUser)
646 pool := loadCertsIntoPoolOrDie(t, test.chain)
647 chain := createJSONChain(t, *pool)
648 if len(test.toSign) > 0 {
649 root := info.roots.RawCertificates()[0]
650 merkleLeaf, err := ct.MerkleTreeLeafFromChain([]*x509.Certificate{pool.RawCertificates()[0], root}, ct.PrecertLogEntryType, fakeTimeMillis)
651 if err != nil {
652 t.Fatalf("Unexpected error signing SCT: %v", err)
653 }
654 leafChain := pool.RawCertificates()
655 if !leafChain[len(leafChain)-1].Equal(root) {
656
657 fullChain := make([]*x509.Certificate, len(leafChain)+1)
658 copy(fullChain, leafChain)
659 fullChain[len(leafChain)] = root
660 leafChain = fullChain
661 }
662 leaf := logLeafForCert(t, leafChain, merkleLeaf, true)
663 queuedLeaf := &trillian.QueuedLogLeaf{
664 Leaf: leaf,
665 Status: status.New(codes.OK, "ok").Proto(),
666 }
667 rsp := trillian.QueueLeafResponse{QueuedLeaf: queuedLeaf}
668 req := &trillian.QueueLeafRequest{LogId: 0x42, Leaf: leaf}
669 if len(test.wantQuotaUser) != 0 {
670 req.ChargeTo = &trillian.ChargeTo{User: []string{test.wantQuotaUser}}
671 }
672 info.client.EXPECT().QueueLeaf(deadlineMatcher(), cmpMatcher{req}).Return(&rsp, test.err)
673 }
674
675 recorder := makeAddPrechainRequest(t, info.li, chain)
676 if recorder.Code != test.want {
677 t.Fatalf("addPrechain()=%d (body:%v); want %d", recorder.Code, recorder.Body, test.want)
678 }
679 if test.want == http.StatusOK {
680 var resp ct.AddChainResponse
681 if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil {
682 t.Fatalf("json.Decode(%s)=%v; want nil", recorder.Body.Bytes(), err)
683 }
684
685 if got, want := ct.Version(resp.SCTVersion), ct.V1; got != want {
686 t.Errorf("resp.SCTVersion=%v; want %v", got, want)
687 }
688 if got, want := resp.ID, demoLogID[:]; !bytes.Equal(got, want) {
689 t.Errorf("resp.ID=%x; want %x", got, want)
690 }
691 if got, want := resp.Timestamp, uint64(1469185273000); got != want {
692 t.Errorf("resp.Timestamp=%d; want %d", got, want)
693 }
694 if got, want := hex.EncodeToString(resp.Signature), "040300067369676e6564"; got != want {
695 t.Errorf("resp.Signature=%s; want %s", got, want)
696 }
697 }
698 })
699 }
700 }
701
702 func TestGetSTH(t *testing.T) {
703 var tests = []struct {
704 descr string
705 rpcRsp *trillian.GetLatestSignedLogRootResponse
706 rpcErr error
707 toSign string
708 signErr error
709 want int
710 wantQuotaUser string
711 errStr string
712 }{
713 {
714 descr: "backend-failure",
715 rpcErr: errors.New("backendfailure"),
716 want: http.StatusInternalServerError,
717 errStr: "backendfailure",
718 },
719 {
720 descr: "backend-unimplemented",
721 rpcErr: status.Errorf(codes.Unimplemented, "no-such-thing"),
722 want: http.StatusNotImplemented,
723 errStr: "no-such-thing",
724 },
725 {
726 descr: "bad-hash",
727 rpcRsp: makeGetRootResponseForTest(t, 12345, 25, []byte("thisisnot32byteslong")),
728 want: http.StatusInternalServerError,
729 errStr: "bad hash size",
730 },
731 {
732 descr: "signer-fail",
733 rpcRsp: makeGetRootResponseForTest(t, 12345, 25, []byte("abcdabcdabcdabcdabcdabcdabcdabcd")),
734 want: http.StatusInternalServerError,
735 signErr: errors.New("signerfails"),
736 errStr: "signerfails",
737 },
738 {
739 descr: "ok",
740 rpcRsp: makeGetRootResponseForTest(t, 12345000000, 25, []byte("abcdabcdabcdabcdabcdabcdabcdabcd")),
741 toSign: "1e88546f5157bfaf77ca2454690b602631fedae925bbe7cf708ea275975bfe74",
742 want: http.StatusOK,
743 },
744 {
745 descr: "ok with quota",
746 rpcRsp: makeGetRootResponseForTest(t, 12345000000, 25, []byte("abcdabcdabcdabcdabcdabcdabcdabcd")),
747 toSign: "1e88546f5157bfaf77ca2454690b602631fedae925bbe7cf708ea275975bfe74",
748 want: http.StatusOK,
749 wantQuotaUser: remoteQuotaUser,
750 },
751 }
752
753 block, _ := pem.Decode([]byte(testdata.DemoPublicKey))
754 key, err := x509.ParsePKIXPublicKey(block.Bytes)
755 if err != nil {
756 t.Fatalf("Failed to load public key: %v", err)
757 }
758
759 for _, test := range tests {
760
761 func() {
762 var signer crypto.Signer
763 if test.signErr != nil {
764 signer = testdata.NewSignerWithErr(key, test.signErr)
765 } else {
766 signer = testdata.NewSignerWithFixedSig(key, fakeSignature)
767 }
768
769 info := setupTest(t, []string{cttestonly.CACertPEM}, signer)
770 info.setRemoteQuotaUser(test.wantQuotaUser)
771 defer info.mockCtrl.Finish()
772
773 srReq := &trillian.GetLatestSignedLogRootRequest{LogId: 0x42}
774 if len(test.wantQuotaUser) != 0 {
775 srReq.ChargeTo = &trillian.ChargeTo{User: []string{test.wantQuotaUser}}
776 }
777 info.client.EXPECT().GetLatestSignedLogRoot(deadlineMatcher(), cmpMatcher{srReq}).Return(test.rpcRsp, test.rpcErr)
778 req, err := http.NewRequest("GET", "http://example.com/ct/v1/get-sth", nil)
779 if err != nil {
780 t.Errorf("Failed to create request: %v", err)
781 return
782 }
783
784 handler := AppHandler{Info: info.li, Handler: getSTH, Name: "GetSTH", Method: http.MethodGet}
785 w := httptest.NewRecorder()
786 handler.ServeHTTP(w, req)
787 if got := w.Code; got != test.want {
788 t.Errorf("GetSTH(%s).Code=%d; want %d", test.descr, got, test.want)
789 }
790 if test.errStr != "" {
791 if body := w.Body.String(); !strings.Contains(body, test.errStr) {
792 t.Errorf("GetSTH(%s)=%q; want to find %q", test.descr, body, test.errStr)
793 }
794 return
795 }
796
797 var rsp ct.GetSTHResponse
798 if err := json.Unmarshal(w.Body.Bytes(), &rsp); err != nil {
799 t.Errorf("Failed to unmarshal json response: %s", w.Body.Bytes())
800 return
801 }
802
803 if got, want := rsp.TreeSize, uint64(25); got != want {
804 t.Errorf("GetSTH(%s).TreeSize=%d; want %d", test.descr, got, want)
805 }
806 if got, want := rsp.Timestamp, uint64(12345); got != want {
807 t.Errorf("GetSTH(%s).Timestamp=%d; want %d", test.descr, got, want)
808 }
809 if got, want := hex.EncodeToString(rsp.SHA256RootHash), "6162636461626364616263646162636461626364616263646162636461626364"; got != want {
810 t.Errorf("GetSTH(%s).SHA256RootHash=%s; want %s", test.descr, got, want)
811 }
812 if got, want := hex.EncodeToString(rsp.TreeHeadSignature), "040300067369676e6564"; got != want {
813 t.Errorf("GetSTH(%s).TreeHeadSignature=%s; want %s", test.descr, got, want)
814 }
815 }()
816 }
817 }
818
819 func TestGetEntries(t *testing.T) {
820
821 merkleLeaf1 := ct.MerkleTreeLeaf{
822 Version: ct.V1,
823 LeafType: ct.TimestampedEntryLeafType,
824 TimestampedEntry: &ct.TimestampedEntry{
825 Timestamp: 12345,
826 EntryType: ct.X509LogEntryType,
827 X509Entry: &ct.ASN1Cert{Data: []byte("certdatacertdata")},
828 Extensions: ct.CTExtensions{},
829 },
830 }
831 merkleLeaf2 := ct.MerkleTreeLeaf{
832 Version: ct.V1,
833 LeafType: ct.TimestampedEntryLeafType,
834 TimestampedEntry: &ct.TimestampedEntry{
835 Timestamp: 67890,
836 EntryType: ct.X509LogEntryType,
837 X509Entry: &ct.ASN1Cert{Data: []byte("certdat2certdat2")},
838 Extensions: ct.CTExtensions{},
839 },
840 }
841 merkleBytes1, err1 := tls.Marshal(merkleLeaf1)
842 merkleBytes2, err2 := tls.Marshal(merkleLeaf2)
843 if err1 != nil || err2 != nil {
844 t.Fatalf("failed to tls.Marshal() test data for get-entries: %v %v", err1, err2)
845 }
846
847 var tests = []struct {
848 descr string
849 req string
850 want int
851 wantQuotaUser string
852 glbrr *trillian.GetLeavesByRangeRequest
853 leaves []*trillian.LogLeaf
854 rpcErr error
855 slr *trillian.SignedLogRoot
856 errStr string
857 }{
858 {
859 descr: "invalid &&s",
860 req: "start=&&&&&&&&&end=wibble",
861 want: http.StatusBadRequest,
862 },
863 {
864 descr: "start non numeric",
865 req: "start=fish&end=3",
866 want: http.StatusBadRequest,
867 },
868 {
869 descr: "end non numeric",
870 req: "start=10&end=wibble",
871 want: http.StatusBadRequest,
872 },
873 {
874 descr: "both non numeric",
875 req: "start=fish&end=wibble",
876 want: http.StatusBadRequest,
877 },
878 {
879 descr: "end missing",
880 req: "start=1",
881 want: http.StatusBadRequest,
882 },
883 {
884 descr: "start missing",
885 req: "end=1",
886 want: http.StatusBadRequest,
887 },
888 {
889 descr: "both missing",
890 req: "",
891 want: http.StatusBadRequest,
892 },
893 {
894 descr: "backend rpc error",
895 req: "start=1&end=2",
896 want: http.StatusInternalServerError,
897 rpcErr: errors.New("bang"),
898 errStr: "bang",
899 },
900 {
901 descr: "invalid log root",
902 req: "start=2&end=3",
903 slr: &trillian.SignedLogRoot{
904 LogRoot: []byte("not tls encoded data"),
905 },
906 glbrr: &trillian.GetLeavesByRangeRequest{LogId: 0x42, StartIndex: 2, Count: 2},
907 want: http.StatusInternalServerError,
908 leaves: []*trillian.LogLeaf{{LeafIndex: 2}, {LeafIndex: 3}},
909 errStr: "failed to unmarshal",
910 },
911 {
912 descr: "start outside tree size",
913 req: "start=2&end=3",
914 slr: mustMarshalRoot(t, &types.LogRootV1{
915 TreeSize: 2,
916 }),
917 glbrr: &trillian.GetLeavesByRangeRequest{LogId: 0x42, StartIndex: 2, Count: 2},
918 want: http.StatusBadRequest,
919 leaves: []*trillian.LogLeaf{{LeafIndex: 2}, {LeafIndex: 3}},
920 errStr: "need tree size: 3 to get leaves but only got: 2",
921 },
922 {
923 descr: "backend extra leaves",
924 req: "start=1&end=2",
925 slr: mustMarshalRoot(t, &types.LogRootV1{
926 TreeSize: 2,
927 }),
928 want: http.StatusInternalServerError,
929 leaves: []*trillian.LogLeaf{{LeafIndex: 1}, {LeafIndex: 2}, {LeafIndex: 3}},
930 errStr: "too many leaves",
931 },
932 {
933 descr: "backend non-contiguous range",
934 req: "start=1&end=2",
935 slr: mustMarshalRoot(t, &types.LogRootV1{TreeSize: 100}),
936 want: http.StatusInternalServerError,
937 leaves: []*trillian.LogLeaf{{LeafIndex: 1}, {LeafIndex: 3}},
938 errStr: "unexpected leaf index",
939 },
940 {
941 descr: "backend leaf corrupt",
942 req: "start=1&end=2",
943 slr: mustMarshalRoot(t, &types.LogRootV1{TreeSize: 100}),
944 want: http.StatusOK,
945 leaves: []*trillian.LogLeaf{
946 {LeafIndex: 1, MerkleLeafHash: []byte("hash"), LeafValue: []byte("NOT A MERKLE TREE LEAF")},
947 {LeafIndex: 2, MerkleLeafHash: []byte("hash"), LeafValue: []byte("NOT A MERKLE TREE LEAF")},
948 },
949 },
950 {
951 descr: "leaves ok",
952 req: "start=1&end=2",
953 slr: mustMarshalRoot(t, &types.LogRootV1{TreeSize: 100}),
954 want: http.StatusOK,
955 leaves: []*trillian.LogLeaf{
956 {LeafIndex: 1, MerkleLeafHash: []byte("hash"), LeafValue: merkleBytes1, ExtraData: []byte("extra1")},
957 {LeafIndex: 2, MerkleLeafHash: []byte("hash"), LeafValue: merkleBytes2, ExtraData: []byte("extra2")},
958 },
959 },
960 {
961 descr: "leaves ok with quota",
962 req: "start=1&end=2",
963 slr: mustMarshalRoot(t, &types.LogRootV1{TreeSize: 100}),
964 want: http.StatusOK,
965 wantQuotaUser: remoteQuotaUser,
966 leaves: []*trillian.LogLeaf{
967 {LeafIndex: 1, MerkleLeafHash: []byte("hash"), LeafValue: merkleBytes1, ExtraData: []byte("extra1")},
968 {LeafIndex: 2, MerkleLeafHash: []byte("hash"), LeafValue: merkleBytes2, ExtraData: []byte("extra2")},
969 },
970 },
971 {
972 descr: "tree too small",
973 req: "start=5&end=6",
974 glbrr: &trillian.GetLeavesByRangeRequest{LogId: 0x42, StartIndex: 5, Count: 2},
975 want: http.StatusBadRequest,
976 slr: mustMarshalRoot(t, &types.LogRootV1{
977 TreeSize: 5,
978 }),
979 leaves: []*trillian.LogLeaf{},
980 },
981 {
982 descr: "tree includes 1 of 2",
983 req: "start=5&end=6",
984 glbrr: &trillian.GetLeavesByRangeRequest{LogId: 0x42, StartIndex: 5, Count: 2},
985 want: http.StatusOK,
986 slr: mustMarshalRoot(t, &types.LogRootV1{
987 TreeSize: 6,
988 }),
989 leaves: []*trillian.LogLeaf{
990 {LeafIndex: 5, MerkleLeafHash: []byte("hash5"), LeafValue: merkleBytes1, ExtraData: []byte("extra5")},
991 },
992 },
993 {
994 descr: "tree includes 2 of 2",
995 req: "start=5&end=6",
996 glbrr: &trillian.GetLeavesByRangeRequest{LogId: 0x42, StartIndex: 5, Count: 2},
997 want: http.StatusOK,
998 slr: mustMarshalRoot(t, &types.LogRootV1{
999 TreeSize: 7,
1000 }),
1001 leaves: []*trillian.LogLeaf{
1002 {LeafIndex: 5, MerkleLeafHash: []byte("hash5"), LeafValue: merkleBytes1, ExtraData: []byte("extra5")},
1003 {LeafIndex: 6, MerkleLeafHash: []byte("hash6"), LeafValue: merkleBytes1, ExtraData: []byte("extra6")},
1004 },
1005 },
1006 }
1007
1008 for _, test := range tests {
1009 info := setupTest(t, nil, nil)
1010 info.setRemoteQuotaUser(test.wantQuotaUser)
1011 handler := AppHandler{Info: info.li, Handler: getEntries, Name: "GetEntries", Method: http.MethodGet}
1012 path := fmt.Sprintf("/ct/v1/get-entries?%s", test.req)
1013 req, err := http.NewRequest("GET", path, nil)
1014 if err != nil {
1015 t.Errorf("Failed to create request: %v", err)
1016 continue
1017 }
1018 slr := test.slr
1019 if slr == nil {
1020 slr = mustMarshalRoot(t, &types.LogRootV1{})
1021 }
1022 if test.leaves != nil || test.rpcErr != nil {
1023 var chargeTo *trillian.ChargeTo
1024 if len(test.wantQuotaUser) != 0 {
1025 chargeTo = &trillian.ChargeTo{User: []string{test.wantQuotaUser}}
1026 }
1027 glbrr := &trillian.GetLeavesByRangeRequest{LogId: 0x42, StartIndex: 1, Count: 2, ChargeTo: chargeTo}
1028 if test.glbrr != nil {
1029 glbrr = test.glbrr
1030 }
1031 rsp := trillian.GetLeavesByRangeResponse{SignedLogRoot: slr, Leaves: test.leaves}
1032 info.client.EXPECT().GetLeavesByRange(deadlineMatcher(), cmpMatcher{glbrr}).Return(&rsp, test.rpcErr)
1033 }
1034
1035 w := httptest.NewRecorder()
1036 handler.ServeHTTP(w, req)
1037 if got := w.Code; got != test.want {
1038 t.Errorf("GetEntries(%q)=%d; want %d (because %s)", test.req, got, test.want, test.descr)
1039 }
1040 if test.errStr != "" {
1041 if body := w.Body.String(); !strings.Contains(body, test.errStr) {
1042 t.Errorf("GetEntries(%q)=%q; want to find %q (because %s)", test.req, body, test.errStr, test.descr)
1043 }
1044 continue
1045 }
1046 if test.want != http.StatusOK {
1047 continue
1048 }
1049 if got, want := w.Header().Get("Cache-Control"), "public"; !strings.Contains(got, want) {
1050 t.Errorf("GetEntries(%q): Cache-Control response header = %q, want %q", test.req, got, want)
1051 }
1052
1053 var jsonMap map[string][]ct.LeafEntry
1054 if err := json.Unmarshal(w.Body.Bytes(), &jsonMap); err != nil {
1055 t.Errorf("Failed to unmarshal json response %s: %v", w.Body.Bytes(), err)
1056 continue
1057 }
1058 if got := len(jsonMap); got != 1 {
1059 t.Errorf("len(rspMap)=%d; want 1", got)
1060 }
1061 entries := jsonMap["entries"]
1062 if got, want := len(entries), len(test.leaves); got != want {
1063 t.Errorf("len(rspMap['entries']=%d; want %d", got, want)
1064 continue
1065 }
1066 for i := 0; i < len(entries); i++ {
1067 if got, want := string(entries[i].LeafInput), string(test.leaves[i].LeafValue); got != want {
1068 t.Errorf("rspMap['entries'][%d].LeafInput=%s; want %s", i, got, want)
1069 }
1070 if got, want := string(entries[i].ExtraData), string(test.leaves[i].ExtraData); got != want {
1071 t.Errorf("rspMap['entries'][%d].ExtraData=%s; want %s", i, got, want)
1072 }
1073 }
1074
1075 info.mockCtrl.Finish()
1076 }
1077 }
1078
1079 func TestGetEntriesRanges(t *testing.T) {
1080 var tests = []struct {
1081 desc string
1082 start int64
1083 end int64
1084 rpcEnd int64
1085 want int
1086 wantQuotaUser string
1087 rpc bool
1088 }{
1089 {
1090 desc: "-ve start value not allowed",
1091 start: -1,
1092 end: 0,
1093 want: http.StatusBadRequest,
1094 },
1095 {
1096 desc: "-ve end value not allowed",
1097 start: 0,
1098 end: -1,
1099 want: http.StatusBadRequest,
1100 },
1101 {
1102 desc: "invalid range end>start",
1103 start: 20,
1104 end: 10,
1105 want: http.StatusBadRequest,
1106 },
1107 {
1108 desc: "invalid range, -ve end",
1109 start: 3000,
1110 end: -50,
1111 want: http.StatusBadRequest,
1112 },
1113 {
1114 desc: "valid range",
1115 start: 10,
1116 end: 20,
1117 want: http.StatusInternalServerError,
1118 rpc: true,
1119 },
1120 {
1121 desc: "valid range quota",
1122 start: 10,
1123 end: 20,
1124 want: http.StatusInternalServerError,
1125 wantQuotaUser: remoteQuotaUser,
1126 rpc: true,
1127 },
1128 {
1129 desc: "valid range, one entry",
1130 start: 10,
1131 end: 10,
1132 want: http.StatusInternalServerError,
1133 rpc: true,
1134 },
1135 {
1136 desc: "invalid range, edge case",
1137 start: 10,
1138 end: 9,
1139 want: http.StatusBadRequest,
1140 },
1141 {
1142 desc: "range too large, coerced into alignment",
1143 start: 14,
1144 end: 50000,
1145 want: http.StatusInternalServerError,
1146 rpcEnd: MaxGetEntriesAllowed - 1,
1147 rpc: true,
1148 },
1149 {
1150 desc: "range too large, already in alignment",
1151 start: MaxGetEntriesAllowed,
1152 end: 5000,
1153 want: http.StatusInternalServerError,
1154 rpcEnd: MaxGetEntriesAllowed + MaxGetEntriesAllowed - 1,
1155 rpc: true,
1156 },
1157 {
1158 desc: "small range straddling boundary, not coerced",
1159 start: MaxGetEntriesAllowed - 2,
1160 end: MaxGetEntriesAllowed + 2,
1161 want: http.StatusInternalServerError,
1162 rpcEnd: MaxGetEntriesAllowed + 2,
1163 rpc: true,
1164 },
1165 }
1166
1167
1168
1169
1170 for _, test := range tests {
1171 t.Run(test.desc, func(t *testing.T) {
1172 info := setupTest(t, nil, nil)
1173 defer info.mockCtrl.Finish()
1174 handler := AppHandler{Info: info.li, Handler: getEntries, Name: "GetEntries", Method: http.MethodGet}
1175
1176 info.setRemoteQuotaUser(test.wantQuotaUser)
1177 if test.rpc {
1178 end := test.rpcEnd
1179 if end == 0 {
1180 end = test.end
1181 }
1182 var chargeTo *trillian.ChargeTo
1183 if len(test.wantQuotaUser) != 0 {
1184 chargeTo = &trillian.ChargeTo{User: []string{test.wantQuotaUser}}
1185 }
1186 info.client.EXPECT().GetLeavesByRange(deadlineMatcher(), cmpMatcher{&trillian.GetLeavesByRangeRequest{LogId: 0x42, StartIndex: test.start, Count: end + 1 - test.start, ChargeTo: chargeTo}}).Return(nil, errors.New("RPCMADE"))
1187 }
1188
1189 path := fmt.Sprintf("/ct/v1/get-entries?start=%d&end=%d", test.start, test.end)
1190 req, err := http.NewRequest("GET", path, nil)
1191 if err != nil {
1192 t.Fatalf("Failed to create request: %v", err)
1193 }
1194 w := httptest.NewRecorder()
1195 handler.ServeHTTP(w, req)
1196
1197 if got := w.Code; got != test.want {
1198 t.Errorf("getEntries(%d, %d)=%d; want %d for test %s", test.start, test.end, got, test.want, test.desc)
1199 }
1200 if test.rpc && !strings.Contains(w.Body.String(), "RPCMADE") {
1201
1202 t.Errorf("getEntries(%d, %d)=%q; expect RPCMADE for test %s", test.start, test.end, w.Body, test.desc)
1203 }
1204 })
1205 }
1206 }
1207
1208 func TestGetProofByHash(t *testing.T) {
1209 auditHashes := [][]byte{
1210 []byte("abcdef78901234567890123456789012"),
1211 []byte("ghijkl78901234567890123456789012"),
1212 []byte("mnopqr78901234567890123456789012"),
1213 }
1214 inclusionProof := ct.GetProofByHashResponse{
1215 LeafIndex: 2,
1216 AuditPath: auditHashes,
1217 }
1218
1219 var tests = []struct {
1220 req string
1221 want int
1222 wantQuotaUser string
1223 rpcRsp *trillian.GetInclusionProofByHashResponse
1224 httpRsp *ct.GetProofByHashResponse
1225 httpJSON string
1226 rpcErr error
1227 errStr string
1228 }{
1229 {
1230 req: "",
1231 want: http.StatusBadRequest,
1232 },
1233 {
1234 req: "hash=&tree_size=1",
1235 want: http.StatusBadRequest,
1236 },
1237 {
1238 req: "hash=''&tree_size=1",
1239 want: http.StatusBadRequest,
1240 },
1241 {
1242 req: "hash=notbase64data&tree_size=1",
1243 want: http.StatusBadRequest,
1244 },
1245 {
1246 req: "tree_size=-1&hash=aGkK",
1247 want: http.StatusBadRequest,
1248 },
1249 {
1250 req: "tree_size=6&hash=YWhhc2g=",
1251 want: http.StatusInternalServerError,
1252 rpcErr: errors.New("RPCFAIL"),
1253 errStr: "RPCFAIL",
1254 },
1255 {
1256 req: "tree_size=11&hash=YWhhc2g=",
1257 want: http.StatusNotFound,
1258 rpcRsp: &trillian.GetInclusionProofByHashResponse{
1259 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1260 TreeSize: 10,
1261 }),
1262 Proof: []*trillian.Proof{
1263 {
1264 LeafIndex: 0,
1265 Hashes: nil,
1266 },
1267 },
1268 },
1269 },
1270 {
1271 req: "tree_size=11&hash=YWhhc2g=",
1272 want: http.StatusInternalServerError,
1273 rpcRsp: &trillian.GetInclusionProofByHashResponse{
1274 SignedLogRoot: &trillian.SignedLogRoot{
1275 LogRoot: []byte("not tls encoded data"),
1276 },
1277 },
1278 },
1279 {
1280 req: "tree_size=1&hash=YWhhc2g=",
1281 want: http.StatusOK,
1282 rpcRsp: &trillian.GetInclusionProofByHashResponse{
1283 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1284 TreeSize: 10,
1285 }),
1286 Proof: []*trillian.Proof{
1287 {
1288 LeafIndex: 0,
1289 Hashes: nil,
1290 },
1291 },
1292 },
1293 httpRsp: &ct.GetProofByHashResponse{LeafIndex: 0, AuditPath: nil},
1294
1295 httpJSON: "{\"leaf_index\":0,\"audit_path\":[]}",
1296 },
1297 {
1298 req: "tree_size=1&hash=YWhhc2g=",
1299 want: http.StatusOK,
1300
1301 wantQuotaUser: remoteQuotaUser,
1302 rpcRsp: &trillian.GetInclusionProofByHashResponse{
1303 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1304 TreeSize: 10,
1305 }),
1306 Proof: []*trillian.Proof{
1307 {
1308 LeafIndex: 0,
1309 Hashes: nil,
1310 },
1311 },
1312 },
1313 httpRsp: &ct.GetProofByHashResponse{LeafIndex: 0, AuditPath: nil},
1314
1315 httpJSON: "{\"leaf_index\":0,\"audit_path\":[]}",
1316 },
1317 {
1318 req: "tree_size=7&hash=YWhhc2g=",
1319 want: http.StatusOK,
1320 rpcRsp: &trillian.GetInclusionProofByHashResponse{
1321 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1322 TreeSize: 10,
1323 }),
1324 Proof: []*trillian.Proof{
1325 {
1326 LeafIndex: 2,
1327 Hashes: auditHashes,
1328 },
1329
1330 {
1331 LeafIndex: 2,
1332 Hashes: [][]byte{[]byte("ghijkl")},
1333 },
1334 },
1335 },
1336 httpRsp: &inclusionProof,
1337 },
1338 {
1339 req: "tree_size=9&hash=YWhhc2g=",
1340 want: http.StatusInternalServerError,
1341 rpcRsp: &trillian.GetInclusionProofByHashResponse{
1342 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1343 TreeSize: 10,
1344 }),
1345 Proof: []*trillian.Proof{
1346 {
1347 LeafIndex: 2,
1348 Hashes: [][]byte{
1349 auditHashes[0],
1350 {},
1351 auditHashes[2],
1352 },
1353 },
1354 },
1355 },
1356 errStr: "invalid proof",
1357 },
1358 {
1359 req: "tree_size=7&hash=YWhhc2g=",
1360 want: http.StatusOK,
1361 rpcRsp: &trillian.GetInclusionProofByHashResponse{
1362 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1363 TreeSize: 10,
1364 }),
1365 Proof: []*trillian.Proof{
1366 {
1367 LeafIndex: 2,
1368 Hashes: auditHashes,
1369 },
1370 },
1371 },
1372 httpRsp: &inclusionProof,
1373 },
1374 {
1375
1376 req: "hash=WtfX3Axbm7UwtY7GhHoAHPCtXJVrY5vZsH%2ByaXOD2GI=&tree_size=1",
1377 want: http.StatusOK,
1378 rpcRsp: &trillian.GetInclusionProofByHashResponse{
1379 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1380 TreeSize: 10,
1381 }),
1382 Proof: []*trillian.Proof{
1383 {
1384 LeafIndex: 2,
1385 Hashes: auditHashes,
1386 },
1387 },
1388 },
1389 httpRsp: &inclusionProof,
1390 },
1391 {
1392 req: "tree_size=10&hash=YWhhc2g=",
1393 want: http.StatusNotFound,
1394 rpcRsp: &trillian.GetInclusionProofByHashResponse{
1395 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1396 TreeSize: 5,
1397 }),
1398 Proof: []*trillian.Proof{
1399 {
1400 LeafIndex: 0,
1401 Hashes: nil,
1402 },
1403 },
1404 },
1405 },
1406 {
1407 req: "tree_size=10&hash=YWhhc2g=",
1408 want: http.StatusOK,
1409 rpcRsp: &trillian.GetInclusionProofByHashResponse{
1410 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1411
1412 TreeSize: 10,
1413 }),
1414 Proof: []*trillian.Proof{
1415 {
1416 LeafIndex: 2,
1417 Hashes: auditHashes,
1418 },
1419 },
1420 },
1421 httpRsp: &inclusionProof,
1422 },
1423 {
1424 req: "tree_size=10&hash=YWhhc2g=",
1425 want: http.StatusOK,
1426 rpcRsp: &trillian.GetInclusionProofByHashResponse{
1427 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1428
1429 TreeSize: 20,
1430 }),
1431 Proof: []*trillian.Proof{
1432 {
1433 LeafIndex: 2,
1434 Hashes: auditHashes,
1435 },
1436 },
1437 },
1438 httpRsp: &inclusionProof,
1439 },
1440 }
1441 info := setupTest(t, nil, nil)
1442 defer info.mockCtrl.Finish()
1443 handler := AppHandler{Info: info.li, Handler: getProofByHash, Name: "GetProofByHash", Method: http.MethodGet}
1444
1445 for _, test := range tests {
1446 info.setRemoteQuotaUser(test.wantQuotaUser)
1447 req, err := http.NewRequest("GET", fmt.Sprintf("/ct/v1/proof-by-hash?%s", test.req), nil)
1448 if err != nil {
1449 t.Errorf("Failed to create request: %v", err)
1450 continue
1451 }
1452 if test.rpcRsp != nil || test.rpcErr != nil {
1453 info.client.EXPECT().GetInclusionProofByHash(deadlineMatcher(), gomock.Any()).Return(test.rpcRsp, test.rpcErr)
1454 }
1455 w := httptest.NewRecorder()
1456 handler.ServeHTTP(w, req)
1457 if got := w.Code; got != test.want {
1458 t.Errorf("proofByHash(%s)=%d; want %d", test.req, got, test.want)
1459 }
1460 if test.errStr != "" {
1461 if body := w.Body.String(); !strings.Contains(body, test.errStr) {
1462 t.Errorf("proofByHash(%q)=%q; want to find %q", test.req, body, test.errStr)
1463 }
1464 continue
1465 }
1466 if test.want != http.StatusOK {
1467 continue
1468 }
1469 if got, want := w.Header().Get("Cache-Control"), "public"; !strings.Contains(got, want) {
1470 t.Errorf("proofByHash(%q): Cache-Control response header = %q, want %q", test.req, got, want)
1471 }
1472 jsonData, err := io.ReadAll(w.Body)
1473 if err != nil {
1474 t.Errorf("failed to read response body: %v", err)
1475 continue
1476 }
1477 var resp ct.GetProofByHashResponse
1478 if err = json.Unmarshal(jsonData, &resp); err != nil {
1479 t.Errorf("Failed to unmarshal json response %s: %v", jsonData, err)
1480 continue
1481 }
1482 if diff := pretty.Compare(resp, test.httpRsp); diff != "" {
1483 t.Errorf("proofByHash(%q) diff:\n%v", test.req, diff)
1484 }
1485 if test.httpJSON != "" {
1486
1487 if diff := pretty.Compare(string(jsonData), test.httpJSON); diff != "" {
1488 t.Errorf("proofByHash(%q) diff:\n%v", test.req, diff)
1489 }
1490 }
1491 }
1492 }
1493
1494 func TestGetSTHConsistency(t *testing.T) {
1495 auditHashes := [][]byte{
1496 []byte("abcdef78901234567890123456789012"),
1497 []byte("ghijkl78901234567890123456789012"),
1498 []byte("mnopqr78901234567890123456789012"),
1499 }
1500 var tests = []struct {
1501 req string
1502 want int
1503 wantQuotaUser string
1504 first, second int64
1505 rpcRsp *trillian.GetConsistencyProofResponse
1506 httpRsp *ct.GetSTHConsistencyResponse
1507 httpJSON string
1508 rpcErr error
1509 errStr string
1510 }{
1511 {
1512 req: "",
1513 want: http.StatusBadRequest,
1514 errStr: "parameter 'first' is required",
1515 },
1516 {
1517 req: "first=apple&second=orange",
1518 want: http.StatusBadRequest,
1519 errStr: "parameter 'first' is malformed",
1520 },
1521 {
1522 req: "first=1&last=2",
1523 want: http.StatusBadRequest,
1524 errStr: "parameter 'second' is required",
1525 },
1526 {
1527 req: "first=1&second=a",
1528 want: http.StatusBadRequest,
1529 errStr: "parameter 'second' is malformed",
1530 },
1531 {
1532 req: "first=a&second=2",
1533 want: http.StatusBadRequest,
1534 errStr: "parameter 'first' is malformed",
1535 },
1536 {
1537 req: "first=-1&second=10",
1538 want: http.StatusBadRequest,
1539 errStr: "first and second params cannot be <0: -1 10",
1540 },
1541 {
1542 req: "first=10&second=-11",
1543 want: http.StatusBadRequest,
1544 errStr: "first and second params cannot be <0: 10 -11",
1545 },
1546 {
1547 req: "first=0&second=1",
1548 want: http.StatusOK,
1549 httpRsp: &ct.GetSTHConsistencyResponse{
1550 Consistency: nil,
1551 },
1552
1553 httpJSON: "{\"consistency\":[]}",
1554 },
1555 {
1556 req: "first=0&second=1",
1557 want: http.StatusOK,
1558
1559 wantQuotaUser: remoteQuotaUser,
1560 httpRsp: &ct.GetSTHConsistencyResponse{
1561 Consistency: nil,
1562 },
1563
1564 httpJSON: "{\"consistency\":[]}",
1565 },
1566 {
1567
1568 req: "first=0&second=1&third=2&fourth=3",
1569 want: http.StatusOK,
1570 httpRsp: &ct.GetSTHConsistencyResponse{},
1571 },
1572 {
1573 req: "first=998&second=997",
1574 want: http.StatusBadRequest,
1575 errStr: "invalid first, second params: 998 997",
1576 },
1577 {
1578 req: "first=1000&second=200",
1579 want: http.StatusBadRequest,
1580 errStr: "invalid first, second params: 1000 200",
1581 },
1582 {
1583 req: "first=10",
1584 want: http.StatusBadRequest,
1585 errStr: "parameter 'second' is required",
1586 },
1587 {
1588 req: "second=20",
1589 want: http.StatusBadRequest,
1590 errStr: "parameter 'first' is required",
1591 },
1592 {
1593 req: "first=10&second=20",
1594 first: 10,
1595 second: 20,
1596 want: http.StatusInternalServerError,
1597 rpcErr: errors.New("RPCFAIL"),
1598 errStr: "RPCFAIL",
1599 },
1600 {
1601 req: "first=10&second=20",
1602 first: 10,
1603 second: 20,
1604 want: http.StatusInternalServerError,
1605 rpcRsp: &trillian.GetConsistencyProofResponse{
1606 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1607 TreeSize: 50,
1608 }),
1609 Proof: &trillian.Proof{
1610 LeafIndex: 2,
1611 Hashes: [][]byte{
1612 auditHashes[0],
1613 {},
1614 auditHashes[2],
1615 },
1616 },
1617 },
1618 errStr: "invalid proof",
1619 },
1620 {
1621 req: "first=10&second=20",
1622 first: 10,
1623 second: 20,
1624 want: http.StatusInternalServerError,
1625 rpcRsp: &trillian.GetConsistencyProofResponse{
1626 SignedLogRoot: &trillian.SignedLogRoot{
1627 LogRoot: []byte("not tls encoded data"),
1628 },
1629 },
1630 errStr: "failed to unmarshal",
1631 },
1632 {
1633 req: "first=10&second=20",
1634 first: 10,
1635 second: 20,
1636 want: http.StatusBadRequest,
1637 rpcRsp: &trillian.GetConsistencyProofResponse{
1638 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1639 TreeSize: 19,
1640 }),
1641 Proof: &trillian.Proof{
1642 LeafIndex: 2,
1643 Hashes: [][]byte{
1644 auditHashes[0],
1645 {},
1646 auditHashes[2],
1647 },
1648 },
1649 },
1650 errStr: "need tree size: 20",
1651 },
1652 {
1653 req: "first=10&second=20",
1654 first: 10,
1655 second: 20,
1656 want: http.StatusInternalServerError,
1657 rpcRsp: &trillian.GetConsistencyProofResponse{
1658 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1659 TreeSize: 50,
1660 }),
1661 Proof: &trillian.Proof{
1662 LeafIndex: 2,
1663 Hashes: [][]byte{
1664 auditHashes[0],
1665 auditHashes[1][:30],
1666 auditHashes[2],
1667 },
1668 },
1669 },
1670 errStr: "invalid proof",
1671 },
1672 {
1673 req: "first=10&second=20",
1674 first: 10,
1675 second: 20,
1676 want: http.StatusOK,
1677 rpcRsp: &trillian.GetConsistencyProofResponse{
1678 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1679 TreeSize: 50,
1680 }),
1681 Proof: &trillian.Proof{
1682 LeafIndex: 2,
1683 Hashes: auditHashes,
1684 },
1685 },
1686 httpRsp: &ct.GetSTHConsistencyResponse{
1687 Consistency: auditHashes,
1688 },
1689 },
1690 {
1691 req: "first=1&second=2",
1692 first: 1,
1693 second: 2,
1694 want: http.StatusOK,
1695 rpcRsp: &trillian.GetConsistencyProofResponse{
1696 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1697 TreeSize: 50,
1698 }),
1699 Proof: &trillian.Proof{
1700 LeafIndex: 0,
1701 Hashes: nil,
1702 },
1703 },
1704 httpRsp: &ct.GetSTHConsistencyResponse{
1705 Consistency: nil,
1706 },
1707
1708 httpJSON: "{\"consistency\":[]}",
1709 },
1710 {
1711 req: "first=332&second=332",
1712 first: 332,
1713 second: 332,
1714 want: http.StatusOK,
1715 rpcRsp: &trillian.GetConsistencyProofResponse{
1716 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1717 TreeSize: 333,
1718 }),
1719 Proof: &trillian.Proof{
1720 LeafIndex: 0,
1721 Hashes: nil,
1722 },
1723 },
1724 httpRsp: &ct.GetSTHConsistencyResponse{
1725 Consistency: nil,
1726 },
1727
1728 httpJSON: "{\"consistency\":[]}",
1729 },
1730 {
1731 req: "first=332&second=332",
1732 first: 332,
1733 second: 332,
1734 want: http.StatusBadRequest,
1735 rpcRsp: &trillian.GetConsistencyProofResponse{
1736 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1737
1738 TreeSize: 331,
1739 }),
1740 Proof: &trillian.Proof{
1741 Hashes: nil,
1742 },
1743 },
1744 },
1745 {
1746 req: "first=332&second=332",
1747 first: 332,
1748 second: 332,
1749 want: http.StatusOK,
1750 rpcRsp: &trillian.GetConsistencyProofResponse{
1751 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1752
1753 TreeSize: 332,
1754 }),
1755 Proof: &trillian.Proof{
1756 LeafIndex: 2,
1757 Hashes: auditHashes,
1758 },
1759 },
1760 httpRsp: &ct.GetSTHConsistencyResponse{
1761 Consistency: auditHashes,
1762 },
1763 },
1764 {
1765 req: "first=332&second=332",
1766 first: 332,
1767 second: 332,
1768 want: http.StatusOK,
1769 rpcRsp: &trillian.GetConsistencyProofResponse{
1770 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1771
1772 TreeSize: 333,
1773 }),
1774 Proof: &trillian.Proof{
1775 LeafIndex: 2,
1776 Hashes: auditHashes,
1777 },
1778 },
1779 httpRsp: &ct.GetSTHConsistencyResponse{
1780 Consistency: auditHashes,
1781 },
1782 },
1783 {
1784 req: "first=332&second=331",
1785 want: http.StatusBadRequest,
1786 },
1787 }
1788
1789 info := setupTest(t, nil, nil)
1790 defer info.mockCtrl.Finish()
1791 handler := AppHandler{Info: info.li, Handler: getSTHConsistency, Name: "GetSTHConsistency", Method: http.MethodGet}
1792
1793 for _, test := range tests {
1794 info.setRemoteQuotaUser(test.wantQuotaUser)
1795 req, err := http.NewRequest("GET", fmt.Sprintf("/ct/v1/get-sth-consistency?%s", test.req), nil)
1796 if err != nil {
1797 t.Errorf("Failed to create request: %v", err)
1798 continue
1799 }
1800 if test.rpcRsp != nil || test.rpcErr != nil {
1801 req := trillian.GetConsistencyProofRequest{
1802 LogId: 0x42,
1803 FirstTreeSize: test.first,
1804 SecondTreeSize: test.second,
1805 }
1806 if len(test.wantQuotaUser) > 0 {
1807 req.ChargeTo = &trillian.ChargeTo{User: []string{test.wantQuotaUser}}
1808 }
1809 info.client.EXPECT().GetConsistencyProof(deadlineMatcher(), cmpMatcher{&req}).Return(test.rpcRsp, test.rpcErr)
1810 }
1811 w := httptest.NewRecorder()
1812 handler.ServeHTTP(w, req)
1813 if got := w.Code; got != test.want {
1814 t.Errorf("getSTHConsistency(%s)=%d; want %d", test.req, got, test.want)
1815 }
1816 if test.errStr != "" {
1817 if body := w.Body.String(); !strings.Contains(body, test.errStr) {
1818 t.Errorf("getSTHConsistency(%q)=%q; want to find %q", test.req, body, test.errStr)
1819 }
1820 continue
1821 }
1822 if test.want != http.StatusOK {
1823 continue
1824 }
1825 if got, want := w.Header().Get("Cache-Control"), "public"; !strings.Contains(got, want) {
1826 t.Errorf("getSTHConsistency(%q): Cache-Control response header = %q, want %q", test.req, got, want)
1827 }
1828 jsonData, err := io.ReadAll(w.Body)
1829 if err != nil {
1830 t.Errorf("failed to read response body: %v", err)
1831 continue
1832 }
1833 var resp ct.GetSTHConsistencyResponse
1834 if err = json.Unmarshal(jsonData, &resp); err != nil {
1835 t.Errorf("Failed to unmarshal json response %s: %v", jsonData, err)
1836 continue
1837 }
1838 if diff := pretty.Compare(resp, test.httpRsp); diff != "" {
1839 t.Errorf("getSTHConsistency(%q) diff:\n%v", test.req, diff)
1840 }
1841 if test.httpJSON != "" {
1842
1843 if diff := pretty.Compare(string(jsonData), test.httpJSON); diff != "" {
1844 t.Errorf("getSTHConsistency(%q) diff:\n%v", test.req, diff)
1845 }
1846 }
1847 }
1848 }
1849
1850 func TestGetEntryAndProof(t *testing.T) {
1851 merkleLeaf := ct.MerkleTreeLeaf{
1852 Version: ct.V1,
1853 LeafType: ct.TimestampedEntryLeafType,
1854 TimestampedEntry: &ct.TimestampedEntry{
1855 Timestamp: 12345,
1856 EntryType: ct.X509LogEntryType,
1857 X509Entry: &ct.ASN1Cert{Data: []byte("certdatacertdata")},
1858 Extensions: ct.CTExtensions{},
1859 },
1860 }
1861 leafBytes, err := tls.Marshal(merkleLeaf)
1862 if err != nil {
1863 t.Fatalf("failed to build test Merkle leaf data: %v", err)
1864 }
1865 proofRsp := ct.GetEntryAndProofResponse{
1866 LeafInput: leafBytes,
1867 ExtraData: []byte("extra"),
1868 AuditPath: [][]byte{[]byte("abcdef"), []byte("ghijkl"), []byte("mnopqr")},
1869 }
1870
1871 var tests = []struct {
1872 req string
1873 idx, sz int64
1874 want int
1875 wantQuotaUser string
1876 wantRsp *ct.GetEntryAndProofResponse
1877 rpcRsp *trillian.GetEntryAndProofResponse
1878 rpcErr error
1879 errStr string
1880 }{
1881 {
1882 req: "",
1883 want: http.StatusBadRequest,
1884 },
1885 {
1886 req: "leaf_index=b",
1887 want: http.StatusBadRequest,
1888 },
1889 {
1890 req: "leaf_index=1&tree_size=-1",
1891 want: http.StatusBadRequest,
1892 },
1893 {
1894 req: "leaf_index=-1&tree_size=1",
1895 want: http.StatusBadRequest,
1896 },
1897 {
1898 req: "leaf_index=1&tree_size=d",
1899 want: http.StatusBadRequest,
1900 },
1901 {
1902 req: "leaf_index=&tree_size=",
1903 want: http.StatusBadRequest,
1904 },
1905 {
1906 req: "leaf_index=",
1907 want: http.StatusBadRequest,
1908 },
1909 {
1910 req: "leaf_index=1&tree_size=0",
1911 want: http.StatusBadRequest,
1912 },
1913 {
1914 req: "leaf_index=10&tree_size=5",
1915 want: http.StatusBadRequest,
1916 },
1917 {
1918 req: "leaf_index=tree_size",
1919 want: http.StatusBadRequest,
1920 },
1921 {
1922 req: "leaf_index=1&tree_size=3",
1923 idx: 1,
1924 sz: 3,
1925 want: http.StatusInternalServerError,
1926 rpcErr: errors.New("RPCFAIL"),
1927 errStr: "RPCFAIL",
1928 },
1929 {
1930 req: "leaf_index=1&tree_size=3",
1931 idx: 1,
1932 sz: 3,
1933 want: http.StatusInternalServerError,
1934
1935 rpcRsp: &trillian.GetEntryAndProofResponse{},
1936 },
1937 {
1938 req: "leaf_index=1&tree_size=3",
1939 idx: 1,
1940 sz: 3,
1941 want: http.StatusOK,
1942 wantRsp: &proofRsp,
1943 rpcRsp: &trillian.GetEntryAndProofResponse{
1944 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1945
1946 TreeSize: 20,
1947 }),
1948 Proof: &trillian.Proof{
1949 LeafIndex: 2,
1950 Hashes: [][]byte{
1951 []byte("abcdef"),
1952 []byte("ghijkl"),
1953 []byte("mnopqr"),
1954 },
1955 },
1956
1957 Leaf: &trillian.LogLeaf{
1958 LeafValue: leafBytes,
1959 MerkleLeafHash: []byte("ahash"),
1960 ExtraData: []byte("extra"),
1961 },
1962 },
1963 },
1964 {
1965 req: "leaf_index=1&tree_size=3",
1966 idx: 1,
1967 sz: 3,
1968 want: http.StatusOK,
1969 wantRsp: &proofRsp,
1970
1971 wantQuotaUser: remoteQuotaUser,
1972 rpcRsp: &trillian.GetEntryAndProofResponse{
1973 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
1974
1975 TreeSize: 20,
1976 }),
1977 Proof: &trillian.Proof{
1978 LeafIndex: 2,
1979 Hashes: [][]byte{
1980 []byte("abcdef"),
1981 []byte("ghijkl"),
1982 []byte("mnopqr"),
1983 },
1984 },
1985
1986 Leaf: &trillian.LogLeaf{
1987 LeafValue: leafBytes,
1988 MerkleLeafHash: []byte("ahash"),
1989 ExtraData: []byte("extra"),
1990 },
1991 },
1992 },
1993 {
1994 req: "leaf_index=1&tree_size=3",
1995 idx: 1,
1996 sz: 3,
1997 want: http.StatusBadRequest,
1998 rpcRsp: &trillian.GetEntryAndProofResponse{
1999 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
2000
2001 TreeSize: 2,
2002 }),
2003 Proof: &trillian.Proof{},
2004 },
2005 },
2006 {
2007 req: "leaf_index=1&tree_size=3",
2008 idx: 1,
2009 sz: 3,
2010 want: http.StatusOK,
2011 wantRsp: &proofRsp,
2012 rpcRsp: &trillian.GetEntryAndProofResponse{
2013 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
2014
2015 TreeSize: 3,
2016 }),
2017 Proof: &trillian.Proof{
2018 LeafIndex: 2,
2019 Hashes: [][]byte{
2020 []byte("abcdef"),
2021 []byte("ghijkl"),
2022 []byte("mnopqr"),
2023 },
2024 },
2025
2026 Leaf: &trillian.LogLeaf{
2027 LeafValue: leafBytes,
2028 MerkleLeafHash: []byte("ahash"),
2029 ExtraData: []byte("extra"),
2030 },
2031 },
2032 },
2033 {
2034 req: "leaf_index=1&tree_size=3",
2035 idx: 1,
2036 sz: 3,
2037 want: http.StatusOK,
2038 wantRsp: &proofRsp,
2039 rpcRsp: &trillian.GetEntryAndProofResponse{
2040 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
2041
2042 TreeSize: 300,
2043 }),
2044 Proof: &trillian.Proof{
2045 LeafIndex: 2,
2046 Hashes: [][]byte{
2047 []byte("abcdef"),
2048 []byte("ghijkl"),
2049 []byte("mnopqr"),
2050 },
2051 },
2052
2053 Leaf: &trillian.LogLeaf{
2054 LeafValue: leafBytes,
2055 MerkleLeafHash: []byte("ahash"),
2056 ExtraData: []byte("extra"),
2057 },
2058 },
2059 },
2060 {
2061 req: "leaf_index=0&tree_size=1",
2062 idx: 0,
2063 sz: 1,
2064 want: http.StatusOK,
2065 wantRsp: &ct.GetEntryAndProofResponse{
2066 LeafInput: leafBytes,
2067 ExtraData: []byte("extra"),
2068 },
2069 rpcRsp: &trillian.GetEntryAndProofResponse{
2070 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
2071
2072 TreeSize: 300,
2073 }),
2074 Proof: &trillian.Proof{
2075
2076 LeafIndex: 0,
2077 },
2078
2079 Leaf: &trillian.LogLeaf{
2080 LeafValue: leafBytes,
2081 MerkleLeafHash: []byte("ahash"),
2082 ExtraData: []byte("extra"),
2083 },
2084 },
2085 },
2086 {
2087 req: "leaf_index=0&tree_size=1",
2088 idx: 0,
2089 sz: 1,
2090 want: http.StatusInternalServerError,
2091 rpcRsp: &trillian.GetEntryAndProofResponse{
2092 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
2093
2094 TreeSize: 300,
2095 }),
2096
2097 Leaf: &trillian.LogLeaf{
2098 LeafValue: leafBytes,
2099 MerkleLeafHash: []byte("ahash"),
2100 ExtraData: []byte("extra"),
2101 },
2102 },
2103 },
2104 {
2105 req: "leaf_index=0&tree_size=1",
2106 idx: 0,
2107 sz: 1,
2108 want: http.StatusInternalServerError,
2109 rpcRsp: &trillian.GetEntryAndProofResponse{
2110 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
2111
2112 TreeSize: 300,
2113 }),
2114 Proof: &trillian.Proof{
2115
2116 LeafIndex: 0,
2117 },
2118
2119 },
2120 },
2121 }
2122
2123 info := setupTest(t, nil, nil)
2124 defer info.mockCtrl.Finish()
2125 handler := AppHandler{Info: info.li, Handler: getEntryAndProof, Name: "GetEntryAndProof", Method: http.MethodGet}
2126
2127 for _, test := range tests {
2128 info.setRemoteQuotaUser(test.wantQuotaUser)
2129 req, err := http.NewRequest("GET", fmt.Sprintf("/ct/v1/get-entry-and-proof?%s", test.req), nil)
2130 if err != nil {
2131 t.Errorf("Failed to create request: %v", err)
2132 continue
2133 }
2134
2135 if test.rpcRsp != nil || test.rpcErr != nil {
2136 req := &trillian.GetEntryAndProofRequest{LogId: 0x42, LeafIndex: test.idx, TreeSize: test.sz}
2137 if len(test.wantQuotaUser) > 0 {
2138 req.ChargeTo = &trillian.ChargeTo{User: []string{test.wantQuotaUser}}
2139 }
2140 info.client.EXPECT().GetEntryAndProof(deadlineMatcher(), cmpMatcher{req}).Return(test.rpcRsp, test.rpcErr)
2141 }
2142
2143 w := httptest.NewRecorder()
2144 handler.ServeHTTP(w, req)
2145 if got := w.Code; got != test.want {
2146 t.Errorf("getEntryAndProof(%s)=%d; want %d", test.req, got, test.want)
2147 }
2148 if test.errStr != "" {
2149 if body := w.Body.String(); !strings.Contains(body, test.errStr) {
2150 t.Errorf("getEntryAndProof(%q)=%q; want to find %q", test.req, body, test.errStr)
2151 }
2152 continue
2153 }
2154 if test.want != http.StatusOK {
2155 continue
2156 }
2157
2158 if got, want := w.Header().Get("Cache-Control"), "public"; !strings.Contains(got, want) {
2159 t.Errorf("getEntryAndProof(%q): Cache-Control response header = %q, want %q", test.req, got, want)
2160 }
2161
2162 var resp ct.GetEntryAndProofResponse
2163 if err = json.NewDecoder(w.Body).Decode(&resp); err != nil {
2164 t.Errorf("Failed to unmarshal json response %s: %v", w.Body.Bytes(), err)
2165 continue
2166 }
2167
2168 if diff := pretty.Compare(&resp, test.wantRsp); diff != "" {
2169 t.Errorf("getEntryAndProof(%q) diff:\n%v", test.req, diff)
2170 }
2171 }
2172 }
2173
2174 func createJSONChain(t *testing.T, p x509util.PEMCertPool) io.Reader {
2175 t.Helper()
2176 var req ct.AddChainRequest
2177 for _, rawCert := range p.RawCertificates() {
2178 req.Chain = append(req.Chain, rawCert.Raw)
2179 }
2180
2181 var buffer bytes.Buffer
2182
2183 writer := bufio.NewWriter(&buffer)
2184 err := json.NewEncoder(writer).Encode(&req)
2185 if err := writer.Flush(); err != nil {
2186 t.Error(err)
2187 }
2188
2189 if err != nil {
2190 t.Fatalf("Failed to create test json: %v", err)
2191 }
2192
2193 return bufio.NewReader(&buffer)
2194 }
2195
2196 func logLeafForCert(t *testing.T, certs []*x509.Certificate, merkleLeaf *ct.MerkleTreeLeaf, isPrecert bool) *trillian.LogLeaf {
2197 t.Helper()
2198 leafData, err := tls.Marshal(*merkleLeaf)
2199 if err != nil {
2200 t.Fatalf("failed to serialize leaf: %v", err)
2201 }
2202
2203 raw := extractRawCerts(certs)
2204 leafIDHash := sha256.Sum256(raw[0].Data)
2205
2206 extraData, err := util.ExtraDataForChain(raw[0], raw[1:], isPrecert)
2207 if err != nil {
2208 t.Fatalf("failed to serialize extra data: %v", err)
2209 }
2210
2211 return &trillian.LogLeaf{LeafIdentityHash: leafIDHash[:], LeafValue: leafData, ExtraData: extraData}
2212 }
2213
2214 type dlMatcher struct {
2215 }
2216
2217 func deadlineMatcher() gomock.Matcher {
2218 return dlMatcher{}
2219 }
2220
2221 func (d dlMatcher) Matches(x interface{}) bool {
2222 ctx, ok := x.(context.Context)
2223 if !ok {
2224 return false
2225 }
2226
2227 deadlineTime, ok := ctx.Deadline()
2228 if !ok {
2229 return false
2230 }
2231
2232 return deadlineTime == fakeDeadlineTime
2233 }
2234
2235 func (d dlMatcher) String() string {
2236 return fmt.Sprintf("deadline is %v", fakeDeadlineTime)
2237 }
2238
2239 func makeAddPrechainRequest(t *testing.T, li *logInfo, body io.Reader) *httptest.ResponseRecorder {
2240 t.Helper()
2241 handler := AppHandler{Info: li, Handler: addPreChain, Name: "AddPreChain", Method: http.MethodPost}
2242 return makeAddChainRequestInternal(t, handler, "add-pre-chain", body)
2243 }
2244
2245 func makeAddChainRequest(t *testing.T, li *logInfo, body io.Reader) *httptest.ResponseRecorder {
2246 t.Helper()
2247 handler := AppHandler{Info: li, Handler: addChain, Name: "AddChain", Method: http.MethodPost}
2248 return makeAddChainRequestInternal(t, handler, "add-chain", body)
2249 }
2250
2251 func makeAddChainRequestInternal(t *testing.T, handler AppHandler, path string, body io.Reader) *httptest.ResponseRecorder {
2252 t.Helper()
2253 req, err := http.NewRequest("POST", fmt.Sprintf("http://example.com/ct/v1/%s", path), body)
2254 if err != nil {
2255 t.Fatalf("Failed to create POST request: %v", err)
2256 }
2257
2258 w := httptest.NewRecorder()
2259 handler.ServeHTTP(w, req)
2260
2261 return w
2262 }
2263
2264 func makeGetRootResponseForTest(t *testing.T, stamp, treeSize int64, hash []byte) *trillian.GetLatestSignedLogRootResponse {
2265 t.Helper()
2266 return &trillian.GetLatestSignedLogRootResponse{
2267 SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
2268 TimestampNanos: uint64(stamp),
2269 TreeSize: uint64(treeSize),
2270 RootHash: hash,
2271 }),
2272 }
2273 }
2274
2275 func loadCertsIntoPoolOrDie(t *testing.T, certs []string) *x509util.PEMCertPool {
2276 t.Helper()
2277 pool := x509util.NewPEMCertPool()
2278 for _, cert := range certs {
2279 if !pool.AppendCertsFromPEM([]byte(cert)) {
2280 t.Fatalf("couldn't parse test certs: %v", certs)
2281 }
2282 }
2283 return pool
2284 }
2285
2286 func mustMarshalRoot(t *testing.T, lr *types.LogRootV1) *trillian.SignedLogRoot {
2287 t.Helper()
2288 rootBytes, err := lr.MarshalBinary()
2289 if err != nil {
2290 t.Fatalf("Failed to marshal root in test: %v", err)
2291 }
2292 return &trillian.SignedLogRoot{
2293 LogRoot: rootBytes,
2294 }
2295 }
2296
2297
2298
2299 type cmpMatcher struct{ want interface{} }
2300
2301 func (m cmpMatcher) Matches(got interface{}) bool {
2302 return cmp.Equal(got, m.want, cmp.Comparer(proto.Equal))
2303 }
2304 func (m cmpMatcher) String() string {
2305 return fmt.Sprintf("equals %v", m.want)
2306 }
2307
View as plain text