...

Source file src/github.com/letsencrypt/boulder/ocsp/responder/filter_source.go

Documentation: github.com/letsencrypt/boulder/ocsp/responder

     1  package responder
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto"
     7  	"encoding/hex"
     8  	"errors"
     9  	"fmt"
    10  	"strings"
    11  
    12  	"github.com/jmhodges/clock"
    13  	"github.com/letsencrypt/boulder/core"
    14  	"github.com/letsencrypt/boulder/issuance"
    15  	blog "github.com/letsencrypt/boulder/log"
    16  	"github.com/prometheus/client_golang/prometheus"
    17  	"golang.org/x/crypto/ocsp"
    18  )
    19  
    20  type responderID struct {
    21  	nameHash []byte
    22  	keyHash  []byte
    23  }
    24  
    25  type filterSource struct {
    26  	wrapped        Source
    27  	hashAlgorithm  crypto.Hash
    28  	issuers        map[issuance.IssuerNameID]responderID
    29  	serialPrefixes []string
    30  	counter        *prometheus.CounterVec
    31  	log            blog.Logger
    32  	clk            clock.Clock
    33  }
    34  
    35  // NewFilterSource returns a filterSource which performs various checks on the
    36  // OCSP requests sent to the wrapped Source, and the OCSP responses returned
    37  // by it.
    38  func NewFilterSource(issuerCerts []*issuance.Certificate, serialPrefixes []string, wrapped Source, stats prometheus.Registerer, log blog.Logger, clk clock.Clock) (*filterSource, error) {
    39  	if len(issuerCerts) < 1 {
    40  		return nil, errors.New("filter must include at least 1 issuer cert")
    41  	}
    42  
    43  	issuersByNameId := make(map[issuance.IssuerNameID]responderID)
    44  	for _, issuerCert := range issuerCerts {
    45  		keyHash := issuerCert.KeyHash()
    46  		nameHash := issuerCert.NameHash()
    47  		rid := responderID{
    48  			keyHash:  keyHash[:],
    49  			nameHash: nameHash[:],
    50  		}
    51  		issuersByNameId[issuerCert.NameID()] = rid
    52  	}
    53  
    54  	counter := prometheus.NewCounterVec(prometheus.CounterOpts{
    55  		Name: "ocsp_filter_responses",
    56  		Help: "Count of OCSP requests/responses by action taken by the filter",
    57  	}, []string{"result"})
    58  	stats.MustRegister(counter)
    59  
    60  	return &filterSource{
    61  		wrapped:        wrapped,
    62  		hashAlgorithm:  crypto.SHA1,
    63  		issuers:        issuersByNameId,
    64  		serialPrefixes: serialPrefixes,
    65  		counter:        counter,
    66  		log:            log,
    67  		clk:            clk,
    68  	}, nil
    69  }
    70  
    71  // Response implements the Source interface. It checks the incoming request
    72  // to ensure that we want to handle it, fetches the response from the wrapped
    73  // Source, and checks that the response matches the request.
    74  func (src *filterSource) Response(ctx context.Context, req *ocsp.Request) (*Response, error) {
    75  	iss, err := src.checkRequest(req)
    76  	if err != nil {
    77  		src.log.Debugf("Not responding to filtered OCSP request: %s", err.Error())
    78  		src.counter.WithLabelValues("request_filtered").Inc()
    79  		return nil, err
    80  	}
    81  
    82  	resp, err := src.wrapped.Response(ctx, req)
    83  	if err != nil {
    84  		src.counter.WithLabelValues("wrapped_error").Inc()
    85  		return nil, err
    86  	}
    87  
    88  	err = src.checkResponse(iss, resp)
    89  	if err != nil {
    90  		src.log.Warningf("OCSP Response not sent for CA=%s, Serial=%s, err: %s", hex.EncodeToString(req.IssuerKeyHash), core.SerialToString(req.SerialNumber), err)
    91  		src.counter.WithLabelValues("response_filtered").Inc()
    92  		return nil, err
    93  	}
    94  
    95  	src.counter.WithLabelValues("success").Inc()
    96  	return resp, nil
    97  }
    98  
    99  // checkNextUpdate evaluates whether the nextUpdate field of the requested OCSP
   100  // response is in the past. If so, `errOCSPResponseExpired` will be returned.
   101  func (src *filterSource) checkNextUpdate(resp *Response) error {
   102  	if src.clk.Now().Before(resp.NextUpdate) {
   103  		return nil
   104  	}
   105  	return errOCSPResponseExpired
   106  }
   107  
   108  // checkRequest returns a descriptive error if the request does not satisfy any of
   109  // the requirements of an OCSP request, or nil if the request should be handled.
   110  // If the request passes all checks, then checkRequest returns the unique id of
   111  // the issuer cert specified in the request.
   112  func (src *filterSource) checkRequest(req *ocsp.Request) (issuance.IssuerNameID, error) {
   113  	if req.HashAlgorithm != src.hashAlgorithm {
   114  		return 0, fmt.Errorf("unsupported issuer key/name hash algorithm %s: %w", req.HashAlgorithm, ErrNotFound)
   115  	}
   116  
   117  	if len(src.serialPrefixes) > 0 {
   118  		serialString := core.SerialToString(req.SerialNumber)
   119  		match := false
   120  		for _, prefix := range src.serialPrefixes {
   121  			if strings.HasPrefix(serialString, prefix) {
   122  				match = true
   123  				break
   124  			}
   125  		}
   126  		if !match {
   127  			return 0, fmt.Errorf("unrecognized serial prefix: %w", ErrNotFound)
   128  		}
   129  	}
   130  
   131  	for nameID, rid := range src.issuers {
   132  		if bytes.Equal(req.IssuerNameHash, rid.nameHash) && bytes.Equal(req.IssuerKeyHash, rid.keyHash) {
   133  			return nameID, nil
   134  		}
   135  	}
   136  	return 0, fmt.Errorf("unrecognized issuer key hash %s: %w", hex.EncodeToString(req.IssuerKeyHash), ErrNotFound)
   137  }
   138  
   139  // checkResponse returns nil if the ocsp response was generated by the same
   140  // issuer as was identified in the request, or an error otherwise. This filters
   141  // out, for example, responses which are for a serial that we issued, but from a
   142  // different issuer than that contained in the request.
   143  func (src *filterSource) checkResponse(reqIssuerID issuance.IssuerNameID, resp *Response) error {
   144  	respIssuerID := issuance.GetOCSPIssuerNameID(resp.Response)
   145  	if reqIssuerID != respIssuerID {
   146  		// This would be allowed if we used delegated responders, but we don't.
   147  		return fmt.Errorf("responder name does not match requested issuer name")
   148  	}
   149  
   150  	err := src.checkNextUpdate(resp)
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	// In an ideal world, we'd also compare the Issuer Key Hash from the request's
   156  	// CertID (equivalent to looking up the key hash in src.issuers) against the
   157  	// Issuer Key Hash contained in the response's CertID. However, the Go OCSP
   158  	// library does not provide access to the response's CertID, so we can't.
   159  	// Specifically, we want to compare `src.issuers[reqIssuerID].keyHash` against
   160  	// something like resp.CertID.IssuerKeyHash, but the latter does not exist.
   161  
   162  	return nil
   163  }
   164  

View as plain text