1 /* keyfiles.c */ 2 /* This part of the library handles private and public key files needed for publickey authentication,*/ 3 /* as well as servers public hashes verifications and certifications. Lot of code here handles openssh */ 4 /* implementations (key files aren't standardized yet). */ 5 6 /* 7 Copyright 2003,04 Aris Adamantiadis 8 9 This file is part of the SSH Library 10 11 The SSH Library is free software; you can redistribute it and/or modify 12 it under the terms of the GNU Lesser General Public License as published by 13 the Free Software Foundation; either version 2.1 of the License, or (at your 14 option) any later version. 15 16 The SSH Library is distributed in the hope that it will be useful, but 17 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 18 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 19 License for more details. 20 21 You should have received a copy of the GNU Lesser General Public License 22 along with the SSH Library; see the file COPYING. If not, write to 23 the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, 24 MA 02111-1307, USA. */ 25 #include <stdio.h> 26 #include <string.h> 27 #include <errno.h> 28 #include <unistd.h> 29 #include <stdlib.h> 30 #include <fcntl.h> 31 #include <openssl/pem.h> 32 #include <openssl/dsa.h> 33 #include <openssl/err.h> 34 #include <openssl/rsa.h> 35 #include "libssh/priv.h" 36 #define MAXLINESIZE 80 37 38 static int default_get_password(char *buf, int size,int rwflag, char *descr){ 39 char *pass; 40 char buffer[256]; 41 int len; 42 snprintf(buffer,256,"Please enter passphrase for %s",descr); 43 #ifdef HAVE_GETPASS 44 pass=getpass(buffer); 45 snprintf(buf,size,"%s",buffer); 46 memset(pass,0,strlen(pass)); 47 #else 48 fgets(buf, size, stdin); 49 if (strlen(buf) > 0) { 50 if (buf[strlen(buf) - 1] == '\n') { 51 buf[strlen(buf) - 1] = '\0'; 52 } 53 } 54 #endif 55 len=strlen(buf); 56 return len; 57 } 58 59 /* in case the passphrase has been given in parameter */ 60 static int get_password_specified(char *buf,int size, int rwflag, char *password){ 61 snprintf(buf,size,"%s",password); 62 return strlen(buf); 63 } 64 65 /* TODO : implement it to read both DSA and RSA at once */ 66 PRIVATE_KEY *privatekey_from_file(SSH_SESSION *session,char *filename,int type,char *passphrase){ 67 FILE *file=fopen(filename,"r"); 68 PRIVATE_KEY *privkey; 69 DSA *dsa=NULL; 70 RSA *rsa=NULL; 71 if(!file){ 72 ssh_set_error(session,SSH_REQUEST_DENIED,"Error opening %s : %s",filename,strerror(errno)); 73 return NULL; 74 } 75 if(type==TYPE_DSS){ 76 if(!passphrase){ 77 if(session && session->options->passphrase_function) 78 dsa=PEM_read_DSAPrivateKey(file,NULL, session->options->passphrase_function,"DSA private key"); 79 else 80 dsa=PEM_read_DSAPrivateKey(file,NULL,(void *)default_get_password, "DSA private key"); 81 } 82 else 83 dsa=PEM_read_DSAPrivateKey(file,NULL,(void *)get_password_specified,passphrase); 84 fclose(file); 85 if(!dsa){ 86 ssh_set_error(session,SSH_FATAL,"parsing private key %s : %s",filename,ERR_error_string(ERR_get_error(),NULL)); 87 return NULL; 88 } 89 } 90 else if (type==TYPE_RSA){ 91 if(!passphrase){ 92 if(session && session->options->passphrase_function) 93 rsa=PEM_read_RSAPrivateKey(file,NULL, session->options->passphrase_function,"RSA private key"); 94 else 95 rsa=PEM_read_RSAPrivateKey(file,NULL,(void *)default_get_password, "RSA private key"); 96 } 97 else 98 rsa=PEM_read_RSAPrivateKey(file,NULL,(void *)get_password_specified,passphrase); 99 fclose(file); 100 if(!rsa){ 101 ssh_set_error(session,SSH_FATAL,"parsing private key %s : %s",filename,ERR_error_string(ERR_get_error(),NULL)); 102 return NULL; 103 } 104 } else { 105 ssh_set_error(session,SSH_FATAL,"Invalid private key type %d",type); 106 return NULL; 107 } 108 109 privkey=malloc(sizeof(PRIVATE_KEY)); 110 privkey->type=type; 111 privkey->dsa_priv=dsa; 112 privkey->rsa_priv=rsa; 113 return privkey; 114 } 115 116 void private_key_free(PRIVATE_KEY *prv){ 117 if(prv->dsa_priv) 118 DSA_free(prv->dsa_priv); 119 if(prv->rsa_priv) 120 RSA_free(prv->rsa_priv); 121 memset(prv,0,sizeof(PRIVATE_KEY)); 122 free(prv); 123 } 124 125 STRING *publickey_from_file(char *filename,int *_type){ 126 BUFFER *buffer; 127 int type; 128 STRING *str; 129 char buf[4096]; /* noone will have bigger keys that that */ 130 /* where have i head that again ? */ 131 int fd=open(filename,O_RDONLY); 132 int r; 133 char *ptr; 134 if(fd<0){ 135 ssh_set_error(NULL,SSH_INVALID_REQUEST,"nonexistent public key file"); 136 return NULL; 137 } 138 if(read(fd,buf,8)!=8){ 139 close(fd); 140 ssh_set_error(NULL,SSH_INVALID_REQUEST,"Invalid public key file"); 141 return NULL; 142 } 143 buf[7]=0; 144 if(!strcmp(buf,"ssh-dss")) 145 type=TYPE_DSS; 146 else if (!strcmp(buf,"ssh-rsa")) 147 type=TYPE_RSA; 148 else { 149 close(fd); 150 ssh_set_error(NULL,SSH_INVALID_REQUEST,"Invalid public key file"); 151 return NULL; 152 } 153 r=read(fd,buf,sizeof(buf)-1); 154 close(fd); 155 if(r<=0){ 156 ssh_set_error(NULL,SSH_INVALID_REQUEST,"Invalid public key file"); 157 return NULL; 158 } 159 buf[r]=0; 160 ptr=strchr(buf,' '); 161 if(ptr) 162 *ptr=0; /* eliminates the garbage at end of file */ 163 buffer=base64_to_bin(buf); 164 if(buffer){ 165 str=string_new(buffer_get_len(buffer)); 166 string_fill(str,buffer_get(buffer),buffer_get_len(buffer)); 167 buffer_free(buffer); 168 if(_type) 169 *_type=type; 170 return str; 171 } else { 172 ssh_set_error(NULL,SSH_INVALID_REQUEST,"Invalid public key file"); 173 return NULL; /* invalid file */ 174 } 175 } 176 177 178 /* why recursing ? i'll explain. on top, publickey_from_next_file will be executed until NULL returned */ 179 /* we can't return null if one of the possible keys is wrong. we must test them before getting over */ 180 STRING *publickey_from_next_file(SSH_SESSION *session,char **pub_keys_path,char **keys_path, 181 char **privkeyfile,int *type,int *count){ 182 static char *home=NULL; 183 char public[256]; 184 char private[256]; 185 char *priv; 186 char *pub; 187 STRING *pubkey; 188 if(!home) 189 home=ssh_get_user_home_dir(); 190 if(home==NULL) { 191 ssh_set_error(session,SSH_FATAL,"User home dir impossible to guess"); 192 return NULL; 193 } 194 ssh_set_error(session,SSH_NO_ERROR,"no public key matched"); 195 if((pub=pub_keys_path[*count])==NULL) 196 return NULL; 197 if((priv=keys_path[*count])==NULL) 198 return NULL; 199 ++*count; 200 /* are them readable ? */ 201 snprintf(public,256,pub,home); 202 ssh_say(2,"Trying to open %s\n",public); 203 if(!ssh_file_readaccess_ok(public)){ 204 ssh_say(2,"Failed\n"); 205 return publickey_from_next_file(session,pub_keys_path,keys_path,privkeyfile,type,count); 206 } 207 snprintf(private,256,priv,home); 208 ssh_say(2,"Trying to open %s\n",private); 209 if(!ssh_file_readaccess_ok(private)){ 210 ssh_say(2,"Failed\n"); 211 return publickey_from_next_file(session,pub_keys_path,keys_path,privkeyfile,type,count); 212 } 213 ssh_say(2,"Okay both files ok\n"); 214 /* ok, we are sure both the priv8 and public key files are readable : we return the public one as a string, 215 and the private filename in arguments */ 216 pubkey=publickey_from_file(public,type); 217 if(!pubkey){ 218 ssh_say(2,"Wasn't able to open public key file %s : %s\n",public,ssh_get_error(session)); 219 return publickey_from_next_file(session,pub_keys_path,keys_path,privkeyfile,type,count); 220 } 221 *privkeyfile=realloc(*privkeyfile,strlen(private)+1); 222 strcpy(*privkeyfile,private); 223 return pubkey; 224 } 225 226 #define FOUND_OTHER ( (void *)-1) 227 #define FILE_NOT_FOUND ((void *)-2) 228 /* will return a token array containing [host,]ip keytype key */ 229 /* NULL if no match was found, FOUND_OTHER if the match is on an other */ 230 /* type of key (ie dsa if type was rsa) */ 231 static char **ssh_parse_knownhost(char *filename, char *hostname, char *type){ 232 FILE *file=fopen(filename,"r"); 233 char buffer[4096]; 234 char *ptr; 235 char **tokens; 236 char **ret=NULL; 237 if(!file) 238 return FILE_NOT_FOUND; 239 while(fgets(buffer,sizeof(buffer),file)){ 240 ptr=strchr(buffer,'\n'); 241 if(ptr) *ptr=0; 242 if((ptr=strchr(buffer,'\r'))) *ptr=0; 243 if(!buffer[0]) 244 continue; /* skip empty lines */ 245 tokens=space_tokenize(buffer); 246 if(!tokens[0] || !tokens[1] || !tokens[2]){ 247 /* it should have exactly 3 tokens */ 248 free(tokens[0]); 249 free(tokens); 250 continue; 251 } 252 if(tokens[3]){ 253 /* 3 tokens only, not four */ 254 free(tokens[0]); 255 free(tokens); 256 continue; 257 } 258 ptr=tokens[0]; 259 while(*ptr==' ') 260 ptr++; /* skip the initial spaces */ 261 /* we allow spaces or ',' to follow the hostname. It's generaly an IP */ 262 /* we don't care about ip, if the host key match there is no problem with ip */ 263 if(strncasecmp(ptr,hostname,strlen(hostname))==0){ 264 if(ptr[strlen(hostname)]==' ' || ptr[strlen(hostname)]=='\0' 265 || ptr[strlen(hostname)]==','){ 266 if(strcasecmp(tokens[1],type)==0){ 267 fclose(file); 268 return tokens; 269 } else { 270 ret=FOUND_OTHER; 271 } 272 } 273 } 274 /* not the good one */ 275 free(tokens[0]); 276 free(tokens); 277 } 278 fclose(file); 279 /* we did not find */ 280 return ret; 281 } 282 283 /* public function to test if the server is known or not */ 284 int ssh_is_server_known(SSH_SESSION *session){ 285 char *pubkey_64; 286 BUFFER *pubkey_buffer; 287 STRING *pubkey=session->current_crypto->server_pubkey; 288 char **tokens; 289 290 options_default_known_hosts_file(session->options); 291 if(!session->options->host){ 292 ssh_set_error(session,SSH_FATAL,"Can't verify host in known hosts if the hostname isn't known"); 293 return SSH_SERVER_ERROR; 294 } 295 tokens=ssh_parse_knownhost(session->options->known_hosts_file, 296 session->options->host,session->current_crypto->server_pubkey_type); 297 if(tokens==NULL) 298 return SSH_SERVER_NOT_KNOWN; 299 if(tokens==FOUND_OTHER) 300 return SSH_SERVER_FOUND_OTHER; 301 if(tokens==FILE_NOT_FOUND){ 302 #if 0 303 ssh_set_error(session,SSH_FATAL,"verifying that server is a known host : file %s not found",session->options->known_hosts_file); 304 return SSH_SERVER_ERROR; 305 #else 306 return SSH_SERVER_KNOWN_OK; 307 #endif 308 } 309 /* ok we found some public key in known hosts file. now un-base64it */ 310 /* Some time, we may verify the IP address did not change. I honestly think */ 311 /* it's not an important matter as IP address are known not to be secure */ 312 /* and the crypto stuff is enough to prove the server's identity */ 313 pubkey_64=tokens[2]; 314 pubkey_buffer=base64_to_bin(pubkey_64); 315 /* at this point, we may free the tokens */ 316 free(tokens[0]); 317 free(tokens); 318 if(!pubkey_buffer){ 319 ssh_set_error(session,SSH_FATAL,"verifying that server is a known host : base 64 error"); 320 return SSH_SERVER_ERROR; 321 } 322 if(buffer_get_len(pubkey_buffer)!=string_len(pubkey)){ 323 buffer_free(pubkey_buffer); 324 return SSH_SERVER_KNOWN_CHANGED; 325 } 326 /* now test that they are identical */ 327 if(memcmp(buffer_get(pubkey_buffer),pubkey->string,buffer_get_len(pubkey_buffer))!=0){ 328 buffer_free(pubkey_buffer); 329 return SSH_SERVER_KNOWN_CHANGED; 330 } 331 buffer_free(pubkey_buffer); 332 return SSH_SERVER_KNOWN_OK; 333 } 334 335 int ssh_write_knownhost(SSH_SESSION *session){ 336 char *pubkey_64; 337 STRING *pubkey=session->current_crypto->server_pubkey; 338 char buffer[4096]; 339 FILE *file; 340 options_default_known_hosts_file(session->options); 341 if(!session->options->host){ 342 ssh_set_error(session,SSH_FATAL,"Cannot write host in known hosts if the hostname is unknown"); 343 return -1; 344 } 345 /* a = append only */ 346 file=fopen(session->options->known_hosts_file,"a"); 347 if(!file){ 348 ssh_set_error(session,SSH_FATAL,"Opening known host file %s for appending : %s", 349 session->options->known_hosts_file,strerror(errno)); 350 return -1; 351 } 352 pubkey_64=bin_to_base64(pubkey->string,string_len(pubkey)); 353 snprintf(buffer,sizeof(buffer),"%s %s %s\n",session->options->host,session->current_crypto->server_pubkey_type,pubkey_64); 354 free(pubkey_64); 355 fwrite(buffer,strlen(buffer),1,file); 356 fclose(file); 357 return 0; 358 } |