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 25, 2024
1 parent a61b9d4 commit 00a37d1
Show file tree
Hide file tree
Showing 28 changed files with 399 additions and 110 deletions.
2 changes: 1 addition & 1 deletion certificate-authority/service/cleanDatabase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func TestCertificateAuthorityServerCleanUpSigningRecords(t *testing.T) {
}()

ch := new(inprocgrpc.Channel)
ca, err := grpc.NewCertificateAuthorityServer(ownerClaim, config.HubID(), config.CERTIFICATE_AUTHORITY_HTTP_HOST, test.MakeConfig(t).Signer, storeDB, fileWatcher, logger)
ca, err := grpc.NewCertificateAuthorityServer(ownerClaim, config.HubID(), "https://"+config.CERTIFICATE_AUTHORITY_HTTP_HOST, test.MakeConfig(t).Signer, storeDB, fileWatcher, logger)
require.NoError(t, err)
defer ca.Close()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func TestCertificateAuthorityServerDeleteSigningRecords(t *testing.T) {
}()

ch := new(inprocgrpc.Channel)
ca, err := grpc.NewCertificateAuthorityServer(ownerClaim, config.HubID(), config.CERTIFICATE_AUTHORITY_HTTP_HOST, test.MakeConfig(t).Signer, store, fileWatcher, logger)
ca, err := grpc.NewCertificateAuthorityServer(ownerClaim, config.HubID(), "https://"+config.CERTIFICATE_AUTHORITY_HTTP_HOST, test.MakeConfig(t).Signer, store, fileWatcher, logger)
require.NoError(t, err)
defer ca.Close()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func TestCertificateAuthorityServerGetSigningRecords(t *testing.T) {
}()

ch := new(inprocgrpc.Channel)
ca, err := grpc.NewCertificateAuthorityServer(ownerClaim, config.HubID(), config.CERTIFICATE_AUTHORITY_HTTP_HOST, test.MakeConfig(t).Signer, store, fileWatcher, logger)
ca, err := grpc.NewCertificateAuthorityServer(ownerClaim, config.HubID(), "https://"+config.CERTIFICATE_AUTHORITY_HTTP_HOST, test.MakeConfig(t).Signer, store, fileWatcher, logger)
require.NoError(t, err)
defer ca.Close()

