| /* |
| * |
| * 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 |