From b4da24177940dc30fe06e0ef67bf90524cc06272 Mon Sep 17 00:00:00 2001 From: Song Guo Date: Thu, 23 Feb 2023 15:14:12 +0800 Subject: [PATCH] Update --- src/controller/python/BUILD.gn | 4 ++ src/controller/python/OpCredsBinding.cpp | 6 +-- .../commissioning_flow_blocks.py | 43 ++++++++++++---- .../python/chip/credentials/cert.cpp | 49 ++++++++++++++++++ src/controller/python/chip/credentials/cert.h | 30 +++++++++++ .../python/chip/credentials/cert.py | 51 +++++++++++++++++++ src/controller/python/chip/crypto/fabric.py | 38 ++++++++++++++ 7 files changed, 209 insertions(+), 12 deletions(-) create mode 100644 src/controller/python/chip/credentials/cert.cpp create mode 100644 src/controller/python/chip/credentials/cert.h create mode 100644 src/controller/python/chip/credentials/cert.py create mode 100644 src/controller/python/chip/crypto/fabric.py diff --git a/src/controller/python/BUILD.gn b/src/controller/python/BUILD.gn index 0065659f15cf64..750de2b837c170 100644 --- a/src/controller/python/BUILD.gn +++ b/src/controller/python/BUILD.gn @@ -68,6 +68,7 @@ shared_library("ChipDeviceCtrl") { "OpCredsBinding.cpp", "chip/clusters/attribute.cpp", "chip/clusters/command.cpp", + "chip/credentials/cert.cpp", "chip/crypto/p256keypair.cpp", "chip/discovery/NodeResolution.cpp", "chip/interaction_model/Delegate.cpp", @@ -218,6 +219,8 @@ chip_python_wheel_action("chip-core") { "chip/commissioning/commissioning_flow_blocks.py", "chip/commissioning/pase.py", "chip/configuration/__init__.py", + "chip/credentials/cert.py", + "chip/crypto/fabric.py", "chip/crypto/p256keypair.py", "chip/discovery/__init__.py", "chip/discovery/library_handle.py", @@ -277,6 +280,7 @@ chip_python_wheel_action("chip-core") { "chip.configuration", "chip.commissioning", "chip.clusters", + "chip.credentials", "chip.crypto", "chip.utils", "chip.discovery", diff --git a/src/controller/python/OpCredsBinding.cpp b/src/controller/python/OpCredsBinding.cpp index 919a127efcbb47..3c4da74f904683 100644 --- a/src/controller/python/OpCredsBinding.cpp +++ b/src/controller/python/OpCredsBinding.cpp @@ -591,11 +591,11 @@ PyChipError pychip_DeviceController_SetIpk(chip::Controller::DeviceCommissioner uint8_t compressedFabricId[sizeof(uint64_t)] = { 0 }; chip::MutableByteSpan compressedFabricIdSpan(compressedFabricId); - err = devCtrl->GetCompressedFabricIdBytes(compressedFabricIdSpan); + CHIP_ERROR err = devCtrl->GetCompressedFabricIdBytes(compressedFabricIdSpan); VerifyOrReturnError(err == CHIP_NO_ERROR, ToPyChipError(err)); - CHIP_ERROR err = chip::Credentials::SetSingleIpkEpochKey(&sGroupDataProvider, devCtrl->GetFabricIndex(), ByteSpan(ipk, ipkLen), - compressedFabricIdSpan); + err = chip::Credentials::SetSingleIpkEpochKey(&sGroupDataProvider, devCtrl->GetFabricIndex(), ByteSpan(ipk, ipkLen), + compressedFabricIdSpan); return ToPyChipError(err); } diff --git a/src/controller/python/chip/commissioning/commissioning_flow_blocks.py b/src/controller/python/chip/commissioning/commissioning_flow_blocks.py index 0d6d87f8e0ebe9..5fd468cb650fb8 100644 --- a/src/controller/python/chip/commissioning/commissioning_flow_blocks.py +++ b/src/controller/python/chip/commissioning/commissioning_flow_blocks.py @@ -21,9 +21,15 @@ from chip import ChipDeviceCtrl from chip import clusters as Clusters from chip import commissioning, tlv +import chip.credentials.cert +import chip.crypto.fabric from . import pase +from cryptography import x509 +from cryptography.hazmat.primitives import serialization +import base64 + class CommissioningFlowBlocks: def __init__(self, devCtrl: ChipDeviceCtrl.ChipDeviceControllerBase, credential_provider: commissioning.CredentialProvider, logger: logging.Logger): @@ -105,20 +111,39 @@ async def operational_credentials_commissioning(self, parameter: commissioning.P raise commissioning.CommissionFailure(f"Failed to add Root Certificate: {ex}") try: - certificate_data = tlv.TLVReader(commissionee_credentials.noc).get()['Any'] - - self._logger.info(f"Parsed NOC TLV: {certificate_data}") + x509_rcac = x509.load_pem_x509_certificate( + b'''-----BEGIN CERTIFICATE-----\n''' + + base64.b64encode(chip.credentials.cert.convert_chip_cert_to_x509_cert(commissionee_credentials.rcac)) + + b'''\n-----END CERTIFICATE-----''') + root_public_key = x509_rcac.public_key().public_bytes(serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint) + + x509_noc = x509.load_pem_x509_certificate( + b'''-----BEGIN CERTIFICATE-----\n''' + + base64.b64encode(chip.credentials.cert.convert_chip_cert_to_x509_cert(commissionee_credentials.noc)) + + b'''\n-----END CERTIFICATE-----''') + + for subject in x509_noc.subject: + if subject.oid.dotted_string == '1.3.6.1.4.1.37244.1.1': + cert_fabric_id = int(subject.value, 16) + elif subject.oid.dotted_string == '1.3.6.1.4.1.37244.1.5': + cert_node_id = int(subject.value, 16) + + if cert_fabric_id != commissionee_credentials.fabric_id: + self._logger.warning("Fabric ID in certificate does not match the fabric id in commissionee credentials struct.") + if cert_node_id != commissionee_credentials.node_id: + self._logger.warning("Node ID in certificate does not match the node id in commissionee credentials struct.") + + compressed_fabric_id = chip.crypto.fabric.generate_compressed_fabric_id(root_public_key) - # TODO: chip.tlv.Reader will remove tag information in List containers. Which is used in NOC TLV - # Should extract matter-node-id and matter-fabric-id after List TLV is well handled. except: self._logger.exception("The certificate should be a valid CHIP Certificate, but failed to parse it") + raise - # TODO: Calculate compressed fabric id based on the NOC infomations. self._logger.info( - f"Commissioning FabricID: {commissionee_credentials.fabric_id:016X} " - f"Compressed FabricID: {self._devCtrl.GetCompressedFabricId():016X} " - f"Node ID: {commissionee_credentials.node_id:016X}") + f"Commissioning FabricID: {cert_fabric_id:016X} " + f"Compressed FabricID: {compressed_fabric_id:016X} " + f"Node ID: {cert_node_id:016X}") self._logger.info("Adding Operational Certificate") response = await self._devCtrl.SendCommand(node_id, commissioning.ROOT_ENDPOINT_ID, Clusters.OperationalCredentials.Commands.AddNOC( diff --git a/src/controller/python/chip/credentials/cert.cpp b/src/controller/python/chip/credentials/cert.cpp new file mode 100644 index 00000000000000..813047ca02ec2e --- /dev/null +++ b/src/controller/python/chip/credentials/cert.cpp @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2023 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. + */ + +#include "cert.h" + +#include +#include + +using namespace chip; +using namespace chip::Credentials; + +PyChipError pychip_ConvertX509CertToChipCert(const uint8_t * x509Cert, size_t x509CertLen, uint8_t * chipCert, size_t * chipCertLen) +{ + MutableByteSpan output(chipCert, *chipCertLen); + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrReturnError((err = ConvertX509CertToChipCert(ByteSpan(x509Cert, x509CertLen), output)) == CHIP_NO_ERROR, + ToPyChipError(err)); + *chipCertLen = output.size(); + + return ToPyChipError(err); +} + +PyChipError pychip_ConvertChipCertToX509Cert(const uint8_t * chipCert, size_t chipCertLen, uint8_t * x509Cert, size_t * x509CertLen) +{ + MutableByteSpan output(x509Cert, *x509CertLen); + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrReturnError((err = ConvertChipCertToX509Cert(ByteSpan(chipCert, chipCertLen), output)) == CHIP_NO_ERROR, + ToPyChipError(err)); + *x509CertLen = output.size(); + + return ToPyChipError(err); +} diff --git a/src/controller/python/chip/credentials/cert.h b/src/controller/python/chip/credentials/cert.h new file mode 100644 index 00000000000000..5d86273db01d03 --- /dev/null +++ b/src/controller/python/chip/credentials/cert.h @@ -0,0 +1,30 @@ +/* + * + * Copyright (c) 2023 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. + */ + +#pragma once + +#include + +#include + +extern "C" { +PyChipError pychip_ConvertX509CertToChipCert(const uint8_t * x509Cert, size_t x509CertLen, uint8_t * chipCert, + size_t * chipCertLen); +PyChipError pychip_ConvertChipCertToX509Cert(const uint8_t * chipCert, size_t chipCertLen, uint8_t * x509Cert, + size_t * x509CertLen); +} diff --git a/src/controller/python/chip/credentials/cert.py b/src/controller/python/chip/credentials/cert.py new file mode 100644 index 00000000000000..cc42c91876b7ba --- /dev/null +++ b/src/controller/python/chip/credentials/cert.py @@ -0,0 +1,51 @@ +# +# Copyright (c) 2023 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. +# + +import chip.native +import builtins +import ctypes + + +def _handle(): + handle = chip.native.GetLibraryHandle() + if handle.pychip_ConvertX509CertToChipCert.argtypes is None: + setter = chip.native.NativeLibraryHandleMethodArguments(handle) + setter.Set("pychip_ConvertX509CertToChipCert", chip.native.PyChipError, [ctypes.POINTER( + ctypes.c_uint8), ctypes.c_size_t, ctypes.POINTER(ctypes.c_uint8), ctypes.POINTER(ctypes.c_size_t)]) + setter.Set("pychip_ConvertChipCertToX509Cert", chip.native.PyChipError, [ctypes.POINTER( + ctypes.c_uint8), ctypes.c_size_t, ctypes.POINTER(ctypes.c_uint8), ctypes.POINTER(ctypes.c_size_t)]) + return handle + + +def convert_x509_cert_to_chip_cert(x509Cert: bytes) -> bytes: + """Converts a x509 certificate to CHIP Certificate.""" + output_buffer = (ctypes.c_uint8 * 1024)() + output_size = ctypes.c_size_t(1024) + + _handle().pychip_ConvertX509CertToChipCert(x509Cert, len(x509Cert), output_buffer, ctypes.byref(output_size)).raise_on_error() + + return bytes(output_buffer)[:output_size.value] + + +def convert_chip_cert_to_x509_cert(chipCert: bytes) -> bytes: + """Converts a x509 certificate to CHIP Certificate.""" + output_buffer = (ctypes.c_byte * 1024)() + output_size = ctypes.c_size_t(1024) + + _handle().pychip_ConvertChipCertToX509Cert(chipCert, len(chipCert), output_buffer, ctypes.byref(output_size)).raise_on_error() + + return bytes(output_buffer)[:output_size.value] diff --git a/src/controller/python/chip/crypto/fabric.py b/src/controller/python/chip/crypto/fabric.py new file mode 100644 index 00000000000000..3662fc164306d9 --- /dev/null +++ b/src/controller/python/chip/crypto/fabric.py @@ -0,0 +1,38 @@ +# +# Copyright (c) 2023 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. +# + +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.hkdf import HKDF + +from . import p256keypair + + +def generate_compressed_fabric_id(root_public_key: bytes, fabric_id: int) -> int: + """Generates compressed fabric id from Root CA's public key and fabric id. + + Returns: + Compressed fabric id as a int + """ + if len(root_public_key) != p256keypair.P256_PUBLIC_KEY_LENGTH and root_public_key[0] != b'\x04': + raise ValueError("Root public key must be an uncompressed P256 point.") + + return int.from_bytes(HKDF( + algorithm=hashes.SHA256(), + length=8, + salt=fabric_id.to_bytes(length=8, byteorder="big", signed=False), + info=b"CompressedFabric", + ).derive(key_material=root_public_key[1:]), byteorder="big")