- jakob@cvs.openbsd.org 2003/05/14 18:16:20
     [key.c key.h readconf.c readconf.h ssh_config.5 sshconnect.c]
     [dns.c dns.h README.dns ssh-keygen.1 ssh-keygen.c]
     add experimental support for verifying hos keys using DNS as described
     in draft-ietf-secsh-dns-xx.txt. more information in README.dns.
     ok markus@ and henning@
diff --git a/ChangeLog b/ChangeLog
index 8ad7108..8feae8b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -4,6 +4,12 @@
      [ssh-agent.1]
      setup -> set up;
      from wiz@netbsd
+   - jakob@cvs.openbsd.org 2003/05/14 18:16:20
+     [key.c key.h readconf.c readconf.h ssh_config.5 sshconnect.c]
+     [dns.c dns.h README.dns ssh-keygen.1 ssh-keygen.c]
+     add experimental support for verifying hos keys using DNS as described
+     in draft-ietf-secsh-dns-xx.txt. more information in README.dns.
+     ok markus@ and henning@
 
 20030514
  - (djm) Bug #117: Don't lie to PAM about username
@@ -1479,4 +1485,4 @@
      save auth method before monitor_reset_key_state(); bugzilla bug #284;
      ok provos@
 
-$Id: ChangeLog,v 1.2701 2003/05/15 00:16:21 djm Exp $
+$Id: ChangeLog,v 1.2702 2003/05/15 00:19:46 djm Exp $
diff --git a/Makefile.in b/Makefile.in
index ba898db..f25fe0a 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -1,4 +1,4 @@
-# $Id: Makefile.in,v 1.232 2003/05/14 04:31:11 djm Exp $
+# $Id: Makefile.in,v 1.233 2003/05/15 00:19:46 djm Exp $
 
 # uncomment if you run a non bourne compatable shell. Ie. csh
 #SHELL = @SH@
@@ -62,11 +62,11 @@
 
 LIBSSH_OBJS=authfd.o authfile.o bufaux.o buffer.o canohost.o channels.o \
 	cipher.o compat.o compress.o crc32.o deattack.o fatal.o \
-	hostfile.o log.o match.o mpaux.o nchan.o packet.o radix.o readpass.o \
-	rsa.o tildexpand.o ttymodes.o xmalloc.o atomicio.o \
+	hostfile.o log.o match.o mpaux.o nchan.o packet.o radix.o \
+	readpass.o rsa.o tildexpand.o ttymodes.o xmalloc.o atomicio.o \
 	key.o dispatch.o kex.o mac.o uuencode.o misc.o \
 	rijndael.o ssh-dss.o ssh-rsa.o dh.o kexdh.o kexgex.o \
-	kexdhc.o kexgexc.o scard.o msg.o progressmeter.o \
+	kexdhc.o kexgexc.o scard.o msg.o progressmeter.o dns.o \
 	entropy.o
 
 SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \
diff --git a/README.dns b/README.dns
new file mode 100644
index 0000000..d6889b9
--- /dev/null
+++ b/README.dns
@@ -0,0 +1,55 @@
+How to verify host keys using OpenSSH and DNS
+---------------------------------------------
+
+OpenSSH contains experimental support for verifying host keys using DNS
+as described in draft-ietf-secsh-dns-xx.txt. The document contains
+very brief instructions on how to test this feature. Configuring DNS
+and DNSSEC is out of the scope of this document.
+
+
+(1) Enable DNS fingerprint support in OpenSSH
+
+Edit /usr/src/usr.bin/ssh/Makefile.inc and uncomment the line containing
+
+	CFLAGS+= -DDNS
+
+
+(2) Generate and publish the DNS RR
+
+To create a DNS resource record (RR) containing a fingerprint of the
+public host key, use the following command:
+
+	ssh-keygen -r hostname -f keyfile -g
+
+where "hostname" is your fully qualified hostname and "keyfile" is the
+file containing the public host key file. If you have multiple keys,
+you should generate one RR for each key.
+
+In the example above, ssh-keygen will print the fingerprint in a
+generic DNS RR format parsable by most modern name server
+implementations. If your nameserver has support for the SSHFP RR, as
+defined by the draft, you can omit the -g flag and ssh-keygen will
+print a standard RR.
+
+To publish the fingerprint using the DNS you must add the generated RR
+to your DNS zone file and sign your zone.
+
+
+(3) Enable the ssh client to verify host keys using DNS
+
+To enable the ssh client to verify host keys using DNS, you have to
+add the following option to the ssh configuration file
+($HOME/.ssh/config or /etc/ssh/ssh_config):
+
+    VerifyHostKeyDNS yes
+
+Upon connection the client will try to look up the fingerprint RR
+using DNS. If the fingerprint received from the DNS server matches
+the remote host key, the user will be notified.
+
+
+	Jakob Schlyter
+	Wesley Griffin
+
+
+$OpenBSD: README.dns,v 1.1 2003/05/14 18:16:20 jakob Exp $
diff --git a/dns.c b/dns.c
new file mode 100644
index 0000000..9b7a0e7
--- /dev/null
+++ b/dns.c
@@ -0,0 +1,293 @@
+/*	$OpenBSD: dns.c,v 1.4 2003/05/14 23:29:22 jakob Exp $	*/
+
+/*
+ * Copyright (c) 2003 Wesley Griffin. All rights reserved.
+ * Copyright (c) 2003 Jakob Schlyter. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "includes.h"
+
+#ifdef DNS
+#include <openssl/bn.h>
+#ifdef LWRES
+#include <lwres/netdb.h>
+#include <dns/result.h>
+#else /* LWRES */
+#include <netdb.h>
+#endif /* LWRES */
+
+#include "xmalloc.h"
+#include "key.h"
+#include "dns.h"
+#include "log.h"
+#include "uuencode.h"
+
+extern char *__progname;
+RCSID("$OpenBSD: dns.c,v 1.4 2003/05/14 23:29:22 jakob Exp $");
+
+#ifndef LWRES
+static const char *errset_text[] = {
+	"success",		/* 0 ERRSET_SUCCESS */
+	"out of memory",	/* 1 ERRSET_NOMEMORY */
+	"general failure",	/* 2 ERRSET_FAIL */
+	"invalid parameter",	/* 3 ERRSET_INVAL */
+	"name does not exist",	/* 4 ERRSET_NONAME */
+	"data does not exist",	/* 5 ERRSET_NODATA */
+};
+
+static const char *
+dns_result_totext(unsigned int error)
+{
+	switch (error) {
+	case ERRSET_SUCCESS:
+		return errset_text[ERRSET_SUCCESS];
+	case ERRSET_NOMEMORY:
+		return errset_text[ERRSET_NOMEMORY];
+	case ERRSET_FAIL:
+		return errset_text[ERRSET_FAIL];
+	case ERRSET_INVAL:
+		return errset_text[ERRSET_INVAL];
+	case ERRSET_NONAME:
+		return errset_text[ERRSET_NONAME];
+	case ERRSET_NODATA:
+		return errset_text[ERRSET_NODATA];
+	default:
+		return "unknown error";
+	}
+}
+#endif /* LWRES */
+
+
+/*
+ * Read SSHFP parameters from key buffer.
+ */
+static int
+dns_read_key(u_int8_t *algorithm, u_int8_t *digest_type,
+    u_char **digest, u_int *digest_len, Key *key)
+{
+	int success = 0;
+
+	switch (key->type) {
+	case KEY_RSA:
+		*algorithm = SSHFP_KEY_RSA;
+		break;
+	case KEY_DSA:
+		*algorithm = SSHFP_KEY_DSA;
+		break;
+	default:
+		*algorithm = SSHFP_KEY_RESERVED;
+	}
+
+	if (*algorithm) {
+		*digest_type = SSHFP_HASH_SHA1;
+		*digest = key_fingerprint_raw(key, SSH_FP_SHA1, digest_len);
+		success = 1;
+	} else {
+		*digest_type = SSHFP_HASH_RESERVED;
+		*digest = NULL;
+		*digest_len = 0;
+		success = 0;
+	}
+
+	return success;
+}
+
+/*
+ * Read SSHFP parameters from rdata buffer.
+ */
+static int
+dns_read_rdata(u_int8_t *algorithm, u_int8_t *digest_type,
+    u_char **digest, u_int *digest_len, u_char *rdata, int rdata_len)
+{
+	int success = 0;
+
+	*algorithm = SSHFP_KEY_RESERVED;
+	*digest_type = SSHFP_HASH_RESERVED;
+
+	if (rdata_len >= 2) {
+		*algorithm = rdata[0];
+		*digest_type = rdata[1];
+		*digest_len = rdata_len - 2;
+
+		if (*digest_len > 0) {
+			*digest = (u_char *) xmalloc(*digest_len);
+			memcpy(*digest, rdata + 2, *digest_len);
+		} else {
+			*digest = NULL;
+		}
+
+		success = 1;
+	}
+
+	return success;
+}
+
+
+/*
+ * Verify the given hostname, address and host key using DNS.
+ * Returns 0 if key verifies or -1 if key does NOT verify
+ */
+int
+verify_host_key_dns(const char *hostname, struct sockaddr *address,
+    Key *hostkey)
+{
+	int counter;
+	int result;
+	struct rrsetinfo *fingerprints = NULL;
+	int failures = 0;
+
+	u_int8_t hostkey_algorithm;
+	u_int8_t hostkey_digest_type;
+	u_char *hostkey_digest;
+	u_int hostkey_digest_len;
+
+	u_int8_t dnskey_algorithm;
+	u_int8_t dnskey_digest_type;
+	u_char *dnskey_digest;
+	u_int dnskey_digest_len;
+
+
+	debug3("verify_hostkey_dns");
+	if (hostkey == NULL)
+		fatal("No key to look up!");
+
+	result = getrrsetbyname(hostname, DNS_RDATACLASS_IN,
+	    DNS_RDATATYPE_SSHFP, 0, &fingerprints);
+	if (result) {
+		verbose("DNS lookup error: %s", dns_result_totext(result));
+		return DNS_VERIFY_ERROR;
+	}
+
+#ifdef DNSSEC
+	/* Only accept validated answers */
+	if (!fingerprints->rri_flags & RRSET_VALIDATED) {
+		error("Ignored unvalidated fingerprint from DNS.");
+		return DNS_VERIFY_ERROR;
+	}
+#endif
+
+	debug("found %d fingerprints in DNS", fingerprints->rri_nrdatas);
+
+	/* Initialize host key parameters */
+	if (!dns_read_key(&hostkey_algorithm, &hostkey_digest_type,
+	    &hostkey_digest, &hostkey_digest_len, hostkey)) {
+		error("Error calculating host key fingerprint.");
+		return DNS_VERIFY_ERROR;
+	}
+
+	for (counter = 0 ; counter < fingerprints->rri_nrdatas ; counter++)  {
+		/*
+		 * Extract the key from the answer. Ignore any badly
+		 * formatted fingerprints.
+		 */
+		if (!dns_read_rdata(&dnskey_algorithm, &dnskey_digest_type,
+		    &dnskey_digest, &dnskey_digest_len,
+		    fingerprints->rri_rdatas[counter].rdi_data,
+		    fingerprints->rri_rdatas[counter].rdi_length)) {
+			verbose("Error parsing fingerprint from DNS.");
+			continue;
+		}
+
+		/* Check if the current key is the same as the given key */
+		if (hostkey_algorithm == dnskey_algorithm &&
+		    hostkey_digest_type == dnskey_digest_type) {
+
+			if (hostkey_digest_len == dnskey_digest_len &&
+			    memcmp(hostkey_digest, dnskey_digest,
+			    hostkey_digest_len) == 0) {
+
+				/* Matching algoritm and digest. */
+				freerrset(fingerprints);
+#ifdef DNSSEC
+				debug("matching host key fingerprint found in DNS");
+				return DNS_VERIFY_OK;
+#else
+				logit("Matching host key fingerprint found in DNS.");
+				return DNS_VERIFY_ERROR;
+#endif
+			} else {
+				/* Correct algorithm but bad digest */
+				debug("verify_hostkey_dns: failed");
+				failures++;
+			}
+		}
+	}
+
+	freerrset(fingerprints);
+
+	if (failures) {
+		error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+		error("@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @");
+		error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+		error("IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!");
+		error("Someone could be eavesdropping on you right now (man-in-the-middle attack)!");
+		error("It is also possible that the %s host key has just been changed.",
+		    key_type(hostkey));
+		error("Please contact your system administrator.");
+		return DNS_VERIFY_FAILED;
+	}
+
+	debug("fingerprints found in DNS, but none of them matched");
+
+	return DNS_VERIFY_ERROR;
+}
+
+
+/*
+ * Export the fingerprint of a key as a DNS resource record
+ */
+int
+export_dns_rr(const char *hostname, Key *key, FILE *f, int generic)
+{
+	u_int8_t rdata_pubkey_algorithm = 0;
+	u_int8_t rdata_digest_type = SSHFP_HASH_SHA1;
+	u_char *rdata_digest;
+	u_int rdata_digest_len;
+
+	int i;
+	int success = 0;
+
+	if (dns_read_key(&rdata_pubkey_algorithm, &rdata_digest_type,
+			 &rdata_digest, &rdata_digest_len, key)) {
+
+		if (generic)
+			fprintf(f, "%s IN TYPE%d \\# %d %02x %02x ", hostname,
+			    DNS_RDATATYPE_SSHFP, 2 + rdata_digest_len,
+			    rdata_pubkey_algorithm, rdata_digest_type);
+		else
+			fprintf(f, "%s IN SSHFP %d %d ", hostname,
+			    rdata_pubkey_algorithm, rdata_digest_type);
+
+		for (i = 0; i < rdata_digest_len; i++)
+			fprintf(f, "%02x", rdata_digest[i]);
+		fprintf(f, "\n");
+		success = 1;
+	} else {
+		error("dns_export_rr: unsupported algorithm");
+	}
+
+	return success;
+}
+
+#endif /* DNS */
diff --git a/dns.h b/dns.h
new file mode 100644
index 0000000..ba0ea9f
--- /dev/null
+++ b/dns.h
@@ -0,0 +1,57 @@
+/*	$OpenBSD: dns.h,v 1.3 2003/05/14 22:56:51 jakob Exp $	*/
+
+/*
+ * Copyright (c) 2003 Wesley Griffin. All rights reserved.
+ * Copyright (c) 2003 Jakob Schlyter. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "includes.h"
+
+#ifdef DNS
+#ifndef DNS_H
+#define DNS_H
+
+enum sshfp_types {
+	SSHFP_KEY_RESERVED,
+	SSHFP_KEY_RSA,
+	SSHFP_KEY_DSA
+};
+
+enum sshfp_hashes {
+	SSHFP_HASH_RESERVED,
+	SSHFP_HASH_SHA1
+};
+
+#define DNS_RDATACLASS_IN	1
+#define DNS_RDATATYPE_SSHFP	44
+
+#define DNS_VERIFY_FAILED	-1
+#define DNS_VERIFY_OK		0
+#define DNS_VERIFY_ERROR	1
+
+int	verify_host_key_dns(const char *, struct sockaddr *, Key *);
+int	export_dns_rr(const char *, Key *, FILE *, int);
+
+#endif /* DNS_H */
+#endif /* DNS */
diff --git a/key.c b/key.c
index 060b637..d918cfd 100644
--- a/key.c
+++ b/key.c
@@ -32,7 +32,7 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 #include "includes.h"
-RCSID("$OpenBSD: key.c,v 1.51 2003/02/12 09:33:04 markus Exp $");
+RCSID("$OpenBSD: key.c,v 1.52 2003/05/14 18:16:20 jakob Exp $");
 
 #include <openssl/evp.h>
 
