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