Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ When running, please provide following format:
| p | port | "5683" |
| d | data to be sent in POST or PUT | "" |
| cf | content format | 50 (JSON format) |
|tls | dtls support | false |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

align


The dtls configuration currently supports PKI certificates. Place the relevant certificates in the certs folder before building.
## Examples:

```bash
coap-cli get channels/0bb5ba61-a66e-4972-bab6-26f19962678f/messages/subtopic -auth 1e1017e6-dee7-45b4-8a13-00e6afeb66eb -o
coap-cli get channels/0bb5ba61-a66e-4972-bab6-26f19962678f/messages/subtopic -auth 1e1017e6-dee7-45b4-8a13-00e6afeb66eb -o -tls
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add seperate example for dtls

```
```bash
coap-cli post channels/0bb5ba61-a66e-4972-bab6-26f19962678f/messages/subtopic -auth 1e1017e6-dee7-45b4-8a13-00e6afeb66eb -d "hello world"
Expand Down
145 changes: 145 additions & 0 deletions cert_utility/utility.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package cert_utility
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename package name to follow guidelines https://go.dev/blog/package-names


import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"log"
"os"
"strings"

piondtls "github.com/pion/dtls/v2"
)

// Create pion dtls config from certificates.
func CreateClientConfig(ctx context.Context) (*piondtls.Config, error) {
clientKeyBytes := make([]byte, 2048)
clientCrtBytes := make([]byte, 2048)
caBytes := make([]byte, 2048)

kb, err := os.Open("../certs/client.key")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use os.ReadFile and make file paths configurable.

if err != nil {
log.Fatal(err)
}
defer kb.Close()
kb.Read(clientKeyBytes)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle errors


cb, err := os.Open("../certs/client.crt")
if err != nil {
log.Fatal(err)
}
defer cb.Close()
cb.Read(clientCrtBytes)

cab, err := os.Open("../certs/ca.crt")
if err != nil {
log.Fatal(err)
}
defer cab.Close()
cab.Read(caBytes)

certificate, err := LoadKeyAndCertificate(clientKeyBytes, clientCrtBytes)
if err != nil {
return nil, err
}
// cert pool
certPool, err := LoadCertPool(caBytes)
if err != nil {
return nil, err
}

return &piondtls.Config{
Certificates: []tls.Certificate{*certificate},
ExtendedMasterSecret: piondtls.RequireExtendedMasterSecret,
RootCAs: certPool,
InsecureSkipVerify: true,
}, nil
}

func LoadCertificate(certBytes []byte) (*tls.Certificate, error) {
var certificate tls.Certificate

for {
block, rest := pem.Decode(certBytes)
if block == nil {
break
}

if block.Type != "CERTIFICATE" {
return nil, errors.New("block is not a certificate, unable to load certificates")
}

certificate.Certificate = append(certificate.Certificate, block.Bytes)
certBytes = rest
}

if len(certificate.Certificate) == 0 {
return nil, errors.New("no certificate found, unable to load certificates")
}

return &certificate, nil
}

func LoadKey(keyBytes []byte) (crypto.PrivateKey, error) {
block, _ := pem.Decode(keyBytes)
if block == nil || !strings.HasSuffix(block.Type, "PRIVATE KEY") {
return nil, errors.New("block is not a private key, unable to load key")
}

if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
return key, nil
}

if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
switch key := key.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey:
return key, nil
default:
return nil, errors.New("unknown key time in PKCS#8 wrapping, unable to load key")
}
}

if key, err := x509.ParseECPrivateKey(block.Bytes); err == nil {
return key, nil
}

return nil, errors.New("no private key found, unable to load key")
}

// LoadKeyAndCertificate loads client certificate
func LoadKeyAndCertificate(keyBytes []byte, certBytes []byte) (*tls.Certificate, error) {
certificate, err := LoadCertificate(certBytes)
if err != nil {
return nil, err
}
key, err := LoadKey(keyBytes)
if err != nil {
return nil, err
}
certificate.PrivateKey = key
return certificate, nil
}

// LoadCertPool loads cert pool from ca certificate
func LoadCertPool(caBytes []byte) (*x509.CertPool, error) {
rootCertificate, err := LoadCertificate(caBytes)
if err != nil {
return nil, err
}
certPool := x509.NewCertPool()
for _, certBytes := range rootCertificate.Certificate {
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
certPool = nil
return nil, err
}
certPool.AddCert(cert)
}

return certPool, nil
}
3 changes: 3 additions & 0 deletions certs/ca.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN CERTIFICATE-----
^
-----END CERTIFICATE-----
3 changes: 3 additions & 0 deletions certs/client.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN CERTIFICATE-----
^
-----END CERTIFICATE-----
3 changes: 3 additions & 0 deletions certs/client.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN CERTIFICATE-----
^
-----END CERTIFICATE-----
26 changes: 15 additions & 11 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import (
"syscall"

coap "github.com/mainflux/coap-cli/coap"
"github.com/plgd-dev/go-coap/v2/message"
coapmsg "github.com/plgd-dev/go-coap/v2/message"
"github.com/plgd-dev/go-coap/v2/message/codes"
"github.com/plgd-dev/go-coap/v2/udp/message/pool"

"github.com/plgd-dev/go-coap/v3/message"
coapmsg "github.com/plgd-dev/go-coap/v3/message"
"github.com/plgd-dev/go-coap/v3/message/codes"
"github.com/plgd-dev/go-coap/v3/message/pool"
)

const (
Expand All @@ -36,9 +37,10 @@ mathod: get, put, post or delete
-p port (default: "5683")
-d data to be sent in POST or PUT (default: "")
-cf content format (default: 50 - JSON format))

-tls use DTLS (default: false)
The current implementation of DTLS uses PKI certificates. Please generate certificates and place them in certs folder.
Examples:
coap-cli get channels/0bb5ba61-a66e-4972-bab6-26f19962678f/messages/subtopic -auth 1e1017e6-dee7-45b4-8a13-00e6afeb66eb -o
coap-cli get channels/0bb5ba61-a66e-4972-bab6-26f19962678f/messages/subtopic -auth 1e1017e6-dee7-45b4-8a13-00e6afeb66eb -o -tls
coap-cli post channels/0bb5ba61-a66e-4972-bab6-26f19962678f/messages/subtopic -auth 1e1017e6-dee7-45b4-8a13-00e6afeb66eb -d "hello world"
coap-cli post channels/0bb5ba61-a66e-4972-bab6-26f19962678f/messages/subtopic -auth 1e1017e6-dee7-45b4-8a13-00e6afeb66eb -d "hello world" -h 0.0.0.0 -p 1234
`
Expand All @@ -55,7 +57,7 @@ func parseCode(code string) (codes.Code, error) {
case delete:
return codes.DELETE, nil
}
return 0, errors.New("Message can be GET, POST, PUT or DELETE")
return 0, errors.New("MESSAGE CAN BE GET, POST, PUT OR DELETE")
}

