...

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

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

     1  package redis
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"errors"
     7  	"fmt"
     8  	"math/big"
     9  	"testing"
    10  	"time"
    11  
    12  	"golang.org/x/crypto/ocsp"
    13  	"google.golang.org/grpc"
    14  	"google.golang.org/protobuf/types/known/timestamppb"
    15  
    16  	"github.com/letsencrypt/boulder/core"
    17  	"github.com/letsencrypt/boulder/db"
    18  	berrors "github.com/letsencrypt/boulder/errors"
    19  	blog "github.com/letsencrypt/boulder/log"
    20  	"github.com/letsencrypt/boulder/metrics"
    21  	"github.com/letsencrypt/boulder/mocks"
    22  	"github.com/letsencrypt/boulder/ocsp/responder"
    23  	ocsp_test "github.com/letsencrypt/boulder/ocsp/test"
    24  	"github.com/letsencrypt/boulder/sa"
    25  	sapb "github.com/letsencrypt/boulder/sa/proto"
    26  	"github.com/letsencrypt/boulder/test"
    27  )
    28  
    29  // echoSource implements rocspSourceInterface, returning the provided response
    30  // and panicking if signAndSave is called.
    31  type echoSource struct {
    32  	resp *ocsp.Response
    33  }
    34  
    35  func (es echoSource) Response(ctx context.Context, req *ocsp.Request) (*responder.Response, error) {
    36  	return &responder.Response{Response: es.resp, Raw: es.resp.Raw}, nil
    37  }
    38  
    39  func (es echoSource) signAndSave(ctx context.Context, req *ocsp.Request, cause signAndSaveCause) (*responder.Response, error) {
    40  	panic("should not happen")
    41  }
    42  
    43  // recordingEchoSource acts like echoSource, but instead of panicking on signAndSave,
    44  // it records the serial number it was called with and returns the given secondResp.
    45  type recordingEchoSource struct {
    46  	echoSource
    47  	secondResp *responder.Response
    48  	ch         chan string
    49  }
    50  
    51  func (res recordingEchoSource) signAndSave(ctx context.Context, req *ocsp.Request, cause signAndSaveCause) (*responder.Response, error) {
    52  	res.ch <- req.SerialNumber.String()
    53  	return res.secondResp, nil
    54  }
    55  
    56  // errorSource implements rocspSourceInterface, and always returns an error.
    57  type errorSource struct{}
    58  
    59  func (es errorSource) Response(ctx context.Context, req *ocsp.Request) (*responder.Response, error) {
    60  	return nil, errors.New("sad trombone")
    61  }
    62  
    63  func (es errorSource) signAndSave(ctx context.Context, req *ocsp.Request, cause signAndSaveCause) (*responder.Response, error) {
    64  	panic("should not happen")
    65  }
    66  
    67  // echoSelector always returns the given certificateStatus.
    68  type echoSelector struct {
    69  	db.MockSqlExecutor
    70  	status sa.RevocationStatusModel
    71  }
    72  
    73  func (s echoSelector) SelectOne(_ context.Context, output interface{}, _ string, _ ...interface{}) error {
    74  	outputPtr, ok := output.(*sa.RevocationStatusModel)
    75  	if !ok {
    76  		return fmt.Errorf("incorrect output type %T", output)
    77  	}
    78  	*outputPtr = s.status
    79  	return nil
    80  }
    81  
    82  // errorSelector always returns an error.
    83  type errorSelector struct {
    84  	db.MockSqlExecutor
    85  }
    86  
    87  func (s errorSelector) SelectOne(_ context.Context, _ interface{}, _ string, _ ...interface{}) error {
    88  	return errors.New("oops")
    89  }
    90  
    91  // notFoundSelector always returns an NoRows error.
    92  type notFoundSelector struct {
    93  	db.MockSqlExecutor
    94  }
    95  
    96  func (s notFoundSelector) SelectOne(_ context.Context, _ interface{}, _ string, _ ...interface{}) error {
    97  	return db.ErrDatabaseOp{Err: sql.ErrNoRows}
    98  }
    99  
   100  // echoSA always returns the given revocation status.
   101  type echoSA struct {
   102  	mocks.StorageAuthorityReadOnly
   103  	status *sapb.RevocationStatus
   104  }
   105  
   106  func (s *echoSA) GetRevocationStatus(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*sapb.RevocationStatus, error) {
   107  	return s.status, nil
   108  }
   109  
   110  // errorSA always returns an error.
   111  type errorSA struct {
   112  	mocks.StorageAuthorityReadOnly
   113  }
   114  
   115  func (s *errorSA) GetRevocationStatus(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*sapb.RevocationStatus, error) {
   116  	return nil, errors.New("oops")
   117  }
   118  
   119  // notFoundSA always returns a NotFound error.
   120  type notFoundSA struct {
   121  	mocks.StorageAuthorityReadOnly
   122  }
   123  
   124  func (s *notFoundSA) GetRevocationStatus(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*sapb.RevocationStatus, error) {
   125  	return nil, berrors.NotFoundError("purged")
   126  }
   127  
   128  func TestCheckedRedisSourceSuccess(t *testing.T) {
   129  	serial := big.NewInt(17777)
   130  	thisUpdate := time.Now().Truncate(time.Second).UTC()
   131  
   132  	resp, _, err := ocsp_test.FakeResponse(ocsp.Response{
   133  		SerialNumber: serial,
   134  		Status:       ocsp.Good,
   135  		ThisUpdate:   thisUpdate,
   136  	})
   137  	test.AssertNotError(t, err, "making fake response")
   138  
   139  	status := sa.RevocationStatusModel{
   140  		Status: core.OCSPStatusGood,
   141  	}
   142  	src := newCheckedRedisSource(echoSource{resp: resp}, echoSelector{status: status}, nil, metrics.NoopRegisterer, blog.NewMock())
   143  	responderResponse, err := src.Response(context.Background(), &ocsp.Request{
   144  		SerialNumber: serial,
   145  	})
   146  	test.AssertNotError(t, err, "getting response")
   147  	test.AssertEquals(t, responderResponse.SerialNumber.String(), resp.SerialNumber.String())
   148  }
   149  
   150  func TestCheckedRedisSourceDBError(t *testing.T) {
   151  	serial := big.NewInt(404040)
   152  	thisUpdate := time.Now().Truncate(time.Second).UTC()
   153  
   154  	resp, _, err := ocsp_test.FakeResponse(ocsp.Response{
   155  		SerialNumber: serial,
   156  		Status:       ocsp.Good,
   157  		ThisUpdate:   thisUpdate,
   158  	})
   159  	test.AssertNotError(t, err, "making fake response")
   160  
   161  	src := newCheckedRedisSource(echoSource{resp: resp}, errorSelector{}, nil, metrics.NoopRegisterer, blog.NewMock())
   162  	_, err = src.Response(context.Background(), &ocsp.Request{
   163  		SerialNumber: serial,
   164  	})
   165  	test.AssertError(t, err, "getting response")
   166  	test.AssertContains(t, err.Error(), "oops")
   167  
   168  	src = newCheckedRedisSource(echoSource{resp: resp}, notFoundSelector{}, nil, metrics.NoopRegisterer, blog.NewMock())
   169  	_, err = src.Response(context.Background(), &ocsp.Request{
   170  		SerialNumber: serial,
   171  	})
   172  	test.AssertError(t, err, "getting response")
   173  	test.AssertErrorIs(t, err, responder.ErrNotFound)
   174  }
   175  
   176  func TestCheckedRedisSourceSAError(t *testing.T) {
   177  	serial := big.NewInt(404040)
   178  	thisUpdate := time.Now().Truncate(time.Second).UTC()
   179  
   180  	resp, _, err := ocsp_test.FakeResponse(ocsp.Response{
   181  		SerialNumber: serial,
   182  		Status:       ocsp.Good,
   183  		ThisUpdate:   thisUpdate,
   184  	})
   185  	test.AssertNotError(t, err, "making fake response")
   186  
   187  	src := newCheckedRedisSource(echoSource{resp: resp}, nil, &errorSA{}, metrics.NoopRegisterer, blog.NewMock())
   188  	_, err = src.Response(context.Background(), &ocsp.Request{
   189  		SerialNumber: serial,
   190  	})
   191  	test.AssertError(t, err, "getting response")
   192  	test.AssertContains(t, err.Error(), "oops")
   193  
   194  	src = newCheckedRedisSource(echoSource{resp: resp}, nil, &notFoundSA{}, metrics.NoopRegisterer, blog.NewMock())
   195  	_, err = src.Response(context.Background(), &ocsp.Request{
   196  		SerialNumber: serial,
   197  	})
   198  	test.AssertError(t, err, "getting response")
   199  	test.AssertErrorIs(t, err, responder.ErrNotFound)
   200  }
   201  
   202  func TestCheckedRedisSourceRedisError(t *testing.T) {
   203  	serial := big.NewInt(314159262)
   204  
   205  	status := sa.RevocationStatusModel{
   206  		Status: core.OCSPStatusGood,
   207  	}
   208  	src := newCheckedRedisSource(errorSource{}, echoSelector{status: status}, nil, metrics.NoopRegisterer, blog.NewMock())
   209  	_, err := src.Response(context.Background(), &ocsp.Request{
   210  		SerialNumber: serial,
   211  	})
   212  	test.AssertError(t, err, "getting response")
   213  }
   214  
   215  func TestCheckedRedisStatusDisagreement(t *testing.T) {
   216  	serial := big.NewInt(2718)
   217  	thisUpdate := time.Now().Truncate(time.Second).UTC()
   218  
   219  	resp, _, err := ocsp_test.FakeResponse(ocsp.Response{
   220  		SerialNumber: serial,
   221  		Status:       ocsp.Good,
   222  		ThisUpdate:   thisUpdate.Add(-time.Minute),
   223  	})
   224  	test.AssertNotError(t, err, "making fake response")
   225  
   226  	secondResp, _, err := ocsp_test.FakeResponse(ocsp.Response{
   227  		SerialNumber:     serial,
   228  		Status:           ocsp.Revoked,
   229  		RevokedAt:        thisUpdate,
   230  		RevocationReason: ocsp.KeyCompromise,
   231  		ThisUpdate:       thisUpdate,
   232  	})
   233  	test.AssertNotError(t, err, "making fake response")
   234  	status := sa.RevocationStatusModel{
   235  		Status:        core.OCSPStatusRevoked,
   236  		RevokedDate:   thisUpdate,
   237  		RevokedReason: ocsp.KeyCompromise,
   238  	}
   239  	source := recordingEchoSource{
   240  		echoSource: echoSource{resp: resp},
   241  		secondResp: &responder.Response{Response: secondResp, Raw: secondResp.Raw},
   242  		ch:         make(chan string, 1),
   243  	}
   244  	src := newCheckedRedisSource(source, echoSelector{status: status}, nil, metrics.NoopRegisterer, blog.NewMock())
   245  	fetchedResponse, err := src.Response(context.Background(), &ocsp.Request{
   246  		SerialNumber: serial,
   247  	})
   248  	test.AssertNotError(t, err, "getting re-signed response")
   249  	test.Assert(t, fetchedResponse.ThisUpdate.Equal(thisUpdate), "thisUpdate not updated")
   250  	test.AssertEquals(t, fetchedResponse.SerialNumber.String(), serial.String())
   251  	test.AssertEquals(t, fetchedResponse.RevokedAt, thisUpdate)
   252  	test.AssertEquals(t, fetchedResponse.RevocationReason, ocsp.KeyCompromise)
   253  	test.AssertEquals(t, fetchedResponse.ThisUpdate, thisUpdate)
   254  }
   255  
   256  func TestCheckedRedisStatusSADisagreement(t *testing.T) {
   257  	serial := big.NewInt(2718)
   258  	thisUpdate := time.Now().Truncate(time.Second).UTC()
   259  
   260  	resp, _, err := ocsp_test.FakeResponse(ocsp.Response{
   261  		SerialNumber: serial,
   262  		Status:       ocsp.Good,
   263  		ThisUpdate:   thisUpdate.Add(-time.Minute),
   264  	})
   265  	test.AssertNotError(t, err, "making fake response")
   266  
   267  	secondResp, _, err := ocsp_test.FakeResponse(ocsp.Response{
   268  		SerialNumber:     serial,
   269  		Status:           ocsp.Revoked,
   270  		RevokedAt:        thisUpdate,
   271  		RevocationReason: ocsp.KeyCompromise,
   272  		ThisUpdate:       thisUpdate,
   273  	})
   274  	test.AssertNotError(t, err, "making fake response")
   275  	statusPB := sapb.RevocationStatus{
   276  		Status:        1,
   277  		RevokedDate:   timestamppb.New(thisUpdate),
   278  		RevokedReason: ocsp.KeyCompromise,
   279  	}
   280  	source := recordingEchoSource{
   281  		echoSource: echoSource{resp: resp},
   282  		secondResp: &responder.Response{Response: secondResp, Raw: secondResp.Raw},
   283  		ch:         make(chan string, 1),
   284  	}
   285  	src := newCheckedRedisSource(source, nil, &echoSA{status: &statusPB}, metrics.NoopRegisterer, blog.NewMock())
   286  	fetchedResponse, err := src.Response(context.Background(), &ocsp.Request{
   287  		SerialNumber: serial,
   288  	})
   289  	test.AssertNotError(t, err, "getting re-signed response")
   290  	test.Assert(t, fetchedResponse.ThisUpdate.Equal(thisUpdate), "thisUpdate not updated")
   291  	test.AssertEquals(t, fetchedResponse.SerialNumber.String(), serial.String())
   292  	test.AssertEquals(t, fetchedResponse.RevokedAt, thisUpdate)
   293  	test.AssertEquals(t, fetchedResponse.RevocationReason, ocsp.KeyCompromise)
   294  	test.AssertEquals(t, fetchedResponse.ThisUpdate, thisUpdate)
   295  }
   296  

View as plain text