SSH 4.9.1.4 May 3, 2022
SSH 4.9.1.4 May 3, 2022
May 3, 2022
1.1 Introduction
1.1 Introduction
SSH is a protocol for secure remote logon and other secure network services over an insecure network.
1.1.2 Prerequisites
It is assumed that the reader is familiar with the Erlang programming language, concepts of OTP, and has a basic
understanding of public keys.
Transport Protocol
The SSH Transport Protocol is a secure, low-level transport. It provides strong encryption, cryptographic host
authentication, and integrity protection. A minimum of Message Authentication Code (MAC) and encryption
algorithms are supported. For details, see the ssh(3) manual page in ssh.
Authentication Protocol
The SSH Authentication Protocol is a general-purpose user authentication protocol run over the SSH Transport Layer
Protocol. The ssh application supports user authentication as follows:
• Using public key technology. RSA and DSA, X509-certificates are not supported.
• Using keyboard-interactive authentication. This is suitable for interactive authentication methods that do
not need any special software support on the client side. Instead, all authentication data is entered from the
keyboard.
• Using a pure password-based authentication scheme. Here, the plain text password is encrypted before sent over
the network.
Several configuration options for authentication handling are available in ssh:connect/[3,4] and ssh:daemon/[2,3].
The public key handling can be customized by implementing the following behaviours from ssh:
• Module ssh_client_key_api.
• Module ssh_server_key_api.
Connection Protocol
The SSH Connection Protocol provides application-support services over the transport pipe, for example, channel
multiplexing, flow control, remote program execution, signal propagation, and connection forwarding. Functions for
handling the SSH Connection Protocol can be found in the module ssh_connection in ssh.
Channels
All terminal sessions, forwarded connections, and so on, are channels. Multiple channels are multiplexed into a single
connection. All channels are flow-controlled. This means that no data is sent to a channel peer until a message is
received to indicate that window space is available. The initial window size specifies how many bytes of channel data
that can be sent to the channel peer without adjusting the window. Typically, an SSH client opens a channel, sends
data (commands), receives data (control information), and then closes the channel. The ssh_client_channel behaviour
handles generic parts of SSH channel management. This makes it easy to write your own SSH client/server processes
that use flow-control and thus opens for more focus on the application logic.
Channels come in the following three flavors:
• Subsystem - Named services that can be run as part of an SSH server, such as SFTP (ssh_sftpd), that is built
into the SSH daemon (server) by default, but it can be disabled. The Erlang ssh daemon can be configured to
run any Erlang- implemented SSH subsystem.
• Shell - Interactive shell. By default the Erlang daemon runs the Erlang shell. The shell can be customized
by providing your own read-eval-print loop. You can also provide your own Command-Line Interface (CLI)
implementation, but that is much more work.
• Exec - One-time remote execution of commands. See function ssh_connection:exec/4 for more information.
1> ssh:start().
ok
2> {ok, S} = ssh:shell("tarlop").
otptest@tarlop:> pwd
/home/otptest
otptest@tarlop:> exit
logout
3>
Note:
Normally, the /etc/ssh directory is only readable by root.
Step 2. Create the file /tmp/otptest_user/.ssh/authorized_keys and add the content of /tmp/
otptest_user/.ssh/id_rsa.pub.
Step 3. Start the Erlang ssh daemon:
1> ssh:start().
ok
2> {ok, Sshd} = ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"},
{user_dir, "/tmp/otptest_user/.ssh"}]).
{ok,<0.54.0>}
3>
Step 4. Use the openssh client from a shell to connect to the Erlang ssh daemon:
There are two ways of shutting down an ssh daemon, see Step 5a and Step 5b.
Step 5a. Shut down the Erlang ssh daemon so that it stops the listener but leaves existing connections, started by
the listener, operational:
3> ssh:stop_listener(Sshd).
ok
4>
Step 5b. Shut down the Erlang ssh daemon so that it stops the listener and all connections started by the listener:
3> ssh:stop_daemon(Sshd).
ok
4>
1> ssh:start().
ok
2> {ok, ConnectionRef} = ssh:connect("tarlop", 22, []).
{ok,<0.57.0>}
3> {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity).
{ok,0}
4> success = ssh_connection:exec(ConnectionRef, ChannelId, "pwd", infinity).
5> flush(). % Get all pending messages. NOTE: ordering may vary!
Shell got {ssh_cm,<0.57.0>,{data,0,0,<<"/home/otptest\n">>}}
Shell got {ssh_cm,<0.57.0>,{eof,0}}
Shell got {ssh_cm,<0.57.0>,{exit_status,0,0}}
Shell got {ssh_cm,<0.57.0>,{closed,0}}
ok
6> ssh:connection_info(ConnectionRef, channels).
{channels,[]}
7>
See ssh_connection and ssh_connection:exec/4 for finding documentation of the channel messages.
To collect the channel messages in a program, use receive...end instead of flush/1:
5> receive
5> {ssh_cm, ConnectionRef, {data, ChannelId, Type, Result}} when Type == 0 ->
5> {ok,Result}
5> {ssh_cm, ConnectionRef, {data, ChannelId, Type, Result}} when Type == 1 ->
5> {error,Result}
5> end.
{ok,<<"/home/otptest\n">>}
6>
Note that only the exec channel is closed after the one-time execution. The connection is still up and can handle
previously opened channels. It is also possible to open a new channel:
To close the connection, call the function ssh:close(ConnectionRef). As an alternative, set the option
{idle_time, 1} when opening the connection. This will cause the connection to be closed automaticaly when
there are no channels open for the specified time period, in this case 1 ms.
The same example but now using the Erlang ssh client to contact the Erlang server:
Note that Erlang shell specific functions and control sequences like for example h(). are not supported.
And similar for reading from stdin. As an example we use io:read/1 which displays the argument as a prompt on
stdout, reads a term from stdin and returns it in an ok-tuple:
%% Send a newline (it could have been included in the last send):
8> ssh_connection:send(ConnectionRef, ChannelId, <<"\n">>).
ok
9> flush().
Shell got {ssh_cm,<0.92.0>,{data,0,0,<<"{ok,[a,b,c]}">>}}
Shell got {ssh_cm,<0.92.0>,{exit_status,0,0}}
Shell got {ssh_cm,<0.92.0>,{eof,0}}
Shell got {ssh_cm,<0.92.0>,{closed,0}}
ok
10>
There is often a need to configure some other exec evaluator to tailor the input language or restrict the possible
functions to call. There are two ways of doing this which will be shown with examples below. See ssh:daemon/2,3
and exec_daemon_option()) for details.
Examples of the two ways to configure the exec evaluator:
• Disable one-time execution.
To modify the daemon start example above to reject one-time execution requests, we change Step 3 by adding
the option {exec, disabled} to:
1> ssh:start().
ok
2> {ok, Sshd} = ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"},
{user_dir, "/tmp/otptest_user/.ssh"},
{exec, disabled}
]).
{ok,<0.54.0>}
3>
A call to that daemon will return the text "Prohibited." on stderr (depending on the client and OS), and the exit
status 255:
And the Erlang client library also returns the text "Prohibited." on data type 1 instead of the normal 0 and exit
status 255:
1> ssh:start().
ok
2> MyEvaluator = fun("1") -> {ok, some_value};
("2") -> {ok, some_other_value};
("3") -> {ok, V} = io:read("input erlang term>> "),
{ok, V};
(Err) -> {error,{bad_input,Err}}
end.
3> {ok, Sshd} = ssh:daemon(1234, [{system_dir, "/tmp/ssh_daemon"},
{user_dir, "/tmp/otptest_user/.ssh"},
{exec, {direct,MyEvaluator}}
]).
{ok,<0.275.0>}
4>
Note that spaces are preserved and that no point (.) is needed at the end - that was required by the default
evaluator.
The error return in the Erlang client (The text as data type 1 and exit_status 255):
The fun() in the exec option could take up to three arguments (Cmd, User and ClientAddress). See the
exec_daemon_option() for the details.
Note:
An old, discouraged and undocumented way of installing an alternative evaluator exists.
It still works, but lacks for example I/O possibility. It is because of that compatibility we need the {direct,...}
construction.
1> ssh:start().
ok
2> ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"},
{user_dir, "/tmp/otptest_user/.ssh"},
{subsystems, [ssh_sftpd:subsystem_spec(
[{cwd, "/tmp/sftp/example"}])
]}]).
{ok,<0.54.0>}
3>
1> ssh:start().
ok
2> {ok, ChannelPid, Connection} = ssh_sftp:start_channel("tarlop", []).
{ok,<0.57.0>,<0.51.0>}
3> ssh_sftp:read_file(ChannelPid, "/home/otptest/test.txt").
{ok,<<"This is a test file\n">>}
%% How to encrypt:
EncryptFun =
fun(PlainBin,Ivec) ->
EncryptedBin = crypto:block_encrypt(aes_cbc256, Key, Ivec, PlainBin),
{ok, EncryptedBin, crypto:next_iv(aes_cbc,EncryptedBin)}
end,
Cw = {InitFun,EncryptFun,CloseFun},
{ok,HandleWrite} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [write,{crypto,Cw}]),
ok = erl_tar:add(HandleWrite, .... ),
ok = erl_tar:add(HandleWrite, .... ),
...
ok = erl_tar:add(HandleWrite, .... ),
ok = erl_tar:close(HandleWrite),
%% And for decryption (in this crypto example we could use the same InitFun
%% as for encryption):
DecryptFun =
fun(EncryptedBin,Ivec) ->
PlainBin = crypto:block_decrypt(aes_cbc256, Key, Ivec, EncryptedBin),
{ok, PlainBin, crypto:next_iv(aes_cbc,EncryptedBin)}
end,
Cr = {InitFun,DecryptFun},
{ok,HandleRead} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [read,{crypto,Cw}]),
{ok,NameValueList} = erl_tar:extract(HandleRead,[memory]),
ok = erl_tar:close(HandleRead),
-module(ssh_echo_server).
-behaviour(ssh_server_channel). % replaces ssh_daemon_channel
-record(state, {
n,
id,
cm
}).
-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]).
init([N]) ->
{ok, #state{n = N}}.
The subsystem can be run on the host tarlop with the generated keys, as described in Section Running an Erlang
ssh Daemon:
1> ssh:start().
ok
2> ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"},
{user_dir, "/tmp/otptest_user/.ssh"}
{subsystems, [{"echo_n", {ssh_echo_server, [10]}}]}]).
{ok,<0.54.0>}
3>
1> ssh:start().
ok
2> {ok, ConnectionRef} = ssh:connect("tarlop", 8989,
[{user_dir, "/tmp/otptest_user/.ssh"}]).
{ok,<0.57.0>}
3> {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity).
4> success = ssh_connection:subsystem(ConnectionRef, ChannelId, "echo_n", infinity).
5> ok = ssh_connection:send(ConnectionRef, ChannelId, "0123456789", infinity).
6> flush().
{ssh_msg, <0.57.0>, {data, 0, 1, "0123456789"}}
{ssh_msg, <0.57.0>, {eof, 0}}
{ssh_msg, <0.57.0>, {closed, 0}}
7> {error, closed} = ssh_connection:send(ConnectionRef, ChannelId, "10", infinity).
1.3 Terminology
1.3.1 General Information
In the following terms that may cause confusion are explained.
In OpenSSH
Many have been in contact with the command 'ssh' on a Linux machine (or similar) to remotly log in on another
machine. One types
ssh host
to log in on the machine named host. The command prompts for your password on the remote host and then you
can read, write and execute as your user name has rights on the remote host. There are stronger variants with pre-
distributed keys or certificates, but that are for now just details in the authentication process.
You could log in as the user anotheruser with
ssh anotheruser@host
and you will then be enabled to act as anotheruser on the host if authorized correctly.
So what does "your user name has rights" mean? In a UNIX/Linux/etc context it is exactly as that context: The user
could read, write and execute programs according to the OS rules. In addition, the user has a home directory ($HOME)
and there is a $HOME/.ssh/ directory with ssh-specific files.
SSH password authentication
When SSH tries to log in to a host, the ssh protocol communicates the user name (as a string) and a password. The
remote ssh server checks that there is such a user defined and that the provided password is acceptable.
If so, the user is authorized.
SSH public key authentication
This is a stronger method where the ssh protocol brings the user name, the user's public key and some cryptographic
information which we could ignore here.
The ssh server on the remote host checks:
• That the user has a home directory,
• that home directory contains a .ssh/ directory and
• the .ssh/ directory contains the public key just received in the authorized_keys file
if so, the user is authorized.
The SSH server on UNIX/Linux/etc after a succesful authentication
After a succesful incoming authentication, a new process runs as the just authenticated user.
Next step is to start a service according to the ssh request. In case of a request of a shell, a new one is started which
handles the OS-commands that arrives from the client (that's "you").
In case of a sftp request, an sftp server is started in with the user's rights. So it could read, write or delete files if
allowed for that user.
In Erlang/OTP SSH
For the Erlang/OTP SSH server the situation is different. The server executes in an Erlang process in the Erlang
emulator which in turn executes in an OS process. The emulator does not try to change its user when authenticated over
the SSH protocol. So the remote user name is only for authentication purposes in the Erlang/OTP SSH application.
Password authentication in Erlang SSH
The Erlang/OTP SSH server checks the user name and password in the following order:
• If a pwdfun is defined, that one is called and the returned boolean is the authentication result.
• Else, if the user_passwords option is defined and the username and the password matches, the
authentication is a success.
• Else, if the option password is defined and matches the password the authentication is a success. Note that the
use of this option is not recommended in non-test code.
Public key authentication in Erlang SSH
The user name, public key and cryptographic data (a signature) that is sent by the client, are used as follows (some
steps left out for clearity):
• A callback module is selected using the options key_cb.
• The callback module is used to check that the provided public key is one of the user's pre-stored. In case of the
default callback module, the files authorized_keys and authorized_keys2 are searched in a directory
found in the following order:
• If the option user_dir_fun is defined, that fun is called and the returned directory is used,
• Else, If the option user_dir is defined, that directory is used,
• Else the subdirectory .ssh in the home directory of the user executing the OS process of the Erlang
emulator is used.
If the provided public key is not found, the authentication fails.
• Finally, if the provided public key is found, the signature provided by the client is checked with the public key.
The Erlang/OTP SSH server after a succesful authentication
After a successful authentication an Erlang process is handling the service request from the remote ssh client. The
rights of that process are those of the user of the OS process running the Erlang emulator.
If a shell service request arrives to the server, an Erlang shell is opened in the server's emulator. The rights in that
shell is independent of the just authenticated user.
In case of an sftp request, an sftp server is started with the rights of the user of the Erlang emulator's OS process. So
with sftp the authenticated user does not influence the rights.
So after an authentication, the user name is not used anymore and has no influence.
The asymetric encryption algorithm used in the server's private-public host key pair. Examples include the well-
known RSA 'ssh-rsa' and elliptic curve 'ecdsa-sha2-nistp521'.
cipher
Symetric cipher algorithm used for the payload encryption. This algorithm will use the key calculated in the kex
phase (together with other info) to genereate the actual key used. Examples are tripple-DES '3des-cbc' and
one of many AES variants 'aes192-ctr'.
This list is actually two - one for each direction server-to-client and client-to-server. Therefore it is possible but
rare to have different algorithms in the two directions in one connection.
mac
Message authentication code
"Check sum" of each message sent between the peers. Examples are SHA 'hmac-sha1' and SHA2 'hmac-
sha2-512'.
This list is also divided into two for the both directions
compression
If and how to compress the message. Examples are none, that is, no compression and zlib.
This list is also divided into two for the both directions
0> ssh:default_algorithms().
[{kex,['ecdh-sha2-nistp384','ecdh-sha2-nistp521',
'ecdh-sha2-nistp256','diffie-hellman-group-exchange-sha256',
'diffie-hellman-group16-sha512',
'diffie-hellman-group18-sha512',
'diffie-hellman-group14-sha256',
'diffie-hellman-group14-sha1',
'diffie-hellman-group-exchange-sha1']},
{public_key,['ecdsa-sha2-nistp384','ecdsa-sha2-nistp521',
'ecdsa-sha2-nistp256','ssh-rsa','rsa-sha2-256',
'rsa-sha2-512','ssh-dss']},
{cipher,[{client2server,['[email protected]',
'aes256-ctr','aes192-ctr','[email protected]',
'aes128-ctr','aes128-cbc','3des-cbc']},
{server2client,['[email protected]','aes256-ctr',
'aes192-ctr','[email protected]','aes128-ctr',
'aes128-cbc','3des-cbc']}]},
{mac,[{client2server,['hmac-sha2-256','hmac-sha2-512',
'hmac-sha1']},
{server2client,['hmac-sha2-256','hmac-sha2-512',
'hmac-sha1']}]},
{compression,[{client2server,[none,'[email protected]',zlib]},
{server2client,[none,'[email protected]',zlib]}]}]
To change the algorithm list, there are two options which can be used in ssh:connect/2,3,4 and ssh:daemon/2,3. The
options could of course be used in all other functions that initiates connections.
The options are preferred_algorithms and modify_algorithms. The first one replaces the default set,
while the latter modifies the default set.
Example 1
Replace the kex algorithms list with the single algorithm 'diffie-hellman-group14-sha256':
1> ssh:chk_algos_opts(
[{preferred_algorithms,
[{kex, ['diffie-hellman-group14-sha256']}
]
}
]).
[{kex,['diffie-hellman-group14-sha256']},
{public_key,['ecdsa-sha2-nistp384','ecdsa-sha2-nistp521',
'ecdsa-sha2-nistp256','ssh-rsa','rsa-sha2-256',
'rsa-sha2-512','ssh-dss']},
{cipher,[{client2server,['[email protected]',
'aes256-ctr','aes192-ctr','[email protected]',
'aes128-ctr','aes128-cbc','3des-cbc']},
{server2client,['[email protected]','aes256-ctr',
'aes192-ctr','[email protected]','aes128-ctr',
'aes128-cbc','3des-cbc']}]},
{mac,[{client2server,['hmac-sha2-256','hmac-sha2-512',
'hmac-sha1']},
{server2client,['hmac-sha2-256','hmac-sha2-512',
'hmac-sha1']}]},
{compression,[{client2server,[none,'[email protected]',zlib]},
{server2client,[none,'[email protected]',zlib]}]}]
Note that the unmentioned lists (public_key, cipher, mac and compression) are un-changed.
Example 2
In the lists that are divided in two for the two directions (c.f cipher) it is possible to change both directions at once:
2> ssh:chk_algos_opts(
[{preferred_algorithms,
[{cipher,['aes128-ctr']}
]
}
]).
[{kex,['ecdh-sha2-nistp384','ecdh-sha2-nistp521',
'ecdh-sha2-nistp256','diffie-hellman-group-exchange-sha256',
'diffie-hellman-group16-sha512',
'diffie-hellman-group18-sha512',
'diffie-hellman-group14-sha256',
'diffie-hellman-group14-sha1',
'diffie-hellman-group-exchange-sha1']},
{public_key,['ecdsa-sha2-nistp384','ecdsa-sha2-nistp521',
'ecdsa-sha2-nistp256','ssh-rsa','rsa-sha2-256',
'rsa-sha2-512','ssh-dss']},
{cipher,[{client2server,['aes128-ctr']},
{server2client,['aes128-ctr']}]},
{mac,[{client2server,['hmac-sha2-256','hmac-sha2-512',
'hmac-sha1']},
{server2client,['hmac-sha2-256','hmac-sha2-512',
'hmac-sha1']}]},
{compression,[{client2server,[none,'[email protected]',zlib]},
{server2client,[none,'[email protected]',zlib]}]}]
Note that both lists in cipher has been changed to the provided value ('aes128-ctr').
Example 3
In the lists that are divided in two for the two directions (c.f cipher) it is possible to change only one of the directions:
3> ssh:chk_algos_opts(
[{preferred_algorithms,
[{cipher,[{client2server,['aes128-ctr']}]}
]
}
]).
[{kex,['ecdh-sha2-nistp384','ecdh-sha2-nistp521',
'ecdh-sha2-nistp256','diffie-hellman-group-exchange-sha256',
'diffie-hellman-group16-sha512',
'diffie-hellman-group18-sha512',
'diffie-hellman-group14-sha256',
'diffie-hellman-group14-sha1',
'diffie-hellman-group-exchange-sha1']},
{public_key,['ecdsa-sha2-nistp384','ecdsa-sha2-nistp521',
'ecdsa-sha2-nistp256','ssh-rsa','rsa-sha2-256',
'rsa-sha2-512','ssh-dss']},
{cipher,[{client2server,['aes128-ctr']},
{server2client,['[email protected]','aes256-ctr',
'aes192-ctr','[email protected]','aes128-ctr',
'aes128-cbc','3des-cbc']}]},
{mac,[{client2server,['hmac-sha2-256','hmac-sha2-512',
'hmac-sha1']},
{server2client,['hmac-sha2-256','hmac-sha2-512',
'hmac-sha1']}]},
{compression,[{client2server,[none,'[email protected]',zlib]},
{server2client,[none,'[email protected]',zlib]}]}]
Example 4
It is of course possible to change more than one list:
4> ssh:chk_algos_opts(
[{preferred_algorithms,
[{cipher,['aes128-ctr']},
{mac,['hmac-sha2-256']},
{kex,['ecdh-sha2-nistp384']},
{public_key,['ssh-rsa']},
{compression,[{server2client,[none]},
{client2server,[zlib]}]}
]
}
]).
[{kex,['ecdh-sha2-nistp384']},
{public_key,['ssh-rsa']},
{cipher,[{client2server,['aes128-ctr']},
{server2client,['aes128-ctr']}]},
{mac,[{client2server,['hmac-sha2-256']},
{server2client,['hmac-sha2-256']}]},
{compression,[{client2server,[zlib]},
{server2client,[none]}]}]
Note that the ordering of the tuples in the lists didn't matter.
Each of the ... can be a algs_list() as the argument to the preferred_algorithms option.
Example 5
As an example let's add the Diffie-Hellman Group1 first in the kex list. It is supported according to Supported algoritms.
5> ssh:chk_algos_opts(
[{modify_algorithms,
[{prepend,
[{kex,['diffie-hellman-group1-sha1']}]
}
]
}
]).
[{kex,['diffie-hellman-group1-sha1','ecdh-sha2-nistp384',
'ecdh-sha2-nistp521','ecdh-sha2-nistp256',
'diffie-hellman-group-exchange-sha256',
'diffie-hellman-group16-sha512',
'diffie-hellman-group18-sha512',
'diffie-hellman-group14-sha256',
'diffie-hellman-group14-sha1',
'diffie-hellman-group-exchange-sha1']},
{public_key,['ecdsa-sha2-nistp384','ecdsa-sha2-nistp521',
'ecdsa-sha2-nistp256','ssh-rsa','rsa-sha2-256',
'rsa-sha2-512','ssh-dss']},
{cipher,[{client2server,['[email protected]',
'aes256-ctr','aes192-ctr','[email protected]',
'aes128-ctr','aes128-cbc','3des-cbc']},
{server2client,['[email protected]','aes256-ctr',
'aes192-ctr','[email protected]','aes128-ctr',
'aes128-cbc','3des-cbc']}]},
{mac,[{client2server,['hmac-sha2-256','hmac-sha2-512',
'hmac-sha1']},
{server2client,['hmac-sha2-256','hmac-sha2-512',
'hmac-sha1']}]},
{compression,[{client2server,[none,'[email protected]',zlib]},
{server2client,[none,'[email protected]',zlib]}]}]
And the result shows that the Diffie-Hellman Group1 is added at the head of the kex list
Example 6
In this example, we in put the 'diffie-hellman-group1-sha1' first and also move the 'ecdh-sha2-nistp521' to
the end in the kex list, that is, append it.
6> ssh:chk_algos_opts(
[{modify_algorithms,
[{prepend,
[{kex, ['diffie-hellman-group1-sha1']}
]},
{append,
[{kex, ['ecdh-sha2-nistp521']}
]}
]
}
]).
[{kex,['diffie-hellman-group1-sha1','ecdh-sha2-nistp384',
'ecdh-sha2-nistp256','diffie-hellman-group-exchange-sha256',
'diffie-hellman-group16-sha512',
'diffie-hellman-group18-sha512',
'diffie-hellman-group14-sha256',
'diffie-hellman-group14-sha1',
'diffie-hellman-group-exchange-sha1','ecdh-sha2-nistp521']},
{public_key,['ecdsa-sha2-nistp384','ecdsa-sha2-nistp521',
.....
]
Note that the appended algorithm is removed from its original place and then appended to the same list.
Example 7
In this example, we use both options (preferred_algorithms and modify_algorithms) and also try to
prepend an unsupported algorithm. Any unsupported algorithm is quietly removed.
7> ssh:chk_algos_opts(
[{preferred_algorithms,
[{cipher,['aes128-ctr']},
{mac,['hmac-sha2-256']},
{kex,['ecdh-sha2-nistp384']},
{public_key,['ssh-rsa']},
{compression,[{server2client,[none]},
{client2server,[zlib]}]}
]
},
{modify_algorithms,
[{prepend,
[{kex, ['some unsupported algorithm']}
]},
{append,
[{kex, ['diffie-hellman-group1-sha1']}
]}
]
}
]).
[{kex,['ecdh-sha2-nistp384','diffie-hellman-group1-sha1']},
{public_key,['ssh-rsa']},
{cipher,[{client2server,['aes128-ctr']},
{server2client,['aes128-ctr']}]},
{mac,[{client2server,['hmac-sha2-256']},
{server2client,['hmac-sha2-256']}]},
{compression,[{client2server,[zlib]},
{server2client,[none]}]}]
It is of course questionable why anyone would like to use the both these options together, but it is possible if an
unforeseen need should arise.
2 Reference Manual
The ssh application is an Erlang implementation of the Secure Shell Protocol (SSH) as defined by RFC 4250 - 4254.
SSH
Application
The ssh application is an implementation of the SSH protocol in Erlang. ssh offers API functions to write customized
SSH clients and servers as well as making the Erlang shell available over SSH. An SFTP client, ssh_sftp, and
server, ssh_sftpd, are also included.
DEPENDENCIES
The ssh application uses the applications public_key and crypto to handle public keys and encryption. Hence, these
applications must be loaded for the ssh application to work. In an embedded environment this means that they must
be started with application:start/1,2 before the ssh application is started.
CONFIGURATION
The ssh application does not have an application- specific configuration file, as described in application(3). However,
by default it use the following configuration files from OpenSSH:
• known_hosts
• authorized_keys
• authorized_keys2
• id_dsa
• id_rsa
• id_ecdsa
• ssh_host_dsa_key
• ssh_host_rsa_key
• ssh_host_ecdsa_key
By default, ssh looks for id_dsa, id_rsa, id_ecdsa_key, known_hosts, and authorized_keys in
~/.ssh, and for the host key files in /etc/ssh. These locations can be changed by the options user_dir and
system_dir.
Public key handling can also be customized through a callback module that implements the behaviors
ssh_client_key_api and ssh_server_key_api.
See also the default callback module documentation in ssh_file.
Public Keys
id_dsa, id_rsa and id_ecdsa are the users private key files. Notice that the public key is part of the private key
so the ssh application does not use the id_<*>.pub files. These are for the user's convenience when it is needed
to convey the user's public key.
Known Hosts
The known_hosts file contains a list of approved servers and their public keys. Once a server is listed, it can be
verified without user interaction.
Authorized Keys
The authorized_key file keeps track of the user's authorized public keys. The most common use of this file is to
let users log in without entering their password, which is supported by the Erlang ssh daemon.
Host Keys
RSA, DSA and ECDSA host keys are supported and are expected to be found in files named ssh_host_rsa_key,
ssh_host_dsa_key and ssh_host_ecdsa_key.
Algorithms
The actual set of algorithms may vary depending on which OpenSSL crypto library that is installed on the machine.
For the list on a particular installation, use the command ssh:default_algorithms/0. The user may override the
default algorithm configuration both on the server side and the client side. See the options preferred_algorithms and
modify_algorithms in the ssh:daemon/1,2,3 and ssh:connect/3,4 functions.
Supported algorithms are (in the default order):
Key exchange algorithms
• ecdh-sha2-nistp384
• ecdh-sha2-nistp521
• ecdh-sha2-nistp256
• diffie-hellman-group-exchange-sha256
• diffie-hellman-group16-sha512
• diffie-hellman-group18-sha512
• diffie-hellman-group14-sha256
• curve25519-sha256
• [email protected]
• curve448-sha512
• diffie-hellman-group14-sha1
• diffie-hellman-group-exchange-sha1
• (diffie-hellman-group1-sha1, retired: It can be enabled with the preferred_algorithms or
modify_algorithms options. Use for example the Option value {modify_algorithms, [{append,
[{kex,['diffie-hellman-group1-sha1']}]}]})
Public key algorithms
• ecdsa-sha2-nistp384
• ecdsa-sha2-nistp521
• ecdsa-sha2-nistp256
• ssh-ed25519
• ssh-ed448
• ssh-rsa
• rsa-sha2-256
• rsa-sha2-512
• ssh-dss
MAC algorithms
• hmac-sha2-256
• hmac-sha2-512
• hmac-sha1
• (hmac-sha1-96 It can be enabled with the preferred_algorithms or modify_algorithms options. Use
for example the Option value {modify_algorithms, [{append, [{mac,['hmac-
sha1-96']}]}]})
Encryption algorithms (ciphers)
• [email protected]
• [email protected]
• aes256-ctr
• aes192-ctr
• [email protected]
• aes128-ctr
• aes256-cbc
• aes192-cbc
• aes128-cbc
• 3des-cbc
• (AEAD_AES_128_GCM, not enabled per default)
• (AEAD_AES_256_GCM, not enabled per default)
See the text at the description of the rfc 5647 further down for more information regarding AEAD_AES_*_GCM.
Following the internet de-facto standard, the cipher and mac algorithm AEAD_AES_128_GCM is selected when
the cipher [email protected] is negotiated. The cipher and mac algorithm AEAD_AES_256_GCM is
selected when the cipher [email protected] is negotiated.
Compression algorithms
• none
• [email protected]
• zlib
Unicode support
Unicode filenames are supported if the emulator and the underlaying OS support it. See section DESCRIPTION in
the file manual page in Kernel for information about this subject.
The shell and the cli both support unicode.
Rfcs
The following rfc:s are supported:
• RFC 4251, The Secure Shell (SSH) Protocol Architecture.
Except
• 9.4.6 Host-Based Authentication
• 9.5.2 Proxy Forwarding
• 9.5.3 X11 Forwarding
Warning:
If the client or the server is not Erlang/OTP, it is the users responsibility to check that other implementation
has the same interpretation of AEAD_AES_*_GCM as the Erlang/OTP SSH before enabling them. The aes*-
[email protected] variants are always safe to use since they lack the ambiguity.
• RFC 5656, Elliptic Curve Algorithm Integration in the Secure Shell Transport Layer.
Except
• 5. ECMQV Key Exchange
• 6.4. ECMQV Key Exchange and Verification Method Name
• 7.2. ECMQV Message Numbers
• 10.2. Recommended Curves
• RFC 6668, SHA-2 Data Integrity Verification for the Secure Shell (SSH) Transport Layer Protocol
Comment: Defines hmac-sha2-256 and hmac-sha2-512
• Draft-ietf-curdle-ssh-kex-sha2 (work in progress), Key Exchange (KEX) Method Updates and
Recommendations for Secure Shell (SSH).
Deviations:
• The diffie-hellman-group1-sha1 is not enabled by default, but is still supported and can be
enabled with the options preferred_algorithms or modify_algorithms.
• The questionable sha1-based algorithms diffie-hellman-group-exchange-sha1 and diffie-
hellman-group14-sha1 are still enabled by default for compatibility with ancient clients and servers.
They can be disabled with the options preferred_algorithms or modify_algorithms. They will be disabled
by default when the draft is turned into an RFC.
• RFC 8332, Use of RSA Keys with SHA-256 and SHA-512 in the Secure Shell (SSH) Protocol.
• RFC 8308, Extension Negotiation in the Secure Shell (SSH) Protocol.
Implemented are:
• The Extension Negotiation Mechanism
• The extension server-sig-algs
• Secure Shell (SSH) Key Exchange Method using Curve25519 and Curve448 (work in progress)
• Ed25519 and Ed448 public key algorithms for the Secure Shell (SSH) protocol (work in progress)
SEE ALSO
application(3)
ssh
Erlang module
This is the interface module for the SSH application. The Secure Shell (SSH) Protocol is a protocol for secure remote
login and other secure network services over an insecure network. See ssh(6) for details of supported RFCs, versions,
algorithms and unicode handling.
With the SSH application it is possible to start clients and to start daemons (servers).
Clients are started with connect/2, connect/3 or connect/4. They open an encrypted connection on top of TCP/IP. In
that encrypted connection one or more channels could be opened with ssh_connection:session_channel/2,4.
Each channel is an isolated "pipe" between a client-side process and a server-side process. Those process pairs could
handle for example file transfers (sftp) or remote command execution (shell, exec and/or cli). If a custom shell is
implemented, the user of the client could execute the special commands remotely. Note that the user is not necessarily
a human but probably a system interfacing the SSH app.
A server-side subssystem (channel) server is requested by the client with ssh_connection:subsystem/4.
A server (daemon) is started with daemon/1, daemon/2 or daemon/3. Possible channel handlers (subsystems) are
declared with the subsystem option when the daemon is started.
To just run a shell on a remote machine, there are functions that bundles the needed three steps needed into one:
shell/1,2,3. Similarily, to just open an sftp (file transfer) connection to a remote machine, the simplest way is to use
ssh_sftp:start_channel/1,2,3.
To write your own client channel handler, use the behaviour ssh_client_channel. For server channel handlers use
ssh_server_channel behaviour (replaces ssh_daemon_channel).
Both clients and daemons accepts options that controls the exact behaviour. Some options are common to both. The
three sets are called Client Options, Daemon Options and Common Options.
The descriptions of the options uses the Erlang Type Language with explaining text.
Note:
The User's Guide has examples and a Getting Started section.
Daemons
The keys are by default stored in files:
• Mandatory: one or more Host key(s), both private and public. Default is to store them in the directory /etc/
ssh in the files
• ssh_host_dsa_key and ssh_host_dsa_key.pub
• ssh_host_rsa_key and ssh_host_rsa_key.pub
• ssh_host_ecdsa_key and ssh_host_ecdsa_key.pub
The host keys directory could be changed with the option system_dir.
• Optional: one or more User's public key in case of publickey authorization. Default is to store them
concatenated in the file .ssh/authorized_keys in the user's home directory.
The user keys directory could be changed with the option user_dir.
Clients
The keys and some other data are by default stored in files in the directory .ssh in the user's home directory.
The directory could be changed with the option user_dir.
• Optional: a list of Host public key(s) for previously connected hosts. This list is handled by the SSH application
without any need of user assistance. The default is to store them in the file known_hosts.
The host_accepting_client_options() are associated with this list of keys.
• Optional: one or more User's private key(s) in case of publickey authorization. The default files are
• id_dsa and id_dsa.pub
• id_rsa and id_rsa.pub
• id_ecdsa and id_ecdsa.pub
Data Types
Client Options
client_options() = [client_option()]
client_option() =
ssh_file:pubkey_passphrase_client_options() |
host_accepting_client_options() |
authentication_client_options() |
diffie_hellman_group_exchange_client_option() |
connect_timeout_client_option() |
recv_ext_info_client_option() |
opaque_client_options() |
gen_tcp:connect_option() |
common_option()
Options for clients. The individual options are further explained below or by following the hyperlinks.
host_accepting_client_options() =
{silently_accept_hosts, accept_hosts()} |
{user_interaction, boolean()} |
{save_accepted_host, boolean()} |
{quiet_mode, boolean()}
accept_hosts() =
boolean() |
accept_callback() |
The subsystem_name is the name that a client requests to start with for example ssh_connection:subsystem/4.
The channel_callback is the module that implements the ssh_server_channel (replaces ssh_daemon_channel)
behaviour in the daemon. See the section Creating a Subsystem in the User's Guide for more information and an
example.
If the subsystems option is not present, the value of ssh_sftpd:subsystem_spec([]) is used. This enables the
sftp subsystem by default. The option can be set to the empty list if you do not want the daemon to run any subsystems.
shell_daemon_option() = {shell, shell_spec()}
shell_spec() = mod_fun_args() | shell_fun() | disabled
shell_fun() = 'shell_fun/1'() | 'shell_fun/2'()
'shell_fun/1'() = fun((User :: string()) -> pid())
'shell_fun/2'() =
fun((User :: string(), PeerAddr :: inet:ip_address()) -> pid())
Defines the read-eval-print loop used in a daemon when a shell is requested by the client. The default is to use the
Erlang shell: {shell, start, []}
See the option exec-option for a description of how the daemon executes shell-requests and exec-requests
depending on the shell- and exec-options.
exec_daemon_option() = {exec, exec_spec()}
exec_spec() =
{direct, exec_fun()} | disabled | deprecated_exec_opt()
exec_fun() = 'exec_fun/1'() | 'exec_fun/2'() | 'exec_fun/3'()
'exec_fun/1'() = fun((Cmd :: string()) -> exec_result())
'exec_fun/2'() =
fun((Cmd :: string(), User :: string()) -> exec_result())
'exec_fun/3'() =
fun((Cmd :: string(),
User :: string(),
ClientAddr :: ip_port()) ->
exec_result())
exec_result() =
{ok, Result :: term()} | {error, Reason :: term()}
This option changes how the daemon executes exec-requests from clients. The term in the return value is formatted to
a string if it is a non-string type. No trailing newline is added in the ok-case.
See the User's Guide section on One-Time Execution for examples.
Error texts are returned on channel-type 1 which usually is piped to stderr on e.g Linux systems. Texts from a
successful execution are returned on channel-type 0 and will in similar manner be piped to stdout. The exit-status
code is set to 0 for success and 255 for errors. The exact results presented on the client side depends on the client
and the client's operating system.
In case of the {direct, exec_fun()} variant or no exec-option at all, all reads from standard_input will
be from the received data-events of type 0. Those are sent by the client. Similarily all writes to standard_output
will be sent as data-events to the client. An OS shell client like the command 'ssh' will usally use stdin and stdout
for the user interface.
The option cooperates with the daemon-option shell in the following way:
1. If neither the exec-option nor the shell-option is present:
The default Erlang evaluator is used both for exec and shell requests. The result is returned to the client.
2. If the exec_spec's value is disabled (the shell-option may or may not be present):
No exec-requests are executed but shell-requests are not affected, they follow the shell_spec's value.
3. If the exec-option is present and the exec_spec value =/= disabled (the shell-option may or may
not be present):
The exec_spec fun() is called with the same number of parameters as the arity of the fun, and the result is
returned to the client. Shell-requests are not affected, they follow the shell_spec's value.
4. If the exec-option is absent, and the shell-option is present with the default Erlang shell as the
shell_spec's value:
The default Erlang evaluator is used both for exec and shell requests. The result is returned to the client.
5. If the exec-option is absent, and the shell-option is present with a value that is neither the default
Erlang shell nor the value disabled:
The exec-request is not evaluated and an error message is returned to the client. Shell-requests are executed
according to the value of the shell_spec.
6. If the exec-option is absent, and the shell_spec's value is disabled:
Exec requests are executed by the default shell, but shell-requests are not executed.
If a custom CLI is installed (see the option ssh_cli) the rules above are replaced by thoose implied by the custom
CLI.
Note:
The exec-option has existed for a long time but has not previously been documented. The old definition and
behaviour are retained but obey the rules 1-6 above if conflicting. The old and undocumented style should not be
used in new programs.
kb_int_tuple())
pwdfun_2() =
fun((User :: string(), Password :: string()) -> boolean())
pwdfun_4() =
fun((User :: string(),
Password :: string(),
PeerAddress :: ip_port(),
State :: any()) ->
boolean() |
disconnect |
{boolean(), NewState :: any()})
auth_method_kb_interactive_data
Sets the text strings that the daemon sends to the client for presentation to the user when using keyboard-
interactive authentication.
If the fun/3 is used, it is called when the actual authentication occurs and may therefore return dynamic data like
time, remote ip etc.
The parameter Echo guides the client about need to hide the password.
The default value is: {auth_method_kb_interactive_data, {"SSH server", "Enter
password for \""++User++"\"", "password: ", false}>
user_passwords
Provides passwords for password authentication. The passwords are used when someone tries to connect to
the server and public key user-authentication fails. The option provides a list of valid usernames and the
corresponding passwords.
password
Provides a global password that authenticates any user.
Warning:
Intended to facilitate testing.
From a security perspective this option makes the server very vulnerable.
A third usage is to block login attempts from a missbehaving peer. The State described above can be used for
this. The return value disconnect is useful for this.
pwdfun with pwdfun_2()
Provides a function for password validation. This function is called with user and password as strings, and returns:
• true if the user and password is valid
• false if the user or password is invalid
This variant is kept for compatibility.
diffie_hellman_group_exchange_daemon_option() =
{dh_gex_groups,
[explicit_group()] |
explicit_group_file() |
ssh_moduli_file()} |
{dh_gex_limits, {Min :: integer() >= 1, Max :: integer() >= 1}}
explicit_group() =
{Size :: integer() >= 1,
G :: integer() >= 1,
P :: integer() >= 1}
explicit_group_file() = {file, string()}
ssh_moduli_file() = {ssh_moduli_file, string()}
dh_gex_groups
Defines the groups the server may choose among when diffie-hellman-group-exchange is negotiated. See RFC
4419 for details. The three variants of this option are:
{Size=integer(),G=integer(),P=integer()}
The groups are given explicitly in this list. There may be several elements with the same Size. In such a
case, the server will choose one randomly in the negotiated Size.
{file,filename()}
The file must have one or more three-tuples {Size=integer(),G=integer(),P=integer()}
terminated by a dot. The file is read when the daemon starts.
{ssh_moduli_file,filename()}
The file must be in ssh-keygen moduli file format. The file is read when the daemon starts.
The default list is fetched from the public_key application.
dh_gex_limits
Limits what a client can ask for in diffie-hellman-group-exchange. The limits will be {MaxUsed =
min(MaxClient,Max), MinUsed = max(MinClient,Min)} where MaxClient and MinClient
are the values proposed by a connecting client.
The default value is {0,infinity}.
If MaxUsed < MinUsed in a key exchange, it will fail with a disconnect.
See RFC 4419 for the function of the Max and Min values.
hello_timeout_daemon_option() = {hello_timeout, timeout()}
Maximum time in milliseconds for the first part of the ssh session setup, the hello message exchange. Defaults to
30000 ms (30 seconds). If the client fails to send the first message within this time, the connection is closed.
negotiation_timeout_daemon_option() =
{negotiation_timeout, timeout()}
Maximum time in milliseconds for the authentication negotiation. Defaults to 120000 ms (2 minutes). If the client
fails to log in within this time, the connection is closed.
hardening_daemon_options() =
{max_sessions, integer() >= 1} |
{max_channels, integer() >= 1} |
{parallel_login, boolean()} |
{minimal_remote_max_packet_size, integer() >= 1}
max_sessions
The maximum number of simultaneous sessions that are accepted at any time for this daemon. This includes
sessions that are being authorized. Thus, if set to N, and N clients have connected but not started the login process,
connection attempt N+1 is aborted. If N connections are authenticated and still logged in, no more logins are
accepted until one of the existing ones log out.
The counter is per listening port. Thus, if two daemons are started, one with {max_sessions,N} and the other
with {max_sessions,M}, in total N+M connections are accepted for the whole ssh application.
Notice that if parallel_login is false, only one client at a time can be in the authentication phase.
By default, this option is not set. This means that the number is not limited.
max_channels
The maximum number of channels with active remote subsystem that are accepted for each connection to this
daemon
By default, this option is not set. This means that the number is not limited.
parallel_login
If set to false (the default value), only one login is handled at a time. If set to true, an unlimited number of login
attempts are allowed simultaneously.
If the max_sessions option is set to N and parallel_login is set to true, the maximum number of
simultaneous login attempts at any time is limited to N-K, where K is the number of authenticated connections
present at this daemon.
Warning:
Do not enable parallel_logins without protecting the server by other means, for example, by the
max_sessions option or a firewall configuration. If set to true, there is no protection against DOS attacks.
minimal_remote_max_packet_size
The least maximum packet size that the daemon will accept in channel open requests from the client. The default
value is 0.
callbacks_daemon_options() =
{failfun,
fun((User :: string(),
PeerAddress :: inet:ip_address(),
Reason :: term()) ->
term())} |
{connectfun,
fun((User :: string(),
PeerAddress :: inet:ip_address(),
Method :: string()) ->
term())}
connectfun
Provides a fun to implement your own logging when a user authenticates to the server.
failfun
Provides a fun to implement your own logging when a user fails to authenticate.
send_ext_info_daemon_option() = {send_ext_info, boolean()}
Make the server (daemon) tell the client that the server accepts extension negotiation, that is, include ext-info-s
in the kexinit message sent. See RFC 8308 for details and ssh(6) for a list of currently implemented extensions.
Default value is true which is compatible with other implementations not supporting ext-info.
• {Minutes, Bytes} initiate rekeying when any of the limits are reached.
• Bytes initiate rekeying when Bytes number of bytes are transferred, or at latest after one hour.
When a rekeying is done, both the timer and the byte counter are restarted. Defaults to one hour and one GByte.
If Minutes is set to infinity, no rekeying will ever occur due to that max time has passed. Setting Bytes
to infinity will inhibit rekeying after a certain amount of data has been transferred. If the option value is set
to {infinity, infinity}, no rekeying will be initiated. Note that rekeying initiated by the peer will still be
performed.
key_cb_common_option() =
{key_cb,
Module :: atom() | {Module :: atom(), Opts :: [term()]}}
Module implementing the behaviour ssh_client_key_api and/or ssh_server_key_api. Can be used to customize the
handling of public keys. If callback options are provided along with the module name, they are made available to the
callback module via the options passed to it under the key 'key_cb_private'.
The Opts defaults to [] when only the Module is specified.
The default value of this option is {ssh_file, []}. See also the manpage of ssh_file.
A call to the call-back function F will be
Module:F(..., [{key_cb_private,Opts}|UserOptions])
where ... are arguments to F as in ssh_client_key_api and/or ssh_server_key_api. The UserOptions are the
options given to ssh:connect, ssh:shell or ssh:daemon.
pref_public_key_algs_common_option() =
{pref_public_key_algs, [pubkey_alg()]}
List of user (client) public key algorithms to try to use.
The default value is the public_key entry in the list returned by ssh:default_algorithms/0.
If there is no public key of a specified type available, the corresponding entry is ignored. Note that the available set
is dependent on the underlying cryptolib and current user's public keys.
See also the option user_dir for specifying the path to the user's keys.
disconnectfun_common_option() =
{disconnectfun, fun((Reason :: term()) -> void | any())}
Provides a fun to implement your own logging when the peer disconnects.
unexpectedfun_common_option() =
{unexpectedfun,
fun((Message :: term(), {Host :: term(), Port :: term()}) ->
report | skip)}
Provides a fun to implement your own logging or other action when an unexpected message arrives. If the fun returns
report the usual info report is issued but if skip is returned no report is generated.
ssh_msg_debug_fun_common_option() =
{ssh_msg_debug_fun,
fun((ssh:connection_ref(),
AlwaysDisplay :: boolean(),
Msg :: binary(),
LanguageTag :: binary()) ->
any())}
Provide a fun to implement your own logging of the SSH message SSH_MSG_DEBUG. The last three parameters
are from the message, see RFC 4253, section 11.3. The connection_ref() is the reference to the connection on
which the message arrived. The return value from the fun is not checked.
The default behaviour is ignore the message. To get a printout for each message with AlwaysDisplay = true,
use for example {ssh_msg_debug_fun, fun(_,true,M,_)-> io:format("DEBUG: ~p~n", [M])
end}
id_string_common_option() =
{id_string,
string() |
random |
{random, Nmin :: integer() >= 1, Nmax :: integer() >= 1}}
The string the daemon will present to a connecting peer initially. The default value is "Erlang/VSN" where VSN is
the ssh application version number.
The value random will cause a random string to be created at each connection attempt. This is to make it a bit more
difficult for a malicious peer to find the ssh software brand and version.
The value {random, Nmin, Nmax} will make a random string with at least Nmin characters and at most Nmax
characters.
preferred_algorithms_common_option() =
{preferred_algorithms, algs_list()}
algs_list() = [alg_entry()]
alg_entry() =
{kex, [kex_alg()]} |
{public_key, [pubkey_alg()]} |
{cipher, double_algs(cipher_alg())} |
{mac, double_algs(mac_alg())} |
{compression, double_algs(compression_alg())}
kex_alg() =
'diffie-hellman-group-exchange-sha1' |
'diffie-hellman-group-exchange-sha256' |
'diffie-hellman-group1-sha1' | 'diffie-hellman-group14-sha1' |
'diffie-hellman-group14-sha256' |
'diffie-hellman-group16-sha512' |
'diffie-hellman-group18-sha512' | 'curve25519-sha256' |
'[email protected]' | 'curve448-sha512' |
'ecdh-sha2-nistp256' | 'ecdh-sha2-nistp384' |
'ecdh-sha2-nistp521'
pubkey_alg() =
'ecdsa-sha2-nistp256' | 'ecdsa-sha2-nistp384' |
'ecdsa-sha2-nistp521' | 'ssh-ed25519' | 'ssh-ed448' |
'rsa-sha2-256' | 'rsa-sha2-512' | 'ssh-dss' | 'ssh-rsa'
cipher_alg() =
'3des-cbc' | 'AEAD_AES_128_GCM' | 'AEAD_AES_256_GCM' |
'aes128-cbc' | 'aes128-ctr' | '[email protected]' |
'aes192-ctr' | 'aes192-cbc' | 'aes256-cbc' | 'aes256-ctr' |
'[email protected]' | '[email protected]'
mac_alg() =
'AEAD_AES_128_GCM' | 'AEAD_AES_256_GCM' | 'hmac-sha1' |
{preferred_algorithms,
[{public_key,['ssh-rsa','ssh-dss']},
{cipher,[{client2server,['aes128-ctr']},
{server2client,['aes128-cbc','3des-cbc']}]},
{mac,['hmac-sha2-256','hmac-sha1']},
{compression,[none,zlib]}
]
}
The example specifies different algorithms in the two directions (client2server and server2client), for cipher but
specifies the same algorithms for mac and compression in both directions. The kex (key exchange) is implicit but
public_key is set explicitly.
For background and more examples see the User's Guide.
If an algorithm name occurs more than once in a list, the behaviour is undefined. The tags in the property lists are
also assumed to occur at most one time.
Warning:
Changing the values can make a connection less secure. Do not change unless you know exactly what you are
doing. If you do not understand the values then you are not supposed to change them.
modify_algorithms_common_option() =
{modify_algorithms, modify_algs_list()}
modify_algs_list() =
[{append, algs_list()} |
{prepend, algs_list()} |
{rm, algs_list()}]
Modifies the list of algorithms to use in the algorithm negotiation. The modifications are applied after the option
preferred_algorithms (if existing) is applied.
The algoritm for modifications works like this:
• Input is the modify_algs_list() and a set of algorithms A obtained from the preferred_algorithms
option if existing, or else from the ssh:default_algorithms/0.
• The head of the modify_algs_list() modifies A giving the result A'.
The possible modifications are:
• Append or prepend supported but not enabled algorithm(s) to the list of algorithms. If the wanted algorithms
already are in A they will first be removed and then appended or prepended,
• Remove (rm) one or more algorithms from A.
• Repeat the modification step with the tail of modify_algs_list() and the resulting A'.
{modify_algorithms,
[{prepend, [{kex, ['diffie-hellman-group1-sha1']}],
{rm, [{compression, [none]}]}
]
}
{client_version, version()} |
{server_version, version()} |
{user, string()} |
{peer, {inet:hostname(), ip_port()}} |
{sockname, ip_port()} |
{options, client_options()} |
{algorithms, conn_info_algs()} |
{channels, conn_info_channels()}
version() = {protocol_version(), software_version()}
protocol_version() =
{Major :: integer() >= 1, Minor :: integer() >= 0}
software_version() = string()
conn_info_algs() =
[{kex, kex_alg()} |
{hkey, pubkey_alg()} |
{encrypt, cipher_alg()} |
{decrypt, cipher_alg()} |
{send_mac, mac_alg()} |
{recv_mac, mac_alg()} |
{compress, compression_alg()} |
{decompress, compression_alg()} |
{send_ext_info, boolean()} |
{recv_ext_info, boolean()}]
conn_info_channels() = [proplists:proplist()]
Return values from the connection_info/1 and connection_info/2 functions.
In the option info tuple are only the options included that differs from the default values.
daemon_info_tuple() =
{port, inet:port_number()} |
{ip, inet:ip_address()} |
{profile, atom()} |
{options, daemon_options()}
Return values from the daemon_info/1 and daemon_info/2 functions.
In the option info tuple are only the options included that differs from the default values.
opaque_client_optionsopaque_daemon_optionsopaque_common_options
Opaque types that define experimental options that are not to be used in products.
Exports
Warning:
This is an extremly dangerous function. You use it on your own risk.
Some options are OS and OS version dependent. Do not use it unless you know what effect your option values
will have on an TCP stream.
Some values may destroy the functionality of the SSH protocol.
daemon_info(DaemonRef) ->
{ok, InfoTupleList} | {error, bad_daemon_ref}
daemon_info(DaemonRef, Key :: ItemList | Item) ->
ssh_client_channel
Erlang module
Note:
This module replaces ssh_channel.
The old module is still available for compatibility, but should not be used for new programs. The old module will
not be maintained except for some error corrections
SSH services (clients and servers) are implemented as channels that are multiplexed over an SSH connection and
communicates over the SSH Connection Protocol. This module provides a callback API that takes care of generic
channel aspects for clients, such as flow control and close messages. It lets the callback functions take care of the
service (application) specific parts. This behavior also ensures that the channel process honors the principal of an OTP-
process so that it can be part of a supervisor tree. This is a requirement of channel processes implementing a subsystem
that will be added to the ssh applications supervisor tree.
Note:
When implementing a ssh subsystem for daemons, use -behaviour(ssh_server_channel) (Replaces
ssh_daemon_channel) instead.
Don't:
Functions in this module are not supposed to be called outside a module implementing this behaviour!
Exports
Msg = term()
Sends an asynchronous message to the channel process and returns ok immediately, ignoring if the destination node
or channel process does not exist. The channel calls Module:handle_cast/2 to handle the message.
enter_loop(State) -> _
Types:
State = term()
as returned by init/1
Makes an existing process an ssh_client_channel (replaces ssh_channel) process. Does not return, instead
the calling process enters the ssh_client_channel (replaces ssh_channel) process receive loop and become an
ssh_client_channel process. The process must have been started using one of the start functions in proc_lib,
see the proc_lib(3) manual page in STDLIB. The user is responsible for any initialization of the process and must
call init/1.
Note:
This function is normally not called by the user. The user only needs to call if the channel process needs to be
started with help of proc_lib instead of calling start/4 or start_link/4.
Callback Functions
The following functions are to be exported from a ssh_client_channel callback module.
Callback timeouts
The timeout values that can be returned by the callback functions have the same semantics as in a gen_server. If the
time-out occurs, handle_msg/2 is called as handle_msg(timeout, State).
Exports
Note:
Soft upgrade according to the OTP release concept is not straight forward for the server side, as subsystem channel
processes are spawned by the ssh application and hence added to its supervisor tree. The subsystem channels can
be upgraded when upgrading the user application, if the callback functions can handle two versions of the state,
but this function cannot be used in the normal way.
ssh_server_channel
Erlang module
Note:
This module replaces ssh_daemon_channel.
The old module is still available for compatibility, but should not be used for new programs. The old module will
not be maintained except for some error corrections
SSH services (clients and servers) are implemented as channels that are multiplexed over an SSH connection and
communicates over the SSH Connection Protocol. This module provides a callback API that takes care of generic
channel aspects for daemons, such as flow control and close messages. It lets the callback functions take care of the
service (application) specific parts. This behavior also ensures that the channel process honors the principal of an OTP-
process so that it can be part of a supervisor tree. This is a requirement of channel processes implementing a subsystem
that will be added to the ssh applications supervisor tree.
Note:
When implementing a client subsystem handler, use -behaviour(ssh_client_channel) instead.
Callback Functions
The following functions are to be exported from a ssh_server_channel callback module.
Exports
ssh_connection
Erlang module
The SSH Connection Protocol is used by clients and servers, that is, SSH channels, to communicate over the SSH
connection. The API functions in this module send SSH Connection Protocol events, which are received as messages
by the remote channel handling the remote channel. The Erlang format of thoose messages is (see also below):
{ssh_cm, ssh:connection_ref(), channel_msg()}
If the ssh_client_channel behavior is used to implement the channel process, these messages are handled by
handle_ssh_msg/2.
Data Types
ssh_data_type_code() = integer() >= 0
The valid values are 0 ("normal") and 1 ("stderr"), see RFC 4254, Section 5.2.
result() = req_status() | {error, reason()}
reason() = closed | timeout
The result of a call.
If the request reached the peer, was handled and the response reached the requesting node the req_status() is the status
reported from the peer.
If not, the reason() indicates what went wrong:
closed
indicates that the channel or connection was closed when trying to send the request
timeout
indicates that the operation exceeded a time limit
req_status() = success | failure
The status of a request. Coresponds to the SSH_MSG_CHANNEL_SUCCESS and SSH_MSG_CHANNEL_FAILURE
values in RFC 4254, Section 5.4.
want_reply() = boolean()
Messages that include a WantReply expect the channel handling process to call ssh_connection:reply_request/4
with the boolean value of WantReply as the second argument.
Exports
Note:
Channels implemented with the ssh_client_channel behavior do not normally need to call this function as flow
control is handled by the behavior. The behavior adjusts the window every time the callback handle_ssh_msg/2
returns after processing channel data.
Note:
This function is called by the ssh_client_channel behavior when the channel is terminated, see
ssh_client_channel(3). Thus, channels implemented with the behavior are not to call this function explicitly.
ConnectionRef = ssh:connection_ref()
WantReply = boolean()
Status = req_status()
ChannelId = ssh:channel_id()
Sends status replies to requests where the requester has stated that it wants a status report, that is, WantReply =
true. If WantReply is false, calling this function becomes a "noop". Is to be called while handling an SSH
Connection Protocol message containing a WantReply boolean value.
ConnectionRef = ssh:connection_ref()
ChannelId = ssh:channel_id()
Var = Value = string()
Timeout = timeout()
Environment variables can be passed before starting the shell/command. Is to be called by a client channel processes.
ssh_client_key_api
Erlang module
Behavior describing the API for public key handling of an SSH client. By implementing the callbacks defined in this
behavior, the public key handling of an SSH client can be customized. By default the ssh application implements this
behavior with help of the standard OpenSSH files, see the ssh(6) application manual.
Data Types
client_key_cb_options() =
[{key_cb_private, term()} | ssh:client_option()]
Options provided to ssh:connect/[3,4].
The option list given in the key_cb option is available with the key key_cb_private.
Exports
ConnectOptions = client_key_cb_options()
PrivateKey = public_key:private_key()
Private key of the user matching the Algorithm.
Reason = term()
Fetches the users public key matching the Algorithm.
Note:
The private key contains the public key.
ssh_server_key_api
Erlang module
Behaviour describing the API for public key handling of an SSH server. By implementing the callbacks defined in this
behavior, the public key handling of an SSH server can be customized. By default the SSH application implements
this behavior with help of the standard OpenSSH files, see the ssh(6) application manual.
Data Types
daemon_key_cb_options() =
[{key_cb_private, term()} | ssh:daemon_option()]
Options provided to ssh:daemon/2,3.
The option list given in the key_cb option is available with the key key_cb_private.
Exports
ssh_file
Erlang module
This module is the default callback handler for the client's and the server's user and host "database" operations. All
data, for instance key pairs, are stored in files in the normal file system. This page documents the files, where they are
stored and configuration options for this callback module.
The intention is to be compatible with the OpenSSH storage in files. Therefore it mimics directories and filenames
of OpenSSH.
Ssh_file implements the ssh_server_key_api and the ssh_client_key_api. This enables the user to make an own
interface using for example a database handler.
Such another callback module could be used by setting the option key_cb when starting a client or a server (with
for example ssh:connect, ssh:daemon of ssh:shell ).
Note:
The functions are Callbacks for the SSH app. They are not intended to be called from the user's code!
Clients
Clients uses all files stored in the USERDIR directory.
Directory contents
LOCALUSER
The user name of the OS process running the Erlang virtual machine (emulator).
SYSDIR
This is the directory holding the server's files:
• ssh_host_dsa_key - private dss host key (optional)
• ssh_host_rsa_key - private rsa host key (optional)
• ssh_host_ecdsa_key - private ecdsa host key (optional)
• ssh_host_ed25519_key - private eddsa host key for curve 25519 (optional)
• ssh_host_ed448_key - private eddsa host key for curve 448 (optional)
At least one host key must be defined. The default value of SYSDIR is /etc/ssh.
For security reasons, this directory is normally accessible only to the root user.
To change the SYSDIR, see the system_dir option.
USERDIR
This is the directory holding the files:
• authorized_keys and, as second alternative authorized_keys2 - the user's public keys are stored
concatenated in one of those files.
• known_hosts - host keys from hosts visited concatenated. The file is created and used by the client.
• id_dsa - private dss user key (optional)
• id_rsa - private rsa user key (optional)
• id_ecdsa - private ecdsa user key (optional)
• id_ed25519 - private eddsa user key for curve 25519 (optional)
• id_ed448 - private eddsa user key for curve 448 (optional)
The default value of USERDIR is /home/LOCALUSER/.ssh.
To change the USERDIR, see the user_dir option
Data Types
Options for the default ssh_file callback module
user_dir_common_option() = {user_dir, string()}
Sets the user directory.
user_dir_fun_common_option() = {user_dir_fun, user2dir()}
user2dir() =
fun((RemoteUserName :: string()) -> UserDir :: string())
Sets the user directory dynamically by evaluating the user2dir function.
system_dir_daemon_option() = {system_dir, string()}
Sets the system directory.
pubkey_passphrase_client_options() =
{dsa_pass_phrase, string()} |
{rsa_pass_phrase, string()} |
{ecdsa_pass_phrase, string()}
If the user's DSA, RSA or ECDSA key is protected by a passphrase, it can be supplied with thoose options.
Note that EdDSA passhrases (Curves 25519 and 448) are not implemented.
Exports
• USERDIR/id_ecdsa
• USERDIR/id_ed25519
• USERDIR/id_ed448
ssh_sftp
Erlang module
This module implements an SSH FTP (SFTP) client. SFTP is a secure, encrypted file transfer service available for SSH.
Data Types
sftp_option() =
{timeout, timeout()} |
{sftp_vsn, integer() >= 1} |
{window_size, integer() >= 1} |
{packet_size, integer() >= 1}
Error cause
reason() = atom() | string() | tuple()
A description of the reason why an operation failed.
The atom() value is formed from the sftp error codes in the protocol-level responses as defined in draft-ietf-secsh-
filexfer-13 section 9.1. The codes are named as SSH_FX_* which are transformed into lowercase of the star-part.
E.g. the error code SSH_FX_NO_SUCH_FILE will cause the reason() to be no_such_file.
The string() reason is the error information from the server in case of an exit-signal. If that information is empty,
the reason is the exit signal name.
The tuple() reason are other errors like for example {exit_status,1}.
Exports
ChannelPid = pid()
Handle = term()
Len = integer()
Error = {error, reason()}
N = term()
Reads from an open file, without waiting for the result. If the handle is valid, the function returns {async, N},
where N is a term guaranteed to be unique between calls of aread. The actual data is sent as a message to the calling
process. This message has the form {async_reply, N, Result}, where Result is the result from the read,
either {ok, Data}, eof, or {error, reason()}.
ChannelPid = pid()
Name = string()
Timeout = timeout()
Error = {error, reason()}
Deletes a directory specified by Name. The directory must be empty before it can be successfully deleted.
ChannelPid = pid()
Name = string()
Mode = [read | write | append | binary | raw]
Timeout = timeout()
Handle = term()
Error = {error, reason()}
Opens a file on the server and returns a handle, which can be used for reading or writing.
ChannelPid = pid()
Handle = term()
Location =
Offset |
{bof, Offset} |
{cur, Offset} |
{eof, Offset} |
bof | cur | eof
Timeout = timeout()
Offset = NewPosition = integer()
Error = {error, reason()}
Sets the file position of the file referenced by Handle. Returns {ok, NewPosition} (as an absolute offset) if
successful, otherwise {error, reason()}. Location is one of the following:
Offset
The same as {bof, Offset}.
{bof, Offset}
Absolute offset.
{cur, Offset}
Offset from the current position.
{eof, Offset}
Offset from the end of file.
bof | cur | eof
The same as eariler with Offset 0, that is, {bof, 0} | {cur, 0} | {eof, 0}.
ChannelPid = pid()
Handle = term()
Position = integer()
Data = iolist()
Timeout = timeout()
Error = {error, reason()}
The pwrite/3,4 function writes to a specified position, combining the position/3 and write/3,4 functions.
Depending on the underlying OS:es links might be followed and info on the final file, directory etc is returned. See
read_link_info/2 on how to get information on links instead.
start_channel(ConnectionRef) ->
start_channel(ConnectionRef, SftpOptions) -> {ok, ChannelPid} | Error
start_channel(Host) ->
start_channel(Host, Options) ->
start_channel(Host, Port, Options) ->
start_channel(TcpSocket) ->
start_channel(TcpSocket, Options) -> {ok, ChannelPid, ConnectionRef} | Error
Types:
Host = ssh:host()
Port = inet:port_number()
TcpSocket = ssh:open_socket()
stop_channel(ChannelPid) -> ok
Types:
ChannelPid = pid()
Stops an SFTP channel. Does not close the SSH connection. Use ssh:close/1 to close it.
ChannelPid = pid()
File = string()
Data = iodata()
Timeout = timeout()
Error = {error, reason()}
Writes a file to the server. The file is created if it does not exist but overwritten if it exists.
ssh_sftpd
Erlang module
Exports