From 331447fd7547718d5080bd0b3b88f903a1a60c2c Mon Sep 17 00:00:00 2001 From: Jean-Francois Penven <67962328+jepenven-silabs@users.noreply.github.com> Date: Thu, 31 Aug 2023 23:02:05 -0400 Subject: [PATCH] [ICD] Add Check-In payload generation (#27615) * Add Check-In payload generation * Applied PR comments * Fix last comment * address comments * address comments * fix comments * fix payload representation type * Fix OpenIotSDK failure * fix last test issue --- src/crypto/CHIPCryptoPAL.h | 2 + src/protocols/secure_channel/BUILD.gn | 3 + .../secure_channel/CheckinMessage.cpp | 102 +++++++ src/protocols/secure_channel/CheckinMessage.h | 90 ++++++ src/protocols/secure_channel/tests/BUILD.gn | 2 + .../secure_channel/tests/TestCheckinMsg.cpp | 272 ++++++++++++++++++ 6 files changed, 471 insertions(+) create mode 100644 src/protocols/secure_channel/CheckinMessage.cpp create mode 100644 src/protocols/secure_channel/CheckinMessage.h create mode 100644 src/protocols/secure_channel/tests/TestCheckinMsg.cpp diff --git a/src/crypto/CHIPCryptoPAL.h b/src/crypto/CHIPCryptoPAL.h index be19881c342c43..9e276b9eea4359 100644 --- a/src/crypto/CHIPCryptoPAL.h +++ b/src/crypto/CHIPCryptoPAL.h @@ -93,6 +93,8 @@ constexpr size_t kAES_CCM128_Block_Length = kAES_CCM128_Key_Length; constexpr size_t kAES_CCM128_Nonce_Length = 13; constexpr size_t kAES_CCM128_Tag_Length = 16; +constexpr size_t CHIP_CRYPTO_AEAD_NONCE_LENGTH_BYTES = kAES_CCM128_Nonce_Length; + /* These sizes are hardcoded here to remove header dependency on underlying crypto library * in a public interface file. The validity of these sizes is verified by static_assert in * the implementation files. diff --git a/src/protocols/secure_channel/BUILD.gn b/src/protocols/secure_channel/BUILD.gn index 93997d0642b37f..5a24a984719a5e 100644 --- a/src/protocols/secure_channel/BUILD.gn +++ b/src/protocols/secure_channel/BUILD.gn @@ -29,6 +29,8 @@ static_library("secure_channel") { "CASEServer.h", "CASESession.cpp", "CASESession.h", + "CheckinMessage.cpp", + "CheckinMessage.h", "DefaultSessionResumptionStorage.cpp", "DefaultSessionResumptionStorage.h", "PASESession.cpp", @@ -50,6 +52,7 @@ static_library("secure_channel") { public_deps = [ ":type_definitions", + "${chip_root}/src/crypto", "${chip_root}/src/lib/core", "${chip_root}/src/lib/support", "${chip_root}/src/messaging", diff --git a/src/protocols/secure_channel/CheckinMessage.cpp b/src/protocols/secure_channel/CheckinMessage.cpp new file mode 100644 index 00000000000000..358133a42b81b1 --- /dev/null +++ b/src/protocols/secure_channel/CheckinMessage.cpp @@ -0,0 +1,102 @@ +/* + * 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. + */ + +/** + * @file + * This file implements the Matter Checkin protocol. + */ + +#include "CheckinMessage.h" +#include + +#include +#include + +namespace chip { +namespace Protocols { +namespace SecureChannel { + +CHIP_ERROR CheckinMessage::GenerateCheckinMessagePayload(Crypto::Aes128KeyHandle & key, CounterType counter, + const ByteSpan & appData, MutableByteSpan & output) +{ + VerifyOrReturnError(appData.size() <= sMaxAppDataSize, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(output.size() >= (appData.size() + sMinPayloadSize), CHIP_ERROR_INVALID_ARGUMENT); + + CHIP_ERROR err = CHIP_NO_ERROR; + uint8_t * appDataStartPtr = output.data() + CHIP_CRYPTO_AEAD_NONCE_LENGTH_BYTES; + Encoding::LittleEndian::Put32(appDataStartPtr, counter); + + chip::Crypto::HMAC_sha shaHandler; + uint8_t nonceWorkBuffer[CHIP_CRYPTO_HASH_LEN_BYTES] = { 0 }; + + ReturnErrorOnFailure(shaHandler.HMAC_SHA256(key.As(), sizeof(Aes128KeyByteArray), appDataStartPtr, + sizeof(CounterType), nonceWorkBuffer, CHIP_CRYPTO_HASH_LEN_BYTES)); + + static_assert(sizeof(nonceWorkBuffer) >= CHIP_CRYPTO_AEAD_NONCE_LENGTH_BYTES, "We're reading off the end of our buffer."); + memcpy(output.data(), nonceWorkBuffer, CHIP_CRYPTO_AEAD_NONCE_LENGTH_BYTES); + + // In place encryption to save some RAM + memcpy(appDataStartPtr + sizeof(CounterType), appData.data(), appData.size()); + + uint8_t * micPtr = appDataStartPtr + sizeof(CounterType) + appData.size(); + ReturnErrorOnFailure(Crypto::AES_CCM_encrypt(appDataStartPtr, sizeof(CounterType) + appData.size(), nullptr, 0, key, + output.data(), CHIP_CRYPTO_AEAD_NONCE_LENGTH_BYTES, appDataStartPtr, micPtr, + CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES)); + + output.reduce_size(appData.size() + sMinPayloadSize); + + return err; +} + +CHIP_ERROR CheckinMessage::ParseCheckinMessagePayload(Crypto::Aes128KeyHandle & key, ByteSpan & payload, CounterType & counter, + MutableByteSpan & appData) +{ + VerifyOrReturnError(payload.size() >= sMinPayloadSize, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(payload.size() <= (sMinPayloadSize + sMaxAppDataSize), CHIP_ERROR_INVALID_ARGUMENT); + + CHIP_ERROR err = CHIP_NO_ERROR; + size_t appDataSize = GetAppDataSize(payload); + + // To prevent workbuffer usage, appData size needs to be large enough to hold both the appData and the counter + VerifyOrReturnError(appData.size() >= sizeof(CounterType) + appDataSize, CHIP_ERROR_INVALID_ARGUMENT); + + ByteSpan nonce = payload.SubSpan(0, CHIP_CRYPTO_AEAD_NONCE_LENGTH_BYTES); + ByteSpan encryptedData = payload.SubSpan(CHIP_CRYPTO_AEAD_NONCE_LENGTH_BYTES, sizeof(CounterType) + appDataSize); + ByteSpan mic = + payload.SubSpan(CHIP_CRYPTO_AEAD_NONCE_LENGTH_BYTES + sizeof(CounterType) + appDataSize, CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES); + + err = Crypto::AES_CCM_decrypt(encryptedData.data(), encryptedData.size(), nullptr, 0, mic.data(), mic.size(), key, nonce.data(), + nonce.size(), appData.data()); + + ReturnErrorOnFailure(err); + + counter = Encoding::LittleEndian::Get32(appData.data()); + // Shift to remove the counter from the appData + memmove(appData.data(), sizeof(CounterType) + appData.data(), appDataSize); + + appData.reduce_size(appDataSize); + return err; +} + +size_t CheckinMessage::GetAppDataSize(ByteSpan & payload) +{ + return (payload.size() <= sMinPayloadSize) ? 0 : payload.size() - sMinPayloadSize; +} + +} // namespace SecureChannel +} // namespace Protocols +} // namespace chip diff --git a/src/protocols/secure_channel/CheckinMessage.h b/src/protocols/secure_channel/CheckinMessage.h new file mode 100644 index 00000000000000..aa494c3689b5c8 --- /dev/null +++ b/src/protocols/secure_channel/CheckinMessage.h @@ -0,0 +1,90 @@ +/* + * 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. + */ + +/** + * @file + * This file implements the Matter Checkin protocol. + */ + +#pragma once + +#include +#include +#include + +namespace chip { +namespace Protocols { +namespace SecureChannel { +using namespace Crypto; + +using CounterType = uint32_t; + +/** + * @brief Implement section 4.18.2 of the spec regarding + * Check-in message payload + * + */ +class DLL_EXPORT CheckinMessage +{ +public: + ~CheckinMessage(); + /** + * @brief Generate Check-in Message payload + * + * @param key Key with which to encrypt the check-in payload + * @param counter Check-in counter + * @param appData Application Data to incorporate within the Check-in message. Allowed to be empty. + * @param output Buffer in Which to store the generated payload. SUFFICIENT SPACE MUST BE ALLOCATED by the caller + * Required Buffer Size is : GetCheckinPayloadSize(appData.size()) + * @return CHIP_ERROR + */ + static CHIP_ERROR GenerateCheckinMessagePayload(Crypto::Aes128KeyHandle & key, CounterType counter, const ByteSpan & appData, + MutableByteSpan & output); + + /** + * @brief Parse Check-in Message payload + * + * @param key Key with which to decrypt the check-in payload + * @param payload The received payload to decrypt and parse + * @param counter The counter value retrieved from the payload + * @param appData The optional application data decrypted. The size of appData must be at least the size of + * GetAppDataSize(payload) + sizeof(CounterType) + * @return CHIP_ERROR + */ + static CHIP_ERROR ParseCheckinMessagePayload(Crypto::Aes128KeyHandle & key, ByteSpan & payload, CounterType & counter, + MutableByteSpan & appData); + + static inline size_t GetCheckinPayloadSize(size_t appDataSize) { return appDataSize + sMinPayloadSize; } + + /** + * @brief Get the App Data Size + * + * @param payload The undecrypted payload + * @return size_t size in byte of the application data from the payload + */ + static size_t GetAppDataSize(ByteSpan & payload); + + static constexpr uint16_t sMinPayloadSize = + CHIP_CRYPTO_AEAD_NONCE_LENGTH_BYTES + sizeof(CounterType) + CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES; + + // Issue #28603 + static constexpr uint16_t sMaxAppDataSize = 1024; +}; + +} // namespace SecureChannel +} // namespace Protocols +} // namespace chip diff --git a/src/protocols/secure_channel/tests/BUILD.gn b/src/protocols/secure_channel/tests/BUILD.gn index 6f7963a2396883..5c3ec4a30696c3 100644 --- a/src/protocols/secure_channel/tests/BUILD.gn +++ b/src/protocols/secure_channel/tests/BUILD.gn @@ -13,6 +13,7 @@ chip_test_suite("tests") { # TODO - Fix Message Counter Sync to use group key # "TestMessageCounterManager.cpp", + "TestCheckinMsg.cpp", "TestDefaultSessionResumptionStorage.cpp", "TestPASESession.cpp", "TestPairingSession.cpp", @@ -22,6 +23,7 @@ chip_test_suite("tests") { public_deps = [ "${chip_root}/src/credentials/tests:cert_test_vectors", + "${chip_root}/src/crypto/tests:tests_lib", "${chip_root}/src/lib/core", "${chip_root}/src/lib/support", "${chip_root}/src/lib/support:testing", diff --git a/src/protocols/secure_channel/tests/TestCheckinMsg.cpp b/src/protocols/secure_channel/tests/TestCheckinMsg.cpp new file mode 100644 index 00000000000000..37f7c765729dd3 --- /dev/null +++ b/src/protocols/secure_channel/tests/TestCheckinMsg.cpp @@ -0,0 +1,272 @@ +/* + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +using namespace chip; +using namespace chip::Protocols; +using namespace chip::Protocols::SecureChannel; +using TestSessionKeystoreImpl = Crypto::DefaultSessionKeystore; + +void TestCheckin_Generate(nlTestSuite * inSuite, void * inContext) +{ + uint8_t a[300] = { 0 }; + uint8_t b[300] = { 0 }; + MutableByteSpan outputBuffer{ a }; + MutableByteSpan oldOutputBuffer{ b }; + uint32_t counter = 0; + ByteSpan userData; + CHIP_ERROR err = CHIP_NO_ERROR; + TestSessionKeystoreImpl keystore; + + // Verify that keys imported to the keystore behave as expected. + for (const ccm_128_test_vector * testPtr : ccm_128_test_vectors) + { + const ccm_128_test_vector & test = *testPtr; + + Aes128KeyByteArray keyMaterial; + memcpy(keyMaterial, test.key, test.key_len); + + Aes128KeyHandle keyHandle; + NL_TEST_ASSERT_SUCCESS(inSuite, keystore.CreateKey(keyMaterial, keyHandle)); + + // Validate that counter change, indeed changes the output buffer content + counter = 0; + for (uint8_t j = 0; j < 5; j++) + { + err = CheckinMessage::GenerateCheckinMessagePayload(keyHandle, counter, userData, outputBuffer); + NL_TEST_ASSERT(inSuite, (CHIP_NO_ERROR == err)); + + // Verifiy that the output buffer changed + NL_TEST_ASSERT(inSuite, !outputBuffer.data_equal(oldOutputBuffer)); + CopySpanToMutableSpan(outputBuffer, oldOutputBuffer); + + // Increment by a random count. On the slim changes the increment is 0 add 1 to change output buffer + counter += chip::Crypto::GetRandU32() + 1; + outputBuffer = MutableByteSpan(a); + } + keystore.DestroyKey(keyHandle); + } + + // Parameter check + { + uint8_t data[] = { "This is some user Data. It should be encrypted" }; + userData = chip::ByteSpan(data); + const ccm_128_test_vector & test = *ccm_128_test_vectors[0]; + uint8_t gargantuaBuffer[2 * CheckinMessage::sMaxAppDataSize] = { 0 }; + + Aes128KeyByteArray keyMaterial; + memcpy(keyMaterial, test.key, test.key_len); + + Aes128KeyHandle keyHandle; + NL_TEST_ASSERT_SUCCESS(inSuite, keystore.CreateKey(keyMaterial, keyHandle)); + + // As of now passing an empty key handle while using PSA crypto will result in a failure. + // However when using OpenSSL this same test result in a success. + // Issue #28986 + + // Aes128KeyHandle emptyKeyHandle; + // err = CheckinMessage::GenerateCheckinMessagePayload(emptyKeyHandle, counter, userData, outputBuffer); + // ChipLogError(Inet, "%s", err.AsString()); + // NL_TEST_ASSERT(inSuite, (CHIP_NO_ERROR == err)); + + ByteSpan emptyData; + err = CheckinMessage::GenerateCheckinMessagePayload(keyHandle, counter, emptyData, outputBuffer); + NL_TEST_ASSERT(inSuite, (CHIP_NO_ERROR == err)); + + MutableByteSpan empty; + err = CheckinMessage::GenerateCheckinMessagePayload(keyHandle, counter, emptyData, empty); + NL_TEST_ASSERT(inSuite, (CHIP_ERROR_INVALID_ARGUMENT == err)); + + userData = chip::ByteSpan(gargantuaBuffer, sizeof(gargantuaBuffer)); + err = CheckinMessage::GenerateCheckinMessagePayload(keyHandle, counter, userData, outputBuffer); + NL_TEST_ASSERT(inSuite, (CHIP_ERROR_INVALID_ARGUMENT == err)); + + // Cleanup + keystore.DestroyKey(keyHandle); + } +} + +void TestCheckin_Parse(nlTestSuite * inSuite, void * inContext) +{ + uint8_t a[300] = { 0 }; + uint8_t b[300] = { 0 }; + MutableByteSpan outputBuffer{ a }; + MutableByteSpan buffer{ b }; + uint32_t counter = 0, decryptedCounter; + ByteSpan userData; + + CHIP_ERROR err = CHIP_NO_ERROR; + + TestSessionKeystoreImpl keystore; + + // Verify User Data Encryption Decryption + uint8_t data[] = { "This is some user Data. It should be encrypted" }; + userData = chip::ByteSpan(data); + const ccm_128_test_vector & test = *ccm_128_test_vectors[0]; + + Aes128KeyByteArray keyMaterial; + memcpy(keyMaterial, test.key, test.key_len); + + Aes128KeyHandle keyHandle; + NL_TEST_ASSERT_SUCCESS(inSuite, keystore.CreateKey(keyMaterial, keyHandle)); + + //=================Encrypt======================= + + err = CheckinMessage::GenerateCheckinMessagePayload(keyHandle, counter, userData, outputBuffer); + ByteSpan payload = chip::ByteSpan(outputBuffer.data(), outputBuffer.size()); + NL_TEST_ASSERT(inSuite, (CHIP_NO_ERROR == err)); + + //=================Decrypt======================= + + MutableByteSpan empty; + err = CheckinMessage::ParseCheckinMessagePayload(keyHandle, payload, decryptedCounter, empty); + NL_TEST_ASSERT(inSuite, (CHIP_NO_ERROR != err)); + + ByteSpan emptyPayload; + err = CheckinMessage::ParseCheckinMessagePayload(keyHandle, emptyPayload, decryptedCounter, buffer); + NL_TEST_ASSERT(inSuite, (CHIP_NO_ERROR != err)); +} + +void TestCheckin_GenerateParse(nlTestSuite * inSuite, void * inContext) +{ + uint8_t a[300] = { 0 }; + uint8_t b[300] = { 0 }; + MutableByteSpan outputBuffer{ a }; + MutableByteSpan buffer{ b }; + uint32_t counter = 0xDEADBEEF; + ByteSpan userData; + + CHIP_ERROR err = CHIP_NO_ERROR; + + TestSessionKeystoreImpl keystore; + + // Verify User Data Encryption Decryption + uint8_t data[] = { "This is some user Data. It should be encrypted" }; + userData = chip::ByteSpan(data); + for (const ccm_128_test_vector * testPtr : ccm_128_test_vectors) + { + const ccm_128_test_vector & test = *testPtr; + + Aes128KeyByteArray keyMaterial; + memcpy(keyMaterial, test.key, test.key_len); + + Aes128KeyHandle keyHandle; + NL_TEST_ASSERT_SUCCESS(inSuite, keystore.CreateKey(keyMaterial, keyHandle)); + + //=================Encrypt======================= + + err = CheckinMessage::GenerateCheckinMessagePayload(keyHandle, counter, userData, outputBuffer); + NL_TEST_ASSERT(inSuite, (CHIP_NO_ERROR == err)); + + //=================Decrypt======================= + uint32_t decryptedCounter = 0; + ByteSpan payload = chip::ByteSpan(outputBuffer.data(), outputBuffer.size()); + + err = CheckinMessage::ParseCheckinMessagePayload(keyHandle, payload, decryptedCounter, buffer); + NL_TEST_ASSERT(inSuite, (CHIP_NO_ERROR == err)); + + NL_TEST_ASSERT(inSuite, (memcmp(data, buffer.data(), sizeof(data)) == 0)); + NL_TEST_ASSERT(inSuite, (counter == decryptedCounter)); + + // reset buffers + memset(a, 0, sizeof(a)); + memset(b, 0, sizeof(b)); + outputBuffer = MutableByteSpan(a); + buffer = MutableByteSpan(b); + + counter += chip::Crypto::GetRandU32() + 1; + keystore.DestroyKey(keyHandle); + } +} + +// Test Suite + +/** + * Test Suite that lists all the test functions. + */ +// clang-format off +static const nlTest sTests[] = +{ + NL_TEST_DEF("TestCheckin_Generate", TestCheckin_Generate), + NL_TEST_DEF("TestCheckin_Parse", TestCheckin_Parse), + NL_TEST_DEF("TestCheckin_GenerateParse", TestCheckin_GenerateParse), + + NL_TEST_SENTINEL() +}; +// clang-format on + +/** + * Set up the test suite. + */ +static int TestSetup(void * inContext) +{ + CHIP_ERROR error = chip::Platform::MemoryInit(); + if (error != CHIP_NO_ERROR) + return FAILURE; + return SUCCESS; +} + +/** + * Tear down the test suite. + */ +static int TestTeardown(void * inContext) +{ + chip::Platform::MemoryShutdown(); + return SUCCESS; +} + +// clang-format off +static nlTestSuite sSuite = +{ + "Test-CHIP-Checkin-Message", + &sTests[0], + TestSetup, + TestTeardown, +}; +// clang-format on + +/** + * Main + */ +int TestCheckinMessage() +{ + // Run test suit against one context + nlTestRunner(&sSuite, nullptr); + + return (nlTestRunnerStats(&sSuite)); +} + +CHIP_REGISTER_TEST_SUITE(TestCheckinMessage)