Skip to content

Commit efac6aa

Browse files
committed
fixup! Implement Certificate Revocation List
1 parent bf38565 commit efac6aa

File tree

8 files changed

+85
-16
lines changed

8 files changed

+85
-16
lines changed

certificate-authority/service/grpc/signer.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type Signer struct {
2525
hubID string
2626
crl struct {
2727
serverAddress string
28-
validFor time.Duration // TODO: schedule the CRL to be regenerated -> increment the Number, ThisUpdate and NextUpdate fields
28+
validFor time.Duration
2929
}
3030
}
3131

certificate-authority/service/http/requestHandler.go

+3-6
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,24 @@ import (
1313
"github.com/plgd-dev/hub/v2/certificate-authority/service/uri"
1414
"github.com/plgd-dev/hub/v2/certificate-authority/store"
1515
"github.com/plgd-dev/hub/v2/http-gateway/serverMux"
16-
"github.com/plgd-dev/hub/v2/pkg/log"
1716
)
1817

1918
// RequestHandler for handling incoming request
2019
type RequestHandler struct {
2120
config *Config
2221
mux *runtime.ServeMux
2322

24-
cas *grpcService.CertificateAuthorityServer
25-
store store.Store
26-
logger log.Logger
23+
cas *grpcService.CertificateAuthorityServer
24+
store store.Store
2725
}
2826

