diff --git a/src/credentials/CHIPCert.cpp b/src/credentials/CHIPCert.cpp index 3d6af3db842e8e..1ae3551a166114 100644 --- a/src/credentials/CHIPCert.cpp +++ b/src/credentials/CHIPCert.cpp @@ -1142,7 +1142,7 @@ DLL_EXPORT CHIP_ERROR ChipEpochToASN1Time(uint32_t epochTime, chip::ASN1::ASN1Un // times, which in consuming code can create a conversion from CHIP epoch 0 seconds to 99991231235959Z // for NotBefore, which is not conventional. // - // If an original X509 certificate encloses a NotBefore time that this the CHIP Epoch itself, 2000-01-01 + // If an original X509 certificate encloses a NotBefore time that is the CHIP Epoch itself, 2000-01-01 // 00:00:00, the resultant X509 certificate in a conversion back from CHIP TLV format using this time // conversion method will instead enclose the NotBefore time 99991231235959Z, which will invalidiate the // TBS signature. Thus, certificates with this specific attribute are not usable with this code. diff --git a/src/credentials/GenerateChipX509Cert.cpp b/src/credentials/GenerateChipX509Cert.cpp index b51c49e0174f22..12aacab9bf87fb 100644 --- a/src/credentials/GenerateChipX509Cert.cpp +++ b/src/credentials/GenerateChipX509Cert.cpp @@ -330,7 +330,8 @@ CHIP_ERROR EncodeTBSCert(const X509CertRequestParams & requestParams, const Cryp bool isCA; VerifyOrReturnError(requestParams.SerialNumber >= 0, CHIP_ERROR_INVALID_ARGUMENT); - VerifyOrReturnError(requestParams.ValidityEnd >= requestParams.ValidityStart, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(requestParams.ValidityEnd == kNullCertTime || requestParams.ValidityEnd >= requestParams.ValidityStart, + CHIP_ERROR_INVALID_ARGUMENT); ReturnErrorOnFailure(requestParams.SubjectDN.GetCertType(certType)); isCA = (certType == kCertType_ICA || certType == kCertType_Root); diff --git a/src/darwin/Framework/CHIP/MTRCertificates.h b/src/darwin/Framework/CHIP/MTRCertificates.h index 268ee3e223ac30..1c333697436db4 100644 --- a/src/darwin/Framework/CHIP/MTRCertificates.h +++ b/src/darwin/Framework/CHIP/MTRCertificates.h @@ -44,14 +44,27 @@ NS_ASSUME_NONNULL_BEGIN * If fabricID is not nil, it will be included in the subject DN of the * certificate. In this case it must be a valid Matter fabric id. * + * validityPeriod specifies when the certificate will be valid. + * * On failure returns nil and if "error" is not null sets *error to the relevant * error. */ + (MTRCertificateDERBytes _Nullable)createRootCertificate:(id)keypair issuerID:(NSNumber * _Nullable)issuerID fabricID:(NSNumber * _Nullable)fabricID + validityPeriod:(NSDateInterval *)validityPeriod error:(NSError * __autoreleasing _Nullable * _Nullable)error - API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)); + MTR_NEWLY_AVAILABLE; + +/** + * As above, but defaults to a 10-year validity period starting now. + */ ++ (MTRCertificateDERBytes _Nullable)createRootCertificate:(id)keypair + issuerID:(NSNumber * _Nullable)issuerID + fabricID:(NSNumber * _Nullable)fabricID + error:(NSError * __autoreleasing _Nullable * _Nullable)error + API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)) + MTR_NEWLY_DEPRECATED("Please use the version that specifies an explicit validity period"); /** * Create an intermediate X.509 DER encoded certificate that has the @@ -66,6 +79,8 @@ NS_ASSUME_NONNULL_BEGIN * If fabricID is not nil, it will be included in the subject DN of the * certificate. In this case it must be a valid Matter fabric id. * + * validityPeriod specifies when the certificate will be valid. + * * On failure returns nil and if "error" is not null sets *error to the relevant * error. */ @@ -74,8 +89,21 @@ NS_ASSUME_NONNULL_BEGIN intermediatePublicKey:(SecKeyRef)intermediatePublicKey issuerID:(NSNumber * _Nullable)issuerID fabricID:(NSNumber * _Nullable)fabricID + validityPeriod:(NSDateInterval *)validityPeriod error:(NSError * __autoreleasing _Nullable * _Nullable)error - API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)); + MTR_NEWLY_AVAILABLE; + +/** + * As above, but defaults to a 10-year validity period starting now. + */ ++ (MTRCertificateDERBytes _Nullable)createIntermediateCertificate:(id)rootKeypair + rootCertificate:(MTRCertificateDERBytes)rootCertificate + intermediatePublicKey:(SecKeyRef)intermediatePublicKey + issuerID:(NSNumber * _Nullable)issuerID + fabricID:(NSNumber * _Nullable)fabricID + error:(NSError * __autoreleasing _Nullable * _Nullable)error + API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)) + MTR_NEWLY_DEPRECATED("Please use the version that specifies an explicit validity period"); /** * Create an X.509 DER encoded certificate that has the @@ -95,6 +123,8 @@ NS_ASSUME_NONNULL_BEGIN * 3 numbers, which are expected to be 32-bit unsigned Case Authenticated Tag * values. * + * validityPeriod specifies when the certificate will be valid. + * * On failure returns nil and if "error" is not null sets *error to the relevant * error. */ @@ -104,8 +134,22 @@ NS_ASSUME_NONNULL_BEGIN fabricID:(NSNumber *)fabricID nodeID:(NSNumber *)nodeID caseAuthenticatedTags:(NSSet * _Nullable)caseAuthenticatedTags + validityPeriod:(NSDateInterval *)validityPeriod error:(NSError * __autoreleasing _Nullable * _Nullable)error - API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)); + MTR_NEWLY_AVAILABLE; + +/** + * As above, but defaults to a 10-year validity period starting now. + */ ++ (MTRCertificateDERBytes _Nullable)createOperationalCertificate:(id)signingKeypair + signingCertificate:(MTRCertificateDERBytes)signingCertificate + operationalPublicKey:(SecKeyRef)operationalPublicKey + fabricID:(NSNumber *)fabricID + nodeID:(NSNumber *)nodeID + caseAuthenticatedTags:(NSSet * _Nullable)caseAuthenticatedTags + error:(NSError * __autoreleasing _Nullable * _Nullable)error + API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)) + MTR_NEWLY_DEPRECATED("Please use the version that specifies an explicit validity period"); /** * Check whether the given keypair's public key matches the given certificate's diff --git a/src/darwin/Framework/CHIP/MTRCertificates.mm b/src/darwin/Framework/CHIP/MTRCertificates.mm index a8c28f32ea95ec..8cdb060f9ac9d0 100644 --- a/src/darwin/Framework/CHIP/MTRCertificates.mm +++ b/src/darwin/Framework/CHIP/MTRCertificates.mm @@ -39,11 +39,13 @@ + (void)initialize + (MTRCertificateDERBytes _Nullable)createRootCertificate:(id)keypair issuerID:(NSNumber * _Nullable)issuerID fabricID:(NSNumber * _Nullable)fabricID + validityPeriod:(NSDateInterval *)validityPeriod error:(NSError * __autoreleasing *)error { MTR_LOG_DEFAULT("Generating root certificate"); NSData * rootCert = nil; - CHIP_ERROR err = MTROperationalCredentialsDelegate::GenerateRootCertificate(keypair, issuerID, fabricID, &rootCert); + CHIP_ERROR err + = MTROperationalCredentialsDelegate::GenerateRootCertificate(keypair, issuerID, fabricID, validityPeriod, &rootCert); if (error) { *error = [MTRError errorForCHIPErrorCode:err]; } @@ -55,17 +57,28 @@ + (MTRCertificateDERBytes _Nullable)createRootCertificate:(id)keypai return rootCert; } ++ (MTRCertificateDERBytes _Nullable)createRootCertificate:(id)keypair + issuerID:(NSNumber * _Nullable)issuerID + fabricID:(NSNumber * _Nullable)fabricID + error:(NSError * __autoreleasing *)error +{ + auto * validityPeriod = [[NSDateInterval alloc] initWithStartDate:[NSDate now] + duration:MTROperationalCredentialsDelegate::kCertificateValiditySecs]; + return [self createRootCertificate:keypair issuerID:issuerID fabricID:fabricID validityPeriod:validityPeriod error:error]; +} + + (MTRCertificateDERBytes _Nullable)createIntermediateCertificate:(id)rootKeypair rootCertificate:(MTRCertificateDERBytes)rootCertificate intermediatePublicKey:(SecKeyRef)intermediatePublicKey issuerID:(NSNumber * _Nullable)issuerID fabricID:(NSNumber * _Nullable)fabricID + validityPeriod:(NSDateInterval *)validityPeriod error:(NSError * __autoreleasing *)error { MTR_LOG_DEFAULT("Generating intermediate certificate"); NSData * intermediate = nil; CHIP_ERROR err = MTROperationalCredentialsDelegate::GenerateIntermediateCertificate( - rootKeypair, rootCertificate, intermediatePublicKey, issuerID, fabricID, &intermediate); + rootKeypair, rootCertificate, intermediatePublicKey, issuerID, fabricID, validityPeriod, &intermediate); if (error) { *error = [MTRError errorForCHIPErrorCode:err]; } @@ -77,18 +90,37 @@ + (MTRCertificateDERBytes _Nullable)createIntermediateCertificate:(id)rootKeypair + rootCertificate:(MTRCertificateDERBytes)rootCertificate + intermediatePublicKey:(SecKeyRef)intermediatePublicKey + issuerID:(NSNumber * _Nullable)issuerID + fabricID:(NSNumber * _Nullable)fabricID + error:(NSError * __autoreleasing *)error +{ + auto * validityPeriod = [[NSDateInterval alloc] initWithStartDate:[NSDate now] + duration:MTROperationalCredentialsDelegate::kCertificateValiditySecs]; + return [self createIntermediateCertificate:rootKeypair + rootCertificate:rootCertificate + intermediatePublicKey:intermediatePublicKey + issuerID:issuerID + fabricID:fabricID + validityPeriod:validityPeriod + error:error]; +} + + (MTRCertificateDERBytes _Nullable)createOperationalCertificate:(id)signingKeypair signingCertificate:(MTRCertificateDERBytes)signingCertificate operationalPublicKey:(SecKeyRef)operationalPublicKey fabricID:(NSNumber *)fabricID nodeID:(NSNumber *)nodeID caseAuthenticatedTags:(NSSet * _Nullable)caseAuthenticatedTags + validityPeriod:(NSDateInterval *)validityPeriod error:(NSError * __autoreleasing _Nullable * _Nullable)error { MTR_LOG_DEFAULT("Generating operational certificate"); NSData * opcert = nil; CHIP_ERROR err = MTROperationalCredentialsDelegate::GenerateOperationalCertificate( - signingKeypair, signingCertificate, operationalPublicKey, fabricID, nodeID, caseAuthenticatedTags, &opcert); + signingKeypair, signingCertificate, operationalPublicKey, fabricID, nodeID, caseAuthenticatedTags, validityPeriod, &opcert); if (error) { *error = [MTRError errorForCHIPErrorCode:err]; } @@ -100,6 +132,26 @@ + (MTRCertificateDERBytes _Nullable)createOperationalCertificate:(id return opcert; } ++ (MTRCertificateDERBytes _Nullable)createOperationalCertificate:(id)signingKeypair + signingCertificate:(MTRCertificateDERBytes)signingCertificate + operationalPublicKey:(SecKeyRef)operationalPublicKey + fabricID:(NSNumber *)fabricID + nodeID:(NSNumber *)nodeID + caseAuthenticatedTags:(NSSet * _Nullable)caseAuthenticatedTags + error:(NSError * __autoreleasing _Nullable * _Nullable)error +{ + auto * validityPeriod = [[NSDateInterval alloc] initWithStartDate:[NSDate now] + duration:MTROperationalCredentialsDelegate::kCertificateValiditySecs]; + return [self createOperationalCertificate:signingKeypair + signingCertificate:signingCertificate + operationalPublicKey:operationalPublicKey + fabricID:fabricID + nodeID:nodeID + caseAuthenticatedTags:caseAuthenticatedTags + validityPeriod:validityPeriod + error:error]; +} + + (BOOL)keypair:(id)keypair matchesCertificate:(NSData *)certificate { P256PublicKey keypairPubKey; diff --git a/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.h b/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.h index a824971cb8abb3..4e7ed74cbe8cae 100644 --- a/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.h +++ b/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.h @@ -92,7 +92,7 @@ class MTROperationalCredentialsDelegate : public chip::Controller::OperationalCr // // The outparam must not be null and is set to nil on errors. static CHIP_ERROR GenerateRootCertificate(id keypair, NSNumber * _Nullable issuerId, NSNumber * _Nullable fabricId, - NSData * _Nullable __autoreleasing * _Nonnull rootCert); + NSDateInterval * validityPeriod, NSData * _Nullable __autoreleasing * _Nonnull rootCert); // Generate an intermediate DER-encoded X.509 certificate for the given root // and intermediate public key. If issuerId is provided, it is used; @@ -102,21 +102,26 @@ class MTROperationalCredentialsDelegate : public chip::Controller::OperationalCr // The outparam must not be null and is set to nil on errors. static CHIP_ERROR GenerateIntermediateCertificate(id rootKeypair, NSData * rootCertificate, SecKeyRef intermediatePublicKey, NSNumber * _Nullable issuerId, NSNumber * _Nullable fabricId, - NSData * _Nullable __autoreleasing * _Nonnull intermediateCert); + NSDateInterval * validityPeriod, NSData * _Nullable __autoreleasing * _Nonnull intermediateCert); // Generate an operational DER-encoded X.509 certificate for the given // signing certificate and operational public key, using the given fabric // id, node id, and CATs. static CHIP_ERROR GenerateOperationalCertificate(id signingKeypair, NSData * signingCertificate, SecKeyRef operationalPublicKey, NSNumber * fabricId, NSNumber * nodeId, NSSet * _Nullable caseAuthenticatedTags, - NSData * _Nullable __autoreleasing * _Nonnull operationalCert); + NSDateInterval * validityPeriod, NSData * _Nullable __autoreleasing * _Nonnull operationalCert); + + // 10 years. + static const uint32_t kCertificateValiditySecs = 10 * 365 * 24 * 60 * 60; private: - static bool ToChipEpochTime(uint32_t offset, uint32_t & epoch); + // notAfter times can represent "forever". + static bool ToChipNotAfterEpochTime(NSDate * date, uint32_t & epoch); + static bool ToChipEpochTime(NSDate * date, uint32_t & epoch); static CHIP_ERROR GenerateNOC(chip::Crypto::P256Keypair & signingKeypair, NSData * signingCertificate, chip::NodeId nodeId, chip::FabricId fabricId, const chip::CATValues & cats, const chip::Crypto::P256PublicKey & pubkey, - chip::MutableByteSpan & noc); + NSDateInterval * validityPeriod, chip::MutableByteSpan & noc); // Called asynchronously in response to the MTROperationalCertificateIssuer // calling the completion we passed it when asking it to generate a NOC @@ -135,8 +140,6 @@ class MTROperationalCredentialsDelegate : public chip::Controller::OperationalCr chip::Crypto::IdentityProtectionKey mIPK; - static const uint32_t kCertificateValiditySecs = 365 * 24 * 60 * 60; - MTRPersistentStorageDelegateBridge * mStorage; chip::NodeId mDeviceBeingPaired = chip::kUndefinedNodeId; diff --git a/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.mm b/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.mm index b14ac062784105..bbae67a39d5a62 100644 --- a/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.mm +++ b/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.mm @@ -89,21 +89,22 @@ return CHIP_ERROR_INCORRECT_STATE; } - return GenerateNOC( - *mIssuerKey, (mIntermediateCert != nil) ? mIntermediateCert : mRootCert, nodeId, fabricId, cats, pubkey, noc); + auto * validityPeriod = [[NSDateInterval alloc] initWithStartDate:[NSDate now] duration:kCertificateValiditySecs]; + return GenerateNOC(*mIssuerKey, (mIntermediateCert != nil) ? mIntermediateCert : mRootCert, nodeId, fabricId, cats, pubkey, + validityPeriod, noc); } CHIP_ERROR MTROperationalCredentialsDelegate::GenerateNOC(P256Keypair & signingKeypair, NSData * signingCertificate, NodeId nodeId, - FabricId fabricId, const CATValues & cats, const P256PublicKey & pubkey, MutableByteSpan & noc) + FabricId fabricId, const CATValues & cats, const P256PublicKey & pubkey, NSDateInterval * validityPeriod, MutableByteSpan & noc) { uint32_t validityStart, validityEnd; - if (!ToChipEpochTime(0, validityStart)) { + if (!ToChipEpochTime(validityPeriod.startDate, validityStart)) { MTR_LOG_ERROR("Failed in computing certificate validity start date"); return CHIP_ERROR_INTERNAL; } - if (!ToChipEpochTime(kCertificateValiditySecs, validityEnd)) { + if (!ToChipNotAfterEpochTime(validityPeriod.endDate, validityEnd)) { MTR_LOG_ERROR("Failed in computing certificate validity end date"); return CHIP_ERROR_INTERNAL; } @@ -310,12 +311,28 @@ return AsByteSpan(mIntermediateCert); } -bool MTROperationalCredentialsDelegate::ToChipEpochTime(uint32_t offset, uint32_t & epoch) +bool MTROperationalCredentialsDelegate::ToChipNotAfterEpochTime(NSDate * date, uint32_t & epoch) +{ + if ([date isEqualToDate:[NSDate distantFuture]]) { + epoch = kNullCertTime; + return true; + } + + return ToChipEpochTime(date, epoch); +} + +bool MTROperationalCredentialsDelegate::ToChipEpochTime(NSDate * date, uint32_t & epoch) { - NSDate * date = [NSDate dateWithTimeIntervalSinceNow:offset]; NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; NSDateComponents * components = [calendar componentsInTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0] fromDate:date]; + if (!CanCastTo(components.year)) { + MTR_LOG_ERROR( + "Year %lu is too large for Matter epoch time. Please use [NSDate distantFuture] to represent \"never expires\".", + static_cast(components.year)); + return false; + } + uint16_t year = static_cast([components year]); uint8_t month = static_cast([components month]); uint8_t day = static_cast([components day]); @@ -337,7 +354,7 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId) } // anonymous namespace CHIP_ERROR MTROperationalCredentialsDelegate::GenerateRootCertificate(id keypair, NSNumber * _Nullable issuerId, - NSNumber * _Nullable fabricId, NSData * _Nullable __autoreleasing * _Nonnull rootCert) + NSNumber * _Nullable fabricId, NSDateInterval * validityPeriod, NSData * _Nullable __autoreleasing * _Nonnull rootCert) { *rootCert = nil; MTRP256KeypairBridge keypairBridge; @@ -354,12 +371,12 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId) uint32_t validityStart, validityEnd; - if (!ToChipEpochTime(0, validityStart)) { + if (!ToChipEpochTime(validityPeriod.startDate, validityStart)) { MTR_LOG_ERROR("Failed in computing certificate validity start date"); return CHIP_ERROR_INTERNAL; } - if (!ToChipEpochTime(kCertificateValiditySecs, validityEnd)) { + if (!ToChipNotAfterEpochTime(validityPeriod.endDate, validityEnd)) { MTR_LOG_ERROR("Failed in computing certificate validity end date"); return CHIP_ERROR_INTERNAL; } @@ -373,7 +390,7 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId) } CHIP_ERROR MTROperationalCredentialsDelegate::GenerateIntermediateCertificate(id rootKeypair, NSData * rootCertificate, - SecKeyRef intermediatePublicKey, NSNumber * _Nullable issuerId, NSNumber * _Nullable fabricId, + SecKeyRef intermediatePublicKey, NSNumber * _Nullable issuerId, NSNumber * _Nullable fabricId, NSDateInterval * validityPeriod, NSData * _Nullable __autoreleasing * _Nonnull intermediateCert) { *intermediateCert = nil; @@ -404,12 +421,12 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId) uint32_t validityStart, validityEnd; - if (!ToChipEpochTime(0, validityStart)) { + if (!ToChipEpochTime(validityPeriod.startDate, validityStart)) { MTR_LOG_ERROR("Failed in computing certificate validity start date"); return CHIP_ERROR_INTERNAL; } - if (!ToChipEpochTime(kCertificateValiditySecs, validityEnd)) { + if (!ToChipNotAfterEpochTime(validityPeriod.endDate, validityEnd)) { MTR_LOG_ERROR("Failed in computing certificate validity end date"); return CHIP_ERROR_INTERNAL; } @@ -424,7 +441,8 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId) CHIP_ERROR MTROperationalCredentialsDelegate::GenerateOperationalCertificate(id signingKeypair, NSData * signingCertificate, SecKeyRef operationalPublicKey, NSNumber * fabricId, NSNumber * nodeId, - NSSet * _Nullable caseAuthenticatedTags, NSData * _Nullable __autoreleasing * _Nonnull operationalCert) + NSSet * _Nullable caseAuthenticatedTags, NSDateInterval * validityPeriod, + NSData * _Nullable __autoreleasing * _Nonnull operationalCert) { *operationalCert = nil; @@ -459,7 +477,7 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId) uint8_t nocBuffer[Controller::kMaxCHIPDERCertLength]; MutableByteSpan noc(nocBuffer); - ReturnErrorOnFailure(GenerateNOC(keypairBridge, signingCertificate, node, fabric, cats, pubKey, noc)); + ReturnErrorOnFailure(GenerateNOC(keypairBridge, signingCertificate, node, fabric, cats, pubKey, validityPeriod, noc)); *operationalCert = AsData(noc); return CHIP_NO_ERROR; diff --git a/src/darwin/Framework/CHIPTests/MTRCertificateTests.m b/src/darwin/Framework/CHIPTests/MTRCertificateTests.m index 449343066b9d1f..307f427a62aef9 100644 --- a/src/darwin/Framework/CHIPTests/MTRCertificateTests.m +++ b/src/darwin/Framework/CHIPTests/MTRCertificateTests.m @@ -45,6 +45,72 @@ - (void)testGenerateRootCert XCTAssertEqualObjects(rootCert, derCert); } +- (void)testGenerateRootCertWithValidityPeriod +{ + __auto_type * testKeys = [[MTRTestKeys alloc] init]; + XCTAssertNotNil(testKeys); + + __auto_type * startDate = [NSDate dateWithTimeIntervalSinceNow:100]; + // Round down to the nearest second, since the certificate bits will do that + // when it computes validity dates. + NSTimeInterval seconds = floor([startDate timeIntervalSinceReferenceDate]); + startDate = [NSDate dateWithTimeIntervalSinceReferenceDate:seconds]; + __auto_type * validityPeriod = [[NSDateInterval alloc] initWithStartDate:startDate duration:200]; + + __auto_type * rootCert = [MTRCertificates createRootCertificate:testKeys + issuerID:nil + fabricID:nil + validityPeriod:validityPeriod + error:nil]; + XCTAssertNotNil(rootCert); + + // Test round-trip through TLV format. + __auto_type * tlvCert = [MTRCertificates convertX509Certificate:rootCert]; + XCTAssertNotNil(tlvCert); + + __auto_type * derCert = [MTRCertificates convertMatterCertificate:tlvCert]; + XCTAssertNotNil(derCert); + + XCTAssertEqualObjects(rootCert, derCert); + + __auto_type * certInfo = [[MTRCertificateInfo alloc] initWithTLVBytes:tlvCert]; + XCTAssertEqualObjects(validityPeriod.startDate, certInfo.notBefore); + XCTAssertEqualObjects(validityPeriod.endDate, certInfo.notAfter); +} + +- (void)testGenerateRootCertWithInfiniteValidity +{ + __auto_type * testKeys = [[MTRTestKeys alloc] init]; + XCTAssertNotNil(testKeys); + + __auto_type * startDate = [NSDate dateWithTimeIntervalSinceNow:100]; + // Round down to the nearest second, since the certificate bits will do that + // when it computes validity dates. + NSTimeInterval seconds = floor([startDate timeIntervalSinceReferenceDate]); + startDate = [NSDate dateWithTimeIntervalSinceReferenceDate:seconds]; + __auto_type * validityPeriod = [[NSDateInterval alloc] initWithStartDate:startDate endDate:[NSDate distantFuture]]; + + __auto_type * rootCert = [MTRCertificates createRootCertificate:testKeys + issuerID:nil + fabricID:nil + validityPeriod:validityPeriod + error:nil]; + XCTAssertNotNil(rootCert); + + // Test round-trip through TLV format. + __auto_type * tlvCert = [MTRCertificates convertX509Certificate:rootCert]; + XCTAssertNotNil(tlvCert); + + __auto_type * derCert = [MTRCertificates convertMatterCertificate:tlvCert]; + XCTAssertNotNil(derCert); + + XCTAssertEqualObjects(rootCert, derCert); + + __auto_type * certInfo = [[MTRCertificateInfo alloc] initWithTLVBytes:tlvCert]; + XCTAssertEqualObjects(validityPeriod.startDate, certInfo.notBefore); + XCTAssertEqualObjects(validityPeriod.endDate, certInfo.notAfter); +} + - (void)testGenerateIntermediateCert { __auto_type * rootKeys = [[MTRTestKeys alloc] init]; @@ -74,6 +140,88 @@ - (void)testGenerateIntermediateCert XCTAssertEqualObjects(intermediateCert, derCert); } +- (void)testGenerateIntermediateCertWithValidityPeriod +{ + __auto_type * rootKeys = [[MTRTestKeys alloc] init]; + XCTAssertNotNil(rootKeys); + + __auto_type * rootCert = [MTRCertificates createRootCertificate:rootKeys issuerID:nil fabricID:nil error:nil]; + XCTAssertNotNil(rootCert); + + __auto_type * intermediateKeys = [[MTRTestKeys alloc] init]; + XCTAssertNotNil(intermediateKeys); + + __auto_type * startDate = [NSDate dateWithTimeIntervalSinceNow:300]; + // Round down to the nearest second, since the certificate bits will do that + // when it computes validity dates. + NSTimeInterval seconds = floor([startDate timeIntervalSinceReferenceDate]); + startDate = [NSDate dateWithTimeIntervalSinceReferenceDate:seconds]; + __auto_type * validityPeriod = [[NSDateInterval alloc] initWithStartDate:startDate duration:400]; + + __auto_type * intermediateCert = [MTRCertificates createIntermediateCertificate:rootKeys + rootCertificate:rootCert + intermediatePublicKey:intermediateKeys.publicKey + issuerID:nil + fabricID:nil + validityPeriod:validityPeriod + error:nil]; + XCTAssertNotNil(intermediateCert); + + // Test round-trip through TLV format. + __auto_type * tlvCert = [MTRCertificates convertX509Certificate:intermediateCert]; + XCTAssertNotNil(tlvCert); + + __auto_type * derCert = [MTRCertificates convertMatterCertificate:tlvCert]; + XCTAssertNotNil(derCert); + + XCTAssertEqualObjects(intermediateCert, derCert); + + __auto_type * certInfo = [[MTRCertificateInfo alloc] initWithTLVBytes:tlvCert]; + XCTAssertEqualObjects(validityPeriod.startDate, certInfo.notBefore); + XCTAssertEqualObjects(validityPeriod.endDate, certInfo.notAfter); +} + +- (void)testGenerateIntermediateCertWithInfiniteValidity +{ + __auto_type * rootKeys = [[MTRTestKeys alloc] init]; + XCTAssertNotNil(rootKeys); + + __auto_type * rootCert = [MTRCertificates createRootCertificate:rootKeys issuerID:nil fabricID:nil error:nil]; + XCTAssertNotNil(rootCert); + + __auto_type * intermediateKeys = [[MTRTestKeys alloc] init]; + XCTAssertNotNil(intermediateKeys); + + __auto_type * startDate = [NSDate dateWithTimeIntervalSinceNow:300]; + // Round down to the nearest second, since the certificate bits will do that + // when it computes validity dates. + NSTimeInterval seconds = floor([startDate timeIntervalSinceReferenceDate]); + startDate = [NSDate dateWithTimeIntervalSinceReferenceDate:seconds]; + __auto_type * validityPeriod = [[NSDateInterval alloc] initWithStartDate:startDate endDate:[NSDate distantFuture]]; + + __auto_type * intermediateCert = [MTRCertificates createIntermediateCertificate:rootKeys + rootCertificate:rootCert + intermediatePublicKey:intermediateKeys.publicKey + issuerID:nil + fabricID:nil + validityPeriod:validityPeriod + error:nil]; + XCTAssertNotNil(intermediateCert); + + // Test round-trip through TLV format. + __auto_type * tlvCert = [MTRCertificates convertX509Certificate:intermediateCert]; + XCTAssertNotNil(tlvCert); + + __auto_type * derCert = [MTRCertificates convertMatterCertificate:tlvCert]; + XCTAssertNotNil(derCert); + + XCTAssertEqualObjects(intermediateCert, derCert); + + __auto_type * certInfo = [[MTRCertificateInfo alloc] initWithTLVBytes:tlvCert]; + XCTAssertEqualObjects(validityPeriod.startDate, certInfo.notBefore); + XCTAssertEqualObjects(validityPeriod.endDate, certInfo.notAfter); +} + - (void)testGenerateOperationalCertNoIntermediate { __auto_type * rootKeys = [[MTRTestKeys alloc] init]; @@ -110,6 +258,102 @@ - (void)testGenerateOperationalCertNoIntermediate XCTAssertEqualObjects(operationalCert, derCert); } +- (void)testGenerateOperationalCertNoIntermediateWithValidityPeriod +{ + __auto_type * rootKeys = [[MTRTestKeys alloc] init]; + XCTAssertNotNil(rootKeys); + + __auto_type * rootCert = [MTRCertificates createRootCertificate:rootKeys issuerID:nil fabricID:nil error:nil]; + XCTAssertNotNil(rootCert); + + __auto_type * operationalKeys = [[MTRTestKeys alloc] init]; + XCTAssertNotNil(operationalKeys); + + __auto_type * cats = [[NSMutableSet alloc] initWithCapacity:3]; + // High bits are identifier, low bits are version. + [cats addObject:@0x00010001]; + [cats addObject:@0x00020001]; + [cats addObject:@0x0003FFFF]; + + __auto_type * startDate = [NSDate dateWithTimeIntervalSinceNow:1000]; + // Round down to the nearest second, since the certificate bits will do that + // when it computes validity dates. + NSTimeInterval seconds = floor([startDate timeIntervalSinceReferenceDate]); + startDate = [NSDate dateWithTimeIntervalSinceReferenceDate:seconds]; + __auto_type * validityPeriod = [[NSDateInterval alloc] initWithStartDate:startDate duration:500]; + + __auto_type * operationalCert = [MTRCertificates createOperationalCertificate:rootKeys + signingCertificate:rootCert + operationalPublicKey:operationalKeys.publicKey + fabricID:@1 + nodeID:@1 + caseAuthenticatedTags:cats + validityPeriod:validityPeriod + error:nil]; + XCTAssertNotNil(operationalCert); + + // Test round-trip through TLV format. + __auto_type * tlvCert = [MTRCertificates convertX509Certificate:operationalCert]; + XCTAssertNotNil(tlvCert); + + __auto_type * derCert = [MTRCertificates convertMatterCertificate:tlvCert]; + XCTAssertNotNil(derCert); + + XCTAssertEqualObjects(operationalCert, derCert); + + __auto_type * certInfo = [[MTRCertificateInfo alloc] initWithTLVBytes:tlvCert]; + XCTAssertEqualObjects(validityPeriod.startDate, certInfo.notBefore); + XCTAssertEqualObjects(validityPeriod.endDate, certInfo.notAfter); +} + +- (void)testGenerateOperationalCertNoIntermediateWithInfiniteValidity +{ + __auto_type * rootKeys = [[MTRTestKeys alloc] init]; + XCTAssertNotNil(rootKeys); + + __auto_type * rootCert = [MTRCertificates createRootCertificate:rootKeys issuerID:nil fabricID:nil error:nil]; + XCTAssertNotNil(rootCert); + + __auto_type * operationalKeys = [[MTRTestKeys alloc] init]; + XCTAssertNotNil(operationalKeys); + + __auto_type * cats = [[NSMutableSet alloc] initWithCapacity:3]; + // High bits are identifier, low bits are version. + [cats addObject:@0x00010001]; + [cats addObject:@0x00020001]; + [cats addObject:@0x0003FFFF]; + + __auto_type * startDate = [NSDate dateWithTimeIntervalSinceNow:1000]; + // Round down to the nearest second, since the certificate bits will do that + // when it computes validity dates. + NSTimeInterval seconds = floor([startDate timeIntervalSinceReferenceDate]); + startDate = [NSDate dateWithTimeIntervalSinceReferenceDate:seconds]; + __auto_type * validityPeriod = [[NSDateInterval alloc] initWithStartDate:startDate endDate:[NSDate distantFuture]]; + + __auto_type * operationalCert = [MTRCertificates createOperationalCertificate:rootKeys + signingCertificate:rootCert + operationalPublicKey:operationalKeys.publicKey + fabricID:@1 + nodeID:@1 + caseAuthenticatedTags:cats + validityPeriod:validityPeriod + error:nil]; + XCTAssertNotNil(operationalCert); + + // Test round-trip through TLV format. + __auto_type * tlvCert = [MTRCertificates convertX509Certificate:operationalCert]; + XCTAssertNotNil(tlvCert); + + __auto_type * derCert = [MTRCertificates convertMatterCertificate:tlvCert]; + XCTAssertNotNil(derCert); + + XCTAssertEqualObjects(operationalCert, derCert); + + __auto_type * certInfo = [[MTRCertificateInfo alloc] initWithTLVBytes:tlvCert]; + XCTAssertEqualObjects(validityPeriod.startDate, certInfo.notBefore); + XCTAssertEqualObjects(validityPeriod.endDate, certInfo.notAfter); +} + - (void)testGenerateOperationalCertWithIntermediate { __auto_type * rootKeys = [[MTRTestKeys alloc] init];