raw
ffa_ch20_litmus.kv      1 #!/bin/sh
ffa_ch20_litmus.kv 2
ffa_ch20_litmus.kv 3 ############################################################################
ffa_ch20_litmus.kv 4 # 'Litmus' Utility. Verifies traditional GPG RSA signatures using Peh. #
ffa_ch20_litmus.kv 5 # #
ffa_ch20_litmus.kv 6 # Usage: ./litmus.sh publickey.peh signature.sig datafile #
ffa_ch20_litmus.kv 7 # #
ffa_ch20c_litmus_... 8 # Currently, supports RSA 'clearsigned' and 'detached' sigs made with the #
ffa_ch20c_litmus_... 9 # following hashes: #
ffa_ch20c_litmus_... 10 # SHA1 (warns: known-breakable!), SHA224, SHA256, SHA384, SHA512. #
ffa_ch20b_litmus_... 11 # #
ffa_ch20_litmus.kv 12 # See instructions re: converting traditional GPG public keys for use with #
ffa_ch20_litmus.kv 13 # this program. #
ffa_ch20_litmus.kv 14 # #
ffa_ch20_litmus.kv 15 # Peh, xxd, hexdump, shasum, and a number of common utils (see EXTERNALS) #
ffa_ch20_litmus.kv 16 # must be present on your machine. #
ffa_ch20_litmus.kv 17 # #
ffa_ch20_litmus.kv 18 # (C) 2020 Stanislav Datskovskiy ( www.loper-os.org ) #
ffa_ch20_litmus.kv 19 # http://wot.deedbot.org/17215D118B7239507FAFED98B98228A001ABFFC7.html #
ffa_ch20_litmus.kv 20 # #
ffa_ch20_litmus.kv 21 # You do not have, nor can you ever acquire the right to use, copy or #
ffa_ch20_litmus.kv 22 # distribute this software ; Should you use this software for any purpose, #
ffa_ch20_litmus.kv 23 # or copy and distribute it to anyone or in any manner, you are breaking #
ffa_ch20_litmus.kv 24 # the laws of whatever soi-disant jurisdiction, and you promise to #
ffa_ch20_litmus.kv 25 # continue doing so for the indefinite future. In any case, please #
ffa_ch20_litmus.kv 26 # always : read and understand any software ; verify any PGP signatures #
ffa_ch20_litmus.kv 27 # that you use - for any purpose. #
ffa_ch20_litmus.kv 28 ############################################################################
ffa_ch20_litmus.kv 29
ffa_ch20_litmus.kv 30 # External programs that are required (if not found, will eggog) :
ffa_ch20c_litmus_... 31 EXTERNALS="peh xxd hexdump base64 shasum cut tr sed wc grep printf
ffa_ch20c_litmus_... 32 mktemp awk truncate"
ffa_ch20_litmus.kv 33
ffa_ch20_litmus.kv 34 # Return Codes:
ffa_ch20_litmus.kv 35
ffa_ch20_litmus.kv 36 # Signature is VALID for given Sig, Data File, and Public Key:
ffa_ch20_litmus.kv 37 RET_VALID_SIG=0
ffa_ch20_litmus.kv 38
ffa_ch20_litmus.kv 39 # Signature is INVALID:
ffa_ch20_litmus.kv 40 RET_BAD_SIG=1
ffa_ch20_litmus.kv 41
ffa_ch20_litmus.kv 42 # All Other Cases:
ffa_ch20_litmus.kv 43 RET_EGGOG=-1
ffa_ch20_litmus.kv 44
ffa_ch21a_ter_ch1... 45 # Verify that all of the necessary external programs in fact exist:
ffa_ch21a_ter_ch1... 46 for i in $EXTERNALS
ffa_ch21a_ter_ch1... 47 do
ffa_ch21a_ter_ch1... 48 command -v $i >/dev/null && continue || \
ffa_ch21a_ter_ch1... 49 { echo "$i is required but was not found! Please install it." >&2 ; \
ffa_ch21a_ter_ch1... 50 exit $RET_EGGOG; }
ffa_ch21a_ter_ch1... 51 done
ffa_ch20_litmus.kv 52
ffa_ch20_litmus.kv 53 # Terminations:
ffa_ch20_litmus.kv 54
ffa_ch20_litmus.kv 55 # Success (Valid RSA signature) :
ffa_ch20_litmus.kv 56 done_sig_valid() {
ffa_ch20_litmus.kv 57 echo "VALID $pubkey_algo signature from $pubkey_owner"
ffa_ch20_litmus.kv 58 exit $RET_VALID_SIG
ffa_ch20_litmus.kv 59 }
ffa_ch20_litmus.kv 60
ffa_ch20_litmus.kv 61 # Failure (INVALID RSA signature) :
ffa_ch20_litmus.kv 62 done_sig_bad() {
ffa_ch20_litmus.kv 63 echo "Signature is INVALID for this public key and input file!"
ffa_ch20_litmus.kv 64 exit $RET_BAD_SIG
ffa_ch20_litmus.kv 65 }
ffa_ch20_litmus.kv 66
ffa_ch20_litmus.kv 67 # Failure in decoding 'GPG ASCII armour' :
ffa_ch20_litmus.kv 68 eggog_sig_armour() {
ffa_ch20_litmus.kv 69 echo "$SIGFILE could not decode as a GPG ASCII-Armoured Signature!" >&2
ffa_ch20_litmus.kv 70 exit $RET_EGGOG
ffa_ch20_litmus.kv 71 }
ffa_ch20_litmus.kv 72
ffa_ch20_litmus.kv 73 # Failure from corrupt signature :
ffa_ch20_litmus.kv 74 eggog_sig_corrupt() {
ffa_ch20_litmus.kv 75 echo "$SIGFILE is corrupt!" >&2
ffa_ch20_litmus.kv 76 exit $RET_EGGOG
ffa_ch20_litmus.kv 77 }
ffa_ch20_litmus.kv 78
ffa_ch20b_litmus_... 79 # If Sig was made with an unsupported hash algo:
ffa_ch20b_litmus_... 80 eggog_unsupported_hash() {
ffa_ch20b_litmus_... 81 algo=$1
ffa_ch20b_litmus_... 82 echo "This sig uses an unsupported Digest Algo: $1 !" >&2
ffa_ch20b_litmus_... 83 exit $RET_EGGOG
ffa_ch20b_litmus_... 84 }
ffa_ch20_litmus.kv 85
ffa_ch20c_litmus_... 86 # Malformed 'clearsigned' text file:
ffa_ch20c_litmus_... 87 eggog_broken_clearsigned() {
ffa_ch20c_litmus_... 88 echo "$SIGFILE does not contain a clearsigned PGP message!" >&2
ffa_ch20c_litmus_... 89 exit $RET_EGGOG
ffa_ch20c_litmus_... 90 }
ffa_ch20c_litmus_... 91
ffa_ch20_litmus.kv 92 # Failure from bad Peh :
ffa_ch20_litmus.kv 93 eggog_peh() {
ffa_ch20_litmus.kv 94 echo "EGGOG in executing Peh tape! Please check Public Key." >&2
ffa_ch20_litmus.kv 95 exit $RET_EGGOG
ffa_ch20_litmus.kv 96 }
ffa_ch20_litmus.kv 97
ffa_ch20b_litmus_... 98 # Warnings:
ffa_ch20b_litmus_... 99 achtung() {
ffa_ch20b_litmus_... 100 echo "WARNING: $1" >&2
ffa_ch20b_litmus_... 101 }
ffa_ch20b_litmus_... 102
ffa_ch20_litmus.kv 103
ffa_ch20c_litmus_... 104 # First argument is always the given public key file (a Peh tape, see docs)
ffa_ch20c_litmus_... 105 PUBFILE=$1
ffa_ch20c_litmus_... 106
ffa_ch20c_litmus_... 107 # The given Detached GPG Signature file to be verified.
ffa_ch20c_litmus_... 108 # In 'clearsigned' mode, contains both signature and payload to be verified.
ffa_ch20c_litmus_... 109 SIGFILE=$2
ffa_ch20_litmus.kv 110
ffa_ch20c_litmus_... 111 # Get total number of arguments on command line:
ffa_ch20c_litmus_... 112 ARGCOUNT="$#"
ffa_ch20c_litmus_... 113
ffa_ch20c_litmus_... 114 # On exit (if in 'clearsign' mode) :
ffa_ch20c_litmus_... 115 remove_temp_file() {
ffa_ch20c_litmus_... 116 rm -f $DATAFILE
ffa_ch20c_litmus_... 117 }
ffa_ch20c_litmus_... 118
ffa_ch20c_litmus_... 119 # Whether we are working on a 'clearsigned text'
ffa_ch20c_litmus_... 120 CLEARSIGN_MODE=false
ffa_ch20c_litmus_... 121
ffa_ch20c_litmus_... 122 # Set up in the selected mode:
ffa_ch20c_litmus_... 123 case $ARGCOUNT in
ffa_ch20c_litmus_... 124 2) # If given two arguments, verify a 'clearsigned' text file:
ffa_ch20c_litmus_... 125 CLEARSIGN_MODE=true
ffa_ch20c_litmus_... 126 # The processed payload will end up in a temporary file:
ffa_ch20c_litmus_... 127 DATAFILE=$(mktemp) || { echo "Failed to create temp file!" >&2; \
ffa_ch20c_litmus_... 128 exit $RET_EGGOG; }
ffa_ch20c_litmus_... 129 # On exit, if in 'clearsign' mode, remove temporary file with payload:
ffa_ch20c_litmus_... 130 trap remove_temp_file EXIT
ffa_ch20c_litmus_... 131 # Expect 'Canonical Text Signature' in GPG sig packet turd
ffa_ch20c_litmus_... 132 expect_sig_class=1
ffa_ch20c_litmus_... 133 ;;
ffa_ch20c_litmus_... 134 3) # Verify Detached Signature on given Data File (third argument is path):
ffa_ch20c_litmus_... 135 # The given Data file to be verified against the Signature
ffa_ch20c_litmus_... 136 DATAFILE=$3 # i.e. path given on command line
ffa_ch20c_litmus_... 137 # Expect 'Detached Binary Signature' in GPG sig packet turd
ffa_ch20c_litmus_... 138 expect_sig_class=0
ffa_ch20c_litmus_... 139 ;;
ffa_ch20c_litmus_... 140 *) # If invalid arg count -- print usage and abort:
ffa_ch20c_litmus_... 141 echo "Usage: $0 publickey.peh signature.sig datafile"
ffa_ch20c_litmus_... 142 echo " or: $0 publickey.peh clearsigned.txt"
ffa_ch20c_litmus_... 143 exit $RET_EGGOG
ffa_ch20c_litmus_... 144 ;;
ffa_ch20c_litmus_... 145 esac
ffa_ch20_litmus.kv 146
ffa_ch20_litmus.kv 147
ffa_ch20_litmus.kv 148 # Minimal Peh Width (used for non-arithmetical ops, e.g. 'Owner')
ffa_ch20_litmus.kv 149 MIN_PEH_WIDTH=256
ffa_ch20_litmus.kv 150
ffa_ch20c_litmus_... 151 # Peh data stack height for public key operations
ffa_ch20c_litmus_... 152 PEH_HEIGHT=3
ffa_ch20c_litmus_... 153
ffa_ch20b_litmus_... 154 # Peh RNG (NOT USED in verifications, but needed to silence warning)
ffa_ch20b_litmus_... 155 PEH_RNG_DEV="/dev/random"
ffa_ch20_litmus.kv 156
ffa_ch20_litmus.kv 157 # Verify that each of the given input files exists:
ffa_ch20_litmus.kv 158 FILES=($PUBFILE $SIGFILE $DATAFILE)
ffa_ch20_litmus.kv 159 for f in ${FILES[@]}; do
ffa_ch20_litmus.kv 160 if ! [ -f $f ]; then
ffa_ch20_litmus.kv 161 echo "$f does not exist!" >&2
ffa_ch20_litmus.kv 162 exit $RET_EGGOG
ffa_ch20_litmus.kv 163 fi
ffa_ch20_litmus.kv 164 done
ffa_ch20_litmus.kv 165
ffa_ch20_litmus.kv 166 # Calculate length of the pubkey file:
ffa_ch20_litmus.kv 167 PUBFILE_LEN=$(wc -c $PUBFILE | cut -d ' ' -f1)
ffa_ch20_litmus.kv 168
ffa_ch20_litmus.kv 169
ffa_ch20_litmus.kv 170 # Peh's Return Codes
ffa_ch20_litmus.kv 171 PEH_YES=1
ffa_ch20_litmus.kv 172 PEH_NO=0
ffa_ch20_litmus.kv 173 PEH_MU=255
ffa_ch20_litmus.kv 174 PEH_EGGOG=254
ffa_ch20_litmus.kv 175
ffa_ch20_litmus.kv 176 # Execute given Peh tape, with given FFA Width and Height,
ffa_ch20_litmus.kv 177 # on top of the pubkey tape; returns output in $peh_res and $peh_code.
ffa_ch20_litmus.kv 178 run_peh_tape() {
ffa_ch20_litmus.kv 179 # The tape itself
ffa_ch20_litmus.kv 180 tape=$1
ffa_ch20_litmus.kv 181
ffa_ch20_litmus.kv 182 # FFA Width for the tape
ffa_ch20_litmus.kv 183 peh_width=$2
ffa_ch20_litmus.kv 184
ffa_ch20_litmus.kv 185 # FFA Stack Height for the tape
ffa_ch20_litmus.kv 186 peh_height=$3
ffa_ch20_litmus.kv 187
ffa_ch20_litmus.kv 188 # Compute the length of the given tape
ffa_ch20_litmus.kv 189 tape_len=${#tape}
ffa_ch20_litmus.kv 190
ffa_ch20_litmus.kv 191 # Add the length of the Public Key tape to the above
ffa_ch20_litmus.kv 192 tape_len=$(($tape_len + $PUBFILE_LEN))
ffa_ch20_litmus.kv 193
ffa_ch20_litmus.kv 194 # Max Peh Life for all such tapes
ffa_ch20_litmus.kv 195 peh_life=$(($tape_len * 2))
ffa_ch20_litmus.kv 196
ffa_ch20_litmus.kv 197 # Execute the tape:
ffa_ch20_litmus.kv 198 peh_res=$((cat $PUBFILE; echo $tape) | \
ffa_ch20b_litmus_... 199 peh $peh_width $peh_height $tape_len $peh_life $PEH_RNG_DEV);
ffa_ch20_litmus.kv 200 peh_code=$?
ffa_ch20_litmus.kv 201
ffa_ch20_litmus.kv 202 # # If Peh returned PEH_EGGOG:
ffa_ch20_litmus.kv 203 if [ $peh_code -eq $PEH_EGGOG ]
ffa_ch20_litmus.kv 204 then
ffa_ch20_litmus.kv 205 # Abort: likely, coarse error of pilotage in the public key tape.
ffa_ch20_litmus.kv 206 eggog_peh
ffa_ch20_litmus.kv 207 fi
ffa_ch20_litmus.kv 208 }
ffa_ch20_litmus.kv 209
ffa_ch20b_litmus_... 210 # Ask the public key about Algo Type:
ffa_ch20_litmus.kv 211 run_peh_tape "@Algo!QY" $MIN_PEH_WIDTH 1
ffa_ch20_litmus.kv 212 pubkey_algo=$peh_res
ffa_ch20_litmus.kv 213
ffa_ch20b_litmus_... 214 # Ask the public key about the Owner:
ffa_ch20_litmus.kv 215 run_peh_tape "@Owner!QY" $MIN_PEH_WIDTH 1
ffa_ch20_litmus.kv 216 pubkey_owner=$peh_res
ffa_ch20_litmus.kv 217
ffa_ch20_litmus.kv 218 # The only supported algo is GPG RSA:
ffa_ch20_litmus.kv 219 if [ "$pubkey_algo" != "GPG RSA" ]
ffa_ch20_litmus.kv 220 then
ffa_ch20_litmus.kv 221 echo "This public key specifies algo '$pubkey_algo';" >&2
ffa_ch20_litmus.kv 222 echo "The only algo supported is 'GPG RSA' !" >&2
ffa_ch20_litmus.kv 223 exit $RET_EGGOG
ffa_ch20_litmus.kv 224 fi
ffa_ch20_litmus.kv 225
ffa_ch20_litmus.kv 226 # 'ASCII-Armoured' PGP signatures have mandatory start and end markers:
ffa_ch20d_litmus_... 227 START_MARKER="^\-\-\-\-\-BEGIN PGP SIGNATURE\-\-\-\-\-"
ffa_ch20d_litmus_... 228 END_MARKER="^\-\-\-\-\-END PGP SIGNATURE\-\-\-\-\-"
ffa_ch20_litmus.kv 229
ffa_ch20_litmus.kv 230 # Determine start and end line positions for payload:
ffa_ch20_litmus.kv 231 start_ln=$(grep -m 1 -n "$START_MARKER" $SIGFILE | cut -d ':' -f1)
ffa_ch20_litmus.kv 232 end_ln=$(grep -m 1 -n "$END_MARKER" $SIGFILE | cut -d ':' -f1)
ffa_ch20_litmus.kv 233
ffa_ch20_litmus.kv 234 # Both start and end markers must exist :
ffa_ch20_litmus.kv 235 if [ "$start_ln" == "" ] || [ "$end_ln" == "" ]
ffa_ch20_litmus.kv 236 then
ffa_ch20_litmus.kv 237 echo "$SIGFILE does not contain ASCII-armoured PGP Signature!" >&2
ffa_ch20_litmus.kv 238 exit $RET_EGGOG
ffa_ch20_litmus.kv 239 fi
ffa_ch20_litmus.kv 240
ffa_ch20_litmus.kv 241 # Discard the markers:
ffa_ch20_litmus.kv 242 start_ln=$(($start_ln + 1))
ffa_ch20_litmus.kv 243 end_ln=$(($end_ln - 1))
ffa_ch20_litmus.kv 244
ffa_ch20_litmus.kv 245 # If there is no payload, or the markers are misplaced, abort:
ffa_ch20_litmus.kv 246 if [ $start_ln -ge $end_ln ]
ffa_ch20_litmus.kv 247 then
ffa_ch20_litmus.kv 248 eggog_sig_armour
ffa_ch20_litmus.kv 249 fi
ffa_ch20_litmus.kv 250
ffa_ch20_litmus.kv 251 # Extract sig payload:
ffa_ch20_litmus.kv 252 sig_payload=$(sed -n "$start_ln,$end_ln p" < $SIGFILE | \
ffa_ch20_litmus.kv 253 sed -n "/^Version/!p" | sed -n "/^=/!p" | tr -d " \t\n\r")
ffa_ch20_litmus.kv 254
ffa_ch20_litmus.kv 255 # If eggog -- abort:
ffa_ch20_litmus.kv 256 if [ $? -ne 0 ]
ffa_ch20_litmus.kv 257 then
ffa_ch20_litmus.kv 258 eggog_sig_armour
ffa_ch20_litmus.kv 259 fi
ffa_ch20_litmus.kv 260
ffa_ch20_litmus.kv 261 # Obtain the sig bytes:
ffa_ch20_litmus.kv 262 sig_bytes=($(echo $sig_payload | base64 -d | hexdump -ve '1/1 "%.2x "'))
ffa_ch20_litmus.kv 263
ffa_ch20_litmus.kv 264 # If eggog -- abort:
ffa_ch20_litmus.kv 265 if [ $? -ne 0 ]
ffa_ch20_litmus.kv 266 then
ffa_ch20_litmus.kv 267 eggog_sig_armour
ffa_ch20_litmus.kv 268 fi
ffa_ch20_litmus.kv 269
ffa_ch20c_litmus_... 270 # If we are operating on a 'clearsigned' text file, $DATAFILE will be
ffa_ch20c_litmus_... 271 # an empty temporary file, and the payload is to be extracted to it,
ffa_ch20c_litmus_... 272 # with certain munges (see http://tools.ietf.org/html/rfc4880#section-7.1)
ffa_ch20c_litmus_... 273 if [ $CLEARSIGN_MODE == true ]
ffa_ch20c_litmus_... 274 then
ffa_ch20c_litmus_... 275 # Find position of 'clearsign' payload start marker:
ffa_ch20d_litmus_... 276 CLEAR_MARKER="^\-\-\-\-\-BEGIN PGP SIGNED MESSAGE\-\-\-\-\-"
ffa_ch20c_litmus_... 277 start_clr=$(grep -m 1 -n "$CLEAR_MARKER" $SIGFILE | cut -d ':' -f1)
ffa_ch20c_litmus_... 278
ffa_ch20c_litmus_... 279 # If payload start marker was not found:
ffa_ch20c_litmus_... 280 if [ "$start_clr" == "" ]
ffa_ch20c_litmus_... 281 then
ffa_ch20c_litmus_... 282 eggog_broken_clearsigned
ffa_ch20c_litmus_... 283 fi
ffa_ch20c_litmus_... 284
ffa_ch20c_litmus_... 285 # Discard the start marker:
ffa_ch20c_litmus_... 286 start_clr=$(($start_clr + 1))
ffa_ch20c_litmus_... 287
ffa_ch20c_litmus_... 288 # The payload ends with the line preceding the sig start:
ffa_ch20c_litmus_... 289 end_clr=$((start_ln - 2))
ffa_ch20c_litmus_... 290
ffa_ch20c_litmus_... 291 # Find any 'Hash:' headers:
ffa_ch20c_litmus_... 292 start_body=$(tail -n "+$start_clr" $SIGFILE | \
ffa_ch20c_litmus_... 293 grep -v -n -m 1 "^Hash:" | cut -d ':' -f1)
ffa_ch20c_litmus_... 294
ffa_ch20c_litmus_... 295 # Skip the above headers and mandatory empty line:
ffa_ch20c_litmus_... 296 start_clr=$(($start_clr + $start_body))
ffa_ch20c_litmus_... 297
ffa_ch20c_litmus_... 298 # If there is no payload, or the markers are misplaced, abort:
ffa_ch20c_litmus_... 299 if [ $start_clr -ge $end_clr ]
ffa_ch20c_litmus_... 300 then
ffa_ch20c_litmus_... 301 eggog_broken_clearsigned
ffa_ch20c_litmus_... 302 fi
ffa_ch20c_litmus_... 303
ffa_ch20c_litmus_... 304 # Extract the 'clearsign' payload to the temporary file:
ffa_ch20c_litmus_... 305 cat $SIGFILE | sed -n "$start_clr,$end_clr p" | \
ffa_ch20c_litmus_... 306 sed 's/[ \t]*$//; s/^- //' | \
ffa_ch20c_litmus_... 307 awk '{printf("%s\r\n",$0)}' \
ffa_ch20c_litmus_... 308 > $DATAFILE
ffa_ch20c_litmus_... 309
ffa_ch20c_litmus_... 310 # Remove the trailing CR,LF ending:
ffa_ch20c_litmus_... 311 truncate -s -2 $DATAFILE
ffa_ch20c_litmus_... 312
ffa_ch20c_litmus_... 313 # After this, proceed exactly like with 'detached' sigs, but
ffa_ch20c_litmus_... 314 # with the expected 'class' being 1 rather than 0.
ffa_ch20c_litmus_... 315 fi
ffa_ch20c_litmus_... 316
ffa_ch20c_litmus_... 317
ffa_ch20_litmus.kv 318 # Number of bytes in the sig file
ffa_ch20_litmus.kv 319 sig_len=${#sig_bytes[@]}
ffa_ch20_litmus.kv 320
ffa_ch20_litmus.kv 321
ffa_ch20_litmus.kv 322 # Test that certain fields in the Sig have their mandatory value
ffa_ch20_litmus.kv 323 sig_field_mandatory() {
ffa_ch20_litmus.kv 324 f_name=$1
ffa_ch20_litmus.kv 325 f_value=$2
ffa_ch20_litmus.kv 326 f_mandate=$3
ffa_ch20_litmus.kv 327 if [ "$f_value" != "$f_mandate" ]
ffa_ch20_litmus.kv 328 then
ffa_ch20_litmus.kv 329 reason="$f_name must equal $f_mandate; instead is $f_value."
ffa_ch20_litmus.kv 330 echo "$SIGFILE is UNSUPPORTED : $reason" >&2
ffa_ch20_litmus.kv 331 exit $RET_EGGOG
ffa_ch20_litmus.kv 332 fi
ffa_ch20_litmus.kv 333 }
ffa_ch20_litmus.kv 334
ffa_ch20_litmus.kv 335
ffa_ch20_litmus.kv 336 # Starting Position for get_sig_bytes()
ffa_ch20_litmus.kv 337 sig_pos=0
ffa_ch20_litmus.kv 338
ffa_ch20_litmus.kv 339 # Extract given # of sig bytes from the current sig_pos; advance sig_pos.
ffa_ch20_litmus.kv 340 get_sig_bytes() {
ffa_ch20_litmus.kv 341 # Number of bytes requested
ffa_ch20_litmus.kv 342 count=$1
ffa_ch20_litmus.kv 343
ffa_ch20_litmus.kv 344 # Result: $count bytes from current $sig_pos (contiguous hex string)
ffa_ch20_litmus.kv 345 r=$(echo ${sig_bytes[@]:$sig_pos:$count} | sed "s/ //g" | tr 'a-z' 'A-Z')
ffa_ch20_litmus.kv 346
ffa_ch20_litmus.kv 347 # Advance $sig_pos by $count:
ffa_ch20_litmus.kv 348 sig_pos=$(($sig_pos + $count))
ffa_ch20_litmus.kv 349
ffa_ch20_litmus.kv 350 # If more bytes were requested than were available in sig_bytes:
ffa_ch20_litmus.kv 351 if [ $sig_pos -gt $sig_len ]
ffa_ch20_litmus.kv 352 then
ffa_ch20_litmus.kv 353 # Abort. The signature was mutilated somehow.
ffa_ch20_litmus.kv 354 eggog_sig_corrupt
ffa_ch20_litmus.kv 355 fi
ffa_ch20_litmus.kv 356 }
ffa_ch20_litmus.kv 357
ffa_ch20_litmus.kv 358 # Convert the current sig component to integer
ffa_ch20_litmus.kv 359 hex_to_int() {
ffa_ch20_litmus.kv 360 r=$((16#$r))
ffa_ch20_litmus.kv 361 }
ffa_ch20_litmus.kv 362
ffa_ch20_litmus.kv 363 # Turd to be composed of certain values from the sig, per RFC4880.
ffa_ch20_litmus.kv 364 # Final hash will run on the concatenation of DATAFILE and this turd.
ffa_ch20_litmus.kv 365 turd=""
ffa_ch20_litmus.kv 366
ffa_ch20_litmus.kv 367 ## Parse all of the necessary fields in the GPG Signature:
ffa_ch20_litmus.kv 368
ffa_ch20_litmus.kv 369 # CTB (must equal 0x89)
ffa_ch20_litmus.kv 370 get_sig_bytes 1
ffa_ch20_litmus.kv 371 sig_ctb=$r
ffa_ch20_litmus.kv 372 sig_field_mandatory "Version" $sig_ctb 89
ffa_ch20_litmus.kv 373
ffa_ch20_litmus.kv 374 # Length
ffa_ch20_litmus.kv 375 get_sig_bytes 2
ffa_ch20_litmus.kv 376 hex_to_int
ffa_ch20_litmus.kv 377 sig_length=$r
ffa_ch20_litmus.kv 378
ffa_ch20_litmus.kv 379 # Version (only Version 4 -- what GPG 1.4.x outputs -- is supported)
ffa_ch20_litmus.kv 380 get_sig_bytes 1
ffa_ch20_litmus.kv 381 turd+=$r
ffa_ch20_litmus.kv 382 sig_version=$r
ffa_ch20_litmus.kv 383 sig_field_mandatory "Version" $sig_version 04
ffa_ch20_litmus.kv 384
ffa_ch20c_litmus_... 385 # Class (must be 'detached' or 'clearsign')
ffa_ch20_litmus.kv 386 get_sig_bytes 1
ffa_ch20_litmus.kv 387 turd+=$r
ffa_ch20c_litmus_... 388 hex_to_int
ffa_ch20_litmus.kv 389 sig_class=$r
ffa_ch20c_litmus_... 390 sig_field_mandatory "Class" $sig_class $expect_sig_class
ffa_ch20_litmus.kv 391
ffa_ch20_litmus.kv 392 # Public Key Algo (only RSA is supported)
ffa_ch20_litmus.kv 393 get_sig_bytes 1
ffa_ch20_litmus.kv 394 turd+=$r
ffa_ch20_litmus.kv 395 sig_pk_algo=$r
ffa_ch20_litmus.kv 396 sig_field_mandatory "Public Key Algo" $sig_pk_algo 01
ffa_ch20_litmus.kv 397
ffa_ch20b_litmus_... 398 # Digest Algo (only certain hash algos are supported)
ffa_ch20_litmus.kv 399 get_sig_bytes 1
ffa_ch20_litmus.kv 400 turd+=$r
ffa_ch20b_litmus_... 401 hex_to_int
ffa_ch20_litmus.kv 402 sig_digest_algo=$r
ffa_ch20b_litmus_... 403
ffa_ch20b_litmus_... 404 # If hash algo is supported, get ASN turd and MD_LEN; and if not, eggog:
ffa_ch20b_litmus_... 405 case $sig_digest_algo in
ffa_ch20b_litmus_... 406 1) ## MD5 -- NOT SUPPORTED ##
ffa_ch20b_litmus_... 407 eggog_unsupported_hash "MD5"
ffa_ch20b_litmus_... 408 ;;
ffa_ch20b_litmus_... 409
ffa_ch20b_litmus_... 410 2) ## SHA1 ##
ffa_ch20b_litmus_... 411 achtung "This sig was made with SHA-1, which is cheaply breakable!"
ffa_ch20b_litmus_... 412 achtung "Please contact the signer ($pubkey_owner) !"
ffa_ch20b_litmus_... 413 HASHER="shasum -a 1 -b"
ffa_ch20b_litmus_... 414 ASN="3021300906052b0e03021a05000414"
ffa_ch20b_litmus_... 415 MD_LEN=20
ffa_ch20b_litmus_... 416 ;;
ffa_ch20b_litmus_... 417
ffa_ch20b_litmus_... 418 3) ## RIPE-MD/160 -- NOT SUPPORTED ##
ffa_ch20b_litmus_... 419 eggog_unsupported_hash "RIPE-MD/160"
ffa_ch20b_litmus_... 420 ;;
ffa_ch20b_litmus_... 421
ffa_ch20b_litmus_... 422 8) ## SHA256 ##
ffa_ch20b_litmus_... 423 achtung "This sig was made with SHA-256; GPG supports SHA-512."
ffa_ch20b_litmus_... 424 achtung "Please contact the signer ($pubkey_owner) !"
ffa_ch20b_litmus_... 425 HASHER="shasum -a 256 -b"
ffa_ch20b_litmus_... 426 ASN="3031300d060960864801650304020105000420"
ffa_ch20b_litmus_... 427 MD_LEN=32
ffa_ch20b_litmus_... 428 ;;
ffa_ch20b_litmus_... 429
ffa_ch20b_litmus_... 430 9) ## SHA384 ##
ffa_ch20b_litmus_... 431 achtung "This sig was made with SHA-384; GPG supports SHA-512."
ffa_ch20b_litmus_... 432 achtung "Please contact the signer ($pubkey_owner) !"
ffa_ch20b_litmus_... 433 HASHER="shasum -a 384 -b"
ffa_ch20b_litmus_... 434 ASN="3041300d060960864801650304020205000430"
ffa_ch20b_litmus_... 435 MD_LEN=48
ffa_ch20b_litmus_... 436 ;;
ffa_ch20b_litmus_... 437
ffa_ch20b_litmus_... 438 10) ## SHA512 ##
ffa_ch20b_litmus_... 439 HASHER="shasum -a 512 -b"
ffa_ch20b_litmus_... 440 ASN="3051300D060960864801650304020305000440"
ffa_ch20b_litmus_... 441 MD_LEN=64 # 512 / 8 == 64 bytes
ffa_ch20b_litmus_... 442 ;;
ffa_ch20b_litmus_... 443
ffa_ch20b_litmus_... 444 11) ## SHA224 ##
ffa_ch20b_litmus_... 445 achtung "This sig was made with SHA-224; GPG supports SHA-512."
ffa_ch20b_litmus_... 446 achtung "Please contact the signer ($pubkey_owner) !"
ffa_ch20b_litmus_... 447 HASHER="shasum -a 224 -b"
ffa_ch20b_litmus_... 448 ASN="302D300d06096086480165030402040500041C"
ffa_ch20b_litmus_... 449 MD_LEN=28
ffa_ch20b_litmus_... 450 ;;
ffa_ch20b_litmus_... 451
ffa_ch20b_litmus_... 452 *) ## Unknown Digest Type ##
ffa_ch20b_litmus_... 453 eggog_unsupported_hash "UNKNOWN (type $sig_digest_algo)"
ffa_ch20b_litmus_... 454 ;;
ffa_ch20b_litmus_... 455 esac
ffa_ch20b_litmus_... 456
ffa_ch20b_litmus_... 457 # Calculate length (bytes) of the ASN turd for the digest used in the sig:
ffa_ch20b_litmus_... 458 ASN_LEN=$((${#ASN} / 2))
ffa_ch20b_litmus_... 459
ffa_ch21a_ter_ch1... 460 # Read the hashed and unhashed sections :
ffa_ch20_litmus.kv 461
ffa_ch20_litmus.kv 462 # Hashed Section Length
ffa_ch20_litmus.kv 463 get_sig_bytes 2
ffa_ch20_litmus.kv 464 turd+=$r
ffa_ch20_litmus.kv 465 hex_to_int
ffa_ch20_litmus.kv 466 sig_hashed_len=$r
ffa_ch20_litmus.kv 467
ffa_ch20_litmus.kv 468 # Hashed Section (typically: timestamp)
ffa_ch20_litmus.kv 469 get_sig_bytes $sig_hashed_len
ffa_ch20_litmus.kv 470 turd+=$r
ffa_ch20_litmus.kv 471 sig_hashed=$r
ffa_ch20_litmus.kv 472
ffa_ch20_litmus.kv 473 # Unhashed Section Length
ffa_ch21a_ter_ch1... 474 get_sig_bytes 2
ffa_ch20_litmus.kv 475 hex_to_int
ffa_ch20_litmus.kv 476 sig_unhashed_len=$r
ffa_ch20_litmus.kv 477
ffa_ch20_litmus.kv 478 # Unhashed Section (discard)
ffa_ch20_litmus.kv 479 get_sig_bytes $sig_unhashed_len
ffa_ch20_litmus.kv 480
ffa_ch20_litmus.kv 481 # Compute Byte Length of Hashed Header (for last field)
ffa_ch20_litmus.kv 482 hashed_header_len=$((${#turd} / 2))
ffa_ch20_litmus.kv 483
ffa_ch20_litmus.kv 484 # Final section of the hashed turd (not counted in hashed_header_len)
ffa_ch20_litmus.kv 485 turd+=$sig_version
ffa_ch20_litmus.kv 486 turd+="FF"
ffa_ch20_litmus.kv 487 turd+=$(printf "%08x" $hashed_header_len)
ffa_ch20_litmus.kv 488
ffa_ch20_litmus.kv 489 # Compute the hash of data file and the hashed appendix from sig :
ffa_ch20_litmus.kv 490 hash=$((cat $DATAFILE; xxd -r -p <<< $turd) | $HASHER | cut -d ' ' -f1)
ffa_ch20_litmus.kv 491 # Convert to upper case
ffa_ch20_litmus.kv 492 hash=$(echo $hash | tr 'a-z' 'A-Z')
ffa_ch20_litmus.kv 493
ffa_ch21a_ter_ch1... 494 # Read and validate digest prefix :
ffa_ch20_litmus.kv 495
ffa_ch20_litmus.kv 496 # Digest Prefix (2 bytes)
ffa_ch20_litmus.kv 497 get_sig_bytes 2
ffa_ch20_litmus.kv 498 digest_prefix=$r
ffa_ch20_litmus.kv 499
ffa_ch20_litmus.kv 500 # See whether it matches the first two bytes of the actual computed hash :
ffa_ch20_litmus.kv 501 computed_prefix=$(printf "%.4s" $hash)
ffa_ch20_litmus.kv 502
ffa_ch20_litmus.kv 503 if [ "$digest_prefix" != "$computed_prefix" ]
ffa_ch20_litmus.kv 504 then
ffa_ch20_litmus.kv 505 # It didn't match, so we can return 'bad signature' immediately:
ffa_ch20_litmus.kv 506 done_sig_bad
ffa_ch20_litmus.kv 507 fi
ffa_ch20_litmus.kv 508
ffa_ch20_litmus.kv 509 # If prefix matched, we will proceed to do the actual RSA operation.
ffa_ch20_litmus.kv 510
ffa_ch20_litmus.kv 511 # RSA Bitness given in Sig
ffa_ch20_litmus.kv 512 get_sig_bytes 2
ffa_ch20_litmus.kv 513 hex_to_int
ffa_ch20_litmus.kv 514 rsa_bitness=$r
ffa_ch20_litmus.kv 515
ffa_ch20_litmus.kv 516 # Compute RSA Byteness from the above
ffa_ch20_litmus.kv 517 rsa_byteness=$((($rsa_bitness + 7) / 8))
ffa_ch20_litmus.kv 518
ffa_ch20_litmus.kv 519 # RSA Bitness for use in determining required Peh width:
ffa_ch20_litmus.kv 520 rsa_width=$(($rsa_byteness * 8))
ffa_ch20_litmus.kv 521
ffa_ch20_litmus.kv 522 # Only traditional GPG RSA widths are supported:
ffa_ch20_litmus.kv 523 if [ $rsa_width != 2048 ] && [ $rsa_width != 4096 ] && [ $rsa_width != 8192 ]
ffa_ch20_litmus.kv 524 then
ffa_ch20_litmus.kv 525 reason="Only 2048, 4096, and 8192-bit RSA are supported."
ffa_ch20_litmus.kv 526 echo "$SIGFILE is UNSUPPORTED : $reason" >&2
ffa_ch20_litmus.kv 527 exit $RET_EGGOG
ffa_ch20_litmus.kv 528 fi
ffa_ch20_litmus.kv 529
ffa_ch20_litmus.kv 530 # RSA Signature per se (final item read from sig file)
ffa_ch20_litmus.kv 531 get_sig_bytes $rsa_byteness
ffa_ch20_litmus.kv 532 rsa_sig=$r
ffa_ch20_litmus.kv 533
ffa_ch20_litmus.kv 534 # Per RFC4880, 'PKCS' encoding of hash is as follows:
ffa_ch20_litmus.kv 535 # 0 1 [PAD] 0 [ASN] [MD]
ffa_ch20_litmus.kv 536
ffa_ch20_litmus.kv 537 # First two bytes of PKCS-encoded hash will always be 00 01 :
ffa_ch20_litmus.kv 538 pkcs="0001"
ffa_ch20_litmus.kv 539
ffa_ch20_litmus.kv 540 # Compute necessary number of padding FF bytes :
ffa_ch20_litmus.kv 541 pkcs_pad_bytes=$(($rsa_byteness - $MD_LEN - $ASN_LEN - 3))
ffa_ch20_litmus.kv 542
ffa_ch20_litmus.kv 543 # Attach the padding bytes:
ffa_ch20_litmus.kv 544 for ((x=1; x<=$pkcs_pad_bytes; x++)); do
ffa_ch20_litmus.kv 545 pkcs+="FF"
ffa_ch20_litmus.kv 546 done
ffa_ch20_litmus.kv 547
ffa_ch20_litmus.kv 548 # Attach the 00 separator between the padding and the ASN:
ffa_ch20_litmus.kv 549 pkcs+="00"
ffa_ch20_litmus.kv 550
ffa_ch20_litmus.kv 551 # Attach the ASN ('magic' corresponding to the hash algo) :
ffa_ch20_litmus.kv 552 pkcs+=$ASN
ffa_ch20_litmus.kv 553
ffa_ch20_litmus.kv 554 # Finally, attach the computed (from Data file) hash itself :
ffa_ch20_litmus.kv 555 pkcs+=$hash
ffa_ch20_litmus.kv 556
ffa_ch20_litmus.kv 557 # Generate a Peh tape which will attempt to verify $rsa_sig against the pubkey,
ffa_ch20_litmus.kv 558 # computing the expression $rsa_sig ^ PUB_E mod PUB_M and comparing to $pkcs.
ffa_ch20_litmus.kv 559 # Outputs 'Valid' and returns Yes_Code (1) if and only if signature is valid.
ffa_ch20_litmus.kv 560 tape=".$rsa_sig@Public-Op!.$pkcs={[Valid]QY}{[Invalid]QN}_"
ffa_ch20_litmus.kv 561
ffa_ch20_litmus.kv 562 # Execute the tape:
ffa_ch20c_litmus_... 563 run_peh_tape $tape $rsa_width $PEH_HEIGHT
ffa_ch20_litmus.kv 564
ffa_ch20_litmus.kv 565 # 'Belt and suspenders' -- test both output and return code:
ffa_ch20_litmus.kv 566 # If verification succeeded, return code will be 1, and output 'Valid':
ffa_ch20_litmus.kv 567 if [ $peh_code -eq $PEH_YES ] && [ "$peh_res" == "Valid" ]
ffa_ch20_litmus.kv 568 then
ffa_ch20_litmus.kv 569 # Valid RSA signature:
ffa_ch20_litmus.kv 570 done_sig_valid
ffa_ch20_litmus.kv 571 else
ffa_ch20_litmus.kv 572 # Signature was not valid:
ffa_ch20_litmus.kv 573 done_sig_bad
ffa_ch20_litmus.kv 574 fi
ffa_ch20_litmus.kv 575 # The end.