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 }