334 строки
17 KiB
Plaintext
334 строки
17 KiB
Plaintext
/**
|
|
* @page tutorial The Tutorial
|
|
*
|
|
* @section introduction Introduction
|
|
*
|
|
* Before inserting SSH hooks into your programs, you must know some basics
|
|
* about the SSH protocol, and understand why the SSH library must implement
|
|
* them. A lot of the protocol specifications are hidden by the SSH library API
|
|
* (of course, that's the benefits of using a library) but some still needs an
|
|
* attention from the programmer.
|
|
*
|
|
* The SSH protocol was designed for some goals which I state here :
|
|
*
|
|
* - Privacy of data
|
|
* - Data integrity (alteration of data is always detected)
|
|
* - Authentication of the server
|
|
* - Authentication of the client
|
|
*
|
|
* The following document explains how to set up a client-side connection. libssh
|
|
* supports both client and server side of the SSH protocol. If you are going to
|
|
* program a server, I suggest you try first to code some client-side programs
|
|
* in order to understand how libssh works.
|
|
*
|
|
* The client MUST be sure who's speaking to before entering into any
|
|
* authentication way. That's where the end programmer must ensure the given
|
|
* fingerprints *are* from the legitimate server. A SSH connection must follow
|
|
* the following steps:
|
|
*
|
|
* - Before connecting the socket, you can set up if you wish one or other
|
|
* server public key authentication i.e. DSA or RSA. You can choose
|
|
* cryptographic algorithms you trust and compression algorithms if any. You
|
|
* must of course set up the hostname
|
|
*
|
|
* - The connection is made. A secure handshake is made, and resulting from
|
|
* it, a public key from the server is gained. You MUST verify that the public
|
|
* key is legitimate, using for instance the MD5 fingerprint of the known hosts
|
|
* file.
|
|
*
|
|
* - The client must authenticate : the classical ways are password and
|
|
* public keys (from dsa and rsa key-pairs generated by openssh). It is
|
|
* harmless to authenticate to a fake server with these keys because the
|
|
* protocol ensures the data you sign can't be used twice. It just avoids
|
|
* man-in-the-middle attacks. If a SSH-agent is running, it is possible
|
|
* to use it.
|
|
*
|
|
* - Now that the user has been authenticated, you must open one or several
|
|
* channels. channels are different subways for information into a single ssh
|
|
* connection. Each channel has a standard stream (stdout) and an error stream
|
|
* (stderr). You can theoretically open an infinity of channel.
|
|
*
|
|
* - With the channel you opened, you can do several things :
|
|
* - Open a shell. You may want to request a pseudo virtual terminal before,
|
|
* - Execute a single command,
|
|
* - Invoke the sftp subsystem.
|
|
* - invoke your own subsystem. This is out the scope of this document but it is easy to do.
|
|
*
|
|
* - When everything is finished, just close the channels, and then the connection.
|
|
*
|
|
* At every place, a function which returns an error code (typically SSH_ERROR for int
|
|
* values, NULL for pointers) also sets an error message and an error code.
|
|
*
|
|
*
|
|
* @section setup Creating the session and setting options
|
|
*
|
|
* The most important object in a SSH connection is the SSH session. In order to allocate a new
|
|
* SSH session, you must use ssh_new(). Don't forget to always verify that the allocation
|
|
* successed.
|
|
* @code
|
|
* #include <libssh/libssh.h>
|
|
* int main(){
|
|
* ssh_session my_ssh_session = ssh_new();
|
|
* if(my_ssh_session == NULL)
|
|
* exit(-1);
|
|
* ...
|
|
* ssh_free(my_ssh_session);
|
|
* @endcode
|
|
*
|
|
* libssh follows the allocate-it-deallocate-it pattern. Each object that you allocate
|
|
* using xxxxx_new() must be deallocated using xxxxx_free(). In this case, ssh_free() does this.
|
|
*
|
|
* The ssh_options_set() is a setter for the options of the session. The most important options are the
|
|
* following :
|
|
* - SSH_OPTIONS_HOST
|
|
* - SSH_OPTIONS_PORT
|
|
* - SSH_OPTIONS_USER
|
|
* - SSH_OPTIONS_LOG_VERBOSITY
|
|
* - ...
|
|
*
|
|
* The complete list can be found in the documentation of ssh_options_set(). The only mandatory option is
|
|
* SSH_OPTIONS_HOST. If you don't use SSH_OPTIONS_USER, the local username of your account will be used.
|
|
* Here is a small example of how to use it:
|
|
* @code
|
|
* #include <libssh/libssh.h>
|
|
* int main(){
|
|
* int verbosity = SSH_LOG_PROTOCOL;
|
|
* int port = 22;
|
|
* ssh_session my_ssh_session = ssh_new();
|
|
* if(my_ssh_session == NULL)
|
|
* exit(-1);
|
|
* ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost");
|
|
* ssh_options_set(my_ssh_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
|
|
* ssh_options_set(my_ssh_session, SSH_OPTIONS_LOG_PORT, &port);
|
|
* ...
|
|
* ssh_free(my_ssh_session);
|
|
* @endcode
|
|
* It's no more complicated than that. A few pointers on additionnal functions related to the session
|
|
* and options setting :
|
|
* @see ssh_new
|
|
* @see ssh_free
|
|
* @see ssh_options_set
|
|
* @see ssh_options_parse_config
|
|
* @see ssh_options_copy
|
|
* @see ssh_options_getopt
|
|
* @section connect Connecting to the server
|
|
*
|
|
* Once all settings have been made, you can connect using ssh_connect(). That function will return
|
|
* SSH_OK if the connection worked, SSH_ERROR otherwise.
|
|
* You can get the error string using ssh_get_error() in order to show the user what went wrong. Then, use
|
|
* ssh_disconnect() when you want to stop the session. Here's an example:
|
|
*
|
|
* @code
|
|
* #include <libssh/libssh.h>
|
|
* #include <stdio.h>
|
|
* int main(){
|
|
* int ret;
|
|
* ssh_session my_ssh_session = ssh_new();
|
|
* if(my_ssh_session == NULL)
|
|
* exit(-1);
|
|
* ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost");
|
|
* ret = ssh_connect(my_ssh_session);
|
|
* if(ret != SSH_OK)
|
|
* fprintf(stderr, "Error connecting to localhost : %s\n",
|
|
* ssh_get_error(my_ssh_session));
|
|
* else
|
|
* ssh_disconnect(my_ssh_session);
|
|
* ssh_free(my_ssh_session);
|
|
* @endcode
|
|
*
|
|
* Once you're connected, the following step is mandatory : you must check that the server
|
|
* you just connected to is known and safe to use (remember, SSH is about security and
|
|
* authentication).
|
|
*
|
|
* There are two ways of doing this. The first way (recommended) is to use the ssh_is_server_known()
|
|
* function. This function will check into the known host file (~/.ssh/known_hosts on unix)
|
|
* for the server hostname's pattern and check that this host is present or not in the list.
|
|
*
|
|
* The second way is of using ssh_get_pubkey_hash() to get a binary version of the public key hash.
|
|
* You can then use your own database to check if this public key is known and secure. You can also
|
|
* use that function to show the public key hash to the user, in case he knows what the public
|
|
* key hash is (some paranoid people writes their public key hashes on paper before going abroad,
|
|
* just in case ...).
|
|
*
|
|
* Once you concluded that the host is valid and worth being added in the known hosts file, just use
|
|
* ssh_write_knownhost(). The following example is part of the examples suite available in the examples/
|
|
* directory.
|
|
*
|
|
* @include knownhosts.c
|
|
*
|
|
* @see ssh_connect
|
|
* @see ssh_disconnect
|
|
* @see ssh_get_error
|
|
* @see ssh_get_error_code
|
|
* @see ssh_get_pubkey_hash
|
|
* @see ssh_is_server_known
|
|
* @see ssh_write_knownhost
|
|
*
|
|
* @section auth Authentication
|
|
*
|
|
* The authentication process is the way a service provider can identify a user and verify its
|
|
* identity. The authorization process is about authorizing the authenticated user the access
|
|
* to ressources. In SSH, the two concepts are linked. After authentication, the server can
|
|
* allow the user access to several ressources such as port forwarding, shell, sftp subsystem,
|
|
* ...
|
|
* libssh supports several authentication ways:
|
|
* - "none" method. This method serves to get the available authentications methods, as well as
|
|
* giving a chance to the server to authenticate the user with just his login. Some very old
|
|
* hardware are using this feature to fallback the user on a "telnet over SSH" style of login.
|
|
* - password method. A password is sent to the server, which accepts it or not
|
|
* - keyboard-interactive method. The server sends several challenges to the user, who must answer
|
|
* correctly.
|
|
* This makes possible the authentication via a codebook for instance (give code at 23:R at page 3).
|
|
* - public key method. The host knows the public key of the user, and the user must prove he knows the
|
|
* associated private key. This can be done manually, or delegated by the SSH agent as we'll see
|
|
* later.
|
|
*
|
|
* And last but not least, all of this methods can be combined. You can for instance force the user to
|
|
* authenticate with at least two of the authentication methods. In that case, we speak of "Partial
|
|
* authentication". A partial authentication is a response from authentication functions stating that
|
|
* your credential was accepted, but yet another one is required to get in.
|
|
*
|
|
*
|
|
* @subsection pubkeys Public keys
|
|
*
|
|
* The public key authentication is the only method which does not compromise your key if the remote host
|
|
* has been compromised (the server can't do anything more than getting your public key). This is not the
|
|
* case of a password authentication (the server can get your plaintext password).
|
|
* libssh is fully compatible with the openssh public and private keys. You can either use the automatic
|
|
* public key authentication method provided by libssh, or roll your own using the public key functions.
|
|
*
|
|
* The process of authenticating by public key to a server is the following :
|
|
* you scan a list of files which contain public keys. each key is sent to the SSH server, until the server
|
|
* acknowledges a key (a key it knows can be used to authenticate the user).
|
|
* Then, you retrieve the private key for this key and send a message proving you know that private key.
|
|
* The function ssh_userauth_autopubkey() does this using the available keys in "~/.ssh/".
|
|
* The return values are the following:
|
|
* - SSH_AUTH_ERROR : some serious error happened during authentication
|
|
* - SSH_AUTH_DENIED : no key matched
|
|
* - SSH_AUTH_SUCCESS : you are now authenticated
|
|
* - SSH_AUTH_PARTIAL : some key matched but you still have to give an other mean of authentication
|
|
(like password).
|
|
*
|
|
* The ssh_userauth_autopubkey function also tries to authenticate using the SSH agent, if you have one
|
|
* running, or the "none" method.
|
|
*
|
|
* If you wish to authenticate by public key by your own, follow these steps :
|
|
* - Retrieve the public key in a ssh_string using publickey_from_file().
|
|
* - Offer the public key to the SSH server using ssh_userauth_offer_pubkey(). If the return value is
|
|
* SSH_AUTH_SUCCESS, the SSH server accepts to authenticate using the public key and you can
|
|
* go to the next step.
|
|
* - Retrieve the private key, using the privatekey_from_file() function. If a passphrase is needed, either
|
|
* The passphrase specified as argument or a callback (see callbacks section) will be used.
|
|
* - Authenticate using ssh_userauth_pubkey() with your public key string and private key.
|
|
* - Do not forget cleaning up memory using string_free() and privatekey_free().
|
|
*
|
|
* @see ssh_userauth_autopubkey
|
|
* @see ssh_userauth_offer_pubkey
|
|
* @see ssh_userauth_pubkey
|
|
* @see publickey_from_file
|
|
* @see publickey_from_privatekey
|
|
* @see string_free
|
|
* @see privatekey_from_file
|
|
* @see privatekey_free
|
|
*
|
|
* @subsection password Password
|
|
*
|
|
* The function ssh_userauth_password() serves the purpose of authenticating using a password.
|
|
* It will return SSH_AUTH_SUCCESS if the password worked, one of other constants otherwise. It's
|
|
* your work to ask the password and to deallocate it in a secure manner. If your server complains
|
|
* that the password is wrong, but you can still authenticate using openssh's client (issuing password),
|
|
* it's probably because openssh only accept keyboard-interactive.
|
|
* Switch to keyboard-interactive authentication or try to configure plaintext-passwords into the SSH server.
|
|
*
|
|
* @see ssh_userauth_password
|
|
*
|
|
* @subsection keyb-int Keyboard-interactive
|
|
*
|
|
* The keyboard-interactive method is, as its name tells, interactive. The server will issue one or more
|
|
* challenges, which the used has to answer, until the server takes an authentication decision.
|
|
* ssh_userauth_kbdint() is the the main keyboard-interactive function.
|
|
* It will return SSH_AUTH_SUCCESS,SSH_AUTH_DENIED, SSH_AUTH_PARTIAL, SSH_AUTH_ERROR, or SSH_AUTH_INFO depending
|
|
* on the result of the request.
|
|
*
|
|
* The keyboard-interactive authentication method of SSH2 is a feature which permits the server
|
|
* to ask a certain number of questions in an interactive manner to the client, until it decides to
|
|
* accept or deny the login.
|
|
*
|
|
* To begin, you call this function (just put user and submethods to NULL) and store the answer. If the answer is
|
|
* SSH_AUTH_INFO, it means the server has sent a few questions to ask your user, which you can retrieve with
|
|
* the following functions : ssh_userauth_kbdint_getnprompts(), ssh_userauth_kbdint_getname(),
|
|
* ssh_userauth_kbdint_getinstruction() and ssh_userauth_kbdint_getprompt().
|
|
* Set the answer for each question in the challenge using ssh_userauth_kbdint_setanswer(). Then,
|
|
* call back ssh_userauth_kbdint() and start the process again until this functions returns something else
|
|
* than SSH_AUTH_INFO.
|
|
*
|
|
* Few remarks :
|
|
* - Even the first call can return SSH_AUTH_DENIED or SSH_AUTH_SUCCESS.
|
|
* - The server can send an empty question set (this is the default behavior on my system) after you have
|
|
* sent the answers to the first questions. you must still parse the answer, it might contain some
|
|
* message from the server saying hello or such things. Just call ssh_userauth_kbdint() until needed.
|
|
* - The meaning of "name", "prompt", "instruction" may be a little confusing. And explanation is given
|
|
* in the following documentation.
|
|
* A little note about how to use the informations from keyboard-interactive authentication, coming from
|
|
* The RFC itself (rfc4256) :
|
|
*
|
|
* @verbatim
|
|
|
|
3.3 User Interface Upon receiving a request message, the client SHOULD prompt the user as follows:
|
|
A command line interface (CLI) client SHOULD print the name and instruction (if non-empty), adding newlines.
|
|
Then for each prompt in turn, the client SHOULD display the prompt and read the user input.
|
|
|
|
A graphical user interface (GUI) client has many choices on how to prompt the user. One possibility is to use
|
|
the name field (possibly prefixed with the application's name) as the title of a dialog window in which
|
|
the prompt(s) are presented. In that dialog window, the instruction field would be a text message, and the
|
|
prompts would be labels for text entry fields. All fields SHOULD be presented to the user, for example an
|
|
implementation SHOULD NOT discard the name field because its windows lack titles; it SHOULD instead find another
|
|
way to display this information. If prompts are presented in a dialog window, then the client SHOULD NOT
|
|
present each prompt in a separate window.
|
|
|
|
All clients MUST properly handle an instruction field with embedded newlines. They SHOULD also be able to display
|
|
at least 30 characters for the name and prompts. If the server presents names or prompts longer than 30
|
|
characters, the client MAY truncate these fields to the length it can display. If the client does truncate any
|
|
fields, there MUST be an obvious indication that such truncation has occured.
|
|
|
|
The instruction field SHOULD NOT be truncated.
|
|
Clients SHOULD use control character filtering as discussed in [SSH-ARCH] to avoid attacks by including terminal
|
|
control characters in the fields to be displayed.
|
|
|
|
For each prompt, the corresponding echo field indicates whether or not the user input should be echoed as
|
|
characters are typed. Clients SHOULD correctly echo/mask user input for each prompt independently of other
|
|
prompts in the request message. If a client does not honor the echo field for whatever reason, then the client
|
|
MUST err on the side of masking input. A GUI client might like to have a checkbox toggling echo/mask. Clients
|
|
SHOULD NOT add any additional characters to the prompt such as ": " (colon-space); the server is responsible for
|
|
supplying all text to be displayed to the user. Clients MUST also accept empty responses from the user and pass
|
|
them on as empty strings.
|
|
@endverbatim
|
|
* @see ssh_userauth_kbdint()
|
|
* @see ssh_userauth_kbdint_getnprompts
|
|
* @see ssh_userauth_kbdint_getname
|
|
* @see ssh_userauth_kbdint_getinstruction
|
|
* @see ssh_userauth_kbdint_getprompt
|
|
* @see ssh_userauth_kbdint_setanswer()
|
|
*
|
|
* @subsection none "none"
|
|
*
|
|
* In fact this mode only serve to get the list of supported authentications, or to be authenticated
|
|
* **without** any credental. It happens on anonymous services for an example. You can try authentication
|
|
* with this mode by using ssh_userauth_none().
|
|
* It also serves to get the banner message from the server, if any. You should always firstly try this method,
|
|
* at least for getting the banner, then to enter if there is no password at all. You're advised to use
|
|
* ssh_userauth_autopubkey instead.
|
|
* if the account has no password (and the server is configured to let you pass), ssh_userauth_none might
|
|
* answer SSH_AUTH_SUCCESS.
|
|
|
|
* @subsection banner Issue banner
|
|
* ssh_get_banner() retrieves the issue banner that a server may send. That banner may be receive right after a
|
|
* call to ssh_userauth_none().
|
|
*
|
|
* @subsection auth_example example
|
|
* The following code is an example of authentication using libssh. It's the content of the examples/authentication.c
|
|
* file.
|
|
*
|
|
* @include authentication.c
|
|
*/
|