gomat

package module
v0.0.0-...-5413b3e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 10, 2023 License: BSD-2-Clause Imports: 29 Imported by: 0

README

gomat

Simple matter protocol library

Go Reference go build Go Report Card

goal of project

The goal is to create golang library and supporting tools to access matter devices.

status of project
  • it can
    • commission devices
    • send commands to devices
    • read attributes from devices
    • subscribe and receive events
    • decode onboarding info (qr text, manual pair code)
    • discover commissionable devices
    • discover commissioned devices
    • open commissioning window
tested devices
general info
  • it is best to understand matter to use this, but here is most important info:
    • device access is managed using certificates
    • easiest way how to talk to device is to have signed certificate of device admin user (alternative is setup ACLs and use non-admin user)
    • certificates are signed by CA
    • during commissioning procedure root CA certificate is pushed to device together with id of device admin user
    • root CA certificate is something you need to create once and store. loosing CA keys usually means that you will have to commission devices again
    • to talk to device you have to commission it first
      • to commission device you usually need its pin/passcode and device be in state open for commisioning
      • device gets into commisioning window open state often by "factory reset"
      • when device is commissioned - connected to some fabric, it can be commissionined into other fabrics using api, where existing admin user sets device to be open for additional commissioning. During that device can be connected to additional fabric(s) - additional root CA installed and additional admin user configured
how to use test application
  • compile
git clone git@github.com:tom-code/gomat.git
cd gomat
go build -o gomat demo/main.go 
  • create directory to hold keys and certificates mkdir pem
  • generate CA key and certificate using ./gomat ca-bootstrap
  • generate controller key and certificate using ./gomat ca-createuser 100
    • 100 is example node-id of controller
  • find device IP
    • discover command can be used to discover matter devices and their ip address ./gomat discover commissionable -d
  • find device commissioning passcode/pin
    • device may show it
    • it can be extracted from QR code. use decode-qr to extract passcode from text representation of QR code ./gomat decode-qr MT:-24J0AFN00SIQ663000
    • it can be extracted from manual pairing code. use command decode-mc to extract passcode from manual pairing code ./gomat decode-mc 35792000079
  • perform commissioning of device. This authenticates using passcode, uploads CA certificate to device, signs and uploads device's own certificate and sets admin user id.
    • required for commisioning:
      • ip address of device
      • device commissioning passcode/pin
      • ca key and certificate
      • controller node key and certificate
    • example: ./gomat commission --ip 192.168.5.178 --pin 123456 --controller-id 100 --device-id 500
  • light on! ./gomat cmd on --ip 192.168.5.178 --controller-id 100 --device-id 500
  • set color hue=150 saturation=200 transition_time=10 ./gomat cmd color --ip 192.168.5.220 --controller-id 100 --device-id 500 150 200 10
how to use api
Example applications
commission device using api

create ca with root certificate, create admin user, then commission device:

package main

import (
  "net"

  "github.com/tom-code/gomat"
)


func main() {
  var fabric_id uint64 = 0x100
  var admin_user uint64 = 5
  var device_id uint64 = 10
  device_ip := "192.168.5.178"
  pin := 123456

  cm := gomat.NewFileCertManager(fabric_id)
  cm.BootstrapCa()
  cm.Load()
  cm.CreateUser(admin_user)
  fabric := gomat.NewFabric(fabric_id, cm)
  gomat.Commission(fabric, net.ParseIP(device_ip), pin, admin_user, device_id)
}
send ON command to commissioned device using api
package main

import (
  "net"

  "github.com/tom-code/gomat"
)


func main() {
  var fabric_id uint64 = 0x100
  var admin_user uint64 = 5
  var device_id uint64 = 10
  device_ip := "192.168.5.178"

  cm := gomat.NewFileCertManager(fabric_id)
  cm.Load()
  fabric := gomat.NewFabric(fabric_id, cm)

  secure_channel, err := gomat.StartSecureChannel(net.ParseIP(device_ip), 5540, 55555)
  if err != nil {
    panic(err)
  }
  defer secure_channel.Close()
  secure_channel, err = gomat.SigmaExchange(fabric, admin_user, device_id, secure_channel)
  if err != nil {
    panic(err)
  }

  on_command := gomat.EncodeInvokeCommand(1,        // endpoint
                                          6,        // api cluster (on/off)
                                          1,        // on command
                                          []byte{}, // no extra data
                                          )
  secure_channel.Send(on_command)
  resp, err := secure_channel.Receive()
  if err != nil {
    panic(err)
  }
  resp.Tlv.Dump(0)
}
discover IP address of previously commissioned device using api

Device exposes its info using mdns under identifier [compressed-fabric-id]-[device-id]. For this reason to discover commissioned device fabric info is required.

package main

