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 }