Skip to content

Commit ba37749

Browse files
authored
Merge pull request #3571 from Spugna85/master
OpenWebNet: HMAC authentication (for MyHome Server1, screen 10 "..)
2 parents a652c8b + 0bdb088 commit ba37749

File tree

5 files changed

+266
-52
lines changed

5 files changed

+266
-52
lines changed

hardware/OpenWebNetTCP.cpp

Lines changed: 236 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ License: Public domain
2525

2626
#include <string.h>
2727
#include "hardwaretypes.h"
28-
#include "../main/RFXNames.h"
2928
#include "../main/RFXtrx.h"
3029

30+
#include <openssl/sha.h>
31+
3132
#define OPENWEBNET_HEARTBEAT_DELAY 1
3233
#define OPENWEBNET_STATUS_NB_HEARTBEAT 600
3334
#define OPENWEBNET_RETRY_DELAY 30
@@ -176,6 +177,30 @@ bool COpenWebNetTCP::isStatusSocketConnected()
176177
};
177178

178179

180+
/**
181+
write ...
182+
**/
183+
bool COpenWebNetTCP::ownWrite(csocket *connectionSocket, const char* pdata, size_t size)
184+
{
185+
int bytesWritten = connectionSocket->write(pdata, size);
186+
if (bytesWritten != size)
187+
{
188+
_log.Log(LOG_ERROR, "COpenWebNetTCP: partial write: %u/%u", bytesWritten, size);
189+
return (false);
190+
}
191+
return (true);
192+
}
193+
194+
/**
195+
read ...
196+
**/
197+
int COpenWebNetTCP::ownRead(csocket *connectionSocket, char* pdata, size_t size)
198+
{
199+
memset(pdata, 0, size);
200+
int read = connectionSocket->read(pdata, size, false);
201+
return (read);
202+
}
203+
179204
/**
180205
Calculate 'nonce-hash' authentication
181206
**/
@@ -289,52 +314,231 @@ uint32_t COpenWebNetTCP::ownCalcPass(const std::string &password, const std::str
289314
return (num1 & m_1);
290315
}
291316

