Working new known_host algorithm
git-svn-id: svn+ssh://svn.berlios.de/svnroot/repos/libssh/trunk@302 7dcaeef0-15fb-0310-b436-a5af3365683c
Этот коммит содержится в:
родитель
75d5bb457f
Коммит
3090d104cf
@ -245,7 +245,7 @@ struct ssh_options_struct {
|
||||
typedef struct ssh_crypto_struct {
|
||||
bignum e,f,x,k,y;
|
||||
unsigned char session_id[SHA_DIGEST_LEN];
|
||||
|
||||
|
||||
unsigned char encryptIV[SHA_DIGEST_LEN*2];
|
||||
unsigned char decryptIV[SHA_DIGEST_LEN*2];
|
||||
|
||||
@ -307,32 +307,32 @@ struct ssh_session {
|
||||
/* status flags */
|
||||
int closed;
|
||||
int closed_by_except;
|
||||
|
||||
int connected;
|
||||
|
||||
int connected;
|
||||
/* !=0 when the user got a session handle */
|
||||
int alive;
|
||||
/* two previous are deprecated */
|
||||
int auth_service_asked;
|
||||
|
||||
|
||||
/* socket status */
|
||||
int blocking; // functions should block
|
||||
|
||||
STRING *banner; /* that's the issue banner from
|
||||
|
||||
STRING *banner; /* that's the issue banner from
|
||||
the server */
|
||||
char *remotebanner; /* that's the SSH- banner from
|
||||
remote host. */
|
||||
char *discon_msg; /* disconnect message from
|
||||
char *discon_msg; /* disconnect message from
|
||||
the remote host */
|
||||
BUFFER *in_buffer;
|
||||
PACKET in_packet;
|
||||
BUFFER *out_buffer;
|
||||
|
||||
|
||||
/* the states are used by the nonblocking stuff to remember */
|
||||
/* where it was before being interrupted */
|
||||
int packet_state;
|
||||
int dh_handshake_state;
|
||||
STRING *dh_server_signature; //information used by dh_handshake.
|
||||
|
||||
|
||||
KEX server_kex;
|
||||
KEX client_kex;
|
||||
BUFFER *in_hashbuf;
|
||||
@ -342,7 +342,7 @@ struct ssh_session {
|
||||
|
||||
CHANNEL *channels; /* linked list of channels */
|
||||
int maxchannel;
|
||||
int exec_channel_opened; /* version 1 only. more
|
||||
int exec_channel_opened; /* version 1 only. more
|
||||
info in channels1.c */
|
||||
AGENT *agent; /* ssh agent */
|
||||
|
||||
@ -353,7 +353,7 @@ struct ssh_session {
|
||||
PRIVATE_KEY *rsa_key;
|
||||
PRIVATE_KEY *dsa_key;
|
||||
/* auths accepted by server */
|
||||
int auth_methods;
|
||||
int auth_methods;
|
||||
int hostkeys; /* contains type of host key wanted by client, in server impl */
|
||||
struct ssh_message *ssh_message; /* ssh message */
|
||||
int log_verbosity; /*cached copy of the option structure */
|
||||
@ -407,7 +407,7 @@ struct ssh_channel_request {
|
||||
u32 pxwidth;
|
||||
u32 pxheight;
|
||||
STRING *modes;
|
||||
|
||||
|
||||
/* env type request */
|
||||
char *var_name;
|
||||
char *var_value;
|
||||
@ -552,7 +552,7 @@ int packet_wait(SSH_SESSION *session,int type,int blocking);
|
||||
int packet_flush(SSH_SESSION *session, int enforce_blocking);
|
||||
/* connect.c */
|
||||
SSH_SESSION *ssh_session_new();
|
||||
socket_t ssh_connect_host(SSH_SESSION *session, const char *host,const char
|
||||
socket_t ssh_connect_host(SSH_SESSION *session, const char *host,const char
|
||||
*bind_addr, int port, long timeout, long usec);
|
||||
|
||||
/* in kex.c */
|
||||
@ -586,7 +586,7 @@ SIGNATURE *signature_from_string(SSH_SESSION *session, STRING *signature,PUBLIC_
|
||||
void signature_free(SIGNATURE *sign);
|
||||
STRING *ssh_do_sign_with_agent(struct ssh_session *session,
|
||||
struct buffer_struct *buf, struct public_key_struct *publickey);
|
||||
STRING *ssh_do_sign(SSH_SESSION *session,BUFFER *sigbuf,
|
||||
STRING *ssh_do_sign(SSH_SESSION *session,BUFFER *sigbuf,
|
||||
PRIVATE_KEY *privatekey);
|
||||
STRING *ssh_sign_session_id(SSH_SESSION *session, PRIVATE_KEY *privatekey);
|
||||
STRING *ssh_encrypt_rsa1(SSH_SESSION *session, STRING *data, PUBLIC_KEY *key);
|
||||
@ -656,7 +656,7 @@ u32 ssh_crc32(char *buffer, int len);
|
||||
int ssh_userauth1_none(SSH_SESSION *session, char *username);
|
||||
int ssh_userauth1_offer_pubkey(SSH_SESSION *session, char *username,
|
||||
int type, STRING *pubkey);
|
||||
int ssh_userauth1_password(SSH_SESSION *session, char *username,
|
||||
int ssh_userauth1_password(SSH_SESSION *session, char *username,
|
||||
char *password);
|
||||
/* in misc.c */
|
||||
/* gets the user home dir. */
|
||||
@ -669,7 +669,7 @@ u64 ntohll(u64);
|
||||
|
||||
/* channels1.c */
|
||||
int channel_open_session1(CHANNEL *channel);
|
||||
int channel_request_pty_size1(CHANNEL *channel, char *terminal,int cols,
|
||||
int channel_request_pty_size1(CHANNEL *channel, char *terminal,int cols,
|
||||
int rows);
|
||||
int channel_change_pty_size1(CHANNEL *channel, int cols, int rows);
|
||||
int channel_request_shell1(CHANNEL *channel);
|
||||
@ -681,6 +681,9 @@ int channel_write1(CHANNEL *channel, void *data, int len);
|
||||
|
||||
int ssh_handle_packets(SSH_SESSION *session);
|
||||
|
||||
/* match.c */
|
||||
int match_hostname(const char *host, const char *pattern, unsigned int len);
|
||||
|
||||
/* log.c */
|
||||
|
||||
#define _enter_function(sess) \
|
||||
@ -718,7 +721,7 @@ char *my_gcry_bn2dec(bignum bn);
|
||||
#endif /* !HAVE_LIBGCRYPT */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _LIBSSH_PRIV_H */
|
||||
|
@ -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
|
||||
|
@ -37,7 +37,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "libssh/priv.h"
|
||||
/*
|
||||
* Returns true if the given string matches the pattern (which may contain ?
|
||||
* 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
|
||||
* 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);
|
||||
}
|
||||
|
Загрузка…
x
Ссылка в новой задаче
Block a user