|  | /** | 
|  | *    Copyright (c) 2022 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 "MTRDeviceControllerStartupParams.h" | 
|  | #import "MTRCertificates.h" | 
|  | #import "MTRDeviceControllerStartupParams_Internal.h" | 
|  | #import "MTRLogging_Internal.h" | 
|  | #import "MTRP256KeypairBridge.h" | 
|  | #import "NSDataSpanConversion.h" | 
|  |  | 
|  | #include <controller/OperationalCredentialsDelegate.h> | 
|  | #include <credentials/CHIPCert.h> | 
|  | #include <credentials/FabricTable.h> | 
|  | #include <lib/core/PeerId.h> | 
|  |  | 
|  | using namespace chip; | 
|  |  | 
|  | @implementation MTRDeviceControllerStartupParams | 
|  |  | 
|  | - (instancetype)initWithIPK:(NSData *)ipk fabricID:(NSNumber *)fabricID nocSigner:(id<MTRKeypair>)nocSigner | 
|  | { | 
|  | if (!(self = [super init])) { | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | if (!IsValidFabricId(fabricID.unsignedLongLongValue)) { | 
|  | MTR_LOG_ERROR("%llu is not a valid fabric id to initialize a device controller with", fabricID.unsignedLongLongValue); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | _nocSigner = nocSigner; | 
|  | _fabricID = [fabricID copy]; | 
|  | _ipk = [ipk copy]; | 
|  |  | 
|  | return self; | 
|  | } | 
|  |  | 
|  | - (instancetype)initWithIPK:(NSData *)ipk | 
|  | operationalKeypair:(id<MTRKeypair>)operationalKeypair | 
|  | operationalCertificate:(MTRCertificateDERBytes)operationalCertificate | 
|  | intermediateCertificate:(MTRCertificateDERBytes _Nullable)intermediateCertificate | 
|  | rootCertificate:(MTRCertificateDERBytes)rootCertificate | 
|  | { | 
|  | if (!(self = [super init])) { | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | { // Scope for temporaries | 
|  | // ExtractNodeIdFabricIdFromOpCert needs a TLV-encoded opcert, not a DER-encoded one. | 
|  | uint8_t tlvOpCertBuf[Credentials::kMaxCHIPCertLength]; | 
|  | MutableByteSpan tlvOpCert(tlvOpCertBuf); | 
|  | CHIP_ERROR err = Credentials::ConvertX509CertToChipCert(AsByteSpan(operationalCertificate), tlvOpCert); | 
|  | if (err != CHIP_NO_ERROR) { | 
|  | MTR_LOG_ERROR("Unable to convert operational certificate to TLV: %s", ErrorStr(err)); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | FabricId fabricId = kUndefinedFabricId; | 
|  | NodeId unused = kUndefinedNodeId; | 
|  | err = Credentials::ExtractNodeIdFabricIdFromOpCert(tlvOpCert, &unused, &fabricId); | 
|  | if (err != CHIP_NO_ERROR) { | 
|  | MTR_LOG_ERROR("Unable to extract fabric id from operational certificate: %s", ErrorStr(err)); | 
|  | return nil; | 
|  | } | 
|  | _fabricID = @(fabricId); | 
|  | } | 
|  |  | 
|  | _operationalKeypair = operationalKeypair; | 
|  | _operationalCertificate = [operationalCertificate copy]; | 
|  | _intermediateCertificate = [intermediateCertificate copy]; | 
|  | _rootCertificate = [rootCertificate copy]; | 
|  | _ipk = [ipk copy]; | 
|  |  | 
|  | return self; | 
|  | } | 
|  |  | 
|  | - (instancetype)initWithParams:(MTRDeviceControllerStartupParams *)params | 
|  | { | 
|  | if (!(self = [super init])) { | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | _nocSigner = params.nocSigner; | 
|  | _fabricID = params.fabricID; | 
|  | _ipk = params.ipk; | 
|  | _vendorID = params.vendorID; | 
|  | _nodeID = params.nodeID; | 
|  | _caseAuthenticatedTags = params.caseAuthenticatedTags; | 
|  | _rootCertificate = params.rootCertificate; | 
|  | _intermediateCertificate = params.intermediateCertificate; | 
|  | _operationalCertificate = params.operationalCertificate; | 
|  | _operationalKeypair = params.operationalKeypair; | 
|  | _operationalCertificateIssuer = params.operationalCertificateIssuer; | 
|  | _operationalCertificateIssuerQueue = params.operationalCertificateIssuerQueue; | 
|  |  | 
|  | return self; | 
|  | } | 
|  |  | 
|  | @end | 
|  |  | 
|  | // Convert a ByteSpan representing a Matter TLV certificate into NSData holding | 
|  | // a DER X.509 certificate.  Returns nil on failures. | 
|  | static NSData * _Nullable MatterCertToX509Data(const ByteSpan & cert) | 
|  | { | 
|  | uint8_t buf[Controller::kMaxCHIPDERCertLength]; | 
|  | MutableByteSpan derCert(buf); | 
|  | CHIP_ERROR err = Credentials::ConvertChipCertToX509Cert(cert, derCert); | 
|  | if (err != CHIP_NO_ERROR) { | 
|  | MTR_LOG_ERROR("Failed do convert Matter certificate to X.509 DER: %s", ErrorStr(err)); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | return AsData(derCert); | 
|  | } | 
|  |  | 
|  | @implementation MTRDeviceControllerStartupParams (Deprecated) | 
|  |  | 
|  | - (uint64_t)fabricId | 
|  | { | 
|  | return self.fabricID.unsignedLongLongValue; | 
|  | } | 
|  |  | 
|  | - (nullable NSNumber *)vendorId | 
|  | { | 
|  | return self.vendorID; | 
|  | } | 
|  |  | 
|  | - (void)setVendorId:(nullable NSNumber *)vendorId | 
|  | { | 
|  | self.vendorID = vendorId; | 
|  | } | 
|  |  | 
|  | - (nullable NSNumber *)nodeId | 
|  | { | 
|  | return self.nodeID; | 
|  | } | 
|  |  | 
|  | - (void)setNodeId:(nullable NSNumber *)nodeId | 
|  | { | 
|  | self.nodeID = nodeId; | 
|  | } | 
|  |  | 
|  | - (instancetype)initWithSigningKeypair:(id<MTRKeypair>)nocSigner fabricId:(uint64_t)fabricId ipk:(NSData *)ipk | 
|  | { | 
|  | return [self initWithIPK:ipk fabricID:@(fabricId) nocSigner:nocSigner]; | 
|  | } | 
|  |  | 
|  | - (instancetype)initWithOperationalKeypair:(id<MTRKeypair>)operationalKeypair | 
|  | operationalCertificate:(MTRCertificateDERBytes)operationalCertificate | 
|  | intermediateCertificate:(MTRCertificateDERBytes _Nullable)intermediateCertificate | 
|  | rootCertificate:(MTRCertificateDERBytes)rootCertificate | 
|  | ipk:(NSData *)ipk | 
|  | { | 
|  | return [self initWithIPK:ipk | 
|  | operationalKeypair:operationalKeypair | 
|  | operationalCertificate:operationalCertificate | 
|  | intermediateCertificate:intermediateCertificate | 
|  | rootCertificate:rootCertificate]; | 
|  | } | 
|  |  | 
|  | @end | 
|  |  | 
|  | @implementation MTRDeviceControllerStartupParamsInternal | 
|  |  | 
|  | - (instancetype)initWithParams:(MTRDeviceControllerStartupParams *)params | 
|  | { | 
|  | if (!(self = [super initWithParams:params])) { | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | if (self.nocSigner == nil && self.rootCertificate == nil) { | 
|  | MTR_LOG_ERROR("nocSigner and rootCertificate are both nil; no public key available to identify the fabric"); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | if (self.operationalCertificate != nil && self.nodeID != nil) { | 
|  | MTR_LOG_ERROR("nodeID must be nil if operationalCertificate is not nil"); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | if (self.caseAuthenticatedTags != nil && self.nodeID == nil) { | 
|  | MTR_LOG_ERROR("caseAuthenticatedTags must be nil if nodeID is nil"); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | if (self.operationalCertificate != nil) { | 
|  | if (self.operationalKeypair == nil) { | 
|  | MTR_LOG_ERROR("Must have an operational keypair if an operational certificate is provided"); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | if (![MTRCertificates keypair:self.operationalKeypair matchesCertificate:self.operationalCertificate]) { | 
|  | MTR_LOG_ERROR("operationalKeypair public key does not match operationalCertificate"); | 
|  | return nil; | 
|  | } | 
|  | } | 
|  |  | 
|  | return self; | 
|  | } | 
|  |  | 
|  | - (instancetype)initForNewFabric:(chip::FabricTable *)fabricTable | 
|  | keystore:(chip::Crypto::OperationalKeystore *)keystore | 
|  | advertiseOperational:(BOOL)advertiseOperational | 
|  | params:(MTRDeviceControllerStartupParams *)params | 
|  | { | 
|  | if (!(self = [self initWithParams:params])) { | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | if (self.nocSigner == nil && self.operationalCertificate == nil) { | 
|  | MTR_LOG_ERROR("No way to get an operational certificate: nocSigner and operationalCertificate are both nil"); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | if (self.operationalCertificate == nil && self.nodeID == nil) { | 
|  | // Just avoid setting the top bit, to avoid issues with node | 
|  | // ids outside the operational range. | 
|  | uint64_t nodeId = arc4random(); | 
|  | nodeId = (nodeId << 31) | (arc4random() >> 1); | 
|  | self.nodeID = @(nodeId); | 
|  | } | 
|  |  | 
|  | if (self.rootCertificate == nil) { | 
|  | NSError * error; | 
|  | self.rootCertificate = [MTRCertificates createRootCertificate:self.nocSigner | 
|  | issuerID:nil | 
|  | fabricID:self.fabricID | 
|  | error:&error]; | 
|  | if (error != nil || self.rootCertificate == nil) { | 
|  | MTR_LOG_ERROR("Failed to generate root certificate: %@", error); | 
|  | return nil; | 
|  | } | 
|  | } | 
|  |  | 
|  | _fabricTable = fabricTable; | 
|  | _keystore = keystore; | 
|  | _advertiseOperational = advertiseOperational; | 
|  |  | 
|  | return self; | 
|  | } | 
|  |  | 
|  | - (instancetype)initForExistingFabric:(FabricTable *)fabricTable | 
|  | fabricIndex:(FabricIndex)fabricIndex | 
|  | keystore:(chip::Crypto::OperationalKeystore *)keystore | 
|  | advertiseOperational:(BOOL)advertiseOperational | 
|  | params:(MTRDeviceControllerStartupParams *)params | 
|  | { | 
|  | if (!(self = [self initWithParams:params])) { | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | const FabricInfo * fabric = fabricTable->FindFabricWithIndex(fabricIndex); | 
|  |  | 
|  | if (self.vendorID == nil) { | 
|  | self.vendorID = @(fabric->GetVendorId()); | 
|  | } | 
|  |  | 
|  | BOOL usingExistingNOC = NO; | 
|  | if (self.operationalCertificate == nil && self.nodeID == nil) { | 
|  | self.nodeID = @(fabric->GetNodeId()); | 
|  |  | 
|  | // Make sure to preserve caseAuthenticatedTags from the existing certificate. | 
|  | uint8_t nocBuf[Credentials::kMaxCHIPCertLength]; | 
|  | MutableByteSpan noc(nocBuf); | 
|  | CHIP_ERROR err = fabricTable->FetchNOCCert(fabric->GetFabricIndex(), noc); | 
|  | if (err != CHIP_NO_ERROR) { | 
|  | MTR_LOG_ERROR("Failed to get existing NOC: %s", ErrorStr(err)); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | if (self.operationalKeypair == nil) { | 
|  | self.operationalCertificate = MatterCertToX509Data(noc); | 
|  | if (self.operationalCertificate == nil) { | 
|  | MTR_LOG_ERROR("Failed to convert TLV NOC to DER X.509: %s", ErrorStr(err)); | 
|  | return nil; | 
|  | } | 
|  | if (!keystore->HasOpKeypairForFabric(fabric->GetFabricIndex())) { | 
|  | MTR_LOG_ERROR("No existing operational key for fabric"); | 
|  | return nil; | 
|  | } | 
|  | } | 
|  |  | 
|  | CATValues cats; | 
|  | err = Credentials::ExtractCATsFromOpCert(noc, cats); | 
|  | if (err != CHIP_NO_ERROR) { | 
|  | MTR_LOG_ERROR("Failed to extract existing CATs: %s", ErrorStr(err)); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | auto tagCount = cats.GetNumTagsPresent(); | 
|  | if (tagCount > 0) { | 
|  | auto * catSet = [[NSMutableSet alloc] initWithCapacity:tagCount]; | 
|  | for (auto & value : cats.values) { | 
|  | if (value != kUndefinedCAT) { | 
|  | [catSet addObject:@(value)]; | 
|  | } | 
|  | } | 
|  | self.caseAuthenticatedTags = [NSSet setWithSet:catSet]; | 
|  | } else { | 
|  | self.caseAuthenticatedTags = nil; | 
|  | } | 
|  |  | 
|  | usingExistingNOC = YES; | 
|  | } | 
|  |  | 
|  | NSData * oldIntermediateCert = nil; | 
|  | { | 
|  | uint8_t icaBuf[Credentials::kMaxCHIPCertLength]; | 
|  | MutableByteSpan icaCert(icaBuf); | 
|  | CHIP_ERROR err = fabricTable->FetchICACert(fabric->GetFabricIndex(), icaCert); | 
|  | if (err != CHIP_NO_ERROR) { | 
|  | MTR_LOG_ERROR("Failed to get existing intermediate certificate: %s", ErrorStr(err)); | 
|  | return nil; | 
|  | } | 
|  | // There might not be an ICA cert for this fabric. | 
|  | if (!icaCert.empty()) { | 
|  | oldIntermediateCert = MatterCertToX509Data(icaCert); | 
|  | if (oldIntermediateCert == nil) { | 
|  | return nil; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (self.nocSigner != nil && self.intermediateCertificate == nil && oldIntermediateCert != nil) { | 
|  | // It's possible that we are switching from using an ICA cert to not using | 
|  | // one.  We can detect this case by checking whether the provided nocSigner | 
|  | // matches the ICA cert. | 
|  | if ([MTRCertificates keypair:self.nocSigner matchesCertificate:oldIntermediateCert] == YES) { | 
|  | // Keep using the existing intermediate certificate. | 
|  | self.intermediateCertificate = oldIntermediateCert; | 
|  | } | 
|  | // else presumably the nocSigner matches the root (will be verified later) | 
|  | // and we are no longer using an intermediate. | 
|  | } | 
|  |  | 
|  | // If we were planning to use our existing NOC from the fabric info but we are | 
|  | // changing from having an ICA to not having one, or changing from having one | 
|  | // to not having one, or changing the identity of our ICA, we need to generate | 
|  | // a new NOC.  But we can keep our existing operational keypair and node id; | 
|  | // nothing is forcing us to rotate those. | 
|  | if (usingExistingNOC == YES | 
|  | && ((oldIntermediateCert == nil) != (self.intermediateCertificate == nil) | 
|  | || ((oldIntermediateCert != nil) && | 
|  | [MTRCertificates isCertificate:oldIntermediateCert equalTo:self.intermediateCertificate] == NO))) { | 
|  | self.operationalCertificate = nil; | 
|  | } | 
|  |  | 
|  | NSData * oldRootCert; | 
|  | { | 
|  | uint8_t rootBuf[Credentials::kMaxCHIPCertLength]; | 
|  | MutableByteSpan rootCert(rootBuf); | 
|  | CHIP_ERROR err = fabricTable->FetchRootCert(fabric->GetFabricIndex(), rootCert); | 
|  | if (err != CHIP_NO_ERROR) { | 
|  | MTR_LOG_ERROR("Failed to get existing root certificate: %s", ErrorStr(err)); | 
|  | return nil; | 
|  | } | 
|  | oldRootCert = MatterCertToX509Data(rootCert); | 
|  | if (oldRootCert == nil) { | 
|  | return nil; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (self.rootCertificate == nil) { | 
|  | self.rootCertificate = oldRootCert; | 
|  | } else if ([MTRCertificates isCertificate:oldRootCert equalTo:self.rootCertificate] == NO) { | 
|  | MTR_LOG_ERROR("Root certificate identity does not match existing root certificate"); | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | _fabricTable = fabricTable; | 
|  | _fabricIndex.Emplace(fabricIndex); | 
|  | _keystore = keystore; | 
|  | _advertiseOperational = advertiseOperational; | 
|  |  | 
|  | return self; | 
|  | } | 
|  |  | 
|  | - (BOOL)keypairsMatchCertificates | 
|  | { | 
|  | if (self.nocSigner != nil) { | 
|  | NSData * signingCert = self.intermediateCertificate; | 
|  | if (signingCert == nil) { | 
|  | signingCert = self.rootCertificate; | 
|  | if (signingCert == nil) { | 
|  | MTR_LOG_ERROR("No certificate to match nocSigner"); | 
|  | return NO; | 
|  | } | 
|  | } | 
|  |  | 
|  | if ([MTRCertificates keypair:self.nocSigner matchesCertificate:signingCert] == NO) { | 
|  | MTR_LOG_ERROR("Provided nocSigner does not match certificates"); | 
|  | return NO; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (self.operationalCertificate != nil && self.operationalKeypair != nil) { | 
|  | if ([MTRCertificates keypair:self.operationalKeypair matchesCertificate:self.operationalCertificate] == NO) { | 
|  | MTR_LOG_ERROR("Provided operationalKeypair does not match operationalCertificate"); | 
|  | return NO; | 
|  | } | 
|  | } | 
|  |  | 
|  | return YES; | 
|  | } | 
|  |  | 
|  | @end |