upstream: When adding certificates to an agent, set the expiry to

the certificate expiry time plus a short (5 min) grace period.

This will cause the agent to automtically remove certificates shortly
after they expire.

A new ssh-add -N option disables this behaviour.

Feedback/ok deraadt@

OpenBSD-Commit-ID: 92fed1bba1025069ad45deebb534be7530e181df
This commit is contained in:
djm@openbsd.org
2025-09-11 02:54:42 +00:00
committed by Damien Miller
parent e9dcccc354
commit 0c719c6aab
2 changed files with 60 additions and 24 deletions

View File

@@ -1,4 +1,4 @@
.\" $OpenBSD: ssh-add.1,v 1.87 2024/06/17 08:30:29 djm Exp $ .\" $OpenBSD: ssh-add.1,v 1.88 2025/09/11 02:54:42 djm Exp $
.\" .\"
.\" Author: Tatu Ylonen <ylo@cs.hut.fi> .\" Author: Tatu Ylonen <ylo@cs.hut.fi>
.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland .\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -35,7 +35,7 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\" .\"
.Dd $Mdocdate: June 17 2024 $ .Dd $Mdocdate: September 11 2025 $
.Dt SSH-ADD 1 .Dt SSH-ADD 1
.Os .Os
.Sh NAME .Sh NAME
@@ -43,7 +43,7 @@
.Nd adds private key identities to the OpenSSH authentication agent .Nd adds private key identities to the OpenSSH authentication agent
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm ssh-add .Nm ssh-add
.Op Fl CcDdKkLlqvXx .Op Fl CcDdKkLlNqvXx
.Op Fl E Ar fingerprint_hash .Op Fl E Ar fingerprint_hash
.Op Fl H Ar hostkey_file .Op Fl H Ar hostkey_file
.Op Fl h Ar destination_constraint .Op Fl h Ar destination_constraint
@@ -223,6 +223,13 @@ Lists public key parameters of all identities currently represented
by the agent. by the agent.
.It Fl l .It Fl l
Lists fingerprints of all identities currently represented by the agent. Lists fingerprints of all identities currently represented by the agent.
.It Fl N
When adding certificates, by default
.Nm
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 .It Fl q
Be quiet after a successful operation. Be quiet after a successful operation.
.It Fl S Ar provider .It Fl S Ar provider

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: ssh-add.c,v 1.175 2025/08/29 03:50:38 djm Exp $ */ /* $OpenBSD: ssh-add.c,v 1.176 2025/09/11 02:54:42 djm Exp $ */
/* /*
* Author: Tatu Ylonen <ylo@cs.hut.fi> * Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -70,6 +70,8 @@
#include "sk-api.h" #include "sk-api.h"
#include "hostfile.h" #include "hostfile.h"
#define CERT_EXPIRY_GRACE (5*60)
/* argv0 */ /* argv0 */
extern char *__progname; extern char *__progname;
@@ -234,16 +236,36 @@ delete_all(int agent_fd, int qflag)
return ret; return ret;
} }
static int
check_cert_lifetime(const struct sshkey *cert, int cert_lifetime)
{
time_t now;
uint64_t n;
if (cert == NULL || cert->cert == NULL || !sshkey_is_cert(cert) ||
cert->cert->valid_before == 0xFFFFFFFFFFFFFFFFULL)
return cert_lifetime;
if ((now = time(NULL)) <= 0)
fatal_f("system time is at/before epoch");
if ((uint64_t)now > (cert->cert->valid_before + CERT_EXPIRY_GRACE))
return -1; /* certificate already expired */
n = (CERT_EXPIRY_GRACE + cert->cert->valid_before) - (uint64_t)now;
n = MINIMUM(n, INT_MAX);
if (cert_lifetime <= 0)
return (int)n;
return MINIMUM(cert_lifetime, (int)n);
}
static int static int
add_file(int agent_fd, const char *filename, int key_only, int cert_only, add_file(int agent_fd, const char *filename, int key_only, int cert_only,
int qflag, const char *skprovider, int qflag, int Nflag, const char *skprovider,
struct dest_constraint **dest_constraints, struct dest_constraint **dest_constraints,
size_t ndest_constraints) size_t ndest_constraints)
{ {
struct sshkey *private, *cert; struct sshkey *private = NULL, *cert = NULL;
char *comment = NULL; char *comment = NULL;
char msg[1024], *certpath = NULL; char msg[1024], *certpath = NULL;
int r, fd, ret = -1; int cert_lifetime, r, fd, ret = -1;
struct sshbuf *keyblob; struct sshbuf *keyblob;
if (strcmp(filename, "-") == 0) { if (strcmp(filename, "-") == 0) {
@@ -340,8 +362,8 @@ add_file(int agent_fd, const char *filename, int key_only, int cert_only,
fprintf(stderr, "Identity added: %s (%s)\n", fprintf(stderr, "Identity added: %s (%s)\n",
filename, comment); filename, comment);
if (lifetime != 0) { if (lifetime != 0) {
fprintf(stderr, fprintf(stderr, "Lifetime set to %s\n",
"Lifetime set to %d seconds\n", lifetime); fmt_timeframe((time_t)lifetime));
} }
if (confirm != 0) { if (confirm != 0) {
fprintf(stderr, "The user must confirm " fprintf(stderr, "The user must confirm "
@@ -369,25 +391,28 @@ add_file(int agent_fd, const char *filename, int key_only, int cert_only,
if (!sshkey_equal_public(cert, private)) { if (!sshkey_equal_public(cert, private)) {
error("Certificate %s does not match private key %s", error("Certificate %s does not match private key %s",
certpath, filename); certpath, filename);
sshkey_free(cert); goto out;
}
cert_lifetime = lifetime;
if (!Nflag &&
(cert_lifetime = check_cert_lifetime(cert, cert_lifetime)) == -1) {
logit("Certificate %s has already expired; ignored", certpath);
goto out; goto out;
} }
/* Graft with private bits */ /* Graft with private bits */
if ((r = sshkey_to_certified(private)) != 0) { if ((r = sshkey_to_certified(private)) != 0) {
error_fr(r, "sshkey_to_certified"); error_fr(r, "sshkey_to_certified");
sshkey_free(cert);
goto out; goto out;
} }
if ((r = sshkey_cert_copy(cert, private)) != 0) { if ((r = sshkey_cert_copy(cert, private)) != 0) {
error_fr(r, "sshkey_cert_copy"); error_fr(r, "sshkey_cert_copy");
sshkey_free(cert);
goto out; goto out;
} }
sshkey_free(cert); /* send to agent */
if ((r = ssh_add_identity_constrained(agent_fd, private, comment, if ((r = ssh_add_identity_constrained(agent_fd, private, comment,
lifetime, confirm, skprovider, cert_lifetime, confirm, skprovider,
dest_constraints, ndest_constraints)) != 0) { dest_constraints, ndest_constraints)) != 0) {
error_r(r, "Certificate %s (%s) add failed", certpath, error_r(r, "Certificate %s (%s) add failed", certpath,
private->cert->key_id); private->cert->key_id);
@@ -397,9 +422,9 @@ add_file(int agent_fd, const char *filename, int key_only, int cert_only,
if (!qflag) { if (!qflag) {
fprintf(stderr, "Certificate added: %s (%s)\n", certpath, fprintf(stderr, "Certificate added: %s (%s)\n", certpath,
private->cert->key_id); private->cert->key_id);
if (lifetime != 0) { if (cert_lifetime != 0) {
fprintf(stderr, "Lifetime set to %d seconds\n", fprintf(stderr, "Lifetime set to %s\n",
lifetime); fmt_timeframe((time_t)cert_lifetime));
} }
if (confirm != 0) { if (confirm != 0) {
fprintf(stderr, "The user must confirm each use " fprintf(stderr, "The user must confirm each use "
@@ -410,6 +435,7 @@ add_file(int agent_fd, const char *filename, int key_only, int cert_only,
out: out:
free(certpath); free(certpath);
free(comment); free(comment);
sshkey_free(cert);
sshkey_free(private); sshkey_free(private);
return ret; return ret;
@@ -606,7 +632,7 @@ load_resident_keys(int agent_fd, const char *skprovider, int qflag,
static int static int
do_file(int agent_fd, int deleting, int key_only, int cert_only, do_file(int agent_fd, int deleting, int key_only, int cert_only,
char *file, int qflag, const char *skprovider, char *file, int qflag, int Nflag, const char *skprovider,
struct dest_constraint **dest_constraints, size_t ndest_constraints) struct dest_constraint **dest_constraints, size_t ndest_constraints)
{ {
if (deleting) { if (deleting) {
@@ -614,7 +640,7 @@ do_file(int agent_fd, int deleting, int key_only, int cert_only,
cert_only, qflag) == -1) cert_only, qflag) == -1)
return -1; return -1;
} else { } else {
if (add_file(agent_fd, file, key_only, cert_only, qflag, if (add_file(agent_fd, file, key_only, cert_only, qflag, Nflag,
skprovider, dest_constraints, ndest_constraints) == -1) skprovider, dest_constraints, ndest_constraints) == -1)
return -1; return -1;
} }
@@ -762,7 +788,7 @@ main(int argc, char **argv)
char **dest_constraint_strings = NULL, **hostkey_files = NULL; char **dest_constraint_strings = NULL, **hostkey_files = NULL;
int r, i, ch, deleting = 0, ret = 0, key_only = 0, cert_only = 0; 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 do_download = 0, xflag = 0, lflag = 0, Dflag = 0;
int qflag = 0, Tflag = 0; int qflag = 0, Tflag = 0, Nflag = 0;
SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
LogLevel log_level = SYSLOG_LEVEL_INFO; LogLevel log_level = SYSLOG_LEVEL_INFO;
struct sshkey *k, **certs = NULL; struct sshkey *k, **certs = NULL;
@@ -794,7 +820,7 @@ main(int argc, char **argv)
skprovider = getenv("SSH_SK_PROVIDER"); skprovider = getenv("SSH_SK_PROVIDER");
while ((ch = getopt(argc, argv, "vkKlLCcdDTxXE:e:h:H:M:m:qs:S:t:")) != -1) { while ((ch = getopt(argc, argv, "vVkKlLCcdDTxXE:e:h:H:M:m:qs:S:t:")) != -1) {
switch (ch) { switch (ch) {
case 'v': case 'v':
if (log_level == SYSLOG_LEVEL_INFO) if (log_level == SYSLOG_LEVEL_INFO)
@@ -802,6 +828,9 @@ main(int argc, char **argv)
else if (log_level < SYSLOG_LEVEL_DEBUG3) else if (log_level < SYSLOG_LEVEL_DEBUG3)
log_level++; log_level++;
break; break;
case 'V':
Nflag = 1;
break;
case 'E': case 'E':
fingerprint_hash = ssh_digest_alg_by_name(optarg); fingerprint_hash = ssh_digest_alg_by_name(optarg);
if (fingerprint_hash == -1) if (fingerprint_hash == -1)
@@ -968,7 +997,7 @@ main(int argc, char **argv)
if (stat(buf, &st) == -1) if (stat(buf, &st) == -1)
continue; continue;
if (do_file(agent_fd, deleting, key_only, cert_only, if (do_file(agent_fd, deleting, key_only, cert_only,
buf, qflag, skprovider, buf, qflag, Nflag, skprovider,
dest_constraints, ndest_constraints) == -1) dest_constraints, ndest_constraints) == -1)
ret = 1; ret = 1;
else else
@@ -979,7 +1008,7 @@ main(int argc, char **argv)
} else { } else {
for (i = 0; i < argc; i++) { for (i = 0; i < argc; i++) {
if (do_file(agent_fd, deleting, key_only, cert_only, if (do_file(agent_fd, deleting, key_only, cert_only,
argv[i], qflag, skprovider, argv[i], qflag, Nflag, skprovider,
dest_constraints, ndest_constraints) == -1) dest_constraints, ndest_constraints) == -1)
ret = 1; ret = 1;
} }