blob: 59dcf3080a8ec3397d1d546fd416954dfab70d1a [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 <gtest/gtest.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/TestPersistentStorageDelegate.h>
// DefaultSessionResumptionStorage is a partial implementation.
// Use SimpleSessionResumptionStorage, which extends it, to test.
#include <protocols/secure_channel/SimpleSessionResumptionStorage.h>
TEST(TestDefaultSessionResumptionStorage, TestSave)
{
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)
{
EXPECT_EQ(chip::Crypto::DRBG_get_bytes(vectors[i].resumptionId.data(), vectors[i].resumptionId.size()), CHIP_NO_ERROR);
*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());
EXPECT_EQ(chip::Crypto::DRBG_get_bytes(vectors[i].sharedSecret.Bytes(), vectors[i].sharedSecret.Length()), CHIP_NO_ERROR);
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)
{
EXPECT_EQ(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;
EXPECT_EQ(
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.
EXPECT_EQ(sessionStorage.FindByScopedNodeId(vector.node, outResumptionId, outSharedSecret, outCats), CHIP_NO_ERROR);
EXPECT_EQ(memcmp(vector.resumptionId.data(), outResumptionId.data(), vector.resumptionId.size()), 0);
EXPECT_EQ(memcmp(vector.sharedSecret.ConstBytes(), outSharedSecret.ConstBytes(), vector.sharedSecret.Length()), 0);
EXPECT_EQ(vector.cats.values[0], outCats.values[0]);
EXPECT_EQ(vector.cats.values[1], outCats.values[1]);
EXPECT_EQ(vector.cats.values[2], outCats.values[2]);
// Validate retrieval by resumption ID.
EXPECT_EQ(sessionStorage.FindByResumptionId(vector.resumptionId, outNode, outSharedSecret, outCats), CHIP_NO_ERROR);
EXPECT_EQ(vector.node, outNode);
EXPECT_EQ(memcmp(vector.sharedSecret.Bytes(), outSharedSecret.ConstBytes(), vector.sharedSecret.Length()), 0);
EXPECT_EQ(vector.cats.values[0], outCats.values[0]);
EXPECT_EQ(vector.cats.values[1], outCats.values[1]);
EXPECT_EQ(vector.cats.values[2], outCats.values[2]);
}
}
TEST(TestDefaultSessionResumptionStorage, TestInPlaceSave)
{
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)
{
EXPECT_EQ(chip::Crypto::DRBG_get_bytes(vectors[i].resumptionId.data(), vectors[i].resumptionId.size()), CHIP_NO_ERROR);
*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());
EXPECT_EQ(chip::Crypto::DRBG_get_bytes(vectors[i].sharedSecret.Bytes(), vectors[i].sharedSecret.Length()), CHIP_NO_ERROR);
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)
{
EXPECT_EQ(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.
EXPECT_EQ(sessionStorage.FindByScopedNodeId(vectors[i].node, outResumptionId, outSharedSecret, outCats), CHIP_NO_ERROR);
EXPECT_EQ(memcmp(vectors[i].resumptionId.data(), outResumptionId.data(), vectors[i].resumptionId.size()), 0);
EXPECT_EQ(memcmp(vectors[i].sharedSecret.ConstBytes(), outSharedSecret.ConstBytes(), vectors[i].sharedSecret.Length()), 0);
EXPECT_EQ(vectors[i].cats.values[0], outCats.values[0]);
EXPECT_EQ(vectors[i].cats.values[1], outCats.values[1]);
EXPECT_EQ(vectors[i].cats.values[2], outCats.values[2]);
// Validate retrieval by resumption ID.
EXPECT_EQ(sessionStorage.FindByResumptionId(vectors[i].resumptionId, outNode, outSharedSecret, outCats), CHIP_NO_ERROR);
EXPECT_EQ(vectors[i].node, outNode);
EXPECT_EQ(memcmp(vectors[i].sharedSecret.ConstBytes(), outSharedSecret.ConstBytes(), vectors[i].sharedSecret.Length()), 0);
EXPECT_EQ(vectors[i].cats.values[0], outCats.values[0]);
EXPECT_EQ(vectors[i].cats.values[1], outCats.values[1]);
EXPECT_EQ(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)
{
EXPECT_EQ(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.
EXPECT_EQ(sessionStorage.FindByScopedNodeId(vectors[i].node, outResumptionId, outSharedSecret, outCats), CHIP_NO_ERROR);
EXPECT_EQ(memcmp(vectors[i].resumptionId.data(), outResumptionId.data(), vectors[i].resumptionId.size()), 0);
EXPECT_EQ(memcmp(vectors[i].sharedSecret.ConstBytes(), outSharedSecret.ConstBytes(), vectors[i].sharedSecret.Length()), 0);
EXPECT_EQ(vectors[i].cats.values[0], outCats.values[0]);
EXPECT_EQ(vectors[i].cats.values[1], outCats.values[1]);
EXPECT_EQ(vectors[i].cats.values[2], outCats.values[2]);
// Validate retrieval by resumption ID.
EXPECT_EQ(sessionStorage.FindByResumptionId(vectors[i].resumptionId, outNode, outSharedSecret, outCats), CHIP_NO_ERROR);
EXPECT_EQ(vectors[i].node, outNode);
EXPECT_EQ(memcmp(vectors[i].sharedSecret.ConstBytes(), outSharedSecret.ConstBytes(), vectors[i].sharedSecret.Length()), 0);
EXPECT_EQ(vectors[i].cats.values[0], outCats.values[0]);
EXPECT_EQ(vectors[i].cats.values[1], outCats.values[1]);
EXPECT_EQ(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)
{
EXPECT_EQ(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.
EXPECT_NE(sessionStorage.FindByScopedNodeId(vector.node, outResumptionId, outSharedSecret, outCats), CHIP_NO_ERROR);
// Verify all records for all resumption IDs are gone.
EXPECT_NE(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);
EXPECT_EQ(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);
EXPECT_EQ(rv, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
}
TEST(TestDefaultSessionResumptionStorage, TestDelete)
{
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());
EXPECT_EQ(chip::Crypto::DRBG_get_bytes(sharedSecret.Bytes(), sharedSecret.Length()), CHIP_NO_ERROR);
// Populate test vectors.
for (size_t i = 0; i < ArraySize(vectors); ++i)
{
EXPECT_EQ(chip::Crypto::DRBG_get_bytes(vectors[i].resumptionId.data(), vectors[i].resumptionId.size()), CHIP_NO_ERROR);
*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)
{
EXPECT_EQ(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;
EXPECT_EQ(sessionStorage.Delete(vector.node), CHIP_NO_ERROR);
EXPECT_NE(sessionStorage.FindByScopedNodeId(vector.node, outResumptionId, outSharedSecret, outCats), CHIP_NO_ERROR);
EXPECT_NE(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);
EXPECT_EQ(rv, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
{
auto rv = storage.SyncGetKeyValue(chip::SimpleSessionResumptionStorage::GetStorageKey(vector.resumptionId).KeyName(),
nullptr, size);
EXPECT_EQ(rv, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
}
}
TEST(TestDefaultSessionResumptionStorage, TestDeleteAll)
{
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());
EXPECT_EQ(chip::Crypto::DRBG_get_bytes(sharedSecret.Bytes(), sharedSecret.Length()), CHIP_NO_ERROR);
// 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)
{
EXPECT_EQ(
chip::Crypto::DRBG_get_bytes(vectors[i].nodes[j].resumptionId.data(), vectors[i].nodes[j].resumptionId.size()),
CHIP_NO_ERROR);
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)
{
EXPECT_EQ(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)
{
EXPECT_EQ(sessionStorage.FindByScopedNodeId(node.node, outResumptionId, outSharedSecret, outCats), CHIP_NO_ERROR);
}
// Delete fabric.
EXPECT_EQ(sessionStorage.DeleteAll(vector.fabricIndex), CHIP_NO_ERROR);
// Verify fabric node entries no longer exist.
for (const auto & node : vector.nodes)
{
EXPECT_NE(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);
EXPECT_EQ(rv, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
{
auto rv = storage.SyncGetKeyValue(chip::SimpleSessionResumptionStorage::GetStorageKey(node.resumptionId).KeyName(),
nullptr, size);
EXPECT_EQ(rv, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
}
}
}