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