2927
// NewHTTP returns HTTP handler
30-
func NewRequestHandler(config *Config, r *mux.Router, cas *grpcService.CertificateAuthorityServer, s store.Store, logger log.Logger) (*RequestHandler, error) {
28+
func NewRequestHandler(config *Config, r *mux.Router, cas *grpcService.CertificateAuthorityServer, s store.Store) (*RequestHandler, error) {
3129
requestHandler := &RequestHandler{
3230
config: config,
3331
mux: serverMux.New(),
3432
cas: cas,
3533
store: s,
36-
logger: logger,
3734
}
3835

3936
if config.CRLEnabled {

certificate-authority/service/http/service.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func New(serviceName string, config Config, s store.Store, ca *grpcService.Certi
4949
return nil, fmt.Errorf("cannot create http service: %w", err)
5050
}
5151

52-
requestHandler, err := NewRequestHandler(&config, service.GetRouter(), ca, s, logger)
52+
requestHandler, err := NewRequestHandler(&config, service.GetRouter(), ca, s)
5353
if err != nil {
5454
_ = service.Close()
5555
return nil, err

certificate-authority/store/mongodb/revocationList.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package mongodb
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"math/big"
78
"time"
89

@@ -121,6 +122,9 @@ func (s *Store) UpdateRevocationList(ctx context.Context, query *store.UpdateRev
121122
Certificates: maps.Values(upd.certificatesToInsert),
122123
}
123124
if err = s.InsertRevocationLists(ctx, newRL); err != nil {
125+
if mongo.IsDuplicateKeyError(err) {
126+
return nil, fmt.Errorf("%w: %w", store.ErrDuplicateID, err)
127+
}
124128
return nil, err
125129
}
126130
return newRL, nil
@@ -157,7 +161,7 @@ func (s *Store) UpdateRevocationList(ctx context.Context, query *store.UpdateRev
157161
var updatedRL store.RevocationList
158162
if err = s.Collection(revocationListCol).FindOneAndUpdate(ctx, filter, update, opts).Decode(&updatedRL); err != nil {
159163
if errors.Is(err, mongo.ErrNoDocuments) {
160-
return nil, store.ErrNotFound
164+
return nil, fmt.Errorf("%w: %w", store.ErrNotFound, err)
161165
}
162166
return nil, err
163167
}

certificate-authority/store/mongodb/revocationList_test.go

+63-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@ package mongodb_test
22

33
import (
44
"context"
5+
"errors"
6+
"sync"
7+
"sync/atomic"
58
"testing"
69
"time"
710

811
"github.com/google/uuid"
912
"github.com/plgd-dev/hub/v2/certificate-authority/store"
1013
"github.com/plgd-dev/hub/v2/certificate-authority/test"
14+
"github.com/stretchr/testify/assert"
1115
"github.com/stretchr/testify/require"
16+
"golang.org/x/exp/maps"
1217
)
1318

1419
func TestUpdateRevocationList(t *testing.T) {
@@ -179,8 +184,64 @@ func TestUpdateRevocationList(t *testing.T) {
179184
}
180185
}
181186

182-
func TestParallelUpdateRevocationList(*testing.T) {
183-
// TODO: run 10 parallel updates, they all should eventually succeed
187+
func TestParallelUpdateRevocationList(t *testing.T) {
188+
s, cleanUpStore := test.NewMongoStore(t)
189+
defer cleanUpStore()
190+
191+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
192+
defer cancel()
193+
194+
issuerID := uuid.NewString()
195+
count := 10
196+
certificates := make(map[int]*store.RevocationListCertificate)
197+
for i := range count {
198+
certificates[i] = test.GetCertificate(i, time.Now(), time.Now().Add(time.Hour))
199+
}
200+
201+
var failed atomic.Bool
202+
failed.Store(false)
203+
var wg sync.WaitGroup
204+
wg.Add(10)
205+
for i := range count {
206+
go func(index int) {
207+
defer wg.Done()
208+
cert, ok := certificates[index]
209+
assert.True(t, ok)
210+
var err error
211+
for range 100 { // parallel execution should eventually succeed in cases when we get duplicate _id and
212+
q := &store.UpdateRevocationListQuery{
213+
IssuerID: issuerID,
214+
RevokedCertificates: []*store.RevocationListCertificate{cert},
215+
}
216+
_, err = s.UpdateRevocationList(ctx, q)
217+
if errors.Is(err, store.ErrDuplicateID) || errors.Is(err, store.ErrNotFound) {
218+
continue
219+
}
220+
if err == nil {
221+
break
222+
}
223+
failed.Store(true)
224+
assert.NoError(t, err)
225+
}
226+
assert.NoError(t, err)
227+
}(i)
228+
}
229+
wg.Wait()
230+
require.False(t, failed.Load())
231+
232+
rl, err := s.GetLatestIssuedOrIssueRevocationList(ctx, issuerID, time.Hour)
233+
require.NoError(t, err)
234+
235+
require.NotEmpty(t, rl.IssuedAt)
236+
require.NotEmpty(t, rl.ValidUntil)
237+
expected := &store.RevocationList{
238+
Id: issuerID,
239+
Number: "1",
240+
IssuedAt: rl.IssuedAt,
241+
ValidUntil: rl.ValidUntil,
242+
Certificates: maps.Values(certificates),
243+
}
244+
test.CheckRevocationList(t, expected, rl, true)
184245
}
185246

186247
func TestGetRevocationList(t *testing.T) {

certificate-authority/store/mongodb/signingRecords.go

+4-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"go.mongodb.org/mongo-driver/bson"
1212
"go.mongodb.org/mongo-driver/mongo"
1313
"go.mongodb.org/mongo-driver/mongo/options"
14+
"golang.org/x/exp/maps"
1415
)
1516

1617
const signingRecordsCol = "signedCertificateRecords"
@@ -124,7 +125,7 @@ func (s *Store) RevokeSigningRecords(ctx context.Context, ownerID string, query
124125
ids []string
125126
certificates []*store.RevocationListCertificate
126127
}
127-
idFilter := []string{}
128+
idFilter := make(map[string]struct{})
128129
irs := make(map[string]issuersRecord)
129130
err := s.LoadSigningRecords(ctx, ownerID, &pb.GetSigningRecordsRequest{
130131
IdFilter: query.GetIdFilter(),
@@ -134,8 +135,8 @@ func (s *Store) RevokeSigningRecords(ctx context.Context, ownerID string, query
134135
if credential == nil {
135136
return nil
136137
}
138+
idFilter[v.GetId()] = struct{}{}
137139
if credential.GetValidUntilDate() <= now {
138-
idFilter = append(idFilter, v.GetId())
139140
return nil
140141
}
141142
record := irs[credential.GetIssuerId()]
@@ -162,8 +163,6 @@ func (s *Store) RevokeSigningRecords(ctx context.Context, ownerID string, query
162163
if err != nil {
163164
return 0, err
164165
}
165-
// TODO
166-
// idFilter = append(idFilter, serials...)
167166
}
168167

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

173172
// delete the signing records
174173
return s.DeleteSigningRecords(ctx, ownerID, &pb.DeleteSigningRecordsRequest{
175-
IdFilter: idFilter,
174+
IdFilter: maps.Keys(idFilter),
176175
})
177176
}
178177

certificate-authority/store/store.go

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
var (
1414
ErrNotSupported = errors.New("not supported")
1515
ErrNotFound = errors.New("no document found")
16+
ErrDuplicateID = errors.New("duplicate ID")
1617
)
1718

1819
type (

certificate-authority/test/revocationList.go

+7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"math/rand"
7+
"sort"
78
"strconv"
89
"testing"
910
"time"
@@ -71,6 +72,12 @@ func CheckRevocationList(t *testing.T, expected, actual *store.RevocationList, i
7172
require.Equal(t, expected.IssuedAt, actual.IssuedAt)
7273
require.Equal(t, expected.ValidUntil, actual.ValidUntil)
7374
require.Len(t, actual.Certificates, len(expected.Certificates))
75+
sort.Slice(actual.Certificates, func(i, j int) bool {
76+
return actual.Certificates[i].Serial < actual.Certificates[j].Serial
77+
})
78+
sort.Slice(expected.Certificates, func(i, j int) bool {
79+
return expected.Certificates[i].Serial < expected.Certificates[j].Serial
80+
})
7481
for i := range actual.Certificates {
7582
require.Equal(t, expected.Certificates[i].Serial, actual.Certificates[i].Serial)
7683
require.Equal(t, expected.Certificates[i].ValidUntil, actual.Certificates[i].ValidUntil)

0 commit comments

Comments
 (0)