diff --git a/.claude/skills/dmod-boot/SKILL.md b/.claude/skills/dmod-boot/SKILL.md new file mode 100644 index 0000000..efe6bb1 --- /dev/null +++ b/.claude/skills/dmod-boot/SKILL.md @@ -0,0 +1,65 @@ +--- +name: dmod-boot +description: Explains dmod-boot, the firmware entry point that initializes hardware/FreeRTOS/heap/VFS/logging and boots into a loaded module package — how module packaging (modules.dmp) works, how to test a locally-built .dmf on real hardware without a release, and the flash/connect/monitor CMake targets for working with real boards. Load this when working in the dmod-boot repo, tracing how firmware boots into a usable system, or flashing/debugging a board. Companion to the dmod-ecosystem skill. +--- + +# `dmod-boot`: the entry point + +`dmod-boot` is the bootloader/firmware entry point for a real board. It +initializes hardware, FreeRTOS (via `dmosi-freertos`), heap (`dmheap`), VFS +(`dmvfs`, mounting `dmramfs` on `/`, `dmdevfs` on `/dev`), logging (`dmlog`), +then loads a package of modules embedded directly in ROM at link time and +starts a main module — typically the shell, `dmell`. If you're tracing "how +does firmware boot into a usable system", `dmod-boot/src/main.c` is the +starting point. + +**Module packaging.** During the build, `dmod-boot` bundles the modules it +finds in `/build/dmf/` into a single `modules.dmp` package, which +is then embedded in flash alongside the rest of the firmware (`modules/modules.dmd` +is the manifest that determines which modules get fetched/built into +`build/dmf/` in the first place). A module can be present there in two forms: +`.dmf` (plain) and `.dmfc` (the same module, fastlz-compressed). +`dmf-get` normally downloads both, and **the `.dmfc` takes precedence when the +module is loaded**. + +**Testing a locally-built module on real hardware, without a release:** drop +the `.dmf` binary straight into `/build/dmf/`, **delete that +module's `.dmfc` from the same directory**, then delete +`/build/modules.dmp` and `/build/__modules_dmp.o` so the +next build is forced to regenerate the package (an incremental build won't +notice a `.dmf` was added/changed in place otherwise). + +Skipping the `.dmfc` step is the classic trap: the build succeeds, the new +`.dmf` really is in `build/dmf/`, and the board still silently runs the old +released module. It usually surfaces as a *dependency* error rather than an +obvious "stale module" one — the released build was linked against older +versions of its dependencies, so you get +`Module version mismatch: != ` and +`API '@:/' is not connected`, followed by the module failing +to enable. Two things make this hard to see: `strings build/modules.dmp` will +*not* find the offending signature (it is compressed inside the `.dmfc`), and +comparing `.dmf`/`.dmfc` timestamps in `build/dmf/` is the quickest way to +spot it — every module should have a matching pair, so the one module whose +two files disagree is the culprit. + +**Beware of reconfiguring while testing a local module.** Re-running +`cmake -S . -B ` (as opposed to `cmake --build `) re-runs +`dmf-get`, which re-downloads *every* module into `build/dmf/` and overwrites +any `.dmf` you dropped there by hand — and restores the `.dmfc` you deleted. +After any reconfigure, redo the steps above. Note also that `dmf-get` resolves +each repo's dependencies independently, so a refresh can legitimately leave +the set internally inconsistent (e.g. a command module still built against an +older version of a library module it uses); such a module fails to bind on its +own, which is harmless to the rest of the boot, but it is worth telling apart +from the stale-`.dmfc` case above. + +**Flashing and debugging** (from `/build`): +- `cmake --build . --target install-firmware` — flash the firmware to the board. +- `cmake --build . --target connect` — attach OpenOCD to the target. +- `cmake --build . --target monitor` — attach a console to `dmell` over the + existing OpenOCD connection (run `connect` first). This gives you stdin/stdout + independent of any firmware-level driver — you get a working shell even + before a UART driver is loaded, since it doesn't go through `dmuart`/`dmtty` + at all. Log output for this relies on `dmlog`, the base/kernel-level logging + system used from very early boot (before most modules are up), which is why + `dmod-boot` depends on it directly rather than through a loaded module. diff --git a/.claude/skills/dmod-coding-conventions/SKILL.md b/.claude/skills/dmod-coding-conventions/SKILL.md new file mode 100644 index 0000000..bdda94b --- /dev/null +++ b/.claude/skills/dmod-coding-conventions/SKILL.md @@ -0,0 +1,90 @@ +--- +name: dmod-coding-conventions +description: Coding conventions for DMOD module C code — heap-allocate strings/lists instead of fixed-size buffers (Dmod_StrDup, dmlist), never statically link a dependency into a module (always dmod_link_modules), keep functions under ~50 lines, and the _create/_destroy opaque-context pattern with a magic-field guard. Load this when writing or reviewing module implementation code (.c/.h files) in a dmod-* repo. Companion to the dmod-ecosystem skill. +--- + +# DMOD Module Coding Conventions + +## Strings: prefer the heap over fixed-size buffers + +The target is embedded (STM32F4/F7, ESP32-S3) — RAM is scarce and every byte +of it matters, so default to heap-allocating rather than reserving fixed +static/stack space "just in case". This applies to strings in general, not +just paths: don't declare a fixed-size `char buf[N]` for a string whose +length isn't tightly bounded — heap-allocate it instead. Beyond the general +RAM-budget argument, fixed-size stack/static buffers silently truncate or +overflow once a value gets longer than whoever wrote `N` expected, which is a +real risk for anything composed at runtime (paths, module names, mount +points under `/`, `.dmr`/`.dmm` entries, log/error messages, etc.). A static +fixed-size array is only acceptable when you can genuinely guarantee the +string will never exceed ~20 bytes (e.g. a short fixed enum-like tag) — +paths and other composed/runtime strings do not qualify. + +When you need an owned copy of a string (not just a borrowed pointer), copy +it with `Dmod_StrDup` rather than `malloc` + `strcpy`/`memcpy`, and `free` it +when done. + +The same reasoning applies to lists: don't hand-roll a fixed-size static +array for a collection whose size grows/shrinks at runtime. Use the `dmlist` +module instead — pull it in with `dmod_link_modules(${DMOD_MODULE_NAME} +dmlist)` in `CMakeLists.txt` (see `dmtty/CMakeLists.txt` for a working +example) rather than reimplementing a dynamic list. + +## Never statically link libraries into a module + +Always link a module against another module's `_if` target dynamically +(`dmod_link_modules`, the standard Built-in API / MAL / DIF mechanisms) — +never statically link a library's object code directly into a module. DMOD +modules are loaded/unloaded independently at runtime; statically linking a +dependency into a module defeats that (bloats the `.dmf`, duplicates the +dependency's RAM/flash footprint across every module that links it, and +breaks the loader's ability to resolve/share a single instance of it). If +you find yourself reaching for `target_link_libraries` with anything other +than a module's own `_if` interface target, stop and use +`dmod_link_modules` instead. + +## Keep functions short and extract shared logic + +Functions should stay short — **max ~50 lines**. If a function is growing +past that, it's a signal to split it, not to keep going. Always look for the +common part between two similar code paths (two callers doing almost the +same thing, two branches with overlapping logic, ...) and pull it into its +own function rather than duplicating it inline. + +## Prefer the `_create`/`_destroy` context pattern + +The preferred way to manage a piece of stateful data is a context object: +a `_create` function allocates and initializes a context struct holding all +the data that logic needs, callers thread that context pointer through the +rest of the API, and a `_destroy` function frees it once it's no longer +needed. See `dm_sw_ring_create()`/`dm_sw_ring_destroy()` in +`dm_sw_ring/include/dm_sw_ring.h` for a worked example of this shape. + +**Context structs must be opaque/private.** Declare only the typedef in the +public/interface header, and define the actual struct body in the private +source file: + +```c +/* public header (e.g. include/.h) */ +typedef struct context context_t; +``` + +```c +/* private file (e.g. src/.c or a private header) */ +struct context +{ + ... +}; +``` + +This keeps callers from reaching into the struct directly or depending on +its layout — all access must go through the module's API functions. + +**Guard context structs with a magic field.** Give the struct a `magic` +field (`uint32_t`) set in `_create` to a fixed value derived from a short +form of the module's name (e.g. a 4-char tag packed into a `uint32_t`), and +check it at the top of every function that receives the context pointer, +before the context is dereferenced (or used) — the check should be present +in every entry point (`_destroy` included), not just a subset. This catches +use of a stale/freed, corrupted, or wrong-type pointer early instead of +leading to undefined behavior further down. diff --git a/.claude/skills/dmod-ecosystem/SKILL.md b/.claude/skills/dmod-ecosystem/SKILL.md new file mode 100644 index 0000000..b267107 --- /dev/null +++ b/.claude/skills/dmod-ecosystem/SKILL.md @@ -0,0 +1,118 @@ +--- +name: dmod-ecosystem +description: Entry-point map of the DMOD (Dynamic Modules) embedded ecosystem architecture — the core loader, the three inter-module communication mechanisms (Built-in API, MAL, DIF), and the shared CMake/Make build system. Load this first when working in any dmod-* repository (dmod, dmod-boot, dmuart, dmtty, dmfmc, dmell, dm_sw_ring, dmclk, dmgpio, dmini, dmheap, dmvfs, dmffs, dmdevfs, dmdrvi, dmhaman, dmosi*, or a new module created from this template) to understand how the repo fits into the larger system before making changes. Points to companion skills (dmod-module-structure, dmod-boot, dmod-hardware-ports, dmod-coding-conventions) for deeper topics. +--- + +# DMOD Ecosystem + +DMOD ("Dynamic Modules") is a framework for dynamically loading and running code +on embedded microcontrollers (STM32F4/F7, ESP32-S3 seen so far), similar in spirit +to shared libraries on a desktop OS but built for firmware. A "module" is a +self-contained unit of code (library or application) compiled to a `.dmf` +(DMOD File) — optionally compressed to `.dmfc` — that a loader can load, run, +and unload at runtime without recompiling or resetting the whole firmware. + +This ecosystem is spread across many small, independent git repositories under +one parent directory (each repo = one module, or the core framework). There is +no monorepo and no shared parent git tree — treat each repo you open as +self-contained, but expect it to `FetchContent` the `dmod` repo at configure time. + +**Naming convention: snake_case everywhere** — module names, file names, +functions, types (`dm_sw_ring`, `dm_sw_ring_create`, `dm_sw_ring_t`, +`dmfmc_configure_sdram`, ...). Don't introduce camelCase or PascalCase names +in module code (the handful of `Dmod_*`/`Pascal_Case` symbols you'll see, like +`Dmod_Printf`, are dmod's own SAL/core API, not a pattern to copy in module code). + +## The core repo: `dmod` + +`dmod` is the SDK and build system every other repo depends on. It provides: + +- **The loader/runtime** (`src/system/`, `src/module/`) — loads `.dmf`/`.dmfc`/`.dmp` + files, manages module contexts, resolves inter-module calls. +- **Two build modes**, selected via `DMOD_MODE`: + - `DMOD_SYSTEM` — builds the loader itself, embedded in firmware. + - `DMOD_MODULE` — builds a single loadable module (`.dmf`). +- **CMake functions** every module repo uses (see `dmod/docs/cmake-functions.md` + for the full reference): + - `dmod_add_library(name version sources...)` / `dmod_add_executable(...)` — + create a module. Reads `DMOD_MODULE_NAME`, `DMOD_MODULE_VERSION`, + `DMOD_AUTHOR_NAME`, `DMOD_STACK_SIZE`, optionally `DMOD_PRIORITY`, + `DMOD_MAL_IMPLS`, `DMOD_DIF_IMPLS`, `DMOD_DMR_PATH` (set before calling). + Also creates a `_if` INTERFACE target for consumers that only need headers. + - `dmod_link_modules(target [PRIVATE|PUBLIC|INTERFACE] module[@version]...)` — + downloads another module's headers via `dmf-get` at configure time and adds + them as include dirs. This is how modules depend on each other without + vendoring source. + - `dmod_setup_external_module()` — call after `project()` in a standalone repo + to wire up DMOD's CMake machinery. +- **The package manager**: `dmf-get` (installs/downloads modules), plus + `todmfc`/`todmd`/`todmm`/`todmp`/`mkdmrpkg`/`dmf-man` tools and three manifest + file formats, all documented under `dmod/docs/`: + - `.dmm` (manifest) — registry mapping module name → download URL. + - `.dmr` (resource file) — declares what files go where when a module is + installed/packaged (`docs/dmr-file-format.md`). + - `.dmd` (dependencies) — generated list of a module's dependency versions. + +## Three ways modules talk to each other + +1. **Built-in / Module API** — a module's own public functions, declared in + its header with the `dmod__api(version, ret, _suffix, (args))` + macro and defined in the `.c` file with the matching + `dmod__api_declaration(version, ret, _suffix, (args))` macro (both + auto-generated per-module into `_defs.h` by the dmod core build + from `dmod/scripts/api.h.in` — you only follow the naming convention, never + hand-write the macro itself). **This is not optional boilerplate**: a + plain C function prototype/definition in a module's public header will + compile but **fail to link** (or simply never be found), because the + loader resolves these calls dynamically rather than through normal static + linkage. Use this as the default way to expose any function another module + (or this module's own `tests/`) should be able to call — see + `dm_sw_ring/include/dm_sw_ring.h` + `dm_sw_ring/src/dm_sw_ring.c` for a + fully worked example, and the `dmod__port_api(...)` port variant + described below. + - A `dmod__global_api(...)` / `_global_api_declaration(...)` + variant also exists, declaring the function in the *global* namespace + instead of the module's own — reserved for exceptional cases (e.g. + defining a `printf`-style utility meant to be called unqualified from + anywhere). Default to the plain `_api`/`_api_declaration` pair unless you + have a specific reason to go global. +2. **MAL (Module Abstraction Layer)** — 1:1 inversion of control. One module + defines an interface; another module implements it and registers via + `DMOD_MAL_IMPLS` in its `CMakeLists.txt`/`Makefile`. Lets you swap an + implementation (e.g. which UART driver backs a generic "serial" interface) + without touching the caller. +3. **DIF (Dmod Interface)** — 1:N. Multiple modules implement the same + interface (e.g. several SPI/UART drivers) and are discovered dynamically at + runtime via `Dmod_GetNextDifModule()`/`Dmod_GetDifFunction()`, registered + through `DMOD_DIF_IMPLS`. + +Pick Built-in API for a hard compile-time dependency, MAL for "one pluggable +backend", DIF for "many interchangeable backends discovered at runtime". + +## Companion skills for deeper topics + +This skill is the map, not the territory — load these alongside it as the +task calls for them, rather than assuming everything is covered here: + +- **`dmod-module-structure`** — the standard module repo layout + (`CMakeLists.txt`, `manifest.dmm`, `docs/`, `tests/`), scaffolding a new + module with `dmod/scripts/new-module.sh`, and when to extract a reusable + library out of an application module. Load when creating or restructuring + a module repo. +- **`dmod-boot`** — the firmware entry point (`dmod-boot`): boot sequence, + module packaging (`modules.dmp`), testing a locally-built `.dmf` on real + hardware, and the flash/connect/monitor CMake targets. Load when working + in `dmod-boot` or tracing how firmware boots into a usable system. +- **`dmod-hardware-ports`** — the driver+port split used by hardware + modules (`dmuart`, `dmfmc`, ...), `DMOD_CPU_FAMILY`, and the + `dmod__port_api` macro. Load when writing or modifying a module + that talks to real hardware/peripherals. +- **`dmod-coding-conventions`** — module C code style: heap over + fixed-size buffers, never statically link a dependency into a module, + keep functions short, and the `_create`/`_destroy` opaque-context pattern + with a magic-field guard. Load when writing or reviewing module + implementation code. + +For a specific module's exact API, config keys, or behavior, read that +repo's `docs/api-reference.md` and `include/.h` directly rather than +guessing from any of these skills — module APIs evolve independently of them. diff --git a/.claude/skills/dmod-hardware-ports/SKILL.md b/.claude/skills/dmod-hardware-ports/SKILL.md new file mode 100644 index 0000000..9d9c9ef --- /dev/null +++ b/.claude/skills/dmod-hardware-ports/SKILL.md @@ -0,0 +1,45 @@ +--- +name: dmod-hardware-ports +description: Explains the driver+port pattern used by DMOD hardware modules (dmuart, dmfmc, dmgpio, dmclk, and any new peripheral driver) — splitting a module into a hardware-independent core and an architecture-specific "_port" module selected via DMOD_CPU_FAMILY, the dmod__port_api macro, and sharing logic across MCU families via src/port/_common/. Load this when writing or modifying a module that talks to real hardware/peripherals. Companion to the dmod-ecosystem skill. +--- + +# Driver + port pattern (hardware modules) + +Modules that touch real hardware (`dmuart`, `dmfmc`, and any new peripheral +driver) split into **two independent DMOD modules in one repo**: + +- `` — the core module: public API, config parsing (via `dmini`), + hardware-independent logic. Links against `_port_if`. +- `_port` — a second module, built from `src/port/CMakeLists.txt`, + containing the architecture-specific implementation. One subdirectory per + MCU family under `src/port//` (e.g. `src/port/stm32f7/`), each with a + `config.cmake` that sets `DMOD_TOOLS_NAME` (e.g. + `"arch/armv7/cortex-m7"`) and a `port.c` implementing the lifecycle + (`dmod_init`/`dmod_deinit`) plus any `DMOD_IRQ_HANDLER(...)`. + +The active architecture is selected via **`DMOD_CPU_FAMILY`** (a cache +variable already defined in `dmod/dmod-cfg.cmake`, consumed by `dmf-get +--cpu-family` for package resolution) — set it directly in the module's +`CMakeLists.txt` before `FetchContent_MakeAvailable(dmod)`: + +```cmake +set(DMOD_CPU_FAMILY "stm32f7" CACHE STRING "Target CPU family") +include(${CMAKE_CURRENT_SOURCE_DIR}/src/port/${DMOD_CPU_FAMILY}/config.cmake) +``` + +Do **not** invent a module-specific variable like `_MCU_SERIES` for +this — `DMOD_CPU_FAMILY` already exists and is understood by the rest of the +toolchain (`dmf-get`, package naming). `dmfmc`'s `DMFMC_MCU_SERIES` predates +this and is legacy, not a pattern to copy in new modules. + +The port's public API is declared with the `dmod__port_api(version, +return_type, _suffix, (args))` macro — this macro is generated automatically +by the dmod core build from `dmod/scripts/api.h.in` into +`_port_defs.h`; you only need to follow the naming convention, not +hand-write the macro. See `dmfmc/include/dmfmc_port.h` for a full example, or +`dmuart/include/dmuart_port.h` for a simpler one. + +If a peripheral IP block is identical across MCU families, keep the shared +logic in `src/port/_common/` and make each `src/port//port.c` a +thin wrapper (lifecycle + IRQ only) — see `dmfmc/docs/port-implementation.md` +for the exact recipe used there. diff --git a/.claude/skills/dmod-module-structure/SKILL.md b/.claude/skills/dmod-module-structure/SKILL.md new file mode 100644 index 0000000..77446b2 --- /dev/null +++ b/.claude/skills/dmod-module-structure/SKILL.md @@ -0,0 +1,49 @@ +--- +name: dmod-module-structure +description: Explains the standard DMOD module repo layout (CMakeLists.txt/Makefile skeleton, manifest.dmm, docs/, tests/), how to scaffold a new module with dmod/scripts/new-module.sh, and when to extract a reusable library out of an application module. Load this when creating a new dmod-* module repo, restructuring an existing one, or deciding whether functionality should become its own library module. Companion to the dmod-ecosystem skill. +--- + +# DMOD Module Structure + +Every real module repo (`dmuart`, `dmtty`, `dmfmc`, `dm_sw_ring`, `dmell`, ...) +follows the same shape: + +``` +/ +├── CMakeLists.txt # FetchContent's dmod, then dmod_add_library/_executable +├── Makefile # Make-based equivalent build +├── manifest.dmm # $include .../versions.dmm + this module's download URL +├── .dmr # install/package resource mapping +├── include/.h # public API +├── src/.c # implementation + dmod_init()/dmod_deinit() +├── docs/ +│ ├── README.md # doc index +│ ├── api-reference.md # full API reference +│ └── (examples.md, configuration.md, port-implementation.md as needed) +├── tests/ +│ ├── CMakeLists.txt # dmod_add_test(...) +│ └── _test.c # #include "dmod_test.h", DMOD_TEST_STEP(...) macros +└── README.md # includes a "Project Structure" tree section +``` + +Every `CMakeLists.txt` in a standalone module repo follows the same skeleton — +`FetchContent_Declare(dmod GIT_REPOSITORY .../dmod.git GIT_TAG develop)` + +`FetchContent_MakeAvailable(dmod)` + `include(${DMOD_DIR}/paths.cmake)` + +`dmod_setup_external_module()` — rather than assuming a local checkout path. +Look at `dm_sw_ring/CMakeLists.txt` or `dmtty/CMakeLists.txt` for a clean example. + +New modules should be scaffolded with `dmod/scripts/new-module.sh` (see +`--help` for `--type library|application`, `--port`, `--dif`, `--mal`), which +generates this exact layout. + +## Extracting a library out of an application module + +Sometimes an `application`-type module accumulates functionality that other +modules need to call — not just as an implementation detail, but as an API +other modules should link against. In that case, split the reusable part +out into its own `library`-type DMOD module rather than exposing the +application module's internals directly, and give that library module a +`lib` prefix. E.g. if the application module is `systemd`, the extracted +library becomes `libsystemd`: `systemd` itself becomes a consumer of +`libsystemd` (via `dmod_link_modules`) alongside every other module that +needs that API, rather than a special case other modules reach into. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5a979b0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: CI +on: + push: + branches: [main, master, develop] + pull_request: + branches: [main, master, develop] +jobs: + build: + name: Build and test (${{ matrix.tools }}) + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + fail-fast: false + matrix: + tools: [arch/x86_64, arch/armv7/cortex-m4, arch/armv7/cortex-m7] + container: + image: chocotechnologies/dmod:1.0.4 + steps: + - uses: actions/checkout@v4 + - name: Install interoperability tools + run: | + apt-get update + apt-get install -y --no-install-recommends python3 openssl + - name: Build + run: | + cmake -S . -B build -DDMOD_TOOLS_NAME=${{ matrix.tools }} + cmake --build build --parallel 4 + test -f build/dmf/dmtls.dmf + test -f build/dmf/test_dmtls.dmf + - name: Native tests + if: matrix.tools == 'arch/x86_64' + run: ctest --test-dir build --output-on-failure diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6693555 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,246 @@ +name: Release + +on: + release: + types: [created] + +jobs: + build-release: + name: Build Release for ${{ matrix.arch_name }} + runs-on: ubuntu-latest + permissions: + contents: write + strategy: + matrix: + arch_name: [x86_64, armv7/cortex-m4, armv7/cortex-m7] + + container: + image: chocotechnologies/dmod:1.0.4 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install build-time audit interpreter + run: | + apt-get update + apt-get install -y --no-install-recommends python3 + + - name: Extract version from tag + id: get_version + run: | + # Extract version from tag (e.g., v1.2 -> 1.2) + VERSION="${{ github.event.release.tag_name }}" + VERSION="${VERSION#v}" # Remove 'v' prefix if present + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + - name: Build dmtls for ${{ matrix.arch_name }} + run: | + set -e + ARCH_DIR_NAME=$(echo "${{ matrix.arch_name }}" | sed 's|/|-|') + echo "ARCH_DIR_NAME=$ARCH_DIR_NAME" >> $GITHUB_ENV + echo "ARTIFACT_NAME=release-$ARCH_DIR_NAME" >> $GITHUB_ENV + mkdir -p build_$ARCH_DIR_NAME + cd build_$ARCH_DIR_NAME + + # DMOD_DMR_PATH is already set in CMakeLists.txt, so this build also + # assembles build_$ARCH_DIR_NAME/packages// for every module + # defined in this repo's CMake config - nothing left to do here but + # discover whatever got packaged, add release notes, and zip it. + cmake .. -DDMOD_TOOLS_NAME=arch/${{ matrix.arch_name }} -DDMOD_MODULE_VERSION="${{ steps.get_version.outputs.version }}" + cmake --build . + + echo "Build completed for ${{ matrix.arch_name }}" + ls -la packages/ + + - name: Add release notes and create release archives + run: | + set -e + BUILD_DIR="build_$ARCH_DIR_NAME" + TAG="${{ github.event.release.tag_name }}" + PACKAGES_DIR="$BUILD_DIR/packages" + + if [ ! -d "$PACKAGES_DIR" ] || [ -z "$(ls -A "$PACKAGES_DIR")" ]; then + echo "Error: no packages found in $PACKAGES_DIR" + exit 1 + fi + + # A repo may define more than one module (e.g. a core + port split, + # or several independent modules) - package every subdirectory + # found under packages/, not just dmtls. + : > package-names.txt + for pkg_dir in "$PACKAGES_DIR"/*/; do + pkg=$(basename "$pkg_dir") + echo "$pkg" >> package-names.txt + + echo "${{ github.event.release.body }}" > "${pkg_dir}RELEASE_NOTES.txt" + (cd "$pkg_dir" && zip -r "${OLDPWD}/${pkg}-${TAG}-${ARCH_DIR_NAME}.zip" .) + done + + echo "Created archives:" + ls -lh *-"${TAG}"-"${ARCH_DIR_NAME}".zip + echo "Packages found:" + cat package-names.txt + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.ARTIFACT_NAME }} + path: | + *.zip + package-names.txt + retention-days: 1 + + generate-versions-manifest: + name: Generate versions.dmm + needs: [build-release] + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history to get all tags + + - name: Download release artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Generate versions.dmm + run: | + set -e + echo "# List of available versions for dmtls modules" > versions.dmm + echo "# Generated automatically by CI" >> versions.dmm + echo "" >> versions.dmm + + # Get all version tags (starting with 'v') and extract version numbers + VERSIONS=$(git tag -l 'v*' | sed 's/^v//' | sort -V | tr '\n' ' ' | sed 's/ $//') + + # Remove vlatest from the list if present + VERSIONS=$(echo $VERSIONS | sed 's/\blatest\b//g' | xargs) + + if [ -z "$VERSIONS" ]; then + echo "Warning: No version tags found" + VERSIONS="${{ github.event.release.tag_name }}" + VERSIONS="${VERSIONS#v}" + fi + + echo "Found versions: $VERSIONS" + + # Union of package names discovered across every architecture build + # (the same set is expected everywhere, but union is safe even if + # some packages only exist for a subset of architectures). + PACKAGES=$(cat artifacts/release-*/package-names.txt | sort -u) + echo "Found packages: $PACKAGES" + + for pkg in $PACKAGES; do + echo "\$version-available $pkg $VERSIONS" >> versions.dmm + done + + echo "Generated versions.dmm:" + cat versions.dmm + + - name: Upload versions.dmm as artifact + uses: actions/upload-artifact@v4 + with: + name: versions-manifest + path: versions.dmm + retention-days: 1 + + upload-release-assets: + name: Upload Release Assets + needs: [build-release, generate-versions-manifest] + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Display artifact structure + run: | + echo "Downloaded artifacts:" + ls -lR artifacts/ + + - name: Upload release assets to versioned tag + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -e + shopt -s nullglob + zip_files=(artifacts/release-*/*.zip) + + if [ ${#zip_files[@]} -eq 0 ]; then + echo "Error: No artifacts found to upload" + exit 1 + fi + + for zip_file in "${zip_files[@]}"; do + echo "Uploading $zip_file to ${{ github.event.release.tag_name }}..." + gh release upload ${{ github.event.release.tag_name }} \ + "$zip_file" \ + --repo ${{ github.repository }} \ + --clobber + done + + if [ -f artifacts/versions-manifest/versions.dmm ]; then + echo "Uploading versions.dmm to ${{ github.event.release.tag_name }}..." + gh release upload ${{ github.event.release.tag_name }} \ + artifacts/versions-manifest/versions.dmm \ + --repo ${{ github.repository }} \ + --clobber + fi + + echo "Successfully uploaded ${#zip_files[@]} artifact(s) to ${{ github.event.release.tag_name }}" + + - name: Create or update latest release + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -e + if gh release view vlatest --repo ${{ github.repository }} >/dev/null 2>&1; then + echo "Release vlatest exists, deleting it..." + gh release delete vlatest --repo ${{ github.repository }} --yes + fi + + echo "Creating vlatest release..." + gh release create vlatest \ + --repo ${{ github.repository }} \ + --title "Latest Release (based on ${{ github.event.release.tag_name }})" \ + --notes "This release always points to the latest stable version. Currently based on ${{ github.event.release.tag_name }}." + + - name: Upload release assets to latest tag + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -e + shopt -s nullglob + zip_files=(artifacts/release-*/*.zip) + + for zip_file in "${zip_files[@]}"; do + echo "Uploading $zip_file to vlatest..." + gh release upload vlatest \ + "$zip_file" \ + --repo ${{ github.repository }} \ + --clobber + done + + if [ -f artifacts/versions-manifest/versions.dmm ]; then + echo "Uploading versions.dmm to vlatest..." + gh release upload vlatest \ + artifacts/versions-manifest/versions.dmm \ + --repo ${{ github.repository }} \ + --clobber + fi + + echo "Successfully uploaded ${#zip_files[@]} artifact(s) to vlatest" diff --git a/.gitignore b/.gitignore index 1f99f9d..362ce0a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,19 @@ -CMakeLists.txt.user -CMakeCache.txt -CMakeFiles -CMakeScripts -Testing -Makefile -cmake_install.cmake -install_manifest.txt -compile_commands.json -CTestTestfile.cmake -_deps -CMakeUserPresets.json +# Build directories +build/ +*.dmf +*.dmfc -# CLion -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#cmake-build-* +# IDE +# .vscode/tasks.json and .vscode/settings.json are committed (see every real +# module repo) so the build tasks are shared with the team - don't ignore +# .vscode/ wholesale. +.idea/ + +# Temporary files +*.tmp +*.swp +*~ + +# Python diagnostics +__pycache__/ +*.pyc diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7017c25 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.associations": { + "dmtls.h": "c" + }, + "C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json", + "C_Cpp.intelliSenseEngine": "default", + "C_Cpp.errorSquiggles": "enabled" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..2a66b75 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,55 @@ +{ + // See https://code.visualstudio.com/docs/editor/tasks for more information + "version": "2.0.0", + "tasks": [ + { + "label": "CMake: Configure", + "type": "shell", + "command": "cmake", + "args": [ + "-S", + "${workspaceFolder}", + "-B", + "${workspaceFolder}/build", + "-DDMOD_DIR=${input:dmodDir}" + ], + "group": "build", + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "CMake: Build", + "type": "shell", + "command": "cmake", + "args": [ + "--build", + "${workspaceFolder}/build" + ], + "group": "build", + "problemMatcher": [] + }, + { + "label": "CMake: Clean", + "type": "shell", + "command": "cmake", + "args": [ + "--build", + "${workspaceFolder}/build", + "--target", + "clean" + ], + "group": "build", + "problemMatcher": [] + } + ], + "inputs": [ + { + "id": "dmodDir", + "description": "Path to a local dmod checkout (leave blank to fetch dmod's develop branch from GitHub instead)", + "default": "${workspaceFolder}/../dmod", + "type": "promptString" + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..934b1df --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,97 @@ +cmake_minimum_required(VERSION 3.18) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +if(NOT DEFINED DMOD_MODULE_VERSION) + set(DMOD_MODULE_VERSION "0.1" CACHE STRING "DMOD module version") +endif() + +# If a local dmod checkout is provided, use it instead of fetching from git +if(DEFINED DMOD_DIR AND NOT "${DMOD_DIR}" STREQUAL "") + set(FETCHCONTENT_SOURCE_DIR_DMOD "${DMOD_DIR}" CACHE PATH "" FORCE) +endif() + +include(FetchContent) +FetchContent_Declare( + dmod + GIT_REPOSITORY https://github.com/choco-technologies/dmod.git + GIT_TAG develop +) + +set(DMOD_MODE "DMOD_MODULE" CACHE STRING "DMOD build mode") +set(DMOD_BUILD_TESTS OFF CACHE BOOL "Build tests") +set(DMOD_BUILD_EXAMPLES OFF CACHE BOOL "Build examples") +set(DMOD_BUILD_TOOLS OFF CACHE BOOL "Build tools") +set(DMOD_BUILD_TEMPLATES OFF CACHE BOOL "Build templates") + +FetchContent_MakeAvailable(dmod) + +project(dmtls VERSION ${DMOD_MODULE_VERSION} + DESCRIPTION "dmtls DMOD library module" + LANGUAGES C CXX) + +set(DMOD_DIR ${dmod_SOURCE_DIR} CACHE PATH "DMOD source directory") +set(DMOD_SCRIPTS_DIR ${DMOD_DIR}/scripts CACHE PATH "DMOD scripts directory") +include(${DMOD_DIR}/paths.cmake) +dmod_setup_external_module() + +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/backend.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/pointer-audit.cmake) + +# Name of the module +set(DMOD_MODULE_NAME dmtls) + +# Author (should be string) +set(DMOD_AUTHOR_NAME "Patryk Kubiak") + +# Stack size for the module (should be integer) +set(DMOD_STACK_SIZE 1024) + +# Path to the .dmr file - enables release package creation in build/packages/ +set(DMOD_DMR_PATH ${CMAKE_CURRENT_SOURCE_DIR}/dmtls.dmr) + +# List of MAL interfaces implemented by this module (leave empty if none) +set(DMOD_MAL_IMPLS ) + +# List of DIF interfaces implemented by this module (leave empty if none) +set(DMOD_DIF_IMPLS ) + +# +# dmod_add_library - create a library module +# it has the same signature as add_library +# and can be used in the same way after the creation +# (for example, to link libraries) +# +dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION} + # List of source files - can include C and C++ files + src/dmtls.c + src/dmtls_platform.c + src/dmtls_config.c + src/dmtls_connection.c + src/dmtls_io.c + ${DMTLS_BACKEND_SOURCES} +) + +target_include_directories(${DMOD_MODULE_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/config + ${DMTLS_BACKEND_INCLUDES} +) + +target_include_directories(${DMOD_MODULE_NAME}_if INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_compile_definitions(${DMOD_MODULE_NAME} PRIVATE + strstr=dmtls_backend_strstr + MBEDTLS_CONFIG_FILE="dmtls_mbedtls_config.h" + TF_PSA_CRYPTO_CONFIG_FILE="dmtls_psa_config.h" +) +# Retain ELF metadata for a build-time audit only. The emitted DMF keeps the +# existing header/GOT format; no relocation table or custom loader is used. +dmtls_audit_module(${DMOD_MODULE_NAME}) +dmod_link_modules(${DMOD_MODULE_NAME} dmosi dmutc) +option(DMTLS_BUILD_HARDWARE_PROBE "Build the STM32 hardware/RNG interoperability diagnostic" OFF) +enable_testing() +add_subdirectory(tests) diff --git a/README.md b/README.md index a2cada2..b8634c3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,71 @@ # dmtls -TLS library + +Transport-independent TLS client library for DMOD, backed by DMOD-adapted forks of Mbed TLS 4.1.1 +and TF-PSA-Crypto 1.1.1. It supports TLS 1.2 and TLS 1.3, certificate chains, +hostname verification and SNI, with ECDHE and RSA/ECDSA authentication. +The backend is compiled into this module; its headers and ABI are private. + +Certificate time comes from **dmutc**. Missing, unset or stopped UTC fails the +handshake. Applications provide an explicit strong-entropy callback, used to +seed/reseed the backend DRBG, and nonblocking byte-stream transport callbacks. +There is no software entropy fallback, implicit device path, system CA store +or option to disable certificate verification. + +## Build + +```sh +cmake -S . -B build/native -DDMOD_DIR=/path/to/dmod +cmake --build build/native --parallel +ctest --test-dir build/native --output-on-failure +``` + +The existing DMOD SDK, tools and GOT loader are sufficient. The module keeps +the original 240-byte DMF header. A read-only ELF audit rejects absolute pointers +outside the sections already handled by DMOD. + +For Cortex-M7 use `-DDMOD_TOOLS_NAME=arch/armv7/cortex-m7` in a separate build +directory. Direct DMOD dependencies (`dmosi`, `dmutc`) are unversioned; the +packager resolves them. The reviewed third-party fork source archives are pinned by commit and +SHA-256 for reproducibility. Its licenses are included in the package. The validated Cortex-M7 build is about +109.9 KiB compressed (137.1 KiB loaded image), plus TLS heap. + +## Use + +1. Enable the board's entropy provider and UTC service. dmutc receives its RTC + source through dmdevfs/libsystemd device rules. +2. Call `dmtls_initialize()` once with the entropy callback, allocation budget + and maximum connection count. The callback/context must remain valid until + shutdown. An application adapter can call `dmrng_fill()`; dmtls itself does + not depend on that hardware driver. +3. Create an immutable configuration from explicit PEM CA certificates or one + DER CA certificate, then create a connection with the expected hostname, + transport callbacks and finite handshake/I/O deadlines. +4. Drive handshake/read/write/close. On WANT_READ/WANT_WRITE, wait for transport + readiness **outside** dmtls, then retry. A partial successful write advances + the caller's buffer; a WANT retry retains the exact pointer, size and bytes. +5. Destroy connections, destroy configurations, then shut down. The caller owns + the underlying transport; dmtls never opens or closes its socket. + +Configurations can be shared by connections. Destroying an in-use configuration +or shutting down with live handles returns BUSY. Engine calls are serialized +with a recursive dmosi mutex; callback reentry is rejected rather than recursed. +Lifecycle operations must be externally serialized with users. + +## Resource and protocol boundaries + +- All TLS allocations, including context metadata, count toward `heap_limit`. + The minimum accepted limit is 32 KiB; realistic handshakes need more. A + configured quota can fail an operation cleanly; it does not promise that + every CA chain fits. Freed TLS allocations are wiped. +- Incoming plaintext records can be 16 KiB; outgoing records are limited to + 4 KiB. This remains compatible with peers that do not negotiate smaller + fragments. Deadlines use monotonic dmosi ticks, with wrap-safe elapsed time. +- TCP EOF without authenticated close_notify is a truncation error. Failed + connections cannot be reused. Destroy remains available after failure. +- Client authentication, TLS servers, DTLS, renegotiation, 0-RTT, session + persistence and automatic revocation fetching are not provided. No certificate + or RNG callback is allowed to silently weaken authentication. +- RTC readiness does not prove that its UTC value is authentic or accurate; + provisioning/synchronization remains an application responsibility. + +See [API reference](docs/api-reference.md) and [validation](docs/testing.md). diff --git a/cmake/backend.cmake b/cmake/backend.cmake new file mode 100644 index 0000000..dbd2553 --- /dev/null +++ b/cmake/backend.cmake @@ -0,0 +1,41 @@ +# One pinned upstream implementation is compiled into dmtls itself. Consumers +# import only dmtls_if; no upstream library ABI or duplicate backend is exposed. +include(FetchContent) +# Reviewed third-party fork revisions, independent of DMOD module versions. +# The fork preserves the original DMF/GOT ABI; no loader extension is needed. +FetchContent_Declare(dmtls_backend + URL https://github.com/choco-technologies/mbedtls/archive/41853d1d2a94a92f97fe70fbc1d5c14e19d79d25.tar.gz + URL_HASH SHA256=f7b170378f6b7e2c84168f36869504dbf67c685fdb7a08cd0cde1a9e8a5aa0f3 +) +FetchContent_Declare(dmtls_crypto_backend + URL https://github.com/choco-technologies/TF-PSA-Crypto/archive/a119c22d541a783aab6f0c5b68047f5d81d1969a.tar.gz + URL_HASH SHA256=af999d687e6ebf287e170e3b3b1fbb3ff0e80cb331a5e5fa88ef452885b56603 +) +foreach(backend IN ITEMS dmtls_backend dmtls_crypto_backend) + FetchContent_GetProperties(${backend}) + if(NOT ${backend}_POPULATED) + FetchContent_Populate(${backend}) + endif() +endforeach() +set(DMTLS_PSA_DIR ${dmtls_crypto_backend_SOURCE_DIR}) +# Fork archives include unchanged generated library sources from the releases. +file(GLOB DMTLS_BACKEND_SOURCES CONFIGURE_DEPENDS + ${dmtls_backend_SOURCE_DIR}/library/*.c + ${DMTLS_PSA_DIR}/core/*.c + ${DMTLS_PSA_DIR}/drivers/builtin/src/*.c + ${DMTLS_PSA_DIR}/extras/*.c + ${DMTLS_PSA_DIR}/platform/*.c + ${DMTLS_PSA_DIR}/utilities/*.c +) +set(DMTLS_BACKEND_INCLUDES + ${dmtls_backend_SOURCE_DIR}/include + ${dmtls_backend_SOURCE_DIR}/library + ${DMTLS_PSA_DIR}/include + ${DMTLS_PSA_DIR}/core + ${DMTLS_PSA_DIR}/dispatch + ${DMTLS_PSA_DIR}/extras + ${DMTLS_PSA_DIR}/platform + ${DMTLS_PSA_DIR}/utilities + ${DMTLS_PSA_DIR}/drivers/builtin/include + ${DMTLS_PSA_DIR}/drivers/builtin/src +) diff --git a/cmake/pointer-audit.cmake b/cmake/pointer-audit.cmake new file mode 100644 index 0000000..3c23b59 --- /dev/null +++ b/cmake/pointer-audit.cmake @@ -0,0 +1,11 @@ +find_package(Python3 REQUIRED COMPONENTS Interpreter) +function(dmtls_audit_module target) + # Metadata is checked at build time and excluded from the binary by objcopy. + target_link_options(${target} PRIVATE -Wl,--emit-relocs,--no-relax) + set_property(TARGET ${target} APPEND PROPERTY LINK_DEPENDS + ${PROJECT_SOURCE_DIR}/scripts/check_dmod_pointers.py) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/check_dmod_pointers.py + $ ${DMOD_DMF_DIR}/${target}.dmf + COMMENT "Checking compatibility with the existing DMOD GOT loader" VERBATIM) +endfunction() diff --git a/config/dmtls_mbedtls_config.h b/config/dmtls_mbedtls_config.h new file mode 100644 index 0000000..1ac61f5 --- /dev/null +++ b/config/dmtls_mbedtls_config.h @@ -0,0 +1,29 @@ +#ifndef DMTLS_MBEDTLS_CONFIG_H +#define DMTLS_MBEDTLS_CONFIG_H +#define MBEDTLS_DMOD_C +#define MBEDTLS_X509_REMOVE_INFO +#define MBEDTLS_CONFIG_VERSION 0x04010100 +/* Use the upstream portable IP-literal parser on every DMOD architecture. */ +#define MBEDTLS_TEST_SW_INET_PTON +#define MBEDTLS_ERROR_C +#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED +#define MBEDTLS_SSL_ALL_ALERT_MESSAGES +#define MBEDTLS_SSL_CLI_C +#define MBEDTLS_SSL_TLS_C +#define MBEDTLS_SSL_PROTO_TLS1_2 +#define MBEDTLS_SSL_PROTO_TLS1_3 +#define MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE +#define MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED +#define MBEDTLS_SSL_SERVER_NAME_INDICATION +#define MBEDTLS_SSL_EXTENDED_MASTER_SECRET +#define MBEDTLS_SSL_MAX_FRAGMENT_LENGTH +#define MBEDTLS_SSL_KEEP_PEER_CERTIFICATE +#define MBEDTLS_X509_USE_C +#define MBEDTLS_X509_CRT_PARSE_C +#define MBEDTLS_X509_RSASSA_PSS_SUPPORT +#define MBEDTLS_SSL_IN_CONTENT_LEN 16384 +#define MBEDTLS_SSL_OUT_CONTENT_LEN 4096 +/* No DTLS, server, renegotiation, early data, persistence or OS sockets. + * Certificate dates are always checked by our per-connection UTC callback. */ +#endif diff --git a/config/dmtls_psa_config.h b/config/dmtls_psa_config.h new file mode 100644 index 0000000..f2b95d5 --- /dev/null +++ b/config/dmtls_psa_config.h @@ -0,0 +1,59 @@ +#ifndef DMTLS_PSA_CONFIG_H +#define DMTLS_PSA_CONFIG_H +#define TF_PSA_CRYPTO_CONFIG_VERSION 0x01010100 +#include "dmtls_platform.h" +#define MBEDTLS_DMOD_C +/* DMOD supplies SAL, not a process/libc ABI, including on x86 hosts. */ +#define MBEDTLS_TEST_PLATFORM_IS_NOT_UNIXLIKE +#define MBEDTLS_PLATFORM_ZEROIZE_ALT +#define MBEDTLS_PLATFORM_C +#define MBEDTLS_PLATFORM_MEMORY +#define MBEDTLS_PLATFORM_NO_STD_FUNCTIONS +#define MBEDTLS_PLATFORM_CALLOC_MACRO dmtls_backend_calloc +#define MBEDTLS_PLATFORM_FREE_MACRO dmtls_backend_free +#define MBEDTLS_PLATFORM_SNPRINTF_MACRO dmtls_backend_snprintf +#define MBEDTLS_PLATFORM_VSNPRINTF_MACRO dmtls_backend_vsnprintf +#define MBEDTLS_PSA_CRYPTO_C +#define MBEDTLS_PSA_DRIVER_GET_ENTROPY +#define MBEDTLS_CTR_DRBG_C +#define MBEDTLS_MD_C +#define MBEDTLS_PK_C +#define MBEDTLS_PK_PARSE_C +#define MBEDTLS_ASN1_PARSE_C +#define MBEDTLS_ASN1_WRITE_C +#define MBEDTLS_BASE64_C +#define MBEDTLS_PEM_PARSE_C +#define MBEDTLS_ECP_NIST_OPTIM +#define MBEDTLS_PSA_KEY_SLOT_COUNT 32 +#define PSA_WANT_ALG_GCM 1 +#define PSA_WANT_ALG_CHACHA20_POLY1305 1 +#define PSA_WANT_ALG_ECDH 1 +#define PSA_WANT_ALG_ECDSA 1 +#define PSA_WANT_ALG_RSA_PKCS1V15_SIGN 1 +#define PSA_WANT_ALG_RSA_PSS 1 +#define PSA_WANT_ALG_SHA_1 1 +#define PSA_WANT_ALG_SHA_256 1 +#define PSA_WANT_ALG_SHA_384 1 +#define PSA_WANT_ALG_SHA_512 1 +#define PSA_WANT_ALG_HKDF 1 +#define PSA_WANT_ALG_HKDF_EXTRACT 1 +#define PSA_WANT_ALG_HKDF_EXPAND 1 +#define PSA_WANT_ALG_HMAC 1 +#define PSA_WANT_ALG_TLS12_PRF 1 +#define PSA_WANT_KEY_TYPE_AES 1 +#define PSA_WANT_KEY_TYPE_CHACHA20 1 +#define PSA_WANT_KEY_TYPE_HMAC 1 +#define PSA_WANT_KEY_TYPE_DERIVE 1 +#define PSA_WANT_KEY_TYPE_RAW_DATA 1 +#define PSA_WANT_KEY_TYPE_RSA_PUBLIC_KEY 1 +#define PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC 1 +#define PSA_WANT_ALG_RSA_OAEP 1 +#define PSA_WANT_KEY_TYPE_ECC_PUBLIC_KEY 1 +#define PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_BASIC 1 +#define PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT 1 +#define PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT 1 +#define PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_GENERATE 1 +#define PSA_WANT_ECC_SECP_R1_256 1 +#define PSA_WANT_ECC_SECP_R1_384 1 +#define PSA_WANT_ECC_MONTGOMERY_255 1 +#endif diff --git a/dmtls.dmr b/dmtls.dmr new file mode 100644 index 0000000..491809e --- /dev/null +++ b/dmtls.dmr @@ -0,0 +1,36 @@ +# DMOD Resource File for dmtls +# This file specifies where resources should be installed + +# === Core Module === +# Main module file - always installed +dmf=./${module}.dmf => ${destination}/${module}.dmf [origin=${dmf_dir}/${module}.dmf] + +# Compressed module +dmfc=./${module}.dmfc => ${destination}/${module}.dmfc [origin=${build_dir}/dmfc/${module}.dmfc] + +# Dependencies file (if exists) +dmd=./${module}.dmd => ${destination}/${module}.dmd [origin=${dmf_dir}/${module}.dmd] + +# Version information +version=./${module}_version.txt => ${destination}/${module}_version.txt [origin=${dmf_dir}/${module}_version.txt] + +# === Documentation === +# Module documentation in markdown format for dmf-man tool +docs=./docs => ${destination}/${module}/docs [origin=${repo_dir}/docs] + +# README file +readme=./README.md => ${destination}/${module}/README.md [origin=${repo_dir}/README.md] + +# === Header Files === +# Include directory with all headers (public header + generated _defs.h) +inc=./include => ${destination}/${module}/include [origin=${repo_dir}/include] [origin=${build_dir}/${module}_defs.h] + +# === License === +# License file +license=./LICENSE => ${destination}/${module}/LICENSE [origin=${repo_dir}/LICENSE] + +# Adding itself +dmr=./${module}.dmr => ${destination}/${module}/${module}.dmr [origin=${repo_dir}/${module}.dmr] + +# Required notices for the crypto backend compiled into this module. +third_party=./third_party => ${destination}/${module}/third_party [origin=${repo_dir}/third_party] diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..991d14f --- /dev/null +++ b/docs/README.md @@ -0,0 +1,7 @@ +# dmtls documentation + +- [API and ownership](api-reference.md) +- [Build and validation evidence](testing.md) + +The backend is private. Applications use dmtls, dmutc and transport/entropy +adapters rather than Mbed TLS data structures. diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..5751485 --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,75 @@ +# dmtls API + +Include `dmtls.h`. All functions use stable `dmtls_result_t` values rather than +backend error numbers. Output handles/counts are cleared before failure. +Opaque handles remain valid until explicitly destroyed; handles absent from the live registry +are rejected by registry membership without dereferencing the supplied pointer. +Concurrent destruction/use and module enable/disable require caller coordination. + +## Lifecycle and ownership + +`initialize(platform)` requires a full-buffer strong-entropy callback, its +context, a heap limit of at least 32768 bytes and a nonzero connection limit. +The callback is used by a CTR-DRBG. An error during entropy collection fails +closed; no host device, deterministic seed or weak fallback is substituted. +Initialization while already initialized returns BUSY. + +`shutdown()` releases PSA crypto state, but returns BUSY while a configuration +or connection exists. After shutdown, initialize can be called again. The +module cannot deinitialize while it has live objects. + +`config_create(ca, size, &config)` copies and parses an explicit CA PEM bundle +(NUL optional) or one DER certificate. Empty, malformed and partially invalid +bundles are rejected. Input is bounded to 256 KiB and the global allocation +budget still applies. Peer verification is always REQUIRED; the configuration +is immutable. `config_destroy(config)` returns BUSY while referenced. + +`connection_create(config, hostname, transport, handshake_ms, io_ms, &connection)` +creates a client session. Hostname is copied and used for verification and SNI; +empty/oversized or malformed names are rejected. DNS hostnames and IPv4 literals +are accepted; colon-bearing IPv6 literals are not exposed by this interface. +Both deadlines must be in 1..INT32_MAX milliseconds. Handshake time starts at +creation; an I/O deadline spans all WANT retries of one logical operation. + +`connection_destroy(connection)` frees TLS state after success or failure; +it does not invoke transport close or implicitly perform a TLS handshake. +Caller callback functions and their context must remain valid until destruction. + +## Nonblocking operations + +`handshake(connection)` checks dmutc readiness on every step, verifies chain, +hostname and certificate validity periods, then transitions NEW/HANDSHAKE to +OPEN. Dates are checked for each verified certificate, including the trust +anchor, and the retained peer chain is rechecked at completion. UTC failure +produces ERR_TIME; verification failure produces ERR_CERTIFICATE. No application +data is released before successful authentication. + +`read(connection, buffer, capacity, &received)` and +`write(connection, buffer, size, &sent)` return OK on positive progress, +WANT_READ/WANT_WRITE when the transport must become ready, or an error. +A write can succeed partially. After WANT, repeat the same logical operation; +write pointer/size/bytes must remain unchanged. Switching operation while one +is pending returns BUSY. Read may use another buffer on retry. Zero-sized +application reads/writes are rejected. Deadline expiry fails the connection. + +`close(connection)` sends close_notify and can also require WANT retries. It +is idempotent after success. PEER_CLOSED indicates an authenticated peer +close_notify; a bare TCP close produces ERR_TRUNCATED. Close is not a wait for +TCP shutdown or confirmation of application-level delivery. + +`status(connection, &status)` returns state, persistent error and stable +verification bits: UNTRUSTED, HOSTNAME, EXPIRED, FUTURE, OTHER. Flags provide +failure diagnostics; they never authorize overriding verification. + +## Transport contract + +The send/receive callbacks must not block or reenter dmtls. Set the count to +zero unless returning OK. Send OK requires a count in 1..requested; receive +OK permits zero only to indicate transport EOF. WANT reports no consumed bytes. +An impossible count or callback error fails the connection as ERR_TRANSPORT. +TLS never calls blocking OS networking or sleeps while waiting for bytes. +Cryptographic processing itself is synchronous within a library call. + +The recursive global lock serializes access to PSA globals and the allocator. +Reentry from a callback returns BUSY; callers on other tasks wait for the current +step. This provides thread safety without enabling an OS-specific crypto ABI. diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000..8f3c376 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,93 @@ +# Build and validation + +Supported build profiles are x86_64, Cortex-M4 and Cortex-M7. The existing +DMOD SDK, host tools and runtime are used without format changes. DMFs retain +the original 240-byte header. The backend forks use typed DMOD_GLOBAL_POINTER +slots for callbacks/coordinates and inline immutable descriptor text. + +Final ELF metadata is audited at build time; it is never used to rewrite a DMF +or generate a runtime relocation table. Absolute pointers outside existing +header/API/GOT relocation sections fail the build. The audit currently reads +ARM ELF32 and x86-64 ELF64; it rejects unsupported ELF architectures explicitly. + +## Native + +```sh +cmake -S . -B build/native -DDMOD_DIR=/path/to/dmod +cmake --build build/native --parallel +ctest --test-dir build/native --output-on-failure +``` + +Three CTest entries cover different boundaries: + +- `test_dmtls`: three checks in the actual DMOD module loaded by dmod_loader, + including crypto initialization, the entropy callback, CA parsing, ownership + and shutdown. The deterministic entropy in this fixture is for tests only; + that test never opens a connection or protects application data. +- `tls_interoperability`: 18 checks compile the production engine and backend + into a host test library with controllable SAL/UTC/timer providers. An + independent OpenSSL peer exercises TLS 1.2/1.3, RSA/ECDSA certificates, + hostname/CA/date failures, missing UTC, entropy failure, short I/O and WANT + retries, write retry identity, truncation versus close_notify, deadlines, + callback reentry, connection limits, malformed CA bundles and allocation + quotas. Each test checks final TLS allocation cleanup. + +- `dmtls_pointer_audit`: three regression cases prove that GOT pointers are + accepted, a pointer embedded in a mixed static structure is rejected, and + missing relocation metadata cannot silently disable the check. + +The public test CA contains no private key. Interoperability tests generate +private keys in temporary directories; production trust stores are always +supplied explicitly by the caller. + +## STM32F746G-DISCO + +Build against the existing SDK with Cortex-M7 tools and the board CPU family: + +```sh +cmake -S . -B build/cortex-m7 -DDMOD_DIR=/path/to/dmod \ + -DDMOD_TOOLS_NAME=arch/armv7/cortex-m7 -DDMOD_CPU_FAMILY=stm32f7 \ + -DDMTLS_BUILD_HARDWARE_PROBE=ON +cmake --build build/cortex-m7 --parallel +python3 tests/probe/make_fixtures.py build/board-fixtures +``` + +Install local `dmtls`, `test_dmtls` and `tlsprobe` packages into a dmod-boot test +image with hardware RNG, RTC, the rule-driven UTC service, and `rtc`, +`test_dmutc`, `test_dmrtc`, `test_dmrng` diagnostics. Use the local packages +manifest to select these artifacts. Flash the board, wait for its console, +then upload **only** ca.pem and other.pem to `/` using the boot repository's +`scripts/ymodem_send.py`. Keep every private key on the host. + +```sh +python3 tests/probe/run_board.py --boot-dir /path/to/dmod-boot \ + --fixtures build/board-fixtures --port /dev/ttyACM0 +``` + +The opt-in probe uses real dmtls, hardware RNG and rule-selected dmutc. Its +nonblocking callbacks exchange bytes through memory queues. UART waits happen +in the diagnostic application, outside TLS. It claims terminal foreground while +running; its input handle is released at process exit. Python OpenSSL MemoryBIO +provides the independent peer. This validates TLS on hardware; it does not +exercise dmtcp, HTTPS or external Internet connectivity. + +Hardware validation completed with seven consecutive probes: TLS 1.2 and TLS +1.3 encrypted echo/close_notify, rejected hostname, untrusted CA, future and +expired certificates, and missing UTC. The harness then passed UTC 6/6, RTC +12/12 and RNG 6/6 checks, restored host UTC and confirmed dmutc@rtc running. +The real module's three lifecycle checks also passed on the board. Renode was +omitted as requested for this change. + +Cortex-M7 artifacts measured 140388 bytes DMF and 112574 bytes DMFC (about +137.1 and 109.9 KiB). The compressed image is storage size; loading and TLS +heap need additional RAM. The diagnostic uses a 512 KiB heap quota and one +connection; 32 KiB is only the minimum accepted quota, not a handshake budget. +Raw board logs and generated fixture keys remain local under ignored build +paths, outside release resources. + +Cortex-M4 was built from fresh downloads of both fork archives, verified by +SHA-256. Native tests ran with the existing installed dmod_loader/todmd tools. +Hardware firmware used the original DMOD loader revision, with no relocation +extension. The independent process/FreeRTOS lifetime fixes remain in the boot +integration. Fork changes: [Mbed TLS](https://github.com/choco-technologies/mbedtls/pull/1) +and [TF-PSA-Crypto](https://github.com/choco-technologies/TF-PSA-Crypto/pull/1). diff --git a/include/dmtls.h b/include/dmtls.h new file mode 100644 index 0000000..f30eb27 --- /dev/null +++ b/include/dmtls.h @@ -0,0 +1,75 @@ +#ifndef DMTLS_H +#define DMTLS_H +#include +#include +#include "dmtls_defs.h" +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct dmtls_config* dmtls_config_t; +typedef struct dmtls_connection* dmtls_connection_t; +typedef enum { + DMTLS_OK = 0, DMTLS_WANT_READ = 1, DMTLS_WANT_WRITE = 2, + DMTLS_PEER_CLOSED = 3, + DMTLS_ERR_ARGUMENT = -1, DMTLS_ERR_MEMORY = -2, DMTLS_ERR_STATE = -3, + DMTLS_ERR_ENTROPY = -4, DMTLS_ERR_TIME = -5, DMTLS_ERR_CERTIFICATE = -6, + DMTLS_ERR_PROTOCOL = -7, DMTLS_ERR_TRANSPORT = -8, DMTLS_ERR_BUSY = -9, + DMTLS_ERR_LIMIT = -10, DMTLS_ERR_TRUNCATED = -11, DMTLS_ERR_TIMEOUT = -12 +} dmtls_result_t; +typedef enum { + DMTLS_STATE_NEW, DMTLS_STATE_HANDSHAKE, DMTLS_STATE_OPEN, + DMTLS_STATE_PEER_CLOSED, DMTLS_STATE_CLOSING, DMTLS_STATE_CLOSED, + DMTLS_STATE_FAILED +} dmtls_state_t; +/* Entropy callback fills the whole buffer with strong entropy or returns error. + * It feeds the backend's DRBG, not TLS directly. No fallback is provided. + * UTC is read from dmutc. Deadlines use the dmosi monotonic tick counter. */ +typedef int (*dmtls_entropy_fn)(void*, uint8_t*, size_t); +typedef struct { + dmtls_entropy_fn entropy; + void* user; + size_t heap_limit; /* Total TLS allocation budget, including context metadata. */ + uint32_t max_connections; +} dmtls_platform_t; +/* Transport callbacks must never block or reenter dmtls. OK reports a positive + * byte count, receive OK/0 reports TCP EOF, WANT_* reports no progress. */ +typedef dmtls_result_t (*dmtls_send_fn)(void*, const uint8_t*, size_t, size_t*); +typedef dmtls_result_t (*dmtls_recv_fn)(void*, uint8_t*, size_t, size_t*); +typedef struct { dmtls_send_fn send; dmtls_recv_fn receive; void* user; } dmtls_transport_t; +typedef struct { + dmtls_state_t state; + dmtls_result_t error; + uint32_t verification_flags; /* Stable DMTLS_VERIFY_* bits, not backend ABI. */ +} dmtls_status_t; +#define DMTLS_VERIFY_UNTRUSTED 1u +#define DMTLS_VERIFY_HOSTNAME 2u +#define DMTLS_VERIFY_EXPIRED 4u +#define DMTLS_VERIFY_FUTURE 8u +#define DMTLS_VERIFY_OTHER 16u +/* Platform callbacks and user data remain valid until shutdown. Initialize and + * shutdown are lifecycle operations, externally serialized against all users. */ +dmod_dmtls_api(1.0, dmtls_result_t, _initialize, (const dmtls_platform_t* platform)); +dmod_dmtls_api(1.0, dmtls_result_t, _shutdown, (void)); +/* CA bytes are PEM (NUL not required) or one DER certificate. They are copied. + * Configuration is immutable and shared; destroy returns BUSY while in use. */ +dmod_dmtls_api(1.0, dmtls_result_t, _config_create, (const void* ca, size_t size, dmtls_config_t* output)); +dmod_dmtls_api(1.0, dmtls_result_t, _config_destroy, (dmtls_config_t config)); +/* Hostname is mandatory, copied, and used for both SNI and peer verification. + * Transport is borrowed: dmtls never opens or closes the underlying socket. + * Handshake deadline starts at create; io timeout applies across WANT retries. */ +dmod_dmtls_api(1.0, dmtls_result_t, _connection_create, + (dmtls_config_t config, const char* hostname, const dmtls_transport_t* transport, + uint32_t handshake_timeout_ms, uint32_t io_timeout_ms, dmtls_connection_t* output)); +dmod_dmtls_api(1.0, dmtls_result_t, _connection_destroy, (dmtls_connection_t connection)); +dmod_dmtls_api(1.0, dmtls_result_t, _handshake, (dmtls_connection_t connection)); +/* Nonblocking operations. Retry writes with the identical bytes/length after + * WANT_*; the caller retains the buffer until write succeeds or fails. */ +dmod_dmtls_api(1.0, dmtls_result_t, _read, (dmtls_connection_t connection, void* buffer, size_t capacity, size_t* received)); +dmod_dmtls_api(1.0, dmtls_result_t, _write, (dmtls_connection_t connection, const void* buffer, size_t size, size_t* sent)); +dmod_dmtls_api(1.0, dmtls_result_t, _close, (dmtls_connection_t connection)); +dmod_dmtls_api(1.0, dmtls_result_t, _status, (dmtls_connection_t connection, dmtls_status_t* status)); +#ifdef __cplusplus +} +#endif +#endif diff --git a/manifest.dmm b/manifest.dmm new file mode 100644 index 0000000..10c15c9 --- /dev/null +++ b/manifest.dmm @@ -0,0 +1,5 @@ +# Include dynamically generated versions list from latest release +$include https://github.com/choco-technologies/dmtls/releases/download/vlatest/versions.dmm + +# Module entry with version placeholder - expanded by $version-available +dmtls https://github.com/choco-technologies/dmtls/releases/download/v/dmtls-v-.zip diff --git a/scripts/check_dmod_pointers.py b/scripts/check_dmod_pointers.py new file mode 100644 index 0000000..6a8603a --- /dev/null +++ b/scripts/check_dmod_pointers.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Reject absolute pointers outside legacy DMOD relocation sections. + +Read-only ELF audit: it never modifies ELF/DMF or emits runtime relocation data. +The fork must use DMOD_GLOBAL_POINTER or eliminate stored absolute pointers. +""" +import struct +import sys +from pathlib import Path + + +def check(elf, dmf): + if len(elf)<64 or elf[:4]!=b'\x7fELF' or elf[5]!=1: + raise ValueError('expected little-endian ELF') + wide=elf[4]==2 + machine=struct.unpack_from('=count: + raise ValueError('invalid ELF section table') + sections=[struct.unpack_from(fmt,elf,shoff+i*stride) for i in range(count)] + ns=sections[names_index];names=elf[ns[4]:ns[4]+ns[5]] + def name(section): + start=section[0];return names[start:names.index(b'\0',start)].decode('ascii') + allowed={'.header','.inputs','.outputs','.got'} + absolute=(1,10,11) if wide else (2,5,6,8,38,43,44,47,48) + relative=(0,2,4,9,24,25,26,27,28,29,41,42) if wide else (0,3,10,24,25,26,28,29,30,42,51,96,102,103) + records=0;got=0;failures=[] + for section in sections: + if section[1]!=(4 if wide else 9):continue + target=sections[section[7]] + if not target[2]&2:continue + symbol_table=sections[section[6]] + expected=24 if wide else 8 + if section[9]!=expected:raise ValueError('invalid relocation record size') + for pos in range(section[4],section[4]+section[5],expected): + offset,info=struct.unpack_from('>32) if wide else (info&255,info>>8) + records+=1 + if kind in absolute: + symbol_index=struct.unpack_from('&2 +} + +print_success() { + echo -e "${GREEN}$1${NC}" +} + +print_help() { + cat << EOF +Sync Claude Code skills from dmod + +Usage: + ./scripts/sync-claude.sh [--dmod-dir PATH] [--ref REF] + +Options: + --dmod-dir PATH Use a local dmod checkout instead of cloning from GitHub + --ref REF Git branch/tag to clone when no --dmod-dir is given (default: develop) + --help Show this help message +EOF +} + +while [[ $# -gt 0 ]]; do + case $1 in + --dmod-dir) + DMOD_DIR="$2" + shift 2 + ;; + --ref) + GIT_REF="$2" + shift 2 + ;; + --help) + print_help + exit 0 + ;; + *) + print_error "Unknown parameter: $1" + print_help + exit 1 + ;; + esac +done + +TMP_CLONE_DIR="" + +cleanup() { + if [[ -n "${TMP_CLONE_DIR}" && -d "${TMP_CLONE_DIR}" ]]; then + rm -rf "${TMP_CLONE_DIR}" + fi +} +trap cleanup EXIT + +if [[ -n "${DMOD_DIR}" ]]; then + if [[ ! -d "${DMOD_DIR}" ]]; then + print_error "--dmod-dir does not exist: ${DMOD_DIR}" + exit 1 + fi + SOURCE_SKILLS_DIR="${DMOD_DIR}/.claude/skills" + echo "Using local dmod checkout: ${DMOD_DIR}" +else + TMP_CLONE_DIR="$(mktemp -d)" + echo "Cloning dmod (branch: ${GIT_REF}) ..." + git clone --depth 1 --branch "${GIT_REF}" "${DMOD_REPO_URL}" "${TMP_CLONE_DIR}" > /dev/null 2>&1 + SOURCE_SKILLS_DIR="${TMP_CLONE_DIR}/.claude/skills" +fi + +if [[ ! -d "${SOURCE_SKILLS_DIR}" ]]; then + print_error "No .claude/skills directory found at ${SOURCE_SKILLS_DIR}" + exit 1 +fi + +mkdir -p .claude/skills +cp -r "${SOURCE_SKILLS_DIR}/." .claude/skills/ + +echo "" +print_success "Synced Claude Code skills:" +for skill_dir in .claude/skills/*/; do + [[ -d "${skill_dir}" ]] || continue + echo " - $(basename "${skill_dir}")" +done +echo "" +echo "Review the changes with 'git status'/'git diff' and commit them like any other file." diff --git a/src/dmtls.c b/src/dmtls.c new file mode 100644 index 0000000..59065fb --- /dev/null +++ b/src/dmtls.c @@ -0,0 +1,79 @@ +#define DMOD_ENABLE_REGISTRATION ON +#include "dmtls_internal.h" + +int dmod_init(const Dmod_Config_t* config) {(void)config; return dmtls_module_init();} +int dmod_deinit(void) {return dmtls_module_deinit();} + +dmod_dmtls_api_declaration(1.0, dmtls_result_t, _initialize, (const dmtls_platform_t* platform)) +{return dmtls_platform_initialize(platform);} +dmod_dmtls_api_declaration(1.0, dmtls_result_t, _shutdown, (void)) +{return dmtls_platform_shutdown();} + +dmod_dmtls_api_declaration(1.0, dmtls_result_t, _config_create, (const void* ca, size_t size, dmtls_config_t* output)) +{ + if (output == NULL) return DMTLS_ERR_ARGUMENT; + *output = NULL; + dmtls_result_t rc = dmtls_enter(); if (rc != DMTLS_OK) return rc; + rc = dmtls_config_new(ca, size, output); dmtls_leave(); return rc; +} +dmod_dmtls_api_declaration(1.0, dmtls_result_t, _config_destroy, (dmtls_config_t config)) +{ + dmtls_result_t rc = dmtls_enter(); if (rc != DMTLS_OK) return rc; + rc = dmtls_config_free(config); dmtls_leave(); return rc; +} +dmod_dmtls_api_declaration(1.0, dmtls_result_t, _connection_create, + (dmtls_config_t config, const char* hostname, const dmtls_transport_t* transport, + uint32_t handshake_timeout_ms, uint32_t io_timeout_ms, dmtls_connection_t* output)) +{ + if (output == NULL) return DMTLS_ERR_ARGUMENT; + *output = NULL; + dmtls_result_t rc = dmtls_enter(); if (rc != DMTLS_OK) return rc; + rc = dmtls_connection_new(config, hostname, transport, handshake_timeout_ms, io_timeout_ms, output); + dmtls_leave(); return rc; +} +dmod_dmtls_api_declaration(1.0, dmtls_result_t, _connection_destroy, (dmtls_connection_t connection)) +{ + dmtls_result_t rc = dmtls_enter(); if (rc != DMTLS_OK) return rc; + if (!dmtls_valid_connection(connection)) rc = DMTLS_ERR_ARGUMENT; + else dmtls_connection_free(connection); + dmtls_leave(); return rc; +} +dmod_dmtls_api_declaration(1.0, dmtls_result_t, _handshake, (dmtls_connection_t connection)) +{ + dmtls_result_t rc = dmtls_enter(); if (rc != DMTLS_OK) return rc; + rc = dmtls_valid_connection(connection) ? dmtls_do_handshake(connection) : DMTLS_ERR_ARGUMENT; + dmtls_leave(); return rc; +} +dmod_dmtls_api_declaration(1.0, dmtls_result_t, _read, (dmtls_connection_t connection, void* buffer, size_t capacity, size_t* received)) +{ + if (received == NULL) return DMTLS_ERR_ARGUMENT; + *received = 0; + if (buffer == NULL || capacity == 0) return DMTLS_ERR_ARGUMENT; + dmtls_result_t rc = dmtls_enter(); if (rc != DMTLS_OK) return rc; + rc = dmtls_valid_connection(connection) ? dmtls_do_read(connection, buffer, capacity, received) : DMTLS_ERR_ARGUMENT; + dmtls_leave(); return rc; +} +dmod_dmtls_api_declaration(1.0, dmtls_result_t, _write, (dmtls_connection_t connection, const void* buffer, size_t size, size_t* sent)) +{ + if (sent == NULL) return DMTLS_ERR_ARGUMENT; + *sent = 0; + if (buffer == NULL || size == 0) return DMTLS_ERR_ARGUMENT; + dmtls_result_t rc = dmtls_enter(); if (rc != DMTLS_OK) return rc; + rc = dmtls_valid_connection(connection) ? dmtls_do_write(connection, buffer, size, sent) : DMTLS_ERR_ARGUMENT; + dmtls_leave(); return rc; +} +dmod_dmtls_api_declaration(1.0, dmtls_result_t, _close, (dmtls_connection_t connection)) +{ + dmtls_result_t rc = dmtls_enter(); if (rc != DMTLS_OK) return rc; + rc = dmtls_valid_connection(connection) ? dmtls_do_close(connection) : DMTLS_ERR_ARGUMENT; + dmtls_leave(); return rc; +} +dmod_dmtls_api_declaration(1.0, dmtls_result_t, _status, (dmtls_connection_t connection, dmtls_status_t* status)) +{ + if (status == NULL) return DMTLS_ERR_ARGUMENT; + *status = (dmtls_status_t){0}; + dmtls_result_t rc = dmtls_enter(); if (rc != DMTLS_OK) return rc; + if (!dmtls_valid_connection(connection)) rc = DMTLS_ERR_ARGUMENT; + else *status = connection->status; + dmtls_leave(); return rc; +} diff --git a/src/dmtls_config.c b/src/dmtls_config.c new file mode 100644 index 0000000..3a06fe8 --- /dev/null +++ b/src/dmtls_config.c @@ -0,0 +1,39 @@ +#include "dmtls_internal.h" +#include "dmtls_platform.h" +#include + +dmtls_result_t dmtls_config_new(const void* ca, size_t size, dmtls_config_t* output) +{ + if (ca == NULL || size == 0 || size > DMTLS_MAX_CA_SIZE) return DMTLS_ERR_ARGUMENT; + dmtls_config_t c = dmtls_backend_calloc(1, sizeof(*c)); + if (c == NULL) return DMTLS_ERR_MEMORY; + mbedtls_ssl_config_init(&c->backend); mbedtls_x509_crt_init(&c->ca); + unsigned char* bytes = dmtls_backend_calloc(1, size + 1); + if (bytes == NULL) {dmtls_backend_free(c); return DMTLS_ERR_MEMORY;} + memcpy(bytes, ca, size); + bool pem = size >= 10 && memcmp(bytes, "-----BEGIN", 10) == 0; + int rc = mbedtls_x509_crt_parse(&c->ca, bytes, pem ? size + (bytes[size - 1] != 0) : size); + dmtls_backend_free(bytes); + if (rc != 0 || c->ca.raw.p == NULL) { + mbedtls_x509_crt_free(&c->ca); dmtls_backend_free(c); return DMTLS_ERR_CERTIFICATE; + } + rc = mbedtls_ssl_config_defaults(&c->backend, MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); + if (rc != 0) {mbedtls_x509_crt_free(&c->ca); mbedtls_ssl_config_free(&c->backend); dmtls_backend_free(c); return DMTLS_ERR_MEMORY;} + mbedtls_ssl_conf_authmode(&c->backend, MBEDTLS_SSL_VERIFY_REQUIRED); + mbedtls_ssl_conf_ca_chain(&c->backend, &c->ca, NULL); + mbedtls_ssl_conf_min_tls_version(&c->backend, MBEDTLS_SSL_VERSION_TLS1_2); + mbedtls_ssl_conf_max_tls_version(&c->backend, MBEDTLS_SSL_VERSION_TLS1_3); + c->magic = DMTLS_CONFIG_MAGIC; c->next = dmtls_config_list; dmtls_config_list = c; + ++dmtls_configs; *output = c; return DMTLS_OK; +} +dmtls_result_t dmtls_config_free(dmtls_config_t c) +{ + if (!dmtls_valid_config(c)) return DMTLS_ERR_ARGUMENT; + if (c->references != 0) return DMTLS_ERR_BUSY; + dmtls_config_t* slot = &dmtls_config_list; + while (*slot != c) slot = &(*slot)->next; + *slot = c->next; + mbedtls_ssl_config_free(&c->backend); mbedtls_x509_crt_free(&c->ca); + dmtls_backend_free(c); --dmtls_configs; return DMTLS_OK; +} diff --git a/src/dmtls_connection.c b/src/dmtls_connection.c new file mode 100644 index 0000000..fc5e9a1 --- /dev/null +++ b/src/dmtls_connection.c @@ -0,0 +1,120 @@ +#include "dmtls_internal.h" +#include "dmtls_platform.h" +#include +#include + +static int compare_time(const dmutc_calendar_t* a, const mbedtls_x509_time* b) +{ + const int x[] = {a->year,a->month,a->day,a->hour,a->minute,a->second}; + const int y[] = {b->year,b->mon,b->day,b->hour,b->min,b->sec}; + for (unsigned i=0;i<6;++i) if (x[i] != y[i]) return x[i] < y[i] ? -1 : 1; + return 0; +} +uint32_t dmtls_verify_bits(uint32_t flags) +{ + uint32_t bits = 0; + if (flags & MBEDTLS_X509_BADCERT_NOT_TRUSTED) bits |= DMTLS_VERIFY_UNTRUSTED; + if (flags & MBEDTLS_X509_BADCERT_CN_MISMATCH) bits |= DMTLS_VERIFY_HOSTNAME; + if (flags & MBEDTLS_X509_BADCERT_EXPIRED) bits |= DMTLS_VERIFY_EXPIRED; + if (flags & MBEDTLS_X509_BADCERT_FUTURE) bits |= DMTLS_VERIFY_FUTURE; + if (flags & ~(MBEDTLS_X509_BADCERT_NOT_TRUSTED | MBEDTLS_X509_BADCERT_CN_MISMATCH | + MBEDTLS_X509_BADCERT_EXPIRED | MBEDTLS_X509_BADCERT_FUTURE)) bits |= DMTLS_VERIFY_OTHER; + return bits; +} +static int verify(void* user, mbedtls_x509_crt* cert, int depth, uint32_t* flags) +{ + (void)depth; + dmtls_connection_t c = user; + if (compare_time(&c->utc, &cert->valid_from) < 0) *flags |= MBEDTLS_X509_BADCERT_FUTURE; + if (compare_time(&c->utc, &cert->valid_to) > 0) *flags |= MBEDTLS_X509_BADCERT_EXPIRED; + c->status.verification_flags |= dmtls_verify_bits(*flags); + return 0; /* Never clears or suppresses backend verification flags. */ +} +static int send_bytes(void* user, const unsigned char* data, size_t size) +{ + dmtls_connection_t c = user; size_t sent = 0; + dmtls_result_t rc = c->transport.send(c->transport.user, data, size, &sent); + if (rc == DMTLS_WANT_READ || rc == DMTLS_WANT_WRITE) { + if (sent != 0) goto error; + return rc == DMTLS_WANT_READ ? MBEDTLS_ERR_SSL_WANT_READ : MBEDTLS_ERR_SSL_WANT_WRITE; + } + if (rc != DMTLS_OK || sent == 0 || sent > size || sent > INT_MAX) goto error; + return (int)sent; +error: + c->callback_error = DMTLS_ERR_TRANSPORT; return MBEDTLS_ERR_SSL_INTERNAL_ERROR; +} +static int receive_bytes(void* user, unsigned char* data, size_t size) +{ + dmtls_connection_t c = user; size_t received = 0; + dmtls_result_t rc = c->transport.receive(c->transport.user, data, size, &received); + if (rc == DMTLS_WANT_READ || rc == DMTLS_WANT_WRITE) { + if (received != 0) goto error; + return rc == DMTLS_WANT_READ ? MBEDTLS_ERR_SSL_WANT_READ : MBEDTLS_ERR_SSL_WANT_WRITE; + } + if (rc != DMTLS_OK || received > size || received > INT_MAX) goto error; + if (received == 0) c->callback_error = DMTLS_ERR_TRUNCATED; + return (int)received; +error: + c->callback_error = DMTLS_ERR_TRANSPORT; return MBEDTLS_ERR_SSL_INTERNAL_ERROR; +} +dmtls_result_t dmtls_connection_new(dmtls_config_t config, const char* host, + const dmtls_transport_t* transport, uint32_t handshake_ms, uint32_t io_ms, dmtls_connection_t* output) +{ + if (!dmtls_valid_config(config) || host == NULL || transport == NULL || + transport->send == NULL || transport->receive == NULL || handshake_ms == 0 || + io_ms == 0 || handshake_ms > INT32_MAX || io_ms > INT32_MAX) return DMTLS_ERR_ARGUMENT; + size_t length = 0; + while (host[length] != 0 && length <= 253) { + unsigned char ch = (unsigned char)host[length++]; + if (ch <= 32 || ch >= 127 || ch == '/' || ch == '\\' || ch == ':') return DMTLS_ERR_ARGUMENT; + } + if (length == 0 || length > 253) return DMTLS_ERR_ARGUMENT; + if (dmtls_connections >= dmtls_platform.max_connections || config->references == UINT32_MAX) return DMTLS_ERR_LIMIT; + dmtls_connection_t c = dmtls_backend_calloc(1, sizeof(*c)); + if (c == NULL) return DMTLS_ERR_MEMORY; + c->config = config; c->transport = *transport; + c->handshake_timeout = handshake_ms; c->io_timeout = io_ms; + c->handshake_started = dmosi_get_tick_count(); + mbedtls_ssl_init(&c->backend); + int rc = mbedtls_ssl_setup(&c->backend, &config->backend); + if (rc == 0) rc = mbedtls_ssl_set_hostname(&c->backend, host); + if (rc != 0) {mbedtls_ssl_free(&c->backend); dmtls_backend_free(c); return DMTLS_ERR_MEMORY;} + mbedtls_ssl_set_verify(&c->backend, verify, c); + mbedtls_ssl_set_bio(&c->backend, c, send_bytes, receive_bytes, NULL); + c->magic = DMTLS_CONNECTION_MAGIC; c->status.state = DMTLS_STATE_NEW; + ++config->references; ++dmtls_connections; + c->next = dmtls_connection_list; dmtls_connection_list = c; + *output = c; return DMTLS_OK; +} +void dmtls_connection_free(dmtls_connection_t c) +{ + dmtls_connection_t* slot = &dmtls_connection_list; + while (*slot != c) slot = &(*slot)->next; + *slot = c->next; --c->config->references; --dmtls_connections; + mbedtls_ssl_free(&c->backend); dmtls_backend_free(c); +} +dmtls_result_t dmtls_do_handshake(dmtls_connection_t c) +{ + if (c->status.state == DMTLS_STATE_FAILED) return c->status.error; + if (c->status.state == DMTLS_STATE_OPEN) return DMTLS_OK; + if (c->status.state != DMTLS_STATE_NEW && c->status.state != DMTLS_STATE_HANDSHAKE) return DMTLS_ERR_STATE; + if ((uint32_t)(dmosi_get_tick_count() - c->handshake_started) >= c->handshake_timeout) + return dmtls_fail(c, DMTLS_ERR_TIMEOUT); + if (dmtls_refresh_utc(c) != DMTLS_OK) return dmtls_fail(c, DMTLS_ERR_TIME); + c->status.state = DMTLS_STATE_HANDSHAKE; + c->callback_error = DMTLS_OK; + int rc = mbedtls_ssl_handshake(&c->backend); + if ((uint32_t)(dmosi_get_tick_count() - c->handshake_started) >= c->handshake_timeout) + return dmtls_fail(c, DMTLS_ERR_TIMEOUT); + if (rc != 0) return dmtls_backend_result(c, rc); + /* Recheck dates at completion as a handshake can span UTC adjustments. */ + const mbedtls_x509_crt* cert = mbedtls_ssl_get_peer_cert(&c->backend); + if (cert == NULL) return dmtls_fail(c, DMTLS_ERR_CERTIFICATE); + for (; cert != NULL; cert = cert->next) { + uint32_t flags = 0; verify(c, (mbedtls_x509_crt*)cert, 0, &flags); + if (flags != 0) return dmtls_fail(c, DMTLS_ERR_CERTIFICATE); + } + c->status.verification_flags |= dmtls_verify_bits(mbedtls_ssl_get_verify_result(&c->backend)); + if (c->status.verification_flags != 0) return dmtls_fail(c, DMTLS_ERR_CERTIFICATE); + c->status.state = DMTLS_STATE_OPEN; return DMTLS_OK; +} diff --git a/src/dmtls_internal.h b/src/dmtls_internal.h new file mode 100644 index 0000000..8f4658a --- /dev/null +++ b/src/dmtls_internal.h @@ -0,0 +1,68 @@ +#ifndef DMTLS_INTERNAL_H +#define DMTLS_INTERNAL_H +#include "dmod.h" +#include "dmtls.h" +#include "dmosi.h" +#include "dmutc.h" +#include "mbedtls/ssl.h" +#include "mbedtls/x509_crt.h" +#include "psa/crypto.h" +#include "psa/crypto_extra.h" +#include +#define DMTLS_CONFIG_MAGIC 0x544C5343u +#define DMTLS_CONNECTION_MAGIC 0x544C534Eu +#define DMTLS_MAX_CA_SIZE (256u * 1024u) +struct dmtls_config { + struct dmtls_config* next; + uint32_t magic; + uint32_t references; + mbedtls_ssl_config backend; + mbedtls_x509_crt ca; +}; +struct dmtls_connection { + struct dmtls_connection* next; + dmutc_calendar_t utc; + uint32_t magic; + dmtls_config_t config; + dmtls_transport_t transport; + mbedtls_ssl_context backend; + dmtls_status_t status; + uint32_t handshake_started; + uint32_t io_started; + uint32_t handshake_timeout; + uint32_t io_timeout; + unsigned pending_operation; + const void* pending_write; + size_t pending_size; + dmtls_result_t callback_error; +}; +extern struct dmtls_config* dmtls_config_list; +extern struct dmtls_connection* dmtls_connection_list; +extern dmtls_platform_t dmtls_platform; +extern dmosi_mutex_t dmtls_lock; +extern bool dmtls_initialized; +extern uint32_t dmtls_configs; +extern uint32_t dmtls_connections; +extern size_t dmtls_heap_used; +dmtls_result_t dmtls_enter(void); +int dmtls_module_init(void); +int dmtls_module_deinit(void); +dmtls_result_t dmtls_platform_initialize(const dmtls_platform_t* platform); +dmtls_result_t dmtls_platform_shutdown(void); +uint32_t dmtls_verify_bits(uint32_t flags); +dmtls_result_t dmtls_refresh_utc(dmtls_connection_t connection); +void dmtls_leave(void); +bool dmtls_valid_config(dmtls_config_t config); +bool dmtls_valid_connection(dmtls_connection_t connection); +dmtls_result_t dmtls_fail(dmtls_connection_t connection, dmtls_result_t error); +dmtls_result_t dmtls_backend_result(dmtls_connection_t connection, int result); +dmtls_result_t dmtls_config_new(const void* ca, size_t size, dmtls_config_t* output); +dmtls_result_t dmtls_config_free(dmtls_config_t config); +dmtls_result_t dmtls_connection_new(dmtls_config_t config, const char* host, + const dmtls_transport_t* transport, uint32_t handshake_ms, uint32_t io_ms, dmtls_connection_t* output); +void dmtls_connection_free(dmtls_connection_t connection); +dmtls_result_t dmtls_do_handshake(dmtls_connection_t connection); +dmtls_result_t dmtls_do_read(dmtls_connection_t connection, void* buffer, size_t size, size_t* read); +dmtls_result_t dmtls_do_write(dmtls_connection_t connection, const void* buffer, size_t size, size_t* written); +dmtls_result_t dmtls_do_close(dmtls_connection_t connection); +#endif diff --git a/src/dmtls_io.c b/src/dmtls_io.c new file mode 100644 index 0000000..4dffa44 --- /dev/null +++ b/src/dmtls_io.c @@ -0,0 +1,68 @@ +#include "dmtls_internal.h" + +dmtls_result_t dmtls_fail(dmtls_connection_t c, dmtls_result_t error) +{ + c->status.error = error; c->status.state = DMTLS_STATE_FAILED; + c->pending_operation = 0; c->pending_write = NULL; c->pending_size = 0; + return error; +} +dmtls_result_t dmtls_backend_result(dmtls_connection_t c, int result) +{ + if (c->callback_error != DMTLS_OK) return dmtls_fail(c, c->callback_error); + if (result == MBEDTLS_ERR_SSL_WANT_READ) return DMTLS_WANT_READ; + if (result == MBEDTLS_ERR_SSL_WANT_WRITE) return DMTLS_WANT_WRITE; + if (result == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + c->status.state = DMTLS_STATE_PEER_CLOSED; c->pending_operation = 0; + return DMTLS_PEER_CLOSED; + } + uint32_t flags = mbedtls_ssl_get_verify_result(&c->backend); + c->status.verification_flags |= dmtls_verify_bits(flags); + dmtls_result_t error = DMTLS_ERR_PROTOCOL; + if (result == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED || c->status.verification_flags != 0) + error = DMTLS_ERR_CERTIFICATE; + else if (result == MBEDTLS_ERR_SSL_ALLOC_FAILED) error = DMTLS_ERR_MEMORY; + else if (result == MBEDTLS_ERR_SSL_CONN_EOF || result == 0) error = DMTLS_ERR_TRUNCATED; + return dmtls_fail(c, error); +} +static dmtls_result_t begin(dmtls_connection_t c, unsigned operation) +{ + if (c->status.state == DMTLS_STATE_FAILED) return c->status.error; + if (c->status.state == DMTLS_STATE_PEER_CLOSED && operation == 1) return DMTLS_PEER_CLOSED; + if (c->status.state != DMTLS_STATE_OPEN && + !(operation == 3 && (c->status.state == DMTLS_STATE_PEER_CLOSED || c->status.state == DMTLS_STATE_CLOSING))) + return DMTLS_ERR_STATE; + if (c->pending_operation != 0 && c->pending_operation != operation) return DMTLS_ERR_BUSY; + if (c->pending_operation == 0) {c->pending_operation = operation; c->io_started = dmosi_get_tick_count();} + if ((uint32_t)(dmosi_get_tick_count() - c->io_started) >= c->io_timeout) + return dmtls_fail(c, DMTLS_ERR_TIMEOUT); + c->callback_error = DMTLS_OK; return DMTLS_OK; +} +dmtls_result_t dmtls_do_read(dmtls_connection_t c, void* buffer, size_t size, size_t* received) +{ + dmtls_result_t rc = begin(c, 1); if (rc != DMTLS_OK) return rc; + int n = mbedtls_ssl_read(&c->backend, buffer, size); + if ((uint32_t)(dmosi_get_tick_count() - c->io_started) >= c->io_timeout) return dmtls_fail(c, DMTLS_ERR_TIMEOUT); + if (n <= 0) return dmtls_backend_result(c, n); + *received = (size_t)n; c->pending_operation = 0; return DMTLS_OK; +} +dmtls_result_t dmtls_do_write(dmtls_connection_t c, const void* buffer, size_t size, size_t* sent) +{ + if (c->pending_operation == 2 && (c->pending_write != buffer || c->pending_size != size)) return DMTLS_ERR_ARGUMENT; + dmtls_result_t rc = begin(c, 2); if (rc != DMTLS_OK) return rc; + c->pending_write = buffer; c->pending_size = size; + int n = mbedtls_ssl_write(&c->backend, buffer, size); + if ((uint32_t)(dmosi_get_tick_count() - c->io_started) >= c->io_timeout) return dmtls_fail(c, DMTLS_ERR_TIMEOUT); + if (n <= 0) return dmtls_backend_result(c, n); + *sent = (size_t)n; c->pending_operation = 0; c->pending_write = NULL; c->pending_size = 0; + return DMTLS_OK; +} +dmtls_result_t dmtls_do_close(dmtls_connection_t c) +{ + if (c->status.state == DMTLS_STATE_CLOSED) return DMTLS_OK; + dmtls_result_t rc = begin(c, 3); if (rc != DMTLS_OK) return rc; + c->status.state = DMTLS_STATE_CLOSING; + int n = mbedtls_ssl_close_notify(&c->backend); + if ((uint32_t)(dmosi_get_tick_count() - c->io_started) >= c->io_timeout) return dmtls_fail(c, DMTLS_ERR_TIMEOUT); + if (n != 0) return dmtls_backend_result(c, n); + c->status.state = DMTLS_STATE_CLOSED; c->pending_operation = 0; return DMTLS_OK; +} diff --git a/src/dmtls_platform.c b/src/dmtls_platform.c new file mode 100644 index 0000000..35c4d59 --- /dev/null +++ b/src/dmtls_platform.c @@ -0,0 +1,135 @@ +#include "dmtls_internal.h" +#include "dmtls_platform.h" +#include "mbedtls/platform_util.h" +#include "mbedtls/dmod.h" +#include "psa/crypto_driver_random.h" +#include +#include +#include + +struct dmtls_config* dmtls_config_list; +struct dmtls_connection* dmtls_connection_list; +dmtls_platform_t dmtls_platform; +dmosi_mutex_t dmtls_lock; +bool dmtls_initialized; +uint32_t dmtls_configs, dmtls_connections; +size_t dmtls_heap_used; +static bool entered; +/* Preserve the strongest fundamental alignment on both Cortex-M and x86. */ +typedef union {size_t size; max_align_t align;} allocation_header; + +void* dmtls_backend_calloc(size_t count, size_t size) +{ + if (size != 0 && count > (SIZE_MAX - sizeof(allocation_header)) / size) return NULL; + size_t bytes = count * size + sizeof(allocation_header); + if (dmtls_heap_used > dmtls_platform.heap_limit || bytes > dmtls_platform.heap_limit - dmtls_heap_used) return NULL; + allocation_header* p = Dmod_Malloc(bytes); + if (p == NULL) return NULL; + p->size = bytes; dmtls_heap_used += bytes; + memset(p + 1, 0, bytes - sizeof(*p)); return p + 1; +} +void dmtls_backend_free(void* pointer) +{ + if (pointer == NULL) return; + allocation_header* p = (allocation_header*)pointer - 1; + size_t bytes = p->size; + mbedtls_platform_zeroize(p, bytes); dmtls_heap_used -= bytes; Dmod_Free(p); +} +int mbedtls_platform_get_entropy(psa_driver_get_entropy_flags_t flags, + size_t* bits, unsigned char* output, size_t size) +{ + *bits = 0; + if (flags != 0) return PSA_ERROR_NOT_SUPPORTED; + if (size > SIZE_MAX / 8 || dmtls_platform.entropy == NULL || + dmtls_platform.entropy(dmtls_platform.user, output, size) != 0) { + mbedtls_platform_zeroize(output, size); return PSA_ERROR_INSUFFICIENT_ENTROPY; + } + *bits = size * 8; return 0; +} +int dmtls_module_init(void) +{ + if (dmtls_lock != NULL) return 0; + mbedtls_dmod_pk_init(); + mbedtls_dmod_ecp_init(); + dmtls_lock = dmosi_mutex_create(true); + return dmtls_lock == NULL ? -ENOMEM : 0; +} +int dmtls_module_deinit(void) +{ + if (dmtls_configs != 0 || dmtls_connections != 0 || entered) return -EBUSY; + if (dmtls_initialized) mbedtls_psa_crypto_free(); + dmtls_initialized = false; + if (dmtls_lock != NULL) dmosi_mutex_destroy(dmtls_lock); + dmtls_lock = NULL; memset(&dmtls_platform, 0, sizeof(dmtls_platform)); return 0; +} +dmtls_result_t dmtls_enter(void) +{ + if (dmtls_lock == NULL) return DMTLS_ERR_STATE; + if (dmosi_mutex_lock(dmtls_lock) != 0) return DMTLS_ERR_BUSY; + if (entered || !dmtls_initialized) {dmosi_mutex_unlock(dmtls_lock); return entered ? DMTLS_ERR_BUSY : DMTLS_ERR_STATE;} + entered = true; return DMTLS_OK; +} +void dmtls_leave(void) {entered = false; dmosi_mutex_unlock(dmtls_lock);} +dmtls_result_t dmtls_platform_initialize(const dmtls_platform_t* platform) +{ + if (platform == NULL || platform->entropy == NULL || platform->heap_limit < 32768 || platform->max_connections == 0) + return DMTLS_ERR_ARGUMENT; + if (dmtls_lock == NULL) return DMTLS_ERR_STATE; + if (dmosi_mutex_lock(dmtls_lock) != 0) return DMTLS_ERR_BUSY; + if (entered || dmtls_initialized) {dmosi_mutex_unlock(dmtls_lock); return DMTLS_ERR_BUSY;} + entered = true; dmtls_platform = *platform; + psa_status_t rc = psa_crypto_init(); + if (rc != PSA_SUCCESS) { + mbedtls_psa_crypto_free(); memset(&dmtls_platform, 0, sizeof(dmtls_platform)); + } else dmtls_initialized = true; + dmtls_leave(); + return rc == PSA_SUCCESS ? DMTLS_OK : rc == PSA_ERROR_INSUFFICIENT_MEMORY ? DMTLS_ERR_MEMORY : DMTLS_ERR_ENTROPY; +} +dmtls_result_t dmtls_platform_shutdown(void) +{ + dmtls_result_t rc = dmtls_enter(); if (rc != DMTLS_OK) return rc; + if (dmtls_configs || dmtls_connections) rc = DMTLS_ERR_BUSY; + else {mbedtls_psa_crypto_free(); dmtls_initialized = false; memset(&dmtls_platform, 0, sizeof(dmtls_platform));} + dmtls_leave(); return rc; +} +bool dmtls_valid_config(dmtls_config_t config) +{ + for (dmtls_config_t p = dmtls_config_list; p != NULL; p = p->next) if (p == config) return true; + return false; +} +bool dmtls_valid_connection(dmtls_connection_t connection) +{ + for (dmtls_connection_t p = dmtls_connection_list; p != NULL; p = p->next) if (p == connection) return true; + return false; +} +dmtls_result_t dmtls_refresh_utc(dmtls_connection_t c) +{ + dmutc_time_t time; + if (dmutc_get(&time) != 0 || dmutc_to_calendar(&time, &c->utc) != 0) return DMTLS_ERR_TIME; + return DMTLS_OK; +} +int dmtls_backend_vsnprintf(char* out, size_t size, const char* format, va_list args) +{return Dmod_VSnPrintf(out, size, format, args);} +int dmtls_backend_snprintf(char* out, size_t size, const char* format, ...) +{ + va_list args; va_start(args, format); + int rc = Dmod_VSnPrintf(out, size, format, args); + va_end(args); return rc; +} + +void mbedtls_platform_zeroize(void* buffer, size_t size) +{ + volatile unsigned char* p = buffer; + while (size-- != 0) *p++ = 0; +} +char* dmtls_backend_strstr(const char* text, const char* needle) +{ + size_t length = strlen(needle); + if (length == 0) return (char*)text; + for (; *text != 0; ++text) { + size_t i = 0; + while (i < length && text[i] != 0 && text[i] == needle[i]) ++i; + if (i == length) return (char*)text; + } + return NULL; +} diff --git a/src/dmtls_platform.h b/src/dmtls_platform.h new file mode 100644 index 0000000..8523dae --- /dev/null +++ b/src/dmtls_platform.h @@ -0,0 +1,10 @@ +#ifndef DMTLS_PLATFORM_H +#define DMTLS_PLATFORM_H +#include +#include +char* dmtls_backend_strstr(const char* haystack, const char* needle); +void* dmtls_backend_calloc(size_t count, size_t size); +void dmtls_backend_free(void* pointer); +int dmtls_backend_snprintf(char* out, size_t size, const char* format, ...); +int dmtls_backend_vsnprintf(char* out, size_t size, const char* format, va_list args); +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..35cbf11 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,62 @@ +set(DMOD_MODULE_NAME test_dmtls) +set(DMOD_MODULE_VERSION ${PROJECT_VERSION}) +set(DMOD_AUTHOR_NAME "Patryk Kubiak") +set(DMOD_STACK_SIZE 8192) +set(DMOD_DMR_PATH ${CMAKE_CURRENT_SOURCE_DIR}/test_dmtls.dmr) +set(DMOD_PRIORITY 0) + +dmod_add_test(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION} + dmtls_test.c +) + +target_include_directories(${DMOD_MODULE_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../include +) + +target_link_libraries(${DMOD_MODULE_NAME} dmtls_if) +dmtls_audit_module(${DMOD_MODULE_NAME}) + +# Register the test module with ctest - this installs dependencies and runs the real module +# through the loader: install the test module's +# dependencies with dmf-get, then execute it through dmod_loader. The test's +# exit code equals its number of failed steps (see dmod_add_test). +find_program(DMF_GET_EXECUTABLE dmf-get HINTS ${DMOD_TOOLS_BIN_DIR} PATHS ENV PATH) +find_program(DMOD_LOADER_EXECUTABLE dmod_loader HINTS ${DMOD_TOOLS_BIN_DIR} PATHS ENV PATH) + +if(DMF_GET_EXECUTABLE AND DMOD_LOADER_EXECUTABLE) + add_test( + NAME ${DMOD_MODULE_NAME} + COMMAND ${CMAKE_COMMAND} -E env DMOD_DMF_DIR=${DMOD_DMF_DIR} + bash -c "${DMF_GET_EXECUTABLE} install -d ${DMOD_DMF_DIR}/${DMOD_MODULE_NAME}-local.dmd -y && ${DMOD_LOADER_EXECUTABLE} ${DMOD_DMF_DIR}/${DMOD_MODULE_NAME}.dmf" + ) +else() + message(WARNING "dmf-get/dmod_loader not found - 'ctest' will not run ${DMOD_MODULE_NAME}. Build the tools first with -DDMOD_BUILD_TOOLS=ON") +endif() + +# The real TLS engine and crypto backend, with only SAL/dmosi/UTC replaced by +# controllable host providers. OpenSSL provides an independent TLS peer. +if(CMAKE_CROSSCOMPILING OR DMOD_ARCH MATCHES "arm") + if(DMTLS_BUILD_HARDWARE_PROBE) + add_subdirectory(probe) + endif() +else() + find_package(Python3 REQUIRED COMPONENTS Interpreter) + find_package(Threads REQUIRED) + add_test(NAME dmtls_pointer_audit COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/host/test_pointer_audit.py) + add_library(dmtls_test_engine SHARED + ../src/dmtls.c ../src/dmtls_platform.c ../src/dmtls_config.c + ../src/dmtls_connection.c ../src/dmtls_io.c host/platform.c + ${DMTLS_BACKEND_SOURCES}) + target_include_directories(dmtls_test_engine PRIVATE + host/stubs ../include ../src ../config + ${DMOD_DMF_DIR}/inc/dmutc/include ${DMTLS_BACKEND_INCLUDES}) + target_compile_definitions(dmtls_test_engine PRIVATE + strstr=dmtls_backend_strstr + MBEDTLS_CONFIG_FILE="dmtls_mbedtls_config.h" + TF_PSA_CRYPTO_CONFIG_FILE="dmtls_psa_config.h") + target_link_libraries(dmtls_test_engine PRIVATE Threads::Threads) + add_test(NAME tls_interoperability COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/host/interoperability.py $) + set_tests_properties(tls_interoperability PROPERTIES TIMEOUT 180) +endif() diff --git a/tests/dmtls_test.c b/tests/dmtls_test.c new file mode 100644 index 0000000..0fa2991 --- /dev/null +++ b/tests/dmtls_test.c @@ -0,0 +1,39 @@ +#define DMOD_ENABLE_REGISTRATION ON +#include "dmod_test.h" +#include "dmtls.h" +#include "fixture_ca.h" + +DMOD_TEST_STEP(tls_requires_an_explicit_entropy_source) +{ + DMOD_TEST_EXPECT_EQ(dmtls_initialize(NULL), DMTLS_ERR_ARGUMENT); + dmtls_platform_t options = {0}; + DMOD_TEST_EXPECT_EQ(dmtls_initialize(&options), DMTLS_ERR_ARGUMENT); +} +DMOD_TEST_STEP(tls_rejects_calls_before_initialization) +{ + dmtls_config_t config = (dmtls_config_t)1; + DMOD_TEST_EXPECT_EQ(dmtls_config_create("invalid", 7, &config), DMTLS_ERR_STATE); + DMOD_TEST_EXPECT_NULL(config); +} + +/* Deliberately deterministic test entropy: this test creates no TLS connection + * and never protects application data. Production callers must use a strong + * entropy source as required by the public API. */ +static int fixture_entropy(void* user,uint8_t* output,size_t size) +{ + (void)user; + for(size_t i=0;ideadline:self.fail('TLS operation failed to make progress') + time.sleep(.0001) + def status(self,c): + out=Status();self.assertEqual(lib.dmtls_status(c,C.byref(out)),OK);return out + def exchange(self,version,abrupt=False): + c,peer=self.connection(version,abrupt=abrupt) + self.assertEqual(self.drive(lib.dmtls_handshake,c),OK) + self.assertIn('tls.test',self.sni) + message=b'hello over authenticated TLS';buffer=C.create_string_buffer(message);sent=C.c_size_t() + self.assertEqual(self.drive(lib.dmtls_write,c,buffer,len(message),C.byref(sent)),OK) + self.assertEqual(sent.value,len(message)) + received=bytearray() + while len(received) +#include +#include +#include +#include +#include +static _Atomic uint32_t ticks_offset; +static _Atomic int64_t utc_offset; +static _Atomic bool utc_available = true; +static _Atomic size_t allocations; +void test_ticks_advance(uint32_t ms) {ticks_offset += ms;} +void test_utc_offset(int64_t seconds) {utc_offset = seconds;} +void test_utc_available(int available) {utc_available = available != 0;} +size_t test_live_allocations(void) {return allocations;} +void* Dmod_Malloc(size_t size) {void* p = malloc(size); if(p != NULL) ++allocations; return p;} +void Dmod_Free(void* p) {if(p != NULL) --allocations; free(p);} +int Dmod_VSnPrintf(char* out,size_t n,const char* fmt,va_list args) {return vsnprintf(out,n,fmt,args);} +struct test_mutex {pthread_mutex_t native;}; +dmosi_mutex_t dmosi_mutex_create(bool recursive) +{ + dmosi_mutex_t p=malloc(sizeof(*p));if(p==NULL)return NULL; + pthread_mutexattr_t attr;pthread_mutexattr_init(&attr); + if(recursive)pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE); + int rc=pthread_mutex_init(&p->native,&attr);pthread_mutexattr_destroy(&attr); + if(rc!=0){free(p);return NULL;}return p; +} +void dmosi_mutex_destroy(dmosi_mutex_t p) {pthread_mutex_destroy(&p->native);free(p);} +int dmosi_mutex_lock(dmosi_mutex_t p) {return pthread_mutex_lock(&p->native);} +int dmosi_mutex_unlock(dmosi_mutex_t p) {return pthread_mutex_unlock(&p->native);} +uint32_t dmosi_get_tick_count(void) +{struct timespec ts;clock_gettime(CLOCK_MONOTONIC,&ts);return (uint32_t)((uint64_t)ts.tv_sec*1000+ts.tv_nsec/1000000)+ticks_offset;} +int dmutc_get(dmutc_time_t* out) +{if(!utc_available)return -ENODATA; *out=(dmutc_time_t){time(NULL)+utc_offset,0,0};return 0;} +int dmutc_to_calendar(const dmutc_time_t* value,dmutc_calendar_t* out) +{ + time_t t=(time_t)value->seconds;struct tm c; + if(gmtime_r(&t,&c)==NULL)return -ERANGE; + *out=(dmutc_calendar_t){c.tm_year+1900,c.tm_mon+1,c.tm_mday,c.tm_hour,c.tm_min,c.tm_sec,0,0,0};return 0; +} diff --git a/tests/host/stubs/dmod.h b/tests/host/stubs/dmod.h new file mode 100644 index 0000000..a85a6e1 --- /dev/null +++ b/tests/host/stubs/dmod.h @@ -0,0 +1,15 @@ +#ifndef TEST_DMOD_H +#define TEST_DMOD_H +#define DMOD_GLOBAL_POINTER __attribute__((section(".got"))) +#define DMOD_MOD_DEFS_H_dmutc +#define dmod_dmutc_api(v,r,n,a) r dmutc##n a +#include +#include +#include +#include +typedef struct {int unused;} Dmod_Config_t; +void* Dmod_Malloc(size_t size); +void Dmod_Free(void* p); +int Dmod_VSnPrintf(char*, size_t, const char*, va_list); +#define dmod_dmtls_api_declaration(v,r,n,a) r dmtls##n a +#endif diff --git a/tests/host/stubs/dmosi.h b/tests/host/stubs/dmosi.h new file mode 100644 index 0000000..cb1fbb6 --- /dev/null +++ b/tests/host/stubs/dmosi.h @@ -0,0 +1,11 @@ +#ifndef TEST_DMOSI_H +#define TEST_DMOSI_H +#include +#include +typedef struct test_mutex* dmosi_mutex_t; +dmosi_mutex_t dmosi_mutex_create(bool recursive); +void dmosi_mutex_destroy(dmosi_mutex_t mutex); +int dmosi_mutex_lock(dmosi_mutex_t mutex); +int dmosi_mutex_unlock(dmosi_mutex_t mutex); +uint32_t dmosi_get_tick_count(void); +#endif diff --git a/tests/host/stubs/dmtls_defs.h b/tests/host/stubs/dmtls_defs.h new file mode 100644 index 0000000..02aa52a --- /dev/null +++ b/tests/host/stubs/dmtls_defs.h @@ -0,0 +1,4 @@ +#ifndef TEST_DMTLS_DEFS_H +#define TEST_DMTLS_DEFS_H +#define dmod_dmtls_api(v,r,n,a) r dmtls##n a +#endif diff --git a/tests/host/stubs/dmutc_defs.h b/tests/host/stubs/dmutc_defs.h new file mode 100644 index 0000000..a966a98 --- /dev/null +++ b/tests/host/stubs/dmutc_defs.h @@ -0,0 +1,4 @@ +#ifndef TEST_DMUTC_DEFS_H +#define TEST_DMUTC_DEFS_H +#define dmod_dmutc_api(v,r,n,a) r dmutc##n a +#endif diff --git a/tests/host/test_pointer_audit.py b/tests/host/test_pointer_audit.py new file mode 100644 index 0000000..5e9ec55 --- /dev/null +++ b/tests/host/test_pointer_audit.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Prove the audit accepts GOT pointers and rejects mixed static aggregates.""" +import importlib.util +from pathlib import Path +import struct +import subprocess +import tempfile +import unittest + +ROOT=Path(__file__).resolve().parents[2] +spec=importlib.util.spec_from_file_location('audit',ROOT/'scripts/check_dmod_pointers.py') +audit=importlib.util.module_from_spec(spec);spec.loader.exec_module(audit) + +class PointerAudit(unittest.TestCase): + def fixture(self,bad=False,metadata=True): + with tempfile.TemporaryDirectory() as directory: + root=Path(directory) + source=''' +#include +__attribute__((section(".header"),used)) const uint32_t header[60]={0x444d4f44,240,256,8}; +static void callback(void) {__asm__ volatile("nop");} +static void (*const volatile pointer)(void) __attribute__((section(".got")))=callback; +void entry(void) {pointer();} +''' + if bad:source+='struct mixed {int value;void (*callback)(void);}; const struct mixed bad={7,callback};\n' + (root/'source.c').write_text(source) + (root/'layout.ld').write_text('SECTIONS { . = 0; .header : {KEEP(*(.header))} .text : {*(.text*)} .data : {*(.data*)} .got : {*(.got*)} /DISCARD/ : {*(.note*) *(.eh_frame*)} }') + command=['gcc','-fPIC','-fPIE','-O2','-nostdlib','-static','-Wl,--no-relax', + '-T',str(root/'layout.ld'),str(root/'source.c'),'-o',str(root/'module')] + if metadata:command.insert(1,'-Wl,--emit-relocs') + subprocess.run(command,check=True,capture_output=True) + subprocess.run(['objcopy','-O','binary',str(root/'module'),str(root/'module.dmf')],check=True) + return (root/'module').read_bytes(),(root/'module.dmf').read_bytes() + + def test_existing_got_format_is_accepted(self): + elf,dmf=self.fixture();self.assertEqual(audit.check(elf,dmf),1) + self.assertEqual(struct.unpack_from(' +#define CAPACITY 32768u +struct transport {unsigned char rx[CAPACITY];size_t rx_pos,rx_size;unsigned char tx[CAPACITY];size_t tx_size;}; +static int entropy(void* user, uint8_t* out, size_t size) {(void)user;return dmrng_fill(out,size);} +static dmtls_entropy_fn const volatile entropy_source DMOD_GLOBAL_POINTER = entropy; +static dmtls_result_t send_bytes(void* user,const uint8_t* in,size_t size,size_t* sent) +{ + struct transport* t=user;*sent=0; + size_t n=CAPACITY-t->tx_size;if(n==0)return DMTLS_WANT_WRITE; + if(n>size)n=size;if(n>257)n=257; + memcpy(t->tx+t->tx_size,in,n);t->tx_size+=n;*sent=n;return DMTLS_OK; +} +static dmtls_result_t receive_bytes(void* user,uint8_t* out,size_t size,size_t* received) +{ + struct transport* t=user;*received=0; + size_t n=t->rx_size-t->rx_pos;if(n==0)return DMTLS_WANT_READ; + if(n>size)n=size;if(n>113)n=113; + memcpy(out,t->rx+t->rx_pos,n);t->rx_pos+=n;*received=n;return DMTLS_OK; +} +static void flush(struct transport* t) +{ + if(t->tx_size==0)return; + static const char digits[]="0123456789abcdef"; + Dmod_Printf("TLS_OUT "); + char line[128]; + for(size_t pos=0;postx_size;){ + size_t count=0; + while(postx_size && counttx[pos]>>4];line[count++]=digits[t->tx[pos]&15];++pos; + } + Dmod_FileWrite(line,1,count,DMOD_STDOUT); + } + Dmod_Printf("\n");t->tx_size=0; +} +static int unhex(int c) +{if(c>='0'&&c<='9')return c-'0';if(c>='a'&&c<='f')return c-'a'+10;return -1;} +static bool exchange(struct transport* t) +{ + flush(t);Dmod_Printf("TLS_INPUT\n"); + if(t->rx_pos!=0){memmove(t->rx,t->rx+t->rx_pos,t->rx_size-t->rx_pos);t->rx_size-=t->rx_pos;t->rx_pos=0;} + int ch;do{ch=Dmod_Getc();}while(ch=='\r'||ch=='\n'); + if(ch!='I')return false; + int high=-1; + while((ch=Dmod_Getc())!='\n' && ch!='\r' && ch!=DMOD_EOF){ + if(ch==' ')continue; + int nibble=unhex(ch);if(nibble<0)return false; + if(high<0)high=nibble; + else {if(t->rx_size==CAPACITY)return false;t->rx[t->rx_size++]=(unsigned char)((high<<4)|nibble);high=-1;} + } + return high<0 && ch!=DMOD_EOF; +} +int main(int argc,char* argv[]) +{ + if(argc!=3){Dmod_Printf("Usage: tlsprobe HOSTNAME CA_FILE\n");return 1;} + Dmod_Printf("TLS_STAGE start\n"); + struct transport* t=Dmod_Malloc(sizeof(*t)); + if(t==NULL)return 1;memset(t,0,sizeof(*t)); + void* file=Dmod_FileOpen(argv[2],"rb"); + if(file==NULL){Dmod_Free(t);return 1;} + unsigned char ca[4096];size_t size=Dmod_FileRead(ca,1,sizeof(ca),file);Dmod_FileClose(file); + dmtls_platform_t platform={0}; + platform.entropy=entropy_source;platform.heap_limit=512*1024;platform.max_connections=1; + dmtls_config_t config=NULL;dmtls_connection_t connection=NULL; + Dmod_Printf("TLS_STAGE initialize\n"); + dmtls_result_t rc=dmtls_initialize(&platform); + Dmod_Printf("TLS_STAGE initialized %d\n",(int)rc); + bool initialized=rc==DMTLS_OK; + if(rc==DMTLS_OK)rc=dmtls_config_create(ca,size,&config); + Dmod_Printf("TLS_STAGE config %d\n",(int)rc); + dmtls_transport_t bio={send_bytes,receive_bytes,t}; + if(rc==DMTLS_OK)rc=dmtls_connection_create(config,argv[1],&bio,30000,10000,&connection); + Dmod_Printf("TLS_STAGE connection %d\n",(int)rc); + uint32_t flags=Dmod_Stdin_GetFlags(); + void* input=Dmod_LockStdio(DMOD_STDIN); + if(input==NULL || Dmod_Ioctl(input,dmtty_ioctl_cmd_claim_foreground,NULL)!=0)rc=DMTLS_ERR_TRANSPORT; + Dmod_UnlockStdio(DMOD_STDIN); + Dmod_Stdin_SetFlags(0); + if(rc==DMTLS_OK){ + do {rc=dmtls_handshake(connection);if(rc==DMTLS_WANT_READ||rc==DMTLS_WANT_WRITE){if(!exchange(t)){rc=DMTLS_ERR_TRANSPORT;break;}}} + while(rc==DMTLS_WANT_READ||rc==DMTLS_WANT_WRITE); + } + if(rc==DMTLS_OK){ + Dmod_Printf("TLS_OPEN\n"); + static const char message[]="hardware TLS echo";size_t offset=0,n; + while(rc==DMTLS_OK && offset ${destination}/${module}.dmf [origin=${dmf_dir}/${module}.dmf] +dmfc=./${module}.dmfc => ${destination}/${module}.dmfc [origin=${build_dir}/dmfc/${module}.dmfc] +dmd=./${module}.dmd => ${destination}/${module}.dmd [origin=${dmf_dir}/${module}.dmd] +version=./${module}_version.txt => ${destination}/${module}_version.txt [origin=${dmf_dir}/${module}_version.txt] +license=./LICENSE => ${destination}/${module}/LICENSE [origin=${repo_dir}/LICENSE] diff --git a/tests/test_dmtls.dmr b/tests/test_dmtls.dmr new file mode 100644 index 0000000..e79c829 --- /dev/null +++ b/tests/test_dmtls.dmr @@ -0,0 +1,6 @@ +# Runtime diagnostic package (no generated public header). +dmf=./${module}.dmf => ${destination}/${module}.dmf [origin=${dmf_dir}/${module}.dmf] +dmfc=./${module}.dmfc => ${destination}/${module}.dmfc [origin=${build_dir}/dmfc/${module}.dmfc] +dmd=./${module}.dmd => ${destination}/${module}.dmd [origin=${dmf_dir}/${module}.dmd] +version=./${module}_version.txt => ${destination}/${module}_version.txt [origin=${dmf_dir}/${module}_version.txt] +license=./LICENSE => ${destination}/${module}/LICENSE [origin=${repo_dir}/LICENSE] diff --git a/third_party/MBEDTLS-LICENSE b/third_party/MBEDTLS-LICENSE new file mode 100644 index 0000000..776ac77 --- /dev/null +++ b/third_party/MBEDTLS-LICENSE @@ -0,0 +1,553 @@ +Mbed TLS files are provided under a dual [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) +OR [GPL-2.0-or-later](https://spdx.org/licenses/GPL-2.0-or-later.html) license. +This means that users may choose which of these licenses they take the code +under. + +The full text of each of these licenses is given below. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +=============================================================================== + + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/third_party/README.md b/third_party/README.md new file mode 100644 index 0000000..2d526bc --- /dev/null +++ b/third_party/README.md @@ -0,0 +1,17 @@ +# Crypto backend licenses and source + +The backend uses the DMOD forks of Mbed TLS 4.1.1 and TF-PSA-Crypto 1.1.1. +This project elects the Apache-2.0 option offered by those distributions; +the upstream full license notices are retained here and shipped with the module. +Source files retain the upstream copyright and SPDX notices. + +Reviewed sources (archive SHA-256 values are enforced in cmake/backend.cmake): + +- [Mbed TLS fork, 41853d1](https://github.com/choco-technologies/mbedtls/commit/41853d1d2a94a92f97fe70fbc1d5c14e19d79d25) +- [TF-PSA-Crypto fork, a119c22](https://github.com/choco-technologies/TF-PSA-Crypto/commit/a119c22d541a783aab6f0c5b68047f5d81d1969a) + +The adaptation uses DMOD_GLOBAL_POINTER, initializes backend tables at module +startup, and stores immutable descriptor text inline. Cryptographic algorithms, +constants and verification policy remain upstream implementations. The forks' +README-DMOD.md files describe the integration and generated-source provenance. +No DMOD loader or file-format extension is required. diff --git a/third_party/TF-PSA-CRYPTO-LICENSE b/third_party/TF-PSA-CRYPTO-LICENSE new file mode 100644 index 0000000..4c47cb2 --- /dev/null +++ b/third_party/TF-PSA-CRYPTO-LICENSE @@ -0,0 +1,552 @@ +TF-PSA-Crypto files are provided under a dual [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) +OR [GPL-2.0-or-later](https://spdx.org/licenses/GPL-2.0-or-later.html) license. +This means that users may choose which of these licenses they take the code +under. + +The full text of each of these licenses is given below. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +=============================================================================== + + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License.