blob: e3bec1c39361ade6a4a9c957e0ba9d5c108b04d3 [file] [log] [blame]
/*
* Copyright (c) 2021 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 <lib/support/CodeUtils.h>
#include <lib/support/TestPersistentStorageDelegate.h>
#include <lib/support/UnitTestRegistration.h>
#include <nlunit-test.h>
// DefaultSessionResumptionStorage is a partial implementation.
// Use SimpleSessionResumptionStorage, which extends it, to test.
#include <protocols/secure_channel/SimpleSessionResumptionStorage.h>
void TestSave(nlTestSuite * inSuite, void * inContext)
{
chip::SimpleSessionResumptionStorage sessionStorage;
chip::TestPersistentStorageDelegate storage;
sessionStorage.Init(&storage);
struct
{
chip::SessionResumptionStorage::ResumptionIdStorage resumptionId;
chip::Crypto::P256ECDHDerivedSecret sharedSecret;
chip::ScopedNodeId node;
chip::CATValues cats;
} vectors[CHIP_CONFIG_CASE_SESSION_RESUME_CACHE_SIZE + 1];
// Populate test vectors.
for (size_t i = 0; i < ArraySize(vectors); ++i)
{
NL_TEST_ASSERT(
inSuite, CHIP_NO_ERROR == chip::Crypto::DRBG_get_bytes(vectors[i].resumptionId.data(), vectors[i].resumptionId.size()));
*vectors[i].resumptionId.data() =
static_cast<uint8_t>(i); // set first byte to our index to ensure uniqueness for the FindByResumptionId call
vectors[i].sharedSecret.SetLength(vectors[i].sharedSecret.Capacity());
NL_TEST_ASSERT(inSuite,
CHIP_NO_ERROR ==
chip::Crypto::DRBG_get_bytes(vectors[i].sharedSecret.Bytes(), vectors[i].sharedSecret.Length()));
vectors[i].node = chip::ScopedNodeId(static_cast<chip::NodeId>(i + 1), static_cast<chip::FabricIndex>(i + 1));
vectors[i].cats.values[0] = static_cast<chip::CASEAuthTag>(rand());
vectors[i].cats.values[1] = static_cast<chip::CASEAuthTag>(rand());
vectors[i].cats.values[2] = static_cast<chip::CASEAuthTag>(rand());
}
// Fill storage.
for (size_t i = 0; i < CHIP_CONFIG_CASE_SESSION_RESUME_CACHE_SIZE; ++i)
{
NL_TEST_ASSERT(inSuite,
sessionStorage.Save(vectors[i].node, vectors[i].resumptionId, vectors[i].sharedSecret, vectors[i].cats) ==
CHIP_NO_ERROR);
}
// Verify behavior for over-fill.
//
// Currently, DefaultSessionResumptionStorage replaces index 0.
// If more sophisticated LRU behavior is implemented, this test
// case should be modified to match.
{
size_t last = ArraySize(vectors) - 1;
NL_TEST_ASSERT(inSuite,
sessionStorage.Save(vectors[last].node, vectors[last].resumptionId, vectors[last].sharedSecret,
vectors[last].cats) == CHIP_NO_ERROR);
// Copy our data to our test vector index 0 to match
// what is now in storage.
vectors[0].node = vectors[last].node;
vectors[0].cats = vectors[last].cats;
memcpy(vectors[0].resumptionId.data(), vectors[last].resumptionId.data(), vectors[0].resumptionId.size());
memcpy(vectors[0].sharedSecret.Bytes(), vectors[last].sharedSecret.Bytes(), vectors[0].sharedSecret.Length());
}
// Read back and verify values.
for (auto & vector : vectors)
{
chip::ScopedNodeId outNode;
chip::SessionResumptionStorage::ResumptionIdStorage outResumptionId;
chip::Crypto::P256ECDHDerivedSecret outSharedSecret;
chip::CATValues outCats;
// Verify retrieval by node.
NL_TEST_ASSERT(inSuite,
sessionStorage.FindByScopedNodeId(vector.node, outResumptionId, outSharedSecret, outCats) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, memcmp(vector.resumptionId.data(), outResumptionId.data(), vector.resumptionId.size()) == 0);
NL_TEST_ASSERT(inSuite, memcmp(vector.sharedSecret.ConstBytes(), outSharedSecret, vector.sharedSecret.Length()) == 0);
NL_TEST_ASSERT(inSuite, vector.cats.values[0] == outCats.values[0]);
NL_TEST_ASSERT(inSuite, vector.cats.values[1] == outCats.values[1]);
NL_TEST_ASSERT(inSuite, vector.cats.values[2] == outCats.values[2]);
// Validate retrieval by resumption ID.
NL_TEST_ASSERT(inSuite,
sessionStorage.FindByResumptionId(vector.resumptionId, outNode, outSharedSecret, outCats) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, vector.node == outNode);
NL_TEST_ASSERT(inSuite, memcmp(vector.sharedSecret.Bytes(), outSharedSecret, vector.sharedSecret.Length()) == 0);
NL_TEST_ASSERT(inSuite, vector.cats.values[0] == outCats.values[0]);
NL_TEST_ASSERT(inSuite, vector.cats.values[1] == outCats.values[1]);
NL_TEST_ASSERT(inSuite, vector.cats.values[2] == outCats.values[2]);
}
}
void TestInPlaceSave(nlTestSuite * inSuite, void * inContext)
{
chip::SimpleSessionResumptionStorage sessionStorage;
chip::TestPersistentStorageDelegate storage;
sessionStorage.Init(&storage);
struct
{
chip::SessionResumptionStorage::ResumptionIdStorage resumptionId;
chip::Crypto::P256ECDHDerivedSecret sharedSecret;
chip::ScopedNodeId node;
chip::CATValues cats;
} vectors[CHIP_CONFIG_CASE_SESSION_RESUME_CACHE_SIZE + 10];
// Construct only a few unique node identities to simulate talking to a
// couple peers.
chip::ScopedNodeId nodes[3];
static_assert(ArraySize(nodes) < CHIP_CONFIG_CASE_SESSION_RESUME_CACHE_SIZE,
"must have fewer nodes than slots in session resumption storage");
for (size_t i = 0; i < ArraySize(nodes); ++i)
{
do
{
nodes[i] = chip::ScopedNodeId(static_cast<chip::NodeId>(rand()), static_cast<chip::FabricIndex>(i + 1));
} while (!nodes[i].IsOperational());
}
// Populate test vectors.
for (size_t i = 0; i < ArraySize(vectors); ++i)
{
NL_TEST_ASSERT(
inSuite, CHIP_NO_ERROR == chip::Crypto::DRBG_get_bytes(vectors[i].resumptionId.data(), vectors[i].resumptionId.size()));
*vectors[i].resumptionId.data() =
static_cast<uint8_t>(i); // set first byte to our index to ensure uniqueness for the FindByResumptionId call
vectors[i].sharedSecret.SetLength(vectors[i].sharedSecret.Capacity());
NL_TEST_ASSERT(inSuite,
CHIP_NO_ERROR ==
chip::Crypto::DRBG_get_bytes(vectors[i].sharedSecret.Bytes(), vectors[i].sharedSecret.Length()));
vectors[i].node = nodes[i % ArraySize(nodes)];
vectors[i].cats.values[0] = static_cast<chip::CASEAuthTag>(rand());
vectors[i].cats.values[1] = static_cast<chip::CASEAuthTag>(rand());
vectors[i].cats.values[2] = static_cast<chip::CASEAuthTag>(rand());
}
// Add one entry for each node.
for (size_t i = 0; i < ArraySize(nodes); ++i)
{
NL_TEST_ASSERT(inSuite,
sessionStorage.Save(vectors[i].node, vectors[i].resumptionId, vectors[i].sharedSecret, vectors[i].cats) ==
CHIP_NO_ERROR);
}
// Read back and verify values.
for (size_t i = 0; i < ArraySize(nodes); ++i)
{
chip::ScopedNodeId outNode;
chip::SessionResumptionStorage::ResumptionIdStorage outResumptionId;
chip::Crypto::P256ECDHDerivedSecret outSharedSecret;
chip::CATValues outCats;
// Verify retrieval by node.
NL_TEST_ASSERT(inSuite,
sessionStorage.FindByScopedNodeId(vectors[i].node, outResumptionId, outSharedSecret, outCats) ==
CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite,
memcmp(vectors[i].resumptionId.data(), outResumptionId.data(), vectors[i].resumptionId.size()) == 0);
NL_TEST_ASSERT(inSuite, memcmp(vectors[i].sharedSecret.Bytes(), outSharedSecret, vectors[i].sharedSecret.Length()) == 0);
NL_TEST_ASSERT(inSuite, vectors[i].cats.values[0] == outCats.values[0]);
NL_TEST_ASSERT(inSuite, vectors[i].cats.values[1] == outCats.values[1]);
NL_TEST_ASSERT(inSuite, vectors[i].cats.values[2] == outCats.values[2]);
// Validate retrieval by resumption ID.
NL_TEST_ASSERT(inSuite,
sessionStorage.FindByResumptionId(vectors[i].resumptionId, outNode, outSharedSecret, outCats) ==
CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, vectors[i].node == outNode);
NL_TEST_ASSERT(inSuite, memcmp(vectors[i].sharedSecret.Bytes(), outSharedSecret, vectors[i].sharedSecret.Length()) == 0);
NL_TEST_ASSERT(inSuite, vectors[i].cats.values[0] == outCats.values[0]);
NL_TEST_ASSERT(inSuite, vectors[i].cats.values[1] == outCats.values[1]);
NL_TEST_ASSERT(inSuite, vectors[i].cats.values[2] == outCats.values[2]);
}
// Now add all test vectors. This should overwrite each node's record
// many times.
for (auto & vector : vectors)
{
NL_TEST_ASSERT(inSuite,
sessionStorage.Save(vector.node, vector.resumptionId, vector.sharedSecret, vector.cats) == CHIP_NO_ERROR);
}
// Read back and verify that only the last record for each node was retained.
for (size_t i = ArraySize(vectors) - ArraySize(nodes); i < ArraySize(vectors); ++i)
{
chip::ScopedNodeId outNode;
chip::SessionResumptionStorage::ResumptionIdStorage outResumptionId;
chip::Crypto::P256ECDHDerivedSecret outSharedSecret;
chip::CATValues outCats;
// Verify retrieval by node.
NL_TEST_ASSERT(inSuite,
sessionStorage.FindByScopedNodeId(vectors[i].node, outResumptionId, outSharedSecret, outCats) ==
CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite,
memcmp(vectors[i].resumptionId.data(), outResumptionId.data(), vectors[i].resumptionId.size()) == 0);
NL_TEST_ASSERT(inSuite, memcmp(vectors[i].sharedSecret.Bytes(), outSharedSecret, vectors[i].sharedSecret.Length()) == 0);
NL_TEST_ASSERT(inSuite, vectors[i].cats.values[0] == outCats.values[0]);
NL_TEST_ASSERT(inSuite, vectors[i].cats.values[1] == outCats.values[1]);
NL_TEST_ASSERT(inSuite, vectors[i].cats.values[2] == outCats.values[2]);
// Validate retrieval by resumption ID.
NL_TEST_ASSERT(inSuite,
sessionStorage.FindByResumptionId(vectors[i].resumptionId, outNode, outSharedSecret, outCats) ==
CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, vectors[i].node == outNode);
NL_TEST_ASSERT(inSuite, memcmp(vectors[i].sharedSecret.Bytes(), outSharedSecret, vectors[i].sharedSecret.Length()) == 0);
NL_TEST_ASSERT(inSuite, vectors[i].cats.values[0] == outCats.values[0]);
NL_TEST_ASSERT(inSuite, vectors[i].cats.values[1] == outCats.values[1]);
NL_TEST_ASSERT(inSuite, vectors[i].cats.values[2] == outCats.values[2]);
}
// Remove all records for all fabrics. If all three tables of (index, state,
// links) are in sync, deleting for each fabric should clean error free.
for (const auto & node : nodes)
{
NL_TEST_ASSERT(inSuite, sessionStorage.DeleteAll(node.GetFabricIndex()) == CHIP_NO_ERROR);
}
// Verify that no entries can be located any longer for any node or
// resumption ID.
for (auto & vector : vectors)
{
chip::ScopedNodeId outNode;
chip::SessionResumptionStorage::ResumptionIdStorage outResumptionId;
chip::Crypto::P256ECDHDerivedSecret outSharedSecret;
chip::CATValues outCats;
// Verify all records for all nodes are gone.
NL_TEST_ASSERT(inSuite,
sessionStorage.FindByScopedNodeId(vector.node, outResumptionId, outSharedSecret, outCats) != CHIP_NO_ERROR);
// Verify all records for all resumption IDs are gone.
NL_TEST_ASSERT(inSuite,
sessionStorage.FindByResumptionId(vector.resumptionId, outNode, outSharedSecret, outCats) != CHIP_NO_ERROR);
}
// Verify no state table persistent storage entries were leaked.
for (const auto & node : nodes)
{
uint16_t size = 0;
auto rv = storage.SyncGetKeyValue(chip::SimpleSessionResumptionStorage::GetStorageKey(node).KeyName(), nullptr, size);
NL_TEST_ASSERT(inSuite, rv == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
// Verify no link table persistent storage entries were leaked.
for (auto & vector : vectors)
{
uint16_t size = 0;
auto rv = storage.SyncGetKeyValue(chip::SimpleSessionResumptionStorage::GetStorageKey(vector.resumptionId).KeyName(),
nullptr, size);
NL_TEST_ASSERT(inSuite, rv == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
}
void TestDelete(nlTestSuite * inSuite, void * inContext)
{
chip::SimpleSessionResumptionStorage sessionStorage;
chip::TestPersistentStorageDelegate storage;
sessionStorage.Init(&storage);
chip::Crypto::P256ECDHDerivedSecret sharedSecret;
struct
{
chip::SessionResumptionStorage::ResumptionIdStorage resumptionId;
chip::ScopedNodeId node;
} vectors[CHIP_CONFIG_CASE_SESSION_RESUME_CACHE_SIZE];
// Create a shared secret. We can use the same one for all entries.
sharedSecret.SetLength(sharedSecret.Capacity());
NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == chip::Crypto::DRBG_get_bytes(sharedSecret.Bytes(), sharedSecret.Length()));
// Populate test vectors.
for (size_t i = 0; i < ArraySize(vectors); ++i)
{
NL_TEST_ASSERT(
inSuite, CHIP_NO_ERROR == chip::Crypto::DRBG_get_bytes(vectors[i].resumptionId.data(), vectors[i].resumptionId.size()));
*vectors[i].resumptionId.data() =
static_cast<uint8_t>(i); // set first byte to our index to ensure uniqueness for the delete test
vectors[i].node = chip::ScopedNodeId(static_cast<chip::NodeId>(i + 1), static_cast<chip::FabricIndex>(i + 1));
}
// Fill storage.
for (auto & vector : vectors)
{
NL_TEST_ASSERT(inSuite,
sessionStorage.Save(vector.node, vector.resumptionId, sharedSecret, chip::CATValues{}) == CHIP_NO_ERROR);
}
// Delete values in turn from storage and verify they are removed.
for (auto & vector : vectors)
{
chip::ScopedNodeId outNode;
chip::SessionResumptionStorage::ResumptionIdStorage outResumptionId;
chip::Crypto::P256ECDHDerivedSecret outSharedSecret;
chip::CATValues outCats;
NL_TEST_ASSERT(inSuite, sessionStorage.Delete(vector.node) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite,
sessionStorage.FindByScopedNodeId(vector.node, outResumptionId, outSharedSecret, outCats) != CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite,
sessionStorage.FindByResumptionId(vector.resumptionId, outNode, outSharedSecret, outCats) != CHIP_NO_ERROR);
}
// Verify no state or link table persistent storage entries were leaked.
for (auto & vector : vectors)
{
uint16_t size = 0;
{
auto rv =
storage.SyncGetKeyValue(chip::SimpleSessionResumptionStorage::GetStorageKey(vector.node).KeyName(), nullptr, size);
NL_TEST_ASSERT(inSuite, rv == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
{
auto rv = storage.SyncGetKeyValue(chip::SimpleSessionResumptionStorage::GetStorageKey(vector.resumptionId).KeyName(),
nullptr, size);
NL_TEST_ASSERT(inSuite, rv == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
}
}
void TestDeleteAll(nlTestSuite * inSuite, void * inContext)
{
chip::SimpleSessionResumptionStorage sessionStorage;
chip::TestPersistentStorageDelegate storage;
sessionStorage.Init(&storage);
chip::Crypto::P256ECDHDerivedSecret sharedSecret;
struct
{
chip::FabricIndex fabricIndex;
struct
{
chip::SessionResumptionStorage::ResumptionIdStorage resumptionId;
chip::ScopedNodeId node;
} nodes[3];
} vectors[CHIP_CONFIG_CASE_SESSION_RESUME_CACHE_SIZE / 3];
// Create a shared secret. We can use the same one for all entries.
sharedSecret.SetLength(sharedSecret.Capacity());
NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == chip::Crypto::DRBG_get_bytes(sharedSecret.Bytes(), sharedSecret.Length()));
// Populate test vectors.
for (size_t i = 0; i < sizeof(vectors) / sizeof(vectors[0]); ++i)
{
vectors[i].fabricIndex = static_cast<chip::FabricIndex>(i + 1);
for (size_t j = 0; j < sizeof(vectors[0].nodes) / sizeof(vectors[0].nodes[0]); ++j)
{
NL_TEST_ASSERT(
inSuite,
CHIP_NO_ERROR ==
chip::Crypto::DRBG_get_bytes(vectors[i].nodes[j].resumptionId.data(), vectors[i].nodes[j].resumptionId.size()));
vectors[i].nodes[j].node = chip::ScopedNodeId(static_cast<chip::NodeId>(j), vectors[i].fabricIndex);
}
}
// Fill storage.
for (auto & vector : vectors)
{
for (auto & node : vector.nodes)
{
NL_TEST_ASSERT(inSuite,
sessionStorage.Save(node.node, node.resumptionId, sharedSecret, chip::CATValues{}) == CHIP_NO_ERROR);
}
}
// Validate Fabric deletion.
for (const auto & vector : vectors)
{
chip::ScopedNodeId outNode;
chip::SessionResumptionStorage::ResumptionIdStorage outResumptionId;
chip::Crypto::P256ECDHDerivedSecret outSharedSecret;
chip::CATValues outCats;
// Verify fabric node entries exist.
for (const auto & node : vector.nodes)
{
NL_TEST_ASSERT(
inSuite, sessionStorage.FindByScopedNodeId(node.node, outResumptionId, outSharedSecret, outCats) == CHIP_NO_ERROR);
}
// Delete fabric.
NL_TEST_ASSERT(inSuite, sessionStorage.DeleteAll(vector.fabricIndex) == CHIP_NO_ERROR);
// Verify fabric node entries no longer exist.
for (const auto & node : vector.nodes)
{
NL_TEST_ASSERT(
inSuite, sessionStorage.FindByScopedNodeId(node.node, outResumptionId, outSharedSecret, outCats) != CHIP_NO_ERROR);
}
}
// Verify no state or link table persistent storage entries were leaked.
for (auto & vector : vectors)
{
for (auto & node : vector.nodes)
{
uint16_t size = 0;
{
auto rv = storage.SyncGetKeyValue(chip::SimpleSessionResumptionStorage::GetStorageKey(node.node).KeyName(), nullptr,
size);
NL_TEST_ASSERT(inSuite, rv == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
{
auto rv = storage.SyncGetKeyValue(chip::SimpleSessionResumptionStorage::GetStorageKey(node.resumptionId).KeyName(),
nullptr, size);
NL_TEST_ASSERT(inSuite, rv == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
}
}
}
// Test Suite
/**
* Test Suite that lists all the test functions.
*/
// clang-format off
static const nlTest sTests[] =
{
NL_TEST_DEF("TestSave", TestSave),
NL_TEST_DEF("TestInPlaceSave", TestInPlaceSave),
NL_TEST_DEF("TestDelete", TestDelete),
NL_TEST_DEF("TestDeleteAll", TestDeleteAll),
NL_TEST_SENTINEL()
};
// clang-format on
// clang-format off
static nlTestSuite sSuite =
{
"Test-CHIP-DefaultSessionResumptionStorage",
&sTests[0],
nullptr,
nullptr,
};
// clang-format on
/**
* Main
*/
int TestDefaultSessionResumptionStorage()
{
// Run test suit against one context
nlTestRunner(&sSuite, nullptr);
return (nlTestRunnerStats(&sSuite));
}
CHIP_REGISTER_TEST_SUITE(TestDefaultSessionResumptionStorage)