317+
/**
318+
Perform conversion 80/128 DEC-chars to 40/64 HEX-chars
319+
**/
320+
const std::string COpenWebNetTCP::decToHexStrConvert(std::string paramString)
321+
{
322+
char retStr[256];
323+
size_t idxb, idxh;
324+
for (idxb = 0, idxh = 0; idxb < paramString.length(); idxb += 2, idxh++)
325+
{
326+
std::string str = paramString.substr(idxb, 2);
327+
sprintf(&retStr[idxh], "%x", atoi(str.c_str()));
328+
}
329+
return std::string(retStr);
330+
}
331+
332+
/**
333+
Perform conversion HEX-40/64 chars to 80/128 DEC-chars
334+
**/
335+
const std::string COpenWebNetTCP::hexToDecStrConvert(std::string paramString)
336+
{
337+
uint32_t bval;
338+
size_t idxb, idxh;
339+
char retStr[256];
340+
for (idxb = 0, idxh = 0; idxb < paramString.length(); idxb++, idxh += 2)
341+
{
342+
std::stringstream s_strid;
343+
s_strid << std::hex << paramString.substr(idxb, 1);
344+
s_strid >> bval;
345+
sprintf(&retStr[idxh], "%02u", bval & 0xf);
346+
}
347+
348+
return std::string(retStr);
349+
}
350+
351+
/**
352+
Perform conversion byte to HEX-chars
353+
**/
354+
const std::string COpenWebNetTCP::byteToHexStrConvert(uint8_t *digest, size_t digestLen, char *pArray)
355+
{
356+
size_t idxb, idxh;
357+
char arrayOfChar1[] = "0123456789abcdef";
358+
for (idxb = 0, idxh = 0; idxb < digestLen; idxb++, idxh += 2)
359+
{
360+
uint8_t bval = digest[idxb] & 0xFF;
361+
pArray[idxh] = arrayOfChar1[(bval >> 4) & 0xf];
362+
pArray[idxh + 1] = arrayOfChar1[bval & 0xF];
363+
}
364+
pArray[idxh] = 0;
365+
return(std::string(pArray));
366+
}
367+
368+
/**
369+
Perform SHA1/SHA256 and convert into HEX-chars
370+
**/
371+
const std::string COpenWebNetTCP::shaCalc(std::string paramString, int auth_type)
372+
{
373+
uint8_t *digest;
374+
uint8_t strArray[OPENWEBNET_BUFFER_SIZE];
375+
memset(strArray, 0, sizeof(strArray));
376+
memcpy(strArray, paramString.c_str(), paramString.length());
377+
378+
if (auth_type == 0)
379+
{
380+
// Perform SHA1
381+
digest = SHA1(strArray, paramString.length(), 0);
382+
char arrayOfChar2[(SHA_DIGEST_LENGTH * 2) + 1];
383+
return (byteToHexStrConvert(digest, SHA_DIGEST_LENGTH, arrayOfChar2));
384+
}
385+
else
386+
{
387+
// Perform SHA256
388+
digest = SHA256(strArray, paramString.length(), 0);
389+
char arrayOfChar2[(SHA256_DIGEST_LENGTH * 2) + 1];
390+
return (byteToHexStrConvert(digest, SHA256_DIGEST_LENGTH, arrayOfChar2));
391+
}
392+
393+
return(std::string(""));
394+
}
395+
396+
/**
397+
Perform HMAC authentication
398+
**/
399+
bool COpenWebNetTCP::hmacAuthentication(csocket *connectionSocket, int auth_type)
400+
{
401+
// Write ACK
402+
ownWrite(connectionSocket, OPENWEBNET_MSG_OPEN_OK, strlen(OPENWEBNET_MSG_OPEN_OK));
403+
404+
// Read server Response
405+
char databuffer[OPENWEBNET_BUFFER_SIZE];
406+
int read = ownRead(connectionSocket, databuffer, sizeof(databuffer));
407+
bt_openwebnet responseSrv(std::string(databuffer, read));
408+
if (responseSrv.IsPwdFrame())
409+
{
410+
struct timeval tv;
411+
gettimeofday(&tv, NULL);
412+
413+
// receive Ra
414+
const std::string strRcvSrv = responseSrv.Extract_who(); // Ra from server in DEC-chars
415+
const std::string strRa = decToHexStrConvert(strRcvSrv); // convert Ra in HEX-chars
416+
std::stringstream strRb_rand;
417+
strRb_rand << "Time" << tv.tv_sec << tv.tv_usec; // get random Rb from time..
418+
const std::string strRb = shaCalc(strRb_rand.str(), auth_type); // sha of Rb and convert into HEX-chars
419+
const std::string strA = "736F70653E"; // is the client identity (HEX-chars)
420+
const std::string strB = "636F70653E"; // is the server identity (HEX-chars)
421+
const std::string strKab = shaCalc(m_ownPassword, auth_type); // perform SHA of password
422+
const std::string strHMAC = shaCalc(strRa + strRb + strA + strB + strKab, auth_type); // HMAC
423+
424+
#if 0
425+
// print some logs for debug..
426+
_log.Log(LOG_STATUS, "COpenWebNetTCP: HMAC Ra digits received: '%s'", strRcvSrv.c_str());
427+
_log.Log(LOG_STATUS, "COpenWebNetTCP: Ra = '%s'", strRa.c_str());
428+
_log.Log(LOG_STATUS, "COpenWebNetTCP: Rb = '%s'", strRb.c_str());
429+
_log.Log(LOG_STATUS, "COpenWebNetTCP: A = '%s', B = '%s'", strA.c_str(), strB.c_str());
430+
_log.Log(LOG_STATUS, "COpenWebNetTCP: pwd = '%s', Kab = '%s'", m_ownPassword.c_str(), strKab.c_str());
431+
_log.Log(LOG_STATUS, "COpenWebNetTCP: HMAC(Ra,Rb,A,B,Kab) = '%s'", strHMAC.c_str());
432+
#endif
433+
// Write HMAC
434+
const std::string strSend = "*#" + hexToDecStrConvert(strRb) + "*" + hexToDecStrConvert(strHMAC) + "##";
435+
ownWrite(connectionSocket, strSend.c_str(), strSend.length());
436+
437+
// Server response....
438+
read = ownRead(connectionSocket, databuffer, sizeof(databuffer));
439+
bt_openwebnet responseSrv2(std::string(databuffer, read));
440+
if (responseSrv2.IsPwdFrame())
441+
{
442+
const std::string strRcvSrv2 = decToHexStrConvert(responseSrv2.Extract_who());
443+
const std::string strHMAC2 = shaCalc(strRa + strRb + strKab, auth_type);
444+
445+
if (strHMAC2.compare(strRcvSrv2) == 0)
446+
{
447+
ownWrite(connectionSocket, OPENWEBNET_MSG_OPEN_OK, strlen(OPENWEBNET_MSG_OPEN_OK)); // Write ACK
448+
return (true); // HMAC authentication OK
449+
}
450+
else
451+
{
452+
_log.Log(LOG_ERROR, "COpenWebNetTCP: HMAC(Ra,Rb,Kab) received: '%s'", strRcvSrv2.c_str());
453+
_log.Log(LOG_ERROR, "COpenWebNetTCP: not match with: '%s'", strHMAC2.c_str());
454+
}
455+
}
456+
}
457+
_log.Log(LOG_ERROR, "COpenWebNetTCP: HMAC authentication ERROR!");
458+
return false; // error!
459+
}
460+
461+
/**
462+
Perform nonce-hash authentication
463+
**/
464+
bool COpenWebNetTCP::nonceHashAuthentication(csocket *connectionSocket, std::string nonce)
465+
{
466+
std::stringstream frame;
467+
/** calculate nonce-hash **/
468+
uint32_t ownHash = ownCalcPass(m_ownPassword, nonce);
469+
/** write frame with nonce-hash **/
470+
frame << "*#";
471+
frame << ownHash;
472+
frame << "##";
473+
ownWrite(connectionSocket, frame.str().c_str(), frame.str().length());
474+
475+
char databuffer[OPENWEBNET_BUFFER_SIZE];
476+
int read = ownRead(connectionSocket, databuffer, sizeof(databuffer));
477+
bt_openwebnet responseNonce2(std::string(databuffer, read));
478+
479+
if (responseNonce2.IsOKFrame()) return true; // hash authentication OK
480+
481+
_log.Log(LOG_ERROR, "COpenWebNetTCP: hash authentication ERROR!");
482+
return false;
483+
}
484+
292485
/**
293486
Perform nonce-hash authentication
294487
**/
295488

