...

Source file src/github.com/google/certificate-transparency-go/client/logclient_test.go

Documentation: github.com/google/certificate-transparency-go/client

     1  // Copyright 2014 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 client_test
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"encoding/base64"
    21  	"encoding/hex"
    22  	"encoding/json"
    23  	"fmt"
    24  	"math"
    25  	"net/http"
    26  	"net/http/httptest"
    27  	"reflect"
    28  	"regexp"
    29  	"strconv"
    30  	"strings"
    31  	"testing"
    32  	"time"
    33  
    34  	ct "github.com/google/certificate-transparency-go"
    35  	"github.com/google/certificate-transparency-go/client"
    36  	"github.com/google/certificate-transparency-go/jsonclient"
    37  	"github.com/google/certificate-transparency-go/testdata"
    38  	"github.com/google/certificate-transparency-go/tls"
    39  	"github.com/google/certificate-transparency-go/x509"
    40  	"github.com/google/certificate-transparency-go/x509util"
    41  )
    42  
    43  func dh(s string) []byte {
    44  	b, err := hex.DecodeString(s)
    45  	if err != nil {
    46  		panic(err)
    47  	}
    48  	return b
    49  }
    50  
    51  const (
    52  	ValidSTHResponseTreeSize          = 3721782
    53  	ValidSTHResponseTimestamp         = 1396609800587
    54  	ValidSTHResponseSHA256RootHash    = "SxKOxksguvHPyUaKYKXoZHzXl91Q257+JQ0AUMlFfeo="
    55  	ValidSTHResponseTreeHeadSignature = "BAMARjBEAiBUYO2tODlUUw4oWGiVPUHqZadRRyXs9T2rSXchA79VsQIgLASkQv3cu4XdPFCZbgFkIUefniNPCpO3LzzHX53l+wg="
    56  
    57  	PrecertEntryB64          = "AAAAAAFLSYHwyAABN2DieQ8zpJj5tsFJ/s/KOZOVS1NvvzatRdCoQVt5M30ABHowggR2oAMCAQICEAUyKYw5aj4l/KoZd+gntfMwDQYJKoZIhvcNAQELBQAwbTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHzAdBgNVBAsTFkZPUiBURVNUIFBVUlBPU0VTIE9OTFkxJTAjBgNVBAMTHEdlb1RydXN0IEVWIFNTTCBURVNUIENBIC0gRzQwHhcNMTUwMjAyMDAwMDAwWhcNMTYwMjI3MjM1OTU5WjCBwzETMBEGCysGAQQBgjc8AgEDEwJHQjEbMBkGCysGAQQBgjc8AgECFApDYWxpZm9ybmlhMR4wHAYLKwYBBAGCNzwCAQEMDU1vdW50YWluIFZpZXcxCzAJBgNVBAYTAkdCMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MR0wGwYDVQQKDBRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEWMBQGA1UEAwwNc2RmZWRzZi50cnVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGdl97zn/gpxl6gmaMlcpizP/Z1RR/cVkGiIjR67kpWIB9MGkBvLxmBXYbewaYRdo59VWyOM6fxtMeNsZzOrlQOl64fBmCy7k+M/yBFuEqdoig0l0RAbs6u0LCNRv2rNUOz2G6nCGJ6YaUpt5Onatxrd2vI1bPU/iHixKqSz9M7RedBIGjgaDor7/rR3y/DILjdvwL/tgPSz3R5gnf9lla1rNRWWbDl12HgLc+VxTCVVVqTGtW/qbSWfARdXxLeLWtTfNk68q2LReVUC9QyeYdtE+N2+2SXeOEN+lYWW5Ab036d7k5GAntMBzLKftZEkYYquvaiSkqu2PSaCSLKT7UCAwEAAaOCAdEwggHNMEcGA1UdEQRAMD6CDWtqYXNkaGYudHJ1c3SCC3NzZGZzLnRydXN0gg1zZGZlZHNmLnRydXN0ghF3d3cuc2RmZWRzZi50cnVzdDAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIFoDArBgNVHR8EJDAiMCCgHqAchhpodHRwOi8vZ20uc3ltY2IuY29tL2dtLmNybDCBoAYDVR0gBIGYMIGVMIGSBgkrBgEEAfAiAQYwgYQwPwYIKwYBBQUHAgEWM2h0dHBzOi8vd3d3Lmdlb3RydXN0LmNvbS9yZXNvdXJjZXMvcmVwb3NpdG9yeS9sZWdhbDBBBggrBgEFBQcCAjA1DDNodHRwczovL3d3dy5nZW90cnVzdC5jb20vcmVzb3VyY2VzL3JlcG9zaXRvcnkvbGVnYWwwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFLFplGGr5ssMTOdZr1pJixgzweFHMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYTaHR0cDovL2dtLnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2dtLnN5bWNiLmNvbS9nbS5jcnQAAA=="
    58  	PrecertEntryExtraDataB64 = "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"
    59  
    60  	CertEntryB64          = "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"
    61  	CertEntryExtraDataB64 = "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"
    62  
    63  	SubmissionCertB64 = "MIIEijCCA3KgAwIBAgICEk0wDQYJKoZIhvcNAQELBQAwKzEpMCcGA1UEAwwgY2Fja2xpbmcgY3J5cHRvZ3JhcGhlciBmYWtlIFJPT1QwHhcNMTUxMDIxMjAxMTUyWhcNMjAxMDE5MjAxMTUyWjAfMR0wGwYDVQQDExRoYXBweSBoYWNrZXIgZmFrZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMIKR3maBcUSsncXYzQT13D5Nr+Z3mLxMMh3TUdt6sACmqbJ0btRlgXfMtNLM2OU1I6a3Ju+tIZSdn2v21JBwvxUzpZQ4zy2cimIiMQDZCQHJwzC9GZn8HaW091iz9H0Go3A7WDXwYNmsdLNRi00o14UjoaVqaPsYrZWvRKaIRqaU0hHmS0AWwQSvN/93iMIXuyiwywmkwKbWnnxCQ/gsctKFUtcNrwEx9Wgj6KlhwDTyI1QWSBbxVYNyUgPFzKxrSmwMO0yNff7ho+QT9x5+Y/7XE59S4Mc4ZXxcXKew/gSlN9U5mvT+D2BhDtkCupdfsZNCQWp27A+b/DmrFI9NqsCAwEAAaOCAcIwggG+MBIGA1UdEwEB/wQIMAYBAf8CAQAwQwYDVR0eBDwwOqE4MAaCBC5taWwwCocIAAAAAAAAAAAwIocgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwDgYDVR0PAQH/BAQDAgGGMH8GCCsGAQUFBwEBBHMwcTAyBggrBgEFBQcwAYYmaHR0cDovL2lzcmcudHJ1c3RpZC5vY3NwLmlkZW50cnVzdC5jb20wOwYIKwYBBQUHMAKGL2h0dHA6Ly9hcHBzLmlkZW50cnVzdC5jb20vcm9vdHMvZHN0cm9vdGNheDMucDdjMB8GA1UdIwQYMBaAFOmkP+6epeby1dd5YDyTpi4kjpeqMFQGA1UdIARNMEswCAYGZ4EMAQIBMD8GCysGAQQBgt8TAQEBMDAwLgYIKwYBBQUHAgEWImh0dHA6Ly9jcHMucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5pZGVudHJ1c3QuY29tL0RTVFJPT1RDQVgzQ1JMLmNybDAdBgNVHQ4EFgQU+3hPEvlgFYMsnxd/NBmzLjbqQYkwDQYJKoZIhvcNAQELBQADggEBAA0YAeLXOklx4hhCikUUl+BdnFfn1g0W5AiQLVNIOL6PnqXu0wjnhNyhqdwnfhYMnoy4idRh4lB6pz8Gf9pnlLd/DnWSV3gS+/I/mAl1dCkKby6H2V790e6IHmIK2KYm3jm+U++FIdGpBdsQTSdmiX/rAyuxMDM0adMkNBwTfQmZQCz6nGHw1QcSPZMvZpsC8SkvekzxsjF1otOrMUPNPQvtTWrVx8GlR2qfx/4xbQa1v2frNvFBCmO59goz+jnWvfTtj2NjwDZ7vlMBsPm16dbKYC840uvRoZjxqsdc3ChCZjqimFqlNG/xoPA8+dTicZzCXE9ijPIcvW6y1aa3bGw="
    64  	AddJSONResp       = `{
    65  	   "sct_version":0,
    66  	   "id":"KHYaGJAn++880NYaAY12sFBXKcenQRvMvfYE9F1CYVM=",
    67  	   "timestamp":1337,
    68  	   "extensions":"",
    69  	   "signature":"BAMARjBEAiAIc21J5ZbdKZHw5wLxCP+MhBEsV5+nfvGyakOIv6FOvAIgWYMZb6Pw///uiNM7QTg2Of1OqmK1GbeGuEl9VJN8v8c="
    70  	}`
    71  	ProofByHashResp = `
    72  	{
    73  		"leaf_index": 3,
    74  		"audit_path": [
    75  		"pMumx96PIUB3TX543ljlpQ/RgZRqitRfykupIZrXq0Q=",
    76  		"5s2NQWkjmesu+Kqgp70TCwVLwq8obpHw/JyMGwN56pQ=",
    77  		"7VelXijfmGFSl62BWIsG8LRmxJGBq9XP8FxmszuT2Cg="
    78  		]
    79  	}`
    80  	GetRootsResp = `
    81  	{
    82  		"certificates":[
    83  		"MIIFLjCCAxagAwIBAgIQNgEiBHAkH6lLUWKp42Ob1DANBgkqhkiG9w0BAQ0FADAWMRQwEgYDVQQDEwtlc2lnbml0Lm9yZzAeFw0xNDA2MjAxODM3NTRaFw0zMDA2MjAxODQ3NDZaMBYxFDASBgNVBAMTC2VzaWduaXQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtylZx/zTLxRDsok14XO0Z3PvWMIY4HWro0YLgCF8dYv3tUaNkmN3ghlQvY8UcByH2LMOBGiQAcMHxgEJ53cnWRyc2DjoGhkDkiPdS2JttNEB0B/XTaGvaHwJh2CSgIBbpZpWTaqGywbe7AgJQ81L8h7tZ4E6W8ZM0vt4mnzqkPBT+BmyjTXG/McGhYTQAsmdsYZDBAdB2Y4X1/RAyL0e9MHdSboRofhg+8d5MeC0VEIgHXU/R4f4wz/pSw0FI9xxWJR3UUK/qOWqNsVYZfmCu6+ksDQtezxSTAuymoL094Dwn+hnXb8RS6dEbIQ+b0bIHxxpypcxH7rBMIpQcbZ8JSqNVDZPI9QahKNPQMQiuBE66KlqbnLOj7lGBxsbpU2Dx8QL8W96op6dTGtniFyXqhuYN2UxDMNI+fb1j9G7ENpoqvTVfjxa4RUU6uZ9ZygOiiOZD4P54vEQFteiu4OM+mWOm5Vll9yPXqHPc5oiCfyvCNVzfapqPoGbaCM6oQtcHdAca9VpE2eDTo36zfdFo31YYBOEjWNsfXwp8frNduS/L6gmWYrd91HeEoOVX2ZQKqBLp5ydW72xDSeCIr5kugqdY6whW80ugjLlc9mDd8/LEGQQKnrxzeeWdjiQG/WwcOse9GRktOzH2gvmkJ+vY82z1jhrZP4REoA6T+aYGR8CAwEAAaN4MHYwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFPOGsFKraD+/FoPAUXSf77qYfZHRMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFEq/BT//OC3eNeJ4wEfNqJXdZRNpMA0GCSqGSIb3DQEBDQUAA4ICAQBEvh2kzI+1uoUx/emM654QvpM6WtgQSJMubKwKeBY5UNgwwNpwmtswiEKzdZwBiGb1xEehPrAKz0d7aiIIEOonYEohIV6szl0+F56nN16813n1lPsCjdLSA8fjgf28jvlTKcrLRqeyCn4APadh6g7/FRiGcmIxEFPf/VNTUBZ7l4e2zzb06PxCq8oDaOsbAVYXQz8A0KX50KURZrdC2knUg1HX0J/orVpdaQ9UZYVNp2WAbe9vYTCCF5FdtzNU+nJDojpDxF5guMe9bifL3YTvd87YQwsH7+o+UbtHX4lG8VsSfmvvJulNBY6RtzZEpZvyRWIvQahM9qTrzFpsxl4wyPSBDPLDZ6YvVWsXvU4PqLOWTbPdq4BB24P9kFxeYjEe/rDQ8bd1/V/OFZTEM0rxdZDDN9vWnybzl8xL5VmNLDGl1u6JrOVvCzVAWP++L9l5UTusQI/BPSMebz6msd8vhTluD4jQIba1/6zOwfBraFgCIktCT3GEIiyt59x3rdSirLyjzmeQA9NkwoG/GqlFlSdWmQCK/sCL+z050rqjL0kEwIl/D6ncCXfBvhCpCmcrIlZFruyeOlsISZ410T1w/pLK8OXhbCr13Gb7A5jhv1nn811cQaR7XUXhcn6Wq/VV/oQZLunBYvoYOs3dc8wpBabPrrRhkdNmN6Rib6TvMg=="
    84  		]
    85  	}`
    86  	GetSTHConsistencyResp = `{ "consistency": [ "IqlrapPQKtmCY1jCr8+lpCtscRyjjZAA7nyadtFPRFQ=", "ytf6K2GnSRZ3Au+YkivCb7N1DygfKyZmE4aEs9OXl\/8=" ] }`
    87  	GetEntryAndProofResp  = `{
    88      "leaf_input": "AAAAAAFhw8UTtQAAAAJ1MIICcTCCAhegAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29nbGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhvcml0eTAgFw0xNjEyMDcxNTEzMzZaGA8wMDAxMDEwMTAwMDAwMFowVjELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UECgwGR29vZ2xlMQwwCgYDVQQLDANFbmcxFzAVBgNVBAMMDmxlYWYwMS5jc3IucGVtMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6zdOUkWcRtWouMXtWLkwKaZwimmgJlyeL264ayNshOFGOpg2gkSliheLQYIy9C3gCFt+BzhS/EdWKCeb7WCLrKOBszCBsDAPBgNVHQ8BAf8EBQMDB/mAMIGLBgNVHQ4EgYMEgYBPRBC+90lR8pRLbTi3ID4j0WRzjoJOT3MGkKko87o8z6gEifk9zCwOiHeIgclTA0ZUTxXMRI5r+nUY0frjRCWZu4uthPlE90iJM+RyjcNTwDJGu2StvLnJ8y4t5fdnwdGssncXiBQMuM7/1eMEwAOfHgTFzJ0UBC2Umztl0hul3zAPBgNVHSMECDAGgAQBAgMEMAoGCCqGSM49BAMCA0gAMEUCIQCrwywGKvyt/BwR+e7yDs78qt4sSEVJltv7Y0W6gOI5awIgQ+IAjejYivLEfqNufFRezCBWHWhbq/HHGdNQtv6EArkAAA==",
    89  		"extra_data": "RXh0cmEK",
    90  		"audit_path": [
    91  		"pMumx96PIUB3TX543ljlpQ/RgZRqitRfykupIZrXq0Q=",
    92  		"5s2NQWkjmesu+Kqgp70TCwVLwq8obpHw/JyMGwN56pQ=",
    93  		"7VelXijfmGFSl62BWIsG8LRmxJGBq9XP8FxmszuT2Cg="
    94  		]
    95    }`
    96  )
    97  
    98  func b64(s string) []byte {
    99  	b, err := base64.StdEncoding.DecodeString(s)
   100  	if err != nil {
   101  		panic(err)
   102  	}
   103  	return b
   104  }
   105  
   106  // serveHandlerAt returns a test HTTP server that only expects requests at the given path, and invokes
   107  // the provided handler for that path.
   108  func serveHandlerAt(t *testing.T, path string, handler func(http.ResponseWriter, *http.Request)) *httptest.Server {
   109  	t.Helper()
   110  	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   111  		if r.URL.Path == path {
   112  			handler(w, r)
   113  		} else {
   114  			t.Fatalf("Incorrect URL path: %s", r.URL.Path)
   115  		}
   116  	}))
   117  }
   118  
   119  // serveRspAt returns a test HTTP server that returns a canned response body rsp for a given path.
   120  func serveRspAt(t *testing.T, path, rsp string) *httptest.Server {
   121  	t.Helper()
   122  	return serveHandlerAt(t, path, func(w http.ResponseWriter, r *http.Request) {
   123  		if _, err := fmt.Fprint(w, rsp); err != nil {
   124  			t.Fatal(err)
   125  		}
   126  	})
   127  }
   128  
   129  func sctToJSON(rawSCT []byte) ([]byte, error) {
   130  	var sct ct.SignedCertificateTimestamp
   131  	_, err := tls.Unmarshal(rawSCT, &sct)
   132  	if err != nil {
   133  		return nil, fmt.Errorf("failed to tls-unmarshal test certificate proof: %v", err)
   134  	}
   135  	data, err := json.Marshal(sct)
   136  	if err != nil {
   137  		return nil, fmt.Errorf("failed to json-marshal test certificate proof: %v", err)
   138  	}
   139  	return data, nil
   140  }
   141  
   142  // serveSCTAt returns a test HTTP server that returns the given SCT as a canned response for
   143  // a given path.
   144  func serveSCTAt(t *testing.T, path string, rawSCT []byte) *httptest.Server {
   145  	t.Helper()
   146  	return serveHandlerAt(t, path, func(w http.ResponseWriter, r *http.Request) {
   147  		data, err := sctToJSON(rawSCT)
   148  		if err != nil {
   149  			t.Fatal(err)
   150  		}
   151  		if _, err := w.Write(data); err != nil {
   152  			t.Fatal(err)
   153  		}
   154  	})
   155  }
   156  
   157  func TestGetEntries(t *testing.T) {
   158  	ts := serveHandlerAt(t, "/ct/v1/get-entries", func(w http.ResponseWriter, r *http.Request) {
   159  		q := r.URL.Query()
   160  		numRE := regexp.MustCompile("[0-9]+")
   161  		if !numRE.MatchString(q["start"][0]) || !numRE.MatchString(q["end"][0]) {
   162  			t.Fatalf("Invalid parameter: start=%q, end=%q", q["start"][0], q["end"][0])
   163  		}
   164  		_, err := fmt.Fprintf(w, `{"entries":[{"leaf_input": "%s","extra_data": "%s"},{"leaf_input": "%s","extra_data": "%s"}]}`,
   165  			PrecertEntryB64,
   166  			PrecertEntryExtraDataB64,
   167  			CertEntryB64,
   168  			CertEntryExtraDataB64)
   169  		if err != nil {
   170  			t.Fatal(err)
   171  		}
   172  	})
   173  	defer ts.Close()
   174  	lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
   175  	if err != nil {
   176  		t.Fatalf("Failed to create client: %v", err)
   177  	}
   178  	leaves, err := lc.GetEntries(context.Background(), 0, 1)
   179  	if err != nil {
   180  		t.Errorf("GetEntries(0,1)=nil,%v; want 2 leaves,nil", err)
   181  	} else if len(leaves) != 2 {
   182  		t.Errorf("GetEntries(0,1)=%d leaves,nil; want 2 leaves,nil", len(leaves))
   183  	}
   184  }
   185  
   186  func TestGetEntriesErrors(t *testing.T) {
   187  	ctx := context.Background()
   188  	var tests = []struct {
   189  		desc       string
   190  		start, end int64
   191  		rsp, want  string
   192  	}{
   193  		{desc: "eof", start: 1, end: 2, rsp: "", want: "EOF"},
   194  		{desc: "negative end", start: 0, end: -1, want: "end should be >= 0"},
   195  		{desc: "invalid range", start: 3, end: 2, want: "start should be <= end"},
   196  		{desc: "non json input", start: 4, end: 5, rsp: "not-json", want: "invalid"},
   197  		{desc: "leaf_input not base64", start: 5, end: 6, rsp: `{"entries":[{"leaf_input":"bogus","extra_data":"Z29vZA=="}]}`, want: "illegal base64"},
   198  		{desc: "extra_data not base64", start: 5, end: 6, rsp: `{"entries":[{"leaf_input":"Z29vZA","extra_data":"bogus"}]}`, want: "illegal base64"},
   199  		{desc: "both not base64", start: 5, end: 6, rsp: `{"entries":[{"leaf_input":"bogus","extra_data":"bogus"}]}`, want: "illegal base64"},
   200  		{desc: "bad json", start: 6, end: 7, rsp: `{"entries":[{"leaf_input":"bbbb","extra_data":"bbbb"}]}`, want: "failed to unmarshal"},
   201  	}
   202  
   203  	for _, test := range tests {
   204  		t.Run(test.desc, func(t *testing.T) {
   205  			ts := serveRspAt(t, "/ct/v1/get-entries", test.rsp)
   206  			defer ts.Close()
   207  			lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
   208  			if err != nil {
   209  				t.Fatalf("Failed to create client: %v", err)
   210  			}
   211  			got, err := lc.GetEntries(ctx, test.start, test.end)
   212  			if err == nil {
   213  				t.Errorf("GetEntries(%d, %d)=%+v, nil; want nil, %q", test.start, test.end, got, test.want)
   214  			} else if !strings.Contains(err.Error(), test.want) {
   215  				t.Errorf("GetEntries(%d, %d)=nil, %q; want nil, %q", test.start, test.end, err, test.want)
   216  			}
   217  			if got != nil {
   218  				t.Errorf("GetEntries(%d, %d)=%+v, _; want nil, _", test.start, test.end, got)
   219  			}
   220  		})
   221  	}
   222  }
   223  
   224  func TestGetRawEntriesErrors(t *testing.T) {
   225  	ctx := context.Background()
   226  	var tests = []struct {
   227  		desc       string
   228  		start, end int64
   229  		rsp, want  string
   230  	}{
   231  		{desc: "empty", start: 1, end: 2, rsp: "", want: "EOF"},
   232  		{desc: "negative end", start: 0, end: -1, want: "end should be >= 0"},
   233  		{desc: "bad range", start: 3, end: 2, want: "start should be <= end"},
   234  		{desc: "invalid json", start: 4, end: 5, rsp: "not-json", want: "invalid"},
   235  		{desc: "leaf_input not base64", start: 5, end: 6, rsp: `{"entries":[{"leaf_input":"bogus","extra_data":"Z29vZA=="}]}`, want: "illegal base64"},
   236  		{desc: "extra_data not base64", start: 5, end: 6, rsp: `{"entries":[{"leaf_input":"Z29vZA==","extra_data":"bogus"}]}`, want: "illegal base64"},
   237  		{desc: "both not base64", start: 5, end: 6, rsp: `{"entries":[{"leaf_input":"bogus","extra_data":"bogus"}]}`, want: "illegal base64"},
   238  	}
   239  
   240  	for _, test := range tests {
   241  		t.Run(test.desc, func(t *testing.T) {
   242  			ts := serveRspAt(t, "/ct/v1/get-entries", test.rsp)
   243  			defer ts.Close()
   244  			lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
   245  			if err != nil {
   246  				t.Fatalf("Failed to create client: %v", err)
   247  			}
   248  			got, err := lc.GetRawEntries(ctx, test.start, test.end)
   249  			if err == nil {
   250  				t.Errorf("GetRawEntries(%d, %d)=%+v, nil; want nil, %q", test.start, test.end, got, test.want)
   251  			} else if !strings.Contains(err.Error(), test.want) {
   252  				t.Errorf("GetRawEntries(%d, %d)=nil, %q; want nil, %q", test.start, test.end, err, test.want)
   253  			}
   254  			if got != nil {
   255  				t.Errorf("GetRawEntries(%d, %d)=%+v, _; want nil, _", test.start, test.end, got)
   256  			}
   257  			if len(test.rsp) > 0 {
   258  				// Expect the error to include the HTTP response
   259  				if rspErr, ok := err.(client.RspError); !ok {
   260  					t.Errorf("GetRawEntries(%d, %d)=nil, .(%T); want nil, .(RspError)", test.start, test.end, err)
   261  				} else if string(rspErr.Body) != test.rsp {
   262  					t.Errorf("GetRawEntries(%d, %d)=nil, .Body=%q; want nil, .Body=%q", test.start, test.end, rspErr.Body, test.rsp)
   263  				}
   264  			}
   265  		})
   266  	}
   267  }
   268  
   269  func TestGetSTH(t *testing.T) {
   270  	ts := serveRspAt(t, "/ct/v1/get-sth",
   271  		fmt.Sprintf(`{"tree_size": %d, "timestamp": %d, "sha256_root_hash": "%s", "tree_head_signature": "%s"}`,
   272  			ValidSTHResponseTreeSize,
   273  			int64(ValidSTHResponseTimestamp),
   274  			ValidSTHResponseSHA256RootHash,
   275  			ValidSTHResponseTreeHeadSignature))
   276  	defer ts.Close()
   277  	lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
   278  	if err != nil {
   279  		t.Fatalf("Failed to create client: %v", err)
   280  	}
   281  	sth, err := lc.GetSTH(context.Background())
   282  	if err != nil {
   283  		t.Fatal(err)
   284  	}
   285  	if sth.TreeSize != ValidSTHResponseTreeSize {
   286  		t.Errorf("GetSTH().TreeSize=%d; want %d", sth.TreeSize, ValidSTHResponseTreeSize)
   287  	}
   288  	if sth.Timestamp != ValidSTHResponseTimestamp {
   289  		t.Errorf("GetSTH().Timestamp=%v; want %v", sth.Timestamp, ValidSTHResponseTimestamp)
   290  	}
   291  	if sth.SHA256RootHash.Base64String() != ValidSTHResponseSHA256RootHash {
   292  		t.Errorf("GetSTH().SHA256RootHash=%v; want %v", sth.SHA256RootHash.Base64String(), ValidSTHResponseSHA256RootHash)
   293  	}
   294  	wantRawSignature, err := base64.StdEncoding.DecodeString(ValidSTHResponseTreeHeadSignature)
   295  	if err != nil {
   296  		t.Fatalf("Couldn't b64 decode 'correct' STH signature: %v", err)
   297  	}
   298  	var wantDS ct.DigitallySigned
   299  	if _, err := tls.Unmarshal(wantRawSignature, &wantDS); err != nil {
   300  		t.Fatalf("Couldn't unmarshal DigitallySigned: %v", err)
   301  	}
   302  	if sth.TreeHeadSignature.Algorithm.Hash != wantDS.Algorithm.Hash {
   303  		t.Errorf("GetSTH().TreeHeadSignature.Algorithm.Hash=%v; %v", wantDS.Algorithm.Hash, sth.TreeHeadSignature.Algorithm.Hash)
   304  	}
   305  	if sth.TreeHeadSignature.Algorithm.Signature != wantDS.Algorithm.Signature {
   306  		t.Errorf("GetSTH().TreeHeadSignature.Algorithm.Signature=%v; want %v", wantDS.Algorithm.Signature, sth.TreeHeadSignature.Algorithm.Signature)
   307  	}
   308  	if !bytes.Equal(sth.TreeHeadSignature.Signature, wantDS.Signature) {
   309  		t.Errorf("GetSTH().TreeHeadSignature.Signature=%v; want %v", wantDS.Signature, sth.TreeHeadSignature.Signature)
   310  	}
   311  }
   312  
   313  func TestGetSTHErrors(t *testing.T) {
   314  	ctx := context.Background()
   315  	var tests = []struct {
   316  		desc      string
   317  		rsp, want string
   318  	}{
   319  		{desc: "empty", rsp: "", want: "EOF"},
   320  		{desc: "bad json", rsp: "not-json", want: "invalid"},
   321  		{desc: "root not base64", rsp: `{"tree_size":228163,"timestamp":1507127718502,"sha256_root_hash":"bogus","tree_head_signature":"Z29vZA=="}`, want: "illegal base64"},
   322  		{desc: "sig not base64", rsp: `{"tree_size":228163,"timestamp":1507127718502,"sha256_root_hash":"Z29vZA==","tree_head_signature":"bogus"}`, want: "illegal base64"},
   323  		{desc: "both not base64", rsp: `{"tree_size":228163,"timestamp":1507127718502,"sha256_root_hash":"bogus","tree_head_signature":"bogus"}`, want: "illegal base64"},
   324  		{desc: "invalid hash len", rsp: `{"tree_size":228163,"timestamp":1507127718502,"sha256_root_hash":"bbbb","tree_head_signature":"bbbb"}`, want: "hash is invalid length"},
   325  		{desc: "bad tls data", rsp: `{"tree_size":228163,"timestamp":1507127718502,"sha256_root_hash":"tncuLXiPAo711IOxjaYTwLmwbSyyE8hEcRhaOXvFb3g=","tree_head_signature":"Z29vZA=="}`, want: "tls: syntax error"},
   326  		{desc: "trailing junk", rsp: `{"tree_size":228163,"timestamp":1507127718502,"sha256_root_hash":"tncuLXiPAo711IOxjaYTwLmwbSyyE8hEcRhaOXvFb3g=","tree_head_signature":"BAMARjBEAiAi5045/h8Yvs1mNlsYskWvuFbu2A6hO2J45KDFfOR1OwIgZ2jq8iFCwKuTbcIgsBB1ibHEupv97CeAQynK0Dw2PT8bbbb="}`, want: "trailing data"},
   327  	}
   328  
   329  	for _, test := range tests {
   330  		t.Run(test.desc, func(t *testing.T) {
   331  			ts := serveRspAt(t, "/ct/v1/get-sth", test.rsp)
   332  			defer ts.Close()
   333  			lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
   334  			if err != nil {
   335  				t.Fatalf("Failed to create client: %v", err)
   336  			}
   337  			got, err := lc.GetSTH(ctx)
   338  			if err == nil {
   339  				t.Errorf("GetSTH()=%+v, nil; want nil, %q", got, test.want)
   340  			} else if !strings.Contains(err.Error(), test.want) {
   341  				t.Errorf("GetSTH()=nil, %q; want nil, %q", err, test.want)
   342  			}
   343  			if got != nil {
   344  				t.Errorf("GetSTH()=%+v, _; want nil, _", got)
   345  			}
   346  			if len(test.rsp) > 0 {
   347  				// Expect the error to include the HTTP response
   348  				if rspErr, ok := err.(client.RspError); !ok {
   349  					t.Errorf("GetSTH()=nil, .(%T); want nil, .(RspError)", err)
   350  				} else if string(rspErr.Body) != test.rsp {
   351  					t.Errorf("GetSTH()=nil, .Body=%q; want nil, .Body=%q", rspErr.Body, test.rsp)
   352  				}
   353  			}
   354  		})
   355  	}
   356  }
   357  
   358  func TestAddChainRetries(t *testing.T) {
   359  	if testing.Short() {
   360  		t.Skip("skipping retry test in short mode")
   361  	}
   362  	retryAfter := 0 * time.Second
   363  	currentFailures := 0
   364  	failuresBeforeSuccess := 0
   365  	hs := serveHandlerAt(t, "/ct/v1/add-chain", func(w http.ResponseWriter, r *http.Request) {
   366  		if failuresBeforeSuccess > 0 && currentFailures < failuresBeforeSuccess {
   367  			currentFailures++
   368  			if retryAfter != 0 {
   369  				if retryAfter > 0 {
   370  					w.Header().Add("Retry-After", strconv.Itoa(int(retryAfter.Seconds())))
   371  				}
   372  				w.WriteHeader(503)
   373  				return
   374  			}
   375  			w.WriteHeader(408)
   376  			return
   377  		}
   378  		_, err := w.Write([]byte(AddJSONResp))
   379  		if err != nil {
   380  			return
   381  		}
   382  	})
   383  	defer hs.Close()
   384  
   385  	certBytes, err := base64.StdEncoding.DecodeString(SubmissionCertB64)
   386  	if err != nil {
   387  		t.Fatalf("Failed to decode chain array B64: %s", err)
   388  	}
   389  	chain := []ct.ASN1Cert{{Data: certBytes}}
   390  
   391  	const leeway = time.Millisecond * 100
   392  	const leewayRatio = 0.2 // 20%
   393  
   394  	tests := []struct {
   395  		desc                  string
   396  		deadlineLength        time.Duration // -1 indicates no deadline
   397  		expected              time.Duration
   398  		retryAfter            time.Duration // -1 indicates: generate 503 with no Retry-After
   399  		failuresBeforeSuccess int
   400  		success               bool
   401  	}{
   402  		{
   403  			desc:                  "success first time",
   404  			deadlineLength:        -1,
   405  			expected:              1 * time.Millisecond,
   406  			retryAfter:            0,
   407  			failuresBeforeSuccess: 0,
   408  			success:               true,
   409  		},
   410  		{
   411  			desc:                  "multiple attempts 503",
   412  			deadlineLength:        -1,
   413  			expected:              7 * time.Second, // 1 + 2 + 4
   414  			retryAfter:            -1,
   415  			failuresBeforeSuccess: 3,
   416  			success:               true,
   417  		},
   418  		{
   419  			desc:                  "one retry",
   420  			deadlineLength:        6 * time.Second,
   421  			expected:              5 * time.Second,
   422  			retryAfter:            5 * time.Second,
   423  			failuresBeforeSuccess: 1,
   424  			success:               true,
   425  		},
   426  		{
   427  			desc:                  "fail after one retry",
   428  			deadlineLength:        5 * time.Second,
   429  			expected:              5 * time.Second,
   430  			retryAfter:            10 * time.Second,
   431  			failuresBeforeSuccess: 1,
   432  			success:               false,
   433  		},
   434  		{
   435  			desc:                  "multiple retries",
   436  			deadlineLength:        10 * time.Second,
   437  			expected:              5 * time.Second,
   438  			retryAfter:            1 * time.Second,
   439  			failuresBeforeSuccess: 5,
   440  			success:               true,
   441  		},
   442  		{
   443  			desc:                  "immediate 10 retries",
   444  			deadlineLength:        1 * time.Second,
   445  			expected:              10 * time.Millisecond,
   446  			retryAfter:            0,
   447  			failuresBeforeSuccess: 10,
   448  			success:               true,
   449  		},
   450  	}
   451  
   452  	for i, test := range tests {
   453  		t.Run(test.desc, func(t *testing.T) {
   454  			deadline := context.Background()
   455  			lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{})
   456  			if err != nil {
   457  				t.Fatalf("Failed to create client: %v", err)
   458  			}
   459  			if test.deadlineLength >= 0 {
   460  				var cancel context.CancelFunc
   461  				deadline, cancel = context.WithDeadline(context.Background(), time.Now().Add(test.deadlineLength))
   462  				defer cancel()
   463  			}
   464  			retryAfter = test.retryAfter
   465  			failuresBeforeSuccess = test.failuresBeforeSuccess
   466  			currentFailures = 0
   467  
   468  			started := time.Now()
   469  			sct, err := lc.AddChain(deadline, chain)
   470  			took := time.Since(started)
   471  			delta := math.Abs(float64(took - test.expected))
   472  			ratio := delta / float64(test.expected)
   473  			if delta > float64(leeway) && ratio > leewayRatio {
   474  				t.Errorf("#%d Submission took an unexpected length of time: %s, expected ~%s", i, took, test.expected)
   475  			}
   476  			if test.success && err != nil {
   477  				t.Errorf("#%d Failed to submit chain: %s", i, err)
   478  			} else if !test.success && err == nil {
   479  				t.Errorf("#%d Expected AddChain to fail", i)
   480  			}
   481  			if test.success && sct == nil {
   482  				t.Errorf("#%d Nil SCT returned", i)
   483  			}
   484  		})
   485  	}
   486  }
   487  
   488  func TestAddChain(t *testing.T) {
   489  	hs := serveSCTAt(t, "/ct/v1/add-chain", testdata.TestCertProof)
   490  	defer hs.Close()
   491  	lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{PublicKey: testdata.LogPublicKeyPEM})
   492  	if err != nil {
   493  		t.Fatalf("Failed to create client: %v", err)
   494  	}
   495  
   496  	cert, err := x509util.CertificateFromPEM([]byte(testdata.TestCertPEM))
   497  	if x509.IsFatal(err) {
   498  		t.Fatalf("Failed to parse certificate from PEM: %v", err)
   499  	}
   500  
   501  	// AddChain will verify the signature because the client has a public key.
   502  	chain := []ct.ASN1Cert{{Data: cert.Raw}}
   503  	_, err = lc.AddChain(context.Background(), chain)
   504  	if err != nil {
   505  		t.Errorf("AddChain()=nil,%v; want sct,nil", err)
   506  	}
   507  }
   508  
   509  func TestAddPreChain(t *testing.T) {
   510  	hs := serveSCTAt(t, "/ct/v1/add-pre-chain", testdata.TestPreCertProof)
   511  	defer hs.Close()
   512  	lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{PublicKey: testdata.LogPublicKeyPEM})
   513  	if err != nil {
   514  		t.Fatalf("Failed to create client: %v", err)
   515  	}
   516  
   517  	cert, err := x509util.CertificateFromPEM([]byte(testdata.TestPreCertPEM))
   518  	if x509.IsFatal(err) {
   519  		t.Fatalf("Failed to parse pre-certificate from PEM: %v", err)
   520  	}
   521  	issuer, err := x509util.CertificateFromPEM([]byte(testdata.CACertPEM))
   522  	if x509.IsFatal(err) {
   523  		t.Fatalf("Failed to parse issuer certificate from PEM: %v", err)
   524  	}
   525  
   526  	// AddPreChain will verify the signature because the client has a public key.
   527  	chain := []ct.ASN1Cert{{Data: cert.Raw}, {Data: issuer.Raw}}
   528  	_, err = lc.AddPreChain(context.Background(), chain)
   529  	if err != nil {
   530  		t.Errorf("AddPreChain()=nil,%v; want sct,nil", err)
   531  	}
   532  }
   533  
   534  func TestGetSTHConsistency(t *testing.T) {
   535  	hs := serveRspAt(t, "/ct/v1/get-sth-consistency", GetSTHConsistencyResp)
   536  	defer hs.Close()
   537  	lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{})
   538  	if err != nil {
   539  		t.Fatalf("Failed to create client: %v", err)
   540  	}
   541  
   542  	tests := []struct {
   543  		first  uint64
   544  		second uint64
   545  		proof  [][]byte
   546  	}{
   547  		{1, 3, [][]byte{
   548  			b64("IqlrapPQKtmCY1jCr8+lpCtscRyjjZAA7nyadtFPRFQ="),
   549  			b64("ytf6K2GnSRZ3Au+YkivCb7N1DygfKyZmE4aEs9OXl/8="),
   550  		}},
   551  	}
   552  
   553  	for _, test := range tests {
   554  		proof, err := lc.GetSTHConsistency(context.Background(), test.first, test.second)
   555  		if err != nil {
   556  			t.Errorf("GetSTHConsistency(%d, %d)=nil,%v; want proof,nil", test.first, test.second, err)
   557  		} else if !reflect.DeepEqual(proof, test.proof) {
   558  			t.Errorf("GetSTHConsistency(%d, %d)=%v,nil; want %v,nil", test.first, test.second, proof, test.proof)
   559  		}
   560  	}
   561  }
   562  
   563  func TestGetSTHConsistencyErrors(t *testing.T) {
   564  	ctx := context.Background()
   565  	var tests = []struct {
   566  		desc          string
   567  		first, second uint64
   568  		rsp, want     string
   569  	}{
   570  		{desc: "empty", first: 1, second: 2, rsp: "", want: "EOF"},
   571  		{desc: "invalid json", first: 1, second: 2, rsp: "not-json", want: "invalid"},
   572  		{desc: "not base64", first: 1, second: 2, rsp: `{"consistency":["bogus"]}`, want: "illegal base64"},
   573  		{desc: "bad proof", first: 1, second: 2, rsp: `{"consistency":["2SyPbmCNzn9l7dhWVz1uz6nW7DB7p0EkSsfH9M+qU5E=",]}`, want: "invalid"},
   574  	}
   575  
   576  	for _, test := range tests {
   577  		t.Run(test.desc, func(t *testing.T) {
   578  			ts := serveRspAt(t, "/ct/v1/get-sth-consistency", test.rsp)
   579  			defer ts.Close()
   580  			lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
   581  			if err != nil {
   582  				t.Fatalf("Failed to create client: %v", err)
   583  			}
   584  			got, err := lc.GetSTHConsistency(ctx, test.first, test.second)
   585  			if err == nil {
   586  				t.Errorf("GetSTHConsistency(%d, %d)=%+v, nil; want nil, %q", test.first, test.second, got, test.want)
   587  			} else if !strings.Contains(err.Error(), test.want) {
   588  				t.Errorf("GetSTHConsistency(%d, %d)=nil, %q; want nil, %q", test.first, test.second, err, test.want)
   589  			}
   590  			if got != nil {
   591  				t.Errorf("GetSTHConsistency(%d, %d)=%+v, _; want nil, _", test.first, test.second, got)
   592  			}
   593  			if len(test.rsp) > 0 {
   594  				// Expect the error to include the HTTP response
   595  				if rspErr, ok := err.(client.RspError); !ok {
   596  					t.Errorf("GetSTHConsistency(%d, %d)=nil, .(%T); want nil, .(RspError)", test.first, test.second, err)
   597  				} else if string(rspErr.Body) != test.rsp {
   598  					t.Errorf("GetSTHConsistency(%d, %d)=nil, .Body=%q; want nil, .Body=%q", test.first, test.second, rspErr.Body, test.rsp)
   599  				}
   600  			}
   601  		})
   602  	}
   603  }
   604  
   605  func TestGetProofByHash(t *testing.T) {
   606  	hs := serveRspAt(t, "/ct/v1/get-proof-by-hash", ProofByHashResp)
   607  	defer hs.Close()
   608  	lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{})
   609  	if err != nil {
   610  		t.Fatalf("Failed to create client: %v", err)
   611  	}
   612  
   613  	tests := []struct {
   614  		desc     string
   615  		hash     []byte
   616  		treeSize uint64
   617  	}{
   618  		{desc: "ok", hash: dh("4a9e8edbe5ce2d2da69d483edb45186675d4be37b649d40923b156a7d1277463"), treeSize: 5},
   619  	}
   620  
   621  	for _, test := range tests {
   622  		t.Run(test.desc, func(t *testing.T) {
   623  			resp, err := lc.GetProofByHash(context.Background(), test.hash, test.treeSize)
   624  			if err != nil {
   625  				t.Errorf("GetProofByHash(%v, %v)=nil,%v; want proof,nil", test.hash, test.treeSize, err)
   626  			} else if got := len(resp.AuditPath); got < 1 {
   627  				t.Errorf("len(GetProofByHash(%v, %v)): %v; want > 1", test.hash, test.treeSize, got)
   628  			}
   629  		})
   630  	}
   631  }
   632  
   633  func TestGetProofByHashErrors(t *testing.T) {
   634  	ctx := context.Background()
   635  	aHash := dh("4a9e8edbe5ce2d2da69d483edb45186675d4be37b649d40923b156a7d1277463")
   636  	var tests = []struct {
   637  		desc      string
   638  		rsp, want string
   639  	}{
   640  		{desc: "empty", rsp: "", want: "EOF"},
   641  		{desc: "bad json", rsp: "not-json", want: "invalid"},
   642  		{desc: "path not base64", rsp: `{"leaf_index": 17, "audit_path":["bogus"]}`, want: "illegal base64"},
   643  		{desc: "malformed json", rsp: `{"leaf_index": 17, "audit_path":["Z29vZA==",]}`, want: "invalid"},
   644  	}
   645  
   646  	for _, test := range tests {
   647  		t.Run(test.desc, func(t *testing.T) {
   648  			ts := serveRspAt(t, "/ct/v1/get-proof-by-hash", test.rsp)
   649  			defer ts.Close()
   650  			lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
   651  			if err != nil {
   652  				t.Fatalf("Failed to create client: %v", err)
   653  			}
   654  			got, err := lc.GetProofByHash(ctx, aHash, 100)
   655  			if err == nil {
   656  				t.Errorf("GetProofByHash()=%+v, nil; want nil, %q", got, test.want)
   657  			} else if !strings.Contains(err.Error(), test.want) {
   658  				t.Errorf("GetProofByHash()=nil, %q; want nil, %q", err, test.want)
   659  			}
   660  			if got != nil {
   661  				t.Errorf("GetProofByHash()=%+v, _; want nil, _", got)
   662  			}
   663  			if len(test.rsp) > 0 {
   664  				// Expect the error to include the HTTP response
   665  				if rspErr, ok := err.(client.RspError); !ok {
   666  					t.Errorf("GetProofByHash()=nil, .(%T); want nil, .(RspError)", err)
   667  				} else if string(rspErr.Body) != test.rsp {
   668  					t.Errorf("GetProofByHash()=nil, .Body=%q; want nil, .Body=%q", rspErr.Body, test.rsp)
   669  				}
   670  			}
   671  		})
   672  	}
   673  }
   674  
   675  func TestGetAcceptedRoots(t *testing.T) {
   676  	hs := serveRspAt(t, "/ct/v1/get-roots", GetRootsResp)
   677  	defer hs.Close()
   678  	lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{})
   679  	if err != nil {
   680  		t.Fatalf("Failed to create client: %v", err)
   681  	}
   682  
   683  	certs, err := lc.GetAcceptedRoots(context.Background())
   684  	if err != nil {
   685  		t.Errorf("GetAcceptedRoots()=nil,%q; want roots,nil", err.Error())
   686  	} else if len(certs) < 1 {
   687  		t.Errorf("len(GetAcceptedRoots())=0; want > 1")
   688  	}
   689  }
   690  
   691  func TestGetAcceptedRootsErrors(t *testing.T) {
   692  	ctx := context.Background()
   693  	var tests = []struct {
   694  		desc      string
   695  		rsp, want string
   696  	}{
   697  		{desc: "empty", rsp: "", want: "EOF"},
   698  		{desc: "not json", rsp: "not-json", want: "invalid"},
   699  		{desc: "not base64", rsp: `{"certificates":["bogus"]}`, want: "illegal base64"},
   700  		{desc: "malformed json", rsp: `{"certificates":["bbbb",]}`, want: "invalid"},
   701  	}
   702  
   703  	for _, test := range tests {
   704  		t.Run(test.desc, func(t *testing.T) {
   705  			ts := serveRspAt(t, "/ct/v1/get-roots", test.rsp)
   706  			defer ts.Close()
   707  			lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
   708  			if err != nil {
   709  				t.Fatalf("Failed to create client: %v", err)
   710  			}
   711  			got, err := lc.GetAcceptedRoots(ctx)
   712  			if err == nil {
   713  				t.Errorf("GetAcceptedRoots()=%+v, nil; want nil, %q", got, test.want)
   714  			} else if !strings.Contains(err.Error(), test.want) {
   715  				t.Errorf("GetAcceptedRoots()=nil, %q; want nil, %q", err, test.want)
   716  			}
   717  			if got != nil {
   718  				t.Errorf("GetAcceptedRoots()=%+v, _; want nil, _", got)
   719  			}
   720  			if len(test.rsp) > 0 {
   721  				// Expect the error to include the HTTP response
   722  				if rspErr, ok := err.(client.RspError); !ok {
   723  					t.Errorf("GetAcceptedRoots()=nil, .(%T); want nil, .(RspError)", err)
   724  				} else if string(rspErr.Body) != test.rsp {
   725  					t.Errorf("GetAcceptedRoots()=nil, .Body=%q; want nil, .Body=%q", rspErr.Body, test.rsp)
   726  				}
   727  			}
   728  		})
   729  	}
   730  }
   731  
   732  func TestGetEntryAndProof(t *testing.T) {
   733  	hs := serveRspAt(t, "/ct/v1/get-entry-and-proof", GetEntryAndProofResp)
   734  	defer hs.Close()
   735  	lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{})
   736  	if err != nil {
   737  		t.Fatalf("Failed to create client: %v", err)
   738  	}
   739  
   740  	tests := []struct {
   741  		index    uint64
   742  		treeSize uint64
   743  	}{
   744  		{1000, 2000},
   745  	}
   746  
   747  	for _, test := range tests {
   748  		resp, err := lc.GetEntryAndProof(context.Background(), test.index, test.treeSize)
   749  		if err != nil {
   750  			t.Errorf("GetEntryAndProof(%v, %v)=nil,%v; want proof,nil", test.index, test.treeSize, err)
   751  		} else if got := len(resp.AuditPath); got < 1 {
   752  			t.Errorf("len(GetEntryAndProof(%v, %v)): %v; want > 1", test.index, test.treeSize, got)
   753  		}
   754  	}
   755  }
   756  
   757  func TestGetEntryAndProofErrors(t *testing.T) {
   758  	ctx := context.Background()
   759  	var tests = []struct {
   760  		desc      string
   761  		rsp, want string
   762  	}{
   763  		{desc: "empty", rsp: "", want: "EOF"},
   764  		{desc: "bad json", rsp: "not-json", want: "invalid"},
   765  		{desc: "leaf_input not base64", rsp: `{"leaf_input": "bogus", "extra_data": "Z29vZAo=", "audit_path": ["Z29vZAo="]}`, want: "illegal base64"},
   766  		{desc: "extra_data not base64", rsp: `{"leaf_input": "Z29vZAo=", "extra_data": "bogus", "audit_path": ["Z29vZAo="]}`, want: "illegal base64"},
   767  		{desc: "audit_path no base64", rsp: `{"leaf_input": "Z29vZAo=", "extra_data": "Z29vZAo=", "audit_path": ["bogus"]}`, want: "illegal base64"},
   768  		{desc: "malformed json", rsp: `{"leaf_input": "Z29vZAo=", "extra_data": "Z29vZAo=", "audit_path": ["bbbb",]}`, want: "invalid"},
   769  	}
   770  
   771  	for _, test := range tests {
   772  		t.Run(test.desc, func(t *testing.T) {
   773  			ts := serveRspAt(t, "/ct/v1/get-entry-and-proof", test.rsp)
   774  			defer ts.Close()
   775  			lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
   776  			if err != nil {
   777  				t.Fatalf("Failed to create client: %v", err)
   778  			}
   779  			got, err := lc.GetEntryAndProof(ctx, 99, 100)
   780  			if err == nil {
   781  				t.Errorf("GetEntryAndProof()=%+v, nil; want nil, %q", got, test.want)
   782  			} else if !strings.Contains(err.Error(), test.want) {
   783  				t.Errorf("GetEntryAndProof()=nil, %q; want nil, %q", err, test.want)
   784  			}
   785  			if got != nil {
   786  				t.Errorf("GetEntryAndProof()=%+v, _; want nil, _", got)
   787  			}
   788  			if len(test.rsp) > 0 {
   789  				// Expect the error to include the HTTP response
   790  				if rspErr, ok := err.(client.RspError); !ok {
   791  					t.Errorf("GetEntryAndProof()=nil, .(%T); want nil, .(RspError)", err)
   792  				} else if string(rspErr.Body) != test.rsp {
   793  					t.Errorf("GetEntryAndProof()=nil, .Body=%q; want nil, .Body=%q", rspErr.Body, test.rsp)
   794  				}
   795  			}
   796  		})
   797  	}
   798  }
   799  

View as plain text