Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Firmware/AUTH_MQTT/MQTT_Gateway/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.pio
105 changes: 105 additions & 0 deletions Firmware/AUTH_MQTT/MQTT_Gateway/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# ── Configuration ─────────────────────────────────────────────────────────────
SHELL := /bin/bash
PORT ?= /dev/ttyUSB0
LOG_DIR := .pio/test_logs

# ── Colors ────────────────────────────────────────────────────────────────────
RED := \033[0;31m
GREEN := \033[0;32m
YELLOW := \033[0;33m
CYAN := \033[0;36m
BOLD := \033[1m
RESET := \033[0m

# ── Help (default target) ─────────────────────────────────────────────────────
.PHONY: help
help:
@echo -e "$(BOLD)Usage: make <target>$(RESET)"
@echo ""
@echo -e " $(CYAN)Build:$(RESET)"
@echo " build Build firmware for esp32dev"
@echo " clean Clean all build artifacts"
@echo ""
@echo -e " $(CYAN)Flash & Monitor:$(RESET)"
@echo " flash Flash firmware to esp32dev"
@echo " monitor Open serial monitor"
@echo " flash-monitor Flash then open serial monitor"
@echo ""
@echo -e " $(CYAN)Test:$(RESET)"
@echo " test Run all native tests"
@echo " test-device Run only test_device suite"
@echo " test-utils Run only test_utils suite"
@echo " test-verbose Run all native tests with verbose output"
@echo ""
@echo -e " $(CYAN)Options:$(RESET)"
@echo " PORT=/dev/ttyUSB0 Override serial port (default: /dev/ttyUSB0)"

# ── Helpers ───────────────────────────────────────────────────────────────────
define run_test
@mkdir -p $(LOG_DIR)
@pio test $(1) 2>&1 | \
tee $(LOG_DIR)/last.log | \
sed 's/\[PASSED\]/\x1b[0;32m[PASSED]\x1b[0m/g; s/\[FAILED\]/\x1b[0;31m[FAILED]\x1b[0m/g; s/\bFAIL\b/\x1b[0;31mFAIL\x1b[0m/g; s/\bPASSED\b/\x1b[0;32mPASSED\x1b[0m/g'; \
EXIT=$${PIPESTATUS[0]}; \
echo ""; \
if grep -q ":FAIL" $(LOG_DIR)/last.log; then \
echo -e "$(RED)$(BOLD)============================================$(RESET)"; \
echo -e "$(RED)$(BOLD) FAILED TESTS $(RESET)"; \
echo -e "$(RED)$(BOLD)============================================$(RESET)"; \
echo ""; \
grep ":FAIL" $(LOG_DIR)/last.log | while IFS=: read -r file line test msg; do \
echo -e " $(YELLOW)File :$(RESET) $$file"; \
echo -e " $(YELLOW)Line :$(RESET) $$line"; \
echo -e " $(YELLOW)Test :$(RESET) $$test"; \
echo -e " $(RED)Reason:$(RESET) $$msg"; \
echo -e " $(RED)--------------------------------------------$(RESET)"; \
done; \
else \
echo -e "$(GREEN)$(BOLD)============================================$(RESET)"; \
echo -e "$(GREEN)$(BOLD) All tests passed! $(RESET)"; \
echo -e "$(GREEN)$(BOLD)============================================$(RESET)"; \
fi; \
exit $$EXIT
endef
# ── Build ─────────────────────────────────────────────────────────────────────
.PHONY: build
build:
@echo -e "$(CYAN)$(BOLD)Building firmware...$(RESET)"
pio run -e esp32dev

.PHONY: clean
clean:
@echo -e "$(YELLOW)$(BOLD)Cleaning build artifacts...$(RESET)"
pio run --target clean
@rm -rf $(LOG_DIR)

# ── Flash & Monitor ───────────────────────────────────────────────────────────
.PHONY: flash
flash:
@echo -e "$(CYAN)$(BOLD)Flashing firmware to $(PORT)...$(RESET)"
pio run -e esp32dev --target upload --upload-port $(PORT)

.PHONY: monitor
monitor:
@echo -e "$(CYAN)$(BOLD)Opening serial monitor on $(PORT)...$(RESET)"
pio device monitor --port $(PORT)

.PHONY: flash-monitor
flash-monitor: flash monitor

# ── Test ──────────────────────────────────────────────────────────────────────
.PHONY: test
test:
$(call run_test, -e native_device -e native_utils)

.PHONY: test-device
test-device:
$(call run_test, -e native_device)

.PHONY: test-utils
test-utils:
$(call run_test, -e native_utils)

.PHONY: test-verbose
test-verbose:
$(call run_test, -e native_device -e native_utils -vvv)
97 changes: 97 additions & 0 deletions Firmware/AUTH_MQTT/MQTT_Gateway/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# MQTT_Gateway
### An implementation for adding authentications and authorization for the MQTT messages without depend on the broker side


## Overview

This project is an IoT gateway system with two components:

1. **ESP32 Gateway** (`MQTT_Gateway.ino`) — Runs on an ESP32 microcontroller. Hosts a web dashboard, manages device connections over MQTT, and dispatches JSON-RPC calls using the Mongoose networking library.
2. **Sensor Simulator** — A Python script that simulates IoT sensor devices for testing. Connects to the same MQTT broker and goes through the full authentication flow.

**Architecture:**

```
┌──────────────┐ MQTT broker ┌──────────────────┐
│ ESP32 │◄─────────────────────────────────────►│ Sensor Device │
│ Gateway │ jrpc/connect/request │ (Python or real │
│ │ jrpc/gateway/rx │ ESP32 sensor) │
│ Port 80: │ jrpc/devices/{id}/rx └──────────────────┘
│ Dashboard │
│ + WebSocket │◄──── Browser (real-time dashboard)
└──────────────┘
```


```
1. Device generates X25519 keypair, publishes PUBLIC key
in connect request to jrpc/connect/request
2. Gateway registers device as PENDING, stores device pubkey
(appears in dashboard "Pending Approval")
3. Admin clicks "Approve" on dashboard
4. Gateway generates its own ephemeral X25519 keypair
Gateway computes: shared = X25519(gateway_prv, device_pub)
Gateway derives: hmac_key = HMAC-SHA256("esp32-dashboard-hmac-key", shared)
Gateway sends its PUBLIC key to device
Device status → APPROVED
5. Device receives gateway pubkey, computes same shared secret:
shared = X25519(device_prv, gateway_pub) ← identical result
hmac_key = HMAC-SHA256("esp32-dashboard-hmac-key-v1", shared)
Both sides now have the same HMAC key without ever transmitting it
6. Device signs each RPC with HMAC-SHA256(hmac_key, canonical_msg)
canonical_msg = "device_id\nmethod\nrpc_id\nnonce\ntimestamp"
Nonce is monotonically increasing (prevents replay attacks)
Device status → AUTHORIZED on first valid HMAC
7. If no messages for 60s → device marked OFFLINE
Admin can Revoke access or Remove device at any time
Revoke wipes the HMAC key — device must re-do full ECDH
```
### Pre-Shared Key (PSK) — Device Identity Verification

Since the gateway connects to devices via an online MQTT broker, a previously-approved device that reconnects with new keys cannot be automatically distinguished from an attacker spoofing the same device ID. **PSK** solves this by requiring proof-of-identity on every reconnection.

**How it works:**

1. Admin Enter a PSK string of the Device pending to be approved (e.g., `"my-device-secret"`) in the dashboard when first approving a device
2. The device operator configures the same PSK on the physical device
3. Both sides hash the PSK: `SHA-256("my-device-secret")`
4. On every connection request, the device computes:
```
psk_proof = HMAC-SHA256(SHA256(psk), device_id + pubkey_hex)
```
5. Gateway computes the same HMAC and verifies with constant-time comparison
6. If verification fails, the connection is rejected

**Security properties of PSK:**
- PSK is **never transmitted** over MQTT — only a cryptographic proof is sent
- Proof is **bound to the public key** — cannot be replayed with a different keypair
- Proof is **bound to the device ID** — cannot be reused for a different device
- PSK is wiped on revoke — a new PSK must be set when re-approving

### Payload Encryption (ChaCha20-Poly1305)

All MQTT payloads are encrypted with ChaCha20-Poly1305 using the **Encrypt-then-MAC** pattern. This prevents eavesdroppers on the MQTT broker from reading sensor data, method names, or any message content.

**Crypto providers:** Cryptographic primitives are provided by:
- **X25519 ECDH** — standalone implementation in `x25519.h` (extracted from Mongoose, public domain)
- **ChaCha20-Poly1305** — standalone implementation in `chacha20.h`/`chacha20.c` (extracted from Mongoose, public domain)
- **SHA-256 / HMAC-SHA256** — Mongoose (always compiled, not gated by TLS setting)
- **Random** — Mongoose's `mg_random()` which uses `esp_random()` on ESP32

**Key derivation** (from the same ECDH shared secret):
```
hmac_key = HMAC-SHA256("esp32-dashboard-hmac-key", shared_secret)
enc_key = HMAC-SHA256("esp32-dashboard-enc-key", shared_secret)
```
Empty file.
37 changes: 37 additions & 0 deletions Firmware/AUTH_MQTT/MQTT_Gateway/include/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

This directory is intended for project header files.

A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.

```src/main.c

#include "header.h"

int main (void)
{
...
}
```

Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.

In C, the convention is to give header files names that end with `.h'.

Read more about using header files in official GCC documentation:

* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes

https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
8 changes: 8 additions & 0 deletions Firmware/AUTH_MQTT/MQTT_Gateway/include/gateway.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef __GATEWAY__H_
#define __GATEWAY__H_

void gateway_init();

void gateway_poll();

#endif
24 changes: 24 additions & 0 deletions Firmware/AUTH_MQTT/MQTT_Gateway/include/gateway_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef __GATEWAY_CONFIG__H_
#define __GATEWAY_CONFIG__H_

#define MG_DEBUG 1

// ── WiFi ──────────────────────────────────────
#define GW_WIFI_SSID "Galaxy A53 5GD5E1"
#define GW_WIFI_PASSWORD "1234567888*"
// #define GW_WIFI_SSID "WE_NET"
// #define GW_WIFI_PASSWORD "AymanSH@2025_**"

// ── MQTT ──────────────────────────────────────
#define GW_MQTT_BROKER "broker.hivemq.com"
#define GW_MQTT_PORT 1883
#define GW_GATEWAY_ID "gateway_01"

// ── MQTT Topics ───────────────────────────────
#define GW_T_GATEWAY_CONNECT "jrpc/gateway/connect"
#define GW_T_GATEWAY_RX "jrpc/gateway/rx"

// ── Timing ────────────────────────────────────
#define GW_MQTT_RECONNECT_MS 3000UL

#endif
46 changes: 46 additions & 0 deletions Firmware/AUTH_MQTT/MQTT_Gateway/lib/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into the executable file.

The source code of each library should be placed in a separate directory
("lib/your_library_name/[Code]").

For example, see the structure of the following example libraries `Foo` and `Bar`:

|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c

Example contents of `src/main.c` using Foo and Bar:
```
#include <Foo.h>
#include <Bar.h>

int main (void)
{
...
}

```

The PlatformIO Library Dependency Finder will find automatically dependent
libraries by scanning project source files.

More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html
Loading