296-
bool COpenWebNetTCP::nonceHashAuthentication(csocket *connectionSocket)
489+
bool COpenWebNetTCP::ownAuthentication(csocket *connectionSocket)
297490
{
298491
char databuffer[OPENWEBNET_BUFFER_SIZE];
299-
memset(databuffer, 0, OPENWEBNET_BUFFER_SIZE);
300-
int read = connectionSocket->read(databuffer, OPENWEBNET_BUFFER_SIZE, false);
492+
int read = ownRead(connectionSocket, databuffer, sizeof(databuffer));
301493
bt_openwebnet responseNonce(std::string(databuffer, read));
494+
495+
//_log.Log(LOG_STATUS, "COpenWebNetTCP: authentication rcv: '%s'", responseNonce.Extract_frame().c_str());
496+
302497
if (responseNonce.IsPwdFrame())
303498
{
304-
std::stringstream frame;
305-
uint32_t ownHash;
499+
if (!m_ownPassword.length())
500+
{
501+
_log.Log(LOG_ERROR, "COpenWebNetTCP: no password set for a unofficial bticino gateway");
502+
return false;
503+
}
306504

505+
// Hash authentication for unofficial gateway
506+
return(nonceHashAuthentication(connectionSocket, responseNonce.Extract_who()));
507+
}
508+
else if (responseNonce.IsNormalFrame())
509+
{
307510
if (!m_ownPassword.length())
308511
{
309-
_log.Log(LOG_STATUS, "COpenWebNetTCP: no password set for a unofficial bticino gateway");
512+
_log.Log(LOG_ERROR, "COpenWebNetTCP: bticino gateway requires the password");
310513
return false;
311514
}
312515

313-
/** calculate nonce-hash **/
314-
ownHash = ownCalcPass(m_ownPassword, responseNonce.Extract_who());
315-
/** write frame with nonce-hash **/
316-
frame << "*#";
317-
frame << ownHash;
318-
frame << "##";
516+
// TODO: only alphanumeric password....
319517

320-
int bytesWritten = connectionSocket->write(frame.str().c_str(), frame.str().length());
321-
if (bytesWritten != frame.str().length()) {
322-
_log.Log(LOG_ERROR, "COpenWebNetTCP: partial write");
518+
const std::string strFrame = responseNonce.Extract_frame();
519+
if (strFrame.compare(OPENWEBNET_AUTH_REQ_SHA1) == 0) // *98*1##
520+
{
521+
// HMAC authentication with SHA-1
522+
return (hmacAuthentication(connectionSocket, 0));
523+
}
524+
else if (strFrame.compare(OPENWEBNET_AUTH_REQ_SHA2) == 0) // *98*2##
525+
{
526+
// HMAC authentication with SHA-256
527+
return (hmacAuthentication(connectionSocket, 1));
528+
}
529+
else
530+
{
531+
_log.Log(LOG_ERROR, "COpenWebNetTCP: frame request error:'%s'", strFrame);
532+
return false;
323533
}
324-
325-
/** Open password for test **/
326-
memset(databuffer, 0, OPENWEBNET_BUFFER_SIZE);
327-
read = connectionSocket->read(databuffer, OPENWEBNET_BUFFER_SIZE, false);
328-
bt_openwebnet responseNonce2(std::string(databuffer, read));
329-
if (responseNonce2.IsOKFrame()) return true;
330-
_log.Log(LOG_ERROR, "COpenWebNetTCP: authentication ERROR!");
331-
return false;
332534
}
333535
else if (responseNonce.IsOKFrame())
334536
{
537+
// no authentication required..ok!
538+
//_log.Log(LOG_STATUS, "COpenWebNetTCP: authentication OK, no password!");
335539
return true;
336540
}
337-
_log.Log(LOG_STATUS, "COpenWebNetTCP: ERROR_FRAME? %d", responseNonce.frame_type);
541+
_log.Log(LOG_ERROR, "COpenWebNetTCP: ERROR_FRAME? %d", responseNonce.frame_type);
338542
return false;
339543
}
340544

@@ -361,22 +565,18 @@ csocket* COpenWebNetTCP::connectGwOwn(const char *connectionMode)
361565
}
362566

