aboutsummaryrefslogtreecommitdiff
path: root/types
diff options
context:
space:
mode:
Diffstat (limited to 'types')
-rw-r--r--types/namespace.go56
-rw-r--r--types/namespace_pool.go61
-rw-r--r--types/namespace_pool_test.go91
-rw-r--r--types/namespace_test.go198
-rw-r--r--types/serialize.go28
-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.go40
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)
+ }
+ }
+}