diff --git a/src/credentials/CertificationDeclaration.cpp b/src/credentials/CertificationDeclaration.cpp index 4ebaba921b658d..43694bac1c25f3 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)); @@ -174,6 +176,128 @@ CHIP_ERROR DecodeCertificationElements(const ByteSpan & encodedCertElements, Cer return CHIP_NO_ERROR; } +CHIP_ERROR CertificationElementsDecoder::DecodeCertificationElements(const ByteSpan & encodedCertElements) +{ + CHIP_ERROR err; + TLVReader reader; + TLVType outerContainer1, outerContainer2; + + VerifyOrReturnError(encodedCertElements.size() <= kMaxCMSSignedCDMessage, CHIP_ERROR_INVALID_ARGUMENT); + + reader.Init(encodedCertElements); + + ReturnErrorOnFailure(reader.Next(kTLVType_Structure, AnonymousTag)); + + ReturnErrorOnFailure(reader.EnterContainer(outerContainer1)); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_FormatVersion))); + ReturnErrorOnFailure(reader.Get(FormatVersion)); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_VendorId))); + ReturnErrorOnFailure(reader.Get(VendorId)); + + ReturnErrorOnFailure(reader.Next(kTLVType_Array, ContextTag(kTag_ProductIdArray))); + ReturnErrorOnFailure(reader.EnterContainer(outerContainer2)); + + ContainsPID = true; + + ReturnErrorOnFailure(reader.ExitContainer(outerContainer2)); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_DeviceTypeId))); + ReturnErrorOnFailure(reader.Get(DeviceTypeId)); + + ReturnErrorOnFailure(reader.Next(kTLVType_UTF8String, ContextTag(kTag_CertificateId))); + ReturnErrorOnFailure(reader.GetString(CertificateId, sizeof(CertificateId))); + VerifyOrReturnError(strlen(CertificateId) == kCertificateIdLength, CHIP_ERROR_INVALID_TLV_ELEMENT); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_SecurityLevel))); + ReturnErrorOnFailure(reader.Get(SecurityLevel)); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_SecurityInformation))); + ReturnErrorOnFailure(reader.Get(SecurityInformation)); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_VersionNumber))); + ReturnErrorOnFailure(reader.Get(VersionNumber)); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_CertificationType))); + ReturnErrorOnFailure(reader.Get(CertificationType)); + + 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(DACOriginVendorId)); + + ReturnErrorOnFailure(reader.Next(ContextTag(kTag_DACOriginProductId))); + ReturnErrorOnFailure(reader.Get(DACOriginProductId)); + + 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(outerContainer1)); + + 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..62889f30714ad3 100644 --- a/src/credentials/CertificationDeclaration.h +++ b/src/credentials/CertificationDeclaration.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -62,6 +63,36 @@ struct CertificationElements bool DACOriginVIDandPIDPresent; }; +class CertificationElementsDecoder +{ +public: + uint16_t FormatVersion; + uint16_t VendorId; + bool ContainsPID; + uint32_t DeviceTypeId; + char CertificateId[kCertificateIdLength + 1]; + uint8_t SecurityLevel; + uint16_t SecurityInformation; + uint16_t VersionNumber; + uint8_t CertificationType; + uint16_t DACOriginVendorId; + uint16_t DACOriginProductId; + bool DACOriginVIDandPIDPresent; + + CHIP_ERROR DecodeCertificationElements(const ByteSpan & encodedCertElements); + 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. * @@ -70,7 +101,8 @@ struct CertificationElements * * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise **/ -CHIP_ERROR EncodeCertificationElements(const CertificationElements & certElements, MutableByteSpan & encodedCertElements); +CHIP_ERROR +EncodeCertificationElements(const CertificationElements & certElements, MutableByteSpan & encodedCertElements); /** * @brief Decode certification elements from TLV encoded structure. diff --git a/src/credentials/DeviceAttestationVerifier.cpp b/src/credentials/DeviceAttestationVerifier.cpp index 3b1aaaf16af1a9..2467b0432d60e7 100644 --- a/src/credentials/DeviceAttestationVerifier.cpp +++ b/src/credentials/DeviceAttestationVerifier.cpp @@ -54,17 +54,12 @@ class UnimplementedDACVerifier : public DeviceAttestationVerifier } AttestationVerificationResult ValidateCertificateDeclarationPayload(const ByteSpan & certDeclBuffer, - const ByteSpan & firmwareInfo, uint16_t clusterVendorId, - uint16_t clusterProductId, uint16_t dacVendorId, - uint16_t dacProductId, uint16_t paiProductId) override + const ByteSpan & firmwareInfo, + DeviceInfoForAttestation deviceInfo) override { (void) certDeclBuffer; (void) firmwareInfo; - (void) clusterVendorId; - (void) clusterProductId; - (void) dacVendorId; - (void) dacProductId; - (void) paiProductId; + (void) deviceInfo; return AttestationVerificationResult::kNotImplemented; } }; diff --git a/src/credentials/DeviceAttestationVerifier.h b/src/credentials/DeviceAttestationVerifier.h index c831b0d35044ec..e1714548ec18bf 100644 --- a/src/credentials/DeviceAttestationVerifier.h +++ b/src/credentials/DeviceAttestationVerifier.h @@ -78,6 +78,22 @@ enum CertificateType : uint8_t kPAI = 2, }; +struct DeviceInfoForAttestation +{ + // Vendor ID reported by device in Basic Information cluster + uint16_t vendorId = 0; + // Product ID reported by device in Basic Information cluster + uint16_t productId = 0; + // Vendor ID from DAC/PAI/PAA + uint16_t dacVendorId = 0; + // Product ID from DAC + uint16_t dacProductId = 0; + // Product ID from PAI cert (0 if absent) + uint16_t paiProductId = 0; + // Vendor ID from PAA cert (0 if absent) + uint16_t paaProductId = 0; +}; + class DeviceAttestationVerifier { public: @@ -125,20 +141,14 @@ class DeviceAttestationVerifier * * @param[in] certDeclBuffer A ByteSpan with the Certification Declaration content. * @param[in] firmwareInfo A ByteSpan with the Firmware Information content. - * @param[in] clusterVendorId - * @param[in] clusterProductId - * @param[in] dacVendorId - * @param[in] dacProductId - * @param[in] paiProductId + * @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, - uint16_t clusterVendorId, uint16_t clusterProductId, - uint16_t dacVendorId, uint16_t dacProductId, - uint16_t paiProductId) = 0; + DeviceInfoForAttestation deviceInfo) = 0; // TODO: Validate Firmware Information diff --git a/src/credentials/examples/DeviceAttestationVerifierExample.cpp b/src/credentials/examples/DeviceAttestationVerifierExample.cpp index 7a56eb321ed4a6..4026ce8e40d5c9 100644 --- a/src/credentials/examples/DeviceAttestationVerifierExample.cpp +++ b/src/credentials/examples/DeviceAttestationVerifierExample.cpp @@ -35,19 +35,6 @@ namespace Examples { namespace { -bool IsIDContainedInArray(uint16_t id, const uint16_t * array, uint8_t arraySize) -{ - uint8_t arrayIdx; - for (arrayIdx = 0; arrayIdx < arraySize; ++arrayIdx) - { - if (array[arrayIdx] == id) - { - return true; - } - } - return false; -} - CHIP_ERROR GetProductAttestationAuthorityCert(const ByteSpan & skid, MutableByteSpan & outDacBuffer) { struct PAALookupTable @@ -215,9 +202,8 @@ class ExampleDACVerifier : public DeviceAttestationVerifier ByteSpan & certDeclBuffer) override; AttestationVerificationResult ValidateCertificateDeclarationPayload(const ByteSpan & certDeclBuffer, - const ByteSpan & firmwareInfo, uint16_t clusterVendorId, - uint16_t clusterProductId, uint16_t dacVendorId, - uint16_t dacProductId, uint16_t paiProductId) override; + const ByteSpan & firmwareInfo, + DeviceInfoForAttestation deviceInfo) override; }; AttestationVerificationResult ExampleDACVerifier::VerifyAttestationInformation(const ByteSpan & attestationInfoBuffer, @@ -231,20 +217,20 @@ AttestationVerificationResult ExampleDACVerifier::VerifyAttestationInformation(c // match DAC and PAI VIDs if (!paiCertDerBuffer.empty()) { - VendorId paiVid; - VendorId dacVid; + uint16_t paiVid; + uint16_t dacVid; - CHIP_ERROR error = ExtractVIDFromX509Cert(paiCertDerBuffer, paiVid); + CHIP_ERROR error = ExtractDNAttributeFromX509Cert(MatterOid::kVendorId, 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, + VerifyOrReturnError(ExtractDNAttributeFromX509Cert(MatterOid::kVendorId, dacCertDerBuffer, dacVid) == CHIP_NO_ERROR, AttestationVerificationResult::kDacFormatInvalid); VerifyOrReturnError(paiVid == dacVid, AttestationVerificationResult::kDacVendorIdMismatch); - dacVendorId = dacVid; + dacVendorId = static_cast(dacVid); } } @@ -277,6 +263,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; + 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; @@ -291,22 +289,28 @@ AttestationVerificationResult ExampleDACVerifier::VerifyAttestationInformation(c VerifyOrReturnError(attestationNonceSpan.data_equal(attestationNonce), AttestationVerificationResult::kAttestationNonceMismatch); - AttestationVerificationResult error; + AttestationVerificationResult attestationError; ByteSpan certificationDeclarationPayload; - error = ValidateCertificationDeclarationSignature(certificationDeclarationSpan, certificationDeclarationPayload); - VerifyOrReturnError(error == AttestationVerificationResult::kSuccess, error); - // TODO: Retrieve these IDs from Basic Information Cluster - uint16_t clusterVendorId = 0xFFF1; - uint16_t clusterProductId = 0x8000; - - uint16_t dacProductId = 0xFFFFu; // not specified - uint16_t paiProductId = 0xFFFFu; // not specified - VerifyOrReturnError(ExtractPIDFromX509Cert(dacCertDerBuffer, dacProductId) == CHIP_NO_ERROR, + 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, + }; + VerifyOrReturnError(ExtractDNAttributeFromX509Cert(MatterOid::kProductId, dacCertDerBuffer, deviceInfo.dacProductId) == + CHIP_NO_ERROR, AttestationVerificationResult::kDacFormatInvalid); - VerifyOrReturnError(ExtractPIDFromX509Cert(paiCertDerBuffer, paiProductId) == CHIP_NO_ERROR, + // 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, clusterVendorId, - clusterProductId, dacVendorId, dacProductId, paiProductId); + error = ExtractDNAttributeFromX509Cert(MatterOid::kProductId, paa, deviceInfo.paaProductId); + VerifyOrReturnError(error == CHIP_NO_ERROR || error == CHIP_ERROR_KEY_NOT_FOUND, + AttestationVerificationResult::kPaiFormatInvalid); + return ValidateCertificateDeclarationPayload(certificationDeclarationPayload, firmwareInfoSpan, deviceInfo); } AttestationVerificationResult ExampleDACVerifier::ValidateCertificationDeclarationSignature(const ByteSpan & cmsEnvelopeBuffer, @@ -328,13 +332,12 @@ AttestationVerificationResult ExampleDACVerifier::ValidateCertificationDeclarati return AttestationVerificationResult::kSuccess; } -AttestationVerificationResult -ExampleDACVerifier::ValidateCertificateDeclarationPayload(const ByteSpan & certDeclBuffer, const ByteSpan & firmwareInfo, - uint16_t clusterVendorId, uint16_t clusterProductId, uint16_t dacVendorId, - uint16_t dacProductId, uint16_t paiProductId) +AttestationVerificationResult ExampleDACVerifier::ValidateCertificateDeclarationPayload(const ByteSpan & certDeclBuffer, + const ByteSpan & firmwareInfo, + DeviceInfoForAttestation deviceInfo) { - CertificationElements decodedElements; - VerifyOrReturnError(DecodeCertificationElements(certDeclBuffer, decodedElements) == CHIP_NO_ERROR, + CertificationElementsDecoder cdElementsDecoder; + VerifyOrReturnError(cdElementsDecoder.DecodeCertificationElements(certDeclBuffer) == CHIP_NO_ERROR, AttestationVerificationResult::kCertificationDeclarationInvalidFormat); if (!firmwareInfo.empty()) @@ -344,44 +347,60 @@ ExampleDACVerifier::ValidateCertificateDeclarationPayload(const ByteSpan & certD // The vendor_id field in the Certification Declaration SHALL match the VendorID attribute found in the Basic Information // cluster - VerifyOrReturnError(decodedElements.VendorId == clusterVendorId, + VerifyOrReturnError(cdElementsDecoder.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(IsIDContainedInArray(clusterProductId, decodedElements.ProductIds, decodedElements.ProductIdsCount), + // 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 (decodedElements.DACOriginVIDandPIDPresent) + if (cdElementsDecoder.DACOriginVIDandPIDPresent) { - // The Vendor ID (VID) subject DN in the DAC SHALL match the dac_origin_vendor_id field in the Certification Declaration. - VerifyOrReturnError(dacVendorId == decodedElements.DACOriginVendorId, + // The Vendor ID (VID) subject DN in the DAC SHALL match the dac_origin_vendor_id field in the Certification + // Declaration. + VerifyOrReturnError(deviceInfo.dacVendorId == cdElementsDecoder.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(dacProductId == decodedElements.DACOriginProductId, + // The Product ID (PID) subject DN in the DAC SHALL match the dac_origin_product_id field in the Certification + // Declaration. + VerifyOrReturnError(deviceInfo.dacProductId == cdElementsDecoder.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 (paiProductId != 0xFFFFu) // if PAI PID is present + // 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 == cdElementsDecoder.DACOriginProductId, + AttestationVerificationResult::kCertificationDeclarationInvalidProductId); + } + // The Product ID (PID) subject DN in the PAA, if such a Product ID is present, SHALL match the dac_origin_product_id + // field in the Certification Declaration. + if (deviceInfo.paaProductId != 0) // if PAA PID is present { - VerifyOrReturnError(paiProductId == decodedElements.DACOriginProductId, + VerifyOrReturnError(deviceInfo.paaProductId == cdElementsDecoder.DACOriginProductId, AttestationVerificationResult::kCertificationDeclarationInvalidProductId); } } else { // The Vendor ID (VID) subject DN in the DAC SHALL match the vendor_id field in the Certification Declaration - VerifyOrReturnError(dacVendorId == decodedElements.VendorId, + VerifyOrReturnError(deviceInfo.dacVendorId == cdElementsDecoder.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(IsIDContainedInArray(dacProductId, decodedElements.ProductIds, decodedElements.ProductIdsCount), + 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 (paiProductId != 0xFFFFu) // if PAI PID is present + // 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); + } + // The Product ID (PID) subject DN in the PAA, 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.paaProductId != 0) // if PAA PID is present { - VerifyOrReturnError(IsIDContainedInArray(paiProductId, decodedElements.ProductIds, decodedElements.ProductIdsCount), + VerifyOrReturnError(cdElementsDecoder.IsProductIdIn(certDeclBuffer, deviceInfo.paaProductId), AttestationVerificationResult::kCertificationDeclarationInvalidProductId); } } diff --git a/src/credentials/tests/TestCertificationDeclaration.cpp b/src/credentials/tests/TestCertificationDeclaration.cpp index 61c9e0c612a02d..3875e0b0ef9df8 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)); + + CertificationElementsDecoder certificationElementsDecoder; + NL_TEST_ASSERT(inSuite, certificationElementsDecoder.DecodeCertificationElements(encodedCDPayload) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, certificationElementsDecoder.FormatVersion == testCase.cdElements.FormatVersion); + NL_TEST_ASSERT(inSuite, certificationElementsDecoder.VendorId == testCase.cdElements.VendorId); + NL_TEST_ASSERT(inSuite, certificationElementsDecoder.ContainsPID == true); + 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, certificationElementsDecoder.DeviceTypeId == testCase.cdElements.DeviceTypeId); + NL_TEST_ASSERT( + inSuite, + memcmp(certificationElementsDecoder.CertificateId, testCase.cdElements.CertificateId, kCertificateIdLength) == 0); + NL_TEST_ASSERT(inSuite, certificationElementsDecoder.SecurityLevel == testCase.cdElements.SecurityLevel); + NL_TEST_ASSERT(inSuite, certificationElementsDecoder.SecurityInformation == testCase.cdElements.SecurityInformation); + NL_TEST_ASSERT(inSuite, certificationElementsDecoder.VersionNumber == testCase.cdElements.VersionNumber); + NL_TEST_ASSERT(inSuite, certificationElementsDecoder.CertificationType == testCase.cdElements.CertificationType); + NL_TEST_ASSERT(inSuite, + certificationElementsDecoder.DACOriginVIDandPIDPresent == testCase.cdElements.DACOriginVIDandPIDPresent); + if (certificationElementsDecoder.DACOriginVIDandPIDPresent) + { + NL_TEST_ASSERT(inSuite, certificationElementsDecoder.DACOriginVendorId == testCase.cdElements.DACOriginVendorId); + NL_TEST_ASSERT(inSuite, certificationElementsDecoder.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 99ad6a0c9e4aa0..35ae86c8531375 100644 --- a/src/credentials/tests/TestDeviceAttestationCredentials.cpp +++ b/src/credentials/tests/TestDeviceAttestationCredentials.cpp @@ -282,9 +282,15 @@ static void TestDACVerifierExample_CertDeclarationVerification(nlTestSuite * inS NL_TEST_ASSERT(inSuite, cd_payload.data_equal(ByteSpan(sTestCMS_CDContent))); - attestation_result = default_verifier->ValidateCertificateDeclarationPayload( - cd_payload, ByteSpan(), sTestCMS_CertElements.VendorId, sTestCMS_CertElements.ProductIds[0], sTestCMS_CertElements.VendorId, - sTestCMS_CertElements.ProductIds[0], sTestCMS_CertElements.ProductIds[0]); + DeviceInfoForAttestation deviceInfo{ + .vendorId = sTestCMS_CertElements.VendorId, + .productId = sTestCMS_CertElements.ProductIds[0], + .dacVendorId = sTestCMS_CertElements.VendorId, + .dacProductId = sTestCMS_CertElements.ProductIds[0], + .paiProductId = sTestCMS_CertElements.ProductIds[0], + .paaProductId = sTestCMS_CertElements.ProductIds[0], + }; + 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 4a81574db66903..28792a5076d9bf 100644 --- a/src/crypto/CHIPCryptoPAL.h +++ b/src/crypto/CHIPCryptoPAL.h @@ -1217,15 +1217,16 @@ CHIP_ERROR ExtractSKIDFromX509Cert(const ByteSpan & certificate, MutableByteSpan **/ CHIP_ERROR ExtractAKIDFromX509Cert(const ByteSpan & certificate, MutableByteSpan & akid); -/** - * @brief Extracts the Vendor ID from an X509 Certificate. - **/ -CHIP_ERROR ExtractVIDFromX509Cert(const ByteSpan & certificate, VendorId & vid); +enum class MatterOid +{ + kVendorId, + kProductId, +}; /** - * @brief Extracts the Product ID from an X509 Certificate. + * @brief Extracts one of the IDs listed in MatterOid enum from an X509 Certificate. **/ -CHIP_ERROR ExtractPIDFromX509Cert(const ByteSpan & certificate, uint16_t & pid); +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 2299b441b2043e..e0970845edf193 100644 --- a/src/crypto/CHIPCryptoPALOpenSSL.cpp +++ b/src/crypto/CHIPCryptoPALOpenSSL.cpp @@ -41,6 +41,7 @@ #include #include +#include #include #include #include @@ -1775,17 +1776,18 @@ CHIP_ERROR ExtractAKIDFromX509Cert(const ByteSpan & certificate, MutableByteSpan namespace { -CHIP_ERROR ExtractDNAttributeFromX509Cert(bool isVID, const ByteSpan & certificate, uint16_t & id) +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 char pidNeedle[] = "1.3.6.1.4.1.37244.2.2"; // Matter PID OID - taken from Spec - constexpr size_t needleSize = sizeof(vidNeedle); - char buff[needleSize] = { 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; + + VerifyOrReturnError(oidStringSize == sOidStringSize, CHIP_ERROR_INVALID_ARGUMENT); x509certificate = d2i_X509(NULL, &pCertificate, static_cast(certificate.size())); VerifyOrExit(x509certificate != nullptr, err = CHIP_ERROR_NO_MEMORY); @@ -1799,22 +1801,22 @@ CHIP_ERROR ExtractDNAttributeFromX509Cert(bool isVID, const ByteSpan & certifica 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(isVID ? vidNeedle : pidNeedle, buff, needleSize) == 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); - id = 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); @@ -1824,19 +1826,22 @@ CHIP_ERROR ExtractDNAttributeFromX509Cert(bool isVID, const ByteSpan & certifica } // namespace -CHIP_ERROR ExtractVIDFromX509Cert(const ByteSpan & certificate, VendorId & vid) +CHIP_ERROR ExtractDNAttributeFromX509Cert(MatterOid matterOid, const ByteSpan & certificate, uint16_t & id) { - vid = VendorId::NotSpecified; - uint16_t id; - ReturnErrorOnFailure(ExtractDNAttributeFromX509Cert(true, certificate, id)); - vid = static_cast(id); - return CHIP_NO_ERROR; -} + 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 -CHIP_ERROR ExtractPIDFromX509Cert(const ByteSpan & certificate, uint16_t & pid) -{ - pid = 0xFFFFu; // not specified - return ExtractDNAttributeFromX509Cert(false, certificate, pid); + 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 diff --git a/src/crypto/CHIPCryptoPALmbedTLS.cpp b/src/crypto/CHIPCryptoPALmbedTLS.cpp index 93d025895090a2..fa366cca3895a8 100644 --- a/src/crypto/CHIPCryptoPALmbedTLS.cpp +++ b/src/crypto/CHIPCryptoPALmbedTLS.cpp @@ -46,6 +46,7 @@ #include #include +#include #include #include #include @@ -1407,36 +1408,47 @@ CHIP_ERROR ExtractAKIDFromX509Cert(const ByteSpan & certificate, MutableByteSpan namespace { -CHIP_ERROR ExtractDNAttributeFromX509Cert(bool isVID, const ByteSpan & certificate, uint16_t & id) +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 * asn1IdEntry; + 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 - // product id is the third element of subject and should be of size 4 - // returning CHIP_ERROR_KEY_NOT_FOUND to sinalize VID is not present in the certificate. - asn1IdEntry = isVID ? mbed_cert.subject.next : mbed_cert.subject.next->next; - VerifyOrExit(asn1IdEntry != nullptr && asn1IdEntry->val.p != nullptr && asn1IdEntry->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 - asn1IdEntry->val.p[4] = 0; - id = static_cast(strtoul(reinterpret_cast(asn1IdEntry->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) isVID; + (void) oidAttribute; + (void) oidAttributeLen; (void) certificate; (void) id; CHIP_ERROR error = CHIP_ERROR_NOT_IMPLEMENTED; @@ -1447,19 +1459,24 @@ CHIP_ERROR ExtractDNAttributeFromX509Cert(bool isVID, const ByteSpan & certifica } // namespace -CHIP_ERROR ExtractVIDFromX509Cert(const ByteSpan & certificate, VendorId & vid) +CHIP_ERROR ExtractDNAttributeFromX509Cert(MatterOid matterOid, const ByteSpan & certificate, uint16_t & id) { - vid = VendorId::NotSpecified; - uint16_t id; - ReturnErrorOnFailure(ExtractDNAttributeFromX509Cert(true, certificate, id)); - vid = static_cast(id); - return CHIP_NO_ERROR; -} + 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 }; -CHIP_ERROR ExtractPIDFromX509Cert(const ByteSpan & certificate, uint16_t & pid) -{ - pid = 0xFFFFu; // not specified - return ExtractDNAttributeFromX509Cert(false, certificate, pid); + 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 diff --git a/src/crypto/tests/CHIPCryptoPALTest.cpp b/src/crypto/tests/CHIPCryptoPALTest.cpp index 2a79e890011de7..df2a737913afd6 100644 --- a/src/crypto/tests/CHIPCryptoPALTest.cpp +++ b/src/crypto/tests/CHIPCryptoPALTest.cpp @@ -1275,8 +1275,8 @@ static void TestCSR_Gen(nlTestSuite * inSuite, void * inContext) NL_TEST_ASSERT(inSuite, memcmp(pubkey.ConstBytes(), keypair.Pubkey().ConstBytes(), pubkey.Length()) == 0); // Let's corrupt the CSR buffer and make sure it fails to verify - csr[length - 2] = (uint8_t) (csr[length - 2] + 1); - csr[length - 1] = (uint8_t) (csr[length - 1] + 1); + csr[length - 2] = (uint8_t)(csr[length - 2] + 1); + csr[length - 1] = (uint8_t)(csr[length - 1] + 1); NL_TEST_ASSERT(inSuite, VerifyCertificateSigningRequest(csr, length, pubkey) != CHIP_NO_ERROR); } @@ -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,14 @@ 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); } @@ -2039,14 +2039,14 @@ static void TestPID_x509Extraction(nlTestSuite * inSuite, void * inContext) }; ByteSpan cert(sDacCertificate); - err = ExtractPIDFromX509Cert(cert, pid); + 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 = ExtractPIDFromX509Cert(cert, pid); + err = ExtractDNAttributeFromX509Cert(MatterOid::kProductId, cert, pid); NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_KEY_NOT_FOUND); }