-/* $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 <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
#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 */
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;
+}
-/* $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 <ylo@cs.hut.fi>
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
-.\" $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 <ylo@cs.hut.fi>
.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
.\" (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
.Nm ssh-add
.Fl T
.Ar pubkey ...
+.Nm ssh-add
+.Fl Q
.Sh DESCRIPTION
.Nm
adds private key identities to the authentication agent,
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
-/* $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 <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
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)
{
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;
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)
case 'q':
qflag = 1;
break;
+ case 'Q':
+ Qflag = 1;
+ break;
case 'T':
Tflag = 1;
break;
}
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)
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