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 Sep 26, 2024
1 parent 68ca5a5 commit 1b116e2
Show file tree
Hide file tree
Showing 20 changed files with 334 additions and 445 deletions.
31 changes: 23 additions & 8 deletions certificate-authority/pb/signingRecords.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pb
import (
"errors"
"fmt"
"math/big"
"sort"

"github.com/google/uuid"
Expand All @@ -17,6 +18,26 @@ func (p SigningRecords) Sort() {
})
}

func (credential *CredentialStatus) Validate() error {
if credential.GetDate() == 0 {
return errors.New("empty signing credential date")
}
if credential.GetValidUntilDate() == 0 {
return errors.New("empty signing record credential expiration date")
}
if credential.GetCertificatePem() == "" {
return errors.New("empty signing record credential certificate")
}
serial := big.Int{}
if _, ok := serial.SetString(credential.GetSerial(), 10); !ok {
return errors.New("invalid signing record credential certificate serial number")
}
if credential.GetIssuerId() == "" {
return errors.New("empty signing record credential issuer's ID")
}
return nil
}

func (signingRecord *SigningRecord) Marshal() ([]byte, error) {
return proto.Marshal(signingRecord)
}
Expand Down Expand Up @@ -44,14 +65,8 @@ func (signingRecord *SigningRecord) Validate() error {
return errors.New("empty signing record owner")
}
credential := signingRecord.GetCredential()
if credential != nil && credential.GetDate() == 0 {
return errors.New("empty signing credential date")
}
if credential != nil && credential.GetValidUntilDate() == 0 {
return errors.New("empty signing record credential expiration date")
}
if credential != nil && credential.GetCertificatePem() == "" {
return errors.New("empty signing record credential certificate")
if credential != nil {
return credential.Validate()
}
return nil
}
3 changes: 3 additions & 0 deletions certificate-authority/service/cleanDatabase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"math/big"
"testing"
"time"

Expand Down Expand Up @@ -48,6 +49,8 @@ func TestCertificateAuthorityServerCleanUpSigningRecords(t *testing.T) {
CertificatePem: "certificate1",
Date: date.UnixNano(),
ValidUntilDate: date.UnixNano(),
Serial: big.NewInt(42).String(),
IssuerId: "issuerID",
},
}

Expand Down
71 changes: 67 additions & 4 deletions certificate-authority/service/grpc/deleteSigningRecords.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,85 @@ package grpc

