77# Generates a signature file named `<file>.sig` in the same directory.
88#
99# Author: Alan O'Cais
10+ # Author: Thomas Roeblitz
1011#
1112# This program is free software: you can redistribute it and/or modify
1213# it under the terms of the GNU General Public License as published by
2324
2425# Usage message
2526usage () {
27+ local exit_code=${1:- 9}
2628 cat << EOF
2729Usage:
28- $0 sign <private_key> <file>
29- $0 verify <allowed_signers_file> <file> [signature_file]
30+ $0 -- sign --private-key <private_key> --file <file> [--namespace <namespace>]
31+ $0 -- verify --allowed-signers-file <allowed_signers_file> --file <file> [--signature-file < signature_file>] [--terse ]
3032
3133Options:
32- sign:
33- - <private_key>: Path to SSH private key (use KEY_PASSPHRASE env for passphrase)
34- - <file>: File to sign
34+ --sign:
35+ --private-key <private_key>: Path to SSH private key (use KEY_PASSPHRASE env for passphrase)
36+ --file <file>: File to sign
37+ --namespace <namespace>: Optional, defaults to "file" if not specified
3538
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'
39+ --verify:
40+ --allowed-signers-file <allowed_signers_file>: Path to the allowed signers file
41+ --file <file>: File to verify
42+ --signature-file <signature_file>: Optional, defaults to '<file>.sig'
43+ --terse: If set, output only matching identity and namespace for verification in JSON format
4044
4145Example allowed signers format:
42- identity_1 <public-key>
46+ identity_1 namespaces="namespace",valid-before="last-valid-day" <public-key>
47+
48+ If the private key has a passphrase, this can be provided via a 'KEY_PASSPHRASE' environment variable.
4349EOF
44- exit 9
50+ exit " $exit_code "
4551}
4652
4753# Error codes
@@ -50,19 +56,84 @@ CONVERSION_FAILURE=2
5056VALIDATION_FAILED=3
5157
5258# Ensure minimum arguments
53- [ " $# " -lt 3 ] && usage
59+ if [ " $# " -lt 3 ]; then
60+ echo " Error: Missing required arguments."
61+ usage
62+ fi
5463
55- MODE=" $1 "
56- FILE_TO_SIGN=" $3 "
64+ # Parse options
65+ TERSE_MODE=false
66+ while [[ " $# " -gt 0 ]]; do
67+ case " $1 " in
68+ --sign)
69+ MODE=" sign"
70+ shift
71+ ;;
72+ --verify)
73+ MODE=" verify"
74+ shift
75+ ;;
76+ --private-key)
77+ PRIVATE_KEY=" $2 "
78+ shift 2
79+ ;;
80+ --file)
81+ FILE_TO_SIGN=" $2 "
82+ shift 2
83+ ;;
84+ --namespace)
85+ NAMESPACE=" $2 "
86+ shift 2
87+ ;;
88+ --allowed-signers-file)
89+ ALLOWED_SIGNERS_FILE=" $2 "
90+ shift 2
91+ ;;
92+ --signature-file)
93+ SIG_FILE=" $2 "
94+ shift 2
95+ ;;
96+ --terse)
97+ TERSE_MODE=true
98+ shift
99+ ;;
100+ * )
101+ echo " Error: Invalid argument: $1 "
102+ usage
103+ ;;
104+ esac
105+ done
106+
107+ # Set default namespace if not provided
108+ if [ -z " $NAMESPACE " ]; then
109+ NAMESPACE=" file"
110+ fi
111+
112+ # Ensure mode is set
113+ if [ -z " $MODE " ]; then
114+ echo " Error: Missing operation mode (either --sign or --verify)"
115+ usage
116+ fi
117+
118+ # Ensure required arguments
119+ if [ " $MODE " == " sign" ]; then
120+ [ -z " $PRIVATE_KEY " ] && { echo " Error: --private-key not specified." ; usage $FILE_PROBLEM ; }
121+ [ -z " $FILE_TO_SIGN " ] && { echo " Error: --file not specified." ; usage $FILE_PROBLEM ; }
122+ SIG_FILE=" ${FILE_TO_SIGN} .sig"
123+ elif [ " $MODE " == " verify" ]; then
124+ [ -z " $ALLOWED_SIGNERS_FILE " ] && { echo " Error: --allowed-signers-file not specified." ; usage $FILE_PROBLEM ; }
125+ [ -z " $FILE_TO_SIGN " ] && { echo " Error: --file not specified." ; usage $FILE_PROBLEM ; }
126+ SIG_FILE=" ${SIG_FILE:- ${FILE_TO_SIGN} .sig} "
127+ fi
57128
58129# Ensure the target file exists
59130if [ ! -f " $FILE_TO_SIGN " ]; then
60131 echo " Error: File '$FILE_TO_SIGN ' not found."
61132 exit $FILE_PROBLEM
62133fi
63134
64- # Use a very conservatuve umask throughout this script since we are dealing with sensitive things
65- umask 077 || { echo " Error: Failed to set 0177 umask." ; exit $FILE_PROBLEM ; }
135+ # Use a very conservative umask throughout this script since we are dealing with sensitive things
136+ umask 0077 || { echo " Error: Failed to set 0077 umask." ; exit $FILE_PROBLEM ; }
66137
67138# Create a restricted temporary directory and ensure cleanup on exit
68139TEMP_DIR=$( mktemp -d) || { echo " Error: Failed to create temporary directory." ; exit $FILE_PROBLEM ; }
@@ -91,9 +162,7 @@ convert_private_key() {
91162
92163# Sign mode
93164if [ " $MODE " == " sign" ]; then
94- PRIVATE_KEY=" $2 "
95165 TEMP_KEY=" $TEMP_DIR /converted_key"
96- SIG_FILE=" ${FILE_TO_SIGN} .sig"
97166
98167 # Check for key and existing signature
99168 [ ! -f " $PRIVATE_KEY " ] && { echo " Error: Private key not found." ; exit $FILE_PROBLEM ; }
@@ -102,55 +171,57 @@ if [ "$MODE" == "sign" ]; then
102171 convert_private_key " $PRIVATE_KEY " " $TEMP_KEY "
103172
104173 echo " Signing the file..."
105- ssh-keygen -Y sign -f " $TEMP_KEY " -P " ${KEY_PASSPHRASE:- } " -n file " $FILE_TO_SIGN "
106-
107- [ ! -f " $SIG_FILE " ] && { echo " Error: Signing failed." ; exit $FILE_PROBLEM ; }
108- echo " Signature created: $SIG_FILE "
174+ ssh-keygen -Y sign -f " $TEMP_KEY " -P " ${KEY_PASSPHRASE:- } " -n " ${NAMESPACE} " " $FILE_TO_SIGN "
109175
110176 cat << EOF
111177
112178For verification, your allowed signers file could contain:
113- identity_1 $( cat " ${TEMP_KEY} .pub" )
179+ identity_1 namespaces=" ${NAMESPACE} ",valid-before="LAST_VALID_DAY" $( cat " ${TEMP_KEY} .pub" )
114180EOF
115181
182+ [ ! -f " $SIG_FILE " ] && { echo " Error: Signing failed." ; exit $FILE_PROBLEM ; }
183+ echo " Signature created: $SIG_FILE "
184+
116185 echo " Validating the signature..."
117- ssh-keygen -Y check-novalidate -n file -f " ${TEMP_KEY} .pub" -s " $SIG_FILE " < " $FILE_TO_SIGN " || {
186+ ssh-keygen -Y check-novalidate -n " ${NAMESPACE} " -f " ${TEMP_KEY} .pub" -s " $SIG_FILE " < " $FILE_TO_SIGN " || {
118187 echo " Error: Signature validation failed."
119188 exit $VALIDATION_FAILED
120189 }
121190
122191# Verify mode
123192elif [ " $MODE " == " verify" ]; then
124- ALLOWED_SIGNERS_FILE=" $2 "
125- SIG_FILE=" ${4:- ${FILE_TO_SIGN} .sig} "
126-
127193 # Ensure required files exist
128194 for file in " $ALLOWED_SIGNERS_FILE " " $SIG_FILE " ; do
129195 [ ! -f " $file " ] && { echo " Error: File '$file ' not found." ; exit $FILE_PROBLEM ; }
130196 done
131197
132- echo " Verifying the signature against allowed signers..."
133-
134198 # Iterate through each principal in the allowed signers file
135- while IFS= read -r line || [[ -n " $line " ]]; do
136- [[ -z " $line " || " $line " == \# * ]] && continue
137-
138- # Extract and process each principal
139- principals=$( echo " $line " | cut -d' ' -f1)
140- IFS=' ,' read -ra principal_list <<< " $principals"
141-
142- for principal in " ${principal_list[@]} " ; do
143- echo " Checking principal: $principal "
144- if ssh-keygen -Y verify -f " $ALLOWED_SIGNERS_FILE " -n file -I " $principal " -s " $SIG_FILE " < " $FILE_TO_SIGN " ; then
145- echo " Signature is valid for principal: $principal "
199+ while read -r principal options key
200+ do
201+ [[ -z " $principal " || " $principal " == \# * ]] && continue
202+
203+ namespaces=$( echo " $options " | grep -oP " namespaces=\" \K[^\" ]+" )
204+
205+ if [ " $TERSE_MODE " = true ]; then
206+ if ssh-keygen -Y verify -f " $ALLOWED_SIGNERS_FILE " -n " $namespaces " -I " $principal " -s " $SIG_FILE " < " $FILE_TO_SIGN " > /dev/null 2>&1 ; then
207+ # Output in JSON format
208+ echo " {\" identity\" : \" $principal \" , \" namespace\" : \" $namespaces \" }"
146209 exit 0
147210 fi
148- done
211+ else
212+ if ssh-keygen -Y verify -f " $ALLOWED_SIGNERS_FILE " -n " $namespaces " -I " $principal " -s " $SIG_FILE " < " $FILE_TO_SIGN " ; then
213+ echo " Signature is valid for principal: $principal and namespace: $namespaces "
214+ exit 0
215+ else
216+ echo
217+ echo " Signature _not_ valid for principal: $principal and namespace: $namespaces "
218+ fi
219+ fi
149220 done < " $ALLOWED_SIGNERS_FILE "
150221
151222 echo " Error: No valid signature found."
152223 exit $VALIDATION_FAILED
153-
154224else
225+ echo " Error: Invalid operation mode. Use --sign or --verify."
155226 usage
156227fi
0 commit comments