Skip to content

Commit d79b760

Browse files
authored
Deduplicate SCT timestamps (#57)
* deduplicate timestamps * Edit dedup terraform * address comments * fix bug * delete comma * address comments
1 parent 43dbbbe commit d79b760

File tree

9 files changed

+147
-89
lines changed

9 files changed

+147
-89
lines changed

deployment/modules/gcp/storage/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@ resource "google_spanner_database" "dedup_db" {
6060
instance = google_spanner_instance.log_spanner.name
6161
name = "${var.base_name}-dedup-db"
6262
ddl = [
63-
"CREATE TABLE IDSeq (id INT64 NOT NULL, h BYTES(MAX) NOT NULL, idx INT64 NOT NULL,) PRIMARY KEY (id, h)",
63+
"CREATE TABLE IDSeq (id INT64 NOT NULL, h BYTES(MAX) NOT NULL, idx INT64 NOT NULL, timestamp INT64 NOT NULL,) PRIMARY KEY (id, h)",
6464
]
6565
}

handlers.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/google/certificate-transparency-go/x509"
3535
"github.com/google/certificate-transparency-go/x509util"
3636
"github.com/google/trillian/monitoring"
37+
"github.com/transparency-dev/static-ct/modules/dedup"
3738
tessera "github.com/transparency-dev/trillian-tessera"
3839
"github.com/transparency-dev/trillian-tessera/ctonly"
3940
"k8s.io/klog/v2"
@@ -319,13 +320,15 @@ func addChainInternal(ctx context.Context, li *logInfo, w http.ResponseWriter, r
319320
}
320321

321322
klog.V(2).Infof("%s: %s => storage.GetCertIndex", li.LogOrigin, method)
322-
idx, isDup, err := li.storage.GetCertIndex(ctx, chain[0])
323+
sctDedupInfo, isDup, err := li.storage.GetCertDedupInfo(ctx, chain[0])
324+
idx := sctDedupInfo.Idx
323325
if err != nil {
324326
return http.StatusInternalServerError, fmt.Errorf("couldn't deduplicate the request: %s", err)
325327
}
326328

327329
if isDup {
328330
klog.V(3).Infof("%s: %s - found duplicate entry at index %d", li.LogOrigin, method, idx)
331+
entry.Timestamp = sctDedupInfo.Timestamp
329332
} else {
330333
if err := li.storage.AddIssuerChain(ctx, chain[1:]); err != nil {
331334
return http.StatusInternalServerError, fmt.Errorf("failed to store issuer chain: %s", err)
@@ -344,7 +347,7 @@ func addChainInternal(ctx context.Context, li *logInfo, w http.ResponseWriter, r
344347
// It might be stored again later, if a local deduplication storage is synced, potentially
345348
// with a smaller value.
346349
klog.V(2).Infof("%s: %s => storage.AddCertIndex", li.LogOrigin, method)
347-
err := li.storage.AddCertIndex(ctx, chain[0], idx)
350+
err = li.storage.AddCertDedupInfo(ctx, chain[0], dedup.SCTDedupInfo{Idx: idx, Timestamp: entry.Timestamp})
348351
// TODO: block log writes if deduplication breaks
349352
if err != nil {
350353
klog.Warningf("AddCertIndex(): failed to store certificate index: %v", err)

handlers_test.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"github.com/google/go-cmp/cmp/cmpopts"
3737
"github.com/google/trillian/monitoring"
3838
"github.com/transparency-dev/static-ct/mockstorage"
39+
"github.com/transparency-dev/static-ct/modules/dedup"
3940
"github.com/transparency-dev/static-ct/testdata"
4041
"github.com/transparency-dev/trillian-tessera/ctonly"
4142
"google.golang.org/grpc/codes"
@@ -291,10 +292,10 @@ func TestAddChainWhitespace(t *testing.T) {
291292
for _, test := range tests {
292293
t.Run(test.descr, func(t *testing.T) {
293294
if test.want == http.StatusOK {
294-
info.storage.EXPECT().GetCertIndex(deadlineMatcher(), cmpMatcher{leafChain[0]}).Return(uint64(0), false, nil)
295+
info.storage.EXPECT().GetCertDedupInfo(deadlineMatcher(), cmpMatcher{leafChain[0]}).Return(dedup.SCTDedupInfo{Idx: uint64(0), Timestamp: fakeTimeMillis}, false, nil)
295296
info.storage.EXPECT().AddIssuerChain(deadlineMatcher(), cmpMatcher{leafChain[1:]}).Return(nil)
296297
info.storage.EXPECT().Add(deadlineMatcher(), cmpMatcher{req}).Return(func() (uint64, error) { return rsp, nil })
297-
info.storage.EXPECT().AddCertIndex(deadlineMatcher(), cmpMatcher{leafChain[0]}, uint64(0)).Return(nil)
298+
info.storage.EXPECT().AddCertDedupInfo(deadlineMatcher(), cmpMatcher{leafChain[0]}, dedup.SCTDedupInfo{Idx: uint64(0), Timestamp: fakeTimeMillis}).Return(nil)
298299
}
299300

300301
recorder := httptest.NewRecorder()
@@ -367,11 +368,11 @@ func TestAddChain(t *testing.T) {
367368
if len(test.toSign) > 0 {
368369
req, leafChain := parseChain(t, false, test.chain, info.roots.RawCertificates()[0])
369370
rsp := uint64(0)
370-
info.storage.EXPECT().GetCertIndex(deadlineMatcher(), cmpMatcher{leafChain[0]}).Return(uint64(0), false, nil)
371+
info.storage.EXPECT().GetCertDedupInfo(deadlineMatcher(), cmpMatcher{leafChain[0]}).Return(dedup.SCTDedupInfo{Idx: uint64(0), Timestamp: fakeTimeMillis}, false, nil)
371372
info.storage.EXPECT().AddIssuerChain(deadlineMatcher(), cmpMatcher{leafChain[1:]}).Return(nil)
372373
info.storage.EXPECT().Add(deadlineMatcher(), cmpMatcher{req}).Return(func() (uint64, error) { return rsp, test.err })
373374
if test.want == http.StatusOK {
374-
info.storage.EXPECT().AddCertIndex(deadlineMatcher(), cmpMatcher{leafChain[0]}, uint64(0)).Return(nil)
375+
info.storage.EXPECT().AddCertDedupInfo(deadlineMatcher(), cmpMatcher{leafChain[0]}, dedup.SCTDedupInfo{Idx: uint64(0), Timestamp: fakeTimeMillis}).Return(nil)
375376
}
376377
}
377378

@@ -460,11 +461,11 @@ func TestAddPrechain(t *testing.T) {
460461
if len(test.toSign) > 0 {
461462
req, leafChain := parseChain(t, true, test.chain, info.roots.RawCertificates()[0])
462463
rsp := uint64(0)
463-
info.storage.EXPECT().GetCertIndex(deadlineMatcher(), cmpMatcher{leafChain[0]}).Return(uint64(0), false, nil)
464+
info.storage.EXPECT().GetCertDedupInfo(deadlineMatcher(), cmpMatcher{leafChain[0]}).Return(dedup.SCTDedupInfo{Idx: uint64(0), Timestamp: fakeTimeMillis}, false, nil)
464465
info.storage.EXPECT().AddIssuerChain(deadlineMatcher(), cmpMatcher{leafChain[1:]}).Return(nil)
465466
info.storage.EXPECT().Add(deadlineMatcher(), cmpMatcher{req}).Return(func() (uint64, error) { return rsp, test.err })
466467
if test.want == http.StatusOK {
467-
info.storage.EXPECT().AddCertIndex(deadlineMatcher(), cmpMatcher{leafChain[0]}, uint64(0)).Return(nil)
468+
info.storage.EXPECT().AddCertDedupInfo(deadlineMatcher(), cmpMatcher{leafChain[0]}, dedup.SCTDedupInfo{Idx: uint64(0), Timestamp: fakeTimeMillis}).Return(nil)
468469
}
469470
}
470471

@@ -484,7 +485,7 @@ func TestAddPrechain(t *testing.T) {
484485
if got, want := resp.ID, demoLogID[:]; !bytes.Equal(got, want) {
485486
t.Errorf("resp.ID=%x; want %x", got, want)
486487
}
487-
if got, want := resp.Timestamp, uint64(1469185273000); got != want {
488+
if got, want := resp.Timestamp, fakeTimeMillis; got != want {
488489
t.Errorf("resp.Timestamp=%d; want %d", got, want)
489490
}
490491
if got, want := hex.EncodeToString(resp.Signature), "040300067369676e6564"; got != want {

mockstorage/mock_ct_storage.go

Lines changed: 14 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

modules/dedup/dedup.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,31 @@ import (
2828
"k8s.io/klog/v2"
2929
)
3030

31-
// LeafIdx holds a LeafID and an Idx for deduplication
32-
type LeafIdx struct {
31+
// LeafDedupInfo enables building deduplicated add-pre-chain/add-chain responses.
32+
type LeafDedupInfo struct {
3333
LeafID []byte
34-
Idx uint64
34+
SCTDedupInfo
35+
}
36+
37+
// SCTDedupInfo contains data to build idempotent SCTs.
38+
type SCTDedupInfo struct {
39+
Idx uint64
40+
Timestamp uint64
3541
}
3642

3743
type BEDedupStorage interface {
38-
Add(ctx context.Context, lidxs []LeafIdx) error
39-
Get(ctx context.Context, leafID []byte) (uint64, bool, error)
44+
Add(ctx context.Context, lidxs []LeafDedupInfo) error
45+
Get(ctx context.Context, leafID []byte) (SCTDedupInfo, bool, error)
4046
}
4147

4248
// TODO: re-architecture to prevent creating a LocaLBEDedupStorage without calling UpdateFromLog
4349
type LocalBEDedupStorage interface {
44-
Add(ctx context.Context, lidxs []LeafIdx) error
45-
Get(ctx context.Context, leafID []byte) (uint64, bool, error)
50+
Add(ctx context.Context, lidxs []LeafDedupInfo) error
51+
Get(ctx context.Context, leafID []byte) (SCTDedupInfo, bool, error)
4652
LogSize() (uint64, error)
4753
}
4854

49-
type ParseBundleFunc func([]byte, uint64) ([]LeafIdx, error)
55+
type ParseBundleFunc func([]byte, uint64) ([]LeafDedupInfo, error)
5056

5157
// UpdateFromLog synchronises a local best effort deduplication storage with a log.
5258
func UpdateFromLog(ctx context.Context, lds LocalBEDedupStorage, t time.Duration, fcp client.CheckpointFetcherFunc, fb client.EntryBundleFetcherFunc, pb ParseBundleFunc) {
@@ -97,12 +103,12 @@ func sync(ctx context.Context, lds LocalBEDedupStorage, pb ParseBundleFunc, fcp
97103
}
98104
return fmt.Errorf("failed to fetch leaf bundle at index %d: %v", i, err)
99105
}
100-
lidxs, err := pb(eRaw, i)
106+
ldis, err := pb(eRaw, i)
101107
if err != nil {
102108
return fmt.Errorf("parseBundle(): %v", err)
103109
}
104110

105-
if err := lds.Add(ctx, lidxs); err != nil {
111+
if err := lds.Add(ctx, ldis); err != nil {
106112
return fmt.Errorf("error storing deduplication data for tile %d: %v", i, err)
107113
}
108114
klog.V(3).Infof("LocalBEDEdup.sync(): stored dedup data for entry bundle %d, %d more bundles to go", i, ckptSize/256-i)

serialize.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,15 +176,15 @@ func NewCpSigner(signer crypto.Signer, origin string, logID [32]byte, timeSource
176176
return ctSigner
177177
}
178178

179-
// DedupFromBundle converts a bundle into an array of {leafID, idx}.
179+
// DedupFromBundle converts a bundle into an array of dedup.LeafDedupInfo.
180180
//
181181
// The index of a leaf is computed from its position in the log, instead of parsing SCTs.
182182
// Greatly inspired by https://github.com/FiloSottile/sunlight/blob/main/tile.go
183-
func DedupFromBundle(bundle []byte, bundleIdx uint64) ([]dedup.LeafIdx, error) {
184-
kvs := []dedup.LeafIdx{}
183+
func DedupFromBundle(bundle []byte, bundleIdx uint64) ([]dedup.LeafDedupInfo, error) {
184+
kvs := []dedup.LeafDedupInfo{}
185185
s := cryptobyte.String(bundle)
186186

187-
for len(s) > 0 {
187+
for i := bundleIdx * 256; len(s) > 0; i++ {
188188
var timestamp uint64
189189
var entryType uint16
190190
var extensions, fingerprints cryptobyte.String
@@ -213,7 +213,8 @@ func DedupFromBundle(bundle []byte, bundleIdx uint64) ([]dedup.LeafIdx, error) {
213213
return nil, fmt.Errorf("invalid data tile: unknown type %d", entryType)
214214
}
215215
k := sha256.Sum256(crt)
216-
kvs = append(kvs, dedup.LeafIdx{LeafID: k[:], Idx: bundleIdx*256 + uint64(len(kvs))})
216+
sctDedupInfo := dedup.SCTDedupInfo{Idx: uint64(i), Timestamp: timestamp}
217+
kvs = append(kvs, dedup.LeafDedupInfo{LeafID: k[:], SCTDedupInfo: sctDedupInfo})
217218
}
218219
return kvs, nil
219220
}

storage.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import (
2222
"sync"
2323

2424
"github.com/google/certificate-transparency-go/x509"
25+
"github.com/transparency-dev/static-ct/modules/dedup"
2526
tessera "github.com/transparency-dev/trillian-tessera"
2627
"github.com/transparency-dev/trillian-tessera/ctonly"
27-
"github.com/transparency-dev/static-ct/modules/dedup"
2828
"k8s.io/klog/v2"
2929
)
3030

@@ -41,10 +41,10 @@ type Storage interface {
4141
Add(context.Context, *ctonly.Entry) tessera.IndexFuture
4242
// AddIssuerChain stores every the chain certificate in a content-addressable store under their sha256 hash.
4343
AddIssuerChain(context.Context, []*x509.Certificate) error
44-
// AddCertIndex stores the index of certificate in a log under its hash.
45-
AddCertIndex(context.Context, *x509.Certificate, uint64) error
46-
// GetCertIndex gets the index of certificate in a log from its hash.
47-
GetCertIndex(context.Context, *x509.Certificate) (uint64, bool, error)
44+
// AddCertDedupInfo stores the SCTDedupInfo of certificate in a log under its hash.
45+
AddCertDedupInfo(context.Context, *x509.Certificate, dedup.SCTDedupInfo) error
46+
// GetCertDedupInfo gets the SCTDedupInfo of certificate in a log from its hash.
47+
GetCertDedupInfo(context.Context, *x509.Certificate) (dedup.SCTDedupInfo, bool, error)
4848
}
4949

5050
type KV struct {
@@ -131,21 +131,21 @@ func cachedStoreIssuers(s IssuerStorage) func(context.Context, []KV) error {
131131
}
132132
}
133133

134-
// AddCertIndex stores <cert_hash, index> in the deduplication storage.
135-
func (cts CTStorage) AddCertIndex(ctx context.Context, c *x509.Certificate, idx uint64) error {
134+
// AddCertDedupInfo stores <cert_hash, SCTDedupInfo> in the deduplication storage.
135+
func (cts CTStorage) AddCertDedupInfo(ctx context.Context, c *x509.Certificate, sctDedupInfo dedup.SCTDedupInfo) error {
136136
key := sha256.Sum256(c.Raw)
137-
if err := cts.dedupStorage.Add(ctx, []dedup.LeafIdx{{LeafID: key[:], Idx: idx}}); err != nil {
138-
return fmt.Errorf("error storing index %d of %q: %v", idx, hex.EncodeToString(key[:]), err)
137+
if err := cts.dedupStorage.Add(ctx, []dedup.LeafDedupInfo{{LeafID: key[:], SCTDedupInfo: sctDedupInfo}}); err != nil {
138+
return fmt.Errorf("error storing SCTDedupInfo %+v of \"%x\": %v", sctDedupInfo, key, err)
139139
}
140140
return nil
141141
}
142142

143-
// GetCertIndex fetches the index of a given certificate from the deduplication storage.
144-
func (cts CTStorage) GetCertIndex(ctx context.Context, c *x509.Certificate) (uint64, bool, error) {
143+
// GetCertDedupInfo fetches the SCTDedupInfo of a given certificate from the deduplication storage.
144+
func (cts CTStorage) GetCertDedupInfo(ctx context.Context, c *x509.Certificate) (dedup.SCTDedupInfo, bool, error) {
145145
key := sha256.Sum256(c.Raw)
146-
idx, ok, err := cts.dedupStorage.Get(ctx, key[:])
146+
sctC, ok, err := cts.dedupStorage.Get(ctx, key[:])
147147
if err != nil {
148-
return 0, false, fmt.Errorf("error fetching index of %q: %v", hex.EncodeToString(key[:]), err)
148+
return dedup.SCTDedupInfo{}, false, fmt.Errorf("error fetching index of \"%x\": %v", key, err)
149149
}
150-
return idx, ok, nil
150+
return sctC, ok, nil
151151
}

0 commit comments

Comments
 (0)