mirror of
https://github.com/openssh/openssh-portable.git
synced 2026-01-12 00:04:08 +08:00
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:
committed by
Damien Miller
parent
e9dcccc354
commit
0c719c6aab
13
ssh-add.1
13
ssh-add.1
@@ -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
|
||||||
|
|||||||
71
ssh-add.c
71
ssh-add.c
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user