blob: 3c67fb4c2d0654b7c57bee9d0d746bfafd0f2666 [file]
/*
*
* Copyright (c) 2026 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pw_unit_test/framework.h>
#include <app/ClusterStateCache.h>
#include <app/ConcreteAttributePath.h>
#include <app/ReadClient.h>
#include <app/data-model/Encode.h>
#include <controller/CHIPDeviceController.h>
#include <controller/CommissioningDelegate.h>
#include <controller/tests/DeviceCommissionerTestAccess.h>
#include <platform/CHIPDeviceLayer.h>
#include <clusters/OperationalCredentials/Attributes.h>
#include <clusters/OperationalCredentials/ClusterId.h>
#include <clusters/OperationalCredentials/Enums.h>
#include <clusters/OperationalCredentials/Structs.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::Controller;
using namespace chip::Testing;
namespace {
class MockClusterStateCache : public ClusterStateCache
{
public:
MockClusterStateCache() : ClusterStateCache(mCallback) {}
template <typename AttrType>
CHIP_ERROR SetAttribute(const ConcreteAttributePath & path, const AttrType & data)
{
Platform::ScopedMemoryBufferWithSize<uint8_t> handle;
handle.Calloc(3000);
TLV::ScopedBufferTLVWriter writer(std::move(handle), 3000);
ReturnErrorOnFailure(DataModel::Encode(writer, TLV::AnonymousTag(), data));
uint32_t writtenLength = writer.GetLengthWritten();
ReturnErrorOnFailure(writer.Finalize(handle));
TLV::ScopedBufferTLVReader reader;
StatusIB status;
reader.Init(std::move(handle), writtenLength);
ReturnErrorOnFailure(reader.Next());
ReadClient::Callback & cb = GetBufferedCallback();
cb.OnAttributeData(path, &reader, status);
return CHIP_NO_ERROR;
}
private:
class MockCallback : public ClusterStateCache::Callback
{
void OnDone(ReadClient *) override {}
void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override {}
};
MockCallback mCallback;
};
class TestParseOperationalCredentialsInfo : public ::testing::Test
{
public:
static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); }
static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); }
protected:
DeviceCommissioner mCommissioner{};
};
TEST_F(TestParseOperationalCredentialsInfo, LegacyFeatureMapDefaultsToLegacyProfile)
{
auto cache = Platform::MakeUnique<MockClusterStateCache>();
ASSERT_NE(cache, nullptr);
BitFlags<OperationalCredentials::Feature> featureMap;
ConcreteAttributePath featureMapPath(kRootEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::FeatureMap::Id);
ASSERT_EQ(cache->SetAttribute(featureMapPath, *featureMap.RawStorage()), CHIP_NO_ERROR);
DeviceCommissionerTestAccess access(&mCommissioner);
access.SetAttributeCache(Platform::UniquePtr<ClusterStateCache>(cache.release()));
ReadCommissioningInfo info{};
ASSERT_EQ(access.ParseOperationalCredentialsInfo(info), CHIP_NO_ERROR);
EXPECT_FALSE(info.supportsPqcDeviceAttestation);
EXPECT_TRUE(info.paiSupportedAttestationProfiles.Has(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy));
EXPECT_TRUE(info.dacSupportedAttestationProfiles.Has(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy));
}
TEST_F(TestParseOperationalCredentialsInfo, FeatureMapStatusErrorFallsBackToLegacy)
{
using OperationalCredentials::AttestationCryptoProfileBitmap;
auto cache = Platform::MakeUnique<MockClusterStateCache>();
ASSERT_NE(cache, nullptr);
// An unavailable FeatureMap must select pre-PQC attestation instead of failing commissioning.
ConcreteDataAttributePath featureMapPath(kRootEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::FeatureMap::Id);
ReadClient::Callback & callback = cache->GetBufferedCallback();
callback.OnAttributeData(featureMapPath, nullptr, StatusIB(Protocols::InteractionModel::Status::UnsupportedAttribute));
DeviceCommissionerTestAccess access(&mCommissioner);
access.SetAttributeCache(Platform::UniquePtr<ClusterStateCache>(cache.release()));
ReadCommissioningInfo info{};
info.supportsPqcDeviceAttestation = true;
info.paiSupportedAttestationProfiles.Set(AttestationCryptoProfileBitmap::kSupportsMlDsa65);
info.dacSupportedAttestationProfiles.Set(AttestationCryptoProfileBitmap::kSupportsMlDsa44);
ASSERT_EQ(access.ParseOperationalCredentialsInfo(info), CHIP_NO_ERROR);
EXPECT_FALSE(info.supportsPqcDeviceAttestation);
EXPECT_EQ(info.paiSupportedAttestationProfiles.Raw(),
BitMask<AttestationCryptoProfileBitmap>(AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy).Raw());
EXPECT_EQ(info.dacSupportedAttestationProfiles.Raw(),
BitMask<AttestationCryptoProfileBitmap>(AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy).Raw());
}
TEST_F(TestParseOperationalCredentialsInfo, PqcFeaturePreservesIndependentPaiAndDacProfiles)
{
auto cache = Platform::MakeUnique<MockClusterStateCache>();
ASSERT_NE(cache, nullptr);
BitFlags<OperationalCredentials::Feature> featureMap(OperationalCredentials::Feature::kPQCDeviceAttestation);
ConcreteAttributePath featureMapPath(kRootEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::FeatureMap::Id);
ASSERT_EQ(cache->SetAttribute(featureMapPath, *featureMap.RawStorage()), CHIP_NO_ERROR);
OperationalCredentials::Structs::PQCDeviceAttestationProfileStruct::Type profileSupport;
profileSupport.PAASupportedProfiles = BitMask<OperationalCredentials::AttestationCryptoProfileBitmap>(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy,
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsMlDsa44);
profileSupport.PAISupportedProfiles = BitMask<OperationalCredentials::AttestationCryptoProfileBitmap>(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy,
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsMlDsa65);
profileSupport.DACSupportedProfiles = BitMask<OperationalCredentials::AttestationCryptoProfileBitmap>(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy,
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsMlDsa44);
ConcreteAttributePath profilePath(kRootEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::PQCDeviceAttestationProfile::Id);
ASSERT_EQ(cache->SetAttribute(profilePath, profileSupport), CHIP_NO_ERROR);
DeviceCommissionerTestAccess access(&mCommissioner);
access.SetAttributeCache(Platform::UniquePtr<ClusterStateCache>(cache.release()));
ReadCommissioningInfo info{};
ASSERT_EQ(access.ParseOperationalCredentialsInfo(info), CHIP_NO_ERROR);
EXPECT_TRUE(info.supportsPqcDeviceAttestation);
EXPECT_TRUE(info.paiSupportedAttestationProfiles.Has(OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsMlDsa65));
EXPECT_FALSE(
info.paiSupportedAttestationProfiles.Has(OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsMlDsa44));
EXPECT_TRUE(info.dacSupportedAttestationProfiles.Has(OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsMlDsa44));
EXPECT_FALSE(
info.dacSupportedAttestationProfiles.Has(OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsMlDsa65));
}
TEST_F(TestParseOperationalCredentialsInfo, RetainsLargestPaaProfileAndClearsItOnLegacyFallback)
{
using Profile = OperationalCredentials::AttestationCryptoProfileEnum;
using Profiles = OperationalCredentials::AttestationCryptoProfileBitmap;
for (const uint16_t bitmap : { uint16_t(1), uint16_t(3), uint16_t(5), uint16_t(7), uint16_t(9) })
{
auto cache = Platform::MakeUnique<MockClusterStateCache>();
auto * cachePtr = cache.get();
const ConcreteAttributePath featurePath(kRootEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::FeatureMap::Id);
ASSERT_EQ(cache->SetAttribute(featurePath, uint32_t(1)), CHIP_NO_ERROR);
OperationalCredentials::Structs::PQCDeviceAttestationProfileStruct::Type profiles;
profiles.PAASupportedProfiles = BitMask<Profiles>(bitmap);
profiles.PAISupportedProfiles = BitMask<Profiles>(Profiles::kSupportsEcdsaMatterLegacy, Profiles::kSupportsMlDsa44);
profiles.DACSupportedProfiles = BitMask<Profiles>(Profiles::kSupportsEcdsaMatterLegacy);
ASSERT_EQ(cache->SetAttribute(ConcreteAttributePath(kRootEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::PQCDeviceAttestationProfile::Id),
profiles),
CHIP_NO_ERROR);
DeviceCommissionerTestAccess access(&mCommissioner);
access.SetAttributeCache(Platform::UniquePtr<ClusterStateCache>(cache.release()));
ReadCommissioningInfo info;
ASSERT_EQ(access.ParseOperationalCredentialsInfo(info), CHIP_NO_ERROR);
if (bitmap == 9)
{
EXPECT_FALSE(access.PaaAttestationIssuerProfile().HasValue());
}
else
{
ASSERT_TRUE(access.PaaAttestationIssuerProfile().HasValue());
EXPECT_EQ(access.PaaAttestationIssuerProfile().Value(),
bitmap >= 5 ? Profile::kMlDsa65
: bitmap == 3 ? Profile::kMlDsa44
: Profile::kEcdsaMatterLegacy);
}
ASSERT_EQ(cachePtr->SetAttribute(featurePath, uint32_t(0)), CHIP_NO_ERROR);
ASSERT_EQ(access.ParseOperationalCredentialsInfo(info), CHIP_NO_ERROR);
EXPECT_FALSE(access.PaaAttestationIssuerProfile().HasValue());
}
}
TEST_F(TestParseOperationalCredentialsInfo, PqcFeatureFallsBackToLegacyWhenDacDoesNotAdvertiseLegacy)
{
auto cache = Platform::MakeUnique<MockClusterStateCache>();
ASSERT_NE(cache, nullptr);
BitFlags<OperationalCredentials::Feature> featureMap(OperationalCredentials::Feature::kPQCDeviceAttestation);
ConcreteAttributePath featureMapPath(kRootEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::FeatureMap::Id);
ASSERT_EQ(cache->SetAttribute(featureMapPath, *featureMap.RawStorage()), CHIP_NO_ERROR);
OperationalCredentials::Structs::PQCDeviceAttestationProfileStruct::Type profileSupport;
profileSupport.PAASupportedProfiles = BitMask<OperationalCredentials::AttestationCryptoProfileBitmap>(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy,
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsMlDsa44);
profileSupport.PAISupportedProfiles = BitMask<OperationalCredentials::AttestationCryptoProfileBitmap>(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy,
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsMlDsa44);
profileSupport.DACSupportedProfiles = BitMask<OperationalCredentials::AttestationCryptoProfileBitmap>(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsMlDsa44);
ConcreteAttributePath profilePath(kRootEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::PQCDeviceAttestationProfile::Id);
ASSERT_EQ(cache->SetAttribute(profilePath, profileSupport), CHIP_NO_ERROR);
DeviceCommissionerTestAccess access(&mCommissioner);
access.SetAttributeCache(Platform::UniquePtr<ClusterStateCache>(cache.release()));
ReadCommissioningInfo info{};
ASSERT_EQ(access.ParseOperationalCredentialsInfo(info), CHIP_NO_ERROR);
EXPECT_FALSE(info.supportsPqcDeviceAttestation);
EXPECT_TRUE(info.paiSupportedAttestationProfiles.Has(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy));
EXPECT_TRUE(info.dacSupportedAttestationProfiles.Has(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy));
}
TEST_F(TestParseOperationalCredentialsInfo, PqcFeatureFallsBackToLegacyWhenNoPqcIssuerIsAdvertised)
{
auto cache = Platform::MakeUnique<MockClusterStateCache>();
ASSERT_NE(cache, nullptr);
BitFlags<OperationalCredentials::Feature> featureMap(OperationalCredentials::Feature::kPQCDeviceAttestation);
ConcreteAttributePath featureMapPath(kRootEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::FeatureMap::Id);
ASSERT_EQ(cache->SetAttribute(featureMapPath, *featureMap.RawStorage()), CHIP_NO_ERROR);
OperationalCredentials::Structs::PQCDeviceAttestationProfileStruct::Type profileSupport;
profileSupport.PAASupportedProfiles = BitMask<OperationalCredentials::AttestationCryptoProfileBitmap>(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy);
profileSupport.PAISupportedProfiles = BitMask<OperationalCredentials::AttestationCryptoProfileBitmap>(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy);
profileSupport.DACSupportedProfiles = BitMask<OperationalCredentials::AttestationCryptoProfileBitmap>(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy);
ConcreteAttributePath profilePath(kRootEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::PQCDeviceAttestationProfile::Id);
ASSERT_EQ(cache->SetAttribute(profilePath, profileSupport), CHIP_NO_ERROR);
DeviceCommissionerTestAccess access(&mCommissioner);
access.SetAttributeCache(Platform::UniquePtr<ClusterStateCache>(cache.release()));
ReadCommissioningInfo info{};
ASSERT_EQ(access.ParseOperationalCredentialsInfo(info), CHIP_NO_ERROR);
EXPECT_FALSE(info.supportsPqcDeviceAttestation);
EXPECT_TRUE(info.paiSupportedAttestationProfiles.Has(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy));
EXPECT_TRUE(info.dacSupportedAttestationProfiles.Has(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy));
}
TEST_F(TestParseOperationalCredentialsInfo, PqcFeatureFallsBackToLegacyWhenProfileAttributeIsMissing)
{
auto cache = Platform::MakeUnique<MockClusterStateCache>();
ASSERT_NE(cache, nullptr);
BitFlags<OperationalCredentials::Feature> featureMap(OperationalCredentials::Feature::kPQCDeviceAttestation);
ConcreteAttributePath featureMapPath(kRootEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::FeatureMap::Id);
ASSERT_EQ(cache->SetAttribute(featureMapPath, *featureMap.RawStorage()), CHIP_NO_ERROR);
DeviceCommissionerTestAccess access(&mCommissioner);
access.SetAttributeCache(Platform::UniquePtr<ClusterStateCache>(cache.release()));
ReadCommissioningInfo info{};
ASSERT_EQ(access.ParseOperationalCredentialsInfo(info), CHIP_NO_ERROR);
EXPECT_FALSE(info.supportsPqcDeviceAttestation);
EXPECT_TRUE(info.paiSupportedAttestationProfiles.Has(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy));
EXPECT_TRUE(info.dacSupportedAttestationProfiles.Has(
OperationalCredentials::AttestationCryptoProfileBitmap::kSupportsEcdsaMatterLegacy));
}
} // namespace