Restructure, use HMAC, NTP
Remove config details
This commit is contained in:
55
include/crypto.h
Normal file
55
include/crypto.h
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "message.h"
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* @brief Create a message authentication code (MAC) for some data.
|
||||
*
|
||||
* @param data The data to authenticate
|
||||
* @param dataLength The number of bytes to authenticate
|
||||
* @param mac The output to store the MAC (must be at least 32 bytes)
|
||||
* @param key The secret key used for authentication
|
||||
* @param keyLength The length of the secret key
|
||||
* @return true The MAC was successfully written
|
||||
* @return false The MAC could not be created
|
||||
*/
|
||||
bool authenticateData(const uint8_t* data, size_t dataLength, uint8_t* mac, const uint8_t* key, size_t keyLength);
|
||||
|
||||
/**
|
||||
* @brief Calculate a MAC for message content.
|
||||
*
|
||||
* @param message The message for which to calculate the MAC.
|
||||
* @param mac The output where the computed MAC is stored
|
||||
* @param key The secret key used for authentication
|
||||
* @param keyLength The length of the secret key
|
||||
* @return true The MAC was successfully computed
|
||||
* @return false The MAC could not be created
|
||||
*/
|
||||
bool authenticateMessage(Message* message, uint8_t* mac, const uint8_t* key, size_t keyLength);
|
||||
|
||||
/**
|
||||
* @brief Create a message authentication code (MAC) for a message.
|
||||
*
|
||||
* @param message The message to authenticate
|
||||
* @param key The secret key used for authentication
|
||||
* @param keyLength The length of the secret key
|
||||
* @return true The MAC was successfully added to the message
|
||||
* @return false The MAC could not be created
|
||||
*/
|
||||
bool authenticateMessage(AuthenticatedMessage* message, const uint8_t* key, size_t keyLength);
|
||||
|
||||
/**
|
||||
* @brief Check if a received unlock message is authentic
|
||||
*
|
||||
* This function computes the MAC of the message and compares it with
|
||||
* the MAC included in the message. The message is authentic if both
|
||||
* MACs are identical.
|
||||
*
|
||||
* @param message The message to authenticate
|
||||
* @param key The secret key used for authentication
|
||||
* @param keyLength The length of the key in bytes
|
||||
* @return true The message is authentic
|
||||
* @return false The message is invalid, or the MAC could not be calculated
|
||||
*/
|
||||
bool isAuthenticMessage(AuthenticatedMessage* message, const uint8_t* key, size_t keyLength);
|
114
include/fresh.h
Normal file
114
include/fresh.h
Normal file
@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief The size of the message counter in bytes (uint32_t)
|
||||
*/
|
||||
#define MESSAGE_COUNTER_SIZE sizeof(uint32_t)
|
||||
|
||||
/**
|
||||
* @brief Configure an NTP server to get the current time
|
||||
*
|
||||
* @param offsetToGMT The timezone offset in seconds
|
||||
* @param offsetDaylightSavings The daylight savings offset in seconds
|
||||
* @param serverUrl The url of the NTP server
|
||||
*/
|
||||
void configureNTP(int32_t offsetToGMT, int32_t offsetDaylightSavings, const char* serverUrl);
|
||||
|
||||
/**
|
||||
* @brief Print the current time to the serial output
|
||||
*
|
||||
* The time must be initialized by calling `configureNTP()` before use.
|
||||
*/
|
||||
void printLocalTime();
|
||||
|
||||
/**
|
||||
* Gets the current epoch time
|
||||
*/
|
||||
uint32_t getEpochTime();
|
||||
|
||||
/**
|
||||
* @brief The allowed time discrepancy (in seconds)
|
||||
*
|
||||
* Specifies the allowed discrepancy between the time of a received message
|
||||
* and the device time (in seconds).
|
||||
*
|
||||
* A stricter (lower) value better prevents against replay attacks,
|
||||
* but may lead to issues when dealing with slow networks and other
|
||||
* routing delays.
|
||||
*
|
||||
* @param offset The offset in both directions (seconds)
|
||||
*/
|
||||
void setMessageTimeAllowedOffset(uint32_t offset);
|
||||
|
||||
/**
|
||||
* @brief Check wether the time of a message is within the allowed bounds regarding freshness.
|
||||
*
|
||||
* The timestamp is used to ensure 'freshness' of the messages,
|
||||
* i.e. that they are not unreasonably delayed or captured and
|
||||
* later replayed by an attacker.
|
||||
*
|
||||
* @param messageTime The timestamp of the message (seconds since epoch)
|
||||
* @return true The time is within the acceptable offset of the local time
|
||||
* @return false The message time is invalid
|
||||
*/
|
||||
bool isMessageTimeAcceptable(uint32_t messageTime);
|
||||
|
||||
/**
|
||||
* @brief Initialize the use of the message counter API
|
||||
*
|
||||
* The message counter is stored in EEPROM, which must be initialized before use.
|
||||
*
|
||||
* @note The ESP32 does not have a true EEPROM,
|
||||
* which is emulated using a section of the flash memory.
|
||||
*/
|
||||
void prepareMessageCounterUsage();
|
||||
|
||||
/**
|
||||
* @brief Get the expected count for the next message.
|
||||
*
|
||||
* The counter is stored in EEPROM to persist across restarts
|
||||
*
|
||||
* @return The next counter to use by the remote
|
||||
*/
|
||||
uint32_t getNextMessageCounter();
|
||||
|
||||
/**
|
||||
* @brief Print info about the current message counter to the serial output
|
||||
*
|
||||
*/
|
||||
void printMessageCounter();
|
||||
|
||||
/**
|
||||
* @brief Check if a received counter is valid
|
||||
*
|
||||
* The counter is valid if it is larger than the previous counter
|
||||
* (larger or equal to the next expected counter).
|
||||
*
|
||||
* @param counter The counter to check
|
||||
* @return true The counter is valid
|
||||
* @return false The counter belongs to an old message
|
||||
*/
|
||||
bool isMessageCounterValid(uint32_t counter);
|
||||
|
||||
/**
|
||||
* @brief Mark a counter of a message as used.
|
||||
*
|
||||
* The counter value is stored in EEPROM to persist across restarts.
|
||||
*
|
||||
* All messages with counters lower than the given one will become invalid.
|
||||
*
|
||||
* @param counter The counter used in the last message.
|
||||
*/
|
||||
void didUseMessageCounter(uint32_t counter);
|
||||
|
||||
/**
|
||||
* @brief Reset the message counter.
|
||||
*
|
||||
* @warning The counter should never be reset in production environments,
|
||||
* and only together with a new secret key. Otherwise old messages may be
|
||||
* used for replay attacks.
|
||||
*
|
||||
*/
|
||||
void resetMessageCounter();
|
87
include/message.h
Normal file
87
include/message.h
Normal file
@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include "stdint.h"
|
||||
|
||||
/**
|
||||
* @brief The size of a message authentication code
|
||||
*
|
||||
* The MAC size is determined by the size of the output
|
||||
* of the hash function used. In this case, for SHA256,
|
||||
* the size is 32 bytes (= 256 bit)
|
||||
*/
|
||||
#define SHA256_MAC_SIZE 32
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
/**
|
||||
* @brief The content of an unlock message.
|
||||
*
|
||||
* The content is necessary to ensure freshness of the message
|
||||
* by requiring a recent time and a monotonously increasing counter.
|
||||
* This prevents messages from being delayed or being blocked and
|
||||
* replayed later.
|
||||
*/
|
||||
typedef struct {
|
||||
|
||||
/**
|
||||
* The timestamp of message creation
|
||||
*
|
||||
* The timestamp is encoded as the epoch time, i.e. seconds since 1970 (GMT).
|
||||
*
|
||||
* The timestamp is used to ensure 'freshness' of the messages,
|
||||
* i.e. that they are not unreasonably delayed or captured and
|
||||
* later replayed by an attacker.
|
||||
*/
|
||||
uint32_t time;
|
||||
|
||||
/**
|
||||
* The counter of unlock messages
|
||||
*
|
||||
* This counter must always increase with each message from the remote
|
||||
* in order for the messages to be deemed valid. Transfering the counters
|
||||
* back and forth also gives information about lost messages and potential
|
||||
* attacks. Both the remote and the device keep a record of at least the
|
||||
* last used counter.
|
||||
*/
|
||||
uint32_t id;
|
||||
|
||||
} Message;
|
||||
|
||||
/**
|
||||
* @brief An authenticated message by the mobile device to command unlocking.
|
||||
*
|
||||
* The message is protected by a message authentication code (MAC) based on
|
||||
* a symmetric key shared by the device and the remote. This code ensures
|
||||
* that the contents of the request were not altered. The message further
|
||||
* contains a timestamp to ensure that the message is recent, and not replayed
|
||||
* by an attacker. An additional counter is also included for this purpose,
|
||||
* which must continously increase for a message to be valid. This increases
|
||||
* security a bit, since the timestamp validation must be tolerant to some
|
||||
* inaccuracy due to mismatching clocks.
|
||||
*/
|
||||
typedef struct {
|
||||
|
||||
/**
|
||||
* @brief The authentication code of the message
|
||||
*
|
||||
* The code is created by performing HMAC-SHA256
|
||||
* over the bytes of the `Message`.
|
||||
*/
|
||||
uint8_t mac[SHA256_MAC_SIZE];
|
||||
|
||||
/**
|
||||
* @brief The message content.
|
||||
*
|
||||
* The content is necessary to ensure freshness of the message
|
||||
* by requiring a recent time and a monotonously increasing counter.
|
||||
* This prevents messages from being delayed or being blocked and
|
||||
* replayed later.
|
||||
*/
|
||||
Message message;
|
||||
|
||||
} AuthenticatedMessage;
|
||||
#pragma pack(pop)
|
||||
|
||||
#define MESSAGE_CONTENT_SIZE sizeof(Message)
|
||||
|
||||
#define AUTHENTICATED_MESSAGE_SIZE sizeof(AuthenticatedMessage)
|
108
include/server.h
Normal file
108
include/server.h
Normal file
@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include "message.h"
|
||||
#include "crypto.h"
|
||||
#include <WiFiMulti.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <WebSocketsClient.h>
|
||||
|
||||
/**
|
||||
* An event signaled from the device
|
||||
*/
|
||||
enum class SesameEvent {
|
||||
TextReceived = 1,
|
||||
UnexpectedSocketEvent = 2,
|
||||
InvalidPayloadSize = 3,
|
||||
MessageAuthenticationFailed = 4,
|
||||
MessageTimeMismatch = 5,
|
||||
MessageCounterInvalid = 6,
|
||||
MessageAccepted = 7,
|
||||
InfoMessage = 8,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A callback for messages received over the socket
|
||||
*
|
||||
* The first parameter is the received message.
|
||||
* The second parameter is the response to the remote.
|
||||
* The return value is the type of event to respond with.
|
||||
*/
|
||||
typedef SesameEvent (*MessageCallback)(AuthenticatedMessage*, AuthenticatedMessage*);
|
||||
|
||||
class ServerConnection {
|
||||
|
||||
public:
|
||||
|
||||
ServerConnection(const char* url, int port, const char* path);
|
||||
|
||||
void connect(const char* key, uint32_t reconnectTime = 5000);
|
||||
|
||||
void connectSSL(const char* key, uint32_t reconnectTime = 5000);
|
||||
|
||||
void loop();
|
||||
|
||||
void onMessage(MessageCallback callback);
|
||||
|
||||
// Indicator that the socket is connected.
|
||||
bool socketIsConnected = false;
|
||||
|
||||
private:
|
||||
|
||||
const char* url;
|
||||
|
||||
int port;
|
||||
|
||||
const char* path;
|
||||
|
||||
const char* key = NULL;
|
||||
|
||||
MessageCallback messageCallback = NULL;
|
||||
|
||||
// WebSocket to connect to the control server
|
||||
WebSocketsClient webSocket;
|
||||
|
||||
void reconnectAfter(uint32_t reconnectTime);
|
||||
|
||||
void registerEventCallback();
|
||||
|
||||
/**
|
||||
* Callback for WebSocket events.
|
||||
*
|
||||
* Updates the connection state and processes received keys.
|
||||
*
|
||||
* @param payload The pointer to received data
|
||||
* @param length The number of bytes received
|
||||
*/
|
||||
void webSocketEventHandler(WStype_t type, uint8_t * payload, size_t length);
|
||||
|
||||
/**
|
||||
* Process received binary data.
|
||||
*
|
||||
* Checks whether the received data is a valid and unused key,
|
||||
* and then signals that the motor should move.
|
||||
* Sends the event id to the server as a response to the request.
|
||||
*
|
||||
* If the key is valid, then `shouldStartOpening` is set to true.
|
||||
*
|
||||
* @param payload The pointer to the received data.
|
||||
* @param length The number of bytes received.
|
||||
*/
|
||||
void processReceivedBytes(uint8_t* payload, size_t length);
|
||||
|
||||
/**
|
||||
* Send a response event to the server and include the next key index.
|
||||
*
|
||||
* Sends the event type as three byte.
|
||||
* @param event The event type
|
||||
*/
|
||||
void sendFailureResponse(SesameEvent event);
|
||||
|
||||
/**
|
||||
* Send a response event to the server and include the next key index.
|
||||
*
|
||||
* Sends the event type as three byte.
|
||||
* @param event The event type
|
||||
*/
|
||||
void sendResponse(SesameEvent event, AuthenticatedMessage* message);
|
||||
|
||||
};
|
84
include/servo.h
Normal file
84
include/servo.h
Normal file
@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <ESP32Servo.h> // To control the servo
|
||||
|
||||
/**
|
||||
* @brief A controller for the button control servo
|
||||
*
|
||||
* The controller simply configures the servo for operation,
|
||||
* and then sets the desired servo value for the 'pressed' and 'released' states.
|
||||
* The controller requires periodic updating through the `loop()` function
|
||||
* in order to release the button after the specified time.
|
||||
*
|
||||
*/
|
||||
class ServoController {
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Construct a new Servo Controller object
|
||||
*
|
||||
* @param timer The timer to use for the servo control
|
||||
* @param frequency The servo frequency (depending on the model used)
|
||||
* @param pin The pin where the servo PWM line is connected
|
||||
*/
|
||||
ServoController(int timer, int frequency, int pin);
|
||||
|
||||
/**
|
||||
* @brief Configure the button values
|
||||
*
|
||||
* @param openDuration The duration (in ms) for which the button should remain pressed
|
||||
* @param pressedValue The servo value (in µs) that specifies the 'pressed' state
|
||||
* @param releasedValue The servo value (in µs) that specifies the 'released' state
|
||||
*/
|
||||
void configure(uint32_t openDuration, int pressedValue, int releasedValue);
|
||||
|
||||
/**
|
||||
* @brief Update the servo state periodically
|
||||
*
|
||||
* This function should be periodically called to update the servo state,
|
||||
* specifically to release the button after the opening time has elapsed.
|
||||
*
|
||||
* There is no required interval to call this function, but the accuracy of
|
||||
* the opening interval is dependent on the calling frequency.
|
||||
*/
|
||||
void loop();
|
||||
|
||||
/**
|
||||
* Push the door opener button down by moving the servo arm.
|
||||
*/
|
||||
void pressButton();
|
||||
|
||||
/**
|
||||
* Release the door opener button by moving the servo arm.
|
||||
*/
|
||||
void releaseButton();
|
||||
|
||||
private:
|
||||
|
||||
// Indicator that the door button is pushed
|
||||
bool buttonIsPressed = false;
|
||||
|
||||
int timer;
|
||||
|
||||
int frequency;
|
||||
|
||||
int pin;
|
||||
|
||||
uint32_t openDuration = 0;
|
||||
|
||||
int pressedValue = 0;
|
||||
|
||||
int releasedValue = 0;
|
||||
|
||||
// The time (in ms since start) when the door opening should end
|
||||
uint32_t openingEndTime = 0;
|
||||
|
||||
// Servo controller
|
||||
Servo servo;
|
||||
|
||||
// PWM Module needed for the servo
|
||||
ESP32PWM pwm;
|
||||
|
||||
};
|
Reference in New Issue
Block a user