forked from google/certificate-transparency-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ctpolicy.go
224 lines (204 loc) · 7.25 KB
/
ctpolicy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package ctpolicy contains structs describing CT policy requirements and corresponding logic.
package ctpolicy
import (
"fmt"
"sync"
"github.com/google/certificate-transparency-go/loglist2"
"github.com/google/certificate-transparency-go/x509"
)
const (
// BaseName is name for the group covering all logs.
BaseName = "All-logs"
)
// LogGroupInfo holds information on a single group of logs specified by Policy.
type LogGroupInfo struct {
Name string
LogURLs map[string]bool // set of members
MinInclusions int // Required number of submissions.
IsBase bool // True only for Log-group covering all logs.
LogWeights map[string]float32 // weights used for submission, default weight is 1
wMu sync.RWMutex // guards weights
}
func (group *LogGroupInfo) setMinInclusions(i int) error {
if i < 0 {
return fmt.Errorf("cannot assign negative minimal inclusions number")
}
// Assign given number even if it's bigger than group size.
group.MinInclusions = i
if i > len(group.LogURLs) {
return fmt.Errorf("trying to assign %d minimal inclusion number while only %d logs are part of group %q", i, len(group.LogURLs), group.Name)
}
return nil
}
func (group *LogGroupInfo) populate(ll *loglist2.LogList, included func(op *loglist2.Operator) bool) {
group.LogURLs = make(map[string]bool)
group.LogWeights = make(map[string]float32)
for _, op := range ll.Operators {
if included(op) {
for _, l := range op.Logs {
group.LogURLs[l.URL] = true
group.LogWeights[l.URL] = 1.0
}
}
}
}
// satisfyMinimalInclusion returns whether number of positive weights is
// bigger or equal to minimal inclusion number.
func (group *LogGroupInfo) satisfyMinimalInclusion(weights map[string]float32) bool {
nonZeroNum := 0
for logURL, w := range weights {
if group.LogURLs[logURL] && w > 0.0 {
nonZeroNum++
if nonZeroNum >= group.MinInclusions {
return true
}
}
}
return false
}
// SetLogWeights applies suggested weights to the Log-group. Does not reset
// weights and returns error when there are not enough positive weights
// provided to reach minimal inclusion number.
func (group *LogGroupInfo) SetLogWeights(weights map[string]float32) error {
for logURL, w := range weights {
if w < 0.0 {
return fmt.Errorf("trying to assign negative weight %v to Log %q", w, logURL)
}
}
if !group.satisfyMinimalInclusion(weights) {
return fmt.Errorf("trying to assign weights %v resulting into unability to reach minimal inclusion number %d", weights, group.MinInclusions)
}
group.wMu.Lock()
defer group.wMu.Unlock()
// All group weights initially reset to 0.0
for logURL := range group.LogURLs {
group.LogWeights[logURL] = 0.0
}
for logURL, w := range weights {
if group.LogURLs[logURL] {
group.LogWeights[logURL] = w
}
}
return nil
}
// SetLogWeight tries setting the weight for a single Log of the Log-group.
// Does not reset the weight and returns error if weight is non-positive and
// its setting will result innto unability to reach minimal inclusion number.
func (group *LogGroupInfo) SetLogWeight(logURL string, w float32) error {
if !group.LogURLs[logURL] {
return fmt.Errorf("trying to assign weight to Log %q not belonging to the group", logURL)
}
if w < 0.0 {
return fmt.Errorf("trying to assign negative weight %v to Log %q", w, logURL)
}
newWeights := make(map[string]float32)
for l, wt := range group.LogWeights {
newWeights[l] = wt
}
newWeights[logURL] = w
if !group.satisfyMinimalInclusion(newWeights) {
return fmt.Errorf("assigning weight %v to Log %q will result into unability to reach minimal inclusion number %d", w, logURL, group.MinInclusions)
}
group.wMu.Lock()
defer group.wMu.Unlock()
group.LogWeights = newWeights
return nil
}
// GetSubmissionSession produces list of log-URLs of the Log-group.
// Order of the list is weighted random defined by Log-weights within the group
func (group *LogGroupInfo) GetSubmissionSession() []string {
if len(group.LogURLs) == 0 {
return make([]string, 0)
}
session := make([]string, 0)
// modelling weighted random with exclusion
unProcessedWeights := make(map[string]float32)
for logURL, w := range group.LogWeights {
unProcessedWeights[logURL] = w
}
group.wMu.RLock()
defer group.wMu.RUnlock()
for range group.LogURLs {
sampleLog, err := weightedRandomSample(unProcessedWeights)
if err != nil {
// session still valid, not covering all Logs
return session
}
session = append(session, sampleLog)
delete(unProcessedWeights, sampleLog)
}
return session
}
// LogPolicyData contains info on log-partition and submission requirements
// for a single cert. Key always matches value Name field.
type LogPolicyData map[string]*LogGroupInfo
// TotalLogs returns number of logs within set of Log-groups.
// Taking possible intersection into account.
func (groups LogPolicyData) TotalLogs() int {
unifiedLogs := make(map[string]bool)
for _, g := range groups {
if g.IsBase {
return len(g.LogURLs)
}
for l := range g.LogURLs {
unifiedLogs[l] = true
}
}
return len(unifiedLogs)
}
// CTPolicy interface describes requirements determined for logs in terms of
// per-group-submit.
type CTPolicy interface {
// LogsByGroup provides info on Log-grouping. Returns an error if it's not
// possible to satisfy the policy with the provided loglist.
LogsByGroup(cert *x509.Certificate, approved *loglist2.LogList) (LogPolicyData, error)
Name() string
}
// BaseGroupFor creates and propagates all-log group.
func BaseGroupFor(approved *loglist2.LogList, incCount int) (*LogGroupInfo, error) {
baseGroup := LogGroupInfo{Name: BaseName, IsBase: true}
baseGroup.populate(approved, func(op *loglist2.Operator) bool { return true })
err := baseGroup.setMinInclusions(incCount)
return &baseGroup, err
}
// lifetimeInMonths calculates and returns cert lifetime expressed in months
// flooring incomplete month.
func lifetimeInMonths(cert *x509.Certificate) int {
startYear, startMonth, startDay := cert.NotBefore.Date()
endYear, endMonth, endDay := cert.NotAfter.Date()
lifetimeInMonths := (int(endYear)-int(startYear))*12 + (int(endMonth) - int(startMonth))
if endDay < startDay {
// partial month
lifetimeInMonths--
}
return lifetimeInMonths
}
// GroupSet is set of Log-group names.
type GroupSet map[string]bool
// GroupByLogs reverses match-map between Logs and Groups.
// Returns map from log-URLs to set of Group-names that contain the log.
func GroupByLogs(lg LogPolicyData) map[string]GroupSet {
result := make(map[string]GroupSet)
for groupname, g := range lg {
for logURL := range g.LogURLs {
if _, seen := result[logURL]; !seen {
result[logURL] = make(GroupSet)
}
result[logURL][groupname] = true
}
}
return result
}