Skip to content

Commit 2fef67d

Browse files
committed
- Generalize CryptoInterface.
- Add more HMAC and hash functions to CryptoInterface. - Add MeshCryptoInterface as a holder of mesh specific crypto functionality. - Rename broadcastMetadataDelimiter to metadataDelimiter in FloodingMesh since it is not just used for broadcasts, and to save some typing.
1 parent 3132325 commit 2fef67d

File tree

9 files changed

+1028
-111
lines changed

9 files changed

+1028
-111
lines changed

libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ bool useLED = false; // Change this to true if you wish the onboard LED to mark
5555
@return True if this node should forward the received message to other nodes. False otherwise.
5656
*/
5757
bool meshMessageHandler(String &message, FloodingMesh &meshInstance) {
58-
int32_t delimiterIndex = message.indexOf(meshInstance.broadcastMetadataDelimiter());
58+
int32_t delimiterIndex = message.indexOf(meshInstance.metadataDelimiter());
5959
if (delimiterIndex == 0) {
6060
Serial.print("Message received from STA MAC " + meshInstance.getEspnowMeshBackend().getSenderMac() + ": ");
6161
Serial.println(message.substring(2, 102));
@@ -172,7 +172,7 @@ void loop() {
172172
ledState = ledState ^ bool(benchmarkCount); // Make other nodes' LEDs alternate between on and off once benchmarking begins.
173173

174174
// Note: The maximum length of an unencrypted broadcast message is given by floodingMesh.maxUnencryptedMessageSize(). It is around 670 bytes by default.
175-
floodingMesh.broadcast(String(floodingMesh.broadcastMetadataDelimiter()) + String(ledState) + theOneMac + " is The One.");
175+
floodingMesh.broadcast(String(floodingMesh.metadataDelimiter()) + String(ledState) + theOneMac + " is The One.");
176176
Serial.println("Proclamation broadcast done in " + String(millis() - startTime) + " ms.");
177177

178178
timeOfLastProclamation = millis();
@@ -181,7 +181,7 @@ void loop() {
181181

182182
if (millis() - loopStart > 23000) { // Start benchmarking the mesh once three proclamations have been made
183183
uint32_t startTime = millis();
184-
floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.broadcastMetadataDelimiter()) + ": Not a spoon in sight.");
184+
floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.metadataDelimiter()) + ": Not a spoon in sight.");
185185
Serial.println("Benchmark broadcast done in " + String(millis() - startTime) + " ms.");
186186
floodingMeshDelay(20);
187187
}

libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp

Lines changed: 321 additions & 42 deletions
Large diffs are not rendered by default.

libraries/ESP8266WiFiMesh/src/CryptoInterface.h

Lines changed: 559 additions & 35 deletions
Large diffs are not rendered by default.

libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
#include "UtilityFunctions.h"
2727
#include "TypeConversionFunctions.h"
2828
#include "JsonTranslator.h"
29-
#include "CryptoInterface.h"
29+
#include "MeshCryptoInterface.h"
3030

3131
using EspnowProtocolInterpreter::espnowHashKeyLength;
3232

