securechannel

package
v0.0.0-...-72e6a2b Latest Latest
Warning

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

Go to latest
Published: Dec 24, 2025 License: MIT Imports: 17 Imported by: 0

README

securechannel

Manager for Matter Secure Channel Protocol (Spec Section 4.11-4.14).

Architecture

                    ┌──────────────────────────────────────────┐
                    │              Manager                     │
                    │                                          │
                    │   Route(exchangeID, opcode, payload)     │
                    │          │                               │
                    │          ▼                               │
                    │   ┌──────────────────────────────┐       │
                    │   │   Opcode Router              │       │
                    │   │   0x20-0x24 → PASE           │       │
                    │   │   0x30-0x33 → CASE           │       │
                    │   │   0x40      → StatusReport   │       │
                    │   └──────────────────────────────┘       │
                    │          │                               │
                    │   ┌──────┴──────┐                        │
                    │   ▼             ▼                        │
                    │ pase/        case/                       │
                    │ Session      Session                     │
                    │                                          │
                    │          │                               │
                    │          ▼                               │
                    │   OnSessionEstablished(SecureContext)    │
                    └──────────────────────────────────────────┘
                                       │
                    ┌──────────────────┼──────────────────┐
                    ▼                  ▼                  ▼
             pkg/session        pkg/fabric        pkg/credentials
             (SecureContext)    (FabricTable)     (CertValidator)

Usage

Create Manager
mgr := securechannel.NewManager(sessionTable, fabricTable)
Start PASE Handshake (Commissioner)
pbkdfReq, err := mgr.StartPASE(exchangeID, passcode, salt, iterations, localSessionID)
// Send pbkdfReq to peer, then route responses through mgr.Route()
Start CASE Handshake (Initiator)
sigma1, err := mgr.StartCASE(exchangeID, fabricInfo, operationalKey, peerNodeID, localSessionID)
// Send sigma1 to peer, then route responses through mgr.Route()
Route Incoming Messages
response, err := mgr.Route(exchangeID, opcode, payload)
if response != nil {
    // Send response back to peer
}
// On completion, SecureContext is added to SessionTable
Handle Responder Role
// PASE responder
mgr.SetPASEResponder(verifier, salt, iterations)

// CASE responder
mgr.SetCASEResponder(fabricLookupFunc, certValidator)

// Then route incoming Sigma1/PBKDFParamRequest through mgr.Route()
Certificate Validation
// Production: full NOC → ICAC → RCAC chain validation
mgr := securechannel.NewManager(sessionTable, fabricTable)
// Uses NewCertValidator() by default

// Testing: skip validation
mgr := securechannel.NewManagerWithValidator(sessionTable, fabricTable,
    securechannel.NewSkipCertValidator())

Message Flow

PASE:                                    CASE:
  I ── PBKDFParamRequest (0x20) ──▶ R      I ── Sigma1 (0x30) ──────────▶ R
  I ◀── PBKDFParamResponse (0x21) ── R      I ◀── Sigma2 (0x31) ────────── R
  I ── Pake1 (0x22) ───────────────▶ R      I ── Sigma3 (0x32) ──────────▶ R
  I ◀── Pake2 (0x23) ─────────────── R      I ◀── StatusReport (0x40) ──── R
  I ── Pake3 (0x24) ───────────────▶ R
  I ◀── StatusReport (0x40) ──────── R

Session Keys

On successful handshake, both sides derive matching keys:

Key Size Purpose
I2RKey 16 bytes Encrypt initiator → responder
R2IKey 16 bytes Encrypt responder → initiator
AttestationChallenge 16 bytes Device attestation binding

E2E Testing

Two paired Manager instances for deterministic handshake testing without real network I/O.

  Controller Manager                     Device Manager
  ──────────────────                     ──────────────
        │                                      │
        ▼                                      ▼
    StartPASE()                        SetPASEResponder()
        │                                      │
        │  PBKDFParamRequest                   │
        ├─────────────────────────────────────▶│
        │                                      │ Route()
        │  PBKDFParamResponse                  │
        │◀─────────────────────────────────────┤
  Route()                                      │
        │  Pake1                               │
        ├─────────────────────────────────────▶│
        │                             Route()  │
        │  Pake2                               │
        │◀─────────────────────────────────────┤
  Route()                                      │
        │  Pake3                               │
        ├─────────────────────────────────────▶│
        │                             Route()  │
        │  StatusReport                        │
        │◀─────────────────────────────────────┤
  Route()                                      │
        │                                      │
        ▼                                      ▼
  OnSessionEstablished()              OnSessionEstablished()
