Name

Dropbear — Ssh client support

Synopsis

#include <dropbear.h>
      

int cyg_dropbear_ssh_connect(cyg_dropbear_cli_handle* handle, const struct sockaddr_storage* addr, const cyg_dropbear_authenticate* auth, const char * command);

void cyg_dropbear_ssh_close(cyg_dropbear_cli_handle* handle, int wait);

Description

The client-side API allows an eCos application to establish a secure connection to a remote ssh server and run commands on the remote machine. This requires that the application authenticate itself as a valid user on that system. Once the remote command is running the eCos application can interact with its stdin/stdout/stderr stream over sockets.

The client-side code has only been tested against openssh running on a Linux server and and as such descriptions of host-side server configuration settings and files in the remainder of this section refer to Linux. Interoperability with other ssh implementations cannot be guaranteed.

Application developers should be aware that establishing an ssh connection is a complicated business. Even if the eCos application is working correctly there are many things completely outside its control that could go wrong and prevent a secure connection from being established. Some of these are: firewalls intercepting and discarding packets to the ssh server; tcp wrappers intercepting and rejecting requests before they even reach the ssh server, courtesy of settings in the /etc/hosts.allow and /etc/hosts.deny files on the host ssh server; ssh server settings in /etc/ssh/sshd_config which are incompatible with the application's requirements; problems with the user account specified by the application; or problems with the ssh keys in the ~/.ssh/authorized_keys2 file of the user's account on the host server. It is recommended that when experiencing connectivity problems from an eCos application the developer first checks the server's setup, for example by using ssh or dbclient commands on a suitable Linux box on the same network as the eCos system and specifying the same account and keys.

Application developers should also be aware that allowing remote systems running eCos to access an ssh server has security implications. For example if an attacker has physical access to a remote system, that attacker could use technology like jtag to examine the contents of the flash memory and search for plain text passwords or private keys. It is the developers' responsibility to understand the security issues associated with ssh technology and decide whether the risks are acceptable.

API

There are only two functions in the client-side API, one to establish a secure connection and run a command on the remote machine, the other to shut down the connection cleanly. The key data structure is a cyg_dropbear_cli_handle which holds all application-level state relevant to the connection.

typedef struct cyg_dropbear_cli_handle {
    int         db_stdin_stdout;
    int         db_stderr;
    int         db_exitcode;
    char        db_error[CYG_DROPBEAR_MAX_ERROR];
    …
} cyg_dropbear_cli_handle;

Each ssh connection requires its own instance of this data structure, and the instance must exist for the duration of the connection. All of the fields are managed by the dropbear code and the application should only read them, not modify them in any way. The db_stdin_stdout and db_stderr fields are file descriptors corresponding to sockets. Any data written to db_stdin_stdout will appear on the remote application's stdin stream. Any data written by the remote application to its stdout will appear as input on db_stdin_stdout. Any data written to its stderr will appear as input on db_stderr. The db_exitcode field is only valid once the remote application has exited and hold its exit code, usually 0 for a successful run and non-zero to indicate some kind of error. Most error conditions associated with the ssh connection itself will result in an error message being placed in db_error. However error conditions within the remote command will typically be reported via the stderr stream.

A typical client-side application will look like this:

void
run_remote_program(…)
{
    cyg_dropbear_cli_handle handle;

    <Fill in a struct sockaddr with the server's network address>

    <Fill in an authentication structure>

    if (!cyg_dropbear_ssh_connect(&handle, …)) {
        <Something has gone wrong during the connect process>
        <If there is a user, report the handle's db_error message>
        return;
    }

    if ( <reading from remote application> ) {
        while (! <EOF detected on db_stdin_stdout> ) {
            <Use read() on db_stdin_stdout>
            <Optionally, for robustness, look for errors on db_stderr>
        }
    } else { // writing to remote application
        while ( <there is data to be written> ) {
            <use write() on db_stdin_stdout>
            <Optionally, for robustness, look for errors on db_stderr>
        }
    }

    cyg_dropbear_ssh_close(&handle, 1);
    <Optionally check the exit code>
}

More complicated behaviour is possible, and the clitest1.c testcase in the package's tests subdirectory provides numerous examples.

Connecting

The cyg_dropbear_ssh_connect function takes four arguments:

