Skip to content

Commit

Permalink
bridge: implement Credential Resource for CA Configuration during onb…
Browse files Browse the repository at this point in the history
…oarding
  • Loading branch information
jkralik committed Feb 1, 2024
1 parent afd0638 commit 7fa196d
Show file tree
Hide file tree
Showing 29 changed files with 936 additions and 92 deletions.
38 changes: 32 additions & 6 deletions bridge/device/cloud/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"crypto/x509"
"fmt"
"log"
goSync "sync"
"sync"
"time"

"github.com/google/uuid"
Expand All @@ -50,6 +50,7 @@ import (
type (
GetLinksFilteredBy func(endpoints schema.Endpoints, deviceIDfilter uuid.UUID, resourceTypesFitler []string, policyBitMaskFitler schema.BitMask) (links schema.ResourceLinks)
GetCertificates func(deviceID string) []tls.Certificate
RemoveCloudCAs func(cloudID ...string)
)

type Config struct {
Expand All @@ -62,18 +63,25 @@ type Config struct {
CloudURL string
}

type CAPoolGetter = interface {
IsValid() bool
GetPool() (*x509.CertPool, error)
}

type Manager struct {
handler net.RequestHandler
getLinks GetLinksFilteredBy
maxMessageSize uint32
deviceID uuid.UUID
save func()
caPool CAPool
getCertificates func(deviceID string) []tls.Certificate
caPool CAPoolGetter
getCertificates GetCertificates
removeCloudCAs RemoveCloudCAs

private struct {
mutex goSync.Mutex
cfg Configuration
mutex sync.Mutex
cfg Configuration
previousCloudIDs []string
}

creds ocfCloud.CoapSignUpResponse
Expand All @@ -84,7 +92,7 @@ type Manager struct {
trigger chan bool
}

func New(deviceID uuid.UUID, save func(), handler net.RequestHandler, getLinks GetLinksFilteredBy, caPool CAPool, opts ...Option) (*Manager, error) {
func New(deviceID uuid.UUID, save func(), handler net.RequestHandler, getLinks GetLinksFilteredBy, caPool CAPoolGetter, opts ...Option) (*Manager, error) {
if !caPool.IsValid() {
return nil, fmt.Errorf("invalid ca pool")
}
Expand All @@ -93,6 +101,9 @@ func New(deviceID uuid.UUID, save func(), handler net.RequestHandler, getLinks G
getCertificates: func(string) []tls.Certificate {
return nil
},
removeCloudCAs: func(...string) {
// do nothing
},
}
for _, opt := range opts {
opt(&o)
Expand All @@ -108,6 +119,7 @@ func New(deviceID uuid.UUID, save func(), handler net.RequestHandler, getLinks G
save: save,
caPool: caPool,
getCertificates: o.getCertificates,
removeCloudCAs: o.removeCloudCAs,
}
c.private.cfg.ProvisioningStatus = cloud.ProvisioningStatus_UNINITIALIZED
return c, nil
Expand Down Expand Up @@ -168,6 +180,7 @@ func (c *Manager) resetCredentials(ctx context.Context, signOff bool) {
log.Printf("cannot close connection: %v\n", err)
}
c.save()
c.removePreviousCloudIDs()
}

func (c *Manager) cleanup() {
Expand Down Expand Up @@ -216,9 +229,22 @@ func (c *Manager) Post(request *net.Request) (*pool.Message, error) {
return resources.CreateResponseContent(request.Context(), currentCfg, codes.Changed)
}

func (c *Manager) popPreviousCloudIDs() []string {
c.private.mutex.Lock()
defer c.private.mutex.Unlock()
previousCloudIDs := c.private.previousCloudIDs
c.private.previousCloudIDs = nil
return previousCloudIDs
}

func (c *Manager) removePreviousCloudIDs() {
c.removeCloudCAs(c.popPreviousCloudIDs()...)
}

func (c *Manager) setCloudConfiguration(cfg cloud.ConfigurationUpdateRequest) {
c.private.mutex.Lock()
defer c.private.mutex.Unlock()
c.private.previousCloudIDs = append(c.private.previousCloudIDs, c.private.cfg.CloudID)
c.private.cfg.AuthorizationProvider = cfg.AuthorizationProvider
c.private.cfg.CloudID = cfg.CloudID
c.private.cfg.URL = cfg.URL
Expand Down
7 changes: 5 additions & 2 deletions bridge/device/cloud/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func TestProvisioningOnDeviceRestart(t *testing.T) {
_ = s1.Shutdown()
})
deviceID := uuid.New().String()
d1 := bridgeTest.NewBridgedDevice(t, s1, true, deviceID)
d1 := bridgeTest.NewBridgedDevice(t, s1, deviceID, true, false)
s1Shutdown := bridgeTest.RunBridgeService(s1)
t.Cleanup(func() {
_ = s1Shutdown()
Expand Down Expand Up @@ -106,6 +106,9 @@ func TestProvisioningOnDeviceRestart(t *testing.T) {
require.Equal(t, cloudCfg.ProvisioningStatus, cloudSchema.ProvisioningStatus_REGISTERED)

// sign off
d2.UnregisterFromCloud()
cloudManager := d2.GetCloudManager()
if cloudManager != nil {
cloudManager.Unregister()
}
require.Equal(t, 1, ch.WaitForSignOff(time.Second*20))
}
7 changes: 7 additions & 0 deletions bridge/device/cloud/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package cloud
type OptionsCfg struct {
maxMessageSize uint32
getCertificates GetCertificates
removeCloudCAs RemoveCloudCAs
}

type Option func(*OptionsCfg)
Expand All @@ -38,3 +39,9 @@ func WithGetCertificates(getCertificates GetCertificates) Option {
o.getCertificates = getCertificates
}
}

func WithRemoveCloudCAs(removeCloudCA RemoveCloudCAs) Option {
return func(o *OptionsCfg) {
o.removeCloudCAs = removeCloudCA
}
}
4 changes: 2 additions & 2 deletions bridge/device/cloud/security.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ func MakeCAPool(getCAPool GetCAPool, useSystemCAPool bool) CAPool {
}
}

func (c *CAPool) IsValid() bool {
func (c CAPool) IsValid() bool {
return c.useSystemCAPool || (c.getCAPool != nil && c.getCAPool() != nil)
}

func (c *CAPool) GetPool() (*x509.CertPool, error) {
func (c CAPool) GetPool() (*x509.CertPool, error) {
var pool *x509.CertPool
if c.useSystemCAPool {
systemPool, err := x509.SystemCertPool()
Expand Down
7 changes: 7 additions & 0 deletions bridge/device/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,27 @@ import (

"github.com/google/uuid"
"github.com/plgd-dev/device/v2/bridge/device/cloud"
"github.com/plgd-dev/device/v2/bridge/device/credential"
)

type CloudConfig struct {
Enabled bool
cloud.Config
}

type CredentialConfig struct {
Enabled bool
credential.Config
}

type Config struct {
ID uuid.UUID
Name string
ProtocolIndependentID uuid.UUID
ResourceTypes []string
MaxMessageSize uint32
Cloud CloudConfig
Credential CredentialConfig
}

func (cfg *Config) Validate() error {
Expand Down
23 changes: 23 additions & 0 deletions bridge/device/credential/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/****************************************************************************
*
* Copyright (c) 2024 plgd.dev s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License"),
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************/

package credential

import "github.com/plgd-dev/device/v2/schema/credential"

type Config = credential.CredentialResponse
172 changes: 172 additions & 0 deletions bridge/device/credential/manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/****************************************************************************
*
* Copyright (c) 2024 plgd.dev s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License"),
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************/

package credential

import (
"crypto/x509"

"github.com/plgd-dev/device/v2/bridge/net"
"github.com/plgd-dev/device/v2/bridge/resources"
"github.com/plgd-dev/device/v2/pkg/codec/cbor"
pkgX509 "github.com/plgd-dev/device/v2/pkg/security/x509"
"github.com/plgd-dev/device/v2/schema/credential"
"github.com/plgd-dev/device/v2/schema/interfaces"
"github.com/plgd-dev/go-coap/v3/message/codes"
"github.com/plgd-dev/go-coap/v3/message/pool"
"github.com/plgd-dev/go-coap/v3/pkg/sync"
)

type Manager struct {
credentials *sync.Map[int, credential.Credential]
save func()
}

func New(save func()) *Manager {
if save == nil {
save = func() {
// do nothing
}
}
return &Manager{
save: save,
credentials: sync.NewMap[int, credential.Credential](),
}
}

func (m *Manager) GetCAPool() []*x509.Certificate {
certs := make([]*x509.Certificate, 0, m.credentials.Length())
m.credentials.Range(func(key int, value credential.Credential) bool {
if value.Type != credential.CredentialType_ASYMMETRIC_SIGNING_WITH_CERTIFICATE {
return true
}
if value.Usage != credential.CredentialUsage_TRUST_CA && value.Usage != credential.CredentialUsage_MFG_TRUST_CA {
return true
}
cas, err := pkgX509.ParsePemCertificates(value.PublicData.Data())
if err != nil {
return true
}
certs = append(certs, cas...)
return true
})
return certs
}

func (m *Manager) getNextID() int {
var id int
m.credentials.Range(func(key int, value credential.Credential) bool {
if key > id {
id = key
}
return true
})
return id + 1
}

func (m *Manager) add(c credential.Credential) {
for {
_, loaded := m.credentials.LoadOrStore(c.ID, c)
if !loaded {
return
}
c.ID = m.getNextID()
}
}

func (m *Manager) AddOrReplaceCredential(c credential.Credential) {
if c.Type == credential.CredentialType_EMPTY {
return
}
if c.ID != 0 {
// replace
m.credentials.Store(c.ID, c)
return
}
// add
m.add(c)
}

func (m *Manager) AddOrReplaceCredentials(ca ...credential.Credential) {
for _, c := range ca {
m.AddOrReplaceCredential(c)
}
}

func (m *Manager) RemoveCredentials(ids ...int) {
for _, id := range ids {
m.credentials.Delete(id)
}
}

func (m *Manager) RemoveCredentialsBySubjects(subjects ...string) {
m.credentials.Range(func(key int, value credential.Credential) bool {
for _, subject := range subjects {
if value.Subject == subject {
m.credentials.Delete(key)
}
}
return true
})
}

func (m *Manager) ClearCredentials() {
_ = m.credentials.LoadAndDeleteAll()
}

func (m *Manager) getRep(privateData bool) credential.CredentialResponse {
cas := m.credentials.CopyData()
creds := credential.CredentialResponse{
Interfaces: []string{interfaces.OC_IF_BASELINE, interfaces.OC_IF_RW},
ResourceTypes: []string{credential.ResourceType},
Credentials: make([]credential.Credential, 0, len(cas)),
}
for _, cred := range cas {
if !privateData {
// remove private data
cred.PrivateData = nil
}
creds.Credentials = append(creds.Credentials, cred)
}
return creds
}

func (m *Manager) Get(request *net.Request) (*pool.Message, error) {
creds := m.getRep(false)
return resources.CreateResponseContent(request.Context(), creds, codes.Content)
}

func (m *Manager) Post(request *net.Request) (*pool.Message, error) {
var cfg credential.CredentialUpdateRequest
err := cbor.ReadFrom(request.Body(), &cfg)
if err != nil {
return resources.CreateResponseBadRequest(request.Context(), err)
}
m.AddOrReplaceCredentials(cfg.Credentials...)
m.save()
creds := m.getRep(false)
return resources.CreateResponseContent(request.Context(), creds, codes.Changed)
}

func (m *Manager) ExportConfig() Config {
return m.getRep(true)
}

func (m *Manager) Close() {
m.ClearCredentials()
}
Loading

0 comments on commit 7fa196d

Please sign in to comment.