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 <n...@vault24.org>
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

Reply via email to