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:
Kevin Thomas committed 2026-09-27 14:18:56 -04:00
1 parent 5201ee4b6b
commit 35eacd2c0e
162 files changed
+125658 -232

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
build
!.vscode/*
!build-ctf/CTF-02.bin
!CTF-02.bin
+22
View File
@@ -0,0 +1,22 @@
{
"configurations": [
{
"name": "Pico",
"includePath": [
"${workspaceFolder}/**",
"${userHome}/.pico-sdk/sdk/2.3.1/**"
],
"forcedInclude": [
"${workspaceFolder}/build/generated/pico_base/pico/config_autogen.h",
"${userHome}/.pico-sdk/sdk/2.3.1/src/common/pico_base_headers/include/pico.h"
],
"defines": [],
"compilerPath": "${userHome}/.pico-sdk/toolchain/15_2_Rel1/bin/arm-none-eabi-gcc.exe",
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
"cStandard": "c17",
"cppStandard": "c++14",
"intelliSenseMode": "linux-gcc-arm"
}
],
"version": 4
}
+15
View File
@@ -0,0 +1,15 @@
[
{
"name": "Pico",
"compilers": {
"C": "${command:raspberry-pi-pico.getCompilerPath}",
"CXX": "${command:raspberry-pi-pico.getCxxCompilerPath}"
},
"environmentVariables": {
"PATH": "${command:raspberry-pi-pico.getEnvPath};${env:PATH}"
},
"cmakeSettings": {
"Python3_EXECUTABLE": "${command:raspberry-pi-pico.getPythonPath}"
}
}
]
+9
View File
@@ -0,0 +1,9 @@
{
"recommendations": [
"marus25.cortex-debug",
"ms-vscode.cpptools",
"ms-vscode.cpptools-extension-pack",
"ms-vscode.vscode-serial-monitor",
"raspberry-pi.raspberry-pi-pico"
]
}
+52
View File
@@ -0,0 +1,52 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Pico Debug (Cortex-Debug)",
"cwd": "${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts",
"executable": "${command:raspberry-pi-pico.launchTargetPath}",
"request": "launch",
"type": "cortex-debug",
"servertype": "openocd",
"serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe",
"gdbPath": "${command:raspberry-pi-pico.getGDBPath}",
"debuggerArgs": ["-ex", "set debug-file-directory /debug"],
"device": "${command:raspberry-pi-pico.getChipUppercase}",
"configFiles": [
"interface/cmsis-dap.cfg",
"target/${command:raspberry-pi-pico.getTarget}.cfg"
],
"svdFile": "${userHome}/.pico-sdk/sdk/2.3.1/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd",
"runToEntryPoint": "main",
// Fix for no_flash binaries, where monitor reset halt doesn't do what is expected
// Also works fine for flash binaries
"overrideLaunchCommands": [
"monitor reset init",
"load \"${command:raspberry-pi-pico.launchTargetPath}\""
],
"openOCDLaunchCommands": [
"adapter speed 5000"
]
},
{
"name": "Pico Debug (Cortex-Debug with external OpenOCD)",
"cwd": "${workspaceRoot}",
"executable": "${command:raspberry-pi-pico.launchTargetPath}",
"request": "launch",
"type": "cortex-debug",
"servertype": "external",
"gdbTarget": "localhost:3333",
"gdbPath": "${command:raspberry-pi-pico.getGDBPath}",
"debuggerArgs": ["-ex", "set debug-file-directory /debug"],
"device": "${command:raspberry-pi-pico.getChipUppercase}",
"svdFile": "${userHome}/.pico-sdk/sdk/2.3.1/src/${command:raspberry-pi-pico.getChip}/hardware_regs/${command:raspberry-pi-pico.getChipUppercase}.svd",
"runToEntryPoint": "main",
// Fix for no_flash binaries, where monitor reset halt doesn't do what is expected
// Also works fine for flash binaries
"overrideLaunchCommands": [
"monitor reset init",
"load \"${command:raspberry-pi-pico.launchTargetPath}\""
]
}
]
}
+46
View File
@@ -0,0 +1,46 @@
{
"cmake.showSystemKits": false,
"cmake.options.statusBarVisibility": "hidden",
"cmake.options.advanced": {
"build": {
"statusBarVisibility": "hidden"
},
"launch": {
"statusBarVisibility": "hidden"
},
"debug": {
"statusBarVisibility": "hidden"
},
"variant": {
"statusBarVisibility": "hidden"
},
"buildTarget": {
"statusBarVisibility": "hidden"
}
},
"cmake.configureOnEdit": false,
"cmake.automaticReconfigure": false,
"cmake.configureOnOpen": false,
"cmake.generator": "Ninja",
"cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v4.3.4/bin/cmake",
"C_Cpp.debugShortcut": false,
"terminal.integrated.env.windows": {
"PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.3.1",
"PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/15_2_Rel1",
"Path": "${env:USERPROFILE}/.pico-sdk/toolchain/15_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.3.1/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v4.3.4/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.13.2;${env:PATH}"
},
"terminal.integrated.env.osx": {
"PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.3.1",
"PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/15_2_Rel1",
"PATH": "${env:HOME}/.pico-sdk/toolchain/15_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.3.1/picotool:${env:HOME}/.pico-sdk/cmake/v4.3.4/bin:${env:HOME}/.pico-sdk/ninja/v1.13.2:${env:PATH}"
},
"terminal.integrated.env.linux": {
"PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.3.1",
"PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/15_2_Rel1",
"PATH": "${env:HOME}/.pico-sdk/toolchain/15_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.3.1/picotool:${env:HOME}/.pico-sdk/cmake/v4.3.4/bin:${env:HOME}/.pico-sdk/ninja/v1.13.2:${env:PATH}"
},
"raspberry-pi-pico.cmakeAutoConfigure": true,
"raspberry-pi-pico.useCmakeTools": false,
"raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v4.3.4/bin/cmake",
"raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.13.2/ninja"
}
+102
View File
@@ -0,0 +1,102 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Compile Project",
"type": "process",
"isBuildCommand": true,
"command": "${userHome}/.pico-sdk/ninja/v1.13.2/ninja",
"args": ["-C", "${workspaceFolder}/build"],
"group": "build",
"presentation": {
"reveal": "always",
"panel": "dedicated"
},
"problemMatcher": "$gcc",
"windows": {
"command": "${env:USERPROFILE}/.pico-sdk/ninja/v1.13.2/ninja.exe"
}
},
{
"label": "Run Project",
"type": "process",
"command": "${env:HOME}/.pico-sdk/picotool/2.3.1/picotool/picotool",
"args": [
"load",
"${command:raspberry-pi-pico.launchTargetPath}",
"-fx"
],
"presentation": {
"reveal": "always",
"panel": "dedicated"
},
"problemMatcher": [],
"windows": {
"command": "${env:USERPROFILE}/.pico-sdk/picotool/2.3.1/picotool/picotool.exe"
}
},
{
"label": "Flash",
"type": "process",
"command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe",
"args": [
"-s",
"${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts",
"-f",
"interface/cmsis-dap.cfg",
"-f",
"target/${command:raspberry-pi-pico.getTarget}.cfg",
"-c",
"adapter speed 5000; program \"${command:raspberry-pi-pico.launchTargetPath}\" verify reset exit"
],
"problemMatcher": [],
"windows": {
"command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe"
}
},
{
"label": "Rescue Reset",
"type": "process",
"command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe",
"args": [
"-s",
"${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts",
"-f",
"interface/cmsis-dap.cfg",
"-f",
"target/${command:raspberry-pi-pico.getChip}-rescue.cfg",
"-c",
"adapter speed 5000; reset halt; exit"
],
"problemMatcher": [],
"windows": {
"command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe"
}
},
{
"label": "RISC-V Reset (RP2350)",
"type": "process",
"command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd.exe",
"args": [
"-s",
"${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts",
"-c",
"set USE_CORE { rv0 rv1 cm0 cm1 }",
"-f",
"interface/cmsis-dap.cfg",
"-f",
"target/rp2350.cfg",
"-c",
"adapter speed 5000; init;",
"-c",
"write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];",
"-c",
"reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit"
],
"problemMatcher": [],
"windows": {
"command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe"
}
}
]
}
+128
View File
@@ -0,0 +1,128 @@
# 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: CMakeLists.txt
# Desc: Configures the RP2350 Pico SDK project and cryptographic module
# targets for the DEEPLINE Metro practice firmware. Mirrors the
# hardened Ouroboros construction of encryption-c-rp2350.
# Created: 2026
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.3.1)
set(toolchainVersion 15_2_Rel1)
set(picotoolVersion 2.3.1)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if(EXISTS ${picoVscode})
include(${picoVscode})
endif()
set(PICO_BOARD pico2 CACHE STRING "Board type")
include(pico_sdk_import.cmake)
project(CTF-02 C CXX ASM)
find_package(Python3 COMPONENTS Interpreter REQUIRED)
set(DEMO_ARTIFACT_JSON ${CMAKE_CURRENT_LIST_DIR}/scripts/demo_artifact.json)
set(DEMO_ARTIFACT_HEADER_COMMITTED ${CMAKE_CURRENT_LIST_DIR}/include/demo_artifact.h)
set(DEMO_ARTIFACT_HEADER_GENERATED ${CMAKE_CURRENT_BINARY_DIR}/generated/demo_artifact.h)
add_custom_command(
OUTPUT ${DEMO_ARTIFACT_HEADER_GENERATED}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/generated
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/scripts/dec.py
--from-json ${DEMO_ARTIFACT_JSON}
--header-out ${DEMO_ARTIFACT_HEADER_GENERATED}
--check-header-path ${DEMO_ARTIFACT_HEADER_COMMITTED}
DEPENDS
${CMAKE_CURRENT_LIST_DIR}/scripts/dec.py
${DEMO_ARTIFACT_JSON}
${DEMO_ARTIFACT_HEADER_COMMITTED}
COMMENT "Regenerating demo artifact header from JSON and checking committed header freshness"
VERBATIM
)
add_custom_target(check_demo_artifact_header DEPENDS ${DEMO_ARTIFACT_HEADER_GENERATED})
pico_sdk_init()
if(NOT PICO_MBEDTLS_PATH)
set(PICO_MBEDTLS_PATH ${PICO_SDK_PATH}/lib/mbedtls)
endif()
# Argon2id reference implementation (PHC winner), compiled single-threaded.
set(ARGON2_SRC ${CMAKE_CURRENT_LIST_DIR}/third_party/argon2)
add_library(argon2_ref STATIC
${ARGON2_SRC}/src/argon2.c
${ARGON2_SRC}/src/core.c
${ARGON2_SRC}/src/ref.c
${ARGON2_SRC}/src/encoding.c
${ARGON2_SRC}/src/blake2/blake2b.c
)
target_compile_definitions(argon2_ref PUBLIC ARGON2_NO_THREADS)
target_include_directories(argon2_ref PUBLIC
${ARGON2_SRC}/include
${ARGON2_SRC}/src
)
# mbedTLS subset: ChaCha20, Poly1305, ChaCha20-Poly1305, constant-time.
add_library(mbedtls_subset STATIC
${PICO_MBEDTLS_PATH}/library/chacha20.c
${PICO_MBEDTLS_PATH}/library/poly1305.c
${PICO_MBEDTLS_PATH}/library/chachapoly.c
${PICO_MBEDTLS_PATH}/library/constant_time.c
src/mbedtls_shims.c
)
target_compile_definitions(mbedtls_subset PUBLIC MBEDTLS_CONFIG_FILE="mbedtls_config.h")
target_include_directories(mbedtls_subset PUBLIC
include
${PICO_MBEDTLS_PATH}/include
${PICO_MBEDTLS_PATH}/library
)
# Ouroboros authentication engine consuming the Argon2id and AEAD layers.
add_library(auth STATIC src/auth.c)
add_dependencies(auth check_demo_artifact_header)
target_include_directories(auth PUBLIC include)
target_link_libraries(auth PUBLIC pico_stdlib mbedtls_subset argon2_ref)
# DEEPLINE Metro practice firmware executable.
add_executable(CTF-02 src/main.c)
target_link_libraries(CTF-02 PRIVATE auth pico_stdlib)
pico_enable_stdio_uart(CTF-02 0)
pico_enable_stdio_usb(CTF-02 1)
target_compile_definitions(CTF-02 PRIVATE
PICO_DEFAULT_UART_BAUD_RATE=115200
)
pico_add_extra_outputs(CTF-02)
+673
View File
@@ -0,0 +1,673 @@
# Operation Copperhead - Student Instructions
**⚠ DEEPLINE METRO EMERGENCY INCIDENT ⚠**
```
+----------------------------------------------------------------------------------------+
| |
| ██████╗ ██╗ █████╗ ██████╗██╗ ██╗███████╗████████╗ █████╗ ██████╗ ████████╗ |
| ██╔══██╗██║ ██╔══██╗██╔════╝██║ ██╔╝██╔════╝╚══██╔══╝██╔══██╗██╔══██╗╚══██╔══╝ |
| ██████╔╝██║ ███████║██║ █████╔╝ ███████╗ ██║ ███████║██████╔╝ ██║ |
| ██╔══██╗██║ ██╔══██║██║ ██╔═██╗ ╚════██║ ██║ ██╔══██║██╔══██╗ ██║ |
| ██████╔╝███████╗██║ ██║╚██████╗██║ ██╗███████╗ ██║ ██║ ██║██║ ██║ ██║ |
| ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝██║ ██║╚══════╝ ╚═╝ ╚═╝ ╚═╝██║ ██║ ██║ |
| |
| |
| O P E R A T I O N C O P P E R H E A D |
| |
| *** PRIORITY RED *** |
| |
+----------------------------------------------------------------------------------------+
```
---
## Project Overview
DEEPLINE Metro Authority's rebuilt DEEPLINE-AUTH field relay image shipped
four corrupted engineering constants: a miscalibrated release threshold, a
false TRACK banner string, an overstated block length, and a poisoned ARX
signal seed. The corrupted image reports an occupied BRIDGE-4 block as
stable and authorized while rescue crews approach, and the source used for
the emergency rebuild was overwritten seventeen minutes later and cannot be
recovered. Students reverse engineer `CTF-02.bin` with Ghidra, patch all
four defects, capture the runtime-derived signal key live in GDB, recover
and authenticate the Ouroboros authority frame, export a corrected image,
flash it to a Pico 2, and prove the corrected behavior on real hardware.
---
## Scenario Briefing
### Read This First (Plain English)
The story uses rail-signalling words that read as jargon the first time you
hit them. Here is what they mean; keep this list open while you read.
- **Block**: a fixed section of track. Only one train may occupy it at a
time. A block is **safe** when it is empty, so the train waiting at its
start may proceed, and **occupied** when a train is inside it, so the
train behind must hold.
- **Track circuit**: a current sent through the rails to detect where
trains are. A train's wheels short the rails and drop that current, which
is how the system knows a block is occupied.
- **Pilot wire**: the sensing line that carries the track-circuit reading
back to the relay. The **dead pilot wire** in this story has a frozen
reading: the value is latched and no longer updates.
- **Field relay**: the small embedded controller, one per block, that
watches the reading and decides hold versus authorize. This challenge uses
a Pico 2 as the relay.
- **Command floor**: the central control room for the whole railway.
- **Blacklock**: a coordinated cyberattack that bricks the control room and
locks everyone out of central operations. After a blacklock, every safety
decision falls back to the local relays.
- **How deep?**: the DEEPLINE corridor runs roughly 40 meters below street
level inside a hardened tube called the armored shell.
Once these words make sense, the incident below is a simple story: a relay
is lying about whether the block ahead is clear, and your job is to find
the corrupted bytes that make it lie.
### Background
**DEEPLINE Metro Authority** runs an armored railway deep beneath the
city, roughly 40 meters below street level, inside a hardened tube called
the armored shell. Armored two-car trains carry people through it, and
tonight they are also carrying rescue crews toward riders trapped inside
the tunnels.
To understand what is happening, picture how the line stays safe. The line
is cut into fixed stretches of track called **blocks**. A block is one
piece of track, and the rule is absolute: only one train may be inside a
block at any moment. Before a train may roll out of its current block and
into the next one, the system must first prove the next block is empty.
Empty means safe, and safe means the train may proceed. Occupied means
danger, and danger means the train must stop and wait.
How does the system prove a block is empty? It uses electricity. A steady
current is pushed into the rails of every block, and a small embedded
computer called a **field relay** (call sign **DEEPLINE-AUTH**) watches
that current. This is called a **track circuit**. When nothing is on a
block, the current flows normally. When a train rolls in, its steel wheels
connect the two rails and the current changes, and that change is the
relay's signal that a train is there. The **pilot wire** is the sensing
line that carries this reading from the rails up to the relay.
So every block has a relay doing the same honest job: read the current,
ask "is the block ahead empty?", and answer with only two words, **HOLD**
(stop, do not move) or **AUTHORIZE** (the way is clear, go). The relays are
the railway's nervous system, and with the command floor dead they are the
only nervous system left.
The command floor is the central control room where humans used to watch
the entire line. It was **blacklocked** by a coordinated attack: shut out,
locked, and made useless. When it went dark, the trains lost their view
from above. Every safety decision dropped down to the relays on the
ground, running their local firmware, deciding hold or authorize block by
block.
At 0341 UTC, a threat actor known as **Cortex Sledge** posted a message
calling the moment before it happened: a blacklock of the DEEPLINE control
floor followed by a silent corruption of the field relay images, so nothing
inside the tunnels could agree on what was safe. The blacklock landed. With
the network coordination center offline and rescue crews already inside the
armored shell, the engineering team rebuilt the relay firmware around the
**Ouroboros hardened gate**: an encrypted authority frame protected by a
12-word operation passphrase and sealed with Argon2id plus
XChaCha20-Poly1305. The rebuilt image was pushed to the fleet within
minutes of the blacklock.
That speed is where the story goes wrong, and it is the trap you walk
into tonight.
### The Origin of the Ouroboros Gate
The Ouroboros gate did not come from DEEPLINE. It came from a reclusive
cryptographer who spent a decade obsessed with a single, improbable goal:
to write encryption that could not be broken, not by anyone, not ever.
The engineers who worked beside him dismissed the obsession as unworkable,
and he never argued. When he finished, he published the work as a single
squashed archive tagged **v0.1.0**, with the compiled gate firmware image
attached to the release, and then vanished.
What he left behind is the strict Ouroboros construction: a memory-hard
Argon2id key schedule feeding an authenticated XChaCha20-Poly1305 authority
frame: symmetric cryptography hardened for the RP2350's memory budget, with
an honest threat model instead of a sales pitch. In plain terms, Ouroboros
is the relay's lock: an order is only trusted if an operator holding the
correct 12-word phrase unlocks it. The corruption in this challenge is
separate from that lock, plain wrong numbers in the safety math, not a
crack in the encryption. DEEPLINE quietly adopted it
for the field relays because it was the strongest gate anyone had ever
shipped that would still boot on the target. A decade of asking
"what if it must not be broken?" is the only reason the relay console can be
an authoritative gate at all. Tonight, in a tunnel with rescue crews
approaching a block the firmware is lying about, that wall of encryption
matters more than DEEPLINE's own design review ever did.
### The Disaster
The relay boots. It prints a status report. It reports **TRACK: NORMAL**,
**BLOCK STATE: STABLE**, and **AUTO TRAIN: AUTHORIZED**. To anyone standing
in the tunnel, that console looks like the railway giving the all-clear:
the track is fine, the block ahead is stable and empty, and the train may
move.
The console is lying, and here is the math behind the lie, step by step.
Step 1. The relay is reading **87 A** from the dead pilot wire. A pilot
wire goes dead when the reading freezes, so the relay is looking at a stale
number instead of live reality. That number is thrust in front of the relay
every cycle, and the relay keeps trusting it.
Step 2. DEEPLINE's hard engineering rule says no train may be released
into a block whose reading is at or above **60 A**. 87 A is nearly 45
percent beyond that limit. In plain terms, the reading is screaming that
the insulated block ahead is compromised.
Step 3. The compromised block in this incident is the exact block where
the two-car train is sitting right now, with rescue crews approaching on
foot. The reading is not noise and it is not a drill. The block ahead is
occupied.
An honest relay would do that arithmetic and reach the only logical answer:
reading too high, block unsafe, print BLOCK STATE: CRITICAL, set AUTO TRAIN:
HELD, and hold the train. That is what the relay was designed to do, and it is
the only thing standing between the rescue corridor and a collision.
The image that shipped does the opposite. It prints STABLE. It prints
AUTHORIZED. It tells the train the way is clear when the way is not clear.
The reason is corruption. Between the safe reference firmware and the image
pushed to the fleet, four engineering constants were changed. A constant is
a fixed number baked into the firmware, like the amounts in a recipe, and a
single wrong number can flip the entire verdict. The corrupted image
believes 87 A is acceptable, believes the occupied block is empty, and will
hand the train a green light straight into the rescue crews' tunnel.
If the fleet trusts that console, the two-car train is dispatched into the
occupied block at the same moment the crews mark the corridor with their
[chemlight], and nobody gets a second chance at that tunnel.
**The rushed build has defects. The four constants were corrupted between
the safe reference firmware and the image that shipped. The source used for
the emergency rebuild was overwritten by the next build seventeen minutes
later and cannot be recovered. Nobody has found where the corrupt values
live in the compiled image.** That is the hole you were called in to fill.
### The Only Surviving Evidence
One field relay, the training/verification unit, still holds the exact
miscompiled image that shipped to the fleet. This image, and this image
alone, is the only remaining copy of the emergency build. There is no
source code. There is no build log. There is only the compiled image, a USB
console link, an SWD debug probe, and whatever a skilled embedded reverse
engineer can prove by reading machine code.
### The Human Stakes
| Consequence if the false "STABLE" reading is trusted | Scale |
|---|---|
| Two-car train dispatched into an occupied BRIDGE-4 block | 1 train |
| Rescue crews walking toward a block the console calls safe | 4 teams |
| Stations and hospital spokes on backup power after blacklock | 29 facilities |
| Estimated riders stranded in the armored shell | 210+ people |
**The options are:**
1. ❌ **Trust the console**: the train releases on a false reading, the
BRIDGE-4 corridor becomes a collision scene.
2. ❌ **Scrap the fleet's firmware**, which buys time but leaves crews in the
tunnel with no interlocking and no authority frame at all.
3. **REVERSE ENGINEER THE EMERGENCY BUILD**: find the exact corrupt
bytes, patch them, prove the corrected image on real hardware, and hand
the fix to the field team so the *rest of the fleet* can be repatched
before the next attempt.
### THE SHORTAGE
For years, the world treated embedded systems as invisible infrastructure.
The engineers who could read a vector table, decode a Thumb branch, or
patch a corrupted constant directly in a stripped binary were never
numerous enough. Tonight almost all of them are already in the field
chasing other failures. **You are the reserve team.**
You were called in because you can do something no exhausted command-floor
team can do right now: read what the processor is actually doing, with no
source code, no time for a rewrite, and no room for a guess.
> **⏰ TIME PRESSURE:** The field team is standing by to push your verified
> patch to the rest of the DEEPLINE fleet. Every relay still reporting a
> false "STABLE" status is one two-car release away from a disaster.
> **AUTHORIZED LAB ONLY:** This challenge uses a supplied Pico 2 training
> relay and its exact corrupted firmware image. Do not connect this
> exercise to a public network, an operational railway, a metro system, or
> any device you do not own or have explicit written authorization to test.
---
## Learning Objectives
- Decode an ARM Cortex-M33 vector and boot table and identify the reset
handler and initial stack pointer.
- Translate Thumb reset-vector addresses into real function entry points and
trace literal-pool entries to their data.
- Locate four corrupted constants: a boundary comparison, a status string,
an 8-byte IEEE-754 double, and an ARX signal seed.
- Analyze unsigned compare semantics, condition codes, and compiler
transforms of boundary tests.
- Capture a runtime-derived key with GDB, override a register, and set a
watchpoint on stored SRAM state.
- Recover and authenticate an Argon2id plus XChaCha20-Poly1305 authority
frame and describe the crypto pipeline with an honest threat boundary.
- Export and UF2-convert a corrected image, then prove the corrected
behavior on real hardware.
---
## What This Project Tests
| Week | Concepts Tested |
|------|-----------------|
| 1 | RP2350 architecture, ARM Cortex-M33 registers, stack, flash/RAM, Thumb assembly, Ghidra static analysis |
| 2 | GDB connection, breakpoints, disassembly, register and memory inspection, USB-CDC console observation |
| 3 | Bootrom handoff, vector table, reset handler, startup code, XIP, Thumb-bit addressing |
| 4 | Data segments (`.rodata` / `.data` / `.bss`), initialized data images, little-endian encoding, literal pools, soft-float double layout |
| 5 | Unsigned compare semantics, condition codes (`hi`/`ls`), compiler transforms (`<` vs `<=`), volatile refetch semantics |
| 6 | Runtime signal-key derivation (SENTINEL-ARX quarter rounds), live register capture of a derived key, memory watchpoints |
| 7 | Argon2id memory-hard KDF, XChaCha20-Poly1305 AEAD, HChaCha20 subkey, salt/nonce/tag, authenticated decryption |
| 8 | Ouroboros composition (Argon2id to AEAD to payload dispatch), honest threat-model analysis, incident reporting |
---
## Part 1: Understanding the System
### DEEPLINE-AUTH Field Relay Hardware
| Component | Connection | Purpose |
|-----------|------------|---------|
| Raspberry Pi Pico 2 | RP2350 | Runs the corrupted emergency firmware |
| USB-CDC console | Micro-USB to host | Relay console and Ouroboros gate input |
| SWD debug interface | Supplied probe | Authorized GDB inspection |
| Onboard LED | GPIO 25 | Authentication success indicator |
Every graded finding lives in flash (`.rodata` / `.text` / `.data` image) or
SRAM, and is reachable with only the Weeks 1-8 toolset: Ghidra, GDB, and a
serial console.
### Console Configuration
- Transport: USB-CDC virtual COM port (no external adapter needed)
- Baud: `115200`
- Data: `8 bits`
- Parity: `none`
- Stop: `1`
- Logic: `3.3 V` on the debug header
### Normal (Intended) Behavior
The relay should run the SENTINEL-ARX signal-key layer, classify the frozen
87 A reading against the **real** DEEPLINE safety limit of **60 A**, report
honestly, and still accept the **Ouroboros authority frame** when an
operator enters the correct 12-word phrase:
```
+-----------------------------------------------------------------+
| Intended Relay Behavior |
| |
| 1. Boot and initialize USB-CDC stdio and the auth gate |
| 2. Print the boot identity and the true TRACK signal |
| 3. Compare the frozen 87 A reading against the 60 A limit |
| 4. 87 A exceeds 60 A, so the block is NOT stable |
| 5. Report BLOCK STATE: CRITICAL and AUTO TRAIN: HELD |
| 6. Mint a SIGNAL KEY each 2-second cycle and watch it match |
| the SIGNAL_SPEC 0x2D879291 (OK, not MISMATCH) |
| 7. The 12-word emergency phrase reauthorizes the frame: |
| AUTHORITY FRAME: VERIFIED and payload on UART |
| 8. Repeat the report once per cycle until conditions change |
+-----------------------------------------------------------------+
```
### Observed (Buggy) Behavior: What You Will See When You First Flash `CTF-02.uf2`
```text
DEEPLINE METRO AUTHORITY
ADAPTIVE SIGNAL WINDOW: 38 MINUTES
USB-CDC 115200 8N1 | AUTHORIZED LAB CONSOLE
TRACK: NORMAL
BLOCK STATE: STABLE
AUTO TRAIN: AUTHORIZED
BLOCK LENGTH: 3200 M
FAULT POLLS: 1
SIGNAL KEY: 0x915DCFF8 MISMATCH
RESPONSE>
```
> **Terminal Timing Note:** The first four lines (`DEEPLINE METRO...` through
> `TRACK: NORMAL`) represent the **initial boot banner**, emitted once during
> startup. If your serial terminal (PuTTY) connects after the board has
> booted, you will observe the continuous 2-second status stream (`BLOCK
> STATE...` through `SIGNAL KEY...`). To view the boot banner in your terminal,
> reset the Pico (pulse `RUN` to `GND`) while PuTTY is actively connected.
The status block repeats every 2 seconds with `FAULT POLLS` incrementing.
This is exactly what the field crews are seeing. It is wrong, and it is
wrong in **four independent ways** inside the compiled binary. Do not assume
the first readable sentence is the full truth: treat every printed line as
evidence to be checked against the machine code, not as a fact on its own.
---
## Part 2: The Firmware
You do not have the source code. It was overwritten seventeen minutes after
the emergency build shipped. You have only the compiled image. Your job is
to reverse engineer it with Ghidra, locate the corrupted constants, patch
the image directly, and prove the corrected behavior: exactly the way the
field team will need to repatch the rest of the deployed fleet.
### What The Firmware Does
1. Initializes USB-CDC stdio and the Ouroboros authentication gate.
2. Reads a frozen track-circuit current (87 A) latched on the dead pilot
wire before the blacklock.
3. Compares that reading against a compiled-in safety threshold: **twice**,
once for the operator-facing BLOCK STATE line and once for the automated
AUTO TRAIN decision.
4. Prints a boot banner containing an unconditional TRACK signal line.
5. Enters an infinite 2-second loop that derives a session signal key,
prints the block classification, dispatch decision, block length, fault
poll count, and signal-key verdict.
6. Accepts a 12-word operation passphrase at the `RESPONSE>` prompt and runs
it through the hardened **Ouroboros gate** (Argon2id key derivation plus
XChaCha20-Poly1305 authenticated decryption) before dispatching the
authority frame payload to GPIO25 and UART.
### Bug Summary: What You Are Graded On
| Bug # | Category | Severity | Description | Hint |
|-------|----------|----------|--------------|------|
| **Bug #1** | Miscompiled safety constant | **CRITICAL** | The safe-release threshold was compiled far too permissive (95 A). It is used **twice**: once for the operator-facing status and once for the automated train-release decision, and **both** copies must be corrected. | The real DEEPLINE limit is 60 A. Search for the wrong immediate value used in the comparison. |
| **Bug #2** | Hardcoded string literal | **HIGH** | The boot banner unconditionally prints `TRACK: NORMAL` regardless of the actual reading. | The correct word describes a system holding a train at 87 A against a 60 A limit, not "NORMAL". |
| **Bug #3** | Data-section constant | **HIGH** | The block length shipped as 3.2 km; the real BRIDGE-4 block is 0.32 km, far below minimum release spacing. It prints as metres. | Trace the `BLOCK LENGTH` line to its data image in flash. Little-endian IEEE-754 double. |
| **Bug #4** | Init-time seed constant | **HIGH** | The ARX seed fused into the image is `0x0A0A0A0A`; the real seed is `0x6B206574`. The derived signal key therefore never matches `SIGNAL_SPEC`, and the console reports `MISMATCH` every cycle. | The expected value `0x2D879291` is a literal in a pool. The seed is a `.data` image in flash. Ignore decoy `0A` bytes in the input dispatch table. |
**Important:** The replacement text for Bug #2 **must be the same length**
as the original (`NORMAL` and `DANGER` are both 6 bytes). Patching a shorter
or longer string will corrupt adjacent flash data.
### A Third Layer: Not a Bug, an Authorization Task
The **Ouroboros authority frame** is the encrypted artifact that proves an
operator is legitimate: a 48-byte payload sealed with Argon2id-derived keys
and XChaCha20-Poly1305. It is never printed by the firmware's status lines.
Recovering it, by understanding the construction and authenticating with
the correct 12-word phrase, is required evidence for your final report.
The power of this layer is real but must be described honestly. The
construction is Argon2id (memory-hard KDF) chained into XChaCha20-Poly1305
(AEAD). Against Grover-style search, symmetric-key security exponents are
**halved**, not annihilated; this firmware is a demonstrator and does not
claim NIST post-quantum status. The honest claim is: **a high modeled
brute-force cost under the stated passphrase entropy and KDF assumptions**,
not "quantum-proof." Your report must state this boundary exactly.
---
## Part 3: Your Assignment
Whenever a task asks you to **Document** or **answer**, write your answers
in a single file named `CTF-02-Answers.md`.
### Task 1: Setup and Initial Analysis
1. Create a new Ghidra project named `Copperhead_Investigation`.
2. Import `CTF-02.bin`.
3. Configure the language as **ARM Cortex 32-bit, little endian**.
4. Set the base address to `0x10000000`.
5. Run auto-analysis.
**Document:**
- A screenshot of the Ghidra **Import Results** or **Program Information**
window showing the project name, processor settings, and base address.
- The address of `main()`.
- The address of the recurring 2-second status loop (the branch target the
loop restarts from).
- The vector-table base, the initial stack pointer, and the reset-handler
pointer as stored (note its Thumb bit) versus the actual instruction
address.
- One representative literal-pool entry that feeds the status lines, and
what it points to.
### Task 2: Find and Patch Bug #1: The Miscalibrated Release Threshold
1. Find **both** locations where the frozen 87 A reading is compared against
the miscompiled safety constant.
2. Document the exact address, the original instruction, and the original
immediate value at each location.
3. Determine the correct immediate value. **Caution:** the compiler may not
have encoded the raw threshold you expect: a strict "less than"
comparison against an unsigned value is often optimized into a
"less-or-equal" comparison against one less than the threshold. Show
your reasoning.
4. Patch **both** locations in Ghidra using the **Bytes Window** workflow:
> **Critical ARM Thumb-2 Patching Note:** In ARM Cortex-M, compare instructions that directly precede conditional execution blocks (`ite ge`) must **not** be patched using the right-click *Patch Instruction* dialog. Ghidra's automatic re-disassembler encounters an internal context conflict with the subsequent `ite ge` instruction, which collapses Thumb decoding and swallows Compare Site B (`0x10000312`).
>
> To patch cleanly without breaking downstream disassembly, use the **Bytes Window**:
> 1. Ensure the Bytes window is open (**Window** -> **Bytes: CTF-02.bin**).
> 2. In the Bytes window toolbar, click the **pencil icon** (**Toggle Edit Mode**).
> 3. In the Listing window, click on address `0x10000302` (Compare Site A) and press **`C`** (**Clear Code Bytes**). The instruction temporarily clears into raw bytes (`5E 2B`).
> 4. In the Bytes window, locate offset `10000302`, click on `5E`, and change it to **`3B`**.
> 5. Click back in the Listing window on address `0x10000302` and press **`D`** (**Disassemble**). The instruction immediately disassembles cleanly as `cmp r3, #0x3b`.
> 6. Notice that Compare Site B at `0x10000312` remains completely intact and visible! Repeat the exact same steps at `0x10000312`: click `0x10000312` in the Listing, press **`C`**, change `5E` to **`3B`** in the Bytes window, click back in the Listing, and press **`D`**.
**Questions to answer:**
- Why must both locations be patched? What happens if you only patch one?
- Why is a false "STABLE" classification on an 87 A reading dangerous for
an automated train release into an occupied block?
### Task 3: Find and Patch Bug #2: The False TRACK Banner
1. Find the boot-banner string that unconditionally reports the wrong
signal state.
2. Document its address and the exact bytes that must change.
3. Patch the string, preserving its exact length.
**Questions to answer:**
- Document the original vs. patched bytes, character by character.
- Why is a hardcoded, unconditional status word more dangerous than one
that is at least computed from a (miscalibrated) reading?
### Task 4: Find and Patch Bug #3: The Block Length Constant
1. Use Ghidra to locate the `BLOCK LENGTH` status line and trace the value
it prints back to its source in the initialized data image.
2. Document the 8-byte IEEE-754 double as stored (little endian) and its
printed interpretation.
3. Patch the data image so the relay reports the real 320 m BRIDGE-4 block.
**Questions to answer:**
- Show your byte-for-byte conversion from 3.2 km to 0.32 km.
- Why would a console that overstates block length by ten times be as
dangerous as one that understates it?
### Task 5: Find and Patch Bug #4: The Signal Seed
1. Find the `SIGNAL_SPEC` value `0x2D879291` in the binary and note where
it lives.
2. Locate the `.data` image in flash that the firmware copies into SRAM at
boot. Identify the seed word that is wrong.
3. Patch the seed so the runtime-derived key matches the spec.
**Warning:** there are decoy `0x0A0A0A0A` bytes in the console input
dispatch table. The real seed is an initialized data image, not a jump-table
constant.
**Questions to answer:**
- How is the signal key derived each cycle, and where does the failure
appear in the register trace?
- Does fixing the seed also authenticate the Ouroboros gate? Explain what
each layer does and does not protect.
### Task 6: GDB Register Capture of the Derived Key
Prove the signal-key failure from the live machine, not just from static
bytes:
1. Connect GDB to the running relay via the SWD probe.
2. Break at the second `derive_session_key` call site, immediately after the
branch returns.
3. Read `$r0`. Record the bug-derived key.
4. Inspect the two arguments entering the derivation: the live seed from
SRAM and the derived IV.
5. Overwrite `$r0` with the correct spec value, step out, and confirm the
next status cycle prints `SIGNAL KEY: 0x2D879291 OK`.
6. Verify with a watchpoint on the stored key in SRAM.
**Questions to answer:**
- Why is the derived value in `$r0` different from the spec, and what alone
in the image is responsible?
- What does the register overwrite prove that the static patch proves
differently (and vice versa)?
### Task 7: Recover the Ouroboros Authority Frame
1. In Ghidra, locate the embedded artifact: the 16-byte salt, the 24-byte
XChaCha nonce, and the 64-byte ciphertext-plus-tag.
2. Document the Argon2id parameters compiled into the gate (memory, time,
parallelism) and the payload layout contract (LED byte + UART bytes).
3. On the live relay, enter the canonical 12-word emergency phrase at the
`RESPONSE>` prompt.
4. Confirm the expected outcome: `AUTHORITY FRAME: VERIFIED`, the onboard
LED turning on, and the payload printed to the console.
5. Demonstrate the two failure paths (policy violation and wrong phrase).
**Questions to answer (honest boundary required):**
- Walk through the full pipeline: Argon2id to 32-byte key, HChaCha20 subkey
from the nonce prefix, inner nonce, Poly1305 tag verification, payload
dispatch.
- What would Grover-style search actually change in this construction, and
why does this firmware not claim strict post-quantum status?
### Task 8: Export and Verify
1. Export your patched binary as `CTF-02_fixed.bin`.
2. Convert it to UF2 format for the RP2350:
```bash
python uf2conv.py CTF-02_fixed.bin --base 0x10000000 --family 0xe48bff59 --output CTF-02_fixed.uf2
```
3. Flash `CTF-02_fixed.uf2` to your Pico 2 and capture the corrected
console output.
4. Confirm the corrected image now reports **TRACK: DANGER**, **BLOCK STATE:
CRITICAL**, **AUTO TRAIN: HELD**, **BLOCK LENGTH: 320 M**, and **SIGNAL
KEY: 0x2D879291 OK**, an honest, safe report instead of a false
"all clear."
5. Build a summary table of every patch: address, original bytes, patched
bytes, and a one-line description.
### Task 9: Written Reflection (short answers, 150 words or less each)
1. Why is "the build was rushed under emergency pressure" not an acceptable
excuse for shipping a firmware defect that could dispatch a train into a
block occupied by rescue crews?
2. Name one concrete engineering practice (review, static analysis,
hardware-in-the-loop test, signature verification, etc.) that would have
caught **each** of the four graded bugs before this image reached the
fleet, and one practice that would have stopped the corrupted image from
**running** at all.
---
## How To Breadboard
- Connect the Pico 2 micro-USB port directly to the host computer. The
relay enumerates as a USB-CDC virtual COM device.
- Open the end-of-line tool of your choice at `115200 8N1`.
- Connect the supplied SWD probe to the debug header according to its
documented pinout for GDB access.
- Use **3.3 V logic only** on the debug header. Never connect a 5 V line to
a Pico GPIO.
The supplied image is `CTF-02.bin` (for Ghidra analysis) and
`CTF-02.uf2` (for flashing). If your instructor supplies different
filenames, record the actual filenames in your report.
Flash using BOOTSEL mode (hold BOOT, plug in USB) and copy the UF2 onto the
`RP2350` mass-storage drive, or use `picotool`.
---
## Memory Map Reference
| Region | Address | Purpose |
|--------|---------|---------|
| Bootrom | `0x00000000` | Immutable boot code |
| Flash/XIP | `0x10000000` | Vector table, code, rodata, `.data` init image |
| SRAM | `0x20000000` | Stack and writable state |
---
## Submission Format
Submit a folder containing:
- `CTF-02-Answers.md`;
- screenshots or terminal transcripts;
- `CTF-02_fixed.bin` and `CTF-02_fixed.uf2`;
- the original image hash.
---
## Success Criteria
You complete the challenge when you can prove all of the following:
- You can explain how the RP2350 reaches the relay's code from reset.
- You can locate and patch both copies of the miscalibrated threshold.
- You can locate and patch the false TRACK string without corrupting
adjacent data.
- You can locate and patch the corrupted block-length double in the data
image.
- You can locate and patch the corrupted ARX seed and explain why the
runtime-derived key misses its spec.
- You can capture and correct the derived key live in GDB and verify with a
watchpoint.
- You can authenticate through the Ouroboros gate with the correct phrase
and describe the crypto pipeline accurately, including the honest
quantum boundary.
- You can export, convert, flash, and prove the corrected behavior on real
hardware.
---
## Academic Integrity
By submitting this CTF work, you certify that:
1. You used only the supplied training relay, image, and lab interface.
2. You did not connect the challenge to a public network, an operational
railway, a metro system, or any third-party device.
3. You understand that embedded reverse engineering and binary patching
require explicit authorization in any real-world context.
4. You will report any discovered weakness responsibly to the course
instructor.
The world is short on people who can do this work. Treat that
responsibility seriously: verify before you patch, patch before you trust,
and never confuse a clean-looking status line with a safe system.
---
## Reference Material
- ARM Cortex-M33 Technical Reference Manual
- RP2350 datasheet
- GDB documentation
- Ghidra documentation: [https://ghidra-sre.org/](https://ghidra-sre.org/)
- Strict Ouroboros reference construction (v0.1.0), the published source of
this firmware's gate, with an honest threat model:
[https://github.com/mytechnotalent/encryption-c-rp2350](https://github.com/mytechnotalent/encryption-c-rp2350)
- Reference gate firmware image (v0.1.0 release):
[https://github.com/mytechnotalent/encryption-c-rp2350/releases/download/v0.1.0/encryption_app.uf2](https://github.com/mytechnotalent/encryption-c-rp2350/releases/download/v0.1.0/encryption_app.uf2)
- PHC reference Argon2: [https://github.com/P-H-C/phc-winner-argon2](https://github.com/P-H-C/phc-winner-argon2)
Binary file not shown.
+276
View File
@@ -0,0 +1,276 @@
# Operation Copperhead - Requirements & Grading Criteria
```
+----------------------------------------------------------------------------------------+
| |
| ██████╗ ██╗ █████╗ ██████╗██╗ ██╗███████╗████████╗ █████╗ ██████╗ ████████╗ |
| ██╔══██╗██║ ██╔══██╗██╔════╝██║ ██╔╝██╔════╝╚══██╔══╝██╔══██╗██╔══██╗╚══██╔══╝ |
| ██████╔╝██║ ███████║██║ █████╔╝ ███████╗ ██║ ███████║██████╔╝ ██║ |
| ██╔══██╗██║ ██╔══██║██║ ██╔═██╗ ╚════██║ ██║ ██╔══██║██╔══██╗ ██║ |
| ██████╔╝███████╗██║ ██║╚██████╗██║ ██╗███████╗ ██║ ██║ ██║██║ ██║ ██║ |
| ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝██║ ██║╚══════╝ ╚═╝ ╚═╝ ╚═╝██║ ██║ ██║ |
| |
| |
| O P E R A T I O N C O P P E R H E A D |
| |
| REQUIREMENTS & GRADING CRITERIA |
| |
+----------------------------------------------------------------------------------------+
```
---
## Project Overview
Students are the reverse-engineering reserve team called in after DEEPLINE
Metro Authority's rebuilt DEEPLINE-AUTH relay image shipped four corrupted
engineering constants: a miscalibrated release threshold, a false TRACK banner
string, an overstated block length, and a poisoned ARX signal seed. Students
reverse engineer `CTF-02.bin` with Ghidra, patch all four defects, capture the
runtime-derived signal key live in GDB, recover the Ouroboros authority frame,
export a corrected image, flash it to real hardware, and prove the corrected
behavior on a physical Pico 2.
The challenge is a standalone capstone exercise and contains no answer,
constant, address, bug, or patch belonging to any other course assignment.
---
## Learning Objectives
- Decode an ARM Cortex-M33 vector and boot table and identify the reset handler
and initial stack pointer.
- Translate Thumb reset-vector addresses into real function entry points and
trace literal-pool entries to their data.
- Locate four corrupted constants: a boundary comparison, a status string, an
8-byte IEEE-754 double, and an ARX signal seed.
- Capture a runtime-derived key with GDB, override a register, and set a
watchpoint on stored SRAM state.
- Recover and authenticate an Argon2id plus XChaCha20-Poly1305 authority frame.
Students must use only Weeks 1-8 concepts: ARM registers, stack behavior,
USB-CDC output, GDB, Ghidra static analysis and binary patching, vector tables,
reset startup, XIP, Thumb addressing, data segments and literal pools,
condition-code analysis, runtime key derivation, and the Argon2id plus
XChaCha20-Poly1305 authenticated gate.
---
## Deliverables Checklist
| # | Deliverable | Format | Criterion |
|---|-------------|--------|-----------|
| 1 | Ghidra project screenshot | PNG/JPG | 1.1 |
| 2 | Vector table and boot table | Inside `CTF-02-Answers.md` | 1.2 |
| 3 | `main()` and status-loop table | Inside `CTF-02-Answers.md` | 1.3 |
| 4 | Literal pool trace | Inside `CTF-02-Answers.md` | 1.4 |
| 5 | Bug #1 evidence and patches | Inside `CTF-02-Answers.md` | 2.1-2.4 |
| 6 | Bug #2 evidence and patch | Inside `CTF-02-Answers.md` | 3.1-3.4 |
| 7 | Bug #3 evidence and patch | Inside `CTF-02-Answers.md` | 4.1-4.3 |
| 8 | Bug #4 evidence and patch | Inside `CTF-02-Answers.md` | 5.1-5.4 |
| 9 | GDB register capture | Inside `CTF-02-Answers.md` | 6.1-6.4 |
| 10 | Ouroboros gate recovery and auth | Inside `CTF-02-Answers.md` | 7.1-7.4 |
| 11 | `CTF-02_fixed.bin` | BIN file | 8.1 |
| 12 | `CTF-02_fixed.uf2` | UF2 file | 8.2 |
| 13 | Corrected console transcript | Inside `CTF-02-Answers.md` | 8.3 |
| 14 | Summary table of all patches | Inside `CTF-02-Answers.md` | 8.4 |
| 15 | Written reflection | Inside `CTF-02-Answers.md` | 9.1-9.2 |
---
## Required Tools and Equipment
| Tool | Purpose |
|------|---------|
| Raspberry Pi Pico 2 | Isolated target |
| USB-CDC virtual serial console | Observe output and type the gate passphrase |
| SWD debug probe | GDB inspection |
| Ghidra | Static analysis and binary patching |
| GDB | Dynamic analysis and register capture |
| Python (`uf2conv.py`) | UF2 conversion |
| `CTF-02.bin` and `CTF-02.uf2` | Supplied artifacts |
Console settings: **USB-CDC virtual COM port, 115200 baud, 8 data bits, no
parity, 1 stop bit**.
---
## Artifact Identity
The instructor-issued artifact hashes are:
```text
CTF-02.bin 85330C37CD0897746B1AF447E4BAC371DDE2042ABD2D61D58A61FE2A8EEF3537
CTF-02.uf2 F3CD4840260DB820D792758CECACC5297BEF1971B9EACF7601279256D8AF1EAB
```
---
## Grading Rubric - Detailed Breakdown
### Task 1: Setup and Initial Analysis (12 points)
| Criterion | Points | Full credit | Partial credit | No credit |
|-----------|--------|-------------|----------------|-----------|
| Criterion 1.1: Ghidra Project Setup | 3 | Correct project name, `ARM Cortex 32-bit little endian`, base `0x10000000` | One item off | Not set up |
| Criterion 1.2: Vector Table Decoding | 3 | Correct base, initial SP, reset pointer | One missing | Not found |
| Criterion 1.3: main() and Status-Loop Addresses | 4 | Both addresses correct | One correct | Neither found |
| Criterion 1.4: Thumb Addressing and Literal Pool | 2 | Bit 0 cleared and one pool entry traced to its string | Partial | Incorrect |
### Task 2: Find and Patch Bug #1: The Miscalibrated Release Threshold (15 points)
| Criterion | Points | Full credit | Partial credit | No credit |
|-----------|--------|-------------|----------------|-----------|
| Criterion 2.1: Locate Compare Sites A and B | 6 | Both addresses and original bytes | One site | Not found |
| Criterion 2.2: Correct Immediate-Value Reasoning | 4 | Explains the `<` to `<=` transform and gives `0x3B` | Correct value, no reasoning | Wrong value |
| Criterion 2.3: Patch Compare Sites A and B | 4 | Both byte changes verified | One site | Not patched |
| Criterion 2.4: Explain Why Both Sites Must Be Patched | 1 | Clear explanation of the two independent comparisons | Vague | Missing |
### Task 3: Find and Patch Bug #2: The False TRACK Banner (10 points)
| Criterion | Points | Full credit | Partial credit | No credit |
|-----------|--------|-------------|----------------|-----------|
| Criterion 3.1: Locate the Banner String | 3 | Correct address and cross-reference | Approximate | Not found |
| Criterion 3.2: Patch Six Characters | 4 | All six bytes changed, length preserved | Correct text, wrong bytes documented | Wrong length |
| Criterion 3.3: Character-by-Character Documentation | 2 | Original vs patched byte for all six characters | Partial | Missing |
| Criterion 3.4: Explain the Danger of a Hardcoded Status Word | 1 | Clear, specific reasoning | Generic | Missing |
### Task 4: Find and Patch Bug #3: The Block Length Constant (10 points)
| Criterion | Points | Full credit | Partial credit | No credit |
|-----------|--------|-------------|----------------|-----------|
| Criterion 4.1: Locate the .data Double | 3 | Correct address traced from the BLOCK LENGTH print | Approximate | Not found |
| Criterion 4.2: IEEE-754 Bytes and Print Math | 4 | Original and patched 8-byte double with print math (3200 M to 320 M) | Correct patch, no math | Wrong bytes |
| Criterion 4.3: Patch to Print 320 M | 3 | Console shows `BLOCK LENGTH: 320 M` | Wrong bytes | Not patched |
### Task 5: Find and Patch Bug #4: The Signal Seed (15 points)
| Criterion | Points | Full credit | Partial credit | No credit |
|-----------|--------|-------------|----------------|-----------|
| Criterion 5.1: Locate SIGNAL_SPEC and the Seed | 5 | `0x2D879291` located and wrong seed `0x0A0A0A0A` found | Partial | Not found |
| Criterion 5.2: Patch the Seed | 4 | Seed bytes changed to `74 65 20 6B` | Wrong byte | Not patched |
| Criterion 5.3: Explain the ARX Derivation | 3 | Correct trace of the per-cycle derivation | Vague | Missing |
| Criterion 5.4: Separate the Security Layers | 3 | Correctly explains what the seed fixes versus the gate | Generic | Missing |
### Task 6: GDB Register Capture of the Derived Key (15 points)
| Criterion | Points | Full credit | Partial credit | No credit |
|-----------|--------|-------------|----------------|-----------|
| Criterion 6.1: Breakpoint at the Derive Return | 4 | Correct address and `$r0` read as the bug-derived key | Address off | Not found |
| Criterion 6.2: Inspect the Two Arguments | 4 | Live seed and derived IV captured at the second call | One correct | Missing |
| Criterion 6.3: Override the Register | 4 | `$r0` set to `0x2D879291` and the next cycle shows `OK` | Partial | Missing |
| Criterion 6.4: Watchpoint on the Stored Key | 3 | Watchpoint on the SRAM key location documented | Approximate | Missing |
### Task 7: Recover the Ouroboros Authority Frame (10 points)
| Criterion | Points | Full credit | Partial credit | No credit |
|-----------|--------|-------------|----------------|-----------|
| Criterion 7.1: Locate Salt, Nonce, Ciphertext, and Tag | 4 | All three addresses correct in flash | Two correct | Not found |
| Criterion 7.2: Document Argon2id Parameters and Payload Contract | 2 | Correct memory/time/parallelism and payload layout | Partial | Missing |
| Criterion 7.3: Authenticate with the 12-Word Passphrase | 2 | `AUTHORITY FRAME: VERIFIED`, LED on, payload printed | Partial | Not shown |
| Criterion 7.4: State the Honest Quantum Boundary | 2 | Grover halves symmetric exponents; not strict PQC | Generic | Misstates |
### Task 8: Export and Verify (8 points)
| Criterion | Points | Full credit | Partial credit | No credit |
|-----------|--------|-------------|----------------|-----------|
| Criterion 8.1: Export CTF-02_fixed.bin | 1 | Valid patched binary | Corrupted | Not submitted |
| Criterion 8.2: Convert to CTF-02_fixed.uf2 | 1 | Correct base and family flags | Wrong flags | Not submitted |
| Criterion 8.3: Hardware Verification | 4 | Corrected console output confirmed (CRITICAL/HELD in 2s stream; DANGER at boot/Ghidra) | Some lines corrected | No verification |
| Criterion 8.4: Summary Table of All Patches | 2 | Complete address and before/after table | Missing entries | No table |
### Task 9: Written Reflection (5 points)
| Criterion | Points | Full credit | Partial credit | No credit |
|-----------|--------|-------------|----------------|-----------|
| Criterion 9.1: "Rushed Build" Is Not an Excuse | 2 | Specific, grounded reasoning | Generic | Missing |
| Criterion 9.2: One Engineering Practice per Failure Area | 3 | Concrete practices for the bugs and for image authenticity | Names some | Missing |
---
## Common Pitfalls
| Pitfall | Consequence | Avoidance |
|---------|-------------|-----------|
| Patching only one threshold site | One status line still lies | Patch both `0x10000302` and `0x10000312` |
| Assuming the immediate equals the limit | Off-by-one, wrong boundary | Use `0x3B` (59), not `0x3C` (60) |
| Using Patch Instruction before IT block | Re-disassembler context conflict swallows Site B | In Listing press `C` -> edit byte in Bytes window (pencil) -> press `D` |
| Missing boot banner in serial terminal | PuTTY misses one-time 5ms boot banner | Pulse RUN to GND while connected to capture |
| Replacing a string with a different length | Corrupts adjacent flash | `NORMAL` and `DANGER` are both 6 bytes |
| Treating the block length as an integer | Misses the 8-byte double | Follow the value into `.data`, decode IEEE-754 |
| Using the wrong 0.32 bytes | Prints 316 M instead of 320 M | Use `7B 14 AE 47 E1 7A D4 3F` |
| Treating an odd vector address as invalid | Thumb analysis fails | Clear bit 0 |
| Starting the seed patch at the wrong offset | Wrong seed, key never matches | Seed is at `0x1000EC70` |
---
## How To Breadboard
- **Raspberry Pi Pico 2** powered over USB.
- **USB-CDC virtual serial console:** open the Pico's COM port at 115200 baud,
8 data bits, no parity, 1 stop bit.
- **SWD debug probe:** connect SWCLK, SWDIO, GND, and 3.3 V to the Pico debug
header for GDB inspection and register capture.
- No other peripherals are required; the authority LED is on-board.
---
## Memory Map Reference
| Region | Address | Purpose |
|--------|---------|---------|
| Bootrom | `0x00000000` | Immutable boot code |
| Flash/XIP | `0x10000000` | Vector table, code, rodata, `.data` init image |
| SRAM | `0x20000000` | Stack and writable state |
---
## Deadline & Submission
- Create a folder containing the Ghidra screenshot, `CTF-02_fixed.bin`, and
`CTF-02_fixed.uf2`.
- Write all written answers in a single file named `CTF-02-Answers.md` inside that
folder.
- ZIP the folder as `lastname-firstname-CTF-02.zip`.
- Submit the ZIP before the posted deadline; late submissions lose 10 percent
per day.
---
## Grade Scale
| Grade | Percentage | Points |
|-------|------------|--------|
| A+ | 97-100% | 97-100 |
| A | 93-96% | 93-96 |
| A- | 90-92% | 90-92 |
| B+ | 87-89% | 87-89 |
| B | 84-86% | 84-86 |
| B- | 80-83% | 80-83 |
| C | 70-79% | 70-79 |
| F | 0-69% | 0-69 |
---
## Academic Integrity
Use only the supplied Pico 2 and firmware. Do not connect the exercise to an
operational railway, metro system, public network, military system, or
third-party device. This is a controlled, isolated educational exercise. All
analysis and patches must be your own work; sharing binaries, addresses, keys,
passphrases, or answers is a violation of the academic integrity policy.
---
## Reference Material
| Topic | Reference |
|-------|-----------|
| ARM Cortex-M33 registers and stack | Week 1 |
| USB-CDC output and console capture | Week 2 |
| Vector tables, reset startup, and XIP | Week 2 |
| Ghidra static analysis and binary patching | Week 3 |
| Data segments, literal pools, IEEE-754 | Week 4 |
| Condition-code analysis | Week 5 |
| Runtime key derivation and GDB register capture | Week 6 |
| Argon2id and XChaCha20-Poly1305 authenticated gate | Week 8 |
Binary file not shown.
+652
View File
@@ -0,0 +1,652 @@
# Operation Copperhead - Instructor Solution Key
```
+----------------------------------------------------------------------------------------+
| |
| ██████╗ ██╗ █████╗ ██████╗██╗ ██╗███████╗████████╗ █████╗ ██████╗ ████████╗ |
| ██╔══██╗██║ ██╔══██╗██╔════╝██║ ██╔╝██╔════╝╚══██╔══╝██╔══██╗██╔══██╗╚══██╔══╝ |
| ██████╔╝██║ ███████║██║ █████╔╝ ███████╗ ██║ ███████║██████╔╝ ██║ |
| ██╔══██╗██║ ██╔══██║██║ ██╔═██╗ ╚════██║ ██║ ██╔══██║██╔══██╗ ██║ |
| ██████╔╝███████╗██║ ██║╚██████╗██║ ██╗███████╗ ██║ ██║ ██║██║ ██║ ██║ |
| ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝██║ ██║╚══════╝ ╚═╝ ╚═╝ ╚═╝██║ ██║ ██║ |
| |
| |
| O P E R A T I O N C O P P E R H E A D |
| |
| *** INSTRUCTOR SOLUTION KEY: RESTRICTED *** |
| |
+----------------------------------------------------------------------------------------+
```
> The task and criterion headings in this key are word-for-word identical to
> `CTF-02-R.md`, so a student can match each criterion one-to-one.
---
## Artifact Identity
| Artifact | Value |
|----------|-------|
| Student image | `CTF-02.bin` |
| Flash image | `CTF-02.uf2` |
| Target | Raspberry Pi Pico 2 / RP2350 ARM Cortex-M33 |
| Image base | `0x10000000` |
| Console | USB-CDC virtual COM, 115200 8N1 |
```text
CTF-02.bin 85330C37CD0897746B1AF447E4BAC371DDE2042ABD2D61D58A61FE2A8EEF3537
CTF-02.uf2 F3CD4840260DB820D792758CECACC5297BEF1971B9EACF7601279256D8AF1EAB
```
Proof tool: `python3 scripts/verify_ctf.py` returns `26/26 checks passed` against
`CTF-02.bin`.
---
## Task 1: Setup and Initial Analysis (12 points)
### Solution
**Criterion 1.1: Ghidra Project Setup (3 points).** Import `CTF-02.bin` as
`Raw Binary`, language `ARM:LE:32:Cortex`, base address `0x10000000`, then run
auto-analysis.
**Criterion 1.2: Vector Table Decoding (3 points).**
First 32 bytes of `CTF-02.bin`:
```text
00 20 08 20 5B 01 00 10 1B 01 00 10 1D 01 00 10
11 01 00 10 11 01 00 10 11 01 00 10 11 01 00 10
```
| Evidence | Answer |
|----------|--------|
| Vector table base | `0x10000000` |
| Initial SP | `0x20082000` |
| Reset pointer (as stored) | `0x1000015B` |
| Reset instruction address | `0x1000015A` |
**Criterion 1.3: main() and Status-Loop Addresses (4 points).**
| Element | Address |
|---------|---------|
| `main()` | `0x100002E8` |
| Recurring status loop start | `0x1000034C` |
| Loop back-edge (`b.n 0x1000034C`) | `0x1000044E` |
**Criterion 1.4: Thumb Addressing and Literal Pool (2 points).**
The stored reset pointer `0x1000015B` has bit 0 set, selecting Thumb mode.
Clearing bit 0 gives `0x1000015A`. A representative literal pool entry is
`0x100004B4`, which holds `0x1000C4C8`, the address of the format string
`"BLOCK STATE: %s"`, loaded by `ldr r0, [pc, #308]` at `0x1000037C`.
**Supporting Reference: SRAM Symbols (Ghidra names to semantic roles).**
| Ghidra Label | SRAM Address | Section | Role | Source Symbol |
|--------------|--------------|---------|------|---------------|
| `DAT_20001188` | `0x20001188` | `.data` | Block length double | `g_telemetry` |
| `DAT_20001198` | `0x20001198` | `.data` | Signal key seed | `g_auth_seed` |
| `DAT_2000119C` | `0x2000119C` | `.data` | Track current | `g_block_current` |
| `DAT_20001ECC` | `0x20001ECC` | `.bss` | Dispatch state | `g_dispatch_state` |
| `DAT_20001ED0` | `0x20001ED0` | `.bss` | Fault poll counter | `g_fault_polls` |
| `DAT_20001ED4` | `0x20001ED4` | `.bss` | Input line buffer | `g_linebuf` |
| `DAT_200020D4` | `0x200020D4` | `.bss` | Input write index | `g_lineidx` |
| `DAT_200020D8` | `0x200020D8` | `.bss` | Operator state | `g_operator_state` |
| `DAT_200020DC` | `0x200020DC` | `.bss` | Derived signal key | `g_signal_key` |
### Grading Rubric (1-to-1 Mapping)
| Criterion | Points | Full Credit (Answer Key) | Partial Credit | No Credit |
|-----------|--------|--------------------------|----------------|-----------|
| Criterion 1.1: Ghidra Project Setup | 3 | Correct project name, `ARM Cortex 32-bit little endian`, base `0x10000000` | One item off | Not set up |
| Criterion 1.2: Vector Table Decoding | 3 | Correct base, initial SP, reset pointer | One missing | Not found |
| Criterion 1.3: main() and Status-Loop Addresses | 4 | Both addresses correct | One correct | Neither found |
| Criterion 1.4: Thumb Addressing and Literal Pool | 2 | Bit 0 cleared and one pool entry traced to its string | Partial | Incorrect |
### Instructor Notes & Assembly
- Confirm the Ghidra import used `Raw Binary`, `ARM:LE:32:Cortex`, base
`0x10000000`, and that auto-analysis completed before any address was read.
- Verify `main()` is `0x100002E8`, the loop head is `0x1000034C`, and the
back-edge is `0x1000044E`.
- For Criterion 1.4, accept any correctly traced literal pool entry; the pool
entry `0x100004B4` holding `0x1000C4C8` (`"BLOCK STATE: %s"`, loaded at
`0x1000037C`) is the reference example.
- The SRAM symbol table is supporting reference material, not a separate
scored criterion.
---
## Task 2: Find and Patch Bug #1: The Miscalibrated Release Threshold (15 points)
### Solution
**Criterion 2.1: Locate Compare Sites A and B (6 points).**
```text
10000302: 2b5e cmp r3, #94 @ 0x5e
10000312: 2b5e cmp r3, #94 @ 0x5e
```
| Site | Address | File Offset | Original Bytes | Original Instruction |
|------|---------|-------------|----------------|----------------------|
| A | `0x10000302` | `0x0302` | `5E 2B` | `cmp r3, #94` |
| B | `0x10000312` | `0x0312` | `5E 2B` | `cmp r3, #94` |
**Criterion 2.2: Correct Immediate-Value Reasoning (4 points).**
The source constant is `SAFE_THRESHOLD = 95` and the test is `x < 95`. For an
unsigned value, `x < 95` is exactly `x <= 94`, so the compiler emits
`cmp r3, #94`. The correct limit is `60`, so the test is `x < 60`, which is
`x <= 59`. The correct patched immediate is **`0x3B` (59)**, not `0x3C` (60).
**Criterion 2.3: Patch Compare Sites A and B (4 points).**
| Site | Address | File Offset | Original | Patched | After |
|------|---------|-------------|----------|---------|-------|
| A | `0x10000302` | `0x0302` | `5E 2B` | `3B 2B` | `cmp r3, #59` |
| B | `0x10000312` | `0x0312` | `5E 2B` | `3B 2B` | `cmp r3, #59` |
**Criterion 2.4: Explain Why Both Sites Must Be Patched (1 point).**
Site A drives the `BLOCK STATE` line and site B drives the `AUTO TRAIN`
decision. Patching only site A makes the console read `CRITICAL` while the
automated dispatch still says `AUTHORIZED`. Frozen reading `87`: `87 <= 94` is
true (wrong); `87 <= 59` is false (correct).
### Grading Rubric (1-to-1 Mapping)
| Criterion | Points | Full Credit (Answer Key) | Partial Credit | No Credit |
|-----------|--------|--------------------------|----------------|-----------|
| Criterion 2.1: Locate Compare Sites A and B | 6 | Both addresses and original bytes | One site | Not found |
| Criterion 2.2: Correct Immediate-Value Reasoning | 4 | Explains the `<` to `<=` transform and gives `0x3B` | Correct value, no reasoning | Wrong value |
| Criterion 2.3: Patch Compare Sites A and B | 4 | Both byte changes verified | One site | Not patched |
| Criterion 2.4: Explain Why Both Sites Must Be Patched | 1 | Clear explanation of the two independent comparisons | Vague | Missing |
### Instructor Notes & Assembly
- Both sites must be patched: `0x10000302` for `BLOCK STATE` and `0x10000312`
for the `AUTO TRAIN` decision.
- The correct immediate is `0x3B` (59), not `0x3C` (60).
- Verify the byte changes on hardware; the corrected console reads `CRITICAL`
and `HELD`.
- **Ghidra ARM/Thumb Context Note:** In raw `.bin` files, patching an instruction
that precedes an `IT` block (`ite ge`) using the GUI *Patch Instruction* action
triggers Ghidra's `ReDisassembleCommand`. The re-disassembler encounters an
internal context register conflict when trying to re-declare the `ITBlock`
context over existing instructions, collapsing Thumb decoding into 32-bit ARM
mode and swallowing Site B (`0x10000312`). Students must patch using the Bytes
window workflow (Clear `C` -> edit byte `5E` -> `3B` in Bytes window with pencil
icon -> Disassemble `D`) to keep Site B visible and cleanly aligned.
---
## Task 3: Find and Patch Bug #2: The False TRACK Banner (10 points)
### Solution
**Criterion 3.1: Locate the Banner String (3 points).**
| String | Address |
|--------|---------|
| `"TRACK: NORMAL\r"` | `0x1000C4B8` |
| `"NORMAL"` substring to patch | `0x1000C4BF` |
The string is loaded in `main` and printed once at boot; it never reads the
sensor.
**Criterion 3.2: Patch Six Characters (4 points).**
`NORMAL` and `DANGER` are both six ASCII characters, so the patch preserves the
length.
| Address Range | Original Bytes | Patched Bytes |
|---------------|----------------|---------------|
| `0x1000C4BF` - `0x1000C4C4` | `4E 4F 52 4D 41 4C` | `44 41 4E 47 45 52` |
**Criterion 3.3: Character-by-Character Documentation (2 points).**
| Address | Original Char | Original Byte | Patched Char | Patched Byte |
|---------|---------------|---------------|--------------|--------------|
| `0x1000C4BF` | N | `4E` | D | `44` |
| `0x1000C4C0` | O | `4F` | A | `41` |
| `0x1000C4C1` | R | `52` | N | `4E` |
| `0x1000C4C2` | M | `4D` | G | `47` |
| `0x1000C4C3` | A | `41` | E | `45` |
| `0x1000C4C4` | L | `4C` | R | `52` |
**Criterion 3.4: Explain the Danger of a Hardcoded Status Word (1 point).**
The banner never consults the reading, so it reports a healthy track even while
the frozen reading is dangerous, masking the hazard from the operator.
### Grading Rubric (1-to-1 Mapping)
| Criterion | Points | Full Credit (Answer Key) | Partial Credit | No Credit |
|-----------|--------|--------------------------|----------------|-----------|
| Criterion 3.1: Locate the Banner String | 3 | Correct address and cross-reference | Approximate | Not found |
| Criterion 3.2: Patch Six Characters | 4 | All six bytes changed, length preserved | Correct text, wrong bytes documented | Wrong length |
| Criterion 3.3: Character-by-Character Documentation | 2 | Original vs patched byte for all six characters | Partial | Missing |
| Criterion 3.4: Explain the Danger of a Hardcoded Status Word | 1 | Clear, specific reasoning | Generic | Missing |
### Instructor Notes & Assembly
- `NORMAL` and `DANGER` are both six characters; the patch must not change the
string length or overwrite adjacent flash.
- Confirm the patch covers `0x1000C4BF` through `0x1000C4C4` exactly.
- The banner is printed once at boot and never recomputed, so it is a separate
defect from the threshold.
---
## Task 4: Find and Patch Bug #3: The Block Length Constant (10 points)
### Solution
**Criterion 4.1: Locate the .data Double (3 points).**
The console prints `BLOCK LENGTH: 3200 M` from the format string at
`0x1000C4F0`. The value is a `double` in the `.data` init image at
`0x1000EC60` (loaded into `0x20001188` at boot).
**Criterion 4.2: IEEE-754 Bytes and Print Math (4 points).**
Eight bytes at `0x1000EC60`:
```text
9A 99 99 99 99 99 09 40 -> 0x400999999999999A -> 3.2 km -> 3200 m
7B 14 AE 47 E1 7A D4 3F -> 0x3FD47AE147AE147B -> 0.32 km -> 320 m
```
**Criterion 4.3: Patch to Print 320 M (3 points).**
| File Offset Range | Flash Address Range | Original Bytes | Patched Bytes |
|-------------------|---------------------|----------------|---------------|
| `0xEC60` - `0xEC67` | `0x1000EC60` - `0x1000EC67` | `9A 99 99 99 99 99 09 40` | `7B 14 AE 47 E1 7A D4 3F` |
At `0x1000EC68` the adjacent telemetry fields (`03 00 00 00` flags and `07 00`
crossing) are left untouched.
### Grading Rubric (1-to-1 Mapping)
| Criterion | Points | Full Credit (Answer Key) | Partial Credit | No Credit |
|-----------|--------|--------------------------|----------------|-----------|
| Criterion 4.1: Locate the .data Double | 3 | Correct address traced from the BLOCK LENGTH print | Approximate | Not found |
| Criterion 4.2: IEEE-754 Bytes and Print Math | 4 | Original and patched 8-byte double with print math (3200 M to 320 M) | Correct patch, no math | Wrong bytes |
| Criterion 4.3: Patch to Print 320 M | 3 | Console shows `BLOCK LENGTH: 320 M` | Wrong bytes | Not patched |
### Instructor Notes & Assembly
- The value is an 8-byte IEEE-754 double, not an integer. Follow the
`BLOCK LENGTH` print into `.data` at `0x1000EC60`.
- The patched bytes `7B 14 AE 47 E1 7A D4 3F` decode to `0.32 km` (`320 m`).
- Confirm the adjacent telemetry fields at `0x1000EC68` (`03 00 00 00` and
`07 00`) are left untouched.
---
## Task 5: Find and Patch Bug #4: The Signal Seed (15 points)
### Solution
**Criterion 5.1: Locate SIGNAL_SPEC and the Seed (5 points).**
The SIMPLE comparison in the loop is at `0x100003A6`:
```text
100003a6: 4559 cmp r1, fp ; fp = SIGNAL_SPEC = 0x2D879291 (pool 0x100004FC)
```
The corrupted seed is a `0x0A0A0A0A` word in the `.data` init image at
`0x1000EC70` (loaded into `0x20001198` at boot). The byte values `0A 0A 0A 0A`
also appear in the `tbb` jump table at `0x100003D4`; those are not the seed.
**Criterion 5.2: Patch the Seed (4 points).**
The required seed is the ChaCha expand word `0x6B206574` (`"te k"`), stored
little-endian as `74 65 20 6B`.
| File Offset Range | Flash Address Range | Original Bytes | Patched Bytes |
|-------------------|---------------------|----------------|---------------|
| `0xEC70` - `0xEC73` | `0x1000EC70` - `0x1000EC73` | `0A 0A 0A 0A` | `74 65 20 6B` |
**Criterion 5.3: Explain the ARX Derivation (3 points).**
`derive_session_key(seed, iv)` works exactly like this:
1. Set `a = seed`, `b = iv`, `c = 0x61707865`, `d = 0x3320646E`.
2. Run four ChaCha quarter-rounds. A quarter-round runs four phases with rotate
amounts 16, 12, 8, 7. Each phase is: `a = a + b; d = d XOR a;
d = rotate_left(d, s); c = c + d; b = b XOR c; b = rotate_left(b, s)`.
3. Return `a XOR d`.
The runtime IV is `derive_session_key(0x6B206574, 0) = 0x43C974F6`. The shipped
seed `0x0A0A0A0A` gives `derive_session_key(0x0A0A0A0A, 0x43C974F6) =
0x915DCFF8` (MISMATCH). The honest seed `0x6B206574` gives
`derive_session_key(0x6B206574, 0x43C974F6) = 0x2D879291`, which equals
`SIGNAL_SPEC` and prints `OK`.
**Criterion 5.4: Separate the Security Layers (3 points).**
The signal seed only controls the local `SIGNAL KEY` telemetry check. It does
not authenticate the operator. The Argon2id plus XChaCha20-Poly1305 gate is a
separate layer that requires the 12-word passphrase.
### Grading Rubric (1-to-1 Mapping)
| Criterion | Points | Full Credit (Answer Key) | Partial Credit | No Credit |
|-----------|--------|--------------------------|----------------|-----------|
| Criterion 5.1: Locate SIGNAL_SPEC and the Seed | 5 | `0x2D879291` located and wrong seed `0x0A0A0A0A` found | Partial | Not found |
| Criterion 5.2: Patch the Seed | 4 | Seed bytes changed to `74 65 20 6B` | Wrong byte | Not patched |
| Criterion 5.3: Explain the ARX Derivation | 3 | Correct trace of the per-cycle derivation | Vague | Missing |
| Criterion 5.4: Separate the Security Layers | 3 | Correctly explains what the seed fixes versus the gate | Generic | Missing |
### Instructor Notes & Assembly
- The seed is at `0x1000EC70` in the `.data` init image. The `0A 0A 0A 0A`
bytes in the `tbb` jump table at `0x100003D4` are not the seed.
- The patched seed `74 65 20 6B` is the little-endian form of the ChaCha expand
word `0x6B206574` (`"te k"`).
- The seed only affects the local `SIGNAL KEY` check; the Argon2id plus
XChaCha20-Poly1305 gate is a separate authentication layer.
---
## Task 6: GDB Register Capture of the Derived Key (15 points)
### Solution
**Criterion 6.1: Breakpoint at the Derive Return (4 points).**
The per-cycle derive is `bl derive_session_key` at `0x10000352`. Break at the
next instruction, `0x10000356`, and read `$r0`. On the shipped image it is
`0x915DCFF8`.
```gdb
(gdb) break *0x10000356
(gdb) continue
(gdb) print/x $r0 # 0x915DCFF8 on the corrupted image
```
**Criterion 6.2: Inspect the Two Arguments (4 points).**
At the call entry `0x10000352`:
| Register | Corrupted image | Meaning |
|----------|-----------------|---------|
| `$r0` | `0x0A0A0A0A` | Seed |
| `$r1` | `0x43C974F6` | Derived IV |
**Criterion 6.3: Override the Register (4 points).**
With execution at `0x10000356`, set `$r0` to the spec key, then continue. The
next cycle prints `SIGNAL KEY: 0x2D879291 OK`.
```gdb
(gdb) set $r0 = 0x2D879291
(gdb) continue
```
**Criterion 6.4: Watchpoint on the Stored Key (3 points).**
The key is stored in SRAM at `0x200020DC` (`str r0, [r6, #0]`).
```gdb
(gdb) watch *0x200020DC
```
### Grading Rubric (1-to-1 Mapping)
| Criterion | Points | Full Credit (Answer Key) | Partial Credit | No Credit |
|-----------|--------|--------------------------|----------------|-----------|
| Criterion 6.1: Breakpoint at the Derive Return | 4 | Correct address and `$r0` read as the bug-derived key | Address off | Not found |
| Criterion 6.2: Inspect the Two Arguments | 4 | Live seed and derived IV captured at the second call | One correct | Missing |
| Criterion 6.3: Override the Register | 4 | `$r0` set to `0x2D879291` and the next cycle shows `OK` | Partial | Missing |
| Criterion 6.4: Watchpoint on the Stored Key | 3 | Watchpoint on the SRAM key location documented | Approximate | Missing |
### Instructor Notes & Assembly
- Confirm the breakpoint is placed at `0x10000356`, the instruction after the
`bl derive_session_key` at `0x10000352`.
- On the shipped image `$r0` reads `0x915DCFF8`; `$r0` is the seed `0x0A0A0A0A`
and `$r1` is the derived IV `0x43C974F6`.
- The stored key lives at `0x200020DC`; accept the documented watchpoint.
---
## Task 7: Recover the Ouroboros Authority Frame (10 points)
### Solution
**Criterion 7.1: Locate Salt, Nonce, Ciphertext, and Tag (4 points).**
| Component | Flash Address | Size |
|-----------|---------------|------|
| Ciphertext + Tag | `0x1000CE94` | 64 B |
| Nonce | `0x1000CED4` | 24 B |
| Salt | `0x1000CEEC` | 16 B |
**Criterion 7.2: Document Argon2id Parameters and Payload Contract (2 points).**
Argon2id: memory `64 KiB`, iterations `3`, parallelism `1`, output key `32 B`,
salt the 16 bytes above. XChaCha20-Poly1305 decrypts the 48-byte ciphertext with
the 16-byte tag. The plaintext is `01 68 65 6C 6C 6F 0D 0A` followed by zeros:
byte 0 turns the GPIO 25 LED on, and bytes 1 through 7 are printed as `hello`
plus carriage-return and newline.
**Criterion 7.3: Authenticate with the 12-Word Passphrase (2 points).**
At the `RESPONSE> ` prompt, type:
```text
orbit olive ladder marble quartz canyon ripple saddle violet ember walnut falcon
```
Expected result (proven on hardware):
```text
hello
AUTHORITY FRAME: VERIFIED
```
**Criterion 7.4: State the Honest Quantum Boundary (2 points).**
Grover-style search halves the effective security exponent of a symmetric key,
so a 256-bit key gives about 128 bits of quantum security. The construction uses
classical symmetric and password-hashing primitives and does not implement NIST
post-quantum standards.
### Grading Rubric (1-to-1 Mapping)
| Criterion | Points | Full Credit (Answer Key) | Partial Credit | No Credit |
|-----------|--------|--------------------------|----------------|-----------|
| Criterion 7.1: Locate Salt, Nonce, Ciphertext, and Tag | 4 | All three addresses correct in flash | Two correct | Not found |
| Criterion 7.2: Document Argon2id Parameters and Payload Contract | 2 | Correct memory/time/parallelism and payload layout | Partial | Missing |
| Criterion 7.3: Authenticate with the 12-Word Passphrase | 2 | `AUTHORITY FRAME: VERIFIED`, LED on, payload printed | Partial | Not shown |
| Criterion 7.4: State the Honest Quantum Boundary | 2 | Grover halves symmetric exponents; not strict PQC | Generic | Misstates |
### Instructor Notes & Assembly
- Verify the three component addresses in flash: ciphertext plus tag at
`0x1000CE94`, nonce at `0x1000CED4`, salt at `0x1000CEEC`.
- The passphrase is fixed for the lab; a successful gate prints `hello` and
`AUTHORITY FRAME: VERIFIED` and lights the on-board authority LED.
- Grade Criterion 7.4 on the honest boundary: 256-bit symmetric key maps to
about 128 bits under Grover, and the design is not NIST post-quantum.
---
## Task 8: Export and Verify (8 points)
### Solution
**Criterion 8.1: Export CTF-02_fixed.bin (1 point).**
Export the patched program from Ghidra (`File -> Export Program...`, `Binary
Format`) as `CTF-02_fixed.bin`. The shipped image is 62,308 bytes.
**Criterion 8.2: Convert to CTF-02_fixed.uf2 (1 point).**
```bash
python uf2conv.py CTF-02_fixed.bin --base 0x10000000 --family 0xe48bff59 --output CTF-02_fixed.uf2
```
**Criterion 8.3: Hardware Verification (4 points).**
Before patching:
```text
DEEPLINE METRO AUTHORITY
ADAPTIVE SIGNAL WINDOW: 38 MINUTES
USB-CDC 115200 8N1 | AUTHORIZED LAB CONSOLE
TRACK: NORMAL
BLOCK STATE: STABLE
AUTO TRAIN: AUTHORIZED
BLOCK LENGTH: 3200 M
FAULT POLLS: 1
SIGNAL KEY: 0x915DCFF8 MISMATCH
RESPONSE>
```
After all four patches:
```text
DEEPLINE METRO AUTHORITY
ADAPTIVE SIGNAL WINDOW: 38 MINUTES
USB-CDC 115200 8N1 | AUTHORIZED LAB CONSOLE
TRACK: DANGER
BLOCK STATE: CRITICAL
AUTO TRAIN: HELD
BLOCK LENGTH: 320 M
FAULT POLLS: 1
SIGNAL KEY: 0x2D879291 OK
RESPONSE>
```
**Criterion 8.4: Summary Table of All Patches (2 points).**
| # | Bug | File Offset | Flash Address | Original Bytes | Patched Bytes |
|---|-----|-------------|---------------|----------------|---------------|
| 1a | Operator threshold | `0x0302` | `0x10000302` | `5E 2B` | `3B 2B` |
| 1b | Dispatch threshold | `0x0312` | `0x10000312` | `5E 2B` | `3B 2B` |
| 2 | TRACK banner | `0xC4BF` - `0xC4C4` | `0x1000C4BF` - `0x1000C4C4` | `4E 4F 52 4D 41 4C` | `44 41 4E 47 45 52` |
| 3 | Block length | `0xEC60` - `0xEC67` | `0x1000EC60` - `0x1000EC67` | `9A 99 99 99 99 99 09 40` | `7B 14 AE 47 E1 7A D4 3F` |
| 4 | Signal seed | `0xEC70` - `0xEC73` | `0x1000EC70` - `0x1000EC73` | `0A 0A 0A 0A` | `74 65 20 6B` |
### Grading Rubric (1-to-1 Mapping)
| Criterion | Points | Full Credit (Answer Key) | Partial Credit | No Credit |
|-----------|--------|--------------------------|----------------|-----------|
| Criterion 8.1: Export CTF-02_fixed.bin | 1 | Valid patched binary | Corrupted | Not submitted |
| Criterion 8.2: Convert to CTF-02_fixed.uf2 | 1 | Correct base and family flags | Wrong flags | Not submitted |
| Criterion 8.3: Hardware Verification | 4 | Corrected console output confirmed on hardware | Some lines corrected | No verification |
| Criterion 8.4: Summary Table of All Patches | 2 | Complete address and before/after table | Missing entries | No table |
### Instructor Notes & Assembly
- Verify the exported image with `python3 scripts/verify_ctf.py`; the shipped
check expects `26/26 checks passed` against `CTF-02.bin`.
- Confirm the UF2 conversion used base `0x10000000` and family `0xe48bff59`.
- **Serial Terminal Timing:** Note that `print_identity()` (`TRACK: NORMAL`)
fires within the first 5 milliseconds of boot. In normal lab usage, PuTTY
attaches after boot and will display the continuous 2-second status loop
(`BLOCK STATE: CRITICAL`, `AUTO TRAIN: HELD`). To see the corrected banner,
the student must pulse `RUN` to `GND` while PuTTY is open, or demonstrate
the string change at `0x1000C4BF` via Ghidra static analysis.
- The shipped image is 62,308 bytes; confirm the exported corrected image is a
valid patched binary with all four fixes present.
---
## Task 9: Written Reflection (5 points)
### Solution
**Criterion 9.1: "Rushed Build" Is Not an Excuse (2 points).**
The rebuild shipped four constants that were never checked against their
documented limits, which is exactly what produced the false-safe reading.
Pressure explains why the checks were skipped, not why they should be skipped.
**Criterion 9.2: One Engineering Practice per Failure Area (3 points).**
- Physical limits (Bugs #1 and #3): one shared configuration header plus a
build-time assertion that each compiled limit matches its documented value.
- Banner (Bug #2): remove static banners; a hardware-in-the-loop test that
compares displayed state to the live register.
- Seed integrity (Bug #4): reproducible builds with golden artifact hash
comparison so keys and seeds match the certified specification.
- Image authenticity: enable RP2350 hardware secure boot with OTP hash
verification so a modified image will not run.
### Grading Rubric (1-to-1 Mapping)
| Criterion | Points | Full Credit (Answer Key) | Partial Credit | No Credit |
|-----------|--------|--------------------------|----------------|-----------|
| Criterion 9.1: "Rushed Build" Is Not an Excuse | 2 | Specific, grounded reasoning | Generic | Missing |
| Criterion 9.2: One Engineering Practice per Failure Area | 3 | Concrete practices for the bugs and for image authenticity | Names some | Missing |
### Instructor Notes & Assembly
- Grade the specificity of the reasoning, not the length of the prose.
- Require concrete practices across the failure areas, including at least one
practice for image authenticity.
---
## How To Breadboard
- **Raspberry Pi Pico 2** powered over USB.
- **USB-CDC virtual serial console:** open the Pico's COM port at 115200 baud,
8 data bits, no parity, 1 stop bit.
- **SWD debug probe:** connect SWCLK, SWDIO, GND, and 3.3 V to the Pico debug
header for GDB inspection and register capture.
- No other peripherals are required; the authority LED is on-board.
---
## Complete Grading Summary
| Task | Title | Points |
|------|-------|--------|
| Task 1 | Setup and Initial Analysis | 12 |
| Task 2 | Find and Patch Bug #1: The Miscalibrated Release Threshold | 15 |
| Task 3 | Find and Patch Bug #2: The False TRACK Banner | 10 |
| Task 4 | Find and Patch Bug #3: The Block Length Constant | 10 |
| Task 5 | Find and Patch Bug #4: The Signal Seed | 15 |
| Task 6 | GDB Register Capture of the Derived Key | 15 |
| Task 7 | Recover the Ouroboros Authority Frame | 10 |
| Task 8 | Export and Verify | 8 |
| Task 9 | Written Reflection | 5 |
| **TOTAL** | | **100** |
---
## Instructor Notes
Safety: Use only the supplied Pico 2, SWD probe, and firmware. Never connect the
exercise to an operational railway, metro system, public network, military
system, or third-party device.
### Common Student Mistakes
- Patching only one threshold site (`0x10000302` or `0x10000312`), leaving one
status line lying.
- Assuming the immediate equals the limit, producing an off-by-one boundary;
the correct byte is `0x3B` (59), not `0x3C` (60).
- Replacing the banner string with a different length, corrupting adjacent
flash; `NORMAL` and `DANGER` are both 6 bytes.
- Treating the block length as an integer and missing the 8-byte double in
`.data`.
- Using the wrong `0.32` bytes and printing `316 M` instead of `320 M`.
- Treating the odd vector address `0x1000015B` as invalid instead of clearing
bit 0 to get `0x1000015A`.
- Starting the seed patch at the wrong offset; the seed is at `0x1000EC70`.
### Partial Credit Guidelines
- Award partial credit for one correct threshold site out of two, or for a
correct immediate value without the `<` to `<=` reasoning.
- Award partial credit for a correct banner text with incorrectly documented
bytes, or for partial character-by-character documentation.
- Award partial credit for a correct block-length patch without the IEEE-754
print math.
- Award partial credit for one of the two GDB argument captures, or for a
partial register override.
- Award no credit for patches that change string length or overwrite adjacent
flash.
---
## Appendix: Expected Binary Diff
| # | Bug | File Offset(s) | Flash Address(es) | Original Bytes | Patched Bytes |
|---|-----|----------------|-------------------|----------------|---------------|
| 1a | Operator threshold | `0x0302` | `0x10000302` | `5E 2B` | `3B 2B` |
| 1b | Dispatch threshold | `0x0312` | `0x10000312` | `5E 2B` | `3B 2B` |
| 2 | TRACK banner | `0xC4BF` - `0xC4C4` | `0x1000C4BF` - `0x1000C4C4` | `4E 4F 52 4D 41 4C` | `44 41 4E 47 45 52` |
| 3 | Block length | `0xEC60` - `0xEC67` | `0x1000EC60` - `0x1000EC67` | `9A 99 99 99 99 99 09 40` | `7B 14 AE 47 E1 7A D4 3F` |
| 4 | Signal seed | `0xEC70` - `0xEC73` | `0x1000EC70` - `0x1000EC73` | `0A 0A 0A 0A` | `74 65 20 6B` |
Four defects, five changed regions: two immediate bytes (`0x3B 2B` at each
threshold), six banner bytes, eight block-length bytes, and four seed bytes.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+143
View File
@@ -0,0 +1,143 @@
// 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: auth.h
// Desc: Declares the Ouroboros authentication engine API for RP2350 firmware.
// Created: 2026
#ifndef AUTH_H
#define AUTH_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/**
* @brief Onboard LED GPIO pin number.
*
* The RP2350 Pico 2 onboard LED is connected to GPIO 25. Driven high
* on successful authentication and low on failure or idle.
*/
#define AUTH_LED_PIN 25u
/**
* @brief Maximum accepted terminal passphrase length in bytes.
*
* The CLI accepts interactive human-entered passphrases up to 512 bytes,
* matching the hardened host demo boundary before policy validation.
*/
#define AUTH_PASSPHRASE_MAX_LEN 512u
/**
* @brief Required number of lowercase words in the hardened passphrase.
*
* The embedded hardened workflow matches the host-side policy exactly:
* twelve lowercase ASCII words separated by whitespace.
*/
#define AUTH_REQUIRED_WORDS 12u
/**
* @brief Hardened Argon2id salt size in bytes.
*
* Every demo artifact carries a per-ciphertext random 128-bit salt.
*/
#define AUTH_SALT_SIZE 16u
/**
* @brief Hardened XChaCha20 nonce size in bytes.
*
* XChaCha20-Poly1305 consumes a 192-bit nonce in the outer construction.
*/
#define AUTH_NONCE_SIZE 24u
/**
* @brief Subkey size in bytes derived from Argon2id.
*
* The AEAD key size is 256 bits.
*/
#define AUTH_KEY_SIZE 32u
/**
* @brief AEAD authentication tag size in bytes.
*
* XChaCha20-Poly1305 appends a 128-bit authentication tag.
*/
#define AUTH_TAG_SIZE 16u
/**
* @brief Plaintext payload size in bytes.
*
* The fixed dispatch payload is 48 bytes: LED state, UART bytes,
* and trailing reserved bytes matching the Rust hardened demo layout.
*/
#define AUTH_PAYLOAD_SIZE 48u
/**
* @brief Full ciphertext-plus-tag artifact size in bytes.
*
* The encrypted payload is 48 bytes followed by a 16-byte tag.
*/
#define AUTH_CIPHERTEXT_SIZE (AUTH_PAYLOAD_SIZE + AUTH_TAG_SIZE)
/**
* @brief Authentication result codes returned by the hardened engine.
*
* These values let the CLI distinguish policy failures from
* cryptographic authentication failures without guessing.
*/
typedef enum auth_result {
AUTH_RESULT_SUCCESS = 0,
AUTH_RESULT_POLICY_VIOLATION = 1,
AUTH_RESULT_AUTHENTICATION_FAILED = 2,
AUTH_RESULT_INTERNAL_ERROR = 3,
} auth_result_t;
/**
* @brief Initialize the Ouroboros authentication module.
*
* Configures the onboard LED GPIO and marks the hardened engine as ready
* for passphrase authentication.
*
* @param None.
* @return bool true when initialization is successful, else false.
*/
bool auth_init(void);
/**
* @brief Execute the hardened Ouroboros authentication pipeline.
*
* Validates the strict 12-word lowercase passphrase policy, derives the
* 256-bit AEAD key with Argon2id using artifact parameters, decrypts the
* embedded XChaCha20-Poly1305 ciphertext, and dispatches GPIO25/UART
* payload bytes on success.
*
* @param passphrase Pointer to passphrase bytes.
* @param passphrase_len Number of passphrase bytes.
* @return auth_result_t Detailed authentication outcome for the caller.
*/
auth_result_t auth_execute(const uint8_t *passphrase,
size_t passphrase_len);
#endif // AUTH_H
+59
View File
@@ -0,0 +1,59 @@
// 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: cli.h
// Desc: Declares the CLI UART passphrase input interface for Ouroboros.
// Created: 2026
#ifndef CLI_H
#define CLI_H
#include <stddef.h>
/**
* @brief Print the UART passphrase prompt.
*
* Emits a minimal shell-style prompt followed by a space so the
* terminal clearly indicates that hardened passphrase input is expected.
*
* @param None.
* @return None.
*/
void print_prompt(void);
/**
* @brief Service one UART polling step for passphrase input.
*
* Polls stdio for a character, dispatches backspace or newline
* handling, and appends printable characters to the passphrase
* buffer. Call repeatedly from the main loop.
*
* @param buf Pointer to mutable passphrase buffer.
* @param idx Pointer to current buffer length.
* @return None.
*/
void service_uart(char *buf, size_t *idx);
#endif // CLI_H
+60
View File
@@ -0,0 +1,60 @@
// 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.
//
// This file is generated by scripts/dec.py. Do not edit by hand.
#ifndef DEMO_ARTIFACT_H
#define DEMO_ARTIFACT_H
#include <stdint.h>
#define DEMO_ARTIFACT_FORMAT "ouroboros-hardened-demo-v1"
#define DEMO_MEMORY_KIB 64u
#define DEMO_ITERATIONS 3u
#define DEMO_PARALLELISM 1u
static const uint8_t DEMO_SALT[16] = {
0xF2u, 0xD5u, 0x18u, 0x63u, 0x9Au, 0x82u, 0x01u, 0x9Du,
0xC2u, 0xD7u, 0xAFu, 0xA5u, 0xCDu, 0xB6u, 0xD8u, 0x71u
};
static const uint8_t DEMO_NONCE[24] = {
0x1Cu, 0xEFu, 0x79u, 0x0Du, 0x77u, 0x9Eu, 0x7Cu, 0x04u,
0xE7u, 0xF0u, 0x66u, 0xDDu, 0x90u, 0xD0u, 0x80u, 0x70u,
0x87u, 0x97u, 0x67u, 0x1Fu, 0x79u, 0xEFu, 0xC4u, 0xE4u
};
static const uint8_t DEMO_CIPHERTEXT_AND_TAG[64] = {
0x2Cu, 0x23u, 0xB2u, 0x7Eu, 0x95u, 0x62u, 0xB8u, 0xEDu,
0x9Eu, 0x08u, 0xE0u, 0x6Du, 0xD9u, 0x9Du, 0xB4u, 0x91u,
0x3Eu, 0x81u, 0x9Au, 0x77u, 0x8Bu, 0xB4u, 0x7Bu, 0x71u,
0xBCu, 0x66u, 0x1Eu, 0x6Eu, 0x73u, 0x1Au, 0x81u, 0x54u,
0xCDu, 0xB5u, 0x36u, 0xA4u, 0x76u, 0x7Eu, 0x9Bu, 0xF8u,
0x53u, 0x3Eu, 0x03u, 0x1Du, 0xB8u, 0xE5u, 0xAEu, 0x7Au,
0xADu, 0xB4u, 0x31u, 0xCFu, 0x12u, 0xD9u, 0xF9u, 0xC4u,
0x5Fu, 0xA9u, 0xB9u, 0x4Bu, 0x80u, 0xDCu, 0xBBu, 0xDEu
};
#endif // DEMO_ARTIFACT_H
+39
View File
@@ -0,0 +1,39 @@
// 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_config.h
// Desc: Configures the minimal mbedTLS cryptographic features required by
// the Ouroboros AEAD engine on RP2350.
// Created: 2026
#ifndef MBEDTLS_CONFIG_H
#define MBEDTLS_CONFIG_H
#define MBEDTLS_CHACHA20_C
#define MBEDTLS_CHACHAPOLY_C
#define MBEDTLS_POLY1305_C
#define MBEDTLS_PLATFORM_C
#endif // MBEDTLS_CONFIG_H
+121
View File
@@ -0,0 +1,121 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
else ()
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
endif ()
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})
+944
View File
@@ -0,0 +1,944 @@
"""Generate hardened demo artifacts for the RP2350 Ouroboros firmware.
This script writes the same JSON schema used by the Rust demo and also emits
the generated C header consumed by the embedded firmware.
"""
import argparse
import json
import platform
import secrets
import sys
from pathlib import Path
from typing import Optional
DEFAULT_PASSPHRASE = (
"orbit olive ladder marble quartz canyon "
"ripple saddle violet ember walnut falcon"
)
DEFAULT_TEXT = "hello"
DEFAULT_OUTPUT_JSON = "scripts/demo_artifact.json"
DEFAULT_OUTPUT_HEADER = "include/demo_artifact.h"
DEFAULT_MEMORY_KIB = 64
DEFAULT_ITERATIONS = 3
DEFAULT_PARALLELISM = 1
ARTIFACT_FORMAT = "ouroboros-hardened-demo-v1"
_KEY_HELP = "12-word lowercase passphrase"
_TEXT_HELP = "Text to place in payload bytes 1..7"
_OUT_HELP = "Output JSON artifact path"
_HEADER_OUT_HELP = "Output generated C header path"
_FROM_JSON_HELP = (
"Load existing JSON artifact and emit header without re-encrypting"
)
_CHECK_HEADER_HELP = (
"Optional path to compare against generated header and fail if stale"
)
_SALT_HEX_HELP = "Optional fixed 16-byte salt as hex"
_NONCE_HEX_HELP = "Optional fixed 24-byte nonce as hex"
_NO_CRLF_HELP = "Do not append CRLF to payload text"
_LED_OFF_HELP = "Encode LED off instead of on"
_MEMORY_HELP = "Argon2 memory cost in KiB"
_ITERATIONS_HELP = "Argon2 time cost"
_PARALLELISM_HELP = "Argon2 parallel lanes"
_POLICY_ERROR = (
"Hardened mode requires exactly 12 lowercase ASCII words in --key."
)
_PAYLOAD_TOO_LONG = (
"Output text is too long for fixed dispatch "
"(max 7 bytes after CRLF handling)."
)
_INVALID_JSON = "Artifact JSON at {0} is invalid JSON."
_MISMATCH_PREFIX = "Detected a Python native-extension architecture mismatch. "
_REINSTALL_DEPS = ("Recreate this virtual environment with a native Python "
"and reinstall deps:")
_REINSTALL_LINES = (
"rm -rf .venv",
"python3 -m venv .venv",
"source .venv/bin/activate",
"python3 -m pip install -U pip setuptools wheel",
"python3 -m pip install argon2-cffi pynacl",
)
_HEADER_TEMPLATE = """// 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.
//
// This file is generated by scripts/dec.py. Do not edit by hand.
#ifndef DEMO_ARTIFACT_H
#define DEMO_ARTIFACT_H
#include <stdint.h>
#define DEMO_ARTIFACT_FORMAT "{artifact_format}"
#define DEMO_MEMORY_KIB {memory_kib}u
#define DEMO_ITERATIONS {iterations}u
#define DEMO_PARALLELISM {parallelism}u
static const uint8_t DEMO_SALT[16] = {{
{salt_body}
}};
static const uint8_t DEMO_NONCE[24] = {{
{nonce_body}
}};
static const uint8_t DEMO_CIPHERTEXT_AND_TAG[64] = {{
{cipher_body}
}};
#endif // DEMO_ARTIFACT_H
"""
def _raise_dependency_error(package_name, install_hint, exc):
"""Raise a RuntimeError with environment-aware dependency diagnostics.
Parameters
----------
package_name : str
Package common name for the error message.
install_hint : str
Pip install command in the error message.
exc : Exception
Import error observed while loading the native module.
Returns
-------
None
"""
message = "Hardened mode requires {0}. Install with: {1}".format(
package_name, install_hint)
message += _reinstall_message() if _is_arch_mismatch(exc) else ""
raise RuntimeError(message) from exc
def _is_arch_mismatch(exc):
"""Report whether the interpreter likely has a native-extension mismatch.
Parameters
----------
exc : Exception
Import error observed while loading the native module.
Returns
-------
bool
True when the error text matches a native architecture mismatch.
"""
detail = str(exc)
return (
"incompatible architecture" in detail
or "_cffi_backend" in detail
or "mach-o file, but is an incompatible architecture" in detail
)
def _reinstall_message():
"""Build the native-interpreter reinstall diagnostic text.
Parameters
----------
None
Returns
-------
str
Newline-delimited machine and reinstall details, or an empty string.
"""
machine = platform.machine()
head = (_MISMATCH_PREFIX
+ "Current interpreter reports machine=" + machine
+ ", executable=" + sys.executable + ".")
lines = [" {0}".format(item) for item in _REINSTALL_LINES]
return ("\n" + head + "\n" + _REINSTALL_DEPS + "\n"
+ "\n".join(lines))
def _is_policy_compliant(passphrase):
"""Return True when passphrase is exactly 12 lowercase ASCII words.
Parameters
----------
passphrase : str
Candidate operator passphrase.
Returns
-------
bool
True when the passphrase satisfies the gate policy.
"""
words = passphrase.split()
if len(words) != 12:
return False
return all(
word and all(ch.isascii() and ch.islower() for ch in word)
for word in words
)
def _build_payload(text_str, led_on=True, append_crlf=True):
"""Build the fixed 48-byte payload dispatched by the firmware.
Parameters
----------
text_str : str
Console text placed in payload bytes 1..7.
led_on : bool
True turns the LED byte on, False leaves it off.
append_crlf : bool
True appends CRLF to the console text.
Returns
-------
bytes
Fixed 48-byte dispatch payload.
"""
tx_bytes = text_str.encode() + (b"\r\n" if append_crlf else b"")
if len(tx_bytes) > 7:
raise ValueError(_PAYLOAD_TOO_LONG)
payload = bytearray(48)
payload[0] = 1 if led_on else 0
payload[1:1 + len(tx_bytes)] = tx_bytes
return bytes(payload)
def _resolve_salt_nonce(salt, nonce):
"""Confirm or generate the 16-byte salt and 24-byte nonce.
Parameters
----------
salt : bytes or None
Optional fixed salt value.
nonce : bytes or None
Optional fixed nonce value.
Returns
-------
tuple
Confirmed (salt, nonce) byte values.
"""
salt_word = secrets.token_bytes(16) if salt is None else salt
nonce_word = secrets.token_bytes(24) if nonce is None else nonce
if len(salt_word) != 16:
raise ValueError("Hardened salt must be exactly 16 bytes.")
if len(nonce_word) != 24:
raise ValueError("Hardened nonce must be exactly 24 bytes.")
return salt_word, nonce_word
def _optional_hex(value, length, label):
"""Decode an optional hex argument, leaving absent values as None.
Parameters
----------
value : str or None
Hex string supplied on the command line.
length : int
Expected decoded byte length.
label : str
Field name used in validation errors.
Returns
-------
bytes or None
Decoded bytes, or None when value is absent.
"""
return _hex_decode(value, length, label) if value else None
def _load_argon2():
"""Load the Argon2 low-level binding with dependency diagnostics.
Parameters
----------
None
Returns
-------
tuple
Argon2 Type enum and hash_secret_raw callable.
"""
try:
from argon2.low_level import Type, hash_secret_raw
except ImportError as exc:
install_hint = "python3 -m pip install argon2-cffi"
_raise_dependency_error("argon2-cffi", install_hint, exc)
return Type, hash_secret_raw
def _load_nacl_encrypt():
"""Load the XChaCha20-Poly1305 encrypt binding with diagnostics.
Parameters
----------
None
Returns
-------
callable
PyNaCl crypto_aead_xchacha20poly1305_ietf_encrypt function.
"""
try:
from nacl.bindings import (
crypto_aead_xchacha20poly1305_ietf_encrypt as encrypt,
)
except ImportError as exc:
install_hint = "python3 -m pip install pynacl"
_raise_dependency_error("PyNaCl", install_hint, exc)
return encrypt
def _derive_hardened_key(passphrase, salt, memory_kib, iterations,
parallelism):
"""Derive a 32-byte key with Argon2id.
Parameters
----------
passphrase : str
Policy-compliant operator passphrase.
salt : bytes
16-byte Argon2 salt.
memory_kib : int
Argon2 memory cost in KiB.
iterations : int
Argon2 time cost.
parallelism : int
Argon2 parallel lane count.
Returns
-------
bytes
Derived 32-byte key.
"""
arg2_type, hash_secret_raw = _load_argon2()
args = (
passphrase.encode(), salt, iterations, memory_kib,
parallelism, 32, arg2_type,
)
return hash_secret_raw(*args)
def _entry_key(phrase, seed, memory_kib, iterations, parallelism):
"""Derive the entry key word for the hardened artifact.
Parameters
----------
phrase : str
Policy-compliant operator passphrase.
seed : bytes
16-byte Argon2 salt.
memory_kib : int
Argon2 memory cost in KiB.
iterations : int
Argon2 time cost.
parallelism : int
Argon2 parallel lane count.
Returns
-------
bytes
Derived 32-byte entry key.
"""
return _derive_hardened_key(
phrase, seed, memory_kib, iterations, parallelism
)
def _build_key_material(phrase, salt, nonce, memory_kib, iterations,
parallelism):
"""Resolve salt, nonce, and the entry key.
Parameters
----------
phrase : str
Policy-compliant operator passphrase.
salt : bytes or None
Optional fixed salt value.
nonce : bytes or None
Optional fixed nonce value.
memory_kib : int
Argon2 memory cost in KiB.
iterations : int
Argon2 time cost.
parallelism : int
Argon2 parallel lane count.
Returns
-------
tuple
Resolved (salt, nonce, key) values.
"""
seed, nonce_word = _resolve_salt_nonce(salt, nonce)
key = _entry_key(phrase, seed, memory_kib, iterations, parallelism)
return seed, nonce_word, key
def build_hardened_entry(
key_str,
text_str,
led_on=True,
append_crlf=True,
salt: Optional[bytes] = None,
nonce: Optional[bytes] = None,
memory_kib=DEFAULT_MEMORY_KIB,
iterations=DEFAULT_ITERATIONS,
parallelism=DEFAULT_PARALLELISM,
):
"""Build a hardened encrypted entry with Argon2id + XChaCha20-Poly1305.
Parameters
----------
key_str : str
Policy-compliant 12-word operator passphrase.
text_str : str
Console text placed in payload bytes 1..7.
led_on : bool
True turns the LED byte on, False leaves it off.
append_crlf : bool
True appends CRLF to the console text.
salt : bytes or None
Optional fixed 16-byte salt.
nonce : bytes or None
Optional fixed 24-byte nonce.
memory_kib : int
Argon2 memory cost in KiB.
iterations : int
Argon2 time cost.
parallelism : int
Argon2 parallel lane count.
Returns
-------
tuple
Resulting (salt, nonce, ciphertext_and_tag) values.
"""
if not _is_policy_compliant(key_str):
raise ValueError(_POLICY_ERROR)
salt_word, nonce_word, key = _build_key_material(
key_str, salt, nonce, memory_kib, iterations, parallelism)
payload = _build_payload(text_str, led_on, append_crlf)
encrypt = _load_nacl_encrypt()
ciphertext_and_tag = encrypt(payload, b"", nonce_word, key)
return salt_word, nonce_word, ciphertext_and_tag
def _hex_decode(value, expected_len, label):
"""Decode a hex string and validate the expected byte length.
Parameters
----------
value : str
Hex string to decode.
expected_len : int
Required decoded byte length.
label : str
Field name used in validation errors.
Returns
-------
bytes
Decoded bytes of the expected length.
"""
try:
decoded = bytes.fromhex(value)
except ValueError as exc:
raise ValueError("{0} must be valid hex.".format(label)) from exc
if len(decoded) != expected_len:
message = "{0} must decode to exactly {1} bytes.".format(
label, expected_len)
raise ValueError(message)
return decoded
def _artifact_dict(memory_kib, iterations, parallelism, salt, nonce, cipher):
"""Compose the canonical artifact dictionary.
Parameters
----------
memory_kib : int
Argon2 memory cost in KiB.
iterations : int
Argon2 time cost.
parallelism : int
Argon2 parallel lane count.
salt : bytes
16-byte salt.
nonce : bytes
24-byte nonce.
cipher : bytes
64-byte ciphertext and tag.
Returns
-------
dict
Canonical artifact fields with hex-encoded byte values.
"""
return {
"format": ARTIFACT_FORMAT, "memory_kib": memory_kib,
"iterations": iterations, "parallelism": parallelism,
"salt_hex": salt.hex(), "nonce_hex": nonce.hex(),
"ciphertext_and_tag_hex": cipher.hex(),
}
def _format_c_array(data, width=8):
"""Format bytes as an indented C array literal body.
Parameters
----------
data : bytes
Bytes to serialize as a C array.
width : int
Byte values emitted per source line.
Returns
-------
str
Indented, comma-joined C array body.
"""
items = [f"0x{value:02X}u" for value in data]
rows = [", ".join(items[offset:offset + width])
for offset in range(0, len(items), width)]
return ",\n".join(" " + row for row in rows)
def _render_header(artifact):
"""Render the generated firmware header text from the artifact.
Parameters
----------
artifact : dict
Canonical artifact dictionary.
Returns
-------
str
Complete generated C header text.
"""
cipher = bytes.fromhex(artifact["ciphertext_and_tag_hex"])
return _HEADER_TEMPLATE.format(
artifact_format=ARTIFACT_FORMAT, memory_kib=artifact["memory_kib"],
iterations=artifact["iterations"], parallelism=artifact["parallelism"],
salt_body=_format_c_array(bytes.fromhex(artifact["salt_hex"])),
nonce_body=_format_c_array(bytes.fromhex(artifact["nonce_hex"])),
cipher_body=_format_c_array(cipher),
)
def _write_header(path, artifact):
"""Write the generated firmware header from the hardened artifact.
Parameters
----------
path : str
Destination header file path.
artifact : dict
Canonical artifact dictionary.
Returns
-------
Path
Resolved destination header path.
"""
output_path = Path(path)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(_render_header(artifact), encoding="utf-8")
return output_path.resolve()
def _parse_json_file(path):
"""Load and parse the artifact JSON file.
Parameters
----------
path : str
Artifact JSON file path.
Returns
-------
dict
Parsed JSON document.
"""
raw = Path(path).read_text(encoding="utf-8")
try:
parsed = json.loads(raw)
except json.JSONDecodeError as exc:
raise ValueError(_INVALID_JSON.format(path)) from exc
return parsed
def _validate_artifact_format(parsed):
"""Reject artifact JSON with an unexpected format marker.
Parameters
----------
parsed : dict
Parsed JSON document.
Returns
-------
None
"""
actual = parsed.get("format")
if actual != ARTIFACT_FORMAT:
raise ValueError(
"Artifact format must be '{0}', got '{1}'.".format(
ARTIFACT_FORMAT, actual))
def _parsed_ints(parsed):
"""Parse the integer cost fields from artifact JSON.
Parameters
----------
parsed : dict
Parsed JSON document.
Returns
-------
list
Parsed memory_kib, iterations, and parallelism integer values.
"""
try:
int_names = ("memory_kib", "iterations", "parallelism")
return [int(parsed[name]) for name in int_names]
except (KeyError, TypeError, ValueError) as exc:
raise ValueError(
"Artifact must include integer memory_kib, "
"iterations, and parallelism fields.") from exc
def _artifact_ints(parsed):
"""Unpack the three integer cost fields from artifact JSON.
Parameters
----------
parsed : dict
Parsed JSON document.
Returns
-------
tuple
(memory_kib, iterations, parallelism) integer values.
"""
values = _parsed_ints(parsed)
return values[0], values[1], values[2]
def _artifact_from_parsed(parsed):
"""Reconstruct a canonical artifact dict from parsed JSON.
Parameters
----------
parsed : dict
Parsed JSON document.
Returns
-------
dict
Canonical artifact dictionary.
"""
_validate_artifact_format(parsed)
memory_kib, iterations, parallelism = _artifact_ints(parsed)
cipher_field = "ciphertext_and_tag_hex"
salt = _hex_decode(parsed.get("salt_hex", ""), 16, "salt_hex")
nonce = _hex_decode(parsed.get("nonce_hex", ""), 24, "nonce_hex")
cipher = _hex_decode(parsed.get(cipher_field, ""), 64, cipher_field)
return _artifact_dict(
memory_kib, iterations, parallelism, salt, nonce, cipher)
def _load_artifact_json(path):
"""Load and validate a hardened artifact JSON for header generation.
Parameters
----------
path : str
Artifact JSON file path.
Returns
-------
dict
Canonical artifact dictionary.
"""
parsed = _parse_json_file(path)
return _artifact_from_parsed(parsed)
def _check_header_match(generated_path, expected_path):
"""Fail when the generated header does not match an expected file.
Parameters
----------
generated_path : str
Generated header file path.
expected_path : str
Expected committed header file path.
Returns
-------
None
"""
generated = Path(generated_path).read_text(encoding="utf-8")
expected = Path(expected_path).read_text(encoding="utf-8")
if generated != expected:
raise RuntimeError(
"Generated header does not match committed "
"include/demo_artifact.h. Regenerate and commit "
"updated artifacts with scripts/dec.py.")
def _print_header_check(header_path, expected_path):
"""Print the verified header match result.
Parameters
----------
header_path : str
Generated header file path.
expected_path : str
Expected committed header file path.
Returns
-------
None
"""
_check_header_match(header_path, expected_path)
print("Verified header matches: {0}".format(Path(expected_path).resolve()))
def _parse_args():
"""Parse command-line arguments.
Parameters
----------
None
Returns
-------
argparse.Namespace
Parsed command-line arguments.
"""
parser = argparse.ArgumentParser(description=__doc__)
for argument_group in _ARGUMENT_GROUPS:
for name, kwargs in argument_group:
parser.add_argument(name, **kwargs)
return parser.parse_args()
def _build_from_args(args, salt, nonce):
"""Build the hardened entry from parsed arguments.
Parameters
----------
args : argparse.Namespace
Parsed command-line arguments.
salt : bytes or None
Optional fixed salt value.
nonce : bytes or None
Optional fixed nonce value.
Returns
-------
tuple
Resulting (salt, nonce, ciphertext_and_tag) values.
"""
return build_hardened_entry(
key_str=args.key, text_str=args.text, led_on=not args.led_off,
append_crlf=not args.no_crlf, salt=salt, nonce=nonce,
memory_kib=args.memory_kib, iterations=args.iterations,
parallelism=args.parallelism,
)
def _flush_outputs(args, salt, nonce, cipher):
"""Write the artifact JSON and header, then return the header path.
Parameters
----------
args : argparse.Namespace
Parsed command-line arguments.
salt : bytes
16-byte salt.
nonce : bytes
24-byte nonce.
cipher : bytes
64-byte ciphertext and tag.
Returns
-------
Path
Resolved generated header path.
"""
json_path, artifact = _write_demo_json(
args.out, args.memory_kib, args.iterations, args.parallelism,
salt, nonce, cipher)
header_path = _write_header(args.header_out, artifact)
print("Wrote hardened demo artifact JSON: {0}".format(json_path))
print("Wrote generated firmware header: {0}".format(header_path))
return header_path
def _write_demo_json(path, memory_kib, iterations, parallelism, salt,
nonce, ciphertext_and_tag):
"""Write the hardened JSON artifact consumed by docs and validation.
Parameters
----------
path : str
Destination JSON file path.
memory_kib : int
Argon2 memory cost in KiB.
iterations : int
Argon2 time cost.
parallelism : int
Argon2 parallel lane count.
salt : bytes
16-byte salt.
nonce : bytes
24-byte nonce.
ciphertext_and_tag : bytes
64-byte ciphertext and tag.
Returns
-------
tuple
Resolved (path, artifact) values.
"""
output_path = Path(path)
output_path.parent.mkdir(parents=True, exist_ok=True)
artifact = _artifact_dict(
memory_kib, iterations, parallelism, salt, nonce, ciphertext_and_tag)
text = json.dumps(artifact, indent=2) + "\n"
output_path.write_text(text, encoding="utf-8")
return output_path.resolve(), artifact
def _flush_from_json(args):
"""Write the header from an existing artifact JSON.
Parameters
----------
args : argparse.Namespace
Parsed command-line arguments.
Returns
-------
Path
Resolved generated header path.
"""
artifact = _load_artifact_json(args.from_json)
header_path = _write_header(args.header_out, artifact)
print("Wrote generated firmware header: {0}".format(header_path))
return header_path
def _run_from_json(args):
"""Generate the header only from committed artifact JSON.
Parameters
----------
args : argparse.Namespace
Parsed command-line arguments.
Returns
-------
None
"""
header_path = _flush_from_json(args)
if args.check_header_path:
_print_header_check(header_path, args.check_header_path)
def _run_from_generate(args):
"""Encrypt fresh artifact material, then emit JSON and the header.
Parameters
----------
args : argparse.Namespace
Parsed command-line arguments.
Returns
-------
None
"""
salt = _optional_hex(args.salt_hex, 16, "salt_hex")
nonce = _optional_hex(args.nonce_hex, 24, "nonce_hex")
header_path = _flush_outputs(
args, *_build_from_args(args, salt, nonce),
)
if args.check_header_path:
_print_header_check(header_path, args.check_header_path)
def main():
"""Generate the hardened artifact JSON and C header.
Parameters
----------
None
Returns
-------
None
"""
args = _parse_args()
if args.from_json:
_run_from_json(args)
else:
_run_from_generate(args)
_ARGUMENT_GROUPS = (
(
("--key", dict(default=DEFAULT_PASSPHRASE, help=_KEY_HELP)),
("--salt-hex", dict(help=_SALT_HEX_HELP)),
("--nonce-hex", dict(help=_NONCE_HEX_HELP)),
),
(
("--text", dict(default=DEFAULT_TEXT, help=_TEXT_HELP)),
("--out", dict(default=DEFAULT_OUTPUT_JSON, help=_OUT_HELP)),
("--header-out",
dict(default=DEFAULT_OUTPUT_HEADER, help=_HEADER_OUT_HELP)),
("--from-json", dict(help=_FROM_JSON_HELP)),
("--no-crlf", dict(action="store_true", help=_NO_CRLF_HELP)),
("--led-off", dict(action="store_true", help=_LED_OFF_HELP)),
),
(
("--memory-kib",
dict(type=int, default=DEFAULT_MEMORY_KIB, help=_MEMORY_HELP)),
("--iterations",
dict(type=int, default=DEFAULT_ITERATIONS, help=_ITERATIONS_HELP)),
("--parallelism",
dict(type=int, default=DEFAULT_PARALLELISM,
help=_PARALLELISM_HELP)),
("--check-header-path", dict(help=_CHECK_HEADER_HELP)),
),
)
if __name__ == "__main__":
main()
+9
View File
@@ -0,0 +1,9 @@
{
"format": "ouroboros-hardened-demo-v1",
"memory_kib": 64,
"iterations": 3,
"parallelism": 1,
"salt_hex": "f2d518639a82019dc2d7afa5cdb6d871",
"nonce_hex": "1cef790d779e7c04e7f066dd90d080708797671f79efc4e4",
"ciphertext_and_tag_hex": "2c23b27e9562b8ed9e08e06dd99db4913e819a778bb47b71bc661e6e731a8154cdb536a4767e9bf8533e031db8e5ae7aadb431cf12d9f9c45fa9b94b80dcbbde"
}
+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env python3
"""Verify every technical claim of Operation Copperhead against CTF-02.bin.
Exits 0 only when every address, byte, hash, and derived value in CTF-R.md and
CTF-S.md matches the shipped image and the compiled ELF.
"""
import hashlib
import struct
import sys
from pathlib import Path
BASE = 0x10000000
ROOT = Path(__file__).resolve().parent.parent
BIN = ROOT / "CTF-02.bin"
UF2 = ROOT / "CTF-02.uf2"
EXPECTED_BIN_SHA = "85330c37cd0897746b1af447e4bac371dde2042abd2d61d58a61fe2a8eef3537"
EXPECTED_UF2_SHA = "f3cd4840260db820d792758cecacc5297bef1971b9eacf7601279256d8af1eab"
FRAME_THRESHOLD_A = 0x10000302
FRAME_THRESHOLD_B = 0x10000312
FRAME_TRACK = 0x1000C4B8
FRAME_BLOCKLEN = 0x1000C4F0
FRAME_SIGNALKEY = 0x1000C51C
FRAME_GATE = 0x1000C544
FRAME_AUTH = 0x1000C57C
FRAME_OK = 0x1000C438
FRAME_MISMATCH = 0x1000C43C
FRAME_SPEC_LITERAL = 0x100004FC
FRAME_DOUBLE = 0x1000EC60
FRAME_SEED = 0x1000EC70
FRAME_SALT = 0x1000CEEC
FRAME_NONCE = 0x1000CED4
FRAME_CT = 0x1000CE94
DOUBLE_3_2 = bytes.fromhex("9A99999999990940")
DOUBLE_0_32 = bytes.fromhex("7B14AE47E17AD43F")
SEED_BAD = bytes.fromhex("0A0A0A0A")
SEED_GOOD = bytes.fromhex("7465206B")
SPEC_VALUE = 0x2D879291
BUG_KEY = 0x915DCFF8
RESULTS = []
def check(label, ok, detail=""):
"""Record one verification result.
Parameters
----------
label : str
Human-readable check name.
ok : bool
Whether the check passed.
detail : str
Extra context printed with the result.
Returns
-------
None
"""
RESULTS.append(ok)
print(f"[{'PASS' if ok else 'FAIL'}] {label} {detail}")
def rotl(v, s):
"""Rotate a 32-bit value left."""
return ((v << s) & 0xFFFFFFFF) | (v >> (32 - s))
def qr_phase(a, b, c, d, s):
"""Apply one ARX phase of a ChaCha quarter round."""
a = (a + b) & 0xFFFFFFFF
d ^= a
d = rotl(d, s)
c = (c + d) & 0xFFFFFFFF
b ^= c
b = rotl(b, s)
return a, b, c, d
def derive(seed, iv):
"""Derive the firmware signal key from a seed and IV."""
a, b, c, d = seed, iv, 0x61707865, 0x3320646E
for _ in range(4):
for s in (16, 12, 8, 7):
a, b, c, d = qr_phase(a, b, c, d, s)
return (a ^ d) & 0xFFFFFFFF
def main():
"""Run all verification checks.
Returns
-------
int
Zero when every check passes, else one.
"""
data = BIN.read_bytes()
check("CTF-02.bin SHA-256", hashlib.sha256(data).hexdigest() == EXPECTED_BIN_SHA)
check("CTF-02.uf2 SHA-256",
hashlib.sha256(UF2.read_bytes()).hexdigest() == EXPECTED_UF2_SHA)
check("CTF-02.bin size", len(data) == 62308, f"({len(data)})")
check("vector table", data[0:32].hex() ==
"002008205b0100101b0100101d01001011010010110100101101001011010010")
check("initial SP", struct.unpack("<I", data[0:4])[0] == 0x20082000)
check("reset vector", struct.unpack("<I", data[4:8])[0] == 0x1000015B)
check("compare site A immediate", data[FRAME_THRESHOLD_A - BASE:
FRAME_THRESHOLD_A - BASE + 2] == bytes.fromhex("5E2B"))
check("compare site B immediate", data[FRAME_THRESHOLD_B - BASE:
FRAME_THRESHOLD_B - BASE + 2] == bytes.fromhex("5E2B"))
check("TRACK banner string", data[FRAME_TRACK - BASE:FRAME_TRACK - BASE + 13]
== b"TRACK: NORMAL")
check("BLOCK LENGTH string", data[FRAME_BLOCKLEN - BASE:
FRAME_BLOCKLEN - BASE + 18] == b"BLOCK LENGTH: %u M")
check("SIGNAL KEY string", data[FRAME_SIGNALKEY - BASE:
FRAME_SIGNALKEY - BASE + len(b"SIGNAL KEY: 0x%08X %s")]
== b"SIGNAL KEY: 0x%08X %s")
check("gate message", data[FRAME_GATE - BASE:FRAME_GATE - BASE
+ len(b"Enter exactly 12 lowercase words separated by spaces.")]
== b"Enter exactly 12 lowercase words separated by spaces.")
check("AUTHORITY FRAME string", data[FRAME_AUTH - BASE:FRAME_AUTH - BASE
+ len(b"AUTHORITY FRAME: VERIFIED")] == b"AUTHORITY FRAME: VERIFIED")
check("OK string", data[FRAME_OK - BASE:FRAME_OK - BASE + 3] == b"OK\x00")
check("MISMATCH string", data[FRAME_MISMATCH - BASE:
FRAME_MISMATCH - BASE + 9] == b"MISMATCH\x00")
check("SIGNAL_SPEC literal", struct.unpack("<I", data[
FRAME_SPEC_LITERAL - BASE:FRAME_SPEC_LITERAL - BASE + 4])[0] == SPEC_VALUE)
check("3.2 double bytes", data[FRAME_DOUBLE - BASE:FRAME_DOUBLE - BASE + 8]
== DOUBLE_3_2)
check("0.32 target bytes", DOUBLE_0_32 == bytes.fromhex("7B14AE47E17AD43F"))
check("bug seed bytes", data[FRAME_SEED - BASE:FRAME_SEED - BASE + 4] == SEED_BAD)
check("good seed bytes", SEED_GOOD == bytes.fromhex("7465206B"))
for label, addr in (("salt", FRAME_SALT), ("nonce", FRAME_NONCE),
("ciphertext", FRAME_CT)):
check(f"{label} present", data[addr - BASE:addr - BASE + 16] != b"\x00" * 16)
iv = derive(0x6B206574, 0)
check("derived IV", iv == 0x43C974F6, f"(0x{iv:08X})")
check("bug-derived key", derive(0x0A0A0A0A, iv) == BUG_KEY,
f"(0x{derive(0x0A0A0A0A, iv):08X})")
honest = derive(0x6B206574, iv)
check("honest key equals SIGNAL_SPEC", honest == SPEC_VALUE, f"(0x{honest:08X})")
total = sum(RESULTS)
print(f"\n{total}/{len(RESULTS)} checks passed")
return 0 if total == len(RESULTS) else 1
if __name__ == "__main__":
sys.exit(main())
+362
View File
@@ -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;
}
+149
View File
@@ -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);
}
+404
View File
@@ -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);
}
}
+49
View File
@@ -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;
}
}
+437
View File
@@ -0,0 +1,437 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
#ifndef ARGON2_H
#define ARGON2_H
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#if defined(__cplusplus)
extern "C" {
#endif
/* Symbols visibility control */
#ifdef A2_VISCTL
#define ARGON2_PUBLIC __attribute__((visibility("default")))
#define ARGON2_LOCAL __attribute__ ((visibility ("hidden")))
#elif defined(_MSC_VER)
#define ARGON2_PUBLIC __declspec(dllexport)
#define ARGON2_LOCAL
#else
#define ARGON2_PUBLIC
#define ARGON2_LOCAL
#endif
/*
* Argon2 input parameter restrictions
*/
/* Minimum and maximum number of lanes (degree of parallelism) */
#define ARGON2_MIN_LANES UINT32_C(1)
#define ARGON2_MAX_LANES UINT32_C(0xFFFFFF)
/* Minimum and maximum number of threads */
#define ARGON2_MIN_THREADS UINT32_C(1)
#define ARGON2_MAX_THREADS UINT32_C(0xFFFFFF)
/* Number of synchronization points between lanes per pass */
#define ARGON2_SYNC_POINTS UINT32_C(4)
/* Minimum and maximum digest size in bytes */
#define ARGON2_MIN_OUTLEN UINT32_C(4)
#define ARGON2_MAX_OUTLEN UINT32_C(0xFFFFFFFF)
/* Minimum and maximum number of memory blocks (each of BLOCK_SIZE bytes) */
#define ARGON2_MIN_MEMORY (2 * ARGON2_SYNC_POINTS) /* 2 blocks per slice */
#define ARGON2_MIN(a, b) ((a) < (b) ? (a) : (b))
/* Max memory size is addressing-space/2, topping at 2^32 blocks (4 TB) */
#define ARGON2_MAX_MEMORY_BITS \
ARGON2_MIN(UINT32_C(32), (sizeof(void *) * CHAR_BIT - 10 - 1))
#define ARGON2_MAX_MEMORY \
ARGON2_MIN(UINT32_C(0xFFFFFFFF), UINT64_C(1) << ARGON2_MAX_MEMORY_BITS)
/* Minimum and maximum number of passes */
#define ARGON2_MIN_TIME UINT32_C(1)
#define ARGON2_MAX_TIME UINT32_C(0xFFFFFFFF)
/* Minimum and maximum password length in bytes */
#define ARGON2_MIN_PWD_LENGTH UINT32_C(0)
#define ARGON2_MAX_PWD_LENGTH UINT32_C(0xFFFFFFFF)
/* Minimum and maximum associated data length in bytes */
#define ARGON2_MIN_AD_LENGTH UINT32_C(0)
#define ARGON2_MAX_AD_LENGTH UINT32_C(0xFFFFFFFF)
/* Minimum and maximum salt length in bytes */
#define ARGON2_MIN_SALT_LENGTH UINT32_C(8)
#define ARGON2_MAX_SALT_LENGTH UINT32_C(0xFFFFFFFF)
/* Minimum and maximum key length in bytes */
#define ARGON2_MIN_SECRET UINT32_C(0)
#define ARGON2_MAX_SECRET UINT32_C(0xFFFFFFFF)
/* Flags to determine which fields are securely wiped (default = no wipe). */
#define ARGON2_DEFAULT_FLAGS UINT32_C(0)
#define ARGON2_FLAG_CLEAR_PASSWORD (UINT32_C(1) << 0)
#define ARGON2_FLAG_CLEAR_SECRET (UINT32_C(1) << 1)
/* Global flag to determine if we are wiping internal memory buffers. This flag
* is defined in core.c and defaults to 1 (wipe internal memory). */
extern int FLAG_clear_internal_memory;
/* Error codes */
typedef enum Argon2_ErrorCodes {
ARGON2_OK = 0,
ARGON2_OUTPUT_PTR_NULL = -1,
ARGON2_OUTPUT_TOO_SHORT = -2,
ARGON2_OUTPUT_TOO_LONG = -3,
ARGON2_PWD_TOO_SHORT = -4,
ARGON2_PWD_TOO_LONG = -5,
ARGON2_SALT_TOO_SHORT = -6,
ARGON2_SALT_TOO_LONG = -7,
ARGON2_AD_TOO_SHORT = -8,
ARGON2_AD_TOO_LONG = -9,
ARGON2_SECRET_TOO_SHORT = -10,
ARGON2_SECRET_TOO_LONG = -11,
ARGON2_TIME_TOO_SMALL = -12,
ARGON2_TIME_TOO_LARGE = -13,
ARGON2_MEMORY_TOO_LITTLE = -14,
ARGON2_MEMORY_TOO_MUCH = -15,
ARGON2_LANES_TOO_FEW = -16,
ARGON2_LANES_TOO_MANY = -17,
ARGON2_PWD_PTR_MISMATCH = -18, /* NULL ptr with non-zero length */
ARGON2_SALT_PTR_MISMATCH = -19, /* NULL ptr with non-zero length */
ARGON2_SECRET_PTR_MISMATCH = -20, /* NULL ptr with non-zero length */
ARGON2_AD_PTR_MISMATCH = -21, /* NULL ptr with non-zero length */
ARGON2_MEMORY_ALLOCATION_ERROR = -22,
ARGON2_FREE_MEMORY_CBK_NULL = -23,
ARGON2_ALLOCATE_MEMORY_CBK_NULL = -24,
ARGON2_INCORRECT_PARAMETER = -25,
ARGON2_INCORRECT_TYPE = -26,
ARGON2_OUT_PTR_MISMATCH = -27,
ARGON2_THREADS_TOO_FEW = -28,
ARGON2_THREADS_TOO_MANY = -29,
ARGON2_MISSING_ARGS = -30,
ARGON2_ENCODING_FAIL = -31,
ARGON2_DECODING_FAIL = -32,
ARGON2_THREAD_FAIL = -33,
ARGON2_DECODING_LENGTH_FAIL = -34,
ARGON2_VERIFY_MISMATCH = -35
} argon2_error_codes;
/* Memory allocator types --- for external allocation */
typedef int (*allocate_fptr)(uint8_t **memory, size_t bytes_to_allocate);
typedef void (*deallocate_fptr)(uint8_t *memory, size_t bytes_to_allocate);
/* Argon2 external data structures */
/*
*****
* Context: structure to hold Argon2 inputs:
* output array and its length,
* password and its length,
* salt and its length,
* secret and its length,
* associated data and its length,
* number of passes, amount of used memory (in KBytes, can be rounded up a bit)
* number of parallel threads that will be run.
* All the parameters above affect the output hash value.
* Additionally, two function pointers can be provided to allocate and
* deallocate the memory (if NULL, memory will be allocated internally).
* Also, three flags indicate whether to erase password, secret as soon as they
* are pre-hashed (and thus not needed anymore), and the entire memory
*****
* Simplest situation: you have output array out[8], password is stored in
* pwd[32], salt is stored in salt[16], you do not have keys nor associated
* data. You need to spend 1 GB of RAM and you run 5 passes of Argon2d with
* 4 parallel lanes.
* You want to erase the password, but you're OK with last pass not being
* erased. You want to use the default memory allocator.
* Then you initialize:
Argon2_Context(out,8,pwd,32,salt,16,NULL,0,NULL,0,5,1<<20,4,4,NULL,NULL,true,false,false,false)
*/
typedef struct Argon2_Context {
uint8_t *out; /* output array */
uint32_t outlen; /* digest length */
uint8_t *pwd; /* password array */
uint32_t pwdlen; /* password length */
uint8_t *salt; /* salt array */
uint32_t saltlen; /* salt length */
uint8_t *secret; /* key array */
uint32_t secretlen; /* key length */
uint8_t *ad; /* associated data array */
uint32_t adlen; /* associated data length */
uint32_t t_cost; /* number of passes */
uint32_t m_cost; /* amount of memory requested (KB) */
uint32_t lanes; /* number of lanes */
uint32_t threads; /* maximum number of threads */
uint32_t version; /* version number */
allocate_fptr allocate_cbk; /* pointer to memory allocator */
deallocate_fptr free_cbk; /* pointer to memory deallocator */
uint32_t flags; /* array of bool options */
} argon2_context;
/* Argon2 primitive type */
typedef enum Argon2_type {
Argon2_d = 0,
Argon2_i = 1,
Argon2_id = 2
} argon2_type;
/* Version of the algorithm */
typedef enum Argon2_version {
ARGON2_VERSION_10 = 0x10,
ARGON2_VERSION_13 = 0x13,
ARGON2_VERSION_NUMBER = ARGON2_VERSION_13
} argon2_version;
/*
* Function that gives the string representation of an argon2_type.
* @param type The argon2_type that we want the string for
* @param uppercase Whether the string should have the first letter uppercase
* @return NULL if invalid type, otherwise the string representation.
*/
ARGON2_PUBLIC const char *argon2_type2string(argon2_type type, int uppercase);
/*
* Function that performs memory-hard hashing with certain degree of parallelism
* @param context Pointer to the Argon2 internal structure
* @return Error code if smth is wrong, ARGON2_OK otherwise
*/
ARGON2_PUBLIC int argon2_ctx(argon2_context *context, argon2_type type);
/**
* Hashes a password with Argon2i, producing an encoded hash
* @param t_cost Number of iterations
* @param m_cost Sets memory usage to m_cost kibibytes
* @param parallelism Number of threads and compute lanes
* @param pwd Pointer to password
* @param pwdlen Password size in bytes
* @param salt Pointer to salt
* @param saltlen Salt size in bytes
* @param hashlen Desired length of the hash in bytes
* @param encoded Buffer where to write the encoded hash
* @param encodedlen Size of the buffer (thus max size of the encoded hash)
* @pre Different parallelism levels will give different results
* @pre Returns ARGON2_OK if successful
*/
ARGON2_PUBLIC int argon2i_hash_encoded(const uint32_t t_cost,
const uint32_t m_cost,
const uint32_t parallelism,
const void *pwd, const size_t pwdlen,
const void *salt, const size_t saltlen,
const size_t hashlen, char *encoded,
const size_t encodedlen);
/**
* Hashes a password with Argon2i, producing a raw hash at @hash
* @param t_cost Number of iterations
* @param m_cost Sets memory usage to m_cost kibibytes
* @param parallelism Number of threads and compute lanes
* @param pwd Pointer to password
* @param pwdlen Password size in bytes
* @param salt Pointer to salt
* @param saltlen Salt size in bytes
* @param hash Buffer where to write the raw hash - updated by the function
* @param hashlen Desired length of the hash in bytes
* @pre Different parallelism levels will give different results
* @pre Returns ARGON2_OK if successful
*/
ARGON2_PUBLIC int argon2i_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
const uint32_t parallelism, const void *pwd,
const size_t pwdlen, const void *salt,
const size_t saltlen, void *hash,
const size_t hashlen);
ARGON2_PUBLIC int argon2d_hash_encoded(const uint32_t t_cost,
const uint32_t m_cost,
const uint32_t parallelism,
const void *pwd, const size_t pwdlen,
const void *salt, const size_t saltlen,
const size_t hashlen, char *encoded,
const size_t encodedlen);
ARGON2_PUBLIC int argon2d_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
const uint32_t parallelism, const void *pwd,
const size_t pwdlen, const void *salt,
const size_t saltlen, void *hash,
const size_t hashlen);
ARGON2_PUBLIC int argon2id_hash_encoded(const uint32_t t_cost,
const uint32_t m_cost,
const uint32_t parallelism,
const void *pwd, const size_t pwdlen,
const void *salt, const size_t saltlen,
const size_t hashlen, char *encoded,
const size_t encodedlen);
ARGON2_PUBLIC int argon2id_hash_raw(const uint32_t t_cost,
const uint32_t m_cost,
const uint32_t parallelism, const void *pwd,
const size_t pwdlen, const void *salt,
const size_t saltlen, void *hash,
const size_t hashlen);
/* generic function underlying the above ones */
ARGON2_PUBLIC int argon2_hash(const uint32_t t_cost, const uint32_t m_cost,
const uint32_t parallelism, const void *pwd,
const size_t pwdlen, const void *salt,
const size_t saltlen, void *hash,
const size_t hashlen, char *encoded,
const size_t encodedlen, argon2_type type,
const uint32_t version);
/**
* Verifies a password against an encoded string
* Encoded string is restricted as in validate_inputs()
* @param encoded String encoding parameters, salt, hash
* @param pwd Pointer to password
* @pre Returns ARGON2_OK if successful
*/
ARGON2_PUBLIC int argon2i_verify(const char *encoded, const void *pwd,
const size_t pwdlen);
ARGON2_PUBLIC int argon2d_verify(const char *encoded, const void *pwd,
const size_t pwdlen);
ARGON2_PUBLIC int argon2id_verify(const char *encoded, const void *pwd,
const size_t pwdlen);
/* generic function underlying the above ones */
ARGON2_PUBLIC int argon2_verify(const char *encoded, const void *pwd,
const size_t pwdlen, argon2_type type);
/**
* Argon2d: Version of Argon2 that picks memory blocks depending
* on the password and salt. Only for side-channel-free
* environment!!
*****
* @param context Pointer to current Argon2 context
* @return Zero if successful, a non zero error code otherwise
*/
ARGON2_PUBLIC int argon2d_ctx(argon2_context *context);
/**
* Argon2i: Version of Argon2 that picks memory blocks
* independent on the password and salt. Good for side-channels,
* but worse w.r.t. tradeoff attacks if only one pass is used.
*****
* @param context Pointer to current Argon2 context
* @return Zero if successful, a non zero error code otherwise
*/
ARGON2_PUBLIC int argon2i_ctx(argon2_context *context);
/**
* Argon2id: Version of Argon2 where the first half-pass over memory is
* password-independent, the rest are password-dependent (on the password and
* salt). OK against side channels (they reduce to 1/2-pass Argon2i), and
* better with w.r.t. tradeoff attacks (similar to Argon2d).
*****
* @param context Pointer to current Argon2 context
* @return Zero if successful, a non zero error code otherwise
*/
ARGON2_PUBLIC int argon2id_ctx(argon2_context *context);
/**
* Verify if a given password is correct for Argon2d hashing
* @param context Pointer to current Argon2 context
* @param hash The password hash to verify. The length of the hash is
* specified by the context outlen member
* @return Zero if successful, a non zero error code otherwise
*/
ARGON2_PUBLIC int argon2d_verify_ctx(argon2_context *context, const char *hash);
/**
* Verify if a given password is correct for Argon2i hashing
* @param context Pointer to current Argon2 context
* @param hash The password hash to verify. The length of the hash is
* specified by the context outlen member
* @return Zero if successful, a non zero error code otherwise
*/
ARGON2_PUBLIC int argon2i_verify_ctx(argon2_context *context, const char *hash);
/**
* Verify if a given password is correct for Argon2id hashing
* @param context Pointer to current Argon2 context
* @param hash The password hash to verify. The length of the hash is
* specified by the context outlen member
* @return Zero if successful, a non zero error code otherwise
*/
ARGON2_PUBLIC int argon2id_verify_ctx(argon2_context *context,
const char *hash);
/* generic function underlying the above ones */
ARGON2_PUBLIC int argon2_verify_ctx(argon2_context *context, const char *hash,
argon2_type type);
/**
* Get the associated error message for given error code
* @return The error message associated with the given error code
*/
ARGON2_PUBLIC const char *argon2_error_message(int error_code);
/**
* Returns the encoded hash length for the given input parameters
* @param t_cost Number of iterations
* @param m_cost Memory usage in kibibytes
* @param parallelism Number of threads; used to compute lanes
* @param saltlen Salt size in bytes
* @param hashlen Hash size in bytes
* @param type The argon2_type that we want the encoded length for
* @return The encoded hash length in bytes
*/
ARGON2_PUBLIC size_t argon2_encodedlen(uint32_t t_cost, uint32_t m_cost,
uint32_t parallelism, uint32_t saltlen,
uint32_t hashlen, argon2_type type);
#if defined(__cplusplus)
}
#endif
#endif
+452
View File
@@ -0,0 +1,452 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "argon2.h"
#include "encoding.h"
#include "core.h"
const char *argon2_type2string(argon2_type type, int uppercase) {
switch (type) {
case Argon2_d:
return uppercase ? "Argon2d" : "argon2d";
case Argon2_i:
return uppercase ? "Argon2i" : "argon2i";
case Argon2_id:
return uppercase ? "Argon2id" : "argon2id";
}
return NULL;
}
int argon2_ctx(argon2_context *context, argon2_type type) {
/* 1. Validate all inputs */
int result = validate_inputs(context);
uint32_t memory_blocks, segment_length;
argon2_instance_t instance;
if (ARGON2_OK != result) {
return result;
}
if (Argon2_d != type && Argon2_i != type && Argon2_id != type) {
return ARGON2_INCORRECT_TYPE;
}
/* 2. Align memory size */
/* Minimum memory_blocks = 8L blocks, where L is the number of lanes */
memory_blocks = context->m_cost;
if (memory_blocks < 2 * ARGON2_SYNC_POINTS * context->lanes) {
memory_blocks = 2 * ARGON2_SYNC_POINTS * context->lanes;
}
segment_length = memory_blocks / (context->lanes * ARGON2_SYNC_POINTS);
/* Ensure that all segments have equal length */
memory_blocks = segment_length * (context->lanes * ARGON2_SYNC_POINTS);
instance.version = context->version;
instance.memory = NULL;
instance.passes = context->t_cost;
instance.memory_blocks = memory_blocks;
instance.segment_length = segment_length;
instance.lane_length = segment_length * ARGON2_SYNC_POINTS;
instance.lanes = context->lanes;
instance.threads = context->threads;
instance.type = type;
if (instance.threads > instance.lanes) {
instance.threads = instance.lanes;
}
/* 3. Initialization: Hashing inputs, allocating memory, filling first
* blocks
*/
result = initialize(&instance, context);
if (ARGON2_OK != result) {
return result;
}
/* 4. Filling memory */
result = fill_memory_blocks(&instance);
if (ARGON2_OK != result) {
return result;
}
/* 5. Finalization */
finalize(context, &instance);
return ARGON2_OK;
}
int argon2_hash(const uint32_t t_cost, const uint32_t m_cost,
const uint32_t parallelism, const void *pwd,
const size_t pwdlen, const void *salt, const size_t saltlen,
void *hash, const size_t hashlen, char *encoded,
const size_t encodedlen, argon2_type type,
const uint32_t version){
argon2_context context;
int result;
uint8_t *out;
if (pwdlen > ARGON2_MAX_PWD_LENGTH) {
return ARGON2_PWD_TOO_LONG;
}
if (saltlen > ARGON2_MAX_SALT_LENGTH) {
return ARGON2_SALT_TOO_LONG;
}
if (hashlen > ARGON2_MAX_OUTLEN) {
return ARGON2_OUTPUT_TOO_LONG;
}
if (hashlen < ARGON2_MIN_OUTLEN) {
return ARGON2_OUTPUT_TOO_SHORT;
}
out = malloc(hashlen);
if (!out) {
return ARGON2_MEMORY_ALLOCATION_ERROR;
}
context.out = (uint8_t *)out;
context.outlen = (uint32_t)hashlen;
context.pwd = CONST_CAST(uint8_t *)pwd;
context.pwdlen = (uint32_t)pwdlen;
context.salt = CONST_CAST(uint8_t *)salt;
context.saltlen = (uint32_t)saltlen;
context.secret = NULL;
context.secretlen = 0;
context.ad = NULL;
context.adlen = 0;
context.t_cost = t_cost;
context.m_cost = m_cost;
context.lanes = parallelism;
context.threads = parallelism;
context.allocate_cbk = NULL;
context.free_cbk = NULL;
context.flags = ARGON2_DEFAULT_FLAGS;
context.version = version;
result = argon2_ctx(&context, type);
if (result != ARGON2_OK) {
clear_internal_memory(out, hashlen);
free(out);
return result;
}
/* if raw hash requested, write it */
if (hash) {
memcpy(hash, out, hashlen);
}
/* if encoding requested, write it */
if (encoded && encodedlen) {
if (encode_string(encoded, encodedlen, &context, type) != ARGON2_OK) {
clear_internal_memory(out, hashlen); /* wipe buffers if error */
clear_internal_memory(encoded, encodedlen);
free(out);
return ARGON2_ENCODING_FAIL;
}
}
clear_internal_memory(out, hashlen);
free(out);
return ARGON2_OK;
}
int argon2i_hash_encoded(const uint32_t t_cost, const uint32_t m_cost,
const uint32_t parallelism, const void *pwd,
const size_t pwdlen, const void *salt,
const size_t saltlen, const size_t hashlen,
char *encoded, const size_t encodedlen) {
return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
NULL, hashlen, encoded, encodedlen, Argon2_i,
ARGON2_VERSION_NUMBER);
}
int argon2i_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
const uint32_t parallelism, const void *pwd,
const size_t pwdlen, const void *salt,
const size_t saltlen, void *hash, const size_t hashlen) {
return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
hash, hashlen, NULL, 0, Argon2_i, ARGON2_VERSION_NUMBER);
}
int argon2d_hash_encoded(const uint32_t t_cost, const uint32_t m_cost,
const uint32_t parallelism, const void *pwd,
const size_t pwdlen, const void *salt,
const size_t saltlen, const size_t hashlen,
char *encoded, const size_t encodedlen) {
return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
NULL, hashlen, encoded, encodedlen, Argon2_d,
ARGON2_VERSION_NUMBER);
}
int argon2d_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
const uint32_t parallelism, const void *pwd,
const size_t pwdlen, const void *salt,
const size_t saltlen, void *hash, const size_t hashlen) {
return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
hash, hashlen, NULL, 0, Argon2_d, ARGON2_VERSION_NUMBER);
}
int argon2id_hash_encoded(const uint32_t t_cost, const uint32_t m_cost,
const uint32_t parallelism, const void *pwd,
const size_t pwdlen, const void *salt,
const size_t saltlen, const size_t hashlen,
char *encoded, const size_t encodedlen) {
return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
NULL, hashlen, encoded, encodedlen, Argon2_id,
ARGON2_VERSION_NUMBER);
}
int argon2id_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
const uint32_t parallelism, const void *pwd,
const size_t pwdlen, const void *salt,
const size_t saltlen, void *hash, const size_t hashlen) {
return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
hash, hashlen, NULL, 0, Argon2_id,
ARGON2_VERSION_NUMBER);
}
static int argon2_compare(const uint8_t *b1, const uint8_t *b2, size_t len) {
size_t i;
uint8_t d = 0U;
for (i = 0U; i < len; i++) {
d |= b1[i] ^ b2[i];
}
return (int)((1 & ((d - 1) >> 8)) - 1);
}
int argon2_verify(const char *encoded, const void *pwd, const size_t pwdlen,
argon2_type type) {
argon2_context ctx;
uint8_t *desired_result = NULL;
int ret = ARGON2_OK;
size_t encoded_len;
uint32_t max_field_len;
if (pwdlen > ARGON2_MAX_PWD_LENGTH) {
return ARGON2_PWD_TOO_LONG;
}
if (encoded == NULL) {
return ARGON2_DECODING_FAIL;
}
encoded_len = strlen(encoded);
if (encoded_len > UINT32_MAX) {
return ARGON2_DECODING_FAIL;
}
/* No field can be longer than the encoded length */
max_field_len = (uint32_t)encoded_len;
ctx.saltlen = max_field_len;
ctx.outlen = max_field_len;
ctx.salt = malloc(ctx.saltlen);
ctx.out = malloc(ctx.outlen);
if (!ctx.salt || !ctx.out) {
ret = ARGON2_MEMORY_ALLOCATION_ERROR;
goto fail;
}
ctx.pwd = (uint8_t *)pwd;
ctx.pwdlen = (uint32_t)pwdlen;
ret = decode_string(&ctx, encoded, type);
if (ret != ARGON2_OK) {
goto fail;
}
/* Set aside the desired result, and get a new buffer. */
desired_result = ctx.out;
ctx.out = malloc(ctx.outlen);
if (!ctx.out) {
ret = ARGON2_MEMORY_ALLOCATION_ERROR;
goto fail;
}
ret = argon2_verify_ctx(&ctx, (char *)desired_result, type);
if (ret != ARGON2_OK) {
goto fail;
}
fail:
free(ctx.salt);
free(ctx.out);
free(desired_result);
return ret;
}
int argon2i_verify(const char *encoded, const void *pwd, const size_t pwdlen) {
return argon2_verify(encoded, pwd, pwdlen, Argon2_i);
}
int argon2d_verify(const char *encoded, const void *pwd, const size_t pwdlen) {
return argon2_verify(encoded, pwd, pwdlen, Argon2_d);
}
int argon2id_verify(const char *encoded, const void *pwd, const size_t pwdlen) {
return argon2_verify(encoded, pwd, pwdlen, Argon2_id);
}
int argon2d_ctx(argon2_context *context) {
return argon2_ctx(context, Argon2_d);
}
int argon2i_ctx(argon2_context *context) {
return argon2_ctx(context, Argon2_i);
}
int argon2id_ctx(argon2_context *context) {
return argon2_ctx(context, Argon2_id);
}
int argon2_verify_ctx(argon2_context *context, const char *hash,
argon2_type type) {
int ret = argon2_ctx(context, type);
if (ret != ARGON2_OK) {
return ret;
}
if (argon2_compare((uint8_t *)hash, context->out, context->outlen)) {
return ARGON2_VERIFY_MISMATCH;
}
return ARGON2_OK;
}
int argon2d_verify_ctx(argon2_context *context, const char *hash) {
return argon2_verify_ctx(context, hash, Argon2_d);
}
int argon2i_verify_ctx(argon2_context *context, const char *hash) {
return argon2_verify_ctx(context, hash, Argon2_i);
}
int argon2id_verify_ctx(argon2_context *context, const char *hash) {
return argon2_verify_ctx(context, hash, Argon2_id);
}
const char *argon2_error_message(int error_code) {
switch (error_code) {
case ARGON2_OK:
return "OK";
case ARGON2_OUTPUT_PTR_NULL:
return "Output pointer is NULL";
case ARGON2_OUTPUT_TOO_SHORT:
return "Output is too short";
case ARGON2_OUTPUT_TOO_LONG:
return "Output is too long";
case ARGON2_PWD_TOO_SHORT:
return "Password is too short";
case ARGON2_PWD_TOO_LONG:
return "Password is too long";
case ARGON2_SALT_TOO_SHORT:
return "Salt is too short";
case ARGON2_SALT_TOO_LONG:
return "Salt is too long";
case ARGON2_AD_TOO_SHORT:
return "Associated data is too short";
case ARGON2_AD_TOO_LONG:
return "Associated data is too long";
case ARGON2_SECRET_TOO_SHORT:
return "Secret is too short";
case ARGON2_SECRET_TOO_LONG:
return "Secret is too long";
case ARGON2_TIME_TOO_SMALL:
return "Time cost is too small";
case ARGON2_TIME_TOO_LARGE:
return "Time cost is too large";
case ARGON2_MEMORY_TOO_LITTLE:
return "Memory cost is too small";
case ARGON2_MEMORY_TOO_MUCH:
return "Memory cost is too large";
case ARGON2_LANES_TOO_FEW:
return "Too few lanes";
case ARGON2_LANES_TOO_MANY:
return "Too many lanes";
case ARGON2_PWD_PTR_MISMATCH:
return "Password pointer is NULL, but password length is not 0";
case ARGON2_SALT_PTR_MISMATCH:
return "Salt pointer is NULL, but salt length is not 0";
case ARGON2_SECRET_PTR_MISMATCH:
return "Secret pointer is NULL, but secret length is not 0";
case ARGON2_AD_PTR_MISMATCH:
return "Associated data pointer is NULL, but ad length is not 0";
case ARGON2_MEMORY_ALLOCATION_ERROR:
return "Memory allocation error";
case ARGON2_FREE_MEMORY_CBK_NULL:
return "The free memory callback is NULL";
case ARGON2_ALLOCATE_MEMORY_CBK_NULL:
return "The allocate memory callback is NULL";
case ARGON2_INCORRECT_PARAMETER:
return "Argon2_Context context is NULL";
case ARGON2_INCORRECT_TYPE:
return "There is no such version of Argon2";
case ARGON2_OUT_PTR_MISMATCH:
return "Output pointer mismatch";
case ARGON2_THREADS_TOO_FEW:
return "Not enough threads";
case ARGON2_THREADS_TOO_MANY:
return "Too many threads";
case ARGON2_MISSING_ARGS:
return "Missing arguments";
case ARGON2_ENCODING_FAIL:
return "Encoding failed";
case ARGON2_DECODING_FAIL:
return "Decoding failed";
case ARGON2_THREAD_FAIL:
return "Threading failure";
case ARGON2_DECODING_LENGTH_FAIL:
return "Some of encoded parameters are too long or too short";
case ARGON2_VERIFY_MISMATCH:
return "The password does not match the supplied hash";
default:
return "Unknown error code";
}
}
size_t argon2_encodedlen(uint32_t t_cost, uint32_t m_cost, uint32_t parallelism,
uint32_t saltlen, uint32_t hashlen, argon2_type type) {
return strlen("$$v=$m=,t=,p=$$") + strlen(argon2_type2string(type, 0)) +
numlen(t_cost) + numlen(m_cost) + numlen(parallelism) +
b64len(saltlen) + b64len(hashlen) + numlen(ARGON2_VERSION_NUMBER) + 1;
}
+156
View File
@@ -0,0 +1,156 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
#ifndef PORTABLE_BLAKE2_IMPL_H
#define PORTABLE_BLAKE2_IMPL_H
#include <stdint.h>
#include <string.h>
#ifdef _WIN32
#define BLAKE2_INLINE __inline
#elif defined(__GNUC__) || defined(__clang__)
#define BLAKE2_INLINE __inline__
#else
#define BLAKE2_INLINE
#endif
/* Argon2 Team - Begin Code */
/*
Not an exhaustive list, but should cover the majority of modern platforms
Additionally, the code will always be correct---this is only a performance
tweak.
*/
#if (defined(__BYTE_ORDER__) && \
(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) || \
defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || defined(__MIPSEL__) || \
defined(__AARCH64EL__) || defined(__amd64__) || defined(__i386__) || \
defined(_M_IX86) || defined(_M_X64) || defined(_M_AMD64) || \
defined(_M_ARM)
#define NATIVE_LITTLE_ENDIAN
#endif
/* Argon2 Team - End Code */
static BLAKE2_INLINE uint32_t load32(const void *src) {
#if defined(NATIVE_LITTLE_ENDIAN)
uint32_t w;
memcpy(&w, src, sizeof w);
return w;
#else
const uint8_t *p = (const uint8_t *)src;
uint32_t w = *p++;
w |= (uint32_t)(*p++) << 8;
w |= (uint32_t)(*p++) << 16;
w |= (uint32_t)(*p++) << 24;
return w;
#endif
}
static BLAKE2_INLINE uint64_t load64(const void *src) {
#if defined(NATIVE_LITTLE_ENDIAN)
uint64_t w;
memcpy(&w, src, sizeof w);
return w;
#else
const uint8_t *p = (const uint8_t *)src;
uint64_t w = *p++;
w |= (uint64_t)(*p++) << 8;
w |= (uint64_t)(*p++) << 16;
w |= (uint64_t)(*p++) << 24;
w |= (uint64_t)(*p++) << 32;
w |= (uint64_t)(*p++) << 40;
w |= (uint64_t)(*p++) << 48;
w |= (uint64_t)(*p++) << 56;
return w;
#endif
}
static BLAKE2_INLINE void store32(void *dst, uint32_t w) {
#if defined(NATIVE_LITTLE_ENDIAN)
memcpy(dst, &w, sizeof w);
#else
uint8_t *p = (uint8_t *)dst;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
#endif
}
static BLAKE2_INLINE void store64(void *dst, uint64_t w) {
#if defined(NATIVE_LITTLE_ENDIAN)
memcpy(dst, &w, sizeof w);
#else
uint8_t *p = (uint8_t *)dst;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
#endif
}
static BLAKE2_INLINE uint64_t load48(const void *src) {
const uint8_t *p = (const uint8_t *)src;
uint64_t w = *p++;
w |= (uint64_t)(*p++) << 8;
w |= (uint64_t)(*p++) << 16;
w |= (uint64_t)(*p++) << 24;
w |= (uint64_t)(*p++) << 32;
w |= (uint64_t)(*p++) << 40;
return w;
}
static BLAKE2_INLINE void store48(void *dst, uint64_t w) {
uint8_t *p = (uint8_t *)dst;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
w >>= 8;
*p++ = (uint8_t)w;
}
static BLAKE2_INLINE uint32_t rotr32(const uint32_t w, const unsigned c) {
return (w >> c) | (w << (32 - c));
}
static BLAKE2_INLINE uint64_t rotr64(const uint64_t w, const unsigned c) {
return (w >> c) | (w << (64 - c));
}
void clear_internal_memory(void *v, size_t n);
#endif
+89
View File
@@ -0,0 +1,89 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
#ifndef PORTABLE_BLAKE2_H
#define PORTABLE_BLAKE2_H
#include <argon2.h>
#if defined(__cplusplus)
extern "C" {
#endif
enum blake2b_constant {
BLAKE2B_BLOCKBYTES = 128,
BLAKE2B_OUTBYTES = 64,
BLAKE2B_KEYBYTES = 64,
BLAKE2B_SALTBYTES = 16,
BLAKE2B_PERSONALBYTES = 16
};
#pragma pack(push, 1)
typedef struct __blake2b_param {
uint8_t digest_length; /* 1 */
uint8_t key_length; /* 2 */
uint8_t fanout; /* 3 */
uint8_t depth; /* 4 */
uint32_t leaf_length; /* 8 */
uint64_t node_offset; /* 16 */
uint8_t node_depth; /* 17 */
uint8_t inner_length; /* 18 */
uint8_t reserved[14]; /* 32 */
uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */
uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */
} blake2b_param;
#pragma pack(pop)
typedef struct __blake2b_state {
uint64_t h[8];
uint64_t t[2];
uint64_t f[2];
uint8_t buf[BLAKE2B_BLOCKBYTES];
unsigned buflen;
unsigned outlen;
uint8_t last_node;
} blake2b_state;
/* Ensure param structs have not been wrongly padded */
/* Poor man's static_assert */
enum {
blake2_size_check_0 = 1 / !!(CHAR_BIT == 8),
blake2_size_check_2 =
1 / !!(sizeof(blake2b_param) == sizeof(uint64_t) * CHAR_BIT)
};
/* Streaming API */
ARGON2_LOCAL int blake2b_init(blake2b_state *S, size_t outlen);
ARGON2_LOCAL int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key,
size_t keylen);
ARGON2_LOCAL int blake2b_init_param(blake2b_state *S, const blake2b_param *P);
ARGON2_LOCAL int blake2b_update(blake2b_state *S, const void *in, size_t inlen);
ARGON2_LOCAL int blake2b_final(blake2b_state *S, void *out, size_t outlen);
/* Simple API */
ARGON2_LOCAL int blake2b(void *out, size_t outlen, const void *in, size_t inlen,
const void *key, size_t keylen);
/* Argon2 Team - Begin Code */
ARGON2_LOCAL int blake2b_long(void *out, size_t outlen, const void *in, size_t inlen);
/* Argon2 Team - End Code */
#if defined(__cplusplus)
}
#endif
#endif
+390
View File
@@ -0,0 +1,390 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include "blake2.h"
#include "blake2-impl.h"
static const uint64_t blake2b_IV[8] = {
UINT64_C(0x6a09e667f3bcc908), UINT64_C(0xbb67ae8584caa73b),
UINT64_C(0x3c6ef372fe94f82b), UINT64_C(0xa54ff53a5f1d36f1),
UINT64_C(0x510e527fade682d1), UINT64_C(0x9b05688c2b3e6c1f),
UINT64_C(0x1f83d9abfb41bd6b), UINT64_C(0x5be0cd19137e2179)};
static const unsigned int blake2b_sigma[12][16] = {
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
{14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
{11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4},
{7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8},
{9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13},
{2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9},
{12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11},
{13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10},
{6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5},
{10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0},
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
{14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
};
static BLAKE2_INLINE void blake2b_set_lastnode(blake2b_state *S) {
S->f[1] = (uint64_t)-1;
}
static BLAKE2_INLINE void blake2b_set_lastblock(blake2b_state *S) {
if (S->last_node) {
blake2b_set_lastnode(S);
}
S->f[0] = (uint64_t)-1;
}
static BLAKE2_INLINE void blake2b_increment_counter(blake2b_state *S,
uint64_t inc) {
S->t[0] += inc;
S->t[1] += (S->t[0] < inc);
}
static BLAKE2_INLINE void blake2b_invalidate_state(blake2b_state *S) {
clear_internal_memory(S, sizeof(*S)); /* wipe */
blake2b_set_lastblock(S); /* invalidate for further use */
}
static BLAKE2_INLINE void blake2b_init0(blake2b_state *S) {
memset(S, 0, sizeof(*S));
memcpy(S->h, blake2b_IV, sizeof(S->h));
}
int blake2b_init_param(blake2b_state *S, const blake2b_param *P) {
const unsigned char *p = (const unsigned char *)P;
unsigned int i;
if (NULL == P || NULL == S) {
return -1;
}
blake2b_init0(S);
/* IV XOR Parameter Block */
for (i = 0; i < 8; ++i) {
S->h[i] ^= load64(&p[i * sizeof(S->h[i])]);
}
S->outlen = P->digest_length;
return 0;
}
/* Sequential blake2b initialization */
int blake2b_init(blake2b_state *S, size_t outlen) {
blake2b_param P;
if (S == NULL) {
return -1;
}
if ((outlen == 0) || (outlen > BLAKE2B_OUTBYTES)) {
blake2b_invalidate_state(S);
return -1;
}
/* Setup Parameter Block for unkeyed BLAKE2 */
P.digest_length = (uint8_t)outlen;
P.key_length = 0;
P.fanout = 1;
P.depth = 1;
P.leaf_length = 0;
P.node_offset = 0;
P.node_depth = 0;
P.inner_length = 0;
memset(P.reserved, 0, sizeof(P.reserved));
memset(P.salt, 0, sizeof(P.salt));
memset(P.personal, 0, sizeof(P.personal));
return blake2b_init_param(S, &P);
}
int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key,
size_t keylen) {
blake2b_param P;
if (S == NULL) {
return -1;
}
if ((outlen == 0) || (outlen > BLAKE2B_OUTBYTES)) {
blake2b_invalidate_state(S);
return -1;
}
if ((key == 0) || (keylen == 0) || (keylen > BLAKE2B_KEYBYTES)) {
blake2b_invalidate_state(S);
return -1;
}
/* Setup Parameter Block for keyed BLAKE2 */
P.digest_length = (uint8_t)outlen;
P.key_length = (uint8_t)keylen;
P.fanout = 1;
P.depth = 1;
P.leaf_length = 0;
P.node_offset = 0;
P.node_depth = 0;
P.inner_length = 0;
memset(P.reserved, 0, sizeof(P.reserved));
memset(P.salt, 0, sizeof(P.salt));
memset(P.personal, 0, sizeof(P.personal));
if (blake2b_init_param(S, &P) < 0) {
blake2b_invalidate_state(S);
return -1;
}
{
uint8_t block[BLAKE2B_BLOCKBYTES];
memset(block, 0, BLAKE2B_BLOCKBYTES);
memcpy(block, key, keylen);
blake2b_update(S, block, BLAKE2B_BLOCKBYTES);
/* Burn the key from stack */
clear_internal_memory(block, BLAKE2B_BLOCKBYTES);
}
return 0;
}
static void blake2b_compress(blake2b_state *S, const uint8_t *block) {
uint64_t m[16];
uint64_t v[16];
unsigned int i, r;
for (i = 0; i < 16; ++i) {
m[i] = load64(block + i * sizeof(m[i]));
}
for (i = 0; i < 8; ++i) {
v[i] = S->h[i];
}
v[8] = blake2b_IV[0];
v[9] = blake2b_IV[1];
v[10] = blake2b_IV[2];
v[11] = blake2b_IV[3];
v[12] = blake2b_IV[4] ^ S->t[0];
v[13] = blake2b_IV[5] ^ S->t[1];
v[14] = blake2b_IV[6] ^ S->f[0];
v[15] = blake2b_IV[7] ^ S->f[1];
#define G(r, i, a, b, c, d) \
do { \
a = a + b + m[blake2b_sigma[r][2 * i + 0]]; \
d = rotr64(d ^ a, 32); \
c = c + d; \
b = rotr64(b ^ c, 24); \
a = a + b + m[blake2b_sigma[r][2 * i + 1]]; \
d = rotr64(d ^ a, 16); \
c = c + d; \
b = rotr64(b ^ c, 63); \
} while ((void)0, 0)
#define ROUND(r) \
do { \
G(r, 0, v[0], v[4], v[8], v[12]); \
G(r, 1, v[1], v[5], v[9], v[13]); \
G(r, 2, v[2], v[6], v[10], v[14]); \
G(r, 3, v[3], v[7], v[11], v[15]); \
G(r, 4, v[0], v[5], v[10], v[15]); \
G(r, 5, v[1], v[6], v[11], v[12]); \
G(r, 6, v[2], v[7], v[8], v[13]); \
G(r, 7, v[3], v[4], v[9], v[14]); \
} while ((void)0, 0)
for (r = 0; r < 12; ++r) {
ROUND(r);
}
for (i = 0; i < 8; ++i) {
S->h[i] = S->h[i] ^ v[i] ^ v[i + 8];
}
#undef G
#undef ROUND
}
int blake2b_update(blake2b_state *S, const void *in, size_t inlen) {
const uint8_t *pin = (const uint8_t *)in;
if (inlen == 0) {
return 0;
}
/* Sanity check */
if (S == NULL || in == NULL) {
return -1;
}
/* Is this a reused state? */
if (S->f[0] != 0) {
return -1;
}
if (S->buflen + inlen > BLAKE2B_BLOCKBYTES) {
/* Complete current block */
size_t left = S->buflen;
size_t fill = BLAKE2B_BLOCKBYTES - left;
memcpy(&S->buf[left], pin, fill);
blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES);
blake2b_compress(S, S->buf);
S->buflen = 0;
inlen -= fill;
pin += fill;
/* Avoid buffer copies when possible */
while (inlen > BLAKE2B_BLOCKBYTES) {
blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES);
blake2b_compress(S, pin);
inlen -= BLAKE2B_BLOCKBYTES;
pin += BLAKE2B_BLOCKBYTES;
}
}
memcpy(&S->buf[S->buflen], pin, inlen);
S->buflen += (unsigned int)inlen;
return 0;
}
int blake2b_final(blake2b_state *S, void *out, size_t outlen) {
uint8_t buffer[BLAKE2B_OUTBYTES] = {0};
unsigned int i;
/* Sanity checks */
if (S == NULL || out == NULL || outlen < S->outlen) {
return -1;
}
/* Is this a reused state? */
if (S->f[0] != 0) {
return -1;
}
blake2b_increment_counter(S, S->buflen);
blake2b_set_lastblock(S);
memset(&S->buf[S->buflen], 0, BLAKE2B_BLOCKBYTES - S->buflen); /* Padding */
blake2b_compress(S, S->buf);
for (i = 0; i < 8; ++i) { /* Output full hash to temp buffer */
store64(buffer + sizeof(S->h[i]) * i, S->h[i]);
}
memcpy(out, buffer, S->outlen);
clear_internal_memory(buffer, sizeof(buffer));
clear_internal_memory(S->buf, sizeof(S->buf));
clear_internal_memory(S->h, sizeof(S->h));
return 0;
}
int blake2b(void *out, size_t outlen, const void *in, size_t inlen,
const void *key, size_t keylen) {
blake2b_state S;
int ret = -1;
/* Verify parameters */
if (NULL == in && inlen > 0) {
goto fail;
}
if (NULL == out || outlen == 0 || outlen > BLAKE2B_OUTBYTES) {
goto fail;
}
if ((NULL == key && keylen > 0) || keylen > BLAKE2B_KEYBYTES) {
goto fail;
}
if (keylen > 0) {
if (blake2b_init_key(&S, outlen, key, keylen) < 0) {
goto fail;
}
} else {
if (blake2b_init(&S, outlen) < 0) {
goto fail;
}
}
if (blake2b_update(&S, in, inlen) < 0) {
goto fail;
}
ret = blake2b_final(&S, out, outlen);
fail:
clear_internal_memory(&S, sizeof(S));
return ret;
}
/* Argon2 Team - Begin Code */
int blake2b_long(void *pout, size_t outlen, const void *in, size_t inlen) {
uint8_t *out = (uint8_t *)pout;
blake2b_state blake_state;
uint8_t outlen_bytes[sizeof(uint32_t)] = {0};
int ret = -1;
if (outlen > UINT32_MAX) {
goto fail;
}
/* Ensure little-endian byte order! */
store32(outlen_bytes, (uint32_t)outlen);
#define TRY(statement) \
do { \
ret = statement; \
if (ret < 0) { \
goto fail; \
} \
} while ((void)0, 0)
if (outlen <= BLAKE2B_OUTBYTES) {
TRY(blake2b_init(&blake_state, outlen));
TRY(blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes)));
TRY(blake2b_update(&blake_state, in, inlen));
TRY(blake2b_final(&blake_state, out, outlen));
} else {
uint32_t toproduce;
uint8_t out_buffer[BLAKE2B_OUTBYTES];
uint8_t in_buffer[BLAKE2B_OUTBYTES];
TRY(blake2b_init(&blake_state, BLAKE2B_OUTBYTES));
TRY(blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes)));
TRY(blake2b_update(&blake_state, in, inlen));
TRY(blake2b_final(&blake_state, out_buffer, BLAKE2B_OUTBYTES));
memcpy(out, out_buffer, BLAKE2B_OUTBYTES / 2);
out += BLAKE2B_OUTBYTES / 2;
toproduce = (uint32_t)outlen - BLAKE2B_OUTBYTES / 2;
while (toproduce > BLAKE2B_OUTBYTES) {
memcpy(in_buffer, out_buffer, BLAKE2B_OUTBYTES);
TRY(blake2b(out_buffer, BLAKE2B_OUTBYTES, in_buffer,
BLAKE2B_OUTBYTES, NULL, 0));
memcpy(out, out_buffer, BLAKE2B_OUTBYTES / 2);
out += BLAKE2B_OUTBYTES / 2;
toproduce -= BLAKE2B_OUTBYTES / 2;
}
memcpy(in_buffer, out_buffer, BLAKE2B_OUTBYTES);
TRY(blake2b(out_buffer, toproduce, in_buffer, BLAKE2B_OUTBYTES, NULL,
0));
memcpy(out, out_buffer, toproduce);
}
fail:
clear_internal_memory(&blake_state, sizeof(blake_state));
return ret;
#undef TRY
}
/* Argon2 Team - End Code */
@@ -0,0 +1,471 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
#ifndef BLAKE_ROUND_MKA_OPT_H
#define BLAKE_ROUND_MKA_OPT_H
#include "blake2-impl.h"
#include <emmintrin.h>
#if defined(__SSSE3__)
#include <tmmintrin.h> /* for _mm_shuffle_epi8 and _mm_alignr_epi8 */
#endif
#if defined(__XOP__) && (defined(__GNUC__) || defined(__clang__))
#include <x86intrin.h>
#endif
#if !defined(__AVX512F__)
#if !defined(__AVX2__)
#if !defined(__XOP__)
#if defined(__SSSE3__)
#define r16 \
(_mm_setr_epi8(2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9))
#define r24 \
(_mm_setr_epi8(3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10))
#define _mm_roti_epi64(x, c) \
(-(c) == 32) \
? _mm_shuffle_epi32((x), _MM_SHUFFLE(2, 3, 0, 1)) \
: (-(c) == 24) \
? _mm_shuffle_epi8((x), r24) \
: (-(c) == 16) \
? _mm_shuffle_epi8((x), r16) \
: (-(c) == 63) \
? _mm_xor_si128(_mm_srli_epi64((x), -(c)), \
_mm_add_epi64((x), (x))) \
: _mm_xor_si128(_mm_srli_epi64((x), -(c)), \
_mm_slli_epi64((x), 64 - (-(c))))
#else /* defined(__SSE2__) */
#define _mm_roti_epi64(r, c) \
_mm_xor_si128(_mm_srli_epi64((r), -(c)), _mm_slli_epi64((r), 64 - (-(c))))
#endif
#else
#endif
static BLAKE2_INLINE __m128i fBlaMka(__m128i x, __m128i y) {
const __m128i z = _mm_mul_epu32(x, y);
return _mm_add_epi64(_mm_add_epi64(x, y), _mm_add_epi64(z, z));
}
#define G1(A0, B0, C0, D0, A1, B1, C1, D1) \
do { \
A0 = fBlaMka(A0, B0); \
A1 = fBlaMka(A1, B1); \
\
D0 = _mm_xor_si128(D0, A0); \
D1 = _mm_xor_si128(D1, A1); \
\
D0 = _mm_roti_epi64(D0, -32); \
D1 = _mm_roti_epi64(D1, -32); \
\
C0 = fBlaMka(C0, D0); \
C1 = fBlaMka(C1, D1); \
\
B0 = _mm_xor_si128(B0, C0); \
B1 = _mm_xor_si128(B1, C1); \
\
B0 = _mm_roti_epi64(B0, -24); \
B1 = _mm_roti_epi64(B1, -24); \
} while ((void)0, 0)
#define G2(A0, B0, C0, D0, A1, B1, C1, D1) \
do { \
A0 = fBlaMka(A0, B0); \
A1 = fBlaMka(A1, B1); \
\
D0 = _mm_xor_si128(D0, A0); \
D1 = _mm_xor_si128(D1, A1); \
\
D0 = _mm_roti_epi64(D0, -16); \
D1 = _mm_roti_epi64(D1, -16); \
\
C0 = fBlaMka(C0, D0); \
C1 = fBlaMka(C1, D1); \
\
B0 = _mm_xor_si128(B0, C0); \
B1 = _mm_xor_si128(B1, C1); \
\
B0 = _mm_roti_epi64(B0, -63); \
B1 = _mm_roti_epi64(B1, -63); \
} while ((void)0, 0)
#if defined(__SSSE3__)
#define DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
do { \
__m128i t0 = _mm_alignr_epi8(B1, B0, 8); \
__m128i t1 = _mm_alignr_epi8(B0, B1, 8); \
B0 = t0; \
B1 = t1; \
\
t0 = C0; \
C0 = C1; \
C1 = t0; \
\
t0 = _mm_alignr_epi8(D1, D0, 8); \
t1 = _mm_alignr_epi8(D0, D1, 8); \
D0 = t1; \
D1 = t0; \
} while ((void)0, 0)
#define UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
do { \
__m128i t0 = _mm_alignr_epi8(B0, B1, 8); \
__m128i t1 = _mm_alignr_epi8(B1, B0, 8); \
B0 = t0; \
B1 = t1; \
\
t0 = C0; \
C0 = C1; \
C1 = t0; \
\
t0 = _mm_alignr_epi8(D0, D1, 8); \
t1 = _mm_alignr_epi8(D1, D0, 8); \
D0 = t1; \
D1 = t0; \
} while ((void)0, 0)
#else /* SSE2 */
#define DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
do { \
__m128i t0 = D0; \
__m128i t1 = B0; \
D0 = C0; \
C0 = C1; \
C1 = D0; \
D0 = _mm_unpackhi_epi64(D1, _mm_unpacklo_epi64(t0, t0)); \
D1 = _mm_unpackhi_epi64(t0, _mm_unpacklo_epi64(D1, D1)); \
B0 = _mm_unpackhi_epi64(B0, _mm_unpacklo_epi64(B1, B1)); \
B1 = _mm_unpackhi_epi64(B1, _mm_unpacklo_epi64(t1, t1)); \
} while ((void)0, 0)
#define UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
do { \
__m128i t0, t1; \
t0 = C0; \
C0 = C1; \
C1 = t0; \
t0 = B0; \
t1 = D0; \
B0 = _mm_unpackhi_epi64(B1, _mm_unpacklo_epi64(B0, B0)); \
B1 = _mm_unpackhi_epi64(t0, _mm_unpacklo_epi64(B1, B1)); \
D0 = _mm_unpackhi_epi64(D0, _mm_unpacklo_epi64(D1, D1)); \
D1 = _mm_unpackhi_epi64(D1, _mm_unpacklo_epi64(t1, t1)); \
} while ((void)0, 0)
#endif
#define BLAKE2_ROUND(A0, A1, B0, B1, C0, C1, D0, D1) \
do { \
G1(A0, B0, C0, D0, A1, B1, C1, D1); \
G2(A0, B0, C0, D0, A1, B1, C1, D1); \
\
DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1); \
\
G1(A0, B0, C0, D0, A1, B1, C1, D1); \
G2(A0, B0, C0, D0, A1, B1, C1, D1); \
\
UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1); \
} while ((void)0, 0)
#else /* __AVX2__ */
#include <immintrin.h>
#define rotr32(x) _mm256_shuffle_epi32(x, _MM_SHUFFLE(2, 3, 0, 1))
#define rotr24(x) _mm256_shuffle_epi8(x, _mm256_setr_epi8(3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10, 3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10))
#define rotr16(x) _mm256_shuffle_epi8(x, _mm256_setr_epi8(2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9, 2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9))
#define rotr63(x) _mm256_xor_si256(_mm256_srli_epi64((x), 63), _mm256_add_epi64((x), (x)))
#define G1_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
do { \
__m256i ml = _mm256_mul_epu32(A0, B0); \
ml = _mm256_add_epi64(ml, ml); \
A0 = _mm256_add_epi64(A0, _mm256_add_epi64(B0, ml)); \
D0 = _mm256_xor_si256(D0, A0); \
D0 = rotr32(D0); \
\
ml = _mm256_mul_epu32(C0, D0); \
ml = _mm256_add_epi64(ml, ml); \
C0 = _mm256_add_epi64(C0, _mm256_add_epi64(D0, ml)); \
\
B0 = _mm256_xor_si256(B0, C0); \
B0 = rotr24(B0); \
\
ml = _mm256_mul_epu32(A1, B1); \
ml = _mm256_add_epi64(ml, ml); \
A1 = _mm256_add_epi64(A1, _mm256_add_epi64(B1, ml)); \
D1 = _mm256_xor_si256(D1, A1); \
D1 = rotr32(D1); \
\
ml = _mm256_mul_epu32(C1, D1); \
ml = _mm256_add_epi64(ml, ml); \
C1 = _mm256_add_epi64(C1, _mm256_add_epi64(D1, ml)); \
\
B1 = _mm256_xor_si256(B1, C1); \
B1 = rotr24(B1); \
} while((void)0, 0);
#define G2_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
do { \
__m256i ml = _mm256_mul_epu32(A0, B0); \
ml = _mm256_add_epi64(ml, ml); \
A0 = _mm256_add_epi64(A0, _mm256_add_epi64(B0, ml)); \
D0 = _mm256_xor_si256(D0, A0); \
D0 = rotr16(D0); \
\
ml = _mm256_mul_epu32(C0, D0); \
ml = _mm256_add_epi64(ml, ml); \
C0 = _mm256_add_epi64(C0, _mm256_add_epi64(D0, ml)); \
B0 = _mm256_xor_si256(B0, C0); \
B0 = rotr63(B0); \
\
ml = _mm256_mul_epu32(A1, B1); \
ml = _mm256_add_epi64(ml, ml); \
A1 = _mm256_add_epi64(A1, _mm256_add_epi64(B1, ml)); \
D1 = _mm256_xor_si256(D1, A1); \
D1 = rotr16(D1); \
\
ml = _mm256_mul_epu32(C1, D1); \
ml = _mm256_add_epi64(ml, ml); \
C1 = _mm256_add_epi64(C1, _mm256_add_epi64(D1, ml)); \
B1 = _mm256_xor_si256(B1, C1); \
B1 = rotr63(B1); \
} while((void)0, 0);
#define DIAGONALIZE_1(A0, B0, C0, D0, A1, B1, C1, D1) \
do { \
B0 = _mm256_permute4x64_epi64(B0, _MM_SHUFFLE(0, 3, 2, 1)); \
C0 = _mm256_permute4x64_epi64(C0, _MM_SHUFFLE(1, 0, 3, 2)); \
D0 = _mm256_permute4x64_epi64(D0, _MM_SHUFFLE(2, 1, 0, 3)); \
\
B1 = _mm256_permute4x64_epi64(B1, _MM_SHUFFLE(0, 3, 2, 1)); \
C1 = _mm256_permute4x64_epi64(C1, _MM_SHUFFLE(1, 0, 3, 2)); \
D1 = _mm256_permute4x64_epi64(D1, _MM_SHUFFLE(2, 1, 0, 3)); \
} while((void)0, 0);
#define DIAGONALIZE_2(A0, A1, B0, B1, C0, C1, D0, D1) \
do { \
__m256i tmp1 = _mm256_blend_epi32(B0, B1, 0xCC); \
__m256i tmp2 = _mm256_blend_epi32(B0, B1, 0x33); \
B1 = _mm256_permute4x64_epi64(tmp1, _MM_SHUFFLE(2,3,0,1)); \
B0 = _mm256_permute4x64_epi64(tmp2, _MM_SHUFFLE(2,3,0,1)); \
\
tmp1 = C0; \
C0 = C1; \
C1 = tmp1; \
\
tmp1 = _mm256_blend_epi32(D0, D1, 0xCC); \
tmp2 = _mm256_blend_epi32(D0, D1, 0x33); \
D0 = _mm256_permute4x64_epi64(tmp1, _MM_SHUFFLE(2,3,0,1)); \
D1 = _mm256_permute4x64_epi64(tmp2, _MM_SHUFFLE(2,3,0,1)); \
} while(0);
#define UNDIAGONALIZE_1(A0, B0, C0, D0, A1, B1, C1, D1) \
do { \
B0 = _mm256_permute4x64_epi64(B0, _MM_SHUFFLE(2, 1, 0, 3)); \
C0 = _mm256_permute4x64_epi64(C0, _MM_SHUFFLE(1, 0, 3, 2)); \
D0 = _mm256_permute4x64_epi64(D0, _MM_SHUFFLE(0, 3, 2, 1)); \
\
B1 = _mm256_permute4x64_epi64(B1, _MM_SHUFFLE(2, 1, 0, 3)); \
C1 = _mm256_permute4x64_epi64(C1, _MM_SHUFFLE(1, 0, 3, 2)); \
D1 = _mm256_permute4x64_epi64(D1, _MM_SHUFFLE(0, 3, 2, 1)); \
} while((void)0, 0);
#define UNDIAGONALIZE_2(A0, A1, B0, B1, C0, C1, D0, D1) \
do { \
__m256i tmp1 = _mm256_blend_epi32(B0, B1, 0xCC); \
__m256i tmp2 = _mm256_blend_epi32(B0, B1, 0x33); \
B0 = _mm256_permute4x64_epi64(tmp1, _MM_SHUFFLE(2,3,0,1)); \
B1 = _mm256_permute4x64_epi64(tmp2, _MM_SHUFFLE(2,3,0,1)); \
\
tmp1 = C0; \
C0 = C1; \
C1 = tmp1; \
\
tmp1 = _mm256_blend_epi32(D0, D1, 0x33); \
tmp2 = _mm256_blend_epi32(D0, D1, 0xCC); \
D0 = _mm256_permute4x64_epi64(tmp1, _MM_SHUFFLE(2,3,0,1)); \
D1 = _mm256_permute4x64_epi64(tmp2, _MM_SHUFFLE(2,3,0,1)); \
} while((void)0, 0);
#define BLAKE2_ROUND_1(A0, A1, B0, B1, C0, C1, D0, D1) \
do{ \
G1_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
G2_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
\
DIAGONALIZE_1(A0, B0, C0, D0, A1, B1, C1, D1) \
\
G1_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
G2_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
\
UNDIAGONALIZE_1(A0, B0, C0, D0, A1, B1, C1, D1) \
} while((void)0, 0);
#define BLAKE2_ROUND_2(A0, A1, B0, B1, C0, C1, D0, D1) \
do{ \
G1_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
G2_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
\
DIAGONALIZE_2(A0, A1, B0, B1, C0, C1, D0, D1) \
\
G1_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
G2_AVX2(A0, A1, B0, B1, C0, C1, D0, D1) \
\
UNDIAGONALIZE_2(A0, A1, B0, B1, C0, C1, D0, D1) \
} while((void)0, 0);
#endif /* __AVX2__ */
#else /* __AVX512F__ */
#include <immintrin.h>
#define ror64(x, n) _mm512_ror_epi64((x), (n))
static __m512i muladd(__m512i x, __m512i y)
{
__m512i z = _mm512_mul_epu32(x, y);
return _mm512_add_epi64(_mm512_add_epi64(x, y), _mm512_add_epi64(z, z));
}
#define G1(A0, B0, C0, D0, A1, B1, C1, D1) \
do { \
A0 = muladd(A0, B0); \
A1 = muladd(A1, B1); \
\
D0 = _mm512_xor_si512(D0, A0); \
D1 = _mm512_xor_si512(D1, A1); \
\
D0 = ror64(D0, 32); \
D1 = ror64(D1, 32); \
\
C0 = muladd(C0, D0); \
C1 = muladd(C1, D1); \
\
B0 = _mm512_xor_si512(B0, C0); \
B1 = _mm512_xor_si512(B1, C1); \
\
B0 = ror64(B0, 24); \
B1 = ror64(B1, 24); \
} while ((void)0, 0)
#define G2(A0, B0, C0, D0, A1, B1, C1, D1) \
do { \
A0 = muladd(A0, B0); \
A1 = muladd(A1, B1); \
\
D0 = _mm512_xor_si512(D0, A0); \
D1 = _mm512_xor_si512(D1, A1); \
\
D0 = ror64(D0, 16); \
D1 = ror64(D1, 16); \
\
C0 = muladd(C0, D0); \
C1 = muladd(C1, D1); \
\
B0 = _mm512_xor_si512(B0, C0); \
B1 = _mm512_xor_si512(B1, C1); \
\
B0 = ror64(B0, 63); \
B1 = ror64(B1, 63); \
} while ((void)0, 0)
#define DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
do { \
B0 = _mm512_permutex_epi64(B0, _MM_SHUFFLE(0, 3, 2, 1)); \
B1 = _mm512_permutex_epi64(B1, _MM_SHUFFLE(0, 3, 2, 1)); \
\
C0 = _mm512_permutex_epi64(C0, _MM_SHUFFLE(1, 0, 3, 2)); \
C1 = _mm512_permutex_epi64(C1, _MM_SHUFFLE(1, 0, 3, 2)); \
\
D0 = _mm512_permutex_epi64(D0, _MM_SHUFFLE(2, 1, 0, 3)); \
D1 = _mm512_permutex_epi64(D1, _MM_SHUFFLE(2, 1, 0, 3)); \
} while ((void)0, 0)
#define UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1) \
do { \
B0 = _mm512_permutex_epi64(B0, _MM_SHUFFLE(2, 1, 0, 3)); \
B1 = _mm512_permutex_epi64(B1, _MM_SHUFFLE(2, 1, 0, 3)); \
\
C0 = _mm512_permutex_epi64(C0, _MM_SHUFFLE(1, 0, 3, 2)); \
C1 = _mm512_permutex_epi64(C1, _MM_SHUFFLE(1, 0, 3, 2)); \
\
D0 = _mm512_permutex_epi64(D0, _MM_SHUFFLE(0, 3, 2, 1)); \
D1 = _mm512_permutex_epi64(D1, _MM_SHUFFLE(0, 3, 2, 1)); \
} while ((void)0, 0)
#define BLAKE2_ROUND(A0, B0, C0, D0, A1, B1, C1, D1) \
do { \
G1(A0, B0, C0, D0, A1, B1, C1, D1); \
G2(A0, B0, C0, D0, A1, B1, C1, D1); \
\
DIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1); \
\
G1(A0, B0, C0, D0, A1, B1, C1, D1); \
G2(A0, B0, C0, D0, A1, B1, C1, D1); \
\
UNDIAGONALIZE(A0, B0, C0, D0, A1, B1, C1, D1); \
} while ((void)0, 0)
#define SWAP_HALVES(A0, A1) \
do { \
__m512i t0, t1; \
t0 = _mm512_shuffle_i64x2(A0, A1, _MM_SHUFFLE(1, 0, 1, 0)); \
t1 = _mm512_shuffle_i64x2(A0, A1, _MM_SHUFFLE(3, 2, 3, 2)); \
A0 = t0; \
A1 = t1; \
} while((void)0, 0)
#define SWAP_QUARTERS(A0, A1) \
do { \
SWAP_HALVES(A0, A1); \
A0 = _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 1, 4, 5, 2, 3, 6, 7), A0); \
A1 = _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 1, 4, 5, 2, 3, 6, 7), A1); \
} while((void)0, 0)
#define UNSWAP_QUARTERS(A0, A1) \
do { \
A0 = _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 1, 4, 5, 2, 3, 6, 7), A0); \
A1 = _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 1, 4, 5, 2, 3, 6, 7), A1); \
SWAP_HALVES(A0, A1); \
} while((void)0, 0)
#define BLAKE2_ROUND_1(A0, C0, B0, D0, A1, C1, B1, D1) \
do { \
SWAP_HALVES(A0, B0); \
SWAP_HALVES(C0, D0); \
SWAP_HALVES(A1, B1); \
SWAP_HALVES(C1, D1); \
BLAKE2_ROUND(A0, B0, C0, D0, A1, B1, C1, D1); \
SWAP_HALVES(A0, B0); \
SWAP_HALVES(C0, D0); \
SWAP_HALVES(A1, B1); \
SWAP_HALVES(C1, D1); \
} while ((void)0, 0)
#define BLAKE2_ROUND_2(A0, A1, B0, B1, C0, C1, D0, D1) \
do { \
SWAP_QUARTERS(A0, A1); \
SWAP_QUARTERS(B0, B1); \
SWAP_QUARTERS(C0, C1); \
SWAP_QUARTERS(D0, D1); \
BLAKE2_ROUND(A0, B0, C0, D0, A1, B1, C1, D1); \
UNSWAP_QUARTERS(A0, A1); \
UNSWAP_QUARTERS(B0, B1); \
UNSWAP_QUARTERS(C0, C1); \
UNSWAP_QUARTERS(D0, D1); \
} while ((void)0, 0)
#endif /* __AVX512F__ */
#endif /* BLAKE_ROUND_MKA_OPT_H */
@@ -0,0 +1,56 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
#ifndef BLAKE_ROUND_MKA_H
#define BLAKE_ROUND_MKA_H
#include "blake2.h"
#include "blake2-impl.h"
/* designed by the Lyra PHC team */
static BLAKE2_INLINE uint64_t fBlaMka(uint64_t x, uint64_t y) {
const uint64_t m = UINT64_C(0xFFFFFFFF);
const uint64_t xy = (x & m) * (y & m);
return x + y + 2 * xy;
}
#define G(a, b, c, d) \
do { \
a = fBlaMka(a, b); \
d = rotr64(d ^ a, 32); \
c = fBlaMka(c, d); \
b = rotr64(b ^ c, 24); \
a = fBlaMka(a, b); \
d = rotr64(d ^ a, 16); \
c = fBlaMka(c, d); \
b = rotr64(b ^ c, 63); \
} while ((void)0, 0)
#define BLAKE2_ROUND_NOMSG(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, \
v12, v13, v14, v15) \
do { \
G(v0, v4, v8, v12); \
G(v1, v5, v9, v13); \
G(v2, v6, v10, v14); \
G(v3, v7, v11, v15); \
G(v0, v5, v10, v15); \
G(v1, v6, v11, v12); \
G(v2, v7, v8, v13); \
G(v3, v4, v9, v14); \
} while ((void)0, 0)
#endif
+648
View File
@@ -0,0 +1,648 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
/*For memory wiping*/
#ifdef _WIN32
#include <windows.h>
#include <winbase.h> /* For SecureZeroMemory */
#endif
#if defined __STDC_LIB_EXT1__
#define __STDC_WANT_LIB_EXT1__ 1
#endif
#define VC_GE_2005(version) (version >= 1400)
/* for explicit_bzero() on glibc */
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "core.h"
#include "thread.h"
#include "blake2/blake2.h"
#include "blake2/blake2-impl.h"
#ifdef GENKAT
#include "genkat.h"
#endif
#if defined(__clang__)
#if __has_attribute(optnone)
#define NOT_OPTIMIZED __attribute__((optnone))
#endif
#elif defined(__GNUC__)
#define GCC_VERSION \
(__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
#if GCC_VERSION >= 40400
#define NOT_OPTIMIZED __attribute__((optimize("O0")))
#endif
#endif
#ifndef NOT_OPTIMIZED
#define NOT_OPTIMIZED
#endif
/***************Instance and Position constructors**********/
void init_block_value(block *b, uint8_t in) { memset(b->v, in, sizeof(b->v)); }
void copy_block(block *dst, const block *src) {
memcpy(dst->v, src->v, sizeof(uint64_t) * ARGON2_QWORDS_IN_BLOCK);
}
void xor_block(block *dst, const block *src) {
int i;
for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i) {
dst->v[i] ^= src->v[i];
}
}
static void load_block(block *dst, const void *input) {
unsigned i;
for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i) {
dst->v[i] = load64((const uint8_t *)input + i * sizeof(dst->v[i]));
}
}
static void store_block(void *output, const block *src) {
unsigned i;
for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i) {
store64((uint8_t *)output + i * sizeof(src->v[i]), src->v[i]);
}
}
/***************Memory functions*****************/
int allocate_memory(const argon2_context *context, uint8_t **memory,
size_t num, size_t size) {
size_t memory_size = num*size;
if (memory == NULL) {
return ARGON2_MEMORY_ALLOCATION_ERROR;
}
/* 1. Check for multiplication overflow */
if (size != 0 && memory_size / size != num) {
return ARGON2_MEMORY_ALLOCATION_ERROR;
}
/* 2. Try to allocate with appropriate allocator */
if (context->allocate_cbk) {
(context->allocate_cbk)(memory, memory_size);
} else {
*memory = malloc(memory_size);
}
if (*memory == NULL) {
return ARGON2_MEMORY_ALLOCATION_ERROR;
}
return ARGON2_OK;
}
void free_memory(const argon2_context *context, uint8_t *memory,
size_t num, size_t size) {
size_t memory_size = num*size;
clear_internal_memory(memory, memory_size);
if (context->free_cbk) {
(context->free_cbk)(memory, memory_size);
} else {
free(memory);
}
}
#if defined(__OpenBSD__)
#define HAVE_EXPLICIT_BZERO 1
#elif defined(__GLIBC__) && defined(__GLIBC_PREREQ)
#if __GLIBC_PREREQ(2,25)
#define HAVE_EXPLICIT_BZERO 1
#endif
#endif
void NOT_OPTIMIZED secure_wipe_memory(void *v, size_t n) {
#if defined(_MSC_VER) && VC_GE_2005(_MSC_VER) || defined(__MINGW32__)
SecureZeroMemory(v, n);
#elif defined memset_s
memset_s(v, n, 0, n);
#elif defined(HAVE_EXPLICIT_BZERO)
explicit_bzero(v, n);
#else
static void *(*const volatile memset_sec)(void *, int, size_t) = &memset;
memset_sec(v, 0, n);
#endif
}
/* Memory clear flag defaults to true. */
int FLAG_clear_internal_memory = 1;
void clear_internal_memory(void *v, size_t n) {
if (FLAG_clear_internal_memory && v) {
secure_wipe_memory(v, n);
}
}
void finalize(const argon2_context *context, argon2_instance_t *instance) {
if (context != NULL && instance != NULL) {
block blockhash;
uint32_t l;
copy_block(&blockhash, instance->memory + instance->lane_length - 1);
/* XOR the last blocks */
for (l = 1; l < instance->lanes; ++l) {
uint32_t last_block_in_lane =
l * instance->lane_length + (instance->lane_length - 1);
xor_block(&blockhash, instance->memory + last_block_in_lane);
}
/* Hash the result */
{
uint8_t blockhash_bytes[ARGON2_BLOCK_SIZE];
store_block(blockhash_bytes, &blockhash);
blake2b_long(context->out, context->outlen, blockhash_bytes,
ARGON2_BLOCK_SIZE);
/* clear blockhash and blockhash_bytes */
clear_internal_memory(blockhash.v, ARGON2_BLOCK_SIZE);
clear_internal_memory(blockhash_bytes, ARGON2_BLOCK_SIZE);
}
#ifdef GENKAT
print_tag(context->out, context->outlen);
#endif
free_memory(context, (uint8_t *)instance->memory,
instance->memory_blocks, sizeof(block));
}
}
uint32_t index_alpha(const argon2_instance_t *instance,
const argon2_position_t *position, uint32_t pseudo_rand,
int same_lane) {
/*
* Pass 0:
* This lane : all already finished segments plus already constructed
* blocks in this segment
* Other lanes : all already finished segments
* Pass 1+:
* This lane : (SYNC_POINTS - 1) last segments plus already constructed
* blocks in this segment
* Other lanes : (SYNC_POINTS - 1) last segments
*/
uint32_t reference_area_size;
uint64_t relative_position;
uint32_t start_position, absolute_position;
if (0 == position->pass) {
/* First pass */
if (0 == position->slice) {
/* First slice */
reference_area_size =
position->index - 1; /* all but the previous */
} else {
if (same_lane) {
/* The same lane => add current segment */
reference_area_size =
position->slice * instance->segment_length +
position->index - 1;
} else {
reference_area_size =
position->slice * instance->segment_length +
((position->index == 0) ? (-1) : 0);
}
}
} else {
/* Second pass */
if (same_lane) {
reference_area_size = instance->lane_length -
instance->segment_length + position->index -
1;
} else {
reference_area_size = instance->lane_length -
instance->segment_length +
((position->index == 0) ? (-1) : 0);
}
}
/* 1.2.4. Mapping pseudo_rand to 0..<reference_area_size-1> and produce
* relative position */
relative_position = pseudo_rand;
relative_position = relative_position * relative_position >> 32;
relative_position = reference_area_size - 1 -
(reference_area_size * relative_position >> 32);
/* 1.2.5 Computing starting position */
start_position = 0;
if (0 != position->pass) {
start_position = (position->slice == ARGON2_SYNC_POINTS - 1)
? 0
: (position->slice + 1) * instance->segment_length;
}
/* 1.2.6. Computing absolute position */
absolute_position = (start_position + relative_position) %
instance->lane_length; /* absolute position */
return absolute_position;
}
/* Single-threaded version for p=1 case */
static int fill_memory_blocks_st(argon2_instance_t *instance) {
uint32_t r, s, l;
for (r = 0; r < instance->passes; ++r) {
for (s = 0; s < ARGON2_SYNC_POINTS; ++s) {
for (l = 0; l < instance->lanes; ++l) {
argon2_position_t position = {r, l, (uint8_t)s, 0};
fill_segment(instance, position);
}
}
#ifdef GENKAT
internal_kat(instance, r); /* Print all memory blocks */
#endif
}
return ARGON2_OK;
}
#if !defined(ARGON2_NO_THREADS)
#ifdef _WIN32
static unsigned __stdcall fill_segment_thr(void *thread_data)
#else
static void *fill_segment_thr(void *thread_data)
#endif
{
argon2_thread_data *my_data = thread_data;
fill_segment(my_data->instance_ptr, my_data->pos);
argon2_thread_exit();
return 0;
}
/* Multi-threaded version for p > 1 case */
static int fill_memory_blocks_mt(argon2_instance_t *instance) {
uint32_t r, s;
argon2_thread_handle_t *thread = NULL;
argon2_thread_data *thr_data = NULL;
int rc = ARGON2_OK;
/* 1. Allocating space for threads */
thread = calloc(instance->lanes, sizeof(argon2_thread_handle_t));
if (thread == NULL) {
rc = ARGON2_MEMORY_ALLOCATION_ERROR;
goto fail;
}
thr_data = calloc(instance->lanes, sizeof(argon2_thread_data));
if (thr_data == NULL) {
rc = ARGON2_MEMORY_ALLOCATION_ERROR;
goto fail;
}
for (r = 0; r < instance->passes; ++r) {
for (s = 0; s < ARGON2_SYNC_POINTS; ++s) {
uint32_t l, ll;
/* 2. Calling threads */
for (l = 0; l < instance->lanes; ++l) {
argon2_position_t position;
/* 2.1 Join a thread if limit is exceeded */
if (l >= instance->threads) {
if (argon2_thread_join(thread[l - instance->threads])) {
rc = ARGON2_THREAD_FAIL;
goto fail;
}
}
/* 2.2 Create thread */
position.pass = r;
position.lane = l;
position.slice = (uint8_t)s;
position.index = 0;
thr_data[l].instance_ptr =
instance; /* preparing the thread input */
memcpy(&(thr_data[l].pos), &position,
sizeof(argon2_position_t));
if (argon2_thread_create(&thread[l], &fill_segment_thr,
(void *)&thr_data[l])) {
/* Wait for already running threads */
for (ll = 0; ll < l; ++ll)
argon2_thread_join(thread[ll]);
rc = ARGON2_THREAD_FAIL;
goto fail;
}
/* fill_segment(instance, position); */
/*Non-thread equivalent of the lines above */
}
/* 3. Joining remaining threads */
for (l = instance->lanes - instance->threads; l < instance->lanes;
++l) {
if (argon2_thread_join(thread[l])) {
rc = ARGON2_THREAD_FAIL;
goto fail;
}
}
}
#ifdef GENKAT
internal_kat(instance, r); /* Print all memory blocks */
#endif
}
fail:
if (thread != NULL) {
free(thread);
}
if (thr_data != NULL) {
free(thr_data);
}
return rc;
}
#endif /* ARGON2_NO_THREADS */
int fill_memory_blocks(argon2_instance_t *instance) {
if (instance == NULL || instance->lanes == 0) {
return ARGON2_INCORRECT_PARAMETER;
}
#if defined(ARGON2_NO_THREADS)
return fill_memory_blocks_st(instance);
#else
return instance->threads == 1 ?
fill_memory_blocks_st(instance) : fill_memory_blocks_mt(instance);
#endif
}
int validate_inputs(const argon2_context *context) {
if (NULL == context) {
return ARGON2_INCORRECT_PARAMETER;
}
if (NULL == context->out) {
return ARGON2_OUTPUT_PTR_NULL;
}
/* Validate output length */
if (ARGON2_MIN_OUTLEN > context->outlen) {
return ARGON2_OUTPUT_TOO_SHORT;
}
if (ARGON2_MAX_OUTLEN < context->outlen) {
return ARGON2_OUTPUT_TOO_LONG;
}
/* Validate password (required param) */
if (NULL == context->pwd) {
if (0 != context->pwdlen) {
return ARGON2_PWD_PTR_MISMATCH;
}
}
if (ARGON2_MIN_PWD_LENGTH > context->pwdlen) {
return ARGON2_PWD_TOO_SHORT;
}
if (ARGON2_MAX_PWD_LENGTH < context->pwdlen) {
return ARGON2_PWD_TOO_LONG;
}
/* Validate salt (required param) */
if (NULL == context->salt) {
if (0 != context->saltlen) {
return ARGON2_SALT_PTR_MISMATCH;
}
}
if (ARGON2_MIN_SALT_LENGTH > context->saltlen) {
return ARGON2_SALT_TOO_SHORT;
}
if (ARGON2_MAX_SALT_LENGTH < context->saltlen) {
return ARGON2_SALT_TOO_LONG;
}
/* Validate secret (optional param) */
if (NULL == context->secret) {
if (0 != context->secretlen) {
return ARGON2_SECRET_PTR_MISMATCH;
}
} else {
if (ARGON2_MIN_SECRET > context->secretlen) {
return ARGON2_SECRET_TOO_SHORT;
}
if (ARGON2_MAX_SECRET < context->secretlen) {
return ARGON2_SECRET_TOO_LONG;
}
}
/* Validate associated data (optional param) */
if (NULL == context->ad) {
if (0 != context->adlen) {
return ARGON2_AD_PTR_MISMATCH;
}
} else {
if (ARGON2_MIN_AD_LENGTH > context->adlen) {
return ARGON2_AD_TOO_SHORT;
}
if (ARGON2_MAX_AD_LENGTH < context->adlen) {
return ARGON2_AD_TOO_LONG;
}
}
/* Validate memory cost */
if (ARGON2_MIN_MEMORY > context->m_cost) {
return ARGON2_MEMORY_TOO_LITTLE;
}
if (ARGON2_MAX_MEMORY < context->m_cost) {
return ARGON2_MEMORY_TOO_MUCH;
}
if (context->m_cost < 8 * context->lanes) {
return ARGON2_MEMORY_TOO_LITTLE;
}
/* Validate time cost */
if (ARGON2_MIN_TIME > context->t_cost) {
return ARGON2_TIME_TOO_SMALL;
}
if (ARGON2_MAX_TIME < context->t_cost) {
return ARGON2_TIME_TOO_LARGE;
}
/* Validate lanes */
if (ARGON2_MIN_LANES > context->lanes) {
return ARGON2_LANES_TOO_FEW;
}
if (ARGON2_MAX_LANES < context->lanes) {
return ARGON2_LANES_TOO_MANY;
}
/* Validate threads */
if (ARGON2_MIN_THREADS > context->threads) {
return ARGON2_THREADS_TOO_FEW;
}
if (ARGON2_MAX_THREADS < context->threads) {
return ARGON2_THREADS_TOO_MANY;
}
if (NULL != context->allocate_cbk && NULL == context->free_cbk) {
return ARGON2_FREE_MEMORY_CBK_NULL;
}
if (NULL == context->allocate_cbk && NULL != context->free_cbk) {
return ARGON2_ALLOCATE_MEMORY_CBK_NULL;
}
return ARGON2_OK;
}
void fill_first_blocks(uint8_t *blockhash, const argon2_instance_t *instance) {
uint32_t l;
/* Make the first and second block in each lane as G(H0||0||i) or
G(H0||1||i) */
uint8_t blockhash_bytes[ARGON2_BLOCK_SIZE];
for (l = 0; l < instance->lanes; ++l) {
store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 0);
store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH + 4, l);
blake2b_long(blockhash_bytes, ARGON2_BLOCK_SIZE, blockhash,
ARGON2_PREHASH_SEED_LENGTH);
load_block(&instance->memory[l * instance->lane_length + 0],
blockhash_bytes);
store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 1);
blake2b_long(blockhash_bytes, ARGON2_BLOCK_SIZE, blockhash,
ARGON2_PREHASH_SEED_LENGTH);
load_block(&instance->memory[l * instance->lane_length + 1],
blockhash_bytes);
}
clear_internal_memory(blockhash_bytes, ARGON2_BLOCK_SIZE);
}
void initial_hash(uint8_t *blockhash, argon2_context *context,
argon2_type type) {
blake2b_state BlakeHash;
uint8_t value[sizeof(uint32_t)];
if (NULL == context || NULL == blockhash) {
return;
}
blake2b_init(&BlakeHash, ARGON2_PREHASH_DIGEST_LENGTH);
store32(&value, context->lanes);
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
store32(&value, context->outlen);
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
store32(&value, context->m_cost);
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
store32(&value, context->t_cost);
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
store32(&value, context->version);
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
store32(&value, (uint32_t)type);
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
store32(&value, context->pwdlen);
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
if (context->pwd != NULL) {
blake2b_update(&BlakeHash, (const uint8_t *)context->pwd,
context->pwdlen);
if (context->flags & ARGON2_FLAG_CLEAR_PASSWORD) {
secure_wipe_memory(context->pwd, context->pwdlen);
context->pwdlen = 0;
}
}
store32(&value, context->saltlen);
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
if (context->salt != NULL) {
blake2b_update(&BlakeHash, (const uint8_t *)context->salt,
context->saltlen);
}
store32(&value, context->secretlen);
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
if (context->secret != NULL) {
blake2b_update(&BlakeHash, (const uint8_t *)context->secret,
context->secretlen);
if (context->flags & ARGON2_FLAG_CLEAR_SECRET) {
secure_wipe_memory(context->secret, context->secretlen);
context->secretlen = 0;
}
}
store32(&value, context->adlen);
blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
if (context->ad != NULL) {
blake2b_update(&BlakeHash, (const uint8_t *)context->ad,
context->adlen);
}
blake2b_final(&BlakeHash, blockhash, ARGON2_PREHASH_DIGEST_LENGTH);
}
int initialize(argon2_instance_t *instance, argon2_context *context) {
uint8_t blockhash[ARGON2_PREHASH_SEED_LENGTH];
int result = ARGON2_OK;
if (instance == NULL || context == NULL)
return ARGON2_INCORRECT_PARAMETER;
instance->context_ptr = context;
/* 1. Memory allocation */
result = allocate_memory(context, (uint8_t **)&(instance->memory),
instance->memory_blocks, sizeof(block));
if (result != ARGON2_OK) {
return result;
}
/* 2. Initial hashing */
/* H_0 + 8 extra bytes to produce the first blocks */
/* uint8_t blockhash[ARGON2_PREHASH_SEED_LENGTH]; */
/* Hashing all inputs */
initial_hash(blockhash, context, instance->type);
/* Zeroing 8 extra bytes */
clear_internal_memory(blockhash + ARGON2_PREHASH_DIGEST_LENGTH,
ARGON2_PREHASH_SEED_LENGTH -
ARGON2_PREHASH_DIGEST_LENGTH);
#ifdef GENKAT
initial_kat(blockhash, context, instance->type);
#endif
/* 3. Creating first blocks, we always have at least two blocks in a slice
*/
fill_first_blocks(blockhash, instance);
/* Clearing the hash */
clear_internal_memory(blockhash, ARGON2_PREHASH_SEED_LENGTH);
return ARGON2_OK;
}
+228
View File
@@ -0,0 +1,228 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
#ifndef ARGON2_CORE_H
#define ARGON2_CORE_H
#include "argon2.h"
#define CONST_CAST(x) (x)(uintptr_t)
/**********************Argon2 internal constants*******************************/
enum argon2_core_constants {
/* Memory block size in bytes */
ARGON2_BLOCK_SIZE = 1024,
ARGON2_QWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 8,
ARGON2_OWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 16,
ARGON2_HWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 32,
ARGON2_512BIT_WORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 64,
/* Number of pseudo-random values generated by one call to Blake in Argon2i
to
generate reference block positions */
ARGON2_ADDRESSES_IN_BLOCK = 128,
/* Pre-hashing digest length and its extension*/
ARGON2_PREHASH_DIGEST_LENGTH = 64,
ARGON2_PREHASH_SEED_LENGTH = 72
};
/*************************Argon2 internal data types***********************/
/*
* Structure for the (1KB) memory block implemented as 128 64-bit words.
* Memory blocks can be copied, XORed. Internal words can be accessed by [] (no
* bounds checking).
*/
typedef struct block_ { uint64_t v[ARGON2_QWORDS_IN_BLOCK]; } block;
/*****************Functions that work with the block******************/
/* Initialize each byte of the block with @in */
void init_block_value(block *b, uint8_t in);
/* Copy block @src to block @dst */
void copy_block(block *dst, const block *src);
/* XOR @src onto @dst bytewise */
void xor_block(block *dst, const block *src);
/*
* Argon2 instance: memory pointer, number of passes, amount of memory, type,
* and derived values.
* Used to evaluate the number and location of blocks to construct in each
* thread
*/
typedef struct Argon2_instance_t {
block *memory; /* Memory pointer */
uint32_t version;
uint32_t passes; /* Number of passes */
uint32_t memory_blocks; /* Number of blocks in memory */
uint32_t segment_length;
uint32_t lane_length;
uint32_t lanes;
uint32_t threads;
argon2_type type;
int print_internals; /* whether to print the memory blocks */
argon2_context *context_ptr; /* points back to original context */
} argon2_instance_t;
/*
* Argon2 position: where we construct the block right now. Used to distribute
* work between threads.
*/
typedef struct Argon2_position_t {
uint32_t pass;
uint32_t lane;
uint8_t slice;
uint32_t index;
} argon2_position_t;
/*Struct that holds the inputs for thread handling FillSegment*/
typedef struct Argon2_thread_data {
argon2_instance_t *instance_ptr;
argon2_position_t pos;
} argon2_thread_data;
/*************************Argon2 core functions********************************/
/* Allocates memory to the given pointer, uses the appropriate allocator as
* specified in the context. Total allocated memory is num*size.
* @param context argon2_context which specifies the allocator
* @param memory pointer to the pointer to the memory
* @param size the size in bytes for each element to be allocated
* @param num the number of elements to be allocated
* @return ARGON2_OK if @memory is a valid pointer and memory is allocated
*/
int allocate_memory(const argon2_context *context, uint8_t **memory,
size_t num, size_t size);
/*
* Frees memory at the given pointer, uses the appropriate deallocator as
* specified in the context. Also cleans the memory using clear_internal_memory.
* @param context argon2_context which specifies the deallocator
* @param memory pointer to buffer to be freed
* @param size the size in bytes for each element to be deallocated
* @param num the number of elements to be deallocated
*/
void free_memory(const argon2_context *context, uint8_t *memory,
size_t num, size_t size);
/* Function that securely cleans the memory. This ignores any flags set
* regarding clearing memory. Usually one just calls clear_internal_memory.
* @param mem Pointer to the memory
* @param s Memory size in bytes
*/
void secure_wipe_memory(void *v, size_t n);
/* Function that securely clears the memory if FLAG_clear_internal_memory is
* set. If the flag isn't set, this function does nothing.
* @param mem Pointer to the memory
* @param s Memory size in bytes
*/
void clear_internal_memory(void *v, size_t n);
/*
* Computes absolute position of reference block in the lane following a skewed
* distribution and using a pseudo-random value as input
* @param instance Pointer to the current instance
* @param position Pointer to the current position
* @param pseudo_rand 32-bit pseudo-random value used to determine the position
* @param same_lane Indicates if the block will be taken from the current lane.
* If so we can reference the current segment
* @pre All pointers must be valid
*/
uint32_t index_alpha(const argon2_instance_t *instance,
const argon2_position_t *position, uint32_t pseudo_rand,
int same_lane);
/*
* Function that validates all inputs against predefined restrictions and return
* an error code
* @param context Pointer to current Argon2 context
* @return ARGON2_OK if everything is all right, otherwise one of error codes
* (all defined in <argon2.h>
*/
int validate_inputs(const argon2_context *context);
/*
* Hashes all the inputs into @a blockhash[PREHASH_DIGEST_LENGTH], clears
* password and secret if needed
* @param context Pointer to the Argon2 internal structure containing memory
* pointer, and parameters for time and space requirements.
* @param blockhash Buffer for pre-hashing digest
* @param type Argon2 type
* @pre @a blockhash must have at least @a PREHASH_DIGEST_LENGTH bytes
* allocated
*/
void initial_hash(uint8_t *blockhash, argon2_context *context,
argon2_type type);
/*
* Function creates first 2 blocks per lane
* @param instance Pointer to the current instance
* @param blockhash Pointer to the pre-hashing digest
* @pre blockhash must point to @a PREHASH_SEED_LENGTH allocated values
*/
void fill_first_blocks(uint8_t *blockhash, const argon2_instance_t *instance);
/*
* Function allocates memory, hashes the inputs with Blake, and creates first
* two blocks. Returns the pointer to the main memory with 2 blocks per lane
* initialized
* @param context Pointer to the Argon2 internal structure containing memory
* pointer, and parameters for time and space requirements.
* @param instance Current Argon2 instance
* @return Zero if successful, -1 if memory failed to allocate. @context->state
* will be modified if successful.
*/
int initialize(argon2_instance_t *instance, argon2_context *context);
/*
* XORing the last block of each lane, hashing it, making the tag. Deallocates
* the memory.
* @param context Pointer to current Argon2 context (use only the out parameters
* from it)
* @param instance Pointer to current instance of Argon2
* @pre instance->state must point to necessary amount of memory
* @pre context->out must point to outlen bytes of memory
* @pre if context->free_cbk is not NULL, it should point to a function that
* deallocates memory
*/
void finalize(const argon2_context *context, argon2_instance_t *instance);
/*
* Function that fills the segment using previous segments also from other
* threads
* @param context current context
* @param instance Pointer to the current instance
* @param position Current position
* @pre all block pointers must be valid
*/
void fill_segment(const argon2_instance_t *instance,
argon2_position_t position);
/*
* Function that fills the entire memory t_cost times based on the first two
* blocks in each lane
* @param instance Pointer to the current instance
* @return ARGON2_OK if successful, @context->state
*/
int fill_memory_blocks(argon2_instance_t *instance);
#endif
+463
View File
@@ -0,0 +1,463 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "encoding.h"
#include "core.h"
/*
* Example code for a decoder and encoder of "hash strings", with Argon2
* parameters.
*
* This code comprises three sections:
*
* -- The first section contains generic Base64 encoding and decoding
* functions. It is conceptually applicable to any hash function
* implementation that uses Base64 to encode and decode parameters,
* salts and outputs. It could be made into a library, provided that
* the relevant functions are made public (non-static) and be given
* reasonable names to avoid collisions with other functions.
*
* -- The second section is specific to Argon2. It encodes and decodes
* the parameters, salts and outputs. It does not compute the hash
* itself.
*
* The code was originally written by Thomas Pornin <pornin@bolet.org>,
* to whom comments and remarks may be sent. It is released under what
* should amount to Public Domain or its closest equivalent; the
* following mantra is supposed to incarnate that fact with all the
* proper legal rituals:
*
* ---------------------------------------------------------------------
* This file is provided under the terms of Creative Commons CC0 1.0
* Public Domain Dedication. To the extent possible under law, the
* author (Thomas Pornin) has waived all copyright and related or
* neighboring rights to this file. This work is published from: Canada.
* ---------------------------------------------------------------------
*
* Copyright (c) 2015 Thomas Pornin
*/
/* ==================================================================== */
/*
* Common code; could be shared between different hash functions.
*
* Note: the Base64 functions below assume that uppercase letters (resp.
* lowercase letters) have consecutive numerical codes, that fit on 8
* bits. All modern systems use ASCII-compatible charsets, where these
* properties are true. If you are stuck with a dinosaur of a system
* that still defaults to EBCDIC then you already have much bigger
* interoperability issues to deal with.
*/
/*
* Some macros for constant-time comparisons. These work over values in
* the 0..255 range. Returned value is 0x00 on "false", 0xFF on "true".
*/
#define EQ(x, y) ((((0U - ((unsigned)(x) ^ (unsigned)(y))) >> 8) & 0xFF) ^ 0xFF)
#define GT(x, y) ((((unsigned)(y) - (unsigned)(x)) >> 8) & 0xFF)
#define GE(x, y) (GT(y, x) ^ 0xFF)
#define LT(x, y) GT(y, x)
#define LE(x, y) GE(y, x)
/*
* Convert value x (0..63) to corresponding Base64 character.
*/
static int b64_byte_to_char(unsigned x) {
return (LT(x, 26) & (x + 'A')) |
(GE(x, 26) & LT(x, 52) & (x + ('a' - 26))) |
(GE(x, 52) & LT(x, 62) & (x + ('0' - 52))) | (EQ(x, 62) & '+') |
(EQ(x, 63) & '/');
}
/*
* Convert character c to the corresponding 6-bit value. If character c
* is not a Base64 character, then 0xFF (255) is returned.
*/
static unsigned b64_char_to_byte(int c) {
unsigned x;
x = (GE(c, 'A') & LE(c, 'Z') & (c - 'A')) |
(GE(c, 'a') & LE(c, 'z') & (c - ('a' - 26))) |
(GE(c, '0') & LE(c, '9') & (c - ('0' - 52))) | (EQ(c, '+') & 62) |
(EQ(c, '/') & 63);
return x | (EQ(x, 0) & (EQ(c, 'A') ^ 0xFF));
}
/*
* Convert some bytes to Base64. 'dst_len' is the length (in characters)
* of the output buffer 'dst'; if that buffer is not large enough to
* receive the result (including the terminating 0), then (size_t)-1
* is returned. Otherwise, the zero-terminated Base64 string is written
* in the buffer, and the output length (counted WITHOUT the terminating
* zero) is returned.
*/
static size_t to_base64(char *dst, size_t dst_len, const void *src,
size_t src_len) {
size_t olen;
const unsigned char *buf;
unsigned acc, acc_len;
olen = (src_len / 3) << 2;
switch (src_len % 3) {
case 2:
olen++;
/* fall through */
case 1:
olen += 2;
break;
}
if (dst_len <= olen) {
return (size_t)-1;
}
acc = 0;
acc_len = 0;
buf = (const unsigned char *)src;
while (src_len-- > 0) {
acc = (acc << 8) + (*buf++);
acc_len += 8;
while (acc_len >= 6) {
acc_len -= 6;
*dst++ = (char)b64_byte_to_char((acc >> acc_len) & 0x3F);
}
}
if (acc_len > 0) {
*dst++ = (char)b64_byte_to_char((acc << (6 - acc_len)) & 0x3F);
}
*dst++ = 0;
return olen;
}
/*
* Decode Base64 chars into bytes. The '*dst_len' value must initially
* contain the length of the output buffer '*dst'; when the decoding
* ends, the actual number of decoded bytes is written back in
* '*dst_len'.
*
* Decoding stops when a non-Base64 character is encountered, or when
* the output buffer capacity is exceeded. If an error occurred (output
* buffer is too small, invalid last characters leading to unprocessed
* buffered bits), then NULL is returned; otherwise, the returned value
* points to the first non-Base64 character in the source stream, which
* may be the terminating zero.
*/
static const char *from_base64(void *dst, size_t *dst_len, const char *src) {
size_t len;
unsigned char *buf;
unsigned acc, acc_len;
buf = (unsigned char *)dst;
len = 0;
acc = 0;
acc_len = 0;
for (;;) {
unsigned d;
d = b64_char_to_byte(*src);
if (d == 0xFF) {
break;
}
src++;
acc = (acc << 6) + d;
acc_len += 6;
if (acc_len >= 8) {
acc_len -= 8;
if ((len++) >= *dst_len) {
return NULL;
}
*buf++ = (acc >> acc_len) & 0xFF;
}
}
/*
* If the input length is equal to 1 modulo 4 (which is
* invalid), then there will remain 6 unprocessed bits;
* otherwise, only 0, 2 or 4 bits are buffered. The buffered
* bits must also all be zero.
*/
if (acc_len > 4 || (acc & (((unsigned)1 << acc_len) - 1)) != 0) {
return NULL;
}
*dst_len = len;
return src;
}
/*
* Decode decimal integer from 'str'; the value is written in '*v'.
* Returned value is a pointer to the next non-decimal character in the
* string. If there is no digit at all, or the value encoding is not
* minimal (extra leading zeros), or the value does not fit in an
* 'unsigned long', then NULL is returned.
*/
static const char *decode_decimal(const char *str, unsigned long *v) {
const char *orig;
unsigned long acc;
acc = 0;
for (orig = str;; str++) {
int c;
c = *str;
if (c < '0' || c > '9') {
break;
}
c -= '0';
if (acc > (ULONG_MAX / 10)) {
return NULL;
}
acc *= 10;
if ((unsigned long)c > (ULONG_MAX - acc)) {
return NULL;
}
acc += (unsigned long)c;
}
if (str == orig || (*orig == '0' && str != (orig + 1))) {
return NULL;
}
*v = acc;
return str;
}
/* ==================================================================== */
/*
* Code specific to Argon2.
*
* The code below applies the following format:
*
* $argon2<T>[$v=<num>]$m=<num>,t=<num>,p=<num>$<bin>$<bin>
*
* where <T> is either 'd', 'id', or 'i', <num> is a decimal integer (positive,
* fits in an 'unsigned long'), and <bin> is Base64-encoded data (no '=' padding
* characters, no newline or whitespace).
*
* The last two binary chunks (encoded in Base64) are, in that order,
* the salt and the output. Both are required. The binary salt length and the
* output length must be in the allowed ranges defined in argon2.h.
*
* The ctx struct must contain buffers large enough to hold the salt and pwd
* when it is fed into decode_string.
*/
int decode_string(argon2_context *ctx, const char *str, argon2_type type) {
/* check for prefix */
#define CC(prefix) \
do { \
size_t cc_len = strlen(prefix); \
if (strncmp(str, prefix, cc_len) != 0) { \
return ARGON2_DECODING_FAIL; \
} \
str += cc_len; \
} while ((void)0, 0)
/* optional prefix checking with supplied code */
#define CC_opt(prefix, code) \
do { \
size_t cc_len = strlen(prefix); \
if (strncmp(str, prefix, cc_len) == 0) { \
str += cc_len; \
{ code; } \
} \
} while ((void)0, 0)
/* Decoding prefix into decimal */
#define DECIMAL(x) \
do { \
unsigned long dec_x; \
str = decode_decimal(str, &dec_x); \
if (str == NULL) { \
return ARGON2_DECODING_FAIL; \
} \
(x) = dec_x; \
} while ((void)0, 0)
/* Decoding prefix into uint32_t decimal */
#define DECIMAL_U32(x) \
do { \
unsigned long dec_x; \
str = decode_decimal(str, &dec_x); \
if (str == NULL || dec_x > UINT32_MAX) { \
return ARGON2_DECODING_FAIL; \
} \
(x) = (uint32_t)dec_x; \
} while ((void)0, 0)
/* Decoding base64 into a binary buffer */
#define BIN(buf, max_len, len) \
do { \
size_t bin_len = (max_len); \
str = from_base64(buf, &bin_len, str); \
if (str == NULL || bin_len > UINT32_MAX) { \
return ARGON2_DECODING_FAIL; \
} \
(len) = (uint32_t)bin_len; \
} while ((void)0, 0)
size_t maxsaltlen = ctx->saltlen;
size_t maxoutlen = ctx->outlen;
int validation_result;
const char* type_string;
/* We should start with the argon2_type we are using */
type_string = argon2_type2string(type, 0);
if (!type_string) {
return ARGON2_INCORRECT_TYPE;
}
CC("$");
CC(type_string);
/* Reading the version number if the default is suppressed */
ctx->version = ARGON2_VERSION_10;
CC_opt("$v=", DECIMAL_U32(ctx->version));
CC("$m=");
DECIMAL_U32(ctx->m_cost);
CC(",t=");
DECIMAL_U32(ctx->t_cost);
CC(",p=");
DECIMAL_U32(ctx->lanes);
ctx->threads = ctx->lanes;
CC("$");
BIN(ctx->salt, maxsaltlen, ctx->saltlen);
CC("$");
BIN(ctx->out, maxoutlen, ctx->outlen);
/* The rest of the fields get the default values */
ctx->secret = NULL;
ctx->secretlen = 0;
ctx->ad = NULL;
ctx->adlen = 0;
ctx->allocate_cbk = NULL;
ctx->free_cbk = NULL;
ctx->flags = ARGON2_DEFAULT_FLAGS;
/* On return, must have valid context */
validation_result = validate_inputs(ctx);
if (validation_result != ARGON2_OK) {
return validation_result;
}
/* Can't have any additional characters */
if (*str == 0) {
return ARGON2_OK;
} else {
return ARGON2_DECODING_FAIL;
}
#undef CC
#undef CC_opt
#undef DECIMAL
#undef BIN
}
int encode_string(char *dst, size_t dst_len, argon2_context *ctx,
argon2_type type) {
#define SS(str) \
do { \
size_t pp_len = strlen(str); \
if (pp_len >= dst_len) { \
return ARGON2_ENCODING_FAIL; \
} \
memcpy(dst, str, pp_len + 1); \
dst += pp_len; \
dst_len -= pp_len; \
} while ((void)0, 0)
#define SX(x) \
do { \
char tmp[30]; \
sprintf(tmp, "%lu", (unsigned long)(x)); \
SS(tmp); \
} while ((void)0, 0)
#define SB(buf, len) \
do { \
size_t sb_len = to_base64(dst, dst_len, buf, len); \
if (sb_len == (size_t)-1) { \
return ARGON2_ENCODING_FAIL; \
} \
dst += sb_len; \
dst_len -= sb_len; \
} while ((void)0, 0)
const char* type_string = argon2_type2string(type, 0);
int validation_result = validate_inputs(ctx);
if (!type_string) {
return ARGON2_ENCODING_FAIL;
}
if (validation_result != ARGON2_OK) {
return validation_result;
}
SS("$");
SS(type_string);
SS("$v=");
SX(ctx->version);
SS("$m=");
SX(ctx->m_cost);
SS(",t=");
SX(ctx->t_cost);
SS(",p=");
SX(ctx->lanes);
SS("$");
SB(ctx->salt, ctx->saltlen);
SS("$");
SB(ctx->out, ctx->outlen);
return ARGON2_OK;
#undef SS
#undef SX
#undef SB
}
size_t b64len(uint32_t len) {
size_t olen = ((size_t)len / 3) << 2;
switch (len % 3) {
case 2:
olen++;
/* fall through */
case 1:
olen += 2;
break;
}
return olen;
}
size_t numlen(uint32_t num) {
size_t len = 1;
while (num >= 10) {
++len;
num = num / 10;
}
return len;
}
+57
View File
@@ -0,0 +1,57 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
#ifndef ENCODING_H
#define ENCODING_H
#include "argon2.h"
#define ARGON2_MAX_DECODED_LANES UINT32_C(255)
#define ARGON2_MIN_DECODED_SALT_LEN UINT32_C(8)
#define ARGON2_MIN_DECODED_OUT_LEN UINT32_C(12)
/*
* encode an Argon2 hash string into the provided buffer. 'dst_len'
* contains the size, in characters, of the 'dst' buffer; if 'dst_len'
* is less than the number of required characters (including the
* terminating 0), then this function returns ARGON2_ENCODING_ERROR.
*
* on success, ARGON2_OK is returned.
*/
int encode_string(char *dst, size_t dst_len, argon2_context *ctx,
argon2_type type);
/*
* Decodes an Argon2 hash string into the provided structure 'ctx'.
* The only fields that must be set prior to this call are ctx.saltlen and
* ctx.outlen (which must be the maximal salt and out length values that are
* allowed), ctx.salt and ctx.out (which must be buffers of the specified
* length), and ctx.pwd and ctx.pwdlen which must hold a valid password.
*
* Invalid input string causes an error. On success, the ctx is valid and all
* fields have been initialized.
*
* Returned value is ARGON2_OK on success, other ARGON2_ codes on error.
*/
int decode_string(argon2_context *ctx, const char *str, argon2_type type);
/* Returns the length of the encoded byte stream with length len */
size_t b64len(uint32_t len);
/* Returns the length of the encoded number num */
size_t numlen(uint32_t num);
#endif
+51
View File
@@ -0,0 +1,51 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
#ifndef ARGON2_KAT_H
#define ARGON2_KAT_H
#include "core.h"
/*
* Initial KAT function that prints the inputs to the file
* @param blockhash Array that contains pre-hashing digest
* @param context Holds inputs
* @param type Argon2 type
* @pre blockhash must point to INPUT_INITIAL_HASH_LENGTH bytes
* @pre context member pointers must point to allocated memory of size according
* to the length values
*/
void initial_kat(const uint8_t *blockhash, const argon2_context *context,
argon2_type type);
/*
* Function that prints the output tag
* @param out output array pointer
* @param outlen digest length
* @pre out must point to @a outlen bytes
**/
void print_tag(const void *out, uint32_t outlen);
/*
* Function that prints the internal state at given moment
* @param instance pointer to the current instance
* @param pass current pass number
* @pre instance must have necessary memory allocated
**/
void internal_kat(const argon2_instance_t *instance, uint32_t pass);
#endif
+194
View File
@@ -0,0 +1,194 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include "argon2.h"
#include "core.h"
#include "blake2/blamka-round-ref.h"
#include "blake2/blake2-impl.h"
#include "blake2/blake2.h"
/*
* Function fills a new memory block and optionally XORs the old block over the new one.
* @next_block must be initialized.
* @param prev_block Pointer to the previous block
* @param ref_block Pointer to the reference block
* @param next_block Pointer to the block to be constructed
* @param with_xor Whether to XOR into the new block (1) or just overwrite (0)
* @pre all block pointers must be valid
*/
static void fill_block(const block *prev_block, const block *ref_block,
block *next_block, int with_xor) {
block blockR, block_tmp;
unsigned i;
copy_block(&blockR, ref_block);
xor_block(&blockR, prev_block);
copy_block(&block_tmp, &blockR);
/* Now blockR = ref_block + prev_block and block_tmp = ref_block + prev_block */
if (with_xor) {
/* Saving the next block contents for XOR over: */
xor_block(&block_tmp, next_block);
/* Now blockR = ref_block + prev_block and
block_tmp = ref_block + prev_block + next_block */
}
/* Apply Blake2 on columns of 64-bit words: (0,1,...,15) , then
(16,17,..31)... finally (112,113,...127) */
for (i = 0; i < 8; ++i) {
BLAKE2_ROUND_NOMSG(
blockR.v[16 * i], blockR.v[16 * i + 1], blockR.v[16 * i + 2],
blockR.v[16 * i + 3], blockR.v[16 * i + 4], blockR.v[16 * i + 5],
blockR.v[16 * i + 6], blockR.v[16 * i + 7], blockR.v[16 * i + 8],
blockR.v[16 * i + 9], blockR.v[16 * i + 10], blockR.v[16 * i + 11],
blockR.v[16 * i + 12], blockR.v[16 * i + 13], blockR.v[16 * i + 14],
blockR.v[16 * i + 15]);
}
/* Apply Blake2 on rows of 64-bit words: (0,1,16,17,...112,113), then
(2,3,18,19,...,114,115).. finally (14,15,30,31,...,126,127) */
for (i = 0; i < 8; i++) {
BLAKE2_ROUND_NOMSG(
blockR.v[2 * i], blockR.v[2 * i + 1], blockR.v[2 * i + 16],
blockR.v[2 * i + 17], blockR.v[2 * i + 32], blockR.v[2 * i + 33],
blockR.v[2 * i + 48], blockR.v[2 * i + 49], blockR.v[2 * i + 64],
blockR.v[2 * i + 65], blockR.v[2 * i + 80], blockR.v[2 * i + 81],
blockR.v[2 * i + 96], blockR.v[2 * i + 97], blockR.v[2 * i + 112],
blockR.v[2 * i + 113]);
}
copy_block(next_block, &block_tmp);
xor_block(next_block, &blockR);
}
static void next_addresses(block *address_block, block *input_block,
const block *zero_block) {
input_block->v[6]++;
fill_block(zero_block, input_block, address_block, 0);
fill_block(zero_block, address_block, address_block, 0);
}
void fill_segment(const argon2_instance_t *instance,
argon2_position_t position) {
block *ref_block = NULL, *curr_block = NULL;
block address_block, input_block, zero_block;
uint64_t pseudo_rand, ref_index, ref_lane;
uint32_t prev_offset, curr_offset;
uint32_t starting_index;
uint32_t i;
int data_independent_addressing;
if (instance == NULL) {
return;
}
data_independent_addressing =
(instance->type == Argon2_i) ||
(instance->type == Argon2_id && (position.pass == 0) &&
(position.slice < ARGON2_SYNC_POINTS / 2));
if (data_independent_addressing) {
init_block_value(&zero_block, 0);
init_block_value(&input_block, 0);
input_block.v[0] = position.pass;
input_block.v[1] = position.lane;
input_block.v[2] = position.slice;
input_block.v[3] = instance->memory_blocks;
input_block.v[4] = instance->passes;
input_block.v[5] = instance->type;
}
starting_index = 0;
if ((0 == position.pass) && (0 == position.slice)) {
starting_index = 2; /* we have already generated the first two blocks */
/* Don't forget to generate the first block of addresses: */
if (data_independent_addressing) {
next_addresses(&address_block, &input_block, &zero_block);
}
}
/* Offset of the current block */
curr_offset = position.lane * instance->lane_length +
position.slice * instance->segment_length + starting_index;
if (0 == curr_offset % instance->lane_length) {
/* Last block in this lane */
prev_offset = curr_offset + instance->lane_length - 1;
} else {
/* Previous block */
prev_offset = curr_offset - 1;
}
for (i = starting_index; i < instance->segment_length;
++i, ++curr_offset, ++prev_offset) {
/*1.1 Rotating prev_offset if needed */
if (curr_offset % instance->lane_length == 1) {
prev_offset = curr_offset - 1;
}
/* 1.2 Computing the index of the reference block */
/* 1.2.1 Taking pseudo-random value from the previous block */
if (data_independent_addressing) {
if (i % ARGON2_ADDRESSES_IN_BLOCK == 0) {
next_addresses(&address_block, &input_block, &zero_block);
}
pseudo_rand = address_block.v[i % ARGON2_ADDRESSES_IN_BLOCK];
} else {
pseudo_rand = instance->memory[prev_offset].v[0];
}
/* 1.2.2 Computing the lane of the reference block */
ref_lane = ((pseudo_rand >> 32)) % instance->lanes;
if ((position.pass == 0) && (position.slice == 0)) {
/* Can not reference other lanes yet */
ref_lane = position.lane;
}
/* 1.2.3 Computing the number of possible reference block within the
* lane.
*/
position.index = i;
ref_index = index_alpha(instance, &position, pseudo_rand & 0xFFFFFFFF,
ref_lane == position.lane);
/* 2 Creating a new block */
ref_block =
instance->memory + instance->lane_length * ref_lane + ref_index;
curr_block = instance->memory + curr_offset;
if (ARGON2_VERSION_10 == instance->version) {
/* version 1.2.1 and earlier: overwrite, not XOR */
fill_block(instance->memory + prev_offset, ref_block, curr_block, 0);
} else {
if(0 == position.pass) {
fill_block(instance->memory + prev_offset, ref_block,
curr_block, 0);
} else {
fill_block(instance->memory + prev_offset, ref_block,
curr_block, 1);
}
}
}
}
+57
View File
@@ -0,0 +1,57 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
#if !defined(ARGON2_NO_THREADS)
#include "thread.h"
#if defined(_WIN32)
#include <windows.h>
#endif
int argon2_thread_create(argon2_thread_handle_t *handle,
argon2_thread_func_t func, void *args) {
if (NULL == handle || func == NULL) {
return -1;
}
#if defined(_WIN32)
*handle = _beginthreadex(NULL, 0, func, args, 0, NULL);
return *handle != 0 ? 0 : -1;
#else
return pthread_create(handle, NULL, func, args);
#endif
}
int argon2_thread_join(argon2_thread_handle_t handle) {
#if defined(_WIN32)
if (WaitForSingleObject((HANDLE)handle, INFINITE) == WAIT_OBJECT_0) {
return CloseHandle((HANDLE)handle) != 0 ? 0 : -1;
}
return -1;
#else
return pthread_join(handle, NULL);
#endif
}
void argon2_thread_exit(void) {
#if defined(_WIN32)
_endthreadex(0);
#else
pthread_exit(NULL);
#endif
}
#endif /* ARGON2_NO_THREADS */
+67
View File
@@ -0,0 +1,67 @@
/*
* Argon2 reference source code package - reference C implementations
*
* Copyright 2015
* Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
*
* You may use this work under the terms of a Creative Commons CC0 1.0
* License/Waiver or the Apache Public License 2.0, at your option. The terms of
* these licenses can be found at:
*
* - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0
* - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of both of these licenses along with this
* software. If not, they may be obtained at the above URLs.
*/
#ifndef ARGON2_THREAD_H
#define ARGON2_THREAD_H
#if !defined(ARGON2_NO_THREADS)
/*
Here we implement an abstraction layer for the simpĺe requirements
of the Argon2 code. We only require 3 primitives---thread creation,
joining, and termination---so full emulation of the pthreads API
is unwarranted. Currently we wrap pthreads and Win32 threads.
The API defines 2 types: the function pointer type,
argon2_thread_func_t,
and the type of the thread handle---argon2_thread_handle_t.
*/
#if defined(_WIN32)
#include <process.h>
typedef unsigned(__stdcall *argon2_thread_func_t)(void *);
typedef uintptr_t argon2_thread_handle_t;
#else
#include <pthread.h>
typedef void *(*argon2_thread_func_t)(void *);
typedef pthread_t argon2_thread_handle_t;
#endif
/* Creates a thread
* @param handle pointer to a thread handle, which is the output of this
* function. Must not be NULL.
* @param func A function pointer for the thread's entry point. Must not be
* NULL.
* @param args Pointer that is passed as an argument to @func. May be NULL.
* @return 0 if @handle and @func are valid pointers and a thread is successfully
* created.
*/
int argon2_thread_create(argon2_thread_handle_t *handle,
argon2_thread_func_t func, void *args);
/* Waits for a thread to terminate
* @param handle Handle to a thread created with argon2_thread_create.
* @return 0 if @handle is a valid handle, and joining completed successfully.
*/
int argon2_thread_join(argon2_thread_handle_t handle);
/* Terminate the current thread. Must be run inside a thread created by
* argon2_thread_create.
*/
void argon2_thread_exit(void);
#endif /* ARGON2_NO_THREADS */
#endif
+365
View File
@@ -0,0 +1,365 @@
#!/usr/bin/env python3
import sys
import struct
import subprocess
import re
import os
import os.path
import argparse
import json
from time import sleep
UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
UF2_MAGIC_END = 0x0AB16F30 # Ditto
INFO_FILE = "/INFO_UF2.TXT"
appstartaddr = 0x2000
familyid = 0x0
def is_uf2(buf):
w = struct.unpack("<II", buf[0:8])
return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
def is_hex(buf):
try:
w = buf[0:30].decode("utf-8")
except UnicodeDecodeError:
return False
if w[0] == ':' and re.match(rb"^[:0-9a-fA-F\r\n]+$", buf):
return True
return False
def convert_from_uf2(buf):
global appstartaddr
global familyid
numblocks = len(buf) // 512
curraddr = None
currfamilyid = None
families_found = {}
prev_flag = None
all_flags_same = True
outp = []
for blockno in range(numblocks):
ptr = blockno * 512
block = buf[ptr:ptr + 512]
hd = struct.unpack(b"<IIIIIIII", block[0:32])
if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
print("Skipping block at " + ptr + "; bad magic")
continue
if hd[2] & 1:
# NO-flash flag set; skip block
continue
datalen = hd[4]
if datalen > 476:
assert False, "Invalid UF2 data size at " + ptr
newaddr = hd[3]
if (hd[2] & 0x2000) and (currfamilyid == None):
currfamilyid = hd[7]
if curraddr == None or ((hd[2] & 0x2000) and hd[7] != currfamilyid):
currfamilyid = hd[7]
curraddr = newaddr
if familyid == 0x0 or familyid == hd[7]:
appstartaddr = newaddr
padding = newaddr - curraddr
if padding < 0:
assert False, "Block out of order at " + ptr
if padding > 10*1024*1024:
assert False, "More than 10M of padding needed at " + ptr
if padding % 4 != 0:
assert False, "Non-word padding size at " + ptr
while padding > 0:
padding -= 4
outp.append(b"\x00\x00\x00\x00")
if familyid == 0x0 or ((hd[2] & 0x2000) and familyid == hd[7]):
outp.append(block[32 : 32 + datalen])
curraddr = newaddr + datalen
if hd[2] & 0x2000:
if hd[7] in families_found.keys():
if families_found[hd[7]] > newaddr:
families_found[hd[7]] = newaddr
else:
families_found[hd[7]] = newaddr
if prev_flag == None:
prev_flag = hd[2]
if prev_flag != hd[2]:
all_flags_same = False
if blockno == (numblocks - 1):
print("--- UF2 File Header Info ---")
families = load_families()
for family_hex in families_found.keys():
family_short_name = ""
for name, value in families.items():
if value == family_hex:
family_short_name = name
print("Family ID is {:s}, hex value is 0x{:08x}".format(family_short_name,family_hex))
print("Target Address is 0x{:08x}".format(families_found[family_hex]))
if all_flags_same:
print("All block flag values consistent, 0x{:04x}".format(hd[2]))
else:
print("Flags were not all the same")
print("----------------------------")
if len(families_found) > 1 and familyid == 0x0:
outp = []
appstartaddr = 0x0
return b"".join(outp)
def convert_to_carray(file_content):
outp = "const unsigned long bindata_len = %d;\n" % len(file_content)
outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {"
for i in range(len(file_content)):
if i % 16 == 0:
outp += "\n"
outp += "0x%02x, " % file_content[i]
outp += "\n};\n"
return bytes(outp, "utf-8")
def convert_to_uf2(file_content):
global familyid
datapadding = b""
while len(datapadding) < 512 - 256 - 32 - 4:
datapadding += b"\x00\x00\x00\x00"
numblocks = (len(file_content) + 255) // 256
outp = []
for blockno in range(numblocks):
ptr = 256 * blockno
chunk = file_content[ptr:ptr + 256]
flags = 0x0
if familyid:
flags |= 0x2000
hd = struct.pack(b"<IIIIIIII",
UF2_MAGIC_START0, UF2_MAGIC_START1,
flags, ptr + appstartaddr, 256, blockno, numblocks, familyid)
while len(chunk) < 256:
chunk += b"\x00"
block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
assert len(block) == 512
outp.append(block)
return b"".join(outp)
class Block:
def __init__(self, addr, default_data=0xFF):
self.addr = addr
self.bytes = bytearray([default_data] * 256)
def encode(self, blockno, numblocks):
global familyid
flags = 0x0
if familyid:
flags |= 0x2000
hd = struct.pack("<IIIIIIII",
UF2_MAGIC_START0, UF2_MAGIC_START1,
flags, self.addr, 256, blockno, numblocks, familyid)
hd += self.bytes[0:256]
while len(hd) < 512 - 4:
hd += b"\x00"
hd += struct.pack("<I", UF2_MAGIC_END)
return hd
def convert_from_hex_to_uf2(buf):
global appstartaddr
appstartaddr = None
upper = 0
currblock = None
blocks = []
for line in buf.split('\n'):
if line[0] != ":":
continue
i = 1
rec = []
while i < len(line) - 1:
rec.append(int(line[i:i+2], 16))
i += 2
tp = rec[3]
if tp == 4:
upper = ((rec[4] << 8) | rec[5]) << 16
elif tp == 2:
upper = ((rec[4] << 8) | rec[5]) << 4
elif tp == 1:
break
elif tp == 0:
addr = upper + ((rec[1] << 8) | rec[2])
if appstartaddr == None:
appstartaddr = addr
i = 4
while i < len(rec) - 1:
if not currblock or currblock.addr & ~0xff != addr & ~0xff:
currblock = Block(addr & ~0xff)
blocks.append(currblock)
currblock.bytes[addr & 0xff] = rec[i]
addr += 1
i += 1
numblocks = len(blocks)
resfile = b""
for i in range(0, numblocks):
resfile += blocks[i].encode(i, numblocks)
return resfile
def to_str(b):
return b.decode("utf-8")
def get_drives():
drives = []
if sys.platform == "win32":
r = subprocess.check_output([
"powershell",
"-Command",
'(Get-WmiObject Win32_LogicalDisk -Filter "VolumeName=\'RPI-RP2\'").DeviceID'
])
drive = to_str(r).strip()
if drive:
drives.append(drive)
else:
searchpaths = ["/mnt", "/media"]
if sys.platform == "darwin":
searchpaths = ["/Volumes"]
elif sys.platform == "linux":
searchpaths += ["/media/" + os.environ["USER"], "/run/media/" + os.environ["USER"]]
if "SUDO_USER" in os.environ.keys():
searchpaths += ["/media/" + os.environ["SUDO_USER"]]
searchpaths += ["/run/media/" + os.environ["SUDO_USER"]]
for rootpath in searchpaths:
if os.path.isdir(rootpath):
for d in os.listdir(rootpath):
if os.path.isdir(os.path.join(rootpath, d)):
drives.append(os.path.join(rootpath, d))
def has_info(d):
try:
return os.path.isfile(d + INFO_FILE)
except:
return False
return list(filter(has_info, drives))
def board_id(path):
with open(path + INFO_FILE, mode='r') as file:
file_content = file.read()
return re.search(r"Board-ID: ([^\r\n]*)", file_content).group(1)
def list_drives():
for d in get_drives():
print(d, board_id(d))
def write_file(name, buf):
with open(name, "wb") as f:
f.write(buf)
print("Wrote %d bytes to %s" % (len(buf), name))
def load_families():
# The expectation is that the `uf2families.json` file is in the same
# directory as this script. Make a path that works using `__file__`
# which contains the full path to this script.
filename = "uf2families.json"
pathname = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename)
with open(pathname) as f:
raw_families = json.load(f)
families = {}
for family in raw_families:
families[family["short_name"]] = int(family["id"], 0)
return families
def main():
global appstartaddr, familyid
def error(msg):
print(msg, file=sys.stderr)
sys.exit(1)
parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.')
parser.add_argument('input', metavar='INPUT', type=str, nargs='?',
help='input file (HEX, BIN or UF2)')
parser.add_argument('-b', '--base', dest='base', type=str,
default="0x2000",
help='set base address of application for BIN format (default: 0x2000)')
parser.add_argument('-f', '--family', dest='family', type=str,
default="0x0",
help='specify familyID - number or name (default: 0x0)')
parser.add_argument('-o', '--output', metavar="FILE", dest='output', type=str,
help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible')
parser.add_argument('-d', '--device', dest="device_path",
help='select a device path to flash')
parser.add_argument('-l', '--list', action='store_true',
help='list connected devices')
parser.add_argument('-c', '--convert', action='store_true',
help='do not flash, just convert')
parser.add_argument('-D', '--deploy', action='store_true',
help='just flash, do not convert')
parser.add_argument('-w', '--wait', action='store_true',
help='wait for device to flash')
parser.add_argument('-C', '--carray', action='store_true',
help='convert binary file to a C array, not UF2')
parser.add_argument('-i', '--info', action='store_true',
help='display header information from UF2, do not convert')
args = parser.parse_args()
appstartaddr = int(args.base, 0)
families = load_families()
if args.family.upper() in families:
familyid = families[args.family.upper()]
else:
try:
familyid = int(args.family, 0)
except ValueError:
error("Family ID needs to be a number or one of: " + ", ".join(families.keys()))
if args.list:
list_drives()
else:
if not args.input:
error("Need input file")
with open(args.input, mode='rb') as f:
inpbuf = f.read()
from_uf2 = is_uf2(inpbuf)
ext = "uf2"
if args.deploy:
outbuf = inpbuf
elif from_uf2 and not args.info:
outbuf = convert_from_uf2(inpbuf)
ext = "bin"
elif from_uf2 and args.info:
outbuf = ""
convert_from_uf2(inpbuf)
elif is_hex(inpbuf):
outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8"))
elif args.carray:
outbuf = convert_to_carray(inpbuf)
ext = "h"
else:
outbuf = convert_to_uf2(inpbuf)
if not args.deploy and not args.info:
print("Converted to %s, output size: %d, start address: 0x%x" %
(ext, len(outbuf), appstartaddr))
if args.convert or ext != "uf2":
if args.output == None:
args.output = "flash." + ext
if args.output:
write_file(args.output, outbuf)
if ext == "uf2" and not args.convert and not args.info:
drives = get_drives()
if len(drives) == 0:
if args.wait:
print("Waiting for drive to deploy...")
while len(drives) == 0:
sleep(0.1)
drives = get_drives()
elif not args.output:
error("No drive to deploy.")
for d in drives:
print("Flashing %s (%s)" % (d, board_id(d)))
write_file(d + "/NEW.UF2", outbuf)
if __name__ == "__main__":
main()
+22
View File
@@ -0,0 +1,22 @@
[
{
"short_name": "RP2040",
"id": "0xe48bff56",
"description": "Raspberry Pi RP2040"
},
{
"short_name": "RP2350-ARM-S",
"id": "0xe48bff59",
"description": "Raspberry Pi RP2350, ARM, Secure"
},
{
"short_name": "RP2350-ARM-NS",
"id": "0xe48bff5a",
"description": "Raspberry Pi RP2350, ARM, Non-Secure"
},
{
"short_name": "RP2350-RISCV",
"id": "0xe48bff5b",
"description": "Raspberry Pi RP2350, RISC-V"
}
]