version 1.115.2.5, 2001/03/21 18:53:16 |
version 1.116, 2000/05/17 08:20:16 |
|
|
* Author: Tatu Ylonen <ylo@cs.hut.fi> |
* Author: Tatu Ylonen <ylo@cs.hut.fi> |
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
* All rights reserved |
* All rights reserved |
* This program is the ssh daemon. It listens for connections from clients, |
* Created: Fri Mar 17 17:09:28 1995 ylo |
* and performs authentication, executes use commands or shell, and forwards |
* This program is the ssh daemon. It listens for connections from clients, and |
|
* performs authentication, executes use commands or shell, and forwards |
* information to/from the application to the user client over an encrypted |
* information to/from the application to the user client over an encrypted |
* connection. This can also handle forwarding of X11, TCP/IP, and |
* connection. This can also handle forwarding of X11, TCP/IP, and authentication |
* authentication agent connections. |
* agent connections. |
* |
* |
* As far as I am concerned, the code I have written for this software |
* SSH2 implementation, |
* can be used freely for any purpose. Any derived versions of this |
* Copyright (c) 2000 Markus Friedl. All rights reserved. |
* software must be clearly marked as such, and if the derived work is |
|
* incompatible with the protocol description in the RFC file, it must be |
|
* called by a name other than "ssh" or "Secure Shell". |
|
* |
|
* SSH2 implementation: |
|
* |
|
* Copyright (c) 2000 Markus Friedl. All rights reserved. |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions |
|
* are met: |
|
* 1. Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions and the following disclaimer. |
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
* notice, this list of conditions and the following disclaimer in the |
|
* documentation and/or other materials provided with the distribution. |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
*/ |
*/ |
|
|
#include "includes.h" |
#include "includes.h" |
RCSID("$OpenBSD$"); |
RCSID("$OpenBSD$"); |
|
|
#include <openssl/dh.h> |
|
#include <openssl/bn.h> |
|
#include <openssl/hmac.h> |
|
|
|
#include "ssh.h" |
|
#include "ssh1.h" |
|
#include "ssh2.h" |
|
#include "xmalloc.h" |
#include "xmalloc.h" |
#include "rsa.h" |
#include "rsa.h" |
#include "sshpty.h" |
#include "ssh.h" |
|
#include "pty.h" |
#include "packet.h" |
#include "packet.h" |
|
#include "cipher.h" |
#include "mpaux.h" |
#include "mpaux.h" |
#include "log.h" |
|
#include "servconf.h" |
#include "servconf.h" |
#include "uidswap.h" |
#include "uidswap.h" |
#include "compat.h" |
#include "compat.h" |
#include "buffer.h" |
#include "buffer.h" |
#include "cipher.h" |
|
|
#include "ssh2.h" |
|
#include <openssl/dh.h> |
|
#include <openssl/bn.h> |
|
#include <openssl/hmac.h> |
#include "kex.h" |
#include "kex.h" |
|
#include <openssl/dsa.h> |
|
#include <openssl/rsa.h> |
#include "key.h" |
#include "key.h" |
#include "dh.h" |
#include "dsa.h" |
|
|
|
#include "auth.h" |
#include "myproposal.h" |
#include "myproposal.h" |
#include "authfile.h" |
#include "authfile.h" |
#include "pathnames.h" |
|
#include "atomicio.h" |
|
#include "canohost.h" |
|
#include "auth.h" |
|
#include "misc.h" |
|
|
|
#ifdef LIBWRAP |
#ifdef LIBWRAP |
#include <tcpd.h> |
#include <tcpd.h> |
|
|
#define O_NOCTTY 0 |
#define O_NOCTTY 0 |
#endif |
#endif |
|
|
extern char *__progname; |
|
|
|
/* Server configuration options. */ |
/* Server configuration options. */ |
ServerOptions options; |
ServerOptions options; |
|
|
/* Name of the server configuration file. */ |
/* Name of the server configuration file. */ |
char *config_file_name = _PATH_SERVER_CONFIG_FILE; |
char *config_file_name = SERVER_CONFIG_FILE; |
|
|
/* |
/* |
* Flag indicating whether IPv4 or IPv6. This can be set on the command line. |
* Flag indicating whether IPv4 or IPv6. This can be set on the command line. |
|
|
/* Flag indicating that the daemon is being started from inetd. */ |
/* Flag indicating that the daemon is being started from inetd. */ |
int inetd_flag = 0; |
int inetd_flag = 0; |
|
|
/* Flag indicating that sshd should not detach and become a daemon. */ |
|
int no_daemon_flag = 0; |
|
|
|
/* debug goes to stderr unless inetd_flag is set */ |
/* debug goes to stderr unless inetd_flag is set */ |
int log_stderr = 0; |
int log_stderr = 0; |
|
|
|
/* argv[0] without path. */ |
|
char *av0; |
|
|
/* Saved arguments to main(). */ |
/* Saved arguments to main(). */ |
char **saved_argv; |
char **saved_argv; |
|
|
|
|
* not very useful. Currently, memory locking is not implemented. |
* not very useful. Currently, memory locking is not implemented. |
*/ |
*/ |
struct { |
struct { |
Key *server_key; /* ephemeral server key */ |
RSA *private_key; /* Private part of empheral server key. */ |
Key *ssh1_host_key; /* ssh1 host key */ |
RSA *host_key; /* Private part of host key. */ |
Key **host_keys; /* all private host keys */ |
Key *dsa_host_key; /* Private DSA host key. */ |
int have_ssh1_key; |
|
int have_ssh2_key; |
|
u_char ssh1_cookie[SSH_SESSION_KEY_LENGTH]; |
|
} sensitive_data; |
} sensitive_data; |
|
|
/* |
/* |
* Flag indicating whether the RSA server key needs to be regenerated. |
* Flag indicating whether the current session key has been used. This flag |
* Is set in the SIGALRM handler and cleared when the key is regenerated. |
* is set whenever the key is used, and cleared when the key is regenerated. |
*/ |
*/ |
int key_do_regen = 0; |
int key_used = 0; |
|
|
/* This is set to true when SIGHUP is received. */ |
/* This is set to true when SIGHUP is received. */ |
int received_sighup = 0; |
int received_sighup = 0; |
|
|
|
/* Public side of the server key. This value is regenerated regularly with |
|
the private key. */ |
|
RSA *public_key; |
|
|
/* session identifier, used by RSA-auth */ |
/* session identifier, used by RSA-auth */ |
u_char session_id[16]; |
unsigned char session_id[16]; |
|
|
/* same for ssh2 */ |
/* same for ssh2 */ |
u_char *session_id2 = NULL; |
unsigned char *session_id2 = NULL; |
int session_id2_len = 0; |
int session_id2_len = 0; |
|
|
/* record remote hostname or ip */ |
|
u_int utmp_len = MAXHOSTNAMELEN; |
|
|
|
/* Prototypes for various functions defined later in this file. */ |
/* Prototypes for various functions defined later in this file. */ |
void do_ssh1_kex(void); |
void do_ssh1_kex(); |
void do_ssh2_kex(void); |
void do_ssh2_kex(); |
|
|
void ssh_dh1_server(Kex *, Buffer *_kexinit, Buffer *); |
|
void ssh_dhgex_server(Kex *, Buffer *_kexinit, Buffer *); |
|
|
|
/* |
/* |
* Close all listening sockets |
* Close all listening sockets |
*/ |
*/ |
|
|
* Restarts the server. |
* Restarts the server. |
*/ |
*/ |
void |
void |
sighup_restart(void) |
sighup_restart() |
{ |
{ |
log("Received SIGHUP; restarting."); |
log("Received SIGHUP; restarting."); |
close_listen_socks(); |
close_listen_socks(); |
execv(saved_argv[0], saved_argv); |
execv(saved_argv[0], saved_argv); |
log("RESTART FAILED: av[0]='%.100s', error: %.100s.", saved_argv[0], strerror(errno)); |
log("RESTART FAILED: av0='%s', error: %s.", av0, strerror(errno)); |
exit(1); |
exit(1); |
} |
} |
|
|
|
|
* Thus there should be no concurrency control/asynchronous execution |
* Thus there should be no concurrency control/asynchronous execution |
* problems. |
* problems. |
*/ |
*/ |
|
/* XXX do we really want this work to be done in a signal handler ? -m */ |
void |
void |
generate_ephemeral_server_key(void) |
key_regeneration_alarm(int sig) |
{ |
{ |
u_int32_t rand = 0; |
int save_errno = errno; |
int i; |
|
|
|
log("Generating %s%d bit RSA key.", sensitive_data.server_key ? "new " : "", |
/* Check if we should generate a new key. */ |
options.server_key_bits); |
if (key_used) { |
if (sensitive_data.server_key != NULL) |
/* This should really be done in the background. */ |
key_free(sensitive_data.server_key); |
log("Generating new %d bit RSA key.", options.server_key_bits); |
sensitive_data.server_key = key_generate(KEY_RSA1, options.server_key_bits); |
|
log("RSA key generation complete."); |
|
|
|
for (i = 0; i < SSH_SESSION_KEY_LENGTH; i++) { |
if (sensitive_data.private_key != NULL) |
if (i % 4 == 0) |
RSA_free(sensitive_data.private_key); |
rand = arc4random(); |
sensitive_data.private_key = RSA_new(); |
sensitive_data.ssh1_cookie[i] = rand & 0xff; |
|
rand >>= 8; |
|
} |
|
arc4random_stir(); |
|
} |
|
|
|
void |
if (public_key != NULL) |
key_regeneration_alarm(int sig) |
RSA_free(public_key); |
{ |
public_key = RSA_new(); |
int save_errno = errno; |
|
signal(SIGALRM, SIG_DFL); |
rsa_generate_key(sensitive_data.private_key, public_key, |
|
options.server_key_bits); |
|
arc4random_stir(); |
|
key_used = 0; |
|
log("RSA key generation complete."); |
|
} |
|
/* Reschedule the alarm. */ |
|
signal(SIGALRM, key_regeneration_alarm); |
|
alarm(options.key_regeneration_time); |
errno = save_errno; |
errno = save_errno; |
key_do_regen = 1; |
|
} |
} |
|
|
void |
void |
|
|
fatal_cleanup(); |
fatal_cleanup(); |
} |
} |
|
|
/* Read other side's version identification. */ |
/* Read other side\'s version identification. */ |
memset(buf, 0, sizeof(buf)); |
|
for (i = 0; i < sizeof(buf) - 1; i++) { |
for (i = 0; i < sizeof(buf) - 1; i++) { |
if (atomicio(read, sock_in, &buf[i], 1) != 1) { |
if (read(sock_in, &buf[i], 1) != 1) { |
log("Did not receive identification string from %s.", |
log("Did not receive ident string from %s.", get_remote_ipaddr()); |
get_remote_ipaddr()); |
|
fatal_cleanup(); |
fatal_cleanup(); |
} |
} |
if (buf[i] == '\r') { |
if (buf[i] == '\r') { |
buf[i] = '\n'; |
buf[i] = '\n'; |
buf[i + 1] = 0; |
buf[i + 1] = 0; |
/* Kludge for F-Secure Macintosh < 1.0.2 */ |
|
if (i == 12 && |
|
strncmp(buf, "SSH-1.5-W1.0", 12) == 0) |
|
break; |
|
continue; |
continue; |
} |
} |
if (buf[i] == '\n') { |
if (buf[i] == '\n') { |
|
|
|
|
compat_datafellows(remote_version); |
compat_datafellows(remote_version); |
|
|
if (datafellows & SSH_BUG_SCANNER) { |
|
log("scanned from %s with %s. Don't panic.", |
|
get_remote_ipaddr(), client_version_string); |
|
fatal_cleanup(); |
|
} |
|
|
|
mismatch = 0; |
mismatch = 0; |
switch(remote_major) { |
switch(remote_major) { |
case 1: |
case 1: |
|
|
break; |
break; |
} |
} |
if (remote_minor < 3) { |
if (remote_minor < 3) { |
packet_disconnect("Your ssh version is too old and " |
packet_disconnect("Your ssh version is too old and" |
"is no longer supported. Please install a newer version."); |
"is no longer supported. Please install a newer version."); |
} else if (remote_minor == 3) { |
} else if (remote_minor == 3) { |
/* note that this disables agent-forwarding */ |
/* note that this disables agent-forwarding */ |
|
|
} |
} |
|
|
|
|
/* Destroy the host and server keys. They will no longer be needed. */ |
|
void |
void |
destroy_sensitive_data(void) |
destroy_sensitive_data(void) |
{ |
{ |
int i; |
/* Destroy the private and public keys. They will no longer be needed. */ |
|
RSA_free(public_key); |
if (sensitive_data.server_key) { |
RSA_free(sensitive_data.private_key); |
key_free(sensitive_data.server_key); |
RSA_free(sensitive_data.host_key); |
sensitive_data.server_key = NULL; |
if (sensitive_data.dsa_host_key != NULL) |
} |
key_free(sensitive_data.dsa_host_key); |
for(i = 0; i < options.num_host_key_files; i++) { |
|
if (sensitive_data.host_keys[i]) { |
|
key_free(sensitive_data.host_keys[i]); |
|
sensitive_data.host_keys[i] = NULL; |
|
} |
|
} |
|
sensitive_data.ssh1_host_key = NULL; |
|
memset(sensitive_data.ssh1_cookie, 0, SSH_SESSION_KEY_LENGTH); |
|
} |
} |
Key * |
|
load_private_key_autodetect(const char *filename) |
|
{ |
|
struct stat st; |
|
int type; |
|
Key *public, *private; |
|
|
|
if (stat(filename, &st) < 0) { |
|
perror(filename); |
|
return NULL; |
|
} |
|
/* |
|
* try to load the public key. right now this only works for RSA1, |
|
* since SSH2 keys are fully encrypted |
|
*/ |
|
type = KEY_RSA1; |
|
public = key_new(type); |
|
if (!load_public_key(filename, public, NULL)) { |
|
/* ok, so we will assume this is 'some' key */ |
|
type = KEY_UNSPEC; |
|
} |
|
key_free(public); |
|
|
|
/* Ok, try key with empty passphrase */ |
|
private = key_new(type); |
|
if (load_private_key(filename, "", private, NULL)) { |
|
debug("load_private_key_autodetect: type %d %s", |
|
private->type, key_type(private)); |
|
return private; |
|
} |
|
key_free(private); |
|
return NULL; |
|
} |
|
|
|
char * |
|
list_hostkey_types(void) |
|
{ |
|
static char buf[1024]; |
|
int i; |
|
buf[0] = '\0'; |
|
for(i = 0; i < options.num_host_key_files; i++) { |
|
Key *key = sensitive_data.host_keys[i]; |
|
if (key == NULL) |
|
continue; |
|
switch(key->type) { |
|
case KEY_RSA: |
|
case KEY_DSA: |
|
strlcat(buf, key_ssh_name(key), sizeof buf); |
|
strlcat(buf, ",", sizeof buf); |
|
break; |
|
} |
|
} |
|
i = strlen(buf); |
|
if (i > 0 && buf[i-1] == ',') |
|
buf[i-1] = '\0'; |
|
debug("list_hostkey_types: %s", buf); |
|
return buf; |
|
} |
|
|
|
Key * |
|
get_hostkey_by_type(int type) |
|
{ |
|
int i; |
|
for(i = 0; i < options.num_host_key_files; i++) { |
|
Key *key = sensitive_data.host_keys[i]; |
|
if (key != NULL && key->type == type) |
|
return key; |
|
} |
|
return NULL; |
|
} |
|
|
|
/* |
/* |
* returns 1 if connection should be dropped, 0 otherwise. |
|
* dropping starts at connection #max_startups_begin with a probability |
|
* of (max_startups_rate/100). the probability increases linearly until |
|
* all connections are dropped for startups > max_startups |
|
*/ |
|
int |
|
drop_connection(int startups) |
|
{ |
|
double p, r; |
|
|
|
if (startups < options.max_startups_begin) |
|
return 0; |
|
if (startups >= options.max_startups) |
|
return 1; |
|
if (options.max_startups_rate == 100) |
|
return 1; |
|
|
|
p = 100 - options.max_startups_rate; |
|
p *= startups - options.max_startups_begin; |
|
p /= (double) (options.max_startups - options.max_startups_begin); |
|
p += options.max_startups_rate; |
|
p /= 100.0; |
|
r = arc4random() / (double) UINT_MAX; |
|
|
|
debug("drop_connection: p %g, r %g", p, r); |
|
return (r < p) ? 1 : 0; |
|
} |
|
|
|
int *startup_pipes = NULL; /* options.max_startup sized array of fd ints */ |
|
int startup_pipe; /* in child */ |
|
|
|
/* |
|
* Main program for the daemon. |
* Main program for the daemon. |
*/ |
*/ |
int |
int |
|
|
{ |
{ |
extern char *optarg; |
extern char *optarg; |
extern int optind; |
extern int optind; |
int opt, sock_in = 0, sock_out = 0, newsock, j, i, fdsetsz, on = 1; |
int opt, sock_in = 0, sock_out = 0, newsock, i, fdsetsz, on = 1; |
pid_t pid; |
pid_t pid; |
socklen_t fromlen; |
socklen_t fromlen; |
|
int silent = 0; |
fd_set *fdset; |
fd_set *fdset; |
struct sockaddr_storage from; |
struct sockaddr_storage from; |
const char *remote_ip; |
const char *remote_ip; |
|
|
struct addrinfo *ai; |
struct addrinfo *ai; |
char ntop[NI_MAXHOST], strport[NI_MAXSERV]; |
char ntop[NI_MAXHOST], strport[NI_MAXSERV]; |
int listen_sock, maxfd; |
int listen_sock, maxfd; |
int startup_p[2]; |
|
int startups = 0; |
|
int ret, key_used = 0; |
|
|
|
/* Save argv. */ |
/* Save argv[0]. */ |
saved_argv = av; |
saved_argv = av; |
|
if (strchr(av[0], '/')) |
|
av0 = strrchr(av[0], '/') + 1; |
|
else |
|
av0 = av[0]; |
|
|
/* Initialize configuration options to their default values. */ |
/* Initialize configuration options to their default values. */ |
initialize_server_options(&options); |
initialize_server_options(&options); |
|
|
/* Parse command-line arguments. */ |
/* Parse command-line arguments. */ |
while ((opt = getopt(ac, av, "f:p:b:k:h:g:V:u:dDiqQ46")) != -1) { |
while ((opt = getopt(ac, av, "f:p:b:k:h:g:V:diqQ46")) != EOF) { |
switch (opt) { |
switch (opt) { |
case '4': |
case '4': |
IPv4or6 = AF_INET; |
IPv4or6 = AF_INET; |
|
|
config_file_name = optarg; |
config_file_name = optarg; |
break; |
break; |
case 'd': |
case 'd': |
if (0 == debug_flag) { |
debug_flag = 1; |
debug_flag = 1; |
options.log_level = SYSLOG_LEVEL_DEBUG; |
options.log_level = SYSLOG_LEVEL_DEBUG1; |
|
} else if (options.log_level < SYSLOG_LEVEL_DEBUG3) { |
|
options.log_level++; |
|
} else { |
|
fprintf(stderr, "Too high debugging level.\n"); |
|
exit(1); |
|
} |
|
break; |
break; |
case 'D': |
|
no_daemon_flag = 1; |
|
break; |
|
case 'i': |
case 'i': |
inetd_flag = 1; |
inetd_flag = 1; |
break; |
break; |
case 'Q': |
case 'Q': |
/* ignored */ |
silent = 1; |
break; |
break; |
case 'q': |
case 'q': |
options.log_level = SYSLOG_LEVEL_QUIET; |
options.log_level = SYSLOG_LEVEL_QUIET; |
|
|
break; |
break; |
case 'p': |
case 'p': |
options.ports_from_cmdline = 1; |
options.ports_from_cmdline = 1; |
if (options.num_ports >= MAX_PORTS) { |
if (options.num_ports >= MAX_PORTS) |
fprintf(stderr, "too many ports.\n"); |
fatal("too many ports.\n"); |
exit(1); |
|
} |
|
options.ports[options.num_ports++] = atoi(optarg); |
options.ports[options.num_ports++] = atoi(optarg); |
break; |
break; |
case 'g': |
case 'g': |
|
|
options.key_regeneration_time = atoi(optarg); |
options.key_regeneration_time = atoi(optarg); |
break; |
break; |
case 'h': |
case 'h': |
if (options.num_host_key_files >= MAX_HOSTKEYS) { |
options.host_key_file = optarg; |
fprintf(stderr, "too many host keys.\n"); |
|
exit(1); |
|
} |
|
options.host_key_files[options.num_host_key_files++] = optarg; |
|
break; |
break; |
case 'V': |
case 'V': |
client_version_string = optarg; |
client_version_string = optarg; |
/* only makes sense with inetd_flag, i.e. no listen() */ |
/* only makes sense with inetd_flag, i.e. no listen() */ |
inetd_flag = 1; |
inetd_flag = 1; |
break; |
break; |
case 'u': |
|
utmp_len = atoi(optarg); |
|
break; |
|
case '?': |
case '?': |
default: |
default: |
fprintf(stderr, "sshd version %s\n", SSH_VERSION); |
fprintf(stderr, "sshd version %s\n", SSH_VERSION); |
fprintf(stderr, "Usage: %s [options]\n", __progname); |
fprintf(stderr, "Usage: %s [options]\n", av0); |
fprintf(stderr, "Options:\n"); |
fprintf(stderr, "Options:\n"); |
fprintf(stderr, " -f file Configuration file (default %s)\n", _PATH_SERVER_CONFIG_FILE); |
fprintf(stderr, " -f file Configuration file (default %s)\n", SERVER_CONFIG_FILE); |
fprintf(stderr, " -d Debugging mode (multiple -d means more debugging)\n"); |
fprintf(stderr, " -d Debugging mode\n"); |
fprintf(stderr, " -i Started from inetd\n"); |
fprintf(stderr, " -i Started from inetd\n"); |
fprintf(stderr, " -D Do not fork into daemon mode\n"); |
|
fprintf(stderr, " -q Quiet (no logging)\n"); |
fprintf(stderr, " -q Quiet (no logging)\n"); |
fprintf(stderr, " -p port Listen on the specified port (default: 22)\n"); |
fprintf(stderr, " -p port Listen on the specified port (default: 22)\n"); |
fprintf(stderr, " -k seconds Regenerate server key every this many seconds (default: 3600)\n"); |
fprintf(stderr, " -k seconds Regenerate server key every this many seconds (default: 3600)\n"); |
fprintf(stderr, " -g seconds Grace period for authentication (default: 600)\n"); |
fprintf(stderr, " -g seconds Grace period for authentication (default: 300)\n"); |
fprintf(stderr, " -b bits Size of server RSA key (default: 768 bits)\n"); |
fprintf(stderr, " -b bits Size of server RSA key (default: 768 bits)\n"); |
fprintf(stderr, " -h file File from which to read host key (default: %s)\n", |
fprintf(stderr, " -h file File from which to read host key (default: %s)\n", |
_PATH_HOST_KEY_FILE); |
HOST_KEY_FILE); |
fprintf(stderr, " -u len Maximum hostname length for utmp recording\n"); |
|
fprintf(stderr, " -4 Use IPv4 only\n"); |
fprintf(stderr, " -4 Use IPv4 only\n"); |
fprintf(stderr, " -6 Use IPv6 only\n"); |
fprintf(stderr, " -6 Use IPv6 only\n"); |
exit(1); |
exit(1); |
|
|
* Force logging to stderr until we have loaded the private host |
* Force logging to stderr until we have loaded the private host |
* key (unless started from inetd) |
* key (unless started from inetd) |
*/ |
*/ |
log_init(__progname, |
log_init(av0, |
options.log_level == -1 ? SYSLOG_LEVEL_INFO : options.log_level, |
options.log_level == -1 ? SYSLOG_LEVEL_INFO : options.log_level, |
options.log_facility == -1 ? SYSLOG_FACILITY_AUTH : options.log_facility, |
options.log_facility == -1 ? SYSLOG_FACILITY_AUTH : options.log_facility, |
!inetd_flag); |
!silent && !inetd_flag); |
|
|
/* Read server configuration options from the configuration file. */ |
/* Read server configuration options from the configuration file. */ |
read_server_config(&options, config_file_name); |
read_server_config(&options, config_file_name); |
|
|
|
|
debug("sshd version %.100s", SSH_VERSION); |
debug("sshd version %.100s", SSH_VERSION); |
|
|
/* load private host keys */ |
sensitive_data.dsa_host_key = NULL; |
sensitive_data.host_keys = xmalloc(options.num_host_key_files*sizeof(Key*)); |
sensitive_data.host_key = NULL; |
for(i = 0; i < options.num_host_key_files; i++) |
|
sensitive_data.host_keys[i] = NULL; |
|
sensitive_data.server_key = NULL; |
|
sensitive_data.ssh1_host_key = NULL; |
|
sensitive_data.have_ssh1_key = 0; |
|
sensitive_data.have_ssh2_key = 0; |
|
|
|
for(i = 0; i < options.num_host_key_files; i++) { |
/* check if RSA support exists */ |
Key *key = load_private_key_autodetect(options.host_key_files[i]); |
if ((options.protocol & SSH_PROTO_1) && |
if (key == NULL) { |
rsa_alive() == 0) { |
|
log("no RSA support in libssl and libcrypto. See ssl(8)"); |
|
log("Disabling protocol version 1"); |
|
options.protocol &= ~SSH_PROTO_1; |
|
} |
|
/* Load the RSA/DSA host key. It must have empty passphrase. */ |
|
if (options.protocol & SSH_PROTO_1) { |
|
Key k; |
|
sensitive_data.host_key = RSA_new(); |
|
k.type = KEY_RSA; |
|
k.rsa = sensitive_data.host_key; |
|
errno = 0; |
|
if (!load_private_key(options.host_key_file, "", &k, NULL)) { |
error("Could not load host key: %.200s: %.100s", |
error("Could not load host key: %.200s: %.100s", |
options.host_key_files[i], strerror(errno)); |
options.host_key_file, strerror(errno)); |
continue; |
log("Disabling protocol version 1"); |
|
options.protocol &= ~SSH_PROTO_1; |
} |
} |
switch(key->type){ |
k.rsa = NULL; |
case KEY_RSA1: |
} |
sensitive_data.ssh1_host_key = key; |
if (options.protocol & SSH_PROTO_2) { |
sensitive_data.have_ssh1_key = 1; |
sensitive_data.dsa_host_key = key_new(KEY_DSA); |
break; |
if (!load_private_key(options.host_dsa_key_file, "", sensitive_data.dsa_host_key, NULL)) { |
case KEY_RSA: |
|
case KEY_DSA: |
error("Could not load DSA host key: %.200s", options.host_dsa_key_file); |
sensitive_data.have_ssh2_key = 1; |
log("Disabling protocol version 2"); |
break; |
options.protocol &= ~SSH_PROTO_2; |
} |
} |
sensitive_data.host_keys[i] = key; |
|
} |
} |
if ((options.protocol & SSH_PROTO_1) && !sensitive_data.have_ssh1_key) { |
if (! options.protocol & (SSH_PROTO_1|SSH_PROTO_2)) { |
log("Disabling protocol version 1. Could not load host key"); |
if (silent == 0) |
options.protocol &= ~SSH_PROTO_1; |
fprintf(stderr, "sshd: no hostkeys available -- exiting.\n"); |
} |
log("sshd: no hostkeys available -- exiting.\n"); |
if ((options.protocol & SSH_PROTO_2) && !sensitive_data.have_ssh2_key) { |
|
log("Disabling protocol version 2. Could not load host key"); |
|
options.protocol &= ~SSH_PROTO_2; |
|
} |
|
if (!(options.protocol & (SSH_PROTO_1|SSH_PROTO_2))) { |
|
log("sshd: no hostkeys available -- exiting."); |
|
exit(1); |
exit(1); |
} |
} |
|
|
|
|
* hate software patents. I dont know if this can go? Niels |
* hate software patents. I dont know if this can go? Niels |
*/ |
*/ |
if (options.server_key_bits > |
if (options.server_key_bits > |
BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) - SSH_KEY_BITS_RESERVED && |
BN_num_bits(sensitive_data.host_key->n) - SSH_KEY_BITS_RESERVED && |
options.server_key_bits < |
options.server_key_bits < |
BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + SSH_KEY_BITS_RESERVED) { |
BN_num_bits(sensitive_data.host_key->n) + SSH_KEY_BITS_RESERVED) { |
options.server_key_bits = |
options.server_key_bits = |
BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + SSH_KEY_BITS_RESERVED; |
BN_num_bits(sensitive_data.host_key->n) + SSH_KEY_BITS_RESERVED; |
debug("Forcing server key to %d bits to make it differ from host key.", |
debug("Forcing server key to %d bits to make it differ from host key.", |
options.server_key_bits); |
options.server_key_bits); |
} |
} |
|
|
/* Initialize the log (it is reinitialized below in case we forked). */ |
/* Initialize the log (it is reinitialized below in case we forked). */ |
if (debug_flag && !inetd_flag) |
if (debug_flag && !inetd_flag) |
log_stderr = 1; |
log_stderr = 1; |
log_init(__progname, options.log_level, options.log_facility, log_stderr); |
log_init(av0, options.log_level, options.log_facility, log_stderr); |
|
|
/* |
/* |
* If not in debugging mode, and not started from inetd, disconnect |
* If not in debugging mode, and not started from inetd, disconnect |
* from the controlling terminal, and fork. The original process |
* from the controlling terminal, and fork. The original process |
* exits. |
* exits. |
*/ |
*/ |
if (!(debug_flag || inetd_flag || no_daemon_flag)) { |
if (!debug_flag && !inetd_flag) { |
#ifdef TIOCNOTTY |
#ifdef TIOCNOTTY |
int fd; |
int fd; |
#endif /* TIOCNOTTY */ |
#endif /* TIOCNOTTY */ |
|
|
|
|
/* Disconnect from the controlling tty. */ |
/* Disconnect from the controlling tty. */ |
#ifdef TIOCNOTTY |
#ifdef TIOCNOTTY |
fd = open(_PATH_TTY, O_RDWR | O_NOCTTY); |
fd = open("/dev/tty", O_RDWR | O_NOCTTY); |
if (fd >= 0) { |
if (fd >= 0) { |
(void) ioctl(fd, TIOCNOTTY, NULL); |
(void) ioctl(fd, TIOCNOTTY, NULL); |
close(fd); |
close(fd); |
|
|
#endif /* TIOCNOTTY */ |
#endif /* TIOCNOTTY */ |
} |
} |
/* Reinitialize the log (because of the fork above). */ |
/* Reinitialize the log (because of the fork above). */ |
log_init(__progname, options.log_level, options.log_facility, log_stderr); |
log_init(av0, options.log_level, options.log_facility, log_stderr); |
|
|
|
/* Do not display messages to stdout in RSA code. */ |
|
rsa_set_verbose(0); |
|
|
/* Initialize the random number generator. */ |
/* Initialize the random number generator. */ |
arc4random_stir(); |
arc4random_stir(); |
|
|
|
|
s2 = dup(s1); |
s2 = dup(s1); |
sock_in = dup(0); |
sock_in = dup(0); |
sock_out = dup(1); |
sock_out = dup(1); |
startup_pipe = -1; |
|
/* |
/* |
* We intentionally do not close the descriptors 0, 1, and 2 |
* We intentionally do not close the descriptors 0, 1, and 2 |
* as our code for setting the descriptors won\'t work if |
* as our code for setting the descriptors won\'t work if |
* ttyfd happens to be one of those. |
* ttyfd happens to be one of those. |
*/ |
*/ |
debug("inetd sockets after dupping: %d, %d", sock_in, sock_out); |
debug("inetd sockets after dupping: %d, %d", sock_in, sock_out); |
if (options.protocol & SSH_PROTO_1) |
|
generate_ephemeral_server_key(); |
if (options.protocol & SSH_PROTO_1) { |
|
public_key = RSA_new(); |
|
sensitive_data.private_key = RSA_new(); |
|
log("Generating %d bit RSA key.", options.server_key_bits); |
|
rsa_generate_key(sensitive_data.private_key, public_key, |
|
options.server_key_bits); |
|
arc4random_stir(); |
|
log("RSA key generation complete."); |
|
} |
} else { |
} else { |
for (ai = options.listen_addrs; ai; ai = ai->ai_next) { |
for (ai = options.listen_addrs; ai; ai = ai->ai_next) { |
if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) |
if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) |
|
|
|
|
if (!debug_flag) { |
if (!debug_flag) { |
/* |
/* |
* Record our pid in /var/run/sshd.pid to make it |
* Record our pid in /etc/sshd_pid to make it easier |
* easier to kill the correct sshd. We don't want to |
* to kill the correct sshd. We don\'t want to do |
* do this before the bind above because the bind will |
* this before the bind above because the bind will |
* fail if there already is a daemon, and this will |
* fail if there already is a daemon, and this will |
* overwrite any old pid in the file. |
* overwrite any old pid in the file. |
*/ |
*/ |
f = fopen(options.pid_file, "w"); |
f = fopen(options.pid_file, "w"); |
if (f) { |
if (f) { |
fprintf(f, "%u\n", (u_int) getpid()); |
fprintf(f, "%u\n", (unsigned int) getpid()); |
fclose(f); |
fclose(f); |
} |
} |
} |
} |
if (options.protocol & SSH_PROTO_1) |
if (options.protocol & SSH_PROTO_1) { |
generate_ephemeral_server_key(); |
public_key = RSA_new(); |
|
sensitive_data.private_key = RSA_new(); |
|
|
|
log("Generating %d bit RSA key.", options.server_key_bits); |
|
rsa_generate_key(sensitive_data.private_key, public_key, |
|
options.server_key_bits); |
|
arc4random_stir(); |
|
log("RSA key generation complete."); |
|
|
|
/* Schedule server key regeneration alarm. */ |
|
signal(SIGALRM, key_regeneration_alarm); |
|
alarm(options.key_regeneration_time); |
|
} |
|
|
/* Arrange to restart on SIGHUP. The handler needs listen_sock. */ |
/* Arrange to restart on SIGHUP. The handler needs listen_sock. */ |
signal(SIGHUP, sighup_handler); |
signal(SIGHUP, sighup_handler); |
|
|
signal(SIGTERM, sigterm_handler); |
signal(SIGTERM, sigterm_handler); |
signal(SIGQUIT, sigterm_handler); |
signal(SIGQUIT, sigterm_handler); |
|
|
|
|
signal(SIGCHLD, main_sigchld_handler); |
signal(SIGCHLD, main_sigchld_handler); |
|
|
/* setup fd set for listen */ |
/* setup fd set for listen */ |
fdset = NULL; |
|
maxfd = 0; |
maxfd = 0; |
for (i = 0; i < num_listen_socks; i++) |
for (i = 0; i < num_listen_socks; i++) |
if (listen_socks[i] > maxfd) |
if (listen_socks[i] > maxfd) |
maxfd = listen_socks[i]; |
maxfd = listen_socks[i]; |
/* pipes connected to unauthenticated childs */ |
fdsetsz = howmany(maxfd, NFDBITS) * sizeof(fd_mask); |
startup_pipes = xmalloc(options.max_startups * sizeof(int)); |
fdset = (fd_set *)xmalloc(fdsetsz); |
for (i = 0; i < options.max_startups; i++) |
|
startup_pipes[i] = -1; |
|
|
|
/* |
/* |
* Stay listening for connections until the system crashes or |
* Stay listening for connections until the system crashes or |
|
|
for (;;) { |
for (;;) { |
if (received_sighup) |
if (received_sighup) |
sighup_restart(); |
sighup_restart(); |
if (fdset != NULL) |
/* Wait in select until there is a connection. */ |
xfree(fdset); |
|
fdsetsz = howmany(maxfd+1, NFDBITS) * sizeof(fd_mask); |
|
fdset = (fd_set *)xmalloc(fdsetsz); |
|
memset(fdset, 0, fdsetsz); |
memset(fdset, 0, fdsetsz); |
|
|
for (i = 0; i < num_listen_socks; i++) |
for (i = 0; i < num_listen_socks; i++) |
FD_SET(listen_socks[i], fdset); |
FD_SET(listen_socks[i], fdset); |
for (i = 0; i < options.max_startups; i++) |
if (select(maxfd + 1, fdset, NULL, NULL, NULL) < 0) { |
if (startup_pipes[i] != -1) |
if (errno != EINTR) |
FD_SET(startup_pipes[i], fdset); |
error("select: %.100s", strerror(errno)); |
|
|
/* Wait in select until there is a connection. */ |
|
ret = select(maxfd+1, fdset, NULL, NULL, NULL); |
|
if (ret < 0 && errno != EINTR) |
|
error("select: %.100s", strerror(errno)); |
|
if (key_used && key_do_regen) { |
|
generate_ephemeral_server_key(); |
|
key_used = 0; |
|
key_do_regen = 0; |
|
} |
|
if (ret < 0) |
|
continue; |
continue; |
|
} |
for (i = 0; i < options.max_startups; i++) |
|
if (startup_pipes[i] != -1 && |
|
FD_ISSET(startup_pipes[i], fdset)) { |
|
/* |
|
* the read end of the pipe is ready |
|
* if the child has closed the pipe |
|
* after successful authentication |
|
* or if the child has died |
|
*/ |
|
close(startup_pipes[i]); |
|
startup_pipes[i] = -1; |
|
startups--; |
|
} |
|
for (i = 0; i < num_listen_socks; i++) { |
for (i = 0; i < num_listen_socks; i++) { |
if (!FD_ISSET(listen_socks[i], fdset)) |
if (!FD_ISSET(listen_socks[i], fdset)) |
continue; |
continue; |
fromlen = sizeof(from); |
fromlen = sizeof(from); |
newsock = accept(listen_socks[i], (struct sockaddr *)&from, |
newsock = accept(listen_socks[i], (struct sockaddr *)&from, |
&fromlen); |
&fromlen); |
if (newsock < 0) { |
if (newsock < 0) { |
if (errno != EINTR && errno != EWOULDBLOCK) |
if (errno != EINTR && errno != EWOULDBLOCK) |
error("accept: %.100s", strerror(errno)); |
error("accept: %.100s", strerror(errno)); |
continue; |
continue; |
} |
} |
if (fcntl(newsock, F_SETFL, 0) < 0) { |
if (fcntl(newsock, F_SETFL, 0) < 0) { |
error("newsock del O_NONBLOCK: %s", strerror(errno)); |
error("newsock del O_NONBLOCK: %s", strerror(errno)); |
continue; |
continue; |
} |
} |
if (drop_connection(startups) == 1) { |
/* |
debug("drop connection #%d", startups); |
* Got connection. Fork a child to handle it, unless |
close(newsock); |
* we are in debugging mode. |
continue; |
*/ |
} |
if (debug_flag) { |
if (pipe(startup_p) == -1) { |
|
close(newsock); |
|
continue; |
|
} |
|
|
|
for (j = 0; j < options.max_startups; j++) |
|
if (startup_pipes[j] == -1) { |
|
startup_pipes[j] = startup_p[0]; |
|
if (maxfd < startup_p[0]) |
|
maxfd = startup_p[0]; |
|
startups++; |
|
break; |
|
} |
|
|
|
/* |
/* |
* Got connection. Fork a child to handle it, unless |
* In debugging mode. Close the listening |
* we are in debugging mode. |
* socket, and start processing the |
|
* connection without forking. |
*/ |
*/ |
if (debug_flag) { |
debug("Server will not fork when running in debugging mode."); |
|
close_listen_socks(); |
|
sock_in = newsock; |
|
sock_out = newsock; |
|
pid = getpid(); |
|
break; |
|
} else { |
|
/* |
|
* Normal production daemon. Fork, and have |
|
* the child process the connection. The |
|
* parent continues listening. |
|
*/ |
|
if ((pid = fork()) == 0) { |
/* |
/* |
* In debugging mode. Close the listening |
* Child. Close the listening socket, and start using the |
* socket, and start processing the |
* accepted socket. Reinitialize logging (since our pid has |
* connection without forking. |
* changed). We break out of the loop to handle the connection. |
*/ |
*/ |
debug("Server will not fork when running in debugging mode."); |
|
close_listen_socks(); |
close_listen_socks(); |
sock_in = newsock; |
sock_in = newsock; |
sock_out = newsock; |
sock_out = newsock; |
startup_pipe = -1; |
log_init(av0, options.log_level, options.log_facility, log_stderr); |
pid = getpid(); |
|
break; |
break; |
} else { |
|
/* |
|
* Normal production daemon. Fork, and have |
|
* the child process the connection. The |
|
* parent continues listening. |
|
*/ |
|
if ((pid = fork()) == 0) { |
|
/* |
|
* Child. Close the listening and max_startup |
|
* sockets. Start using the accepted socket. |
|
* Reinitialize logging (since our pid has |
|
* changed). We break out of the loop to handle |
|
* the connection. |
|
*/ |
|
startup_pipe = startup_p[1]; |
|
for (j = 0; j < options.max_startups; j++) |
|
if (startup_pipes[j] != -1) |
|
close(startup_pipes[j]); |
|
close_listen_socks(); |
|
sock_in = newsock; |
|
sock_out = newsock; |
|
log_init(__progname, options.log_level, options.log_facility, log_stderr); |
|
break; |
|
} |
|
} |
} |
|
} |
|
|
/* Parent. Stay in the loop. */ |
/* Parent. Stay in the loop. */ |
if (pid < 0) |
if (pid < 0) |
error("fork: %.100s", strerror(errno)); |
error("fork: %.100s", strerror(errno)); |
else |
else |
debug("Forked child %d.", pid); |
debug("Forked child %d.", pid); |
|
|
close(startup_p[1]); |
/* Mark that the key has been used (it was "given" to the child). */ |
|
key_used = 1; |
|
|
/* Mark that the key has been used (it was "given" to the child). */ |
arc4random_stir(); |
if ((options.protocol & SSH_PROTO_1) && |
|
key_used == 0) { |
|
/* Schedule server key regeneration alarm. */ |
|
signal(SIGALRM, key_regeneration_alarm); |
|
alarm(options.key_regeneration_time); |
|
key_used = 1; |
|
} |
|
|
|
arc4random_stir(); |
/* Close the new socket (the child is now taking care of it). */ |
|
close(newsock); |
/* Close the new socket (the child is now taking care of it). */ |
} /* for (i = 0; i < num_listen_socks; i++) */ |
close(newsock); |
|
} |
|
/* child process check (or debug mode) */ |
/* child process check (or debug mode) */ |
if (num_listen_socks < 0) |
if (num_listen_socks < 0) |
break; |
break; |
|
|
linger.l_linger = 5; |
linger.l_linger = 5; |
setsockopt(sock_in, SOL_SOCKET, SO_LINGER, (void *) &linger, sizeof(linger)); |
setsockopt(sock_in, SOL_SOCKET, SO_LINGER, (void *) &linger, sizeof(linger)); |
|
|
/* Set keepalives if requested. */ |
|
if (options.keepalives && |
|
setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, |
|
sizeof(on)) < 0) |
|
error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); |
|
|
|
/* |
/* |
* Register our connection. This turns encryption off because we do |
* Register our connection. This turns encryption off because we do |
* not have a key. |
* not have a key. |
|
|
{ |
{ |
struct request_info req; |
struct request_info req; |
|
|
request_init(&req, RQ_DAEMON, __progname, RQ_FILE, sock_in, NULL); |
request_init(&req, RQ_DAEMON, av0, RQ_FILE, sock_in, NULL); |
fromhost(&req); |
fromhost(&req); |
|
|
if (!hosts_access(&req)) { |
if (!hosts_access(&req)) { |
|
|
|
|
sshd_exchange_identification(sock_in, sock_out); |
sshd_exchange_identification(sock_in, sock_out); |
/* |
/* |
* Check that the connection comes from a privileged port. |
* Check that the connection comes from a privileged port. Rhosts- |
* Rhosts-Authentication only makes sense from priviledged |
* and Rhosts-RSA-Authentication only make sense from priviledged |
* programs. Of course, if the intruder has root access on his local |
* programs. Of course, if the intruder has root access on his local |
* machine, he can connect from any port. So do not use these |
* machine, he can connect from any port. So do not use these |
* authentication methods from machines that you do not trust. |
* authentication methods from machines that you do not trust. |
*/ |
*/ |
if (remote_port >= IPPORT_RESERVED || |
if (remote_port >= IPPORT_RESERVED || |
remote_port < IPPORT_RESERVED / 2) { |
remote_port < IPPORT_RESERVED / 2) { |
debug("Rhosts Authentication disabled, " |
|
"originating port not trusted."); |
|
options.rhosts_authentication = 0; |
options.rhosts_authentication = 0; |
|
options.rhosts_rsa_authentication = 0; |
} |
} |
#ifdef KRB4 |
#ifdef KRB4 |
if (!packet_connection_is_ipv4() && |
if (!packet_connection_is_ipv4() && |
|
|
options.kerberos_authentication = 0; |
options.kerberos_authentication = 0; |
} |
} |
#endif /* KRB4 */ |
#endif /* KRB4 */ |
#ifdef AFS |
|
/* If machine has AFS, set process authentication group. */ |
|
if (k_hasafs()) { |
|
k_setpag(); |
|
k_unlog(); |
|
} |
|
#endif /* AFS */ |
|
|
|
packet_set_nonblocking(); |
packet_set_nonblocking(); |
|
|
|
|
* SSH1 key exchange |
* SSH1 key exchange |
*/ |
*/ |
void |
void |
do_ssh1_kex(void) |
do_ssh1_kex() |
{ |
{ |
int i, len; |
int i, len; |
int plen, slen; |
int plen, slen; |
int rsafail = 0; |
|
BIGNUM *session_key_int; |
BIGNUM *session_key_int; |
u_char session_key[SSH_SESSION_KEY_LENGTH]; |
unsigned char session_key[SSH_SESSION_KEY_LENGTH]; |
u_char cookie[8]; |
unsigned char cookie[8]; |
u_int cipher_type, auth_mask, protocol_flags; |
unsigned int cipher_type, auth_mask, protocol_flags; |
u_int32_t rand = 0; |
u_int32_t rand = 0; |
|
|
/* |
/* |
|
|
packet_put_char(cookie[i]); |
packet_put_char(cookie[i]); |
|
|
/* Store our public server RSA key. */ |
/* Store our public server RSA key. */ |
packet_put_int(BN_num_bits(sensitive_data.server_key->rsa->n)); |
packet_put_int(BN_num_bits(public_key->n)); |
packet_put_bignum(sensitive_data.server_key->rsa->e); |
packet_put_bignum(public_key->e); |
packet_put_bignum(sensitive_data.server_key->rsa->n); |
packet_put_bignum(public_key->n); |
|
|
/* Store our public host RSA key. */ |
/* Store our public host RSA key. */ |
packet_put_int(BN_num_bits(sensitive_data.ssh1_host_key->rsa->n)); |
packet_put_int(BN_num_bits(sensitive_data.host_key->n)); |
packet_put_bignum(sensitive_data.ssh1_host_key->rsa->e); |
packet_put_bignum(sensitive_data.host_key->e); |
packet_put_bignum(sensitive_data.ssh1_host_key->rsa->n); |
packet_put_bignum(sensitive_data.host_key->n); |
|
|
/* Put protocol flags. */ |
/* Put protocol flags. */ |
packet_put_int(SSH_PROTOFLAG_HOST_IN_FWD_OPEN); |
packet_put_int(SSH_PROTOFLAG_HOST_IN_FWD_OPEN); |
|
|
/* Declare which ciphers we support. */ |
/* Declare which ciphers we support. */ |
packet_put_int(cipher_mask_ssh1(0)); |
packet_put_int(cipher_mask1()); |
|
|
/* Declare supported authentication types. */ |
/* Declare supported authentication types. */ |
auth_mask = 0; |
auth_mask = 0; |
|
|
if (options.afs_token_passing) |
if (options.afs_token_passing) |
auth_mask |= 1 << SSH_PASS_AFS_TOKEN; |
auth_mask |= 1 << SSH_PASS_AFS_TOKEN; |
#endif |
#endif |
if (options.challenge_reponse_authentication == 1) |
#ifdef SKEY |
|
if (options.skey_authentication == 1) |
auth_mask |= 1 << SSH_AUTH_TIS; |
auth_mask |= 1 << SSH_AUTH_TIS; |
|
#endif |
if (options.password_authentication) |
if (options.password_authentication) |
auth_mask |= 1 << SSH_AUTH_PASSWORD; |
auth_mask |= 1 << SSH_AUTH_PASSWORD; |
packet_put_int(auth_mask); |
packet_put_int(auth_mask); |
|
|
packet_send(); |
packet_send(); |
packet_write_wait(); |
packet_write_wait(); |
|
|
debug("Sent %d bit server key and %d bit host key.", |
debug("Sent %d bit public key and %d bit host key.", |
BN_num_bits(sensitive_data.server_key->rsa->n), |
BN_num_bits(public_key->n), BN_num_bits(sensitive_data.host_key->n)); |
BN_num_bits(sensitive_data.ssh1_host_key->rsa->n)); |
|
|
|
/* Read clients reply (cipher type and session key). */ |
/* Read clients reply (cipher type and session key). */ |
packet_read_expect(&plen, SSH_CMSG_SESSION_KEY); |
packet_read_expect(&plen, SSH_CMSG_SESSION_KEY); |
|
|
/* Get cipher type and check whether we accept this. */ |
/* Get cipher type and check whether we accept this. */ |
cipher_type = packet_get_char(); |
cipher_type = packet_get_char(); |
|
|
if (!(cipher_mask_ssh1(0) & (1 << cipher_type))) |
if (!(cipher_mask() & (1 << cipher_type))) |
packet_disconnect("Warning: client selects unsupported cipher."); |
packet_disconnect("Warning: client selects unsupported cipher."); |
|
|
/* Get check bytes from the packet. These must match those we |
/* Get check bytes from the packet. These must match those we |
|
|
* Decrypt it using our private server key and private host key (key |
* Decrypt it using our private server key and private host key (key |
* with larger modulus first). |
* with larger modulus first). |
*/ |
*/ |
if (BN_cmp(sensitive_data.server_key->rsa->n, sensitive_data.ssh1_host_key->rsa->n) > 0) { |
if (BN_cmp(sensitive_data.private_key->n, sensitive_data.host_key->n) > 0) { |
/* Server key has bigger modulus. */ |
/* Private key has bigger modulus. */ |
if (BN_num_bits(sensitive_data.server_key->rsa->n) < |
if (BN_num_bits(sensitive_data.private_key->n) < |
BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + SSH_KEY_BITS_RESERVED) { |
BN_num_bits(sensitive_data.host_key->n) + SSH_KEY_BITS_RESERVED) { |
fatal("do_connection: %s: server_key %d < host_key %d + SSH_KEY_BITS_RESERVED %d", |
fatal("do_connection: %s: private_key %d < host_key %d + SSH_KEY_BITS_RESERVED %d", |
get_remote_ipaddr(), |
get_remote_ipaddr(), |
BN_num_bits(sensitive_data.server_key->rsa->n), |
BN_num_bits(sensitive_data.private_key->n), |
BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), |
BN_num_bits(sensitive_data.host_key->n), |
SSH_KEY_BITS_RESERVED); |
SSH_KEY_BITS_RESERVED); |
} |
} |
if (rsa_private_decrypt(session_key_int, session_key_int, |
rsa_private_decrypt(session_key_int, session_key_int, |
sensitive_data.server_key->rsa) <= 0) |
sensitive_data.private_key); |
rsafail++; |
rsa_private_decrypt(session_key_int, session_key_int, |
if (rsa_private_decrypt(session_key_int, session_key_int, |
sensitive_data.host_key); |
sensitive_data.ssh1_host_key->rsa) <= 0) |
|
rsafail++; |
|
} else { |
} else { |
/* Host key has bigger modulus (or they are equal). */ |
/* Host key has bigger modulus (or they are equal). */ |
if (BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) < |
if (BN_num_bits(sensitive_data.host_key->n) < |
BN_num_bits(sensitive_data.server_key->rsa->n) + SSH_KEY_BITS_RESERVED) { |
BN_num_bits(sensitive_data.private_key->n) + SSH_KEY_BITS_RESERVED) { |
fatal("do_connection: %s: host_key %d < server_key %d + SSH_KEY_BITS_RESERVED %d", |
fatal("do_connection: %s: host_key %d < private_key %d + SSH_KEY_BITS_RESERVED %d", |
get_remote_ipaddr(), |
get_remote_ipaddr(), |
BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), |
BN_num_bits(sensitive_data.host_key->n), |
BN_num_bits(sensitive_data.server_key->rsa->n), |
BN_num_bits(sensitive_data.private_key->n), |
SSH_KEY_BITS_RESERVED); |
SSH_KEY_BITS_RESERVED); |
} |
} |
if (rsa_private_decrypt(session_key_int, session_key_int, |
rsa_private_decrypt(session_key_int, session_key_int, |
sensitive_data.ssh1_host_key->rsa) < 0) |
sensitive_data.host_key); |
rsafail++; |
rsa_private_decrypt(session_key_int, session_key_int, |
if (rsa_private_decrypt(session_key_int, session_key_int, |
sensitive_data.private_key); |
sensitive_data.server_key->rsa) < 0) |
|
rsafail++; |
|
} |
} |
|
|
|
compute_session_id(session_id, cookie, |
|
sensitive_data.host_key->n, |
|
sensitive_data.private_key->n); |
|
|
|
/* Destroy the private and public keys. They will no longer be needed. */ |
|
destroy_sensitive_data(); |
|
|
/* |
/* |
* Extract session key from the decrypted integer. The key is in the |
* Extract session key from the decrypted integer. The key is in the |
* least significant 256 bits of the integer; the first byte of the |
* least significant 256 bits of the integer; the first byte of the |
* key is in the highest bits. |
* key is in the highest bits. |
*/ |
*/ |
if (!rsafail) { |
BN_mask_bits(session_key_int, sizeof(session_key) * 8); |
BN_mask_bits(session_key_int, sizeof(session_key) * 8); |
len = BN_num_bytes(session_key_int); |
len = BN_num_bytes(session_key_int); |
if (len < 0 || len > sizeof(session_key)) |
if (len < 0 || len > sizeof(session_key)) { |
fatal("do_connection: bad len from %s: session_key_int %d > sizeof(session_key) %d", |
error("do_connection: bad session key len from %s: " |
get_remote_ipaddr(), |
"session_key_int %d > sizeof(session_key) %lu", |
len, sizeof(session_key)); |
get_remote_ipaddr(), len, (u_long)sizeof(session_key)); |
memset(session_key, 0, sizeof(session_key)); |
rsafail++; |
BN_bn2bin(session_key_int, session_key + sizeof(session_key) - len); |
} else { |
|
memset(session_key, 0, sizeof(session_key)); |
|
BN_bn2bin(session_key_int, |
|
session_key + sizeof(session_key) - len); |
|
|
|
compute_session_id(session_id, cookie, |
|
sensitive_data.ssh1_host_key->rsa->n, |
|
sensitive_data.server_key->rsa->n); |
|
/* |
|
* Xor the first 16 bytes of the session key with the |
|
* session id. |
|
*/ |
|
for (i = 0; i < 16; i++) |
|
session_key[i] ^= session_id[i]; |
|
} |
|
} |
|
if (rsafail) { |
|
int bytes = BN_num_bytes(session_key_int); |
|
char *buf = xmalloc(bytes); |
|
MD5_CTX md; |
|
|
|
log("do_connection: generating a fake encryption key"); |
|
BN_bn2bin(session_key_int, buf); |
|
MD5_Init(&md); |
|
MD5_Update(&md, buf, bytes); |
|
MD5_Update(&md, sensitive_data.ssh1_cookie, SSH_SESSION_KEY_LENGTH); |
|
MD5_Final(session_key, &md); |
|
MD5_Init(&md); |
|
MD5_Update(&md, session_key, 16); |
|
MD5_Update(&md, buf, bytes); |
|
MD5_Update(&md, sensitive_data.ssh1_cookie, SSH_SESSION_KEY_LENGTH); |
|
MD5_Final(session_key + 16, &md); |
|
memset(buf, 0, bytes); |
|
xfree(buf); |
|
for (i = 0; i < 16; i++) |
|
session_id[i] = session_key[i] ^ session_key[i + 16]; |
|
} |
|
/* Destroy the private and public keys. They will no longer be needed. */ |
|
destroy_sensitive_data(); |
|
|
|
/* Destroy the decrypted integer. It is no longer needed. */ |
/* Destroy the decrypted integer. It is no longer needed. */ |
BN_clear_free(session_key_int); |
BN_clear_free(session_key_int); |
|
|
|
/* Xor the first 16 bytes of the session key with the session id. */ |
|
for (i = 0; i < 16; i++) |
|
session_key[i] ^= session_id[i]; |
|
|
/* Set the session key. From this on all communications will be encrypted. */ |
/* Set the session key. From this on all communications will be encrypted. */ |
packet_set_encryption_key(session_key, SSH_SESSION_KEY_LENGTH, cipher_type); |
packet_set_encryption_key(session_key, SSH_SESSION_KEY_LENGTH, cipher_type); |
|
|
|
|
* SSH2 key exchange: diffie-hellman-group1-sha1 |
* SSH2 key exchange: diffie-hellman-group1-sha1 |
*/ |
*/ |
void |
void |
do_ssh2_kex(void) |
do_ssh2_kex() |
{ |
{ |
Buffer *server_kexinit; |
Buffer *server_kexinit; |
Buffer *client_kexinit; |
Buffer *client_kexinit; |
int payload_len; |
int payload_len, dlen; |
|
int slen; |
|
unsigned int klen, kout; |
|
char *ptr; |
|
unsigned char *signature = NULL; |
|
unsigned char *server_host_key_blob = NULL; |
|
unsigned int sbloblen; |
|
DH *dh; |
|
BIGNUM *dh_client_pub = 0; |
|
BIGNUM *shared_secret = 0; |
int i; |
int i; |
|
unsigned char *kbuf; |
|
unsigned char *hash; |
Kex *kex; |
Kex *kex; |
char *cprop[PROPOSAL_MAX]; |
char *cprop[PROPOSAL_MAX]; |
|
char *sprop[PROPOSAL_MAX]; |
|
|
/* KEXINIT */ |
/* KEXINIT */ |
|
|
|
|
myproposal[PROPOSAL_ENC_ALGS_CTOS] = |
myproposal[PROPOSAL_ENC_ALGS_CTOS] = |
myproposal[PROPOSAL_ENC_ALGS_STOC] = options.ciphers; |
myproposal[PROPOSAL_ENC_ALGS_STOC] = options.ciphers; |
} |
} |
if (options.macs != NULL) { |
|
myproposal[PROPOSAL_MAC_ALGS_CTOS] = |
|
myproposal[PROPOSAL_MAC_ALGS_STOC] = options.macs; |
|
} |
|
myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = list_hostkey_types(); |
|
|
|
server_kexinit = kex_init(myproposal); |
debug("Sending KEX init."); |
client_kexinit = xmalloc(sizeof(*client_kexinit)); |
|
buffer_init(client_kexinit); |
|
|
|
/* algorithm negotiation */ |
|
kex_exchange_kexinit(server_kexinit, client_kexinit, cprop); |
|
kex = kex_choose_conf(cprop, myproposal, 1); |
|
for (i = 0; i < PROPOSAL_MAX; i++) |
for (i = 0; i < PROPOSAL_MAX; i++) |
xfree(cprop[i]); |
sprop[i] = xstrdup(myproposal[i]); |
|
server_kexinit = kex_init(sprop); |
switch (kex->kex_type) { |
packet_start(SSH2_MSG_KEXINIT); |
case DH_GRP1_SHA1: |
packet_put_raw(buffer_ptr(server_kexinit), buffer_len(server_kexinit)); |
ssh_dh1_server(kex, client_kexinit, server_kexinit); |
|
break; |
|
case DH_GEX_SHA1: |
|
ssh_dhgex_server(kex, client_kexinit, server_kexinit); |
|
break; |
|
default: |
|
fatal("Unsupported key exchange %d", kex->kex_type); |
|
} |
|
|
|
debug("send SSH2_MSG_NEWKEYS."); |
|
packet_start(SSH2_MSG_NEWKEYS); |
|
packet_send(); |
packet_send(); |
packet_write_wait(); |
packet_write_wait(); |
debug("done: send SSH2_MSG_NEWKEYS."); |
|
|
|
debug("Wait SSH2_MSG_NEWKEYS."); |
debug("done"); |
packet_read_expect(&payload_len, SSH2_MSG_NEWKEYS); |
|
debug("GOT SSH2_MSG_NEWKEYS."); |
|
|
|
#ifdef DEBUG_KEXDH |
packet_read_expect(&payload_len, SSH2_MSG_KEXINIT); |
/* send 1st encrypted/maced/compressed message */ |
|
packet_start(SSH2_MSG_IGNORE); |
|
packet_put_cstring("markus"); |
|
packet_send(); |
|
packet_write_wait(); |
|
#endif |
|
|
|
debug("done: KEX2."); |
/* |
} |
* save raw KEXINIT payload in buffer. this is used during |
|
* computation of the session_id and the session keys. |
|
*/ |
|
client_kexinit = xmalloc(sizeof(*client_kexinit)); |
|
buffer_init(client_kexinit); |
|
ptr = packet_get_raw(&payload_len); |
|
buffer_append(client_kexinit, ptr, payload_len); |
|
|
/* |
/* skip cookie */ |
* SSH2 key exchange |
for (i = 0; i < 16; i++) |
*/ |
(void) packet_get_char(); |
|
/* save kex init proposal strings */ |
|
for (i = 0; i < PROPOSAL_MAX; i++) { |
|
cprop[i] = packet_get_string(NULL); |
|
debug("got kexinit string: %s", cprop[i]); |
|
} |
|
|
/* diffie-hellman-group1-sha1 */ |
i = (int) packet_get_char(); |
|
debug("first kex follow == %d", i); |
|
i = packet_get_int(); |
|
debug("reserved == %d", i); |
|
|
void |
debug("done read kexinit"); |
ssh_dh1_server(Kex *kex, Buffer *client_kexinit, Buffer *server_kexinit) |
kex = kex_choose_conf(cprop, sprop, 1); |
{ |
|
#ifdef DEBUG_KEXDH |
|
int i; |
|
#endif |
|
int payload_len, dlen; |
|
int slen; |
|
u_char *signature = NULL; |
|
u_char *server_host_key_blob = NULL; |
|
u_int sbloblen; |
|
u_int klen, kout; |
|
u_char *kbuf; |
|
u_char *hash; |
|
BIGNUM *shared_secret = 0; |
|
DH *dh; |
|
BIGNUM *dh_client_pub = 0; |
|
Key *hostkey; |
|
|
|
hostkey = get_hostkey_by_type(kex->hostkey_type); |
|
if (hostkey == NULL) |
|
fatal("Unsupported hostkey type %d", kex->hostkey_type); |
|
|
|
/* KEXDH */ |
/* KEXDH */ |
/* generate DH key */ |
|
dh = dh_new_group1(); /* XXX depends on 'kex' */ |
|
dh_gen_key(dh, kex->we_need * 8); |
|
|
|
debug("Wait SSH2_MSG_KEXDH_INIT."); |
debug("Wait SSH2_MSG_KEXDH_INIT."); |
packet_read_expect(&payload_len, SSH2_MSG_KEXDH_INIT); |
packet_read_expect(&payload_len, SSH2_MSG_KEXDH_INIT); |
|
|
|
|
#ifdef DEBUG_KEXDH |
#ifdef DEBUG_KEXDH |
fprintf(stderr, "\ndh_client_pub= "); |
fprintf(stderr, "\ndh_client_pub= "); |
BN_print_fp(stderr, dh_client_pub); |
bignum_print(dh_client_pub); |
fprintf(stderr, "\n"); |
fprintf(stderr, "\n"); |
debug("bits %d", BN_num_bits(dh_client_pub)); |
debug("bits %d", BN_num_bits(dh_client_pub)); |
#endif |
#endif |
|
|
|
/* generate DH key */ |
|
dh = dh_new_group1(); /* XXX depends on 'kex' */ |
|
|
#ifdef DEBUG_KEXDH |
#ifdef DEBUG_KEXDH |
fprintf(stderr, "\np= "); |
fprintf(stderr, "\np= "); |
BN_print_fp(stderr, dh->p); |
bignum_print(dh->p); |
fprintf(stderr, "\ng= "); |
fprintf(stderr, "\ng= "); |
bn_print(dh->g); |
bignum_print(dh->g); |
fprintf(stderr, "\npub= "); |
fprintf(stderr, "\npub= "); |
BN_print_fp(stderr, dh->pub_key); |
bignum_print(dh->pub_key); |
fprintf(stderr, "\n"); |
fprintf(stderr, "\n"); |
DHparams_print_fp(stderr, dh); |
|
#endif |
#endif |
if (!dh_pub_is_valid(dh, dh_client_pub)) |
if (!dh_pub_is_valid(dh, dh_client_pub)) |
packet_disconnect("bad client public DH value"); |
packet_disconnect("bad client public DH value"); |
|
|
xfree(kbuf); |
xfree(kbuf); |
|
|
/* XXX precompute? */ |
/* XXX precompute? */ |
key_to_blob(hostkey, &server_host_key_blob, &sbloblen); |
dsa_make_key_blob(sensitive_data.dsa_host_key, &server_host_key_blob, &sbloblen); |
|
|
/* calc H */ /* XXX depends on 'kex' */ |
/* calc H */ /* XXX depends on 'kex' */ |
hash = kex_hash( |
hash = kex_hash( |
|
|
buffer_free(server_kexinit); |
buffer_free(server_kexinit); |
xfree(client_kexinit); |
xfree(client_kexinit); |
xfree(server_kexinit); |
xfree(server_kexinit); |
BN_free(dh_client_pub); |
|
#ifdef DEBUG_KEXDH |
#ifdef DEBUG_KEXDH |
fprintf(stderr, "hash == "); |
fprintf(stderr, "hash == "); |
for (i = 0; i< 20; i++) |
for (i = 0; i< 20; i++) |
|
|
|
|
/* sign H */ |
/* sign H */ |
/* XXX hashlen depends on KEX */ |
/* XXX hashlen depends on KEX */ |
key_sign(hostkey, &signature, &slen, hash, 20); |
dsa_sign(sensitive_data.dsa_host_key, &signature, &slen, hash, 20); |
|
|
destroy_sensitive_data(); |
destroy_sensitive_data(); |
|
|
|
|
packet_write_wait(); |
packet_write_wait(); |
|
|
kex_derive_keys(kex, hash, shared_secret); |
kex_derive_keys(kex, hash, shared_secret); |
BN_clear_free(shared_secret); |
|
packet_set_kex(kex); |
packet_set_kex(kex); |
|
|
/* have keys, free DH */ |
/* have keys, free DH */ |
DH_free(dh); |
DH_free(dh); |
} |
|
|
|
/* diffie-hellman-group-exchange-sha1 */ |
debug("send SSH2_MSG_NEWKEYS."); |
|
packet_start(SSH2_MSG_NEWKEYS); |
void |
|
ssh_dhgex_server(Kex *kex, Buffer *client_kexinit, Buffer *server_kexinit) |
|
{ |
|
#ifdef DEBUG_KEXDH |
|
int i; |
|
#endif |
|
int payload_len, dlen; |
|
int slen, nbits; |
|
u_char *signature = NULL; |
|
u_char *server_host_key_blob = NULL; |
|
u_int sbloblen; |
|
u_int klen, kout; |
|
u_char *kbuf; |
|
u_char *hash; |
|
BIGNUM *shared_secret = 0; |
|
DH *dh; |
|
BIGNUM *dh_client_pub = 0; |
|
Key *hostkey; |
|
|
|
hostkey = get_hostkey_by_type(kex->hostkey_type); |
|
if (hostkey == NULL) |
|
fatal("Unsupported hostkey type %d", kex->hostkey_type); |
|
|
|
/* KEXDHGEX */ |
|
debug("Wait SSH2_MSG_KEX_DH_GEX_REQUEST."); |
|
packet_read_expect(&payload_len, SSH2_MSG_KEX_DH_GEX_REQUEST); |
|
nbits = packet_get_int(); |
|
dh = choose_dh(nbits); |
|
|
|
debug("Sending SSH2_MSG_KEX_DH_GEX_GROUP."); |
|
packet_start(SSH2_MSG_KEX_DH_GEX_GROUP); |
|
packet_put_bignum2(dh->p); |
|
packet_put_bignum2(dh->g); |
|
packet_send(); |
packet_send(); |
packet_write_wait(); |
packet_write_wait(); |
|
debug("done: send SSH2_MSG_NEWKEYS."); |
|
|
/* Compute our exchange value in parallel with the client */ |
debug("Wait SSH2_MSG_NEWKEYS."); |
|
packet_read_expect(&payload_len, SSH2_MSG_NEWKEYS); |
|
debug("GOT SSH2_MSG_NEWKEYS."); |
|
|
dh_gen_key(dh, kex->we_need * 8); |
|
|
|
debug("Wait SSH2_MSG_KEX_DH_GEX_INIT."); |
|
packet_read_expect(&payload_len, SSH2_MSG_KEX_DH_GEX_INIT); |
|
|
|
/* key, cert */ |
|
dh_client_pub = BN_new(); |
|
if (dh_client_pub == NULL) |
|
fatal("dh_client_pub == NULL"); |
|
packet_get_bignum2(dh_client_pub, &dlen); |
|
|
|
#ifdef DEBUG_KEXDH |
#ifdef DEBUG_KEXDH |
fprintf(stderr, "\ndh_client_pub= "); |
/* send 1st encrypted/maced/compressed message */ |
BN_print_fp(stderr, dh_client_pub); |
packet_start(SSH2_MSG_IGNORE); |
fprintf(stderr, "\n"); |
packet_put_cstring("markus"); |
debug("bits %d", BN_num_bits(dh_client_pub)); |
|
#endif |
|
|
|
#ifdef DEBUG_KEXDH |
|
fprintf(stderr, "\np= "); |
|
BN_print_fp(stderr, dh->p); |
|
fprintf(stderr, "\ng= "); |
|
bn_print(dh->g); |
|
fprintf(stderr, "\npub= "); |
|
BN_print_fp(stderr, dh->pub_key); |
|
fprintf(stderr, "\n"); |
|
DHparams_print_fp(stderr, dh); |
|
#endif |
|
if (!dh_pub_is_valid(dh, dh_client_pub)) |
|
packet_disconnect("bad client public DH value"); |
|
|
|
klen = DH_size(dh); |
|
kbuf = xmalloc(klen); |
|
kout = DH_compute_key(kbuf, dh_client_pub, dh); |
|
|
|
#ifdef DEBUG_KEXDH |
|
debug("shared secret: len %d/%d", klen, kout); |
|
fprintf(stderr, "shared secret == "); |
|
for (i = 0; i< kout; i++) |
|
fprintf(stderr, "%02x", (kbuf[i])&0xff); |
|
fprintf(stderr, "\n"); |
|
#endif |
|
shared_secret = BN_new(); |
|
|
|
BN_bin2bn(kbuf, kout, shared_secret); |
|
memset(kbuf, 0, klen); |
|
xfree(kbuf); |
|
|
|
/* XXX precompute? */ |
|
key_to_blob(hostkey, &server_host_key_blob, &sbloblen); |
|
|
|
/* calc H */ /* XXX depends on 'kex' */ |
|
hash = kex_hash_gex( |
|
client_version_string, |
|
server_version_string, |
|
buffer_ptr(client_kexinit), buffer_len(client_kexinit), |
|
buffer_ptr(server_kexinit), buffer_len(server_kexinit), |
|
(char *)server_host_key_blob, sbloblen, |
|
nbits, dh->p, dh->g, |
|
dh_client_pub, |
|
dh->pub_key, |
|
shared_secret |
|
); |
|
buffer_free(client_kexinit); |
|
buffer_free(server_kexinit); |
|
xfree(client_kexinit); |
|
xfree(server_kexinit); |
|
BN_free(dh_client_pub); |
|
#ifdef DEBUG_KEXDH |
|
fprintf(stderr, "hash == "); |
|
for (i = 0; i< 20; i++) |
|
fprintf(stderr, "%02x", (hash[i])&0xff); |
|
fprintf(stderr, "\n"); |
|
#endif |
|
/* save session id := H */ |
|
/* XXX hashlen depends on KEX */ |
|
session_id2_len = 20; |
|
session_id2 = xmalloc(session_id2_len); |
|
memcpy(session_id2, hash, session_id2_len); |
|
|
|
/* sign H */ |
|
/* XXX hashlen depends on KEX */ |
|
key_sign(hostkey, &signature, &slen, hash, 20); |
|
|
|
destroy_sensitive_data(); |
|
|
|
/* send server hostkey, DH pubkey 'f' and singed H */ |
|
packet_start(SSH2_MSG_KEX_DH_GEX_REPLY); |
|
packet_put_string((char *)server_host_key_blob, sbloblen); |
|
packet_put_bignum2(dh->pub_key); /* f */ |
|
packet_put_string((char *)signature, slen); |
|
packet_send(); |
packet_send(); |
xfree(signature); |
|
xfree(server_host_key_blob); |
|
packet_write_wait(); |
packet_write_wait(); |
|
#endif |
kex_derive_keys(kex, hash, shared_secret); |
debug("done: KEX2."); |
BN_clear_free(shared_secret); |
|
packet_set_kex(kex); |
|
|
|
/* have keys, free DH */ |
|
DH_free(dh); |
|
} |
} |