X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/c2b544a5afe0736a3c6bbef5e9e63f365fbae483..01dafcb504c448f0d4d5834c21b99e025f492550:/sshd.c diff --git a/sshd.c b/sshd.c index bd90d0e5..589a1160 100644 --- a/sshd.c +++ b/sshd.c @@ -15,8 +15,10 @@ * called by a name other than "ssh" or "Secure Shell". * * SSH2 implementation: + * Privilege Separation: * - * Copyright (c) 2000 Markus Friedl. All rights reserved. + * Copyright (c) 2000, 2001, 2002 Markus Friedl. All rights reserved. + * Copyright (c) 2002 Niels Provos. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -40,11 +42,16 @@ */ #include "includes.h" -RCSID("$OpenBSD: sshd.c,v 1.168 2001/02/19 23:09:05 deraadt Exp $"); +RCSID("$OpenBSD: sshd.c,v 1.240 2002/04/23 22:16:29 djm Exp $"); #include #include -#include +#include +#include +#ifdef HAVE_SECUREWARE +#include +#include +#endif #include "ssh.h" #include "ssh1.h" @@ -70,6 +77,13 @@ RCSID("$OpenBSD: sshd.c,v 1.168 2001/02/19 23:09:05 deraadt Exp $"); #include "canohost.h" #include "auth.h" #include "misc.h" +#include "dispatch.h" +#include "channels.h" +#include "session.h" +#include "monitor_mm.h" +#include "monitor.h" +#include "monitor_wrap.h" +#include "monitor_fdpass.h" #ifdef LIBWRAP #include @@ -112,6 +126,9 @@ int IPv4or6 = AF_UNSPEC; */ int debug_flag = 0; +/* Flag indicating that the daemon should only test the configuration and keys. */ +int test_flag = 0; + /* Flag indicating that the daemon is being started from inetd. */ int inetd_flag = 0; @@ -140,6 +157,9 @@ int num_listen_socks = 0; char *client_version_string = NULL; char *server_version_string = NULL; +/* for rekeying XXX fixme */ +Kex *xxx_kex; + /* * Any really sensitive data in the application is contained in this * structure. The idea is that this structure could be locked into memory so @@ -149,21 +169,23 @@ char *server_version_string = NULL; * not very useful. Currently, memory locking is not implemented. */ struct { - Key *server_key; /* empheral server key */ + Key *server_key; /* ephemeral server key */ Key *ssh1_host_key; /* ssh1 host key */ Key **host_keys; /* all private host keys */ int have_ssh1_key; int have_ssh2_key; + u_char ssh1_cookie[SSH_SESSION_KEY_LENGTH]; } sensitive_data; /* * Flag indicating whether the RSA server key needs to be regenerated. * Is set in the SIGALRM handler and cleared when the key is regenerated. */ -int key_do_regen = 0; +static volatile sig_atomic_t key_do_regen = 0; -/* This is set to true when SIGHUP is received. */ -int received_sighup = 0; +/* This is set to true when a signal is received. */ +static volatile sig_atomic_t received_sighup = 0; +static volatile sig_atomic_t received_sigterm = 0; /* session identifier, used by RSA-auth */ u_char session_id[16]; @@ -175,17 +197,25 @@ int session_id2_len = 0; /* record remote hostname or ip */ u_int utmp_len = MAXHOSTNAMELEN; +/* options.max_startup sized array of fd ints */ +int *startup_pipes = NULL; +int startup_pipe; /* in child */ + +/* variables used for privilege separation */ +extern struct monitor *monitor; +extern int use_privsep; + /* Prototypes for various functions defined later in this file. */ -void do_ssh1_kex(void); -void do_ssh2_kex(void); +void destroy_sensitive_data(void); +void demote_sensitive_data(void); -void ssh_dh1_server(Kex *, Buffer *_kexinit, Buffer *); -void ssh_dhgex_server(Kex *, Buffer *_kexinit, Buffer *); +static void do_ssh1_kex(void); +static void do_ssh2_kex(void); /* * Close all listening sockets */ -void +static void close_listen_socks(void) { int i; @@ -194,27 +224,41 @@ close_listen_socks(void) num_listen_socks = -1; } +static void +close_startup_pipes(void) +{ + int i; + if (startup_pipes) + for (i = 0; i < options.max_startups; i++) + if (startup_pipes[i] != -1) + close(startup_pipes[i]); +} + /* * Signal handler for SIGHUP. Sshd execs itself when it receives SIGHUP; * the effect is to reread the configuration file (and to regenerate * the server key). */ -void +static void sighup_handler(int sig) { + int save_errno = errno; + received_sighup = 1; signal(SIGHUP, sighup_handler); + errno = save_errno; } /* * Called from the main program after receiving SIGHUP. * Restarts the server. */ -void +static void sighup_restart(void) { log("Received SIGHUP; restarting."); close_listen_socks(); + close_startup_pipes(); execv(saved_argv[0], saved_argv); log("RESTART FAILED: av[0]='%.100s', error: %.100s.", saved_argv[0], strerror(errno)); exit(1); @@ -222,29 +266,26 @@ sighup_restart(void) /* * Generic signal handler for terminating signals in the master daemon. - * These close the listen socket; not closing it seems to cause "Address - * already in use" problems on some machines, which is inconvenient. */ -void +static void sigterm_handler(int sig) { - log("Received signal %d; terminating.", sig); - close_listen_socks(); - unlink(options.pid_file); - exit(255); + received_sigterm = sig; } /* * SIGCHLD handler. This is called whenever a child dies. This will then - * reap any zombies left by exited c. + * reap any zombies left by exited children. */ -void +static void main_sigchld_handler(int sig) { + pid_t pid; int save_errno = errno; int status; - while (waitpid(-1, &status, WNOHANG) > 0) + while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || + (pid < 0 && errno == EINTR)) ; signal(SIGCHLD, main_sigchld_handler); @@ -254,9 +295,11 @@ main_sigchld_handler(int sig) /* * Signal handler for the alarm after the login grace period has expired. */ -void +static void grace_alarm_handler(int sig) { + /* XXX no idea how fix this signal handler */ + /* Close the connection. */ packet_close(); @@ -271,19 +314,30 @@ grace_alarm_handler(int sig) * Thus there should be no concurrency control/asynchronous execution * problems. */ -void -generate_empheral_server_key(void) +static void +generate_ephemeral_server_key(void) { - log("Generating %s%d bit RSA key.", sensitive_data.server_key ? "new " : "", - options.server_key_bits); + u_int32_t rand = 0; + int i; + + verbose("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); + sensitive_data.server_key = key_generate(KEY_RSA1, + options.server_key_bits); + verbose("RSA key generation complete."); + + for (i = 0; i < SSH_SESSION_KEY_LENGTH; i++) { + if (i % 4 == 0) + rand = arc4random(); + sensitive_data.ssh1_cookie[i] = rand & 0xff; + rand >>= 8; + } arc4random_stir(); - log("RSA key generation complete."); } -void +static void key_regeneration_alarm(int sig) { int save_errno = errno; @@ -292,7 +346,7 @@ key_regeneration_alarm(int sig) key_do_regen = 1; } -void +static void sshd_exchange_identification(int sock_in, int sock_out) { int i, mismatch; @@ -320,21 +374,20 @@ sshd_exchange_identification(int sock_in, int sock_out) /* Send our protocol version identification. */ if (atomicio(write, sock_out, server_version_string, strlen(server_version_string)) != strlen(server_version_string)) { - log("Could not write ident string to %s.", get_remote_ipaddr()); + log("Could not write ident string to %s", get_remote_ipaddr()); fatal_cleanup(); } /* Read other side's version identification. */ - memset(buf, 0, sizeof(buf)); + memset(buf, 0, sizeof(buf)); for (i = 0; i < sizeof(buf) - 1; i++) { if (atomicio(read, sock_in, &buf[i], 1) != 1) { - log("Did not receive identification string from %s.", + log("Did not receive identification string from %s", get_remote_ipaddr()); fatal_cleanup(); } if (buf[i] == '\r') { - buf[i] = '\n'; - buf[i + 1] = 0; + buf[i] = 0; /* Kludge for F-Secure Macintosh < 1.0.2 */ if (i == 12 && strncmp(buf, "SSH-1.5-W1.0", 12) == 0) @@ -342,8 +395,7 @@ sshd_exchange_identification(int sock_in, int sock_out) continue; } if (buf[i] == '\n') { - /* buf[i] == '\n' */ - buf[i + 1] = 0; + buf[i] = 0; break; } } @@ -366,12 +418,18 @@ sshd_exchange_identification(int sock_in, int sock_out) fatal_cleanup(); } debug("Client protocol version %d.%d; client software version %.100s", - remote_major, remote_minor, remote_version); + remote_major, remote_minor, 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; - switch(remote_major) { + switch (remote_major) { case 1: if (remote_minor == 99) { if (options.protocol & SSH_PROTO_2) @@ -403,7 +461,6 @@ sshd_exchange_identification(int sock_in, int sock_out) break; } chop(server_version_string); - chop(client_version_string); debug("Local version string %.200s", server_version_string); if (mismatch) { @@ -416,8 +473,6 @@ sshd_exchange_identification(int sock_in, int sock_out) server_version_string, client_version_string); fatal_cleanup(); } - if (compat20) - packet_set_ssh2_format(); } @@ -431,78 +486,204 @@ destroy_sensitive_data(void) key_free(sensitive_data.server_key); sensitive_data.server_key = NULL; } - for(i = 0; i < options.num_host_key_files; i++) { + 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) + +/* Demote private to public keys for network child */ +void +demote_sensitive_data(void) { - struct stat st; - int type; - Key *public, *private; + Key *tmp; + int i; - if (stat(filename, &st) < 0) { - perror(filename); - return NULL; + if (sensitive_data.server_key) { + tmp = key_demote(sensitive_data.server_key); + key_free(sensitive_data.server_key); + sensitive_data.server_key = tmp; } - /* - * 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; + + for (i = 0; i < options.num_host_key_files; i++) { + if (sensitive_data.host_keys[i]) { + tmp = key_demote(sensitive_data.host_keys[i]); + key_free(sensitive_data.host_keys[i]); + sensitive_data.host_keys[i] = tmp; + if (tmp->type == KEY_RSA1) + sensitive_data.ssh1_host_key = tmp; + } + } + + /* We do not clear ssh1_host key and cookie. XXX - Okay Niels? */ +} + +static void +privsep_preauth_child(void) +{ + u_int32_t rand[256]; + int i; + struct passwd *pw; + + /* Enable challenge-response authentication for privilege separation */ + privsep_challenge_enable(); + + for (i = 0; i < 256; i++) + rand[i] = arc4random(); + RAND_seed(rand, sizeof(rand)); + + /* Demote the private keys to public keys. */ + demote_sensitive_data(); + + if ((pw = getpwnam(SSH_PRIVSEP_USER)) == NULL) + fatal("Privilege separation user %s does not exist", + SSH_PRIVSEP_USER); + memset(pw->pw_passwd, 0, strlen(pw->pw_passwd)); + endpwent(); + + /* Change our root directory*/ + if (chroot(_PATH_PRIVSEP_CHROOT_DIR) == -1) + fatal("chroot(\"%s\"): %s", _PATH_PRIVSEP_CHROOT_DIR, + strerror(errno)); + if (chdir("/") == -1) + fatal("chdir(\"/\"): %s", strerror(errno)); + + /* Drop our privileges */ + debug3("privsep user:group %u:%u", (u_int)pw->pw_uid, + (u_int)pw->pw_gid); + do_setusercontext(pw); +} + +static Authctxt* +privsep_preauth(void) +{ + Authctxt *authctxt = NULL; + int status; + pid_t pid; + + /* Set up unprivileged child process to deal with network data */ + monitor = monitor_init(); + /* Store a pointer to the kex for later rekeying */ + monitor->m_pkex = &xxx_kex; + + pid = fork(); + if (pid == -1) { + fatal("fork of unprivileged child failed"); + } else if (pid != 0) { + debug2("Network child is on pid %d", pid); + + close(monitor->m_recvfd); + authctxt = monitor_child_preauth(monitor); + close(monitor->m_sendfd); + + /* Sync memory */ + monitor_sync(monitor); + + /* Wait for the child's exit status */ + while (waitpid(pid, &status, 0) < 0) + if (errno != EINTR) + break; + return (authctxt); + } else { + /* child */ + + close(monitor->m_sendfd); + + /* Demote the child */ + if (getuid() == 0 || geteuid() == 0) + privsep_preauth_child(); + setproctitle("%s", "[net]"); } - 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; + return (NULL); +} + +static void +privsep_postauth(Authctxt *authctxt) +{ + extern Authctxt *x_authctxt; + + /* XXX - Remote port forwarding */ + x_authctxt = authctxt; + + if (authctxt->pw->pw_uid == 0 || options.use_login) { + /* File descriptor passing is broken or root login */ + monitor_apply_keystate(monitor); + use_privsep = 0; + return; } - key_free(private); - return NULL; + + /* Authentication complete */ + alarm(0); + if (startup_pipe != -1) { + close(startup_pipe); + startup_pipe = -1; + } + + /* New socket pair */ + monitor_reinit(monitor); + + monitor->m_pid = fork(); + if (monitor->m_pid == -1) + fatal("fork of unprivileged child failed"); + else if (monitor->m_pid != 0) { + debug2("User child is on pid %d", monitor->m_pid); + close(monitor->m_recvfd); + monitor_child_postauth(monitor); + + /* NEVERREACHED */ + exit(0); + } + + close(monitor->m_sendfd); + + /* Demote the private keys to public keys. */ + demote_sensitive_data(); + + /* Drop privileges */ + do_setusercontext(authctxt->pw); + + /* It is safe now to apply the key state */ + monitor_apply_keystate(monitor); } -char * +static char * list_hostkey_types(void) { - static char buf[1024]; + Buffer b; + char *p; int i; - buf[0] = '\0'; - for(i = 0; i < options.num_host_key_files; i++) { + + buffer_init(&b); + for (i = 0; i < options.num_host_key_files; i++) { Key *key = sensitive_data.host_keys[i]; if (key == NULL) continue; - switch(key->type) { + switch (key->type) { case KEY_RSA: case KEY_DSA: - strlcat(buf, key_ssh_name(key), sizeof buf); - strlcat(buf, ",", sizeof buf); + if (buffer_len(&b) > 0) + buffer_append(&b, ",", 1); + p = key_ssh_name(key); + buffer_append(&b, p, strlen(p)); break; } } - i = strlen(buf); - if (i > 0 && buf[i-1] == ',') - buf[i-1] = '\0'; - debug("list_hostkey_types: %s", buf); - return buf; + buffer_append(&b, "\0", 1); + p = xstrdup(buffer_ptr(&b)); + buffer_free(&b); + debug("list_hostkey_types: %s", p); + return p; } Key * get_hostkey_by_type(int type) { int i; - for(i = 0; i < options.num_host_key_files; 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; @@ -510,13 +691,32 @@ get_hostkey_by_type(int type) return NULL; } +Key * +get_hostkey_by_index(int ind) +{ + if (ind < 0 || ind >= options.num_host_key_files) + return (NULL); + return (sensitive_data.host_keys[ind]); +} + +int +get_hostkey_index(Key *key) +{ + int i; + for (i = 0; i < options.num_host_key_files; i++) { + if (key == sensitive_data.host_keys[i]) + return (i); + } + return (-1); +} + /* * 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 +static int drop_connection(int startups) { double p, r; @@ -539,8 +739,30 @@ drop_connection(int startups) return (r < p) ? 1 : 0; } -int *startup_pipes = NULL; /* options.max_startup sized array of fd ints */ -int startup_pipe; /* in child */ +static void +usage(void) +{ + fprintf(stderr, "sshd version %s\n", SSH_VERSION); + fprintf(stderr, "Usage: %s [options]\n", __progname); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -f file Configuration file (default %s)\n", _PATH_SERVER_CONFIG_FILE); + fprintf(stderr, " -d Debugging mode (multiple -d means more debugging)\n"); + fprintf(stderr, " -i Started from inetd\n"); + fprintf(stderr, " -D Do not fork into daemon mode\n"); + fprintf(stderr, " -t Only test configuration file and keys\n"); + fprintf(stderr, " -q Quiet (no logging)\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, " -g seconds Grace period for authentication (default: 600)\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", + _PATH_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"); + fprintf(stderr, " -o option Process the option as if it was read from a configuration file.\n"); + exit(1); +} /* * Main program for the daemon. @@ -564,8 +786,13 @@ main(int ac, char **av) int listen_sock, maxfd; int startup_p[2]; int startups = 0; + Authctxt *authctxt; + Key *key; int ret, key_used = 0; +#ifdef HAVE_SECUREWARE + (void)set_auth_parameters(ac, av); +#endif __progname = get_progname(av[0]); init_rng(); @@ -577,7 +804,7 @@ main(int ac, char **av) initialize_server_options(&options); /* 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:u:o:dDeiqtQ46")) != -1) { switch (opt) { case '4': IPv4or6 = AF_INET; @@ -602,6 +829,9 @@ main(int ac, char **av) case 'D': no_daemon_flag = 1; break; + case 'e': + log_stderr = 1; + break; case 'i': inetd_flag = 1; break; @@ -620,13 +850,23 @@ main(int ac, char **av) fprintf(stderr, "too many ports.\n"); exit(1); } - options.ports[options.num_ports++] = atoi(optarg); + options.ports[options.num_ports++] = a2port(optarg); + if (options.ports[options.num_ports-1] == 0) { + fprintf(stderr, "Bad port number.\n"); + exit(1); + } break; case 'g': - options.login_grace_time = atoi(optarg); + if ((options.login_grace_time = convtime(optarg)) == -1) { + fprintf(stderr, "Invalid login grace time.\n"); + exit(1); + } break; case 'k': - options.key_regeneration_time = atoi(optarg); + if ((options.key_regeneration_time = convtime(optarg)) == -1) { + fprintf(stderr, "Invalid key regeneration interval.\n"); + exit(1); + } break; case 'h': if (options.num_host_key_files >= MAX_HOSTKEYS) { @@ -640,41 +880,46 @@ main(int ac, char **av) /* only makes sense with inetd_flag, i.e. no listen() */ inetd_flag = 1; break; + case 't': + test_flag = 1; + break; case 'u': utmp_len = atoi(optarg); break; + case 'o': + if (process_server_config_line(&options, optarg, + "command-line", 0) != 0) + exit(1); + break; case '?': default: - fprintf(stderr, "sshd version %s\n", SSH_VERSION); - fprintf(stderr, "Usage: %s [options]\n", __progname); - fprintf(stderr, "Options:\n"); - fprintf(stderr, " -f file Configuration file (default %s)\n", _PATH_SERVER_CONFIG_FILE); - fprintf(stderr, " -d Debugging mode (multiple -d means more debugging)\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, " -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, " -g seconds Grace period for authentication (default: 600)\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", - _PATH_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); + usage(); + break; } } + SSLeay_add_all_algorithms(); + channel_set_af(IPv4or6); /* * Force logging to stderr until we have loaded the private host * key (unless started from inetd) */ log_init(__progname, - options.log_level == -1 ? SYSLOG_LEVEL_INFO : options.log_level, - options.log_facility == -1 ? SYSLOG_FACILITY_AUTH : options.log_facility, + options.log_level == SYSLOG_LEVEL_NOT_SET ? + SYSLOG_LEVEL_INFO : options.log_level, + options.log_facility == SYSLOG_FACILITY_NOT_SET ? + SYSLOG_FACILITY_AUTH : options.log_facility, !inetd_flag); +#ifdef _CRAY + /* Cray can define user privs drop all prives now! + * Not needed on PRIV_SU systems! + */ + drop_cray_privs(); +#endif + + seed_rng(); + /* Read server configuration options from the configuration file. */ read_server_config(&options, config_file_name); @@ -691,21 +936,23 @@ main(int ac, char **av) /* load private host keys */ sensitive_data.host_keys = xmalloc(options.num_host_key_files*sizeof(Key*)); - for(i = 0; i < options.num_host_key_files; i++) + 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++) { - Key *key = load_private_key_autodetect(options.host_key_files[i]); + for (i = 0; i < options.num_host_key_files; i++) { + key = key_load_private(options.host_key_files[i], "", NULL); + sensitive_data.host_keys[i] = key; if (key == NULL) { - error("Could not load host key: %.200s: %.100s", - options.host_key_files[i], strerror(errno)); + error("Could not load host key: %s", + options.host_key_files[i]); + sensitive_data.host_keys[i] = NULL; continue; } - switch(key->type){ + switch (key->type) { case KEY_RSA1: sensitive_data.ssh1_host_key = key; sensitive_data.have_ssh1_key = 1; @@ -715,7 +962,8 @@ main(int ac, char **av) sensitive_data.have_ssh2_key = 1; break; } - sensitive_data.host_keys[i] = key; + debug("private host key: #%d type %d %s", i, key->type, + key_type(key)); } if ((options.protocol & SSH_PROTO_1) && !sensitive_data.have_ssh1_key) { log("Disabling protocol version 1. Could not load host key"); @@ -726,7 +974,7 @@ main(int ac, char **av) options.protocol &= ~SSH_PROTO_2; } if (!(options.protocol & (SSH_PROTO_1|SSH_PROTO_2))) { - log("sshd: no hostkeys available -- exiting.\n"); + log("sshd: no hostkeys available -- exiting."); exit(1); } @@ -753,9 +1001,19 @@ main(int ac, char **av) } } -#ifdef HAVE_SCO_PROTECTED_PW - (void) set_auth_parameters(ac, av); -#endif + /* Configuration looks good, so exit if in test mode. */ + if (test_flag) + exit(0); + + /* + * Clear out any supplemental groups we may have inherited. This + * prevents inadvertent creation of files with bad modes (in the + * portable version at least, it's certainly possible for PAM + * to create a file, and we can't control the code in every + * module which might be used). + */ + if (setgroups(0, NULL) < 0) + debug("setgroups() failed: %.200s", strerror(errno)); /* Initialize the log (it is reinitialized below in case we forked). */ if (debug_flag && !inetd_flag) @@ -793,11 +1051,14 @@ main(int ac, char **av) unmounted if desired. */ chdir("/"); + /* ignore SIGPIPE */ + signal(SIGPIPE, SIG_IGN); + /* Start listening for a socket, unless started from inetd. */ if (inetd_flag) { - int s1, s2; + int s1; s1 = dup(0); /* Make sure descriptors 0, 1, and 2 are in use. */ - s2 = dup(s1); + dup(s1); sock_in = dup(0); sock_out = dup(1); startup_pipe = -1; @@ -808,7 +1069,7 @@ main(int ac, char **av) */ debug("inetd sockets after dupping: %d, %d", sock_in, sock_out); if (options.protocol & SSH_PROTO_1) - generate_empheral_server_key(); + generate_ephemeral_server_key(); } else { for (ai = options.listen_addrs; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) @@ -841,11 +1102,11 @@ main(int ac, char **av) * close. */ setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, - (void *) &on, sizeof(on)); + &on, sizeof(on)); linger.l_onoff = 1; linger.l_linger = 5; setsockopt(listen_sock, SOL_SOCKET, SO_LINGER, - (void *) &linger, sizeof(linger)); + &linger, sizeof(linger)); debug("Bind to port %s on %s.", strport, ntop); @@ -871,6 +1132,22 @@ main(int ac, char **av) if (!num_listen_socks) fatal("Cannot bind any address."); + if (options.protocol & SSH_PROTO_1) + generate_ephemeral_server_key(); + + /* + * Arrange to restart on SIGHUP. The handler needs + * listen_sock. + */ + signal(SIGHUP, sighup_handler); + + signal(SIGTERM, sigterm_handler); + signal(SIGQUIT, sigterm_handler); + + /* Arrange SIGCHLD to be caught. */ + signal(SIGCHLD, main_sigchld_handler); + + /* Write out the pid file after the sigterm handler is setup */ if (!debug_flag) { /* * Record our pid in /var/run/sshd.pid to make it @@ -885,17 +1162,6 @@ main(int ac, char **av) fclose(f); } } - if (options.protocol & SSH_PROTO_1) - generate_empheral_server_key(); - - /* Arrange to restart on SIGHUP. The handler needs listen_sock. */ - signal(SIGHUP, sighup_handler); - - signal(SIGTERM, sigterm_handler); - signal(SIGQUIT, sigterm_handler); - - /* Arrange SIGCHLD to be caught. */ - signal(SIGCHLD, main_sigchld_handler); /* setup fd set for listen */ fdset = NULL; @@ -931,8 +1197,15 @@ main(int ac, char **av) ret = select(maxfd+1, fdset, NULL, NULL, NULL); if (ret < 0 && errno != EINTR) error("select: %.100s", strerror(errno)); + if (received_sigterm) { + log("Received signal %d; terminating.", + (int) received_sigterm); + close_listen_socks(); + unlink(options.pid_file); + exit(255); + } if (key_used && key_do_regen) { - generate_empheral_server_key(); + generate_ephemeral_server_key(); key_used = 0; key_do_regen = 0; } @@ -965,6 +1238,7 @@ main(int ac, char **av) } if (fcntl(newsock, F_SETFL, 0) < 0) { error("newsock del O_NONBLOCK: %s", strerror(errno)); + close(newsock); continue; } if (drop_connection(startups) == 1) { @@ -1018,9 +1292,7 @@ main(int ac, char **av) * 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_startup_pipes(); close_listen_socks(); sock_in = newsock; sock_out = newsock; @@ -1080,11 +1352,11 @@ main(int ac, char **av) /* setsockopt(sock_in, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)); */ linger.l_onoff = 1; linger.l_linger = 5; - setsockopt(sock_in, SOL_SOCKET, SO_LINGER, (void *) &linger, sizeof(linger)); + setsockopt(sock_in, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)); /* Set keepalives if requested. */ if (options.keepalives && - setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, + setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) < 0) error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); @@ -1097,23 +1369,23 @@ main(int ac, char **av) remote_port = get_remote_port(); remote_ip = get_remote_ipaddr(); - /* Check whether logins are denied from this host. */ #ifdef LIBWRAP - /* XXX LIBWRAP noes not know about IPv6 */ + /* Check whether logins are denied from this host. */ { struct request_info req; - request_init(&req, RQ_DAEMON, __progname, RQ_FILE, sock_in, NULL); + request_init(&req, RQ_DAEMON, __progname, RQ_FILE, sock_in, 0); fromhost(&req); if (!hosts_access(&req)) { - close(sock_in); - close(sock_out); + debug("Connection refused by tcp wrapper"); refuse(&req); + /* NOTREACHED */ + fatal("libwrap refuse returns"); } -/*XXX IPv6 verbose("Connection from %.500s port %d", eval_client(&req), remote_port); */ } #endif /* LIBWRAP */ + /* Log the connection. */ verbose("Connection from %.500s port %d", remote_ip, remote_port); @@ -1137,19 +1409,20 @@ main(int ac, char **av) * 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) { + if (options.rhosts_authentication && + (remote_port >= IPPORT_RESERVED || + remote_port < IPPORT_RESERVED / 2)) { debug("Rhosts Authentication disabled, " - "originating port not trusted."); + "originating port %d not trusted.", remote_port); options.rhosts_authentication = 0; } -#ifdef KRB4 +#if defined(KRB4) && !defined(KRB5) if (!packet_connection_is_ipv4() && options.kerberos_authentication) { debug("Kerberos Authentication disabled, only available for IPv4."); options.kerberos_authentication = 0; } -#endif /* KRB4 */ +#endif /* KRB4 && !KRB5 */ #ifdef AFS /* If machine has AFS, set process authentication group. */ if (k_hasafs()) { @@ -1160,21 +1433,42 @@ main(int ac, char **av) packet_set_nonblocking(); + if (use_privsep) + if ((authctxt = privsep_preauth()) != NULL) + goto authenticated; + /* perform the key exchange */ /* authenticate user and start session */ if (compat20) { do_ssh2_kex(); - do_authentication2(); + authctxt = do_authentication2(); } else { do_ssh1_kex(); - do_authentication(); + authctxt = do_authentication(); + } + /* + * If we use privilege separation, the unprivileged child transfers + * the current keystate and exits + */ + if (use_privsep) { + mm_send_keystate(monitor); + exit(0); + } + + authenticated: + /* + * In privilege separation, we fork another child and prepare + * file descriptor passing. + */ + if (use_privsep) { + privsep_postauth(authctxt); + /* the monitor process [priv] will not return */ + if (!compat20) + destroy_sensitive_data(); } -#ifdef KRB4 - /* Cleanup user's ticket cache file. */ - if (options.kerberos_ticket_cleanup) - (void) dest_tkt(); -#endif /* KRB4 */ + /* Perform session preparation. */ + do_authenticated(authctxt); /* The connection has been terminated. */ verbose("Closing connection to %.100s", remote_ip); @@ -1184,17 +1478,64 @@ main(int ac, char **av) #endif /* USE_PAM */ packet_close(); + + if (use_privsep) + mm_terminate(); + exit(0); } +/* + * Decrypt session_key_int using our private server key and private host key + * (key with larger modulus first). + */ +int +ssh1_session_key(BIGNUM *session_key_int) +{ + int rsafail = 0; + + if (BN_cmp(sensitive_data.server_key->rsa->n, sensitive_data.ssh1_host_key->rsa->n) > 0) { + /* Server key has bigger modulus. */ + 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); + } + if (rsa_private_decrypt(session_key_int, session_key_int, + sensitive_data.server_key->rsa) <= 0) + rsafail++; + if (rsa_private_decrypt(session_key_int, session_key_int, + sensitive_data.ssh1_host_key->rsa) <= 0) + rsafail++; + } else { + /* Host key has bigger modulus (or they are equal). */ + 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); + } + if (rsa_private_decrypt(session_key_int, session_key_int, + sensitive_data.ssh1_host_key->rsa) < 0) + rsafail++; + if (rsa_private_decrypt(session_key_int, session_key_int, + sensitive_data.server_key->rsa) < 0) + rsafail++; + } + return (rsafail); +} /* * SSH1 key exchange */ -void +static void do_ssh1_kex(void) { int i, len; - int plen, slen; int rsafail = 0; BIGNUM *session_key_int; u_char session_key[SSH_SESSION_KEY_LENGTH]; @@ -1251,17 +1592,19 @@ do_ssh1_kex(void) auth_mask |= 1 << SSH_AUTH_RHOSTS_RSA; if (options.rsa_authentication) auth_mask |= 1 << SSH_AUTH_RSA; -#ifdef KRB4 +#if defined(KRB4) || defined(KRB5) if (options.kerberos_authentication) auth_mask |= 1 << SSH_AUTH_KERBEROS; #endif -#ifdef AFS +#if defined(AFS) || defined(KRB5) if (options.kerberos_tgt_passing) auth_mask |= 1 << SSH_PASS_KERBEROS_TGT; +#endif +#ifdef AFS if (options.afs_token_passing) auth_mask |= 1 << SSH_PASS_AFS_TOKEN; #endif - if (options.challenge_reponse_authentication == 1) + if (options.challenge_response_authentication == 1) auth_mask |= 1 << SSH_AUTH_TIS; if (options.password_authentication) auth_mask |= 1 << SSH_AUTH_PASSWORD; @@ -1276,7 +1619,7 @@ do_ssh1_kex(void) 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); + packet_read_expect(SSH_CMSG_SESSION_KEY); /* Get cipher type and check whether we accept this. */ cipher_type = packet_get_char(); @@ -1293,58 +1636,16 @@ do_ssh1_kex(void) debug("Encryption type: %.200s", cipher_name(cipher_type)); /* Get the encrypted integer. */ - session_key_int = BN_new(); - packet_get_bignum(session_key_int, &slen); + if ((session_key_int = BN_new()) == NULL) + fatal("do_ssh1_kex: BN_new failed"); + packet_get_bignum(session_key_int); protocol_flags = packet_get_int(); packet_set_protocol_flags(protocol_flags); + packet_check_eom(); - packet_integrity_check(plen, 1 + 8 + slen + 4, SSH_CMSG_SESSION_KEY); - - /* - * Decrypt it using our private server key and private host key (key - * with larger modulus first). - */ - if (BN_cmp(sensitive_data.server_key->rsa->n, sensitive_data.ssh1_host_key->rsa->n) > 0) { - /* Server key has bigger modulus. */ - 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); - } - if (rsa_private_decrypt(session_key_int, session_key_int, - sensitive_data.server_key->rsa) <= 0) - rsafail++; - if (rsa_private_decrypt(session_key_int, session_key_int, - sensitive_data.ssh1_host_key->rsa) <= 0) - rsafail++; - } else { - /* Host key has bigger modulus (or they are equal). */ - 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); - } - if (rsa_private_decrypt(session_key_int, session_key_int, - sensitive_data.ssh1_host_key->rsa) < 0) - rsafail++; - if (rsa_private_decrypt(session_key_int, session_key_int, - sensitive_data.server_key->rsa) < 0) - rsafail++; - } - - compute_session_id(session_id, cookie, - 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. */ - destroy_sensitive_data(); + /* Decrypt session_key_int using host/server keys */ + rsafail = PRIVSEP(ssh1_session_key(session_key_int)); /* * Extract session key from the decrypted integer. The key is in the @@ -1363,24 +1664,48 @@ do_ssh1_kex(void) 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); + u_char *buf = xmalloc(bytes); + MD5_CTX md; + log("do_connection: generating a fake encryption key"); - for (i = 0; i < SSH_SESSION_KEY_LENGTH; i++) { - if (i % 4 == 0) - rand = arc4random(); - session_key[i] = rand & 0xff; - rand >>= 8; - } + 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. No longer. */ + destroy_sensitive_data(); + + if (use_privsep) + mm_ssh1_session_id(session_id); + /* Destroy the decrypted integer. It is no longer needed. */ 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. */ packet_set_encryption_key(session_key, SSH_SESSION_KEY_LENGTH, cipher_type); @@ -1398,58 +1723,40 @@ do_ssh1_kex(void) /* * SSH2 key exchange: diffie-hellman-group1-sha1 */ -void +static void do_ssh2_kex(void) { - Buffer *server_kexinit; - Buffer *client_kexinit; - int payload_len; - int i; Kex *kex; - char *cprop[PROPOSAL_MAX]; - -/* KEXINIT */ if (options.ciphers != NULL) { myproposal[PROPOSAL_ENC_ALGS_CTOS] = myproposal[PROPOSAL_ENC_ALGS_STOC] = options.ciphers; } + myproposal[PROPOSAL_ENC_ALGS_CTOS] = + compat_cipher_proposal(myproposal[PROPOSAL_ENC_ALGS_CTOS]); + myproposal[PROPOSAL_ENC_ALGS_STOC] = + compat_cipher_proposal(myproposal[PROPOSAL_ENC_ALGS_STOC]); + 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); - client_kexinit = xmalloc(sizeof(*client_kexinit)); - buffer_init(client_kexinit); + /* start key exchange */ + kex = kex_setup(myproposal); + kex->server = 1; + kex->client_version_string=client_version_string; + kex->server_version_string=server_version_string; + kex->load_host_key=&get_hostkey_by_type; + kex->host_key_index=&get_hostkey_index; - /* algorithm negotiation */ - kex_exchange_kexinit(server_kexinit, client_kexinit, cprop); - kex = kex_choose_conf(cprop, myproposal, 1); - for (i = 0; i < PROPOSAL_MAX; i++) - xfree(cprop[i]); + xxx_kex = kex; - 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."); + dispatch_run(DISPATCH_BLOCK, &kex->done, kex); - debug("Wait SSH2_MSG_NEWKEYS."); - packet_read_expect(&payload_len, SSH2_MSG_NEWKEYS); - debug("GOT SSH2_MSG_NEWKEYS."); + session_id2 = kex->session_id; + session_id2_len = kex->session_id_len; #ifdef DEBUG_KEXDH /* send 1st encrypted/maced/compressed message */ @@ -1458,285 +1765,5 @@ do_ssh2_kex(void) packet_send(); packet_write_wait(); #endif - - debug("done: KEX2."); -} - -/* - * SSH2 key exchange - */ - -/* diffie-hellman-group1-sha1 */ - -void -ssh_dh1_server(Kex *kex, Buffer *client_kexinit, Buffer *server_kexinit) -{ -#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 */ - /* 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); - - /* 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 - 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( - 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, - 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_KEXDH_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(); - - kex_derive_keys(kex, hash, shared_secret); - BN_clear_free(shared_secret); - packet_set_kex(kex); - - /* have keys, free DH */ - DH_free(dh); -} - -/* 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; - 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_write_wait(); - - /* 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 - 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); - 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(); - xfree(signature); - xfree(server_host_key_blob); - packet_write_wait(); - - kex_derive_keys(kex, hash, shared_secret); - BN_clear_free(shared_secret); - packet_set_kex(kex); - - /* have keys, free DH */ - DH_free(dh); + debug("KEX done"); }