@@ -128,7 +128,7 @@ uint64_t EncryptedConnectionData::getOwnSessionKey() const { return _ownSessionK
128128

129129
uint64_t EncryptedConnectionData::incrementSessionKey(uint64_t sessionKey, const uint8_t *hashKey, uint8_t hashKeyLength)
130130
{
131-
String hmac = CryptoInterface::createBearsslHmac(uint64ToString(sessionKey), hashKey, hashKeyLength);
131+
String hmac = MeshCryptoInterface::createMeshHmac(uint64ToString(sessionKey), hashKey, hashKeyLength);
132132

133133
/* HMAC truncation should be OK since hmac sha256 is a PRF and we are truncating to the leftmost (MSB) bits.
134134
PRF: https://crypto.stackexchange.com/questions/26410/whats-the-gcm-sha-256-of-a-tls-protocol/26434#26434

libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
std::set<FloodingMesh *> FloodingMesh::availableFloodingMeshes = {};
3030

31-
char FloodingMesh::_broadcastMetadataDelimiter = 23;
31+
char FloodingMesh::_metadataDelimiter = 23;
3232

3333
void floodingMeshDelay(uint32_t durationMs)
3434
{
@@ -156,10 +156,10 @@ void FloodingMesh::broadcast(const String &message)
156156

157157
String messageID = generateMessageID();
158158

159-
// Remove getEspnowMeshBackend().getMeshName() from the broadcastMetadata below to broadcast to all ESP-NOW nodes regardless of MeshName.
159+
// Remove getEspnowMeshBackend().getMeshName() from the metadata below to broadcast to all ESP-NOW nodes regardless of MeshName.
160160
String targetMeshName = getEspnowMeshBackend().getMeshName();
161161

162-
broadcastKernel(targetMeshName + String(broadcastMetadataDelimiter()) + messageID + String(broadcastMetadataDelimiter()) + message);
162+
broadcastKernel(targetMeshName + String(metadataDelimiter()) + messageID + String(metadataDelimiter()) + message);
163163
}
164164

165165
void FloodingMesh::broadcastKernel(const String &message)
@@ -180,7 +180,7 @@ void FloodingMesh::encryptedBroadcast(const String &message)
180180

181181
String messageID = generateMessageID();
182182

183-
encryptedBroadcastKernel(messageID + String(broadcastMetadataDelimiter()) + message);
183+
encryptedBroadcastKernel(messageID + String(metadataDelimiter()) + message);
184184
}
185185

186186
void FloodingMesh::encryptedBroadcastKernel(const String &message)
@@ -232,15 +232,15 @@ void FloodingMesh::setMessageLogSize(uint16_t messageLogSize)
232232
}
233233
uint16_t FloodingMesh::messageLogSize() { return _messageLogSize; }
234234

235-
void FloodingMesh::setBroadcastMetadataDelimiter(char broadcastMetadataDelimiter)
235+
void FloodingMesh::setMetadataDelimiter(char metadataDelimiter)
236236
{
237237
// Using HEX number characters as a delimiter is a bad idea regardless of broadcast type, since they are always in the broadcast metadata
238-
assert(broadcastMetadataDelimiter < 48 || 57 < broadcastMetadataDelimiter);
239-
assert(broadcastMetadataDelimiter < 65 || 70 < broadcastMetadataDelimiter);
238+
assert(metadataDelimiter < 48 || 57 < metadataDelimiter);
239+
assert(metadataDelimiter < 65 || 70 < metadataDelimiter);
240240

241-
_broadcastMetadataDelimiter = broadcastMetadataDelimiter;
241+
_metadataDelimiter = metadataDelimiter;
242242
}
243-
char FloodingMesh::broadcastMetadataDelimiter() { return _broadcastMetadataDelimiter; }
243+
char FloodingMesh::metadataDelimiter() { return _metadataDelimiter; }
244244

245245
EspnowMeshBackend &FloodingMesh::getEspnowMeshBackend()
246246
{
@@ -342,12 +342,12 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa
342342
String broadcastTarget = "";
343343
String remainingRequest = "";
344344

345-
if(request.charAt(0) == broadcastMetadataDelimiter())
345+
if(request.charAt(0) == metadataDelimiter())
346346
{
347-
int32_t broadcastTargetEndIndex = request.indexOf(broadcastMetadataDelimiter(), 1);
347+
int32_t broadcastTargetEndIndex = request.indexOf(metadataDelimiter(), 1);
348348

349349
if(broadcastTargetEndIndex == -1)
350-
return ""; // broadcastMetadataDelimiter not found
350+
return ""; // metadataDelimiter not found
351351

352352
broadcastTarget = request.substring(1, broadcastTargetEndIndex + 1); // Include delimiter
353353
remainingRequest = request.substring(broadcastTargetEndIndex + 1);
@@ -357,10 +357,10 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa
357357
remainingRequest = request;
358358
}
359359

360-
int32_t messageIDEndIndex = remainingRequest.indexOf(broadcastMetadataDelimiter());
360+
int32_t messageIDEndIndex = remainingRequest.indexOf(metadataDelimiter());
361361

362362
if(messageIDEndIndex == -1)
363-
return ""; // broadcastMetadataDelimiter not found
363+
return ""; // metadataDelimiter not found
364364

365365
uint64_t messageID = stringToUint64(remainingRequest.substring(0, messageIDEndIndex));
366366

@@ -447,16 +447,16 @@ void FloodingMesh::_defaultNetworkFilter(int numberOfNetworks, MeshBackendBase &
447447
*/
448448
bool FloodingMesh::_defaultBroadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance)
449449
{
450-
// This broadcastFilter will accept a transmission if it contains the broadcastMetadataDelimiter
450+
// This broadcastFilter will accept a transmission if it contains the metadataDelimiter
451451
// and as metaData either no targetMeshName or a targetMeshName that matches the MeshName of meshInstance
452452
// and insertPreliminaryMessageID(messageID) returns true.
453453

454454
// Broadcast firstTransmission String structure: targetMeshName+messageID+message.
455455

456-
int32_t metadataEndIndex = firstTransmission.indexOf(broadcastMetadataDelimiter());
456+
int32_t metadataEndIndex = firstTransmission.indexOf(metadataDelimiter());
457457

458458
if(metadataEndIndex == -1)
459-
return false; // broadcastMetadataDelimiter not found
459+
return false; // metadataDelimiter not found
460460

461461
String targetMeshName = firstTransmission.substring(0, metadataEndIndex);
462462

@@ -466,17 +466,17 @@ bool FloodingMesh::_defaultBroadcastFilter(String &firstTransmission, EspnowMesh
466466
}
467467
else
468468
{
469-
int32_t messageIDEndIndex = firstTransmission.indexOf(broadcastMetadataDelimiter(), metadataEndIndex + 1);
469+
int32_t messageIDEndIndex = firstTransmission.indexOf(metadataDelimiter(), metadataEndIndex + 1);
470470

471471
if(messageIDEndIndex == -1)
472-
return false; // broadcastMetadataDelimiter not found
472+
return false; // metadataDelimiter not found
473473

474474
uint64_t messageID = stringToUint64(firstTransmission.substring(metadataEndIndex + 1, messageIDEndIndex));
475475

476476
if(insertPreliminaryMessageID(messageID))
477477
{
478478
// Add broadcast identifier to stored message and mark as accepted broadcast.
479-
firstTransmission = String(broadcastMetadataDelimiter()) + firstTransmission;
479+
firstTransmission = String(metadataDelimiter()) + firstTransmission;
480480
return true;
481481
}
482482
else

libraries/ESP8266WiFiMesh/src/FloodingMesh.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,11 @@ class FloodingMesh {
217217
* Set the delimiter character used for metadata by every FloodingMesh instance.
218218
* Using characters found in the mesh name or in HEX numbers is unwise, as is using ','.
219219
*
220-
* @param broadcastMetadataDelimiter The metadata delimiter character to use.
220+
* @param metadataDelimiter The metadata delimiter character to use.
221221
* Defaults to 23 = End-of-Transmission-Block (ETB) control character in ASCII
222222
*/
223-
static void setBroadcastMetadataDelimiter(char broadcastMetadataDelimiter);
224-
static char broadcastMetadataDelimiter();
223+
static void setMetadataDelimiter(char metadataDelimiter);
224+
static char metadataDelimiter();
225225

226226
/*
227227
* Gives you access to the EspnowMeshBackend used by the mesh node.
@@ -274,7 +274,7 @@ class FloodingMesh {
274274

275275
uint8_t _broadcastReceptionRedundancy = 2;
276276

277-
static char _broadcastMetadataDelimiter; // Defaults to 23 = End-of-Transmission-Block (ETB) control character in ASCII
277+
static char _metadataDelimiter; // Defaults to 23 = End-of-Transmission-Block (ETB) control character in ASCII
278278

279279
uint8_t _originMac[6] = {0};
280280

libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
#include "JsonTranslator.h"
2626
#include "EspnowProtocolInterpreter.h"
2727
#include "TypeConversionFunctions.h"
28-
#include "CryptoInterface.h"
28+
#include "MeshCryptoInterface.h"
2929

3030
namespace JsonTranslator
3131
{
@@ -69,14 +69,14 @@ namespace JsonTranslator
6969
uint8_t staMac[6] {0};
7070
uint8_t apMac[6] {0};
7171
String requesterStaApMac = macToString(WiFi.macAddress(staMac)) + macToString(WiFi.softAPmacAddress(apMac));
72-
String hmac = CryptoInterface::createBearsslHmac(requesterStaApMac + mainMessage, hashKey, hashKeyLength);
72+
String hmac = MeshCryptoInterface::createMeshHmac(requesterStaApMac + mainMessage, hashKey, hashKeyLength);
7373
return mainMessage + createJsonEndPair(jsonHmac, hmac);
7474
}
7575

7676
bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac,
7777
const uint8_t *hashKey, uint8_t hashKeyLength)
7878
{
79-
using namespace CryptoInterface;
79+
using MeshCryptoInterface::verifyMeshHmac;
8080

8181
String hmac = "";
8282
if(getHmac(encryptionRequestHmacMessage, hmac))
@@ -85,8 +85,8 @@ namespace JsonTranslator
8585
if(hmacStartIndex < 0)
8686
return false;
8787

88-
if(hmac.length() == 2*SHA256HMAC_NATURAL_LENGTH // We know that each HMAC byte should become 2 String characters due to uint8ArrayToHexString.
89-
&& verifyBearsslHmac(macToString(requesterStaMac) + macToString(requesterApMac) + encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength))
88+
if(hmac.length() == 2*CryptoInterface::SHA256_NATURAL_LENGTH // We know that each HMAC byte should become 2 String characters due to uint8ArrayToHexString.
89+
&& verifyMeshHmac(macToString(requesterStaMac) + macToString(requesterApMac) + encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength))
9090
{
9191
return true;
9292
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (C) 2019 Anders Löfgren
3+
*
4+
* License (MIT license):
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
#include "MeshCryptoInterface.h"
26+
27+
namespace MeshCryptoInterface
28+
{
29+
String createMeshHmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
30+
{
31+
return CryptoInterface::sha256Hmac(message, hashKey, hashKeyLength, hmacLength);
32+
}
33+
34+
bool verifyMeshHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength)
35+
{
36+
String generatedHmac = createMeshHmac(message, hashKey, hashKeyLength, messageHmac.length()/2); // We know that each HMAC byte should become 2 String characters due to uint8ArrayToHexString.
37+
if(generatedHmac == messageHmac)
38+
return true;
39+
else
40+
return false;
41+
}
42+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (C) 2019 Anders Löfgren
3+
*
4+
* License (MIT license):
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
#ifndef __MESHCRYPTOINTERFACE_H__
26+
#define __MESHCRYPTOINTERFACE_H__
27+
28+
#include <Arduino.h>
29+
#include "CryptoInterface.h"
30+
31+
namespace MeshCryptoInterface
32+
{
33+
/**
34+
* There is a constant-time HMAC version available. More constant-time info here: https://www.bearssl.org/constanttime.html
35+
* For small messages, it takes substantially longer time to complete than a normal HMAC (5 ms vs 2 ms in a quick benchmark,
36+
* determined by the difference between min and max allowed message length), and it also sets a maximum length that messages can be (1024 bytes by default).
37+
* Making the fixed max length variable would defeat the whole purpose of using constant-time, and not making it variable would create the wrong HMAC if message size exceeds the maximum.
38+
*
39+
* Also, HMAC is already partially constant-time. Quoting the link above:
40+
* "Hash functions implemented by BearSSL (MD5, SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512) consist in bitwise logical operations and additions on 32-bit or 64-bit words,
41+
* naturally yielding constant-time operations. HMAC is naturally as constant-time as the underlying hash function. The size of the MACed data, and the size of the key,
42+
* may leak, though; only the contents are protected."
43+
*
44+
* Thus the non constant-time version is used within the mesh framework instead.
45+
*/
46+
47+
/**
48+
* Create a SHA256 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format.
49+
*
50+
* @param message The string from which to create the HMAC.
51+
* @param hashKey The hash key to use when creating the HMAC.
52+
* @param hashKeyLength The length of the hash key in bytes.
53+
* @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to 32. Defaults to CryptoInterface::SHA256_NATURAL_LENGTH.
54+
*
55+
* @return A String with the generated HMAC in HEX format.
56+
*/
57+
String createMeshHmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength = CryptoInterface::SHA256_NATURAL_LENGTH);
58+
59+
/**
60+
* Verify a SHA256 HMAC which was created from the message using the provided hashKey.
61+
*
62+
* @param message The string from which the HMAC was created.
63+
* @param messageHmac A string with the generated HMAC in HEX format. Valid messageHmac.length() is 2 to 64.
64+
* @param hashKey The hash key to use when creating the HMAC.
65+
* @param hashKeyLength The length of the hash key in bytes.
66+
*
67+
* @return True if the HMAC is correct. False otherwise.
68+
*/
69+
bool verifyMeshHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength);
70+
}
71+
72+
#endif

0 commit comments

Comments
 (0)