/** @brief Select the different methods on basis of client's and * server's kex messages, and watches out if a match is possible. */ int ssh_kex_select_methods (ssh_session session){ struct ssh_kex_struct *server = &session->next_crypto->server_kex; struct ssh_kex_struct *client = &session->next_crypto->client_kex; char *ext_start = NULL; int i; /* Here we should drop the ext-info-c from the list so we avoid matching. * it. We added it to the end, so we can just truncate the string here */ ext_start = strstr(client->methods[SSH_KEX], ","KEX_EXTENSION_CLIENT); if (ext_start != NULL) { ext_start[0] = '\0'; } for (i = 0; i < KEX_METHODS_SIZE; i++) { session->next_crypto->kex_methods[i]=ssh_find_matching(server->methods[i],client->methods[i]); if(session->next_crypto->kex_methods[i] == NULL && i < SSH_LANG_C_S){ ssh_set_error(session,SSH_FATAL,"kex error : no match for method %s: server [%s], client [%s]", ssh_kex_descriptions[i],server->methods[i],client->methods[i]); return SSH_ERROR; } else if ((i >= SSH_LANG_C_S) && (session->next_crypto->kex_methods[i] == NULL)) { /* we can safely do that for languages */ session->next_crypto->kex_methods[i] = strdup(""); } } if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group1-sha1") == 0){ session->next_crypto->kex_type=SSH_KEX_DH_GROUP1_SHA1; } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group14-sha1") == 0){ session->next_crypto->kex_type=SSH_KEX_DH_GROUP14_SHA1; } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group16-sha512") == 0){ session->next_crypto->kex_type=SSH_KEX_DH_GROUP16_SHA512; } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group18-sha512") == 0){ session->next_crypto->kex_type=SSH_KEX_DH_GROUP18_SHA512; #ifdef WITH_GEX } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group-exchange-sha1") == 0){ session->next_crypto->kex_type=SSH_KEX_DH_GEX_SHA1; } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group-exchange-sha256") == 0){ session->next_crypto->kex_type=SSH_KEX_DH_GEX_SHA256; #endif /* WITH_GEX */ } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp256") == 0){ session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP256; } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp384") == 0){ session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP384; } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp521") == 0){ session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP521; } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "*****@*****.**") == 0){ session->next_crypto->kex_type=SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG; } else if(strcmp(session->next_crypto->kex_methods[SSH_KEX], "curve25519-sha256") == 0){ session->next_crypto->kex_type=SSH_KEX_CURVE25519_SHA256; } SSH_LOG(SSH_LOG_INFO, "Negotiated %s,%s,%s,%s,%s,%s,%s,%s,%s,%s", session->next_crypto->kex_methods[SSH_KEX], session->next_crypto->kex_methods[SSH_HOSTKEYS], session->next_crypto->kex_methods[SSH_CRYPT_C_S], session->next_crypto->kex_methods[SSH_CRYPT_S_C], session->next_crypto->kex_methods[SSH_MAC_C_S], session->next_crypto->kex_methods[SSH_MAC_S_C], session->next_crypto->kex_methods[SSH_COMP_C_S], session->next_crypto->kex_methods[SSH_COMP_S_C], session->next_crypto->kex_methods[SSH_LANG_C_S], session->next_crypto->kex_methods[SSH_LANG_S_C] ); return SSH_OK; }
int ssh_message_auth_interactive_request(ssh_message msg, const char *name, const char *instruction, unsigned int num_prompts, const char **prompts, char *echo) { int r; unsigned int i = 0; ssh_string tmp = NULL; if(name == NULL || instruction == NULL) { return SSH_ERROR; } if(num_prompts > 0 && (prompts == NULL || echo == NULL)) { return SSH_ERROR; } if (buffer_add_u8(msg->session->out_buffer, SSH2_MSG_USERAUTH_INFO_REQUEST) < 0) { return SSH_ERROR; } /* name */ tmp = ssh_string_from_char(name); if (tmp == NULL) { return SSH_ERROR; } r = buffer_add_ssh_string(msg->session->out_buffer, tmp); ssh_string_free(tmp); if (r < 0) { return SSH_ERROR; } /* instruction */ tmp = ssh_string_from_char(instruction); if (tmp == NULL) { return SSH_ERROR; } r = buffer_add_ssh_string(msg->session->out_buffer, tmp); ssh_string_free(tmp); if (r < 0) { return SSH_ERROR; } /* language tag */ tmp = ssh_string_from_char(""); if (tmp == NULL) { return SSH_ERROR; } r = buffer_add_ssh_string(msg->session->out_buffer, tmp); ssh_string_free(tmp); if (r < 0) { return SSH_ERROR; } /* num prompts */ if (buffer_add_u32(msg->session->out_buffer, ntohl(num_prompts)) < 0) { return SSH_ERROR; } for(i = 0; i < num_prompts; i++) { /* prompt[i] */ tmp = ssh_string_from_char(prompts[i]); if (tmp == NULL) { return SSH_ERROR; } r = buffer_add_ssh_string(msg->session->out_buffer, tmp); ssh_string_free(tmp); if (r < 0) { return SSH_ERROR; } /* echo[i] */ if (buffer_add_u8(msg->session->out_buffer, echo[i]) < 0) { return SSH_ERROR; } } r = packet_send(msg->session); /* fill in the kbdint structure */ if (msg->session->kbdint == NULL) { SSH_LOG(SSH_LOG_PROTOCOL, "Warning: Got a " "keyboard-interactive response but it " "seems we didn't send the request."); msg->session->kbdint = ssh_kbdint_new(); if (msg->session->kbdint == NULL) { ssh_set_error_oom(msg->session); return SSH_ERROR; } } else { ssh_kbdint_clean(msg->session->kbdint); } msg->session->kbdint->name = strdup(name); if(msg->session->kbdint->name == NULL) { ssh_set_error_oom(msg->session); ssh_kbdint_free(msg->session->kbdint); msg->session->kbdint = NULL; return SSH_PACKET_USED; } msg->session->kbdint->instruction = strdup(instruction); if(msg->session->kbdint->instruction == NULL) { ssh_set_error_oom(msg->session); ssh_kbdint_free(msg->session->kbdint); msg->session->kbdint = NULL; return SSH_PACKET_USED; } msg->session->kbdint->nprompts = num_prompts; if(num_prompts > 0) { msg->session->kbdint->prompts = malloc(num_prompts * sizeof(char *)); if (msg->session->kbdint->prompts == NULL) { msg->session->kbdint->nprompts = 0; ssh_set_error_oom(msg->session); ssh_kbdint_free(msg->session->kbdint); msg->session->kbdint = NULL; return SSH_ERROR; } msg->session->kbdint->echo = malloc(num_prompts * sizeof(unsigned char)); if (msg->session->kbdint->echo == NULL) { ssh_set_error_oom(msg->session); ssh_kbdint_free(msg->session->kbdint); msg->session->kbdint = NULL; return SSH_ERROR; } for (i = 0; i < num_prompts; i++) { msg->session->kbdint->echo[i] = echo[i]; msg->session->kbdint->prompts[i] = strdup(prompts[i]); if (msg->session->kbdint->prompts[i] == NULL) { ssh_set_error_oom(msg->session); msg->session->kbdint->nprompts = i; ssh_kbdint_free(msg->session->kbdint); msg->session->kbdint = NULL; return SSH_PACKET_USED; } } } else { msg->session->kbdint->prompts = NULL; msg->session->kbdint->echo = NULL; } return r; }
/** * @internal * * @brief Gets the banner from socket and saves it in session. * Updates the session state * * @param data pointer to the beginning of header * @param len size of the banner * @param user is a pointer to session * @returns Number of bytes processed, or zero if the banner is not complete. */ static int callback_receive_banner(const void *data, size_t len, void *user) { char *buffer = (char *)data; ssh_session session=(ssh_session) user; char *str = NULL; size_t i; int ret=0; if (session->session_state != SSH_SESSION_STATE_SOCKET_CONNECTED) { ssh_set_error(session,SSH_FATAL, "Wrong state in callback_receive_banner : %d", session->session_state); return SSH_ERROR; } for (i = 0; i < len; ++i) { #ifdef WITH_PCAP if (session->pcap_ctx && buffer[i] == '\n') { ssh_pcap_context_write(session->pcap_ctx, SSH_PCAP_DIR_IN, buffer,i+1, i+1); } #endif if (buffer[i] == '\r') { buffer[i] = '\0'; } if (buffer[i] == '\n') { int cmp; buffer[i] = '\0'; /* The server MAY send other lines of data... */ cmp = strncmp(buffer, "SSH-", 4); if (cmp == 0) { str = strdup(buffer); if (str == NULL) { return SSH_ERROR; } /* number of bytes read */ ret = i + 1; session->serverbanner = str; session->session_state = SSH_SESSION_STATE_BANNER_RECEIVED; SSH_LOG(SSH_LOG_PACKET, "Received banner: %s", str); session->ssh_connection_callback(session); return ret; } else { SSH_LOG(SSH_LOG_DEBUG, "ssh_protocol_version_exchange: %s", buffer); ret = i + 1; break; } } /* According to RFC 4253 the max banner length is 255 */ if (i > 255) { /* Too big banner */ session->session_state=SSH_SESSION_STATE_ERROR; ssh_set_error(session, SSH_FATAL, "Receiving banner: too large banner"); return 0; } } return ret; }
/** * @brief SSH poll callback. This callback will be used when an event * caught on the socket. * * @param p Poll object this callback belongs to. * @param fd The raw socket. * @param revents The current poll events on the socket. * @param userdata Userdata to be passed to the callback function, * in this case the socket object. * * @return 0 on success, < 0 when the poll object has been removed * from its poll context. */ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, socket_t fd, int revents, void *v_s) { ssh_socket s = (ssh_socket)v_s; char buffer[MAX_BUF_SIZE]; int r; int err = 0; socklen_t errlen = sizeof(err); /* Do not do anything if this socket was already closed */ if (!ssh_socket_is_open(s)) { return -1; } SSH_LOG(SSH_LOG_TRACE, "Poll callback on socket %d (%s%s%s), out buffer %d",fd, (revents & POLLIN) ? "POLLIN ":"", (revents & POLLOUT) ? "POLLOUT ":"", (revents & POLLERR) ? "POLLERR":"", ssh_buffer_get_len(s->out_buffer)); if (revents & POLLERR || revents & POLLHUP) { /* Check if we are in a connecting state */ if (s->state == SSH_SOCKET_CONNECTING) { s->state = SSH_SOCKET_ERROR; r = getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen); if (r < 0) { err = errno; } s->last_errno = err; ssh_socket_close(s); if (s->callbacks && s->callbacks->connected) { s->callbacks->connected(SSH_SOCKET_CONNECTED_ERROR, err, s->callbacks->userdata); } return -1; } /* Then we are in a more standard kind of error */ /* force a read to get an explanation */ revents |= POLLIN; } if ((revents & POLLIN) && s->state == SSH_SOCKET_CONNECTED) { s->read_wontblock = 1; r = ssh_socket_unbuffered_read(s, buffer, sizeof(buffer)); if (r < 0) { if (p != NULL) { ssh_poll_remove_events(p, POLLIN); } if (s->callbacks && s->callbacks->exception) { s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, s->last_errno, s->callbacks->userdata); /* p may have been freed, so don't use it * anymore in this function */ p = NULL; return -2; } } if (r == 0) { if (p != NULL) { ssh_poll_remove_events(p, POLLIN); } if (p != NULL) { ssh_poll_remove_events(p, POLLIN); } if (s->callbacks && s->callbacks->exception) { s->callbacks->exception(SSH_SOCKET_EXCEPTION_EOF, 0, s->callbacks->userdata); /* p may have been freed, so don't use it * anymore in this function */ p = NULL; return -2; } } if (r > 0) { if (s->session->socket_counter != NULL) { s->session->socket_counter->in_bytes += r; } /* Bufferize the data and then call the callback */ r = ssh_buffer_add_data(s->in_buffer, buffer, r); if (r < 0) { return -1; } if (s->callbacks && s->callbacks->data) { do { r = s->callbacks->data(ssh_buffer_get(s->in_buffer), ssh_buffer_get_len(s->in_buffer), s->callbacks->userdata); ssh_buffer_pass_bytes(s->in_buffer, r); } while ((r > 0) && (s->state == SSH_SOCKET_CONNECTED)); /* p may have been freed, so don't use it * anymore in this function */ p = NULL; } } } #ifdef _WIN32 if (revents & POLLOUT || revents & POLLWRNORM) { #else if (revents & POLLOUT) { #endif /* First, POLLOUT is a sign we may be connected */ if (s->state == SSH_SOCKET_CONNECTING) { SSH_LOG(SSH_LOG_PACKET, "Received POLLOUT in connecting state"); s->state = SSH_SOCKET_CONNECTED; if (p != NULL) { ssh_poll_set_events(p, POLLOUT | POLLIN); } r = ssh_socket_set_blocking(ssh_socket_get_fd_in(s)); if (r < 0) { return -1; } if (s->callbacks && s->callbacks->connected) { s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, 0, s->callbacks->userdata); } return 0; } /* So, we can write data */ s->write_wontblock=1; if (p != NULL) { ssh_poll_remove_events(p, POLLOUT); } /* If buffered data is pending, write it */ if (ssh_buffer_get_len(s->out_buffer) > 0) { ssh_socket_nonblocking_flush(s); } else if (s->callbacks && s->callbacks->controlflow) { /* Otherwise advertise the upper level that write can be done */ SSH_LOG(SSH_LOG_TRACE,"sending control flow event"); s->callbacks->controlflow(SSH_SOCKET_FLOW_WRITEWONTBLOCK, s->callbacks->userdata); } /* TODO: Find a way to put back POLLOUT when buffering occurs */ } /* Return -1 if one of the poll handlers disappeared */ return (s->poll_in == NULL || s->poll_out == NULL) ? -1 : 0; } /** @internal * @brief returns the input poll handle corresponding to the socket, * creates it if it does not exist. * @returns allocated and initialized ssh_poll_handle object */ ssh_poll_handle ssh_socket_get_poll_handle_in(ssh_socket s){ if(s->poll_in) return s->poll_in; s->poll_in=ssh_poll_new(s->fd_in,0,ssh_socket_pollcallback,s); if(s->fd_in == s->fd_out && s->poll_out == NULL) s->poll_out=s->poll_in; return s->poll_in; } /** @internal * @brief returns the output poll handle corresponding to the socket, * creates it if it does not exist. * @returns allocated and initialized ssh_poll_handle object */ ssh_poll_handle ssh_socket_get_poll_handle_out(ssh_socket s){ if(s->poll_out) return s->poll_out; s->poll_out=ssh_poll_new(s->fd_out,0,ssh_socket_pollcallback,s); if(s->fd_in == s->fd_out && s->poll_in == NULL) s->poll_in=s->poll_out; return s->poll_out; } /** \internal * \brief Deletes a socket object */ void ssh_socket_free(ssh_socket s){ if (s == NULL) { return; } ssh_socket_close(s); ssh_buffer_free(s->in_buffer); ssh_buffer_free(s->out_buffer); SAFE_FREE(s); } #ifndef _WIN32 int ssh_socket_unix(ssh_socket s, const char *path) { struct sockaddr_un sunaddr; socket_t fd; sunaddr.sun_family = AF_UNIX; snprintf(sunaddr.sun_path, sizeof(sunaddr.sun_path), "%s", path); fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == SSH_INVALID_SOCKET) { ssh_set_error(s->session, SSH_FATAL, "Error from socket(AF_UNIX, SOCK_STREAM, 0): %s", strerror(errno)); return -1; } if (fcntl(fd, F_SETFD, 1) == -1) { ssh_set_error(s->session, SSH_FATAL, "Error from fcntl(fd, F_SETFD, 1): %s", strerror(errno)); close(fd); return -1; } if (connect(fd, (struct sockaddr *) &sunaddr, sizeof(sunaddr)) < 0) { ssh_set_error(s->session, SSH_FATAL, "Error from connect(): %s", strerror(errno)); close(fd); return -1; } ssh_socket_set_fd(s,fd); return 0; } #endif /** \internal * \brief closes a socket */ void ssh_socket_close(ssh_socket s){ if (ssh_socket_is_open(s)) { #ifdef _WIN32 CLOSE_SOCKET(s->fd_in); /* fd_in = fd_out under win32 */ s->last_errno = WSAGetLastError(); #else if (s->fd_out != s->fd_in && s->fd_out != -1) { CLOSE_SOCKET(s->fd_out); } CLOSE_SOCKET(s->fd_in); s->last_errno = errno; #endif } if(s->poll_in != NULL){ if(s->poll_out == s->poll_in) s->poll_out = NULL; ssh_poll_free(s->poll_in); s->poll_in=NULL; } if(s->poll_out != NULL){ ssh_poll_free(s->poll_out); s->poll_out=NULL; } s->state = SSH_SOCKET_CLOSED; } /** * @internal * @brief sets the file descriptor of the socket. * @param[out] s ssh_socket to update * @param[in] fd file descriptor to set * @warning this function updates boths the input and output * file descriptors */ void ssh_socket_set_fd(ssh_socket s, socket_t fd) { s->fd_in = s->fd_out = fd; if (s->poll_in) { ssh_poll_set_fd(s->poll_in,fd); } else { s->state = SSH_SOCKET_CONNECTING; /* POLLOUT is the event to wait for in a nonblocking connect */ ssh_poll_set_events(ssh_socket_get_poll_handle_in(s), POLLOUT); #ifdef _WIN32 ssh_poll_add_events(ssh_socket_get_poll_handle_in(s), POLLWRNORM); #endif } }
/** * @internal * * @brief A function to be called each time a step has been done in the * connection. */ static void ssh_server_connection_callback(ssh_session session){ int ssh1,ssh2; switch(session->session_state){ case SSH_SESSION_STATE_NONE: case SSH_SESSION_STATE_CONNECTING: case SSH_SESSION_STATE_SOCKET_CONNECTED: break; case SSH_SESSION_STATE_BANNER_RECEIVED: if (session->clientbanner == NULL) { goto error; } set_status(session, 0.4f); SSH_LOG(SSH_LOG_RARE, "SSH client banner: %s", session->clientbanner); /* Here we analyze the different protocols the server allows. */ if (ssh_analyze_banner(session, 1, &ssh1, &ssh2) < 0) { goto error; } /* Here we decide which version of the protocol to use. */ if (ssh2 && session->opts.ssh2) { session->version = 2; } else if (ssh1 && session->opts.ssh1) { session->version = 1; } else if (ssh1 && !session->opts.ssh1) { #ifdef WITH_SSH1 ssh_set_error(session, SSH_FATAL, "SSH-1 protocol not available (configure session to allow SSH-1)"); goto error; #else ssh_set_error(session, SSH_FATAL, "SSH-1 protocol not available (libssh compiled without SSH-1 support)"); goto error; #endif } else { ssh_set_error(session, SSH_FATAL, "No version of SSH protocol usable (banner: %s)", session->clientbanner); goto error; } /* from now, the packet layer is handling incoming packets */ if(session->version==2) session->socket_callbacks.data=ssh_packet_socket_callback; #ifdef WITH_SSH1 else session->socket_callbacks.data=ssh_packet_socket_callback1; #endif ssh_packet_set_default_callbacks(session); set_status(session, 0.5f); session->session_state=SSH_SESSION_STATE_INITIAL_KEX; if (ssh_send_kex(session, 1) < 0) { goto error; } break; case SSH_SESSION_STATE_INITIAL_KEX: /* TODO: This state should disappear in favor of get_key handle */ #ifdef WITH_SSH1 if(session->version==1){ if (ssh_get_kex1(session) < 0) goto error; set_status(session,0.6f); session->connected = 1; break; } #endif break; case SSH_SESSION_STATE_KEXINIT_RECEIVED: set_status(session,0.6f); if(session->next_crypto->server_kex.methods[0]==NULL){ if(server_set_kex(session) == SSH_ERROR) goto error; /* We are in a rekeying, so we need to send the server kex */ if(ssh_send_kex(session, 1) < 0) goto error; } ssh_list_kex(&session->next_crypto->client_kex); // log client kex if (ssh_kex_select_methods(session) < 0) { goto error; } if (crypt_set_algorithms_server(session) == SSH_ERROR) goto error; set_status(session,0.8f); session->session_state=SSH_SESSION_STATE_DH; break; case SSH_SESSION_STATE_DH: if(session->dh_handshake_state==DH_STATE_FINISHED){ if (generate_session_keys(session) < 0) { goto error; } /* * Once we got SSH2_MSG_NEWKEYS we can switch next_crypto and * current_crypto */ if (session->current_crypto) { crypto_free(session->current_crypto); } /* FIXME TODO later, include a function to change keys */ session->current_crypto = session->next_crypto; session->next_crypto = crypto_new(); if (session->next_crypto == NULL) { goto error; } session->next_crypto->session_id = malloc(session->current_crypto->digest_len); if (session->next_crypto->session_id == NULL) { ssh_set_error_oom(session); goto error; } memcpy(session->next_crypto->session_id, session->current_crypto->session_id, session->current_crypto->digest_len); set_status(session,1.0f); session->connected = 1; session->session_state=SSH_SESSION_STATE_AUTHENTICATING; if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) session->session_state = SSH_SESSION_STATE_AUTHENTICATED; } break; case SSH_SESSION_STATE_AUTHENTICATING: break; case SSH_SESSION_STATE_ERROR: goto error; default: ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state); } return; error: ssh_socket_close(session->socket); session->alive = 0; session->session_state=SSH_SESSION_STATE_ERROR; }
int ssh_server_ecdh_init(ssh_session session, ssh_buffer packet){ /* ECDH keys */ ssh_string q_c_string; ssh_string q_s_string; EC_KEY *ecdh_key; const EC_GROUP *group; const EC_POINT *ecdh_pubkey; bignum_CTX ctx; /* SSH host keys (rsa,dsa,ecdsa) */ ssh_key privkey; ssh_string sig_blob = NULL; int len; int rc; /* Extract the client pubkey from the init packet */ q_c_string = buffer_get_ssh_string(packet); if (q_c_string == NULL) { ssh_set_error(session,SSH_FATAL, "No Q_C ECC point in packet"); return SSH_ERROR; } session->next_crypto->ecdh_client_pubkey = q_c_string; /* Build server's keypair */ ctx = BN_CTX_new(); ecdh_key = EC_KEY_new_by_curve_name(NISTP256); if (ecdh_key == NULL) { ssh_set_error_oom(session); BN_CTX_free(ctx); return SSH_ERROR; } group = EC_KEY_get0_group(ecdh_key); EC_KEY_generate_key(ecdh_key); ecdh_pubkey = EC_KEY_get0_public_key(ecdh_key); len = EC_POINT_point2oct(group, ecdh_pubkey, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, ctx); q_s_string = ssh_string_new(len); if (q_s_string == NULL) { EC_KEY_free(ecdh_key); BN_CTX_free(ctx); return SSH_ERROR; } EC_POINT_point2oct(group, ecdh_pubkey, POINT_CONVERSION_UNCOMPRESSED, ssh_string_data(q_s_string), len, ctx); BN_CTX_free(ctx); session->next_crypto->ecdh_privkey = ecdh_key; session->next_crypto->ecdh_server_pubkey = q_s_string; rc = buffer_add_u8(session->out_buffer, SSH2_MSG_KEXDH_REPLY); if (rc < 0) { ssh_set_error_oom(session); return SSH_ERROR; } /* build k and session_id */ rc = ecdh_build_k(session); if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Cannot build k number"); return SSH_ERROR; } /* privkey is not allocated */ rc = ssh_get_key_params(session, &privkey); if (rc == SSH_ERROR) { return SSH_ERROR; } rc = make_sessionid(session); if (rc != SSH_OK) { ssh_set_error(session, SSH_FATAL, "Could not create a session id"); return SSH_ERROR; } /* add host's public key */ rc = buffer_add_ssh_string(session->out_buffer, session->next_crypto->server_pubkey); if (rc < 0) { ssh_set_error_oom(session); return SSH_ERROR; } /* add ecdh public key */ rc = buffer_add_ssh_string(session->out_buffer, q_s_string); if (rc < 0) { ssh_set_error_oom(session); return SSH_ERROR; } /* add signature blob */ sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey); if (sig_blob == NULL) { ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); return SSH_ERROR; } rc = buffer_add_ssh_string(session->out_buffer, sig_blob); ssh_string_free(sig_blob); if (rc < 0) { ssh_set_error_oom(session); return SSH_ERROR; } SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_KEXDH_REPLY sent"); rc = packet_send(session); if (rc == SSH_ERROR) { return SSH_ERROR; } /* Send the MSG_NEWKEYS */ rc = buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); if (rc < 0) { return SSH_ERROR;; } session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; rc = packet_send(session); SSH_LOG(SSH_LOG_PROTOCOL, "SSH_MSG_NEWKEYS sent"); return rc; }
static void evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, void *in, void *out, size_t len, uint8_t *tag, uint64_t seq) { size_t authlen, aadlen; uint8_t lastiv[1]; int tmplen = 0; size_t outlen; int rc; (void) seq; aadlen = cipher->lenfield_blocksize; authlen = cipher->tag_size; /* increment IV */ rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, EVP_CTRL_GCM_IV_GEN, 1, lastiv); if (rc == 0) { SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_IV_GEN failed"); return; } /* Pass over the authenticated data (not encrypted) */ rc = EVP_EncryptUpdate(cipher->ctx, NULL, &tmplen, (unsigned char *)in, (int)aadlen); outlen = tmplen; if (rc == 0 || outlen != aadlen) { SSH_LOG(SSH_LOG_WARNING, "Failed to pass authenticated data"); return; } memcpy(out, in, aadlen); /* Encrypt the rest of the data */ rc = EVP_EncryptUpdate(cipher->ctx, (unsigned char *)out + aadlen, &tmplen, (unsigned char *)in + aadlen, (int)len - aadlen); outlen = tmplen; if (rc != 1 || outlen != (int)len - aadlen) { SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptUpdate failed"); return; } /* compute tag */ rc = EVP_EncryptFinal(cipher->ctx, NULL, &tmplen); if (rc < 0) { SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptFinal failed: Failed to create a tag"); return; } rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, EVP_CTRL_GCM_GET_TAG, authlen, (unsigned char *)tag); if (rc != 1) { SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_GET_TAG failed"); return; } }
/** @internal * @brief handles an user authentication using GSSAPI */ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n_oid, ssh_string *oids){ char service_name[]="host"; gss_buffer_desc name_buf; gss_name_t server_name; /* local server fqdn */ OM_uint32 maj_stat, min_stat; unsigned int i; char *ptr; gss_OID_set supported; /* oids supported by server */ gss_OID_set both_supported; /* oids supported by both client and server */ gss_OID_set selected; /* oid selected for authentication */ int present=0; int oid_count=0; struct gss_OID_desc_struct oid; int rc; if (ssh_callbacks_exists(session->server_callbacks, gssapi_select_oid_function)){ ssh_string oid_s = session->server_callbacks->gssapi_select_oid_function(session, user, n_oid, oids, session->server_callbacks->userdata); if (oid_s != NULL){ if (ssh_gssapi_init(session) == SSH_ERROR) return SSH_ERROR; session->gssapi->state = SSH_GSSAPI_STATE_RCV_TOKEN; rc = ssh_gssapi_send_response(session, oid_s); ssh_string_free(oid_s); return rc; } else { return ssh_auth_reply_default(session,0); } } gss_create_empty_oid_set(&min_stat, &both_supported); maj_stat = gss_indicate_mechs(&min_stat, &supported); for (i=0; i < supported->count; ++i){ ptr = ssh_get_hexa(supported->elements[i].elements, supported->elements[i].length); SSH_LOG(SSH_LOG_DEBUG, "Supported mech %d: %s\n", i, ptr); free(ptr); } for (i=0 ; i< n_oid ; ++i){ unsigned char *oid_s = (unsigned char *) ssh_string_data(oids[i]); size_t len = ssh_string_len(oids[i]); if(len < 2 || oid_s[0] != SSH_OID_TAG || ((size_t)oid_s[1]) != len - 2){ SSH_LOG(SSH_LOG_WARNING,"GSSAPI: received invalid OID"); continue; } oid.elements = &oid_s[2]; oid.length = len - 2; gss_test_oid_set_member(&min_stat,&oid,supported,&present); if(present){ gss_add_oid_set_member(&min_stat,&oid,&both_supported); oid_count++; } } gss_release_oid_set(&min_stat, &supported); if (oid_count == 0){ SSH_LOG(SSH_LOG_PROTOCOL,"GSSAPI: no OID match"); ssh_auth_reply_default(session, 0); gss_release_oid_set(&min_stat, &both_supported); return SSH_OK; } /* from now we have room for context */ if (ssh_gssapi_init(session) == SSH_ERROR) return SSH_ERROR; name_buf.value = service_name; name_buf.length = strlen(name_buf.value) + 1; maj_stat = gss_import_name(&min_stat, &name_buf, (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &server_name); if (maj_stat != GSS_S_COMPLETE) { SSH_LOG(SSH_LOG_WARNING, "importing name %d, %d", maj_stat, min_stat); ssh_gssapi_log_error(SSH_LOG_WARNING, "importing name", maj_stat); return -1; } maj_stat = gss_acquire_cred(&min_stat, server_name, 0, both_supported, GSS_C_ACCEPT, &session->gssapi->server_creds, &selected, NULL); gss_release_name(&min_stat, &server_name); gss_release_oid_set(&min_stat, &both_supported); if (maj_stat != GSS_S_COMPLETE) { SSH_LOG(SSH_LOG_WARNING, "error acquiring credentials %d, %d", maj_stat, min_stat); ssh_gssapi_log_error(SSH_LOG_WARNING, "acquiring creds", maj_stat); ssh_auth_reply_default(session,0); return SSH_ERROR; } SSH_LOG(SSH_LOG_PROTOCOL, "acquiring credentials %d, %d", maj_stat, min_stat); /* finding which OID from client we selected */ for (i=0 ; i< n_oid ; ++i){ unsigned char *oid_s = (unsigned char *) ssh_string_data(oids[i]); size_t len = ssh_string_len(oids[i]); if(len < 2 || oid_s[0] != SSH_OID_TAG || ((size_t)oid_s[1]) != len - 2){ SSH_LOG(SSH_LOG_WARNING,"GSSAPI: received invalid OID"); continue; } oid.elements = &oid_s[2]; oid.length = len - 2; gss_test_oid_set_member(&min_stat,&oid,selected,&present); if(present){ SSH_LOG(SSH_LOG_PACKET, "Selected oid %d", i); break; } } session->gssapi->mech.length = oid.length; session->gssapi->mech.elements = malloc(oid.length); if (session->gssapi->mech.elements == NULL){ ssh_set_error_oom(session); return SSH_ERROR; } memcpy(session->gssapi->mech.elements, oid.elements, oid.length); gss_release_oid_set(&min_stat, &selected); session->gssapi->user = strdup(user); session->gssapi->service = service_name; session->gssapi->state = SSH_GSSAPI_STATE_RCV_TOKEN; return ssh_gssapi_send_response(session, oids[i]); }
/** @brief returns the OIDs of the mechs that have usable credentials */ static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) { OM_uint32 maj_stat, min_stat, lifetime; gss_OID_set actual_mechs; gss_buffer_desc namebuf; gss_name_t client_id = GSS_C_NO_NAME; gss_OID oid; unsigned int i; char *ptr; int ret; if (session->gssapi->client.client_deleg_creds == NULL) { if (session->opts.gss_client_identity != NULL) { namebuf.value = (void *)session->opts.gss_client_identity; namebuf.length = strlen(session->opts.gss_client_identity); maj_stat = gss_import_name(&min_stat, &namebuf, GSS_C_NT_USER_NAME, &client_id); if (GSS_ERROR(maj_stat)) { ret = SSH_ERROR; goto end; } } maj_stat = gss_acquire_cred(&min_stat, client_id, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_INITIATE, &session->gssapi->client.creds, &actual_mechs, NULL); if (GSS_ERROR(maj_stat)) { ret = SSH_ERROR; goto end; } } else { session->gssapi->client.creds = session->gssapi->client.client_deleg_creds; maj_stat = gss_inquire_cred(&min_stat, session->gssapi->client.creds, &client_id, NULL, NULL, &actual_mechs); if (GSS_ERROR(maj_stat)) { ret = SSH_ERROR; goto end; } } gss_create_empty_oid_set(&min_stat, valid_oids); /* double check each single cred */ for (i = 0; i < actual_mechs->count; i++) { /* check lifetime is not 0 or skip */ lifetime = 0; oid = &actual_mechs->elements[i]; maj_stat = gss_inquire_cred_by_mech(&min_stat, session->gssapi->client.creds, oid, NULL, &lifetime, NULL, NULL); if (maj_stat == GSS_S_COMPLETE && lifetime > 0) { gss_add_oid_set_member(&min_stat, oid, valid_oids); ptr = ssh_get_hexa(oid->elements, oid->length); SSH_LOG(SSH_LOG_DEBUG, "GSSAPI valid oid %d : %s\n", i, ptr); SAFE_FREE(ptr); } } ret = SSH_OK; end: gss_release_name(&min_stat, &client_id); return ret; }
/** * @internal * * @brief A function to be called each time a step has been done in the * connection. */ static void ssh_client_connection_callback(ssh_session session) { int rc; switch(session->session_state) { case SSH_SESSION_STATE_NONE: case SSH_SESSION_STATE_CONNECTING: break; case SSH_SESSION_STATE_SOCKET_CONNECTED: ssh_set_fd_towrite(session); ssh_send_banner(session, 0); break; case SSH_SESSION_STATE_BANNER_RECEIVED: if (session->serverbanner == NULL) { goto error; } set_status(session, 0.4f); SSH_LOG(SSH_LOG_RARE, "SSH server banner: %s", session->serverbanner); /* Here we analyze the different protocols the server allows. */ rc = ssh_analyze_banner(session, 0); if (rc < 0) { ssh_set_error(session, SSH_FATAL, "No version of SSH protocol usable (banner: %s)", session->serverbanner); goto error; } ssh_packet_register_socket_callback(session, session->socket); ssh_packet_set_default_callbacks(session); session->session_state = SSH_SESSION_STATE_INITIAL_KEX; rc = ssh_set_client_kex(session); if (rc != SSH_OK) { goto error; } rc = ssh_send_kex(session, 0); if (rc < 0) { goto error; } set_status(session, 0.5f); break; case SSH_SESSION_STATE_INITIAL_KEX: /* TODO: This state should disappear in favor of get_key handle */ break; case SSH_SESSION_STATE_KEXINIT_RECEIVED: set_status(session,0.6f); ssh_list_kex(&session->next_crypto->server_kex); if (session->next_crypto->client_kex.methods[0] == NULL) { /* in rekeying state if next_crypto client_kex is empty */ rc = ssh_set_client_kex(session); if (rc != SSH_OK) { goto error; } rc = ssh_send_kex(session, 0); if (rc < 0) { goto error; } } if (ssh_kex_select_methods(session) == SSH_ERROR) goto error; set_status(session,0.8f); session->session_state=SSH_SESSION_STATE_DH; if (dh_handshake(session) == SSH_ERROR) { goto error; } /* FALL THROUGH */ case SSH_SESSION_STATE_DH: if(session->dh_handshake_state==DH_STATE_FINISHED){ set_status(session,1.0f); session->connected = 1; if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) session->session_state = SSH_SESSION_STATE_AUTHENTICATED; else session->session_state=SSH_SESSION_STATE_AUTHENTICATING; } break; case SSH_SESSION_STATE_AUTHENTICATING: break; case SSH_SESSION_STATE_ERROR: goto error; default: ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state); } return; error: ssh_socket_close(session->socket); session->alive = 0; session->session_state=SSH_SESSION_STATE_ERROR; }
static void ssh_gssapi_log_error(int verb, const char *msg, int maj_stat){ gss_buffer_desc buffer; OM_uint32 dummy, message_context; gss_display_status(&dummy,maj_stat,GSS_C_GSS_CODE, GSS_C_NO_OID, &message_context, &buffer); SSH_LOG(verb, "GSSAPI(%s): %s", msg, (const char *)buffer.value); }
/** @internal * @brief encrypts an ed25519 private key blob * */ static int pki_private_key_encrypt(ssh_buffer privkey_buffer, const char* passphrase, const char *ciphername, const char *kdfname, ssh_auth_callback auth_fn, void *auth_data, uint32_t rounds, ssh_string salt) { struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); struct ssh_cipher_struct cipher; uint8_t key_material[128]; size_t key_material_len; char passphrase_buffer[128]; int rc; int i; uint8_t padding = 1; int cmp; cmp = strcmp(ciphername, "none"); if (cmp == 0){ /* no encryption required */ return SSH_OK; } for (i = 0; ciphers[i].name != NULL; i++) { cmp = strcmp(ciphername, ciphers[i].name); if (cmp == 0){ memcpy(&cipher, &ciphers[i], sizeof(cipher)); break; } } if (ciphers[i].name == NULL){ SSH_LOG(SSH_LOG_WARN, "Unsupported cipher %s", ciphername); return SSH_ERROR; } cmp = strcmp(kdfname, "bcrypt"); if (cmp != 0){ SSH_LOG(SSH_LOG_WARN, "Unsupported KDF %s", kdfname); return SSH_ERROR; } while (ssh_buffer_get_len(privkey_buffer) % cipher.blocksize != 0) { rc = buffer_add_u8(privkey_buffer, padding); if (rc < 0) { return SSH_ERROR; } padding++; } /* We need material for key (keysize bits / 8) and IV (blocksize) */ key_material_len = cipher.keysize/8 + cipher.blocksize; if (key_material_len > sizeof(key_material)){ ssh_pki_log("Key material too big"); return SSH_ERROR; } ssh_pki_log("Encryption: %d key, %d IV, %d rounds, %zu bytes salt", cipher.keysize/8, cipher.blocksize, rounds, ssh_string_len(salt)); if (passphrase == NULL){ if (auth_fn == NULL){ ssh_pki_log("No passphrase provided"); return SSH_ERROR; } rc = auth_fn("Passphrase", passphrase_buffer, sizeof(passphrase_buffer), 0, 0, auth_data); if (rc != SSH_OK){ return SSH_ERROR; } passphrase = passphrase_buffer; } rc = bcrypt_pbkdf(passphrase, strlen(passphrase), ssh_string_data(salt), ssh_string_len(salt), key_material, key_material_len, rounds); if (rc < 0){ return SSH_ERROR; } cipher.set_encrypt_key(&cipher, key_material, key_material + cipher.keysize/8); cipher.encrypt(&cipher, ssh_buffer_get_begin(privkey_buffer), ssh_buffer_get_begin(privkey_buffer), ssh_buffer_get_len(privkey_buffer)); ssh_cipher_clear(&cipher); BURN_BUFFER(passphrase_buffer, sizeof(passphrase_buffer)); return SSH_OK; }
/** @internal * @brief Import a private key in OpenSSH (new) format. This format is * typically used with ed25519 keys but can be used for others. */ ssh_key ssh_pki_openssh_privkey_import(const char *text_key, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data) { const char *ptr=text_key; const char *end; char *base64; int cmp; int rc; int i; ssh_buffer buffer = NULL, privkey_buffer=NULL; char *magic = NULL, *ciphername = NULL, *kdfname = NULL; uint32_t nkeys = 0, checkint1, checkint2; ssh_string kdfoptions = NULL; ssh_string pubkey0 = NULL; ssh_string privkeys = NULL; ssh_string comment = NULL; ssh_key key = NULL; uint8_t padding; cmp = strncmp(ptr, OPENSSH_HEADER_BEGIN, strlen(OPENSSH_HEADER_BEGIN)); if (cmp != 0){ SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (no header)"); goto error; } ptr += strlen(OPENSSH_HEADER_BEGIN); while(ptr[0] != '\0' && !isspace((int)ptr[0])) { ptr++; } end = strstr(ptr, OPENSSH_HEADER_END); if (end == NULL){ SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (no footer)"); goto error; } base64 = malloc(end - ptr + 1); if (base64 == NULL){ goto error; } for (i = 0; ptr < end; ptr++){ if (!isspace((int)ptr[0])) { base64[i] = ptr[0]; i++; } } base64[i] = '\0'; buffer = base64_to_bin(base64); SAFE_FREE(base64); if (buffer == NULL){ SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (base64 error)"); goto error; } rc = ssh_buffer_unpack(buffer, "PssSdSS", strlen(OPENSSH_AUTH_MAGIC) + 1, &magic, &ciphername, &kdfname, &kdfoptions, &nkeys, &pubkey0, &privkeys); if (rc == SSH_ERROR){ SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (unpack error)"); goto error; } cmp = strncmp(magic, OPENSSH_AUTH_MAGIC, strlen(OPENSSH_AUTH_MAGIC)); if (cmp != 0){ SSH_LOG(SSH_LOG_WARN, "Not an OpenSSH private key (bad magic)"); goto error; } ssh_pki_log("Opening OpenSSH private key: ciphername: %s, kdf: %s, nkeys: %d\n", ciphername, kdfname, nkeys); if (nkeys != 1){ SSH_LOG(SSH_LOG_WARN, "Opening OpenSSH private key: only 1 key supported (%d available)", nkeys); goto error; } rc = pki_private_key_decrypt(privkeys, passphrase, ciphername, kdfname, kdfoptions, auth_fn, auth_data); if (rc == SSH_ERROR){ goto error; } privkey_buffer = ssh_buffer_new(); if (privkey_buffer == NULL) { rc = SSH_ERROR; goto error; } ssh_buffer_set_secure(privkey_buffer); ssh_buffer_add_data(privkey_buffer, ssh_string_data(privkeys), ssh_string_len(privkeys)); rc = ssh_buffer_unpack(privkey_buffer, "dd", &checkint1, &checkint2); if (rc == SSH_ERROR || checkint1 != checkint2){ SSH_LOG(SSH_LOG_WARN, "OpenSSH private key unpack error (correct password?)"); goto error; } rc = pki_openssh_import_privkey_blob(privkey_buffer, &key); if (rc == SSH_ERROR){ goto error; } comment = buffer_get_ssh_string(privkey_buffer); SAFE_FREE(comment); /* verify that the remaining data is correct padding */ for (i=1; buffer_get_rest_len(privkey_buffer) > 0; ++i){ buffer_get_u8(privkey_buffer, &padding); if (padding != i){ ssh_key_free(key); key = NULL; ssh_pki_log("Invalid padding"); goto error; } } error: if(buffer != NULL){ ssh_buffer_free(buffer); buffer = NULL; } if(privkey_buffer != NULL){ ssh_buffer_free(privkey_buffer); privkey_buffer = NULL; } SAFE_FREE(magic); SAFE_FREE(ciphername); SAFE_FREE(kdfname); SAFE_FREE(kdfoptions); SAFE_FREE(pubkey0); SAFE_FREE(privkeys); return key; }
/** * @brief decrypts an encrypted ed25519 private key blob * */ static int pki_private_key_decrypt(ssh_string blob, const char* passphrase, const char *ciphername, const char *kdfname, ssh_string kdfoptions, ssh_auth_callback auth_fn, void *auth_data) { struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); struct ssh_cipher_struct cipher; uint8_t key_material[128]; char passphrase_buffer[128]; size_t key_material_len; ssh_buffer buffer; ssh_string salt; uint32_t rounds; int cmp; int rc; int i; cmp = strcmp(ciphername, "none"); if (cmp == 0){ /* no decryption required */ return SSH_OK; } for (i = 0; ciphers[i].name != NULL; i++) { cmp = strcmp(ciphername, ciphers[i].name); if (cmp == 0){ memcpy(&cipher, &ciphers[i], sizeof(cipher)); break; } } if (ciphers[i].name == NULL){ SSH_LOG(SSH_LOG_WARN, "Unsupported cipher %s", ciphername); return SSH_ERROR; } cmp = strcmp(kdfname, "bcrypt"); if (cmp != 0) { SSH_LOG(SSH_LOG_WARN, "Unsupported KDF %s", kdfname); return SSH_ERROR; } if (ssh_string_len(blob) % cipher.blocksize != 0) { SSH_LOG(SSH_LOG_WARN, "Encrypted string not multiple of blocksize: %zu", ssh_string_len(blob)); return SSH_ERROR; } buffer = ssh_buffer_new(); if (buffer == NULL){ return SSH_ERROR; } rc = ssh_buffer_add_data(buffer, ssh_string_data(kdfoptions), ssh_string_len(kdfoptions)); if (rc != SSH_ERROR){ rc = ssh_buffer_unpack(buffer, "Sd", &salt, &rounds); } ssh_buffer_free(buffer); if (rc == SSH_ERROR){ return SSH_ERROR; } /* We need material for key (keysize bits / 8) and IV (blocksize) */ key_material_len = cipher.keysize/8 + cipher.blocksize; if (key_material_len > sizeof(key_material)) { ssh_pki_log("Key material too big"); return SSH_ERROR; } ssh_pki_log("Decryption: %d key, %d IV, %d rounds, %zu bytes salt", cipher.keysize/8, cipher.blocksize, rounds, ssh_string_len(salt)); if (passphrase == NULL) { if (auth_fn == NULL) { SAFE_FREE(salt); ssh_pki_log("No passphrase provided"); return SSH_ERROR; } rc = auth_fn("Passphrase", passphrase_buffer, sizeof(passphrase_buffer), 0, 0, auth_data); if (rc != SSH_OK) { SAFE_FREE(salt); return SSH_ERROR; } passphrase = passphrase_buffer; } rc = bcrypt_pbkdf(passphrase, strlen(passphrase), ssh_string_data(salt), ssh_string_len(salt), key_material, key_material_len, rounds); SAFE_FREE(salt); if (rc < 0){ return SSH_ERROR; } BURN_BUFFER(passphrase_buffer, sizeof(passphrase_buffer)); cipher.set_decrypt_key(&cipher, key_material, key_material + cipher.keysize/8); cipher.decrypt(&cipher, ssh_string_data(blob), ssh_string_data(blob), ssh_string_len(blob)); ssh_cipher_clear(&cipher); return SSH_OK; }
static int packet_send2(ssh_session session) { unsigned int blocksize = (session->current_crypto ? session->current_crypto->out_cipher->blocksize : 8); unsigned int lenfield_blocksize = (session->current_crypto ? session->current_crypto->out_cipher->lenfield_blocksize : 0); enum ssh_hmac_e hmac_type = (session->current_crypto ? session->current_crypto->out_hmac : session->next_crypto->out_hmac); uint32_t currentlen = ssh_buffer_get_len(session->out_buffer); unsigned char *hmac = NULL; char padstring[32] = { 0 }; int rc = SSH_ERROR; uint32_t finallen,payloadsize,compsize; uint8_t padding; ssh_buffer header_buffer = ssh_buffer_new(); payloadsize = currentlen; #ifdef WITH_ZLIB if (session->current_crypto && session->current_crypto->do_compress_out && ssh_buffer_get_len(session->out_buffer)) { if (compress_buffer(session,session->out_buffer) < 0) { goto error; } currentlen = ssh_buffer_get_len(session->out_buffer); } #endif /* WITH_ZLIB */ compsize = currentlen; /* compressed payload + packet len (4) + padding len (1) */ /* totallen - lenfield_blocksize must be equal to 0 (mod blocksize) */ padding = (blocksize - ((blocksize - lenfield_blocksize + currentlen + 5) % blocksize)); if(padding < 4) { padding += blocksize; } if (session->current_crypto != NULL) { int ok; ok = ssh_get_random(padstring, padding, 0); if (!ok) { ssh_set_error(session, SSH_FATAL, "PRNG error"); goto error; } } if (header_buffer == NULL){ ssh_set_error_oom(session); goto error; } finallen = currentlen + padding + 1; rc = ssh_buffer_pack(header_buffer, "db", finallen, padding); if (rc == SSH_ERROR){ goto error; } rc = ssh_buffer_prepend_data(session->out_buffer, ssh_buffer_get(header_buffer), ssh_buffer_get_len(header_buffer)); if (rc < 0) { goto error; } rc = ssh_buffer_add_data(session->out_buffer, padstring, padding); if (rc < 0) { goto error; } #ifdef WITH_PCAP if (session->pcap_ctx) { ssh_pcap_context_write(session->pcap_ctx, SSH_PCAP_DIR_OUT, ssh_buffer_get(session->out_buffer), ssh_buffer_get_len(session->out_buffer), ssh_buffer_get_len(session->out_buffer)); } #endif hmac = ssh_packet_encrypt(session, ssh_buffer_get(session->out_buffer), ssh_buffer_get_len(session->out_buffer)); if (hmac) { rc = ssh_buffer_add_data(session->out_buffer, hmac, hmac_digest_len(hmac_type)); if (rc < 0) { goto error; } } rc = ssh_packet_write(session); session->send_seq++; if (session->raw_counter != NULL) { session->raw_counter->out_bytes += payloadsize; session->raw_counter->out_packets++; } SSH_LOG(SSH_LOG_PACKET, "packet: wrote [len=%d,padding=%hhd,comp=%d,payload=%d]", finallen, padding, compsize, payloadsize); if (ssh_buffer_reinit(session->out_buffer) < 0) { rc = SSH_ERROR; } error: if (header_buffer != NULL) { ssh_buffer_free(header_buffer); } return rc; /* SSH_OK, AGAIN or ERROR */ }
/** * @brief launches a gssapi-with-mic auth request * @returns SSH_AUTH_ERROR: A serious error happened\n * SSH_AUTH_DENIED: Authentication failed : use another method\n * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again * later. */ int ssh_gssapi_auth_mic(ssh_session session){ int i; gss_OID_set selected; /* oid selected for authentication */ ssh_string *oids; int rc; int n_oids = 0; OM_uint32 maj_stat, min_stat; char name_buf[256]; gss_buffer_desc hostname; const char *gss_host = session->opts.host; rc = ssh_gssapi_init(session); if (rc == SSH_ERROR) { return SSH_AUTH_ERROR; } if (session->opts.gss_server_identity != NULL) { gss_host = session->opts.gss_server_identity; } /* import target host name */ snprintf(name_buf, sizeof(name_buf), "host@%s", gss_host); hostname.value = name_buf; hostname.length = strlen(name_buf) + 1; maj_stat = gss_import_name(&min_stat, &hostname, (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, &session->gssapi->client.server_name); if (maj_stat != GSS_S_COMPLETE) { SSH_LOG(SSH_LOG_WARNING, "importing name %d, %d", maj_stat, min_stat); ssh_gssapi_log_error(SSH_LOG_WARNING, "importing name", maj_stat); return SSH_PACKET_USED; } /* copy username */ session->gssapi->user = strdup(session->opts.username); if (session->gssapi->user == NULL) { ssh_set_error_oom(session); return SSH_AUTH_ERROR; } SSH_LOG(SSH_LOG_PROTOCOL, "Authenticating with gssapi to host %s with user %s", session->opts.host, session->gssapi->user); rc = ssh_gssapi_match(session, &selected); if (rc == SSH_ERROR) { return SSH_AUTH_DENIED; } n_oids = selected->count; SSH_LOG(SSH_LOG_PROTOCOL, "Sending %d oids", n_oids); oids = calloc(n_oids, sizeof(ssh_string)); if (oids == NULL) { ssh_set_error_oom(session); return SSH_AUTH_ERROR; } for (i=0; i<n_oids; ++i){ oids[i] = ssh_string_new(selected->elements[i].length + 2); ((unsigned char *)oids[i]->data)[0] = SSH_OID_TAG; ((unsigned char *)oids[i]->data)[1] = selected->elements[i].length; memcpy((unsigned char *)oids[i]->data + 2, selected->elements[i].elements, selected->elements[i].length); } rc = ssh_gssapi_send_auth_mic(session, oids, n_oids); for (i = 0; i < n_oids; i++) { ssh_string_free(oids[i]); } free(oids); if (rc != SSH_ERROR) { return SSH_AUTH_AGAIN; } return SSH_AUTH_ERROR; }
/** * @internal * * @brief This function parses the last end of a channel request packet. * * This is normally converted to a SSH message and placed in the queue. * * @param[in] session The SSH session. * * @param[in] channel The channel the request is made on. * * @param[in] packet The rest of the packet to be parsed. * * @param[in] request The type of request. * * @param[in] want_reply The want_reply field from the request. * * @returns SSH_OK on success, SSH_ERROR if an error occured. */ int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, ssh_buffer packet, const char *request, uint8_t want_reply) { ssh_message msg = NULL; msg = ssh_message_new(session); if (msg == NULL) { ssh_set_error_oom(session); goto error; } SSH_LOG(SSH_LOG_PACKET, "Received a %s channel_request for channel (%d:%d) (want_reply=%hhd)", request, channel->local_channel, channel->remote_channel, want_reply); msg->type = SSH_REQUEST_CHANNEL; msg->channel_request.channel = channel; msg->channel_request.want_reply = want_reply; if (strcmp(request, "pty-req") == 0) { ssh_string term = NULL; char *term_c = NULL; term = buffer_get_ssh_string(packet); if (term == NULL) { ssh_set_error_oom(session); goto error; } term_c = ssh_string_to_char(term); if (term_c == NULL) { ssh_set_error_oom(session); ssh_string_free(term); goto error; } ssh_string_free(term); msg->channel_request.type = SSH_CHANNEL_REQUEST_PTY; msg->channel_request.TERM = term_c; buffer_get_u32(packet, &msg->channel_request.width); buffer_get_u32(packet, &msg->channel_request.height); buffer_get_u32(packet, &msg->channel_request.pxwidth); buffer_get_u32(packet, &msg->channel_request.pxheight); msg->channel_request.width = ntohl(msg->channel_request.width); msg->channel_request.height = ntohl(msg->channel_request.height); msg->channel_request.pxwidth = ntohl(msg->channel_request.pxwidth); msg->channel_request.pxheight = ntohl(msg->channel_request.pxheight); msg->channel_request.modes = buffer_get_ssh_string(packet); if (msg->channel_request.modes == NULL) { SAFE_FREE(term_c); goto error; } goto end; } if (strcmp(request, "window-change") == 0) { msg->channel_request.type = SSH_CHANNEL_REQUEST_WINDOW_CHANGE; buffer_get_u32(packet, &msg->channel_request.width); buffer_get_u32(packet, &msg->channel_request.height); buffer_get_u32(packet, &msg->channel_request.pxwidth); buffer_get_u32(packet, &msg->channel_request.pxheight); msg->channel_request.width = ntohl(msg->channel_request.width); msg->channel_request.height = ntohl(msg->channel_request.height); msg->channel_request.pxwidth = ntohl(msg->channel_request.pxwidth); msg->channel_request.pxheight = ntohl(msg->channel_request.pxheight); goto end; } if (strcmp(request, "subsystem") == 0) { ssh_string subsys = NULL; char *subsys_c = NULL; subsys = buffer_get_ssh_string(packet); if (subsys == NULL) { ssh_set_error_oom(session); goto error; } subsys_c = ssh_string_to_char(subsys); if (subsys_c == NULL) { ssh_set_error_oom(session); ssh_string_free(subsys); goto error; } ssh_string_free(subsys); msg->channel_request.type = SSH_CHANNEL_REQUEST_SUBSYSTEM; msg->channel_request.subsystem = subsys_c; goto end; } if (strcmp(request, "shell") == 0) { msg->channel_request.type = SSH_CHANNEL_REQUEST_SHELL; goto end; } if (strcmp(request, "exec") == 0) { ssh_string cmd = NULL; cmd = buffer_get_ssh_string(packet); if (cmd == NULL) { ssh_set_error_oom(session); goto error; } msg->channel_request.type = SSH_CHANNEL_REQUEST_EXEC; msg->channel_request.command = ssh_string_to_char(cmd); ssh_string_free(cmd); if (msg->channel_request.command == NULL) { goto error; } goto end; } if (strcmp(request, "env") == 0) { ssh_string name = NULL; ssh_string value = NULL; name = buffer_get_ssh_string(packet); if (name == NULL) { ssh_set_error_oom(session); goto error; } value = buffer_get_ssh_string(packet); if (value == NULL) { ssh_set_error_oom(session); ssh_string_free(name); goto error; } msg->channel_request.type = SSH_CHANNEL_REQUEST_ENV; msg->channel_request.var_name = ssh_string_to_char(name); msg->channel_request.var_value = ssh_string_to_char(value); if (msg->channel_request.var_name == NULL || msg->channel_request.var_value == NULL) { ssh_string_free(name); ssh_string_free(value); goto error; } ssh_string_free(name); ssh_string_free(value); goto end; } if (strcmp(request, "x11-req") == 0) { ssh_string auth_protocol = NULL; ssh_string auth_cookie = NULL; buffer_get_u8(packet, &msg->channel_request.x11_single_connection); auth_protocol = buffer_get_ssh_string(packet); if (auth_protocol == NULL) { ssh_set_error_oom(session); goto error; } auth_cookie = buffer_get_ssh_string(packet); if (auth_cookie == NULL) { ssh_set_error_oom(session); ssh_string_free(auth_protocol); goto error; } msg->channel_request.type = SSH_CHANNEL_REQUEST_X11; msg->channel_request.x11_auth_protocol = ssh_string_to_char(auth_protocol); msg->channel_request.x11_auth_cookie = ssh_string_to_char(auth_cookie); if (msg->channel_request.x11_auth_protocol == NULL || msg->channel_request.x11_auth_cookie == NULL) { ssh_string_free(auth_protocol); ssh_string_free(auth_cookie); goto error; } ssh_string_free(auth_protocol); ssh_string_free(auth_cookie); buffer_get_u32(packet, &msg->channel_request.x11_screen_number); goto end; } msg->channel_request.type = SSH_CHANNEL_REQUEST_UNKNOWN; end: ssh_message_queue(session,msg); return SSH_OK; error: ssh_message_free(msg); return SSH_ERROR; }
/** @internal * @handles a data received event. It then calls the handlers for the different packet types * or and exception handler callback. * @param user pointer to current ssh_session * @param data pointer to the data received * @len length of data received. It might not be enough for a complete packet * @returns number of bytes read and processed. */ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) { ssh_session session= (ssh_session) user; unsigned int blocksize = (session->current_crypto ? session->current_crypto->in_cipher->blocksize : 8); unsigned char mac[DIGEST_MAX_LEN] = {0}; char buffer[16] = {0}; size_t current_macsize = 0; const uint8_t *packet; int to_be_read; int rc; uint32_t len, compsize, payloadsize; uint8_t padding; size_t processed = 0; /* number of byte processed from the callback */ if(session->current_crypto != NULL) { current_macsize = hmac_digest_len(session->current_crypto->in_hmac); } if (data == NULL) { goto error; } if (session->session_state == SSH_SESSION_STATE_ERROR) { goto error; } switch(session->packet_state) { case PACKET_STATE_INIT: if (receivedlen < blocksize) { /* * We didn't receive enough data to read at least one * block size, give up */ return 0; } memset(&session->in_packet, 0, sizeof(PACKET)); if (session->in_buffer) { rc = ssh_buffer_reinit(session->in_buffer); if (rc < 0) { goto error; } } else { session->in_buffer = ssh_buffer_new(); if (session->in_buffer == NULL) { goto error; } } memcpy(buffer, data, blocksize); processed += blocksize; len = ssh_packet_decrypt_len(session, buffer); rc = ssh_buffer_add_data(session->in_buffer, buffer, blocksize); if (rc < 0) { goto error; } if (len > MAX_PACKET_LEN) { ssh_set_error(session, SSH_FATAL, "read_packet(): Packet len too high(%u %.4x)", len, len); goto error; } to_be_read = len - blocksize + sizeof(uint32_t); if (to_be_read < 0) { /* remote sshd sends invalid sizes? */ ssh_set_error(session, SSH_FATAL, "Given numbers of bytes left to be read < 0 (%d)!", to_be_read); goto error; } /* Saves the status of the current operations */ session->in_packet.len = len; session->packet_state = PACKET_STATE_SIZEREAD; /* FALL TROUGH */ case PACKET_STATE_SIZEREAD: len = session->in_packet.len; to_be_read = len - blocksize + sizeof(uint32_t) + current_macsize; /* if to_be_read is zero, the whole packet was blocksize bytes. */ if (to_be_read != 0) { if (receivedlen - processed < (unsigned int)to_be_read) { /* give up, not enough data in buffer */ SSH_LOG(SSH_LOG_PACKET,"packet: partial packet (read len) [len=%d]",len); return processed; } packet = ((uint8_t*)data) + processed; #if 0 ssh_socket_read(session->socket, packet, to_be_read - current_macsize); #endif rc = ssh_buffer_add_data(session->in_buffer, packet, to_be_read - current_macsize); if (rc < 0) { goto error; } processed += to_be_read - current_macsize; } if (session->current_crypto) { /* * Decrypt the rest of the packet (blocksize bytes already * have been decrypted) */ uint32_t buffer_len = ssh_buffer_get_len(session->in_buffer); /* The following check avoids decrypting zero bytes */ if (buffer_len > blocksize) { uint8_t *payload = ((uint8_t*)ssh_buffer_get(session->in_buffer) + blocksize); uint32_t plen = buffer_len - blocksize; rc = ssh_packet_decrypt(session, payload, plen); if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Decrypt error"); goto error; } } /* copy the last part from the incoming buffer */ packet = ((uint8_t *)data) + processed; memcpy(mac, packet, current_macsize); rc = ssh_packet_hmac_verify(session, session->in_buffer, mac, session->current_crypto->in_hmac); if (rc < 0) { ssh_set_error(session, SSH_FATAL, "HMAC error"); goto error; } processed += current_macsize; } /* skip the size field which has been processed before */ ssh_buffer_pass_bytes(session->in_buffer, sizeof(uint32_t)); rc = ssh_buffer_get_u8(session->in_buffer, &padding); if (rc == 0) { ssh_set_error(session, SSH_FATAL, "Packet too short to read padding"); goto error; } if (padding > ssh_buffer_get_len(session->in_buffer)) { ssh_set_error(session, SSH_FATAL, "Invalid padding: %d (%d left)", padding, ssh_buffer_get_len(session->in_buffer)); goto error; } ssh_buffer_pass_bytes_end(session->in_buffer, padding); compsize = ssh_buffer_get_len(session->in_buffer); #ifdef WITH_ZLIB if (session->current_crypto && session->current_crypto->do_compress_in && ssh_buffer_get_len(session->in_buffer) > 0) { rc = decompress_buffer(session, session->in_buffer,MAX_PACKET_LEN); if (rc < 0) { goto error; } } #endif /* WITH_ZLIB */ payloadsize = ssh_buffer_get_len(session->in_buffer); session->recv_seq++; if (session->raw_counter != NULL) { session->raw_counter->in_bytes += payloadsize; session->raw_counter->in_packets++; } /* * We don't want to rewrite a new packet while still executing the * packet callbacks */ session->packet_state = PACKET_STATE_PROCESSING; ssh_packet_parse_type(session); SSH_LOG(SSH_LOG_PACKET, "packet: read type %hhd [len=%d,padding=%hhd,comp=%d,payload=%d]", session->in_packet.type, len, padding, compsize, payloadsize); /* Execute callbacks */ ssh_packet_process(session, session->in_packet.type); session->packet_state = PACKET_STATE_INIT; if (processed < receivedlen) { /* Handle a potential packet left in socket buffer */ SSH_LOG(SSH_LOG_PACKET, "Processing %" PRIdS " bytes left in socket buffer", receivedlen-processed); packet = ((uint8_t*)data) + processed; rc = ssh_packet_socket_callback(packet, receivedlen - processed,user); processed += rc; } return processed; case PACKET_STATE_PROCESSING: SSH_LOG(SSH_LOG_PACKET, "Nested packet processing. Delaying."); return 0; } ssh_set_error(session, SSH_FATAL, "Invalid state into packet_read2(): %d", session->packet_state); error: session->session_state= SSH_SESSION_STATE_ERROR; return processed; }
static void evp_cipher_init(struct ssh_cipher_struct *cipher) { if (cipher->ctx == NULL) { cipher->ctx = EVP_CIPHER_CTX_new(); } switch(cipher->ciphertype){ case SSH_AES128_CBC: cipher->cipher = EVP_aes_128_cbc(); break; case SSH_AES192_CBC: cipher->cipher = EVP_aes_192_cbc(); break; case SSH_AES256_CBC: cipher->cipher = EVP_aes_256_cbc(); break; #ifdef HAVE_OPENSSL_EVP_AES_CTR case SSH_AES128_CTR: cipher->cipher = EVP_aes_128_ctr(); break; case SSH_AES192_CTR: cipher->cipher = EVP_aes_192_ctr(); break; case SSH_AES256_CTR: cipher->cipher = EVP_aes_256_ctr(); break; #else case SSH_AES128_CTR: case SSH_AES192_CTR: case SSH_AES256_CTR: SSH_LOG(SSH_LOG_WARNING, "This cipher is not available in evp_cipher_init"); break; #endif #ifdef HAVE_OPENSSL_EVP_AES_GCM case SSH_AEAD_AES128_GCM: cipher->cipher = EVP_aes_128_gcm(); break; case SSH_AEAD_AES256_GCM: cipher->cipher = EVP_aes_256_gcm(); break; #else case SSH_AEAD_AES128_GCM: case SSH_AEAD_AES256_GCM: SSH_LOG(SSH_LOG_WARNING, "This cipher is not available in evp_cipher_init"); break; #endif /* HAVE_OPENSSL_EVP_AES_GCM */ case SSH_3DES_CBC: cipher->cipher = EVP_des_ede3_cbc(); break; #ifdef WITH_BLOWFISH_CIPHER case SSH_BLOWFISH_CBC: cipher->cipher = EVP_bf_cbc(); break; /* ciphers not using EVP */ #endif case SSH_AEAD_CHACHA20_POLY1305: SSH_LOG(SSH_LOG_WARNING, "The ChaCha cipher cannot be handled here"); break; case SSH_NO_CIPHER: SSH_LOG(SSH_LOG_WARNING, "No valid ciphertype found"); break; } }
static int packet_send2(ssh_session session) { unsigned int blocksize = (session->current_crypto ? session->current_crypto->out_cipher->blocksize : 8); enum ssh_hmac_e hmac_type = (session->current_crypto ? session->current_crypto->out_hmac : session->next_crypto->out_hmac); uint32_t currentlen = ssh_buffer_get_len(session->out_buffer); unsigned char *hmac = NULL; char padstring[32] = { 0 }; int rc = SSH_ERROR; uint32_t finallen,payloadsize,compsize; uint8_t padding; uint8_t header[sizeof(padding) + sizeof(finallen)] = { 0 }; payloadsize = currentlen; #ifdef WITH_ZLIB if (session->current_crypto && session->current_crypto->do_compress_out && ssh_buffer_get_len(session->out_buffer)) { if (compress_buffer(session,session->out_buffer) < 0) { goto error; } currentlen = ssh_buffer_get_len(session->out_buffer); } #endif /* WITH_ZLIB */ compsize = currentlen; padding = (blocksize - ((currentlen +5) % blocksize)); if(padding < 4) { padding += blocksize; } if (session->current_crypto) { ssh_get_random(padstring, padding, 0); } finallen = htonl(currentlen + padding + 1); memcpy(&header[0], &finallen, sizeof(finallen)); header[sizeof(finallen)] = padding; rc = ssh_buffer_prepend_data(session->out_buffer, &header, sizeof(header)); if (rc < 0) { goto error; } rc = ssh_buffer_add_data(session->out_buffer, padstring, padding); if (rc < 0) { goto error; } #ifdef WITH_PCAP if(session->pcap_ctx){ ssh_pcap_context_write(session->pcap_ctx,SSH_PCAP_DIR_OUT, ssh_buffer_get(session->out_buffer),ssh_buffer_get_len(session->out_buffer) ,ssh_buffer_get_len(session->out_buffer)); } #endif hmac = ssh_packet_encrypt(session, ssh_buffer_get(session->out_buffer), ssh_buffer_get_len(session->out_buffer)); if (hmac) { rc = ssh_buffer_add_data(session->out_buffer, hmac, hmac_digest_len(hmac_type)); if (rc < 0) { goto error; } } rc = ssh_packet_write(session); session->send_seq++; if (session->raw_counter != NULL) { session->raw_counter->out_bytes += payloadsize; session->raw_counter->out_packets++; } SSH_LOG(SSH_LOG_PACKET, "packet: wrote [len=%d,padding=%hhd,comp=%d,payload=%d]", ntohl(finallen), padding, compsize, payloadsize); if (ssh_buffer_reinit(session->out_buffer) < 0) { rc = SSH_ERROR; } error: return rc; /* SSH_OK, AGAIN or ERROR */ }
static int evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, void *complete_packet, uint8_t *out, size_t encrypted_size, uint64_t seq) { size_t authlen, aadlen; uint8_t lastiv[1]; int outlen = 0; int rc = 0; (void)seq; aadlen = cipher->lenfield_blocksize; authlen = cipher->tag_size; /* increment IV */ rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, EVP_CTRL_GCM_IV_GEN, 1, lastiv); if (rc == 0) { SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_IV_GEN failed"); return SSH_ERROR; } /* set tag for authentication */ rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, EVP_CTRL_GCM_SET_TAG, authlen, (unsigned char *)complete_packet + aadlen + encrypted_size); if (rc == 0) { SSH_LOG(SSH_LOG_WARNING, "EVP_CTRL_GCM_SET_TAG failed"); return SSH_ERROR; } /* Pass over the authenticated data (not encrypted) */ rc = EVP_DecryptUpdate(cipher->ctx, NULL, &outlen, (unsigned char *)complete_packet, (int)aadlen); if (rc == 0) { SSH_LOG(SSH_LOG_WARNING, "Failed to pass authenticated data"); return SSH_ERROR; } /* Do not copy the length to the target buffer, because it is already processed */ //memcpy(out, complete_packet, aadlen); /* Decrypt the rest of the data */ rc = EVP_DecryptUpdate(cipher->ctx, (unsigned char *)out, &outlen, (unsigned char *)complete_packet + aadlen, encrypted_size /* already substracted aadlen*/); if (rc != 1) { SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptUpdate failed"); return SSH_ERROR; } if (outlen != (int)encrypted_size) { SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptUpdate: output size %d for " SIZET_SPECIFIER " in", outlen, encrypted_size); return SSH_ERROR; } /* verify tag */ rc = EVP_DecryptFinal(cipher->ctx, NULL, &outlen); if (rc < 0) { SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptFinal failed: Failed authentication"); return SSH_ERROR; } return SSH_OK; }
int channel_request_pty_size1(ssh_channel channel, const char *terminal, int col, int row) { ssh_session session; ssh_string str = NULL; if (channel == NULL) { return SSH_ERROR; } session = channel->session; if(channel->request_state != SSH_CHANNEL_REQ_STATE_NONE && channel->request_state != SSH_CHANNEL_REQ_STATE_ACCEPTED){ ssh_set_error(session,SSH_REQUEST_DENIED,"Wrong request state"); return SSH_ERROR; } str = ssh_string_from_char(terminal); if (str == NULL) { ssh_set_error_oom(session); return -1; } if (buffer_add_u8(session->out_buffer, SSH_CMSG_REQUEST_PTY) < 0 || buffer_add_ssh_string(session->out_buffer, str) < 0) { ssh_string_free(str); return -1; } ssh_string_free(str); if (buffer_add_u32(session->out_buffer, ntohl(row)) < 0 || buffer_add_u32(session->out_buffer, ntohl(col)) < 0 || buffer_add_u32(session->out_buffer, 0) < 0 || /* x */ buffer_add_u32(session->out_buffer, 0) < 0 || /* y */ buffer_add_u8(session->out_buffer, 0) < 0) { /* tty things */ return -1; } SSH_LOG(SSH_LOG_FUNCTIONS, "Opening a ssh1 pty"); channel->request_state = SSH_CHANNEL_REQ_STATE_PENDING; if (packet_send(session) == SSH_ERROR) { return -1; } while (channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING) { ssh_handle_packets(session, SSH_TIMEOUT_INFINITE); } switch(channel->request_state){ case SSH_CHANNEL_REQ_STATE_ERROR: case SSH_CHANNEL_REQ_STATE_PENDING: case SSH_CHANNEL_REQ_STATE_NONE: channel->request_state=SSH_CHANNEL_REQ_STATE_NONE; return SSH_ERROR; case SSH_CHANNEL_REQ_STATE_ACCEPTED: channel->request_state=SSH_CHANNEL_REQ_STATE_NONE; SSH_LOG(SSH_LOG_RARE, "PTY: Success"); return SSH_OK; case SSH_CHANNEL_REQ_STATE_DENIED: channel->request_state=SSH_CHANNEL_REQ_STATE_NONE; ssh_set_error(session, SSH_REQUEST_DENIED, "Server denied PTY allocation"); SSH_LOG(SSH_LOG_RARE, "PTY: denied\n"); return SSH_ERROR; } // Not reached return SSH_ERROR; }
static int dh_handshake_server(ssh_session session) { ssh_key privkey; //ssh_string pubkey_blob = NULL; ssh_string sig_blob; ssh_string f; if (dh_generate_y(session) < 0) { ssh_set_error(session, SSH_FATAL, "Could not create y number"); return -1; } if (dh_generate_f(session) < 0) { ssh_set_error(session, SSH_FATAL, "Could not create f number"); return -1; } f = dh_get_f(session); if (f == NULL) { ssh_set_error(session, SSH_FATAL, "Could not get the f number"); return -1; } if (ssh_get_key_params(session,&privkey) != SSH_OK){ ssh_string_free(f); return -1; } if (dh_build_k(session) < 0) { ssh_set_error(session, SSH_FATAL, "Could not import the public key"); ssh_string_free(f); return -1; } if (make_sessionid(session) != SSH_OK) { ssh_set_error(session, SSH_FATAL, "Could not create a session id"); ssh_string_free(f); return -1; } sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey); if (sig_blob == NULL) { ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); ssh_string_free(f); return -1; } if (buffer_add_u8(session->out_buffer, SSH2_MSG_KEXDH_REPLY) < 0 || buffer_add_ssh_string(session->out_buffer, session->next_crypto->server_pubkey) < 0 || buffer_add_ssh_string(session->out_buffer, f) < 0 || buffer_add_ssh_string(session->out_buffer, sig_blob) < 0) { ssh_set_error(session, SSH_FATAL, "Not enough space"); buffer_reinit(session->out_buffer); ssh_string_free(f); ssh_string_free(sig_blob); return -1; } ssh_string_free(f); ssh_string_free(sig_blob); if (packet_send(session) == SSH_ERROR) { return -1; } if (buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { buffer_reinit(session->out_buffer); return -1; } if (packet_send(session) == SSH_ERROR) { return -1; } SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state=DH_STATE_NEWKEYS_SENT; return 0; }
/** * @internal * * @brief A function to be called each time a step has been done in the * connection. */ static void ssh_server_connection_callback(ssh_session session){ int rc; switch(session->session_state){ case SSH_SESSION_STATE_NONE: case SSH_SESSION_STATE_CONNECTING: case SSH_SESSION_STATE_SOCKET_CONNECTED: break; case SSH_SESSION_STATE_BANNER_RECEIVED: if (session->clientbanner == NULL) { goto error; } set_status(session, 0.4f); SSH_LOG(SSH_LOG_RARE, "SSH client banner: %s", session->clientbanner); /* Here we analyze the different protocols the server allows. */ rc = ssh_analyze_banner(session, 1); if (rc < 0) { ssh_set_error(session, SSH_FATAL, "No version of SSH protocol usable (banner: %s)", session->clientbanner); goto error; } /* from now, the packet layer is handling incoming packets */ session->socket_callbacks.data=ssh_packet_socket_callback; ssh_packet_register_socket_callback(session, session->socket); ssh_packet_set_default_callbacks(session); set_status(session, 0.5f); session->session_state=SSH_SESSION_STATE_INITIAL_KEX; if (ssh_send_kex(session, 1) < 0) { goto error; } break; case SSH_SESSION_STATE_INITIAL_KEX: /* TODO: This state should disappear in favor of get_key handle */ break; case SSH_SESSION_STATE_KEXINIT_RECEIVED: set_status(session,0.6f); if(session->next_crypto->server_kex.methods[0]==NULL){ if(server_set_kex(session) == SSH_ERROR) goto error; /* We are in a rekeying, so we need to send the server kex */ if(ssh_send_kex(session, 1) < 0) goto error; } ssh_list_kex(&session->next_crypto->client_kex); // log client kex if (ssh_kex_select_methods(session) < 0) { goto error; } if (crypt_set_algorithms_server(session) == SSH_ERROR) goto error; set_status(session,0.8f); session->session_state=SSH_SESSION_STATE_DH; break; case SSH_SESSION_STATE_DH: if(session->dh_handshake_state==DH_STATE_FINISHED){ rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); if (rc != SSH_OK) { goto error; } /* * If the client supports extension negotiation, we will send * our supported extensions now. This is the first message after * sending NEWKEYS message and after turning on crypto. */ if (session->extensions & SSH_EXT_NEGOTIATION && session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { /* * Only send an SSH_MSG_EXT_INFO message the first time the client * undergoes NEWKEYS. It is unexpected for this message to be sent * upon rekey, and may cause clients to log error messages. * * The session_state can not be used for this purpose because it is * re-set to SSH_SESSION_STATE_KEXINIT_RECEIVED during rekey. So, * use the connected flag which transitions from non-zero below. * * See also: * - https://bugzilla.mindrot.org/show_bug.cgi?id=2929 */ if (session->connected == 0) { ssh_server_send_extensions(session); } } set_status(session,1.0f); session->connected = 1; session->session_state=SSH_SESSION_STATE_AUTHENTICATING; if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) session->session_state = SSH_SESSION_STATE_AUTHENTICATED; } break; case SSH_SESSION_STATE_AUTHENTICATING: break; case SSH_SESSION_STATE_ERROR: goto error; default: ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state); } return; error: ssh_socket_close(session->socket); session->alive = 0; session->session_state=SSH_SESSION_STATE_ERROR; }
/** @internal * replies to an SSH_AUTH packet with a default (denied) response. */ int ssh_auth_reply_default(ssh_session session,int partial) { char methods_c[128] = {0}; ssh_string methods = NULL; int rc = SSH_ERROR; if (buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_FAILURE) < 0) { return rc; } if (session->auth_methods == 0) { session->auth_methods = SSH_AUTH_METHOD_PUBLICKEY | SSH_AUTH_METHOD_PASSWORD; } if (session->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { strncat(methods_c, "publickey,", sizeof(methods_c) - strlen(methods_c) - 1); } if (session->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC){ strncat(methods_c,"gssapi-with-mic,", sizeof(methods_c) - strlen(methods_c) - 1); } if (session->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { strncat(methods_c, "keyboard-interactive,", sizeof(methods_c) - strlen(methods_c) - 1); } if (session->auth_methods & SSH_AUTH_METHOD_PASSWORD) { strncat(methods_c, "password,", sizeof(methods_c) - strlen(methods_c) - 1); } if (session->auth_methods & SSH_AUTH_METHOD_HOSTBASED) { strncat(methods_c, "hostbased,", sizeof(methods_c) - strlen(methods_c) - 1); } if (methods_c[0] == '\0' || methods_c[strlen(methods_c)-1] != ',') { return SSH_ERROR; } /* Strip the comma. */ methods_c[strlen(methods_c) - 1] = '\0'; // strip the comma. We are sure there is at SSH_LOG(SSH_LOG_PACKET, "Sending a auth failure. methods that can continue: %s", methods_c); methods = ssh_string_from_char(methods_c); if (methods == NULL) { goto error; } if (buffer_add_ssh_string(session->out_buffer, methods) < 0) { goto error; } if (partial) { if (buffer_add_u8(session->out_buffer, 1) < 0) { goto error; } } else { if (buffer_add_u8(session->out_buffer, 0) < 0) { goto error; } } rc = packet_send(session); error: ssh_string_free(methods); return rc; }
int ssh_message_auth_interactive_request(ssh_message msg, const char *name, const char *instruction, unsigned int num_prompts, const char **prompts, char *echo) { int rc; unsigned int i = 0; if(name == NULL || instruction == NULL) { return SSH_ERROR; } if(num_prompts > 0 && (prompts == NULL || echo == NULL)) { return SSH_ERROR; } rc = ssh_buffer_pack(msg->session->out_buffer, "bsssd", SSH2_MSG_USERAUTH_INFO_REQUEST, name, instruction, "", /* language tag */ num_prompts); if (rc != SSH_OK){ ssh_set_error_oom(msg->session); return SSH_ERROR; } for(i = 0; i < num_prompts; i++) { rc = ssh_buffer_pack(msg->session->out_buffer, "sb", prompts[i], echo[i] ? 1 : 0); if (rc != SSH_OK){ ssh_set_error_oom(msg->session); return SSH_ERROR; } } rc = ssh_packet_send(msg->session); /* fill in the kbdint structure */ if (msg->session->kbdint == NULL) { SSH_LOG(SSH_LOG_PROTOCOL, "Warning: Got a " "keyboard-interactive response but it " "seems we didn't send the request."); msg->session->kbdint = ssh_kbdint_new(); if (msg->session->kbdint == NULL) { ssh_set_error_oom(msg->session); return SSH_ERROR; } } else { ssh_kbdint_clean(msg->session->kbdint); } msg->session->kbdint->name = strdup(name); if(msg->session->kbdint->name == NULL) { ssh_set_error_oom(msg->session); ssh_kbdint_free(msg->session->kbdint); msg->session->kbdint = NULL; return SSH_PACKET_USED; } msg->session->kbdint->instruction = strdup(instruction); if(msg->session->kbdint->instruction == NULL) { ssh_set_error_oom(msg->session); ssh_kbdint_free(msg->session->kbdint); msg->session->kbdint = NULL; return SSH_PACKET_USED; } msg->session->kbdint->nprompts = num_prompts; if(num_prompts > 0) { msg->session->kbdint->prompts = calloc(num_prompts, sizeof(char *)); if (msg->session->kbdint->prompts == NULL) { msg->session->kbdint->nprompts = 0; ssh_set_error_oom(msg->session); ssh_kbdint_free(msg->session->kbdint); msg->session->kbdint = NULL; return SSH_ERROR; } msg->session->kbdint->echo = calloc(num_prompts, sizeof(unsigned char)); if (msg->session->kbdint->echo == NULL) { ssh_set_error_oom(msg->session); ssh_kbdint_free(msg->session->kbdint); msg->session->kbdint = NULL; return SSH_ERROR; } for (i = 0; i < num_prompts; i++) { msg->session->kbdint->echo[i] = echo[i]; msg->session->kbdint->prompts[i] = strdup(prompts[i]); if (msg->session->kbdint->prompts[i] == NULL) { ssh_set_error_oom(msg->session); msg->session->kbdint->nprompts = i; ssh_kbdint_free(msg->session->kbdint); msg->session->kbdint = NULL; return SSH_PACKET_USED; } } } else { msg->session->kbdint->prompts = NULL; msg->session->kbdint->echo = NULL; } msg->session->auth.state = SSH_AUTH_STATE_INFO; return rc; }
/** * @brief Connect to the ssh server. * * @param[in] session The ssh session to connect. * * @returns SSH_OK on success, SSH_ERROR on error. * @returns SSH_AGAIN, if the session is in nonblocking mode, * and call must be done again. * * @see ssh_new() * @see ssh_disconnect() */ int ssh_connect(ssh_session session) { int ret; if (session == NULL) { return SSH_ERROR; } switch(session->pending_call_state){ case SSH_PENDING_CALL_NONE: break; case SSH_PENDING_CALL_CONNECT: goto pending; default: ssh_set_error(session,SSH_FATAL,"Bad call during pending SSH call in ssh_connect"); return SSH_ERROR; } session->alive = 0; session->client = 1; if (ssh_init() < 0) { return SSH_ERROR; } if (session->opts.fd == SSH_INVALID_SOCKET && session->opts.host == NULL && session->opts.ProxyCommand == NULL) { ssh_set_error(session, SSH_FATAL, "Hostname required"); return SSH_ERROR; } ret = ssh_options_apply(session); if (ret < 0) { ssh_set_error(session, SSH_FATAL, "Couldn't apply options"); return SSH_ERROR; } SSH_LOG(SSH_LOG_PROTOCOL, "libssh %s, using threading %s", ssh_copyright(), ssh_threads_get_type()); session->ssh_connection_callback = ssh_client_connection_callback; session->session_state=SSH_SESSION_STATE_CONNECTING; ssh_socket_set_callbacks(session->socket,&session->socket_callbacks); session->socket_callbacks.connected=socket_callback_connected; session->socket_callbacks.data=callback_receive_banner; session->socket_callbacks.exception=ssh_socket_exception_callback; session->socket_callbacks.userdata=session; if (session->opts.fd != SSH_INVALID_SOCKET) { session->session_state=SSH_SESSION_STATE_SOCKET_CONNECTED; ssh_socket_set_fd(session->socket, session->opts.fd); ret=SSH_OK; #ifndef _WIN32 } else if (session->opts.ProxyCommand != NULL){ ret = ssh_socket_connect_proxycommand(session->socket, session->opts.ProxyCommand); #endif } else { ret=ssh_socket_connect(session->socket, session->opts.host, session->opts.port > 0 ? session->opts.port : 22, session->opts.bindaddr); } if (ret == SSH_ERROR) { return SSH_ERROR; } set_status(session, 0.2f); session->alive = 1; SSH_LOG(SSH_LOG_PROTOCOL,"Socket connecting, now waiting for the callbacks to work"); pending: session->pending_call_state=SSH_PENDING_CALL_CONNECT; if(ssh_is_blocking(session)) { int timeout = (session->opts.timeout * 1000) + (session->opts.timeout_usec / 1000); if (timeout == 0) { timeout = 10 * 1000; } SSH_LOG(SSH_LOG_PACKET,"Actual timeout : %d", timeout); ret = ssh_handle_packets_termination(session, timeout, ssh_connect_termination, session); if (session->session_state != SSH_SESSION_STATE_ERROR && (ret == SSH_ERROR || !ssh_connect_termination(session))) { ssh_set_error(session, SSH_FATAL, "Timeout connecting to %s", session->opts.host); session->session_state = SSH_SESSION_STATE_ERROR; } } else { ret = ssh_handle_packets_termination(session, SSH_TIMEOUT_NONBLOCKING, ssh_connect_termination, session); if (ret == SSH_ERROR) { session->session_state = SSH_SESSION_STATE_ERROR; } } SSH_LOG(SSH_LOG_PACKET,"current state : %d",session->session_state); if(!ssh_is_blocking(session) && !ssh_connect_termination(session)){ return SSH_AGAIN; } session->pending_call_state=SSH_PENDING_CALL_NONE; if(session->session_state == SSH_SESSION_STATE_ERROR || session->session_state == SSH_SESSION_STATE_DISCONNECTED) return SSH_ERROR; return SSH_OK; }
/** @internal * @handles a data received event. It then calls the handlers for the different packet types * or and exception handler callback. * @param user pointer to current ssh_session * @param data pointer to the data received * @len length of data received. It might not be enough for a complete packet * @returns number of bytes read and processed. */ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) { ssh_session session= (ssh_session) user; unsigned int blocksize = (session->current_crypto ? session->current_crypto->in_cipher->blocksize : 8); unsigned int lenfield_blocksize = (session->current_crypto ? session->current_crypto->in_cipher->lenfield_blocksize : 8); size_t current_macsize = 0; uint8_t *ptr = NULL; int to_be_read; int rc; uint8_t *cleartext_packet = NULL; uint8_t *packet_second_block = NULL; uint8_t *mac = NULL; size_t packet_remaining; uint32_t packet_len, compsize, payloadsize; uint8_t padding; size_t processed = 0; /* number of byte processed from the callback */ if(session->current_crypto != NULL) { current_macsize = hmac_digest_len(session->current_crypto->in_hmac); } if (lenfield_blocksize == 0) { lenfield_blocksize = blocksize; } if (data == NULL) { goto error; } if (session->session_state == SSH_SESSION_STATE_ERROR) { goto error; } #ifdef DEBUG_PACKET SSH_LOG(SSH_LOG_PACKET, "rcv packet cb (len=%zu, state=%s)", receivedlen, session->packet_state == PACKET_STATE_INIT ? "INIT" : session->packet_state == PACKET_STATE_SIZEREAD ? "SIZE_READ" : session->packet_state == PACKET_STATE_PROCESSING ? "PROCESSING" : "unknown"); #endif switch(session->packet_state) { case PACKET_STATE_INIT: if (receivedlen < lenfield_blocksize) { /* * We didn't receive enough data to read at least one * block size, give up */ #ifdef DEBUG_PACKET SSH_LOG(SSH_LOG_PACKET, "Waiting for more data (%zu < %u)", receivedlen, lenfield_blocksize); #endif return 0; } memset(&session->in_packet, 0, sizeof(PACKET)); if (session->in_buffer) { rc = ssh_buffer_reinit(session->in_buffer); if (rc < 0) { goto error; } } else { session->in_buffer = ssh_buffer_new(); if (session->in_buffer == NULL) { goto error; } } ptr = ssh_buffer_allocate(session->in_buffer, lenfield_blocksize); if (ptr == NULL) { goto error; } processed += lenfield_blocksize; packet_len = ssh_packet_decrypt_len(session, ptr, (uint8_t *)data); if (packet_len > MAX_PACKET_LEN) { ssh_set_error(session, SSH_FATAL, "read_packet(): Packet len too high(%u %.4x)", packet_len, packet_len); goto error; } to_be_read = packet_len - lenfield_blocksize + sizeof(uint32_t); if (to_be_read < 0) { /* remote sshd sends invalid sizes? */ ssh_set_error(session, SSH_FATAL, "Given numbers of bytes left to be read < 0 (%d)!", to_be_read); goto error; } session->in_packet.len = packet_len; session->packet_state = PACKET_STATE_SIZEREAD; FALL_THROUGH; case PACKET_STATE_SIZEREAD: packet_len = session->in_packet.len; processed = lenfield_blocksize; to_be_read = packet_len + sizeof(uint32_t) + current_macsize; /* if to_be_read is zero, the whole packet was blocksize bytes. */ if (to_be_read != 0) { if (receivedlen < (unsigned int)to_be_read) { /* give up, not enough data in buffer */ SSH_LOG(SSH_LOG_PACKET, "packet: partial packet (read len) " "[len=%d, receivedlen=%d, to_be_read=%d]", packet_len, (int)receivedlen, to_be_read); return 0; } packet_second_block = (uint8_t*)data + lenfield_blocksize; processed = to_be_read - current_macsize; } /* remaining encrypted bytes from the packet, MAC not included */ packet_remaining = packet_len - (lenfield_blocksize - sizeof(uint32_t)); cleartext_packet = ssh_buffer_allocate(session->in_buffer, packet_remaining); if (session->current_crypto) { /* * Decrypt the rest of the packet (lenfield_blocksize bytes already * have been decrypted) */ if (packet_remaining > 0) { rc = ssh_packet_decrypt(session, cleartext_packet, (uint8_t *)data, lenfield_blocksize, processed - lenfield_blocksize); if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Decryption error"); goto error; } } mac = packet_second_block + packet_remaining; rc = ssh_packet_hmac_verify(session, session->in_buffer, mac, session->current_crypto->in_hmac); if (rc < 0) { ssh_set_error(session, SSH_FATAL, "HMAC error"); goto error; } processed += current_macsize; } else { memcpy(cleartext_packet, packet_second_block, packet_remaining); } /* skip the size field which has been processed before */ ssh_buffer_pass_bytes(session->in_buffer, sizeof(uint32_t)); rc = ssh_buffer_get_u8(session->in_buffer, &padding); if (rc == 0) { ssh_set_error(session, SSH_FATAL, "Packet too short to read padding"); goto error; } if (padding > ssh_buffer_get_len(session->in_buffer)) { ssh_set_error(session, SSH_FATAL, "Invalid padding: %d (%d left)", padding, ssh_buffer_get_len(session->in_buffer)); goto error; } ssh_buffer_pass_bytes_end(session->in_buffer, padding); compsize = ssh_buffer_get_len(session->in_buffer); #ifdef WITH_ZLIB if (session->current_crypto && session->current_crypto->do_compress_in && ssh_buffer_get_len(session->in_buffer) > 0) { rc = decompress_buffer(session, session->in_buffer,MAX_PACKET_LEN); if (rc < 0) { goto error; } } #endif /* WITH_ZLIB */ payloadsize = ssh_buffer_get_len(session->in_buffer); session->recv_seq++; if (session->raw_counter != NULL) { session->raw_counter->in_bytes += payloadsize; session->raw_counter->in_packets++; } /* * We don't want to rewrite a new packet while still executing the * packet callbacks */ session->packet_state = PACKET_STATE_PROCESSING; ssh_packet_parse_type(session); SSH_LOG(SSH_LOG_PACKET, "packet: read type %hhd [len=%d,padding=%hhd,comp=%d,payload=%d]", session->in_packet.type, packet_len, padding, compsize, payloadsize); /* Execute callbacks */ ssh_packet_process(session, session->in_packet.type); session->packet_state = PACKET_STATE_INIT; if (processed < receivedlen) { /* Handle a potential packet left in socket buffer */ SSH_LOG(SSH_LOG_PACKET, "Processing %" PRIdS " bytes left in socket buffer", receivedlen-processed); ptr = ((uint8_t*)data) + processed; rc = ssh_packet_socket_callback(ptr, receivedlen - processed,user); processed += rc; } return processed; case PACKET_STATE_PROCESSING: SSH_LOG(SSH_LOG_PACKET, "Nested packet processing. Delaying."); return 0; } ssh_set_error(session, SSH_FATAL, "Invalid state into packet_read2(): %d", session->packet_state); error: session->session_state= SSH_SESSION_STATE_ERROR; SSH_LOG(SSH_LOG_PACKET,"Packet: processed %" PRIdS " bytes", processed); return processed; }
/** * @internal * * @brief A function to be called each time a step has been done in the * connection. */ static void ssh_client_connection_callback(ssh_session session){ int ssh1,ssh2; switch(session->session_state){ case SSH_SESSION_STATE_NONE: case SSH_SESSION_STATE_CONNECTING: case SSH_SESSION_STATE_SOCKET_CONNECTED: break; case SSH_SESSION_STATE_BANNER_RECEIVED: if (session->serverbanner == NULL) { goto error; } set_status(session, 0.4f); SSH_LOG(SSH_LOG_RARE, "SSH server banner: %s", session->serverbanner); /* Here we analyze the different protocols the server allows. */ if (ssh_analyze_banner(session, 0, &ssh1, &ssh2) < 0) { goto error; } /* Here we decide which version of the protocol to use. */ if (ssh2 && session->opts.ssh2) { session->version = 2; #ifdef WITH_SSH1 } else if(ssh1 && session->opts.ssh1) { session->version = 1; #endif } else if(ssh1 && !session->opts.ssh1){ #ifdef WITH_SSH1 ssh_set_error(session, SSH_FATAL, "SSH-1 protocol not available (configure session to allow SSH-1)"); goto error; #else ssh_set_error(session, SSH_FATAL, "SSH-1 protocol not available (libssh compiled without SSH-1 support)"); goto error; #endif } else { ssh_set_error(session, SSH_FATAL, "No version of SSH protocol usable (banner: %s)", session->serverbanner); goto error; } /* from now, the packet layer is handling incoming packets */ if(session->version==2) session->socket_callbacks.data=ssh_packet_socket_callback; #ifdef WITH_SSH1 else session->socket_callbacks.data=ssh_packet_socket_callback1; #endif ssh_packet_set_default_callbacks(session); session->session_state=SSH_SESSION_STATE_INITIAL_KEX; ssh_send_banner(session, 0); set_status(session, 0.5f); break; case SSH_SESSION_STATE_INITIAL_KEX: /* TODO: This state should disappear in favor of get_key handle */ #ifdef WITH_SSH1 if(session->version==1){ if (ssh_get_kex1(session) < 0) goto error; set_status(session,0.6f); session->connected = 1; break; } #endif break; case SSH_SESSION_STATE_KEXINIT_RECEIVED: set_status(session,0.6f); ssh_list_kex(&session->next_crypto->server_kex); if (set_client_kex(session) < 0) { goto error; } if (ssh_kex_select_methods(session) == SSH_ERROR) goto error; if (ssh_send_kex(session, 0) < 0) { goto error; } set_status(session,0.8f); session->session_state=SSH_SESSION_STATE_DH; if (dh_handshake(session) == SSH_ERROR) { goto error; } /* FALL THROUGH */ case SSH_SESSION_STATE_DH: if(session->dh_handshake_state==DH_STATE_FINISHED){ set_status(session,1.0f); session->connected = 1; if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) session->session_state = SSH_SESSION_STATE_AUTHENTICATED; else session->session_state=SSH_SESSION_STATE_AUTHENTICATING; } break; case SSH_SESSION_STATE_AUTHENTICATING: break; case SSH_SESSION_STATE_ERROR: goto error; default: ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state); } return; error: ssh_socket_close(session->socket); session->alive = 0; session->session_state=SSH_SESSION_STATE_ERROR; }
/** * @internal * @brief selects the hostkey mechanisms to be chosen for the key exchange, * as some hostkey mechanisms may be present in known_hosts file and preferred * @returns a cstring containing a comma-separated list of hostkey methods. * NULL if no method matches */ char *ssh_client_select_hostkeys(ssh_session session) { char methods_buffer[128]={0}; char tail_buffer[128]={0}; char *new_hostkeys = NULL; static const char *preferred_hostkeys[] = { "ssh-ed25519", "ecdsa-sha2-nistp521", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp256", "rsa-sha2-512", "rsa-sha2-256", "ssh-rsa", #ifdef HAVE_DSA "ssh-dss", #endif NULL }; struct ssh_list *algo_list = NULL; struct ssh_iterator *it = NULL; size_t algo_count; int needcomma = 0; size_t i, len; algo_list = ssh_known_hosts_get_algorithms(session); if (algo_list == NULL) { return NULL; } algo_count = ssh_list_count(algo_list); if (algo_count == 0) { ssh_list_free(algo_list); return NULL; } for (i = 0; preferred_hostkeys[i] != NULL; ++i) { bool found = false; /* This is a signature type: We list also the SHA2 extensions */ enum ssh_keytypes_e base_preferred = ssh_key_type_from_signature_name(preferred_hostkeys[i]); for (it = ssh_list_get_iterator(algo_list); it != NULL; it = it->next) { const char *algo = ssh_iterator_value(const char *, it); /* This is always key type so we do not have to care for the * SHA2 extension */ enum ssh_keytypes_e base_algo = ssh_key_type_from_name(algo); if (base_preferred == base_algo) { /* Matching the keys already verified it is a known type */ if (needcomma) { strncat(methods_buffer, ",", sizeof(methods_buffer) - strlen(methods_buffer) - 1); } strncat(methods_buffer, preferred_hostkeys[i], sizeof(methods_buffer) - strlen(methods_buffer) - 1); needcomma = 1; found = true; } } /* Collect the rest of the algorithms in other buffer, that will * follow the preferred buffer. This will signalize all the algorithms * we are willing to accept. */ if (!found) { snprintf(tail_buffer + strlen(tail_buffer), sizeof(tail_buffer) - strlen(tail_buffer), ",%s", preferred_hostkeys[i]); } } ssh_list_free(algo_list); if (strlen(methods_buffer) == 0) { SSH_LOG(SSH_LOG_DEBUG, "No supported kex method for existing key in known_hosts file"); return NULL; } /* Append the supported list to the preferred. * The length is maximum 128 + 128 + 1, which will not overflow */ len = strlen(methods_buffer) + strlen(tail_buffer) + 1; new_hostkeys = malloc(len); if (new_hostkeys == NULL) { ssh_set_error_oom(session); return NULL; } snprintf(new_hostkeys, len, "%s%s", methods_buffer, tail_buffer); SSH_LOG(SSH_LOG_DEBUG, "Changing host key method to \"%s\"", new_hostkeys); return new_hostkeys; }