commit 3227411c46ee24313aa87c0dfd50467188faba13
parent 771e83607222a3648f4d522a1c35ad88d4817595
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 16 Jul 2026 11:17:42 -0700
dist: reject reserialized nonrelease signer
Diffstat:
6 files changed, 71 insertions(+), 33 deletions(-)
diff --git a/mk/dist.mk b/mk/dist.mk
@@ -18,9 +18,9 @@ ifeq ($(strip $(KIT_SIGN_KEY)),)
$(error dist requires KIT_SIGN_KEY (production Minisign secret key file))
endif
ifneq ($(KIT_RELEASE_ALLOW_TEST_KEY),1)
-DIST_TEST_KEY_MATERIAL := $(strip $(shell sh scripts/minisign_key_material.sh test/dist/keys/nonrelease.key 2>/dev/null))
-DIST_SIGN_KEY_MATERIAL := $(strip $(shell sh scripts/minisign_key_material.sh "$(KIT_SIGN_KEY)" 2>/dev/null))
-ifeq ($(DIST_SIGN_KEY_MATERIAL),$(DIST_TEST_KEY_MATERIAL))
+DIST_TEST_KEY_ID := $(strip $(shell sh scripts/minisign_key_id.sh test/dist/keys/nonrelease.key 2>/dev/null))
+DIST_SIGN_KEY_ID := $(strip $(shell sh scripts/minisign_key_id.sh "$(KIT_SIGN_KEY)" 2>/dev/null))
+ifeq ($(DIST_SIGN_KEY_ID),$(DIST_TEST_KEY_ID))
$(error dist refuses the repository NON-RELEASE test signing key)
endif
endif
diff --git a/mk/flags.mk b/mk/flags.mk
@@ -164,8 +164,8 @@ ifeq ($(strip $(KIT_RELEASE_PUBKEYS)),)
$(error RELEASE=1 requires KIT_RELEASE_PUBKEYS (production Minisign public key file(s)))
endif
ifneq ($(KIT_RELEASE_ALLOW_TEST_KEY),1)
-KIT_RELEASE_TEST_KEY_MATERIAL := $(strip $(shell sh scripts/minisign_key_material.sh test/dist/keys/nonrelease.pub 2>/dev/null))
-KIT_RELEASE_TEST_ANCHOR := $(strip $(foreach key,$(KIT_RELEASE_PUBKEYS),$(if $(filter $(KIT_RELEASE_TEST_KEY_MATERIAL),$(strip $(shell sh scripts/minisign_key_material.sh "$(key)" 2>/dev/null))),$(key))))
+KIT_RELEASE_TEST_KEY_ID := $(strip $(shell sh scripts/minisign_key_id.sh test/dist/keys/nonrelease.pub 2>/dev/null))
+KIT_RELEASE_TEST_ANCHOR := $(strip $(foreach key,$(KIT_RELEASE_PUBKEYS),$(if $(filter $(KIT_RELEASE_TEST_KEY_ID),$(strip $(shell sh scripts/minisign_key_id.sh "$(key)" 2>/dev/null))),$(key))))
ifneq ($(KIT_RELEASE_TEST_ANCHOR),)
$(error RELEASE=1 refuses repository NON-RELEASE test anchor: $(KIT_RELEASE_TEST_ANCHOR))
endif
diff --git a/scripts/minisign_key_id.sh b/scripts/minisign_key_id.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+# Decode the stable 8-byte signer identity from a Minisign public or secret
+# key. Pure POSIX awk keeps release gating independent of host base64 variants.
+
+set -eu
+
+[ "$#" -eq 1 ] || {
+ printf 'usage: minisign_key_id.sh KEY\n' >&2
+ exit 2
+}
+
+LC_ALL=C awk '
+ function b64(c) {
+ if (c == "=") return -1
+ return index("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", c) - 1
+ }
+ /^untrusted comment:/ { next }
+ /^[[:space:]]*$/ { next }
+ {
+ s = $0
+ gsub(/[[:space:]]/, "", s)
+ n = 0
+ for (i = 1; i <= length(s); i += 4) {
+ a = b64(substr(s, i, 1))
+ b = b64(substr(s, i + 1, 1))
+ c = b64(substr(s, i + 2, 1))
+ d = b64(substr(s, i + 3, 1))
+ if (a < 0 || b < 0) exit 1
+ byte[++n] = a * 4 + int(b / 16)
+ if (c >= 0) byte[++n] = (b % 16) * 16 + int(c / 4)
+ if (d >= 0) byte[++n] = (c % 4) * 64 + d
+ }
+ if (byte[1] != 69 || byte[2] != 100) exit 1
+ if (n == 42)
+ off = 3
+ else if (n == 158)
+ off = 55
+ else
+ exit 1
+ found = 1
+ for (i = off; i < off + 8; ++i) printf "%02x", byte[i]
+ printf "\n"
+ exit
+ }
+ END { if (!found) exit 1 }
+' "$1"
diff --git a/scripts/minisign_key_material.sh b/scripts/minisign_key_material.sh
@@ -1,22 +0,0 @@
-#!/bin/sh
-# Print the first Minisign key-material record, ignoring the mutable untrusted
-# comment. Works for both public and passwordless secret-key files.
-
-set -eu
-
-[ "$#" -eq 1 ] || {
- printf 'usage: minisign_key_material.sh KEY\n' >&2
- exit 2
-}
-
-LC_ALL=C awk '
- /^untrusted comment:/ { next }
- /^[[:space:]]*$/ { next }
- {
- gsub(/[[:space:]]/, "")
- print
- found = 1
- exit
- }
- END { if (!found) exit 1 }
-' "$1"
diff --git a/scripts/release.sh b/scripts/release.sh
@@ -87,15 +87,15 @@ done
"KIT_UPDATE_INDEX_URL is unset — provide the production stable channel URL"
TEST_SIGN_KEY="$ROOT/test/dist/keys/nonrelease.key"
TEST_PUBKEY="$ROOT/test/dist/keys/nonrelease.pub"
-TEST_SIGN_MATERIAL="$(sh "$ROOT/scripts/minisign_key_material.sh" "$TEST_SIGN_KEY")"
-TEST_PUB_MATERIAL="$(sh "$ROOT/scripts/minisign_key_material.sh" "$TEST_PUBKEY")"
+TEST_SIGN_ID="$(sh "$ROOT/scripts/minisign_key_id.sh" "$TEST_SIGN_KEY")"
+TEST_PUB_ID="$(sh "$ROOT/scripts/minisign_key_id.sh" "$TEST_PUBKEY")"
if [ "${KIT_RELEASE_ALLOW_TEST_KEY:-0}" != 1 ]; then
- [ "$(sh "$ROOT/scripts/minisign_key_material.sh" "$KIT_SIGN_KEY")" != \
- "$TEST_SIGN_MATERIAL" ] ||
+ [ "$(sh "$ROOT/scripts/minisign_key_id.sh" "$KIT_SIGN_KEY")" != \
+ "$TEST_SIGN_ID" ] ||
die "KIT_SIGN_KEY contains the repository NON-RELEASE test key; set KIT_SIGN_KEY to the real release key"
for release_pubkey in $KIT_RELEASE_PUBKEYS; do
- [ "$(sh "$ROOT/scripts/minisign_key_material.sh" "$release_pubkey")" != \
- "$TEST_PUB_MATERIAL" ] ||
+ [ "$(sh "$ROOT/scripts/minisign_key_id.sh" "$release_pubkey")" != \
+ "$TEST_PUB_ID" ] ||
die "KIT_RELEASE_PUBKEYS contains the repository NON-RELEASE test anchor: $release_pubkey"
done
fi
diff --git a/test/dist/run.sh b/test/dist/run.sh
@@ -76,6 +76,20 @@ run_fail "release-dist-copied-test-key-rejected" make -n -C "$repo_root" dist \
KIT_SIGN_KEY="$work/copied-nonrelease.key" \
KIT_UPDATE_INDEX_URL=https://updates.example/stable.index \
KIT_RELEASE_PUBKEYS="$work/release-config.pub"
+# Passwordless Minisign ignores its salt field. Mutating that field produces a
+# different base64 record for the same valid signer identity; the release gate
+# must still recognize it rather than relying on raw record equality.
+{
+ printf '%s\n' 'untrusted comment: reserialized release-looking secret key'
+ sed -n '2p' "$SECKEY" | awk '{ print substr($0, 1, 12) "B" substr($0, 14) }'
+} > "$work/reserialized-nonrelease.key"
+printf 'identity regression payload\n' > "$work/identity-payload"
+run_ok "release-reserialized-test-key-still-signs" "$KIT" pkg sign \
+ -s "$work/reserialized-nonrelease.key" "$work/identity-payload"
+run_fail "release-dist-reserialized-test-key-rejected" make -n \
+ -C "$repo_root" dist KIT_SIGN_KEY="$work/reserialized-nonrelease.key" \
+ KIT_UPDATE_INDEX_URL=https://updates.example/stable.index \
+ KIT_RELEASE_PUBKEYS="$work/release-config.pub"
run_fail "release-script-copied-test-key-rejected" env \
KIT_SIGN_KEY="$work/copied-nonrelease.key" \
KIT_UPDATE_INDEX_URL=https://updates.example/stable.index \