@@ -169,7 +169,7 @@
 	return 0;
 }
 
-static u_char *
+u_char*
 key_fingerprint_raw(Key *k, enum fp_type dgst_type, u_int *dgst_raw_length)
 {
 	const EVP_MD *md = NULL;
diff --git a/key.h b/key.h
index 725c7a0..a7b6afe 100644
--- a/key.h
+++ b/key.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: key.h,v 1.20 2003/02/12 09:33:04 markus Exp $	*/
+/*	$OpenBSD: key.h,v 1.21 2003/05/14 18:16:20 jakob Exp $	*/
 
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
@@ -61,6 +61,7 @@
 Key	*key_demote(Key *);
 int	 key_equal(Key *, Key *);
 char	*key_fingerprint(Key *, enum fp_type, enum fp_rep);
+u_char	*key_fingerprint_raw(Key *, enum fp_type, u_int *);
 char	*key_type(Key *);
 int	 key_write(Key *, FILE *);
 int	 key_read(Key *, char **);
diff --git a/readconf.c b/readconf.c
index acdf128..c9c463b 100644
--- a/readconf.c
+++ b/readconf.c
@@ -12,7 +12,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: readconf.c,v 1.106 2003/04/09 12:00:37 djm Exp $");
+RCSID("$OpenBSD: readconf.c,v 1.107 2003/05/14 18:16:20 jakob Exp $");
 
 #include "ssh.h"
 #include "xmalloc.h"
@@ -114,7 +114,7 @@
 	oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication,
 	oHostKeyAlgorithms, oBindAddress, oSmartcardDevice,
 	oClearAllForwardings, oNoHostAuthenticationForLocalhost,
-	oEnableSSHKeysign, oRekeyLimit,
+	oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS,
 	oDeprecated
 } OpCodes;
 
