...

Source file src/github.com/google/certificate-transparency-go/trillian/ctfe/handlers_test.go

Documentation: github.com/google/certificate-transparency-go/trillian/ctfe

     1  // Copyright 2016 Google LLC. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    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  // Arbitrary time for use in tests
    59  var fakeTime = time.Date(2016, 7, 22, 11, 01, 13, 0, time.UTC)
    60  var fakeTimeMillis = uint64(fakeTime.UnixNano() / millisPerNano)
    61  
    62  // The deadline should be the above bumped by 500ms
    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  // setupTest creates mock objects and contexts.  Caller should invoke info.mockCtrl.Finish().
   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  	// Anything in the post handler list should reject GET
   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  	// Anything in the get handler list should reject POST.
   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  			// Check each entrypoint has a handler
   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  			// We want to see the same set of handler names that we think we registered.
   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  	// Throughout we use variants of a hard-coded POST body derived from a chain of:
   353  	pemChain := []string{cttestonly.LeafSignedByFakeIntermediateCertPEM, cttestonly.FakeIntermediateCertPEM}
   354  
   355  	// Break the JSON into chunks:
   356  	intro := "{\"chain\""
   357  	// followed by colon then the first line of the PEM file
   358  	chunk1a := "[\"MIIH6DCCBtCgAwIBAgIIQoIqW4Zvv+swDQYJKoZIhvcNAQELBQAwcjELMAkGA1UE"
   359  	// straight into rest of first entry
   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  	// followed by comma then
   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  	// Which (if successful) produces a QueueLeaf response with a Merkle leaf:
   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  	// The generated LogLeaf will include the root cert as well.
   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 // hex-encoded
   441  		want            int
   442  		err             error
   443  		remoteQuotaUser string
   444  		enableCertQuota bool
   445  		// if remote quota enabled, it must be the first entry here
   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  					// The submitted chain may not include a root, but the generated LogLeaf will
   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 // hex-encoded
   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  					// The submitted chain may not include a root, but the generated LogLeaf will
   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 // hex-encoded
   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  		// Run deferred funcs at the end of each iteration.
   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  	// Create a couple of valid serialized ct.MerkleTreeLeaf objects
   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, // Not large enough - only indices 0 and 1 valid.
   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  		// Leaf data should be passed through as-is even if invalid.
  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 // same as end if zero
  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  	// This tests that only valid ranges make it to the backend for get-entries.
  1168  	// We're testing request handling up to the point where we make the RPC so arrange for
  1169  	// it to fail with a specific error.
  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  				// If an RPC was emitted, it should have received and propagated an error.
  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, // Not large enough to handle the request.
  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  			// Check undecoded JSON to confirm use of '[]' not 'null'
  1295  			httpJSON: "{\"leaf_index\":0,\"audit_path\":[]}",
  1296  		},
  1297  		{
  1298  			req:  "tree_size=1&hash=YWhhc2g=",
  1299  			want: http.StatusOK,
  1300  			// Want quota
  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  			// Check undecoded JSON to confirm use of '[]' not 'null'
  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  					// Second proof ignored.
  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  							{}, // missing hash
  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  			// Hash with URL-encoded %2B -> '+'.
  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  					// Returned tree large enough to include the leaf.
  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  					// Returned tree larger than needed to include the leaf.
  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  			// Also check the JSON string is as expected
  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  			// Check a nil proof is passed through as '[]' not 'null' in raw JSON.
  1553  			httpJSON: "{\"consistency\":[]}",
  1554  		},
  1555  		{
  1556  			req:  "first=0&second=1",
  1557  			want: http.StatusOK,
  1558  			// Want quota
  1559  			wantQuotaUser: remoteQuotaUser,
  1560  			httpRsp: &ct.GetSTHConsistencyResponse{
  1561  				Consistency: nil,
  1562  			},
  1563  			// Check a nil proof is passed through as '[]' not 'null' in raw JSON.
  1564  			httpJSON: "{\"consistency\":[]}",
  1565  		},
  1566  		{
  1567  			// Check that unrecognized parameters are ignored.
  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  						{}, // missing hash
  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, // Tree not large enough to serve the request.
  1640  				}),
  1641  				Proof: &trillian.Proof{
  1642  					LeafIndex: 2,
  1643  					Hashes: [][]byte{
  1644  						auditHashes[0],
  1645  						{}, // missing hash
  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], // wrong size hash
  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  			// Check a nil proof is passed through as '[]' not 'null' in raw JSON.
  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  			// Check a nil proof is passed through as '[]' not 'null' in raw JSON.
  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  					// Backend returns a tree size too small to satisfy the proof.
  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  					// Backend returns a tree size just large enough to satisfy the proof.
  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  					// Backend returns a tree size larger than needed to satisfy the proof.
  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  			// Also check the JSON string is as expected
  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  			// No result data in backend response
  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  					// Server returns a tree not large enough for the proof.
  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  				// To match merkleLeaf above.
  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  			// wantQuota
  1971  			wantQuotaUser: remoteQuotaUser,
  1972  			rpcRsp: &trillian.GetEntryAndProofResponse{
  1973  				SignedLogRoot: mustMarshalRoot(t, &types.LogRootV1{
  1974  					// Server returns a tree not large enough for the proof.
  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  				// To match merkleLeaf above.
  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  					// Server returns a tree not large enough for the proof.
  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  					// Server returns a tree just large enough for the proof.
  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  				// To match merkleLeaf above.
  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  					// Server returns a tree larger than needed for the proof.
  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  				// To match merkleLeaf above.
  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  					// Server returns a tree larger than needed for the proof.
  2072  					TreeSize: 300,
  2073  				}),
  2074  				Proof: &trillian.Proof{
  2075  					// Empty proof OK for requested tree size of 1.
  2076  					LeafIndex: 0,
  2077  				},
  2078  				// To match merkleLeaf above.
  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  					// Server returns a tree larger than needed for the proof.
  2094  					TreeSize: 300,
  2095  				}),
  2096  				// No proof.
  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  					// Server returns a tree larger than needed for the proof.
  2112  					TreeSize: 300,
  2113  				}),
  2114  				Proof: &trillian.Proof{
  2115  					// Empty proof OK for requested tree size of 1.
  2116  					LeafIndex: 0,
  2117  				},
  2118  				// No leaf.
  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  		// The result we expect after a roundtrip in the successful get entry and proof test
  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  	// It's tempting to avoid creating and flushing the intermediate writer but it doesn't work
  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 // we never make RPC calls without a deadline set
  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  // cmpMatcher is a custom gomock.Matcher that uses cmp.Equal combined with a
  2298  // cmp.Comparer that knows how to properly compare proto.Message types.
  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