import (
  "encoding/hex"
  "fmt"
  "strings"

  "github.com/tom-code/gomat"
  "github.com/tom-code/gomat/discover"
)



func main() {
  var fabric_id uint64 = 0x100
  var device_id uint64 = 10


  cm := gomat.NewFileCertManager(fabric_id)
  cm.Load()
  fabric := gomat.NewFabric(fabric_id, cm)

  identifier := fmt.Sprintf("%s-%016X", hex.EncodeToString(fabric.CompressedFabric()), device_id)
  identifier = strings.ToUpper(identifier)
  identifier = identifier + "._matter._tcp.local."
  fmt.Printf("%s\n", identifier)
  devices := discover.DiscoverComissioned("", true, identifier)
  for _, d := range devices {
    fmt.Printf("host:%s ip:%v\n", d.Host, d.Addrs)
  }
}
extract pairing passcode from QR code and manual pairing code

Following example shows how to extract passcode from textual representation of QR code or from manual pairing code. Manual pairing code can have dash characters at any position(they are discarded)

package main

import (
	"fmt"

	"github.com/tom-code/gomat/onboarding_payload"
)


func main() {
	setup_qr_code := "MT:-24J0AFN00SIQ663000"
	qr_decoded := onboarding_payload.DecodeQrText(setup_qr_code)
	fmt.Printf("passcode: %d\n", qr_decoded.Passcode)


	manual_pair_code := "357-920-000-79"
	code_decoded := onboarding_payload.DecodeManualPairingCode(manual_pair_code)
	fmt.Printf("passcode: %d\n", code_decoded.Passcode)
}

Set color of light to specific hue color
package main

import (
	"fmt"
	"net"

	"github.com/tom-code/gomat"
	"github.com/tom-code/gomat/mattertlv"
)


func main() {
	var fabric_id uint64 = 0x100
	var admin_user uint64 = 5
	var device_id uint64 = 10
	device_ip := "192.168.5.178"

	cm := gomat.NewFileCertManager(fabric_id)
	cm.Load()
	fabric := gomat.NewFabric(fabric_id, cm)


	secure_channel, err := gomat.StartSecureChannel(net.ParseIP(device_ip), 5540, 55555)
	if err != nil {
		panic(err)
	}
	defer secure_channel.Close()
	secure_channel, err = gomat.SigmaExchange(fabric, admin_user, device_id, secure_channel)
	if err != nil {
		panic(err)
	}

	var tlv mattertlv.TLVBuffer
	tlv.WriteUInt8(0, byte(hue))        // hue
	tlv.WriteUInt8(1, byte(saturation)) // saturation
	tlv.WriteUInt8(2, byte(time))       // time
	to_send := gomat.EncodeInvokeCommand(1, 0x300, 6, tlv.Bytes())
	secure_channel.Send(to_send)

	resp, err := secure_channel.Receive()
	if err != nil {
		panic(err)
	}
	status, err := resp.Tlv.GetIntRec([]int{1,0,1,1,0})
	if err != nil {
		panic(err)
	}
	fmt.Printf("result status: %d\n", status)
}
certificate manager

NewFabric function accepts certificate manager object as input parameter. Certificate manager must implement interface CertificateManager and user can supply own implementation. Supplied CertManager created by NewFileCertManager is very simple and stores all data in .pem files under pem directory.

notes

consider move to https://pkg.go.dev/filippo.io/nistec

Documentation

Overview

Package gomat implements matter protocol to allow talking to matter enabled devices.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Commission

func Commission(fabric *Fabric, device_ip net.IP, pin int, controller_id, device_id uint64) error

Commission performs commissioning procedure on device with device_ip ip address

  • fabric is fabric object with approriate certificate authority
  • pin is passcode used for device pairing
  • controller_id is identifier of node whioch will be owner/admin of this device
  • device_id_id is identifier of "new" device

func CreateRandomBytes

func CreateRandomBytes(n int) []byte

func EncodeIMInvokeRequest

func EncodeIMInvokeRequest(endpoint uint16, cluster uint32, command uint32, payload []byte, timed bool, exchange uint16) []byte

EncodeIMInvokeRequest encodes Interaction Model Invoke Request message

func EncodeIMReadRequest

func EncodeIMReadRequest(endpoint uint16, cluster uint32, attr uint32) []byte

EncodeIMInvokeRequest encodes Interaction Model Read Request message

func EncodeIMStatusResponse

func EncodeIMStatusResponse(exchange_id uint16, iflag byte) []byte

EncodeIMStatusResponse encodes success Interaction Model Invoke Response

func EncodeIMSubscribeRequest

func EncodeIMSubscribeRequest(endpoint uint16, cluster uint32, event uint32) []byte

EncodeIMInvokeRequest encodes Interaction Model Read Request message

