I wrote a patch to fix this last night. My C++ is *REALLY* rusty but I think I managed to create something reasonable. (And now I see why so much C++ code requires C++11.)
-- Jon Doge Wrangler X(7): A program for managing terminal windows. See also screen(1) and tmux(1).
>From 5c86fb077ada9c2def1a5403a69120768fe9a9f7 Mon Sep 17 00:00:00 2001 From: Jon DeVree <[email protected]> Date: Mon, 9 Apr 2018 01:05:54 -0400 Subject: [PATCH] Support OpenSSH's SHA-256 fingerprints OpenSSH 6.8+ use base64 encoded SHA-256 fingerprints by default instead of hex encoded MD5 fingerprints. --- ssh-agent-filter.C | 89 +++++++++++++++++++++++++++++++++++++--------- tests | 6 +++- 2 files changed, 77 insertions(+), 18 deletions(-) diff --git a/ssh-agent-filter.C b/ssh-agent-filter.C index 74b15ab..23aa57a 100644 --- a/ssh-agent-filter.C +++ b/ssh-agent-filter.C @@ -78,6 +78,7 @@ using std::lock_guard; #include <sys/wait.h> #include <sysexits.h> #include <nettle/md5.h> +#include <nettle/sha2.h> #include <nettle/base64.h> #include <nettle/base16.h> @@ -90,10 +91,12 @@ using std::lock_guard; #endif vector<string> allowed_b64; -vector<string> allowed_md5; +std::set<string> allowed_md5; +std::set<string> allowed_sha256; vector<string> allowed_comment; vector<string> confirmed_b64; -vector<string> confirmed_md5; +std::set<string> confirmed_md5; +std::set<string> confirmed_sha256; vector<string> confirmed_comment; std::set<rfc4251::string> allowed_pubkeys; std::map<rfc4251::string, string> confirmed_pubkeys; @@ -115,13 +118,56 @@ string md5_hex (string const & s) { return {hex, sizeof(hex)}; } -string base64_encode (string const & s) { +string base64_encode (uint8_t const * s, size_t ilen) { struct base64_encode_ctx ctx; base64_encode_init(&ctx); - 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 {b64, len}; + char b64[BASE64_ENCODE_LENGTH(ilen)]; + auto olen = base64_encode_update(&ctx, b64, ilen, s); + olen += base64_encode_final(&ctx, b64 + olen); + return {b64, olen}; +} + +string base64_encode (string const & s) { + return base64_encode(reinterpret_cast<uint8_t const *>(s.data()), s.size()); +} + +string sha256_b64 (string const & s) { + struct sha256_ctx ctx; + sha256_init(&ctx); + sha256_update(&ctx, s.size(), reinterpret_cast<uint8_t const *>(s.data())); + uint8_t bin[SHA256_DIGEST_SIZE]; + sha256_digest(&ctx, SHA256_DIGEST_SIZE, bin); + auto b64 = base64_encode(bin, SHA256_DIGEST_SIZE); + // remove any trailing padding like OpenSSH does + auto pos = b64.find_last_not_of('='); + b64.resize(pos + 1); + return b64; +} + +void parse_fingerprints(vector<string> in_fp, std::set<string> & out_md5, std::set<string> & out_sha256) { + for (auto & s : in_fp) { + if (s.compare(0, strlen("SHA256:"), "SHA256:") == 0) { + s.erase(0, strlen("SHA256:")); + if (debug) clog << "fingerprint detected as sha256: " << s << endl; + out_sha256.emplace(std::move(s)); + } else { + if (s.compare(0, strlen("MD5:"), "MD5:") == 0) + s.erase(0, strlen("MD5:")); + + // canonicalize hex + for (auto it = s.begin(); it != s.end(); ) { + if (isxdigit(*it)) { + *it = tolower(*it); + ++it; + } else { + it = s.erase(it); + } + } + + if (debug) clog << "fingerprint detected as md5: " << s << endl; + out_md5.emplace(std::move(s)); + } + } } void cloexec (int fd) { @@ -192,14 +238,17 @@ int make_listen_sock () { } void parse_cmdline (int const argc, char const * const * const argv) { + vector<string> allowed_fp; + vector<string> confirmed_fp; + po::options_description opts{"Options"}; opts.add_options() ("all-confirmed,A", po::bool_switch(&all_confirmed),"allow all other keys with confirmation") ("comment,c", po::value(&allowed_comment), "key specified by comment") ("comment-confirmed,C", po::value(&confirmed_comment), "key specified by comment, with confirmation") ("debug,d", po::bool_switch(&debug), "show some debug info, don't fork") - ("fingerprint,fp,f", po::value(&allowed_md5), "key specified by pubkey's hex-encoded md5 fingerprint") - ("fingerprint-confirmed,F", po::value(&confirmed_md5), "key specified by pubkey's hex-encoded md5 fingerprint, with confirmation") + ("fingerprint,fp,f", po::value(&allowed_fp), "key specified by pubkey's fingerprint") + ("fingerprint-confirmed,F", po::value(&confirmed_fp), "key specified by pubkey's fingerprint, with confirmation") ("help,h", "print this help message") ("key,k", po::value(&allowed_b64), "key specified by base64-encoded pubkey") ("key-confirmed,K", po::value(&confirmed_b64), "key specified by base64-encoded pubkey, with confirmation") @@ -227,14 +276,9 @@ void parse_cmdline (int const argc, char const * const * const argv) { exit(EX_OK); } - // canonicalize hashes - for (auto & s : allowed_md5) - for (auto it = s.begin(); it != s.end(); ) - if (isxdigit(*it)) { - *it = tolower(*it); - ++it; - } else - it = s.erase(it); + parse_fingerprints(std::move(allowed_fp), allowed_md5, allowed_sha256); + parse_fingerprints(std::move(confirmed_fp), confirmed_md5, confirmed_sha256); + if (debug) clog << endl; } void setup_filters () { @@ -259,6 +303,9 @@ void setup_filters () { auto md5 = md5_hex(key); if (debug) clog << md5 << endl; + auto sha256 = sha256_b64(key); + if (debug) clog << sha256 << endl; + string comm(comment); if (debug) clog << comm << endl; @@ -272,6 +319,10 @@ void setup_filters () { allow = true; if (debug) clog << "key allowed by matching md5 fingerprint" << endl; } + if (std::count(allowed_sha256.begin(), allowed_sha256.end(), sha256)) { + allow = true; + if (debug) clog << "key allowed by matching sha256 fingerprint" << endl; + } if (std::count(allowed_comment.begin(), allowed_comment.end(), comm)) { allow = true; if (debug) clog << "key allowed by matching comment" << endl; @@ -289,6 +340,10 @@ void setup_filters () { confirm = true; if (debug) clog << "key allowed with confirmation by matching md5 fingerprint" << endl; } + if (std::count(confirmed_sha256.begin(), confirmed_sha256.end(), sha256)) { + confirm = true; + if (debug) clog << "key allowed with confirmation by matching sha256 fingerprint" << endl; + } if (std::count(confirmed_comment.begin(), confirmed_comment.end(), comm)) { confirm = true; if (debug) clog << "key allowed with confirmation by matching comment" << endl; diff --git a/tests b/tests index d47daf1..0e9b79d 100755 --- a/tests +++ b/tests @@ -45,7 +45,7 @@ with_saf_in_tmp () { set -e cd "$SHUNIT_TMPDIR" unset SSH_AGENT_PID - eval "$(ssh-agent-filter "$@")" > /dev/null + eval "$($OLDPWD/ssh-agent-filter "$@")" > /dev/null trap 'kill "$SSH_AGENT_PID"' EXIT } @@ -67,6 +67,10 @@ test_list_filter () { assertSame "$reference_out" "$(produce_filtered_list --fingerprint "$key0_md5")" assertSame "$reference_out" "$(produce_filtered_list --fingerprint-confirmed "$key0_md5")" + key0_native=$(ssh-keygen -l -f "$SHUNIT_TMPDIR/key0.pub" | cut -d\ -f2) + assertSame "$reference_out" "$(produce_filtered_list --fingerprint "$key0_native")" + assertSame "$reference_out" "$(produce_filtered_list --fingerprint-confirmed "$key0_native")" + 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")" -- 2.17.0