363567
char databuffer[OPENWEBNET_BUFFER_SIZE];
364-
memset(databuffer, 0, OPENWEBNET_BUFFER_SIZE);
365-
int read = connectionSocket->read(databuffer, OPENWEBNET_BUFFER_SIZE, false);
568+
int read = ownRead(connectionSocket, databuffer, OPENWEBNET_BUFFER_SIZE);
366569
bt_openwebnet responseSession(std::string(databuffer, read));
367570
if (!responseSession.IsOKFrame())
368571
{
369-
_log.Log(LOG_STATUS, "COpenWebNetTCP: failed to begin session, NACK received (%s:%d)-> %s", m_szIPAddress.c_str(), m_usIPPort, databuffer);
572+
_log.Log(LOG_ERROR, "COpenWebNetTCP: failed to begin session, (%s:%d)-> '%s'", m_szIPAddress.c_str(), m_usIPPort, databuffer);
370573
disconnect(); // disconnet socket if present
371574
return NULL;
372575
}
373576

374-
int bytesWritten = connectionSocket->write(connectionMode, strlen(connectionMode));
375-
if (bytesWritten != strlen(connectionMode)) {
376-
_log.Log(LOG_ERROR, "COpenWebNetTCP: partial write");
377-
}
577+
ownWrite(connectionSocket, connectionMode, strlen(connectionMode));
378578

379-
if (!nonceHashAuthentication(connectionSocket)) return NULL;
579+
if (!ownAuthentication(connectionSocket)) return NULL;
380580

