-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Port certificate-transparency-go's Trillian CTFE to Trillian Tessera (#…
…105) * Copy CTFE from certificate-transparency-go * Port CTFE to Tessera * refactor MerkleLeafHash * address comments * simplify configs methods * unexport a few methods, and add comments * edit logorigin comment * drop support for non ECDSA public keys * fix copyright * create newctstorage more beautifuler * s/ct-static-api/sctfe
- Loading branch information
Showing
27 changed files
with
4,023 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
// Copyright 2016 Google LLC. 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 sctfe | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/google/certificate-transparency-go/asn1" | ||
"github.com/google/certificate-transparency-go/x509" | ||
"github.com/google/certificate-transparency-go/x509util" | ||
) | ||
|
||
var ( | ||
ErrNoRFCCompliantPathFound = errors.New("no RFC compliant path to root found when trying to validate chain") | ||
) | ||
|
||
// isPrecertificate tests if a certificate is a pre-certificate as defined in CT. | ||
// An error is returned if the CT extension is present but is not ASN.1 NULL as defined | ||
// by the spec. | ||
func isPrecertificate(cert *x509.Certificate) (bool, error) { | ||
for _, ext := range cert.Extensions { | ||
if x509.OIDExtensionCTPoison.Equal(ext.Id) { | ||
if !ext.Critical || !bytes.Equal(asn1.NullBytes, ext.Value) { | ||
return false, fmt.Errorf("CT poison ext is not critical or invalid: %v", ext) | ||
} | ||
|
||
return true, nil | ||
} | ||
} | ||
|
||
return false, nil | ||
} | ||
|
||
// validateChain takes the certificate chain as it was parsed from a JSON request. Ensures all | ||
// elements in the chain decode as X.509 certificates. Ensures that there is a valid path from the | ||
// end entity certificate in the chain to a trusted root cert, possibly using the intermediates | ||
// supplied in the chain. Then applies the RFC requirement that the path must involve all | ||
// the submitted chain in the order of submission. | ||
func validateChain(rawChain [][]byte, validationOpts CertValidationOpts) ([]*x509.Certificate, error) { | ||
// First make sure the certs parse as X.509 | ||
chain := make([]*x509.Certificate, 0, len(rawChain)) | ||
intermediatePool := x509util.NewPEMCertPool() | ||
|
||
for i, certBytes := range rawChain { | ||
cert, err := x509.ParseCertificate(certBytes) | ||
if x509.IsFatal(err) { | ||
return nil, err | ||
} | ||
|
||
chain = append(chain, cert) | ||
|
||
// All but the first cert form part of the intermediate pool | ||
if i > 0 { | ||
intermediatePool.AddCert(cert) | ||
} | ||
} | ||
|
||
naStart := validationOpts.notAfterStart | ||
naLimit := validationOpts.notAfterLimit | ||
cert := chain[0] | ||
|
||
// Check whether the expiry date of the cert is within the acceptable range. | ||
if naStart != nil && cert.NotAfter.Before(*naStart) { | ||
return nil, fmt.Errorf("certificate NotAfter (%v) < %v", cert.NotAfter, *naStart) | ||
} | ||
if naLimit != nil && !cert.NotAfter.Before(*naLimit) { | ||
return nil, fmt.Errorf("certificate NotAfter (%v) >= %v", cert.NotAfter, *naLimit) | ||
} | ||
|
||
if validationOpts.acceptOnlyCA && !cert.IsCA { | ||
return nil, errors.New("only certificates with CA bit set are accepted") | ||
} | ||
|
||
now := validationOpts.currentTime | ||
if now.IsZero() { | ||
now = time.Now() | ||
} | ||
expired := now.After(cert.NotAfter) | ||
if validationOpts.rejectExpired && expired { | ||
return nil, errors.New("rejecting expired certificate") | ||
} | ||
if validationOpts.rejectUnexpired && !expired { | ||
return nil, errors.New("rejecting unexpired certificate") | ||
} | ||
|
||
// Check for unwanted extension types, if required. | ||
// TODO(al): Refactor CertValidationOpts c'tor to a builder pattern and | ||
// pre-calc this in there | ||
if len(validationOpts.rejectExtIds) != 0 { | ||
badIDs := make(map[string]bool) | ||
for _, id := range validationOpts.rejectExtIds { | ||
badIDs[id.String()] = true | ||
} | ||
for idx, ext := range cert.Extensions { | ||
extOid := ext.Id.String() | ||
if _, ok := badIDs[extOid]; ok { | ||
return nil, fmt.Errorf("rejecting certificate containing extension %v at index %d", extOid, idx) | ||
} | ||
} | ||
} | ||
|
||
// TODO(al): Refactor CertValidationOpts c'tor to a builder pattern and | ||
// pre-calc this in there too. | ||
if len(validationOpts.extKeyUsages) > 0 { | ||
acceptEKUs := make(map[x509.ExtKeyUsage]bool) | ||
for _, eku := range validationOpts.extKeyUsages { | ||
acceptEKUs[eku] = true | ||
} | ||
good := false | ||
for _, certEKU := range cert.ExtKeyUsage { | ||
if _, ok := acceptEKUs[certEKU]; ok { | ||
good = true | ||
break | ||
} | ||
} | ||
if !good { | ||
return nil, fmt.Errorf("rejecting certificate without EKU in %v", validationOpts.extKeyUsages) | ||
} | ||
} | ||
|
||
// We can now do the verification. Use fairly lax options for verification, as | ||
// CT is intended to observe certificates rather than police them. | ||
verifyOpts := x509.VerifyOptions{ | ||
Roots: validationOpts.trustedRoots.CertPool(), | ||
CurrentTime: now, | ||
Intermediates: intermediatePool.CertPool(), | ||
DisableTimeChecks: true, | ||
// Precertificates have the poison extension; also the Go library code does not | ||
// support the standard PolicyConstraints extension (which is required to be marked | ||
// critical, RFC 5280 s4.2.1.11), so never check unhandled critical extensions. | ||
DisableCriticalExtensionChecks: true, | ||
// Pre-issued precertificates have the Certificate Transparency EKU; also some | ||
// leaves have unknown EKUs that should not be bounced just because the intermediate | ||
// does not also have them (cf. https://github.com/golang/go/issues/24590) so | ||
// disable EKU checks inside the x509 library, but we've already done our own check | ||
// on the leaf above. | ||
DisableEKUChecks: true, | ||
// Path length checks get confused by the presence of an additional | ||
// pre-issuer intermediate, so disable them. | ||
DisablePathLenChecks: true, | ||
DisableNameConstraintChecks: true, | ||
DisableNameChecks: false, | ||
KeyUsages: validationOpts.extKeyUsages, | ||
} | ||
|
||
verifiedChains, err := cert.Verify(verifyOpts) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if len(verifiedChains) == 0 { | ||
return nil, errors.New("no path to root found when trying to validate chains") | ||
} | ||
|
||
// Verify might have found multiple paths to roots. Now we check that we have a path that | ||
// uses all the certs in the order they were submitted so as to comply with RFC 6962 | ||
// requirements detailed in Section 3.1. | ||
for _, verifiedChain := range verifiedChains { | ||
if chainsEquivalent(chain, verifiedChain) { | ||
return verifiedChain, nil | ||
} | ||
} | ||
|
||
return nil, ErrNoRFCCompliantPathFound | ||
} | ||
|
||
func chainsEquivalent(inChain []*x509.Certificate, verifiedChain []*x509.Certificate) bool { | ||
// The verified chain includes a root, but the input chain may or may not include a | ||
// root (RFC 6962 s4.1/ s4.2 "the last [certificate] is either the root certificate | ||
// or a certificate that chains to a known root certificate"). | ||
if len(inChain) != len(verifiedChain) && len(inChain) != (len(verifiedChain)-1) { | ||
return false | ||
} | ||
|
||
for i, certInChain := range inChain { | ||
if !certInChain.Equal(verifiedChain[i]) { | ||
return false | ||
} | ||
} | ||
return true | ||
} |
Oops, something went wrong.