blob: 9a24b2ff7275716751b35aabb7163b0682091afa [file] [log] [blame]
/*
*
* Copyright (c) 2025 Project CHIP Authors
*
* 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 <app/clusters/tls-certificate-management-server/CertificateTableImpl.h>
#include <app/InteractionModelEngine.h>
#include <app/util/mock/Constants.h>
#include <app/util/mock/MockNodeConfig.h>
#include <data-model-providers/codegen/CodegenDataModelProvider.h>
#include <lib/support/TestPersistentStorageDelegate.h>
#include <lib/core/StringBuilderAdapters.h>
#include <lib/support/tests/ExtraPwTestMacros.h>
#include <pw_unit_test/framework.h>
using namespace chip;
using namespace chip::Testing;
using namespace chip::app::DataModel;
using namespace chip::app::Storage;
using namespace chip::app::Storage::Data;
using namespace chip::app::Clusters::Tls;
using namespace chip::app::Clusters::TlsCertificateManagement;
using namespace chip::app::Clusters::TlsCertificateManagement::Structs;
using chip::TLV::EstimateStructOverhead;
using namespace chip::Crypto;
using namespace chip::Credentials;
namespace TestCertificates {
static constexpr uint16_t kSpecMaxCertBytes = 3000;
struct InlineBufferedRootCert : CertificateTable::BufferedRootCert
{
PersistenceBuffer<CHIP_CONFIG_TLS_PERSISTED_ROOT_CERT_BYTES> buffer;
InlineBufferedRootCert() : CertificateTable::BufferedRootCert(buffer) {}
};
struct InlineBufferedClientCert : CertificateTable::BufferedClientCert
{
PersistenceBuffer<CHIP_CONFIG_TLS_PERSISTED_CLIENT_CERT_BYTES> buffer;
InlineBufferedClientCert() : CertificateTable::BufferedClientCert(buffer) {}
};
// Test constants
constexpr FabricIndex kFabric1 = 1;
constexpr FabricIndex kFabric2 = 2;
class TestCertificateTableImpl : public ::testing::Test
{
public:
static void SetUpTestSuite()
{
mpTestStorage = new chip::TestPersistentStorageDelegate;
ASSERT_EQ(Platform::MemoryInit(), CHIP_NO_ERROR);
ASSERT_NE(mpTestStorage, nullptr);
ASSERT_EQ(sCertificateTable.Init(*mpTestStorage), CHIP_NO_ERROR);
ASSERT_EQ(sCertificateTable.SetEndpoint(kMockEndpoint1), CHIP_NO_ERROR);
app::InteractionModelEngine::GetInstance()->SetDataModelProvider(&app::CodegenDataModelProvider::Instance());
}
static void TearDownTestSuite()
{
sCertificateTable.Finish();
delete mpTestStorage;
mpTestStorage = nullptr;
Platform::MemoryShutdown();
}
static void ResetCertificateTable()
{
EXPECT_SUCCESS(sCertificateTable.RemoveFabric(kFabric1));
EXPECT_SUCCESS(sCertificateTable.RemoveFabric(kFabric2));
}
protected:
static CertificateTableImpl sCertificateTable;
static chip::TestPersistentStorageDelegate * mpTestStorage;
};
CertificateTableImpl TestCertificateTableImpl::sCertificateTable;
chip::TestPersistentStorageDelegate * TestCertificateTableImpl::mpTestStorage = nullptr;
TEST_F(TestCertificateTableImpl, TestRootCertificateOperations)
{
ResetCertificateTable();
const uint8_t byteBuf[] = { 0x1, 0x2, 0x3, 0x4, 0x8, 0xF, 0xFF };
ByteSpan testCert(byteBuf);
auto buffer = std::make_unique<InlineBufferedRootCert>();
Optional<TLSCAID> id;
// Add a root certificate
EXPECT_EQ(sCertificateTable.UpsertRootCertificateEntry(kFabric1, id, buffer->buffer, testCert), CHIP_NO_ERROR);
EXPECT_TRUE(id.HasValue());
TLSCAID certId = id.Value();
// Check if it exists
EXPECT_EQ(sCertificateTable.HasRootCertificateEntry(kFabric1, certId), CHIP_NO_ERROR);
// Get the certificate
auto entry = std::make_unique<InlineBufferedRootCert>();
EXPECT_EQ(sCertificateTable.GetRootCertificateEntry(kFabric1, certId, *entry), CHIP_NO_ERROR);
EXPECT_TRUE(entry->mCert.certificate.Value().data_equal(testCert));
// Remove the certificate
EXPECT_EQ(sCertificateTable.RemoveRootCertificate(kFabric1, certId), CHIP_NO_ERROR);
// Check that it's gone
EXPECT_EQ(sCertificateTable.HasRootCertificateEntry(kFabric1, certId), CHIP_ERROR_NOT_FOUND);
}
TEST_F(TestCertificateTableImpl, TestClientCertificateOperations)
{
ResetCertificateTable();
const uint8_t byteBuf[] = { 0x1, 0x2, 0x3, 0x4, 0x8, 0xF, 0xFF };
ByteSpan nonce(byteBuf);
auto certBuffer = std::make_unique<InlineBufferedClientCert>();
Optional<TLSCCDID> id;
auto csr_buf = std::make_unique<std::array<uint8_t, kSpecMaxCertBytes>>();
MutableByteSpan csr(*csr_buf);
uint8_t nonce_sig_buf[Crypto::P256ECDSASignature::Capacity()];
MutableByteSpan nonceSignature(nonce_sig_buf);
// Prepare a client certificate
EXPECT_EQ(sCertificateTable.PrepareClientCertificate(kFabric1, nonce, certBuffer->buffer, id, csr, nonceSignature),
CHIP_NO_ERROR);
EXPECT_TRUE(id.HasValue());
TLSCCDID certId = id.Value();
// Check if it exists
EXPECT_EQ(sCertificateTable.HasClientCertificateEntry(kFabric1, certId), CHIP_NO_ERROR);
// Remove the certificate
EXPECT_EQ(sCertificateTable.RemoveClientCertificate(kFabric1, certId), CHIP_NO_ERROR);
// Check that it's gone
EXPECT_EQ(sCertificateTable.HasClientCertificateEntry(kFabric1, certId), CHIP_ERROR_NOT_FOUND);
}
TEST_F(TestCertificateTableImpl, TestFabricRemoval)
{
ResetCertificateTable();
const uint8_t byteBuf[] = { 0x1, 0x2, 0x3, 0x4, 0x8, 0xF, 0xFF };
ByteSpan rootCert(byteBuf);
auto rootBuffer = std::make_unique<InlineBufferedRootCert>();
Optional<TLSCAID> rootId;
EXPECT_EQ(sCertificateTable.UpsertRootCertificateEntry(kFabric1, rootId, rootBuffer->buffer, rootCert), CHIP_NO_ERROR);
const uint8_t nonceByteBuf[] = { 0x1, 0x2, 0x3, 0x4, 0x8, 0xF, 0xFF };
ByteSpan nonce(nonceByteBuf);
auto clientBuffer = std::make_unique<InlineBufferedClientCert>();
Optional<TLSCCDID> clientId;
auto csr_buf = std::make_unique<std::array<uint8_t, kSpecMaxCertBytes>>();
MutableByteSpan csr(*csr_buf);
uint8_t nonce_sig_buf[Crypto::P256ECDSASignature::Capacity()];
MutableByteSpan nonceSignature(nonce_sig_buf);
EXPECT_EQ(sCertificateTable.PrepareClientCertificate(kFabric1, nonce, clientBuffer->buffer, clientId, csr, nonceSignature),
CHIP_NO_ERROR);
uint8_t count;
EXPECT_EQ(sCertificateTable.GetRootCertificateCount(kFabric1, count), CHIP_NO_ERROR);
EXPECT_EQ(count, 1);
EXPECT_EQ(sCertificateTable.GetClientCertificateCount(kFabric1, count), CHIP_NO_ERROR);
EXPECT_EQ(count, 1);
EXPECT_EQ(sCertificateTable.RemoveFabric(kFabric1), CHIP_NO_ERROR);
EXPECT_EQ(sCertificateTable.GetRootCertificateCount(kFabric1, count), CHIP_NO_ERROR);
EXPECT_EQ(count, 0);
EXPECT_EQ(sCertificateTable.GetClientCertificateCount(kFabric1, count), CHIP_NO_ERROR);
EXPECT_EQ(count, 0);
}
TEST_F(TestCertificateTableImpl, TestRootCertificateIteration)
{
ResetCertificateTable();
const uint8_t byteBuf1[] = { 0x1, 0x2, 0x3, 0x4, 0x8, 0xF, 0xFF, 0xA };
const uint8_t byteBuf2[] = { 0x1, 0x2, 0x3, 0x4, 0x8, 0xF, 0xFF, 0xB };
ByteSpan rootCert1(byteBuf1);
ByteSpan rootCert2(byteBuf2);
auto rootBuffer1 = std::make_unique<InlineBufferedRootCert>();
auto rootBuffer2 = std::make_unique<InlineBufferedRootCert>();
Optional<TLSCAID> rootId1;
Optional<TLSCAID> rootId2;
EXPECT_EQ(sCertificateTable.UpsertRootCertificateEntry(kFabric1, rootId1, rootBuffer1->buffer, rootCert1), CHIP_NO_ERROR);
EXPECT_EQ(sCertificateTable.UpsertRootCertificateEntry(kFabric1, rootId2, rootBuffer2->buffer, rootCert2), CHIP_NO_ERROR);
EXPECT_NE(rootId1, rootId2);
uint8_t count = 0;
auto certBuffer = std::make_unique<InlineBufferedRootCert>();
auto iterFn = [&](auto & iterator) {
while (iterator.Next(certBuffer->GetCert()))
{
count++;
}
return CHIP_NO_ERROR;
};
EXPECT_EQ(sCertificateTable.IterateRootCertificates(kFabric1, *certBuffer, iterFn), CHIP_NO_ERROR);
EXPECT_EQ(count, 2);
}
CHIP_ERROR GetTestCert(MutableByteSpan & signedCert, InlineBufferedClientCert & cert, const ByteSpan & csr)
{
ChipDN ica_dn;
ReturnErrorOnFailure(ica_dn.AddAttribute_MatterICACId(0xABCDABCDABCDABCD));
ChipDN issuer_dn;
ReturnErrorOnFailure(issuer_dn.AddAttribute_MatterRCACId(0x43215678FEDCABCD));
X509CertRequestParams params = { 1234, 631161876, 729942000, ica_dn, issuer_dn };
P256PublicKey pubkey;
ReturnErrorOnFailure(VerifyCertificateSigningRequest(csr.data(), csr.size(), pubkey));
Crypto::P256Keypair testPair;
ReturnErrorOnFailure(testPair.Initialize(Crypto::ECPKeyTarget::ECDSA));
ReturnErrorOnFailure(NewICAX509Cert(params, pubkey, testPair, signedCert));
cert.GetCert().clientCertificate.SetValue(signedCert);
return CHIP_NO_ERROR;
}
TEST_F(TestCertificateTableImpl, TestClientCertificateIteration)
{
ResetCertificateTable();
const uint8_t byteBuf[] = { 0x1, 0x2, 0x3, 0x4, 0x8, 0xF, 0xFF };
ByteSpan nonce(byteBuf);
auto certBuffer1 = std::make_unique<InlineBufferedClientCert>();
auto certBuffer2 = std::make_unique<InlineBufferedClientCert>();
Optional<TLSCAID> clientId1;
Optional<TLSCAID> clientId2;
auto csr_buf = std::make_unique<std::array<uint8_t, kSpecMaxCertBytes>>();
MutableByteSpan csr(*csr_buf);
uint8_t nonceSigBuff[Crypto::P256ECDSASignature::Capacity()];
MutableByteSpan nonceSignature(nonceSigBuff);
uint8_t signedCert[kMaxDERCertLength];
MutableByteSpan signedCertSpan(signedCert);
// Prepare client certificates
EXPECT_EQ(sCertificateTable.PrepareClientCertificate(kFabric1, nonce, certBuffer1->buffer, clientId1, csr, nonceSignature),
CHIP_NO_ERROR);
EXPECT_EQ(GetTestCert(signedCertSpan, *certBuffer1, csr), CHIP_NO_ERROR);
EXPECT_EQ(
sCertificateTable.UpdateClientCertificateEntry(kFabric1, clientId1.Value(), certBuffer1->buffer, certBuffer1->GetCert()),
CHIP_NO_ERROR);
csr = MutableByteSpan(*csr_buf);
signedCertSpan = MutableByteSpan(signedCert);
EXPECT_EQ(sCertificateTable.PrepareClientCertificate(kFabric1, nonce, certBuffer2->buffer, clientId2, csr, nonceSignature),
CHIP_NO_ERROR);
EXPECT_EQ(GetTestCert(signedCertSpan, *certBuffer2, csr), CHIP_NO_ERROR);
EXPECT_EQ(
sCertificateTable.UpdateClientCertificateEntry(kFabric1, clientId2.Value(), certBuffer2->buffer, certBuffer2->GetCert()),
CHIP_NO_ERROR);
EXPECT_NE(clientId1, clientId2);
uint8_t count = 0;
auto certBuffer = std::make_unique<InlineBufferedClientCert>();
auto iterFn = [&](auto & iterator) {
while (iterator.Next(certBuffer->mCertWithKey))
{
count++;
}
return CHIP_NO_ERROR;
};
EXPECT_EQ(sCertificateTable.IterateClientCertificates(kFabric1, *certBuffer, iterFn), CHIP_NO_ERROR);
EXPECT_EQ(count, 2);
}
} // namespace TestCertificates