diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e92792..3c249f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,31 @@ name: CI +# ci: calls sidick/amiga-workflows' build-test.yml, which implements the +# five-verb contract (see that repo's README.md, the document of record) -- +# build/test-host/test-target/lint all resolve to Makefile targets of the +# same name (see Makefile's own "Verb contract" comment). Each of those +# jobs is independent (no artifact-passing between them), unlike this +# repo's previous bespoke pipeline (one m68k-build job producing both +# binaries, one host-tests job, one asm-crypto-tests job) -- the m68k +# cross-build now runs more than once per CI run (build and test-target +# each call it), a deliberate tradeoff of standardizing on the shared +# workflow rather than keeping a repo-specific optimization. +# +# The `changes` job stays local (repo-specific path patterns aren't +# something a shared workflow should know about) and its outputs feed +# build-test.yml's run-* inputs directly, preserving the path-based +# skip-on-docs-only-PR behavior. `differential` (opt-in OpenSSL fuzz) and +# `docs-build` (strict MkDocs + AmigaGuide check) stay local jobs too -- +# neither fits the five verbs, and the shared workflow has no hook for an +# extra per-project job. `catalog-lint`'s check-catalog now runs as `make +# lint` inside build-test.yml's lint job instead of its own job. +# +# NOTE: branch protection's required status checks reference job names +# directly (e.g. "m68k Amiga build") -- those all changed shape here +# (nested reusable-workflow jobs report as "ci / build" etc). Updated to +# match as part of this same change; see amiga-dev's docs/plan.md Phase 4 +# entry for the mapping. + on: push: branches: [main] @@ -47,6 +73,18 @@ jobs: echo "catalog=$(match "$always|^(locale/|tools/check_catalog\.py)")" } >> "$GITHUB_OUTPUT" + ci: + name: ci + needs: changes + uses: sidick/amiga-workflows/.github/workflows/build-test.yml@v1 + with: + run-build: ${{ needs.changes.outputs.build == 'true' }} + run-test-host: ${{ needs.changes.outputs.host == 'true' }} + run-test-target: ${{ needs.changes.outputs.target == 'true' }} + run-lint: ${{ needs.changes.outputs.catalog == 'true' }} + secrets: + AMIGA_REAL_ROM_B64: ${{ secrets.AMIGA_REAL_ROM_B64 }} + docs-build: name: Docs site + AmigaGuide build runs-on: ubuntu-latest @@ -64,30 +102,6 @@ jobs: - name: AmigaGuide conversion run: make guide - host-tests: - name: Host vector tests - runs-on: ubuntu-latest - needs: changes - if: needs.changes.outputs.host == 'true' - steps: - - uses: actions/checkout@v7 - - name: Build and run tests - run: make test - - name: Build native CLI - run: make cli - - name: CLI smoke test - run: make smoke - - catalog-lint: - name: Catalog structural checks - runs-on: ubuntu-latest - needs: changes - if: needs.changes.outputs.catalog == 'true' - steps: - - uses: actions/checkout@v7 - - name: Check placeholders, mnemonics, re-key prompt markers - run: make check-catalog - differential: name: Differential fuzz (vs OpenSSL) runs-on: ubuntu-latest @@ -99,82 +113,3 @@ jobs: run: sudo apt-get update && sudo apt-get install -y libssl-dev pkg-config - name: Fuzz crypto primitives against OpenSSL run: make diff DIFF_ITERS=20000 - - m68k-build: - name: m68k Amiga build - runs-on: ubuntu-latest - needs: changes - if: needs.changes.outputs.build == 'true' - container: ghcr.io/reinauer/container-amiga-gcc:latest - steps: - - uses: actions/checkout@v7 - - name: Cross-build CLI for 68000 - run: PATH="/opt/amiga/bin:$PATH" make m68k M68K_CC=m68k-amigaos-gcc - # The GUI pulls in sources the CLI build never touches (src/gui/, - # qrimage.c, arexx.c, the m68k quirc objects) - without this step a PR - # that breaks GUI compilation merges green and only fails at release - # tag time. - - name: Cross-build GUI for 68000 - run: PATH="/opt/amiga/bin:$PATH" make gui M68K_CC=m68k-amigaos-gcc - - asm-crypto-tests: - name: m68k asm crypto tests (vamos) - runs-on: ubuntu-latest - needs: changes - if: needs.changes.outputs.build == 'true' - steps: - - uses: actions/checkout@v7 - - name: Install amitools (vamos) - run: pip install 'amitools[vamos]' - - name: Cross-build the asm test binaries - run: make asm-tests-docker - # Every existing SHA-1/HMAC/PBKDF2 RFC vector, run against the - # hand-written asm (#47) instead of the C reference, on both the plain - # 68000 baseline and 68020+ - the asm is meant to be correct on every - # CPU tier this project supports, not just an accelerated one. ChaCha20 - # has no asm path (measured slower than C on real hardware; see - # src/core/crypto_dispatch.h), so there's nothing to validate here. - - name: Validate against RFC vectors (68000 + 68020) - run: | - for cpu in 000 020; do - echo "--- CPU $cpu ---" - vamos -C "$cpu" build/asm-test-sha1 - done - - copperline-smoke: - name: Copperline on-target smoke test (AROS) - runs-on: ubuntu-latest - needs: changes - if: needs.changes.outputs.target == 'true' - env: - # Pin the Copperline release; bump to adopt upstream changes. - COPPERLINE_VERSION: "0.14.0" - steps: - - uses: actions/checkout@v7 - - # Software Vulkan (lavapipe), in case the emulator initialises wgpu at - # start-up; the test itself runs windowless via --benchmark-until. - - name: Install runtime deps - run: sudo apt-get update && sudo apt-get install -y mesa-vulkan-drivers - - # Use the prebuilt AppImage instead of compiling from source — a ~9 MB - # download vs a multi-minute cargo build. It bundles the emulator, its - # libs, and the redistributable AROS Kickstart replacement (no licensed - # ROM needed). Extracted (no FUSE on runners) and put on PATH. - - name: Install Copperline (prebuilt AppImage) - run: | - url="https://github.com/LinuxJedi/Copperline/releases/download/v${COPPERLINE_VERSION}/Copperline-${COPPERLINE_VERSION}-x86_64.AppImage" - curl -fsSL "$url" -o copperline.AppImage - chmod +x copperline.AppImage - ./copperline.AppImage --appimage-extract >/dev/null - mkdir -p "$HOME/.local/bin" - ln -sf "$PWD/squashfs-root/AppRun" "$HOME/.local/bin/copperline" - echo "$HOME/.local/bin" >> "$GITHUB_PATH" - # Point at the AppImage's bundled AROS explicitly, so resolution - # doesn't depend on the extracted layout. - aros_bin=$(find "$PWD/squashfs-root" -name aros-amiga-m68k-rom.bin | head -1) - [ -n "$aros_bin" ] && echo "COPPERLINE_AROS_DIR=$(dirname "$aros_bin")" >> "$GITHUB_ENV" - - # No KICK set -> run.sh boots the bundled AROS (no ROM needed). - - name: On-target smoke test - run: make copperline-smoke diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6a7d1e3..170e727 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,18 +1,17 @@ name: Docs -# Publishes the versioned user-docs site (MkDocs Material + mike) to GitHub -# Pages: each release tag deploys its docs as a named version (v1.0, v1.1, -# ...) plus the floating `latest` alias, giving the site a per-release -# version picker. Day-to-day docs edits on main do NOT deploy - the site is -# refreshed at release time (PRs get a strict build check in ci.yml). +# Publishes the versioned user-docs site via sidick/amiga-workflows' docs.yml +# (MkDocs Material + mike -> GitHub Pages, each release tag deploys as a +# named version plus the floating `latest` alias). Day-to-day docs edits on +# main do NOT deploy -- the site is refreshed at release time (PRs get a +# strict build check in ci.yml's docs-build job). # -# workflow_dispatch exists for the initial publish (the v1.0 tag predates -# this workflow, so its docs deploy from current main) and for re-publishing -# a version after a docs-only fix. +# workflow_dispatch exists for re-publishing a version after a docs-only +# fix, or for a first publish ahead of the next release tag. # -# One-time repo setup: Settings -> Pages -> Source: "GitHub Actions" is NOT -# used here - mike works by committing the built site to the gh-pages -# branch, so Pages must be set to serve from gh-pages / root. +# One-time repo setup: Settings -> Pages -> Source: "Deploy from a branch" / +# gh-pages / root -- mike commits the built site to gh-pages itself, it +# does not use the "GitHub Actions" Pages source. on: push: @@ -23,32 +22,16 @@ on: description: 'Version to publish (e.g. v1.0)' required: true +# A reusable workflow's own `permissions:` block can only narrow, never +# widen, what the calling job already has - so this repo's own contents: +# write grant (mike pushes to gh-pages) has to be declared here too, not +# just relied on inside amiga-workflows/docs.yml. The old inline docs.yml +# declared this same permission for the same reason. permissions: contents: write jobs: - deploy: - name: Publish versioned docs site - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 0 # mike commits to gh-pages; needs the full branch - - - uses: actions/setup-python@v7 - with: - python-version: '3.14' - - - name: Install docs toolchain - run: pip install -r tools/docs-requirements.txt - - - name: Strict build first (fail before touching gh-pages) - run: mkdocs build --strict - - - name: Deploy versioned site with mike - run: | - version="${{ inputs.version || github.ref_name }}" - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - mike deploy --push --update-aliases "$version" latest - mike set-default --push latest + docs: + uses: sidick/amiga-workflows/.github/workflows/docs.yml@v1 + with: + version: ${{ inputs.version || github.ref_name }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c9538b6..f3fefd6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,116 +1,40 @@ name: Release # Tag-driven releases: a release PR bumps src/version.h + AmiAuth.readme -# through normal review/CI, then pushing the matching v tag runs this. -# The GitHub release (with the Aminet package attached) is created -# automatically; the actual Aminet FTP upload waits for manual approval via the -# `aminet` environment. +# through normal review/CI, then pushing the matching v tag runs +# this. verify-version confirms the pushed tag actually matches those two +# files; release then delegates the build -> GitHub release -> Aminet- +# upload-behind-required-review pipeline to sidick/amiga-workflows' +# aminet-release.yml. The old inline $VER-embedded-in-binary check moved +# into the Makefile's own `dist` target (see its comment there) - +# aminet-release.yml's dist job runs `make dist` standalone with no +# separate build step to hang a caller-side check off of. on: push: tags: ['v*'] -permissions: - contents: write - jobs: - release: - name: Build and publish GitHub release + verify-version: + name: Verify tag matches src/version.h / AmiAuth.readme runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 + - run: ./scripts/verify-version.sh "${{ github.ref_name }}" - - name: Verify tag matches the source version - run: | - tag="${GITHUB_REF_NAME#v}" - src=$(sed -n 's/^#define AMIAUTH_VERSION[[:space:]]*"\(.*\)"$/\1/p' src/version.h) - readme=$(sed -n 's/^Version:[[:space:]]*\(.*\)$/\1/p' AmiAuth.readme) - echo "tag=$tag src/version.h=$src AmiAuth.readme=$readme" - [ "$tag" = "$src" ] || { echo "::error file=src/version.h::Tag v$tag does not match AMIAUTH_VERSION \"$src\""; exit 1; } - [ "$tag" = "$readme" ] || { echo "::error file=AmiAuth.readme::Tag v$tag does not match Version: \"$readme\""; exit 1; } - - - name: Cross-build m68k binaries (CLI + GUI) - run: make m68k-docker gui-docker - - - name: Check the $VER strings made it into the binaries - run: | - v="${GITHUB_REF_NAME#v}" - for b in AmiAuth AmiAuthGUI; do - # -F: fixed-string match (the pattern contains $ and parens); - # -a: search the binary as text. - grep -aqF "\$VER: $b $v (" "build/$b" || { echo "::error::build/$b lacks \"\$VER: $b $v (...)\""; exit 1; } - done - - # `make dist` builds its own pinned lha (Ubuntu's lhasa is extract-only) - - name: Package the Aminet upload pair - run: make dist - - - name: Validate the readme (no upload) - uses: sidick/aminet-release-action@v1 - with: - filename: build/dist/AmiAuth.lha - readme: build/dist/AmiAuth.readme - category: util/crypt - validate-only: true - - - name: Create GitHub release with the package attached - env: - GH_TOKEN: ${{ github.token }} - run: | - if [ "$(gh release list --limit 1 --json tagName -q 'length')" = "0" ]; then - # First-ever release: a --generate-notes PR-title dump has - # nothing meaningful to diff against (every merged PR since the - # repo's creation), so use a curated body sourced from - # AmiAuth.readme's own intro + Features section instead. Later - # releases have a previous tag to diff against, so they fall - # through to --generate-notes below, which becomes genuinely - # useful once there's real release-to-release history. - { - sed -n '/^Distribution:/,/^Features$/p' AmiAuth.readme | sed '1d;$d;/^--------$/d' - echo "**Features:**" - echo - sed -n '/^Features$/,/^Requirements$/p' AmiAuth.readme | sed '1d;$d;/^--------$/d;/^$/d' - echo - echo "See the [documentation site](https://sidick.github.io/amiauth/) for full documentation, and [docs/SECURITY.md](https://github.com/sidick/amiauth/blob/main/docs/SECURITY.md) for the honest threat model." - } > /tmp/release-notes.md - gh release create "$GITHUB_REF_NAME" \ - --title "AmiAuth $GITHUB_REF_NAME" \ - --notes-file /tmp/release-notes.md \ - build/dist/AmiAuth.lha build/dist/AmiAuth.readme - else - gh release create "$GITHUB_REF_NAME" \ - --title "AmiAuth $GITHUB_REF_NAME" \ - --generate-notes \ - build/dist/AmiAuth.lha build/dist/AmiAuth.readme - fi - - - name: Keep the package for the Aminet job - uses: actions/upload-artifact@v7 - with: - name: aminet-dist - path: | - build/dist/AmiAuth.lha - build/dist/AmiAuth.readme - if-no-files-found: error - - aminet: - name: Upload to Aminet (waits for approval) - runs-on: ubuntu-latest - needs: release - # The `aminet` environment has a required reviewer, so this job pauses - # until the upload is explicitly approved in the Actions UI. Publishing to - # Aminet is irreversible-ish (uploads land in /new for moderation) - keep - # the human in the loop. - environment: aminet - steps: - - uses: actions/download-artifact@v8 - with: - name: aminet-dist - path: build/dist - - - name: Upload to Aminet and attach to the GitHub release - uses: sidick/aminet-release-action@v1 - with: - filename: build/dist/AmiAuth.lha - readme: build/dist/AmiAuth.readme - category: util/crypt + release: + name: Build, release, and (on approval) publish to Aminet + needs: verify-version + # A reusable workflow's own `permissions:` can only narrow, never + # widen, what the calling job already has - aminet-release.yml's + # `gh release create` step needs contents: write, so it has to be + # granted here too, not just relied on inside that workflow. + permissions: + contents: write + uses: sidick/amiga-workflows/.github/workflows/aminet-release.yml@v1 + with: + tag: ${{ github.ref_name }} + project-name: AmiAuth + lha-path: build/dist/AmiAuth.lha + readme-path: build/dist/AmiAuth.readme + category: util/crypt diff --git a/AmiAuth.readme b/AmiAuth.readme index 1d88e5e..37531cd 100644 --- a/AmiAuth.readme +++ b/AmiAuth.readme @@ -2,7 +2,7 @@ Short: TOTP/HOTP two-factor authenticator Author: simond@irrelevant.org (Simon Dick) Uploader: simond@irrelevant.org (Simon Dick) Type: util/crypt -Version: 1.0 +Version: 1.1 Architecture: m68k-amigaos >= 2.0.4 Distribution: Aminet diff --git a/Makefile b/Makefile index f0e9d89..3399e45 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ endif # Containerised cross-build: same image as CI, so local m68k builds match. DOCKER ?= docker -AMIGA_GCC_IMAGE ?= ghcr.io/reinauer/container-amiga-gcc:latest +AMIGA_GCC_IMAGE ?= ghcr.io/sidick/amiga-dev:1 # Run as the calling user, not root: the container bind-mounts $(CURDIR), and # without this, files it creates (build/, the m68k binaries) come out root- # owned on Linux hosts - breaking any later non-Docker step (e.g. `make dist`) @@ -104,22 +104,57 @@ QUIRC_M68K_OBJS := $(patsubst src/qr/%.c,$(BUILD)/qr-m68k/%.o,$(QUIRC_SRCS)) QRCODEGEN_HOST_OBJ := $(BUILD)/qr-host/qrcodegen.o QRCODEGEN_M68K_OBJ := $(BUILD)/qr-m68k/qrcodegen.o -.PHONY: all test cli smoke diff m68k m68k-docker gui gui-docker gui-smoke qr-onhw qr-onhw-docker qr-onhw-smoke arexx-onhw arexx-onhw-docker arexx-onhw-smoke serialtest-m68k serialtest-m68k-docker copperline-smoke pbkdf2-bench asm-bench amissl-bench flexcat flexcat-docker catalog-strings catalog-strings-docker check-catalog catalog-onhw-smoke catalog-nolib-onhw catalog-nolib-onhw-docker asm-tests asm-tests-docker guide dist movepointer movepointer-docker clean +.PHONY: all test cli smoke diff m68k m68k-docker gui gui-docker gui-smoke qr-onhw qr-onhw-docker qr-onhw-smoke arexx-onhw arexx-onhw-docker arexx-onhw-smoke serialtest-m68k serialtest-m68k-docker copperline-smoke pbkdf2-bench asm-bench amissl-bench flexcat flexcat-docker catalog-strings catalog-strings-docker check-catalog catalog-onhw-smoke catalog-nolib-onhw catalog-nolib-onhw-docker asm-tests asm-tests-docker guide dist movepointer movepointer-docker clean build test-host test-target lint all: test cli +# --- Verb contract (sidick/amiga-workflows' build-test.yml) --------------- +# ci.yml calls these five names; each build-test.yml job is independent (no +# artifact-passing between them). The named targets below (test/cli/smoke/ +# m68k/gui/copperline-smoke/check-catalog/...) stay as the documented local +# entry points - README.md/CLAUDE.md/userdocs/ reference them already, so +# this adds the contract's names rather than renaming what they point to. +build: m68k gui + +# "Host-side tests: vectors, portable core, vamos runs" (the verb contract's +# own definition) maps onto three previously-separate things: RFC vector +# tests + native CLI + e2e CLI smoke (host-tests job), the m68k asm crypto +# tests built here and validated via vamos below (asm-crypto-tests job) - +# one verb, not two CI jobs, since build-test.yml's test-host job already +# runs inside the amiga-dev image (has cc, m68k-amigaos-gcc, and vamos all +# at once - no need for asm-tests-docker's nested container indirection). +test-host: test cli smoke asm-tests + for cpu in 000 020; do \ + echo "--- CPU $$cpu ---"; \ + vamos -C "$$cpu" $(BUILD)/asm-test-sha1 || exit 1; \ + done + +# Matches today's copperline-smoke job exactly (RFC 4226 HOTP core on real +# m68k) - not gui-smoke/qr-onhw-smoke/arexx-onhw-smoke/catalog-onhw-smoke, +# none of which run in CI today (dev-only Copperline checks, need more +# local setup than a fresh checkout provides). AMIGA_REAL_ROM, if the +# workflow decoded one from the optional AMIGA_REAL_ROM_B64 secret, becomes +# KICK=; unset (the common case), boots Copperline's bundled AROS instead. +test-target: serialtest-m68k + KICK="$${AMIGA_REAL_ROM:-}" sh tests/copperline/run.sh + +# The only static/structural check this repo has today - catalog-lint's own +# job folds in here rather than staying separate, unlike sana2loop's +# docs-build (which had no verb-contract home at all). +lint: check-catalog + # --- Host: unit / RFC-vector tests --- test: $(BUILD)/run-tests VAULT_TEST_FILE=$(BUILD)/amiauth-test.vault \ AMIAUTH_PREFS_DIR=$(BUILD)/prefs-test $(BUILD)/run-tests -$(BUILD)/run-tests: $(CORE_SRCS) $(TEST_SRCS) $(QR_WRAP) $(QUIRC_HOST_OBJS) $(QRENC_WRAP) $(QRCODEGEN_HOST_OBJ) $(CORE_HDRS) $(QR_HDRS) $(TEST_HDRS) | $(BUILD) +$(BUILD)/run-tests: $(CORE_SRCS) $(TEST_SRCS) $(QR_WRAP) $(QUIRC_HOST_OBJS) $(QRENC_WRAP) $(QRCODEGEN_HOST_OBJ) $(CORE_HDRS) $(QR_HDRS) $(TEST_HDRS) | $(BUILD)/.dir $(CC) $(CFLAGS) $(CObjINC) $(QR_CPPFLAGS) -Itests \ $(CORE_SRCS) $(TEST_SRCS) $(QR_WRAP) $(QUIRC_HOST_OBJS) \ $(QRENC_WRAP) $(QRCODEGEN_HOST_OBJ) -o $@ # Vendored quirc objects — host toolchain, warnings suppressed (third-party). -$(BUILD)/qr-host/%.o: src/qr/%.c $(QR_HDRS) | $(BUILD) +$(BUILD)/qr-host/%.o: src/qr/%.c $(QR_HDRS) | $(BUILD)/.dir @mkdir -p $(BUILD)/qr-host $(CC) $(CFLAGS) -w $(QR_CPPFLAGS) -c $< -o $@ @@ -129,7 +164,7 @@ $(BUILD)/qr-host/%.o: src/qr/%.c $(QR_HDRS) | $(BUILD) # command): our qrencode.c wrapper + the vendored qrcodegen object. cli: $(BUILD)/amiauth-host -$(BUILD)/amiauth-host: $(CORE_SRCS) $(CLI_SRCS) $(QRENC_WRAP) $(QRCODEGEN_HOST_OBJ) $(CORE_HDRS) $(AMIGA_HDRS) $(QR_HDRS) | $(BUILD) +$(BUILD)/amiauth-host: $(CORE_SRCS) $(CLI_SRCS) $(QRENC_WRAP) $(QRCODEGEN_HOST_OBJ) $(CORE_HDRS) $(AMIGA_HDRS) $(QR_HDRS) | $(BUILD)/.dir $(CC) $(CFLAGS) $(CObjINC) -Isrc/qr $(CORE_SRCS) $(CLI_SRCS) \ $(QRENC_WRAP) $(QRCODEGEN_HOST_OBJ) -o $@ @@ -141,14 +176,14 @@ smoke: $(BUILD)/amiauth-host diff: $(BUILD)/run-diff $(BUILD)/run-diff $(DIFF_ITERS) -$(BUILD)/run-diff: $(CORE_SRCS) $(DIFF_SRCS) $(CORE_HDRS) | $(BUILD) +$(BUILD)/run-diff: $(CORE_SRCS) $(DIFF_SRCS) $(CORE_HDRS) | $(BUILD)/.dir $(CC) $(CFLAGS) $(CObjINC) $(OPENSSL_CFLAGS) $(CORE_SRCS) $(DIFF_SRCS) \ $(OPENSSL_LIBS) -o $@ # --- m68k: Amiga CLI binary (amiga-gcc on PATH) --- # Includes the QR encoder (#45's CLI QR command): see the qr-m68k object rule # below (shared with the GUI's decoder build). -m68k: $(QRCODEGEN_M68K_OBJ) | $(BUILD) +m68k: $(QRCODEGEN_M68K_OBJ) | $(BUILD)/.dir $(M68K_CC) $(M68K_CFLAGS) -Isrc/qr $(CORE_SRCS) $(ASM_SRCS) $(AMIGA_SRCS) $(CLI_SRCS) \ $(QRENC_WRAP) $(QRCODEGEN_M68K_OBJ) -o $(BUILD)/AmiAuth @@ -159,7 +194,7 @@ m68k: $(QRCODEGEN_M68K_OBJ) | $(BUILD) # components - see tests/gui/catalog-onhw.sh). Never shipped. CATALOG_NOLIB_DEFS := -DAMIAUTH_LOCALE_LIBNAME='"locale.library.nonexistent"' -catalog-nolib-onhw: $(QRCODEGEN_M68K_OBJ) | $(BUILD) +catalog-nolib-onhw: $(QRCODEGEN_M68K_OBJ) | $(BUILD)/.dir $(M68K_CC) $(M68K_CFLAGS) $(CATALOG_NOLIB_DEFS) -Isrc/qr $(CORE_SRCS) $(ASM_SRCS) $(AMIGA_SRCS) $(CLI_SRCS) \ $(QRENC_WRAP) $(QRCODEGEN_M68K_OBJ) -o $(BUILD)/AmiAuth-nolib @@ -174,11 +209,11 @@ catalog-nolib-onhw-docker: GUI_SRCS := src/gui/main.c src/amiga/qrimage.c src/amiga/arexx.c # Vendored quirc objects — m68k toolchain, warnings suppressed (third-party). -$(BUILD)/qr-m68k/%.o: src/qr/%.c $(QR_HDRS) | $(BUILD) +$(BUILD)/qr-m68k/%.o: src/qr/%.c $(QR_HDRS) | $(BUILD)/.dir @mkdir -p $(BUILD)/qr-m68k $(M68K_CC) $(M68K_CFLAGS) -w $(QR_CPPFLAGS) -c $< -o $@ -gui: $(QUIRC_M68K_OBJS) $(QRCODEGEN_M68K_OBJ) | $(BUILD) +gui: $(QUIRC_M68K_OBJS) $(QRCODEGEN_M68K_OBJ) | $(BUILD)/.dir $(M68K_CC) $(M68K_CFLAGS) $(VERSION_DEFS) $(QR_CPPFLAGS) $(CORE_SRCS) $(ASM_SRCS) $(AMIGA_SRCS) $(GUI_SRCS) \ $(QR_WRAP) $(QUIRC_M68K_OBJS) $(QRENC_WRAP) $(QRCODEGEN_M68K_OBJ) -lm -lamiga -o $(BUILD)/AmiAuthGUI @@ -197,7 +232,7 @@ gui-docker: # clang chokes on FlexCat's own build flags, so use `flexcat-docker` there. flexcat: $(BUILD)/flexcat -$(BUILD)/flexcat: | $(BUILD) +$(BUILD)/flexcat: | $(BUILD)/.dir src=$$(bash tools/fetch-flexcat.sh) && \ cp "$$src" $(BUILD)/flexcat && chmod +x $(BUILD)/flexcat && \ cp "$$(dirname $$src)/../sd/CatComp_h.sd" $(BUILD)/CatComp_h.sd @@ -240,7 +275,7 @@ gui-smoke: $(BUILD)/amiauth-host QR_ONHW_DEFS ?= QR_ONHW_SRCS := tests/gui/qr_onhw.c src/amiga/qrimage.c $(QR_WRAP) -qr-onhw: $(QUIRC_M68K_OBJS) | $(BUILD) +qr-onhw: $(QUIRC_M68K_OBJS) | $(BUILD)/.dir $(M68K_CC) $(M68K_CFLAGS) $(QR_CPPFLAGS) $(QR_ONHW_DEFS) $(QR_ONHW_SRCS) \ $(QUIRC_M68K_OBJS) -lm -lamiga -o $(BUILD)/qr-onhw @@ -264,7 +299,7 @@ m68k-docker: # any other target here). asm-tests: $(BUILD)/asm-test-sha1 -$(BUILD)/asm-test-sha1: tests/asm/test_sha1_asm.c $(CORE_SRCS) $(ASM_SRCS) $(CORE_HDRS) $(TEST_HDRS) | $(BUILD) +$(BUILD)/asm-test-sha1: tests/asm/test_sha1_asm.c $(CORE_SRCS) $(ASM_SRCS) $(CORE_HDRS) $(TEST_HDRS) | $(BUILD)/.dir $(M68K_CC) $(M68K_CFLAGS) -Itests tests/asm/test_sha1_asm.c \ tests/test_sha1.c tests/test_hmac.c tests/test_pbkdf2.c tests/test_kdf.c \ $(CORE_SRCS) $(ASM_SRCS) src/amiga/prefs.c -lamiga -o $@ @@ -281,7 +316,7 @@ SERIALTEST_SRCS := src/core/otp.c src/core/hmac.c src/core/sha1.c \ src/core/sha256.c src/core/sha512.c src/core/steamguard.c \ src/core/drbg.c tests/copperline/serialtest.c -serialtest-m68k: | $(BUILD) +serialtest-m68k: | $(BUILD)/.dir $(M68K_CC) $(M68K_CFLAGS) $(SERIALTEST_SRCS) -o $(BUILD)/serialtest serialtest-m68k-docker: @@ -296,7 +331,7 @@ copperline-smoke: serialtest-m68k-docker # the WB image's resident RexxMast (`rx`) against AMIAUTH.1, then relay its # redirected output back over serial with this small m68k program (arexxtest). # See tests/gui/arexx-onhw.sh. -arexx-onhw: | $(BUILD) +arexx-onhw: | $(BUILD)/.dir $(M68K_CC) $(M68K_CFLAGS) tests/copperline/arexxtest.c -o $(BUILD)/arexxtest arexx-onhw-docker: @@ -334,7 +369,7 @@ amissl-bench: # --- guide: AmigaGuide user documentation, generated from userdocs/ ---------- # userdocs/ is the single source of truth for user docs (published as the # MkDocs site); this converts it for on-Amiga reading (MultiView/AmigaGuide). -guide: | $(BUILD) +guide: | $(BUILD)/.dir python3 tools/docs2guide.py userdocs $(BUILD)/AmiAuth.guide # --- lha: build the real LHa for UNIX (archive-capable), pinned -------------- @@ -359,14 +394,24 @@ $(BUILD)/tools/lha: rm -rf $(BUILD)/tools/lha-src # --- dist: assemble the Aminet upload pair (archive + .readme) --------------- -# Expects prebuilt m68k binaries (make m68k-docker gui-docker); the lha -# archiver is built automatically (above). Produces build/dist/AmiAuth.lha -# (drawer with binaries, docs, icons) and build/dist/AmiAuth.readme alongside -# — the two files Aminet wants. Icons: the drawer icon sits next to the -# drawer; the CLI deliberately has no icon (it is a Shell command). -dist: guide $(LHA) - @test -f $(BUILD)/AmiAuth -a -f $(BUILD)/AmiAuthGUI || \ - { echo "dist: missing m68k binaries; run: make m68k-docker gui-docker"; exit 1; } +# Builds the m68k binaries itself (build-test.yml's dist job runs `make +# dist` standalone, with no prior `build` job's artifacts to reuse - see the +# verb-contract comment above); the lha archiver is built automatically +# (above). Produces build/dist/AmiAuth.lha (drawer with binaries, docs, +# icons) and build/dist/AmiAuth.readme alongside it — the two files Aminet +# wants. Icons: the drawer icon sits next to the drawer; the CLI +# deliberately has no icon (it is a Shell command). +# +# The $VER grep below confirms the binaries just built actually embed the +# CURRENT src/version.h AMIAUTH_VERSION - the release workflow's own +# tag-vs-source check (scripts/verify-version.sh, run before this target) +# separately confirms the git tag matches src/version.h itself, closing the +# loop: tag == src/version.h == what's actually in the binaries. +dist: build guide $(LHA) + @v=$$(sed -n 's/^#define AMIAUTH_VERSION[[:space:]]*"\(.*\)"$$/\1/p' src/version.h); \ + for b in AmiAuth AmiAuthGUI; do \ + grep -aqF "\$$VER: $$b $$v (" $(BUILD)/$$b || { echo "dist: $(BUILD)/$$b lacks \"\$$VER: $$b $$v (...)\" - stale build/?"; exit 1; }; \ + done rm -rf $(BUILD)/dist mkdir -p $(BUILD)/dist/AmiAuth cp $(BUILD)/AmiAuth $(BUILD)/AmiAuthGUI $(BUILD)/AmiAuth.guide \ @@ -383,7 +428,7 @@ dist: guide $(LHA) # from source with our own toolchain rather than trusting the original # Aminet prebuilt binary; -w since it's third-party (mirrors quirc's # treatment) - the warnings are 1987 K&R implicit-declarations, harmless. -movepointer: | $(BUILD) +movepointer: | $(BUILD)/.dir $(M68K_CC) -w -m68000 -noixemul tests/tools/movepointer/movepointer.c \ -lamiga -o $(BUILD)/MovePointer @@ -391,8 +436,16 @@ movepointer-docker: $(DOCKER) run --rm --platform linux/amd64 $(DOCKER_USER) -v "$(CURDIR)":/work -w /work \ $(AMIGA_GCC_IMAGE) sh -lc 'PATH=/opt/amiga/bin:$$PATH make movepointer' -$(BUILD): - mkdir -p $(BUILD) +# A marker file, not $(BUILD) itself: BUILD's value is literally the string +# "build", so a target named $(BUILD) would collide with the verb-contract +# `build:` target below (Make merges prerequisites across every rule +# sharing one target name) - every one of this file's `| $(BUILD)/.dir` +# order-only prerequisites would silently gain build's own m68k+gui +# prerequisites, breaking e.g. `make test` on a host with no cross-compiler. +# Reproduced and confirmed before making this change; see amiga-dev's +# docs/plan.md Phase 3 entry for the same bug caught for real in sana2loop. +$(BUILD)/.dir: + @mkdir -p $(BUILD)/.dir clean: rm -rf $(BUILD) diff --git a/mkdocs.yml b/mkdocs.yml index d7ce4b4..e52f07d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -60,3 +60,4 @@ nav: - Help: - Troubleshooting and FAQ: Troubleshooting-and-FAQ.md - Building from Source: Building-from-Source.md + - Changelog: Changelog.md diff --git a/scripts/verify-version.sh b/scripts/verify-version.sh new file mode 100755 index 0000000..1c47c31 --- /dev/null +++ b/scripts/verify-version.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# Verifies a release tag (e.g. v1.1) matches src/version.h and AmiAuth.readme +# before release.yml hands off to sidick/amiga-workflows' aminet-release.yml +# - the calling repo's own version-file format is project-specific, so this +# check stays here rather than in the shared workflow (see that repo's own +# aminet-release.yml header comment for why). +# +# Usage: scripts/verify-version.sh vX.Y +set -eu + +tag_ref="${1:?usage: verify-version.sh }" +tag="${tag_ref#v}" + +src=$(sed -n 's/^#define AMIAUTH_VERSION[[:space:]]*"\(.*\)"$/\1/p' src/version.h) +readme=$(sed -n 's/^Version:[[:space:]]*\(.*\)$/\1/p' AmiAuth.readme) + +echo "tag=$tag src/version.h=$src AmiAuth.readme=$readme" +[ "$tag" = "$src" ] || { echo "::error file=src/version.h::Tag v$tag does not match AMIAUTH_VERSION \"$src\""; exit 1; } +[ "$tag" = "$readme" ] || { echo "::error file=AmiAuth.readme::Tag v$tag does not match Version: \"$readme\""; exit 1; } diff --git a/src/cli/main.c b/src/cli/main.c index 3b0a0a6..a8c5b36 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -18,6 +18,10 @@ * salt/nonce come from POSIX /dev/tty + /dev/urandom on the host, and from the * AmigaOS RAW console + entropy source (src/amiga/random.c) on hardware; where * no secure RNG exists, encrypted create/save is cleanly refused. */ +/* fileno() (read_passphrase, below) is POSIX, not C99 - needs an explicit + * feature-test macro before any header is included, or a strict-C99 glibc + * hides it. Must come before every #include, not just . */ +#define _POSIX_C_SOURCE 200809L #include #include #include diff --git a/src/version.h b/src/version.h index 5cbc6ac..a242d43 100644 --- a/src/version.h +++ b/src/version.h @@ -9,8 +9,8 @@ * * AMIAUTH_VERSION_DATE is the AmigaOS $VER date,
.. per * https://wiki.amigaos.net/wiki/Version_Strings */ -#define AMIAUTH_VERSION "1.0" -#define AMIAUTH_VERSION_DATE "20.07.2026" +#define AMIAUTH_VERSION "1.1" +#define AMIAUTH_VERSION_DATE "28.07.2026" /* Embedded AmigaOS version string, findable by the shell `Version` command. * The leading "\0" guards against an adjacent string in the binary running diff --git a/tools/docs2guide.py b/tools/docs2guide.py index f39faee..3a0bb13 100644 --- a/tools/docs2guide.py +++ b/tools/docs2guide.py @@ -39,6 +39,7 @@ ('Security-Model', 'Security Model'), ('Troubleshooting-and-FAQ', 'Troubleshooting and FAQ'), ('Building-from-Source', 'Building from Source'), + ('Changelog', 'Changelog'), ] SLUGS = {slug for slug, _ in PAGES} diff --git a/userdocs/ARexx-Port.md b/userdocs/ARexx-Port.md index 9e07dca..b01cbb6 100644 --- a/userdocs/ARexx-Port.md +++ b/userdocs/ARexx-Port.md @@ -16,7 +16,7 @@ unattended. See [Security Model](Security-Model.md). The port is named `AMIAUTH.` (uppercase), where `` is the lowest free slot — normally `AMIAUTH.1`. It's shown in the main window's title bar once -open, e.g. `AmiAuth 1.0 (abc1234) [AMIAUTH.1]`. Override it with the +open, e.g. `AmiAuth 1.1 (abc1234) [AMIAUTH.1]`. Override it with the `PORTNAME` tooltype/argument, the same way as `VAULT`/`PUBSCREEN` (see [Commodity and Tooltypes](Commodity-and-Tooltypes.md)): diff --git a/userdocs/Changelog.md b/userdocs/Changelog.md new file mode 100644 index 0000000..b52ee40 --- /dev/null +++ b/userdocs/Changelog.md @@ -0,0 +1,45 @@ +# Changelog + +What changed in each AmiAuth release, most recent first. For the full +commit-level history see the +[GitHub releases](https://github.com/sidick/amiauth/releases). + +## 1.1 (28.07.2026) + +### New features + +- **SHA-256 and SHA-512 TOTP variants** — accounts using + `algorithm=SHA256`/`SHA512` in their otpauth URI now work; see + [Managing Accounts](Managing-Accounts.md). +- **Steam Guard support** — Steam's 5-character alphanumeric TOTP variant. +- **QR code export** — display an account as a QR code to scan with a phone: + on-screen in the GUI, ASCII-art in the CLI. +- **ARexx port** — automate the running GUI from scripts; see + [ARexx Port](ARexx-Port.md). +- **Localization** — all CLI and GUI strings can now come from + locale.library catalogs, with fallback through each of your preferred + languages; see [Localization](Localization.md). +- **Bare Base32 secrets** — when adding an account you can paste just the + Base32 secret, without an `otpauth://` wrapper. +- **GUI `VAULT` argument** — the GUI accepts a vault path as a tooltype or + Shell argument, like the CLI. + +### Improvements + +- Faster code generation on plain 68000s: the SHA-1 hot loop is now + hand-written m68k assembly. +- The GUI opens its window immediately and runs the startup SNTP sync + afterwards, instead of blocking on the network first. + +### Fixes + +- Security hardening: all findings from the July 2026 independent code + audits fixed (high, medium and low severity). +- Updated the vendored quirc QR decoder, fixing memory-safety bugs on + malformed QR input. + +## 1.0 (20.07.2026) + +Initial release: TOTP (RFC 6238) and HOTP (RFC 4226) code generation, +encrypted vault, CLI for AmigaOS 2.04+ and ReAction GUI for AmigaOS 3.0+, +QR import, SNTP clock sync, and commodity support. diff --git a/userdocs/Installation.md b/userdocs/Installation.md index b767a0a..016facd 100644 --- a/userdocs/Installation.md +++ b/userdocs/Installation.md @@ -92,7 +92,7 @@ Both binaries carry a standard AmigaOS version string, so the Shell `Version` command reports it: > Version AmiAuth - AmiAuth 1.0 (20.07.2026) + AmiAuth 1.1 (28.07.2026) (Also works for `AmiAuthGUI`, and matches the release tag on GitHub.) diff --git a/userdocs/index.md b/userdocs/index.md index 9df6102..3e1d397 100644 --- a/userdocs/index.md +++ b/userdocs/index.md @@ -1,8 +1,8 @@ # AmiAuth -> 🎉 **AmiAuth 1.0 is released!** Grab it from +> 🎉 **AmiAuth 1.1 is released!** Grab it from > [Aminet](https://aminet.net/package/util/crypt/AmiAuth) or the -> [GitHub release](https://github.com/sidick/amiauth/releases/tag/v1.0), +> [GitHub release](https://github.com/sidick/amiauth/releases/tag/v1.1), > then head to [Installation](Installation.md) to get going. **A native two-factor authentication (2FA) code generator for classic AmigaOS.**