handle
A pointer to a cyg_dropbear_cli_handle structure. This structure will be filled in and managed by the dropbear code, and should only be read by the eCos application. The structure must remain valid for the duration of the ssh connection, until after the call to cyg_dropbear_ssh_close.
addr
The full address of the ssh server on the remote machine. Typically this will actually be a sockaddr_in or sockaddr_in6 structure (assuming CYGPKG_NET_INET6 is enabled). The ssh server must be accessible via this address irrespective of any firewall filtering, tcpwrapper settings (/etc/hosts.allow and /etc/hosts.deny), and ssh server settings (/etc/ssh/sshd_config on the host, especially the AddressFamily, ListenAddress and Port settings). Usually the port number will be htons(22) but an alternative ssh server listening on a different port may also be used. The dropbear code does not examine the contents of the address, it simply passes the address on to the TCP/IP stack's connect function.
auth
This structure holds all the authentication information, and is discussed in detail below.
command
The command to be executed on the remote server. Typically this will be run using the account's default shell with a -c <command> option. If NULL is passed then the remote ssh server will start an interactive shell.

If the connection attempt succeeds and the remote ssh server starts the remote shell then cyg_dropbear_ssh_connect will return 1 and the db_stdin_stdout and db_stderr fields in the handle structure will be filled in with suitable sockets. Note that this does not mean that the remote shell has successfully started the requested command. If that part of the operation fails then the shell will output an error message on stderr and exit.

If the connection attempt fails because of a lack of resources, because the remote ssh server is not accessible, or because of an authentication failure, then cyg_dropbear_ssh_connect will return 0 and the handle's db_error field will contain an error message.

Internally, establishing an ssh connection involves starting a separate worker thread and it is the worker thread which runs the main dropbear code. It accepts messages over the network socket from the remote ssh server, decrypts them, and forwards them over local sockets to the application's db_stdin_stdout and db_stderr. It also accepts data written to db_stdin_stdout via a local socket, encrypts it, and passes it on to the remote application via the network socket and the ssh server.

Authentication

The cyg_dropbear_authenticate structure passed to cyg_dropbear_ssh_connect holds the information needed to authenticate the connection with the remote ssh server. Most of the fields are optional, as long as at least one valid authentication mechanism is provided. The structure contains the following fields:

typedef struct cyg_dropbear_authenticate {
    const char*     db_username;
    const char*     db_host_rsa_key_pub;
    const char*     db_host_dsa_key_pub;
    const char*     db_id_rsa;
    int             db_id_rsa_keylen;
    const char*     db_id_dsa;
    int             db_id_dsa_keylen;
    const char*     db_password;
} cyg_dropbear_authenticate;

The structure can be constructed at run-time or statically allocated. The dropbear code only reads the various fields during the call to cyg_dropbear_ssh_connect.

The db_username field must be filled in. It should be a simple string corresponding to a valid account name on the ssh server machine, for example:

    struct cyg_dropbear_authenticate auth;
    …
    auth.db_username = "dropbeartest";

The account name must also be one allowed by the ssh server, as per the /etc/ssh/sshd_config file's AllowUsers setting on the host.

The rsa and dsa host keys can be used to validate the identity of the remote ssh server, preventing certain man-in-the-middle attacks. These fields serve much the same purpose as the ~/.ssh/known_hosts file when using the ssh command on a Linux system. During the authentication stage of establishing a connection the remote ssh server will send a signature encrypted using the server's private key, and the public keys can be used to validate this signature. The fields should be initialized using the contents of the /etc/ssh/ssh_host_dsa_key.pub and /etc/ssh/ssh_host_rsa_key.pub files on the host server, for example:

    …
    auth.db_host_rsa_key_pub = "ssh-rsa AAAAB3Nz…rb8=";
    auth.db_host_dsa_key_pub = "ssh-dss AAAABDNz…v7s=";

Note that the host's /etc/ssh/sshd_config file can specify alternative keys using HostKey settings. It is not necessary to supply the public host keys to the dropbear code. If neither host key is supplied then the code will simply not attempt to validate the identity of the remote ssh server and the known_hosts protection is not applied.

Specifying the public host keys in the authentication structure has one major disadvantage. If it ever becomes necessary to change the host keys on the ssh server then the eCos boxes will not be able to connect to the remote ssh server until the boxes are updated with a new host key. This behaviour is different from running ssh interactively on the Linux command line where the user will be given the choice of accepting the new key and updating the known_hosts file.

