From 1067880bbbec89cdd735349c7ac8bdf0b6547aeb Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Fri, 12 May 2023 14:18:45 -0400 Subject: [PATCH] Stop validating certificate lifetimes in Matter.framework. (#26546) --- .../CHIP/MTRDeviceControllerFactory.mm | 13 + .../CHIPTests/MTRCertificateValidityTests.m | 323 ++++++++++++++++++ .../Matter.xcodeproj/project.pbxproj | 4 + 3 files changed, 340 insertions(+) create mode 100644 src/darwin/Framework/CHIPTests/MTRCertificateValidityTests.m diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm index ada02b3817529b..93f7abeb91a979 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm @@ -57,6 +57,7 @@ static NSString * const kErrorDACVerifierInit = @"Init failure while creating the device attestation verifier"; static NSString * const kErrorGroupProviderInit = @"Init failure while initializing group data provider"; static NSString * const kErrorControllersInit = @"Init controllers array failure"; +static NSString * const kErrorCertificateValidityPolicyInit = @"Init certificate validity policy failure"; static NSString * const kErrorControllerFactoryInit = @"Init failure while initializing controller factory"; static NSString * const kErrorKeystoreInit = @"Init failure while initializing persistent storage keystore"; static NSString * const kErrorCertStoreInit = @"Init failure while initializing persistent storage operational certificate store"; @@ -86,6 +87,7 @@ @interface MTRDeviceControllerFactory () @property (readonly) MTROperationalBrowser * operationalBrowser; @property () chip::Credentials::DeviceAttestationVerifier * deviceAttestationVerifier; @property (readonly) BOOL advertiseOperational; +@property (nonatomic, readonly) Credentials::IgnoreCertificateValidityPeriodPolicy * certificateValidityPolicy; - (BOOL)findMatchingFabric:(FabricTable &)fabricTable params:(MTRDeviceControllerStartupParams *)params @@ -150,6 +152,11 @@ - (instancetype)init return nil; } + _certificateValidityPolicy = new Credentials::IgnoreCertificateValidityPeriodPolicy(); + if ([self checkForInitError:(_certificateValidityPolicy != nil) logMsg:kErrorCertificateValidityPolicyInit]) { + return nil; + } + return self; } @@ -204,6 +211,11 @@ - (void)cleanupInitObjects delete _sessionKeystore; _sessionKeystore = nullptr; } + + if (_certificateValidityPolicy) { + delete _certificateValidityPolicy; + _certificateValidityPolicy = nullptr; + } } - (void)cleanupStartupObjects @@ -443,6 +455,7 @@ - (BOOL)startControllerFactory:(MTRDeviceControllerFactoryParams *)startupParams params.fabricIndependentStorage = _persistentStorageDelegateBridge; params.operationalKeystore = _keystore; params.opCertStore = _opCertStore; + params.certificateValidityPolicy = _certificateValidityPolicy; errorCode = _controllerFactory->Init(params); if (errorCode != CHIP_NO_ERROR) { MTR_LOG_ERROR("Error: %@", kErrorControllerFactoryInit); diff --git a/src/darwin/Framework/CHIPTests/MTRCertificateValidityTests.m b/src/darwin/Framework/CHIPTests/MTRCertificateValidityTests.m new file mode 100644 index 00000000000000..34fd5d5814d254 --- /dev/null +++ b/src/darwin/Framework/CHIPTests/MTRCertificateValidityTests.m @@ -0,0 +1,323 @@ +/* + * + * 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. + */ + +// module headers +#import + +#import "MTRErrorTestUtils.h" +#import "MTRTestKeys.h" +#import "MTRTestResetCommissioneeHelper.h" +#import "MTRTestStorage.h" + +// system dependencies +#import + +static const uint16_t kPairingTimeoutInSeconds = 10; +static const uint16_t kTimeoutInSeconds = 3; +static const uint64_t kDeviceId = 0x12341234; +static const uint64_t kControllerId = 0x56788765; +static NSString * kOnboardingPayload = @"MT:-24J0AFN00KA0648G00"; +static const uint16_t kLocalPort = 5541; +static const uint16_t kTestVendorId = 0xFFF1u; + +static MTRBaseDevice * sConnectedDevice; + +// Singleton controller we use. +static MTRDeviceController * sController = nil; + +@interface MTRCertificateValidityTestControllerDelegate : NSObject +@property (nonatomic, readonly) XCTestExpectation * expectation; +@property (nonatomic, readonly) NSNumber * commissioneeNodeID; +@end + +@implementation MTRCertificateValidityTestControllerDelegate +- (id)initWithExpectation:(XCTestExpectation *)expectation commissioneeNodeID:(NSNumber *)nodeID +{ + self = [super init]; + if (self) { + _expectation = expectation; + _commissioneeNodeID = nodeID; + } + return self; +} + +- (void)controller:(MTRDeviceController *)controller commissioningSessionEstablishmentDone:(NSError * _Nullable)error +{ + XCTAssertEqual(error.code, 0); + + NSError * commissionError = nil; + [sController commissionNodeWithID:self.commissioneeNodeID + commissioningParams:[[MTRCommissioningParameters alloc] init] + error:&commissionError]; + XCTAssertNil(commissionError); + + // Keep waiting for onCommissioningComplete +} + +- (void)controller:(MTRDeviceController *)controller commissioningComplete:(NSError *)error +{ + XCTAssertEqual(error.code, 0); + [_expectation fulfill]; + _expectation = nil; +} +@end + +@interface MTRTestCertificateIssuer : NSObject + +@property (nonatomic, readonly) MTRTestKeys * rootKey; +@property (nonatomic, copy, readonly) MTRCertificateDERBytes rootCertificate; +@property (nonatomic, copy, readonly) NSDateInterval * validityPeriod; +@property (nonatomic, copy, readonly) NSNumber * fabricID; +@property (nonatomic, readonly) BOOL shouldSkipAttestationCertificateValidation; + +- (nullable instancetype)initWithRootKey:(MTRTestKeys *)key + fabricID:(NSNumber *)fabricID + validityPeriod:(NSDateInterval *)validityPeriod; + +- (nullable MTRCertificateDERBytes)issueOperationalCertificateForNode:(NSNumber *)nodeID + operationalPublicKey:(SecKeyRef)operationalPublicKey; + +- (void)issueOperationalCertificateForRequest:(MTROperationalCSRInfo *)csrInfo + attestationInfo:(MTRDeviceAttestationInfo *)attestationInfo + controller:(MTRDeviceController *)controller + completion:(void (^)(MTROperationalCertificateChain * _Nullable info, + NSError * _Nullable error))completion; +@end + +@implementation MTRTestCertificateIssuer +- (nullable instancetype)initWithRootKey:(MTRTestKeys *)key + fabricID:(NSNumber *)fabricID + validityPeriod:(NSDateInterval *)validityPeriod +{ + if (!(self = [super init])) { + return nil; + } + + NSError * error; + __auto_type * rootCertificate = [MTRCertificates createRootCertificate:key + issuerID:nil + fabricID:fabricID + validityPeriod:validityPeriod + error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(rootCertificate); + + if (rootCertificate == nil) { + return nil; + } + + _validityPeriod = validityPeriod; + _rootCertificate = rootCertificate; + _rootKey = key; + _fabricID = fabricID; + _shouldSkipAttestationCertificateValidation = NO; + + return self; +} + +- (nullable MTRCertificateDERBytes)issueOperationalCertificateForNode:(NSNumber *)nodeID + operationalPublicKey:(SecKeyRef)operationalPublicKey +{ + return [MTRCertificates createOperationalCertificate:self.rootKey + signingCertificate:self.rootCertificate + operationalPublicKey:operationalPublicKey + fabricID:self.fabricID + nodeID:nodeID + caseAuthenticatedTags:nil + validityPeriod:self.validityPeriod + error:nil]; +} + +- (void)issueOperationalCertificateForRequest:(MTROperationalCSRInfo *)csrInfo + attestationInfo:(MTRDeviceAttestationInfo *)attestationInfo + controller:(MTRDeviceController *)controller + completion:(void (^)(MTROperationalCertificateChain * _Nullable info, + NSError * _Nullable error))completion +{ + NSError * error; + __auto_type * publicKey = [MTRCertificates publicKeyFromCSR:csrInfo.csr error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(publicKey); + + NSDictionary * attributes = + @{ (id) kSecAttrKeyType : (id) kSecAttrKeyTypeECSECPrimeRandom, (id) kSecAttrKeyClass : (id) kSecAttrKeyClassPublic }; + CFErrorRef keyCreationError = NULL; + SecKeyRef operationalPublicKey + = SecKeyCreateWithData((__bridge CFDataRef) publicKey, (__bridge CFDictionaryRef) attributes, &keyCreationError); + XCTAssertNotNil((__bridge id) operationalPublicKey); + XCTAssertNil((__bridge id) keyCreationError); + + __auto_type * operationalCertificate = [self issueOperationalCertificateForNode:@(kDeviceId) + operationalPublicKey:operationalPublicKey]; + XCTAssertNotNil(operationalCertificate); + + __auto_type * certChain = [[MTROperationalCertificateChain alloc] initWithOperationalCertificate:operationalCertificate + intermediateCertificate:nil + rootCertificate:self.rootCertificate + adminSubject:nil]; + XCTAssertNotNil(certChain); + completion(certChain, nil); +} +@end + +@interface MTRTestExpiredCertificateIssuer : MTRTestCertificateIssuer + +- (nullable instancetype)initWithRootKey:(MTRTestKeys *)key fabricID:(NSNumber *)fabricID; + +@end + +@implementation MTRTestExpiredCertificateIssuer +- (nullable instancetype)initWithRootKey:(MTRTestKeys *)key fabricID:(NSNumber *)fabricID +{ + // Ensure oldDate is before newDate and both are in the past. + __auto_type * oldDate = [NSDate dateWithTimeIntervalSinceNow:-5]; + __auto_type * newDate = [NSDate dateWithTimeIntervalSinceNow:-2]; + __auto_type * validityPeriod = [[NSDateInterval alloc] initWithStartDate:oldDate endDate:newDate]; + + return [super initWithRootKey:key fabricID:fabricID validityPeriod:validityPeriod]; +} + +@end + +@interface MTRCertificateValidityTests : XCTestCase +@end + +static BOOL sNeedsStackShutdown = YES; + +@implementation MTRCertificateValidityTests + ++ (void)tearDown +{ + // Global teardown, runs once + if (sNeedsStackShutdown) { + // We don't need to worry about ResetCommissionee. If we get here, + // we're running only one of our test methods (using + // -only-testing:MatterTests/MTROTAProviderTests/testMethodName), since + // we did not run test999_TearDown. + [self shutdownStack]; + } +} + +- (void)setUp +{ + // Per-test setup, runs before each test. + [super setUp]; + [self setContinueAfterFailure:NO]; +} + +- (MTRBaseDevice *)commissionDeviceWithPayload:(NSString *)payloadString nodeID:(NSNumber *)nodeID +{ + XCTestExpectation * expectation = + [self expectationWithDescription:[NSString stringWithFormat:@"Commissioning Complete for %@", nodeID]]; + __auto_type * deviceControllerDelegate = [[MTRCertificateValidityTestControllerDelegate alloc] initWithExpectation:expectation + commissioneeNodeID:nodeID]; + dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.device_controller_delegate", DISPATCH_QUEUE_SERIAL); + + [sController setDeviceControllerDelegate:deviceControllerDelegate queue:callbackQueue]; + + NSError * error; + __auto_type * payload = [MTRSetupPayload setupPayloadWithOnboardingPayload:payloadString error:&error]; + XCTAssertNotNil(payload); + XCTAssertNil(error); + + [sController setupCommissioningSessionWithPayload:payload newNodeID:nodeID error:&error]; + XCTAssertNil(error); + + [self waitForExpectations:@[ expectation ] timeout:kPairingTimeoutInSeconds]; + + return [MTRBaseDevice deviceWithNodeID:nodeID controller:sController]; +} + +- (void)initStack:(MTRTestCertificateIssuer *)certificateIssuer +{ + __auto_type * factory = [MTRDeviceControllerFactory sharedInstance]; + XCTAssertNotNil(factory); + + __auto_type * storage = [[MTRTestStorage alloc] init]; + + __auto_type * factoryParams = [[MTRDeviceControllerFactoryParams alloc] initWithStorage:storage]; + factoryParams.port = @(kLocalPort); + factoryParams.shouldStartServer = YES; + + BOOL ok = [factory startControllerFactory:factoryParams error:nil]; + XCTAssertTrue(ok); + + __auto_type * controllerOperationalKeys = [[MTRTestKeys alloc] init]; + XCTAssertNotNil(controllerOperationalKeys); + + __auto_type * controllerOperationalCert = + [certificateIssuer issueOperationalCertificateForNode:@(kControllerId) + operationalPublicKey:controllerOperationalKeys.publicKey]; + XCTAssertNotNil(controllerOperationalCert); + + __auto_type * params = [[MTRDeviceControllerStartupParams alloc] initWithIPK:certificateIssuer.rootKey.ipk + operationalKeypair:controllerOperationalKeys + operationalCertificate:controllerOperationalCert + intermediateCertificate:nil + rootCertificate:certificateIssuer.rootCertificate]; + XCTAssertNotNil(params); + + params.vendorID = @(kTestVendorId); + params.operationalCertificateIssuer = certificateIssuer; + params.operationalCertificateIssuerQueue = dispatch_get_main_queue(); + + MTRDeviceController * controller = [factory createControllerOnNewFabric:params error:nil]; + XCTAssertNotNil(controller); + + sController = controller; + + sConnectedDevice = [self commissionDeviceWithPayload:kOnboardingPayload nodeID:@(kDeviceId)]; +} + ++ (void)shutdownStack +{ + sNeedsStackShutdown = NO; + + MTRDeviceController * controller = sController; + [controller shutdown]; + XCTAssertFalse([controller isRunning]); + + [[MTRDeviceControllerFactory sharedInstance] stopControllerFactory]; +} + +- (void)test001_TestExpiredCertificates +{ + __auto_type * testKeys = [[MTRTestKeys alloc] init]; + XCTAssertNotNil(testKeys); + + __auto_type * certificateIssuer = [[MTRTestExpiredCertificateIssuer alloc] initWithRootKey:testKeys fabricID:@(1)]; + XCTAssertNotNil(certificateIssuer); + + [self initStack:certificateIssuer]; + + XCTestExpectation * toggleExpectation = [self expectationWithDescription:@"Toggle command executed"]; + + __auto_type * onOffCluster = [[MTRBaseClusterOnOff alloc] initWithDevice:sConnectedDevice + endpointID:@(1) + queue:dispatch_get_main_queue()]; + [onOffCluster toggleWithCompletion:^(NSError * _Nullable error) { + XCTAssertNil(error); + [toggleExpectation fulfill]; + }]; + [self waitForExpectations:@[ toggleExpectation ] timeout:kTimeoutInSeconds]; + + ResetCommissionee(sConnectedDevice, dispatch_get_main_queue(), self, kTimeoutInSeconds); + + [[self class] shutdownStack]; +} + +@end diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 2561d02229b67b..406bd64a9552f8 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -132,6 +132,7 @@ 511913FB28C100EF009235E9 /* MTRBaseSubscriptionCallback.mm in Sources */ = {isa = PBXBuildFile; fileRef = 511913F928C100EF009235E9 /* MTRBaseSubscriptionCallback.mm */; }; 511913FC28C100EF009235E9 /* MTRBaseSubscriptionCallback.h in Headers */ = {isa = PBXBuildFile; fileRef = 511913FA28C100EF009235E9 /* MTRBaseSubscriptionCallback.h */; }; 5129BCFD26A9EE3300122DDF /* MTRError.h in Headers */ = {isa = PBXBuildFile; fileRef = 5129BCFC26A9EE3300122DDF /* MTRError.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 51339B1F2A0DA64D00C798C1 /* MTRCertificateValidityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51339B1E2A0DA64D00C798C1 /* MTRCertificateValidityTests.m */; }; 5136661328067D550025EDAE /* MTRDeviceController_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 5136660F28067D540025EDAE /* MTRDeviceController_Internal.h */; }; 5136661428067D550025EDAE /* MTRDeviceControllerFactory.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5136661028067D540025EDAE /* MTRDeviceControllerFactory.mm */; }; 5136661528067D550025EDAE /* MTRDeviceControllerFactory_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 5136661128067D540025EDAE /* MTRDeviceControllerFactory_Internal.h */; }; @@ -418,6 +419,7 @@ 511913F928C100EF009235E9 /* MTRBaseSubscriptionCallback.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRBaseSubscriptionCallback.mm; sourceTree = ""; }; 511913FA28C100EF009235E9 /* MTRBaseSubscriptionCallback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRBaseSubscriptionCallback.h; sourceTree = ""; }; 5129BCFC26A9EE3300122DDF /* MTRError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRError.h; sourceTree = ""; }; + 51339B1E2A0DA64D00C798C1 /* MTRCertificateValidityTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRCertificateValidityTests.m; sourceTree = ""; }; 5136660F28067D540025EDAE /* MTRDeviceController_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDeviceController_Internal.h; sourceTree = ""; }; 5136661028067D540025EDAE /* MTRDeviceControllerFactory.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDeviceControllerFactory.mm; sourceTree = ""; }; 5136661128067D540025EDAE /* MTRDeviceControllerFactory_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDeviceControllerFactory_Internal.h; sourceTree = ""; }; @@ -1060,6 +1062,7 @@ 51742B4D29CB6B88009974FE /* MTRPairingTests.m */, 5142E39729D377F000A206F0 /* MTROTAProviderTests.m */, 51A2F1312A00402A00F03298 /* MTRDataValueParserTests.m */, + 51339B1E2A0DA64D00C798C1 /* MTRCertificateValidityTests.m */, B202529D2459E34F00F97062 /* Info.plist */, ); path = CHIPTests; @@ -1458,6 +1461,7 @@ 99C65E10267282F1003402F6 /* MTRControllerTests.m in Sources */, 1E5801C328941C050033A199 /* MTRTestOTAProvider.m in Sources */, 5A6FEC9D27B5E48900F25F42 /* MTRXPCProtocolTests.m in Sources */, + 51339B1F2A0DA64D00C798C1 /* MTRCertificateValidityTests.m in Sources */, 5173A47929C0E82300F67F48 /* MTRFabricInfoTests.m in Sources */, 3D0C484B29DA4FA0006D811F /* MTRErrorTests.m in Sources */, 51742B4A29CB5FC1009974FE /* MTRTestResetCommissioneeHelper.m in Sources */,