1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
|
#!/bin/sh
# Test the Stateless OpenPGP implementation Verification-only subset.
# This needs to be run from within a directory created by the
# setup-sopv-test script.
# https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/
# Author: Daniel Kahn Gillmor
# License: CC-0
SOPV=$1
if [ -z "$SOPV" ]; then
cat >&2 <<EOF
Usage: $0 SOPV
SOPV should refer (either by \$PATH or by absolute path) to an
implementation of the Stateless OpenPGP Verification-only subset.
See https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/
EOF
exit 1
fi
sopv() {
local suffix=""
if [ -n "$SIN" ]; then
suffix=" < $SIN"
fi
if [ -n "$SOUT" ]; then
suffix="$suffix > $SOUT"
fi
if [ -n "$FD_3" ]; then
suffix="$suffix 3< $FD_3"
fi
if [ -n "$FD_4" ]; then
suffix="$suffix 4< $FD_4"
fi
if [ -n "$FD_5" ]; then
suffix="$suffix 5< $FD_5"
fi
# FD 9 is used for output, not input
if [ -n "$FD_9" ]; then
suffix="$suffix 9> $FD_9"
fi
local missing=""
printf "π [%s %s%s]\n" "$SOPV" "$*" "$suffix"
if ! ( if [ -n "$SIN" ]; then exec < "$SIN"; fi;
if [ -n "$SOUT" ]; then exec > "$SOUT"; fi;
if [ -n "$FD_3" ]; then exec 3< "$FD_3"; fi;
if [ -n "$FD_4" ]; then exec 4< "$FD_4"; fi;
if [ -n "$FD_5" ]; then exec 5< "$FD_5"; fi;
if [ -n "$FD_9" ]; then exec 9> "$FD_9"; fi;
$SOPV "$@") ; then
printf "π£ Failed: %s%s\n" "$*" "$suffix"
rm -f "$SOUT"
ERRORS="$ERRORS
$*$suffix"
return 1
else
PASSCOUNT=$(( $PASSCOUNT + 1 ))
return 0
fi
}
sopv_fail() {
local suffix=""
if [ -n "$SIN" ]; then
suffix=" < $SIN"
fi
if [ -n "$SOUT" ]; then
printf 'ERROR: do not call sopv_fail and expect stdout\n'
exit 1
fi
if [ -n "$FD_3" ]; then
suffix="$suffix 3< $FD_3"
fi
if [ -n "$FD_4" ]; then
suffix="$suffix 4< $FD_4"
fi
if [ -n "$FD_5" ]; then
suffix="$suffix 5< $FD_5"
fi
# FD 9 is used for output, not input
if [ -n "$FD_9" ]; then
suffix="$suffix 9> $FD_9"
fi
local missing=""
printf "πβ [%s %s%s]\n" "$SOPV" "$*" "$suffix"
if ( if [ -n "$SIN" ]; then exec < "$SIN"; fi;
if [ -n "$FD_3" ]; then exec 3< "$FD_3"; fi;
if [ -n "$FD_4" ]; then exec 4< "$FD_4"; fi;
if [ -n "$FD_5" ]; then exec 5< "$FD_5"; fi;
if [ -n "$FD_9" ]; then exec 9> "$FD_9"; fi;
$SOPV "$@" > fail.out); then
printf >&2 "π£ succeeded when it should have failed: %s%s\n" \
"$*" "$suffix"
ERRORS="$ERRORS
! $*$suffix"
else
if [ -s fail.out ]; then
printf >&2 "π£ produced material to stdout: %s%s\n" \
"$*" "$suffix"
sed 's/^/ π£> /' < fail.out >&2
ERRORS="$ERRORS
! $*$suffix β PRODUCED OUTPUTβ "
else
PASSCOUNT=$(( $PASSCOUNT + 1 ))
fi
fi
rm -f fail.out
}
compare() {
local args=""
if [ "$1" = text -o "$1" = clearsigned ]; then
args=--ignore-trailing-space
fi
comptype="$1"
shift
if ! [ -r "$1" -a -r "$2" ]; then
printf "β
skipped %s cmp (missing inputs): %s and %s\n" \
"$comptype" "$1" "$2"
SKIPCOUNT=$(( $SKIPCOUNT + 1 ))
return
fi
if diff --unified $args "$1" "$2"; then
printf "π %s and %s match!\n" "$1" "$2"
PASSCOUNT=$(( $PASSCOUNT + 1 ))
else
printf " π£ %s and %s do not match!\n" "$1" "$2"
ERRORS="$ERRORS
Mismatch ($*)"
fi
}
reject_output() {
for f in "$@"; do
if [ -s "$f" ]; then
printf "π£ %s should not exist with content!\n" "$f"
ERRORS="$ERRORS
Should-not-exist $f"
else
PASSCOUNT=$(( $PASSCOUNT + 1 ))
fi
done
}
confirm_mode() {
local foundmode=''
for m in $(cut -f4 -d\ < "$2"); do
if [ "$m" != "mode:$1" ]; then
printf "π£ %s should have mentioned mode:%s, was %s!\n" \
"$2" "$1" "$m"
ERRORS="$ERRORS
VERIFICATIONS-bad-mode $2 (was: $m; wanted mode:$1)"
else
foundmode=yes
fi
done
if [ -z "$foundmode" ]; then
printf "π£ %s had no mode, wanted %s!\n" "$2" "$1"
ERRORS="$ERRORS
VERIFICATIONS-no-mode $2 (wanted mode:$1)"
else
PASSCOUNT=$(( $PASSCOUNT + 1 ))
fi
}
show_errs() {
if [ -z "$1" ]; then
if [ 0 -ne $SKIPCOUNT ]; then
printf "No errors, %d tests passed.\n"
printf "but %d tests skipped somehow\n" \
$PASSCOUNT $SKIPCOUNT
else
printf "No errors! %d tests passed\n" $PASSCOUNT
fi
else
local SKIPMSG=''
if [ 0 -ne $SKIPCOUNT ]; then
SKIPMSG=$(printf "%d tests skipped due to prior errors" \
$SKIPCOUNT)
fi
cat <<EOF
$PASSCOUNT tests passed.
$SKIPMSG
=== ERRORS ===
$1
=== Error summary ===
EOF
E=$(echo "$1" | grep -v '^$')
printf "%d Errors:\n" $(echo "$E" | wc -l)
echo "$E" | sed 's/^! //' | cut -f1 -d\ | sort | uniq -c
fi
}
ERRORS=""
SKIPCOUNT=0
PASSCOUNT=0
combine() {
# runners take: sopv|sopv_fail signer cert [cert...]
local runner=$1
shift
$runner sopv alice alice.$cert
$runner sopv bob bob.$cert
$runner sopv_fail bob alice.$cert
$runner sopv_fail alice bob.$cert
$runner sopv both alice.$cert
$runner sopv both bob.$cert
$runner sopv both both.$cert
$runner sopv alice both.$cert
$runner sopv bob both.$cert
$runner sopv alice alice.$cert bob.$cert
$runner sopv alice bob.$cert alice.$cert
$runner sopv bob alice.$cert bob.$cert
$runner sopv bob bob.$cert alice.$cert
FD_3=alice.$cert $runner sopv alice @FD:3
FD_3=bob.$cert FD_4=alice.$cert $runner sopv alice @FD:3 @FD:4
# don't try to test @ENV on non-armored certs
if [ "$cert" = "cert" ]; then
SIGNER_CERT=$(cat alice.$cert) $runner sopv \
alice @ENV:SIGNER_CERT
fi
}
detached() {
local sopv=$1
shift
local signer=$1
shift
SIN=msg.$form $sopv verify $delim msg.$form.$signer.$sig "$@"
FD_5=msg.$form.$signer.$sig SIN=msg.$form $sopv verify $delim \
@FD:5 "$@"
# don't try to test @ENV on non-armored signatures
if [ "$sig" = "sig" ]; then
SIGNATURE=$(cat msg.$form.$signer.$sig) SIN=msg.$form $sopv \
verify $delim @ENV:SIGNATURE "$@"
fi
}
inlinesigned() {
local sopv=$1
shift
local signer=$1
shift
local vout=msg.$form.$signer.$inlmsg.verifs
rm -f "$vout"
if [ "$sopv" = sopv ]; then
if SIN=msg.$form.$signer.$inlmsg \
SOUT=msg.$form.$signer.$inlmsg.out $sopv \
inline-verify --verifications-out=$vout \
$delim "$@" ; then
confirm_mode "$form" "$vout"
fi
if FD_9=$vout.fd SIN=msg.$form.$signer.$inlmsg \
SOUT=msg.$form.$signer.$inlmsg.out.fd \
$sopv inline-verify \
--verifications-out=@FD:9 \
$delim "$@" ; then
confirm_mode "$form" "$vout.fd"
fi
compare $form msg.$form msg.$form.$signer.$inlmsg.out
compare binary msg.$form.$signer.$inlmsg.out \
msg.$form.$signer.$inlmsg.out.fd
rm -f msg.$form.$signer.$inlmsg.out $vout \
msg.$form.$signer.$inlmsg.out.fd $vout.fd
# inlinesigned msgs can't be used as detached signatures:
SIN=msg.$form sopv_fail verify $delim \
msg.$form.$signer.$inlmsg "$@"
else
SIN=msg.$form.$signer.$inlmsg $sopv inline-verify \
--verifications-out=$vout \
$delim "$@"
FD_9=$vout.fd SIN=msg.$form.$signer.$inlmsg $sopv \
inline-verify --verifications-out=@FD:9 \
$delim "$@"
reject_output $vout $vout.fd
fi
}
sopv version --extended
sopv version --sopv
for delim in '' --; do
for cert in cert cert.bin; do
for form in binary text; do
# test detached signature
for sig in sig sig.bin; do
combine detached
done
# test inline-signed messages
for inlmsg in inlinesigned inlinesigned.bin; do
combine inlinesigned
done
done
# test CSF
form=text inlmsg=csf combine inlinesigned
done
done
sopv verify valid-from-expired.sig expired.cert < msg.binary
sopv_fail verify invalid-from-expired.sig expired.cert < msg.binary
sopv_fail verify timetravel-from-expired.sig expired.cert < msg.binary
sopv verify baseline.sig baseline.cert < msg.binary
sopv_fail verify baseline.sig baseline-revoked.cert < msg.binary
# FIXME:
#
# - --not-before and --not-after
# - JSON extension to VERIFICATIONS, including "signers" (sopv 1.1)
# - using --argument=foo vs. --argument foo ?
# - review equivalence of VERIFICATIONS
# - confirm failure when --verifications-out already exists
# - passing CERTS where SIGNATURES are expected MUST fail
# - passing KEYS where CERTS are expected MUST fail
show_errs "$ERRORS"
# Succeed only if $ERRORS is empty:
[ -z "$ERRORS" ]
|