summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDamien Miller <djm@mindrot.org>2005-03-01 21:48:35 +1100
committerDamien Miller <djm@mindrot.org>2005-03-01 21:48:35 +1100
commit4b42d7f195fc38bc6eda25c2e0782321ff1f2a16 (patch)
treef945653699b303d57203f950eba69946d2a59039
parentdb7b8171ee084cbbe3a5ec5ef26df88e17aa3905 (diff)
- djm@cvs.openbsd.org 2005/03/01 10:42:49
[ssh-keygen.1 ssh-keygen.c ssh_config.5] add tools for managing known_hosts files with hashed hostnames, including hashing existing files and deleting hosts by name; ok markus@ deraadt@
-rw-r--r--ChangeLog6
-rw-r--r--ssh-keygen.143
-rw-r--r--ssh-keygen.c228
-rw-r--r--ssh_config.55
4 files changed, 268 insertions, 14 deletions
diff --git a/ChangeLog b/ChangeLog
index fd307622..762f8dce 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -36,6 +36,10 @@
- djm@cvs.openbsd.org 2005/03/01 10:41:28
[ssh-keyscan.1 ssh-keyscan.c]
option to hash hostnames output by ssh-keyscan; ok markus@ deraadt@
+ - djm@cvs.openbsd.org 2005/03/01 10:42:49
+ [ssh-keygen.1 ssh-keygen.c ssh_config.5]
+ add tools for managing known_hosts files with hashed hostnames, including
+ hashing existing files and deleting hosts by name; ok markus@ deraadt@
20050226
- (dtucker) [openbsd-compat/bsd-openpty.c openbsd-compat/inet_ntop.c]
@@ -2212,4 +2216,4 @@
- (djm) Trim deprecated options from INSTALL. Mention UsePAM
- (djm) Fix quote handling in sftp; Patch from admorten AT umich.edu
-$Id: ChangeLog,v 1.3674 2005/03/01 10:48:03 djm Exp $
+$Id: ChangeLog,v 1.3675 2005/03/01 10:48:35 djm Exp $
diff --git a/ssh-keygen.1 b/ssh-keygen.1
index c0f24dcd..f4c5ebcb 100644
--- a/ssh-keygen.1
+++ b/ssh-keygen.1
@@ -1,4 +1,4 @@
-.\" $OpenBSD: ssh-keygen.1,v 1.63 2004/08/13 00:01:43 jmc Exp $
+.\" $OpenBSD: ssh-keygen.1,v 1.64 2005/03/01 10:42:49 djm Exp $
.\"
.\" -*- nroff -*-
.\"
@@ -81,6 +81,15 @@
.Nm ssh-keygen
.Fl D Ar reader
.Nm ssh-keygen
+.Fl F Ar hostname
+.Op Fl f Ar known_hosts_file
+.Nm ssh-keygen
+.Fl H
+.Op Fl f Ar known_hosts_file
+.Nm ssh-keygen
+.Fl R Ar hostname
+.Op Fl f Ar known_hosts_file
+.Nm ssh-keygen
.Fl U Ar reader
.Op Fl f Ar input_keyfile
.Nm ssh-keygen
@@ -243,6 +252,38 @@ Provides the new comment.
.It Fl D Ar reader
Download the RSA public key stored in the smartcard in
.Ar reader .
+.It Fl F Ar hostname
+Search for the specified
+.Ar hostname
+in a
+.Pa known_hosts
+file, listing any occurances found.
+This option is useful to find hashed host names or addresses and may also be
+used in conjunction with the
+.Fl H
+option to print found keys in a hashed format.
+.It Fl H
+Hash a
+.Pa known_hosts
+file, printing the result to standard output.
+This replaces all hostnames and addresses with hashed representations.
+These hashes may be used normally by
+.Nm ssh
+and
+.Nm sshd ,
+but they do not reveal identifying information should the file's contents
+be disclosed.
+This option will not modify existing hashed hostnames and is therefore safe
+to use on files that mix hashed and non-hashed names.
+.It Fl R Ar hostname
+Removes all keys belonging to
+.Ar hostname
+from a
+.Pa known_hosts
+file.
+This option is useful to delete hashed hosts (see the
+.Fl H
+option above).
.It Fl G Ar output_file
Generate candidate primes for DH-GEX.
These primes must be screened for
diff --git a/ssh-keygen.c b/ssh-keygen.c
index 7ed62a30..00ddb90c 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -12,7 +12,7 @@
*/
#include "includes.h"
-RCSID("$OpenBSD: ssh-keygen.c,v 1.118 2004/12/23 17:38:07 markus Exp $");
+RCSID("$OpenBSD: ssh-keygen.c,v 1.119 2005/03/01 10:42:49 djm Exp $");
#include <openssl/evp.h>
#include <openssl/pem.h>
@@ -27,6 +27,8 @@ RCSID("$OpenBSD: ssh-keygen.c,v 1.118 2004/12/23 17:38:07 markus Exp $");
#include "pathnames.h"
#include "log.h"
#include "misc.h"
+#include "match.h"
+#include "hostfile.h"
#ifdef SMARTCARD
#include "scard.h"
@@ -50,6 +52,13 @@ int change_comment = 0;
int quiet = 0;
+/* Flag indicating that we want to hash a known_hosts file */
+int hash_hosts = 0;
+/* Flag indicating that we want lookup a host in known_hosts file */
+int find_host = 0;
+/* Flag indicating that we want to delete a host from a known_hosts file */
+int delete_host = 0;
+
/* Flag indicating that we just want to see the key fingerprint */
int print_fingerprint = 0;
int print_bubblebabble = 0;
@@ -541,6 +550,194 @@ do_fingerprint(struct passwd *pw)
exit(0);
}
+static void
+print_host(FILE *f, char *name, Key *public, int hash)
+{
+ if (hash && (name = host_hash(name, NULL, 0)) == NULL)
+ fatal("hash_host failed");
+ fprintf(f, "%s ", name);
+ if (!key_write(public, f))
+ fatal("key_write failed");
+ fprintf(f, "\n");
+}
+
+static void
+do_known_hosts(struct passwd *pw, const char *name)
+{
+ FILE *in, *out = stdout;
+ Key *public;
+ char *cp, *cp2, *kp, *kp2;
+ char line[16*1024], tmp[MAXPATHLEN], old[MAXPATHLEN];
+ int c, i, skip = 0, inplace = 0, num = 0, invalid = 0, has_unhashed = 0;
+
+ if (!have_identity) {
+ cp = tilde_expand_filename(_PATH_SSH_USER_HOSTFILE, pw->pw_uid);
+ if (strlcpy(identity_file, cp, sizeof(identity_file)) >=
+ sizeof(identity_file))
+ fatal("Specified known hosts path too long");
+ xfree(cp);
+ have_identity = 1;
+ }
+ if ((in = fopen(identity_file, "r")) == NULL)
+ fatal("fopen: %s", strerror(errno));
+
+ /*
+ * Find hosts goes to stdout, hash and deletions happen in-place
+ * A corner case is ssh-keygen -HF foo, which should go to stdout
+ */
+ if (!find_host && (hash_hosts || delete_host)) {
+ if (strlcpy(tmp, identity_file, sizeof(tmp)) >= sizeof(tmp) ||
+ strlcat(tmp, ".XXXXXXXXXX", sizeof(tmp)) >= sizeof(tmp) ||
+ strlcpy(old, identity_file, sizeof(old)) >= sizeof(old) ||
+ strlcat(old, ".old", sizeof(old)) >= sizeof(old))
+ fatal("known_hosts path too long");
+ umask(077);
+ if ((c = mkstemp(tmp)) == -1)
+ fatal("mkstemp: %s", strerror(errno));
+ if ((out = fdopen(c, "w")) == NULL) {
+ c = errno;
+ unlink(tmp);
+ fatal("fdopen: %s", strerror(c));
+ }
+ inplace = 1;
+ }
+
+ while (fgets(line, sizeof(line), in)) {
+ num++;
+ i = strlen(line) - 1;
+ if (line[i] != '\n') {
+ error("line %d too long: %.40s...", num, line);
+ skip = 1;
+ invalid = 1;
+ continue;
+ }
+ if (skip) {
+ skip = 0;
+ continue;
+ }
+ line[i] = '\0';
+
+ /* Skip leading whitespace, empty and comment lines. */
+ for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
+ ;
+ if (!*cp || *cp == '\n' || *cp == '#') {
+ if (inplace)
+ fprintf(out, "%s\n", cp);
+ continue;
+ }
+ /* Find the end of the host name portion. */
+ for (kp = cp; *kp && *kp != ' ' && *kp != '\t'; kp++)
+ ;
+ if (*kp == '\0' || *(kp + 1) == '\0') {
+ error("line %d missing key: %.40s...",
+ num, line);
+ invalid = 1;
+ continue;
+ }
+ *kp++ = '\0';
+ kp2 = kp;
+
+ public = key_new(KEY_RSA1);
+ if (key_read(public, &kp) != 1) {
+ kp = kp2;
+ key_free(public);
+ public = key_new(KEY_UNSPEC);
+ if (key_read(public, &kp) != 1) {
+ error("line %d invalid key: %.40s...",
+ num, line);
+ key_free(public);
+ invalid = 1;
+ continue;
+ }
+ }
+
+ if (*cp == HASH_DELIM) {
+ if (find_host || delete_host) {
+ cp2 = host_hash(name, cp, strlen(cp));
+ if (cp2 == NULL) {
+ error("line %d: invalid hashed "
+ "name: %.64s...", num, line);
+ invalid = 1;
+ continue;
+ }
+ c = (strcmp(cp2, cp) == 0);
+ if (find_host && c) {
+ printf("# Host %s found: "
+ "line %d type %s\n", name,
+ num, key_type(public));
+ print_host(out, cp, public, 0);
+ }
+ if (delete_host && !c)
+ print_host(out, cp, public, 0);
+ } else if (hash_hosts)
+ print_host(out, cp, public, 0);
+ } else {
+ if (find_host || delete_host) {
+ c = (match_hostname(name, cp,
+ strlen(cp)) == 1);
+ if (find_host && c) {
+ printf("# Host %s found: "
+ "line %d type %s\n", name,
+ num, key_type(public));
+ print_host(out, cp, public, hash_hosts);
+ }
+ if (delete_host && !c)
+ print_host(out, cp, public, 0);
+ } else if (hash_hosts) {
+ for(cp2 = strsep(&cp, ",");
+ cp2 != NULL && *cp2 != '\0';
+ cp2 = strsep(&cp, ","))
+ print_host(out, cp2, public, 1);
+ has_unhashed = 1;
+ }
+ }
+ key_free(public);
+ }
+ fclose(in);
+
+ if (invalid) {
+ fprintf(stderr, "%s is not a valid known_host file.\n",
+ identity_file);
+ if (inplace) {
+ fprintf(stderr, "Not replacing existing known_hosts "
+ "file beacuse of errors");
+ fclose(out);
+ unlink(tmp);
+ }
+ exit(1);
+ }
+
+ if (inplace) {
+ fclose(out);
+
+ /* Backup existing file */
+ if (unlink(old) == -1 && errno != ENOENT)
+ fatal("unlink %.100s: %s", old, strerror(errno));
+ if (link(identity_file, old) == -1)
+ fatal("link %.100s to %.100s: %s", identity_file, old,
+ strerror(errno));
+ /* Move new one into place */
+ if (rename(tmp, identity_file) == -1) {
+ error("rename\"%s\" to \"%s\": %s", tmp, identity_file,
+ strerror(errno));
+ unlink(tmp);
+ unlink(old);
+ exit(1);
+ }
+
+ fprintf(stderr, "%s updated.\n", identity_file);
+ fprintf(stderr, "Original contents retained as %s\n", old);
+ if (has_unhashed) {
+ fprintf(stderr, "WARNING: %s contains unhashed "
+ "entries\n", old);
+ fprintf(stderr, "Delete this file to ensure privacy "
+ "of hostnames\n");
+ }
+ }
+
+ exit(0);
+}
+
/*
* Perform changing a passphrase. The argument is the passwd structure
* for the current user.
@@ -767,6 +964,8 @@ usage(void)
fprintf(stderr, " -y Read private key file and print public key.\n");
fprintf(stderr, " -t type Specify type of key to create.\n");
fprintf(stderr, " -B Show bubblebabble digest of key file.\n");
+ fprintf(stderr, " -H Hash names in known_hosts file\n");
+ fprintf(stderr, " -F hostname Find hostname in known hosts file\n");
fprintf(stderr, " -C comment Provide new comment.\n");
fprintf(stderr, " -N phrase Provide new passphrase.\n");
fprintf(stderr, " -P phrase Provide old passphrase.\n");
@@ -790,7 +989,7 @@ main(int ac, char **av)
{
char dotsshdir[MAXPATHLEN], comment[1024], *passphrase1, *passphrase2;
char out_file[MAXPATHLEN], *reader_id = NULL;
- char *resource_record_hostname = NULL;
+ char *rr_hostname = NULL;
Key *private, *public;
struct passwd *pw;
struct stat st;
@@ -824,7 +1023,7 @@ main(int ac, char **av)
}
while ((opt = getopt(ac, av,
- "degiqpclBRvxXyb:f:t:U:D:P:N:C:r:g:T:G:M:S:a:W:")) != -1) {
+ "degiqpclBHvxXyF:b:f:t:U:D:P:N:C:r:g:R:T:G:M:S:a:W:")) != -1) {
switch (opt) {
case 'b':
bits = atoi(optarg);
@@ -833,6 +1032,17 @@ main(int ac, char **av)
exit(1);
}
break;
+ case 'F':
+ find_host = 1;
+ rr_hostname = optarg;
+ break;
+ case 'H':
+ hash_hosts = 1;
+ break;
+ case 'R':
+ delete_host = 1;
+ rr_hostname = optarg;
+ break;
case 'l':
print_fingerprint = 1;
break;
@@ -864,10 +1074,6 @@ main(int ac, char **av)
case 'q':
quiet = 1;
break;
- case 'R':
- /* unused */
- exit(0);
- break;
case 'e':
case 'x':
/* export key */
@@ -902,7 +1108,7 @@ main(int ac, char **av)
}
break;
case 'r':
- resource_record_hostname = optarg;
+ rr_hostname = optarg;
break;
case 'W':
generator_wanted = atoi(optarg);
@@ -945,6 +1151,8 @@ main(int ac, char **av)
printf("Can only have one of -p and -c.\n");
usage();
}
+ if (delete_host || hash_hosts || find_host)
+ do_known_hosts(pw, rr_hostname);
if (print_fingerprint || print_bubblebabble)
do_fingerprint(pw);
if (change_passphrase)
@@ -957,8 +1165,8 @@ main(int ac, char **av)
do_convert_from_ssh2(pw);
if (print_public)
do_print_public(pw);
- if (resource_record_hostname != NULL) {
- do_print_resource_record(pw, resource_record_hostname);
+ if (rr_hostname != NULL) {
+ do_print_resource_record(pw, rr_hostname);
}
if (reader_id != NULL) {
#ifdef SMARTCARD
diff --git a/ssh_config.5 b/ssh_config.5
index 9077acbe..2a85485e 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -34,7 +34,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.
.\"
-.\" $OpenBSD: ssh_config.5,v 1.44 2005/03/01 10:40:27 djm Exp $
+.\" $OpenBSD: ssh_config.5,v 1.45 2005/03/01 10:42:49 djm Exp $
.Dd September 25, 1999
.Dt SSH_CONFIG 5
.Os
@@ -421,7 +421,8 @@ be disclosed.
The default is
.Dq no .
Note that hashing of names and addresses will not be retrospectively applied
-to existing known hosts files.
+to existing known hosts files, but these may be manually hashed using
+.Xr ssh-keygen 1 .
.It Cm HostbasedAuthentication
Specifies whether to try rhosts based authentication with public key
authentication.