Adapt break-tests.sh to run on an attached Android device.

Tests can now be run either in a local build or on an attached
device.  The script tries to infer the correct mode of operation
but it can also be specified on the command line.

Test: Ran break-tests.sh in both modes
Change-Id: I515ac0cede23e2cb775b99e0af8108a3ce0bde37
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/53585
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/util/fipstools/break-tests.sh b/util/fipstools/break-tests.sh
index 1b3b7d9..f9ae2e8 100644
--- a/util/fipstools/break-tests.sh
+++ b/util/fipstools/break-tests.sh
@@ -15,38 +15,159 @@
 # This script runs test_fips repeatedly with different FIPS tests broken. It is
 # intended to be observed to demonstrate that the various tests are working and
 # thus pauses for a keystroke between tests.
+#
+# Runs in either device mode (on an attached Android device) or in a locally built
+# BoringSSL checkout.
+#
+# On Android static binaries are not built using FIPS mode, so in device mode each
+# test makes changes to libcrypto.so rather than the test binary, test_fips.
 
 set -e
 
-TEST_FIPS_BIN="build/util/fipstools/test_fips"
+die () {
+    echo "ERROR: $@"
+    exit 1
+}
 
-if [ ! -f $TEST_FIPS_BIN ]; then
-  echo "$TEST_FIPS_BIN is missing. Run this script from the top level of a"
-  echo "BoringSSL checkout and ensure that BoringSSL has been built in"
-  echo "build/ with -DFIPS_BREAK_TEST=TESTS passed to CMake."
-  exit 1
+usage() {
+    echo "USAGE: $0 [local|device]"
+    exit 1
+}
+
+inferred_mode() {
+    # Try and infer local or device mode based on makefiles and artifacts.
+    if [ -f Android.bp -o -f external/boringssl/Android.bp ]
+    then
+       echo device
+    elif [ -f CMakeLists.txt -a -d build/crypto -a -d build/ssl ]
+    then
+	echo local
+    else
+	echo "Unable to infer mode, please specify on the command line."
+	usage
+    fi
+}
+
+# Prefer mode from command line if present.
+case "$1" in
+    local|device)
+	MODE=$1
+	;;
+
+    "")
+	MODE=`inferred_mode`
+	;;
+
+    *)
+	usage
+	;;
+esac
+
+check_directory() {
+    test -d $1 || die "Directory $1 not found."
+}
+
+check_file() {
+    test -f $1 || die "File $1 not found."
+}
+
+run_test_locally() {
+    eval "$1" || true
+}
+
+run_test_on_device() {
+    EXECFILE="$1"
+    LIBRARY="$2"
+    adb shell rm -rf $DEVICE_TMP
+    adb shell mkdir -p $DEVICE_TMP
+    adb push $EXECFILE $DEVICE_TMP > /dev/null
+    EXECPATH=$(basename $EXECFILE)
+    adb push $LIBRARY $DEVICE_TMP > /dev/null
+    adb shell "LD_LIBRARY_PATH=$DEVICE_TMP $DEVICE_TMP/$EXECPATH" || true
+}
+
+device_integrity_break_test() {
+    go run $BORINGSSL/util/fipstools/break-hash.go $LIBCRYPTO_BIN ./libcrypto.so
+    $RUN $TEST_FIPS_BIN ./libcrypto.so
+    rm ./libcrypto.so
+}
+
+local_integrity_break_test() {
+    go run $BORINGSSL/util/fipstools/break-hash.go $TEST_FIPS_BIN ./break-bin
+    chmod u+x ./break-bin
+    $RUN ./break-bin
+    rm ./break-bin
+}
+
+# TODO(prb): make break-hash and break-kat take similar arguments to save having
+# separate functions for each.
+device_kat_break_test() {
+    KAT="$1"
+    go run $BORINGSSL/util/fipstools/break-kat.go $LIBCRYPTO_BIN $KAT > ./libcrypto.so
+    $RUN $TEST_FIPS_BIN ./libcrypto.so
+    rm ./libcrypto.so
+}
+
+local_kat_break_test() {
+    KAT="$1"
+    go run $BORINGSSL/util/fipstools/break-kat.go $TEST_FIPS_BIN $KAT > ./break-bin
+    chmod u+x ./break-bin
+    $RUN ./break-bin
+    rm ./break-bin
+}
+
+pause () {
+    echo -n "Press <Enter> "
+    read
+}
+
+if [ "$MODE" = "local" ]
+then
+    TEST_FIPS_BIN="build/util/fipstools/test_fips"
+    BORINGSSL=.
+    RUN=run_test_locally
+    BREAK_TEST=local_break_test
+    INTEGRITY_BREAK_TEST=local_integrity_break_test
+    KAT_BREAK_TEST=local_kat_break_test
+    if [ ! -f $TEST_FIPS_BIN ]; then
+	echo "$TEST_FIPS_BIN is missing. Run this script from the top level of a"
+	echo "BoringSSL checkout and ensure that BoringSSL has been built in"
+	echo "build/ with -DFIPS_BREAK_TEST=TESTS passed to CMake."
+	exit 1
+    fi
+else # Device mode
+    test "$ANDROID_BUILD_TOP" || die "'lunch aosp_arm64-eng' first"
+    check_directory "$ANDROID_PRODUCT_OUT"
+
+    TEST_FIPS_BIN="$ANDROID_PRODUCT_OUT/system/bin/test_fips"
+    check_file "$TEST_FIPS_BIN"
+    LIBCRYPTO_BIN="$ANDROID_PRODUCT_OUT/system/lib64/libcrypto.so"
+    check_file "$LIBCRYPTO_BIN"
+
+    test "$ANDROID_SERIAL" || die "ANDROID_SERIAL not set"
+    DEVICE_TMP=/data/local/tmp
+
+    BORINGSSL="$ANDROID_BUILD_TOP/external/boringssl/src"
+    RUN=run_test_on_device
+    INTEGRITY_BREAK_TEST=device_integrity_break_test
+    KAT_BREAK_TEST=device_kat_break_test
 fi
 
-KATS=$(go run util/fipstools/break-kat.go --list-tests)
+
+KATS=$(go run $BORINGSSL/util/fipstools/break-kat.go --list-tests)
 
 echo -e '\033[1mNormal output\033[0m'
-$TEST_FIPS_BIN
-read
+$RUN $TEST_FIPS_BIN $LIBCRYPTO_BIN
+pause
 
 echo
 echo -e '\033[1mIntegrity test failure\033[0m'
-go run util/fipstools/break-hash.go $TEST_FIPS_BIN break-bin
-chmod u+x ./break-bin
-./break-bin || true
-rm ./break-bin
-read
+$INTEGRITY_BREAK_TEST
+pause
 
 for kat in $KATS; do
   echo
   echo -e "\033[1mKAT failure ${kat}\033[0m"
-  go run util/fipstools/break-kat.go $TEST_FIPS_BIN $kat > break-bin
-  chmod u+x ./break-bin
-  ./break-bin || true
-  rm ./break-bin
-  read
+  $KAT_BREAK_TEST $kat
+  pause
 done