381581
return connectionSocket;
382582
}
@@ -415,8 +615,7 @@ void COpenWebNetTCP::MonitorFrames()
415615
if (bIsDataReadable)
416616
{
417617
char data[OPENWEBNET_BUFFER_SIZE];
418-
memset(data, 0, OPENWEBNET_BUFFER_SIZE);
419-
int bread = m_pStatusSocket->read(data, OPENWEBNET_BUFFER_SIZE, false);
618+
int bread = ownRead(m_pStatusSocket, data, OPENWEBNET_BUFFER_SIZE);
420619

421620
if (IsStopRequested(0))
422621
break;
@@ -1375,20 +1574,13 @@ bool COpenWebNetTCP::sendCommand(bt_openwebnet& command, std::vector<bt_openwebn
13751574
_log.Log(LOG_STATUS, "COpenWebNetTCP: Command session connected to: %s:%d", m_szIPAddress.c_str(), m_usIPPort);
13761575

13771576
// Command session correctly open -> write command
1378-
int bytesWritten = commandSocket->write(command.frame_open.c_str(), command.frame_open.length());
1379-
if (bytesWritten != command.frame_open.length()) {
1380-
if (!silent) {
1381-
_log.Log(LOG_ERROR, "COpenWebNetTCP sendCommand: partial write");
1382-
}
1383-
}
1577+
ownWrite(commandSocket, command.frame_open.c_str(), command.frame_open.length());
13841578

13851579
if (waitForResponse > 0) {
13861580
sleep_seconds(waitForResponse);
13871581

13881582
char responseBuffer[OPENWEBNET_BUFFER_SIZE];
1389-
memset(responseBuffer, 0, OPENWEBNET_BUFFER_SIZE);
1390-
int read = commandSocket->read(responseBuffer, OPENWEBNET_BUFFER_SIZE, false);
1391-
1583+
int read = ownRead(commandSocket, responseBuffer, OPENWEBNET_BUFFER_SIZE);
13921584
if (!silent) {
13931585
_log.Log(LOG_STATUS, "COpenWebNetTCP: sent=%s received=%s", command.frame_open.c_str(), responseBuffer);
13941586
}

hardware/OpenWebNetTCP.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,17 @@ class COpenWebNetTCP : public CDomoticzHardwareBase
5353
void Do_Work();
5454
void MonitorFrames();
5555
uint32_t ownCalcPass(const std::string &password, const std::string &nonce);
56-
bool nonceHashAuthentication(csocket *connectionSocket);
56+
bool ownAuthentication(csocket* connectionSocket);
57+
bool nonceHashAuthentication(csocket *connectionSocket, std::string nonce);
58+
const std::string decToHexStrConvert(std::string paramString);
59+
const std::string hexToDecStrConvert(std::string paramString);
60+
const std::string byteToHexStrConvert(uint8_t* digest, size_t digestLen, char* pArray);
61+
const std::string shaCalc(std::string paramString, int auth_type);
62+
bool hmacAuthentication(csocket* connectionSocket, int auth_type);
5763
csocket* connectGwOwn(const char *connectionMode);
5864
void disconnect();
59-
bool write(const char *pdata, size_t size);
65+
bool ownWrite(csocket* connectionSocket, const char* pdata, size_t size);
66+
int ownRead(csocket* connectionSocket, char* pdata, size_t size);
6067
bool sendCommand(bt_openwebnet& command, std::vector<bt_openwebnet>& response, int waitForResponse = 0, bool silent = false);
6168
bool ParseData(char* data, int length, std::vector<bt_openwebnet>& messages);
6269
bool FindDevice(const int who, const int where, const int iInterface, int *used);

hardware/openwebnet/bt_openwebnet.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ std::string bt_openwebnet::DeleteControlCharacters(const std::string& in_frame)
678678
std::string out_frame = in_frame;
679679

680680
// delete control characters ....
681-
while ((out_frame.at(out_frame.length() - 1) == '\n') || ((out_frame.at(out_frame.length() - 1) == '\r')))
681+
while (out_frame.length() && ((out_frame.at(out_frame.length() - 1) == '\n') || ((out_frame.at(out_frame.length() - 1) == '\r'))))
682682
{
683683
out_frame.erase(out_frame.length() - 1, 1);
684684
}

hardware/openwebnet/bt_openwebnet.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#define OPENWEBNET_MSG_OPEN_KO "*#*0##"
1212
#define OPENWEBNET_COMMAND_SESSION "*99*0##"
1313
#define OPENWEBNET_EVENT_SESSION "*99*1##"
14+
#define OPENWEBNET_AUTH_REQ_SHA1 "*98*1##"
15+
#define OPENWEBNET_AUTH_REQ_SHA2 "*98*2##"
1416
#define OPENWEBNET_END_FRAME "##"
1517
#define OPENWEBNET_COMMAND_SOCKET_DURATION 30
1618

0 commit comments

Comments
 (0)