blob: 23f3b2f2d68ca4d5e0bcc456efa6ca613feb07b9 [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pw_unit_test/framework.h>
#include <crypto/DefaultSessionKeystore.h>
#include <crypto/RandUtils.h>
#include <lib/core/StringBuilderAdapters.h>
#include <lib/support/BufferWriter.h>
#include <lib/support/CHIPMem.h>
#include <protocols/Protocols.h>
#include <protocols/secure_channel/CheckinMessage.h>
#include <protocols/secure_channel/Constants.h>
#include <protocols/secure_channel/StatusReport.h>
#include <protocols/secure_channel/tests/CheckIn_Message_test_vectors.h>
#include <transport/CryptoContext.h>
using namespace chip;
using namespace chip::Protocols;
using namespace chip::Protocols::SecureChannel;
using namespace chip::Crypto;
using TestSessionKeystoreImpl = Crypto::DefaultSessionKeystore;
namespace {
/**
* @brief Helper function that generates the Check-In message based on the test vector
* and verifies the generated Check-In message
* Helper is to avoid having the same code three times in different tests
*
* @return CHIP_NO_ERROR if the generation was successful
* error code if the generation failed - see GenerateCheckinMessagePayload
*/
CHIP_ERROR GenerateAndVerifyPayload(MutableByteSpan & output, const CheckIn_Message_test_vector & vector)
{
TestSessionKeystoreImpl keystore;
// Two distinct key material buffers to ensure crypto-hardware-assist with single-usage keys create two different handles.
Symmetric128BitsKeyByteArray aesKeyMaterial;
memcpy(aesKeyMaterial, vector.key, vector.key_len);
Symmetric128BitsKeyByteArray hmacKeyMaterial;
memcpy(hmacKeyMaterial, vector.key, vector.key_len);
Aes128KeyHandle aes128KeyHandle;
EXPECT_EQ(keystore.CreateKey(aesKeyMaterial, aes128KeyHandle), CHIP_NO_ERROR);
Hmac128KeyHandle hmac128KeyHandle;
EXPECT_EQ(keystore.CreateKey(hmacKeyMaterial, hmac128KeyHandle), CHIP_NO_ERROR);
// Create application data ByteSpan
ByteSpan applicationData(vector.application_data, vector.application_data_len);
// Verify that the generation succeeded
CHIP_ERROR err =
CheckinMessage::GenerateCheckinMessagePayload(aes128KeyHandle, hmac128KeyHandle, vector.counter, applicationData, output);
if (err != CHIP_NO_ERROR)
{
keystore.DestroyKey(aes128KeyHandle);
keystore.DestroyKey(hmac128KeyHandle);
return err;
}
// Validate Full payload
EXPECT_EQ(output.size(), vector.payload_len);
EXPECT_EQ(memcmp(vector.payload, output.data(), output.size()), 0);
size_t cursorIndex = 0;
// Validate Nonce
MutableByteSpan nonce = output.SubSpan(cursorIndex, vector.nonce_len);
EXPECT_EQ(memcmp(vector.nonce, nonce.data(), nonce.size()), 0);
cursorIndex += nonce.size();
// Validate ciphertext
MutableByteSpan ciphertext = output.SubSpan(cursorIndex, vector.ciphertext_len);
EXPECT_EQ(memcmp(vector.ciphertext, ciphertext.data(), ciphertext.size()), 0);
cursorIndex += ciphertext.size();
// Validate MIC
MutableByteSpan mic = output.SubSpan(cursorIndex, vector.mic_len);
EXPECT_EQ(memcmp(vector.mic, mic.data(), mic.size()), 0);
cursorIndex += mic.size();
// Clean up
keystore.DestroyKey(aes128KeyHandle);
keystore.DestroyKey(hmac128KeyHandle);
return err;
}
/**
* @brief Helper function that parses the Check-In message based on the test vector
* and verifies parsed Check-In message
* Helper is to avoid having the same code in multiple tests
*
* @return CHIP_NO_ERROR if the parsing was successful
* error code if the generation failed - see ParseCheckinMessagePayload
*/
CHIP_ERROR ParseAndVerifyPayload(MutableByteSpan & applicationData, const CheckIn_Message_test_vector & vector,
bool injectInvalidNonce)
{
TestSessionKeystoreImpl keystore;
// Copy payload to be able to modify it for invalid nonce tests
uint8_t payloadBuffer[300] = { 0 };
memcpy(payloadBuffer, vector.payload, vector.payload_len);
if (injectInvalidNonce)
{
// Modify nonce to validate that the parsing can detect that the message was manipulated
payloadBuffer[0] ^= 0xFF;
}
// Create payload byte span
ByteSpan payload(payloadBuffer, vector.payload_len);
CounterType decryptedCounter = 0;
// Two distinct key material buffers to ensure crypto-hardware-assist with single-usage keys create two different handles.
Symmetric128BitsKeyByteArray aesKeyMaterial;
memcpy(aesKeyMaterial, vector.key, vector.key_len);
Symmetric128BitsKeyByteArray hmacKeyMaterial;
memcpy(hmacKeyMaterial, vector.key, vector.key_len);
Aes128KeyHandle aes128KeyHandle;
EXPECT_EQ(keystore.CreateKey(aesKeyMaterial, aes128KeyHandle), CHIP_NO_ERROR);
Hmac128KeyHandle hmac128KeyHandle;
EXPECT_EQ(keystore.CreateKey(hmacKeyMaterial, hmac128KeyHandle), CHIP_NO_ERROR);
// Verify that the Parsing succeeded
CHIP_ERROR err =
CheckinMessage::ParseCheckinMessagePayload(aes128KeyHandle, hmac128KeyHandle, payload, decryptedCounter, applicationData);
if (err != CHIP_NO_ERROR)
{
keystore.DestroyKey(aes128KeyHandle);
keystore.DestroyKey(hmac128KeyHandle);
return err;
}
// Verify decrypted counter value
EXPECT_EQ(vector.counter, decryptedCounter);
// Verify application data
EXPECT_EQ(vector.application_data_len, applicationData.size());
EXPECT_EQ(memcmp(vector.application_data, applicationData.data(), applicationData.size()), 0);
// Cleanup
keystore.DestroyKey(aes128KeyHandle);
keystore.DestroyKey(hmac128KeyHandle);
return err;
}
/**
* @brief Test verifies that the Check-In message generation is successful when using an output size equal to the payload size
*/
TEST(TestCheckInMsg, TestCheckinMessageGenerate_ValidInputsSameSizeOutputAsPayload)
{
int numOfTestCases = ArraySize(checkIn_message_test_vectors);
for (int numOfTestsExecuted = 0; numOfTestsExecuted < numOfTestCases; numOfTestsExecuted++)
{
CheckIn_Message_test_vector vector = checkIn_message_test_vectors[numOfTestsExecuted];
// Create output buffer
uint8_t buffer[300] = { 0 };
MutableByteSpan output(buffer, sizeof(buffer));
// Force output buffer to the payload size
output.reduce_size(vector.payload_len);
EXPECT_EQ(GenerateAndVerifyPayload(output, vector), CHIP_NO_ERROR);
}
}
/**
* @brief Test verifies that the Check-In message generation is successful when using an output size greater than the payload size
*/
TEST(TestCheckInMsg, TestCheckinMessageGenerate_ValidInputsBiggerSizeOutput)
{
int numOfTestCases = ArraySize(checkIn_message_test_vectors);
for (int numOfTestsExecuted = 0; numOfTestsExecuted < numOfTestCases; numOfTestsExecuted++)
{
CheckIn_Message_test_vector vector = checkIn_message_test_vectors[numOfTestsExecuted];
// Create output buffer
uint8_t buffer[300] = { 0 };
MutableByteSpan output(buffer, sizeof(buffer));
EXPECT_EQ(GenerateAndVerifyPayload(output, vector), CHIP_NO_ERROR);
}
}
/**
* @brief Test verifies that the Check-In message generation returns an error if the output buffer is too small
*/
TEST(TestCheckInMsg, TestCheckinMessageGenerate_ValidInputsTooSmallOutput)
{
CheckIn_Message_test_vector vector = checkIn_message_test_vectors[0];
// Create output buffer with 0 size
MutableByteSpan output;
EXPECT_EQ(GenerateAndVerifyPayload(output, vector), CHIP_ERROR_BUFFER_TOO_SMALL);
}
/**
* @brief Test verifies that the Check-In Message generations returns an error if the AesKeyHandle is empty
*/
TEST(TestCheckInMsg, TestCheckInMessageGenerate_EmptyAesKeyHandle)
{
TestSessionKeystoreImpl keystore;
CheckIn_Message_test_vector vector = checkIn_message_test_vectors[0];
// Create output buffer
uint8_t buffer[300] = { 0 };
MutableByteSpan output(buffer, sizeof(buffer));
// Force output buffer to the payload size
output.reduce_size(vector.payload_len);
// Empty AES Key handle
Aes128KeyHandle aes128KeyHandle;
Symmetric128BitsKeyByteArray hmacKeyMaterial;
memcpy(hmacKeyMaterial, vector.key, vector.key_len);
Hmac128KeyHandle hmac128KeyHandle;
EXPECT_EQ(keystore.CreateKey(hmacKeyMaterial, hmac128KeyHandle), CHIP_NO_ERROR);
// Create application data ByteSpan
ByteSpan applicationData(vector.application_data, vector.application_data_len);
/*
TODO(#28986): Passing an empty key handle while using PSA crypto will result in a failure.
When using OpenSSL this same test result in a success.
*/
#if 0
// Verify that the generation fails with an empty key handle
EXPECT_NE(
CHIP_NO_ERROR,
CheckinMessage::GenerateCheckinMessagePayload(aes128KeyHandle, hmac128KeyHandle, vector.counter, applicationData, output));
#endif
// Clean up
keystore.DestroyKey(hmac128KeyHandle);
}
/**
* @brief Test verifies that the Check-In Message generations returns an error if the HmacKeyHandle is empty
*/
TEST(TestCheckInMsg, TestCheckInMessageGenerate_EmptyHmacKeyHandle)
{
TestSessionKeystoreImpl keystore;
CheckIn_Message_test_vector vector = checkIn_message_test_vectors[0];
// Create output buffer
uint8_t buffer[300] = { 0 };
MutableByteSpan output(buffer, sizeof(buffer));
// Force output buffer to the payload size
output.reduce_size(vector.payload_len);
Symmetric128BitsKeyByteArray aesKeyMaterial;
memcpy(aesKeyMaterial, vector.key, vector.key_len);
Aes128KeyHandle aes128KeyHandle;
EXPECT_EQ(keystore.CreateKey(aesKeyMaterial, aes128KeyHandle), CHIP_NO_ERROR);
Hmac128KeyHandle hmac128KeyHandle;
// Create application data ByteSpan
ByteSpan applicationData(vector.application_data, vector.application_data_len);
/*
TODO(#28986): Passing an empty key handle while using PSA crypto will result in a failure.
When using OpenSSL this same test result in a success.
*/
#if 0
// Verify that the generation fails with an empty key handle
EXPECT_NE(
CHIP_NO_ERROR,
CheckinMessage::GenerateCheckinMessagePayload(aes128KeyHandle, hmac128KeyHandle, vector.counter, applicationData, output));
#endif
// Clean up
keystore.DestroyKey(aes128KeyHandle);
}
/**
* @brief Test verifies that the Check-In message parsing succeeds with the Application buffer set to the minimum required size
*/
TEST(TestCheckInMsg, TestCheckinMessageParse_ValidInputsSameSizeMinAppData)
{
int numOfTestCases = ArraySize(checkIn_message_test_vectors);
for (int numOfTestsExecuted = 0; numOfTestsExecuted < numOfTestCases; numOfTestsExecuted++)
{
CheckIn_Message_test_vector vector = checkIn_message_test_vectors[numOfTestsExecuted];
uint8_t applicationDataBuffer[128] = { 0 };
MutableByteSpan applicationData(applicationDataBuffer, sizeof(applicationDataBuffer));
applicationData.reduce_size(vector.application_data_len + sizeof(CounterType));
EXPECT_EQ(ParseAndVerifyPayload(applicationData, vector, false), CHIP_NO_ERROR);
}
}
/**
* @brief Test verifies that the Check-In message parsing succeeds with the Application buffer set to a larger than necessary size
*/
TEST(TestCheckInMsg, TestCheckinMessageParse_ValidInputsBiggerSizeMinAppData)
{
int numOfTestCases = ArraySize(checkIn_message_test_vectors);
for (int numOfTestsExecuted = 0; numOfTestsExecuted < numOfTestCases; numOfTestsExecuted++)
{
CheckIn_Message_test_vector vector = checkIn_message_test_vectors[numOfTestsExecuted];
uint8_t applicationDataBuffer[128] = { 0 };
MutableByteSpan applicationData(applicationDataBuffer, sizeof(applicationDataBuffer));
EXPECT_EQ(ParseAndVerifyPayload(applicationData, vector, false), CHIP_NO_ERROR);
}
}
/**
* @brief Test verifies that the Check-In message throws an error if the application data buffer is too small
*/
TEST(TestCheckInMsg, TestCheckinMessageParse_ValidInputsTooSmallAppData)
{
CheckIn_Message_test_vector vector = checkIn_message_test_vectors[0];
// Create applicationData buffer with 0 size
MutableByteSpan applicationData;
EXPECT_EQ(ParseAndVerifyPayload(applicationData, vector, false), CHIP_ERROR_BUFFER_TOO_SMALL);
}
/**
* @brief Test verifies that the Check-In Message parsing returns an error if the AesKeyHandle is empty
*/
TEST(TestCheckInMsg, TestCheckInMessageParse_EmptyAesKeyHandle)
{
TestSessionKeystoreImpl keystore;
CheckIn_Message_test_vector vector = checkIn_message_test_vectors[0];
// Create application data ByteSpan
uint8_t applicationDataBuffer[128] = { 0 };
MutableByteSpan applicationData(applicationDataBuffer, sizeof(applicationDataBuffer));
applicationData.reduce_size(vector.application_data_len + sizeof(CounterType));
// Create payload byte span
ByteSpan payload(vector.payload, vector.payload_len);
CounterType decryptedCounter = 0;
//
(void) decryptedCounter;
// Empty AES Key handle
Aes128KeyHandle aes128KeyHandle;
Symmetric128BitsKeyByteArray hmacKeyMaterial;
memcpy(hmacKeyMaterial, vector.key, vector.key_len);
Hmac128KeyHandle hmac128KeyHandle;
EXPECT_EQ(keystore.CreateKey(hmacKeyMaterial, hmac128KeyHandle), CHIP_NO_ERROR);
/*
TODO(#28986): Passing an empty key handle while using PSA crypto will result in a failure.
When using OpenSSL this same test result in a success.
*/
#if 0
// Verify that the generation fails with an empty key handle
EXPECT_NE(
CHIP_NO_ERROR,
CheckinMessage::ParseCheckinMessagePayload(aes128KeyHandle, hmac128KeyHandle, payload, decryptedCounter, applicationData));
#endif
// Clean up
keystore.DestroyKey(hmac128KeyHandle);
}
/**
* @brief Test verifies that the Check-In Message parsing returns an error if the HmacKeyHandle is empty
*/
TEST(TestCheckInMsg, TestCheckInMessageParse_EmptyHmacKeyHandle)
{
TestSessionKeystoreImpl keystore;
CheckIn_Message_test_vector vector = checkIn_message_test_vectors[0];
// Create application data ByteSpan
uint8_t applicationDataBuffer[128] = { 0 };
MutableByteSpan applicationData(applicationDataBuffer, sizeof(applicationDataBuffer));
applicationData.reduce_size(vector.application_data_len + sizeof(CounterType));
// Create payload byte span
ByteSpan payload(vector.payload, vector.payload_len);
CounterType decryptedCounter = 0;
//
(void) decryptedCounter;
// Empty Hmac Key handle
Hmac128KeyHandle hmac128KeyHandle;
Symmetric128BitsKeyByteArray aesKeyMaterial;
memcpy(aesKeyMaterial, vector.key, vector.key_len);
Aes128KeyHandle aes128KeyHandle;
EXPECT_EQ(keystore.CreateKey(aesKeyMaterial, aes128KeyHandle), CHIP_NO_ERROR);
/*
TODO(#28986): Passing an empty key handle while using PSA crypto will result in a failure.
When using OpenSSL this same test result in a success.
*/
#if 0
// Verify that the generation fails with an empty key handle
EXPECT_NE(
CHIP_NO_ERROR,
CheckinMessage::ParseCheckinMessagePayload(aes128KeyHandle, hmac128KeyHandle, payload, decryptedCounter, applicationData));
#endif
// Clean up
keystore.DestroyKey(aes128KeyHandle);
}
/**
* @brief Test verifies that the Check-In message processing throws an error if the nonce is corrupted
*/
TEST(TestCheckInMsg, TestCheckinMessageParse_CorruptedNonce)
{
int numOfTestCases = ArraySize(checkIn_message_test_vectors);
for (int numOfTestsExecuted = 0; numOfTestsExecuted < numOfTestCases; numOfTestsExecuted++)
{
CheckIn_Message_test_vector vector = checkIn_message_test_vectors[numOfTestsExecuted];
uint8_t applicationDataBuffer[128] = { 0 };
MutableByteSpan applicationData(applicationDataBuffer, sizeof(applicationDataBuffer));
applicationData.reduce_size(vector.application_data_len + sizeof(CounterType));
EXPECT_EQ(ParseAndVerifyPayload(applicationData, vector, true), CHIP_ERROR_INTERNAL);
}
}
/**
* @brief Test verifies that the Check-In message processing throws an error if the nonce was not calculated with the counter in the
* payload
*/
TEST(TestCheckInMsg, TestCheckinMessageParse_InvalidNonce)
{
CheckIn_Message_test_vector vector = invalidNonceVector;
uint8_t applicationDataBuffer[128] = { 0 };
MutableByteSpan applicationData(applicationDataBuffer, sizeof(applicationDataBuffer));
applicationData.reduce_size(vector.application_data_len + sizeof(CounterType));
EXPECT_EQ(ParseAndVerifyPayload(applicationData, vector, true), CHIP_ERROR_INTERNAL);
}
/**
* @brief test verifies that GetAppDataSize returns the correct application data size
*/
TEST(TestCheckInMsg, TestCheckInMessagePayloadSize)
{
int numOfTestCases = ArraySize(checkIn_message_test_vectors);
for (int numOfTestsExecuted = 0; numOfTestsExecuted < numOfTestCases; numOfTestsExecuted++)
{
CheckIn_Message_test_vector vector = checkIn_message_test_vectors[numOfTestsExecuted];
ByteSpan payload(vector.payload, vector.payload_len);
size_t calculated_size = CheckinMessage::GetAppDataSize(payload);
// Verify the AppData size matches the expected application data size
EXPECT_EQ(vector.application_data_len, calculated_size);
}
}
/**
* @brief test verifies that GetAppDataSize returns 0 if the payload is smaller that the minimum size
*/
TEST(TestCheckInMsg, TestCheckInMessagePayloadSizeNullBuffer)
{
ByteSpan payload;
size_t calculated_size = CheckinMessage::GetAppDataSize(payload);
size_t expected_size = 0;
// Verify that the size is 0
EXPECT_EQ(calculated_size, expected_size);
}
} // namespace