Skip to content

Commit 0c8b7cb

Browse files
committed
Another round of tidying up
1 parent 524760c commit 0c8b7cb

1 file changed

Lines changed: 92 additions & 103 deletions

File tree

sign_verify_file_ssh.sh

Lines changed: 92 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,119 @@
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
925
usage() {
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
2648
FILE_PROBLEM=1
2749
CONVERSION_FAILURE=2
2850
VALIDATION_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

3555
MODE="$1"
3656
FILE_TO_SIGN="$3"
3757

38-
# Ensure the file exists
58+
# Ensure the target file exists
3959
if [ ! -f "$FILE_TO_SIGN" ]; then
4060
echo "Error: File '$FILE_TO_SIGN' not found."
41-
exit 1
61+
exit $FILE_PROBLEM
4262
fi
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
7191
if [ "$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

162152
else
163153
usage
164154
fi
165-

0 commit comments

Comments
 (0)