2019-08-22 22:04:46 +02:00
#!/bin/bash
#
# This script will set up a docker image with multiple versions of OpenSSH, then
# use it to run tests.
#
# For debugging purposes, here is a cheat sheet for manually running the docker image:
#
# docker run -p 2222:22 -it ssh-audit-test:X /bin/bash
# docker run -p 2222:22 --security-opt seccomp:unconfined -it ssh-audit-test /debug.sh
# docker run -d -p 2222:22 ssh-audit-test:X /openssh/sshd-5.6p1 -D -f /etc/ssh/sshd_config-5.6p1_test1
# docker run -d -p 2222:22 ssh-audit-test:X /openssh/sshd-8.0p1 -D -f /etc/ssh/sshd_config-8.0p1_test1
#
# This is the docker tag for the image. If this tag doesn't exist, then we assume the
# image is out of date, and generate a new one with this tag.
2019-08-28 04:28:24 +02:00
IMAGE_VERSION = 3
2019-08-22 22:04:46 +02:00
# This is the name of our docker image.
IMAGE_NAME = ssh-audit-test
# Terminal colors.
CLR = "\033[0m"
RED = "\033[0;31m"
GREEN = "\033[0;32m"
REDB = "\033[1;31m" # Red + bold
GREENB = "\033[1;32m" # Green + bold
# Returns 0 if current docker image exists.
function check_if_docker_image_exists {
images = ` docker image ls | egrep " $IMAGE_NAME [[:space:]]+ $IMAGE_VERSION " `
}
2019-08-26 20:45:31 +02:00
# Uncompresses and compiles the specified version of Dropbear.
function compile_dropbear {
version = $1
compile 'Dropbear' $version
}
2019-08-22 22:04:46 +02:00
# Uncompresses and compiles the specified version of OpenSSH.
function compile_openssh {
2019-08-26 20:45:31 +02:00
version = $1
compile 'OpenSSH' $version
}
2019-08-28 04:28:24 +02:00
# Uncompresses and compiles the specified version of TinySSH.
function compile_tinyssh {
version = $1
compile 'TinySSH' $version
}
2019-08-26 20:45:31 +02:00
function compile {
project = $1
version = $2
tarball =
uncompress_options =
source_dir =
server_executable =
if [ [ $project = = 'OpenSSH' ] ] ; then
tarball = " openssh- ${ version } .tar.gz "
uncompress_options = "xzf"
source_dir = " openssh- ${ version } "
server_executable = sshd
elif [ [ $project = = 'Dropbear' ] ] ; then
tarball = " dropbear- ${ version } .tar.bz2 "
uncompress_options = "xjf"
source_dir = " dropbear- ${ version } "
server_executable = dropbear
2019-08-28 04:28:24 +02:00
elif [ [ $project = = 'TinySSH' ] ] ; then
tarball = " ${ version } .tar.gz "
uncompress_options = "xzf"
source_dir = " tinyssh- ${ version } "
server_executable = 'build/bin/tinysshd'
2019-08-26 20:45:31 +02:00
fi
echo " Uncompressing ${ project } ${ version } ... "
tar $uncompress_options $tarball
2019-08-22 22:04:46 +02:00
2019-08-26 20:45:31 +02:00
echo " Compiling ${ project } ${ version } ... "
pushd $source_dir > /dev/null
2019-08-28 04:28:24 +02:00
# TinySSH has no configure script... only a Makefile.
if [ [ $project = = 'TinySSH' ] ] ; then
make -j 10
else
./configure && make -j 10
fi
2019-08-22 22:04:46 +02:00
2019-08-26 20:45:31 +02:00
if [ [ ! -f $server_executable ] ] ; then
echo -e " ${ REDB } Error: ${ server_executable } not built! ${ CLR } "
2019-08-22 22:04:46 +02:00
exit 1
fi
2019-08-26 20:45:31 +02:00
echo -e " \n ${ GREEN } Successfully built ${ project } ${ version } ${ CLR } \n "
2019-08-22 22:04:46 +02:00
popd > /dev/null
}
# Creates a new docker image.
function create_docker_image {
# Create a new temporary directory.
TMP_DIR = ` mktemp -d /tmp/sshaudit-docker-XXXXXXXXXX`
2019-08-28 04:28:24 +02:00
# Copy the Dockerfile and all files in the test/docker/ dir to our new temp directory.
find test/docker/ -maxdepth 1 -type f | xargs cp -t $TMP_DIR
2019-08-22 22:04:46 +02:00
# Make the temp directory our working directory for the duration of the build
# process.
pushd $TMP_DIR > /dev/null
2019-08-28 04:28:24 +02:00
# Get the release keys.
2019-08-26 20:45:31 +02:00
get_dropbear_release_key
2019-08-28 04:28:24 +02:00
get_openssh_release_key
get_tinyssh_release_key
2019-08-22 22:04:46 +02:00
# Aside from checking the GPG signatures, we also compare against this known-good
# SHA-256 hash just in case.
2019-08-22 22:48:23 +02:00
get_openssh '4.0p1' '5adb9b2c2002650e15216bf94ed9db9541d9a17c96fcd876784861a8890bc92b'
2019-08-22 22:04:46 +02:00
get_openssh '5.6p1' '538af53b2b8162c21a293bb004ae2bdb141abd250f61b4cea55244749f3c6c2b'
get_openssh '8.0p1' 'bd943879e69498e8031eb6b7f44d08cdc37d59a7ab689aa0b437320c3481fd68'
2019-08-26 20:45:31 +02:00
get_dropbear '2019.78' '525965971272270995364a0eb01f35180d793182e63dd0b0c3eb0292291644a4'
2019-08-28 04:28:24 +02:00
get_tinyssh '20190101' '554a9a94e53b370f0cd0c5fbbd322c34d1f695cbcea6a6a32dcb8c9f595b3fea'
2019-08-22 22:04:46 +02:00
# Compile the versions of OpenSSH.
2019-08-22 22:48:23 +02:00
compile_openssh '4.0p1'
2019-08-22 22:04:46 +02:00
compile_openssh '5.6p1'
compile_openssh '8.0p1'
2019-08-26 20:45:31 +02:00
# Compile the versions of Dropbear.
compile_dropbear '2019.78'
2019-08-28 04:28:24 +02:00
# Compile the versions of TinySSH.
compile_tinyssh '20190101'
2019-08-26 20:45:31 +02:00
2019-08-22 22:04:46 +02:00
# Rename the default config files so we know they are our originals.
2019-08-22 22:48:23 +02:00
mv openssh-4.0p1/sshd_config sshd_config-4.0p1_orig
2019-08-22 22:04:46 +02:00
mv openssh-5.6p1/sshd_config sshd_config-5.6p1_orig
mv openssh-8.0p1/sshd_config sshd_config-8.0p1_orig
# Create the configurations for each test.
2019-08-22 22:48:23 +02:00
#
# OpenSSH v4.0p1
#
# Test 1: Basic test.
create_openssh_config '4.0p1' 'test1' "HostKey /etc/ssh/ssh1_host_key\nHostKey /etc/ssh/ssh_host_rsa_key_1024\nHostKey /etc/ssh/ssh_host_dsa_key"
2019-08-22 22:04:46 +02:00
#
# OpenSSH v5.6p1
#
# Test 1: Basic test.
create_openssh_config '5.6p1' 'test1' "HostKey /etc/ssh/ssh_host_rsa_key_1024\nHostKey /etc/ssh/ssh_host_dsa_key"
# Test 2: RSA 1024 host key with RSA 1024 certificate.
create_openssh_config '5.6p1' 'test2' "HostKey /etc/ssh/ssh_host_rsa_key_1024\nHostCertificate /etc/ssh/ssh_host_rsa_key_1024-cert_1024.pub"
# Test 3: RSA 1024 host key with RSA 3072 certificate.
create_openssh_config '5.6p1' 'test3' "HostKey /etc/ssh/ssh_host_rsa_key_1024\nHostCertificate /etc/ssh/ssh_host_rsa_key_1024-cert_3072.pub"
# Test 4: RSA 3072 host key with RSA 1024 certificate.
create_openssh_config '5.6p1' 'test4' "HostKey /etc/ssh/ssh_host_rsa_key_3072\nHostCertificate /etc/ssh/ssh_host_rsa_key_3072-cert_1024.pub"
# Test 5: RSA 3072 host key with RSA 3072 certificate.
create_openssh_config '5.6p1' 'test5' "HostKey /etc/ssh/ssh_host_rsa_key_3072\nHostCertificate /etc/ssh/ssh_host_rsa_key_3072-cert_3072.pub"
#
# OpenSSH v8.0p1
#
# Test 1: Basic test.
create_openssh_config '8.0p1' 'test1' "HostKey /etc/ssh/ssh_host_rsa_key_3072\nHostKey /etc/ssh/ssh_host_ecdsa_key\nHostKey /etc/ssh/ssh_host_ed25519_key"
# Test 2: ED25519 certificate test.
create_openssh_config '8.0p1' 'test2' "HostKey /etc/ssh/ssh_host_ed25519_key\nHostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub"
# Test 3: Hardened installation test.
create_openssh_config '8.0p1' 'test3' "HostKey /etc/ssh/ssh_host_ed25519_key\nKexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com"
# Now build the docker image!
docker build --tag $IMAGE_NAME :$IMAGE_VERSION .
popd > /dev/null
rm -rf $TMP_DIR
}
# Creates an OpenSSH configuration file for a specific test.
function create_openssh_config {
openssh_version = $1
test_number = $2
config_text = $3
cp sshd_config-${ openssh_version } _orig sshd_config-${ openssh_version } _${ test_number }
echo -e " ${ config_text } " >> sshd_config-${ openssh_version } _${ test_number }
}
2019-08-26 20:45:31 +02:00
# Downloads the Dropbear release key and adds it to the local keyring.
function get_dropbear_release_key {
get_release_key 'Dropbear' 'https://matt.ucc.asn.au/dropbear/releases/dropbear-key-2015.asc' 'F29C6773' 'F734 7EF2 EE2E 07A2 6762 8CA9 4493 1494 F29C 6773'
}
2019-08-22 22:04:46 +02:00
# Downloads the OpenSSH release key and adds it to the local keyring.
function get_openssh_release_key {
2019-08-26 20:45:31 +02:00
get_release_key 'OpenSSH' 'https://ftp.openbsd.org/pub/OpenBSD/OpenSSH/RELEASE_KEY.asc' '6D920D30' '59C2 118E D206 D927 E667 EBE3 D3E5 F56B 6D92 0D30'
}
2019-08-28 04:28:24 +02:00
# Downloads the TinySSH release key and adds it to the local keyring.
function get_tinyssh_release_key {
get_release_key 'TinySSH' '' '96939FF9' 'AADF 2EDF 5529 F170 2772 C8A2 DEC4 D246 931E F49B'
}
2019-08-26 20:45:31 +02:00
function get_release_key {
project = $1
key_url = $2
key_id = $3
release_key_fingerprint_expected = $4
2019-08-22 22:04:46 +02:00
2019-08-28 04:28:24 +02:00
# The TinySSH release key isn't on any website, apparently.
if [ [ $project = = 'TinySSH' ] ] ; then
gpg --recv-key $key_id
else
echo -e " \nGetting ${ project } release key...\n "
wget -O key.asc $2
2019-08-22 22:04:46 +02:00
2019-08-28 04:28:24 +02:00
echo -e " \nImporting ${ project } release key...\n "
gpg --import key.asc
2019-08-22 22:04:46 +02:00
2019-08-28 04:28:24 +02:00
rm key.asc
fi
2019-08-26 20:45:31 +02:00
local release_key_fingerprint_actual = ` gpg --fingerprint ${ key_id } `
2019-08-22 22:04:46 +02:00
if [ [ $release_key_fingerprint_actual != *" $release_key_fingerprint_expected " * ] ] ; then
2019-08-26 20:45:31 +02:00
echo -e " \n ${ REDB } Error: ${ project } release key fingerprint does not match expected value!\n\tExpected: $release_key_fingerprint_expected \n\tActual: $release_key_fingerprint_actual \n\nTerminating. ${ CLR } "
2019-08-22 22:04:46 +02:00
exit -1
fi
2019-08-26 20:45:31 +02:00
echo -e " \n\n ${ GREEN } ${ project } release key matches expected value. ${ CLR } \n "
}
# Downloads the specified version of Dropbear.
function get_dropbear {
version = $1
tarball_checksum_expected = $2
get_source 'Dropbear' $version $tarball_checksum_expected
2019-08-22 22:04:46 +02:00
}
# Downloads the specified version of OpenSSH.
function get_openssh {
2019-08-26 20:45:31 +02:00
version = $1
tarball_checksum_expected = $2
get_source 'OpenSSH' $version $tarball_checksum_expected
}
2019-08-22 22:04:46 +02:00
2019-08-22 22:48:23 +02:00
2019-08-28 04:28:24 +02:00
# Downloads the specified version of TinySSH.
function get_tinyssh {
version = $1
tarball_checksum_expected = $2
get_source 'TinySSH' $version $tarball_checksum_expected
}
2019-08-26 20:45:31 +02:00
function get_source {
project = $1
version = $2
tarball_checksum_expected = $3
2019-08-28 04:28:24 +02:00
base_url_source =
base_url_sig =
2019-08-26 20:45:31 +02:00
tarball =
sig =
signer =
if [ [ $project = = 'OpenSSH' ] ] ; then
2019-08-28 04:28:24 +02:00
base_url_source = 'https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/'
base_url_sig = $base_url_source
2019-08-26 20:45:31 +02:00
tarball = " openssh- ${ version } .tar.gz "
sig = " ${ tarball } .asc "
signer = "Damien Miller "
elif [ [ $project = = 'Dropbear' ] ] ; then
2019-08-28 04:28:24 +02:00
base_url_source = 'https://matt.ucc.asn.au/dropbear/releases/'
base_url_sig = $base_url_source
2019-08-26 20:45:31 +02:00
tarball = " dropbear- ${ version } .tar.bz2 "
sig = " ${ tarball } .asc "
signer = "Dropbear SSH Release Signing <matt@ucc.asn.au>"
2019-08-28 04:28:24 +02:00
elif [ [ $project = = 'TinySSH' ] ] ; then
base_url_source = 'https://github.com/janmojzis/tinyssh/archive/'
base_url_sig = " https://github.com/janmojzis/tinyssh/releases/download/ ${ version } / "
tarball = " ${ version } .tar.gz "
sig = " ${ tarball } .asc "
signer = "Jan Mojžíš <jan.mojzis@gmail.com>"
2019-08-22 22:48:23 +02:00
fi
2019-08-22 22:04:46 +02:00
2019-08-26 20:45:31 +02:00
echo -e " \nGetting ${ project } ${ version } sources...\n "
2019-08-28 04:28:24 +02:00
wget " ${ base_url_source } ${ tarball } "
2019-08-26 20:45:31 +02:00
echo -e " \nGetting ${ project } ${ version } signature...\n "
2019-08-28 04:28:24 +02:00
wget " ${ base_url_sig } ${ sig } "
2019-08-26 20:45:31 +02:00
# Older OpenSSH releases were .sigs.
if [ [ ( $project = = 'OpenSSH' ) && ( ! -f $sig ) ] ] ; then
2019-08-28 04:28:24 +02:00
wget ${ base_url_sig } openssh-${ version } .tar.gz.sig
2019-08-26 20:45:31 +02:00
sig = openssh-${ version } .tar.gz.sig
fi
local gpg_verify = ` gpg --verify ${ sig } ${ tarball } 2>& 1`
if [ [ $gpg_verify != *" Good signature from \" ${ signer } " * ] ] ; then
echo -e " \n\n ${ REDB } Error: ${ project } signature invalid!\n $gpg_verify \n\nTerminating. ${ CLR } "
2019-08-22 22:04:46 +02:00
exit -1
fi
# Check GPG's return value. 0 denotes a valid signature, and 1 is returned
# on invalid signatures.
if [ [ $? != 0 ] ] ; then
2019-08-26 20:45:31 +02:00
echo -e " \n\n ${ REDB } Error: ${ project } signature invalid! Verification returned code: $? \n\nTerminating. ${ CLR } "
2019-08-22 22:04:46 +02:00
exit -1
fi
2019-08-26 20:45:31 +02:00
echo -e " ${ GREEN } Signature on ${ project } sources verified. ${ CLR } \n "
2019-08-22 22:04:46 +02:00
2019-08-26 20:45:31 +02:00
local checksum_actual = ` sha256sum ${ tarball } | cut -f1 -d" " `
if [ [ $checksum_actual != $tarball_checksum_expected ] ] ; then
echo -e " ${ REDB } Error: ${ project } checksum is invalid!\n Expected: ${ tarball_checksum_expected } \n Actual: ${ checksum_actual } \n\n Terminating. ${ CLR } "
2019-08-22 22:04:46 +02:00
exit -1
fi
2019-08-26 20:45:31 +02:00
}
# Runs a Dropbear test. Upon failure, a diff between the expected and actual results
# is shown, then the script immediately terminates.
function run_dropbear_test {
dropbear_version = $1
test_number = $2
options = $3
2019-08-22 22:04:46 +02:00
2019-08-26 20:45:31 +02:00
run_test 'Dropbear' $dropbear_version $test_number " $options "
2019-08-22 22:04:46 +02:00
}
# Runs an OpenSSH test. Upon failure, a diff between the expected and actual results
# is shown, then the script immediately terminates.
function run_openssh_test {
openssh_version = $1
test_number = $2
2019-08-26 20:45:31 +02:00
run_test 'OpenSSH' $openssh_version $test_number ''
}
2019-08-28 04:28:24 +02:00
# Runs a TinySSH test. Upon failure, a diff between the expected and actual results
# is shown, then the script immediately terminates.
function run_tinyssh_test {
tinyssh_version = $1
test_number = $2
run_test 'TinySSH' $tinyssh_version $test_number ''
}
2019-08-26 20:45:31 +02:00
function run_test {
server_type = $1
version = $2
test_number = $3
options = $4
server_exec =
test_result =
expected_result =
test_name =
if [ [ $server_type = = 'OpenSSH' ] ] ; then
server_exec = " /openssh/sshd- ${ version } -D -f /etc/ssh/sshd_config- ${ version } _ ${ test_number } "
test_result = " ${ TEST_RESULT_DIR } /openssh_ ${ version } _ ${ test_number } .txt "
expected_result = " test/docker/expected_results/openssh_ ${ version } _ ${ test_number } .txt "
test_name = " OpenSSH ${ version } ${ test_number } "
options =
elif [ [ $server_type = = 'Dropbear' ] ] ; then
server_exec = " /dropbear/dropbear- ${ version } -F ${ options } "
test_result = " ${ TEST_RESULT_DIR } /dropbear_ ${ version } _ ${ test_number } .txt "
expected_result = " test/docker/expected_results/dropbear_ ${ version } _ ${ test_number } .txt "
test_name = " Dropbear ${ version } ${ test_number } "
2019-08-28 04:28:24 +02:00
elif [ [ $server_type = = 'TinySSH' ] ] ; then
server_exec = "/usr/bin/tcpserver -HRDl0 0.0.0.0 22 /tinysshd/tinyssh-20190101 -v /etc/tinyssh/"
test_result = " ${ TEST_RESULT_DIR } /tinyssh_ ${ version } _ ${ test_number } .txt "
expected_result = " test/docker/expected_results/tinyssh_ ${ version } _ ${ test_number } .txt "
test_name = " TinySSH ${ version } ${ test_number } "
2019-08-26 20:45:31 +02:00
fi
cid = ` docker run -d -p 2222:22 ${ IMAGE_NAME } :${ IMAGE_VERSION } ${ server_exec } `
2019-08-22 22:04:46 +02:00
if [ [ $? != 0 ] ] ; then
echo -e " ${ REDB } Failed to run docker image! (exit code: $? ) ${ CLR } "
exit 1
fi
2019-08-26 20:45:31 +02:00
./ssh-audit.py localhost:2222 > $test_result
2019-08-22 22:04:46 +02:00
if [ [ $? != 0 ] ] ; then
2019-08-26 20:45:31 +02:00
echo -e " ${ REDB } Failed to run ssh-audit.py! (exit code: $? ) ${ CLR } "
2019-08-22 22:04:46 +02:00
docker container stop $cid > /dev/null
exit 1
fi
docker container stop $cid > /dev/null
if [ [ $? != 0 ] ] ; then
echo -e " ${ REDB } Failed to stop docker container ${ cid } ! (exit code: $? ) ${ CLR } "
exit 1
fi
2019-08-28 04:28:24 +02:00
# TinySSH outputs a random string in each banner, which breaks our test. So
# we need to filter out the banner part of the output so we get stable, repeatable
# results.
if [ [ $server_type = = 'TinySSH' ] ] ; then
grep -v "(gen) banner: " ${ test_result } > " ${ test_result } .tmp "
mv " ${ test_result } .tmp " ${ test_result }
fi
2019-08-26 20:45:31 +02:00
diff = ` diff -u ${ expected_result } ${ test_result } `
2019-08-22 22:04:46 +02:00
if [ [ $? = = 0 ] ] ; then
2019-08-26 20:45:31 +02:00
echo -e " ${ test_name } ${ GREEN } passed ${ CLR } . "
2019-08-22 22:04:46 +02:00
else
2019-08-26 20:45:31 +02:00
echo -e " ${ test_name } ${ REDB } FAILED ${ CLR } .\n\n ${ diff } \n "
2019-08-22 22:04:46 +02:00
exit 1
fi
}
2019-08-26 20:45:31 +02:00
2019-08-22 22:04:46 +02:00
# First check if docker is functional.
docker version > /dev/null
if [ [ $? != 0 ] ] ; then
echo -e " ${ REDB } Error: 'docker version' command failed (error code: $? ). Is docker installed and functioning? ${ CLR } "
exit 1
fi
# Check if the docker image is the most up-to-date version. If not, create it.
check_if_docker_image_exists
if [ [ $? = = 0 ] ] ; then
echo -e " \n ${ GREEN } Docker image $IMAGE_NAME : $IMAGE_VERSION already exists. ${ CLR } "
else
echo -e " \nCreating docker image $IMAGE_NAME : $IMAGE_VERSION ... "
create_docker_image
echo -e " \n ${ GREEN } Done creating docker image! ${ CLR } "
fi
# Create a temporary directory to write test results to.
TEST_RESULT_DIR = ` mktemp -d /tmp/ssh-audit_test-results_XXXXXXXXXX`
# Now run all the tests.
echo -e "\nRunning tests..."
2019-08-22 22:48:23 +02:00
run_openssh_test '4.0p1' 'test1'
echo
2019-08-22 22:04:46 +02:00
run_openssh_test '5.6p1' 'test1'
run_openssh_test '5.6p1' 'test2'
run_openssh_test '5.6p1' 'test3'
run_openssh_test '5.6p1' 'test4'
run_openssh_test '5.6p1' 'test5'
2019-08-22 22:48:23 +02:00
echo
2019-08-22 22:04:46 +02:00
run_openssh_test '8.0p1' 'test1'
run_openssh_test '8.0p1' 'test2'
run_openssh_test '8.0p1' 'test3'
2019-08-26 20:45:31 +02:00
echo
run_dropbear_test '2019.78' 'test1' '-r /etc/dropbear/dropbear_rsa_host_key_1024 -r /etc/dropbear/dropbear_dss_host_key -r /etc/dropbear/dropbear_ecdsa_host_key'
2019-08-28 04:28:24 +02:00
echo
run_tinyssh_test '20190101' 'test1'
2019-08-22 22:04:46 +02:00
# The test functions above will terminate the script on failure, so if we reached here,
# all tests are successful.
echo -e " \n ${ GREENB } ALL TESTS PASS! ${ CLR } \n "
rm -rf $TEST_RESULT_DIR
exit 0