The db_id_rsa, db_id_rsa_keylen, db_id_dsa and db_id_dsakeylen fields are used to hold the private keys for public key authentication. If neither key is supplied then the dropbear code will only attempt password authentication. It should be noted that establishing a secure connection using an RSA private key requires many more cpu cycles than using a DSA private key. If both keys are supplied then the dropbear code will try the DSA key first, then the RSA key. The overheads can be reduced by using a smaller keysize, but obviously that has security implications.

The dropbear code requires its private keys in a different format from the Linux ssh command, and embedding these keys requires a somewhat convoluted process. One approach involves generating the keys using the host's ssh-keygen utility with -t rsa or -t dsa. No passphrase must be used, and great care should be taken not to overwrite the user's default private key file. The ssh private key can then be converted into a dropbear key using the dropbearconvert utility, either built from the sources or installed via e.g. the standard dropbear package.

[Note]Notes
  1. dropbearconvert requires the private ssh key to be in legacy PEM private key format, while the default format of private keys for OpenSSH's ssh-keygen command is its own internal format. The option -m PEM to ssh-keygen may be used to specify the private key format of PEM when creating a new private key, or to convert an existing key from internal to PEM format using the -p option (traditionally used to remove or change a key's passphrase). For example:

    ssh-keygen -m PEM -t rsa -C "dropbear@example.com"  # Create new rsa key
    ssh-keygen -m PEM -p -f ~/.ssh/id_rsa               # Convert existing key to PEM format
  2. OpenSSH 7.0 and greater disables the ssh-dss (DSA) public key algorithm by default as it is considered too weak. It's use is not recommended. For additional details see:

The dropbear-format db_id_dsa file can then be converted into a C array using the privatekey2c utility supplied in the package's misc/hangman and misc/shell directories.

For example, to generate a set of private keys for both dsa and rsa, convert them into dropbear format, and generate a C array for inclusion into an eCos client:

ssh-keygen -m PEM -t rsa -C "dropbear@ecoscentric.com"      # Create rsa key
ssh-keygen -m PEM -t dsa -C "dropbear@ecoscentric.com"      # Create dsa key
dropbearconvert openssh dropbear ~/.ssh/id_rsa db_id_rsa    # Convert to dropbear format
dropbearconvert openssh dropbear ~/.ssh/id_dsa db_id_dsa    # Convert to dropbear format
privatekey2c id_rsa db_id_rsa > id_rsa.c                    # Create C code for eCos
privatekey2c id_dsa db_id_dsa > id_dsa.c                    # Create C code for eCos

The resulting id_dsa.c file will contain code like the following:

#define ID_DSA_LEN 457
static const char id_dsa[ID_DSA_LEN] = {
    0x00, 0x00, 0x00, 0x07, \
    … \
    0xd3 \
};

The resulting file can be #include'd by the application code and used to fill in the auth structure's db_id_dsa and db_id_dsa_keylen fields.

As an alternative to using the ssh-keygen and dropbearconvert utilities, dropbear's dropbearkey can be used to generate the private key files directly in the dropbear format. It will still be necessary to use privatekey2c to turn the keys into a C array.

The remote ssh server will use the account's ~/.ssh/authorized_keys file to validate the private keys supplied by the eCos application. This file must be created or updated with the corresponding public keys. For example:

cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys
chmod go-rw ~/.ssh/authorized_keys
[Note]Note

Prior to version 3 of OpenSSH, public key authentication used the file authorized_keys for SSH Protocols 1.3 and 1.5, while authorized_keys2 was used for SSH protocol 2.0. ersion 3 and above of OpenSSH deprecated the use of authorized_keys2, allowing all public keys to be put into authorized_keys.

It is also necessary to have PubkeyAuthentication enabled in /etc/ssh/sshd_config on the ssh server host, and if DSA is to be permitted, PubkeyAcceptedKeyTypes must include ssh-dss.

The final field in the authentication structure is db_password. Again this should be a C string holding the plaintext password, or NULL if password authentication should not be attempted. The ssh server will only allow plaintext passwords if PasswordAuthentication is enabled in the server's configuration file.

I/O with the remote process

Once a secure connection has been established using cyg_dropbear_ssh_connect the eCos application code can interact with the remote command using the db_stdin_stdout and db_stderr fields in the connection's handle structure. These fields hold file descriptors corresponding to local sockets, and the other ends of these local sockets are managed by an internal worker thread started by the connect operation and running the bulk of the dropbear code. Typically I/O involves the read, write and select calls. Any data written to db_stdin_stdout will be read by the worker thread, encrypted, forwarded to the remote ssh server over the network connection, decrypted, and can be read by the remote command using its stdin stream. Any data written by the remote command to stdout or stderr will follow the reverse path and can be read by the eCos application code using the db_stin_stdout and db_stderr file descriptors.

