From f367d220ff99eaee7debb234c3234de6c781359c Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Fri, 30 Oct 2020 20:40:17 +0100 Subject: refactor types and documentation Structured files a bit better, added more documentation, switched to pointers as default (unless specifically motivated not to do so), and encapsulated TLS (un)marshaling for the respective types that use it. --- type.go | 352 ++++++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 197 insertions(+), 155 deletions(-) (limited to 'type.go') diff --git a/type.go b/type.go index 5a32964..780d70e 100644 --- a/type.go +++ b/type.go @@ -2,10 +2,10 @@ package stfe import ( "fmt" + "time" "crypto/x509" "encoding/base64" - "time" "github.com/google/certificate-transparency-go/tls" "github.com/google/trillian" @@ -24,7 +24,7 @@ const ( StFormatChecksumV1 = 5 ) -// StItem references a versioned item based on a given format specifier. +// StItem references a versioned item based on a given format specifier type StItem struct { Format StFormat `tls:"maxval:65535"` SignedTreeHeadV1 *SignedTreeHeadV1 `tls:"selector:Format,val:1"` @@ -34,42 +34,15 @@ type StItem struct { ChecksumV1 *ChecksumV1 `tls:"selector:Format,val:5"` } -type ConsistencyProofV1 struct { - LogId []byte `tls:"minlen:32,maxlen:32"` - TreeSize1 uint64 - TreeSize2 uint64 - ConsistencyPath []NodeHash `tls:"minlen:1,maxlen:65535"` -} - +// SignedTreeHeadV1 is a signed tree head as defined by RFC 6962/bis, §4.10 type SignedTreeHeadV1 struct { LogId []byte `tls:"minlen:32,maxlen:32"` TreeHead TreeHeadV1 Signature []byte `tls:"minlen:1,maxlen:65535"` } -type TreeHeadV1 struct { - Timestamp uint64 - TreeSize uint64 - RootHash NodeHash - Extension []byte `tls:"minlen:0,maxlen:65535"` -} - -// ChecksumV1 associates a package name with an arbitrary checksum value -type ChecksumV1 struct { - Package []byte `tls:"minlen:1,maxlen:255"` - Checksum []byte `tls:"minlen:1,maxlen:64"` -} - -// InclusionProofV1 is a Merkle tree inclusion proof, see RFC 6962/bis (§4.12) -type InclusionProofV1 struct { - LogID []byte `tls:"minlen:32,maxlen:32"` - TreeSize uint64 - LeafIndex uint64 - InclusionPath []NodeHash `tls:"minlen:1,maxlen:65535"` -} - // SignedDebugInfoV1 is a signed statement that we intend (but do not promise) -// to insert an entry into the log. Only Ed25519 signatures are supported. +// to insert an entry into the log as defined by markdown/api.md // TODO: double-check that crypto/ed25519 encodes signature as in RFC 8032 type SignedDebugInfoV1 struct { LogId []byte `tls:"minlen:32,maxlen:32"` @@ -77,102 +50,51 @@ type SignedDebugInfoV1 struct { Signature []byte `tls:"minlen:1,maxlen:65535"` } -// NodeHash is a hashed Merkle tree node, see RFC 6962/bis (§4.9) -type NodeHash struct { - Data []byte `tls:"minlen:32,maxlen:255"` +// ConsistencyProofV1 is a consistency proof as defined by RFC 6962/bis, §4.11 +type ConsistencyProofV1 struct { + LogId []byte `tls:"minlen:32,maxlen:32"` + TreeSize1 uint64 + TreeSize2 uint64 + ConsistencyPath []NodeHash `tls:"minlen:1,maxlen:65535"` } -func NewSignedTreeHeadV1(th TreeHeadV1, logId, signature []byte) StItem { - return StItem{ - Format: StFormatSignedTreeHeadV1, - SignedTreeHeadV1: &SignedTreeHeadV1{ - LogId: logId, - TreeHead: th, - Signature: signature, - }, - } +// InclusionProofV1 is an inclusion proof as defined by RFC 6962/bis, §4.12 +type InclusionProofV1 struct { + LogId []byte `tls:"minlen:32,maxlen:32"` + TreeSize uint64 + LeafIndex uint64 + InclusionPath []NodeHash `tls:"minlen:1,maxlen:65535"` } -// NewTreeHead converts a Trillian-signed log root to a tree head without -// verifying any signature. In other words, Trillian <-> STFE is trusted. -func NewTreeHeadV1(lp *LogParameters, slr *trillian.SignedLogRoot) (TreeHeadV1, error) { - if slr == nil { - return TreeHeadV1{}, fmt.Errorf("Trillian returned no tree head") - } - - var lr types.LogRootV1 - if err := lr.UnmarshalBinary(slr.GetLogRoot()); err != nil { - return TreeHeadV1{}, fmt.Errorf("failed unmarshaling Trillian slr: %v", err) - } - if lp.HashType.Size() != len(lr.RootHash) { - return TreeHeadV1{}, fmt.Errorf("invalid Trillian root hash: %v", lr.RootHash) - } - - return TreeHeadV1{ - Timestamp: uint64(lr.TimestampNanos / 1000 / 1000), - TreeSize: uint64(lr.TreeSize), - RootHash: NodeHash{ - Data: lr.RootHash, - }, - Extension: nil, // no known extensions - }, nil +// ChecksumV1 associates a leaf type as defined by markdown/api.md +type ChecksumV1 struct { + Package []byte `tls:"minlen:1,maxlen:255"` + Checksum []byte `tls:"minlen:1,maxlen:64"` } -func NewSignedDebugInfoV1(logId, message, signature []byte) StItem { - return StItem{ - Format: StFormatSignedDebugInfoV1, - SignedDebugInfoV1: &SignedDebugInfoV1{ - LogId: logId, - Message: message, - Signature: signature, - }, - } +// TreeHeadV1 is a tree head as defined by RFC 6962/bis, §4.10 +type TreeHeadV1 struct { + Timestamp uint64 + TreeSize uint64 + RootHash NodeHash + Extension []byte `tls:"minlen:0,maxlen:65535"` } -// NewChecksumV1 creates a new StItem of type checksum_v1 -func NewChecksumV1(identifier []byte, checksum []byte) StItem { - return StItem{ - Format: StFormatChecksumV1, - ChecksumV1: &ChecksumV1{ - Package: identifier, - Checksum: checksum, - }, - } +// NodeHash is a Merkle tree hash as defined by RFC 6962/bis, §4.9 +type NodeHash struct { + Data []byte `tls:"minlen:32,maxlen:255"` } -// NewInclusionProofV1 creates a new StItem of type inclusion_proof_v1 -func NewInclusionProofV1(logID []byte, treeSize uint64, proof *trillian.Proof) StItem { - inclusionPath := make([]NodeHash, 0, len(proof.Hashes)) - for _, hash := range proof.Hashes { - inclusionPath = append(inclusionPath, NodeHash{Data: hash}) - } - - return StItem{ - Format: StFormatInclusionProofV1, - InclusionProofV1: &InclusionProofV1{ - LogID: logID, - TreeSize: treeSize, - LeafIndex: uint64(proof.LeafIndex), - InclusionPath: inclusionPath, - }, - } +// RawCertificate is a serialized X.509 certificate +type RawCertificate struct { + Data []byte `tls:"minlen:0,maxlen:65535"` } -func NewConsistencyProofV1(logId []byte, first, second int64, proof *trillian.Proof) StItem { - path := make([]NodeHash, 0, len(proof.Hashes)) - for _, hash := range proof.Hashes { - path = append(path, NodeHash{Data: hash}) - } - - return StItem{ - Format: StFormatConsistencyProofV1, - ConsistencyProofV1: &ConsistencyProofV1{ - LogId: logId, - TreeSize1: uint64(first), - TreeSize2: uint64(second), - ConsistencyPath: path, - }, - } +// Appendix is extra leaf data that is not stored in the log's Merkle tree +type Appendix struct { + Signature []byte `tls:"minlen:0,maxlen:16383"` + SignatureScheme uint16 + Chain []RawCertificate `tls:"minlen:0,maxlen:65535"` } func (f StFormat) String() string { @@ -197,24 +119,20 @@ func (f StFormat) String() string { func (i StItem) String() string { switch i.Format { case StFormatChecksumV1: - return fmt.Sprintf("Format(%s): %s", i.Format, *i.ChecksumV1) + return fmt.Sprintf("Format(%s): %s", i.Format, i.ChecksumV1) case StFormatConsistencyProofV1: - return fmt.Sprintf("Format(%s): %s", i.Format, *i.ConsistencyProofV1) + return fmt.Sprintf("Format(%s): %s", i.Format, i.ConsistencyProofV1) case StFormatInclusionProofV1: - return fmt.Sprintf("Format(%s): %s", i.Format, *i.InclusionProofV1) + return fmt.Sprintf("Format(%s): %s", i.Format, i.InclusionProofV1) case StFormatSignedDebugInfoV1: - return fmt.Sprintf("Format(%s): %s", i.Format, *i.SignedDebugInfoV1) + return fmt.Sprintf("Format(%s): %s", i.Format, i.SignedDebugInfoV1) case StFormatSignedTreeHeadV1: - return fmt.Sprintf("Format(%s): %s", i.Format, *i.SignedTreeHeadV1) + return fmt.Sprintf("Format(%s): %s", i.Format, i.SignedTreeHeadV1) default: return fmt.Sprintf("unknown StItem: %s", i.Format) } } -func (th TreeHeadV1) String() string { - return fmt.Sprintf("Timestamp(%s) TreeSize(%d) RootHash(%s)", time.Unix(int64(th.Timestamp/1000), 0), th.TreeSize, base64.StdEncoding.EncodeToString(th.RootHash.Data)) -} - func (i SignedTreeHeadV1) String() string { return fmt.Sprintf("LogId(%s) TreeHead(%s) Signature(%s)", base64.StdEncoding.EncodeToString(i.LogId), i.TreeHead, base64.StdEncoding.EncodeToString(i.Signature)) } @@ -223,8 +141,13 @@ func (i SignedDebugInfoV1) String() string { return fmt.Sprintf("LogId(%s) Message(%s) Signature(%s)", base64.StdEncoding.EncodeToString(i.LogId), string(i.Message), base64.StdEncoding.EncodeToString(i.Signature)) } -func (i ChecksumV1) String() string { - return fmt.Sprintf("Package(%v) Checksum(%v)", string(i.Package), base64.StdEncoding.EncodeToString(i.Checksum)) +func (i ConsistencyProofV1) String() string { + path := make([]string, 0, len(i.ConsistencyPath)) + for _, hash := range i.ConsistencyPath { + path = append(path, base64.StdEncoding.EncodeToString(hash.Data)) + } + + return fmt.Sprintf("LogID(%s) TreeSize1(%d) TreeSize2(%d) ConsistencyPath(%v)", base64.StdEncoding.EncodeToString(i.LogId), i.TreeSize1, i.TreeSize2, path) } func (i InclusionProofV1) String() string { @@ -233,63 +156,182 @@ func (i InclusionProofV1) String() string { path = append(path, base64.StdEncoding.EncodeToString(hash.Data)) } - return fmt.Sprintf("LogID(%s) TreeSize(%d) LeafIndex(%d) AuditPath(%v)", base64.StdEncoding.EncodeToString(i.LogID), i.TreeSize, i.LeafIndex, path) + return fmt.Sprintf("LogID(%s) TreeSize(%d) LeafIndex(%d) AuditPath(%v)", base64.StdEncoding.EncodeToString(i.LogId), i.TreeSize, i.LeafIndex, path) } -func (i ConsistencyProofV1) String() string { - path := make([]string, 0, len(i.ConsistencyPath)) - for _, hash := range i.ConsistencyPath { - path = append(path, base64.StdEncoding.EncodeToString(hash.Data)) +func (i ChecksumV1) String() string { + return fmt.Sprintf("Package(%s) Checksum(%s)", string(i.Package), base64.StdEncoding.EncodeToString(i.Checksum)) +} + +func (th TreeHeadV1) String() string { + return fmt.Sprintf("Timestamp(%s) TreeSize(%d) RootHash(%s)", time.Unix(int64(th.Timestamp/1000), 0), th.TreeSize, base64.StdEncoding.EncodeToString(th.RootHash.Data)) +} + +// Marshal serializes an Stitem as defined by RFC 5246 +func (i *StItem) Marshal() ([]byte, error) { + serialized, err := tls.Marshal(*i) + if err != nil { + return nil, fmt.Errorf("marshal failed for StItem(%s): %v", i.Format, err) } + return serialized, nil +} - return fmt.Sprintf("LogID(%s) TreeSize1(%d) TreeSize2(%d) ConsistencyPath(%v)", base64.StdEncoding.EncodeToString(i.LogId), i.TreeSize1, i.TreeSize2, path) +// MarshalB64 base64-encodes a serialized StItem +func (i *StItem) MarshalB64() (string, error) { + serialized, err := i.Marshal() + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(serialized), nil +} + +// Unmarshal unpacks a serialized StItem +func (i *StItem) Unmarshal(serialized []byte) error { + extra, err := tls.Unmarshal(serialized, i) + if err != nil { + return fmt.Errorf("unmarshal failed for StItem(%s): %v", i.Format, err) + } else if len(extra) > 0 { + return fmt.Errorf("unmarshal found extra data for StItem(%s): %v", i.Format, extra) + } + return nil } -// StItemFromB64 creates an StItem from a serialized and base64-encoded string -func StItemFromB64(s string) (StItem, error) { - b, err := base64.StdEncoding.DecodeString(s) +// UnmarshalB64 unpacks a base64-encoded serialized StItem +func (i *StItem) UnmarshalB64(s string) error { + serialized, err := base64.StdEncoding.DecodeString(s) if err != nil { - return StItem{}, fmt.Errorf("base64 decoding failed: %v", err) + return fmt.Errorf("base64 decoding failed for StItem(%s): %v", i.Format, err) } + return i.Unmarshal(serialized) +} - var item StItem - extra, err := tls.Unmarshal(b, &item) +// Marshal serializes an Appendix as defined by RFC 5246 +func (a *Appendix) Marshal() ([]byte, error) { + serialized, err := tls.Marshal(*a) if err != nil { - return StItem{}, fmt.Errorf("tls unmarshal failed: %v", err) + return nil, fmt.Errorf("marshal failed for Appendix(%v): %v", a, err) + } + return serialized, nil +} + +// Unmarshal unpacks an serialized Appendix +func (a *Appendix) Unmarshal(serialized []byte) error { + extra, err := tls.Unmarshal(serialized, a) + if err != nil { + return fmt.Errorf("unmarshal failed for Appendix(%v): %v", a, err) } else if len(extra) > 0 { - return StItem{}, fmt.Errorf("tls unmarshal found extra data: %v", extra) + return fmt.Errorf("unmarshal found extra data for Appendix(%v): %v", a, extra) } - return item, nil + return nil } -func StItemToB64(i StItem) (string, error) { - b, err := tls.Marshal(i) +// Marshal serializes a TreeHeadV1 as defined by RFC 5246 +func (th *TreeHeadV1) Marshal() ([]byte, error) { + serialized, err := tls.Marshal(*th) if err != nil { - return "", fmt.Errorf("StItem(%v) tls marshal failed: %v", i.Format, err) + return nil, fmt.Errorf("marshal failed for TreeHeadV1: %v", err) } - return base64.StdEncoding.EncodeToString(b), nil + return serialized, nil } -// Appendix is extra data that Trillian can store about a leaf -type Appendix struct { - Signature []byte `tls:"minlen:0,maxlen:16383"` - SignatureScheme uint16 - Chain []RawCertificate `tls:"minlen:0,maxlen:65535"` +// NewSignedTreeHead creates a new StItem of type signed_tree_head_v1 +func NewSignedTreeHeadV1(th *TreeHeadV1, logId, signature []byte) *StItem { + return &StItem{ + Format: StFormatSignedTreeHeadV1, + SignedTreeHeadV1: &SignedTreeHeadV1{ + LogId: logId, + TreeHead: *th, + Signature: signature, + }, + } } -// RawCertificate is a serialized X.509 certificate -type RawCertificate struct { - Data []byte `tls:"minlen:0,maxlen:65535"` +// NewSignedDebugInfoV1 creates a new StItem of type inclusion_proof_v1 +func NewSignedDebugInfoV1(logId, message, signature []byte) *StItem { + return &StItem{ + Format: StFormatSignedDebugInfoV1, + SignedDebugInfoV1: &SignedDebugInfoV1{ + LogId: logId, + Message: message, + Signature: signature, + }, + } +} + +// NewInclusionProofV1 creates a new StItem of type inclusion_proof_v1 +func NewInclusionProofV1(logID []byte, treeSize uint64, proof *trillian.Proof) *StItem { + inclusionPath := make([]NodeHash, 0, len(proof.Hashes)) + for _, hash := range proof.Hashes { + inclusionPath = append(inclusionPath, NodeHash{Data: hash}) + } + return &StItem{ + Format: StFormatInclusionProofV1, + InclusionProofV1: &InclusionProofV1{ + LogId: logID, + TreeSize: treeSize, + LeafIndex: uint64(proof.LeafIndex), + InclusionPath: inclusionPath, + }, + } +} + +// NewConsistencyProofV1 creates a new StItem of type consistency_proof_v1 +func NewConsistencyProofV1(logId []byte, first, second int64, proof *trillian.Proof) *StItem { + path := make([]NodeHash, 0, len(proof.Hashes)) + for _, hash := range proof.Hashes { + path = append(path, NodeHash{Data: hash}) + } + return &StItem{ + Format: StFormatConsistencyProofV1, + ConsistencyProofV1: &ConsistencyProofV1{ + LogId: logId, + TreeSize1: uint64(first), + TreeSize2: uint64(second), + ConsistencyPath: path, + }, + } +} + +// NewChecksumV1 creates a new StItem of type checksum_v1 +func NewChecksumV1(identifier []byte, checksum []byte) *StItem { + return &StItem{ + Format: StFormatChecksumV1, + ChecksumV1: &ChecksumV1{ + Package: identifier, + Checksum: checksum, + }, + } +} + +// NewTreeHead creates a new TreeHeadV1 from a Trillian-signed log root without +// verifying any signature. In other words, Trillian <-> STFE must be trusted. +func NewTreeHeadV1(lp *LogParameters, slr *trillian.SignedLogRoot) (*TreeHeadV1, error) { + var lr types.LogRootV1 + if err := lr.UnmarshalBinary(slr.GetLogRoot()); err != nil { + return nil, fmt.Errorf("failed unmarshaling Trillian slr: %v", err) + } + if lp.HashType.Size() != len(lr.RootHash) { + return nil, fmt.Errorf("invalid Trillian root hash: %v", lr.RootHash) + } + + return &TreeHeadV1{ + Timestamp: uint64(lr.TimestampNanos / 1000 / 1000), + TreeSize: uint64(lr.TreeSize), + RootHash: NodeHash{ + Data: lr.RootHash, + }, + Extension: nil, // no known extensions + }, nil } // NewAppendix creates a new leaf Appendix for an X.509 chain and signature -func NewAppendix(x509Chain []*x509.Certificate, signature []byte, signatureScheme uint16) Appendix { +func NewAppendix(x509Chain []*x509.Certificate, signature []byte, signatureScheme uint16) *Appendix { chain := make([]RawCertificate, 0, len(x509Chain)) for _, c := range x509Chain { chain = append(chain, RawCertificate{c.Raw}) } - return Appendix{ + return &Appendix{ Signature: signature, Chain: chain, SignatureScheme: signatureScheme, -- cgit v1.2.3