aboutsummaryrefslogtreecommitdiff
path: root/type.go
diff options
context:
space:
mode:
Diffstat (limited to 'type.go')
-rw-r--r--type.go352
1 files changed, 197 insertions, 155 deletions
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,