github.com/zmap/zcrypto@v0.0.0-20240512203510-0fef58d9a9db/verifier/verifier.go (about)

     1  /*
     2   * ZCrypto Copyright 2017 Regents of the University of Michigan
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License"); you may not
     5   * use this file except in compliance with the License. You may obtain a copy
     6   * of the License at http://www.apache.org/licenses/LICENSE-2.0
     7   *
     8   * Unless required by applicable law or agreed to in writing, software
     9   * distributed under the License is distributed on an "AS IS" BASIS,
    10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    11   * implied. See the License for the specific language governing
    12   * permissions and limitations under the License.
    13   */
    14  
    15  package verifier
    16  
    17  import (
    18  	"context"
    19  	"encoding/hex"
    20  	"time"
    21  
    22  	"github.com/zmap/zcrypto/x509"
    23  	"github.com/zmap/zcrypto/x509/pkix"
    24  	"github.com/zmap/zcrypto/x509/revocation/crl"
    25  	"github.com/zmap/zcrypto/x509/revocation/google"
    26  	"github.com/zmap/zcrypto/x509/revocation/mozilla"
    27  )
    28  
    29  // VerificationResult contains the result of a verification of a certificate
    30  // against a store and its associated policies. It discerns between NameError,
    31  // meaning the name on the certificate does not match, and a ValidationError,
    32  // meaning some sort of issue with the cryptography, or other issues with the
    33  // chain.
    34  type VerificationResult struct {
    35  
    36  	// The name being checked, e.g. "www.censys.io".
    37  	Name string
    38  
    39  	// Whitelisted is true if the certificate is whitelisted by a given verifier
    40  	// as trusted, e.g. when Apple whitelisted subset of StartCom certs by SPKI
    41  	// hash in the wake of the WoSign incidents surrounding misissuance.
    42  	Whitelisted bool
    43  
    44  	// Blacklisted is true if the certificate is blacklisted by a given verifier
    45  	// as untrusted, e.g. Cloudflare certificates valid at the time of Heartbleed
    46  	// disclosure in Chrome.
    47  	Blacklisted bool
    48  
    49  	// InRevocationSet is true if the certificate has been revoked and is listed
    50  	// in the revocation set that is part of the Verifier (e.g. in OneCRL).
    51  	InRevocationSet bool
    52  
    53  	// OCSPRevoked is true if the certificate has been revoked through OCSP,
    54  	// which is only checked if VerificationOptions.ShouldCheckOCSP flag is set
    55  	OCSPRevoked bool
    56  
    57  	// OCSPRevocationInfo provides revocation info when OCSPRevoked is true
    58  	OCSPRevocationInfo *RevocationInfo
    59  
    60  	// CRLRevoked is true if the certificate has been revoked through CRL,
    61  	// which is only checked if VerificationOptions.ShouldCheckCRL flag is set
    62  	CRLRevoked bool
    63  
    64  	// CRLRevocationInfo provides revocation info when CRLRevoked is true
    65  	CRLRevocationInfo *RevocationInfo
    66  
    67  	// OCSPCheckError will be non-nil when there was some sort of error from OCSP check
    68  	OCSPCheckError error
    69  
    70  	// CRLCheckError will be non-nil when there was some sort of error from CRL check
    71  	CRLCheckError error
    72  
    73  	// ValiditionError will be non-nil when there was some sort of error during
    74  	// validation not involving a name mismatch, e.g. if a chain could not be
    75  	// built.
    76  	ValidationError error
    77  
    78  	// NameError will be non-nil when there was a mismatch between the name on the
    79  	// certificate and the name being verified against.
    80  	NameError error
    81  
    82  	// Parents is a set of currently valid certificates that are immediate parents
    83  	// of the certificate being verified, and are part of a chain that is valid at
    84  	// the time the certificate being verified expires.
    85  	Parents []*x509.Certificate
    86  
    87  	// CurrentChains is a list of validated certificate chains that are valid at
    88  	// ValidationTime, starting at the certificate being verified, and ending at a
    89  	// certificate in the root store.
    90  	CurrentChains []x509.CertificateChain
    91  
    92  	// ExpiredChains is a list of certificate chains that were valid at some
    93  	// point, but not at ValidationTime.
    94  	ExpiredChains []x509.CertificateChain
    95  
    96  	// NeverValidChains is a list of certificate chains that could never be valid
    97  	// due to date-related issues, but are otherwise valid.
    98  	NeverValidChains []x509.CertificateChain
    99  
   100  	// ValidAtExpirationChains is a list of certificate chains that were valid at
   101  	// the time of expiration of the certificate being validated.
   102  	ValidAtExpirationChains []x509.CertificateChain
   103  
   104  	// CertificateType is one of Leaf, Intermediate, or Root.
   105  	CertificateType x509.CertificateType
   106  
   107  	// VerifyTime is time used in verification, set in the VerificationOptions.
   108  	VerifyTime time.Time
   109  
   110  	// Expired is false if NotBefore < VerifyTime < NotAfter
   111  	Expired bool
   112  
   113  	// ParentSPKISubjectFingerprint is the SHA256 of the (SPKI, Subject) for
   114  	// parents of this certificate.
   115  	ParentSPKISubjectFingerprint x509.CertificateFingerprint
   116  
   117  	// ParentSPKI is the raw bytes of the subject public key info of the parent. It
   118  	// is the SPKI used as part of the ParentSPKISubjectFingerprint.
   119  	ParentSPKI []byte
   120  }
   121  
   122  // MatchesDomain returns true if NameError == nil and Name != "".
   123  func (res *VerificationResult) MatchesDomain() bool {
   124  	return res.NameError == nil && res.Name != ""
   125  }
   126  
   127  // HasTrustedChain returns true if len(current) > 0
   128  func (res *VerificationResult) HasTrustedChain() bool {
   129  	return len(res.CurrentChains) > 0
   130  }
   131  
   132  // HadTrustedChain returns true if at some point in time, the certificate had a
   133  // chain to a trusted root in this store.
   134  //
   135  // This is equivalent to checking if len(Current) > 0 || len(ValidAtExpired) > 0
   136  func (res *VerificationResult) HadTrustedChain() bool {
   137  	return res.HasTrustedChain() || len(res.ValidAtExpirationChains) > 0
   138  }
   139  
   140  // VerifyProcedure is an interface to implement additional browser specific logic at
   141  // the start and end of verification.
   142  type VerifyProcedure interface {
   143  	// TODO
   144  }
   145  
   146  // RevocationInfo provides basic revocation information
   147  type RevocationInfo struct {
   148  	NextUpdate     time.Time
   149  	RevocationTime *time.Time
   150  	Reason         crl.RevocationReasonCode
   151  }
   152  
   153  // RevocationProvider is an interface to implement revocation status provider
   154  type RevocationProvider interface {
   155  	// CheckOCSP - check the ocsp status of a provided certificate
   156  	CheckOCSP(ctx context.Context, c *x509.Certificate, issuer *x509.Certificate) (isRevoked bool, info *RevocationInfo, e error)
   157  	// CheckCRL - check whether the provided certificate has been revoked through
   158  	// a CRL. If no certList is provided, function will attempt to fetch it.
   159  	CheckCRL(ctx context.Context, c *x509.Certificate, certList *pkix.CertificateList) (isRevoked bool, info *RevocationInfo, err error)
   160  }
   161  
   162  // VerificationOptions contains settings for Verifier.Verify().
   163  // VerificationOptions should be safely copyable.
   164  type VerificationOptions struct {
   165  	VerifyTime         time.Time
   166  	Name               string
   167  	PresentedChain     *Graph // XXX: Unused
   168  	ShouldCheckOCSP    bool
   169  	ShouldCheckCRL     bool
   170  	RevocationProvider RevocationProvider
   171  	CRLSet             *google.CRLSet
   172  	OneCRL             *mozilla.OneCRL
   173  }
   174  
   175  func (opt *VerificationOptions) clean() {
   176  	if opt.VerifyTime.IsZero() {
   177  		opt.VerifyTime = time.Now()
   178  	}
   179  }
   180  
   181  // A Verifier represents a context for verifying certificates.
   182  type Verifier struct {
   183  	PKI             *Graph
   184  	VerifyProcedure VerifyProcedure
   185  }
   186  
   187  // NewVerifier returns and initializes a new Verifier given a PKI graph and set
   188  // of verification procedures.
   189  func NewVerifier(pki *Graph, verifyProc VerifyProcedure) *Verifier {
   190  	out := new(Verifier)
   191  	out.PKI = pki
   192  	out.VerifyProcedure = verifyProc
   193  	return out
   194  }
   195  
   196  func parentsFromChains(chains []x509.CertificateChain) (parents []*x509.Certificate) {
   197  	// parentSet is a map from FingerprintSHA256 to the index of the chain the
   198  	// parent was in. We use this to deduplicate parents.
   199  	parentSet := make(map[string]int)
   200  	for chainIdx, chain := range chains {
   201  		if len(chain) < 2 {
   202  			continue
   203  		}
   204  		parent := chain[1]
   205  		// We can overwrite safely. If a parent is in multiple chains, we don't
   206  		// actually care which chain we pull the parent from.
   207  		parentSet[string(parent.FingerprintSHA256)] = chainIdx
   208  	}
   209  	// Convert the map to a slice.
   210  	for _, chainIdx := range parentSet {
   211  		// The parents are always at index 1 in the chain.
   212  		parents = append(parents, chains[chainIdx][1])
   213  	}
   214  	return
   215  }
   216  
   217  // Verify checks if c chains back to a certificate in Roots, possibly via a
   218  // certificate in Intermediates, and returns all such chains. It additional
   219  // checks if the Name in the VerificationOptions matches the name on the
   220  // certificate.
   221  func (v *Verifier) Verify(c *x509.Certificate, opts VerificationOptions) (res *VerificationResult) {
   222  	return v.VerifyWithContext(context.Background(), c, opts)
   223  }
   224  
   225  // VerifyWithContext checks if c chains back to a certificate in Roots, possibly via a
   226  // certificate in Intermediates, and returns all such chains. It additional
   227  // checks if the Name in the VerificationOptions matches the name on the
   228  // certificate.
   229  func (v *Verifier) VerifyWithContext(ctx context.Context, c *x509.Certificate, opts VerificationOptions) (res *VerificationResult) {
   230  	opts.clean()
   231  
   232  	res = new(VerificationResult)
   233  	res.Name = opts.Name
   234  	res.Expired = !c.TimeInValidityPeriod(opts.VerifyTime)
   235  
   236  	// Build chains back to the roots.
   237  	graphChains := v.PKI.WalkChains(c)
   238  	res.CurrentChains, res.ExpiredChains, res.NeverValidChains = x509.FilterByDate(graphChains, opts.VerifyTime)
   239  
   240  	// If we have a DNSName, verify the leaf certificate matches.
   241  	if len(opts.Name) > 0 {
   242  		res.NameError = c.VerifyHostname(opts.Name)
   243  	}
   244  
   245  	var allChains []x509.CertificateChain
   246  	allChains = append(allChains, res.CurrentChains...)
   247  	allChains = append(allChains, res.ExpiredChains...)
   248  	allChains = append(allChains, res.NeverValidChains...)
   249  
   250  	expirationTime := c.NotAfter.Add(-time.Second)
   251  	res.ValidAtExpirationChains, _, _ = x509.FilterByDate(allChains, expirationTime)
   252  
   253  	// Calculate the parents at the time of expiration for expired certs.
   254  	if res.Expired {
   255  		res.Parents = parentsFromChains(res.ValidAtExpirationChains)
   256  	} else {
   257  		res.Parents = parentsFromChains(res.CurrentChains)
   258  	}
   259  
   260  	if opts.OneCRL != nil && opts.OneCRL.Check(c) != nil {
   261  		res.InRevocationSet = true
   262  	}
   263  	if !res.InRevocationSet && opts.CRLSet != nil {
   264  		for _, parent := range res.Parents {
   265  			if opts.CRLSet.Check(c, hex.EncodeToString(parent.SPKIFingerprint)) != nil {
   266  				res.InRevocationSet = true
   267  				break
   268  			}
   269  		}
   270  	}
   271  
   272  	rp := opts.RevocationProvider
   273  	if rp == nil {
   274  		rp = defaultRevocation{}
   275  	}
   276  
   277  	if opts.ShouldCheckOCSP && len(c.OCSPServer) > 0 {
   278  		var issuer *x509.Certificate
   279  		if res.Parents != nil {
   280  			issuer = res.Parents[0] // only need issuer SPKI, so any parent will do
   281  		} else {
   282  			issuer = nil
   283  		}
   284  		res.OCSPRevoked, res.OCSPRevocationInfo, res.OCSPCheckError = rp.CheckOCSP(ctx, c, issuer)
   285  	}
   286  
   287  	if opts.ShouldCheckCRL && len(c.CRLDistributionPoints) > 0 {
   288  		res.CRLRevoked, res.CRLRevocationInfo, res.CRLCheckError = rp.CheckCRL(ctx, c, nil)
   289  	}
   290  
   291  	// Determine certificate type.
   292  	if v.PKI.IsRoot(c) {
   293  		// A certificate is only a root if it's in the root store.
   294  		res.CertificateType = x509.CertificateTypeRoot
   295  	} else if c.IsCA && len(res.Parents) > 0 {
   296  		// We define an intermediate as any certificate that is not a root, but has
   297  		// IsCA = true and at least one parent.
   298  		res.CertificateType = x509.CertificateTypeIntermediate
   299  	} else if len(res.Parents) > 0 {
   300  		// If a certificate is not a root or an intermediate, but has a parent,
   301  		// we'll call it a leaf.
   302  		res.CertificateType = x509.CertificateTypeLeaf
   303  	} else {
   304  		// Default to Unknown
   305  		res.CertificateType = x509.CertificateTypeUnknown
   306  	}
   307  
   308  	// Set the ParentSPKISubjectFingerprint and ParentSPKI
   309  	if len(res.Parents) > 0 {
   310  		// All parents should have the same (SPKI, Subject) fingerprint. If not,
   311  		// there's a bug.
   312  		fp := res.Parents[0].SPKISubjectFingerprint
   313  		res.ParentSPKISubjectFingerprint = make([]byte, len(fp))
   314  		copy(res.ParentSPKISubjectFingerprint, fp)
   315  		parentSPKI := res.Parents[0].RawSubjectPublicKeyInfo
   316  		res.ParentSPKI = make([]byte, len(parentSPKI))
   317  		copy(res.ParentSPKI, parentSPKI)
   318  	}
   319  
   320  	return
   321  }
   322  
   323  type defaultRevocation struct{}
   324  
   325  // CheckOCSP - check the ocsp status of a provided certificate
   326  func (defaultRevocation) CheckOCSP(ctx context.Context, c *x509.Certificate, issuer *x509.Certificate) (isRevoked bool, info *RevocationInfo, e error) {
   327  	return CheckOCSP(ctx, c, issuer)
   328  }
   329  
   330  // CheckCRL - check whether the provided certificate has been revoked through
   331  // a CRL. If no certList is provided, function will attempt to fetch it.
   332  func (defaultRevocation) CheckCRL(ctx context.Context, c *x509.Certificate, certList *pkix.CertificateList) (isRevoked bool, info *RevocationInfo, err error) {
   333  	return CheckCRL(ctx, c, certList)
   334  }