import (
"context"
"time"

"github.com/plgd-dev/hub/v2/certificate-authority/pb"
"github.com/plgd-dev/hub/v2/certificate-authority/store"
"github.com/plgd-dev/hub/v2/certificate-authority/store/mongodb"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

func errDeleteSigningRecords(err error) error {
return status.Errorf(codes.InvalidArgument, "cannot delete signing records: %v", err)
}

func (s *CertificateAuthorityServer) revokeSigningRecords(ctx context.Context, owner string, req *pb.DeleteSigningRecordsRequest) (int64, error) {
revokedAt := time.Now().UnixNano()
// get signing records to be deleted
type issuersRecord struct {
ids []string
certificates []*store.RevocationListCertificate
}
irs := make(map[string]issuersRecord)
err := s.store.LoadSigningRecords(ctx, owner, &pb.GetSigningRecordsRequest{
IdFilter: req.GetIdFilter(),
DeviceIdFilter: req.GetDeviceIdFilter(),
}, func(v *pb.SigningRecord) error {
credential := v.GetCredential()
if credential == nil {
return nil
}
record := irs[credential.GetIssuerId()]
record.ids = append(record.ids, v.GetId())
record.certificates = append(record.certificates, &store.RevocationListCertificate{
Serial: credential.GetSerial(),
Expiration: credential.GetValidUntilDate(),
Revocation: revokedAt,
})
irs[credential.GetIssuerId()] = record
return nil
})
if err != nil {
return 0, err
}
if len(irs) == 0 {
return 0, nil
}

// add certificates for the signing records to revocation lists
idFilter := []string{}
for issuerID, record := range irs {
err = s.store.RevokeCertificates(ctx, issuerID, record.certificates...)
if err != nil {
s.logger.Errorf("failed to revoke certificates for issuer(%v)", issuerID)
continue
}
idFilter = append(idFilter, record.ids...)
}

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

func (s *CertificateAuthorityServer) DeleteSigningRecords(ctx context.Context, req *pb.DeleteSigningRecordsRequest) (*pb.DeletedSigningRecords, error) {
owner, err := ownerToUUID(ctx, s.ownerClaim)
if err != nil {
return nil, s.logger.LogAndReturnError(status.Errorf(codes.InvalidArgument, "cannot delete signing records: %v", err))
return nil, s.logger.LogAndReturnError(errDeleteSigningRecords(err))
}
var count int64
_, isMongoDB := s.store.(*mongodb.Store)
if isMongoDB {
count, err = s.revokeSigningRecords(ctx, owner, req)
} else {
count, err = s.store.DeleteSigningRecords(ctx, owner, req)
}
n, err := s.store.DeleteSigningRecords(ctx, owner, req)
if err != nil {
return nil, s.logger.LogAndReturnError(status.Errorf(codes.InvalidArgument, "cannot delete signing records: %v", err))
return nil, s.logger.LogAndReturnError(errDeleteSigningRecords(err))
}
return &pb.DeletedSigningRecords{
Count: n,
Count: count,
}, nil
}
11 changes: 6 additions & 5 deletions certificate-authority/service/grpc/deleteSigningRecords_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package grpc_test

import (
"context"
"math/big"
"testing"

"github.com/fullstorydev/grpchan/inprocgrpc"
Expand Down Expand Up @@ -31,16 +32,17 @@ func TestCertificateAuthorityServerDeleteSigningRecords(t *testing.T) {
CertificatePem: "certificate1",
Date: constDate().UnixNano(),
ValidUntilDate: constDate().UnixNano(),
Serial: big.NewInt(42).String(),
IssuerId: "issuerId",
},
}
type args struct {
req *pb.DeleteSigningRecordsRequest
}
tests := []struct {
name string
args args
want int64
wantErr bool
name string
args args
want int64
}{
{
name: "invalidID",
Expand All @@ -49,7 +51,6 @@ func TestCertificateAuthorityServerDeleteSigningRecords(t *testing.T) {
IdFilter: []string{"invalidID"},
},
},
wantErr: true,
},
{
name: "valid",
Expand Down
16 changes: 4 additions & 12 deletions certificate-authority/service/grpc/getSigningRecords.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package grpc

import (
"context"

"github.com/plgd-dev/hub/v2/certificate-authority/pb"
"github.com/plgd-dev/hub/v2/certificate-authority/store"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
Expand All @@ -14,16 +11,11 @@ func (s *CertificateAuthorityServer) GetSigningRecords(req *pb.GetSigningRecords
if err != nil {
return s.logger.LogAndReturnError(status.Errorf(codes.InvalidArgument, "cannot get signing records: %v", err))
}
err = s.store.LoadSigningRecords(srv.Context(), owner, req, func(ctx context.Context, iter store.SigningRecordIter) (err error) {
for {
var sub pb.SigningRecord
if ok := iter.Next(ctx, &sub); !ok {
return iter.Err()
}
if err = srv.Send(&sub); err != nil {
return err
}
err = s.store.LoadSigningRecords(srv.Context(), owner, req, func(sr *pb.SigningRecord) (err error) {
if err = srv.Send(sr); err != nil {
return err
}
return nil
})
if err != nil {
return s.logger.LogAndReturnError(status.Errorf(codes.InvalidArgument, "cannot get signing records: %v", err))
Expand Down
3 changes: 3 additions & 0 deletions certificate-authority/service/grpc/getSigningRecords_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"io"
"math/big"
"testing"
"time"

Expand Down Expand Up @@ -39,6 +40,8 @@ func TestCertificateAuthorityServerGetSigningRecords(t *testing.T) {
CertificatePem: "certificate1",
Date: constDate().UnixNano(),
ValidUntilDate: constDate().UnixNano(),
Serial: big.NewInt(42).String(),
IssuerId: "issuerId",
},
}
type args struct {
Expand Down
18 changes: 6 additions & 12 deletions certificate-authority/service/grpc/signCertificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,11 @@ func (s *CertificateAuthorityServer) updateSigningIdentityCertificateRecord(ctx
now := time.Now().UnixNano()
err := s.store.LoadSigningRecords(ctx, updateSigningRecord.GetOwner(), &store.SigningRecordsQuery{
CommonNameFilter: []string{updateSigningRecord.GetCommonName()},
}, func(ctx context.Context, iter store.SigningRecordIter) (err error) {
for {
var signingRecord pb.SigningRecord
ok := iter.Next(ctx, &signingRecord)
if !ok {
break
}
if updateSigningRecord.GetPublicKey() != signingRecord.GetPublicKey() && signingRecord.GetCredential().GetValidUntilDate() > now {
return fmt.Errorf("common name %v with different public key fingerprint exist", signingRecord.GetCommonName())
}
found = true
}, func(sr *store.SigningRecord) (err error) {
if updateSigningRecord.GetPublicKey() != sr.GetPublicKey() && sr.GetCredential().GetValidUntilDate() > now {
return fmt.Errorf("common name %v with different public key fingerprint exist", sr.GetCommonName())
}
found = true
return nil
})
if err != nil {
Expand Down Expand Up @@ -125,13 +118,14 @@ func (s *CertificateAuthorityServer) SignCertificate(ctx context.Context, req *p
logger.With("crt", string(cert)).Debugf("CertificateAuthorityServer.SignCertificate")
replacedCredential := replacedRecord.GetCredential()
if replacedRecord != nil {
err = s.store.AddRevocationListCertificate(ctx, replacedCredential.GetIssuerId(), &store.RevocationListCertificate{
err = s.store.RevokeCertificates(ctx, replacedCredential.GetIssuerId(), &store.RevocationListCertificate{
Serial: replacedCredential.GetSerial(),
Expiration: replacedCredential.GetValidUntilDate(),
Revocation: time.Now().UnixNano(),
})
if err != nil {
// TODO: what to do here? remove the new signing record? restore the original?
panic(err)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,14 @@ func (s *CertificateAuthorityServer) SignIdentityCertificate(ctx context.Context
logger.With("crt", string(cert)).Debugf("CertificateAuthorityServer.SignIdentityCertificate")
replacedCredential := replacedRecord.GetCredential()
if replacedCredential != nil {
err = s.store.AddRevocationListCertificate(ctx, replacedCredential.GetIssuerId(), &store.RevocationListCertificate{
err = s.store.RevokeCertificates(ctx, replacedCredential.GetIssuerId(), &store.RevocationListCertificate{
Serial: replacedCredential.GetSerial(),
Expiration: replacedCredential.GetValidUntilDate(),
Revocation: time.Now().UnixNano(),
})
if err != nil {
// TODO: what to do here? remove the new signing record? restore the original?
panic(err)
}
}

Expand Down
25 changes: 11 additions & 14 deletions certificate-authority/service/http/revocationList.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,25 @@ import (
pkgTime "github.com/plgd-dev/hub/v2/pkg/time"
)

var revocationListNumber = big.NewInt(0)

func (requestHandler *RequestHandler) revocationList(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
issuerID := vars[uri.IssuerIDKey]

var rles []x509.RevocationListEntry
err := requestHandler.store.GetRevokedCertificates(r.Context(), store.CertificatesQuery{
template := &x509.RevocationList{
NextUpdate: time.Now().Add(time.Minute * 10), // TODO: pridat konfiguraciu, default napr. 10min
}
err := requestHandler.store.GetRevocationLists(r.Context(), store.RevocationListsQuery{
IssuerIdFilter: []string{issuerID},
}, func(rl *store.RevocationList) error {
template.Number = big.NewInt(rl.Number)
template.ThisUpdate = pkgTime.Unix(0, rl.UpdatedAt)
for _, c := range rl.Certificates {
var sn big.Int
_, ok := sn.SetString(c.Serial, 10)
if !ok {
panic("invalid serial number string " + c.Serial)
}
rles = append(rles, x509.RevocationListEntry{
template.RevokedCertificateEntries = append(template.RevokedCertificateEntries, x509.RevocationListEntry{
SerialNumber: &sn,
RevocationTime: pkgTime.Unix(0, c.Revocation),
})
Expand All @@ -43,19 +45,14 @@ func (requestHandler *RequestHandler) revocationList(w http.ResponseWriter, r *h
panic(err)
}

if len(template.RevokedCertificateEntries) == 0 {
return
}

issuingCert := requestHandler.cas.GetSigner().GetCertificate()
if issuingCert == nil {
panic("issuer certificate not set")
}
now := time.Now()
template := &x509.RevocationList{
RevokedCertificateEntries: rles,
Number: revocationListNumber,
ThisUpdate: now,
NextUpdate: now.Add(time.Minute * 10), // TODO: pridat konfiguraciu, default napr. 10min
}
// TODO: store CRLs in DB and only increase the number if a new CRL has been generated
revocationListNumber.Add(revocationListNumber, big.NewInt(1))

signer := requestHandler.cas.GetSigner()
crl, err := x509.CreateRevocationList(rand.Reader, template, issuingCert, signer.GetPrivateKey().(*ecdsa.PrivateKey))
Expand Down
5 changes: 1 addition & 4 deletions certificate-authority/service/http/revocationList_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"time"

certAuthURI "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/certificate-authority/test"
httpgwTest "github.com/plgd-dev/hub/v2/http-gateway/test"
"github.com/plgd-dev/hub/v2/pkg/config/database"
Expand Down Expand Up @@ -38,8 +37,6 @@ func TestRevocationList(t *testing.T) {
ctx = pkgGrpc.CtxWithToken(ctx, token)

test.AddRevocationListToStore(ctx, t, s, time.Now())
err := s.RevokeCertificates(ctx, store.CertificatesQuery{})
require.NoError(t, err)

request := httpgwTest.NewRequest(http.MethodGet, certAuthURI.SigningRevocationList, nil).Host(config.CERTIFICATE_AUTHORITY_HTTP_HOST).AuthToken(token).AddIssuerID(test.GetIssuerID(2)).Build()
httpResp := httpgwTest.HTTPDo(t, request)
Expand All @@ -51,5 +48,5 @@ func TestRevocationList(t *testing.T) {
_, err = x509.ParseRevocationList(respBody)
require.NoError(t, err)

time.Sleep(time.Minute)
// time.Sleep(time.Minute)
}
Loading

0 comments on commit 1b116e2

Please sign in to comment.