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 pass=getpass(buffer); 44 snprintf(buf,size,"%s",buffer); 45 len=strlen(buf); 46 memset(pass,0,strlen(pass)); 47 return len; 48 } 49 50 /* in case the passphrase has been given in parameter */ 51 static int get_password_specified(char *buf,int size, int rwflag, char *password){ 52 snprintf(buf,size,"%s",password); 53 return strlen(buf); 54 } 55 56 /* TODO : implement it to read both DSA and RSA at once */ 57 PRIVATE_KEY *privatekey_from_file(SSH_SESSION *session,char *filename,int type,char *passphrase){ 58 FILE *file=fopen(filename,"r"); 59 PRIVATE_KEY *privkey; 60 DSA *dsa=NULL; 61 RSA *rsa=NULL; 62 if(!file){ 63 ssh_set_error(session,SSH_REQUEST_DENIED,"Error opening %s : %s",filename,strerror(errno)); 64 return NULL; 65 } 66 if(type==TYPE_DSS){ 67 if(!passphrase){ 68 if(session && session->options->passphrase_function) 69 dsa=PEM_read_DSAPrivateKey(file,NULL, session->options->passphrase_function,"DSA private key"); 70 else 71 dsa=PEM_read_DSAPrivateKey(file,NULL,(void *)default_get_password, "DSA private key"); 72 } 73 else 74 dsa=PEM_read_DSAPrivateKey(file,NULL,(void *)get_password_specified,passphrase); 75 fclose(file); 76 if(!dsa){ 77 ssh_set_error(session,SSH_FATAL,"parsing private key %s : %s",filename,ERR_error_string(ERR_get_error(),NULL)); 78 return NULL; 79 } 80 } 81 else if (type==TYPE_RSA){ 82 if(!passphrase){ 83 if(session && session->options->passphrase_function) 84 rsa=PEM_read_RSAPrivateKey(file,NULL, session->options->passphrase_function,"RSA private key"); 85 else 86 rsa=PEM_read_RSAPrivateKey(file,NULL,(void *)default_get_password, "RSA private key"); 87 } 88 else 89 rsa=PEM_read_RSAPrivateKey(file,NULL,(void *)get_password_specified,passphrase); 90 fclose(file); 91 if(!rsa){ 92 ssh_set_error(session,SSH_FATAL,"parsing private key %s : %s",filename,ERR_error_string(ERR_get_error(),NULL)); 93 return NULL; 94 } 95 } else { 96 ssh_set_error(session,SSH_FATAL,"Invalid private key type %d",type); 97 return NULL; 98 } 99 100 privkey=malloc(sizeof(PRIVATE_KEY)); 101 privkey->type=type; 102 privkey->dsa_priv=dsa; 103 privkey->rsa_priv=rsa; 104 return privkey; 105 } 106 107 void private_key_free(PRIVATE_KEY *prv){ 108 if(prv->dsa_priv) 109 DSA_free(prv->dsa_priv); 110 if(prv->rsa_priv) 111 RSA_free(prv->rsa_priv); 112 memset(prv,0,sizeof(PRIVATE_KEY)); 113 free(prv); 114 } 115 116 STRING *publickey_from_file(char *filename,int *_type){ 117 BUFFER *buffer; 118 int type; 119 STRING *str; 120 char buf[4096]; /* noone will have bigger keys that that */ 121 /* where have i head that again ? */ 122 int fd=open(filename,O_RDONLY); 123 int r; 124 char *ptr; 125 if(fd<0){ 126 ssh_set_error(NULL,SSH_INVALID_REQUEST,"nonexistent public key file"); 127 return NULL; 128 } 129 if(read(fd,buf,8)!=8){ 130 close(fd); 131 ssh_set_error(NULL,SSH_INVALID_REQUEST,"Invalid public key file"); 132 return NULL; 133 } 134 buf[7]=0; 135 if(!strcmp(buf,"ssh-dss")) 136 type=TYPE_DSS; 137 else if (!strcmp(buf,"ssh-rsa")) 138 type=TYPE_RSA; 139 else { 140 close(fd); 141 ssh_set_error(NULL,SSH_INVALID_REQUEST,"Invalid public key file"); 142 return NULL; 143 } 144 r=read(fd,buf,sizeof(buf)-1); 145 close(fd); 146 if(r<=0){ 147 ssh_set_error(NULL,SSH_INVALID_REQUEST,"Invalid public key file"); 148 return NULL; 149 } 150 buf[r]=0; 151 ptr=strchr(buf,' '); 152 if(ptr) 153 *ptr=0; /* eliminates the garbage at end of file */ 154 buffer=base64_to_bin(buf); 155 if(buffer){ 156 str=string_new(buffer_get_len(buffer)); 157 string_fill(str,buffer_get(buffer),buffer_get_len(buffer)); 158 buffer_free(buffer); 159 if(_type) 160 *_type=type; 161 return str; 162 } else { 163 ssh_set_error(NULL,SSH_INVALID_REQUEST,"Invalid public key file"); 164 return NULL; /* invalid file */ 165 } 166 } 167 168 169 /* why recursing ? i'll explain. on top, publickey_from_next_file will be executed until NULL returned */ 170 /* we can't return null if one of the possible keys is wrong. we must test them before getting over */ 171 STRING *publickey_from_next_file(SSH_SESSION *session,char **pub_keys_path,char **keys_path, 172 char **privkeyfile,int *type,int *count){ 173 static char *home=NULL; 174 char public[256]; 175 char private[256]; 176 char *priv; 177 char *pub; 178 STRING *pubkey; 179 if(!home) 180 home=ssh_get_user_home_dir(); 181 if(home==NULL) { 182 ssh_set_error(session,SSH_FATAL,"User home dir impossible to guess"); 183 return NULL; 184 } 185 ssh_set_error(session,SSH_NO_ERROR,"no public key matched"); 186 if((pub=pub_keys_path[*count])==NULL) 187 return NULL; 188 if((priv=keys_path[*count])==NULL) 189 return NULL; 190 ++*count; 191 /* are them readable ? */ 192 snprintf(public,256,pub,home); 193 ssh_say(2,"Trying to open %s\n",public); 194 if(!ssh_file_readaccess_ok(public)){ 195 ssh_say(2,"Failed\n"); 196 return publickey_from_next_file(session,pub_keys_path,keys_path,privkeyfile,type,count); 197 } 198 snprintf(private,256,priv,home); 199 ssh_say(2,"Trying to open %s\n",private); 200 if(!ssh_file_readaccess_ok(private)){ 201 ssh_say(2,"Failed\n"); 202 return publickey_from_next_file(session,pub_keys_path,keys_path,privkeyfile,type,count); 203 } 204 ssh_say(2,"Okay both files ok\n"); 205 /* ok, we are sure both the priv8 and public key files are readable : we return the public one as a string, 206 and the private filename in arguments */ 207 pubkey=publickey_from_file(public,type); 208 if(!pubkey){ 209 ssh_say(2,"Wasn't able to open public key file %s : %s\n",public,ssh_get_error(session)); 210 return publickey_from_next_file(session,pub_keys_path,keys_path,privkeyfile,type,count); 211 } 212 *privkeyfile=realloc(*privkeyfile,strlen(private)+1); 213 strcpy(*privkeyfile,private); 214 return pubkey; 215 } 216 217 #define FOUND_OTHER ( (void *)-1) 218 #define FILE_NOT_FOUND ((void *)-2) 219 /* will return a token array containing [host,]ip keytype key */ 220 /* NULL if no match was found, FOUND_OTHER if the match is on an other */ 221 /* type of key (ie dsa if type was rsa) */ 222 static char **ssh_parse_knownhost(char *filename, char *hostname, char *type){ 223 FILE *file=fopen(filename,"r"); 224 char buffer[4096]; 225 char *ptr; 226 char **tokens; 227 char **ret=NULL; 228 if(!file) 229 return FILE_NOT_FOUND; 230 while(fgets(buffer,sizeof(buffer),file)){ 231 ptr=strchr(buffer,'\n'); 232 if(ptr) *ptr=0; 233 if((ptr=strchr(buffer,'\r'))) *ptr=0; 234 if(!buffer[0]) 235 continue; /* skip empty lines */ 236 tokens=space_tokenize(buffer); 237 if(!tokens[0] || !tokens[1] || !tokens[2]){ 238 /* it should have exactly 3 tokens */ 239 free(tokens[0]); 240 free(tokens); 241 continue; 242 } 243 if(tokens[3]){ 244 /* 3 tokens only, not four */ 245 free(tokens[0]); 246 free(tokens); 247 continue; 248 } 249 ptr=tokens[0]; 250 while(*ptr==' ') 251 ptr++; /* skip the initial spaces */ 252 /* we allow spaces or ',' to follow the hostname. It's generaly an IP */ 253 /* we don't care about ip, if the host key match there is no problem with ip */ 254 if(strncasecmp(ptr,hostname,strlen(hostname))==0){ 255 if(ptr[strlen(hostname)]==' ' || ptr[strlen(hostname)]=='\0' 256 || ptr[strlen(hostname)]==','){ 257 if(strcasecmp(tokens[1],type)==0){ 258 fclose(file); 259 return tokens; 260 } else { 261 ret=FOUND_OTHER; 262 } 263 } 264 } 265 /* not the good one */ 266 free(tokens[0]); 267 free(tokens); 268 } 269 fclose(file); 270 /* we did not find */ 271 return ret; 272 } 273 274 /* public function to test if the server is known or not */ 275 int ssh_is_server_known(SSH_SESSION *session){ 276 char *pubkey_64; 277 BUFFER *pubkey_buffer; 278 STRING *pubkey=session->current_crypto->server_pubkey; 279 char **tokens; 280 options_default_known_hosts_file(session->options); 281 if(!session->options->host){ 282 ssh_set_error(session,SSH_FATAL,"Can't verify host in known hosts if the hostname isn't known"); 283 return SSH_SERVER_ERROR; 284 } 285 tokens=ssh_parse_knownhost(session->options->known_hosts_file, 286 session->options->host,session->current_crypto->server_pubkey_type); 287 if(tokens==NULL) 288 return SSH_SERVER_NOT_KNOWN; 289 if(tokens==FOUND_OTHER) 290 return SSH_SERVER_FOUND_OTHER; 291 if(tokens==FILE_NOT_FOUND){ 292 ssh_set_error(session,SSH_FATAL,"verifying that server is a known host : file %s not found",session->options->known_hosts_file); 293 return SSH_SERVER_ERROR; 294 } 295 /* ok we found some public key in known hosts file. now un-base64it */ 296 /* Some time, we may verify the IP address did not change. I honestly think */ 297 /* it's not an important matter as IP address are known not to be secure */ 298 /* and the crypto stuff is enough to prove the server's identity */ 299 pubkey_64=tokens[2]; 300 pubkey_buffer=base64_to_bin(pubkey_64); 301 /* at this point, we may free the tokens */ 302 free(tokens[0]); 303 free(tokens); 304 if(!pubkey_buffer){ 305 ssh_set_error(session,SSH_FATAL,"verifying that server is a known host : base 64 error"); 306 return SSH_SERVER_ERROR; 307 } 308 if(buffer_get_len(pubkey_buffer)!=string_len(pubkey)){ 309 buffer_free(pubkey_buffer); 310 return SSH_SERVER_KNOWN_CHANGED; 311 } 312 /* now test that they are identical */ 313 if(memcmp(buffer_get(pubkey_buffer),pubkey->string,buffer_get_len(pubkey_buffer))!=0){ 314 buffer_free(pubkey_buffer); 315 return SSH_SERVER_KNOWN_CHANGED; 316 } 317 buffer_free(pubkey_buffer); 318 return SSH_SERVER_KNOWN_OK; 319 } 320 321 int ssh_write_knownhost(SSH_SESSION *session){ 322 char *pubkey_64; 323 STRING *pubkey=session->current_crypto->server_pubkey; 324 char buffer[4096]; 325 FILE *file; 326 options_default_known_hosts_file(session->options); 327 if(!session->options->host){ 328 ssh_set_error(session,SSH_FATAL,"Cannot write host in known hosts if the hostname is unknown"); 329 return -1; 330 } 331 /* a = append only */ 332 file=fopen(session->options->known_hosts_file,"a"); 333 if(!file){ 334 ssh_set_error(session,SSH_FATAL,"Opening known host file %s for appending : %s", 335 session->options->known_hosts_file,strerror(errno)); 336 return -1; 337 } 338 pubkey_64=bin_to_base64(pubkey->string,string_len(pubkey)); 339 snprintf(buffer,sizeof(buffer),"%s %s %s\n",session->options->host,session->current_crypto->server_pubkey_type,pubkey_64); 340 free(pubkey_64); 341 fwrite(buffer,strlen(buffer),1,file); 342 fclose(file); 343 return 0; 344 } |