Skip to content

Commit 1740ce3

Browse files
committed
addChain, addPreChain, addChainWhitespace tests
1 parent 8c6dfe7 commit 1740ce3

File tree

1 file changed

+260
-0
lines changed

1 file changed

+260
-0
lines changed

internal/scti/handlers_test.go

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
package scti
1616

1717
import (
18+
"bufio"
1819
"bytes"
1920
"context"
2021
"encoding/base64"
22+
"encoding/hex"
2123
"encoding/json"
2224
"encoding/pem"
2325
"io"
@@ -346,3 +348,261 @@ func TestGetRoots(t *testing.T) {
346348
}
347349
})
348350
}
351+
352+
// TODO(phboneff): this could just be a parseBodyJSONChain test
353+
func TestAddChainWhitespace(t *testing.T) {
354+
// Throughout we use variants of a hard-coded POST body derived from a chain of:
355+
// testdata.LeafSignedByFakeIntermediateCertPEM, testdata.FakeIntermediateCertPEM
356+
cert, rest := pem.Decode([]byte(testdata.CertFromIntermediate))
357+
if len(rest) > 0 {
358+
t.Fatalf("got %d bytes remaining after decoding cert, want 0", len(rest))
359+
}
360+
certB64 := base64.StdEncoding.EncodeToString(cert.Bytes)
361+
intermediate, rest := pem.Decode([]byte(testdata.IntermediateFromRoot))
362+
if len(rest) > 0 {
363+
t.Fatalf("got %d bytes remaining after decoding intermediate, want 0", len(rest))
364+
}
365+
intermediateB64 := base64.StdEncoding.EncodeToString(intermediate.Bytes)
366+
367+
// Break the JSON into chunks:
368+
intro := "{\"chain\""
369+
// followed by colon then the first line of the PEM file
370+
chunk1a := "[\"" + certB64[:64]
371+
// straight into rest of first entry
372+
chunk1b := certB64[64:] + "\""
373+
// followed by comma then
374+
chunk2 := "\"" + intermediateB64 + "\""
375+
epilog := "]}\n"
376+
377+
var tests = []struct {
378+
descr string
379+
body string
380+
want int
381+
}{
382+
{
383+
descr: "valid",
384+
body: intro + ":" + chunk1a + chunk1b + "," + chunk2 + epilog,
385+
want: http.StatusOK,
386+
},
387+
{
388+
descr: "valid-space-between",
389+
body: intro + " : " + chunk1a + chunk1b + " , " + chunk2 + epilog,
390+
want: http.StatusOK,
391+
},
392+
{
393+
descr: "valid-newline-between",
394+
body: intro + " : " + chunk1a + chunk1b + ",\n" + chunk2 + epilog,
395+
want: http.StatusOK,
396+
},
397+
{
398+
descr: "invalid-raw-newline-in-string",
399+
body: intro + ":" + chunk1a + "\n" + chunk1b + "," + chunk2 + epilog,
400+
want: http.StatusBadRequest,
401+
},
402+
{
403+
descr: "valid-escaped-newline-in-string",
404+
body: intro + ":" + chunk1a + "\\n" + chunk1b + "," + chunk2 + epilog,
405+
want: http.StatusOK,
406+
},
407+
}
408+
409+
log := setupTestLog(t)
410+
server := setupTestServer(t, log, path.Join(prefix, "ct/v1/add-chain"))
411+
defer server.Close()
412+
413+
for _, test := range tests {
414+
t.Run(test.descr, func(t *testing.T) {
415+
resp, err := http.Post(server.URL+"/ct/v1/add-chain", "application/json", strings.NewReader(test.body))
416+
if err != nil {
417+
t.Fatalf("http.Post(%s)=(_,%q); want (_,nil)", types.AddChainPath, err)
418+
}
419+
if got, want := resp.StatusCode, test.want; got != want {
420+
t.Errorf("http.Post(%s)=(%d,nil); want (%d,nil)", types.AddChainPath, got, want)
421+
}
422+
})
423+
}
424+
}
425+
426+
func TestAddChain(t *testing.T) {
427+
var tests = []struct {
428+
descr string
429+
chain []string
430+
want int
431+
err error
432+
}{
433+
{
434+
descr: "leaf-only",
435+
chain: []string{testdata.CertFromIntermediate},
436+
want: http.StatusBadRequest,
437+
},
438+
{
439+
descr: "wrong-entry-type",
440+
chain: []string{testdata.PreCertFromIntermediate},
441+
want: http.StatusBadRequest,
442+
},
443+
{
444+
descr: "success-without-root",
445+
chain: []string{testdata.CertFromIntermediate, testdata.IntermediateFromRoot},
446+
want: http.StatusOK,
447+
},
448+
{
449+
descr: "success",
450+
chain: []string{testdata.CertFromIntermediate, testdata.IntermediateFromRoot, testdata.CACertPEM},
451+
want: http.StatusOK,
452+
},
453+
}
454+
455+
log := setupTestLog(t)
456+
server := setupTestServer(t, log, path.Join(prefix, "ct/v1/add-chain"))
457+
defer server.Close()
458+
459+
for _, test := range tests {
460+
t.Run(test.descr, func(t *testing.T) {
461+
pool := loadCertsIntoPoolOrDie(t, test.chain)
462+
chain := createJSONChain(t, *pool)
463+
464+
resp, err := http.Post(server.URL+"/ct/v1/add-chain", "application/json", chain)
465+
if err != nil {
466+
t.Fatalf("http.Post(%s)=(_,%q); want (_,nil)", types.AddChainPath, err)
467+
}
468+
if got, want := resp.StatusCode, test.want; got != want {
469+
t.Errorf("http.Post(%s)=(%d,nil); want (%d,nil)", types.AddChainPath, got, want)
470+
}
471+
if test.want == http.StatusOK {
472+
var gotRsp types.AddChainResponse
473+
if err := json.NewDecoder(resp.Body).Decode(&gotRsp); err != nil {
474+
t.Fatalf("json.Decode()=%v; want nil", err)
475+
}
476+
if got, want := types.Version(gotRsp.SCTVersion), types.V1; got != want {
477+
t.Errorf("resp.SCTVersion=%v; want %v", got, want)
478+
}
479+
if got, want := gotRsp.ID, demoLogID[:]; !bytes.Equal(got, want) {
480+
t.Errorf("resp.ID=%v; want %v", got, want)
481+
}
482+
if got, want := gotRsp.Timestamp, fakeTimeMillis; got != want {
483+
t.Errorf("resp.Timestamp=%d; want %d", got, want)
484+
}
485+
if got, want := hex.EncodeToString(gotRsp.Signature), "040300067369676e6564"; got != want {
486+
t.Errorf("resp.Signature=%s; want %s", got, want)
487+
}
488+
// TODO(phboneff): read from the log and compare values
489+
// TODO(phboneff): add a test with a backend write failure
490+
// TODO(phboneff): check that the index is in the SCT
491+
// TODO(phboneff): add a test with a not after range
492+
// TODO(phboneff): add a test with a start date only
493+
// TODO(phboneff): add duplicate tests
494+
}
495+
})
496+
}
497+
}
498+
499+
func TestAddPreChain(t *testing.T) {
500+
var tests = []struct {
501+
descr string
502+
chain []string
503+
want int
504+
err error
505+
}{
506+
{
507+
descr: "leaf-signed-by-different",
508+
chain: []string{testdata.PrecertPEMValid, testdata.FakeIntermediateCertPEM},
509+
want: http.StatusBadRequest,
510+
},
511+
{
512+
descr: "wrong-entry-type",
513+
chain: []string{testdata.TestCertPEM},
514+
want: http.StatusBadRequest,
515+
},
516+
{
517+
descr: "success",
518+
chain: []string{testdata.PrecertPEMValid, testdata.CACertPEM},
519+
want: http.StatusOK,
520+
},
521+
{
522+
descr: "success-with-intermediate",
523+
chain: []string{testdata.PreCertFromIntermediate, testdata.IntermediateFromRoot, testdata.CACertPEM},
524+
want: http.StatusOK,
525+
},
526+
{
527+
descr: "success-without-root",
528+
chain: []string{testdata.PrecertPEMValid},
529+
want: http.StatusOK,
530+
},
531+
}
532+
533+
log := setupTestLog(t)
534+
server := setupTestServer(t, log, path.Join(prefix, "ct/v1/add-pre-chain"))
535+
defer server.Close()
536+
537+
for _, test := range tests {
538+
t.Run(test.descr, func(t *testing.T) {
539+
pool := loadCertsIntoPoolOrDie(t, test.chain)
540+
chain := createJSONChain(t, *pool)
541+
542+
resp, err := http.Post(server.URL+"/ct/v1/add-pre-chain", "application/json", chain)
543+
if err != nil {
544+
t.Fatalf("http.Post(%s)=(_,%q); want (_,nil)", types.AddPreChainPath, err)
545+
}
546+
if got, want := resp.StatusCode, test.want; got != want {
547+
t.Errorf("http.Post(%s)=(%d,nil); want (%d,nil)", types.AddPreChainPath, got, want)
548+
}
549+
if test.want == http.StatusOK {
550+
var gotRsp types.AddChainResponse
551+
if err := json.NewDecoder(resp.Body).Decode(&gotRsp); err != nil {
552+
t.Fatalf("json.Decode()=%v; want nil", err)
553+
}
554+
if got, want := types.Version(gotRsp.SCTVersion), types.V1; got != want {
555+
t.Errorf("resp.SCTVersion=%v; want %v", got, want)
556+
}
557+
if got, want := gotRsp.ID, demoLogID[:]; !bytes.Equal(got, want) {
558+
t.Errorf("resp.ID=%v; want %v", got, want)
559+
}
560+
if got, want := gotRsp.Timestamp, fakeTimeMillis; got != want {
561+
t.Errorf("resp.Timestamp=%d; want %d", got, want)
562+
}
563+
if got, want := hex.EncodeToString(gotRsp.Signature), "040300067369676e6564"; got != want {
564+
t.Errorf("resp.Signature=%s; want %s", got, want)
565+
}
566+
// TODO(phboneff): read from the log and compare values
567+
// TODO(phboneff): add a test with a backend write failure
568+
// TODO(phboneff): check that the index is in the SCT
569+
// TODO(phboneff): add a test with a not after range
570+
// TODO(phboneff): add a test with a start date only
571+
// TODO(phboneff): add duplicate tests
572+
}
573+
})
574+
}
575+
}
576+
577+
func createJSONChain(t *testing.T, p x509util.PEMCertPool) io.Reader {
578+
t.Helper()
579+
var req types.AddChainRequest
580+
for _, rawCert := range p.RawCertificates() {
581+
req.Chain = append(req.Chain, rawCert.Raw)
582+
}
583+
584+
var buffer bytes.Buffer
585+
// It's tempting to avoid creating and flushing the intermediate writer but it doesn't work
586+
writer := bufio.NewWriter(&buffer)
587+
err := json.NewEncoder(writer).Encode(&req)
588+
if err := writer.Flush(); err != nil {
589+
t.Error(err)
590+
}
591+
592+
if err != nil {
593+
t.Fatalf("Failed to create test json: %v", err)
594+
}
595+
596+
return bufio.NewReader(&buffer)
597+
}
598+
599+
func loadCertsIntoPoolOrDie(t *testing.T, certs []string) *x509util.PEMCertPool {
600+
t.Helper()
601+
pool := x509util.NewPEMCertPool()
602+
for _, cert := range certs {
603+
if !pool.AppendCertsFromPEM([]byte(cert)) {
604+
t.Fatalf("couldn't parse test certs: %v", certs)
605+
}
606+
}
607+
return pool
608+
}

0 commit comments

Comments
 (0)