@@ -187,6 +187,7 @@
 	{ "smartcarddevice", oSmartcardDevice },
 	{ "clearallforwardings", oClearAllForwardings },
 	{ "enablesshkeysign", oEnableSSHKeysign },
+	{ "verifyhostkeydns", oVerifyHostKeyDNS },
 	{ "nohostauthenticationforlocalhost", oNoHostAuthenticationForLocalhost },
 	{ "rekeylimit", oRekeyLimit },
 	{ NULL, oBadOption }
@@ -392,6 +393,10 @@
 		intptr = &options->check_host_ip;
 		goto parse_flag;
 
+	case oVerifyHostKeyDNS:
+		intptr = &options->verify_host_key_dns;
+		goto parse_flag;
+
 	case oStrictHostKeyChecking:
 		intptr = &options->strict_host_key_checking;
 		arg = strdelim(&s);
@@ -829,6 +834,7 @@
 	options->enable_ssh_keysign = - 1;
 	options->no_host_authentication_for_localhost = - 1;
 	options->rekey_limit = - 1;
+	options->verify_host_key_dns = -1;
 }
 
 /*
@@ -947,6 +953,8 @@
 		options->enable_ssh_keysign = 0;
 	if (options->rekey_limit == -1)
 		options->rekey_limit = 0;
+	if (options->verify_host_key_dns == -1)
+		options->verify_host_key_dns = 0;
 	/* options->proxy_command should not be set by default */
 	/* options->user will be set in the main program if appropriate */
 	/* options->hostname will be set in the main program if appropriate */
