1
0
mirror of https://git.libssh.org/projects/libssh.git synced 2025-12-02 01:17:52 +03:00

Working new known_host algorithm

git-svn-id: svn+ssh://svn.berlios.de/svnroot/repos/libssh/trunk@302 7dcaeef0-15fb-0310-b436-a5af3365683c
This commit is contained in:
Aris Adamantiadis
2009-03-28 23:43:17 +00:00
parent 75d5bb457f
commit 3090d104cf
3 changed files with 195 additions and 131 deletions

View File

@@ -681,6 +681,9 @@ int channel_write1(CHANNEL *channel, void *data, int len);
int ssh_handle_packets(SSH_SESSION *session); int ssh_handle_packets(SSH_SESSION *session);
/* match.c */
int match_hostname(const char *host, const char *pattern, unsigned int len);
/* log.c */ /* log.c */
#define _enter_function(sess) \ #define _enter_function(sess) \

View File

@@ -28,6 +28,7 @@ MA 02111-1307, USA. */
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
#include <fcntl.h> #include <fcntl.h>
#include <ctype.h>
#include "libssh/priv.h" #include "libssh/priv.h"
#ifdef HAVE_LIBGCRYPT #ifdef HAVE_LIBGCRYPT
#include <gcrypt.h> #include <gcrypt.h>
@@ -780,79 +781,160 @@ static int alldigits(char *s)
} }
return 1; return 1;
} }
/** @}
*/
#define FOUND_OTHER ( (void *)-1) /** \brief lowercases a string
#define FILE_NOT_FOUND ((void *)-2) * \arg string string to lowercase
/* will return a token array containing [host,]ip keytype key */ * \internal
/* 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 void lowercase(char *string){
static char **ssh_parse_knownhost(char *filename, char *hostname, char *type){ for (;*string;string++){
FILE *file=fopen(filename,"r"); *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 buffer[4096];
char *ptr; char *ptr;
char *found_type;
char **tokens; char **tokens;
char **ret=NULL; enter_function();
if(!file) if(!*file){
return FILE_NOT_FOUND; *file=fopen(filename,"r");
while(fgets(buffer,sizeof(buffer),file)){ if(!file){
leave_function();
return NULL;
}
}
while(fgets(buffer,sizeof(buffer),*file)){
ptr=strchr(buffer,'\n'); ptr=strchr(buffer,'\n');
if(ptr) *ptr=0; if(ptr) *ptr=0;
if((ptr=strchr(buffer,'\r'))) *ptr=0; if((ptr=strchr(buffer,'\r'))) *ptr=0;
if(!buffer[0]) if(!buffer[0] || buffer[0]=='#')
continue; /* skip empty lines */ continue; /* skip empty lines */
tokens=space_tokenize(buffer); tokens=space_tokenize(buffer);
if(!tokens[0] || !tokens[1] || !tokens[2]){ if(!tokens[0] || !tokens[1] || !tokens[2]){
/* it should have at least 3 tokens */ /* it should have at least 3 tokens */
free(tokens[0]); tokens_free(tokens);
free(tokens);
continue; continue;
} }
found_type = tokens[1]; *found_type = tokens[1];
if(tokens[3]){ if(tokens[3]){
/* openssh rsa1 format has 4 tokens on the line. Recognize it /* openssh rsa1 format has 4 tokens on the line. Recognize it
by the fact that everything is all digits */ by the fact that everything is all digits */
if (tokens[4]) { if (tokens[4]) {
/* that's never valid */ /* that's never valid */
free(tokens[0]); tokens_free(tokens);
free(tokens);
continue; continue;
} }
if (alldigits(tokens[1]) && alldigits(tokens[2]) && alldigits(tokens[3])) { if (alldigits(tokens[1]) && alldigits(tokens[2]) && alldigits(tokens[3])) {
found_type = "ssh-rsa1"; *found_type = "ssh-rsa1";
} else { } else {
/* 3 tokens only, not four */ /* 3 tokens only, not four */
free(tokens[0]); tokens_free(tokens);
free(tokens);
continue; continue;
} }
} }
ptr=tokens[0]; leave_function();
while(*ptr==' ') return tokens;
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);
} }
fclose(file); fclose(*file);
/* we did not find */ *file=NULL;
return ret; /* 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 /** \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 * \todo TODO this is a real mess. Clean this up someday
*/ */
int ssh_is_server_known(SSH_SESSION *session){ int ssh_is_server_known(SSH_SESSION *session){
char *pubkey_64;
BUFFER *pubkey_buffer;
STRING *pubkey=session->current_crypto->server_pubkey;
char **tokens; 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); ssh_options_default_known_hosts_file(session->options);
if(!session->options->host){ if(!session->options->host){
ssh_set_error(session,SSH_FATAL,"Can't verify host in known hosts if the hostname isn't known"); 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; return SSH_SERVER_ERROR;
} }
tokens=ssh_parse_knownhost(session->options->known_hosts_file, host=strdup(session->options->host);
session->options->host,session->current_crypto->server_pubkey_type); lowercase(host);
if(tokens==NULL) do {
return SSH_SERVER_NOT_KNOWN; tokens=ssh_get_knownhost_line(session,&file,session->options->known_hosts_file,&type);
if(tokens==FOUND_OTHER) //
return SSH_SERVER_FOUND_OTHER; /* End of file, return the current state */
if(tokens==FILE_NOT_FOUND){ if(tokens==NULL)
ssh_set_error(session,SSH_FATAL,"verifying that server is a known host : file %s not found",session->options->known_hosts_file); break;
return SSH_SERVER_ERROR; match=match_hostname(host,tokens[0],strlen(tokens[0]));
} if(match){
/* ok we found some public key in known hosts file. now un-base64it */ // We got a match. Now check the key type
/* Some time, we may verify the IP address did not change. I honestly think */ if(strcmp(session->current_crypto->server_pubkey_type,type)!=0){
/* it's not an important matter as IP address are known not to be secure */ // different type. We don't override the known_changed error which is more important
/* and the crypto stuff is enough to prove the server's identity */ if(ret != SSH_SERVER_KNOWN_CHANGED)
if (alldigits(tokens[1])) { /* openssh rsa1 format */ ret= SSH_SERVER_FOUND_OTHER;
bignum tmpbn; tokens_free(tokens);
int i; continue;
unsigned int len; }
STRING *tmpstring; // so we know the key type is good. We may get a good key or a bad key.
match=check_public_key(session,tokens);
pubkey_buffer = buffer_new(); tokens_free(tokens);
tmpstring = string_from_char("ssh-rsa1"); if(match<0){
buffer_add_ssh_string(pubkey_buffer, tmpstring); leave_function();
return SSH_SERVER_ERROR;
for (i = 2; i < 4; i++) { /* e, then n */ }
tmpbn = NULL; if(match==1){
bignum_dec2bn(tokens[i], &tmpbn); fclose(file);
/* for some reason, make_bignum_string does not work leave_function();
because of the padding which it does --kv */ return SSH_SERVER_KNOWN_OK;
/* tmpstring = make_bignum_string(tmpbn); */ }
/* do it manually instead */ if(match==0){
len = bignum_num_bytes(tmpbn); /* We override the status with the wrong key state */
tmpstring = malloc(4 + len); ret=SSH_SERVER_KNOWN_CHANGED;
tmpstring->size = htonl(len); }
#ifdef HAVE_LIBGCRYPT }
bignum_bn2bin(tmpbn, len, tmpstring->string); } while (1);
#elif defined HAVE_LIBCRYPTO /* Return the current state at end of file */
bignum_bn2bin(tmpbn, tmpstring->string); leave_function();
#endif return ret;
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;
} }
/** You generaly use it when ssh_is_server_known() answered SSH_SERVER_NOT_KNOWN /** You generaly use it when ssh_is_server_known() answered SSH_SERVER_NOT_KNOWN

View File

@@ -37,7 +37,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <ctype.h> #include <ctype.h>
#include <string.h> #include <string.h>
#include "libssh/priv.h"
/* /*
* Returns true if the given string matches the pattern (which may contain ? * Returns true if the given string matches the pattern (which may contain ?
* and * as wildcards), and zero if it does not match. * and * as wildcards), and zero if it does not match.
@@ -164,6 +164,6 @@ static int match_pattern_list(const char *string, const char *pattern,
* indicate negation). Returns -1 if negation matches, 1 if there is * indicate negation). Returns -1 if negation matches, 1 if there is
* a positive match, 0 if there is no match at all. * a positive match, 0 if there is no match at all.
*/ */
static int match_hostname(const char *host, const char *pattern, unsigned int len) { int match_hostname(const char *host, const char *pattern, unsigned int len) {
return match_pattern_list(host, pattern, len, 1); return match_pattern_list(host, pattern, len, 1);
} }