diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index db59dbf665b26f..fff0d0811387a0 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -28,6 +28,10 @@ #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS #endif +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif + // module header, comes first #include @@ -46,6 +50,7 @@ #include #include +#include #include #include #include @@ -58,6 +63,22 @@ namespace DeviceController { using namespace chip::Encoding; +constexpr const char * kDeviceCredentialsKeyPrefix = "DeviceCredentials"; +constexpr const char * kDeviceAddressKeyPrefix = "DeviceAddress"; + +// This macro generates a key using node ID an key prefix, and performs the given action +// on that key. +#define PERSISTENT_KEY_OP(node, keyPrefix, key, action) \ + do \ + { \ + const size_t len = strlen(keyPrefix); \ + /* 2 * sizeof(NodeId) to accomodate 2 character for each byte in Node Id */ \ + char key[len + 2 * sizeof(NodeId) + 1]; \ + nlSTATIC_ASSERT_PRINT(sizeof(node) <= sizeof(uint64_t), "Node ID size is greater than expected"); \ + snprintf(key, sizeof(key), "%s%" PRIx64, keyPrefix, node); \ + action; \ + } while (0) + ChipDeviceController::ChipDeviceController() { mState = kState_NotInitialized; @@ -69,6 +90,7 @@ ChipDeviceController::ChipDeviceController() mOnError = nullptr; mOnNewConnection = nullptr; mPairingDelegate = nullptr; + mStorageDelegate = nullptr; mDeviceAddr = IPAddress::Any; mDevicePort = CHIP_PORT; mInterface = INET_NULL_INTERFACEID; @@ -117,12 +139,14 @@ CHIP_ERROR ChipDeviceController::Init(NodeId localNodeId, System::Layer * system return err; } -CHIP_ERROR ChipDeviceController::Init(NodeId localNodeId, DevicePairingDelegate * pairingDelegate) +CHIP_ERROR ChipDeviceController::Init(NodeId localNodeId, DevicePairingDelegate * pairingDelegate, + PersistentStorageDelegate * storage) { CHIP_ERROR err = Init(localNodeId); SuccessOrExit(err); mPairingDelegate = pairingDelegate; + mStorageDelegate = storage; exit: return err; @@ -167,7 +191,6 @@ CHIP_ERROR ChipDeviceController::Shutdown() memset(&mOnComplete, 0, sizeof(mOnComplete)); mOnError = nullptr; mOnNewConnection = nullptr; - mMessageNumber = 0; mRemoteDeviceId.ClearValue(); exit: @@ -251,11 +274,14 @@ CHIP_ERROR ChipDeviceController::ConnectDeviceWithoutSecurePairing(NodeId remote return CHIP_NO_ERROR; } -CHIP_ERROR ChipDeviceController::EstablishSecureSession() +CHIP_ERROR ChipDeviceController::EstablishSecureSession(NodeId peer) { CHIP_ERROR err = CHIP_NO_ERROR; + SecurePairingSession pairing; + SecurePairingSession * pairingSession = mSecurePairingSession; + Inet::IPAddress peerAddr = mDeviceAddr; - if (mState != kState_Initialized || mSessionManager != nullptr || mConState != kConnectionState_Connected) + if (mState != kState_Initialized || mSessionManager != nullptr || mConState == kConnectionState_SecureConnected) { ExitNow(err = CHIP_ERROR_INCORRECT_STATE); } @@ -270,13 +296,34 @@ CHIP_ERROR ChipDeviceController::EstablishSecureSession() mConState = kConnectionState_SecureConnected; + if (mStorageDelegate != nullptr) + { + const char * credentials; + const char * address; + + PERSISTENT_KEY_OP(peer, kDeviceCredentialsKeyPrefix, key, credentials = mStorageDelegate->GetKeyValue(key)); + PERSISTENT_KEY_OP(peer, kDeviceAddressKeyPrefix, key, address = mStorageDelegate->GetKeyValue(key)); + + SecurePairingSessionSerialized serialized; + + VerifyOrExit(credentials != nullptr, err = CHIP_ERROR_KEY_NOT_FOUND_FROM_PEER); + VerifyOrExit(strlen(credentials) <= sizeof(serialized.inner), err = CHIP_ERROR_INVALID_STRING_LENGTH); + strncpy(Uint8::to_char(serialized.inner), credentials, sizeof(serialized.inner)); + + err = pairing.Deserialize(serialized); + SuccessOrExit(err); + + pairingSession = &pairing; + + VerifyOrExit(address != nullptr, err = CHIP_ERROR_KEY_NOT_FOUND_FROM_PEER); + + VerifyOrExit(IPAddress::FromString(address, peerAddr), err = CHIP_ERROR_INVALID_ADDRESS); + } + err = mSessionManager->NewPairing( - Optional::Value(Transport::PeerAddress::UDP(mDeviceAddr, mDevicePort, mInterface)), - mSecurePairingSession); + Optional::Value(Transport::PeerAddress::UDP(peerAddr, mDevicePort, mInterface)), pairingSession); SuccessOrExit(err); - mMessageNumber = 1; - exit: if (err != CHIP_NO_ERROR) @@ -291,7 +338,7 @@ CHIP_ERROR ChipDeviceController::EstablishSecureSession() return err; } -CHIP_ERROR ChipDeviceController::ResumeSecureSession() +CHIP_ERROR ChipDeviceController::ResumeSecureSession(NodeId peer) { if (mConState == kConnectionState_SecureConnected) { @@ -304,13 +351,9 @@ CHIP_ERROR ChipDeviceController::ResumeSecureSession() mSessionManager = nullptr; } - uint32_t currentMessageNumber = mMessageNumber; - - CHIP_ERROR err = EstablishSecureSession(); + CHIP_ERROR err = EstablishSecureSession(peer); SuccessOrExit(err); - mMessageNumber = currentMessageNumber; - exit: if (err != CHIP_NO_ERROR) @@ -377,8 +420,8 @@ CHIP_ERROR ChipDeviceController::SendMessage(void * appReqState, PacketBuffer * if (!IsSecurelyConnected()) { // For now, it's expected that the device is connected - VerifyOrExit(IsConnected(), err = CHIP_ERROR_INCORRECT_STATE); - err = EstablishSecureSession(); + VerifyOrExit(mState == kState_Initialized, err = CHIP_ERROR_INCORRECT_STATE); + err = EstablishSecureSession(mRemoteDeviceId.Value()); SuccessOrExit(err); trySessionResumption = false; @@ -394,7 +437,8 @@ CHIP_ERROR ChipDeviceController::SendMessage(void * appReqState, PacketBuffer * // Try sesion resumption if needed if (err != CHIP_NO_ERROR && trySessionResumption) { - err = ResumeSecureSession(); + VerifyOrExit(mRemoteDeviceId.HasValue(), err = CHIP_ERROR_INCORRECT_STATE); + err = ResumeSecureSession(mRemoteDeviceId.Value()); // If session resumption failed, let's free the extra reference to // the buffer. If not, SendMessage would free it. VerifyOrExit(err == CHIP_NO_ERROR, PacketBuffer::Free(buffer)); @@ -521,12 +565,30 @@ void ChipDeviceController::OnRendezvousStatusUpdate(RendezvousSessionDelegate::S { mPairingDelegate->OnNetworkCredentialsRequested(mRendezvousSession); } + + if (mStorageDelegate != nullptr) + { + SecurePairingSessionSerialized serialized; + CHIP_ERROR err = mSecurePairingSession->Serialize(serialized); + if (err == CHIP_NO_ERROR) + { + PERSISTENT_KEY_OP(mSecurePairingSession->GetPeerNodeId(), kDeviceCredentialsKeyPrefix, key, + mStorageDelegate->SetKeyValue(key, Uint8::to_const_char(serialized.inner))); + } + } break; case RendezvousSessionDelegate::NetworkProvisioningSuccess: ChipLogDetail(Controller, "Remote device was assigned an ip address\n"); mDeviceAddr = mRendezvousSession->GetIPAddress(); + if (mStorageDelegate != nullptr) + { + char addrStr[INET6_ADDRSTRLEN]; + mDeviceAddr.ToString(addrStr, INET6_ADDRSTRLEN); + PERSISTENT_KEY_OP(mRendezvousSession->GetPairingSession().GetPeerNodeId(), kDeviceAddressKeyPrefix, key, + mStorageDelegate->SetKeyValue(key, addrStr)); + } break; default: diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h index ef6b129da19c97..95695995feec3c 100644 --- a/src/controller/CHIPDeviceController.h +++ b/src/controller/CHIPDeviceController.h @@ -28,6 +28,7 @@ #pragma once +#include #include #include #include @@ -114,7 +115,8 @@ class DLL_EXPORT ChipDeviceController : public SecureSessionMgrDelegate, public * Init function to be used when already-initialized System::Layer and InetLayer are available. */ CHIP_ERROR Init(NodeId localDeviceId, System::Layer * systemLayer, Inet::InetLayer * inetLayer); - CHIP_ERROR Init(NodeId localDeviceId, DevicePairingDelegate * pairingDelegate); + CHIP_ERROR Init(NodeId localDeviceId, DevicePairingDelegate * pairingDelegate, + PersistentStorageDelegate * storageDelegate = nullptr); CHIP_ERROR Shutdown(); // ----- Connection Management ----- @@ -257,7 +259,6 @@ class DLL_EXPORT ChipDeviceController : public SecureSessionMgrDelegate, public uint16_t mDevicePort; Inet::InterfaceId mInterface; Optional mRemoteDeviceId; - uint32_t mMessageNumber = 0; SecurePairingSession mPairingSession; SecurePairingUsingTestSecret * mTestSecurePairingSecret = nullptr; @@ -266,11 +267,13 @@ class DLL_EXPORT ChipDeviceController : public SecureSessionMgrDelegate, public DevicePairingDelegate * mPairingDelegate; + PersistentStorageDelegate * mStorageDelegate; + void ClearRequestState(); void ClearOpState(); - CHIP_ERROR EstablishSecureSession(); - CHIP_ERROR ResumeSecureSession(); + CHIP_ERROR EstablishSecureSession(NodeId peer); + CHIP_ERROR ResumeSecureSession(NodeId peer); }; } // namespace DeviceController diff --git a/src/controller/CHIPPersistentStorageDelegate.h b/src/controller/CHIPPersistentStorageDelegate.h new file mode 100644 index 00000000000000..56b3fc4d079194 --- /dev/null +++ b/src/controller/CHIPPersistentStorageDelegate.h @@ -0,0 +1,64 @@ +/* + * + * Copyright (c) 2020 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 +#include + +namespace chip { +namespace DeviceController { + +class DLL_EXPORT PersistentStorageDelegate +{ +public: + virtual ~PersistentStorageDelegate() {} + + /** + * @brief + * Lookup the key and return it's stringified value + * + * @param[in] key Key to lookup + * @return Value or nullptr if not found. Lifetime of the returned + * buffer is tied to the delegate object. If the delegate is + * freed, the returned value would be inaccessible. + */ + virtual const char * GetKeyValue(const char * key) = 0; + + /** + * @brief + * Set the value for the key + * + * @param[in] key Key to be set + * @param[in] value Value to be set + * @return returns corresponding error if unsuccessful + */ + virtual CHIP_ERROR SetKeyValue(const char * key, const char * value) = 0; + + /** + * @brief + * Deletes the value for the key + * + * @param[in] key Key to be deleted + * @return returns corresponding error if unsuccessful + */ + virtual CHIP_ERROR DeleteKeyValue(const char * key) = 0; +}; + +} // namespace DeviceController +} // namespace chip diff --git a/src/transport/SecurePairingSession.cpp b/src/transport/SecurePairingSession.cpp index 85942a2bd33e93..20ffff0b84a38a 100644 --- a/src/transport/SecurePairingSession.cpp +++ b/src/transport/SecurePairingSession.cpp @@ -28,6 +28,8 @@ * */ +#include + #include #include #include @@ -56,6 +58,78 @@ SecurePairingSession::~SecurePairingSession() memset(&mKe[0], 0, sizeof(mKe)); } +CHIP_ERROR SecurePairingSession::Serialize(SecurePairingSessionSerialized & output) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + + const NodeId localNodeId = (mLocalNodeId.HasValue()) ? mLocalNodeId.Value() : kUndefinedNodeId; + const NodeId peerNodeId = (mPeerNodeId.HasValue()) ? mPeerNodeId.Value() : kUndefinedNodeId; + VerifyOrExit(CanCastTo(mKeLen), error = CHIP_ERROR_INTERNAL); + VerifyOrExit(CanCastTo(localNodeId), error = CHIP_ERROR_INTERNAL); + VerifyOrExit(CanCastTo(peerNodeId), error = CHIP_ERROR_INTERNAL); + VerifyOrExit(CanCastTo(sizeof(SecurePairingSessionSerializable)), error = CHIP_ERROR_INTERNAL); + + { + SecurePairingSessionSerializable serializable; + memset(&serializable, 0, sizeof(serializable)); + serializable.mKeLen = static_cast(mKeLen); + serializable.mPairingComplete = (mPairingComplete) ? 1 : 0; + serializable.mLocalNodeId = localNodeId; + serializable.mPeerNodeId = peerNodeId; + serializable.mLocalKeyId = mLocalKeyId; + serializable.mPeerKeyId = mPeerKeyId; + + memcpy(serializable.mKe, mKe, mKeLen); + + uint16_t serializedLen = 0; + + VerifyOrExit(BASE64_ENCODED_LEN(sizeof(serializable)) <= sizeof(output.inner), error = CHIP_ERROR_INVALID_ARGUMENT); + + serializedLen = chip::Base64Encode(Uint8::to_const_uchar(reinterpret_cast(&serializable)), + static_cast(sizeof(serializable)), Uint8::to_char(output.inner)); + VerifyOrExit(serializedLen > 0, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(serializedLen < sizeof(output.inner), error = CHIP_ERROR_INVALID_ARGUMENT); + output.inner[serializedLen] = '\0'; + } + +exit: + return error; +} + +CHIP_ERROR SecurePairingSession::Deserialize(SecurePairingSessionSerialized & input) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + SecurePairingSessionSerializable serializable; + size_t maxlen = BASE64_ENCODED_LEN(sizeof(serializable)); + size_t len = strnlen(Uint8::to_char(input.inner), maxlen); + uint16_t deserializedLen = 0; + + VerifyOrExit(len < sizeof(SecurePairingSessionSerialized), error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(CanCastTo(len), error = CHIP_ERROR_INVALID_ARGUMENT); + + memset(&serializable, 0, sizeof(serializable)); + deserializedLen = + Base64Decode(Uint8::to_const_char(input.inner), static_cast(len), Uint8::to_uchar((uint8_t *) &serializable)); + VerifyOrExit(deserializedLen > 0, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(deserializedLen <= sizeof(serializable), error = CHIP_ERROR_INVALID_ARGUMENT); + + mPairingComplete = (serializable.mPairingComplete == 1); + mKeLen = static_cast(serializable.mKeLen); + + VerifyOrExit(mKeLen <= sizeof(mKe), error = CHIP_ERROR_INVALID_ARGUMENT); + memset(mKe, 0, sizeof(mKe)); + memcpy(mKe, serializable.mKe, mKeLen); + + mLocalNodeId = Optional::Value(serializable.mLocalNodeId); + mPeerNodeId = Optional::Value(serializable.mPeerNodeId); + + mLocalKeyId = serializable.mLocalKeyId; + mPeerKeyId = serializable.mPeerKeyId; + +exit: + return error; +} + CHIP_ERROR SecurePairingSession::Init(uint32_t setupCode, uint32_t pbkdf2IterCount, const uint8_t * salt, size_t saltLen, Optional myNodeId, uint16_t myKeyId, SecurePairingSessionDelegate * delegate) { diff --git a/src/transport/SecurePairingSession.h b/src/transport/SecurePairingSession.h index 5ba9ed70ca01e8..51223388bd5f94 100644 --- a/src/transport/SecurePairingSession.h +++ b/src/transport/SecurePairingSession.h @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -67,6 +68,8 @@ class DLL_EXPORT SecurePairingSessionDelegate : public ReferenceCounted myNodeId, uint16_t myKeyId, SecurePairingSessionDelegate * delegate); @@ -248,4 +263,21 @@ class SecurePairingUsingTestSecret : public SecurePairingSession CHIP_ERROR HandlePeerMessage(const PacketHeader & packetHeader, System::PacketBuffer * msg) override { return CHIP_NO_ERROR; } }; +typedef struct SecurePairingSessionSerializable +{ + uint16_t mKeLen; + uint8_t mKe[kMAX_Hash_Length]; + uint8_t mPairingComplete; + uint64_t mLocalNodeId; + uint64_t mPeerNodeId; + uint16_t mLocalKeyId; + uint16_t mPeerKeyId; +} SecurePairingSessionSerializable; + +typedef struct SecurePairingSessionSerialized +{ + // Extra uint64_t to account for padding bytes (NULL termination, and some decoding overheads) + uint8_t inner[BASE64_ENCODED_LEN(sizeof(SecurePairingSessionSerializable) + sizeof(uint64_t))]; +} SecurePairingSessionSerialized; + } // namespace chip diff --git a/src/transport/tests/TestSecurePairingSession.cpp b/src/transport/tests/TestSecurePairingSession.cpp index 4eb3f23c54ce69..f98ab5fc9a37a8 100644 --- a/src/transport/tests/TestSecurePairingSession.cpp +++ b/src/transport/tests/TestSecurePairingSession.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -108,11 +109,11 @@ void SecurePairingStartTest(nlTestSuite * inSuite, void * inContext) CHIP_ERROR_BAD_REQUEST); } -void SecurePairingHandshakeTest(nlTestSuite * inSuite, void * inContext) +void SecurePairingHandshakeTestCommon(nlTestSuite * inSuite, void * inContext, SecurePairingSession & pairingCommissioner) { // Test all combinations of invalid parameters TestSecurePairingDelegate delegateAccessory, deleageCommissioner; - SecurePairingSession pairingAccessory, pairingCommissioner; + SecurePairingSession pairingAccessory; deleageCommissioner.peer = &pairingAccessory; delegateAccessory.peer = &pairingCommissioner; @@ -131,6 +132,63 @@ void SecurePairingHandshakeTest(nlTestSuite * inSuite, void * inContext) NL_TEST_ASSERT(inSuite, deleageCommissioner.mNumPairingComplete == 1); } +void SecurePairingHandshakeTest(nlTestSuite * inSuite, void * inContext) +{ + SecurePairingSession pairingCommissioner; + SecurePairingHandshakeTestCommon(inSuite, inContext, pairingCommissioner); +} + +void SecurePairingDeserialize(nlTestSuite * inSuite, void * inContext, SecurePairingSession & pairingCommissioner, + SecurePairingSession & deserialized) +{ + SecurePairingSessionSerialized serialized; + NL_TEST_ASSERT(inSuite, pairingCommissioner.Serialize(serialized) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, deserialized.Deserialize(serialized) == CHIP_NO_ERROR); + + // Serialize from the deserialized session, and check we get the same string back + SecurePairingSessionSerialized serialized2; + NL_TEST_ASSERT(inSuite, deserialized.Serialize(serialized2) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, strncmp(Uint8::to_char(serialized.inner), Uint8::to_char(serialized2.inner), sizeof(serialized)) == 0); +} + +// Defining these globally to avoid stack overflow in some restricted test scenarios (e.g. QEMU) +static SecurePairingSession gTestPairingSession1, gTestPairingSession2; + +void SecurePairingSerializeTest(nlTestSuite * inSuite, void * inContext) +{ + SecurePairingHandshakeTestCommon(inSuite, inContext, gTestPairingSession1); + SecurePairingDeserialize(inSuite, inContext, gTestPairingSession1, gTestPairingSession2); + + const uint8_t plain_text[] = { 0x86, 0x74, 0x64, 0xe5, 0x0b, 0xd4, 0x0d, 0x90, 0xe1, 0x17, 0xa3, 0x2d, 0x4b, 0xd4, 0xe1, 0xe6 }; + uint8_t encrypted[64]; + PacketHeader header; + MessageAuthenticationCode mac; + + // Let's try encrypting using original session, and decrypting using deserialized + { + SecureSession session1; + + NL_TEST_ASSERT(inSuite, + gTestPairingSession1.DeriveSecureSession(Uint8::from_const_char("abc"), 3, session1) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, + session1.Encrypt(plain_text, sizeof(plain_text), encrypted, header, Header::Flags(), mac) == CHIP_NO_ERROR); + } + + { + SecureSession session2; + NL_TEST_ASSERT(inSuite, + gTestPairingSession2.DeriveSecureSession(Uint8::from_const_char("abc"), 3, session2) == CHIP_NO_ERROR); + + uint8_t decrypted[64]; + NL_TEST_ASSERT(inSuite, + session2.Decrypt(encrypted, sizeof(plain_text), decrypted, header, Header::Flags(), mac) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, memcmp(plain_text, decrypted, sizeof(plain_text)) == 0); + } +} + // Test Suite /** @@ -142,6 +200,7 @@ static const nlTest sTests[] = NL_TEST_DEF("WaitInit", SecurePairingWaitTest), NL_TEST_DEF("Start", SecurePairingStartTest), NL_TEST_DEF("Handshake", SecurePairingHandshakeTest), + NL_TEST_DEF("Serialize", SecurePairingSerializeTest), NL_TEST_SENTINEL() };