PASE E2E Test
// Setup
verifier, _ := pase.GenerateVerifier(passcode, salt, iterations)
controllerMgr := NewManager(ManagerConfig{SessionManager: controllerSessionMgr})
deviceMgr := NewManager(ManagerConfig{SessionManager: deviceSessionMgr})
deviceMgr.SetPASEResponder(verifier, salt, iterations)

// Handshake
exchangeID := uint16(1)
pbkdfReq, _ := controllerMgr.StartPASE(exchangeID, passcode)
pbkdfRespMsg, _ := deviceMgr.Route(exchangeID, &Message{OpcodePBKDFParamRequest, pbkdfReq})
pake1Msg, _ := controllerMgr.Route(exchangeID, &Message{OpcodePBKDFParamResponse, pbkdfRespMsg.Payload})
pake2Msg, _ := deviceMgr.Route(exchangeID, &Message{OpcodePASEPake1, pake1Msg.Payload})
pake3Msg, _ := controllerMgr.Route(exchangeID, &Message{OpcodePASEPake2, pake2Msg.Payload})
statusMsg, _ := deviceMgr.Route(exchangeID, &Message{OpcodePASEPake3, pake3Msg.Payload})
_, _ = controllerMgr.Route(exchangeID, &Message{OpcodeStatusReport, statusMsg.Payload})
// Both sides now have SecureContext with matching keys
CASE E2E Test
// Setup with fabric credentials
initiatorFabric, initiatorKey := createTestFabricInfo(t, fabricID, initiatorNodeID)
responderFabric, responderKey := createTestFabricInfo(t, fabricID, responderNodeID)

initiatorMgr := NewManager(ManagerConfig{
    SessionManager: initiatorSessionMgr,
    CertValidator:  certValidator,
    LocalNodeID:    initiatorNodeID,
})

responderCASE := casesession.NewResponder(fabricLookup, nil)
responderCASE.WithCertValidator(certValidator)

// Handshake
sigma1, _ := initiatorMgr.StartCASE(exchangeID, initiatorFabric, initiatorKey, responderNodeID, nil)
sigma2, _, _ := responderCASE.HandleSigma1(sigma1, responderLocalSessionID)
sigma3Msg, _ := initiatorMgr.Route(exchangeID, &Message{OpcodeCASESigma2, sigma2})
_ = responderCASE.HandleSigma3(sigma3Msg.Payload)
_, _ = initiatorMgr.Route(exchangeID, &Message{OpcodeStatusReport, Success().Encode()})
Negative Tests
Test Scenario Expected
WrongPasscode Controller uses wrong passcode ErrConfirmationFailed at Pake2
CorruptedTLV Malformed TLV in message Decode error
TruncatedMessage Empty/short payload EOF or decode error
WindowClosed No PASE responder configured PASE responder not configured
InvalidState Message in wrong state ErrInvalidState
NoSharedRoot Different fabric roots ErrNoSharedRoot
ConfirmationMismatch Corrupted Pake3 cA ErrConfirmationFailed
Key Verification
// Direct PASE session comparison
initiator, _ := pase.NewInitiator(passcode)
responder, _ := pase.NewResponder(verifier, salt, iterations)
// ... complete handshake ...

initiatorKeys := initiator.SessionKeys()
responderKeys := responder.SessionKeys()
// I2RKey, R2IKey, AttestationChallenge all match
Encryption Round-Trip
// After handshake, verify keys work with message.Codec
codec, _ := message.NewCodec(keys.I2RKey[:], 0)
encrypted, _ := codec.Encode(header, protocol, payload, false)
decrypted, _ := codec.Decode(encrypted, 0)
// decrypted.Payload == payload

Documentation

Overview

Package securechannel implements the Matter Secure Channel Protocol.

This package provides constants and types for secure channel operations including PASE (Passcode-Authenticated Session Establishment) and CASE (Certificate Authenticated Session Establishment).

