blob: b57cc0f2eee6d65d79f3116157d0edd7f5f6c529 [file] [log] [blame]
/*
*
* Copyright (c) 2016-2017 Nest Labs, Inc.
* 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 a unit test for the Weave message encoding
* and decoding functions of the WeaveMessageLayer class.
*
*/
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif
#include <stdio.h>
#include <nltest.h>
#include <string.h>
#include "ToolCommon.h"
#include <Weave/Core/WeaveConfig.h>
#include <Weave/Support/crypto/CTRMode.h>
#include <Weave/Support/crypto/WeaveCrypto.h>
#if WEAVE_SYSTEM_CONFIG_USE_LWIP
#include "lwip/tcpip.h"
#endif // WEAVE_SYSTEM_CONFIG_USE_LWIP
using namespace nl::Inet;
using namespace nl::Weave::Encoding;
using namespace nl::Weave::Crypto;
using namespace nl::Weave::Profiles::Security;
#define DEBUG_PRINT_ENABLE 0
namespace nl {
namespace Weave {
class NL_DLL_EXPORT WeaveMessageLayerTestObject
{
public:
WeaveMessageLayer *msgLayer;
WEAVE_ERROR DecodeMessage(PacketBuffer *msgBuf, uint64_t sourceNodeId, WeaveConnection *con,
WeaveMessageInfo *msgInfo, uint8_t **rPayload, uint16_t *rPayloadLen)
{
return msgLayer->DecodeMessage(msgBuf, sourceNodeId, con, msgInfo, rPayload, rPayloadLen);
}
};
} // namespace nl
} // namespace Weave
static const uint8_t sMsgPayload[] =
{
0x05, 0x26, 0xAD, 0xB7, 0xBB, 0xD7, 0x82, 0x52, 0x78, 0x2D, 0x60, 0xD6, 0x40, 0xFD, 0xE6, 0xF9,
0x0A, 0x1A, 0x2A, 0x3A, 0x4A, 0x5A, 0x6A, 0x7A, 0x8A, 0x9A, 0xAA, 0xBA, 0xCA, 0xDA, 0xEA, 0xFA,
0x09, 0x19, 0x29, 0x39, 0x49, 0x59, 0x69, 0x79, 0x89, 0x99, 0xA9, 0xB9, 0xC9, 0xD9, 0xE9, 0xF9,
0x04, 0x14, 0x24, 0x34, 0x44, 0x54, 0x6A
};
static const uint8_t sMsgEncKey_DataKey[] =
{
0xF7, 0xE7, 0xD7, 0xC7, 0xB7, 0xA7, 0x97, 0x87, 0x07, 0x17, 0x27, 0x37, 0x47, 0x57, 0x67, 0x77
};
static const uint8_t sMsgEncKey_IntegrityKey[] =
{
0xFD, 0xED, 0xDD, 0xCD, 0xBD, 0xAD, 0x9D, 0x8D, 0x0D, 0x1D, 0x2D, 0x3D, 0x4D, 0x5D, 0x6D, 0x7D,
0x82, 0x52, 0x78, 0x2D
};
static const uint8_t sEncodedMsg_V1[] =
{
0x10, 0x1B, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x30, 0xB4, 0x18, 0x78, 0x56,
0x34, 0x12, 0x00, 0x30, 0xB4, 0x18, 0x2A, 0x20, 0xAC, 0xBD, 0x69, 0x5C, 0x20, 0x44, 0x51, 0x71,
0x16, 0x1C, 0x29, 0x48, 0xC6, 0x3D, 0xBD, 0x91, 0x33, 0x77, 0x7B, 0x58, 0xFA, 0x9C, 0x08, 0x6B,
0x2B, 0xAF, 0x1D, 0x25, 0x04, 0x01, 0x4E, 0xE2, 0x1E, 0xB7, 0x2F, 0x41, 0x19, 0x2B, 0x81, 0x60,
0x6B, 0xE8, 0xB9, 0x00, 0x08, 0xA1, 0x1C, 0x9C, 0x62, 0x3B, 0x23, 0x1D, 0x99, 0xBC, 0xA4, 0x7B,
0x54, 0x00, 0xA2, 0x0B, 0x20, 0x3B, 0xA7, 0x80, 0x01, 0xAC, 0xF4, 0xF4, 0xF5, 0x50, 0x24, 0x63,
0xF1, 0xBA, 0x50
};
static const uint8_t sEncodedMsg_V2[] =
{
0x10, 0x2B, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x30, 0xB4, 0x18, 0x78, 0x56,
0x34, 0x12, 0x00, 0x30, 0xB4, 0x18, 0x2A, 0x20, 0xAC, 0xBD, 0x69, 0x5C, 0x20, 0x44, 0x51, 0x71,
0x16, 0x1C, 0x29, 0x48, 0xC6, 0x3D, 0xBD, 0x91, 0x33, 0x77, 0x7B, 0x58, 0xFA, 0x9C, 0x08, 0x6B,
0x2B, 0xAF, 0x1D, 0x25, 0x04, 0x01, 0x4E, 0xE2, 0x1E, 0xB7, 0x2F, 0x41, 0x19, 0x2B, 0x81, 0x60,
0x6B, 0xE8, 0xB9, 0x00, 0x08, 0xA1, 0x1C, 0x9C, 0x62, 0x3B, 0x23, 0x1D, 0x99, 0xBC, 0xA4, 0x96,
0xD0, 0xAE, 0x61, 0xFD, 0x55, 0x05, 0x43, 0xEA, 0x6C, 0x49, 0x1D, 0xA5, 0x8D, 0x57, 0x3C, 0xF6,
0xD9, 0x27, 0x9D
};
// Test input vector format.
struct TestContext {
uint8_t MsgVersion;
const uint8_t *MsgPayload;
uint16_t MsgPayloadLen;
const uint8_t *EncodedMsg;
uint16_t EncodedMsgLen;
WEAVE_ERROR EncodeError;
WEAVE_ERROR DecodeError;
};
// Test input data.
static struct TestContext sContext[] = {
{ kWeaveMessageVersion_V1, sMsgPayload, sizeof(sMsgPayload), sEncodedMsg_V1, sizeof(sEncodedMsg_V1), WEAVE_NO_ERROR, WEAVE_NO_ERROR },
{ kWeaveMessageVersion_V2, sMsgPayload, sizeof(sMsgPayload), sEncodedMsg_V2, sizeof(sEncodedMsg_V2), WEAVE_NO_ERROR, WEAVE_NO_ERROR },
{ kWeaveMessageVersion_V2, sMsgPayload, sizeof(sMsgPayload), sEncodedMsg_V2, sizeof(sEncodedMsg_V2), WEAVE_NO_ERROR, WEAVE_ERROR_WRONG_ENCRYPTION_TYPE },
{ kWeaveMessageVersion_V2, sMsgPayload, sizeof(sMsgPayload), sEncodedMsg_V2, sizeof(sEncodedMsg_V2), WEAVE_ERROR_WRONG_ENCRYPTION_TYPE, WEAVE_NO_ERROR },
};
// Number of test context examples.
static const size_t kTestElements = sizeof(sContext) / sizeof(struct TestContext);
void WeaveMessageEncryption_Test1(nlTestSuite *inSuite, void *inContext)
{
static WeaveFabricState fabricState;
static WeaveMessageLayer messageLayer;
static WeaveMessageInfo msgInfo;
WEAVE_ERROR err;
PacketBuffer *msgBuf;
WeaveSessionKey *sessionKey;
uint64_t srcNodeId;
uint64_t destNodeId = 0x18B4300012345678;
uint32_t msgId = 3;
uint8_t encType = kWeaveEncryptionType_AES128CTRSHA1;
uint16_t sessionKeyId = sTestDefaultSessionKeyId;
uint8_t *p;
const char localAddrStr[] = "fd00:0:1:1:18B4:3000::2";
IPAddress localIPv6Addr;
NL_TEST_ASSERT(inSuite, ParseIPAddress(localAddrStr, localIPv6Addr));
// Initialize the FabricState object.
err = fabricState.Init();
NL_TEST_ASSERT(inSuite, err == WEAVE_NO_ERROR);
srcNodeId = localIPv6Addr.InterfaceId();
fabricState.LocalNodeId = srcNodeId;
fabricState.FabricId = localIPv6Addr.GlobalId();
fabricState.DefaultSubnet = localIPv6Addr.Subnet();
// Initialize message encryption session key.
WeaveEncryptionKey msgEncSessionKey;
WeaveAuthMode authMode = kWeaveAuthMode_CASE_Device;
memcpy(msgEncSessionKey.AES128CTRSHA1.DataKey, sMsgEncKey_DataKey, sizeof(sMsgEncKey_DataKey));
memcpy(msgEncSessionKey.AES128CTRSHA1.IntegrityKey, sMsgEncKey_IntegrityKey, sizeof(sMsgEncKey_IntegrityKey));
// Initialize session key with destination node id.
err = fabricState.AllocSessionKey(destNodeId, sessionKeyId, NULL, sessionKey);
NL_TEST_ASSERT(inSuite, err == WEAVE_NO_ERROR);
fabricState.SetSessionKey(sessionKey, encType, authMode, &msgEncSessionKey);
// Initialize the same session key with LocalNodeId as a destination node id.
err = fabricState.AllocSessionKey(srcNodeId, sessionKeyId, NULL, sessionKey);
NL_TEST_ASSERT(inSuite, err == WEAVE_NO_ERROR);
fabricState.SetSessionKey(sessionKey, encType, authMode, &msgEncSessionKey);
// Initialize the MessageLayer object.
messageLayer.FabricState = &fabricState;
struct TestContext *theContext = (struct TestContext *)(inContext);
for (size_t ith = 0; ith < kTestElements; ith++, theContext++)
{
uint8_t msgVersion = theContext->MsgVersion;
const uint8_t *msgPayload = theContext->MsgPayload;
uint16_t msgPayloadLen = theContext->MsgPayloadLen;
const uint8_t *encodedMsg = theContext->EncodedMsg;
uint16_t encodedMsgLen = theContext->EncodedMsgLen;
WEAVE_ERROR encodeError = theContext->EncodeError;
WEAVE_ERROR decodeError = theContext->DecodeError;
uint8_t localMsgBuf[theContext->EncodedMsgLen];
// Allocate buffer.
msgBuf = PacketBuffer::New();
NL_TEST_ASSERT(inSuite, msgBuf != NULL);
if (msgBuf == NULL)
continue;
// Copy payload data.
memcpy(msgBuf->Start(), msgPayload, msgPayloadLen);
msgBuf->SetDataLength(msgPayloadLen);
// Initialize the Message info object.
msgInfo.Clear();
msgInfo.SourceNodeId = srcNodeId;
msgInfo.DestNodeId = destNodeId;
msgInfo.MessageId = msgId;
if (encodeError == WEAVE_ERROR_WRONG_ENCRYPTION_TYPE)
msgInfo.KeyId = WeaveKeyId::kNone;
else
msgInfo.KeyId = sessionKeyId;
msgInfo.Flags = kWeaveMessageFlag_DestNodeId |
kWeaveMessageFlag_SourceNodeId |
kWeaveMessageFlag_MsgCounterSyncReq |
kWeaveMessageFlag_ReuseMessageId;
msgInfo.MessageVersion = msgVersion;
msgInfo.EncryptionType = encType;
// =====================================================================================================
// Encode message using EncodeMessage() function.
// =====================================================================================================
err = messageLayer.EncodeMessage(&msgInfo, msgBuf, NULL, UINT16_MAX, 0);
NL_TEST_ASSERT(inSuite, err == encodeError);
if (err != WEAVE_NO_ERROR)
{
PacketBuffer::Free(msgBuf);
msgBuf = NULL;
continue;
}
#if DEBUG_PRINT_ENABLE
printf("Encoded message generated by EncodeMessage():\n");
DumpMemoryCStyle(msgBuf->Start(), msgBuf->DataLength(), " ", 16);
#endif
// =====================================================================================================
// Manually encode message and compare against the result generated by EncodeMessage() function.
// =====================================================================================================
p = localMsgBuf;
// Write the header field.
uint16_t headerVal = ((uint16_t) (msgInfo.Flags & 0xF0F) << 0) |
((uint16_t) (msgInfo.EncryptionType & 0xF) << 4) |
((uint16_t) (msgInfo.MessageVersion & 0xF) << 12);
LittleEndian::Write16(p, headerVal);
LittleEndian::Write32(p, msgId);
LittleEndian::Write64(p, srcNodeId);
LittleEndian::Write64(p, destNodeId);
LittleEndian::Write16(p, sessionKeyId);
// Message data to encrypt.
uint8_t aesDataIn[msgPayloadLen + HMACSHA1::kDigestLength];
memcpy(aesDataIn, msgPayload, msgPayloadLen);
// Message data to authenticate.
uint16_t sha1DataLen;
uint8_t sha1DataIn[2 * sizeof(uint64_t) + sizeof(uint16_t) + sizeof(uint32_t) + msgPayloadLen];
uint8_t *p2 = sha1DataIn;
Encoding::LittleEndian::Write64(p2, srcNodeId);
Encoding::LittleEndian::Write64(p2, destNodeId);
sha1DataLen = 2 * sizeof(uint64_t);
if (msgVersion == kWeaveMessageVersion_V2)
{
// Mask destination and source node Id flags.
headerVal &= kMsgHeaderField_MessageHMACMask;
// Encode the message header field and the message Id in a little-endian format.
Encoding::LittleEndian::Write16(p2, headerVal);
Encoding::LittleEndian::Write32(p2, msgId);
sha1DataLen += sizeof(uint16_t) + sizeof(uint32_t);
}
memcpy(p2, msgPayload, msgPayloadLen);
sha1DataLen += msgPayloadLen;
// Compute integrity check.
HMACSHA1 sha1;
sha1.Begin(sMsgEncKey_IntegrityKey, sizeof(sMsgEncKey_IntegrityKey));
sha1.AddData(sha1DataIn, sha1DataLen);
sha1.Finish(aesDataIn + msgPayloadLen);
// Encrypt the message payload and the integrity value.
AES128CTRMode aes128CTR;
aes128CTR.SetKey(sMsgEncKey_DataKey);
aes128CTR.SetWeaveMessageCounter(srcNodeId, msgId);
aes128CTR.EncryptData(aesDataIn, sizeof(aesDataIn), p);
#if DEBUG_PRINT_ENABLE
printf("Manually generated encoded message:\n");
DumpMemoryCStyle(localMsgBuf, sizeof(localMsgBuf), " ", 16);
#endif
// Compare the result.
NL_TEST_ASSERT(inSuite, memcmp(msgBuf->Start(), localMsgBuf, sizeof(localMsgBuf)) == 0);
// Compare the result against the static value saved in this test.
NL_TEST_ASSERT(inSuite, msgBuf->DataLength() == encodedMsgLen);
NL_TEST_ASSERT(inSuite, memcmp(msgBuf->Start(), encodedMsg, encodedMsgLen) == 0);
// =====================================================================================================
// Verify that DecodeMessage() generates original payload context.
// =====================================================================================================
WeaveMessageLayerTestObject msgLayerTestObject;
uint8_t *payload;
uint16_t payloadLen;
// Inject wrong KeyId.
if (decodeError == WEAVE_ERROR_WRONG_ENCRYPTION_TYPE)
{
uint8_t *injectKeyId = msgBuf->Start() + sizeof(headerVal) + sizeof(msgId) + sizeof(srcNodeId) + sizeof(destNodeId);
LittleEndian::Write16(injectKeyId, WeaveKeyId::kNone);
}
msgLayerTestObject.msgLayer = &messageLayer;
err = msgLayerTestObject.DecodeMessage(msgBuf, srcNodeId, NULL, &msgInfo, &payload, &payloadLen);
NL_TEST_ASSERT(inSuite, err == decodeError);
if (err == WEAVE_NO_ERROR)
{
#if DEBUG_PRINT_ENABLE
printf("Decoded Payload generated by DecodeMessage():\n");
DumpMemoryCStyle(payload, payloadLen, " ", 16);
#endif
// Compare the result of DecodeMessage() to the original Payload message data.
NL_TEST_ASSERT(inSuite, payloadLen == msgPayloadLen);
NL_TEST_ASSERT(inSuite, memcmp(payload, msgPayload, msgPayloadLen) == 0);
}
// Release buffer.
PacketBuffer::Free(msgBuf);
msgBuf = NULL;
}
}
int main(int argc, char *argv[])
{
static const nlTest tests[] = {
NL_TEST_DEF("WeaveMessageEncryption", WeaveMessageEncryption_Test1),
NL_TEST_SENTINEL()
};
static nlTestSuite testSuite = {
"provisioning-hash",
&tests[0]
};
#if WEAVE_SYSTEM_CONFIG_USE_LWIP
tcpip_init(NULL, NULL);
#endif // WEAVE_SYSTEM_CONFIG_USE_LWIP
nl_test_set_output_style(OUTPUT_CSV);
nlTestRunner(&testSuite, &sContext);
return nlTestRunnerStats(&testSuite);
}