From 65cfdb4394501a8d6783b063d12c8019ce3d934e Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Tue, 17 May 2022 12:03:59 -0400 Subject: [PATCH] Add Darwin framework API for creating operational certificates. Will be useful for writing tests, if nothing else. --- .../CHIP/CHIPOperationalCredentialsDelegate.h | 11 ++ .../CHIPOperationalCredentialsDelegate.mm | 66 ++++++++- src/darwin/Framework/CHIP/MTRCertificates.h | 33 ++++- src/darwin/Framework/CHIP/MTRCertificates.mm | 26 ++++ .../CHIPTests/MatterCertificateTests.m | 136 ++++++++++++++++++ 5 files changed, 264 insertions(+), 8 deletions(-) diff --git a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h index b353fd6830ec84..3912fcff798006 100644 --- a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h +++ b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h @@ -84,9 +84,20 @@ class CHIPOperationalCredentialsDelegate : public chip::Controller::OperationalC SecKeyRef intermediatePublicKey, NSNumber * _Nullable issuerId, NSNumber * _Nullable fabricId, 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, + NSArray * _Nullable caseAuthenticatedTags, NSData * _Nullable __autoreleasing * _Nonnull operationalCert); + private: static bool ToChipEpochTime(uint32_t offset, 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); + ChipP256KeypairPtr mIssuerKey; chip::Crypto::AesCcm128Key mIPK; diff --git a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm index ff284ab24815e5..163449a365b717 100644 --- a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm +++ b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm @@ -76,6 +76,13 @@ CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateNOC( NodeId nodeId, FabricId fabricId, const chip::CATValues & cats, const Crypto::P256PublicKey & pubkey, MutableByteSpan & noc) +{ + return GenerateNOC( + *mIssuerKey, (mIntermediateCert != nil) ? mIntermediateCert : mRootCert, nodeId, fabricId, cats, pubkey, noc); +} + +CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateNOC(P256Keypair & signingKeypair, NSData * signingCertificate, NodeId nodeId, + FabricId fabricId, const CATValues & cats, const P256PublicKey & pubkey, MutableByteSpan & noc) { uint32_t validityStart, validityEnd; @@ -90,8 +97,7 @@ } ChipDN signerSubject; - NSData * signer = (mIntermediateCert != nil) ? mIntermediateCert : mRootCert; - ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(AsByteSpan(signer), signerSubject)); + ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(AsByteSpan(signingCertificate), signerSubject)); ChipDN noc_dn; ReturnErrorOnFailure(noc_dn.AddAttribute_MatterFabricId(fabricId)); @@ -99,7 +105,7 @@ ReturnErrorOnFailure(noc_dn.AddCATs(cats)); X509CertRequestParams noc_request = { 1, validityStart, validityEnd, noc_dn, signerSubject }; - return NewNodeOperationalX509Cert(noc_request, pubkey, *mIssuerKey, noc); + return NewNodeOperationalX509Cert(noc_request, pubkey, signingKeypair, noc); } CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateNOCChain(const chip::ByteSpan & csrElements, const chip::ByteSpan & csrNonce, @@ -199,7 +205,9 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId) ReturnErrorOnFailure(rcac_dn.AddAttribute_MatterRCACId(GetIssuerId(issuerId))); if (fabricId != nil) { - ReturnErrorOnFailure(rcac_dn.AddAttribute_MatterFabricId([fabricId unsignedLongLongValue])); + FabricId fabric = [fabricId unsignedLongLongValue]; + VerifyOrReturnError(fabric != kUndefinedFabricId, CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(rcac_dn.AddAttribute_MatterFabricId(fabric)); } uint32_t validityStart, validityEnd; @@ -228,7 +236,7 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId) { *intermediateCert = nil; - // Verify that the provided certificate public key matches the root keypair. + // Verify that the provided root certificate public key matches the root keypair. if ([MTRCertificates keypair:rootKeypair matchesCertificate:rootCertificate] == NO) { return CHIP_ERROR_INVALID_ARGUMENT; } @@ -248,7 +256,9 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId) ChipDN icac_dn; ReturnErrorOnFailure(icac_dn.AddAttribute_MatterICACId(GetIssuerId(issuerId))); if (fabricId != nil) { - ReturnErrorOnFailure(icac_dn.AddAttribute_MatterFabricId([fabricId unsignedLongLongValue])); + FabricId fabric = [fabricId unsignedLongLongValue]; + VerifyOrReturnError(fabric != kUndefinedFabricId, CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(icac_dn.AddAttribute_MatterFabricId(fabric)); } uint32_t validityStart, validityEnd; @@ -270,3 +280,47 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId) *intermediateCert = AsData(icac); return CHIP_NO_ERROR; } + +CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateOperationalCertificate(id signingKeypair, + NSData * signingCertificate, SecKeyRef operationalPublicKey, NSNumber * fabricId, NSNumber * nodeId, + NSArray * _Nullable caseAuthenticatedTags, NSData * _Nullable __autoreleasing * _Nonnull operationalCert) +{ + *operationalCert = nil; + + // Verify that the provided signing certificate public key matches the signing keypair. + if ([MTRCertificates keypair:signingKeypair matchesCertificate:signingCertificate] == NO) { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + if ([caseAuthenticatedTags count] > kMaxSubjectCATAttributeCount) { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + FabricId fabric = [fabricId unsignedLongLongValue]; + VerifyOrReturnError(fabric != kUndefinedFabricId, CHIP_ERROR_INVALID_ARGUMENT); + + NodeId node = [nodeId unsignedLongLongValue]; + VerifyOrReturnError(IsOperationalNodeId(node), CHIP_ERROR_INVALID_ARGUMENT); + + CHIPP256KeypairBridge keypairBridge; + ReturnErrorOnFailure(keypairBridge.Init(signingKeypair)); + CHIPP256KeypairNativeBridge nativeSigningKeypair(keypairBridge); + + P256PublicKey pubKey; + ReturnErrorOnFailure(CHIPP256KeypairBridge::MatterPubKeyFromSecKeyRef(operationalPublicKey, &pubKey)); + + CATValues cats; + if (caseAuthenticatedTags != nil) { + size_t idx = 0; + for (NSNumber * cat in caseAuthenticatedTags) { + cats.values[idx++] = [cat unsignedIntValue]; + } + } + + uint8_t nocBuffer[Controller::kMaxCHIPDERCertLength]; + MutableByteSpan noc(nocBuffer); + ReturnErrorOnFailure(GenerateNOC(nativeSigningKeypair, signingCertificate, node, fabric, cats, pubKey, noc)); + + *operationalCert = AsData(noc); + return CHIP_NO_ERROR; +} diff --git a/src/darwin/Framework/CHIP/MTRCertificates.h b/src/darwin/Framework/CHIP/MTRCertificates.h index 1cd603a10cdb13..fbf8ef67957ded 100644 --- a/src/darwin/Framework/CHIP/MTRCertificates.h +++ b/src/darwin/Framework/CHIP/MTRCertificates.h @@ -36,7 +36,7 @@ NS_ASSUME_NONNULL_BEGIN * issuer id is used. * * If fabricId is not nil, it will be included in the subject DN of the - * certificate. + * certificate. In this case it must be a valid Matter fabric id. * * On failure returns nil and if "error" is not null sets *error to the relevant * error. @@ -54,7 +54,7 @@ NS_ASSUME_NONNULL_BEGIN * issuer id is used. * * If fabricId is not nil, it will be included in the subject DN of the - * certificate. + * certificate. In this case it must be a valid Matter fabric id. * * On failure returns nil and if "error" is not null sets *error to the relevant * error. @@ -66,6 +66,35 @@ NS_ASSUME_NONNULL_BEGIN fabricId:(nullable NSNumber *)fabricId error:(NSError * __autoreleasing _Nullable * _Nullable)error; +/** + * Generate an X.509 DER encoded certificate that has the + * right fields to be a valid Matter operational certificate. + * + * signingKeypair and signingCertificate are the root or intermediate that is + * signing the operational certificate. + * + * nodeId and fabricId are expected to be 64-bit unsigned integers. + * + * nodeId must be a valid Matter operational node id. + * + * fabricId must be a valid Matter fabric id. + * + * caseAuthenticatedTags may be nil to indicate no CASE Authenticated Tags + * should be used. If caseAuthenticatedTags is not nil, it must have length at + * most 3 and the values in the array are expected to be 32-bit unsigned Case + * Authenticated Tag values. + * + * On failure returns nil and if "error" is not null sets *error to the relevant + * error. + */ ++ (nullable NSData *)generateOperationalCertificate:(id)signingKeypair + signingCertificate:(NSData *)signingCertificate + operationalPublicKey:(SecKeyRef)operationalPublicKey + fabricId:(NSNumber *)fabricId + nodeId:(NSNumber *)nodeId + caseAuthenticatedTags:(NSArray * _Nullable)caseAuthenticatedTags + error:(NSError * __autoreleasing _Nullable * _Nullable)error; + /** * Check whether the given keypair's public key matches the given certificate's * public key. The certificate is expected to be an X.509 DER encoded diff --git a/src/darwin/Framework/CHIP/MTRCertificates.mm b/src/darwin/Framework/CHIP/MTRCertificates.mm index 65eb195ded2c49..0c9f6bc2dc12c3 100644 --- a/src/darwin/Framework/CHIP/MTRCertificates.mm +++ b/src/darwin/Framework/CHIP/MTRCertificates.mm @@ -86,6 +86,32 @@ + (nullable NSData *)generateIntermediateCertificate:(id)rootKeypai return intermediate; } ++ (nullable NSData *)generateOperationalCertificate:(id)signingKeypair + signingCertificate:(NSData *)signingCertificate + operationalPublicKey:(SecKeyRef)operationalPublicKey + fabricId:(NSNumber *)fabricId + nodeId:(NSNumber *)nodeId + caseAuthenticatedTags:(NSArray * _Nullable)caseAuthenticatedTags + error:(NSError * __autoreleasing _Nullable * _Nullable)error +{ + NSLog(@"Generating operational certificate"); + + AutoPlatformMemory platformMemory; + + NSData * opcert = nil; + CHIP_ERROR err = CHIPOperationalCredentialsDelegate::GenerateOperationalCertificate( + signingKeypair, signingCertificate, operationalPublicKey, fabricId, nodeId, caseAuthenticatedTags, &opcert); + if (error) { + *error = [CHIPError errorForCHIPErrorCode:err]; + } + + if (err != CHIP_NO_ERROR) { + NSLog(@"Generating operational certificate failed: %s", ErrorStr(err)); + } + + return opcert; +} + + (BOOL)keypair:(id)keypair matchesCertificate:(NSData *)certificate { P256PublicKey keypairPubKey; diff --git a/src/darwin/Framework/CHIPTests/MatterCertificateTests.m b/src/darwin/Framework/CHIPTests/MatterCertificateTests.m index eedaffe762c1d2..5feb32d737db13 100644 --- a/src/darwin/Framework/CHIPTests/MatterCertificateTests.m +++ b/src/darwin/Framework/CHIPTests/MatterCertificateTests.m @@ -56,4 +56,140 @@ - (void)testGenerateIntermediateCert XCTAssertNotNil(intermediateCert); } +- (void)testGenerateOperationalCertNoIntermediate +{ + __auto_type * rootKeys = [[CHIPTestKeys alloc] init]; + XCTAssertNotNil(rootKeys); + + __auto_type * rootCert = [MTRCertificates generateRootCertificate:rootKeys issuerId:nil fabricId:nil error:nil]; + XCTAssertNotNil(rootCert); + + __auto_type * operationalKeys = [[CHIPTestKeys alloc] init]; + XCTAssertNotNil(operationalKeys); + + __auto_type * cats = [[NSMutableArray alloc] initWithCapacity:3]; + [cats addObject:@1]; + [cats addObject:@2]; + [cats addObject:@3]; + + __auto_type * operationalCert = [MTRCertificates generateOperationalCertificate:rootKeys + signingCertificate:rootCert + operationalPublicKey:operationalKeys.pubkey + fabricId:@1 + nodeId:@1 + caseAuthenticatedTags:cats + error:nil]; + XCTAssertNotNil(operationalCert); +} + +- (void)testGenerateOperationalCertWithIntermediate +{ + __auto_type * rootKeys = [[CHIPTestKeys alloc] init]; + XCTAssertNotNil(rootKeys); + + __auto_type * rootCert = [MTRCertificates generateRootCertificate:rootKeys issuerId:nil fabricId:nil error:nil]; + XCTAssertNotNil(rootCert); + + __auto_type * intermediateKeys = [[CHIPTestKeys alloc] init]; + XCTAssertNotNil(intermediateKeys); + + __auto_type * intermediateCert = [MTRCertificates generateIntermediateCertificate:rootKeys + rootCertificate:rootCert + intermediatePublicKey:intermediateKeys.pubkey + issuerId:nil + fabricId:nil + error:nil]; + XCTAssertNotNil(intermediateCert); + + __auto_type * operationalKeys = [[CHIPTestKeys alloc] init]; + XCTAssertNotNil(operationalKeys); + + __auto_type * operationalCert = [MTRCertificates generateOperationalCertificate:intermediateKeys + signingCertificate:intermediateCert + operationalPublicKey:operationalKeys.pubkey + fabricId:@1 + nodeId:@1 + caseAuthenticatedTags:nil + error:nil]; + XCTAssertNotNil(operationalCert); +} + +- (void)testGenerateOperationalCertErrorCases +{ + __auto_type * rootKeys = [[CHIPTestKeys alloc] init]; + XCTAssertNotNil(rootKeys); + + __auto_type * rootCert = [MTRCertificates generateRootCertificate:rootKeys issuerId:nil fabricId:nil error:nil]; + XCTAssertNotNil(rootCert); + + __auto_type * operationalKeys = [[CHIPTestKeys alloc] init]; + XCTAssertNotNil(operationalKeys); + + __auto_type * cats = [[NSMutableArray alloc] initWithCapacity:4]; + [cats addObject:@1]; + [cats addObject:@2]; + [cats addObject:@3]; + [cats addObject:@4]; + + // Check basic case works + __auto_type * operationalCert = [MTRCertificates generateOperationalCertificate:rootKeys + signingCertificate:rootCert + operationalPublicKey:operationalKeys.pubkey + fabricId:@1 + nodeId:@1 + caseAuthenticatedTags:nil + error:nil]; + XCTAssertNotNil(operationalCert); + + // CATs too long + operationalCert = [MTRCertificates generateOperationalCertificate:rootKeys + signingCertificate:rootCert + operationalPublicKey:operationalKeys.pubkey + fabricId:@1 + nodeId:@1 + caseAuthenticatedTags:cats + error:nil]; + XCTAssertNil(operationalCert); + + // Signing key mismatch + operationalCert = [MTRCertificates generateOperationalCertificate:operationalKeys + signingCertificate:rootCert + operationalPublicKey:operationalKeys.pubkey + fabricId:@1 + nodeId:@1 + caseAuthenticatedTags:nil + error:nil]; + XCTAssertNil(operationalCert); + + // Invalid fabric id + operationalCert = [MTRCertificates generateOperationalCertificate:rootKeys + signingCertificate:rootCert + operationalPublicKey:operationalKeys.pubkey + fabricId:@0 + nodeId:@1 + caseAuthenticatedTags:nil + error:nil]; + XCTAssertNil(operationalCert); + + // Undefined node id + operationalCert = [MTRCertificates generateOperationalCertificate:rootKeys + signingCertificate:rootCert + operationalPublicKey:operationalKeys.pubkey + fabricId:@1 + nodeId:@0 + caseAuthenticatedTags:nil + error:nil]; + XCTAssertNil(operationalCert); + + // Non-operational node id + operationalCert = [MTRCertificates generateOperationalCertificate:rootKeys + signingCertificate:rootCert + operationalPublicKey:operationalKeys.pubkey + fabricId:@1 + nodeId:@(0xFFFFFFFFFFFFFFFFLLU) + caseAuthenticatedTags:nil + error:nil]; + XCTAssertNil(operationalCert); +} + @end