From 762d3ab88e176b1ed3d6b3ebd007ad497777f1cd Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Mon, 9 Oct 2023 11:34:26 -0400 Subject: [PATCH] Fix MTROperationalCredentialsClusterAttestationResponseParams.attestationChallenge 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. --- src/darwin/Framework/CHIP/MTRBaseDevice.h | 7 +- src/darwin/Framework/CHIP/MTRBaseDevice.mm | 65 +++++++++++++-- .../Framework/CHIP/MTRBaseDevice_Internal.h | 16 ++++ .../Framework/CHIP/MTRCallbackBridgeBase.h | 37 +-------- .../MTRCommandPayloadExtensions_Internal.h | 28 +++++++ .../templates/MTRCommandPayloadsObjc-src.zapt | 81 +++++++++++++++++++ .../MTRCommandPayloads_Internal.zapt | 5 +- .../zap-generated/MTRCommandPayloadsObjc.mm | 74 +++++++++++++++++ .../MTRCommandPayloads_Internal.h | 5 +- .../Framework/CHIPTests/MTRDeviceTests.m | 78 ++++++++++++++++++ .../CHIPTests/MTRSwiftDeviceTests.swift | 3 + .../Matter.xcodeproj/project.pbxproj | 8 ++ 12 files changed, 355 insertions(+), 52 deletions(-) create mode 100644 src/darwin/Framework/CHIP/MTRCommandPayloadExtensions_Internal.h diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.h b/src/darwin/Framework/CHIP/MTRBaseDevice.h index c6acd9046d2c67..725f4759a10da5 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.h +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.h @@ -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 diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.mm b/src/darwin/Framework/CHIP/MTRBaseDevice.mm index 1d76e22d9fc53a..1c9b1a9d851f4e 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.mm +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.mm @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#import #import #import "MTRAttributeTLVValueDecoder_Internal.h" @@ -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]; } @@ -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(tagValue)); + } ReturnErrorOnFailure( - MTREncodeTLVFromDataValueDictionary(elementValue, writer, chip::TLV::ContextTag([elementTag unsignedCharValue]))); + MTREncodeTLVFromDataValueDictionary(elementValue, writer, tag)); } ReturnErrorOnFailure(writer.EndContainer(outer)); return CHIP_NO_ERROR; @@ -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 * _Nullable GetDecodedObject() const { return decodedObj; } private: - id _Nullable decodedObj; + NSDictionary * _Nullable decodedObj; }; // Callback bridge for MTRDataValueDictionaryCallback @@ -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 *> * value = response[MTRValueKey]; + NSMutableArray *> * 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] }]; diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h b/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h index 671ef1a25b2660..19af65317cc7da 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h +++ b/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h @@ -26,10 +26,26 @@ #include #include #include +#include +#include #include @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) diff --git a/src/darwin/Framework/CHIP/MTRCallbackBridgeBase.h b/src/darwin/Framework/CHIP/MTRCallbackBridgeBase.h index e1f81b101bc88e..e99030749a5baa 100644 --- a/src/darwin/Framework/CHIP/MTRCallbackBridgeBase.h +++ b/src/darwin/Framework/CHIP/MTRCallbackBridgeBase.h @@ -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 #include #include #include #include -#include - NS_ASSUME_NONNULL_BEGIN /** @@ -152,23 +147,8 @@ using MTRActionBlockT = CHIP_ERROR (^)(chip::Messaging::ExchangeManager & exchan template 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 MTRCallbackBridge : public MTRCallbackBridgeBase, - protected std::conditional, - HaveAttestationChallenge, NoAttestationChallenge>::type { +class MTRCallbackBridge : public MTRCallbackBridgeBase { public: using MTRActionBlock = MTRActionBlockT; using MTRLocalActionBlock = MTRLocalActionBlockT; @@ -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", @@ -277,18 +253,7 @@ class MTRCallbackBridge : public MTRCallbackBridgeBase, static void DispatchFailure(void * context, NSError * error) { DispatchCallbackResult(context, error, nil); } - template - static void SetAttestationChallengeIfNeeded(void * context, ResponseType * _Nonnull response) - { - if constexpr (HaveAttestationChallenge()) { - auto * self = static_cast(context); - response.attestationChallenge = self->mAttestationChallenge; - } - } - private: - static constexpr bool HaveAttestationChallenge() { return std::is_same_v; } - static void DispatchCallbackResult(void * context, NSError * _Nullable error, id _Nullable value) { MTRCallbackBridge * callbackBridge = static_cast(context); diff --git a/src/darwin/Framework/CHIP/MTRCommandPayloadExtensions_Internal.h b/src/darwin/Framework/CHIP/MTRCommandPayloadExtensions_Internal.h new file mode 100644 index 00000000000000..b695649894f2a1 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRCommandPayloadExtensions_Internal.h @@ -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 +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MTROperationalCredentialsClusterAttestationResponseParams () +@property (nonatomic, copy) NSData * attestationChallenge; +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/templates/MTRCommandPayloadsObjc-src.zapt b/src/darwin/Framework/CHIP/templates/MTRCommandPayloadsObjc-src.zapt index 960e15a392407a..aa7fe84de732d2 100644 --- a/src/darwin/Framework/CHIP/templates/MTRCommandPayloadsObjc-src.zapt +++ b/src/darwin/Framework/CHIP/templates/MTRCommandPayloadsObjc-src.zapt @@ -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" @@ -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; } diff --git a/src/darwin/Framework/CHIP/templates/MTRCommandPayloads_Internal.zapt b/src/darwin/Framework/CHIP/templates/MTRCommandPayloads_Internal.zapt index 83a6d1c44b5bc1..098769fe0a4995 100644 --- a/src/darwin/Framework/CHIP/templates/MTRCommandPayloads_Internal.zapt +++ b/src/darwin/Framework/CHIP/templates/MTRCommandPayloads_Internal.zapt @@ -1,15 +1,12 @@ {{> header excludeZapComment=true}} #import +#import #include 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)}} diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm index c9e838300eff1a..6ffbc9daaa07ae 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm @@ -18,6 +18,7 @@ #import "MTRCommandPayloadsObjc.h" #import "MTRBackwardsCompatShims.h" #import "MTRBaseDevice_Internal.h" +#import "MTRCommandPayloadExtensions_Internal.h" #import "MTRCommandPayloads_Internal.h" #import "MTRError_Internal.h" #import "MTRLogging_Internal.h" @@ -9783,6 +9784,79 @@ - (nullable instancetype)initWithResponseValue:(NSDictionary *)r err = chip::app::DataModel::Decode(reader, decodedStruct); if (err == CHIP_NO_ERROR) { err = [self _setFieldsFromDecodableStruct:decodedStruct]; + 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 (err == CHIP_NO_ERROR) { return self; } diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloads_Internal.h b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloads_Internal.h index fdecc5a05fdd24..c00abea960dec0 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloads_Internal.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloads_Internal.h @@ -15,16 +15,13 @@ * limitations under the License. */ +#import #import #include NS_ASSUME_NONNULL_BEGIN -@interface MTROperationalCredentialsClusterAttestationResponseParams () -@property (nonatomic, strong) NSData * attestationChallenge; -@end - @interface MTRIdentifyClusterIdentifyParams (InternalMethods) - (NSDictionary * _Nullable)_encodeAsDataValue:(NSError * __autoreleasing *)error; diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m index 661bde70075fee..083d241d104263 100644 --- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m +++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m @@ -24,6 +24,7 @@ #import #import +#import "MTRCommandPayloadExtensions_Internal.h" #import "MTRErrorTestUtils.h" #import "MTRTestKeys.h" #import "MTRTestResetCommissioneeHelper.h" @@ -2446,6 +2447,83 @@ - (void)test026_LocationAttribute [self waitForExpectations:@[ expectation ] timeout:kTimeoutInSeconds]; } +- (void)test027_AttestationChallenge +{ + // Check that we have an attestation challenge result in + // MTROperationalCredentialsClusterAttestationResponseParams. + dispatch_queue_t queue = dispatch_get_main_queue(); + + // We don't care about our nonce being random here, so just all-0 is fine. + __auto_type * nonce = [[NSMutableData alloc] initWithLength:32]; + __auto_type * params = [[MTROperationalCredentialsClusterAttestationRequestParams alloc] init]; + params.attestationNonce = nonce; + + __auto_type * baseDevice = GetConnectedDevice(); + __auto_type * baseCluster = [[MTRBaseClusterOperationalCredentials alloc] initWithDevice:baseDevice endpointID:@(0) queue:queue]; + XCTestExpectation * attestationRequestedViaBaseCluster = [self expectationWithDescription:@"Invoked AttestationRequest via base cluster"]; + [baseCluster attestationRequestWithParams:params completion:^(MTROperationalCredentialsClusterAttestationResponseParams * _Nullable data, NSError * _Nullable error) { + XCTAssertNil(error); + XCTAssertNotNil(data); + XCTAssertNotNil(data.attestationChallenge); + [attestationRequestedViaBaseCluster fulfill]; + }]; + + [self waitForExpectations:@[ attestationRequestedViaBaseCluster ] timeout:kTimeoutInSeconds]; + + __auto_type * requestFields = @{ + MTRTypeKey : MTRStructureValueType, + MTRValueKey : @[ + @{ + MTRContextTagKey : @(0), // AttestationNonce + MTRDataKey : @ { + MTRTypeKey : MTROctetStringValueType, + MTRValueKey : nonce, + }, + }, + ], + }; + + XCTestExpectation * attestationRequestedViaBaseDevice = [self expectationWithDescription:@"Invoked AttestationRequest via base device"]; + [baseDevice invokeCommandWithEndpointID:@(0) clusterID:@(MTRClusterIDTypeOperationalCredentialsID) commandID:@(MTRCommandIDTypeClusterOperationalCredentialsCommandAttestationRequestID) commandFields:requestFields timedInvokeTimeout:nil queue:queue completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + XCTAssertNil(error); + XCTAssertNotNil(values); + XCTAssertEqual(values.count, 1); + __auto_type * response = [[MTROperationalCredentialsClusterAttestationResponseParams alloc] initWithResponseValue:values[0] error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(response); + XCTAssertNotNil(response.attestationChallenge); + [attestationRequestedViaBaseDevice fulfill]; + }]; + + [self waitForExpectations:@[ attestationRequestedViaBaseDevice ] timeout:kTimeoutInSeconds]; + + __auto_type * device = [MTRDevice deviceWithNodeID:kDeviceId deviceController:sController]; + __auto_type * cluster = [[MTRClusterOperationalCredentials alloc] initWithDevice:device endpointID:@(0) queue:queue]; + XCTestExpectation * attestationRequestedViaCluster = [self expectationWithDescription:@"Invoked AttestationRequest via cluster"]; + [cluster attestationRequestWithParams:params expectedValues:nil expectedValueInterval:nil completion:^(MTROperationalCredentialsClusterAttestationResponseParams * _Nullable data, NSError * _Nullable error) { + XCTAssertNil(error); + XCTAssertNotNil(data); + XCTAssertNotNil(data.attestationChallenge); + [attestationRequestedViaCluster fulfill]; + }]; + + [self waitForExpectations:@[ attestationRequestedViaCluster ] timeout:kTimeoutInSeconds]; + + XCTestExpectation * attestationRequestedViaDevice = [self expectationWithDescription:@"Invoked AttestationRequest via device"]; + [device invokeCommandWithEndpointID:@(0) clusterID:@(MTRClusterIDTypeOperationalCredentialsID) commandID:@(MTRCommandIDTypeClusterOperationalCredentialsCommandAttestationRequestID) commandFields:requestFields expectedValues:nil expectedValueInterval:nil timedInvokeTimeout:nil queue:queue completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + XCTAssertNil(error); + XCTAssertNotNil(values); + XCTAssertEqual(values.count, 1); + __auto_type * response = [[MTROperationalCredentialsClusterAttestationResponseParams alloc] initWithResponseValue:values[0] error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(response); + XCTAssertNotNil(response.attestationChallenge); + [attestationRequestedViaDevice fulfill]; + }]; + + [self waitForExpectations:@[ attestationRequestedViaDevice ] timeout:kTimeoutInSeconds]; +} + - (void)test900_SubscribeAllAttributes { MTRBaseDevice * device = GetConnectedDevice(); diff --git a/src/darwin/Framework/CHIPTests/MTRSwiftDeviceTests.swift b/src/darwin/Framework/CHIPTests/MTRSwiftDeviceTests.swift index e40c855763d60b..badcbabbca2fd2 100644 --- a/src/darwin/Framework/CHIPTests/MTRSwiftDeviceTests.swift +++ b/src/darwin/Framework/CHIPTests/MTRSwiftDeviceTests.swift @@ -389,6 +389,9 @@ class MTRSwiftDeviceTests : XCTestCase { XCTAssertEqual(eventReportsReceived, 0); } + // Note: test027_AttestationChallenge is not implementable in Swift so far, + // because the attestationChallenge property is internal-only + func test999_TearDown() { ResetCommissionee(sConnectedDevice, DispatchQueue.main, self, DeviceConstants.timeoutInSeconds) diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 87196b6063c07d..f0ff708848e53b 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -192,6 +192,8 @@ 51E95DFB2A78443C00A434F0 /* MTRSessionResumptionStorageBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E95DF92A78443C00A434F0 /* MTRSessionResumptionStorageBridge.h */; }; 51E95DFC2A78443C00A434F0 /* MTRSessionResumptionStorageBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51E95DFA2A78443C00A434F0 /* MTRSessionResumptionStorageBridge.mm */; }; 51EF279F2A2A3EB100E33F75 /* MTRBackwardsCompatShims.h in Headers */ = {isa = PBXBuildFile; fileRef = 51EF279E2A2A3EB100E33F75 /* MTRBackwardsCompatShims.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 51FE72352ACDB40000437032 /* MTRCommandPayloads_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 51FE72342ACDB40000437032 /* MTRCommandPayloads_Internal.h */; }; + 51FE723F2ACDEF3E00437032 /* MTRCommandPayloadExtensions_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 51FE723E2ACDEF3E00437032 /* MTRCommandPayloadExtensions_Internal.h */; }; 5A60370827EA1FF60020DB79 /* MTRClusterStateCacheContainer+XPC.h in Headers */ = {isa = PBXBuildFile; fileRef = 5A60370727EA1FF60020DB79 /* MTRClusterStateCacheContainer+XPC.h */; }; 5A6FEC9027B563D900F25F42 /* MTRDeviceControllerOverXPC.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5A6FEC8F27B563D900F25F42 /* MTRDeviceControllerOverXPC.mm */; }; 5A6FEC9227B5669C00F25F42 /* MTRDeviceControllerOverXPC.h in Headers */ = {isa = PBXBuildFile; fileRef = 5A6FEC8D27B5624E00F25F42 /* MTRDeviceControllerOverXPC.h */; }; @@ -552,6 +554,8 @@ 51E95DF92A78443C00A434F0 /* MTRSessionResumptionStorageBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRSessionResumptionStorageBridge.h; sourceTree = ""; }; 51E95DFA2A78443C00A434F0 /* MTRSessionResumptionStorageBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRSessionResumptionStorageBridge.mm; sourceTree = ""; }; 51EF279E2A2A3EB100E33F75 /* MTRBackwardsCompatShims.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRBackwardsCompatShims.h; sourceTree = ""; }; + 51FE72342ACDB40000437032 /* MTRCommandPayloads_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRCommandPayloads_Internal.h; sourceTree = ""; }; + 51FE723E2ACDEF3E00437032 /* MTRCommandPayloadExtensions_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRCommandPayloadExtensions_Internal.h; sourceTree = ""; }; 5A60370727EA1FF60020DB79 /* MTRClusterStateCacheContainer+XPC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MTRClusterStateCacheContainer+XPC.h"; sourceTree = ""; }; 5A6FEC8B27B5609C00F25F42 /* MTRDeviceOverXPC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDeviceOverXPC.h; sourceTree = ""; }; 5A6FEC8D27B5624E00F25F42 /* MTRDeviceControllerOverXPC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDeviceControllerOverXPC.h; sourceTree = ""; }; @@ -984,6 +988,7 @@ 7596A85228788557004DAE0E /* MTRClusters.mm */, 51B22C212740CB1D008D5055 /* MTRCommandPayloadsObjc.h */, 51B22C292740CB47008D5055 /* MTRCommandPayloadsObjc.mm */, + 51FE72342ACDB40000437032 /* MTRCommandPayloads_Internal.h */, 7560FD1B27FBBD3F005E85B3 /* MTREventTLVValueDecoder.mm */, 51B22C1D2740CB0A008D5055 /* MTRStructsObjc.h */, 51B22C252740CB32008D5055 /* MTRStructsObjc.mm */, @@ -1131,6 +1136,7 @@ 5ACDDD7B27CD14AF00EFD68A /* MTRClusterStateCacheContainer_Internal.h */, 5ACDDD7C27CD16D200EFD68A /* MTRClusterStateCacheContainer.mm */, 5A60370727EA1FF60020DB79 /* MTRClusterStateCacheContainer+XPC.h */, + 51FE723E2ACDEF3E00437032 /* MTRCommandPayloadExtensions_Internal.h */, 99D466E02798936D0089A18F /* MTRCommissioningParameters.h */, 99AECC7F2798A57E00B6355B /* MTRCommissioningParameters.mm */, 3DFCB32B29678C9500332B35 /* MTRConversion.h */, @@ -1454,6 +1460,7 @@ 2C5EEEF6268A85C400CAE3D3 /* MTRDeviceConnectionBridge.h in Headers */, 2C8C8FC0253E0C2100797F05 /* MTRPersistentStorageDelegateBridge.h in Headers */, 51E4D121291D0EB400C8C535 /* MTRBaseClusterUtils.h in Headers */, + 51FE72352ACDB40000437032 /* MTRCommandPayloads_Internal.h in Headers */, 51E51FC0282AD37A00FC978D /* MTRDeviceControllerStartupParams_Internal.h in Headers */, 3DECCB702934AECD00585AEC /* MTRLogging.h in Headers */, 1E4D654E29C208DD00BC3478 /* MTRCommissionableBrowserResult.h in Headers */, @@ -1463,6 +1470,7 @@ 51565CB12A7AD77600469F18 /* MTRDeviceControllerDataStore.h in Headers */, 3D843713294977000070D20A /* NSDataSpanConversion.h in Headers */, 991DC08B247704DC00C13860 /* MTRLogging_Internal.h in Headers */, + 51FE723F2ACDEF3E00437032 /* MTRCommandPayloadExtensions_Internal.h in Headers */, 1E4D655029C208DD00BC3478 /* MTRCommissionableBrowserDelegate.h in Headers */, 7596A84828762783004DAE0E /* MTRAsyncCallbackWorkQueue.h in Headers */, 5A7947E527C0129F00434CF2 /* MTRDeviceController+XPC.h in Headers */,