diff --git a/readconf.h b/readconf.h
index d354721..d141b8c 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: readconf.h,v 1.47 2003/04/02 09:48:07 markus Exp $	*/
+/*	$OpenBSD: readconf.h,v 1.48 2003/05/14 18:16:20 jakob Exp $	*/
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -86,6 +86,7 @@
 	char   *preferred_authentications;
 	char   *bind_address;	/* local socket address for connection to sshd */
 	char   *smartcard_device; /* Smartcard reader device */
+	int	verify_host_key_dns;	/* Verify host key using DNS */
 
 	int     num_identity_files;	/* Number of files for RSA/DSA identities. */
 	char   *identity_files[SSH_MAX_IDENTITY_FILES];
diff --git a/ssh-keygen.1 b/ssh-keygen.1
index 000e8ff..613d71a 100644
--- a/ssh-keygen.1
+++ b/ssh-keygen.1
@@ -1,4 +1,4 @@
-.\"	$OpenBSD: ssh-keygen.1,v 1.56 2003/03/28 10:11:43 jmc Exp $
+.\"	$OpenBSD: ssh-keygen.1,v 1.57 2003/05/14 18:16:20 jakob Exp $
 .\"
 .\"  -*- nroff -*-
 .\"
@@ -83,6 +83,10 @@
 .Nm ssh-keygen
 .Fl U Ar reader
 .Op Fl f Ar input_keyfile
+.Nm ssh-keygen
+.Fl r Ar hostname
+.Op Fl f Ar input_keyfile
+.Op Fl g
 .Sh DESCRIPTION
 .Nm
 generates, manages and converts authentication keys for
@@ -163,6 +167,8 @@
 to stdout.
 This option allows exporting keys for use by several commercial
 SSH implementations.
+.It Fl g
+Use generic DNS resource record format.
 .It Fl f Ar filename
 Specifies the filename of the key file.
 .It Fl i
@@ -218,6 +224,9 @@
 .It Fl U Ar reader
 Upload an existing RSA private key into the smartcard in
 .Ar reader .
+.It Fl r Ar hostname
+Print DNS resource record with the specified
+.Ar hostname .
 .El
 .Sh FILES
 .Bl -tag -width Ds
diff --git a/ssh-keygen.c b/ssh-keygen.c
index 1d08c7c..f3ea4f1 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -12,7 +12,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: ssh-keygen.c,v 1.104 2003/05/11 16:56:48 markus Exp $");
+RCSID("$OpenBSD: ssh-keygen.c,v 1.105 2003/05/14 18:16:20 jakob Exp $");
 
 #include <openssl/evp.h>
 #include <openssl/pem.h>
@@ -70,6 +70,7 @@
 int convert_to_ssh2 = 0;
 int convert_from_ssh2 = 0;
 int print_public = 0;
+int print_generic = 0;
 
 char *key_type_name = NULL;
 
@@ -620,6 +621,38 @@
 	exit(0);
 }
 
+#ifdef DNS
+/*
+ * Print the SSHFP RR.
+ */
+static void
+do_print_resource_record(struct passwd *pw, char *hostname)
+{
+	Key *public;
+	char *comment = NULL;
+	struct stat st;
+
+	if (!have_identity)
+		ask_filename(pw, "Enter file in which the key is");
+	if (stat(identity_file, &st) < 0) {
+		perror(identity_file);
+		exit(1);
+	}
+	public = key_load_public(identity_file, &comment);
+	if (public != NULL) {
+		export_dns_rr(hostname, public, stdout, print_generic);
+		key_free(public);
+		xfree(comment);
+		exit(0);
+	}
+	if (comment)
+		xfree(comment);
+
+	printf("failed to read v2 public key from %s.\n", identity_file);
+	exit(1);
+}
+#endif /* DNS */
+
 /*
  * Change the comment of a private key file.
  */
@@ -726,6 +759,7 @@
 	fprintf(stderr, "  -c          Change comment in private and public key files.\n");
 	fprintf(stderr, "  -e          Convert OpenSSH to IETF SECSH key file.\n");
 	fprintf(stderr, "  -f filename Filename of the key file.\n");
+	fprintf(stderr, "  -g          Use generic DNS resource record format.\n");
 	fprintf(stderr, "  -i          Convert IETF SECSH to OpenSSH key file.\n");
 	fprintf(stderr, "  -l          Show fingerprint of key file.\n");
 	fprintf(stderr, "  -p          Change passphrase of private key file.\n");
@@ -736,6 +770,9 @@
 	fprintf(stderr, "  -C comment  Provide new comment.\n");
 	fprintf(stderr, "  -N phrase   Provide new passphrase.\n");
 	fprintf(stderr, "  -P phrase   Provide old passphrase.\n");
+#ifdef DNS
+	fprintf(stderr, "  -r hostname Print DNS resource record.\n");
+#endif /* DNS */
 #ifdef SMARTCARD
 	fprintf(stderr, "  -D reader   Download public key from smartcard.\n");
 	fprintf(stderr, "  -U reader   Upload private key to smartcard.\n");
