commit 2c7434a4beb4f2c026bd1713c26008e4725174cd
parent 29589741aac6f8b540851739e81997479a19ad73
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 17 Jun 2026 13:22:49 -0700
selfdist: kit pkg sign (detached minisig, stock-minisign verifiable) + trusted_keys single-root reconcile (KIT_HOME/config + legacy fallback)
Diffstat:
3 files changed, 134 insertions(+), 8 deletions(-)
diff --git a/driver/cmd/pkg.c b/driver/cmd/pkg.c
@@ -32,6 +32,7 @@ void driver_help_pkg(void) {
"kpkg|tar.gz]\n"
" [--external DIR] FILE -C DIR\n"
" kit pkg inspect [--manifest | --encoding] FILE\n"
+ " kit pkg sign -s SECKEY [-o OUT] [--comment C] FILE\n"
" kit pkg trust {path | list | add PUBKEY [label] | remove KEYID}\n");
}
@@ -67,15 +68,26 @@ static void pkg_parent_dir(const char* path, char* buf, size_t cap) {
buf[n] = '\0';
}
+/* Preferred trusted-keys path under the single-root layout: $KIT_TRUSTED_KEYS,
+ * else $KIT_HOME/config/trusted_keys (with $KIT_HOME defaulting per
+ * driver_kit_home). Used for both reads and the tofu/trust writes. */
static int pkg_trust_path(char* buf, size_t cap) {
const char* env = driver_getenv("KIT_TRUSTED_KEYS");
- const char* home;
- if (env) {
+ char home[PKG_PATH_BUF];
+ if (env && *env) {
snprintf(buf, cap, "%s", env);
return 0;
}
- home = driver_getenv("HOME");
- if (!home) return 1;
+ if (driver_kit_home(home, sizeof home) != 0) return 1;
+ snprintf(buf, cap, "%s/config/trusted_keys", home);
+ return 0;
+}
+
+/* Legacy pre-single-root location, still honored for reads so existing setups
+ * keep working: $HOME/.config/kit/trusted_keys. Returns 0 on success. */
+static int pkg_trust_legacy_path(char* buf, size_t cap) {
+ const char* home = driver_getenv("HOME");
+ if (!home || !*home) return 1;
snprintf(buf, cap, "%s/.config/kit/trusted_keys", home);
return 0;
}
@@ -392,10 +404,17 @@ static int pkg_verify_or_unpack(DriverEnv* env, const KitContext* ctx, int argc,
}
have_tpath = (pkg_trust_path(tpath, sizeof tpath) == 0);
if (!have_tpath) tpath[0] = '\0';
- if (have_tpath && !pubkey && pkg_read(ctx, tpath, &trustfd)) {
- trust_loaded = 1;
- opts.trusted_keys = trustfd.data;
- opts.trusted_keys_len = trustfd.size;
+ if (!pubkey) {
+ char legacy[PKG_PATH_BUF];
+ if (have_tpath && pkg_read(ctx, tpath, &trustfd))
+ trust_loaded = 1;
+ else if (pkg_trust_legacy_path(legacy, sizeof legacy) == 0 &&
+ pkg_read(ctx, legacy, &trustfd))
+ trust_loaded = 1; /* fall back to the pre-single-root location */
+ if (trust_loaded) {
+ opts.trusted_keys = trustfd.data;
+ opts.trusted_keys_len = trustfd.size;
+ }
}
host = driver_cas_host(env);
@@ -597,6 +616,74 @@ static int pkg_trust(DriverEnv* env, const KitContext* ctx, int argc,
return 2;
}
+/* ---------------------------------------------------------------------- */
+/* sign: detached minisign signature over a file */
+/* ---------------------------------------------------------------------- */
+
+static int pkg_sign(DriverEnv* env, const KitContext* ctx, int argc,
+ char** argv) {
+ const char *file = NULL, *seckey = NULL, *out_path = NULL, *comment = NULL;
+ KitFileData msgfd, skfd;
+ KitWriter* w = NULL;
+ char out_buf[PKG_PATH_BUF];
+ int msg_loaded = 0, sk_loaded = 0, i, rc = 1;
+ KitStatus st;
+ (void)env;
+ for (i = 0; i < argc; ++i) {
+ if (driver_streq(argv[i], "-s") && i + 1 < argc)
+ seckey = argv[++i];
+ else if (driver_streq(argv[i], "-o") && i + 1 < argc)
+ out_path = argv[++i];
+ else if (driver_streq(argv[i], "--comment") && i + 1 < argc)
+ comment = argv[++i];
+ else if (argv[i][0] != '-')
+ file = argv[i];
+ else {
+ driver_errf(PKG_TOOL, "sign: unknown option: %s", argv[i]);
+ return 2;
+ }
+ }
+ if (!file || !seckey) {
+ driver_errf(PKG_TOOL, "sign: FILE and -s SECKEY are required");
+ return 2;
+ }
+ if (!out_path) {
+ snprintf(out_buf, sizeof out_buf, "%s.minisig", file);
+ out_path = out_buf;
+ }
+ if (!pkg_read(ctx, file, &msgfd)) {
+ driver_errf(PKG_TOOL, "sign: cannot read file: %s", file);
+ return 1;
+ }
+ msg_loaded = 1;
+ if (!pkg_read(ctx, seckey, &skfd)) {
+ driver_errf(PKG_TOOL, "sign: cannot read secret key: %s", seckey);
+ goto done;
+ }
+ sk_loaded = 1;
+ if (ctx->file_io->open_writer(ctx->file_io->user, out_path, &w) != KIT_OK) {
+ driver_errf(PKG_TOOL, "sign: cannot open output: %s", out_path);
+ goto done;
+ }
+ st = kit_pkg_sign_detached(ctx, msgfd.data, msgfd.size, skfd.data, skfd.size,
+ comment, w);
+ if (st != KIT_OK) driver_writer_abort(w);
+ kit_writer_close(w);
+ if (st == KIT_UNSUPPORTED)
+ driver_errf(PKG_TOOL, "sign: encrypted secret keys need scrypt");
+ else if (st != KIT_OK)
+ driver_errf(PKG_TOOL, "sign: failed to sign %s", file);
+ else {
+ driver_printf("wrote %s\n", out_path);
+ rc = 0;
+ }
+
+done:
+ if (sk_loaded) pkg_release(ctx, &skfd);
+ if (msg_loaded) pkg_release(ctx, &msgfd);
+ return rc;
+}
+
int driver_pkg(int argc, char** argv) {
DriverEnv env;
KitContext ctx;
@@ -619,6 +706,8 @@ int driver_pkg(int argc, char** argv) {
rc = pkg_verify_or_unpack(&env, &ctx, argc - 2, argv + 2, 1);
else if (driver_streq(sub, "inspect"))
rc = pkg_inspect(&env, &ctx, argc - 2, argv + 2);
+ else if (driver_streq(sub, "sign"))
+ rc = pkg_sign(&env, &ctx, argc - 2, argv + 2);
else if (driver_streq(sub, "trust"))
rc = pkg_trust(&env, &ctx, argc - 2, argv + 2);
else {
diff --git a/include/kit/package.h b/include/kit/package.h
@@ -63,6 +63,18 @@ KIT_API KitStatus kit_minisig_parse_seckey(
const uint8_t* data, size_t len, uint8_t sk_out[KIT_PKG_SK_LEN],
uint8_t keyid_out[KIT_PKG_KEYID_LEN]);
+/* Write a detached minisign signature over msg[0..msglen) to *out*, signing
+ * with the passwordless minisign secret-key file content seckey_bytes. The
+ * signature is interchangeable with stock minisign (`minisign -Vm FILE`), so it
+ * backs the bootstrap-verification of a release .tar.gz before any kit exists.
+ * `comment` (NULL for a default) is the signed trusted comment. Returns
+ * KIT_UNSUPPORTED for scrypt-encrypted keys, KIT_MALFORMED for a bad key. */
+KIT_API KitStatus kit_pkg_sign_detached(const KitContext* ctx,
+ const uint8_t* msg, size_t msglen,
+ const uint8_t* seckey_bytes,
+ size_t seckey_len, const char* comment,
+ KitWriter* out);
+
/* Trusted-keys store helpers (the store is plain text managed by the caller).
* lookup finds keyid's public key; format_entry renders one NUL-terminated,
* newline-included store line. */
diff --git a/src/api/package.c b/src/api/package.c
@@ -1721,6 +1721,31 @@ KitStatus kit_calver_compare(const char* a, const char* b, int* cmp) {
return dist_calver_compare(a, b, cmp) == DIST_OK ? KIT_OK : KIT_INVALID;
}
+KitStatus kit_pkg_sign_detached(const KitContext* ctx, const uint8_t* msg,
+ size_t msglen, const uint8_t* seckey_bytes,
+ size_t seckey_len, const char* comment,
+ KitWriter* out) {
+ uint8_t sk[DIST_ED25519_SK_LEN], keyid[DIST_KEYID_LEN];
+ int rc;
+ const char* tc = (comment && *comment) ? comment : "kit detached signature";
+ if (!ctx || (!msg && msglen) || !seckey_bytes || !out) return KIT_INVALID;
+ rc = dist_minisig_parse_seckey(seckey_bytes, seckey_len, sk, keyid);
+ if (rc == DIST_ENCRYPTED) {
+ kit_ctx_diagf(ctx, "encrypted secret keys need scrypt");
+ return KIT_UNSUPPORTED;
+ }
+ if (rc != DIST_OK) {
+ kit_ctx_diagf(ctx, "malformed secret key");
+ return KIT_MALFORMED;
+ }
+ if (dist_minisig_sign(out, msg, msglen, sk, keyid, "kit signature", tc) !=
+ DIST_OK) {
+ kit_ctx_diagf(ctx, "could not produce signature");
+ return KIT_ERR;
+ }
+ return kit_writer_status(out) == KIT_OK ? KIT_OK : KIT_IO;
+}
+
KitStatus kit_pkg_keygen(const KitContext* ctx, KitPkgRandomFn rng,
void* rng_user, KitWriter* pub_out, KitWriter* sec_out,
uint8_t out_keyid[KIT_PKG_KEYID_LEN]) {