From fbee997bdcc5a7af48103dd2152bd4c2578d9b36 Mon Sep 17 00:00:00 2001 From: Pankaj Garg Date: Fri, 7 May 2021 09:08:45 -0700 Subject: [PATCH] Define OperationalCredentialsDelegate interface and implement for Darwin (#6474) * Define OperationalCredentialsDelegate interface and implement for Darwin * Update src/controller/OperationalCredentialsDelegate.h Co-authored-by: Boris Zbarsky * Update src/controller/OperationalCredentialsDelegate.h Co-authored-by: Boris Zbarsky * Update src/controller/OperationalCredentialsDelegate.h Co-authored-by: Boris Zbarsky * address review comments * address review comments * Fix build errors * Fix build. * disable use of SecItemAdd for unit tests * use scoped buffer instead of unique_ptr * cleanup Co-authored-by: Justin Wood Co-authored-by: Boris Zbarsky --- src/controller/CHIPDeviceController.cpp | 73 +++++- src/controller/CHIPDeviceController.h | 13 +- .../OperationalCredentialsDelegate.h | 101 ++++++++ .../Framework/CHIP.xcodeproj/project.pbxproj | 8 + .../Framework/CHIP/CHIPDeviceController.mm | 14 + .../CHIP/CHIPOperationalCredentialsDelegate.h | 65 +++++ .../CHIPOperationalCredentialsDelegate.mm | 242 ++++++++++++++++++ src/lib/core/CHIPError.h | 8 + 8 files changed, 520 insertions(+), 4 deletions(-) create mode 100644 src/controller/OperationalCredentialsDelegate.h create mode 100644 src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h create mode 100644 src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 327e6512553424..6e12937efe5253 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -50,6 +50,7 @@ #include #include #include +#include #include #include @@ -60,6 +61,7 @@ #include #include +#include #include #include #include @@ -80,7 +82,9 @@ constexpr const char kNextAvailableKeyID[] = "StartKeyID"; constexpr uint16_t kMdnsPort = 5353; #endif -constexpr const uint32_t kSessionEstablishmentTimeout = 30 * kMillisecondPerSecond; +constexpr uint32_t kSessionEstablishmentTimeout = 30 * kMillisecondPerSecond; + +constexpr uint32_t kMaxCHIPOpCertLength = 600; // This macro generates a key using node ID an key prefix, and performs the given action // on that key. @@ -649,6 +653,8 @@ CHIP_ERROR DeviceCommissioner::Init(NodeId localDeviceId, CommissionerInitParams } mPairingDelegate = params.pairingDelegate; + + mOperationalCredentialsDelegate = params.operationalCredentialsDelegate; return CHIP_NO_ERROR; } @@ -915,6 +921,16 @@ void DeviceCommissioner::OnSessionEstablished() } ChipLogDetail(Controller, "Remote device completed SPAKE2+ handshake\n"); + + // TODO: Add code to receive OpCSR from the device, and process the signing request + err = SendOperationalCertificateSigningRequestCommand(device->GetDeviceId()); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Ble, "Failed in sending opcsr request command to the device: err %s", ErrorStr(err)); + OnSessionEstablishmentError(err); + return; + } + mPairingSession.ToSerializable(device->GetPairing()); mSystemLayer->CancelTimer(OnSessionEstablishmentTimeoutCallback, this); @@ -936,6 +952,61 @@ void DeviceCommissioner::OnSessionEstablished() RendezvousCleanup(CHIP_NO_ERROR); } +CHIP_ERROR DeviceCommissioner::SendOperationalCertificateSigningRequestCommand(NodeId remoteDeviceId) +{ + // TODO: Call OperationalCredentials cluster API to send command + return CHIP_NO_ERROR; +} + +CHIP_ERROR DeviceCommissioner::OnOperationalCertificateSigningRequest(NodeId node, const ByteSpan & csr) +{ + ReturnErrorCodeIf(mOperationalCredentialsDelegate == nullptr, CHIP_ERROR_INCORRECT_STATE); + + chip::Platform::ScopedMemoryBuffer opCert; + ReturnErrorCodeIf(!opCert.Alloc(kMaxCHIPOpCertLength), CHIP_ERROR_NO_MEMORY); + + uint32_t opCertLen = 0; + ReturnErrorOnFailure(mOperationalCredentialsDelegate->GenerateNodeOperationalCertificate( + PeerId().SetNodeId(node), csr, 0, opCert.Get(), kMaxCHIPOpCertLength, opCertLen)); + + chip::Platform::ScopedMemoryBuffer signingCert; + ReturnErrorCodeIf(!signingCert.Alloc(kMaxCHIPOpCertLength), CHIP_ERROR_NO_MEMORY); + + uint32_t signingCertLen = 0; + CHIP_ERROR err = + mOperationalCredentialsDelegate->GetIntermediateCACertificate(0, signingCert.Get(), kMaxCHIPOpCertLength, signingCertLen); + if (err == CHIP_ERROR_INTERMEDIATE_CA_NOT_REQUIRED) + { + // This implies that the commissioner application uses root CA to sign the operational + // certificates, and an intermediate CA is not needed. It's not an error condition, so + // let's just send operational certificate and root CA certificate to the device. + err = CHIP_NO_ERROR; + signingCertLen = 0; + } + ReturnErrorOnFailure(err); + + ReturnErrorOnFailure( + SendOperationalCertificate(node, ByteSpan(opCert.Get(), opCertLen), ByteSpan(signingCert.Get(), signingCertLen))); + + ReturnErrorOnFailure( + mOperationalCredentialsDelegate->GetRootCACertificate(0, signingCert.Get(), kMaxCHIPOpCertLength, signingCertLen)); + + return SendTrustedRootCertificate(node, ByteSpan(signingCert.Get(), signingCertLen)); +} + +CHIP_ERROR DeviceCommissioner::SendOperationalCertificate(NodeId remoteDeviceId, const ByteSpan & opCertBuf, + const ByteSpan & icaCertBuf) +{ + // TODO: Call OperationalCredentials cluster API to add operational credentials on the device + return CHIP_NO_ERROR; +} + +CHIP_ERROR DeviceCommissioner::SendTrustedRootCertificate(NodeId remoteDeviceId, const ByteSpan & certBuf) +{ + // TODO: Call TrustedRootCertificate cluster API to add root certificate on the device + return CHIP_NO_ERROR; +} + void DeviceCommissioner::PersistDeviceList() { if (mStorageDelegate != nullptr && mPairedDevicesUpdated && mState == State::Initialized) diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h index 8667cb48553ca0..fa88d835e5282a 100644 --- a/src/controller/CHIPDeviceController.h +++ b/src/controller/CHIPDeviceController.h @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -118,6 +119,8 @@ class DLL_EXPORT DevicePairingDelegate struct CommissionerInitParams : public ControllerInitParams { DevicePairingDelegate * pairingDelegate = nullptr; + + OperationalCredentialsDelegate * operationalCredentialsDelegate = nullptr; }; /** @@ -199,7 +202,7 @@ class DLL_EXPORT DeviceController : public Messaging::ExchangeDelegate, /** * @brief * Allow the CHIP Stack to process any pending events - * This can be called in an event handler loop to tigger callbacks within the CHIP stack + * This can be called in an event handler loop to trigger callbacks within the CHIP stack * @return CHIP_ERROR The return status */ CHIP_ERROR ServiceEventSignal(); @@ -306,8 +309,6 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, public SessionEst */ CHIP_ERROR Init(NodeId localDeviceId, CommissionerInitParams params); - void SetDevicePairingDelegate(DevicePairingDelegate * pairingDelegate) { mPairingDelegate = pairingDelegate; } - CHIP_ERROR Shutdown() override; // ----- Connection Management ----- @@ -369,6 +370,7 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, public SessionEst #endif private: + OperationalCredentialsDelegate * mOperationalCredentialsDelegate; DevicePairingDelegate * mPairingDelegate; /* This field is an index in mActiveDevices list. The object at this index in the list @@ -401,6 +403,11 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, public SessionEst static void OnSessionEstablishmentTimeoutCallback(System::Layer * aLayer, void * aAppState, System::Error aError); + CHIP_ERROR SendOperationalCertificateSigningRequestCommand(NodeId remoteDeviceId); + CHIP_ERROR OnOperationalCertificateSigningRequest(NodeId node, const ByteSpan & csr); + CHIP_ERROR SendOperationalCertificate(NodeId remoteDeviceId, const ByteSpan & opCertBuf, const ByteSpan & icaCertBuf); + CHIP_ERROR SendTrustedRootCertificate(NodeId remoteDeviceId, const ByteSpan & certBuf); + uint16_t mNextKeyId = 0; PASESession mPairingSession; diff --git a/src/controller/OperationalCredentialsDelegate.h b/src/controller/OperationalCredentialsDelegate.h new file mode 100644 index 00000000000000..5af3b27689396b --- /dev/null +++ b/src/controller/OperationalCredentialsDelegate.h @@ -0,0 +1,101 @@ +/* + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace Controller { + +/// Callbacks for CHIP operational credentials generation +class DLL_EXPORT OperationalCredentialsDelegate +{ +public: + virtual ~OperationalCredentialsDelegate() {} + + /** + * @brief + * This function generates an operational certificate for the given node. + * The API generates the certificate in X.509 DER format. + * + * The delegate is expected to use the certificate authority whose certificate + * is returned in `GetIntermediateCACertificate()` or `GetRootCACertificate()` + * API calls. + * + * @param[in] peerId Node ID and Fabric ID of the target device. + * @param[in] csr Certificate Signing Request from the node in DER format. + * @param[in] serialNumber Serial number to assign to the new certificate. + * @param[in] certBuf The API will fill in the generated cert in this buffer. The buffer is allocated by the caller. + * @param[in] certBufSize The size of certBuf buffer. + * @param[out] outCertLen The size of the actual certificate that was written in the certBuf. + * + * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code. + */ + virtual CHIP_ERROR GenerateNodeOperationalCertificate(const PeerId & peerId, const ByteSpan & csr, int64_t serialNumber, + uint8_t * certBuf, uint32_t certBufSize, uint32_t & outCertLen) = 0; + + /** + * @brief + * This function returns the intermediate certificate authority (ICA) certificate corresponding to the + * provided fabric ID. Intermediate certificate authority is optional. If the controller + * application does not require ICA, this API call will return `CHIP_ERROR_INTERMEDIATE_CA_NOT_REQUIRED`. + * + * The returned certificate is in X.509 DER format. + * + * @param[in] fabricId Fabric ID for which the certificate is being requested. + * @param[in] certBuf The API will fill in the cert in this buffer. The buffer is allocated by the caller. + * @param[in] certBufSize The size of certBuf buffer. + * @param[out] outCertLen The size of the actual certificate that was written in the certBuf. + * + * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code. + * CHIP_ERROR_INTERMEDIATE_CA_NOT_REQUIRED is not a critical error. It indicates that ICA is not needed. + */ + virtual CHIP_ERROR GetIntermediateCACertificate(FabricId fabricId, uint8_t * certBuf, uint32_t certBufSize, + uint32_t & outCertLen) + { + // By default, let's return CHIP_ERROR_INTERMEDIATE_CA_NOT_REQUIRED status. It'll allow + // commissioner applications to not implement GetIntermediateCACertificate() if they don't require an + // intermediate CA. + return CHIP_ERROR_INTERMEDIATE_CA_NOT_REQUIRED; + } + + /** + * @brief + * This function returns the root certificate authority (root CA) certificate corresponding to the + * provided fabric ID. + * + * The returned certificate is in X.509 DER format. + * + * @param[in] fabricId Fabric ID for which the certificate is being requested. + * @param[in] certBuf The API will fill in the cert in this buffer. The buffer is allocated by the caller. + * @param[in] certBufSize The size of certBuf buffer. + * @param[out] outCertLen The size of the actual certificate that was written in the certBuf. + * + * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code. + */ + virtual CHIP_ERROR GetRootCACertificate(FabricId fabricId, uint8_t * certBuf, uint32_t certBufSize, uint32_t & outCertLen) = 0; +}; + +} // namespace Controller +} // namespace chip diff --git a/src/darwin/Framework/CHIP.xcodeproj/project.pbxproj b/src/darwin/Framework/CHIP.xcodeproj/project.pbxproj index 7c9f2efdd31731..c362b373e347b2 100644 --- a/src/darwin/Framework/CHIP.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/CHIP.xcodeproj/project.pbxproj @@ -32,6 +32,8 @@ 1EC4CE6025CC26E900D7304F /* CHIPClientCallbacks.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1EC4CE5C25CC26E900D7304F /* CHIPClientCallbacks.cpp */; }; 1EC4CE6225CC271B00D7304F /* af-event.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1EC4CE6125CC271B00D7304F /* af-event.cpp */; }; 1EC4CE6425CC276600D7304F /* CHIPClustersObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EC4CE6325CC276600D7304F /* CHIPClustersObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2C1B027A2641DB4E00780EF1 /* CHIPOperationalCredentialsDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2C1B02782641DB4E00780EF1 /* CHIPOperationalCredentialsDelegate.mm */; }; + 2C1B027B2641DB4E00780EF1 /* CHIPOperationalCredentialsDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2C1B02792641DB4E00780EF1 /* CHIPOperationalCredentialsDelegate.h */; }; 2C222AD0255C620600E446B9 /* CHIPDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 2C222ACE255C620600E446B9 /* CHIPDevice.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2C222AD1255C620600E446B9 /* CHIPDevice.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2C222ACF255C620600E446B9 /* CHIPDevice.mm */; }; 2C222ADF255C811800E446B9 /* CHIPDevice_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 2C222ADE255C811800E446B9 /* CHIPDevice_Internal.h */; }; @@ -98,6 +100,8 @@ 1EC4CE5C25CC26E900D7304F /* CHIPClientCallbacks.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CHIPClientCallbacks.cpp; path = gen/CHIPClientCallbacks.cpp; sourceTree = ""; }; 1EC4CE6125CC271B00D7304F /* af-event.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "af-event.cpp"; path = "../../../app/util/af-event.cpp"; sourceTree = ""; }; 1EC4CE6325CC276600D7304F /* CHIPClustersObjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CHIPClustersObjc.h; path = gen/CHIPClustersObjc.h; sourceTree = ""; }; + 2C1B02782641DB4E00780EF1 /* CHIPOperationalCredentialsDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CHIPOperationalCredentialsDelegate.mm; sourceTree = ""; }; + 2C1B02792641DB4E00780EF1 /* CHIPOperationalCredentialsDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHIPOperationalCredentialsDelegate.h; sourceTree = ""; }; 2C222ACE255C620600E446B9 /* CHIPDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHIPDevice.h; sourceTree = ""; }; 2C222ACF255C620600E446B9 /* CHIPDevice.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CHIPDevice.mm; sourceTree = ""; }; 2C222ADE255C811800E446B9 /* CHIPDevice_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHIPDevice_Internal.h; sourceTree = ""; }; @@ -215,6 +219,8 @@ B202528F2459E34F00F97062 /* CHIP */ = { isa = PBXGroup; children = ( + 2C1B02792641DB4E00780EF1 /* CHIPOperationalCredentialsDelegate.h */, + 2C1B02782641DB4E00780EF1 /* CHIPOperationalCredentialsDelegate.mm */, 1EC4CE5825CC26AB00D7304F /* CHIPGeneratedFiles */, 1EC4CE3525CC259700D7304F /* CHIPApp */, 2C222ADE255C811800E446B9 /* CHIPDevice_Internal.h */, @@ -273,6 +279,7 @@ buildActionMask = 2147483647; files = ( 2CB7163B252E8A7B0026E2BB /* CHIPDevicePairingDelegateBridge.h in Headers */, + 2C1B027B2641DB4E00780EF1 /* CHIPOperationalCredentialsDelegate.h in Headers */, B289D4212639C0D300D4E314 /* CHIPOnboardingPayloadParser.h in Headers */, 2CB7163F252F731E0026E2BB /* CHIPDevicePairingDelegate.h in Headers */, 2C222AD0255C620600E446B9 /* CHIPDevice.h in Headers */, @@ -436,6 +443,7 @@ B289D4222639C0D300D4E314 /* CHIPOnboardingPayloadParser.m in Sources */, 1EC4CE5E25CC26E900D7304F /* call-command-handler.cpp in Sources */, 1EC4CE3B25CC263E00D7304F /* reporting-default-configuration.cpp in Sources */, + 2C1B027A2641DB4E00780EF1 /* CHIPOperationalCredentialsDelegate.mm in Sources */, 1EC4CE4F25CC267700D7304F /* process-cluster-message.cpp in Sources */, 1EC4CE3D25CC265200D7304F /* DataModelHandler.cpp in Sources */, 1EC4CE5725CC267700D7304F /* attribute-storage.cpp in Sources */, diff --git a/src/darwin/Framework/CHIP/CHIPDeviceController.mm b/src/darwin/Framework/CHIP/CHIPDeviceController.mm index 745dedc3bda7f9..04307985434554 100644 --- a/src/darwin/Framework/CHIP/CHIPDeviceController.mm +++ b/src/darwin/Framework/CHIP/CHIPDeviceController.mm @@ -20,6 +20,7 @@ #import "CHIPDevice_Internal.h" #import "CHIPError.h" #import "CHIPLogging.h" +#import "CHIPOperationalCredentialsDelegate.h" #import "CHIPPersistentStorageDelegateBridge.h" #import "CHIPSetupPayload.h" #import "gen/CHIPClustersObjc.h" @@ -34,6 +35,7 @@ static NSString * const kErrorMemoryInit = @"Init Memory failure"; static NSString * const kErrorCommissionerInit = @"Init failure while initializing a commissioner"; +static NSString * const kErrorOperationalCredentialsInit = @"Init failure while creating operational credentials delegate"; static NSString * const kErrorPairingInit = @"Init failure while creating a pairing delegate"; static NSString * const kErrorPersistentStorageInit = @"Init failure while creating a persistent storage delegate"; static NSString * const kErrorPairDevice = @"Failure while pairing the device"; @@ -52,6 +54,7 @@ @interface CHIPDeviceController () @property (readonly) chip::Controller::DeviceCommissioner * cppCommissioner; @property (readonly) CHIPDevicePairingDelegateBridge * pairingDelegateBridge; @property (readonly) CHIPPersistentStorageDelegateBridge * persistentStorageDelegateBridge; +@property (readonly) CHIPOperationalCredentialsDelegate * operationalCredentialsDelegate; @property (readonly) chip::NodeId localDeviceId; @property (readonly) uint16_t listenPort; @end @@ -90,6 +93,11 @@ - (instancetype)init if ([self checkForInitError:(_persistentStorageDelegateBridge != nullptr) logMsg:kErrorPersistentStorageInit]) { return nil; } + + _operationalCredentialsDelegate = new CHIPOperationalCredentialsDelegate(); + if ([self checkForInitError:(_operationalCredentialsDelegate != nullptr) logMsg:kErrorOperationalCredentialsInit]) { + return nil; + } } return self; } @@ -134,6 +142,12 @@ - (BOOL)startup:(_Nullable id)storageDelegate CHIP_ERROR errorCode = CHIP_ERROR_INCORRECT_STATE; _persistentStorageDelegateBridge->setFrameworkDelegate(storageDelegate); + + errorCode = _operationalCredentialsDelegate->init(_persistentStorageDelegateBridge); + if ([self checkForStartError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorOperationalCredentialsInit]) { + return; + } + // initialize NodeID if needed [self _getControllerNodeId]; diff --git a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h new file mode 100644 index 00000000000000..54a99d11375ca8 --- /dev/null +++ b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h @@ -0,0 +1,65 @@ +/** + * + * Copyright (c) 2021 Project CHIP Authors + * + * 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 +#import + +#import "CHIPError.h" +#import "CHIPPersistentStorageDelegateBridge.h" + +#include + +NS_ASSUME_NONNULL_BEGIN + +class CHIPOperationalCredentialsDelegate : public chip::Controller::OperationalCredentialsDelegate { +public: + CHIPOperationalCredentialsDelegate() {} + + ~CHIPOperationalCredentialsDelegate() {} + + CHIP_ERROR init(CHIPPersistentStorageDelegateBridge * storage); + + CHIP_ERROR GenerateNodeOperationalCertificate(const chip::PeerId & peerId, const chip::ByteSpan & csr, int64_t serialNumber, + uint8_t * certBuf, uint32_t certBufSize, uint32_t & outCertLen) override; + + CHIP_ERROR GetRootCACertificate( + chip::FabricId fabricId, uint8_t * certBuf, uint32_t certBufSize, uint32_t & outCertLen) override; + +private: + CHIP_ERROR GenerateKeys(); + CHIP_ERROR LoadKeysFromKeyChain(); + CHIP_ERROR DeleteKeys(); + + CHIP_ERROR ConvertToP256Keypair(SecKeyRef privateKey); + + CHIP_ERROR SetIssuerID(CHIPPersistentStorageDelegateBridge * storage); + + bool ToChipEpochTime(uint32_t offset, uint32_t & epoch); + + chip::Crypto::P256Keypair mIssuerKey; + uint32_t mIssuerId; + + const uint32_t kCertificateValiditySecs = 365 * 24 * 60 * 60; + const NSString * kCHIPCAKeyLabel = @"chip.nodeopcerts.CA:0"; + + id mKeyType = (id) kSecAttrKeyTypeECSECPrimeRandom; + id mKeySize = @256; + + CHIPPersistentStorageDelegateBridge * mStorage; +}; + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm new file mode 100644 index 00000000000000..3edff95b191334 --- /dev/null +++ b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm @@ -0,0 +1,242 @@ +/** + * + * Copyright (c) 2021 Project CHIP Authors + * + * 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 "CHIPOperationalCredentialsDelegate.h" + +#import + +#include + +#import "CHIPLogging.h" + +#include +#include +#include + +static BOOL isRunningTests(void) +{ + NSDictionary * environment = [[NSProcessInfo processInfo] environment]; + return (environment[@"XCTestConfigurationFilePath"] != nil); +} + +CHIP_ERROR CHIPOperationalCredentialsDelegate::init(CHIPPersistentStorageDelegateBridge * storage) +{ + if (storage == nil) { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + mStorage = storage; + + CHIP_ERROR err = LoadKeysFromKeyChain(); + if (err != CHIP_NO_ERROR) { + // Generate keys if keys could not be loaded + err = GenerateKeys(); + } + + if (err == CHIP_NO_ERROR) { + // If keys were loaded, or generated, let's get the certificate issuer ID + err = SetIssuerID(storage); + } + + CHIP_LOG_ERROR("CHIPOperationalCredentialsDelegate::init returning %d", err); + return err; +} + +CHIP_ERROR CHIPOperationalCredentialsDelegate::SetIssuerID(CHIPPersistentStorageDelegateBridge * storage) +{ + static const char * const CHIP_COMMISSIONER_CA_ISSUER_ID = "com.zigbee.chip.commissioner.ca.issuer.id"; + if (storage == nil) { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + uint16_t idStringLen = 16; + char issuerIdString[idStringLen]; + if (CHIP_NO_ERROR != storage->SyncGetKeyValue(CHIP_COMMISSIONER_CA_ISSUER_ID, issuerIdString, idStringLen)) { + mIssuerId = arc4random(); + CHIP_LOG_ERROR("Assigned %d certificate issuer ID to the commissioner", mIssuerId); + storage->SyncSetKeyValue(CHIP_COMMISSIONER_CA_ISSUER_ID, &mIssuerId, sizeof(mIssuerId)); + } else { + CHIP_LOG_ERROR("Found %d certificate issuer ID for the commissioner", mIssuerId); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR CHIPOperationalCredentialsDelegate::ConvertToP256Keypair(SecKeyRef keypair) +{ + NSData * keypairData = (__bridge_transfer NSData *) SecKeyCopyExternalRepresentation(keypair, nil); + if (keypairData == nil) { + NSLog(@"Failed in getting keypair data"); + return CHIP_ERROR_INTERNAL; + } + + chip::Crypto::P256SerializedKeypair serialized; + if ([keypairData length] != serialized.Capacity()) { + NSLog(@"Keypair length %zu does not match expected length %zu", [keypairData length], serialized.Capacity()); + return CHIP_ERROR_INTERNAL; + } + + std::memmove((uint8_t *) serialized, [keypairData bytes], [keypairData length]); + serialized.SetLength([keypairData length]); + + CHIP_LOG_ERROR("Deserializing the key"); + return mIssuerKey.Deserialize(serialized); +} + +CHIP_ERROR CHIPOperationalCredentialsDelegate::LoadKeysFromKeyChain() +{ + SecKeyRef privateKey; + + const NSDictionary * query = @ { + (id) kSecClass : (id) kSecClassKey, + (id) kSecAttrKeyType : mKeyType, + (id) kSecAttrKeySizeInBits : mKeySize, + (id) kSecAttrLabel : kCHIPCAKeyLabel, + (id) kSecReturnRef : (id) kCFBooleanTrue + }; + + OSStatus status = SecItemCopyMatching((CFDictionaryRef) query, (CFTypeRef *) &privateKey); + if (status == errSecItemNotFound || privateKey == nil) { + CHIP_LOG_ERROR("Did not find an existing key in the keychain"); + return CHIP_ERROR_KEY_NOT_FOUND; + } + + CHIP_LOG_ERROR("Found an existing keypair in the keychain"); + return ConvertToP256Keypair(privateKey); +} + +CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateKeys() +{ + SecKeyRef publicKey; + SecKeyRef privateKey; + + CHIP_LOG_ERROR("Generating keys for the CA"); + OSStatus status = noErr; + + const NSDictionary * keygenParams = @ { + (id) kSecAttrKeyType : mKeyType, + (id) kSecAttrKeySizeInBits : mKeySize, + (id) kSecAttrLabel : kCHIPCAKeyLabel, + }; + + status = SecKeyGeneratePair((__bridge CFDictionaryRef) keygenParams, &publicKey, &privateKey); + if (status != noErr || publicKey == nil || privateKey == nil) { + NSLog(@"Failed in keygen"); + return CHIP_ERROR_INTERNAL; + } + + const NSDictionary * addParams = @ { + (id) kSecClass : (id) kSecClassKey, + (id) kSecAttrKeyType : mKeyType, + (id) kSecAttrKeySizeInBits : mKeySize, + (id) kSecAttrLabel : kCHIPCAKeyLabel, + (id) kSecValueRef : (__bridge id) privateKey, + }; + + status = SecItemAdd((__bridge CFDictionaryRef) addParams, NULL); + // TODO: Enable SecItemAdd for Darwin unit tests + if (status != errSecSuccess && !isRunningTests()) { + NSLog(@"Failed in storing key : %d", status); + return CHIP_ERROR_INTERNAL; + } + + NSLog(@"Stored the keys"); + return ConvertToP256Keypair(privateKey); +} + +CHIP_ERROR CHIPOperationalCredentialsDelegate::DeleteKeys() +{ + CHIP_LOG_ERROR("Deleting current CA keys"); + OSStatus status = noErr; + + const NSDictionary * deleteParams = @ { + (id) kSecClass : (id) kSecClassKey, + (id) kSecAttrKeyType : mKeyType, + (id) kSecAttrKeySizeInBits : mKeySize, + (id) kSecAttrLabel : kCHIPCAKeyLabel, + }; + + status = SecItemDelete((__bridge CFDictionaryRef) deleteParams); + if (status != errSecSuccess) { + NSLog(@"Failed in deleting key : %d", status); + return CHIP_ERROR_INTERNAL; + } + + NSLog(@"Deleted the key"); + return CHIP_NO_ERROR; +} + +CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateNodeOperationalCertificate(const chip::PeerId & peerId, + const chip::ByteSpan & csr, int64_t serialNumber, uint8_t * certBuf, uint32_t certBufSize, uint32_t & outCertLen) +{ + uint32_t validityStart, validityEnd; + + if (!ToChipEpochTime(0, validityStart)) { + NSLog(@"Failed in computing certificate validity start date"); + return CHIP_ERROR_INTERNAL; + } + + if (!ToChipEpochTime(kCertificateValiditySecs, validityEnd)) { + NSLog(@"Failed in computing certificate validity end date"); + return CHIP_ERROR_INTERNAL; + } + + chip::Credentials::X509CertRequestParams request + = { serialNumber, mIssuerId, validityStart, validityEnd, true, peerId.GetFabricId(), true, peerId.GetNodeId() }; + + chip::Crypto::P256PublicKey pubkey; + CHIP_ERROR err = chip::Crypto::VerifyCertificateSigningRequest(csr.data(), csr.size(), pubkey); + if (err != CHIP_NO_ERROR) { + return err; + } + + return chip::Credentials::NewNodeOperationalX509Cert( + request, chip::Credentials::CertificateIssuerLevel::kIssuerIsRootCA, pubkey, mIssuerKey, certBuf, certBufSize, outCertLen); +} + +CHIP_ERROR CHIPOperationalCredentialsDelegate::GetRootCACertificate( + chip::FabricId fabricId, uint8_t * certBuf, uint32_t certBufSize, uint32_t & outCertLen) +{ + // TODO: Don't generate root certificate unless there's none, or the current is expired. + uint32_t validityStart, validityEnd; + + if (!ToChipEpochTime(0, validityStart)) { + NSLog(@"Failed in computing certificate validity start date"); + return CHIP_ERROR_INTERNAL; + } + + if (!ToChipEpochTime(kCertificateValiditySecs, validityEnd)) { + NSLog(@"Failed in computing certificate validity end date"); + return CHIP_ERROR_INTERNAL; + } + + chip::Credentials::X509CertRequestParams request = { 0, mIssuerId, validityStart, validityEnd, true, fabricId, false, 0 }; + + return chip::Credentials::NewRootX509Cert(request, mIssuerKey, certBuf, certBufSize, outCertLen); +} + +bool CHIPOperationalCredentialsDelegate::ToChipEpochTime(uint32_t offset, uint32_t & epoch) +{ + NSDate * date = [NSDate dateWithTimeIntervalSinceNow:offset]; + unsigned units = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute + | NSCalendarUnitSecond; + NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; + NSDateComponents * components = [calendar components:units fromDate:date]; + + return chip::CalendarToChipEpochTime([components year], [components month], [components day], [components hour], + [components minute], [components second], epoch); +} diff --git a/src/lib/core/CHIPError.h b/src/lib/core/CHIPError.h index a687e8db889601..36d792da372e7f 100644 --- a/src/lib/core/CHIPError.h +++ b/src/lib/core/CHIPError.h @@ -1777,6 +1777,14 @@ typedef CHIP_CONFIG_ERROR_TYPE CHIP_ERROR; */ #define CHIP_ERROR_HSM _CHIP_ERROR(189) +/** + * @def CHIP_ERROR_INTERMEDIATE_CA_NOT_REQUIRED + * + * @brief + * The commissioner doesn't require an intermediate CA to sign the operational certificates. + */ +#define CHIP_ERROR_INTERMEDIATE_CA_NOT_REQUIRED _CHIP_ERROR(190) + /** * @} */