summaryrefslogtreecommitdiffstats
path: root/libssh/keyfiles.c
diff options
context:
space:
mode:
authorAris Adamantiadis <aris@0xbadc0de.be>2009-03-28 23:43:17 +0000
committerAris Adamantiadis <aris@0xbadc0de.be>2009-03-28 23:43:17 +0000
commit3090d104cf050028dbe2b29788ace057d8eca321 (patch)
treeb323c5d8f9fe95a392133234981abce3a485b64c /libssh/keyfiles.c
parent75d5bb457f604da62a5b821ea0abfa61d38ee156 (diff)
downloadlibssh-3090d104cf050028dbe2b29788ace057d8eca321.tar.gz
libssh-3090d104cf050028dbe2b29788ace057d8eca321.tar.xz
libssh-3090d104cf050028dbe2b29788ace057d8eca321.zip
Working new known_host algorithm
git-svn-id: svn+ssh://svn.berlios.de/svnroot/repos/libssh/trunk@302 7dcaeef0-15fb-0310-b436-a5af3365683c
Diffstat (limited to 'libssh/keyfiles.c')
-rw-r--r--libssh/keyfiles.c285
1 files changed, 173 insertions, 112 deletions
diff --git a/libssh/keyfiles.c b/libssh/keyfiles.c
index 3803f03..1a145de 100644
--- a/libssh/keyfiles.c
+++ b/libssh/keyfiles.c
@@ -28,6 +28,7 @@ MA 02111-1307, USA. */
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
+#include <ctype.h>
#include "libssh/priv.h"
#ifdef HAVE_LIBGCRYPT
#include <gcrypt.h>
@@ -780,79 +781,160 @@ static int alldigits(char *s)
}
return 1;
}
+/** @}
+ */
-#define FOUND_OTHER ( (void *)-1)
-#define FILE_NOT_FOUND ((void *)-2)
-/* will return a token array containing [host,]ip keytype key */
-/* NULL if no match was found, FOUND_OTHER if the match is on an other */
-/* type of key (ie dsa if type was rsa) */
-static char **ssh_parse_knownhost(char *filename, char *hostname, char *type){
- FILE *file=fopen(filename,"r");
+/** \brief lowercases a string
+ * \arg string string to lowercase
+ * \internal
+ */
+static void lowercase(char *string){
+ for (;*string;string++){
+ *string=tolower(*string);
+ }
+}
+/** \brief frees a token array
+ * \internal
+ */
+static void tokens_free(char **tokens){
+ free(tokens[0]);
+ /* It's not needed to free other pointers because tokens generated by
+ * space_tokenize fit all in one malloc
+ */
+ free(tokens);
+}
+/** \brief returns one line of known host file
+ * will return a token array containing (host|ip) keytype key
+ * \internal
+ * \returns NULL if no match was found or the file was not found
+ * \returns found_type type of key (ie "dsa","ssh-rsa1"). Don't free that value.
+ */
+
+static char **ssh_get_knownhost_line(SSH_SESSION *session,FILE **file, char *filename,char **found_type){
char buffer[4096];
char *ptr;
- char *found_type;
char **tokens;
- char **ret=NULL;
- if(!file)
- return FILE_NOT_FOUND;
- while(fgets(buffer,sizeof(buffer),file)){
+ enter_function();
+ if(!*file){
+ *file=fopen(filename,"r");
+ if(!file){
+ leave_function();
+ return NULL;
+ }
+ }
+ while(fgets(buffer,sizeof(buffer),*file)){
ptr=strchr(buffer,'\n');
if(ptr) *ptr=0;
if((ptr=strchr(buffer,'\r'))) *ptr=0;
- if(!buffer[0])
+ if(!buffer[0] || buffer[0]=='#')
continue; /* skip empty lines */
tokens=space_tokenize(buffer);
if(!tokens[0] || !tokens[1] || !tokens[2]){
/* it should have at least 3 tokens */
- free(tokens[0]);
- free(tokens);
+ tokens_free(tokens);
continue;
}
- found_type = tokens[1];
+ *found_type = tokens[1];
if(tokens[3]){
/* openssh rsa1 format has 4 tokens on the line. Recognize it
by the fact that everything is all digits */
if (tokens[4]) {
/* that's never valid */
- free(tokens[0]);
- free(tokens);
+ tokens_free(tokens);
continue;
}
if (alldigits(tokens[1]) && alldigits(tokens[2]) && alldigits(tokens[3])) {
- found_type = "ssh-rsa1";
+ *found_type = "ssh-rsa1";
} else {
/* 3 tokens only, not four */
- free(tokens[0]);
- free(tokens);
+ tokens_free(tokens);
continue;
}
}
- ptr=tokens[0];
- while(*ptr==' ')
- ptr++; /* skip the initial spaces */
- /* we allow spaces or ',' to follow the hostname. It's generaly an IP */
- /* we don't care about ip, if the host key match there is no problem with ip */
- if(strncasecmp(ptr,hostname,strlen(hostname))==0){
- if(ptr[strlen(hostname)]==' ' || ptr[strlen(hostname)]=='\0'
- || ptr[strlen(hostname)]==','){
- if(strcasecmp(found_type, type)==0){
- fclose(file);
- return tokens;
- } else {
- ret=FOUND_OTHER;
- }
- }
- }
- /* not the good one */
- free(tokens[0]);
- free(tokens);
+ leave_function();
+ return tokens;
}
- fclose(file);
- /* we did not find */
- return ret;
+ fclose(*file);
+ *file=NULL;
+ /* we did not find anything, end of file*/
+ leave_function();
+ return NULL;
}
-/** @}
+/** \brief Check the public key in the known host line matches the
+ * public key of the currently connected server.
+ * \arg tokens list of tokens in the known_hosts line.
+ * \return 1 if the key matches
+ * \return 0 if the key doesn't match
+ * \return -1 on error
+ */
+
+static int check_public_key(SSH_SESSION *session, char **tokens){
+ char *pubkey_64;
+ BUFFER *pubkey_buffer;
+ STRING *pubkey=session->current_crypto->server_pubkey;
+ /* ok we found some public key in known hosts file. now un-base64it */
+ if (alldigits(tokens[1])) {
+ /* openssh rsa1 format */
+ bignum tmpbn;
+ int i;
+ unsigned int len;
+ STRING *tmpstring;
+
+ pubkey_buffer = buffer_new();
+ tmpstring = string_from_char("ssh-rsa1");
+ buffer_add_ssh_string(pubkey_buffer, tmpstring);
+
+ for (i = 2; i < 4; i++) { /* e, then n */
+ tmpbn = NULL;
+ bignum_dec2bn(tokens[i], &tmpbn);
+ /* for some reason, make_bignum_string does not work
+ because of the padding which it does --kv */
+ /* tmpstring = make_bignum_string(tmpbn); */
+ /* do it manually instead */
+ len = bignum_num_bytes(tmpbn);
+ tmpstring = malloc(4 + len);
+ tmpstring->size = htonl(len);
+#ifdef HAVE_LIBGCRYPT
+ bignum_bn2bin(tmpbn, len, tmpstring->string);
+#elif defined HAVE_LIBCRYPTO
+ bignum_bn2bin(tmpbn, tmpstring->string);
+#endif
+ bignum_free(tmpbn);
+ buffer_add_ssh_string(pubkey_buffer, tmpstring);
+ free(tmpstring);
+ }
+ } else {
+ /* ssh-dss or ssh-rsa */
+ pubkey_64=tokens[2];
+ pubkey_buffer=base64_to_bin(pubkey_64);
+ }
+
+ if(!pubkey_buffer){
+ ssh_set_error(session,SSH_FATAL,"verifying that server is a known host : base 64 error");
+ return -1;
+ }
+ if(buffer_get_len(pubkey_buffer)!=string_len(pubkey)){
+ buffer_free(pubkey_buffer);
+ return 0;
+ }
+ /* now test that they are identical */
+ if(memcmp(buffer_get(pubkey_buffer),pubkey->string,buffer_get_len(pubkey_buffer))!=0){
+ buffer_free(pubkey_buffer);
+ return 0;
+ }
+ buffer_free(pubkey_buffer);
+ return 1;
+}
+
+
+/* How it's working :
+ * 1- we open the known host file and bitch if it doesn't exist
+ * 2- we need to examine each line of the file, until going on state SSH_SERVER_KNOWN_OK:
+ * - there's a match. if the key is good, state is SSH_SERVER_KNOWN_OK,
+ * else it's SSH_SERVER_KNOWN_CHANGED (or SSH_SERVER_FOUND_OTHER)
+ * - there's no match : no change
+ * - there's an antimatch : no change (that line is simply ignored)
*/
/** \addtogroup ssh_session
* @{ */
@@ -874,80 +956,59 @@ static char **ssh_parse_knownhost(char *filename, char *hostname, char *type){
* \todo TODO this is a real mess. Clean this up someday
*/
int ssh_is_server_known(SSH_SESSION *session){
- char *pubkey_64;
- BUFFER *pubkey_buffer;
- STRING *pubkey=session->current_crypto->server_pubkey;
+
char **tokens;
+ char *host;
+ char *type;
+ int match;
+ FILE *file=NULL;
+ int ret=SSH_SERVER_NOT_KNOWN;
+ enter_function();
ssh_options_default_known_hosts_file(session->options);
if(!session->options->host){
ssh_set_error(session,SSH_FATAL,"Can't verify host in known hosts if the hostname isn't known");
+ leave_function();
return SSH_SERVER_ERROR;
}
- tokens=ssh_parse_knownhost(session->options->known_hosts_file,
- session->options->host,session->current_crypto->server_pubkey_type);
- if(tokens==NULL)
- return SSH_SERVER_NOT_KNOWN;
- if(tokens==FOUND_OTHER)
- return SSH_SERVER_FOUND_OTHER;
- if(tokens==FILE_NOT_FOUND){
- ssh_set_error(session,SSH_FATAL,"verifying that server is a known host : file %s not found",session->options->known_hosts_file);
- return SSH_SERVER_ERROR;
- }
- /* ok we found some public key in known hosts file. now un-base64it */
- /* Some time, we may verify the IP address did not change. I honestly think */
- /* it's not an important matter as IP address are known not to be secure */
- /* and the crypto stuff is enough to prove the server's identity */
- if (alldigits(tokens[1])) { /* openssh rsa1 format */
- bignum tmpbn;
- int i;
- unsigned int len;
- STRING *tmpstring;
-
- pubkey_buffer = buffer_new();
- tmpstring = string_from_char("ssh-rsa1");
- buffer_add_ssh_string(pubkey_buffer, tmpstring);
-
- for (i = 2; i < 4; i++) { /* e, then n */
- tmpbn = NULL;
- bignum_dec2bn(tokens[i], &tmpbn);
- /* for some reason, make_bignum_string does not work
- because of the padding which it does --kv */
- /* tmpstring = make_bignum_string(tmpbn); */
- /* do it manually instead */
- len = bignum_num_bytes(tmpbn);
- tmpstring = malloc(4 + len);
- tmpstring->size = htonl(len);
-#ifdef HAVE_LIBGCRYPT
- bignum_bn2bin(tmpbn, len, tmpstring->string);
-#elif defined HAVE_LIBCRYPTO
- bignum_bn2bin(tmpbn, tmpstring->string);
-#endif
- bignum_free(tmpbn);
- buffer_add_ssh_string(pubkey_buffer, tmpstring);
- free(tmpstring);
- }
- } else {
- pubkey_64=tokens[2];
- pubkey_buffer=base64_to_bin(pubkey_64);
- }
- /* at this point, we may free the tokens */
- free(tokens[0]);
- free(tokens);
- if(!pubkey_buffer){
- ssh_set_error(session,SSH_FATAL,"verifying that server is a known host : base 64 error");
- return SSH_SERVER_ERROR;
- }
- if(buffer_get_len(pubkey_buffer)!=string_len(pubkey)){
- buffer_free(pubkey_buffer);
- return SSH_SERVER_KNOWN_CHANGED;
- }
- /* now test that they are identical */
- if(memcmp(buffer_get(pubkey_buffer),pubkey->string,buffer_get_len(pubkey_buffer))!=0){
- buffer_free(pubkey_buffer);
- return SSH_SERVER_KNOWN_CHANGED;
- }
- buffer_free(pubkey_buffer);
- return SSH_SERVER_KNOWN_OK;
+ host=strdup(session->options->host);
+ lowercase(host);
+ do {
+ tokens=ssh_get_knownhost_line(session,&file,session->options->known_hosts_file,&type);
+ //
+ /* End of file, return the current state */
+ if(tokens==NULL)
+ break;
+ match=match_hostname(host,tokens[0],strlen(tokens[0]));
+ if(match){
+ // We got a match. Now check the key type
+ if(strcmp(session->current_crypto->server_pubkey_type,type)!=0){
+ // different type. We don't override the known_changed error which is more important
+ if(ret != SSH_SERVER_KNOWN_CHANGED)
+ ret= SSH_SERVER_FOUND_OTHER;
+ tokens_free(tokens);
+ continue;
+ }
+ // so we know the key type is good. We may get a good key or a bad key.
+ match=check_public_key(session,tokens);
+ tokens_free(tokens);
+ if(match<0){
+ leave_function();
+ return SSH_SERVER_ERROR;
+ }
+ if(match==1){
+ fclose(file);
+ leave_function();
+ return SSH_SERVER_KNOWN_OK;
+ }
+ if(match==0){
+ /* We override the status with the wrong key state */
+ ret=SSH_SERVER_KNOWN_CHANGED;
+ }
+ }
+ } while (1);
+ /* Return the current state at end of file */
+ leave_function();
+ return ret;
}
/** You generaly use it when ssh_is_server_known() answered SSH_SERVER_NOT_KNOWN