If the remote command exits while the eCos application is still reading data, this will result in an end-of-file condition on the db_stdin_stdout file descriptor. In other words a read call will return 0. At that point the application should close the connection using cyg_dropbear_ssh_close, and not by using the close call on the file descriptor. Similarly if the eCos application wants to close the connection while the remote command is still running then it should use cyg_dropbear_ssh_close.

Given the complexity of the data flow, details of any error conditions such as broken network connections will not get as far as the application code. Specifically, the value of the errno variable will not correspond to the underlying error condition. Instead error conditions are likely to manifest as end-of-file conditions indistinguishable from the command exiting.

Closing a connection

When the eCos application wants to close down the ssh connection it should call cyg_dropbear_ssh_close. This takes two arguments, the handle filled in by cyg_dropbear_ssh_connect and a flag to indicate whether or not the application wants to wait for the remote command to exit. If the application does not wait then the db_exitcode field may not be valid, and the internal worker thread will continue to run in the background and consume resources until the remote command has finished.

Configuration

There are no configuration options specific to the client-side API, other than ones related to testing as described below. However there are a number of options common to the server-side and client-side API.

CYGNUM_NET_DROPBEAR_MAX_PAYLOAD_LEN
While establishing a secure connection the client and daemon negotiate an upper limit for the outgoing packet size. This has to be large enough to cope with the initial key exchange and related information. For eCos the default size is set to 1K, which should be sufficient but is still rather smaller than the dropbear default on other platforms. If the application involves transferring large amounts of data over ssh then throughput may be improved by increasing this packet size, at the cost of increased memory usage.
CYGNUM_NET_DROPBEAR_THREAD_PRI
The client-side dropbear code involves an internal worker thread for each secure connection. These worker threads run at the same priority controlled by this configuration option. The ssh protocol involves computationally intensive operations such as encrypting and decrypting packets, so these threads may have a significiant impact on the number of cpu cycles available to the rest of the system. It may be necessary to manipulate the thread priority to achieve an acceptable balance between overall system performance and the achievable ssh bandwidth.
CYGNUM_NET_DROPBEAR_THREAD_STACKSIZE
This configuration option controls the size of the stacks allocated for internal worker threads. Fairly large stacks are needed to implement the ssh protocol, and in addition the various application callback functions will also run on these stacks. The default value should suffice for all architectures, but this can be checked by using a debug build with stack checking enabled. If the stack sizes are reduced to save memory then it is strongly recommended that the system be tested with stack checking enabled.

Testing

The client-side code comes with an extensive testcase tests/clitest1.c. However this testcase is not built by default. It requires information about the testing environment such as the address of a suitable ssh server, as well as authentication information such as private keys and plain-text passwords. Embedding that kind of information directly into readily-available source code would have obvious security implications. Instead it is necessary to set a variety of configuration options before the testcase can be built.

Not all relevant configurations options need to be set. For example if the booldata option CYGTST_NET_DROPBEAR_TESTS_CLI_PASSWORD is not enabled then the testcase will not attempt any tests related to plain-text passwords. Details of the authentication mechanisms and relevant server settings can be found in the authentication section above.

Some of the configuration values are long, for example complete private ssh keys. Entering these using the graphical configuration tool may be problematical. Instead the ecos.ecc savefile can be edited directly. Alternatively the dropbear package comes with a file misc/clitest1.ecm, containing the configuration options related to testing. This file can be edited and then imported into the eCos configuration. Where appropriate the testcase will turn the configuration values into C strings automatically, so there is no need to add extra quotes to the values. The relevant options are:

CYGPKG_NET_DROPBEAR_TESTS_CLI
Unless this option is enabled the client-side testcase will not be built.
CYGTST_NET_DROPBEAR_TESTS_CLI_SERVER_ADDR

This should be the address of the remote ssh server in a format acceptable to the standard inet_pton function, for example 192.168.0.42.

cdl_option CYGTST_NET_DROPBEAR_TESTS_CLI_SERVER_ADDR {
    user_value 192.168.0.42
};
CYGTST_NET_DROPBEAR_TESTS_CLI_SERVER_PORT

