diff --git a/src/credentials/CertificationDeclaration.cpp b/src/credentials/CertificationDeclaration.cpp index 5d64230673250a..5ca6224a5ff605 100644 --- a/src/credentials/CertificationDeclaration.cpp +++ b/src/credentials/CertificationDeclaration.cpp @@ -109,6 +109,8 @@ CHIP_ERROR DecodeCertificationElements(const ByteSpan & encodedCertElements, Cer TLVReader reader; TLVType outerContainer1, outerContainer2; + VerifyOrReturnError(encodedCertElements.size() <= kMaxCMSSignedCDMessage, CHIP_ERROR_INVALID_ARGUMENT); + reader.Init(encodedCertElements); ReturnErrorOnFailure(reader.Next(kTLVType_Structure, AnonymousTag)); @@ -165,7 +167,7 @@ CHIP_ERROR DecodeCertificationElements(const ByteSpan & encodedCertElements, Cer err = reader.Next(); } - VerifyOrReturnError(err == CHIP_END_OF_TLV, err); + VerifyOrReturnError(err == CHIP_END_OF_TLV || err == CHIP_ERROR_UNEXPECTED_TLV_ELEMENT || err == CHIP_NO_ERROR, err); ReturnErrorOnFailure(reader.ExitContainer(outerContainer1)); @@ -174,6 +176,125 @@ CHIP_ERROR DecodeCertificationElements(const ByteSpan & encodedCertElements, Cer return CHIP_NO_ERROR; } +CHIP_ERROR DecodeCertificationElements(const ByteSpan & encodedCertElements, CertificationElementsWithoutPIDs & certDeclContent) +{ + CHIP_ERROR err; + TLVReader reader; + TLVType outerContainer; + + VerifyOrReturnError(encodedCertElements.size() <= kMaxCMSSignedCDMessage, CHIP_ERROR_INVALID_ARGUMENT); + + reader.Init(encodedCertElements); + + ReturnErrorOnFailure(reader.Next(kTLVType_Structure, AnonymousTag)); + + ReturnErrorOnFailure(reader.EnterContainer(outerContainer)); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_FormatVersion))); + ReturnErrorOnFailure(reader.Get(certDeclContent.formatVersion)); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_VendorId))); + ReturnErrorOnFailure(reader.Get(certDeclContent.vendorId)); + + ReturnErrorOnFailure(reader.Next(kTLVType_Array, ContextTag(kTag_ProductIdArray))); + + // skip PID Array + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_DeviceTypeId))); + ReturnErrorOnFailure(reader.Get(certDeclContent.deviceTypeId)); + + ReturnErrorOnFailure(reader.Next(kTLVType_UTF8String, ContextTag(kTag_CertificateId))); + ReturnErrorOnFailure(reader.GetString(certDeclContent.certificateId, sizeof(certDeclContent.certificateId))); + VerifyOrReturnError(strlen(certDeclContent.certificateId) == kCertificateIdLength, CHIP_ERROR_INVALID_TLV_ELEMENT); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_SecurityLevel))); + ReturnErrorOnFailure(reader.Get(certDeclContent.securityLevel)); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_SecurityInformation))); + ReturnErrorOnFailure(reader.Get(certDeclContent.securityInformation)); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_VersionNumber))); + ReturnErrorOnFailure(reader.Get(certDeclContent.versionNumber)); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_CertificationType))); + ReturnErrorOnFailure(reader.Get(certDeclContent.certificationType)); + + certDeclContent.dacOriginVIDandPIDPresent = false; + + // If kTag_DACOriginVendorId present then kTag_DACOriginProductId must be present. + if ((err = reader.Next(ContextTag(kTag_DACOriginVendorId))) == CHIP_NO_ERROR) + { + ReturnErrorOnFailure(reader.Get(certDeclContent.dacOriginVendorId)); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_DACOriginProductId))); + ReturnErrorOnFailure(reader.Get(certDeclContent.dacOriginProductId)); + + certDeclContent.dacOriginVIDandPIDPresent = true; + + err = reader.Next(); + } + VerifyOrReturnError(err == CHIP_END_OF_TLV || err == CHIP_ERROR_UNEXPECTED_TLV_ELEMENT || err == CHIP_NO_ERROR, err); + + ReturnErrorOnFailure(reader.ExitContainer(outerContainer)); + + ReturnErrorOnFailure(reader.VerifyEndOfContainer()); + + return CHIP_NO_ERROR; +} + +bool CertificationElementsDecoder::IsProductIdIn(const ByteSpan & encodedCertElements, uint16_t productId) +{ + VerifyOrReturnError(PrepareToReadProductIdList(encodedCertElements) == CHIP_NO_ERROR, false); + + uint16_t cdProductId = 0; + CHIP_ERROR error = CHIP_NO_ERROR; + + while ((error = GetNextProductId(cdProductId)) == CHIP_NO_ERROR) + { + if (productId == cdProductId) + { + return true; + } + } + + return false; +} + +CHIP_ERROR CertificationElementsDecoder::PrepareToReadProductIdList(const ByteSpan & encodedCertElements) +{ + mIsInitialized = false; + mCertificationDeclarationData = encodedCertElements; + + mReader.Init(mCertificationDeclarationData); + ReturnErrorOnFailure(mReader.Next(kTLVType_Structure, AnonymousTag)); + ReturnErrorOnFailure(mReader.EnterContainer(mOuterContainerType1)); + + // position to ProductId Array + CHIP_ERROR error = CHIP_NO_ERROR; + do + { + error = mReader.Next(kTLVType_Array, ContextTag(kTag_ProductIdArray)); + // return error code if Next method returned different than CHIP_NO_ERROR. + // also return if different error code than CHIP_ERROR_WRONG_TLV_TYPE/CHIP_ERROR_UNEXPECTED_TLV_ELEMENT, which means that + // the expected type and tags do not match. + VerifyOrReturnError( + error == CHIP_NO_ERROR || error == CHIP_ERROR_WRONG_TLV_TYPE || error == CHIP_ERROR_UNEXPECTED_TLV_ELEMENT, error); + } while (error != CHIP_NO_ERROR); + + ReturnErrorOnFailure(mReader.EnterContainer(mOuterContainerType2)); + + mIsInitialized = true; + return CHIP_NO_ERROR; +} + +CHIP_ERROR CertificationElementsDecoder::GetNextProductId(uint16_t & productId) +{ + VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE); + ReturnErrorOnFailure(mReader.Next(AnonymousTag)); + ReturnErrorOnFailure(mReader.Get(productId)); + return CHIP_NO_ERROR; +} + namespace { CHIP_ERROR EncodeEncapsulatedContent(const ByteSpan & cdContent, ASN1Writer & writer) diff --git a/src/credentials/CertificationDeclaration.h b/src/credentials/CertificationDeclaration.h index 2fc29171f773d3..e627b9cba93fd0 100644 --- a/src/credentials/CertificationDeclaration.h +++ b/src/credentials/CertificationDeclaration.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -62,6 +63,37 @@ struct CertificationElements bool DACOriginVIDandPIDPresent; }; +struct CertificationElementsWithoutPIDs +{ + uint16_t formatVersion = 0; + uint16_t vendorId = VendorId::NotSpecified; + uint32_t deviceTypeId = 0; + uint8_t securityLevel = 0; + uint16_t securityInformation = 0; + uint16_t versionNumber = 0; + uint8_t certificationType = 0; + uint16_t dacOriginVendorId = VendorId::NotSpecified; + uint16_t dacOriginProductId = 0; + bool dacOriginVIDandPIDPresent = false; + char certificateId[kCertificateIdLength + 1] = { 0 }; +}; + +class CertificationElementsDecoder +{ +public: + bool IsProductIdIn(const ByteSpan & encodedCertElements, uint16_t productId); + +private: + CHIP_ERROR PrepareToReadProductIdList(const ByteSpan & encodedCertElements); + CHIP_ERROR GetNextProductId(uint16_t & productId); + + ByteSpan mCertificationDeclarationData; + bool mIsInitialized = false; + TLV::TLVReader mReader; + TLV::TLVType mOuterContainerType1 = TLV::kTLVType_Structure; + TLV::TLVType mOuterContainerType2 = TLV::kTLVType_Structure; +}; + /** * @brief Encode certification elements in TLV format. * @@ -82,6 +114,16 @@ CHIP_ERROR EncodeCertificationElements(const CertificationElements & certElement **/ CHIP_ERROR DecodeCertificationElements(const ByteSpan & encodedCertElements, CertificationElements & certElements); +/** + * @brief Decode certification elements from TLV encoded structure. + * + * @param[in] encodedCertElements A byte span to read the TLV encoded certification elements. + * @param[out] certDeclContent Decoded Certification Declaration Content. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ +CHIP_ERROR DecodeCertificationElements(const ByteSpan & encodedCertElements, CertificationElementsWithoutPIDs & certDeclContent); + /** * @brief Generate CMS signed message. * diff --git a/src/credentials/DeviceAttestationVerifier.cpp b/src/credentials/DeviceAttestationVerifier.cpp index 428b3d6823466c..379b08aa7af640 100644 --- a/src/credentials/DeviceAttestationVerifier.cpp +++ b/src/credentials/DeviceAttestationVerifier.cpp @@ -52,6 +52,16 @@ class UnimplementedDACVerifier : public DeviceAttestationVerifier (void) certDeclBuffer; return AttestationVerificationResult::kNotImplemented; } + + AttestationVerificationResult ValidateCertificateDeclarationPayload(const ByteSpan & certDeclBuffer, + const ByteSpan & firmwareInfo, + const DeviceInfoForAttestation & deviceInfo) override + { + (void) certDeclBuffer; + (void) firmwareInfo; + (void) deviceInfo; + return AttestationVerificationResult::kNotImplemented; + } }; // Default to avoid nullptr on getter and cleanly handle new products/clients before diff --git a/src/credentials/DeviceAttestationVerifier.h b/src/credentials/DeviceAttestationVerifier.h index 129d50310357e6..09d843153943d5 100644 --- a/src/credentials/DeviceAttestationVerifier.h +++ b/src/credentials/DeviceAttestationVerifier.h @@ -18,6 +18,7 @@ #include #include +#include #include namespace chip { @@ -60,9 +61,14 @@ enum class AttestationVerificationResult : uint16_t kCertificationDeclarationNoKeyId = 600, kCertificationDeclarationNoCertificateFound = 601, kCertificationDeclarationInvalidSignature = 602, + kCertificationDeclarationInvalidFormat = 603, + kCertificationDeclarationInvalidVendorId = 604, + kCertificationDeclarationInvalidProductId = 605, kNoMemory = 700, + kInvalidArgument = 800, + kNotImplemented = 0xFFFFU, // TODO: Add more attestation verification errors @@ -75,6 +81,24 @@ enum CertificateType : uint8_t kPAI = 2, }; +struct DeviceInfoForAttestation +{ + // Vendor ID reported by device in Basic Information cluster + uint16_t vendorId = VendorId::NotSpecified; + // Product ID reported by device in Basic Information cluster + uint16_t productId = 0; + // Vendor ID from DAC + uint16_t dacVendorId = VendorId::NotSpecified; + // Product ID from DAC + uint16_t dacProductId = 0; + // Vendor ID from PAI cert + uint16_t paiVendorId = VendorId::NotSpecified; + // Product ID from PAI cert (0 if absent) + uint16_t paiProductId = 0; + // Vendor ID from PAA cert + uint16_t paaVendorId = VendorId::NotSpecified; +}; + class DeviceAttestationVerifier { public: @@ -117,7 +141,19 @@ class DeviceAttestationVerifier virtual AttestationVerificationResult ValidateCertificationDeclarationSignature(const ByteSpan & cmsEnvelopeBuffer, ByteSpan & certDeclBuffer) = 0; - // TODO: Validate Certification Declaration Payload + /** + * @brief Verify a CMS Signed Data Payload against the Basic Information Cluster and DAC/PAI's Vendor and Product IDs + * + * @param[in] certDeclBuffer A ByteSpan with the Certification Declaration content. + * @param[in] firmwareInfo A ByteSpan with the Firmware Information content. + * @param[in] deviceInfo + * + * @returns AttestationVerificationResult::kSuccess on success or another specific + * value from AttestationVerificationResult enum on failure. + */ + virtual AttestationVerificationResult ValidateCertificateDeclarationPayload(const ByteSpan & certDeclBuffer, + const ByteSpan & firmwareInfo, + const DeviceInfoForAttestation & deviceInfo) = 0; // TODO: Validate Firmware Information diff --git a/src/credentials/examples/DeviceAttestationVerifierExample.cpp b/src/credentials/examples/DeviceAttestationVerifierExample.cpp index 43b42ed1fbc79a..11f34289435d0e 100644 --- a/src/credentials/examples/DeviceAttestationVerifierExample.cpp +++ b/src/credentials/examples/DeviceAttestationVerifierExample.cpp @@ -200,6 +200,10 @@ class ExampleDACVerifier : public DeviceAttestationVerifier AttestationVerificationResult ValidateCertificationDeclarationSignature(const ByteSpan & cmsEnvelopeBuffer, ByteSpan & certDeclBuffer) override; + + AttestationVerificationResult ValidateCertificateDeclarationPayload(const ByteSpan & certDeclBuffer, + const ByteSpan & firmwareInfo, + const DeviceInfoForAttestation & deviceInfo) override; }; AttestationVerificationResult ExampleDACVerifier::VerifyAttestationInformation(const ByteSpan & attestationInfoBuffer, @@ -209,23 +213,24 @@ AttestationVerificationResult ExampleDACVerifier::VerifyAttestationInformation(c const ByteSpan & dacCertDerBuffer, const ByteSpan & attestationNonce) { + VerifyOrReturnError(!attestationInfoBuffer.empty() && !attestationChallengeBuffer.empty() && + !attestationSignatureBuffer.empty() && !paiCertDerBuffer.empty() && !dacCertDerBuffer.empty() && + !attestationNonce.empty(), + AttestationVerificationResult::kInvalidArgument); + + VendorId dacVendorId = VendorId::NotSpecified; // match DAC and PAI VIDs - if (!paiCertDerBuffer.empty()) { - VendorId paiVid; - VendorId dacVid; + uint16_t paiVid = VendorId::NotSpecified; + uint16_t dacVid = VendorId::NotSpecified; - CHIP_ERROR error = ExtractVIDFromX509Cert(paiCertDerBuffer, paiVid); - const bool paiHasVid = error != CHIP_ERROR_KEY_NOT_FOUND; - VerifyOrReturnError(error == CHIP_NO_ERROR || paiHasVid == false, AttestationVerificationResult::kPaiFormatInvalid); - - if (paiHasVid) - { - VerifyOrReturnError(ExtractVIDFromX509Cert(dacCertDerBuffer, dacVid) == CHIP_NO_ERROR, - AttestationVerificationResult::kDacFormatInvalid); + VerifyOrReturnError(ExtractDNAttributeFromX509Cert(MatterOid::kVendorId, paiCertDerBuffer, paiVid) == CHIP_NO_ERROR, + AttestationVerificationResult::kPaiFormatInvalid); + VerifyOrReturnError(ExtractDNAttributeFromX509Cert(MatterOid::kVendorId, dacCertDerBuffer, dacVid) == CHIP_NO_ERROR, + AttestationVerificationResult::kDacFormatInvalid); - VerifyOrReturnError(paiVid == dacVid, AttestationVerificationResult::kDacVendorIdMismatch); - } + VerifyOrReturnError(paiVid == dacVid, AttestationVerificationResult::kDacVendorIdMismatch); + dacVendorId = static_cast(dacVid); } P256PublicKey remoteManufacturerPubkey; @@ -244,7 +249,7 @@ AttestationVerificationResult ExampleDACVerifier::VerifyAttestationInformation(c uint8_t akidBuf[Credentials::kKeyIdentifierLength]; MutableByteSpan akid(akidBuf); - ExtractAKIDFromX509Cert(paiCertDerBuffer.empty() ? dacCertDerBuffer : paiCertDerBuffer, akid); + ExtractAKIDFromX509Cert(paiCertDerBuffer, akid); constexpr size_t paaCertAllocatedLen = kMaxDERCertLength; chip::Platform::ScopedMemoryBuffer paaCert; @@ -257,6 +262,18 @@ AttestationVerificationResult ExampleDACVerifier::VerifyAttestationInformation(c dacCertDerBuffer.data(), dacCertDerBuffer.size()) == CHIP_NO_ERROR, AttestationVerificationResult::kDacSignatureInvalid); + // if PAA contains VID, see if matches with DAC's VID. + { + uint16_t paaVid = VendorId::NotSpecified; + CHIP_ERROR error = ExtractDNAttributeFromX509Cert(MatterOid::kVendorId, paa, paaVid); + VerifyOrReturnError(error == CHIP_NO_ERROR || error == CHIP_ERROR_KEY_NOT_FOUND, + AttestationVerificationResult::kPaaFormatInvalid); + if (error != CHIP_ERROR_KEY_NOT_FOUND) + { + VerifyOrReturnError(paaVid == dacVendorId, AttestationVerificationResult::kDacVendorIdMismatch); + } + } + ByteSpan certificationDeclarationSpan; ByteSpan attestationNonceSpan; uint32_t timestampDeconstructed; @@ -271,8 +288,26 @@ AttestationVerificationResult ExampleDACVerifier::VerifyAttestationInformation(c VerifyOrReturnError(attestationNonceSpan.data_equal(attestationNonce), AttestationVerificationResult::kAttestationNonceMismatch); + AttestationVerificationResult attestationError; ByteSpan certificationDeclarationPayload; - return ValidateCertificationDeclarationSignature(certificationDeclarationSpan, certificationDeclarationPayload); + attestationError = ValidateCertificationDeclarationSignature(certificationDeclarationSpan, certificationDeclarationPayload); + VerifyOrReturnError(attestationError == AttestationVerificationResult::kSuccess, attestationError); + + DeviceInfoForAttestation deviceInfo{ + .vendorId = 0xFFF1, + .productId = 0x8000, // TODO: Retrieve vendorId and ProductId from Basic Information Cluster + .dacVendorId = dacVendorId, + .paiVendorId = dacVendorId, + }; + VerifyOrReturnError(ExtractDNAttributeFromX509Cert(MatterOid::kProductId, dacCertDerBuffer, deviceInfo.dacProductId) == + CHIP_NO_ERROR, + AttestationVerificationResult::kDacFormatInvalid); + // If PID is missing from PAI, the next method call will return CHIP_ERROR_KEY_NOT_FOUND. + // Valid return values are then CHIP_NO_ERROR or CHIP_ERROR_KEY_NOT_FOUND. + CHIP_ERROR error = ExtractDNAttributeFromX509Cert(MatterOid::kProductId, paiCertDerBuffer, deviceInfo.paiProductId); + VerifyOrReturnError(error == CHIP_NO_ERROR || error == CHIP_ERROR_KEY_NOT_FOUND, + AttestationVerificationResult::kPaiFormatInvalid); + return ValidateCertificateDeclarationPayload(certificationDeclarationPayload, firmwareInfoSpan, deviceInfo); } AttestationVerificationResult ExampleDACVerifier::ValidateCertificationDeclarationSignature(const ByteSpan & cmsEnvelopeBuffer, @@ -294,6 +329,73 @@ AttestationVerificationResult ExampleDACVerifier::ValidateCertificationDeclarati return AttestationVerificationResult::kSuccess; } +AttestationVerificationResult ExampleDACVerifier::ValidateCertificateDeclarationPayload(const ByteSpan & certDeclBuffer, + const ByteSpan & firmwareInfo, + const DeviceInfoForAttestation & deviceInfo) +{ + CertificationElementsWithoutPIDs cdContent; + CertificationElementsDecoder cdElementsDecoder; + VerifyOrReturnError(DecodeCertificationElements(certDeclBuffer, cdContent) == CHIP_NO_ERROR, + AttestationVerificationResult::kCertificationDeclarationInvalidFormat); + + if (!firmwareInfo.empty()) + { + // TODO: check if version_number field in Certification Declaration matches the one in Firmware Information. + } + + // The vendor_id field in the Certification Declaration SHALL match the VendorID attribute found in the Basic Information + // cluster + VerifyOrReturnError(cdContent.vendorId == deviceInfo.vendorId, + AttestationVerificationResult::kCertificationDeclarationInvalidVendorId); + + // The product_id_array field in the Certification Declaration SHALL contain the value of the ProductID attribute found in + // the Basic Information cluster. + VerifyOrReturnError(cdElementsDecoder.IsProductIdIn(certDeclBuffer, deviceInfo.productId), + AttestationVerificationResult::kCertificationDeclarationInvalidProductId); + + if (cdContent.dacOriginVIDandPIDPresent) + { + // The Vendor ID (VID) subject DN in the DAC SHALL match the dac_origin_vendor_id field in the Certification Declaration. + VerifyOrReturnError(deviceInfo.dacVendorId == cdContent.dacOriginVendorId, + AttestationVerificationResult::kCertificationDeclarationInvalidVendorId); + // The Vendor ID (VID) subject DN in the PAI SHALL match the dac_origin_vendor_id field in the Certification Declaration. + VerifyOrReturnError(deviceInfo.paiVendorId == cdContent.dacOriginVendorId, + AttestationVerificationResult::kCertificationDeclarationInvalidVendorId); + // The Product ID (PID) subject DN in the DAC SHALL match the dac_origin_product_id field in the Certification Declaration. + VerifyOrReturnError(deviceInfo.dacProductId == cdContent.dacOriginProductId, + AttestationVerificationResult::kCertificationDeclarationInvalidProductId); + // The Product ID (PID) subject DN in the PAI, if such a Product ID is present, SHALL match the dac_origin_product_id field + // in the Certification Declaration. + if (deviceInfo.paiProductId != 0) // if PAI PID is present + { + VerifyOrReturnError(deviceInfo.paiProductId == cdContent.dacOriginProductId, + AttestationVerificationResult::kCertificationDeclarationInvalidProductId); + } + } + else + { + // The Vendor ID (VID) subject DN in the DAC SHALL match the vendor_id field in the Certification Declaration + VerifyOrReturnError(deviceInfo.dacVendorId == cdContent.vendorId, + AttestationVerificationResult::kCertificationDeclarationInvalidVendorId); + // The Vendor ID (VID) subject DN in the PAI SHALL match the vendor_id field in the Certification Declaration. + VerifyOrReturnError(deviceInfo.paiVendorId == cdContent.vendorId, + AttestationVerificationResult::kCertificationDeclarationInvalidVendorId); + // The Product ID (PID) subject DN in the DAC SHALL be present in the product_id_array field in the Certification + // Declaration. + VerifyOrReturnError(cdElementsDecoder.IsProductIdIn(certDeclBuffer, deviceInfo.dacProductId), + AttestationVerificationResult::kCertificationDeclarationInvalidProductId); + // The Product ID (PID) subject DN in the PAI, if such a Product ID is present, SHALL match one of the values present in the + // product_id_array field in the Certification Declaration. + if (deviceInfo.paiProductId != 0) // if PAI PID is present + { + VerifyOrReturnError(cdElementsDecoder.IsProductIdIn(certDeclBuffer, deviceInfo.paiProductId), + AttestationVerificationResult::kCertificationDeclarationInvalidProductId); + } + } + + return AttestationVerificationResult::kSuccess; +} + } // namespace DeviceAttestationVerifier * GetExampleDACVerifier() diff --git a/src/credentials/tests/TestCertificationDeclaration.cpp b/src/credentials/tests/TestCertificationDeclaration.cpp index 61c9e0c612a02d..12e49518de3e8f 100644 --- a/src/credentials/tests/TestCertificationDeclaration.cpp +++ b/src/credentials/tests/TestCertificationDeclaration.cpp @@ -307,12 +307,58 @@ static void TestCD_CMSVerifyAndExtract(nlTestSuite * inSuite, void * inContext) } } +static void TestCD_CertificationElementsDecoder(nlTestSuite * inSuite, void * inContext) +{ + for (size_t i = 0; i < sNumTestCases; i++) + { + const TestCase & testCase = sTestCases[i]; + + uint8_t encodedCertElemBuf[kCertificationElements_TLVEncodedMaxLength]; + MutableByteSpan encodedCDPayload(encodedCertElemBuf); + + NL_TEST_ASSERT(inSuite, EncodeCertificationElements(testCase.cdElements, encodedCDPayload) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, testCase.cdContent.data_equal(encodedCDPayload)); + + CertificationElementsWithoutPIDs certificationDeclarationContent; + CertificationElementsDecoder certificationElementsDecoder; + NL_TEST_ASSERT(inSuite, DecodeCertificationElements(encodedCDPayload, certificationDeclarationContent) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, certificationDeclarationContent.formatVersion == testCase.cdElements.FormatVersion); + NL_TEST_ASSERT(inSuite, certificationDeclarationContent.vendorId == testCase.cdElements.VendorId); + for (uint8_t j = 0; j < testCase.cdElements.ProductIdsCount; j++) + { + NL_TEST_ASSERT(inSuite, + certificationElementsDecoder.IsProductIdIn(encodedCDPayload, testCase.cdElements.ProductIds[j])); + // now test for an unexistent ProductId + NL_TEST_ASSERT(inSuite, certificationElementsDecoder.IsProductIdIn(encodedCDPayload, 0x9000) == false); + } + NL_TEST_ASSERT(inSuite, certificationDeclarationContent.deviceTypeId == testCase.cdElements.DeviceTypeId); + NL_TEST_ASSERT( + inSuite, + memcmp(certificationDeclarationContent.certificateId, testCase.cdElements.CertificateId, kCertificateIdLength) == 0); + NL_TEST_ASSERT(inSuite, certificationDeclarationContent.securityLevel == testCase.cdElements.SecurityLevel); + NL_TEST_ASSERT(inSuite, certificationDeclarationContent.securityInformation == testCase.cdElements.SecurityInformation); + NL_TEST_ASSERT(inSuite, certificationDeclarationContent.versionNumber == testCase.cdElements.VersionNumber); + NL_TEST_ASSERT(inSuite, certificationDeclarationContent.certificationType == testCase.cdElements.CertificationType); + NL_TEST_ASSERT(inSuite, + certificationDeclarationContent.dacOriginVIDandPIDPresent == testCase.cdElements.DACOriginVIDandPIDPresent); + if (certificationDeclarationContent.dacOriginVIDandPIDPresent) + { + NL_TEST_ASSERT(inSuite, certificationDeclarationContent.dacOriginVendorId == testCase.cdElements.DACOriginVendorId); + NL_TEST_ASSERT(inSuite, certificationDeclarationContent.dacOriginProductId == testCase.cdElements.DACOriginProductId); + } + } +} + #define NL_TEST_DEF_FN(fn) NL_TEST_DEF("Test " #fn, fn) /** * Test Suite. It lists all the test functions. */ -static const nlTest sTests[] = { NL_TEST_DEF_FN(TestCD_EncodeDecode), NL_TEST_DEF_FN(TestCD_EncodeDecode_Errors), - NL_TEST_DEF_FN(TestCD_CMSSignAndVerify), NL_TEST_DEF_FN(TestCD_CMSVerifyAndExtract), +static const nlTest sTests[] = { NL_TEST_DEF_FN(TestCD_EncodeDecode), + NL_TEST_DEF_FN(TestCD_EncodeDecode_Errors), + NL_TEST_DEF_FN(TestCD_CMSSignAndVerify), + NL_TEST_DEF_FN(TestCD_CMSVerifyAndExtract), + NL_TEST_DEF_FN(TestCD_CertificationElementsDecoder), NL_TEST_SENTINEL() }; int TestCertificationDeclaration(void) diff --git a/src/credentials/tests/TestDeviceAttestationCredentials.cpp b/src/credentials/tests/TestDeviceAttestationCredentials.cpp index f802ae2eee4c48..1d92c0cf8dc672 100644 --- a/src/credentials/tests/TestDeviceAttestationCredentials.cpp +++ b/src/credentials/tests/TestDeviceAttestationCredentials.cpp @@ -240,6 +240,9 @@ static void TestDACVerifierExample_CertDeclarationVerification(nlTestSuite * inS // -> certification_type = 0 // -> dac_origin_vendor_id is not present // -> dac_origin_product_id is not present + static constexpr CertificationElements sTestCMS_CertElements = { 1, 0xFFF1, { 0x8000 }, 1, 0x1234, "ZIG20141ZB330001-24", + 0, 0, 0x2694, 0, 0, 0, + false }; static constexpr uint8_t sTestCMS_CDContent[] = { 0x15, 0x24, 0x00, 0x01, 0x25, 0x01, 0xf1, 0xff, 0x36, 0x02, 0x05, 0x00, 0x80, 0x18, 0x25, 0x03, 0x34, 0x12, 0x2c, 0x04, 0x13, 0x5a, 0x49, 0x47, 0x32, 0x30, 0x31, 0x34, 0x31, 0x5a, 0x42, 0x33, 0x33, @@ -278,6 +281,18 @@ static void TestDACVerifierExample_CertDeclarationVerification(nlTestSuite * inS NL_TEST_ASSERT(inSuite, attestation_result == AttestationVerificationResult::kSuccess); NL_TEST_ASSERT(inSuite, cd_payload.data_equal(ByteSpan(sTestCMS_CDContent))); + + DeviceInfoForAttestation deviceInfo{ + .vendorId = sTestCMS_CertElements.VendorId, + .productId = sTestCMS_CertElements.ProductIds[0], + .dacVendorId = sTestCMS_CertElements.VendorId, + .dacProductId = sTestCMS_CertElements.ProductIds[0], + .paiVendorId = sTestCMS_CertElements.VendorId, + .paiProductId = sTestCMS_CertElements.ProductIds[0], + .paaVendorId = sTestCMS_CertElements.VendorId, + }; + attestation_result = default_verifier->ValidateCertificateDeclarationPayload(cd_payload, ByteSpan(), deviceInfo); + NL_TEST_ASSERT(inSuite, attestation_result == AttestationVerificationResult::kSuccess); } /** diff --git a/src/crypto/CHIPCryptoPAL.h b/src/crypto/CHIPCryptoPAL.h index 89eda40a4a987b..28792a5076d9bf 100644 --- a/src/crypto/CHIPCryptoPAL.h +++ b/src/crypto/CHIPCryptoPAL.h @@ -1217,10 +1217,16 @@ CHIP_ERROR ExtractSKIDFromX509Cert(const ByteSpan & certificate, MutableByteSpan **/ CHIP_ERROR ExtractAKIDFromX509Cert(const ByteSpan & certificate, MutableByteSpan & akid); +enum class MatterOid +{ + kVendorId, + kProductId, +}; + /** - * @brief Extracts the Vendor ID from an X509 Certificate. + * @brief Extracts one of the IDs listed in MatterOid enum from an X509 Certificate. **/ -CHIP_ERROR ExtractVIDFromX509Cert(const ByteSpan & certificate, VendorId & vid); +CHIP_ERROR ExtractDNAttributeFromX509Cert(MatterOid matterOid, const ByteSpan & certificate, uint16_t & id); } // namespace Crypto } // namespace chip diff --git a/src/crypto/CHIPCryptoPALOpenSSL.cpp b/src/crypto/CHIPCryptoPALOpenSSL.cpp index fdebc4ba6304fe..e20826b6f86c14 100644 --- a/src/crypto/CHIPCryptoPALOpenSSL.cpp +++ b/src/crypto/CHIPCryptoPALOpenSSL.cpp @@ -41,6 +41,7 @@ #include #include +#include #include #include #include @@ -1773,18 +1774,20 @@ CHIP_ERROR ExtractAKIDFromX509Cert(const ByteSpan & certificate, MutableByteSpan return ExtractKIDFromX509Cert(false, certificate, akid); } -CHIP_ERROR ExtractVIDFromX509Cert(const ByteSpan & certificate, VendorId & vid) +namespace { + +CHIP_ERROR ExtractDNAttributeFromX509Cert(const char * oidString, const ByteSpan & certificate, uint16_t & id) { - CHIP_ERROR err = CHIP_NO_ERROR; - X509 * x509certificate = nullptr; - const unsigned char * pCertificate = certificate.data(); - constexpr char vidNeedle[] = "1.3.6.1.4.1.37244.2.1"; // Matter VID OID - taken from Spec - constexpr size_t vidNeedleSize = sizeof(vidNeedle); - char buff[vidNeedleSize] = { 0 }; - X509_NAME * subject = nullptr; - int x509EntryCountIdx = 0; + CHIP_ERROR err = CHIP_NO_ERROR; + X509 * x509certificate = nullptr; + const unsigned char * pCertificate = certificate.data(); + size_t oidStringSize = strlen(oidString) + 1; + constexpr size_t sOidStringSize = 22; + char dnAttributeOidString[sOidStringSize] = { 0 }; + X509_NAME * subject = nullptr; + int x509EntryCountIdx = 0; - vid = VendorId::NotSpecified; + VerifyOrReturnError(oidStringSize == sOidStringSize, CHIP_ERROR_INVALID_ARGUMENT); x509certificate = d2i_X509(NULL, &pCertificate, static_cast(certificate.size())); VerifyOrExit(x509certificate != nullptr, err = CHIP_ERROR_NO_MEMORY); @@ -1798,22 +1801,22 @@ CHIP_ERROR ExtractVIDFromX509Cert(const ByteSpan & certificate, VendorId & vid) VerifyOrExit(name_entry != nullptr, err = CHIP_ERROR_INTERNAL); ASN1_OBJECT * object = X509_NAME_ENTRY_get_object(name_entry); VerifyOrExit(object != nullptr, err = CHIP_ERROR_INTERNAL); - VerifyOrExit(OBJ_obj2txt(buff, sizeof(buff), object, 0) != 0, err = CHIP_ERROR_INTERNAL); + VerifyOrExit(OBJ_obj2txt(dnAttributeOidString, sizeof(dnAttributeOidString), object, 0) != 0, err = CHIP_ERROR_INTERNAL); - if (strncmp(vidNeedle, buff, vidNeedleSize) == 0) + if (strncmp(oidString, dnAttributeOidString, sizeof(dnAttributeOidString)) == 0) { ASN1_STRING * data_entry = X509_NAME_ENTRY_get_data(name_entry); VerifyOrExit(data_entry != nullptr, err = CHIP_ERROR_INTERNAL); unsigned char * str = ASN1_STRING_data(data_entry); VerifyOrExit(str != nullptr, err = CHIP_ERROR_INTERNAL); - vid = static_cast(strtoul(reinterpret_cast(str), NULL, 16)); + VerifyOrExit(ArgParser::ParseInt(reinterpret_cast(str), id, 16), err = CHIP_ERROR_INTERNAL); break; } } // returning CHIP_ERROR_KEY_NOT_FOUND to indicate VID is not present in the certificate. - VerifyOrReturnError(x509EntryCountIdx < X509_NAME_entry_count(subject), CHIP_ERROR_KEY_NOT_FOUND); + VerifyOrExit(x509EntryCountIdx < X509_NAME_entry_count(subject), err = CHIP_ERROR_KEY_NOT_FOUND); exit: X509_free(x509certificate); @@ -1821,5 +1824,25 @@ CHIP_ERROR ExtractVIDFromX509Cert(const ByteSpan & certificate, VendorId & vid) return err; } +} // namespace + +CHIP_ERROR ExtractDNAttributeFromX509Cert(MatterOid matterOid, const ByteSpan & certificate, uint16_t & id) +{ + constexpr char vidOidString[] = "1.3.6.1.4.1.37244.2.1"; // Matter VID OID - taken from Spec + constexpr char pidOidString[] = "1.3.6.1.4.1.37244.2.2"; // Matter PID OID - taken from Spec + + switch (matterOid) + { + case MatterOid::kVendorId: + id = VendorId::NotSpecified; + return ExtractDNAttributeFromX509Cert(vidOidString, certificate, id); + case MatterOid::kProductId: + id = 0; // PID not specified value + return ExtractDNAttributeFromX509Cert(pidOidString, certificate, id); + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } +} + } // namespace Crypto } // namespace chip diff --git a/src/crypto/CHIPCryptoPALmbedTLS.cpp b/src/crypto/CHIPCryptoPALmbedTLS.cpp index c81513b124f587..5d9c047fd6d336 100644 --- a/src/crypto/CHIPCryptoPALmbedTLS.cpp +++ b/src/crypto/CHIPCryptoPALmbedTLS.cpp @@ -46,6 +46,7 @@ #include #include +#include #include #include #include @@ -1405,39 +1406,78 @@ CHIP_ERROR ExtractAKIDFromX509Cert(const ByteSpan & certificate, MutableByteSpan return ExtractKIDFromX509Cert(false, certificate, akid); } -CHIP_ERROR ExtractVIDFromX509Cert(const ByteSpan & certificate, VendorId & vid) +namespace { + +CHIP_ERROR ExtractDNAttributeFromX509Cert(const uint8_t * oidAttribute, size_t oidAttributeLen, const ByteSpan & certificate, + uint16_t & id) { #if defined(MBEDTLS_X509_CRT_PARSE_C) CHIP_ERROR error = CHIP_NO_ERROR; mbedtls_x509_crt mbed_cert; + mbedtls_asn1_named_data * dnIterator = nullptr; + constexpr size_t dnAttributeSize = 4; + constexpr size_t dnAttributeStringSize = dnAttributeSize + 1; + char dnAttribute[dnAttributeStringSize] = { 0 }; mbedtls_x509_crt_init(&mbed_cert); int result = mbedtls_x509_crt_parse(&mbed_cert, Uint8::to_const_uchar(certificate.data()), certificate.size()); VerifyOrExit(result == 0, error = CHIP_ERROR_INTERNAL); - // vendor id is the second element of subject and should be of size 4 - // returning CHIP_ERROR_KEY_NOT_FOUND to sinalize VID is not present in the certificate. - VerifyOrExit(mbed_cert.subject.next->val.p != nullptr && mbed_cert.subject.next->val.len == 4, - error = CHIP_ERROR_KEY_NOT_FOUND); + for (dnIterator = &mbed_cert.subject; dnIterator != nullptr; dnIterator = dnIterator->next) + { + if (dnIterator != nullptr && dnIterator->oid.p != nullptr && dnIterator->oid.len == oidAttributeLen && + memcmp(oidAttribute, dnIterator->oid.p, dnIterator->oid.len) == 0 && dnIterator->val.p != nullptr && + dnIterator->val.len == dnAttributeSize) + { + // vendor id is of size 4, we should ensure the string is null terminated before passing in to strtoul to avoid + // undefined behavior + memcpy(dnAttribute, dnIterator->val.p, dnAttributeSize); + dnAttribute[dnAttributeSize] = 0; + VerifyOrExit(ArgParser::ParseInt(dnAttribute, id, 16), error = CHIP_ERROR_INTERNAL); + break; + } + } - // vendor id is of size 4, we should ensure the string is null terminated before passing in to strtoul to avoid undefined - // behavior - mbed_cert.subject.next->val.p[4] = 0; - vid = static_cast(strtoul(reinterpret_cast(mbed_cert.subject.next->val.p), NULL, 16)); + // returning CHIP_ERROR_KEY_NOT_FOUND to indicate that the DN Attribute is not present in the certificate. + VerifyOrExit(dnIterator != nullptr, error = CHIP_ERROR_KEY_NOT_FOUND); exit: _log_mbedTLS_error(result); mbedtls_x509_crt_free(&mbed_cert); #else + (void) oidAttribute; + (void) oidAttributeLen; (void) certificate; - (void) vid; + (void) id; CHIP_ERROR error = CHIP_ERROR_NOT_IMPLEMENTED; #endif // defined(MBEDTLS_X509_CRT_PARSE_C) return error; } +} // namespace + +CHIP_ERROR ExtractDNAttributeFromX509Cert(MatterOid matterOid, const ByteSpan & certificate, uint16_t & id) +{ + constexpr uint8_t sOID_AttributeType_ChipVendorId[] = { 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x02, 0x01 }; + constexpr uint8_t sOID_AttributeType_ChipProductId[] = { 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x02, 0x02 }; + + switch (matterOid) + { + case MatterOid::kVendorId: + id = VendorId::NotSpecified; + return ExtractDNAttributeFromX509Cert(sOID_AttributeType_ChipVendorId, sizeof(sOID_AttributeType_ChipVendorId), certificate, + id); + case MatterOid::kProductId: + id = 0; // PID not specified value + return ExtractDNAttributeFromX509Cert(sOID_AttributeType_ChipProductId, sizeof(sOID_AttributeType_ChipProductId), + certificate, id); + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } +} + } // namespace Crypto } // namespace chip diff --git a/src/crypto/tests/CHIPCryptoPALTest.cpp b/src/crypto/tests/CHIPCryptoPALTest.cpp index d22e12f17f8988..df2a737913afd6 100644 --- a/src/crypto/tests/CHIPCryptoPALTest.cpp +++ b/src/crypto/tests/CHIPCryptoPALTest.cpp @@ -1928,7 +1928,7 @@ static void TestVID_x509Extraction(nlTestSuite * inSuite, void * inContext) HeapChecker heapChecker(inSuite); CHIP_ERROR err = CHIP_NO_ERROR; - VendorId vid; + uint16_t vid; /* credentials/test/attestation/Chip-Test-DAC-FFF1-8000-000A-Cert.pem -----BEGIN CERTIFICATE----- @@ -1945,7 +1945,7 @@ static void TestVID_x509Extraction(nlTestSuite * inSuite, void * inContext) OS8H8W2E/ctS268o19k= -----END CERTIFICATE----- */ - VendorId expectedVid = (VendorId) 0xFFF1; + uint16_t expectedVid = 0xFFF1; static const uint8_t sDacCertificate[] = { 0x30, 0x82, 0x01, 0xEA, 0x30, 0x82, 0x01, 0x8F, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x08, 0x05, 0x1A, 0x69, 0xE5, 0xE7, 0x80, 0x34, 0x3E, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02, 0x30, 0x46, 0x31, 0x18, 0x30, @@ -1975,14 +1975,78 @@ static void TestVID_x509Extraction(nlTestSuite * inSuite, void * inContext) }; ByteSpan cert(sDacCertificate); - err = ExtractVIDFromX509Cert(cert, vid); + err = ExtractDNAttributeFromX509Cert(MatterOid::kVendorId, cert, vid); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(inSuite, vid == expectedVid); // Test scenario where Certificate does not contain a Vendor ID field err = GetTestCert(TestCert::kNode01_01, TestCertLoadFlags::kDERForm, cert); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); - err = ExtractVIDFromX509Cert(cert, vid); + err = ExtractDNAttributeFromX509Cert(MatterOid::kVendorId, cert, vid); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_KEY_NOT_FOUND); +} + +static void TestPID_x509Extraction(nlTestSuite * inSuite, void * inContext) +{ + using namespace TestCerts; + + HeapChecker heapChecker(inSuite); + CHIP_ERROR err = CHIP_NO_ERROR; + uint16_t pid; + /* + credentials/test/attestation/Chip-Test-DAC-FFF1-8000-000A-Cert.pem + -----BEGIN CERTIFICATE----- + MIIB6jCCAY+gAwIBAgIIBRpp5eeAND4wCgYIKoZIzj0EAwIwRjEYMBYGA1UEAwwP + TWF0dGVyIFRlc3QgUEFJMRQwEgYKKwYBBAGConwCAQwERkZGMTEUMBIGCisGAQQB + gqJ8AgIMBDgwMDAwIBcNMjEwNjI4MTQyMzQzWhgPOTk5OTEyMzEyMzU5NTlaMEsx + HTAbBgNVBAMMFE1hdHRlciBUZXN0IERBQyAwMDBBMRQwEgYKKwYBBAGConwCAQwE + RkZGMTEUMBIGCisGAQQBgqJ8AgIMBDgwMDAwWTATBgcqhkjOPQIBBggqhkjOPQMB + BwNCAAR6hFivu5vNFeGa3NJm9mycL2B8dHR6NfgPN+EYEz+A8XYBEyePkfFaoPf4 + eTIJT+aftyhoqB4ml5s2izO1VDEDo2AwXjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB + /wQEAwIHgDAdBgNVHQ4EFgQU1a2yuIOOyAc8R3LcfoeX/rsjs64wHwYDVR0jBBgw + FoAUhPUd/57M2ik1lEhSDoXxKS2j7dcwCgYIKoZIzj0EAwIDSQAwRgIhAPL+Fnlk + P0xbynYuijQV7VEwBvzQUtpQbWLYvVFeN70IAiEAvi20eqszdReOEkmgeSCgrG6q + OS8H8W2E/ctS268o19k= + -----END CERTIFICATE----- + */ + uint16_t expectedPid = 0x8000u; + static const uint8_t sDacCertificate[] = { + 0x30, 0x82, 0x01, 0xEA, 0x30, 0x82, 0x01, 0x8F, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x08, 0x05, 0x1A, 0x69, 0xE5, 0xE7, + 0x80, 0x34, 0x3E, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02, 0x30, 0x46, 0x31, 0x18, 0x30, + 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x0F, 0x4D, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, + 0x50, 0x41, 0x49, 0x31, 0x14, 0x30, 0x12, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x02, 0x01, 0x0C, + 0x04, 0x46, 0x46, 0x46, 0x31, 0x31, 0x14, 0x30, 0x12, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x02, + 0x02, 0x0C, 0x04, 0x38, 0x30, 0x30, 0x30, 0x30, 0x20, 0x17, 0x0D, 0x32, 0x31, 0x30, 0x36, 0x32, 0x38, 0x31, 0x34, 0x32, + 0x33, 0x34, 0x33, 0x5A, 0x18, 0x0F, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, + 0x5A, 0x30, 0x4B, 0x31, 0x1D, 0x30, 0x1B, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x14, 0x4D, 0x61, 0x74, 0x74, 0x65, 0x72, + 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x44, 0x41, 0x43, 0x20, 0x30, 0x30, 0x30, 0x41, 0x31, 0x14, 0x30, 0x12, 0x06, 0x0A, + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x02, 0x01, 0x0C, 0x04, 0x46, 0x46, 0x46, 0x31, 0x31, 0x14, 0x30, 0x12, + 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0xA2, 0x7C, 0x02, 0x02, 0x0C, 0x04, 0x38, 0x30, 0x30, 0x30, 0x30, 0x59, + 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, + 0x07, 0x03, 0x42, 0x00, 0x04, 0x7A, 0x84, 0x58, 0xAF, 0xBB, 0x9B, 0xCD, 0x15, 0xE1, 0x9A, 0xDC, 0xD2, 0x66, 0xF6, 0x6C, + 0x9C, 0x2F, 0x60, 0x7C, 0x74, 0x74, 0x7A, 0x35, 0xF8, 0x0F, 0x37, 0xE1, 0x18, 0x13, 0x3F, 0x80, 0xF1, 0x76, 0x01, 0x13, + 0x27, 0x8F, 0x91, 0xF1, 0x5A, 0xA0, 0xF7, 0xF8, 0x79, 0x32, 0x09, 0x4F, 0xE6, 0x9F, 0xB7, 0x28, 0x68, 0xA8, 0x1E, 0x26, + 0x97, 0x9B, 0x36, 0x8B, 0x33, 0xB5, 0x54, 0x31, 0x03, 0xA3, 0x60, 0x30, 0x5E, 0x30, 0x0C, 0x06, 0x03, 0x55, 0x1D, 0x13, + 0x01, 0x01, 0xFF, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0E, 0x06, 0x03, 0x55, 0x1D, 0x0F, 0x01, 0x01, 0xFF, 0x04, 0x04, 0x03, + 0x02, 0x07, 0x80, 0x30, 0x1D, 0x06, 0x03, 0x55, 0x1D, 0x0E, 0x04, 0x16, 0x04, 0x14, 0xD5, 0xAD, 0xB2, 0xB8, 0x83, 0x8E, + 0xC8, 0x07, 0x3C, 0x47, 0x72, 0xDC, 0x7E, 0x87, 0x97, 0xFE, 0xBB, 0x23, 0xB3, 0xAE, 0x30, 0x1F, 0x06, 0x03, 0x55, 0x1D, + 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x84, 0xF5, 0x1D, 0xFF, 0x9E, 0xCC, 0xDA, 0x29, 0x35, 0x94, 0x48, 0x52, 0x0E, + 0x85, 0xF1, 0x29, 0x2D, 0xA3, 0xED, 0xD7, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02, 0x03, + 0x49, 0x00, 0x30, 0x46, 0x02, 0x21, 0x00, 0xF2, 0xFE, 0x16, 0x79, 0x64, 0x3F, 0x4C, 0x5B, 0xCA, 0x76, 0x2E, 0x8A, 0x34, + 0x15, 0xED, 0x51, 0x30, 0x06, 0xFC, 0xD0, 0x52, 0xDA, 0x50, 0x6D, 0x62, 0xD8, 0xBD, 0x51, 0x5E, 0x37, 0xBD, 0x08, 0x02, + 0x21, 0x00, 0xBE, 0x2D, 0xB4, 0x7A, 0xAB, 0x33, 0x75, 0x17, 0x8E, 0x12, 0x49, 0xA0, 0x79, 0x20, 0xA0, 0xAC, 0x6E, 0xAA, + 0x39, 0x2F, 0x07, 0xF1, 0x6D, 0x84, 0xFD, 0xCB, 0x52, 0xDB, 0xAF, 0x28, 0xD7, 0xD9 + }; + ByteSpan cert(sDacCertificate); + + err = ExtractDNAttributeFromX509Cert(MatterOid::kProductId, cert, pid); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, pid == expectedPid); + + // Test scenario where Certificate does not contain a Vendor ID field + err = GetTestCert(TestCert::kNode01_01, TestCertLoadFlags::kDERForm, cert); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + err = ExtractDNAttributeFromX509Cert(MatterOid::kProductId, cert, pid); NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_KEY_NOT_FOUND); } @@ -2050,6 +2114,7 @@ static const nlTest sTests[] = { NL_TEST_DEF("Test Subject Key Id Extraction from x509 Certificate", TestSKID_x509Extraction), NL_TEST_DEF("Test Authority Key Id Extraction from x509 Certificate", TestAKID_x509Extraction), NL_TEST_DEF("Test Vendor ID Extraction from x509 Attestation Certificate", TestVID_x509Extraction), + NL_TEST_DEF("Test Product ID Extraction from x509 Attestation Certificate", TestPID_x509Extraction), NL_TEST_SENTINEL() };