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 27, 2024
1 parent 68ca5a5 commit 549b38e
Show file tree
Hide file tree
Showing 22 changed files with 394 additions and 455 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
12 changes: 8 additions & 4 deletions certificate-authority/service/grpc/deleteSigningRecords.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@ import (
"google.golang.org/grpc/status"
)

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

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))
}
n, err := s.store.DeleteSigningRecords(ctx, owner, req)
count, err := s.store.RevokeSigningRecords(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
9 changes: 6 additions & 3 deletions certificate-authority/service/http/requestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,27 @@ 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
cas *grpcService.CertificateAuthorityServer
store store.Store
logger log.Logger
}

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

r.HandleFunc(uri.SigningRevocationList, requestHandler.revocationList).Methods(http.MethodGet)
Expand Down
44 changes: 24 additions & 20 deletions certificate-authority/service/http/revocationList.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,67 +4,71 @@ import (
"crypto/ecdsa"
"crypto/rand"
"crypto/x509"
"errors"
"math/big"
"net/http"
"time"

"github.com/gorilla/mux"
"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"
pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc"
pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http"
pkgTime "github.com/plgd-dev/hub/v2/pkg/time"
"google.golang.org/grpc/codes"
)

var revocationListNumber = big.NewInt(0)
func errCannotGetRevocationList(err error) error {
return pkgGrpc.ForwardErrorf(codes.Internal, "cannot get revocation list: %v", err)
}

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)
requestHandler.logger.Errorf("invalid serial number string " + c.Serial)
continue
}
rles = append(rles, x509.RevocationListEntry{
template.RevokedCertificateEntries = append(template.RevokedCertificateEntries, x509.RevocationListEntry{
SerialNumber: &sn,
RevocationTime: pkgTime.Unix(0, c.Revocation),
})
}
return nil
})
// TODO: remove panics
if err != nil {
panic(err)
serverMux.WriteError(w, errCannotGetRevocationList(err))
return
}

issuingCert := requestHandler.cas.GetSigner().GetCertificate()
if issuingCert == nil {
panic("issuer certificate not set")
serverMux.WriteError(w, errCannotGetRevocationList(errors.New("issuer certificate not set")))
return
}
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))
if err != nil {
panic(err)
serverMux.WriteError(w, errCannotGetRevocationList(err))
return
}

w.Header().Set(pkgHttp.ContentTypeHeaderKey, "application/pkix-crl")
if _, err = w.Write(crl); err != nil {
panic(err)
serverMux.WriteError(w, errCannotGetRevocationList(err))
return
}
}
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)
}
2 changes: 1 addition & 1 deletion certificate-authority/service/http/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,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)
requestHandler, err := NewRequestHandler(&config, service.GetRouter(), ca, s, logger)
if err != nil {
_ = service.Close()
return nil, err
Expand Down
Loading

0 comments on commit 549b38e

Please sign in to comment.