Skip to content

Commit

Permalink
add credential resource to allow configure CA during onboard bridge t…
Browse files Browse the repository at this point in the history
…he device
  • Loading branch information
jkralik committed Jan 31, 2024
1 parent c9ba38a commit 8a73731
Show file tree
Hide file tree
Showing 17 changed files with 312 additions and 59 deletions.
29 changes: 12 additions & 17 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 Down Expand Up @@ -73,7 +73,7 @@ type Manager struct {
getCertificates func(deviceID string) []tls.Certificate

private struct {
mutex goSync.Mutex
mutex sync.Mutex
cfg Configuration
}

Expand Down Expand Up @@ -351,26 +351,21 @@ func (c *Manager) dial(ctx context.Context) error {
_ = c.close()
cfg := c.getCloudConfiguration()

var verifyPeer func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
var rootCAs *x509.CertPool
cas := c.getCAPool()
if (len(cas)) > 0 {
rootCAs = x509.NewCertPool()
for _, ca := range cas {
rootCAs.AddCert(ca)
}
verifyPeer = coap.NewVerifyPeerCertificate(rootCAs, func(cert *x509.Certificate) error {
cloudID, err := uuid.Parse(c.getCloudConfiguration().CloudID)
if err != nil {
return fmt.Errorf("cannot parse cloudID: %w", err)
}
return coap.VerifyCloudCertificate(cert, cloudID)
})
rootCAs := x509.NewCertPool()
for _, ca := range cas {
rootCAs.AddCert(ca)
}
verifyPeer := coap.NewVerifyPeerCertificate(rootCAs, func(cert *x509.Certificate) error {
cloudID, err := uuid.Parse(c.getCloudConfiguration().CloudID)
if err != nil {
return fmt.Errorf("cannot parse cloudID: %w", err)
}
return coap.VerifyCloudCertificate(cert, cloudID)
})

tlsConfig := &tls.Config{
InsecureSkipVerify: true, //nolint:gosec
RootCAs: rootCAs,
Certificates: c.getCertificates(c.deviceID.String()),
VerifyPeerCertificate: verifyPeer,
}
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
141 changes: 141 additions & 0 deletions bridge/device/credential/manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/****************************************************************************
*
* 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"
"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"
coapSync "github.com/plgd-dev/go-coap/v3/pkg/sync"
"github.com/plgd-dev/kit/v2/security"
)

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

func New(save func()) *Manager {
if save == nil {
save = func() {}
}
return &Manager{
save: save,
credentials: coapSync.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 := security.ParseX509FromPEM(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) AddOrReplaceCredential(c credential.Credential) {
if c.Type == credential.CredentialType_EMPTY {
return
}
for {
_, loaded := m.credentials.LoadOrStore(c.ID, c)
if !loaded {
return
}
c.ID = m.getNextID()
}
}

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

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

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

func (m *Manager) getRep() 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 {
creds.Credentials = append(creds.Credentials, cred)
}
return creds
}

func (m *Manager) Get(request *net.Request) (*pool.Message, error) {
creds := m.getRep()
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()
return resources.CreateResponseContent(request.Context(), creds, codes.Changed)
}

func (m *Manager) ExportConfig() Config {
return m.getRep()
}
27 changes: 26 additions & 1 deletion bridge/device/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,22 @@ package device
import (
"bytes"
"context"
"crypto/x509"
"fmt"

"github.com/google/uuid"
"github.com/plgd-dev/device/v2/bridge/device/cloud"
"github.com/plgd-dev/device/v2/bridge/device/credential"
"github.com/plgd-dev/device/v2/bridge/net"
"github.com/plgd-dev/device/v2/bridge/resources"
cloudResource "github.com/plgd-dev/device/v2/bridge/resources/cloud"
resourcesDevice "github.com/plgd-dev/device/v2/bridge/resources/device"
"github.com/plgd-dev/device/v2/bridge/resources/discovery"
"github.com/plgd-dev/device/v2/bridge/resources/maintenance"
credentialResource "github.com/plgd-dev/device/v2/bridge/resources/secure/credential"
"github.com/plgd-dev/device/v2/schema"
cloudSchema "github.com/plgd-dev/device/v2/schema/cloud"
credentialSchema "github.com/plgd-dev/device/v2/schema/credential"
plgdDevice "github.com/plgd-dev/device/v2/schema/device"
maintenanceSchema "github.com/plgd-dev/device/v2/schema/maintenance"
plgdResources "github.com/plgd-dev/device/v2/schema/resources"
Expand All @@ -58,6 +62,7 @@ type Device struct {
cfg Config
resources *sync.Map[string, Resource]
cloudManager *cloud.Manager
credential *credential.Manager
onDeviceUpdated func(d *Device)
}

Expand All @@ -81,6 +86,13 @@ func (d *Device) ExportConfig() Config {
cfg := d.cfg
if d.cloudManager != nil {
cfg.Cloud.Config = d.cloudManager.ExportConfig()
} else {
cfg.Cloud.Enabled = false
}
if d.credential != nil {
cfg.Credential.Config = d.credential.ExportConfig()
} else {
cfg.Credential.Enabled = false
}
return cfg
}
Expand Down Expand Up @@ -146,7 +158,20 @@ func New(cfg Config, opts ...Option) *Device {
d.AddResource(maintenance.New(maintenanceSchema.ResourceURI, func() {
d.UnregisterFromCloud()
}))

if cfg.Credential.Enabled {
d.credential = credential.New(func() {
d.onDeviceUpdated(d)
})
d.AddResource(credentialResource.New(credentialSchema.ResourceURI, d.credential))
origGetCAPool := o.getCAPool
o.getCAPool = func() []*x509.Certificate {
cas := d.credential.GetCAPool()
if origGetCAPool != nil {
return append(cas, origGetCAPool()...)
}
return cas
}
}
if cfg.Cloud.Enabled {
opts := []cloud.Option{cloud.WithMaxMessageSize(cfg.MaxMessageSize)}
if o.getCAPool != nil {
Expand Down
25 changes: 20 additions & 5 deletions bridge/net/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,16 +366,31 @@ func New(cfg Config, handler RequestHandler) (*Net, error) {
return n, nil
}

func (n *Net) getNetwork(cm *net.ControlMessage, localHost, localPort string) string {
if cm != nil {
if cm.Dst.To4() == nil {
return UDP6
}
return UDP4
}
p := n.cfg.externalAddressesPort.filterByPort(localPort)
if len(p) == 1 {
return p[0].network
}
ip := gonet.ParseIP(localHost)
if ip.To4() == nil {
return UDP6
}
return UDP4
}

func (n *Net) GetEndpoints(cm *net.ControlMessage, localAddr string) schema.Endpoints {
_, localPort, err := gonet.SplitHostPort(localAddr)
localHost, localPort, err := gonet.SplitHostPort(localAddr)
if err != nil {
log.Printf("cannot get local address: %v", err)
return nil
}
network := UDP4
if cm.Dst.To4() == nil {
network = UDP6
}
network := n.getNetwork(cm, localHost, localPort)
filteredByNetwork := n.cfg.externalAddressesPort.filterByNetwork(network)
filtered := filteredByNetwork.filterByPort(localPort)
if len(filtered) == 0 {
Expand Down
2 changes: 1 addition & 1 deletion bridge/resources/cloud/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type Manager interface {

func New(uri string, m Manager) *Resource {
d := &Resource{}
d.Resource = resources.NewResource(uri, m.Get, m.Post, []string{plgdCloud.ResourceType}, []string{interfaces.OC_IF_BASELINE, interfaces.OC_IF_R})
d.Resource = resources.NewResource(uri, m.Get, m.Post, []string{plgdCloud.ResourceType}, []string{interfaces.OC_IF_BASELINE, interfaces.OC_IF_RW})
// don't publish cloud resource to cloud
d.PolicyBitMask &= ^resources.PublishToCloud
return d
Expand Down
Loading

0 comments on commit 8a73731

Please sign in to comment.