See Matter Specification Section 4.11.

Index

Constants

View Source
const (
	// DefaultBusyWaitTime is the default wait time in milliseconds for Busy responses.
	DefaultBusyWaitTime = 5000

	// HandshakeTimeout is the maximum duration for a handshake to complete.
	HandshakeTimeout = 60 * time.Second
)

Constants for secure channel manager.

View Source
const ProtocolID uint16 = 0x0000

ProtocolID is the Secure Channel protocol identifier.

View Source
const (
	// StatusReportMinSize is the minimum size of a StatusReport (no ProtocolData).
	StatusReportMinSize = 8 // GeneralCode(2) + ProtocolID(4) + ProtocolCode(2)
)

StatusReport message sizes.

Variables

View Source
var (
	ErrCertificateParseFailed  = errors.New("securechannel: failed to parse certificate")
	ErrCertificateTypeMismatch = errors.New("securechannel: certificate type mismatch")
	ErrCertificateExpired      = errors.New("securechannel: certificate expired")
	ErrCertificateNotYetValid  = errors.New("securechannel: certificate not yet valid")
	ErrCertificateChainBroken  = errors.New("securechannel: certificate chain validation failed")
	ErrSignatureVerifyFailed   = errors.New("securechannel: signature verification failed")
	ErrPublicKeyMismatch       = errors.New("securechannel: root public key mismatch")
	ErrMissingNodeID           = errors.New("securechannel: NOC missing node ID")
	ErrMissingFabricID         = errors.New("securechannel: NOC missing fabric ID")
	ErrFabricIDMismatch        = errors.New("securechannel: fabric ID mismatch in certificate chain")
)

Certificate validation errors.

View Source
var (
	ErrNoHandler           = errors.New("securechannel: no handler for message type")
	ErrHandshakeInProgress = errors.New("securechannel: handshake already in progress")
	ErrNoActiveHandshake   = errors.New("securechannel: no active handshake")
	ErrSessionTableFull    = errors.New("securechannel: session table full")
	ErrInvalidOpcode       = errors.New("securechannel: invalid opcode for current state")
	ErrSessionClosed       = errors.New("securechannel: session closed by peer")
)

Errors returned by the Manager.

View Source
var (
	ErrStatusReportTooShort = errors.New("securechannel: status report too short")
)

Errors

Functions

func IsBusyStatus

func IsBusyStatus(status *StatusReport) bool

IsBusyStatus returns true if the status report is a Busy status.

func IsCASEOpcode

func IsCASEOpcode(opcode Opcode) bool

IsCASEOpcode returns true if the opcode is for CASE protocol.

func IsCloseSession

func IsCloseSession(status *StatusReport) bool

IsCloseSession returns true if the status report is a CloseSession.

func IsPASEOpcode

func IsPASEOpcode(opcode Opcode) bool

IsPASEOpcode returns true if the opcode is for PASE protocol.

func MessagePermitted

func MessagePermitted(opcode Opcode) bool

MessagePermitted returns true if the opcode is allowed during session establishment. This implements the SessionEstablishmentExchangeDispatch whitelist from the C reference.

func NewCertValidator

func NewCertValidator() casesession.ValidatePeerCertChainFunc

NewCertValidator creates a ValidatePeerCertChainFunc that uses pkg/credentials to parse and validate certificates.

This validator:

  1. Parses the NOC (and ICAC if present) from Matter TLV format
  2. Verifies the certificate signatures form a valid chain
  3. Validates certificate types (NOC → ICAC → RCAC)
  4. Checks certificate validity periods
  5. Extracts and returns the peer's node ID, fabric ID, and public key

The trustedRootPubKey parameter must be the expected RCAC public key (65 bytes).

func NewSkipCertValidator

func NewSkipCertValidator() casesession.ValidatePeerCertChainFunc

NewSkipCertValidator creates a validator that skips certificate validation. This is for testing only and should NEVER be used in production.

func SendBusy

func SendBusy(waitTimeMs uint16) []byte

SendBusy creates a Busy status report to send to a peer. waitTimeMs is the minimum time in milliseconds the peer should wait before retrying.

