Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'ssh/login1.c')
-rw-r--r--ssh/login1.c1193
1 files changed, 1193 insertions, 0 deletions
diff --git a/ssh/login1.c b/ssh/login1.c
new file mode 100644
index 00000000..52aaea0b
--- /dev/null
+++ b/ssh/login1.c
@@ -0,0 +1,1193 @@
+/*
+ * 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, 0,
+ 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_dup(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);
+}