From ae54f74070b06c45a00b04f79a219906a4002beb Mon Sep 17 00:00:00 2001 From: Liam Waldron Date: Wed, 14 Oct 2026 00:50:56 -0400 Subject: [PATCH] 10/14 --- .gitignore | 2 + CLAUDE.md | 685 ++++- GLACIER_BOOTSTRAP.txt | 92 +- IMAGE_DESIGN.txt | 19 +- Makefile | 46 +- PACKAGE_SCOPES.txt | 7 + REPO_DESIGN.txt | 446 +++ ROADMAP.txt | 173 +- SECURITY_DESIGN.txt | 14 + SECURITY_REVIEW_2026-07-16.md | 6 + SECURITY_REVIEW_2026-10-06.md | 62 + build/bin/gsha256sum | Bin 16784 -> 0 bytes build/include/glacier/log.h | 41 - build/lib/shared/libglacier_dag.so | Bin 15952 -> 0 bytes build/lib/shared/libglacier_istoreutils.so | Bin 62312 -> 0 bytes build/lib/shared/libglacier_log.so | Bin 16608 -> 0 bytes build/lib/shared/libglacier_pkg.so | Bin 21768 -> 0 bytes build/lib/shared/libglacier_transaction.so | Bin 20936 -> 0 bytes build/lib/shared/libglacier_verify.so | Bin 15968 -> 0 bytes build/lib/tmp/log.o | Bin 14000 -> 0 bytes build/lib/tmp/make_conf.o | Bin 3680 -> 0 bytes build/lib/tmp/runtime.o | Bin 2008 -> 0 bytes build/lib/tmp/sha256.o | Bin 5744 -> 0 bytes config.mk | 1 - src/common.h | 191 +- src/dag.c | 132 - src/dag.h | 34 - src/deps.c | 727 +++++ src/deps.h | 55 + src/grepo.c | 389 +++ src/gsha256sum.c | 44 +- src/gsign.c | 212 ++ src/istoreutils.c | 868 +++--- src/istoreutils.h | 35 +- src/log.c | 134 +- src/log.h | 15 +- src/make_conf.c | 95 - src/monocypher/LICENCE.md | 167 ++ src/monocypher/README | 14 + src/monocypher/monocypher-ed25519.c | 503 ++++ src/monocypher/monocypher-ed25519.h | 140 + src/monocypher/monocypher.c | 2988 ++++++++++++++++++++ src/monocypher/monocypher.h | 321 +++ src/pkg.c | 759 +++-- src/pkg.h | 62 +- src/runtime.c | 45 - src/sign.c | 428 +++ src/sign.h | 82 + src/transaction.c | 229 +- src/transaction.h | 77 +- src/x_log | Bin 16616 -> 0 bytes src/x_pkg | Bin 30304 -> 0 bytes test_backup | Bin 15952 -> 0 bytes 53 files changed, 8916 insertions(+), 1424 deletions(-) create mode 100644 .gitignore create mode 100644 REPO_DESIGN.txt create mode 100644 SECURITY_REVIEW_2026-10-06.md delete mode 100755 build/bin/gsha256sum delete mode 100644 build/include/glacier/log.h delete mode 100755 build/lib/shared/libglacier_dag.so delete mode 100755 build/lib/shared/libglacier_istoreutils.so delete mode 100755 build/lib/shared/libglacier_log.so delete mode 100755 build/lib/shared/libglacier_pkg.so delete mode 100755 build/lib/shared/libglacier_transaction.so delete mode 100755 build/lib/shared/libglacier_verify.so delete mode 100644 build/lib/tmp/log.o delete mode 100644 build/lib/tmp/make_conf.o delete mode 100644 build/lib/tmp/runtime.o delete mode 100644 build/lib/tmp/sha256.o delete mode 100644 src/dag.c delete mode 100644 src/dag.h create mode 100644 src/deps.c create mode 100644 src/deps.h create mode 100644 src/grepo.c create mode 100644 src/gsign.c delete mode 100644 src/make_conf.c create mode 100644 src/monocypher/LICENCE.md create mode 100644 src/monocypher/README create mode 100644 src/monocypher/monocypher-ed25519.c create mode 100644 src/monocypher/monocypher-ed25519.h create mode 100644 src/monocypher/monocypher.c create mode 100644 src/monocypher/monocypher.h delete mode 100644 src/runtime.c create mode 100644 src/sign.c create mode 100644 src/sign.h delete mode 100755 src/x_log delete mode 100755 src/x_pkg delete mode 100755 test_backup diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..578baa0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Build outputs (make; make libglacier-ng) +/build/ diff --git a/CLAUDE.md b/CLAUDE.md index 4064097..6cceaa7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,7 +3,8 @@ Context for Claude Code sessions on libglacier-ng: the package manager for Everest Linux. Kept as a handoff, so a fresh session starts with full continuity instead of re-deriving everything. Last full rewrite: -2026-10-04, at the end of a long session. **Update it as you go.** +2026-10-04; brought fully up to date at the end of the 2026-10-07 +session. **Update it as you go.** ## Start here @@ -15,15 +16,231 @@ continuity instead of re-deriving everything. Last full rewrite: - `SECURITY_DESIGN.txt`: integrity verification, scope separation, refusal matrix, syspkg's privilege model. - `PACKAGE_SCOPES.txt`: layout, link routing, `/etc` config handling. - - `IMAGE_DESIGN.txt`: PROPOSAL for system images (bootstrap OUTPUT, - the sysroot library change). Open decisions are in section 9. - - `GLACIER_BOOTSTRAP.txt`: PLAN for `glacier-bootstrap`. Prerequisites - are in section 7, decisions in section 9 (9c and 9d are decided). + - `IMAGE_DESIGN.txt`: system images (bootstrap OUTPUT). Sections 3 + (sysroot) and 4 (`syspkg --root`) are IMPLEMENTED (milestones 1-2); + the rest is still a proposal. Open decisions are in section 9. + - `GLACIER_BOOTSTRAP.txt`: `glacier-bootstrap`. Local mode is + IMPLEMENTED (ROADMAP 1.3; section 10 says how it differs from the + plan); remote mode and `--pin` are still a plan. Open: 9a, 9f. + - `REPO_DESIGN.txt`: remote repos + signing (ROADMAP 3.1). Sections + 1 and 10 = the user's decisions (TLS library still open); steps + 1-4 of section 9 DONE (see "3.1 status"); sections 7, 5 and 6 + describe steps 2, 3 and 4 as implemented. + - `SECURITY_REVIEW_2026-10-06.md`: the full audit (findings, fixes, + what was removed, what's still open). - `SECURITY_REVIEW_2026-07-16.md`: older review findings. -Nothing from the 2026-10-04 session is committed. libglacier-ng is on -branch `dev` with a large uncommitted diff (`src/`, the docs). Commit only -when the user asks. +Nothing from the 2026-10-04 through 2026-10-07 sessions is +committed. The user said "do not commit yet" (2026-10-05), and on +2026-10-06 that their git server is down: they will commit themselves +once it's back. libglacier-ng is on branch +`dev` with a large uncommitted diff (`src/`, the docs, `build/`). Commit +only when the user asks; when they do, propose a few logical commits. + +2026-10-05 session: system-scope permissions fix (bug 24), ROADMAP 2.3 +dependencies (`src/deps.c`), index corruption on abort (bug 25), ROADMAP +1.1 sysroot, live removal deleting foreign files (bug 26), ROADMAP 1.2 +(`syspkg -R`, `--strict`) + 2.6 (file collisions), gpkg ignoring unknown +flags (bug 27). + +2026-10-06 session: ROADMAP 1.3 `glacier-bootstrap` local mode, and +Everest's initramfs switched to it (boots in QEMU). Tidy-up (gbuild's +hint removed). ROADMAP 1.4 DONE (packages for glacier itself; details in +ROADMAP 1.4 and section 7): full regression green, new +`tests/installed_alpine.sh` for the installed layout, which found bug 29 +(fixed, mutation-checked, Everest rebuilt and booted with the fix). + +The installer (1.5) is DEFERRED by the user: manual installation is +one of Everest's main features. Then ROADMAP 3.1 was designed with the +user (REPO_DESIGN.txt, every decision taken; only the TLS library for +curl in base is open) and steps 1, 2 and (that evening, after the +laptop died mid-rebuild) 3 of its section 9 were done. + +2026-10-07 session: ROADMAP 3.1 step 4 (`-f`, `-u`, dependency +closure, the cache; see "3.1 status"). Help text for -f/-u/-O +shortened at the user's request. Refresh output (user's wording): `[i] Syncing +all repositories...`, then `[✓] All repositories synced successfully.` +if every repo succeeded; the per-repo success/up-to-date lines only +with `-V`; errors always (repo_alpine.sh checks both ways). +Then (same day) glacier-bootstrap configures repositories (user: it must +leave a semi-functional system, pacstrap-like): see "Bootstrap +repositories" below. + +2026-10-09: help text. `gsign`, `grepo`, `gsha256sum` (C, `getopt_long`, +static `help()` in each tool's own file), `glacier-bootstrap` and `gworld` +now have `-h/--help` and `-v/--version` (`v4.0.0`) in gpkg's format +(`%-25s` `{-x --long} ARG` lines, GPL footer), with long forms for every +flag. `-h` goes to stdout, exit 0; usage errors stay one usage block on +stderr, exit 1. repo_alpine.sh checks the help of grepo/gsign/gsha256sum. +Backups: `glacier-bootstrap.old2`, `gworld.old`, `tests/repo_alpine.sh.old5`. + +### Bootstrap repositories (2026-10-07) + +The user chose: the config comes from a NEW base package +(`everest-repos` 1.0.0: `etc/glacier/repos.conf` + `keys/everest-2026.pub`, +source `~/Projects/everest/pkgsrc/everest-repos`; everest-bootscripts +1.3.0 no longer ships `etc/glacier`, its old copy is +`pkgsrc/everest-bootscripts/glacier.old`); bootstrap runs `syspkg -R T +-y` after installing and a failed fetch is an error (exit 7, no +"Bootstrapped"); under `-R`, `-y`/`-f`/`-u` use T's OWN keyring +(`keyring(sysroot)` in gpkg_common.lua; `require_trusted` also refuses a +file whose realpath leaves T). `-R -l` signatures stay the host's +(`HOST_KEYRING`). No url in T: `[!] No remote repositories are +configured in T.`, exit 0 (Everest today: base isn't published). +Copying the host's repos.conf (pacstrap's mirrorlist) left out for now. +The user said everything a template lists for base also goes in the +base repo (the published repo = the template's base packages); no url +yet. Everest rebuilt (bootscripts 1.3.0, everest-repos, glacier-ng 4.0.0 +in place), initramfs remade, booted: `/etc/glacier` from everest-repos, +12 packages. + +### Audit and cleanup (2026-10-06, late) + +At the user's request, before 3.1 step 4: a full security audit +(`SECURITY_REVIEW_2026-10-06.md`, bugs 30-35 below), unused code removed, +and junk cleaned up. The user's choices: delete only the `.old` backups +and stray binaries (every prototype/note file stays: libglacier-ng's +`src/{deb,gpkg,templ,report,test_*}.c`, `src/html`, `pkg/`, glacier-ng's +`todo`, `tree/`, `test-*`, `pkgs/`...); libglacier-ng's build outputs are +untracked (`.gitignore`: `/build/`; the deletions show in the next +commit); the `.old` backup rule STAYS (see "Backups" below for current ones). New tests: +`tests/audit_alpine.sh` (26 checks), `tests/test_abort_unlocked.lua`. +Full regression green; Everest rebuilt and booted. Everest's own `.old` +files were not touched (not one of the two repos). + +### 3.1 status + +Step 4 DONE (2026-10-07; REPO_DESIGN section 6 "As implemented"). The +user chose: the FIRST repo (repos.conf order) serving a name wins, at +its highest version; a dependency installed too old is upgraded in the +same transaction (keeps its auto/explicit mark); cached downloads are +deleted after the commit that installs them. +- libglacier-ng: `gl_scope_cache_dir(ctx, repo, out)` (transaction.c; + `/cache/REPO`, checked like `repos` via the shared static + `checked_subdir`); `log.c` help lists `-f`/`-u`. +- glacier-ng `gpkg_common.lua`: `M.fetch(names, opts)`, + `M.upgrade(names, opts)`; `load_indexes` (refresh, then read the + accepted copies), `pick`, `close_deps`, `download` (SIZE-capped, + hashed), `matches_index` (manifest vs index entry), `install_plan`. + `M.install` is now `verify_all` + `install_copies(copies, original, + opts, meta)`, shared with -f/-u (`meta`: auto, may_replace, dep, + cache). `verify_and_copy(path, vouched)` takes the index's hash in + place of a sidecar. syspkg `-f`/`-u` modes; gpkg `-f`/`-u` (no + longer "not implemented"). +- New suite `tests/fetch_alpine.sh` (84 checks; 16 Lua mutants + 1 C, + all caught except the download-level hash check, equivalent: kept as + defence in depth). The `gpkg -u "%s%n"` format check moved there from + audit_alpine.sh. Full regression green. +- Everest's libglacier-ng/glacier-ng 4.0.0 were REBUILT in place + 2026-10-07 with -f/-u/-y (user: keep 4.0.0, no bump) and the + initramfs remade; in QEMU `gpkg -f hi` as a wheel user now says + `[x] No remote repositories are configured.` (shipped repos.conf has + no url; no curl in base). `container.sh initramfs` now `mkdir -p`s + `$G/lib/glacier` (gone from the tree). Not done: no published repo; + TLS library still open. Next: step 5 (`glacier-bootstrap -r`), + then 6 (user repos). + +Step 3 DONE (2026-10-06, evening; REPO_DESIGN section 5 "As +implemented"). The user chose: a refresh-only flag `-y`, the date-form +SERIAL (YYYYMMDDNN), grepo in C. +- libglacier-ng: `src/grepo.c` (`grepo -s KEY.sec -r REPO [-n SERIAL] + [-e DAYS] DIR`, Makefile target in `all`, installed to bin, built from + source like gsign); `pkg.c`: `gl_index_parse(text, len, repo, out)` / + `gl_index_free` (strict; takes the signed BYTES; refuses NUL and '@'), + `gl_conf_repos(path, out, len)` ("NAME\tURL\n" lines), repos.conf + validation shared in `read_repos_conf`; `transaction.c`: + `gl_scope_state_dir(ctx, "repos", out)` (creates/checks + `/repos`). `log.c` help lists `-y`. +- glacier-ng: `M.refresh(opts)` in `gpkg_common.lua` (fetch file:// or + curl https, verify with a key listed for THAT repo, parse, rollback / + same-serial / expiry checks, rename into `/repos`, under the + scope lock); `syspkg -y` (needs root and `-n`; `-R` works), `gpkg -y` + (system repos.conf + keyring until step 6). Dockerfiles copy grepo to + /usr/local/bin. New suite `tests/repo_alpine.sh` (103 checks; 18 + mutants, all caught). Full regression green. Everest's libglacier-ng + and glacier-ng packages rebuilt with it. +- Next: step 4 (`-f` with dependency closure, then `-u`; the cache in + `/cache`, section 5's Fetch). + +Step 2 DONE (2026-10-06, second part of the session; REPO_DESIGN +section 7 "As implemented" has the details): +- libglacier-ng `src/pkg.c`: `gl_conf_repo_keys(path, repo, out, len)`, + a strict repos.conf reader; new statuses `GL_CONF_ERR_INVALID = 4`, + `GL_CONF_ERR_SPACE = 5`. +- glacier-ng `lib/gpkg_common.lua`: `verify_all(paths, opts)` refuses a + base package in system scope without a trusted signature + (`signed_for`, `trusted_keys`, `untrusted`); `verify_and_copy` returns + the sidecar's bytes. glacier-bootstrap copies the sidecar's BYTES and + the `.sig`. `gbuild -k`/`--key`, `tests/mkpkg -k`. +- Every `Dockerfile.*` installs gsign + gsha256sum in /usr/local/bin and + a TEST keyring (`glacier-test`, secret `/root/glacier-test.sec`). + `syspkg_alpine.sh` / `overwrite_alpine.sh` make their own key. +- New suite `tests/keyring_alpine.sh` (68 checks; 17 mutants: 10 Lua/ + shell, 7 C, all caught). Full regression green. +- Everest: `./everest.sh keygen` made `everest-2026`; the SECRET key is + `~/.config/everest/keys/everest-2026.sec` (user's choice: outside the + project), mounted read-only for `build`, which refuses without it. + Every package is built `gbuild -k`. everest-bootscripts 1.2.0 ships + `etc/glacier/repos.conf` (base: everest-2026) and + `etc/glacier/keys/everest-2026.pub`; `container.sh initramfs` + installs the same into the build container's `/etc/glacier`. + The signed `build all` was cut off after busybox (laptop died); the + other 9 were rebuilt `-k` the evening of 2026-10-06, `initramfs` ran + through glacier-bootstrap (signature checks passed for all 11), and + Everest booted in QEMU: repos.conf + key in `/etc/glacier`, index + lists all 11, syspkg/gpkg run as root and as a fresh user. +- Base KEEP_OLD backups can't be restored with `-lO` (no signature): + the user accepted that until the restore interface (REPO_DESIGN 10f). + +Step 1 DONE (2026-10-06): +- `src/monocypher/`: Monocypher 4.0.3, vendored UNMODIFIED (provenance + and sha512 in its README; GitHub and monocypher.org copies matched). + Compiles clean under the project's `-Werror` flags. +- `src/sign.{c,h}`: signify formats (strict parse, canonical base64), + `gl_sign_read_pubkey`/`_read_sig`/`_check_seckey`/`_verify`/ + `_verify_file`/`_file`/`_generate`, statuses `GL_SIGN_OK`.. + `GL_SIGN_ERR_WRITE = 9`. Built into `libglacier_verify.so` (NOT into + the combined `libglacier-ng.so`). +- `src/gsign.c` (`-G`/`-S`/`-V`, lg_printf messages naming the file at + fault), Makefile targets `gsign` (in `all`, installed to bin like + gsha256sum). +- `glacier-ng/tests/sign_alpine.sh` (alpine:latest, mounts /libsrc): + round trips, signify interop both ways (Alpine `signify`), encrypted + and corrupted secret keys, malformed key/sig files, 64 MiB limit, + O_NOFOLLOW, key modes, a direct FFI call of `gl_sign_generate`. + Passes in full. (`luajit -e CODE ARG` runs ARG as a script, so the + FFI snippet is written to `/gen.lua` first.) +- Mutation checks: 12 mutants, all caught except m9 (padding position, + `j < 2`), which is EQUIVALENT: kept, it prevents `3 - pad` + underflowing. m5 (fchmod), m6 (final newline) and m10 (O_EXCL) needed + the umask-077, final-char and FFI checks respectively. +- `glacier-ng/lib/glacier_cdef.lua`: `gl_sign_*` declarations and + `M.sign_name()`; checked against the real `.so` (struct sizes 40/72, + every call on signify-made files). Full regression green after it. + +Also open, for the user (raised 2026-10-06, not answered yet): +- libgcc: luajit is built `-static-libgcc` (Everest has no libgcc_s). + Claude's call; the alternative is a GCC runtime package in base + (ROADMAP section 6). +- Leftover `~/Projects/everest/pkgs/everest-bootscripts-1.0.0.gpkg` + (+ sidecar), superseded by 1.1.0. Not deleted: ask first. + +### Everest's glacier packages (1.4) + +- `~/Projects/everest/scripts/container.sh` builds zlib, libarchive + (gzip/tar only), libconfig, luajit (pinned commit, + `LDFLAGS=-static-libgcc`), libglacier-ng, glacier-ng (all base) and + doas (extra, OpenDoas, no PAM). +- glacier-ng layout: `usr/lib/glacier-ng/{syspkg,gpkg,gstore,lib/*.lua}`, + `lib/glacier -> /usr/lib/glacier` (absolute: relative links resolve + inside the store), `usr/bin/X -> ../lib/glacier-ng/X`, + `etc/glacier/glacier.conf`. +- everest-bootscripts 1.2.0 (1.1.0 before signing) ships `/etc/doas.conf` (0400): `permit + :wheel` plus nopass `gstore args -n` for BOTH `cmd gstore` and `cmd + /usr/bin/gstore` (OpenDoas matches `cmd` against what was typed). +- `templates/minimal.template` and `PACKAGES` in everest.sh list all 12 + packages (everest-repos added 2026-10-07). `Dockerfile.build` has autoconf, automake, libtool, cmake, + pkgconf. ## The three projects @@ -31,11 +248,12 @@ when the user asks. |---|---|---| | `~/Projects/libglacier-ng` | C library, design docs, this file | yes (`dev`) | | `~/Projects/glacier-ng` | Frontend: `gpkg`, `gstore`, `syspkg`, `gbuild`, `gworld`, `lib/*.lua`, `tests/`, Dockerfiles, `pkgs/` | **no**: back files up before editing (`file.old`; if that exists, `.old2`, `.old3`, ...) | -| `~/Projects/everest` | The distro build: `everest.sh build\|initramfs\|boot`, packages, bootable QEMU image | no | +| `~/Projects/everest` | The distro build: `everest.sh build\|initramfs\|boot`, packages, bootable QEMU image | **no**: same backup rule (but never inside `pkgsrc/*/etc/`, see Boot test) | -`glacier-ng/lib/glacier/` holds `.so`s built on the host (glibc). They -won't load on musl. Every Alpine image and test builds libglacier-ng from -source instead. +Nothing prebuilt is kept in either repo (the host-built `.so`s in +`glacier-ng/lib/glacier/` were removed 2026-10-06). Every image and test +builds libglacier-ng from source; the Dockerfiles create +`/app/lib/glacier/` with the musl `.so`s. ## What this project is @@ -56,10 +274,20 @@ These are preferences stated in sessions. Follow them. - The pre-trust-check `die()` in syspkg/gstore reproduces the `[x]` format byte for byte, because it can't load `libglacier_log.so` before that file has been verified. +- **Never say "Completed" after an error.** If the post-commit link pass + refused any path (`ctx->link_refusals`, counted only for packages + installed by that transaction), gpkg/syspkg end with + `[x] N paths were not linked.` (`1 path was not linked.`) and exit 1. + The transaction itself stays committed (2026-10-05). - **No hint messages.** Don't print lines that point users to other flags or commands (for example "use -lO to ..."). The user called these clutter. A refusal states the fact, in one line. The user also removed a `Run doas gstore -n ...` follow-up hint from gstore themselves. +- **What goes where.** ALL design docs (and ROADMAP, this file) live in + libglacier-ng, frontend ones included. Other than docs, libglacier-ng + holds only backend material (C sources, the build). Frontend code, + tests, Dockerfiles and everything the bootstrap runs on live in + glacier-ng. Clarified 2026-10-06. - **Ask before settling a design choice that is the user's to make**, and give a recommendation. They have answered every such question promptly. Don't silently pick a layout or semantics. @@ -68,6 +296,10 @@ These are preferences stated in sessions. Follow them. defence in a throwaway copy and confirm the test fails ("mutation check"). Several defences here are verified that way. - Use doas, not sudo. syspkg is never setuid. +- **Help text is terse** (2026-10-07): one short phrase per flag, no + mechanism details (`-f` "Install packages from a remote repository", + `-u` "Update system or individual packages", `-O` "Allow Glacier to + overwrite existing files"). ## Decisions the user has made (don't relitigate) @@ -90,18 +322,105 @@ These are preferences stated in sessions. Follow them. - "Backed up" means the old package's files are compressed into a `.gpkg`. A restore interface comes later. - **`gpkg -u` / `syspkg -u` upgrade from a REMOTE repository**, and - `gpkg -f` is `-l` but fetched by name from a remote repository. Both wait - for remote repos + signing (ROADMAP 3.1). Don't implement them against - local files. + `gpkg -f` is `-l` but fetched by name from a remote repository. Done + 2026-10-07 (3.1 step 4). Never against local files. +- **-f/-u details (2026-10-07):** first repo by priority wins (then its + highest version); a too-old installed dependency is upgraded in the + same transaction; cached downloads are deleted after commit. +- **Dependencies (ROADMAP 2.3, decided 2026-10-05):** see the spec in + ROADMAP 2.3. In short: minimum versions, conflicts both ways by name, + no override flag, report every problem, missing deps refuse until 3.1 + brings automatic closure, auto/explicit recorded in the index now. +- **sysroot (IMAGE_DESIGN section 3) accepted as proposed**, plus the + library refuses a non-"/" sysroot for `GL_SCOPE_USR`. Order of work: + 2.3 first, then 1.1. +- **System scope is world-readable** (dirs 0755, index files 0644): every + user must be able to run what syspkg installs (2026-10-05). User scope + stays 0700, and other users' programs stay unreachable to non-root + users. Root CAN run them by full path, but other users' links are + deliberately NOT on root's `PATH` (a user could get code run as root). +- **Link refusals after a commit** end with `[x] N paths were not linked.` + and exit 1, never "Completed" (the user's wording rule: don't say + "completed" if there was an error). +- **`syspkg -R DIR`** (not `-r`), `--root DIR`, `--root=DIR`; `-R` + IMPLIES `--strict`; `--strict` alone is opt-in on the host. +- **File collisions (2.6) are always refused**, every scope, strict or + not (folded into 1.2 at the user's go-ahead). +- **PATH:** per-user links, then `/glacier/sys/links/bin`, then the system + (`/usr/bin` on Everest), in the Docker images AND Everest's + `/etc/profile`. Non-base system packages must be on `PATH`; base + programs must be runnable by every user. - `everest-bootscripts` is the name of the base `/etc` package. Its source lives in `~/Projects/everest/pkgsrc/`. +- **glacier-bootstrap (2026-10-06):** template = `NAME=` + `PKG=name ver + [sha256:hex]` lines, no `SYS_PROFILE` (unknown key until ROADMAP 3.3); + it runs the `syspkg` on PATH (doas unless root); a template dependency + must be listed at that version OR LATER (the 2.3 rule); Everest's + initramfs is built through it. +- **Installing Everest (2026-10-07):** possible from a live Everest + system OR any Linux host; the LIVE SYSTEM is the preferred, officially + supported way (so base/the live medium must eventually carry what a + manual install needs). The user does the first QEMU disk install + themselves: don't start it. +- **Everest supports glibc as well as musl** (2026-10-07), Gentoo/Void + style: the user picks the libc at install time, one per system. + Today everything is built for musl (Alpine); glibc needs ROADMAP + 4.1's profiles. How packages are kept apart is still open (ROADMAP 6). +- **Signing (2026-10-06, REPO_DESIGN 10e/10f):** Everest's SECRET key + stays outside the project (`~/.config/everest/keys`, mounted + read-only); a base KEEP_OLD backup refusing under `-lO` (unsigned) is + accepted until the restore interface. ## Core architecture -**`gl_context_t`** (transaction.h) is the central abstraction. Fields: -`uid`, `root_path`, `index_path`, `store_path`, `links_path`, `mode` -(`GL_CTX_LIVE`/`GL_CTX_STAGE`), `scope` (`GL_SCOPE_USR`/`GL_SCOPE_SYS`), -`lock_fd`, and `overwrite` (last field, zeroed by the init functions). +**`gl_context_t`** (transaction.h) is the central abstraction. Fields, in +order: `uid`, `root_path`, `index_path`, `store_path`, `links_path`, +`mode` (`GL_CTX_LIVE`/`GL_CTX_STAGE`), `scope` +(`GL_SCOPE_USR`/`GL_SCOPE_SYS`), `lock_fd`, then: + +| Field | Set by | Meaning | +|---|---|---| +| `overwrite` | caller (after init, which zeroes it) | `-O`: the next link pass may displace foreign files | +| `sysroot` | `*_at` init | install root, `""` = the running system | +| `link_refusals` | link pass; commit copies the relink's count back | paths refused for this transaction's packages | +| `strict` | caller (after init, which zeroes it) | refuse those before committing instead | + +New fields go at the END, and `lib/glacier_cdef.lua` must mirror the +struct exactly (FFI has no compiler to catch a mismatch). + +**Status codes worth knowing:** `GL_TXN_ERR_DEPS = 9` covers every +pre-commit refusal (dependency, conflict, file collision, strict link +refusal); the library has already printed why, so the CLIs print nothing +more. Live-context equivalents: `GL_INSTALL_ERR_DEPS = 8`, +`GL_REMOVE_ERR_DEPS = 4`. `GL_INSTALL_ERR_SCOPE = 7` is base-in-user- +scope. + +**Install root / sysroot** (ROADMAP 1.1, done 2026-10-05): +- `gl_init_live_context_at` / `gl_init_stage_context_at` / `gl_init_sys_at` + take a sysroot; the old functions wrap them with `"/"`. + `gl_normalize_sysroot` (common.h) refuses relative paths and `.`/`..` + components. **User scope refuses any sysroot** other than `/`. +- The context's paths are **INSTALL paths** (`T/glacier/sys/store`), so + every filesystem operation uses them unchanged: a forgotten translation + writes into T, never the host. That's deliberately the reverse of + IMAGE_DESIGN's wording (runtime paths stored, `gl_at` on every + operation), with the same effect and a safer failure mode. +- Translate ONLY where a path's runtime meaning matters: + `gl_runtime_path(ctx, p)` for link targets, `is_glacier_symlink` + prefixes and `.displaced` keys; `gl_at(ctx, runtime, out)` to check a + link's target (prune). `/usr` and `/etc` destinations are + `ctx->sysroot` + path. +- **Trust check** (`gl_check_sysroot` in transaction.c, also inside + every `*_at` init and `gl_init_sys_at`; helper `gl_sysroot_trusted` in + common.h): the root and every ancestor must exist and be root-owned, + non-symlink directories, not group/world-writable. So `/tmp/x` is + refused. Messages: `Refusing install root R: P is a symlink.` / `is + missing.` / `is not root-owned, or is group/world-writable.` +- Test: `tests/sysroot_alpine.sh` (host `/usr`, `/glacier`, `/etc` + byte-identical; `chroot T` runs a linked binary; 8 mutations caught). +- Not translated, on purpose: `KEEP_OLD` is read from the HOST's + `/etc/glacier/glacier.conf` even under `-R` (the build host's policy); + user scope's dependency view of "the system" is always the host's. **Layout.** Each scope owns ONE base directory holding `{index,store,links,stage,old,lock,backup}`. Every create, rename and lock @@ -110,7 +429,7 @@ a transaction does stays inside it. | Scope | Base dir | Ownership | |---|---|---| | `GL_SCOPE_USR` | `/glacier/usr//` | uid, mode 0700, inside a root-owned 0755 `/glacier/usr` | -| `GL_SCOPE_SYS` | `/glacier/sys/` | root | +| `GL_SCOPE_SYS` | `/glacier/sys/` | root, world-readable: dirs 0755, index files 0644 (`gl_scope_dir_mode`/`gl_scope_file_mode` in transaction.h), whatever the umask | - The helpers `gl_usr_base`, `gl_usr_base_check` and the layout comment are in `common.h`. @@ -126,11 +445,22 @@ a transaction does stays inside it. live package inside a transaction needs no special casing. Seeding copies dotfiles: only `.` and `..` are skipped. - A stage on another filesystem fails at seeding with `GL_TXN_ERR_XDEV`. -- **Commit:** rename-based promotion (stage → live, previous live → `old/`), - then fsync the base. Then `gl_relink_store` + `gl_rebuild_index` - regenerate links and index from a *fresh live context* and rescan the - WHOLE store (every commit relinks every package, so link-time logic must - be idempotent). `old/` is then deleted. +- **Commit:** first `gl_check_txn` (deps, conflicts, collisions, and + strict link refusals if `ctx->strict`); any problem → nothing changes, + `GL_TXN_ERR_DEPS`. Then rename-based promotion (stage → live, previous + live → `old/`), then fsync the base. Then `gl_relink_store` regenerates + links from a *fresh live context* and rescans the WHOLE store (every + commit relinks every package, so link-time logic must be idempotent); + its `link_refusals` is copied back into the caller's context. `old/` is + then deleted. (The index is rebuilt inside the stage by each + install/remove, and promoted with it.) +- **Live contexts** (no transaction; tests and third-party callers): + `gl_install_pkg` runs `gl_check_plan` itself, and `gl_remove_pkg` + removes the package's links by pruning (`gl_relink_store`), like a + commit. +- **Index files** are named to the second and seeding hardlinks them: + never open a seeded file for writing, always write elsewhere and + `rename()` (bug 25). **Link routing** (per package, in `gl_link_pkg`): @@ -157,7 +487,8 @@ transactions): | `/.glacier-manifest` | the package's manifest, saved at install, used by `gl_backup_pkg` | | `/.glacier-fresh` | one-shot "installed in this transaction"; link conflicts are only reported when present | | `/.glacier-overwrite` | one-shot `-O` permission for this package's next link pass | -| `/.displaced/` | originals displaced by `-O`, restored when the link that replaced them is pruned | +| `/.glacier-auto` | installed as a dependency, not by name (index field 4 = `auto`); set by `gl_set_pkg_auto`, nothing sets it until 3.1 | +| `/.displaced/` | originals displaced by `-O`, restored when the link that replaced them is pruned; keyed by RUNTIME path (no sysroot prefix) | | `/.etc-state/` | the last version of each `/etc` file a package offered | **Configuration.** `/etc/glacier/glacier.conf` (libconfig syntax). @@ -189,8 +520,20 @@ possibly shared: plus `O_NOFOLLOW`). - Any entry with `..` or an absolute path refuses the whole package. - A failed extraction removes its half-made version directory. -- **Recursive deletes.** `gl_rmdir_recursive` skips only `.`/`..`. Every - caller passes a glacier-only path; keep it that way. `gl_delete_user` +- **Recursive deletes.** `gl_rmdir_recursive` skips only `.`/`..`, and + walks by descriptor (`openat(O_NOFOLLOW)`, `unlinkat`): an entry + swapped for a symlink is unlinked, never followed (bug 31). Every + caller passes a glacier-only path; keep it that way. +- **Root reading a user's tree** (`add_entry_at`, `gstore -b`) is + fd-relative with `O_NOFOLLOW` too (bug 32). +- **Reserved names.** A package's top-level `files/.glacier-*` entries + are refused (`GL_MARKER_PREFIX`, common.h): those names are glacier's + own version-dir files, and every marker write uses `O_NOFOLLOW` + (bug 30). The marker names are defined ONCE, in common.h. +- **Manifests are untrusted**: `gl_gpm2gpkg` refuses `@include` and + dependency/conflict entries without a valid name (bug 33). The CLIs + read manifests ONLY through the library (`manifest_of` in + gpkg_common.lua), never by pattern matching (bug 34). `gl_delete_user` lstat-checks its target first, because a symlinked base would otherwise have its *target* emptied. - **Wide wrappers.** Don't add convenience wrappers that silently touch a @@ -225,7 +568,8 @@ possibly shared: **Version replacement** (`-lO`, `M.install` in `gpkg_common.lua`): 1. Installed entries are read from the live index's `current` file - (`repo::name::version` lines). + (`repo::name::version::auto|explicit` lines; files before 2026-10-05 + stop at the version, and every parser accepts both). 2. A same name+repo with a different version is a replacement. Without `-O` it's refused; with `-O`, `keep_old()` reads `KEEP_OLD`, and the summary shows `old -> new`. @@ -260,7 +604,70 @@ the merged-/usr tree: `/bin` and `/sbin` → `usr/bin`, `/lib` → `usr/lib`, - `gl_init_sys` runs it before writing the index. The index marks `-n` as complete, so a second `syspkg -n` is refused: `Cannot create /glacier/sys: already exists`. -- It takes a root prefix, ready for `syspkg --root`. +- It takes a root prefix; `gl_init_sys_at` / `syspkg -R DIR -n` use it. + +**Dependencies and conflicts** (ROADMAP 2.3; `src/deps.c`, built into +`libglacier_istoreutils.so`; rules in `deps.h`): +- `gl_check_plan(live, adds, n, "repo::name" removes, n)` runs before the + prompt (`check_plan` in `gpkg_common.lua`). `gl_check_txn(stage)` + compares stage with live, and `gl_commit_transaction` calls it itself + (`GL_TXN_ERR_DEPS = 9`). A live context runs the plan check inside + `gl_install_pkg`/`gl_remove_pkg` (`GL_INSTALL_ERR_DEPS = 8`, + `GL_REMOVE_ERR_DEPS = 4`). So no library caller can skip it. +- It reads each version dir's `.glacier-manifest`; a version without one + has no deps. Only what the transaction changes is checked. +- `-x` of a name installed in several repos is only checked at commit + (its repo is chosen after the prompt). +- Claude's own calls, not reviewed by the user: conflicts only apply + within one scope; only what a transaction changes is checked. + +**Strict link check** (`check_links` in deps.c; `ctx->strict`): system +scope only, for this transaction's packages, mirrors `gl_link_pkg`: a +foreign file/symlink at a file's destination (unless `-O` and no earlier +`.displaced` copy), a non-directory where a directory goes, or a +directory over another package's symlink → `[x] Refusing to overwrite +non-glacier path: P` / `Refusing to merge directory ...`. A foreign +symlink where a directory goes (`usr/sbin -> bin`) is followed, as the +link pass does. + +**File collisions** (ROADMAP 2.6, `check_collisions` in deps.c): two +packages whose entries land on the same path (by `dest_of`, which mirrors +`gl_link_pkg`) are refused in every scope, before the prompt and at +commit, when either is in the transaction: `[x] extra::a and extra::b +both ship /glacier/sys/links/bin/x.` Sharing a directory is fine; a file +or symlink where the other has a directory is a collision. + +**Signatures** (REPO_DESIGN section 7; `gpkg_common.lua`): in system +scope, a package whose LIBRARY-read manifest (`gl_read_pkg_manifest`, +never `read_manifest_fields`) says base needs `FILE.sha256.sig` over the +exact sidecar bytes verified, by the key with that keynum among those +`/etc/glacier/repos.conf` lists for base (`gl_conf_repo_keys`). Else +`[x] base::NAME is not signed by a key trusted for base.` before the +prompt. repos.conf and keys (and all ancestors, real paths too) must be +root-owned, not group/world-writable. Under `-R`, the HOST's keyring +for this check; `-y`/`-f`/`-u` under `-R` use T's (inside T only). +No repos.conf = nothing trusted. gpkg / other repos: sidecar only. + +**Repository indexes** (REPO_DESIGN sections 4-5; `M.refresh` in +`gpkg_common.lua`): `syspkg -y` / `gpkg -y` fetch `URL/repo.idx` + `.sig` +for every repos.conf entry with a url, check the signature over the +fetched bytes with a key listed for that repo, `gl_index_parse` the +same bytes, refuse SERIAL below the accepted one (or equal with other +bytes) and an expired VALID_UNTIL, then rename into +`/repos/REPO.idx` (+ `.sig`). Any failure keeps the accepted +copy; every repo is tried; exit 1 if any failed. + +**`-f` / `-u`** (REPO_DESIGN section 6; `M.fetch`/`M.upgrade`): +refresh first (any failure refuses), pick each name from the first +repo serving it, add missing deps (auto) and upgrade too-old ones, +download into `/cache/REPO` (hash and SIZE from the index), copy +privately with the index's hash, require the manifest to equal the +index entry (`[x] extra::x 1.0.0 does not match its index.`), then the +`-l` path (`install_copies`): summary with `(dependency)`, 2.3 checks, +prompt, staged transaction, `gl_set_pkg_auto`, backups, cache files +deleted after commit. A base package fetched this way needs no `.sig` +(the base index vouches). `-f` of an installed other version needs +`-O`; `-u` and dependency upgrades don't. gpkg never fetches from base. **Package symlinks** are extracted verbatim and linked like files. Hardlink, device and fifo entries are skipped. `add_to_archive` (used by @@ -270,14 +677,28 @@ backups and `gstore -b`) archives symlinks. | Tool | Flags | Runs as | |---|---|---| -| `gpkg` | `-l FILE...` `-x NAME...` `-O` `-S` `-V` `-h` `-v`; `-f`/`-u` are stubs | the user; per-user scope only, NEVER constructs `GL_SCOPE_SYS` | -| `syspkg` | `-n` `-l FILE...` `-x NAME...` `-O` `-S` `-V` `-h` `-v` | root via doas; `-h`/`-v` without root | -| `gstore` | `-n [UID]` `-b UID OUTFILE` `-x UID` `-s` (stub) `-V` `-h` `-v` | `-n` needs root (direct, or `doas gstore -n` for yourself) | +| `gpkg` | `-l FILE...` `-f NAME...` `-u [NAME...]` `-x NAME...` `-y` `-O` `-S` `-V` `-h` `-v` | the user; per-user scope only, NEVER constructs `GL_SCOPE_SYS` | +| `syspkg` | `-n` `-l FILE...` `-f NAME...` `-u [NAME...]` `-x NAME...` `-y` `-O` `-S` `-V` `-R DIR` `--strict` `-h` `-v` | root via doas; `-h`/`-v` without root | +| `gstore` | `-n [UID]` `-b UID OUTFILE` `-x UID` `-s` (refuses: not implemented) `-V` `-h` `-v` | `-n` needs root (direct, or `doas gstore -n` for yourself) | +| `glacier-bootstrap` | `-d PKGDIR TEMPLATE TARGET` `-n` `-t` `-V` `-h` `-v` | the user; borrows root only for `doas syspkg` / `doas chroot` | - **gpkg:** modifiers `-O`/`-S`/`-V` are applied in a pre-pass, so their position doesn't matter (`-lO` == `-Ol`). It finds its user via - `/proc/self/loginuid`, falling back to `getuid()`. + `/proc/self/loginuid`, falling back to `getuid()`. Unknown flags are + refused (`[x] Unknown option: -R`); gpkg has no `-R`/`--strict`. +- **Endings (both CLIs):** `[✓] Completed with no errors.` (exit 0), or + after link refusals `[x] N paths were not linked.` (exit 1; + `report_done` in gpkg_common.lua). Refusals before the prompt exit 1 + with only the `[x]` lines. - **syspkg, `-n`:** creates the tree plus `/glacier/sys`. +- **syspkg, `-R DIR` / `--root DIR` / `--root=DIR`** (last in a bundle: + `-lR DIR`): operate on the system under DIR, through the `*_at` + contexts. Refused first by `gl_check_sysroot`: DIR and every ancestor + must be root-owned, non-symlink directories, not group/world-writable + (`/tmp/...` is refused). Implies `--strict`. +- **syspkg, `--strict`:** anything the link pass would refuse fails the + whole transaction before the prompt (and at commit, `ctx->strict`); + `-O` still displaces. - **syspkg as non-root:** `-n`/`-l`/`-x` are refused after argument parsing with `Cannot open system index: Permission denied`. - **gstore, `-n` without a UID, under doas:** acts for the invoking user @@ -290,11 +711,25 @@ backups and `gstore -b`) archives symlinks. installed fails before the prompt. - **Hardened loading** (syspkg and gstore): `#!/usr/bin/luajit -E`, `package.path` and every `ffi.load` pinned to their own `realpath`, and - when euid is 0, every ancestor directory and every loaded `.lua`/`.so` - must be root-owned and not group/world-writable. The preamble is + when euid is 0, every loaded `.lua`/`.so`, and every ancestor of the + script's realpath AND of each loaded file's realpath, must be + root-owned and not group/world-writable (bug 29). The preamble is DUPLICATED in both files on purpose: change one, change both. -- **gpkg** uses the older CWD-relative convention (`require("lib/...")`, - `ffi.load("./lib/glacier/...")`): run it from its own directory. +- **gpkg** (2026-10-06) loads `lib/` from its own realpath, like + glacier-bootstrap (no ownership checks; it's unprivileged), so an + installed `/usr/bin/gpkg` works from any directory. +- **glacier-bootstrap** (GLACIER_BOOTSTRAP section 10): parse → + verify (copies each `PKGDIR/NAME-VER.gpkg` into a private `mktemp -d` + while hashing; sidecar + pin; writes the copy's sidecar from that hash) + → resolve (reads manifests of the COPIES via `gl_read_pkg_manifest`: + name/version match, deps listed at >= version, conflicts) → `syspkg -R + T -n` (skipped if T is initialised with nothing installed) → `syspkg -R + T -l COPIES` (one strict transaction; its prompt passes through) → + checks T's index lists every package (syspkg exits 0 when declined) → + `syspkg -R T -y` if T's repos.conf has a url → `-t` smoke test. + Never writes into T itself. Exit: 1 usage/target, 2 template, + 3 verify, 4 resolve, 5 install, 6 smoke, 7 repositories. Loads its `lib/` + from its realpath, no root checks. No `-r` / `--pin` yet. - **Declarations:** `lib/glacier_cdef.lua` is the single source of truth for every `ffi.cdef`. `tests/glacier_ffi.lua` is only a wrapper around it. Declaration ORDER matters: a declaration using `gl_context_t` must @@ -303,7 +738,9 @@ backups and `gstore -b`) archives symlinks. ## Build tooling - **gbuild** (bash, the only copy is `glacier-ng/gbuild`): - - `-n -V -g -c -d -m -D -C -b -i -o -p -j -F -t -x -u -r -s`; + - `-n -V -g -c -d -m -D -C -b -i -o -p -j -F -t -x -u -r -k -s` + (`-k KEY.sec`: sign the sidecar with gsign, `$GSIGN_BIN` / build/bin + / PATH, key tried before building); `-v`/`--version` prints the version, `--verbose` turns on verbose output. - Autodetects the build system, with autotools checked BEFORE a bare @@ -320,15 +757,29 @@ backups and `gstore -b`) archives symlinks. (`Dockerfile.build`). - **Kernel version:** linux `6.12.112`; plain `v6.12` doesn't build with GCC 15. +- **Everest's image IS built through glacier** (2026-10-06): `container.sh + initramfs` copies glacier-ng to a root-owned `/opt/glacier-ng` (with + the musl-built `.so`s; syspkg's trust checks need that) and runs + `glacier-bootstrap -t -d /everest/pkgs templates/minimal.template + /target` (root-owned: not under `/tmp`). T's links name RUNTIME paths, + so anything reading through them from the build host must resolve + inside T (vmlinuz is read with `chroot T cat`). Only `root` exists in + the shipped `/etc/passwd`. +- gbuild ends with the package's contents list. Its "To install with + gpkg: gpkg -l ..." hint was removed 2026-10-06 at the user's request. ## libglacier-ng build - **Separate `.so` per module** (`libglacier_log`, `_pkg`, `_istoreutils`, - `_transaction`, `_dag`, `_verify`). This split is deliberate: a + `_transaction`, `_verify`; `_dag` was removed 2026-10-06, unused). This split is deliberate: a third-party program links only what it needs. - **The combined `libglacier-ng.so`** (`make libglacier-ng`) exists only for the FFI tests. - **`libglacier_verify.so`** is private: no public header. +- **Tools** in `build/bin`: `gsha256sum`, `gsign`, `grepo` (gsign and + grepo compile the sources they need in, no rpath). Maintainer tools, + later "glacier-tools": Everest's libglacier-ng package drops + `usr/bin`. - **Warnings:** `config.mk` builds with `-Werror`. `-Wno-format-truncation` is set because that warning is noise; `-Wstringop-truncation` stays fatal. @@ -340,7 +791,7 @@ backups and `gstore -b`) archives symlinks. Run everything in throwaway containers. Several suites write to `/usr`, `/etc` and `/glacier`. -**FFI suite** (`glacier-ng/tests/*.lua`; 8 files, all passing): +**FFI suite** (`glacier-ng/tests/*.lua`; 11 files, all passing): ```sh docker run --rm --cap-add SYS_ADMIN -v ~/Projects/glacier-ng:/src:ro \ -v ~/Projects/libglacier-ng:/libsrc:ro alpine:latest sh -c ' @@ -355,8 +806,9 @@ GNU `tar` is required: the hostile-archive fixtures use `tar --append`. | Suite | Image | |---|---| +| `sign_alpine.sh` | `alpine:latest` with `/libsrc` and `/t.sh` mounted | | `syspkg_alpine.sh`, `overwrite_alpine.sh` | `alpine:latest` with `/src`, `/libsrc` and `/t.sh` mounted (see each header); `overwrite_alpine.sh` runs both as-is and with `--tmpfs /glacier:exec` | -| `verify_alpine.sh`, `user_layout_alpine.sh`, `upgrade_alpine.sh` | `glacier-alpine_root` | +| `verify_alpine.sh`, `user_layout_alpine.sh`, `upgrade_alpine.sh`, `sys_perms_alpine.sh`, `deps_alpine.sh`, `sysroot_alpine.sh`, `root_alpine.sh`, `bootstrap_alpine.sh`, `installed_alpine.sh`, `keyring_alpine.sh`, `repo_alpine.sh`, `audit_alpine.sh`, `fetch_alpine.sh` | `glacier-alpine_root` | Build the `glacier-alpine_root` image with: ```sh @@ -365,6 +817,46 @@ cd ~/Projects/glacier-ng && docker build -f Dockerfile.alpine_root \ docker run --rm -v "$PWD/tests/NAME.sh:/t.sh:ro" glacier-alpine_root sh /t.sh ``` +`glacier-ng/lib/glacier/` does not exist in the tree (the `.so`s are +built per image); syspkg_alpine/overwrite_alpine `mkdir -p` it +(2026-10-07). If Docker fails with `veth ... operation not supported`, +the host kernel was upgraded without a reboot (modules missing): use +`docker build --network host` and `docker run --network=host` (or +`--network none` where nothing is fetched) until the user reboots. +Mutating a Lua file syspkg loads: mount it elsewhere and `cp` it over +`/app/lib/...` inside the container (a bind mount is user-owned, so +syspkg's trust check refuses it). + +**Demo packages:** `glacier-ng/tests/mkpkg [-o DIR] [-k KEY.sec] +[-d NAME:VER]... [-c NAME:VER]... NAME VERSION REPO` writes a one-script +`.gpkg` plus its sidecar (`/app/tests/mkpkg` in the images); a base one +needs `-k /root/glacier-test.sec` (the images' test key) for syspkg. The user plays with changes +in `./run_docker.sh Dockerfile.alpine_root` using it; for an image, +`mkdir -p /mnt/img && ./syspkg -R /mnt/img -n`. + +**Mutation checks, how:** `cp -a` libglacier-ng (and glacier-ng, for Lua +changes) into the scratchpad, break the defence there, then +`docker build -f Dockerfile.alpine_root --build-context libglacier=COPY +-t glacier-mut .` from the (copied) glacier-ng and run the suite against +`glacier-mut`; delete the image after. Docker tags must be lowercase. A +mutation that doesn't compile under `-Werror` proves nothing: rewrite it +so it compiles. Watch for checks that pass for the wrong reason (e.g. a +refusal caused by a missing directory instead of the defence). In shell +suites, `sh -c '... $OUT ...'` sees an EMPTY `$OUT` (not exported), so a +negative check written that way passes vacuously; use a function +(`lacks` in bootstrap_alpine.sh). Found 2026-10-06 by a surviving +mutation. The host shell is zsh: an unquoted `$VAR` holding `-v a:b` +is NOT word-split, so a docker command built that way silently runs +without the mount (a mutant "survived" that way, 2026-10-06). To +mutate a Lua CLI without rebuilding, bind-mount the mutated file over +`/app/NAME` (`-v $PWD/mut:/app/glacier-bootstrap:ro`). + +**Full regression** = the FFI suite + `syspkg_alpine.sh` + +`overwrite_alpine.sh` (both ways) + `sign_alpine.sh` + the thirteen `glacier-alpine_root` +suites, all in parallel; the two output-based suites (`syspkg_alpine`, +`user_layout_alpine`) have no PASS lines, so grep their output for +`Unknown|traceback|failed|GL_` instead (only `--bogus` is expected). + **Interactive images:** `./run_docker.sh Dockerfile.{alpine,alpine_root,arch,debian}`. `run_docker.sh` passes the `libglacier` build context. `alpine`, `arch` and `debian` default to user `glacier` (uid 1000, in wheel); `alpine_root` @@ -372,10 +864,24 @@ defaults to root. All of them have: - root-owned `/app`; - no `/glacier` until `gstore -n` / `syspkg -n`; - the doas rule plus `permit nopass glacier`; +- `/etc/profile.d/glacier.sh` putting `/glacier/usr//links/bin` then + `/glacier/sys/links/bin` on `PATH` (and `lib` on `LD_LIBRARY_PATH`) + unconditionally, since `/glacier` only appears after login (fixed + 2026-10-05: it had no system links and checked `-d` at login); - `glacier.conf` installed. **Boot test:** `cd ~/Projects/everest && ./everest.sh boot` boots QEMU -(KVM). Press Enter for a root shell; `poweroff -f` to leave. +(KVM). Press Enter for a root shell; `poweroff -f` to leave. To test as +a non-root user, `adduser -D -H -h /tmp tester` at runtime, then +`su - tester -c ...` (nothing shipped has a user besides root). +Everest's `/etc/profile` sets `PATH` to per-user links, then +`/glacier/sys/links/bin`, then `/usr/bin` (2026-10-05). Backups of +everest-bootscripts files go NEXT TO `etc/`, never inside it: the package +copies `etc/` wholesale. After changing a package: +`./everest.sh build PKG && ./everest.sh initramfs`, then boot. +To drive the boot non-interactively, fork QEMU on a pty from Python +(`pty.fork()`), wait for `Please press Enter`, send lines and match the +`# `/`$ ` prompt (no `expect` on the host). ## Bug history: so these don't get reintroduced @@ -430,6 +936,99 @@ All fixed in the current tree. context every retry reported `ALREADY_INSTALLED`. 23. Backups written in the same second were pruned in the wrong order (whole-second mtimes). +24. System scope was root 0700 (index, store, links, every version dir), + so no non-root user could run anything syspkg installed, through + `/usr` or `/glacier/sys/links`. Found 2026-10-05. Also fixed: every + mode glacier creates was filtered by the caller's umask (`gl_mkdirp`, + seeding, extracted files), so syspkg under umask 077 made even a + 0755 store private. `gl_mkdirp`/`gl_mkdir_exact` now chmod what they + create, and extracted entries get `archive mode & 07755` explicitly + (never group/world-writable). Test: `tests/sys_perms_alpine.sh` + (mutation-checked). No migration: a store made before the fix keeps + its 0700 dirs until reinstalled. +25. `gl_rebuild_index` `fopen(name, "w")`'d the new index file. Index + names have one-second resolution and seeding HARDLINKS live's index + files into the stage, so a staged install in the same second as the + last commit rewrote the LIVE index in place; an aborted transaction + then left live listing the stage's packages (and history files were + overwritten). It now writes to `tmpfile()` and only `rename()`s onto + the name. Found 2026-10-05 by `deps_alpine.sh`; regression test + `tests/test_index_abort.lua` (mutation-checked). Rule: never open a + seeded file for writing; replace it by rename. +26. Live-context removal (`gl_unlink_pkg`) `unlink()`ed the destination + of every regular file in the package WITHOUT `is_glacier_symlink`: + a foreign file the package had been refused to replace was deleted, + and the package's own symlink entries were never unlinked. The CLIs + always stage, so only library callers were exposed. Removed; + live removal now prunes like a commit (`gl_relink_store`). Found + 2026-10-05; `tests/test_live_remove_foreign.lua` (fails on the old + code). +27. gpkg's `handle_flag` had no `else`: an unknown flag (`-R`) was + silently ignored and the NEXT word reported as the unknown option. + Now `[x] Unknown option: -R` (capitalized, as syspkg). 2026-10-05. +28. gbuild's `clone_source` printed its "Shallow clone failed" warning + (and the full clone's/checkout's output) to stdout, which + `SOURCE_DIR="$(clone_source)"` captured, so building from a commit + hash (`-r `) always failed. Those now go to stderr. Found + 2026-10-06 building luajit. +29. syspkg's and gstore's trust checks walked only the ancestors of the + script's own realpath. Installed, `lib/glacier -> /usr/lib/glacier` + and the `.so`s link into libglacier-ng's store dir, so a non-root- + owned directory there was never checked: root loaded `.so`s from it. + Both now also check every ancestor of each loaded file's realpath. + Found 2026-10-06 by `tests/installed_alpine.sh` (both mutations + caught). + +30. A package could ship top-level `files/.glacier-*` entries. As a + symlink, `.glacier-fresh` made syspkg truncate its target as root + (an unsigned extra package emptied `/root/victim`, reproduced); + `.glacier-overwrite` self-granted `-O`. Reserved names refused, + marker writes `O_NOFOLLOW`. 2026-10-06 audit. +31. `gl_rmdir_recursive` (lstat, then opendir by path) could be steered + into a symlink's target when root deleted a user's tree (`gstore + -x`). Now fd-relative. Race test in audit_alpine.sh. +32. `add_to_archive` (same class, `gstore -b`). Now fd-relative; no test + fails on the old code (window too narrow; review only). +33. Manifests could `@include` any file (read as root); a dependency + entry without a name cut the list short. Both refused. +34. gpkg_common's `read_manifest_fields` (tar + pattern match) could + disagree with the library: a decoy comment hid a replacement. Now + `manifest_of` (`gl_read_pkg_manifest`). +35. Format strings: gstore's `argv[0]`/UID and gpkg `-u`'s operand + reached `lg_printf` as the format; `%d` was given Lua numbers; + gstore UIDs wrapped past uid_t; `gl_abort_transaction` wiped a stage + without holding the lock. All fixed (`lg_printf` now has a printf + format attribute). + +## Known gaps (not bugs, not yet scheduled) + +- No migration for stores made before bug 24's fix (0700 dirs stay). +- Extracted entries keep setuid/setgid bits (`& 07755`), as before. +- No size limit on extraction (a decompression bomb fills the disk). +- Bug 32's fix has no test that fails on the old code. +- Hash verification is still CLI-only (ROADMAP 3.2); everything else + added 2026-10-05 is enforced in the library. +- A dependency refusal for `-x` of a name installed in several repos + comes at commit, after the prompt (its repo is chosen after it). + +## Backups of glacier-ng / everest files (no git there) + +The rule stands (back up before editing: `file.old`, then `.old2`...). +All of glacier-ng's backups were deleted 2026-10-06 at the user's +request. The 2026-10-07 session made new ones: `lib/glacier_cdef.lua.old`, +`lib/gpkg_common.lua.old`, `gpkg.old`, `syspkg.old`, +`tests/{audit,overwrite,syspkg,repo}_alpine.sh.old`, +`lib/gpkg_common.lua.old2`/`.old3`, `tests/repo_alpine.sh.old2`/`.old3`, +`tests/fetch_alpine.sh.old`, `lib/gpkg_common.lua.old4`, +`glacier-bootstrap.old`, `tests/bootstrap_alpine.sh.old`, +`tests/repo_alpine.sh.old4`, `tests/fetch_alpine.sh.old2`; Everest: +`scripts/container.sh.old4`, `everest.sh.old3`, `README.md.old3`, +`templates/minimal.template.old2`, `pkgsrc/everest-bootscripts/glacier.old`. +Everest still has its earlier ones (`everest.sh.old`/`.old2`, +`scripts/container.sh.old`/`.old2`/`.old3`, `README.md.old`/`.old2`, +`Dockerfile.build.old`, `templates/minimal.template.old`, +`pkgsrc/everest-bootscripts/profile.old`). Leave them unless the user +says to clean up. ## Conventions diff --git a/GLACIER_BOOTSTRAP.txt b/GLACIER_BOOTSTRAP.txt index e179c62..428fd76 100644 --- a/GLACIER_BOOTSTRAP.txt +++ b/GLACIER_BOOTSTRAP.txt @@ -2,9 +2,10 @@ | glacier-bootstrap | +--------------------------------------------+ -STATUS: PLAN. Nothing here is implemented. Section 9 lists the -decisions still open; everything else is a recommendation to argue -with, not a settled design. +STATUS: LOCAL MODE IMPLEMENTED 2026-10-06 (glacier-ng/glacier-bootstrap, +tests/bootstrap_alpine.sh; see section 10 for how it differs from the +plan below). Remote mode (-r) and the pinning helper are still a plan. +Section 9 lists the decisions still open. glacier-bootstrap takes a template index and builds a chrootable Everest system in a target directory, from either a local directory of @@ -37,7 +38,8 @@ Sequence: 3. fetch into a private cache; verify every hash (section 5) 4. doas syspkg --root TARGET -n create the tree 5. doas syspkg --root TARGET --strict -l PKG... ONE transaction - 6. optional smoke test (-t) + 6. doas syspkg --root TARGET -y TARGET's indexes + 7. optional smoke test (-t) Step 5 is one transaction on purpose: TARGET ends up either the whole system or an empty tree, never half a system. @@ -100,7 +102,6 @@ per package, so packages can carry pins: # templates/minimal.template NAME=minimal - SYS_PROFILE=x86_64-musl PKG=filesystem 1.0.0 PKG=musl 1.2.5 sha256:3a5f... PKG=busybox 1.37.0 sha256:91c0... @@ -110,7 +111,9 @@ per package, so packages can carry pins: - A pin is optional for -d (if present, it must ALSO match the sidecar) and required for -r until signed repos exist. - Strict parsing: unknown keys, a duplicate PKG name, a malformed pin - and an empty package list are all hard errors. Remember gworld bug + and an empty package list are all hard errors. SYS_PROFILE is an + unknown key (9b): nothing could check it until manifests record a + profile (ROADMAP 3.3). Remember gworld bug 10: a field silently never parsing, with a default that looks right. - A `glacier-bootstrap --pin PKGDIR TEMPLATE` helper that fills pins in from local sidecars is the obvious way to produce a pinned template @@ -131,8 +134,10 @@ doesn't.) Recommendation: CHECK, don't auto-resolve. - Every PKG_DEPS entry of every listed package must itself be listed, - with a matching version. Otherwise error: "busybox needs musl 1.2.5, - which the template does not list." + at that version or later (decided 2026-10-06: the ROADMAP 2.3 rule, so + the template check and syspkg's commit check agree). Otherwise error: + "base::busybox requires musl >= 1.2.5, which the template does not + list." / "..., but the template lists 1.2.4." - No two listed packages may conflict (PKG_CONFS). - Each manifest's PKG_NAME/PKG_VER must match its template line. This catches a mis-named file in -d mode, where there may be no pin. @@ -242,14 +247,18 @@ independent of glacier-bootstrap itself: section 4). Pins mean nothing until syspkg actually checks sidecars. 2. [DONE] Symlink extraction + linking, with the write-through-symlink defense (6a). Needed for anything to run. - 3. sysroot in the library (IMAGE_DESIGN section 3), plus the "host + 3. [DONE 2026-10-05] sysroot in the library (IMAGE_DESIGN section 3), plus the "host /usr and /glacier untouched" test. - 4. syspkg --root DIR (IMAGE_DESIGN section 4), --strict (any link + 4. [DONE 2026-10-05, as -R/--root] syspkg --root DIR (IMAGE_DESIGN section 4), --strict (any link refusal fails the transaction), and the skeleton on -n (6b) [the skeleton is DONE; --root and --strict are not]. - 5. Packages: busybox recipe (base), sbase rebuilt as base, every + 5. [DONE: ~/Projects/everest builds musl, busybox, linux and + everest-bootscripts, all base, with sidecars.] Packages: busybox + recipe (base), sbase rebuilt as base, every package rebuilt with sidecars (6d-f). - 6. glacier-bootstrap, local mode. + 6. [DONE 2026-10-06: Everest's `everest.sh initramfs` now builds its + tree this way, and the result boots in QEMU.] glacier-bootstrap, + local mode. MILESTONE: `glacier-bootstrap -t -d pkgs templates/minimal.template /mnt/everest` succeeds, `chroot /mnt/everest /bin/sh` gives a working shell, and the host is byte-identical before and after. @@ -281,8 +290,8 @@ orchestration over things that have to exist anyway. a. IMAGE_DESIGN.txt section 9a still stands: images are bootstrap OUTPUT, never install INPUT. Everything here assumes yes. - b. Template format (section 3): one PKG= line per package with an - optional pin, or something else. + b. DECIDED 2026-10-06: NAME= plus one `PKG=name version [sha256:hex]` + per package; no SYS_PROFILE until manifests record one (section 3). c. DECIDED 2026-10-04: option 1, copy on install, .new on conflict. Implemented in istoreutils.c (install_config / prune_config): a system-scope package's top-level etc/ is copied into /etc, never @@ -293,7 +302,58 @@ orchestration over things that have to exist anyway. d. DECIDED 2026-10-04: syspkg -n creates the top-level tree itself (6b). Implemented as gl_create_fs_tree(root, verbose), called by gl_init_sys before the index is written. - e. Dependency handling (section 4): check-only, as recommended, or - automatic closure. + e. DECIDED 2026-10-05: automatic closure, once there is a source to + resolve from (remote repos, ROADMAP 3.1). Until then a missing + dependency refuses; the resolver is written so 3.1 plugs in. The + index already records auto vs explicit installs (ROADMAP 2.3). + Section 4's template check still applies to bootstrap: a template + lists what it installs. f. Pinning helper: part of glacier-bootstrap (--pin) or of gworld (which already knows what it just built). + + ++--------------------------------------------+ +| 10. As implemented (local mode, 2026-10-06)| ++--------------------------------------------+ + + glacier-bootstrap [-hvntV] -d PKGDIR TEMPLATE TARGET + +Where it differs from, or adds to, sections 1-8: + + - syspkg is the one on the caller's PATH (decided 2026-10-06), + resolved to an absolute path first and run through doas, or + directly when already root. -t runs `doas chroot`. + - Order: parse, then verify, then resolve. Manifests are read only + from VERIFIED copies: each PKGDIR/NAME-VER.gpkg is copied into a + private mktemp -d while being hashed, checked against its sidecar and + the pin, and given a sidecar written from that hash. syspkg is handed + the copies, so a pin binds syspkg too, and a file swapped in PKGDIR + later is never read. The copies are removed on every exit. + - TARGET must be absolute, not /, and pass gl_check_sysroot (checked + read-only up front). It must be fresh, or initialised by syspkg -n + with nothing installed: then -n is skipped, so a run declined at + syspkg's prompt can simply be repeated. TARGET with packages is + refused. + - syspkg's prompt is passed through (it's the review step). It exits 0 + when declined, so afterwards TARGET's index is read (it's world- + readable) and every template package must be listed. + - Every problem in a phase is reported before failing. Exit status: + 1 usage or target, 2 template, 3 verification, 4 resolution, + 5 installation, 6 smoke test, 7 repositories. + - Repositories (2026-10-07; the user: bootstrap must leave a + semi-functional system, so it configures repositories, pacstrap + style). The configuration comes from a PACKAGE in the template + (Everest: everest-repos, shipping /etc/glacier/repos.conf and + keys/*.pub), so bootstrap still never writes into TARGET. After the + install check, if TARGET's repos.conf gives any repository a url, + it runs `syspkg -R TARGET -y`: syspkg reads TARGET's OWN keyring + under -R for repository operations (REPO_DESIGN section 6, "Keyrings + under -R"), fetches into TARGET/glacier/sys/repos, and so sets + TARGET's rollback floor at build time. A failed fetch: exit 7, + TARGET stays installed, no "Bootstrapped" line. No repos.conf, or + no url: `[!] No remote repositories are configured in TARGET.` and + exit 0. Copying the host's repos.conf (pacstrap's mirrorlist copy) + is left out for now (user, 2026-10-07). + - Its own lib/ is found from its realpath (package.path and ffi.load), + not the current directory; no root-ownership checks (unprivileged). + - Not implemented: -r (remote) and --pin (9f still open). diff --git a/IMAGE_DESIGN.txt b/IMAGE_DESIGN.txt index d7ca5b4..0648e88 100644 --- a/IMAGE_DESIGN.txt +++ b/IMAGE_DESIGN.txt @@ -116,8 +116,11 @@ string against the runtime store prefix. But every "does the target exist" check has to become "does T + target exist". The same goes for restore_displaced and for anything else that follows a link. -Proposed shape: +Shape (ACCEPTED 2026-10-05): + - The library refuses a sysroot other than "/" for GL_SCOPE_USR + contexts. Only system scope may target another root, for every + caller, not just the CLIs (section 4). - gl_context_t gains `char sysroot[PATH_MAX]` ("" == "/"; appended at the end, matched in lib/glacier_cdef.lua and tests/glacier_ffi.lua). @@ -126,6 +129,12 @@ Proposed shape: wrappers passing "/", so every current caller is unchanged. - One helper, gl_at(ctx, runtime_path, out), used for EVERY filesystem operation. Runtime paths stay what's stored and compared. + AS BUILT (2026-10-05): the reverse, same effect. The context stores + INSTALL paths, used for every filesystem operation unchanged, and + gl_runtime_path() translates only where runtime meaning matters + (link targets, is_glacier_symlink, .displaced keys); gl_at() checks + a link's target during prune. A missed translation then lands in T, + never on the host. - GL_BASE_LINKS_DEST resolves to sysroot + "/usr". Never the host's /usr when a sysroot is set. @@ -239,9 +248,13 @@ Consequences of /usr starting empty that the design has to own: | 8. Milestones | +--------------------------------------------+ - 1. sysroot in the library (section 3), plus the host-untouched test. + 1. [DONE 2026-10-05] sysroot in the library (section 3), plus the + host-untouched test (glacier-ng/tests/sysroot_alpine.sh). Implemented + with INSTALL paths in the context and runtime translation where a + path's meaning matters; see CLAUDE.md "Install root / sysroot". No CLI changes, testable through FFI alone. - 2. syspkg --root (section 4), with the root-owned-target check. + 2. [DONE 2026-10-05] syspkg --root (section 4), with the root-owned-target + check, as -R/--root (implies --strict). glacier-ng/tests/root_alpine.sh. 3. A `filesystem` base package (section 7). 4. images/minimal.image + a driver script (gworld -> syspkg --root -> finalize). Milestone: `chroot T /bin/sh` works, and diff --git a/Makefile b/Makefile index 3b627c5..1bb07b6 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ include config.mk -.PHONY: all prepare log log_test rt pkg istoreutils make_conf dag install clean static libglacier.a install_static libglacier-ng sha256 gsha256sum libglacier_verify +.PHONY: all prepare log log_test pkg transaction istoreutils install clean static libglacier.a install_static libglacier-ng sha256 gsha256sum libglacier_verify gsign grepo -all: prepare log log_test rt pkg transaction istoreutils make_conf dag sha256 libglacier_verify gsha256sum +all: prepare log log_test pkg transaction istoreutils sha256 libglacier_verify gsha256sum gsign grepo @echo "libglacier-ng has finished building" static: prepare log_static pkg_static istoreutils_static libglacier.a @@ -15,12 +15,14 @@ pkg_static: istoreutils_static: pkg_static $(CC) $(CFLAGS) -c src/istoreutils.c -o build/lib/tmp/istoreutils.o + $(CC) $(CFLAGS) -c src/deps.c -o build/lib/tmp/deps.o libglacier.a: ar rcs build/lib/libglacier.a \ build/lib/tmp/log.o \ build/lib/tmp/pkg.o \ - build/lib/tmp/istoreutils.o + build/lib/tmp/istoreutils.o \ + build/lib/tmp/deps.o prepare: mkdir -p build @@ -38,19 +40,30 @@ log: log_test: $(CC) $(CFLAGS) -shared -fPIC src/log.c -o build/lib/shared/libglacier_log.so -rt: - $(CC) $(CFLAGS) src/runtime.c -c -o build/lib/tmp/runtime.o - sha256: $(CC) $(CFLAGS) src/sha256.c -c -o build/lib/tmp/sha256.o +# Monocypher, vendored unmodified (src/monocypher/README): Ed25519 for +# sign.c. +MONOCYPHER = src/monocypher/monocypher.c src/monocypher/monocypher-ed25519.c + libglacier_verify: - $(CC) $(CFLAGS) -shared -fPIC src/sha256.c \ + $(CC) $(CFLAGS) -shared -fPIC src/sha256.c src/sign.c $(MONOCYPHER) \ -o build/lib/shared/libglacier_verify.so gsha256sum: prepare $(CC) $(CFLAGS) src/gsha256sum.c src/sha256.c -o build/bin/gsha256sum +gsign: prepare + $(CC) $(CFLAGS) src/gsign.c src/sign.c src/log.c $(MONOCYPHER) -o build/bin/gsign + +# grepo reads packages (manifest, hash) and signs the index: built +# from source like gsign, so it needs no rpath to the shared objects. +grepo: prepare + $(CC) $(CFLAGS) src/grepo.c src/sign.c src/sha256.c src/log.c src/pkg.c \ + src/istoreutils.c src/deps.c src/transaction.c $(MONOCYPHER) \ + -larchive -lconfig -o build/bin/grepo + transaction: log istoreutils $(CC) $(CFLAGS) -shared -fPIC src/transaction.c \ -Lbuild/lib/shared -lglacier_istoreutils -lglacier_log \ @@ -58,7 +71,7 @@ transaction: log istoreutils -o build/lib/shared/libglacier_transaction.so istoreutils: pkg - $(CC) $(CFLAGS) -shared -fPIC src/istoreutils.c \ + $(CC) $(CFLAGS) -shared -fPIC src/istoreutils.c src/deps.c \ -Lbuild/lib/shared -lglacier_log -lglacier_pkg -larchive \ -Wl,-rpath,'$$ORIGIN' \ -o build/lib/shared/libglacier_istoreutils.so @@ -69,21 +82,12 @@ pkg: -Wl,-rpath,'$$ORIGIN' \ -o build/lib/shared/libglacier_pkg.so -make_conf: - $(CC) $(CFLAGS) src/make_conf.c -c -o build/lib/tmp/make_conf.o - libglacier-ng: prepare $(CC) $(CFLAGS) -shared -fPIC \ - src/log.c src/pkg.c src/transaction.c src/istoreutils.c \ + src/log.c src/pkg.c src/transaction.c src/istoreutils.c src/deps.c \ -larchive -lconfig \ -o build/lib/shared/libglacier-ng.so -dag: - $(CC) $(CFLAGS) -shared -fPIC src/dag.c \ - -Lbuild/lib/shared -lglacier_log \ - -Wl,-rpath,'$$ORIGIN' \ - -o build/lib/shared/libglacier_dag.so - install: mkdir -p $(PREFIX)/lib/glacier/ install build/lib/shared/libglacier_log.so $(PREFIX)/lib/glacier/ -m 755 @@ -92,14 +96,12 @@ install: install build/lib/shared/libglacier_pkg.so $(PREFIX)/lib/glacier -m 755 install build/lib/shared/libglacier_verify.so $(PREFIX)/lib/glacier -m 755 install build/bin/gsha256sum $(PREFIX)/bin/ -m 755 + install build/bin/gsign $(PREFIX)/bin/ -m 755 + install build/bin/grepo $(PREFIX)/bin/ -m 755 install_static: mkdir -p $(PREFIX)/lib/glacier install build/lib/libglacier.a $(PREFIX)/lib/glacier/ -m 644 -install_test_dir: - install build/lib/shared/lg_log.so $(TEST_DIR) - install build/lib/shared/ - clean: rm -rf build diff --git a/PACKAGE_SCOPES.txt b/PACKAGE_SCOPES.txt index df46661..03daac9 100644 --- a/PACKAGE_SCOPES.txt +++ b/PACKAGE_SCOPES.txt @@ -35,6 +35,13 @@ SYS (GL_SCOPE_SYS) System-wide packages, not owned by any particular uid: /glacier/sys/{index,store,links,stage,old,lock} Symlink destination depends on the package's repo (see section 3). + Root-owned and world-READABLE, like /usr: index/store/links, the + store's repo/name/version dirs and implicit parent dirs are 0755, + index files 0644 (gl_scope_dir_mode / gl_scope_file_mode). /usr's + links point into the store, so every user must be able to traverse + it, and user-scope dependency checks read the system index. The + modes are applied explicitly, not left to syspkg's umask. The lock + stays 0600; .displaced, .etc-state and backup/ stay 0700. Requires root. gpkg enforces this via geteuid() before it will even attempt a system-scope operation. diff --git a/REPO_DESIGN.txt b/REPO_DESIGN.txt new file mode 100644 index 0000000..eea4b82 --- /dev/null +++ b/REPO_DESIGN.txt @@ -0,0 +1,446 @@ ++--------------------------------------------+ +| Remote repositories and signing | ++--------------------------------------------+ + +STATUS: DESIGN, 2026-10-06. ROADMAP 3.1. Steps 1 (Ed25519, signify +formats, gsign), 2 (keyring, the local base rule, gbuild -k, +bootstrap carrying .sig, Everest's key) and 3 (grepo, the index, +refresh with -y) of section 9 DONE 2026-10-06, step 4 (-f, -u, +dependency closure, the cache) DONE 2026-10-07; sections 7, 5 and 6 +say how they came out. Step 5 next. See CLAUDE.md. +Sections 1 and 10 are decided; the only open item is 10a's TLS library. + +Adds AUTHENTICITY to glacier: SECURITY_DESIGN.txt section 1 deferred it +to "a future phase, tied to remote-repo support". This is that phase. +It also unblocks gpkg/syspkg -f (ROADMAP 2.2), -u (2.1), automatic +dependency closure (2.3) and glacier-bootstrap -r (GLACIER_BOOTSTRAP +section 5), and closes Finding 1 of SECURITY_REVIEW_2026-07-16.md (a +manifest that says PKG_REPO = "base" gets linked into /usr). + +Companion documents: SECURITY_DESIGN.txt (integrity, refusal matrix), +PACKAGE_SCOPES.txt (repo routing), GLACIER_BOOTSTRAP.txt (pins). + + ++--------------------------------------------+ +| 1. Decisions (the user, 2026-10-06) | ++--------------------------------------------+ + + a. Signatures: Ed25519, vendored (Monocypher's Ed25519 variant, which + brings its own SHA-512), compiled into the private + libglacier_verify.so next to sha256. Key and signature files are + signify-compatible, so OpenBSD's signify can make and check them + too. No new base dependency. + b. What is signed: one INDEX per repository (repo.idx + repo.idx.sig), + listing every package it serves with its sha256. Packages are + verified through the hash in the signed index, i.e. through the + existing sha256 path. The index has a serial; a client refuses an + index older than the last one it accepted (rollback). + c. Local base packages: once signing exists, `syspkg -l FILE` of a + package whose manifest says base requires a signature by a key + trusted for base. extra/community local installs stay + sidecar-only (integrity). Manual installation keeps working: a + builder makes a key and root adds it to the keyring. + d. Users may add their own repositories and keys, for their own scope + only (gpkg). Never read by syspkg. + + ++--------------------------------------------+ +| 2. Keys and signatures (proposal) | ++--------------------------------------------+ + +signify's formats, byte for byte (two lines: "untrusted comment: ..." +then base64): + + public key "Ed" | keynum[8] | pubkey[32] + signature "Ed" | keynum[8] | sig[64] + secret key "Ed" | "BK" | rounds[4] | salt[16] | checksum[8] | + keynum[8] | seckey[64] + + - keynum is a random 8-byte key id; a signature names the key that + made it, so verification picks the key by keynum and never tries + every key in the keyring. + - Signing is pure Ed25519 over the WHOLE message, as signify does. So + we never sign a .gpkg directly (it would have to be read into memory + whole). What gets signed is small: + - a repository's repo.idx; + - a local package's SIDECAR (NAME-VER.gpkg.sha256, the one line + "sha256:"), as NAME-VER.gpkg.sha256.sig. Checking the + sidecar against the file is the existing path; the signature + makes the sidecar authentic. `signify -V -p KEY -m X.sha256` + checks the same file. + - Secret keys: glacier's tools read and write only UNENCRYPTED secret + keys (rounds = 0, what `signify -G -n` makes): passphrase support + needs bcrypt_pbkdf, which is more vendored crypto for a build-host + convenience. A passphrase-protected key works by signing with + signify itself (10b). + +New tool `gsign` (C, built with gsha256sum; a build/maintainer tool, +later in "glacier-tools", never in base): + + gsign -G -p KEY.pub -s KEY.sec [-c COMMENT] make a keypair + gsign -S -s KEY.sec -m FILE [-x SIGFILE] sign (default FILE.sig) + gsign -V -p KEY.pub -m FILE [-x SIGFILE] verify + +gbuild gains `-k KEY.sec`: sign the sidecar it writes. + + ++--------------------------------------------+ +| 3. Keyring and repository configuration | ++--------------------------------------------+ + +System (root-owned; syspkg applies its trust checks to these files and +every ancestor, like its own lib/): + + /etc/glacier/keys/NAME.pub + /etc/glacier/repos.conf + +repos.conf (libconfig, like glacier.conf): + + repos = ( + { name = "base"; url = "https://pkg.example.org/base"; + keys = [ "everest-2026" ]; }, + { name = "extra"; url = "https://pkg.example.org/extra"; + keys = [ "everest-2026" ]; } + ); + + - A key is trusted ONLY for the repos that list it. That binding is + what makes "base" mean something: the base index must be signed by a + key listed for base, and a local base package's sidecar likewise. + Several keys per repo allow rotation (old and new side by side). + - Order = priority, when one name is served by several repos. + - name follows the gl_valid_pkg_ident rules. url is https:// or + file:// (local mirrors, tests); http:// is refused. + - url is optional: an entry without one only says which keys are + trusted for that repo, for a system installed and kept up by hand + from signed local files (section 7). + - Missing file = no remote repositories; -f/-u then refuse in one + line. No defaults, as with KEEP_OLD. + +User (gpkg only): + + ~/.config/glacier/repos.conf ($XDG_CONFIG_HOME) + ~/.config/glacier/keys/NAME.pub + + - Merged AFTER the system list (lower priority). + - A user repo named "base", or named like a system repo, is refused. + - System keys are trusted for system repos only; a user's keys for the + user's repos only. + + ++--------------------------------------------+ +| 4. The index | ++--------------------------------------------+ + +URL/repo.idx and URL/repo.idx.sig. libconfig, parsed only AFTER its +signature checks: + + REPO = "base"; + SERIAL = 2026100601L; # strictly increases with each publish + VALID_UNTIL = 1761955200L; # unix time; required (10c) + PKGS = ( + { NAME = "musl"; VER = "1.2.5"; FILE = "musl-1.2.5.gpkg"; + SHA256 = "sha256:3a5f..."; SIZE = 812345L; + DEPS = (); CONFS = (); }, + ... + ); + + - REPO must equal the configured name the index was fetched for. + - Every version a repo serves is listed (so a pinned or older version + can still be fetched by -f NAME=VER later). + - DEPS/CONFS repeat the manifest's, so dependency closure can be + planned before downloading anything. After download, the manifest + must match the entry (NAME, VER, PKG_REPO = REPO, DEPS, CONFS): + a mismatch refuses the package. That is the Finding 1 rule for + remote packages: a package is "base" because the base index lists + it AND it says so, never because it says so. + - FILE is a bare file name (gl_valid_pkg_ident rules plus ".gpkg"): + no paths, no URLs. + +New tool `grepo` (maintainer side, glacier-tools): `grepo -s KEY.sec +-r REPO DIR` reads every NAME-VER.gpkg in DIR (manifests, hashes), +writes DIR/repo.idx with SERIAL = the previous one + 1 (or the date +form), and signs it. The server is then any static file host. + + ++--------------------------------------------+ +| 5. Client state and fetching | ++--------------------------------------------+ + +Inside each scope's base directory (one more entry beside index, store, +...; same ownership and modes as the rest): + + /repos/REPO.idx, REPO.idx.sig the last ACCEPTED index + /cache/REPO/FILE downloaded packages + +Refresh (every -f / -u; indexes are small; 10d): + 1. curl --proto =https --proto-redir =https --fail (or a file:// copy) + URL/repo.idx and .sig into a temporary file in /repos/. + 2. Verify the signature with a key listed for that repo (by keynum). + 3. Parse. REPO must match. SERIAL must be > the stored one, or equal + with identical bytes. VALID_UNTIL must be in the future. + 4. rename() over the stored copy (never written in place: bug 25). +Any failure keeps the old index and refuses the operation in one line: + [x] Index for extra is older than the one already accepted. + +Fetch: download FILE into the cache under a temporary name, hash it +while downloading, rename into place only if the hash matches the +index, then write the sidecar FROM THE INDEX and hand the cached file +to the existing install path (which re-hashes it, as today). The cache +is a download optimization, not a trusted store: a cache hit is hashed +again. + + As implemented (2026-10-06, step 3; the user chose a refresh flag, + the date-form SERIAL and grepo in C): + - grepo is C (src/grepo.c, libglacier-ng; built from source like + gsign, installed to bin; not in base): `grepo -s KEY.sec -r REPO + [-n SERIAL] [-e DAYS] DIR`. SERIAL = max(previous + 1, today (UTC) + * 100 + 1), e.g. 2026100601; -n must still be above the previous + one. VALID_UNTIL = now + DAYS (default 30, 1..3650). Packages are + sorted by file name; each must be a regular file, its manifest must + say REPO and its name must be NAME-VER.gpkg (VER as the store names + versions, "%d.%d.%d"). An existing DIR/repo.idx must be a + well-formed index of REPO (its SERIAL is the previous one). It is + written to a mkstemp file, signed, and both renamed into place, the + .sig first. + - The index format is section 4's, with DEPS and CONFS as lists of + { NAME = "x"; VER = "1.0.0"; } (the manifest's PKG_DEPS shape). + gl_index_parse (pkg.c) takes the BYTES the signature was checked + over, never a path, and is strict: exactly REPO, SERIAL > 0, + VALID_UNTIL > 0, PKGS; each package exactly NAME, VER, FILE, SHA256, + SIZE >= 0, DEPS, CONFS; FILE must be exactly "NAME-VER.gpkg"; SHA256 + "sha256:" + 64 lowercase hex; no NAME + VER twice. A NUL or an '@' + anywhere is refused before libconfig sees it ('@include'). + GL_INDEX_ERR_REPO when REPO is not the repo it was fetched for. + - `syspkg -y` / `gpkg -y` (--refresh) refresh every repos.conf entry + with a url and stop (decision 10d still holds for -f/-u). gpkg + reads the SYSTEM repos.conf and keyring until step 6 (user repos), + and stores into the user's base. No repos.conf, or no entry with a + url: `[x] No remote repositories are configured.` (exit 1). + - /repos is made by gl_scope_state_dir (transaction.c): the + base must be the scope's; repos is created with the scope's mode + (0755 root / 0700 uid) and refused unless it is a real directory + with exactly that owner and mode. Files are 0644 / 0600. -y holds + the scope's lock (gl_begin_transaction on a live context); under + -R the indexes go under the install root, checked with the install + root's OWN keyring (DIR/etc/glacier; changed 2026-10-07, see + "Keyrings under -R" below). + - file:// is read directly (absolute paths only); https:// goes + through `curl -sf --proto =https --proto-redir =https`. 64 MiB at + most (GL_SIGN_MAX_MSG). Every repo is tried; exit 1 if any failed. + - One line per repo: + [✓] Index for base: serial 2026100601, 11 packages. + [i] Index for base is up to date: serial 2026100601. + [x] Cannot fetch the index for base. (a missing .sig too) + [x] Index for base is not signed by a key trusted for base. + [x] Malformed index for base. + [x] Index fetched for base is the index of another repository. + [x] Index for base is older than the one already accepted. + [x] Index for base has the serial of the one already accepted, but other contents. + [x] Index for base has expired. + [x] Malformed /glacier/sys/repos/base.idx. (left for the admin) + - The accepted copy is trusted by where it lives (root's or the + user's private repos dir): it is parsed for its SERIAL, not + re-verified. + - Test: glacier-ng/tests/repo_alpine.sh (103 checks; 18 mutants, 12 C + and 6 Lua, all caught). + + ++--------------------------------------------+ +| 6. -f, -u and dependency closure | ++--------------------------------------------+ + + -f NAME... (ROADMAP 2.2): like -l, with the file found by name in the + accepted indexes (highest version, first repo by priority). Missing + dependencies are added automatically (ROADMAP 2.3's closure): the + highest version satisfying the minimum, recorded `auto`. The + summary lists them, marked as dependencies. Conflicts and every + 2.3 check run as today, before the prompt. + -u (ROADMAP 2.1): every installed package whose repo is configured and + whose index has a higher version is replaced, as -lO does (backups, + KEEP_OLD). Packages from repos not configured are left alone. + `-u NAME...` limits it to those names. + syspkg -f/-u fetch only from system repos; gpkg -f/-u from system and + the user's repos. Base is still refused in user scope (library rule). + + As implemented (2026-10-07, step 4; M.fetch / M.upgrade in + glacier-ng/lib/gpkg_common.lua). The user decided three details: + - Which repo: the FIRST repo by repos.conf order that serves NAME, at + its highest version. A lower-priority repo never wins by publishing + a bigger version. -u stays within the installed package's repo. + - A dependency installed at too old a version is UPGRADED in the same + transaction from its own repo (backed up per KEEP_OLD, like -lO), + keeping its auto/explicit mark; the summary shows + "old -> new (dependency)". + - Cache: downloads stay in /cache/REPO while a run is declined + or refused; the files a commit installed are deleted after it. + The rest: + - Every -f/-u runs -y's refresh first; if any repo fails, the + operation is refused (decision 10d). The accepted indexes are then + read from /repos (gl_index_parse again; trusted by location). + - Closure: for each planned package, each DEPS entry of its INDEX + entry is met by a planned package, an installed one at >= VER + (this scope; for a user, the system's index too), else upgraded (an + installed one too old) or added (pick(): first repo serving it, + recorded auto). What can't be met is left to gl_check_plan, which + prints the 2.3 refusal. The summary marks additions + "(dependency)". + - Download: gl_scope_cache_dir (transaction.c) makes and checks + /cache and /cache/REPO as gl_scope_state_dir checks + repos (owner, exact mode, not a symlink). A cached file whose hash + matches the index is reused; otherwise file:// is read, https:// + goes through `curl -sf --proto =https --proto-redir =https` on a + pipe; at most SIZE + 1 bytes are ever read, hashed on the way, into + a dot-named temporary, renamed into place only if size and hash + match. 0644 / 0600. + - No sidecar is written: the index's sha256 is handed to + verify_and_copy directly (its `vouched` argument), which re-hashes + while copying into the private dir, as for -l. A base package needs + no .sig of its own: the base index vouches for it. + - The copy's manifest (library-read) must say exactly what the index + entry says: NAME, VER, PKG_REPO = the repo, DEPS and CONFS (same + order, "NAME MAJ.MIN.PAT"). Else, and for a bad hash or size: + [x] extra::evil 1.0.0 does not match its index. + and the cached file is deleted. Nothing to download: + [x] Cannot fetch extra::foo 1.0.0. + No repo serves a name: + [x] foo is not in any repository. + - -f of a name installed at another version needs -O (as -l); + dependency upgrades and -u don't (meta.may_replace). -O still also + means displacing foreign files. + - -u [NAME...]: a named package not installed, or whose repo has no + url, is refused (`[x] foo is not installed.` / `[x] community::foo + is not in any configured repository.`); plain -u skips the latter. + Nothing newer: `[✓] Nothing to do.`, exit 0. + - gpkg: user scope never fetches from base (base indexes are not even + considered, so `[x] core is not in any repository.`); the system's + repos.conf until step 6; indexes and cache in the user's base. + - syspkg -R DIR -f/-u: indexes and cache under DIR, DIR's keyring. + + Keyrings under -R (decided 2026-10-07, for glacier-bootstrap + configuring the system's repositories): -y, -f and -u read + DIR/etc/glacier/repos.conf and DIR/etc/glacier/keys, with the same + trust checks, plus: each file's real path must lie inside DIR + (`[x] Refusing P: it resolves outside DIR.`), so a symlink in DIR + can't make the host's file stand in for DIR's. The indexes are DIR's + state and DIR checks them with its own keys from then on, and a host + with no glacier configuration can still bootstrap. A local `-R -l` + base package's .sig is still checked with the HOST's keyring (section + 7). Tests: repo_alpine.sh / fetch_alpine.sh give the image its own + keyring; bootstrap_alpine.sh sections 14-16. + - Test: glacier-ng/tests/fetch_alpine.sh (84 checks; 16 Lua mutants + and 1 C mutant, all caught except the download-level hash check, + which is EQUIVALENT: the private copy's re-hash refuses the same + package and the mismatch path deletes the cached file. Kept as + defence in depth: a bad download never sits in the cache under its + final name). + - Known gap: a run killed mid-download leaves its dot-named temporary + in the cache (never read; the next fetch writes its own). + + ++--------------------------------------------+ +| 7. Local installs (decision 1c) | ++--------------------------------------------+ + + syspkg -l FILE, manifest says base: + FILE.sha256 must match (as today) AND FILE.sha256.sig must verify + with a key listed for base in repos.conf. Otherwise: + [x] base::musl is not signed by a key trusted for base. + syspkg -l, other repos; gpkg -l: unchanged (sidecar only). + glacier-bootstrap -d: carries each package's .sig into its private + copies, so syspkg -R checks it. The keyring checked is the HOST's + /etc/glacier (the build host's trust, as KEEP_OLD is today). + Consequence for ~/Projects/everest: it needs a key; container.sh signs + every base package (gbuild -k); the build host's /etc/glacier/keys and + repos.conf list it. The image ships the PUBLIC key and a repos.conf so + the installed system trusts its own base (since 2026-10-07 in the + everest-repos package, split out of everest-bootscripts). + + As implemented (2026-10-06, step 2): + - The repo is the one the LIBRARY reads from the verified copy + (gl_read_pkg_manifest, as gl_install_pkg), never gpkg_common's + pattern-matching read_manifest_fields: a manifest comment + `# PKG_REPO = "extra"` would make that one say extra. + - The sidecar is read ONCE; those bytes are both what the copy's hash + is checked against and what the signature is checked over. So + glacier-bootstrap copies the sidecar's bytes as read (it used to + write a new one from the hash) next to the .sig. + - The key is the one, among those listed for base, whose keynum the + signature names. Any other problem with the signature (missing, + malformed, other bytes, other key) is the one-line refusal above, + before the prompt; one refused package refuses the batch. + - repos.conf is read by gl_conf_repo_keys (pkg.c), strictly: `repos` + is the only setting; each entry has name, keys and optionally url, + nothing else; names and key names follow gl_valid_pkg_ident; a + duplicate name, or a url other than https://X or file://X, is + refused. Any of that: `[x] Malformed /etc/glacier/repos.conf.` + - repos.conf and each listed key, every ancestor of them and of their + real paths, must be root-owned and not group/world-writable: + `[x] Refusing F: P is not root-owned, or is group/world-writable.` + A listed key that is missing (`Cannot read F.`) or not a public key + (`Malformed F.`) stops the run: the admin's configuration is at + fault, not the package. Read only if some package is base. + - gbuild -k KEY.sec signs the sidecar (gsign from $GSIGN_BIN, + build/bin, or PATH), trying the key before building; a build + without -k removes a stale .sig. tests/mkpkg -k likewise. + - Test images (Dockerfile.*) carry a TEST key, glacier-test, listed for + base, its secret at /root/glacier-test.sec. tests/keyring_alpine.sh + covers this section (17 mutants, all caught). + - KEEP_OLD backups are re-packed from the store, so a base backup has + no signature: `syspkg -lO` refuses it until the restore interface + exists (decision 10f). + + ++--------------------------------------------+ +| 8. Where the checks live | ++--------------------------------------------+ + +Signature verification is C (libglacier_verify.so). The POLICY (which +key for which repo, the base rule, rollback state) lives in the CLIs' +shared module, like hash verification today, so a third-party caller of +gl_install_pkg still isn't checked. ROADMAP 3.2 (move verification into +the library) now covers signatures too. + + ++--------------------------------------------+ +| 9. Order of work | ++--------------------------------------------+ + + 1. Vendored Ed25519 + signify formats in libglacier_verify.so; gsign; + tests including interop with signify (Alpine's `signify` package) + and hostile inputs (bad base64, wrong keynum, truncated files). + 2. Keyring + repos.conf + the local base rule (section 7); gbuild -k; + glacier-bootstrap carries .sig; Everest gets a key. + 3. Index: grepo; refresh, verify, rollback state. DONE 2026-10-06 + (section 5, "As implemented"; syspkg/gpkg -y). + 4. -f with closure; then -u. DONE 2026-10-07 (section 6, "As + implemented"). + 5. glacier-bootstrap -r against an index (pins stay optional extra + assurance). + 6. User repos. +Each step is tested in Docker (file:// repos need no network) and +mutation-checked, as usual. + + ++--------------------------------------------+ +| 10. Decisions on the details (2026-10-06) | ++--------------------------------------------+ + + a. Fetching on Everest: curl plus one TLS library in base. Signatures + make packages authentic regardless; TLS also hides WHAT is fetched + and stops a network attacker serving a stale (still valid) index. + STILL OPEN: which TLS library (BearSSL or LibreSSL); decide when + packaging it. Until then, everything is built and tested with + file:// repos and Alpine's curl. + b. No encrypted secret keys in gsign (no bcrypt_pbkdf). A + passphrase-protected key signs with signify itself. + c. VALID_UNTIL is REQUIRED; an expired index is refused. The + maintainer sets the lifetime; grepo defaults to 30 days. + d. Indexes are refreshed on every -f / -u. Offline, they refuse. + e. Everest's SECRET key lives outside the project tree: + ~/.config/everest/keys/everest-2026.sec (EVEREST_KEYS), made by + `everest.sh keygen`, mounted read-only into the build container. + The public key ships in everest-bootscripts (/etc/glacier/keys). + f. Base backups (KEEP_OLD) are refused by `syspkg -lO` for want of a + signature; accepted for now. The restore interface (KEEP_OLD's + "later") can trust backups glacier made in its root-owned backup + dir. diff --git a/ROADMAP.txt b/ROADMAP.txt index 7ea18e0..b05a16a 100644 --- a/ROADMAP.txt +++ b/ROADMAP.txt @@ -2,13 +2,14 @@ | Glacier roadmap | +--------------------------------------------+ -What's left to build, in priority order. Status as of 2026-10-04. +What's left to build, in priority order. Status as of 2026-10-07. Design detail lives in the companion docs; this file says WHAT and in what order, and links to WHERE: SECURITY_DESIGN.txt integrity, scope separation, refusal matrix PACKAGE_SCOPES.txt layout, link routing, /etc config handling IMAGE_DESIGN.txt system images as bootstrap output, sysroot + REPO_DESIGN.txt remote repositories, signing, keyring GLACIER_BOOTSTRAP.txt glacier-bootstrap plan, prerequisites, decisions Mark an item [DONE yyyy-mm-dd] rather than deleting it, so the history @@ -23,7 +24,9 @@ file tracks work. The critical path: from "QEMU boots an initramfs assembled by a script" (~/Projects/everest) to "glacier installs a system". - 1.1 sysroot in the library (IMAGE_DESIGN section 3). Every path is + 1.1 [DONE 2026-10-05: see CLAUDE.md "Install root / sysroot" and + tests/sysroot_alpine.sh.] + sysroot in the library (IMAGE_DESIGN section 3). Every path is hardcoded to /glacier and /usr. Needs separate install-root (where bytes are written) and runtime-root (what symlink targets say). The trap: prune_stale_links follows absolute links, so a @@ -31,20 +34,58 @@ The critical path: from "QEMU boots an initramfs assembled by a script" filesystem. Required test: an image build leaves the host's /usr and /glacier byte-identical. - 1.2 syspkg --root DIR (refuses unless DIR and its ancestors are + 1.2 [DONE 2026-10-05: -R/--root, strict by default under -R, --strict.] + syspkg --root DIR (refuses unless DIR and its ancestors are root-owned and not group/world-writable) and --strict (any link refusal fails the transaction; an image's /usr starts empty). gl_create_fs_tree already takes a root prefix. - 1.3 glacier-bootstrap, local mode (GLACIER_BOOTSTRAP.txt sections 1-6). + 1.3 [DONE 2026-10-06: glacier-ng/glacier-bootstrap, GLACIER_BOOTSTRAP + section 10; Everest's initramfs is built with it and boots.] + glacier-bootstrap, local mode (GLACIER_BOOTSTRAP.txt sections 1-6). Template index -> resolve -> verify -> syspkg --root, one transaction. Never writes into TARGET itself. + [DONE 2026-10-07: repositories. TARGET's repos.conf + keys come + from a package (Everest: everest-repos); bootstrap then runs + syspkg -R TARGET -y, which reads TARGET's own keyring; exit 7 on + a failed fetch. GLACIER_BOOTSTRAP section 10. Later, maybe: an + opt-in copy of the host's repos.conf (pacstrap's mirrorlist).] - 1.4 Packages for glacier itself, so an installed system can manage + 1.4 [DONE 2026-10-06: built by ~/Projects/everest/scripts/container.sh, + in templates/minimal.template, boot-verified in QEMU; full + regression passes.] + Packages for glacier itself, so an installed system can manage itself: luajit, libarchive (+ zlib), libconfig, libglacier-ng, glacier-ng (syspkg, gpkg, gstore), doas. + Decided 2026-10-06: + - base: luajit, zlib, libarchive, libconfig, libglacier-ng, + glacier-ng. extra: doas (replaceable, e.g. by sudo). + - glacier-ng ships syspkg, gpkg and gstore only. glacier-bootstrap, + gbuild, gworld and gsha256sum become a later "glacier-tools" + package in extra (after the release, which brings a real + install Makefile). + - everest-bootscripts ships /etc/doas.conf: `permit :wheel`, plus + `permit nopass :wheel as root cmd /usr/bin/gstore args -n`. + - Layout: scripts + lib/ in usr/lib/glacier-ng/, usr/bin/{syspkg, + gpkg,gstore} symlink into it (realpath lands in the store, lib/ + beside it); gpkg moves to the same real-path loading. + As built: zlib 1.3.2, libarchive 3.8.9 (gzip + tar only; every + other codec, crypto library and front-end disabled), libconfig + 1.8.2 (no C++), luajit 2.1.1788856981 (commit c6ffc141), + libglacier-ng 4.0.0, glacier-ng 4.0.0, doas 6.8.2 (OpenDoas, no + PAM, no timestamp). The doas.conf rule is given for both `cmd + gstore` and `cmd /usr/bin/gstore`: OpenDoas matches what was typed. + luajit is linked -static-libgcc, since Everest ships no libgcc_s + (see section 6). - 1.5 The installer. Undesigned; needs its own design session + 1.5 [DEFERRED 2026-10-06 by the user: manual installation is one of + Everest's main features, so the installer comes later. 3.1 is + next.] + [2026-10-07, the user: a manual install works from a live Everest + system or any Linux host; the live system is the preferred, + officially supported way. The user is trying the first disk + install (QEMU) themselves.] + The installer. Undesigned; needs its own design session (SECURITY_DESIGN section 10). Partitioning, a bootloader or an EFI-stub kernel on the ESP, copying the kernel there (a symlink won't do on FAT), a disk image. @@ -72,16 +113,35 @@ The critical path: from "QEMU boots an initramfs assembled by a script" - A restore interface for backups: later. (A backup can already be reinstalled with -lO.) - `gpkg -u` / `syspkg -u` = upgrade from a REMOTE repository; - waits for 3.1, like -f. + waits for 3.1, like -f. [DONE 2026-10-07: REPO_DESIGN + section 6, "As implemented".] - /etc follows the existing config rules regardless. - 2.2 gpkg -f / syspkg -f: MERGE. Spec (2026-10-04): exactly like -l, + 2.2 [DONE 2026-10-07: REPO_DESIGN section 6, "As implemented".] + gpkg -f / syspkg -f: MERGE. Spec (2026-10-04): exactly like -l, except the package comes from a remote repository by name instead of from a local file. Same summary, verification, scope rules and transaction. Depends on 3.1 (remote repositories + signing): until then there is nothing safe to fetch from. - 2.3 Dependencies. PKG_DEPS / PKG_CONFS are parsed and ignored. + 2.3 Dependencies. [DONE 2026-10-05; automatic closure DONE + 2026-10-07 with -f/-u (REPO_DESIGN section 6).] Spec (2026-10-05), all three checks in one pass, no override flag, + every problem reported (one line each) before failing: + - A dependency's PKG_VER is a MINIMUM (installed >= PKG_VER). + Matched by name, any repo. + - A user package's dependency may be met by system scope or the + user's own scope. syspkg -x checks system-scope dependents + only. + - Conflicts: both directions (new lists installed, or installed + lists new), by name; PKG_VER is ignored. + - Missing dependencies REFUSE until 3.1; then automatic closure + (GLACIER_BOOTSTRAP 9e). + - Index records auto vs explicit now: /.glacier-auto in the + store, index line repo::name::version::auto|explicit. + - Messages: + extra::foo requires bar >= 1.2.0, which is not installed. + Removing base::musl breaks extra::foo. + extra::foo conflicts with extra::bar. - install: refuse if a dependency isn't installed (or in the same transaction); - REMOVE: refuse to remove a package others depend on. Today @@ -89,7 +149,7 @@ The critical path: from "QEMU boots an initramfs assembled by a script" the system. Highest-value item in this section; - conflicts: refuse to install a package that conflicts with an installed one. - Check-only vs automatic resolution: GLACIER_BOOTSTRAP decision 9e. + 9e DECIDED 2026-10-05: automatic closure, after 3.1 (see above). 2.4 Queries: list installed packages, show a package's details, find which package owns a file. The index has the data; no CLI shows it. @@ -98,7 +158,9 @@ The critical path: from "QEMU boots an initramfs assembled by a script" deletes it. Keep it (or the last N), plus a command to return to it. Interacts with 2.1's KEEP_OLD. - 2.6 File conflicts between packages: two packages shipping the same + 2.6 [DONE 2026-10-05: refused in every scope, before the prompt and at + commit (deps.c check_collisions).] + File conflicts between packages: two packages shipping the same path — whichever links last silently wins. Detect and refuse. 2.7 glacier.conf parsing. [DONE 2026-10-04 for integers: @@ -121,7 +183,14 @@ The critical path: from "QEMU boots an initramfs assembled by a script" +--------------------------------------------+ 3.1 Remote repositories + signing (authenticity, keyring). The agreed - next major item; separate design pass. Unblocks 2.2 and remote + next major item. Design: REPO_DESIGN.txt (2026-10-06; decisions in + sections 1 and 10, order of work section 9). [IN PROGRESS + 2026-10-06: steps 1 and 2 DONE (gsign; the local base rule, + repos.conf + keyring, gbuild -k, bootstrap carries .sig, Everest + signs with everest-2026) and 3 (grepo; the signed index; + syspkg/gpkg -y refresh with rollback state) DONE; step 4 (-f with + closure, -u, the cache) DONE 2026-10-07; step 5 (glacier- + bootstrap -r) next, then 6 (user repos).] Unblocks remote glacier-bootstrap. Until then, GLACIER_BOOTSTRAP's pinned templates are the safe bridge for remote fetches. @@ -145,6 +214,10 @@ The critical path: from "QEMU boots an initramfs assembled by a script" exists in the tree (only one gbuild, in glacier-ng). Today the target libc is decided by where gbuild runs (~/Projects/everest builds in Alpine for musl). + [2026-10-07, the user: Everest will run under glibc as well as + musl, so profiles are a real requirement, not a nicety. The + libc is picked at install time (Gentoo/Void style), one per + system. Open: how packages are kept apart (section 6).] 4.2 Move ~/Projects/everest's per-package gbuild commands into gworld recipes once 4.1 lands. @@ -164,18 +237,88 @@ The critical path: from "QEMU boots an initramfs assembled by a script" - Images are only ever bootstrap OUTPUT (IMAGE_DESIGN 9a / GLACIER_BOOTSTRAP 9a). - - Template index format (GLACIER_BOOTSTRAP 9b). - - Dependencies: check-only or automatic (GLACIER_BOOTSTRAP 9e). - Where the pinning helper lives (GLACIER_BOOTSTRAP 9f). - 2.1's open questions a-d (upgrade backups). - extra vs community: should they ever behave differently? - - The installer (1.5). + - The installer (1.5), deferred. + - libgcc (1.4): luajit is built with -static-libgcc because Everest + has no libgcc_s. Claude's call, not yet reviewed; the alternative is + a GCC runtime package in base, which later C++/unwinding packages + would need anyway. + - glibc and musl (4.1): DECIDED 2026-10-07 that the user picks the + libc at install time, like Gentoo's profiles / Void's glibc and musl + ISOs; one libc per system. Still open: how packages are kept apart + (Claude recommends both per-libc repos, as Void, AND a libc field in + manifest + index that the library refuses on a mismatch with the + system's recorded libc). +--------------------------------------------+ | 7. Done | +--------------------------------------------+ + [DONE 2026-10-07] -f and -u from repositories (3.1 step 4; 2.1's -u, + 2.2, 2.3's closure): first repo serving a name wins; missing deps + added auto, too-old ones upgraded; downloads in /cache/REPO, + hashed against the signed index, manifest must match the entry, + deleted after commit. tests/fetch_alpine.sh (84 checks, 17 mutants). + + [DONE 2026-10-06] Packages for glacier itself (1.4): zlib, libarchive, + libconfig, luajit, libglacier-ng, glacier-ng in base, doas in extra; + everest-bootscripts 1.1.0 ships /etc/doas.conf. Everest's initramfs + installs all 11 packages; in QEMU, syspkg/gpkg run from the store, + syspkg -x musl is refused (dependents), a wheel user runs doas + gstore -n without a password, then gpkg -l. gpkg loads lib/ from its + realpath. tests/installed_alpine.sh (30 checks) installs glacier in + that layout and runs it from the store. Found on the way: gbuild's + clone warning broke builds from a commit hash (CLAUDE.md bug 28); + syspkg/gstore never checked the ancestors of libglacier-ng's store + dir, which the installed .so's live in (bug 29; both mutations + caught). + [DONE 2026-10-07] glacier-bootstrap configures repositories (1.3): + syspkg -R's -y/-f/-u use TARGET's own repos.conf and keys (each + file's real path must stay inside TARGET); bootstrap runs + syspkg -R T -y when T's repos.conf gives a url (exit 7 on failure, + a warning when none does). Everest: new base package everest-repos + 1.0.0 (/etc/glacier/repos.conf + everest-2026.pub), moved out of + everest-bootscripts (now 1.3.0). bootstrap_alpine.sh 100 checks + (6 mutants, all caught); repo/fetch suites updated; full regression + green; Everest rebuilt and booted. + [DONE 2026-10-06] glacier-bootstrap, local mode (1.3): template + (NAME=, PKG=name ver [sha256:pin]; strict), verified private copies + handed to syspkg -R in one transaction, template-level dependency / + conflict / name checks, -n, -t. tests/bootstrap_alpine.sh (77 + checks, 11 mutations caught). ~/Projects/everest's initramfs is now + a glacier-installed system (templates/minimal.template), booted in + QEMU: /glacier/sys, merged-/usr links into the store, /etc copied, + a non-root user runs busybox. + + [DONE 2026-10-05] syspkg -R/--root DIR + --strict (1.2) and file + collisions (2.6): gl_check_sysroot (root-owned, non-symlink, not + group/world-writable, every ancestor), ctx->strict (a would-be link + refusal fails the transaction before the prompt and at commit; -O + still displaces), two packages shipping one path refused in every + scope. tests/root_alpine.sh (50 checks, 7 mutations caught). gpkg + silently ignored unknown flags (CLAUDE.md bug 27). + [DONE 2026-10-05] sysroot in the library (1.1): *_at contexts and + gl_init_sys_at, install paths in the context with runtime translation + at link/prune/displace, /usr and /etc under the sysroot, user scope + refuses one. tests/sysroot_alpine.sh (33 checks, 8 mutations caught). + Found on the way: live-context removal deleted foreign files + (CLAUDE.md bug 26). + [DONE 2026-10-05] Dependencies and conflicts (2.3): src/deps.c + (gl_check_plan before the prompt; gl_check_txn enforced by + gl_commit_transaction; live-context installs/removals checked inside + gl_install_pkg/gl_remove_pkg), index field 4 auto|explicit + + .glacier-auto + gl_set_pkg_auto, gl_read_pkg_manifest split out of + gl_install_pkg. tests/deps_alpine.sh (41 checks, 8 mutations caught). + Found on the way: the live index was rewritten through a seeded + hardlink (CLAUDE.md bug 25), tests/test_index_abort.lua. + [DONE 2026-10-05] System scope world-readable (found while specifying + 2.3): /glacier/sys was root 0700 throughout, so non-root users could + run nothing syspkg installed. Dirs 0755 / index 0644, and created + modes no longer depend on the umask. tests/sys_perms_alpine.sh. + [DONE 2026-10-04] Version replacement + backups (2.1): -lO replaces, KEEP_OLD backups, gl_backup_pkg / gl_prune_backups / gl_conf_get_int, the manifest saved in the store (.glacier-manifest), add_to_archive diff --git a/SECURITY_DESIGN.txt b/SECURITY_DESIGN.txt index 7909ba0..a56ef01 100644 --- a/SECURITY_DESIGN.txt +++ b/SECURITY_DESIGN.txt @@ -27,6 +27,9 @@ it. This phase is INTEGRITY ONLY. Authenticity (signing, a trusted keyring) is a deliberate future phase, tied to remote-repo support — not being designed in detail now, but a few choices below are made to stay cheap to extend into later. +[2026-10-06: that phase is REPO_DESIGN.txt. Its first part is in: a +base package installed with syspkg -l needs a signed sidecar +(REPO_DESIGN section 7).] Everest is a security-focused distro (this is also *why* per-user package isolation exists in the first place). Two sides of the CIA @@ -209,6 +212,17 @@ refusing otherwise. The actual privilege boundary is enforced by doas — a hardened, audited, purpose-built tool for exactly this — not reimplemented inside syspkg itself. +LATER AMENDMENT (loading, as built): syspkg and gstore run under +`luajit -E`, and pin package.path and every ffi.load to their own +realpath's lib/. Before loading anything as root, each file they load +(every .lua, every .so, including those pulled in through $ORIGIN) and +lib/ itself must be root-owned and not group/world-writable, and so +must every ancestor of the script's realpath AND of each loaded file's +realpath. The second set matters once installed: lib/glacier is a +symlink to /usr/lib/glacier, whose entries link into libglacier-ng's +store directory, a tree the script's own ancestors never reach +(2026-10-06, CLAUDE.md bug 29). + +--------------------------------------------+ | 7. Shared code: gpkg_common.lua | diff --git a/SECURITY_REVIEW_2026-07-16.md b/SECURITY_REVIEW_2026-07-16.md index dec0589..d21ba06 100644 --- a/SECURITY_REVIEW_2026-07-16.md +++ b/SECURITY_REVIEW_2026-07-16.md @@ -27,6 +27,12 @@ root, or shadows real `/usr` binaries." * **Severity:** High * **Category:** `authorization-bypass` * **Confidence:** High +* **Status (2026-10-06):** closed for local installs by recommendation + (b): `syspkg -l` of a package whose manifest says base requires its + sidecar signed by a key `/etc/glacier/repos.conf` lists for base + (REPO_DESIGN.txt section 7); user scope refuses base outright. Remote + packages get (a) with the signed index (REPO_DESIGN section 4). Still + open for a third-party caller of `gl_install_pkg` (ROADMAP 3.2). **Description:** System-scope symlink placement into `/usr` is gated only by a package's manifest self-declaring `PKG_REPO="base"`. There is no diff --git a/SECURITY_REVIEW_2026-10-06.md b/SECURITY_REVIEW_2026-10-06.md new file mode 100644 index 0000000..2911d07 --- /dev/null +++ b/SECURITY_REVIEW_2026-10-06.md @@ -0,0 +1,62 @@ +# Security review, 2026-10-06 + +Scope: every active source file in libglacier-ng (`src/*.c`, `src/*.h`, +not the vendored Monocypher, not the unbuilt early prototypes) and +glacier-ng (`gpkg`, `syspkg`, `gstore`, `glacier-bootstrap`, `gbuild`, +`gworld`, `lib/*.lua`, `tests/mkpkg`, the Dockerfiles). Read line by line; +findings confirmed by running them in Docker where possible; each fix +has a regression test, mutation-checked unless noted. + +Tests: `glacier-ng/tests/audit_alpine.sh` (shell, `glacier-alpine_root`) +and `glacier-ng/tests/test_abort_unlocked.lua` (FFI suite). + +## Findings and fixes + +| # | Severity | Where | Finding | Fix | Test | +|---|---|---|---|---|---| +| 1 | **High** | `gl_install_pkg` | A package could ship top-level `files/.glacier-*` entries, glacier's own store files. As a symlink, `.glacier-fresh` made syspkg `open(O_CREAT\|O_TRUNC)` its target as root: an unsigned extra package truncated `/root/victim` to 0 bytes (reproduced). `.glacier-overwrite` granted the package `-O` without the user asking; `.glacier-manifest` rewrote its saved manifest. | Top-level names starting `.glacier-` are refused (`GL_MARKER_PREFIX`, common.h); the marker writes use `O_NOFOLLOW`. | audit_alpine: reserved names (mutants c3, c4 caught) | +| 2 | Medium | `gl_rmdir_recursive` (common.h) | `lstat` then `opendir` by path: root deleting a tree a user can write to (`gstore -x UID`) could be steered, by swapping a directory for a symlink between the two calls, into emptying the symlink's target. | fd-relative walk: `openat(O_DIRECTORY\|O_NOFOLLOW)`, `fdopendir`, `unlinkat`; the top path must not be a symlink. | audit_alpine: `gstore -x` race, 40 runs (mutant c1 caught 34-38/40) | +| 3 | Medium | `add_to_archive` (istoreutils.c) | Same class: `lstat` then `open`/`opendir` by path, no `O_NOFOLLOW`. Root backing up a user's store (`gstore -b`) could be steered into archiving root-only files. | fd-relative (`fstatat`/`openat(O_NOFOLLOW\|O_NONBLOCK)`/`readlinkat`); the opened descriptor's type is what gets archived. | **No failing-on-old test**: the window is too narrow for a shell race to hit (mutant c2 survived). Verified by review only. | +| 4 | Medium | `gl_gpm2gpkg` (pkg.c) | Manifests come from untrusted archives; libconfig honours `@include "FILE"`, so a manifest could make syspkg read any file as root. | A manifest with a line starting `@include` is refused (read into memory first, 1 MiB cap, no NUL). | audit_alpine (mutant c5 caught) | +| 5 | Low | `gl_gpm2gpkg` | A dependency/conflict entry without `PKG_NAME` ended the list early (later entries silently ignored, and leaked); names were not checked. | Every entry needs a `gl_valid_pkg_ident` name, else the manifest is refused. | audit_alpine (mutants c6, c7 caught) | +| 6 | Low | `gpkg_common.lua` | `read_manifest_fields` pattern-matched the manifest (via `tar` as root) and could disagree with the library: a decoy `// PKG_REPO = "community";` line hid that a package replaced an installed one, skipping the `-O` rule and the KEEP_OLD backup (two versions in the store). The tar extraction itself was not exploitable (GNU and busybox tar strip `..`; checked). | Replaced by `manifest_of`, a wrapper around `gl_read_pkg_manifest`; an unreadable manifest is refused before the prompt. | audit_alpine (old file as mutant l1, caught) | +| 7 | Low | `gstore`, `gpkg` | Text concatenated into `lg_printf` format strings: gstore's `argv[0]` and UID (as root), gpkg `-u`'s operand. | Passed as `%s` arguments; the `-u`/`-f`/`-s` stubs now refuse in one line. | audit_alpine (mutants l2, l3 caught) | +| 8 | Low | `gstore` | A UID above uid_t's range wrapped around in C to another UID. | Refused (`0..4294967294`). | audit_alpine (mutant l4 caught) | +| 9 | Low | `gl_abort_transaction` | Wiped the stage even for a context that never got the lock, i.e. another process's running transaction. | Only the lock holder's abort removes the stage (`GL_TXN_ERR_NO_TXN` otherwise). | test_abort_unlocked.lua (mutant f8 caught) | +| 10 | Low | `gpkg_common.lua` | Lua numbers passed to `lg_printf("%d")` (a double into varargs: undefined behaviour). | `%s` + `tostring`. | none (not observable) | +| 11 | Low | `gl_parse_semver` | `sscanf("%d")` on an untrusted, oversized number is undefined behaviour. | `strtol`, clamped to `0..INT_MAX`. | none (not observable) | +| 12 | Hardening | `is_glacier_symlink` | A plain prefix match: `/glacier/sys/store` also matched `/glacier/sys/store2/...` (only root can create such paths). | The prefix must be followed by `/` or end the target. | none | +| 13 | Bug | `join_with_cwd` (pkg.c) | `strlen(cwd + strlen(path) + 2)`: an out-of-bounds read. Unused. | Removed (see below). | n/a | +| 14 | Hardening | `lg_printf` | No format checking for C callers. | `__attribute__((format(printf, 2, 3)))`; the build is clean under `-Wformat=2 -Werror`. | the build | + +Reviewed and found sound: signature code (`sign.c`: strict signify +parsing, secrets wiped, `O_NOFOLLOW|O_EXCL` writes), the index parser, +extraction's tar-slip defences (`is_safe_rel_path`, deferred symlinks, +`crosses_symlink`, `O_NOFOLLOW`), the sysroot trust check, syspkg's and +gstore's hardened loading, the private-copy verification, shell quoting +in every `os.execute`/`io.popen`, `/etc` config handling, `-O` displace +and restore. + +## Removed as unused or redundant + +- libglacier-ng: the `dag`, `runtime` and `make_conf` modules (nothing + linked them); `gl_pkgver`, `gl_mkdir`, `gl_spawn_pkg`, `gl_cat_dir`, + `gl_cat_dir_and_ver`, `gl_return_dir`, `gl_return_dir_and_ver`, + `gl_free_path`, `gl_free_dir`, `gl_show_pkg_props`, `join_with_cwd`, + `empty`/`null_dep`/`null_conf`, `dir_t`; `gl_parse_index`, + `gl_free_index` (and `struct gindex`), `gl_find_pkg_repo` (duplicate of + `gl_find_pkg_repos`), `gl_sys_istore_exists`, `gl_restore_istore` + (also unsafe: followed symlinks when restoring); `lg_printf_no_newline`, + `lg_syslog`, `lg_log` (its declaration and definition disagreed), + `p2s`; `GL_CONF_PATH`; the broken `install_test_dir` Makefile target. + The marker names, defined twice (deps.c, istoreutils.c), now live once + in common.h; log.c includes log.h instead of redefining its enum. +- glacier-ng: `M.read_manifest_fields`, `M.extract_pkg_info`, + `M.summary`; `install_with_reinstall` is module-local; the matching + `glacier_cdef.lua` declarations. + +## Open + +- Finding 3 has no test that fails on the old code (see the table). +- Extracted entries keep setuid/setgid bits (known gap, unchanged). +- No size limits on extraction (a decompression bomb fills the disk). diff --git a/build/bin/gsha256sum b/build/bin/gsha256sum deleted file mode 100755 index 8c74a0fc72d8bfe025b45c9c802e5d52aecc3191..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16784 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 diff --git a/build/include/glacier/log.h b/build/include/glacier/log.h deleted file mode 100644 index 0521ae1..0000000 --- a/build/include/glacier/log.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef LOG_H_ -#define LOG_H_ - -#define _POSIX_C_SOURCE 200809L -#define _DEFAULT_SOURCE -#define _XOPEN_SOURCE 700 - -#include -#include -#include -#include -#include - -#include "color.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum -{ - LG_INFO, - LG_WARN, - LG_ERR, - LG_SUCCESS -} -loglevel_t; - -int supports_utf8(void); -const char *p2s(int priority); - -void lg_printf(loglevel_t loglevel, const char *fmt, ...); -void lg_printf_no_newline(loglevel_t loglevel, const char *fmt, ...); -void lg_syslog(int priority, const char *fmt, ...); -void lg_log(bool use_syslog, FILE *logfile, int priority, const char *fmt, ...); - -#ifdef __cplusplus -} -#endif - -#endif /* LOG_H_ */ diff --git a/build/lib/shared/libglacier_dag.so b/build/lib/shared/libglacier_dag.so deleted file mode 100755 index 0f61c7da79f3e20a5118f1a9a0e6d838f3768fb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15952 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 diff --git a/build/lib/shared/libglacier_istoreutils.so b/build/lib/shared/libglacier_istoreutils.so deleted file mode 100755 index 480cadef888325d0780c000d770b5d494bb63886..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62312 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 iiK;u*Gmi-qtrpqh< diff --git a/build/lib/shared/libglacier_pkg.so b/build/lib/shared/libglacier_pkg.so deleted file mode 100755 index 09c019f704a605528f3394b01b8c7f83fec08fa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21768 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 HVAcOmreRPH diff --git a/build/lib/shared/libglacier_transaction.so b/build/lib/shared/libglacier_transaction.so deleted file mode 100755 index f367968b230ab8adbc1b2e19856adbcffb679d9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20936 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 diff --git a/build/lib/shared/libglacier_verify.so b/build/lib/shared/libglacier_verify.so deleted file mode 100755 index 9ba87ef82564e93ea3f252f0172286e158935b5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15968 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 oi}{DXYl*fes7ogLkcMR#>(`AJ~jA@@n-~+IY3Q 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 diff --git a/build/lib/tmp/make_conf.o b/build/lib/tmp/make_conf.o deleted file mode 100644 index 03ea14212f62cc655f8e3ddd784e791cb3ff3fbe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3680 zcmbVPZ)_Ar6ra5-v?AVCKy89a&$yZraMNp)0I7IK3rx+G5CIcWavayamh}EKdwYfo zMo6!O&2rKYCXna{6VWu0C?upI21%2H*6_CLnVdUQw+`DE{t32sr?P}pv6vqnXWFG%Wj2|NnLF&u%#uH z4oxc)VTI7u*R4R8vqDqH8r_AyqxSVzcSK$*sm+~)!1}j7RGSGs@Z)qHAwAT2_Nf+F zjM}HQQ2=Q1Ib7O}YS(FWcwA+{sQsRH1_|~FZ5+m^eO&v3jVH7zHlEh5v+=BU14i7G zoS(BqJ4XPg#k1P`l?b?c5$ae$zx=ygruGGOjKKacwm)f2oO>SF)c%>LRDz7GQxh)q z5weSWp=zC4)ATXsJ4x*l<3@{pN|gI`*^_K1`Et3ej`_Ro`-Sm*aApsLPO`d04%J8P z-@1k-6__qSR+k`Zw3t)cwfs$WtX~Nieo_mofbZr}+@}S>fjX_!z6bRTt7Bi?t$Vp4 zKR>+xuULLDo@Mw@3mqDV1($t1%F%bw^eT14qu{d;aj z)c%3mmevr3eKj%?bPLY>Y*u)fMaK@XhAPAgGSGW*`l9f*P%&6tqnf9))mnn{w`0V4yAgYDrK_6#gm zo&Vg9(xJQ zp(t;>M3g;^%F>1P4I@y&6@Wd?W(Xmm+2LHm0sH(XovtsCcILb zxP0a6wE-E!{Ep+IAdlh?_~1}W@bK%%opQB#f29huvk!p^HL3t?uiWyJ`4wC!i$L*7 zILisJrx^jOK3A4Aa~}B26+ExTxe&9~*1*>Tz6kOh;XbU!pkv*ut+TxbzOx42TLaJ3 z!1FcmH*4U98n{yf$9-1Y*U=jIXbqgCgc=tipAP9muUI2lA zj35a17j~<_Pa+6{e7v6mU&LD-@r4rJA>nu@1V1a`a@@TV9+3E#BwWtFo?ifrt4jRG zBwSv1g0SA`AlkezmXawdLo-n8yRrRWGzF7&&o5b zd%4}4$?F3qe6>sqxXj<^izUr?+Nfmpzm*_(VoQ17#9_ctHj4vrO%mjz7?y&9^$81r zk0W?kASZdhV$Y*0&aDY2LGZnfxF-x5@d8K&;mf%O{Ni^M*Ywi=t$zr7am_Y(WD))% zUoj`*ixIyYsC(;oj3*?%UtWJuBk1NG!d@Zwzrp&s<&sZeKj-%DPX^r8cknfbln%)IyJZC=dH--<{Q zaFO69bR&)eJcxFE!*>l(;24;#pYjiiU$wmoO#l@}#$WnbOZPA% znKw71=aUGS8<|Ah-0G(_bK{%2RY}CmjcB3+WfHQvd0qd*dyHd`&HIUX-n=~OS7@HY zp2^2gcCbYTi?nuRb9%S==>wf&_luu5;dr@;V#Ust9hX}zSFaV|z)IC#aRL~!v$NOK zWOiZclB!Q?X-!wtsq|!ON>9;FQRq5URN6VxAOcdGFn)JZC?3T%66x7pkk(?-*zkb7 zjpVb4k&;}bpNtZ_GpNXOLko(G;T6;ygrhX+XGV#=j?5_WR$oRjB8!7c9FZAC&JNM) zp+ftL!tecFguc$MyQ@Jpe=c1Ry_g^M!%`~x>7Na; zY1CeP;}9I&X3Z;Cac-c^&!*@{hf2{ZH!Qba_n_!_&2pX)Mzox@a>HwYoy$3m23S_Z zvvVs}uC!tm>~aO$_`*+4Gd+U3w;#yqVA-F)h-VodLE9(r9|<_s;&sXlCyeII@wWng zRKQh+Q+-k{26H&h?-R7fdeyU+(RyxROWbpvimiFhng?3m_H59W@gg+0P65$%O$YoA zs#T|klv>?$G<@P${flC8vDVa9-TJEIdh3`<1)Y*raP6wYclLi)#dLbJbjP9?EO;k^ z=!f6T7-Dp{2C67=8V<^KV1V8^^`RM%!~+8t&=St*3qzbHT%M(sxp i=Tpt_>=A-CJJ2Epg0Qk31 diff --git a/build/lib/tmp/sha256.o b/build/lib/tmp/sha256.o deleted file mode 100644 index e2beb7ac4eafb02885d54094497f0645b8dc86bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5744 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 #include +#include #include #include #include @@ -13,13 +14,6 @@ #include #include -typedef struct -{ - const char *path; - mode_t mode; -} -dir_t; - /* * On-disk layout. Each scope keeps everything it ever creates, renames * or locks under ONE base directory it owns: @@ -35,6 +29,21 @@ dir_t; * an unprivileged user run their own transactions at all. */ #define GL_USR_ROOT "/glacier/usr" + +/* In a package's version dir: "installed as a dependency, not by name". + * Dot-prefixed like the other store markers, so seeding carries it and + * linking skips it; the index's 4th field is derived from it. */ +#define GL_AUTO_MARKER ".glacier-auto" +/* The other files glacier keeps in a version dir (see gl_install_pkg): + * the package's saved manifest, and the one-shot markers for the next + * link pass (this transaction installed it; it may displace, -O). A + * package may not ship any top-level name starting with GL_MARKER_PREFIX: + * these are glacier's, and one shipped as a symlink would redirect + * glacier's own writes. */ +#define GL_MARKER_PREFIX ".glacier-" +#define GL_MANIFEST_COPY ".glacier-manifest" +#define GL_FRESH_MARKER ".glacier-fresh" +#define GL_OVERWRITE_MARKER ".glacier-overwrite" #define GL_SYS_ROOT "/glacier/sys" static inline void @@ -78,6 +87,98 @@ gl_usr_base_check(uid_t uid) return GL_USR_BASE_OK; } +/* An install root (gl_context_t.sysroot) in canonical form: NULL, "" and + * "/" mean the running system and give ""; anything else must be an + * absolute path with no "." or ".." components, and loses its trailing + * slashes. Room is left for the paths built under it. Returns -1 if + * it's refused. */ +static inline int +gl_normalize_sysroot(const char *in, char *out, size_t outsz) +{ + out[0] = '\0'; + if (!in || in[0] == '\0') { return 0; } + if (in[0] != '/') { return -1; } + + size_t len = strlen(in); + while (len > 0 && in[len - 1] == '/') { len--; } + if (len == 0) { return 0; } /* "/" or "//" */ + if (len + 256 > outsz || len + 256 > PATH_MAX) { return -1; } + + for (size_t i = 0; i < len; ) { + size_t j = i + 1; /* in[i] == '/' */ + while (j < len && in[j] != '/') { j++; } + size_t clen = j - i - 1; + const char *comp = in + i + 1; + if (clen == 0 || (clen == 1 && comp[0] == '.') || + (clen == 2 && comp[0] == '.' && comp[1] == '.')) { + return -1; + } + i = j; + } + + memcpy(out, in, len); + out[len] = '\0'; + return 0; +} + +/* Is a (normalized, non-empty) install root safe for root to write into? + * It and every directory above it must be a real directory (not a + * symlink), owned by root, and not group/world-writable: otherwise a + * user could swap part of the tree for a symlink to the host's /usr + * between two syscalls (IMAGE_DESIGN section 4). Returns 0 if trusted; + * otherwise -1, with the offending path in `bad` and `why` set to + * "a symlink", "not a directory", or "not root-owned, or is + * group/world-writable". */ +static inline int +gl_sysroot_check_one(const char *path, char *bad, size_t badsz, const char **why) +{ + struct stat st; + if (lstat(path, &st) != 0) { + *why = "missing"; + } else if (S_ISLNK(st.st_mode)) { + *why = "a symlink"; + } else if (!S_ISDIR(st.st_mode)) { + *why = "not a directory"; + } else if (st.st_uid != 0 || (st.st_mode & 022) != 0) { + *why = "not root-owned, or is group/world-writable"; + } else { + return 0; + } + snprintf(bad, badsz, "%s", path); + return -1; +} + +static inline int +gl_sysroot_trusted(const char *root, char *bad, size_t badsz, const char **why) +{ + if (gl_sysroot_check_one("/", bad, badsz, why) != 0) { return -1; } + + char prefix[PATH_MAX]; + snprintf(prefix, sizeof(prefix), "%s", root); + /* each ancestor, shortest first, then root itself */ + for (char *p = prefix + 1; *p; p++) { + if (*p != '/') { continue; } + *p = '\0'; + int rc = gl_sysroot_check_one(prefix, bad, badsz, why); + *p = '/'; + if (rc != 0) { return -1; } + } + return gl_sysroot_check_one(prefix, bad, badsz, why); +} + +/* mkdir -p. Every directory this creates gets exactly `mode`, whatever + * the caller's umask: a system store made under umask 077 would otherwise + * be unreachable for every user but root. Directories that already exist + * are left alone. */ +static inline int +gl_mkdir_exact(const char *path, mode_t mode) +{ + if (mkdir(path, mode) == 0) { + return chmod(path, mode); + } + return errno == EEXIST ? 0 : -1; +} + static inline int gl_mkdirp(const char *path, mode_t mode) { @@ -88,52 +189,58 @@ gl_mkdirp(const char *path, mode_t mode) for (char *p = tmp + 1; *p; p++) { if (*p == '/') { *p = '\0'; - if (mkdir(tmp, mode) == -1 && errno != EEXIST) { + if (gl_mkdir_exact(tmp, mode) != 0) { return -1; } *p = '/'; } } - if (mkdir(tmp, mode) == -1 && errno != EEXIST) { - return -1; - } - - return 0; + return gl_mkdir_exact(tmp, mode); } +/* Empties the directory open at `fd` (and closes it). Every step is + * relative to a descriptor opened with O_NOFOLLOW, so nothing is ever + * resolved through a symlink: an entry swapped for a symlink mid-walk + * (root deleting a tree a user can write to, gstore -x) is unlinked + * itself, never followed into its target. */ +static inline void +gl_empty_dir_fd(int fd) +{ + DIR *dir = fdopendir(fd); + if (!dir) { + close(fd); + return; + } + + struct dirent *ent; + while ((ent = readdir(dir))) { + /* Only . and ..: dotfiles are content too (a package's own, + * the store's .displaced tree). */ + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) { + continue; + } + int sub = openat(dirfd(dir), ent->d_name, + O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (sub >= 0) { + gl_empty_dir_fd(sub); + unlinkat(dirfd(dir), ent->d_name, AT_REMOVEDIR); + } else { + unlinkat(dirfd(dir), ent->d_name, 0); + } + } + closedir(dir); +} + +/* rm -r of a directory. `path` itself must be a directory, not a + * symlink to one (refused, -1). */ static inline int gl_rmdir_recursive(const char *path) { - DIR *dir = opendir(path); - if (!dir) { return -1; } - - struct dirent *ent; - while ((ent = readdir(dir))) { - /* Only . and .. — skipping every dotfile left any directory - * containing one (a package's own dotfiles, the store's - * .displaced tree) impossible to rmdir, so the old/ and stage - * wipes silently failed and the next commit hit ENOTEMPTY. */ - if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) { - continue; - } - - char full[PATH_MAX]; - snprintf(full, sizeof(full), "%s/%s", path, ent->d_name); - - struct stat st; - if (lstat(full, &st) != 0) { continue; } - - if (S_ISDIR(st.st_mode)) { - gl_rmdir_recursive(full); - } - else { - unlink(full); - } - } - - closedir(dir); - return rmdir(path); + int fd = open(path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (fd < 0) { return -1; } + gl_empty_dir_fd(fd); + return rmdir(path); } diff --git a/src/dag.c b/src/dag.c deleted file mode 100644 index 7957cb1..0000000 --- a/src/dag.c +++ /dev/null @@ -1,132 +0,0 @@ -#define _POSIX_C_SOURCE 200809L - -#include -#include - -#include "dag.h" - -graph_t -*gl_graph_create(void) -{ - return calloc(1, sizeof(graph_t)); -} - -void -gl_graph_free(graph_t *g) -{ - if (!g) { return; } - - for (size_t i = 0; i < g->node_count; i++) { - free(g->nodes[i]->edges); - free(g->nodes[i]); - } - - free(g->nodes); - free(g); -} - -graph_node_t -*gl_graph_add_node(graph_t *g, void *data) -{ - if (!g) { return NULL; } - - graph_node_t *n = calloc(1, sizeof(graph_node_t)); - if (!n) { return NULL; } - n->data = data; - - graph_node_t **tmp = realloc(g->nodes, sizeof(*g->nodes) * (g->node_count + 1)); - if (!tmp) { - free(n); - return NULL; - } - - g->nodes = tmp; - g->nodes[g->node_count++] = n; - return n; -} - -void -gl_graph_add_edge(graph_t *g, graph_node_t *from, graph_node_t *to) -{ - if (!g || !from || !to) { return; } - graph_node_t **tmp = realloc(from->edges, sizeof(*from->edges) * (from->edge_count + 1)); - if (!tmp) { return; } - from->edges = tmp; - from->edges[from->edge_count++] = to; -} - -static bool -dfs_cycle(graph_node_t *n) -{ - n->visited = true; - n->in_stack = true; - - for (size_t i = 0; i < n->edge_count; i++) { - graph_node_t *child = n->edges[i]; - if (!child->visited && dfs_cycle(child)) { - return true; - } - else if (child->in_stack) { - return true; - } - } - - n->in_stack = false; - return false; -} - -bool -gl_graph_has_cycle(graph_t *g) -{ - if (!g) { return false; } - for (size_t i = 0; i < g->node_count; i++) { - g->nodes[i]->visited = false; - g->nodes[i]->in_stack = false; - } - for (size_t i = 0; i < g->node_count; i++) { - if (!g->nodes[i]->visited && dfs_cycle(g->nodes[i])) { - return true; - } - } - return false; -} - -static void -dfs_toposort(graph_node_t *n, void **result, size_t *index) -{ - n->visited = true; - for (size_t i = 0; i < n->edge_count; i++) { - if (!n->edges[i]->visited) { - dfs_toposort(n->edges[i], result, index); - } - } - - result[(*index)++] = n->data; -} - -void -**gl_graph_toposort(graph_t *g, size_t *out_count) -{ - if (!g || !out_count) { return NULL; } - if (gl_graph_has_cycle(g)) { - fprintf(stderr, "Error: dependency cycle detected\n"); - return NULL; - } - - *out_count = g->node_count; - void **order = calloc(g->node_count, sizeof(void *)); - if (!order) { return NULL; } - - for (size_t i = 0; i < g->node_count; i++) { - g->nodes[i]->visited = false; - } - - size_t index = 0; - for (size_t i = 0; i < g->node_count; i++) { - if (!g->nodes[i]->visited) { - dfs_toposort(g->nodes[i], order, (size_t *)&index); - } - } - - return order; -} diff --git a/src/dag.h b/src/dag.h deleted file mode 100644 index 79f79e3..0000000 --- a/src/dag.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef DAG_H_ -#define DAG_H_ - -#define _POSIX_C_SOURCE 200809L - -#include -#include - -typedef struct graph_node graph_node_t; -typedef struct graph graph_t; - -struct graph_node { - void *data; - graph_node_t **edges; - size_t edge_count; - bool visited; - bool in_stack; -}; - -struct graph { - graph_node_t **nodes; - size_t node_count; -}; - -graph_t *graph_create(void); -void graph_free(graph_t *g); - -graph_node_t *graph_add_node(graph_t *g, void *data); -void graph_add_edge(graph_t *g, graph_node_t *from, graph_node_t *to); - -bool graph_has_cycle(graph_t *g); -void **graph_toposort(graph_t *g, size_t *out_count); - -#endif diff --git a/src/deps.c b/src/deps.c new file mode 100644 index 0000000..e04814e --- /dev/null +++ b/src/deps.c @@ -0,0 +1,727 @@ +#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 700 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "deps.h" +#include "istoreutils.h" +#include "log.h" +#include "pkg.h" + +/* Where one entry of a package lands once linked: its RUNTIME path (what + * a message names and what two packages are compared by). */ +struct dest { + char *path; + bool dir; +}; + +/* One installed (or about to be installed) package. repo/name/dirname + * come from the store layout (store///-), or from + * the manifest for a package that isn't installed yet. deps/confs come + * from the manifest; a version dir without one (installed before + * manifests were saved) has none. */ +struct rec { + char repo[NAME_MAX + 1]; + char name[NAME_MAX + 1]; + char dirname[NAME_MAX + 1]; + semver_t ver; + gl_pkg_t pkg; + bool touched; /* added or replaced by this transaction */ + bool gone; /* removed or replaced: not in the result */ + + /* For the file checks: where its files come from (a version dir in + * a store, or a .gpkg not installed yet), whether its link pass is + * this transaction's (fresh: it reports refusals), whether that + * pass may displace foreign files (-O), and, once computed, where + * every entry lands. */ + char src[PATH_MAX]; + bool from_archive; + bool fresh; + bool overwrite; + struct dest *dests; + size_t ndests; + bool dests_loaded; +}; + +/* What the file checks need to know about the transaction's context. */ +struct env { + gl_ctx_scope_t scope; + uid_t uid; + const char *sysroot; + int strict; + char store_rt[PATH_MAX]; /* live store, runtime path: link targets */ + char store_at[PATH_MAX]; /* the store being checked, install path */ +}; + +struct set { + struct rec *v; + size_t n; +}; + +/* A package the transaction removes outright (not replaced). */ +struct removed { + char repo[NAME_MAX + 1]; + char name[NAME_MAX + 1]; +}; + +static void +set_free(struct set *s) +{ + for (size_t i = 0; i < s->n; i++) { + gl_free_pkg(&s->v[i].pkg); + gl_free_semver(&s->v[i].ver); + for (size_t k = 0; k < s->v[i].ndests; k++) { free(s->v[i].dests[k].path); } + free(s->v[i].dests); + } + free(s->v); + s->v = NULL; + s->n = 0; +} + +static struct rec * +set_add(struct set *s) +{ + struct rec *grown = realloc(s->v, (s->n + 1) * sizeof(*s->v)); + if (!grown) { return NULL; } + s->v = grown; + memset(&s->v[s->n], 0, sizeof(*s->v)); + return &s->v[s->n++]; +} + +static int +semver_cmp(const semver_t *a, const semver_t *b) +{ + if (a->maj != b->maj) { return a->maj < b->maj ? -1 : 1; } + if (a->min != b->min) { return a->min < b->min ? -1 : 1; } + if (a->pat != b->pat) { return a->pat < b->pat ? -1 : 1; } + return 0; +} + +/* Every installed package in `store`. A missing store is empty (nothing + * installed); any other failure to read it is an error, unless `lenient` + * (the system store as seen from user scope: unreadable means "nothing + * there to depend on", and the check then says what's missing). */ +static int +load_store(const char *store, struct set *s, bool lenient) +{ + DIR *sd = opendir(store); + if (!sd) { + return (errno == ENOENT || lenient) ? 0 : -1; + } + + int rc = 0; + struct dirent *re; + while (rc == 0 && (re = readdir(sd))) { + if (re->d_name[0] == '.') { continue; } + + char repo_path[PATH_MAX]; + snprintf(repo_path, sizeof(repo_path), "%s/%s", store, re->d_name); + DIR *rd = opendir(repo_path); + if (!rd) { continue; } /* not a directory */ + + struct dirent *ne; + while (rc == 0 && (ne = readdir(rd))) { + if (ne->d_name[0] == '.') { continue; } + + char name_path[PATH_MAX]; + snprintf(name_path, sizeof(name_path), "%s/%s", repo_path, ne->d_name); + DIR *nd = opendir(name_path); + if (!nd) { continue; } + + struct dirent *ve; + while ((ve = readdir(nd))) { + if (ve->d_name[0] == '.') { continue; } + + struct rec *r = set_add(s); + if (!r) { rc = -1; break; } + snprintf(r->repo, sizeof(r->repo), "%s", re->d_name); + snprintf(r->name, sizeof(r->name), "%s", ne->d_name); + snprintf(r->dirname, sizeof(r->dirname), "%s", ve->d_name); + snprintf(r->src, sizeof(r->src), "%s/%s", name_path, ve->d_name); + + char manifest[PATH_MAX]; + snprintf(manifest, sizeof(manifest), "%s/%s/" GL_MANIFEST_COPY, + name_path, ve->d_name); + if (gl_gpm2gpkg(manifest, &r->pkg) != 0) { + gl_free_pkg(&r->pkg); + memset(&r->pkg, 0, sizeof(r->pkg)); + } + + /* The version is the dir name minus "-". */ + size_t prefix = strlen(ne->d_name) + 1; + r->ver = gl_parse_semver(strlen(ve->d_name) > prefix + ? ve->d_name + prefix : NULL); + break; /* one version per package */ + } + closedir(nd); + } + closedir(rd); + } + closedir(sd); + return rc; +} + +static const struct rec * +find_live(const struct set *s, const char *repo, const char *name) +{ + for (size_t i = 0; i < s->n; i++) { + if (!s->v[i].gone && strcmp(s->v[i].repo, repo) == 0 && + strcmp(s->v[i].name, name) == 0) { + return &s->v[i]; + } + } + return NULL; +} + +/* Is there a package named `name` (any repo) in the result or, from user + * scope, the system? With `min`, it must also be at least that version. */ +static bool +provided(const struct set *res, const struct set *sys, const char *name, + const semver_t *min) +{ + const struct set *sets[] = { res, sys }; + for (int k = 0; k < 2; k++) { + for (size_t i = 0; i < sets[k]->n; i++) { + const struct rec *r = &sets[k]->v[i]; + if (r->gone || strcmp(r->name, name) != 0) { continue; } + if (!min || semver_cmp(&r->ver, min) >= 0) { return true; } + } + } + return false; +} + +static bool +lists_conflict(const struct rec *r, const char *name) +{ + if (!r->pkg.pkg_conflicts) { return false; } + for (const conf_t *c = r->pkg.pkg_conflicts; c->conf_name; c++) { + if (strcmp(c->conf_name, name) == 0) { return true; } + } + return false; +} + +/* Does the transaction touch packages named `name`: add or replace one, + * or remove one outright? */ +static bool +name_touched(const struct set *res, const struct removed *rm, size_t nrm, + const char *name) +{ + for (size_t i = 0; i < res->n; i++) { + if (res->v[i].touched && strcmp(res->v[i].name, name) == 0) { return true; } + } + for (size_t i = 0; i < nrm; i++) { + if (strcmp(rm[i].name, name) == 0) { return true; } + } + return false; +} + +static void +fmt_ver(const semver_t *v, char *out, size_t outsz) +{ + snprintf(out, outsz, "%d.%d.%d", v->maj, v->min, v->pat); +} + +/* + * File checks (ROADMAP 2.6 and 1.2's --strict). + * + * Every entry of a package lands somewhere once linked; dest_of mirrors + * gl_link_pkg exactly: entries under a dot-prefixed name are never + * linked, a leading usr/ is stripped, base-repo packages in system scope + * land in /usr and everything else in the scope's links tree, and in + * system scope the top-level etc/ is copied into /etc. + */ +static bool +has_dot_component(const char *rel) +{ + for (const char *c = rel; c; c = strchr(c, '/')) { + if (*c == '/') { c++; } + if (*c == '.') { return true; } + if (!*c) { break; } + } + return false; +} + +static int +dest_of(const struct env *e, const char *repo, const char *rel, char *out, size_t outsz) +{ + if (e->scope == GL_SCOPE_SYS && + (strcmp(rel, "etc") == 0 || strncmp(rel, "etc/", 4) == 0)) { + return snprintf(out, outsz, "/%s", rel) >= (int)outsz ? -1 : 0; + } + if (strncmp(rel, "usr/", 4) == 0) { rel += 4; } + + char base[PATH_MAX]; + if (e->scope == GL_SCOPE_SYS) { + snprintf(base, sizeof(base), "%s", strcmp(repo, "base") == 0 + ? "/usr" : GL_SYS_ROOT "/links"); + } else { + char ub[PATH_MAX]; + gl_usr_base(e->uid, ub, sizeof(ub)); + snprintf(base, sizeof(base), "%s/links", ub); + } + return snprintf(out, outsz, "%s/%s", base, rel) >= (int)outsz ? -1 : 0; +} + +static int +add_dest(struct rec *r, const char *path, bool dir) +{ + for (size_t k = 0; k < r->ndests; k++) { + if (strcmp(r->dests[k].path, path) == 0) { + if (!dir) { r->dests[k].dir = false; } + return 0; + } + } + struct dest *grown = realloc(r->dests, (r->ndests + 1) * sizeof(*grown)); + if (!grown) { return -1; } + r->dests = grown; + r->dests[r->ndests].path = strdup(path); + if (!r->dests[r->ndests].path) { return -1; } + r->dests[r->ndests].dir = dir; + r->ndests++; + return 0; +} + +/* `rel` (and, for an archive entry, its parents, which tar needn't list) */ +static int +add_rel(const struct env *e, struct rec *r, const char *rel, bool dir, bool parents) +{ + if (rel[0] == '\0' || has_dot_component(rel)) { return 0; } + + char d[PATH_MAX]; + if (parents) { + char buf[PATH_MAX]; + snprintf(buf, sizeof(buf), "%s", rel); + for (char *p = buf; (p = strchr(p, '/')); p++) { + *p = '\0'; + if (dest_of(e, r->repo, buf, d, sizeof(d)) != 0 || add_dest(r, d, true) != 0) { + return -1; + } + *p = '/'; + } + } + if (dest_of(e, r->repo, rel, d, sizeof(d)) != 0) { return -1; } + return add_dest(r, d, dir); +} + +static int +walk_store_dir(const struct env *e, struct rec *r, const char *dir, size_t strip) +{ + DIR *d = opendir(dir); + if (!d) { return 0; } + int rc = 0; + struct dirent *ent; + while (rc == 0 && (ent = readdir(d))) { + if (ent->d_name[0] == '.') { continue; } + char full[PATH_MAX]; + snprintf(full, sizeof(full), "%s/%s", dir, ent->d_name); + struct stat st; + if (lstat(full, &st) != 0) { continue; } + const char *rel = full + strip; + if (S_ISDIR(st.st_mode)) { + rc = add_rel(e, r, rel, true, false); + if (rc == 0) { rc = walk_store_dir(e, r, full, strip); } + } else if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { + rc = add_rel(e, r, rel, false, false); + } + } + closedir(d); + return rc; +} + +static int +walk_archive(const struct env *e, struct rec *r) +{ + char prefix[PATH_MAX]; + snprintf(prefix, sizeof(prefix), "%s-%d.%d.%d/files/", r->name, + r->pkg.pkg_ver.maj, r->pkg.pkg_ver.min, r->pkg.pkg_ver.pat); + size_t plen = strlen(prefix); + + struct archive *a = archive_read_new(); + archive_read_support_filter_gzip(a); + archive_read_support_format_tar(a); + if (archive_read_open_filename(a, r->src, 10240) != ARCHIVE_OK) { + archive_read_free(a); + return -1; + } + + int rc = 0; + struct archive_entry *entry; + while (rc == 0 && archive_read_next_header(a, &entry) == ARCHIVE_OK) { + const char *path = archive_entry_pathname(entry); + if (strncmp(path, prefix, plen) == 0) { + char rel[PATH_MAX]; + snprintf(rel, sizeof(rel), "%s", path + plen); + size_t len = strlen(rel); + while (len > 0 && rel[len - 1] == '/') { rel[--len] = '\0'; } + mode_t type = archive_entry_filetype(entry); + if (type == AE_IFDIR) { + rc = add_rel(e, r, rel, true, true); + } else if (type == AE_IFREG || type == AE_IFLNK) { + rc = add_rel(e, r, rel, false, true); + } + } + archive_read_data_skip(a); + } + archive_read_free(a); + return rc; +} + +static int +load_dests(const struct env *e, struct rec *r) +{ + if (r->dests_loaded) { return 0; } + r->dests_loaded = true; + if (r->from_archive) { return walk_archive(e, r); } + return walk_store_dir(e, r, r->src, strlen(r->src) + 1); +} + +struct item { + const char *path; + bool dir; + size_t rec; +}; + +static int +item_cmp(const void *a, const void *b) +{ + const struct item *x = a, *y = b; + int c = strcmp(x->path, y->path); + if (c != 0) { return c; } + return x->rec < y->rec ? -1 : x->rec > y->rec; +} + +/* Two packages shipping the same path (ROADMAP 2.6): whichever linked + * last used to win silently. Refused when either package is part of + * this transaction; at least one of the two entries must be a file or + * symlink (sharing a directory is normal). */ +static int +check_collisions(const struct env *e, struct set *res) +{ + size_t n = 0; + for (size_t i = 0; i < res->n; i++) { + if (res->v[i].gone) { continue; } + if (load_dests(e, &res->v[i]) != 0) { return -1; } + n += res->v[i].ndests; + } + if (n == 0) { return 0; } + + struct item *items = malloc(n * sizeof(*items)); + if (!items) { return -1; } + size_t k = 0; + for (size_t i = 0; i < res->n; i++) { + if (res->v[i].gone) { continue; } + for (size_t j = 0; j < res->v[i].ndests; j++) { + items[k++] = (struct item){ res->v[i].dests[j].path, res->v[i].dests[j].dir, i }; + } + } + qsort(items, n, sizeof(*items), item_cmp); + + int problems = 0; + for (size_t start = 0; start < n; ) { + size_t end = start + 1; + while (end < n && strcmp(items[end].path, items[start].path) == 0) { end++; } + for (size_t a = start; a < end; a++) { + const struct rec *ra = &res->v[items[a].rec]; + if (!ra->touched && !ra->fresh) { continue; } + for (size_t b = start; b < end; b++) { + if (b == a || (items[a].dir && items[b].dir)) { continue; } + const struct rec *rb = &res->v[items[b].rec]; + /* two of this transaction's packages: report once */ + if ((rb->touched || rb->fresh) && b < a) { continue; } + lg_printf(2, "%s::%s and %s::%s both ship %s.", + ra->repo, ra->name, rb->repo, rb->name, items[a].path); + problems++; + } + } + start = end; + } + free(items); + return problems; +} + +/* --strict: what the post-commit link pass would refuse, refused now + * instead, before anything is committed. Same rules as gl_link_pkg, for + * this transaction's packages only (the ones whose refusals it reports). + * User scope never has a foreign file in its links tree (rebuilt on + * every commit), and config in /etc is offered as .new, never refused. */ +static int +check_links(const struct env *e, struct set *res) +{ + if (!e->strict || e->scope != GL_SCOPE_SYS) { return 0; } + + char store_prefix[PATH_MAX]; + snprintf(store_prefix, sizeof(store_prefix), "%s/", e->store_rt); + size_t splen = strlen(store_prefix); + + int problems = 0; + for (size_t i = 0; i < res->n; i++) { + struct rec *r = &res->v[i]; + if (r->gone || !(r->touched || r->fresh)) { continue; } + if (load_dests(e, r) != 0) { return -1; } + + for (size_t k = 0; k < r->ndests; k++) { + const struct dest *d = &r->dests[k]; + if (strcmp(d->path, "/etc") == 0 || strncmp(d->path, "/etc/", 5) == 0) { continue; } + + char at[PATH_MAX]; + snprintf(at, sizeof(at), "%s%s", e->sysroot, d->path); + struct stat st; + if (lstat(at, &st) != 0) { + if (errno == ENOTDIR) { + lg_printf(2, "Refusing to overwrite non-glacier path: %s", at); + problems++; + } + continue; + } + + bool ours = false; + if (S_ISLNK(st.st_mode)) { + char target[PATH_MAX]; + ssize_t tn = readlink(at, target, sizeof(target) - 1); + ours = tn >= 0 && (size_t)tn >= splen && + strncmp(target, store_prefix, splen) == 0; + } + + if (d->dir) { + /* a foreign symlink here (usr/sbin -> bin) is followed, + * like gl_link_pkg does; one of glacier's isn't */ + if (S_ISDIR(st.st_mode) || (S_ISLNK(st.st_mode) && !ours)) { continue; } + if (ours) { + lg_printf(2, "Refusing to merge directory %s into another package's symlink", at); + } else { + lg_printf(2, "Refusing to overwrite non-glacier path: %s", at); + } + problems++; + continue; + } + + if (ours) { continue; } + if (r->overwrite && (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) { + char kept[PATH_MAX]; + snprintf(kept, sizeof(kept), "%s/.displaced%s", e->store_at, d->path); + struct stat kst; + if (lstat(kept, &kst) != 0) { continue; } /* -O moves it aside */ + } + lg_printf(2, "Refusing to overwrite non-glacier path: %s", at); + problems++; + } + } + return problems; +} + +/* The checks themselves, on the result of a transaction. */ +static int +check(const struct env *e, struct set *res, const struct set *sys, + const struct removed *rm, size_t nrm) +{ + int problems = 0; + char ver[64]; + + for (size_t i = 0; i < res->n; i++) { + const struct rec *p = &res->v[i]; + if (p->gone) { continue; } + + const dep_t *deps = p->pkg.pkg_dependencies; + for (const dep_t *d = deps; d && d->dep_name; d++) { + /* An added package: all its deps. An untouched one: + * only deps on something this transaction changes. */ + if (!p->touched && !name_touched(res, rm, nrm, d->dep_name)) { continue; } + if (provided(res, sys, d->dep_name, &d->dep_ver)) { continue; } + + const struct removed *gone = NULL; + for (size_t k = 0; !p->touched && k < nrm; k++) { + if (strcmp(rm[k].name, d->dep_name) == 0) { gone = &rm[k]; break; } + } + if (gone && !provided(res, sys, d->dep_name, NULL)) { + lg_printf(2, "Removing %s::%s breaks %s::%s.", + gone->repo, gone->name, p->repo, p->name); + } else { + fmt_ver(&d->dep_ver, ver, sizeof(ver)); + lg_printf(2, "%s::%s requires %s >= %s, which is not installed.", + p->repo, p->name, d->dep_name, ver); + } + problems++; + } + + if (!p->touched) { continue; } + for (size_t j = 0; j < res->n; j++) { + const struct rec *q = &res->v[j]; + if (j == i || q->gone) { continue; } + /* Two added packages: report the pair once. */ + if (q->touched && j < i) { continue; } + if (lists_conflict(p, q->name) || lists_conflict(q, p->name)) { + lg_printf(2, "%s::%s conflicts with %s::%s.", + p->repo, p->name, q->repo, q->name); + problems++; + } + } + } + + int files = check_collisions(e, res); + if (files < 0) { return -1; } + problems += files; + + files = check_links(e, res); + if (files < 0) { return -1; } + return problems + files; +} + +/* The runtime path of the live store for a context's scope and root. */ +static void +env_init(struct env *e, const gl_context_t *ctx, const char *store_at) +{ + e->scope = ctx->scope; + e->uid = ctx->uid; + e->sysroot = ctx->sysroot; + e->strict = ctx->strict; + if (ctx->scope == GL_SCOPE_USR) { + char ub[PATH_MAX]; + gl_usr_base(ctx->uid, ub, sizeof(ub)); + snprintf(e->store_rt, sizeof(e->store_rt), "%s/store", ub); + } else { + snprintf(e->store_rt, sizeof(e->store_rt), "%s/store", GL_SYS_ROOT); + } + snprintf(e->store_at, sizeof(e->store_at), "%s", store_at); +} + +/* The system store, which a user-scope transaction's deps may rely on. + * Always the running system's: user scope never has an install root. */ +static int +load_sys(gl_ctx_scope_t scope, struct set *sys) +{ + if (scope != GL_SCOPE_USR) { return 0; } + return load_store(GL_SYS_ROOT "/store", sys, true); +} + +int +gl_check_plan(const gl_context_t *live, + const char *const *add_paths, size_t n_add, + const char *const *remove_ids, size_t n_remove) +{ + if (!live) { return -1; } + + struct set res = { NULL, 0 }, sys = { NULL, 0 }; + struct removed *rm = calloc(n_remove + 1, sizeof(*rm)); + size_t nrm = 0; + int rc = -1; + + if (!rm || load_store(live->store_path, &res, false) != 0 || + load_sys(live->scope, &sys) != 0) { + goto out; + } + + for (size_t i = 0; i < n_remove; i++) { + const char *sep = strstr(remove_ids[i], "::"); + if (!sep) { goto out; } + char repo[NAME_MAX + 1]; + snprintf(repo, sizeof(repo), "%.*s", (int)(sep - remove_ids[i]), remove_ids[i]); + struct rec *r = (struct rec *)find_live(&res, repo, sep + 2); + if (!r) { continue; } /* not installed: nothing to break */ + r->gone = true; + snprintf(rm[nrm].repo, sizeof(rm[nrm].repo), "%s", r->repo); + snprintf(rm[nrm].name, sizeof(rm[nrm].name), "%s", r->name); + nrm++; + } + + for (size_t i = 0; i < n_add; i++) { + gl_pkg_t pkg = {0}; + if (gl_read_pkg_manifest(add_paths[i], &pkg) != GL_INSTALL_OK) { goto out; } + + /* Same repo::name installed: this replaces it. */ + struct rec *old = (struct rec *)find_live(&res, pkg.pkg_repo, pkg.pkg_name); + if (old) { old->gone = true; } + + struct rec *r = set_add(&res); + if (!r) { gl_free_pkg(&pkg); goto out; } + snprintf(r->repo, sizeof(r->repo), "%s", pkg.pkg_repo); + snprintf(r->name, sizeof(r->name), "%s", pkg.pkg_name); + r->ver = (semver_t){ pkg.pkg_ver.maj, pkg.pkg_ver.min, pkg.pkg_ver.pat, NULL }; + r->pkg = pkg; + r->touched = true; + r->fresh = true; + r->overwrite = live->overwrite != 0; + r->from_archive = true; + snprintf(r->src, sizeof(r->src), "%s", add_paths[i]); + } + + struct env e; + env_init(&e, live, live->store_path); + rc = check(&e, &res, &sys, rm, nrm); +out: + set_free(&res); + set_free(&sys); + free(rm); + return rc; +} + +int +gl_check_txn(const gl_context_t *stage) +{ + if (!stage || stage->mode != GL_CTX_STAGE) { return -1; } + + char base[PATH_MAX], live_store[PATH_MAX]; + if (stage->scope == GL_SCOPE_USR) { + gl_usr_base(stage->uid, base, sizeof(base)); + } else { + snprintf(base, sizeof(base), "%s%s", stage->sysroot, GL_SYS_ROOT); + } + snprintf(live_store, sizeof(live_store), "%s/store", base); + + struct set res = { NULL, 0 }, live = { NULL, 0 }, sys = { NULL, 0 }; + struct removed *rm = NULL; + size_t nrm = 0; + int rc = -1; + + if (load_store(stage->store_path, &res, false) != 0 || + load_store(live_store, &live, false) != 0 || + load_sys(stage->scope, &sys) != 0) { + goto out; + } + + rm = calloc(live.n + 1, sizeof(*rm)); + if (!rm) { goto out; } + + /* Touched: in the stage, but not as this exact version in live. + * Fresh: installed by this transaction (a reinstall of the same + * version is fresh but not touched); its link pass is this one's. */ + for (size_t i = 0; i < res.n; i++) { + struct rec *r = &res.v[i]; + const struct rec *l = find_live(&live, r->repo, r->name); + r->touched = !l || strcmp(l->dirname, r->dirname) != 0; + + char marker[PATH_MAX]; + struct stat mst; + snprintf(marker, sizeof(marker), "%s/" GL_FRESH_MARKER, r->src); + r->fresh = lstat(marker, &mst) == 0; + snprintf(marker, sizeof(marker), "%s/" GL_OVERWRITE_MARKER, r->src); + r->overwrite = lstat(marker, &mst) == 0; + } + /* Removed: in live, and no longer in the stage under that repo::name. */ + for (size_t i = 0; i < live.n; i++) { + if (find_live(&res, live.v[i].repo, live.v[i].name)) { continue; } + snprintf(rm[nrm].repo, sizeof(rm[nrm].repo), "%s", live.v[i].repo); + snprintf(rm[nrm].name, sizeof(rm[nrm].name), "%s", live.v[i].name); + nrm++; + } + + struct env e; + env_init(&e, stage, stage->store_path); + rc = check(&e, &res, &sys, rm, nrm); +out: + set_free(&res); + set_free(&live); + set_free(&sys); + free(rm); + return rc; +} diff --git a/src/deps.h b/src/deps.h new file mode 100644 index 0000000..aca34cb --- /dev/null +++ b/src/deps.h @@ -0,0 +1,55 @@ +#ifndef DEPS_H_ +#define DEPS_H_ + +#include + +#include "transaction.h" + +/* + * Dependency and conflict checks (ROADMAP 2.3). Built into + * libglacier_istoreutils.so. + * + * Rules: + * - A PKG_DEPS entry {name, ver} is met by any installed package of + * that name (any repo) at version >= ver. + * - In user scope, a dependency may also be met by a system package. + * System scope never looks at user scopes. + * - PKG_CONFS is matched by name, both ways (either package listing + * the other is a conflict); its version is ignored. Only packages in + * the same scope conflict. + * - Only what a transaction CHANGES is checked: the packages it adds + * (deps and conflicts), and the installed packages that depend on + * something it removes or replaces. A store that was already + * inconsistent doesn't block unrelated work. + * - Two packages may not ship the same path (ROADMAP 2.6), compared by + * where each entry lands once linked; sharing a directory is fine. + * Checked whenever either package is part of the transaction. + * - With ctx->strict (syspkg --strict, implied by -R), anything the + * post-commit link pass would refuse (a foreign file in the way, a + * directory over another package's symlink) is refused up front, + * unless -O will move it aside. System scope only: a user's links + * tree is rebuilt on every commit. + * - Every problem is reported with lg_printf, one line each: + * extra::foo requires bar >= 1.2.0, which is not installed. + * Removing base::musl breaks extra::foo. + * extra::foo conflicts with extra::bar. + * extra::foo and extra::bar both ship /glacier/sys/links/bin/x. + * Refusing to overwrite non-glacier path: /usr/bin/x + * + * Both return the number of problems (0 = fine), or -1 if a store or a + * manifest couldn't be read. + */ + +/* Before a transaction: `live` is a live context; add_paths are .gpkg + * files to install (a package already installed under the same + * repo::name is replaced); remove_ids are "repo::name" strings. */ +int gl_check_plan(const gl_context_t *live, + const char *const *add_paths, size_t n_add, + const char *const *remove_ids, size_t n_remove); + +/* At commit: compares the stage against live. gl_commit_transaction + * calls this itself and refuses with GL_TXN_ERR_DEPS, so a caller that + * skips gl_check_plan is still held to the rules. */ +int gl_check_txn(const gl_context_t *stage); + +#endif diff --git a/src/grepo.c b/src/grepo.c new file mode 100644 index 0000000..15edf8c --- /dev/null +++ b/src/grepo.c @@ -0,0 +1,389 @@ +/* + * grepo: write and sign a repository index (REPO_DESIGN.txt section 4). + * A maintainer tool, like gsign. + * + * grepo -s KEY.sec -r REPO [-n SERIAL] [-e DAYS] DIR + * + * Reads every NAME-VER.gpkg in DIR (its manifest, sha256 and size) and + * writes DIR/repo.idx plus DIR/repo.idx.sig, signed with KEY.sec. Each + * package's manifest must say REPO, and its file name must be + * NAME-VER.gpkg as the manifest names it. The server is then any static + * file host serving DIR. + * + * SERIAL (decided 2026-10-06) is the date form YYYYMMDDNN: the larger + * of the previous DIR/repo.idx's SERIAL + 1 and today (UTC) * 100 + 1. + * -n sets it explicitly; it must still be above the previous one, or + * clients would refuse the index. VALID_UNTIL is now + DAYS days + * (default 30; decision 10c). + * + * Exit 0 on success, 1 otherwise. + */ + +#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 700 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "istoreutils.h" +#include "log.h" +#include "pkg.h" +#include "sha256.h" +#include "sign.h" + +#define DEFAULT_DAYS 30 +#define MAX_DAYS 3650 + +#define USAGE "usage: grepo [-hv] -s KEY.sec -r REPO [-n SERIAL] [-e DAYS] DIR\n" + +static const struct option longopts[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "seckey", required_argument, NULL, 's' }, + { "repo", required_argument, NULL, 'r' }, + { "serial", required_argument, NULL, 'n' }, + { "expire", required_argument, NULL, 'e' }, + { NULL, 0, NULL, 0 } +}; + +static void +usage(void) +{ + fprintf(stderr, USAGE); +} + +static void +help(void) +{ + printf("grepo: Write and sign a repository index\n"); + printf(USAGE); + printf("\n"); + printf("%-25s %s\n", "{-h --help}", "Show this message"); + printf("%-25s %s\n", "{-v --version}", "Show the current version"); + printf("%-25s %s\n", "{-s --seckey} KEY.sec", "Sign the index with this secret key"); + printf("%-25s %s\n", "{-r --repo} REPO", "Repository the packages in DIR belong to"); + printf("%-25s %s\n", "{-n --serial} SERIAL", "Index serial (default: YYYYMMDDNN)"); + printf("%-25s Days until the index expires (default: %d)\n", "{-e --expire} DAYS", DEFAULT_DAYS); + printf("\n"); + printf("This program is free software.\n"); + printf("See the GNU GPL version 3 for details.\n"); +} + +static int +by_name(const void *a, const void *b) +{ + return strcmp(*(char *const *)a, *(char *const *)b); +} + +/* sha256 ("sha256:") and size of a regular file, read once. */ +static int +hash_file(const char *path, char sha[72], int64_t *size) +{ + int fd = open(path, O_RDONLY | O_NOFOLLOW); + if (fd < 0) + return -1; + gl_sha256_ctx_t h; + gl_sha256_init(&h); + uint8_t buf[65536]; + ssize_t n; + int64_t total = 0; + while ((n = read(fd, buf, sizeof buf)) > 0) { + gl_sha256_update(&h, buf, (size_t)n); + total += n; + } + close(fd); + if (n < 0) + return -1; + uint8_t digest[32]; + char hex[65]; + gl_sha256_final(&h, digest); + gl_sha256_to_hex(digest, hex); + snprintf(sha, 72, "sha256:%s", hex); + *size = total; + return 0; +} + +/* Reads a whole file (an index: at most GL_SIGN_MAX_MSG). NULL if it + * doesn't exist (errno ENOENT) or can't be read. */ +static char * +slurp(const char *path, size_t *len) +{ + FILE *f = fopen(path, "rb"); + if (!f) + return NULL; + char *buf = malloc(GL_SIGN_MAX_MSG + 1); + size_t n = buf ? fread(buf, 1, GL_SIGN_MAX_MSG + 1, f) : 0; + int bad = !buf || ferror(f) || n > GL_SIGN_MAX_MSG; + fclose(f); + if (bad) { + free(buf); + errno = EIO; + return NULL; + } + *len = n; + return buf; +} + +/* One DEPS/CONFS element. 0 having said why if the name is unsafe. */ +static int +relation(FILE *f, const char *path, const char *what, const char *name, + const semver_t *v, int first) +{ + if (!gl_valid_pkg_ident(name)) { + lg_printf(LG_ERR, "%s has a %s that is not a safe name.", path, what); + return 0; + } + fprintf(f, "%s { NAME = \"%s\"; VER = \"%d.%d.%d\"; }", first ? "" : ",", + name, v->maj, v->min, v->pat); + return 1; +} + +/* One package's PKGS entry, appended to f. 0, or -1 having said why. */ +static int +write_entry(FILE *f, const char *dir, const char *file, const char *repo) +{ + char path[PATH_MAX]; + snprintf(path, sizeof path, "%s/%s", dir, file); + + struct stat st; + if (lstat(path, &st) != 0 || !S_ISREG(st.st_mode)) { + lg_printf(LG_ERR, "Refusing %s: not a regular file.", path); + return -1; + } + gl_pkg_t pkg = {0}; + if (gl_read_pkg_manifest(path, &pkg) != 0) { + lg_printf(LG_ERR, "Cannot read the manifest of %s.", path); + return -1; + } + + int rc = -1; + char ver[64], want[PATH_MAX]; + snprintf(ver, sizeof ver, "%d.%d.%d", pkg.pkg_ver.maj, pkg.pkg_ver.min, + pkg.pkg_ver.pat); + snprintf(want, sizeof want, "%s-%s.gpkg", pkg.pkg_name, ver); + if (strcmp(pkg.pkg_repo, repo) != 0) { + lg_printf(LG_ERR, "%s is a %s package, not %s.", path, pkg.pkg_repo, repo); + goto out; + } + if (strcmp(file, want) != 0) { + lg_printf(LG_ERR, "%s must be named %s, as its manifest says.", path, want); + goto out; + } + + char sha[72]; + int64_t size; + if (hash_file(path, sha, &size) != 0) { + lg_printf(LG_ERR, "Cannot read %s.", path); + goto out; + } + + fprintf(f, "\t{ NAME = \"%s\"; VER = \"%s\"; FILE = \"%s\";\n" + "\t SHA256 = \"%s\"; SIZE = %" PRId64 "L;\n\t DEPS = (", + pkg.pkg_name, ver, file, sha, size); + int first = 1; + for (dep_t *d = pkg.pkg_dependencies; d && d->dep_name; d++) { + if (!relation(f, path, "dependency", d->dep_name, &d->dep_ver, first)) + goto out; + first = 0; + } + fprintf(f, "%s); CONFS = (", first ? "" : " "); + first = 1; + for (conf_t *k = pkg.pkg_conflicts; k && k->conf_name; k++) { + if (!relation(f, path, "conflict", k->conf_name, &k->conf_ver, first)) + goto out; + first = 0; + } + fprintf(f, "%s); }", first ? "" : " "); + rc = 0; +out: + gl_free_pkg(&pkg); + return rc; +} + +int +main(int argc, char *argv[]) +{ + const char *sec = NULL, *repo = NULL; + int64_t serial = 0; + long days = DEFAULT_DAYS; + int c; + + while ((c = getopt_long(argc, argv, "hvs:r:n:e:", longopts, NULL)) != -1) { + char *end; + switch (c) { + case 'h': + help(); + return 0; + case 'v': + printf("v4.0.0\n"); + return 0; + case 's': sec = optarg; break; + case 'r': repo = optarg; break; + case 'n': + errno = 0; + serial = strtoll(optarg, &end, 10); + if (errno || *end || end == optarg || serial <= 0) { + lg_printf(LG_ERR, "SERIAL must be a whole number above 0."); + return 1; + } + break; + case 'e': + errno = 0; + days = strtol(optarg, &end, 10); + if (errno || *end || end == optarg || days < 1 || days > MAX_DAYS) { + lg_printf(LG_ERR, "DAYS must be a whole number from 1 to %d.", MAX_DAYS); + return 1; + } + break; + default: + usage(); + return 1; + } + } + if (!sec || !repo || optind != argc - 1) { + usage(); + return 1; + } + const char *dir = argv[optind]; + if (!gl_valid_pkg_ident(repo)) { + lg_printf(LG_ERR, "Repository name %s is not a safe name ([A-Za-z0-9._-]).", repo); + return 1; + } + + int st = gl_sign_check_seckey(sec); + if (st != GL_SIGN_OK) { + lg_printf(LG_ERR, "Secret key %s %s.", sec, + st == GL_SIGN_ERR_ENCRYPTED ? "is encrypted: sign with signify" : + st == GL_SIGN_ERR_FORMAT ? "is malformed" : "cannot be read"); + return 1; + } + + char idx_path[PATH_MAX], sig_path[PATH_MAX]; + if ((size_t)snprintf(idx_path, sizeof idx_path, "%s/repo.idx", dir) >= sizeof idx_path || + (size_t)snprintf(sig_path, sizeof sig_path, "%s/repo.idx.sig", dir) >= sizeof sig_path) { + lg_printf(LG_ERR, "Path too long: %s", dir); + return 1; + } + + /* The previous index, for its SERIAL. The maintainer's own file: + * parsed, not verified, but it must be a well-formed index for + * this repo (a mixed-up DIR is better caught here). */ + int64_t prev = 0; + size_t plen; + char *ptext = slurp(idx_path, &plen); + if (!ptext && errno != ENOENT) { + lg_printf(LG_ERR, "Cannot read %s.", idx_path); + return 1; + } + if (ptext) { + gl_index_t old; + gl_index_status_t ist = gl_index_parse(ptext, plen, repo, &old); + free(ptext); + if (ist == GL_INDEX_ERR_REPO) { + lg_printf(LG_ERR, "%s is the index of another repository.", idx_path); + return 1; + } + if (ist != GL_INDEX_OK) { + lg_printf(LG_ERR, "Malformed %s.", idx_path); + return 1; + } + prev = old.serial; + gl_index_free(&old); + } + + time_t now = time(NULL); + if (serial == 0) { + struct tm tm; + gmtime_r(&now, &tm); + int64_t today = ((int64_t)(tm.tm_year + 1900) * 10000 + + (tm.tm_mon + 1) * 100 + tm.tm_mday) * 100 + 1; + serial = prev + 1 > today ? prev + 1 : today; + } else if (serial <= prev) { + lg_printf(LG_ERR, "SERIAL must be above %" PRId64 ", the previous index's.", prev); + return 1; + } + + DIR *d = opendir(dir); + if (!d) { + lg_printf(LG_ERR, "Cannot open %s: %s", dir, strerror(errno)); + return 1; + } + char **names = NULL; + size_t n = 0; + struct dirent *de; + while ((de = readdir(d))) { + size_t l = strlen(de->d_name); + if (de->d_name[0] == '.' || l < 6 || strcmp(de->d_name + l - 5, ".gpkg") != 0) + continue; + char **grown = realloc(names, (n + 1) * sizeof *names); + if (!grown || !(grown[n] = strdup(de->d_name))) { + lg_printf(LG_ERR, "Out of memory."); + return 1; + } + names = grown; + n++; + } + closedir(d); + qsort(names, n, sizeof *names, by_name); + + /* Written beside the result, then renamed over it: never in place. */ + char tmp_idx[PATH_MAX], tmp_sig[PATH_MAX]; + snprintf(tmp_idx, sizeof tmp_idx, "%s/.repo.idx.XXXXXX", dir); + int fd = mkstemp(tmp_idx); + FILE *f = fd < 0 ? NULL : fdopen(fd, "w"); + if (!f) { + lg_printf(LG_ERR, "Cannot write in %s: %s", dir, strerror(errno)); + return 1; + } + snprintf(tmp_sig, sizeof tmp_sig, "%s.sig", tmp_idx); + + fprintf(f, "# Repository index written by grepo. Signed: repo.idx.sig.\n" + "REPO = \"%s\";\nSERIAL = %" PRId64 "L;\nVALID_UNTIL = %" PRId64 "L;\n" + "PKGS = (\n", repo, serial, (int64_t)now + (int64_t)days * 86400); + int rc = 0; + for (size_t i = 0; i < n && rc == 0; i++) { + if (i) + fprintf(f, ",\n"); + rc = write_entry(f, dir, names[i], repo); + } + fprintf(f, "%s);\n", n ? "\n" : ""); + if (fchmod(fd, 0644) != 0 || fflush(f) != 0 || fsync(fd) != 0) + rc = rc ? rc : (lg_printf(LG_ERR, "Cannot write %s.", tmp_idx), -1); + if (fclose(f) != 0 && rc == 0) { + lg_printf(LG_ERR, "Cannot write %s.", tmp_idx); + rc = -1; + } + for (size_t i = 0; i < n; i++) + free(names[i]); + free(names); + + if (rc == 0 && (st = gl_sign_file(sec, tmp_idx, tmp_sig)) != GL_SIGN_OK) { + lg_printf(LG_ERR, "Cannot sign %s.", tmp_idx); + rc = -1; + } + /* The signature first: a client that fetches between the renames + * gets a new .sig with the old index, which fails to verify and is + * refused, never an index accepted under the wrong signature. */ + if (rc == 0 && (rename(tmp_sig, sig_path) != 0 || rename(tmp_idx, idx_path) != 0)) { + lg_printf(LG_ERR, "Cannot replace %s: %s", idx_path, strerror(errno)); + rc = -1; + } + if (rc != 0) { + unlink(tmp_idx); + unlink(tmp_sig); + return 1; + } + lg_printf(LG_SUCCESS, "Wrote %s: %zu %s, serial %" PRId64 ".", idx_path, n, + n == 1 ? "package" : "packages", serial); + return 0; +} diff --git a/src/gsha256sum.c b/src/gsha256sum.c index 1f26b5b..b54ecdf 100644 --- a/src/gsha256sum.c +++ b/src/gsha256sum.c @@ -1,18 +1,54 @@ +#include #include #include "sha256.h" +#define USAGE "usage: gsha256sum [-hv] FILE\n" + +static const struct option longopts[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 } +}; + +static void +help(void) +{ + printf("gsha256sum: Print a file's sha256 as a Glacier sidecar line\n"); + printf(USAGE); + printf("\n"); + printf("%-25s %s\n", "{-h --help}", "Show this message"); + printf("%-25s %s\n", "{-v --version}", "Show the current version"); + printf("\n"); + printf("This program is free software.\n"); + printf("See the GNU GPL version 3 for details.\n"); +} + int main(int argc, char *argv[]) { - if (argc != 2) { - fprintf(stderr, "usage: %s FILE\n", argv[0]); + int c; + while ((c = getopt_long(argc, argv, "hv", longopts, NULL)) != -1) { + switch (c) { + case 'h': + help(); + return 0; + case 'v': + printf("v4.0.0\n"); + return 0; + default: + fprintf(stderr, USAGE); + return 1; + } + } + if (optind != argc - 1) { + fprintf(stderr, USAGE); return 1; } char hex[GL_SHA256_HEX_SIZE]; - if (gl_sha256_file(argv[1], hex) != 0) { - perror(argv[1]); + if (gl_sha256_file(argv[optind], hex) != 0) { + perror(argv[optind]); return 1; } diff --git a/src/gsign.c b/src/gsign.c new file mode 100644 index 0000000..8137f20 --- /dev/null +++ b/src/gsign.c @@ -0,0 +1,212 @@ +/* + * gsign: make signify-compatible Ed25519 keys and signatures + * (REPO_DESIGN.txt section 2). A build/maintainer tool, like + * gsha256sum. + * + * gsign -G -p KEY.pub -s KEY.sec [-c COMMENT] + * gsign -S -s KEY.sec -m FILE [-x SIGFILE] + * gsign -V -p KEY.pub -m FILE [-x SIGFILE] + * + * SIGFILE defaults to FILE.sig. Exit 0 on success, 1 otherwise. + */ + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "sign.h" + +#define USAGE \ + "usage: gsign [-hv]\n" \ + " gsign -G -p KEY.pub -s KEY.sec [-c COMMENT]\n" \ + " gsign -S -s KEY.sec -m FILE [-x SIGFILE]\n" \ + " gsign -V -p KEY.pub -m FILE [-x SIGFILE]\n" + +static const struct option longopts[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "generate", no_argument, NULL, 'G' }, + { "sign", no_argument, NULL, 'S' }, + { "verify", no_argument, NULL, 'V' }, + { "pubkey", required_argument, NULL, 'p' }, + { "seckey", required_argument, NULL, 's' }, + { "message", required_argument, NULL, 'm' }, + { "sig", required_argument, NULL, 'x' }, + { "comment", required_argument, NULL, 'c' }, + { NULL, 0, NULL, 0 } +}; + +static void +usage(void) +{ + fprintf(stderr, USAGE); +} + +static void +help(void) +{ + printf("gsign: Make and check signify-compatible signatures\n"); + printf(USAGE); + printf("\n"); + printf("%-25s %s\n", "{-h --help}", "Show this message"); + printf("%-25s %s\n", "{-v --version}", "Show the current version"); + printf("%-25s %s\n", "{-G --generate}", "Generate a new key pair"); + printf("%-25s %s\n", "{-S --sign}", "Sign a file"); + printf("%-25s %s\n", "{-V --verify}", "Verify a file's signature"); + printf("%-25s %s\n", "{-p --pubkey} KEY.pub", "Public key file"); + printf("%-25s %s\n", "{-s --seckey} KEY.sec", "Secret key file"); + printf("%-25s %s\n", "{-m --message} FILE", "File to sign or verify"); + printf("%-25s %s\n", "{-x --sig} SIGFILE", "Signature file (default: FILE.sig)"); + printf("%-25s %s\n", "{-c --comment} COMMENT", "Comment for a new key pair (default: gsign)"); + printf("\n"); + printf("This program is free software.\n"); + printf("See the GNU GPL version 3 for details.\n"); +} + +/* " ." about one file. */ +static int +fail(const char *what, const char *path, int st) +{ + const char *problem; + switch (st) { + case GL_SIGN_ERR_FORMAT: problem = "is malformed"; break; + case GL_SIGN_ERR_ENCRYPTED: problem = "is encrypted: sign with signify"; break; + case GL_SIGN_ERR_TOOBIG: problem = "is too large"; break; + case GL_SIGN_ERR_EXISTS: problem = "already exists"; break; + case GL_SIGN_ERR_WRITE: problem = "cannot be written"; break; + default: problem = "cannot be read"; break; + } + lg_printf(LG_ERR, "%s %s %s.", what, path, problem); + return 1; +} + +int +main(int argc, char *argv[]) +{ + char mode = 0; + const char *pub = NULL, *sec = NULL, *msg = NULL, *sig = NULL; + const char *comment = "gsign"; + int c; + + while ((c = getopt_long(argc, argv, "hvGSVp:s:m:x:c:", longopts, NULL)) != -1) { + switch (c) { + case 'h': + help(); + return 0; + case 'v': + printf("v4.0.0\n"); + return 0; + case 'G': case 'S': case 'V': + if (mode && mode != c) { + usage(); + return 1; + } + mode = (char)c; + break; + case 'p': pub = optarg; break; + case 's': sec = optarg; break; + case 'm': msg = optarg; break; + case 'x': sig = optarg; break; + case 'c': comment = optarg; break; + default: + usage(); + return 1; + } + } + if (optind != argc) { + usage(); + return 1; + } + + char sigbuf[PATH_MAX]; + if (msg && !sig) { + if ((size_t)snprintf(sigbuf, sizeof sigbuf, "%s.sig", msg) >= sizeof sigbuf) { + lg_printf(LG_ERR, "Path too long: %s", msg); + return 1; + } + sig = sigbuf; + } + + int st; + switch (mode) { + case 'G': + if (!pub || !sec || msg) { + usage(); + return 1; + } + /* The library refuses these too (O_EXCL); this names which. */ + struct stat sb; + if (lstat(sec, &sb) == 0) + return fail("Secret key", sec, GL_SIGN_ERR_EXISTS); + if (lstat(pub, &sb) == 0) + return fail("Public key", pub, GL_SIGN_ERR_EXISTS); + st = gl_sign_generate(pub, sec, comment); + if (st == GL_SIGN_ERR_FORMAT) { + lg_printf(LG_ERR, "Comment must be one line, at most %d characters.", + GL_SIGN_MAX_COMMENT - 11); + return 1; + } + if (st == GL_SIGN_ERR_RANDOM) { + lg_printf(LG_ERR, "Cannot get random bytes for a new key."); + return 1; + } + if (st != GL_SIGN_OK) { + lg_printf(LG_ERR, "Cannot write %s and %s.", pub, sec); + return 1; + } + lg_printf(LG_SUCCESS, "Wrote %s and %s.", pub, sec); + return 0; + + case 'S': + if (!sec || !msg || pub) { + usage(); + return 1; + } + if ((st = gl_sign_check_seckey(sec)) != GL_SIGN_OK) + return fail("Secret key", sec, st); + st = gl_sign_file(sec, msg, sig); + if (st == GL_SIGN_ERR_WRITE) + return fail("Signature", sig, st); + if (st != GL_SIGN_OK) + return fail("File", msg, st); + lg_printf(LG_SUCCESS, "Signed %s: %s.", msg, sig); + return 0; + + case 'V': { + if (!pub || !msg || sec) { + usage(); + return 1; + } + gl_sign_pubkey_t pk; + gl_sign_sig_t sg; + if ((st = gl_sign_read_pubkey(pub, &pk)) != GL_SIGN_OK) + return fail("Public key", pub, st); + if ((st = gl_sign_read_sig(sig, &sg)) != GL_SIGN_OK) + return fail("Signature", sig, st); + st = gl_sign_verify_file(pub, msg, sig); + switch (st) { + case GL_SIGN_OK: + lg_printf(LG_SUCCESS, "Signature verified: %s.", msg); + return 0; + case GL_SIGN_ERR_KEYNUM: + lg_printf(LG_ERR, "%s was signed by a different key than %s.", msg, pub); + return 1; + case GL_SIGN_ERR_BAD: + lg_printf(LG_ERR, "Signature does not verify: %s.", msg); + return 1; + default: + return fail("File", msg, st); + } + } + + default: + usage(); + return 1; + } +} diff --git a/src/istoreutils.c b/src/istoreutils.c index 821afa0..6ea20bc 100644 --- a/src/istoreutils.c +++ b/src/istoreutils.c @@ -18,15 +18,13 @@ #include #include "common.h" +#include "deps.h" #include "istoreutils.h" #include "log.h" #include "pkg.h" #include "transaction.h" /* Layout: see the comment above GL_USR_ROOT in common.h. */ -static const dir_t sys_index = { GL_SYS_ROOT "/index", 0700 }; -static const dir_t sys_store = { GL_SYS_ROOT "/store", 0700 }; -static const dir_t sys_links = { GL_SYS_ROOT "/links", 0700 }; /* Base-system exception: packages in the "base" repo under system * scope get their symlinks placed directly in /usr rather than the @@ -35,13 +33,12 @@ static const dir_t sys_links = { GL_SYS_ROOT "/links", 0700 }; * setup. Everything else installed under system scope (an operator * running `gpkg -s` on something that isn't part of the base system) * stays under /glacier/sys/links. See gl_link_pkg / gl_relink_store. */ -/* The package's manifest, kept beside its files in the store so - * gl_backup_pkg can rebuild a faithful .gpkg from an installed version. - * Dot-prefixed, so linking, index rebuilds and the store scanners all - * skip it. */ -#define GL_MANIFEST_COPY ".glacier-manifest" - #define GL_BASE_REPO "base" + +/* An extracted entry's mode: the archive's, never group/world-writable, + * and applied explicitly so the caller's umask doesn't decide whether + * other users can run a system package. */ +#define GL_ENTRY_MODE(e) ((mode_t)(archive_entry_perm(e) & 07755)) #define GL_BASE_LINKS_DEST "/usr" bool @@ -78,31 +75,6 @@ gl_usr_base_status(uid_t uid) return (int)gl_usr_base_check(uid); } -bool -gl_sys_istore_exists(index_store_t index_or_store) -{ - char path[PATH_MAX]; - struct stat st; - - switch (index_or_store) { - case INDEX: - snprintf(path, sizeof(path), "%s", sys_index.path); - break; - case STORE: - snprintf(path, sizeof(path), "%s", sys_store.path); - break; - default: - printf("libglacier: argument index_or_store of function gl_sys_istore_exists must be INDEX or STORE\n"); - return false; - } - - if (stat(path, &st) != 0) { - return false; - } - - return S_ISDIR(st.st_mode); -} - static void make_index_name(char *buffer, size_t buffersz) { @@ -112,7 +84,7 @@ make_index_name(char *buffer, size_t buffersz) } static int -create_index(const char *ind_path, uid_t uid) +create_index(const char *ind_path, uid_t uid, mode_t mode) { char fname[64]; make_index_name(fname, sizeof(fname)); @@ -128,11 +100,10 @@ create_index(const char *ind_path, uid_t uid) /* fopen()'s mode is subject to the caller's umask; every later * index file goes through mkstemp() in gl_rebuild_index(), which - * POSIX guarantees creates at 0600 regardless of umask. Match that - * here explicitly so the first index isn't the one file whose - * permissions depend on whatever umask gstore happened to run - * under. */ - if (chmod(full_path, 0600) != 0) { return -1; } + * then sets the scope's mode explicitly. Do the same here so the + * first index isn't the one file whose permissions depend on + * whatever umask gstore happened to run under. */ + if (chmod(full_path, mode) != 0) { return -1; } if (chown(full_path, uid, (gid_t)-1) != 0) { return -1; } @@ -172,7 +143,11 @@ is_glacier_symlink(const char *path, const char *store_prefix) ssize_t n = readlink(path, target, sizeof(target) - 1); if (n < 0) { return false; } /* not a symlink, or doesn't exist */ target[n] = '\0'; - return strncmp(target, store_prefix, strlen(store_prefix)) == 0; + /* The store itself, or something under it: "/glacier/sys/store" + * must not also match "/glacier/sys/store2/...". */ + size_t plen = strlen(store_prefix); + return strncmp(target, store_prefix, plen) == 0 && + (target[plen] == '/' || target[plen] == '\0'); } @@ -254,7 +229,7 @@ gl_init_user(uid_t uid, bool isVerbose) } } - if (create_index(ind_path, uid) != 0) { + if (create_index(ind_path, uid, gl_scope_file_mode(GL_SCOPE_USR)) != 0) { lg_printf(2, "Cannot create initial index file"); goto fail; } @@ -359,47 +334,81 @@ gl_create_fs_tree(const char *root, bool isVerbose) int gl_init_sys(bool isVerbose) { + return gl_init_sys_at("/", isVerbose); +} + +/* gl_init_sys under an install root (IMAGE_DESIGN section 3): the tree, + * /glacier/sys and its index are all created under `sysroot`, which + * must already exist. "/" is the running system. */ +int +gl_init_sys_at(const char *sysroot, bool isVerbose) +{ + char root[PATH_MAX]; + if (gl_normalize_sysroot(sysroot, root, sizeof(root)) != 0) { + lg_printf(2, "Refusing install root %s: not an absolute path without . or .. components.", + sysroot ? sysroot : "(null)"); + return 1; + } + + if (root[0] != '\0') { + char bad[PATH_MAX]; + const char *why = ""; + if (gl_sysroot_trusted(root, bad, sizeof(bad), &why) != 0) { + lg_printf(2, "Refusing install root %s: %s is %s.", root, bad, why); + return 1; + } + } + + char glacier[PATH_MAX], sys[PATH_MAX], index[PATH_MAX], store[PATH_MAX], links[PATH_MAX]; + snprintf(glacier, sizeof(glacier), "%s/glacier", root); + snprintf(sys, sizeof(sys), "%s%s", root, GL_SYS_ROOT); + snprintf(index, sizeof(index), "%s%s/index", root, GL_SYS_ROOT); + snprintf(store, sizeof(store), "%s%s/store", root, GL_SYS_ROOT); + snprintf(links, sizeof(links), "%s%s/links", root, GL_SYS_ROOT); + /* Already initialized == a system index exists. Refuse, same as * gl_init_user does for an existing user base, before touching * anything. A partial tree (dirs but no index yet, e.g. pre-created * by an image build) is NOT this case: it gets completed below. */ char current[PATH_MAX]; - snprintf(current, sizeof(current), "%s/current", sys_index.path); + snprintf(current, sizeof(current), "%s/current", index); struct stat st; if (lstat(current, &st) == 0) { - lg_printf(2, "Cannot create %s: already exists", GL_SYS_ROOT); + lg_printf(2, "Cannot create %s: already exists", sys); return 1; } /* The top-level tree goes BEFORE the index: the index is what marks * -n as done, so if the tree fails part-way a re-run can finish it * instead of being refused as "already exists". */ - if (gl_create_fs_tree("/", isVerbose) != 0) { + if (gl_create_fs_tree(root[0] ? root : "/", isVerbose) != 0) { return 1; } /* Parents first: unlike /glacier/usr, nothing else creates these, * and mkdir() of sys/index would otherwise fail ENOENT. */ const char *paths[] = { - "/glacier", - "/glacier/sys", - sys_index.path, - sys_store.path, - sys_links.path, - }; - mode_t modes[] = { - 0755, - 0755, - sys_index.mode, - sys_store.mode, - sys_links.mode, + glacier, + sys, + index, + store, + links, }; + mode_t mode = gl_scope_dir_mode(GL_SCOPE_SYS); for (int i = 0; i < 5; i++) { if (isVerbose) { lg_printf(0, "Creating %s", paths[i]); } - if (mkdir(paths[i], modes[i]) == -1) { + /* chmod after a successful mkdir: the store must be 0755 + * whatever umask syspkg runs under (see gl_scope_dir_mode). + * A directory that already existed is left as it is. */ + if (mkdir(paths[i], mode) == 0) { + if (chmod(paths[i], mode) != 0) { + lg_printf(2, "Cannot set the mode of %s: %s", paths[i], strerror(errno)); + return 1; + } + } else { switch (errno) { case EEXIST: break; @@ -424,7 +433,7 @@ gl_init_sys(bool isVerbose) * fresh index. lstat() rather than stat() so a dangling `current` * still counts as present and is left for a human to look at. */ if (lstat(current, &st) == 0) { - lg_printf(2, "Cannot create %s: already exists", GL_SYS_ROOT); + lg_printf(2, "Cannot create %s: already exists", sys); return 1; } if (errno != ENOENT) { @@ -433,7 +442,7 @@ gl_init_sys(bool isVerbose) } if (isVerbose) { lg_printf(0, "Creating initial system index"); } - if (create_index(sys_index.path, 0) != 0) { + if (create_index(index, 0, gl_scope_file_mode(GL_SCOPE_SYS)) != 0) { lg_printf(2, "Cannot create initial system index file"); return 1; } @@ -441,118 +450,6 @@ gl_init_sys(bool isVerbose) return 0; } -static void -trim(char *s) -{ - char *p = s; - while (*p && isspace((unsigned char)*p)) { - p++; - } - - if (p != s) { - memmove(s, p, strlen(p) + 1); - } - - size_t len = strlen(s); - while (len && isspace((unsigned char)s[len - 1])) { - s[--len] = 0; - } -} - -static char -*dupstr(const char *s) -{ - size_t n = strlen(s); - char *p = malloc(n + 1); - if (!p) { - return NULL; - } - - memcpy(p, s, n + 1); - return p; -} - -int -gl_parse_index(struct gindex *idx, FILE *f) -{ - idx->uid = -1; - idx->listed = -1; - idx->entries = NULL; - idx->count = 0; - - char line[512]; - - while (fgets(line, sizeof(line), f)) { - trim(line); - if (!line[0] || line[0] == '#') { continue; } - - char *eq = strchr(line, '='); - if (eq) { - *eq = 0; - char *key = line; - char *val = eq + 1; - trim(key); - trim(val); - - if (strcmp(key, "uid") == 0) { idx->uid = atoi(val); } - else if (strcmp(key, "listed") == 0) { idx->listed=atoi(val); } - continue; - } - - char *first = strstr(line, "::"); - if (!first) { continue; } - *first = 0; - - char *repo = line; - char *rest = first + 2; - - char *second = strstr(rest, "::"); - char *pkg; - char *ver; - if (second) { - *second = 0; - pkg = rest; - ver = second + 2; - } else { - /* Older index files may only have repo::pkg with no - * version field — tolerate that rather than reject it. */ - pkg = rest; - ver = "unknown"; - } - - trim(repo); - trim(pkg); - trim(ver); - - struct gpkg_entry ent; - ent.repo = dupstr(repo); - ent.pkg = dupstr(pkg); - ent.ver = dupstr(ver); - if (!ent.repo || !ent.pkg || !ent.ver) { return -1; } - - struct gpkg_entry *tmp = - realloc(idx->entries, sizeof(*idx->entries) * (idx->count + 1)); - if (!tmp) { return -1; } - idx->entries = tmp; - idx->entries[idx->count++] = ent; - } - - return 0; -} - -void -gl_free_index(struct gindex *idx) -{ - for (size_t i = 0; i < idx->count; i++) { - free(idx->entries[i].repo); - free(idx->entries[i].pkg); - free(idx->entries[i].ver); - } - - free(idx->entries); - idx->entries = NULL; - idx->count = 0; -} int gl_rebuild_index(gl_context_t *ctx) { @@ -569,7 +466,15 @@ gl_rebuild_index(gl_context_t *ctx) snprintf(new_index_path, sizeof(new_index_path), "%s/%s", ctx->index_path, fname); - FILE *out = fopen(new_index_path, "w"); + /* Entries go to an anonymous temp file first (the header needs + * the count). Never fopen(new_index_path, "w"): in a stage, an + * index file written in the same second as live's has the same + * name and is a HARDLINK to live's (seeding), so truncating it + * rewrote the live index in place, and an aborted transaction + * left live listing the stage's packages. The new index only + * reaches its name by rename(), which replaces the entry, not + * the inode. */ + FILE *out = tmpfile(); if (!out) { closedir(store_dir); return -1; @@ -614,6 +519,7 @@ gl_rebuild_index(gl_context_t *ctx) * contains dashes, since we already know its * exact length from pkg_ent->d_name. */ char pkg_ver[64] = "unknown"; + bool automatic = false; DIR *ver_dir = opendir(pkg_path); if (ver_dir) { struct dirent *ver_ent; @@ -624,31 +530,42 @@ gl_rebuild_index(gl_context_t *ctx) snprintf(pkg_ver, sizeof(pkg_ver), "%s", ver_ent->d_name + prefix_len); } + char marker[PATH_MAX]; + struct stat mst; + snprintf(marker, sizeof(marker), "%s/%s/" GL_AUTO_MARKER, + pkg_path, ver_ent->d_name); + automatic = lstat(marker, &mst) == 0; break; } closedir(ver_dir); } - fprintf(out, "%s::%s::%s\n", + /* repo::name::version::auto|explicit. "auto" = + * pulled in as a dependency (GL_AUTO_MARKER), + * not asked for by name. */ + fprintf(out, "%s::%s::%s::%s\n", repo_ent->d_name, pkg_ent->d_name, - pkg_ver); + pkg_ver, + automatic ? "auto" : "explicit"); listed++; } closedir(repo_dir); } closedir(store_dir); - fclose(out); /* Prepend header by copying through a temp file then renaming over */ - FILE *tmp_in = fopen(new_index_path, "r"); - if (!tmp_in) return -1; + FILE *tmp_in = out; + rewind(tmp_in); char tmp_path[PATH_MAX]; snprintf(tmp_path, sizeof(tmp_path), "%s/.index_tmp_XXXXXX", ctx->index_path); int tmp_fd = mkstemp(tmp_path); if (tmp_fd < 0) { fclose(tmp_in); return -1; } + if (fchmod(tmp_fd, gl_scope_file_mode(ctx->scope)) != 0) { + close(tmp_fd); unlink(tmp_path); fclose(tmp_in); return -1; + } FILE *tmp_out = fdopen(tmp_fd, "w"); if (!tmp_out) { close(tmp_fd); fclose(tmp_in); return -1; } @@ -677,107 +594,26 @@ gl_rebuild_index(gl_context_t *ctx) return 0; } +/* Archive the entry `name` of the directory open at `parent` (AT_FDCWD: + * a path) as `archive_path`, recursively. Everything is opened relative + * to its parent's descriptor with O_NOFOLLOW, and what is archived is + * what the descriptor turned out to be: root backing up a user's store + * (gstore -b) can't be steered into a symlink to /etc/shadow, or into + * /root, by swapping an entry between a check and an open. */ static int -add_to_archive(struct archive *a, const char *real_path, const char *archive_path) +add_entry_at(struct archive *a, int parent, const char *name, const char *archive_path) { struct stat st; - - if (lstat(real_path, &st) != 0) { + if (fstatat(parent, name, &st, AT_SYMLINK_NOFOLLOW) != 0) { return -1; } - if (S_ISDIR(st.st_mode)) { - struct archive_entry *entry = archive_entry_new(); - archive_entry_set_pathname(entry, archive_path); - archive_entry_set_filetype(entry, AE_IFDIR); - archive_entry_set_perm(entry, st.st_mode & 0777); - - if (archive_write_header(a, entry) != ARCHIVE_OK) { - archive_entry_free(entry); - return -1; - } - - archive_entry_free(entry); - - DIR *dir = opendir(real_path); /* NEW */ - if (!dir) { - return -1; - } - - struct dirent *ent; - while ((ent = readdir(dir))) { /* NEW */ - if (strcmp(ent->d_name, ".") == 0 || - strcmp(ent->d_name, "..") == 0) { - continue; - } - - char child_real[PATH_MAX]; /* NEW */ - char child_arch[PATH_MAX]; /* NEW */ - - snprintf(child_real, sizeof(child_real), - "%s/%s", real_path, ent->d_name); - - snprintf(child_arch, sizeof(child_arch), - "%s/%s", archive_path, ent->d_name); - - if (add_to_archive(a, child_real, child_arch) != 0) { - closedir(dir); - return -1; - } - } - - closedir(dir); - return 0; - } - - /* existing file handling logic continues below */ - - if (S_ISREG(st.st_mode)) { - - int fd = open(real_path, O_RDONLY); - if (fd < 0) { - return -1; - } - - struct archive_entry *entry = archive_entry_new(); - archive_entry_set_pathname(entry, archive_path); - archive_entry_set_size(entry, st.st_size); - archive_entry_set_filetype(entry, AE_IFREG); - archive_entry_set_perm(entry, st.st_mode & 0777); - - if (archive_write_header(a, entry) != ARCHIVE_OK) { - archive_entry_free(entry); - close(fd); - return -1; - } - - char buf[8192]; - ssize_t n; - - while ((n = read(fd, buf, sizeof(buf))) > 0) { - if (archive_write_data(a, buf, (size_t)n) < 0) { - archive_entry_free(entry); - close(fd); - return -1; - } - } - - archive_entry_free(entry); - close(fd); - - if (n < 0) { - return -1; - } - - return 0; - } - /* Symlinks too: a package's soname links and its loader * (musl's lib/ld-musl-*.so.1) are symlinks, and a backup that * dropped them wouldn't restore a working package. */ if (S_ISLNK(st.st_mode)) { char target[PATH_MAX]; - ssize_t n = readlink(real_path, target, sizeof(target) - 1); + ssize_t n = readlinkat(parent, name, target, sizeof(target) - 1); if (n < 0) { return -1; } target[n] = '\0'; @@ -790,8 +626,71 @@ add_to_archive(struct archive *a, const char *real_path, const char *archive_pat archive_entry_free(entry); return rc; } + if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) { + return 0; /* devices, fifos, sockets: never in a package */ + } - return 0; /* devices, fifos, sockets: never in a package */ + /* O_NONBLOCK: an entry swapped for a fifo must not hang us. */ + int fd = openat(parent, name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC | + (S_ISDIR(st.st_mode) ? O_DIRECTORY : 0)); + if (fd < 0 || fstat(fd, &st) != 0) { + if (fd >= 0) { close(fd); } + return -1; + } + + struct archive_entry *entry = archive_entry_new(); + archive_entry_set_pathname(entry, archive_path); + archive_entry_set_perm(entry, st.st_mode & 0777); + + if (S_ISDIR(st.st_mode)) { + archive_entry_set_filetype(entry, AE_IFDIR); + int rc = archive_write_header(a, entry) == ARCHIVE_OK ? 0 : -1; + archive_entry_free(entry); + DIR *dir = rc == 0 ? fdopendir(fd) : NULL; + if (!dir) { + close(fd); + return -1; + } + + struct dirent *ent; + while (rc == 0 && (ent = readdir(dir))) { + if (strcmp(ent->d_name, ".") == 0 || + strcmp(ent->d_name, "..") == 0) { + continue; + } + char child_arch[PATH_MAX]; + snprintf(child_arch, sizeof(child_arch), + "%s/%s", archive_path, ent->d_name); + rc = add_entry_at(a, dirfd(dir), ent->d_name, child_arch); + } + closedir(dir); + return rc; + } + + if (!S_ISREG(st.st_mode)) { /* swapped for something else */ + archive_entry_free(entry); + close(fd); + return 0; + } + + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_size(entry, st.st_size); + int rc = archive_write_header(a, entry) == ARCHIVE_OK ? 0 : -1; + archive_entry_free(entry); + + char buf[8192]; + ssize_t n = 0; + while (rc == 0 && (n = read(fd, buf, sizeof(buf))) > 0) { + if (archive_write_data(a, buf, (size_t)n) < 0) { rc = -1; } + } + close(fd); + return (rc == 0 && n == 0) ? 0 : -1; +} + +static int +add_to_archive(struct archive *a, const char *real_path, const char *archive_path) +{ + return add_entry_at(a, AT_FDCWD, real_path, archive_path); } /* Write `content` as a regular file entry. */ @@ -834,6 +733,40 @@ find_version_dir(const char *store, const char *repo, const char *name, return rc; } +/* + * gl_set_pkg_auto + * DESCRIPTION: marks the installed repo/name as installed automatically + * (as a dependency) or explicitly, and rebuilds ctx's index so + * its 4th field says so. Nothing calls this with true until + * dependencies are resolved from remote repositories (ROADMAP + * 2.3 / 3.1); reinstalling a package by name drops the marker. + * RETURNS: 0 on success, -1 if the package isn't installed or the marker + * or index can't be written + */ +int +gl_set_pkg_auto(gl_context_t *ctx, const char *repo, const char *name, bool automatic) +{ + if (!ctx || !gl_valid_pkg_ident(repo) || !gl_valid_pkg_ident(name)) { return -1; } + + char vdir[PATH_MAX], dirname[NAME_MAX + 1]; + if (find_version_dir(ctx->store_path, repo, name, vdir, sizeof(vdir), + dirname, sizeof(dirname)) != 0) { + return -1; + } + + char marker[PATH_MAX]; + snprintf(marker, sizeof(marker), "%s/" GL_AUTO_MARKER, vdir); + if (automatic) { + int fd = open(marker, O_WRONLY | O_CREAT | O_NOFOLLOW, 0600); + if (fd < 0) { return -1; } + close(fd); + } else if (unlink(marker) != 0 && errno != ENOENT) { + return -1; + } + + return gl_rebuild_index(ctx) == 0 ? 0 : -1; +} + /* * Archive the installed version of repo/name back into a .gpkg at * out_path (same layout gl_install_pkg reads: NAME-VER/manifest.gpm.cfg @@ -1026,67 +959,6 @@ gl_backup_istore(const char *tar_path, return GL_BACKUP_OK; } -static int -gl_unlink_pkg(gl_context_t *ctx, const char *pkg_store_final, const char *pkg_repo) -{ - char links_base[PATH_MAX]; - if (ctx->scope == GL_SCOPE_SYS && strcmp(pkg_repo, GL_BASE_REPO) == 0) { - snprintf(links_base, sizeof(links_base), "%s", GL_BASE_LINKS_DEST); - } else { - snprintf(links_base, sizeof(links_base), "%s", ctx->links_path); - } - - size_t strip_len = strlen(pkg_store_final); - - char *stack[4096]; - int top = 0; - stack[top++] = strdup(pkg_store_final); - - while (top > 0) { - char * current = stack[--top]; - - DIR *dir = opendir(current); - if (!dir) { - free(current); - continue; - } - - struct dirent *ent; - while ((ent = readdir(dir))) { - if (ent->d_name[0] == '.') { continue; } - - char full_path[PATH_MAX]; - snprintf(full_path, sizeof(full_path), "%s/%s", - current, ent->d_name); - - struct stat st; - if (lstat(full_path, &st) != 0) { continue; } - const char *rel = full_path + strip_len; - if (rel[0] == '/') { rel++; } - - if (strncmp(rel, "usr/", 4) == 0) { rel += 4; } - - char link_path[PATH_MAX]; - snprintf(link_path, sizeof(link_path), "%s/%s", - links_base, rel); - - if (S_ISDIR(st.st_mode)) { - if (top < 4096) { - stack[top++] = strdup(full_path); - } - } - else if (S_ISREG(st.st_mode)) { - unlink(link_path); - } - } - - closedir(dir); - free(current); - } - - return 0; -} - gl_remove_status_t gl_remove_pkg(gl_context_t *ctx, const char *pkg_name, const char *pkg_repo) { @@ -1109,6 +981,16 @@ gl_remove_pkg(gl_context_t *ctx, const char *pkg_name, const char *pkg_repo) return GL_REMOVE_ERR_NOT_FOUND; } + /* Live context: no commit will check this (see gl_install_pkg). */ + if (ctx->mode == GL_CTX_LIVE) { + char id[2 * NAME_MAX + 3]; + snprintf(id, sizeof(id), "%s::%s", pkg_repo, pkg_name); + const char *ids = id; + if (gl_check_plan(ctx, NULL, 0, &ids, 1) != 0) { + return GL_REMOVE_ERR_DEPS; + } + } + /* find the versioned subdirectory */ DIR *dir = opendir(pkg_store_base); if (!dir) { return GL_REMOVE_ERR_NOT_FOUND; } @@ -1127,16 +1009,6 @@ gl_remove_pkg(gl_context_t *ctx, const char *pkg_name, const char *pkg_repo) return GL_REMOVE_ERR_NOT_FOUND; } - /* remove symlinks from links tree. GL_CTX_STAGE: skip — the - * stage links tree isn't seeded (links are fully derived from - * the store post-commit by relink_store), so there's nothing - * there to unlink. */ - if (ctx->mode == GL_CTX_LIVE) { - if (gl_unlink_pkg(ctx, pkg_store_final, pkg_repo) != 0) { - return GL_REMOVE_ERR_UNLINK; - } - } - /* remove package from store */ if (gl_rmdir_recursive(pkg_store_base) != 0) { return GL_REMOVE_ERR_UNLINK; @@ -1147,6 +1019,16 @@ gl_remove_pkg(gl_context_t *ctx, const char *pkg_name, const char *pkg_repo) return GL_REMOVE_ERR_INDEX; } + /* Live context: drop the package's links the way a commit does, + * by pruning glacier's now-stale links (which also restores what + * -O displaced, and handles /etc). This used to unlink() each of + * the package's destinations unchecked, deleting foreign files + * the package had been refused to replace (CLAUDE.md bug 26). + * GL_CTX_STAGE: nothing to do, links are derived after commit. */ + if (ctx->mode == GL_CTX_LIVE && gl_relink_store(ctx) != 0) { + return GL_REMOVE_ERR_UNLINK; + } + return GL_REMOVE_OK; } @@ -1170,11 +1052,10 @@ gl_remove_pkg(gl_context_t *ctx, const char *pkg_name, const char *pkg_repo) * anything on its own. */ #define GL_DISPLACED_DIR ".displaced" -#define GL_OVERWRITE_MARKER ".glacier-overwrite" -/* Same one-shot mechanism, for every install: marks a package as new in - * this transaction, so its link conflicts are reported once, when it's - * installed — not again on every later commit's full relink. */ -#define GL_FRESH_MARKER ".glacier-fresh" +/* GL_FRESH_MARKER (common.h) is the same one-shot mechanism, for every + * install: marks a package as new in this transaction, so its link + * conflicts are reported once, when it's installed — not again on every + * later commit's full relink. */ static int displaced_path(char *buf, size_t bufsz, const char *store_path, const char *path) @@ -1266,8 +1147,10 @@ move_path(const char *src, const char *dst) * untouched) for anything but a regular file or symlink, and if a * displaced original for this path is already being kept — never * overwrite the one copy of something we promised to put back. */ +/* `path` is an install path; its backup is keyed by its RUNTIME path, so + * an image's .displaced means the same thing once the image is booted. */ static int -displace_path(const char *store_path, const char *path, const struct stat *st) +displace_path(const gl_context_t *ctx, const char *path, const struct stat *st) { if (!S_ISREG(st->st_mode) && !S_ISLNK(st->st_mode)) { lg_printf(1, "Refusing to overwrite %s: not a regular file or symlink", path); @@ -1275,7 +1158,8 @@ displace_path(const char *store_path, const char *path, const struct stat *st) } char backup[PATH_MAX]; - if (displaced_path(backup, sizeof(backup), store_path, path) != 0) { + if (displaced_path(backup, sizeof(backup), ctx->store_path, + gl_runtime_path(ctx, path)) != 0) { lg_printf(1, "Refusing to overwrite %s: path too long", path); return -1; } @@ -1310,10 +1194,12 @@ displace_path(const char *store_path, const char *path, const struct stat *st) * Returns 1 if restored, 0 if there was nothing to restore, -1 on * failure (backup left where it is). */ static int -restore_displaced(const char *store_path, const char *path) +restore_displaced(const gl_context_t *ctx, const char *path) { + const char *store_path = ctx->store_path; char backup[PATH_MAX]; - if (displaced_path(backup, sizeof(backup), store_path, path) != 0) { return 0; } + if (displaced_path(backup, sizeof(backup), store_path, + gl_runtime_path(ctx, path)) != 0) { return 0; } struct stat bst; if (lstat(backup, &bst) != 0) { return 0; } @@ -1429,7 +1315,7 @@ static int install_config(gl_context_t *ctx, const char *src, const char *rel) { char target[PATH_MAX], record[PATH_MAX], offer[PATH_MAX]; - snprintf(target, sizeof(target), GL_SYS_ETC "/%s", rel); + snprintf(target, sizeof(target), "%s" GL_SYS_ETC "/%s", ctx->sysroot, rel); snprintf(record, sizeof(record), "%s/" GL_ETC_STATE_DIR "/%s", ctx->store_path, rel); snprintf(offer, sizeof(offer), "%s.new", target); @@ -1517,7 +1403,8 @@ store_provides_config(const char *store, const char *rel) /* Walk the records; for any file no package ships any more, remove it * from /etc if the admin never changed it, keep it if they did. */ static void -prune_config_dir(const char *store, const char *state_root, const char *sub) +prune_config_dir(const char *store, const char *etc, const char *state_root, + const char *sub) { char dir[PATH_MAX]; snprintf(dir, sizeof(dir), "%s%s%s", state_root, sub[0] ? "/" : "", sub); @@ -1536,7 +1423,7 @@ prune_config_dir(const char *store, const char *state_root, const char *sub) struct stat st; if (lstat(record, &st) != 0) { continue; } if (S_ISDIR(st.st_mode)) { - prune_config_dir(store, state_root, rel); + prune_config_dir(store, etc, state_root, rel); rmdir(record); /* only succeeds once empty */ continue; } @@ -1544,7 +1431,7 @@ prune_config_dir(const char *store, const char *state_root, const char *sub) if (store_provides_config(store, rel)) { continue; } char target[PATH_MAX]; - snprintf(target, sizeof(target), GL_SYS_ETC "/%s", rel); + snprintf(target, sizeof(target), "%s/%s", etc, rel); if (config_equal(target, record)) { unlink(target); } else if (lstat(target, &st) == 0) { @@ -1560,15 +1447,22 @@ prune_config(gl_context_t *ctx) { char state_root[PATH_MAX]; snprintf(state_root, sizeof(state_root), "%s/" GL_ETC_STATE_DIR, ctx->store_path); - prune_config_dir(ctx->store_path, state_root, ""); + char etc[PATH_MAX]; + snprintf(etc, sizeof(etc), "%s" GL_SYS_ETC, ctx->sysroot); + prune_config_dir(ctx->store_path, etc, state_root, ""); } static int gl_link_pkg(gl_context_t *ctx, const char *pkg_store_final, const char *pkg_repo) { + /* Install paths for everything touched on disk; the RUNTIME store + * path for what a link says and for recognising glacier's links + * (see gl_context_t.sysroot). */ + const char *store_rt = gl_runtime_path(ctx, ctx->store_path); char links_base[PATH_MAX]; if (ctx->scope == GL_SCOPE_SYS && strcmp(pkg_repo, GL_BASE_REPO) == 0) { - snprintf(links_base, sizeof(links_base), "%s", GL_BASE_LINKS_DEST); + snprintf(links_base, sizeof(links_base), "%s%s", ctx->sysroot, + GL_BASE_LINKS_DEST); } else { snprintf(links_base, sizeof(links_base), "%s", ctx->links_path); } @@ -1646,15 +1540,22 @@ gl_link_pkg(gl_context_t *ctx, const char *pkg_store_final, const char *pkg_repo * INSIDE A's store directory. */ struct stat lst; if (lstat(link_path, &lst) == 0 && S_ISLNK(lst.st_mode) && - is_glacier_symlink(link_path, ctx->store_path)) { - lg_printf(1, - "Refusing to merge directory %s into another package's symlink", - link_path); + is_glacier_symlink(link_path, store_rt)) { + /* Reported (and counted) once, by + * the transaction that installs + * this package, like the refusal + * below (bug 21). */ + if (fresh) { + lg_printf(1, + "Refusing to merge directory %s into another package's symlink", + link_path); + ctx->link_refusals++; + } continue; } /* create the directory in the links tree */ - gl_mkdirp(link_path, 0700); + gl_mkdirp(link_path, gl_scope_dir_mode(ctx->scope)); /* push onto stack to recurse into */ if (top < 4096) { @@ -1675,7 +1576,7 @@ gl_link_pkg(gl_context_t *ctx, const char *pkg_store_final, const char *pkg_repo char *slash = strrchr(parent, '/'); if (slash) { *slash = '\0'; - gl_mkdirp(parent, 0700); + gl_mkdirp(parent, gl_scope_dir_mode(ctx->scope)); } /* Only ever replace an existing entry if it's a @@ -1688,16 +1589,20 @@ gl_link_pkg(gl_context_t *ctx, const char *pkg_store_final, const char *pkg_repo bool was_displaced = false; struct stat existing_st; if (lstat(link_path, &existing_st) == 0 && - !is_glacier_symlink(link_path, ctx->store_path)) { + !is_glacier_symlink(link_path, store_rt)) { if (!overwrite) { if (fresh) { lg_printf(1, "Refusing to overwrite non-glacier path: %s", link_path); + ctx->link_refusals++; } may_link = false; - } else if (displace_path(ctx->store_path, link_path, + } else if (displace_path(ctx, link_path, &existing_st) != 0) { + /* only with the -O marker, + * i.e. a fresh package */ + ctx->link_refusals++; may_link = false; } else { was_displaced = true; @@ -1708,10 +1613,11 @@ gl_link_pkg(gl_context_t *ctx, const char *pkg_store_final, const char *pkg_repo if (may_link) { unlink(link_path); - /* symlink store path -> links tree */ - if (symlink(full_path, link_path) != 0) { + /* symlink store path -> links tree; the + * target is the runtime path */ + if (symlink(gl_runtime_path(ctx, full_path), link_path) != 0) { if (was_displaced) { - restore_displaced(ctx->store_path, link_path); + restore_displaced(ctx, link_path); } closedir(dir); free(current); @@ -1730,8 +1636,8 @@ gl_link_pkg(gl_context_t *ctx, const char *pkg_store_final, const char *pkg_repo if (displaced > 0) { const char *pkg_label = strrchr(pkg_store_final, '/'); - lg_printf(0, "%s: overwrote %d existing path%s; originals kept under %s/" - GL_DISPLACED_DIR " and restored on removal", + lg_printf(0, "%s: displaced %d existing path%s to %s" + GL_DISPLACED_DIR, pkg_label ? pkg_label + 1 : pkg_store_final, displaced, displaced == 1 ? "" : "s", ctx->store_path); } @@ -1771,38 +1677,11 @@ gl_delete_user(uid_t uid, bool isVerbose) return 0; } -char -*gl_find_pkg_repo(gl_context_t *ctx, const char *pkg_name) -{ - char store_path[PATH_MAX]; - snprintf(store_path, sizeof(store_path), "%s", ctx->store_path); - - DIR *uid_dir = opendir(store_path); - if (!uid_dir) { return NULL; } - - struct dirent *repo_entry; - while ((repo_entry = readdir(uid_dir)) != NULL) { - if (repo_entry->d_name[0] == '.') { continue; } - - char pkg_path[PATH_MAX]; - snprintf(pkg_path, sizeof(pkg_path), "%s/%s/%s", store_path, repo_entry->d_name, pkg_name); - - struct stat st; - if (stat(pkg_path, &st) == 0 && S_ISDIR(st.st_mode)) { - char *repo = strdup(repo_entry->d_name); - closedir(uid_dir); - return repo; - } - } - - closedir(uid_dir); - return NULL; -} - gl_repo_list_t gl_find_pkg_repos(gl_context_t *ctx, const char *pkg_name) { gl_repo_list_t result = { NULL, 0 }; + if (!ctx || !gl_valid_pkg_ident(pkg_name)) { return result; } char store_path[PATH_MAX]; snprintf(store_path, sizeof(store_path), "%s", ctx->store_path); @@ -1843,80 +1722,6 @@ gl_free_repo_list(gl_repo_list_t *list) list->count = 0; } -static bool is_safe_rel_path(const char *rel); - -gl_restore_status_t -gl_restore_istore(const char *tar_path, const char *glacier_root, const char *uid) -{ - struct archive *a = archive_read_new(); - archive_read_support_filter_gzip(a); - archive_read_support_format_tar(a); - - if (archive_read_open_filename(a, tar_path, 10240) != ARCHIVE_OK) { - archive_read_free(a); - return GL_RESTORE_ERR_OPEN; - } - - struct archive_entry *entry; - while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { - const char *rel = archive_entry_pathname(entry); - - /* Entries are written as "/{index,store,links}/..." - * by gl_backup_istore, so they join onto glacier_root - * directly (joining "//" doubled the uid). - * Only accept this uid's own subtree, with no traversal: - * the archive is just a file someone hands us. */ - size_t uid_len = strlen(uid); - if (strncmp(rel, uid, uid_len) != 0 || rel[uid_len] != '/' || - !is_safe_rel_path(rel)) { - archive_read_free(a); - return GL_RESTORE_ERR_EXTRACT; - } - - char dest[PATH_MAX]; - snprintf(dest, sizeof(dest), "%s/%s", glacier_root, rel); - - archive_entry_set_pathname(entry, dest); - - if (archive_entry_filetype(entry) == AE_IFDIR) { - gl_mkdirp(dest, archive_entry_perm(entry)); - archive_read_data_skip(a); - continue; - } - - /* create parent dirs */ - char parent[PATH_MAX]; - strncpy(parent, dest, sizeof(parent)); - char *slash = strrchr(parent, '/'); - if (slash) { - *slash = '\0'; - gl_mkdirp(parent, 0700); - } - - int fd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, - archive_entry_perm(entry)); - if (fd < 0) { - archive_read_free(a); - return GL_RESTORE_ERR_EXTRACT; - } - - char buf[8192]; - ssize_t n; - while ((n = archive_read_data(a, buf, sizeof(buf))) > 0) { - ssize_t written = write(fd, buf, (size_t)n); - if (written < 0 || written != n) { - close(fd); - archive_read_free(a); - return GL_RESTORE_ERR_EXTRACT; - } - } - close(fd); - } - - archive_read_free(a); - return GL_RESTORE_OK; -} - /* * A tar entry path (after the "-/files/" prefix is stripped) is * safe to join onto the package's store directory only if it is relative @@ -1965,16 +1770,16 @@ crosses_symlink(const char *base, const char *path) return false; } -gl_install_status_t -gl_install_pkg(gl_context_t *ctx, const char *gpkg_path) +/* + * gl_read_pkg_manifest + * DESCRIPTION: parses the manifest.gpm.cfg inside a .gpkg into *pkg + * (which must be zeroed; free it with gl_free_pkg on success). + * RETURNS: GL_INSTALL_OK, GL_INSTALL_ERR_OPEN (archive unreadable) or + * GL_INSTALL_ERR_MANIFEST (missing, unparseable, or unsafe name/repo) + */ +int +gl_read_pkg_manifest(const char *gpkg_path, gl_pkg_t *pkg) { - if (!ctx) { return GL_INSTALL_ERR_OPEN; } - char original_cwd[PATH_MAX]; - if (getcwd(original_cwd, sizeof(original_cwd)) == NULL) { - return GL_INSTALL_ERR_OPEN; - } - - /* The archive is opened and the manifest is parsed here */ struct archive *a = archive_read_new(); archive_read_support_filter_gzip(a); archive_read_support_format_tar(a); @@ -1985,9 +1790,7 @@ gl_install_pkg(gl_context_t *ctx, const char *gpkg_path) } struct archive_entry *entry; - gl_pkg_t pkg = {0}; bool manifest_found = false; - while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { const char *path = archive_entry_pathname(entry); @@ -2017,11 +1820,11 @@ gl_install_pkg(gl_context_t *ctx, const char *gpkg_path) close(fd); - int rc = gl_gpm2gpkg(tmp_manifest, &pkg); + int rc = gl_gpm2gpkg(tmp_manifest, pkg); if (rc != 0) { if (rc == EINVAL) { lg_printf(2, - "Manifest PKG_NAME/PKG_REPO missing or not a safe identifier ([A-Za-z0-9._-])."); + "Manifest has a missing or unsafe name ([A-Za-z0-9._-] only)."); } unlink(tmp_manifest); archive_read_free(a); @@ -2042,6 +1845,20 @@ gl_install_pkg(gl_context_t *ctx, const char *gpkg_path) return GL_INSTALL_ERR_MANIFEST; } + return GL_INSTALL_OK; +} + +gl_install_status_t +gl_install_pkg(gl_context_t *ctx, const char *gpkg_path) +{ + if (!ctx) { return GL_INSTALL_ERR_OPEN; } + + gl_pkg_t pkg = {0}; + int mrc = gl_read_pkg_manifest(gpkg_path, &pkg); + if (mrc != GL_INSTALL_OK) { + return (gl_install_status_t)mrc; + } + /* * Base-repo packages can NEVER be installed under per-user scope. * Enforced here, at the library level, rather than in any CLI: the @@ -2062,7 +1879,18 @@ gl_install_pkg(gl_context_t *ctx, const char *gpkg_path) gl_free_pkg(&pkg); return GL_INSTALL_ERR_SCOPE; } - + + /* A live context has no commit to check it (gl_check_txn), so the + * dependency rules are applied here instead. Staged installs are + * checked as a whole at commit: a batch may satisfy itself. */ + if (ctx->mode == GL_CTX_LIVE) { + const char *self = gpkg_path; + if (gl_check_plan(ctx, &self, 1, NULL, 0) != 0) { + gl_free_pkg(&pkg); + return GL_INSTALL_ERR_DEPS; + } + } + char pkg_store_base[PATH_MAX]; char pkg_store_final[PATH_MAX]; @@ -2074,12 +1902,20 @@ gl_install_pkg(gl_context_t *ctx, const char *gpkg_path) "%s/%s-%d.%d.%d", pkg_store_base, pkg.pkg_name, pkg.pkg_ver.maj, pkg.pkg_ver.min, pkg.pkg_ver.pat); - if (gl_mkdirp(pkg_store_base, 0700) == -1) { + /* store/, store// and the version dir get the + * scope's mode explicitly, whatever the umask: in system scope + * every user must be able to reach what /usr links to. */ + mode_t dir_mode = gl_scope_dir_mode(ctx->scope); + char pkg_repo_dir[PATH_MAX]; + snprintf(pkg_repo_dir, sizeof(pkg_repo_dir), "%s/%s", ctx->store_path, pkg.pkg_repo); + + if (gl_mkdirp(pkg_store_base, dir_mode) == -1 || + chmod(pkg_repo_dir, dir_mode) != 0 || chmod(pkg_store_base, dir_mode) != 0) { gl_free_pkg(&pkg); return GL_INSTALL_ERR_EXTRACT; } - if (mkdir(pkg_store_final, 0700) == -1) { + if (mkdir(pkg_store_final, dir_mode) == -1) { if (errno == EEXIST) { gl_free_pkg(&pkg); return GL_INSTALL_ERR_ALREADY_INSTALLED; @@ -2088,8 +1924,14 @@ gl_install_pkg(gl_context_t *ctx, const char *gpkg_path) gl_free_pkg(&pkg); return GL_INSTALL_ERR_EXTRACT; } + if (chmod(pkg_store_final, dir_mode) != 0) { + gl_rmdir_recursive(pkg_store_final); + gl_free_pkg(&pkg); + return GL_INSTALL_ERR_EXTRACT; + } /* now the archive must be reopened in order to extract package files */ + struct archive_entry *entry; struct archive *a2 = archive_read_new(); archive_read_support_filter_gzip(a2); archive_read_support_format_tar(a2); @@ -2164,6 +2006,17 @@ gl_install_pkg(gl_context_t *ctx, const char *gpkg_path) goto extract_fail; } + /* Top-level .glacier-* names are glacier's own files in the + * version dir (GL_MARKER_PREFIX). A package shipping one could + * grant itself -O, rewrite its saved manifest, or, as a + * symlink, redirect glacier's marker write (as root, under + * syspkg) to any file on the system. */ + if (strncmp(rel_path, GL_MARKER_PREFIX, strlen(GL_MARKER_PREFIX)) == 0) { + lg_printf(2, "Refusing archive entry '%s': %s* names are reserved.", + entry_path, GL_MARKER_PREFIX); + goto extract_fail; + } + char dest_path[PATH_MAX]; snprintf(dest_path, sizeof(dest_path), "%s/%s", pkg_store_final, rel_path); @@ -2176,8 +2029,7 @@ gl_install_pkg(gl_context_t *ctx, const char *gpkg_path) switch (archive_entry_filetype(entry)) { case AE_IFDIR: - if (mkdir(dest_path, archive_entry_perm(entry)) == -1 - && errno != EEXIST) { + if (gl_mkdir_exact(dest_path, GL_ENTRY_MODE(entry)) != 0) { goto extract_fail; } break; @@ -2188,12 +2040,16 @@ gl_install_pkg(gl_context_t *ctx, const char *gpkg_path) char *slash = strrchr(parent, '/'); if (slash) { *slash = '\0'; - gl_mkdirp(parent, 0700); + gl_mkdirp(parent, dir_mode); } int fd = open(dest_path, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, - archive_entry_perm(entry)); + GL_ENTRY_MODE(entry)); if (fd < 0) { goto extract_fail; } + if (fchmod(fd, GL_ENTRY_MODE(entry)) != 0) { + close(fd); + goto extract_fail; + } char buffer[8192]; ssize_t n; @@ -2256,7 +2112,7 @@ gl_install_pkg(gl_context_t *ctx, const char *gpkg_path) char *slash = strrchr(parent, '/'); if (slash) { *slash = '\0'; - gl_mkdirp(parent, 0700); + gl_mkdirp(parent, dir_mode); } /* symlink() fails EEXIST rather than replacing: an archive that @@ -2318,7 +2174,7 @@ extract_fail: if (!markers[m]) { continue; } char marker[PATH_MAX]; snprintf(marker, sizeof(marker), "%s/%s", pkg_store_final, markers[m]); - int mfd = open(marker, O_WRONLY | O_CREAT | O_TRUNC, 0600); + int mfd = open(marker, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0600); if (mfd < 0) { gl_free_pkg(&pkg); return GL_INSTALL_ERR_EXTRACT; @@ -2340,7 +2196,6 @@ extract_fail: * incrementally here (see relink_store in transaction.c). */ gl_free_pkg(&pkg); - chdir(original_cwd); return GL_INSTALL_OK; } @@ -2356,8 +2211,14 @@ extract_fail: * holds plenty of content glacier has no business deleting. */ static void -prune_stale_links(const char *dir_path, const char *store_prefix, int *restored) +prune_stale_links(const gl_context_t *ctx, const char *dir_path, int *restored) { + /* Links say runtime paths; their targets are checked under the + * install root. Without that, an image's links would be judged + * against the HOST's store: pruned because the host lacks the + * package, or kept because it has it (IMAGE_DESIGN section 3). */ + const char *store_prefix = gl_runtime_path(ctx, ctx->store_path); + DIR *dir = opendir(dir_path); if (!dir) { return; } @@ -2380,20 +2241,21 @@ prune_stale_links(const char *dir_path, const char *store_prefix, int *restored) * stat() through it: a package's own symlink may * legitimately dangle, and following the chain would * prune (and un-displace) it on every commit. */ - char store_entry[PATH_MAX]; + char store_entry[PATH_MAX], entry_at[PATH_MAX]; ssize_t tn = readlink(full, store_entry, sizeof(store_entry) - 1); struct stat target_st; if (tn >= 0) { store_entry[tn] = '\0'; } - if (tn >= 0 && lstat(store_entry, &target_st) != 0 && errno == ENOENT) { + if (tn >= 0 && gl_at(ctx, store_entry, entry_at, sizeof(entry_at)) == 0 && + lstat(entry_at, &target_st) != 0 && errno == ENOENT) { /* our symlink, but its target is gone — and if * installing it displaced an original, put that back */ if (unlink(full) == 0 && - restore_displaced(store_prefix, full) == 1) { + restore_displaced(ctx, full) == 1) { (*restored)++; } } } else if (S_ISDIR(lst.st_mode)) { - prune_stale_links(full, store_prefix, restored); + prune_stale_links(ctx, full, restored); } /* regular files: never ours, never touched */ } @@ -2413,11 +2275,11 @@ gl_relink_store(gl_context_t *ctx) * glacier's own stale symlinks get removed. gl_mkdirp is * non-destructive (mkdir tolerates EEXIST) so this is still safe * to call even if links_path already exists. */ - if (gl_mkdirp(ctx->links_path, 0700) != 0) { + if (gl_mkdirp(ctx->links_path, gl_scope_dir_mode(ctx->scope)) != 0) { return -1; } int restored = 0; - prune_stale_links(ctx->links_path, ctx->store_path, &restored); + prune_stale_links(ctx, ctx->links_path, &restored); /* Base-repo packages under system scope route to /usr instead of * ctx->links_path (see GL_BASE_REPO) — prune there too, since a @@ -2426,7 +2288,9 @@ gl_relink_store(gl_context_t *ctx) * for system scope; /usr is never a valid destination for * per-uid packages. */ if (ctx->scope == GL_SCOPE_SYS) { - prune_stale_links(GL_BASE_LINKS_DEST, ctx->store_path, &restored); + char usr[PATH_MAX]; + snprintf(usr, sizeof(usr), "%s" GL_BASE_LINKS_DEST, ctx->sysroot); + prune_stale_links(ctx, usr, &restored); prune_config(ctx); } @@ -2484,4 +2348,4 @@ gl_relink_store(gl_context_t *ctx) } closedir(repo_dir); return 0; -} \ No newline at end of file +} diff --git a/src/istoreutils.h b/src/istoreutils.h index 87acd6c..dc878dc 100644 --- a/src/istoreutils.h +++ b/src/istoreutils.h @@ -5,6 +5,7 @@ #include #include +#include "pkg.h" #include "transaction.h" typedef enum { @@ -24,12 +25,6 @@ typedef enum { } gl_backup_status_t; -typedef enum { - GL_RESTORE_OK = 0, - GL_RESTORE_ERR_OPEN = 1, - GL_RESTORE_ERR_EXTRACT = 2 -} gl_restore_status_t; - /* * GL_INSTALL_OK: installation successful * GL_INSTALL_ERR_OPEN: unable to open .gpkg file @@ -37,6 +32,9 @@ typedef enum { * GL_INSTALL_ERR_EXTRACT: unable to extract files * GL_INSTALL_ERR_INDEX: unable to update index * GL_INSTALL_ERR_ALREADY_INSTALLED: package already installed + * GL_INSTALL_ERR_SCOPE: base-repo package under user scope + * GL_INSTALL_ERR_DEPS: unmet dependency or conflict (live context only; + * a staged install is checked at commit, GL_TXN_ERR_DEPS) */ typedef enum { @@ -48,6 +46,7 @@ typedef enum { GL_INSTALL_ERR_ALREADY_INSTALLED = 5, GL_INSTALL_SKIPPED = 6, GL_INSTALL_ERR_SCOPE = 7, + GL_INSTALL_ERR_DEPS = 8, /* live context only; see deps.h */ } gl_install_status_t; @@ -56,39 +55,22 @@ typedef enum { GL_REMOVE_ERR_NOT_FOUND = 1, GL_REMOVE_ERR_UNLINK = 2, GL_REMOVE_ERR_INDEX = 3, + GL_REMOVE_ERR_DEPS = 4, /* live context only; see deps.h */ } gl_remove_status_t; -struct gpkg_entry { - char *repo; - char *pkg; - char *ver; -}; - -struct gindex { - int uid; - int listed; - struct gpkg_entry *entries; - size_t count; -}; - bool gl_usr_istore_exists(index_store_t index_or_store, int uid); -bool gl_sys_istore_exists(index_store_t index_or_store); int gl_usr_base_status(uid_t uid); int gl_backup_pkg(gl_context_t *ctx, const char *repo, const char *name, const char *out_path); int gl_prune_backups(const char *dir, int keep); int gl_init_user(uid_t uid, bool isVerbose); int gl_init_sys(bool isVerbose); +int gl_init_sys_at(const char *sysroot, bool isVerbose); int gl_create_fs_tree(const char *root, bool isVerbose); int gl_delete_user(uid_t uid, bool isVerbose); -int gl_parse_index(struct gindex *idx, FILE *f); -void gl_free_index(struct gindex *idx); - int gl_rebuild_index(gl_context_t *ctx); -char *gl_find_pkg_repo(gl_context_t *ctx, const char *pkg_name); - typedef struct { char **repos; size_t count; @@ -100,9 +82,10 @@ void gl_free_repo_list(gl_repo_list_t *list); gl_backup_status_t gl_backup_istore(const char *tar_path, const char *glacier_root, const char *uid); -gl_restore_status_t gl_restore_istore(const char *tar_path, const char *glacier_root, const char *uid); +int gl_read_pkg_manifest(const char *gpkg_path, gl_pkg_t *pkg); gl_install_status_t gl_install_pkg(gl_context_t *ctx, const char *gpkg_path); +int gl_set_pkg_auto(gl_context_t *ctx, const char *repo, const char *name, bool automatic); gl_remove_status_t gl_remove_pkg(gl_context_t *ctx, const char *pkg_name, const char *pkg_repo); int gl_relink_store(gl_context_t *ctx); diff --git a/src/log.c b/src/log.c index 3cef043..9498327 100644 --- a/src/log.c +++ b/src/log.c @@ -8,19 +8,8 @@ #include #include #include -#include -#include -#include "color.h" - -typedef enum -{ - LG_INFO, - LG_WARN, - LG_ERR, - LG_SUCCESS -} -loglevel_t; +#include "log.h" static int supports_utf8(void) @@ -34,26 +23,6 @@ supports_utf8(void) #endif } - -/* Converts priority integer to string */ -const char -*p2s(int priority) -{ - switch (priority) - { - case LOG_EMERG: return "EMERG"; break; - case LOG_ALERT: return "ALERT"; break; - case LOG_CRIT: return "CRIT"; break; - case LOG_ERR: return "ERR"; break; - case LOG_WARNING: return "WARN"; break; - case LOG_NOTICE: return "NOTICE"; break; - case LOG_INFO: return "INFO"; break; - case LOG_DEBUG: return "DEBUG"; break; - default: return "UNKNOWN"; break; - } -} - - void lg_printf(loglevel_t loglevel, const char *fmt, ...) { @@ -92,104 +61,22 @@ lg_printf(loglevel_t loglevel, const char *fmt, ...) printf("\n"); } -void -lg_printf_no_newline(loglevel_t loglevel, const char *fmt, ...) -{ - const char *prefix; - - int utf8_ok = supports_utf8(); - - switch (loglevel) { - case LG_INFO: - prefix = COL_BLUE "[i]" COL_RESET " "; - break; - case LG_WARN: - prefix = COL_YELLOW "[!]" COL_RESET " "; - break; - case LG_ERR: - prefix = COL_RED "[x]" COL_RESET " "; - break; - case LG_SUCCESS: - if (utf8_ok) { - prefix = COL_GREEN "[" "\xE2\x9C\x93" "]" COL_RESET " "; - } else { - prefix = COL_GREEN "[*]" COL_RESET " "; - } - break; - default: - prefix = "[?] "; - } - - fputs(prefix, stdout); - - va_list args; - va_start(args, fmt); - vprintf(fmt, args); - va_end(args); -} - -void -lg_syslog(int priority, const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - vsyslog(priority, fmt, args); - va_end(args); -} - -void -lg_log(bool use_syslog, const char *logfile, int priority, const char *fmt, ...) -{ - FILE *log = fopen(logfile, "a"); - if (!log) { - perror("fopen"); - return; - } - - time_t now = time(NULL); - struct tm *t = localtime(&now); - - char timestamp[20]; - strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", t); - - fprintf(log, "[%s] [%s] ", timestamp, p2s(priority)); - - va_list args; - va_start(args, fmt); - vfprintf(log, fmt, args); - va_end(args); - - fputc('\n', log); - fflush(log); - - if (use_syslog) { - va_list args2; - va_start(args2, fmt); - vsyslog(priority, fmt, args2); - va_end(args2); - fclose(log); - } - else { - fclose(log); - return; - } -} - void lg_gpkg_help(int argc, char *argv[]) { (void)argc; printf("%s: Install, update, and remove packages\n", argv[0]); - printf("usage: %s [-hv] [-flux PKG] [-OSV]\n", argv[0]); + printf("usage: %s [-hv] [-y] [-flux PKG] [-OSV]\n", argv[0]); printf("\n"); printf("%-25s %s\n", "{-h --help}", "Show this message"); printf("%-25s %s\n", "{-v --version}", "Show the current version"); printf("%-25s %s\n", "{-l --local} FILE...", "Install local package files"); - printf("%-25s %s\n", "{-f --merge} PKG...", "Install packages from a remote repository (not yet implemented)"); - printf("%-25s %s\n", "{-u --update} PKG...", "Upgrade installed packages (not yet implemented)"); + printf("%-25s %s\n", "{-f --merge} PKG...", "Install packages from a remote repository"); + printf("%-25s %s\n", "{-u --update} [PKG...]", "Update system or individual packages"); printf("%-25s %s\n", "{-x --remove} PKG...", "Remove installed packages"); - printf("%-25s %s\n", "{-O --overwrite}", "With -l: replace an installed version (backed up per KEEP_OLD) and existing non-glacier files"); + printf("%-25s %s\n", "{-y --refresh}", "Fetch and verify the remote repositories' indexes"); + printf("%-25s %s\n", "{-O --overwrite}", "Allow Glacier to overwrite existing files"); printf("%-25s %s\n", "{-S --skip-installed}", "Skip packages that are already up to date"); printf("%-25s %s\n", "{-V --verbose}", "Log events more verbosely"); printf("\n"); @@ -203,16 +90,21 @@ lg_syspkg_help(int argc, char *argv[]) (void)argc; printf("%s: Manage the system package index\n", argv[0]); - printf("usage: %s [-hv] [-n] [-lx PKG] [-OSV]\n", argv[0]); + printf("usage: %s [-hv] [-n] [-y] [-flux PKG] [-OSV] [-R DIR] [--strict]\n", argv[0]); printf("\n"); printf("%-25s %s\n", "{-h --help}", "Show this message"); printf("%-25s %s\n", "{-v --version}", "Show the current version"); printf("%-25s %s\n", "{-n --new}", "Create the system index and store"); printf("%-25s %s\n", "{-l --local} PKG...", "Install a local package into the system index and store"); + printf("%-25s %s\n", "{-f --merge} PKG...", "Install packages from a remote repository"); + printf("%-25s %s\n", "{-u --update} [PKG...]", "Update system or individual packages"); printf("%-25s %s\n", "{-x --remove} PKG...", "Remove a package from the system index and store"); - printf("%-25s %s\n", "{-O --overwrite}", "With -l: replace an installed version (backed up per KEEP_OLD) and existing non-glacier files"); + printf("%-25s %s\n", "{-y --refresh}", "Fetch and verify the remote repositories' indexes"); + printf("%-25s %s\n", "{-O --overwrite}", "Allow Glacier to overwrite existing files"); printf("%-25s %s\n", "{-S --skip-installed}", "Skip packages that are already up to date"); printf("%-25s %s\n", "{-V --verbose}", "Log events more verbosely"); + printf("%-25s %s\n", "{-R --root} DIR", "Operate on the system installed under DIR (implies --strict)"); + printf("%-25s %s\n", "{--strict}", "Refuse the whole transaction if any path can't be linked"); printf("\n"); printf("This program is free software.\n"); printf("See the GNU GPL version 3 for details.\n"); diff --git a/src/log.h b/src/log.h index 0521ae1..f66eb7c 100644 --- a/src/log.h +++ b/src/log.h @@ -7,9 +7,6 @@ #include #include -#include -#include -#include #include "color.h" @@ -26,13 +23,13 @@ typedef enum } loglevel_t; -int supports_utf8(void); -const char *p2s(int priority); +void lg_printf(loglevel_t loglevel, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); -void lg_printf(loglevel_t loglevel, const char *fmt, ...); -void lg_printf_no_newline(loglevel_t loglevel, const char *fmt, ...); -void lg_syslog(int priority, const char *fmt, ...); -void lg_log(bool use_syslog, FILE *logfile, int priority, const char *fmt, ...); +/* The CLIs' -h output. */ +void lg_gpkg_help(int argc, char *argv[]); +void lg_syspkg_help(int argc, char *argv[]); +void lg_gstore_help(int argc, char *argv[]); #ifdef __cplusplus } diff --git a/src/make_conf.c b/src/make_conf.c deleted file mode 100644 index 985622b..0000000 --- a/src/make_conf.c +++ /dev/null @@ -1,95 +0,0 @@ -#define _POSIX_C_SOURcE 200809L -#define _DEFAULT_SOURCE -#define _XOPEN_SOURCE 700 - -#include -#include -#include -#include - -#define MAX_LINE 1024 - -static char -*trim(char *s) { - char *end; - while (isspace((unsigned char)*s)) { s++; } - if (*s == 0) { return s; } - end = s + strlen(s) - 1; - while (end > s && isspace((unsigned char)*end)) { *end-- = 0; } - return s; -} - -static char -*expand(char *value) -{ - size_t cap = strlen(value) + 1; - char *buffer = malloc(cap); - if (!buffer) { return NULL; } - char *out = buffer; - - char *p = value; - - while (*p) { - if (*p == '$') { - p++; - char var[128], *vp = var; - while (*p && (isalnum((unsigned char)*p) || *p == '_')) { - *vp++ = *p++; - } - - *vp = '\0'; - char *val = getenv(var); - if (val) { - size_t len = strlen(val); - size_t used = (size_t)(out - buffer); - if (used + len + 1 > cap) { - cap = used + len + 1; - buffer = realloc(buffer, cap); - if (!buffer) { return NULL; } - out = buffer + used; - } - - memcpy(out, val, len); - out += len; - } - } - else { - *out++ = *p++; - } - } - - *out = '\0'; - return buffer; -} - -int -parse_makeconf(const char *filename) -{ - FILE *make_conf = fopen(filename, "r"); - if (!make_conf) { - perror("fopen"); - return -1; - } - - char line[MAX_LINE]; - while (fgets(line, sizeof(line), make_conf)) { - char *p = trim(line); - if (*p == '\0' || *p == '#') { continue; } - - char *eq = strchr(p, '='); - if (!eq) { continue; } - - *eq = '\0'; - char *key = trim(p); - char *val = trim(eq + 1); - - char *expanded = expand(val); - if (expanded) { - setenv(key, expanded, 1); - free(expanded); - } - } - - fclose(make_conf); - return 0; -} diff --git a/src/monocypher/LICENCE.md b/src/monocypher/LICENCE.md new file mode 100644 index 0000000..9c3fae9 --- /dev/null +++ b/src/monocypher/LICENCE.md @@ -0,0 +1,167 @@ +Monocypher as a whole is dual-licensed. Choose whichever licence you +want from the two licences listed below. + +The first licence is a regular 2-clause BSD licence. The second licence +is the CC-0 from Creative Commons. It is intended to release Monocypher +to the public domain. The BSD licence serves as a fallback option. + +See the individual files for specific information about who contributed +to what file during which years. See below for special notes. + +Licence 1 (2-clause BSD) +------------------------ + +Copyright (c) 2017-2023, Loup Vaillant +Copyright (c) 2017-2019, Michael Savage +Copyright (c) 2017-2023, Fabio Scotoni +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Licence 2 (CC-0) +---------------- + +> CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE +> LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN +> ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS +> INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES +> REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS +> PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM +> THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED +> HEREUNDER. + +### Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work +of authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without +fear of later claims of infringement build upon, modify, incorporate in +other works, reuse and redistribute as freely as possible in any form +whatsoever and for any purposes, including without limitation commercial +purposes. These owners may contribute to the Commons to promote the +ideal of a free culture and the further production of creative, cultural +and scientific works, or to gain reputation or greater distribution for +their Work in part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or +she is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under +its terms, with knowledge of his or her Copyright and Related Rights in +the Work and the meaning and intended legal effect of CC0 on those +rights. + +1. **Copyright and Related Rights.** A Work made available under CC0 may + be protected by copyright and related or neighboring rights + ("Copyright and Related Rights"). Copyright and Related Rights + include, but are not limited to, the following: + + - the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + - moral rights retained by the original author(s) and/or + performer(s); publicity and privacy rights pertaining to a person's + image or likeness depicted in a Work; + - rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + - rights protecting the extraction, dissemination, use and reuse of + data in a Work; + - database rights (such as those arising under Directive 96/9/EC of + the European Parliament and of the Council of 11 March 1996 on the + legal protection of databases, and under any national + implementation thereof, including any amended or successor version + of such directive); and + - other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. **Waiver.** To the greatest extent permitted by, but not in + contravention of, applicable law, Affirmer hereby overtly, fully, + permanently, irrevocably and unconditionally waives, abandons, and + surrenders all of Affirmer's Copyright and Related Rights and + associated claims and causes of action, whether now known or unknown + (including existing as well as future claims and causes of action), + in the Work (i) in all territories worldwide, (ii) for the maximum + duration provided by applicable law or treaty (including future time + extensions), (iii) in any current or future medium and for any number + of copies, and (iv) for any purpose whatsoever, including without + limitation commercial, advertising or promotional purposes (the + "Waiver"). Affirmer makes the Waiver for the benefit of each member + of the public at large and to the detriment of Affirmer's heirs and + successors, fully intending that such Waiver shall not be subject to + revocation, rescission, cancellation, termination, or any other legal + or equitable action to disrupt the quiet enjoyment of the Work by the + public as contemplated by Affirmer's express Statement of Purpose. + +3. **Public License Fallback.** Should any part of the Waiver for any + reason be judged legally invalid or ineffective under applicable law, + then the Waiver shall be preserved to the maximum extent permitted + taking into account Affirmer's express Statement of Purpose. In + addition, to the extent the Waiver is so judged Affirmer hereby + grants to each affected person a royalty-free, non transferable, non + sublicensable, non exclusive, irrevocable and unconditional license + to exercise Affirmer's Copyright and Related Rights in the Work (i) + in all territories worldwide, (ii) for the maximum duration provided + by applicable law or treaty (including future time extensions), (iii) + in any current or future medium and for any number of copies, and + (iv) for any purpose whatsoever, including without limitation + commercial, advertising or promotional purposes (the "License"). The + License shall be deemed effective as of the date CC0 was applied by + Affirmer to the Work. Should any part of the License for any reason + be judged legally invalid or ineffective under applicable law, such + partial invalidity or ineffectiveness shall not invalidate the + remainder of the License, and in such case Affirmer hereby affirms + that he or she will not (i) exercise any of his or her remaining + Copyright and Related Rights in the Work or (ii) assert any + associated claims and causes of action with respect to the Work, in + either case contrary to Affirmer's express Statement of Purpose. + +4. **Limitations and Disclaimers.** + + - No trademark or patent rights held by Affirmer are waived, + abandoned, surrendered, licensed or otherwise affected by this + document. + - Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, + or the present or absence of errors, whether or not discoverable, + all to the greatest extent permissible under applicable law. + - Affirmer disclaims responsibility for clearing rights of other + persons that may apply to the Work or any use thereof, including + without limitation any person's Copyright and Related Rights in the + Work. Further, Affirmer disclaims responsibility for obtaining any + necessary consents, permissions or other rights required for any + use of the Work. + - Affirmer understands and acknowledges that Creative Commons is not + a party to this document and has no duty or obligation with respect + to this CC0 or use of the Work. diff --git a/src/monocypher/README b/src/monocypher/README new file mode 100644 index 0000000..dd96e6c --- /dev/null +++ b/src/monocypher/README @@ -0,0 +1,14 @@ +Monocypher 4.0.3, vendored UNMODIFIED (REPO_DESIGN.txt section 1a). + + monocypher.c, monocypher.h from src/ + monocypher-ed25519.c, monocypher-ed25519.h from src/optional/ + LICENCE.md BSD-2-Clause or CC0 + +Source: monocypher-4.0.3.tar.gz, fetched 2026-10-06 from both +https://github.com/LoupVaillant/Monocypher/releases/download/4.0.3/ +and https://monocypher.org/download/ ; identical, sha512 +40904ada5c7ee4f7741733e38b69a30a4b0561cbffba5ffe7c2dce16136d540251ec0d9056ff606510d3b5b708fb8a40db7e0870d4a0b2dc17ba2bfb880f8965 + +Only Ed25519 (with its SHA-512) is used, through src/sign.c. Never +edit these files: replace them with a newer release, and update this +note. diff --git a/src/monocypher/monocypher-ed25519.c b/src/monocypher/monocypher-ed25519.c new file mode 100644 index 0000000..8619117 --- /dev/null +++ b/src/monocypher/monocypher-ed25519.c @@ -0,0 +1,503 @@ +// Monocypher version 4.0.3 +// +// This file is dual-licensed. Choose whichever licence you want from +// the two licences listed below. +// +// The first licence is a regular 2-clause BSD licence. The second licence +// is the CC-0 from Creative Commons. It is intended to release Monocypher +// to the public domain. The BSD licence serves as a fallback option. +// +// SPDX-License-Identifier: BSD-2-Clause OR CC0-1.0 +// +// ------------------------------------------------------------------------ +// +// Copyright (c) 2017-2019, Loup Vaillant +// All rights reserved. +// +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ------------------------------------------------------------------------ +// +// Written in 2017-2019 by Loup Vaillant +// +// To the extent possible under law, the author(s) have dedicated all copyright +// and related neighboring rights to this software to the public domain +// worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication along +// with this software. If not, see +// + +#include "monocypher-ed25519.h" + +#ifdef MONOCYPHER_CPP_NAMESPACE +namespace MONOCYPHER_CPP_NAMESPACE { +#endif + +///////////////// +/// Utilities /// +///////////////// +#define FOR(i, min, max) for (size_t i = min; i < max; i++) +#define COPY(dst, src, size) FOR(_i_, 0, size) (dst)[_i_] = (src)[_i_] +#define ZERO(buf, size) FOR(_i_, 0, size) (buf)[_i_] = 0 +#define WIPE_CTX(ctx) crypto_wipe(ctx , sizeof(*(ctx))) +#define WIPE_BUFFER(buffer) crypto_wipe(buffer, sizeof(buffer)) +#define MIN(a, b) ((a) <= (b) ? (a) : (b)) +typedef uint8_t u8; +typedef uint64_t u64; + +// Returns the smallest positive integer y such that +// (x + y) % pow_2 == 0 +// Basically, it's how many bytes we need to add to "align" x. +// Only works when pow_2 is a power of 2. +// Note: we use ~x+1 instead of -x to avoid compiler warnings +static size_t align(size_t x, size_t pow_2) +{ + return (~x + 1) & (pow_2 - 1); +} + +static u64 load64_be(const u8 s[8]) +{ + return((u64)s[0] << 56) + | ((u64)s[1] << 48) + | ((u64)s[2] << 40) + | ((u64)s[3] << 32) + | ((u64)s[4] << 24) + | ((u64)s[5] << 16) + | ((u64)s[6] << 8) + | (u64)s[7]; +} + +static void store64_be(u8 out[8], u64 in) +{ + out[0] = (u8)(in >> 56); + out[1] = (u8)(in >> 48); + out[2] = (u8)(in >> 40); + out[3] = (u8)(in >> 32); + out[4] = (u8)(in >> 24); + out[5] = (u8)(in >> 16); + out[6] = (u8)(in >> 8); + out[7] = (u8) in ; +} + +static void load64_be_buf (u64 *dst, const u8 *src, size_t size) { + FOR(i, 0, size) { dst[i] = load64_be(src + i*8); } +} + +/////////////// +/// SHA 512 /// +/////////////// +static u64 rot(u64 x, int c ) { return (x >> c) | (x << (64 - c)); } +static u64 ch (u64 x, u64 y, u64 z) { return (x & y) ^ (~x & z); } +static u64 maj(u64 x, u64 y, u64 z) { return (x & y) ^ ( x & z) ^ (y & z); } +static u64 big_sigma0(u64 x) { return rot(x, 28) ^ rot(x, 34) ^ rot(x, 39); } +static u64 big_sigma1(u64 x) { return rot(x, 14) ^ rot(x, 18) ^ rot(x, 41); } +static u64 lit_sigma0(u64 x) { return rot(x, 1) ^ rot(x, 8) ^ (x >> 7); } +static u64 lit_sigma1(u64 x) { return rot(x, 19) ^ rot(x, 61) ^ (x >> 6); } + +static const u64 K[80] = { + 0x428a2f98d728ae22,0x7137449123ef65cd,0xb5c0fbcfec4d3b2f,0xe9b5dba58189dbbc, + 0x3956c25bf348b538,0x59f111f1b605d019,0x923f82a4af194f9b,0xab1c5ed5da6d8118, + 0xd807aa98a3030242,0x12835b0145706fbe,0x243185be4ee4b28c,0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f,0x80deb1fe3b1696b1,0x9bdc06a725c71235,0xc19bf174cf692694, + 0xe49b69c19ef14ad2,0xefbe4786384f25e3,0x0fc19dc68b8cd5b5,0x240ca1cc77ac9c65, + 0x2de92c6f592b0275,0x4a7484aa6ea6e483,0x5cb0a9dcbd41fbd4,0x76f988da831153b5, + 0x983e5152ee66dfab,0xa831c66d2db43210,0xb00327c898fb213f,0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2,0xd5a79147930aa725,0x06ca6351e003826f,0x142929670a0e6e70, + 0x27b70a8546d22ffc,0x2e1b21385c26c926,0x4d2c6dfc5ac42aed,0x53380d139d95b3df, + 0x650a73548baf63de,0x766a0abb3c77b2a8,0x81c2c92e47edaee6,0x92722c851482353b, + 0xa2bfe8a14cf10364,0xa81a664bbc423001,0xc24b8b70d0f89791,0xc76c51a30654be30, + 0xd192e819d6ef5218,0xd69906245565a910,0xf40e35855771202a,0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8,0x1e376c085141ab53,0x2748774cdf8eeb99,0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63,0x4ed8aa4ae3418acb,0x5b9cca4f7763e373,0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc,0x78a5636f43172f60,0x84c87814a1f0ab72,0x8cc702081a6439ec, + 0x90befffa23631e28,0xa4506cebde82bde9,0xbef9a3f7b2c67915,0xc67178f2e372532b, + 0xca273eceea26619c,0xd186b8c721c0c207,0xeada7dd6cde0eb1e,0xf57d4f7fee6ed178, + 0x06f067aa72176fba,0x0a637dc5a2c898a6,0x113f9804bef90dae,0x1b710b35131c471b, + 0x28db77f523047d84,0x32caab7b40c72493,0x3c9ebe0a15c9bebc,0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6,0x597f299cfc657e2a,0x5fcb6fab3ad6faec,0x6c44198c4a475817 +}; + +static void sha512_compress(crypto_sha512_ctx *ctx) +{ + u64 a = ctx->hash[0]; u64 b = ctx->hash[1]; + u64 c = ctx->hash[2]; u64 d = ctx->hash[3]; + u64 e = ctx->hash[4]; u64 f = ctx->hash[5]; + u64 g = ctx->hash[6]; u64 h = ctx->hash[7]; + + FOR (j, 0, 16) { + u64 in = K[j] + ctx->input[j]; + u64 t1 = big_sigma1(e) + ch (e, f, g) + h + in; + u64 t2 = big_sigma0(a) + maj(a, b, c); + h = g; g = f; f = e; e = d + t1; + d = c; c = b; b = a; a = t1 + t2; + } + size_t i16 = 0; + FOR(i, 1, 5) { + i16 += 16; + FOR (j, 0, 16) { + ctx->input[j] += lit_sigma1(ctx->input[(j- 2) & 15]); + ctx->input[j] += lit_sigma0(ctx->input[(j-15) & 15]); + ctx->input[j] += ctx->input[(j- 7) & 15]; + u64 in = K[i16 + j] + ctx->input[j]; + u64 t1 = big_sigma1(e) + ch (e, f, g) + h + in; + u64 t2 = big_sigma0(a) + maj(a, b, c); + h = g; g = f; f = e; e = d + t1; + d = c; c = b; b = a; a = t1 + t2; + } + } + + ctx->hash[0] += a; ctx->hash[1] += b; + ctx->hash[2] += c; ctx->hash[3] += d; + ctx->hash[4] += e; ctx->hash[5] += f; + ctx->hash[6] += g; ctx->hash[7] += h; +} + +// Write 1 input byte +static void sha512_set_input(crypto_sha512_ctx *ctx, u8 input) +{ + size_t word = ctx->input_idx >> 3; + size_t byte = ctx->input_idx & 7; + ctx->input[word] |= (u64)input << (8 * (7 - byte)); +} + +// Increment a 128-bit "word". +static void sha512_incr(u64 x[2], u64 y) +{ + x[1] += y; + if (x[1] < y) { + x[0]++; + } +} + +void crypto_sha512_init(crypto_sha512_ctx *ctx) +{ + ctx->hash[0] = 0x6a09e667f3bcc908; + ctx->hash[1] = 0xbb67ae8584caa73b; + ctx->hash[2] = 0x3c6ef372fe94f82b; + ctx->hash[3] = 0xa54ff53a5f1d36f1; + ctx->hash[4] = 0x510e527fade682d1; + ctx->hash[5] = 0x9b05688c2b3e6c1f; + ctx->hash[6] = 0x1f83d9abfb41bd6b; + ctx->hash[7] = 0x5be0cd19137e2179; + ctx->input_size[0] = 0; + ctx->input_size[1] = 0; + ctx->input_idx = 0; + ZERO(ctx->input, 16); +} + +void crypto_sha512_update(crypto_sha512_ctx *ctx, + const u8 *message, size_t message_size) +{ + // Avoid undefined NULL pointer increments with empty messages + if (message_size == 0) { + return; + } + + // Align ourselves with word boundaries + if ((ctx->input_idx & 7) != 0) { + size_t nb_bytes = MIN(align(ctx->input_idx, 8), message_size); + FOR (i, 0, nb_bytes) { + sha512_set_input(ctx, message[i]); + ctx->input_idx++; + } + message += nb_bytes; + message_size -= nb_bytes; + } + + // Align ourselves with block boundaries + if ((ctx->input_idx & 127) != 0) { + size_t nb_words = MIN(align(ctx->input_idx, 128), message_size) >> 3; + load64_be_buf(ctx->input + (ctx->input_idx >> 3), message, nb_words); + ctx->input_idx += nb_words << 3; + message += nb_words << 3; + message_size -= nb_words << 3; + } + + // Compress block if needed + if (ctx->input_idx == 128) { + sha512_incr(ctx->input_size, 1024); // size is in bits + sha512_compress(ctx); + ctx->input_idx = 0; + ZERO(ctx->input, 16); + } + + // Process the message block by block + FOR (i, 0, message_size >> 7) { // number of blocks + load64_be_buf(ctx->input, message, 16); + sha512_incr(ctx->input_size, 1024); // size is in bits + sha512_compress(ctx); + ctx->input_idx = 0; + ZERO(ctx->input, 16); + message += 128; + } + message_size &= 127; + + if (message_size != 0) { + // Remaining words + size_t nb_words = message_size >> 3; + load64_be_buf(ctx->input, message, nb_words); + ctx->input_idx += nb_words << 3; + message += nb_words << 3; + message_size -= nb_words << 3; + + // Remaining bytes + FOR (i, 0, message_size) { + sha512_set_input(ctx, message[i]); + ctx->input_idx++; + } + } +} + +void crypto_sha512_final(crypto_sha512_ctx *ctx, u8 hash[64]) +{ + // Add padding bit + if (ctx->input_idx == 0) { + ZERO(ctx->input, 16); + } + sha512_set_input(ctx, 128); + + // Update size + sha512_incr(ctx->input_size, ctx->input_idx * 8); + + // Compress penultimate block (if any) + if (ctx->input_idx > 111) { + sha512_compress(ctx); + ZERO(ctx->input, 14); + } + // Compress last block + ctx->input[14] = ctx->input_size[0]; + ctx->input[15] = ctx->input_size[1]; + sha512_compress(ctx); + + // Copy hash to output (big endian) + FOR (i, 0, 8) { + store64_be(hash + i*8, ctx->hash[i]); + } + + WIPE_CTX(ctx); +} + +void crypto_sha512(u8 hash[64], const u8 *message, size_t message_size) +{ + crypto_sha512_ctx ctx; + crypto_sha512_init (&ctx); + crypto_sha512_update(&ctx, message, message_size); + crypto_sha512_final (&ctx, hash); +} + +//////////////////// +/// HMAC SHA 512 /// +//////////////////// +void crypto_sha512_hmac_init(crypto_sha512_hmac_ctx *ctx, + const u8 *key, size_t key_size) +{ + // hash key if it is too long + if (key_size > 128) { + crypto_sha512(ctx->key, key, key_size); + key = ctx->key; + key_size = 64; + } + // Compute inner key: padded key XOR 0x36 + FOR (i, 0, key_size) { ctx->key[i] = key[i] ^ 0x36; } + FOR (i, key_size, 128) { ctx->key[i] = 0x36; } + // Start computing inner hash + crypto_sha512_init (&ctx->ctx); + crypto_sha512_update(&ctx->ctx, ctx->key, 128); +} + +void crypto_sha512_hmac_update(crypto_sha512_hmac_ctx *ctx, + const u8 *message, size_t message_size) +{ + crypto_sha512_update(&ctx->ctx, message, message_size); +} + +void crypto_sha512_hmac_final(crypto_sha512_hmac_ctx *ctx, u8 hmac[64]) +{ + // Finish computing inner hash + crypto_sha512_final(&ctx->ctx, hmac); + // Compute outer key: padded key XOR 0x5c + FOR (i, 0, 128) { + ctx->key[i] ^= 0x36 ^ 0x5c; + } + // Compute outer hash + crypto_sha512_init (&ctx->ctx); + crypto_sha512_update(&ctx->ctx, ctx->key , 128); + crypto_sha512_update(&ctx->ctx, hmac, 64); + crypto_sha512_final (&ctx->ctx, hmac); // outer hash + WIPE_CTX(ctx); +} + +void crypto_sha512_hmac(u8 hmac[64], const u8 *key, size_t key_size, + const u8 *message, size_t message_size) +{ + crypto_sha512_hmac_ctx ctx; + crypto_sha512_hmac_init (&ctx, key, key_size); + crypto_sha512_hmac_update(&ctx, message, message_size); + crypto_sha512_hmac_final (&ctx, hmac); +} + +//////////////////// +/// HKDF SHA 512 /// +//////////////////// +void crypto_sha512_hkdf_expand(u8 *okm, size_t okm_size, + const u8 *prk, size_t prk_size, + const u8 *info, size_t info_size) +{ + int not_first = 0; + u8 ctr = 1; + u8 blk[64]; + + while (okm_size > 0) { + size_t out_size = MIN(okm_size, sizeof(blk)); + + crypto_sha512_hmac_ctx ctx; + crypto_sha512_hmac_init(&ctx, prk , prk_size); + if (not_first) { + // For some reason HKDF uses some kind of CBC mode. + // For some reason CTR mode alone wasn't enough. + // Like what, they didn't trust HMAC in 2010? Really?? + crypto_sha512_hmac_update(&ctx, blk , sizeof(blk)); + } + crypto_sha512_hmac_update(&ctx, info, info_size); + crypto_sha512_hmac_update(&ctx, &ctr, 1); + crypto_sha512_hmac_final(&ctx, blk); + + COPY(okm, blk, out_size); + + not_first = 1; + okm += out_size; + okm_size -= out_size; + ctr++; + } + WIPE_BUFFER(blk); +} + +void crypto_sha512_hkdf(u8 *okm , size_t okm_size, + const u8 *ikm , size_t ikm_size, + const u8 *salt, size_t salt_size, + const u8 *info, size_t info_size) +{ + // Extract + u8 prk[64]; + crypto_sha512_hmac(prk, salt, salt_size, ikm, ikm_size); + + // Expand + crypto_sha512_hkdf_expand(okm, okm_size, prk, sizeof(prk), info, info_size); + + WIPE_BUFFER(prk); +} + +/////////////// +/// Ed25519 /// +/////////////// +void crypto_ed25519_key_pair(u8 secret_key[64], u8 public_key[32], u8 seed[32]) +{ + u8 a[64]; + COPY(a, seed, 32); // a[ 0..31] = seed + crypto_wipe(seed, 32); + COPY(secret_key, a, 32); // secret key = seed + crypto_sha512(a, a, 32); // a[ 0..31] = scalar + crypto_eddsa_trim_scalar(a, a); // a[ 0..31] = trimmed scalar + crypto_eddsa_scalarbase(public_key, a); // public key = [trimmed scalar]B + COPY(secret_key + 32, public_key, 32); // secret key includes public half + WIPE_BUFFER(a); +} + +static void hash_reduce(u8 h[32], + const u8 *a, size_t a_size, + const u8 *b, size_t b_size, + const u8 *c, size_t c_size, + const u8 *d, size_t d_size) +{ + u8 hash[64]; + crypto_sha512_ctx ctx; + crypto_sha512_init (&ctx); + crypto_sha512_update(&ctx, a, a_size); + crypto_sha512_update(&ctx, b, b_size); + crypto_sha512_update(&ctx, c, c_size); + crypto_sha512_update(&ctx, d, d_size); + crypto_sha512_final (&ctx, hash); + crypto_eddsa_reduce(h, hash); +} + +static void ed25519_dom_sign(u8 signature[64], const u8 secret_key[64], + const u8 *dom, size_t dom_size, + const u8 *message, size_t message_size) +{ + u8 a[64]; // secret scalar and prefix + u8 r[32]; // secret deterministic "random" nonce + u8 h[32]; // publically verifiable hash of the message (not wiped) + u8 R[32]; // first half of the signature (allows overlapping inputs) + const u8 *pk = secret_key + 32; + + crypto_sha512(a, secret_key, 32); + crypto_eddsa_trim_scalar(a, a); + hash_reduce(r, dom, dom_size, a + 32, 32, message, message_size, 0, 0); + crypto_eddsa_scalarbase(R, r); + hash_reduce(h, dom, dom_size, R, 32, pk, 32, message, message_size); + COPY(signature, R, 32); + crypto_eddsa_mul_add(signature + 32, h, a, r); + + WIPE_BUFFER(a); + WIPE_BUFFER(r); +} + +void crypto_ed25519_sign(u8 signature [64], const u8 secret_key[64], + const u8 *message, size_t message_size) +{ + ed25519_dom_sign(signature, secret_key, 0, 0, message, message_size); +} + +int crypto_ed25519_check(const u8 signature[64], const u8 public_key[32], + const u8 *msg, size_t msg_size) +{ + u8 h_ram[32]; + hash_reduce(h_ram, signature, 32, public_key, 32, msg, msg_size, 0, 0); + return crypto_eddsa_check_equation(signature, public_key, h_ram); +} + +static const u8 domain[34] = "SigEd25519 no Ed25519 collisions\1"; + +void crypto_ed25519_ph_sign(uint8_t signature[64], const uint8_t secret_key[64], + const uint8_t message_hash[64]) +{ + ed25519_dom_sign(signature, secret_key, domain, sizeof(domain), + message_hash, 64); +} + +int crypto_ed25519_ph_check(const uint8_t sig[64], const uint8_t pk[32], + const uint8_t msg_hash[64]) +{ + u8 h_ram[32]; + hash_reduce(h_ram, domain, sizeof(domain), sig, 32, pk, 32, msg_hash, 64); + return crypto_eddsa_check_equation(sig, pk, h_ram); +} + + +#ifdef MONOCYPHER_CPP_NAMESPACE +} +#endif diff --git a/src/monocypher/monocypher-ed25519.h b/src/monocypher/monocypher-ed25519.h new file mode 100644 index 0000000..9ad1c54 --- /dev/null +++ b/src/monocypher/monocypher-ed25519.h @@ -0,0 +1,140 @@ +// Monocypher version 4.0.3 +// +// This file is dual-licensed. Choose whichever licence you want from +// the two licences listed below. +// +// The first licence is a regular 2-clause BSD licence. The second licence +// is the CC-0 from Creative Commons. It is intended to release Monocypher +// to the public domain. The BSD licence serves as a fallback option. +// +// SPDX-License-Identifier: BSD-2-Clause OR CC0-1.0 +// +// ------------------------------------------------------------------------ +// +// Copyright (c) 2017-2019, Loup Vaillant +// All rights reserved. +// +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ------------------------------------------------------------------------ +// +// Written in 2017-2019 by Loup Vaillant +// +// To the extent possible under law, the author(s) have dedicated all copyright +// and related neighboring rights to this software to the public domain +// worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication along +// with this software. If not, see +// + +#ifndef ED25519_H +#define ED25519_H + +#include "monocypher.h" + +#ifdef MONOCYPHER_CPP_NAMESPACE +namespace MONOCYPHER_CPP_NAMESPACE { +#elif defined(__cplusplus) +extern "C" { +#endif + +//////////////////////// +/// Type definitions /// +//////////////////////// + +// Do not rely on the size or content on any of those types, +// they may change without notice. +typedef struct { + uint64_t hash[8]; + uint64_t input[16]; + uint64_t input_size[2]; + size_t input_idx; +} crypto_sha512_ctx; + +typedef struct { + uint8_t key[128]; + crypto_sha512_ctx ctx; +} crypto_sha512_hmac_ctx; + + +// SHA 512 +// ------- +void crypto_sha512_init (crypto_sha512_ctx *ctx); +void crypto_sha512_update(crypto_sha512_ctx *ctx, + const uint8_t *message, size_t message_size); +void crypto_sha512_final (crypto_sha512_ctx *ctx, uint8_t hash[64]); +void crypto_sha512(uint8_t hash[64], + const uint8_t *message, size_t message_size); + +// SHA 512 HMAC +// ------------ +void crypto_sha512_hmac_init(crypto_sha512_hmac_ctx *ctx, + const uint8_t *key, size_t key_size); +void crypto_sha512_hmac_update(crypto_sha512_hmac_ctx *ctx, + const uint8_t *message, size_t message_size); +void crypto_sha512_hmac_final(crypto_sha512_hmac_ctx *ctx, uint8_t hmac[64]); +void crypto_sha512_hmac(uint8_t hmac[64], + const uint8_t *key , size_t key_size, + const uint8_t *message, size_t message_size); + +// SHA 512 HKDF +// ------------ +void crypto_sha512_hkdf_expand(uint8_t *okm, size_t okm_size, + const uint8_t *prk, size_t prk_size, + const uint8_t *info, size_t info_size); +void crypto_sha512_hkdf(uint8_t *okm , size_t okm_size, + const uint8_t *ikm , size_t ikm_size, + const uint8_t *salt, size_t salt_size, + const uint8_t *info, size_t info_size); + +// Ed25519 +// ------- +// Signatures (EdDSA with curve25519 + SHA-512) +// -------------------------------------------- +void crypto_ed25519_key_pair(uint8_t secret_key[64], + uint8_t public_key[32], + uint8_t seed[32]); +void crypto_ed25519_sign(uint8_t signature [64], + const uint8_t secret_key[64], + const uint8_t *message, size_t message_size); +int crypto_ed25519_check(const uint8_t signature [64], + const uint8_t public_key[32], + const uint8_t *message, size_t message_size); + +// Pre-hash variants +void crypto_ed25519_ph_sign(uint8_t signature [64], + const uint8_t secret_key [64], + const uint8_t message_hash[64]); +int crypto_ed25519_ph_check(const uint8_t signature [64], + const uint8_t public_key [32], + const uint8_t message_hash[64]); + +#ifdef __cplusplus +} +#endif + +#endif // ED25519_H diff --git a/src/monocypher/monocypher.c b/src/monocypher/monocypher.c new file mode 100644 index 0000000..fe5bbdc --- /dev/null +++ b/src/monocypher/monocypher.c @@ -0,0 +1,2988 @@ +// Monocypher version 4.0.3 +// +// This file is dual-licensed. Choose whichever licence you want from +// the two licences listed below. +// +// The first licence is a regular 2-clause BSD licence. The second licence +// is the CC-0 from Creative Commons. It is intended to release Monocypher +// to the public domain. The BSD licence serves as a fallback option. +// +// SPDX-License-Identifier: BSD-2-Clause OR CC0-1.0 +// +// ------------------------------------------------------------------------ +// +// Copyright (c) 2017-2020, Loup Vaillant +// All rights reserved. +// +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ------------------------------------------------------------------------ +// +// Written in 2017-2020 by Loup Vaillant +// +// To the extent possible under law, the author(s) have dedicated all copyright +// and related neighboring rights to this software to the public domain +// worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication along +// with this software. If not, see +// + +#include "monocypher.h" + +#ifdef MONOCYPHER_CPP_NAMESPACE +namespace MONOCYPHER_CPP_NAMESPACE { +#endif + +///////////////// +/// Utilities /// +///////////////// +#define FOR_T(type, i, start, end) for (type i = (start); i < (end); i++) +#define FOR(i, start, end) FOR_T(size_t, i, start, end) +#define COPY(dst, src, size) FOR(_i_, 0, size) (dst)[_i_] = (src)[_i_] +#define ZERO(buf, size) FOR(_i_, 0, size) (buf)[_i_] = 0 +#define WIPE_CTX(ctx) crypto_wipe(ctx , sizeof(*(ctx))) +#define WIPE_BUFFER(buffer) crypto_wipe(buffer, sizeof(buffer)) +#define MIN(a, b) ((a) <= (b) ? (a) : (b)) +#define MAX(a, b) ((a) >= (b) ? (a) : (b)) + +typedef int8_t i8; +typedef uint8_t u8; +typedef int16_t i16; +typedef uint32_t u32; +typedef int32_t i32; +typedef int64_t i64; +typedef uint64_t u64; + +static const u8 zero[128] = {0}; + +// returns the smallest positive integer y such that +// (x + y) % pow_2 == 0 +// Basically, y is the "gap" missing to align x. +// Only works when pow_2 is a power of 2. +// Note: we use ~x+1 instead of -x to avoid compiler warnings +static size_t gap(size_t x, size_t pow_2) +{ + return (~x + 1) & (pow_2 - 1); +} + +static u32 load24_le(const u8 s[3]) +{ + return + ((u32)s[0] << 0) | + ((u32)s[1] << 8) | + ((u32)s[2] << 16); +} + +static u32 load32_le(const u8 s[4]) +{ + return + ((u32)s[0] << 0) | + ((u32)s[1] << 8) | + ((u32)s[2] << 16) | + ((u32)s[3] << 24); +} + +static u64 load64_le(const u8 s[8]) +{ + return load32_le(s) | ((u64)load32_le(s+4) << 32); +} + +static void store32_le(u8 out[4], u32 in) +{ + out[0] = (u8)(in ); + out[1] = (u8)(in >> 8); + out[2] = (u8)(in >> 16); + out[3] = (u8)(in >> 24); +} + +static void store64_le(u8 out[8], u64 in) +{ + store32_le(out , (u32)(in )); + store32_le(out + 4, (u32)(in >> 32)); +} + +static void load32_le_buf (u32 *dst, const u8 *src, size_t size) { + FOR(i, 0, size) { dst[i] = load32_le(src + i*4); } +} +static void load64_le_buf (u64 *dst, const u8 *src, size_t size) { + FOR(i, 0, size) { dst[i] = load64_le(src + i*8); } +} +static void store32_le_buf(u8 *dst, const u32 *src, size_t size) { + FOR(i, 0, size) { store32_le(dst + i*4, src[i]); } +} +static void store64_le_buf(u8 *dst, const u64 *src, size_t size) { + FOR(i, 0, size) { store64_le(dst + i*8, src[i]); } +} + +static u64 rotr64(u64 x, u64 n) { return (x >> n) ^ (x << (64 - n)); } +static u32 rotl32(u32 x, u32 n) { return (x << n) ^ (x >> (32 - n)); } + +static int neq0(u64 diff) +{ + // constant time comparison to zero + // return diff != 0 ? -1 : 0 + u64 half = (diff >> 32) | ((u32)diff); // half < 2^32 + u64 eq0 = 1 & ((half - 1) >> 32); // half == 0 ? 1 : 0 + return (int)eq0 - 1; // half == 0 ? 0 : -1 +} + +static u64 x16(const u8 a[16], const u8 b[16]) +{ + return (load64_le(a + 0) ^ load64_le(b + 0)) + | (load64_le(a + 8) ^ load64_le(b + 8)); +} +static u64 x32(const u8 a[32],const u8 b[32]){return x16(a,b)| x16(a+16, b+16);} +static u64 x64(const u8 a[64],const u8 b[64]){return x32(a,b)| x32(a+32, b+32);} +int crypto_verify16(const u8 a[16], const u8 b[16]){ return neq0(x16(a, b)); } +int crypto_verify32(const u8 a[32], const u8 b[32]){ return neq0(x32(a, b)); } +int crypto_verify64(const u8 a[64], const u8 b[64]){ return neq0(x64(a, b)); } + +void crypto_wipe(void *secret, size_t size) +{ + volatile u8 *v_secret = (u8*)secret; + ZERO(v_secret, size); +} + +///////////////// +/// Chacha 20 /// +///////////////// +#define QUARTERROUND(a, b, c, d) \ + a += b; d = rotl32(d ^ a, 16); \ + c += d; b = rotl32(b ^ c, 12); \ + a += b; d = rotl32(d ^ a, 8); \ + c += d; b = rotl32(b ^ c, 7) + +static void chacha20_rounds(u32 out[16], const u32 in[16]) +{ + // The temporary variables make Chacha20 10% faster. + u32 t0 = in[ 0]; u32 t1 = in[ 1]; u32 t2 = in[ 2]; u32 t3 = in[ 3]; + u32 t4 = in[ 4]; u32 t5 = in[ 5]; u32 t6 = in[ 6]; u32 t7 = in[ 7]; + u32 t8 = in[ 8]; u32 t9 = in[ 9]; u32 t10 = in[10]; u32 t11 = in[11]; + u32 t12 = in[12]; u32 t13 = in[13]; u32 t14 = in[14]; u32 t15 = in[15]; + + FOR (i, 0, 10) { // 20 rounds, 2 rounds per loop. + QUARTERROUND(t0, t4, t8 , t12); // column 0 + QUARTERROUND(t1, t5, t9 , t13); // column 1 + QUARTERROUND(t2, t6, t10, t14); // column 2 + QUARTERROUND(t3, t7, t11, t15); // column 3 + QUARTERROUND(t0, t5, t10, t15); // diagonal 0 + QUARTERROUND(t1, t6, t11, t12); // diagonal 1 + QUARTERROUND(t2, t7, t8 , t13); // diagonal 2 + QUARTERROUND(t3, t4, t9 , t14); // diagonal 3 + } + out[ 0] = t0; out[ 1] = t1; out[ 2] = t2; out[ 3] = t3; + out[ 4] = t4; out[ 5] = t5; out[ 6] = t6; out[ 7] = t7; + out[ 8] = t8; out[ 9] = t9; out[10] = t10; out[11] = t11; + out[12] = t12; out[13] = t13; out[14] = t14; out[15] = t15; +} + +static const u8 *chacha20_constant = (const u8*)"expand 32-byte k"; // 16 bytes + +void crypto_chacha20_h(u8 out[32], const u8 key[32], const u8 in [16]) +{ + u32 block[16]; + load32_le_buf(block , chacha20_constant, 4); + load32_le_buf(block + 4, key , 8); + load32_le_buf(block + 12, in , 4); + + chacha20_rounds(block, block); + + // prevent reversal of the rounds by revealing only half of the buffer. + store32_le_buf(out , block , 4); // constant + store32_le_buf(out+16, block+12, 4); // counter and nonce + WIPE_BUFFER(block); +} + +u64 crypto_chacha20_djb(u8 *cipher_text, const u8 *plain_text, + size_t text_size, const u8 key[32], const u8 nonce[8], + u64 ctr) +{ + u32 input[16]; + load32_le_buf(input , chacha20_constant, 4); + load32_le_buf(input + 4, key , 8); + load32_le_buf(input + 14, nonce , 2); + input[12] = (u32) ctr; + input[13] = (u32)(ctr >> 32); + + // Whole blocks + u32 pool[16]; + size_t nb_blocks = text_size >> 6; + FOR (i, 0, nb_blocks) { + chacha20_rounds(pool, input); + if (plain_text != NULL) { + FOR (j, 0, 16) { + u32 p = pool[j] + input[j]; + store32_le(cipher_text, p ^ load32_le(plain_text)); + cipher_text += 4; + plain_text += 4; + } + } else { + FOR (j, 0, 16) { + u32 p = pool[j] + input[j]; + store32_le(cipher_text, p); + cipher_text += 4; + } + } + input[12]++; + if (input[12] == 0) { + input[13]++; + } + } + text_size &= 63; + + // Last (incomplete) block + if (text_size > 0) { + if (plain_text == NULL) { + plain_text = zero; + } + chacha20_rounds(pool, input); + u8 tmp[64]; + FOR (i, 0, 16) { + store32_le(tmp + i*4, pool[i] + input[i]); + } + FOR (i, 0, text_size) { + cipher_text[i] = tmp[i] ^ plain_text[i]; + } + WIPE_BUFFER(tmp); + } + ctr = input[12] + ((u64)input[13] << 32) + (text_size > 0); + + WIPE_BUFFER(pool); + WIPE_BUFFER(input); + return ctr; +} + +u32 crypto_chacha20_ietf(u8 *cipher_text, const u8 *plain_text, + size_t text_size, + const u8 key[32], const u8 nonce[12], u32 ctr) +{ + u64 big_ctr = ctr + ((u64)load32_le(nonce) << 32); + return (u32)crypto_chacha20_djb(cipher_text, plain_text, text_size, + key, nonce + 4, big_ctr); +} + +u64 crypto_chacha20_x(u8 *cipher_text, const u8 *plain_text, + size_t text_size, + const u8 key[32], const u8 nonce[24], u64 ctr) +{ + u8 sub_key[32]; + crypto_chacha20_h(sub_key, key, nonce); + ctr = crypto_chacha20_djb(cipher_text, plain_text, text_size, + sub_key, nonce + 16, ctr); + WIPE_BUFFER(sub_key); + return ctr; +} + +///////////////// +/// Poly 1305 /// +///////////////// + +// h = (h + c) * r +// preconditions: +// ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff +// ctx->r <= 0ffffffc_0ffffffc_0ffffffc_0fffffff +// end <= 1 +// Postcondition: +// ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff +static void poly_blocks(crypto_poly1305_ctx *ctx, const u8 *in, + size_t nb_blocks, unsigned end) +{ + // Local all the things! + const u32 r0 = ctx->r[0]; + const u32 r1 = ctx->r[1]; + const u32 r2 = ctx->r[2]; + const u32 r3 = ctx->r[3]; + const u32 rr0 = (r0 >> 2) * 5; // lose 2 bits... + const u32 rr1 = (r1 >> 2) + r1; // rr1 == (r1 >> 2) * 5 + const u32 rr2 = (r2 >> 2) + r2; // rr1 == (r2 >> 2) * 5 + const u32 rr3 = (r3 >> 2) + r3; // rr1 == (r3 >> 2) * 5 + const u32 rr4 = r0 & 3; // ...recover 2 bits + u32 h0 = ctx->h[0]; + u32 h1 = ctx->h[1]; + u32 h2 = ctx->h[2]; + u32 h3 = ctx->h[3]; + u32 h4 = ctx->h[4]; + + FOR (i, 0, nb_blocks) { + // h + c, without carry propagation + const u64 s0 = (u64)h0 + load32_le(in); in += 4; + const u64 s1 = (u64)h1 + load32_le(in); in += 4; + const u64 s2 = (u64)h2 + load32_le(in); in += 4; + const u64 s3 = (u64)h3 + load32_le(in); in += 4; + const u32 s4 = h4 + end; + + // (h + c) * r, without carry propagation + const u64 x0 = s0*r0+ s1*rr3+ s2*rr2+ s3*rr1+ s4*rr0; + const u64 x1 = s0*r1+ s1*r0 + s2*rr3+ s3*rr2+ s4*rr1; + const u64 x2 = s0*r2+ s1*r1 + s2*r0 + s3*rr3+ s4*rr2; + const u64 x3 = s0*r3+ s1*r2 + s2*r1 + s3*r0 + s4*rr3; + const u32 x4 = s4*rr4; + + // partial reduction modulo 2^130 - 5 + const u32 u5 = (u32)(x3 >> 32) + x4; // u5 <= 7ffffff5 + const u64 u0 = (u32)(u5 >> 2) * 5 + (x0 & 0xffffffff); + const u64 u1 = (u32)(u0 >> 32) + (x1 & 0xffffffff) + (x0 >> 32); + const u64 u2 = (u32)(u1 >> 32) + (x2 & 0xffffffff) + (x1 >> 32); + const u64 u3 = (u32)(u2 >> 32) + (x3 & 0xffffffff) + (x2 >> 32); + const u32 u4 = (u32)(u3 >> 32) + (u5 & 3); // u4 <= 4 + + // Update the hash + h0 = u0 & 0xffffffff; + h1 = u1 & 0xffffffff; + h2 = u2 & 0xffffffff; + h3 = u3 & 0xffffffff; + h4 = u4; + } + ctx->h[0] = h0; + ctx->h[1] = h1; + ctx->h[2] = h2; + ctx->h[3] = h3; + ctx->h[4] = h4; +} + +void crypto_poly1305_init(crypto_poly1305_ctx *ctx, const u8 key[32]) +{ + ZERO(ctx->h, 5); // Initial hash is zero + ctx->c_idx = 0; + // load r and pad (r has some of its bits cleared) + load32_le_buf(ctx->r , key , 4); + load32_le_buf(ctx->pad, key+16, 4); + FOR (i, 0, 1) { ctx->r[i] &= 0x0fffffff; } + FOR (i, 1, 4) { ctx->r[i] &= 0x0ffffffc; } +} + +void crypto_poly1305_update(crypto_poly1305_ctx *ctx, + const u8 *message, size_t message_size) +{ + // Avoid undefined NULL pointer increments with empty messages + if (message_size == 0) { + return; + } + + // Align ourselves with block boundaries + size_t aligned = MIN(gap(ctx->c_idx, 16), message_size); + FOR (i, 0, aligned) { + ctx->c[ctx->c_idx] = *message; + ctx->c_idx++; + message++; + message_size--; + } + + // If block is complete, process it + if (ctx->c_idx == 16) { + poly_blocks(ctx, ctx->c, 1, 1); + ctx->c_idx = 0; + } + + // Process the message block by block + size_t nb_blocks = message_size >> 4; + poly_blocks(ctx, message, nb_blocks, 1); + message += nb_blocks << 4; + message_size &= 15; + + // remaining bytes (we never complete a block here) + FOR (i, 0, message_size) { + ctx->c[ctx->c_idx] = message[i]; + ctx->c_idx++; + } +} + +void crypto_poly1305_final(crypto_poly1305_ctx *ctx, u8 mac[16]) +{ + // Process the last block (if any) + // We move the final 1 according to remaining input length + // (this will add less than 2^130 to the last input block) + if (ctx->c_idx != 0) { + ZERO(ctx->c + ctx->c_idx, 16 - ctx->c_idx); + ctx->c[ctx->c_idx] = 1; + poly_blocks(ctx, ctx->c, 1, 0); + } + + // check if we should subtract 2^130-5 by performing the + // corresponding carry propagation. + u64 c = 5; + FOR (i, 0, 4) { + c += ctx->h[i]; + c >>= 32; + } + c += ctx->h[4]; + c = (c >> 2) * 5; // shift the carry back to the beginning + // c now indicates how many times we should subtract 2^130-5 (0 or 1) + FOR (i, 0, 4) { + c += (u64)ctx->h[i] + ctx->pad[i]; + store32_le(mac + i*4, (u32)c); + c = c >> 32; + } + WIPE_CTX(ctx); +} + +void crypto_poly1305(u8 mac[16], const u8 *message, + size_t message_size, const u8 key[32]) +{ + crypto_poly1305_ctx ctx; + crypto_poly1305_init (&ctx, key); + crypto_poly1305_update(&ctx, message, message_size); + crypto_poly1305_final (&ctx, mac); +} + +//////////////// +/// BLAKE2 b /// +//////////////// +static const u64 iv[8] = { + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, +}; + +static void blake2b_compress(crypto_blake2b_ctx *ctx, int is_last_block) +{ + static const u8 sigma[12][16] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 }, + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 }, + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 }, + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 }, + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }, + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, + }; + + // increment input offset + u64 *x = ctx->input_offset; + size_t y = ctx->input_idx; + x[0] += y; + if (x[0] < y) { + x[1]++; + } + + // init work vector + u64 v0 = ctx->hash[0]; u64 v8 = iv[0]; + u64 v1 = ctx->hash[1]; u64 v9 = iv[1]; + u64 v2 = ctx->hash[2]; u64 v10 = iv[2]; + u64 v3 = ctx->hash[3]; u64 v11 = iv[3]; + u64 v4 = ctx->hash[4]; u64 v12 = iv[4] ^ ctx->input_offset[0]; + u64 v5 = ctx->hash[5]; u64 v13 = iv[5] ^ ctx->input_offset[1]; + u64 v6 = ctx->hash[6]; u64 v14 = iv[6] ^ (u64)~(is_last_block - 1); + u64 v7 = ctx->hash[7]; u64 v15 = iv[7]; + + // mangle work vector + u64 *input = ctx->input; +#define BLAKE2_G(a, b, c, d, x, y) \ + a += b + x; d = rotr64(d ^ a, 32); \ + c += d; b = rotr64(b ^ c, 24); \ + a += b + y; d = rotr64(d ^ a, 16); \ + c += d; b = rotr64(b ^ c, 63) +#define BLAKE2_ROUND(i) \ + BLAKE2_G(v0, v4, v8 , v12, input[sigma[i][ 0]], input[sigma[i][ 1]]); \ + BLAKE2_G(v1, v5, v9 , v13, input[sigma[i][ 2]], input[sigma[i][ 3]]); \ + BLAKE2_G(v2, v6, v10, v14, input[sigma[i][ 4]], input[sigma[i][ 5]]); \ + BLAKE2_G(v3, v7, v11, v15, input[sigma[i][ 6]], input[sigma[i][ 7]]); \ + BLAKE2_G(v0, v5, v10, v15, input[sigma[i][ 8]], input[sigma[i][ 9]]); \ + BLAKE2_G(v1, v6, v11, v12, input[sigma[i][10]], input[sigma[i][11]]); \ + BLAKE2_G(v2, v7, v8 , v13, input[sigma[i][12]], input[sigma[i][13]]); \ + BLAKE2_G(v3, v4, v9 , v14, input[sigma[i][14]], input[sigma[i][15]]) + +#ifdef BLAKE2_NO_UNROLLING + FOR (i, 0, 12) { + BLAKE2_ROUND(i); + } +#else + BLAKE2_ROUND(0); BLAKE2_ROUND(1); BLAKE2_ROUND(2); BLAKE2_ROUND(3); + BLAKE2_ROUND(4); BLAKE2_ROUND(5); BLAKE2_ROUND(6); BLAKE2_ROUND(7); + BLAKE2_ROUND(8); BLAKE2_ROUND(9); BLAKE2_ROUND(10); BLAKE2_ROUND(11); +#endif + + // update hash + ctx->hash[0] ^= v0 ^ v8; ctx->hash[1] ^= v1 ^ v9; + ctx->hash[2] ^= v2 ^ v10; ctx->hash[3] ^= v3 ^ v11; + ctx->hash[4] ^= v4 ^ v12; ctx->hash[5] ^= v5 ^ v13; + ctx->hash[6] ^= v6 ^ v14; ctx->hash[7] ^= v7 ^ v15; +} + +void crypto_blake2b_keyed_init(crypto_blake2b_ctx *ctx, size_t hash_size, + const u8 *key, size_t key_size) +{ + // initial hash + COPY(ctx->hash, iv, 8); + ctx->hash[0] ^= 0x01010000 ^ (key_size << 8) ^ hash_size; + + ctx->input_offset[0] = 0; // beginning of the input, no offset + ctx->input_offset[1] = 0; // beginning of the input, no offset + ctx->hash_size = hash_size; + ctx->input_idx = 0; + ZERO(ctx->input, 16); + + // if there is a key, the first block is that key (padded with zeroes) + if (key_size > 0) { + u8 key_block[128] = {0}; + COPY(key_block, key, key_size); + // same as calling crypto_blake2b_update(ctx, key_block , 128) + load64_le_buf(ctx->input, key_block, 16); + ctx->input_idx = 128; + WIPE_BUFFER(key_block); + } +} + +void crypto_blake2b_init(crypto_blake2b_ctx *ctx, size_t hash_size) +{ + crypto_blake2b_keyed_init(ctx, hash_size, 0, 0); +} + +void crypto_blake2b_update(crypto_blake2b_ctx *ctx, + const u8 *message, size_t message_size) +{ + // Avoid undefined NULL pointer increments with empty messages + if (message_size == 0) { + return; + } + + // Align with word boundaries + if ((ctx->input_idx & 7) != 0) { + size_t nb_bytes = MIN(gap(ctx->input_idx, 8), message_size); + size_t word = ctx->input_idx >> 3; + size_t byte = ctx->input_idx & 7; + FOR (i, 0, nb_bytes) { + ctx->input[word] |= (u64)message[i] << ((byte + i) << 3); + } + ctx->input_idx += nb_bytes; + message += nb_bytes; + message_size -= nb_bytes; + } + + // Align with block boundaries (faster than byte by byte) + if ((ctx->input_idx & 127) != 0) { + size_t nb_words = MIN(gap(ctx->input_idx, 128), message_size) >> 3; + load64_le_buf(ctx->input + (ctx->input_idx >> 3), message, nb_words); + ctx->input_idx += nb_words << 3; + message += nb_words << 3; + message_size -= nb_words << 3; + } + + // Process block by block + size_t nb_blocks = message_size >> 7; + FOR (i, 0, nb_blocks) { + if (ctx->input_idx == 128) { + blake2b_compress(ctx, 0); + } + load64_le_buf(ctx->input, message, 16); + message += 128; + ctx->input_idx = 128; + } + message_size &= 127; + + if (message_size != 0) { + // Compress block & flush input buffer as needed + if (ctx->input_idx == 128) { + blake2b_compress(ctx, 0); + ctx->input_idx = 0; + } + if (ctx->input_idx == 0) { + ZERO(ctx->input, 16); + } + // Fill remaining words (faster than byte by byte) + size_t nb_words = message_size >> 3; + load64_le_buf(ctx->input, message, nb_words); + ctx->input_idx += nb_words << 3; + message += nb_words << 3; + message_size -= nb_words << 3; + + // Fill remaining bytes + FOR (i, 0, message_size) { + size_t word = ctx->input_idx >> 3; + size_t byte = ctx->input_idx & 7; + ctx->input[word] |= (u64)message[i] << (byte << 3); + ctx->input_idx++; + } + } +} + +void crypto_blake2b_final(crypto_blake2b_ctx *ctx, u8 *hash) +{ + blake2b_compress(ctx, 1); // compress the last block + size_t hash_size = MIN(ctx->hash_size, 64); + size_t nb_words = hash_size >> 3; + store64_le_buf(hash, ctx->hash, nb_words); + FOR (i, nb_words << 3, hash_size) { + hash[i] = (ctx->hash[i >> 3] >> (8 * (i & 7))) & 0xff; + } + WIPE_CTX(ctx); +} + +void crypto_blake2b_keyed(u8 *hash, size_t hash_size, + const u8 *key, size_t key_size, + const u8 *message, size_t message_size) +{ + crypto_blake2b_ctx ctx; + crypto_blake2b_keyed_init(&ctx, hash_size, key, key_size); + crypto_blake2b_update (&ctx, message, message_size); + crypto_blake2b_final (&ctx, hash); +} + +void crypto_blake2b(u8 *hash, size_t hash_size, const u8 *msg, size_t msg_size) +{ + crypto_blake2b_keyed(hash, hash_size, 0, 0, msg, msg_size); +} + +////////////// +/// Argon2 /// +////////////// +// references to R, Z, Q etc. come from the spec + +// Argon2 operates on 1024 byte blocks. +typedef struct { u64 a[128]; } blk; + +// updates a BLAKE2 hash with a 32 bit word, little endian. +static void blake_update_32(crypto_blake2b_ctx *ctx, u32 input) +{ + u8 buf[4]; + store32_le(buf, input); + crypto_blake2b_update(ctx, buf, 4); + WIPE_BUFFER(buf); +} + +static void blake_update_32_buf(crypto_blake2b_ctx *ctx, + const u8 *buf, u32 size) +{ + blake_update_32(ctx, size); + crypto_blake2b_update(ctx, buf, size); +} + + +static void copy_block(blk *o,const blk*in){FOR(i, 0, 128) o->a[i] = in->a[i];} +static void xor_block(blk *o,const blk*in){FOR(i, 0, 128) o->a[i] ^= in->a[i];} + +// Hash with a virtually unlimited digest size. +// Doesn't extract more entropy than the base hash function. +// Mainly used for filling a whole kilobyte block with pseudo-random bytes. +// (One could use a stream cipher with a seed hash as the key, but +// this would introduce another dependency —and point of failure.) +static void extended_hash(u8 *digest, u32 digest_size, + const u8 *input , u32 input_size) +{ + crypto_blake2b_ctx ctx; + crypto_blake2b_init (&ctx, MIN(digest_size, 64)); + blake_update_32 (&ctx, digest_size); + crypto_blake2b_update(&ctx, input, input_size); + crypto_blake2b_final (&ctx, digest); + + if (digest_size > 64) { + // the conversion to u64 avoids integer overflow on + // ludicrously big hash sizes. + u32 r = (u32)(((u64)digest_size + 31) >> 5) - 2; + u32 i = 1; + u32 in = 0; + u32 out = 32; + while (i < r) { + // Input and output overlap. This is intentional + crypto_blake2b(digest + out, 64, digest + in, 64); + i += 1; + in += 32; + out += 32; + } + crypto_blake2b(digest + out, digest_size - (32 * r), digest + in , 64); + } +} + +#define LSB(x) ((u64)(u32)x) +#define G(a, b, c, d) \ + a += b + ((LSB(a) * LSB(b)) << 1); d ^= a; d = rotr64(d, 32); \ + c += d + ((LSB(c) * LSB(d)) << 1); b ^= c; b = rotr64(b, 24); \ + a += b + ((LSB(a) * LSB(b)) << 1); d ^= a; d = rotr64(d, 16); \ + c += d + ((LSB(c) * LSB(d)) << 1); b ^= c; b = rotr64(b, 63) +#define ROUND(v0, v1, v2, v3, v4, v5, v6, v7, \ + v8, v9, v10, v11, v12, v13, v14, v15) \ + G(v0, v4, v8, v12); G(v1, v5, v9, v13); \ + G(v2, v6, v10, v14); G(v3, v7, v11, v15); \ + G(v0, v5, v10, v15); G(v1, v6, v11, v12); \ + G(v2, v7, v8, v13); G(v3, v4, v9, v14) + +// Core of the compression function G. Computes Z from R in place. +static void g_rounds(blk *b) +{ + // column rounds (work_block = Q) + for (int i = 0; i < 128; i += 16) { + ROUND(b->a[i ], b->a[i+ 1], b->a[i+ 2], b->a[i+ 3], + b->a[i+ 4], b->a[i+ 5], b->a[i+ 6], b->a[i+ 7], + b->a[i+ 8], b->a[i+ 9], b->a[i+10], b->a[i+11], + b->a[i+12], b->a[i+13], b->a[i+14], b->a[i+15]); + } + // row rounds (b = Z) + for (int i = 0; i < 16; i += 2) { + ROUND(b->a[i ], b->a[i+ 1], b->a[i+ 16], b->a[i+ 17], + b->a[i+32], b->a[i+33], b->a[i+ 48], b->a[i+ 49], + b->a[i+64], b->a[i+65], b->a[i+ 80], b->a[i+ 81], + b->a[i+96], b->a[i+97], b->a[i+112], b->a[i+113]); + } +} + +const crypto_argon2_extras crypto_argon2_no_extras = { 0, 0, 0, 0 }; + +void crypto_argon2(u8 *hash, u32 hash_size, void *work_area, + crypto_argon2_config config, + crypto_argon2_inputs inputs, + crypto_argon2_extras extras) +{ + const u32 segment_size = config.nb_blocks / config.nb_lanes / 4; + const u32 lane_size = segment_size * 4; + const u32 nb_blocks = lane_size * config.nb_lanes; // rounding down + + // work area seen as blocks (must be suitably aligned) + blk *blocks = (blk*)work_area; + { + u8 initial_hash[72]; // 64 bytes plus 2 words for future hashes + crypto_blake2b_ctx ctx; + crypto_blake2b_init (&ctx, 64); + blake_update_32 (&ctx, config.nb_lanes ); // p: number of "threads" + blake_update_32 (&ctx, hash_size); + blake_update_32 (&ctx, config.nb_blocks); + blake_update_32 (&ctx, config.nb_passes); + blake_update_32 (&ctx, 0x13); // v: version number + blake_update_32 (&ctx, config.algorithm); // y: Argon2i, Argon2d... + blake_update_32_buf (&ctx, inputs.pass, inputs.pass_size); + blake_update_32_buf (&ctx, inputs.salt, inputs.salt_size); + blake_update_32_buf (&ctx, extras.key, extras.key_size); + blake_update_32_buf (&ctx, extras.ad, extras.ad_size); + crypto_blake2b_final(&ctx, initial_hash); // fill 64 first bytes only + + // fill first 2 blocks of each lane + u8 hash_area[1024]; + FOR_T(u32, l, 0, config.nb_lanes) { + FOR_T(u32, i, 0, 2) { + store32_le(initial_hash + 64, i); // first additional word + store32_le(initial_hash + 68, l); // second additional word + extended_hash(hash_area, 1024, initial_hash, 72); + load64_le_buf(blocks[l * lane_size + i].a, hash_area, 128); + } + } + + WIPE_BUFFER(initial_hash); + WIPE_BUFFER(hash_area); + } + + // Argon2i and Argon2id start with constant time indexing + int constant_time = config.algorithm != CRYPTO_ARGON2_D; + + // Fill (and re-fill) the rest of the blocks + // + // Note: even though each segment within the same slice can be + // computed in parallel, (one thread per lane), we are computing + // them sequentially, because Monocypher doesn't support threads. + // + // Yet optimal performance (and therefore security) requires one + // thread per lane. The only reason Monocypher supports multiple + // lanes is compatibility. + blk tmp; + FOR_T(u32, pass, 0, config.nb_passes) { + FOR_T(u32, slice, 0, 4) { + // On the first slice of the first pass, + // blocks 0 and 1 are already filled, hence pass_offset. + u32 pass_offset = pass == 0 && slice == 0 ? 2 : 0; + u32 slice_offset = slice * segment_size; + + // Argon2id switches back to non-constant time indexing + // after the first two slices of the first pass + if (slice == 2 && config.algorithm == CRYPTO_ARGON2_ID) { + constant_time = 0; + } + + // Each iteration of the following loop may be performed in + // a separate thread. All segments must be fully completed + // before we start filling the next slice. + FOR_T(u32, segment, 0, config.nb_lanes) { + blk index_block; + u32 index_ctr = 1; + FOR_T (u32, block, pass_offset, segment_size) { + // Current and previous blocks + u32 lane_offset = segment * lane_size; + blk *segment_start = blocks + lane_offset + slice_offset; + blk *current = segment_start + block; + blk *previous = + block == 0 && slice_offset == 0 + ? segment_start + lane_size - 1 + : segment_start + block - 1; + + u64 index_seed; + if (constant_time) { + if (block == pass_offset || (block % 128) == 0) { + // Fill or refresh deterministic indices block + + // seed the beginning of the block... + ZERO(index_block.a, 128); + index_block.a[0] = pass; + index_block.a[1] = segment; + index_block.a[2] = slice; + index_block.a[3] = nb_blocks; + index_block.a[4] = config.nb_passes; + index_block.a[5] = config.algorithm; + index_block.a[6] = index_ctr; + index_ctr++; + + // ... then shuffle it + copy_block(&tmp, &index_block); + g_rounds (&index_block); + xor_block (&index_block, &tmp); + copy_block(&tmp, &index_block); + g_rounds (&index_block); + xor_block (&index_block, &tmp); + } + index_seed = index_block.a[block % 128]; + } else { + index_seed = previous->a[0]; + } + + // Establish the reference set. *Approximately* comprises: + // - The last 3 slices (if they exist yet) + // - The already constructed blocks in the current segment + u32 next_slice = ((slice + 1) % 4) * segment_size; + u32 window_start = pass == 0 ? 0 : next_slice; + u32 nb_segments = pass == 0 ? slice : 3; + u32 lane = + pass == 0 && slice == 0 + ? segment + : (u32)(index_seed >> 32) % config.nb_lanes; + u32 window_size = + nb_segments * segment_size + + (lane == segment ? block-1 : + block == 0 ? (u32)-1 : 0); + + // Find reference block + u64 j1 = index_seed & 0xffffffff; // block selector + u64 x = (j1 * j1) >> 32; + u64 y = (window_size * x) >> 32; + u64 z = (window_size - 1) - y; + u32 ref = (u32)((window_start + z) % lane_size); + u32 index = lane * lane_size + ref; + blk *reference = blocks + index; + + // Shuffle the previous & reference block + // into the current block + copy_block(&tmp, previous); + xor_block (&tmp, reference); + if (pass == 0) { copy_block(current, &tmp); } + else { xor_block (current, &tmp); } + g_rounds (&tmp); + xor_block (current, &tmp); + } + } + } + } + + // Wipe temporary block + volatile u64* p = tmp.a; + ZERO(p, 128); + + // XOR last blocks of each lane + blk *last_block = blocks + lane_size - 1; + FOR_T (u32, lane, 1, config.nb_lanes) { + blk *next_block = last_block + lane_size; + xor_block(next_block, last_block); + last_block = next_block; + } + + // Serialize last block + u8 final_block[1024]; + store64_le_buf(final_block, last_block->a, 128); + + // Wipe work area + p = (u64*)work_area; + ZERO(p, 128 * nb_blocks); + + // Hash the very last block with H' into the output hash + extended_hash(hash, hash_size, final_block, 1024); + WIPE_BUFFER(final_block); +} + +//////////////////////////////////// +/// Arithmetic modulo 2^255 - 19 /// +//////////////////////////////////// +// Originally taken from SUPERCOP's ref10 implementation. +// A bit bigger than TweetNaCl, over 4 times faster. + +// field element +typedef i32 fe[10]; + +// field constants +// +// fe_one : 1 +// sqrtm1 : sqrt(-1) +// d : -121665 / 121666 +// D2 : 2 * -121665 / 121666 +// lop_x, lop_y: low order point in Edwards coordinates +// ufactor : -sqrt(-1) * 2 +// A2 : 486662^2 (A squared) +static const fe fe_one = {1}; +static const fe sqrtm1 = { + -32595792, -7943725, 9377950, 3500415, 12389472, + -272473, -25146209, -2005654, 326686, 11406482, +}; +static const fe d = { + -10913610, 13857413, -15372611, 6949391, 114729, + -8787816, -6275908, -3247719, -18696448, -12055116, +}; +static const fe D2 = { + -21827239, -5839606, -30745221, 13898782, 229458, + 15978800, -12551817, -6495438, 29715968, 9444199, +}; +static const fe lop_x = { + 21352778, 5345713, 4660180, -8347857, 24143090, + 14568123, 30185756, -12247770, -33528939, 8345319, +}; +static const fe lop_y = { + -6952922, -1265500, 6862341, -7057498, -4037696, + -5447722, 31680899, -15325402, -19365852, 1569102, +}; +static const fe ufactor = { + -1917299, 15887451, -18755900, -7000830, -24778944, + 544946, -16816446, 4011309, -653372, 10741468, +}; +static const fe A2 = { + 12721188, 3529, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static void fe_0(fe h) { ZERO(h , 10); } +static void fe_1(fe h) { h[0] = 1; ZERO(h+1, 9); } + +static void fe_copy(fe h,const fe f ){FOR(i,0,10) h[i] = f[i]; } +static void fe_neg (fe h,const fe f ){FOR(i,0,10) h[i] = -f[i]; } +static void fe_add (fe h,const fe f,const fe g){FOR(i,0,10) h[i] = f[i] + g[i];} +static void fe_sub (fe h,const fe f,const fe g){FOR(i,0,10) h[i] = f[i] - g[i];} + +// Some compilers, when inlining fe_cswap() or fe_ccopy(), may introduce +// a timing leak. It happens when it notices `b` has only 2 possible +// values, and either replace the arithmetic by a secret dependent +// branch, or (as has been observed), swap pointers instead of values, +// which intruduces a secret dependent index. +// +// We apply two mitigations here: +// - Add `volatile` in the mask declaration. +// - Unroll the copy loop (costs couple hundred bytes of binary code). +// +// As of June 2026, those mitigation work when applied separately or +// together. Applying them both is currently overkill, but may help +// delay the day compilers grow clever enough to defeat it. (The true +// fix is in the semantics of the language itself: C currently has no +// way to specify constant time code). +// +// Note (Loup): as of June 2026, the problem has yet to surface in +// fe_cswap(), but since this is almost the same code as fe_ccopy() I +// believe it is more prudent to apply the precaution there too. +static void fe_cswap(fe f, fe g, int b) +{ + volatile i32 mask = -b; // -1 = 0xffffffff + i32 x0 = (f[0] ^ g[0]) & mask; f[0] = f[0] ^ x0; g[0] = g[0] ^ x0; + i32 x1 = (f[1] ^ g[1]) & mask; f[1] = f[1] ^ x1; g[1] = g[1] ^ x1; + i32 x2 = (f[2] ^ g[2]) & mask; f[2] = f[2] ^ x2; g[2] = g[2] ^ x2; + i32 x3 = (f[3] ^ g[3]) & mask; f[3] = f[3] ^ x3; g[3] = g[3] ^ x3; + i32 x4 = (f[4] ^ g[4]) & mask; f[4] = f[4] ^ x4; g[4] = g[4] ^ x4; + i32 x5 = (f[5] ^ g[5]) & mask; f[5] = f[5] ^ x5; g[5] = g[5] ^ x5; + i32 x6 = (f[6] ^ g[6]) & mask; f[6] = f[6] ^ x6; g[6] = g[6] ^ x6; + i32 x7 = (f[7] ^ g[7]) & mask; f[7] = f[7] ^ x7; g[7] = g[7] ^ x7; + i32 x8 = (f[8] ^ g[8]) & mask; f[8] = f[8] ^ x8; g[8] = g[8] ^ x8; + i32 x9 = (f[9] ^ g[9]) & mask; f[9] = f[9] ^ x9; g[9] = g[9] ^ x9; +} + +static void fe_ccopy(fe f, const fe g, int b) +{ + volatile i32 mask = -b; // -1 = 0xffffffff + i32 x0 = (f[0] ^ g[0]) & mask; f[0] = f[0] ^ x0; + i32 x1 = (f[1] ^ g[1]) & mask; f[1] = f[1] ^ x1; + i32 x2 = (f[2] ^ g[2]) & mask; f[2] = f[2] ^ x2; + i32 x3 = (f[3] ^ g[3]) & mask; f[3] = f[3] ^ x3; + i32 x4 = (f[4] ^ g[4]) & mask; f[4] = f[4] ^ x4; + i32 x5 = (f[5] ^ g[5]) & mask; f[5] = f[5] ^ x5; + i32 x6 = (f[6] ^ g[6]) & mask; f[6] = f[6] ^ x6; + i32 x7 = (f[7] ^ g[7]) & mask; f[7] = f[7] ^ x7; + i32 x8 = (f[8] ^ g[8]) & mask; f[8] = f[8] ^ x8; + i32 x9 = (f[9] ^ g[9]) & mask; f[9] = f[9] ^ x9; +} + + +// Signed carry propagation +// ------------------------ +// +// Let t be a number. It can be uniquely decomposed thus: +// +// t = h*2^26 + l +// such that -2^25 <= l < 2^25 +// +// Let c = (t + 2^25) / 2^26 (rounded down) +// c = (h*2^26 + l + 2^25) / 2^26 (rounded down) +// c = h + (l + 2^25) / 2^26 (rounded down) +// c = h (exactly) +// Because 0 <= l + 2^25 < 2^26 +// +// Let u = t - c*2^26 +// u = h*2^26 + l - h*2^26 +// u = l +// Therefore, -2^25 <= u < 2^25 +// +// Additionally, if |t| < x, then |h| < x/2^26 (rounded down) +// +// Notations: +// - In C, 1<<25 means 2^25. +// - In C, x>>25 means floor(x / (2^25)). +// - All of the above applies with 25 & 24 as well as 26 & 25. +// +// +// Note on negative right shifts +// ----------------------------- +// +// In C, x >> n, where x is a negative integer, is implementation +// defined. In practice, all platforms do arithmetic shift, which is +// equivalent to division by 2^26, rounded down. Some compilers, like +// GCC, even guarantee it. +// +// If we ever stumble upon a platform that does not propagate the sign +// bit (we won't), visible failures will show at the slightest test, and +// the signed shifts can be replaced by the following: +// +// typedef struct { i64 x:39; } s25; +// typedef struct { i64 x:38; } s26; +// i64 shift25(i64 x) { s25 s; s.x = ((u64)x)>>25; return s.x; } +// i64 shift26(i64 x) { s26 s; s.x = ((u64)x)>>26; return s.x; } +// +// Current compilers cannot optimise this, causing a 30% drop in +// performance. Fairly expensive for something that never happens. +// +// +// Precondition +// ------------ +// +// |t0| < 2^63 +// |t1|..|t9| < 2^62 +// +// Algorithm +// --------- +// c = t0 + 2^25 / 2^26 -- |c| <= 2^36 +// t0 -= c * 2^26 -- |t0| <= 2^25 +// t1 += c -- |t1| <= 2^63 +// +// c = t4 + 2^25 / 2^26 -- |c| <= 2^36 +// t4 -= c * 2^26 -- |t4| <= 2^25 +// t5 += c -- |t5| <= 2^63 +// +// c = t1 + 2^24 / 2^25 -- |c| <= 2^38 +// t1 -= c * 2^25 -- |t1| <= 2^24 +// t2 += c -- |t2| <= 2^63 +// +// c = t5 + 2^24 / 2^25 -- |c| <= 2^38 +// t5 -= c * 2^25 -- |t5| <= 2^24 +// t6 += c -- |t6| <= 2^63 +// +// c = t2 + 2^25 / 2^26 -- |c| <= 2^37 +// t2 -= c * 2^26 -- |t2| <= 2^25 < 1.1 * 2^25 (final t2) +// t3 += c -- |t3| <= 2^63 +// +// c = t6 + 2^25 / 2^26 -- |c| <= 2^37 +// t6 -= c * 2^26 -- |t6| <= 2^25 < 1.1 * 2^25 (final t6) +// t7 += c -- |t7| <= 2^63 +// +// c = t3 + 2^24 / 2^25 -- |c| <= 2^38 +// t3 -= c * 2^25 -- |t3| <= 2^24 < 1.1 * 2^24 (final t3) +// t4 += c -- |t4| <= 2^25 + 2^38 < 2^39 +// +// c = t7 + 2^24 / 2^25 -- |c| <= 2^38 +// t7 -= c * 2^25 -- |t7| <= 2^24 < 1.1 * 2^24 (final t7) +// t8 += c -- |t8| <= 2^63 +// +// c = t4 + 2^25 / 2^26 -- |c| <= 2^13 +// t4 -= c * 2^26 -- |t4| <= 2^25 < 1.1 * 2^25 (final t4) +// t5 += c -- |t5| <= 2^24 + 2^13 < 1.1 * 2^24 (final t5) +// +// c = t8 + 2^25 / 2^26 -- |c| <= 2^37 +// t8 -= c * 2^26 -- |t8| <= 2^25 < 1.1 * 2^25 (final t8) +// t9 += c -- |t9| <= 2^63 +// +// c = t9 + 2^24 / 2^25 -- |c| <= 2^38 +// t9 -= c * 2^25 -- |t9| <= 2^24 < 1.1 * 2^24 (final t9) +// t0 += c * 19 -- |t0| <= 2^25 + 2^38*19 < 2^44 +// +// c = t0 + 2^25 / 2^26 -- |c| <= 2^18 +// t0 -= c * 2^26 -- |t0| <= 2^25 < 1.1 * 2^25 (final t0) +// t1 += c -- |t1| <= 2^24 + 2^18 < 1.1 * 2^24 (final t1) +// +// Postcondition +// ------------- +// |t0|, |t2|, |t4|, |t6|, |t8| < 1.1 * 2^25 +// |t1|, |t3|, |t5|, |t7|, |t9| < 1.1 * 2^24 +#define FE_CARRY \ + i64 c; \ + c = (t0 + ((i64)1<<25)) >> 26; t0 -= c * ((i64)1 << 26); t1 += c; \ + c = (t4 + ((i64)1<<25)) >> 26; t4 -= c * ((i64)1 << 26); t5 += c; \ + c = (t1 + ((i64)1<<24)) >> 25; t1 -= c * ((i64)1 << 25); t2 += c; \ + c = (t5 + ((i64)1<<24)) >> 25; t5 -= c * ((i64)1 << 25); t6 += c; \ + c = (t2 + ((i64)1<<25)) >> 26; t2 -= c * ((i64)1 << 26); t3 += c; \ + c = (t6 + ((i64)1<<25)) >> 26; t6 -= c * ((i64)1 << 26); t7 += c; \ + c = (t3 + ((i64)1<<24)) >> 25; t3 -= c * ((i64)1 << 25); t4 += c; \ + c = (t7 + ((i64)1<<24)) >> 25; t7 -= c * ((i64)1 << 25); t8 += c; \ + c = (t4 + ((i64)1<<25)) >> 26; t4 -= c * ((i64)1 << 26); t5 += c; \ + c = (t8 + ((i64)1<<25)) >> 26; t8 -= c * ((i64)1 << 26); t9 += c; \ + c = (t9 + ((i64)1<<24)) >> 25; t9 -= c * ((i64)1 << 25); t0 += c * 19; \ + c = (t0 + ((i64)1<<25)) >> 26; t0 -= c * ((i64)1 << 26); t1 += c; \ + h[0]=(i32)t0; h[1]=(i32)t1; h[2]=(i32)t2; h[3]=(i32)t3; h[4]=(i32)t4; \ + h[5]=(i32)t5; h[6]=(i32)t6; h[7]=(i32)t7; h[8]=(i32)t8; h[9]=(i32)t9 + +// Decodes a field element from a byte buffer. +// mask specifies how many bits we ignore. +// Traditionally we ignore 1. It's useful for EdDSA, +// which uses that bit to denote the sign of x. +// Elligator however uses positive representatives, +// which means ignoring 2 bits instead. +static void fe_frombytes_mask(fe h, const u8 s[32], unsigned nb_mask) +{ + u32 mask = 0xffffff >> nb_mask; + i64 t0 = load32_le(s); // t0 < 2^32 + i64 t1 = load24_le(s + 4) << 6; // t1 < 2^30 + i64 t2 = load24_le(s + 7) << 5; // t2 < 2^29 + i64 t3 = load24_le(s + 10) << 3; // t3 < 2^27 + i64 t4 = load24_le(s + 13) << 2; // t4 < 2^26 + i64 t5 = load32_le(s + 16); // t5 < 2^32 + i64 t6 = load24_le(s + 20) << 7; // t6 < 2^31 + i64 t7 = load24_le(s + 23) << 5; // t7 < 2^29 + i64 t8 = load24_le(s + 26) << 4; // t8 < 2^28 + i64 t9 = (load24_le(s + 29) & mask) << 2; // t9 < 2^25 + FE_CARRY; // Carry precondition OK +} + +static void fe_frombytes(fe h, const u8 s[32]) +{ + fe_frombytes_mask(h, s, 1); +} + + +// Precondition +// |h[0]|, |h[2]|, |h[4]|, |h[6]|, |h[8]| < 1.1 * 2^25 +// |h[1]|, |h[3]|, |h[5]|, |h[7]|, |h[9]| < 1.1 * 2^24 +// +// Therefore, |h| < 2^255-19 +// There are two possibilities: +// +// - If h is positive, all we need to do is reduce its individual +// limbs down to their tight positive range. +// - If h is negative, we also need to add 2^255-19 to it. +// Or just remove 19 and chop off any excess bit. +static void fe_tobytes(u8 s[32], const fe h) +{ + i32 t[10]; + COPY(t, h, 10); + i32 q = (19 * t[9] + (((i32) 1) << 24)) >> 25; + // |t9| < 1.1 * 2^24 + // -1.1 * 2^24 < t9 < 1.1 * 2^24 + // -21 * 2^24 < 19 * t9 < 21 * 2^24 + // -2^29 < 19 * t9 + 2^24 < 2^29 + // -2^29 / 2^25 < (19 * t9 + 2^24) / 2^25 < 2^29 / 2^25 + // -16 < (19 * t9 + 2^24) / 2^25 < 16 + FOR (i, 0, 5) { + q += t[2*i ]; q >>= 26; // q = 0 or -1 + q += t[2*i+1]; q >>= 25; // q = 0 or -1 + } + // q = 0 iff h >= 0 + // q = -1 iff h < 0 + // Adding q * 19 to h reduces h to its proper range. + q *= 19; // Shift carry back to the beginning + FOR (i, 0, 5) { + t[i*2 ] += q; q = t[i*2 ] >> 26; t[i*2 ] -= q * ((i32)1 << 26); + t[i*2+1] += q; q = t[i*2+1] >> 25; t[i*2+1] -= q * ((i32)1 << 25); + } + // h is now fully reduced, and q represents the excess bit. + + store32_le(s + 0, ((u32)t[0] >> 0) | ((u32)t[1] << 26)); + store32_le(s + 4, ((u32)t[1] >> 6) | ((u32)t[2] << 19)); + store32_le(s + 8, ((u32)t[2] >> 13) | ((u32)t[3] << 13)); + store32_le(s + 12, ((u32)t[3] >> 19) | ((u32)t[4] << 6)); + store32_le(s + 16, ((u32)t[5] >> 0) | ((u32)t[6] << 25)); + store32_le(s + 20, ((u32)t[6] >> 7) | ((u32)t[7] << 19)); + store32_le(s + 24, ((u32)t[7] >> 13) | ((u32)t[8] << 12)); + store32_le(s + 28, ((u32)t[8] >> 20) | ((u32)t[9] << 6)); + + WIPE_BUFFER(t); +} + +// Precondition +// ------------- +// |f0|, |f2|, |f4|, |f6|, |f8| < 1.65 * 2^26 +// |f1|, |f3|, |f5|, |f7|, |f9| < 1.65 * 2^25 +// +// |g0|, |g2|, |g4|, |g6|, |g8| < 1.65 * 2^26 +// |g1|, |g3|, |g5|, |g7|, |g9| < 1.65 * 2^25 +static void fe_mul_small(fe h, const fe f, i32 g) +{ + i64 t0 = f[0] * (i64) g; i64 t1 = f[1] * (i64) g; + i64 t2 = f[2] * (i64) g; i64 t3 = f[3] * (i64) g; + i64 t4 = f[4] * (i64) g; i64 t5 = f[5] * (i64) g; + i64 t6 = f[6] * (i64) g; i64 t7 = f[7] * (i64) g; + i64 t8 = f[8] * (i64) g; i64 t9 = f[9] * (i64) g; + // |t0|, |t2|, |t4|, |t6|, |t8| < 1.65 * 2^26 * 2^31 < 2^58 + // |t1|, |t3|, |t5|, |t7|, |t9| < 1.65 * 2^25 * 2^31 < 2^57 + + FE_CARRY; // Carry precondition OK +} + +// Precondition +// ------------- +// |f0|, |f2|, |f4|, |f6|, |f8| < 1.65 * 2^26 +// |f1|, |f3|, |f5|, |f7|, |f9| < 1.65 * 2^25 +// +// |g0|, |g2|, |g4|, |g6|, |g8| < 1.65 * 2^26 +// |g1|, |g3|, |g5|, |g7|, |g9| < 1.65 * 2^25 +static void fe_mul(fe h, const fe f, const fe g) +{ + // Everything is unrolled and put in temporary variables. + // We could roll the loop, but that would make curve25519 twice as slow. + i32 f0 = f[0]; i32 f1 = f[1]; i32 f2 = f[2]; i32 f3 = f[3]; i32 f4 = f[4]; + i32 f5 = f[5]; i32 f6 = f[6]; i32 f7 = f[7]; i32 f8 = f[8]; i32 f9 = f[9]; + i32 g0 = g[0]; i32 g1 = g[1]; i32 g2 = g[2]; i32 g3 = g[3]; i32 g4 = g[4]; + i32 g5 = g[5]; i32 g6 = g[6]; i32 g7 = g[7]; i32 g8 = g[8]; i32 g9 = g[9]; + i32 F1 = f1*2; i32 F3 = f3*2; i32 F5 = f5*2; i32 F7 = f7*2; i32 F9 = f9*2; + i32 G1 = g1*19; i32 G2 = g2*19; i32 G3 = g3*19; + i32 G4 = g4*19; i32 G5 = g5*19; i32 G6 = g6*19; + i32 G7 = g7*19; i32 G8 = g8*19; i32 G9 = g9*19; + // |F1|, |F3|, |F5|, |F7|, |F9| < 1.65 * 2^26 + // |G0|, |G2|, |G4|, |G6|, |G8| < 2^31 + // |G1|, |G3|, |G5|, |G7|, |G9| < 2^30 + + i64 t0 = f0*(i64)g0 + F1*(i64)G9 + f2*(i64)G8 + F3*(i64)G7 + f4*(i64)G6 + + F5*(i64)G5 + f6*(i64)G4 + F7*(i64)G3 + f8*(i64)G2 + F9*(i64)G1; + i64 t1 = f0*(i64)g1 + f1*(i64)g0 + f2*(i64)G9 + f3*(i64)G8 + f4*(i64)G7 + + f5*(i64)G6 + f6*(i64)G5 + f7*(i64)G4 + f8*(i64)G3 + f9*(i64)G2; + i64 t2 = f0*(i64)g2 + F1*(i64)g1 + f2*(i64)g0 + F3*(i64)G9 + f4*(i64)G8 + + F5*(i64)G7 + f6*(i64)G6 + F7*(i64)G5 + f8*(i64)G4 + F9*(i64)G3; + i64 t3 = f0*(i64)g3 + f1*(i64)g2 + f2*(i64)g1 + f3*(i64)g0 + f4*(i64)G9 + + f5*(i64)G8 + f6*(i64)G7 + f7*(i64)G6 + f8*(i64)G5 + f9*(i64)G4; + i64 t4 = f0*(i64)g4 + F1*(i64)g3 + f2*(i64)g2 + F3*(i64)g1 + f4*(i64)g0 + + F5*(i64)G9 + f6*(i64)G8 + F7*(i64)G7 + f8*(i64)G6 + F9*(i64)G5; + i64 t5 = f0*(i64)g5 + f1*(i64)g4 + f2*(i64)g3 + f3*(i64)g2 + f4*(i64)g1 + + f5*(i64)g0 + f6*(i64)G9 + f7*(i64)G8 + f8*(i64)G7 + f9*(i64)G6; + i64 t6 = f0*(i64)g6 + F1*(i64)g5 + f2*(i64)g4 + F3*(i64)g3 + f4*(i64)g2 + + F5*(i64)g1 + f6*(i64)g0 + F7*(i64)G9 + f8*(i64)G8 + F9*(i64)G7; + i64 t7 = f0*(i64)g7 + f1*(i64)g6 + f2*(i64)g5 + f3*(i64)g4 + f4*(i64)g3 + + f5*(i64)g2 + f6*(i64)g1 + f7*(i64)g0 + f8*(i64)G9 + f9*(i64)G8; + i64 t8 = f0*(i64)g8 + F1*(i64)g7 + f2*(i64)g6 + F3*(i64)g5 + f4*(i64)g4 + + F5*(i64)g3 + f6*(i64)g2 + F7*(i64)g1 + f8*(i64)g0 + F9*(i64)G9; + i64 t9 = f0*(i64)g9 + f1*(i64)g8 + f2*(i64)g7 + f3*(i64)g6 + f4*(i64)g5 + + f5*(i64)g4 + f6*(i64)g3 + f7*(i64)g2 + f8*(i64)g1 + f9*(i64)g0; + // t0 < 0.67 * 2^61 + // t1 < 0.41 * 2^61 + // t2 < 0.52 * 2^61 + // t3 < 0.32 * 2^61 + // t4 < 0.38 * 2^61 + // t5 < 0.22 * 2^61 + // t6 < 0.23 * 2^61 + // t7 < 0.13 * 2^61 + // t8 < 0.09 * 2^61 + // t9 < 0.03 * 2^61 + + FE_CARRY; // Everything below 2^62, Carry precondition OK +} + +// Precondition +// ------------- +// |f0|, |f2|, |f4|, |f6|, |f8| < 1.65 * 2^26 +// |f1|, |f3|, |f5|, |f7|, |f9| < 1.65 * 2^25 +// +// Note: we could use fe_mul() for this, but this is significantly faster +static void fe_sq(fe h, const fe f) +{ + i32 f0 = f[0]; i32 f1 = f[1]; i32 f2 = f[2]; i32 f3 = f[3]; i32 f4 = f[4]; + i32 f5 = f[5]; i32 f6 = f[6]; i32 f7 = f[7]; i32 f8 = f[8]; i32 f9 = f[9]; + i32 f0_2 = f0*2; i32 f1_2 = f1*2; i32 f2_2 = f2*2; i32 f3_2 = f3*2; + i32 f4_2 = f4*2; i32 f5_2 = f5*2; i32 f6_2 = f6*2; i32 f7_2 = f7*2; + i32 f5_38 = f5*38; i32 f6_19 = f6*19; i32 f7_38 = f7*38; + i32 f8_19 = f8*19; i32 f9_38 = f9*38; + // |f0_2| , |f2_2| , |f4_2| , |f6_2| , |f8_2| < 1.65 * 2^27 + // |f1_2| , |f3_2| , |f5_2| , |f7_2| , |f9_2| < 1.65 * 2^26 + // |f5_38|, |f6_19|, |f7_38|, |f8_19|, |f9_38| < 2^31 + + i64 t0 = f0 *(i64)f0 + f1_2*(i64)f9_38 + f2_2*(i64)f8_19 + + f3_2*(i64)f7_38 + f4_2*(i64)f6_19 + f5 *(i64)f5_38; + i64 t1 = f0_2*(i64)f1 + f2 *(i64)f9_38 + f3_2*(i64)f8_19 + + f4 *(i64)f7_38 + f5_2*(i64)f6_19; + i64 t2 = f0_2*(i64)f2 + f1_2*(i64)f1 + f3_2*(i64)f9_38 + + f4_2*(i64)f8_19 + f5_2*(i64)f7_38 + f6 *(i64)f6_19; + i64 t3 = f0_2*(i64)f3 + f1_2*(i64)f2 + f4 *(i64)f9_38 + + f5_2*(i64)f8_19 + f6 *(i64)f7_38; + i64 t4 = f0_2*(i64)f4 + f1_2*(i64)f3_2 + f2 *(i64)f2 + + f5_2*(i64)f9_38 + f6_2*(i64)f8_19 + f7 *(i64)f7_38; + i64 t5 = f0_2*(i64)f5 + f1_2*(i64)f4 + f2_2*(i64)f3 + + f6 *(i64)f9_38 + f7_2*(i64)f8_19; + i64 t6 = f0_2*(i64)f6 + f1_2*(i64)f5_2 + f2_2*(i64)f4 + + f3_2*(i64)f3 + f7_2*(i64)f9_38 + f8 *(i64)f8_19; + i64 t7 = f0_2*(i64)f7 + f1_2*(i64)f6 + f2_2*(i64)f5 + + f3_2*(i64)f4 + f8 *(i64)f9_38; + i64 t8 = f0_2*(i64)f8 + f1_2*(i64)f7_2 + f2_2*(i64)f6 + + f3_2*(i64)f5_2 + f4 *(i64)f4 + f9 *(i64)f9_38; + i64 t9 = f0_2*(i64)f9 + f1_2*(i64)f8 + f2_2*(i64)f7 + + f3_2*(i64)f6 + f4 *(i64)f5_2; + // t0 < 0.67 * 2^61 + // t1 < 0.41 * 2^61 + // t2 < 0.52 * 2^61 + // t3 < 0.32 * 2^61 + // t4 < 0.38 * 2^61 + // t5 < 0.22 * 2^61 + // t6 < 0.23 * 2^61 + // t7 < 0.13 * 2^61 + // t8 < 0.09 * 2^61 + // t9 < 0.03 * 2^61 + + FE_CARRY; +} + +// Parity check. Returns 0 if even, 1 if odd +static int fe_isodd(const fe f) +{ + u8 s[32]; + fe_tobytes(s, f); + u8 isodd = s[0] & 1; + WIPE_BUFFER(s); + return isodd; +} + +// Returns 1 if equal, 0 if not equal +static int fe_isequal(const fe f, const fe g) +{ + u8 fs[32]; + u8 gs[32]; + fe_tobytes(fs, f); + fe_tobytes(gs, g); + int isdifferent = crypto_verify32(fs, gs); + WIPE_BUFFER(fs); + WIPE_BUFFER(gs); + return 1 + isdifferent; +} + +// Inverse square root. +// Returns true if x is a square, false otherwise. +// After the call: +// isr = sqrt(1/x) if x is a non-zero square. +// isr = sqrt(sqrt(-1)/x) if x is not a square. +// isr = 0 if x is zero. +// We do not guarantee the sign of the square root. +// +// Notes: +// Let quartic = x^((p-1)/4) +// +// x^((p-1)/2) = chi(x) +// quartic^2 = chi(x) +// quartic = sqrt(chi(x)) +// quartic = 1 or -1 or sqrt(-1) or -sqrt(-1) +// +// Note that x is a square if quartic is 1 or -1 +// There are 4 cases to consider: +// +// if quartic = 1 (x is a square) +// then x^((p-1)/4) = 1 +// x^((p-5)/4) * x = 1 +// x^((p-5)/4) = 1/x +// x^((p-5)/8) = sqrt(1/x) or -sqrt(1/x) +// +// if quartic = -1 (x is a square) +// then x^((p-1)/4) = -1 +// x^((p-5)/4) * x = -1 +// x^((p-5)/4) = -1/x +// x^((p-5)/8) = sqrt(-1) / sqrt(x) +// x^((p-5)/8) * sqrt(-1) = sqrt(-1)^2 / sqrt(x) +// x^((p-5)/8) * sqrt(-1) = -1/sqrt(x) +// x^((p-5)/8) * sqrt(-1) = -sqrt(1/x) or sqrt(1/x) +// +// if quartic = sqrt(-1) (x is not a square) +// then x^((p-1)/4) = sqrt(-1) +// x^((p-5)/4) * x = sqrt(-1) +// x^((p-5)/4) = sqrt(-1)/x +// x^((p-5)/8) = sqrt(sqrt(-1)/x) or -sqrt(sqrt(-1)/x) +// +// Note that the product of two non-squares is always a square: +// For any non-squares a and b, chi(a) = -1 and chi(b) = -1. +// Since chi(x) = x^((p-1)/2), chi(a)*chi(b) = chi(a*b) = 1. +// Therefore a*b is a square. +// +// Since sqrt(-1) and x are both non-squares, their product is a +// square, and we can compute their square root. +// +// if quartic = -sqrt(-1) (x is not a square) +// then x^((p-1)/4) = -sqrt(-1) +// x^((p-5)/4) * x = -sqrt(-1) +// x^((p-5)/4) = -sqrt(-1)/x +// x^((p-5)/8) = sqrt(-sqrt(-1)/x) +// x^((p-5)/8) = sqrt( sqrt(-1)/x) * sqrt(-1) +// x^((p-5)/8) * sqrt(-1) = sqrt( sqrt(-1)/x) * sqrt(-1)^2 +// x^((p-5)/8) * sqrt(-1) = sqrt( sqrt(-1)/x) * -1 +// x^((p-5)/8) * sqrt(-1) = -sqrt(sqrt(-1)/x) or sqrt(sqrt(-1)/x) +static int invsqrt(fe isr, const fe x) +{ + fe t0, t1, t2; + + // t0 = x^((p-5)/8) + // Can be achieved with a simple double & add ladder, + // but it would be slower. + fe_sq(t0, x); + fe_sq(t1,t0); fe_sq(t1, t1); fe_mul(t1, x, t1); + fe_mul(t0, t0, t1); + fe_sq(t0, t0); fe_mul(t0, t1, t0); + fe_sq(t1, t0); FOR (i, 1, 5) { fe_sq(t1, t1); } fe_mul(t0, t1, t0); + fe_sq(t1, t0); FOR (i, 1, 10) { fe_sq(t1, t1); } fe_mul(t1, t1, t0); + fe_sq(t2, t1); FOR (i, 1, 20) { fe_sq(t2, t2); } fe_mul(t1, t2, t1); + fe_sq(t1, t1); FOR (i, 1, 10) { fe_sq(t1, t1); } fe_mul(t0, t1, t0); + fe_sq(t1, t0); FOR (i, 1, 50) { fe_sq(t1, t1); } fe_mul(t1, t1, t0); + fe_sq(t2, t1); FOR (i, 1, 100) { fe_sq(t2, t2); } fe_mul(t1, t2, t1); + fe_sq(t1, t1); FOR (i, 1, 50) { fe_sq(t1, t1); } fe_mul(t0, t1, t0); + fe_sq(t0, t0); FOR (i, 1, 2) { fe_sq(t0, t0); } fe_mul(t0, t0, x); + + // quartic = x^((p-1)/4) + i32 *quartic = t1; + fe_sq (quartic, t0); + fe_mul(quartic, quartic, x); + + i32 *check = t2; + fe_0 (check); int z0 = fe_isequal(x , check); + fe_1 (check); int p1 = fe_isequal(quartic, check); + fe_neg(check, check ); int m1 = fe_isequal(quartic, check); + fe_neg(check, sqrtm1); int ms = fe_isequal(quartic, check); + + // if quartic == -1 or sqrt(-1) + // then isr = x^((p-1)/4) * sqrt(-1) + // else isr = x^((p-1)/4) + fe_mul(isr, t0, sqrtm1); + fe_ccopy(isr, t0, 1 - (m1 | ms)); + + WIPE_BUFFER(t0); + WIPE_BUFFER(t1); + WIPE_BUFFER(t2); + return p1 | m1 | z0; +} + +// Inverse in terms of inverse square root. +// Requires two additional squarings to get rid of the sign. +// +// 1/x = x * (+invsqrt(x^2))^2 +// = x * (-invsqrt(x^2))^2 +// +// A fully optimised exponentiation by p-1 would save 6 field +// multiplications, but it would require more code. +static void fe_invert(fe out, const fe x) +{ + fe tmp; + fe_sq(tmp, x); + invsqrt(tmp, tmp); + fe_sq(tmp, tmp); + fe_mul(out, tmp, x); + WIPE_BUFFER(tmp); +} + +// trim a scalar for scalar multiplication +void crypto_eddsa_trim_scalar(u8 out[32], const u8 in[32]) +{ + COPY(out, in, 32); + out[ 0] &= 248; + out[31] &= 127; + out[31] |= 64; +} + +// get bit from scalar at position i +static int scalar_bit(const u8 s[32], int i) +{ + if (i < 0) { return 0; } // handle -1 for sliding windows + return (s[i>>3] >> (i&7)) & 1; +} + +/////////////// +/// X-25519 /// Taken from SUPERCOP's ref10 implementation. +/////////////// +static void scalarmult(u8 q[32], const u8 scalar[32], const u8 p[32], + int nb_bits) +{ + // computes the scalar product + fe x1; + fe_frombytes(x1, p); + + // computes the actual scalar product (the result is in x2 and z2) + fe x2, z2, x3, z3, t0, t1; + // Montgomery ladder + // In projective coordinates, to avoid divisions: x = X / Z + // We don't care about the y coordinate, it's only 1 bit of information + fe_1(x2); fe_0(z2); // "zero" point + fe_copy(x3, x1); fe_1(z3); // "one" point + int swap = 0; + for (int pos = nb_bits-1; pos >= 0; --pos) { + // constant time conditional swap before ladder step + int b = scalar_bit(scalar, pos); + swap ^= b; // xor trick avoids swapping at the end of the loop + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + swap = b; // anticipates one last swap after the loop + + // Montgomery ladder step: replaces (P2, P3) by (P2*2, P2+P3) + // with differential addition + fe_sub(t0, x3, z3); + fe_sub(t1, x2, z2); + fe_add(x2, x2, z2); + fe_add(z2, x3, z3); + fe_mul(z3, t0, x2); + fe_mul(z2, z2, t1); + fe_sq (t0, t1 ); + fe_sq (t1, x2 ); + fe_add(x3, z3, z2); + fe_sub(z2, z3, z2); + fe_mul(x2, t1, t0); + fe_sub(t1, t1, t0); + fe_sq (z2, z2 ); + fe_mul_small(z3, t1, 121666); + fe_sq (x3, x3 ); + fe_add(t0, t0, z3); + fe_mul(z3, x1, z2); + fe_mul(z2, t1, t0); + } + // last swap is necessary to compensate for the xor trick + // Note: after this swap, P3 == P2 + P1. + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + + // normalises the coordinates: x == X / Z + fe_invert(z2, z2); + fe_mul(x2, x2, z2); + fe_tobytes(q, x2); + + WIPE_BUFFER(x1); + WIPE_BUFFER(x2); WIPE_BUFFER(z2); WIPE_BUFFER(t0); + WIPE_BUFFER(x3); WIPE_BUFFER(z3); WIPE_BUFFER(t1); +} + +void crypto_x25519(u8 raw_shared_secret[32], + const u8 your_secret_key [32], + const u8 their_public_key [32]) +{ + // restrict the possible scalar values + u8 e[32]; + crypto_eddsa_trim_scalar(e, your_secret_key); + scalarmult(raw_shared_secret, e, their_public_key, 255); + WIPE_BUFFER(e); +} + +void crypto_x25519_public_key(u8 public_key[32], + const u8 secret_key[32]) +{ + static const u8 base_point[32] = {9}; + crypto_x25519(public_key, secret_key, base_point); +} + +/////////////////////////// +/// Arithmetic modulo L /// +/////////////////////////// +static const u32 L[8] = { + 0x5cf5d3ed, 0x5812631a, 0xa2f79cd6, 0x14def9de, + 0x00000000, 0x00000000, 0x00000000, 0x10000000, +}; + +// p = a*b + p +static void multiply(u32 p[16], const u32 a[8], const u32 b[8]) +{ + FOR (i, 0, 8) { + u64 carry = 0; + FOR (j, 0, 8) { + carry += p[i+j] + (u64)a[i] * b[j]; + p[i+j] = (u32)carry; + carry >>= 32; + } + p[i+8] = (u32)carry; + } +} + +static int is_above_l(const u32 x[8]) +{ + // We work with L directly, in a 2's complement encoding + // (-L == ~L + 1) + u64 carry = 1; + FOR (i, 0, 8) { + carry += (u64)x[i] + (~L[i] & 0xffffffff); + carry >>= 32; + } + return (int)carry; // carry is either 0 or 1 +} + +// Final reduction modulo L, by conditionally removing L. +// if x < l , then r = x +// if l <= x 2*l, then r = x-l +// otherwise the result will be wrong +static void remove_l(u32 r[8], const u32 x[8]) +{ + u64 carry = (u64)is_above_l(x); + u32 mask = ~(u32)carry + 1; // carry == 0 or 1 + FOR (i, 0, 8) { + carry += (u64)x[i] + (~L[i] & mask); + r[i] = (u32)carry; + carry >>= 32; + } +} + +// Full reduction modulo L (Barrett reduction) +static void mod_l(u8 reduced[32], const u32 x[16]) +{ + static const u32 r[9] = { + 0x0a2c131b,0xed9ce5a3,0x086329a7,0x2106215d, + 0xffffffeb,0xffffffff,0xffffffff,0xffffffff,0xf, + }; + // xr = x * r + u32 xr[25] = {0}; + FOR (i, 0, 9) { + u64 carry = 0; + FOR (j, 0, 16) { + carry += xr[i+j] + (u64)r[i] * x[j]; + xr[i+j] = (u32)carry; + carry >>= 32; + } + xr[i+16] = (u32)carry; + } + // xr = floor(xr / 2^512) * L + // Since the result is guaranteed to be below 2*L, + // it is enough to only compute the first 256 bits. + // The division is performed by saying xr[i+16]. (16 * 32 = 512) + ZERO(xr, 8); + FOR (i, 0, 8) { + u64 carry = 0; + FOR (j, 0, 8-i) { + carry += xr[i+j] + (u64)xr[i+16] * L[j]; + xr[i+j] = (u32)carry; + carry >>= 32; + } + } + // xr = x - xr + u64 carry = 1; + FOR (i, 0, 8) { + carry += (u64)x[i] + (~xr[i] & 0xffffffff); + xr[i] = (u32)carry; + carry >>= 32; + } + // Final reduction modulo L (conditional subtraction) + remove_l(xr, xr); + store32_le_buf(reduced, xr, 8); + + WIPE_BUFFER(xr); +} + +void crypto_eddsa_reduce(u8 reduced[32], const u8 expanded[64]) +{ + u32 x[16]; + load32_le_buf(x, expanded, 16); + mod_l(reduced, x); + WIPE_BUFFER(x); +} + +// r = (a * b) + c +void crypto_eddsa_mul_add(u8 r[32], + const u8 a[32], const u8 b[32], const u8 c[32]) +{ + u32 A[8]; load32_le_buf(A, a, 8); + u32 B[8]; load32_le_buf(B, b, 8); + u32 p[16]; load32_le_buf(p, c, 8); ZERO(p + 8, 8); + multiply(p, A, B); + mod_l(r, p); + WIPE_BUFFER(p); + WIPE_BUFFER(A); + WIPE_BUFFER(B); +} + +/////////////// +/// Ed25519 /// +/////////////// + +// Point (group element, ge) in a twisted Edwards curve, +// in extended projective coordinates. +// ge : x = X/Z, y = Y/Z, T = XY/Z +// ge_cached : Yp = X+Y, Ym = X-Y, T2 = T*D2 +// ge_precomp: Z = 1 +typedef struct { fe X; fe Y; fe Z; fe T; } ge; +typedef struct { fe Yp; fe Ym; fe Z; fe T2; } ge_cached; +typedef struct { fe Yp; fe Ym; fe T2; } ge_precomp; + +static void ge_zero(ge *p) +{ + fe_0(p->X); + fe_1(p->Y); + fe_1(p->Z); + fe_0(p->T); +} + +static void ge_tobytes(u8 s[32], const ge *h) +{ + fe recip, x, y; + fe_invert(recip, h->Z); + fe_mul(x, h->X, recip); + fe_mul(y, h->Y, recip); + fe_tobytes(s, y); + s[31] ^= (u8)fe_isodd(x) << 7; + + WIPE_BUFFER(recip); + WIPE_BUFFER(x); + WIPE_BUFFER(y); +} + +// h = -s, where s is a point encoded in 32 bytes +// +// Variable time! Inputs must not be secret! +// => Use only to *check* signatures. +// +// From the specifications: +// The encoding of s contains y and the sign of x +// x = sqrt((y^2 - 1) / (d*y^2 + 1)) +// In extended coordinates: +// X = x, Y = y, Z = 1, T = x*y +// +// Note that num * den is a square iff num / den is a square +// If num * den is not a square, the point was not on the curve. +// From the above: +// Let num = y^2 - 1 +// Let den = d*y^2 + 1 +// x = sqrt((y^2 - 1) / (d*y^2 + 1)) +// x = sqrt(num / den) +// x = sqrt(num^2 / (num * den)) +// x = num * sqrt(1 / (num * den)) +// +// Therefore, we can just compute: +// num = y^2 - 1 +// den = d*y^2 + 1 +// isr = invsqrt(num * den) // abort if not square +// x = num * isr +// Finally, negate x if its sign is not as specified. +static int ge_frombytes_neg_vartime(ge *h, const u8 s[32]) +{ + fe_frombytes(h->Y, s); + fe_1(h->Z); + fe_sq (h->T, h->Y); // t = y^2 + fe_mul(h->X, h->T, d ); // x = d*y^2 + fe_sub(h->T, h->T, h->Z); // t = y^2 - 1 + fe_add(h->X, h->X, h->Z); // x = d*y^2 + 1 + fe_mul(h->X, h->T, h->X); // x = (y^2 - 1) * (d*y^2 + 1) + int is_square = invsqrt(h->X, h->X); + if (!is_square) { + return -1; // Not on the curve, abort + } + fe_mul(h->X, h->T, h->X); // x = sqrt((y^2 - 1) / (d*y^2 + 1)) + if (fe_isodd(h->X) == (s[31] >> 7)) { + fe_neg(h->X, h->X); + } + fe_mul(h->T, h->X, h->Y); + return 0; +} + +static void ge_cache(ge_cached *c, const ge *p) +{ + fe_add (c->Yp, p->Y, p->X); + fe_sub (c->Ym, p->Y, p->X); + fe_copy(c->Z , p->Z ); + fe_mul (c->T2, p->T, D2 ); +} + +// Internal buffers are not wiped! Inputs must not be secret! +// => Use only to *check* signatures. +static void ge_add(ge *s, const ge *p, const ge_cached *q) +{ + fe a, b; + fe_add(a , p->Y, p->X ); + fe_sub(b , p->Y, p->X ); + fe_mul(a , a , q->Yp); + fe_mul(b , b , q->Ym); + fe_add(s->Y, a , b ); + fe_sub(s->X, a , b ); + + fe_add(s->Z, p->Z, p->Z ); + fe_mul(s->Z, s->Z, q->Z ); + fe_mul(s->T, p->T, q->T2); + fe_add(a , s->Z, s->T ); + fe_sub(b , s->Z, s->T ); + + fe_mul(s->T, s->X, s->Y); + fe_mul(s->X, s->X, b ); + fe_mul(s->Y, s->Y, a ); + fe_mul(s->Z, a , b ); +} + +// Internal buffers are not wiped! Inputs must not be secret! +// => Use only to *check* signatures. +static void ge_sub(ge *s, const ge *p, const ge_cached *q) +{ + ge_cached neg; + fe_copy(neg.Ym, q->Yp); + fe_copy(neg.Yp, q->Ym); + fe_copy(neg.Z , q->Z ); + fe_neg (neg.T2, q->T2); + ge_add(s, p, &neg); +} + +static void ge_madd(ge *s, const ge *p, const ge_precomp *q, fe a, fe b) +{ + fe_add(a , p->Y, p->X ); + fe_sub(b , p->Y, p->X ); + fe_mul(a , a , q->Yp); + fe_mul(b , b , q->Ym); + fe_add(s->Y, a , b ); + fe_sub(s->X, a , b ); + + fe_add(s->Z, p->Z, p->Z ); + fe_mul(s->T, p->T, q->T2); + fe_add(a , s->Z, s->T ); + fe_sub(b , s->Z, s->T ); + + fe_mul(s->T, s->X, s->Y); + fe_mul(s->X, s->X, b ); + fe_mul(s->Y, s->Y, a ); + fe_mul(s->Z, a , b ); +} + +// Internal buffers are not wiped! Inputs must not be secret! +// => Use only to *check* signatures. +static void ge_msub(ge *s, const ge *p, const ge_precomp *q, fe a, fe b) +{ + ge_precomp neg; + fe_copy(neg.Ym, q->Yp); + fe_copy(neg.Yp, q->Ym); + fe_neg (neg.T2, q->T2); + ge_madd(s, p, &neg, a, b); +} + +static void ge_double(ge *s, const ge *p, ge *q) +{ + fe_sq (q->X, p->X); + fe_sq (q->Y, p->Y); + fe_sq (q->Z, p->Z); // qZ = pZ^2 + fe_mul_small(q->Z, q->Z, 2); // qZ = pZ^2 * 2 + fe_add(q->T, p->X, p->Y); + fe_sq (s->T, q->T); + fe_add(q->T, q->Y, q->X); + fe_sub(q->Y, q->Y, q->X); + fe_sub(q->X, s->T, q->T); + fe_sub(q->Z, q->Z, q->Y); + + fe_mul(s->X, q->X , q->Z); + fe_mul(s->Y, q->T , q->Y); + fe_mul(s->Z, q->Y , q->Z); + fe_mul(s->T, q->X , q->T); +} + +// 5-bit signed window in cached format (Niels coordinates, Z=1) +static const ge_precomp b_window[8] = { + {{25967493,-14356035,29566456,3660896,-12694345, + 4014787,27544626,-11754271,-6079156,2047605,}, + {-12545711,934262,-2722910,3049990,-727428, + 9406986,12720692,5043384,19500929,-15469378,}, + {-8738181,4489570,9688441,-14785194,10184609, + -12363380,29287919,11864899,-24514362,-4438546,},}, + {{15636291,-9688557,24204773,-7912398,616977, + -16685262,27787600,-14772189,28944400,-1550024,}, + {16568933,4717097,-11556148,-1102322,15682896, + -11807043,16354577,-11775962,7689662,11199574,}, + {30464156,-5976125,-11779434,-15670865,23220365, + 15915852,7512774,10017326,-17749093,-9920357,},}, + {{10861363,11473154,27284546,1981175,-30064349, + 12577861,32867885,14515107,-15438304,10819380,}, + {4708026,6336745,20377586,9066809,-11272109, + 6594696,-25653668,12483688,-12668491,5581306,}, + {19563160,16186464,-29386857,4097519,10237984, + -4348115,28542350,13850243,-23678021,-15815942,},}, + {{5153746,9909285,1723747,-2777874,30523605, + 5516873,19480852,5230134,-23952439,-15175766,}, + {-30269007,-3463509,7665486,10083793,28475525, + 1649722,20654025,16520125,30598449,7715701,}, + {28881845,14381568,9657904,3680757,-20181635, + 7843316,-31400660,1370708,29794553,-1409300,},}, + {{-22518993,-6692182,14201702,-8745502,-23510406, + 8844726,18474211,-1361450,-13062696,13821877,}, + {-6455177,-7839871,3374702,-4740862,-27098617, + -10571707,31655028,-7212327,18853322,-14220951,}, + {4566830,-12963868,-28974889,-12240689,-7602672, + -2830569,-8514358,-10431137,2207753,-3209784,},}, + {{-25154831,-4185821,29681144,7868801,-6854661, + -9423865,-12437364,-663000,-31111463,-16132436,}, + {25576264,-2703214,7349804,-11814844,16472782, + 9300885,3844789,15725684,171356,6466918,}, + {23103977,13316479,9739013,-16149481,817875, + -15038942,8965339,-14088058,-30714912,16193877,},}, + {{-33521811,3180713,-2394130,14003687,-16903474, + -16270840,17238398,4729455,-18074513,9256800,}, + {-25182317,-4174131,32336398,5036987,-21236817, + 11360617,22616405,9761698,-19827198,630305,}, + {-13720693,2639453,-24237460,-7406481,9494427, + -5774029,-6554551,-15960994,-2449256,-14291300,},}, + {{-3151181,-5046075,9282714,6866145,-31907062, + -863023,-18940575,15033784,25105118,-7894876,}, + {-24326370,15950226,-31801215,-14592823,-11662737, + -5090925,1573892,-2625887,2198790,-15804619,}, + {-3099351,10324967,-2241613,7453183,-5446979, + -2735503,-13812022,-16236442,-32461234,-12290683,},}, +}; + +// Incremental sliding windows (left to right) +// Based on Roberto Maria Avanzi[2005] +typedef struct { + i16 next_index; // position of the next signed digit + i8 next_digit; // next signed digit (odd number below 2^window_width) + u8 next_check; // point at which we must check for a new window +} slide_ctx; + +static void slide_init(slide_ctx *ctx, const u8 scalar[32]) +{ + // scalar is guaranteed to be below L, either because we checked (s), + // or because we reduced it modulo L (h_ram). L is under 2^253, so + // so bits 253 to 255 are guaranteed to be zero. No need to test them. + // + // Note however that L is very close to 2^252, so bit 252 is almost + // always zero. If we were to start at bit 251, the tests wouldn't + // catch the off-by-one error (constructing one that does would be + // prohibitively expensive). + // + // We should still check bit 252, though. + int i = 252; + while (i > 0 && scalar_bit(scalar, i) == 0) { + i--; + } + ctx->next_check = (u8)(i + 1); + ctx->next_index = -1; + ctx->next_digit = -1; +} + +static int slide_step(slide_ctx *ctx, int width, int i, const u8 scalar[32]) +{ + if (i == ctx->next_check) { + if (scalar_bit(scalar, i) == scalar_bit(scalar, i - 1)) { + ctx->next_check--; + } else { + // compute digit of next window + int w = MIN(width, i + 1); + int v = -(scalar_bit(scalar, i) << (w-1)); + FOR_T (int, j, 0, w-1) { + v += scalar_bit(scalar, i-(w-1)+j) << j; + } + v += scalar_bit(scalar, i-w); + int lsb = v & (~v + 1); // smallest bit of v + int s = // log2(lsb) + (((lsb & 0xAA) != 0) << 0) | + (((lsb & 0xCC) != 0) << 1) | + (((lsb & 0xF0) != 0) << 2); + ctx->next_index = (i16)(i-(w-1)+s); + ctx->next_digit = (i8) (v >> s ); + ctx->next_check -= (u8) w; + } + } + return i == ctx->next_index ? ctx->next_digit: 0; +} + +#define P_W_WIDTH 3 // Affects the size of the stack +#define B_W_WIDTH 5 // Affects the size of the binary +#define P_W_SIZE (1<<(P_W_WIDTH-2)) + +int crypto_eddsa_check_equation(const u8 signature[64], const u8 public_key[32], + const u8 h[32]) +{ + ge minus_A; // -public_key + ge minus_R; // -first_half_of_signature + const u8 *s = signature + 32; + + // Check that A and R are on the curve + // Check that 0 <= S < L (prevents malleability) + // *Allow* non-cannonical encoding for A and R + { + u32 s32[8]; + load32_le_buf(s32, s, 8); + if (ge_frombytes_neg_vartime(&minus_A, public_key) || + ge_frombytes_neg_vartime(&minus_R, signature) || + is_above_l(s32)) { + return -1; + } + } + + // look-up table for minus_A + ge_cached lutA[P_W_SIZE]; + { + ge minus_A2, tmp; + ge_double(&minus_A2, &minus_A, &tmp); + ge_cache(&lutA[0], &minus_A); + FOR (i, 1, P_W_SIZE) { + ge_add(&tmp, &minus_A2, &lutA[i-1]); + ge_cache(&lutA[i], &tmp); + } + } + + // sum = [s]B - [h]A + // Merged double and add ladder, fused with sliding + slide_ctx h_slide; slide_init(&h_slide, h); + slide_ctx s_slide; slide_init(&s_slide, s); + int i = MAX(h_slide.next_check, s_slide.next_check); + ge *sum = &minus_A; // reuse minus_A for the sum + ge_zero(sum); + while (i >= 0) { + ge tmp; + ge_double(sum, sum, &tmp); + int h_digit = slide_step(&h_slide, P_W_WIDTH, i, h); + int s_digit = slide_step(&s_slide, B_W_WIDTH, i, s); + if (h_digit > 0) { ge_add(sum, sum, &lutA[ h_digit / 2]); } + if (h_digit < 0) { ge_sub(sum, sum, &lutA[-h_digit / 2]); } + fe t1, t2; + if (s_digit > 0) { ge_madd(sum, sum, b_window + s_digit/2, t1, t2); } + if (s_digit < 0) { ge_msub(sum, sum, b_window + -s_digit/2, t1, t2); } + i--; + } + + // Compare [8](sum-R) and the zero point + // The multiplication by 8 eliminates any low-order component + // and ensures consistency with batched verification. + ge_cached cached; + u8 check[32]; + static const u8 zero_point[32] = {1}; // Point of order 1 + ge_cache(&cached, &minus_R); + ge_add(sum, sum, &cached); + ge_double(sum, sum, &minus_R); // reuse minus_R as temporary + ge_double(sum, sum, &minus_R); // reuse minus_R as temporary + ge_double(sum, sum, &minus_R); // reuse minus_R as temporary + ge_tobytes(check, sum); + return crypto_verify32(check, zero_point); +} + +// 5-bit signed comb in cached format (Niels coordinates, Z=1) +static const ge_precomp b_comb_low[8] = { + {{-6816601,-2324159,-22559413,124364,18015490, + 8373481,19993724,1979872,-18549925,9085059,}, + {10306321,403248,14839893,9633706,8463310, + -8354981,-14305673,14668847,26301366,2818560,}, + {-22701500,-3210264,-13831292,-2927732,-16326337, + -14016360,12940910,177905,12165515,-2397893,},}, + {{-12282262,-7022066,9920413,-3064358,-32147467, + 2927790,22392436,-14852487,2719975,16402117,}, + {-7236961,-4729776,2685954,-6525055,-24242706, + -15940211,-6238521,14082855,10047669,12228189,}, + {-30495588,-12893761,-11161261,3539405,-11502464, + 16491580,-27286798,-15030530,-7272871,-15934455,},}, + {{17650926,582297,-860412,-187745,-12072900, + -10683391,-20352381,15557840,-31072141,-5019061,}, + {-6283632,-2259834,-4674247,-4598977,-4089240, + 12435688,-31278303,1060251,6256175,10480726,}, + {-13871026,2026300,-21928428,-2741605,-2406664, + -8034988,7355518,15733500,-23379862,7489131,},}, + {{6883359,695140,23196907,9644202,-33430614, + 11354760,-20134606,6388313,-8263585,-8491918,}, + {-7716174,-13605463,-13646110,14757414,-19430591, + -14967316,10359532,-11059670,-21935259,12082603,}, + {-11253345,-15943946,10046784,5414629,24840771, + 8086951,-6694742,9868723,15842692,-16224787,},}, + {{9639399,11810955,-24007778,-9320054,3912937, + -9856959,996125,-8727907,-8919186,-14097242,}, + {7248867,14468564,25228636,-8795035,14346339, + 8224790,6388427,-7181107,6468218,-8720783,}, + {15513115,15439095,7342322,-10157390,18005294, + -7265713,2186239,4884640,10826567,7135781,},}, + {{-14204238,5297536,-5862318,-6004934,28095835, + 4236101,-14203318,1958636,-16816875,3837147,}, + {-5511166,-13176782,-29588215,12339465,15325758, + -15945770,-8813185,11075932,-19608050,-3776283,}, + {11728032,9603156,-4637821,-5304487,-7827751, + 2724948,31236191,-16760175,-7268616,14799772,},}, + {{-28842672,4840636,-12047946,-9101456,-1445464, + 381905,-30977094,-16523389,1290540,12798615,}, + {27246947,-10320914,14792098,-14518944,5302070, + -8746152,-3403974,-4149637,-27061213,10749585,}, + {25572375,-6270368,-15353037,16037944,1146292, + 32198,23487090,9585613,24714571,-1418265,},}, + {{19844825,282124,-17583147,11004019,-32004269, + -2716035,6105106,-1711007,-21010044,14338445,}, + {8027505,8191102,-18504907,-12335737,25173494, + -5923905,15446145,7483684,-30440441,10009108,}, + {-14134701,-4174411,10246585,-14677495,33553567, + -14012935,23366126,15080531,-7969992,7663473,},}, +}; + +static const ge_precomp b_comb_high[8] = { + {{33055887,-4431773,-521787,6654165,951411, + -6266464,-5158124,6995613,-5397442,-6985227,}, + {4014062,6967095,-11977872,3960002,8001989, + 5130302,-2154812,-1899602,-31954493,-16173976,}, + {16271757,-9212948,23792794,731486,-25808309, + -3546396,6964344,-4767590,10976593,10050757,},}, + {{2533007,-4288439,-24467768,-12387405,-13450051, + 14542280,12876301,13893535,15067764,8594792,}, + {20073501,-11623621,3165391,-13119866,13188608, + -11540496,-10751437,-13482671,29588810,2197295,}, + {-1084082,11831693,6031797,14062724,14748428, + -8159962,-20721760,11742548,31368706,13161200,},}, + {{2050412,-6457589,15321215,5273360,25484180, + 124590,-18187548,-7097255,-6691621,-14604792,}, + {9938196,2162889,-6158074,-1711248,4278932, + -2598531,-22865792,-7168500,-24323168,11746309,}, + {-22691768,-14268164,5965485,9383325,20443693, + 5854192,28250679,-1381811,-10837134,13717818,},}, + {{-8495530,16382250,9548884,-4971523,-4491811, + -3902147,6182256,-12832479,26628081,10395408,}, + {27329048,-15853735,7715764,8717446,-9215518, + -14633480,28982250,-5668414,4227628,242148,}, + {-13279943,-7986904,-7100016,8764468,-27276630, + 3096719,29678419,-9141299,3906709,11265498,},}, + {{11918285,15686328,-17757323,-11217300,-27548967, + 4853165,-27168827,6807359,6871949,-1075745,}, + {-29002610,13984323,-27111812,-2713442,28107359, + -13266203,6155126,15104658,3538727,-7513788,}, + {14103158,11233913,-33165269,9279850,31014152, + 4335090,-1827936,4590951,13960841,12787712,},}, + {{1469134,-16738009,33411928,13942824,8092558, + -8778224,-11165065,1437842,22521552,-2792954,}, + {31352705,-4807352,-25327300,3962447,12541566, + -9399651,-27425693,7964818,-23829869,5541287,}, + {-25732021,-6864887,23848984,3039395,-9147354, + 6022816,-27421653,10590137,25309915,-1584678,},}, + {{-22951376,5048948,31139401,-190316,-19542447, + -626310,-17486305,-16511925,-18851313,-12985140,}, + {-9684890,14681754,30487568,7717771,-10829709, + 9630497,30290549,-10531496,-27798994,-13812825,}, + {5827835,16097107,-24501327,12094619,7413972, + 11447087,28057551,-1793987,-14056981,4359312,},}, + {{26323183,2342588,-21887793,-1623758,-6062284, + 2107090,-28724907,9036464,-19618351,-13055189,}, + {-29697200,14829398,-4596333,14220089,-30022969, + 2955645,12094100,-13693652,-5941445,7047569,}, + {-3201977,14413268,-12058324,-16417589,-9035655, + -7224648,9258160,1399236,30397584,-5684634,},}, +}; + +static void lookup_add(ge *p, ge_precomp *tmp_c, fe tmp_a, fe tmp_b, + const ge_precomp comb[8], const u8 scalar[32], int i) +{ + u8 teeth = (u8)((scalar_bit(scalar, i) ) + + (scalar_bit(scalar, i + 32) << 1) + + (scalar_bit(scalar, i + 64) << 2) + + (scalar_bit(scalar, i + 96) << 3)); + u8 high = teeth >> 3; + u8 index = (teeth ^ (high - 1)) & 7; + FOR (j, 0, 8) { + i32 select = 1 & (((j ^ index) - 1) >> 8); + fe_ccopy(tmp_c->Yp, comb[j].Yp, select); + fe_ccopy(tmp_c->Ym, comb[j].Ym, select); + fe_ccopy(tmp_c->T2, comb[j].T2, select); + } + fe_neg(tmp_a, tmp_c->T2); + fe_cswap(tmp_c->T2, tmp_a , high ^ 1); + fe_cswap(tmp_c->Yp, tmp_c->Ym, high ^ 1); + ge_madd(p, p, tmp_c, tmp_a, tmp_b); +} + +// p = [scalar]B, where B is the base point +static void ge_scalarmult_base(ge *p, const u8 scalar[32]) +{ + // twin 4-bits signed combs, from Mike Hamburg's + // Fast and compact elliptic-curve cryptography (2012) + // 1 / 2 modulo L + static const u8 half_mod_L[32] = { + 247,233,122,46,141,49,9,44,107,206,123,81,239,124,111,10, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8, + }; + // (2^256 - 1) / 2 modulo L + static const u8 half_ones[32] = { + 142,74,204,70,186,24,118,107,184,231,190,57,250,173,119,99, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,7, + }; + + // All bits set form: 1 means 1, 0 means -1 + u8 s_scalar[32]; + crypto_eddsa_mul_add(s_scalar, scalar, half_mod_L, half_ones); + + // Double and add ladder + fe tmp_a, tmp_b; // temporaries for addition + ge_precomp tmp_c; // temporary for comb lookup + ge tmp_d; // temporary for doubling + fe_1(tmp_c.Yp); + fe_1(tmp_c.Ym); + fe_0(tmp_c.T2); + + // Save a double on the first iteration + ge_zero(p); + lookup_add(p, &tmp_c, tmp_a, tmp_b, b_comb_low , s_scalar, 31); + lookup_add(p, &tmp_c, tmp_a, tmp_b, b_comb_high, s_scalar, 31+128); + // Regular double & add for the rest + for (int i = 30; i >= 0; i--) { + ge_double(p, p, &tmp_d); + lookup_add(p, &tmp_c, tmp_a, tmp_b, b_comb_low , s_scalar, i); + lookup_add(p, &tmp_c, tmp_a, tmp_b, b_comb_high, s_scalar, i+128); + } + // Note: we could save one addition at the end if we assumed the + // scalar fit in 252 bits. Which it does in practice if it is + // selected at random. However, non-random, non-hashed scalars + // *can* overflow 252 bits in practice. Better account for that + // than leaving that kind of subtle corner case. + + WIPE_BUFFER(tmp_a); WIPE_CTX(&tmp_d); + WIPE_BUFFER(tmp_b); WIPE_CTX(&tmp_c); + WIPE_BUFFER(s_scalar); +} + +void crypto_eddsa_scalarbase(u8 point[32], const u8 scalar[32]) +{ + ge P; + ge_scalarmult_base(&P, scalar); + ge_tobytes(point, &P); + WIPE_CTX(&P); +} + +void crypto_eddsa_key_pair(u8 secret_key[64], u8 public_key[32], u8 seed[32]) +{ + // To allow overlaps, observable writes happen in this order: + // 1. seed + // 2. secret_key + // 3. public_key + u8 a[64]; + COPY(a, seed, 32); + crypto_wipe(seed, 32); + COPY(secret_key, a, 32); + crypto_blake2b(a, 64, a, 32); + crypto_eddsa_trim_scalar(a, a); + crypto_eddsa_scalarbase(secret_key + 32, a); + COPY(public_key, secret_key + 32, 32); + WIPE_BUFFER(a); +} + +static void hash_reduce(u8 h[32], + const u8 *a, size_t a_size, + const u8 *b, size_t b_size, + const u8 *c, size_t c_size) +{ + u8 hash[64]; + crypto_blake2b_ctx ctx; + crypto_blake2b_init (&ctx, 64); + crypto_blake2b_update(&ctx, a, a_size); + crypto_blake2b_update(&ctx, b, b_size); + crypto_blake2b_update(&ctx, c, c_size); + crypto_blake2b_final (&ctx, hash); + crypto_eddsa_reduce(h, hash); +} + +// Digital signature of a message with from a secret key. +// +// The secret key comprises two parts: +// - The seed that generates the key (secret_key[ 0..31]) +// - The public key (secret_key[32..63]) +// +// The seed and the public key are bundled together to make sure users +// don't use mismatched seeds and public keys, which would instantly +// leak the secret scalar and allow forgeries (allowing this to happen +// has resulted in critical vulnerabilities in the wild). +// +// The seed is hashed to derive the secret scalar and a secret prefix. +// The sole purpose of the prefix is to generate a secret random nonce. +// The properties of that nonce must be as follows: +// - Unique: we need a different one for each message. +// - Secret: third parties must not be able to predict it. +// - Random: any detectable bias would break all security. +// +// There are two ways to achieve these properties. The obvious one is +// to simply generate a random number. Here that would be a parameter +// (Monocypher doesn't have an RNG). It works, but then users may reuse +// the nonce by accident, which _also_ leaks the secret scalar and +// allows forgeries. This has happened in the wild too. +// +// This is no good, so instead we generate that nonce deterministically +// by reducing modulo L a hash of the secret prefix and the message. +// The secret prefix makes the nonce unpredictable, the message makes it +// unique, and the hash/reduce removes all bias. +// +// The cost of that safety is hashing the message twice. If that cost +// is unacceptable, there are two alternatives: +// +// - Signing a hash of the message instead of the message itself. This +// is fine as long as the hash is collision resistant. It is not +// compatible with existing "pure" signatures, but at least it's safe. +// +// - Using a random nonce. Please exercise **EXTREME CAUTION** if you +// ever do that. It is absolutely **critical** that the nonce is +// really an unbiased random number between 0 and L-1, never reused, +// and wiped immediately. +// +// To lower the likelihood of complete catastrophe if the RNG is +// either flawed or misused, you can hash the RNG output together with +// the secret prefix and the beginning of the message, and use the +// reduction of that hash instead of the RNG output itself. It's not +// foolproof (you'd need to hash the whole message) but it helps. +// +// Signing a message involves the following operations: +// +// scalar, prefix = HASH(secret_key) +// r = HASH(prefix || message) % L +// R = [r]B +// h = HASH(R || public_key || message) % L +// S = ((h * a) + r) % L +// signature = R || S +void crypto_eddsa_sign(u8 signature [64], const u8 secret_key[64], + const u8 *message, size_t message_size) +{ + u8 a[64]; // secret scalar and prefix + u8 r[32]; // secret deterministic "random" nonce + u8 h[32]; // publically verifiable hash of the message (not wiped) + u8 R[32]; // first half of the signature (allows overlapping inputs) + + crypto_blake2b(a, 64, secret_key, 32); + crypto_eddsa_trim_scalar(a, a); + hash_reduce(r, a + 32, 32, message, message_size, 0, 0); + crypto_eddsa_scalarbase(R, r); + hash_reduce(h, R, 32, secret_key + 32, 32, message, message_size); + COPY(signature, R, 32); + crypto_eddsa_mul_add(signature + 32, h, a, r); + + WIPE_BUFFER(a); + WIPE_BUFFER(r); +} + +// To check the signature R, S of the message M with the public key A, +// there are 3 steps: +// +// compute h = HASH(R || A || message) % L +// check that A is on the curve. +// check that R == [s]B - [h]A +// +// The last two steps are done in crypto_eddsa_check_equation() +int crypto_eddsa_check(const u8 signature[64], const u8 public_key[32], + const u8 *message, size_t message_size) +{ + u8 h[32]; + hash_reduce(h, signature, 32, public_key, 32, message, message_size); + return crypto_eddsa_check_equation(signature, public_key, h); +} + +///////////////////////// +/// EdDSA <--> X25519 /// +///////////////////////// +void crypto_eddsa_to_x25519(u8 x25519[32], const u8 eddsa[32]) +{ + // (u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x) + // Only converting y to u, the sign of x is ignored. + fe t1, t2; + fe_frombytes(t2, eddsa); + fe_add(t1, fe_one, t2); + fe_sub(t2, fe_one, t2); + fe_invert(t2, t2); + fe_mul(t1, t1, t2); + fe_tobytes(x25519, t1); + WIPE_BUFFER(t1); + WIPE_BUFFER(t2); +} + +void crypto_x25519_to_eddsa(u8 eddsa[32], const u8 x25519[32]) +{ + // (x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1)) + // Only converting u to y, x is assumed positive. + fe t1, t2; + fe_frombytes(t2, x25519); + fe_sub(t1, t2, fe_one); + fe_add(t2, t2, fe_one); + fe_invert(t2, t2); + fe_mul(t1, t1, t2); + fe_tobytes(eddsa, t1); + WIPE_BUFFER(t1); + WIPE_BUFFER(t2); +} + +///////////////////////////////////////////// +/// Dirty ephemeral public key generation /// +///////////////////////////////////////////// + +// Those functions generates a public key, *without* clearing the +// cofactor. Sending that key over the network leaks 3 bits of the +// private key. Use only to generate ephemeral keys that will be hidden +// with crypto_curve_to_hidden(). +// +// The public key is otherwise compatible with crypto_x25519(), which +// properly clears the cofactor. +// +// Note that the distribution of the resulting public keys is almost +// uniform. Flipping the sign of the v coordinate (not provided by this +// function), covers the entire key space almost perfectly, where +// "almost" means a 2^-128 bias (undetectable). This uniformity is +// needed to ensure the proper randomness of the resulting +// representatives (once we apply crypto_curve_to_hidden()). +// +// Recall that Curve25519 has order C = 2^255 + e, with e < 2^128 (not +// to be confused with the prime order of the main subgroup, L, which is +// 8 times less than that). +// +// Generating all points would require us to multiply a point of order C +// (the base point plus any point of order 8) by all scalars from 0 to +// C-1. Clamping limits us to scalars between 2^254 and 2^255 - 1. But +// by negating the resulting point at random, we also cover scalars from +// -2^255 + 1 to -2^254 (which modulo C is congruent to e+1 to 2^254 + e). +// +// In practice: +// - Scalars from 0 to e + 1 are never generated +// - Scalars from 2^255 to 2^255 + e are never generated +// - Scalars from 2^254 + 1 to 2^254 + e are generated twice +// +// Since e < 2^128, detecting this bias requires observing over 2^100 +// representatives from a given source (this will never happen), *and* +// recovering enough of the private key to determine that they do, or do +// not, belong to the biased set (this practically requires solving +// discrete logarithm, which is conjecturally intractable). +// +// In practice, this means the bias is impossible to detect. + +// s + (x*L) % 8*L +// Guaranteed to fit in 256 bits iff s fits in 255 bits. +// L < 2^253 +// x%8 < 2^3 +// L * (x%8) < 2^255 +// s < 2^255 +// s + L * (x%8) < 2^256 +static void add_xl(u8 s[32], u8 x) +{ + u64 mod8 = x & 7; + u64 carry = 0; + FOR (i , 0, 8) { + carry = carry + load32_le(s + 4*i) + L[i] * mod8; + store32_le(s + 4*i, (u32)carry); + carry >>= 32; + } +} + +// "Small" dirty ephemeral key. +// Use if you need to shrink the size of the binary, and can afford to +// slow down by a factor of two (compared to the fast version) +// +// This version works by decoupling the cofactor from the main factor. +// +// - The trimmed scalar determines the main factor +// - The clamped bits of the scalar determine the cofactor. +// +// Cofactor and main factor are combined into a single scalar, which is +// then multiplied by a point of order 8*L (unlike the base point, which +// has prime order). That "dirty" base point is the addition of the +// regular base point (9), and a point of order 8. +void crypto_x25519_dirty_small(u8 public_key[32], const u8 secret_key[32]) +{ + // Base point of order 8*L + // Raw scalar multiplication with it does not clear the cofactor, + // and the resulting public key will reveal 3 bits of the scalar. + // + // The low order component of this base point has been chosen + // to yield the same results as crypto_x25519_dirty_fast(). + static const u8 dirty_base_point[32] = { + 0xd8, 0x86, 0x1a, 0xa2, 0x78, 0x7a, 0xd9, 0x26, + 0x8b, 0x74, 0x74, 0xb6, 0x82, 0xe3, 0xbe, 0xc3, + 0xce, 0x36, 0x9a, 0x1e, 0x5e, 0x31, 0x47, 0xa2, + 0x6d, 0x37, 0x7c, 0xfd, 0x20, 0xb5, 0xdf, 0x75, + }; + // separate the main factor & the cofactor of the scalar + u8 scalar[32]; + crypto_eddsa_trim_scalar(scalar, secret_key); + + // Separate the main factor and the cofactor + // + // The scalar is trimmed, so its cofactor is cleared. The three + // least significant bits however still have a main factor. We must + // remove it for X25519 compatibility. + // + // cofactor = lsb * L (modulo 8*L) + // combined = scalar + cofactor (modulo 8*L) + add_xl(scalar, secret_key[0]); + scalarmult(public_key, scalar, dirty_base_point, 256); + WIPE_BUFFER(scalar); +} + +// Select low order point +// We're computing the [cofactor]lop scalar multiplication, where: +// +// cofactor = tweak & 7. +// lop = (lop_x, lop_y) +// lop_x = sqrt((sqrt(d + 1) + 1) / d) +// lop_y = -lop_x * sqrtm1 +// +// The low order point has order 8. There are 4 such points. We've +// chosen the one whose both coordinates are positive (below p/2). +// The 8 low order points are as follows: +// +// [0]lop = ( 0 , 1 ) +// [1]lop = ( lop_x , lop_y) +// [2]lop = ( sqrt(-1), -0 ) +// [3]lop = ( lop_x , -lop_y) +// [4]lop = (-0 , -1 ) +// [5]lop = (-lop_x , -lop_y) +// [6]lop = (-sqrt(-1), 0 ) +// [7]lop = (-lop_x , lop_y) +// +// The x coordinate is either 0, sqrt(-1), lop_x, or their opposite. +// The y coordinate is either 0, -1 , lop_y, or their opposite. +// The pattern for both is the same, except for a rotation of 2 (modulo 8) +// +// This helper function captures the pattern, and we can use it thus: +// +// select_lop(x, lop_x, sqrtm1, cofactor); +// select_lop(y, lop_y, fe_one, cofactor + 2); +// +// This is faster than an actual scalar multiplication, +// and requires less code than naive constant time look up. +static void select_lop(fe out, const fe x, const fe k, u8 cofactor) +{ + fe tmp; + fe_0(out); + fe_ccopy(out, k , (cofactor >> 1) & 1); // bit 1 + fe_ccopy(out, x , (cofactor >> 0) & 1); // bit 0 + fe_neg (tmp, out); + fe_ccopy(out, tmp, (cofactor >> 2) & 1); // bit 2 + WIPE_BUFFER(tmp); +} + +// "Fast" dirty ephemeral key +// We use this one by default. +// +// This version works by performing a regular scalar multiplication, +// then add a low order point. The scalar multiplication is done in +// Edwards space for more speed (*2 compared to the "small" version). +// The cost is a bigger binary for programs that don't also sign messages. +void crypto_x25519_dirty_fast(u8 public_key[32], const u8 secret_key[32]) +{ + // Compute clean scalar multiplication + u8 scalar[32]; + ge pk; + crypto_eddsa_trim_scalar(scalar, secret_key); + ge_scalarmult_base(&pk, scalar); + + // Compute low order point + fe t1, t2; + select_lop(t1, lop_x, sqrtm1, secret_key[0]); + select_lop(t2, lop_y, fe_one, secret_key[0] + 2); + ge_precomp low_order_point; + fe_add(low_order_point.Yp, t2, t1); + fe_sub(low_order_point.Ym, t2, t1); + fe_mul(low_order_point.T2, t2, t1); + fe_mul(low_order_point.T2, low_order_point.T2, D2); + + // Add low order point to the public key + ge_madd(&pk, &pk, &low_order_point, t1, t2); + + // Convert to Montgomery u coordinate (we ignore the sign) + fe_add(t1, pk.Z, pk.Y); + fe_sub(t2, pk.Z, pk.Y); + fe_invert(t2, t2); + fe_mul(t1, t1, t2); + + fe_tobytes(public_key, t1); + + WIPE_BUFFER(t1); WIPE_CTX(&pk); + WIPE_BUFFER(t2); WIPE_CTX(&low_order_point); + WIPE_BUFFER(scalar); +} + +/////////////////// +/// Elligator 2 /// +/////////////////// +static const fe A = {486662}; + +// Elligator direct map +// +// Computes the point corresponding to a representative, encoded in 32 +// bytes (little Endian). Since positive representatives fits in 254 +// bits, The two most significant bits are ignored. +// +// From the paper: +// w = -A / (fe(1) + non_square * r^2) +// e = chi(w^3 + A*w^2 + w) +// u = e*w - (fe(1)-e)*(A//2) +// v = -e * sqrt(u^3 + A*u^2 + u) +// +// We ignore v because we don't need it for X25519 (the Montgomery +// ladder only uses u). +// +// Note that e is either 0, 1 or -1 +// if e = 0 u = 0 and v = 0 +// if e = 1 u = w +// if e = -1 u = -w - A = w * non_square * r^2 +// +// Let r1 = non_square * r^2 +// Let r2 = 1 + r1 +// Note that r2 cannot be zero, -1/non_square is not a square. +// We can (tediously) verify that: +// w^3 + A*w^2 + w = (A^2*r1 - r2^2) * A / r2^3 +// Therefore: +// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * (A / r2^3)) +// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * (A / r2^3)) * 1 +// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * (A / r2^3)) * chi(r2^6) +// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * (A / r2^3) * r2^6) +// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * A * r2^3) +// Corollary: +// e = 1 if (A^2*r1 - r2^2) * A * r2^3) is a non-zero square +// e = -1 if (A^2*r1 - r2^2) * A * r2^3) is not a square +// Note that w^3 + A*w^2 + w (and therefore e) can never be zero: +// w^3 + A*w^2 + w = w * (w^2 + A*w + 1) +// w^3 + A*w^2 + w = w * (w^2 + A*w + A^2/4 - A^2/4 + 1) +// w^3 + A*w^2 + w = w * (w + A/2)^2 - A^2/4 + 1) +// which is zero only if: +// w = 0 (impossible) +// (w + A/2)^2 = A^2/4 - 1 (impossible, because A^2/4-1 is not a square) +// +// Let isr = invsqrt((A^2*r1 - r2^2) * A * r2^3) +// isr = sqrt(1 / ((A^2*r1 - r2^2) * A * r2^3)) if e = 1 +// isr = sqrt(sqrt(-1) / ((A^2*r1 - r2^2) * A * r2^3)) if e = -1 +// +// if e = 1 +// let u1 = -A * (A^2*r1 - r2^2) * A * r2^2 * isr^2 +// u1 = w +// u1 = u +// +// if e = -1 +// let ufactor = -non_square * sqrt(-1) * r^2 +// let vfactor = sqrt(ufactor) +// let u2 = -A * (A^2*r1 - r2^2) * A * r2^2 * isr^2 * ufactor +// u2 = w * -1 * -non_square * r^2 +// u2 = w * non_square * r^2 +// u2 = u +void crypto_elligator_map(u8 curve[32], const u8 hidden[32]) +{ + fe r, u, t1, t2, t3; + fe_frombytes_mask(r, hidden, 2); // r is encoded in 254 bits. + fe_sq(r, r); + fe_add(t1, r, r); + fe_add(u, t1, fe_one); + fe_sq (t2, u); + fe_mul(t3, A2, t1); + fe_sub(t3, t3, t2); + fe_mul(t3, t3, A); + fe_mul(t1, t2, u); + fe_mul(t1, t3, t1); + int is_square = invsqrt(t1, t1); + fe_mul(u, r, ufactor); + fe_ccopy(u, fe_one, is_square); + fe_sq (t1, t1); + fe_mul(u, u, A); + fe_mul(u, u, t3); + fe_mul(u, u, t2); + fe_mul(u, u, t1); + fe_neg(u, u); + fe_tobytes(curve, u); + + WIPE_BUFFER(t1); WIPE_BUFFER(r); + WIPE_BUFFER(t2); WIPE_BUFFER(u); + WIPE_BUFFER(t3); +} + +// Elligator inverse map +// +// Computes the representative of a point, if possible. If not, it does +// nothing and returns -1. Note that the success of the operation +// depends only on the point (more precisely its u coordinate). The +// tweak parameter is used only upon success +// +// The tweak should be a random byte. Beyond that, its contents are an +// implementation detail. Currently, the tweak comprises: +// - Bit 1 : sign of the v coordinate (0 if positive, 1 if negative) +// - Bit 2-5: not used +// - Bits 6-7: random padding +// +// From the paper: +// Let sq = -non_square * u * (u+A) +// if sq is not a square, or u = -A, there is no mapping +// Assuming there is a mapping: +// if v is positive: r = sqrt(-u / (non_square * (u+A))) +// if v is negative: r = sqrt(-(u+A) / (non_square * u )) +// +// We compute isr = invsqrt(-non_square * u * (u+A)) +// if it wasn't a square, abort. +// else, isr = sqrt(-1 / (non_square * u * (u+A)) +// +// If v is positive, we return isr * u: +// isr * u = sqrt(-1 / (non_square * u * (u+A)) * u +// isr * u = sqrt(-u / (non_square * (u+A)) +// +// If v is negative, we return isr * (u+A): +// isr * (u+A) = sqrt(-1 / (non_square * u * (u+A)) * (u+A) +// isr * (u+A) = sqrt(-(u+A) / (non_square * u) +int crypto_elligator_rev(u8 hidden[32], const u8 public_key[32], u8 tweak) +{ + fe t1, t2, t3; + fe_frombytes(t1, public_key); // t1 = u + + fe_add(t2, t1, A); // t2 = u + A + fe_mul(t3, t1, t2); + fe_mul_small(t3, t3, -2); + int is_square = invsqrt(t3, t3); // t3 = sqrt(-1 / non_square * u * (u+A)) + if (is_square) { + // The only variable time bit. This ultimately reveals how many + // tries it took us to find a representable key. + // This does not affect security as long as we try keys at random. + + fe_ccopy (t1, t2, tweak & 1); // multiply by u if v is positive, + fe_mul (t3, t1, t3); // multiply by u+A otherwise + fe_mul_small(t1, t3, 2); + fe_neg (t2, t3); + fe_ccopy (t3, t2, fe_isodd(t1)); + fe_tobytes(hidden, t3); + + // Pad with two random bits + hidden[31] |= tweak & 0xc0; + } + + WIPE_BUFFER(t1); + WIPE_BUFFER(t2); + WIPE_BUFFER(t3); + return is_square - 1; +} + +void crypto_elligator_key_pair(u8 hidden[32], u8 secret_key[32], u8 seed[32]) +{ + u8 pk [32]; // public key + u8 buf[64]; // seed + representative + COPY(buf + 32, seed, 32); + do { + crypto_chacha20_djb(buf, 0, 64, buf+32, zero, 0); + crypto_x25519_dirty_fast(pk, buf); // or the "small" version + } while(crypto_elligator_rev(buf+32, pk, buf[32])); + // Note that the return value of crypto_elligator_rev() is + // independent from its tweak parameter. + // Therefore, buf[32] is not actually reused. Either we loop one + // more time and buf[32] is used for the new seed, or we succeeded, + // and buf[32] becomes the tweak parameter. + + crypto_wipe(seed, 32); + COPY(hidden , buf + 32, 32); + COPY(secret_key, buf , 32); + WIPE_BUFFER(buf); + WIPE_BUFFER(pk); +} + +/////////////////////// +/// Scalar division /// +/////////////////////// + +// Montgomery reduction. +// Divides x by (2^256), and reduces the result modulo L +// +// Precondition: +// x < L * 2^256 +// Constants: +// r = 2^256 (makes division by r trivial) +// k = (r * (1/r) - 1) // L (1/r is computed modulo L ) +// Algorithm: +// s = (x * k) % r +// t = x + s*L (t is always a multiple of r) +// u = (t/r) % L (u is always below 2*L, conditional subtraction is enough) +static void redc(u32 u[8], u32 x[16]) +{ + static const u32 k[8] = { + 0x12547e1b, 0xd2b51da3, 0xfdba84ff, 0xb1a206f2, + 0xffa36bea, 0x14e75438, 0x6fe91836, 0x9db6c6f2, + }; + + // s = x * k (modulo 2^256) + // This is cheaper than the full multiplication. + u32 s[8] = {0}; + FOR (i, 0, 8) { + u64 carry = 0; + FOR (j, 0, 8-i) { + carry += s[i+j] + (u64)x[i] * k[j]; + s[i+j] = (u32)carry; + carry >>= 32; + } + } + u32 t[16] = {0}; + multiply(t, s, L); + + // t = t + x + u64 carry = 0; + FOR (i, 0, 16) { + carry += (u64)t[i] + x[i]; + t[i] = (u32)carry; + carry >>= 32; + } + + // u = (t / 2^256) % L + // Note that t / 2^256 is always below 2*L, + // So a constant time conditional subtraction is enough + remove_l(u, t+8); + + WIPE_BUFFER(s); + WIPE_BUFFER(t); +} + +void crypto_x25519_inverse(u8 blind_salt [32], const u8 private_key[32], + const u8 curve_point[32]) +{ + static const u8 Lm2[32] = { // L - 2 + 0xeb, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, + 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + }; + // 1 in Montgomery form + u32 m_inv [8] = { + 0x8d98951d, 0xd6ec3174, 0x737dcf70, 0xc6ef5bf4, + 0xfffffffe, 0xffffffff, 0xffffffff, 0x0fffffff, + }; + + u8 scalar[32]; + crypto_eddsa_trim_scalar(scalar, private_key); + + // Convert the scalar in Montgomery form + // m_scl = scalar * 2^256 (modulo L) + u32 m_scl[8]; + { + u32 tmp[16]; + ZERO(tmp, 8); + load32_le_buf(tmp+8, scalar, 8); + mod_l(scalar, tmp); + load32_le_buf(m_scl, scalar, 8); + WIPE_BUFFER(tmp); // Wipe ASAP to save stack space + } + + // Compute the inverse + u32 product[16]; + for (int i = 252; i >= 0; i--) { + ZERO(product, 16); + multiply(product, m_inv, m_inv); + redc(m_inv, product); + if (scalar_bit(Lm2, i)) { + ZERO(product, 16); + multiply(product, m_inv, m_scl); + redc(m_inv, product); + } + } + // Convert the inverse *out* of Montgomery form + // scalar = m_inv / 2^256 (modulo L) + COPY(product, m_inv, 8); + ZERO(product + 8, 8); + redc(m_inv, product); + store32_le_buf(scalar, m_inv, 8); // the *inverse* of the scalar + + // Clear the cofactor of scalar: + // cleared = scalar * (3*L + 1) (modulo 8*L) + // cleared = scalar + scalar * 3 * L (modulo 8*L) + // Note that (scalar * 3) is reduced modulo 8, so we only need the + // first byte. + add_xl(scalar, scalar[0] * 3); + + // Recall that 8*L < 2^256. However it is also very close to + // 2^255. If we spanned the ladder over 255 bits, random tests + // wouldn't catch the off-by-one error. + scalarmult(blind_salt, scalar, curve_point, 256); + + WIPE_BUFFER(scalar); WIPE_BUFFER(m_scl); + WIPE_BUFFER(product); WIPE_BUFFER(m_inv); +} + +//////////////////////////////// +/// Authenticated encryption /// +//////////////////////////////// +static void lock_auth(u8 mac[16], const u8 auth_key[32], + const u8 *ad , size_t ad_size, + const u8 *cipher_text, size_t text_size) +{ + u8 sizes[16]; // Not secret, not wiped + store64_le(sizes + 0, ad_size); + store64_le(sizes + 8, text_size); + crypto_poly1305_ctx poly_ctx; // auto wiped... + crypto_poly1305_init (&poly_ctx, auth_key); + crypto_poly1305_update(&poly_ctx, ad , ad_size); + crypto_poly1305_update(&poly_ctx, zero , gap(ad_size, 16)); + crypto_poly1305_update(&poly_ctx, cipher_text, text_size); + crypto_poly1305_update(&poly_ctx, zero , gap(text_size, 16)); + crypto_poly1305_update(&poly_ctx, sizes , 16); + crypto_poly1305_final (&poly_ctx, mac); // ...here +} + +void crypto_aead_init_x(crypto_aead_ctx *ctx, + u8 const key[32], const u8 nonce[24]) +{ + crypto_chacha20_h(ctx->key, key, nonce); + COPY(ctx->nonce, nonce + 16, 8); + ctx->counter = 0; +} + +void crypto_aead_init_djb(crypto_aead_ctx *ctx, + const u8 key[32], const u8 nonce[8]) +{ + COPY(ctx->key , key , 32); + COPY(ctx->nonce, nonce, 8); + ctx->counter = 0; +} + +void crypto_aead_init_ietf(crypto_aead_ctx *ctx, + const u8 key[32], const u8 nonce[12]) +{ + COPY(ctx->key , key , 32); + COPY(ctx->nonce, nonce + 4, 8); + ctx->counter = (u64)load32_le(nonce) << 32; +} + +void crypto_aead_write(crypto_aead_ctx *ctx, u8 *cipher_text, u8 mac[16], + const u8 *ad, size_t ad_size, + const u8 *plain_text, size_t text_size) +{ + u8 auth_key[64]; // the last 32 bytes are used for rekeying. + crypto_chacha20_djb(auth_key, 0, 64, ctx->key, ctx->nonce, ctx->counter); + crypto_chacha20_djb(cipher_text, plain_text, text_size, + ctx->key, ctx->nonce, ctx->counter + 1); + lock_auth(mac, auth_key, ad, ad_size, cipher_text, text_size); + COPY(ctx->key, auth_key + 32, 32); + WIPE_BUFFER(auth_key); +} + +int crypto_aead_read(crypto_aead_ctx *ctx, u8 *plain_text, const u8 mac[16], + const u8 *ad, size_t ad_size, + const u8 *cipher_text, size_t text_size) +{ + u8 auth_key[64]; // the last 32 bytes are used for rekeying. + u8 real_mac[16]; + crypto_chacha20_djb(auth_key, 0, 64, ctx->key, ctx->nonce, ctx->counter); + lock_auth(real_mac, auth_key, ad, ad_size, cipher_text, text_size); + int mismatch = crypto_verify16(mac, real_mac); + if (!mismatch) { + crypto_chacha20_djb(plain_text, cipher_text, text_size, + ctx->key, ctx->nonce, ctx->counter + 1); + COPY(ctx->key, auth_key + 32, 32); + } + WIPE_BUFFER(auth_key); + WIPE_BUFFER(real_mac); + return mismatch; +} + +void crypto_aead_lock(u8 *cipher_text, u8 mac[16], const u8 key[32], + const u8 nonce[24], const u8 *ad, size_t ad_size, + const u8 *plain_text, size_t text_size) +{ + crypto_aead_ctx ctx; + crypto_aead_init_x(&ctx, key, nonce); + crypto_aead_write(&ctx, cipher_text, mac, ad, ad_size, + plain_text, text_size); + crypto_wipe(&ctx, sizeof(ctx)); +} + +int crypto_aead_unlock(u8 *plain_text, const u8 mac[16], const u8 key[32], + const u8 nonce[24], const u8 *ad, size_t ad_size, + const u8 *cipher_text, size_t text_size) +{ + crypto_aead_ctx ctx; + crypto_aead_init_x(&ctx, key, nonce); + int mismatch = crypto_aead_read(&ctx, plain_text, mac, ad, ad_size, + cipher_text, text_size); + crypto_wipe(&ctx, sizeof(ctx)); + return mismatch; +} + +#ifdef MONOCYPHER_CPP_NAMESPACE +} +#endif diff --git a/src/monocypher/monocypher.h b/src/monocypher/monocypher.h new file mode 100644 index 0000000..291b8b9 --- /dev/null +++ b/src/monocypher/monocypher.h @@ -0,0 +1,321 @@ +// Monocypher version 4.0.3 +// +// This file is dual-licensed. Choose whichever licence you want from +// the two licences listed below. +// +// The first licence is a regular 2-clause BSD licence. The second licence +// is the CC-0 from Creative Commons. It is intended to release Monocypher +// to the public domain. The BSD licence serves as a fallback option. +// +// SPDX-License-Identifier: BSD-2-Clause OR CC0-1.0 +// +// ------------------------------------------------------------------------ +// +// Copyright (c) 2017-2019, Loup Vaillant +// All rights reserved. +// +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ------------------------------------------------------------------------ +// +// Written in 2017-2019 by Loup Vaillant +// +// To the extent possible under law, the author(s) have dedicated all copyright +// and related neighboring rights to this software to the public domain +// worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication along +// with this software. If not, see +// + +#ifndef MONOCYPHER_H +#define MONOCYPHER_H + +#include +#include + +#ifdef MONOCYPHER_CPP_NAMESPACE +namespace MONOCYPHER_CPP_NAMESPACE { +#elif defined(__cplusplus) +extern "C" { +#endif + +// Constant time comparisons +// ------------------------- + +// Return 0 if a and b are equal, -1 otherwise +int crypto_verify16(const uint8_t a[16], const uint8_t b[16]); +int crypto_verify32(const uint8_t a[32], const uint8_t b[32]); +int crypto_verify64(const uint8_t a[64], const uint8_t b[64]); + + +// Erase sensitive data +// -------------------- +void crypto_wipe(void *secret, size_t size); + + +// Authenticated encryption +// ------------------------ +void crypto_aead_lock(uint8_t *cipher_text, + uint8_t mac [16], + const uint8_t key [32], + const uint8_t nonce[24], + const uint8_t *ad, size_t ad_size, + const uint8_t *plain_text, size_t text_size); +int crypto_aead_unlock(uint8_t *plain_text, + const uint8_t mac [16], + const uint8_t key [32], + const uint8_t nonce[24], + const uint8_t *ad, size_t ad_size, + const uint8_t *cipher_text, size_t text_size); + +// Authenticated stream +// -------------------- +typedef struct { + uint64_t counter; + uint8_t key[32]; + uint8_t nonce[8]; +} crypto_aead_ctx; + +void crypto_aead_init_x(crypto_aead_ctx *ctx, + const uint8_t key[32], const uint8_t nonce[24]); +void crypto_aead_init_djb(crypto_aead_ctx *ctx, + const uint8_t key[32], const uint8_t nonce[8]); +void crypto_aead_init_ietf(crypto_aead_ctx *ctx, + const uint8_t key[32], const uint8_t nonce[12]); + +void crypto_aead_write(crypto_aead_ctx *ctx, + uint8_t *cipher_text, + uint8_t mac[16], + const uint8_t *ad , size_t ad_size, + const uint8_t *plain_text, size_t text_size); +int crypto_aead_read(crypto_aead_ctx *ctx, + uint8_t *plain_text, + const uint8_t mac[16], + const uint8_t *ad , size_t ad_size, + const uint8_t *cipher_text, size_t text_size); + + +// General purpose hash (BLAKE2b) +// ------------------------------ + +// Direct interface +void crypto_blake2b(uint8_t *hash, size_t hash_size, + const uint8_t *message, size_t message_size); + +void crypto_blake2b_keyed(uint8_t *hash, size_t hash_size, + const uint8_t *key, size_t key_size, + const uint8_t *message, size_t message_size); + +// Incremental interface +typedef struct { + // Do not rely on the size or contents of this type, + // for they may change without notice. + uint64_t hash[8]; + uint64_t input_offset[2]; + uint64_t input[16]; + size_t input_idx; + size_t hash_size; +} crypto_blake2b_ctx; + +void crypto_blake2b_init(crypto_blake2b_ctx *ctx, size_t hash_size); +void crypto_blake2b_keyed_init(crypto_blake2b_ctx *ctx, size_t hash_size, + const uint8_t *key, size_t key_size); +void crypto_blake2b_update(crypto_blake2b_ctx *ctx, + const uint8_t *message, size_t message_size); +void crypto_blake2b_final(crypto_blake2b_ctx *ctx, uint8_t *hash); + + +// Password key derivation (Argon2) +// -------------------------------- +#define CRYPTO_ARGON2_D 0 +#define CRYPTO_ARGON2_I 1 +#define CRYPTO_ARGON2_ID 2 + +typedef struct { + uint32_t algorithm; // Argon2d, Argon2i, Argon2id + uint32_t nb_blocks; // memory hardness, >= 8 * nb_lanes + uint32_t nb_passes; // CPU hardness, >= 1 (>= 3 recommended for Argon2i) + uint32_t nb_lanes; // parallelism level (single threaded anyway) +} crypto_argon2_config; + +typedef struct { + const uint8_t *pass; + const uint8_t *salt; + uint32_t pass_size; + uint32_t salt_size; // 16 bytes recommended +} crypto_argon2_inputs; + +typedef struct { + const uint8_t *key; // may be NULL if no key + const uint8_t *ad; // may be NULL if no additional data + uint32_t key_size; // 0 if no key (32 bytes recommended otherwise) + uint32_t ad_size; // 0 if no additional data +} crypto_argon2_extras; + +extern const crypto_argon2_extras crypto_argon2_no_extras; + +void crypto_argon2(uint8_t *hash, uint32_t hash_size, void *work_area, + crypto_argon2_config config, + crypto_argon2_inputs inputs, + crypto_argon2_extras extras); + + +// Key exchange (X-25519) +// ---------------------- + +// Shared secrets are not quite random. +// Hash them to derive an actual shared key. +void crypto_x25519_public_key(uint8_t public_key[32], + const uint8_t secret_key[32]); +void crypto_x25519(uint8_t raw_shared_secret[32], + const uint8_t your_secret_key [32], + const uint8_t their_public_key [32]); + +// Conversion to EdDSA +void crypto_x25519_to_eddsa(uint8_t eddsa[32], const uint8_t x25519[32]); + +// scalar "division" +// Used for OPRF. Be aware that exponential blinding is less secure +// than Diffie-Hellman key exchange. +void crypto_x25519_inverse(uint8_t blind_salt [32], + const uint8_t private_key[32], + const uint8_t curve_point[32]); + +// "Dirty" versions of x25519_public_key(). +// Use with crypto_elligator_rev(). +// Leaks 3 bits of the private key. +void crypto_x25519_dirty_small(uint8_t pk[32], const uint8_t sk[32]); +void crypto_x25519_dirty_fast (uint8_t pk[32], const uint8_t sk[32]); + + +// Signatures +// ---------- + +// EdDSA with curve25519 + BLAKE2b +void crypto_eddsa_key_pair(uint8_t secret_key[64], + uint8_t public_key[32], + uint8_t seed[32]); +void crypto_eddsa_sign(uint8_t signature [64], + const uint8_t secret_key[64], + const uint8_t *message, size_t message_size); +int crypto_eddsa_check(const uint8_t signature [64], + const uint8_t public_key[32], + const uint8_t *message, size_t message_size); + +// Conversion to X25519 +void crypto_eddsa_to_x25519(uint8_t x25519[32], const uint8_t eddsa[32]); + +// EdDSA building blocks +void crypto_eddsa_trim_scalar(uint8_t out[32], const uint8_t in[32]); +void crypto_eddsa_reduce(uint8_t reduced[32], const uint8_t expanded[64]); +void crypto_eddsa_mul_add(uint8_t r[32], + const uint8_t a[32], + const uint8_t b[32], + const uint8_t c[32]); +void crypto_eddsa_scalarbase(uint8_t point[32], const uint8_t scalar[32]); +int crypto_eddsa_check_equation(const uint8_t signature[64], + const uint8_t public_key[32], + const uint8_t h_ram[32]); + + +// Chacha20 +// -------- + +// Specialised hash. +// Used to hash X25519 shared secrets. +void crypto_chacha20_h(uint8_t out[32], + const uint8_t key[32], + const uint8_t in [16]); + +// Unauthenticated stream cipher. +// Don't forget to add authentication. +uint64_t crypto_chacha20_djb(uint8_t *cipher_text, + const uint8_t *plain_text, + size_t text_size, + const uint8_t key[32], + const uint8_t nonce[8], + uint64_t ctr); +uint32_t crypto_chacha20_ietf(uint8_t *cipher_text, + const uint8_t *plain_text, + size_t text_size, + const uint8_t key[32], + const uint8_t nonce[12], + uint32_t ctr); +uint64_t crypto_chacha20_x(uint8_t *cipher_text, + const uint8_t *plain_text, + size_t text_size, + const uint8_t key[32], + const uint8_t nonce[24], + uint64_t ctr); + + +// Poly 1305 +// --------- + +// This is a *one time* authenticator. +// Disclosing the mac reveals the key. +// See crypto_lock() on how to use it properly. + +// Direct interface +void crypto_poly1305(uint8_t mac[16], + const uint8_t *message, size_t message_size, + const uint8_t key[32]); + +// Incremental interface +typedef struct { + // Do not rely on the size or contents of this type, + // for they may change without notice. + uint8_t c[16]; // chunk of the message + size_t c_idx; // How many bytes are there in the chunk. + uint32_t r [4]; // constant multiplier (from the secret key) + uint32_t pad[4]; // random number added at the end (from the secret key) + uint32_t h [5]; // accumulated hash +} crypto_poly1305_ctx; + +void crypto_poly1305_init (crypto_poly1305_ctx *ctx, const uint8_t key[32]); +void crypto_poly1305_update(crypto_poly1305_ctx *ctx, + const uint8_t *message, size_t message_size); +void crypto_poly1305_final (crypto_poly1305_ctx *ctx, uint8_t mac[16]); + + +// Elligator 2 +// ----------- + +// Elligator mappings proper +void crypto_elligator_map(uint8_t curve [32], const uint8_t hidden[32]); +int crypto_elligator_rev(uint8_t hidden[32], const uint8_t curve [32], + uint8_t tweak); + +// Easy to use key pair generation +void crypto_elligator_key_pair(uint8_t hidden[32], uint8_t secret_key[32], + uint8_t seed[32]); + +#ifdef __cplusplus +} +#endif + +#endif // MONOCYPHER_H diff --git a/src/pkg.c b/src/pkg.c index 0616bd6..f5c487c 100644 --- a/src/pkg.c +++ b/src/pkg.c @@ -15,63 +15,35 @@ #include "common.h" #include "pkg.h" -/* null dependencies/conflicts for error throwing */ -dep_t null_dep = { "null", {-1, -1, -1, NULL} }; -conf_t null_conf = { "null", {-1, -1, -1, NULL} }; - -char -*join_with_cwd(const char *path) -{ - char cwd[PATH_MAX]; - - if (!getcwd(cwd, sizeof(cwd))) { - perror("getcwd"); - return NULL; - } - - size_t length = strlen(cwd + strlen(path) + 2); - char *result = malloc(length); - if (!result) { return NULL; } - - snprintf(result, length, "%s/%s", cwd, path); - return result; -} - -/* - * gl_pkgver - * DESCRIPTION: takes three integers and formats them as a semver_t struct - * PARAMETERS: - * ~ int major (the MAJOR number) - * ~ int minor (the MINOR number) - * ~ int patch (the PATCH number) - * RETURNS: - * ~ a pointer to a semver_t struct - */ -semver_t -gl_pkgver(int major, int minor, int patch) -{ - semver_t pkgver = {major, minor, patch, NULL}; - return pkgver; -} - +/* "MAJ.MIN.PAT[-TAG]". Each number is read with strtol and clamped to + * 0..INT_MAX (a manifest is untrusted: sscanf's %d overflowing is + * undefined); anything missing or unparseable is 0. */ semver_t gl_parse_semver(const char *str) { semver_t v = {0, 0, 0, NULL}; if (!str) { return v; } - + char buffer[64]; - strncpy(buffer, str, sizeof(buffer) - 1); - buffer[sizeof(buffer) - 1] = '\0'; + snprintf(buffer, sizeof(buffer), "%s", str); char *dash = strchr(buffer, '-'); if (dash) { *dash = '\0'; - dash++; - v.tag = strdup(dash); + v.tag = strdup(dash + 1); } - sscanf(buffer, "%d.%d.%d", &v.maj, &v.min, &v.pat); + int *parts[] = { &v.maj, &v.min, &v.pat }; + const char *p = buffer; + for (int i = 0; i < 3 && *p; i++) { + char *end; + errno = 0; + long n = strtol(p, &end, 10); + if (end == p) { break; } + *parts[i] = (errno || n < 0) ? 0 : n > INT_MAX ? INT_MAX : (int)n; + if (*end != '.') { break; } + p = end + 1; + } return v; } @@ -81,47 +53,6 @@ gl_free_semver(semver_t *v) if (v && v->tag) { free (v->tag); }; } -gl_pkg_t empty = { - "null", - {-1, -1, -1, NULL}, - "Null package for internal use. Returned upon an error.", - "null@null.null", - "null", - "null", - &null_dep, - &null_conf -}; - -int -gl_mkdir(dir_t *dir) -{ - if (mkdir(dir -> path, dir -> mode) == 0) { - return 0; - } - - if (errno == EEXIST) { return 0; } - perror("libglacier: mkdir"); - return -1; -} - -gl_pkg_t -*gl_spawn_pkg(char *name, semver_t *ver, char *desc, char *maint, char *copy, char *repo, dep_t *dep, conf_t *conf) -{ - gl_pkg_t *new_pkg = malloc(sizeof(gl_pkg_t)); - if (!new_pkg) { return NULL; } - - new_pkg -> pkg_name = name; - new_pkg -> pkg_ver = *ver; - new_pkg -> pkg_desc = desc; - new_pkg -> pkg_maint = maint; - new_pkg -> pkg_copyright = copy; - new_pkg -> pkg_repo = repo; - new_pkg -> pkg_dependencies = dep; - new_pkg -> pkg_conflicts = conf; - - return new_pkg; -} - /* * gl_valid_pkg_ident * DESCRIPTION: checks that a package name or repo name is safe to use as a @@ -151,26 +82,103 @@ gl_valid_pkg_ident(const char *s) return true; } +/* A manifest comes out of an untrusted archive. libconfig honours + * `@include "FILE"` at the start of a line, which would have it read any + * file (as root, under syspkg): such a manifest is refused. */ +static char * +read_manifest_text(const char *filename) +{ + FILE *f = fopen(filename, "rb"); + if (!f) { return NULL; } + enum { MAX_MANIFEST = 1024 * 1024 }; + char *buf = malloc(MAX_MANIFEST + 1); + size_t n = buf ? fread(buf, 1, MAX_MANIFEST + 1, f) : 0; + bool bad = !buf || ferror(f) || n > MAX_MANIFEST || memchr(buf, '\0', n); + fclose(f); + if (bad) { + free(buf); + return NULL; + } + buf[n] = '\0'; + for (const char *line = buf; line; line = strchr(line, '\n')) { + if (*line == '\n') { line++; } + while (*line == ' ' || *line == '\t') { line++; } + if (strncmp(line, "@include", 8) == 0) { + free(buf); + return NULL; + } + } + return buf; +} + +/* PKG_DEPS / PKG_CONFS: a list of { PKG_NAME; PKG_VER; }, each name a + * gl_valid_pkg_ident, into a NULL-name-terminated array of + * (name, version) pairs, names and vers parallel. false if malformed. */ +static bool +manifest_relations(config_t *cfg, const char *key, char ***names, semver_t **vers) +{ + *names = NULL; + *vers = NULL; + config_setting_t *l = config_lookup(cfg, key); + if (!l) { return true; } + if (!config_setting_is_list(l)) { return false; } + + int count = config_setting_length(l); + *names = calloc((size_t)count + 1, sizeof(**names)); + *vers = calloc((size_t)count + 1, sizeof(**vers)); + if (!*names || !*vers) { return false; } + for (int i = 0; i < count; i++) { + config_setting_t *d = config_setting_get_elem(l, (unsigned)i); + const char *name = NULL, *ver = NULL; + config_setting_lookup_string(d, "PKG_NAME", &name); + config_setting_lookup_string(d, "PKG_VER", &ver); + if (!gl_valid_pkg_ident(name) || !((*names)[i] = strdup(name))) { + return false; + } + (*vers)[i] = gl_parse_semver(ver); + } + return true; +} + +static void +free_relations(char **names, semver_t *vers) +{ + for (size_t i = 0; names && names[i]; i++) { + free(names[i]); + gl_free_semver(&vers[i]); + } + free(names); + free(vers); +} + int gl_gpm2gpkg(const char *filename, gl_pkg_t *pkg) { + char *text = read_manifest_text(filename); + if (!text) { return ENOENT; } + config_t cfg; - config_setting_t *deps, *confs; - const char *tmp_str; - const char *name = NULL, *repo = NULL; - config_init(&cfg); - - if (!config_read_file(&cfg, filename)) { + int parsed = config_read_string(&cfg, text); + free(text); + if (!parsed) { config_destroy(&cfg); return ENOENT; } /* Validate before anything is strdup'd, so a rejected manifest leaves * pkg untouched and the caller has nothing to free. */ + const char *tmp_str, *name = NULL, *repo = NULL; + char **dnames, **cnames; + semver_t *dvers, *cvers; config_lookup_string(&cfg, "PKG_NAME", &name); config_lookup_string(&cfg, "PKG_REPO", &repo); - if (!gl_valid_pkg_ident(name) || !gl_valid_pkg_ident(repo)) { + bool ok = gl_valid_pkg_ident(name) && gl_valid_pkg_ident(repo); + bool rels = manifest_relations(&cfg, "PKG_DEPS", &dnames, &dvers); + rels = manifest_relations(&cfg, "PKG_CONFS", &cnames, &cvers) && rels; + if (!ok || !rels) { + free_relations(dnames, dvers); + free_relations(cnames, cvers); config_destroy(&cfg); return EINVAL; } @@ -182,102 +190,37 @@ gl_gpm2gpkg(const char *filename, gl_pkg_t *pkg) if (config_lookup_string(&cfg, "PKG_COPYRIGHT", &tmp_str)) { pkg->pkg_copyright = strdup(tmp_str); } if (config_lookup_string(&cfg, "PKG_VER", &tmp_str)) { pkg->pkg_ver = gl_parse_semver(tmp_str); } - deps = config_lookup(&cfg, "PKG_DEPS"); - if (deps && config_setting_is_list(deps)) { - int count = config_setting_length(deps); - pkg->pkg_dependencies = calloc((size_t)(count + 1), sizeof(dep_t)); - - for (int i = 0; i < count; i++) { - config_setting_t *d = config_setting_get_elem(deps, (unsigned int)i); - const char *dep_name = NULL; - const char *dep_ver = NULL; - - config_setting_lookup_string(d, "PKG_NAME", &dep_name); - config_setting_lookup_string(d, "PKG_VER", &dep_ver); - - pkg->pkg_dependencies[i].dep_name = dep_name ? strdup(dep_name) : NULL; - pkg->pkg_dependencies[i].dep_ver = gl_parse_semver(dep_ver); + if (dnames) { + size_t n = 0; + while (dnames[n]) { n++; } + pkg->pkg_dependencies = calloc(n + 1, sizeof(dep_t)); + for (size_t i = 0; pkg->pkg_dependencies && i < n; i++) { + pkg->pkg_dependencies[i].dep_name = dnames[i]; + pkg->pkg_dependencies[i].dep_ver = dvers[i]; + dnames[i] = NULL; } } - - confs = config_lookup(&cfg, "PKG_CONFS"); - if (confs && config_setting_is_list(confs)) { - int count = config_setting_length(confs); - pkg->pkg_conflicts = calloc((size_t)(count + 1), sizeof(conf_t)); - - for (int i = 0; i < count; i++) { - config_setting_t *c = config_setting_get_elem(confs, (unsigned int)i); - const char *conf_name = NULL; - const char *conf_ver = NULL; - - config_setting_lookup_string(c, "PKG_NAME", &conf_name); - config_setting_lookup_string(c, "PKG_VER", &conf_ver); - - pkg->pkg_conflicts[i].conf_name = conf_name ? strdup(conf_name) : NULL; - pkg->pkg_conflicts[i].conf_ver = gl_parse_semver(conf_ver); + if (cnames) { + size_t n = 0; + while (cnames[n]) { n++; } + pkg->pkg_conflicts = calloc(n + 1, sizeof(conf_t)); + for (size_t i = 0; pkg->pkg_conflicts && i < n; i++) { + pkg->pkg_conflicts[i].conf_name = cnames[i]; + pkg->pkg_conflicts[i].conf_ver = cvers[i]; + cnames[i] = NULL; } } - + free_relations(dnames, dvers); + free_relations(cnames, cvers); config_destroy(&cfg); - return 0; -} -char -*gl_cat_dir(const dir_t *a, const dir_t *b) -{ - size_t length = strlen(a->path) + strlen(b->path) + 2; - char *result = malloc(length); - - if (!result) { return NULL; } - - snprintf(result, length, "%s/%s", a->path, b-> path); - return result; -} - -char -*gl_cat_dir_and_ver(const dir_t *base, const gl_pkg_t *pkg) -{ - char version[32]; - snprintf(version, sizeof(version), "%d.%d.%d", - pkg->pkg_ver.maj, pkg->pkg_ver.min, pkg->pkg_ver.pat); - - size_t length = strlen(base->path) + strlen(pkg->pkg_name) + strlen(version) + 3; - char *result = malloc(length); - if (!result) { return NULL; } - - snprintf(result, length, "%s/%s-%s", base->path, pkg->pkg_name, version); - return result; -} - -dir_t -gl_return_dir(const dir_t *a, const dir_t *b, mode_t mode) -{ - char *path = gl_cat_dir(a, b); - dir_t result = { path, mode }; - return result; -} - -dir_t -gl_return_dir_and_ver(const dir_t *base, gl_pkg_t *pkg, mode_t mode) -{ - char *path = gl_cat_dir_and_ver(base, pkg); - dir_t result = { path, mode }; - return result; -} - -void -gl_free_path(char *path) -{ - free(path); -} - -void -gl_free_dir(dir_t *dir) -{ - if (dir && dir->path) { - free((char *)dir->path); - dir->path = NULL; + if (!pkg->pkg_name || !pkg->pkg_repo || (dnames && !pkg->pkg_dependencies) || + (cnames && !pkg->pkg_conflicts)) { + gl_free_pkg(pkg); + memset(pkg, 0, sizeof(*pkg)); + return ENOMEM; } + return 0; } void @@ -310,43 +253,6 @@ gl_free_pkg(gl_pkg_t *pkg) } } -void -gl_show_pkg_props(const gl_pkg_t *pkg) -{ - if (!pkg) { - printf("(null pkg)\n"); - return; - } - - printf("Package: %s\n", pkg->pkg_name); - printf("Version: %d.%d.%d\n", pkg->pkg_ver.maj, pkg->pkg_ver.min, pkg->pkg_ver.pat); - printf("Description: %s\n", pkg->pkg_desc); - printf("Maintainer: %s\n", pkg->pkg_maint); - printf("Copyright: %s\n", pkg->pkg_copyright); - - if (pkg->pkg_dependencies) { - printf("Dependencies:\n"); - dep_t *dep = pkg->pkg_dependencies; - printf("\t~ %s (%d.%d.%d)\n", - dep->dep_name, - dep->dep_ver.maj, dep->dep_ver.min, dep->dep_ver.pat); - } - else { - printf("Dependencies: (none)\n"); - } - - if (pkg->pkg_conflicts) { - printf("Conflicting packages:\n"); - conf_t *conf = pkg->pkg_conflicts; - printf("\t~ %s (%d.%d.%d)\n", - conf->conf_name, - conf->conf_ver.maj, conf->conf_ver.min, conf->conf_ver.pat); - } - else { - printf("Conflicting packages: (none)\n"); - } -} - /* * Read integer `key` from a libconfig file (glacier.conf). Distinguishes * the failures a caller has to report differently: @@ -383,3 +289,418 @@ gl_conf_get_int(const char *path, const char *key, int *out) config_destroy(&cfg); return GL_CONF_OK; } + +/* A repos.conf url: https:// or file://, then at least one printable, + * non-space ASCII character. http:// and everything else are refused. */ +static bool +valid_repo_url(const char *u) +{ + const char *rest; + if (strncmp(u, "https://", 8) == 0) + rest = u + 8; + else if (strncmp(u, "file://", 7) == 0) + rest = u + 7; + else + return false; + if (*rest == '\0') + return false; + for (; *rest; rest++) + if (*rest < 0x21 || *rest > 0x7e) + return false; + return true; +} + +/* + * Read and check a whole repos.conf (format: gl_conf_repo_keys). On + * GL_CONF_OK, *cfg is open (the caller config_destroy()s it) and *repos + * is its `repos` list; on anything else nothing is left open. + */ +static gl_conf_status_t +read_repos_conf(const char *path, config_t *cfg, config_setting_t **repos) +{ + config_init(cfg); + if (!config_read_file(cfg, path)) { + config_destroy(cfg); + return GL_CONF_ERR_READ; + } + + config_setting_t *root = config_root_setting(cfg); + config_setting_t *list = config_setting_get_member(root, "repos"); + if (config_setting_length(root) != 1 || !list || + config_setting_type(list) != CONFIG_TYPE_LIST) + goto invalid; + + int n = config_setting_length(list); + for (int i = 0; i < n; i++) { + config_setting_t *e = config_setting_get_elem(list, (unsigned)i); + if (config_setting_type(e) != CONFIG_TYPE_GROUP) + goto invalid; + + config_setting_t *name = config_setting_get_member(e, "name"); + config_setting_t *url = config_setting_get_member(e, "url"); + config_setting_t *keys = config_setting_get_member(e, "keys"); + if (!name || !keys || + config_setting_length(e) != (url ? 3 : 2)) + goto invalid; + const char *nm = config_setting_get_string(name); + if (!nm || !gl_valid_pkg_ident(nm)) + goto invalid; + if (url) { + const char *u = config_setting_get_string(url); + if (!u || !valid_repo_url(u)) + goto invalid; + } + for (int j = 0; j < i; j++) { + config_setting_t *prev = config_setting_get_member( + config_setting_get_elem(list, (unsigned)j), "name"); + if (strcmp(config_setting_get_string(prev), nm) == 0) + goto invalid; + } + if (config_setting_type(keys) != CONFIG_TYPE_ARRAY && + config_setting_type(keys) != CONFIG_TYPE_LIST) + goto invalid; + int nk = config_setting_length(keys); + for (int k = 0; k < nk; k++) { + const char *key = config_setting_get_string( + config_setting_get_elem(keys, (unsigned)k)); + if (!key || !gl_valid_pkg_ident(key)) + goto invalid; + } + } + *repos = list; + return GL_CONF_OK; +invalid: + config_destroy(cfg); + return GL_CONF_ERR_INVALID; +} + +/* + * The names of the keys repos.conf trusts for `repo` (REPO_DESIGN.txt + * section 3), '\n'-separated (each followed by '\n') into out. + * GL_CONF_OK out set (possibly "": the repo lists no keys) + * GL_CONF_ERR_READ file missing, unreadable, or not valid libconfig + * GL_CONF_ERR_MISSING the file is valid, but doesn't list `repo` + * GL_CONF_ERR_INVALID anything else than the format below + * GL_CONF_ERR_SPACE the names don't fit in outlen + * The WHOLE file is checked on every call, not only `repo`'s entry, and + * nothing unknown is ignored (a setting that silently never parses is + * CLAUDE.md bug 10's class): + * repos = ( { name = "base"; url = "https://..."; keys = [ "k" ]; }, ... ); + * `repos` is the only top-level setting; each entry is a group with a + * name (a gl_valid_pkg_ident, unique), an optional url (valid_repo_url) + * and keys (an array or list of gl_valid_pkg_ident strings). + */ +gl_conf_status_t +gl_conf_repo_keys(const char *path, const char *repo, char *out, size_t outlen) +{ + if (!out || outlen == 0) + return GL_CONF_ERR_SPACE; + out[0] = '\0'; + + config_t cfg; + config_setting_t *repos; + gl_conf_status_t st = read_repos_conf(path, &cfg, &repos); + if (st != GL_CONF_OK) + return st; + + st = GL_CONF_ERR_MISSING; + int n = config_setting_length(repos); + for (int i = 0; i < n; i++) { + config_setting_t *e = config_setting_get_elem(repos, (unsigned)i); + if (strcmp(config_setting_get_string( + config_setting_get_member(e, "name")), repo) != 0) + continue; + config_setting_t *keys = config_setting_get_member(e, "keys"); + size_t used = 0; + int nk = config_setting_length(keys); + for (int k = 0; k < nk; k++) { + const char *key = config_setting_get_string( + config_setting_get_elem(keys, (unsigned)k)); + size_t len = strlen(key); + if (used + len + 2 > outlen) { + out[0] = '\0'; + st = GL_CONF_ERR_SPACE; + goto out; + } + memcpy(out + used, key, len); + out[used + len] = '\n'; + used += len + 1; + out[used] = '\0'; + } + st = GL_CONF_OK; + break; + } +out: + config_destroy(&cfg); + return st; +} + +/* + * Every repository repos.conf lists, in its order (= priority), one + * "NAME\tURL\n" line each into out; URL is empty for an entry without + * one. The whole file is checked as by gl_conf_repo_keys. + * GL_CONF_OK out set (possibly "": no repositories) + * GL_CONF_ERR_READ file missing, unreadable, or not valid libconfig + * GL_CONF_ERR_INVALID not the format gl_conf_repo_keys describes + * GL_CONF_ERR_SPACE the lines don't fit in outlen + */ +gl_conf_status_t +gl_conf_repos(const char *path, char *out, size_t outlen) +{ + if (!out || outlen == 0) + return GL_CONF_ERR_SPACE; + out[0] = '\0'; + + config_t cfg; + config_setting_t *repos; + gl_conf_status_t st = read_repos_conf(path, &cfg, &repos); + if (st != GL_CONF_OK) + return st; + + size_t used = 0; + int n = config_setting_length(repos); + for (int i = 0; i < n; i++) { + config_setting_t *e = config_setting_get_elem(repos, (unsigned)i); + const char *nm = config_setting_get_string( + config_setting_get_member(e, "name")); + config_setting_t *url = config_setting_get_member(e, "url"); + const char *u = url ? config_setting_get_string(url) : ""; + int len = snprintf(out + used, outlen - used, "%s\t%s\n", nm, u); + if (len < 0 || (size_t)len >= outlen - used) { + out[0] = '\0'; + st = GL_CONF_ERR_SPACE; + break; + } + used += (size_t)len; + } + config_destroy(&cfg); + return st; +} + +/* ------------------------------------------------------------------ */ +/* Repository indexes (REPO_DESIGN.txt section 4) */ +/* ------------------------------------------------------------------ */ + +/* A version as the store names it: "%d.%d.%d", each part decimal + * without leading zeros and fitting an int. Anything else would name a + * version directory the store never makes. */ +static bool +valid_index_ver(const char *v) +{ + for (int part = 0; part < 3; part++) { + const char *start = v; + long n = 0; + while (*v >= '0' && *v <= '9') { + n = n * 10 + (*v - '0'); + if (n > INT_MAX) + return false; + v++; + } + if (v == start || (*start == '0' && v - start > 1)) + return false; + if (part < 2 && *v++ != '.') + return false; + } + return *v == '\0'; +} + +static bool +valid_sha256_field(const char *s) +{ + if (strncmp(s, "sha256:", 7) != 0 || strlen(s) != 7 + 64) + return false; + for (s += 7; *s; s++) + if (!((*s >= '0' && *s <= '9') || (*s >= 'a' && *s <= 'f'))) + return false; + return true; +} + +/* A group with exactly the members named in `names` (NULL-terminated). */ +static bool +has_exactly(config_setting_t *g, const char *const *names) +{ + if (!g || config_setting_type(g) != CONFIG_TYPE_GROUP) + return false; + int n = 0; + for (; names[n]; n++) + if (!config_setting_get_member(g, names[n])) + return false; + return config_setting_length(g) == n; +} + +static const char * +str_member(config_setting_t *g, const char *name) +{ + config_setting_t *m = config_setting_get_member(g, name); + return m && config_setting_type(m) == CONFIG_TYPE_STRING ? + config_setting_get_string(m) : NULL; +} + +/* An integer member (int or int64), or false. */ +static bool +int_member(config_setting_t *g, const char *name, int64_t *out) +{ + config_setting_t *m = config_setting_get_member(g, name); + if (!m || (config_setting_type(m) != CONFIG_TYPE_INT && + config_setting_type(m) != CONFIG_TYPE_INT64)) + return false; + *out = (int64_t)config_setting_get_int64(m); + return true; +} + +/* DEPS or CONFS: a list of { NAME; VER; } groups, as "NAME VER\n" lines + * into a new string (*out; "" for an empty list). */ +static bool +read_relations(config_setting_t *pkg, const char *name, char **out) +{ + static const char *const members[] = { "NAME", "VER", NULL }; + config_setting_t *l = config_setting_get_member(pkg, name); + if (!l || config_setting_type(l) != CONFIG_TYPE_LIST) + return false; + int n = config_setting_length(l); + size_t cap = 1, used = 0; + for (int i = 0; i < n; i++) { + config_setting_t *r = config_setting_get_elem(l, (unsigned)i); + const char *nm, *ver; + if (!has_exactly(r, members) || !(nm = str_member(r, "NAME")) || + !(ver = str_member(r, "VER")) || !gl_valid_pkg_ident(nm) || + !valid_index_ver(ver)) + return false; + cap += strlen(nm) + strlen(ver) + 2; + } + if (!(*out = malloc(cap))) + return false; + (*out)[0] = '\0'; + for (int i = 0; i < n; i++) { + config_setting_t *r = config_setting_get_elem(l, (unsigned)i); + used += (size_t)snprintf(*out + used, cap - used, "%s %s\n", + str_member(r, "NAME"), str_member(r, "VER")); + } + return true; +} + +void +gl_index_free(gl_index_t *idx) +{ + if (!idx) + return; + for (size_t i = 0; i < idx->npkgs; i++) { + gl_index_pkg_t *p = &idx->pkgs[i]; + free(p->name); + free(p->ver); + free(p->file); + free(p->sha256); + free(p->deps); + free(p->confs); + } + free(idx->pkgs); + free(idx->repo); + memset(idx, 0, sizeof(*idx)); +} + +/* + * Parse an index's bytes (text[0..len)) into *out, which is zeroed + * first; free it with gl_index_free on GL_INDEX_OK. The caller checks + * the signature over the SAME bytes first: nothing here is trusted + * otherwise, and nothing here checks SERIAL or VALID_UNTIL against + * anything (that is the caller's rollback/expiry policy). + * + * Strict, like repos.conf: exactly REPO, SERIAL (> 0), VALID_UNTIL + * (> 0) and PKGS at the top; each package exactly NAME, VER, FILE, + * SHA256, SIZE (>= 0), DEPS, CONFS; NAME a gl_valid_pkg_ident; VER as + * the store names versions; FILE exactly "NAME-VER.gpkg"; SHA256 + * "sha256:" + 64 lowercase hex; DEPS/CONFS lists of { NAME; VER; }; no + * NAME + VER twice. A NUL or '@' anywhere is refused before parsing: + * an index never has one, and '@' is how libconfig spells @include. + * GL_INDEX_OK, GL_INDEX_ERR_FORMAT, GL_INDEX_ERR_REPO (well formed, + * but REPO is not `repo`), GL_INDEX_ERR_NOMEM + */ +gl_index_status_t +gl_index_parse(const char *text, size_t len, const char *repo, gl_index_t *out) +{ + static const char *const top[] = { "REPO", "SERIAL", "VALID_UNTIL", "PKGS", NULL }; + static const char *const fields[] = { "NAME", "VER", "FILE", "SHA256", + "SIZE", "DEPS", "CONFS", NULL }; + + if (!out) + return GL_INDEX_ERR_FORMAT; + memset(out, 0, sizeof(*out)); + if (!text || !repo || memchr(text, '\0', len) || memchr(text, '@', len)) + return GL_INDEX_ERR_FORMAT; + + char *buf = malloc(len + 1); + if (!buf) + return GL_INDEX_ERR_NOMEM; + memcpy(buf, text, len); + buf[len] = '\0'; + + config_t cfg; + config_init(&cfg); + int ok = config_read_string(&cfg, buf); + free(buf); + if (!ok) { + config_destroy(&cfg); + return GL_INDEX_ERR_FORMAT; + } + + gl_index_status_t st = GL_INDEX_ERR_FORMAT; + config_setting_t *root = config_root_setting(&cfg); + const char *r = str_member(root, "REPO"); + config_setting_t *pkgs = config_setting_get_member(root, "PKGS"); + if (config_setting_length(root) != 4 || !has_exactly(root, top) || + !r || !gl_valid_pkg_ident(r) || + !int_member(root, "SERIAL", &out->serial) || out->serial <= 0 || + !int_member(root, "VALID_UNTIL", &out->valid_until) || + out->valid_until <= 0 || + config_setting_type(pkgs) != CONFIG_TYPE_LIST) + goto fail; + + size_t n = (size_t)config_setting_length(pkgs); + if (n && !(out->pkgs = calloc(n, sizeof(*out->pkgs)))) { + st = GL_INDEX_ERR_NOMEM; + goto fail; + } + for (size_t i = 0; i < n; i++) { + config_setting_t *e = config_setting_get_elem(pkgs, (unsigned)i); + gl_index_pkg_t *p = &out->pkgs[i]; + out->npkgs = i + 1; + const char *nm, *ver, *file, *sha; + if (!has_exactly(e, fields) || + !(nm = str_member(e, "NAME")) || !gl_valid_pkg_ident(nm) || + !(ver = str_member(e, "VER")) || !valid_index_ver(ver) || + !(file = str_member(e, "FILE")) || + !(sha = str_member(e, "SHA256")) || !valid_sha256_field(sha) || + !int_member(e, "SIZE", &p->size) || p->size < 0) + goto fail; + char want[PATH_MAX]; + snprintf(want, sizeof want, "%s-%s.gpkg", nm, ver); + if (strcmp(file, want) != 0) + goto fail; + for (size_t j = 0; j < i; j++) + if (strcmp(out->pkgs[j].name, nm) == 0 && + strcmp(out->pkgs[j].ver, ver) == 0) + goto fail; + if (!(p->name = strdup(nm)) || !(p->ver = strdup(ver)) || + !(p->file = strdup(file)) || !(p->sha256 = strdup(sha))) { + st = GL_INDEX_ERR_NOMEM; + goto fail; + } + if (!read_relations(e, "DEPS", &p->deps) || + !read_relations(e, "CONFS", &p->confs)) + goto fail; + } + if (strcmp(r, repo) != 0) { + st = GL_INDEX_ERR_REPO; + goto fail; + } + if (!(out->repo = strdup(r))) { + st = GL_INDEX_ERR_NOMEM; + goto fail; + } + config_destroy(&cfg); + return GL_INDEX_OK; +fail: + config_destroy(&cfg); + gl_index_free(out); + return st; +} diff --git a/src/pkg.h b/src/pkg.h index f93b625..f41bca5 100644 --- a/src/pkg.h +++ b/src/pkg.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "common.h" @@ -43,44 +44,57 @@ typedef struct { } gl_pkg_t; -extern dep_t null_dep; -extern conf_t null_conf; -extern gl_pkg_t empty; - -semver_t gl_pkgver(int major, int minor, int patch); semver_t gl_parse_semver(const char *str); void gl_free_semver(semver_t *v); - -gl_pkg_t *gl_spawn_pkg( - char *name, semver_t *ver, char *desc, - char *maint, char *copy, char *repo, - dep_t *dep, conf_t *conf -); - -void gl_show_pkg_props(const gl_pkg_t *pkg); void gl_free_pkg(gl_pkg_t *pkg); -int gl_mkdir(dir_t *dir); -char *gl_cat_dir(const dir_t *a, const dir_t *b); -char *gl_cat_dir_and_ver(const dir_t *base, const gl_pkg_t *pkg); -dir_t gl_return_dir(const dir_t *a, const dir_t *b, mode_t mode); -dir_t gl_return_dir_and_ver(const dir_t *base, gl_pkg_t *pkg, mode_t mode); -void gl_free_path(char *path); -void gl_free_dir(dir_t *dir); - typedef enum { GL_CONF_OK = 0, GL_CONF_ERR_READ = 1, GL_CONF_ERR_MISSING = 2, - GL_CONF_ERR_TYPE = 3 + GL_CONF_ERR_TYPE = 3, + GL_CONF_ERR_INVALID = 4, + GL_CONF_ERR_SPACE = 5 } gl_conf_status_t; -#define GL_CONF_PATH "/etc/glacier/glacier.conf" gl_conf_status_t gl_conf_get_int(const char *path, const char *key, int *out); +gl_conf_status_t gl_conf_repo_keys(const char *path, const char *repo, + char *out, size_t outlen); +gl_conf_status_t gl_conf_repos(const char *path, char *out, size_t outlen); + +/* A repository index (REPO_DESIGN.txt section 4), as gl_index_parse + * reads it. deps/confs are "NAME VER\n" lines ("" for none). */ +typedef struct { + char *name; + char *ver; + char *file; + char *sha256; + int64_t size; + char *deps; + char *confs; +} gl_index_pkg_t; + +typedef struct { + char *repo; + int64_t serial; + int64_t valid_until; + size_t npkgs; + gl_index_pkg_t *pkgs; +} gl_index_t; + +typedef enum { + GL_INDEX_OK = 0, + GL_INDEX_ERR_FORMAT = 1, + GL_INDEX_ERR_REPO = 2, + GL_INDEX_ERR_NOMEM = 3 +} gl_index_status_t; + +gl_index_status_t gl_index_parse(const char *text, size_t len, + const char *repo, gl_index_t *out); +void gl_index_free(gl_index_t *idx); bool gl_valid_pkg_ident(const char *s); int gl_gpm2gpkg(const char *filename, gl_pkg_t *pkg); -char *join_with_cwd(const char *path); #endif diff --git a/src/runtime.c b/src/runtime.c deleted file mode 100644 index 35c9de5..0000000 --- a/src/runtime.c +++ /dev/null @@ -1,45 +0,0 @@ -#define _POSIX_C_SOURCE 200809L -#define _DEFAULT_SOURCE -#define _XOPEN_SOURCE 700 - -#include -#include -#include - -#include "color.h" - -bool -lg_is_root(void) -{ - if (getuid() != 0) { - return false; - } - else if (getuid() == 0) { - return true; - } - else { - return false; - } -} - -bool -lg_runtime_exists(void) -{ - const char *rt_files[] = { - "/etc/glacier/glacier.conf", - "/etc/glacier/make.conf" - }; - - size_t num_of_files = sizeof(rt_files) / sizeof(rt_files[0]); - - for (size_t f_index = 0; f_index < num_of_files; f_index++) { - if (access(rt_files[f_index], F_OK) == 0) { - continue; - } - else { - return false; - } - } - - return true; -} diff --git a/src/sign.c b/src/sign.c new file mode 100644 index 0000000..6b0263c --- /dev/null +++ b/src/sign.c @@ -0,0 +1,428 @@ +/* + * gl_sign: Ed25519 signatures in signify's file formats. See sign.h. + * + * Parsing is strict: exactly two lines, the "untrusted comment: " + * prefix, canonical base64 of exactly the expected length, the right + * algorithm bytes. Anything else is GL_SIGN_ERR_FORMAT. + */ + +#define _POSIX_C_SOURCE 200809L +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sign.h" +#include "monocypher/monocypher.h" +#include "monocypher/monocypher-ed25519.h" + +#define COMMENT_PREFIX "untrusted comment: " +#define COMMENT_PREFIX_LEN (sizeof COMMENT_PREFIX - 1) + +/* Decoded sizes of the three kinds of file. */ +#define PUBKEY_BYTES 42 +#define SIG_BYTES 74 +#define SECKEY_BYTES 104 + +/* A key or signature file is a few hundred bytes at most. */ +#define MAX_KEYFILE (COMMENT_PREFIX_LEN + GL_SIGN_MAX_COMMENT + 1 + 4 * ((SECKEY_BYTES + 2) / 3) + 1) + +/* Reads all of a regular file (at most `max` bytes) into a malloc'd + * buffer. */ +static int +read_all(const char *path, size_t max, uint8_t **out, size_t *len) +{ + int fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return GL_SIGN_ERR_IO; + + struct stat st; + if (fstat(fd, &st) != 0 || !S_ISREG(st.st_mode)) { + close(fd); + return GL_SIGN_ERR_IO; + } + if (st.st_size < 0 || (uintmax_t)st.st_size > max) { + close(fd); + return GL_SIGN_ERR_TOOBIG; + } + + size_t size = (size_t)st.st_size; + uint8_t *buf = malloc(size ? size : 1); + if (!buf) { + close(fd); + return GL_SIGN_ERR_IO; + } + + size_t got = 0; + while (got < size) { + ssize_t n = read(fd, buf + got, size - got); + if (n < 0 && errno == EINTR) + continue; + if (n <= 0) + break; + got += (size_t)n; + } + /* The file must not have grown or shrunk under us. */ + uint8_t extra; + ssize_t more = read(fd, &extra, 1); + close(fd); + if (got != size || more != 0) { + free(buf); + return GL_SIGN_ERR_IO; + } + + *out = buf; + *len = size; + return GL_SIGN_OK; +} + +static int +b64_value(uint8_t c) +{ + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '+') return 62; + if (c == '/') return 63; + return -1; +} + +/* Decodes canonical, padded base64 that must yield exactly `outlen` + * bytes. Returns 0 on success. */ +static int +b64_decode_exact(const uint8_t *in, size_t n, uint8_t *out, size_t outlen) +{ + if (n != (outlen + 2) / 3 * 4) + return -1; + + size_t o = 0; + for (size_t i = 0; i < n; i += 4) { + uint32_t t = 0; + size_t pad = 0; + for (size_t j = 0; j < 4; j++) { + uint8_t c = in[i + j]; + int v; + if (c == '=') { + /* Padding only in the last quartet's last two places. */ + if (i + 4 != n || j < 2) + return -1; + pad++; + v = 0; + } else { + if (pad) + return -1; + v = b64_value(c); + if (v < 0) + return -1; + } + t = (t << 6) | (uint32_t)v; + } + /* Canonical: the bits padding drops must be zero. */ + if ((pad == 1 && (t & 0xffu)) || (pad == 2 && (t & 0xffffu))) + return -1; + + size_t bytes = 3 - pad; + if (o + bytes > outlen) + return -1; + uint8_t b[3] = { (uint8_t)(t >> 16), (uint8_t)(t >> 8), (uint8_t)t }; + memcpy(out + o, b, bytes); + o += bytes; + } + return o == outlen ? 0 : -1; +} + +static void +b64_encode(const uint8_t *in, size_t n, char *out) +{ + static const char tab[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + size_t o = 0; + for (size_t i = 0; i < n; i += 3) { + uint32_t t = (uint32_t)in[i] << 16; + if (i + 1 < n) t |= (uint32_t)in[i + 1] << 8; + if (i + 2 < n) t |= in[i + 2]; + out[o++] = tab[(t >> 18) & 63]; + out[o++] = tab[(t >> 12) & 63]; + out[o++] = i + 1 < n ? tab[(t >> 6) & 63] : '='; + out[o++] = i + 2 < n ? tab[t & 63] : '='; + } + out[o] = '\0'; +} + +/* Reads a signify file and decodes its payload into out[0..outlen). */ +static int +read_signify(const char *path, uint8_t *out, size_t outlen) +{ + uint8_t *buf; + size_t len; + int st = read_all(path, MAX_KEYFILE, &buf, &len); + if (st == GL_SIGN_ERR_TOOBIG) + return GL_SIGN_ERR_FORMAT; + if (st != GL_SIGN_OK) + return st; + + st = GL_SIGN_ERR_FORMAT; + if (len < COMMENT_PREFIX_LEN || memcmp(buf, COMMENT_PREFIX, COMMENT_PREFIX_LEN) != 0) + goto out; + + uint8_t *nl = memchr(buf, '\n', len); + if (!nl || memchr(buf, '\0', (size_t)(nl - buf))) + goto out; + if ((size_t)(nl - buf) - COMMENT_PREFIX_LEN > GL_SIGN_MAX_COMMENT) + goto out; + + /* The base64 line runs to a final newline that ends the file. */ + uint8_t *b64 = nl + 1; + uint8_t *end = buf + len; + if (b64 >= end || end[-1] != '\n') + goto out; + if (b64_decode_exact(b64, (size_t)(end - 1 - b64), out, outlen) != 0) + goto out; + if (out[0] != 'E' || out[1] != 'd') + goto out; + st = GL_SIGN_OK; +out: + crypto_wipe(buf, len); + free(buf); + return st; +} + +int +gl_sign_read_pubkey(const char *path, gl_sign_pubkey_t *out) +{ + uint8_t raw[PUBKEY_BYTES]; + int st = read_signify(path, raw, sizeof raw); + if (st != GL_SIGN_OK) + return st; + memcpy(out->keynum, raw + 2, GL_SIGN_KEYNUM_SIZE); + memcpy(out->key, raw + 10, sizeof out->key); + return GL_SIGN_OK; +} + +int +gl_sign_read_sig(const char *path, gl_sign_sig_t *out) +{ + uint8_t raw[SIG_BYTES]; + int st = read_signify(path, raw, sizeof raw); + if (st != GL_SIGN_OK) + return st; + memcpy(out->keynum, raw + 2, GL_SIGN_KEYNUM_SIZE); + memcpy(out->sig, raw + 10, sizeof out->sig); + return GL_SIGN_OK; +} + +/* sec[0..64) = seed | public key, as signify and Monocypher both lay + * it out. */ +static int +read_seckey(const char *path, uint8_t keynum[GL_SIGN_KEYNUM_SIZE], uint8_t sec[64]) +{ + uint8_t raw[SECKEY_BYTES]; + int st = read_signify(path, raw, sizeof raw); + if (st != GL_SIGN_OK) + goto out; + + st = GL_SIGN_ERR_FORMAT; + if (raw[2] != 'B' || raw[3] != 'K') + goto out; + uint32_t rounds = (uint32_t)raw[4] << 24 | (uint32_t)raw[5] << 16 + | (uint32_t)raw[6] << 8 | raw[7]; + if (rounds != 0) { + st = GL_SIGN_ERR_ENCRYPTED; + goto out; + } + + /* checksum = the first 8 bytes of SHA-512(seckey) */ + uint8_t digest[64]; + crypto_sha512(digest, raw + 40, 64); + int ok = memcmp(digest, raw + 24, 8) == 0; + crypto_wipe(digest, sizeof digest); + if (!ok) + goto out; + + memcpy(keynum, raw + 32, GL_SIGN_KEYNUM_SIZE); + memcpy(sec, raw + 40, 64); + st = GL_SIGN_OK; +out: + crypto_wipe(raw, sizeof raw); + return st; +} + +int +gl_sign_check_seckey(const char *path) +{ + uint8_t keynum[GL_SIGN_KEYNUM_SIZE], sec[64]; + int st = read_seckey(path, keynum, sec); + crypto_wipe(sec, sizeof sec); + return st; +} + +int +gl_sign_verify(const gl_sign_pubkey_t *pub, const gl_sign_sig_t *sig, + const uint8_t *msg, size_t len) +{ + if (memcmp(pub->keynum, sig->keynum, GL_SIGN_KEYNUM_SIZE) != 0) + return GL_SIGN_ERR_KEYNUM; + return crypto_ed25519_check(sig->sig, pub->key, msg, len) == 0 + ? GL_SIGN_OK : GL_SIGN_ERR_BAD; +} + +int +gl_sign_verify_file(const char *pubkey_path, const char *msg_path, + const char *sig_path) +{ + gl_sign_pubkey_t pub; + gl_sign_sig_t sig; + int st = gl_sign_read_pubkey(pubkey_path, &pub); + if (st != GL_SIGN_OK) + return st; + st = gl_sign_read_sig(sig_path, &sig); + if (st != GL_SIGN_OK) + return st; + /* Cheap mismatch first: no need to read the message. */ + if (memcmp(pub.keynum, sig.keynum, GL_SIGN_KEYNUM_SIZE) != 0) + return GL_SIGN_ERR_KEYNUM; + + uint8_t *msg; + size_t len; + st = read_all(msg_path, GL_SIGN_MAX_MSG, &msg, &len); + if (st != GL_SIGN_OK) + return st; + st = gl_sign_verify(&pub, &sig, msg, len); + free(msg); + return st; +} + +/* Writes "untrusted comment: COMMENT\n" + base64(raw) + "\n" to an fd + * opened with `flags`. */ +static int +write_signify(const char *path, int flags, mode_t mode, const char *comment, + const uint8_t *raw, size_t rawlen) +{ + char b64[4 * ((SECKEY_BYTES + 2) / 3) + 1]; + if (rawlen > SECKEY_BYTES) + return GL_SIGN_ERR_WRITE; + b64_encode(raw, rawlen, b64); + + int fd = open(path, O_WRONLY | O_CREAT | O_NOFOLLOW | O_CLOEXEC | flags, mode); + if (fd < 0) + return errno == EEXIST ? GL_SIGN_ERR_EXISTS : GL_SIGN_ERR_WRITE; + /* Exactly `mode`, whatever the umask: a secret key 0600, a public + * key 0644 (users' gpkg reads the system's keys). */ + if (fchmod(fd, mode) != 0) { + close(fd); + return GL_SIGN_ERR_WRITE; + } + + FILE *f = fdopen(fd, "w"); + if (!f) { + close(fd); + return GL_SIGN_ERR_WRITE; + } + int bad = fprintf(f, COMMENT_PREFIX "%s\n%s\n", comment, b64) < 0; + crypto_wipe(b64, sizeof b64); + if (fclose(f) != 0 || bad) + return GL_SIGN_ERR_WRITE; + return GL_SIGN_OK; +} + +int +gl_sign_file(const char *seckey_path, const char *msg_path, const char *sig_path) +{ + uint8_t keynum[GL_SIGN_KEYNUM_SIZE], sec[64]; + int st = read_seckey(seckey_path, keynum, sec); + if (st != GL_SIGN_OK) + return st; + + uint8_t *msg; + size_t len; + st = read_all(msg_path, GL_SIGN_MAX_MSG, &msg, &len); + if (st != GL_SIGN_OK) { + crypto_wipe(sec, sizeof sec); + return st; + } + + uint8_t raw[SIG_BYTES] = { 'E', 'd' }; + memcpy(raw + 2, keynum, GL_SIGN_KEYNUM_SIZE); + crypto_ed25519_sign(raw + 10, sec, msg, len); + crypto_wipe(sec, sizeof sec); + free(msg); + + /* "verify with NAME.pub", NAME = the secret key's name minus .sec */ + const char *base = strrchr(seckey_path, '/'); + base = base ? base + 1 : seckey_path; + size_t blen = strlen(base); + if (blen > 4 && strcmp(base + blen - 4, ".sec") == 0) + blen -= 4; + if (blen > GL_SIGN_MAX_COMMENT - 16) + blen = GL_SIGN_MAX_COMMENT - 16; + char comment[GL_SIGN_MAX_COMMENT + 1]; + snprintf(comment, sizeof comment, "verify with %.*s.pub", (int)blen, base); + + return write_signify(sig_path, O_TRUNC, 0644, comment, raw, sizeof raw); +} + +static int +random_bytes(uint8_t *buf, size_t n) +{ + while (n > 0) { + ssize_t got = getrandom(buf, n, 0); + if (got < 0 && errno == EINTR) + continue; + if (got <= 0) + return -1; + buf += got; + n -= (size_t)got; + } + return 0; +} + +int +gl_sign_generate(const char *pubkey_path, const char *seckey_path, + const char *comment) +{ + if (strchr(comment, '\n') || strlen(comment) > GL_SIGN_MAX_COMMENT - 11) + return GL_SIGN_ERR_FORMAT; + + /* rand = seed[32] | keynum[8] | salt[16] */ + uint8_t rand[56]; + if (random_bytes(rand, sizeof rand) != 0) + return GL_SIGN_ERR_RANDOM; + + uint8_t sec[64], pub[32]; + crypto_ed25519_key_pair(sec, pub, rand); /* wipes the seed */ + + uint8_t sraw[SECKEY_BYTES] = { 'E', 'd', 'B', 'K', 0, 0, 0, 0 }; + memcpy(sraw + 8, rand + 40, 16); /* salt (unused at 0 rounds) */ + uint8_t digest[64]; + crypto_sha512(digest, sec, sizeof sec); + memcpy(sraw + 24, digest, 8); /* checksum */ + memcpy(sraw + 32, rand + 32, GL_SIGN_KEYNUM_SIZE); + memcpy(sraw + 40, sec, sizeof sec); + + uint8_t praw[PUBKEY_BYTES] = { 'E', 'd' }; + memcpy(praw + 2, rand + 32, GL_SIGN_KEYNUM_SIZE); + memcpy(praw + 10, pub, sizeof pub); + + char c[GL_SIGN_MAX_COMMENT + 1]; + snprintf(c, sizeof c, "%s secret key", comment); + int st = write_signify(seckey_path, O_EXCL, 0600, c, sraw, sizeof sraw); + if (st == GL_SIGN_OK) { + snprintf(c, sizeof c, "%s public key", comment); + st = write_signify(pubkey_path, O_EXCL, 0644, c, praw, sizeof praw); + /* Never leave a secret key without its public half. */ + if (st != GL_SIGN_OK) + unlink(seckey_path); + } + + crypto_wipe(rand, sizeof rand); + crypto_wipe(sec, sizeof sec); + crypto_wipe(sraw, sizeof sraw); + crypto_wipe(digest, sizeof digest); + return st; +} diff --git a/src/sign.h b/src/sign.h new file mode 100644 index 0000000..6b1f7fe --- /dev/null +++ b/src/sign.h @@ -0,0 +1,82 @@ +#ifndef GL_SIGN_H_ +#define GL_SIGN_H_ + +/* + * gl_sign: Ed25519 signatures in OpenBSD signify's file formats + * (REPO_DESIGN.txt section 2). Private to libglacier_verify.so, like + * sha256.h: no public header is installed. + * + * Every file is two lines, "untrusted comment: ..." and base64 of: + * public key "Ed" | keynum[8] | pubkey[32] + * signature "Ed" | keynum[8] | sig[64] + * secret key "Ed" | "BK" | rounds[4, big-endian] | salt[16] | + * checksum[8] | keynum[8] | seckey[64] + * Only unencrypted secret keys (rounds == 0, `signify -G -n`) are + * read; an encrypted one is GL_SIGN_ERR_ENCRYPTED. + * + * Signatures are pure Ed25519 over the whole message, as signify's, so + * `signify -V -p KEY.pub -m FILE` checks what gl_sign_file makes and + * gl_sign_verify_file checks what signify makes. + */ + +#include +#include + +#define GL_SIGN_KEYNUM_SIZE 8 +/* Largest message signed or verified: the whole file is read. */ +#define GL_SIGN_MAX_MSG (64u * 1024u * 1024u) +/* Longest comment signify accepts. */ +#define GL_SIGN_MAX_COMMENT 1024 + +typedef enum { + GL_SIGN_OK = 0, + GL_SIGN_ERR_IO = 1, /* a file can't be opened or read */ + GL_SIGN_ERR_FORMAT = 2, /* not a well-formed file of the expected kind */ + GL_SIGN_ERR_ENCRYPTED = 3, /* a passphrase-protected secret key */ + GL_SIGN_ERR_KEYNUM = 4, /* the signature was made by another key */ + GL_SIGN_ERR_BAD = 5, /* the signature does not verify */ + GL_SIGN_ERR_TOOBIG = 6, /* message larger than GL_SIGN_MAX_MSG */ + GL_SIGN_ERR_RANDOM = 7, /* no randomness for a new key */ + GL_SIGN_ERR_EXISTS = 8, /* a key file to create already exists */ + GL_SIGN_ERR_WRITE = 9, /* a file to write can't be created or written */ +} gl_sign_status_t; + +typedef struct { + uint8_t keynum[GL_SIGN_KEYNUM_SIZE]; + uint8_t key[32]; +} gl_sign_pubkey_t; + +typedef struct { + uint8_t keynum[GL_SIGN_KEYNUM_SIZE]; + uint8_t sig[64]; +} gl_sign_sig_t; + +int gl_sign_read_pubkey(const char *path, gl_sign_pubkey_t *out); +/* Reads a secret key only to check it (then wipes it). */ +int gl_sign_check_seckey(const char *path); +int gl_sign_read_sig(const char *path, gl_sign_sig_t *out); + +/* Checks `sig` over msg[0..len) with `pub`: GL_SIGN_ERR_KEYNUM before + * any crypto if the keynums differ, else GL_SIGN_OK or GL_SIGN_ERR_BAD. */ +int gl_sign_verify(const gl_sign_pubkey_t *pub, const gl_sign_sig_t *sig, + const uint8_t *msg, size_t len); + +/* Reads all three files and verifies. The status names the first + * problem found, in the order: public key, signature, message, check. */ +int gl_sign_verify_file(const char *pubkey_path, const char *msg_path, + const char *sig_path); + +/* Signs msg_path with the secret key, writing sig_path (replaced if it + * exists, never followed if it's a symlink). The signature's comment + * is "verify with NAME.pub", NAME being the secret key's file name + * without ".sec", as signify writes it. */ +int gl_sign_file(const char *seckey_path, const char *msg_path, + const char *sig_path); + +/* Makes a new keypair. Neither file may exist (GL_SIGN_ERR_EXISTS). + * The secret key is created 0600 and unencrypted; the comments are + * "COMMENT public key" / "COMMENT secret key". */ +int gl_sign_generate(const char *pubkey_path, const char *seckey_path, + const char *comment); + +#endif diff --git a/src/transaction.c b/src/transaction.c index 9245590..668f5e5 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -12,33 +12,76 @@ #include #include "common.h" +#include "deps.h" #include "istoreutils.h" #include "log.h" #include "transaction.h" /* The one directory a scope owns; everything below derives from it. * See the layout comment in common.h. */ +/* `sysroot` is the install root ("" for the running system, see + * gl_context_t). User scope never has one (set_sysroot refuses it). */ static void -scope_base(gl_ctx_scope_t scope, uid_t uid, char *out) +scope_base(gl_ctx_scope_t scope, uid_t uid, const char *sysroot, char *out) { switch (scope) { case GL_SCOPE_USR: gl_usr_base(uid, out, PATH_MAX); break; case GL_SCOPE_SYS: - snprintf(out, PATH_MAX, "%s", GL_SYS_ROOT); + snprintf(out, PATH_MAX, "%s%s", sysroot, GL_SYS_ROOT); break; } } static void -scope_subdir(gl_ctx_scope_t scope, uid_t uid, const char *name, char *out) +scope_subdir(gl_ctx_scope_t scope, uid_t uid, const char *sysroot, + const char *name, char *out) { char base[PATH_MAX]; - scope_base(scope, uid, base); + scope_base(scope, uid, sysroot, base); snprintf(out, PATH_MAX, "%s/%s", base, name); } +int +gl_check_sysroot(const char *sysroot) +{ + char root[PATH_MAX]; + if (gl_normalize_sysroot(sysroot, root, sizeof(root)) != 0) { + lg_printf(2, "Refusing install root %s: not an absolute path without . or .. components.", + sysroot ? sysroot : "(null)"); + return -1; + } + if (root[0] == '\0') { return 0; } + + char bad[PATH_MAX]; + const char *why = ""; + if (gl_sysroot_trusted(root, bad, sizeof(bad), &why) != 0) { + lg_printf(2, "Refusing install root %s: %s is %s.", root, bad, why); + return -1; + } + return 0; +} + +/* Store the install root (gl_normalize_sysroot). Only system scope may + * have one (IMAGE_DESIGN section 3): per-user scope has no business in + * an image. */ +static int +set_sysroot(gl_context_t *ctx, gl_ctx_scope_t scope, const char *sysroot) +{ + ctx->sysroot[0] = '\0'; + if (gl_check_sysroot(sysroot) != 0) { + return -1; + } + gl_normalize_sysroot(sysroot, ctx->sysroot, sizeof(ctx->sysroot)); + if (scope == GL_SCOPE_USR && ctx->sysroot[0] != '\0') { + lg_printf(2, "Refusing a user-scope context with install root %s.", ctx->sysroot); + ctx->sysroot[0] = '\0'; + return -1; + } + return 0; +} + static int fsync_dir(const char *path) { @@ -67,11 +110,11 @@ atomic_swap(const char *live, const char *stage, const char *backup) } static void -derive_base_paths(gl_ctx_scope_t scope, uid_t uid, char *index_out, - char *store_out, char *links_out) +derive_base_paths(gl_ctx_scope_t scope, uid_t uid, const char *sysroot, + char *index_out, char *store_out, char *links_out) { - scope_subdir(scope, uid, "index", index_out); - scope_subdir(scope, uid, "store", store_out); + scope_subdir(scope, uid, sysroot, "index", index_out); + scope_subdir(scope, uid, sysroot, "store", store_out); /* For system scope this is the DEFAULT links location — for * packages the operator explicitly installs under system scope that * aren't part of the base system. Base-repo packages get routed to @@ -79,7 +122,7 @@ derive_base_paths(gl_ctx_scope_t scope, uid_t uid, char *index_out, * gl_link_pkg/gl_relink_store (which know a package's repo), not * something the context can decide up front. See GL_BASE_REPO in * istoreutils.c. */ - scope_subdir(scope, uid, "links", links_out); + scope_subdir(scope, uid, sysroot, "links", links_out); } static int @@ -118,7 +161,9 @@ hardlink_tree(const char *src, const char *dest) if(lstat(s, &st) != 0) { continue; } if (S_ISDIR(st.st_mode)) { - if (mkdir(d, st.st_mode & 0777) != 0 && errno != EEXIST) { + /* Exact mode, not umask-filtered: the stage becomes + * live, so seeding must not tighten what it copies. */ + if (gl_mkdir_exact(d, st.st_mode & 07777) != 0) { closedir(dir); return -1; } @@ -148,6 +193,13 @@ hardlink_tree(const char *src, const char *dest) int gl_init_live_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid) +{ + return gl_init_live_context_at(ctx, scope, uid, "/"); +} + +int +gl_init_live_context_at(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid, + const char *sysroot) { if (!ctx) { return -1; @@ -158,16 +210,28 @@ gl_init_live_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid) ctx->scope = scope; ctx->lock_fd = -1; ctx->overwrite = 0; + ctx->link_refusals = 0; + ctx->strict = 0; + if (set_sysroot(ctx, scope, sysroot) != 0) { + return -1; + } - strncpy(ctx->root_path, "/glacier", PATH_MAX - 1); - derive_base_paths(scope, uid, ctx->index_path, ctx->store_path, - ctx->links_path); + snprintf(ctx->root_path, PATH_MAX, "%s/glacier", ctx->sysroot); + derive_base_paths(scope, uid, ctx->sysroot, ctx->index_path, + ctx->store_path, ctx->links_path); return 0; } gl_txn_status_t gl_init_stage_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid) +{ + return gl_init_stage_context_at(ctx, scope, uid, "/"); +} + +gl_txn_status_t +gl_init_stage_context_at(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid, + const char *sysroot) { if (!ctx) { return GL_TXN_ERR_NO_TXN; @@ -178,8 +242,13 @@ gl_init_stage_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid) ctx->scope = scope; ctx->lock_fd = -1; ctx->overwrite = 0; + ctx->link_refusals = 0; + ctx->strict = 0; + if (set_sysroot(ctx, scope, sysroot) != 0) { + return GL_TXN_ERR_PERM; + } - strncpy(ctx->root_path, "/glacier", PATH_MAX - 1); + snprintf(ctx->root_path, PATH_MAX, "%s/glacier", ctx->sysroot); if (scope == GL_SCOPE_USR) { switch (gl_usr_base_check(uid)) { @@ -200,7 +269,7 @@ gl_init_stage_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid) } char stage_base[PATH_MAX - 7]; - scope_subdir(scope, uid, "stage", stage_base); + scope_subdir(scope, uid, ctx->sysroot, "stage", stage_base); snprintf(ctx->index_path, PATH_MAX, "%s/index", stage_base); snprintf(ctx->store_path, PATH_MAX, "%s/store", stage_base); @@ -226,17 +295,23 @@ gl_init_stage_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid) } } - if (gl_mkdirp(ctx->index_path, 0700) != 0) { + /* chmod, not just mkdir's mode: the stage becomes live on commit, + * and a system store must be 0755 whatever syspkg's umask is. */ + mode_t dir_mode = gl_scope_dir_mode(scope); + if (gl_mkdirp(ctx->index_path, dir_mode) != 0 || + chmod(ctx->index_path, dir_mode) != 0) { gl_abort_transaction(ctx); return GL_TXN_ERR_MKDIR; } - if (gl_mkdirp(ctx->store_path, 0700) != 0) { + if (gl_mkdirp(ctx->store_path, dir_mode) != 0 || + chmod(ctx->store_path, dir_mode) != 0) { gl_abort_transaction(ctx); return GL_TXN_ERR_MKDIR; } - if (gl_mkdirp(ctx->links_path, 0700) != 0) { + if (gl_mkdirp(ctx->links_path, dir_mode) != 0 || + chmod(ctx->links_path, dir_mode) != 0) { gl_abort_transaction(ctx); return GL_TXN_ERR_MKDIR; } @@ -247,7 +322,7 @@ gl_init_stage_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid) } char live_index[PATH_MAX], live_store[PATH_MAX], live_links[PATH_MAX]; - derive_base_paths(scope, uid, live_index, live_store, live_links); + derive_base_paths(scope, uid, ctx->sysroot, live_index, live_store, live_links); /* live_links is populated (derive_base_paths always fills all * three) but intentionally unused below — see the comment on the * hardlink_tree call. */ @@ -275,6 +350,85 @@ gl_init_stage_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid) return GL_TXN_OK; } +/* parent/name into out (PATH_MAX), created with `mode` if missing, then + * refused unless it is a real directory owned by `owner` with exactly + * that mode. 0, or -1 having printed why. */ +static int +checked_subdir(const char *parent, const char *name, uid_t owner, mode_t mode, + char *out) +{ + struct stat st; + snprintf(out, PATH_MAX, "%s/%s", parent, name); + if (gl_mkdir_exact(out, mode) != 0 && errno != EEXIST) { + lg_printf(2, "Cannot create %s: %s", out, strerror(errno)); + return -1; + } + if (lstat(out, &st) != 0 || !S_ISDIR(st.st_mode) || st.st_uid != owner || + (st.st_mode & 07777) != mode) { + lg_printf(2, "Refusing %s: not a directory owned by UID %u with mode %o.", + out, owner, (unsigned)mode); + return -1; + } + return 0; +} + +/* + * /NAME: a directory for state a scope keeps beside its index and + * store ("repos": the accepted repository indexes, REPO_DESIGN.txt + * section 5). The base must already exist and be the scope's (owned by + * root, or by the uid for user scope; no group/world write). NAME is + * created if missing with the scope's mode, and is then refused unless + * it is a real directory owned like the base with exactly that mode. + * The path goes to out (PATH_MAX). 0, or -1 having printed why. + */ +int +gl_scope_state_dir(const gl_context_t *ctx, const char *name, char *out) +{ + if (!ctx || !out || !gl_valid_pkg_ident(name)) { + return -1; + } + uid_t owner = ctx->scope == GL_SCOPE_SYS ? 0 : ctx->uid; + mode_t mode = gl_scope_dir_mode(ctx->scope); + + char base[PATH_MAX]; + scope_base(ctx->scope, ctx->uid, ctx->sysroot, base); + struct stat st; + if (ctx->scope == GL_SCOPE_USR && gl_usr_base_check(ctx->uid) != GL_USR_BASE_OK) { + lg_printf(2, "Cannot open %s: not a private directory owned by UID %u.", + base, ctx->uid); + return -1; + } + if (lstat(base, &st) != 0) { + lg_printf(2, "Cannot open %s: %s", base, strerror(errno)); + return -1; + } + if (!S_ISDIR(st.st_mode) || st.st_uid != owner || (st.st_mode & 022)) { + lg_printf(2, "Refusing %s: not a directory owned by UID %u, or group/world-writable.", + base, owner); + return -1; + } + + return checked_subdir(base, name, owner, mode, out); +} + +/* + * /cache/REPO: where packages fetched from REPO are downloaded + * (REPO_DESIGN.txt section 5). Both levels are made and checked as + * gl_scope_state_dir makes and checks NAME. 0, or -1 having printed why. + */ +int +gl_scope_cache_dir(const gl_context_t *ctx, const char *repo, char *out) +{ + char cache[PATH_MAX]; + if (!ctx || !out || !gl_valid_pkg_ident(repo) || + gl_scope_state_dir(ctx, "cache", cache) != 0) { + return -1; + } + return checked_subdir(cache, repo, + ctx->scope == GL_SCOPE_SYS ? 0 : ctx->uid, + gl_scope_dir_mode(ctx->scope), out); +} + gl_txn_status_t gl_begin_transaction(gl_context_t *ctx) { @@ -283,7 +437,7 @@ gl_begin_transaction(gl_context_t *ctx) } char lock_path[PATH_MAX]; - scope_subdir(ctx->scope, ctx->uid, "lock", lock_path); + scope_subdir(ctx->scope, ctx->uid, ctx->sysroot, "lock", lock_path); int fd = open(lock_path, O_WRONLY | O_CREAT, 0600); if (fd < 0) { @@ -309,8 +463,19 @@ gl_commit_transaction(gl_context_t *ctx) return GL_TXN_ERR_NO_TXN; } + /* Dependencies and conflicts (ROADMAP 2.3). The CLIs check the plan + * before prompting; this holds every other caller of the library to + * the same rules. gl_check_txn reports each problem itself. */ + int dep_problems = gl_check_txn(ctx); + if (dep_problems != 0) { + if (dep_problems < 0) { + lg_printf(2, "Cannot read the package store to check dependencies."); + } + return GL_TXN_ERR_DEPS; + } + char stage_base[PATH_MAX]; - scope_subdir(ctx->scope, ctx->uid, "stage", stage_base); + scope_subdir(ctx->scope, ctx->uid, ctx->sysroot, "stage", stage_base); int same = check_same_filesystem(ctx->root_path, stage_base); if (same < 0) { @@ -322,12 +487,12 @@ gl_commit_transaction(gl_context_t *ctx) /* Derive live paths the same way gl_init_live_context would */ char live_index[PATH_MAX], live_store[PATH_MAX], live_links[PATH_MAX]; - derive_base_paths(ctx->scope, ctx->uid, live_index, live_store, - live_links); + derive_base_paths(ctx->scope, ctx->uid, ctx->sysroot, live_index, + live_store, live_links); /* Derive old/backup paths */ char old_base[PATH_MAX]; - scope_subdir(ctx->scope, ctx->uid, "old", old_base); + scope_subdir(ctx->scope, ctx->uid, ctx->sysroot, "old", old_base); char old_index[PATH_MAX], old_store[PATH_MAX], old_links[PATH_MAX]; snprintf(old_index, PATH_MAX, "%s/index", old_base); @@ -363,18 +528,22 @@ gl_commit_transaction(gl_context_t *ctx) /* fsync the directory the renames happened in */ char base[PATH_MAX]; - scope_base(ctx->scope, ctx->uid, base); + scope_base(ctx->scope, ctx->uid, ctx->sysroot, base); fsync_dir(base); if (ctx->scope == GL_SCOPE_SYS) { - fsync_dir("/usr"); /* base-repo packages land here instead */ + char usr[PATH_MAX]; + snprintf(usr, sizeof(usr), "%s/usr", ctx->sysroot); + fsync_dir(usr); /* base-repo packages land here instead */ } gl_context_t live_ctx; - gl_init_live_context(&live_ctx, ctx->scope, ctx->uid); + gl_init_live_context_at(&live_ctx, ctx->scope, ctx->uid, + ctx->sysroot[0] ? ctx->sysroot : "/"); if (gl_relink_store(&live_ctx) != 0) { lg_printf(2, "Post-commit relink incomplete for uid %u", ctx->uid); } + ctx->link_refusals = live_ctx.link_refusals; gl_rmdir_recursive(old_base); @@ -390,13 +559,15 @@ gl_commit_transaction(gl_context_t *ctx) gl_txn_status_t gl_abort_transaction(gl_context_t *ctx) { - if (!ctx) { + /* Only the lock holder's stage is its own to remove: without the + * lock, this would wipe another process's running transaction. */ + if (!ctx || ctx->lock_fd < 0) { return GL_TXN_ERR_NO_TXN; } /* Remove stage area — live is untouched */ char stage_uid[PATH_MAX]; - scope_subdir(ctx->scope, ctx->uid, "stage", stage_uid); + scope_subdir(ctx->scope, ctx->uid, ctx->sysroot, "stage", stage_uid); gl_rmdir_recursive(stage_uid); /* best-effort; ignore errors */ diff --git a/src/transaction.h b/src/transaction.h index fd61fca..63eb3f9 100644 --- a/src/transaction.h +++ b/src/transaction.h @@ -5,6 +5,8 @@ #include #include +#include +#include #include #define GL_UID_NONE ((uid_t)0) @@ -19,6 +21,22 @@ typedef enum { GL_SCOPE_SYS } gl_ctx_scope_t; +/* Modes for what glacier creates in a scope. System scope is + * world-readable, like /usr: its store is where /usr's links point, so + * every user must be able to traverse it, and user-scope dependency + * checks read its index. User scope stays private to its uid. */ +static inline mode_t +gl_scope_dir_mode(gl_ctx_scope_t scope) +{ + return scope == GL_SCOPE_SYS ? 0755 : 0700; +} + +static inline mode_t +gl_scope_file_mode(gl_ctx_scope_t scope) +{ + return scope == GL_SCOPE_SYS ? 0644 : 0600; +} + typedef struct { uid_t uid; @@ -37,8 +55,53 @@ typedef struct { * existing non-glacier files at their link destinations; see * gl_link_pkg. */ int overwrite; + + /* Install root (IMAGE_DESIGN section 3), set by the *_at init + * functions: "" for the running system, else e.g. "/mnt/everest". + * The paths above are INSTALL paths, already under it: every + * filesystem operation uses them as they are. What a path MEANS + * once the image is booted (a symlink target, a .displaced key) + * is its runtime path, gl_runtime_path(). System scope only. */ + char sysroot[PATH_MAX]; + + /* Paths the link pass refused to link (a foreign file in the + * way, say), counted only for packages installed in this + * transaction. Zeroed by the init functions; after + * gl_commit_transaction, the post-commit relink's count. A + * non-zero count means the transaction committed but did not + * fully succeed. */ + int link_refusals; + + /* Set by the caller after gl_init_*_context (which zero it). + * Non-zero: a path the link pass would refuse (a foreign file in + * the way, say) fails the transaction before anything is + * committed, instead of being counted in link_refusals after. + * See deps.h. */ + int strict; } gl_context_t; +/* An install path's runtime meaning: sysroot stripped. A path not under + * the sysroot is returned unchanged. */ +static inline const char * +gl_runtime_path(const gl_context_t *ctx, const char *install_path) +{ + size_t n = strlen(ctx->sysroot); + if (n > 0 && strncmp(install_path, ctx->sysroot, n) == 0 && + install_path[n] == '/') { + return install_path + n; + } + return install_path; +} + +/* The install path of a runtime path (a symlink's absolute target, say): + * sysroot prepended. Returns -1 if it doesn't fit. */ +static inline int +gl_at(const gl_context_t *ctx, const char *runtime_path, char *out, size_t outsz) +{ + int n = snprintf(out, outsz, "%s%s", ctx->sysroot, runtime_path); + return (n < 0 || (size_t)n >= outsz) ? -1 : 0; +} + typedef enum { GL_TXN_OK = 0, /* success */ GL_TXN_ERR_LOCK = 1, /* unable to acquire lock */ @@ -48,13 +111,25 @@ typedef enum { GL_TXN_ERR_SYNC = 5, /* fsync failed */ GL_TXN_ERR_CLEANUP = 6, /* cleanup failed */ GL_TXN_ERR_NO_TXN = 7, /* aborted without beginning */ - GL_TXN_ERR_XDEV = 8 /* stage and live on different filesystems */ + GL_TXN_ERR_XDEV = 8, /* stage and live on different filesystems */ + GL_TXN_ERR_DEPS = 9 /* unmet dependency, conflict, file collision, + * or (strict) a link refusal: see deps.h */ } gl_txn_status_t; +/* Is `sysroot` acceptable as an install root (absolute, no . or .., + * trusted: see gl_sysroot_trusted)? Logs why not. 0 if so. "/" is. */ +int gl_check_sysroot(const char *sysroot); + int gl_init_live_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid); +int gl_init_live_context_at(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid, + const char *sysroot); gl_txn_status_t gl_init_stage_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid); +gl_txn_status_t gl_init_stage_context_at(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid, + const char *sysroot); gl_txn_status_t gl_begin_transaction(gl_context_t *ctx); +int gl_scope_state_dir(const gl_context_t *ctx, const char *name, char *out); +int gl_scope_cache_dir(const gl_context_t *ctx, const char *repo, char *out); gl_txn_status_t gl_commit_transaction(gl_context_t *ctx); gl_txn_status_t gl_abort_transaction(gl_context_t *ctx); diff --git a/src/x_log b/src/x_log deleted file mode 100755 index 255048266a1f6fb7014f6cb04a13662b8e6e5186..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16616 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