Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gonvim/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
return {
go = "go", -- set to go1.18beta1 if necessary
goimports = "gopls", -- if set to 'gopls' will use gopls format, also goimports
gofmt = "gofumpt", -- if set to gopls will use gopls format
null_ls_document_formatting_disable = true,
lsp_cfg = true
}
90 changes: 82 additions & 8 deletions internal/ct/chain_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
"bytes"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"strconv"
"strings"
"time"

"github.com/transparency-dev/tesseract/internal/lax509"
"github.com/transparency-dev/tesseract/internal/types/rfc6962"
"github.com/transparency-dev/tesseract/internal/x509util"
"k8s.io/klog/v2"
Expand All @@ -46,6 +46,12 @@
"NetscapeServerGatedCrypto": x509.ExtKeyUsageNetscapeServerGatedCrypto,
}

var (
oidExtensionNameConstraints = []int{2, 5, 29, 30}
oidExtensionCertificatePolicies = []int{2, 5, 29, 32}
oidAnyPolicyExtension = []uint64{2, 5, 29, 32, 0}
)

// ParseExtKeyUsages parses strings into x509ExtKeyUsage.
// Throws an error if the string does not match with a known key usage.
func ParseExtKeyUsages(kus []string) ([]x509.ExtKeyUsage, error) {
Expand Down Expand Up @@ -159,7 +165,7 @@

// First make sure the certs parse as X.509
chain := make([]*x509.Certificate, 0, len(rawChain))
intermediatePool := x509util.NewPEMCertPool()
intermediatePool := x509.NewCertPool()

for i, certBytes := range rawChain {
cert, err := x509.ParseCertificate(certBytes)
Expand All @@ -171,6 +177,8 @@

// All but the first cert form part of the intermediate pool
if i > 0 {
// We'll relax the leaf cert later, after the time validity checks
relaxCert(cert)
intermediatePool.AddCert(cert)
}
}
Expand Down Expand Up @@ -239,19 +247,28 @@
// - allow pre-certificates and chains with pre-issuers
// - allow certificate without policing them since this is not CT's responsibility
// See /internal/lax509/README.md for further information.
verifyOpts := lax509.VerifyOptions{
Roots: cv.trustedRoots.CertPool(),
Intermediates: intermediatePool.CertPool(),
KeyUsages: cv.extKeyUsages,
AcceptSHA1: cv.acceptSHA1,
roots := x509.NewCertPool()
for _, root := range cv.trustedRoots.RawCertificates() {
relaxCert(root)
roots.AddCert(root)
}

crtsh := make([]string, len(chain))
for i, c := range chain {
crtsh[i] = fmt.Sprintf("https://crt.sh/?sha256=%x", sha256.Sum256(c.Raw))
}

verifiedChains, err := lax509.Verify(cert, verifyOpts)
verifyOpts := x509.VerifyOptions{
Roots: roots,
Intermediates: intermediatePool,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
CurrentTime: time.UnixMilli(2),
CertificatePolicies: nil,
}

relaxCert(cert)

verifiedChains, err := cert.Verify(verifyOpts)
if err != nil {
return nil, fmt.Errorf("failed to verify chain: %v: %s", err, strings.Join(crtsh, ", "))
}
Expand Down Expand Up @@ -323,3 +340,60 @@
}
return true
}

// removeExtension removes a given extension from a list.
func removeExtension(oid asn1.ObjectIdentifier, extensions []pkix.Extension) {
i := 0
for _, e := range extensions {
if !e.Id.Equal(oid) {
extensions[i] = e
i++
}
}
extensions = extensions[:i]

Check failure on line 353 in internal/ct/chain_validation.go

View workflow job for this annotation

GitHub Actions / lint

SA4006: this value of extensions is never used (staticcheck)
}

