447 lines
23 KiB
Plaintext
447 lines
23 KiB
Plaintext
+--------------------------------------------+
|
|
| 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:<hex>"), 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):
|
|
|
|
<base>/repos/REPO.idx, REPO.idx.sig the last ACCEPTED index
|
|
<base>/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 <base>/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).
|
|
- <base>/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 <base>/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 <base>/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
|
|
<base>/cache and <base>/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.
|