Skip to content

Commit

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

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 committed Oct 5, 2023
1 parent d15daf8 commit 6bb4fd7
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 6bb4fd7

Please sign in to comment.