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