func printMsg(m *pool.Message) {
Expand All @@ -70,7 +72,7 @@ func main() {
}
help := strings.ToLower(os.Args[1])
if help == "-h" || help == "--help" {
log.Println(helpMsg)
log.Print(helpMsg)
os.Exit(0)
}

Expand All @@ -95,9 +97,10 @@ func main() {
cf := flag.Int("cf", 50, "Content format")
d := flag.String("d", "", "Message data")
a := flag.String("auth", "", "Auth token")
s := flag.Bool("tls", false, "Use DTLS")
flag.Parse()

client, err := coap.New(*h + ":" + *p)
client, err := coap.New(*h+":"+*p, *s)
if err != nil {
log.Fatal("Error creating client: ", err)
}
Expand All @@ -123,9 +126,10 @@ func main() {
if err != nil {
log.Fatal("Error observing resource: ", err)
}
errs := make(chan error, 2)
errs := make(chan error, 1) // make the channel buffered

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove comment


go func() {
c := make(chan os.Signal)
c := make(chan os.Signal, 1) // make the channel buffered

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove comment

signal.Notify(c, syscall.SIGINT)
errs <- fmt.Errorf("%s", <-c)
}()
Expand Down
45 changes: 32 additions & 13 deletions coap/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,41 @@ import (
"log"
"time"

"github.com/plgd-dev/go-coap/v2/message"
"github.com/plgd-dev/go-coap/v2/message/codes"
"github.com/plgd-dev/go-coap/v2/udp"
"github.com/plgd-dev/go-coap/v2/udp/client"
"github.com/plgd-dev/go-coap/v2/udp/message/pool"
cert_utility "github.com/mainflux/coap-cli/cert_utility"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to certutil.


"github.com/plgd-dev/go-coap/v3/dtls"
"github.com/plgd-dev/go-coap/v3/message"
"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/udp"
"github.com/plgd-dev/go-coap/v3/udp/client"
)

// Client represents CoAP client.
type Client struct {
conn *client.ClientConn
conn *client.Conn
}

// New returns new CoAP client connecting it to the server.
func New(addr string) (Client, error) {
c, err := udp.Dial(addr)
if err != nil {
log.Fatalf("Error dialing: %v", err)
func New(addr string, to_dtls bool) (Client, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using snake_case. Also, use switch instead of if-else.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try using cert and key paths to determine DTLS. If the cert path is set, try establishing DTLS connection with cert on that path.

if !to_dtls { // UDP
c, err := udp.Dial(addr)
if err != nil {
log.Fatalf("Error dialing: %v", err)
}
return Client{conn: c}, nil
} else { // DTLS
config, err := cert_utility.CreateClientConfig(context.Background())
if err != nil {
log.Fatalln(err)
}
co, err := dtls.Dial("localhost:5688", config)
if err != nil {
log.Fatalf("Error dialing: %v", err)
}
return Client{conn: co}, err
}

return Client{conn: c}, nil
}

// Send send a message.
Expand All @@ -45,11 +60,14 @@ func (c Client) Send(path string, msgCode codes.Code, cf message.MediaType, payl
case codes.DELETE:
return c.conn.Delete(ctx, path, opts...)
}
return nil, errors.New("Invalid message code")
return nil, errors.New("INVALID MESSAGE CODE")
}

// Receive receives a message.
func (c Client) Receive(path string, opts ...message.Option) (*client.Observation, error) {
func (c Client) Receive(path string, opts ...message.Option) (interface {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declare the returned interface somewhere first for readability

Cancel(ctx context.Context, opts ...message.Option) error
Canceled() bool
}, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (c Client) Receive(path string, opts ...message.Option) (interface {
Cancel(ctx context.Context, opts ...message.Option) error
Canceled() bool
}, error) {
func (c Client) Receive(path string, opts ...message.Option) (client.Observation, error) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think the Conn type has an implementation of Observation.

ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

Expand All @@ -64,4 +82,5 @@ func (c Client) Receive(path string, opts ...message.Option) (*client.Observatio
fmt.Println("Payload: ", string(body))
}
}, opts...)

}
24 changes: 17 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
module github.com/mainflux/coap-cli

go 1.15
go 1.21

require (
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/plgd-dev/go-coap/v2 v2.4.0
golang.org/x/net v0.0.0-20200513185701-a91f0712d120 // indirect
golang.org/x/sys v0.0.0-20201013081832-0aaa2718063a // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
github.com/pion/dtls/v2 v2.2.8-0.20230905141523-2b584af66577
github.com/plgd-dev/go-coap/v3 v3.1.5
)

require (
github.com/dsnet/golib/memfile v1.0.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/transport/v3 v3.0.1 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/crypto v0.13.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.12.0 // indirect
)
Loading