diff options
| author | Rasmus Dahlberg <rasmus.dahlberg@kau.se> | 2021-02-23 11:59:46 +0100 | 
|---|---|---|
| committer | Rasmus Dahlberg <rasmus.dahlberg@kau.se> | 2021-02-23 11:59:46 +0100 | 
| commit | b4dd74a3f36ca46b7bcda69e8d95949babfeb76d (patch) | |
| tree | 6f3cbe32be4e675ba385e2575e28cf7a0f4dca00 /types | |
| parent | 1505c6db8e0e66d88a645ac5cc36647a6f8b2f43 (diff) | |
renamed and reordered to improve readability
Diffstat (limited to 'types')
| -rw-r--r-- | types/namespace.go | 56 | ||||
| -rw-r--r-- | types/namespace_pool.go | 61 | ||||
| -rw-r--r-- | types/namespace_pool_test.go | 91 | ||||
| -rw-r--r-- | types/namespace_test.go | 198 | ||||
| -rw-r--r-- | types/serialize.go | 28 | ||||
| -rw-r--r-- | types/serialize_test.go (renamed from types/item_test.go) | 502 | ||||
| -rw-r--r-- | types/stitem.go (renamed from types/item.go) | 22 | ||||
| -rw-r--r-- | types/stitem_test.go | 40 | 
8 files changed, 509 insertions, 489 deletions
| diff --git a/types/namespace.go b/types/namespace.go index f221960..3c6b90a 100644 --- a/types/namespace.go +++ b/types/namespace.go @@ -89,59 +89,3 @@ func (ns *Namespace) Verify(message, signature []byte) error {  	}  	return nil  } - -// NamespacePool is a pool of namespaces that contain complete verification keys -type NamespacePool struct { -	pool map[string]*Namespace -	list []*Namespace -	// If we need to update this structure without a restart => add mutex. -} - -// NewNameSpacePool creates a new namespace pool from a list of namespaces.  An -// error is returned if there are duplicate namespaces or namespaces without a -// complete verification key.  The latter is determined by namespaceWithKey(). -func NewNamespacePool(namespaces []*Namespace) (*NamespacePool, error) { -	np := &NamespacePool{ -		pool: make(map[string]*Namespace), -		list: make([]*Namespace, 0), -	} -	for _, namespace := range namespaces { -		if !namespaceWithKey(namespace.Format) { -			return nil, fmt.Errorf("need verification key in namespace pool: %v", namespace.Format) -		} -		if _, ok := np.pool[namespace.String()]; ok { -			return nil, fmt.Errorf("duplicate namespace: %v", namespace.String()) -		} -		np.pool[namespace.String()] = namespace -		np.list = append(np.list, namespace) -	} -	return np, nil -} - -// Find checks if namespace is a member of the namespace pool. -func (np *NamespacePool) Find(namespace *Namespace) (*Namespace, bool) { -	if _, ok := np.pool[namespace.String()]; !ok { -		return nil, false -	} -	// If the passed namespace is a key fingerprint the actual key needs to be -	// attached before returning.  Not applicable for Ed25519.  Docdoc later. -	return namespace, true -} - -// List returns a copied list of namespaces that is used by this pool. -func (np *NamespacePool) List() []*Namespace { -	namespaces := make([]*Namespace, len(np.list)) -	copy(namespaces, np.list) -	return namespaces -} - -// namespaceWithKey returns true if a namespace format contains a complete -// verification key.  I.e., some formats might have a key fingerprint instead. -func namespaceWithKey(format NamespaceFormat) bool { -	switch format { -	case NamespaceFormatEd25519V1: -		return true -	default: -		return false -	} -} diff --git a/types/namespace_pool.go b/types/namespace_pool.go new file mode 100644 index 0000000..0f30567 --- /dev/null +++ b/types/namespace_pool.go @@ -0,0 +1,61 @@ +package types + +import ( +	"fmt" +) + +// NamespacePool is a pool of namespaces that contain complete verification keys +type NamespacePool struct { +	pool map[string]*Namespace +	list []*Namespace +	// If we need to update this structure without a restart => add mutex. +} + +// NewNameSpacePool creates a new namespace pool from a list of namespaces.  An +// error is returned if there are duplicate namespaces or namespaces without a +// complete verification key.  The latter is determined by namespaceWithKey(). +func NewNamespacePool(namespaces []*Namespace) (*NamespacePool, error) { +	np := &NamespacePool{ +		pool: make(map[string]*Namespace), +		list: make([]*Namespace, 0), +	} +	for _, namespace := range namespaces { +		if !namespaceWithKey(namespace.Format) { +			return nil, fmt.Errorf("need verification key in namespace pool: %v", namespace.Format) +		} +		if _, ok := np.pool[namespace.String()]; ok { +			return nil, fmt.Errorf("duplicate namespace: %v", namespace.String()) +		} +		np.pool[namespace.String()] = namespace +		np.list = append(np.list, namespace) +	} +	return np, nil +} + +// Find checks if namespace is a member of the namespace pool. +func (np *NamespacePool) Find(namespace *Namespace) (*Namespace, bool) { +	if _, ok := np.pool[namespace.String()]; !ok { +		return nil, false +	} +	// If the passed namespace is a key fingerprint the actual key needs to be +	// attached before returning.  Not applicable for Ed25519.  Docdoc later. +	return namespace, true +} + +// List returns a copied list of namespaces that is used by this pool. +func (np *NamespacePool) List() []*Namespace { +	namespaces := make([]*Namespace, len(np.list)) +	copy(namespaces, np.list) +	return namespaces +} + +// namespaceWithKey returns true if a namespace format contains a complete +// verification key.  I.e., some formats might have a key fingerprint instead. +func namespaceWithKey(format NamespaceFormat) bool { +	switch format { +	case NamespaceFormatEd25519V1: +		return true +	default: +		return false +	} +} diff --git a/types/namespace_pool_test.go b/types/namespace_pool_test.go new file mode 100644 index 0000000..f5810a2 --- /dev/null +++ b/types/namespace_pool_test.go @@ -0,0 +1,91 @@ +package types + +import ( +	"bytes" +	"reflect" +	"testing" +) + +func TestNewNamespacePool(t *testing.T) { +	ns1 := mustInitNamespaceEd25519V1(t, 0x00) +	ns2 := mustInitNamespaceEd25519V1(t, 0xff) +	nsr := &Namespace{Format: NamespaceFormatReserved} +	for _, table := range []struct { +		description string +		namespaces  []*Namespace +		wantErr     bool +	}{ +		{ +			description: "invalid: duplicate namespace", +			namespaces:  []*Namespace{ns1, ns1, ns2}, +			wantErr:     true, +		}, +		{ +			description: "invalid: namespace without key", +			namespaces:  []*Namespace{ns1, nsr, ns2}, +			wantErr:     true, +		}, +		{ +			description: "valid: empty", +			namespaces:  []*Namespace{}, +		}, +		{ +			description: "valid: one namespace", +			namespaces:  []*Namespace{ns1}, +		}, +		{ +			description: "valid: two namespaces", +			namespaces:  []*Namespace{ns1, ns2}, +		}, +	} { +		_, err := NewNamespacePool(table.namespaces) +		if got, want := err != nil, table.wantErr; got != want { +			t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) +		} +	} +} + +func TestFind(t *testing.T) { +	ns1 := mustInitNamespaceEd25519V1(t, 0x00) +	ns2 := mustInitNamespaceEd25519V1(t, 0xff) + +	// Empty pool +	pool, err := NewNamespacePool(nil) +	if err != nil { +		t.Fatalf("must create new namespace pool: %v", err) +	} +	if _, ok := pool.Find(ns1); ok { +		t.Errorf("found namespace in empty pool") +	} + +	// Pool with one namespace +	pool, err = NewNamespacePool([]*Namespace{ns1}) +	if err != nil { +		t.Fatalf("must create new namespace pool: %v", err) +	} +	if ns, ok := pool.Find(ns1); !ok { +		t.Errorf("could not find namespace that is a member of the pool") +	} else if !reflect.DeepEqual(ns, ns1) { +		t.Errorf("found namespace but it is wrong") +	} +	if _, ok := pool.Find(ns2); ok { +		t.Errorf("found namespace although it is not a member of the pool") +	} +} + +func TestList(t *testing.T) { +	ns1 := mustInitNamespaceEd25519V1(t, 0x00) +	ns2 := mustInitNamespaceEd25519V1(t, 0xff) +	namespaces := []*Namespace{ns1, ns2} +	pool, err := NewNamespacePool(namespaces) +	if err != nil { +		t.Fatalf("must create new namespace pool: %v", err) +	} +	if got, want := len(pool.List()), len(namespaces); got != want { +		t.Errorf("got len %v but wanted %v", got, want) +	} +	pool.List()[0] = ns2 +	if got, want := pool.List()[0].Ed25519V1.Namespace[:], ns1.Ed25519V1.Namespace[:]; !bytes.Equal(got, want) { +		t.Errorf("returned list is not a copy") +	} +} diff --git a/types/namespace_test.go b/types/namespace_test.go index e7e89ad..cd151d8 100644 --- a/types/namespace_test.go +++ b/types/namespace_test.go @@ -2,80 +2,21 @@ package types  import (  	"bytes" -	"reflect"  	"strings"  	"testing"  	"crypto/ed25519"  ) -var ( -	// Namespace -	testNamespaceReserved = Namespace{ -		Format: NamespaceFormatReserved, -	} - -	testNamespace          = testNamespaceEd25519V1 -	testNamespaceBytes     = testNamespaceEd25519V1Bytes -	testNamespaceEd25519V1 = Namespace{ -		Format:    NamespaceFormatEd25519V1, -		Ed25519V1: &testEd25519V1, -	} -	testNamespaceEd25519V1Bytes = bytes.Join([][]byte{ -		[]byte{0x00, 0x01}, // format ed25519_v1 -		testEd25519V1Bytes, // Ed25519V1 -	}, nil) - -	// Subtypes used by Namespace -	testEd25519V1 = Ed25519V1{ -		Namespace: [32]byte{}, -	} -	testEd25519V1Bytes = bytes.Join([][]byte{ -		make([]byte, 32), // namespace, no length specifier because fixed size -	}, nil) -) - -func TestNewNamespaceEd25519V1(t *testing.T) { -	size := 32 // verification key size -	for _, table := range []struct { -		description string -		vk          []byte -		wantErr     bool -	}{ -		{ -			description: "invalid", -			vk:          make([]byte, size+1), -			wantErr:     true, -		}, -		{ -			description: "valid", -			vk:          make([]byte, size), -		}, -	} { -		n, err := NewNamespaceEd25519V1(table.vk) -		if got, want := err != nil, table.wantErr; got != want { -			t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) -		} -		if err != nil { -			continue -		} -		if got, want := n.Format, NamespaceFormatEd25519V1; got != want { -			t.Errorf("got namespace format %v but wanted %v in test %q", got, want, table.description) -			continue -		} -		if got, want := n.Ed25519V1.Namespace[:], table.vk; !bytes.Equal(got, want) { -			t.Errorf("got namespace %X but wanted %X in test %q", got, want, table.description) -		} -	} -} - +// TestNamespaceString checks that the String() function prints the right +// format, and that the body is printed without a nil-pointer panic.  func TestNamespaceString(t *testing.T) {  	wantPrefix := map[NamespaceFormat]string{  		NamespaceFormatReserved:    "Format(reserved)",  		NamespaceFormatEd25519V1:   "Format(ed25519_v1): &{Namespace",  		NamespaceFormat(1<<16 - 1): "unknown Namespace: unknown NamespaceFormat: 65535",  	} -	tests := append(test_cases_namespace(t), testCaseType{ +	tests := append(test_cases_namespace(t), testCaseSerialize{  		description: "valid: unknown Namespace",  		item: Namespace{  			Format: NamespaceFormat(1<<16 - 1), @@ -135,128 +76,49 @@ func TestFingerprint(t *testing.T) {  	}  } -func TestVerify(t *testing.T) { -	var tests []testCaseNamespace -	tests = append(tests, test_cases_verify(t)...) -	tests = append(tests, test_cases_verify_ed25519v1(t)...) -	for _, table := range tests { -		err := table.namespace.Verify(table.msg, table.sig) -		if got, want := err != nil, table.wantErr; got != want { -			t.Errorf("got error=%v but wanted %v in test %q: %v", got, want, table.description, err) -		} -	} -} - -func TestNewNamespacePool(t *testing.T) { -	ns1 := mustInitNamespaceEd25519V1(t, 0x00) -	ns2 := mustInitNamespaceEd25519V1(t, 0xff) -	nsr := &Namespace{Format: NamespaceFormatReserved} +func TestNewNamespaceEd25519V1(t *testing.T) { +	size := 32 // verification key size  	for _, table := range []struct {  		description string -		namespaces  []*Namespace +		vk          []byte  		wantErr     bool  	}{  		{ -			description: "invalid: duplicate namespace", -			namespaces:  []*Namespace{ns1, ns1, ns2}, -			wantErr:     true, -		}, -		{ -			description: "invalid: namespace without key", -			namespaces:  []*Namespace{ns1, nsr, ns2}, +			description: "invalid", +			vk:          make([]byte, size+1),  			wantErr:     true,  		},  		{ -			description: "valid: empty", -			namespaces:  []*Namespace{}, -		}, -		{ -			description: "valid: one namespace", -			namespaces:  []*Namespace{ns1}, -		}, -		{ -			description: "valid: two namespaces", -			namespaces:  []*Namespace{ns1, ns2}, +			description: "valid", +			vk:          make([]byte, size),  		},  	} { -		_, err := NewNamespacePool(table.namespaces) +		n, err := NewNamespaceEd25519V1(table.vk)  		if got, want := err != nil, table.wantErr; got != want {  			t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err)  		} +		if err != nil { +			continue +		} +		if got, want := n.Format, NamespaceFormatEd25519V1; got != want { +			t.Errorf("got namespace format %v but wanted %v in test %q", got, want, table.description) +			continue +		} +		if got, want := n.Ed25519V1.Namespace[:], table.vk; !bytes.Equal(got, want) { +			t.Errorf("got namespace %X but wanted %X in test %q", got, want, table.description) +		}  	}  } -func TestFind(t *testing.T) { -	ns1 := mustInitNamespaceEd25519V1(t, 0x00) -	ns2 := mustInitNamespaceEd25519V1(t, 0xff) - -	// Empty pool -	pool, err := NewNamespacePool(nil) -	if err != nil { -		t.Fatalf("must create new namespace pool: %v", err) -	} -	if _, ok := pool.Find(ns1); ok { -		t.Errorf("found namespace in empty pool") -	} - -	// Pool with one namespace -	pool, err = NewNamespacePool([]*Namespace{ns1}) -	if err != nil { -		t.Fatalf("must create new namespace pool: %v", err) -	} -	if ns, ok := pool.Find(ns1); !ok { -		t.Errorf("could not find namespace that is a member of the pool") -	} else if !reflect.DeepEqual(ns, ns1) { -		t.Errorf("found namespace but it is wrong") -	} -	if _, ok := pool.Find(ns2); ok { -		t.Errorf("found namespace although it is not a member of the pool") -	} -} - -func TestList(t *testing.T) { -	ns1 := mustInitNamespaceEd25519V1(t, 0x00) -	ns2 := mustInitNamespaceEd25519V1(t, 0xff) -	namespaces := []*Namespace{ns1, ns2} -	pool, err := NewNamespacePool(namespaces) -	if err != nil { -		t.Fatalf("must create new namespace pool: %v", err) -	} -	if got, want := len(pool.List()), len(namespaces); got != want { -		t.Errorf("got len %v but wanted %v", got, want) -	} -	pool.List()[0] = ns2 -	if got, want := pool.List()[0].Ed25519V1.Namespace[:], ns1.Ed25519V1.Namespace[:]; !bytes.Equal(got, want) { -		t.Errorf("returned list is not a copy") -	} -} - -// test_cases_namespace returns test cases for the different Namespace types. -// It is used by TestMarshalUnmarshal(), see test_item.go. -func test_cases_namespace(t *testing.T) []testCaseType { -	return []testCaseType{ -		{ -			description: "invalid: Namespace: reserved", -			item:        testNamespaceReserved, -			wantErr:     true, -		}, -		{ -			description: "valid: Namespace: ed25519_v1", -			item:        testNamespaceEd25519V1, -			wantBytes:   testNamespaceEd25519V1Bytes, -		}, -	} -} - -// test_cases_ed25519v1 returns test cases for the Ed25519V1 structure -// It is used by TestMarshalUnmarshal(), see test_item.go. -func test_cases_ed25519v1(t *testing.T) []testCaseType { -	return []testCaseType{ -		{ -			description: "valid: testNamespaceEd25519V1", -			item:        testEd25519V1, -			wantBytes:   testEd25519V1Bytes, -		}, +func TestVerify(t *testing.T) { +	var tests []testCaseNamespace +	tests = append(tests, test_cases_verify(t)...) +	tests = append(tests, test_cases_verify_ed25519v1(t)...) +	for _, table := range tests { +		err := table.namespace.Verify(table.msg, table.sig) +		if got, want := err != nil, table.wantErr; got != want { +			t.Errorf("got error=%v but wanted %v in test %q: %v", got, want, table.description, err) +		}  	}  } diff --git a/types/serialize.go b/types/serialize.go new file mode 100644 index 0000000..da4bd9f --- /dev/null +++ b/types/serialize.go @@ -0,0 +1,28 @@ +package types + +import ( +	"fmt" + +	"github.com/google/certificate-transparency-go/tls" +) + +// Marshal marshals a TLS-encodable structure +func Marshal(item interface{}) ([]byte, error) { +	serialized, err := tls.Marshal(item) +	if err != nil { +		return nil, fmt.Errorf("tls.Marshal: %v", err) +	} +	return serialized, nil +} + +// Unmarshal unmarshals a TLS-encoded structure +func Unmarshal(serialized []byte, out interface{}) error { +	extra, err := tls.Unmarshal(serialized, out) +	if err != nil { +		return fmt.Errorf("tls.Unmarshal: %v", err) +	} +	if len(extra) > 0 { +		return fmt.Errorf("tls.Unmarshal: extra data: %X", extra) +	} +	return nil +} diff --git a/types/item_test.go b/types/serialize_test.go index 6788f51..e835ad2 100644 --- a/types/item_test.go +++ b/types/serialize_test.go @@ -2,14 +2,13 @@ package types  import (  	"bytes" -	"strings"  	"testing"  	"encoding/binary"  ) -// testCaseType is a common test case used for ST log types -type testCaseType struct { +// testCaseSerialize is a common test case used for ST log types +type testCaseSerialize struct {  	description string  	item        interface{}  	wantErr     bool @@ -20,7 +19,7 @@ type testCaseType struct {  // then unmarshalled without error, and that invalid ST log structures cannot be  // marshalled.  If wantBytes is non-nil the marshalled result must also match.  func TestMarshalUnmarshal(t *testing.T) { -	var tests []testCaseType +	var tests []testCaseSerialize  	tests = append(tests, test_cases_stitemlist(t)...)  	tests = append(tests, test_cases_stitem(t)...)  	tests = append(tests, test_cases_sthv1(t)...) @@ -121,231 +120,16 @@ func TestUnmarshalStItem(t *testing.T) {  	}  } -// TestStItemString checks that the String() function prints the right format, -// and that following body is printed in a verbose mode without a nil-ptr panic. -func TestStItemString(t *testing.T) { -	wantPrefix := map[StFormat]string{ -		StFormatReserved:           "Format(reserved)", -		StFormatSignedTreeHeadV1:   "Format(signed_tree_head_v1): &{TreeHead", -		StFormatCosignedTreeHeadV1: "Format(cosigned_tree_head_v1): &{SignedTreeHead", -		StFormatConsistencyProofV1: "Format(consistency_proof_v1): &{LogId", -		StFormatInclusionProofV1:   "Format(inclusion_proof_v1): &{LogId", -		StFormatSignedChecksumV1:   "Format(signed_checksum_v1): &{Data", -		StFormat(1<<16 - 1):        "unknown StItem: unknown StFormat: 65535", -	} -	tests := append(test_cases_stitem(t), testCaseType{ -		description: "valid: unknown StItem", -		item: StItem{ -			Format: StFormat(1<<16 - 1), -		}, -	}) -	for _, table := range tests { -		item, ok := table.item.(StItem) -		if !ok { -			t.Fatalf("must cast to StItem in test %q", table.description) -		} - -		prefix, ok := wantPrefix[item.Format] -		if !ok { -			t.Fatalf("must have prefix for StFormat %v in test %q", item.Format, table.description) -		} -		if got, want := item.String(), prefix; !strings.HasPrefix(got, want) { -			t.Errorf("got %q but wanted prefix %q in test %q", got, want, table.description) -		} -	} -} - -var ( -	// StItemList -	testStItemList = StItemList{ -		Items: []StItem{ -			testStItemSignedChecksumV1, -			testStItemInclusionProofV1, -			testStItemCosignedTreeHeadV1, -		}, -	} -	testStItemListBytes = bytes.Join([][]byte{ -		func() []byte { -			sum := uint32(len(testStItemSignedChecksumV1Bytes)) -			sum += uint32(len(testStItemInclusionProofV1Bytes)) -			sum += uint32(len(testStItemCosignedTreeHeadV1Bytes)) -			buf := make([]byte, 4) -			binary.BigEndian.PutUint32(buf, sum) -			return buf -		}(), // length specifier list -		testStItemSignedChecksumV1Bytes,   // first StItem -		testStItemInclusionProofV1Bytes,   // second StItem -		testStItemCosignedTreeHeadV1Bytes, // third StItem -	}, nil) - -	// StItem -	testStItemReserved = StItem{ -		Format: StFormatReserved, -	} - -	testStItemSignedTreeHeadV1 = StItem{ -		Format:           StFormatSignedTreeHeadV1, -		SignedTreeHeadV1: &testSignedTreeHeadV1, -	} -	testStItemSignedTreeHeadV1Bytes = bytes.Join([][]byte{ -		[]byte{0x00, 0x01},        // format signed_tree_head_v1 -		testSignedTreeHeadV1Bytes, // SignedTreeHeadV1 -	}, nil) - -	testStItemCosignedTreeHeadV1 = StItem{ -		Format:             StFormatCosignedTreeHeadV1, -		CosignedTreeHeadV1: &testCosignedTreeHeadV1, -	} -	testStItemCosignedTreeHeadV1Bytes = bytes.Join([][]byte{ -		[]byte{0x00, 0x02},          // format cosigned_tree_head_v1 -		testCosignedTreeHeadV1Bytes, // CosignedTreeHeadV1, -	}, nil) - -	testStItemConsistencyProofV1 = StItem{ -		Format:             StFormatConsistencyProofV1, -		ConsistencyProofV1: &testConsistencyProofV1, -	} -	testStItemConsistencyProofV1Bytes = bytes.Join([][]byte{ -		[]byte{0x00, 0x03},          // format consistency_proof_v1 -		testConsistencyProofV1Bytes, // ConsistencyProofV1 -	}, nil) - -	testStItemInclusionProofV1 = StItem{ -		Format:           StFormatInclusionProofV1, -		InclusionProofV1: &testInclusionProofV1, -	} -	testStItemInclusionProofV1Bytes = bytes.Join([][]byte{ -		[]byte{0x00, 0x04},        // format inclusion_proof_v1 -		testInclusionProofV1Bytes, // InclusionProofV1 -	}, nil) - -	testStItemSignedChecksumV1 = StItem{ -		Format:           StFormatSignedChecksumV1, -		SignedChecksumV1: &testSignedChecksumV1, -	} -	testStItemSignedChecksumV1Bytes = bytes.Join([][]byte{ -		[]byte{0x00, 0x05},        // format signed_checksum_v1 -		testSignedChecksumV1Bytes, // SignedChecksumV1 -	}, nil) - -	// Subtypes used by StItem -	testSignedTreeHeadV1 = SignedTreeHeadV1{ -		TreeHead:  testTreeHeadV1, -		Signature: testSignatureV1, -	} -	testSignedTreeHeadV1Bytes = bytes.Join([][]byte{ -		testTreeHeadV1Bytes,  // tree head -		testSignatureV1Bytes, // signature -	}, nil) - -	testCosignedTreeHeadV1 = CosignedTreeHeadV1{ -		SignedTreeHead: testSignedTreeHeadV1, -		Cosignatures: []SignatureV1{ -			testSignatureV1, -		}, -	} -	testCosignedTreeHeadV1Bytes = bytes.Join([][]byte{ -		testSignedTreeHeadV1Bytes,                                 // signed tree head -		[]byte{0x00, 0x00, 0x00, byte(len(testSignatureV1Bytes))}, // cosignature length specifier -		testSignatureV1Bytes,                                      // the only cosignature in this list -	}, nil) - -	testConsistencyProofV1 = ConsistencyProofV1{ -		LogId:     testNamespace, -		TreeSize1: 16909060, -		TreeSize2: 16909060, -		ConsistencyPath: []NodeHash{ -			testNodeHash, -		}, -	} -	testConsistencyProofV1Bytes = bytes.Join([][]byte{ -		testNamespaceBytes, // log id -		[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // tree size 1 -		[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // tree size 2 -		[]byte{0x00, byte(len(testNodeHashBytes))},             // consistency path length specifier -		testNodeHashBytes, // the only node hash in this proof -	}, nil) - -	testInclusionProofV1 = InclusionProofV1{ -		LogId:     testNamespace, -		TreeSize:  16909060, -		LeafIndex: 16909060, -		InclusionPath: []NodeHash{ -			testNodeHash, -		}, -	} -	testInclusionProofV1Bytes = bytes.Join([][]byte{ -		testNamespaceBytes, // log id -		[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // tree size -		[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // leaf index -		[]byte{0x00, byte(len(testNodeHashBytes))},             // inclusion path length specifier -		testNodeHashBytes, // the only node hash in this proof -	}, nil) - -	testSignedChecksumV1 = SignedChecksumV1{ -		Data:      testChecksumV1, -		Signature: testSignatureV1, -	} -	testSignedChecksumV1Bytes = bytes.Join([][]byte{ -		testChecksumV1Bytes,  // data -		testSignatureV1Bytes, // signature -	}, nil) - -	// Additional subtypes -	testChecksumV1 = ChecksumV1{ -		Identifier: []byte("foobar-1-2-3"), -		Checksum:   make([]byte, 32), -	} -	testChecksumV1Bytes = bytes.Join([][]byte{ -		[]byte{12},             // identifier length specifier -		[]byte("foobar-1-2-3"), // identifier -		[]byte{32},             // checksum length specifier -		make([]byte, 32),       // checksum -	}, nil) - -	testTreeHeadV1 = TreeHeadV1{ -		Timestamp: 16909060, -		TreeSize:  16909060, -		RootHash:  testNodeHash, -		Extension: make([]byte, 0), -	} -	testTreeHeadV1Bytes = bytes.Join([][]byte{ -		[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // timestamp -		[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // tree size -		testNodeHashBytes,  // root hash -		[]byte{0x00, 0x00}, // extension length specifier -		// no extension -	}, nil) - -	testNodeHash = NodeHash{ -		Data: make([]byte, 32), -	} -	testNodeHashBytes = bytes.Join([][]byte{ -		[]byte{32}, // node hash length specifier -		make([]byte, 32), -	}, nil) - -	testSignatureV1 = SignatureV1{ -		Namespace: testNamespace, -		Signature: make([]byte, 64), -	} -	testSignatureV1Bytes = bytes.Join([][]byte{ -		testNamespaceBytes, // namespace field -		[]byte{0, 64},      // signature length specifier -		make([]byte, 64),   // signature -	}, nil) -) -  // test_cases_stitemlist returns test cases for the StItemList type -func test_cases_stitemlist(t *testing.T) []testCaseType { +func test_cases_stitemlist(t *testing.T) []testCaseSerialize {  	t.Helper() -	return []testCaseType{ -		testCaseType{ +	return []testCaseSerialize{ +		testCaseSerialize{  			description: "test_cases_stitemlist: valid: StItemList: empty",  			item:        StItemList{},  			wantBytes:   []byte{0x00, 0x00, 0x00, 0x00},  		}, // skip max len check because it is huge -		testCaseType{ +		testCaseSerialize{  			description: "test_cases_stitemlist: valid: mixed content",  			item:        testStItemList,  			wantBytes:   testStItemListBytes, @@ -354,9 +138,9 @@ func test_cases_stitemlist(t *testing.T) []testCaseType {  }  // test_cases_stitem returns test cases for the different StItem types -func test_cases_stitem(t *testing.T) []testCaseType { +func test_cases_stitem(t *testing.T) []testCaseSerialize {  	t.Helper() -	return []testCaseType{ +	return []testCaseSerialize{  		{  			description: "invalid: StItem: reserved",  			item:        testStItemReserved, @@ -391,9 +175,9 @@ func test_cases_stitem(t *testing.T) []testCaseType {  }  // test_cases_sthv1 returns test cases for the SignedTreeHeadV1 structure -func test_cases_sthv1(t *testing.T) []testCaseType { +func test_cases_sthv1(t *testing.T) []testCaseSerialize {  	t.Helper() -	return []testCaseType{ +	return []testCaseSerialize{  		{  			description: "valid: testSignedTreeHeadV1",  			item:        testSignedTreeHeadV1, @@ -403,9 +187,9 @@ func test_cases_sthv1(t *testing.T) []testCaseType {  }  // test_cases_costhv1 returns test cases for the CosignedTreeHeadV1 structure -func test_cases_costhv1(t *testing.T) []testCaseType { +func test_cases_costhv1(t *testing.T) []testCaseSerialize {  	t.Helper() -	return []testCaseType{ +	return []testCaseSerialize{  		{  			description: "test_cases_costhv1: valid: min",  			item: CosignedTreeHeadV1{ @@ -422,10 +206,10 @@ func test_cases_costhv1(t *testing.T) []testCaseType {  }  // test_cases_cpv1 returns test cases for the ConsistencyProofV1 structure -func test_cases_cpv1(t *testing.T) []testCaseType { +func test_cases_cpv1(t *testing.T) []testCaseSerialize {  	t.Helper()  	max := 65535 // max consistency proof -	return []testCaseType{ +	return []testCaseSerialize{  		{  			description: "test_cases_cpv1: invalid: >max",  			item: ConsistencyProofV1{ @@ -460,10 +244,10 @@ func test_cases_cpv1(t *testing.T) []testCaseType {  }  // test_cases_ipv1 returns test cases for the InclusionProofV1 structure -func test_cases_ipv1(t *testing.T) []testCaseType { +func test_cases_ipv1(t *testing.T) []testCaseSerialize {  	t.Helper()  	max := 65535 // max inclusion proof -	return []testCaseType{ +	return []testCaseSerialize{  		{  			description: "test_cases_ipv1: invalid: >max",  			item: InclusionProofV1{ @@ -498,9 +282,9 @@ func test_cases_ipv1(t *testing.T) []testCaseType {  }  // test_cases_signed_checksumv1 returns test cases for the SignedChecksumV1 structure -func test_cases_signed_checksumv1(t *testing.T) []testCaseType { +func test_cases_signed_checksumv1(t *testing.T) []testCaseSerialize {  	t.Helper() -	return []testCaseType{ +	return []testCaseSerialize{  		{  			description: "test_cases_signed_checksumv1: valid: testSignedChecksumV1",  			item:        testSignedChecksumV1, @@ -510,11 +294,11 @@ func test_cases_signed_checksumv1(t *testing.T) []testCaseType {  }  // test_cases_checksumv1 returns test cases for the ChecksumV1 structure -func test_cases_checksumv1(t *testing.T) []testCaseType { +func test_cases_checksumv1(t *testing.T) []testCaseSerialize {  	t.Helper()  	minIdentifier, maxIdentifier, identifier := 1, 128, []byte("foobar-1-2-3")  	minChecksum, maxChecksum, checksum := 1, 64, make([]byte, 32) -	return []testCaseType{ +	return []testCaseSerialize{  		{  			description: "test_cases_checksumv1: invalid: identifier: min",  			item: ChecksumV1{ @@ -556,10 +340,10 @@ func test_cases_checksumv1(t *testing.T) []testCaseType {  }  // test_cases_thv1 returns test cases for the TreeHeadV1 structure -func test_cases_thv1(t *testing.T) []testCaseType { +func test_cases_thv1(t *testing.T) []testCaseSerialize {  	t.Helper()  	min, max := 0, 1<<16-1 // extensions min and max -	return []testCaseType{ +	return []testCaseSerialize{  		{  			description: "test_cases_thv1: invalid: max",  			item: TreeHeadV1{ @@ -597,10 +381,10 @@ func test_cases_thv1(t *testing.T) []testCaseType {  }  // test_cases_nh returns test cases for the NodeHash structure -func test_cases_nh(t *testing.T) []testCaseType { +func test_cases_nh(t *testing.T) []testCaseSerialize {  	t.Helper()  	min, max := 32, 1<<8-1 // NodeHash min and max -	return []testCaseType{ +	return []testCaseSerialize{  		{  			description: "test_cases_nh: invalid: min",  			item:        NodeHash{make([]byte, min-1)}, @@ -628,10 +412,10 @@ func test_cases_nh(t *testing.T) []testCaseType {  }  // test_cases_sigv1 returns test cases for the SignatureV1 structure -func test_cases_sigv1(t *testing.T) []testCaseType { +func test_cases_sigv1(t *testing.T) []testCaseSerialize {  	t.Helper()  	min, max := 1, 1<<16-1 // signature min and max -	return []testCaseType{ +	return []testCaseSerialize{  		{  			description: "test_cases_sigv1: invalid: min",  			item: SignatureV1{ @@ -669,3 +453,235 @@ func test_cases_sigv1(t *testing.T) []testCaseType {  		},  	}  } + +// test_cases_namespace returns test cases for the different Namespace types. +func test_cases_namespace(t *testing.T) []testCaseSerialize { +	return []testCaseSerialize{ +		{ +			description: "invalid: Namespace: reserved", +			item:        testNamespaceReserved, +			wantErr:     true, +		}, +		{ +			description: "valid: Namespace: ed25519_v1", +			item:        testNamespaceEd25519V1, +			wantBytes:   testNamespaceEd25519V1Bytes, +		}, +	} +} + +// test_cases_ed25519v1 returns test cases for the Ed25519V1 structure +func test_cases_ed25519v1(t *testing.T) []testCaseSerialize { +	return []testCaseSerialize{ +		{ +			description: "valid: testNamespaceEd25519V1", +			item:        testEd25519V1, +			wantBytes:   testEd25519V1Bytes, +		}, +	} +} + +var ( +	// StItemList +	testStItemList = StItemList{ +		Items: []StItem{ +			testStItemSignedChecksumV1, +			testStItemInclusionProofV1, +			testStItemCosignedTreeHeadV1, +		}, +	} +	testStItemListBytes = bytes.Join([][]byte{ +		func() []byte { +			sum := uint32(len(testStItemSignedChecksumV1Bytes)) +			sum += uint32(len(testStItemInclusionProofV1Bytes)) +			sum += uint32(len(testStItemCosignedTreeHeadV1Bytes)) +			buf := make([]byte, 4) +			binary.BigEndian.PutUint32(buf, sum) +			return buf +		}(), // length specifier list +		testStItemSignedChecksumV1Bytes,   // first StItem +		testStItemInclusionProofV1Bytes,   // second StItem +		testStItemCosignedTreeHeadV1Bytes, // third StItem +	}, nil) + +	// StItem +	testStItemReserved = StItem{ +		Format: StFormatReserved, +	} + +	testStItemSignedTreeHeadV1 = StItem{ +		Format:           StFormatSignedTreeHeadV1, +		SignedTreeHeadV1: &testSignedTreeHeadV1, +	} +	testStItemSignedTreeHeadV1Bytes = bytes.Join([][]byte{ +		[]byte{0x00, 0x01},        // format signed_tree_head_v1 +		testSignedTreeHeadV1Bytes, // SignedTreeHeadV1 +	}, nil) + +	testStItemCosignedTreeHeadV1 = StItem{ +		Format:             StFormatCosignedTreeHeadV1, +		CosignedTreeHeadV1: &testCosignedTreeHeadV1, +	} +	testStItemCosignedTreeHeadV1Bytes = bytes.Join([][]byte{ +		[]byte{0x00, 0x02},          // format cosigned_tree_head_v1 +		testCosignedTreeHeadV1Bytes, // CosignedTreeHeadV1, +	}, nil) + +	testStItemConsistencyProofV1 = StItem{ +		Format:             StFormatConsistencyProofV1, +		ConsistencyProofV1: &testConsistencyProofV1, +	} +	testStItemConsistencyProofV1Bytes = bytes.Join([][]byte{ +		[]byte{0x00, 0x03},          // format consistency_proof_v1 +		testConsistencyProofV1Bytes, // ConsistencyProofV1 +	}, nil) + +	testStItemInclusionProofV1 = StItem{ +		Format:           StFormatInclusionProofV1, +		InclusionProofV1: &testInclusionProofV1, +	} +	testStItemInclusionProofV1Bytes = bytes.Join([][]byte{ +		[]byte{0x00, 0x04},        // format inclusion_proof_v1 +		testInclusionProofV1Bytes, // InclusionProofV1 +	}, nil) + +	testStItemSignedChecksumV1 = StItem{ +		Format:           StFormatSignedChecksumV1, +		SignedChecksumV1: &testSignedChecksumV1, +	} +	testStItemSignedChecksumV1Bytes = bytes.Join([][]byte{ +		[]byte{0x00, 0x05},        // format signed_checksum_v1 +		testSignedChecksumV1Bytes, // SignedChecksumV1 +	}, nil) + +	// Subtypes used by StItem +	testSignedTreeHeadV1 = SignedTreeHeadV1{ +		TreeHead:  testTreeHeadV1, +		Signature: testSignatureV1, +	} +	testSignedTreeHeadV1Bytes = bytes.Join([][]byte{ +		testTreeHeadV1Bytes,  // tree head +		testSignatureV1Bytes, // signature +	}, nil) + +	testCosignedTreeHeadV1 = CosignedTreeHeadV1{ +		SignedTreeHead: testSignedTreeHeadV1, +		Cosignatures: []SignatureV1{ +			testSignatureV1, +		}, +	} +	testCosignedTreeHeadV1Bytes = bytes.Join([][]byte{ +		testSignedTreeHeadV1Bytes,                                 // signed tree head +		[]byte{0x00, 0x00, 0x00, byte(len(testSignatureV1Bytes))}, // cosignature length specifier +		testSignatureV1Bytes,                                      // the only cosignature in this list +	}, nil) + +	testConsistencyProofV1 = ConsistencyProofV1{ +		LogId:     testNamespace, +		TreeSize1: 16909060, +		TreeSize2: 16909060, +		ConsistencyPath: []NodeHash{ +			testNodeHash, +		}, +	} +	testConsistencyProofV1Bytes = bytes.Join([][]byte{ +		testNamespaceBytes, // log id +		[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // tree size 1 +		[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // tree size 2 +		[]byte{0x00, byte(len(testNodeHashBytes))},             // consistency path length specifier +		testNodeHashBytes, // the only node hash in this proof +	}, nil) + +	testInclusionProofV1 = InclusionProofV1{ +		LogId:     testNamespace, +		TreeSize:  16909060, +		LeafIndex: 16909060, +		InclusionPath: []NodeHash{ +			testNodeHash, +		}, +	} +	testInclusionProofV1Bytes = bytes.Join([][]byte{ +		testNamespaceBytes, // log id +		[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // tree size +		[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // leaf index +		[]byte{0x00, byte(len(testNodeHashBytes))},             // inclusion path length specifier +		testNodeHashBytes, // the only node hash in this proof +	}, nil) + +	testSignedChecksumV1 = SignedChecksumV1{ +		Data:      testChecksumV1, +		Signature: testSignatureV1, +	} +	testSignedChecksumV1Bytes = bytes.Join([][]byte{ +		testChecksumV1Bytes,  // data +		testSignatureV1Bytes, // signature +	}, nil) + +	// Additional subtypes +	testChecksumV1 = ChecksumV1{ +		Identifier: []byte("foobar-1-2-3"), +		Checksum:   make([]byte, 32), +	} +	testChecksumV1Bytes = bytes.Join([][]byte{ +		[]byte{12},             // identifier length specifier +		[]byte("foobar-1-2-3"), // identifier +		[]byte{32},             // checksum length specifier +		make([]byte, 32),       // checksum +	}, nil) + +	testTreeHeadV1 = TreeHeadV1{ +		Timestamp: 16909060, +		TreeSize:  16909060, +		RootHash:  testNodeHash, +		Extension: make([]byte, 0), +	} +	testTreeHeadV1Bytes = bytes.Join([][]byte{ +		[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // timestamp +		[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, // tree size +		testNodeHashBytes,  // root hash +		[]byte{0x00, 0x00}, // extension length specifier +		// no extension +	}, nil) + +	testNodeHash = NodeHash{ +		Data: make([]byte, 32), +	} +	testNodeHashBytes = bytes.Join([][]byte{ +		[]byte{32}, // node hash length specifier +		make([]byte, 32), +	}, nil) + +	testSignatureV1 = SignatureV1{ +		Namespace: testNamespace, +		Signature: make([]byte, 64), +	} +	testSignatureV1Bytes = bytes.Join([][]byte{ +		testNamespaceBytes, // namespace field +		[]byte{0, 64},      // signature length specifier +		make([]byte, 64),   // signature +	}, nil) + +	// Namespace +	testNamespaceReserved = Namespace{ +		Format: NamespaceFormatReserved, +	} + +	testNamespace          = testNamespaceEd25519V1 +	testNamespaceBytes     = testNamespaceEd25519V1Bytes +	testNamespaceEd25519V1 = Namespace{ +		Format:    NamespaceFormatEd25519V1, +		Ed25519V1: &testEd25519V1, +	} +	testNamespaceEd25519V1Bytes = bytes.Join([][]byte{ +		[]byte{0x00, 0x01}, // format ed25519_v1 +		testEd25519V1Bytes, // Ed25519V1 +	}, nil) + +	// Subtypes used by Namespace +	testEd25519V1 = Ed25519V1{ +		Namespace: [32]byte{}, +	} +	testEd25519V1Bytes = bytes.Join([][]byte{ +		make([]byte, 32), // namespace, no length specifier because fixed size +	}, nil) +) diff --git a/types/item.go b/types/stitem.go index 8596ce4..b214082 100644 --- a/types/item.go +++ b/types/stitem.go @@ -28,7 +28,6 @@ type StItem struct {  	SignedChecksumV1   *SignedChecksumV1   `tls:"selector:Format,val:5"`  } -// StItemList is an StItem List that is at most 2^32-1 bytes when serialized.  type StItemList struct {  	Items []StItem `tls:"minlen:0,maxlen:4294967295"`  } @@ -120,24 +119,3 @@ func (i StItem) String() string {  		return fmt.Sprintf("unknown StItem: %v", i.Format)  	}  } - -// Marshal marshals a TLS-encodable item -func Marshal(item interface{}) ([]byte, error) { -	serialized, err := tls.Marshal(item) -	if err != nil { -		return nil, fmt.Errorf("tls.Marshal: %v", err) -	} -	return serialized, nil -} - -// Unmarshal unmarshals a TLS-encoded item -func Unmarshal(serialized []byte, out interface{}) error { -	extra, err := tls.Unmarshal(serialized, out) -	if err != nil { -		return fmt.Errorf("tls.Unmarshal: %v", err) -	} -	if len(extra) > 0 { -		return fmt.Errorf("tls.Unmarshal: extra data: %X", extra) -	} -	return nil -} diff --git a/types/stitem_test.go b/types/stitem_test.go new file mode 100644 index 0000000..c6e413a --- /dev/null +++ b/types/stitem_test.go @@ -0,0 +1,40 @@ +package types + +import ( +	"strings" +	"testing" +) + +// TestStItemString checks that the String() function prints the right format, +// and that the body is printed without a nil-pointer panic. +func TestStItemString(t *testing.T) { +	wantPrefix := map[StFormat]string{ +		StFormatReserved:           "Format(reserved)", +		StFormatSignedTreeHeadV1:   "Format(signed_tree_head_v1): &{TreeHead", +		StFormatCosignedTreeHeadV1: "Format(cosigned_tree_head_v1): &{SignedTreeHead", +		StFormatConsistencyProofV1: "Format(consistency_proof_v1): &{LogId", +		StFormatInclusionProofV1:   "Format(inclusion_proof_v1): &{LogId", +		StFormatSignedChecksumV1:   "Format(signed_checksum_v1): &{Data", +		StFormat(1<<16 - 1):        "unknown StItem: unknown StFormat: 65535", +	} +	tests := append(test_cases_stitem(t), testCaseSerialize{ +		description: "valid: unknown StItem", +		item: StItem{ +			Format: StFormat(1<<16 - 1), +		}, +	}) +	for _, table := range tests { +		item, ok := table.item.(StItem) +		if !ok { +			t.Fatalf("must cast to StItem in test %q", table.description) +		} + +		prefix, ok := wantPrefix[item.Format] +		if !ok { +			t.Fatalf("must have prefix for StFormat %v in test %q", item.Format, table.description) +		} +		if got, want := item.String(), prefix; !strings.HasPrefix(got, want) { +			t.Errorf("got %q but wanted prefix %q in test %q", got, want, table.description) +		} +	} +} | 
