X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/8ce6434545b07ac2fa57de569faf6086483cd4e8..3dc1102ec55da4c4f412db1224270bd3e60962cc:/sshd.c diff --git a/sshd.c b/sshd.c index bb5685dc..d1ed1506 100644 --- a/sshd.c +++ b/sshd.c @@ -8,10 +8,13 @@ * 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. + * + * SSH2 implementation, + * Copyright (c) 2000 Markus Friedl. All rights reserved. */ #include "includes.h" -RCSID("$OpenBSD: sshd.c,v 1.97 2000/04/04 21:37:27 markus Exp $"); +RCSID("$OpenBSD: sshd.c,v 1.115 2000/05/03 10:21:49 markus Exp $"); #include "xmalloc.h" #include "rsa.h" @@ -25,23 +28,19 @@ RCSID("$OpenBSD: sshd.c,v 1.97 2000/04/04 21:37:27 markus Exp $"); #include "compat.h" #include "buffer.h" -#ifdef HAVE_OPENSSL -# include -# include -# include -# include -# include -#endif -#ifdef HAVE_SSL -# include -# include -# include -# include -# include -#endif +#include "ssh2.h" +#include +#include +#include +#include "kex.h" +#include +#include #include "key.h" +#include "dsa.h" #include "auth.h" +#include "myproposal.h" +#include "authfile.h" #ifdef LIBWRAP #include @@ -60,7 +59,7 @@ 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. */ @@ -114,8 +113,9 @@ char *server_version_string = NULL; * not very useful. Currently, memory locking is not implemented. */ struct { - RSA *private_key; /* Private part of server key. */ + RSA *private_key; /* Private part of empheral server key. */ RSA *host_key; /* Private part of host key. */ + Key *dsa_host_key; /* Private DSA host key. */ } sensitive_data; /* @@ -134,8 +134,13 @@ 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; + /* Prototypes for various functions defined later in this file. */ void do_ssh1_kex(); +void do_ssh2_kex(); /* * Close all listening sockets @@ -154,7 +159,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; @@ -165,7 +170,7 @@ sighup_handler(int sig) * Called from the main program after receiving SIGHUP. * Restarts the server. */ -void +void sighup_restart() { log("Received SIGHUP; restarting."); @@ -180,11 +185,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); } @@ -192,7 +198,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; @@ -208,7 +214,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. */ @@ -225,7 +231,8 @@ 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 key_regeneration_alarm(int sig) { int save_errno = errno; @@ -255,17 +262,43 @@ 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) { - int i; + int i, mismatch; int remote_major, remote_minor; + int major, minor; char *s; char buf[256]; /* Must not be larger than remote_version. */ char remote_version[256]; /* Must be at least as big as buf. */ - snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s\n", - PROTOCOL_MAJOR, PROTOCOL_MINOR, SSH_VERSION); + if ((options.protocol & SSH_PROTO_1) && + (options.protocol & SSH_PROTO_2)) { + major = PROTOCOL_MAJOR_1; + minor = 99; + } else if (options.protocol & SSH_PROTO_2) { + major = PROTOCOL_MAJOR_2; + minor = PROTOCOL_MINOR_2; + } else { + major = PROTOCOL_MAJOR_1; + minor = PROTOCOL_MINOR_1; + } + snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s\n", major, minor, SSH_VERSION); server_version_string = xstrdup(buf); if (client_version_string == NULL) { @@ -286,7 +319,6 @@ sshd_exchange_identification(int sock_in, int sock_out) buf[i] = '\n'; buf[i + 1] = 0; continue; - /*break; XXX eat \r */ } if (buf[i] == '\n') { /* buf[i] == '\n' */ @@ -304,7 +336,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); @@ -315,8 +347,22 @@ sshd_exchange_identification(int sock_in, int sock_out) debug("Client protocol version %d.%d; client software version %.100s", remote_major, remote_minor, remote_version); + compat_datafellows(remote_version); + + 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" "is no longer supported. Please install a newer version."); @@ -325,16 +371,44 @@ sshd_exchange_identification(int sock_in, int sock_out) enable_compat13(); } break; - default: + case 2: + if (options.protocol & SSH_PROTO_2) { + enable_compat20(); + break; + } + /* FALLTHROUGH */ + default: + mismatch = 1; + break; + } + chop(server_version_string); + chop(client_version_string); + debug("Local version string %.200s", server_version_string); + + if (mismatch) { s = "Protocol major versions differ.\n"; (void) atomicio(write, sock_out, s, strlen(s)); close(sock_in); close(sock_out); - log("Protocol major versions differ for %s: %d vs. %d", - get_remote_ipaddr(), PROTOCOL_MAJOR, remote_major); + log("Protocol major versions differ for %s: %.200s vs. %.200s", + get_remote_ipaddr(), + server_version_string, client_version_string); fatal_cleanup(); - break; } + if (compat20) + packet_set_ssh2_format(); +} + + +void +destroy_sensitive_data(void) +{ + /* 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); + if (sensitive_data.dsa_host_key != NULL) + key_free(sensitive_data.dsa_host_key); } /* @@ -345,14 +419,14 @@ 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, 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; @@ -389,7 +463,7 @@ main(int ac, char **av) inetd_flag = 1; break; case 'Q': - silentrsa = 1; + silent = 1; break; case 'q': options.log_level = SYSLOG_LEVEL_QUIET; @@ -445,27 +519,14 @@ main(int ac, char **av) log_init(av0, 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]); @@ -474,26 +535,80 @@ 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)); + sensitive_data.dsa_host_key = NULL; + sensitive_data.host_key = NULL; + + /* check if RSA support exists */ + if ((options.protocol & SSH_PROTO_1) && + rsa_alive() == 0) { + log("no RSA support in libssl and libcrypto. See ssl(8)"); + log("Disabling protocol version 1"); + options.protocol &= ~SSH_PROTO_1; + } + /* Load the RSA/DSA host key. It must have empty passphrase. */ + if (options.protocol & SSH_PROTO_1) { + Key k; + sensitive_data.host_key = RSA_new(); + k.type = KEY_RSA; + k.rsa = sensitive_data.host_key; + errno = 0; + if (!load_private_key(options.host_key_file, "", &k, NULL)) { + error("Could not load host key: %.200s: %.100s", + options.host_key_file, strerror(errno)); + log("Disabling protocol version 1"); + options.protocol &= ~SSH_PROTO_1; + } + k.rsa = NULL; + } + if (options.protocol & SSH_PROTO_2) { + sensitive_data.dsa_host_key = key_new(KEY_DSA); + if (!load_private_key(options.host_dsa_key_file, "", sensitive_data.dsa_host_key, NULL)) { + + error("Could not load DSA host key: %.200s", options.host_dsa_key_file); + log("Disabling protocol version 2"); + 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.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); + } + } + + /* 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); - /* If not in debugging mode, and not started from inetd, - disconnect from the controlling terminal, and fork. The - original process exits. */ + /* + * 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) { #ifdef TIOCNOTTY int fd; @@ -513,18 +628,6 @@ main(int ac, char **av) /* 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); @@ -542,19 +645,22 @@ 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. */ + /* + * 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(); - - 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) { + public_key = RSA_new(); + sensitive_data.private_key = RSA_new(); + log("Generating %d bit RSA key.", options.server_key_bits); + rsa_generate_key(sensitive_data.private_key, public_key, + options.server_key_bits); + arc4random_stir(); + log("RSA key generation complete."); + } } else { for (ai = options.listen_addrs; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) @@ -625,25 +731,26 @@ main(int ac, char **av) * 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, "w"); if (f) { fprintf(f, "%u\n", (unsigned int) getpid()); fclose(f); } } + if (options.protocol & SSH_PROTO_1) { + public_key = RSA_new(); + sensitive_data.private_key = RSA_new(); - 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."); + 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); @@ -658,8 +765,8 @@ main(int ac, char **av) 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); + fdsetsz = howmany(maxfd, NFDBITS) * sizeof(fd_mask); + fdset = (fd_set *)xmalloc(fdsetsz); /* * Stay listening for connections until the system crashes or @@ -837,9 +944,14 @@ main(int ac, char **av) packet_set_nonblocking(); /* perform the key exchange */ - do_ssh1_kex(); /* authenticate user and start session */ - do_authentication(); + if (compat20) { + do_ssh2_kex(); + do_authentication2(); + } else { + do_ssh1_kex(); + do_authentication(); + } #ifdef KRB4 /* Cleanup user's ticket cache file. */ @@ -952,7 +1064,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() & (1 << cipher_type))) packet_disconnect("Warning: client selects unsupported cipher."); /* Get check bytes from the packet. These must match those we @@ -1011,9 +1123,7 @@ do_ssh1_kex() sensitive_data.private_key->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 @@ -1049,3 +1159,197 @@ do_ssh1_kex() packet_send(); packet_write_wait(); } + +/* + * SSH2 key exchange: diffie-hellman-group1-sha1 + */ +void +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 i; + unsigned char *kbuf; + unsigned char *hash; + Kex *kex; + char *cprop[PROPOSAL_MAX]; + char *sprop[PROPOSAL_MAX]; + +/* KEXINIT */ + + if (options.ciphers != NULL) { + myproposal[PROPOSAL_ENC_ALGS_CTOS] = + myproposal[PROPOSAL_ENC_ALGS_STOC] = options.ciphers; + } + + debug("Sending KEX init."); + + 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)); + packet_send(); + packet_write_wait(); + + debug("done"); + + packet_read_expect(&payload_len, SSH2_MSG_KEXINIT); + + /* + * save raw KEXINIT payload in buffer. this is used during + * computation of the session_id and the session keys. + */ + client_kexinit = xmalloc(sizeof(*client_kexinit)); + buffer_init(client_kexinit); + ptr = packet_get_raw(&payload_len); + buffer_append(client_kexinit, ptr, payload_len); + + /* skip cookie */ + 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]); + } + + i = (int) packet_get_char(); + debug("first kex follow == %d", i); + i = packet_get_int(); + debug("reserved == %d", i); + + debug("done read kexinit"); + kex = kex_choose_conf(cprop, sprop, 1); + +/* KEXDH */ + + 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= "); + bignum_print(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); + fprintf(stderr, "\ng= "); + bignum_print(dh->g); + fprintf(stderr, "\npub= "); + bignum_print(dh->pub_key); + fprintf(stderr, "\n"); +#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? */ + dsa_make_key_blob(sensitive_data.dsa_host_key, &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); +#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 */ + dsa_sign(sensitive_data.dsa_host_key, &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); + packet_set_kex(kex); + + /* have keys, free DH */ + DH_free(dh); + + debug("send SSH2_MSG_NEWKEYS."); + packet_start(SSH2_MSG_NEWKEYS); + 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."); + +#ifdef DEBUG_KEXDH + /* send 1st encrypted/maced/compressed message */ + packet_start(SSH2_MSG_IGNORE); + packet_put_cstring("markus"); + packet_send(); + packet_write_wait(); +#endif + debug("done: KEX2."); +}