#!/bin/bash # Copyright (c) 2012 Peter Palfrader # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. set -e set -u usage() { echo "Usage: $0 [-v [-v]] [-d ] []" echo " If torserver-fpr is -, a list of fingerprints is read from stdin" } verbose=0 datadir="" while getopts "vhd:" OPTION do case "$OPTION" in v) verbose=$((verbose + 1)) ;; h) usage exit 0 ;; d) datadir="$OPTARG" ;; *) usage >&2 exit 1 esac done shift $(($OPTIND - 1)) if [ "${1:-}" = "--help" ]; then usage exit 0 elif [ "$#" -lt 2 ]; then usage >&2 exit 1 fi torserver="$1"; shift hostname="$1"; shift port="${1:-443}" socksport=$((RANDOM % 40000 + 20000)) mapaddr="192.0.2.1" logpid="" torpid="" tmpdir="" cleanup() { [ -z "$torpid" ] || kill "$torpid" || true [ -z "$logpid" ] || kill "$logpid" || true [ -z "$tmpdir" ] || rm -rf "$tmpdir" || true } tmpdir=$(mktemp -d "/tmp/cert-check-XXXXXX") trap 'cleanup' EXIT pidfile="$tmpdir/pid" torlog="$tmpdir/log" if [ "$verbose" -gt 0 ]; then tail -F "$torlog" & logpid=$! fi if command -v tor > /dev/null; then tor="tor" elif [ -x /usr/sbin/tor ]; then tor="/usr/sbin/tor" else echo >&2 "Cannot find tor executable" exit 1 fi if [ "$verbose" -gt 1 ]; then loglevel="info" else loglevel="notice" fi datadir=${datadir:-$tmpdir/tor} cat > "$tmpdir/torrc" << EOF DataDirectory $datadir RunAsDaemon 1 SocksPort $socksport PidFile $pidfile Log $loglevel file $torlog SafeLogging 0 # MapAddress $mapaddr $hostname.\$$torserver.exit ControlSocket $tmpdir/sock StrictNodes 1 EOF cat > "$tmpdir/torsocks.conf" << EOF server = 127.0.0.1 server_port = $socksport EOF mkdir -p -m 0700 "$datadir" if [ "$verbose" -gt 0 ]; then hush=""; else hush="--hush"; fi "$tor" $hush -f "$tmpdir/torrc" torpid="$(cat $pidfile)" eatdata() { local fn="$1"; shift if [ "$verbose" -gt 0 ]; then tee "$fn" else cat > "$fn" fi } expect_ok() { local line read code line <&${COPROC[0]} line=$(echo "$line" | tr -d '\r') if ! [ "$code" = "250" ]; then echo >&2 "Tor said '$code $line' - no idea what that means" exit 1 fi } [ "$verbose" = 0 ] || echo "Directly:" openssl s_client -no_ticket -showcerts -connect "$hostname":"$port" < /dev/null 2>&1 | eatdata "$tmpdir/cert-direct" egrep -v '(Session-ID|Master-Key|Start Time):' < "$tmpdir/cert-direct" > "$tmpdir/cert-direct.filtered" [ "$verbose" = 0 ] || echo "====" coproc socat UNIX-CONNECT:"$tmpdir/sock" - echo 'AUTHENTICATE' >&${COPROC[1]} expect_ok errors=0 while : ; do if [ "$torserver" = "-" ]; then read server [ -n "$server" ] || break else server="$torserver" fi server="${server// /}" # de-base64 if needed if [ "$(echo "$server" | wc -c)" = 28 ] ; then server="$(echo "$server" | perl -MMIME::Base64 -e 'print unpack("H*", decode_base64(<>))')" fi [ "$verbose" = 0 ] || echo "Setting ExitNodes $server" echo "RESETCONF ExitNodes" >&${COPROC[1]} expect_ok echo "SETCONF ExitNodes=\$$server" >&${COPROC[1]} expect_ok rm -f "$tmpdir/cert-tor" "$tmpdir/cert-tor.filtered" [ "$verbose" = 0 ] || echo "Via $server:" TORSOCKS_CONF_FILE="$tmpdir/torsocks.conf" torify openssl s_client -no_ticket -showcerts -connect "$hostname":"$port" < /dev/null 2>&1 | eatdata "$tmpdir/cert-tor" egrep -v '(Session-ID|Master-Key|Start Time):' < "$tmpdir/cert-tor" > "$tmpdir/cert-tor.filtered" if diff "$tmpdir/cert-tor.filtered" "$tmpdir/cert-direct.filtered" > /dev/null; then echo "RESULT: $server: No real differences." [ "$verbose" = 0 ] || diff -U100 "$tmpdir/cert-tor" "$tmpdir/cert-direct" || true elif egrep '^connect:errno=' "$tmpdir/cert-tor" > /dev/null; then [ "$verbose" -lt 1 ] || diff -U100 "$tmpdir/cert-tor" "$tmpdir/cert-direct" || true echo "RESULT: $server: Connect failed" errors=$((errors | 0x02)) elif egrep '^[0-9]*:error:.*:ssl handshake failure:' "$tmpdir/cert-tor" > /dev/null; then [ "$verbose" -lt 1 ] || diff -U100 "$tmpdir/cert-tor" "$tmpdir/cert-direct" || true echo "RESULT: $server: SSL Handshake failed" errors=$((errors | 0x04)) else echo "RESULT: $server: differences!" [ "$verbose" = 0 ] || echo "====" [ "$verbose" = 0 ] || echo "Diff:" diff -U100 "$tmpdir/cert-tor" "$tmpdir/cert-direct" || true errors=$((errors | 0x08)) fi [ "$torserver" = "-" ] || break done exit "$errors"