diff options
-rw-r--r-- | Makefile | 5 | ||||
-rwxr-xr-x | afssh | 3 | ||||
-rw-r--r-- | changelog | 55 | ||||
-rw-r--r-- | ssh-agent-filter.C | 74 | ||||
-rwxr-xr-x | tests | 104 | ||||
-rw-r--r-- | version.h | 2 |
6 files changed, 234 insertions, 9 deletions
@@ -1,4 +1,4 @@ -# Copyright (C) 2013-2016 Timo Weingärtner <timo@tiwe.de> +# Copyright (C) 2013-2018 Timo Weingärtner <timo@tiwe.de> # # This file is part of ssh-agent-filter. # @@ -36,6 +36,9 @@ ssh-agent-filter.o: ssh-agent-filter.C rfc4251.H ssh-agent.h version.h rfc4251.o: rfc4251.C rfc4251.H rfc4251_gmp.o: rfc4251_gmp.C rfc4251.H +test: + PATH="$$(pwd):$$PATH" ./tests + version.h: test ! -d .git || git describe | sed 's/^.*$$/#define SSH_AGENT_FILTER_VERSION "ssh-agent-filter \0"/' > $@ @@ -52,7 +52,8 @@ fi declare -a agent_filter_args if [ -x "${BASH_SOURCE%/*}/ssh-agent-filter" ]; then - SAF=$(readlink -f "${BASH_SOURCE%/*}/ssh-agent-filter") + type realpath >/dev/null 2>&1 || realpath () { readlink -f "$@"; } + SAF=$(realpath "${BASH_SOURCE%/*}/ssh-agent-filter") else SAF=$(which ssh-agent-filter) fi @@ -1,3 +1,58 @@ +commit 3b9460a74b51119e15e0d57dafb2e0c66326890a +Author: Timo Weingärtner <timo@tiwe.de> +Date: 2018-01-08 21:08:09 +0100 + + update copyright + +commit bae7e0329adf50af874bf323cefc9e54c2cd00a0 +Author: Timo Weingärtner <timo@tiwe.de> +Date: 2018-01-08 20:44:32 +0100 + + add tests using shunit2 + +commit c97c3087ad393eef928f961869b0c5e400f8a66f +Author: Timo Weingärtner <timo@tiwe.de> +Date: 2018-01-06 12:55:28 +0100 + + add dissection for cert signing requests + +commit 6b20192d53aea20ea5ba9725aebe2b7c7d8d8875 +Author: Timo Weingärtner <timo@tiwe.de> +Date: 2017-12-27 16:54:54 +0100 + + follow API change in nettle 3.4 + + Closes: 884400 + +commit 3944f8c3b52e23e7e617fbfa24307ce8c07757e6 +Author: Timo Weingärtner <timo@tiwe.de> +Date: 2016-10-11 16:42:02 +0200 + + fixup! afssh: use realpath instead of readlink -f, with fallback + +commit 2f994963899ca8e039dd0aa38884693a9928e116 (tiwe/master) +Author: Timo Weingärtner <timo@tiwe.de> +Date: 2016-10-11 14:25:39 +0200 + + afssh: use realpath instead of readlink -f, with fallback + + this add compatibility for some BSD systems which don't have -f to readlink + while retaining compatibility with GNU systems without a readlink command + + Thanks: Konstantinos Koukopoulos <kouk@transifex.com> + +commit f8e42e89e3404f91d397e325c94af6494786e90f +Author: Timo Weingärtner <timo@tiwe.de> +Date: 2016-10-09 12:31:27 +0200 + + avoid warning about unused result of chdir() + +commit d765ef1acb318e1b97481805b66f7b45f8f08f41 (tag: 0.4.2) +Author: Timo Weingärtner <timo@tiwe.de> +Date: 2016-08-27 22:51:46 +0200 + + release 0.4.2 + commit 7152b927e22ef602011f8acf865c3cafc113c502 Author: Timo Weingärtner <timo@tiwe.de> Date: 2016-08-27 22:47:48 +0200 diff --git a/ssh-agent-filter.C b/ssh-agent-filter.C index 2878678..74b15ab 100644 --- a/ssh-agent-filter.C +++ b/ssh-agent-filter.C @@ -1,7 +1,7 @@ /* * ssh-agent-filter.C -- filtering proxy for ssh-agent meant to be forwarded to untrusted servers * - * Copyright (C) 2013-2016 Timo Weingärtner <timo@tiwe.de> + * Copyright (C) 2013-2018 Timo Weingärtner <timo@tiwe.de> * * This file is part of ssh-agent-filter. * @@ -110,18 +110,18 @@ string md5_hex (string const & s) { md5_update(&ctx, s.size(), reinterpret_cast<uint8_t const *>(s.data())); uint8_t bin[MD5_DIGEST_SIZE]; md5_digest(&ctx, MD5_DIGEST_SIZE, bin); - uint8_t hex[BASE16_ENCODE_LENGTH(MD5_DIGEST_SIZE)]; + char hex[BASE16_ENCODE_LENGTH(MD5_DIGEST_SIZE)]; base16_encode_update(hex, MD5_DIGEST_SIZE, bin); - return {reinterpret_cast<char const *>(hex), sizeof(hex)}; + return {hex, sizeof(hex)}; } string base64_encode (string const & s) { struct base64_encode_ctx ctx; base64_encode_init(&ctx); - uint8_t b64[BASE64_ENCODE_LENGTH(s.size())]; + char b64[BASE64_ENCODE_LENGTH(s.size())]; auto len = base64_encode_update(&ctx, b64, s.size(), reinterpret_cast<uint8_t const *>(s.data())); len += base64_encode_final(&ctx, b64 + len); - return {reinterpret_cast<char const *>(b64), len}; + return {b64, len}; } void cloexec (int fd) { @@ -329,6 +329,65 @@ bool confirm (string const & question) { } } +bool dissect_auth_data_ssh_cert (rfc4251::string const & data, string & request_description) try { + io::stream<io::array_source> datastream{data.data(), data.size()}; + arm(datastream); + + // Format specified in https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=1.13 + rfc4251::string keytype{datastream}; + std::string keytype_str{keytype}; + { + // check for and remove suffix to get the base keytype + std::string const suffix{"-cert-v01@openssh.com"}; + if (keytype_str.length() <= suffix.length()) + return false; + auto suffix_start = keytype_str.end() - suffix.length(); + if (!std::equal(suffix.begin(), suffix.end(), suffix_start)) + return false; + keytype_str.erase(suffix_start, keytype_str.end()); + } + rfc4251::string nonce{datastream}; + std::ostringstream key_to_be_signed{}; + if (keytype_str == "ssh-rsa") { + rfc4251::string e{datastream}; + rfc4251::string n{datastream}; + key_to_be_signed << rfc4251::string{keytype_str} << e << n; + } else if (keytype_str == "ssh-dss") { + rfc4251::string p{datastream}; + rfc4251::string q{datastream}; + rfc4251::string g{datastream}; + rfc4251::string y{datastream}; + key_to_be_signed << rfc4251::string{keytype_str} << p << q << g << y; + } else if (keytype_str == "ecdsa-sha2-nistp256" + || keytype_str == "ecdsa-sha2-nistp384" + || keytype_str == "ecdsa-sha2-nistp521") { + rfc4251::string curve{datastream}; + rfc4251::string public_key{datastream}; + key_to_be_signed << rfc4251::string{keytype_str} << curve << public_key; + } else if (keytype_str == "ssh-ed25519") { + rfc4251::string pk{datastream}; + key_to_be_signed << rfc4251::string{keytype_str} << pk; + } else { + return false; + } + rfc4251::uint64 serial{datastream}; + rfc4251::uint32 type{datastream}; + rfc4251::string key_id{datastream}; + rfc4251::string valid_principals{datastream}; + rfc4251::uint64 valid_after{datastream}; + rfc4251::uint64 valid_before{datastream}; + rfc4251::string critical_options{datastream}; + rfc4251::string extensions{datastream}; + rfc4251::string reserved{datastream}; + rfc4251::string signature_key{datastream}; + + request_description = "The request is for a certificate signature on key " + base64_encode(key_to_be_signed.str()) + "."; + + return true; +} catch (...) { + return false; +} + bool dissect_auth_data_ssh (rfc4251::string const & data, string & request_description) try { io::stream<io::array_source> datastream{data.data(), data.size()}; arm(datastream); @@ -448,6 +507,8 @@ rfc4251::string handle_request (rfc4251::string const & r) { string request_description; bool dissect_ok{false}; if (!dissect_ok) + dissect_ok = dissect_auth_data_ssh_cert(data, request_description); + if (!dissect_ok) dissect_ok = dissect_auth_data_ssh(data, request_description); if (!dissect_ok) request_description = "The request format is unknown."; @@ -543,8 +604,9 @@ int main (int const argc, char const * const * const argv) { exit(EX_OK); } + // the following stuff is optional, so we don't do error checking setsid(); - chdir("/"); + static_cast<void>(chdir("/")); int devnull = open("/dev/null", O_RDWR); dup2(devnull, 0); dup2(devnull, 1); @@ -0,0 +1,104 @@ +#!/bin/sh + +# tests for ssh-agent-filter +# +# Copyright (C) 2018 Timo Weingärtner <timo@tiwe.de> +# +# This file is part of ssh-agent-filter. +# +# ssh-agent-filter is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ssh-agent-filter is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ssh-agent-filter. If not, see <http://www.gnu.org/licenses/>. + +oneTimeSetUp () { + set -e + + # prepare keys + ( cd "$SHUNIT_TMPDIR"; ssh-keygen -q -t ed25519 -N '' -C key0 -f key0 ) + ( cd "$SHUNIT_TMPDIR"; ssh-keygen -q -t ed25519 -N '' -C key1 -f key1 ) + + # prepare agent + eval "$(ssh-agent)" + + ( cd "$SHUNIT_TMPDIR"; ssh-add key0 key1 ) + + # delete private keys from file system, they are in the agent now + ( cd "$SHUNIT_TMPDIR"; rm key0 key1 ) + + set +e +} + +oneTimeTearDown () { + [ -z "$SSH_AGENT_PID" ] || kill "$SSH_AGENT_PID" +} + +with_saf_in_tmp () { + set -e + cd "$SHUNIT_TMPDIR" + unset SSH_AGENT_PID + eval "$(ssh-agent-filter "$@")" > /dev/null + trap 'kill "$SSH_AGENT_PID"' EXIT +} + +produce_filtered_list () ( + with_saf_in_tmp "$@" + ssh-add -L +) + +test_list_filter () { + reference_out=$(ssh-add -L | grep ' key0$') + + # sanity check: unfiltered shold be different from filtered + assertNotSame "$reference_out" "$(ssh-add -L)" + + assertSame "$reference_out" "$(produce_filtered_list --comment key0)" + assertSame "$reference_out" "$(produce_filtered_list --comment-confirmed key0)" + + key0_md5=$(cut -d\ -f2 "$SHUNIT_TMPDIR/key0.pub" | base64 -d | md5sum - | cut -d\ -f1) + assertSame "$reference_out" "$(produce_filtered_list --fingerprint "$key0_md5")" + assertSame "$reference_out" "$(produce_filtered_list --fingerprint-confirmed "$key0_md5")" + + key0_base64=$(cut -d\ -f2 "$SHUNIT_TMPDIR/key0.pub") + assertSame "$reference_out" "$(produce_filtered_list --key "$key0_base64")" + assertSame "$reference_out" "$(produce_filtered_list --key-confirmed "$key0_base64")" +} + +sign_key_with_key_filtered () ( + key_to_be_signed="$1" + signing_key="$2" + shift 2 + with_saf_in_tmp "$@" + ssh-keygen -Us "$signing_key" -I identify "$key_to_be_signed" +) + +test_sign_filter () { + # try to sign with a key that is allowed by the filter + assertTrue 'sign_key_with_key_filtered key0 key1 --comment key1' + + # try to sign with a key that is not allowed by the filter + assertFalse 'sign_key_with_key_filtered key1 key0 --comment key1' +} + +test_confirmation () { + assertTrue 'export SSH_ASKPASS=/bin/true; sign_key_with_key_filtered key0 key1 --comment-confirmed key1' + assertFalse 'export SSH_ASKPASS=/bin/false; sign_key_with_key_filtered key0 key1 --comment-confirmed key1' + + cat > "$SHUNIT_TMPDIR/sap" <<-EOT + #!/bin/sh + echo "\$1" > "$SHUNIT_TMPDIR/sap_out" + EOT + chmod +x "$SHUNIT_TMPDIR/sap" + assertTrue 'export SSH_ASKPASS="$SHUNIT_TMPDIR/sap"; sign_key_with_key_filtered key0 key1 --comment-confirmed key1' + assertSame "Something behind the ssh-agent-filter requested use of the key named 'key1'." "$(head -n1 "$SHUNIT_TMPDIR/sap_out")" +} + +. shunit2 @@ -1 +1 @@ -#define SSH_AGENT_FILTER_VERSION "ssh-agent-filter 0.4.2" +#define SSH_AGENT_FILTER_VERSION "ssh-agent-filter 0.5" |