Per Section 4.11.1.5:

  • SHALL NOT be sent in response to any message except Sigma1 or PBKDFParamRequest
  • R Flag SHALL be 0 (no response expected)
  • S Flag SHALL be 0

func SendCloseSession

func SendCloseSession() []byte

SendCloseSession creates a CloseSession message to send to a peer. This should be sent when:

  • The interaction between nodes is complete
  • The node needs to free up resources for a new session
  • Fabric configuration associated with the session was removed

The returned bytes should be sent over the secure session before removing it.

Types

type Callbacks

type Callbacks struct {
	// OnSessionEstablished is called when a session is successfully established.
	// The callback receives the new secure context.
	OnSessionEstablished func(ctx *session.SecureContext)

	// OnSessionError is called when session establishment fails.
	// The callback receives the error and the stage at which it occurred.
	OnSessionError func(err error, stage string)

	// OnSessionClosed is called when a peer closes a session via CloseSession.
	// The callback receives the closed session's local ID.
	OnSessionClosed func(localSessionID uint16)

	// OnResponderBusy is called when a responder sends a Busy status.
	// The callback receives the minimum wait time in milliseconds.
	OnResponderBusy func(waitTimeMs uint16)
}

Callbacks provides callback functions for Manager events.

type GeneralCode

type GeneralCode uint16

GeneralCode represents protocol-agnostic status codes (Appendix D.3.1).

const (
	GeneralCodeSuccess           GeneralCode = 0
	GeneralCodeFailure           GeneralCode = 1
	GeneralCodeBadPrecondition   GeneralCode = 2
	GeneralCodeOutOfRange        GeneralCode = 3
	GeneralCodeBadRequest        GeneralCode = 4
	GeneralCodeUnsupported       GeneralCode = 5
	GeneralCodeUnexpected        GeneralCode = 6
	GeneralCodeResourceExhausted GeneralCode = 7
	GeneralCodeBusy              GeneralCode = 8
	GeneralCodeTimeout           GeneralCode = 9
	GeneralCodeContinue          GeneralCode = 10
	GeneralCodeAborted           GeneralCode = 11
	GeneralCodeInvalidArgument   GeneralCode = 12
	GeneralCodeNotFound          GeneralCode = 13
	GeneralCodeAlreadyExists     GeneralCode = 14
	GeneralCodePermissionDenied  GeneralCode = 15
	GeneralCodeDataLoss          GeneralCode = 16
)

func (GeneralCode) String

func (g GeneralCode) String() string

String returns the general code name.

type HandshakeType

type HandshakeType int

HandshakeType indicates the type of secure session being established.

const (
	HandshakeTypePASE HandshakeType = iota
	HandshakeTypeCASE
)

func (HandshakeType) String

func (h HandshakeType) String() string

String returns the handshake type name.

type Manager

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

Manager coordinates secure channel protocol operations.

func NewManager

func NewManager(config ManagerConfig) *Manager

NewManager creates a new secure channel Manager.

func (*Manager) ActiveHandshakeCount

func (m *Manager) ActiveHandshakeCount() int

ActiveHandshakeCount returns the number of active handshakes.

func (*Manager) CleanupExpiredHandshakes

func (m *Manager) CleanupExpiredHandshakes()

CleanupExpiredHandshakes removes handshakes that have timed out.

func (*Manager) ClearPASEResponder

func (m *Manager) ClearPASEResponder()

ClearPASEResponder clears the PASE responder configuration. Call this when the commissioning window closes.

func (*Manager) GetHandshakeType

func (m *Manager) GetHandshakeType(exchangeID uint16) (HandshakeType, bool)

GetHandshakeType returns the type of handshake on the exchange, if any.

func (*Manager) HasActiveHandshake

func (m *Manager) HasActiveHandshake(exchangeID uint16) bool

HasActiveHandshake returns true if there's an active handshake on the exchange.

func (*Manager) HasPASEResponder

func (m *Manager) HasPASEResponder() bool

HasPASEResponder returns true if PASE responder is configured.

func (*Manager) Route

func (m *Manager) Route(exchangeID uint16, msg *Message) (*Message, error)

Route dispatches an incoming message to the appropriate handler. Returns the response message (opcode + payload) if any, and an error.

