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

     1  // Package ocsp implements an OCSP responder based on a generic storage backend.
     2  // It provides a couple of sample implementations.
     3  // Because OCSP responders handle high query volumes, we have to be careful
     4  // about how much logging we do. Error-level logs are reserved for problems
     5  // internal to the server, that can be fixed by an administrator. Any type of
     6  // incorrect input from a user should be logged and Info or below. For things
     7  // that are logged on every request, Debug is the appropriate level.
     8  package ocsp
     9  
    10  import (
    11  	"crypto/sha256"
    12  	"encoding/base64"
    13  	"encoding/hex"
    14  	"errors"
    15  	"fmt"
    16  	"github.com/hellobchain/newcryptosm/http"
    17  	"io/ioutil"
    18  	"net/url"
    19  	"regexp"
    20  	"time"
    21  
    22  	"github.com/jmhodges/clock"
    23  	"github.com/hellobchain/third_party/cloudflare/cfssl/certdb"
    24  	"github.com/hellobchain/third_party/cloudflare/cfssl/log"
    25  	"golang.org/x/crypto/ocsp"
    26  )
    27  
    28  var (
    29  	malformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01}
    30  	internalErrorErrorResponse    = []byte{0x30, 0x03, 0x0A, 0x01, 0x02}
    31  	tryLaterErrorResponse         = []byte{0x30, 0x03, 0x0A, 0x01, 0x03}
    32  	sigRequredErrorResponse       = []byte{0x30, 0x03, 0x0A, 0x01, 0x05}
    33  	unauthorizedErrorResponse     = []byte{0x30, 0x03, 0x0A, 0x01, 0x06}
    34  
    35  	// ErrNotFound indicates the request OCSP response was not found. It is used to
    36  	// indicate that the responder should reply with unauthorizedErrorResponse.
    37  	ErrNotFound = errors.New("Request OCSP Response not found")
    38  )
    39  
    40  // Source represents the logical source of OCSP responses, i.e.,
    41  // the logic that actually chooses a response based on a request.  In
    42  // order to create an actual responder, wrap one of these in a Responder
    43  // object and pass it to http.Handle. By default the Responder will set
    44  // the headers Cache-Control to "max-age=(response.NextUpdate-now), public, no-transform, must-revalidate",
    45  // Last-Modified to response.ThisUpdate, Expires to response.NextUpdate,
    46  // ETag to the SHA256 hash of the response, and Content-Type to
    47  // application/ocsp-response. If you want to override these headers,
    48  // or set extra headers, your source should return a http.Header
    49  // with the headers you wish to set. If you don't want to set any
    50  // extra headers you may return nil instead.
    51  type Source interface {
    52  	Response(*ocsp.Request) ([]byte, http.Header, error)
    53  }
    54  
    55  // An InMemorySource is a map from serialNumber -> der(response)
    56  type InMemorySource map[string][]byte
    57  
    58  // Response looks up an OCSP response to provide for a given request.
    59  // InMemorySource looks up a response purely based on serial number,
    60  // without regard to what issuer the request is asking for.
    61  func (src InMemorySource) Response(request *ocsp.Request) ([]byte, http.Header, error) {
    62  	response, present := src[request.SerialNumber.String()]
    63  	if !present {
    64  		return nil, nil, ErrNotFound
    65  	}
    66  	return response, nil, nil
    67  }
    68  
    69  // DBSource represnts a source of OCSP responses backed by the certdb package.
    70  type DBSource struct {
    71  	Accessor certdb.Accessor
    72  }
    73  
    74  // NewDBSource creates a new DBSource type with an associated dbAccessor.
    75  func NewDBSource(dbAccessor certdb.Accessor) Source {
    76  	return DBSource{
    77  		Accessor: dbAccessor,
    78  	}
    79  }
    80  
    81  // Response implements cfssl.ocsp.responder.Source, which returns the
    82  // OCSP response in the Database for the given request with the expiration
    83  // date furthest in the future.
    84  func (src DBSource) Response(req *ocsp.Request) ([]byte, http.Header, error) {
    85  	if req == nil {
    86  		return nil, nil, errors.New("called with nil request")
    87  	}
    88  
    89  	aki := hex.EncodeToString(req.IssuerKeyHash)
    90  	sn := req.SerialNumber
    91  
    92  	if sn == nil {
    93  		return nil, nil, errors.New("request contains no serial")
    94  	}
    95  	strSN := sn.String()
    96  
    97  	if src.Accessor == nil {
    98  		log.Errorf("No DB Accessor")
    99  		return nil, nil, errors.New("called with nil DB accessor")
   100  	}
   101  	records, err := src.Accessor.GetOCSP(strSN, aki)
   102  
   103  	// Response() logs when there are errors obtaining the OCSP response
   104  	// and returns nil, false.
   105  	if err != nil {
   106  		log.Errorf("Error obtaining OCSP response: %s", err)
   107  		return nil, nil, fmt.Errorf("failed to obtain OCSP response: %s", err)
   108  	}
   109  
   110  	if len(records) == 0 {
   111  		return nil, nil, ErrNotFound
   112  	}
   113  
   114  	// Response() finds the OCSPRecord with the expiration date furthest in the future.
   115  	cur := records[0]
   116  	for _, rec := range records {
   117  		if rec.Expiry.After(cur.Expiry) {
   118  			cur = rec
   119  		}
   120  	}
   121  	return []byte(cur.Body), nil, nil
   122  }
   123  
   124  // NewSourceFromFile reads the named file into an InMemorySource.
   125  // The file read by this function must contain whitespace-separated OCSP
   126  // responses. Each OCSP response must be in base64-encoded DER form (i.e.,
   127  // PEM without headers or whitespace).  Invalid responses are ignored.
   128  // This function pulls the entire file into an InMemorySource.
   129  func NewSourceFromFile(responseFile string) (Source, error) {
   130  	fileContents, err := ioutil.ReadFile(responseFile)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	responsesB64 := regexp.MustCompile("\\s").Split(string(fileContents), -1)
   136  	src := InMemorySource{}
   137  	for _, b64 := range responsesB64 {
   138  		// if the line/space is empty just skip
   139  		if b64 == "" {
   140  			continue
   141  		}
   142  		der, tmpErr := base64.StdEncoding.DecodeString(b64)
   143  		if tmpErr != nil {
   144  			log.Errorf("Base64 decode error %s on: %s", tmpErr, b64)
   145  			continue
   146  		}
   147  
   148  		response, tmpErr := ocsp.ParseResponse(der, nil)
   149  		if tmpErr != nil {
   150  			log.Errorf("OCSP decode error %s on: %s", tmpErr, b64)
   151  			continue
   152  		}
   153  
   154  		src[response.SerialNumber.String()] = der
   155  	}
   156  
   157  	log.Infof("Read %d OCSP responses", len(src))
   158  	return src, nil
   159  }
   160  
   161  // A Responder object provides the HTTP logic to expose a
   162  // Source of OCSP responses.
   163  type Responder struct {
   164  	Source Source
   165  	clk    clock.Clock
   166  }
   167  
   168  // NewResponder instantiates a Responder with the give Source.
   169  func NewResponder(source Source) *Responder {
   170  	return &Responder{
   171  		Source: source,
   172  		clk:    clock.Default(),
   173  	}
   174  }
   175  
   176  func overrideHeaders(response http.ResponseWriter, headers http.Header) {
   177  	for k, v := range headers {
   178  		if len(v) == 1 {
   179  			response.Header().Set(k, v[0])
   180  		} else if len(v) > 1 {
   181  			response.Header().Del(k)
   182  			for _, e := range v {
   183  				response.Header().Add(k, e)
   184  			}
   185  		}
   186  	}
   187  }
   188  
   189  // A Responder can process both GET and POST requests.  The mapping
   190  // from an OCSP request to an OCSP response is done by the Source;
   191  // the Responder simply decodes the request, and passes back whatever
   192  // response is provided by the source.
   193  // Note: The caller must use http.StripPrefix to strip any path components
   194  // (including '/') on GET requests.
   195  // Do not use this responder in conjunction with http.NewServeMux, because the
   196  // default handler will try to canonicalize path components by changing any
   197  // strings of repeated '/' into a single '/', which will break the base64
   198  // encoding.
   199  func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Request) {
   200  	// By default we set a 'max-age=0, no-cache' Cache-Control header, this
   201  	// is only returned to the client if a valid authorized OCSP response
   202  	// is not found or an error is returned. If a response if found the header
   203  	// will be altered to contain the proper max-age and modifiers.
   204  	response.Header().Add("Cache-Control", "max-age=0, no-cache")
   205  	// Read response from request
   206  	var requestBody []byte
   207  	var err error
   208  	switch request.Method {
   209  	case "GET":
   210  		base64Request, err := url.QueryUnescape(request.URL.Path)
   211  		if err != nil {
   212  			log.Infof("Error decoding URL: %s", request.URL.Path)
   213  			response.WriteHeader(http.StatusBadRequest)
   214  			return
   215  		}
   216  		// url.QueryUnescape not only unescapes %2B escaping, but it additionally
   217  		// turns the resulting '+' into a space, which makes base64 decoding fail.
   218  		// So we go back afterwards and turn ' ' back into '+'. This means we
   219  		// accept some malformed input that includes ' ' or %20, but that's fine.
   220  		base64RequestBytes := []byte(base64Request)
   221  		for i := range base64RequestBytes {
   222  			if base64RequestBytes[i] == ' ' {
   223  				base64RequestBytes[i] = '+'
   224  			}
   225  		}
   226  		// In certain situations a UA may construct a request that has a double
   227  		// slash between the host name and the base64 request body due to naively
   228  		// constructing the request URL. In that case strip the leading slash
   229  		// so that we can still decode the request.
   230  		if len(base64RequestBytes) > 0 && base64RequestBytes[0] == '/' {
   231  			base64RequestBytes = base64RequestBytes[1:]
   232  		}
   233  		requestBody, err = base64.StdEncoding.DecodeString(string(base64RequestBytes))
   234  		if err != nil {
   235  			log.Infof("Error decoding base64 from URL: %s", string(base64RequestBytes))
   236  			response.WriteHeader(http.StatusBadRequest)
   237  			return
   238  		}
   239  	case "POST":
   240  		requestBody, err = ioutil.ReadAll(request.Body)
   241  		if err != nil {
   242  			log.Errorf("Problem reading body of POST: %s", err)
   243  			response.WriteHeader(http.StatusBadRequest)
   244  			return
   245  		}
   246  	default:
   247  		response.WriteHeader(http.StatusMethodNotAllowed)
   248  		return
   249  	}
   250  	b64Body := base64.StdEncoding.EncodeToString(requestBody)
   251  	log.Debugf("Received OCSP request: %s", b64Body)
   252  
   253  	// All responses after this point will be OCSP.
   254  	// We could check for the content type of the request, but that
   255  	// seems unnecessariliy restrictive.
   256  	response.Header().Add("Content-Type", "application/ocsp-response")
   257  
   258  	// Parse response as an OCSP request
   259  	// XXX: This fails if the request contains the nonce extension.
   260  	//      We don't intend to support nonces anyway, but maybe we
   261  	//      should return unauthorizedRequest instead of malformed.
   262  	ocspRequest, err := ocsp.ParseRequest(requestBody)
   263  	if err != nil {
   264  		log.Infof("Error decoding request body: %s", b64Body)
   265  		response.WriteHeader(http.StatusBadRequest)
   266  		response.Write(malformedRequestErrorResponse)
   267  		return
   268  	}
   269  
   270  	// Look up OCSP response from source
   271  	ocspResponse, headers, err := rs.Source.Response(ocspRequest)
   272  	if err != nil {
   273  		if err == ErrNotFound {
   274  			log.Infof("No response found for request: serial %x, request body %s",
   275  				ocspRequest.SerialNumber, b64Body)
   276  			response.Write(unauthorizedErrorResponse)
   277  			return
   278  		}
   279  		log.Infof("Error retrieving response for request: serial %x, request body %s, error: %s",
   280  			ocspRequest.SerialNumber, b64Body, err)
   281  		response.WriteHeader(http.StatusInternalServerError)
   282  		response.Write(internalErrorErrorResponse)
   283  		return
   284  	}
   285  
   286  	parsedResponse, err := ocsp.ParseResponse(ocspResponse, nil)
   287  	if err != nil {
   288  		log.Errorf("Error parsing response for serial %x: %s",
   289  			ocspRequest.SerialNumber, err)
   290  		response.Write(unauthorizedErrorResponse)
   291  		return
   292  	}
   293  
   294  	// Write OCSP response to response
   295  	response.Header().Add("Last-Modified", parsedResponse.ThisUpdate.Format(time.RFC1123))
   296  	response.Header().Add("Expires", parsedResponse.NextUpdate.Format(time.RFC1123))
   297  	now := rs.clk.Now()
   298  	maxAge := 0
   299  	if now.Before(parsedResponse.NextUpdate) {
   300  		maxAge = int(parsedResponse.NextUpdate.Sub(now) / time.Second)
   301  	} else {
   302  		// TODO(#530): we want max-age=0 but this is technically an authorized OCSP response
   303  		//             (despite being stale) and 5019 forbids attaching no-cache
   304  		maxAge = 0
   305  	}
   306  	response.Header().Set(
   307  		"Cache-Control",
   308  		fmt.Sprintf(
   309  			"max-age=%d, public, no-transform, must-revalidate",
   310  			maxAge,
   311  		),
   312  	)
   313  	responseHash := sha256.Sum256(ocspResponse)
   314  	response.Header().Add("ETag", fmt.Sprintf("\"%X\"", responseHash))
   315  
   316  	if headers != nil {
   317  		overrideHeaders(response, headers)
   318  	}
   319  
   320  	// RFC 7232 says that a 304 response must contain the above
   321  	// headers if they would also be sent for a 200 for the same
   322  	// request, so we have to wait until here to do this
   323  	if etag := request.Header.Get("If-None-Match"); etag != "" {
   324  		if etag == fmt.Sprintf("\"%X\"", responseHash) {
   325  			response.WriteHeader(http.StatusNotModified)
   326  			return
   327  		}
   328  	}
   329  	response.WriteHeader(http.StatusOK)
   330  	response.Write(ocspResponse)
   331  }