Skip to content

Commit

Permalink
Fix MTROperationalCredentialsClusterAttestationResponseParams.attesta…
Browse files Browse the repository at this point in the history
…tionChallenge on Darwin. (#29565)

This got broken because we had no tests.

Also makes attestationChallenge work when invoking via the
MTRDevice/MTRBaseDevice interface, not just MTRCluster/MTRBaseCluster.
  • Loading branch information
bzbarsky-apple authored Oct 9, 2023
1 parent 52225dd commit 762d3ab
Show file tree
Hide file tree
Showing 12 changed files with 355 additions and 52 deletions.
7 changes: 6 additions & 1 deletion src/darwin/Framework/CHIP/MTRBaseDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,12 @@ NS_ASSUME_NONNULL_BEGIN
* A structure-value is an NSArray object with NSDictionary objects as its elements. Each dictionary element will
* contain the following key values.
*
* MTRContextTagKey : NSNumber object as context tag.
* MTRContextTagKey : NSNumber object as context tag. This can
* actually be a fully-qualified profile tag,
* but for compatibility it's using the same
* key name. The two types of tags can be
* told apart by checking whether the value is
* in the context tag range (0 <= tag <= 0xFF).
* MTRDataKey : Data-value NSDictionary object.
*
* An array-value is an NSArray object with NSDictionary objects as its elements. Each dictionary element will
Expand Down
65 changes: 58 additions & 7 deletions src/darwin/Framework/CHIP/MTRBaseDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Matter/MTRClusterConstants.h>
#import <Matter/MTRDefines.h>

#import "MTRAttributeTLVValueDecoder_Internal.h"
Expand Down Expand Up @@ -583,7 +584,17 @@ - (void)subscribeWithQueue:(dispatch_queue_t)queue
NSMutableDictionary * arrayElement = [NSMutableDictionary dictionary];
[arrayElement setObject:value forKey:MTRDataKey];
if (dataTLVType == chip::TLV::kTLVType_Structure) {
[arrayElement setObject:[NSNumber numberWithUnsignedLong:TagNumFromTag(tag)] forKey:MTRContextTagKey];
uint64_t tagNum;
if (IsContextTag(tag)) {
tagNum = TagNumFromTag(tag);
} else if (IsProfileTag(tag)) {
uint64_t profile = ProfileIdFromTag(tag);
tagNum = (profile << kProfileIdShift) | TagNumFromTag(tag);
} else {
MTR_LOG_ERROR("Skipping unknown tag type when decoding TLV structure.");
continue;
}
[arrayElement setObject:[NSNumber numberWithUnsignedLongLong:tagNum] forKey:MTRContextTagKey];
}
[array addObject:arrayElement];
}
Expand Down Expand Up @@ -680,14 +691,28 @@ static CHIP_ERROR MTREncodeTLVFromDataValueDictionary(id object, chip::TLV::TLVW
MTR_LOG_ERROR("Error: Structure element to encode has corrupt type: %@", [element class]);
return CHIP_ERROR_INVALID_ARGUMENT;
}
NSNumber * elementTag = element[MTRContextTagKey];
id elementTag = element[MTRContextTagKey];
id elementValue = element[MTRDataKey];
if (!elementTag || !elementValue) {
MTR_LOG_ERROR("Error: Structure element to encode has corrupt value: %@", element);
return CHIP_ERROR_INVALID_ARGUMENT;
}
if (![elementTag isKindOfClass:NSNumber.class]) {
MTR_LOG_ERROR("Error: Structure element to encode has corrupt tag type: %@", [elementTag class]);
return CHIP_ERROR_INVALID_ARGUMENT;
}

// Our tag might actually be a profile tag.
uint64_t tagValue = [elementTag unsignedLongLongValue];
TLV::Tag tag;
if (tagValue > UINT8_MAX) {
tag = TLV::ProfileTag(tagValue >> kProfileIdShift,
(tagValue & ((1ull << kProfileIdShift) - 1)));
} else {
tag = TLV::ContextTag(static_cast<uint8_t>(tagValue));
}
ReturnErrorOnFailure(
MTREncodeTLVFromDataValueDictionary(elementValue, writer, chip::TLV::ContextTag([elementTag unsignedCharValue])));
MTREncodeTLVFromDataValueDictionary(elementValue, writer, tag));
}
ReturnErrorOnFailure(writer.EndContainer(outer));
return CHIP_NO_ERROR;
Expand Down Expand Up @@ -751,10 +776,10 @@ CHIP_ERROR Encode(chip::TLV::TLVWriter & writer, chip::TLV::Tag tag) const

