d82bf58e78
- New bin/dm-sign.sh: produce signed DMs (ssh-keygen -Y sign, namespace dm) in the [from:X] [id:Y] wire format that dm.py verify-sig checks. dm.py referenced it in usage but it never existed. - dm.py: rewrite stale docstring/argparse (claimed verification was removed / delivery unconfirmed — it does recipient-side read-back with 3 retries); unify all attribution on [from:X]/[id:Y] (was three formats: [from X], [X], [from:X]); fix dm_thread double-attribution; --no-verify kept as a documented no-op; raw mode sends verbatim and reuses the embedded [id:Y] for the audit log; navigation/send/park now all target the recipient browser (fixes cross-operator side-chat sends); drop dead --from-sender flag. - Register dm-signers/operator-main.pub. - Round-trip verified: sign (operator-main) -> send --raw via opm loopback -> read-back SENT+VERIFIED -> verify-sig GOOD.
75 lines
2.4 KiB
Bash
Executable File
75 lines
2.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# dm-sign.sh — produce a signed DM for the fleet DM system.
|
|
#
|
|
# Signs a message with an SSH key (namespace "dm", Ed25519) and prints the
|
|
# signed wire format that `dm.py verify-sig` checks:
|
|
#
|
|
# [from:<identity>] [id:<id>]
|
|
#
|
|
# <message body>
|
|
#
|
|
# -----BEGIN SSH SIGNATURE-----
|
|
# ...
|
|
# -----END SSH SIGNATURE-----
|
|
#
|
|
# The signed payload is exactly "[from:X] [id:Y]\n\n<body>" (no trailing
|
|
# newline) — verify-sig reconstructs it by stripping everything from the
|
|
# signature block onward, so the two must match byte-for-byte.
|
|
#
|
|
# Usage:
|
|
# dm-sign.sh --from <identity> [--key <privkey>] [--id <id>] <message>
|
|
#
|
|
# Defaults: --key ~/.ssh/id_frontdoor, --id = 8 random hex chars.
|
|
# The private key is only ever read locally; it is never moved or copied.
|
|
# Pipe the output straight into `dm.py send --raw` (never truncate it).
|
|
#
|
|
# Example:
|
|
# dm.py send --agent opm --target main --raw \
|
|
# "$(dm-sign.sh --from operator-main 'hello from the operator')"
|
|
set -euo pipefail
|
|
|
|
FROM=""
|
|
KEY="$HOME/.ssh/id_frontdoor"
|
|
ID="$(head -c4 /dev/urandom | od -An -tx1 | tr -d ' \n')"
|
|
|
|
usage() {
|
|
sed -n '2,/^set -euo/p' "$0" | sed 's/^# \?//'
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--from) FROM="${2:?--from needs a value}"; shift 2 ;;
|
|
--key) KEY="${2:?--key needs a value}"; shift 2 ;;
|
|
--id) ID="${2:?--id needs a value}"; shift 2 ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
--) shift; break ;;
|
|
-*) echo "error: unknown option: $1" >&2; exit 1 ;;
|
|
*) break ;;
|
|
esac
|
|
done
|
|
|
|
if [[ $# -eq 0 ]]; then
|
|
echo "error: no message given" >&2
|
|
echo "usage: dm-sign.sh --from <identity> [--key <privkey>] [--id <id>] <message>" >&2
|
|
exit 1
|
|
fi
|
|
MESSAGE="$*"
|
|
|
|
[[ -n "$FROM" ]] || { echo "error: --from <identity> is required" >&2; exit 1; }
|
|
[[ -f "$KEY" ]] || { echo "error: private key not found: $KEY" >&2; exit 1; }
|
|
|
|
TD="$(mktemp -d)"
|
|
trap 'rm -rf "$TD"' EXIT
|
|
PAYLOAD="$TD/payload"
|
|
|
|
# Payload: header, blank line, body — NO trailing newline (verify-sig strips).
|
|
printf '[from:%s] [id:%s]\n\n%s' "$FROM" "$ID" "$MESSAGE" > "$PAYLOAD"
|
|
|
|
# Never reuse a stale signature: a leftover .sig from an earlier run would
|
|
# silently sign the wrong payload (burned 20 minutes on the board, 2026-10-03).
|
|
rm -f "$PAYLOAD.sig"
|
|
ssh-keygen -Y sign -f "$KEY" -n dm "$PAYLOAD" >/dev/null
|
|
|
|
printf '[from:%s] [id:%s]\n\n%s\n\n' "$FROM" "$ID" "$MESSAGE"
|
|
cat "$PAYLOAD.sig"
|