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