Files
OpenMQTT-Gateway/lib/ESPiLight/pilight/libs/openzwave/command_classes/Security.cpp
T
Florian 67dd7ac078 update esppilight lib
content of the library is not filtered on 433mhz only
2018-11-28 20:41:37 +01:00

1062 lines
34 KiB
C++

//-----------------------------------------------------------------------------
//
// Security.cpp
//
// Implementation of the Z-Wave COMMAND_CLASS_Security
//
// Copyright (c) 2011 Mal Lansell <openzwave@lansell.org>
//
// SOFTWARE NOTICE AND LICENSE
//
// This file is part of OpenZWave.
//
// OpenZWave is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// OpenZWave is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with OpenZWave. If not, see <http://www.gnu.org/licenses/>.
//
//-----------------------------------------------------------------------------
#include <ctime>
#include "CommandClasses.h"
#include "Security.h"
#include "../Defs.h"
#include "../Msg.h"
#include "../Node.h"
#include "../Driver.h"
#include "../platform/Log.h"
#include "../value_classes/ValueBool.h"
using namespace OpenZWave;
#define UNUSED(x) (void)(x)
/* in order to communicate with a Secure Device, we need to send the Network Key to the
* Device with a SecurityCmd_NetworkKeySet packet containing our Network Key.
* Fairly simple right?
*
* BUT
*
* The NetworkKeySet should be done during a inclusion of the device (there is technically a timeout)
* and not at any random time. This means that when including Secure Devices, we must use the OZW AddNode
* Controller Commands, and then once the Inclusion is complete, initiate a NetworkKeySet. Including a
* Secure Device by say using the button on a Z-Stick is technically not possible, as OZW needs to send
* the Network Key right away after negotiating the SecurityScheme (before the timeout).
*
*/
enum SecurityCmd
{
SecurityCmd_SupportedGet = 0x02,
SecurityCmd_SupportedReport = 0x03,
SecurityCmd_SchemeGet = 0x04,
SecurityCmd_SchemeReport = 0x05,
SecurityCmd_NetworkKeySet = 0x06,
SecurityCmd_NetworkKeyVerify = 0x07,
SecurityCmd_SchemeInherit = 0x08,
SecurityCmd_NonceGet = 0x40,
SecurityCmd_NonceReport = 0x80,
SecurityCmd_MessageEncap = 0x81,
SecurityCmd_MessageEncapNonceGet = 0xc1
};
enum SecurityScheme
{
SecurityScheme_Zero = 0x00,
};
uint8_t SecuritySchemes[1][16] = {
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
};
uint8_t EncryptPassword[16] = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA};
uint8_t AuthPassword[16] = {0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55};
void PrintHex(std::string prefix, uint8_t const *data, uint32 const length) {
char byteStr[16];
std::string str;
for( uint32 i=0; i<length; ++i )
{
if( i )
{
str += ", ";
}
snprintf( byteStr, sizeof(byteStr), "0x%.2x", data[i] );
str += byteStr;
}
Log::Write(LogLevel_Info, "%s Packet: %s", prefix.c_str(), str.c_str());
}
Security::Security
(
uint32 const _homeId,
uint8 const _nodeId
):
CommandClass( _homeId, _nodeId ),
m_queueMutex( new Mutex() ),
m_waitingForNonce(false),
m_sequenceCounter(0),
m_networkkeyset(false),
m_schemeagreed(false),
m_secured(false)
{
/* We don't want the Driver to route "Security" messages back to us for Encryption,
* so disable SecureSupport for the Security Command Class
* (This stops this Command Class getting Marked as as IsSecured() if its listed
* in the SecurityCmd_SupportedReport from the device - Which some devices do)
*/
ClearSecureSupport();
/* seed our Random Number Generator for NONCE Generation
* It doesn't matter we might seed it multiple times with each device that
* supports this class, it just adds more "randomness" to the NONCE
* although I'm sure its no way cryptographically secure :) */
srand((unsigned)time(0));
SetupNetworkKey();
}
Security::~Security
(
)
{
m_queueMutex->Release();
delete this->AuthKey;
delete this->EncryptKey;
}
//-----------------------------------------------------------------------------
// <Version::ReadXML>
// Read configuration.
//-----------------------------------------------------------------------------
void Security::ReadXML
(
TiXmlElement const* _ccElement
)
{
CommandClass::ReadXML( _ccElement );
}
//-----------------------------------------------------------------------------
// <Version::WriteXML>
// Save changed configuration
//-----------------------------------------------------------------------------
void Security::WriteXML
(
TiXmlElement* _ccElement
)
{
CommandClass::WriteXML( _ccElement );
}
void Security::SetupNetworkKey
(
)
{
//#define TESTENC 1
#if TESTENC
// uint8_t iv[16] = { 0x81, 0x42, 0xd1, 0x51, 0xf1, 0x59, 0x3d, 0x70, 0xd5, 0xe3, 0x6c, 0xcb, 0x02, 0xd0, 0x3f, 0x5c, /* */ };
uint8_t iv[16] = { 0xee, 0x2c, 0x1c, 0x0e, 0xe1, 0x54, 0xe7, 0xfa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa /* */ };
// uint8_t pck[] = { 0x25, 0x68, 0x06, 0xc5, 0xb3, 0xee, 0x2c, 0x17, 0x26, 0x7e, 0xf0, 0x84, 0xd4, 0xc3, 0xba, 0xed, 0xe5, 0xb9, 0x55};
uint8_t pck[] = { 0x65, 0xce, 0x5e, 0x75, 0xec, 0xe6, 0x09, 0x9b};
/* 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 */
uint8_t out[50];
memset(&out[0], 0, 50);
this->nk = SecuritySchemes[0];
#endif
#if 0
this->nk = GetDriver->GetNetworkKey();
#endif
if ((GetNodeUnsafe()->IsAddingNode() == true) && (m_networkkeyset == false)) {
Log::Write(LogLevel_Info, GetNodeId(), " Using Scheme0 Network Key for Key Exchange (AddingNode: %s KeySet: %s)", GetNodeUnsafe()->IsAddingNode() ? "true" : "false", m_networkkeyset ? "true" : "false" );
this->nk = SecuritySchemes[0];
} else {
Log::Write(LogLevel_Info, GetNodeId(), " Using Configured Network Key (AddingNode: %s KeySet: %s)", GetNodeUnsafe()->IsAddingNode() ? "true" : "false", m_networkkeyset ? "true" : "false" );
this->nk = GetDriver()->GetNetworkKey();
}
#ifdef DEBUG
PrintHex("Network Key", this->nk, 16);
#endif
this->AuthKey = new aes_encrypt_ctx;
this->EncryptKey = new aes_encrypt_ctx;
if (aes_init1() == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed to Init AES Engine");
return;
}
if (aes_encrypt_key128(this->nk, this->EncryptKey) == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed to Set Initial Network Key for Encryption");
return;
}
if (aes_encrypt_key128(this->nk, this->AuthKey) == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed to Set Initial Network Key for Authentication");
return;
}
uint8 tmpEncKey[32];
uint8 tmpAuthKey[32];
aes_mode_reset(this->EncryptKey);
aes_mode_reset(this->AuthKey);
if (aes_ecb_encrypt(EncryptPassword, tmpEncKey, 16, this->EncryptKey) == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed to Generate Encrypted Network Key for Encryption");
return;
}
if (aes_ecb_encrypt(AuthPassword, tmpAuthKey, 16, this->AuthKey) == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed to Generate Encrypted Network Key for Authentication");
return;
}
aes_mode_reset(this->EncryptKey);
aes_mode_reset(this->AuthKey);
if (aes_encrypt_key128(tmpEncKey, this->EncryptKey) == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed to set Encrypted Network Key for Encryption");
return;
}
if (aes_encrypt_key128(tmpAuthKey, this->AuthKey) == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed to set Encrypted Network Key for Authentication");
return;
}
aes_mode_reset(this->EncryptKey);
aes_mode_reset(this->AuthKey);
#if TESTENC
PrintHex("Key", this->nk, 16);
PrintHex("Packet Encryption Key", tmpEncKey, 16);
PrintHex("IV", iv, 16);
PrintHex("input", pck, 19);
if (aes_ofb_decrypt(pck, out, 19, iv, this->EncryptKey) == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed to Decrypt Packet");
return;
}
PrintHex("Pck", out, 19);
//exit(-1);
#endif
//uint8 tmpiv[8] = { 0x16, 0xc2, 0x96, 0x60, 0x46, 0x33, 0x69, 0x04 };
/* expected output = 0x06, 0xcc, 0xab, 0x1a, 0x86, 0x91, 0x39, 0xfd
* expected Auth = 0x64, 0x42, 0x26, 0x59, 0xf3, 0x04, 0x72, 0x16
* plaintext packet = 0x62, 0x03, 0x00, 0x10, 0x02, 0xfe, 0xfe
*/
//this->EncryptMessage(tmpiv);
}
bool Security::Init
(
)
{
/* if we are adding this node, then instead to a SchemeGet Command instead - This
* will start the Network Key Exchange
*/
if (GetNodeUnsafe()->IsAddingNode()) {
Msg * msg = new Msg ("SecurityCmd_SchemeGet", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true, true, FUNC_ID_APPLICATION_COMMAND_HANDLER, GetCommandClassId() );
msg->Append( GetNodeId() );
msg->Append( 3 );
msg->Append( GetCommandClassId() );
msg->Append( SecurityCmd_SchemeGet );
msg->Append( 0 );
msg->Append( GetDriver()->GetTransmitOptions() );
/* SchemeGet is unencrypted */
GetDriver()->SendMsg(msg, Driver::MsgQueue_Security);
} else {
Msg* msg = new Msg( "SecurityCmd_SupportedGet", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true, true, FUNC_ID_APPLICATION_COMMAND_HANDLER, GetCommandClassId() );
msg->Append( GetNodeId() );
msg->Append( 2 );
msg->Append( GetCommandClassId() );
msg->Append( SecurityCmd_SupportedGet );
msg->Append( GetDriver()->GetTransmitOptions() );
this->SendMsg( msg);
}
return true;
}
//-----------------------------------------------------------------------------
// <Security::RequestState>
// Request current state from the device
//-----------------------------------------------------------------------------
bool Security::RequestState
(
uint32 const _requestFlags,
uint8 const _instance,
Driver::MsgQueue const _queue
)
{
#if 0
if( _requestFlags & RequestFlag_Static )
{
}
return false;
#endif
return true;
}
//-----------------------------------------------------------------------------
// <Security::RequestValue>
// Request current state from the device
//-----------------------------------------------------------------------------
bool Security::RequestValue
(
uint32 const _requestFlags,
uint8 const _index,
uint8 const _instance,
Driver::MsgQueue const _queue
)
{
Log::Write(LogLevel_Info, GetNodeId(), "Got a RequestValue Call");
return true;
}
bool Security::HandleSupportedReport
(
uint8 const* _data,
uint32 const _length
)
{
#ifdef DEBUG
PrintHex("Security Classes", _data, _length);
#endif
GetNodeUnsafe()->SetSecuredClasses(_data, _length);
/* advance the Query Stage */
GetNodeUnsafe()->QueryStageComplete(Node::QueryStage_SecurityReport);
GetNodeUnsafe()->AdvanceQueries();
return true;
}
//-----------------------------------------------------------------------------
// <Security::HandleMsg>
// Handle a message from the Z-Wave network
//-----------------------------------------------------------------------------
bool Security::HandleMsg
(
uint8 const* _data,
uint32 const _length,
uint32 const _instance // = 1
)
{
switch( (SecurityCmd)_data[0] )
{
case SecurityCmd_SupportedReport:
{
/* this is a list of CommandClasses that should be Encrypted.
* and it might contain new command classes that were not present in the NodeInfoFrame
* so we have to run through, mark existing Command Classes as SetSecured (so SendMsg in the Driver
* class will route the unecrypted messages to our SendMsg) and for New Command
* Classes, create them, and of course, also do a SetSecured on them.
*
* This means we must do a SecurityCmd_SupportedGet request ASAP so we dont have
* Command Classes created after the Discovery Phase is completed!
*/
Log::Write(LogLevel_Info, GetNodeId(), "Received SecurityCmd_SupportedReport from node %d", GetNodeId() );
HandleSupportedReport(&_data[2], _length-2);
break;
}
case SecurityCmd_SchemeReport:
{
Log::Write(LogLevel_Info, GetNodeId(), "Received SecurityCmd_SchemeReport from node %d: %d", GetNodeId(), _data[1]);
uint8 schemes = _data[1];
if (m_schemeagreed == true) {
Log::Write(LogLevel_Warning, GetNodeId(), " Already Received a SecurityCmd_SchemeReport from the node. Ignoring");
break;
}
if( schemes == SecurityScheme_Zero )
{
/* We're good to go. We now should send our NetworkKey to the device if this is the first
* time we have seen it
*/
Log::Write(LogLevel_Info, GetNodeId(), " Security scheme agreed." );
/* create the NetworkKey Packet. EncryptMessage will encrypt it for us (And request the NONCE) */
Msg * msg = new Msg ("SecurityCmd_NetworkKeySet", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true, true, FUNC_ID_APPLICATION_COMMAND_HANDLER, GetCommandClassId() );
msg->Append( GetNodeId() );
msg->Append( 18 );
msg->Append( GetCommandClassId() );
msg->Append( SecurityCmd_NetworkKeySet );
for (int i = 0; i < 16; i++)
msg->Append(GetDriver()->GetNetworkKey()[i]);
msg->Append( GetDriver()->GetTransmitOptions() );
this->SendMsg( msg);
m_schemeagreed = true;
}
else
{
/* No common security scheme. The device should continue as an unsecured node.
* but Some Command Classes might not be present...
*/
Log::Write(LogLevel_Warning, GetNodeId(), " No common security scheme. The device will continue as an unsecured node." );
}
break;
}
case SecurityCmd_NetworkKeySet:
{
/* we shouldn't get a NetworkKeySet from a node if we are the controller
* as we send it out to the Devices
*/
Log::Write(LogLevel_Info, GetNodeId(), "Received SecurityCmd_NetworkKeySet from node %d", GetNodeId() );
break;
}
case SecurityCmd_NetworkKeyVerify:
{
/* if we can decrypt this packet, then we are assured that our NetworkKeySet is successfull
* and thus should set the Flag referenced in SecurityCmd_SchemeReport
*/
Log::Write(LogLevel_Info, GetNodeId(), "Received SecurityCmd_NetworkKeyVerify from node %d", GetNodeId() );
/* now as for our SupportedGet */
Msg* msg = new Msg( "SecurityCmd_SupportedGet", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true, true, FUNC_ID_APPLICATION_COMMAND_HANDLER, GetCommandClassId() );
msg->Append( GetNodeId() );
msg->Append( 2 );
msg->Append( GetCommandClassId() );
msg->Append( SecurityCmd_SupportedGet );
msg->Append( GetDriver()->GetTransmitOptions() );
this->SendMsg( msg);
break;
}
case SecurityCmd_SchemeInherit:
{
/* only used in a Controller Replication Type enviroment.
*
*/
Log::Write(LogLevel_Info, GetNodeId(), "Received SecurityCmd_SchemeInherit from node %d", GetNodeId() );
break;
}
case SecurityCmd_NonceGet:
{
/* the Device wants to send us a Encrypted Packet, and thus requesting for our latest NONCE
*
*/
Log::Write(LogLevel_Info, GetNodeId(), "Received SecurityCmd_NonceGet from node %d", GetNodeId() );
SendNonceReport();
break;
}
case SecurityCmd_NonceReport:
{
/* we recieved a NONCE from a device, so assume that there is something in a queue to send
* out
*/
Log::Write(LogLevel_Info, GetNodeId(), "Received SecurityCmd_NonceReport from node %d", GetNodeId() );
EncryptMessage( &_data[1] );
m_waitingForNonce = false;
break;
}
case SecurityCmd_MessageEncap:
{
/* We recieved a Encrypted single packet from the Device. Decrypt it.
*
*/
Log::Write(LogLevel_Info, GetNodeId(), "Received SecurityCmd_MessageEncap from node %d", GetNodeId() );
DecryptMessage( _data, _length );
break;
}
case SecurityCmd_MessageEncapNonceGet:
{
/* we recieved a encrypted packet from the device, and the device is also asking us to send a
* new NONCE to it, hence there must be multiple packets.
*/
Log::Write(LogLevel_Info, GetNodeId(), "Received SecurityCmd_MessageEncapNonceGet from node %d", GetNodeId() );
DecryptMessage( _data, _length );
/* Regardless of the success/failure of Decrypting, send a new NONCE */
SendNonceReport();
break;
}
default:
{
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------
// <Security::SendMsg>
// Queue a message to be securely sent by the Z-Wave PC Interface
//-----------------------------------------------------------------------------
void Security::SendMsg
(
Msg* _msg
)
{
_msg->Finalize();
uint8* buffer = _msg->GetBuffer();
if( _msg->GetLength() < 7 )
{
// Message too short
assert(0);
return;
}
if( buffer[3] != FUNC_ID_ZW_SEND_DATA )
{
// Invalid message type
assert(0);
return;
}
uint8 length = buffer[5];
if( length > 28 )
{
// Message must be split into two parts
SecurityPayload *payload1 = new SecurityPayload();
payload1->m_length = 28;
payload1->m_part = 1;
memcpy( payload1->m_data, &buffer[6], payload1->m_length );
payload1->logmsg = _msg->GetLogText();
QueuePayload( payload1 );
SecurityPayload *payload2 = new SecurityPayload();
payload2->m_length = length-28;
payload2->m_part = 2;
memcpy( payload2->m_data, &buffer[34], payload2->m_length );
payload2->logmsg = _msg->GetLogText();
QueuePayload( payload2 );
}
else
{
// The entire message can be encapsulated as one
SecurityPayload *payload = new SecurityPayload();
payload->m_length = length;
payload->m_part = 0; // Zero means not split into separate messages
memcpy( payload->m_data, &buffer[6], payload->m_length );
payload->logmsg = _msg->GetLogText();
QueuePayload( payload );
}
delete _msg;
}
//-----------------------------------------------------------------------------
// <Security::QueuePayload>
// Queue data to be encapsulated by the Security Command Class, on
// receipt of a nonce value from the remote node.
//-----------------------------------------------------------------------------
void Security::QueuePayload
(
SecurityPayload *_payload
)
{
m_queueMutex->Lock();
m_queue.push_back( _payload );
if( !m_waitingForNonce )
{
// Request a nonce from the node. Its arrival
// will trigger the sending of the first payload
RequestNonce();
}
m_queueMutex->Unlock();
}
//-----------------------------------------------------------------------------
// <Security::EncryptMessage>
// Encrypt and send a Z-Wave message securely.
//-----------------------------------------------------------------------------
bool Security::EncryptMessage
(
uint8 const* _nonce
)
{
#if 1
if( m_nonceTimer.GetMilliseconds() > 10000 )
{
// The nonce was not received within 10 seconds
// of us sending the nonce request. Send it again
RequestNonce();
return false;
}
// Fetch the next payload from the queue and encapsulate it
m_queueMutex->Lock();
if( m_queue.empty() )
{
// Nothing to do
m_queueMutex->Release();
return false;
}
SecurityPayload * payload = m_queue.front();
m_queue.pop_front();
//uint32 queueSize = m_queue.size();
m_queueMutex->Unlock();
#else
uint32 queueSize = m_queue.size();
struct SecurityPayload payload;
payload.m_length = 7;
payload.m_part = 0;
uint8 tmpdata[7] = {0x62, 0x03, 0x00, 0x10, 0x02, 0xfe, 0xfe};
for (int i = 0; i < payload.m_length; i++)
payload.m_data[i] = tmpdata[i];
#endif
// Encapsulate the message fragment
/* MessageEncapNonceGet doesn't seem to work */
//Msg* msg = new Msg( (queueSize>1) ? "SecurityCmd_MessageEncapNonceGet" : "SecurityCmd_MessageEncap", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true );
string LogMessage("SecurityCmd_MessageEncap (");
LogMessage.append(payload->logmsg);
LogMessage.append(")");
Msg* msg = new Msg( LogMessage, GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true, true, FUNC_ID_APPLICATION_COMMAND_HANDLER, GetCommandClassId() );
msg->Append( GetNodeId() );
msg->Append( payload->m_length + 20 );
msg->Append( GetCommandClassId() );
//msg->Append( (queueSize>1) ? SecurityCmd_MessageEncapNonceGet : SecurityCmd_MessageEncap );
msg->Append( SecurityCmd_MessageEncap );
/* create the iv
*
*/
uint8 initializationVector[16];
/* the first 8 bytes of a outgoing IV are random */
for (int i = 0; i < 8; i++) {
//initializationVector[i] = (rand()%0xFF)+1;
initializationVector[i] = 0xAA;
}
/* the remaining 8 bytes are the NONCE we got from the device */
for (int i = 0; i < 8; i++) {
initializationVector[8+i] = _nonce[i];
}
/* Append the first 8 bytes of the initialization vector
* to the message. The remaining 8 bytes are the NONCE we recieved from
* the node, and is ommitted from sending back to the Node. But we use the full 16 bytes to
* as the IV to encrypt out message.
*/
for(int i=0; i<8; ++i )
{
msg->Append( initializationVector[i] );
}
// Append the sequence data
uint8 sequence = 0;
if( payload->m_part == 0 )
{
sequence = 0;
}
else if( payload->m_part == 1 )
{
sequence = (++m_sequenceCounter) & 0x0f;
sequence |= 0x10; // Sequenced, first frame
}
if( payload->m_part == 2 )
{
sequence = m_sequenceCounter & 0x0f;
sequence |= 0x30; // Sequenced, second frame
}
/* at most, the payload will be 28 bytes + 1 byte for the Sequence. */
uint8 plaintextmsg[32];
plaintextmsg[0] = sequence;
for (int i = 0; i < payload->m_length; i++)
plaintextmsg[i+1] = payload->m_data[i];
/* Append the message payload after encrypting it with AES-OFB (key is EncryptPassword,
* full IV (16 bytes - 8 Random and 8 NONCE) and payload.m_data
*/
PrintHex("Input Packet:", plaintextmsg, payload->m_length+1);
#ifdef DEBUG
PrintHex("IV:", initializationVector, 16);
#endif
uint8 encryptedpayload[30];
aes_mode_reset(this->EncryptKey);
if (aes_ofb_encrypt(plaintextmsg, encryptedpayload, payload->m_length+1, initializationVector, this->EncryptKey) == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed to Encrypt Packet");
delete msg;
return false;
}
#ifdef DEBUG
PrintHex("Encrypted Output", encryptedpayload, payload->m_length+1);
/* The Following Code attempts to Decrypt the Packet and Verify */
/* the first 8 bytes of a outgoing IV are random */
for (int i = 0; i < 8; i++) {
//initializationVector[i] = (rand()%0xFF)+1;
initializationVector[i] = 0xAA;
}
/* the remaining 8 bytes are the NONCE we got from the device */
for (int i = 0; i < 8; i++) {
initializationVector[8+i] = _nonce[i];
}
aes_mode_reset(this->EncryptKey);
uint8 tmpoutput[16];
if (aes_ofb_encrypt(encryptedpayload, tmpoutput, payload->m_length+1, initializationVector, this->EncryptKey) == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed to Encrypt Packet");
delete msg;
return false;
}
PrintHex("Decrypted output", tmpoutput, payload->m_length+1);
#endif
for(int i=0; i<payload->m_length+1; ++i )
{
msg->Append( encryptedpayload[i] );
}
// Append the nonce identifier :)
msg->Append(_nonce[0]);
/* Append space for the authentication data Set with AES-CBCMAC (key is AuthPassword,
* Full IV (16 bytes - 8 random and 8 NONCE) and sequence|SrcNode|DstNode|payload.m_length|payload.m_data
*
*/
/* Regenerate IV */
/* the first 8 bytes of a outgoing IV are random */
for (int i = 0; i < 8; i++) {
//initializationVector[i] = (rand()%0xFF)+1;
initializationVector[i] = 0xAA;
}
/* the remaining 8 bytes are the NONCE we got from the device */
for (int i = 0; i < 8; i++) {
initializationVector[8+i] = _nonce[i];
}
uint8 mac[8];
this->GenerateAuthentication(&msg->GetBuffer()[7], msg->GetLength()+2, GetDriver()->GetNodeId(), GetNodeId(), initializationVector, mac);
for(int i=0; i<8; ++i )
{
msg->Append( mac[i] );
}
#ifdef DEBUG
PrintHex("Auth", mac, 8);
#endif
msg->Append( GetDriver()->GetTransmitOptions() );
#ifdef DEBUG
PrintHex("Outgoing", msg->GetBuffer(), msg->GetLength());
#endif
GetDriver()->SendMsg(msg, Driver::MsgQueue_Security);
/* finally, if the message we just sent is a NetworkKeySet, then we need to reset our Network Key here
* as the reply we will get back will be encrypted with the new Network key
*/
if ((this->m_networkkeyset == false) && (payload->m_data[0] == 0x98) && (payload->m_data[1] == 0x06)) {
Log::Write(LogLevel_Info, GetNodeId(), "Reseting Network Key after Inclusion");
this->m_networkkeyset = true;
SetupNetworkKey();
}
delete payload;
return true;
}
bool Security::createIVFromPacket_inbound(uint8 const* _data, uint8 *iv) {
for (int i = 0; i < 8; i++) {
iv[i] = _data[1+i];
}
for (int i = 0; i < 8; i++) {
iv[8+i] = this->currentNonce[i];
}
return true;
}
bool Security::createIVFromPacket_outbound(uint8 const* _data, uint8 *iv) {
for (int i = 0; i < 8; i++) {
iv[i] = this->currentNonce[i];
}
for (int i = 0; i < 8; i++) {
iv[8+i] = _data[1+i];
}
return true;
}
//-----------------------------------------------------------------------------
// <Security::DecryptMessage>
// Decrypt a security-encapsulated message from the Z-Wave network
//-----------------------------------------------------------------------------
bool Security::DecryptMessage
(
uint8 const* _data,
uint32 const _length
)
{
if( m_nonceTimer.GetMilliseconds() > 10000 )
{
// The message was not received within 10 seconds
// of us sending the nonce report.
// TBD - clear any partial message that has been stored.
return false;
}
uint8 iv[17];
createIVFromPacket_inbound(_data, iv); /* first 8 bytes of Packet are the Random Value generated by the Device
* 2nd 8 bytes of the IV are our nonce we sent previously
*/
uint8 decryptpacket[32];
memset(&decryptpacket[0], 0, 32);
uint32 encryptedpacketsize = _length-11-8;
uint8 encyptedpacket[32];
for (uint32 i = 0; i < 32; i++) {
if (i >= encryptedpacketsize) {
/* pad the remaining fields */
encyptedpacket[i] = 0;
} else {
encyptedpacket[i] = _data[9+i];
}
}
#ifdef DEBUG
Log::Write(LogLevel_Debug, GetNodeId(), "Encrypted Packet Sizes: %d (Total) %d (Payload)", _length, encryptedpacketsize);
PrintHex("IV", iv, 16);
PrintHex("Encrypted", encyptedpacket, 16);
/* 8 - IV - 2 - Command Header */
PrintHex("Auth", &_data[8+encryptedpacketsize+2], 8);
#endif
aes_mode_reset(this->EncryptKey);
#if 0
uint8_t iv[16] = { 0x81, 0x42, 0xd1, 0x51, 0xf1, 0x59, 0x3d, 0x70, 0xd5, 0xe3, 0x6c, 0xcb, 0x02, 0xd0, 0x3f, 0x5c, /* */ };
uint8_t pck[] = { 0x25, 0x68, 0x06, 0xc5, 0xb3, 0xee, 0x2c, 0x17, 0x26, 0x7e, 0xf0, 0x84, 0xd4, 0xc3, 0xba, 0xed, 0xe5, 0xb9, 0x55};
if (aes_ofb_decrypt(pck, decryptpacket, 19, iv, this->EncryptKey) == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed to Decrypt Packet");
return false;
}
PrintHex("Pck", decryptpacket, 19);
#else
if (aes_ofb_decrypt(encyptedpacket, decryptpacket, encryptedpacketsize, iv, this->EncryptKey) == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed to Decrypt Packet");
if (m_queue.size() > 1)
RequestNonce();
return false;
}
PrintHex("Decrypted", decryptpacket, encryptedpacketsize);
#endif
uint8 mac[32];
/* we have to regenerate the IV as the ofb decryption routine will alter it. */
createIVFromPacket_inbound(_data, iv);
this->GenerateAuthentication(_data, _length, GetNodeId(), GetDriver()->GetNodeId(), iv, mac);
if (memcmp(&_data[8+encryptedpacketsize+2], mac, 8) != 0) {
Log::Write(LogLevel_Warning, GetNodeId(), "MAC Authentication of Packet Failed. Dropping");
if (m_queue.size() > 1)
RequestNonce();
return false;
}
/* XXX TODO: Check the Sequence Header Frame to see if this is the first part of a
* message, or 2nd part, or a entire message.
*
* I havn't actually seen a Z-Wave Message thats too big to fit in a encrypted message
* yet, so we will look at this if such a message actually exists!
*/
/* if the command class is us, send it back to our HandleMessage */
if (decryptpacket[1] == this->StaticGetCommandClassId()) {
/* drop the sequence header, and the command class when we pass through to our handler */
this->HandleMsg(&decryptpacket[2], encryptedpacketsize-2);
} else {
/* send to the Command Class for processing.... */
if( Node* node = GetNodeUnsafe() )
{
uint8 commandClassId = decryptpacket[1];
if( CommandClass* pCommandClass = node->GetCommandClass( commandClassId ) )
{
Log::Write( LogLevel_Info, GetNodeId(), "Received a SecurityCmd_MessageEncap from node %d for Command Class %s", GetNodeId(), pCommandClass->GetCommandClassName().c_str() );
pCommandClass->ReceivedCntIncr();
pCommandClass->HandleMsg( &decryptpacket[2], encryptedpacketsize-2);
} else {
Log::Write( LogLevel_Info, GetNodeId(), "ApplicationCommandHandler - Unhandled Command Class 0x%.2x", decryptpacket[1] );
}
}
}
if (m_queue.size() > 0)
RequestNonce();
if (m_secured == false) {
if( ValueBool* value = static_cast<ValueBool*>( GetValue( 1, 0 ) ) )
{
value->OnValueRefreshed( true );
value->Release();
}
m_secured = true;
}
return true;
}
//-----------------------------------------------------------------------------
// <Security::GenerateAuthentication>
// Generate authentication data from a security-encrypted message
//-----------------------------------------------------------------------------
bool Security::GenerateAuthentication
(
uint8 const* _data, // Starting from the command class command
uint32 const _length,
uint8 const _sendingNode,
uint8 const _receivingNode,
uint8 *iv,
uint8* _authentication // 8-byte buffer that will be filled with the authentication data
)
{
// Build a buffer containing a 4-byte header and the encrypted
// message data, padded with zeros to a 16-byte boundary.
uint8 buffer[256];
uint8 tmpauth[16];
memset(buffer, 0, 256);
memset(tmpauth, 0, 16);
buffer[0] = _data[0]; // Security command class command
buffer[1] = _sendingNode;
buffer[2] = _receivingNode;
buffer[3] = _length - 19; // Subtract 19 to account for the 9 security command class bytes that come before and after the encrypted data
memcpy( &buffer[4], &_data[9], _length-19 ); // Encrypted message
uint8 bufsize = _length - 19 + 4; /* the size of buffer */
#ifdef DEBUG
PrintHex("Raw Auth (minus IV)", buffer, bufsize);
Log::Write(LogLevel_Debug, GetNodeId(), "Raw Auth (Minus IV) Size: %d (%d)", bufsize, bufsize+16);
#endif
aes_mode_reset(this->AuthKey);
/* encrypt the IV with ecb */
if (aes_ecb_encrypt(iv, tmpauth, 16, this->AuthKey) == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed Initial ECB Encrypt of Auth Packet");
return false;
}
/* our temporary holding var */
uint8 encpck[16];
int block = 0;
/* reset our encpck temp var */
memset(encpck, 0, 16);
/* now xor the buffer with our encrypted IV */
for (int i = 0; i < bufsize; i++) {
encpck[block] = buffer[i];
block++;
/* if we hit a blocksize, then encrypt */
if (block == 16) {
for (int j = 0; j < 16; j++) {
/* here we do our xor */
tmpauth[j] = encpck[j] ^ tmpauth[j];
/* and reset encpck for good measure */
encpck[j] = 0;
}
/* reset our block counter back to 0 */
block = 0;
aes_mode_reset(this->AuthKey);
if (aes_ecb_encrypt(tmpauth, tmpauth, 16, this->AuthKey) == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed Subsequent (%d) ECB Encrypt of Auth Packet", i);
return false;
}
}
}
/* any left over data that isn't a full block size*/
if (block > 0) {
for (int i= 0; i < 16; i++) {
/* encpck from block to 16 is already gauranteed to be 0
* so its safe to xor it with out tmpmac */
tmpauth[i] = encpck[i] ^ tmpauth[i];
}
aes_mode_reset(this->AuthKey);
if (aes_ecb_encrypt(tmpauth, tmpauth, 16, this->AuthKey) == EXIT_FAILURE) {
Log::Write(LogLevel_Warning, GetNodeId(), "Failed Final ECB Encrypt of Auth Packet");
return false;
}
}
/* we only care about the first 8 bytes of tmpauth as the mac */
#ifdef DEBUG
PrintHex("Computed Auth", tmpauth, 8);
#endif
/* so only copy 8 bytes to the _authentication var */
memcpy(_authentication, tmpauth, 8);
return true;
}
//-----------------------------------------------------------------------------
// <Security::RequestNonce>
// Request a nonce from the node
//-----------------------------------------------------------------------------
void Security::RequestNonce
(
)
{
if (m_waitingForNonce == true)
return;
m_waitingForNonce = true;
Msg* msg = new Msg( "SecurityCmd_NonceGet", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true, true, FUNC_ID_APPLICATION_COMMAND_HANDLER, GetCommandClassId() );
msg->Append( GetNodeId() );
msg->Append( 2 );
msg->Append( GetCommandClassId() );
msg->Append( SecurityCmd_NonceGet );
msg->Append( TRANSMIT_OPTION_ACK | TRANSMIT_OPTION_AUTO_ROUTE );
GetDriver()->SendMsg( msg, Driver::MsgQueue_Security);
// Reset the nonce timer. The nonce report
// must be received within 10 seconds.
m_nonceTimer.Reset();
}
//-----------------------------------------------------------------------------
// <Security::SendNonceReport>
// Send a nonce to the node
//-----------------------------------------------------------------------------
void Security::SendNonceReport
(
)
{
//uint8 publicNonce[8];
/* this should be pretty random */
for (int i = 0; i < 8; i++) {
//this->currentNonce[i] = (rand()%0xFF)+1;
this->currentNonce[i] = 0xAA;
}
Msg* msg = new Msg( "SecurityCmd_NonceReport", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true, true, FUNC_ID_APPLICATION_COMMAND_HANDLER, GetCommandClassId() );
msg->Append( GetNodeId() );
msg->Append( 10 );
msg->Append( GetCommandClassId() );
msg->Append( SecurityCmd_NonceReport );
for( int i=0; i<8; ++i )
{
msg->Append( this->currentNonce[i] );
}
msg->Append( TRANSMIT_OPTION_ACK | TRANSMIT_OPTION_AUTO_ROUTE );
GetDriver()->SendMsg( msg, Driver::MsgQueue_Security);
// Reset the nonce timer. The encapsulated message
// must be received within 10 seconds.
m_nonceTimer.Reset();
}
//-----------------------------------------------------------------------------
// <Battery::CreateVars>
// Create the values managed by this command class
//-----------------------------------------------------------------------------
void Security::CreateVars
(
uint8 const _instance
)
{
if( Node* node = GetNodeUnsafe() )
{
node->CreateValueBool( ValueID::ValueGenre_System, GetCommandClassId(), _instance, 0, "Secured", "", true, false, false, 0 );
}
}