Skip to content

Commit

Permalink
Add abstract Session Resumption Storage base class (#18021)
Browse files Browse the repository at this point in the history
* Add abstract Session Resumption Storage base class

The SessionResumptionStorage class has some pure virtual methods, but
still includes concrete implementation.  The intended usage is for
inheriting classes to provide implementation of some of the lower-level
storage interfaces, which can for instance allow for different types of
fifo behavior.  The SimpleSessionResumptionStorage is an example of an
inheriting implementation.

The problem is that the concrete implementation in the base class
assumes and requires organization of session resumption data across
three separate kvstore tables.  This precludes atomic storage of session
resumption data, which may not be acceptable in some implementations.

This commit adds an abstract session resumption storage base class
so that implementations can fully control session resumption storage
behavior.

Testing: An AbstractSessionResumptionStorage test is added to
TestCASESession.  This tests the public interface that was previously
defined by SessionResumptionStorage, but for which there was not
existing coverage.

* per tcarmelveilleux, change class naming scheme

* per tcarmelveilleux, add Delete method to base class interface

* per tcarmelveilleux, add ReomveFabric method

* per tcarmelveilleux, s/RemoveFabric/DeleteAll, no Delete(node) in base class interface

* fix out-of-bounds memmove

* fix CI breakage

* updates per bzbarsky
  • Loading branch information
msandstedt authored and pull[bot] committed Aug 1, 2023
1 parent a5bcefe commit 4458517
Show file tree
Hide file tree
Showing 10 changed files with 634 additions and 67 deletions.
2 changes: 1 addition & 1 deletion src/app/tests/TestOperationalDeviceProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ void TestOperationalDeviceProxy_EstablishSessionDirectly(nlTestSuite * inSuite,
Platform::MemoryInit();
TestTransportMgr transportMgr;
SessionManager sessionManager;
SessionResumptionStorage sessionResumptionStorage;
SimpleSessionResumptionStorage sessionResumptionStorage;
ExchangeManager exchangeMgr;
Inet::UDPEndPointManagerImpl udpEndPointManager;
System::LayerImpl systemLayer;
Expand Down
3 changes: 2 additions & 1 deletion src/protocols/secure_channel/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ static_library("secure_channel") {
"CASEServer.h",
"CASESession.cpp",
"CASESession.h",
"DefaultSessionResumptionStorage.cpp",
"DefaultSessionResumptionStorage.h",
"PASESession.cpp",
"PASESession.h",
"RendezvousParameters.h",
"SessionEstablishmentDelegate.h",
"SessionEstablishmentExchangeDispatch.cpp",
"SessionEstablishmentExchangeDispatch.h",
"SessionResumptionStorage.cpp",
"SessionResumptionStorage.h",
"SimpleSessionResumptionStorage.cpp",
"SimpleSessionResumptionStorage.h",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,22 @@
* limitations under the License.
*/

/**
* @file
* This file defines the CHIP CASE Session object that provides
* APIs for constructing a secure session using a certificate from the device's
* operational credentials.
*/

#include <protocols/secure_channel/SessionResumptionStorage.h>
#include <protocols/secure_channel/DefaultSessionResumptionStorage.h>

#include <lib/support/Base64.h>
#include <lib/support/SafeInt.h>

namespace chip {

CHIP_ERROR SessionResumptionStorage::FindByScopedNodeId(const ScopedNodeId & node, ResumptionIdStorage & resumptionId,
Crypto::P256ECDHDerivedSecret & sharedSecret, CATValues & peerCATs)
CHIP_ERROR DefaultSessionResumptionStorage::FindByScopedNodeId(const ScopedNodeId & node, ResumptionIdStorage & resumptionId,
Crypto::P256ECDHDerivedSecret & sharedSecret, CATValues & peerCATs)
{
ReturnErrorOnFailure(LoadState(node, resumptionId, sharedSecret, peerCATs));
return CHIP_NO_ERROR;
}

CHIP_ERROR SessionResumptionStorage::FindByResumptionId(ConstResumptionIdView resumptionId, ScopedNodeId & node,
Crypto::P256ECDHDerivedSecret & sharedSecret, CATValues & peerCATs)
CHIP_ERROR DefaultSessionResumptionStorage::FindByResumptionId(ConstResumptionIdView resumptionId, ScopedNodeId & node,
Crypto::P256ECDHDerivedSecret & sharedSecret, CATValues & peerCATs)
{
ReturnErrorOnFailure(FindNodeByResumptionId(resumptionId, node));
ResumptionIdStorage tmpResumptionId;
Expand All @@ -47,14 +40,14 @@ CHIP_ERROR SessionResumptionStorage::FindByResumptionId(ConstResumptionIdView re
return CHIP_NO_ERROR;
}

CHIP_ERROR SessionResumptionStorage::FindNodeByResumptionId(ConstResumptionIdView resumptionId, ScopedNodeId & node)
CHIP_ERROR DefaultSessionResumptionStorage::FindNodeByResumptionId(ConstResumptionIdView resumptionId, ScopedNodeId & node)
{
ReturnErrorOnFailure(LoadLink(resumptionId, node));
return CHIP_NO_ERROR;
}

CHIP_ERROR SessionResumptionStorage::Save(const ScopedNodeId & node, ConstResumptionIdView resumptionId,
const Crypto::P256ECDHDerivedSecret & sharedSecret, const CATValues & peerCATs)
CHIP_ERROR DefaultSessionResumptionStorage::Save(const ScopedNodeId & node, ConstResumptionIdView resumptionId,
const Crypto::P256ECDHDerivedSecret & sharedSecret, const CATValues & peerCATs)
{
SessionIndex index;
ReturnErrorOnFailure(LoadIndex(index));
Expand All @@ -75,7 +68,7 @@ CHIP_ERROR SessionResumptionStorage::Save(const ScopedNodeId & node, ConstResump
return CHIP_NO_ERROR;
}

CHIP_ERROR SessionResumptionStorage::Delete(const ScopedNodeId & node)
CHIP_ERROR DefaultSessionResumptionStorage::Delete(const ScopedNodeId & node)
{
SessionIndex index;
ReturnErrorOnFailure(LoadIndex(index));
Expand Down Expand Up @@ -150,4 +143,77 @@ CHIP_ERROR SessionResumptionStorage::Delete(const ScopedNodeId & node)
return CHIP_NO_ERROR;
}

CHIP_ERROR DefaultSessionResumptionStorage::DeleteAll(FabricIndex fabricIndex)
{
CHIP_ERROR stickyErr = CHIP_NO_ERROR;
size_t found = 0;
SessionIndex index;
ReturnErrorOnFailure(LoadIndex(index));
size_t initialSize = index.mSize;
for (size_t i = 0; i < initialSize; ++i)
{
CHIP_ERROR err = CHIP_NO_ERROR;
size_t cur = i - found;
size_t remain = initialSize - i;
ResumptionIdStorage resumptionId;
Crypto::P256ECDHDerivedSecret sharedSecret;
CATValues peerCATs;
if (index.mNodes[cur].GetFabricIndex() != fabricIndex)
{
continue;
}
err = LoadState(index.mNodes[cur], resumptionId, sharedSecret, peerCATs);
stickyErr = stickyErr == CHIP_NO_ERROR ? err : stickyErr;
if (err != CHIP_NO_ERROR)
{
ChipLogError(SecureChannel,
"Session resumption cache deletion partially failed for fabric index %u, "
"unable to load node state: %" CHIP_ERROR_FORMAT,
fabricIndex, err.Format());
continue;
}
err = DeleteLink(resumptionId);
stickyErr = stickyErr == CHIP_NO_ERROR ? err : stickyErr;
if (err != CHIP_NO_ERROR)
{
ChipLogError(SecureChannel,
"Session resumption cache deletion partially failed for fabric index %u, "
"unable to delete node link: %" CHIP_ERROR_FORMAT,
fabricIndex, err.Format());
continue;
}
err = DeleteState(index.mNodes[cur]);
stickyErr = stickyErr == CHIP_NO_ERROR ? err : stickyErr;
if (err != CHIP_NO_ERROR)
{
ChipLogError(SecureChannel,
"Session resumption cache is in an inconsistent state! "
"Unable to delete node state during attempted deletion of fabric index %u: %" CHIP_ERROR_FORMAT,
fabricIndex, err.Format());
continue;
}
++found;
--remain;
if (remain)
{
memmove(&index.mNodes[cur], &index.mNodes[cur + 1], remain * sizeof(index.mNodes[0]));
}
}
if (found)
{
index.mSize -= found;
CHIP_ERROR err = SaveIndex(index);
stickyErr = stickyErr == CHIP_NO_ERROR ? err : stickyErr;
if (err != CHIP_NO_ERROR)
{
ChipLogError(
SecureChannel,
"Session resumption cache is in an inconsistent state! "
"Unable to save session resumption index during atetmpted deletion of fabric index %u: %" CHIP_ERROR_FORMAT,
fabricIndex, err.Format());
}
}
return stickyErr;
}

} // namespace chip
71 changes: 71 additions & 0 deletions src/protocols/secure_channel/DefaultSessionResumptionStorage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (c) 2022 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.
*/

#pragma once

#include <protocols/secure_channel/SessionResumptionStorage.h>

namespace chip {

/**
* @brief Stores assets for session resumption. The resumption data are indexed by 2 indexes: ScopedNodeId and ResumptionId. The
* index of ScopedNodeId is used when initiating a CASE session, it will look up the storage and check whether it is able to
* resume a previous session. The index of ResumptionId is used when receiving a Sigma1 with ResumptionId.
*
* The implementation saves 2 maps:
* * <FabricIndex, PeerNodeId> => <ResumptionId, ShareSecret, PeerCATs>
* * <ResumptionId> => <FabricIndex, PeerNodeId>
*/
class DefaultSessionResumptionStorage : public SessionResumptionStorage
{
public:
using ResumptionIdView = FixedSpan<uint8_t, kResumptionIdSize>;

struct SessionIndex
{
size_t mSize;
ScopedNodeId mNodes[CHIP_CONFIG_CASE_SESSION_RESUME_CACHE_SIZE];
};

virtual ~DefaultSessionResumptionStorage() {}

CHIP_ERROR FindByScopedNodeId(const ScopedNodeId & node, ResumptionIdStorage & resumptionId,
Crypto::P256ECDHDerivedSecret & sharedSecret, CATValues & peerCATs) override;
CHIP_ERROR FindByResumptionId(ConstResumptionIdView resumptionId, ScopedNodeId & node,
Crypto::P256ECDHDerivedSecret & sharedSecret, CATValues & peerCATs) override;
CHIP_ERROR FindNodeByResumptionId(ConstResumptionIdView resumptionId, ScopedNodeId & node);
CHIP_ERROR Save(const ScopedNodeId & node, ConstResumptionIdView resumptionId,
const Crypto::P256ECDHDerivedSecret & sharedSecret, const CATValues & peerCATs) override;
CHIP_ERROR Delete(const ScopedNodeId & node);
CHIP_ERROR DeleteAll(FabricIndex fabricIndex) override;

protected:
CHIP_ERROR virtual SaveIndex(const SessionIndex & index) = 0;
CHIP_ERROR virtual LoadIndex(SessionIndex & index) = 0;

CHIP_ERROR virtual SaveLink(ConstResumptionIdView resumptionId, const ScopedNodeId & node) = 0;
CHIP_ERROR virtual LoadLink(ConstResumptionIdView resumptionId, ScopedNodeId & node) = 0;
CHIP_ERROR virtual DeleteLink(ConstResumptionIdView resumptionId) = 0;

CHIP_ERROR virtual SaveState(const ScopedNodeId & node, ConstResumptionIdView resumptionId,
const Crypto::P256ECDHDerivedSecret & sharedSecret, const CATValues & peerCATs) = 0;
CHIP_ERROR virtual LoadState(const ScopedNodeId & node, ResumptionIdStorage & resumptionId,
Crypto::P256ECDHDerivedSecret & sharedSecret, CATValues & peerCATs) = 0;
CHIP_ERROR virtual DeleteState(const ScopedNodeId & node) = 0;
};

} // namespace chip
96 changes: 54 additions & 42 deletions src/protocols/secure_channel/SessionResumptionStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@
* limitations under the License.
*/

/**
* @file
* This file defines the CHIP CASE Session object that provides
* APIs for constructing a secure session using a certificate from the device's
* operational credentials.
*/

#pragma once

#include <crypto/CHIPCryptoPAL.h>
Expand All @@ -31,52 +24,71 @@
namespace chip {

/**
* @brief Stores assets for session resumption. The resumption data are indexed by 2 indexes: ScopedNodeId and ResumptionId. The
* index of ScopedNodeId is used when initiating a CASE session, it will look up the storage and check whether it is able to
* resume a previous session. The index of ResumptionId is used when receiving a Sigma1 with ResumptionId.
* @brief Interface to store and recover assets for session resumption. The
* resumption data are indexed by 2 parameters: ScopedNodeId and
* ResumptionId. The index on ScopedNodeId is used when initiating a CASE
* session. It allows the caller to query storage to check whether there is a
* previous session with the given peer for which session resumption may be
* attempted. The index on ResumptionId is used when receiving a Sigma1 with
* ResumptionId.
*
* The implementation saves 2 maps:
* * <FabricIndex, PeerNodeId> => <ResumptionId, ShareSecret, PeerCATs>
* * <ResumptionId> => <FabricIndex, PeerNodeId>
*/
class SessionResumptionStorage
{
public:
static constexpr size_t kResumptionIdSize = 16;
using ResumptionIdStorage = std::array<uint8_t, kResumptionIdSize>;
using ResumptionIdView = FixedSpan<uint8_t, kResumptionIdSize>;
using ConstResumptionIdView = FixedSpan<const uint8_t, kResumptionIdSize>;

struct SessionIndex
{
size_t mSize;
ScopedNodeId mNodes[CHIP_CONFIG_CASE_SESSION_RESUME_CACHE_SIZE];
};

virtual ~SessionResumptionStorage() {}

CHIP_ERROR FindByScopedNodeId(const ScopedNodeId & node, ResumptionIdStorage & resumptionId,
Crypto::P256ECDHDerivedSecret & sharedSecret, CATValues & peerCATs);
CHIP_ERROR FindByResumptionId(ConstResumptionIdView resumptionId, ScopedNodeId & node,
Crypto::P256ECDHDerivedSecret & sharedSecret, CATValues & peerCATs);
CHIP_ERROR FindNodeByResumptionId(ConstResumptionIdView resumptionId, ScopedNodeId & node);
CHIP_ERROR Save(const ScopedNodeId & node, ConstResumptionIdView resumptionId,
const Crypto::P256ECDHDerivedSecret & sharedSecret, const CATValues & peerCATs);
CHIP_ERROR Delete(const ScopedNodeId & node);

protected:
CHIP_ERROR virtual SaveIndex(const SessionIndex & index) = 0;
CHIP_ERROR virtual LoadIndex(SessionIndex & index) = 0;
virtual ~SessionResumptionStorage(){};

CHIP_ERROR virtual SaveLink(ConstResumptionIdView resumptionId, const ScopedNodeId & node) = 0;
CHIP_ERROR virtual LoadLink(ConstResumptionIdView resumptionId, ScopedNodeId & node) = 0;
CHIP_ERROR virtual DeleteLink(ConstResumptionIdView resumptionId) = 0;
/**
* Recover session resumption ID, shared secret and CAT values for a given
* fabric-scoped node identity.
*
* @param node the node for which to recover session resumption information
* @param resumptionId (out) recovered session resumption ID
* @param sharedSecret (out) recovered session shared secret
* @param peerCATs (out) recovered CATs for the session peer
* @return CHIP_NO_ERROR on success, CHIP_ERROR_KEY_NOT_FOUND if no session resumption information can be found, else an
* appropriate CHIP error on failure
*/
virtual CHIP_ERROR FindByScopedNodeId(const ScopedNodeId & node, ResumptionIdStorage & resumptionId,
Crypto::P256ECDHDerivedSecret & sharedSecret, CATValues & peerCATs) = 0;
/**
* Recover session shared secret, fabric-scoped node identity and CAT values
* for a given session resumption ID.
*
* @param resumptionId the session resumption ID for which to recover session resumption information
* @param node (out) the peer node associated with the session resumption ID
* @param sharedSecret (out) recovered session shared secret
* @param peerCATs (out) recovered CATs for the session peer
* @return CHIP_NO_ERROR on success, CHIP_ERROR_KEY_NOT_FOUND if no session resumption information can be found, else an
* appropriate CHIP error on failure
*/
virtual CHIP_ERROR FindByResumptionId(ConstResumptionIdView resumptionId, ScopedNodeId & node,
Crypto::P256ECDHDerivedSecret & sharedSecret, CATValues & peerCATs) = 0;
/**
* Save session resumption information to storage.
*
* @param resumptionId the session resumption ID for the current session
* @param node the peer node for the session
* @param sharedSecret the session shared secret
* @param peerCATs the CATs of the session peer
* @return CHIP_NO_ERROR on success, else an appropriate CHIP error on failure
*/
virtual CHIP_ERROR Save(const ScopedNodeId & node, ConstResumptionIdView resumptionId,
const Crypto::P256ECDHDerivedSecret & sharedSecret, const CATValues & peerCATs) = 0;

CHIP_ERROR virtual SaveState(const ScopedNodeId & node, ConstResumptionIdView resumptionId,
const Crypto::P256ECDHDerivedSecret & sharedSecret, const CATValues & peerCATs) = 0;
CHIP_ERROR virtual LoadState(const ScopedNodeId & node, ResumptionIdStorage & resumptionId,
Crypto::P256ECDHDerivedSecret & sharedSecret, CATValues & peerCATs) = 0;
CHIP_ERROR virtual DeleteState(const ScopedNodeId & node) = 0;
/**
* Remove all session resumption information associated with the specified
* fabric index. If no entries for the fabric index exist, this is a no-op
* and is considered successful.
*
* @param fabricIndex the index of the fabric for which to remove session resumption information
* @return CHIP_NO_ERROR on success, else an appropriate CHIP error on failure
*/
virtual CHIP_ERROR DeleteAll(FabricIndex fabricIndex) = 0;
};

} // namespace chip
4 changes: 2 additions & 2 deletions src/protocols/secure_channel/SimpleSessionResumptionStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@

#include <lib/core/CHIPTLV.h>
#include <lib/support/DefaultStorageKeyAllocator.h>
#include <protocols/secure_channel/SessionResumptionStorage.h>
#include <protocols/secure_channel/DefaultSessionResumptionStorage.h>

namespace chip {

/**
* An example SessionResumptionStorage using PersistentStorageDelegate as it backend.
*/
class SimpleSessionResumptionStorage : public SessionResumptionStorage
class SimpleSessionResumptionStorage : public DefaultSessionResumptionStorage
{
public:
CHIP_ERROR Init(PersistentStorageDelegate * storage)
Expand Down
1 change: 1 addition & 0 deletions src/protocols/secure_channel/tests/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ chip_test_suite("tests") {

# TODO - Fix Message Counter Sync to use group key
# "TestMessageCounterManager.cpp",
"TestDefaultSessionResumptionStorage.cpp",
"TestPASESession.cpp",
"TestSimpleSessionResumptionStorage.cpp",
"TestStatusReport.cpp",
Expand Down
Loading

0 comments on commit 4458517

Please sign in to comment.