diff --git a/authfd.c b/authfd.c index 07925be44..fe3226140 100644 --- a/authfd.c +++ b/authfd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: authfd.c,v 1.140 2026/03/05 05:35:44 djm Exp $ */ +/* $OpenBSD: authfd.c,v 1.141 2026/03/05 05:44:15 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -53,8 +53,10 @@ #include "sshkey.h" #include "authfd.h" #include "log.h" +#include "misc.h" #include "atomicio.h" #include "ssherr.h" +#include "xmalloc.h" #define MAX_AGENT_IDENTITIES 2048 /* Max keys in agent reply */ #define MAX_AGENT_REPLY_LEN (256 * 1024) /* Max bytes in agent reply */ @@ -769,3 +771,54 @@ ssh_agent_bind_hostkey(int sock, const struct sshkey *key, sshbuf_free(msg); return r; } + +/* Queries supported extension request types */ +int +ssh_agent_query_extensions(int sock, char ***exts) +{ + struct sshbuf *msg; + int r; + u_char type; + char *cp = NULL, **ret = NULL; + size_t i = 0; + + *exts = NULL; + if ((msg = sshbuf_new()) == NULL) + return SSH_ERR_ALLOC_FAIL; + if ((r = sshbuf_put_u8(msg, SSH_AGENTC_EXTENSION)) != 0 || + (r = sshbuf_put_cstring(msg, "query")) != 0) + goto out; + if ((r = ssh_request_reply(sock, msg, msg)) != 0) + goto out; + if ((r = sshbuf_get_u8(msg, &type)) != 0) + goto out; + if (agent_failed(type)) { + r = SSH_ERR_AGENT_FAILURE; + goto out; + } + /* Reply should start with "query" */ + if (type != SSH_AGENT_EXTENSION_RESPONSE || + (r = sshbuf_get_cstring(msg, &cp, NULL)) != 0 || + strcmp(cp, "query") != 0) { + r = SSH_ERR_INVALID_FORMAT; + goto out; + } + ret = calloc(1, sizeof(*ret)); + while (sshbuf_len(msg)) { + ret = xrecallocarray(ret, i + 1, i + 2, sizeof(*ret)); + if ((r = sshbuf_get_cstring(msg, ret + i, NULL)) != 0) { + r = SSH_ERR_INVALID_FORMAT; + goto out; + } + i++; + } + /* success */ + r = 0; + *exts = ret; + ret = NULL; /* transferred */ + out: + free(cp); + stringlist_free(ret); + sshbuf_free(msg); + return r; +} diff --git a/authfd.h b/authfd.h index da4830a96..b2e07bcf2 100644 --- a/authfd.h +++ b/authfd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: authfd.h,v 1.54 2026/01/27 06:48:29 djm Exp $ */ +/* $OpenBSD: authfd.h,v 1.55 2026/03/05 05:44:15 djm Exp $ */ /* * Author: Tatu Ylonen @@ -67,6 +67,8 @@ int ssh_agent_bind_hostkey(int sock, const struct sshkey *key, const struct sshbuf *session_id, const struct sshbuf *signature, int forwarding); +int ssh_agent_query_extensions(int sock, char ***exts); + /* Messages for the authentication agent connection. */ #define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1 #define SSH_AGENT_RSA_IDENTITIES_ANSWER 2 diff --git a/ssh-add.1 b/ssh-add.1 index babe78040..af5f8f7b0 100644 --- a/ssh-add.1 +++ b/ssh-add.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-add.1,v 1.88 2025/09/11 02:54:42 djm Exp $ +.\" $OpenBSD: ssh-add.1,v 1.89 2026/03/05 05:44:15 djm Exp $ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -35,7 +35,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: September 11 2025 $ +.Dd $Mdocdate: March 5 2026 $ .Dt SSH-ADD 1 .Os .Sh NAME @@ -59,6 +59,8 @@ .Nm ssh-add .Fl T .Ar pubkey ... +.Nm ssh-add +.Fl Q .Sh DESCRIPTION .Nm adds private key identities to the authentication agent, @@ -230,6 +232,9 @@ will request that the agent automatically delete the certificate shortly after the certificate's expiry date. This flag suppresses this behaviour and does not specify a lifetime for certificates added to an agent. +.It Fl Q +Query the agent for the list of protocol extensions it supports. +Note: not all agents support this query. .It Fl q Be quiet after a successful operation. .It Fl S Ar provider diff --git a/ssh-add.c b/ssh-add.c index dcdbbfeca..1e9eddf90 100644 --- a/ssh-add.c +++ b/ssh-add.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-add.c,v 1.185 2026/02/11 17:01:34 dtucker Exp $ */ +/* $OpenBSD: ssh-add.c,v 1.186 2026/03/05 05:44:15 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -237,6 +237,21 @@ delete_all(int agent_fd, int qflag) return ret; } +static int +query_exts(int agent_fd) +{ + int r; + char **exts = NULL; + size_t i; + + if ((r = ssh_agent_query_extensions(agent_fd, &exts)) != 0) + fatal_r(r, "unable to query supported extensions"); + for (i = 0; exts != NULL && exts[i] != NULL; i++) + puts(exts[i]); + stringlist_free(exts); + return 0; +} + static int check_cert_lifetime(const struct sshkey *cert, int cert_lifetime) { @@ -803,7 +818,7 @@ main(int argc, char **argv) char **dest_constraint_strings = NULL, **hostkey_files = NULL; int r, i, ch, deleting = 0, ret = 0, key_only = 0, cert_only = 0; int do_download = 0, xflag = 0, lflag = 0, Dflag = 0; - int qflag = 0, Tflag = 0, Nflag = 0; + int Qflag = 0, qflag = 0, Tflag = 0, Nflag = 0; SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; LogLevel log_level = SYSLOG_LEVEL_INFO; struct sshkey *k, **certs = NULL; @@ -835,7 +850,7 @@ main(int argc, char **argv) skprovider = getenv("SSH_SK_PROVIDER"); - while ((ch = getopt(argc, argv, "vkKlLNCcdDTxXE:e:h:H:M:m:qs:S:t:")) != -1) { + while ((ch = getopt(argc, argv, "vkKlLNCcdDTxXE:e:h:H:M:m:Qqs:S:t:")) != -1) { switch (ch) { case 'v': if (log_level == SYSLOG_LEVEL_INFO) @@ -912,6 +927,9 @@ main(int argc, char **argv) case 'q': qflag = 1; break; + case 'Q': + Qflag = 1; + break; case 'T': Tflag = 1; break; @@ -923,7 +941,7 @@ main(int argc, char **argv) } log_init(__progname, log_level, log_facility, 1); - if ((xflag != 0) + (lflag != 0) + (Dflag != 0) > 1) + if ((xflag != 0) + (lflag != 0) + (Dflag != 0) + (Qflag != 0) > 1) fatal("Invalid combination of actions"); else if (xflag) { if (lock_agent(agent_fd, xflag == 'x' ? 1 : 0) == -1) @@ -937,6 +955,10 @@ main(int argc, char **argv) if (delete_all(agent_fd, qflag) == -1) ret = 1; goto done; + } else if (Qflag) { + if (query_exts(agent_fd) == -1) + ret = 1; + goto done; } #ifdef ENABLE_SK_INTERNAL