1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-03-15 03:23:02 -05:00
putty-source/ssh/login1.c
Simon Tatham a2ff884512 Richer data type for interactive prompt results.
All the seat functions that request an interactive prompt of some kind
to the user - both the main seat_get_userpass_input and the various
confirmation dialogs for things like host keys - were using a simple
int return value, with the general semantics of 0 = "fail", 1 =
"proceed" (and in the case of seat_get_userpass_input, answers to the
prompts were provided), and -1 = "request in progress, wait for a
callback".

In this commit I change all those functions' return types to a new
struct called SeatPromptResult, whose primary field is an enum
replacing those simple integer values.

The main purpose is that the enum has not three but _four_ values: the
"fail" result has been split into 'user abort' and 'software abort'.
The distinction is that a user abort occurs as a result of an
interactive UI action, such as the user clicking 'cancel' in a dialog
box or hitting ^D or ^C at a terminal password prompt - and therefore,
there's no need to display an error message telling the user that the
interactive operation has failed, because the user already knows,
because they _did_ it. 'Software abort' is from any other cause, where
PuTTY is the first to know there was a problem, and has to tell the
user.

We already had this 'user abort' vs 'software abort' distinction in
other parts of the code - the SSH backend has separate termination
functions which protocol layers can call. But we assumed that any
failure from an interactive prompt request fell into the 'user abort'
category, which is not true. A couple of examples: if you configure a
host key fingerprint in your saved session via the SSH > Host keys
pane, and the server presents a host key that doesn't match it, then
verify_ssh_host_key would report that the user had aborted the
connection, and feel no need to tell the user what had gone wrong!
Similarly, if a password provided on the command line was not
accepted, then (after I fixed the semantics of that in the previous
commit) the same wrong handling would occur.

So now, those Seat prompt functions too can communicate whether the
user or the software originated a connection abort. And in the latter
case, we also provide an error message to present to the user. Result:
in those two example cases (and others), error messages should no
longer go missing.

Implementation note: to avoid the hassle of having the error message
in a SeatPromptResult being a dynamically allocated string (and hence,
every recipient of one must always check whether it's non-NULL and
free it on every exit path, plus being careful about copying the
struct around), I've instead arranged that the structure contains a
function pointer and a couple of parameters, so that the string form
of the message can be constructed on demand. That way, the only users
who need to free it are the ones who actually _asked_ for it in the
first place, which is a much smaller set.

(This is one of the rare occasions that I regret not having C++'s
extra features available in this code base - a unique_ptr or
shared_ptr to a string would have been just the thing here, and the
compiler would have done all the hard work for me of remembering where
to insert the frees!)
2021-12-28 18:08:31 +00:00

1195 lines
46 KiB
C

