changeset 51:095d689fed16

- Hostkey checking is mostly there, just aren't appending yet. - Rearranged various bits of the fingerprint/base64 type code, so it can be shared between versions
author Matt Johnston <matt@ucc.asn.au>
date Sun, 08 Aug 2004 16:17:05 +0000
parents cc59bfcdee17
children c8fcd4841956
files cli-kex.c dbutil.c options.h signkey.c signkey.h svr-authpubkey.c
diffstat 6 files changed, 258 insertions(+), 111 deletions(-) [+]
line wrap: on
line diff
--- a/cli-kex.c	Sat Aug 07 15:50:58 2004 +0000
+++ b/cli-kex.c	Sun Aug 08 16:17:05 2004 +0000
@@ -37,6 +37,8 @@
 #include "signkey.h"
 
 
+static void checkhostkey(unsigned char* keyblob, unsigned int keybloblen);
+#define MAX_KNOWNHOSTS_LINE 4500
 
 void send_msg_kexdh_init() {
 
@@ -58,14 +60,22 @@
 
 	mp_int dh_f;
 	sign_key *hostkey = NULL;
-	int type, keylen;
+	unsigned int type, keybloblen;
+	unsigned char* keyblob = NULL;
+
 
 	TRACE(("enter recv_msg_kexdh_reply"));
 	type = ses.newkeys->algo_hostkey;
 	TRACE(("type is %d", type));
 
 	hostkey = new_sign_key();
-	keylen = buf_getint(ses.payload);
+	keybloblen = buf_getint(ses.payload);
+
+	keyblob = buf_getptr(ses.payload, keybloblen);
+	if (!ses.kexstate.donefirstkex) {
+		/* Only makes sense the first time */
+		checkhostkey(keyblob, keybloblen);
+	}
 
 	if (buf_get_pub_key(ses.payload, hostkey, &type) != DROPBEAR_SUCCESS) {
 		TRACE(("failed getting pubkey"));
@@ -86,9 +96,6 @@
 		dropbear_exit("Bad hostkey signature");
 	}
 
-	/* XXX TODO */
-	dropbear_log(LOG_WARNING,"Not checking hostkey fingerprint for the moment");
-
 	sign_key_free(hostkey);
 	hostkey = NULL;
 
@@ -96,3 +103,123 @@
 	ses.requirenext = SSH_MSG_NEWKEYS;
 	TRACE(("leave recv_msg_kexdh_init"));
 }
+
+static void ask_to_confirm(unsigned char* keyblob, unsigned int keybloblen) {
+
+	char* fp = NULL;
+
+	fp = sign_key_fingerprint(keyblob, keybloblen);
+	fprintf(stderr, "\nHost '%s' is not in the trusted hosts file.\n(fingerprint %s)\nDo you want to continue connecting? (y/n)\n", 
+			cli_opts.remotehost, 
+			fp);
+
+	if (getc(stdin) == 'y') {
+		m_free(fp);
+		return;
+	}
+
+	dropbear_exit("Didn't validate host key");
+}
+
+static void checkhostkey(unsigned char* keyblob, unsigned int keybloblen) {
+
+	char * filename = NULL;
+	FILE *hostsfile = NULL;
+	struct passwd *pw = NULL;
+	unsigned int len, hostlen;
+	const char *algoname = NULL;
+	buffer * line = NULL;
+	int ret;
+	
+	pw = getpwuid(getuid());
+
+	if (pw == NULL) {
+		dropbear_exit("Failed to get homedir");
+	}
+
+	len = strlen(pw->pw_dir);
+	filename = m_malloc(len + 18); /* "/.ssh/known_hosts" and null-terminator*/
+
+	snprintf(filename, len+18, "%s/.ssh", pw->pw_dir);
+	/* Check that ~/.ssh exists - easiest way is just to mkdir */
+	if (mkdir(filename, S_IRWXU) != 0) {
+		if (errno != EEXIST) {
+			ask_to_confirm(keyblob, keybloblen);
+			goto out; /* only get here on success */
+		}
+	}
+
+	snprintf(filename, len+18, "%s/.ssh/known_hosts", pw->pw_dir);
+	hostsfile = fopen(filename, "r+");
+	if (hostsfile == NULL) {
+		ask_to_confirm(keyblob, keybloblen);
+		goto out; /* We only get here on success */
+	}
+
+	line = buf_new(MAX_KNOWNHOSTS_LINE);
+	hostlen = strlen(cli_opts.remotehost);
+
+	do {
+		if (buf_getline(line, hostsfile) == DROPBEAR_FAILURE) {
+			TRACE(("failed reading line: prob EOF"));
+			break;
+		}
+
+		/* The line is too short to be sensible */
+		/* "30" is 'enough to hold ssh-dss plus the spaces, ie so we don't
+		 * buf_getfoo() past the end and die horribly - the base64 parsing
+		 * code is what tiptoes up to the end nicely */
+		if (line->len < (hostlen+30) ) {
+			TRACE(("line is too short to be sensible"));
+			continue;
+		}
+
+		/* Compare hostnames */
+		if (strncmp(cli_opts.remotehost, buf_getptr(line, hostlen),
+					hostlen) != 0) {
+			TRACE(("hosts don't match"));
+			continue;
+		}
+
+		buf_incrpos(line, hostlen);
+		if (buf_getbyte(line) != ' ') {
+			/* there wasn't a space after the hostname, something dodgy */
+			TRACE(("missing space afte matching hostname"));
+			continue;
+		}
+
+		algoname = signkey_name_from_type(ses.newkeys->algo_hostkey, &len);
+		if ( strncmp(buf_getptr(line, len), algoname, len) != 0) {
+			TRACE(("algo doesn't match"));
+			continue;
+		}
+
+		buf_incrpos(line, len);
+		if (buf_getbyte(line) != ' ') {
+			TRACE(("missing space after algo"));
+			continue;
+		}
+
+		/* Now we're at the interesting hostkey */
+		ret = cmp_base64_key(keyblob, keybloblen, algoname, len, line);
+
+		if (ret == DROPBEAR_SUCCESS) {
+			/* Good matching key */
+			TRACE(("good matching key"));
+			goto out;
+		}
+
+		/* The keys didn't match. eep. */
+	} while (1); /* keep going 'til something happens */
+
+	/* Key doesn't exist yet */
+	ask_to_confirm(keyblob, keybloblen);
+	/* If we get here, they said yes */
+
+out:
+	if (hostsfile != NULL) {
+		fclose(hostsfile);
+	}
+	m_free(filename);
+	buf_free(line);
+}
--- a/dbutil.c	Sat Aug 07 15:50:58 2004 +0000
+++ b/dbutil.c	Sun Aug 08 16:17:05 2004 +0000
@@ -320,6 +320,52 @@
 	return DROPBEAR_SUCCESS;
 }
 
+/* get a line from the file into buffer in the style expected for an
+ * authkeys file.
+ * Will return DROPBEAR_SUCCESS if data is read, or DROPBEAR_FAILURE on EOF.*/
+/* Only used for ~/.ssh/known_hosts and ~/.ssh/authorized_keys */
+#if defined(DROPBEAR_CLIENT) || defined(DROPBEAR_PUBKEY_AUTH)
+int buf_getline(buffer * line, FILE * authfile) {
+
+	int c = EOF;
+
+	TRACE(("enter buf_getline"));
+
+	buf_setpos(line, 0);
+	buf_setlen(line, 0);
+
+	while (line->pos < line->size) {
+
+		c = fgetc(authfile); /*getc() is weird with some uClibc systems*/
+		if (c == EOF || c == '\n' || c == '\r') {
+			goto out;
+		}
+
+		buf_putbyte(line, (unsigned char)c);
+	}
+
+	TRACE(("leave getauthline: line too long"));
+	/* We return success, but the line length will be zeroed - ie we just
+	 * ignore that line */
+	buf_setlen(line, 0);
+
+out:
+
+	buf_setpos(line, 0);
+
+	/* if we didn't read anything before EOF or error, exit */
+	if (c == EOF && line->pos == 0) {
+		TRACE(("leave getauthline: failure"));
+		return DROPBEAR_FAILURE;
+	} else {
+		TRACE(("leave getauthline: success"));
+		return DROPBEAR_SUCCESS;
+	}
+
+	TRACE(("leave buf_getline"));
+}	
+#endif
+
 /* loop until the socket is closed (in case of EINTR) or
  * we get and error.
  * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
--- a/options.h	Sat Aug 07 15:50:58 2004 +0000
+++ b/options.h	Sun Aug 08 16:17:05 2004 +0000
@@ -301,6 +301,10 @@
 #define USING_LISTENERS
 #endif
 
+#if defined(DROPBEAR_CLIENT) || defined(DROPBEAR_PUBKEY_AUTH)
+#define DROPBEAR_KEY_LINES /* ie we're using authorized_keys or known_hosts */
+#endif
+
 /* We use dropbear_client and dropbear_server as shortcuts to avoid redundant
  * code, if we're just compiling as client or server */
 #if defined(DROPBEAR_SERVER) && defined(DROPBEAR_CLIENT)
--- a/signkey.c	Sat Aug 07 15:50:58 2004 +0000
+++ b/signkey.c	Sun Aug 08 16:17:05 2004 +0000
@@ -272,25 +272,20 @@
 /* Since we're not sure if we'll have md5 or sha1, we present both.
  * MD5 is used in preference, but sha1 could still be useful */
 #ifdef DROPBEAR_MD5_HMAC
-static char * sign_key_md5_fingerprint(sign_key *key, int type) {
+static char * sign_key_md5_fingerprint(unsigned char* keyblob,
+		unsigned int keybloblen) {
 
 	char * ret;
 	hash_state hs;
-	buffer *pubkeys;
 	unsigned char hash[MD5_HASH_SIZE];
 	unsigned int h, i;
 	unsigned int buflen;
 
 	md5_init(&hs);
 
-	pubkeys = buf_new(1000);
-	buf_put_pub_key(pubkeys, key, type);
 	/* skip the size int of the string - this is a bit messy */
-	buf_setpos(pubkeys, 4);
-	md5_process(&hs, buf_getptr(pubkeys, pubkeys->len-pubkeys->pos),
-			pubkeys->len-pubkeys->pos);
+	md5_process(&hs, keyblob, keybloblen);
 
-	buf_free(pubkeys);
 	md5_done(&hs, hash);
 
 	/* "md5 hexfingerprinthere\0", each hex digit is "AB:" etc */
@@ -311,25 +306,20 @@
 }
 
 #else /* use SHA1 rather than MD5 for fingerprint */
-static char * sign_key_sha1_fingerprint(sign_key *key, int type) {
+static char * sign_key_sha1_fingerprint(unsigned char* keyblob, 
+		unsigned int keybloblen) {
 
 	char * ret;
 	hash_state hs;
-	buffer *pubkeys;
 	unsigned char hash[SHA1_HASH_SIZE];
 	unsigned int h, i;
 	unsigned int buflen;
 
 	sha1_init(&hs);
 
-	pubkeys = buf_new(1000);
-	buf_put_pub_key(pubkeys, key, type);
-	buf_setpos(pubkeys, 4);
 	/* skip the size int of the string - this is a bit messy */
-	sha1_process(&hs, buf_getptr(pubkeys, pubkeys->len-pubkeys->pos),
-			pubkeys->len-pubkeys->pos);
+	sha1_process(&hs, keyblob, keybloblen);
 
-	buf_free(pubkeys);
 	sha1_done(&hs, hash);
 
 	/* "sha1 hexfingerprinthere\0", each hex digit is "AB:" etc */
@@ -352,12 +342,12 @@
 
 /* This will return a freshly malloced string, containing a fingerprint
  * in either sha1 or md5 */
-char * sign_key_fingerprint(sign_key *key, int type) {
+char * sign_key_fingerprint(unsigned char* keyblob, unsigned int keybloblen) {
 
 #ifdef DROPBEAR_MD5_HMAC
-	return sign_key_md5_fingerprint(key, type);
+	return sign_key_md5_fingerprint(keyblob, keybloblen);
 #else
-	return sign_key_sha1_fingerprint(key, type);
+	return sign_key_sha1_fingerprint(keyblob, keybloblen);
 #endif
 }
 
@@ -427,3 +417,59 @@
 	return DROPBEAR_FAILURE;
 }
 #endif /* DROPBEAR_SIGNKEY_VERIFY */
+
+#ifdef DROPBEAR_KEY_LINES /* ie we're using authorized_keys or known_hosts */
+
+/* Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE when given a buffer containing
+ * a key, a key, and a type. The buffer is positioned at the start of the
+ * base64 data, and contains no trailing data */
+int cmp_base64_key(const unsigned char* keyblob, unsigned int keybloblen, 
+					const unsigned char* algoname, unsigned int algolen, 
+					buffer * line) {
+
+	buffer * decodekey = NULL;
+	int ret = DROPBEAR_FAILURE;
+	unsigned int len, filealgolen;
+	unsigned long decodekeylen;
+	unsigned char* filealgo = NULL;
+
+	/* now we have the actual data */
+	len = line->len - line->pos;
+	decodekeylen = len * 2; /* big to be safe */
+	decodekey = buf_new(decodekeylen);
+
+	if (base64_decode(buf_getptr(line, len), len,
+				buf_getwriteptr(decodekey, decodekey->size),
+				&decodekeylen) != CRYPT_OK) {
+		TRACE(("checkpubkey: base64 decode failed"));
+		goto out;
+	}
+	TRACE(("checkpubkey: base64_decode success"));
+	buf_incrlen(decodekey, decodekeylen);
+	
+	/* compare the keys */
+	if ( ( decodekeylen != keybloblen )
+			|| memcmp( buf_getptr(decodekey, decodekey->len),
+						keyblob, decodekey->len) != 0) {
+		TRACE(("checkpubkey: compare failed"));
+		goto out;
+	}
+
+	/* ... and also check that the algo specified and the algo in the key
+	 * itself match */
+	filealgolen = buf_getint(decodekey);
+	filealgo = buf_getptr(decodekey, filealgolen);
+	if (filealgolen != algolen || memcmp(filealgo, algoname, algolen) != 0) {
+		TRACE(("checkpubkey: algo match failed")); 
+		goto out;
+	}
+
+	/* All checks passed */
+	ret = DROPBEAR_SUCCESS;
+
+out:
+	buf_free(decodekey);
+	decodekey = NULL;
+	return ret;
+}
+#endif
--- a/signkey.h	Sat Aug 07 15:50:58 2004 +0000
+++ b/signkey.h	Sun Aug 08 16:17:05 2004 +0000
@@ -54,7 +54,10 @@
 #ifdef DROPBEAR_SIGNKEY_VERIFY
 int buf_verify(buffer * buf, sign_key *key, const unsigned char *data,
 		unsigned int len);
-char * sign_key_fingerprint(sign_key *key, int type);
+char * sign_key_fingerprint(unsigned char* keyblob, unsigned int keybloblen);
 #endif
+int cmp_base64_key(const unsigned char* keyblob, unsigned int keybloblen, 
+					const unsigned char* algoname, unsigned int algolen, 
+					buffer * line);
 
 #endif /* _SIGNKEY_H_ */
--- a/svr-authpubkey.c	Sat Aug 07 15:50:58 2004 +0000
+++ b/svr-authpubkey.c	Sun Aug 08 16:17:05 2004 +0000
@@ -38,7 +38,7 @@
 #ifdef DROPBEAR_PUBKEY_AUTH
 
 #define MIN_AUTHKEYS_LINE 10 /* "ssh-rsa AB" - short but doesn't matter */
-#define MAX_AUTHKEYS_LINE 1000 /* max length of a line in authkeys */
+#define MAX_AUTHKEYS_LINE 4200 /* max length of a line in authkeys */
 
 static int checkpubkey(unsigned char* algo, unsigned int algolen,
 		unsigned char* keyblob, unsigned int keybloblen);
@@ -46,7 +46,6 @@
 static void send_msg_userauth_pk_ok(unsigned char* algo, unsigned int algolen,
 		unsigned char* keyblob, unsigned int keybloblen);
 static int checkfileperm(char * filename);
-static int getauthline(buffer * line, FILE * authfile);
 
 /* process a pubkey auth request, sending success or failure message as
  * appropriate */
@@ -102,7 +101,7 @@
 	buf_setpos(signbuf, 0);
 
 	/* ... and finally verify the signature */
-	fp = sign_key_fingerprint(key, type);
+	fp = sign_key_fingerprint(keyblob, keybloblen);
 	if (buf_verify(ses.payload, key, buf_getptr(signbuf, signbuf->len),
 				signbuf->len) == DROPBEAR_SUCCESS) {
 		dropbear_log(LOG_NOTICE,
@@ -160,10 +159,6 @@
 	char * filename = NULL;
 	int ret = DROPBEAR_FAILURE;
 	buffer * line = NULL;
-	buffer * decodekey = NULL;
-	unsigned long decodekeylen;
-	unsigned char* filealgo = NULL;
-	unsigned int filealgolen;
 	unsigned int len, pos;
 	
 	TRACE(("enter checkpubkey"));
@@ -202,14 +197,8 @@
 
 	/* iterate through the lines */
 	do {
-		/* free reused vars */
-		if (decodekey) {
-			buf_free(decodekey);
-			decodekey = NULL;
-		}
-		m_free(filealgo);
 
-		if (getauthline(line, authfile) == DROPBEAR_FAILURE) {
+		if (buf_getline(line, authfile) == DROPBEAR_FAILURE) {
 			/* EOF reached */
 			TRACE(("checkpubkey: authorized_keys EOF reached"));
 			break;
@@ -243,38 +232,12 @@
 
 		TRACE(("checkpubkey: line pos = %d len = %d", line->pos, line->len));
 
-		/* now we have the actual data */
-		decodekeylen = (line->len - line->pos) * 2;
-		decodekey = buf_new(decodekeylen);
-		if (base64_decode(buf_getptr(line, line->len - line->pos),
-					line->len - line->pos,
-					buf_getwriteptr(decodekey, decodekey->size),
-					&decodekeylen) != CRYPT_OK) {
-			TRACE(("checkpubkey: base64 decode failed"));
-			continue;
-		}
-		TRACE(("checkpubkey: base64_decode success"));
-		buf_incrlen(decodekey, decodekeylen);
-		
-		/* compare the keys */
-		if (decodekeylen != keybloblen || memcmp(
-					buf_getptr(decodekey, decodekey->len),
-					keyblob, decodekey->len) != 0) {
-			TRACE(("checkpubkey: compare failed"));
-			continue;
+		ret = cmp_base64_key(keyblob, keybloblen, algo, algolen, line);
+		if (ret == DROPBEAR_SUCCESS) {
+			break;
 		}
 
-		/* and also check that the algo specified and the algo in the key
-		 * itself match */
-		filealgo = buf_getstring(decodekey, &filealgolen);
-		if (filealgolen != algolen || memcmp(filealgo, algo, algolen) != 0) {
-			TRACE(("checkpubkey: algo match failed")); 
-			continue;
-		}
-
-		/* now we know this key is good */
-		ret = DROPBEAR_SUCCESS;
-		break;
+		/* We continue to the next line otherwise */
 		
 	} while (1);
 
@@ -285,53 +248,11 @@
 	if (line) {
 		buf_free(line);
 	}
-	if (decodekey) {
-		buf_free(decodekey);
-	}
 	m_free(filename);
-	m_free(filealgo);
 	TRACE(("leave checkpubkey: ret=%d", ret));
 	return ret;
 }
 
-/* get a line from the file into buffer in the style expected for an
- * authkeys file.
- * Will return DROPBEAR_SUCCESS if data is read, or DROPBEAR_FAILURE on EOF.*/
-static int getauthline(buffer * line, FILE * authfile) {
-
-	int c = EOF;
-
-	TRACE(("enter getauthline"));
-
-	buf_setpos(line, 0);
-	buf_setlen(line, 0);
-
-	while (line->pos < line->size) {
-		c = fgetc(authfile); /*getc() is weird with some uClibc systems*/
-		if (c == EOF || c == '\n' || c == '\r') {
-			goto out;
-		}
-		buf_putbyte(line, (unsigned char)c);
-	}
-
-	TRACE(("leave getauthline: line too long"));
-	return DROPBEAR_FAILURE;
-
-out:
-
-	buf_setpos(line, 0);
-
-	/* if we didn't read anything before EOF or error, exit */
-	if (c == EOF && line->pos == 0) {
-		TRACE(("leave getauthline: failure"));
-		return DROPBEAR_FAILURE;
-	} else {
-		TRACE(("leave getauthline: success"));
-		return DROPBEAR_SUCCESS;
-	}
-
-	TRACE(("leave getauthline"));
-}	
 
 /* Returns DROPBEAR_SUCCESS if file permissions for pubkeys are ok,
  * DROPBEAR_FAILURE otherwise.