Index | Thread | Search

From:
Dimitri John Ledkov <dimitri.ledkov@surgut.co.uk>
Subject:
[PATCH] Upgrade ssh_connection_hash from hex SHA1 to base64url SHA256
To:
tech@openbsd.org
Date:
Mon, 8 Sep 2025 10:24:36 +0100

Download raw body.

Thread
Upgrade ssh_connection_hash from SHA1 to SHA256. Due to increased
length, instead of using hex encoding, use base64url encoding, which
for SHA256 is only slightly longer than hex SHA1. Rename related
variables for clarity.

This change enables building and using ssh completely without SHA1.
---
 regress/usr.bin/ssh/percent.sh |  3 ++-
 usr.bin/ssh/readconf.c         | 23 ++++++++++++++++-------
 usr.bin/ssh/ssh.c              |  4 ++--
 usr.bin/ssh/sshconnect.h       |  4 ++--
 4 files changed, 22 insertions(+), 12 deletions(-)

diff --git a/regress/usr.bin/ssh/percent.sh b/regress/usr.bin/ssh/percent.sh
index c607c8d23aa..d38eb694790 100644
--- a/regress/usr.bin/ssh/percent.sh
+++ b/regress/usr.bin/ssh/percent.sh
@@ -107,7 +107,8 @@ for i in matchexec localcommand remotecommand controlpath identityagent \
 	# Matches implementation in readconf.c:ssh_connection_hash()
 	if [ ! -z "${OPENSSL_BIN}" ]; then
 		HASH=`printf "${HOSTNAME}127.0.0.1${PORT}${REMUSER}" |
-		    $OPENSSL_BIN sha1 | cut -f2 -d' '`
+		    $OPENSSL_BIN sha256 -binary |
+		    $OPENSSL_BIN base64 | tr '+/' '-_' | tr -d =`
 		trial $i '%C' $HASH
 	fi
 	trial $i '%%' '%'
diff --git a/usr.bin/ssh/readconf.c b/usr.bin/ssh/readconf.c
index 4e94f1e0bc1..75860dccf10 100644
--- a/usr.bin/ssh/readconf.c
+++ b/usr.bin/ssh/readconf.c
@@ -41,6 +41,7 @@
 
 #include "xmalloc.h"
 #include "ssh.h"
+#include "sshbuf.h"
 #include "ssherr.h"
 #include "cipher.h"
 #include "pathnames.h"
@@ -345,17 +346,25 @@ ssh_connection_hash(const char *thishost, const char *host, const char *portstr,
 {
 	struct ssh_digest_ctx *md;
 	u_char conn_hash[SSH_DIGEST_MAX_LENGTH];
+	struct sshbuf *hashbuffer;
+	struct sshbuf *urlb64buffer;
+	char *ret;
 
-	if ((md = ssh_digest_start(SSH_DIGEST_SHA1)) == NULL ||
+	if ((md = ssh_digest_start(SSH_DIGEST_SHA256)) == NULL ||
 	    ssh_digest_update(md, thishost, strlen(thishost)) < 0 ||
 	    ssh_digest_update(md, host, strlen(host)) < 0 ||
 	    ssh_digest_update(md, portstr, strlen(portstr)) < 0 ||
 	    ssh_digest_update(md, user, strlen(user)) < 0 ||
 	    ssh_digest_update(md, jumphost, strlen(jumphost)) < 0 ||
-	    ssh_digest_final(md, conn_hash, sizeof(conn_hash)) < 0)
+	    ssh_digest_final(md, conn_hash, sizeof(conn_hash)) < 0 ||
+	    (urlb64buffer = sshbuf_new()) == NULL ||
+	    (hashbuffer = sshbuf_from(conn_hash, ssh_digest_bytes(SSH_DIGEST_SHA256))) == NULL ||
+	    sshbuf_dtourlb64(hashbuffer, urlb64buffer, 0) < 0 ||
+	    (ret = sshbuf_dup_string(urlb64buffer)) == NULL)
 		fatal_f("mux digest failed");
 	ssh_digest_free(md);
-	return tohex(conn_hash, ssh_digest_bytes(SSH_DIGEST_SHA1));
+	sshbuf_free(urlb64buffer);
+	return ret;
 }
 
 /*
@@ -637,7 +646,7 @@ expand_match_exec_or_include_path(const char *path, Options *options,
     int final_pass, int is_include_path)
 {
 	char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV];
-	char uidstr[32], *conn_hash_hex, *keyalias, *jmphost, *ruser;
+	char uidstr[32], *conn_hash_urlb64, *keyalias, *jmphost, *ruser;
 	char *host, *ret;
 	int port;
 
@@ -661,12 +670,12 @@ expand_match_exec_or_include_path(const char *path, Options *options,
 	snprintf(portstr, sizeof(portstr), "%d", port);
 	snprintf(uidstr, sizeof(uidstr), "%llu",
 	    (unsigned long long)pw->pw_uid);
-	conn_hash_hex = ssh_connection_hash(thishost, host,
+	conn_hash_urlb64 = ssh_connection_hash(thishost, host,
 	    portstr, ruser, jmphost);
 	keyalias = options->host_key_alias ?  options->host_key_alias : host;
 
 	ret = (is_include_path ? percent_dollar_expand : percent_expand)(path,
-	    "C", conn_hash_hex,
+	    "C", conn_hash_urlb64,
 	    "L", shorthost,
 	    "d", pw->pw_dir,
 	    "h", host,
@@ -680,7 +689,7 @@ expand_match_exec_or_include_path(const char *path, Options *options,
 	    "j", jmphost,
 	    (char *)NULL);
 	free(host);
-	free(conn_hash_hex);
+	free(conn_hash_urlb64);
 	return ret;
 }
 
diff --git a/usr.bin/ssh/ssh.c b/usr.bin/ssh/ssh.c
index 865d3f72dc3..29fe40f6aa4 100644
--- a/usr.bin/ssh/ssh.c
+++ b/usr.bin/ssh/ssh.c
@@ -614,7 +614,7 @@ ssh_conn_info_free(struct ssh_conn_info *cinfo)
 {
 	if (cinfo == NULL)
 		return;
-	free(cinfo->conn_hash_hex);
+	free(cinfo->conn_hash_urlb64);
 	free(cinfo->shorthost);
 	free(cinfo->uidstr);
 	free(cinfo->keyalias);
@@ -1478,7 +1478,7 @@ main(int ac, char **av)
 
 	/* Now User is expanded, store it and calculate hash. */
 	cinfo->remuser = xstrdup(options.user);
-	cinfo->conn_hash_hex = ssh_connection_hash(cinfo->thishost,
+	cinfo->conn_hash_urlb64 = ssh_connection_hash(cinfo->thishost,
 	    cinfo->remhost, cinfo->portstr, cinfo->remuser, cinfo->jmphost);
 
 	/*
diff --git a/usr.bin/ssh/sshconnect.h b/usr.bin/ssh/sshconnect.h
index 30827016040..bce77176d20 100644
--- a/usr.bin/ssh/sshconnect.h
+++ b/usr.bin/ssh/sshconnect.h
@@ -33,7 +33,7 @@ struct Sensitive {
 };
 
 struct ssh_conn_info {
-	char *conn_hash_hex;
+	char *conn_hash_urlb64;
 	char *shorthost;
 	char *uidstr;
 	char *keyalias;
@@ -68,7 +68,7 @@ struct ssh_conn_info;
 /* same plus remote user and hash which has user as a component */
 #define DEFAULT_CLIENT_PERCENT_EXPAND_ARGS(conn_info) \
 	DEFAULT_CLIENT_PERCENT_EXPAND_ARGS_NOUSER(conn_info), \
-	"C", conn_info->conn_hash_hex, \
+	"C", conn_info->conn_hash_urlb64, \
 	"r", conn_info->remuser
 
 int	 ssh_connect(struct ssh *, const char *, const char *,
-- 
2.48.1