Skip to content

Commit

Permalink
Stop validating certificate lifetimes in Matter.framework. (#26546)
Browse files Browse the repository at this point in the history
  • Loading branch information
bzbarsky-apple authored and pull[bot] committed Dec 22, 2023
1 parent dfed86a commit 1067880
Show file tree
Hide file tree
Showing 3 changed files with 340 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -150,6 +152,11 @@ - (instancetype)init
return nil;
}

_certificateValidityPolicy = new Credentials::IgnoreCertificateValidityPeriodPolicy();
if ([self checkForInitError:(_certificateValidityPolicy != nil) logMsg:kErrorCertificateValidityPolicyInit]) {
return nil;
}

return self;
}

Expand Down Expand Up @@ -204,6 +211,11 @@ - (void)cleanupInitObjects
delete _sessionKeystore;
_sessionKeystore = nullptr;
}

if (_certificateValidityPolicy) {
delete _certificateValidityPolicy;
_certificateValidityPolicy = nullptr;
}
}

- (void)cleanupStartupObjects
Expand Down Expand Up @@ -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);
Expand Down
323 changes: 323 additions & 0 deletions src/darwin/Framework/CHIPTests/MTRCertificateValidityTests.m
Original file line number Diff line number Diff line change
@@ -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 <Matter/Matter.h>

#import "MTRErrorTestUtils.h"
#import "MTRTestKeys.h"
#import "MTRTestResetCommissioneeHelper.h"
#import "MTRTestStorage.h"

// system dependencies
#import <XCTest/XCTest.h>

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 <MTRDeviceControllerDelegate>
@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 <MTROperationalCertificateIssuer>

@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
Loading

0 comments on commit 1067880

Please sign in to comment.