github.com/hellobchain/third_party@v0.0.0-20230331131523-deb0478a2e52/cloudflare/cfssl/revoke/revoke.go (about)

     1  // Package revoke provides functionality for checking the validity of
     2  // a cert. Specifically, the temporal validity of the certificate is
     3  // checked first, then any CRL and OCSP url in the cert is checked.
     4  package revoke
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/base64"
     9  	"encoding/pem"
    10  	"errors"
    11  	"github.com/hellobchain/newcryptosm"
    12  	"github.com/hellobchain/newcryptosm/http"
    13  	"github.com/hellobchain/newcryptosm/x509"
    14  	"github.com/hellobchain/newcryptosm/x509/pkix"
    15  	"io"
    16  	"io/ioutil"
    17  	neturl "net/url"
    18  	"sync"
    19  	"time"
    20  
    21  	"github.com/hellobchain/third_party/ocsp"
    22  
    23  	"github.com/hellobchain/third_party/cloudflare/cfssl/helpers"
    24  	"github.com/hellobchain/third_party/cloudflare/cfssl/log"
    25  )
    26  
    27  // HardFail determines whether the failure to check the revocation
    28  // status of a certificate (i.e. due to network failure) causes
    29  // verification to fail (a hard failure).
    30  var HardFail = false
    31  
    32  // CRLSet associates a PKIX certificate list with the URL the CRL is
    33  // fetched from.
    34  var CRLSet = map[string]*pkix.CertificateList{}
    35  var crlLock = new(sync.Mutex)
    36  
    37  // We can't handle LDAP certificates, so this checks to see if the
    38  // URL string points to an LDAP resource so that we can ignore it.
    39  func ldapURL(url string) bool {
    40  	u, err := neturl.Parse(url)
    41  	if err != nil {
    42  		log.Warningf("error parsing url %s: %v", url, err)
    43  		return false
    44  	}
    45  	if u.Scheme == "ldap" {
    46  		return true
    47  	}
    48  	return false
    49  }
    50  
    51  // revCheck should check the certificate for any revocations. It
    52  // returns a pair of booleans: the first indicates whether the certificate
    53  // is revoked, the second indicates whether the revocations were
    54  // successfully checked.. This leads to the following combinations:
    55  //
    56  //  false, false: an error was encountered while checking revocations.
    57  //
    58  //  false, true:  the certificate was checked successfully and
    59  //                  it is not revoked.
    60  //
    61  //  true, true:   the certificate was checked successfully and
    62  //                  it is revoked.
    63  //
    64  //  true, false:  failure to check revocation status causes
    65  //                  verification to fail
    66  func revCheck(cert *x509.Certificate) (revoked, ok bool) {
    67  	for _, url := range cert.CRLDistributionPoints {
    68  		if ldapURL(url) {
    69  			log.Infof("skipping LDAP CRL: %s", url)
    70  			continue
    71  		}
    72  
    73  		if revoked, ok := certIsRevokedCRL(cert, url); !ok {
    74  			log.Warning("error checking revocation via CRL")
    75  			if HardFail {
    76  				return true, false
    77  			}
    78  			return false, false
    79  		} else if revoked {
    80  			log.Info("certificate is revoked via CRL")
    81  			return true, true
    82  		}
    83  	}
    84  
    85  	if revoked, ok := certIsRevokedOCSP(cert, HardFail); !ok {
    86  		log.Warning("error checking revocation via OCSP")
    87  		if HardFail {
    88  			return true, false
    89  		}
    90  		return false, false
    91  	} else if revoked {
    92  		log.Info("certificate is revoked via OCSP")
    93  		return true, true
    94  	}
    95  
    96  	return false, true
    97  }
    98  
    99  // fetchCRL fetches and parses a CRL.
   100  func fetchCRL(url string) (*pkix.CertificateList, error) {
   101  	resp, err := http.Get(url)
   102  	if err != nil {
   103  		return nil, err
   104  	} else if resp.StatusCode >= 300 {
   105  		return nil, errors.New("failed to retrieve CRL")
   106  	}
   107  
   108  	body, err := crlRead(resp.Body)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	resp.Body.Close()
   113  
   114  	return x509.ParseCRL(body)
   115  }
   116  
   117  func getIssuer(cert *x509.Certificate) *x509.Certificate {
   118  	var issuer *x509.Certificate
   119  	var err error
   120  	for _, issuingCert := range cert.IssuingCertificateURL {
   121  		issuer, err = fetchRemote(issuingCert)
   122  		if err != nil {
   123  			continue
   124  		}
   125  		break
   126  	}
   127  
   128  	return issuer
   129  
   130  }
   131  
   132  // check a cert against a specific CRL. Returns the same bool pair
   133  // as revCheck.
   134  func certIsRevokedCRL(cert *x509.Certificate, url string) (revoked, ok bool) {
   135  	crl, ok := CRLSet[url]
   136  	if ok && crl == nil {
   137  		ok = false
   138  		crlLock.Lock()
   139  		delete(CRLSet, url)
   140  		crlLock.Unlock()
   141  	}
   142  
   143  	var shouldFetchCRL = true
   144  	if ok {
   145  		if !crl.HasExpired(time.Now()) {
   146  			shouldFetchCRL = false
   147  		}
   148  	}
   149  
   150  	issuer := getIssuer(cert)
   151  
   152  	if shouldFetchCRL {
   153  		var err error
   154  		crl, err = fetchCRL(url)
   155  		if err != nil {
   156  			log.Warningf("failed to fetch CRL: %v", err)
   157  			return false, false
   158  		}
   159  
   160  		// check CRL signature
   161  		if issuer != nil {
   162  			err = issuer.CheckCRLSignature(crl)
   163  			if err != nil {
   164  				log.Warningf("failed to verify CRL: %v", err)
   165  				return false, false
   166  			}
   167  		}
   168  
   169  		crlLock.Lock()
   170  		CRLSet[url] = crl
   171  		crlLock.Unlock()
   172  	}
   173  
   174  	for _, revoked := range crl.TBSCertList.RevokedCertificates {
   175  		if cert.SerialNumber.Cmp(revoked.SerialNumber) == 0 {
   176  			log.Info("Serial number match: intermediate is revoked.")
   177  			return true, true
   178  		}
   179  	}
   180  
   181  	return false, true
   182  }
   183  
   184  // VerifyCertificate ensures that the certificate passed in hasn't
   185  // expired and checks the CRL for the server.
   186  func VerifyCertificate(cert *x509.Certificate) (revoked, ok bool) {
   187  	if !time.Now().Before(cert.NotAfter) {
   188  		log.Infof("Certificate expired %s\n", cert.NotAfter)
   189  		return true, true
   190  	} else if !time.Now().After(cert.NotBefore) {
   191  		log.Infof("Certificate isn't valid until %s\n", cert.NotBefore)
   192  		return true, true
   193  	}
   194  
   195  	return revCheck(cert)
   196  }
   197  
   198  func fetchRemote(url string) (*x509.Certificate, error) {
   199  	resp, err := http.Get(url)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  
   204  	in, err := remoteRead(resp.Body)
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  	resp.Body.Close()
   209  
   210  	p, _ := pem.Decode(in)
   211  	if p != nil {
   212  		return helpers.ParseCertificatePEM(in)
   213  	}
   214  
   215  	return x509.ParseCertificate(in)
   216  }
   217  
   218  var ocspOpts = ocsp.RequestOptions{
   219  	Hash: newcryptosm.SHA1,
   220  }
   221  
   222  func certIsRevokedOCSP(leaf *x509.Certificate, strict bool) (revoked, ok bool) {
   223  	var err error
   224  
   225  	ocspURLs := leaf.OCSPServer
   226  	if len(ocspURLs) == 0 {
   227  		// OCSP not enabled for this certificate.
   228  		return false, true
   229  	}
   230  
   231  	issuer := getIssuer(leaf)
   232  
   233  	if issuer == nil {
   234  		return false, false
   235  	}
   236  
   237  	ocspRequest, err := ocsp.CreateRequest(leaf, issuer, &ocspOpts)
   238  	if err != nil {
   239  		return
   240  	}
   241  
   242  	for _, server := range ocspURLs {
   243  		resp, err := sendOCSPRequest(server, ocspRequest, leaf, issuer)
   244  		if err != nil {
   245  			if strict {
   246  				return
   247  			}
   248  			continue
   249  		}
   250  
   251  		// There wasn't an error fetching the OCSP status.
   252  		ok = true
   253  
   254  		if resp.Status != ocsp.Good {
   255  			// The certificate was revoked.
   256  			revoked = true
   257  		}
   258  
   259  		return
   260  	}
   261  	return
   262  }
   263  
   264  // sendOCSPRequest attempts to request an OCSP response from the
   265  // server. The error only indicates a failure to *fetch* the
   266  // certificate, and *does not* mean the certificate is valid.
   267  func sendOCSPRequest(server string, req []byte, leaf, issuer *x509.Certificate) (*ocsp.Response, error) {
   268  	var resp *http.Response
   269  	var err error
   270  	if len(req) > 256 {
   271  		buf := bytes.NewBuffer(req)
   272  		resp, err = http.Post(server, "application/ocsp-request", buf)
   273  	} else {
   274  		reqURL := server + "/" + base64.StdEncoding.EncodeToString(req)
   275  		resp, err = http.Get(reqURL)
   276  	}
   277  
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  
   282  	if resp.StatusCode != http.StatusOK {
   283  		return nil, errors.New("failed to retrieve OSCP")
   284  	}
   285  
   286  	body, err := ocspRead(resp.Body)
   287  	if err != nil {
   288  		return nil, err
   289  	}
   290  	resp.Body.Close()
   291  
   292  	switch {
   293  	case bytes.Equal(body, ocsp.UnauthorizedErrorResponse):
   294  		return nil, errors.New("OSCP unauthorized")
   295  	case bytes.Equal(body, ocsp.MalformedRequestErrorResponse):
   296  		return nil, errors.New("OSCP malformed")
   297  	case bytes.Equal(body, ocsp.InternalErrorErrorResponse):
   298  		return nil, errors.New("OSCP internal error")
   299  	case bytes.Equal(body, ocsp.TryLaterErrorResponse):
   300  		return nil, errors.New("OSCP try later")
   301  	case bytes.Equal(body, ocsp.SigRequredErrorResponse):
   302  		return nil, errors.New("OSCP signature required")
   303  	}
   304  
   305  	return ocsp.ParseResponseForCert(body, leaf, issuer)
   306  }
   307  
   308  var crlRead = ioutil.ReadAll
   309  
   310  // SetCRLFetcher sets the function to use to read from the http response body
   311  func SetCRLFetcher(fn func(io.Reader) ([]byte, error)) {
   312  	crlRead = fn
   313  }
   314  
   315  var remoteRead = ioutil.ReadAll
   316  
   317  // SetRemoteFetcher sets the function to use to read from the http response body
   318  func SetRemoteFetcher(fn func(io.Reader) ([]byte, error)) {
   319  	remoteRead = fn
   320  }
   321  
   322  var ocspRead = ioutil.ReadAll
   323  
   324  // SetOCSPFetcher sets the function to use to read from the http response body
   325  func SetOCSPFetcher(fn func(io.Reader) ([]byte, error)) {
   326  	ocspRead = fn
   327  }