Expand Down
4 changes: 1 addition & 3 deletions certificate-authority/service/grpc/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ func TestReloadCerts(t *testing.T) {
store, closeStore := test.NewMongoStore(t)
defer closeStore()

logCfg := log.MakeDefaultConfig()
logCfg.Level = log.DebugLevel
logger := log.NewLogger(log.MakeDefaultConfig())

fileWatcher, err := fsnotify.NewWatcher(logger)
Expand Down Expand Up @@ -85,7 +83,7 @@ func TestReloadCerts(t *testing.T) {
err = s.Validate()
require.NoError(t, err)

ca, err := grpc.NewCertificateAuthorityServer(ownerClaim, config.HubID(), config.CERTIFICATE_AUTHORITY_HTTP_HOST, s, store, fileWatcher, logger)
ca, err := grpc.NewCertificateAuthorityServer(ownerClaim, config.HubID(), "https://"+config.CERTIFICATE_AUTHORITY_HTTP_HOST, s, store, fileWatcher, logger)
require.NoError(t, err)
defer ca.Close()

Expand Down
43 changes: 32 additions & 11 deletions certificate-authority/service/grpc/signCertificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,8 @@ func (s *CertificateAuthorityServer) getSigningRecord(ctx context.Context, signi
return originalSr, nil
}

func (s *CertificateAuthorityServer) updateSigningRecord(ctx context.Context, signingRecord *pb.SigningRecord) error {
// try to get previous signing record
prevSr, err := s.getSigningRecord(ctx, signingRecord)
if err != nil {
return err
}
if s.store.SupportsRevocationList() && prevSr != nil {
func (s *CertificateAuthorityServer) updateRevocationListForSigningRecord(ctx context.Context, sr, prevSr *pb.SigningRecord) error {
if prevSr != nil {
// revoke previous signing record
prevCred := prevSr.GetCredential()
if prevCred != nil {
Expand All @@ -113,10 +108,36 @@ func (s *CertificateAuthorityServer) updateSigningRecord(ctx context.Context, si
},
},
}
_, err = s.store.UpdateRevocationList(ctx, &query)
if err != nil {
return err
}
_, err := s.store.UpdateRevocationList(ctx, &query)
return err
}
return nil
}
cred := sr.GetCredential()
if cred != nil {
// create new RevocationList if it doesn't exist
err := s.store.InsertRevocationLists(ctx, &store.RevocationList{
Id: cred.GetIssuerId(),
Number: "1",
})
if errors.Is(err, store.ErrDuplicateID) {
return nil
}
return err
}
return nil
}

func (s *CertificateAuthorityServer) updateSigningRecord(ctx context.Context, signingRecord *pb.SigningRecord) error {
// try to get previous signing record
prevSr, err := s.getSigningRecord(ctx, signingRecord)
if err != nil {
return err
}
if s.store.SupportsRevocationList() {
err = s.updateRevocationListForSigningRecord(ctx, signingRecord, prevSr)
if err != nil {
return err
}
}
// upsert new one
Expand Down
2 changes: 1 addition & 1 deletion certificate-authority/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logg
}
closerFn.AddFunc(closeStore)

ca, err := grpcService.NewCertificateAuthorityServer(config.APIs.GRPC.Authorization.OwnerClaim, config.HubID, config.APIs.HTTP.Addr, config.Signer, dbStorage, fileWatcher, logger)
ca, err := grpcService.NewCertificateAuthorityServer(config.APIs.GRPC.Authorization.OwnerClaim, config.HubID, config.APIs.HTTP.ExternalAddress, config.Signer, dbStorage, fileWatcher, logger)
if err != nil {
closerFn.Execute()
return nil, fmt.Errorf("cannot create grpc certificate authority server: %w", err)
Expand Down
3 changes: 3 additions & 0 deletions certificate-authority/store/mongodb/revocationList.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ func (s *Store) InsertRevocationLists(ctx context.Context, rls ...*store.Revocat
documents = append(documents, rl)
}
_, err := s.Collection(revocationListCol).InsertMany(ctx, documents)
if err != nil && mongo.IsDuplicateKeyError(err) {
return fmt.Errorf("%w: %w", store.ErrDuplicateID, err)
}
return err
}

Expand Down
2 changes: 1 addition & 1 deletion certificate-authority/store/revocationList.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type RevocationList struct {
// Time until the issued CRL is valid in Unix nanoseconds timestamp format
ValidUntil int64 `bson:"validUntil"`
// List of certificates issued by the issuer
Certificates []*RevocationListCertificate `bson:"certificates"`
Certificates []*RevocationListCertificate `bson:"certificates,omitempty"`
}

func ParseBigInt(s string) (*big.Int, error) {
Expand Down
2 changes: 1 addition & 1 deletion certificate-authority/test/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (

func MakeHTTPConfig() service.HTTPConfig {
return service.HTTPConfig{
ExternalAddress: "https://" + config.CERTIFICATE_AUTHORITY_HOST,
ExternalAddress: "https://" + config.CERTIFICATE_AUTHORITY_HTTP_HOST,
Addr: config.CERTIFICATE_AUTHORITY_HTTP_HOST,
Server: config.MakeHttpServerConfig(),
}
Expand Down
34 changes: 19 additions & 15 deletions coap-gateway/service/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import (
pkgX509 "github.com/plgd-dev/hub/v2/pkg/security/x509"
)

type Interceptor = func(ctx context.Context, code codes.Code, path string) (context.Context, error)
type (
Interceptor = func(ctx context.Context, code codes.Code, path string) (context.Context, error)
VerifyByCRL = func(context.Context, *x509.Certificate, []string) error
)

func newAuthInterceptor() Interceptor {
return func(ctx context.Context, _ codes.Code, path string) (context.Context, error) {
Expand Down Expand Up @@ -80,12 +83,10 @@ func (s *Service) VerifyAndResolveDeviceID(tlsDeviceID, paramDeviceID string, cl
return deviceID, nil
}

func verifyChain(chain []*x509.Certificate, capool *x509.CertPool, identityPropertiesRequired bool) error {
func verifyChain(ctx context.Context, chain []*x509.Certificate, capool *x509.CertPool, identityPropertiesRequired bool, verifyByCRL VerifyByCRL) error {
if len(chain) == 0 {
return errors.New("certificate chain is empty")
}
panic(errors.New("here"))

certificate := chain[0]
intermediateCAPool := x509.NewCertPool()
for i := 1; i < len(chain); i++ {
Expand Down Expand Up @@ -118,22 +119,25 @@ func verifyChain(chain []*x509.Certificate, capool *x509.CertPool, identityPrope
return errors.New("the extended key usage field in the device certificate does not contain server authentication")
}

// verify CRL
if len(certificate.CRLDistributionPoints) > 0 {
return errors.New("failed to check certificate validity by CRL")
if identityPropertiesRequired {
_, err = coap.GetDeviceIDFromIdentityCertificate(certificate)
if err != nil {
return fmt.Errorf("the device ID is not part of the certificate's common name: %w", err)
}
}

if !identityPropertiesRequired {
return nil
}
_, err = coap.GetDeviceIDFromIdentityCertificate(certificate)
if err != nil {
return fmt.Errorf("the device ID is not part of the certificate's common name: %w", err)
if len(certificate.CRLDistributionPoints) > 0 {
if verifyByCRL == nil {
return errors.New("failed to check certificate validity by CRL")
}
if err = verifyByCRL(ctx, certificate, certificate.CRLDistributionPoints); err != nil {
return err
}
}
return nil
}

func MakeGetConfigForClient(tlsCfg *tls.Config, identityPropertiesRequired bool) tls.Config {
func MakeGetConfigForClient(ctx context.Context, tlsCfg *tls.Config, identityPropertiesRequired bool, verifyByCRL VerifyByCRL) tls.Config {
return tls.Config{
GetCertificate: tlsCfg.GetCertificate,
MinVersion: tlsCfg.MinVersion,
Expand All @@ -142,7 +146,7 @@ func MakeGetConfigForClient(tlsCfg *tls.Config, identityPropertiesRequired bool)
VerifyPeerCertificate: func(_ [][]byte, chains [][]*x509.Certificate) error {
var errs *multierror.Error
for _, chain := range chains {
err := verifyChain(chain, tlsCfg.ClientCAs, identityPropertiesRequired)
err := verifyChain(ctx, chain, tlsCfg.ClientCAs, identityPropertiesRequired, verifyByCRL)
if err == nil {
return nil
}
Expand Down
71 changes: 71 additions & 0 deletions coap-gateway/service/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//go:build test
// +build test

package service_test

import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"testing"
"time"

pbCA "github.com/plgd-dev/hub/v2/certificate-authority/pb"
coapgwTest "github.com/plgd-dev/hub/v2/coap-gateway/test"
pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc"
"github.com/plgd-dev/hub/v2/test"
"github.com/plgd-dev/hub/v2/test/config"
oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)

func TestCertificateWithCRL(t *testing.T) {
coapgwCfg := coapgwTest.MakeConfig(t)
coapgwCfg.APIs.COAP.TLS.Enabled = new(bool)
*coapgwCfg.APIs.COAP.TLS.Enabled = true
coapgwCfg.APIs.COAP.TLS.Embedded.ClientCertificateRequired = true
shutdown := setUp(t, coapgwCfg)
defer shutdown()

ctx, cancel := context.WithTimeout(context.Background(), time.Second*30*20)
defer cancel()
tokenWithoutDeviceID := oauthTest.GetDefaultAccessToken(t)
ctx = pkgGrpc.CtxWithToken(ctx, tokenWithoutDeviceID)
conn, err := grpc.NewClient(config.CERTIFICATE_AUTHORITY_HOST, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
RootCAs: test.GetRootCertificatePool(t),
})))
require.NoError(t, err)
caClient := pbCA.NewCertificateAuthorityClient(conn)

signerKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
cg := coapgwTest.NewCACertificateGenerator(caClient, signerKey)

crt, err := cg.GetIdentityCertificate(ctx, CertIdentity)
require.NoError(t, err)
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{crt},
InsecureSkipVerify: true,
}
co := testCoapDialWithHandler(t, makeTestCoapHandler(t), WithTLSConfig(tlsConfig))
require.NotEmpty(t, co)
testSignUp(t, CertIdentity, co)
_ = co.Close()

// revoke all certs for device
resp, err := caClient.DeleteSigningRecords(ctx, &pbCA.DeleteSigningRecordsRequest{
DeviceIdFilter: []string{CertIdentity},
})
require.NoError(t, err)
require.Equal(t, int64(1), resp.Count)
// sign-up with revoked cert should fail
co = testCoapDialWithHandler(t, makeTestCoapHandler(t), WithTLSConfig(tlsConfig))
require.NotEmpty(t, co)
_, err = doSignUp(t, CertIdentity, co)
_ = co.Close()
require.Error(t, err)
}
4 changes: 2 additions & 2 deletions coap-gateway/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,8 +621,8 @@ func (s *Service) createServices(fileWatcher *fsnotify.Watcher, logger log.Logge
coapService.WithOnNewConnection(s.coapConnOnNew),
coapService.WithOnInactivityConnection(s.onInactivityConnection),
coapService.WithMessagePool(s.messagePool),
coapService.WithOverrideTLS(func(cfg *tls.Config) *tls.Config {
tlsCfg := MakeGetConfigForClient(cfg, s.config.APIs.COAP.InjectedCOAPConfig.TLSConfig.IdentityPropertiesRequired)
coapService.WithOverrideTLS(func(cfg *tls.Config, verifyByCRL VerifyByCRL) *tls.Config {
tlsCfg := MakeGetConfigForClient(s.ctx, cfg, s.config.APIs.COAP.InjectedCOAPConfig.TLSConfig.IdentityPropertiesRequired, verifyByCRL)
return &tlsCfg
}),
)
Expand Down
8 changes: 4 additions & 4 deletions coap-gateway/service/signIn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
coapgwTest "github.com/plgd-dev/hub/v2/coap-gateway/test"
"github.com/plgd-dev/hub/v2/coap-gateway/uri"
"github.com/plgd-dev/hub/v2/grpc-gateway/pb"
kitNetGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc"
pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc"
test "github.com/plgd-dev/hub/v2/test"
"github.com/plgd-dev/hub/v2/test/config"
oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test"
Expand Down Expand Up @@ -73,7 +73,7 @@ func TestSignInDeviceSubscriptionHandler(t *testing.T) {
shutdown := setUp(t)
defer shutdown()

ctx := kitNetGrpc.CtxWithToken(context.Background(), oauthTest.GetDefaultAccessToken(t))
ctx := pkgGrpc.CtxWithToken(context.Background(), oauthTest.GetDefaultAccessToken(t))
conn, err := grpc.NewClient(config.GRPC_GW_HOST, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
RootCAs: test.GetRootCertificatePool(t),
})))
Expand Down Expand Up @@ -142,7 +142,7 @@ func TestDontCreateObservationAfterRefreshTokenAndSignIn(t *testing.T) {

h := makeTestCoapHandler(t)
observedPath := make(map[string]struct{})
co := testCoapDialWithHandler(t, "", true, true, time.Now().Add(time.Minute), func(w *responsewriter.ResponseWriter[*coapTcpClient.Conn], r *pool.Message) {
co := testCoapDialWithHandler(t, func(w *responsewriter.ResponseWriter[*coapTcpClient.Conn], r *pool.Message) {
if r.Code() != coapCodes.GET {
h(w, r)
return
Expand All @@ -160,7 +160,7 @@ func TestDontCreateObservationAfterRefreshTokenAndSignIn(t *testing.T) {
} else {
require.NoError(t, errors.New("cannot observe the same resource twice"))
}
})
}, WithGenerateTLS("", true, time.Now().Add(time.Minute)))
if co == nil {
return
}
Expand Down
Loading

0 comments on commit 00a37d1

Please sign in to comment.