/*
* Packet protocol layer for the SSH-1 login phase (combining what
* SSH-2 would think of as key exchange and user authentication).
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "mpint.h"
#include "bpp.h"
#include "ppl.h"
#include "sshcr.h"
typedef struct agent_key {
RSAKey key;
strbuf *comment;
ptrlen blob; /* only used during initial parsing of agent response */
} agent_key;
struct ssh1_login_state {
int crState;
PacketProtocolLayer *successor_layer;
Conf *conf;
char *savedhost;
int savedport;
bool try_agent_auth, is_trivial_auth;
int remote_protoflags;
int local_protoflags;
unsigned char session_key[32];
char *username;
agent_pending_query *auth_agent_query;
int len;
unsigned char *rsabuf;
unsigned long supported_ciphers_mask, supported_auths_mask;
bool tried_publickey, tried_agent;
bool tis_auth_refused, ccard_auth_refused;
unsigned char cookie[8];
unsigned char session_id[16];
int cipher_type;
strbuf *publickey_blob;
char *publickey_comment;
bool privatekey_available, privatekey_encrypted;
prompts_t *cur_prompt;
SeatPromptResult spr;
char c;
int pwpkt_type;
void *agent_response_to_free;
ptrlen agent_response;
BinarySource asrc[1]; /* response from SSH agent */
size_t agent_keys_len;
agent_key *agent_keys;
size_t agent_key_index, agent_key_limit;
bool authed;
RSAKey key;
Filename *keyfile;
RSAKey servkey, hostkey;
StripCtrlChars *tis_scc;
bool tis_scc_initialised;
PacketProtocolLayer ppl;
};
static void ssh1_login_free(PacketProtocolLayer *);
static void ssh1_login_process_queue(PacketProtocolLayer *);
static void ssh1_login_dialog_callback(void *, SeatPromptResult);
static void ssh1_login_special_cmd(PacketProtocolLayer *ppl,
SessionSpecialCode code, int arg);
static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
static const PacketProtocolLayerVtable ssh1_login_vtable = {
.free = ssh1_login_free,
.process_queue = ssh1_login_process_queue,
.get_specials = ssh1_common_get_specials,
.special_cmd = ssh1_login_special_cmd,
.reconfigure = ssh1_login_reconfigure,
.queued_data_size = ssh_ppl_default_queued_data_size,
.name = NULL, /* no layer names in SSH-1 */
};
static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req);
static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen);
PacketProtocolLayer *ssh1_login_new(
Conf *conf, const char *host, int port,
PacketProtocolLayer *successor_layer)
{
struct ssh1_login_state *s = snew(struct ssh1_login_state);
memset(s, 0, sizeof(*s));
s->ppl.vt = &ssh1_login_vtable;
s->conf = conf_copy(conf);
s->savedhost = dupstr(host);
s->savedport = port;
s->successor_layer = successor_layer;
s->is_trivial_auth = true;
return &s->ppl;
}
static void ssh1_login_free(PacketProtocolLayer *ppl)
{
struct ssh1_login_state *s =
container_of(ppl, struct ssh1_login_state, ppl);
if (s->successor_layer)
ssh_ppl_free(s->successor_layer);
conf_free(s->conf);
sfree(s->savedhost);
sfree(s->rsabuf);
sfree(s->username);
if (s->publickey_blob)
strbuf_free(s->publickey_blob);
sfree(s->publickey_comment);
if (s->cur_prompt)
free_prompts(s->cur_prompt);
if (s->agent_keys) {
for (size_t i = 0; i < s->agent_keys_len; i++) {
freersakey(&s->agent_keys[i].key);
strbuf_free(s->agent_keys[i].comment);
}
sfree(s->agent_keys);
}
sfree(s->agent_response_to_free);
if (s->auth_agent_query)
agent_cancel_query(s->auth_agent_query);
sfree(s);
}
static bool ssh1_login_filter_queue(struct ssh1_login_state *s)
{
return ssh1_common_filter_queue(&s->ppl);
}
static PktIn *ssh1_login_pop(struct ssh1_login_state *s)
{
if (ssh1_login_filter_queue(s))
return NULL;
return pq_pop(s->ppl.in_pq);
}
static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s);
static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
{
struct ssh1_login_state *s =
container_of(ppl, struct ssh1_login_state, ppl);
PktIn *pktin;
PktOut *pkt;
int i;
/* Filter centrally handled messages off the front of the queue on
* every entry to this coroutine, no matter where we're resuming
* from, even if we're _not_ looping on pq_pop. That way we can
* still proactively handle those messages even if we're waiting
* for a user response. */
if (ssh1_login_filter_queue(s))
return;
crBegin(s->crState);
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
if (pktin->type != SSH1_SMSG_PUBLIC_KEY) {
ssh_proto_error(s->ppl.ssh, "Public key packet not received");
return;
}
ppl_logevent("Received public keys");
{
ptrlen pl = get_data(pktin, 8);
memcpy(s->cookie, pl.ptr, pl.len);
}
get_rsa_ssh1_pub(pktin, &s->servkey, RSA_SSH1_EXPONENT_FIRST);
get_rsa_ssh1_pub(pktin, &s->hostkey, RSA_SSH1_EXPONENT_FIRST);
s->hostkey.comment = NULL; /* avoid confusing rsa_ssh1_fingerprint */
/*
* Log the host key fingerprint.
*/
if (!get_err(pktin)) {
char *fingerprint = rsa_ssh1_fingerprint(&s->hostkey);
ppl_logevent("Host key fingerprint is:");
ppl_logevent(" %s", fingerprint);
sfree(fingerprint);
}
s->remote_protoflags = get_uint32(pktin);
s->supported_ciphers_mask = get_uint32(pktin);
s->supported_auths_mask = get_uint32(pktin);
if (get_err(pktin)) {
ssh_proto_error(s->ppl.ssh, "Bad SSH-1 public key packet");
return;
}
if ((s->ppl.remote_bugs & BUG_CHOKES_ON_RSA))
s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA);
s->local_protoflags =
s->remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED;
s->local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER;
ssh1_compute_session_id(s->session_id, s->cookie,
&s->hostkey, &s->servkey);
random_read(s->session_key, 32);
/*
* Verify that the `bits' and `bytes' parameters match.
*/
if (s->hostkey.bits > s->hostkey.bytes * 8 ||
s->servkey.bits > s->servkey.bytes * 8) {
ssh_proto_error(s->ppl.ssh, "SSH-1 public keys were badly formatted");
return;
}
s->len = 32;
if (s->len < s->hostkey.bytes)
s->len = s->hostkey.bytes;
if (s->len < s->servkey.bytes)
s->len = s->servkey.bytes;
s->rsabuf = snewn(s->len, unsigned char);
/*
* Verify the host key.
*/
{
char *keystr = rsastr_fmt(&s->hostkey);
char *keydisp = ssh1_pubkey_str(&s->hostkey);
char **fingerprints = rsa_ssh1_fake_all_fingerprints(&s->hostkey);
s->spr = verify_ssh_host_key(
ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, NULL,
"rsa", keystr, keydisp, fingerprints,
ssh1_login_dialog_callback, s);
ssh2_free_all_fingerprints(fingerprints);
sfree(keydisp);
sfree(keystr);
}
#ifdef FUZZING
s->spr = SPR_OK;
#endif
crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
if (spr_is_abort(s->spr)) {
ssh_spr_close(s->ppl.ssh, s->spr, "host key verification");
return;
}
for (i = 0; i < 32; i++) {
s->rsabuf[i] = s->session_key[i];
if (i < 16)
s->rsabuf[i] ^= s->session_id[i];
}
{
RSAKey *smaller = (s->hostkey.bytes > s->servkey.bytes ?
&s->servkey : &s->hostkey);
RSAKey *larger = (s->hostkey.bytes > s->servkey.bytes ?
&s->hostkey : &s->servkey);
if (!rsa_ssh1_encrypt(s->rsabuf, 32, smaller) ||
!rsa_ssh1_encrypt(s->rsabuf, smaller->bytes, larger)) {
ssh_proto_error(s->ppl.ssh, "SSH-1 public key encryptions failed "
"due to bad formatting");
return;
}
}
ppl_logevent("Encrypted session key");
{
bool cipher_chosen = false, warn = false;
const char *cipher_string = NULL;
int i;
for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) {
int next_cipher = conf_get_int_int(
s->conf, CONF_ssh_cipherlist, i);
if (next_cipher == CIPHER_WARN) {
/* If/when we choose a cipher, warn about it */
warn = true;
} else if (next_cipher == CIPHER_AES) {
/* XXX Probably don't need to mention this. */
ppl_logevent("AES not supported in SSH-1, skipping");
} else {
switch (next_cipher) {
case CIPHER_3DES: s->cipher_type = SSH1_CIPHER_3DES;
cipher_string = "3DES"; break;
case CIPHER_BLOWFISH: s->cipher_type = SSH1_CIPHER_BLOWFISH;
cipher_string = "Blowfish"; break;
case CIPHER_DES: s->cipher_type = SSH1_CIPHER_DES;
cipher_string = "single-DES"; break;
}
if (s->supported_ciphers_mask & (1 << s->cipher_type))
cipher_chosen = true;
}
}
if (!cipher_chosen) {
if ((s->supported_ciphers_mask & (1 << SSH1_CIPHER_3DES)) == 0) {
ssh_proto_error(s->ppl.ssh, "Server violates SSH-1 protocol "
"by not supporting 3DES encryption");
} else {
/* shouldn't happen */
ssh_sw_abort(s->ppl.ssh, "No supported ciphers found");
}
return;
}
/* Warn about chosen cipher if necessary. */
if (warn) {
s->spr = seat_confirm_weak_crypto_primitive(
ppl_get_iseat(&s->ppl), "cipher", cipher_string,
ssh1_login_dialog_callback, s);
crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
if (spr_is_abort(s->spr)) {
ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning");
return;
}
}
}
switch (s->cipher_type) {
case SSH1_CIPHER_3DES:
ppl_logevent("Using 3DES encryption");
break;
case SSH1_CIPHER_DES:
ppl_logevent("Using single-DES encryption");
break;
case SSH1_CIPHER_BLOWFISH:
ppl_logevent("Using Blowfish encryption");
break;
}
pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_SESSION_KEY);
put_byte(pkt, s->cipher_type);
put_data(pkt, s->cookie, 8);
put_uint16(pkt, s->len * 8);
put_data(pkt, s->rsabuf, s->len);
put_uint32(pkt, s->local_protoflags);
pq_push(s->ppl.out_pq, pkt);
ppl_logevent("Trying to enable encryption...");
sfree(s->rsabuf);
s->rsabuf = NULL;
/*
* Force the BPP to synchronously marshal all packets up to and
* including the SESSION_KEY into wire format, before we turn on
* crypto.
*/
ssh_bpp_handle_output(s->ppl.bpp);
{
const ssh_cipheralg *cipher =
(s->cipher_type == SSH1_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 :
s->cipher_type == SSH1_CIPHER_DES ? &ssh_des : &ssh_3des_ssh1);
ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key);
}
freersakey(&s->servkey);
freersakey(&s->hostkey);
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
if (pktin->type != SSH1_SMSG_SUCCESS) {
ssh_proto_error(s->ppl.ssh, "Encryption not successfully enabled");
return;
}
ppl_logevent("Successfully started encryption");
if ((s->username = get_remote_username(s->conf)) == NULL) {
s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
s->cur_prompt->to_server = true;
s->cur_prompt->from_server = false;
s->cur_prompt->name = dupstr("SSH login name");
add_prompt(s->cur_prompt, dupstr("login as: "), true);
s->spr = seat_get_userpass_input(
ppl_get_iseat(&s->ppl), s->cur_prompt);
while (s->spr.kind == SPRK_INCOMPLETE) {
crReturnV;
s->spr = seat_get_userpass_input(
ppl_get_iseat(&s->ppl), s->cur_prompt);
}
if (spr_is_abort(s->spr)) {
/*
* Failed to get a username. Terminate.
*/
ssh_spr_close(s->ppl.ssh, s->spr, "username prompt");
return;
}
s->username = prompt_get_result(s->cur_prompt->prompts[0]);
free_prompts(s->cur_prompt);
s->cur_prompt = NULL;
}
pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_USER);
put_stringz(pkt, s->username);
pq_push(s->ppl.out_pq, pkt);
ppl_logevent("Sent username \"%s\"", s->username);
if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))
ppl_printf("Sent username \"%s\"\r\n", s->username);
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
if (!(s->supported_auths_mask & (1 << SSH1_AUTH_RSA))) {
/* We must not attempt PK auth. Pretend we've already tried it. */
s->tried_publickey = s->tried_agent = true;
} else {
s->tried_publickey = s->tried_agent = false;
}
s->tis_auth_refused = s->ccard_auth_refused = false;
/*
* Load the public half of any configured keyfile for later use.
*/
s->keyfile = conf_get_filename(s->conf, CONF_keyfile);
if (!filename_is_null(s->keyfile)) {
int keytype;
ppl_logevent("Reading key file \"%s\"", filename_to_str(s->keyfile));
keytype = key_type(s->keyfile);
if (keytype == SSH_KEYTYPE_SSH1 ||
keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
const char *error;
s->publickey_blob = strbuf_new();
if (rsa1_loadpub_f(s->keyfile,
BinarySink_UPCAST(s->publickey_blob),
&s->publickey_comment, &error)) {
s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1);
if (!s->privatekey_available)
ppl_logevent("Key file contains public key only");
s->privatekey_encrypted = rsa1_encrypted_f(s->keyfile, NULL);
} else {
ppl_logevent("Unable to load key (%s)", error);
ppl_printf("Unable to load key file \"%s\" (%s)\r\n",
filename_to_str(s->keyfile), error);
strbuf_free(s->publickey_blob);
s->publickey_blob = NULL;
}
} else {
ppl_logevent("Unable to use this key file (%s)",
key_type_to_str(keytype));
ppl_printf("Unable to use key file \"%s\" (%s)\r\n",
filename_to_str(s->keyfile),
key_type_to_str(keytype));
}
}
/* Check whether we're configured to try Pageant, and also whether
* it's available. */
s->try_agent_auth = (conf_get_bool(s->conf, CONF_tryagent) &&
agent_exists());
while (pktin->type == SSH1_SMSG_FAILURE) {
s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
if (s->try_agent_auth && !s->tried_agent) {
/*
* Attempt RSA authentication using Pageant.
*/
s->authed = false;
s->tried_agent = true;
ppl_logevent("Pageant is running. Requesting keys.");
/* Request the keys held by the agent. */
{
strbuf *request = strbuf_new_for_agent_query();
put_byte(request, SSH1_AGENTC_REQUEST_RSA_IDENTITIES);
ssh1_login_agent_query(s, request);
strbuf_free(request);
crMaybeWaitUntilV(!s->auth_agent_query);
}
BinarySource_BARE_INIT_PL(s->asrc, s->agent_response);
get_uint32(s->asrc); /* skip length field */
if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
size_t nkeys = get_uint32(s->asrc);
size_t origpos = s->asrc->pos;
/*
* Check that the agent response is well formed.
*/
for (size_t i = 0; i < nkeys; i++) {
get_rsa_ssh1_pub(s->asrc, NULL, RSA_SSH1_EXPONENT_FIRST);
get_string(s->asrc); /* comment */
if (get_err(s->asrc)) {
ppl_logevent("Pageant's response was truncated");
goto parsed_agent_query;
}
}
/*
* Copy the list of public-key blobs out of the Pageant
* response.
*/
BinarySource_REWIND_TO(s->asrc, origpos);
s->agent_keys_len = nkeys;
s->agent_keys = snewn(s->agent_keys_len, agent_key);
for (size_t i = 0; i < nkeys; i++) {
memset(&s->agent_keys[i].key, 0,
sizeof(s->agent_keys[i].key));
const char *blobstart = get_ptr(s->asrc);
get_rsa_ssh1_pub(s->asrc, &s->agent_keys[i].key,
RSA_SSH1_EXPONENT_FIRST);
const char *blobend = get_ptr(s->asrc);
s->agent_keys[i].comment = strbuf_new();
put_datapl(s->agent_keys[i].comment, get_string(s->asrc));
s->agent_keys[i].blob = make_ptrlen(
blobstart, blobend - blobstart);
}
ppl_logevent("Pageant has %"SIZEu" SSH-1 keys", nkeys);
if (s->publickey_blob) {
/*
* If we've been given a specific public key blob,
* filter the list of keys to try from the agent
* down to only that one, or none if it's not
* there.
*/
ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob);
size_t i;
for (i = 0; i < nkeys; i++) {
if (ptrlen_eq_ptrlen(our_blob, s->agent_keys[i].blob))
break;
}
if (i < nkeys) {
ppl_logevent("Pageant key #%"SIZEu" matches "
"configured key file", i);
s->agent_key_index = i;
s->agent_key_limit = i+1;
} else {
ppl_logevent("Configured key file not in Pageant");
s->agent_key_index = 0;
s->agent_key_limit = 0;
}
} else {
/*
* Otherwise, try them all.
*/
s->agent_key_index = 0;
s->agent_key_limit = nkeys;
}
} else {
ppl_logevent("Failed to get reply from Pageant");
}
parsed_agent_query:;
for (; s->agent_key_index < s->agent_key_limit;
s->agent_key_index++) {
ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index);
pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA);
put_mp_ssh1(pkt,
s->agent_keys[s->agent_key_index].key.modulus);
pq_push(s->ppl.out_pq, pkt);
crMaybeWaitUntilV((pktin = ssh1_login_pop(s))
!= NULL);
if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
ppl_logevent("Key refused");
continue;
}
ppl_logevent("Received RSA challenge");
{
mp_int *challenge = get_mp_ssh1(pktin);
if (get_err(pktin)) {
mp_free(challenge);
ssh_proto_error(s->ppl.ssh, "Server's RSA challenge "
"was badly formatted");
return;
}
strbuf *agentreq = strbuf_new_for_agent_query();
put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE);
rsa_ssh1_public_blob(
BinarySink_UPCAST(agentreq),
&s->agent_keys[s->agent_key_index].key,
RSA_SSH1_EXPONENT_FIRST);
put_mp_ssh1(agentreq, challenge);
mp_free(challenge);
put_data(agentreq, s->session_id, 16);
put_uint32(agentreq, 1); /* response format */
ssh1_login_agent_query(s, agentreq);
strbuf_free(agentreq);
crMaybeWaitUntilV(!s->auth_agent_query);
}
{
const unsigned char *ret = s->agent_response.ptr;
if (ret) {
if (s->agent_response.len >= 5+16 &&
ret[4] == SSH1_AGENT_RSA_RESPONSE) {
ppl_logevent("Sending Pageant's response");
pkt = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE);
put_data(pkt, ret + 5, 16);
pq_push(s->ppl.out_pq, pkt);
s->is_trivial_auth = false;
crMaybeWaitUntilV(
(pktin = ssh1_login_pop(s))
!= NULL);
if (pktin->type == SSH1_SMSG_SUCCESS) {
ppl_logevent("Pageant's response "
"accepted");
if (seat_verbose(s->ppl.seat)) {
ptrlen comment = ptrlen_from_strbuf(
s->agent_keys[s->agent_key_index].
comment);
ppl_printf("Authenticated using RSA "
"key \"%.*s\" from "
"agent\r\n",
PTRLEN_PRINTF(comment));
}
s->authed = true;
} else
ppl_logevent("Pageant's response not "
"accepted");
} else {
ppl_logevent("Pageant failed to answer "
"challenge");
sfree((char *)ret);
}
} else {
ppl_logevent("No reply received from Pageant");
}
}
if (s->authed)
break;
}
if (s->authed)
break;
}
if (s->publickey_blob && s->privatekey_available &&
!s->tried_publickey) {
/*
* Try public key authentication with the specified
* key file.
*/
bool got_passphrase; /* need not be kept over crReturn */
if (seat_verbose(s->ppl.seat))
ppl_printf("Trying public key authentication.\r\n");
ppl_logevent("Trying public key \"%s\"",
filename_to_str(s->keyfile));
s->tried_publickey = true;
got_passphrase = false;
while (!got_passphrase) {
/*
* Get a passphrase, if necessary.
*/
int retd;
char *passphrase = NULL; /* only written after crReturn */
const char *error;
if (!s->privatekey_encrypted) {
if (seat_verbose(s->ppl.seat))
ppl_printf("No passphrase required.\r\n");
passphrase = NULL;
} else {
s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
s->cur_prompt->to_server = false;
s->cur_prompt->from_server = false;
s->cur_prompt->name = dupstr("SSH key passphrase");
add_prompt(s->cur_prompt,
dupprintf("Passphrase for key \"%s\": ",
s->publickey_comment), false);
s->spr = seat_get_userpass_input(
ppl_get_iseat(&s->ppl), s->cur_prompt);
while (s->spr.kind == SPRK_INCOMPLETE) {
crReturnV;
s->spr = seat_get_userpass_input(
ppl_get_iseat(&s->ppl), s->cur_prompt);
}
if (spr_is_abort(s->spr)) {
/* Failed to get a passphrase. Terminate. */
ssh_spr_close(s->ppl.ssh, s->spr, "passphrase prompt");
return;
}
passphrase = prompt_get_result(s->cur_prompt->prompts[0]);
free_prompts(s->cur_prompt);
s->cur_prompt = NULL;
}
/*
* Try decrypting key with passphrase.
*/
retd = rsa1_load_f(s->keyfile, &s->key, passphrase, &error);
if (passphrase) {
smemclr(passphrase, strlen(passphrase));
sfree(passphrase);
}
if (retd == 1) {
/* Correct passphrase. */
got_passphrase = true;
} else if (retd == 0) {
ppl_printf("Couldn't load private key from %s (%s).\r\n",
filename_to_str(s->keyfile), error);
got_passphrase = false;
break; /* go and try something else */
} else if (retd == -1) {
ppl_printf("Wrong passphrase.\r\n");
got_passphrase = false;
/* and try again */
} else {
unreachable("unexpected return from rsa1_load_f()");
}
}
if (got_passphrase) {
/*
* Send a public key attempt.
*/
pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA);
put_mp_ssh1(pkt, s->key.modulus);
pq_push(s->ppl.out_pq, pkt);
crMaybeWaitUntilV((pktin = ssh1_login_pop(s))
!= NULL);
if (pktin->type == SSH1_SMSG_FAILURE) {
ppl_printf("Server refused our public key.\r\n");
continue; /* go and try something else */
}
if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
" in response to offer of public key, "
"type %d (%s)", pktin->type,
ssh1_pkt_type(pktin->type));
return;
}
{
int i;
unsigned char buffer[32];
mp_int *challenge, *response;
challenge = get_mp_ssh1(pktin);
if (get_err(pktin)) {
mp_free(challenge);
ssh_proto_error(s->ppl.ssh, "Server's RSA challenge "
"was badly formatted");
return;
}
response = rsa_ssh1_decrypt(challenge, &s->key);
freersapriv(&s->key); /* burn the evidence */
for (i = 0; i < 32; i++) {
buffer[i] = mp_get_byte(response, 31 - i);
}
{
ssh_hash *h = ssh_hash_new(&ssh_md5);
put_data(h, buffer, 32);
put_data(h, s->session_id, 16);
ssh_hash_final(h, buffer);
}
pkt = ssh_bpp_new_pktout(
s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE);
put_data(pkt, buffer, 16);
pq_push(s->ppl.out_pq, pkt);
s->is_trivial_auth = false;
mp_free(challenge);
mp_free(response);
}
crMaybeWaitUntilV((pktin = ssh1_login_pop(s))
!= NULL);
if (pktin->type == SSH1_SMSG_FAILURE) {
if (seat_verbose(s->ppl.seat))
ppl_printf("Failed to authenticate with"
" our public key.\r\n");
continue; /* go and try something else */
} else if (pktin->type != SSH1_SMSG_SUCCESS) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
" in response to RSA authentication, "
"type %d (%s)", pktin->type,
ssh1_pkt_type(pktin->type));
return;
}
break; /* we're through! */
}
}
/*
* Otherwise, try various forms of password-like authentication.
*/
s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
if (conf_get_bool(s->conf, CONF_try_tis_auth) &&
(s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) &&
!s->tis_auth_refused) {
ssh1_login_setup_tis_scc(s);
s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
ppl_logevent("Requested TIS authentication");
pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_TIS);
pq_push(s->ppl.out_pq, pkt);
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
if (pktin->type == SSH1_SMSG_FAILURE) {
ppl_logevent("TIS authentication declined");
if (seat_interactive(s->ppl.seat))
ppl_printf("TIS authentication refused.\r\n");
s->tis_auth_refused = true;
continue;
} else if (pktin->type == SSH1_SMSG_AUTH_TIS_CHALLENGE) {
ptrlen challenge = get_string(pktin);
if (get_err(pktin)) {
ssh_proto_error(s->ppl.ssh, "TIS challenge packet was "
"badly formed");
return;
}
ppl_logevent("Received TIS challenge");
s->cur_prompt->to_server = true;
s->cur_prompt->from_server = true;
s->cur_prompt->name = dupstr("SSH TIS authentication");
strbuf *sb = strbuf_new();
put_datapl(sb, PTRLEN_LITERAL("\
-- TIS authentication challenge from server: ---------------------------------\
\r\n"));
if (s->tis_scc) {
stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb));
put_datapl(s->tis_scc, challenge);
stripctrl_retarget(s->tis_scc, NULL);
} else {
put_datapl(sb, challenge);
}
if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL))
put_datapl(sb, PTRLEN_LITERAL("\r\n"));
put_datapl(sb, PTRLEN_LITERAL("\
-- End of TIS authentication challenge from server: --------------------------\
\r\n"));
s->cur_prompt->instruction = strbuf_to_str(sb);
s->cur_prompt->instr_reqd = true;
add_prompt(s->cur_prompt, dupstr(
"TIS authentication response: "), false);
} else {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
" in response to TIS authentication, "
"type %d (%s)", pktin->type,
ssh1_pkt_type(pktin->type));
return;
}
} else if (conf_get_bool(s->conf, CONF_try_tis_auth) &&
(s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) &&
!s->ccard_auth_refused) {
ssh1_login_setup_tis_scc(s);
s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE;
ppl_logevent("Requested CryptoCard authentication");
pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_CCARD);
pq_push(s->ppl.out_pq, pkt);
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
if (pktin->type == SSH1_SMSG_FAILURE) {
ppl_logevent("CryptoCard authentication declined");
ppl_printf("CryptoCard authentication refused.\r\n");
s->ccard_auth_refused = true;
continue;
} else if (pktin->type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
ptrlen challenge = get_string(pktin);
if (get_err(pktin)) {
ssh_proto_error(s->ppl.ssh, "CryptoCard challenge packet "
"was badly formed");
return;
}
ppl_logevent("Received CryptoCard challenge");
s->cur_prompt->to_server = true;
s->cur_prompt->from_server = true;
s->cur_prompt->name = dupstr("SSH CryptoCard authentication");
strbuf *sb = strbuf_new();
put_datapl(sb, PTRLEN_LITERAL("\
-- CryptoCard authentication challenge from server: --------------------------\
\r\n"));
if (s->tis_scc) {
stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb));
put_datapl(s->tis_scc, challenge);
stripctrl_retarget(s->tis_scc, NULL);
} else {
put_datapl(sb, challenge);
}
if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL))
put_datapl(sb, PTRLEN_LITERAL("\r\n"));
put_datapl(sb, PTRLEN_LITERAL("\
-- End of CryptoCard authentication challenge from server: -------------------\
\r\n"));
s->cur_prompt->instruction = strbuf_to_str(sb);
s->cur_prompt->instr_reqd = true;
add_prompt(s->cur_prompt, dupstr(
"CryptoCard authentication response: "), false);
} else {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
" in response to TIS authentication, "
"type %d (%s)", pktin->type,
ssh1_pkt_type(pktin->type));
return;
}
}
if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) {
ssh_sw_abort(s->ppl.ssh, "No supported authentication methods "
"available");
return;
}
s->cur_prompt->to_server = true;
s->cur_prompt->from_server = false;
s->cur_prompt->name = dupstr("SSH password");
add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
s->username, s->savedhost),
false);
}
/*
* Show password prompt, having first obtained it via a TIS
* or CryptoCard exchange if we're doing TIS or CryptoCard
* authentication.
*/
s->spr = seat_get_userpass_input(
ppl_get_iseat(&s->ppl), s->cur_prompt);
while (s->spr.kind == SPRK_INCOMPLETE) {
crReturnV;
s->spr = seat_get_userpass_input(
ppl_get_iseat(&s->ppl), s->cur_prompt);
}
if (spr_is_abort(s->spr)) {
/*
* Failed to get a password (for example
* because one was supplied on the command line
* which has already failed to work). Terminate.
*/
ssh_spr_close(s->ppl.ssh, s->spr, "password prompt");
return;
}
if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
/*
* 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 can't deal with SSH1_MSG_IGNORE, at
* least in this context. 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.
*
* A few servers can deal with neither SSH1_MSG_IGNORE
* here _nor_ a padded password string.
* For these servers we are left with no defences
* against password length sniffing.
*/
if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) &&
!(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
/*
* The server can deal with SSH1_MSG_IGNORE, so
* we can use the primary defence.
*/
int bottom, top, pwlen, i;
const char *pw = prompt_get_result_ref(
s->cur_prompt->prompts[0]);
pwlen = strlen(pw);
if (pwlen < 16) {
bottom = 0; /* zero length passwords are OK! :-) */
top = 15;
} else {
bottom = pwlen & ~7;
top = bottom + 7;
}
assert(pwlen >= bottom && pwlen <= top);
for (i = bottom; i <= top; i++) {
if (i == pwlen) {
pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
put_stringz(pkt, pw);
pq_push(s->ppl.out_pq, pkt);
} else {
strbuf *random_data = strbuf_new_nm();
random_read(strbuf_append(random_data, i), i);
pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE);
put_stringsb(pkt, random_data);
pq_push(s->ppl.out_pq, pkt);
}
}
ppl_logevent("Sending password with camouflage packets");
}
else if (!(s->ppl.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.
*/
strbuf *padded_pw = strbuf_new_nm();
ppl_logevent("Sending length-padded password");
pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
put_asciz(padded_pw, prompt_get_result_ref(
s->cur_prompt->prompts[0]));
size_t pad = 63 & -padded_pw->len;
random_read(strbuf_append(padded_pw, pad), pad);
put_stringsb(pkt, padded_pw);
pq_push(s->ppl.out_pq, pkt);
} else {
/*
* The server is believed unable to cope with
* any of our password camouflage methods.
*/
ppl_logevent("Sending unpadded password");
pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
put_stringz(pkt, prompt_get_result_ref(
s->cur_prompt->prompts[0]));
pq_push(s->ppl.out_pq, pkt);
}
} else {
pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
put_stringz(pkt, prompt_get_result_ref(s->cur_prompt->prompts[0]));
pq_push(s->ppl.out_pq, pkt);
}
s->is_trivial_auth = false;
ppl_logevent("Sent password");
free_prompts(s->cur_prompt);
s->cur_prompt = NULL;
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
if (pktin->type == SSH1_SMSG_FAILURE) {
if (seat_verbose(s->ppl.seat))
ppl_printf("Access denied\r\n");
ppl_logevent("Authentication refused");
} else if (pktin->type != SSH1_SMSG_SUCCESS) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
" in response to password authentication, type %d "
"(%s)", pktin->type, ssh1_pkt_type(pktin->type));
return;
}
}
if (conf_get_bool(s->conf, CONF_ssh_no_trivial_userauth) &&
s->is_trivial_auth) {
ssh_proto_error(s->ppl.ssh, "Authentication was trivial! "
"Abandoning session as specified in configuration.");
return;
}
ppl_logevent("Authentication successful");
if (conf_get_bool(s->conf, CONF_compression)) {
ppl_logevent("Requesting compression");
pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_COMPRESSION);
put_uint32(pkt, 6); /* gzip compression level */
pq_push(s->ppl.out_pq, pkt);
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
if (pktin->type == SSH1_SMSG_SUCCESS) {
/*
* We don't have to actually do anything here: the SSH-1
* BPP will take care of automatically starting the
* compression, by recognising our outgoing request packet
* and the success response. (Horrible, but it's the
* easiest way to avoid race conditions if other packets
* cross in transit.)
*/
} else if (pktin->type == SSH1_SMSG_FAILURE) {
ppl_logevent("Server refused to enable compression");
ppl_printf("Server refused to compress\r\n");
} else {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
" in response to compression request, type %d "
"(%s)", pktin->type, ssh1_pkt_type(pktin->type));
return;
}
}
ssh1_connection_set_protoflags(
s->successor_layer, s->local_protoflags, s->remote_protoflags);
{
PacketProtocolLayer *successor = s->successor_layer;
s->successor_layer = NULL; /* avoid freeing it ourself */
ssh_ppl_replace(&s->ppl, successor);
return; /* we've just freed s, so avoid even touching s->crState */
}
crFinishV;
}
static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s)
{
if (s->tis_scc_initialised)
return;
s->tis_scc = seat_stripctrl_new(s->ppl.seat, NULL, SIC_KI_PROMPTS);
if (s->tis_scc)
stripctrl_enable_line_limiting(s->tis_scc);
s->tis_scc_initialised = true;
}
static void ssh1_login_dialog_callback(void *loginv, SeatPromptResult spr)
{
struct ssh1_login_state *s = (struct ssh1_login_state *)loginv;
s->spr = spr;
ssh_ppl_process_queue(&s->ppl);
}
static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req)
{
void *response;
int response_len;
sfree(s->agent_response_to_free);
s->agent_response_to_free = NULL;
s->auth_agent_query = agent_query(req, &response, &response_len,
ssh1_login_agent_callback, s);
if (!s->auth_agent_query)
ssh1_login_agent_callback(s, response, response_len);
}
static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen)
{
struct ssh1_login_state *s = (struct ssh1_login_state *)loginv;
s->auth_agent_query = NULL;
s->agent_response_to_free = reply;
s->agent_response = make_ptrlen(reply, replylen);
queue_idempotent_callback(&s->ppl.ic_process_queue);
}
static void ssh1_login_special_cmd(PacketProtocolLayer *ppl,
SessionSpecialCode code, int arg)
{
struct ssh1_login_state *s =
container_of(ppl, struct ssh1_login_state, ppl);
PktOut *pktout;
if (code == SS_PING || code == SS_NOP) {
if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE);
put_stringz(pktout, "");
pq_push(s->ppl.out_pq, pktout);
}
}
}
static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
{
struct ssh1_login_state *s =
container_of(ppl, struct ssh1_login_state, ppl);
ssh_ppl_reconfigure(s->successor_layer, conf);
}