func (*Manager) SetPASEResponder

func (m *Manager) SetPASEResponder(verifier *pase.Verifier, salt []byte, iterations uint32) error

SetPASEResponder configures the Manager to respond to PASE requests. This must be called before receiving PBKDFParamRequest messages. Call ClearPASEResponder when the commissioning window closes.

func (*Manager) StartCASE

func (m *Manager) StartCASE(
	exchangeID uint16,
	fabricInfo *fabric.FabricInfo,
	operationalKey *crypto.P256KeyPair,
	targetNodeID uint64,
	resumptionInfo *casesession.ResumptionInfo,
) ([]byte, error)

StartCASE begins a CASE handshake as initiator. Returns the Sigma1 message to send.

func (*Manager) StartPASE

func (m *Manager) StartPASE(exchangeID uint16, passcode uint32) ([]byte, error)

StartPASE begins a PASE handshake as initiator. Returns the PBKDFParamRequest message to send.

type ManagerConfig

type ManagerConfig struct {
	// SessionManager manages secure session contexts.
	SessionManager *session.Manager

	// FabricTable provides fabric lookup for CASE.
	FabricTable *fabric.Table

	// CertValidator validates peer certificate chains during CASE.
	// If nil, certificate validation is skipped (testing only).
	CertValidator casesession.ValidatePeerCertChainFunc

	// Callbacks for Manager events.
	Callbacks Callbacks

	// LocalNodeID is our operational node ID (0 for uncommissioned).
	LocalNodeID fabric.NodeID

	// LoggerFactory is the factory for creating loggers.
	// If nil, logging is disabled.
	LoggerFactory logging.LoggerFactory
}

ManagerConfig configures the secure channel Manager.

type Message

type Message struct {
	Opcode  Opcode
	Payload []byte
}

Message represents a secure channel protocol message (request or response). It pairs an opcode with its payload for symmetric handling.

func NewMessage

func NewMessage(opcode Opcode, payload []byte) *Message

NewMessage creates a new Message. Returns nil if payload is nil.

type Opcode

type Opcode uint8

Opcode represents a Secure Channel protocol message type.

const (
	// Message Counter Synchronization
	OpcodeMsgCounterSyncReq  Opcode = 0x00
	OpcodeMsgCounterSyncResp Opcode = 0x01

	// Reliable Messaging Protocol
	OpcodeStandaloneAck Opcode = 0x10

	// PASE (Password-based session establishment)
	OpcodePBKDFParamRequest  Opcode = 0x20
	OpcodePBKDFParamResponse Opcode = 0x21
	OpcodePASEPake1          Opcode = 0x22
	OpcodePASEPake2          Opcode = 0x23
	OpcodePASEPake3          Opcode = 0x24

	// CASE (Certificate-based session establishment)
	OpcodeCASESigma1       Opcode = 0x30
	OpcodeCASESigma2       Opcode = 0x31
	OpcodeCASESigma3       Opcode = 0x32
	OpcodeCASESigma2Resume Opcode = 0x33

	// Status and ICD
	OpcodeStatusReport Opcode = 0x40
	OpcodeICDCheckIn   Opcode = 0x50
)

Secure Channel Protocol Opcodes (Table 18).

func (Opcode) String

func (o Opcode) String() string

String returns the opcode name.

type ProtocolCode

type ProtocolCode uint16

ProtocolCode represents Secure Channel specific status codes (Table 19).

const (
	ProtocolCodeSuccess         ProtocolCode = 0x0000
	ProtocolCodeNoSharedRoot    ProtocolCode = 0x0001
	ProtocolCodeInvalidParam    ProtocolCode = 0x0002
	ProtocolCodeCloseSession    ProtocolCode = 0x0003
	ProtocolCodeBusy            ProtocolCode = 0x0004
	ProtocolCodeSessionNotFound ProtocolCode = 0x0005
	ProtocolCodeGeneralFailure  ProtocolCode = 0xFFFF
)

func (ProtocolCode) String

func (p ProtocolCode) String() string

String returns the protocol code name.

type StatusReport

type StatusReport struct {
	GeneralCode  GeneralCode
	ProtocolID   uint32 // VendorID (upper 16) | ProtocolID (lower 16)
	ProtocolCode uint16
	ProtocolData []byte // Optional protocol-specific data
}

