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
|
# Universal Settings
|
||||||
Protocol 2
|
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
|
# Permit Local Overrides
|
||||||
Include ~/.ssh/config.d/*
|
Include ~/.ssh/config.d/*
|
||||||
|
|
||||||
@@ -33,4 +39,4 @@ Match final
|
|||||||
ServerAliveCountMax 3
|
ServerAliveCountMax 3
|
||||||
UpdateHostKeys yes
|
UpdateHostKeys yes
|
||||||
User david
|
User david
|
||||||
VerifyHostKeyDNS yes
|
VerifyHostKeyDNS ask
|
||||||
|
|||||||
34
install.sh
34
install.sh
@@ -88,17 +88,35 @@ install_gpg_keys() {
|
|||||||
|
|
||||||
install_known_hosts() {
|
install_known_hosts() {
|
||||||
verbose 'Installing known hosts...' >&2
|
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
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "${HOME}/.ssh"
|
mkdir -p "${HOME}/.ssh"
|
||||||
if [[ -f "${HOME}/.ssh/known_hosts" ]] ; then
|
|
||||||
local tmpf="$(mktemp)"
|
if [[ -f "${user_hosts}" ]]; then
|
||||||
cat "${BASEDIR}"/keys/known_hosts "${HOME}"/.ssh/known_hosts \
|
# User has an existing known_hosts file, merge is required.
|
||||||
| sort -u > "$tmpf"
|
local tmpf
|
||||||
mv "$tmpf" "${HOME}"/.ssh/known_hosts
|
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
|
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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +171,7 @@ save_prefs() {
|
|||||||
echo "INSTALL_KEYS=\"${INSTALL_KEYS}\""
|
echo "INSTALL_KEYS=\"${INSTALL_KEYS}\""
|
||||||
echo "TRUST_ALL_KEYS=\"${TRUST_ALL_KEYS}\""
|
echo "TRUST_ALL_KEYS=\"${TRUST_ALL_KEYS}\""
|
||||||
echo "VERBOSE=\"${VERBOSE}\""
|
echo "VERBOSE=\"${VERBOSE}\""
|
||||||
} > "$pref_file"
|
} >| "$pref_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup() {
|
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