Skip to content

Commit

Permalink
ra: add GenerateOCSP (#6192)
Browse files Browse the repository at this point in the history
Add a new `GenerateOCSP` gRPC method to the RA, which
passes the request through to the CA and returns the resulting
OCSP response bytes. This method does not store the new
response anywhere, it is up to the client (in this case intended to
be the new ocsp-responder's live-signing code path) to store it
in any/all appropriate locations.

Fixes #6189
  • Loading branch information
jsha authored Jun 27, 2022
1 parent 6df6766 commit c653292
Show file tree
Hide file tree
Showing 7 changed files with 379 additions and 194 deletions.
460 changes: 266 additions & 194 deletions ra/proto/ra.pb.go

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions ra/proto/ra.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package ra;
option go_package = "github.com/letsencrypt/boulder/ra/proto";

import "core/proto/core.proto";
import "ca/proto/ca.proto";
import "google/protobuf/empty.proto";

service RegistrationAuthority {
Expand All @@ -18,6 +19,12 @@ service RegistrationAuthority {
rpc AdministrativelyRevokeCertificate(AdministrativelyRevokeCertificateRequest) returns (google.protobuf.Empty) {}
rpc NewOrder(NewOrderRequest) returns (core.Order) {}
rpc FinalizeOrder(FinalizeOrderRequest) returns (core.Order) {}
// Generate an OCSP response based on the DB's current status and reason code.
rpc GenerateOCSP(GenerateOCSPRequest) returns (ca.OCSPResponse) {}
}

message GenerateOCSPRequest {
string serial = 1;
}

message UpdateRegistrationRequest {
Expand Down
39 changes: 39 additions & 0 deletions ra/proto/ra_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions ra/ra.go
Original file line number Diff line number Diff line change
Expand Up @@ -2365,6 +2365,30 @@ func (ra *RegistrationAuthorityImpl) checkOrderNames(names []string) error {
return nil
}

// GenerateOCSP looks up a certificate's status, then requests a signed OCSP
// response for it from the CA. If the certificate status is not available
// or the certificate is expired, it returns berrors.NotFoundError.
// This does not write back the result to the SA or any other storage.
func (ra *RegistrationAuthorityImpl) GenerateOCSP(ctx context.Context, req *rapb.GenerateOCSPRequest) (*capb.OCSPResponse, error) {
status, err := ra.SA.GetCertificateStatus(ctx, &sapb.Serial{Serial: req.Serial})
if err != nil {
return nil, err
}

notAfter := time.Unix(0, status.NotAfter).UTC()
if ra.clk.Now().After(notAfter) {
return nil, berrors.NotFoundError("certificate is expired")
}

return ra.CA.GenerateOCSP(ctx, &capb.GenerateOCSPRequest{
Serial: req.Serial,
Status: status.Status,
Reason: int32(status.RevokedReason),
RevokedAt: status.RevokedDate,
IssuerID: status.IssuerID,
})
}

// NewOrder creates a new order object
func (ra *RegistrationAuthorityImpl) NewOrder(ctx context.Context, req *rapb.NewOrderRequest) (*corepb.Order, error) {
if req == nil || req.RegistrationID == 0 {
Expand Down
35 changes: 35 additions & 0 deletions ra/ra_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3562,6 +3562,41 @@ func (mp *mockPurger) Purge(context.Context, *akamaipb.PurgeRequest, ...grpc.Cal
return &emptypb.Empty{}, nil
}

type mockSAGenerateOCSP struct {
mocks.StorageAuthority
expiration time.Time
}

func (msgo *mockSAGenerateOCSP) GetCertificateStatus(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.CertificateStatus, error) {
return &corepb.CertificateStatus{
Serial: req.Serial,
Status: "good",
NotAfter: msgo.expiration.UTC().UnixNano(),
}, nil
}

func TestGenerateOCSP(t *testing.T) {
_, _, ra, clk, cleanUp := initAuthorities(t)
defer cleanUp()

ra.CA = &mockCAOCSP{}
ra.SA = &mockSAGenerateOCSP{expiration: clk.Now().Add(time.Hour)}

req := &rapb.GenerateOCSPRequest{
Serial: core.SerialToString(big.NewInt(1)),
}

resp, err := ra.GenerateOCSP(context.Background(), req)
test.AssertNotError(t, err, "generating OCSP")
test.AssertByteEquals(t, resp.Response, []byte{1, 2, 3})

ra.SA = &mockSAGenerateOCSP{expiration: clk.Now().Add(-time.Hour)}
_, err = ra.GenerateOCSP(context.Background(), req)
if !errors.Is(err, berrors.NotFound) {
t.Errorf("expected NotFound error, got %s", err)
}
}

func TestRevokerCertificateWithReg(t *testing.T) {
_, _, ra, clk, cleanUp := initAuthorities(t)
defer cleanUp()
Expand Down
3 changes: 3 additions & 0 deletions sa/sa.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,9 @@ func (ssa *SQLStorageAuthority) GetCertificateStatus(ctx context.Context, req *s
}

certStatus, err := SelectCertificateStatus(ssa.dbMap.WithContext(ctx), req.Serial)
if db.IsNoRows(err) {
return nil, berrors.NotFoundError("certificate status with serial %q not found", req.Serial)
}
if err != nil {
return nil, err
}
Expand Down
5 changes: 5 additions & 0 deletions wfe2/wfe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"google.golang.org/protobuf/types/known/emptypb"
jose "gopkg.in/square/go-jose.v2"

capb "github.com/letsencrypt/boulder/ca/proto"
"github.com/letsencrypt/boulder/core"
corepb "github.com/letsencrypt/boulder/core/proto"
berrors "github.com/letsencrypt/boulder/errors"
Expand Down Expand Up @@ -209,6 +210,10 @@ func (ra *MockRegistrationAuthority) RevokeCertByKey(ctx context.Context, in *ra
return &emptypb.Empty{}, nil
}

func (ra *MockRegistrationAuthority) GenerateOCSP(ctx context.Context, req *rapb.GenerateOCSPRequest, _ ...grpc.CallOption) (*capb.OCSPResponse, error) {
return nil, nil
}

func (ra *MockRegistrationAuthority) AdministrativelyRevokeCertificate(context.Context, *rapb.AdministrativelyRevokeCertificateRequest, ...grpc.CallOption) (*emptypb.Empty, error) {
return &emptypb.Empty{}, nil
}
Expand Down

0 comments on commit c653292

Please sign in to comment.