X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/a8be9f800cf90e7d296951556739de21d2f93382..3e1caa83b4b5b9bba6df02792c2e7d194b202336:/sshd.c diff --git a/sshd.c b/sshd.c index 266146bf..b5d66acd 100644 --- a/sshd.c +++ b/sshd.c @@ -2,26 +2,51 @@ * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved - * Created: Fri Mar 17 17:09:28 1995 ylo - * This program is the ssh daemon. It listens for connections from clients, 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 - * connection. This can also handle forwarding of X11, TCP/IP, and authentication - * agent connections. + * connection. This can also handle forwarding of X11, TCP/IP, and + * authentication agent connections. * - * SSH2 implementation, - * Copyright (c) 2000 Markus Friedl. All rights reserved. + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * 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" -RCSID("$OpenBSD: sshd.c,v 1.103 2000/04/12 08:11:36 markus Exp $"); +RCSID("$OpenBSD: sshd.c,v 1.139 2000/12/15 17:30:14 provos Exp $"); #include "xmalloc.h" #include "rsa.h" #include "ssh.h" #include "pty.h" #include "packet.h" -#include "cipher.h" #include "mpaux.h" #include "servconf.h" #include "uidswap.h" @@ -29,26 +54,18 @@ RCSID("$OpenBSD: sshd.c,v 1.103 2000/04/12 08:11:36 markus Exp $"); #include "buffer.h" #include "ssh2.h" -#ifdef HAVE_OPENSSL -# include -# include -# include -# include -# include -#endif -#ifdef HAVE_SSL -# include -# include -# include -# include -# include -#endif +#include +#include +#include #include "kex.h" +#include +#include #include "key.h" -#include "dsa.h" +#include "dh.h" #include "auth.h" #include "myproposal.h" +#include "authfile.h" #ifdef LIBWRAP #include @@ -61,13 +78,19 @@ int deny_severity = LOG_WARNING; #define O_NOCTTY 0 #endif +#ifdef HAVE___PROGNAME +extern char *__progname; +#else +char *__progname; +#endif + /* Server configuration options. */ ServerOptions options; /* Name of the server configuration file. */ char *config_file_name = SERVER_CONFIG_FILE; -/* +/* * Flag indicating whether IPv4 or IPv6. This can be set on the command line. * Default value is AF_UNSPEC means both IPv4 and IPv6. */ @@ -88,14 +111,15 @@ int debug_flag = 0; /* Flag indicating that the daemon is being started from inetd. */ 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 */ int log_stderr = 0; -/* argv[0] without path. */ -char *av0; - /* Saved arguments to main(). */ char **saved_argv; +int saved_argc; /* * The sockets that the server is listening; this is used in the SIGHUP @@ -121,8 +145,11 @@ char *server_version_string = NULL; * not very useful. Currently, memory locking is not implemented. */ struct { - RSA *private_key; /* Private part of server key. */ - RSA *host_key; /* Private part of host key. */ + Key *server_key; /* empheral server key */ + Key *ssh1_host_key; /* ssh1 host key */ + Key **host_keys; /* all private host keys */ + int have_ssh1_key; + int have_ssh2_key; } sensitive_data; /* @@ -134,17 +161,23 @@ int key_used = 0; /* This is set to true when SIGHUP is received. */ 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 */ unsigned char session_id[16]; +/* same for ssh2 */ +unsigned char *session_id2 = NULL; +int session_id2_len = 0; + +/* record remote hostname or ip */ +unsigned int utmp_len = MAXHOSTNAMELEN; + /* Prototypes for various functions defined later in this file. */ void do_ssh1_kex(); void do_ssh2_kex(); +void ssh_dh1_server(Kex *, Buffer *_kexinit, Buffer *); +void ssh_dhgex_server(Kex *, Buffer *_kexinit, Buffer *); + /* * Close all listening sockets */ @@ -162,7 +195,7 @@ close_listen_socks(void) * the effect is to reread the configuration file (and to regenerate * the server key). */ -void +void sighup_handler(int sig) { received_sighup = 1; @@ -173,13 +206,13 @@ sighup_handler(int sig) * Called from the main program after receiving SIGHUP. * Restarts the server. */ -void +void sighup_restart() { log("Received SIGHUP; restarting."); close_listen_socks(); execv(saved_argv[0], saved_argv); - log("RESTART FAILED: av0='%s', error: %s.", av0, strerror(errno)); + log("RESTART FAILED: av[0]='%.100s', error: %.100s.", saved_argv[0], strerror(errno)); exit(1); } @@ -188,11 +221,12 @@ sighup_restart() * These close the listen socket; not closing it seems to cause "Address * already in use" problems on some machines, which is inconvenient. */ -void +void sigterm_handler(int sig) { log("Received signal %d; terminating.", sig); close_listen_socks(); + unlink(options.pid_file); exit(255); } @@ -200,7 +234,7 @@ sigterm_handler(int sig) * SIGCHLD handler. This is called whenever a child dies. This will then * reap any zombies left by exited c. */ -void +void main_sigchld_handler(int sig) { int save_errno = errno; @@ -216,7 +250,7 @@ main_sigchld_handler(int sig) /* * Signal handler for the alarm after the login grace period has expired. */ -void +void grace_alarm_handler(int sig) { /* Close the connection. */ @@ -233,7 +267,19 @@ grace_alarm_handler(int sig) * Thus there should be no concurrency control/asynchronous execution * problems. */ -void +/* XXX do we really want this work to be done in a signal handler ? -m */ +void +generate_empheral_server_key(void) +{ + log("Generating %s%d bit RSA key.", sensitive_data.server_key ? "new " : "", + options.server_key_bits); + if (sensitive_data.server_key != NULL) + key_free(sensitive_data.server_key); + sensitive_data.server_key = key_generate(KEY_RSA1, options.server_key_bits); + arc4random_stir(); + log("RSA key generation complete."); +} +void key_regeneration_alarm(int sig) { int save_errno = errno; @@ -241,21 +287,8 @@ key_regeneration_alarm(int sig) /* Check if we should generate a new key. */ if (key_used) { /* This should really be done in the background. */ - log("Generating new %d bit RSA key.", options.server_key_bits); - - if (sensitive_data.private_key != NULL) - RSA_free(sensitive_data.private_key); - sensitive_data.private_key = RSA_new(); - - if (public_key != NULL) - RSA_free(public_key); - public_key = RSA_new(); - - rsa_generate_key(sensitive_data.private_key, public_key, - options.server_key_bits); - arc4random_stir(); + generate_empheral_server_key(); key_used = 0; - log("RSA key generation complete."); } /* Reschedule the alarm. */ signal(SIGALRM, key_regeneration_alarm); @@ -263,21 +296,6 @@ key_regeneration_alarm(int sig) errno = save_errno; } -char * -chop(char *s) -{ - char *t = s; - while (*t) { - if(*t == '\n' || *t == '\r') { - *t = '\0'; - return s; - } - t++; - } - return s; - -} - void sshd_exchange_identification(int sock_in, int sock_out) { @@ -312,13 +330,17 @@ sshd_exchange_identification(int sock_in, int sock_out) /* Read other side\'s version identification. */ for (i = 0; i < sizeof(buf) - 1; i++) { - if (read(sock_in, &buf[i], 1) != 1) { + if (atomicio(read, sock_in, &buf[i], 1) != 1) { log("Did not receive ident string from %s.", get_remote_ipaddr()); fatal_cleanup(); } if (buf[i] == '\r') { buf[i] = '\n'; 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; } if (buf[i] == '\n') { @@ -337,7 +359,7 @@ sshd_exchange_identification(int sock_in, int sock_out) */ if (sscanf(client_version_string, "SSH-%d.%d-%[^\n]\n", &remote_major, &remote_minor, remote_version) != 3) { - s = "Protocol mismatch.\n"; + s = "Protocol mismatch.\n"; (void) atomicio(write, sock_out, s, strlen(s)); close(sock_in); close(sock_out); @@ -353,23 +375,24 @@ sshd_exchange_identification(int sock_in, int sock_out) mismatch = 0; switch(remote_major) { case 1: + if (remote_minor == 99) { + if (options.protocol & SSH_PROTO_2) + enable_compat20(); + else + mismatch = 1; + break; + } if (!(options.protocol & SSH_PROTO_1)) { mismatch = 1; break; } 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."); } else if (remote_minor == 3) { /* note that this disables agent-forwarding */ enable_compat13(); } - if (remote_minor == 99) { - if (options.protocol & SSH_PROTO_2) - enable_compat20(); - else - mismatch = 1; - } break; case 2: if (options.protocol & SSH_PROTO_2) { @@ -377,7 +400,7 @@ sshd_exchange_identification(int sock_in, int sock_out) break; } /* FALLTHROUGH */ - default: + default: mismatch = 1; break; } @@ -395,8 +418,132 @@ sshd_exchange_identification(int sock_in, int sock_out) server_version_string, client_version_string); fatal_cleanup(); } + if (compat20) + packet_set_ssh2_format(); +} + + +/* Destroy the host and server keys. They will no longer be needed. */ +void +destroy_sensitive_data(void) +{ + int i; + + if (sensitive_data.server_key) { + key_free(sensitive_data.server_key); + sensitive_data.server_key = NULL; + } + 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; +} +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. */ @@ -405,32 +552,34 @@ main(int ac, char **av) { extern char *optarg; extern int optind; - int opt, sock_in = 0, sock_out = 0, newsock, i, fdsetsz, pid, on = 1; + int opt, sock_in = 0, sock_out = 0, newsock, j, i, fdsetsz, on = 1; + pid_t pid; socklen_t fromlen; - int silentrsa = 0; + int silent = 0; fd_set *fdset; struct sockaddr_storage from; const char *remote_ip; int remote_port; - char *comment; FILE *f; struct linger linger; struct addrinfo *ai; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; int listen_sock, maxfd; + int startup_p[2]; + int startups = 0; + + __progname = get_progname(av[0]); + init_rng(); - /* Save argv[0]. */ + /* Save argv. */ + saved_argc = ac; saved_argv = av; - if (strchr(av[0], '/')) - av0 = strrchr(av[0], '/') + 1; - else - av0 = av[0]; /* Initialize configuration options to their default values. */ initialize_server_options(&options); /* Parse command-line arguments. */ - while ((opt = getopt(ac, av, "f:p:b:k:h:g:V:diqQ46")) != EOF) { + while ((opt = getopt(ac, av, "f:p:b:k:h:g:V:u:dDiqQ46")) != EOF) { switch (opt) { case '4': IPv4or6 = AF_INET; @@ -442,14 +591,24 @@ main(int ac, char **av) config_file_name = optarg; break; case 'd': - debug_flag = 1; - options.log_level = SYSLOG_LEVEL_DEBUG; + if (0 == debug_flag) { + debug_flag = 1; + 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; + case 'D': + no_daemon_flag = 1; break; case 'i': inetd_flag = 1; break; case 'Q': - silentrsa = 1; + silent = 1; break; case 'q': options.log_level = SYSLOG_LEVEL_QUIET; @@ -459,8 +618,10 @@ main(int ac, char **av) break; case 'p': options.ports_from_cmdline = 1; - if (options.num_ports >= MAX_PORTS) - fatal("too many ports.\n"); + if (options.num_ports >= MAX_PORTS) { + fprintf(stderr, "too many ports.\n"); + exit(1); + } options.ports[options.num_ports++] = atoi(optarg); break; case 'g': @@ -470,20 +631,27 @@ main(int ac, char **av) options.key_regeneration_time = atoi(optarg); break; case 'h': - options.host_key_file = optarg; + if (options.num_host_key_files >= MAX_HOSTKEYS) { + fprintf(stderr, "too many host keys.\n"); + exit(1); + } + options.host_key_files[options.num_host_key_files++] = optarg; break; case 'V': client_version_string = optarg; /* only makes sense with inetd_flag, i.e. no listen() */ inetd_flag = 1; break; + case 'u': + utmp_len = atoi(optarg); + break; case '?': default: fprintf(stderr, "sshd version %s\n", SSH_VERSION); - fprintf(stderr, "Usage: %s [options]\n", av0); + fprintf(stderr, "Usage: %s [options]\n", __progname); fprintf(stderr, "Options:\n"); fprintf(stderr, " -f file Configuration file (default %s)\n", SERVER_CONFIG_FILE); - fprintf(stderr, " -d Debugging mode\n"); + fprintf(stderr, " -d Debugging mode (multiple -d means more debugging)\n"); fprintf(stderr, " -i Started from inetd\n"); fprintf(stderr, " -q Quiet (no logging)\n"); fprintf(stderr, " -p port Listen on the specified port (default: 22)\n"); @@ -492,6 +660,7 @@ main(int ac, char **av) 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", HOST_KEY_FILE); + fprintf(stderr, " -u len Maximum hostname length for utmp recording\n"); fprintf(stderr, " -4 Use IPv4 only\n"); fprintf(stderr, " -6 Use IPv6 only\n"); exit(1); @@ -502,30 +671,17 @@ main(int ac, char **av) * Force logging to stderr until we have loaded the private host * key (unless started from inetd) */ - log_init(av0, + log_init(__progname, options.log_level == -1 ? SYSLOG_LEVEL_INFO : options.log_level, options.log_facility == -1 ? SYSLOG_FACILITY_AUTH : options.log_facility, - !inetd_flag); + !silent && !inetd_flag); - /* check if RSA support exists */ - if (rsa_alive() == 0) { - if (silentrsa == 0) - printf("sshd: no RSA support in libssl and libcrypto -- exiting. See ssl(8)\n"); - log("no RSA support in libssl and libcrypto -- exiting. See ssl(8)"); - exit(1); - } /* Read server configuration options from the configuration file. */ read_server_config(&options, config_file_name); /* Fill in default values for those options not explicitly set. */ fill_default_server_options(&options); - /* Check certain values for sanity. */ - if (options.server_key_bits < 512 || - options.server_key_bits > 32768) { - fprintf(stderr, "Bad server key size.\n"); - exit(1); - } /* Check that there are no remaining arguments. */ if (optind < ac) { fprintf(stderr, "Extra argument %s.\n", av[optind]); @@ -534,27 +690,85 @@ main(int ac, char **av) debug("sshd version %.100s", SSH_VERSION); - sensitive_data.host_key = RSA_new(); - errno = 0; - /* Load the host key. It must have empty passphrase. */ - if (!load_private_key(options.host_key_file, "", - sensitive_data.host_key, &comment)) { - error("Could not load host key: %.200s: %.100s", - options.host_key_file, strerror(errno)); + /* load private host keys */ + sensitive_data.host_keys = xmalloc(options.num_host_key_files*sizeof(Key*)); + 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++) { + Key *key = load_private_key_autodetect(options.host_key_files[i]); + if (key == NULL) { + error("Could not load host key: %.200s: %.100s", + options.host_key_files[i], strerror(errno)); + continue; + } + switch(key->type){ + case KEY_RSA1: + sensitive_data.ssh1_host_key = key; + sensitive_data.have_ssh1_key = 1; + break; + case KEY_RSA: + case KEY_DSA: + sensitive_data.have_ssh2_key = 1; + break; + } + sensitive_data.host_keys[i] = key; + } + if ((options.protocol & SSH_PROTO_1) && !sensitive_data.have_ssh1_key) { + log("Disabling protocol version 1. Could not load host key"); + options.protocol &= ~SSH_PROTO_1; + } + 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)) { + if (silent == 0) + fprintf(stderr, "sshd: no hostkeys available -- exiting.\n"); + log("sshd: no hostkeys available -- exiting.\n"); exit(1); } - xfree(comment); - /* Initialize the log (it is reinitialized below in case we - forked). */ + /* Check certain values for sanity. */ + if (options.protocol & SSH_PROTO_1) { + if (options.server_key_bits < 512 || + options.server_key_bits > 32768) { + fprintf(stderr, "Bad server key size.\n"); + exit(1); + } + /* + * Check that server and host key lengths differ sufficiently. This + * is necessary to make double encryption work with rsaref. Oh, I + * hate software patents. I dont know if this can go? Niels + */ + if (options.server_key_bits > + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) - SSH_KEY_BITS_RESERVED && + options.server_key_bits < + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + SSH_KEY_BITS_RESERVED) { + options.server_key_bits = + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + SSH_KEY_BITS_RESERVED; + debug("Forcing server key to %d bits to make it differ from host key.", + options.server_key_bits); + } + } + +#ifdef HAVE_SCO_PROTECTED_PW + (void) set_auth_parameters(ac, av); +#endif + + /* Initialize the log (it is reinitialized below in case we forked). */ if (debug_flag && !inetd_flag) log_stderr = 1; - log_init(av0, options.log_level, options.log_facility, log_stderr); + log_init(__progname, options.log_level, options.log_facility, log_stderr); - /* If not in debugging mode, and not started from inetd, - disconnect from the controlling terminal, and fork. The - original process exits. */ - if (!debug_flag && !inetd_flag) { + /* + * If not in debugging mode, and not started from inetd, disconnect + * from the controlling terminal, and fork. The original process + * exits. + */ + if (!(debug_flag || inetd_flag || no_daemon_flag)) { #ifdef TIOCNOTTY int fd; #endif /* TIOCNOTTY */ @@ -571,22 +785,7 @@ main(int ac, char **av) #endif /* TIOCNOTTY */ } /* Reinitialize the log (because of the fork above). */ - log_init(av0, options.log_level, options.log_facility, log_stderr); - - /* Check that server and host key lengths differ sufficiently. - This is necessary to make double encryption work with rsaref. - Oh, I hate software patents. I dont know if this can go? Niels */ - if (options.server_key_bits > - BN_num_bits(sensitive_data.host_key->n) - SSH_KEY_BITS_RESERVED && - options.server_key_bits < - BN_num_bits(sensitive_data.host_key->n) + SSH_KEY_BITS_RESERVED) { - options.server_key_bits = - 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.", - options.server_key_bits); - } - /* Do not display messages to stdout in RSA code. */ - rsa_set_verbose(0); + log_init(__progname, options.log_level, options.log_facility, log_stderr); /* Initialize the random number generator. */ arc4random_stir(); @@ -602,20 +801,15 @@ main(int ac, char **av) s2 = dup(s1); sock_in = dup(0); sock_out = dup(1); - /* We intentionally do not close the descriptors 0, 1, and 2 - as our code for setting the descriptors won\'t work - if ttyfd happens to be one of those. */ + startup_pipe = -1; + /* + * We intentionally do not close the descriptors 0, 1, and 2 + * as our code for setting the descriptors won\'t work if + * ttyfd happens to be one of those. + */ debug("inetd sockets after dupping: %d, %d", sock_in, sock_out); - - public_key = RSA_new(); - sensitive_data.private_key = RSA_new(); - - /* XXX check options.protocol */ - 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."); + if (options.protocol & SSH_PROTO_1) + generate_empheral_server_key(); } else { for (ai = options.listen_addrs; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) @@ -680,34 +874,29 @@ main(int ac, char **av) if (!debug_flag) { /* - * Record our pid in /etc/sshd_pid to make it easier - * to kill the correct sshd. We don\'t want to do - * this before the bind above because the bind will + * Record our pid in /var/run/sshd.pid to make it + * easier to kill the correct sshd. We don't want to + * do this before the bind above because the bind will * fail if there already is a daemon, and this will * overwrite any old pid in the file. */ - f = fopen(SSH_DAEMON_PID_FILE, "w"); + f = fopen(options.pid_file, "wb"); if (f) { fprintf(f, "%u\n", (unsigned int) getpid()); fclose(f); } } + if (options.protocol & SSH_PROTO_1) { + generate_empheral_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); + /* 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. */ signal(SIGHUP, sighup_handler); + signal(SIGTERM, sigterm_handler); signal(SIGQUIT, sigterm_handler); @@ -715,12 +904,15 @@ main(int ac, char **av) signal(SIGCHLD, main_sigchld_handler); /* setup fd set for listen */ + fdset = NULL; maxfd = 0; for (i = 0; i < num_listen_socks; i++) if (listen_socks[i] > maxfd) maxfd = listen_socks[i]; - fdsetsz = howmany(maxfd, NFDBITS) * sizeof(fd_mask); - fdset = (fd_set *)xmalloc(fdsetsz); + /* pipes connected to unauthenticated childs */ + startup_pipes = xmalloc(options.max_startups * sizeof(int)); + for (i = 0; i < options.max_startups; i++) + startup_pipes[i] = -1; /* * Stay listening for connections until the system crashes or @@ -729,80 +921,130 @@ main(int ac, char **av) for (;;) { if (received_sighup) sighup_restart(); - /* Wait in select until there is a connection. */ + if (fdset != NULL) + xfree(fdset); + fdsetsz = howmany(maxfd, NFDBITS) * sizeof(fd_mask); + fdset = (fd_set *)xmalloc(fdsetsz); memset(fdset, 0, fdsetsz); + for (i = 0; i < num_listen_socks; i++) FD_SET(listen_socks[i], fdset); + for (i = 0; i < options.max_startups; i++) + if (startup_pipes[i] != -1) + FD_SET(startup_pipes[i], fdset); + + /* Wait in select until there is a connection. */ if (select(maxfd + 1, fdset, NULL, NULL, NULL) < 0) { if (errno != EINTR) error("select: %.100s", strerror(errno)); 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 successfull authentication + * or if the child has died + */ + close(startup_pipes[i]); + startup_pipes[i] = -1; + startups--; + } for (i = 0; i < num_listen_socks; i++) { if (!FD_ISSET(listen_socks[i], fdset)) continue; - fromlen = sizeof(from); - newsock = accept(listen_socks[i], (struct sockaddr *)&from, - &fromlen); - if (newsock < 0) { - if (errno != EINTR && errno != EWOULDBLOCK) - error("accept: %.100s", strerror(errno)); - continue; - } - if (fcntl(newsock, F_SETFL, 0) < 0) { - error("newsock del O_NONBLOCK: %s", strerror(errno)); - continue; - } - /* - * Got connection. Fork a child to handle it, unless - * we are in debugging mode. - */ - if (debug_flag) { - /* - * In debugging mode. Close the listening - * socket, and start processing the - * connection without forking. - */ - debug("Server will not fork when running in debugging mode."); - close_listen_socks(); - sock_in = newsock; - sock_out = newsock; - pid = getpid(); - break; - } else { + fromlen = sizeof(from); + newsock = accept(listen_socks[i], (struct sockaddr *)&from, + &fromlen); + if (newsock < 0) { + if (errno != EINTR && errno != EWOULDBLOCK) + error("accept: %.100s", strerror(errno)); + continue; + } + if (fcntl(newsock, F_SETFL, 0) < 0) { + error("newsock del O_NONBLOCK: %s", strerror(errno)); + continue; + } + if (drop_connection(startups) == 1) { + debug("drop connection #%d", startups); + close(newsock); + continue; + } + 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; + } + /* - * Normal production daemon. Fork, and have - * the child process the connection. The - * parent continues listening. + * Got connection. Fork a child to handle it, unless + * we are in debugging mode. */ - if ((pid = fork()) == 0) { + if (debug_flag) { /* - * Child. Close the listening socket, and start using the - * accepted socket. Reinitialize logging (since our pid has - * changed). We break out of the loop to handle the connection. + * In debugging mode. Close the listening + * socket, and start processing the + * connection without forking. */ + debug("Server will not fork when running in debugging mode."); close_listen_socks(); sock_in = newsock; sock_out = newsock; - log_init(av0, options.log_level, options.log_facility, log_stderr); + startup_pipe = -1; + pid = getpid(); 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. */ - if (pid < 0) - error("fork: %.100s", strerror(errno)); - else - debug("Forked child %d.", pid); + /* Parent. Stay in the loop. */ + if (pid < 0) + error("fork: %.100s", strerror(errno)); + else + 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). */ + key_used = 1; - arc4random_stir(); + arc4random_stir(); - /* Close the new socket (the child is now taking care of it). */ - close(newsock); - } /* for (i = 0; i < num_listen_socks; i++) */ + /* Close the new socket (the child is now taking care of it). */ + close(newsock); + } /* child process check (or debug mode) */ if (num_listen_socks < 0) break; @@ -822,6 +1064,7 @@ main(int ac, char **av) signal(SIGTERM, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGCHLD, SIG_DFL); + signal(SIGINT, SIG_DFL); /* * Set socket options for the connection. We want the socket to @@ -848,7 +1091,7 @@ main(int ac, char **av) { struct request_info req; - request_init(&req, RQ_DAEMON, av0, RQ_FILE, sock_in, NULL); + request_init(&req, RQ_DAEMON, __progname, RQ_FILE, sock_in, NULL); fromhost(&req); if (!hosts_access(&req)) { @@ -876,16 +1119,17 @@ main(int ac, char **av) sshd_exchange_identification(sock_in, sock_out); /* - * Check that the connection comes from a privileged port. Rhosts- - * and Rhosts-RSA-Authentication only make sense from priviledged + * Check that the connection comes from a privileged port. + * Rhosts-Authentication only makes sense from priviledged * programs. Of course, if the intruder has root access on his local * machine, he can connect from any port. So do not use these * authentication methods from machines that you do not trust. */ if (remote_port >= IPPORT_RESERVED || remote_port < IPPORT_RESERVED / 2) { + debug("Rhosts Authentication disabled, " + "originating port not trusted."); options.rhosts_authentication = 0; - options.rhosts_rsa_authentication = 0; } #ifdef KRB4 if (!packet_connection_is_ipv4() && @@ -964,20 +1208,20 @@ do_ssh1_kex() packet_put_char(cookie[i]); /* Store our public server RSA key. */ - packet_put_int(BN_num_bits(public_key->n)); - packet_put_bignum(public_key->e); - packet_put_bignum(public_key->n); + packet_put_int(BN_num_bits(sensitive_data.server_key->rsa->n)); + packet_put_bignum(sensitive_data.server_key->rsa->e); + packet_put_bignum(sensitive_data.server_key->rsa->n); /* Store our public host RSA key. */ - packet_put_int(BN_num_bits(sensitive_data.host_key->n)); - packet_put_bignum(sensitive_data.host_key->e); - packet_put_bignum(sensitive_data.host_key->n); + packet_put_int(BN_num_bits(sensitive_data.ssh1_host_key->rsa->n)); + packet_put_bignum(sensitive_data.ssh1_host_key->rsa->e); + packet_put_bignum(sensitive_data.ssh1_host_key->rsa->n); /* Put protocol flags. */ packet_put_int(SSH_PROTOFLAG_HOST_IN_FWD_OPEN); /* Declare which ciphers we support. */ - packet_put_int(cipher_mask1()); + packet_put_int(cipher_mask_ssh1(0)); /* Declare supported authentication types. */ auth_mask = 0; @@ -1009,8 +1253,9 @@ do_ssh1_kex() packet_send(); packet_write_wait(); - debug("Sent %d bit public key and %d bit host key.", - BN_num_bits(public_key->n), BN_num_bits(sensitive_data.host_key->n)); + debug("Sent %d bit server key and %d bit host key.", + BN_num_bits(sensitive_data.server_key->rsa->n), + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n)); /* Read clients reply (cipher type and session key). */ packet_read_expect(&plen, SSH_CMSG_SESSION_KEY); @@ -1018,7 +1263,7 @@ do_ssh1_kex() /* Get cipher type and check whether we accept this. */ cipher_type = packet_get_char(); - if (!(cipher_mask() & (1 << cipher_type))) + if (!(cipher_mask_ssh1(0) & (1 << cipher_type))) packet_disconnect("Warning: client selects unsupported cipher."); /* Get check bytes from the packet. These must match those we @@ -1042,44 +1287,42 @@ do_ssh1_kex() * Decrypt it using our private server key and private host key (key * with larger modulus first). */ - if (BN_cmp(sensitive_data.private_key->n, sensitive_data.host_key->n) > 0) { + if (BN_cmp(sensitive_data.server_key->rsa->n, sensitive_data.ssh1_host_key->rsa->n) > 0) { /* Private key has bigger modulus. */ - if (BN_num_bits(sensitive_data.private_key->n) < - BN_num_bits(sensitive_data.host_key->n) + SSH_KEY_BITS_RESERVED) { - fatal("do_connection: %s: private_key %d < host_key %d + SSH_KEY_BITS_RESERVED %d", - get_remote_ipaddr(), - BN_num_bits(sensitive_data.private_key->n), - BN_num_bits(sensitive_data.host_key->n), - SSH_KEY_BITS_RESERVED); + if (BN_num_bits(sensitive_data.server_key->rsa->n) < + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + SSH_KEY_BITS_RESERVED) { + fatal("do_connection: %s: server_key %d < host_key %d + SSH_KEY_BITS_RESERVED %d", + get_remote_ipaddr(), + BN_num_bits(sensitive_data.server_key->rsa->n), + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), + SSH_KEY_BITS_RESERVED); } rsa_private_decrypt(session_key_int, session_key_int, - sensitive_data.private_key); + sensitive_data.server_key->rsa); rsa_private_decrypt(session_key_int, session_key_int, - sensitive_data.host_key); + sensitive_data.ssh1_host_key->rsa); } else { /* Host key has bigger modulus (or they are equal). */ - if (BN_num_bits(sensitive_data.host_key->n) < - BN_num_bits(sensitive_data.private_key->n) + SSH_KEY_BITS_RESERVED) { - fatal("do_connection: %s: host_key %d < private_key %d + SSH_KEY_BITS_RESERVED %d", - get_remote_ipaddr(), - BN_num_bits(sensitive_data.host_key->n), - BN_num_bits(sensitive_data.private_key->n), - SSH_KEY_BITS_RESERVED); + if (BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) < + BN_num_bits(sensitive_data.server_key->rsa->n) + SSH_KEY_BITS_RESERVED) { + fatal("do_connection: %s: host_key %d < server_key %d + SSH_KEY_BITS_RESERVED %d", + get_remote_ipaddr(), + BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), + BN_num_bits(sensitive_data.server_key->rsa->n), + SSH_KEY_BITS_RESERVED); } rsa_private_decrypt(session_key_int, session_key_int, - sensitive_data.host_key); + sensitive_data.ssh1_host_key->rsa); rsa_private_decrypt(session_key_int, session_key_int, - sensitive_data.private_key); + sensitive_data.server_key->rsa); } compute_session_id(session_id, cookie, - sensitive_data.host_key->n, - sensitive_data.private_key->n); + sensitive_data.ssh1_host_key->rsa->n, + sensitive_data.server_key->rsa->n); /* Destroy the private and public keys. They will no longer be needed. */ - RSA_free(public_key); - RSA_free(sensitive_data.private_key); - RSA_free(sensitive_data.host_key); + destroy_sensitive_data(); /* * Extract session key from the decrypted integer. The key is in the @@ -1090,8 +1333,8 @@ do_ssh1_kex() len = BN_num_bytes(session_key_int); if (len < 0 || len > sizeof(session_key)) fatal("do_connection: bad len from %s: session_key_int %d > sizeof(session_key) %d", - get_remote_ipaddr(), - len, sizeof(session_key)); + get_remote_ipaddr(), + len, sizeof(session_key)); memset(session_key, 0, sizeof(session_key)); BN_bn2bin(session_key_int, session_key + sizeof(session_key) - len); @@ -1124,72 +1367,94 @@ do_ssh2_kex() { Buffer *server_kexinit; Buffer *client_kexinit; - 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 payload_len; int i; - unsigned char *kbuf; - unsigned char *hash; Kex *kex; - Key *server_host_key; char *cprop[PROPOSAL_MAX]; - char *sprop[PROPOSAL_MAX]; /* KEXINIT */ if (options.ciphers != NULL) { - myproposal[PROPOSAL_ENC_ALGS_CTOS] = + myproposal[PROPOSAL_ENC_ALGS_CTOS] = myproposal[PROPOSAL_ENC_ALGS_STOC] = options.ciphers; } + myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = list_hostkey_types(); - debug("Sending KEX init."); + server_kexinit = kex_init(myproposal); + 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++) - sprop[i] = xstrdup(myproposal[i]); - server_kexinit = kex_init(sprop); - packet_start(SSH2_MSG_KEXINIT); - packet_put_raw(buffer_ptr(server_kexinit), buffer_len(server_kexinit)); + xfree(cprop[i]); + + switch (kex->kex_type) { + case DH_GRP1_SHA1: + 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_write_wait(); + debug("done: send SSH2_MSG_NEWKEYS."); - debug("done"); + debug("Wait SSH2_MSG_NEWKEYS."); + packet_read_expect(&payload_len, SSH2_MSG_NEWKEYS); + debug("GOT SSH2_MSG_NEWKEYS."); - packet_read_expect(&payload_len, SSH2_MSG_KEXINIT); +#ifdef DEBUG_KEXDH + /* send 1st encrypted/maced/compressed message */ + packet_start(SSH2_MSG_IGNORE); + packet_put_cstring("markus"); + packet_send(); + packet_write_wait(); +#endif - /* - * 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); + debug("done: KEX2."); +} - /* skip cookie */ - 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]); - } +/* + * SSH2 key exchange + */ - i = (int) packet_get_char(); - debug("first kex follow == %d", i); - i = packet_get_int(); - debug("reserved == %d", i); +/* diffie-hellman-group1-sha1 */ - debug("done read kexinit"); - kex = kex_choose_conf(cprop, sprop, 1); +void +ssh_dh1_server(Kex *kex, Buffer *client_kexinit, Buffer *server_kexinit) +{ +#ifdef DEBUG_KEXDH + int i; +#endif + int payload_len, dlen; + int slen; + unsigned char *signature = NULL; + unsigned char *server_host_key_blob = NULL; + unsigned int sbloblen; + unsigned int klen, kout; + unsigned char *kbuf; + unsigned 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 */ + /* generate DH key */ + dh = dh_new_group1(); /* XXX depends on 'kex' */ + dh_gen_key(dh); debug("Wait SSH2_MSG_KEXDH_INIT."); packet_read_expect(&payload_len, SSH2_MSG_KEXDH_INIT); @@ -1202,22 +1467,20 @@ do_ssh2_kex() #ifdef DEBUG_KEXDH fprintf(stderr, "\ndh_client_pub= "); - bignum_print(dh_client_pub); + BN_print_fp(stderr, dh_client_pub); fprintf(stderr, "\n"); debug("bits %d", BN_num_bits(dh_client_pub)); #endif - /* generate DH key */ - dh = dh_new_group1(); /* XXX depends on 'kex' */ - #ifdef DEBUG_KEXDH fprintf(stderr, "\np= "); - bignum_print(dh->p); + BN_print_fp(stderr, dh->p); fprintf(stderr, "\ng= "); - bignum_print(dh->g); + bn_print(dh->g); fprintf(stderr, "\npub= "); - bignum_print(dh->pub_key); + 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"); @@ -1239,8 +1502,8 @@ do_ssh2_kex() memset(kbuf, 0, klen); xfree(kbuf); - server_host_key = dsa_get_serverkey(options.dsa_key_file); - dsa_make_serverkey_blob(server_host_key, &server_host_key_blob, &sbloblen); + /* XXX precompute? */ + key_to_blob(hostkey, &server_host_key_blob, &sbloblen); /* calc H */ /* XXX depends on 'kex' */ hash = kex_hash( @@ -1258,22 +1521,31 @@ do_ssh2_kex() xfree(client_kexinit); xfree(server_kexinit); #ifdef DEBUG_KEXDH - fprintf(stderr, "hash == "); - for (i = 0; i< 20; i++) - fprintf(stderr, "%02x", (hash[i])&0xff); - fprintf(stderr, "\n"); + 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 */ - dsa_sign(server_host_key, &signature, &slen, hash, 20); - /* hashlen depends on KEX */ - key_free(server_host_key); + /* 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_KEXDH_REPLY); packet_put_string((char *)server_host_key_blob, sbloblen); - packet_put_bignum2(dh->pub_key); // f + packet_put_bignum2(dh->pub_key); /* f */ packet_put_string((char *)signature, slen); packet_send(); + xfree(signature); + xfree(server_host_key_blob); packet_write_wait(); kex_derive_keys(kex, hash, shared_secret); @@ -1281,23 +1553,146 @@ do_ssh2_kex() /* have keys, free DH */ DH_free(dh); +} - debug("send SSH2_MSG_NEWKEYS."); - packet_start(SSH2_MSG_NEWKEYS); +/* diffie-hellman-group-exchange-sha1 */ + +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; + unsigned char *signature = NULL; + unsigned char *server_host_key_blob = NULL; + unsigned int sbloblen; + unsigned int klen, kout; + unsigned char *kbuf; + unsigned 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_write_wait(); - debug("done: send SSH2_MSG_NEWKEYS."); - debug("Wait SSH2_MSG_NEWKEYS."); - packet_read_expect(&payload_len, SSH2_MSG_NEWKEYS); - debug("GOT SSH2_MSG_NEWKEYS."); + /* Compute our exchange value in parallel with the client */ + + dh_gen_key(dh); + + 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 - /* send 1st encrypted/maced/compressed message */ - packet_start(SSH2_MSG_IGNORE); - packet_put_cstring("markus"); + fprintf(stderr, "\ndh_client_pub= "); + BN_print_fp(stderr, dh_client_pub); + fprintf(stderr, "\n"); + 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); +#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(); + xfree(signature); + xfree(server_host_key_blob); packet_write_wait(); -#endif - debug("done: KEX2."); + + kex_derive_keys(kex, hash, shared_secret); + packet_set_kex(kex); + + /* have keys, free DH */ + DH_free(dh); }