Skip to content

Commit

Permalink
Move onto attestation
Browse files Browse the repository at this point in the history
  • Loading branch information
tannewt committed Sep 24, 2024
1 parent 3dd02a8 commit 391fc0d
Show file tree
Hide file tree
Showing 5 changed files with 312 additions and 64 deletions.
78 changes: 57 additions & 21 deletions circuitmatter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,6 @@ def encode_into(self, buffer, cipher=None):
self.security_flags,
self.message_counter,
)
print(self.flags, self.session_id)
nonce_start = 3
nonce_end = nonce_start + 1 + 4
offset += 8
Expand Down Expand Up @@ -549,6 +548,8 @@ def encode_into(self, buffer, cipher=None):
] = self.application_payload
unencrypted_offset += len(self.application_payload)

# print("unencrypted", unencrypted_buffer[:unencrypted_offset].hex(" "))

# Encrypt the payload
if cipher is not None:
# The message may not include the source_node_id so we encode the nonce separately.
Expand Down Expand Up @@ -892,6 +893,37 @@ def arm_fail_safe(
response.ErrorCode = data_model.CommissioningErrorEnum.OK
return response

def set_regulatory_config(
self, args: data_model.GeneralCommissioningCluster.SetRegulatoryConfig
) -> data_model.GeneralCommissioningCluster.SetRegulatoryConfigResponse:
response = data_model.GeneralCommissioningCluster.SetRegulatoryConfigResponse()
response.ErrorCode = data_model.CommissioningErrorEnum.OK
return response


class NodeOperationalCredentialsCluster(data_model.NodeOperationalCredentialsCluster):
def certificate_chain_request(
self, args: data_model.NodeOperationalCredentialsCluster.CertificateChainRequest
) -> data_model.NodeOperationalCredentialsCluster.CertificateChainResponse:
response = (
data_model.NodeOperationalCredentialsCluster.CertificateChainResponse()
)
if args.CertificateType == data_model.CertificateChainTypeEnum.PAI:
print("PAI")
elif args.CertificateType == data_model.CertificateChainTypeEnum.DAC:
print("DAC")
response.Certificate = b""
return response

def attestation_request(
self, args: data_model.NodeOperationalCredentialsCluster.AttestationRequest
) -> data_model.NodeOperationalCredentialsCluster.AttestationResponse:
print("attestation")
response = data_model.NodeOperationalCredentialsCluster.AttestationResponse()
response.AttestationElements = b""
response.AttestationSignature = b""
return response


class CircuitMatter:
def __init__(
Expand Down Expand Up @@ -948,6 +980,8 @@ def __init__(
self.add_cluster(0, network_info)
general_commissioning = GeneralCommissioningCluster()
self.add_cluster(0, general_commissioning)
noc = NodeOperationalCredentialsCluster()
self.add_cluster(0, noc)

def start_commissioning(self):
descriminator = self.nonvolatile["descriminator"]
Expand Down Expand Up @@ -1006,21 +1040,23 @@ def get_report(self, cluster, path):
return report

def invoke(self, cluster, path, fields, command_ref):
print("invoke", path)
response = interaction_model.InvokeResponseIB()
cstatus = interaction_model.CommandStatusIB()
cstatus.CommandPath = path
status = interaction_model.StatusIB()
status.Status = 0
status.ClusterStatus = 0
cstatus.Status = status
if command_ref is not None:
cstatus.CommandRef = command_ref
response.Status = cstatus
cdata = interaction_model.CommandDataIB()
cdata.CommandPath = path
cdata.CommandFields = cluster.invoke(path, fields)
cdata = cluster.invoke(path, fields)
if cdata is None:
cstatus = interaction_model.CommandStatusIB()
cstatus.CommandPath = path
status = interaction_model.StatusIB()
status.Status = interaction_model.StatusCode.UNSUPPORTED_COMMAND
cstatus.Status = status
if command_ref is not None:
cstatus.CommandRef = command_ref
response.Status = cstatus
return response

if command_ref is not None:
cdata.CommandRef = command_ref
print("cdata", cdata)
response.Command = cdata
return response

Expand Down Expand Up @@ -1180,8 +1216,6 @@ def process_packet(self, address, data):
elif protocol_opcode == SecureProtocolOpcode.ICD_CHECK_IN:
print("Received ICD Check-in")
elif message.protocol_id == ProtocolId.INTERACTION_MODEL:
print(message)
print("application payload", message.application_payload.hex(" "))
if protocol_opcode == InteractionModelOpcode.READ_REQUEST:
print("Received Read Request")
read_request, _ = interaction_model.ReadRequestMessage.decode(
Expand All @@ -1203,6 +1237,8 @@ def process_packet(self, address, data):
# TODO: The path object probably needs to be cloned. Otherwise we'll
# change the endpoint for all uses.
path.Endpoint = endpoint
print(path.Endpoint)
print(path)
attribute_reports.append(self.get_report(cluster, path))
else:
print(f"Cluster 0x{path.Cluster:02x} not found")
Expand All @@ -1214,6 +1250,8 @@ def process_packet(self, address, data):
print(f"Cluster 0x{path.Cluster:02x} not found")
response = interaction_model.ReportDataMessage()
response.AttributeReports = attribute_reports
for a in attribute_reports:
print(a)
exchange.send(
ProtocolId.INTERACTION_MODEL,
InteractionModelOpcode.REPORT_DATA,
Expand All @@ -1225,13 +1263,7 @@ def process_packet(self, address, data):
message.application_payload[0], message.application_payload[1:]
)
for invoke in invoke_request.InvokeRequests:
print(invoke)
path = invoke.CommandPath
print(path)
command = "*" if path.Command is None else f"0x{path.Command:04x}"
print(
f"Invoke Endpoint: {path.Endpoint}, Cluster: 0x{path.Cluster:04x}, Command: {command}"
)
invoke_responses = []
if path.Endpoint is None:
# Wildcard so we get it from every endpoint.
Expand Down Expand Up @@ -1267,3 +1299,7 @@ def process_packet(self, address, data):
)
elif protocol_opcode == InteractionModelOpcode.INVOKE_RESPONSE:
print("Received Invoke Response")
else:
print(message)
print("application payload", message.application_payload.hex(" "))
print()
178 changes: 160 additions & 18 deletions circuitmatter/data_model.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import enum
import random
import struct
from typing import Iterable
from typing import Iterable, Optional

from . import interaction_model
from . import tlv
Expand Down Expand Up @@ -131,10 +131,6 @@ def __init__(self, command_id, request_type, response_id, response_type):
self.response_id = response_id
self.response_type = response_type

def __call__(self, arg):
print("call command")
pass


class Cluster:
feature_map = FeatureMap()
Expand All @@ -159,7 +155,10 @@ def get_attribute_data(self, path) -> interaction_model.AttributeDataIB:
for field_name, descriptor in self._attributes():
if descriptor.id != path.Attribute:
continue
data.Data = descriptor.encode(getattr(self, field_name))
value = getattr(self, field_name)
print("encoding anything", value)
data.Data = descriptor.encode(value)
print("get", field_name, data.Data.hex(" "))
found = True
break
if not found:
Expand All @@ -173,18 +172,33 @@ def _commands(cls) -> Iterable[tuple[str, Command]]:
if not field_name.startswith("_") and isinstance(descriptor, Command):
yield field_name, descriptor

def invoke(self, path, fields) -> bytes:
print("invoke", path.Command)
def invoke(self, path, fields) -> Optional[interaction_model.CommandDataIB]:
found = False
for field_name, descriptor in self._commands():
if descriptor.command_id != path.Command:
continue

arg = descriptor.request_type.from_value(fields)
print("invoke", field_name, descriptor, arg)
result = getattr(self, field_name)(arg)
return descriptor.response_type.encode(result)
print("invoke", self, field_name, descriptor)
print(arg)
command = getattr(self, field_name)
if callable(command):
result = command(arg)
else:
print(field_name, "not implemented")
return None
print("result", result)
cdata = interaction_model.CommandDataIB()
response_path = interaction_model.CommandPathIB()
response_path.Endpoint = path.Endpoint
response_path.Cluster = path.Cluster
response_path.Command = descriptor.response_id
cdata.CommandPath = response_path
if result:
cdata.CommandFields = descriptor.response_type.encode(result)
return cdata
if not found:
print("not found", path.Attribute)
print("not found", path.Command)
return None


Expand Down Expand Up @@ -269,18 +283,19 @@ class CommissioningErrorEnum(Enum8):
BUSY_WITH_OTHER_ADMIN = 4


class RegulatoryLocationType(Enum8):
INDOOR = 0
OUTDOOR = 1
INDOOR_OUTDOOR = 2


class GeneralCommissioningCluster(Cluster):
CLUSTER_ID = 0x0030

class BasicCommissioningInfo(tlv.Structure):
FailSafeExpiryLengthSeconds = tlv.IntMember(0, signed=False, octets=2)
MaxCumulativeFailsafeSeconds = tlv.IntMember(1, signed=False, octets=2)

class RegulatoryLocationType(Enum8):
INDOOR = 0
OUTDOOR = 1
INDOOR_OUTDOOR = 2

breadcrumb = NumberAttribute(0, signed=False, bits=64, default=0)
basic_commissioning_info = StructAttribute(1, BasicCommissioningInfo)
regulatory_config = EnumAttribute(
Expand All @@ -295,14 +310,29 @@ class ArmFailSafe(tlv.Structure):
ExpiryLengthSeconds = tlv.IntMember(0, signed=False, octets=2, default=900)
Breadcrumb = tlv.IntMember(1, signed=False, octets=8)

class ArmFailSafeResponse(tlv.Structure):
class CommissioningResponse(tlv.Structure):
ErrorCode = tlv.EnumMember(
0, CommissioningErrorEnum, default=CommissioningErrorEnum.OK
)
DebugText = tlv.UTF8StringMember(1, max_length=128, default="")

ArmFailSafeResponse = CommissioningResponse

arm_fail_safe = Command(0x00, ArmFailSafe, 0x01, ArmFailSafeResponse)

class SetRegulatoryConfig(tlv.Structure):
NewRegulatoryConfig = tlv.EnumMember(0, RegulatoryLocationType)
CountryCode = tlv.UTF8StringMember(1, max_length=2)
Breadcrumb = tlv.IntMember(2, signed=False, octets=8)

SetRegulatoryConfigResponse = CommissioningResponse

set_regulatory_config = Command(
0x02, SetRegulatoryConfig, 0x03, SetRegulatoryConfigResponse
)

commissioning_complete = Command(0x04, None, 0x05, CommissioningResponse)


class NetworkCommissioningCluster(Cluster):
CLUSTER_ID = 0x0031
Expand Down Expand Up @@ -363,3 +393,115 @@ class NetworkCommissioningStatus(Enum8):
supported_wifi_bands = ListAttribute(8)
supported_thread_features = BitmapAttribute(9)
thread_version = NumberAttribute(10, signed=False, bits=16)


class CertificateChainTypeEnum(Enum8):
DAC = 1
PAI = 2


class NodeOperationalCertStatusEnum(Enum8):
OK = 0
"""OK, no error"""
INVALID_PUBLIC_KEY = 1
"""Public Key in the NOC does not match the public key in the NOCSR"""
INVALID_NODE_OP_ID = 2
"""The Node Operational ID in the NOC is not formatted correctly."""
INVALID_NOC = 3
"""Any other validation error in NOC chain"""
MISSING_CSR = 4
"""No record of prior CSR for which this NOC could match"""
TABLE_FULL = 5
"""NOCs table full, cannot add another one"""
INVALID_ADMIN_SUBJECT = 6
"""Invalid CaseAdminSubject field for an AddNOC command."""
FABRIC_CONFLICT = 9
"""Trying to AddNOC instead of UpdateNOC against an existing Fabric."""
LABEL_CONFLICT = 10
"""Label already exists on another Fabric."""
INVALID_FABRIC_INDEX = 11
"""FabricIndex argument is invalid."""


RESP_MAX = 900


class NodeOperationalCredentialsCluster(Cluster):
CLUSTER_ID = 0x003E

class NOCStruct(tlv.Structure):
NOC = tlv.OctetStringMember(0, 400)
ICAC = tlv.OctetStringMember(1, 400)

class FabricDescriptorStruct(tlv.Structure):
RootPublicKey = tlv.OctetStringMember(1, 65)
VendorID = tlv.IntMember(2, signed=False, octets=2)
FabricID = tlv.IntMember(3, signed=False, octets=2)
NodeID = tlv.IntMember(4, signed=False, octets=8)
Label = tlv.UTF8StringMember(5, max_length=32, default="")

class AttestationRequest(tlv.Structure):
AttestationNonce = tlv.OctetStringMember(0, 32)

class AttestationResponse(tlv.Structure):
AttestationElements = tlv.OctetStringMember(0, RESP_MAX)
AttestationSignature = tlv.OctetStringMember(1, 64)

class CertificateChainRequest(tlv.Structure):
CertificateType = tlv.EnumMember(0, CertificateChainTypeEnum)

class CertificateChainResponse(tlv.Structure):
Certificate = tlv.OctetStringMember(0, 600)

class CSRRequest(tlv.Structure):
CSRNonce = tlv.OctetStringMember(0, 32)
IsForUpdateNOC = tlv.BoolMember(1, optional=True, default=False)

class CSRResponse(tlv.Structure):
CSR = tlv.OctetStringMember(0, RESP_MAX)
AttestationSignature = tlv.OctetStringMember(1, 64)

class AddNOC(tlv.Structure):
NOCValue = tlv.OctetStringMember(0, 400)
ICACValue = tlv.OctetStringMember(1, 400, optional=True)
IPKValue = tlv.OctetStringMember(2, 16)
CaseAdminSubject = tlv.IntMember(3, signed=False, octets=8)
AdminVendorId = tlv.IntMember(4, signed=False, octets=2)

class UpdateNOC(tlv.Structure):
NOCValue = tlv.OctetStringMember(0, 400)
ICACValue = tlv.OctetStringMember(1, 400, optional=True)

class NOCResponse(tlv.Structure):
StatusCode = tlv.EnumMember(0, NodeOperationalCertStatusEnum)
FabricIndex = tlv.IntMember(1, signed=False, octets=1, optional=True)
DebugText = tlv.UTF8StringMember(2, max_length=128, optional=True)

class UpdateFabricLabel(tlv.Structure):
Label = tlv.UTF8StringMember(0, max_length=32)

class RemoveFabric(tlv.Structure):
FabricIndex = tlv.IntMember(0, signed=False, octets=1)

class AddTrustedRootCertificate(tlv.Structure):
RootCACertificate = tlv.OctetStringMember(0, 400)

attestation_request = Command(0x00, AttestationRequest, 0x01, AttestationResponse)

certificate_chain_request = Command(
0x02, CertificateChainRequest, 0x03, CertificateChainResponse
)

csr_request = Command(0x04, CSRRequest, 0x05, CSRResponse)

add_noc = Command(0x06, AddNOC, 0x08, NOCResponse)

update_noc = Command(0x07, UpdateNOC, 0x08, NOCResponse)

update_fabric_label = Command(0x09, UpdateFabricLabel, 0x08, NOCResponse)

remove_fabric = Command(0x0A, RemoveFabric, 0x08, NOCResponse)

add_trusted_root_certificate = Command(
0x0B, AddTrustedRootCertificate, 0x08, NOCResponse
)
Loading

0 comments on commit 391fc0d

Please sign in to comment.