github.com/zmap/zcrypto@v0.0.0-20240512203510-0fef58d9a9db/verifier/revocation.go (about) 1 package verifier 2 3 import ( 4 "bytes" 5 "context" 6 "crypto" 7 "errors" 8 "io" 9 "io/ioutil" 10 "net/http" 11 "strings" 12 13 "github.com/zmap/zcrypto/x509" 14 "github.com/zmap/zcrypto/x509/pkix" 15 "github.com/zmap/zcrypto/x509/revocation/crl" 16 "github.com/zmap/zcrypto/x509/revocation/ocsp" 17 ) 18 19 const ( 20 ocspReqContentType = "application/ocsp-request" 21 ocspResContentType = "application/ocsp-response" 22 ) 23 24 // CheckOCSP - check the ocsp status of a provided certificate 25 // if issuer is not provided, function will attempt to fetch it 26 // through the AIA issuing field (which will then fail if this field is empty) 27 func CheckOCSP(ctx context.Context, c *x509.Certificate, issuer *x509.Certificate) (isRevoked bool, info *RevocationInfo, e error) { 28 if issuer == nil { 29 // get issuer certificate from OCSP info 30 if c.IssuingCertificateURL == nil { 31 return false, nil, errors.New("This certificate does not list an issuing party") 32 } 33 34 res, err := httpGet(ctx, c.IssuingCertificateURL[0]) 35 if err != nil { 36 return false, nil, errors.New("failed to send HTTP Request for issuing certificate: " + err.Error()) 37 } 38 39 issuer, err = x509.ParseCertificate(res) 40 if err != nil { 41 return false, nil, errors.New("failed to parse issuer certificate PEM: " + err.Error()) 42 } 43 } 44 // create and send OCSP request 45 opts := &ocsp.RequestOptions{Hash: crypto.SHA1} 46 ocspRequestBytes, err := ocsp.CreateRequest(c, issuer, opts) 47 if err != nil { 48 return false, nil, errors.New("failed to construct OCSP request" + err.Error()) 49 } 50 51 requestReader := bytes.NewReader(ocspRequestBytes) 52 ocspRespBytes, err := httpPost(ctx, c.OCSPServer[0], ocspReqContentType, ocspResContentType, requestReader) 53 if err != nil { 54 return false, nil, errors.New("Failed sending OCSP HTTP Request: " + err.Error()) 55 } 56 57 ocspResp, err := ocsp.ParseResponseForCert(ocspRespBytes, c, issuer) 58 if err != nil { 59 return false, nil, errors.New("Failed to parse OCSP Response: " + err.Error()) 60 } 61 62 isRevoked = ocspResp.IsRevoked 63 info = &RevocationInfo{ 64 NextUpdate: ocspResp.NextUpdate, 65 } 66 if isRevoked { 67 info.RevocationTime = &ocspResp.RevokedAt 68 info.Reason = ocspResp.RevocationReason 69 } 70 71 return 72 } 73 74 // CheckCRL - check whether the provided certificate has been revoked through 75 // a CRL. If no certList is provided, function will attempt to fetch it 76 // through the GetCRL function. If performing repeated calls to this function, 77 // independently calling GetCRL and caching the list between calls to 78 // CheckCRL is highly recommended (otherwise the CRL will be fetched on every 79 // single call to CheckCRL!). 80 func CheckCRL(ctx context.Context, c *x509.Certificate, certList *pkix.CertificateList) (isRevoked bool, info *RevocationInfo, err error) { 81 if certList == nil { 82 certList, err = GetCRL(ctx, c.CRLDistributionPoints[0]) 83 } 84 if err != nil { 85 return false, nil, err 86 } 87 crlData, err := crl.CheckCRLForCert(certList, c, nil) 88 if err != nil { 89 return false, nil, err 90 } 91 92 isRevoked = crlData.IsRevoked 93 94 info = &RevocationInfo{ 95 NextUpdate: crlData.NextUpdate, 96 } 97 98 if isRevoked && crlData.CertificateEntryExtensions.Reason != nil { 99 info.Reason = *crlData.CertificateEntryExtensions.Reason 100 info.RevocationTime = &crlData.RevocationTime 101 } 102 103 return 104 } 105 106 // GetCRL - fetch and parse the CRL from the provided distrution point 107 func GetCRL(ctx context.Context, distributionPoint string) (*pkix.CertificateList, error) { 108 if strings.HasPrefix(distributionPoint, "ldap") { 109 return nil, errors.New("This CRL distributionPointribution point operates over LDAP - could not access") 110 } 111 112 crlRespBody, err := httpGet(ctx, distributionPoint) 113 if err != nil { 114 return nil, errors.New("failed to send HTTP Request for CRL: " + err.Error()) 115 } 116 117 certList, err := x509.ParseCRL(crlRespBody) 118 if err != nil { 119 return nil, errors.New("failed to parse CRL" + err.Error()) 120 } 121 return certList, nil 122 } 123 124 func httpGet(ctx context.Context, url string) ([]byte, error) { 125 req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 126 if err != nil { 127 return nil, err 128 } 129 130 resp, err := http.DefaultClient.Do(req) 131 if err != nil { 132 return nil, err 133 } 134 defer resp.Body.Close() 135 136 body, err := ioutil.ReadAll(resp.Body) 137 if err != nil { 138 return nil, err 139 } 140 141 return body, nil 142 } 143 144 func httpPost(ctx context.Context, url string, contentType, accept string, reqBody io.Reader) ([]byte, error) { 145 req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, reqBody) 146 if err != nil { 147 return nil, err 148 } 149 req.Header.Set("Content-Type", contentType) 150 req.Header.Set("Accept", accept) 151 resp, err := http.DefaultClient.Do(req) 152 if err != nil { 153 return nil, err 154 } 155 defer resp.Body.Close() 156 157 body, err := ioutil.ReadAll(resp.Body) 158 if err != nil { 159 return nil, err 160 } 161 162 return body, nil 163 }