This should be the tcp port number for the remote ssh server. It should be 22 when interacting with the system's standard ssh server.

cdl_option CYGTST_NET_DROPBEAR_TESTS_CLI_SERVER_PORT {
#    user_value 22
};
CYGTST_NET_DROPBEAR_TESTS_CLI_AF

This should be the address family, AF_INET for IPv4 testing or AF_INET6 for IPv6 testing.

cdl_option CYGTST_NET_DROPBEAR_TESTS_CLI_AF {
#    user_value AF_INET
};
CYGTST_NET_DROPBEAR_TESTS_CLI_USERNAME

This should be the name of the testing account on the ssh server machine.

cdl_option CYGTST_NET_DROPBEAR_TESTS_CLI_USERNAME {
     user_value dropbeartest
};
CYGTST_NET_DROPBEAR_TESTS_CLI_HOST_RSA_KEY_PUB

The public RSA host key for the remote ssh daemon. On a modern debian Linux systems, this can be found in /etc/ssh/ssh_host_rsa_key.pub. Note that the trailing comment, normally the hostname, should be ommitted.

cdl_option CYGTST_NET_DROPBEAR_TESTS_CLI_HOST_RSA_KEY_PUB {
    user_value 1 "ssh-rsa AAAA…rb8="
};
CYGTST_NET_DROPBEAR_TESTS_CLI_HOST_DSA_KEY_PUB

The public DSA host key for the remote ssh daemon. On a modern debian Linux systems, this can be found in /etc/ssh/ssh_host_dsa_key.pub. Note that the trailing comment, normally the hostname, should be ommitted.

cdl_option CYGTST_NET_DROPBEAR_TESTS_CLI_HOST_DSA_KEY_PUB {
    user_value 1 "ssh-dss AAAA…v7s="
};
CYGTST_NET_DROPBEAR_TESTS_CLI_ID_RSA

This value should be the contents of a private RSA key. This can be obtained using the privatekey2c utility as described above in the authentication section. The contents of the C array in the resulting source file can then be copied and pasted into the .ecm or .ecc file.

cdl_option CYGTST_NET_DROPBEAR_TESTS_CLI_ID_RSA {
    user_value 1 " \
    0x00, 0x00, 0x00, 0x07, 0x73, 0x73, 0x68, 0x2d, \
    … \
    0x76, 0xfe, 0x15"
};
CYGTST_NET_DROPBEAR_TESTS_CLI_ID_DSA

This value should be the contents of a private DSA key. This can be obtained using the privatekey2c utility as described above in the authentication section. The contents of the C array in the resulting source file can then be copied and pasted into the .ecm or .ecc file.

cdl_option CYGTST_NET_DROPBEAR_TESTS_CLI_ID_DSA {
    user_value 1 " \
    0x00, 0x00, 0x00, 0x07, 0x73, 0x73, 0x68, 0x2d, \
    … \
    0x1d"
};
CYGTST_NET_DROPBEAR_TESTS_CLI_PASSWORD

This value should be the plain-text password used for authenticating the user.

cdl_option CYGTST_NET_DROPBEAR_TESTS_CLI_PASSWORD {
    user_value 1 opensesame
};

SSH Server Host Configuration

Modern Linux hosts are normally tightly configured with respect to ssh access so as to minimise risk of being compromised. When using an OpenSSH server on Linux as the test host, you may wish to ensure access to the host is not compromised and is restricted. For example, you may only wish to permit access by the dropbear clitest1 and scptest1 tests to the user specified in CYGTST_NET_DROPBEAR_TESTS_CLI_USERNAME from internal network addresses used by the test hardware. For example, if the test hardware were located on the internal IPv4 segment 192.168.8.0/24 and internal IPv6 segment fd54:5cd1:6fc7:1619::/64, the configuration file /etc/ssh/sshd_config could contain the following configuration extract that permits password and DSA autentication for the user dropbeartest from either network:

# Permit dropbear test user to connect from internal test networks and use password authentication
Match User dropbeartest Address 192.168.8.0/24,fd54:5cd1:6fc7:1619::/64
        PasswordAuthentication yes
        PubkeyAcceptedKeyTypes=+ssh-dss

When testing the user account from another Linux server using ssh, the user file ~/.ssh/config also should contain the following line to permit DSA private key authentication as that client host may likely have such authentication prohibited:

PubkeyAcceptedKeyTypes +ssh-dss