blob: ca92c63bf2171bb67ce8a56049c6c1938cc6ca5e [file] [log] [blame]
/**
* 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 "MTRConversion.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) {
self.caseAuthenticatedTags = CATValuesToSet(cats);
} 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