StatusReport encapsulates the data in a StatusReport message.

See Matter Specification Appendix D.

func Busy

func Busy(waitTimeMs uint16) *StatusReport

Busy creates a busy StatusReport with the minimum wait time in milliseconds.

func CloseSession

func CloseSession() *StatusReport

CloseSession creates a close session StatusReport.

func DecodeStatusReport

func DecodeStatusReport(data []byte) (*StatusReport, error)

DecodeStatusReport parses a StatusReport from bytes.

func InvalidParam

func InvalidParam() *StatusReport

InvalidParam creates an invalid parameter StatusReport.

func NewSecureChannelStatusReport

func NewSecureChannelStatusReport(general GeneralCode, code ProtocolCode) *StatusReport

NewSecureChannelStatusReport creates a StatusReport for the Secure Channel protocol.

func NewStatusReport

func NewStatusReport(general GeneralCode, protocolID uint32, code uint16) *StatusReport

NewStatusReport creates a StatusReport with no protocol data.

func NoSharedTrustRoots

func NoSharedTrustRoots() *StatusReport

NoSharedTrustRoots creates a NoSharedTrustRoots error status report. Sent during CASE when no common root of trust is found.

func RequiredCATMismatch

func RequiredCATMismatch() *StatusReport

RequiredCATMismatch creates a status report for CAT mismatch errors. Sent during CASE Sigma2 validation when required CATs don't match.

func SessionNotFound

func SessionNotFound() *StatusReport

SessionNotFound creates a SessionNotFound error status report.

func Success

func Success() *StatusReport

Success creates a success StatusReport for session establishment.

func (*StatusReport) BusyWaitTime

func (s *StatusReport) BusyWaitTime() uint16

BusyWaitTime returns the minimum wait time in milliseconds if this is a busy status. Returns 0 if not a busy status or if protocol data is missing.

func (*StatusReport) Encode

func (s *StatusReport) Encode() []byte

Encode serializes the StatusReport to bytes.

func (*StatusReport) Error

func (s *StatusReport) Error() string

Error implements the error interface for StatusReport.

func (*StatusReport) IsBusy

func (s *StatusReport) IsBusy() bool

IsBusy returns true if this is a busy status.

func (*StatusReport) IsSecureChannel

func (s *StatusReport) IsSecureChannel() bool

IsSecureChannel returns true if this status is for the Secure Channel protocol.

func (*StatusReport) IsSuccess

func (s *StatusReport) IsSuccess() bool

IsSuccess returns true if this is a success status.

func (*StatusReport) SecureChannelCode

func (s *StatusReport) SecureChannelCode() ProtocolCode

SecureChannelCode returns the ProtocolCode as a SecureChannel ProtocolCode. Only valid if IsSecureChannel() returns true.

func (*StatusReport) String

func (s *StatusReport) String() string

String returns a human-readable representation.

type UnsolicitedHandler

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

UnsolicitedHandler handles unsolicited status reports on secure sessions. This includes CloseSession messages from peers indicating session termination.

See Matter Specification Section 4.11.1.4 (CloseSession) and 4.11.1.5 (Busy).

func NewUnsolicitedHandler

func NewUnsolicitedHandler(sessionManager *session.Manager, callbacks Callbacks) *UnsolicitedHandler

NewUnsolicitedHandler creates a new unsolicited status handler.

func (*UnsolicitedHandler) HandleStatusReport

func (h *UnsolicitedHandler) HandleStatusReport(localSessionID uint16, status *StatusReport) bool

HandleStatusReport processes an unsolicited StatusReport received on a secure session. Returns true if the status report was handled, false if it should be passed to upper layers.

Directories

Path Synopsis
Package casesession implements CASE (Certificate Authenticated Session Establishment).
Package casesession implements CASE (Certificate Authenticated Session Establishment).
Package messages provides common encoding utilities for Matter secure channel protocols.
Package messages provides common encoding utilities for Matter secure channel protocols.
Package pase implements Passcode-Authenticated Session Establishment (PASE).
Package pase implements Passcode-Authenticated Session Establishment (PASE).

Jump to

Keyboard shortcuts

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