X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/e4cb16ddcd754840281ae01b3e554ada4672bdbd..edd0cb8aef57080ae884e06731a7892ca8cdba44:/ssh.c diff --git a/ssh.c b/ssh.c index 5493191e..45ecf260 100644 --- a/ssh.c +++ b/ssh.c @@ -459,6 +459,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, #define OUR_V2_WINSIZE 16384 #define OUR_V2_MAXPKT 0x4000UL +/* Maximum length of passwords/passphrases (arbitrary) */ +#define SSH_MAX_PASSWORD_LEN 100 + const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss }; const static struct ssh_mac *macs[] = { @@ -752,13 +755,6 @@ struct ssh_tag { int fallback_cmd; bufchain banner; /* accumulates banners during do_ssh2_authconn */ - /* - * Used for username and password input. - */ - char *userpass_input_buffer; - int userpass_input_buflen; - int userpass_input_bufpos; - int userpass_input_echo; int pkt_ctx; @@ -1046,27 +1042,28 @@ static int alloc_channel_id(Ssh ssh) return low + 1 + CHANNEL_NUMBER_OFFSET; } +static void c_write_stderr(int trusted, const char *buf, int len) +{ + int i; + for (i = 0; i < len; i++) + if (buf[i] != '\r' && (trusted || buf[i] & 0x60)) + fputc(buf[i], stderr); +} + static void c_write(Ssh ssh, const char *buf, int len) { - if ((flags & FLAG_STDERR)) { - int i; - for (i = 0; i < len; i++) - if (buf[i] != '\r') - fputc(buf[i], stderr); - return; - } - from_backend(ssh->frontend, 1, buf, len); + if (flags & FLAG_STDERR) + c_write_stderr(1, buf, len); + else + from_backend(ssh->frontend, 1, buf, len); } static void c_write_untrusted(Ssh ssh, const char *buf, int len) { - int i; - for (i = 0; i < len; i++) { - if (buf[i] == '\n') - c_write(ssh, "\r\n", 2); - else if ((buf[i] & 0x60) || (buf[i] == '\r')) - c_write(ssh, buf + i, 1); - } + if (flags & FLAG_STDERR) + c_write_stderr(0, buf, len); + else + from_backend_untrusted(ssh->frontend, buf, len); } static void c_write_str(Ssh ssh, const char *buf) @@ -2751,78 +2748,6 @@ static void ssh_throttle_all(Ssh ssh, int enable, int bufsize) } } -/* - * Username and password input, abstracted off into routines - * reusable in several places - even between SSH-1 and SSH-2. - */ - -/* Set up a username or password input loop on a given buffer. */ -static void setup_userpass_input(Ssh ssh, char *buffer, int buflen, int echo) -{ - ssh->userpass_input_buffer = buffer; - ssh->userpass_input_buflen = buflen; - ssh->userpass_input_bufpos = 0; - ssh->userpass_input_echo = echo; -} - -/* - * Process some terminal data in the course of username/password - * input. Returns >0 for success (line of input returned in - * buffer), <0 for failure (user hit ^C/^D, bomb out and exit), 0 - * for inconclusive (keep waiting for more input please). - */ -static int process_userpass_input(Ssh ssh, unsigned char *in, int inlen) -{ - char c; - - while (inlen--) { - switch (c = *in++) { - case 10: - case 13: - ssh->userpass_input_buffer[ssh->userpass_input_bufpos] = 0; - ssh->userpass_input_buffer[ssh->userpass_input_buflen-1] = 0; - return +1; - break; - case 8: - case 127: - if (ssh->userpass_input_bufpos > 0) { - if (ssh->userpass_input_echo) - c_write_str(ssh, "\b \b"); - ssh->userpass_input_bufpos--; - } - break; - case 21: - case 27: - while (ssh->userpass_input_bufpos > 0) { - if (ssh->userpass_input_echo) - c_write_str(ssh, "\b \b"); - ssh->userpass_input_bufpos--; - } - break; - case 3: - case 4: - return -1; - break; - default: - /* - * This simplistic check for printability is disabled - * when we're doing password input, because some people - * have control characters in their passwords.o - */ - if ((!ssh->userpass_input_echo || - (c >= ' ' && c <= '~') || - ((unsigned char) c >= 160)) - && ssh->userpass_input_bufpos < ssh->userpass_input_buflen-1) { - ssh->userpass_input_buffer[ssh->userpass_input_bufpos++] = c; - if (ssh->userpass_input_echo) - c_write(ssh, &c, 1); - } - break; - } - } - return 0; -} - static void ssh_agent_callback(void *sshv, void *reply, int replylen) { Ssh ssh = (Ssh) sshv; @@ -2935,9 +2860,9 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, char username[100]; void *publickey_blob; int publickey_bloblen; - char password[100]; - char prompt[200]; - int pos; + char *publickey_comment; + int publickey_encrypted; + prompts_t *cur_prompt; char c; int pwpkt_type; unsigned char request[5], *response, *p; @@ -3199,33 +3124,33 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, logevent("Successfully started encryption"); - fflush(stdout); + fflush(stdout); /* FIXME eh? */ { if (!*ssh->cfg.username) { - if (ssh_get_line && !ssh_getline_pw_only) { - if (!ssh_get_line("login as: ", - s->username, sizeof(s->username), FALSE)) { - /* - * get_line failed to get a username. - * Terminate. - */ - ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); - crStop(1); - } - } else { - int ret; /* need not be kept over crReturn */ - c_write_str(ssh, "login as: "); + int ret; /* need not be kept over crReturn */ + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH login name"); + add_prompt(s->cur_prompt, dupstr("login as: "), TRUE, + lenof(s->username)); + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { ssh->send_ok = 1; - - setup_userpass_input(ssh, s->username, sizeof(s->username), 1); - do { - crWaitUntil(!pktin); - ret = process_userpass_input(ssh, in, inlen); - } while (ret == 0); - if (ret < 0) - cleanup_exit(0); - c_write_str(ssh, "\r\n"); + crWaitUntil(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get a username. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); + crStop(0); } + memcpy(s->username, s->cur_prompt->prompts[0]->result, + lenof(s->username)); + free_prompts(s->cur_prompt); } else { strncpy(s->username, ssh->cfg.username, sizeof(s->username)); s->username[sizeof(s->username)-1] = '\0'; @@ -3233,14 +3158,14 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, send_packet(ssh, SSH1_CMSG_USER, PKT_STR, s->username, PKT_END); { - char userlog[22 + sizeof(s->username)]; - sprintf(userlog, "Sent username \"%s\"", s->username); + char *userlog = dupprintf("Sent username \"%s\"", s->username); logevent(userlog); if (flags & FLAG_INTERACTIVE && (!((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)))) { - strcat(userlog, "\r\n"); c_write_str(ssh, userlog); + c_write_str(ssh, "\r\n"); } + sfree(userlog); } } @@ -3253,12 +3178,44 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, s->tried_publickey = s->tried_agent = 0; } s->tis_auth_refused = s->ccard_auth_refused = 0; - /* Load the public half of ssh->cfg.keyfile so we notice if it's in Pageant */ + /* + * Load the public half of any configured keyfile for later use. + */ if (!filename_is_null(ssh->cfg.keyfile)) { - if (!rsakey_pubblob(&ssh->cfg.keyfile, - &s->publickey_blob, &s->publickey_bloblen, - NULL, NULL)) + int keytype; + logeventf(ssh, "Reading private key file \"%.150s\"", + filename_to_str(&ssh->cfg.keyfile)); + keytype = key_type(&ssh->cfg.keyfile); + if (keytype == SSH_KEYTYPE_SSH1) { + const char *error; + if (rsakey_pubblob(&ssh->cfg.keyfile, + &s->publickey_blob, &s->publickey_bloblen, + &s->publickey_comment, &error)) { + s->publickey_encrypted = rsakey_encrypted(&ssh->cfg.keyfile, + NULL); + } else { + char *msgbuf; + logeventf(ssh, "Unable to load private key (%s)", error); + msgbuf = dupprintf("Unable to load private key file " + "\"%.150s\" (%s)\r\n", + filename_to_str(&ssh->cfg.keyfile), + error); + c_write_str(ssh, msgbuf); + sfree(msgbuf); + s->publickey_blob = NULL; + } + } else { + char *msgbuf; + logeventf(ssh, "Unable to use this key file (%s)", + key_type_to_str(keytype)); + msgbuf = dupprintf("Unable to use key file \"%.150s\"" + " (%s)\r\n", + filename_to_str(&ssh->cfg.keyfile), + key_type_to_str(keytype)); + c_write_str(ssh, msgbuf); + sfree(msgbuf); s->publickey_blob = NULL; + } } else s->publickey_blob = NULL; @@ -3432,8 +3389,148 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, if (s->authed) break; } - if (!filename_is_null(ssh->cfg.keyfile) && !s->tried_publickey) - s->pwpkt_type = SSH1_CMSG_AUTH_RSA; + if (s->publickey_blob && !s->tried_publickey) { + /* + * Try public key authentication with the specified + * key file. + */ + int got_passphrase; /* need not be kept over crReturn */ + if (flags & FLAG_VERBOSE) + c_write_str(ssh, "Trying public key authentication.\r\n"); + logeventf(ssh, "Trying public key \"%s\"", + filename_to_str(&ssh->cfg.keyfile)); + s->tried_publickey = 1; + got_passphrase = FALSE; + while (!got_passphrase) { + /* + * Get a passphrase, if necessary. + */ + char *passphrase = NULL; /* only written after crReturn */ + const char *error; + if (!s->publickey_encrypted) { + if (flags & FLAG_VERBOSE) + c_write_str(ssh, "No passphrase required.\r\n"); + passphrase = NULL; + } else { + int ret; /* need not be kept over crReturn */ + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = FALSE; + s->cur_prompt->name = dupstr("SSH key passphrase"); + add_prompt(s->cur_prompt, + dupprintf("Passphrase for key \"%.100s\": ", + s->publickey_comment), + FALSE, SSH_MAX_PASSWORD_LEN); + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntil(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* Failed to get a passphrase. Terminate. */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + 0, TRUE); + crStop(0); + } + passphrase = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + } + /* + * Try decrypting key with passphrase. + */ + ret = loadrsakey(&ssh->cfg.keyfile, &s->key, passphrase, + &error); + if (passphrase) { + memset(passphrase, 0, strlen(passphrase)); + sfree(passphrase); + } + if (ret == 1) { + /* Correct passphrase. */ + got_passphrase = TRUE; + } else if (ret == 0) { + c_write_str(ssh, "Couldn't load private key from "); + c_write_str(ssh, filename_to_str(&ssh->cfg.keyfile)); + c_write_str(ssh, " ("); + c_write_str(ssh, error); + c_write_str(ssh, ").\r\n"); + got_passphrase = FALSE; + break; /* go and try something else */ + } else if (ret == -1) { + c_write_str(ssh, "Wrong passphrase.\r\n"); /* FIXME */ + s->tried_publickey = 0; + got_passphrase = FALSE; + /* and try again */ + } + } + + if (got_passphrase) { + + /* + * Send a public key attempt. + */ + send_packet(ssh, SSH1_CMSG_AUTH_RSA, + PKT_BIGNUM, s->key.modulus, PKT_END); + + crWaitUntil(pktin); + if (pktin->type == SSH1_SMSG_FAILURE) { + c_write_str(ssh, "Server refused our public key.\r\n"); + continue; /* go and try password */ + } + if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + bombout(("Bizarre response to offer of public key")); + crStop(0); + } + + { + int i; + unsigned char buffer[32]; + Bignum challenge, response; + + if ((challenge = ssh1_pkt_getmp(pktin)) == NULL) { + bombout(("Server's RSA challenge was badly formatted")); + crStop(0); + } + response = rsadecrypt(challenge, &s->key); + freebn(s->key.private_exponent);/* burn the evidence */ + + for (i = 0; i < 32; i++) { + buffer[i] = bignum_byte(response, 31 - i); + } + + MD5Init(&md5c); + MD5Update(&md5c, buffer, 32); + MD5Update(&md5c, s->session_id, 16); + MD5Final(buffer, &md5c); + + send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE, + PKT_DATA, buffer, 16, PKT_END); + + freebn(challenge); + freebn(response); + } + + crWaitUntil(pktin); + if (pktin->type == SSH1_SMSG_FAILURE) { + if (flags & FLAG_VERBOSE) + c_write_str(ssh, "Failed to authenticate with" + " our public key.\r\n"); + continue; /* go and try password */ + } else if (pktin->type != SSH1_SMSG_SUCCESS) { + bombout(("Bizarre response to RSA authentication response")); + crStop(0); + } + + break; /* we're through! */ + } + + } + + /* + * Otherwise, try various forms of password-like authentication. + */ + s->cur_prompt = new_prompts(ssh->frontend); if (ssh->cfg.try_tis_auth && (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && @@ -3451,23 +3548,31 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, } else { char *challenge; int challengelen; + char *instr_suf, *prompt; ssh_pkt_getstring(pktin, &challenge, &challengelen); if (!challenge) { bombout(("TIS challenge packet was badly formed")); crStop(0); } - c_write_str(ssh, "Using TIS authentication.\r\n"); logevent("Received TIS challenge"); - if (challengelen > sizeof(s->prompt) - 1) - challengelen = sizeof(s->prompt) - 1;/* prevent overrun */ - memcpy(s->prompt, challenge, challengelen); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH TIS authentication"); /* Prompt heuristic comes from OpenSSH */ - strncpy(s->prompt + challengelen, - memchr(s->prompt, '\n', challengelen) ? - "": "\r\nResponse: ", - (sizeof s->prompt) - challengelen); - s->prompt[(sizeof s->prompt) - 1] = '\0'; + if (memchr(challenge, '\n', challengelen)) { + instr_suf = dupstr(""); + prompt = dupprintf("%.*s", challengelen, challenge); + } else { + instr_suf = dupprintf("%.*s", challengelen, challenge); + prompt = dupstr("Response: "); + } + s->cur_prompt->instruction = + dupprintf("Using TIS authentication.%s%s", + (*instr_suf) ? "\n" : "", + instr_suf); + s->cur_prompt->instr_reqd = TRUE; + add_prompt(s->cur_prompt, prompt, FALSE, SSH_MAX_PASSWORD_LEN); + sfree(instr_suf); } } if (ssh->cfg.try_tis_auth && @@ -3485,53 +3590,40 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, } else { char *challenge; int challengelen; + char *instr_suf, *prompt; ssh_pkt_getstring(pktin, &challenge, &challengelen); if (!challenge) { bombout(("CryptoCard challenge packet was badly formed")); crStop(0); } - c_write_str(ssh, "Using CryptoCard authentication.\r\n"); logevent("Received CryptoCard challenge"); - if (challengelen > sizeof(s->prompt) - 1) - challengelen = sizeof(s->prompt) - 1;/* prevent overrun */ - memcpy(s->prompt, challenge, challengelen); - strncpy(s->prompt + challengelen, - memchr(s->prompt, '\n', challengelen) ? - "" : "\r\nResponse: ", - sizeof(s->prompt) - challengelen); - s->prompt[sizeof(s->prompt) - 1] = '\0'; + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH CryptoCard authentication"); + s->cur_prompt->name_reqd = FALSE; + /* Prompt heuristic comes from OpenSSH */ + if (memchr(challenge, '\n', challengelen)) { + instr_suf = dupstr(""); + prompt = dupprintf("%.*s", challengelen, challenge); + } else { + instr_suf = dupprintf("%.*s", challengelen, challenge); + prompt = dupstr("Response: "); + } + s->cur_prompt->instruction = + dupprintf("Using CryptoCard authentication.%s%s", + (*instr_suf) ? "\n" : "", + instr_suf); + s->cur_prompt->instr_reqd = TRUE; + add_prompt(s->cur_prompt, prompt, FALSE, SSH_MAX_PASSWORD_LEN); + sfree(instr_suf); } } if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { - sprintf(s->prompt, "%.90s@%.90s's password: ", - s->username, ssh->savedhost); - } - if (s->pwpkt_type == SSH1_CMSG_AUTH_RSA) { - char *comment = NULL; - int type; - if (flags & FLAG_VERBOSE) - c_write_str(ssh, "Trying public key authentication.\r\n"); - logeventf(ssh, "Trying public key \"%s\"", - filename_to_str(&ssh->cfg.keyfile)); - type = key_type(&ssh->cfg.keyfile); - if (type != SSH_KEYTYPE_SSH1) { - char *msg = dupprintf("Key is of wrong type (%s)", - key_type_to_str(type)); - logevent(msg); - c_write_str(ssh, msg); - c_write_str(ssh, "\r\n"); - sfree(msg); - s->tried_publickey = 1; - continue; - } - if (!rsakey_encrypted(&ssh->cfg.keyfile, &comment)) { - if (flags & FLAG_VERBOSE) - c_write_str(ssh, "No passphrase required.\r\n"); - goto tryauth; - } - sprintf(s->prompt, "Passphrase for key \"%.100s\": ", comment); - sfree(comment); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH password"); + add_prompt(s->cur_prompt, dupprintf("%.90s@%.90s's password: ", + s->username, ssh->savedhost), + FALSE, SSH_MAX_PASSWORD_LEN); } /* @@ -3539,245 +3631,155 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, * or CryptoCard exchange if we're doing TIS or CryptoCard * authentication. */ - if (ssh_get_line) { - if (!ssh_get_line(s->prompt, s->password, - sizeof(s->password), TRUE)) { + { + int ret; /* need not be kept over crReturn */ + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntil(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { /* - * get_line failed to get a password (for example + * Failed to get a password (for example * because one was supplied on the command line * which has already failed to work). Terminate. */ - ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, FALSE); - crStop(1); + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, TRUE); + crStop(0); } - } else { - /* Prompt may have come from server. We've munged it a bit, so - * we know it to be zero-terminated at least once. */ - int ret; /* need not be saved over crReturn */ - c_write_untrusted(ssh, s->prompt, strlen(s->prompt)); - s->pos = 0; - - setup_userpass_input(ssh, s->password, sizeof(s->password), 0); - do { - crWaitUntil(!pktin); - ret = process_userpass_input(ssh, in, inlen); - } while (ret == 0); - if (ret < 0) - cleanup_exit(0); - c_write_str(ssh, "\r\n"); } - tryauth: - if (s->pwpkt_type == SSH1_CMSG_AUTH_RSA) { - /* - * Try public key authentication with the specified - * key file. - */ - s->tried_publickey = 1; - - { - const char *error = NULL; - int ret = loadrsakey(&ssh->cfg.keyfile, &s->key, s->password, - &error); - if (ret == 0) { - c_write_str(ssh, "Couldn't load private key from "); - c_write_str(ssh, filename_to_str(&ssh->cfg.keyfile)); - c_write_str(ssh, " ("); - c_write_str(ssh, error); - c_write_str(ssh, ").\r\n"); - continue; /* go and try password */ - } - if (ret == -1) { - c_write_str(ssh, "Wrong passphrase.\r\n"); - s->tried_publickey = 0; - continue; /* try again */ - } - } - + if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { /* - * Send a public key attempt. + * Defence against traffic analysis: we send a + * whole bunch of packets containing strings of + * different lengths. One of these strings is the + * password, in a SSH1_CMSG_AUTH_PASSWORD packet. + * The others are all random data in + * SSH1_MSG_IGNORE packets. This way a passive + * listener can't tell which is the password, and + * hence can't deduce the password length. + * + * Anybody with a password length greater than 16 + * bytes is going to have enough entropy in their + * password that a listener won't find it _that_ + * much help to know how long it is. So what we'll + * do is: + * + * - if password length < 16, we send 15 packets + * containing string lengths 1 through 15 + * + * - otherwise, we let N be the nearest multiple + * of 8 below the password length, and send 8 + * packets containing string lengths N through + * N+7. This won't obscure the order of + * magnitude of the password length, but it will + * introduce a bit of extra uncertainty. + * + * A few servers (the old 1.2.18 through 1.2.22) + * can't deal with SSH1_MSG_IGNORE. For these + * servers, we need an alternative defence. We make + * use of the fact that the password is interpreted + * as a C string: so we can append a NUL, then some + * random data. + * + * One server (a Cisco one) can deal with neither + * SSH1_MSG_IGNORE _nor_ a padded password string. + * For this server we are left with no defences + * against password length sniffing. */ - send_packet(ssh, SSH1_CMSG_AUTH_RSA, - PKT_BIGNUM, s->key.modulus, PKT_END); - - crWaitUntil(pktin); - if (pktin->type == SSH1_SMSG_FAILURE) { - c_write_str(ssh, "Server refused our public key.\r\n"); - continue; /* go and try password */ - } - if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { - bombout(("Bizarre response to offer of public key")); - crStop(0); - } - - { - int i; - unsigned char buffer[32]; - Bignum challenge, response; - - if ((challenge = ssh1_pkt_getmp(pktin)) == NULL) { - bombout(("Server's RSA challenge was badly formatted")); - crStop(0); - } - response = rsadecrypt(challenge, &s->key); - freebn(s->key.private_exponent);/* burn the evidence */ - - for (i = 0; i < 32; i++) { - buffer[i] = bignum_byte(response, 31 - i); - } - - MD5Init(&md5c); - MD5Update(&md5c, buffer, 32); - MD5Update(&md5c, s->session_id, 16); - MD5Final(buffer, &md5c); - - send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE, - PKT_DATA, buffer, 16, PKT_END); - - freebn(challenge); - freebn(response); - } - - crWaitUntil(pktin); - if (pktin->type == SSH1_SMSG_FAILURE) { - if (flags & FLAG_VERBOSE) - c_write_str(ssh, "Failed to authenticate with" - " our public key.\r\n"); - continue; /* go and try password */ - } else if (pktin->type != SSH1_SMSG_SUCCESS) { - bombout(("Bizarre response to RSA authentication response")); - crStop(0); - } - - break; /* we're through! */ - } else { - if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { /* - * Defence against traffic analysis: we send a - * whole bunch of packets containing strings of - * different lengths. One of these strings is the - * password, in a SSH1_CMSG_AUTH_PASSWORD packet. - * The others are all random data in - * SSH1_MSG_IGNORE packets. This way a passive - * listener can't tell which is the password, and - * hence can't deduce the password length. - * - * Anybody with a password length greater than 16 - * bytes is going to have enough entropy in their - * password that a listener won't find it _that_ - * much help to know how long it is. So what we'll - * do is: - * - * - if password length < 16, we send 15 packets - * containing string lengths 1 through 15 - * - * - otherwise, we let N be the nearest multiple - * of 8 below the password length, and send 8 - * packets containing string lengths N through - * N+7. This won't obscure the order of - * magnitude of the password length, but it will - * introduce a bit of extra uncertainty. - * - * A few servers (the old 1.2.18 through 1.2.22) - * can't deal with SSH1_MSG_IGNORE. For these - * servers, we need an alternative defence. We make - * use of the fact that the password is interpreted - * as a C string: so we can append a NUL, then some - * random data. - * - * One server (a Cisco one) can deal with neither - * SSH1_MSG_IGNORE _nor_ a padded password string. - * For this server we are left with no defences - * against password length sniffing. + * The server can deal with SSH1_MSG_IGNORE, so + * we can use the primary defence. */ - if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { - /* - * The server can deal with SSH1_MSG_IGNORE, so - * we can use the primary defence. - */ - int bottom, top, pwlen, i; - char *randomstr; + int bottom, top, pwlen, i; + char *randomstr; - pwlen = strlen(s->password); - if (pwlen < 16) { - bottom = 0; /* zero length passwords are OK! :-) */ - top = 15; - } else { - bottom = pwlen & ~7; - top = bottom + 7; - } + pwlen = strlen(s->cur_prompt->prompts[0]->result); + if (pwlen < 16) { + bottom = 0; /* zero length passwords are OK! :-) */ + top = 15; + } else { + bottom = pwlen & ~7; + top = bottom + 7; + } - assert(pwlen >= bottom && pwlen <= top); + assert(pwlen >= bottom && pwlen <= top); - randomstr = snewn(top + 1, char); + randomstr = snewn(top + 1, char); - for (i = bottom; i <= top; i++) { - if (i == pwlen) { - defer_packet(ssh, s->pwpkt_type, - PKTT_PASSWORD, PKT_STR, s->password, - PKTT_OTHER, PKT_END); - } else { - for (j = 0; j < i; j++) { - do { - randomstr[j] = random_byte(); - } while (randomstr[j] == '\0'); - } - randomstr[i] = '\0'; - defer_packet(ssh, SSH1_MSG_IGNORE, - PKT_STR, randomstr, PKT_END); + for (i = bottom; i <= top; i++) { + if (i == pwlen) { + defer_packet(ssh, s->pwpkt_type, + PKTT_PASSWORD, PKT_STR, + s->cur_prompt->prompts[0]->result, + PKTT_OTHER, PKT_END); + } else { + for (j = 0; j < i; j++) { + do { + randomstr[j] = random_byte(); + } while (randomstr[j] == '\0'); } + randomstr[i] = '\0'; + defer_packet(ssh, SSH1_MSG_IGNORE, + PKT_STR, randomstr, PKT_END); } - logevent("Sending password with camouflage packets"); - ssh_pkt_defersend(ssh); - sfree(randomstr); - } - else if (!(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { - /* - * The server can't deal with SSH1_MSG_IGNORE - * but can deal with padded passwords, so we - * can use the secondary defence. - */ - char string[64]; - char *ss; - int len; - - len = strlen(s->password); - if (len < sizeof(string)) { - ss = string; - strcpy(string, s->password); - len++; /* cover the zero byte */ - while (len < sizeof(string)) { - string[len++] = (char) random_byte(); - } - } else { - ss = s->password; + } + logevent("Sending password with camouflage packets"); + ssh_pkt_defersend(ssh); + sfree(randomstr); + } + else if (!(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { + /* + * The server can't deal with SSH1_MSG_IGNORE + * but can deal with padded passwords, so we + * can use the secondary defence. + */ + char string[64]; + char *ss; + int len; + + len = strlen(s->cur_prompt->prompts[0]->result); + if (len < sizeof(string)) { + ss = string; + strcpy(string, s->cur_prompt->prompts[0]->result); + len++; /* cover the zero byte */ + while (len < sizeof(string)) { + string[len++] = (char) random_byte(); } - logevent("Sending length-padded password"); - send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD, - PKT_INT, len, PKT_DATA, ss, len, - PKTT_OTHER, PKT_END); } else { - /* - * The server has _both_ - * BUG_CHOKES_ON_SSH1_IGNORE and - * BUG_NEEDS_SSH1_PLAIN_PASSWORD. There is - * therefore nothing we can do. - */ - int len; - len = strlen(s->password); - logevent("Sending unpadded password"); - send_packet(ssh, s->pwpkt_type, - PKTT_PASSWORD, PKT_INT, len, - PKT_DATA, s->password, len, - PKTT_OTHER, PKT_END); + ss = s->cur_prompt->prompts[0]->result; } - } else { + logevent("Sending length-padded password"); send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD, - PKT_STR, s->password, PKTT_OTHER, PKT_END); + PKT_INT, len, PKT_DATA, ss, len, + PKTT_OTHER, PKT_END); + } else { + /* + * The server has _both_ + * BUG_CHOKES_ON_SSH1_IGNORE and + * BUG_NEEDS_SSH1_PLAIN_PASSWORD. There is + * therefore nothing we can do. + */ + int len; + len = strlen(s->cur_prompt->prompts[0]->result); + logevent("Sending unpadded password"); + send_packet(ssh, s->pwpkt_type, + PKTT_PASSWORD, PKT_INT, len, + PKT_DATA, s->cur_prompt->prompts[0]->result, len, + PKTT_OTHER, PKT_END); } + } else { + send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD, + PKT_STR, s->cur_prompt->prompts[0]->result, + PKTT_OTHER, PKT_END); } logevent("Sent password"); - memset(s->password, 0, strlen(s->password)); + free_prompts(s->cur_prompt); crWaitUntil(pktin); if (pktin->type == SSH1_SMSG_FAILURE) { if (flags & FLAG_VERBOSE) @@ -3789,6 +3791,12 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, } } + /* Clear up */ + if (s->publickey_blob) { + sfree(s->publickey_blob); + sfree(s->publickey_comment); + } + logevent("Authentication successful"); crFinish(1); @@ -6482,15 +6490,17 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int done_service_req; int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter; int tried_pubkey_config, tried_agent; - int kbd_inter_running, kbd_inter_refused; + int kbd_inter_refused; int we_are_in; - int num_prompts, curr_prompt, echo; + prompts_t *cur_prompt; + int num_prompts; char username[100]; int got_username; - char pwprompt[512]; - char password[100]; void *publickey_blob; int publickey_bloblen; + int publickey_encrypted; + char *publickey_algorithm; + char *publickey_comment; unsigned char request[5], *response, *p; int responselen; int keyi, nkeys; @@ -6537,6 +6547,58 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } /* + * Misc one-time setup for authentication. + */ + s->publickey_blob = NULL; + if (!s->we_are_in) { + + /* + * Load the public half of any configured public key file + * for later use. + */ + if (!filename_is_null(ssh->cfg.keyfile)) { + int keytype; + logeventf(ssh, "Reading private key file \"%.150s\"", + filename_to_str(&ssh->cfg.keyfile)); + keytype = key_type(&ssh->cfg.keyfile); + if (keytype == SSH_KEYTYPE_SSH2) { + const char *error; + s->publickey_blob = + ssh2_userkey_loadpub(&ssh->cfg.keyfile, + &s->publickey_algorithm, + &s->publickey_bloblen, + &s->publickey_comment, &error); + if (s->publickey_blob) { + s->publickey_encrypted = + ssh2_userkey_encrypted(&ssh->cfg.keyfile, NULL); + } else { + char *msgbuf; + logeventf(ssh, "Unable to load private key (%s)", + error); + msgbuf = dupprintf("Unable to load private key file " + "\"%.150s\" (%s)\r\n", + filename_to_str(&ssh->cfg.keyfile), + error); + c_write_str(ssh, msgbuf); + sfree(msgbuf); + } + } else { + char *msgbuf; + logeventf(ssh, "Unable to use this key file (%s)", + key_type_to_str(keytype)); + msgbuf = dupprintf("Unable to use key file \"%.150s\"" + " (%s)\r\n", + filename_to_str(&ssh->cfg.keyfile), + key_type_to_str(keytype)); + c_write_str(ssh, msgbuf); + sfree(msgbuf); + s->publickey_blob = NULL; + } + } + + } + + /* * We repeat this whole loop, including the username prompt, * until we manage a successful authentication. If the user * types the wrong _password_, they can be sent back to the @@ -6576,30 +6638,31 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, * it again. */ } else if (!*ssh->cfg.username) { - if (ssh_get_line && !ssh_getline_pw_only) { - if (!ssh_get_line("login as: ", - s->username, sizeof(s->username), FALSE)) { - /* - * get_line failed to get a username. - * Terminate. - */ - ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); - crStopV; - } - } else { - int ret; /* need not be saved across crReturn */ - c_write_str(ssh, "login as: "); + int ret; /* need not be kept over crReturn */ + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH login name"); + add_prompt(s->cur_prompt, dupstr("login as: "), TRUE, + lenof(s->username)); + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { ssh->send_ok = 1; - setup_userpass_input(ssh, s->username, sizeof(s->username), 1); - do { - crWaitUntilV(!pktin); - ret = process_userpass_input(ssh, in, inlen); - } while (ret == 0); - if (ret < 0) - cleanup_exit(0); - c_write_str(ssh, "\r\n"); + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; } - s->username[strcspn(s->username, "\n\r")] = '\0'; + if (!ret) { + /* + * get_userpass_input() failed to get a username. + * Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); + crStopV; + } + memcpy(s->username, s->cur_prompt->prompts[0]->result, + lenof(s->username)); + free_prompts(s->cur_prompt); } else { char *stuff; strncpy(s->username, ssh->cfg.username, sizeof(s->username)); @@ -6630,32 +6693,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->tried_pubkey_config = FALSE; s->tried_agent = FALSE; - s->kbd_inter_running = FALSE; s->kbd_inter_refused = FALSE; - /* Load the pub half of ssh->cfg.keyfile so we notice if it's in Pageant */ - if (!filename_is_null(ssh->cfg.keyfile)) { - int keytype; - logeventf(ssh, "Reading private key file \"%.150s\"", - filename_to_str(&ssh->cfg.keyfile)); - keytype = key_type(&ssh->cfg.keyfile); - if (keytype == SSH_KEYTYPE_SSH2) { - s->publickey_blob = - ssh2_userkey_loadpub(&ssh->cfg.keyfile, NULL, - &s->publickey_bloblen, NULL, NULL); - } else { - char *msgbuf; - logeventf(ssh, "Unable to use this key file (%s)", - key_type_to_str(keytype)); - msgbuf = dupprintf("Unable to use key file \"%.150s\"" - " (%s)\r\n", - filename_to_str(&ssh->cfg.keyfile), - key_type_to_str(keytype)); - c_write_str(ssh, msgbuf); - sfree(msgbuf); - s->publickey_blob = NULL; - } - } else - s->publickey_blob = NULL; while (1) { /* @@ -6693,18 +6731,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, break; } - if (s->kbd_inter_running && - pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { - /* - * This is either a further set-of-prompts packet - * in keyboard-interactive authentication, or it's - * the same one and we came back here with `gotit' - * set. In the former case, we must reset the - * curr_prompt variable. - */ - if (!s->gotit) - s->curr_prompt = 0; - } else if (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { + if (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { /* FIXME: perhaps we should support this? */ bombout(("PASSWD_CHANGEREQ not yet supported")); crStopV; @@ -6725,7 +6752,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, char *methods; int methlen; ssh_pkt_getstring(pktin, &methods, &methlen); - s->kbd_inter_running = FALSE; if (!ssh2_pkt_getbool(pktin)) { /* * We have received an unequivocal Access @@ -6782,20 +6808,10 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, in_commasep_string("keyboard-interactive", methods, methlen); } - s->method = 0; ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; - s->need_pw = FALSE; - /* - * Most password/passphrase prompts will be - * non-echoing, so we set this to 0 by default. - * Exception is that some keyboard-interactive prompts - * can be echoing, in which case we'll set this to 1. - */ - s->echo = 0; + if (s->can_pubkey && agent_exists() && !s->tried_agent) { - if (!s->method && s->can_pubkey && - agent_exists() && !s->tried_agent) { /* * Attempt public-key authentication using Pageant. */ @@ -6960,234 +6976,127 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, continue; } sfree(s->response); - } - if (!s->method && s->can_pubkey && s->publickey_blob - && !s->tried_pubkey_config) { - unsigned char *pub_blob; - char *algorithm, *comment; - int pub_blob_len; + } else if (s->can_pubkey && s->publickey_blob && + !s->tried_pubkey_config) { - s->tried_pubkey_config = TRUE; + struct ssh2_userkey *key; /* not live over crReturn */ + char *passphrase; /* not live over crReturn */ ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; ssh->pkt_ctx |= SSH2_PKTCTX_PUBLICKEY; + s->tried_pubkey_config = TRUE; + /* * Try the public key supplied in the configuration. * * First, offer the public blob to see if the server is * willing to accept it. */ - pub_blob = - (unsigned char *)ssh2_userkey_loadpub(&ssh->cfg.keyfile, - &algorithm, - &pub_blob_len, - NULL, NULL); - if (pub_blob) { - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ - ssh2_pkt_addstring(s->pktout, "publickey"); /* method */ - ssh2_pkt_addbool(s->pktout, FALSE); /* no signature included */ - ssh2_pkt_addstring(s->pktout, algorithm); - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, (char *)pub_blob, - pub_blob_len); - ssh2_pkt_send(ssh, s->pktout); - logevent("Offered public key"); - - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { - s->gotit = TRUE; - s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD; - continue; /* key refused; give up on it */ - } - - logevent("Offer of public key accepted"); - /* - * Actually attempt a serious authentication using - * the key. - */ - if (ssh2_userkey_encrypted(&ssh->cfg.keyfile, &comment)) { - sprintf(s->pwprompt, - "Passphrase for key \"%.100s\": ", - comment); - s->need_pw = TRUE; - } else { - s->need_pw = FALSE; - } - if (flags & FLAG_VERBOSE) { - c_write_str(ssh, "Authenticating with public key \""); - c_write_str(ssh, comment); - c_write_str(ssh, "\"\r\n"); - } - s->method = AUTH_PUBLICKEY_FILE; - } - } - - if (!s->method && s->can_keyb_inter && !s->kbd_inter_refused && - !s->kbd_inter_running) { - s->method = AUTH_KEYBOARD_INTERACTIVE; - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; - - ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; - ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER; - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); ssh2_pkt_addstring(s->pktout, s->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ - ssh2_pkt_addstring(s->pktout, "keyboard-interactive"); /* method */ - ssh2_pkt_addstring(s->pktout, ""); /* lang */ - ssh2_pkt_addstring(s->pktout, ""); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "publickey"); /* method */ + ssh2_pkt_addbool(s->pktout, FALSE); + /* no signature included */ + ssh2_pkt_addstring(s->pktout, s->publickey_algorithm); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, + (char *)s->publickey_blob, + s->publickey_bloblen); ssh2_pkt_send(ssh, s->pktout); + logevent("Offered public key"); crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) { - if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) - s->gotit = TRUE; - logevent("Keyboard-interactive authentication refused"); - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET; - s->kbd_inter_refused = TRUE; /* don't try it again */ - continue; - } - - c_write_str(ssh, "Using keyboard-interactive authentication.\r\n"); - s->kbd_inter_running = TRUE; - s->curr_prompt = 0; - } - - if (s->kbd_inter_running) { - s->method = AUTH_KEYBOARD_INTERACTIVE; - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; - - ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; - ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER; - - if (s->curr_prompt == 0) { - /* - * We've got a fresh USERAUTH_INFO_REQUEST. - * Display header data, and start going through - * the prompts. - */ - char *name, *inst, *lang; - int name_len, inst_len, lang_len; - - ssh_pkt_getstring(pktin, &name, &name_len); - ssh_pkt_getstring(pktin, &inst, &inst_len); - ssh_pkt_getstring(pktin, &lang, &lang_len); - if (name_len > 0) { - c_write_untrusted(ssh, name, name_len); - c_write_str(ssh, "\r\n"); - } - if (inst_len > 0) { - c_write_untrusted(ssh, inst, inst_len); - c_write_str(ssh, "\r\n"); - } - s->num_prompts = ssh_pkt_getuint32(pktin); + if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { + /* Key refused. Give up. */ + s->gotit = TRUE; /* reconsider message next loop */ + s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD; + continue; /* process this new message */ } + s->method = AUTH_PUBLICKEY_FILE; + logevent("Offer of public key accepted"); /* - * If there are prompts remaining in the packet, - * display one and get a response. + * Actually attempt a serious authentication using + * the key. */ - if (s->curr_prompt < s->num_prompts) { - char *prompt; - int prompt_len; - - ssh_pkt_getstring(pktin, &prompt, &prompt_len); - if (prompt_len > 0) { - static const char trunc[] = ": "; - static const int prlen = sizeof(s->pwprompt) - - lenof(trunc); - if (prompt_len > prlen) { - memcpy(s->pwprompt, prompt, prlen); - strcpy(s->pwprompt + prlen, trunc); - } else { - memcpy(s->pwprompt, prompt, prompt_len); - s->pwprompt[prompt_len] = '\0'; + if (flags & FLAG_VERBOSE) { + c_write_str(ssh, "Authenticating with public key \""); + c_write_str(ssh, s->publickey_comment); + c_write_str(ssh, "\"\r\n"); + } + key = NULL; + while (!key) { + const char *error; /* not live over crReturn */ + if (s->publickey_encrypted) { + /* + * Get a passphrase from the user. + */ + int ret; /* need not be kept over crReturn */ + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = FALSE; + s->cur_prompt->name = dupstr("SSH key passphrase"); + add_prompt(s->cur_prompt, + dupprintf("Passphrase for key \"%.100s\": ", + s->publickey_comment), + FALSE, SSH_MAX_PASSWORD_LEN); + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, + in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* Failed to get a passphrase. Terminate. */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, + "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; } + passphrase = + dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); } else { - strcpy(s->pwprompt, - ": "); + passphrase = NULL; /* no passphrase needed */ } - s->echo = ssh2_pkt_getbool(pktin); - s->need_pw = TRUE; - } else - s->need_pw = FALSE; - } - - if (!s->method && s->can_passwd) { - s->method = AUTH_PASSWORD; - ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; - ssh->pkt_ctx |= SSH2_PKTCTX_PASSWORD; - sprintf(s->pwprompt, "%.90s@%.90s's password: ", s->username, - ssh->savedhost); - s->need_pw = TRUE; - } - if (s->need_pw) { - if (ssh_get_line) { - if (!ssh_get_line(s->pwprompt, s->password, - sizeof(s->password), TRUE)) { - /* - * get_line failed to get a password (for - * example because one was supplied on the - * command line which has already failed to - * work). Terminate. - */ - ssh_disconnect(ssh, NULL, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, - FALSE); - crStopV; + /* + * Try decrypting the key. + */ + key = ssh2_load_userkey(&ssh->cfg.keyfile, passphrase, + &error); + if (passphrase) { + /* burn the evidence */ + memset(passphrase, 0, strlen(passphrase)); + sfree(passphrase); + } + if (key == SSH2_WRONG_PASSPHRASE || key == NULL) { + if (passphrase && + (key == SSH2_WRONG_PASSPHRASE)) { + c_write_str(ssh, "Wrong passphrase\r\n"); + key = NULL; + /* and loop again */ + } else { + c_write_str(ssh, "Unable to load private key ("); + c_write_str(ssh, error); + c_write_str(ssh, ")\r\n"); + key = NULL; + break; /* try something else */ + } } - } else { - int ret; /* need not be saved across crReturn */ - c_write_untrusted(ssh, s->pwprompt, strlen(s->pwprompt)); - ssh->send_ok = 1; - - setup_userpass_input(ssh, s->password, - sizeof(s->password), s->echo); - do { - crWaitUntilV(!pktin); - ret = process_userpass_input(ssh, in, inlen); - } while (ret == 0); - if (ret < 0) - cleanup_exit(0); - c_write_str(ssh, "\r\n"); } - } - if (s->method == AUTH_PUBLICKEY_FILE) { - /* - * We have our passphrase. Now try the actual authentication. - */ - struct ssh2_userkey *key; - const char *error = NULL; - - key = ssh2_load_userkey(&ssh->cfg.keyfile, s->password, - &error); - if (key == SSH2_WRONG_PASSPHRASE || key == NULL) { - if (key == SSH2_WRONG_PASSPHRASE) { - c_write_str(ssh, "Wrong passphrase\r\n"); - s->tried_pubkey_config = FALSE; - } else { - c_write_str(ssh, "Unable to load private key ("); - c_write_str(ssh, error); - c_write_str(ssh, ")\r\n"); - s->tried_pubkey_config = TRUE; - } - /* Send a spurious AUTH_NONE to return to the top. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ - ssh2_pkt_addstring(s->pktout, "none"); /* method */ - ssh2_pkt_send(ssh, s->pktout); - s->type = AUTH_TYPE_NONE; - } else { + if (key) { unsigned char *pkblob, *sigblob, *sigdata; int pkblob_len, sigblob_len, sigdata_len; - int p; + int p; /* * We have loaded the private key and the server @@ -7196,13 +7105,18 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, */ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); ssh2_pkt_addstring(s->pktout, s->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ - ssh2_pkt_addstring(s->pktout, "publickey"); /* method */ + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "publickey"); + /* method */ ssh2_pkt_addbool(s->pktout, TRUE); + /* signature follows */ ssh2_pkt_addstring(s->pktout, key->alg->name); - pkblob = key->alg->public_blob(key->data, &pkblob_len); + pkblob = key->alg->public_blob(key->data, + &pkblob_len); ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, (char *)pkblob, pkblob_len); + ssh2_pkt_addstring_data(s->pktout, (char *)pkblob, + pkblob_len); /* * The data to be signed is: @@ -7214,21 +7128,21 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, */ sigdata_len = s->pktout->length - 5 + 4 + ssh->v2_session_id_len; - if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID) - sigdata_len -= 4; + if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID) + sigdata_len -= 4; sigdata = snewn(sigdata_len, unsigned char); - p = 0; - if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) { - PUT_32BIT(sigdata+p, ssh->v2_session_id_len); - p += 4; - } + p = 0; + if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) { + PUT_32BIT(sigdata+p, ssh->v2_session_id_len); + p += 4; + } memcpy(sigdata+p, ssh->v2_session_id, ssh->v2_session_id_len); p += ssh->v2_session_id_len; memcpy(sigdata+p, s->pktout->data + 5, s->pktout->length - 5); - p += s->pktout->length - 5; - assert(p == sigdata_len); + p += s->pktout->length - 5; + assert(p == sigdata_len); sigblob = key->alg->sign(key->data, (char *)sigdata, sigdata_len, &sigblob_len); ssh2_add_sigblob(ssh, s->pktout, pkblob, pkblob_len, @@ -7241,8 +7155,169 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->type = AUTH_TYPE_PUBLICKEY; key->alg->freekey(key->data); } - } else if (s->method == AUTH_PASSWORD) { + + } else if (s->can_keyb_inter && !s->kbd_inter_refused) { + + /* + * Keyboard-interactive authentication. + */ + char *name, *inst, *lang; + int name_len, inst_len, lang_len; + int i; + + s->method = AUTH_KEYBOARD_INTERACTIVE; + s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; + + ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; + ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER; + + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "keyboard-interactive"); + /* method */ + ssh2_pkt_addstring(s->pktout, ""); /* lang */ + ssh2_pkt_addstring(s->pktout, ""); /* submethods */ + ssh2_pkt_send(ssh, s->pktout); + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) { + /* Server is not willing to do keyboard-interactive + * at all. Give up on it entirely. */ + s->gotit = TRUE; + if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) + logevent("Keyboard-interactive authentication refused"); + s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET; + s->kbd_inter_refused = TRUE; /* don't try it again */ + continue; + } + + /* + * We've got a fresh USERAUTH_INFO_REQUEST. + * Get the preamble and start building a prompt. + */ + ssh_pkt_getstring(pktin, &name, &name_len); + ssh_pkt_getstring(pktin, &inst, &inst_len); + ssh_pkt_getstring(pktin, &lang, &lang_len); + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + if (name_len) { + /* FIXME: better prefix to distinguish from + * local prompts? */ + s->cur_prompt->name = dupprintf("SSH server: %.*s", + name_len, name); + s->cur_prompt->name_reqd = TRUE; + } else { + s->cur_prompt->name = dupstr("SSH server authentication"); + s->cur_prompt->name_reqd = FALSE; + } + s->cur_prompt->instruction = + dupprintf("Using keyboard-interactive authentication.%s%.*s", + inst_len ? "\n" : "", inst_len, inst); + s->cur_prompt->instr_reqd = TRUE; + /* + * Get the prompts from the packet. + */ + s->num_prompts = ssh_pkt_getuint32(pktin); + for (i = 0; i < s->num_prompts; i++) { + char *prompt; + int prompt_len; + int echo; + static char noprompt[] = + ": "; + + ssh_pkt_getstring(pktin, &prompt, &prompt_len); + echo = ssh2_pkt_getbool(pktin); + if (!prompt_len) { + prompt = noprompt; + prompt_len = lenof(noprompt)-1; + } + add_prompt(s->cur_prompt, + dupprintf("%.*s", prompt_len, prompt), + echo, SSH_MAX_PASSWORD_LEN); + } + + /* + * Get the user's responses. + */ + if (s->num_prompts) { + int ret; /* not live over crReturn */ + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; + } + } + + /* + * Send the responses to the server. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); + s->pktout->forcepad = 256; + ssh2_pkt_adduint32(s->pktout, s->num_prompts); + for (i=0; i < s->num_prompts; i++) { + dont_log_password(ssh, s->pktout, PKTLOG_BLANK); + ssh2_pkt_addstring(s->pktout, + s->cur_prompt->prompts[i]->result); + end_log_omission(ssh, s->pktout); + } + ssh2_pkt_send(ssh, s->pktout); + s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; /*FIXME?*/ + + } else if (s->can_passwd) { + + /* + * Plain old password authentication. + */ + int ret; /* not live over crReturn */ + + s->method = AUTH_PASSWORD; + ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; + ssh->pkt_ctx |= SSH2_PKTCTX_PASSWORD; + + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH password"); + add_prompt(s->cur_prompt, dupprintf("%.90s@%.90s's password: ", + s->username, + ssh->savedhost), + FALSE, SSH_MAX_PASSWORD_LEN); + + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; + } + + /* + * Send the password packet. + * * We pad out the password packet to 256 bytes to make * it harder for an attacker to find the length of the * user's password. @@ -7254,54 +7329,39 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); s->pktout->forcepad = 256; ssh2_pkt_addstring(s->pktout, s->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ ssh2_pkt_addstring(s->pktout, "password"); ssh2_pkt_addbool(s->pktout, FALSE); dont_log_password(ssh, s->pktout, PKTLOG_BLANK); - ssh2_pkt_addstring(s->pktout, s->password); - memset(s->password, 0, sizeof(s->password)); + ssh2_pkt_addstring(s->pktout, + s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); end_log_omission(ssh, s->pktout); ssh2_pkt_send(ssh, s->pktout); logevent("Sent password"); - s->type = AUTH_TYPE_PASSWORD; - } else if (s->method == AUTH_KEYBOARD_INTERACTIVE) { - if (s->curr_prompt == 0) { - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); - s->pktout->forcepad = 256; - ssh2_pkt_adduint32(s->pktout, s->num_prompts); - } - if (s->need_pw) { /* only add pw if we just got one! */ - dont_log_password(ssh, s->pktout, PKTLOG_BLANK); - ssh2_pkt_addstring(s->pktout, s->password); - memset(s->password, 0, sizeof(s->password)); - end_log_omission(ssh, s->pktout); - s->curr_prompt++; - } - if (s->curr_prompt >= s->num_prompts) { - ssh2_pkt_send(ssh, s->pktout); - } else { - /* - * If there are prompts remaining, we set - * `gotit' so that we won't attempt to get - * another packet. Then we go back round the - * loop and will end up retrieving another - * prompt out of the existing packet. Funky or - * what? - */ - s->gotit = TRUE; - } - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; + s->type = AUTH_TYPE_PASSWORD; /*FIXME?*/ + } else { + ssh_disconnect(ssh, NULL, "No supported authentication methods available", SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, FALSE); crStopV; + } + } } ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL; + /* Clear up various bits and pieces from authentication. */ + if (s->publickey_blob) { + sfree(s->publickey_blob); + sfree(s->publickey_comment); + } + /* * Now the connection protocol has started, one way or another. */