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