static bool MustUseTimedInvoke() { return false; }

id _Nullable GetDecodedObject() const { return decodedObj; }
NSDictionary<NSString *, id> * _Nullable GetDecodedObject() const { return decodedObj; }

private:
id _Nullable decodedObj;
NSDictionary<NSString *, id> * _Nullable decodedObj;
};

// Callback bridge for MTRDataValueDictionaryCallback
Expand Down Expand Up @@ -1252,15 +1277,41 @@ - (void)_invokeCommandWithEndpointID:(NSNumber *)endpointID
auto * bridge = new MTRDataValueDictionaryCallbackBridge(queue, completion,
^(ExchangeManager & exchangeManager, const SessionHandle & session, MTRDataValueDictionaryCallback successCb,
MTRErrorCallback failureCb, MTRCallbackBridgeBase * bridge) {
NSData * attestationChallenge;
if ([clusterID isEqualToNumber:@(MTRClusterIDTypeOperationalCredentialsID)] &&
[commandID isEqualToNumber:@(MTRCommandIDTypeClusterOperationalCredentialsCommandAttestationRequestID)] && session->IsSecureSession()) {
// An AttestationResponse command needs to have an attestationChallenge
// to make sense of the results. If we are doing an
// AttestationRequest, store the challenge now.
attestationChallenge = AsData(session->AsSecureSession()->GetCryptoContext().GetAttestationChallenge());
}
// NSObjectCommandCallback guarantees that there will be exactly one call to either the success callback or the failure
// callback.
auto onSuccessCb = [successCb, bridge](const app::ConcreteCommandPath & commandPath, const app::StatusIB & status,
auto onSuccessCb = [successCb, bridge, attestationChallenge](const app::ConcreteCommandPath & commandPath, const app::StatusIB & status,
const MTRDataValueDictionaryDecodableType & responseData) {
auto resultArray = [[NSMutableArray alloc] init];
if (responseData.GetDecodedObject()) {
auto response = responseData.GetDecodedObject();
if (attestationChallenge != nil) {
// Add the attestationChallenge to our data.
NSArray<NSDictionary<NSString *, id> *> * value = response[MTRValueKey];
NSMutableArray<NSDictionary<NSString *, id> *> * newValue = [[NSMutableArray alloc] initWithCapacity:(value.count + 1)];
[newValue addObjectsFromArray:value];
[newValue addObject:@{
MTRContextTagKey : @(kAttestationChallengeTagValue),
MTRDataKey : @ {
MTRTypeKey : MTROctetStringValueType,
MTRValueKey : attestationChallenge,
},
}];
auto * newResponse = [NSMutableDictionary dictionaryWithCapacity:(response.count + 1)];
[newResponse addEntriesFromDictionary:response];
newResponse[MTRValueKey] = newValue;
response = newResponse;
}
[resultArray addObject:@ {
MTRCommandPathKey : [[MTRCommandPath alloc] initWithPath:commandPath],
MTRDataKey : responseData.GetDecodedObject()
MTRDataKey : response,
}];
} else {
[resultArray addObject:@ { MTRCommandPathKey : [[MTRCommandPath alloc] initWithPath:commandPath] }];
Expand Down
16 changes: 16 additions & 0 deletions src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,26 @@
#include <app/EventHeader.h>
#include <app/EventLoggingTypes.h>
#include <app/EventPathParams.h>
#include <lib/core/CHIPVendorIdentifiers.hpp>
#include <lib/core/TLVTags.h>
#include <system/SystemPacketBuffer.h>

@class MTRDeviceController;

// An AttestationResponse command needs to have an attestationChallenge
// to make sense of the results. Encode that with a profile-specific tag under
// the Apple vendor id. Let's select profile 0xFFFF just because, and use 0xFF
// for the actual tag number, so that if someone accidentally casts it to a
// uint8 (aka context tag) that will not collide with anything interesting.
inline constexpr chip::TLV::Tag kAttestationChallengeTag = chip::TLV::ProfileTag(chip::VendorId::Apple, 0xFFFF, 0xFF);

// We have no way to extract the tag value as a single thing, so just do it
// manually.
inline constexpr unsigned kProfileIdShift = 32;
inline constexpr uint64_t kAttestationChallengeTagProfile = chip::TLV::ProfileIdFromTag(kAttestationChallengeTag);
inline constexpr uint64_t kAttestationChallengeTagNumber = chip::TLV::TagNumFromTag(kAttestationChallengeTag);
inline constexpr uint64_t kAttestationChallengeTagValue = (kAttestationChallengeTagProfile << kProfileIdShift) | kAttestationChallengeTagNumber;

NS_ASSUME_NONNULL_BEGIN

static inline MTRTransportType MTRMakeTransportType(chip::Transport::Type type)
Expand Down
37 changes: 1 addition & 36 deletions src/darwin/Framework/CHIP/MTRCallbackBridgeBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,13 @@
#import "MTRBaseDevice_Internal.h"
#import "MTRDeviceController_Internal.h"
#import "MTRError_Internal.h"
#import "NSDataSpanConversion.h"
#import "zap-generated/MTRBaseClusters.h"
#import "zap-generated/MTRCommandPayloads_Internal.h"

#include <app-common/zap-generated/cluster-objects.h>
#include <app/data-model/NullObject.h>
#include <messaging/ExchangeMgr.h>
#include <platform/CHIPDeviceLayer.h>
#include <transport/Session.h>

#include <type_traits>

NS_ASSUME_NONNULL_BEGIN

/**
Expand Down Expand Up @@ -152,23 +147,8 @@ using MTRActionBlockT = CHIP_ERROR (^)(chip::Messaging::ExchangeManager & exchan
template <typename SuccessCallback>
using MTRLocalActionBlockT = CHIP_ERROR (^)(SuccessCallback successCb, MTRErrorCallback failureCb);

class NoAttestationChallenge {
};

class HaveAttestationChallenge {
protected:
NSData * mAttestationChallenge;
};

namespace detail {
using AttestationResponseCallback
= void (*)(void *, const chip::app::Clusters::OperationalCredentials::Commands::AttestationResponse::DecodableType &);
} // namespace detail

template <class T>
class MTRCallbackBridge : public MTRCallbackBridgeBase,
protected std::conditional<std::is_same_v<T, detail::AttestationResponseCallback>,
HaveAttestationChallenge, NoAttestationChallenge>::type {
class MTRCallbackBridge : public MTRCallbackBridgeBase {
public:
using MTRActionBlock = MTRActionBlockT<T>;
using MTRLocalActionBlock = MTRLocalActionBlockT<T>;
Expand Down Expand Up @@ -253,10 +233,6 @@ class MTRCallbackBridge : public MTRCallbackBridgeBase,
return;
}

if constexpr (HaveAttestationChallenge()) {
this->mAttestationChallenge = AsData(session.Value()->AsSecureSession()->GetCryptoContext().GetAttestationChallenge());
}

CHIP_ERROR err = action(*exchangeManager, session.Value(), mSuccess, mFailure, this);
if (err != CHIP_NO_ERROR) {
ChipLogError(Controller, "Failure performing action. C++-mangled success callback type: '%s', error: %s",
Expand All @@ -277,18 +253,7 @@ class MTRCallbackBridge : public MTRCallbackBridgeBase,

static void DispatchFailure(void * context, NSError * error) { DispatchCallbackResult(context, error, nil); }

template <typename ResponseType>
static void SetAttestationChallengeIfNeeded(void * context, ResponseType * _Nonnull response)
{
if constexpr (HaveAttestationChallenge()) {
auto * self = static_cast<MTRCallbackBridge *>(context);
response.attestationChallenge = self->mAttestationChallenge;
}
}

private:
static constexpr bool HaveAttestationChallenge() { return std::is_same_v<T, detail::AttestationResponseCallback>; }

static void DispatchCallbackResult(void * context, NSError * _Nullable error, id _Nullable value)
{
MTRCallbackBridge * callbackBridge = static_cast<MTRCallbackBridge *>(context);
Expand Down
28 changes: 28 additions & 0 deletions src/darwin/Framework/CHIP/MTRCommandPayloadExtensions_Internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 <Foundation/Foundation.h>
#import <Matter/MTRCommandPayloadsObjc.h>
#import <Matter/MTRDefines.h>

NS_ASSUME_NONNULL_BEGIN

@interface MTROperationalCredentialsClusterAttestationResponseParams ()
@property (nonatomic, copy) NSData * attestationChallenge;
@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#import "MTRCommandPayloadsObjc.h"
#import "MTRCommandPayloads_Internal.h"
#import "MTRCommandPayloadExtensions_Internal.h"
#import "MTRBaseDevice_Internal.h"
#import "MTRError_Internal.h"
#import "MTRLogging_Internal.h"
Expand Down Expand Up @@ -97,6 +98,86 @@ NS_ASSUME_NONNULL_BEGIN
err = chip::app::DataModel::Decode(reader, decodedStruct);
if (err == CHIP_NO_ERROR) {
err = [self _setFieldsFromDecodableStruct:decodedStruct];
{{#if (and (isStrEqual (asUpperCamelCase parent.name preserveAcronyms=true) "OperationalCredentials")
(isStrEqual (asUpperCamelCase name preserveAcronyms=true) "AttestationResponse"))}}
if (err == CHIP_NO_ERROR) {
do {
// AttestationResponse has an extra attestationChallenge field. Once we
// have some sort of more direct decoding from the responseValue, we can
// probably make this less hardcoded.
//
// It might be simpler to look for the right profile tag in the TLV, but let's stick to examining
// the responseValue we were handed.
id data = responseValue[MTRDataKey];
if (![data isKindOfClass:NSDictionary.class]) {
err = CHIP_ERROR_INVALID_ARGUMENT;
break;
}

NSDictionary * dataDictionary = data;
if (dataDictionary[MTRTypeKey] == nil ||
![dataDictionary[MTRTypeKey] isKindOfClass:NSString.class] ||
![dataDictionary[MTRTypeKey] isEqualToString:MTRStructureValueType]) {
err = CHIP_ERROR_INVALID_ARGUMENT;
break;
}

id value = dataDictionary[MTRValueKey];
if (value == nil || ![value isKindOfClass:NSArray.class]) {
err = CHIP_ERROR_INVALID_ARGUMENT;
break;
}

NSArray * valueArray = value;
for (id item in valueArray) {
if (![item isKindOfClass:NSDictionary.class]) {
err = CHIP_ERROR_INVALID_ARGUMENT;
break;
}

NSDictionary * itemDictionary = item;
id contextTag = itemDictionary[MTRContextTagKey];
if (contextTag == nil || ![contextTag isKindOfClass:NSNumber.class]) {
err = CHIP_ERROR_INVALID_ARGUMENT;
break;
}

NSNumber * contextTagNumber = contextTag;
if (![contextTagNumber isEqualToNumber:@(kAttestationChallengeTagValue)]) {
// Not the right field; keep going.
continue;
}

id data = itemDictionary[MTRDataKey];
if (data == nil || ![data isKindOfClass:NSDictionary.class]) {
err = CHIP_ERROR_INVALID_ARGUMENT;
break;
}

NSDictionary * dataDictionary = data;
id dataType = dataDictionary[MTRTypeKey];
id dataValue = dataDictionary[MTRValueKey];
if (dataType == nil || dataValue == nil ||
![dataType isKindOfClass:NSString.class] ||
![dataValue isKindOfClass:NSData.class]) {
err = CHIP_ERROR_INVALID_ARGUMENT;
break;
}

NSString * dataTypeString = dataType;
if (![dataTypeString isEqualToString:MTROctetStringValueType]) {
err = CHIP_ERROR_INVALID_ARGUMENT;
break;
}

self.attestationChallenge = dataValue;
break;
}

// Do not add code here without first checking whether err is success.
} while (0);
}
{{/if}}
if (err == CHIP_NO_ERROR) {
return self;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
{{> header excludeZapComment=true}}

#import <Matter/MTRDefines.h>
#import <Matter/MTRCommandPayloadsObjc.h>

#include <app-common/zap-generated/cluster-objects.h>

NS_ASSUME_NONNULL_BEGIN

@interface MTROperationalCredentialsClusterAttestationResponseParams ()
@property (nonatomic, strong) NSData * attestationChallenge;
@end

{{#zcl_clusters}}
{{#zcl_commands}}
{{#if (isSupported (asUpperCamelCase parent.name preserveAcronyms=true) command=(asUpperCamelCase name preserveAcronyms=true) isForCommandPayload=true)}}
Expand Down
Loading

0 comments on commit 762d3ab

Please sign in to comment.