Skip to content

Commit 429d8b5

Browse files
authored
CTPolicy: always try to get SCTs from a tiled log first (#8676)
The Mozilla root program would really appreciate it if more certificates contained SCTs from tiled logs. This is because their revocation mechanism, CRLite, can only return a definitive answer for the status of a certificate after the MMD of at least one SCT on that cert has passed. Once the MMD has passed, CRLite can guarantee that it has included the cert in the total cert population while constructing its clubcard filters, and therefore can prevent any false positives or negatives for that cert. But RFC 6962 log typically have MMDs of 24h, while static logs have MMDs of ~1s (and are much easier for the CRLite infrastructure to read from), so CRLite works better when more certs have SCTs from static logs. Today, we treat tiled (static-ct-api) and untiled (rfc 6962) logs nearly the same when submitting precerts to get SCTs. We shuffle them together, pick two, attempt to get SCTs for a couple seconds, and if one or both are too slow, attempt to get SCTs from other logs further down the shuffled list. However, this doesn't actually result in a large proportion of our certs having static SCTs, for two reasons. First, there just aren't that many static logs: of the 11 logs we get SCTs from, only 4 are tiled. So simple statistics dictate that only about 36% of our certs will have one static SCT. Second, static logs have a slower write path, so we're more likely to give up on a submission to a static log and move on to attempting to submit to an RFC 6962 log instead. In order to give our certs a higher chance of containing an SCT from a static log, always ensure that a tiled log appears in the first two logs we submit to. This won't be a guarantee -- that log could be slow and we could get SCTs from the second and third logs in the shuffled list -- but it should bump our static SCT population from 36% to somewhere north of 90%.
1 parent efb6758 commit 429d8b5

3 files changed

Lines changed: 42 additions & 6 deletions

File tree

ctpolicy/ctpolicy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func (ctp *CTPolicy) GetSCTs(ctx context.Context, cert core.CertDER, expiration
111111
// Identify the set of candidate logs whose temporal interval includes this
112112
// cert's expiry. Randomize the order of the logs so that we're not always
113113
// trying to submit to the same two.
114-
logs := ctp.sctLogs.ForTime(expiration).Permute()
114+
logs := ctp.sctLogs.ForTime(expiration).Shuffle()
115115
if len(logs) < 2 {
116116
return nil, berrors.MissingSCTsError("Insufficient CT logs available (%d)", len(logs))
117117
}

ctpolicy/loglist/loglist.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,13 +226,25 @@ func (ll List) ForTime(expiry time.Time) List {
226226
return res
227227
}
228228

229-
// Permute returns a new log list containing the exact same logs, but in a
230-
// randomly-shuffled order.
231-
func (ll List) Permute() List {
229+
// Shuffle returns a new log list containing the exact same logs, but in a
230+
// nearly-randomly-shuffled order. If possible, it ensures that the first two
231+
// logs consist of one tiled log and one non-tiled log, to boost the percentage
232+
// of certs which include an SCT from a static log.
233+
func (ll List) Shuffle() List {
232234
res := slices.Clone(ll)
233235
rand.Shuffle(len(res), func(i int, j int) {
234236
res[i], res[j] = res[j], res[i]
235237
})
238+
if len(res) > 2 && res[0].Tiled == res[1].Tiled {
239+
// If we have more than two logs, and both are either tiled or not,
240+
// try to bring another log to the front of the list.
241+
for i := 2; i < len(res); i++ {
242+
if res[0].Tiled != res[i].Tiled {
243+
res[0], res[i] = res[i], res[0]
244+
break
245+
}
246+
}
247+
}
236248
return res
237249
}
238250

ctpolicy/loglist/loglist_test.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ func TestForTime(t *testing.T) {
160160
test.AssertDeepEquals(t, actual, expected)
161161
}
162162

163-
func TestPermute(t *testing.T) {
163+
func TestShuffle(t *testing.T) {
164164
input := List{
165165
Log{Name: "Log A1"},
166166
Log{Name: "Log A2"},
@@ -176,7 +176,7 @@ func TestPermute(t *testing.T) {
176176
}
177177

178178
for range 100 {
179-
actual := input.Permute()
179+
actual := input.Shuffle()
180180
for index, log := range actual {
181181
foundIndices[log.Name][index]++
182182
}
@@ -191,6 +191,30 @@ func TestPermute(t *testing.T) {
191191
}
192192
}
193193

194+
func TestShufflePrefersTiled(t *testing.T) {
195+
input := List{
196+
Log{Name: "Log A1"},
197+
Log{Name: "Log A2"},
198+
Log{Name: "Log T1", Tiled: true},
199+
}
200+
201+
foundIndices := make(map[string]map[int]int)
202+
for _, log := range input {
203+
foundIndices[log.Name] = make(map[int]int)
204+
}
205+
206+
for range 100 {
207+
actual := input.Shuffle()
208+
for index, log := range actual {
209+
foundIndices[log.Name][index]++
210+
}
211+
}
212+
213+
if foundIndices["Log T1"][2] != 0 {
214+
t.Errorf("Tiled log should have always been pulled into first two indices")
215+
}
216+
}
217+
194218
func TestGetByID(t *testing.T) {
195219
input := List{
196220
Log{Name: "Log A1", Id: "ID A1"},

0 commit comments

Comments
 (0)