version 1.206.2.2, 2002/03/07 17:37:48 |
version 1.206.2.3, 2002/05/17 00:03:25 |
|
|
* called by a name other than "ssh" or "Secure Shell". |
* called by a name other than "ssh" or "Secure Shell". |
* |
* |
* SSH2 implementation: |
* 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 |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* modification, are permitted provided that the following conditions |
|
|
#include <openssl/dh.h> |
#include <openssl/dh.h> |
#include <openssl/bn.h> |
#include <openssl/bn.h> |
#include <openssl/md5.h> |
#include <openssl/md5.h> |
|
#include <openssl/rand.h> |
|
|
#include "ssh.h" |
#include "ssh.h" |
#include "ssh1.h" |
#include "ssh1.h" |
|
|
#include "misc.h" |
#include "misc.h" |
#include "dispatch.h" |
#include "dispatch.h" |
#include "channels.h" |
#include "channels.h" |
|
#include "session.h" |
|
#include "monitor_mm.h" |
|
#include "monitor.h" |
|
#include "monitor_wrap.h" |
|
#include "monitor_fdpass.h" |
|
|
#ifdef LIBWRAP |
#ifdef LIBWRAP |
#include <tcpd.h> |
#include <tcpd.h> |
|
|
int *startup_pipes = NULL; |
int *startup_pipes = NULL; |
int startup_pipe; /* in child */ |
int startup_pipe; /* in child */ |
|
|
|
/* variables used for privilege separation */ |
|
extern struct monitor *pmonitor; |
|
extern int use_privsep; |
|
|
/* Prototypes for various functions defined later in this file. */ |
/* Prototypes for various functions defined later in this file. */ |
void destroy_sensitive_data(void); |
void destroy_sensitive_data(void); |
|
void demote_sensitive_data(void); |
|
|
static void do_ssh1_kex(void); |
static void do_ssh1_kex(void); |
static void do_ssh2_kex(void); |
static void do_ssh2_kex(void); |
|
|
static void |
static void |
main_sigchld_handler(int sig) |
main_sigchld_handler(int sig) |
{ |
{ |
|
pid_t pid; |
int save_errno = errno; |
int save_errno = errno; |
int status; |
int status; |
|
|
while (waitpid(-1, &status, WNOHANG) > 0) |
while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || |
|
(pid < 0 && errno == EINTR)) |
; |
; |
|
|
signal(SIGCHLD, main_sigchld_handler); |
signal(SIGCHLD, main_sigchld_handler); |
|
|
memset(sensitive_data.ssh1_cookie, 0, SSH_SESSION_KEY_LENGTH); |
memset(sensitive_data.ssh1_cookie, 0, SSH_SESSION_KEY_LENGTH); |
} |
} |
|
|
|
/* Demote private to public keys for network child */ |
|
void |
|
demote_sensitive_data(void) |
|
{ |
|
Key *tmp; |
|
int i; |
|
|
|
if (sensitive_data.server_key) { |
|
tmp = key_demote(sensitive_data.server_key); |
|
key_free(sensitive_data.server_key); |
|
sensitive_data.server_key = tmp; |
|
} |
|
|
|
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 */ |
|
pmonitor = monitor_init(); |
|
/* Store a pointer to the kex for later rekeying */ |
|
pmonitor->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(pmonitor->m_recvfd); |
|
authctxt = monitor_child_preauth(pmonitor); |
|
close(pmonitor->m_sendfd); |
|
|
|
/* Sync memory */ |
|
monitor_sync(pmonitor); |
|
|
|
/* Wait for the child's exit status */ |
|
while (waitpid(pid, &status, 0) < 0) |
|
if (errno != EINTR) |
|
break; |
|
return (authctxt); |
|
} else { |
|
/* child */ |
|
|
|
close(pmonitor->m_sendfd); |
|
|
|
/* Demote the child */ |
|
if (getuid() == 0 || geteuid() == 0) |
|
privsep_preauth_child(); |
|
setproctitle("%s", "[net]"); |
|
} |
|
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(pmonitor); |
|
use_privsep = 0; |
|
return; |
|
} |
|
|
|
/* Authentication complete */ |
|
alarm(0); |
|
if (startup_pipe != -1) { |
|
close(startup_pipe); |
|
startup_pipe = -1; |
|
} |
|
|
|
/* New socket pair */ |
|
monitor_reinit(pmonitor); |
|
|
|
pmonitor->m_pid = fork(); |
|
if (pmonitor->m_pid == -1) |
|
fatal("fork of unprivileged child failed"); |
|
else if (pmonitor->m_pid != 0) { |
|
debug2("User child is on pid %d", pmonitor->m_pid); |
|
close(pmonitor->m_recvfd); |
|
monitor_child_postauth(pmonitor); |
|
|
|
/* NEVERREACHED */ |
|
exit(0); |
|
} |
|
|
|
close(pmonitor->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(pmonitor); |
|
} |
|
|
static char * |
static char * |
list_hostkey_types(void) |
list_hostkey_types(void) |
{ |
{ |
|
|
return p; |
return p; |
} |
} |
|
|
static Key * |
Key * |
get_hostkey_by_type(int type) |
get_hostkey_by_type(int type) |
{ |
{ |
int i; |
int i; |
|
|
return NULL; |
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. |
* returns 1 if connection should be dropped, 0 otherwise. |
* dropping starts at connection #max_startups_begin with a probability |
* dropping starts at connection #max_startups_begin with a probability |
|
|
int listen_sock, maxfd; |
int listen_sock, maxfd; |
int startup_p[2]; |
int startup_p[2]; |
int startups = 0; |
int startups = 0; |
|
Authctxt *authctxt; |
Key *key; |
Key *key; |
int ret, key_used = 0; |
int ret, key_used = 0; |
|
|
|
|
/* This is the child processing a new connection. */ |
/* This is the child processing a new connection. */ |
|
|
/* |
/* |
|
* Create a new session and process group since the 4.4BSD |
|
* setlogin() affects the entire process group. We don't |
|
* want the child to be able to affect the parent. |
|
*/ |
|
if (setsid() < 0) |
|
error("setsid: %.100s", strerror(errno)); |
|
|
|
/* |
* Disable the key regeneration alarm. We will not regenerate the |
* Disable the key regeneration alarm. We will not regenerate the |
* key since we are no longer in a position to give it to anyone. We |
* key since we are no longer in a position to give it to anyone. We |
* will not restart on SIGHUP since it no longer makes sense. |
* will not restart on SIGHUP since it no longer makes sense. |
|
|
|
|
packet_set_nonblocking(); |
packet_set_nonblocking(); |
|
|
|
if (use_privsep) |
|
if ((authctxt = privsep_preauth()) != NULL) |
|
goto authenticated; |
|
|
/* perform the key exchange */ |
/* perform the key exchange */ |
/* authenticate user and start session */ |
/* authenticate user and start session */ |
if (compat20) { |
if (compat20) { |
do_ssh2_kex(); |
do_ssh2_kex(); |
do_authentication2(); |
authctxt = do_authentication2(); |
} else { |
} else { |
do_ssh1_kex(); |
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(pmonitor); |
|
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(); |
|
} |
|
|
|
/* Perform session preparation. */ |
|
do_authenticated(authctxt); |
|
|
/* The connection has been terminated. */ |
/* The connection has been terminated. */ |
verbose("Closing connection to %.100s", remote_ip); |
verbose("Closing connection to %.100s", remote_ip); |
packet_close(); |
packet_close(); |
|
|
|
if (use_privsep) |
|
mm_terminate(); |
|
|
exit(0); |
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 |
* SSH1 key exchange |
*/ |
*/ |
static void |
static void |
|
|
packet_set_protocol_flags(protocol_flags); |
packet_set_protocol_flags(protocol_flags); |
packet_check_eom(); |
packet_check_eom(); |
|
|
|
/* Decrypt session_key_int using host/server keys */ |
|
rsafail = PRIVSEP(ssh1_session_key(session_key_int)); |
|
|
/* |
/* |
* 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++; |
|
} |
|
/* |
|
* Extract session key from the decrypted integer. The key is in the |
* Extract session key from the decrypted integer. The key is in the |
* least significant 256 bits of the integer; the first byte of the |
* least significant 256 bits of the integer; the first byte of the |
* key is in the highest bits. |
* key is in the highest bits. |
|
|
for (i = 0; i < 16; i++) |
for (i = 0; i < 16; i++) |
session_id[i] = session_key[i] ^ session_key[i + 16]; |
session_id[i] = session_key[i] ^ session_key[i + 16]; |
} |
} |
/* Destroy the private and public keys. They will no longer be needed. */ |
/* Destroy the private and public keys. No longer. */ |
destroy_sensitive_data(); |
destroy_sensitive_data(); |
|
|
|
if (use_privsep) |
|
mm_ssh1_session_id(session_id); |
|
|
/* Destroy the decrypted integer. It is no longer needed. */ |
/* Destroy the decrypted integer. It is no longer needed. */ |
BN_clear_free(session_key_int); |
BN_clear_free(session_key_int); |
|
|
|
|
kex->client_version_string=client_version_string; |
kex->client_version_string=client_version_string; |
kex->server_version_string=server_version_string; |
kex->server_version_string=server_version_string; |
kex->load_host_key=&get_hostkey_by_type; |
kex->load_host_key=&get_hostkey_by_type; |
|
kex->host_key_index=&get_hostkey_index; |
|
|
xxx_kex = kex; |
xxx_kex = kex; |
|
|