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  }