From 1894465897fde883153e823afc8577d63f738357 Mon Sep 17 00:00:00 2001 From: Jerry Johns Date: Thu, 18 Aug 2022 09:48:09 -0700 Subject: [PATCH] CertificateAuthority + Manager support in Python (#21981) * CertificateAuthority + Manager support in Python This shifts the logic in the existing FabricAdmin that manages a given Root CA to its own CertificateAuthority class. This now permits a more spec-aligned structure that has a CertificateAuthorityManager that manages a set of CertificateAuthority instances, each associated with a single Root PK. Each of those manages a list of FabricAdmins adminstering a fabric within that CA, which in turn manage a list of ChipDeviceController instances within that fabric. These now permit passing in separate PersistentStorage instances so that it is more flexible/easier to sand-box each CA's storage constructs, which makes it easier to integrate with chip-tool's INI files. The PersistentStorage construct has been updated to permit both storage to file as well as just a 'soft' cache. * Review feedback --- scripts/tools/convert_ini.py | 70 +++-- src/controller/python/BUILD.gn | 1 + .../ChipDeviceController-ScriptBinding.cpp | 44 ++- src/controller/python/OpCredsBinding.cpp | 12 +- .../python/chip/CertificateAuthority.py | 291 ++++++++++++++++++ src/controller/python/chip/ChipDeviceCtrl.py | 8 +- src/controller/python/chip/ChipReplStartup.py | 85 ++--- src/controller/python/chip/ChipStack.py | 4 +- src/controller/python/chip/FabricAdmin.py | 183 ++++------- .../python/chip/storage/__init__.py | 162 +++++++--- .../python/test/test_scripts/base.py | 24 +- src/python_testing/TC_SC_3_6.py | 4 +- src/python_testing/matter_testing_support.py | 49 +-- 13 files changed, 610 insertions(+), 327 deletions(-) create mode 100644 src/controller/python/chip/CertificateAuthority.py diff --git a/scripts/tools/convert_ini.py b/scripts/tools/convert_ini.py index a8c17157ffd802..d1abd22c4498df 100755 --- a/scripts/tools/convert_ini.py +++ b/scripts/tools/convert_ini.py @@ -21,6 +21,8 @@ import click import typing import re +from os.path import exists +import logging def convert_ini_to_json(ini_dir: str, json_path: str): @@ -32,39 +34,55 @@ def convert_ini_to_json(ini_dir: str, json_path: str): """ python_json_store = {} - python_json_store['repl-config'] = { - 'fabricAdmins': { - '1': { - 'fabricId': 1, - 'vendorId': 65521 - }, - '2': { - 'fabricId': 2, - 'vendorId': 65521 - }, - '3': { - 'fabricId': 3, - 'vendorId': 65521 - } - } - } - - python_json_store['sdk-config'] = {} - - load_ini_into_dict(ini_file=ini_dir + '/chip_tool_config.alpha.ini', - json_dict=python_json_store['sdk-config'], replace_suffix='1') - load_ini_into_dict(ini_file=ini_dir + '/chip_tool_config.beta.ini', - json_dict=python_json_store['sdk-config'], replace_suffix='2') - load_ini_into_dict(ini_file=ini_dir + '/chip_tool_config.gamma.ini', - json_dict=python_json_store['sdk-config'], replace_suffix='3') + ini_file_paths = ['/chip_tool_config.alpha.ini', '/chip_tool_config.beta.ini', '/chip_tool_config.gamma.ini'] + counter = 1 + + for path in ini_file_paths: + full_path = ini_dir + path + if (exists(full_path)): + logging.critical(f"Found chip tool INI file at: {full_path} - Converting...") + create_repl_config_from_init(ini_file=full_path, + json_dict=python_json_store, replace_suffix=str(counter)) + counter = counter + 1 json_file = open(json_path, 'w') json.dump(python_json_store, json_file, ensure_ascii=True, indent=4) +def create_repl_config_from_init(ini_file: str, json_dict: typing.Dict, replace_suffix: str): + ''' This updates a provided JSON dictionary to create a REPL compliant configuration store that + contains the correct 'repl-config' and 'sdk-config' keys built from the provided chip-tool + INI file that contains the root public keys. The INI file will typically be named + with the word 'alpha', 'beta' or 'gamma' in the name. + + ini_file: Path to source INI file + json_dict: JSON dictionary to be updated. Multiple passes through this function using + the same dictionary is possible. + replace_suffix: The credentials in the INI file typically have keys that end with 0. This suffix + can be replaced with a different number. + ''' + if ('repl-config' not in json_dict): + json_dict['repl-config'] = {} + + if ('caList' not in json_dict['repl-config']): + json_dict['repl-config']['caList'] = {} + + json_dict['repl-config']['caList'][replace_suffix] = [ + {'fabricId': int(replace_suffix), 'vendorId': 0XFFF1} + ] + + if ('sdk-config' not in json_dict): + json_dict['sdk-config'] = {} + + load_ini_into_dict(ini_file=ini_file, json_dict=json_dict['sdk-config'], replace_suffix=replace_suffix) + + def load_ini_into_dict(ini_file: str, json_dict: typing.Dict, replace_suffix: str): - """ Loads the specific INI file into the provided dictionary. A 'replace_suffix' string + """ Loads the specific INI file containing CA credential information into the provided dictionary. A 'replace_suffix' string has to be provided to convert the existing numerical suffix to a different value. + + NOTE: This does not do any conversion of the keys into a format acceptable by the Python REPL environment. Please see + create_repl_config_from_init above if that is desired. """ config = ConfigParser() diff --git a/src/controller/python/BUILD.gn b/src/controller/python/BUILD.gn index ab84ff86c864cf..21897af175f92d 100644 --- a/src/controller/python/BUILD.gn +++ b/src/controller/python/BUILD.gn @@ -185,6 +185,7 @@ chip_python_wheel_action("chip-core") { { src_dir = "." sources = [ + "chip/CertificateAuthority.py", "chip/ChipBleBase.py", "chip/ChipBleUtility.py", "chip/ChipBluezMgr.py", diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp index 7a4189a52680d2..6e74ded2cd8a32 100644 --- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp +++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp @@ -100,7 +100,6 @@ chip::Controller::CommissioningParameters sCommissioningParameters; chip::Controller::ScriptDevicePairingDelegate sPairingDelegate; chip::Controller::ScriptPairingDeviceDiscoveryDelegate sPairingDeviceDiscoveryDelegate; -chip::Controller::Python::StorageAdapter * sStorageAdapter = nullptr; chip::Credentials::GroupDataProviderImpl sGroupDataProvider; chip::Credentials::PersistentStorageOpCertStore sPersistentStorageOpCertStore; @@ -111,7 +110,7 @@ chip::NodeId kDefaultLocalDeviceId = chip::kTestControllerNodeId; chip::NodeId kRemoteDeviceId = chip::kTestDeviceNodeId; extern "C" { -ChipError::StorageType pychip_DeviceController_StackInit(); +ChipError::StorageType pychip_DeviceController_StackInit(Controller::Python::StorageAdapter * storageAdapter); ChipError::StorageType pychip_DeviceController_StackShutdown(); ChipError::StorageType pychip_DeviceController_NewDeviceController(chip::Controller::DeviceCommissioner ** outDevCtrl, @@ -205,43 +204,40 @@ chip::ChipError::StorageType pychip_InteractionModel_ShutdownSubscription(Subscr // // Storage // -void pychip_Storage_InitializeStorageAdapter(chip::Controller::Python::PyObject * context, - chip::Controller::Python::SyncSetKeyValueCb setCb, - chip::Controller::Python::SetGetKeyValueCb getCb, - chip::Controller::Python::SyncDeleteKeyValueCb deleteCb); -void pychip_Storage_ShutdownAdapter(); +void * pychip_Storage_InitializeStorageAdapter(chip::Controller::Python::PyObject * context, + chip::Controller::Python::SyncSetKeyValueCb setCb, + chip::Controller::Python::SetGetKeyValueCb getCb, + chip::Controller::Python::SyncDeleteKeyValueCb deleteCb); +void pychip_Storage_ShutdownAdapter(chip::Controller::Python::StorageAdapter * storageAdapter); } -void pychip_Storage_InitializeStorageAdapter(chip::Controller::Python::PyObject * context, - chip::Controller::Python::SyncSetKeyValueCb setCb, - chip::Controller::Python::SetGetKeyValueCb getCb, - chip::Controller::Python::SyncDeleteKeyValueCb deleteCb) +void * pychip_Storage_InitializeStorageAdapter(chip::Controller::Python::PyObject * context, + chip::Controller::Python::SyncSetKeyValueCb setCb, + chip::Controller::Python::SetGetKeyValueCb getCb, + chip::Controller::Python::SyncDeleteKeyValueCb deleteCb) { - sStorageAdapter = new chip::Controller::Python::StorageAdapter(context, setCb, getCb, deleteCb); + auto ptr = new chip::Controller::Python::StorageAdapter(context, setCb, getCb, deleteCb); + return ptr; } -void pychip_Storage_ShutdownAdapter() +void pychip_Storage_ShutdownAdapter(chip::Controller::Python::StorageAdapter * storageAdapter) { - delete sStorageAdapter; + delete storageAdapter; } -chip::Controller::Python::StorageAdapter * pychip_Storage_GetStorageAdapter() +ChipError::StorageType pychip_DeviceController_StackInit(Controller::Python::StorageAdapter * storageAdapter) { - return sStorageAdapter; -} - -ChipError::StorageType pychip_DeviceController_StackInit() -{ - VerifyOrDie(sStorageAdapter != nullptr); + VerifyOrDie(storageAdapter != nullptr); FactoryInitParams factoryParams; - factoryParams.fabricIndependentStorage = sStorageAdapter; - sGroupDataProvider.SetStorageDelegate(sStorageAdapter); + factoryParams.fabricIndependentStorage = storageAdapter; + + sGroupDataProvider.SetStorageDelegate(storageAdapter); ReturnErrorOnFailure(sGroupDataProvider.Init().AsInteger()); factoryParams.groupDataProvider = &sGroupDataProvider; - ReturnErrorOnFailure(sPersistentStorageOpCertStore.Init(sStorageAdapter).AsInteger()); + ReturnErrorOnFailure(sPersistentStorageOpCertStore.Init(storageAdapter).AsInteger()); factoryParams.opCertStore = &sPersistentStorageOpCertStore; factoryParams.enableServerInteractions = true; diff --git a/src/controller/python/OpCredsBinding.cpp b/src/controller/python/OpCredsBinding.cpp index a17f726a314bc4..fa779e3d383fc0 100644 --- a/src/controller/python/OpCredsBinding.cpp +++ b/src/controller/python/OpCredsBinding.cpp @@ -97,7 +97,6 @@ class OperationalCredentialsAdapter : public OperationalCredentialsDelegate } // namespace Controller } // namespace chip -extern chip::Controller::Python::StorageAdapter * pychip_Storage_GetStorageAdapter(); extern chip::Credentials::GroupDataProviderImpl sGroupDataProvider; extern chip::Controller::ScriptDevicePairingDelegate sPairingDelegate; @@ -291,17 +290,13 @@ struct OpCredsContext void * mPyContext; }; -void * pychip_OpCreds_InitializeDelegate(void * pyContext, uint32_t fabricCredentialsIndex) +void * pychip_OpCreds_InitializeDelegate(void * pyContext, uint32_t fabricCredentialsIndex, + Controller::Python::StorageAdapter * storageAdapter) { auto context = Platform::MakeUnique(); context->mAdapter = Platform::MakeUnique(fabricCredentialsIndex); - if (pychip_Storage_GetStorageAdapter() == nullptr) - { - return nullptr; - } - - if (context->mAdapter->Initialize(*pychip_Storage_GetStorageAdapter()) != CHIP_NO_ERROR) + if (context->mAdapter->Initialize(*storageAdapter) != CHIP_NO_ERROR) { return nullptr; } @@ -339,6 +334,7 @@ ChipError::StorageType pychip_OpCreds_AllocateController(OpCredsContext * contex { paaTrustStorePath = "./credentials/development/paa-root-certs"; } + ChipLogProgress(Support, "Using device attestation PAA trust store path %s.", paaTrustStorePath); // Initialize device attestation verifier diff --git a/src/controller/python/chip/CertificateAuthority.py b/src/controller/python/chip/CertificateAuthority.py new file mode 100644 index 00000000000000..aa9011fb60a250 --- /dev/null +++ b/src/controller/python/chip/CertificateAuthority.py @@ -0,0 +1,291 @@ +# +# Copyright (c) 2021 Project CHIP Authors +# All rights reserved. +# +# 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. +# + +# Needed to use types in type hints before they are fully defined. +from __future__ import annotations + +import ctypes +from dataclasses import dataclass, field +from typing import * +from ctypes import * +from rich.pretty import pprint +import json +import logging +import builtins +import base64 +import chip.exceptions +from chip import ChipDeviceCtrl +from chip import ChipStack +from chip import FabricAdmin +from chip.storage import PersistentStorage + + +class CertificateAuthority: + ''' This represents an operational Root Certificate Authority (CA) with a root key key pair with associated public key (i.e "Root PK") . This manages + a set of FabricAdmin objects, each administering a fabric identified by a unique FabricId scoped to it. + + Each CertificateAuthority instance is tied to a 'CA index' that is used to look-up the list of fabrics already setup previously + in the provided PersistentStorage object. + + >> C++ Binding Details + + Each CertificateAuthority instance is associated with a single instance of the OperationalCredentialsAdapter. This adapter instance implements + the OperationalCredentialsDelegate and is meant to provide a Python adapter to the functions in that delegate. It relies on the in-built + ExampleOperationalCredentialsIssuer to then generate certificate material for the CA. This instance also uses the 'CA index' to + store/look-up the associated credential material from the provided PersistentStorage object. + ''' + @classmethod + def _Handle(cls): + return chip.native.GetLibraryHandle() + + @classmethod + def logger(cls): + return logging.getLogger('CertificateAuthority') + + def __init__(self, chipStack: ChipStack.ChipStack, caIndex: int, persistentStorage: PersistentStorage = None): + ''' Initializes the CertificateAuthority. This will set-up the associated C++ OperationalCredentialsAdapter + as well. + + Arguments: + chipStack: A reference to a chip.ChipStack object. + caIndex: An index used to look-up details about stored credential material and fabrics from persistent storage. + persistentStorage: An optional reference to a PersistentStorage object. If one is provided, it will pick that over + the default PersistentStorage object retrieved from the chipStack. + ''' + self.logger().warning(f"New CertificateAuthority at index {caIndex}") + + self._chipStack = chipStack + self._caIndex = caIndex + + self._Handle().pychip_OpCreds_InitializeDelegate.restype = c_void_p + self._Handle().pychip_OpCreds_InitializeDelegate.argtypes = [ctypes.py_object, ctypes.c_uint32, ctypes.c_void_p] + + if (persistentStorage is None): + persistentStorage = self._chipStack.GetStorageManager() + + self._persistentStorage = persistentStorage + + self._closure = self._chipStack.Call( + lambda: self._Handle().pychip_OpCreds_InitializeDelegate( + ctypes.py_object(self), ctypes.c_uint32(self._caIndex), self._persistentStorage.GetSdkStorageObject()) + ) + + if (self._closure is None): + raise ValueError("Encountered error initializing OpCreds adapter") + + self._isActive = True + self._activeAdmins = [] + + def LoadFabricAdminsFromStorage(self): + ''' If FabricAdmins had been setup previously, this re-creates them using information from persistent storage. + Otherwise, it initializes the REPL keys in persistent storage to sane defaults. This includes a top-level + key identifying the CA (using the associated CA Index) initialized to an empty list. + + This expects a 'caList' key to be present in the REPL config. + + Each FabricAdmin that is added there-after will insert a dictionary item into that list containing + 'fabricId' and 'vendorId' keys. + ''' + if (not(self._isActive)): + raise RuntimeError("Object isn't active") + + self.logger().warning("Loading fabric admins from storage...") + + caList = self._persistentStorage.GetReplKey(key='caList') + if (str(self._caIndex) not in caList): + caList[str(self._caIndex)] = [] + self._persistentStorage.SetReplKey(key='caList', value=caList) + + fabricAdminMetadataList = self._persistentStorage.GetReplKey(key='caList')[str(self._caIndex)] + for adminMetadata in fabricAdminMetadataList: + self.NewFabricAdmin(vendorId=int(adminMetadata['vendorId']), fabricId=int(adminMetadata['fabricId'])) + + def NewFabricAdmin(self, vendorId: int, fabricId: int): + ''' Creates a new FabricAdmin object initialized with the provided vendorId and fabricId values. + + This will update the REPL keys in persistent storage IF a 'caList' key is present. If it isn't, + will avoid making any updates. + ''' + if (not(self._isActive)): + raise RuntimeError( + f"CertificateAuthority object was previously shutdown and is no longer valid!") + + if (vendorId is None or fabricId is None): + raise ValueError(f"Invalid values for fabricId and vendorId") + + for existingAdmin in self._activeAdmins: + if (existingAdmin.fabricId == fabricId): + raise ValueError(f"Provided fabricId of {fabricId} collides with an existing FabricAdmin instance!") + + fabricAdmin = FabricAdmin.FabricAdmin(self, vendorId=vendorId, fabricId=fabricId) + + caList = self._persistentStorage.GetReplKey('caList') + if (caList is not None): + replFabricEntry = {'fabricId': fabricId, 'vendorId': vendorId} + + if (replFabricEntry not in caList[str(self._caIndex)]): + caList[str(self._caIndex)].append(replFabricEntry) + + self._persistentStorage.SetReplKey(key='caList', value=caList) + + self._activeAdmins.append(fabricAdmin) + + return fabricAdmin + + def Shutdown(self): + ''' Shuts down all active FabricAdmin objects managed by this CertificateAuthority before + shutting itself down. + + You cannot interact with this object there-after. + ''' + if (self._isActive): + for admin in self._activeAdmins: + admin.Shutdown() + + self._activeAdmins = [] + self._Handle().pychip_OpCreds_FreeDelegate.argtypes = [ctypes.c_void_p] + self._chipStack.Call( + lambda: self._Handle().pychip_OpCreds_FreeDelegate( + ctypes.c_void_p(self._closure)) + ) + + self._isActive = False + + def GetOpCredsContext(self): + ''' Returns a pointer to the underlying C++ OperationalCredentialsAdapter. + ''' + if (not(self._isActive)): + raise RuntimeError("Object isn't active") + + return self._closure + + @property + def caIndex(self) -> int: + return self._caIndex + + @property + def adminList(self) -> list[FabricAdmin.FabricAdmin]: + return self._activeAdmins + + def __del__(self): + self.Shutdown() + + +class CertificateAuthorityManager: + ''' Manages a set of CertificateAuthority instances. + ''' + @classmethod + def _Handle(cls): + return chip.native.GetLibraryHandle() + + @classmethod + def logger(cls): + return logging.getLogger('CertificateAuthorityManager') + + def __init__(self, chipStack: ChipStack.ChipStack, persistentStorage: PersistentStorage = None): + ''' Initializes the manager. + + chipStack: Reference to a chip.ChipStack object that is used to initialize + CertificateAuthority instances. + + persistentStorage: If provided, over-rides the default instance in the provided chipStack + when initializing CertificateAuthority instances. + ''' + self._activeCaIndexList = [] + self._chipStack = chipStack + + if (persistentStorage is None): + persistentStorage = self._chipStack.GetStorageManager() + + self._persistentStorage = persistentStorage + self._activeCaList = [] + self._isActive = True + + def _AllocateNextCaIndex(self): + ''' Allocate the next un-used CA index. + ''' + nextCaIndex = 1 + for ca in self._activeCaList: + nextCaIndex = ca.caIndex + 1 + return nextCaIndex + + def LoadAuthoritiesFromStorage(self): + ''' Loads any existing CertificateAuthority instances present in persistent storage. + If the 'caList' key is not present in the REPL config, it will create one. + ''' + if (not(self._isActive)): + raise RuntimeError("Object is not active") + + self.logger().warning("Loading certificate authorities from storage...") + + # + # Persist details to storage (read modify write). + # + caList = self._persistentStorage.GetReplKey('caList') + if (caList is None): + caList = {} + + for caIndex in caList: + ca = self.NewCertificateAuthority(int(caIndex)) + ca.LoadFabricAdminsFromStorage() + + def NewCertificateAuthority(self, caIndex: int = None): + ''' Creates a new CertificateAuthority instance with the provided CA Index and the PersistentStorage + instance previously setup in the constructor. + + This will write to the REPL keys in persistent storage to setup an empty list for the 'CA Index' + item. + ''' + if (not(self._isActive)): + raise RuntimeError("Object is not active") + + if (caIndex is None): + caIndex = self._AllocateNextCaIndex() + + # + # Persist details to storage (read modify write). + # + caList = self._persistentStorage.GetReplKey('caList') + if (caList is None): + caList = {} + + if (str(caIndex) not in caList): + caList[str(caIndex)] = [] + self._persistentStorage.SetReplKey(key='caList', value=caList) + + ca = CertificateAuthority(chipStack=self._chipStack, caIndex=caIndex, persistentStorage=self._persistentStorage) + self._activeCaList.append(ca) + + return ca + + def Shutdown(self): + ''' Shuts down all active CertificateAuthority instances tracked by this manager, before shutting itself down. + + You cannot interact with this object there-after. + ''' + for ca in self._activeCaList: + ca.Shutdown() + + self._activeCaList = [] + self._isActive = False + + @property + def activeCaList(self) -> List[CertificateAuthority]: + return self._activeCaList + + def __del__(self): + self.Shutdown() diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py index 30064a1695ace7..edac6bc8f516dc 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -181,10 +181,10 @@ def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, nodeId: int, self._fabricAdmin = fabricAdmin self._fabricId = fabricId self._nodeId = nodeId - self._adminIndex = fabricAdmin.adminIndex + self._caIndex = fabricAdmin.caIndex if name is None: - self._name = "adminIndex(%x)/fabricId(0x%016X)/nodeId(0x%016X)" % (fabricAdmin.adminIndex, fabricId, nodeId) + self._name = "caIndex(%x)/fabricId(0x%016X)/nodeId(0x%016X)" % (fabricAdmin.caIndex, fabricId, nodeId) else: self._name = name @@ -258,8 +258,8 @@ def fabricId(self) -> int: return self._fabricId @property - def adminIndex(self) -> int: - return self._adminIndex + def caIndex(self) -> int: + return self._caIndex @property def name(self) -> str: diff --git a/src/controller/python/chip/ChipReplStartup.py b/src/controller/python/chip/ChipReplStartup.py index cb38cc37032c92..519c292ba9d9d8 100644 --- a/src/controller/python/chip/ChipReplStartup.py +++ b/src/controller/python/chip/ChipReplStartup.py @@ -12,6 +12,7 @@ import argparse import builtins import chip.FabricAdmin +import chip.CertificateAuthority import chip.native from chip.utils import CommissioningBuildingBlocks import atexit @@ -19,60 +20,6 @@ _fabricAdmins = None -def LoadFabricAdmins(): - global _fabricAdmins - - # - # Shutdown any fabric admins we had before as well as active controllers. This ensures we - # relinquish some resources if this is called multiple times (e.g in a Jupyter notebook) - # - chip.FabricAdmin.FabricAdmin.ShutdownAll() - ChipDeviceCtrl.ChipDeviceController.ShutdownAll() - - _fabricAdmins = [] - storageMgr = builtins.chipStack.GetStorageManager() - - console = Console() - - try: - adminList = storageMgr.GetReplKey('fabricAdmins') - except KeyError: - console.print( - "\n[purple]No previous fabric admins discovered in persistent storage - creating a new one...") - - # - # Initialite a FabricAdmin with a VendorID of TestVendor1 (0xfff1) - # - _fabricAdmins.append(chip.FabricAdmin.FabricAdmin(0XFFF1)) - return _fabricAdmins - - console.print('\n') - - for k in adminList: - console.print( - f"[purple]Restoring FabricAdmin from storage to manage FabricId {adminList[k]['fabricId']}, AdminIndex {k}...") - _fabricAdmins.append(chip.FabricAdmin.FabricAdmin(vendorId=int(adminList[k]['vendorId']), - fabricId=adminList[k]['fabricId'], adminIndex=int(k))) - - console.print( - '\n[blue]Fabric Admins have been loaded and are available at [red]fabricAdmins') - return _fabricAdmins - - -def CreateDefaultDeviceController(): - global _fabricAdmins - - if (len(_fabricAdmins) == 0): - raise RuntimeError("Was called before calling LoadFabricAdmins()") - - console = Console() - - console.print('\n') - console.print( - f"[purple]Creating default device controller on fabric {_fabricAdmins[0]._fabricId}...") - return _fabricAdmins[0].NewController() - - def ReplInit(debug): # # Install the pretty printer that rich provides to replace the existing @@ -105,9 +52,11 @@ def ReplInit(debug): logging.getLogger().setLevel(logging.WARN) +certificateAuthorityManager = None + + def StackShutdown(): - chip.FabricAdmin.FabricAdmin.ShutdownAll() - ChipDeviceCtrl.ChipDeviceController.ShutdownAll() + certificateAuthorityManager.Shutdown() builtins.chipStack.Shutdown() @@ -145,12 +94,30 @@ def mattersetdebug(enableDebugMode: bool = True): ReplInit(args.debug) chipStack = ChipStack(persistentStoragePath=args.storagepath) -fabricAdmins = LoadFabricAdmins() -devCtrl = CreateDefaultDeviceController() +certificateAuthorityManager = chip.CertificateAuthority.CertificateAuthorityManager(chipStack, chipStack.GetStorageManager()) +certificateAuthorityManager.LoadAuthoritiesFromStorage() + +if (len(certificateAuthorityManager.activeCaList) == 0): + ca = certificateAuthorityManager.NewCertificateAuthority() + ca.NewFabricAdmin(vendorId=0xFFF1, fabricId=1) +elif (len(certificateAuthorityManager.activeCaList[0].adminList) == 0): + certificateAuthorityManager.activeCaList[0].NewFabricAdmin(vendorId=0xFFF1, fabricId=1) + +caList = certificateAuthorityManager.activeCaList + +devCtrl = caList[0].adminList[0].NewController() builtins.devCtrl = devCtrl atexit.register(StackShutdown) console.print( - '\n\n[blue]Default CHIP Device Controller has been initialized to manage [bold red]fabricAdmins[0][blue], and is available as [bold red]devCtrl') + '\n\n[blue]The following objects have been created:') + +console.print( + '''\t[red]certificateAuthorityManager[blue]:\tManages a list of CertificateAuthority instances. +\t[red]caList[blue]:\t\t\t\tThe list of CertificateAuthority instances. +\t[red]caList\[n]\[m][blue]:\t\t\tA specific FabricAdmin object at index m for the nth CertificateAuthority instance.''') + +console.print( + f'\n\n[blue]Default CHIP Device Controller (NodeId: {devCtrl.nodeId}): has been initialized to manage [bold red]caList[0].adminList[0][blue] (FabricId = {caList[0].adminList[0].fabricId}), and is available as [bold red]devCtrl') diff --git a/src/controller/python/chip/ChipStack.py b/src/controller/python/chip/ChipStack.py index 5120e68fb8b3b2..cfdeeaef9a5785 100644 --- a/src/controller/python/chip/ChipStack.py +++ b/src/controller/python/chip/ChipStack.py @@ -266,7 +266,7 @@ def HandleChipThreadRun(callback): self._persistentStorage = PersistentStorage(persistentStoragePath) # Initialize the chip stack. - res = self._ChipStackLib.pychip_DeviceController_StackInit() + res = self._ChipStackLib.pychip_DeviceController_StackInit(self._persistentStorage.GetSdkStorageObject()) if res != 0: raise self.ErrorToException(res) @@ -440,7 +440,7 @@ def _loadLib(self): self._ChipStackLib = chip.native.GetLibraryHandle() self._chipDLLPath = chip.native.FindNativeLibraryPath() - self._ChipStackLib.pychip_DeviceController_StackInit.argtypes = [] + self._ChipStackLib.pychip_DeviceController_StackInit.argtypes = [c_void_p] self._ChipStackLib.pychip_DeviceController_StackInit.restype = c_uint32 self._ChipStackLib.pychip_DeviceController_StackShutdown.argtypes = [] self._ChipStackLib.pychip_DeviceController_StackShutdown.restype = c_uint32 diff --git a/src/controller/python/chip/FabricAdmin.py b/src/controller/python/chip/FabricAdmin.py index d44175819ba16a..216da4ebad7646 100644 --- a/src/controller/python/chip/FabricAdmin.py +++ b/src/controller/python/chip/FabricAdmin.py @@ -30,70 +30,29 @@ import chip.exceptions from chip import ChipDeviceCtrl import copy +from .storage import PersistentStorage +from chip.CertificateAuthority import CertificateAuthority class FabricAdmin: - ''' Administers a specific fabric as identified by the tuple of RCAC subject public key and Fabric ID. - The Fabric ID can be passed into the constructor while the RCAC and ICAC are generated. - The Fabric ID *does not* have to be unique across multiple FabricAdmin instances as - it is scoped to the key pair used by the root CA and whose public key is in the RCAC. - - Each admin is identified by an 'admin index' that is unique to the running - process. This is used to store credential information to disk so that - it can be easily loaded later if neccessary (see 'Persistence' below for more details) - - When vending ChipDeviceController instances on a given fabric, each controller instance - is associated with a unique fabric index. In the underlying FabricTable, each FabricInfo - instance can be treated as unique identities that can collide on the same logical fabric. - - >> C++ Binding Details - - Each instance of the fabric admin is associated with a single instance - of the OperationalCredentialsAdapter. This adapter instance implements - the OperationalCredentialsDelegate and is meant to provide a Python - adapter to the functions in that delegate so that the fabric admin - can in turn, provide users the ability to generate their own NOCs for devices - on the network (not implemented yet). For now, it relies on the in-built - ExampleOperationalCredentialsIssuer to do that. - - TODO: Add support for FabricAdmin to permit callers to hook up their own GenerateNOC - logic. - - >> Persistence - - Specifically, each instance persists its fabric ID and admin - index to storage. This is in addition to the persistence built into the ExampleOperationalCredentialsIssuer that persists details - about the RCAC/ICAC and associated keys as well. This facilitates re-construction of a fabric admin on subsequent - boot for a given fabric and ensuring it automatically picks up the right ICAC/RCAC details as well. + ''' Administers a fabric associated with a unique FabricID under a given CertificateAuthority + instance. ''' - - activeAdminIndexList = set() - activeAdmins = set() - @classmethod def _Handle(cls): return chip.native.GetLibraryHandle() - def AllocateNextAdminIndex(self): - ''' Allocate the next un-used admin index. - ''' - nextAdminIndex = 1 - while nextAdminIndex in FabricAdmin.activeAdminIndexList: - nextAdminIndex = nextAdminIndex + 1 - return nextAdminIndex - - def __init__(self, vendorId: int, adminIndex: int = None, fabricId: int = 1): - ''' Creates a valid FabricAdmin object with valid RCAC/ICAC, and registers itself as an OperationalCredentialsDelegate - for other parts of the system (notably, DeviceController) to vend NOCs. - - vendorId: Valid operational Vendor ID associated with this fabric. - adminIndex: Local index to be associated with this fabric. This is NOT the fabric index. Each controller on the fabric - is assigned a unique fabric index. + @classmethod + def logger(cls): + return logging.getLogger('FabricAdmin') - If omitted, one will be automatically assigned. + def __init__(self, certificateAuthority: CertificateAuthority, vendorId: int, fabricId: int = 1): + ''' Initializes the object. - fabricId: Fabric ID to be associated with this fabric. This is scoped to the public key of the resultant - root generated by the underlying ExampleOperationalCredentialsIssuer. + certificateAuthority: CertificateAuthority instance that will be used to vend NOCs for both + DeviceControllers and commissionable nodes on this fabric. + vendorId: Valid operational Vendor ID associated with this fabric. + fabricId: Fabric ID to be associated with this fabric. ''' self._handle = chip.native.GetLibraryHandle() @@ -101,104 +60,68 @@ def __init__(self, vendorId: int, adminIndex: int = None, fabricId: int = 1): raise ValueError( f"Invalid VendorID ({vendorId}) provided!") + if (fabricId is None or fabricId == 0): + raise ValueError( + f"Invalid FabricId ({fabricId}) provided!") + self._vendorId = vendorId self._fabricId = fabricId + self._certificateAuthority = certificateAuthority - if (adminIndex is None): - self._adminIndex = self.AllocateNextAdminIndex() - else: - if (adminIndex in FabricAdmin.activeAdminIndexList): - raise ValueError( - f"AdminIndex {adminIndex} is already being managed by an existing FabricAdmin object!") - - self._adminIndex = adminIndex - - FabricAdmin.activeAdminIndexList.add(self._adminIndex) - - print( - f"New FabricAdmin: FabricId: 0x{self._fabricId:016X}, AdminIndex: {self._adminIndex}, VendorId = 0x{self.vendorId:04X}") - self._Handle().pychip_OpCreds_InitializeDelegate.restype = c_void_p + self.logger().warning(f"New FabricAdmin: FabricId: 0x{self._fabricId:016X}, VendorId = 0x{self.vendorId:04X}") - self.closure = builtins.chipStack.Call( - lambda: self._Handle().pychip_OpCreds_InitializeDelegate( - ctypes.py_object(self), ctypes.c_uint32(self._adminIndex)) - ) - - if (self.closure is None): - raise ValueError("Encountered error initializing OpCreds adapter") - - # - # Persist details to storage (read modify write). - # - try: - adminList = builtins.chipStack.GetStorageManager().GetReplKey('fabricAdmins') - except KeyError: - adminList = {str(self._adminIndex): {'fabricId': self._fabricId}} - builtins.chipStack.GetStorageManager().SetReplKey('fabricAdmins', adminList) + self._isActive = True + self._activeControllers = [] - adminList[str(self._adminIndex)] = {'fabricId': self._fabricId, 'vendorId': self.vendorId} - builtins.chipStack.GetStorageManager().SetReplKey('fabricAdmins', adminList) + def NewController(self, nodeId: int = None, paaTrustStorePath: str = "", useTestCommissioner: bool = False): + ''' Create a new chip.ChipDeviceCtrl.ChipDeviceController instance on this fabric. - self._isActive = True - self.nextControllerId = 112233 + When vending ChipDeviceController instances on a given fabric, each controller instance + is associated with a unique fabric index local to the running process. In the underlying FabricTable, each FabricInfo + instance can be treated as unique identities that can collide on the same logical fabric. - FabricAdmin.activeAdmins.add(self) + nodeId: NodeID to be assigned to the controller. Automatically allocates one starting from 112233 if one + is not provided. - def NewController(self, nodeId: int = None, paaTrustStorePath: str = "", useTestCommissioner: bool = False): - ''' Vend a new controller on this fabric seeded with the right fabric details. + paaTrustStorePath: Path to the PAA trust store. If one isn't provided, a suitable default is selected. + useTestCommissioner: If a test commmisioner is to be created. ''' if (not(self._isActive)): raise RuntimeError( f"FabricAdmin object was previously shutdown and is no longer valid!") + nodeIdList = [controller.nodeId for controller in self._activeControllers] if (nodeId is None): - nodeId = self.nextControllerId - self.nextControllerId = self.nextControllerId + 1 + if (len(nodeIdList) != 0): + nodeId = max(nodeIdList) + 1 + else: + nodeId = 112233 + else: + if (nodeId in nodeIdList): + raise RuntimeError(f"Provided NodeId {nodeId} collides with an existing controller instance!") - print( - f"Allocating new controller with FabricId: 0x{self._fabricId:016X}, NodeId: 0x{nodeId:016X}") + self.logger().warning( + f"Allocating new controller with CaIndex: {self._certificateAuthority.caIndex}, FabricId: 0x{self._fabricId:016X}, NodeId: 0x{nodeId:016X}") controller = ChipDeviceCtrl.ChipDeviceController( - self.closure, self._fabricId, nodeId, self.vendorId, paaTrustStorePath, useTestCommissioner, fabricAdmin=self) - return controller - - def ShutdownAll(): - ''' Shuts down all active fabrics, but without deleting them from storage. - ''' - activeAdmins = copy.copy(FabricAdmin.activeAdmins) + self._certificateAuthority.GetOpCredsContext(), self._fabricId, nodeId, self._vendorId, paaTrustStorePath, useTestCommissioner, fabricAdmin=self) - for admin in activeAdmins: - admin.Shutdown(False) - - FabricAdmin.activeAdmins.clear() + self._activeControllers.append(controller) + return controller - def Shutdown(self, deleteFromStorage: bool = True): - ''' Shutdown this fabric and free up its resources. This is important since relying - solely on the destructor will not guarantee relishining of C++-side resources. + def Shutdown(self): + ''' Shutdown all active controllers on the fabric before shutting down the fabric itself. - deleteFromStorage: Whether to delete this fabric's details from persistent storage. + You cannot interact with this object there-after. ''' if (self._isActive): - builtins.chipStack.Call( - lambda: self._Handle().pychip_OpCreds_FreeDelegate( - ctypes.c_void_p(self.closure)) - ) - - FabricAdmin.activeAdminIndexList.remove(self._adminIndex) - - if (deleteFromStorage): - adminList = builtins.chipStack.GetStorageManager().GetReplKey('fabricAdmins') - del(adminList[str(self._adminIndex)]) - if (len(adminList) == 0): - adminList = None + for controller in self._activeControllers: + controller.Shutdown() - builtins.chipStack.GetStorageManager().SetReplKey('fabricAdmins', adminList) - - FabricAdmin.activeAdmins.remove(self) self._isActive = False def __del__(self): - self.Shutdown(False) + self.Shutdown() @property def vendorId(self) -> int: @@ -209,5 +132,9 @@ def fabricId(self) -> int: return self._fabricId @property - def adminIndex(self) -> int: - return self._adminIndex + def caIndex(self) -> int: + return self._certificateAuthority.caIndex + + @property + def certificateAuthority(self) -> CertificateAuthority: + return self._certificateAuthority diff --git a/src/controller/python/chip/storage/__init__.py b/src/controller/python/chip/storage/__init__.py index 362abda084075c..ad51c75754f9f4 100644 --- a/src/controller/python/chip/storage/__init__.py +++ b/src/controller/python/chip/storage/__init__.py @@ -51,11 +51,7 @@ def _OnSyncGetKeyValueCb(storageObj, key: str, value, size, is_found): this method to the requirements of PersistentStorageDelegate::SyncGetKeyValue. ''' - try: - keyValue = storageObj.GetSdkKey(key.decode("utf-8")) - except Exception as ex: - keyValue = None - + keyValue = storageObj.GetSdkKey(key.decode("utf-8")) if (keyValue is not None): sizeOfValue = size[0] sizeToCopy = min(sizeOfValue, len(keyValue)) @@ -85,37 +81,94 @@ def _OnSyncDeleteKeyValueCb(storageObj, key): class PersistentStorage: + ''' Class that provided persistent storage to back both native Python and + SDK configuration key/value pairs. + + Configuration native to the Python libraries is organized under the top-level + 'repl-config' key while configuration native to the SDK and owned by the various + C++ logic is organized under the top-level 'sdk-config' key. + + This interfaces with a C++ adapter that implements the PersistentStorageDelegate interface + and can be passed into C++ logic that needs an instance of that interface. + ''' + @classmethod + def logger(cls): + return logging.getLogger('PersistentStorage') + + def __init__(self, path: str = None, jsonData: Dict = None): + ''' Initializes the object with either a path to a JSON file that contains the configuration OR + a JSON dictionary that contains an in-memory representation of the configuration. + + In either case, if there are no valid configurations that already exist, empty Python + and SDK configuration records will be created upon construction. + ''' + if (path is None and jsonData is None): + raise ValueError("Need to provide at least one of path or jsonData") + + if (path is not None and jsonData is not None): + raise ValueError("Can't provide both a valid path and jsonData") + + if (path is not None): + self.logger().warn(f"Initializing persistent storage from file: {path}") + else: + self.logger().warn(f"Initializing persistent storage from dict") - def __init__(self, path: str): - self._path = path self._handle = chip.native.GetLibraryHandle() self._isActive = True + self._path = path - try: - self._file = open(path, 'r') - self._file.seek(0, 2) - size = self._file.tell() - self._file.seek(0) + if (self._path): + try: + self._file = open(path, 'r') + self._file.seek(0, 2) + size = self._file.tell() + self._file.seek(0) - if (size != 0): - logging.critical(f"Loading configuration from {path}...") - self.jsonData = json.load(self._file) - else: - logging.warn( - f"No valid configuration present at {path} - clearing out configuration") - self.jsonData = {'repl-config': {}, 'sdk-config': {}} + if (size != 0): + self.logger().warn(f"Loading configuration from {path}...") + self._jsonData = json.load(self._file) + else: + self._jsonData = {} - except Exception as ex: - logging.error(ex) - logging.warn( - f"Could not load configuration from {path} - resetting configuration...") - self.jsonData = {'repl-config': {}, 'sdk-config': {}} + except Exception as ex: + logging.error(ex) + logging.critical(f"Could not load configuration from {path} - resetting configuration...") + self._jsonData = {} + else: + self._jsonData = jsonData + + if ('sdk-config' not in self._jsonData): + logging.warn(f"No valid SDK configuration present - clearing out configuration") + self._jsonData['sdk-config'] = {} + + if ('repl-config' not in self._jsonData): + logging.warn(f"No valid REPL configuration present - clearing out configuration") + self._jsonData['repl-config'] = {} + # Clear out the file so that calling 'Commit' will re-open the file at that time in write mode. self._file = None - self._handle.pychip_Storage_InitializeStorageAdapter(ctypes.py_object( + + self._handle.pychip_Storage_InitializeStorageAdapter.restype = c_void_p + self._handle.pychip_Storage_InitializeStorageAdapter.argtypes = [ctypes.py_object, + _SyncSetKeyValueCbFunct, _SyncGetKeyValueCbFunct, _SyncDeleteKeyValueCbFunct] + + self._closure = self._handle.pychip_Storage_InitializeStorageAdapter(ctypes.py_object( self), _OnSyncSetKeyValueCb, _OnSyncGetKeyValueCb, _OnSyncDeleteKeyValueCb) - def Sync(self): + def GetSdkStorageObject(self): + ''' Returns a ctypes c_void_p reference to the SDK-side adapter instance. + ''' + return self._closure + + def Commit(self): + ''' Commits the cached JSON configuration to file (if one was provided in the constructor). + Otherwise, this is a no-op. + ''' + self.logger().info("Committing...") + + if (self._path is None): + return + if (self._file is None): try: self._file = open(self._path, 'w') @@ -126,28 +179,38 @@ def Sync(self): return self._file.seek(0) - json.dump(self.jsonData, self._file, ensure_ascii=True, indent=4) + json.dump(self._jsonData, self._file, ensure_ascii=True, indent=4) self._file.truncate() self._file.flush() def SetReplKey(self, key: str, value): - logging.info(f"SetReplKey: {key} = {value}") + ''' Set a REPL key to a specific value. Creates the key if one doesn't exist already. + ''' + self.logger().info(f"SetReplKey: {key} = {value}") if (key is None or key == ''): raise ValueError("Invalid Key") if (value is None): - del(self.jsonData['repl-config'][key]) + del(self._jsonData['repl-config'][key]) else: - self.jsonData['repl-config'][key] = value + self._jsonData['repl-config'][key] = value - self.Sync() + self.Commit() def GetReplKey(self, key: str): - return copy.deepcopy(self.jsonData['repl-config'][key]) + ''' Retrieves the value of a REPL key. Returns 'None' if the key + doesn't exist. + ''' + if (key not in self._jsonData['repl-config']): + return None + + return copy.deepcopy(self._jsonData['repl-config'][key]) def SetSdkKey(self, key: str, value: bytes): - logging.info(f"SetSdkKey: {key} = {value}") + ''' Set an SDK key to a specific value. Creates the key if one doesn't exist already. + ''' + self.logger().info(f"SetSdkKey: {key} = {value}") if (key is None or key == ''): raise ValueError("Invalid Key") @@ -155,28 +218,45 @@ def SetSdkKey(self, key: str, value: bytes): if (value is None): raise ValueError('value is not expected to be None') else: - self.jsonData['sdk-config'][key] = base64.b64encode( + self._jsonData['sdk-config'][key] = base64.b64encode( value).decode("utf-8") - self.Sync() + self.Commit() def GetSdkKey(self, key: str): - return base64.b64decode(self.jsonData['sdk-config'][key]) + ''' Returns the SDK key if one exist. Otherwise, returns 'None'. + ''' + if (key not in self._jsonData['sdk-config']): + return None + + return base64.b64decode(self._jsonData['sdk-config'][key]) def DeleteSdkKey(self, key: str): - del(self.jsonData['sdk-config'][key]) - self.Sync() + ''' Deletes an SDK key if one exists. + ''' + self.logger().info(f"DeleteSdkKey: {key}") - def GetUnderlyingStorageAdapter(self): - return self._storageAdapterObj + del(self._jsonData['sdk-config'][key]) + self.Commit() def Shutdown(self): + ''' Shuts down the object by free'ing up the associated adapter instance. + + You cannot interact with this object there-after. + ''' + self._handle.pychip_Storage_ShutdownAdapter.argtypes = [c_void_p] builtins.chipStack.Call( - lambda: self._handle.pychip_Storage_ShutdownAdapter() + lambda: self._handle.pychip_Storage_ShutdownAdapter(self._closure) ) self._isActive = False + @property + def jsonData(self) -> Dict: + ''' Returns a copy of the internal cached JSON data. + ''' + return copy.deepcopy(self._jsonData) + def __del__(self): if (self._isActive): builtins.chipStack.Call( diff --git a/src/controller/python/test/test_scripts/base.py b/src/controller/python/test/test_scripts/base.py index 90bc43cb064781..3b9f3d9288a7ba 100644 --- a/src/controller/python/test/test_scripts/base.py +++ b/src/controller/python/test/test_scripts/base.py @@ -36,6 +36,7 @@ from chip.ChipStack import * import chip.native import chip.FabricAdmin +import chip.CertificateAuthority import copy import secrets import faulthandler @@ -193,8 +194,9 @@ def __init__(self, nodeid: int, paaTrustStorePath: str, testCommissioner: bool = chip.native.Init() self.chipStack = ChipStack('/tmp/repl_storage.json') - self.fabricAdmin = chip.FabricAdmin.FabricAdmin(vendorId=0XFFF1, - fabricId=1, adminIndex=1) + self.certificateAuthorityManager = chip.CertificateAuthority.CertificateAuthorityManager(chipStack=self.chipStack) + self.certificateAuthority = self.certificateAuthorityManager.NewCertificateAuthority() + self.fabricAdmin = self.certificateAuthority.NewFabricAdmin(vendorId=0xFFF1, fabricId=1) self.devCtrl = self.fabricAdmin.NewController( nodeid, paaTrustStorePath, testCommissioner) self.controllerNodeId = nodeid @@ -463,7 +465,8 @@ async def TestAddUpdateRemoveFabric(self, nodeid: int): self.logger.info("Waiting for attribute read for CommissionedFabrics") startOfTestFabricCount = await self._GetCommissonedFabricCount(nodeid) - tempFabric = chip.FabricAdmin.FabricAdmin(vendorId=0xFFF1) + tempCertificateAuthority = self.certificateAuthorityManager.NewCertificateAuthority() + tempFabric = tempCertificateAuthority.NewFabricAdmin(vendorId=0xFFF1, fabricId=1) tempDevCtrl = tempFabric.NewController(self.controllerNodeId, self.paaTrustStorePath) self.logger.info("Starting AddNOC using same node ID") @@ -628,8 +631,7 @@ async def TestMultiFabric(self, ip: str, setuppin: int, nodeid: int): await self.devCtrl.SendCommand(nodeid, 0, Clusters.AdministratorCommissioning.Commands.OpenBasicCommissioningWindow(180), timedRequestTimeoutMs=10000) self.logger.info("Creating 2nd Fabric Admin") - self.fabricAdmin2 = chip.FabricAdmin.FabricAdmin(vendorId=0xFFF1, - fabricId=2, adminIndex=2) + self.fabricAdmin2 = self.certificateAuthority.NewFabricAdmin(vendorId=0xFFF1, fabricId=2) self.logger.info("Creating Device Controller on 2nd Fabric") self.devCtrl2 = self.fabricAdmin2.NewController( @@ -646,15 +648,15 @@ async def TestMultiFabric(self, ip: str, setuppin: int, nodeid: int): self.logger.info( "Shutting down controllers & fabrics and re-initing stack...") - ChipDeviceCtrl.ChipDeviceController.ShutdownAll() - chip.FabricAdmin.FabricAdmin.ShutdownAll() + self.certificateAuthorityManager.Shutdown() self.logger.info("Shutdown completed, starting new controllers...") - self.fabricAdmin = chip.FabricAdmin.FabricAdmin(vendorId=0XFFF1, - fabricId=1, adminIndex=1) - fabricAdmin2 = chip.FabricAdmin.FabricAdmin(vendorId=0xFFF1, - fabricId=2, adminIndex=2) + self.certificateAuthorityManager = chip.CertificateAuthority.CertificateAuthorityManager(chipStack=self.chipStack) + self.certificateAuthority = self.certificateAuthorityManager.NewCertificateAuthority() + self.fabricAdmin = self.certificateAuthority.NewFabricAdmin(vendorId=0xFFF1, fabricId=1) + + fabricAdmin2 = self.certificateAuthority.NewFabricAdmin(vendorId=0xFFF1, fabricId=2) self.devCtrl = self.fabricAdmin.NewController( self.controllerNodeId, self.paaTrustStorePath) diff --git a/src/python_testing/TC_SC_3_6.py b/src/python_testing/TC_SC_3_6.py index ede817f8de4628..2c258c620d86a1 100644 --- a/src/python_testing/TC_SC_3_6.py +++ b/src/python_testing/TC_SC_3_6.py @@ -18,6 +18,7 @@ from matter_testing_support import MatterBaseTest, default_matter_test_main, async_test_body import chip.clusters as Clusters import chip.FabricAdmin +import chip.CertificateAuthority import logging from mobly import asserts from chip.utils import CommissioningBuildingBlocks @@ -137,7 +138,8 @@ async def test_TC_SC_3_6(self): for i in range(num_fabrics_to_commission - 1): admin_index = 2 + i logging.info("Commissioning fabric %d/%d" % (admin_index, num_fabrics_to_commission)) - new_fabric_admin = chip.FabricAdmin.FabricAdmin(vendorId=0xFFF1, adminIndex=admin_index) + new_certificate_authority = self.certificate_authority_manager.NewCertificateAuthority() + new_fabric_admin = new_certificate_authority.NewFabricAdmin(vendorId=0xFFF1, fabricId=1) new_admin_ctrl = new_fabric_admin.NewController(nodeId=dev_ctrl.nodeId) new_admin_ctrl.name = all_names.pop(0) client_list.append(new_admin_ctrl) diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py index 6064d46fda0dcd..607f24a057b9c3 100644 --- a/src/python_testing/matter_testing_support.py +++ b/src/python_testing/matter_testing_support.py @@ -25,6 +25,7 @@ import chip.logging import chip.native import chip.FabricAdmin +import chip.CertificateAuthority from chip.utils import CommissioningBuildingBlocks import builtins from typing import Optional, List, Tuple @@ -45,7 +46,6 @@ from mobly import utils from mobly.test_runner import TestRunner - # TODO: Add utility to commission a device if needed # TODO: Add utilities to keep track of controllers/fabrics @@ -157,7 +157,6 @@ class MatterStackState: def __init__(self, config: MatterTestConfig): self._logger = logger self._config = config - self._fabric_admins = [] if not hasattr(builtins, "chipStack"): chip.native.Init(bluetoothAdapter=config.ble_interface_id) @@ -180,22 +179,17 @@ def _init_stack(self, already_initialized: bool, **kwargs): builtins.chipStack = self._chip_stack self._storage = self._chip_stack.GetStorageManager() + self._certificate_authority_manager = chip.CertificateAuthority.CertificateAuthorityManager(chipStack=self._chip_stack) + self._certificate_authority_manager.LoadAuthoritiesFromStorage() - try: - admin_list = self._storage.GetReplKey('fabricAdmins') - found_admin_list = True - except KeyError: - found_admin_list = False - - if not found_admin_list: - self._logger.warn("No previous fabric administrative data found in persistent data: initializing a new one") - self._fabric_admins.append(chip.FabricAdmin.FabricAdmin(self._config.admin_vendor_id)) - else: - for admin_idx in admin_list: - self._logger.info( - f"Restoring FabricAdmin from storage to manage FabricId {admin_list[admin_idx]['fabricId']}, AdminIndex {admin_idx}") - self._fabric_admins.append(chip.FabricAdmin.FabricAdmin(vendorId=int(admin_list[admin_idx]['vendorId']), - fabricId=admin_list[admin_idx]['fabricId'], adminIndex=int(admin_idx))) + if (len(self._certificate_authority_manager.activeCaList) == 0): + self._logger.warn( + "Didn't find any CertificateAuthorities in storage -- creating a new CertificateAuthority + FabricAdmin...") + ca = self._certificate_authority_manager.NewCertificateAuthority() + ca.NewFabricAdmin(vendorId=0xFFF1, fabricId=0xFFF1) + elif (len(self._certificate_authority_manager.activeCaList[0].adminList) == 0): + self._logger.warn("Didn't find any FabricAdmins in storage -- creating a new one...") + self._certificate_authority_manager.activeCaList[0].NewFabricAdmin(vendorId=0xFFF1, fabricId=0xFFF1) # TODO: support getting access to chip-tool credentials issuer's data @@ -204,14 +198,17 @@ def Shutdown(self): # Unfortunately, all the below are singleton and possibly # managed elsewhere so we have to be careful not to touch unless # we initialized ourselves. - ChipDeviceCtrl.ChipDeviceController.ShutdownAll() - chip.FabricAdmin.FabricAdmin.ShutdownAll() + self._certificate_authority_manager.Shutdown() global_chip_stack = builtins.chipStack global_chip_stack.Shutdown() @property - def fabric_admins(self): - return self._fabric_admins + def certificate_authorities(self): + return self._certificate_authority_manager.activeCaList + + @property + def certificate_authority_manager(self): + return self._certificate_authority_manager @property def storage(self) -> PersistentStorage: @@ -251,6 +248,10 @@ def default_controller(self) -> ChipDeviceCtrl: def matter_stack(self) -> MatterStackState: return unstash_globally(self.user_params.get("matter_stack")) + @property + def certificate_authority_manager(self) -> chip.CertificateAuthority.CertificateAuthorityManager: + return unstash_globally(self.user_params.get("certificate_authority_manager")) + @property def dut_node_id(self) -> int: return self.matter_test_config.dut_node_id @@ -684,12 +685,14 @@ def default_matter_test_main(argv=None): test_config.user_params["matter_stack"] = stash_globally(stack) # TODO: Steer to right FabricAdmin! - default_controller = stack.fabric_admins[0].NewController(nodeId=matter_test_config.controller_node_id, - paaTrustStorePath=str(matter_test_config.paa_trust_store_path)) + default_controller = stack.certificate_authorities[0].adminList[0].NewController(nodeId=matter_test_config.controller_node_id, + paaTrustStorePath=str(matter_test_config.paa_trust_store_path)) test_config.user_params["default_controller"] = stash_globally(default_controller) test_config.user_params["matter_test_config"] = stash_globally(matter_test_config) + test_config.user_params["certificate_authority_manager"] = stash_globally(stack.certificate_authority_manager) + # Execute the test class with the config ok = True