blob: 63b1cd4ca950da081d745c43009cceff74375f36 [file] [log] [blame]
/*
*
* Copyright (c) 2013-2017 Nest Labs, Inc.
* 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.
*/
/**
* @file
* This file implements objects for managing the active node
* state necessary to participate in a Weave fabric.
*
*/
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif // __STDC_LIMIT_MACROS
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif // __STDC_FORMAT_MACROS
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <Weave/Core/WeaveCore.h>
#include <Weave/Core/WeaveKeyIds.h>
#include <Weave/Profiles/security/WeaveApplicationKeys.h>
#include <Weave/Profiles/security/WeaveDummyGroupKeyStore.h>
#include <Weave/Profiles/WeaveProfiles.h>
#include <Weave/Profiles/security/WeaveSecurity.h>
#include <Weave/Support/crypto/WeaveCrypto.h>
#include <Weave/Profiles/fabric-provisioning/FabricProvisioning.h>
#include <Weave/Support/CodeUtils.h>
#include <Weave/Support/RandUtils.h>
#include <Weave/Support/logging/WeaveLogging.h>
#if HAVE_NEW
#include <new>
#else
inline void * operator new (size_t, void * p) throw() { return p; }
inline void * operator new[] (size_t, void * p) throw() { return p; }
#endif
namespace nl {
namespace Weave {
using namespace nl::Weave::TLV;
using namespace nl::Weave::Encoding;
using namespace nl::Weave::Crypto;
using namespace nl::Weave::Profiles;
using namespace nl::Weave::Profiles::FabricProvisioning;
using namespace nl::Weave::Profiles::Security::AppKeys;
#if WEAVE_CONFIG_SECURITY_TEST_MODE
#define EXCLS "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
#pragma message (EXCLS "\n!!!! WARNING - WEAVE SECURITY TEST MODE ENABLED !!!!" EXCLS)
#undef EXCLS
#endif
#ifndef DEFINE_ALIGNED_VAR
#define DEFINE_ALIGNED_VAR(varName, bytes, alignment_type) \
alignment_type varName[(((bytes)+(sizeof(alignment_type)-1))/sizeof(alignment_type))]
#endif
// Identifies the scheme used to rotate fabric secret. The use of this
// identifier is deprecated because fabric secret value never rotates.
//
// NOTE: This value is communicated over the wire--do not renumber.
//
typedef uint8_t FabricSecretRotationScheme;
enum
{
kDeprecatedRotationScheme = 0x00
};
// Key diversifier used for Weave message encryption key derivation.
const uint8_t kWeaveMsgEncAppKeyDiversifier[] = { 0xB1, 0x1D, 0xAE, 0x5B };
/**
* Initialize a WeaveSessionKey object.
*/
void WeaveSessionKey::Init(void)
{
NodeId = kNodeIdNotSpecified;
NextMsgId.Init(0);
MaxRcvdMsgId = 0;
BoundCon = NULL;
RcvFlags = 0;
AuthMode = kWeaveAuthMode_NotSpecified;
memset(&MsgEncKey, 0, sizeof(MsgEncKey));
ReserveCount = 0;
Flags = 0;
}
/**
* Reset a WeaveSessionKey object.
*/
void WeaveSessionKey::Clear(void)
{
Init();
ClearSecretData((uint8_t *)&MsgEncKey.EncKey, sizeof(MsgEncKey.EncKey));
}
/**
* fn bool WeaveSessionKey::IsAllocated() const
*
* Returns true if the WeaveSessionKey object is allocated.
*/
/**
* fn bool WeaveSessionKey::IsKeySet() const
*
* Returns true if the encryption key value has been set in a WeaveSessionKey object.
*/
/**
* fn bool WeaveSessionKey::IsLocallyInitiated() const
*
* Returns true if the session was initiated by the local node.
*/
/*
* fn void WeaveSessionKey::SetLocallyInitiated(bool val)
*
* Sets a flag indicating whether the session was initiated by the local node.
*/
/*
* fn bool WeaveSessionKey::IsSharedSession() const
*
* Returns true the session is a shared--i.e. can be used for multiplexed communication with different peer node ids.
*/
/**
* fn void WeaveSessionKey::SetSharedSession(bool val)
*
* Sets a flag indicating whether the session is a shared session.
*/
/**
* fn bool WeaveSessionKey::IsRemoveOnIdle() const
*
* Returns true if the session is flagged for automatic removal when idle for a period of time.
*/
/**
* fn void WeaveSessionKey::SetRemoveOnIdle(bool val)
*
* Sets a flag indicating whether the session should be automatically removed after a period of idle time.
*/
/**
* fn bool WeaveSessionKey::IsRecentlyActive() const
*
* Returns true if the session has been active in the recent past.
*/
/**
* fn void WeaveSessionKey::MarkRecentlyActive()
*
* Signals the session as having been active in the recent past.
*/
/**
* fn void WeaveSessionKey::ClearRecentlyActive()
*
* Signals the session as NOT having been active in the recent past.
*/
WeaveFabricState::WeaveFabricState()
{
State = kState_NotInitialized;
}
WEAVE_ERROR WeaveFabricState::Init()
{
static DEFINE_ALIGNED_VAR(sDummyGroupKeyStore, sizeof(DummyGroupKeyStore), void*);
return Init(new (&sDummyGroupKeyStore) DummyGroupKeyStore());
}
WEAVE_ERROR WeaveFabricState::Init(GroupKeyStoreBase *groupKeyStore)
{
if (State != kState_NotInitialized)
return WEAVE_ERROR_INCORRECT_STATE;
if (groupKeyStore == NULL)
return WEAVE_ERROR_INVALID_ARGUMENT;
#ifdef WEAVE_NON_PRODUCTION_MARKER
// This is a trick to force the linker to include the WEAVE_NON_PRODUCTION_MARKER symbol
// in the linked output. (Note that the test will never evaluate to true).
if (WEAVE_NON_PRODUCTION_MARKER[0] == 0)
return WEAVE_ERROR_INCORRECT_STATE;
#endif
GroupKeyStore = groupKeyStore;
FabricId = kFabricIdNotSpecified;
LocalNodeId = 1;
PairingCode = NULL;
DefaultSubnet = kWeaveSubnetId_PrimaryWiFi;
PeerCount = 0;
NextUnencUDPMsgId.Init(GetRandU32());
NextUnencTCPMsgId.Init(0);
for (int i = 0; i < WEAVE_CONFIG_MAX_SESSION_KEYS; i++)
SessionKeys[i].Init();
#if WEAVE_CONFIG_USE_APP_GROUP_KEYS_FOR_MSG_ENC
WEAVE_ERROR err = NextGroupKeyMsgId.Init(WEAVE_CONFIG_PERSISTED_STORAGE_ENC_MSG_CNTR_ID, WEAVE_CONFIG_PERSISTED_STORAGE_ENC_MSG_CNTR_EPOCH);
if (err != WEAVE_NO_ERROR)
return err;
GroupKeyMsgIdFreshWindowStart = 0;
MsgCounterSyncStatus = 0;
AppKeyCache.Init();
#endif
memset(&PeerStates, 0, sizeof(PeerStates));
Delegate = NULL;
memset(SharedSessionsNodes, 0, sizeof(SharedSessionsNodes));
#if WEAVE_CONFIG_SECURITY_TEST_MODE
DebugFabricId = 0;
LogKeys = false;
UseTestKey = false; // DEPRECATED -- Temporarily retained for API compatibility
AutoCreateKeys = false; // DEPRECATED -- Temporarily retained for API compatibility
#endif // WEAVE_CONFIG_SECURITY_TEST_MODE
#if WEAVE_CONFIG_ENABLE_TARGETED_LISTEN
ListenIPv4Addr = IPAddress::Any;
ListenIPv6Addr = IPAddress::Any;
#if defined(DEBUG) && !WEAVE_SYSTEM_CONFIG_USE_LWIP
{
const char *envVal = getenv("WEAVE_IPV6_LISTEN_ADDR");
if (envVal != NULL)
IPAddress::FromString(envVal, ListenIPv6Addr);
envVal = getenv("WEAVE_IPV4_LISTEN_ADDR");
if (envVal != NULL)
IPAddress::FromString(envVal, ListenIPv4Addr);
}
#endif
#endif
State = kState_Initialized;
return WEAVE_NO_ERROR;
}
WEAVE_ERROR WeaveFabricState::Shutdown()
{
State = kState_NotInitialized;
#if WEAVE_CONFIG_USE_APP_GROUP_KEYS_FOR_MSG_ENC
AppKeyCache.Shutdown();
#endif
return WEAVE_NO_ERROR;
}
WEAVE_ERROR WeaveFabricState::AllocSessionKey(uint64_t peerNodeId, uint16_t keyId, WeaveConnection *boundCon, WeaveSessionKey *& sessionKey)
{
WEAVE_ERROR err;
bool chooseRandomKeyId = (keyId == WeaveKeyId::kNone);
while (true)
{
if (chooseRandomKeyId)
keyId = WeaveKeyId::MakeSessionKeyId(GetRandU16());
err = FindSessionKey(keyId, peerNodeId, true, sessionKey);
if (err != WEAVE_NO_ERROR)
return err;
if (!sessionKey->IsAllocated())
break;
if (!chooseRandomKeyId)
return WEAVE_ERROR_DUPLICATE_KEY_ID;
}
sessionKey->MsgEncKey.KeyId = keyId;
sessionKey->NodeId = peerNodeId;
sessionKey->MsgEncKey.EncType = kWeaveEncryptionType_None;
sessionKey->NextMsgId.Init(UINT32_MAX);
sessionKey->MaxRcvdMsgId = UINT32_MAX;
sessionKey->BoundCon = boundCon;
sessionKey->RcvFlags = 0;
sessionKey->Flags = WeaveSessionKey::kFlag_RecentlyActive;
sessionKey->ReserveCount = 1;
return WEAVE_NO_ERROR;
}
WEAVE_ERROR WeaveFabricState::SetSessionKey(uint16_t keyId, uint64_t peerNodeId, uint8_t encType, WeaveAuthMode authMode, const WeaveEncryptionKey *encKey)
{
WEAVE_ERROR err;
WeaveSessionKey *sessionKey;
err = FindSessionKey(keyId, peerNodeId, false, sessionKey);
SuccessOrExit(err);
SetSessionKey(sessionKey, encType, authMode, encKey);
exit:
return err;
}
void WeaveFabricState::SetSessionKey(WeaveSessionKey *sessionKey, uint8_t encType, WeaveAuthMode authMode, const WeaveEncryptionKey *encKey)
{
sessionKey->MsgEncKey.EncType = encType;
sessionKey->MsgEncKey.EncKey = *encKey;
sessionKey->NextMsgId.Init(0);
sessionKey->MaxRcvdMsgId = 0;
sessionKey->RcvFlags = 0;
sessionKey->AuthMode = authMode;
#if WEAVE_CONFIG_SECURITY_TEST_MODE && WEAVE_DETAIL_LOGGING
if (LogKeys)
{
char keyString[kMaxEncKeyStringSize];
WeaveEncryptionKeyToString(encType, *encKey, keyString, sizeof(keyString));
WeaveLogDetail(MessageLayer, "Message Encryption Key: Id=%04" PRIX16 " Type=SessionKey Peer=%016" PRIX64 " EncType=%02" PRIX8 " Key=%s",
sessionKey->MsgEncKey.KeyId, sessionKey->NodeId, encType, keyString);
}
#endif // WEAVE_CONFIG_SECURITY_TEST_MODE
}
WEAVE_ERROR WeaveFabricState::RemoveSessionKey(uint16_t keyId, uint64_t peerNodeId)
{
WEAVE_ERROR err;
WeaveSessionKey *sessionKey;
err = FindSessionKey(keyId, peerNodeId, false, sessionKey);
SuccessOrExit(err);
RemoveSessionKey(sessionKey);
exit:
return err;
}
void WeaveFabricState::RemoveSessionKey(WeaveSessionKey *sessionKey, bool wasIdle)
{
WeaveLogDetail(MessageLayer, "Removing %ssession key: Id=%04" PRIX16 " Peer=%016" PRIX64,
(wasIdle) ? "idle " : "", sessionKey->MsgEncKey.KeyId, sessionKey->NodeId);
if (sessionKey->IsSharedSession())
{
SharedSessionEndNode *endNode = SharedSessionsNodes;
// Clear all information about shared session end nodes.
for (int i = 0; i < WEAVE_CONFIG_MAX_SHARED_SESSIONS_END_NODES; i++, endNode++)
{
if (endNode->SessionKey == sessionKey)
{
memset((uint8_t *)endNode, 0, sizeof(SharedSessionEndNode));
}
}
}
sessionKey->Clear();
}
WEAVE_ERROR WeaveFabricState::GetSessionKey(uint16_t keyId, uint64_t peerNodeId, WeaveSessionKey *& outSessionKey)
{
return FindSessionKey(keyId, peerNodeId, false, outSessionKey);
}
/**
* Search the session keys table for an established shared session key that targets the specified
* terminating node and matches the given auth mode and encryption type.
*
* @param[in] terminatingNodeId The node identifier of the session terminator.
* @param[in] authMode The desired session authentication mode.
* @param[in] encType The desired message encryption type.
*
* @retval WeaveSessionKey * A pointer to a WeaveSessionKey object representing the matching
* shared session; or NULL if no matching session was found.
*
*/
WeaveSessionKey *WeaveFabricState::FindSharedSession(uint64_t terminatingNodeId, WeaveAuthMode authMode, uint8_t encType)
{
WeaveSessionKey *sessionKey;
// Search the session keys table for an established shared session key that targets the specified
// terminating node and matches the given auth mode and encryption type.
sessionKey = SessionKeys;
for (int i = 0; i < WEAVE_CONFIG_MAX_SESSION_KEYS; i++, sessionKey++)
{
if (sessionKey->IsAllocated() && sessionKey->IsKeySet() && sessionKey->IsSharedSession() &&
sessionKey->NodeId == terminatingNodeId && sessionKey->AuthMode == authMode &&
sessionKey->MsgEncKey.EncType == encType)
{
return sessionKey;
}
}
return NULL;
}
/**
* This method checks whether secure session associated with the specified peer and keyId is shared.
*
* @param[in] keyId The session key identifier.
* @param[in] peerNodeId The node identifier of the peer.
*
* @retval bool Whether or not specified session is shared.
*
*/
bool WeaveFabricState::IsSharedSession(uint16_t keyId, uint64_t peerNodeId)
{
WEAVE_ERROR err;
WeaveSessionKey *sessionKey;
bool retVal = false;
err = FindSessionKey(keyId, peerNodeId, false, sessionKey);
SuccessOrExit(err);
retVal = sessionKey->IsSharedSession();
exit:
return retVal;
}
/**
* This method checks whether end node already recorded and returns true if
* node is found in the shared end node list.
*
* @param[in] endNodeId Identifier of the session end node.
* @param[in] sessionKey A pointer to the session key object.
*
* @retval bool Whether or not end node exists in the
* shared end nodes list.
*
*/
bool WeaveFabricState::FindSharedSessionEndNode(uint64_t endNodeId, const WeaveSessionKey *sessionKey)
{
SharedSessionEndNode *endNode = SharedSessionsNodes;
bool retVal = false;
for (int i = 0; i < WEAVE_CONFIG_MAX_SHARED_SESSIONS_END_NODES; i++, endNode++)
{
if (endNode->SessionKey == sessionKey && endNode->EndNodeId == endNodeId)
{
retVal = true;
break;
}
}
return retVal;
}
WEAVE_ERROR WeaveFabricState::AddSharedSessionEndNode(uint64_t endNodeId, uint64_t terminatingNodeId, uint16_t keyId)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
WeaveSessionKey *sessionKey;
err = FindSessionKey(keyId, endNodeId, false, sessionKey);
SuccessOrExit(err);
err = AddSharedSessionEndNode(sessionKey, endNodeId);
SuccessOrExit(err);
exit:
return err;
}
/**
* This method adds new end node to the shared end nodes record.
*
* @param[in] sessionKey The WeaveSessionKey object representing the session for which the new
* end node should be added.
* @param[in] endNodeId The node id of the session end node to be added.
*
* @retval #WEAVE_ERROR_TOO_MANY_SHARED_SESSION_END_NODES
* If there is no free space for a new entry in the shared end nodes list.
* @retval #WEAVE_NO_ERROR On success.
*
*/
WEAVE_ERROR WeaveFabricState::AddSharedSessionEndNode(WeaveSessionKey *sessionKey, uint64_t endNodeId)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
SharedSessionEndNode *endNode = SharedSessionsNodes;
SharedSessionEndNode *freeEndNode = NULL;
uint8_t endNodeCount = 0;
// No need to add new shared entry record if the end node is also the terminating node.
VerifyOrExit(endNodeId != sessionKey->NodeId, /* Exit without error. */);
// Check if entry already exists.
for (int i = 0; i < WEAVE_CONFIG_MAX_SHARED_SESSIONS_END_NODES; i++, endNode++)
{
if (endNode->SessionKey == sessionKey)
{
if (endNode->EndNodeId == endNodeId)
{
ExitNow();
}
else
{
endNodeCount++;
}
}
else if (endNode->EndNodeId == kNodeIdNotSpecified && freeEndNode == NULL)
{
freeEndNode = endNode;
}
}
// Verify that there is free entry in the list and that we don't exit maximum
// allowed number of end nodes per single shared session.
VerifyOrExit(freeEndNode != NULL && endNodeCount <= WEAVE_CONFIG_MAX_END_NODES_PER_SHARED_SESSION,
err = WEAVE_ERROR_TOO_MANY_SHARED_SESSION_END_NODES);
// Add new end node.
freeEndNode->EndNodeId = endNodeId;
freeEndNode->SessionKey = sessionKey;
exit:
return err;
}
/**
* This method returns all end node IDs that share specified session.
*
* @param[in] sessionKey A pointer to the session key object.
* @param[in] endNodeIds A pointer to buffer of node IDs.
* @param[in] endNodeIdsMaxCount The maximum number of node IDs that can fit in the buffer.
* @param[out] endNodeIdsCount Number of found end node IDs that share specified session.
*
* @retval #WEAVE_ERROR_BUFFER_TOO_SMALL
* If provided end node Ids buffer is not big enough.
* @retval #WEAVE_NO_ERROR On success.
*
*/
WEAVE_ERROR WeaveFabricState::GetSharedSessionEndNodeIds(const WeaveSessionKey *sessionKey, uint64_t *endNodeIds,
uint8_t endNodeIdsMaxCount, uint8_t& endNodeIdsCount)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
SharedSessionEndNode *endNode = SharedSessionsNodes;
endNodeIdsCount = 0;
for (int i = 0; i < WEAVE_CONFIG_MAX_SHARED_SESSIONS_END_NODES; i++, endNode++)
{
if (endNode->SessionKey == sessionKey)
{
VerifyOrExit(endNodeIdsCount < endNodeIdsMaxCount, err = WEAVE_ERROR_BUFFER_TOO_SMALL);
endNodeIds[endNodeIdsCount] = endNode->EndNodeId;
endNodeIdsCount++;
}
}
exit:
return err;
}
WEAVE_ERROR WeaveFabricState::GetSessionState(uint64_t remoteNodeId,
uint16_t keyId,
uint8_t encType,
WeaveConnection *con,
WeaveSessionState& outSessionState)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
PeerIndexType peerIndex;
switch (WeaveKeyId::GetType(keyId))
{
case WeaveKeyId::kType_None:
if (keyId != WeaveKeyId::kNone)
return WEAVE_ERROR_INVALID_KEY_ID;
if (encType != kWeaveEncryptionType_None)
return WEAVE_ERROR_WRONG_ENCRYPTION_TYPE;
if (con == NULL)
{
FindOrAllocPeerEntry(remoteNodeId, true, peerIndex);
outSessionState = WeaveSessionState(NULL, kWeaveAuthMode_Unauthenticated, &NextUnencUDPMsgId,
&PeerStates.MaxUnencUDPMsgIdRcvd[peerIndex], &PeerStates.UnencRcvFlags[peerIndex]);
}
else
outSessionState = WeaveSessionState(NULL, kWeaveAuthMode_Unauthenticated, &NextUnencTCPMsgId, NULL, NULL);
break;
case WeaveKeyId::kType_Session:
WeaveSessionKey *sessionKey;
err = FindSessionKey(keyId, remoteNodeId, false, sessionKey);
if (err != WEAVE_NO_ERROR)
return err;
if (sessionKey->MsgEncKey.EncType != encType)
return (sessionKey->MsgEncKey.EncType == kWeaveEncryptionType_None) ? WEAVE_ERROR_KEY_NOT_FOUND : WEAVE_ERROR_WRONG_ENCRYPTION_TYPE;
if (sessionKey->BoundCon != NULL && sessionKey->BoundCon != con)
return WEAVE_ERROR_INVALID_USE_OF_SESSION_KEY;
outSessionState = WeaveSessionState(&sessionKey->MsgEncKey, sessionKey->AuthMode, &sessionKey->NextMsgId, &sessionKey->MaxRcvdMsgId, &sessionKey->RcvFlags);
break;
#if WEAVE_CONFIG_USE_APP_GROUP_KEYS_FOR_MSG_ENC
case WeaveKeyId::kType_AppStaticKey:
case WeaveKeyId::kType_AppRotatingKey:
{
WeaveMsgEncryptionKey *applicationKey;
err = FindMsgEncAppKey(keyId, encType, applicationKey);
SuccessOrExit(err);
WeaveAuthMode authMode = GroupKeyAuthMode(keyId);
if (FindOrAllocPeerEntry(remoteNodeId, false, peerIndex))
outSessionState = WeaveSessionState(applicationKey, authMode, &NextGroupKeyMsgId, &PeerStates.MaxGroupKeyMsgIdRcvd[peerIndex], &PeerStates.GroupKeyRcvFlags[peerIndex]);
else
outSessionState = WeaveSessionState(applicationKey, authMode, &NextGroupKeyMsgId, NULL, NULL);
break;
}
#endif
default:
ExitNow(err = WEAVE_ERROR_UNKNOWN_KEY_TYPE);
}
exit:
return err;
}
IPAddress WeaveFabricState::SelectNodeAddress(uint64_t nodeId, uint16_t subnetId) const
{
// Translate 'any' node id to the IPv6 link-local all-nodes multicast address.
if (nodeId == kAnyNodeId)
{
return IPAddress::MakeIPv6Multicast(kIPv6MulticastScope_Link, kIPV6MulticastGroup_AllNodes);
}
else
{
return IPAddress::MakeULA(FabricId, subnetId, WeaveNodeIdToIPv6InterfaceId(nodeId));
}
}
IPAddress WeaveFabricState::SelectNodeAddress(uint64_t nodeId) const
{
return WeaveFabricState::SelectNodeAddress(nodeId, DefaultSubnet);
}
// Determine if an IP address represents an address of a node within the local fabric.
bool WeaveFabricState::IsFabricAddress(const IPAddress &addr) const
{
return addr.IsIPv6ULA() && FabricId != 0 && addr.GlobalId() == WeaveFabricIdToIPv6GlobalId(FabricId);
}
#if WEAVE_CONFIG_USE_APP_GROUP_KEYS_FOR_MSG_ENC
/**
* This method verifies that information received in the message counter synchronization
* message is valid (i.e. requestor message counter is fresh). On success, it initializes
* group key entry in the peer state table.
*
* @param[in] peerNodeId The node identifier of the peer.
* @param[in] peerMsgId Identifier of the received MsgCounterSync message.
* @param[in] requestorMsgCounter Requestor message counter field from the message counter
* synchronization message.
*
* @retval None.
*
*/
void WeaveFabricState::OnMsgCounterSyncRespRcvd(uint64_t peerNodeId, uint32_t peerMsgId, uint32_t requestorMsgCounter)
{
PeerIndexType peerIndex;
// If requestor message counter is fresh.
if (IsMsgCounterSyncReqInProgress() &&
(requestorMsgCounter >= GroupKeyMsgIdFreshWindowStart) &&
(requestorMsgCounter < NextGroupKeyMsgId.GetValue()))
{
FindOrAllocPeerEntry(peerNodeId, true, peerIndex);
// If not already synchronized.
if ((PeerStates.GroupKeyRcvFlags[peerIndex] & WeaveSessionState::kReceiveFlags_MessageIdSynchronized) == 0)
{
// Initialize group key entry in the peer state table.
PeerStates.GroupKeyRcvFlags[peerIndex] = WeaveSessionState::kReceiveFlags_MessageIdSynchronized;
PeerStates.MaxGroupKeyMsgIdRcvd[peerIndex] = peerMsgId;
#if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
// Clear MsgCounterSyncReq flag for all pending messages to that peer.
MessageLayer->ExchangeMgr->ClearMsgCounterSyncReq(peerNodeId);
#endif
}
}
return;
}
// Start message counter synchronization timer.
void WeaveFabricState::StartMsgCounterSyncTimer(void)
{
// Arm timer to call MsgCounterSyncRespTimeout after WEAVE_CONFIG_MSG_COUNTER_SYNC_RESP_TIMEOUT.
WEAVE_ERROR res = MessageLayer->SystemLayer->StartTimer((uint32_t)WEAVE_CONFIG_MSG_COUNTER_SYNC_RESP_TIMEOUT, OnMsgCounterSyncRespTimeout, this);
VerifyOrDie(res == WEAVE_NO_ERROR);
}
/**
* This method is called when message counter synchronization request is sent.
*
* @param[in] messageId Identification of the message with which message
* counter synchronization request is sent.
*
* @retval None.
*
*/
void WeaveFabricState::OnMsgCounterSyncReqSent(uint32_t messageId)
{
// Set ReqSentThisPeriod flag.
MsgCounterSyncStatus |= kFlag_ReqSentThisPeriod;
// If no message counter synchronization request in progress.
if (!IsMsgCounterSyncReqInProgress())
{
// Set ReqInProgress flag.
MsgCounterSyncStatus |= kFlag_ReqInProgress;
// Set fresh window start field.
GroupKeyMsgIdFreshWindowStart = messageId;
// Arm timer.
StartMsgCounterSyncTimer();
// Enable fast-poll mode for SEDs if not already enabled.
MessageLayer->SignalMessageLayerActivityChanged();
}
return;
}
// Handle MsgCounterSyncRespTimeout.
void WeaveFabricState::OnMsgCounterSyncRespTimeout(System::Layer* aSystemLayer, void* aAppState, System::Error aError)
{
WeaveFabricState* fabricState = reinterpret_cast<WeaveFabricState*>(aAppState);
uint32_t freshWindoWidth;
VerifyOrDie(fabricState != NULL && fabricState->MessageLayer->SystemLayer == aSystemLayer);
// If message counter synchronization request was sent this period.
if (fabricState->MsgCounterSyncStatus & fabricState->kFlag_ReqSentThisPeriod)
{
fabricState->GroupKeyMsgIdFreshWindowStart += (fabricState->MsgCounterSyncStatus & fabricState->kMask_GroupKeyMsgIdFreshWindowWidth);
freshWindoWidth = fabricState->NextGroupKeyMsgId.GetValue() - fabricState->GroupKeyMsgIdFreshWindowStart;
// If fresh window exceeds highest supported width.
if (freshWindoWidth > fabricState->kMask_GroupKeyMsgIdFreshWindowWidth)
{
// Adjust fresh window start value.
fabricState->GroupKeyMsgIdFreshWindowStart += (freshWindoWidth - fabricState->kMask_GroupKeyMsgIdFreshWindowWidth);
// Set the highest supported fresh window width.
freshWindoWidth = fabricState->kMask_GroupKeyMsgIdFreshWindowWidth;
}
// Set fresh window width field.
fabricState->MsgCounterSyncStatus |= (freshWindoWidth & fabricState->kMask_GroupKeyMsgIdFreshWindowWidth);
// Clear ReqSentThisPeriod flag.
fabricState->MsgCounterSyncStatus &= ~fabricState->kFlag_ReqSentThisPeriod;
// Arm timer.
fabricState->StartMsgCounterSyncTimer();
}
else
{
// Clear status fields.
fabricState->MsgCounterSyncStatus = 0;
// Disable fast-poll mode for SEDs if needed.
fabricState->MessageLayer->SignalMessageLayerActivityChanged();
}
return;
}
/**
* Get the key ID to be used to encrypt messages for a specific Weave Application Group.
*
* @param[in] appGroupGlobalId
* The global ID of the application group for which the encryption
* key ID should be returned.
* @param[in] rootKeyId The root key used to derive encryption keys for the specified
* Weave Application Group.
* @param[in] useRotatingKey True if the Weave Application Group uses rotating message keys.
* @param[ouy] keyId The key ID to be used to encrypt messages for the specified
* Weave Application Group.
*/
WEAVE_ERROR WeaveFabricState::GetMsgEncKeyIdForAppGroup(uint32_t appGroupGlobalId, uint32_t rootKeyId, bool useRotatingKey, uint32_t& keyId)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint32_t masterKeyId;
// Lookup the master key id for the specified Application Group Global Id.
err = GetAppGroupMasterKeyId(appGroupGlobalId, GroupKeyStore, masterKeyId);
SuccessOrExit(err);
// Form the appropriate message encryption key id.
if (useRotatingKey)
{
keyId = WeaveKeyId::MakeAppRotatingKeyId(rootKeyId, 0, masterKeyId, true);
}
else
{
keyId = WeaveKeyId::MakeAppStaticKeyId(rootKeyId, masterKeyId);
}
exit:
return err;
}
/**
* Ensure that a Weave message was encrypted using the message encryption key for a specific
* Weave Application Group key.
*
* @param[in] msgInfo A pointer to a WeaveMessageInfo structure containing information
* about the received message.
* @param[in] appGroupGlobalId
* The global ID of the application group for which the message is
* expected to be encrypted.
* @param[in] rootKeyId The root key used to derive encryption keys for the specified
* Weave Application Group.
* @param[in] requireRotatingKey
* True if the Weave Application Group uses rotating message keys.
*/
WEAVE_ERROR WeaveFabricState::CheckMsgEncForAppGroup(const WeaveMessageInfo *msgInfo, uint32_t appGroupGlobalId, uint32_t rootKeyId, bool requireRotatingKey)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint32_t expectedMasterKeyId;
// Verify that the message was encrypted with a group key.
VerifyOrExit(WeaveKeyId::IsAppGroupKey(msgInfo->KeyId), err = WEAVE_ERROR_WRONG_KEY_TYPE);
// Verify that the message encryption key was derived from the specified root key.
VerifyOrExit(WeaveKeyId::GetRootKeyId(msgInfo->KeyId) == rootKeyId, err = WEAVE_ERROR_WRONG_KEY_TYPE);
// If requested, verify that the message was encrypted with a rotating key.
VerifyOrExit(!requireRotatingKey || WeaveKeyId::IsAppRotatingKey(msgInfo->KeyId), err = WEAVE_ERROR_WRONG_KEY_TYPE);
// Lookup the master key id for the specified Application Group.
err = GetAppGroupMasterKeyId(appGroupGlobalId, GroupKeyStore, expectedMasterKeyId);
SuccessOrExit(err);
// Verify that the message was encrypted using the master key for the specified Application Group.
VerifyOrExit(WeaveKeyId::GetAppGroupMasterKeyId(msgInfo->KeyId) == expectedMasterKeyId, err = WEAVE_ERROR_WRONG_KEY_TYPE);
exit:
return err;
}
#endif // WEAVE_CONFIG_USE_APP_GROUP_KEYS_FOR_MSG_ENC
/**
* This method finds, allocates (optional), and returns index to the peer entry in the peer state table.
*
* @param[in] peerNodeId The node identifier of the peer.
* @param[in] allocEntry A boolean value indicating whether new entry should be
* allocated for the specified peer if not found in the table.
* @param[out] retPeerIndex Index to the specified peer entry in the peer state table.
*
* @retval bool Whether or not peer's entry found in the peer state table.
* Note, that function always returns true if entry allocation
* was requested.
*
*/
bool WeaveFabricState::FindOrAllocPeerEntry(uint64_t peerNodeId, bool allocEntry, PeerIndexType& retPeerIndex)
{
uint16_t i;
bool retVal = false;
// Find peer entry in the peer state table.
for (i = 0; i < PeerCount; i++)
{
retPeerIndex = PeerStates.MostRecentlyUsedIndexes[i];
if (PeerStates.NodeId[retPeerIndex] == peerNodeId)
{
retVal = true;
break;
}
}
// If peer entry is not found in the peer state table and allocation was requested.
if (!retVal && allocEntry)
{
// If PeerStates table is full then the least recently used entry is discarded
// and allocated for the new peer node. The replacement algorithms tries to find
// least recently used entry that didn't use encryption to avoid future
// complexity associated with encrypted message counter synchronization.
if (PeerCount == WEAVE_CONFIG_MAX_PEER_NODES)
{
// Choose the least recently used peer entry by default.
i = WEAVE_CONFIG_MAX_PEER_NODES - 1;
#if WEAVE_CONFIG_USE_APP_GROUP_KEYS_FOR_MSG_ENC
// Try to find the least recently used peer entry that didn't use encryption.
for (int j = WEAVE_CONFIG_MAX_PEER_NODES - 1; j >= 0; j--)
{
PeerIndexType peerInd = PeerStates.MostRecentlyUsedIndexes[j];
if ((PeerStates.GroupKeyRcvFlags[peerInd] & WeaveSessionState::kReceiveFlags_MessageIdSynchronized) == 0)
{
i = j;
break;
}
}
#endif
// The peer index chosen for replacement.
retPeerIndex = PeerStates.MostRecentlyUsedIndexes[i];
}
// If PeerStates table is not full then the next available entry is "i".
// Entries in the table are allocated sequentially and never discarded until
// the table is full. Only when table is full the least recently used entry
// is discarded and replaced with the new entry.
else
{
PeerCount++;
retPeerIndex = i;
}
PeerStates.NodeId[retPeerIndex] = peerNodeId;
PeerStates.MaxUnencUDPMsgIdRcvd[retPeerIndex] = 0;
#if WEAVE_CONFIG_USE_APP_GROUP_KEYS_FOR_MSG_ENC
PeerStates.MaxGroupKeyMsgIdRcvd[retPeerIndex] = 0;
PeerStates.GroupKeyRcvFlags[retPeerIndex] = 0;
#endif
PeerStates.UnencRcvFlags[retPeerIndex] = 0;
retVal = true;
}
// Move the requested entry to the top of the most recently used indexes list.
if (retVal)
{
memmove(&PeerStates.MostRecentlyUsedIndexes[1],
&PeerStates.MostRecentlyUsedIndexes[0],
i * sizeof(PeerIndexType));
PeerStates.MostRecentlyUsedIndexes[0] = retPeerIndex;
}
return retVal;
}
WEAVE_ERROR WeaveFabricState::GetPassword(uint8_t pwSrc, const char *& ps, uint16_t& pwLen)
{
switch (pwSrc)
{
case kPasswordSource_PairingCode:
if (PairingCode == NULL)
return WEAVE_ERROR_INVALID_ARGUMENT; // TODO: use proper error code
ps = PairingCode;
pwLen = (uint16_t)strlen(PairingCode);
return WEAVE_NO_ERROR;
default:
return WEAVE_ERROR_INVALID_ARGUMENT; // TODO: use proper error code
}
}
WEAVE_ERROR WeaveFabricState::CreateFabric()
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
WeaveGroupKey fabricSecret;
// Fail if the node is already a member of a fabric.
if (FabricId != 0)
return WEAVE_ERROR_INCORRECT_STATE;
// Make sure the fabric state is cleared.
ClearFabricState();
#if WEAVE_CONFIG_SECURITY_TEST_MODE
if (DebugFabricId == 0)
#endif
{
// Generate a unique id for the new fabric, being careful to
// avoid reserved ids.
//
// NOTE: The fabric id is used to form the global ids that
// make up the fabric IPv6 unique local addresses, as
// described in RFC 4193. The mechanism used here to create
// the fabric id (essentially a CSRNG) is different from the
// algorithm described in the RFC. However the algorithm in
// the RFC is a suggestion and this algorithm is more
// straightforward and is sufficient to produce the required
// uniqueness.
do
{
err = nl::Weave::Platform::Security::GetSecureRandomData((unsigned char *)&FabricId, sizeof(FabricId));
SuccessOrExit(err);
} while (FabricId == kFabricIdNotSpecified || FabricId >= kReservedFabricIdStart);
}
#if WEAVE_CONFIG_SECURITY_TEST_MODE
else
{
// Use our debug fabric ID.
FabricId = DebugFabricId;
}
#endif
// Create an initial fabric secret.
fabricSecret.KeyId = WeaveKeyId::kFabricSecret;
fabricSecret.KeyLen = kWeaveFabricSecretSize;
err = nl::Weave::Platform::Security::GetSecureRandomData(fabricSecret.Key, kWeaveFabricSecretSize);
SuccessOrExit(err);
err = GroupKeyStore->StoreGroupKey(fabricSecret);
SuccessOrExit(err);
if (Delegate != NULL)
Delegate->DidJoinFabric(this, FabricId);
exit:
if (err != WEAVE_NO_ERROR)
ClearFabricState();
ClearSecretData((uint8_t *)&fabricSecret, sizeof(fabricSecret));
return err;
}
void WeaveFabricState::ClearFabricState()
{
uint64_t oldFabricId;
oldFabricId = FabricId;
FabricId = kFabricIdNotSpecified;
GroupKeyStore->Clear();
if (oldFabricId != kFabricIdNotSpecified)
{
if (Delegate != NULL)
Delegate->DidLeaveFabric(this, oldFabricId);
}
}
WEAVE_ERROR WeaveFabricState::GetFabricState(uint8_t *buf, uint32_t bufSize, uint32_t &fabricStateLen)
{
WEAVE_ERROR err;
TLVWriter writer;
TLVType containerType;
// Fail if the node is not a member of a fabric.
if (FabricId == 0)
return WEAVE_ERROR_INCORRECT_STATE;
// IMPORTANT NOTE: As a convenience to readers, all elements in a FabricConfig
// must be encoded in numeric tag order, at all levels.
writer.Init(buf, bufSize);
err = writer.StartContainer(ProfileTag(kWeaveProfile_FabricProvisioning, kTag_FabricConfig), kTLVType_Structure, containerType);
SuccessOrExit(err);
err = writer.Put(ContextTag(kTag_FabricId), FabricId);
SuccessOrExit(err);
{
TLVType containerType2;
err = writer.StartContainer(ContextTag(kTag_FabricKeys), kTLVType_Array, containerType2);
SuccessOrExit(err);
{
TLVType containerType3;
WeaveGroupKey fabricSecret;
err = GroupKeyStore->RetrieveGroupKey(WeaveKeyId::kFabricSecret, fabricSecret);
SuccessOrExit(err);
err = writer.StartContainer(AnonymousTag, kTLVType_Structure, containerType3);
SuccessOrExit(err);
err = writer.Put(ContextTag(kTag_FabricKeyId), (uint16_t)(fabricSecret.KeyId));
SuccessOrExit(err);
err = writer.Put(ContextTag(kTag_EncryptionType), (uint8_t)kWeaveEncryptionType_AES128CTRSHA1);
SuccessOrExit(err);
err = writer.PutBytes(ContextTag(kTag_DataKey), fabricSecret.Key, WeaveEncryptionKey_AES128CTRSHA1::DataKeySize);
SuccessOrExit(err);
err = writer.PutBytes(ContextTag(kTag_IntegrityKey), fabricSecret.Key + WeaveEncryptionKey_AES128CTRSHA1::DataKeySize, WeaveEncryptionKey_AES128CTRSHA1::IntegrityKeySize);
SuccessOrExit(err);
err = writer.Put(ContextTag(kTag_KeyScope), (FabricSecretScope)kFabricSecretScope_All);
SuccessOrExit(err);
err = writer.Put(ContextTag(kTag_RotationScheme), (FabricSecretRotationScheme)kDeprecatedRotationScheme);
SuccessOrExit(err);
err = writer.EndContainer(containerType3);
SuccessOrExit(err);
}
err = writer.EndContainer(containerType2);
SuccessOrExit(err);
}
err = writer.EndContainer(containerType);
SuccessOrExit(err);
err = writer.Finalize();
SuccessOrExit(err);
fabricStateLen = writer.GetLengthWritten();
exit:
return err;
}
WEAVE_ERROR WeaveFabricState::JoinExistingFabric(const uint8_t *fabricState, uint32_t fabricStateLen)
{
WEAVE_ERROR err;
TLVReader reader;
// Fail if the node is already a member of a fabric.
if (FabricId != 0)
return WEAVE_ERROR_INCORRECT_STATE;
// Make sure the fabric state is cleared.
ClearFabricState();
reader.Init(fabricState, fabricStateLen);
err = reader.Next(kTLVType_Structure, ProfileTag(kWeaveProfile_FabricProvisioning, kTag_FabricConfig));
SuccessOrExit(err);
{
TLVType containerType;
err = reader.EnterContainer(containerType);
SuccessOrExit(err);
err = reader.Next(kTLVType_UnsignedInteger, ContextTag(kTag_FabricId));
SuccessOrExit(err);
err = reader.Get(FabricId);
SuccessOrExit(err);
err = reader.Next(kTLVType_Array, ContextTag(kTag_FabricKeys));
SuccessOrExit(err);
{
TLVType containerType2;
err = reader.EnterContainer(containerType2);
SuccessOrExit(err);
err = reader.Next(kTLVType_Structure, AnonymousTag);
SuccessOrExit(err);
{
TLVType containerType3;
WeaveGroupKey fabricSecret;
uint16_t keyId;
uint8_t encType;
FabricSecretScope keyScope;
FabricSecretRotationScheme rotationScheme;
err = reader.EnterContainer(containerType3);
SuccessOrExit(err);
err = reader.Next(kTLVType_UnsignedInteger, ContextTag(kTag_FabricKeyId));
SuccessOrExit(err);
err = reader.Get(keyId);
SuccessOrExit(err);
VerifyOrExit(keyId == WeaveKeyId::kFabricSecret, err = WEAVE_ERROR_INVALID_KEY_ID);
fabricSecret.KeyId = keyId;
err = reader.Next(kTLVType_UnsignedInteger, ContextTag(kTag_EncryptionType));
SuccessOrExit(err);
err = reader.Get(encType);
SuccessOrExit(err);
VerifyOrExit(encType == kWeaveEncryptionType_AES128CTRSHA1, err = WEAVE_ERROR_UNSUPPORTED_ENCRYPTION_TYPE);
err = reader.Next(kTLVType_ByteString, ContextTag(kTag_DataKey));
SuccessOrExit(err);
VerifyOrExit(reader.GetLength() == WeaveEncryptionKey_AES128CTRSHA1::DataKeySize, err = WEAVE_ERROR_INVALID_ARGUMENT);
err = reader.GetBytes(fabricSecret.Key, WeaveEncryptionKey_AES128CTRSHA1::DataKeySize);
SuccessOrExit(err);
err = reader.Next(kTLVType_ByteString, ContextTag(kTag_IntegrityKey));
SuccessOrExit(err);
VerifyOrExit(reader.GetLength() == WeaveEncryptionKey_AES128CTRSHA1::IntegrityKeySize, err = WEAVE_ERROR_INVALID_ARGUMENT);
err = reader.GetBytes(fabricSecret.Key + WeaveEncryptionKey_AES128CTRSHA1::DataKeySize, WeaveEncryptionKey_AES128CTRSHA1::IntegrityKeySize);
SuccessOrExit(err);
fabricSecret.KeyLen = kWeaveFabricSecretSize;
err = reader.Next(kTLVType_UnsignedInteger, ContextTag(kTag_KeyScope));
SuccessOrExit(err);
err = reader.Get(keyScope);
SuccessOrExit(err);
VerifyOrExit(keyScope == kFabricSecretScope_All, err = WEAVE_ERROR_INVALID_ARGUMENT);
err = reader.Next(kTLVType_UnsignedInteger, ContextTag(kTag_RotationScheme));
SuccessOrExit(err);
err = reader.Get(rotationScheme);
SuccessOrExit(err);
VerifyOrExit(rotationScheme == kDeprecatedRotationScheme, err = WEAVE_ERROR_INVALID_ARGUMENT);
err = reader.ExitContainer(containerType3);
SuccessOrExit(err);
err = GroupKeyStore->StoreGroupKey(fabricSecret);
SuccessOrExit(err);
}
err = reader.Next(kTLVType_Structure, AnonymousTag);
VerifyOrExit(err == WEAVE_END_OF_TLV, /* no action */);
err = reader.ExitContainer(containerType2);
SuccessOrExit(err);
}
}
if (Delegate != NULL)
Delegate->DidJoinFabric(this, FabricId);
exit:
if (err != WEAVE_NO_ERROR)
ClearFabricState();
return err;
}
void WeaveFabricState::HandleConnectionClosed(WeaveConnection *con)
{
WeaveSessionKey *sessionKey;
// Remove any session keys that are bound to the closed connection.
sessionKey = SessionKeys;
for (int i = 0; i < WEAVE_CONFIG_MAX_SESSION_KEYS; i++, sessionKey++)
{
if (sessionKey->IsAllocated() && SessionKeys[i].BoundCon == con)
{
RemoveSessionKey(sessionKey);
}
}
}
// WeaveSessionState Members
WeaveSessionState::WeaveSessionState(void)
{
MsgEncKey = NULL;
AuthMode = kWeaveAuthMode_NotSpecified;
NextMsgId = NULL;
MaxMsgIdRcvd = NULL;
RcvFlags = NULL;
}
WeaveSessionState::WeaveSessionState(WeaveMsgEncryptionKey *msgEncKey, WeaveAuthMode authMode,
MonotonicallyIncreasingCounter *nextMsgId, uint32_t *maxMsgIdRcvd, ReceiveFlagsType *rcvFlags)
{
MsgEncKey = msgEncKey;
AuthMode = authMode;
NextMsgId = nextMsgId;
MaxMsgIdRcvd = maxMsgIdRcvd;
RcvFlags = rcvFlags;
}
uint32_t WeaveSessionState::NewMessageId(void)
{
uint32_t newMsgId = NextMsgId->GetValue();
NextMsgId->Advance();
return newMsgId;
}
bool WeaveSessionState::MessageIdNotSynchronized(void)
{
return (RcvFlags == NULL) || (((*RcvFlags) & kReceiveFlags_MessageIdSynchronized) == 0);
}
bool WeaveSessionState::IsDuplicateMessage(uint32_t msgId)
{
bool isDup = false;
int32_t delta;
ReceiveFlagsType msgIdFlags;
// This algorithm relies on two values to determine whether a message has been received before:
//
// *MaxMsgIdRcvd is the maximum message id received from from the peer node.
//
// *RcvFlags is a set of flags describing the history of message reception from the peer.
//
// The top-most bit in *RcvFlags indicates whether any messages have ever been received from the peer.
//
// The remaining bits represent individual message ids that have been received prior to the
// message identified by *MaxMsgIdRcvd. Specifically, bit 0 represents the message immediately
// prior to the max id message (i.e. *MaxMsgIdRcvd - 1), bit 1 represents the message immediately
// prior to that message, and so on.
// If message Id is not synchronized.
if (MessageIdNotSynchronized())
{
// Return immediately if duplicate message detection is disabled for this session.
//
// This happens for unencrypted messages sent over TCP. Such messages provide no security against replay
// attacks (since they are not encrypted) and are not subject to message reordering in the network layer
// (since TCP eliminates such reorderings). Thus duplicate message detection is unnecessary in this case.
if (MsgEncKey == NULL && RcvFlags == NULL)
{
ExitNow();
}
#if WEAVE_CONFIG_USE_APP_GROUP_KEYS_FOR_MSG_ENC
// Mark message as a duplicate and exit if it was encrypted with application group key.
// In this case, peer's message counter synchronization can only be done by
// WeaveSecurityManager::HandleMsgCounterSyncRespMsg() function.
else if (MsgEncKey != NULL && WeaveKeyId::IsAppGroupKey(MsgEncKey->KeyId))
{
ExitNow(isDup = true);
}
#endif
// Otherwise mark message as synchronized and initialize peer's max counter.
else
{
*RcvFlags = kReceiveFlags_MessageIdSynchronized;
*MaxMsgIdRcvd = msgId;
ExitNow();
}
}
// Extract the message id flags from the receive flags field.
msgIdFlags = (*RcvFlags) & kReceiveFlags_MessageIdFlagsMask;
// Determine the difference between the id of the newly received message (msgId) and the maximum message
// id received so far (*MaxMsgIdRcvd).
//
// Note that the math here is designed to accommodate wrapping of message ids. Specifically, any newly
// received message with an id in the range (*MaxMsgIdRcvd + 1) to ((*MaxMsgIdRcvd + 2^31 - 1) mod 2^32)
// will be considered to be later than the max id message (i.e. delta > 0), and thus cannot be a duplicate.
// Conversely any message with an id not in this range (i.e. delta <= 0) represents an earlier message
// (or the same message) and thus may be a duplicate.
//
// This approach ensures that duplicates will continue to be detected for an amount of time equal to
// (send-rate * 2^31) past a message's original send time, while allowing (send-rate * (2^31 - 1)) time
// between message arrivals before a new message will be mistakenly considered a duplicate.
//
delta = (int32_t) (msgId - *MaxMsgIdRcvd);
// If the new message was sent after the max id message...
if (delta > 0)
{
// Shift the message received flags by the delta (or simply set the flags to zero if the delta is larger
// than the number of flags).
if (delta < kReceiveFlags_NumMessageIdFlags)
msgIdFlags = ((msgIdFlags << 1) | 1) << (delta - 1);
else
msgIdFlags = 0;
// Update the max received message id.
*MaxMsgIdRcvd = msgId;
}
// If the new id is the same as the max id message, the message is a duplicate.
else if (delta == 0) {
ExitNow(isDup = true);
}
// Otherwise the new message was sent earlier than the max id message...
else
{
// Make the delta positive.
delta = -delta;
// If the delta is within the range of the message id flags, form the appropriate mask
// and check if the message has already been received. If not, set the corresponding flag.
if (delta <= kReceiveFlags_NumMessageIdFlags)
{
ReceiveFlagsType mask = 1 << (delta - 1);
if ((msgIdFlags & mask) == 0)
msgIdFlags |= mask;
else {
ExitNow(isDup = true);
}
}
// If the delta is greater than the range of the message id flags...
else
{
// If the message was encrypted then assume the message is a duplicate.
if (MsgEncKey != NULL) {
ExitNow(isDup = true);
}
// Otherwise the message is not encrypted, so assume the message is valid and reset the received state.
//
// Senders of unencrypted messages are not required to preserve message id ordering across restarts.
// Since duplicate message detection for unencrypted messages is only to eliminate duplicates created
// in the network layer, we allow message ids for unencrypted messages from the same peer to go backwards.
else
{
msgIdFlags = 0;
*MaxMsgIdRcvd = msgId;
}
}
}
// Update the message id flags within the receive flags value and set the MessageIdIsSynchronized flag.
*RcvFlags = kReceiveFlags_MessageIdSynchronized | msgIdFlags | (*RcvFlags & ~kReceiveFlags_MessageIdFlagsMask);
exit:
return isDup;
}
/**
* This method finds session key entry.
*
* @param[in] keyId Weave key identifier.
* @param[in] peerNodeId The node identifier of the peer.
* @param[in] create A boolean value indicating whether new key should be created
* if the specified key is not found.
* @param[out] retRec A pointer reference to a WeaveSessionKey object.
*
* @retval #WEAVE_ERROR_WRONG_KEY_TYPE If specified key is not a session key type.
* @retval #WEAVE_ERROR_INVALID_ARGUMENT If input arguments have wrong values.
* @retval #WEAVE_ERROR_KEY_NOT_FOUND If specified key is not found.
* @retval #WEAVE_ERROR_TOO_MANY_KEYS If there is no free entry to create new session key.
* @retval #WEAVE_NO_ERROR On success.
*
*/
WEAVE_ERROR WeaveFabricState::FindSessionKey(uint16_t keyId, uint64_t peerNodeId, bool create, WeaveSessionKey *& retRec)
{
WeaveSessionKey *curRec = SessionKeys;
WeaveSessionKey *freeRec = NULL;
if (!WeaveKeyId::IsSessionKey(keyId))
return WEAVE_ERROR_WRONG_KEY_TYPE;
if (peerNodeId == kNodeIdNotSpecified || peerNodeId == kAnyNodeId)
return WEAVE_ERROR_INVALID_ARGUMENT;
for (int i = 0; i < WEAVE_CONFIG_MAX_SESSION_KEYS; i++, curRec++)
{
if (!curRec->IsAllocated())
{
if (freeRec == NULL)
freeRec = curRec;
}
else if (curRec->MsgEncKey.KeyId == keyId &&
(curRec->NodeId == peerNodeId ||
(curRec->IsSharedSession() && FindSharedSessionEndNode(peerNodeId, curRec))))
{
retRec = curRec;
return WEAVE_NO_ERROR;
}
}
if (!create)
return WEAVE_ERROR_KEY_NOT_FOUND;
if (freeRec == NULL)
return WEAVE_ERROR_TOO_MANY_KEYS;
retRec = freeRec;
return WEAVE_NO_ERROR;
}
#if WEAVE_CONFIG_USE_APP_GROUP_KEYS_FOR_MSG_ENC
WEAVE_ERROR WeaveFabricState::FindMsgEncAppKey(uint16_t keyId, uint8_t encType, WeaveMsgEncryptionKey *& retRec)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
// Find key or allocate empty key entry in the key cache.
retRec = AppKeyCache.FindOrAllocateKeyEntry(keyId, encType);
// Derive application key if it's not in the key cache.
if (retRec->KeyId == WeaveKeyId::kNone)
{
uint32_t appGroupGlobalId;
err = DeriveMsgEncAppKey(keyId, encType, *retRec, appGroupGlobalId);
SuccessOrExit(err);
#if WEAVE_CONFIG_SECURITY_TEST_MODE && WEAVE_DETAIL_LOGGING
if (LogKeys)
{
char keyString[kMaxEncKeyStringSize];
WeaveEncryptionKeyToString(encType, retRec->EncKey, keyString, sizeof(keyString));
WeaveLogDetail(MessageLayer, "Message Encryption Key: Id=%04" PRIX16 " Type=GroupKey(%08" PRIX32 ") EncType=%02" PRIX8 " Key=%s", keyId, appGroupGlobalId, encType, keyString);
}
#endif
}
exit:
return err;
}
/**
* Derives message encryption application key.
* Three types of message encryption application keys can be requested: current application
* key, rotating application key, and static application key. When current application key
* is requested, the function finds and uses the current epoch key based on the current system
* time and the start time parameter of each epoch key.
*
* @param[in] keyId The requested key ID.
* @param[in] encType The type of the requested message encryption key.
* @param[out] appKey A reference to the message encryption key object.
* @param[out] appGroupGlobalId The application group global ID of the associated key.
*
* @retval #WEAVE_NO_ERROR On success.
* @retval #WEAVE_ERROR_UNSUPPORTED_WEAVE_FEATURE
* If group key store functionality is not implemented.
* @retval #WEAVE_ERROR_UNSUPPORTED_ENCRYPTION_TYPE
* If the requested encryption type is not supported.
* @retval #WEAVE_ERROR_INVALID_KEY_ID
* If the requested key has an invalid key ID.
* @retval #WEAVE_ERROR_INVALID_ARGUMENT
* If the platform key store returns invalid key parameters.
* @retval other Other platform-specific errors returned by the platform
* key store APIs.
*
*/
WEAVE_ERROR WeaveFabricState::DeriveMsgEncAppKey(uint32_t keyId, uint8_t encType, WeaveMsgEncryptionKey& appKey, uint32_t& appGroupGlobalId)
{
WEAVE_ERROR err;
uint8_t keyData[WeaveEncryptionKey_AES128CTRSHA1::KeySize];
uint8_t keyDiversifier[kWeaveMsgEncAppKeyDiversifierSize];
// Verify supported key type.
VerifyOrExit(encType == kWeaveEncryptionType_AES128CTRSHA1, err = WEAVE_ERROR_UNSUPPORTED_ENCRYPTION_TYPE);
// Set application key size and info value.
memcpy(keyDiversifier, kWeaveMsgEncAppKeyDiversifier, sizeof(kWeaveMsgEncAppKeyDiversifier));
keyDiversifier[sizeof(kWeaveMsgEncAppKeyDiversifier)] = encType;
// Derive application key data.
err = GroupKeyStore->DeriveApplicationKey(keyId, NULL, 0, keyDiversifier, kWeaveMsgEncAppKeyDiversifierSize,
keyData, sizeof(keyData), WeaveEncryptionKey_AES128CTRSHA1::KeySize,
appGroupGlobalId);
SuccessOrExit(err);
// Copy the generated key data to the appropriate destinations.
memcpy(appKey.EncKey.AES128CTRSHA1.DataKey,
keyData,
WeaveEncryptionKey_AES128CTRSHA1::DataKeySize);
memcpy(appKey.EncKey.AES128CTRSHA1.IntegrityKey,
keyData + WeaveEncryptionKey_AES128CTRSHA1::DataKeySize,
WeaveEncryptionKey_AES128CTRSHA1::IntegrityKeySize);
// Set key parameters.
appKey.KeyId = keyId;
appKey.EncType = encType;
exit:
ClearSecretData(keyData, sizeof(keyData));
return err;
}
#endif // WEAVE_CONFIG_USE_APP_GROUP_KEYS_FOR_MSG_ENC
void WeaveFabricState::SetDelegate(FabricStateDelegate *aDelegate)
{
Delegate = aDelegate;
}
bool WeaveFabricState::RemoveIdleSessionKeys()
{
WeaveSessionKey *sessionKey;
bool potentialIdleSessionsExist = false;
// For each allocated session key...
sessionKey = SessionKeys;
for (int i = 0; i < WEAVE_CONFIG_MAX_SESSION_KEYS; i++, sessionKey++)
if (sessionKey->IsAllocated())
{
// Ignore the session if it is still in the process of being established.
if (!sessionKey->IsKeySet())
continue;
// Capture and clear the recently active flag.
bool recentlyActive = sessionKey->IsRecentlyActive();
sessionKey->ClearRecentlyActive();
// Ignore the session if it is bound to a connection. (Connection bound
// sessions persist until their connections close).
if (sessionKey->BoundCon != NULL)
continue;
// If the session is marked for remove-on-idle and is not currently reserved...
if (sessionKey->IsRemoveOnIdle() && sessionKey->ReserveCount == 0)
{
// Remove the session if it hasn't been active since the last time RemoveIdleSessionKeys()
// was called.
if (!recentlyActive)
{
RemoveSessionKey(sessionKey, true);
}
// Otherwise, tell the caller that unreserved, remove-on-idle sessions exist which may
// need to be removed on a future call to RemoveIdleSessionKeys().
else
{
potentialIdleSessionsExist = true;
}
}
}
return potentialIdleSessionsExist;
}
// ============================================================
// Weave Message Encryption Application Key Cache.
// ============================================================
void WeaveMsgEncryptionKeyCache::Init()
{
Reset();
}
void WeaveMsgEncryptionKeyCache::Shutdown()
{
Reset();
}
void WeaveMsgEncryptionKeyCache::Reset()
{
for (uint8_t keyEntry = 0; keyEntry < WEAVE_CONFIG_MAX_CACHED_MSG_ENC_APP_KEYS; keyEntry++)
Clear(keyEntry);
memset(mMostRecentlyUsedKeyEntries, 0, sizeof(mMostRecentlyUsedKeyEntries));
}
// Clear key cache entry.
void WeaveMsgEncryptionKeyCache::Clear(uint8_t keyEntryIndex)
{
ClearSecretData((uint8_t *)(&mKeyCache[keyEntryIndex]), sizeof(WeaveMsgEncryptionKey));
mKeyCache[keyEntryIndex].KeyId = WeaveKeyId::kNone;
mKeyCache[keyEntryIndex].EncType = kWeaveEncryptionType_None;
}
// If the key is found in the cache then function returns pointer to the key.
// If the key is not found in the cache then function allocates and returns pointer to the empty key entry in the cache.
WeaveMsgEncryptionKey *WeaveMsgEncryptionKeyCache::FindOrAllocateKeyEntry(uint16_t keyId, uint8_t encType)
{
WeaveMsgEncryptionKey *keyEntry;
uint8_t retKeyEntryIndex = WEAVE_CONFIG_MAX_CACHED_MSG_ENC_APP_KEYS;
uint8_t i;
// Find if key is in the cache.
for (i = 0, keyEntry = mKeyCache; i < WEAVE_CONFIG_MAX_CACHED_MSG_ENC_APP_KEYS; i++, keyEntry++)
{
if (keyEntry->KeyId == keyId && keyEntry->EncType == encType)
{
retKeyEntryIndex = i;
break;
}
else if (retKeyEntryIndex == WEAVE_CONFIG_MAX_CACHED_MSG_ENC_APP_KEYS && keyEntry->KeyId == WeaveKeyId::kNone)
{
retKeyEntryIndex = i;
}
}
// If cache is full and specified key was not found in the cache then replace the least-recently used key entry.
if (retKeyEntryIndex == WEAVE_CONFIG_MAX_CACHED_MSG_ENC_APP_KEYS)
{
// Chose the least-recently used entry in the key cache.
retKeyEntryIndex = mMostRecentlyUsedKeyEntries[WEAVE_CONFIG_MAX_CACHED_MSG_ENC_APP_KEYS - 1];
// Clear replaced key cache entry.
Clear(retKeyEntryIndex);
}
// Find key entry index in the most-recently used list of entries.
for (i = 0; i < WEAVE_CONFIG_MAX_CACHED_MSG_ENC_APP_KEYS; i++)
if (mMostRecentlyUsedKeyEntries[i] == retKeyEntryIndex)
break;
// Mark selected key entry as most-recently used by moving it to the top of the most-recently used key entries list.
memmove(&mMostRecentlyUsedKeyEntries[1], &mMostRecentlyUsedKeyEntries[0], i * sizeof(uint8_t));
mMostRecentlyUsedKeyEntries[0] = retKeyEntryIndex;
return &mKeyCache[retKeyEntryIndex];
}
#if WEAVE_CONFIG_SECURITY_TEST_MODE
static inline char ToHex(const uint8_t data)
{
return (data < 10) ? '0' + data : 'A' + (data - 10);
}
static void ToHexString(const uint8_t *data, size_t dataLen, char *& outBuf, size_t& outBufSize)
{
for (; dataLen > 0 && outBufSize >= 2; data++, dataLen--, outBuf += 2, outBufSize -= 2)
{
outBuf[0] = ToHex(*data >> 4);
outBuf[1] = ToHex(*data & 0xF);
}
}
void WeaveEncryptionKeyToString(uint8_t encType, const WeaveEncryptionKey& key, char *buf, size_t bufSize)
{
if (encType == kWeaveEncryptionType_AES128CTRSHA1)
{
bufSize -= 2; // Reserve size for the comma and null terminator.
ToHexString(key.AES128CTRSHA1.DataKey, sizeof(key.AES128CTRSHA1.DataKey), buf, bufSize);
*buf++ = ',';
ToHexString(key.AES128CTRSHA1.IntegrityKey, sizeof(key.AES128CTRSHA1.IntegrityKey), buf, bufSize);
}
*buf = 0;
}
#endif // WEAVE_CONFIG_SECURITY_TEST_MODE
} // namespace Weave
} // namespace nl