func EncodeIMTimedRequest

func EncodeIMTimedRequest(exchange uint16, timeout uint16) []byte

EncodeIMInvokeRequest encodes Interaction Model Timed Request message

func EncodeStatusReport

func EncodeStatusReport(code StatusReportElements) []byte

func ParseImInvokeResponse

func ParseImInvokeResponse(resp *mattertlv.TlvItem) int

ParseImInvokeResponse parses IM InvokeResponse TLV

  • returns 0 when success
  • returns -1 when parsing did fail
  • returned number > 0 is ClusterStatus code

func SerializeCertificateIntoMatter

func SerializeCertificateIntoMatter(fabric *Fabric, in *x509.Certificate) []byte

SerializeCertificateIntoMatter serializes x509 certificate into matter certificate format. Matter certificate format is way how to make matter even more weird and complicated. Signature of matter vertificate must match signature of certificate reencoded to DER encoding. This requires to handle very carefully order and presence of all elements in original x509.

Types

type CertificateManager

type CertificateManager interface {
	GetCaPublicKey() ecdsa.PublicKey
	GetCaCertificate() *x509.Certificate

	// CreateUser creates keys and certificate for node with specific id
	// it must be possible to later retrieve node keys using GetPrivkey and certificate using GetCertificate
	CreateUser(node_id uint64) error

	// retrieve certificate of specified node (previously created by CreateUser)
	GetCertificate(id uint64) (*x509.Certificate, error)

	// retrieve key of specified node (previously created by CreateUser)
	GetPrivkey(id uint64) (*ecdsa.PrivateKey, error)

	// create and sign certificate using local CA keys
	SignCertificate(user_pubkey *ecdsa.PublicKey, node_id uint64) (*x509.Certificate, error)
}

matter certificate manager interface all generated certificates must be compatible with matter

  • this means that after they are reencoded to matter format and back their signature must match

type DecodedGeneric

type DecodedGeneric struct {
	MessageHeader  MessageHeader
	ProtocolHeader ProtocolMessageHeader
	Tlv            mattertlv.TlvItem
	Payload        []byte
	StatusReport   StatusReportElements
}

type Fabric

type Fabric struct {
	CertificateManager CertificateManager
	// contains filtered or unexported fields
}

Fabric structure represents matter Fabric. Its main parameters are Id of fabric and certificate manager.

func NewFabric

func NewFabric(id uint64, certman CertificateManager) *Fabric

NewFabric constructs new Fabric object.

func (Fabric) CompressedFabric

func (fabric Fabric) CompressedFabric() []byte

CompressedFabric returns Compressed Fabric Identifier which is used to identify fabric in matter protocol.

func (Fabric) GetOperationalDeviceId

func (fabric Fabric) GetOperationalDeviceId(in uint64) string

func (Fabric) Id

func (fabric Fabric) Id() uint64

type FileCertManager

type FileCertManager struct {
	// contains filtered or unexported fields
}

PEM file backed certiticate manager

func NewFileCertManager

func NewFileCertManager(fabric uint64) *FileCertManager

func (*FileCertManager) BootstrapCa

func (cm *FileCertManager) BootstrapCa() error

BootstrapCa initializes CA - creates CA keys and certificate

func (*FileCertManager) CreateUser

func (cm *FileCertManager) CreateUser(node_id uint64) error

func (*FileCertManager) GetCaCertificate

func (cm *FileCertManager) GetCaCertificate() *x509.Certificate

func (*FileCertManager) GetCaPublicKey

func (cm *FileCertManager) GetCaPublicKey() ecdsa.PublicKey

func (*FileCertManager) GetCertificate

func (cm *FileCertManager) GetCertificate(id uint64) (*x509.Certificate, error)

func (*FileCertManager) GetPrivkey

func (cm *FileCertManager) GetPrivkey(id uint64) (*ecdsa.PrivateKey, error)

func (*FileCertManager) Load

func (cm *FileCertManager) Load() error

Load initializes CA. It loads required state from files.

func (*FileCertManager) SignCertificate

func (cm *FileCertManager) SignCertificate(user_pubkey *ecdsa.PublicKey, node_id uint64) (*x509.Certificate, error)

type MessageHeader

type MessageHeader struct {
	// contains filtered or unexported fields
}

func (*MessageHeader) Decode

func (m *MessageHeader) Decode(data *bytes.Buffer) error

func (*MessageHeader) Dump

func (m *MessageHeader) Dump()

func (*MessageHeader) Encode

func (m *MessageHeader) Encode(data *bytes.Buffer)

type Opcode

