Skip to content

Commit

Permalink
fixup! Implement Certificate Revocation List
Browse files Browse the repository at this point in the history
  • Loading branch information
Danielius1922 committed Oct 4, 2024
1 parent bf38565 commit 8983328
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 27 deletions.
2 changes: 1 addition & 1 deletion certificate-authority/service/grpc/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type Signer struct {
hubID string
crl struct {
serverAddress string
validFor time.Duration // TODO: schedule the CRL to be regenerated -> increment the Number, ThisUpdate and NextUpdate fields
validFor time.Duration
}
}

Expand Down
9 changes: 3 additions & 6 deletions certificate-authority/service/http/requestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,24 @@ import (
"github.com/plgd-dev/hub/v2/certificate-authority/service/uri"
"github.com/plgd-dev/hub/v2/certificate-authority/store"
"github.com/plgd-dev/hub/v2/http-gateway/serverMux"
"github.com/plgd-dev/hub/v2/pkg/log"
)

// RequestHandler for handling incoming request
type RequestHandler struct {
config *Config
mux *runtime.ServeMux

cas *grpcService.CertificateAuthorityServer
store store.Store
logger log.Logger
cas *grpcService.CertificateAuthorityServer
store store.Store
}

// NewHTTP returns HTTP handler
func NewRequestHandler(config *Config, r *mux.Router, cas *grpcService.CertificateAuthorityServer, s store.Store, logger log.Logger) (*RequestHandler, error) {
func NewRequestHandler(config *Config, r *mux.Router, cas *grpcService.CertificateAuthorityServer, s store.Store) (*RequestHandler, error) {
requestHandler := &RequestHandler{
config: config,
mux: serverMux.New(),
cas: cas,
store: s,
logger: logger,
}

if config.CRLEnabled {
Expand Down
2 changes: 1 addition & 1 deletion certificate-authority/service/http/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func New(serviceName string, config Config, s store.Store, ca *grpcService.Certi
return nil, fmt.Errorf("cannot create http service: %w", err)
}

requestHandler, err := NewRequestHandler(&config, service.GetRouter(), ca, s, logger)
requestHandler, err := NewRequestHandler(&config, service.GetRouter(), ca, s)
if err != nil {
_ = service.Close()
return nil, err
Expand Down
6 changes: 5 additions & 1 deletion certificate-authority/store/mongodb/revocationList.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mongodb
import (
"context"
"errors"
"fmt"
"math/big"
"time"

Expand Down Expand Up @@ -121,6 +122,9 @@ func (s *Store) UpdateRevocationList(ctx context.Context, query *store.UpdateRev
Certificates: maps.Values(upd.certificatesToInsert),
}
if err = s.InsertRevocationLists(ctx, newRL); err != nil {
if mongo.IsDuplicateKeyError(err) {
return nil, fmt.Errorf("%w: %w", store.ErrDuplicateID, err)
}
return nil, err
}
return newRL, nil
Expand Down Expand Up @@ -157,7 +161,7 @@ func (s *Store) UpdateRevocationList(ctx context.Context, query *store.UpdateRev
var updatedRL store.RevocationList
if err = s.Collection(revocationListCol).FindOneAndUpdate(ctx, filter, update, opts).Decode(&updatedRL); err != nil {

Check failure

Code scanning / CodeQL

Database query built from user-controlled sources High

This query depends on a
user-provided value
.
if errors.Is(err, mongo.ErrNoDocuments) {
return nil, store.ErrNotFound
return nil, fmt.Errorf("%w: %w", store.ErrNotFound, err)
}
return nil, err
}
Expand Down
111 changes: 98 additions & 13 deletions certificate-authority/store/mongodb/revocationList_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ package mongodb_test

import (
"context"
"errors"
"sync"
"sync/atomic"
"testing"
"time"

"github.com/google/uuid"
"github.com/plgd-dev/hub/v2/certificate-authority/store"
"github.com/plgd-dev/hub/v2/certificate-authority/store/mongodb"
"github.com/plgd-dev/hub/v2/certificate-authority/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand All @@ -26,6 +31,13 @@ func TestUpdateRevocationList(t *testing.T) {
ValidUntil: time.Now().Add(time.Hour).Unix(),
Revocation: time.Now().Unix(),
}
rl1 := store.RevocationList{
Id: id,
Number: "1",
IssuedAt: time.Now().UnixNano(),
ValidUntil: time.Now().Add(time.Minute).UnixNano(),
Certificates: []*store.RevocationListCertificate{cert1},
}
cert2 := &store.RevocationListCertificate{
Serial: "2",
ValidUntil: time.Now().Add(time.Hour).Unix(),
Expand Down Expand Up @@ -89,17 +101,13 @@ func TestUpdateRevocationList(t *testing.T) {
name: "valid - new document",
args: args{
query: store.UpdateRevocationListQuery{
IssuerID: id,
RevokedCertificates: []*store.RevocationListCertificate{cert1},
},
},
want: &store.RevocationList{
Id: id,
Number: "1",
Certificates: []*store.RevocationListCertificate{
cert1,
IssuerID: rl1.Id,
RevokedCertificates: rl1.Certificates,
IssuedAt: rl1.IssuedAt,
ValidUntil: rl1.ValidUntil,
},
},
want: &rl1,
},
{
name: "valid - add to existing document",
Expand All @@ -111,7 +119,7 @@ func TestUpdateRevocationList(t *testing.T) {
},
want: &store.RevocationList{
Id: id,
Number: "1",
Number: "2",
Certificates: []*store.RevocationListCertificate{
cert1,
cert2,
Expand All @@ -132,7 +140,7 @@ func TestUpdateRevocationList(t *testing.T) {
},
want: &store.RevocationList{
Id: id,
Number: "1",
Number: "2",
Certificates: []*store.RevocationListCertificate{
cert1,
cert2,
Expand Down Expand Up @@ -179,8 +187,85 @@ func TestUpdateRevocationList(t *testing.T) {
}
}

func TestParallelUpdateRevocationList(*testing.T) {
// TODO: run 10 parallel updates, they all should eventually succeed
func TestParallelUpdateRevocationList(t *testing.T) {
s, cleanUpStore := test.NewMongoStore(t)
defer cleanUpStore()

ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()

issuerID := uuid.NewString()
firstCount := 10
secondCount := 10
certificates := make([]*store.RevocationListCertificate, firstCount+secondCount)
for i := range firstCount + secondCount {
certificates[i] = test.GetCertificate(i, time.Now(), time.Now().Add(time.Hour))
}

// create or update
createOrUpdateRevocationList(ctx, t, 0, firstCount, certificates, issuerID, s)

rl, err := s.GetLatestIssuedOrIssueRevocationList(ctx, issuerID, time.Hour)
require.NoError(t, err)
require.NotEmpty(t, rl.IssuedAt)
require.NotEmpty(t, rl.ValidUntil)
expected := &store.RevocationList{
Id: issuerID,
Number: "1",
IssuedAt: rl.IssuedAt,
ValidUntil: rl.ValidUntil,
Certificates: certificates[:10],
}
test.CheckRevocationList(t, expected, rl, true)

createOrUpdateRevocationList(ctx, t, firstCount, secondCount, certificates, issuerID, s)

rl, err = s.GetLatestIssuedOrIssueRevocationList(ctx, issuerID, time.Hour)
require.NoError(t, err)
require.NotEmpty(t, rl.IssuedAt)
require.NotEmpty(t, rl.ValidUntil)
expected = &store.RevocationList{
Id: issuerID,
Number: "2",
IssuedAt: rl.IssuedAt,
ValidUntil: rl.ValidUntil,
Certificates: certificates,
}
test.CheckRevocationList(t, expected, rl, true)
}

func createOrUpdateRevocationList(ctx context.Context, t *testing.T, start, count int, certificates []*store.RevocationListCertificate, issuerID string, s *mongodb.Store) {
var failed atomic.Bool
failed.Store(false)
var wg sync.WaitGroup
wg.Add(10)
for i := start; i < start+count; i++ {
go func(index int) {
defer wg.Done()
cert := certificates[index]
var err error
// parallel execution should eventually succeed in cases when we get duplicate _id
// or not found errors
for range 100 {
q := &store.UpdateRevocationListQuery{
IssuerID: issuerID,
RevokedCertificates: []*store.RevocationListCertificate{cert},
}
_, err = s.UpdateRevocationList(ctx, q)
if errors.Is(err, store.ErrDuplicateID) || errors.Is(err, store.ErrNotFound) {
continue
}
if err == nil {
break
}
failed.Store(true)
assert.NoError(t, err)
}
assert.NoError(t, err)
}(i)
}
wg.Wait()
require.False(t, failed.Load())
}

func TestGetRevocationList(t *testing.T) {
Expand Down
9 changes: 4 additions & 5 deletions certificate-authority/store/mongodb/signingRecords.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"golang.org/x/exp/maps"
)

const signingRecordsCol = "signedCertificateRecords"
Expand Down Expand Up @@ -124,7 +125,7 @@ func (s *Store) RevokeSigningRecords(ctx context.Context, ownerID string, query
ids []string
certificates []*store.RevocationListCertificate
}
idFilter := []string{}
idFilter := make(map[string]struct{})
irs := make(map[string]issuersRecord)
err := s.LoadSigningRecords(ctx, ownerID, &pb.GetSigningRecordsRequest{
IdFilter: query.GetIdFilter(),
Expand All @@ -134,8 +135,8 @@ func (s *Store) RevokeSigningRecords(ctx context.Context, ownerID string, query
if credential == nil {
return nil
}
idFilter[v.GetId()] = struct{}{}
if credential.GetValidUntilDate() <= now {
idFilter = append(idFilter, v.GetId())
return nil
}
record := irs[credential.GetIssuerId()]
Expand All @@ -162,8 +163,6 @@ func (s *Store) RevokeSigningRecords(ctx context.Context, ownerID string, query
if err != nil {
return 0, err
}
// TODO
// idFilter = append(idFilter, serials...)
}

if len(idFilter) == 0 {
Expand All @@ -172,7 +171,7 @@ func (s *Store) RevokeSigningRecords(ctx context.Context, ownerID string, query

// delete the signing records
return s.DeleteSigningRecords(ctx, ownerID, &pb.DeleteSigningRecordsRequest{
IdFilter: idFilter,
IdFilter: maps.Keys(idFilter),
})
}

Expand Down
1 change: 1 addition & 0 deletions certificate-authority/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
var (
ErrNotSupported = errors.New("not supported")
ErrNotFound = errors.New("no document found")
ErrDuplicateID = errors.New("duplicate ID")
)

type (
Expand Down
7 changes: 7 additions & 0 deletions certificate-authority/test/revocationList.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"math/rand"
"sort"
"strconv"
"testing"
"time"
Expand Down Expand Up @@ -71,6 +72,12 @@ func CheckRevocationList(t *testing.T, expected, actual *store.RevocationList, i
require.Equal(t, expected.IssuedAt, actual.IssuedAt)
require.Equal(t, expected.ValidUntil, actual.ValidUntil)
require.Len(t, actual.Certificates, len(expected.Certificates))
sort.Slice(actual.Certificates, func(i, j int) bool {
return actual.Certificates[i].Serial < actual.Certificates[j].Serial
})
sort.Slice(expected.Certificates, func(i, j int) bool {
return expected.Certificates[i].Serial < expected.Certificates[j].Serial
})
for i := range actual.Certificates {
require.Equal(t, expected.Certificates[i].Serial, actual.Certificates[i].Serial)
require.Equal(t, expected.Certificates[i].ValidUntil, actual.Certificates[i].ValidUntil)
Expand Down

0 comments on commit 8983328

Please sign in to comment.