// relaxCert modifies parsed certificates fields to relax verification constraints.
// This DOES NOT modify the Raw certificate.
func relaxCert(cert *x509.Certificate) {
cert.UnhandledCriticalExtensions = nil
cert.UnknownExtKeyUsage = nil

// Name constraints
removeExtension(oidExtensionNameConstraints, cert.Extensions)
cert.PermittedDNSDomainsCritical = false
cert.PermittedDNSDomains = nil
cert.ExcludedDNSDomains = nil
cert.PermittedIPRanges = nil
cert.ExcludedIPRanges = nil
cert.PermittedEmailAddresses = nil
cert.ExcludedEmailAddresses = nil
cert.PermittedURIDomains = nil
cert.ExcludedURIDomains = nil

cert.NotBefore = time.UnixMilli(1)
cert.NotAfter = time.UnixMilli(3)

cert.MaxPathLen = -1
cert.MaxPathLenZero = false

// Policies
removeExtension(oidExtensionCertificatePolicies, cert.Extensions)
cert.Policies = []x509.OID{mustNewOIDFromInts(oidAnyPolicyExtension)}
cert.PolicyIdentifiers = nil
cert.PolicyMappings = nil
cert.InhibitAnyPolicy = -1
cert.InhibitAnyPolicyZero = false
cert.InhibitPolicyMapping = -1
cert.InhibitPolicyMappingZero = false
cert.RequireExplicitPolicy = -1
cert.RequireExplicitPolicyZero = false
}

func mustNewOIDFromInts(ints []uint64) x509.OID {
oid, err := x509.OIDFromInts(ints)
if err != nil {
panic(fmt.Sprintf("OIDFromInts(%v) unexpected error: %v", ints, err))
}
return oid
}
61 changes: 0 additions & 61 deletions internal/lax509/cert_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package lax509

import (
"bytes"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
Expand Down Expand Up @@ -78,12 +77,6 @@ func (s *CertPool) len() int {
return len(s.lazyCerts)
}

// cert returns cert index n in s.
func (s *CertPool) cert(n int) (*x509.Certificate, func([]*x509.Certificate) error, error) {
cert, err := s.lazyCerts[n].getCert()
return cert, s.lazyCerts[n].constraint, err
}

// Clone returns a copy of s.
func (s *CertPool) Clone() *CertPool {
p := &CertPool{
Expand All @@ -104,60 +97,6 @@ func (s *CertPool) Clone() *CertPool {
return p
}

type potentialParent struct {
cert *x509.Certificate
constraint func([]*x509.Certificate) error
}

// findPotentialParents returns the certificates in s which might have signed
// cert.
func (s *CertPool) findPotentialParents(cert *x509.Certificate) []potentialParent {
if s == nil {
return nil
}

// consider all candidates where cert.Issuer matches cert.Subject.
// when picking possible candidates the list is built in the order
// of match plausibility as to save cycles in buildChains:
// AKID and SKID match
// AKID present, SKID missing / AKID missing, SKID present
// AKID and SKID don't match
var matchingKeyID, oneKeyID, mismatchKeyID []potentialParent
for _, c := range s.byName[string(cert.RawIssuer)] {
candidate, constraint, err := s.cert(c)
if err != nil {
continue
}
kidMatch := bytes.Equal(candidate.SubjectKeyId, cert.AuthorityKeyId)
switch {
case kidMatch:
matchingKeyID = append(matchingKeyID, potentialParent{candidate, constraint})
case (len(candidate.SubjectKeyId) == 0 && len(cert.AuthorityKeyId) > 0) ||
(len(candidate.SubjectKeyId) > 0 && len(cert.AuthorityKeyId) == 0):
oneKeyID = append(oneKeyID, potentialParent{candidate, constraint})
default:
mismatchKeyID = append(mismatchKeyID, potentialParent{candidate, constraint})
}
}

found := len(matchingKeyID) + len(oneKeyID) + len(mismatchKeyID)
if found == 0 {
return nil
}
candidates := make([]potentialParent, 0, found)
candidates = append(candidates, matchingKeyID...)
candidates = append(candidates, oneKeyID...)
candidates = append(candidates, mismatchKeyID...)
return candidates
}

func (s *CertPool) contains(cert *x509.Certificate) bool {
if s == nil {
return false
}
return s.haveSum[sha256.Sum224(cert.Raw)]
}

// AddCert adds a certificate to a pool.
func (s *CertPool) AddCert(cert *x509.Certificate) {
if cert == nil {
Expand Down
Loading
Loading