mirror of
https://github.com/Matir/skel.git
synced 2026-05-25 13:19:07 -07:00
Work
This commit is contained in:
164
bin/ssh-sign
Executable file
164
bin/ssh-sign
Executable file
@@ -0,0 +1,164 @@
|
||||
#!/bin/bash
|
||||
# A robust wrapper for ssh-keygen to sign and verify files.
|
||||
|
||||
# --- Color Codes for Output ---
|
||||
COLOR_RED='\033[0;31m'
|
||||
COLOR_GREEN='\033[0;32m'
|
||||
COLOR_NONE='\033[0m' # No Color
|
||||
|
||||
# --- Default values ---
|
||||
DEFAULT_SIGNING_KEY="$HOME/.ssh/id_signing"
|
||||
DEFAULT_ALLOWED_SIGNERS="$HOME/.ssh/allowed_signers"
|
||||
DEFAULT_IDENTITY="david@systemoverlord.com"
|
||||
DEFAULT_NAMESPACE="file"
|
||||
|
||||
# --- Usage Message ---
|
||||
usage() {
|
||||
cat << EOF
|
||||
Usage: $(basename "$0") <sign|verify> [OPTIONS] [FILE]
|
||||
|
||||
A wrapper for 'ssh-keygen -Y' to simplify file signing and verification.
|
||||
|
||||
COMMANDS:
|
||||
sign Sign a file. The path to the file to be signed is provided as a positional argument.
|
||||
verify Verify a signature. The original file content is read from stdin.
|
||||
|
||||
OPTIONS:
|
||||
-f <file> For 'sign': Path to the private key for signing.
|
||||
Defaults to '$DEFAULT_SIGNING_KEY' if it exists.
|
||||
For 'verify': Path to the allowed_signers file.
|
||||
Defaults to '$DEFAULT_ALLOWED_SIGNERS'.
|
||||
-n <namespace> Signature namespace.
|
||||
Defaults to '$DEFAULT_NAMESPACE'.
|
||||
-I <identity> For 'verify': The identity to check the signature against.
|
||||
Defaults to '$DEFAULT_IDENTITY'.
|
||||
-s <sig_file> For 'verify': Path to the signature file to verify (e.g., file.sig). REQUIRED for verify.
|
||||
-h, --help Show this help message.
|
||||
|
||||
EXAMPLE USAGE:
|
||||
# Sign a file using the default key
|
||||
$(basename "$0") sign release.tar.gz
|
||||
|
||||
# Sign a file with a specific key
|
||||
$(basename "$0") sign -f ~/.ssh/id_ed25519_my_signing_key release.tar.gz
|
||||
|
||||
# Verify a signature using default allowed_signers and identity
|
||||
cat release.tar.gz | $(basename "$0") verify -s release.tar.gz.sig
|
||||
|
||||
# Verify a signature with a specific allowed_signers file and identity
|
||||
cat release.tar.gz | $(basename "$0") verify -f ./my_signers -I other@example.com -s release.tar.gz.sig
|
||||
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Helper for error messages ---
|
||||
error() {
|
||||
echo -e "${COLOR_RED}Error: $1${COLOR_NONE}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Main Script Logic ---
|
||||
|
||||
if [[ "$1" != "sign" && "$1" != "verify" ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
SUBCOMMAND=$1
|
||||
shift # Consume the subcommand
|
||||
|
||||
# --- Argument Parsing and Validation ---
|
||||
|
||||
# Separate arguments from the file to be signed
|
||||
declare -a remaining_args
|
||||
file_to_sign=""
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
# If we see a non-flag argument, assume it's the file to sign.
|
||||
# This works because the file to sign is the only positional argument.
|
||||
if [[ "$1" != -* ]] && [[ -z "$file_to_sign" ]]; then
|
||||
file_to_sign="$1"
|
||||
else
|
||||
remaining_args+=("$1")
|
||||
fi
|
||||
shift
|
||||
done
|
||||
|
||||
# --- Build command based on subcommand ---
|
||||
declare -a CMD_ARGS
|
||||
CMD_ARGS=("ssh-keygen" "-Y" "$SUBCOMMAND")
|
||||
|
||||
# Append all the flag-based arguments (-f, -n, -I, -s)
|
||||
CMD_ARGS+=("${remaining_args[@]}")
|
||||
|
||||
# Scan for provided flags to handle defaults correctly
|
||||
f_provided=false
|
||||
n_provided=false
|
||||
I_provided=false
|
||||
s_provided=false
|
||||
for arg in "${remaining_args[@]}"; do
|
||||
[[ "$arg" == "-f" ]] && f_provided=true
|
||||
[[ "$arg" == "-n" ]] && n_provided=true
|
||||
[[ "$arg" == "-I" ]] && I_provided=true
|
||||
[[ "$arg" == "-s" ]] && s_provided=true
|
||||
done
|
||||
|
||||
if [[ "$SUBCOMMAND" == "sign" ]]; then
|
||||
if [[ -z "$file_to_sign" ]]; then
|
||||
error "Path to file to be signed is required for 'sign' command."
|
||||
fi
|
||||
|
||||
# Set default signing key if -f was not provided
|
||||
if ! $f_provided; then
|
||||
if [[ ! -f "$DEFAULT_SIGNING_KEY" ]]; then
|
||||
error "Default signing key not found at '$DEFAULT_SIGNING_KEY'. Specify one with -f."
|
||||
fi
|
||||
CMD_ARGS+=("-f" "$DEFAULT_SIGNING_KEY")
|
||||
fi
|
||||
|
||||
# Set default namespace if -n was not provided
|
||||
if ! $n_provided; then
|
||||
CMD_ARGS+=("-n" "$DEFAULT_NAMESPACE")
|
||||
fi
|
||||
|
||||
# The file to sign MUST be the last argument for ssh-keygen
|
||||
CMD_ARGS+=("$file_to_sign")
|
||||
|
||||
elif [[ "$SUBCOMMAND" == "verify" ]]; then
|
||||
if [[ -n "$file_to_sign" ]]; then
|
||||
error "The 'verify' command reads from stdin and does not accept a positional file argument. Found '$file_to_sign'."
|
||||
fi
|
||||
if ! $s_provided; then
|
||||
error "Signature file must be provided with -s for 'verify' command."
|
||||
fi
|
||||
|
||||
# Set default allowed_signers if -f was not provided
|
||||
if ! $f_provided; then
|
||||
if [[ ! -f "$DEFAULT_ALLOWED_SIGNERS" ]]; then
|
||||
error "Default allowed signers file not found at '$DEFAULT_ALLOWED_SIGNERS'. Specify one with -f."
|
||||
fi
|
||||
CMD_ARGS+=("-f" "$DEFAULT_ALLOWED_SIGNERS")
|
||||
fi
|
||||
|
||||
# Set default identity if -I was not provided
|
||||
if ! $I_provided; then
|
||||
CMD_ARGS+=("-I" "$DEFAULT_IDENTITY")
|
||||
fi
|
||||
|
||||
# Set default namespace if -n was not provided
|
||||
if ! $n_provided; then
|
||||
CMD_ARGS+=("-n" "$DEFAULT_NAMESPACE")
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Execute and Report ---
|
||||
|
||||
# We capture the output and stderr to show it to the user
|
||||
if output=$("${CMD_ARGS[@]}" 2>&1); then
|
||||
echo -e "${COLOR_GREEN}Success:${COLOR_NONE}"
|
||||
echo "$output"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${COLOR_RED}Command Failed:${COLOR_NONE}"
|
||||
echo "$output" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,6 +1,12 @@
|
||||
# Universal Settings
|
||||
Protocol 2
|
||||
|
||||
Host *
|
||||
# Add the post-quantum (PQ) KEX algorithms to the front of the default list.
|
||||
# The client will try them in this order before falling back to standard ones.
|
||||
# The (+) syntax requires OpenSSH 7.8 or newer.
|
||||
KexAlgorithms +mlkem768x25519-sha256,sntrup761x25519-sha512@openssh.com
|
||||
|
||||
# Permit Local Overrides
|
||||
Include ~/.ssh/config.d/*
|
||||
|
||||
@@ -33,4 +39,4 @@ Match final
|
||||
ServerAliveCountMax 3
|
||||
UpdateHostKeys yes
|
||||
User david
|
||||
VerifyHostKeyDNS yes
|
||||
VerifyHostKeyDNS ask
|
||||
|
||||
34
install.sh
34
install.sh
@@ -88,17 +88,35 @@ install_gpg_keys() {
|
||||
|
||||
install_known_hosts() {
|
||||
verbose 'Installing known hosts...' >&2
|
||||
if [[ ! -f "${BASEDIR}/keys/known_hosts" ]] ; then
|
||||
local skel_hosts="${BASEDIR}/keys/known_hosts"
|
||||
local user_hosts="${HOME}/.ssh/known_hosts"
|
||||
local merge_script="${BASEDIR}/skeltools/merge_authorized_keys"
|
||||
|
||||
if [[ ! -f "${skel_hosts}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
mkdir -p "${HOME}/.ssh"
|
||||
if [[ -f "${HOME}/.ssh/known_hosts" ]] ; then
|
||||
local tmpf="$(mktemp)"
|
||||
cat "${BASEDIR}"/keys/known_hosts "${HOME}"/.ssh/known_hosts \
|
||||
| sort -u > "$tmpf"
|
||||
mv "$tmpf" "${HOME}"/.ssh/known_hosts
|
||||
|
||||
if [[ -f "${user_hosts}" ]]; then
|
||||
# User has an existing known_hosts file, merge is required.
|
||||
local tmpf
|
||||
tmpf="$(mktemp)"
|
||||
if [[ -x "${merge_script}" ]]; then
|
||||
# Use the robust awk script for merging.
|
||||
verbose "Merging known_hosts with authoritative script..."
|
||||
"${merge_script}" "${skel_hosts}" "${user_hosts}" > "$tmpf"
|
||||
else
|
||||
# Fallback to the old, less robust method if the script is missing.
|
||||
verbose "Warning: ${merge_script} not found or not executable. Using simple sort."
|
||||
cat "${skel_hosts}" "${user_hosts}" | sort -u > "$tmpf"
|
||||
fi
|
||||
# Safely replace the original file.
|
||||
cat "$tmpf" >| "${user_hosts}"
|
||||
rm "$tmpf"
|
||||
else
|
||||
cp "${BASEDIR}"/keys/known_hosts "${HOME}"/.ssh/known_hosts
|
||||
# User does not have a known_hosts file, just copy the new one.
|
||||
cp "${skel_hosts}" "${user_hosts}"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -153,7 +171,7 @@ save_prefs() {
|
||||
echo "INSTALL_KEYS=\"${INSTALL_KEYS}\""
|
||||
echo "TRUST_ALL_KEYS=\"${TRUST_ALL_KEYS}\""
|
||||
echo "VERBOSE=\"${VERBOSE}\""
|
||||
} > "$pref_file"
|
||||
} >| "$pref_file"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
|
||||
65
skeltools/merge_authorized_keys
Executable file
65
skeltools/merge_authorized_keys
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check for the correct number of arguments and display usage if incorrect.
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "Usage: $(basename "$0") <authoritative_hosts_file> <target_known_hosts_file>"
|
||||
echo "Merges two known_hosts files, with entries from the authoritative file"
|
||||
echo "replacing matching entries (by hostname and key type) in the target file."
|
||||
echo "The final merged result is printed to standard output."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AUTHORITATIVE_FILE="$1"
|
||||
TARGET_FILE="$2"
|
||||
|
||||
awk '
|
||||
# Part 1: Process the authoritative source file first (NR==FNR).
|
||||
# NR==FNR is an awk pattern that is only true while reading the first file
|
||||
# listed on the command line.
|
||||
NR==FNR {
|
||||
# The key type is always the 2nd field (e.g., "ssh-ed25519").
|
||||
key_type = $2;
|
||||
# Split all hostnames/IPs from the 1st field (e.g., "host.com,192.0.2.1").
|
||||
split($1, hosts, ",");
|
||||
for (i in hosts) {
|
||||
# Build an associative array using a compound key: (hostname, key_type).
|
||||
# The value stored is the entire line from the authoritative file.
|
||||
auth_map[hosts[i], key_type] = $0;
|
||||
}
|
||||
next; # Skip to the next line in the authoritative file.
|
||||
}
|
||||
|
||||
# Part 2: Process the main/target known_hosts file.
|
||||
# This block only runs for the second file onwards (NR != FNR).
|
||||
{
|
||||
is_managed = 0;
|
||||
# The key type is always the 2nd field.
|
||||
key_type = $2;
|
||||
split($1, hosts, ",");
|
||||
for (i in hosts) {
|
||||
# Check if this specific (hostname, key_type) pair is managed
|
||||
# by our authoritative source.
|
||||
if ((hosts[i], key_type) in auth_map) {
|
||||
is_managed = 1; # Mark this key as one that will be replaced.
|
||||
break;
|
||||
}
|
||||
}
|
||||
# If this specific key is NOT managed, keep the original line by printing it.
|
||||
if (!is_managed) {
|
||||
print $0;
|
||||
}
|
||||
}
|
||||
|
||||
# Part 3: After all files are read, print the authoritative entries.
|
||||
END {
|
||||
# Use a "printed" array to ensure we only print each unique line once,
|
||||
# even if it was stored multiple times in auth_map (e.g., for "host,ip").
|
||||
for (entry in auth_map) {
|
||||
line = auth_map[entry];
|
||||
if (!(line in printed)) {
|
||||
print line;
|
||||
printed[line] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
' "$AUTHORITATIVE_FILE" "$TARGET_FILE"
|
||||
Reference in New Issue
Block a user