type Opcode byte
const INTERACTION_OPCODE_INVOKE_REQ Opcode = 0x8
const INTERACTION_OPCODE_INVOKE_RSP Opcode = 0x9
const INTERACTION_OPCODE_READ_REQ Opcode = 0x2
const INTERACTION_OPCODE_REPORT_DATA Opcode = 0x5
const INTERACTION_OPCODE_STATUS_RSP Opcode = 0x1
const INTERACTION_OPCODE_SUBSC_REQ Opcode = 0x3
const INTERACTION_OPCODE_SUBSC_RSP Opcode = 0x4
const INTERACTION_OPCODE_TIMED_REQ Opcode = 0xa
const SEC_CHAN_OPCODE_ACK Opcode = 0x10
const SEC_CHAN_OPCODE_PAKE1 Opcode = 0x22
const SEC_CHAN_OPCODE_PAKE2 Opcode = 0x23
const SEC_CHAN_OPCODE_PAKE3 Opcode = 0x24
const SEC_CHAN_OPCODE_PBKDF_REQ Opcode = 0x20
const SEC_CHAN_OPCODE_PBKDF_RESP Opcode = 0x21
const SEC_CHAN_OPCODE_STATUS_REP Opcode = 0x40

type ProtocolId

type ProtocolId uint16
const ProtocolIdInteraction ProtocolId = 1
const ProtocolIdSecureChannel ProtocolId = 0

type ProtocolMessageHeader

type ProtocolMessageHeader struct {
	Opcode     Opcode
	ExchangeId uint16
	ProtocolId ProtocolId
	// contains filtered or unexported fields
}

func (*ProtocolMessageHeader) Decode

func (m *ProtocolMessageHeader) Decode(data *bytes.Buffer)

func (*ProtocolMessageHeader) Dump

func (m *ProtocolMessageHeader) Dump()

func (*ProtocolMessageHeader) Encode

func (m *ProtocolMessageHeader) Encode(data *bytes.Buffer)

type SecureChannel

type SecureChannel struct {
	Udp *udpChannel

	Counter uint32
	// contains filtered or unexported fields
}

func ConnectDevice

func ConnectDevice(device_ip net.IP, port int, fabric *Fabric, device_id, admin_id uint64) (SecureChannel, error)

func SigmaExchange

func SigmaExchange(fabric *Fabric, controller_id uint64, device_id uint64, secure_channel SecureChannel) (SecureChannel, error)

SigmaExhange establishes secure session using CASE (Certificate Authenticated Session Establishment)

func Spake2pExchange

func Spake2pExchange(pin int, udp *udpChannel) (SecureChannel, error)

Spake2pExchange establishes secure session using PASE (Passcode-Authenticated Session Establishment). This uses SPAKE2+ protocol

func StartSecureChannel

func StartSecureChannel(remote_ip net.IP, remote_port, local_port int) (SecureChannel, error)

StartSecureChannel initializes secure channel for plain unencrypted communication. It initializes UDP interface and blocks local udp port. Secure channel becomes encrypted after encryption keys are supplied.

func (*SecureChannel) Close

func (sc *SecureChannel) Close()

Close secure channel. Send close session message to remote end and relase UDP port.

func (*SecureChannel) Receive

func (sc *SecureChannel) Receive() (DecodedGeneric, error)

func (*SecureChannel) Send

func (sc *SecureChannel) Send(data []byte) error

Send sends Protocol Message via secure channel. It creates Matter Message by adding Message Header. Protocol Message is aes-ccm encrypted when channel does have encryption keys. When encryption keys are empty plain Message is sent.

type SpakeCtx

type SpakeCtx struct {
	W0 []byte
	W1 []byte

	X point
	Y point
	Z point
	V point
	L point

	Ke []byte
	Ka []byte
	// contains filtered or unexported fields
}

func NewSpaceCtx

func NewSpaceCtx() SpakeCtx

func (*SpakeCtx) Calc_X

func (ctx *SpakeCtx) Calc_X()

func (*SpakeCtx) Calc_ZVb

func (ctx *SpakeCtx) Calc_ZVb()

func (*SpakeCtx) Gen_random_X

func (ctx *SpakeCtx) Gen_random_X()

func (*SpakeCtx) Gen_random_Y

func (ctx *SpakeCtx) Gen_random_Y()

func (*SpakeCtx) Gen_w

func (ctx *SpakeCtx) Gen_w(passcode int, salt []byte, iterations int)

type StatusReportElements

type StatusReportElements struct {
	GeneralCode  uint16
	ProtocolId   uint32
	ProtocolCode uint16
}

func (StatusReportElements) Dump

func (sr StatusReportElements) Dump()

func (StatusReportElements) IsOk

func (sr StatusReportElements) IsOk() bool

Directories

Path Synopsis
Package ccm implements a CCM, Counter with CBC-MAC as per RFC 3610.
Package ccm implements a CCM, Counter with CBC-MAC as per RFC 3610.
examples
gen

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL