From 85c6627c861e970ba0bab10ec8b44b6523d6a5fd Mon Sep 17 00:00:00 2001
From: Salvador Fandino <sfandino@yahoo.com>
Date: Sun, 27 Oct 2013 10:55:07 +0100
Subject: [PATCH] knownhosts: handle unknown key types

Store but don't use keys of unsupported types on the known_hosts file.

Currently, when libssh2 parses a known_host file containing keys of some
type it doesn't natively support, it stops reading the file and returns
an error.

That means, that the known_host file can not be safely shared with other
software supporting other key types (i.e. OpenSSH).

This patch adds support for handling keys of unknown type. It can read
and write them, even if they are never going to be matched.

At the source level the patch does the following things:

- add a new unknown key type LIBSSH2_KNOWNHOST_KEY_UNKNOWN

- add a new slot (key_type_name) on the known_host struct that is
used to store the key type in ascii form when it is not supported

- parse correctly known_hosts entries with unknown key types and
populate the key_type_name slot

- print correctly known_hosts entries of unknown type

- when checking a host key ignore keys that do not match the key

Fixes #276
---
 include/libssh2.h |   3 +-
 src/knownhost.c   | 287 +++++++++++++++++++++++++++++++++---------------------
 2 files changed, 176 insertions(+), 114 deletions(-)

Index: libssh2-1.2.9/include/libssh2.h
===================================================================
--- libssh2-1.2.9.orig/include/libssh2.h
+++ libssh2-1.2.9/include/libssh2.h
@@ -839,11 +839,12 @@ libssh2_knownhost_init(LIBSSH2_SESSION *
 #define LIBSSH2_KNOWNHOST_KEYENC_BASE64   (2<<16)
 
 /* type of key (2 bits) */
-#define LIBSSH2_KNOWNHOST_KEY_MASK     (3<<18)
+#define LIBSSH2_KNOWNHOST_KEY_MASK     (7<<18)
 #define LIBSSH2_KNOWNHOST_KEY_SHIFT    18
 #define LIBSSH2_KNOWNHOST_KEY_RSA1     (1<<18)
 #define LIBSSH2_KNOWNHOST_KEY_SSHRSA   (2<<18)
 #define LIBSSH2_KNOWNHOST_KEY_SSHDSS   (3<<18)
+#define LIBSSH2_KNOWNHOST_KEY_UNKNOWN  (7<<18)
 
 LIBSSH2_API int
 libssh2_knownhost_add(LIBSSH2_KNOWNHOSTS *hosts,
Index: libssh2-1.2.9/src/knownhost.c
===================================================================
--- libssh2-1.2.9.orig/src/knownhost.c
+++ libssh2-1.2.9/src/knownhost.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2011 by Daniel Stenberg
+ * Copyright (c) 2009-2013 by Daniel Stenberg
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms,
@@ -50,7 +50,11 @@ struct known_host {
     size_t salt_len; /* size of salt */
     char *key;       /* the (allocated) associated key. This is kept base64
                         encoded in memory. */
-    char *comment;   /* the (allocated) optional comment text, may be NULL */
+    char *key_type_name; /* the (allocated) key type name */
+    size_t key_type_len; /* size of key_type_name */
+    char *comment;       /* the (allocated) optional comment text, may be
+                            NULL */
+    size_t comment_len;  /* the size of comment */
 
     /* this is the struct we expose externally */
     struct libssh2_knownhost external;
@@ -67,6 +71,8 @@ static void free_host(LIBSSH2_SESSION *s
     if(entry) {
         if(entry->comment)
             LIBSSH2_FREE(session, entry->comment);
+        if (entry->key_type_name)
+            LIBSSH2_FREE(session, entry->key_type_name);
         if(entry->key)
             LIBSSH2_FREE(session, entry->key);
         if(entry->salt)
@@ -127,6 +133,7 @@ static struct libssh2_knownhost *knownho
 static int
 knownhost_add(LIBSSH2_KNOWNHOSTS *hosts,
               const char *host, const char *salt,
+              const char *key_type_name, size_t key_type_len,
               const char *key, size_t keylen,
               const char *comment, size_t commentlen,
               int typemask, struct libssh2_knownhost **store)
@@ -164,6 +171,7 @@ knownhost_add(LIBSSH2_KNOWNHOSTS *hosts,
             goto error;
         }
         memcpy(entry->name, host, hostlen+1);
+        entry->name_len = hostlen;
         break;
     case LIBSSH2_KNOWNHOST_TYPE_SHA1:
         rc = libssh2_base64_decode(hosts->session, &ptr, &ptrlen,
@@ -213,6 +221,19 @@ knownhost_add(LIBSSH2_KNOWNHOSTS *hosts,
         entry->key = ptr;
     }
 
+    if (key_type_name && ((typemask & LIBSSH2_KNOWNHOST_KEY_MASK) ==
+                          LIBSSH2_KNOWNHOST_KEY_UNKNOWN)) {
+        entry->key_type_name = LIBSSH2_ALLOC(hosts->session, key_type_len+1);
+        if (!entry->key_type_name) {
+            rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
+                                "Unable to allocate memory for key type");
+            goto error;
+        }
+        memcpy(entry->key_type_name, key_type_name, key_type_len);
+        entry->key_type_name[key_type_len]=0;
+        entry->key_type_len = key_type_len;
+    }
+
     if (comment) {
         entry->comment = LIBSSH2_ALLOC(hosts->session, commentlen+1);
         if(!entry->comment) {
@@ -222,6 +243,7 @@ knownhost_add(LIBSSH2_KNOWNHOSTS *hosts,
         }
         memcpy(entry->comment, comment, commentlen+1);
         entry->comment[commentlen]=0; /* force a terminating zero trailer */
+        entry->comment_len = commentlen;
     }
     else {
         entry->comment = NULL;
@@ -267,8 +289,8 @@ libssh2_knownhost_add(LIBSSH2_KNOWNHOSTS
                       const char *key, size_t keylen,
                       int typemask, struct libssh2_knownhost **store)
 {
-    return knownhost_add(hosts, host, salt, key, keylen, NULL, 0, typemask,
-                         store);
+    return knownhost_add(hosts, host, salt, NULL, 0, key, keylen, NULL,
+                         0, typemask, store);
 }
 
 
@@ -306,8 +328,8 @@ libssh2_knownhost_addc(LIBSSH2_KNOWNHOST
                        const char *comment, size_t commentlen,
                        int typemask, struct libssh2_knownhost **store)
 {
-    return knownhost_add(hosts, host, salt, key, keylen, comment, commentlen,
-                         typemask, store);
+    return knownhost_add(hosts, host, salt, NULL, 0, key, keylen,
+                         comment, commentlen, typemask, store);
 }
 
 /*
@@ -417,21 +439,34 @@ knownhost_check(LIBSSH2_KNOWNHOSTS *host
                 break;
             }
             if(match) {
-                /* host name match, now compare the keys */
-                if(!strcmp(key, node->key)) {
-                    /* they match! */
-                    *ext = knownhost_to_external(node);
-                    badkey = NULL;
-                    rc = LIBSSH2_KNOWNHOST_CHECK_MATCH;
-                    break;
-                }
-                else {
-                    /* remember the first node that had a host match but a
-                       failed key match since we continue our search from
-                       here */
-                    if(!badkey)
-                        badkey = node;
-                    match = 0; /* don't count this as a match anymore */
+		int host_key_type = typemask & LIBSSH2_KNOWNHOST_KEY_MASK;
+		int known_key_type =
+		    node->typemask & LIBSSH2_KNOWNHOST_KEY_MASK;
+		/* match on key type as follows:
+		   - never match on an unknown key type
+		   - if key_type is set to zero, ignore it an match always
+		   - otherwise match when both key types are equal
+		*/
+		if ( (host_key_type != LIBSSH2_KNOWNHOST_KEY_UNKNOWN ) &&
+		     ( (host_key_type == 0) ||
+		       (host_key_type == known_key_type) ) ) {
+		    /* host name and key type match, now compare the keys */
+		    if(!strcmp(key, node->key)) {
+			/* they match! */
+			if (ext)
+			    *ext = knownhost_to_external(node);
+			badkey = NULL;
+			rc = LIBSSH2_KNOWNHOST_CHECK_MATCH;
+			break;
+		    }
+		    else {
+			/* remember the first node that had a host match but a
+			   failed key match since we continue our search from
+			   here */
+			if(!badkey)
+			    badkey = node;
+		    }
+		    match = 0; /* don't count this as a match anymore */
                 }
             }
             node= _libssh2_list_next(&node->node);
@@ -574,6 +609,7 @@ libssh2_knownhost_free(LIBSSH2_KNOWNHOST
 */
 static int oldstyle_hostline(LIBSSH2_KNOWNHOSTS *hosts,
                              const char *host, size_t hostlen,
+                             const char *key_type_name, size_t key_type_len,
                              const char *key, size_t keylen, int key_type,
                              const char *comment, size_t commentlen)
 {
@@ -608,7 +644,9 @@ static int oldstyle_hostline(LIBSSH2_KNO
             memcpy(hostbuf, name, namelen);
             hostbuf[namelen]=0;
 
-            rc = knownhost_add(hosts, hostbuf, NULL, key, keylen,
+            rc = knownhost_add(hosts, hostbuf, NULL,
+                               key_type_name, key_type_len,
+                               key, keylen,
                                comment, commentlen,
                                key_type | LIBSSH2_KNOWNHOST_TYPE_PLAIN |
                                LIBSSH2_KNOWNHOST_KEYENC_BASE64, NULL);
@@ -628,6 +666,7 @@ static int oldstyle_hostline(LIBSSH2_KNO
 /* |1|[salt]|[hash] */
 static int hashed_hostline(LIBSSH2_KNOWNHOSTS *hosts,
                            const char *host, size_t hostlen,
+                           const char *key_type_name, size_t key_type_len,
                            const char *key, size_t keylen, int key_type,
                            const char *comment, size_t commentlen)
 {
@@ -671,9 +710,11 @@ static int hashed_hostline(LIBSSH2_KNOWN
         memcpy(hostbuf, host, hostlen);
         hostbuf[hostlen]=0;
 
-        return knownhost_add(hosts, hostbuf, salt, key, keylen, comment,
-                             commentlen,
-                             key_type | LIBSSH2_KNOWNHOST_TYPE_SHA1 | 
+        return knownhost_add(hosts, hostbuf, salt,
+                             key_type_name, key_type_len,
+                             key, keylen,
+                             comment, commentlen,
+                             key_type | LIBSSH2_KNOWNHOST_TYPE_SHA1 |
                              LIBSSH2_KNOWNHOST_KEYENC_BASE64, NULL);
     }
     else
@@ -695,7 +736,9 @@ static int hostline(LIBSSH2_KNOWNHOSTS *
                     const char *key, size_t keylen)
 {
     const char *comment = NULL;
+    const char *key_type_name = NULL;
     size_t commentlen = 0;
+    size_t key_type_len;
     int key_type;
 
     /* make some checks that the lengths seem sensible */
@@ -704,7 +747,7 @@ static int hostline(LIBSSH2_KNOWNHOSTS *
                               LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
                               "Failed to parse known_hosts line "
                               "(key too short)");
-    
+
     switch(key[0]) {
     case '0': case '1': case '2': case '3': case '4':
     case '5': case '6': case '7': case '8': case '9':
@@ -717,19 +760,21 @@ static int hostline(LIBSSH2_KNOWNHOSTS *
          */
         break;
 
-    case 's': /* ssh-dss or ssh-rsa */
-        if(!strncmp(key, "ssh-dss", 7))
+    default:
+        key_type_name = key;
+        while (keylen && *key &&
+               (*key != ' ') && (*key != '\t')) {
+            key++;
+            keylen--;
+        }
+        key_type_len = key - key_type_name;
+
+        if (!strncmp(key_type_name, "ssh-dss", key_type_len))
             key_type = LIBSSH2_KNOWNHOST_KEY_SSHDSS;
-        else if(!strncmp(key, "ssh-rsa", 7))
+        if (!strncmp(key_type_name, "ssh-rsa", key_type_len))
             key_type = LIBSSH2_KNOWNHOST_KEY_SSHRSA;
         else
-            /* unknown key type */
-            return _libssh2_error(hosts->session,
-                                  LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
-                                  "Unknown key type");
-
-        key += 7;
-        keylen -= 7;
+            key_type = LIBSSH2_KNOWNHOST_KEY_UNKNOWN;
 
         /* skip whitespaces */
         while((*key ==' ') || (*key == '\t')) {
@@ -761,11 +806,6 @@ static int hostline(LIBSSH2_KNOWNHOSTS *
             commentlen--;
         }
         break;
-
-    default: /* unknown key format */
-        return _libssh2_error(hosts->session,
-                              LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
-                              "Unknown key format");
     }
 
     /* Figure out host format */
@@ -775,12 +815,14 @@ static int hostline(LIBSSH2_KNOWNHOSTS *
            for the sake of simplicity, we add them as separate hosts with the
            same key
         */
-        return oldstyle_hostline(hosts, host, hostlen, key, keylen, key_type,
+        return oldstyle_hostline(hosts, host, hostlen, key_type_name,
+                                 key_type_len, key, keylen, key_type,
                                  comment, commentlen);
     }
     else {
         /* |1|[salt]|[hash] */
-        return hashed_hostline(hosts, host, hostlen, key, keylen, key_type,
+        return hashed_hostline(hosts, host, hostlen, key_type_name,
+                               key_type_len, key, keylen, key_type,
                                comment, commentlen);
     }
 }
@@ -941,17 +983,9 @@ knownhost_writeline(LIBSSH2_KNOWNHOSTS *
                     char *buf, size_t buflen,
                     size_t *outlen, int type)
 {
-    int rc = LIBSSH2_ERROR_NONE;
-    int tindex;
-    const char *keytypes[4]={
-        "", /* not used */
-        "", /* this type has no name in the file */
-        " ssh-rsa",
-        " ssh-dss"
-    };
-    const char *keytype;
-    size_t nlen;
-    size_t commentlen = 0;
+    const char *key_type_name;
+    size_t key_type_len;
+    size_t offset = 0;
 
     /* we only support this single file type for now, bail out on all other
        attempts */
@@ -961,75 +995,101 @@ knownhost_writeline(LIBSSH2_KNOWNHOSTS *
                               "Unsupported type of known-host information "
                               "store");
 
-    tindex = (node->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) >>
-        LIBSSH2_KNOWNHOST_KEY_SHIFT;
-
-    /* set the string used in the file */
-    keytype = keytypes[tindex];
-
-    /* calculate extra space needed for comment */
-    if(node->comment)
-        commentlen = strlen(node->comment) + 1;
+    switch(node->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) {
+    case LIBSSH2_KNOWNHOST_KEY_RSA1:
+	key_type_name = NULL;
+        key_type_len = 0;
+        break;
+    case LIBSSH2_KNOWNHOST_KEY_SSHRSA:
+	key_type_name = "ssh-rsa";
+        key_type_len = 7;
+        break;
+    case LIBSSH2_KNOWNHOST_KEY_SSHDSS:
+	key_type_name = "ssh-dss";
+        key_type_len = 7;
+        break;
+    case LIBSSH2_KNOWNHOST_KEY_UNKNOWN:
+	key_type_name = node->key_type_name;
+        if (key_type_name) {
+	    key_type_len = node->key_type_len;
+            break;
+        }
+        /* otherwise fallback to default and error */
+    default:
+	return _libssh2_error(hosts->session,
+                              LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+                              "Unsupported type of known-host entry");
+    }
 
     if((node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) ==
        LIBSSH2_KNOWNHOST_TYPE_SHA1) {
-        char *namealloc;
-        char *saltalloc;
-        nlen = _libssh2_base64_encode(hosts->session, node->name,
-                                      node->name_len, &namealloc);
-        if(!nlen)
-            return _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
-                                  "Unable to allocate memory for "
-                                  "base64-encoded host name");
-
-        nlen = _libssh2_base64_encode(hosts->session,
-                                      node->salt, node->salt_len,
-                                      &saltalloc);
-        if(!nlen) {
-            free(namealloc);
-            return _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
-                                  "Unable to allocate memory for "
-                                  "base64-encoded salt");
-        }
-
-        nlen = strlen(saltalloc) + strlen(namealloc) + strlen(keytype) +
-            strlen(node->key) + commentlen + 7;
-        /* |1| + | + ' ' + \n + \0 = 7 */
-
-        if(nlen <= buflen)
-            if(node->comment)
-                sprintf(buf, "|1|%s|%s%s %s %s\n", saltalloc, namealloc,
-                        keytype, node->key, node->comment);
-            else
-                sprintf(buf, "|1|%s|%s%s %s\n", saltalloc, namealloc,
-                        keytype, node->key);
+	int rc = LIBSSH2_ERROR_NONE;
+	char *namealloc = NULL;
+        char *saltalloc = NULL;
+        if (_libssh2_base64_encode(hosts->session, node->name,
+                                   node->name_len, &namealloc) &&
+            _libssh2_base64_encode(hosts->session,
+                                   node->salt, node->salt_len,
+                                   &saltalloc))
+	    offset = snprintf(buf, buflen, "|1|%s|%s", saltalloc, namealloc);
         else
-            rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_BUFFER_TOO_SMALL,
-                                "Known-host write buffer too small");
-
-        free(namealloc);
-        free(saltalloc);
+	    rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
+                                "Unable allocate memory for known-host line");
+	if (namealloc)
+            free(namealloc);
+	if (saltalloc)
+	    LIBSSH2_FREE(hosts->session, saltalloc);
+	if (rc != LIBSSH2_ERROR_NONE)
+	    return rc;
+
+	if (buflen <= offset)
+	    return _libssh2_error(hosts->session,
+				  LIBSSH2_ERROR_BUFFER_TOO_SMALL,
+				  "Known-host write buffer too small");
     }
     else {
-        nlen = strlen(node->name) + strlen(keytype) + strlen(node->key) +
-            commentlen + 3;
-        /* ' ' + '\n' + \0 = 3 */
-        if(nlen <= buflen)
-            /* these types have the plain name */
-            if(node->comment)
-                sprintf(buf, "%s%s %s %s\n", node->name, keytype, node->key,
-                        node->comment);
-            else
-                sprintf(buf, "%s%s %s\n", node->name, keytype, node->key);
-        else
-            rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_BUFFER_TOO_SMALL,
-                                "Known-host write buffer too small");
-    }
-
-    /* we report the full length of the data with the trailing zero excluded */
-    *outlen = nlen-1;
+	if (buflen <= node->name_len)
+	    return _libssh2_error(hosts->session,
+				  LIBSSH2_ERROR_BUFFER_TOO_SMALL,
+				  "Known-host write buffer too small");
+	memcpy(buf, node->name, node->name_len);
+	offset = node->name_len;
+    }
+
+    if (key_type_name) {
+	buf[offset++] = ' ';
+	if (buflen - offset <= key_type_len)
+	    return _libssh2_error(hosts->session,
+				  LIBSSH2_ERROR_BUFFER_TOO_SMALL,
+				  "Known-host write buffer too small");
+	memcpy(buf + offset, key_type_name, key_type_len);
+	offset += key_type_len;
+    }
+
+    offset += snprintf(buf + offset, buflen - offset,
+                       " %s", node->key);
+    if (buflen <= offset)
+	return _libssh2_error(hosts->session, LIBSSH2_ERROR_BUFFER_TOO_SMALL,
+                              "Known-host write buffer too small");
+
+    if (node->comment) {
+	buf[offset++] = ' ';
+        if (buflen - offset <= node->comment_len)
+	    return _libssh2_error(hosts->session,
+                                  LIBSSH2_ERROR_BUFFER_TOO_SMALL,
+                                  "Known-host write buffer too small");
+        memcpy(buf + offset, node->comment, node->comment_len);
+        offset += node->comment_len;
+    }
+
+    if (buflen - offset <= 1)
+	return _libssh2_error(hosts->session, LIBSSH2_ERROR_BUFFER_TOO_SMALL,
+                              "Known-host write buffer too small");
+    buf[offset++] = '\n';
 
-    return rc;
+    buf[offset] = '\0';
+    *outlen = offset;
+    return LIBSSH2_ERROR_NONE;
 }
 
 /*
