Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop validating certificate lifetimes in Matter.framework. #26546

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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