From fbf61cdad40a7dd3ca0567c06daca6bbbe94c104 Mon Sep 17 00:00:00 2001 From: Alan O'Cais Date: Tue, 11 Mar 2025 17:05:34 +0100 Subject: [PATCH] Add ssh signing script --- .github/workflows/tests_scripts.yml | 39 +++++++ scripts/sign_verify_file_ssh.sh | 156 ++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 .github/workflows/tests_scripts.yml create mode 100755 scripts/sign_verify_file_ssh.sh diff --git a/.github/workflows/tests_scripts.yml b/.github/workflows/tests_scripts.yml new file mode 100644 index 00000000..1f8d012b --- /dev/null +++ b/.github/workflows/tests_scripts.yml @@ -0,0 +1,39 @@ +# documentation: https://help.github.com/en/articles/workflow-syntax-for-github-actions +name: Tests for scripts +on: + push: + paths: + - scripts/sign_verify_file_ssh.sh + pull_request: + paths: + - scripts/sign_verify_file_ssh.sh +permissions: + contents: read # to fetch code (actions/checkout) +jobs: + build: + runs-on: ubuntu-24.04 + steps: + - name: checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: test sign_verify_file_ssh.sh script + run: | + # Create a PEM format ssh identity + ssh-keygen -t rsa -b 4096 -m PEM -f id_rsa.pem -N "" + # Create a file to sign + echo "Very important stuff" > out.txt + export FILE_TO_SIGN="out.txt" + # Sign the file + ./scripts/sign_verify_file_ssh.sh sign id_rsa.pem "$FILE_TO_SIGN" + # Create an allowed_signers file based on the public key + echo -n "allowed_identity " > allowed_signers + cat id_rsa.pem.pub >> allowed_signers + # Verify the signature + ./scripts/sign_verify_file_ssh.sh verify allowed_signers "$FILE_TO_SIGN" + # Make a new signature that does not appear in the allowed signers file + ssh-keygen -t rsa -b 4096 -m PEM -f id_rsa.alt.pem -N "" + # Replace the allowed signers file + echo -n "disallowed_identity " > allowed_signers + cat id_rsa.alt.pem.pub >> allowed_signers + # Make sure signature checking fails in this case + ./scripts/sign_verify_file_ssh.sh verify allowed_signers "$FILE_TO_SIGN" && exit 1 || echo "Expected failure for unknown identity" diff --git a/scripts/sign_verify_file_ssh.sh b/scripts/sign_verify_file_ssh.sh new file mode 100755 index 00000000..679ea7d6 --- /dev/null +++ b/scripts/sign_verify_file_ssh.sh @@ -0,0 +1,156 @@ +#!/bin/bash +# +# SSH Signature Signing and Verification Script +# - Sign a file using an SSH private key. +# - Verify a signed file using an allowed signers file. +# +# Generates a signature file named `.sig` in the same directory. +# +# Author: Alan O'Cais +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Usage message +usage() { + cat < + $0 verify [signature_file] + +Options: + sign: + - : Path to SSH private key (use KEY_PASSPHRASE env for passphrase) + - : File to sign + + verify: + - : Path to the allowed signers file + - : File to verify + - [signature_file]: Optional, defaults to '.sig' + +Example allowed signers format: + identity_1 +EOF + exit 9 +} + +# Error codes +FILE_PROBLEM=1 +CONVERSION_FAILURE=2 +VALIDATION_FAILED=3 + +# Ensure minimum arguments +[ "$#" -lt 3 ] && usage + +MODE="$1" +FILE_TO_SIGN="$3" + +# Ensure the target file exists +if [ ! -f "$FILE_TO_SIGN" ]; then + echo "Error: File '$FILE_TO_SIGN' not found." + exit $FILE_PROBLEM +fi + +# Use a very conservatuve umask throughout this script since we are dealing with sensitive things +umask 077 || { echo "Error: Failed to set 0177 umask."; exit $FILE_PROBLEM; } + +# Create a restricted temporary directory and ensure cleanup on exit +TEMP_DIR=$(mktemp -d) || { echo "Error: Failed to create temporary directory."; exit $FILE_PROBLEM; } +trap 'rm -rf "$TEMP_DIR"' EXIT + +# Converts the SSH private key to OpenSSH format and generates a public key +convert_private_key() { + local input_key="$1" + local output_key="$2" + + echo "Converting SSH key to OpenSSH format..." + cp "$input_key" "$output_key" || { echo "Error: Failed to copy $input_key to $output_key"; exit $FILE_PROBLEM; } + + # This saves the key in the default OpenSSH format (which is required for signing) + ssh-keygen -p -f "$output_key" -P "${KEY_PASSPHRASE:-}" -N "${KEY_PASSPHRASE:-}" || { + echo "Error: Failed to convert key to OpenSSH format." + exit $CONVERSION_FAILURE + } + + # Extract the public key from the private key + ssh-keygen -y -f "$input_key" -P "${KEY_PASSPHRASE:-}" > "${output_key}.pub" || { + echo "Error: Failed to extract public key." + exit $CONVERSION_FAILURE + } +} + +# Sign mode +if [ "$MODE" == "sign" ]; then + PRIVATE_KEY="$2" + TEMP_KEY="$TEMP_DIR/converted_key" + SIG_FILE="${FILE_TO_SIGN}.sig" + + # Check for key and existing signature + [ ! -f "$PRIVATE_KEY" ] && { echo "Error: Private key not found."; exit $FILE_PROBLEM; } + [ -f "$SIG_FILE" ] && { echo "Error: Signature already exists. Remove to re-sign."; exit $FILE_PROBLEM; } + + convert_private_key "$PRIVATE_KEY" "$TEMP_KEY" + + echo "Signing the file..." + ssh-keygen -Y sign -f "$TEMP_KEY" -P "${KEY_PASSPHRASE:-}" -n file "$FILE_TO_SIGN" + + [ ! -f "$SIG_FILE" ] && { echo "Error: Signing failed."; exit $FILE_PROBLEM; } + echo "Signature created: $SIG_FILE" + + cat <