github.com/zmap/zcrypto@v0.0.0-20240512203510-0fef58d9a9db/verifier/walk.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 "github.com/zmap/zcrypto/x509"
    18  
    19  const maxIntermediateCount = 9
    20  
    21  // WalkOptions contains options for the Graph.Walk* functions. It's a structure
    22  // since anything related to verification inevitably results in a large number
    23  // of arguments.
    24  type WalkOptions struct {
    25  	ChannelSize int
    26  }
    27  
    28  // WalkChainsAsync performs a depth-first walk of g, starting at c, to any root
    29  // edges. It returns all non-looping paths from c to a root. WalkChainsAsync
    30  // immediately returns a channel. It sends any chains it finds through the
    31  // channel, and closes it once all paths have been found. If the channel does
    32  // not get consumed, this function may block indefinitely.
    33  func (g *Graph) WalkChainsAsync(c *x509.Certificate, opt WalkOptions) chan x509.CertificateChain {
    34  	if opt.ChannelSize <= 0 {
    35  		opt.ChannelSize = 4
    36  	}
    37  	out := make(chan x509.CertificateChain, opt.ChannelSize)
    38  	start := g.FindEdge(c.FingerprintSHA256)
    39  	if start == nil {
    40  		start = new(GraphEdge)
    41  		start.Certificate = c
    42  		parentCandidates := g.nodesBySubject[string(c.RawIssuer)]
    43  		for _, candidate := range parentCandidates {
    44  			identity := candidate.SubjectAndKey
    45  			if err := x509.CheckSignatureFromKey(identity.PublicKey, c.SignatureAlgorithm, c.RawTBSCertificate, c.Signature); err != nil {
    46  				continue
    47  			}
    48  			start.issuer = candidate
    49  			break
    50  		}
    51  	}
    52  	go g.walkFromEdgeToRoot(start, out)
    53  	return out
    54  }
    55  
    56  // WalkChains is the same as WalkChainsAsync, except synchronous.
    57  func (g *Graph) WalkChains(c *x509.Certificate) (out []x509.CertificateChain) {
    58  	chainChan := g.WalkChainsAsync(c, WalkOptions{})
    59  	for chain := range chainChan {
    60  		out = append(out, chain)
    61  	}
    62  	return
    63  }
    64  
    65  func (g *Graph) walkFromEdgeToRoot(start *GraphEdge, out chan x509.CertificateChain) {
    66  	soFar := x509.CertificateChain{start.Certificate}
    67  	g.continueWalking(out, start, start.issuer, soFar, start)
    68  	close(out)
    69  	return
    70  }
    71  
    72  func (g *Graph) continueWalking(found chan x509.CertificateChain, start *GraphEdge, current *GraphNode, soFar x509.CertificateChain, lastEdge *GraphEdge) {
    73  	// If the chain ends at a root certificate, send the chain through the out
    74  	// channel.
    75  	if lastEdge.root {
    76  		found <- soFar
    77  		return
    78  	}
    79  
    80  	if current == nil {
    81  		return
    82  	}
    83  
    84  	// If we've traveled too far, just stop.
    85  	if len(soFar) >= maxIntermediateCount {
    86  		return
    87  	}
    88  
    89  	// Try to find the next node. Get edges that all go to the same node.
    90  	for skfp, edgeSet := range current.parentsBySubjectAndKey {
    91  		targetNode := g.nodesBySubjectAndKey[skfp]
    92  
    93  		// Check to see if these edges are taking us to something already in the
    94  		// chain. If the node's SubjectAndKey is already in the chain, don't bother.
    95  		if targetNode != nil {
    96  			if soFar.SubjectAndKeyInChain(targetNode.SubjectAndKey) {
    97  				continue
    98  			}
    99  		}
   100  
   101  		// We're not going to revisit anything now. On the off chance the targetNode
   102  		// was nil, we also aren't doing a duplicate visit, because if we were, the
   103  		// edge would not be dangling.
   104  		for _, edge := range edgeSet.edges {
   105  			certType := x509.CertificateTypeIntermediate
   106  			if edge.root {
   107  				certType = x509.CertificateTypeRoot
   108  			}
   109  			if canAddToChain(edge.Certificate, certType, soFar) != nil {
   110  				continue
   111  			}
   112  			nextSoFar := soFar.AppendToFreshChain(edge.Certificate)
   113  			g.continueWalking(found, start, edge.issuer, nextSoFar, edge)
   114  		}
   115  
   116  	}
   117  	return
   118  }
   119  
   120  // isValid performs validity checks on the c. It will never return a
   121  // date-related error.
   122  func canAddToChain(c *x509.Certificate, certType x509.CertificateType, currentChain x509.CertificateChain) error {
   123  
   124  	// KeyUsage status flags are ignored. From Engineering Security, Peter
   125  	// Gutmann: A European government CA marked its signing certificates as
   126  	// being valid for encryption only, but no-one noticed. Another
   127  	// European CA marked its signature keys as not being valid for
   128  	// signatures. A different CA marked its own trusted root certificate
   129  	// as being invalid for certificate signing.  Another national CA
   130  	// distributed a certificate to be used to encrypt data for the
   131  	// country’s tax authority that was marked as only being usable for
   132  	// digital signatures but not for encryption. Yet another CA reversed
   133  	// the order of the bit flags in the keyUsage due to confusion over
   134  	// encoding endianness, essentially setting a random keyUsage in
   135  	// certificates that it issued. Another CA created a self-invalidating
   136  	// certificate by adding a certificate policy statement stipulating
   137  	// that the certificate had to be used strictly as specified in the
   138  	// keyUsage, and a keyUsage containing a flag indicating that the RSA
   139  	// encryption key could only be used for Diffie-Hellman key agreement.
   140  	if certType == x509.CertificateTypeIntermediate && (!c.BasicConstraintsValid || !c.IsCA) {
   141  		return x509.CertificateInvalidError{Cert: c, Reason: x509.NotAuthorizedToSign}
   142  	}
   143  
   144  	if c.BasicConstraintsValid && c.MaxPathLen >= 0 {
   145  		numIntermediates := len(currentChain) - 1
   146  		if numIntermediates > c.MaxPathLen {
   147  			return x509.CertificateInvalidError{Cert: c, Reason: x509.TooManyIntermediates}
   148  		}
   149  	}
   150  
   151  	return nil
   152  }