Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions internal/ct/ctlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ type log struct {
}

// signSCT builds an SCT for a leaf.
type signSCT func(leaf *rfc6962.MerkleTreeLeaf) (*rfc6962.SignedCertificateTimestamp, error)
type signSCT func(sctInput rfc6962.CertificateTimestamp) (*rfc6962.SignedCertificateTimestamp, error)

// Storage provides functions to store certificates in a static-ct-api log.
type Storage interface {
// Add assigns an index to the provided Entry, stages the entry for integration, and returns a future for the assigned index.
Add(context.Context, *ctonly.Entry) (tessera.IndexFuture, error)
// DedupFuture fetches a duplicate tessera ctlog entry from the log and extracts its timestamp.
DedupFuture(context.Context, tessera.IndexFuture) (uint64, error)
// DedupFuture fetches the SCT input fields for a duplicate entry from the log.
DedupFuture(context.Context, tessera.IndexFuture) (rfc6962.CertificateTimestamp, error)
// AddIssuerChain stores every the chain certificate in a content-addressable store under their sha256 hash.
AddIssuerChain(context.Context, []*x509.Certificate) error
}
Expand Down
68 changes: 54 additions & 14 deletions internal/ct/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package ct
import (
"bytes"
"context"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
Expand All @@ -33,10 +34,12 @@ import (
"time"

"github.com/transparency-dev/tessera"
"github.com/transparency-dev/tessera/ctonly"
"github.com/transparency-dev/tesseract/internal/logger"
"github.com/transparency-dev/tesseract/internal/otel"

"github.com/transparency-dev/tesseract/internal/types/rfc6962"
"github.com/transparency-dev/tesseract/internal/types/staticct"
"github.com/transparency-dev/tesseract/internal/types/tls"
"github.com/transparency-dev/tesseract/internal/x509util"
"go.opentelemetry.io/otel/attribute"
Expand Down Expand Up @@ -507,29 +510,30 @@ func addChainInternal(ctx context.Context, opts *HandlerOptions, log *log, w htt
return http.StatusInternalServerError, nil, fmt.Errorf("couldn't resolve tessera future: %v", err)
}

var sctInput rfc6962.CertificateTimestamp
if index.IsDup {
if ok := opts.RateLimits.AcceptDedup(ctx); !ok {
w.Header().Add("Retry-After", strconv.Itoa(rand.IntN(5)+1)) // random retry within [1,6) seconds
return http.StatusTooManyRequests, []attribute.KeyValue{duplicateKey.Bool(index.IsDup), tooManyRequestsReasonKey.String("rate_limit_dedup")}, errors.New(http.StatusText(http.StatusTooManyRequests))
}
entry.Timestamp, err = log.storage.DedupFuture(ctx, future)
var err error
sctInput, err = log.storage.DedupFuture(ctx, future)
if err != nil {
return http.StatusInternalServerError, []attribute.KeyValue{duplicateKey.Bool(index.IsDup)}, fmt.Errorf("could not resolve duplicate: %v", err)
}
if err := sctMatchesEntry(sctInput, *entry, index.Index); err != nil {
return http.StatusInternalServerError, []attribute.KeyValue{duplicateKey.Bool(index.IsDup)}, fmt.Errorf("deduplicated entry in storage does not match submitted entry: %v", err)
}
} else {
var err error
// Build the full leaf at index like Tessera will do, then construct the SCT from it.
leafBytes := entry.MerkleTreeLeaf(index.Index)
sctInput, err = staticct.ExtractCertificateTimestampFromLeaf(leafBytes)
if err != nil {
return http.StatusInternalServerError, nil, fmt.Errorf("failed to extract SCT input from leaf: %v", err)
}
}

// Always use the returned leaf as the basis for an SCT.
var loggedLeaf rfc6962.MerkleTreeLeaf
leafValue := entry.MerkleTreeLeaf(index.Index)
if rest, err := tls.Unmarshal(leafValue, &loggedLeaf); err != nil {
return http.StatusInternalServerError, nil, fmt.Errorf("failed to reconstruct MerkleTreeLeaf: %s", err)
} else if len(rest) > 0 {
return http.StatusInternalServerError, nil, fmt.Errorf("extra data (%d bytes) on reconstructing MerkleTreeLeaf", len(rest))
}

// As the Log server has definitely got the Merkle tree leaf, we can
// generate an SCT and respond with it.
sct, err := log.signSCT(&loggedLeaf)
sct, err := log.signSCT(sctInput)
if err != nil {
return http.StatusInternalServerError, nil, fmt.Errorf("failed to generate SCT: %s", err)
}
Expand All @@ -553,6 +557,42 @@ func addChainInternal(ctx context.Context, opts *HandlerOptions, log *log, w htt
return http.StatusOK, []attribute.KeyValue{duplicateKey.Bool(index.IsDup)}, nil
}

// sctMatchesEntry checks that sctInput fields match with an entry.
// It checks for all the sctInput fields, except for the timestamp which might
// not be set in the entry yet.
func sctMatchesEntry(sct rfc6962.CertificateTimestamp, e ctonly.Entry, idx uint64) error {
extractedIdx, err := staticct.ParseCTExtensionsBytes(sct.Extensions)
if err != nil {
return fmt.Errorf("ParseCTExtensionsBytes: %v", err)
}
if extractedIdx != idx {
return fmt.Errorf("index mismatch: stored=%d, expected=%d", extractedIdx, idx)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message seems like it could be misleading if err != nil?

}
isPrecert := sct.EntryType == rfc6962.PrecertLogEntryType
if isPrecert != e.IsPrecert {
return fmt.Errorf("isPrecert mismatch: sct.IsPrecert=%v, e.IsPrecert=%v", isPrecert, e.IsPrecert)
}
var sctCert []byte
if isPrecert {
if sct.PrecertEntry == nil {
return errors.New("sct.PrecertEntry is nil")
}
sctCert = sct.PrecertEntry.TBSCertificate
if !bytes.Equal(sct.PrecertEntry.IssuerKeyHash[:], e.IssuerKeyHash) {
return fmt.Errorf("issuerKeyHash mismatch: stored=issuer/%x != submitted=issuer/%x", sct.PrecertEntry.IssuerKeyHash[:], e.IssuerKeyHash)
}
} else {
if sct.X509Entry == nil {
return errors.New("sct.X509Entry is nil")
}
sctCert = sct.X509Entry.Data
}
if !bytes.Equal(sctCert, e.Certificate) {
return fmt.Errorf("certificate mismatch: stored(len=%d, sha256=%x) != submitted(len=%d, sha256=%x)", len(sctCert), sha256.Sum256(sctCert), len(e.Certificate), sha256.Sum256(e.Certificate))
}
return nil
}

func addChain(ctx context.Context, opts *HandlerOptions, log *log, w http.ResponseWriter, r *http.Request) (int, []attribute.KeyValue, error) {
ctx, span := tracer.Start(ctx, "tesseract.addChain")
defer span.End()
Expand Down
4 changes: 2 additions & 2 deletions internal/ct/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ func TestAddChain(t *testing.T) {
}

// Check that the Extensions contains the expected index.
idx, err := staticct.ParseCTExtensions(gotRsp.Extensions)
idx, err := staticct.ParseCTExtensionsB64(gotRsp.Extensions)
if err != nil {
t.Errorf("Failed to parse extensions %q: %v", gotRsp.Extensions, err)
}
Expand Down Expand Up @@ -716,7 +716,7 @@ func TestAddPreChain(t *testing.T) {
}

// Check that the Extensions contains the expected index.
idx, err := staticct.ParseCTExtensions(gotRsp.Extensions)
idx, err := staticct.ParseCTExtensionsB64(gotRsp.Extensions)
if err != nil {
t.Errorf("Failed to parse extensions %q: %v", gotRsp.Extensions, err)
}
Expand Down
39 changes: 2 additions & 37 deletions internal/ct/signatures.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,43 +35,8 @@ type sctSigner struct {
signer crypto.Signer
}

// serializeSCTSignatureInput serializes the passed in sct and log entry into
// the correct format for signing.
func serializeSCTSignatureInput(sct rfc6962.SignedCertificateTimestamp, entry rfc6962.LogEntry) ([]byte, error) {
switch sct.SCTVersion {
case rfc6962.V1:
input := rfc6962.CertificateTimestamp{
SCTVersion: sct.SCTVersion,
SignatureType: rfc6962.CertificateTimestampSignatureType,
Timestamp: sct.Timestamp,
EntryType: entry.Leaf.TimestampedEntry.EntryType,
Extensions: sct.Extensions,
}
switch entry.Leaf.TimestampedEntry.EntryType {
case rfc6962.X509LogEntryType:
input.X509Entry = entry.Leaf.TimestampedEntry.X509Entry
case rfc6962.PrecertLogEntryType:
input.PrecertEntry = &rfc6962.PreCert{
IssuerKeyHash: entry.Leaf.TimestampedEntry.PrecertEntry.IssuerKeyHash,
TBSCertificate: entry.Leaf.TimestampedEntry.PrecertEntry.TBSCertificate,
}
default:
return nil, fmt.Errorf("unsupported entry type %s", entry.Leaf.TimestampedEntry.EntryType)
}
return tls.Marshal(input)
default:
return nil, fmt.Errorf("unknown SCT version %d", sct.SCTVersion)
}
}

func (sctSigner *sctSigner) Sign(leaf *rfc6962.MerkleTreeLeaf) (*rfc6962.SignedCertificateTimestamp, error) {
// Serialize SCT signature input to get the bytes that need to be signed
sctInput := rfc6962.SignedCertificateTimestamp{
SCTVersion: rfc6962.V1,
Timestamp: leaf.TimestampedEntry.Timestamp,
Extensions: leaf.TimestampedEntry.Extensions,
}
data, err := serializeSCTSignatureInput(sctInput, rfc6962.LogEntry{Leaf: *leaf})
func (sctSigner *sctSigner) Sign(sctInput rfc6962.CertificateTimestamp) (*rfc6962.SignedCertificateTimestamp, error) {
data, err := tls.Marshal(sctInput)
if err != nil {
return nil, fmt.Errorf("failed to serialize SCT data: %v", err)
}
Expand Down
76 changes: 18 additions & 58 deletions internal/ct/signatures_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/transparency-dev/tesseract/internal/types/rfc6962"
"github.com/transparency-dev/tesseract/internal/types/tls"
"github.com/transparency-dev/tesseract/internal/x509util"
"github.com/transparency-dev/tesseract/internal/types/staticct"
)

var (
Expand All @@ -41,9 +42,7 @@ var (
)

const (
defaultSCTLogIDString string = "iamapublickeyshatwofivesixdigest"
defaultSCTTimestamp uint64 = 1234
defaultSCTSignatureString string = "\x04\x03\x00\x09signature"
defaultCertifictateString string = "certificate"
defaultPrecertIssuerHashString string = "iamapublickeyshatwofivesixdigest"
defaultPrecertTBSString string = "tbs"
Expand Down Expand Up @@ -100,28 +99,6 @@ const (
"696d757374626565786163746c7974686972747974776f62797465736c6f6e67"
)

func defaultSCTLogID() rfc6962.LogID {
var id rfc6962.LogID
copy(id.KeyID[:], defaultSCTLogIDString)
return id
}

func defaultSCTSignature() rfc6962.DigitallySigned {
var ds rfc6962.DigitallySigned
if _, err := tls.Unmarshal([]byte(defaultSCTSignatureString), &ds); err != nil {
panic(err)
}
return ds
}

func defaultSCT() rfc6962.SignedCertificateTimestamp {
return rfc6962.SignedCertificateTimestamp{
SCTVersion: rfc6962.V1,
LogID: defaultSCTLogID(),
Timestamp: defaultSCTTimestamp,
Extensions: []byte{},
Signature: defaultSCTSignature()}
}

func defaultCertificate() []byte {
return []byte(defaultCertifictateString)
Expand All @@ -136,19 +113,8 @@ func defaultCertificateSCTSignatureInput(t *testing.T) []byte {
return r
}

func defaultCertificateLogEntry() rfc6962.LogEntry {
return rfc6962.LogEntry{
Index: 1,
Leaf: rfc6962.MerkleTreeLeaf{
Version: rfc6962.V1,
LeafType: rfc6962.TimestampedEntryLeafType,
TimestampedEntry: &rfc6962.TimestampedEntry{
Timestamp: defaultSCTTimestamp,
EntryType: rfc6962.X509LogEntryType,
X509Entry: &rfc6962.ASN1Cert{Data: defaultCertificate()},
},
},
}
func defaultCertificateSCTInput() *rfc6962.CertificateTimestamp {
return staticct.NewCertificateTimestamp([]byte{}, defaultSCTTimestamp, false, defaultCertificate(), [32]byte{})
}

func defaultPrecertSCTSignatureInput(t *testing.T) []byte {
Expand All @@ -170,22 +136,9 @@ func defaultPrecertIssuerHash() [32]byte {
return b
}

func defaultPrecertLogEntry() rfc6962.LogEntry {
return rfc6962.LogEntry{
Index: 1,
Leaf: rfc6962.MerkleTreeLeaf{
Version: rfc6962.V1,
LeafType: rfc6962.TimestampedEntryLeafType,
TimestampedEntry: &rfc6962.TimestampedEntry{
Timestamp: defaultSCTTimestamp,
EntryType: rfc6962.PrecertLogEntryType,
PrecertEntry: &rfc6962.PreCert{
IssuerKeyHash: defaultPrecertIssuerHash(),
TBSCertificate: defaultPrecertTBS(),
},
},
},
}
func defaultPrecertSCTInput() *rfc6962.CertificateTimestamp {
ikh := defaultPrecertIssuerHash()
return staticct.NewCertificateTimestamp([]byte{}, defaultSCTTimestamp, true, defaultPrecertTBS(), ikh)
}

func defaultSTH() rfc6962.SignedTreeHead {
Expand Down Expand Up @@ -214,7 +167,7 @@ func mustDehex(t *testing.T, h string) []byte {
}

func TestSerializeV1SCTSignatureInputForCertificateKAT(t *testing.T) {
serialized, err := serializeSCTSignatureInput(defaultSCT(), defaultCertificateLogEntry())
serialized, err := tls.Marshal(*defaultCertificateSCTInput())
if err != nil {
t.Fatalf("Failed to serialize SCT for signing: %v", err)
}
Expand All @@ -224,7 +177,7 @@ func TestSerializeV1SCTSignatureInputForCertificateKAT(t *testing.T) {
}

func TestSerializeV1SCTSignatureInputForPrecertKAT(t *testing.T) {
serialized, err := serializeSCTSignatureInput(defaultSCT(), defaultPrecertLogEntry())
serialized, err := tls.Marshal(*defaultPrecertSCTInput())
if err != nil {
t.Fatalf("Failed to serialize SCT for signing: %v", err)
}
Expand Down Expand Up @@ -268,7 +221,11 @@ func TestBuildV1MerkleTreeLeafForCert(t *testing.T) {
} else if len(rest) > 0 {
t.Fatalf("extra data (%d bytes) on reconstructing MerkleTreeLeaf", len(rest))
}
got, err := sctSigner.Sign(&leaf)
input, err := staticct.ExtractCertificateTimestampFromLeaf(leafValue)
if err != nil {
t.Fatalf("ExtractCertificateTimestampFromLeaf()=nil,%v; want _,nil", err)
}
got, err := sctSigner.Sign(input)
if err != nil {
t.Fatalf("buildV1SCT()=nil,%v; want _,nil", err)
}
Expand Down Expand Up @@ -333,8 +290,11 @@ func TestSignV1SCTForPrecertificate(t *testing.T) {
} else if len(rest) > 0 {
t.Fatalf("extra data (%d bytes) on reconstructing MerkleTreeLeaf", len(rest))
}

got, err := sctSigner.Sign(&leaf)
input, err := staticct.ExtractCertificateTimestampFromLeaf(leafValue)
if err != nil {
t.Fatalf("ExtractCertificateTimestampFromLeaf()=nil,%v; want _,nil", err)
}
got, err := sctSigner.Sign(input)
if err != nil {
t.Fatalf("buildV1SCT()=nil,%v; want _,nil", err)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/hammer/hammer.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ func parseAddChainResponse(body []byte) (uint64, uint64, error) {
return 0, 0, fmt.Errorf("can't parse add-chain response: %v", err)
}

leafIdx, err := staticct.ParseCTExtensions(resp.Extensions)
leafIdx, err := staticct.ParseCTExtensionsB64(resp.Extensions)
if err != nil {
return 0, 0, fmt.Errorf("can't parse extensions: %v", err)
}
Expand Down
Loading
Loading