| /* |
| * |
| * 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 = 30; |
| 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]; |
| // Release no-longer-needed key before we do anything else. |
| CFRelease(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); |
| |
| 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 |