diff --git a/.clang_complete b/.clang_complete
new file mode 100644
index 0000000..392d571
--- /dev/null
+++ b/.clang_complete
@@ -0,0 +1,8 @@
+-IC:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino
+-IC:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include
+-Idrivers\Linux
+-Idrivers\ATSHA204
+-Icore
+-DMY_SIGNING_ATSHA204
+-DMY_SIGNING_SOFT
+-DARDUINO_ARCH_AVR
\ No newline at end of file
diff --git a/.mystools/cppcheck/config/includes.cfg b/.mystools/cppcheck/config/includes.cfg
index c723073..76f432b 100644
--- a/.mystools/cppcheck/config/includes.cfg
+++ b/.mystools/cppcheck/config/includes.cfg
@@ -2,3 +2,4 @@
drivers/Linux
drivers/AES
drivers/BCM
+core
diff --git a/MyConfig.h b/MyConfig.h
index 4960801..95ad82b 100644
--- a/MyConfig.h
+++ b/MyConfig.h
@@ -400,17 +400,44 @@
* Message Signing Settings
***********************************/
/**
- * @def MY_DEBUG_VERBOSE_SIGNING
- * @brief Flag for verbose debug prints related to signing. Requires DEBUG to be enabled.
- * This will add even more to the size of the final sketch!
- */
+* @def MY_DEBUG_VERBOSE_SIGNING
+* @brief Flag for verbose debug prints related to signing. Requires DEBUG to be enabled.
+ * This will add even more to the size of the final sketch!
+*/
//#define MY_DEBUG_VERBOSE_SIGNING
+/**
+ * @def MY_SIGNING_SIMPLE_PASSWD
+ * @brief Enables SW backed signing functionality in library and uses provided password as key.
+ *
+ * This flag will enable signing, signature requests and encryption. It has to be identical on ALL
+ * nodes in the network.
+ * Whitelisting is supported and serial will be the first 8 characters of the password, the ninth
+ * character will be the node ID (to make each node have a unique serial).
+ * As with the regular signing modes, whitelisting is only activated if a whitelist is specified in
+ * the sketch.
+ * No personalization is required for this mode.
+ * It is allowed to set @ref MY_SIGNING_WEAK_SECURITY for deployment purposes in this mode as it is
+ * with the regular software and ATSHA204A based modes.
+ * If the provided password is shorter than the size of the HMAC or AES key, it will be null-padded
+ * to accomodate the key size in question. A 32 character password is the maximum length. Any
+ * password longer than that will be truncated.
+ */
+//#define MY_SIGNING_SIMPLE_PASSWD "MyInsecurePassword"
+#if defined(MY_SIGNING_SIMPLE_PASSWD)
+#define MY_SIGNING_SOFT
+#define MY_SIGNING_REQUEST_SIGNATURES
+#define MY_RF24_ENABLE_ENCRYPTION
+#define MY_RFM69_ENABLE_ENCRYPTION
+#define MY_NRF5_ESB_ENABLE_ENCRYPTION
+#endif
+
/**
* @def MY_SIGNING_ATSHA204
* @brief Enables HW backed signing functionality in library.
*
- * For any signing related functionality to be included, this define or @ref MY_SIGNING_SOFT has to be enabled.
+ * For any signing related functionality to be included, this define or @ref MY_SIGNING_SOFT has to
+ * be enabled.
*/
//#define MY_SIGNING_ATSHA204
@@ -418,7 +445,8 @@
* @def MY_SIGNING_SOFT
* @brief Enables SW backed signing functionality in library.
*
- * For any signing related functionality to be included, this define or @ref MY_SIGNING_ATSHA204 has to be enabled.
+ * For any signing related functionality to be included, this define or @ref MY_SIGNING_ATSHA204 has
+ * to be enabled.
*/
//#define MY_SIGNING_SOFT
@@ -432,14 +460,21 @@
//#define MY_SIGNING_REQUEST_SIGNATURES
/**
- * @def MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL
- * @brief Enable this to have gateway require all nodes in the network to sign messages sent to it.
- * @ref MY_SIGNING_REQUEST_SIGNATURES must also be set.
+ * @def MY_SIGNING_WEAK_SECURITY
+ * @brief Enable this to permit downgrade of security preferences and relaxed gateway signing
+ * requirements.
*
- * Use this for maximum security, but be aware that every single node will have to be personalized before they can be used.
- * Note that if this is enabled, and whitelisting is also enabled, whitelisting will also be in effect for all nodes.
+ * Use this for evaluating security. It allows for gradual introduction of signing requirements in
+ * a network. Nodes that present themselves as not requiering signing or whitelisting will be
+ * cleared of this requirement at the receiving end. A gateway which require signatures will only do
+ * so from nodes that in turn require signatures.
+ * When not set, any node that has presented themselves as a node that require signatures or
+ * whitelisting, will be permanently remembered as doing so at the receiver until EEPROM is cleared
+ * or the receiver is reconfigured with this flag set or has signing disabled alltogether.
+ *
+ * @warning This flag when set will weaken security significantly
*/
-//#define MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL
+//#define MY_SIGNING_WEAK_SECURITY
/**
* @def MY_VERIFICATION_TIMEOUT_MS
@@ -1294,10 +1329,11 @@
#define MY_INDICATION_HANDLER
#define MY_DISABLE_REMOTE_RESET
// signing
+#define MY_SIGNING_SIMPLE_PASSWD
#define MY_SIGNING_ATSHA204
#define MY_SIGNING_SOFT
#define MY_SIGNING_REQUEST_SIGNATURES
-#define MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL
+#define MY_SIGNING_WEAK_SECURITY
#define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}}}
#define MY_DEBUG_VERBOSE_SIGNING
// RS485
diff --git a/configure b/configure
index f112ec1..c00f940 100755
--- a/configure
+++ b/configure
@@ -91,15 +91,16 @@ MySensors options:
--my-leds-rx-pin= Receive LED pin.
--my-leds-tx-pin= Transmit LED pin.
--my-leds-blinking-inverse Inverse the blinking feature.
- --my-signing=[none|software]
+ --my-signing=[none|software|password]
Message signing. [none]
--my-signing-debug Enable signing related debug.
--my-signing-request-signatures
Enable signature request from nodes that in turn requested
gateway signature.
- --my-signing-request-gw-signatures-from-all
- Require all nodes in the network to sign messages sent to the
- gateway.
+ --my-signing-weak_security Enable this to permit downgrade of security preferences and
+ relaxed gateway signing requirements.
+ --my-signing-password=
+ If you are using password as the signature type, set your password here.
EOF
}
@@ -459,9 +460,11 @@ for opt do
--my-signing-request-signatures*)
signing_request_signatures=true
;;
- --my-signing-request-gw-signatures-from-all*)
- signing_request_signatures=true
- CPPFLAGS="-DMY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL $CPPFLAGS"
+ --my-signing-weak_security*)
+ CPPFLAGS="-DMY_SIGNING_WEAK_SECURITY $CPPFLAGS"
+ ;;
+ --my-signing-password=*)
+ signing_password=${optarg}
;;
*)
echo "[WARNING] Unknown option detected:$opt, ignored"
@@ -571,8 +574,14 @@ elif [[ ${signing} == "software" ]]; then
if [[ ${signing_request_signatures} == true ]]; then
CPPFLAGS="-DMY_SIGNING_REQUEST_SIGNATURES $CPPFLAGS"
fi
+elif [[ ${signing} == "password" ]]; then
+ if [ -z "${signing_password}" ]; then
+ die "You need to set the password for signing with --my-signing-password= option" 6
+ fi
+ echo "Simplified signing using password"
+ CPPFLAGS="-DMY_SIGNING_SIMPLE_PASSWD=\\\"${signing_password}\\\" $CPPFLAGS"
else
- die "Invalid message signing option." 4
+ die "Invalid signing type." 7
fi
LDFLAGS="-pthread $LDFLAGS"
diff --git a/core/MySensorsCore.cpp b/core/MySensorsCore.cpp
index 9292fb2..d2e9a0f 100644
--- a/core/MySensorsCore.cpp
+++ b/core/MySensorsCore.cpp
@@ -118,7 +118,6 @@ void _begin(void)
ledsInit();
#endif
- (void)signerValidatePersonalization();
signerInit();
// Read latest received controller configuration from EEPROM
diff --git a/core/MySigning.cpp b/core/MySigning.cpp
index c1680ec..37f0085 100644
--- a/core/MySigning.cpp
+++ b/core/MySigning.cpp
@@ -30,11 +30,9 @@
#define SIGN_DEBUG(x,...)
#endif
-#if defined(MY_SIGNING_REQUEST_SIGNATURES) && (!defined(MY_SIGNING_ATSHA204) && !defined(MY_SIGNING_SOFT))
-#error You have to pick either MY_SIGNING_ATSHA204 or MY_SIGNING_SOFT in order to require signatures!
-#endif
-#if defined(MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL) && !defined(MY_SIGNING_REQUEST_SIGNATURES)
-#error You have to require signatures if you want to require signatures from all (also enable MY_SIGNING_REQUEST_SIGNATURES in your gateway)
+#if defined(MY_SIGNING_REQUEST_SIGNATURES) &&\
+ (!defined(MY_SIGNING_ATSHA204) && !defined(MY_SIGNING_SOFT))
+#error You have to pick either MY_SIGNING_ATSHA204 or MY_SIGNING_SOFT to reqire signatures!
#endif
#if defined(MY_SIGNING_SOFT) && defined(MY_SIGNING_ATSHA204)
#error You have to pick one and only one signing backend
@@ -56,10 +54,14 @@ enum { SIGN_WAITING_FOR_NONCE = 0, SIGN_OK = 1 };
// Macros for manipulating signing requirement tables
#define DO_SIGN(node) (~_doSign[node>>3]&(1<>3]&=~(1<>3]|=(1<>3]&(1<>3]&=~(1<>3]|=(1< MY_VERIFICATION_TIMEOUT_MS) {
- SIGN_DEBUG(PSTR("Timeout waiting for nonce!\n"));
+ SIGN_DEBUG(PSTR("!SGN:SGN:NCE TMO\n")); // Timeout waiting for nonce!
ret = false;
} else {
if (_signingNonceStatus == SIGN_OK) {
// process() received a nonce and signerProcessInternal successfully signed the message
msg = _msgSign; // Write the signed message back
- SIGN_DEBUG(PSTR("Message to send has been signed\n"));
+ SIGN_DEBUG(PSTR("SGN:SGN:SGN\n")); // Message to send has been signed
ret = true;
- // After this point, only the 'last' member of the message structure is allowed to be altered if the
- // message has been signed, or signature will become invalid and the message rejected by the receiver
+ // After this point, only the 'last' member of the message structure is allowed to be
+ // altered if the message has been signed, or signature will become invalid and the
+ // message rejected by the receiver
} else {
- SIGN_DEBUG(PSTR("Message to send could not be signed!\n"));
+ SIGN_DEBUG(PSTR("!SGN:SGN:SGN FAIL\n")); // Message to send could not be signed!
ret = false;
}
}
@@ -231,12 +223,11 @@ bool signerSignMsg(MyMessage &msg)
}
} else if (getNodeId() == msg.sender) {
mSetSigned(msg, 0); // Message is not supposed to be signed, make sure it is marked unsigned
- SIGN_DEBUG(PSTR("Will not sign message for destination %d as it does not require it\n"),
- msg.destination);
+ SIGN_DEBUG(PSTR("SGN:SGN:NREQ=%d\n"), msg.destination); // Do not sign message as it is not req
ret = true;
} else {
- SIGN_DEBUG(PSTR("Will not sign message since it was from %d and we are %d\n"), msg.sender,
- getNodeId());
+ SIGN_DEBUG(PSTR("SGN:SGN:%d!=%d NUS\n"), msg.sender,
+ getNodeId()); // Will not sign message since it was from someone else
ret = true;
}
#else
@@ -255,22 +246,24 @@ bool signerVerifyMsg(MyMessage &msg)
#if defined(MY_SIGNING_FEATURE) && defined(MY_SIGNING_REQUEST_SIGNATURES)
// If we are a node, or we are a gateway and the sender require signatures (or just a strict gw)
// and we are the destination...
-#if defined(MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL)
- if (msg.destination == getNodeId()) {
-#else
+#if defined(MY_SIGNING_WEAK_SECURITY)
if ((!MY_IS_GATEWAY || DO_SIGN(msg.sender)) && msg.destination == getNodeId()) {
+#else
+ if (msg.destination == getNodeId()) {
#endif
// Internal messages of certain types are not verified
if (skipSign(msg)) {
verificationResult = true;
} else if (!mGetSigned(msg)) {
// Got unsigned message that should have been signed
- SIGN_DEBUG(PSTR("Message is not signed, but it should have been!\n"));
+ SIGN_DEBUG(PSTR("!SGN:VER:NSG\n")); // Message is not signed, but it should have been!
verificationResult = false;
} else {
if (!signerBackendVerifyMsg(msg)) {
- SIGN_DEBUG(PSTR("Signature verification failed!\n"));
+ SIGN_DEBUG(PSTR("!SGN:VER:FAIL\n")); // Signature verification failed!
verificationResult = false;
+ } else {
+ SIGN_DEBUG(PSTR("SGN:VER:OK\n"));
}
#if defined(MY_NODE_LOCK_FEATURE)
if (verificationResult) {
@@ -279,10 +272,9 @@ bool signerVerifyMsg(MyMessage &msg)
nof_failed_verifications = 0;
} else {
nof_failed_verifications++;
- SIGN_DEBUG(PSTR("Failed verification attempts left until lockdown: %d\n"),
- MY_NODE_LOCK_COUNTER_MAX-nof_failed_verifications);
+ SIGN_DEBUG(PSTR("SGN:VER:LEFT=%d\n"), MY_NODE_LOCK_COUNTER_MAX-nof_failed_verifications);
if (nof_failed_verifications >= MY_NODE_LOCK_COUNTER_MAX) {
- _nodeLock("TMFV"); //Too many failed verifications
+ _nodeLock("TMFV"); // Too many failed verifications
}
}
#endif
@@ -349,30 +341,57 @@ int signerMemcmp(const void* a, const void* b, size_t sz)
#if defined(MY_SIGNING_FEATURE)
// Helper function to centralize signing/verification exceptions
+static bool signerValidatePersonalization(void)
+{
+#ifdef MY_SIGNING_SIMPLE_PASSWD
+ return true;
+#else
+ uint8_t buffer[32];
+ uint8_t* hash;
+ uint8_t checksum;
+ signerSha256Init();
+ hwReadConfigBlock((void*)buffer, (void*)EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS, 32);
+ signerSha256Update(buffer, 32);
+ hwReadConfigBlock((void*)buffer, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16);
+ signerSha256Update(buffer, 16);
+ hwReadConfigBlock((void*)buffer, (void*)EEPROM_SIGNING_SOFT_SERIAL_ADDRESS, 9);
+ signerSha256Update(buffer, 9);
+ hash = signerSha256Final();
+ hwReadConfigBlock((void*)&checksum, (void*)EEPROM_PERSONALIZATION_CHECKSUM_ADDRESS, 1);
+ if (checksum != hash[0]) {
+ SIGN_DEBUG(PSTR("!SGN:PER:TAMPERED\n"));
+ return false;
+ } else {
+ SIGN_DEBUG(PSTR("SGN:PER:OK\n"));
+ return true;
+ }
+#endif
+}
+
static bool skipSign(MyMessage &msg)
{
- bool ret;
+ bool ret = false;
if (mGetAck(msg)) {
- SIGN_DEBUG(PSTR("Skipping security for ACK on command %d type %d\n"), mGetCommand(msg), msg.type);
ret = true;
} else if (mGetCommand(msg) == C_INTERNAL &&
- (msg.type == I_NONCE_REQUEST || msg.type == I_NONCE_RESPONSE ||
- msg.type == I_SIGNING_PRESENTATION ||
+ (msg.type == I_SIGNING_PRESENTATION ||
+ msg.type == I_REGISTRATION_REQUEST ||
+ msg.type == I_NONCE_REQUEST || msg.type == I_NONCE_RESPONSE ||
msg.type == I_ID_REQUEST || msg.type == I_ID_RESPONSE ||
msg.type == I_FIND_PARENT_REQUEST || msg.type == I_FIND_PARENT_RESPONSE ||
msg.type == I_HEARTBEAT_REQUEST || msg.type == I_HEARTBEAT_RESPONSE ||
msg.type == I_PING || msg.type == I_PONG ||
- msg.type == I_REGISTRATION_REQUEST || msg.type == I_DISCOVER_REQUEST ||
- msg.type == I_DISCOVER_RESPONSE )) {
- SIGN_DEBUG(PSTR("Skipping security for command %d type %d\n"), mGetCommand(msg), msg.type);
+ msg.type == I_DISCOVER_REQUEST || msg.type == I_DISCOVER_RESPONSE )) {
ret = true;
} else if (mGetCommand(msg) == C_STREAM &&
- (msg.type == ST_FIRMWARE_REQUEST || msg.type == ST_FIRMWARE_RESPONSE ||
- msg.type == ST_SOUND || msg.type == ST_IMAGE)) {
- SIGN_DEBUG(PSTR("Skipping security for command %d type %d\n"), mGetCommand(msg), msg.type);
+ (msg.type == ST_SOUND ||
+ msg.type == ST_IMAGE ||
+ msg.type == ST_FIRMWARE_REQUEST || msg.type == ST_FIRMWARE_RESPONSE )) {
ret = true;
- } else {
- ret = false;
+ }
+ if (ret) {
+ SIGN_DEBUG(PSTR("SGN:SKP:%s CMD=%d,TYPE=%d\n"), mGetAck(msg) ? "ACK" : "MSG", mGetCommand(msg),
+ msg.type); //Skip signing/verification of this message
}
return ret;
}
@@ -395,27 +414,41 @@ static bool signerInternalProcessPresentation(MyMessage &msg)
const uint8_t sender = msg.sender;
#if defined(MY_SIGNING_FEATURE)
if (msg.data[0] != SIGNING_PRESENTATION_VERSION_1) {
- SIGN_DEBUG(PSTR("Unsupported signing presentation version (%d)!\n"), msg.data[0]);
+ SIGN_DEBUG(PSTR("!SGN:PRE:VER=%d\n"), msg.data[0]); // Unsupported signing presentation version
return true; // Just drop this presentation message
}
// We only handle version 1 here...
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_SIGNATURES) {
// We received an indicator that the sender require us to sign all messages we send to it
- SIGN_DEBUG(PSTR("Mark node %d as one that require signed messages\n"), sender);
+ SIGN_DEBUG(PSTR("SGN:PRE:SGN REQ,FROM=%d\n"), sender); // Node require signatures
SET_SIGN(sender);
} else {
- // We received an indicator that the sender does not require us to sign all messages we send to it
- SIGN_DEBUG(PSTR("Mark node %d as one that do not require signed messages\n"), sender);
+#if defined(MY_SIGNING_WEAK_SECURITY)
+ // We received an indicator that the sender does not require us to sign messages we send to it
+ SIGN_DEBUG(PSTR("SGN:PRE:SGN NREQ,FROM=%d\n"), sender); // Node does not require signatures
CLEAR_SIGN(sender);
+#else
+ if (DO_SIGN(sender)) {
+ SIGN_DEBUG(PSTR("!SGN:PRE:SGN NREQ,FROM=%d REJ\n"),
+ sender); // Node does not require signatures but used to do so
+ }
+#endif
}
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_WHITELISTING) {
// We received an indicator that the sender require us to salt signatures with serial
- SIGN_DEBUG(PSTR("Mark node %d as one that require whitelisting\n"), sender);
+ SIGN_DEBUG(PSTR("SGN:PRE:WHI REQ,FROM=%d\n"), sender); // Node require whitelisting
SET_WHITELIST(sender);
} else {
- // We received an indicator that the sender does not require us to sign all messages we send to it
- SIGN_DEBUG(PSTR("Mark node %d as one that do not require whitelisting\n"), sender);
+#if defined(MY_SIGNING_WEAK_SECURITY)
+ // We received an indicator that the sender does not require us to sign messages we send to it
+ SIGN_DEBUG(PSTR("SGN:PRE:WHI NREQ,FROM=%d\n"), sender); // Node does not require whitelisting
CLEAR_WHITELIST(sender);
+#else
+ if (DO_WHITELIST(sender)) {
+ SIGN_DEBUG(PSTR("!SGN:PRE:WHI NREQ,FROM=%d REJ\n"),
+ sender); // Node does not require whitelisting but used to do so
+ }
+#endif
}
// Save updated tables
@@ -429,35 +462,33 @@ static bool signerInternalProcessPresentation(MyMessage &msg)
#if defined(MY_GATEWAY_FEATURE)
prepareSigningPresentation(msg, sender);
#if defined(MY_SIGNING_REQUEST_SIGNATURES)
-#if defined(MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL)
- msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES;
-#else
+#if defined(MY_SIGNING_WEAK_SECURITY)
if (DO_SIGN(sender)) {
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES;
}
+#else
+ msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES;
#endif
#endif // MY_SIGNING_REQUEST_SIGNATURES
#if defined(MY_SIGNING_NODE_WHITELISTING)
-#if defined(MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL)
msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_WHITELISTING;
-#else
- if (DO_WHITELIST(sender)) {
- msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_WHITELISTING;
- }
-#endif
#endif // MY_SIGNING_NODE_WHITELISTING
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_SIGNATURES) {
- SIGN_DEBUG(PSTR("Informing node %d that we require signatures\n"), sender);
+ SIGN_DEBUG(PSTR("SGN:PRE:SGN REQ,TO=%d\n"), sender); // Inform node that we require signatures
} else {
- SIGN_DEBUG(PSTR("Informing node %d that we do not require signatures\n"), sender);
+ SIGN_DEBUG(PSTR("SGN:PRE:SGN NREQ,TO=%d\n"),
+ sender); // Inform node that we do not require signatures
}
if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_WHITELISTING) {
- SIGN_DEBUG(PSTR("Informing node %d that we require whitelisting\n"), sender);
+ SIGN_DEBUG(PSTR("SGN:PRE:WHI REQ,TO=%d\n"), sender); // Inform node that we require whitelisting
} else {
- SIGN_DEBUG(PSTR("Informing node %d that we do not require whitelisting\n"), sender);
+ SIGN_DEBUG(PSTR("SGN:PRE:WHI NREQ,TO=%d\n"),
+ sender); // Inform node that we do not require whitelisting
}
if (!_sendRoute(msg)) {
- SIGN_DEBUG(PSTR("Failed to transmit signing presentation!\n"));
+ SIGN_DEBUG(PSTR("!SGN:PRE:XMT,TO=%d FAIL\n"), sender); // Failed to transmit signing presentation!
+ } else {
+ SIGN_DEBUG(PSTR("SGN:PRE:XMT,TO=%d\n"), sender);
}
#endif // MY_GATEWAY_FEATURE
#else // not MY_SIGNING_FEATURE
@@ -465,18 +496,20 @@ static bool signerInternalProcessPresentation(MyMessage &msg)
// If we act as gateway and do not have the signing feature and receive a signing request we still
// need to do make sure the requester does not believe the gateway still require signatures
prepareSigningPresentation(msg, sender);
- SIGN_DEBUG(
- PSTR("Informing node %d that we do not require signatures because we do not support it\n"),
- sender);
+ SIGN_DEBUG(PSTR("SGN:PRE:NSUP,TO=%d\n"), sender); // Informing node that we do not support signing
if (!_sendRoute(msg)) {
- SIGN_DEBUG(PSTR("Failed to transmit signing presentation!\n"));
+ SIGN_DEBUG(PSTR("!SGN:PRE:XMT,TO=%d FAIL\n"), sender); // Failed to transmit signing presentation!
+ } else {
+ SIGN_DEBUG(PSTR("SGN:PRE:XMT,TO=%d\n"), sender);
}
#else // not MY_GATEWAY_FEATURE
// If we act as a node and do not have the signing feature then we just silently drop any signing
// presentation messages received
(void)msg;
(void)sender;
- SIGN_DEBUG(PSTR("Received signing presentation, but signing is not supported (message ignored)\n"));
+ // Received signing presentation, but signing is not supported (message ignored)
+ SIGN_DEBUG(
+ PSTR("SGN:PRE:NSUP\n"));
#endif // not MY_GATEWAY_FEATURE
#endif // not MY_SIGNING_FEATURE
return true; // No need to further process I_SIGNING_PRESENTATION
@@ -488,24 +521,25 @@ static bool signerInternalProcessNonceRequest(MyMessage &msg)
#if defined(MY_SIGNING_FEATURE)
#if defined(MY_NODE_LOCK_FEATURE)
nof_nonce_requests++;
- SIGN_DEBUG(PSTR("Nonce requests left until lockdown: %d\n"),
- MY_NODE_LOCK_COUNTER_MAX-nof_nonce_requests);
+ SIGN_DEBUG(PSTR("SGN:NCE:LEFT=%d\n"),
+ MY_NODE_LOCK_COUNTER_MAX-nof_nonce_requests); // Nonce requests left until lockdown
if (nof_nonce_requests >= MY_NODE_LOCK_COUNTER_MAX) {
- _nodeLock("TMNR"); //Too many nonces requested
+ _nodeLock("TMNR"); // Too many nonces requested
}
#endif // MY_NODE_LOCK_FEATURE
if (signerBackendGetNonce(msg)) {
if (!_sendRoute(build(msg, msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_NONCE_RESPONSE))) {
- SIGN_DEBUG(PSTR("Failed to transmit nonce!\n"));
+ SIGN_DEBUG(PSTR("!SGN:NCE:XMT,TO=%d FAIL\n"), msg.sender); // Failed to transmit nonce!
} else {
- SIGN_DEBUG(PSTR("Transmitted nonce\n"));
+ SIGN_DEBUG(PSTR("SGN:NCE:XMT,TO=%d\n"), msg.sender);
}
} else {
- SIGN_DEBUG(PSTR("Failed to generate nonce!\n"));
+ SIGN_DEBUG(PSTR("!SGN:NCE:GEN\n")); // Failed to generate nonce!
}
#else // not MY_SIGNING_FEATURE
(void)msg;
- SIGN_DEBUG(PSTR("Received nonce request, but signing is not supported (message ignored)\n"));
+ SIGN_DEBUG(
+ PSTR("SGN:NCE:NSUP (DROPPED)\n")); // Received nonce request/response without signing support
#endif // MY_SIGNING_FEATURE
return true; // No need to further process I_NONCE_REQUEST
}
@@ -515,24 +549,20 @@ static bool signerInternalProcessNonceResponse(MyMessage &msg)
{
#if defined(MY_SIGNING_FEATURE)
// Proceed with signing if nonce has been received
- SIGN_DEBUG(PSTR("Nonce received from %d.\n"), msg.sender);
+ SIGN_DEBUG(PSTR("SGN:NCE:FROM=%d\n"), msg.sender);
if (msg.sender != _msgSign.destination) {
- SIGN_DEBUG(PSTR("Nonce did not come from the destination (%d) of the message to be signed! "
- "It came from %d.\n"), _msgSign.destination, msg.sender);
- SIGN_DEBUG(PSTR("Silently discarding this nonce\n"));
+ SIGN_DEBUG(PSTR("SGN:NCE:%d!=%d (DROPPED)\n"), _msgSign.destination, msg.sender);
} else {
- SIGN_DEBUG(PSTR("Proceeding with signing...\n"));
signerBackendPutNonce(msg);
- if (!signerBackendSignMsg(_msgSign)) {
- SIGN_DEBUG(PSTR("Failed to sign message!\n"));
- } else {
- SIGN_DEBUG(PSTR("Message signed\n"));
- _signingNonceStatus = SIGN_OK; // _msgSign now contains the signed message pending transmission
+ if (signerBackendSignMsg(_msgSign)) {
+ // _msgSign now contains the signed message pending transmission
+ _signingNonceStatus = SIGN_OK;
}
}
#else
(void)msg;
- SIGN_DEBUG(PSTR("Received nonce response, but signing is not supported (message ignored)\n"));
+ SIGN_DEBUG(
+ PSTR("SGN:NCE:NSUP (DROPPED)\n")); // Received nonce request/response without signing support
#endif
return true; // No need to further process I_NONCE_RESPONSE
}
diff --git a/core/MySigning.h b/core/MySigning.h
index 7840efd..05334d2 100644
--- a/core/MySigning.h
+++ b/core/MySigning.h
@@ -24,150 +24,82 @@
* @brief The signing driver provides a generic API for various signing backends to offer
* signing of MySensors messages.
*
- * Signing support created by Patrick "Anticimex" Fallberg
+ * Signing support created by Patrick "Anticimex" Fallberg ()
*
- * @section MySigninggrpbackground Background and concepts
+ * @section MySigninggrptoc Table of contents
*
- * Suppose two participants, Alice and Bob, wants to exchange a message. Alice sends a message to Bob. In MySensors “language” Alice
- * could be a gateway and Bob an actuator (light swich, electronic lock, etc). But to be generic, we will substitute the term
- * “gateway” with Alice and a “node” with Bob (although the reverse relationship is also supported).
- *
- * Alice sends a message to Bob. This message can be heard by anyone who wants to listen (and also by anyone that is within “hearing”
- * distance). Normally, this is perhaps not a big issue. Nothing Alice says to Bob may be secret or sensitive in any way.
- * However, sometimes (or perhaps always) Bob want to be sure that the message Bob receives actually came from Alice. In cryptography,
- * this is known as authenticity. Bob needs some way of determining that the message is authentic from Alice, when Bob receives it.
- * This prevents an eavesdropper, Eve, to trick Bob into thinking it was Alice that sent a message Eve in fact transmitted. Bob also needs
- * to know how to determine if the message has been repeated. Eve could record a message sent by Alice that Bob accepted and then send
- * the same message again. Eve could also in some way prevent Bob from receiving the message and delay it in order to permit the message
- * to arrive to Bob at a time Eve chooses, instead of Alice. Such an attack is known as a replay attack.
- * Authenticity permits Bob to determine if Alice is the true sender of a message.
- * @image html MySigning/alicenfriends.png
- * @image latex MySigning/alicenfriends.eps "Alice with Eve and Bob" width=6cm
- *
- * It can also be interesting for Bob to know that the message Alice sent has not been tampered with in any way. This is the
- * integrity of the message. We now introduce Mallory, who could be intercepting the communication between Alice and Bob and
- * replace some parts of the message but keeping the parts that authenticate the message. That way, Bob still trusts Alice to be the
- * source, but the contents of the message was not the content Alice sent. Bob needs to be able to determine that the contents of the
- * message was not altered after Alice sent it.
- * Mallory would in this case be a man-in-the-middle attacker.
- * Integrity permits Bob to verify that the messages received from Alice has not been tampered with.
- * This is achieved by adding a signature to the message, which Bob can inspect to validate that Alice is the author.
- * @image html MySigning/alicenfriends2.png
- * @image latex MySigning/alicenfriends2.eps "Alice with Mallory and Bob" width=9cm
- *
- * The signing scheme used, needs to address both these attack scenarios. Neither Eve nor Mallory must be permitted to interfere with the
- * message exchange between Alice and Bob.
- *
- * The key challenge to implementing a secure signing scheme is to ensure that every signature is different, even if the message is not.
- * If not, replay attacks would be very hard to prevent.
- * One way of doing this is to increment some counter on the sender side and include it in the signature. This is however predictable.
- * A better option would be to introduce a random number to the signature. That way, it is impossible to predict what the signature will be.
- * The problem is, that also makes it impossible for the receiver (Bob) to verify that the signature is valid.
- * A solution to this is to let Bob generate the random number, keep it in memory and send it to Alice. Alice can then use the random number
- * in the signature calculation and send the signed message back to Bob who can validate the signature with the random number used.
- * This random number is in cryptography known as a nonce or
- * salt.
- *
- * However, Mallory might be eavesdropping on the communication and snoop up the nonce in order to generate a new valid signature for a
- * different message. To counter this, both Alice and Bob keep a secret that only they know. This secret is never transmitted over the air,
- * nor is it revealed to anybody. This secret is known as a pre-shared key (PSK).
- *
- * If Eve or Mallory are really sophisticated, he/she might use a delayed replay attack. This can be done by allowing Bob to transmit
- * a nonce to Alice. But when Alice transmits the uniquely signed message, Mallory prevents Bob from receiving it, to a point when Mallory
- * decides Bob should receive it. An example of such an attack is described
- * here.
- * This needs to be addressed as well, and one way of doing this is to have Bob keep track of time between a transmitted nonce and a signed
- * message to verify. If Bob is asked for a nonce, Bob knows that a signed message is going to arrive “soon”. Bob can then decide that if the
- * signed message does not arrive within a predefined timeframe, Bob throws away the generated nonce and thus makes it impossible to verify
- * the message if it arrives late.
- *
- * The flow can be described like this:
- * @image html MySigning/alicenbob.png
- * @image latex MySigning/alicenbob.eps "Alice and Bob" width=8cm
- * The benefits for MySensors to support this are obvious. Nobody wants others to be able to control or manipulate any actuators in their home.
- *
- * @section MySigninggrpencryption Why encryption is not part of this
- *
- * Well, some could be uncomfortable with somebody being able to snoop temperatures, motion or the state changes of locks in the environment.
- * Signing does not address these issues. Encryption is needed to prevent this.
- * It is my personal standpoint that encryption should not be part of the MySensors “protocol”. The reason is that a gateway and a node does
- * not really care about messages being readable or not by “others”. It makes more sense that such guarantees are provided by the underlying
- * transmission layer (RF solution in this case). It is the information transmitted over the air that needs to be secret (if user so desires).
- * The “trust” level on the other hand needs to go all the way into the sketches (who might have different requirements of trust depending on
- * the message participant), and for this reason, it is more important (and less complicated) to ensure authenticity and integrity at
- * protocol-level as message contents is still readable throughout the protocol stack. But as soon as the message leaves the “stack” it can be
- * scramble into “garbage” when transmitted over the air and then reassembled by a receiving node before being fed in “the clear” up the stack
- * at the receiving end.
- *
- * There are methods and possibilities to provide encryption also in software, but if this is done, it is my recommendation that this is done
- * after integrity- and authentication information has been provided to the message (if this is desired). Integrity and authentication is of
- * course not mandatory and some might be happy with only having encryption to cover their need for security. I, however, have only focused on
- * integrity and authenticity while at the same time keeping the current message routing mechanisms intact and therefore leave
- * the matter of secrecy to be implemented in the “physical” transport layer. With the integrity and authenticity handled
- * in the protocol it ought to be enough for a simple encryption (nonce-less AES with a PSK for instance) on the message as it is sent to the
- * RF backend. Atmel does provide such circuits as well but I have not investigated the matter further as it given the current size of the
- * ethernet gateway sketch is close to the size limit on an Arduino Nano, so it will be difficult to fit this into some existing gateway
- * designs.
- * Also it is worth to consider that the state of a lock can just as readily be determined by simply looking at the door in question or
- * attempting to open it, so obfuscating this information will not necessarily deter an attacker in any way.
- * Nevertheless, I do acknowledge that people find the fact that all information is sent “in the clear” even if it require some technical
- * effort for an intruder to obtain and inspect this information. So I do encourage the use of encrypting transport layers.
- * This is however not covered by this implementation.
- *
- * @section MySigninggrphow How this is done
- *
- * There exist many forms of message signature solutions to combat Eve and Mallory.
- * Most of these solutions are quite complex in term of computations, so I elected to use an algorithm that an external circuit is able to
- * process. This has the added benefit of protecting any keys and intermediate data used for calculating the signature so that even if
- * someone were to actually steal a sensor and disassembled it, they would not be able to extract the keys and other information from the
- * device.
- * A common scheme for message signing (authenticity and integrity) is implemented using
- * HMAC which in combination with a strong
- * hash function provides a very strong level of protection.
- * The Atmel ATSHA204A is a low-cost, low-voltage/current circuit that provides
- * HMAC calculation capabilities with SHA256 hashing which is a (currently) virtually unbreakable combination. If SHA256 were to be hacked,
- * a certain cryptocurrency would immediately be rendered worthless.
- * The ATSHA device also contain a random number generator (RNG) which enables the generation of a good nonce, as in,
- * non-predictable.
- * As I acknowledge that some might not want to use an additional external circuit, I have also implemented a software version of the ATSHA
- * device, capable of generating the same signatures as the ATSHA device does. Because it is pure-software however, it does not provide as
- * good nonces (it uses the Arduino pseudo-random generator) and the HMAC key is stored
- * in SW and is therefore readable if the memory is dumped. It also naturally claims more flash space due to the more complex software. But
- * for indoor sensors/actuators this might be good enough for most people.
+ * @ref MySigninggrphowuse
+ * @ref MySigningwhitelisting
+ * @ref MySigninglimitations
+ * @ref MySigningusecases
+ * @ref MySigningtechnical
+ * @ref MySigninggrpbackground
+ * @ref MySigninggrphow
+ * @ref MySigninggrpencryption
*
* @section MySigninggrphowuse How to use this
*
- * Before we begin with the details, I just want to emphasize that signing is completely optional and not enabled by default. If you do want
- * the additional security layer signing provides, you pick the backend of your choise in your sketch. Currently, two compatible backends are
- * supported; @ref MY_SIGNING_ATSHA204 (hardware backed) and @ref MY_SIGNING_SOFT (software backed). You can either enable these globally in
- * @ref MyConfig.h or in your sketch for sketch specific/local use.
+ * Before we begin with the details, I just want to emphasize that signing is completely optional
+ * and not enabled by default.
*
- * Firstly, you need to make sure to pick a backend to use as described above.
+ * If you do want the additional security layer signing provides, you pick the backend of your
+ * choise in your sketch. Currently, two compatible backends are supported; @ref MY_SIGNING_ATSHA204
+ * (hardware backed) and @ref MY_SIGNING_SOFT (software backed). There also exist a simplified
+ * variant (@ref MY_SIGNING_SIMPLE_PASSWD) of the software backend which only require one setting
+ * to activate.
+ *
+ * To use signng, you need to perform three major steps which are described below.
+ *
+ * Firstly, you need to make sure to pick a backend to use.
* @code{.cpp}
* //#define MY_SIGNING_SOFT
+ * //#define MY_SIGNING_SIMPLE_PASSWD
* #define MY_SIGNING_ATSHA204
* #include
* ...
* @endcode
* Make sure to set the define before the inclusion of MySensors.h.
- * It is legal to mix hardware- and software-based backends in a network. They work together.
+ * It is ok to mix @ref MY_SIGNING_SOFT and @ref MY_SIGNING_ATSHA204 in a network.
+ * They are fully compatible. It is however not recommended to use @ref MY_SIGNING_SOFT on nodes
+ * that are publicly accessible (more on that later).
*
- * You also need to decide if the node (or gateway) in question require and verify signatures in addition to calculating them.
- * This has to be set by at least one of the node in a "pair" or nobody will actually start calculating a signature for a message.
- * Just set the flag @ref MY_SIGNING_REQUEST_SIGNATURES and the node will inform the gateway that it expects the gateway to sign all
- * messages sent to the node. If this is set in a gateway, it will @b NOT force all nodes to sign messages to it. It will only require
- * signatures from nodes that in turn require signatures. If it is desired that the gateway should require signatures from all nodes,
- * @ref MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL can be set in the gateway sketch.
- * If you want to have two nodes communicate securely directly with each other, the nodes that require signatures must send a presentation
- * message to all nodes it expect signed messages from (only the gateway is informed automatically). See @ref signerPresentation().
+ * If you use @ref MY_SIGNING_SOFT or @ref MY_SIGNING_ATSHA204 you also need to decide if the node
+ * (or gateway) in question require messages to be signed in addition to the ability to generate
+ * signatures for other nodes.
+ * This has to be set by at least one of the nodes in a "pair" or nobody will actually start
+ * calculating a signature for a message.
+ * Just set the flag @ref MY_SIGNING_REQUEST_SIGNATURES and the node will inform the gateway that it
+ * expects the gateway to sign all messages sent to the node. Note that when set in a gateway, the
+ * gateway will require ALL nodes in the network to sign messages.
+ * If this behaviour is undesired, enable the flag @ref MY_SIGNING_WEAK_SECURITY which will allow
+ * the gateway to only require signatures from nodes that in turn require signatures. It will also
+ * allow the gateway (and all nodes) to "downgrade" security by clearing the signing/whitelisting
+ * requirements (whitelisting is described later on in the @ref MySigningwhitelisting section) in
+ * the EEPROM if a node presents itself as not having any security requirements.
+ * If @ref MY_SIGNING_WEAK_SECURITY is not set, any node that has presented itself with
+ * signing/whitelisting requirements will be permanently marked as such by the receiver
+ * (typically the gateway). The only way then to reset/revert this requirement is to clear the
+ * EEPROM at the receiver (or disable @ref MY_SIGNING_REQUEST_SIGNATURES, but the preference will be
+ * remembered if the request flag is re-enabled before EEPROM is cleared).
+ * If you want to have two nodes communicate securely directly with each other, the nodes that
+ * require signatures must send a presentation message to all nodes it expect signed messages from
+ * (only the gateway is informed automatically). See @ref signerPresentation().
* A node can have three "states" with respect to signing:
- * 1. Node does not support signing in any way (neither @ref MY_SIGNING_ATSHA204 nor @ref MY_SIGNING_SOFT is set)
- * 2. Node does support signing but don't require messages sent to it to be signed (@ref MY_SIGNING_REQUEST_SIGNATURES is not set)
- * 3. Node does support signing and require messages sent to it to be signed (@ref MY_SIGNING_REQUEST_SIGNATURES is set)
+ * 1. Node does not support signing in any way (neither @ref MY_SIGNING_ATSHA204,
+ * @ref MY_SIGNING_SOFT nor @ref MY_SIGNING_SIMPLE_PASSWD is set)
+ * 2. Node does support signing but don't require messages sent to it to be signed (neither
+ * @ref MY_SIGNING_REQUEST_SIGNATURES nor @ref MY_SIGNING_SIMPLE_PASSWD is set)
+ * 3. Node does support signing and require messages sent to it to be signed (@ref
+ * MY_SIGNING_SOFT or @ref MY_SIGNING_ATSHA204 together with @ref MY_SIGNING_REQUEST_SIGNATURES or
+ * @ref MY_SIGNING_SIMPLE_PASSWD are set)
*
* Secondly, you need to verify the configuration for the backend.
- * For hardware backed signing it is the pin the device is connected to. In MyConfig.h there are defaults which you might need to adjust to
- * match your personal build. The setting is defined using @ref MY_SIGNING_ATSHA204_PIN and the default is to use pin A3.
+ * For hardware backed signing it is the pin the device is connected to. In MyConfig.h there are
+ * defaults which you might need to adjust to match your personal build. The setting is defined
+ * using @ref MY_SIGNING_ATSHA204_PIN.
+ * If you use an official MySensors board (like the SenseBender GW) you do not need to set the pin,
+ * this is configured automatically by the Arduino board definition files.
+ *
* Similar to picking your backend, this can also be set in your sketch:
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
@@ -176,12 +108,14 @@
* #include
* ...
* @endcode
- * For the software backed signingbackend, an unconnected analog pin is required to set a random seed for the pseudo-random generator.
- * It is important that the pin is floating, or the output of the pseudo-random generator will be predictable, and thus compromise the
- * signatures. The setting is defined using @ref MY_SIGNING_SOFT_RANDOMSEED_PIN and the default is to use pin A7. The same configuration
- * possibilities exist as with the other configuration options.
+ * For the software backed signing backend, an unconnected analog pin is required on boards that
+ * does not provide a hardware based random generator unit to set a random seed for the
+ * pseudo-random generator.
+ * It is important that the pin is floating, or the output of the pseudo-random generator will be
+ * predictable, and thus compromise the signatures. The setting is defined using
+ * @ref MY_SIGNING_SOFT_RANDOMSEED_PIN. The same configuration possibilities exist as with the other
+ * configuration options.
*
- * Thirdly, if you use the software backend, you need to personalize the node (see @ref personalization).
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
@@ -192,10 +126,16 @@
*
* An example of a node that require signatures is available in @ref SecureActuator.ino.
*
- * @anchor personalization If you use the “real” ATSHA204A, before any signing operations can be done, the device needs to be personalized.
- * This can be a daunting process as it involves irreversibly writing configurations to the device, which cannot be undone. I have however tried
- * to simplify the process as much as possibly by creating a helper-sketch specifically for this purpose in @ref SecurityPersonalizer.ino
- * Note that you also need to do personalization for software signing, but then the values are stored in EEPROM.
+ * Thirdly, if you use a signing backend and you don't use @ref MY_SIGNING_SIMPLE_PASSWD, you
+ * need to personalize the node.
+ *
+ * @anchor personalization If you use the ATSHA204A (@ref MY_SIGNING_ATSHA204), before any signing
+ * operations can be done, the device needs to be personalized.
+ * This can be a daunting process as it involves irreversibly writing configurations to the device,
+ * which cannot be undone. I have however tried to simplify the process as much as possibly by
+ * creating a helper-sketch specifically for this purpose in @ref SecurityPersonalizer.ino
+ * Note that you also need to do personalization for @ref MY_SIGNING_SOFT, but then the values are
+ * stored in EEPROM.
*
* To personalize a ATSHA204A do the following procedure:
* 1. Enable @ref GENERATE_KEYS_ATSHA204A
@@ -227,66 +167,84 @@
*
* When you have personalized your first device after step 2 above, you can run the same sketch on
* all devices in your network that needs to be personalized in a compatible manner. Pick
- * @ref PERSONALIZE_ATSHA204A or @ref PERSONALIZE_SOFT as needed by the hardware.
+ * @ref PERSONALIZE_ATSHA204A or @ref PERSONALIZE_SOFT as needed by the hardware. When the
+ * personalization has finished, you just program the sketch you plan to use (with the appropriate
+ * signing flags set).
*
- * If a node does require signing, any unsigned message sent to the node will be rejected.
- * This also applies to the gateway. However, the difference is that the gateway will only require signed messages from nodes it knows in turn
- * require signed messages (unless @ref MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL is set).
- * A node can also inform a different node that it expects to receive signed messages from it. This is done by transmitting an internal message
- * of type @ref I_SIGNING_PRESENTATION and provide flags as payload that inform the receiver of the signing preferences of the sender.
- * All nodes and gateways in a network maintain a table where the signing preferences of all nodes are stored. This is also stored in EEPROM so
- * if the gateway reboots, the nodes does not have to retransmit a signing presentation to the gateway for the gateway to realize that the node
- * expect signed messages.
- * Also, the nodes that do not require signed messages will also inform gateway of this, so if you reprogram a node to stop require signing,
- * the gateway will adhere to this as soon as the new node has presented itself to the gateway. Note however, that if the gateway sets
- * @ref MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL a node that does not support signing will be unable to send any data to the gateway.
- *
- * The following sequence diagram illustrate how messages are passed in a MySensors network with respect to signing:
- * @image html MySigning/signingsequence.png
- * @image latex MySigning/signingsequence.eps "Signing sequence diagram" width=9cm
- *
- * None of this activity is “visible” to you (as the sensor sketch implementor). All you need to do is to set your preferences in MyConfig.h or
- * in your sketch. Depending on chosen backend, do personalization or key configurations and set @ref MY_SIGNING_REQUEST_SIGNATURES on the node that
- * you want protected. That is enough to enable protection from both Eve and Mallory in your network (although if you do not also enable encryption,
- * Eve can eavesdrop, but not do anything about, your messages).
+ * You are now set and ready to use message signing in your network.
+ * As of now, the following restrictions will be applied to your nodes:
+ * * If a node does require signing, any unsigned message sent to the node will be rejected.
+ * This also applies to the gateway.
+ * * Your radio communication quality is expected to work fine (if any NACK happen on a signing
+ * related message, it will fail, and enabeling signing will put maximum strain on your RF link as
+ * maximum sized packets are transmitted in the network). See @ref MySigningTroubleshootinggrp.
+ * * All nodes and gateways in a network maintain a table where the signing preferences of all nodes
+ * are stored. This is also stored in EEPROM so if a node or gateway reboots, the other nodes does
+ * not have to retransmit a signing presentation to the node for the node to start expecting signed
+ * messages from other nodes.
+ * * By default, the signing preferences are not "downgradeable". That is, any node that at any
+ * point in time has indicated a signing requirement will not be able to revert this requirement at
+ * the receiving end (except by manual erase of the EEPROM).
+ * If you for some reason need to be able to downgrade the security requirements, you can set
+ * @ref MY_SIGNING_WEAK_SECURITY at the receiver to allow it to downgrade the security expectations
+ * of the node in question.
+ * You then need to reset your transmitting node, to force it to transmit updated signing
+ * preferences.
*
* @section MySigningwhitelisting Whitelisting and node revocation
*
- * Consider the situation when you have set up your secure topology. We use the remotely operated garage door as an example:
- * - You have a node inside your garage (therefore secure and out of reach from prying eyes) that controls your garage door motor.\n
- * This node requires signing since you do not want an unauthorized person sending it orders to open the door.
- * - You have a keyfob node with a signing backend that uses the same PSK as your door opener node.
+ * Consider the situation when you have set up your secure topology. We use the remotely operated
+ * garage door as an example:
+ * * You have a node inside your garage (considered physically inaccessible) that controls your
+ * garage door motor.
+ * This node requires signing since you do not want an unauthorized person sending it orders to
+ * open the door.
+ * * You have a keyfob node with a signing backend that uses the same PSK as your door opener node.
*
- * In this setup, your keyfob can securely transmit messages to your door node since the keyfob will sign the messages it sends and the door node
- * will verify that these were sent from a trusted node (since it used the correct PSK). If the keyfob does not sign the messages, the door node
- * will not accept them.
+ * In this setup, your keyfob can securely transmit messages to your door node since the keyfob will
+ * sign the messages it sends and the door node will verify that these were sent from a trusted node
+ * (since it used the correct PSK). If the keyfob does not sign the messages, the door node
+ * will not accept them. Optionally, your keyfob sends a signed message to your gateway (which
+ * require signatures) and the gateway in turn sends a signed message to your garage door.
*
* One day your keyfob gets stolen or you lost it or it simply broke down.
*
- * You now end up with a problem; you need some way of telling your door node that the keyfob in question cannot be trusted any more. Furthermore,
- * you maybe locked the data region in your door nodes ATSHA device and is not able to revoke/change your PSK, or you have some other reason for
- * not wanting to replace the PSK. How do you make sure that the "rogue" keyfob can be removed from the "trusted chain"?
+ * You now end up with a problem; you need some way of telling your door node that the keyfob in
+ * question cannot be trusted any more. You could now repersonalize all your node to switch to a
+ * different PSK but this obviously is a hassle. How do you make sure that the "rogue" keyfob can be
+ * removed from the "trusted chain"?
*
- * The answer to this is whitelisting. You let your door node keep a whitelist of all nodes it trusts. If you stop trusting a particular node, you
- * remove it from the nodes whitelist, and it will no longer be able to communicate signed messages to the door node.
+ * The answer to this is whitelisting. You let your door node keep a whitelist of all nodes it
+ * trusts. If you stop trusting a particular node, you remove it from the nodes whitelist
+ * (by uploading a new sketch), and it will no longer be able to communicate signed messages to the
+ * door node.
*
- * In case you want to be able to "whitelist" trusted nodes (in order to be able to revoke them in case they are lost) you also need to take note
- * of the serial number of the ATSHA device or the software value stored in EEPROM. This is unique for each device. The serial number is printed
+ * In case you want to be able to "whitelist" trusted nodes (in order to be able to revoke them in
+ * case they are lost) you also need to take note of the serial number of the ATSHA device or the
+ * software value stored in EEPROM. This is unique for each device. The serial number is printed
* in a copy+paste friendly format by the personalizer for this purpose.
- * The whitelist is stored on the node that require signatures. When a received message is verified, the serial of the sender is looked up in a
- * list stored on the receiving node, and the corresponding serial stored in the list for that sender is then included in the signature verification
- * process. The list is stored as the value of the flag that enables whitelisting, @ref MY_SIGNING_NODE_WHITELISTING.
+ * The whitelist is stored on the node that require signatures. When a received message is
+ * verified, the serial of the sender is looked up in a list stored on the receiving node, and the
+ * corresponding serial stored in the list for that sender is then included in the signature
+ * verification process. The list is stored as the value of the flag that enables whitelisting,
+ * @ref MY_SIGNING_NODE_WHITELISTING.
*
- * Whitelisting is achieved by 'salting' the signature with some node-unique information known to the receiver. In the case of ATSHA204A this is the
- * unique serial number programmed into the circuit. This unique number is never transmitted over the air in clear text, so Eve will not be able to
- * figure out a "trusted" serial by snooping on the traffic.
- * Instead the value is hashed together with the senders NodeId into the HMAC signature to produce the final signature. The receiver will then take
- * the originating NodeId of the signed message and do the corresponding calculation with the serial it has stored in it's whitelist if it finds a
+ * Whitelisting is achieved by 'salting' the signature with some node-unique information known to
+ * the receiver. In the case of @ref MY_SIGNING_ATSHA204 this is the unique serial number programmed
+ * into the circuit. This unique number is never transmitted over the air in clear text, so Eve will
+ * not be able to figure out a "trusted" serial by snooping on the traffic.
+ * Instead the value is hashed together with the senders NodeId into the HMAC signature to produce
+ * the final signature. The receiver will then take the originating NodeId of the signed message and
+ * do the corresponding calculation with the serial it has stored in it's whitelist if it finds a
* matching entry in it's whitelist.
*
- * Whitelisting is an optional alternative because it adds some code and configuration options which might not be desirable for every user. So if
- * you want the ability to use whitelists, you need to enable @ref MY_SIGNING_NODE_WHITELISTING.
- * The whitelist is provided as value of the flag that enable it as follows (example is a node that require signing as well):
+ * Whitelisting is an optional alternative because it adds some code and configuration options which
+ * might not be desirable for every user. So if you want the ability to use whitelists, you need to
+ * enable @ref MY_SIGNING_NODE_WHITELISTING. You need to remember that the gateway will remember if
+ * a node has presented it with a whitelisting requirement as described above, if you at some point
+ * decide to remove the whitelist requirement.
+ * The whitelist is provided as value of the flag that enable it as follows (example is a node that
+ * require signing as well):
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
* #define MY_SIGNING_REQUEST_SIGNATURES
@@ -294,8 +252,9 @@
* #include
* ...
* @endcode
- * In this example, there are two nodes in the whitelist; the gateway, and a separate node that communicates directly with this node (with signed
- * messages). You do not need to do anything special for the sending nodes, apart from making sure they support signing.
+ * In this example, there are two nodes in the whitelist; the gateway, and a separate node that
+ * communicates directly with this node (with signed messages). You do not need to do anything
+ * special for the sending nodes, apart from making sure they support signing.
*
* The "soft" backend of course also support whitelisting. Example:
* @code{.cpp}
@@ -307,65 +266,45 @@
* ...
* @endcode
*
- * For a node that should transmit whitelisted messages but not receive whitelisted messages, you do not need any special configurations:
+ * For a node that should transmit whitelisted messages but not receive whitelisted messages, you do
+ * not need any special configurations:
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
* @endcode
- * Remember that you always need to select a signing backend for all nodes that communicate to a node that require whitelisting.
- * Also, note that a node that use whitelisting will not accept messages from nodes that are not present in it's whitelist.
- * And you have to personalize all nodes that use signing with a common HMAC key but different serial numbers (ATSHA204A always has unique serials).
- *
- * @section MySigningtechnical The technical stuff
- *
- * How are the messages actually affected by the signing?
- * The following illustration shows what part of the message is signed, and where the signature is stored:
- * @image html MySigning/signingillustrated1.png
- * @image latex MySigning/signingillustrated1.eps "Message structure" width=11cm
- *
- * The first byte of the header is not covered by the signature, because in the network, this byte is used to track hops in the network and therefore
- * might change if the message is passing a relay node. So it cannot be part of the signature, or the signature would be invalid when it arrives to
- * its destination. The signature also carries a byte with a signing identifier to prevent false results from accidental mixing of incompatible
- * signing backends in the network. Thus, the maximum size for a payload is 29-7 bytes. Larger payloads are not possible to sign. Another thing to
- * consider is that the strength of the signature is inversely proportional to the payload size.
- *
- * As for the software backend, it turns out that the ATSHA does not do “vanilla” HMAC processing. Fortunately, Atmel has documented exactly how
- * the circuit processes the data and hashes thus making it possible to generate signatures that are identical to signatures generated by the circuit.
- *
- * The signatures are calculates in the following way:
- * @image html MySigning/signingillustrated2.png
- * @image latex MySigning/signingillustrated2.eps "HMAC processing" width=10cm
- *
- * Exactly how this is done can be reviewd in the source for the ATSHA204SOFT backend and the ATSHA204A
- * datasheet.
- * In the MySensors protocol, the following internal messagetypes handles signature requirements and nonce requests:
- * @ref I_SIGNING_PRESENTATION
- * @ref I_NONCE_REQUEST
- * @ref I_NONCE_RESPONSE
- *
- * Also, the version field in the header has been reduced from 3 to 2 bits in order to fit a single bit to indicate that a message is signed.
+ * Remember that you always need to select a signing backend for all nodes that communicate to a
+ * node that require whitelisting. Also, note that a node that use whitelisting will not accept
+ * messages from nodes that are not present in it's whitelist.
+ * And you have to personalize all nodes that use signing with a common HMAC key but different
+ * serial numbers (@ref MY_SIGNING_ATSHA204 always has unique serials).
*
* @section MySigninglimitations Known limitations
*
- * Due to the limiting factor of our cheapest Arduino nodes, the use of diversified keys is not implemented. That mean that all nodes in your
- * network share the same PSK (at least the ones that are supposed to exchange signed data). It is important to understand the implications of
+ * Due to the limiting factor of our cheapest Arduino nodes, the use of diversified keys is not
+ * implemented. That mean that all nodes in your network share the same PSK (at least the ones that
+ * are supposed to exchange signed data). It is important to understand the implications of
* this, and that is covered in the "Typical use cases" chapter below.
- * Also be reminded that the strength of the signature is inversely proportional to the size of the message. The larger the message, the weaker
- * the signature.
+ * Most importantly, if you use @ref MY_SIGNING_SOFT your PSK will be stored in EEPROM and will
+ * therefore be accessible by anyone with physical access to your node. Therefore it is NOT
+ * recommended to use @ref MY_SIGNING_SOFT on nodes that are placed in a public place or worn on
+ * on your person (like a keyfob).
+ * Also be reminded that the strength of the signature is inversely proportional to the size of the
+ * message. The larger the message, the weaker the signature.
*
* @section MySigningusecases Typical use cases
*
- * "Securely located" in this context mean a node which is not physically publicly accessible. Typically at least your gateway.
- * "Public" in this context mean a node that is located outside your "trusted environment". This includes sensors located outdoors, keyfobs etc.
+ * "Securely located" in this context mean a node which is not physically publicly accessible.
+ * Typically at least your gateway.
+ * "Public" in this context mean a node that is located outside your "trusted environment". This
+ * includes sensors located outdoors, keyfobs etc.
*
* @subsection MySigninglock Securely located lock
*
- * You have a securely located gateway and a lock somewhere inside your "trusted environment" (e.g. inside your house door, the door to your
- * dungeon or similar).
- * You should then keep the data section of your gateway and your lock node unlocked. Locking the data (and therefore the PSK) will require you
- * to replace at least the signing circuit in case you need to revoke the PSK because some other node in your network gets compromised.
- * You need to make your node require signed messages but you do not necessarily need to make your gateway require signed messsages (unless you
- * are concerned that someone might spoof the lock status of your lock).
+ * You have a securely located gateway and a lock somewhere inside your "trusted environment" (e.g.
+ * inside your house door, the door to your dungeon or similar).
+ * You need to make your node require signed messages but you do not necessarily need to make your
+ * gateway require signed messsages (unless you are concerned that someone might spoof the lock
+ * status of your lock).
* Configuration example for the secure lock node:
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
@@ -373,18 +312,20 @@
* #include
* ...
* @endcode
- * If you do also want your gateway to require signatures from your lock you just enable the same (or similar if using software signing) settings
- * in the gateway.
+ * If you do also want your gateway to require signatures from your lock you just enable the same
+ * (or similar if using software signing) settings in the gateway.
*
* @subsection MySigningpatio Patio motion sensor
*
- * Your gateway is securely located inside your house, but your motion sensor is located outside your house. You have for some reason elected
- * that this node should sign the messages it send to your gateway. You should lock the data (PSK) in this node then, because if someone were
- * to steal your patio motion sensor, they could rewrite the firmware and spoof your gateway to use it to transmit a correctly signed message
- * to your secure lock inside your house. But if you revoke your gateway (and lock) PSK the outside sensor cannot be used for this anymore.
- * Nor can it be changed in order to do it in the future. You can also use whitelisting to revoke your lost node.
- * This is an unlikely use case because there really is no reason to sign sensor values. If you for some reason want to obfuscate sensor data,
- * encryption is a better alternative.
+ * Your gateway is securely located inside your house, but your motion sensor is located outside
+ * your house. You have for some reason elected that this node should sign the messages it send to
+ * your gateway. You should lock the data (PSK) in this node then, because if someone were to steal
+ * your patio motion sensor, they could rewrite the firmware and spoof your gateway to use it to
+ * transmit a correctly signed message to your secure lock inside your house. But if you revoke your
+ * gateway (and lock) PSK the outside sensor cannot be used for this anymore. Nor can it be changed
+ * in order to do it in the future. You can also use whitelisting to revoke your lost node.
+ * This is an unlikely use case because there really is no reason to sign sensor values. If you for
+ * some reason want to obfuscate sensor data, encryption is a better alternative.
* Configuration example for a motion sensor:
* @code{.cpp}
* #define MY_SIGNING_SOFT
@@ -394,7 +335,8 @@
* ...
* @endcode
*
- * The gateway needs to be configured with a whitelist (and it has to have an entry for all nodes that send and/or require signed messages):
+ * The gateway needs to be configured with a whitelist (and it has to have an entry for all nodes
+ * that send and/or require signed messages):
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
@@ -406,17 +348,21 @@
* @subsection MySigningkeyfob Keyfob for garage door opener
*
- * Perhaps the most typical usecase for signed messages. Your keyfob should be totally locked down. If the garage door opener is secured
- * (and it should be) it can be unlocked. That way, if you loose your keyfob, you can revoke the PSK in both the opener and your gateway,
- * thus rendering the keyfob useless without having to replace your nodes. You can also use whitelisting to revoke your lost keyfob.
- * Configuration example for the keyfob (keyfob will only transmit to another node and not receive anything):
+ * Perhaps the most typical usecase for signed messages. Your keyfob should be totally locked down.
+ * If the garage door opener is secured (and it should be) it can be unlocked. That way, if you
+ * loose your keyfob, you can revoke the PSK in both the opener and your gateway,
+ * thus rendering the keyfob useless without having to replace your nodes. You can also use
+ * whitelisting to revoke your lost keyfob.
+ * Configuration example for the keyfob (keyfob will only transmit to another node and not receive
+ * anything):
* @code{.cpp}
* #define MY_SIGNING_ATSHA204
* #include
* ...
* @endcode
*
- * Configuration example for the door controller node (should require signing from anyone who wants to control it):
+ * Configuration example for the door controller node (should require signing from anyone who wants
+ * to control it):
* @code{.cpp}
* #define MY_SIGNING_SOFT
* #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
@@ -430,6 +376,200 @@
*
* - @ref SecureActuator.ino
* - @ref SecurityPersonalizer.ino
+ *
+ * @section MySigningtechnical The technical stuff
+ *
+ * The following sequence diagram illustrate how messages are passed in a MySensors network with
+ * respect to signing:
+ * @image html MySigning/signingsequence.png
+ * @image latex MySigning/signingsequence.eps "Signing sequence diagram" width=9cm
+ *
+ * None of this activity is “visible” to you (as the sensor sketch implementor). All you need to do
+ * is to set your preferences in your sketch and personalize accordingly.
+ * That is enough to enable protection from both Eve and Mallory in your network
+ * although if you do not also enable encryption, Eve can eavesdrop, but not do anything about,
+ * your messages (except possibly preventing them from arriving). @ref MY_SIGNING_SIMPLE_PASSWD also
+ * enable encryption autmatically.
+ *
+ * How are the messages actually affected by the signing?
+ * The following illustration shows what part of the message is signed, and where the signature is
+ * stored:
+ * @image html MySigning/signingillustrated1.png
+ * @image latex MySigning/signingillustrated1.eps "Message structure" width=11cm
+ *
+ * The first byte of the header is not covered by the signature, because in the network, this byte
+ * is used to track hops in the network and therefore might change if the message is passing a relay
+ * node. So it cannot be part of the signature, or the signature would be invalid when it arrives to
+ * its destination. The signature also carries a byte with a signing identifier to prevent false
+ * results from accidental mixing of incompatible signing backends in the network. Thus, the maximum
+ * size for a payload is 29-7 bytes. Larger payloads are not possible to sign at the moment. Another
+ * thing to consider is that the strength of the signature is inversely proportional to the payload
+ * size.
+ *
+ * As for the software backend, it turns out that the ATSHA does not do “vanilla” HMAC processing.
+ * Fortunately, Atmel has documented exactly how the circuit processes the data and hashes thus
+ * making it possible to generate signatures that are identical to signatures generated by the
+ * circuit.
+ *
+ * The signatures are calculates in the following way:
+ * @image html MySigning/signingillustrated2.png
+ * @image latex MySigning/signingillustrated2.eps "HMAC processing" width=10cm
+ *
+ * Exactly how this is done can be reviewed in the source for the software backend
+ * (MySigningAtsha204Soft.cpp) and the ATSHA204A
+ * datasheet
+ * . In the MySensors protocol, the following internal messagetypes handles signature
+ * requirements and nonce requests:
+ * @ref I_SIGNING_PRESENTATION
+ * @ref I_NONCE_REQUEST
+ * @ref I_NONCE_RESPONSE
+ *
+ * Also, the version field in the header has been reduced from 3 to 2 bits in order to fit a single
+ * bit to indicate that a message is signed.
+ *
+ * @section MySigninggrpbackground Background and concepts
+ *
+ * Suppose two participants, Alice and Bob, wants to exchange a message. Alice sends a message to
+ * Bob. In MySensors “language” Alice could be a gateway and Bob an actuator (light swich,
+ * electronic lock, etc). But to be generic, we will substitute the term “gateway” with Alice and a
+ * “node” with Bob (although the reverse relationship is also supported).
+ *
+ * Alice sends a message to Bob. This message can be heard by anyone who wants to listen (and also
+ * by anyone that is within “hearing” distance). Normally, this is perhaps not a big issue. Nothing
+ * Alice says to Bob may be secret or sensitive in any way. However, sometimes (or perhaps always)
+ * Bob want to be sure that the message Bob receives actually came from Alice. In cryptography,
+ * this is known as authenticity. Bob needs some way of determining that the message is
+ * authentic from Alice, when Bob receives it. This prevents an eavesdropper, Eve, to trick Bob into
+ * thinking it was Alice that sent a message Eve in fact transmitted. Bob also needs to know how to
+ * determine if the message has been repeated. Eve could record a message sent by Alice that Bob
+ * accepted and then send the same message again. Eve could also in some way prevent Bob from
+ * receiving the message and delay it in order to permit the message to arrive to Bob at a time Eve
+ * chooses, instead of Alice. Such an attack is known as a replay attack.
+ * Authenticity permits Bob to determine if Alice is the true sender of a message.
+ * @image html MySigning/alicenfriends.png
+ * @image latex MySigning/alicenfriends.eps "Alice with Eve and Bob" width=6cm
+ *
+ * It can also be interesting for Bob to know that the message Alice sent has not been tampered with
+ * in any way. This is the integrity of the message. We now introduce Mallory, who could be
+ * intercepting the communication between Alice and Bob and replace some parts of the message but
+ * keeping the parts that authenticate the message. That way, Bob still trusts Alice to be the
+ * source, but the contents of the message was not the content Alice sent. Bob needs to be able to
+ * determine that the contents of the message was not altered after Alice sent it.
+ * Mallory would in this case be a man-in-the-middle attacker.
+ * Integrity permits Bob to verify that the messages received from Alice has not been
+ * tampered with.
+ * This is achieved by adding a signature to the message, which Bob can inspect to validate
+ * that Alice is the author.
+ * @image html MySigning/alicenfriends2.png
+ * @image latex MySigning/alicenfriends2.eps "Alice with Mallory and Bob" width=9cm
+ *
+ * The signing scheme used, needs to address both these attack scenarios. Neither Eve nor Mallory
+ * must be permitted to interfere with the message exchange between Alice and Bob.
+ *
+ * The key challenge to implementing a secure signing scheme is to ensure that every signature is
+ * different, even if the message is not. If not, replay attacks would be very hard to
+ * prevent.
+ * One way of doing this is to increment some counter on the sender side and include it in the
+ * signature. This is however predictable.
+ * A better option would be to introduce a random number to the signature. That way, it is
+ * impossible to predict what the signature will be. The problem is, that also makes it impossible
+ * for the receiver (Bob) to verify that the signature is valid.
+ * A solution to this is to let Bob generate the random number, keep it in memory and send it to
+ * Alice. Alice can then use the random number in the signature calculation and send the signed
+ * message back to Bob who can validate the signature with the random number used.
+ * This random number is in cryptography known as a
+ * nonce or
+ * salt.
+ *
+ * However, Mallory might be eavesdropping on the communication and snoop up the nonce in order to
+ * generate a new valid signature for a different message. To counter this, both Alice and Bob keep
+ * a secret that only they know. This secret is never transmitted over the air,
+ * nor is it revealed to anybody. This secret is known as a
+ * pre-shared key (PSK).
+ *
+ * If Eve or Mallory are really sophisticated, he/she might use a delayed replay attack.
+ * This can be done by allowing Bob to transmit a nonce to Alice. But when Alice transmits the
+ * uniquely signed message, Mallory prevents Bob from receiving it, to a point when Mallory
+ * decides Bob should receive it. An example of such an attack is described
+ *
+ * here.
+ * This needs to be addressed as well, and one way of doing this is to have Bob keep track of time
+ * between a transmitted nonce and a signed message to verify. If Bob is asked for a nonce, Bob
+ * knows that a signed message is going to arrive “soon”. Bob can then decide that if the signed
+ * message does not arrive within a predefined timeframe, Bob throws away the generated nonce and
+ * thus makes it impossible to verify the message if it arrives late.
+ *
+ * The flow can be described like this:
+ * @image html MySigning/alicenbob.png
+ * @image latex MySigning/alicenbob.eps "Alice and Bob" width=8cm
+ * The benefits for MySensors to support this are obvious. Nobody wants others to be able to control
+ * or manipulate any actuators in their home.
+ *
+ * @section MySigninggrphow How this is done
+ *
+ * There exist many forms of message signature solutions to combat Eve and Mallory.
+ * Most of these solutions are quite complex in term of computations, so I elected to use an
+ * algorithm that an external circuit is able to process. This has the added benefit of protecting
+ * any keys and intermediate data used for calculating the signature so that even if someone were to
+ * actually steal a sensor and disassembled it, they would not be able to extract the keys and other
+ * information from the device.
+ * A common scheme for message signing (authenticity and integrity) is implemented using
+ * HMAC which in
+ * combination with a strong hash function
+ * provides a very strong level of protection.
+ * The Atmel ATSHA204A is a low-cost,
+ * low-voltage/current circuit that provides HMAC calculation capabilities with SHA256 hashing which
+ * is a (currently) virtually unbreakable combination. If SHA256 were to be hacked, a certain
+ * cryptocurrency would immediately be rendered
+ * worthless.
+ * The ATSHA device also contain a random number generator (RNG) which enables the generation of a
+ * good nonce, as in, non-predictable.
+ * As I acknowledge that some might not want to use an additional external circuit, I have also
+ * implemented a software version of the ATSHA device, capable of generating the same signatures as
+ * the ATSHA device does. Because it is pure-software however, it does not provide as good nonces
+ * (it uses the Arduino pseudo-random generator)
+ * and the HMAC key is stored in SW and is therefore readable if the memory is dumped. It also
+ * naturally claims more flash space due to the more complex software. But for indoor
+ * sensors/actuators this might be good enough for most people.
+ *
+ * @section MySigninggrpencryption Why encryption is not part of this
+ *
+ * Well, some could be uncomfortable with somebody being able to snoop temperatures, motion or the
+ * state changes of locks in the environment.
+ * Signing does not address these issues. Encryption is needed to prevent this.
+ * It is my personal standpoint that encryption should not be part of the MySensors “protocol”. The
+ * reason is that a gateway and a node does not really care about messages being readable or not by
+ * “others”. It makes more sense that such guarantees are provided by the underlying transmission
+ * layer (RF solution in this case). It is the information transmitted over the air that needs to be
+ * secret (if user so desires). The “trust” level on the other hand needs to go all the way into the
+ * sketches (who might have different requirements of trust depending on the message participant),
+ * and for this reason, it is more important (and less complicated) to ensure authenticity and
+ * integrity at protocol-level as message contents is still readable throughout the protocol
+ * stack. But as soon as the message leaves the “stack” it can be scramble into “garbage” when
+ * transmitted over the air and then reassembled by a receiving node before being fed in “the clear”
+ * up the stack at the receiving end.
+ *
+ * There are methods and possibilities to provide encryption also in software, but if this is done,
+ * it is my recommendation that this is done after integrity- and authentication information has
+ * been provided to the message (if this is desired). Integrity and authentication is of course not
+ * mandatory and some might be happy with only having encryption to cover their need for security.
+ * I, however, have only focused on integrity and authenticity while at the same time
+ * keeping the current message routing mechanisms intact and therefore leave the matter of
+ * secrecy to be implemented in the “physical” transport layer. With the integrity and
+ * authenticity handled in the protocol it ought to be enough for a simple encryption
+ * (nonce-less AES with a PSK for instance) on the message as it is sent to the RF backend. Atmel
+ * does provide such circuits as well but I have not investigated the matter further as it given the
+ * current size of the ethernet gateway sketch is close to the size limit on an Arduino Nano, so it
+ * will be difficult to fit this into some existing gateway designs.
+ * Also it is worth to consider that the state of a lock can just as readily be determined by simply
+ * looking at the door in question or attempting to open it, so obfuscating this information will
+ * not necessarily deter an attacker in any way.
+ * Nevertheless, I do acknowledge that people find the fact that all information is sent “in the
+ * clear” even if it require some technical effort for an intruder to obtain and inspect this
+ * information. So I do encourage the use of encrypting transport layers.
+ * This is however not covered by this implementation.
+ * This might change in the future as more powerful platforms emerge which permit more complex
+ * security schemes and better hardware acceleration.
*/
/**
@@ -462,16 +602,6 @@ typedef struct {
*/
void signerInit(void);
-/**
- * @brief Validates personalization data
- *
- * This function checks that the personalization details are in order.
- * \n@b Usage: This fuction should be called before any signing related operations take place.
- *
- * @returns @c true if personalization data is valid.
- */
-bool signerValidatePersonalization(void);
-
/**
* @brief Does signing specific presentation for a node.
*
@@ -605,8 +735,94 @@ int signerMemcmp(const void* a, const void* b, size_t sz);
#endif
/** @}*/
+
/**
- * @defgroup MySigningTroubleshootinggrp Troubleshooting
+ * @defgroup MySigningDebugMessages Signing related debug messages
+ * @ingroup MySigninggrp
+ * @{
+ *
+ * MySigning-related log messages, format: [!]SYSTEM:SUB SYSTEM:MESSAGE
+ * - [!] Exclamation mark is prepended in case of error or warning
+ * - SYSTEM:
+ * - SGN messages emitted by MySigning
+ * - SUB SYSTEMS:
+ * - SGN:INI from @ref signerInit
+ * - SGN:PER from @ref signerInit
+ * - SGN:PRE from @ref signerPresentation
+ * - SGN:SGN from @ref signerSignMsg
+ * - SGN:VER from @ref signerVerifyMsg
+ * - SGN:SKP from @ref signerSignMsg or @ref signerVerifyMsg (skipSign)
+ * - SGN:NCE from @ref signerProcessInternal (signerInternalProcessNonceRequest)
+ * - SGN:BND from the signing backends
+ *
+ * MySigning debug log messages:
+ *
+ * |E| SYS | SUB | Message | Comment
+ * |-|-----|-----|--------------------------|----------------------------------------------------------------------------
+ * | | SGN | INI | BND OK | Backend has initialized ok
+ * |!| SGN | INI | BND FAIL | Backend has not initialized ok
+ * | | SGN | PER | OK | Personalization data is ok
+ * |!| SGN | PER | TAMPERED | Personalization data has been tampered
+ * | | SGN | PRE | SGN REQ | Signing required
+ * | | SGN | PRE | SGN REQ,TO='node' | Tell 'node' that we require signing
+ * | | SGN | PRE | SGN REQ,FROM='node' | Node 'node' require signing
+ * | | SGN | PRE | SGN NREQ | Signing not required
+ * | | SGN | PRE | SGN REQ,TO='node' | Tell 'node' that we do not require signing
+ * | | SGN | PRE | SGN NREQ,FROM='node' | Node 'node' does not require signing
+ * |!| SGN | PRE | SGN NREQ,FROM='node' REJ | Node 'node' does not require signing but used to (requirement remain unchanged)
+ * | | SGN | PRE | WHI REQ | Whitelisting required
+ * | | SGN | PRE | WHI REQ;TO='node' | Tell 'node' that we require whitelisting
+ * | | SGN | PRE | WHI REQ,FROM='node' | Node 'node' require whitelisting
+ * | | SGN | PRE | WHI NREQ | Whitelisting not required
+ * | | SGN | PRE | WHI NREQ,TO='node' | Tell 'node' that we do not require whitelisting
+ * | | SGN | PRE | WHI NREQ,FROM='node' | Node 'node' does not require whitelisting
+ * |!| SGN | PRE | WHI NREQ,FROM='node' REJ | Node 'node' does not require whitelisting but used to (requirement remain unchanged)
+ * | | SGN | PRE | XMT,TO='node' | Presentation data transmitted to 'node'
+ * |!| SGN | PRE | XMT,TO='node' FAIL | Presentation data not properly transmitted to 'node'
+ * | | SGN | PRE | WAIT GW | Waiting for gateway presentation data
+ * |!| SGN | PRE | VER='version' | Presentation version 'version' is not supported
+ * | | SGN | PRE | NSUP | Received signing presentation but signing is not supported
+ * | | SGN | PRE | NSUP,TO='node' | Informing 'node' that we do not support signing
+ * | | SGN | SGN | NCE REQ,TO='node' | Nonce request transmitted to 'node'
+ * |!| SGN | SGN | NCE REQ,TO='node' FAIL | Nonce request not properly transmitted to 'node'
+ * |!| SGN | SGN | NCE TMO | Timeout waiting for nonce
+ * | | SGN | SGN | SGN | Message signed
+ * |!| SGN | SGN | SGN FAIL | Message failed to be signed
+ * | | SGN | SGN | NREQ='node' | 'node' does not require signed messages
+ * | | SGN | SGN | 'sender'!='us' NUS | Will not sign because 'sender' is not 'us' (repeater)
+ * |!| SGN | VER | NSG | Message was not signed, but it should have been
+ * |!| SGN | VER | FAIL | Verification failed
+ * | | SGN | VER | OK | Verification succeeded
+ * | | SGN | VER | LEFT='number' | 'number' of failed verifications left in a row before node is locked
+ * | | SGN | SKP | MSG CMD='cmd',TYPE='type'| Message with command 'cmd' and type 'type' does not need to be signed
+ * | | SGN | SKP | ACK CMD='cmd',TYPE='type'| ACK messages does not need to be signed
+ * | | SGN | NCE | LEFT='number' | 'number' of nonce requests between successful verifications left before node is locked
+ * | | SGN | NCE | XMT,TO='node' | Nonce data transmitted to 'node'
+ * |!| SGN | NCE | XMT,TO='node' FAIL | Nonce data not properly transmitted to 'node'
+ * |!| SGN | NCE | GEN | Failed to generate nonce
+ * | | SGN | NCE | NSUP (DROPPED) | Ignored nonce/request for nonce (signing not supported)
+ * | | SGN | NCE | FROM='node' | Received nonce from 'node'
+ * | | SGN | NCE | 'sender'!='dst' (DROPPED)| Ignoring nonce as it did not come from the desgination of the message to sign
+ * |!| SGN | BND | INIT FAIL | Failed to initialize signing backend
+ * |!| SGN | BND | PWD<8 | Signing password too short
+ * |!| SGN | BND | PER | Backend not personalized
+ * |!| SGN | BND | SER | Could not get device unique serial from backend
+ * |!| SGN | BND | TMR | Backend timed out
+ * |!| SGN | BND | SIG,SIZE,'message'>'max' | Refusing to sign 'message' because it is bigger than 'max' allowed size
+ * | | SGN | BND | SIG WHI,ID='id' | Salting message with our 'id'
+ * | | SGN | BND | SIG WHI,SERIAL='serial' | Salting message with our 'serial'
+ * |!| SGN | BND | VER ONGOING | Verification failed, no ongoing session
+ * |!| SGN | BND | VER,IDENT='identifier' | Verification failed, 'identifier' is unknown
+ * | | SGN | BND | VER WHI,ID='sender' | 'sender' found in whitelist
+ * | | SGN | BND | VER WHI,SERIAL='serial' | Expecting 'serial' for this sender
+ * |!| SGN | BND | VER WHI,ID='id' MISSING | 'id' not found in whitelist
+ * | | SGN | BND | NONCE='nonce' | Calculating signature using 'nonce'
+ * | | SGN | BND | HMAC='hmac' | Calculated signature is 'hmac'
+ */
+/** @}*/
+
+/**
+ * @defgroup MySigningTroubleshootinggrp Signing troubleshooting
* @ingroup MySigninggrp
* @{
*
diff --git a/core/MySigningAtsha204.cpp b/core/MySigningAtsha204.cpp
index c013df5..1734bde 100644
--- a/core/MySigningAtsha204.cpp
+++ b/core/MySigningAtsha204.cpp
@@ -1,4 +1,4 @@
-/**
+/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
@@ -27,34 +27,57 @@
#include "MySigning.h"
+#ifdef MY_SIGNING_ATSHA204
#define SIGNING_IDENTIFIER (1) //HMAC-SHA256
#if defined(MY_DEBUG_VERBOSE_SIGNING)
#define SIGN_DEBUG(x,...) hwDebugPrint(x, ##__VA_ARGS__)
+static char printStr[65];
+static char i2h(uint8_t i)
+{
+ uint8_t k = i & 0x0F;
+ if (k <= 9) {
+ return '0' + k;
+ } else {
+ return 'A' + k - 10;
+ }
+}
+
+static void buf2str(const uint8_t* buf, size_t sz)
+{
+ uint8_t i;
+ if (sz > 32) {
+ sz = 32; //clamp to 32 bytes
+ }
+ for (i = 0; i < sz; i++) {
+ printStr[i * 2] = i2h(buf[i] >> 4);
+ printStr[(i * 2) + 1] = i2h(buf[i]);
+ }
+ printStr[sz * 2] = '\0';
+}
#else
#define SIGN_DEBUG(x,...)
#endif
-// Define MY_DEBUG_VERBOSE_SIGNING in your sketch to enable signing backend debugprints
-
-unsigned long _signing_timestamp;
-bool _signing_verification_ongoing = false;
-uint8_t _signing_verifying_nonce[NONCE_NUMIN_SIZE_PASSTHROUGH+SHA204_SERIAL_SZ+1];
-uint8_t _signing_signing_nonce[NONCE_NUMIN_SIZE_PASSTHROUGH+SHA204_SERIAL_SZ+1];
-uint8_t _signing_temp_message[SHA_MSG_SIZE];
-uint8_t _signing_rx_buffer[SHA204_RSP_SIZE_MAX];
-uint8_t _signing_tx_buffer[SHA204_CMD_SIZE_MAX];
-extern uint8_t _doWhitelist[32];
-
+static unsigned long _signing_timestamp;
+static bool _signing_verification_ongoing = false;
+static uint8_t _signing_verifying_nonce[32+9+1];
+static uint8_t _signing_signing_nonce[32+9+1];
+static uint8_t _signing_temp_message[SHA_MSG_SIZE];
+static uint8_t _signing_rx_buffer[SHA204_RSP_SIZE_MAX];
+static uint8_t _signing_tx_buffer[SHA204_CMD_SIZE_MAX];
+static uint8_t* const _signing_hmac = &_signing_rx_buffer[SHA204_BUFFER_POS_DATA];
+static uint8_t _signing_node_serial_info[9];
#ifdef MY_SIGNING_NODE_WHITELISTING
-const whitelist_entry_t _signing_whitelist[] = MY_SIGNING_NODE_WHITELISTING;
+static const whitelist_entry_t _signing_whitelist[] = MY_SIGNING_NODE_WHITELISTING;
#endif
-static void signerCalculateSignature(MyMessage &msg, bool signing);
-static uint8_t* signerSha256(const uint8_t* data, size_t sz);
-
static bool init_ok = false;
+static void signerCalculateSignature(MyMessage &msg, bool signing);
+static uint8_t* signerAtsha204AHmac(const uint8_t* nonce, const uint8_t* data);
+static uint8_t* signerSha256(const uint8_t* data, size_t sz);
+
bool signerAtsha204Init(void)
{
init_ok = true;
@@ -64,28 +87,41 @@ bool signerAtsha204Init(void)
// Read the configuration lock flag to determine if device is personalized or not
if (atsha204_read(_signing_tx_buffer, _signing_rx_buffer,
SHA204_ZONE_CONFIG, 0x15<<2) != SHA204_SUCCESS) {
- SIGN_DEBUG(PSTR("Could not read ATSHA204A lock config, refusing to use backend\n"));
+ SIGN_DEBUG(PSTR("!SGN:BND:INIT FAIL\n")); //Could not read ATSHA204A lock config
init_ok = false;
} else if (_signing_rx_buffer[SHA204_BUFFER_POS_DATA+3] != 0x00) {
- SIGN_DEBUG(PSTR("ATSHA204A not personalized, refusing to use backend\n"));
+ SIGN_DEBUG(PSTR("!SGN:BND:PER\n")); //ATSHA204A not personalized
init_ok = false;
}
+ if (init_ok) {
+ // Get and cache the serial of the ATSHA204A
+ if (atsha204_getSerialNumber(_signing_node_serial_info) != SHA204_SUCCESS) {
+ SIGN_DEBUG(PSTR("!SGN:BND:SER\n")); //Could not get ATSHA204A serial
+ init_ok = false;
+ }
+ }
return init_ok;
}
bool signerAtsha204CheckTimer(void)
{
if (!init_ok) {
- SIGN_DEBUG(PSTR("Backend not initialized\n"));
return false;
}
if (_signing_verification_ongoing) {
- if (hwMillis() < _signing_timestamp ||
- hwMillis() > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) {
- SIGN_DEBUG(PSTR("Verification timeout\n"));
+ unsigned long time_now = hwMillis();
+ // If timestamp is taken so late a rollover can take place during the timeout,
+ // offset both timestamp and current time to make sure no rollover takes place during the
+ // timeout
+ if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < _signing_timestamp) {
+ _signing_timestamp += MY_VERIFICATION_TIMEOUT_MS;
+ time_now += MY_VERIFICATION_TIMEOUT_MS;
+ }
+ if (time_now > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) {
+ SIGN_DEBUG(PSTR("!SGN:BND:TMR\n")); //Verification timeout
// Purge nonce
- memset(_signing_signing_nonce, 0x00, NONCE_NUMIN_SIZE_PASSTHROUGH);
- memset(_signing_verifying_nonce, 0x00, NONCE_NUMIN_SIZE_PASSTHROUGH);
+ memset(_signing_signing_nonce, 0xAA, 32);
+ memset(_signing_verifying_nonce, 0xAA, 32);
_signing_verification_ongoing = false;
return false;
}
@@ -96,60 +132,57 @@ bool signerAtsha204CheckTimer(void)
bool signerAtsha204GetNonce(MyMessage &msg)
{
if (!init_ok) {
- SIGN_DEBUG(PSTR("Backend not initialized\n"));
return false;
}
- SIGN_DEBUG(PSTR("Signing backend: ATSHA204\n"));
- // Generate random number for use as nonce
- // We used a basic whitening technique that XORs each byte in a 32byte random value with current hwMillis() counter
- // This 32-byte random value is then hashed (SHA256) to produce the resulting nonce
+
+ // We used a basic whitening technique that XORs each byte in a 32byte random value with current
+ // hwMillis() counter. This 32-byte random value is then hashed (SHA256) to produce the resulting
+ // nonce
(void)atsha204_wakeup(_signing_temp_message);
if (atsha204_execute(SHA204_RANDOM, RANDOM_SEED_UPDATE, 0, 0, NULL,
- RANDOM_COUNT, _signing_tx_buffer, RANDOM_RSP_SIZE, _signing_rx_buffer) != SHA204_SUCCESS) {
- SIGN_DEBUG(PSTR("Failed to generate nonce\n"));
+ RANDOM_COUNT, _signing_tx_buffer, RANDOM_RSP_SIZE, _signing_rx_buffer) !=
+ SHA204_SUCCESS) {
return false;
}
for (int i = 0; i < 32; i++) {
_signing_verifying_nonce[i] = _signing_rx_buffer[SHA204_BUFFER_POS_DATA+i] ^ (hwMillis()&0xFF);
}
- memcpy(_signing_verifying_nonce, signerSha256(_signing_verifying_nonce, 32), MAX_PAYLOAD);
+ memcpy(_signing_verifying_nonce, signerSha256(_signing_verifying_nonce, 32),
+ min(MAX_PAYLOAD, 32));
- atsha204_idle(); // We just idle the chip now since we expect to use it soon when the signed message arrives
+ // We just idle the chip now since we expect to use it soon when the signed message arrives
+ atsha204_idle();
- // We set the part of the 32-byte nonce that does not fit into a message to 0xAA
- memset(&_signing_verifying_nonce[MAX_PAYLOAD], 0xAA, sizeof(_signing_verifying_nonce)-MAX_PAYLOAD);
+ if (MAX_PAYLOAD < 32) {
+ // We set the part of the 32-byte nonce that does not fit into a message to 0xAA
+ memset(&_signing_verifying_nonce[MAX_PAYLOAD], 0xAA, 32-MAX_PAYLOAD);
+ }
// Transfer the first part of the nonce to the message
- msg.set(_signing_verifying_nonce, MAX_PAYLOAD);
+ msg.set(_signing_verifying_nonce, min(MAX_PAYLOAD, 32));
_signing_verification_ongoing = true;
_signing_timestamp = hwMillis(); // Set timestamp to determine when to purge nonce
- // Be a little fancy to handle turnover (prolong the time allowed to timeout after turnover)
- // Note that if message is "too" quick, and arrives before turnover, it will be rejected
- // but this is consider such a rare case that it is accepted and rejects are 'safe'
- if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < hwMillis()) {
- _signing_timestamp = 0;
- }
return true;
}
void signerAtsha204PutNonce(MyMessage &msg)
{
if (!init_ok) {
- SIGN_DEBUG(PSTR("Backend not initialized\n"));
return;
}
- SIGN_DEBUG(PSTR("Signing backend: ATSHA204\n"));
- memcpy(_signing_signing_nonce, (uint8_t*)msg.getCustom(), MAX_PAYLOAD);
- // We set the part of the 32-byte nonce that does not fit into a message to 0xAA
- memset(&_signing_signing_nonce[MAX_PAYLOAD], 0xAA, sizeof(_signing_signing_nonce)-MAX_PAYLOAD);
+ memcpy(_signing_signing_nonce, (uint8_t*)msg.getCustom(), min(MAX_PAYLOAD, 32));
+ if (MAX_PAYLOAD < 32) {
+ // We set the part of the 32-byte nonce that does not fit into a message to 0xAA
+ memset(&_signing_signing_nonce[MAX_PAYLOAD], 0xAA, 32-MAX_PAYLOAD);
+ }
}
bool signerAtsha204SignMsg(MyMessage &msg)
{
// If we cannot fit any signature in the message, refuse to sign it
if (mGetLength(msg) > MAX_PAYLOAD-2) {
- SIGN_DEBUG(PSTR("Message too large\n"));
+ SIGN_DEBUG(PSTR("!SGN:BND:SIG,SIZE,%d>%d\n"), mGetLength(msg), MAX_PAYLOAD-2); //Message too large
return false;
}
@@ -159,27 +192,27 @@ bool signerAtsha204SignMsg(MyMessage &msg)
if (DO_WHITELIST(msg.destination)) {
// Salt the signature with the senders nodeId and the unique serial of the ATSHA device
- memcpy(_signing_signing_nonce, &_signing_rx_buffer[SHA204_BUFFER_POS_DATA],
- 32); // We can reuse the nonce buffer now since it is no longer needed
+ // We can reuse the nonce buffer now since it is no longer needed
+ memcpy(_signing_signing_nonce, _signing_hmac, 32);
_signing_signing_nonce[32] = msg.sender;
- atsha204_getSerialNumber(&_signing_signing_nonce[33]);
- (void)signerSha256(_signing_signing_nonce,
- 32+1+SHA204_SERIAL_SZ); // we can 'void' sha256 because the hash is already put in the correct place
- SIGN_DEBUG(PSTR("Signature salted with serial: %02X%02X%02X%02X%02X%02X%02X%02X%02X\n"),
- _signing_signing_nonce[33], _signing_signing_nonce[34], _signing_signing_nonce[35],
- _signing_signing_nonce[36], _signing_signing_nonce[37], _signing_signing_nonce[38],
- _signing_signing_nonce[39], _signing_signing_nonce[40], _signing_signing_nonce[41]);
+ memcpy(&_signing_signing_nonce[33], _signing_node_serial_info, 9);
+ // We can 'void' sha256 because the hash is already put in the correct place
+ (void)signerSha256(_signing_signing_nonce, 32+1+9);
+ SIGN_DEBUG(PSTR("SGN:BND:SIG WHI,ID=%d\n"), msg.sender);
+#ifdef MY_DEBUG_VERBOSE_SIGNING
+ buf2str(_signing_node_serial_info, 9);
+ SIGN_DEBUG(PSTR("SGN:BND:SIG WHI,SERIAL=%s\n"), printStr);
+#endif
}
// Put device back to sleep
atsha204_sleep();
// Overwrite the first byte in the signature with the signing identifier
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA] = SIGNING_IDENTIFIER;
+ _signing_hmac[0] = SIGNING_IDENTIFIER;
// Transfer as much signature data as the remaining space in the message permits
- memcpy(&msg.data[mGetLength(msg)], &_signing_rx_buffer[SHA204_BUFFER_POS_DATA],
- MAX_PAYLOAD-mGetLength(msg));
+ memcpy(&msg.data[mGetLength(msg)], _signing_hmac, min(MAX_PAYLOAD-mGetLength(msg), 32));
return true;
}
@@ -187,7 +220,7 @@ bool signerAtsha204SignMsg(MyMessage &msg)
bool signerAtsha204VerifyMsg(MyMessage &msg)
{
if (!_signing_verification_ongoing) {
- SIGN_DEBUG(PSTR("No active verification session\n"));
+ SIGN_DEBUG(PSTR("!SGN:BND:VER ONGOING\n"));
return false;
} else {
// Make sure we have not expired
@@ -198,7 +231,7 @@ bool signerAtsha204VerifyMsg(MyMessage &msg)
_signing_verification_ongoing = false;
if (msg.data[mGetLength(msg)] != SIGNING_IDENTIFIER) {
- SIGN_DEBUG(PSTR("Incorrect signing identifier\n"));
+ SIGN_DEBUG(PSTR("!SGN:BND:VER,IDENT=%d\n"), msg.data[mGetLength(msg)]);
return false;
}
@@ -209,18 +242,22 @@ bool signerAtsha204VerifyMsg(MyMessage &msg)
size_t j;
for (j=0; j < NUM_OF(_signing_whitelist); j++) {
if (_signing_whitelist[j].nodeId == msg.sender) {
- SIGN_DEBUG(PSTR("Sender found in whitelist\n"));
- memcpy(_signing_verifying_nonce, &_signing_rx_buffer[SHA204_BUFFER_POS_DATA],
- 32); // We can reuse the nonce buffer now since it is no longer needed
+ // We can reuse the nonce buffer now since it is no longer needed
+ memcpy(_signing_verifying_nonce, _signing_hmac, 32);
_signing_verifying_nonce[32] = msg.sender;
- memcpy(&_signing_verifying_nonce[33], _signing_whitelist[j].serial, SHA204_SERIAL_SZ);
- (void)signerSha256(_signing_verifying_nonce,
- 32+1+SHA204_SERIAL_SZ); // we can 'void' sha256 because the hash is already put in the correct place
+ memcpy(&_signing_verifying_nonce[33], _signing_whitelist[j].serial, 9);
+ // We can 'void' sha256 because the hash is already put in the correct place
+ (void)signerSha256(_signing_verifying_nonce, 32+1+9);
+ SIGN_DEBUG(PSTR("SGN:BND:VER WHI,ID=%d\n"), msg.sender);
+#ifdef MY_DEBUG_VERBOSE_SIGNING
+ buf2str(_signing_whitelist[j].serial, 9);
+ SIGN_DEBUG(PSTR("SGN:BND:VER WHI,SERIAL=%s\n"), printStr);
+#endif
break;
}
}
if (j == NUM_OF(_signing_whitelist)) {
- SIGN_DEBUG(PSTR("Sender not found in whitelist, message rejected!\n"));
+ SIGN_DEBUG(PSTR("!SGN:BND:VER WHI,ID=%d MISSING\n"), msg.sender);
// Put device back to sleep
atsha204_sleep();
return false;
@@ -231,134 +268,96 @@ bool signerAtsha204VerifyMsg(MyMessage &msg)
atsha204_sleep();
// Overwrite the first byte in the signature with the signing identifier
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA] = SIGNING_IDENTIFIER;
+ _signing_hmac[0] = SIGNING_IDENTIFIER;
// Compare the caluclated signature with the provided signature
- if (signerMemcmp(&msg.data[mGetLength(msg)], &_signing_rx_buffer[SHA204_BUFFER_POS_DATA],
- MAX_PAYLOAD-mGetLength(msg))) {
- SIGN_DEBUG(PSTR("Signature bad\n"));
-#ifdef MY_SIGNING_NODE_WHITELISTING
- SIGN_DEBUG(PSTR("Is the sender whitelisted and serial correct?\n"));
-#endif
+ if (signerMemcmp(&msg.data[mGetLength(msg)], _signing_hmac,
+ min(MAX_PAYLOAD-mGetLength(msg), 32))) {
return false;
} else {
- SIGN_DEBUG(PSTR("Signature OK\n"));
return true;
}
}
}
// Helper to calculate signature of msg (returned in _signing_rx_buffer[SHA204_BUFFER_POS_DATA])
+// (=_signing_hmac)
static void signerCalculateSignature(MyMessage &msg, bool signing)
{
- (void)atsha204_wakeup(_signing_temp_message);
- memset(_signing_temp_message, 0, 32);
- memcpy(_signing_temp_message, (uint8_t*)&msg.data[1-HEADER_SIZE],
- MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg)));
+ // Signature is calculated on everything expect the first byte in the header
+ uint16_t bytes_left = mGetLength(msg)+HEADER_SIZE-1;
+ int16_t current_pos = 1-(int16_t)HEADER_SIZE; // Start at the second byte in the header
+ uint8_t* nonce = signing ? _signing_signing_nonce : _signing_verifying_nonce;
+#ifdef MY_DEBUG_VERBOSE_SIGNING
+ buf2str(nonce, 32);
+ SIGN_DEBUG(PSTR("SGN:BND:NONCE=%s\n"), printStr);
+#endif
+
+ while (bytes_left) {
+ uint16_t bytes_to_include = min(bytes_left, 32);
+
+ (void)atsha204_wakeup(_signing_temp_message); // Issue wakeup to reset watchdog
+ memset(_signing_temp_message, 0, 32);
+ memcpy(_signing_temp_message, (uint8_t*)&msg.data[current_pos], bytes_to_include);
+
+ // We can 'void' signerAtsha204AHmac because the HMAC is already put in the correct place
+ (void)signerAtsha204AHmac(nonce, _signing_temp_message);
+ // Purge nonce when used
+ memset(nonce, 0xAA, 32);
+
+ bytes_left -= bytes_to_include;
+ current_pos += bytes_to_include;
+
+ if (bytes_left > 0) {
+ // We will do another pass, use current HMAC as nonce for the next HMAC
+ memcpy(nonce, _signing_hmac, 32);
+ atsha204_idle(); // Idle the chip to allow the wakeup call to reset the watchdog
+ }
+ }
+#ifdef MY_DEBUG_VERBOSE_SIGNING
+ buf2str(_signing_hmac, 32);
+ SIGN_DEBUG(PSTR("SGN:BND:HMAC=%s\n"), printStr);
+#endif
+}
+
+// Helper to calculate a ATSHA204A specific HMAC-SHA256 using provided 32 byte nonce and data
+// (zero padded to 32 bytes)
+// The pointer to the HMAC is returned, but the HMAC is also stored in
+// _signing_rx_buffer[SHA204_BUFFER_POS_DATA] (=_signing_hmac)
+static uint8_t* signerAtsha204AHmac(const uint8_t* nonce, const uint8_t* data)
+{
// Program the data to sign into the ATSHA204
- SIGN_DEBUG(PSTR("Current nonce: "
- "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X"
- "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\n"),
- signing ? _signing_signing_nonce[0] : _signing_verifying_nonce[0],
- signing ? _signing_signing_nonce[1] : _signing_verifying_nonce[1],
- signing ? _signing_signing_nonce[2] : _signing_verifying_nonce[2],
- signing ? _signing_signing_nonce[3] : _signing_verifying_nonce[3],
- signing ? _signing_signing_nonce[4] : _signing_verifying_nonce[4],
- signing ? _signing_signing_nonce[5] : _signing_verifying_nonce[5],
- signing ? _signing_signing_nonce[6] : _signing_verifying_nonce[6],
- signing ? _signing_signing_nonce[7] : _signing_verifying_nonce[7],
- signing ? _signing_signing_nonce[8] : _signing_verifying_nonce[8],
- signing ? _signing_signing_nonce[9] : _signing_verifying_nonce[9],
- signing ? _signing_signing_nonce[10] : _signing_verifying_nonce[10],
- signing ? _signing_signing_nonce[11] : _signing_verifying_nonce[11],
- signing ? _signing_signing_nonce[12] : _signing_verifying_nonce[12],
- signing ? _signing_signing_nonce[13] : _signing_verifying_nonce[13],
- signing ? _signing_signing_nonce[14] : _signing_verifying_nonce[14],
- signing ? _signing_signing_nonce[15] : _signing_verifying_nonce[15],
- signing ? _signing_signing_nonce[16] : _signing_verifying_nonce[16],
- signing ? _signing_signing_nonce[17] : _signing_verifying_nonce[17],
- signing ? _signing_signing_nonce[18] : _signing_verifying_nonce[18],
- signing ? _signing_signing_nonce[19] : _signing_verifying_nonce[19],
- signing ? _signing_signing_nonce[20] : _signing_verifying_nonce[20],
- signing ? _signing_signing_nonce[21] : _signing_verifying_nonce[21],
- signing ? _signing_signing_nonce[22] : _signing_verifying_nonce[22],
- signing ? _signing_signing_nonce[23] : _signing_verifying_nonce[23],
- signing ? _signing_signing_nonce[24] : _signing_verifying_nonce[24],
- signing ? _signing_signing_nonce[25] : _signing_verifying_nonce[25],
- signing ? _signing_signing_nonce[26] : _signing_verifying_nonce[26],
- signing ? _signing_signing_nonce[27] : _signing_verifying_nonce[27],
- signing ? _signing_signing_nonce[28] : _signing_verifying_nonce[28],
- signing ? _signing_signing_nonce[29] : _signing_verifying_nonce[29],
- signing ? _signing_signing_nonce[30] : _signing_verifying_nonce[30],
- signing ? _signing_signing_nonce[31] : _signing_verifying_nonce[31]
- );
(void)atsha204_execute(SHA204_WRITE, SHA204_ZONE_DATA | SHA204_ZONE_COUNT_FLAG, 8 << 3, 32,
- _signing_temp_message,
+ (uint8_t*)data,
WRITE_COUNT_LONG, _signing_tx_buffer, WRITE_RSP_SIZE, _signing_rx_buffer);
- // Program the nonce to use for the signature (has to be done just before GENDIG due to chip limitations)
- (void)atsha204_execute(SHA204_NONCE, NONCE_MODE_PASSTHROUGH, 0, NONCE_NUMIN_SIZE_PASSTHROUGH,
- signing ? _signing_signing_nonce : _signing_verifying_nonce,
- NONCE_COUNT_LONG, _signing_tx_buffer, NONCE_RSP_SIZE_SHORT, _signing_rx_buffer);
-
- // Purge nonce when used
- memset(signing ? _signing_signing_nonce : _signing_verifying_nonce, 0x00,
- NONCE_NUMIN_SIZE_PASSTHROUGH);
+ // Program the nonce to use for the signature (has to be done just before GENDIG
+ // due to chip limitations)
+ (void)atsha204_execute(SHA204_NONCE, NONCE_MODE_PASSTHROUGH, 0, 32, (uint8_t*)nonce,
+ NONCE_COUNT_LONG, _signing_tx_buffer, NONCE_RSP_SIZE_SHORT,
+ _signing_rx_buffer);
// Generate digest of data and nonce
(void)atsha204_execute(SHA204_GENDIG, GENDIG_ZONE_DATA, 8, 0, NULL,
- GENDIG_COUNT_DATA, _signing_tx_buffer, GENDIG_RSP_SIZE, _signing_rx_buffer);
+ GENDIG_COUNT_DATA, _signing_tx_buffer, GENDIG_RSP_SIZE,
+ _signing_rx_buffer);
// Calculate HMAC of message+nonce digest and secret key
(void)atsha204_execute(SHA204_HMAC, HMAC_MODE_SOURCE_FLAG_MATCH, 0, 0, NULL,
HMAC_COUNT, _signing_tx_buffer, HMAC_RSP_SIZE, _signing_rx_buffer);
-
- SIGN_DEBUG(PSTR("HMAC: "
- "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X"
- "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\n"),
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+0],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+1],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+2],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+3],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+4],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+5],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+6],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+7],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+8],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+9],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+10],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+11],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+12],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+13],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+14],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+15],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+16],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+17],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+18],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+19],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+20],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+21],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+22],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+23],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+24],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+25],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+26],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+27],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+28],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+29],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+30],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+31]
- );
+ return &_signing_rx_buffer[SHA204_BUFFER_POS_DATA];
}
// Helper to calculate a generic SHA256 digest of provided buffer (only supports one block)
-// The pointer to the hash is returned, but the hash is also stored in _signing_rx_buffer[SHA204_BUFFER_POS_DATA])
+// The pointer to the hash is returned, but the hash is also stored in
+// _signing_rx_buffer[SHA204_BUFFER_POS_DATA])
static uint8_t* signerSha256(const uint8_t* data, size_t sz)
{
// Initiate SHA256 calculator
(void)atsha204_execute(SHA204_SHA, SHA_INIT, 0, 0, NULL,
- SHA_COUNT_SHORT, _signing_tx_buffer, SHA_RSP_SIZE_SHORT, _signing_rx_buffer);
+ SHA_COUNT_SHORT, _signing_tx_buffer, SHA_RSP_SIZE_SHORT,
+ _signing_rx_buffer);
// Calculate a hash
memset(_signing_temp_message, 0x00, SHA_MSG_SIZE);
@@ -369,42 +368,6 @@ static uint8_t* signerSha256(const uint8_t* data, size_t sz)
_signing_temp_message[SHA_MSG_SIZE-1] = (sz << 3);
(void)atsha204_execute(SHA204_SHA, SHA_CALC, 0, SHA_MSG_SIZE, _signing_temp_message,
SHA_COUNT_LONG, _signing_tx_buffer, SHA_RSP_SIZE_LONG, _signing_rx_buffer);
-
- SIGN_DEBUG(PSTR("SHA256: "
- "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X"
- "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\n"),
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+0],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+1],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+2],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+3],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+4],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+5],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+6],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+7],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+8],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+9],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+10],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+11],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+12],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+13],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+14],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+15],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+16],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+17],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+18],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+19],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+20],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+21],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+22],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+23],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+24],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+25],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+26],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+27],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+28],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+29],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+30],
- _signing_rx_buffer[SHA204_BUFFER_POS_DATA+31]
- );
return &_signing_rx_buffer[SHA204_BUFFER_POS_DATA];
}
+#endif //MY_SIGNING_ATSHA204
diff --git a/core/MySigningAtsha204Soft.cpp b/core/MySigningAtsha204Soft.cpp
index 2a4d064..508ea90 100644
--- a/core/MySigningAtsha204Soft.cpp
+++ b/core/MySigningAtsha204Soft.cpp
@@ -1,4 +1,4 @@
-/**
+/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
@@ -23,44 +23,66 @@
* ATSHA204 emulated signing backend. The emulated ATSHA204 implementation offers pseudo random
* number generation and HMAC-SHA256 authentication compatible with a "physical" ATSHA204.
* NOTE: Key is stored in clear text in the Arduino firmware. Therefore, the use of this back-end
- * could compromise the key used in the signed message infrastructure if device is lost and its memory
- * dumped.
+ * could compromise the key used in the signed message infrastructure if device is lost and its
+ * memory dumped.
*
*/
#include "MySigning.h"
-#include "drivers/ATSHA204/sha256.h"
+#ifdef MY_SIGNING_SOFT
+#include "drivers/ATSHA204/sha256.h"
#define SIGNING_IDENTIFIER (1) //HMAC-SHA256
#if defined(MY_DEBUG_VERBOSE_SIGNING)
#define SIGN_DEBUG(x,...) hwDebugPrint(x, ##__VA_ARGS__)
+static char printStr[65];
+static char i2h(uint8_t i)
+{
+ uint8_t k = i & 0x0F;
+ if (k <= 9) {
+ return '0' + k;
+ } else {
+ return 'A' + k - 10;
+ }
+}
+
+static void buf2str(const uint8_t* buf, size_t sz)
+{
+ uint8_t i;
+ if (sz > 32) {
+ sz = 32; //clamp to 32 bytes
+ }
+ for (i = 0; i < sz; i++) {
+ printStr[i * 2] = i2h(buf[i] >> 4);
+ printStr[(i * 2) + 1] = i2h(buf[i]);
+ }
+ printStr[sz * 2] = '\0';
+}
#else
#define SIGN_DEBUG(x,...)
#endif
-// Define MY_DEBUG_VERBOSE_SIGNING in your sketch to enable signing backend debugprints
-
Sha256Class _signing_sha256;
-unsigned long _signing_timestamp;
-bool _signing_verification_ongoing = false;
-uint8_t _signing_verifying_nonce[32];
-uint8_t _signing_signing_nonce[32];
-uint8_t _signing_temp_message[32];
+static unsigned long _signing_timestamp;
+static bool _signing_verification_ongoing = false;
+static uint8_t _signing_verifying_nonce[32+9+1];
+static uint8_t _signing_signing_nonce[32+9+1];
+static uint8_t _signing_temp_message[32];
static uint8_t _signing_hmac_key[32];
-uint8_t _signing_hmac[32];
-extern uint8_t _doWhitelist[32];
-
+static uint8_t _signing_hmac[32];
static uint8_t _signing_node_serial_info[9];
#ifdef MY_SIGNING_NODE_WHITELISTING
-const whitelist_entry_t _signing_whitelist[] = MY_SIGNING_NODE_WHITELISTING;
+static const whitelist_entry_t _signing_whitelist[] = MY_SIGNING_NODE_WHITELISTING;
#endif
-static void signerCalculateSignature(MyMessage &msg, bool signing);
-
static bool init_ok = false;
static const uint8_t reset_serial[9] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+static void signerCalculateSignature(MyMessage &msg, bool signing);
+static uint8_t* signerAtsha204AHmac(const uint8_t* nonce, const uint8_t* data);
+static uint8_t* signerSha256(const uint8_t* data, size_t sz);
+
bool signerAtsha204SoftInit(void)
{
init_ok = true;
@@ -68,8 +90,22 @@ bool signerAtsha204SoftInit(void)
// initialize pseudo-RNG
hwRandomNumberInit();
// Set secrets
+#ifdef MY_SIGNING_SIMPLE_PASSWD
+ if (strnlen(MY_SIGNING_SIMPLE_PASSWD, 32) < 8) {
+ SIGN_DEBUG(PSTR("!SGN:BND:PWD<8\n")); //Password is too short to be acceptable
+ init_ok = false;
+ } else {
+ memset(_signing_hmac_key, 0, 32);
+ memcpy(_signing_hmac_key, MY_SIGNING_SIMPLE_PASSWD, strnlen(MY_SIGNING_SIMPLE_PASSWD, 32));
+ memset(_signing_node_serial_info, 0, 9);
+ memcpy(_signing_node_serial_info, MY_SIGNING_SIMPLE_PASSWD,
+ strnlen(MY_SIGNING_SIMPLE_PASSWD, 8));
+ _signing_node_serial_info[8] = getNodeId();
+ }
+#else
hwReadConfigBlock((void*)_signing_hmac_key, (void*)EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS, 32);
hwReadConfigBlock((void*)_signing_node_serial_info, (void*)EEPROM_SIGNING_SOFT_SERIAL_ADDRESS, 9);
+#endif
if (!memcmp(_signing_node_serial_info, reset_serial, 9)) {
unique_id_t uniqueID;
// There is no serial, attempt to get unique ID for serial instead
@@ -84,13 +120,19 @@ bool signerAtsha204SoftInit(void)
bool signerAtsha204SoftCheckTimer(void)
{
if (!init_ok) {
- SIGN_DEBUG(PSTR("Backend not initialized\n"));
return false;
}
if (_signing_verification_ongoing) {
- if (hwMillis() < _signing_timestamp ||
- hwMillis() > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) {
- SIGN_DEBUG(PSTR("Verification timeout\n"));
+ unsigned long time_now = hwMillis();
+ // If timestamp is taken so late a rollover can take place during the timeout,
+ // offset both timestamp and current time to make sure no rollover takes place during the
+ // timeout
+ if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < _signing_timestamp) {
+ _signing_timestamp += MY_VERIFICATION_TIMEOUT_MS;
+ time_now += MY_VERIFICATION_TIMEOUT_MS;
+ }
+ if (time_now > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) {
+ SIGN_DEBUG(PSTR("!SGN:BND:TMR\n")); //Verification timeout
// Purge nonce
memset(_signing_signing_nonce, 0xAA, 32);
memset(_signing_verifying_nonce, 0xAA, 32);
@@ -104,29 +146,29 @@ bool signerAtsha204SoftCheckTimer(void)
bool signerAtsha204SoftGetNonce(MyMessage &msg)
{
if (!init_ok) {
- SIGN_DEBUG(PSTR("Backend not initialized\n"));
return false;
}
- SIGN_DEBUG(PSTR("Signing backend: ATSHA204Soft\n"));
#ifdef MY_HW_HAS_GETRANDOM
- // Try to get MAX_PAYLOAD random bytes
- while (hwGetentropy(&_signing_verifying_nonce, MAX_PAYLOAD) != MAX_PAYLOAD);
+ // Try to get MAX_PAYLOAD (or 32) random bytes
+ while (hwGetentropy(&_signing_verifying_nonce, MIN(MAX_PAYLOAD, 32)) != MIN(MAX_PAYLOAD, 32));
#else
- // We used a basic whitening technique that XORs a random byte with the current hwMillis() counter and then the byte is
- // hashed (SHA256) to produce the resulting nonce
+ // We used a basic whitening technique that XORs a random byte with the current hwMillis() counter
+ // and then the byte is hashed (SHA256) to produce the resulting nonce
_signing_sha256.init();
for (int i = 0; i < 32; i++) {
_signing_sha256.write(random(256) ^ (hwMillis()&0xFF));
}
- memcpy(_signing_verifying_nonce, _signing_sha256.result(), MAX_PAYLOAD);
+ memcpy(_signing_verifying_nonce, _signing_sha256.result(), MIN(MAX_PAYLOAD, 32));
#endif
- // We set the part of the 32-byte nonce that does not fit into a message to 0xAA
- memset(&_signing_verifying_nonce[MAX_PAYLOAD], 0xAA, sizeof(_signing_verifying_nonce)-MAX_PAYLOAD);
+ if (MAX_PAYLOAD < 32) {
+ // We set the part of the 32-byte nonce that does not fit into a message to 0xAA
+ memset(&_signing_verifying_nonce[MAX_PAYLOAD], 0xAA, 32-MAX_PAYLOAD);
+ }
// Transfer the first part of the nonce to the message
- msg.set(_signing_verifying_nonce, MAX_PAYLOAD);
+ msg.set(_signing_verifying_nonce, MIN(MAX_PAYLOAD, 32));
_signing_verification_ongoing = true;
_signing_timestamp = hwMillis(); // Set timestamp to determine when to purge nonce
// Be a little fancy to handle turnover (prolong the time allowed to timeout after turnover)
@@ -141,20 +183,21 @@ bool signerAtsha204SoftGetNonce(MyMessage &msg)
void signerAtsha204SoftPutNonce(MyMessage &msg)
{
if (!init_ok) {
- SIGN_DEBUG(PSTR("Backend not initialized\n"));
+ return;
}
- SIGN_DEBUG(PSTR("Signing backend: ATSHA204Soft\n"));
- memcpy(_signing_signing_nonce, (uint8_t*)msg.getCustom(), MAX_PAYLOAD);
- // We set the part of the 32-byte nonce that does not fit into a message to 0xAA
- memset(&_signing_signing_nonce[MAX_PAYLOAD], 0xAA, sizeof(_signing_signing_nonce)-MAX_PAYLOAD);
+ memcpy(_signing_signing_nonce, (uint8_t*)msg.getCustom(), MIN(MAX_PAYLOAD, 32));
+ if (MAX_PAYLOAD < 32) {
+ // We set the part of the 32-byte nonce that does not fit into a message to 0xAA
+ memset(&_signing_signing_nonce[MAX_PAYLOAD], 0xAA, 32-MAX_PAYLOAD);
+ }
}
bool signerAtsha204SoftSignMsg(MyMessage &msg)
{
// If we cannot fit any signature in the message, refuse to sign it
if (mGetLength(msg) > MAX_PAYLOAD-2) {
- SIGN_DEBUG(PSTR("Message too large\n"));
+ SIGN_DEBUG(PSTR("!SGN:BND:SIG,SIZE,%d>%d\n"), mGetLength(msg), MAX_PAYLOAD-2); //Message too large
return false;
}
@@ -163,27 +206,24 @@ bool signerAtsha204SoftSignMsg(MyMessage &msg)
signerCalculateSignature(msg, true);
if (DO_WHITELIST(msg.destination)) {
- // Salt the signature with the senders nodeId and the (hopefully) unique serial The Creator has provided
- _signing_sha256.init();
- for (int i=0; i<32; i++) {
- _signing_sha256.write(_signing_hmac[i]);
- }
- _signing_sha256.write(msg.sender);
- for (int i=0; i 0) {
+ // We will do another pass, use current HMAC as nonce for the next HMAC
+ memcpy(nonce, _signing_hmac, 32);
+ }
+ }
+#ifdef MY_DEBUG_VERBOSE_SIGNING
+ buf2str(_signing_hmac, 32);
+ SIGN_DEBUG(PSTR("SGN:BND:HMAC=%s\n"), printStr);
+#endif
+}
+
+// Helper to calculate a ATSHA204A specific HMAC-SHA256 using provided 32 byte nonce and data
+// (zero padded to 32 bytes)
+// The pointer to the HMAC is returned, but the HMAC is also stored in _signing_hmac
+static uint8_t* signerAtsha204AHmac(const uint8_t* nonce, const uint8_t* data)
+{
// ATSHA204 calculates the HMAC with a PSK and a SHA256 digest of the following data:
// 32 bytes zeroes
// 32 bytes digest,
@@ -317,7 +353,7 @@ static void signerCalculateSignature(MyMessage &msg, bool signing)
// Calculate message digest first
_signing_sha256.init();
for (int i=0; i<32; i++) {
- _signing_sha256.write(_signing_temp_message[i]);
+ _signing_sha256.write(data[i]);
}
_signing_sha256.write(0x15); // OPCODE
_signing_sha256.write(0x02); // param1
@@ -330,19 +366,17 @@ static void signerCalculateSignature(MyMessage &msg, bool signing)
_signing_sha256.write(0x00);
}
for (int i=0; i<32; i++) {
- _signing_sha256.write(signing ? _signing_signing_nonce[i] : _signing_verifying_nonce[i]);
+ _signing_sha256.write(nonce[i]);
}
- // Purge nonce when used
- memset(signing ? _signing_signing_nonce : _signing_verifying_nonce, 0xAA, 32);
- memcpy(_signing_temp_message, _signing_sha256.result(), 32);
+ memcpy(_signing_hmac, _signing_sha256.result(), 32);
// Feed "message" to HMAC calculator
- _signing_sha256.initHmac(_signing_hmac_key,32); // Set the key to use
+ _signing_sha256.initHmac(_signing_hmac_key, 32); // Set the key to use
for (int i=0; i<32; i++) {
_signing_sha256.write(0x00); // 32 bytes zeroes
}
for (int i=0; i<32; i++) {
- _signing_sha256.write(_signing_temp_message[i]); // 32 bytes digest
+ _signing_sha256.write(_signing_hmac[i]); // 32 bytes digest
}
_signing_sha256.write(0x11); // OPCODE
_signing_sha256.write(0x04); // Mode
@@ -362,16 +396,17 @@ static void signerCalculateSignature(MyMessage &msg, bool signing)
}
memcpy(_signing_hmac, _signing_sha256.resultHmac(), 32);
-
- SIGN_DEBUG(PSTR("HMAC: "
- "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X"
- "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\n"),
- _signing_hmac[0], _signing_hmac[1], _signing_hmac[2], _signing_hmac[3],
- _signing_hmac[4], _signing_hmac[5], _signing_hmac[6], _signing_hmac[7],
- _signing_hmac[8], _signing_hmac[9], _signing_hmac[10], _signing_hmac[11],
- _signing_hmac[12], _signing_hmac[13], _signing_hmac[14], _signing_hmac[15],
- _signing_hmac[16], _signing_hmac[17], _signing_hmac[18], _signing_hmac[19],
- _signing_hmac[20], _signing_hmac[21], _signing_hmac[22], _signing_hmac[23],
- _signing_hmac[24], _signing_hmac[25], _signing_hmac[26], _signing_hmac[27],
- _signing_hmac[28], _signing_hmac[29], _signing_hmac[30], _signing_hmac[31]);
+ return _signing_hmac;
}
+
+// Helper to calculate a generic SHA256 digest of provided buffer
+// The pointer to the hash is returned
+static uint8_t* signerSha256(const uint8_t* data, size_t sz)
+{
+ _signing_sha256.init();
+ for (size_t i=0; i" : _msg.getString(_convBuf)));
// Reject payloads with incorrect length
if (payloadLength != expectedMessageLength) {
@@ -993,7 +994,8 @@ bool transportSendWrite(const uint8_t to, MyMessage &message)
mGetPayloadType(message), mGetLength(message), mGetSigned(message),
_transportSM.failedUplinkTransmissions,
(result ? "OK" : "NACK"),
- message.getString(_convBuf));
+ ((mGetCommand(message) == C_INTERNAL &&
+ message.type == I_NONCE_RESPONSE) ? "" : message.getString(_convBuf)));
return result;
}
diff --git a/drivers/ATSHA204/ATSHA204.h b/drivers/ATSHA204/ATSHA204.h
index 76462f6..be27d5a 100644
--- a/drivers/ATSHA204/ATSHA204.h
+++ b/drivers/ATSHA204/ATSHA204.h
@@ -1,7 +1,7 @@
#ifndef ATSHA204_H
#define ATSHA204_H
#if !DOXYGEN
-#include "Arduino.h"
+#include
/* This is a scaled down variant of the ATSHA204 library, tweaked to meet the specific needs of the MySensors library. */
diff --git a/drivers/RFM69/new/RFM69_new.h b/drivers/RFM69/new/RFM69_new.h
index 5118b93..9bf2268 100644
--- a/drivers/RFM69/new/RFM69_new.h
+++ b/drivers/RFM69/new/RFM69_new.h
@@ -33,7 +33,7 @@
*/
/**
-* @file RFM69.h
+* @file RFM69_new.h
*
* @defgroup RFM69Newgrp RFM69New
* @ingroup internals
@@ -618,4 +618,4 @@ LOCAL void RFM69_listenModeGetDurations(uint32_t &rxDuration, uint32_t &idleDura
#endif
-/** @}*/
\ No newline at end of file
+/** @}*/
diff --git a/examples/SecureActuator/SecureActuator.ino b/examples/SecureActuator/SecureActuator.ino
index e730d46..ac4844f 100644
--- a/examples/SecureActuator/SecureActuator.ino
+++ b/examples/SecureActuator/SecureActuator.ino
@@ -63,7 +63,9 @@
#define MY_SIGNING_SOFT_RANDOMSEED_PIN 7 //!< Unconnected analog pin for random seed
// SETTINGS FOR MY_SIGNING_ATSHA204
+#ifndef MY_SIGNING_ATSHA204_PIN
#define MY_SIGNING_ATSHA204_PIN 17 //!< A3 - pin where ATSHA204 is attached
+#endif
#include
diff --git a/examples/SecurityPersonalizer/sha204_lib_return_codes.h b/examples/SecurityPersonalizer/sha204_lib_return_codes.h
index db61120..0aaefaa 100644
--- a/examples/SecurityPersonalizer/sha204_lib_return_codes.h
+++ b/examples/SecurityPersonalizer/sha204_lib_return_codes.h
@@ -24,9 +24,6 @@
#include // data type definitions
-/** \todo Use same values for same meanings for SHA204 and AES132.
- * */
-
#define SHA204_SUCCESS ((uint8_t) 0x00) //!< Function succeeded.
#define SHA204_PARSE_ERROR ((uint8_t) 0xD2) //!< response status byte indicates parsing error
#define SHA204_CMD_FAIL ((uint8_t) 0xD3) //!< response status byte indicates command execution error
diff --git a/examples_linux/mysgw.cpp b/examples_linux/mysgw.cpp
index be567f6..da5ba05 100644
--- a/examples_linux/mysgw.cpp
+++ b/examples_linux/mysgw.cpp
@@ -61,11 +61,11 @@
//#define MY_SIGNING_SOFT
// Enable signing related debug
//#define MY_DEBUG_VERBOSE_SIGNING
-// Enable this to request signatures from nodes that in turn request signatures from gateway
+// Enable this to request signatures
//#define MY_SIGNING_REQUEST_SIGNATURES
-// Enable this to have gateway require all nodes in the network to sign messages sent to it
-// Note: MY_SIGNING_REQUEST_SIGNATURES must also be set
-//#define MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL
+// Enable this to to weaken security for gradual deployment purpose
+// (see signing documentation for details)
+//#define MY_SIGNING_WEAK_SECURITY
// Enables RF24 encryption (all nodes and gateway must have this enabled, and all must be
// personalized with the same AES key)
diff --git a/hal/architecture/MyHwAVR.h b/hal/architecture/MyHwAVR.h
index 7eb640d..bf535c8 100644
--- a/hal/architecture/MyHwAVR.h
+++ b/hal/architecture/MyHwAVR.h
@@ -38,6 +38,9 @@
#define MY_SERIALDEVICE Serial
#endif
+#define MIN(a,b) min(a,b)
+#define MAX(a,b) max(a,b)
+
// Define these as macros to save valuable space
#define hwDigitalWrite(__pin, __value) digitalWriteFast(__pin, __value)
#define hwDigitalRead(__pin) digitalReadFast(__pin)
diff --git a/hal/architecture/MyHwESP8266.h b/hal/architecture/MyHwESP8266.h
index 2550f7a..2019b1d 100644
--- a/hal/architecture/MyHwESP8266.h
+++ b/hal/architecture/MyHwESP8266.h
@@ -31,6 +31,8 @@
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
+#define MIN(a,b) min(a,b)
+#define MAX(a,b) max(a,b)
#define EEPROM_size (1024)
diff --git a/hal/architecture/MyHwLinuxGeneric.h b/hal/architecture/MyHwLinuxGeneric.h
index ce89572..b2ca0b5 100644
--- a/hal/architecture/MyHwLinuxGeneric.h
+++ b/hal/architecture/MyHwLinuxGeneric.h
@@ -40,6 +40,9 @@ SerialSimulator Serial = SerialSimulator();
#define MY_SERIALDEVICE Serial
#endif
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#define MAX(a,b) ((a)>(b)?(a):(b))
+
// Define these as macros (do nothing)
#define hwWatchdogReset()
#define hwReboot()
diff --git a/hal/architecture/MyHwNRF5.h b/hal/architecture/MyHwNRF5.h
index 2ebba32..2349046 100644
--- a/hal/architecture/MyHwNRF5.h
+++ b/hal/architecture/MyHwNRF5.h
@@ -45,6 +45,9 @@
#define snprintf_P(s, f, ...) snprintf((s), (f), __VA_ARGS__)
#define printf_P printf
+#define MIN(a,b) min(a,b)
+#define MAX(a,b) max(a,b)
+
// Define these as macros to save valuable space
#define hwDigitalWrite(__pin, __value) digitalWrite(__pin, __value)
#define hwDigitalRead(__pin) digitalRead(__pin)
diff --git a/hal/architecture/MyHwSAMD.h b/hal/architecture/MyHwSAMD.h
index 5af36d6..a3ca0d8 100644
--- a/hal/architecture/MyHwSAMD.h
+++ b/hal/architecture/MyHwSAMD.h
@@ -39,6 +39,8 @@ extEEPROM eep(MY_EXT_EEPROM_SIZE, 1, MY_EXT_EEPROM_PAGE_SIZE,
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
+#define MIN(a,b) min(a,b)
+#define MAX(a,b) max(a,b)
#define snprintf_P(s, f, ...) snprintf((s), (f), __VA_ARGS__)
// Define these as macros to save valuable space
diff --git a/hal/architecture/MyHwSTM32F1.h b/hal/architecture/MyHwSTM32F1.h
index d63bfa1..4cb0906 100644
--- a/hal/architecture/MyHwSTM32F1.h
+++ b/hal/architecture/MyHwSTM32F1.h
@@ -42,6 +42,9 @@
#define MY_SERIALDEVICE Serial
#endif
+#define MIN(a,b) min(a,b)
+#define MAX(a,b) max(a,b)
+
#ifndef digitalPinToInterrupt
#define digitalPinToInterrupt(__pin) (__pin)
#endif
diff --git a/hal/architecture/MyHwTeensy3.h b/hal/architecture/MyHwTeensy3.h
index 529f603..3ff9ebc 100644
--- a/hal/architecture/MyHwTeensy3.h
+++ b/hal/architecture/MyHwTeensy3.h
@@ -50,6 +50,9 @@
#define SIM_SCGC6_RNGA ((uint32_t)0x00000200)
#endif
+#define MIN(a,b) min(a,b)
+#define MAX(a,b) max(a,b)
+
// Define these as macros to save valuable space
#define hwDigitalWrite(__pin, __value) digitalWriteFast(__pin, __value)
#define hwDigitalRead(__pin) digitalReadFast(__pin)
diff --git a/hal/architecture/MyMainLinux.cpp b/hal/architecture/MyMainLinux.cpp
index 764bdd6..9768923 100644
--- a/hal/architecture/MyMainLinux.cpp
+++ b/hal/architecture/MyMainLinux.cpp
@@ -273,7 +273,12 @@ void print_aes_key(uint8_t *key_ptr = NULL)
uint8_t key[16];
if (key_ptr == NULL) {
+#ifdef MY_SIGNING_SIMPLE_PASSWD
+ memset(key, 0, 16);
+ memcpy(key, MY_SIGNING_SIMPLE_PASSWD, strnlen(MY_SIGNING_SIMPLE_PASSWD, 16));
+#else
hwReadConfigBlock(&key, reinterpret_castEEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16);
+#endif
key_ptr = key;
}
diff --git a/hal/transport/MyTransportNRF5_ESB.cpp b/hal/transport/MyTransportNRF5_ESB.cpp
index e50fb62..93f1366 100644
--- a/hal/transport/MyTransportNRF5_ESB.cpp
+++ b/hal/transport/MyTransportNRF5_ESB.cpp
@@ -39,7 +39,12 @@ uint8_t _psk[16];
bool transportInit(void)
{
#if defined(MY_NRF5_ESB_ENABLE_ENCRYPTION)
- hwReadConfigBlock((void *)_psk, (void *)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16);
+#ifdef MY_SIGNING_SIMPLE_PASSWD
+ memset(_psk, 0, 16);
+ memcpy(_psk, MY_SIGNING_SIMPLE_PASSWD, strnlen(MY_SIGNING_SIMPLE_PASSWD, 16));
+#else
+ hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16);
+#endif
// set up AES-key
_aes.set_key(_psk, 16);
// Make sure it is purged from memory when set
diff --git a/hal/transport/MyTransportRF24.cpp b/hal/transport/MyTransportRF24.cpp
index ddebadc..10d6949 100644
--- a/hal/transport/MyTransportRF24.cpp
+++ b/hal/transport/MyTransportRF24.cpp
@@ -68,7 +68,12 @@ uint8_t _psk[16];
bool transportInit(void)
{
#if defined(MY_RF24_ENABLE_ENCRYPTION)
+#ifdef MY_SIGNING_SIMPLE_PASSWD
+ memset(_psk, 0, 16);
+ memcpy(_psk, MY_SIGNING_SIMPLE_PASSWD, strnlen(MY_SIGNING_SIMPLE_PASSWD, 16));
+#else
hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16);
+#endif
//set up AES-key
_aes.set_key(_psk, 16);
// Make sure it is purged from memory when set
diff --git a/hal/transport/MyTransportRFM69.cpp b/hal/transport/MyTransportRFM69.cpp
index d29aecb..39c5562 100644
--- a/hal/transport/MyTransportRFM69.cpp
+++ b/hal/transport/MyTransportRFM69.cpp
@@ -33,7 +33,12 @@ bool transportInit(void)
#ifdef MY_RFM69_ENABLE_ENCRYPTION
uint8_t _psk[16];
+#ifdef MY_SIGNING_SIMPLE_PASSWD
+ memset(_psk, 0, 16);
+ memcpy(_psk, MY_SIGNING_SIMPLE_PASSWD, strnlen(MY_SIGNING_SIMPLE_PASSWD, 16));
+#else
hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16);
+#endif
RFM69_encrypt((const char*)_psk);
(void)memset(_psk, 0, 16); // Make sure it is purged from memory when set
#endif
@@ -163,7 +168,12 @@ bool transportInit(void)
if (_radio.initialize(MY_RFM69_FREQUENCY, _address, MY_RFM69_NETWORKID)) {
#ifdef MY_RFM69_ENABLE_ENCRYPTION
uint8_t _psk[16];
+#ifdef MY_SIGNING_SIMPLE_PASSWD
+ memset(_psk, 0, 16);
+ memcpy(_psk, MY_SIGNING_SIMPLE_PASSWD, strnlen(MY_SIGNING_SIMPLE_PASSWD, 16));
+#else
hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16);
+#endif
_radio.encrypt((const char*)_psk);
(void)memset(_psk, 0, 16); // Make sure it is purged from memory when set
#endif
@@ -262,4 +272,4 @@ int16_t transportGetTxPowerLevel(void)
return INVALID_LEVEL;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/keywords.txt b/keywords.txt
index b20187d..ebc77f1 100644
--- a/keywords.txt
+++ b/keywords.txt
@@ -77,6 +77,7 @@ MY_INCLUSION_MODE_DURATION LITERAL1
# Signing
MY_DEBUG_VERBOSE_SIGNING LITERAL1
+MY_SIGNING_SIMPLE_PASSWD LITERAL1
MY_SIGNING_ATSHA204 LITERAL1
MY_SIGNING_SOFT LITERAL1
MY_VERIFICATION_TIMEOUT_MS LITERAL1
@@ -84,7 +85,7 @@ MY_SIGNING_NODE_WHITELISTING LITERAL1
MY_SIGNING_ATSHA204_PIN LITERAL1
MY_SIGNING_SOFT_RANDOMSEED_PIN LITERAL1
MY_SIGNING_REQUEST_SIGNATURES LITERAL1
-MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL LITERAL1
+MY_SIGNING_WEAK_SECURITY LITERAL1
MY_NODE_LOCK_FEATURE LITERAL1
MY_NODE_UNLOCK_PIN LITERAL1
MY_NODE_LOCK_COUNTER_MAX LITERAL1
diff --git a/projects/SublimeText/MySensors.sublime-project b/projects/SublimeText/MySensors.sublime-project
index 9891739..ea1ca45 100644
--- a/projects/SublimeText/MySensors.sublime-project
+++ b/projects/SublimeText/MySensors.sublime-project
@@ -19,7 +19,6 @@
// EasyClangComplete is a clang based plugin that provides autocompletion features.
// Installation instructions is available here: https://github.com/niosus/EasyClangComplete
- "ecc_errors_on_save": false, // disable clang error checking since it is hard to figure out the standard library locations and this gives many false positives
// MySensors core library uses some coding style rules. Artistic Style can be used to format the code according to these rules
// The below settings enable automatic formatting of the code upon saving in Sublime Text
@@ -55,7 +54,11 @@
"cppcheck":
{
"@disable": false,
- "args": ["--force", "--library=avr"],
+ "args": ["--force", "--library=avr",
+ "--suppressions-list=$project_path/../../.mystools/cppcheck/config/suppressions.cfg",
+ "-I$project_path/../../", "-I$project_path/../../core",
+ "-DMY_SIGNING_NODE_WHITELISTING",
+ "-DMY_SIGNING_SIMPLE_PASSWD"],
"enable": "style,information",
"excludes": [],
"std": []