Skip to content

Commit 7bba8ca

Browse files
committed
go mod init + cp from other repo + go mod tidy
1 parent 4e7ed13 commit 7bba8ca

38 files changed

+7469
-0
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# SCTFE
2+
3+
This personality implements https://c2sp.org/static-ct-api using
4+
Trillian Tessera to store data. It is based on [Trillian's CTFE](https://github.com/google/certificate-transparency-go/tree/master/trillian/ctfe).
5+
6+
It is under active development, tracked under [Issue#88](https://github.com/transparency-dev/trillian-tessera/issues/88).
7+
8+
## Deployment
9+
Each Tessera storage backend needs its own SCTFE binary.
10+
11+
At the moment, these storage backends are supported:
12+
13+
- [GCP](./ct_server_gcp)
14+
15+
16+
TODO(phbnf): add deployment instructions
17+
18+
19+
## Working on the Code
20+
The following files are auto-generated:
21+
- [`config.pb.go`](./configpb/config.pb.go): SCTFE's config
22+
- [`mock_ct_storage.go`](./mockstorage/mock_ct_storage.go): a mock CT storage implementation for tests
23+
24+
To re-generate these files, first install the right tools:
25+
- [protobuf compiler and go gen](https://protobuf.dev/getting-started/gotutorial/#compiling-protocol-buffers). The protos in this repo have been built with protoc v27.3.
26+
- [mockgen](https://github.com/golang/mock?tab=readme-ov-file#installation)
27+
28+
Then, generate the files:
29+
```bash
30+
cd $(go list -f '{{ .Dir }}' github.com/transparency-dev/trillian-tessera/personalities/sctfe); \
31+
go generate -x ./... # hunts for //go:generate comments and runs them
32+
```
33+
34+
TODO(phboneff): provide docker template to build everything

cert_checker.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
// Copyright 2016 Google LLC. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package sctfe
16+
17+
import (
18+
"bytes"
19+
"errors"
20+
"fmt"
21+
"time"
22+
23+
"github.com/google/certificate-transparency-go/asn1"
24+
"github.com/google/certificate-transparency-go/x509"
25+
"github.com/google/certificate-transparency-go/x509util"
26+
)
27+
28+
var (
29+
ErrNoRFCCompliantPathFound = errors.New("no RFC compliant path to root found when trying to validate chain")
30+
)
31+
32+
// isPrecertificate tests if a certificate is a pre-certificate as defined in CT.
33+
// An error is returned if the CT extension is present but is not ASN.1 NULL as defined
34+
// by the spec.
35+
func isPrecertificate(cert *x509.Certificate) (bool, error) {
36+
for _, ext := range cert.Extensions {
37+
if x509.OIDExtensionCTPoison.Equal(ext.Id) {
38+
if !ext.Critical || !bytes.Equal(asn1.NullBytes, ext.Value) {
39+
return false, fmt.Errorf("CT poison ext is not critical or invalid: %v", ext)
40+
}
41+
42+
return true, nil
43+
}
44+
}
45+
46+
return false, nil
47+
}
48+
49+
// validateChain takes the certificate chain as it was parsed from a JSON request. Ensures all
50+
// elements in the chain decode as X.509 certificates. Ensures that there is a valid path from the
51+
// end entity certificate in the chain to a trusted root cert, possibly using the intermediates
52+
// supplied in the chain. Then applies the RFC requirement that the path must involve all
53+
// the submitted chain in the order of submission.
54+
func validateChain(rawChain [][]byte, validationOpts CertValidationOpts) ([]*x509.Certificate, error) {
55+
// First make sure the certs parse as X.509
56+
chain := make([]*x509.Certificate, 0, len(rawChain))
57+
intermediatePool := x509util.NewPEMCertPool()
58+
59+
for i, certBytes := range rawChain {
60+
cert, err := x509.ParseCertificate(certBytes)
61+
if x509.IsFatal(err) {
62+
return nil, err
63+
}
64+
65+
chain = append(chain, cert)
66+
67+
// All but the first cert form part of the intermediate pool
68+
if i > 0 {
69+
intermediatePool.AddCert(cert)
70+
}
71+
}
72+
73+
naStart := validationOpts.notAfterStart
74+
naLimit := validationOpts.notAfterLimit
75+
cert := chain[0]
76+
77+
// Check whether the expiry date of the cert is within the acceptable range.
78+
if naStart != nil && cert.NotAfter.Before(*naStart) {
79+
return nil, fmt.Errorf("certificate NotAfter (%v) < %v", cert.NotAfter, *naStart)
80+
}
81+
if naLimit != nil && !cert.NotAfter.Before(*naLimit) {
82+
return nil, fmt.Errorf("certificate NotAfter (%v) >= %v", cert.NotAfter, *naLimit)
83+
}
84+
85+
now := validationOpts.currentTime
86+
if now.IsZero() {
87+
now = time.Now()
88+
}
89+
expired := now.After(cert.NotAfter)
90+
if validationOpts.rejectExpired && expired {
91+
return nil, errors.New("rejecting expired certificate")
92+
}
93+
if validationOpts.rejectUnexpired && !expired {
94+
return nil, errors.New("rejecting unexpired certificate")
95+
}
96+
97+
// Check for unwanted extension types, if required.
98+
// TODO(al): Refactor CertValidationOpts c'tor to a builder pattern and
99+
// pre-calc this in there
100+
if len(validationOpts.rejectExtIds) != 0 {
101+
badIDs := make(map[string]bool)
102+
for _, id := range validationOpts.rejectExtIds {
103+
badIDs[id.String()] = true
104+
}
105+
for idx, ext := range cert.Extensions {
106+
extOid := ext.Id.String()
107+
if _, ok := badIDs[extOid]; ok {
108+
return nil, fmt.Errorf("rejecting certificate containing extension %v at index %d", extOid, idx)
109+
}
110+
}
111+
}
112+
113+
// TODO(al): Refactor CertValidationOpts c'tor to a builder pattern and
114+
// pre-calc this in there too.
115+
if len(validationOpts.extKeyUsages) > 0 {
116+
acceptEKUs := make(map[x509.ExtKeyUsage]bool)
117+
for _, eku := range validationOpts.extKeyUsages {
118+
acceptEKUs[eku] = true
119+
}
120+
good := false
121+
for _, certEKU := range cert.ExtKeyUsage {
122+
if _, ok := acceptEKUs[certEKU]; ok {
123+
good = true
124+
break
125+
}
126+
}
127+
if !good {
128+
return nil, fmt.Errorf("rejecting certificate without EKU in %v", validationOpts.extKeyUsages)
129+
}
130+
}
131+
132+
// We can now do the verification. Use fairly lax options for verification, as
133+
// CT is intended to observe certificates rather than police them.
134+
verifyOpts := x509.VerifyOptions{
135+
Roots: validationOpts.trustedRoots.CertPool(),
136+
CurrentTime: now,
137+
Intermediates: intermediatePool.CertPool(),
138+
DisableTimeChecks: true,
139+
// Precertificates have the poison extension; also the Go library code does not
140+
// support the standard PolicyConstraints extension (which is required to be marked
141+
// critical, RFC 5280 s4.2.1.11), so never check unhandled critical extensions.
142+
DisableCriticalExtensionChecks: true,
143+
// Pre-issued precertificates have the Certificate Transparency EKU; also some
144+
// leaves have unknown EKUs that should not be bounced just because the intermediate
145+
// does not also have them (cf. https://github.com/golang/go/issues/24590) so
146+
// disable EKU checks inside the x509 library, but we've already done our own check
147+
// on the leaf above.
148+
DisableEKUChecks: true,
149+
// Path length checks get confused by the presence of an additional
150+
// pre-issuer intermediate, so disable them.
151+
DisablePathLenChecks: true,
152+
DisableNameConstraintChecks: true,
153+
DisableNameChecks: false,
154+
KeyUsages: validationOpts.extKeyUsages,
155+
}
156+
157+
verifiedChains, err := cert.Verify(verifyOpts)
158+
if err != nil {
159+
return nil, err
160+
}
161+
162+
if len(verifiedChains) == 0 {
163+
return nil, errors.New("no path to root found when trying to validate chains")
164+
}
165+
166+
// Verify might have found multiple paths to roots. Now we check that we have a path that
167+
// uses all the certs in the order they were submitted so as to comply with RFC 6962
168+
// requirements detailed in Section 3.1.
169+
for _, verifiedChain := range verifiedChains {
170+
if chainsEquivalent(chain, verifiedChain) {
171+
return verifiedChain, nil
172+
}
173+
}
174+
175+
return nil, ErrNoRFCCompliantPathFound
176+
}
177+
178+
func chainsEquivalent(inChain []*x509.Certificate, verifiedChain []*x509.Certificate) bool {
179+
// The verified chain includes a root, but the input chain may or may not include a
180+
// root (RFC 6962 s4.1/ s4.2 "the last [certificate] is either the root certificate
181+
// or a certificate that chains to a known root certificate").
182+
if len(inChain) != len(verifiedChain) && len(inChain) != (len(verifiedChain)-1) {
183+
return false
184+
}
185+
186+
for i, certInChain := range inChain {
187+
if !certInChain.Equal(verifiedChain[i]) {
188+
return false
189+
}
190+
}
191+
return true
192+
}

0 commit comments

Comments
 (0)