mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-10-04 15:06:57 +02:00
Course update: lessons, CTF 0x0011a_cb, and documentation
- 0x0011a_cb (Operation Dark Vector): nation-state CTF redesign with an AES-128-ECB sealed target and a plaintext launch origin; RP2350 firmware with bearing-driven servo, tri-color LEDs, GSV stats, and a realistic no-fix path - docs: story-driven classified brief, GDB and Ghidra tutorials with deep step-throughs, regenerated artifacts and PDFs - scripts: docstring standard, AES per-student randomizer, telemetry monitor - week 3 to week 5 lessons: Ghidra patching tutorial, CMSIS-SVD hardware RE, double floating-point and GPIO architecture chapters, README structure
This commit is contained in:
1 parent
5201ee4b6b
commit
35eacd2c0e
162 files changed
+125658
-232
No files matched your search
@@ -0,0 +1,362 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2026 Kevin Thomas
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// Author: Kevin Thomas
|
||||
// Email: kevin@mytechnotalent.com
|
||||
// GitHub: https://github.com/mytechnotalent
|
||||
// File: auth.c
|
||||
// Desc: Implements the hardened Ouroboros authentication engine used as the
|
||||
// operator gate in the DEEPLINE Metro practice firmware.
|
||||
// Created: 2026
|
||||
|
||||
#include "auth.h"
|
||||
#include "demo_artifact.h"
|
||||
#include "pico/stdlib.h"
|
||||
#include "argon2.h"
|
||||
#include "mbedtls/chachapoly.h"
|
||||
#include <string.h>
|
||||
|
||||
// Non-zero once auth_init() has prepared the onboard LED GPIO. auth_execute()
|
||||
// reports an internal error whenever this flag is not yet set, mirroring the
|
||||
// reference construction from the encryption-c-rp2350 repository.
|
||||
static bool g_auth_ready;
|
||||
|
||||
/**
|
||||
* @brief Clear a byte buffer.
|
||||
*
|
||||
* Writes zero to each byte in the caller-supplied buffer so derived keys
|
||||
* and plaintext are not left resident in memory longer than needed.
|
||||
*
|
||||
* @param buf Pointer to mutable byte buffer.
|
||||
* @param len Number of bytes to clear.
|
||||
* @return None.
|
||||
*/
|
||||
static void clear_bytes(uint8_t *buf, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0u; i < len; ++i) {
|
||||
buf[i] = 0u;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Rotate a 32-bit value left.
|
||||
*
|
||||
* The HChaCha20 core uses 32-bit modular additions and left rotations in
|
||||
* its quarter-round primitive.
|
||||
*
|
||||
* @param value Input 32-bit word.
|
||||
* @param shift Rotation distance in bits.
|
||||
* @return uint32_t Rotated result.
|
||||
*/
|
||||
static uint32_t rotl32(uint32_t value, uint8_t shift)
|
||||
{
|
||||
return (value << shift) | (value >> (32u - shift));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load a 32-bit little-endian word from bytes.
|
||||
*
|
||||
* Converts four little-endian bytes into the word representation used by
|
||||
* the HChaCha20 state machine.
|
||||
*
|
||||
* @param src Pointer to four readable bytes.
|
||||
* @return uint32_t Parsed 32-bit word.
|
||||
*/
|
||||
static uint32_t load32_le(const uint8_t *src)
|
||||
{
|
||||
return (uint32_t)src[0] | ((uint32_t)src[1] << 8u) |
|
||||
((uint32_t)src[2] << 16u) | ((uint32_t)src[3] << 24u);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Store a 32-bit word in little-endian byte order.
|
||||
*
|
||||
* Serializes one HChaCha20 state word into the caller-supplied output
|
||||
* buffer.
|
||||
*
|
||||
* @param dst Pointer to four writable bytes.
|
||||
* @param value 32-bit word to serialize.
|
||||
* @return None.
|
||||
*/
|
||||
static void store32_le(uint8_t *dst, uint32_t value)
|
||||
{
|
||||
dst[0] = (uint8_t)(value & 0xFFu);
|
||||
dst[1] = (uint8_t)((value >> 8u) & 0xFFu);
|
||||
dst[2] = (uint8_t)((value >> 16u) & 0xFFu);
|
||||
dst[3] = (uint8_t)((value >> 24u) & 0xFFu);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Execute one ChaCha quarter-round.
|
||||
*
|
||||
* Mutates four state words in place according to the standard ChaCha20
|
||||
* ARX quarter-round used by the HChaCha20 subkey derivation.
|
||||
*
|
||||
* @param a Pointer to state word a.
|
||||
* @param b Pointer to state word b.
|
||||
* @param c Pointer to state word c.
|
||||
* @param d Pointer to state word d.
|
||||
* @return None.
|
||||
*/
|
||||
static void quarter_round(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d)
|
||||
{
|
||||
*a += *b; *d ^= *a; *d = rotl32(*d, 16u);
|
||||
*c += *d; *b ^= *c; *b = rotl32(*b, 12u);
|
||||
*a += *b; *d ^= *a; *d = rotl32(*d, 8u);
|
||||
*c += *d; *b ^= *c; *b = rotl32(*b, 7u);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Derive a 256-bit XChaCha20 subkey from key and nonce prefix.
|
||||
*
|
||||
* Runs the HChaCha20 core over the first 16 bytes of the 24-byte XChaCha
|
||||
* nonce and emits the derived 32-byte subkey.
|
||||
*
|
||||
* @param key Pointer to 32-byte AEAD key.
|
||||
* @param nonce Pointer to 24-byte XChaCha20 nonce.
|
||||
* @param subkey Output 32-byte subkey buffer.
|
||||
* @return None.
|
||||
*/
|
||||
static void hchacha20(const uint8_t key[32], const uint8_t nonce[24], uint8_t subkey[32])
|
||||
{
|
||||
uint32_t state[16] = {
|
||||
0x61707865u, 0x3320646Eu, 0x79622D32u, 0x6B206574u,
|
||||
load32_le(&key[0]), load32_le(&key[4]), load32_le(&key[8]), load32_le(&key[12]),
|
||||
load32_le(&key[16]), load32_le(&key[20]), load32_le(&key[24]), load32_le(&key[28]),
|
||||
load32_le(&nonce[0]), load32_le(&nonce[4]), load32_le(&nonce[8]), load32_le(&nonce[12]),
|
||||
};
|
||||
uint8_t round;
|
||||
for (round = 0u; round < 10u; ++round) {
|
||||
quarter_round(&state[0], &state[4], &state[8], &state[12]);
|
||||
quarter_round(&state[1], &state[5], &state[9], &state[13]);
|
||||
quarter_round(&state[2], &state[6], &state[10], &state[14]);
|
||||
quarter_round(&state[3], &state[7], &state[11], &state[15]);
|
||||
quarter_round(&state[0], &state[5], &state[10], &state[15]);
|
||||
quarter_round(&state[1], &state[6], &state[11], &state[12]);
|
||||
quarter_round(&state[2], &state[7], &state[8], &state[13]);
|
||||
quarter_round(&state[3], &state[4], &state[9], &state[14]);
|
||||
}
|
||||
store32_le(&subkey[0], state[0]);
|
||||
store32_le(&subkey[4], state[1]);
|
||||
store32_le(&subkey[8], state[2]);
|
||||
store32_le(&subkey[12], state[3]);
|
||||
store32_le(&subkey[16], state[12]);
|
||||
store32_le(&subkey[20], state[13]);
|
||||
store32_le(&subkey[24], state[14]);
|
||||
store32_le(&subkey[28], state[15]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build the inner 96-bit nonce used by ChaCha20-Poly1305.
|
||||
*
|
||||
* XChaCha20 converts the last 8 bytes of the 24-byte outer nonce into the
|
||||
* final 12-byte IETF ChaCha nonce by prefixing four zero bytes.
|
||||
*
|
||||
* @param nonce Pointer to 24-byte XChaCha20 nonce.
|
||||
* @param out Output 12-byte nonce buffer.
|
||||
* @return None.
|
||||
*/
|
||||
static void build_inner_nonce(const uint8_t nonce[24], uint8_t out[12])
|
||||
{
|
||||
memset(out, 0, 4u);
|
||||
memcpy(&out[4], &nonce[16], 8u);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return true when a byte is ASCII whitespace used by the CLI.
|
||||
*
|
||||
* The firmware normalizes spaces, carriage returns, tabs, and newlines in
|
||||
* the same broad spirit as split-whitespace host parsing.
|
||||
*
|
||||
* @param ch Input byte.
|
||||
* @return bool true when byte is treated as whitespace.
|
||||
*/
|
||||
static bool is_space(uint8_t ch)
|
||||
{
|
||||
return (ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return true when a byte is lowercase ASCII.
|
||||
*
|
||||
* Hardened passphrases accept only lowercase a-z characters in each word.
|
||||
*
|
||||
* @param ch Input byte.
|
||||
* @return bool true when byte is in the lowercase ASCII range.
|
||||
*/
|
||||
static bool is_lowercase_ascii(uint8_t ch)
|
||||
{
|
||||
return (ch >= 'a') && (ch <= 'z');
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validate the strict hardened passphrase policy.
|
||||
*
|
||||
* Accepts only passphrases containing exactly 12 lowercase ASCII words
|
||||
* separated by whitespace.
|
||||
*
|
||||
* @param passphrase Pointer to passphrase bytes.
|
||||
* @param passphrase_len Number of passphrase bytes.
|
||||
* @return bool true when the passphrase satisfies the policy.
|
||||
*/
|
||||
static bool validate_hardened_passphrase(const uint8_t *passphrase, size_t passphrase_len)
|
||||
{
|
||||
size_t i = 0u;
|
||||
uint8_t words = 0u;
|
||||
if ((passphrase == NULL) || (passphrase_len == 0u) || (passphrase_len > AUTH_PASSPHRASE_MAX_LEN)) {
|
||||
return false;
|
||||
}
|
||||
while (i < passphrase_len) {
|
||||
while ((i < passphrase_len) && is_space(passphrase[i])) {
|
||||
++i;
|
||||
}
|
||||
if (i == passphrase_len) {
|
||||
break;
|
||||
}
|
||||
++words;
|
||||
while ((i < passphrase_len) && !is_space(passphrase[i])) {
|
||||
if (!is_lowercase_ascii(passphrase[i])) {
|
||||
return false;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
return words == AUTH_REQUIRED_WORDS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Derive the 32-byte hardened key with Argon2id.
|
||||
*
|
||||
* Uses the generated artifact parameters and salt to derive the AEAD key
|
||||
* that protects the embedded ciphertext.
|
||||
*
|
||||
* @param passphrase Pointer to passphrase bytes.
|
||||
* @param passphrase_len Number of passphrase bytes.
|
||||
* @param key_out Output 32-byte key buffer.
|
||||
* @return bool true when derivation succeeds.
|
||||
*/
|
||||
static bool derive_hardened_key(const uint8_t *passphrase, size_t passphrase_len, uint8_t key_out[32])
|
||||
{
|
||||
return argon2id_hash_raw(
|
||||
DEMO_ITERATIONS,
|
||||
DEMO_MEMORY_KIB,
|
||||
DEMO_PARALLELISM,
|
||||
passphrase,
|
||||
passphrase_len,
|
||||
DEMO_SALT,
|
||||
AUTH_SALT_SIZE,
|
||||
key_out,
|
||||
AUTH_KEY_SIZE) == ARGON2_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decrypt the embedded artifact with XChaCha20-Poly1305.
|
||||
*
|
||||
* Derives the XChaCha20 subkey with HChaCha20, converts the outer nonce to
|
||||
* the inner 96-bit nonce, and verifies/decrypts the payload in one shot.
|
||||
*
|
||||
* @param key Pointer to 32-byte Argon2id-derived key.
|
||||
* @param payload_out Output 48-byte plaintext payload buffer.
|
||||
* @return bool true when tag verification and decryption succeed.
|
||||
*/
|
||||
static bool decrypt_artifact(const uint8_t key[32], uint8_t payload_out[AUTH_PAYLOAD_SIZE])
|
||||
{
|
||||
bool ok;
|
||||
int rc;
|
||||
uint8_t subkey[32];
|
||||
uint8_t inner_nonce[12];
|
||||
mbedtls_chachapoly_context ctx;
|
||||
hchacha20(key, DEMO_NONCE, subkey);
|
||||
build_inner_nonce(DEMO_NONCE, inner_nonce);
|
||||
mbedtls_chachapoly_init(&ctx);
|
||||
rc = mbedtls_chachapoly_setkey(&ctx, subkey);
|
||||
if (rc == 0) {
|
||||
rc = mbedtls_chachapoly_auth_decrypt(
|
||||
&ctx,
|
||||
AUTH_PAYLOAD_SIZE,
|
||||
inner_nonce,
|
||||
NULL,
|
||||
0u,
|
||||
&DEMO_CIPHERTEXT_AND_TAG[AUTH_PAYLOAD_SIZE],
|
||||
DEMO_CIPHERTEXT_AND_TAG,
|
||||
payload_out);
|
||||
}
|
||||
mbedtls_chachapoly_free(&ctx);
|
||||
clear_bytes(subkey, sizeof(subkey));
|
||||
clear_bytes(inner_nonce, sizeof(inner_nonce));
|
||||
ok = (rc == 0);
|
||||
if (!ok) {
|
||||
clear_bytes(payload_out, AUTH_PAYLOAD_SIZE);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Dispatch the decrypted payload to GPIO25 and UART.
|
||||
*
|
||||
* Mirrors the Rust demo payload contract: byte 0 controls the LED, and
|
||||
* bytes 1..7 are transmitted verbatim over UART.
|
||||
*
|
||||
* @param payload Pointer to decrypted 48-byte payload.
|
||||
* @return None.
|
||||
*/
|
||||
static void dispatch_payload(const uint8_t payload[AUTH_PAYLOAD_SIZE])
|
||||
{
|
||||
uint8_t i;
|
||||
gpio_put(AUTH_LED_PIN, payload[0] ? 1 : 0);
|
||||
for (i = 1u; i < 8u; ++i) {
|
||||
putchar_raw((char)payload[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool auth_init(void)
|
||||
{
|
||||
g_auth_ready = true;
|
||||
gpio_init(AUTH_LED_PIN);
|
||||
gpio_set_dir(AUTH_LED_PIN, GPIO_OUT);
|
||||
gpio_put(AUTH_LED_PIN, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
auth_result_t auth_execute(const uint8_t *passphrase, size_t passphrase_len)
|
||||
{
|
||||
uint8_t key[AUTH_KEY_SIZE];
|
||||
uint8_t payload[AUTH_PAYLOAD_SIZE];
|
||||
if (!g_auth_ready) {
|
||||
return AUTH_RESULT_INTERNAL_ERROR;
|
||||
}
|
||||
if (!validate_hardened_passphrase(passphrase, passphrase_len)) {
|
||||
return AUTH_RESULT_POLICY_VIOLATION;
|
||||
}
|
||||
if (!derive_hardened_key(passphrase, passphrase_len, key)) {
|
||||
return AUTH_RESULT_INTERNAL_ERROR;
|
||||
}
|
||||
if (!decrypt_artifact(key, payload)) {
|
||||
clear_bytes(key, sizeof(key));
|
||||
return AUTH_RESULT_AUTHENTICATION_FAILED;
|
||||
}
|
||||
dispatch_payload(payload);
|
||||
clear_bytes(payload, sizeof(payload));
|
||||
clear_bytes(key, sizeof(key));
|
||||
return AUTH_RESULT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2026 Kevin Thomas
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// Author: Kevin Thomas
|
||||
// Email: kevin@mytechnotalent.com
|
||||
// GitHub: https://github.com/mytechnotalent/encryption-c-rp2350
|
||||
// File: cli.c
|
||||
// Desc: Implements the CLI UART passphrase input interface for Ouroboros.
|
||||
// Created: 2026
|
||||
|
||||
#include "cli.h"
|
||||
#include "auth.h"
|
||||
#include "pico/stdlib.h"
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* @brief Maximum number of passphrase characters accepted from UART.
|
||||
*
|
||||
* Limits the input buffer to the hardened engine boundary. A null
|
||||
* terminator is written after the last character so the buffer must be
|
||||
* declared with at least this many bytes.
|
||||
*/
|
||||
#define PASS_BUF_LEN AUTH_PASSPHRASE_MAX_LEN
|
||||
|
||||
/**
|
||||
* @brief Print the hardened passphrase policy hint.
|
||||
*
|
||||
* The firmware uses the same interactive policy as the host demo:
|
||||
* exactly 12 lowercase words separated by spaces.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
static void print_policy_hint(void)
|
||||
{
|
||||
printf("Enter exactly 12 lowercase words separated by spaces.\r\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Append one received character to the passphrase buffer.
|
||||
*
|
||||
* Stores printable characters up to the buffer limit minus one to
|
||||
* reserve room for a null terminator. Echoes the character back
|
||||
* over UART for interactive typing feedback.
|
||||
*
|
||||
* @param ch Input character value.
|
||||
* @param buf Pointer to mutable passphrase buffer.
|
||||
* @param idx Pointer to current buffer length.
|
||||
* @return None.
|
||||
*/
|
||||
static void append_char(int ch, char *buf, size_t *idx)
|
||||
{
|
||||
if (*idx + 1u >= PASS_BUF_LEN) {
|
||||
return;
|
||||
}
|
||||
buf[*idx] = (char)ch;
|
||||
*idx += 1u;
|
||||
putchar_raw((char)ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove one character from the passphrase buffer.
|
||||
*
|
||||
* Moves the index back by one and emits the backspace-escape
|
||||
* sequence to erase the last echoed character on the terminal.
|
||||
*
|
||||
* @param idx Pointer to current buffer length.
|
||||
* @return None.
|
||||
*/
|
||||
static void handle_backspace(size_t *idx)
|
||||
{
|
||||
if (*idx == 0u) {
|
||||
return;
|
||||
}
|
||||
*idx -= 1u;
|
||||
printf("\b \b");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finalise and authenticate the current passphrase buffer.
|
||||
*
|
||||
* Null-terminates the input, runs the full Ouroboros authentication
|
||||
* pipeline via auth_execute, prints policy guidance or authentication
|
||||
* failure text as needed, and resets the buffer index for the next
|
||||
* prompt cycle.
|
||||
*
|
||||
* @param buf Pointer to mutable passphrase buffer.
|
||||
* @param idx Pointer to current buffer length.
|
||||
* @return None.
|
||||
*/
|
||||
static void finish_passphrase(char *buf, size_t *idx)
|
||||
{
|
||||
auth_result_t result;
|
||||
putchar_raw('\r');
|
||||
putchar_raw('\n');
|
||||
buf[*idx] = '\0';
|
||||
result = auth_execute((const uint8_t *)buf, *idx);
|
||||
if (result == AUTH_RESULT_POLICY_VIOLATION) {
|
||||
gpio_put(AUTH_LED_PIN, 0);
|
||||
print_policy_hint();
|
||||
} else if (result != AUTH_RESULT_SUCCESS) {
|
||||
gpio_put(AUTH_LED_PIN, 0);
|
||||
printf("Authentication failed.\r\n");
|
||||
}
|
||||
*idx = 0u;
|
||||
print_prompt();
|
||||
}
|
||||
|
||||
void print_prompt(void)
|
||||
{
|
||||
printf("\r\n> ");
|
||||
}
|
||||
|
||||
void service_uart(char *buf, size_t *idx)
|
||||
{
|
||||
int ch = getchar_timeout_us(0);
|
||||
if (ch == PICO_ERROR_TIMEOUT) {
|
||||
tight_loop_contents();
|
||||
return;
|
||||
}
|
||||
if ((ch == '\b') || (ch == 127)) {
|
||||
handle_backspace(idx);
|
||||
return;
|
||||
}
|
||||
if ((ch == '\r') || (ch == '\n')) {
|
||||
finish_passphrase(buf, idx);
|
||||
return;
|
||||
}
|
||||
append_char(ch, buf, idx);
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2026 Kevin Thomas
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// Author: Kevin Thomas
|
||||
// Email: kevin@mytechnotalent.com
|
||||
// GitHub: https://github.com/mytechnotalent
|
||||
// File: main.c
|
||||
// Desc: DEEPLINE Metro interlocking host for the FINAL-PRACTICE exercise.
|
||||
// Chains the strict Ouroboros operator gate (Argon2id plus
|
||||
// XChaCha20-Poly1305) around the frozen relay telemetry puzzles.
|
||||
// Created: 2026
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "auth.h"
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
// Block deviation classification ceiling below which an automated train
|
||||
// release is permitted. BUG: PALLAS compiled in 95; the engineering limit
|
||||
// is 60 (two redundant cmp sites in the ship image).
|
||||
#define SAFE_THRESHOLD 95u
|
||||
|
||||
// Number of 32-bit ARX state words used by the signal-key machinery.
|
||||
#define ARX_WORDS 4u
|
||||
|
||||
// Spec value of the derived signal key minted in the incident report. This is
|
||||
// the key the honest build derives from GATE_SEED_GOOD and the derived IV.
|
||||
#define SIGNAL_SPEC 0x2D879291u
|
||||
|
||||
// ChaCha expand word used by the console-side derivation path.
|
||||
#define GATE_SEED_GOOD 0x6B206574u
|
||||
|
||||
// Track-circuit current deviation frozen by the dead pilot wire (amperes).
|
||||
// A live reading is recalculated in the field; this image holds a wrong
|
||||
// snapshot that engineers must decode from registers and SRAM.
|
||||
static volatile uint32_t g_block_current = 87u;
|
||||
|
||||
// Operator-facing block classification (drives the BLOCK STATE line).
|
||||
static volatile uint32_t g_operator_state = 0u;
|
||||
|
||||
// Automatic train release decision (drives the AUTO TRAIN line).
|
||||
static volatile uint32_t g_dispatch_state = 0u;
|
||||
|
||||
// Static per-cycle poll counter retained in .bss. Watch this from GDB.
|
||||
static volatile uint32_t g_fault_polls = 0u;
|
||||
|
||||
// ARX seed substituted by the poisoned build up to the signal derivation.
|
||||
// BUG: 0x0A0A0A0A was fused in place of the "te k" expand word 0x6B206574.
|
||||
static volatile uint32_t g_auth_seed = 0x0A0A0A0Au;
|
||||
|
||||
// Derived signal key printed each cycle and checked against SIGNAL_SPEC.
|
||||
static uint32_t g_signal_key = 0u;
|
||||
|
||||
/**
|
||||
* @brief Hold one track-block telemetry record for the interlocking.
|
||||
*
|
||||
* Stores the measured block length, the condition flag word, and the
|
||||
* crossing identifier used by the automatic train protection logic.
|
||||
*/
|
||||
typedef struct telemetry_t {
|
||||
double block_length_km;
|
||||
uint32_t block_flags;
|
||||
uint16_t crossing;
|
||||
} telemetry_t;
|
||||
|
||||
// Live telemetry record. BUG: block_length_km shipped as 3.2 km; the real
|
||||
// BRIDGE-4 block is 0.32 km, far below the minimum release spacing.
|
||||
static volatile telemetry_t g_telemetry = { 3.2, 0x3u, 7u };
|
||||
|
||||
// Interactive passphrase buffer and parser cursor for the operator gate.
|
||||
static char g_linebuf[AUTH_PASSPHRASE_MAX_LEN];
|
||||
static size_t g_lineidx = 0u;
|
||||
|
||||
/**
|
||||
* @brief Rotate a 32-bit value left.
|
||||
*
|
||||
* @param value Input 32-bit word.
|
||||
* @param shift Rotation distance in bits.
|
||||
* @return uint32_t Rotated result.
|
||||
*/
|
||||
static uint32_t rotl32(uint32_t value, uint8_t shift)
|
||||
{
|
||||
return (value << shift) | (value >> (32u - shift));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load a 32-bit little-endian word from bytes.
|
||||
*
|
||||
* @param src Pointer to four readable bytes.
|
||||
* @return uint32_t Parsed 32-bit word.
|
||||
*/
|
||||
static uint32_t load32_le(const uint8_t *src)
|
||||
{
|
||||
return (uint32_t)src[0] | ((uint32_t)src[1] << 8u) |
|
||||
((uint32_t)src[2] << 16u) | ((uint32_t)src[3] << 24u);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Store a 32-bit word in little-endian byte order.
|
||||
*
|
||||
* @param dst Pointer to four writable bytes.
|
||||
* @param value 32-bit word to serialize.
|
||||
* @return None.
|
||||
*/
|
||||
static void store32_le(uint8_t *dst, uint32_t value)
|
||||
{
|
||||
dst[0] = (uint8_t)(value & 0xFFu);
|
||||
dst[1] = (uint8_t)((value >> 8u) & 0xFFu);
|
||||
dst[2] = (uint8_t)((value >> 16u) & 0xFFu);
|
||||
dst[3] = (uint8_t)((value >> 24u) & 0xFFu);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply one additive-rotate-xor phase of a ChaCha quarter-round.
|
||||
*
|
||||
* @param a Pointer to state word A.
|
||||
* @param b Pointer to state word B.
|
||||
* @param c Pointer to state word C.
|
||||
* @param d Pointer to state word D.
|
||||
* @param shift Rotation distance in bits.
|
||||
* @return None.
|
||||
*/
|
||||
static void qr_phase(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint8_t shift)
|
||||
{
|
||||
*a += *b;
|
||||
*d ^= *a;
|
||||
*d = rotl32(*d, shift);
|
||||
*c += *d;
|
||||
*b ^= *c;
|
||||
*b = rotl32(*b, shift);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Execute one full ChaCha ARX quarter-round.
|
||||
*
|
||||
* @param a Pointer to state word A.
|
||||
* @param b Pointer to state word B.
|
||||
* @param c Pointer to state word C.
|
||||
* @param d Pointer to state word D.
|
||||
* @return None.
|
||||
*/
|
||||
static void quarter_round(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d)
|
||||
{
|
||||
qr_phase(a, b, c, d, 16u);
|
||||
qr_phase(a, b, c, d, 12u);
|
||||
qr_phase(a, b, c, d, 8u);
|
||||
qr_phase(a, b, c, d, 7u);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run r full quarter-rounds over a four-word ARX state.
|
||||
*
|
||||
* @param a Pointer to state word A.
|
||||
* @param b Pointer to state word B.
|
||||
* @param c Pointer to state word C.
|
||||
* @param d Pointer to state word D.
|
||||
* @param rounds Number of full quarter-rounds to run.
|
||||
* @return None.
|
||||
*/
|
||||
static void run_rounds(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint8_t rounds)
|
||||
{
|
||||
uint8_t r;
|
||||
for (r = 0u; r < rounds; ++r) {
|
||||
quarter_round(a, b, c, d);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Derive a 32-bit ARX session key from a seed and IV.
|
||||
*
|
||||
* Runs four ChaCha quarter-rounds over the seed, the IV, and the ChaCha
|
||||
* expand constants; returns state word A exclusive-or state word D.
|
||||
*
|
||||
* @param seed 32-bit seed word.
|
||||
* @param iv 32-bit IV word.
|
||||
* @return uint32_t Derived session key word.
|
||||
*/
|
||||
static uint32_t derive_session_key(uint32_t seed, uint32_t iv)
|
||||
{
|
||||
uint32_t a = seed;
|
||||
uint32_t b = iv;
|
||||
uint32_t c = 0x61707865u;
|
||||
uint32_t d = 0x3320646Eu;
|
||||
run_rounds(&a, &b, &c, &d, 4u);
|
||||
return a ^ d;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Derive the runtime IV from the good console seed word.
|
||||
*
|
||||
* @param None.
|
||||
* @return uint32_t Derived IV word (0xC0F89829 in an honest image).
|
||||
*/
|
||||
static uint32_t derive_state_iv(void)
|
||||
{
|
||||
return derive_session_key(GATE_SEED_GOOD, 0u);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Refresh the runtime signal key from the live seed and IV.
|
||||
*
|
||||
* Captures the freshly derived IV, then derives the final key word from
|
||||
* the substituted seed. Break after each derivation to read the register.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
static void set_signal_state(void)
|
||||
{
|
||||
uint32_t iv = derive_state_iv();
|
||||
g_signal_key = derive_session_key(g_auth_seed, iv);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Classify the frozen current reading against the compiled limit.
|
||||
*
|
||||
* Compares the frozen track-circuit current reading against SAFE_THRESHOLD
|
||||
* and assigns the resulting boolean status to both g_operator_state and
|
||||
* g_dispatch_state.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
static void classify_blocks(void)
|
||||
{
|
||||
g_operator_state = (g_block_current < SAFE_THRESHOLD) ? 1u : 0u;
|
||||
g_dispatch_state = (g_block_current < SAFE_THRESHOLD) ? 1u : 0u;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print the relay boot identity and unconditional signal line.
|
||||
*
|
||||
* Emits the DEEPLINE authority banner, adaptive signal window, serial console
|
||||
* configuration string, and nominal track status prompt over the console.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
static void print_identity(void)
|
||||
{
|
||||
printf("DEEPLINE METRO AUTHORITY\r\n");
|
||||
printf("ADAPTIVE SIGNAL WINDOW: 38 MINUTES\r\n");
|
||||
printf("USB-CDC 115200 8N1 | AUTHORIZED LAB CONSOLE\r\n");
|
||||
printf("TRACK: NORMAL\r\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print the recurring interlocking status report once per cycle.
|
||||
*
|
||||
* Computes block length in meters, increments the fault poll counter, prints
|
||||
* block state, train authorization status, fault poll tally, and signal key
|
||||
* validation report, and re-emits the command prompt.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
static void print_status(void)
|
||||
{
|
||||
uint32_t metres = (uint32_t)(g_telemetry.block_length_km * 1000.0);
|
||||
g_fault_polls += 1u;
|
||||
printf("BLOCK STATE: %s\r\n", g_operator_state ? "STABLE" : "CRITICAL");
|
||||
printf("AUTO TRAIN: %s\r\n", g_dispatch_state ? "AUTHORIZED" : "HELD");
|
||||
printf("BLOCK LENGTH: %u M\r\n", metres);
|
||||
printf("FAULT POLLS: %u\r\n", g_fault_polls);
|
||||
printf("SIGNAL KEY: 0x%08X %s\r\n", g_signal_key,
|
||||
(g_signal_key == SIGNAL_SPEC) ? "OK" : "MISMATCH");
|
||||
printf("RESPONSE> ");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Append one received character to the passphrase buffer.
|
||||
*
|
||||
* Stores printable characters up to the maximum passphrase length boundary
|
||||
* and echoes the character back to the console for interactive typing feedback.
|
||||
*
|
||||
* @param ch Input character value.
|
||||
* @return None.
|
||||
*/
|
||||
static void append_char(int ch)
|
||||
{
|
||||
if (g_lineidx + 1u >= AUTH_PASSPHRASE_MAX_LEN) {
|
||||
return;
|
||||
}
|
||||
g_linebuf[g_lineidx] = (char)ch;
|
||||
g_lineidx += 1u;
|
||||
putchar_raw((char)ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove one character from the passphrase buffer.
|
||||
*
|
||||
* Decrements the buffer index and emits a backspace-space-backspace escape
|
||||
* sequence to erase the character on the user's terminal.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
static void drop_char(void)
|
||||
{
|
||||
if (g_lineidx == 0u) {
|
||||
return;
|
||||
}
|
||||
g_lineidx -= 1u;
|
||||
printf("\b \b");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Authenticate the completed passphrase against the Ouroboros gate.
|
||||
*
|
||||
* Terminates the string buffer, invokes auth_execute, handles policy violation
|
||||
* or authentication failure outputs, and resets the line buffer for the next input.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
static void submit_gate(void)
|
||||
{
|
||||
auth_result_t result;
|
||||
putchar_raw('\r');
|
||||
putchar_raw('\n');
|
||||
g_linebuf[g_lineidx] = '\0';
|
||||
result = auth_execute((const uint8_t *)g_linebuf, g_lineidx);
|
||||
if (result == AUTH_RESULT_POLICY_VIOLATION) {
|
||||
gpio_put(AUTH_LED_PIN, 0);
|
||||
printf("Enter exactly 12 lowercase words separated by spaces.\r\n");
|
||||
} else if (result == AUTH_RESULT_SUCCESS) {
|
||||
printf("AUTHORITY FRAME: VERIFIED\r\n");
|
||||
} else {
|
||||
gpio_put(AUTH_LED_PIN, 0);
|
||||
printf("Authentication failed.\r\n");
|
||||
}
|
||||
g_lineidx = 0u;
|
||||
printf("RESPONSE> ");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Poll the console for one passphrase input event.
|
||||
*
|
||||
* Reads a single character from standard input with zero timeout and routes
|
||||
* backspace, newline/carriage return, or printable characters to their respective
|
||||
* handlers.
|
||||
*
|
||||
* @param None.
|
||||
* @return None.
|
||||
*/
|
||||
static void poll_console(void)
|
||||
{
|
||||
int ch = getchar_timeout_us(0);
|
||||
while (ch != PICO_ERROR_TIMEOUT) {
|
||||
if ((ch == '\b') || (ch == 127)) {
|
||||
drop_char();
|
||||
} else if ((ch == '\r') || (ch == '\n')) {
|
||||
submit_gate();
|
||||
} else {
|
||||
append_char(ch);
|
||||
}
|
||||
ch = getchar_timeout_us(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Drive the DEEPLINE relay console and operator gate forever.
|
||||
*
|
||||
* Initializes standard I/O and the authentication engine, classifies the track
|
||||
* blocks, emits the initial system banner, and enters an infinite loop refreshing
|
||||
* the signal state, reporting status, and servicing the console every two seconds.
|
||||
*
|
||||
* @param None.
|
||||
* @return int Process exit code (never returns during normal operation).
|
||||
*/
|
||||
int main(void)
|
||||
{
|
||||
stdio_init_all();
|
||||
auth_init();
|
||||
classify_blocks();
|
||||
print_identity();
|
||||
while (true) {
|
||||
set_signal_state();
|
||||
print_status();
|
||||
poll_console();
|
||||
sleep_ms(2000u);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2026 Kevin Thomas
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// Author: Kevin Thomas
|
||||
// Email: kevin@mytechnotalent.com
|
||||
// GitHub: https://github.com/mytechnotalent
|
||||
// File: mbedtls_shims.c
|
||||
// Desc: Implements platform zeroization shims required by mbedTLS on RP2350.
|
||||
// Created: 2026
|
||||
|
||||
#include "mbedtls/platform_util.h"
|
||||
|
||||
/**
|
||||
* @brief Securely clear a memory region.
|
||||
*
|
||||
* Provides the mbedTLS platform zeroization hook for this firmware build.
|
||||
* The volatile pointer prevents the compiler from optimizing away the
|
||||
* clearing loop.
|
||||
*
|
||||
* @param buf Pointer to mutable memory region to clear.
|
||||
* @param len Number of bytes to clear.
|
||||
* @return None.
|
||||
*/
|
||||
void mbedtls_platform_zeroize(void *buf, size_t len)
|
||||
{
|
||||
volatile unsigned char *ptr = (volatile unsigned char *)buf;
|
||||
while (len-- > 0u) {
|
||||
*ptr++ = 0u;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user