11#! /bin/bash
2+ #
23# SSH Signature Signing and Verification Script
3- # - Generate a digital signature for a file using an SSH private key.
4- # - Verify the signature of a signed file using an allowed signers file.
4+ # - Sign a file using an SSH private key.
5+ # - Verify a signed file using an allowed signers file.
6+ #
7+ # Generates a signature file named `<file>.sig` in the same directory.
8+ #
9+ # Author: Alan O'Cais
510#
6- # The script generates a signature file named `<file>.sig` in the same directory.
11+ # This program is free software: you can redistribute it and/or modify
12+ # it under the terms of the GNU General Public License as published by
13+ # the Free Software Foundation, either version 2 of the License, or
14+ # (at your option) any later version.
15+ #
16+ # This program is distributed in the hope that it will be useful,
17+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
18+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+ # GNU General Public License for more details.
20+ #
21+ # You should have received a copy of the GNU General Public License
22+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
723
824# Usage message
925usage () {
10- echo " This script allows you to securely sign files using an SSH private key and verify signatures using an allowed signers file."
11- echo " Usage:"
12- echo " $0 sign <private_key> <file>"
13- echo " $0 verify <allowed_signers_file> <file> [signature_file]"
14- echo " where"
15- echo " - <private_key>: Path to the SSH private key (if a KEY_passphrase exists it can be"
16- echo " provided via the KEY_PASSPHRASE environment variable)"
17- echo " - <file>: Path to the file to be signed/verified"
18- echo " - <allowed_signers_file>: Path to the allowed signers file"
19- echo " - [signature_file]: (optional) Path to the signature file"
20- echo " (defaults to '<file>.sig' if not provided)."
21- echo
22- exit 1
26+ cat << EOF
27+ Usage:
28+ $0 sign <private_key> <file>
29+ $0 verify <allowed_signers_file> <file> [signature_file]
30+
31+ Options:
32+ sign:
33+ - <private_key>: Path to SSH private key (use KEY_PASSPHRASE env for passphrase)
34+ - <file>: File to sign
35+
36+ verify:
37+ - <allowed_signers_file>: Path to the allowed signers file
38+ - <file>: File to verify
39+ - [signature_file]: Optional, defaults to '<file>.sig'
40+
41+ Example allowed signers format:
42+ identity_1 <public-key>
43+ EOF
44+ exit 9
2345}
2446
2547# Error codes
2648FILE_PROBLEM=1
2749CONVERSION_FAILURE=2
2850VALIDATION_FAILED=3
2951
30- # Ensure at least three arguments are provided
31- if [ " $# " -lt 3 ]; then
32- usage
33- fi
52+ # Ensure minimum arguments
53+ [ " $# " -lt 3 ] && usage
3454
3555MODE=" $1 "
3656FILE_TO_SIGN=" $3 "
3757
38- # Ensure the file exists
58+ # Ensure the target file exists
3959if [ ! -f " $FILE_TO_SIGN " ]; then
4060 echo " Error: File '$FILE_TO_SIGN ' not found."
41- exit 1
61+ exit $FILE_PROBLEM
4262fi
4363
44- # Function to securely convert the private key to OpenSSH format
45- # (which is required for signing)
46- convert_private_key_to_openssh_format () {
47- local key_file= " $1 "
48- local output_file= " $2 "
49-
50- # Convert the key to OpenSSH format (the default format hence no '-m <format>') for any input format
51- # (first copy the file as it will be overwritten during the conversion)
52- echo " Copying $key_file to $output_file and performing format conversion "
53- cp " $key_file " " $output_file " || {
54- echo " Copy failed "
55- exit $FILE_PROBLEM
56- }
57- ssh-keygen -p -f " $output_file " -P " $KEY_PASSPHRASE " -N " $KEY_PASSPHRASE " || {
58- echo " Error: Failed to convert key $key_file to OpenSSH format "
59- echo " (set the environment variable KEY_PASSPHRASE to use a passphrase for the key) ."
64+ # Create a restricted temporary directory and ensure cleanup on exit
65+ TEMP_DIR= $( mktemp -d ) || { echo " Error: Failed to create temporary directory. " ; exit $FILE_PROBLEM ; }
66+ chmod 700 " $TEMP_DIR "
67+ trap ' rm -rf "$TEMP_DIR" ' EXIT
68+
69+ # Converts the SSH private key to OpenSSH format and generates a public key
70+ convert_private_key () {
71+ local input_key= " $1 "
72+ local output_key= " $2 "
73+
74+ echo " Converting SSH key to OpenSSH format... "
75+ cp " $input_key " " $output_key " || { echo " Error: Failed to copy $input_key to $output_key " ; exit $FILE_PROBLEM ; }
76+
77+ # This saves the key in the default OpenSSH format (which is required for signing)
78+ ssh-keygen -p -f " $output_key " -P " ${KEY_PASSPHRASE :- } " -N " ${KEY_PASSPHRASE :- } " || {
79+ echo " Error: Failed to convert key to OpenSSH format ."
6080 exit $CONVERSION_FAILURE
6181 }
6282
63- # Generate the public key from the private key
64- ssh-keygen -y -f " $key_file " -P " $KEY_PASSPHRASE " > " $output_file .pub" || {
65- echo " Error: Failed to generate public key from PEM key."
83+ # Extract the public key from the private key
84+ ssh-keygen -y -f " $input_key " -P " ${ KEY_PASSPHRASE:- } " > " ${output_key} .pub" || {
85+ echo " Error: Failed to extract public key."
6686 exit $CONVERSION_FAILURE
6787 }
6888}
6989
7090# Sign mode
7191if [ " $MODE " == " sign" ]; then
72- PRIVATE_KEY_ORIG =" $2 "
73- PRIVATE_KEY= " conversion_id "
92+ PRIVATE_KEY =" $2 "
93+ TEMP_KEY= " $TEMP_DIR /converted_key "
7494 SIG_FILE=" ${FILE_TO_SIGN} .sig"
75- PUB_KEY=" ${PRIVATE_KEY} .pub"
76-
77- # Ensure cleanup on exit of our temporary key
78- trap ' rm -f "$PRIVATE_KEY" "$PUB_KEY"' EXIT
79-
80- if [ ! -f " $PRIVATE_KEY_ORIG " ]; then
81- echo " Error: Private key '$PRIVATE_KEY_ORIG ' not found."
82- exit $FILE_PROBLEM
83- fi
84- if [ -f " $SIG_FILE " ]; then
85- echo " Error: Signature file '$SIG_FILE ' already exists. Please remove to re-sign!"
86- exit $FILE_PROBLEM
87- fi
88- # Convert key to OpenSSH format
89- echo " Converting SSH key to OpenSSH format..."
90- convert_private_key_to_openssh_format " $PRIVATE_KEY_ORIG " " $PRIVATE_KEY "
9195
92- # Sign the file
93- echo " Signing the file... "
94- ssh-keygen -Y sign - f " $PRIVATE_KEY " -P " $KEY_PASSPHRASE " -n file " $FILE_TO_SIGN "
96+ # Check for key and existing signature
97+ [ ! -f " $PRIVATE_KEY " ] && { echo " Error: Private key not found. " ; exit $FILE_PROBLEM ; }
98+ [ - f " $SIG_FILE " ] && { echo " Error: Signature already exists. Remove to re-sign. " ; exit $FILE_PROBLEM ; }
9599
96- if [ ! -f " $SIG_FILE " ]; then
97- echo " Error: Signing failed, no file $SIG_FILE found."
98- exit $FILE_PROBLEM
99- fi
100+ convert_private_key " $PRIVATE_KEY " " $TEMP_KEY "
100101
102+ echo " Signing the file..."
103+ ssh-keygen -Y sign -f " $TEMP_KEY " -P " ${KEY_PASSPHRASE:- } " -n file " $FILE_TO_SIGN "
104+
105+ [ ! -f " $SIG_FILE " ] && { echo " Error: Signing failed." ; exit $FILE_PROBLEM ; }
101106 echo " Signature created: $SIG_FILE "
102- echo -e " \nAn allowed signatures file has the format:"
103- echo -e " \n<principal-list> <optional options> <public-key>\n"
104- echo -e " and so for the provided key could have contents like:\n"
105- echo -e " identity_1 $( cat " $PUB_KEY " ) \n"
106-
107- # Verify the signature
108- echo -e " Validating the signature of the file..."
109- ssh-keygen -Y check-novalidate -n file -f " $PUB_KEY " -s " $SIG_FILE " < " $FILE_TO_SIGN " || {
110- echo " - Signature validation failed."
107+
108+ cat << EOF
109+
110+ For verification, your allowed signers file could contain:
111+ identity_1 $( cat " ${TEMP_KEY} .pub" )
112+ EOF
113+
114+ echo " Validating the signature..."
115+ ssh-keygen -Y check-novalidate -n file -f " ${TEMP_KEY} .pub" -s " $SIG_FILE " < " $FILE_TO_SIGN " || {
116+ echo " Error: Signature validation failed."
111117 exit $VALIDATION_FAILED
112118 }
113119
@@ -116,50 +122,33 @@ elif [ "$MODE" == "verify" ]; then
116122 ALLOWED_SIGNERS_FILE=" $2 "
117123 SIG_FILE=" ${4:- ${FILE_TO_SIGN} .sig} "
118124
119- if [ ! -f " $ALLOWED_SIGNERS_FILE " ] ; then
120- echo " Error: Allowed signers file ' $ALLOWED_SIGNERS_FILE ' not found. "
121- exit 1
122- fi
125+ # Ensure required files exist
126+ for file in " $ALLOWED_SIGNERS_FILE " " $SIG_FILE " ; do
127+ [ ! -f " $file " ] && { echo " Error: File ' $file ' not found. " ; exit $FILE_PROBLEM ; }
128+ done
123129
124- if [ ! -f " $SIG_FILE " ]; then
125- echo " Error: Signature file '$SIG_FILE ' not found."
126- exit 1
127- fi
130+ echo " Verifying the signature against allowed signers..."
128131
129- # Loop through each line of the allowed_signers file
132+ # Iterate through each principal in the allowed signers file
130133 while IFS= read -r line || [[ -n " $line " ]]; do
131- # Skip empty lines or comments
132134 [[ -z " $line " || " $line " == \# * ]] && continue
133135
134- # Extract principals (field 1)
136+ # Extract and process each principal
135137 principals=$( echo " $line " | cut -d' ' -f1)
136- echo " $principals "
137-
138- # Iterate over each principal (comma-separated)
139- OLD_IFS=$IFS
140138 IFS=' ,' read -ra principal_list <<< " $principals"
141- IFS=$OLD_IFS
142139
143140 for principal in " ${principal_list[@]} " ; do
144- echo " Processing Principal: $principal "
145-
146- # Use ssh-keygen to verify the signature
141+ echo " Checking principal: $principal "
147142 if ssh-keygen -Y verify -f " $ALLOWED_SIGNERS_FILE " -n file -I " $principal " -s " $SIG_FILE " < " $FILE_TO_SIGN " ; then
148- echo -e " \nSignature is valid for principal: $principal "
149- exit 0 # Exit on first valid signature
150- else
151- echo " Invalid signature for principal: $principal "
143+ echo " Signature is valid for principal: $principal "
144+ exit 0
152145 fi
153146 done
154147 done < " $ALLOWED_SIGNERS_FILE "
155148
156- echo
157- echo " No valid signature found in allowed signers."
158- echo " The allowed signers file should contain entries in the format:"
159- echo " <principal-list> <optional options> <public-key>"
149+ echo " Error: No valid signature found."
160150 exit $VALIDATION_FAILED
161151
162152else
163153 usage
164154fi
165-
0 commit comments