aboutsummaryrefslogtreecommitdiff
path: root/ssh-add.c
diff options
context:
space:
mode:
Diffstat (limited to 'ssh-add.c')
-rw-r--r--ssh-add.c224
1 files changed, 119 insertions, 105 deletions
diff --git a/ssh-add.c b/ssh-add.c
index 0035cb84a0c1..2d5bec89cec5 100644
--- a/ssh-add.c
+++ b/ssh-add.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-add.c,v 1.173 2024/09/06 02:30:44 djm Exp $ */
+/* $OpenBSD: ssh-add.c,v 1.181 2025/09/29 03:17:54 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -54,6 +54,7 @@
#include <string.h>
#include <unistd.h>
#include <limits.h>
+#include <time.h>
#include "xmalloc.h"
#include "ssh.h"
@@ -70,6 +71,8 @@
#include "sk-api.h"
#include "hostfile.h"
+#define CERT_EXPIRY_GRACE (5*60)
+
/* argv0 */
extern char *__progname;
@@ -84,10 +87,6 @@ static char *default_files[] = {
#endif /* WITH_OPENSSL */
_PATH_SSH_CLIENT_ID_ED25519,
_PATH_SSH_CLIENT_ID_ED25519_SK,
- _PATH_SSH_CLIENT_ID_XMSS,
-#ifdef WITH_DSA
- _PATH_SSH_CLIENT_ID_DSA,
-#endif
NULL
};
@@ -99,10 +98,6 @@ static int lifetime = 0;
/* User has to confirm key use */
static int confirm = 0;
-/* Maximum number of signatures (XMSS) */
-static u_int maxsign = 0;
-static u_int minleft = 0;
-
/* we keep a cache of one passphrase */
static char *pass = NULL;
static void
@@ -243,19 +238,36 @@ delete_all(int agent_fd, int qflag)
}
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
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,
size_t ndest_constraints)
{
- struct sshkey *private, *cert;
+ struct sshkey *private = NULL, *cert = NULL;
char *comment = NULL;
char msg[1024], *certpath = NULL;
- int r, fd, ret = -1;
- size_t i;
- u_int32_t left;
+ int cert_lifetime, r, fd, ret = -1;
struct sshbuf *keyblob;
- struct ssh_identitylist *idlist;
if (strcmp(filename, "-") == 0) {
fd = STDIN_FILENO;
@@ -331,38 +343,6 @@ add_file(int agent_fd, const char *filename, int key_only, int cert_only,
comment = xstrdup(filename);
sshbuf_free(keyblob);
- /* For XMSS */
- if ((r = sshkey_set_filename(private, filename)) != 0) {
- fprintf(stderr, "Could not add filename to private key: %s (%s)\n",
- filename, comment);
- goto out;
- }
- if (maxsign && minleft &&
- (r = ssh_fetch_identitylist(agent_fd, &idlist)) == 0) {
- for (i = 0; i < idlist->nkeys; i++) {
- if (!sshkey_equal_public(idlist->keys[i], private))
- continue;
- left = sshkey_signatures_left(idlist->keys[i]);
- if (left < minleft) {
- fprintf(stderr,
- "Only %d signatures left.\n", left);
- break;
- }
- fprintf(stderr, "Skipping update: ");
- if (left == minleft) {
- fprintf(stderr,
- "required signatures left (%d).\n", left);
- } else {
- fprintf(stderr,
- "more signatures left (%d) than"
- " required (%d).\n", left, minleft);
- }
- ssh_free_identitylist(idlist);
- goto out;
- }
- ssh_free_identitylist(idlist);
- }
-
if (sshkey_is_sk(private)) {
if (skprovider == NULL) {
fprintf(stderr, "Cannot load FIDO key %s "
@@ -374,26 +354,27 @@ add_file(int agent_fd, const char *filename, int key_only, int cert_only,
skprovider = NULL;
}
- if (!cert_only &&
- (r = ssh_add_identity_constrained(agent_fd, private, comment,
- lifetime, confirm, maxsign, skprovider,
- dest_constraints, ndest_constraints)) == 0) {
- ret = 0;
- if (!qflag) {
- fprintf(stderr, "Identity added: %s (%s)\n",
- filename, comment);
- if (lifetime != 0) {
- fprintf(stderr,
- "Lifetime set to %d seconds\n", lifetime);
- }
- if (confirm != 0) {
- fprintf(stderr, "The user must confirm "
- "each use of the key\n");
+ if (!cert_only) {
+ if ((r = ssh_add_identity_constrained(agent_fd, private,
+ comment, lifetime, confirm, skprovider,
+ dest_constraints, ndest_constraints)) == 0) {
+ ret = 0;
+ if (!qflag) {
+ fprintf(stderr, "Identity added: %s (%s)\n",
+ filename, comment);
+ if (lifetime != 0) {
+ fprintf(stderr, "Lifetime set to %s\n",
+ fmt_timeframe((time_t)lifetime));
+ }
+ if (confirm != 0) {
+ fprintf(stderr, "The user must confirm "
+ "each use of the key\n");
+ }
}
+ } else {
+ fprintf(stderr, "Could not add identity \"%s\": %s\n",
+ filename, ssh_err(r));
}
- } else {
- fprintf(stderr, "Could not add identity \"%s\": %s\n",
- filename, ssh_err(r));
}
/* Skip trying to load the cert if requested */
@@ -412,25 +393,28 @@ add_file(int agent_fd, const char *filename, int key_only, int cert_only,
if (!sshkey_equal_public(cert, private)) {
error("Certificate %s does not match private key %s",
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;
}
/* Graft with private bits */
if ((r = sshkey_to_certified(private)) != 0) {
error_fr(r, "sshkey_to_certified");
- sshkey_free(cert);
goto out;
}
if ((r = sshkey_cert_copy(cert, private)) != 0) {
error_fr(r, "sshkey_cert_copy");
- sshkey_free(cert);
goto out;
}
- sshkey_free(cert);
-
+ /* send to agent */
if ((r = ssh_add_identity_constrained(agent_fd, private, comment,
- lifetime, confirm, maxsign, skprovider,
+ cert_lifetime, confirm, skprovider,
dest_constraints, ndest_constraints)) != 0) {
error_r(r, "Certificate %s (%s) add failed", certpath,
private->cert->key_id);
@@ -440,9 +424,9 @@ add_file(int agent_fd, const char *filename, int key_only, int cert_only,
if (!qflag) {
fprintf(stderr, "Certificate added: %s (%s)\n", certpath,
private->cert->key_id);
- if (lifetime != 0) {
- fprintf(stderr, "Lifetime set to %d seconds\n",
- lifetime);
+ if (cert_lifetime != 0) {
+ fprintf(stderr, "Lifetime set to %s\n",
+ fmt_timeframe((time_t)cert_lifetime));
}
if (confirm != 0) {
fprintf(stderr, "The user must confirm each use "
@@ -453,6 +437,7 @@ add_file(int agent_fd, const char *filename, int key_only, int cert_only,
out:
free(certpath);
free(comment);
+ sshkey_free(cert);
sshkey_free(private);
return ret;
@@ -534,7 +519,6 @@ list_identities(int agent_fd, int do_fp)
char *fp;
int r;
struct ssh_identitylist *idlist;
- u_int32_t left;
size_t i;
if ((r = ssh_fetch_identitylist(agent_fd, &idlist)) != 0) {
@@ -559,12 +543,7 @@ list_identities(int agent_fd, int do_fp)
ssh_err(r));
continue;
}
- fprintf(stdout, " %s", idlist->comments[i]);
- left = sshkey_signatures_left(idlist->keys[i]);
- if (left > 0)
- fprintf(stdout,
- " [signatures left %d]", left);
- fprintf(stdout, "\n");
+ fprintf(stdout, " %s\n", idlist->comments[i]);
}
}
ssh_free_identitylist(idlist);
@@ -623,7 +602,7 @@ load_resident_keys(int agent_fd, const char *skprovider, int qflag,
fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
fatal_f("sshkey_fingerprint failed");
if ((r = ssh_add_identity_constrained(agent_fd, key, "",
- lifetime, confirm, maxsign, skprovider,
+ lifetime, confirm, skprovider,
dest_constraints, ndest_constraints)) != 0) {
error("Unable to add key %s %s",
sshkey_type(key), fp);
@@ -655,7 +634,7 @@ load_resident_keys(int agent_fd, const char *skprovider, int qflag,
static int
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)
{
if (deleting) {
@@ -663,7 +642,7 @@ do_file(int agent_fd, int deleting, int key_only, int cert_only,
cert_only, qflag) == -1)
return -1;
} 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)
return -1;
}
@@ -687,6 +666,47 @@ stringlist_append(char ***listp, const char *s)
}
static void
+stringlist_free(char **list)
+{
+ size_t i = 0;
+
+ if (list == NULL)
+ return;
+ for (i = 0; list[i] != NULL; i++)
+ free(list[i]);
+ free(list);
+}
+
+static void
+free_dest_constraint_hop(struct dest_constraint_hop *dch)
+{
+ u_int i;
+
+ if (dch == NULL)
+ return;
+ free(dch->user);
+ free(dch->hostname);
+ for (i = 0; i < dch->nkeys; i++)
+ sshkey_free(dch->keys[i]);
+ free(dch->keys);
+ free(dch->key_is_ca);
+}
+
+static void
+free_dest_constraints(struct dest_constraint **dcs, size_t ndcs)
+{
+ size_t i;
+
+ for (i = 0; i < ndcs; i++) {
+ free_dest_constraint_hop(&dcs[i]->from);
+ free_dest_constraint_hop(&dcs[i]->to);
+ free(dcs[i]);
+ }
+ free(dcs);
+}
+
+
+static void
parse_dest_constraint_hop(const char *s, struct dest_constraint_hop *dch,
char **hostkey_files)
{
@@ -794,9 +814,6 @@ usage(void)
fprintf(stderr,
"usage: ssh-add [-CcDdKkLlqvXx] [-E fingerprint_hash] [-H hostkey_file]\n"
" [-h destination_constraint] [-S provider] [-t life]\n"
-#ifdef WITH_XMSS
-" [-M maxsign] [-m minleft]\n"
-#endif
" [file ...]\n"
" ssh-add -s pkcs11 [-Cv] [certificate ...]\n"
" ssh-add -e pkcs11\n"
@@ -814,12 +831,12 @@ 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;
+ int qflag = 0, Tflag = 0, Nflag = 0;
SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
LogLevel log_level = SYSLOG_LEVEL_INFO;
struct sshkey *k, **certs = NULL;
struct dest_constraint **dest_constraints = NULL;
- size_t ndest_constraints = 0, ncerts = 0;
+ size_t n, ndest_constraints = 0, ncerts = 0;
/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
sanitise_stdfd();
@@ -846,7 +863,7 @@ main(int argc, char **argv)
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, "vkKlLNCcdDTxXE:e:h:H:M:m:qs:S:t:")) != -1) {
switch (ch) {
case 'v':
if (log_level == SYSLOG_LEVEL_INFO)
@@ -854,6 +871,9 @@ main(int argc, char **argv)
else if (log_level < SYSLOG_LEVEL_DEBUG3)
log_level++;
break;
+ case 'N':
+ Nflag = 1;
+ break;
case 'E':
fingerprint_hash = ssh_digest_alg_by_name(optarg);
if (fingerprint_hash == -1)
@@ -890,20 +910,8 @@ main(int argc, char **argv)
confirm = 1;
break;
case 'm':
- minleft = (u_int)strtonum(optarg, 1, UINT_MAX, NULL);
- if (minleft == 0) {
- usage();
- ret = 1;
- goto done;
- }
- break;
case 'M':
- maxsign = (u_int)strtonum(optarg, 1, UINT_MAX, NULL);
- if (maxsign == 0) {
- usage();
- ret = 1;
- goto done;
- }
+ /* deprecated */
break;
case 'd':
deleting = 1;
@@ -1003,6 +1011,9 @@ main(int argc, char **argv)
dest_constraints, ndest_constraints,
certs, ncerts) == -1)
ret = 1;
+ for (n = 0; n < ncerts; n++)
+ sshkey_free(certs[n]);
+ free(certs);
goto done;
}
if (do_download) {
@@ -1032,7 +1043,7 @@ main(int argc, char **argv)
if (stat(buf, &st) == -1)
continue;
if (do_file(agent_fd, deleting, key_only, cert_only,
- buf, qflag, skprovider,
+ buf, qflag, Nflag, skprovider,
dest_constraints, ndest_constraints) == -1)
ret = 1;
else
@@ -1043,13 +1054,16 @@ main(int argc, char **argv)
} else {
for (i = 0; i < argc; i++) {
if (do_file(agent_fd, deleting, key_only, cert_only,
- argv[i], qflag, skprovider,
+ argv[i], qflag, Nflag, skprovider,
dest_constraints, ndest_constraints) == -1)
ret = 1;
}
}
done:
clear_pass();
+ stringlist_free(hostkey_files);
+ stringlist_free(dest_constraint_strings);
+ free_dest_constraints(dest_constraints, ndest_constraints);
ssh_close_authentication_socket(agent_fd);
return ret;
}