/* * Packet protocol layer for the client side of the SSH-2 userauth * protocol (RFC 4252). */ #include #include "putty.h" #include "ssh.h" #include "bpp.h" #include "ppl.h" #include "sshcr.h" #ifndef NO_GSSAPI #include "gssc.h" #include "gss.h" #endif #define BANNER_LIMIT 131072 typedef struct agent_key { strbuf *blob, *comment; ptrlen algorithm; } agent_key; struct ssh2_userauth_state { int crState; PacketProtocolLayer *transport_layer, *successor_layer; Filename *keyfile, *detached_cert_file; bool show_banner, tryagent, notrivialauth, change_username; char *hostname, *fullhostname; int port; char *default_username; bool try_ki_auth, try_gssapi_auth, try_gssapi_kex_auth, gssapi_fwd; ptrlen session_id; enum { AUTH_TYPE_NONE, AUTH_TYPE_PUBLICKEY, AUTH_TYPE_PUBLICKEY_OFFER_LOUD, AUTH_TYPE_PUBLICKEY_OFFER_QUIET, AUTH_TYPE_PASSWORD, AUTH_TYPE_GSSAPI, /* always QUIET */ AUTH_TYPE_KEYBOARD_INTERACTIVE, AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET } type; bool need_pw, can_pubkey, can_passwd, can_keyb_inter; SeatPromptResult spr; bool tried_pubkey_config, done_agent; struct ssh_connection_shared_gss_state *shgss; #ifndef NO_GSSAPI bool can_gssapi; bool can_gssapi_keyex_auth; bool tried_gssapi; bool tried_gssapi_keyex_auth; time_t gss_cred_expiry; Ssh_gss_buf gss_buf; Ssh_gss_buf gss_rcvtok, gss_sndtok; Ssh_gss_stat gss_stat; #endif bool suppress_wait_for_response_packet; strbuf *last_methods_string; bool kbd_inter_refused; prompts_t *cur_prompt; uint32_t num_prompts; const char *username; char *locally_allocated_username; char *password; bool got_username; strbuf *publickey_blob, *detached_cert_blob, *cert_pubkey_diagnosed; bool privatekey_available, privatekey_encrypted; char *publickey_algorithm; char *publickey_comment; void *agent_response_to_free; ptrlen agent_response; BinarySource asrc[1]; /* for reading SSH agent response */ size_t agent_keys_len; agent_key *agent_keys; size_t agent_key_index, agent_key_limit; ptrlen agent_keyalg; unsigned signflags; int len; PktOut *pktout; bool is_trivial_auth; agent_pending_query *auth_agent_query; bufchain banner; bufchain_sink banner_bs; StripCtrlChars *banner_scc; bool banner_scc_initialised; char *authplugin_cmd; Socket *authplugin; uint32_t authplugin_version; Plug authplugin_plug; bufchain authplugin_bc; strbuf *authplugin_incoming_msg; size_t authplugin_backlog; bool authplugin_eof; bool authplugin_ki_active; StripCtrlChars *ki_scc; bool ki_scc_initialised; bool ki_printed_header; PacketProtocolLayer ppl; }; static void ssh2_userauth_free(PacketProtocolLayer *); static void ssh2_userauth_process_queue(PacketProtocolLayer *); static bool ssh2_userauth_get_specials( PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl, SessionSpecialCode code, int arg); static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf); static void ssh2_userauth_agent_query(struct ssh2_userauth_state *, strbuf *); static void ssh2_userauth_agent_callback(void *, void *, int); static void ssh2_userauth_add_sigblob( struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob); static void ssh2_userauth_add_alg_and_publickey( struct ssh2_userauth_state *s, PktOut *pkt, ptrlen alg, ptrlen pkblob); static void ssh2_userauth_add_session_id( struct ssh2_userauth_state *s, strbuf *sigdata); #ifndef NO_GSSAPI static PktOut *ssh2_userauth_gss_packet( struct ssh2_userauth_state *s, const char *authtype); #endif static bool ssh2_userauth_ki_setup_prompts( struct ssh2_userauth_state *s, BinarySource *src, bool plugin); static bool ssh2_userauth_ki_run_prompts(struct ssh2_userauth_state *s); static void ssh2_userauth_ki_write_responses( struct ssh2_userauth_state *s, BinarySink *bs); static const PacketProtocolLayerVtable ssh2_userauth_vtable = { .free = ssh2_userauth_free, .process_queue = ssh2_userauth_process_queue, .get_specials = ssh2_userauth_get_specials, .special_cmd = ssh2_userauth_special_cmd, .reconfigure = ssh2_userauth_reconfigure, .queued_data_size = ssh_ppl_default_queued_data_size, .name = "ssh-userauth", }; PacketProtocolLayer *ssh2_userauth_new( PacketProtocolLayer *successor_layer, const char *hostname, int port, const char *fullhostname, Filename *keyfile, Filename *detached_cert_file, bool show_banner, bool tryagent, bool notrivialauth, const char *default_username, bool change_username, bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss, const char *authplugin_cmd) { struct ssh2_userauth_state *s = snew(struct ssh2_userauth_state); memset(s, 0, sizeof(*s)); s->ppl.vt = &ssh2_userauth_vtable; s->successor_layer = successor_layer; s->hostname = dupstr(hostname); s->port = port; s->fullhostname = dupstr(fullhostname); s->keyfile = filename_copy(keyfile); s->detached_cert_file = filename_copy(detached_cert_file); s->show_banner = show_banner; s->tryagent = tryagent; s->notrivialauth = notrivialauth; s->default_username = dupstr(default_username); s->change_username = change_username; s->try_ki_auth = try_ki_auth; s->try_gssapi_auth = try_gssapi_auth; s->try_gssapi_kex_auth = try_gssapi_kex_auth; s->gssapi_fwd = gssapi_fwd; s->shgss = shgss; s->last_methods_string = strbuf_new(); s->is_trivial_auth = true; bufchain_init(&s->banner); bufchain_sink_init(&s->banner_bs, &s->banner); s->authplugin_cmd = dupstr(authplugin_cmd); bufchain_init(&s->authplugin_bc); return &s->ppl; } void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth, PacketProtocolLayer *transport) { struct ssh2_userauth_state *s = container_of(userauth, struct ssh2_userauth_state, ppl); s->transport_layer = transport; } static void ssh2_userauth_free(PacketProtocolLayer *ppl) { struct ssh2_userauth_state *s = container_of(ppl, struct ssh2_userauth_state, ppl); bufchain_clear(&s->banner); if (s->successor_layer) ssh_ppl_free(s->successor_layer); if (s->agent_keys) { for (size_t i = 0; i < s->agent_keys_len; i++) { strbuf_free(s->agent_keys[i].blob); 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); filename_free(s->keyfile); filename_free(s->detached_cert_file); sfree(s->default_username); sfree(s->locally_allocated_username); sfree(s->hostname); sfree(s->fullhostname); if (s->cur_prompt) free_prompts(s->cur_prompt); sfree(s->publickey_comment); sfree(s->publickey_algorithm); if (s->publickey_blob) strbuf_free(s->publickey_blob); if (s->detached_cert_blob) strbuf_free(s->detached_cert_blob); if (s->cert_pubkey_diagnosed) strbuf_free(s->cert_pubkey_diagnosed); strbuf_free(s->last_methods_string); if (s->banner_scc) stripctrl_free(s->banner_scc); if (s->ki_scc) stripctrl_free(s->ki_scc); sfree(s->authplugin_cmd); if (s->authplugin) sk_close(s->authplugin); bufchain_clear(&s->authplugin_bc); if (s->authplugin_incoming_msg) strbuf_free(s->authplugin_incoming_msg); sfree(s); } static void ssh2_userauth_filter_queue(struct ssh2_userauth_state *s) { PktIn *pktin; ptrlen string; while ((pktin = pq_peek(s->ppl.in_pq)) != NULL) { switch (pktin->type) { case SSH2_MSG_USERAUTH_BANNER: if (!s->show_banner) { pq_pop(s->ppl.in_pq); break; } string = get_string(pktin); if (string.len > BANNER_LIMIT - bufchain_size(&s->banner)) string.len = BANNER_LIMIT - bufchain_size(&s->banner); if (!s->banner_scc_initialised) { s->banner_scc = seat_stripctrl_new( s->ppl.seat, BinarySink_UPCAST(&s->banner_bs), SIC_BANNER); if (s->banner_scc) stripctrl_enable_line_limiting(s->banner_scc); s->banner_scc_initialised = true; } if (s->banner_scc) put_datapl(s->banner_scc, string); else put_datapl(&s->banner_bs, string); pq_pop(s->ppl.in_pq); break; default: return; } } } static PktIn *ssh2_userauth_pop(struct ssh2_userauth_state *s) { ssh2_userauth_filter_queue(s); return pq_pop(s->ppl.in_pq); } static bool ssh2_userauth_signflags(struct ssh2_userauth_state *s, unsigned *signflags, const char **algname) { *signflags = 0; /* default */ const ssh_keyalg *alg = find_pubkey_alg(*algname); if (!alg) return false; /* we don't know how to upgrade this */ unsigned supported_flags = ssh_keyalg_supported_flags(alg); if (s->ppl.bpp->ext_info_rsa_sha512_ok && (supported_flags & SSH_AGENT_RSA_SHA2_512)) { *signflags = SSH_AGENT_RSA_SHA2_512; } else if (s->ppl.bpp->ext_info_rsa_sha256_ok && (supported_flags & SSH_AGENT_RSA_SHA2_256)) { *signflags = SSH_AGENT_RSA_SHA2_256; } else { return false; } *algname = ssh_keyalg_alternate_ssh_id(alg, *signflags); return true; } static void authplugin_plug_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, const char *err_msg, int err_code) { struct ssh2_userauth_state *s = container_of( plug, struct ssh2_userauth_state, authplugin_plug); PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ if (type == PLUGLOG_PROXY_MSG) ppl_logevent("%s", err_msg); } static void authplugin_plug_closing( Plug *plug, PlugCloseType type, const char *error_msg) { struct ssh2_userauth_state *s = container_of( plug, struct ssh2_userauth_state, authplugin_plug); s->authplugin_eof = true; queue_idempotent_callback(&s->ppl.ic_process_queue); } static void authplugin_plug_receive( Plug *plug, int urgent, const char *data, size_t len) { struct ssh2_userauth_state *s = container_of( plug, struct ssh2_userauth_state, authplugin_plug); bufchain_add(&s->authplugin_bc, data, len); queue_idempotent_callback(&s->ppl.ic_process_queue); } static void authplugin_plug_sent(Plug *plug, size_t bufsize) { struct ssh2_userauth_state *s = container_of( plug, struct ssh2_userauth_state, authplugin_plug); s->authplugin_backlog = bufsize; queue_idempotent_callback(&s->ppl.ic_process_queue); } static const PlugVtable authplugin_plugvt = { .log = authplugin_plug_log, .closing = authplugin_plug_closing, .receive = authplugin_plug_receive, .sent = authplugin_plug_sent, }; static strbuf *authplugin_newmsg(uint8_t type) { strbuf *amsg = strbuf_new_nm(); put_uint32(amsg, 0); /* fill in later */ put_byte(amsg, type); return amsg; } static void authplugin_send_free(struct ssh2_userauth_state *s, strbuf *amsg) { PUT_32BIT_MSB_FIRST(amsg->u, amsg->len - 4); assert(s->authplugin); s->authplugin_backlog = sk_write(s->authplugin, amsg->u, amsg->len); strbuf_free(amsg); } static bool authplugin_expect_msg(struct ssh2_userauth_state *s, unsigned *type, BinarySource *src) { if (s->authplugin_eof) { *type = PLUGIN_EOF; return true; } uint8_t len[4]; if (!bufchain_try_fetch(&s->authplugin_bc, len, 4)) return false; size_t size = GET_32BIT_MSB_FIRST(len); if (bufchain_size(&s->authplugin_bc) - 4 < size) return false; if (s->authplugin_incoming_msg) { strbuf_clear(s->authplugin_incoming_msg); } else { s->authplugin_incoming_msg = strbuf_new_nm(); } bufchain_consume(&s->authplugin_bc, 4); /* eat length field */ bufchain_fetch_consume( &s->authplugin_bc, strbuf_append(s->authplugin_incoming_msg, size), size); BinarySource_BARE_INIT_PL( src, ptrlen_from_strbuf(s->authplugin_incoming_msg)); *type = get_byte(src); if (get_err(src)) *type = PLUGIN_NOTYPE; return true; } static void authplugin_bad_packet(struct ssh2_userauth_state *s, unsigned type, const char *fmt, ...) { strbuf *msg = strbuf_new(); switch (type) { case PLUGIN_EOF: put_dataz(msg, "Unexpected end of file from auth helper plugin"); break; case PLUGIN_NOTYPE: put_dataz(msg, "Received malformed packet from auth helper plugin " "(too short to have a type code)"); break; default: put_fmt(msg, "Received unknown message type %u " "from auth helper plugin", type); break; #define CASEDECL(name, value) \ case name: \ put_fmt(msg, "Received unexpected %s message from auth helper " \ "plugin", #name); \ break; AUTHPLUGIN_MSG_NAMES(CASEDECL); #undef CASEDECL } if (fmt) { put_dataz(msg, " ("); va_list ap; va_start(ap, fmt); put_fmt(msg, fmt, ap); va_end(ap); put_dataz(msg, ")"); } ssh_sw_abort(s->ppl.ssh, "%s", msg->s); strbuf_free(msg); } static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) { struct ssh2_userauth_state *s = container_of(ppl, struct ssh2_userauth_state, ppl); PktIn *pktin; ssh2_userauth_filter_queue(s); /* no matter why we were called */ crBegin(s->crState); #ifndef NO_GSSAPI s->tried_gssapi = false; s->tried_gssapi_keyex_auth = false; #endif /* * Misc one-time setup for authentication. */ s->publickey_blob = NULL; s->session_id = ssh2_transport_get_session_id(s->transport_layer); /* * Load the public half of any configured public key file for * later use. */ 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_SSH2 || keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { const char *error; s->publickey_blob = strbuf_new(); if (ppk_loadpub_f(s->keyfile, &s->publickey_algorithm, BinarySink_UPCAST(s->publickey_blob), &s->publickey_comment, &error)) { s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2); if (!s->privatekey_available) ppl_logevent("Key file contains public key only"); s->privatekey_encrypted = ppk_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)); s->publickey_blob = NULL; } } /* * If the user provided a detached certificate file, load that. */ if (!filename_is_null(s->detached_cert_file)) { char *cert_error = NULL; strbuf *cert_blob = strbuf_new(); char *algname = NULL; char *comment = NULL; ppl_logevent("Reading certificate file \"%s\"", filename_to_str(s->detached_cert_file)); int keytype = key_type(s->detached_cert_file); if (!(keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)) { cert_error = dupstr(key_type_to_str(keytype)); goto cert_load_done; } const char *error; bool success = ppk_loadpub_f( s->detached_cert_file, &algname, BinarySink_UPCAST(cert_blob), &comment, &error); if (!success) { cert_error = dupstr(error); goto cert_load_done; } const ssh_keyalg *certalg = find_pubkey_alg(algname); if (!certalg) { cert_error = dupprintf( "unrecognised certificate type '%s'", algname); goto cert_load_done; } if (!certalg->is_certificate) { cert_error = dupprintf( "key type '%s' is not a certificate", certalg->ssh_id); goto cert_load_done; } /* OK, store the certificate blob to substitute for the * public blob in all publickey auth packets. */ if (s->detached_cert_blob) strbuf_free(s->detached_cert_blob); s->detached_cert_blob = cert_blob; cert_blob = NULL; /* prevent free */ cert_load_done: if (cert_error) { ppl_logevent("Unable to use this certificate file (%s)", cert_error); ppl_printf( "Unable to use certificate file \"%s\" (%s)\r\n", filename_to_str(s->detached_cert_file), cert_error); sfree(cert_error); } if (cert_blob) strbuf_free(cert_blob); sfree(algname); sfree(comment); } /* * Find out about any keys Pageant has (but if there's a public * key configured, filter out all others). */ if (s->tryagent && agent_exists()) { ppl_logevent("Pageant is running. Requesting keys."); /* Request the keys held by the agent. */ { strbuf *request = strbuf_new_for_agent_query(); put_byte(request, SSH2_AGENTC_REQUEST_IDENTITIES); ssh2_userauth_agent_query(s, request); strbuf_free(request); crWaitUntilV(!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) == SSH2_AGENT_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_string(s->asrc); /* blob */ get_string(s->asrc); /* comment */ if (get_err(s->asrc)) { ppl_logevent("Pageant's response was truncated"); goto done_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++) { s->agent_keys[i].blob = strbuf_dup(get_string(s->asrc)); s->agent_keys[i].comment = strbuf_dup(get_string(s->asrc)); /* Also, extract the algorithm string from the start * of the public-key blob. */ s->agent_keys[i].algorithm = pubkey_blob_to_alg_name( ptrlen_from_strbuf(s->agent_keys[i].blob)); } ppl_logevent("Pageant has %"SIZEu" SSH-2 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, ptrlen_from_strbuf( 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"); } done_agent_query:; } s->got_username = false; if (*s->authplugin_cmd) { s->authplugin_plug.vt = &authplugin_plugvt; s->authplugin = platform_start_subprocess( s->authplugin_cmd, &s->authplugin_plug, "plugin"); ppl_logevent("Started authentication plugin: %s", s->authplugin_cmd); } if (s->authplugin) { strbuf *amsg = authplugin_newmsg(PLUGIN_INIT); put_uint32(amsg, PLUGIN_PROTOCOL_MAX_VERSION); put_stringz(amsg, s->hostname); put_uint32(amsg, s->port); put_stringz(amsg, s->username ? s->username : ""); authplugin_send_free(s, amsg); BinarySource src[1]; unsigned type; crMaybeWaitUntilV(authplugin_expect_msg(s, &type, src)); switch (type) { case PLUGIN_INIT_RESPONSE: { s->authplugin_version = get_uint32(src); ptrlen username = get_string(src); if (get_err(src)) { ssh_sw_abort(s->ppl.ssh, "Received malformed " "PLUGIN_INIT_RESPONSE from auth helper plugin"); return; } if (s->authplugin_version > PLUGIN_PROTOCOL_MAX_VERSION) { ssh_sw_abort(s->ppl.ssh, "Auth helper plugin announced " "unsupported version number %"PRIu32, s->authplugin_version); return; } if (username.len) { sfree(s->default_username); s->default_username = mkstr(username); ppl_logevent("Authentication plugin set username '%s'", s->default_username); } break; } case PLUGIN_INIT_FAILURE: { ptrlen message = get_string(src); if (get_err(src)) { ssh_sw_abort(s->ppl.ssh, "Received malformed " "PLUGIN_INIT_FAILURE from auth helper plugin"); return; } /* This is a controlled error, so we need not completely * abandon the connection. Instead, inform the user, and * proceed as if the plugin was not present */ ppl_printf("Authentication plugin failed to initialise:\r\n"); seat_set_trust_status(s->ppl.seat, false); ppl_printf("%.*s\r\n", PTRLEN_PRINTF(message)); seat_set_trust_status(s->ppl.seat, true); sk_close(s->authplugin); s->authplugin = NULL; break; } default: authplugin_bad_packet(s, type, "expected PLUGIN_INIT_RESPONSE or " "PLUGIN_INIT_FAILURE"); return; } } /* * 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 * beginning to try another username, if this is configured on. * (If they specify a username in the config, they are never * asked, even if they do give a wrong password.) * * I think this best serves the needs of * * - the people who have no configuration, no keys, and just * want to try repeated (username,password) pairs until they * type both correctly * * - people who have keys and configuration but occasionally * need to fall back to passwords * * - people with a key held in Pageant, who might not have * logged in to a particular machine before; so they want to * type a username, and then _either_ their key will be * accepted, _or_ they will type a password. If they mistype * the username they will want to be able to get back and * retype it! */ while (1) { /* * Get a username. */ if (s->got_username && !s->change_username) { /* * We got a username last time round this loop, and * with change_username turned off we don't try to get * it again. */ } else if ((s->username = s->default_username) == 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)) { /* * seat_get_userpass_input() failed to get a username. * Terminate. */ free_prompts(s->cur_prompt); s->cur_prompt = NULL; ssh_spr_close(s->ppl.ssh, s->spr, "username prompt"); return; } sfree(s->locally_allocated_username); /* for change_username */ s->username = s->locally_allocated_username = prompt_get_result(s->cur_prompt->prompts[0]); free_prompts(s->cur_prompt); s->cur_prompt = NULL; } else { if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat)) ppl_printf("Using username \"%s\".\r\n", s->username); } s->got_username = true; /* * Send an authentication request using method "none": (a) * just in case it succeeds, and (b) so that we know what * authentication methods we can usefully try next. */ s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH; s->pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, s->username); put_stringz(s->pktout, s->successor_layer->vt->name); put_stringz(s->pktout, "none"); /* method */ pq_push(s->ppl.out_pq, s->pktout); s->type = AUTH_TYPE_NONE; s->tried_pubkey_config = false; s->kbd_inter_refused = false; s->done_agent = false; while (1) { /* * Wait for the result of the last authentication request, * unless the request terminated for some reason on our * own side. */ if (s->suppress_wait_for_response_packet) { pktin = NULL; s->suppress_wait_for_response_packet = false; } else { crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); } /* * Now is a convenient point to spew any banner material * that we've accumulated. (This should ensure that when * we exit the auth loop, we haven't any left to deal * with.) * * Don't show the banner if we're operating in non-verbose * non-interactive mode. (It's probably a script, which * means nobody will read the banner _anyway_, and * moreover the printing of the banner will screw up * processing on the output of (say) plink.) * * The banner data has been sanitised already by this * point, but we still need to precede and follow it with * anti-spoofing header lines. */ if (bufchain_size(&s->banner) && (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))) { if (s->banner_scc) { seat_antispoof_msg( ppl_get_iseat(&s->ppl), "Pre-authentication banner message from server:"); seat_set_trust_status(s->ppl.seat, false); } bool mid_line = false; while (bufchain_size(&s->banner) > 0) { ptrlen data = bufchain_prefix(&s->banner); seat_banner_pl(ppl_get_iseat(&s->ppl), data); mid_line = (((const char *)data.ptr)[data.len-1] != '\n'); bufchain_consume(&s->banner, data.len); } bufchain_clear(&s->banner); if (mid_line) seat_banner_pl(ppl_get_iseat(&s->ppl), PTRLEN_LITERAL("\r\n")); if (s->banner_scc) { seat_set_trust_status(s->ppl.seat, true); seat_antispoof_msg(ppl_get_iseat(&s->ppl), "End of banner message from server"); } } if (pktin && pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { ppl_logevent("Access granted"); goto userauth_success; } if (pktin && pktin->type != SSH2_MSG_USERAUTH_FAILURE && s->type != AUTH_TYPE_GSSAPI) { ssh_proto_error(s->ppl.ssh, "Received unexpected packet " "in response to authentication request, " "type %d (%s)", pktin->type, ssh2_pkt_type(s->ppl.bpp->pls->kctx, s->ppl.bpp->pls->actx, pktin->type)); return; } /* * OK, we're now sitting on a USERAUTH_FAILURE message, so * we can look at the string in it and know what we can * helpfully try next. */ if (pktin && pktin->type == SSH2_MSG_USERAUTH_FAILURE) { ptrlen methods = get_string(pktin); bool partial_success = get_bool(pktin); if (!partial_success) { /* * We have received an unequivocal Access * Denied. This can translate to a variety of * messages, or no message at all. * * For forms of authentication which are attempted * implicitly, by which I mean without printing * anything in the window indicating that we're * trying them, we should never print 'Access * denied'. * * If we do print a message saying that we're * attempting some kind of authentication, it's OK * to print a followup message saying it failed - * but the message may sometimes be more specific * than simply 'Access denied'. * * Additionally, if we'd just tried password * authentication, we should break out of this * whole loop so as to go back to the username * prompt (iff we're configured to allow * username change attempts). */ if (s->type == AUTH_TYPE_NONE) { /* do nothing */ } else if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD || s->type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) { if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD) ppl_printf("Server refused our key\r\n"); ppl_logevent("Server refused our key"); } else if (s->type == AUTH_TYPE_PUBLICKEY) { /* This _shouldn't_ happen except by a * protocol bug causing client and server to * disagree on what is a correct signature. */ ppl_printf("Server refused public-key signature" " despite accepting key!\r\n"); ppl_logevent("Server refused public-key signature" " despite accepting key!"); } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) { /* quiet, so no ppl_printf */ ppl_logevent("Server refused keyboard-interactive " "authentication"); } else if (s->type==AUTH_TYPE_GSSAPI) { /* always quiet, so no ppl_printf */ /* also, the code down in the GSSAPI block has * already logged this in the Event Log */ } else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) { ppl_logevent("Keyboard-interactive authentication " "failed"); ppl_printf("Access denied\r\n"); } else { assert(s->type == AUTH_TYPE_PASSWORD); ppl_logevent("Password authentication failed"); ppl_printf("Access denied\r\n"); if (s->change_username) { /* XXX perhaps we should allow * keyboard-interactive to do this too? */ goto try_new_username; } } } else { ppl_printf("Further authentication required\r\n"); ppl_logevent("Further authentication required"); } /* * Save the methods string for use in error messages. */ strbuf_clear(s->last_methods_string); put_datapl(s->last_methods_string, methods); /* * Scan it for method identifiers we know about. */ bool srv_pubkey = false, srv_passwd = false; bool srv_keyb_inter = false; #ifndef NO_GSSAPI bool srv_gssapi = false, srv_gssapi_keyex_auth = false; #endif for (ptrlen method; get_commasep_word(&methods, &method) ;) { if (ptrlen_eq_string(method, "publickey")) srv_pubkey = true; else if (ptrlen_eq_string(method, "password")) srv_passwd = true; else if (ptrlen_eq_string(method, "keyboard-interactive")) srv_keyb_inter = true; #ifndef NO_GSSAPI else if (ptrlen_eq_string(method, "gssapi-with-mic")) srv_gssapi = true; else if (ptrlen_eq_string(method, "gssapi-keyex")) srv_gssapi_keyex_auth = true; #endif } /* * And combine those flags with our own configuration * and context to set the main can_foo variables. */ s->can_pubkey = srv_pubkey; s->can_passwd = srv_passwd; s->can_keyb_inter = s->try_ki_auth && srv_keyb_inter; #ifndef NO_GSSAPI s->can_gssapi = s->try_gssapi_auth && srv_gssapi && s->shgss->libs->nlibraries > 0; s->can_gssapi_keyex_auth = s->try_gssapi_kex_auth && srv_gssapi_keyex_auth && s->shgss->libs->nlibraries > 0 && s->shgss->ctx; #endif } s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH; #ifndef NO_GSSAPI if (s->can_gssapi_keyex_auth && !s->tried_gssapi_keyex_auth) { /* gssapi-keyex authentication */ s->type = AUTH_TYPE_GSSAPI; s->tried_gssapi_keyex_auth = true; s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI; if (s->shgss->lib->gsslogmsg) ppl_logevent("%s", s->shgss->lib->gsslogmsg); ppl_logevent("Trying gssapi-keyex..."); s->pktout = ssh2_userauth_gss_packet(s, "gssapi-keyex"); pq_push(s->ppl.out_pq, s->pktout); s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); s->shgss->ctx = NULL; continue; } else #endif /* NO_GSSAPI */ if (s->can_pubkey && !s->done_agent && s->agent_key_index < s->agent_key_limit) { /* * Attempt public-key authentication using a key from Pageant. */ s->agent_keyalg = s->agent_keys[s->agent_key_index].algorithm; char *alg_tmp = mkstr(s->agent_keyalg); const char *newalg = alg_tmp; if (ssh2_userauth_signflags(s, &s->signflags, &newalg)) s->agent_keyalg = ptrlen_from_asciz(newalg); sfree(alg_tmp); s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index); /* See if server will accept it */ s->pktout = ssh_bpp_new_pktout( s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, s->username); put_stringz(s->pktout, s->successor_layer->vt->name); put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, false); /* no signature included */ ssh2_userauth_add_alg_and_publickey( s, s->pktout, s->agent_keyalg, ptrlen_from_strbuf( s->agent_keys[s->agent_key_index].blob)); pq_push(s->ppl.out_pq, s->pktout); s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { /* Offer of key refused, presumably via * USERAUTH_FAILURE. Requeue for the next iteration. */ pq_push_front(s->ppl.in_pq, pktin); } else { strbuf *agentreq, *sigdata; ptrlen comment = ptrlen_from_strbuf( s->agent_keys[s->agent_key_index].comment); if (seat_verbose(s->ppl.seat)) ppl_printf("Authenticating with public key " "\"%.*s\" from agent\r\n", PTRLEN_PRINTF(comment)); /* * Server is willing to accept the key. * Construct a SIGN_REQUEST. */ s->pktout = ssh_bpp_new_pktout( s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, s->username); put_stringz(s->pktout, s->successor_layer->vt->name); put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, true); /* signature included */ ssh2_userauth_add_alg_and_publickey( s, s->pktout, s->agent_keyalg, ptrlen_from_strbuf( s->agent_keys[s->agent_key_index].blob)); /* Ask agent for signature. */ agentreq = strbuf_new_for_agent_query(); put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST); put_stringpl(agentreq, ptrlen_from_strbuf( s->agent_keys[s->agent_key_index].blob)); /* Now the data to be signed... */ sigdata = strbuf_new(); ssh2_userauth_add_session_id(s, sigdata); put_data(sigdata, s->pktout->data + 5, s->pktout->length - 5); put_stringsb(agentreq, sigdata); /* And finally the flags word. */ put_uint32(agentreq, s->signflags); ssh2_userauth_agent_query(s, agentreq); strbuf_free(agentreq); crWaitUntilV(!s->auth_agent_query); if (s->agent_response.ptr) { ptrlen sigblob; BinarySource src[1]; BinarySource_BARE_INIT(src, s->agent_response.ptr, s->agent_response.len); get_uint32(src); /* skip length field */ if (get_byte(src) == SSH2_AGENT_SIGN_RESPONSE && (sigblob = get_string(src), !get_err(src))) { ppl_logevent("Sending Pageant's response"); ssh2_userauth_add_sigblob( s, s->pktout, ptrlen_from_strbuf( s->agent_keys[s->agent_key_index].blob), sigblob); pq_push(s->ppl.out_pq, s->pktout); s->type = AUTH_TYPE_PUBLICKEY; s->is_trivial_auth = false; } else { ppl_logevent("Pageant refused signing request"); ppl_printf("Pageant failed to " "provide a signature\r\n"); s->suppress_wait_for_response_packet = true; ssh_free_pktout(s->pktout); } } else { ppl_logevent("Pageant failed to respond to " "signing request"); ppl_printf("Pageant failed to " "respond to signing request\r\n"); s->suppress_wait_for_response_packet = true; ssh_free_pktout(s->pktout); } } /* Do we have any keys left to try? */ if (++s->agent_key_index >= s->agent_key_limit) s->done_agent = true; } else if (s->can_pubkey && s->publickey_blob && s->privatekey_available && !s->tried_pubkey_config) { ssh2_userkey *key; /* not live over crReturn */ char *passphrase; /* not live over crReturn */ s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; s->tried_pubkey_config = true; /* * Try the public key supplied in the configuration. * * First, try to upgrade its algorithm. */ const char *newalg = s->publickey_algorithm; if (ssh2_userauth_signflags(s, &s->signflags, &newalg)) { sfree(s->publickey_algorithm); s->publickey_algorithm = dupstr(newalg); } /* * Offer the public blob to see if the server is willing to * accept it. */ s->pktout = ssh_bpp_new_pktout( s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, s->username); put_stringz(s->pktout, s->successor_layer->vt->name); put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, false); /* no signature included */ ssh2_userauth_add_alg_and_publickey( s, s->pktout, ptrlen_from_asciz(s->publickey_algorithm), ptrlen_from_strbuf(s->publickey_blob)); pq_push(s->ppl.out_pq, s->pktout); ppl_logevent("Offered public key"); crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { /* Key refused. Give up. */ pq_push_front(s->ppl.in_pq, pktin); s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD; continue; /* process this new message */ } ppl_logevent("Offer of public key accepted"); /* * Actually attempt a serious authentication using * the key. */ if (seat_verbose(s->ppl.seat)) ppl_printf("Authenticating with public key \"%s\"\r\n", s->publickey_comment); key = NULL; while (!key) { const char *error; /* not live over crReturn */ if (s->privatekey_encrypted) { /* * Get a passphrase from the user. */ 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. */ free_prompts(s->cur_prompt); s->cur_prompt = NULL; ssh_bpp_queue_disconnect( s->ppl.bpp, "Unable to authenticate", SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); 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; } else { passphrase = NULL; /* no passphrase needed */ } /* * Try decrypting the key. */ key = ppk_load_f(s->keyfile, passphrase, &error); if (passphrase) { /* burn the evidence */ smemclr(passphrase, strlen(passphrase)); sfree(passphrase); } if (key == SSH2_WRONG_PASSPHRASE || key == NULL) { if (passphrase && (key == SSH2_WRONG_PASSPHRASE)) { ppl_printf("Wrong passphrase\r\n"); key = NULL; /* and loop again */ } else { ppl_printf("Unable to load private key (%s)\r\n", error); key = NULL; s->suppress_wait_for_response_packet = true; break; /* try something else */ } } else { /* FIXME: if we ever support variable signature * flags, this is somewhere they'll need to be * put */ char *invalid = ssh_key_invalid(key->key, 0); if (invalid) { ppl_printf("Cannot use this private key (%s)\r\n", invalid); ssh_key_free(key->key); sfree(key->comment); sfree(key); sfree(invalid); key = NULL; s->suppress_wait_for_response_packet = true; break; /* try something else */ } } } if (key) { strbuf *pkblob, *sigdata, *sigblob; /* * We have loaded the private key and the server * has announced that it's willing to accept it. * Hallelujah. Generate a signature and send it. */ s->pktout = ssh_bpp_new_pktout( s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, s->username); put_stringz(s->pktout, s->successor_layer->vt->name); put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, true); /* signature follows */ pkblob = strbuf_new(); ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob)); ssh2_userauth_add_alg_and_publickey( s, s->pktout, ptrlen_from_asciz(s->publickey_algorithm), ptrlen_from_strbuf(pkblob)); /* * The data to be signed is: * * string session-id * * followed by everything so far placed in the * outgoing packet. */ sigdata = strbuf_new(); ssh2_userauth_add_session_id(s, sigdata); put_data(sigdata, s->pktout->data + 5, s->pktout->length - 5); sigblob = strbuf_new(); ssh_key_sign(key->key, ptrlen_from_strbuf(sigdata), s->signflags, BinarySink_UPCAST(sigblob)); strbuf_free(sigdata); ssh2_userauth_add_sigblob( s, s->pktout, ptrlen_from_strbuf(pkblob), ptrlen_from_strbuf(sigblob)); strbuf_free(pkblob); strbuf_free(sigblob); pq_push(s->ppl.out_pq, s->pktout); ppl_logevent("Sent public key signature"); s->type = AUTH_TYPE_PUBLICKEY; ssh_key_free(key->key); sfree(key->comment); sfree(key); s->is_trivial_auth = false; } #ifndef NO_GSSAPI } else if (s->can_gssapi && !s->tried_gssapi) { /* gssapi-with-mic authentication */ ptrlen data; s->type = AUTH_TYPE_GSSAPI; s->tried_gssapi = true; s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI; if (s->shgss->lib->gsslogmsg) ppl_logevent("%s", s->shgss->lib->gsslogmsg); /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */ ppl_logevent("Trying gssapi-with-mic..."); s->pktout = ssh_bpp_new_pktout( s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, s->username); put_stringz(s->pktout, s->successor_layer->vt->name); put_stringz(s->pktout, "gssapi-with-mic"); ppl_logevent("Attempting GSSAPI authentication"); /* add mechanism info */ s->shgss->lib->indicate_mech(s->shgss->lib, &s->gss_buf); /* number of GSSAPI mechanisms */ put_uint32(s->pktout, 1); /* length of OID + 2 */ put_uint32(s->pktout, s->gss_buf.length + 2); put_byte(s->pktout, SSH2_GSS_OIDTYPE); /* length of OID */ put_byte(s->pktout, s->gss_buf.length); put_data(s->pktout, s->gss_buf.value, s->gss_buf.length); pq_push(s->ppl.out_pq, s->pktout); crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) { ppl_logevent("GSSAPI authentication request refused"); pq_push_front(s->ppl.in_pq, pktin); continue; } /* check returned packet ... */ data = get_string(pktin); s->gss_rcvtok.value = (char *)data.ptr; s->gss_rcvtok.length = data.len; if (s->gss_rcvtok.length != s->gss_buf.length + 2 || ((char *)s->gss_rcvtok.value)[0] != SSH2_GSS_OIDTYPE || ((char *)s->gss_rcvtok.value)[1] != s->gss_buf.length || memcmp((char *)s->gss_rcvtok.value + 2, s->gss_buf.value,s->gss_buf.length) ) { ppl_logevent("GSSAPI authentication - wrong response " "from server"); continue; } /* Import server name if not cached from KEX */ if (s->shgss->srv_name == GSS_C_NO_NAME) { s->gss_stat = s->shgss->lib->import_name( s->shgss->lib, s->fullhostname, &s->shgss->srv_name); if (s->gss_stat != SSH_GSS_OK) { if (s->gss_stat == SSH_GSS_BAD_HOST_NAME) ppl_logevent("GSSAPI import name failed -" " Bad service name"); else ppl_logevent("GSSAPI import name failed"); continue; } } /* Allocate our gss_ctx */ s->gss_stat = s->shgss->lib->acquire_cred( s->shgss->lib, &s->shgss->ctx, NULL); if (s->gss_stat != SSH_GSS_OK) { ppl_logevent("GSSAPI authentication failed to get " "credentials"); /* The failure was on our side, so the server * won't be sending a response packet indicating * failure. Avoid waiting for it next time round * the loop. */ s->suppress_wait_for_response_packet = true; continue; } /* initial tokens are empty */ SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); SSH_GSS_CLEAR_BUF(&s->gss_sndtok); /* now enter the loop */ do { /* * When acquire_cred yields no useful expiration, go with * the service ticket expiration. */ s->gss_stat = s->shgss->lib->init_sec_context( s->shgss->lib, &s->shgss->ctx, s->shgss->srv_name, s->gssapi_fwd, &s->gss_rcvtok, &s->gss_sndtok, NULL, NULL); if (s->gss_stat!=SSH_GSS_S_COMPLETE && s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) { ppl_logevent("GSSAPI authentication initialisation " "failed"); if (s->shgss->lib->display_status( s->shgss->lib, s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) { ppl_logevent("%s", (char *)s->gss_buf.value); sfree(s->gss_buf.value); } pq_push_front(s->ppl.in_pq, pktin); break; } ppl_logevent("GSSAPI authentication initialised"); /* * Client and server now exchange tokens until GSSAPI * no longer says CONTINUE_NEEDED */ if (s->gss_sndtok.length != 0) { s->is_trivial_auth = false; s->pktout = ssh_bpp_new_pktout( s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_TOKEN); put_string(s->pktout, s->gss_sndtok.value, s->gss_sndtok.length); pq_push(s->ppl.out_pq, s->pktout); s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); } if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) { crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); if (pktin->type == SSH2_MSG_USERAUTH_GSSAPI_ERRTOK) { /* * Per RFC 4462 section 3.9, this packet * type MUST immediately precede an * ordinary USERAUTH_FAILURE. * * We currently don't know how to do * anything with the GSSAPI error token * contained in this packet, so we ignore * it and just wait for the following * FAILURE. */ crMaybeWaitUntilV( (pktin = ssh2_userauth_pop(s)) != NULL); if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) { ssh_proto_error( s->ppl.ssh, "Received unexpected packet " "after SSH_MSG_USERAUTH_GSSAPI_ERRTOK " "(expected SSH_MSG_USERAUTH_FAILURE): " "type %d (%s)", pktin->type, ssh2_pkt_type(s->ppl.bpp->pls->kctx, s->ppl.bpp->pls->actx, pktin->type)); return; } } if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) { ppl_logevent("GSSAPI authentication failed"); s->gss_stat = SSH_GSS_FAILURE; pq_push_front(s->ppl.in_pq, pktin); break; } else if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_TOKEN) { ppl_logevent("GSSAPI authentication -" " bad server response"); s->gss_stat = SSH_GSS_FAILURE; break; } data = get_string(pktin); s->gss_rcvtok.value = (char *)data.ptr; s->gss_rcvtok.length = data.len; } } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED); if (s->gss_stat != SSH_GSS_OK) { s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); continue; } ppl_logevent("GSSAPI authentication loop finished OK"); /* Now send the MIC */ s->pktout = ssh2_userauth_gss_packet(s, "gssapi-with-mic"); pq_push(s->ppl.out_pq, s->pktout); s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); continue; #endif } else if (s->can_keyb_inter && !s->kbd_inter_refused) { /* * Keyboard-interactive authentication. */ s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; s->ppl.bpp->pls->actx = SSH2_PKTCTX_KBDINTER; s->pktout = ssh_bpp_new_pktout( s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, s->username); put_stringz(s->pktout, s->successor_layer->vt->name); put_stringz(s->pktout, "keyboard-interactive"); /* method */ put_stringz(s->pktout, ""); /* lang */ put_stringz(s->pktout, ""); /* submethods */ pq_push(s->ppl.out_pq, s->pktout); ppl_logevent("Attempting keyboard-interactive authentication"); if (s->authplugin) { strbuf *amsg = authplugin_newmsg(PLUGIN_PROTOCOL); put_stringz(amsg, "keyboard-interactive"); authplugin_send_free(s, amsg); BinarySource src[1]; unsigned type; crMaybeWaitUntilV(authplugin_expect_msg(s, &type, src)); switch (type) { case PLUGIN_PROTOCOL_REJECT: { ptrlen message = PTRLEN_LITERAL(""); if (s->authplugin_version >= 2) { /* draft protocol didn't include a message here */ message = get_string(src); } if (get_err(src)) { ssh_sw_abort(s->ppl.ssh, "Received malformed " "PLUGIN_PROTOCOL_REJECT from auth " "helper plugin"); return; } if (message.len) { /* If the plugin sent a message about * _why_ it didn't want to do k-i, pass * that message on to the user. (It might * say, for example, what went wrong when * it tried to open its config file.) */ ppl_printf("Authentication plugin failed to set " "up keyboard-interactive " "authentication:\r\n"); seat_set_trust_status(s->ppl.seat, false); ppl_printf("%.*s\r\n", PTRLEN_PRINTF(message)); seat_set_trust_status(s->ppl.seat, true); ppl_logevent("Authentication plugin declined to " "help with keyboard-interactive: " "%.*s", PTRLEN_PRINTF(message)); } else { ppl_logevent("Authentication plugin declined to " "help with keyboard-interactive"); } s->authplugin_ki_active = false; break; } case PLUGIN_PROTOCOL_ACCEPT: s->authplugin_ki_active = true; ppl_logevent("Authentication plugin agreed to help " "with keyboard-interactive"); break; default: authplugin_bad_packet( s, type, "expected PLUGIN_PROTOCOL_ACCEPT or " "PLUGIN_PROTOCOL_REJECT"); return; } } else { s->authplugin_ki_active = false; } if (!s->ki_scc_initialised) { s->ki_scc = seat_stripctrl_new( s->ppl.seat, NULL, SIC_KI_PROMPTS); if (s->ki_scc) stripctrl_enable_line_limiting(s->ki_scc); s->ki_scc_initialised = true; } crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) { /* Server is not willing to do keyboard-interactive * at all (or, bizarrely but legally, accepts the * user without actually issuing any prompts). * Give up on it entirely. */ pq_push_front(s->ppl.in_pq, pktin); s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET; s->kbd_inter_refused = true; /* don't try it again */ continue; } s->ki_printed_header = false; /* * Loop while we still have prompts to send to the user. */ if (!s->authplugin_ki_active) { /* * The simple case: INFO_REQUESTs are passed on to * the user, and responses are sent straight back * to the SSH server. */ while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { if (!ssh2_userauth_ki_setup_prompts( s, BinarySource_UPCAST(pktin), false)) return; crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s)); if (spr_is_abort(s->spr)) { /* * Failed to get responses. Terminate. */ free_prompts(s->cur_prompt); s->cur_prompt = NULL; ssh_bpp_queue_disconnect( s->ppl.bpp, "Unable to authenticate", SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); ssh_spr_close(s->ppl.ssh, s->spr, "keyboard-" "interactive authentication prompt"); return; } /* * Send the response(s) to the server. */ s->pktout = ssh_bpp_new_pktout( s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); ssh2_userauth_ki_write_responses( s, BinarySink_UPCAST(s->pktout)); s->pktout->minlen = 256; pq_push(s->ppl.out_pq, s->pktout); /* * Get the next packet in case it's another * INFO_REQUEST. */ crMaybeWaitUntilV( (pktin = ssh2_userauth_pop(s)) != NULL); } } else { /* * The case where a plugin is involved: * INFO_REQUEST from the server is sent to the * plugin, which sends responses that we hand back * to the server. But in the meantime, the plugin * might send USER_REQUEST for us to pass to the * user, and then we send responses to that. */ while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { strbuf *amsg = authplugin_newmsg( PLUGIN_KI_SERVER_REQUEST); put_datapl(amsg, get_data(pktin, get_avail(pktin))); authplugin_send_free(s, amsg); BinarySource src[1]; unsigned type; while (true) { crMaybeWaitUntilV(authplugin_expect_msg( s, &type, src)); if (type != PLUGIN_KI_USER_REQUEST) break; if (!ssh2_userauth_ki_setup_prompts(s, src, true)) return; crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s)); if (spr_is_abort(s->spr)) { /* * Failed to get responses. Terminate. */ free_prompts(s->cur_prompt); s->cur_prompt = NULL; ssh_bpp_queue_disconnect( s->ppl.bpp, "Unable to authenticate", SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); ssh_spr_close( s->ppl.ssh, s->spr, "keyboard-" "interactive authentication prompt"); return; } /* * Send the responses on to the plugin. */ strbuf *amsg = authplugin_newmsg( PLUGIN_KI_USER_RESPONSE); ssh2_userauth_ki_write_responses( s, BinarySink_UPCAST(amsg)); authplugin_send_free(s, amsg); } if (type != PLUGIN_KI_SERVER_RESPONSE) { authplugin_bad_packet( s, type, "expected PLUGIN_KI_SERVER_RESPONSE " "or PLUGIN_PROTOCOL_USER_REQUEST"); return; } s->pktout = ssh_bpp_new_pktout( s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); put_datapl(s->pktout, get_data(src, get_avail(src))); s->pktout->minlen = 256; pq_push(s->ppl.out_pq, s->pktout); /* * Get the next packet in case it's another * INFO_REQUEST. */ crMaybeWaitUntilV( (pktin = ssh2_userauth_pop(s)) != NULL); } } /* * Print our trailer line, if we printed a header. */ if (s->ki_printed_header) { seat_set_trust_status(s->ppl.seat, true); seat_antispoof_msg( ppl_get_iseat(&s->ppl), (s->authplugin_ki_active ? "End of keyboard-interactive prompts from plugin" : "End of keyboard-interactive prompts from server")); } /* * We should have SUCCESS or FAILURE now. */ pq_push_front(s->ppl.in_pq, pktin); if (s->authplugin_ki_active) { /* * As our last communication with the plugin, tell * it whether the k-i authentication succeeded. */ int plugin_msg = -1; if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { plugin_msg = PLUGIN_AUTH_SUCCESS; } else if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) { /* * Peek in the failure packet to see if it's a * partial success. */ BinarySource src[1]; BinarySource_BARE_INIT( src, get_ptr(pktin), get_avail(pktin)); get_string(pktin); /* skip methods */ bool partial_success = get_bool(pktin); if (!get_err(src)) { plugin_msg = partial_success ? PLUGIN_AUTH_SUCCESS : PLUGIN_AUTH_FAILURE; } } if (plugin_msg >= 0) { strbuf *amsg = authplugin_newmsg(plugin_msg); authplugin_send_free(s, amsg); /* Wait until we've actually sent it, in case * we close the connection to the plugin * before that outgoing message has left our * own buffers */ crMaybeWaitUntilV(s->authplugin_backlog == 0); } } } else if (s->can_passwd) { s->is_trivial_auth = false; /* * Plain old password authentication. */ bool changereq_first_time; /* not live over crReturn */ s->ppl.bpp->pls->actx = SSH2_PKTCTX_PASSWORD; 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 password"); add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", s->username, s->hostname), 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 responses. Terminate. */ free_prompts(s->cur_prompt); s->cur_prompt = NULL; ssh_bpp_queue_disconnect( s->ppl.bpp, "Unable to authenticate", SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); ssh_spr_close(s->ppl.ssh, s->spr, "password prompt"); return; } /* * Squirrel away the password. (We may need it later if * asked to change it.) */ s->password = prompt_get_result(s->cur_prompt->prompts[0]); free_prompts(s->cur_prompt); s->cur_prompt = NULL; /* * 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. * * Anyone using a password longer than 256 bytes * probably doesn't have much to worry about from * people who find out how long their password is! */ s->pktout = ssh_bpp_new_pktout( s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, s->username); put_stringz(s->pktout, s->successor_layer->vt->name); put_stringz(s->pktout, "password"); put_bool(s->pktout, false); put_stringz(s->pktout, s->password); s->pktout->minlen = 256; pq_push(s->ppl.out_pq, s->pktout); ppl_logevent("Sent password"); s->type = AUTH_TYPE_PASSWORD; /* * Wait for next packet, in case it's a password change * request. */ crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); changereq_first_time = true; while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { /* * We're being asked for a new password * (perhaps not for the first time). * Loop until the server accepts it. */ bool got_new = false; /* not live over crReturn */ ptrlen prompt; /* not live over crReturn */ { const char *msg; if (changereq_first_time) msg = "Server requested password change"; else msg = "Server rejected new password"; ppl_logevent("%s", msg); ppl_printf("%s\r\n", msg); } prompt = get_string(pktin); 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("New SSH password"); s->cur_prompt->instruction = mkstr(prompt); s->cur_prompt->instr_reqd = true; /* * There's no explicit requirement in the protocol * for the "old" passwords in the original and * password-change messages to be the same, and * apparently some Cisco kit supports password change * by the user entering a blank password originally * and the real password subsequently, so, * reluctantly, we prompt for the old password again. * * (On the other hand, some servers don't even bother * to check this field.) */ add_prompt(s->cur_prompt, dupstr("Current password (blank for previously entered password): "), false); add_prompt(s->cur_prompt, dupstr("Enter new password: "), false); add_prompt(s->cur_prompt, dupstr("Confirm new password: "), false); /* * Loop until the user manages to enter the same * password twice. */ while (!got_new) { 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 responses. Terminate. */ /* burn the evidence */ free_prompts(s->cur_prompt); s->cur_prompt = NULL; smemclr(s->password, strlen(s->password)); sfree(s->password); ssh_bpp_queue_disconnect( s->ppl.bpp, "Unable to authenticate", SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); ssh_spr_close(s->ppl.ssh, s->spr, "password-change prompt"); return; } /* * If the user specified a new original password * (IYSWIM), overwrite any previously specified * one. * (A side effect is that the user doesn't have to * re-enter it if they louse up the new password.) */ if (s->cur_prompt->prompts[0]->result->s[0]) { smemclr(s->password, strlen(s->password)); /* burn the evidence */ sfree(s->password); s->password = prompt_get_result( s->cur_prompt->prompts[0]); } /* * Check the two new passwords match. */ got_new = !strcmp( prompt_get_result_ref(s->cur_prompt->prompts[1]), prompt_get_result_ref(s->cur_prompt->prompts[2])); if (!got_new) /* They don't. Silly user. */ ppl_printf("Passwords do not match\r\n"); } /* * Send the new password (along with the old one). * (see above for padding rationale) */ s->pktout = ssh_bpp_new_pktout( s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, s->username); put_stringz(s->pktout, s->successor_layer->vt->name); put_stringz(s->pktout, "password"); put_bool(s->pktout, true); put_stringz(s->pktout, s->password); put_stringz(s->pktout, prompt_get_result_ref( s->cur_prompt->prompts[1])); free_prompts(s->cur_prompt); s->cur_prompt = NULL; s->pktout->minlen = 256; pq_push(s->ppl.out_pq, s->pktout); ppl_logevent("Sent new password"); /* * Now see what the server has to say about it. * (If it's CHANGEREQ again, it's not happy with the * new password.) */ crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); changereq_first_time = false; } /* * We need to reexamine the current pktin at the top * of the loop. Either: * - we weren't asked to change password at all, in * which case it's a SUCCESS or FAILURE with the * usual meaning * - we sent a new password, and the server was * either OK with it (SUCCESS or FAILURE w/partial * success) or unhappy with the _old_ password * (FAILURE w/o partial success) * In any of these cases, we go back to the top of * the loop and start again. */ pq_push_front(s->ppl.in_pq, pktin); /* * We don't need the old password any more, in any * case. Burn the evidence. */ smemclr(s->password, strlen(s->password)); sfree(s->password); } else { ssh_bpp_queue_disconnect( s->ppl.bpp, "No supported authentication methods available", SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE); ssh_sw_abort(s->ppl.ssh, "No supported authentication methods " "available (server sent: %s)", s->last_methods_string->s); return; } } try_new_username:; } userauth_success: if (s->notrivialauth && s->is_trivial_auth) { ssh_proto_error(s->ppl.ssh, "Authentication was trivial! " "Abandoning session as specified in configuration."); return; } /* * We've just received USERAUTH_SUCCESS, and we haven't sent * any packets since. Signal the transport layer to consider * doing an immediate rekey, if it has any reason to want to. */ ssh2_transport_notify_auth_done(s->transport_layer); /* * Finally, hand over to our successor layer, and return * immediately without reaching the crFinishV: ssh_ppl_replace * will have freed us, so crFinishV's zeroing-out of crState would * be a use-after-free bug. */ { 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 bool ssh2_userauth_ki_setup_prompts( struct ssh2_userauth_state *s, BinarySource *src, bool plugin) { ptrlen name, inst; strbuf *sb; /* * We've got a fresh USERAUTH_INFO_REQUEST. Get the preamble and * start building a prompt. */ name = get_string(src); inst = get_string(src); get_string(src); /* skip language tag */ s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); s->cur_prompt->to_server = true; s->cur_prompt->from_server = true; /* * Get any prompt(s) from the packet. */ s->num_prompts = get_uint32(src); for (uint32_t i = 0; i < s->num_prompts; i++) { s->is_trivial_auth = false; ptrlen prompt = get_string(src); bool echo = get_bool(src); if (get_err(src)) { ssh_proto_error(s->ppl.ssh, "%s sent truncated %s packet", plugin ? "Plugin" : "Server", plugin ? "PLUGIN_KI_USER_REQUEST" : "SSH_MSG_USERAUTH_INFO_REQUEST"); return false; } sb = strbuf_new(); if (!prompt.len) { put_fmt(sb, "<%s failed to send prompt>: ", plugin ? "plugin" : "server"); } else if (s->ki_scc) { stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb)); put_datapl(s->ki_scc, prompt); stripctrl_retarget(s->ki_scc, NULL); } else { put_datapl(sb, prompt); } add_prompt(s->cur_prompt, strbuf_to_str(sb), echo); } /* * Make the header strings. This includes the 'name' (optional * dialog-box title) and 'instruction' from the server. * * First, display our disambiguating header line if this is the * first time round the loop - _unless_ the server has sent a * completely empty k-i packet with no prompts _or_ text, which * apparently some do. In that situation there's no need to alert * the user that the following text is server- supplied, because, * well, _what_ text? * * We also only do this if we got a stripctrl, because if we * didn't, that suggests this is all being done via dialog boxes * anyway. */ if (!s->ki_printed_header && s->ki_scc && (s->num_prompts || name.len || inst.len)) { seat_antispoof_msg( ppl_get_iseat(&s->ppl), (plugin ? "Keyboard-interactive authentication prompts from plugin:" : "Keyboard-interactive authentication prompts from server:")); s->ki_printed_header = true; seat_set_trust_status(s->ppl.seat, false); } sb = strbuf_new(); if (name.len) { if (s->ki_scc) { stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb)); put_datapl(s->ki_scc, name); stripctrl_retarget(s->ki_scc, NULL); } else { put_datapl(sb, name); } s->cur_prompt->name_reqd = true; } else { if (plugin) put_datapl(sb, PTRLEN_LITERAL( "Communication with authentication plugin")); else put_datapl(sb, PTRLEN_LITERAL("SSH server authentication")); s->cur_prompt->name_reqd = false; } s->cur_prompt->name = strbuf_to_str(sb); sb = strbuf_new(); if (inst.len) { if (s->ki_scc) { stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb)); put_datapl(s->ki_scc, inst); stripctrl_retarget(s->ki_scc, NULL); } else { put_datapl(sb, inst); } s->cur_prompt->instr_reqd = true; } else { s->cur_prompt->instr_reqd = false; } if (sb->len) s->cur_prompt->instruction = strbuf_to_str(sb); else strbuf_free(sb); return true; } static bool ssh2_userauth_ki_run_prompts(struct ssh2_userauth_state *s) { s->spr = seat_get_userpass_input( ppl_get_iseat(&s->ppl), s->cur_prompt); return s->spr.kind != SPRK_INCOMPLETE; } static void ssh2_userauth_ki_write_responses( struct ssh2_userauth_state *s, BinarySink *bs) { put_uint32(bs, s->num_prompts); for (uint32_t i = 0; i < s->num_prompts; i++) put_stringz(bs, prompt_get_result_ref(s->cur_prompt->prompts[i])); /* * Free the prompts structure from this iteration. If there's * another, a new one will be allocated when we return to the top * of this while loop. */ free_prompts(s->cur_prompt); s->cur_prompt = NULL; } static void ssh2_userauth_add_session_id( struct ssh2_userauth_state *s, strbuf *sigdata) { if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) { put_datapl(sigdata, s->session_id); } else { put_stringpl(sigdata, s->session_id); } } static void ssh2_userauth_agent_query( struct ssh2_userauth_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, ssh2_userauth_agent_callback, s); if (!s->auth_agent_query) ssh2_userauth_agent_callback(s, response, response_len); } static void ssh2_userauth_agent_callback(void *uav, void *reply, int replylen) { struct ssh2_userauth_state *s = (struct ssh2_userauth_state *)uav; 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); } /* * Helper function to add the algorithm and public key strings to a * "publickey" auth packet. Deals with overriding both strings if the * user has provided a detached certificate which matches the public * key in question. */ static void ssh2_userauth_add_alg_and_publickey( struct ssh2_userauth_state *s, PktOut *pkt, ptrlen alg, ptrlen pkblob) { PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ if (s->detached_cert_blob) { ptrlen detached_cert_pl = ptrlen_from_strbuf(s->detached_cert_blob); strbuf *certbase = NULL, *pkbase = NULL; bool done = false; const ssh_keyalg *pkalg = find_pubkey_alg_len(alg); ssh_key *certkey = NULL, *pk = NULL; strbuf *fail_reason = strbuf_new(); bool verbose = true; /* * Whether or not we send the certificate, we're likely to * generate a log message about it. But we don't want to log * once for the offer and once for the real auth attempt, so * we de-duplicate by remembering the last public key this * function saw. */ if (!s->cert_pubkey_diagnosed) s->cert_pubkey_diagnosed = strbuf_new(); if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(s->cert_pubkey_diagnosed), pkblob)) { verbose = false; } else { /* Log this time, but arrange that we don't mention it next time */ strbuf_clear(s->cert_pubkey_diagnosed); put_datapl(s->cert_pubkey_diagnosed, pkblob); } /* * Check that the public key we're replacing is compatible * with the certificate, in that they should have the same * base public key. */ const ssh_keyalg *certalg = pubkey_blob_to_alg(detached_cert_pl); assert(certalg); /* we checked this before setting s->detached_blob */ assert(certalg->is_certificate); /* and this too */ certkey = ssh_key_new_pub(certalg, detached_cert_pl); if (!certkey) { put_fmt(fail_reason, "certificate key file is invalid"); goto no_match; } certbase = strbuf_new(); ssh_key_public_blob(ssh_key_base_key(certkey), BinarySink_UPCAST(certbase)); if (ptrlen_eq_ptrlen(pkblob, ptrlen_from_strbuf(certbase))) goto match; /* yes, a match! */ /* * If we reach here, the certificate's base key was not * identical to the key we're given. But it might still be * identical to the _base_ key of the key we're given, if we * were using a differently certified version of the same key. * In that situation, the detached cert should still override. */ if (!pkalg) { put_fmt(fail_reason, "unable to identify algorithm of base key"); goto no_match; } pk = ssh_key_new_pub(pkalg, pkblob); if (!pk) { put_fmt(fail_reason, "base public key is invalid"); goto no_match; } pkbase = strbuf_new(); ssh_key_public_blob(ssh_key_base_key(pk), BinarySink_UPCAST(pkbase)); if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(pkbase), ptrlen_from_strbuf(certbase))) goto match; /* yes, a match on 2nd attempt! */ /* Give up; we've tried to match these keys up and failed. */ put_fmt(fail_reason, "base public key does not match certificate"); goto no_match; match: /* * The two keys match, so insert the detached certificate into * the output packet in place of the public key we were given. * * However, we need to be a bit careful with the algorithm * name: we might need to upgrade it to one that matches the * original algorithm name. (If we were asked to add an * ssh-rsa key but were given algorithm name "rsa-sha2-512", * then instead of the certificate's algorithm name * ssh-rsa-cert-v01@... we need to write the corresponding * SHA-512 name rsa-sha2-512-cert-v01@... .) */ if (verbose) { ppl_logevent("Sending public key with certificate from \"%s\"", filename_to_str(s->detached_cert_file)); } put_stringz(pkt, ssh_keyalg_related_alg(certalg, pkalg)->ssh_id); put_stringpl(pkt, ptrlen_from_strbuf(s->detached_cert_blob)); done = true; goto out; no_match: /* Log that we didn't send the certificate, if this public key * isn't the same one as last call to this function. (Need to * avoid verbosely logging once for the offer and once for the * real auth attempt.) */ if (verbose) { ppl_logevent("Not substituting certificate \"%s\" for public " "key: %s", filename_to_str(s->detached_cert_file), fail_reason->s); if (s->publickey_blob) { /* If the user provided a specific key file to use (i.e. * this wasn't just a key we picked opportunistically out * of an agent), then they probably _care_ that we didn't * send the certificate, so we should make a loud error * message about it as well as just commenting in the * Event Log. */ ppl_printf("Unable to use certificate \"%s\" with public " "key \"%s\": %s\r\n", filename_to_str(s->detached_cert_file), filename_to_str(s->keyfile), fail_reason->s); } } out: /* Whether we did that or not, free our stuff. */ if (certbase) strbuf_free(certbase); if (pkbase) strbuf_free(pkbase); if (certkey) ssh_key_free(certkey); if (pk) ssh_key_free(pk); strbuf_free(fail_reason); /* And if we did, don't fall through to the alternative below */ if (done) return; } /* In all other cases, just put in what we were given. */ put_stringpl(pkt, alg); put_stringpl(pkt, pkblob); } /* * Helper function to add an SSH-2 signature blob to a packet. Expects * to be shown the public key blob as well as the signature blob. * Normally just appends the sig blob unmodified as a string, except * that it optionally breaks it open and fiddle with it to work around * BUG_SSH2_RSA_PADDING. */ static void ssh2_userauth_add_sigblob( struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob) { BinarySource pk[1], sig[1]; BinarySource_BARE_INIT_PL(pk, pkblob); BinarySource_BARE_INIT_PL(sig, sigblob); /* dmemdump(pkblob, pkblob_len); */ /* dmemdump(sigblob, sigblob_len); */ /* * See if this is in fact an ssh-rsa signature and a buggy * server; otherwise we can just do this the easy way. */ if ((s->ppl.remote_bugs & BUG_SSH2_RSA_PADDING) && ptrlen_eq_string(get_string(pk), "ssh-rsa") && ptrlen_eq_string(get_string(sig), "ssh-rsa")) { ptrlen mod_mp, sig_mp; size_t sig_prefix_len; /* * Find the modulus and signature integers. */ get_string(pk); /* skip over exponent */ mod_mp = get_string(pk); /* remember modulus */ sig_prefix_len = sig->pos; sig_mp = get_string(sig); if (get_err(pk) || get_err(sig)) goto give_up; /* * Find the byte length of the modulus, not counting leading * zeroes. */ while (mod_mp.len > 0 && *(const char *)mod_mp.ptr == 0) { mod_mp.len--; mod_mp.ptr = (const char *)mod_mp.ptr + 1; } /* debug("modulus length is %d\n", len); */ /* debug("signature length is %d\n", siglen); */ if (mod_mp.len > sig_mp.len) { strbuf *substr = strbuf_new(); put_data(substr, sigblob.ptr, sig_prefix_len); put_uint32(substr, mod_mp.len); put_padding(substr, mod_mp.len - sig_mp.len, 0); put_datapl(substr, sig_mp); put_stringsb(pkt, substr); return; } /* Otherwise fall through and do it the easy way. We also come * here as a fallback if we discover above that the key blob * is misformatted in some way. */ give_up:; } put_stringpl(pkt, sigblob); } #ifndef NO_GSSAPI static PktOut *ssh2_userauth_gss_packet( struct ssh2_userauth_state *s, const char *authtype) { strbuf *sb; PktOut *p; Ssh_gss_buf buf; Ssh_gss_buf mic; /* * The mic is computed over the session id + intended * USERAUTH_REQUEST packet. */ sb = strbuf_new(); put_stringpl(sb, s->session_id); put_byte(sb, SSH2_MSG_USERAUTH_REQUEST); put_stringz(sb, s->username); put_stringz(sb, s->successor_layer->vt->name); put_stringz(sb, authtype); /* Compute the mic */ buf.value = sb->s; buf.length = sb->len; s->shgss->lib->get_mic(s->shgss->lib, s->shgss->ctx, &buf, &mic); strbuf_free(sb); /* Now we can build the real packet */ if (strcmp(authtype, "gssapi-with-mic") == 0) { p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_MIC); } else { p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(p, s->username); put_stringz(p, s->successor_layer->vt->name); put_stringz(p, authtype); } put_string(p, mic.value, mic.length); return p; } #endif static bool ssh2_userauth_get_specials( PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) { /* No specials provided by this layer. */ return false; } static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl, SessionSpecialCode code, int arg) { /* No specials provided by this layer. */ } static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf) { struct ssh2_userauth_state *s = container_of(ppl, struct ssh2_userauth_state, ppl); ssh_ppl_reconfigure(s->successor_layer, conf); }