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": []