@@ -752,6 +789,7 @@
 {
 	char dotsshdir[MAXPATHLEN], comment[1024], *passphrase1, *passphrase2;
 	char *reader_id = NULL;
+	char *resource_record_hostname = NULL;
 	Key *private, *public;
 	struct passwd *pw;
 	struct stat st;
@@ -778,7 +816,7 @@
 		exit(1);
 	}
 
-	while ((opt = getopt(ac, av, "deiqpclBRxXyb:f:t:U:D:P:N:C:")) != -1) {
+	while ((opt = getopt(ac, av, "degiqpclBRxXyb:f:t:U:D:P:N:C:r:")) != -1) {
 		switch (opt) {
 		case 'b':
 			bits = atoi(optarg);
@@ -803,6 +841,9 @@
 			strlcpy(identity_file, optarg, sizeof(identity_file));
 			have_identity = 1;
 			break;
+		case 'g':
+			print_generic = 1;
+			break;
 		case 'P':
 			identity_passphrase = optarg;
 			break;
@@ -843,6 +884,9 @@
 		case 'U':
 			reader_id = optarg;
 			break;
+		case 'r':
+			resource_record_hostname = optarg;
+			break;
 		case '?':
 		default:
 			usage();
@@ -868,6 +912,13 @@
 		do_convert_from_ssh2(pw);
 	if (print_public)
 		do_print_public(pw);
+	if (resource_record_hostname != NULL) {
+#ifdef DNS
+		do_print_resource_record(pw, resource_record_hostname);
+#else /* DNS */
+		fatal("no DNS support.");
+#endif /* DNS */
+	}
 	if (reader_id != NULL) {
 #ifdef SMARTCARD
 		if (download)
diff --git a/ssh_config.5 b/ssh_config.5
index 44208b4..2f33aa3 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.7 2003/03/28 10:11:43 jmc Exp $
+.\" $OpenBSD: ssh_config.5,v 1.8 2003/05/14 18:16:20 jakob Exp $
 .Dd September 25, 1999
 .Dt SSH_CONFIG 5
 .Os
@@ -618,6 +618,11 @@
 Specifies a file to use for the user
 host key database instead of
 .Pa $HOME/.ssh/known_hosts .
+.It Cm VerifyHostKeyDNS
+Specifies whether to verify the remote key using DNS and SSHFP resource
+records.
+The default is
+.Dq no .
 .It Cm XAuthLocation
 Specifies the full pathname of the
 .Xr xauth 1
diff --git a/sshconnect.c b/sshconnect.c
index 33d9c72..32bef7d 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -13,7 +13,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: sshconnect.c,v 1.139 2003/04/14 14:17:50 markus Exp $");
+RCSID("$OpenBSD: sshconnect.c,v 1.140 2003/05/14 18:16:21 jakob Exp $");
 
 #include <openssl/bn.h>
 
@@ -33,6 +33,10 @@
 #include "misc.h"
 #include "readpass.h"
 
+#ifdef DNS
+#include "dns.h"
+#endif
+
 char *client_version_string = NULL;
 char *server_version_string = NULL;
 
@@ -797,11 +801,28 @@
 	return -1;
 }
 
+/* returns 0 if key verifies or -1 if key does NOT verify */
 int
 verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key)
 {
 	struct stat st;
 
+#ifdef DNS
+	if (options.verify_host_key_dns) {
+		switch(verify_host_key_dns(host, hostaddr, host_key)) {
+		case DNS_VERIFY_OK:
+			return 0;
+		case DNS_VERIFY_FAILED:
+			return -1;
+		case DNS_VERIFY_ERROR:
+			break;
+		default:
+			debug3("bad return value from verify_host_key_dns");
+			break;
+		}
+	}
+#endif /* DNS */
+
 	/* return ok if the key can be found in an old keyfile */
 	if (stat(options.system_hostfile2, &st) == 0 ||
 	    stat(options.user_hostfile2, &st) == 0) {