/** * @brief Create a new ssh session. * * @returns A new ssh_session pointer, NULL on error. */ ssh_session ssh_new(void) { ssh_session session; char *id = NULL; int rc; session = malloc(sizeof (struct ssh_session_struct)); if (session == NULL) { return NULL; } ZERO_STRUCTP(session); session->next_crypto = crypto_new(); if (session->next_crypto == NULL) { goto err; } session->socket = ssh_socket_new(session); if (session->socket == NULL) { goto err; } session->out_buffer = ssh_buffer_new(); if (session->out_buffer == NULL) { goto err; } session->in_buffer=ssh_buffer_new(); if (session->in_buffer == NULL) { goto err; } session->alive = 0; session->auth_methods = 0; ssh_set_blocking(session, 1); session->common.log_indent = 0; session->maxchannel = FIRST_CHANNEL; #ifndef _WIN32 session->agent = agent_new(session); if (session->agent == NULL) { goto err; } #endif /* _WIN32 */ /* OPTIONS */ session->opts.StrictHostKeyChecking = 1; session->opts.port = 22; session->opts.fd = -1; session->opts.ssh2 = 1; session->opts.compressionlevel=7; #ifdef WITH_SSH1 session->opts.ssh1 = 1; #else session->opts.ssh1 = 0; #endif session->opts.identity = ssh_list_new(); if (session->opts.identity == NULL) { goto err; } id = strdup("%d/id_rsa"); if (id == NULL) { goto err; } rc = ssh_list_append(session->opts.identity, id); if (rc == SSH_ERROR) { goto err; } id = strdup("%d/id_dsa"); if (id == NULL) { goto err; } rc = ssh_list_append(session->opts.identity, id); if (rc == SSH_ERROR) { goto err; } id = strdup("%d/identity"); if (id == NULL) { goto err; } rc = ssh_list_append(session->opts.identity, id); if (rc == SSH_ERROR) { goto err; } return session; err: free(id); ssh_free(session); return NULL; }
/** * @brief Create a new ssh session. * * @returns A new ssh_session pointer, NULL on error. */ ssh_session ssh_new(void) { ssh_session session; char *id = NULL; int rc; session = malloc(sizeof (struct ssh_session_struct)); if (session == NULL) { return NULL; } ZERO_STRUCTP(session); session->next_crypto = crypto_new(); if (session->next_crypto == NULL) { goto err; } session->socket = ssh_socket_new(session); if (session->socket == NULL) { goto err; } session->out_buffer = ssh_buffer_new(); if (session->out_buffer == NULL) { goto err; } session->in_buffer=ssh_buffer_new(); if (session->in_buffer == NULL) { goto err; } session->alive = 0; session->auth_methods = 0; ssh_set_blocking(session, 1); session->maxchannel = FIRST_CHANNEL; #ifndef _WIN32 session->agent = ssh_agent_new(session); if (session->agent == NULL) { goto err; } #endif /* _WIN32 */ /* OPTIONS */ session->opts.StrictHostKeyChecking = 1; session->opts.port = 0; session->opts.fd = -1; session->opts.compressionlevel=7; session->opts.nodelay = 0; session->opts.flags = SSH_OPT_FLAG_PASSWORD_AUTH | SSH_OPT_FLAG_PUBKEY_AUTH | SSH_OPT_FLAG_KBDINT_AUTH | SSH_OPT_FLAG_GSSAPI_AUTH; session->opts.identity = ssh_list_new(); if (session->opts.identity == NULL) { goto err; } id = strdup("%d/id_ed25519"); if (id == NULL) { goto err; } rc = ssh_list_append(session->opts.identity, id); if (rc == SSH_ERROR) { goto err; } #ifdef HAVE_ECC id = strdup("%d/id_ecdsa"); if (id == NULL) { goto err; } rc = ssh_list_append(session->opts.identity, id); if (rc == SSH_ERROR) { goto err; } #endif id = strdup("%d/id_rsa"); if (id == NULL) { goto err; } rc = ssh_list_append(session->opts.identity, id); if (rc == SSH_ERROR) { goto err; } #ifdef HAVE_DSA id = strdup("%d/id_dsa"); if (id == NULL) { goto err; } rc = ssh_list_append(session->opts.identity, id); if (rc == SSH_ERROR) { goto err; } #endif return session; err: free(id); ssh_free(session); return NULL; }
/** * @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; }
/** * @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_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){ if (ssh_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); if (session->current_crypto->in_cipher->set_decrypt_key(session->current_crypto->in_cipher, session->current_crypto->decryptkey, session->current_crypto->decryptIV) < 0) { goto error; } if (session->current_crypto->out_cipher->set_encrypt_key(session->current_crypto->out_cipher, session->current_crypto->encryptkey, session->current_crypto->encryptIV) < 0) { goto error; } 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; }
static int dh_handshake_server(SSH_SESSION *session) { STRING *e; STRING *f; STRING *pubkey; STRING *sign; PUBLIC_KEY *pub; PRIVATE_KEY *prv; if (packet_wait(session, SSH2_MSG_KEXDH_INIT, 1) != SSH_OK) { return -1; } e = buffer_get_ssh_string(session->in_buffer); if (e == NULL) { ssh_set_error(session, SSH_FATAL, "No e number in client request"); return -1; } if (dh_import_e(session, e) < 0) { ssh_set_error(session, SSH_FATAL, "Cannot import e number"); string_free(e); return -1; } string_free(e); 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; } switch(session->hostkeys){ case TYPE_DSS: prv = session->dsa_key; break; case TYPE_RSA: prv = session->rsa_key; break; default: prv = NULL; } pub = publickey_from_privatekey(prv); if (pub == NULL) { ssh_set_error(session, SSH_FATAL, "Could not get the public key from the private key"); string_free(f); return -1; } pubkey = publickey_to_string(pub); publickey_free(pub); if (pubkey == NULL) { ssh_set_error(session, SSH_FATAL, "Not enough space"); string_free(f); return -1; } dh_import_pubkey(session, pubkey); if (dh_build_k(session) < 0) { ssh_set_error(session, SSH_FATAL, "Could not import the public key"); string_free(f); return -1; } if (make_sessionid(session) != SSH_OK) { ssh_set_error(session, SSH_FATAL, "Could not create a session id"); string_free(f); return -1; } sign = ssh_sign_session_id(session, prv); if (sign == NULL) { ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); string_free(f); return -1; } /* Free private keys as they should not be readable after this point */ if (session->rsa_key) { privatekey_free(session->rsa_key); session->rsa_key = NULL; } if (session->dsa_key) { privatekey_free(session->dsa_key); session->dsa_key = NULL; } if (buffer_add_u8(session->out_buffer, SSH2_MSG_KEXDH_REPLY) < 0 || buffer_add_ssh_string(session->out_buffer, pubkey) < 0 || buffer_add_ssh_string(session->out_buffer, f) < 0 || buffer_add_ssh_string(session->out_buffer, sign) < 0) { ssh_set_error(session, SSH_FATAL, "Not enough space"); buffer_free(session->out_buffer); string_free(f); string_free(sign); return -1; } string_free(f); string_free(sign); if (packet_send(session) != SSH_OK) { return -1; } if (buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { buffer_free(session->out_buffer); return -1; } if (packet_send(session) != SSH_OK) { return -1; } ssh_log(session, SSH_LOG_PACKET, "SSH_MSG_NEWKEYS sent"); if (packet_wait(session, SSH2_MSG_NEWKEYS, 1) != SSH_OK) { return -1; } ssh_log(session, SSH_LOG_PACKET, "Got SSH_MSG_NEWKEYS"); if (generate_session_keys(session) < 0) { return -1; } /* * 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) { return -1; } return 0; }
static int dh_handshake(SSH_SESSION *session){ STRING *e,*f,*pubkey,*signature; int ret; switch(session->dh_handshake_state){ case DH_STATE_INIT: packet_clear_out(session); buffer_add_u8(session->out_buffer,SSH2_MSG_KEXDH_INIT); dh_generate_x(session); dh_generate_e(session); e=dh_get_e(session); buffer_add_ssh_string(session->out_buffer,e); ret=packet_send(session); free(e); session->dh_handshake_state=DH_STATE_INIT_TO_SEND; if(ret==SSH_ERROR) return ret; case DH_STATE_INIT_TO_SEND: ret=packet_flush(session,0); if(ret!=SSH_OK) return ret; // SSH_ERROR or SSH_AGAIN session->dh_handshake_state=DH_STATE_INIT_SENT; case DH_STATE_INIT_SENT: ret=packet_wait(session,SSH2_MSG_KEXDH_REPLY,1); if(ret != SSH_OK) return ret; pubkey=buffer_get_ssh_string(session->in_buffer); if(!pubkey){ ssh_set_error(session,SSH_FATAL,"No public key in packet"); return SSH_ERROR; } dh_import_pubkey(session,pubkey); f=buffer_get_ssh_string(session->in_buffer); if(!f){ ssh_set_error(session,SSH_FATAL,"No F number in packet"); return SSH_ERROR; } dh_import_f(session,f); free(f); if(!(signature=buffer_get_ssh_string(session->in_buffer))){ ssh_set_error(session,SSH_FATAL,"No signature in packet"); return SSH_ERROR; } session->dh_server_signature=signature; dh_build_k(session); // send the MSG_NEWKEYS packet_clear_out(session); buffer_add_u8(session->out_buffer,SSH2_MSG_NEWKEYS); packet_send(session); session->dh_handshake_state=DH_STATE_NEWKEYS_TO_SEND; case DH_STATE_NEWKEYS_TO_SEND: ret=packet_flush(session,0); if(ret != SSH_OK) return ret; ssh_say(2,"SSH_MSG_NEWKEYS sent\n"); session->dh_handshake_state=DH_STATE_NEWKEYS_SENT; case DH_STATE_NEWKEYS_SENT: ret=packet_wait(session,SSH2_MSG_NEWKEYS,1); if(ret != SSH_OK) return ret; ssh_say(2,"Got SSH_MSG_NEWKEYS\n"); make_sessionid(session); /* set the cryptographic functions for the next crypto */ /* (it is needed for generate_session_keys for key lenghts) */ if(crypt_set_algorithms(session)) return SSH_ERROR; generate_session_keys(session); /* verify the host's signature. XXX do it sooner */ signature=session->dh_server_signature; session->dh_server_signature=NULL; if(signature_verify(session,signature)){ free(signature); return SSH_ERROR; } free(signature); /* forget it for now ... */ /* once we got SSH2_MSG_NEWKEYS we can switch next_crypto and current_crypto */ if(session->current_crypto) crypto_free(session->current_crypto); /* XXX later, include a function to change keys */ session->current_crypto=session->next_crypto; session->next_crypto=crypto_new(); session->dh_handshake_state=DH_STATE_FINISHED; return SSH_OK; default: ssh_set_error(session,SSH_FATAL,"Invalid state in dh_handshake():%d",session->dh_handshake_state); return SSH_ERROR; } /* not reached */ return SSH_ERROR; }
/** * @brief Disconnect from a session (client or server). * The session can then be reused to open a new session. * * @param[in] session The SSH session to use. */ void ssh_disconnect(ssh_session session) { struct ssh_iterator *it; int rc; if (session == NULL) { return; } if (session->socket != NULL && ssh_socket_is_open(session->socket)) { rc = ssh_buffer_pack(session->out_buffer, "bdss", SSH2_MSG_DISCONNECT, SSH2_DISCONNECT_BY_APPLICATION, "Bye Bye", ""); /* language tag */ if (rc != SSH_OK){ ssh_set_error_oom(session); goto error; } ssh_packet_send(session); ssh_socket_close(session->socket); } error: session->recv_seq = 0; session->send_seq = 0; session->alive = 0; if (session->socket != NULL){ ssh_socket_reset(session->socket); } session->opts.fd = SSH_INVALID_SOCKET; session->session_state=SSH_SESSION_STATE_DISCONNECTED; while ((it=ssh_list_get_iterator(session->channels)) != NULL) { ssh_channel_do_free(ssh_iterator_value(ssh_channel,it)); ssh_list_remove(session->channels, it); } if(session->current_crypto){ crypto_free(session->current_crypto); session->current_crypto=NULL; } if (session->next_crypto) { crypto_free(session->next_crypto); session->next_crypto = crypto_new(); if (session->next_crypto == NULL) { ssh_set_error_oom(session); } } if (session->in_buffer) { ssh_buffer_reinit(session->in_buffer); } if (session->out_buffer) { ssh_buffer_reinit(session->out_buffer); } if (session->in_hashbuf) { ssh_buffer_reinit(session->in_hashbuf); } if (session->out_hashbuf) { ssh_buffer_reinit(session->out_hashbuf); } session->auth.supported_methods = 0; SAFE_FREE(session->serverbanner); SAFE_FREE(session->clientbanner); if(session->ssh_message_list){ ssh_message msg; while((msg=ssh_list_pop_head(ssh_message ,session->ssh_message_list)) != NULL){ ssh_message_free(msg); } ssh_list_free(session->ssh_message_list); session->ssh_message_list=NULL; } if (session->packet_callbacks){ ssh_list_free(session->packet_callbacks); session->packet_callbacks=NULL; } }
static int dh_handshake_server(SSH_SESSION *session){ STRING *e,*f,*pubkey,*sign; PUBLIC_KEY *pub; PRIVATE_KEY *prv; BUFFER *buf=buffer_new(); if(packet_wait(session, SSH2_MSG_KEXDH_INIT)) // FIXME BLOCKING return -1; e=buffer_get_ssh_string(session->in_buffer); if(!e){ ssh_set_error(session,SSH_FATAL,"No e number in client request"); return -1; } dh_import_e(session,e); dh_generate_y(session); dh_generate_f(session); f=dh_get_f(session); switch(session->hostkeys){ case TYPE_DSS: prv=session->dsa_key; break; case TYPE_RSA: prv=session->rsa_key; break; default: prv=NULL; } pub=publickey_from_privatekey(prv); pubkey=publickey_to_string(pub); publickey_free(pub); dh_import_pubkey(session,pubkey); dh_build_k(session); make_sessionid(session); sign=ssh_sign_session_id(session,prv); buffer_free(buf); /* free private keys as they should not be readable past this point */ if(session->rsa_key){ private_key_free(session->rsa_key); session->rsa_key=NULL; } if(session->dsa_key){ private_key_free(session->dsa_key); session->dsa_key=NULL; } buffer_add_u8(session->out_buffer,SSH2_MSG_KEXDH_REPLY); buffer_add_ssh_string(session->out_buffer,pubkey); buffer_add_ssh_string(session->out_buffer,f); buffer_add_ssh_string(session->out_buffer,sign); free(sign); packet_send(session); free(f); packet_clear_out(session); buffer_add_u8(session->out_buffer,SSH2_MSG_NEWKEYS); packet_send(session); ssh_say(2,"SSH_MSG_NEWKEYS sent\n"); packet_wait(session,SSH2_MSG_NEWKEYS);// FIXME BLOCKING ssh_say(2,"Got SSH_MSG_NEWKEYS\n"); generate_session_keys(session); /* once we got SSH2_MSG_NEWKEYS we can switch next_crypto and current_crypto */ if(session->current_crypto) crypto_free(session->current_crypto); /* XXX later, include a function to change keys */ session->current_crypto=session->next_crypto; session->next_crypto=crypto_new(); return 0; }