blob: 9d610579fecb72a1e838b0e3f602ed8237b64e52 [file] [edit]
/*
*
* 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 <credentials/tests/CHIPAttCert_test_vectors.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, RetainsPaaBitmapAndResetsItOnLegacyFallback)
{
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);
EXPECT_EQ(info.paaSupportedAttestationProfiles.Raw(), bitmap);
ASSERT_EQ(cachePtr->SetAttribute(featurePath, uint32_t(0)), CHIP_NO_ERROR);
ASSERT_EQ(access.ParseOperationalCredentialsInfo(info), CHIP_NO_ERROR);
EXPECT_EQ(info.paaSupportedAttestationProfiles.Raw(), 1u);
}
}
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));
}
TEST_F(TestParseOperationalCredentialsInfo, CertificateRequestsSerializeAndRetainTheCompleteChain)
{
using Profile = OperationalCredentials::AttestationCryptoProfileEnum;
const Credentials::DeviceAttestationChainProfile chain{ Profile::kMlDsa65, Profile::kMlDsa44, Profile::kEcdsaMatterLegacy };
DeviceCommissionerTestAccess access(&mCommissioner);
for (auto type : { Credentials::CertificateType::kPAI, Credentials::CertificateType::kDAC })
{
for (uint16_t segment = 0; segment < 3; ++segment)
{
OperationalCredentials::Commands::CertificateChainRequest::Type request;
ASSERT_EQ(
access.PrepareCertificateChainRequest(type, chain, segment == 0 ? NullOptional : MakeOptional(segment), request),
CHIP_NO_ERROR);
uint8_t buffer[128];
TLV::TLVWriter writer;
writer.Init(buffer);
ASSERT_EQ(request.Encode(writer, TLV::AnonymousTag()), CHIP_NO_ERROR);
ASSERT_EQ(writer.Finalize(), CHIP_NO_ERROR);
TLV::TLVReader reader;
reader.Init(buffer, writer.GetLengthWritten());
ASSERT_EQ(reader.Next(), CHIP_NO_ERROR);
OperationalCredentials::Commands::CertificateChainRequest::DecodableType decoded;
ASSERT_EQ(decoded.Decode(reader), CHIP_NO_ERROR);
ASSERT_TRUE(decoded.cryptoProfile.HasValue());
EXPECT_EQ(decoded.cryptoProfile.Value().PAACrypto, chain.PAACrypto);
EXPECT_EQ(decoded.cryptoProfile.Value().PAICrypto, chain.PAICrypto);
EXPECT_EQ(decoded.cryptoProfile.Value().DACCrypto, chain.DACCrypto);
EXPECT_EQ(decoded.segmentID.HasValue(), segment != 0);
EXPECT_EQ(decoded.segmentID.ValueOr(0), segment);
ASSERT_TRUE(decoded.maxSegmentSize.HasValue());
EXPECT_EQ(decoded.maxSegmentSize.Value(), CertificateChainRequestTracker::kDefaultSegmentSize);
}
}
OperationalCredentials::Commands::CertificateChainRequest::Type request;
auto differentChain = chain;
differentChain.PAACrypto = Profile::kMlDsa44;
EXPECT_EQ(access.PrepareCertificateChainRequest(Credentials::CertificateType::kDAC, differentChain, MakeOptional(uint16_t{ 3 }),
request),
CHIP_ERROR_INCORRECT_STATE);
access.CancelCommissioningInteractions();
EXPECT_EQ(
access.PrepareCertificateChainRequest(Credentials::CertificateType::kDAC, chain, MakeOptional(uint16_t{ 3 }), request),
CHIP_ERROR_INCORRECT_STATE);
ASSERT_EQ(access.PrepareCertificateChainRequest(Credentials::CertificateType::kDAC, std::nullopt, NullOptional, request),
CHIP_NO_ERROR);
EXPECT_FALSE(request.cryptoProfile.HasValue());
EXPECT_FALSE(request.segmentID.HasValue());
EXPECT_FALSE(request.maxSegmentSize.HasValue());
differentChain.PAICrypto = Profile::kMlDsa65;
EXPECT_EQ(access.PrepareCertificateChainRequest(Credentials::CertificateType::kPAI, differentChain, NullOptional, request),
CHIP_ERROR_INVALID_ARGUMENT);
}
TEST_F(TestParseOperationalCredentialsInfo, LegacyCertificateRequestsEncodeOnlyCertificateType)
{
DeviceCommissionerTestAccess access(&mCommissioner);
for (auto type : { Credentials::CertificateType::kPAI, Credentials::CertificateType::kDAC })
{
OperationalCredentials::Commands::CertificateChainRequest::Type request;
ASSERT_EQ(access.PrepareCertificateChainRequest(type, std::nullopt, NullOptional, request), CHIP_NO_ERROR);
uint8_t buffer[128];
TLV::TLVWriter writer;
writer.Init(buffer);
ASSERT_EQ(request.Encode(writer, TLV::AnonymousTag()), CHIP_NO_ERROR);
ASSERT_EQ(writer.Finalize(), CHIP_NO_ERROR);
TLV::TLVReader reader;
reader.Init(buffer, writer.GetLengthWritten());
ASSERT_EQ(reader.Next(), CHIP_NO_ERROR);
TLV::TLVType container;
ASSERT_EQ(reader.EnterContainer(container), CHIP_NO_ERROR);
ASSERT_EQ(reader.Next(), CHIP_NO_ERROR);
EXPECT_EQ(reader.GetTag(), TLV::ContextTag(0));
uint8_t certificateType;
ASSERT_EQ(reader.Get(certificateType), CHIP_NO_ERROR);
EXPECT_EQ(certificateType, to_underlying(type));
EXPECT_EQ(reader.Next(), CHIP_END_OF_TLV);
EXPECT_EQ(reader.ExitContainer(container), CHIP_NO_ERROR);
}
}
TEST_F(TestParseOperationalCredentialsInfo, CertificateRequestBoundsUseSelectedSubjectAndIssuer)
{
using Profile = OperationalCredentials::AttestationCryptoProfileEnum;
const Credentials::DeviceAttestationChainProfile chain{ Profile::kMlDsa65, Profile::kMlDsa44, Profile::kEcdsaMatterLegacy };
DeviceCommissionerTestAccess access(&mCommissioner);
OperationalCredentials::Commands::CertificateChainRequest::Type request;
uint8_t bytes[600] = {};
ASSERT_EQ(access.PrepareCertificateChainRequest(Credentials::CertificateType::kPAI, chain, NullOptional, request),
CHIP_NO_ERROR);
EXPECT_EQ(access.CertificateRequestTracker().HandleResponse(
ByteSpan(bytes), MakeOptional(static_cast<uint16_t>(Credentials::kMaxDERCertLengthMlDsa65)),
MakeOptional(uint16_t{ 1 })),
CHIP_NO_ERROR);
ASSERT_EQ(access.PrepareCertificateChainRequest(Credentials::CertificateType::kDAC, chain, NullOptional, request),
CHIP_NO_ERROR);
// The selected DAC has an ML-DSA-44 issuer; a document requiring the ML-DSA-65 bound is invalid.
EXPECT_NE(access.CertificateRequestTracker().HandleResponse(
ByteSpan(bytes), MakeOptional(static_cast<uint16_t>(Credentials::kMaxDERCertLengthMlDsa65)),
MakeOptional(uint16_t{ 1 })),
CHIP_NO_ERROR);
}
TEST_F(TestParseOperationalCredentialsInfo, CertificateResponseRejectsProfilesDifferentFromTheRequest)
{
class CertificateDevice : public DeviceProxy
{
public:
void Disconnect() override {}
NodeId GetDeviceId() const override { return 0x1234; }
Messaging::ExchangeManager * GetExchangeManager() const override { return nullptr; }
Optional<SessionHandle> GetSecureSession() const override { return NullOptional; }
private:
bool IsSecureConnected() const override { return false; }
} device;
class CertificateDelegate : public AutoCommissioner
{
public:
CHIP_ERROR CommissioningStepFinished(CHIP_ERROR error, CommissioningReport report) override
{
completions++;
result = error;
receivedCertificate = report.Is<RequestedCertificate>();
return CHIP_NO_ERROR;
}
unsigned completions = 0;
CHIP_ERROR result = CHIP_ERROR_INTERNAL;
bool receivedCertificate = false;
} delegate;
DeviceCommissionerTestAccess access(&mCommissioner);
access.SetCommissioningDelegate(&delegate);
using Profile = OperationalCredentials::AttestationCryptoProfileEnum;
for (auto type : { Credentials::CertificateType::kPAI, Credentials::CertificateType::kDAC })
{
for (bool mismatch : { false, true })
{
Credentials::DeviceAttestationChainProfile chain{};
if (mismatch)
{
chain.PAACrypto = Profile::kMlDsa65;
chain.PAICrypto = type == Credentials::CertificateType::kPAI ? Profile::kEcdsaMatterLegacy : Profile::kMlDsa44;
}
OperationalCredentials::Commands::CertificateChainRequest::Type request;
ASSERT_EQ(access.PrepareCertificateChainRequest(type, chain, NullOptional, request), CHIP_NO_ERROR);
access.SetDeviceBeingCommissioned(&device);
access.SetCommissioningStage(type == Credentials::CertificateType::kPAI ? kSendPAICertificateRequest
: kSendDACCertificateRequest);
OperationalCredentials::Commands::CertificateChainResponse::DecodableType response;
response.certificate = type == Credentials::CertificateType::kPAI ? TestCerts::sTestCert_PAI_FFF1_8000_Cert
: TestCerts::sTestCert_DAC_FFF1_8000_0000_Cert;
const unsigned before = delegate.completions;
access.OnCertificateChainResponse(response);
EXPECT_EQ(delegate.completions, before + 1);
EXPECT_EQ(delegate.result, mismatch ? CHIP_ERROR_FAILED_DEVICE_ATTESTATION : CHIP_NO_ERROR);
EXPECT_EQ(delegate.receivedCertificate, !mismatch);
EXPECT_EQ(access.PrepareCertificateChainRequest(type, chain, MakeOptional(uint16_t{ 1 }), request),
CHIP_ERROR_INCORRECT_STATE);
}
}
access.SetCommissioningDelegate(nullptr);
}
TEST_F(TestParseOperationalCredentialsInfo, OnlyInitialProfiledPaiRejectionAllowsAnotherChain)
{
class CertificateDevice : public DeviceProxy
{
public:
void Disconnect() override {}
NodeId GetDeviceId() const override { return 0x1234; }
Messaging::ExchangeManager * GetExchangeManager() const override { return nullptr; }
Optional<SessionHandle> GetSecureSession() const override { return NullOptional; }
private:
bool IsSecureConnected() const override { return false; }
} device;
class CertificateDelegate : public AutoCommissioner
{
public:
CHIP_ERROR CommissioningStepFinished(CHIP_ERROR error, CommissioningReport report) override
{
result = error;
unavailable = report.Is<AttestationChainNotAvailable>();
return CHIP_NO_ERROR;
}
CHIP_ERROR result = CHIP_ERROR_INTERNAL;
bool unavailable = false;
} delegate;
DeviceCommissionerTestAccess access(&mCommissioner);
access.SetCommissioningDelegate(&delegate);
for (auto type : { Credentials::CertificateType::kPAI, Credentials::CertificateType::kDAC })
{
for (bool explicitChain : { false, true })
{
for (bool continuation : { false, true })
{
for (auto error : { CHIP_IM_GLOBAL_STATUS(InvalidCommand), CHIP_IM_GLOBAL_STATUS(Failure), CHIP_ERROR_TIMEOUT })
{
std::optional<Credentials::DeviceAttestationChainProfile> chain;
if (explicitChain)
{
chain.emplace();
}
OperationalCredentials::Commands::CertificateChainRequest::Type request;
ASSERT_EQ(access.PrepareCertificateChainRequest(type, chain, NullOptional, request), CHIP_NO_ERROR);
if (continuation)
{
const uint8_t segment[] = { 1 };
ASSERT_EQ(access.CertificateRequestTracker().HandleResponse(ByteSpan(segment), MakeOptional(uint16_t{ 2 }),
MakeOptional(uint16_t{ 1 })),
CHIP_NO_ERROR);
}
access.SetDeviceBeingCommissioned(&device);
access.SetCommissioningStage(type == Credentials::CertificateType::kPAI ? kSendPAICertificateRequest
: kSendDACCertificateRequest);
access.OnCertificateChainFailureResponse(error);
EXPECT_EQ(delegate.result, error);
EXPECT_EQ(delegate.unavailable,
type == Credentials::CertificateType::kPAI && explicitChain && !continuation &&
error == CHIP_IM_GLOBAL_STATUS(InvalidCommand));
EXPECT_EQ(access.PrepareCertificateChainRequest(type, chain, MakeOptional(uint16_t{ 1 }), request),
CHIP_ERROR_INCORRECT_STATE);
}
}
}
}
access.SetCommissioningDelegate(nullptr);
}
} // namespace