Skip to content

Commit a58e5e1

Browse files
authored
ChainVerifier is now an interface (#259)
1 parent 2ebdf80 commit a58e5e1

File tree

8 files changed

+98
-85
lines changed

8 files changed

+98
-85
lines changed

ctlog.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ func (s systemTimeSource) Now() time.Time {
7272

7373
var sysTimeSource = systemTimeSource{}
7474

75-
// newCertValidationOpts checks that a chain validation config is valid,
75+
// newChainValidator checks that a chain validation config is valid,
7676
// parses it, and loads resources to validate chains.
77-
func newCertValidationOpts(cfg ChainValidationConfig) (*scti.ChainValidationOpts, error) {
77+
func newChainValidator(cfg ChainValidationConfig) (scti.ChainValidator, error) {
7878
// Load the trusted roots.
7979
if cfg.RootsPEMFile == "" {
8080
return nil, errors.New("empty rootsPemFile")
@@ -114,19 +114,19 @@ func newCertValidationOpts(cfg ChainValidationConfig) (*scti.ChainValidationOpts
114114
}
115115
}
116116

117-
vOpts := scti.NewChainValidationOpts(roots, cfg.RejectExpired, cfg.RejectUnexpired, cfg.NotAfterStart, cfg.NotAfterLimit, extKeyUsages, rejectExtIds)
118-
return &vOpts, nil
117+
cv := scti.NewChainValidator(roots, cfg.RejectExpired, cfg.RejectUnexpired, cfg.NotAfterStart, cfg.NotAfterLimit, extKeyUsages, rejectExtIds)
118+
return &cv, nil
119119
}
120120

121121
// NewLogHandler creates a Tessera based CT log pluged into HTTP handlers.
122122
// The HTTP server handlers implement https://c2sp.org/static-ct-api write
123123
// endpoints.
124124
func NewLogHandler(ctx context.Context, origin string, signer crypto.Signer, cfg ChainValidationConfig, cs storage.CreateStorage, httpDeadline time.Duration, maskInternalErrors bool) (http.Handler, error) {
125-
cvOpts, err := newCertValidationOpts(cfg)
125+
cv, err := newChainValidator(cfg)
126126
if err != nil {
127127
return nil, fmt.Errorf("newCertValidationOpts(): %v", err)
128128
}
129-
log, err := scti.NewLog(ctx, origin, signer, *cvOpts, cs, sysTimeSource)
129+
log, err := scti.NewLog(ctx, origin, signer, cv, cs, sysTimeSource)
130130
if err != nil {
131131
return nil, fmt.Errorf("newLog(): %v", err)
132132
}

ctlog_test.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func TestNewCertValidationOpts(t *testing.T) {
2727
for _, tc := range []struct {
2828
desc string
2929
wantErr string
30-
cvcfg ChainValidationConfig
30+
cvCfg ChainValidationConfig
3131
}{
3232
{
3333
desc: "empty-rootsPemFile",
@@ -36,103 +36,103 @@ func TestNewCertValidationOpts(t *testing.T) {
3636
{
3737
desc: "missing-root-cert",
3838
wantErr: "failed to read trusted roots",
39-
cvcfg: ChainValidationConfig{
39+
cvCfg: ChainValidationConfig{
4040
RootsPEMFile: "./internal/testdata/bogus.cert",
4141
},
4242
},
4343
{
4444
desc: "rejecting-all",
4545
wantErr: "configuration would reject all certificates",
46-
cvcfg: ChainValidationConfig{
46+
cvCfg: ChainValidationConfig{
4747
RootsPEMFile: "./internal/testdata/fake-ca.cert",
4848
RejectExpired: true,
4949
RejectUnexpired: true},
5050
},
5151
{
5252
desc: "unknown-ext-key-usage-1",
5353
wantErr: "unknown extended key usage",
54-
cvcfg: ChainValidationConfig{
54+
cvCfg: ChainValidationConfig{
5555
RootsPEMFile: "./internal/testdata/fake-ca.cert",
5656
ExtKeyUsages: "wrong_usage"},
5757
},
5858
{
5959
desc: "unknown-ext-key-usage-2",
6060
wantErr: "unknown extended key usage",
61-
cvcfg: ChainValidationConfig{
61+
cvCfg: ChainValidationConfig{
6262
RootsPEMFile: "./internal/testdata/fake-ca.cert",
6363
ExtKeyUsages: "ClientAuth,ServerAuth,TimeStomping",
6464
},
6565
},
6666
{
6767
desc: "unknown-ext-key-usage-3",
6868
wantErr: "unknown extended key usage",
69-
cvcfg: ChainValidationConfig{
69+
cvCfg: ChainValidationConfig{
7070
RootsPEMFile: "./internal/testdata/fake-ca.cert",
7171
ExtKeyUsages: "Any ",
7272
},
7373
},
7474
{
7575
desc: "unknown-reject-ext",
7676
wantErr: "failed to parse RejectExtensions",
77-
cvcfg: ChainValidationConfig{
77+
cvCfg: ChainValidationConfig{
7878
RootsPEMFile: "./internal/testdata/fake-ca.cert",
7979
RejectExtensions: "1.2.3.4,one.banana.two.bananas",
8080
},
8181
},
8282
{
8383
desc: "limit-before-start",
8484
wantErr: "before start",
85-
cvcfg: ChainValidationConfig{
85+
cvCfg: ChainValidationConfig{
8686
RootsPEMFile: "./internal/testdata/fake-ca.cert",
8787
NotAfterStart: &t200,
8888
NotAfterLimit: &t100,
8989
},
9090
},
9191
{
9292
desc: "ok",
93-
cvcfg: ChainValidationConfig{
93+
cvCfg: ChainValidationConfig{
9494
RootsPEMFile: "./internal/testdata/fake-ca.cert",
9595
},
9696
},
9797
{
9898
desc: "ok-ext-key-usages",
99-
cvcfg: ChainValidationConfig{
99+
cvCfg: ChainValidationConfig{
100100
RootsPEMFile: "./internal/testdata/fake-ca.cert",
101101
ExtKeyUsages: "ServerAuth,ClientAuth,OCSPSigning",
102102
},
103103
},
104104
{
105105
desc: "ok-reject-ext",
106-
cvcfg: ChainValidationConfig{
106+
cvCfg: ChainValidationConfig{
107107
RootsPEMFile: "./internal/testdata/fake-ca.cert",
108108
RejectExtensions: "1.2.3.4,5.6.7.8",
109109
},
110110
},
111111
{
112112
desc: "ok-start-timestamp",
113-
cvcfg: ChainValidationConfig{
113+
cvCfg: ChainValidationConfig{
114114
RootsPEMFile: "./internal/testdata/fake-ca.cert",
115115
NotAfterStart: &t100,
116116
},
117117
},
118118
{
119119
desc: "ok-limit-timestamp",
120-
cvcfg: ChainValidationConfig{
120+
cvCfg: ChainValidationConfig{
121121
RootsPEMFile: "./internal/testdata/fake-ca.cert",
122122
NotAfterStart: &t200,
123123
},
124124
},
125125
{
126126
desc: "ok-range-timestamp",
127-
cvcfg: ChainValidationConfig{
127+
cvCfg: ChainValidationConfig{
128128
RootsPEMFile: "./internal/testdata/fake-ca.cert",
129129
NotAfterStart: &t100,
130130
NotAfterLimit: &t200,
131131
},
132132
},
133133
} {
134134
t.Run(tc.desc, func(t *testing.T) {
135-
vc, err := newCertValidationOpts(tc.cvcfg)
135+
vc, err := newChainValidator(tc.cvCfg)
136136
if len(tc.wantErr) == 0 && err != nil {
137137
t.Errorf("ValidateLogConfig()=%v, want nil", err)
138138
}

internal/scti/chain_validation.go

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ func ParseOIDs(oids []string) ([]asn1.ObjectIdentifier, error) {
8585
return ret, nil
8686
}
8787

88-
// ChainValidationOpts contains various parameters for certificate chain validation
89-
type ChainValidationOpts struct {
90-
// trustedRoots is a pool of certificates that defines the roots the CT log will accept
88+
// chainValidator contains various parameters for certificate chain validation.
89+
type chainValidator struct {
90+
// trustedRoots is a pool of certificates that defines the roots the CT log will accept.
9191
trustedRoots *x509util.PEMCertPool
9292
// currentTime is the time used for checking a certificate's validity period
9393
// against. If it's zero then time.Now() is used. Only for testing.
@@ -104,14 +104,14 @@ type ChainValidationOpts struct {
104104
// dates strictly *before* notAfterLimit will be accepted.
105105
// nil means no upper bound on the accepted range.
106106
notAfterLimit *time.Time
107-
// extKeyUsages contains the list of EKUs to use during chain verification
107+
// extKeyUsages contains the list of EKUs to use during chain verification.
108108
extKeyUsages []x509.ExtKeyUsage
109109
// rejectExtIds contains a list of X.509 extension IDs to reject during chain verification.
110110
rejectExtIds []asn1.ObjectIdentifier
111111
}
112112

113-
func NewChainValidationOpts(trustedRoots *x509util.PEMCertPool, rejectExpired, rejectUnexpired bool, notAfterStart, notAfterLimit *time.Time, extKeyUsages []x509.ExtKeyUsage, rejectExtIds []asn1.ObjectIdentifier) ChainValidationOpts {
114-
return ChainValidationOpts{
113+
func NewChainValidator(trustedRoots *x509util.PEMCertPool, rejectExpired, rejectUnexpired bool, notAfterStart, notAfterLimit *time.Time, extKeyUsages []x509.ExtKeyUsage, rejectExtIds []asn1.ObjectIdentifier) chainValidator {
114+
return chainValidator{
115115
trustedRoots: trustedRoots,
116116
rejectExpired: rejectExpired,
117117
rejectUnexpired: rejectUnexpired,
@@ -143,13 +143,12 @@ func isPrecertificate(cert *x509.Certificate) (bool, error) {
143143
return false, nil
144144
}
145145

146-
// validateChain takes the certificate chain as it was parsed from a JSON request. Ensures all
146+
// validate takes the certificate chain as it was parsed from a JSON request. Ensures all
147147
// elements in the chain decode as X.509 certificates. Ensures that there is a valid path from the
148148
// end entity certificate in the chain to a trusted root cert, possibly using the intermediates
149149
// supplied in the chain. Then applies the RFC requirement that the path must involve all
150150
// the submitted chain in the order of submission.
151-
// TODO(phboneff): make this a method func([][]byte) ([]*x509.Certificate, error)
152-
func (opts ChainValidationOpts) validateChain(rawChain [][]byte) ([]*x509.Certificate, error) {
151+
func (cv chainValidator) validate(rawChain [][]byte) ([]*x509.Certificate, error) {
153152
if len(rawChain) == 0 {
154153
return nil, errors.New("empty certificate chain")
155154
}
@@ -172,8 +171,8 @@ func (opts ChainValidationOpts) validateChain(rawChain [][]byte) ([]*x509.Certif
172171
}
173172
}
174173

175-
naStart := opts.notAfterStart
176-
naLimit := opts.notAfterLimit
174+
naStart := cv.notAfterStart
175+
naLimit := cv.notAfterLimit
177176
cert := chain[0]
178177

179178
// Check whether the expiry date of the cert is within the acceptable range.
@@ -184,24 +183,24 @@ func (opts ChainValidationOpts) validateChain(rawChain [][]byte) ([]*x509.Certif
184183
return nil, fmt.Errorf("certificate NotAfter (%v) >= %v", cert.NotAfter, *naLimit)
185184
}
186185

187-
now := opts.currentTime
186+
now := cv.currentTime
188187
if now.IsZero() {
189188
now = time.Now()
190189
}
191190
expired := now.After(cert.NotAfter)
192-
if opts.rejectExpired && expired {
191+
if cv.rejectExpired && expired {
193192
return nil, errors.New("rejecting expired certificate")
194193
}
195-
if opts.rejectUnexpired && !expired {
194+
if cv.rejectUnexpired && !expired {
196195
return nil, errors.New("rejecting unexpired certificate")
197196
}
198197

199198
// Check for unwanted extension types, if required.
200199
// TODO(al): Refactor CertValidationOpts c'tor to a builder pattern and
201200
// pre-calc this in there
202-
if len(opts.rejectExtIds) != 0 {
201+
if len(cv.rejectExtIds) != 0 {
203202
badIDs := make(map[string]bool)
204-
for _, id := range opts.rejectExtIds {
203+
for _, id := range cv.rejectExtIds {
205204
badIDs[id.String()] = true
206205
}
207206
for idx, ext := range cert.Extensions {
@@ -214,9 +213,9 @@ func (opts ChainValidationOpts) validateChain(rawChain [][]byte) ([]*x509.Certif
214213

215214
// TODO(al): Refactor CertValidationOpts c'tor to a builder pattern and
216215
// pre-calc this in there too.
217-
if len(opts.extKeyUsages) > 0 {
216+
if len(cv.extKeyUsages) > 0 {
218217
acceptEKUs := make(map[x509.ExtKeyUsage]bool)
219-
for _, eku := range opts.extKeyUsages {
218+
for _, eku := range cv.extKeyUsages {
220219
acceptEKUs[eku] = true
221220
}
222221
good := false
@@ -227,7 +226,7 @@ func (opts ChainValidationOpts) validateChain(rawChain [][]byte) ([]*x509.Certif
227226
}
228227
}
229228
if !good {
230-
return nil, fmt.Errorf("rejecting certificate without EKU in %v", opts.extKeyUsages)
229+
return nil, fmt.Errorf("rejecting certificate without EKU in %v", cv.extKeyUsages)
231230
}
232231
}
233232

@@ -237,9 +236,9 @@ func (opts ChainValidationOpts) validateChain(rawChain [][]byte) ([]*x509.Certif
237236
// - allow certificate without policing them since this is not CT's responsibility
238237
// See /internal/lax509/README.md for further information.
239238
verifyOpts := lax509.VerifyOptions{
240-
Roots: opts.trustedRoots.CertPool(),
239+
Roots: cv.trustedRoots.CertPool(),
241240
Intermediates: intermediatePool.CertPool(),
242-
KeyUsages: opts.extKeyUsages,
241+
KeyUsages: cv.extKeyUsages,
243242
}
244243

245244
verifiedChains, err := lax509.Verify(cert, verifyOpts)
@@ -263,16 +262,18 @@ func (opts ChainValidationOpts) validateChain(rawChain [][]byte) ([]*x509.Certif
263262
return nil, errors.New("no RFC compliant path to root found when trying to validate chain")
264263
}
265264

266-
// verifyAddChain is used by add-chain and add-pre-chain. It does the checks that the supplied
267-
// cert is of the correct type and chains to a trusted root.
265+
// Validate is used by add-chain and add-pre-chain. It checks that the supplied
266+
// cert is of the correct type, chains to a trusted root and satisties time
267+
// constraints.
268268
// TODO(phbnf): add tests
269-
func (opts ChainValidationOpts) verifyAddChain(req rfc6962.AddChainRequest, expectingPrecert bool) ([]*x509.Certificate, error) {
270-
// We already checked that the chain is not empty so can move on to verification
271-
validPath, err := opts.validateChain(req.Chain)
269+
// TODO(phbnf): merge with validate
270+
func (cv chainValidator) Validate(req rfc6962.AddChainRequest, expectingPrecert bool) ([]*x509.Certificate, error) {
271+
// We already checked that the chain is not empty so can move on to validation.
272+
validPath, err := cv.validate(req.Chain)
272273
if err != nil {
273274
// We rejected it because the cert failed checks or we could not find a path to a root etc.
274275
// Lots of possible causes for errors
275-
return nil, fmt.Errorf("chain failed to verify: %s", err)
276+
return nil, fmt.Errorf("chain failed to validate: %s", err)
276277
}
277278

278279
isPrecert, err := isPrecertificate(validPath[0])
@@ -293,6 +294,10 @@ func (opts ChainValidationOpts) verifyAddChain(req rfc6962.AddChainRequest, expe
293294
return validPath, nil
294295
}
295296

297+
func (cv chainValidator) Roots() []*x509.Certificate {
298+
return cv.trustedRoots.RawCertificates()
299+
}
300+
296301
func chainsEquivalent(inChain []*x509.Certificate, verifiedChain []*x509.Certificate) bool {
297302
// The verified chain includes a root, but the input chain may or may not include a
298303
// root (RFC 6962 s4.1/ s4.2 "the last [certificate] is either the root certificate

0 commit comments

Comments
 (0)