📄 emSSL User Guide & Reference Manual
📄 emUSB-Device User Guide & Reference Manual
📄 emUSB-Host User Guide & Reference Manual
📄 emVNC User Guide & Reference Manual
📄 emWeb User Guide & Reference Manual
📄 emWin User Guide & Reference Manual
📄 IoT Toolkit User Guide & Reference Manual
📄 SEGGER Assembler User Guide & Reference Manual
📄 SEGGER Linker User Guide & Reference Manual
📄 SEGGER SystemView User Guide
📄 SEGGER Online Documentation
📄 AppWizard User Guide & Reference Manual
📄 embOS Real-Time Operating System User Guide & Reference Manual
📄 embOS-Ultra Real-Time Operating System User Guide & Reference Manual
📄 emCompress-Embed User Guide & Reference Manual
📄 emCompress-ToGo User Guide & Reference Manual
📄 emCrypt User Guide & Reference Manual
📄 emDropbox User Guide & Reference Manual
📄 emFile User Guide & Reference Manual
📄 emFloat User Guide & Reference Manual
📄 emNet User Guide & Reference Manual
📄 emRun User Guide & Reference Manual
📄 emSecure-ECDSA User Guide & Reference Manual
📄 emSecure-RSA User Guide & Reference Manual
📄 emSSH User Guide & Reference Manual

emSSH User Guide & Reference Manual

Secure Shell.

Introduction to emSSH

This section presents an overview of emSSH, its structure, and its capabilities.

What is emSSH?

emSSH is a software library that enables you to create secure connections between a client and a server, typically over the Internet using TCP/IP.

Although SSH is usually associated with secure connections to a server using TCP/IP, you can run an SSH session over any bidirectional channel, for instance a serial line or wireless link, and provide a secure connection.

emSSH is both hardware independent and transport independent, and integrates seamlessly with embOS/IP. emSSH supports SSH version 2 only.

Design goals

emSSH is designed with the following goals in mind:

We believe all design goals are achieved by emSSH.

Features

emSSH is written in ANSI C and can be used on virtually any CPU. Here is a list of emSSH features:

Package content

emSSH is provided in source code and contains everything required. The following table shows the content of the emSSH package:

Files Description
Application emSSH sample applications for bare metal and embOS.
Config Configuration header files.
CRYPTO Shared cryptographic toolkit source code.
Doc emSSH documentation.
Sample/Config Example emSSH user configuration.
SEGGER SEGGER software component source code used in emSSH.
SSH emSSH implementation source code.

Exploring emSSH

This chapter describes how to try out emSSH on a PC and embedded hardware with minimal effort. We highly recommend that you try out a working version of emSSH, shipped by SEGGER, with a known-good setup, preferably on an emPower board, before attempting to add it to your own application.

Using a PC to try emSSH

emSSH is shipped with a precompiled example that demonstrate a simple SSH shell. You can run the example and connect to the local SSH server on port 22.

C:> SSH_Shell5.exe

emSSH V2.40 - Shell5 compiled Jun  5 2017 11:52:46
(c) 2015-2017 SEGGER Microcontroller GmbH    www.segger.com

Waiting for connection on port 22.
_

When you run this, Windows Firewall will present a dialog asking whether to grant network access to the shell application. Proceed and grant access otherwise you will not be able to log into the shell.

Once the shell is waiting for a connection, you can connect using your preferred client. In this example we will use Tera Term. Run Tera Term and select a new connection to the local PC:

Tera Term New Connection dialog

Be sure to select SSH, not Telnet, as the protocol. You will notice that the server has accepted the connection and is starting the shell:

C:> SSH_Shell5.exe

emSSH V2.40 - Shell5 compiled Jun  5 2017 11:52:46
(c) 2015-2017 SEGGER Microcontroller GmbH    www.segger.com

Waiting for connection on port 22.
Connection accepted, starting shell.
_

The server requires authentication of the user, and Tera Term presents a user authentication dialog. Enter the user name “admin” with password “secret” to log on:

Tera Term Authentication dialog

Tera Term then opens up a terminal window:

Tera Term terminal and shell

From here you can type commands into a virtual command line. There is nothing behind the command line, it only acts as a demonstration of emSSH. To exit the shell, type Ctrl+D, which also causes Tera Term to close.

At the server end, the connection is closed and it waits for a new connection. Type Ctrl+C to close the emSSH server.

C:> SSH_Shell5.exe

emSSH V2.40 - Shell5 compiled Jun  5 2017 11:52:46
(c) 2015-2017 SEGGER Microcontroller GmbH    www.segger.com

Waiting for connection on port 22.
Connection accepted, starting shell.
Connection closed.
Waiting for connection on port 22.
^C
C:> _

Moving to embedded hardware

When starting to run emSSH on embedded hardware, we recommend that you use one of the supplied “Start” projects for your target system to begin with and gain confidence with a working system before progressing to add emSSH to your own application.

The following sections describe this process using SEGGER Embedded Studio, but the principles are the same for any embedded development or workstation environment. The target hardware is an SEGGER emPower board which is supplied with Embedded Studio PRO or available separately from SEGGER and through authorized distributors.

Start Embedded Studio and load the SEGGER emPower start project:

Start project in Embedded Studio

Once you have loaded your start project, you can test it out by choosing Debug > Go which flashes it into your target and starts running it under control of the debugger.

The Debug Terminal will show the configured IP address of the emPower board and you will be able to use Tera Term to log into the SSH server in the same way as the PC application above. If you do not see the Debug Terminal, choose View > Debug Terminal. The terminal output will look something similar to this:

Log output in Debug Terminal

emSSH will then display its configuration and indicate that it’s waiting for a connection:

emSSH initialized and waiting for a connection

At this point you will be able to use Tera Term, or your selected SSH client, to log into the emPower board.

Using emSSH

This section describes how to configure emSSH for use and set up a shell connection using a sample project. The sample project can be customized and integrated into your application.

In this section we assume that you have a fully-functioning embOS/IP project that is able to connect to the network and all that is required is to add emSSH to the project.

Sample applications

emSSH ships with a number of sample applications that demonstrate how to integrate shell capability into your application. Each sample application adds an additional capability to the shell and is a small incremental step from the previous version.

The sample applications are:

Application Description
SSH_Shell1.c Minimal shell application.
SSH_Shell2.c Adds a functional command line.
SSH_Shell3.c Adds user authentication by password.
SSH_Shell4.c Strengthens password storage.
SSH_Shell5.c Displays a warning banner before login.
SSH_Shell6.c Uses an RTOS to service multiple shells.

A note on the samples

Each sample that we present in this section is written in a style that makes it easy to describe and that fits comfortably within the margins of printed paper. Therefore, it may well be that you would rewrite the sample to have a slightly different structure that fits better, but please keep in mind that these examples are written with clarity as the prime objective, and to that end we sacrifice some brevity and efficiency.

Where to find the sample code

All samples are included in the Application directory of the emSSH distribution.

A minimal shell

The first application, SSH_Shell1.c, provides the capability for a user to log into the shell with no authentication. The terminal on the end of the shell does nothing more than echo incoming data from the terminal.

For a complete listing of this application, see SSH_Shell1.c complete listing.

Application entry

The main application task is responsible for setting up the environment ready to accept incoming SSH requests. This is simply boilerplate code that has no configuration:

void MainTask(void) {
  SSH_SESSION * pSession;
  int           BoundSocket;
  int           Socket;
  int           Status;
  //
  SEGGER_SYS_Init();  
  SEGGER_SYS_IP_Init();
  SSH_Init();  
  //
  // Allow "none" user authentication.
  //
  SSH_SERVICE_Add(&SSH_SERVICE_USERAUTH, 0);  
  SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_NONE, _UserauthRequestNone);
  //
  // Add support for interactive shells.
  //
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_SHELL,         NULL);  
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_ENV,           NULL);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_PTYREQ,        _TerminalRequest);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_WINDOW_CHANGE, NULL);

  Initialize system components

The calls to SEGGER_SYS_Init() and SEGGER_SYS_IP_Init() use the SEGGER system abstraction layer to initialize services to the application.

  Initialize SSH component

This initializes the SSH component. The exact details of setup are described separately.

  Configure user authentication

SSH always requires user authentication even in the case where no password is required.

The first call to SSH_SERVICE_Add() prepares emSSH to handle the ssh-userauth service. This service provides user authentication, as you would expect. The second parameter is a callback that is activated when such a service is requested. We don’t need to hook this particular capability now, so we pass in a null function pointer.

The second call to SSH_USERAUTH_METHOD_Add() adds the “none” method to the set of user authentication methods. The none method requires no authentication for a user, that is, no password, no key, nothing: when a user logs in, he is allowed through the front door without question.

We do, however, provide a callback in this case. This at least authenticates that the user name is valid, rather than accepting any user name. The implementation of this callback is described in User authentication.

  Configure shell support

These three function calls to SSH_CHANNEL_REQUEST_Add() set up SSH to accept shell requests. Each SSH session is capable of multiplexing several channels, each carrying different services, over a secure connection. In this case, we wish to accept shell requests, environment setup requests, and terminal requests. If any of these are missing, an SSH shell client will not connect to your embedded host.

The final request setup asks for a callback to be activated when a pseudo-terminal request is received and when a window change event occurs. The implementation of this callback is described in Terminal requests.

Accepting shell sessions

Once emSSH is correctly configured, the application in responsible for accepting connections and setting up any shell session:

//
// Bind SSH port.
//
BoundSocket = SEGGER_SYS_IP_Bind(22);  
if (BoundSocket < 0) {
  _Exit("Cannot bind port 22!");
}
//
for (;;) {
  //
  // Wait for an incoming connection.
  //
  do {  
    Socket = SEGGER_SYS_IP_Accept(BoundSocket);
  } while (Socket < 0);
  //
  SSH_SESSION_Alloc(&pSession);  
  if (pSession == 0) {
    _Exit("No available session!");
  }
  //
  SSH_SESSION_Init(pSession, Socket, &_IP_Transport);  
  SSH_SESSION_ConfBuffers(pSession,
                          _aRxTxBuffer, sizeof(_aRxTxBuffer),
                          _aRxTxBuffer, sizeof(_aRxTxBuffer));
  do {  
    Status = SSH_SESSION_Process(pSession);
  } while (Status >= 0);
}

  Bind the SSH port

When used over TCP/IP, the server normally listens for connections on port 22. This port number has been registered with IANA, and has been officially assigned for SSH. You can, of course, use a different port number but then you would need to specify that port number when using a client: it’s much easier to simply go with the standard port number.

The call to SEGGER_SYS_IP_Bind() uses the SEGGER abstraction layer to bind port 22 and return a socket corresponding to that binding. If the port is already bound and cannot accept incoming connections, the application terminates.

  Accept an incoming connection

Once the port is bound, we listen for incoming connections. The call to SEGGER_SYS_IP_Accept() waits for an incoming connection and creates a socket for that connection.

  Allocate an SSH session for the connection

The call to SSH_SESSION_Alloc() allocates an SSH session and assigns that session to its argument; if no session can be allocated, as they are all in use, a null pointer is assigned. For this simple example we only deal with a single session and therefore we don’t expect allocation to fail, but if it does we exit the application.

  Initialize and configure the session

The calls to SSH_SESSION_Init() and SSH_SESSION_ConfBuffers() configure the session. SSH_SESSION_Init() sets up how the session communicates over the socket and SSH_SESSION_ConfBuffers() sets up the transmission and reception buffers that the SSH connection will use.

SSH_SESSION_Init() is provided a set of function pointers, in a structure, that vector to the appropriate send, receive, and close functions for a socket. In this example, we use the SEGGER abstraction layer to provide socket services:

static const SSH_TRANSPORT_API _IP_Transport = {
  SEGGER_SYS_IP_Send,
  SEGGER_SYS_IP_Recv,
  SEGGER_SYS_IP_Close,
};

These are very thin “shims” to the underlying embOS/IP or Windows socket functions, with the shim providing a consistent function prototype that adapts between the various implementations available.

SSH_SESSION_ConfBuffers() configures the buffers that emSSH will use when receiving and transmitting protocol packets. It’s possible to specify separate reception and transmission buffers but, in this case, we use a shared buffer for both. With care, only a single buffer is required when transporting the protocol and using emSSH, but for more complex situations you may require separate buffers.

Although SSH is typically used over TCP/IP, it is not necessarily the only medium for SSH communications. For example, CANopen specifies a “shell” port that runs a user-defined protocol that could, quite literally, be SSH. In this case, your application could use emSSH to service TCP/IP connections and CAN connections using the same code but with different transport APIs: one for TCP/IP and one for CAN. And, if you wish to secure a serial connection, you could add functions that read and write over (one or more) serial connections.

  Run the SSH state machine

Once the connection is established and configured, you must call SSH_SESSION_Process() to run the SSH state machine that handles the SSH protocol including user authentication and connections. This is called in a loop, processing incoming messages and replying to them, until the session is closed.

The session may close gracefully or abruptly, and when SSH_SESSION_Process() returns with an error, the session is fully closed from an API perspective, and no further calls should be made to the API using that closed channel: doing so leads to undefined behavior.

Once the session is closed, it is returned for reuse. In this example, the code loops back to service additional incoming SSH connections.

User authentication

We now return to user authentication. Recall that user authentication setup provided a callback function:

//
// Allow "none" user authentication.
//
SSH_SERVICE_Add(&SSH_SERVICE_USERAUTH, 0);
SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_NONE, _UserauthRequestNone);

A call is made to the function _UserauthRequestNone when the “none” user authentication method is requested by the client. emSSH calls any associated callback function to further process the authentication request. This is our implementation:

static int _UserauthRequestNone(SSH_SESSION                * pSession,  
                                SSH_USERAUTH_REQUEST_PARAS * pReqParas) {
  SSH_USERAUTH_NONE_PARAS NoneParas;
  int                     Status;
  //
  SSH_USE_PARA(pSession);
  //
  Status = SSH_USERAUTH_NONE_ParseParas(pReqParas, &NoneParas);  
  if (Status < 0) {
    Status = SSH_ERROR_USERAUTH_FAIL;
  } else if (pReqParas->UserNameLen == 4 &&  
             SSH_MEMCMP(pReqParas->pUserName, "anon", 4) == 0) {
    Status = 0;
  } else {
    Status = SSH_ERROR_USERAUTH_FAIL;
  }
  //
  return Status;  
}

  Receive authentication parameters

The callback function is provided with the session on which authentication is requested and the user authentication parameters that are available so far. We do not make use of the session in this case, so ignore it and silence any warning from compilers and source analysis tools using SSH_USE_PARA.

  Parse method parameters

Each user authentication method provides a different set of parameters for authentication. In this case we know that we are processing a none authentication request and, to do so, call SSH_USERAUTH_NONE_ParseParas() passing in the user authentication request. If there is an error in the incoming packet syntax, SSH_USERAUTH_NONE_ParseParas() returns a negative status indicating an error, and we set our running status to a “user authentication fail” error code.

  Authenticate the user

The user authentication request contains a reference to the user name that is the subject of this authentication request. That user name is pointed to by the pUserName member of the SSH_USERAUTH_REQUEST_PARAS structure, and the user name length is provided by UserNameLen in the same structure.

Rather than accept any user name, we accept only one, “anon,” that can log in. The function tests to see whether user names match and, if they do, sets the running status to “success” which is defined as zero. If the user name does not match user authentication fails and the running status is set accordingly.

  Return authentication status

When user authentication is complete, the callback must return a status to emSSH indicating whether the user is authenticated or not. A zero or positive return value indicates that the user is authenticated and emSSH can continue with the session and send the peer a “success” message. A negative return value indicates that the user is not authenticated and emSSH sends the peer a “failure” message. At this point neither the session nor the channel are closed: the peer will handle the failure at its end and may decide to close the channel or session as it sees fit.

This completes the “none” user authentication scheme.

Terminal requests

We now return to pseudo-terminal requests. Recall that we provided a a callback to activate when a pseudo-terminal request is received:

//
// Add support for interactive shells.
//
SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_SHELL,         NULL);
SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_ENV,           NULL);
SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_PTYREQ,        _TerminalRequest);
SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_WINDOW_CHANGE, NULL);

A call is made to the function _TerminalRequest when the client requests a pseudo-terminal. Typical embedded targets do not support pseudo-terminals in the classic Unix model, but nevertheless we can emulate them such that SSH runs on embedded hosts. This is our implementation:

static int _TerminalRequest(SSH_SESSION               * pSession,  
                            unsigned                    Channel,
                            SSH_CHANNEL_REQUEST_PARAS * pParas) {
  int Status;
  //
  SSH_CHANNEL_Config(pSession, Channel, 128, &_TerminalAPI, 0);  
  if (pParas->WantReply) {  
    Status = SSH_CHANNEL_SendSuccess(pSession, Channel);
  } else {
    Status = 0;
  }
  //
  return Status;  
}

This is a very basic implementation of the terminal setup request and does not attempt to do anything more than configure a channel for terminal data.

  Receive terminal request parameters

The callback function is provided with the session on which the request is made (pSession), the channel number of that request (Channel), and specific per-request parameters (pParas). All three incoming parameters are relevant when setting up a terminal request.

  Configure the channel

Each terminal request is associated with a specific channel. The channel number is a small integer and identifies the local channel that is being addressed. We do not need to be concerned with the local channel number and how it is encoded or what it relates to—this is managed within the core of emSSH.

Each channel is bidirectional: it can receive data and data can be sent to it. In the case of a pseudo-terminal, we are transporting input and output that is human-readable.

The channel is configured by calling SSH_CHANNEL_Config(). The parameters are:

The first two parameters passed to SSH_CHANNEL_Config() are the incoming parameters in our callback: they identify the channel to configure.

The next parameter is the window size of the SSH channel for receiving data form the client. This window size is rather like the window size of TCP/IP connections and it enables connections to optimize resource use and bandwidth at a particular end of the bidirectional connection. (Each end is responsible for setting up its own window, so resource use at each end is individually configurable and independent of the other.)

For low-bandwidth connections, such as channels that carry interactive data from terminals, there is no real advantage to specifying a large window: the window is unlikely to become full (and therefore closed to any data transmission). For high bandwidth connections, such as secure copy or secure file transfer, a large window size is preferred to optimize efficient bulk data transfer over the connection.

In this case we have set a small window size of 128 bytes. This means that no more than 128 bytes of data will be presented to the client at any one time. This has some very real advantages for embedded systems and is covered more deeply in following sections.

The fourth parameter is a pointer to an API structure that contains functions to call when events occur on the channel. In our case we pass in a pointer to the following filled-in structure:

static const SSH_CHANNEL_API _TerminalAPI = {
  _TerminalChannelData,
  0,
  0
};

The specifics of channel data are described in details in the sections that follow, but briefly the first member of this structure is the function to call (_TerminalChannelData) when data is received on the channel.

The final parameter is a user context to associate with the channel. A client can associate any pointer with the context and the user context of a channel can be retrieved by SSH_CHANNEL_QueryUserContext().

  Acknowledge configuration

The final responsibility of the callback is to acknowledge whether the channel was correctly configured or not. emSSH does not do this for you automatically on return because a callback may wish to acknowledge that a channel is correctly configured and then, immediately, perform some action on that channel (such as send data).

Whether the peer has requested an acknowledgment or not is indicated by the WantReply member of the incoming channel request parameters, a pointer to a SSH_CHANNEL_REQUEST_PARAS structure. If the peer has requested a reply, the callback must either call SSH_CHANNEL_SendSuccess() or SSH_CHANNEL_SendFailure() to send the peer the appropriate completion status.

If the peer does not wish to receive an acknowledgment, the callback need not send any reply—but, it is also not precluded from sending a failure even if the peer requested no explicit reply. If there is no need to send a reply, the running status is set to “success”.

  Return configuration status

When configuration is complete, the callback must return a status to emSSH indicating whether the channel is correctly configured or not. If the channel is not correctly configured, the session is closed with the error propagated to the peer, and to the client by returning an error through SSH_SESSION_Process().

Channel callbacks

As part of channel configuration the shell application provided a set of callbacks to handle events on a channel:

SSH_CHANNEL_Config(pSession, Channel, 128, &_TerminalAPI, 0);

We now turn our attention to the callback that deals with data reception on a channel:

static int _TerminalChannelData(      SSH_SESSION * pSession,  
                                      unsigned      Channel,
                                const U8          * pData,
                                      unsigned      DataLen) {
  int Status;
  //
  Status = SSH_CHANNEL_SendData(pSession, Channel, pData, DataLen);  
  //
  return Status;  
}

Whenever the peer sends data to a channel, that data is presented to the client using the pfOnChannelData callback of the channel API.

  Receive data callback parameters

The parameters provided to the channel data callback are:

  Process incoming data

In this simple application, we do no more than echo the received data back to the peer using SSH_CHANNEL_SendData(). The result of calling SSH_CHANNEL_SendData() is nonnegative on success and negative on failure.

What does this mean for a client that connects to a server running this code? It means that anything the user types is simply echoed back: it shows that a secure connection is established between the two peers and that the data can go through a round trip without corruption.

  Return reception status

When reception is complete, the callback must return a status to emSSH indicating whether the received data were handled correctly or not: nonnegative for success and negative for failure.

If there was an error processing the received data (in this case, if there was an error echoing back to the channel), the session is closed with the error propagated to the peer, and to the client by returning an error through SSH_SESSION_Process().

Testing the application

Now that this application is assembled, it’s possible to test it out using an SSH client. In this case we choose to use Mac OS X on a Macintosh and select the standard ssh that comes from the OpenSSH project. The OpenSSH command line to log into a server is:

ssh username@ip-address-or-domain-name

We configured this particular application to only authenticate a single user successfully, the user name “anon”. Trying to gain access using some other user name should fail:

MacBook:~ paul$ ssh somebody@10.0.0.247
Permission denied (none).
MacBook:~ paul$ _

This is correct behavior: when logging in with the user name somebody, we were denied access.

Now let’s try our good user name:

MacBook:~ paul$ ssh anon@10.0.0.247
_

We’re in! Typing some text echoes it to the terminal, as we expect:

MacBook:~ paul$ ssh anon@10.0.0.247
Hello!_

To close the connection from the client requires a specific key sequence. Press the Return key, followed by twiddle (~) followed by period (.). If you don’t hit the keys in this order, the connection will remain open.

MacBook:~ paul$ ssh anon@10.0.0.247
Hello!
Connection to 10.0.0.247 closed.
MacBook:~ paul$ _

This concludes the simple presentation of emSSH; the following sections will elaborate on this and provide additional capabilities.

SSH_Shell1.c complete listing

/*********************************************************************
*                   (c) SEGGER Microcontroller GmbH                  *
*                        The Embedded Experts                        *
*                           www.segger.com                           *
**********************************************************************

-------------------------- END-OF-HEADER -----------------------------

File        : SSH_Shell1.c
Purpose     : Simplest SSH server that accepts incoming connections.

*/

/*********************************************************************
*
*       #include Section
*
**********************************************************************
*/

#include "SSH.h"
#include "SEGGER_SYS.h"
#include <string.h>

/*********************************************************************
*
*       Prototypes
*
**********************************************************************
*/

void MainTask(void);
static int _TerminalChannelData(      SSH_SESSION * pSession,
                                      unsigned      Channel,
                                const U8          * pData,
                                      unsigned      DataLen);

/*********************************************************************
*
*       Static const data
*
**********************************************************************
*/

static const SSH_TRANSPORT_API _IP_Transport = {
  SEGGER_SYS_IP_Send,
  SEGGER_SYS_IP_Recv,
  SEGGER_SYS_IP_Close,
};

static const SSH_CHANNEL_API _TerminalAPI = {
  _TerminalChannelData,
  NULL,
  NULL,
  NULL
};

/*********************************************************************
*
*       Static data
*
**********************************************************************
*/

static U8 _aRxTxBuffer[8192];

/*********************************************************************
*
*       Static code
*
**********************************************************************
*/

/*********************************************************************
*
*       _AppExit()
*
*  Function description
*    Exit the application with an error.
*
*  Parameters
*    sReason - Reason for exit, displayed for the user.
*/
static void _AppExit(const char *sReason) {
  SEGGER_SYS_IO_Printf(sReason);
  SEGGER_SYS_OS_Halt(100);
}

/*********************************************************************
*
*       _TerminalChannelData()
*
*  Function description
*    Handle data received from peer.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel receiving the data.
*    pData    - Pointer to object that contains the data.
*    DataLen  - Octet length of the object that contains the data.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*
*  Additional information
*    Simply echo received data.
*/
static int _TerminalChannelData(      SSH_SESSION * pSession,
                                      unsigned      Channel,
                                const U8          * pData,
                                      unsigned      DataLen) {
  int Status;
  //
  Status = SSH_CHANNEL_SendData(pSession, Channel, pData, DataLen);
  //
  return Status;
}

/*********************************************************************
*
*       _TerminalRequest()
*
*  Function description
*    Request a terminal.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel requesting the terminal.
*    pParas   - Pointer to channel request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _TerminalRequest(SSH_SESSION               * pSession,
                            unsigned                    Channel,
                            SSH_CHANNEL_REQUEST_PARAS * pParas) {
  int Status;
  //
  SSH_CHANNEL_Config(pSession, Channel, 128, &_TerminalAPI, NULL);
  if (pParas->WantReply) {
    Status = SSH_CHANNEL_SendSuccess(pSession, Channel);
  } else {
    Status = 0;
  }
  //
  return Status;
}

/*********************************************************************
*
*       _UserauthRequestNone()
*
*  Function description
*    Request authentication of user with method "none".
*
*  Parameters
*    pSession  - Pointer to session.
*    pReqParas - Pointer to user authentication request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _UserauthRequestNone(SSH_SESSION                * pSession,
                                SSH_USERAUTH_REQUEST_PARAS * pReqParas) {
  SSH_USERAUTH_NONE_PARAS NoneParas;
  int                     Status;
  //
  SSH_USE_PARA(pSession);
  //
  Status = SSH_USERAUTH_NONE_ParseParas(pReqParas, &NoneParas);
  if (Status < 0) {
    Status = SSH_ERROR_USERAUTH_FAIL;
  } else if (pReqParas->UserNameLen == 4 &&
             SSH_MEMCMP(pReqParas->pUserName, "anon", 4) == 0) {
    Status = 0;
  } else {
    Status = SSH_ERROR_USERAUTH_FAIL;
  }
  //
  return Status;
}

/*********************************************************************
*
*             Public code
*
**********************************************************************
*/

/*********************************************************************
*
*       MainTask()
*
*  Function description
*    Application entry point.
*/
void MainTask(void) {
  SSH_SESSION * pSession;
  int           BoundSocket;
  int           Socket;
  int           Status;
  //
  SEGGER_SYS_Init();
  SEGGER_SYS_IP_Init();
  SSH_Init();
  //
  SEGGER_SYS_IO_Printf("\nemSSH V%s - Shell1 compiled " __DATE__ " " __TIME__ "\n",
                       SSH_GetVersionText());
  SEGGER_SYS_IO_Printf("%s    www.segger.com\n\n",
                       SSH_GetCopyrightText());
  //
  // Allow "none" user authentication.
  //
  SSH_SERVICE_Add(&SSH_SERVICE_USERAUTH, NULL);
  SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_NONE, _UserauthRequestNone);
  //
  // Add support for interactive shells.
  //
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_SHELL,         NULL);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_ENV,           NULL);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_PTYREQ,        _TerminalRequest);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_WINDOW_CHANGE, NULL);
  //
  // Bind SSH port.
  //
  BoundSocket = SEGGER_SYS_IP_Bind(22);
  if (BoundSocket < 0) {
    _AppExit("Cannot bind port 22!");
  }
  //
  for (;;) {
    //
    // Wait for an incoming connection.
    //
    do {
      Socket = SEGGER_SYS_IP_Accept(BoundSocket);
    } while (Socket < 0);
    //
    SSH_SESSION_Alloc(&pSession);
    if (pSession == 0) {
      _AppExit("No available session!");
    }
    //
    SSH_SESSION_Init(pSession, Socket, &_IP_Transport);
    SSH_SESSION_ConfBuffers(pSession,
                            _aRxTxBuffer, sizeof(_aRxTxBuffer),
                            _aRxTxBuffer, sizeof(_aRxTxBuffer));
    do {
      Status = SSH_SESSION_Process(pSession);
    } while (Status >= 0);
  }
}

/*************************** End of file ****************************/

Adding a command line

The first application, SSH_Shell1.c, provides a framework that we will now extend with a functional command line. The command line can has no processing behind it, it just gathers input from the user and, when complete, echoes the command line back.

For a complete listing of this application, see SSH_Shell2.c complete listing.

Supplying a sign-on

When we log into a system using SSH one of the first things to be presented is a welcome message. We can provide our own welcome message by hooking the shell request and, when we receive it, write a welcome message. And all systems supply a command prompt, so we define the sign-on message and command prompt we will use:

#define PROMPT                                                   \
  "emSSH> "

#define SIGNON                                                   \
  "\r\n"                                                         \
  "Welcome to the emSSH command line!  Type Ctrl+D to exit.\r\n" \
  "\r\n"                                                         \
  PROMPT

Note that the carriage return and line feed are made explicit in these strings: emSSH does not expand the single C newline character ’\n’ into a carriage-return linefeed pair on transmission.

We modify the startup code to hook the shell request and direct it to our callback:

//
// Add support for interactive shells.
//
SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_SHELL,         _ShellRequest);
SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_ENV,           NULL);
SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_PTYREQ,        _TerminalRequest);
SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_WINDOW_CHANGE, NULL);

With all that in place, we proceed to code the callback function:

static int _ShellRequest(SSH_SESSION               * pSession,  
                         unsigned                    Channel,
                         SSH_CHANNEL_REQUEST_PARAS * pParas) {
  int Status;
  //
  Status = SSH_CHANNEL_SendData(pSession, Channel,
                                SIGNON, strlen(SIGNON));  
  if (Status < 0) {  
    Status = SSH_CHANNEL_SendFailure(pSession, Channel);
  } else if (pParas->WantReply) {  
    Status = SSH_CHANNEL_SendSuccess(pSession, Channel);
  }
  //
  return Status;  
}

This request framework follows the same form as the pseudo-terminal request we’ve seen before.

  Receive shell request parameters

The callback function is provided with the session on which the request is made (pSession), the channel number of that request (Channel), and specific per-request parameters (pParas).

  Send sign-on

Immediately the shell request is received, the callback sends the sign-on message and initial prompt to the peer using SSH_CHANNEL_SendData(). If there’s an error sending the data to the peer, it’s reflected in the value returned by SSH_CHANNEL_SendData(): negative for failure, nonnegative for success.

  Send failure completion status

The final responsibility of the callback is to acknowledge whether the channel request completed successfully or not, as with the terminal request. If sending the channel data failed, it’s likely that sending a failure response will also fail, but we try sending it anyway.

  Send optional completion status

If there is no error sending the channel data, we send the optional success response if the peer has requested it; we maintain the transmission status of the success response so that we can return it from the callback.

  Return shell request status

When the shell request is complete, the callback must return a status to emSSH indicating whether it executed correctly or not. If there was an error processing the shell request, the session is closed with the error propagated to the peer, and to the client by returning an error through SSH_SESSION_Process().

Processing incoming data

We are going to write the command line processor within the received data callback. As you will see later, this is not the recommended way of writing a command line processor, but it is expedient if you need only a simple command line processor.

We declare some static variables at file scope to manage the command line:

static U8       _aCommandLine[70];
static unsigned _Cursor;

This is sufficient because we support only one shell session so far.

The code to implement this single command line is:

static int _TerminalChannelData(      SSH_SESSION * pSession,  
                                      unsigned      Channel,
                                const U8          * pData,
                                      unsigned      DataLen) {
  unsigned i;
  U8       Ch;
  int      Status;
  //
  Status = 0;
  //
  for (i = 0; Status >= 0 && i < DataLen; ++i) {  
    Ch = pData[i];
    if (0x20 <= Ch && Ch <= 0x7E) {  
      if (_Cursor < sizeof(_aCommandLine)) {
        _aCommandLine[_Cursor++] = Ch;
        Status = SSH_CHANNEL_SendData(pSession, Channel, &Ch, 1);
      }
    } else if (Ch == 0x08 || Ch == 0x7F) {  
      if (_Cursor > 0) {
        --_Cursor;
        Status = SSH_CHANNEL_SendData(pSession, Channel, "\b \b", 3);
      }
    } else if (Ch == '\r') {  
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n...", 5);
      SSH_CHANNEL_SendData(pSession, Channel, _aCommandLine, _Cursor);
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n", 3);
      Status = SSH_CHANNEL_SendData(pSession, Channel, PROMPT, strlen(PROMPT));
      _Cursor = 0;
    } else if (Ch == 0x04) {  
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n\r\nBye!\r\n\r\n", 12);
      SSH_CHANNEL_Close(pSession, Channel);
      break;
    }
  }
  //
  return Status;  
}

The callback is more complex now, but we can break it down into its constituent parts:

  Receive data callback parameters

This is identical to the previous example.

  Process incoming data

The loop iterates over all data presented to the callback, but exits if any iteration raises an error condition.

  Process printable characters

On reception of a printable character, the character is added to the command line buffer if there is space and echoed to the user. If there is no space, the character is simply dropped and not echoed which provides a hard-stop indication to the user that there is no more space.

An alternative is to echo back an alert, or bell when the buffer is full, which can be coded like this:

if (_Cursor < sizeof(_aCommandLine)) {
  _aCommandLine[_Cursor++] = Ch;
} else {
  Ch = '\a';
}
Status = SSH_CHANNEL_SendData(pSession, Channel, &Ch, 1);

  Process backspace and delete

Processing a backspace is straightforward: only backspace if not at the start of the buffer, and erase the character before the cursor on the terminal by backspacing, replacing with a space, and backspacing again.

  Process enter

When an enter character is found, the command processor echoes the received command to the terminal, prints a new command prompt, and resets the incoming buffer cursor to receive a new command.

One or more of the first three calls to SSH_CHANNEL_SendData() may well fail, but if one fails then all subsequent data write requests to that channel will fail, so it is only necessary to store the result of the final data write request.

When a data write request fails, the session is not closed by emSSH and remains open. In this case, when a data write fails, the loop terminates and the error status is returned to emSSH which then proceeds to close the session.

  Process log out

For those familiar with any Unix-type system, the universal Ctrl+D “end of file” entered in a shell causes the shell to terminate and log out. In this example, when Ctrl+D is seen we start to close the connection and exit the loop, but as a nicety the command processor sends a final log-out message before closing down.

When closing the connection like this, it doesn’t matter what status code is returned by the callback, the session is already dead and buried.

  Return reception status

This should now be a familiar idiom: we return whether we processed all channel data successfully or not.

Try out the new command processor

Testing the application is the same as the previous example:

MacBook:~ paul$ ssh anon@10.0.0.247

Welcome to the emSSH command line!  Type Ctrl+D to exit.

emSSH> fsck /
...fsck /
emSSH> cd Work
...cd Work
emSSH> 

Bye!

Connection to 10.0.0.247 closed.
MacBook:~ paul$ _

This concludes the section on adding a simple command line processor to emSSH.

SSH_Shell2.c complete listing

/*********************************************************************
*                   (c) SEGGER Microcontroller GmbH                  *
*                        The Embedded Experts                        *
*                           www.segger.com                           *
**********************************************************************

-------------------------- END-OF-HEADER -----------------------------

File        : SSH_Shell2.c
Purpose     : SSH server that supports a command line.

*/

/*********************************************************************
*
*       #include Section
*
**********************************************************************
*/

#include "SSH.h"
#include "SEGGER_SYS.h"
#include <string.h>

/*********************************************************************
*
*       Defines, configurable
*
**********************************************************************
*/

#define PROMPT                                                   \
  "emSSH> "

#define SIGNON                                                   \
  "\r\n"                                                         \
  "Welcome to the emSSH command line!  Type Ctrl+D to exit.\r\n" \
  "\r\n"                                                         \
  PROMPT

/*********************************************************************
*
*       Prototypes
*
**********************************************************************
*/

void MainTask(void);
static int _TerminalChannelData(      SSH_SESSION * pSession,
                                      unsigned      Channel,
                                const U8          * pData,
                                      unsigned      DataLen);

/*********************************************************************
*
*       Static const data
*
**********************************************************************
*/

static const SSH_TRANSPORT_API _IP_Transport = {
  SEGGER_SYS_IP_Send,
  SEGGER_SYS_IP_Recv,
  SEGGER_SYS_IP_Close,
};

static const SSH_CHANNEL_API _TerminalAPI = {
  _TerminalChannelData,
  NULL,
  NULL,
  NULL
};

/*********************************************************************
*
*       Static data
*
**********************************************************************
*/

static U8       _aRxTxBuffer[8192];
static U8       _aCommandLine[70];
static unsigned _Cursor;

/*********************************************************************
*
*       Static code
*
**********************************************************************
*/

/*********************************************************************
*
*       _AppExit()
*
*  Function description
*    Exit the application with an error.
*
*  Parameters
*    sReason - Reason for exit, displayed for the user.
*/
static void _AppExit(const char *sReason) {
  SEGGER_SYS_IO_Printf(sReason);
  SEGGER_SYS_OS_Halt(100);
}

/*********************************************************************
*
*       _ShellRequest()
*
*  Function description
*    Handle a shell channel request.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel receiving the data.
*    pParas   - Pointer to channel request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _ShellRequest(SSH_SESSION               * pSession,
                         unsigned                    Channel,
                         SSH_CHANNEL_REQUEST_PARAS * pParas) {
  int Status;
  //
  Status = SSH_CHANNEL_SendData(pSession, Channel,
                                SIGNON, (unsigned)strlen(SIGNON));
  if (Status < 0) {
    Status = SSH_CHANNEL_SendFailure(pSession, Channel);
  } else if (pParas->WantReply) {
    Status = SSH_CHANNEL_SendSuccess(pSession, Channel);
  }
  //
  return Status;
}

/*********************************************************************
*
*       _TerminalChannelData()
*
*  Function description
*    Handle data received from peer.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel receiving the data.
*    pData    - Pointer to object that contains the data.
*    DataLen  - Octet length of the object that contains the data.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*
*  Additional information
*    Provide in-callback handling of a command line processor.
*    This sample supports only one connection at a time.
*/
static int _TerminalChannelData(      SSH_SESSION * pSession,
                                      unsigned      Channel,
                                const U8          * pData,
                                      unsigned      DataLen) {
  unsigned i;
  U8       Ch;
  int      Status;
  //
  Status = 0;
  //
  for (i = 0; Status >= 0 && i < DataLen; ++i) {
    Ch = pData[i];
    if (0x20 <= Ch && Ch <= 0x7E) {
      if (_Cursor < sizeof(_aCommandLine)) {
        _aCommandLine[_Cursor++] = Ch;
        Status = SSH_CHANNEL_SendData(pSession, Channel, &Ch, 1);
      }
    } else if (Ch == 0x08 || Ch == 0x7F) {
      if (_Cursor > 0) {
        --_Cursor;
        Status = SSH_CHANNEL_SendData(pSession, Channel, "\b \b", 3);
      }
    } else if (Ch == '\r') {
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n...", 5);
      SSH_CHANNEL_SendData(pSession, Channel, _aCommandLine, _Cursor);
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n", 3);
      Status = SSH_CHANNEL_SendData(pSession, Channel, PROMPT, (unsigned)strlen(PROMPT));
      _Cursor = 0;
    } else if (Ch == 0x04) {
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n\r\nBye!\r\n\r\n", 12);
      SSH_CHANNEL_Close(pSession, Channel);
      break;
    }
  }
  //
  return Status;
}

/*********************************************************************
*
*       _TerminalRequest()
*
*  Function description
*    Request a terminal.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel requesting the terminal.
*    pParas   - Pointer to channel request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _TerminalRequest(SSH_SESSION               * pSession,
                            unsigned                    Channel,
                            SSH_CHANNEL_REQUEST_PARAS * pParas) {
  int Status;
  //
  SSH_CHANNEL_Config(pSession, Channel, 128, &_TerminalAPI, 0);
  if (pParas->WantReply) {
    Status = SSH_CHANNEL_SendSuccess(pSession, Channel);
  } else {
    Status = 0;
  }
  //
  return Status;
}

/*********************************************************************
*
*       _UserauthRequestNone()
*
*  Function description
*    Request authentication of user with method "none".
*
*  Parameters
*    pSession  - Pointer to session.
*    pReqParas - Pointer to user authentication request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _UserauthRequestNone(SSH_SESSION                * pSession,
                                SSH_USERAUTH_REQUEST_PARAS * pReqParas) {
  SSH_USERAUTH_NONE_PARAS NoneParas;
  int                     Status;
  //
  SSH_USE_PARA(pSession);
  //
  Status = SSH_USERAUTH_NONE_ParseParas(pReqParas, &NoneParas);
  if (Status < 0) {
    Status = SSH_ERROR_USERAUTH_FAIL;
  } else if (pReqParas->UserNameLen == 4 &&
             SSH_MEMCMP(pReqParas->pUserName, "anon", 4) == 0) {
    Status = 0;
  } else {
    Status = SSH_ERROR_USERAUTH_FAIL;
  }
  //
  return Status;
}

/*********************************************************************
*
*             Public code
*
**********************************************************************
*/

/*********************************************************************
*
*       MainTask()
*
*  Function description
*    Application entry point.
*/
void MainTask(void) {
  SSH_SESSION * pSession;
  int           BoundSocket;
  int           Socket;
  int           Status;
  //
  SEGGER_SYS_Init();
  SEGGER_SYS_IP_Init();
  SSH_Init();
  //
  SEGGER_SYS_IO_Printf("\nemSSH V%s - Shell2 compiled " __DATE__ " " __TIME__ "\n",
                       SSH_GetVersionText());
  SEGGER_SYS_IO_Printf("%s    www.segger.com\n\n",
                       SSH_GetCopyrightText());
  //
  // Allow "none" user authentication.
  //
  SSH_SERVICE_Add(&SSH_SERVICE_USERAUTH, NULL);
  SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_NONE, _UserauthRequestNone);
  //
  // Add support for interactive shells.
  //
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_SHELL,         _ShellRequest);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_ENV,           NULL);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_PTYREQ,        _TerminalRequest);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_WINDOW_CHANGE, NULL);
  //
  // Bind SSH port.
  //
  BoundSocket = SEGGER_SYS_IP_Bind(22);
  if (BoundSocket < 0) {
    _AppExit("Cannot bind port 22!");
  }
  //
  for (;;) {
    //
    // Wait for an incoming connection.
    //
    do {
      Socket = SEGGER_SYS_IP_Accept(BoundSocket);
    } while (Socket < 0);
    //
    SSH_SESSION_Alloc(&pSession);
    if (pSession == 0) {
      _AppExit("No available session!");
    }
    //
    SSH_SESSION_Init(pSession, Socket, &_IP_Transport);
    SSH_SESSION_ConfBuffers(pSession,
                            _aRxTxBuffer, sizeof(_aRxTxBuffer),
                            _aRxTxBuffer, sizeof(_aRxTxBuffer));
    do {
      Status = SSH_SESSION_Process(pSession);
    } while (Status >= 0);
  }
}

/*************************** End of file ****************************/

Users with passwords

Our security for log in is very weak: supplying only the correct user name allows a login. In this section the security is enhanced by supporting password authentication where a user must supply the correct password to log in.

For a complete listing of this application, see SSH_Shell3.c complete listing.

Adding a second user authentication method

Previous samples have added only one user authentication method to emSSH, the none method. This method is useful for guest access, if you need it, where the services offered through the guest login is restricted.

To enhance this with a login protocol that requires user name and password, a second protocol is added when the application is entered:

//
// Allow "none" and "password" user authentication.
//
SSH_SERVICE_Add(&SSH_SERVICE_USERAUTH, 0);
SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_NONE,     _UserauthRequestNone);
SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_PASSWORD, _UserauthRequestPassword);

The second user authentication method uses a different callback that processes login requests with a password. That callback implementation is:

static int _UserauthRequestPassword(SSH_SESSION                * pSession,  
                                    SSH_USERAUTH_REQUEST_PARAS * pReqParas) {
  SSH_USERAUTH_PASSWORD_PARAS PasswordParas;
  int                         Status;
  //
  SSH_USE_PARA(pSession);
  //
  Status = SSH_USERAUTH_PASSWORD_ParseParas(pReqParas, &PasswordParas);  
  if (Status < 0) {
    Status = SSH_ERROR_USERAUTH_FAIL;
  } else if (pReqParas->UserNameLen == 5 &&  
             memcmp(pReqParas->pUserName, "admin", 5) == 0) {
    if (PasswordParas.PasswordLen == 6 &&  
        memcmp(PasswordParas.pPassword, "secret", 6) == 0) {
      Status = 0;
    } else {
      Status = SSH_ERROR_USERAUTH_FAIL;
    }
  } else {
    Status = SSH_ERROR_USERAUTH_FAIL;  
  }
  //
  return Status;
}

This implementation adds one password-authenticated user.

  Recieve user authentication parameters

This is the same as the none user authentication method.

  Parse the password parameters

As each user authentication method has a different packet format containing format-specific parameters, the callback is responsible for decoding the authentication parameters with an appropriate decoding function. For password authentication, the user name is provided in the SSH_USERAUTH_REQUEST_PARAS structure pointed to by pReqParas just as the none method, but the password is stored in (as-yet) undecoded parameters that need parsing.

For password authentication, the SSH_USERAUTH_PASSWORD_ParseParas() function parses the parameters and ensures that the packet format is correct. If the packet format is correct, user authentication can proceed.

  Check the user name

The user name is checked in the same manner as none authentication. We accept only one password-authenticated user, the user “admin”.

  Check the user’s password

If the user name matches admin, the password parsed from the user authentication password parameters is checked for a match with the store password, “secret”. If there is a match, the user is authenticated and the running status is set to zero to reflect a successful authentication, and if not, it’s set to an authentication failure status.

This is a particularly weak implementation of password authentication as all user names and passwords are held in the clear and will be readable in the clear in the flash of the target microprocessor or—worse still—in the PC application if emSSH is compiled for a PC. There are better ways of storing passwords, but this sample shows only the mechanics of verifying a user’s password in the context of the SSH password authentication method, not in the context of “best practice.” We show how this can be achieved in the next sample.

  Reject other users

The if-else-if structure can be extended to allow additional users, of course, but after all user names are exhausted, authentication by password must fail.

Testing password log in

Testing the application now requires that we use the user name admin rather than anon.

Correct password

MacBook:~ paul$ ssh admin@10.0.0.247

admin@10.0.0.247's password: secret

Welcome to the emSSH command line!  Type Ctrl+D to exit.

emSSH> ls -l
...ls -l
emSSH> 

Bye!

Connection to 10.0.0.247 closed.
MacBook:~ paul$ _

Incorrect password

MacBook:~ paul$ ssh admin@10.0.0.247

admin@10.0.0.247's password: 123456
Permission denied, please try again.
admin@10.0.0.247's password: password
Permission denied, please try again.
admin@10.0.0.247's password: qwerty
Permission denied (none,password).
MacBook:~ paul$ _

Unknown user

MacBook:~ paul$ ssh starbuck@10.0.0.247

starbuck@10.0.0.247's password: kara
Permission denied, please try again.
starbuck@10.0.0.247's password: galactica
Permission denied, please try again.
starbuck@10.0.0.247's password: adama
Permission denied (none,password).
MacBook:~ paul$ _

Restricting guest access

This sample has both the none user authentication method and the password user authentication method installed. If all users are required to hold a password, and guest access without password must be denied, modify the code to add only the password authentication method:

//
// Allow only "password" user authentication.
//
SSH_SERVICE_Add(&SSH_SERVICE_USERAUTH, 0);
SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_PASSWORD, _UserauthRequestPassword);

SSH_Shell3.c complete listing

/*********************************************************************
*                   (c) SEGGER Microcontroller GmbH                  *
*                        The Embedded Experts                        *
*                           www.segger.com                           *
**********************************************************************

-------------------------- END-OF-HEADER -----------------------------

File        : SSH_Shell3.c
Purpose     : SSH server that adds password user authentication.

*/

/*********************************************************************
*
*       #include Section
*
**********************************************************************
*/

#include "SSH.h"
#include "SEGGER_SYS.h"
#include <string.h>

/*********************************************************************
*
*       Defines, configurable
*
**********************************************************************
*/

#define PROMPT                                                   \
  "emSSH> "

#define SIGNON                                                   \
  "\r\n"                                                         \
  "Welcome to the emSSH command line!  Type Ctrl+D to exit.\r\n" \
  "\r\n"                                                         \
  PROMPT

/*********************************************************************
*
*       Prototypes
*
**********************************************************************
*/

void MainTask(void);
static int _TerminalChannelData(      SSH_SESSION * pSession,
                                      unsigned      Channel,
                                const U8          * pData,
                                      unsigned      DataLen);

/*********************************************************************
*
*       Static const data
*
**********************************************************************
*/

static const SSH_TRANSPORT_API _IP_Transport = {
  SEGGER_SYS_IP_Send,
  SEGGER_SYS_IP_Recv,
  SEGGER_SYS_IP_Close,
};

static const SSH_CHANNEL_API _TerminalAPI = {
  _TerminalChannelData,
  NULL,
  NULL,
  NULL
};

/*********************************************************************
*
*       Static data
*
**********************************************************************
*/

static U8       _aRxTxBuffer[8192];
static U8       _aCommandLine[70];
static unsigned _Cursor;

/*********************************************************************
*
*       Static code
*
**********************************************************************
*/

/*********************************************************************
*
*       _AppExit()
*
*  Function description
*    Exit the application with an error.
*
*  Parameters
*    sReason - Reason for exit, displayed for the user.
*/
static void _AppExit(const char *sReason) {
  SEGGER_SYS_IO_Printf(sReason);
  SEGGER_SYS_OS_Halt(100);
}

/*********************************************************************
*
*       _ShellRequest()
*
*  Function description
*    Handle a shell channel request.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel receiving the data.
*    pParas   - Pointer to channel request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _ShellRequest(SSH_SESSION               * pSession,
                         unsigned                    Channel,
                         SSH_CHANNEL_REQUEST_PARAS * pParas) {
  int Status;
  //
  Status = SSH_CHANNEL_SendData(pSession, Channel,
                                SIGNON, (unsigned)strlen(SIGNON));
  if (Status < 0) {
    Status = SSH_CHANNEL_SendFailure(pSession, Channel);
  } else if (pParas->WantReply) {
    Status = SSH_CHANNEL_SendSuccess(pSession, Channel);
  }
  //
  return Status;
}

/*********************************************************************
*
*       _TerminalChannelData()
*
*  Function description
*    Handle data received from peer.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel receiving the data.
*    pData    - Pointer to object that contains the data.
*    DataLen  - Octet length of the object that contains the data.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*
*  Additional information
*    Provide in-callback handling of a command line processor.
*    This sample supports only one connection at a time.
*/
static int _TerminalChannelData(      SSH_SESSION * pSession,
                                      unsigned      Channel,
                                const U8          * pData,
                                      unsigned      DataLen) {
  unsigned i;
  U8       Ch;
  int      Status;
  //
  Status = 0;
  //
  for (i = 0; Status >= 0 && i < DataLen; ++i) {
    Ch = pData[i];
    if (0x20 <= Ch && Ch <= 0x7E) {
      if (_Cursor < sizeof(_aCommandLine)) {
        _aCommandLine[_Cursor++] = Ch;
        Status = SSH_CHANNEL_SendData(pSession, Channel, &Ch, 1);
      }
    } else if (Ch == 0x08 || Ch == 0x7F) {
      if (_Cursor > 0) {
        --_Cursor;
        Status = SSH_CHANNEL_SendData(pSession, Channel, "\b \b", 3);
      }
    } else if (Ch == '\r') {
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n...", 5);
      SSH_CHANNEL_SendData(pSession, Channel, _aCommandLine, _Cursor);
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n", 3);
      Status = SSH_CHANNEL_SendData(pSession, Channel, PROMPT, (unsigned)strlen(PROMPT));
      _Cursor = 0;
    } else if (Ch == 0x04) {
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n\r\nBye!\r\n\r\n", 12);
      SSH_CHANNEL_Close(pSession, Channel);
      break;
    }
  }
  //
  return Status;
}

/*********************************************************************
*
*       _TerminalRequest()
*
*  Function description
*    Request a terminal.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel requesting the terminal.
*    pParas   - Pointer to channel request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _TerminalRequest(SSH_SESSION               * pSession,
                            unsigned                    Channel,
                            SSH_CHANNEL_REQUEST_PARAS * pParas) {
  int Status;
  //
  SSH_CHANNEL_Config(pSession, Channel, 128, &_TerminalAPI, NULL);
  if (pParas->WantReply) {
    Status = SSH_CHANNEL_SendSuccess(pSession, Channel);
  } else {
    Status = 0;
  }
  //
  return Status;
}

/*********************************************************************
*
*       _UserauthRequestNone()
*
*  Function description
*    Request authentication of user with method "none".
*
*  Parameters
*    pSession  - Pointer to session.
*    pReqParas - Pointer to user authentication request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _UserauthRequestNone(SSH_SESSION                * pSession,
                                SSH_USERAUTH_REQUEST_PARAS * pReqParas) {
  SSH_USERAUTH_NONE_PARAS NoneParas;
  int                     Status;
  //
  SSH_USE_PARA(pSession);
  //
  Status = SSH_USERAUTH_NONE_ParseParas(pReqParas, &NoneParas);
  if (Status < 0) {
    Status = SSH_ERROR_USERAUTH_FAIL;
  } else if (pReqParas->UserNameLen == 4 &&
             SSH_MEMCMP(pReqParas->pUserName, "anon", 4) == 0) {
    Status = 0;
  } else {
    Status = SSH_ERROR_USERAUTH_FAIL;
  }
  //
  return Status;
}

/*********************************************************************
*
*       _UserauthRequestPassword()
*
*  Function description
*    Request authentication of user with method "password".
*
*  Parameters
*    pSession  - Pointer to session.
*    pReqParas - Pointer to user authentication request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _UserauthRequestPassword(SSH_SESSION                * pSession,
                                    SSH_USERAUTH_REQUEST_PARAS * pReqParas) {
  SSH_USERAUTH_PASSWORD_PARAS PasswordParas;
  int                         Status;
  //
  SSH_USE_PARA(pSession);
  //
  Status = SSH_USERAUTH_PASSWORD_ParseParas(pReqParas, &PasswordParas);
  if (Status < 0) {
    Status = SSH_ERROR_USERAUTH_FAIL;
  } else if (pReqParas->UserNameLen == 5 &&
             memcmp(pReqParas->pUserName, "admin", 5) == 0) {
    if (PasswordParas.PasswordLen == 6 &&
        memcmp(PasswordParas.pPassword, "secret", 6) == 0) {
      Status = 0;
    } else {
      Status = SSH_ERROR_USERAUTH_FAIL;
    }
  } else {
    Status = SSH_ERROR_USERAUTH_FAIL;
  }
  //
  return Status;
}

/*********************************************************************
*
*             Public code
*
**********************************************************************
*/

/*********************************************************************
*
*       MainTask()
*
*  Function description
*    Application entry point.
*/
void MainTask(void) {
  SSH_SESSION * pSession;
  int           BoundSocket;
  int           Socket;
  int           Status;
  //
  SEGGER_SYS_Init();
  SEGGER_SYS_IP_Init();
  SSH_Init();
  //
  SEGGER_SYS_IO_Printf("\nemSSH V%s - Shell3 compiled " __DATE__ " " __TIME__ "\n",
                       SSH_GetVersionText());
  SEGGER_SYS_IO_Printf("%s    www.segger.com\n\n",
                       SSH_GetCopyrightText());
  //
  // Allow "none" and "password" user authentication.
  //
  SSH_SERVICE_Add(&SSH_SERVICE_USERAUTH, NULL);
  SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_NONE,     _UserauthRequestNone);
  SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_PASSWORD, _UserauthRequestPassword);
  //
  // Add support for interactive shells.
  //
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_SHELL,         _ShellRequest);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_ENV,           NULL);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_PTYREQ,        _TerminalRequest);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_WINDOW_CHANGE, NULL);
  //
  // Bind SSH port.
  //
  BoundSocket = SEGGER_SYS_IP_Bind(22);
  if (BoundSocket < 0) {
    _AppExit("Cannot bind port 22!");
  }
  //
  for (;;) {
    //
    // Wait for an incoming connection.
    //
    do {
      Socket = SEGGER_SYS_IP_Accept(BoundSocket);
    } while (Socket < 0);
    //
    SSH_SESSION_Alloc(&pSession);
    if (pSession == 0) {
      _AppExit("No available session!");
    }
    //
    SSH_SESSION_Init(pSession, Socket, &_IP_Transport);
    SSH_SESSION_ConfBuffers(pSession,
                            _aRxTxBuffer, sizeof(_aRxTxBuffer),
                            _aRxTxBuffer, sizeof(_aRxTxBuffer));
    do {
      Status = SSH_SESSION_Process(pSession);
    } while (Status >= 0);
  }
}

/*************************** End of file ****************************/

Better password storage

Rather than storing passwords in the clear, it’s better to use a one-way function and, what’s more, a computationally-expensive one-way function to prevent using brute force to crack the key. Even better still is to use a salted password that prevents dictionary lookup of the one-way function result.

For a complete listing of this application, see SSH_Shell4.c complete listing.

Securing passwords

A secure way of storing passwords is to use the PBKDF2 algorithm combined with an HMAC algorithm, for example PBKDF2-HMAC-SHA-256. If we take the password secret and prepend 16 bytes of random data, the salt, and run it through PBKDF2-HMAC-SHA-256, and ask for 32 bytes of output, the result is the block:

C8 52 33 15 9D 6C 74 E3 04 97 BE 95 54 CE DB 6A
37 DD 07 EE AA 7A 99 01 F1 1E 57 6F 64 0A 99 2F

It is infeasible to find a password combined with the salt which hashes to the same output (this is technically called a collision). We no longer store the password in the clear and, what is more, we’re confident that the password cannot be cracked by brute force.

The salt and password hash can be stored in plain sight, without any need for secrecy, that’s the beauty of this method, because we rely on the hard one-way function of the salted password provide the security.

static const U8 _aAdminPasswordSalt[16] = {
  0xBC, 0x4B, 0xCD, 0x8C, 0xC9, 0x99, 0x74, 0xC6,
  0xC5, 0xFE, 0x5E, 0x98, 0x20, 0xD8, 0x6D, 0x03
};

static const U8 _aAdminHashedPassword[32] = {
  0xC8, 0x52, 0x33, 0x15, 0x9D, 0x6C, 0x74, 0xE3,
  0x04, 0x97, 0xBE, 0x95, 0x54, 0xCE, 0xDB, 0x6A,
  0x37, 0xDD, 0x07, 0xEE, 0xAA, 0x7A, 0x99, 0x01,
  0xF1, 0x1E, 0x57, 0x6F, 0x64, 0x0A, 0x99, 0x2F
};

Modifying the password authenticator

This is how we modify the user authentication callback:

static int _UserauthRequestPassword(SSH_SESSION                * pSession,
                                    SSH_USERAUTH_REQUEST_PARAS * pReqParas) {
  SSH_USERAUTH_PASSWORD_PARAS PasswordParas;
  int                         Status;
  U8                          aHash[32];
  //
  SSH_USE_PARA(pSession);
  //
  Status = SSH_USERAUTH_PASSWORD_ParseParas(pReqParas, &PasswordParas);
  if (Status < 0) {
    Status = SSH_ERROR_USERAUTH_FAIL;
  } else if (pReqParas->UserNameLen == 5 &&
             memcmp(pReqParas->pUserName, "admin", 5) == 0) {
    //
    CRYPTO_PBKDF2_HMAC_SHA256_Calc(PasswordParas.pPassword,  
                                   PasswordParas.PasswordLen,
                                   _aAdminPasswordSalt,
                                   sizeof(_aAdminPasswordSalt),
                                   10000,
                                   aHash,
                                   sizeof(aHash));
    if (CRYPTO_MEMDIF(aHash, _aAdminHashedPassword, sizeof(aHash)) == 0) {  
      Status = 0;
    } else {
      Status = SSH_ERROR_USERAUTH_FAIL;
    }
  } else {
    Status = SSH_ERROR_USERAUTH_FAIL;
  }
  //
  return Status;
}

The direct compare of the entered password with the user’s correct password is replaced with a calculation.

  Compute the hash of the salted password

This uses the emCrypt implementation of PBKDF2-HMAC-SHA-256 to compute the hash of the the salted password, selecting 10,000 iterations and 32 bytes of output. The value 32 happens to match the MAC size of the HMAC-SHA-256 algorithm, but any length can be requested—longer lengths require more computation and are more secure.

  Compare the expected and calculated password hashes

The expected and calculated password hashes are compared for equality: only if they match is the password correct. This particular implementation uses a constant-time comparison function, CRYPTO_MEMDIF, but it’s not absolutely necessary. From here on it’s standard form to return the result of user authentication.

What's the cost?

This implementation uses 10,000 iterations of PBKDF2-HMAC-SHA-256 and therefore it takes slightly longer to log in to a shell than with plaintext password comparisons. On the Cortex-M4 emPower board running at 168 MHz with hardware acceleration of SHA-256, it takes approximately 1.4 seconds to verify the password using 10,000 iterations.

There is no fixed password hashing scheme, this is but one example. You can tune the implementation to your particular requirements.

SSH_Shell4.c complete listing

/*********************************************************************
*                   (c) SEGGER Microcontroller GmbH                  *
*                        The Embedded Experts                        *
*                           www.segger.com                           *
**********************************************************************

-------------------------- END-OF-HEADER -----------------------------

File        : SSH_Shell4.c
Purpose     : SSH server that adds secure passwords.

*/

/*********************************************************************
*
*       #include Section
*
**********************************************************************
*/

#include "SSH.h"
#include "SEGGER_SYS.h"
#include <string.h>

/*********************************************************************
*
*       Defines, configurable
*
**********************************************************************
*/

#define PROMPT                                                   \
  "emSSH> "

#define SIGNON                                                   \
  "\r\n"                                                         \
  "Welcome to the emSSH command line!  Type Ctrl+D to exit.\r\n" \
  "\r\n"                                                         \
  PROMPT

/*********************************************************************
*
*       Prototypes
*
**********************************************************************
*/

void MainTask(void);
static int _TerminalChannelData(      SSH_SESSION * pSession,
                                      unsigned      Channel,
                                const U8          * pData,
                                      unsigned      DataLen);

/*********************************************************************
*
*       Static const data
*
**********************************************************************
*/

static const SSH_TRANSPORT_API _IP_Transport = {
  SEGGER_SYS_IP_Send,
  SEGGER_SYS_IP_Recv,
  SEGGER_SYS_IP_Close,
};

static const SSH_CHANNEL_API _TerminalAPI = {
  _TerminalChannelData,
  NULL,
  NULL,
  NULL
};

static const U8 _aAdminPasswordSalt[16] = {
  0xBC, 0x4B, 0xCD, 0x8C, 0xC9, 0x99, 0x74, 0xC6,
  0xC5, 0xFE, 0x5E, 0x98, 0x20, 0xD8, 0x6D, 0x03
};

static const U8 _aAdminHashedPassword[32] = {
  0xC8, 0x52, 0x33, 0x15, 0x9D, 0x6C, 0x74, 0xE3,
  0x04, 0x97, 0xBE, 0x95, 0x54, 0xCE, 0xDB, 0x6A,
  0x37, 0xDD, 0x07, 0xEE, 0xAA, 0x7A, 0x99, 0x01,
  0xF1, 0x1E, 0x57, 0x6F, 0x64, 0x0A, 0x99, 0x2F
};

/*********************************************************************
*
*       Static data
*
**********************************************************************
*/

static U8       _aRxTxBuffer[8192];
static U8       _aCommandLine[70];
static unsigned _Cursor;

/*********************************************************************
*
*       Static code
*
**********************************************************************
*/

/*********************************************************************
*
*       _AppExit()
*
*  Function description
*    Exit the application with an error.
*
*  Parameters
*    sReason - Reason for exit, displayed for the user.
*/
static void _AppExit(const char *sReason) {
  SEGGER_SYS_IO_Printf(sReason);
  SEGGER_SYS_OS_Halt(100);
}

/*********************************************************************
*
*       _ShellRequest()
*
*  Function description
*    Handle a shell channel request.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel receiving the data.
*    pParas   - Pointer to channel request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _ShellRequest(SSH_SESSION               * pSession,
                         unsigned                    Channel,
                         SSH_CHANNEL_REQUEST_PARAS * pParas) {
  int Status;
  //
  Status = SSH_CHANNEL_SendData(pSession, Channel,
                                SIGNON, (unsigned)strlen(SIGNON));
  if (Status < 0) {
    Status = SSH_CHANNEL_SendFailure(pSession, Channel);
  } else if (pParas->WantReply) {
    Status = SSH_CHANNEL_SendSuccess(pSession, Channel);
  }
  //
  return Status;
}

/*********************************************************************
*
*       _TerminalChannelData()
*
*  Function description
*    Handle data received from peer.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel receiving the data.
*    pData    - Pointer to object that contains the data.
*    DataLen  - Octet length of the object that contains the data.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*
*  Additional information
*    Provide in-callback handling of a command line processor.
*    This sample supports only one connection at a time.
*/
static int _TerminalChannelData(      SSH_SESSION * pSession,
                                      unsigned      Channel,
                                const U8          * pData,
                                      unsigned      DataLen) {
  unsigned i;
  U8       Ch;
  int      Status;
  //
  Status = 0;
  //
  for (i = 0; Status >= 0 && i < DataLen; ++i) {
    Ch = pData[i];
    if (0x20 <= Ch && Ch <= 0x7E) {
      if (_Cursor < sizeof(_aCommandLine)) {
        _aCommandLine[_Cursor++] = Ch;
        Status = SSH_CHANNEL_SendData(pSession, Channel, &Ch, 1);
      }
    } else if (Ch == 0x08 || Ch == 0x7F) {
      if (_Cursor > 0) {
        --_Cursor;
        Status = SSH_CHANNEL_SendData(pSession, Channel, "\b \b", 3);
      }
    } else if (Ch == '\r') {
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n...", 5);
      SSH_CHANNEL_SendData(pSession, Channel, _aCommandLine, _Cursor);
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n", 3);
      Status = SSH_CHANNEL_SendData(pSession, Channel, PROMPT, (unsigned)strlen(PROMPT));
      _Cursor = 0;
    } else if (Ch == 0x04) {
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n\r\nBye!\r\n\r\n", 12);
      SSH_CHANNEL_Close(pSession, Channel);
      break;
    }
  }
  //
  return Status;
}

/*********************************************************************
*
*       _TerminalRequest()
*
*  Function description
*    Request a terminal.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel requesting the terminal.
*    pParas   - Pointer to channel request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _TerminalRequest(SSH_SESSION               * pSession,
                            unsigned                    Channel,
                            SSH_CHANNEL_REQUEST_PARAS * pParas) {
  int Status;
  //
  SSH_CHANNEL_Config(pSession, Channel, 128, &_TerminalAPI, 0);
  if (pParas->WantReply) {
    Status = SSH_CHANNEL_SendSuccess(pSession, Channel);
  } else {
    Status = 0;
  }
  //
  return Status;
}

/*********************************************************************
*
*       _UserauthRequestNone()
*
*  Function description
*    Request authentication of user with method "none".
*
*  Parameters
*    pSession  - Pointer to session.
*    pReqParas - Pointer to user authentication request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _UserauthRequestNone(SSH_SESSION                * pSession,
                                SSH_USERAUTH_REQUEST_PARAS * pReqParas) {
  SSH_USERAUTH_NONE_PARAS NoneParas;
  int                     Status;
  //
  SSH_USE_PARA(pSession);
  //
  Status = SSH_USERAUTH_NONE_ParseParas(pReqParas, &NoneParas);
  if (Status < 0) {
    Status = SSH_ERROR_USERAUTH_FAIL;
  } else if (pReqParas->UserNameLen == 4 &&
             SSH_MEMCMP(pReqParas->pUserName, "anon", 4) == 0) {
    Status = 0;
  } else {
    Status = SSH_ERROR_USERAUTH_FAIL;
  }
  //
  return Status;
}

/*********************************************************************
*
*       _UserauthRequestPassword()
*
*  Function description
*    Request authentication of user with method "password".
*
*  Parameters
*    pSession  - Pointer to session.
*    pReqParas - Pointer to user authentication request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _UserauthRequestPassword(SSH_SESSION                * pSession,
                                    SSH_USERAUTH_REQUEST_PARAS * pReqParas) {
  SSH_USERAUTH_PASSWORD_PARAS PasswordParas;
  int                         Status;
  U8                          aHash[CRYPTO_SHA256_DIGEST_BYTE_COUNT];
  //
  SSH_USE_PARA(pSession);
  //
  Status = SSH_USERAUTH_PASSWORD_ParseParas(pReqParas, &PasswordParas);
  if (Status < 0) {
    Status = SSH_ERROR_USERAUTH_FAIL;
  } else if (pReqParas->UserNameLen == 5 &&
             memcmp(pReqParas->pUserName, "admin", 5) == 0) {
    //
    CRYPTO_PBKDF2_HMAC_SHA256_Calc(PasswordParas.pPassword,
                                   PasswordParas.PasswordLen,
                                   _aAdminPasswordSalt,
                                   sizeof(_aAdminPasswordSalt),
                                   10000,
                                   aHash,
                                   sizeof(aHash));
    if (CRYPTO_MEMDIF(aHash, _aAdminHashedPassword, sizeof(aHash)) == 0) {
      Status = 0;
    } else {
      Status = SSH_ERROR_USERAUTH_FAIL;
    }
  } else {
    Status = SSH_ERROR_USERAUTH_FAIL;
  }
  //
  return Status;
}

/*********************************************************************
*
*             Public code
*
**********************************************************************
*/

/*********************************************************************
*
*       MainTask()
*
*  Function description
*    Application entry point.
*/
void MainTask(void) {
  SSH_SESSION * pSession;
  int           BoundSocket;
  int           Socket;
  int           Status;
  //
  SEGGER_SYS_Init();
  SEGGER_SYS_IP_Init();
  SSH_Init();
  //
  SEGGER_SYS_IO_Printf("\nemSSH V%s - Shell4 compiled " __DATE__ " " __TIME__ "\n",
                       SSH_GetVersionText());
  SEGGER_SYS_IO_Printf("%s    www.segger.com\n\n",
                       SSH_GetCopyrightText());
  //
  // Allow "none" and "password" user authentication.
  //
  SSH_SERVICE_Add(&SSH_SERVICE_USERAUTH, NULL);
  SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_NONE,     _UserauthRequestNone);
  SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_PASSWORD, _UserauthRequestPassword);
  //
  // Add support for interactive shells.
  //
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_SHELL,         _ShellRequest);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_ENV,           NULL);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_PTYREQ,        _TerminalRequest);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_WINDOW_CHANGE, NULL);
  //
  // Bind SSH port.
  //
  BoundSocket = SEGGER_SYS_IP_Bind(22);
  if (BoundSocket < 0) {
    _AppExit("Cannot bind port 22!");
  }
  //
  for (;;) {
    //
    // Wait for an incoming connection.
    //
    do {
      Socket = SEGGER_SYS_IP_Accept(BoundSocket);
    } while (Socket < 0);
    //
    SSH_SESSION_Alloc(&pSession);
    if (pSession == 0) {
      _AppExit("No available session!");
    }
    //
    SSH_SESSION_Init(pSession, Socket, &_IP_Transport);
    SSH_SESSION_ConfBuffers(pSession,
                            _aRxTxBuffer, sizeof(_aRxTxBuffer),
                            _aRxTxBuffer, sizeof(_aRxTxBuffer));
    do {
      Status = SSH_SESSION_Process(pSession);
    } while (Status >= 0);
  }
}

/*************************** End of file ****************************/

Displaying a warning banner

In some jurisdictions it may be desirable, or even necessary, to display a warning or legal notice before logging in. The SSH protocol supports this and emSSH provides the mechanism to implement this.

For a complete listing of this application, see SSH_Shell5.c complete listing.

Hooking the user authentication service

Before a user can log in, the client requests the authentication service. During authentication and before password entry, there is a possibility to send the client a banner to display to the user that warns them that, for instance, all logins attempts are recorded.

This example isn’t so menacing, and we start by defining the banner that we wish to display to the user:

#define BANNER \
  "\r\n"                                                              \
  "*************************************************************\r\n" \
  "* This server is powered by SEGGER emSSH.  It simply works! *\r\n" \
  "*************************************************************\r\n" \
  "\r\n"

We hook requests for the user authentication service and direct them to a callback function that will display the banner:

//
// Hook user authentication to display a banner.  Allow "none"
// and "password" user authentication.
//
SSH_SERVICE_Add(&SSH_SERVICE_USERAUTH, _UserauthServiceRequest);
SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_NONE,     _UserauthRequestNone);
SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_PASSWORD, _UserauthRequestPassword);

The user authentication service callback is:

static int _UserauthServiceRequest(      SSH_SESSION * pSession,
                                   const char        * sServiceName) {  
  int Status;
  //
  Status = SSH_SESSION_SendServiceAccept(pSession, sServiceName);  
  if (Status >= 0) {
    Status = SSH_SESSION_SendUserauthBanner(pSession, BANNER, "en");  
  }
  //
  return Status;  
}

The code is fairly straightfoward:

  Receive user authentication service parameters

The service name will always be “ssh-userauth” as this is the service that we hooked.

  Accept the service

As this callback replaces emSSH’s default of accepting installed services, it’s the callback’s responsibility to accept the service by using SSH_SESSION_SendServiceAccept() to send a service accept message to the peer.

  Ask the peer to display a banner

With the service accepted, the server is cleared to send a user authentication banner to the client using SSH_SESSION_SendUserauthBanner(). The final parameter is the language tag that indicates to the client the language the banner appears in. It is unclear exactly how the client is supposed to handle the language tag—hard-coding this to “en” is good enough in most cases as English is widely understood.

  Return the service accept status

By now this should be a familiar idiom.

Seeing the banner

Testing the application is the same as previous examples:

MacBook:~ paul$ ssh admin@10.0.0.247

*************************************************************
* This server is powered by SEGGER emSSH.  It simply works! *
*************************************************************

admin@10.0.0.247's password: secret

Welcome to the emSSH command line!  Type Ctrl+D to exit.

emSSH> ls -l
...ls -l
emSSH> 

Bye!

Connection to 10.0.0.247 closed.
MacBook:~ paul$ _

SSH_Shell5.c complete listing

/*********************************************************************
*                   (c) SEGGER Microcontroller GmbH                  *
*                        The Embedded Experts                        *
*                           www.segger.com                           *
**********************************************************************

-------------------------- END-OF-HEADER -----------------------------

File        : SSH_Shell5.c
Purpose     : SSH server that adds a warning banner.

*/

/*********************************************************************
*
*       #include Section
*
**********************************************************************
*/

#include "SSH.h"
#include "SEGGER_SYS.h"
#include <string.h>

/*********************************************************************
*
*       Defines, configurable
*
**********************************************************************
*/

#define PROMPT                                                        \
  "emSSH> "

#define SIGNON                                                        \
  "\r\n"                                                              \
  "Welcome to the emSSH command line!  Type Ctrl+D to exit.\r\n"      \
  "\r\n"                                                              \
  PROMPT

#define BANNER \
  "\r\n"                                                              \
  "*************************************************************\r\n" \
  "* This server is powered by SEGGER emSSH.  It simply works! *\r\n" \
  "*************************************************************\r\n" \
  "\r\n"

/*********************************************************************
*
*       Prototypes
*
**********************************************************************
*/

void MainTask(void);
static int _TerminalChannelData(      SSH_SESSION * pSession,
                                      unsigned      Channel,
                                const U8          * pData,
                                      unsigned      DataLen);

/*********************************************************************
*
*       Static const data
*
**********************************************************************
*/

static const SSH_TRANSPORT_API _IP_Transport = {
  SEGGER_SYS_IP_Send,
  SEGGER_SYS_IP_Recv,
  SEGGER_SYS_IP_Close,
};

static const SSH_CHANNEL_API _TerminalAPI = {
  _TerminalChannelData,
  NULL,
  NULL,
  NULL
};

/*********************************************************************
*
*       Static data
*
**********************************************************************
*/

static U8       _aRxTxBuffer[8192];
static U8       _aCommandLine[70];
static unsigned _Cursor;

/*********************************************************************
*
*       Static code
*
**********************************************************************
*/

/*********************************************************************
*
*       _AppExit()
*
*  Function description
*    Exit the application with an error.
*
*  Parameters
*    sReason - Reason for exit, displayed for the user.
*/
static void _AppExit(const char *sReason) {
  SEGGER_SYS_IO_Printf(sReason);
  SEGGER_SYS_OS_Halt(100);
}

/*********************************************************************
*
*       _ShellRequest()
*
*  Function description
*    Handle a shell channel request.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel receiving the data.
*    pParas   - Pointer to channel request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _ShellRequest(SSH_SESSION               * pSession,
                         unsigned                    Channel,
                         SSH_CHANNEL_REQUEST_PARAS * pParas) {
  int Status;
  //
  Status = SSH_CHANNEL_SendData(pSession, Channel,
                                SIGNON, (unsigned)strlen(SIGNON));
  if (Status < 0) {
    Status = SSH_CHANNEL_SendFailure(pSession, Channel);
  } else if (pParas->WantReply) {
    Status = SSH_CHANNEL_SendSuccess(pSession, Channel);
  }
  //
  return Status;
}

/*********************************************************************
*
*       _TerminalChannelData()
*
*  Function description
*    Handle data received from peer.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel receiving the data.
*    pData    - Pointer to object that contains the data.
*    DataLen  - Octet length of the object that contains the data.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*
*  Additional information
*    Provide in-callback handling of a command line processor.
*    This sample supports only one connection at a time.
*/
static int _TerminalChannelData(      SSH_SESSION * pSession,
                                      unsigned      Channel,
                                const U8          * pData,
                                      unsigned      DataLen) {
  unsigned i;
  U8       Ch;
  int      Status;
  //
  Status = 0;
  //
  for (i = 0; Status >= 0 && i < DataLen; ++i) {
    Ch = pData[i];
    if (0x20 <= Ch && Ch <= 0x7E) {
      if (_Cursor < sizeof(_aCommandLine)) {
        _aCommandLine[_Cursor++] = Ch;
        Status = SSH_CHANNEL_SendData(pSession, Channel, &Ch, 1);
      }
    } else if (Ch == 0x08 || Ch == 0x7F) {
      if (_Cursor > 0) {
        --_Cursor;
        Status = SSH_CHANNEL_SendData(pSession, Channel, "\b \b", 3);
      }
    } else if (Ch == '\r') {
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n...", 5);
      SSH_CHANNEL_SendData(pSession, Channel, _aCommandLine, _Cursor);
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n", 3);
      Status = SSH_CHANNEL_SendData(pSession, Channel, PROMPT, (unsigned)strlen(PROMPT));
      _Cursor = 0;
    } else if (Ch == 0x04) {
      SSH_CHANNEL_SendData(pSession, Channel, "\r\n\r\nBye!\r\n\r\n", 12);
      SSH_CHANNEL_Close(pSession, Channel);
      break;
    }
  }
  //
  return Status;
}

/*********************************************************************
*
*       _TerminalRequest()
*
*  Function description
*    Request a terminal.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel requesting the terminal.
*    pParas   - Pointer to channel request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _TerminalRequest(SSH_SESSION               * pSession,
                            unsigned                    Channel,
                            SSH_CHANNEL_REQUEST_PARAS * pParas) {
  int Status;
  //
  SSH_CHANNEL_Config(pSession, Channel, 128, &_TerminalAPI, 0);
  if (pParas->WantReply) {
    Status = SSH_CHANNEL_SendSuccess(pSession, Channel);
  } else {
    Status = 0;
  }
  //
  return Status;
}

/*********************************************************************
*
*       _UserauthServiceRequest()
*
*  Function description
*    Request the user authentication service.
*
*  Parameters
*    pSession     - Pointer to session.
*    sServiceName - Service being requested.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*
*  Additional information
*    Displays a banner before user authentication commences.
*/
static int _UserauthServiceRequest(SSH_SESSION *pSession, const char *sServiceName) {
  int Status;
  //
  Status = SSH_SESSION_SendServiceAccept(pSession, sServiceName);
  if (Status >= 0) {
    Status = SSH_SESSION_SendUserauthBanner(pSession, BANNER, "en");
  }
  //
  return Status;
}

/*********************************************************************
*
*       _UserauthRequestNone()
*
*  Function description
*    Request authentication of user with method "none".
*
*  Parameters
*    pSession  - Pointer to session.
*    pReqParas - Pointer to user authentication request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _UserauthRequestNone(SSH_SESSION                * pSession,
                                SSH_USERAUTH_REQUEST_PARAS * pReqParas) {
  SSH_USERAUTH_NONE_PARAS NoneParas;
  int                     Status;
  //
  SSH_USE_PARA(pSession);
  //
  Status = SSH_USERAUTH_NONE_ParseParas(pReqParas, &NoneParas);
  if (Status < 0) {
    Status = SSH_ERROR_USERAUTH_FAIL;
  } else if (pReqParas->UserNameLen == 4 &&
             SSH_MEMCMP(pReqParas->pUserName, "anon", 4) == 0) {
    Status = 0;
  } else {
    Status = SSH_ERROR_USERAUTH_FAIL;
  }
  //
  return Status;
}

/*********************************************************************
*
*       _UserauthRequestPassword()
*
*  Function description
*    Request authentication of user with method "password".
*
*  Parameters
*    pSession  - Pointer to session.
*    pReqParas - Pointer to user authentication request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _UserauthRequestPassword(SSH_SESSION                * pSession,
                                    SSH_USERAUTH_REQUEST_PARAS * pReqParas) {
  SSH_USERAUTH_PASSWORD_PARAS PasswordParas;
  int                         Status;
  //
  SSH_USE_PARA(pSession);
  //
  Status = SSH_USERAUTH_PASSWORD_ParseParas(pReqParas, &PasswordParas);
  if (Status < 0) {
    Status = SSH_ERROR_USERAUTH_FAIL;
  } else if (pReqParas->UserNameLen == 5 &&
             memcmp(pReqParas->pUserName, "admin", 5) == 0) {
    if (PasswordParas.PasswordLen == 6 &&
        memcmp(PasswordParas.pPassword, "secret", 6) == 0) {
      Status = 0;
    } else {
      Status = SSH_ERROR_USERAUTH_FAIL;
    }
  } else {
    Status = SSH_ERROR_USERAUTH_FAIL;
  }
  //
  return Status;
}

/*********************************************************************
*
*             Public code
*
**********************************************************************
*/

/*********************************************************************
*
*       MainTask()
*
*  Function description
*    Application entry point.
*/
void MainTask(void) {
  SSH_SESSION * pSession;
  int           BoundSocket;
  int           Socket;
  int           Status;
  //
  SEGGER_SYS_Init();
  SEGGER_SYS_IP_Init();
  SSH_Init();
  //
  SEGGER_SYS_IO_Printf("\nemSSH V%s - Shell5 compiled " __DATE__ " " __TIME__ "\n",
                       SSH_GetVersionText());
  SEGGER_SYS_IO_Printf("%s    www.segger.com\n\n",
                       SSH_GetCopyrightText());
  //
  // Hook user authentication to display a banner.  Allow "none"
  // and "password" user authentication.
  //
  SSH_SERVICE_Add(&SSH_SERVICE_USERAUTH, _UserauthServiceRequest);
  SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_PASSWORD, _UserauthRequestPassword);
  SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_NONE,     _UserauthRequestNone);
  //
  // Add support for interactive shells.
  //
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_SHELL,         _ShellRequest);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_ENV,           NULL);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_PTYREQ,        _TerminalRequest);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_WINDOW_CHANGE, NULL);
  //
  // Bind SSH port.
  //
  BoundSocket = SEGGER_SYS_IP_Bind(22);
  if (BoundSocket < 0) {
    _AppExit("Cannot bind port 22!");
  }
  //
  for (;;) {
    //
    // Wait for an incoming connection.
    //
    do {
      Socket = SEGGER_SYS_IP_Accept(BoundSocket);
    } while (Socket < 0);
    //
    SSH_SESSION_Alloc(&pSession);
    if (pSession == 0) {
      _AppExit("No available session!");
    }
    //
    SSH_SESSION_Init(pSession, Socket, &_IP_Transport);
    SSH_SESSION_ConfBuffers(pSession,
                            _aRxTxBuffer, sizeof(_aRxTxBuffer),
                            _aRxTxBuffer, sizeof(_aRxTxBuffer));
    do {
      Status = SSH_SESSION_Process(pSession);
    } while (Status >= 0);
  }
}

/*************************** End of file ****************************/

Multiple shells using an RTOS

All the previous examples have been reactive in that they output small quantities of data only when new data arrives through keyboard input. This is adequate for a very simple shell, but does not cater for asynchronous I/O. In this example we develop a framework that can handle multiple shells, with buffering, using an RTOS. But, of course, it’s equally applicable to a single shell instance.

This example uses SEGGER embOS, but it should be portable to other RTOS APIs with minimal effort. Only the more important, changed pieces are described.

Division of labor

The architecture of this example is to spawn two tasks per shell:

Intertask communication

Communication between the tasks is by way of two ring buffers, one for console input and one for console output. The ring buffer is defined by this structure:

typedef struct {
  volatile unsigned RdPtr;  // Read pointer
  volatile unsigned WrPtr;  // Write pointer
  volatile unsigned RdCnt;  // Number of bytes read
  volatile unsigned WrCnt;  // Number of bytes written
  unsigned          Capacity;
  U8              * pData;
} RING_BUFFER;

The concept is that a task writing into the ring buffer updates (writes) the write pointer member WrPtr and a task reading the ring buffer to retrieve data updates the read pointer member RdPtr. The ring buffer is empty when the read and write pointers are equal. As data is written into the ring buffer, the write pointer advances, and as data is read out, the read pointer advances to meet it.

This scheme is well known and has the advantage that, for a single reader and a single writer, it requires no locking whatsoever, i.e. it is lock free: only the reader advances the read pointer and only the writer advances the write pointer.

The read and write pointers are declared volatile because the buffer could be written, or read, from an interrupt service routine and change without the compiler being aware of it. In our case, it’s a second thread of execution as a task that will modify the pointer behind the compiler’s back.

Writing to the ring buffer

Writing into the ring buffer is as follows:

static unsigned _RING_BUFFER_Wr(      RING_BUFFER * pRB,
                                const void        * pData,
                                      unsigned      DataLen) {
  unsigned N;
  unsigned WrPtr;
  unsigned LChunkLen;
  unsigned RChunkLen;
  //
  N = _RING_BUFFER_QueryCanWrite(pRB);  
  if (N < DataLen) {
    DataLen = N;
  }
  //
  WrPtr = pRB->WrPtr;
  if (WrPtr + DataLen <= pRB->Capacity) {  
    memcpy(pRB->pData+WrPtr, pData, DataLen);
    WrPtr += DataLen;
    if (WrPtr == pRB->Capacity) {
      WrPtr = 0;
    }
    pRB->WrPtr = WrPtr;
  } else {
    //
    LChunkLen = pRB->Capacity - WrPtr;
    RChunkLen = DataLen - LChunkLen;
    //
    memcpy(pRB->pData+WrPtr, pData, LChunkLen);
    memcpy(pRB->pData, (U8 *)pData+LChunkLen, RChunkLen);
    //
    pRB->WrPtr = RChunkLen;
  }
  //
  pRB->WrCnt += DataLen;  
  //
  return DataLen;  
}

  Prevent buffer overflow

If too much data is written to the buffer, it does not overflow, it only becomes full. The return value of the function indicates the number of bytes written to the buffer.

  Handle buffer wrap

Data written to a ring buffer “wraps around” the end of the buffer. The code manages this by deciding whether the data will or will not wrap around: if it doesn’t, there’s one write, and if it does, it’s written in two pieces.

  Update write count

The difference between the write count and read count is the number of characters that are yet to be read from the buffer, and is more efficient to compute than handling the wrapping read and write pointers.

Reading from the ring buffer

Reading from the ring buffer follows the write framework and is not further explained:

static unsigned _RING_BUFFER_Rd(RING_BUFFER * pRB,
                                void        * pData,
                                unsigned      DataLen) {
  unsigned RdPtr;
  unsigned LChunkLen;
  unsigned RChunkLen;
  //
  RdPtr = _RING_BUFFER_QueryCanRead(pRB);
  if (RdPtr < DataLen) {
    DataLen = RdPtr;
  }
  //
  if (DataLen == 0) {
    return DataLen;
  }
  //
  RdPtr = pRB->RdPtr;
  if (RdPtr + DataLen <= pRB->Capacity) {
    memcpy(pData, pRB->pData+RdPtr, DataLen);
    RdPtr += DataLen;
    if (RdPtr == pRB->Capacity) {
      RdPtr = 0;
    }
    pRB->RdPtr = RdPtr;
  } else {
    LChunkLen = pRB->Capacity - RdPtr;
    RChunkLen = DataLen - LChunkLen;
    //
    memcpy(pData, pRB->pData+RdPtr, LChunkLen);
    memcpy((char *)pData+LChunkLen, pRB->pData, RChunkLen);
    //
    pRB->RdPtr = RChunkLen;
  }
  //
  pRB->RdCnt += DataLen;
  //
  return DataLen;
}

Starting the shell

For previous examples, the shell request callback did nothing or very little, such as writing a sign-on message. Now, the shell request callback does what a regular SSH daemon would do, and that is it starts a dedicated shell task:

static int _ShellRequest(SSH_SESSION               * pSession,
                         unsigned                    Channel,
                         SSH_CHANNEL_REQUEST_PARAS * pParas) {
  SHELL_CONTEXT * pShell;
  int             Status;
  //
  Status = 0;
  if (pParas->WantReply) {
    Status = SSH_CHANNEL_SendSuccess(pSession, Channel);
  }
  //
  pShell = &_aShell[SSH_SESSION_QueryIndex(pSession)];  
  SSH_CHANNEL_Config(pSession, Channel, 128, &_TerminalAPI, pShell);
  OS_CreateTaskEx(&pShell->ShellTask,
                  "ShellTask",
                  100,
                  _ShellMain,
                  &pShell->aShellTaskStack,
                  sizeof(pShell->aShellTaskStack),
                  10,
                  pSession);
  //
  return Status;
}

The callback is much as before but the channel associated with the shell is configured when the shell starts rather than when the terminal is requested.

The shell task is written to be very simple: it uses high-level C-style get and put stream functions that read and write the ring buffers. Because it uses these functions, and the task never calls any SSH function directly, it is completely decoupled from SSH and, as such, the ring buffer could be emptied by a regular telnet task, or a task that manages a plain serial line.

static void _ShellMain(void *pContext) {
  SSH_SESSION   * pSession;
  SHELL_CONTEXT * pShell;
  //
  pSession = pContext;
  pShell = &_aShell[SSH_SESSION_QueryIndex(pSession)];  
  //
  _Puts(pShell, SIGNON);
  for (;;) {
    _Puts(pShell, PROMPT);
    if (_Gets(pShell) == 0) {
      _Puts(pShell, "\r\n...");
      _Puts(pShell, pShell->aCommandLine);
      _Puts(pShell, "\r\n");
    } else {
      pShell->Exit = 1;
    }
  }
}

The only notable feature is that at we construct a pointer to a shell context we maintain. You’ll see this idiom throughout the code.

Efficient output

We now turn our attention to the framework that will service the ring buffers, from both tasks, in order to run the SSH protocol and shell efficiently.

Placing data into the ring buffer when it has remaining space for it is no problem, but when the ring buffer doesn’t is the challenge. One very simple implementation scheme would be to poll, but this soaks up processor cycles and is highly inefficient. Many engineers move to a more cycle-efficient scheme by using the RTOS “delay” function to yield processor time to other tasks and not monopolize the CPU. This is an admirable attempt, but it introduces an unintended latency into “restarting” the task once the buffer is full because the task waits for the entire delay period each and every time.

We choose an efficient implementation that has next to zero latency restarting the task when the buffer empties:

static void _Puts(SHELL_CONTEXT *pShell, const char *sText) {
  unsigned Len;
  unsigned ChunkLen;
  //
  Len = strlen(sText);
  //
  while (Len > 0) {  
    ChunkLen = _RING_BUFFER_Wr(&pShell->TxBuffer, sText, Len);  
    sText += ChunkLen;
    Len   -= ChunkLen;
    if (Len != 0) {  
      OS_Delay(10000);
    }
  }
}

  Write in pieces

The loop iterates until all data have been transferred to the ring buffer.

  Try to write

This step tries to write all the data to the ring buffer. If the ring buffer becomes full, only part of that data is written and the return value is the length of the chunk successfully transferred. The data pointer and length are updated to step over the written chunk, ready for the next iteration.

  Wait for the ring buffer empty

If there is data remaining to be written, it’s because the ring buffer is full and cannot accept more data. If this is the case, the calling task is delayed whilst the ring buffer empties. The delay length is set to ten seconds, but this delay is arbitrarily long: when the ring buffer is emptied by the protocol task and more space is made available in the transmission ring buffer, the shell task is prematurely woken from the delay using OS_WakeTask (refer to Buffering incoming data to see this side of the implementation).

Efficient input

Reading characters is much the same as writing. The _Getc function blocks until a character is available and returns that character:

static U8 _Getc(SHELL_CONTEXT *pShell) {
  U8 Ch;
  //
  while (_RING_BUFFER_QueryCanRead(&pShell->RxBuffer) == 0) {  
    OS_Delay(10000);
  }
  _RING_BUFFER_Rd(&pShell->RxBuffer, &Ch, 1);  
  return Ch;
}

  Wait for buffer fill

The function _RING_BUFFER_QueryCanRead() returns the number of characters that are immediately available in the ring buffer. As we are trying to read one character, we wait for the ring buffer to be nonempty. If there are no characters, the task is put to sleep and, rather like the transmit task, is prematurely woken when the SSH channel delivers more characters.

  Read the character

We know the buffer is nonempty and can deliver at least one character, so read and return it.

Buffering incoming data

New channel data is delivered by the channel data callback, which you have seen before. Previously we echoed that data to the terminal from within the callback function, but now we send the data to the receive ring buffer:

static int _TerminalChannelData(      SSH_SESSION * pSession,
                                      unsigned      Channel,
                                const U8          * pData,
                                      unsigned      DataLen) {
  SHELL_CONTEXT *pShell;
  //
  pShell = &_aShell[SSH_SESSION_QueryIndex(pSession)];  
  _RING_BUFFER_Wr(&pShell->RxBuffer, pData, DataLen);  
  OS_WakeTask(&pShell->ShellTask);    
  //
  return 0;
}

  Get a reference to the shell data

This was shown before, but we repeat it here.

  Write the incoming data to the ring buffer.

All incoming data is written to the ring buffer. You might wonder what happens when the ring buffer is full and more data is presented through the SSH protocol to the callback? In fact, this should not happen as the SSH protocol uses a windowing scheme rather like TCP where the transmitter knows exactly how much space is available in the receiver’s buffer and never sends more than the receiver can accept. This window is maintained in both directions by the SSH protocol: emSSH fully implements this windowing scheme when receiving, and requires minor client involvement on transmission.

The above says “should not happen.” What if it does? That is a protocol error and this callback simply discards the data that it cannot store. To emphasize, this should not happen but, if it does, it will not cause emSSH to fail.

  Wake up the receiver

We know that we have written data to the receive buffer and, therefore, it cannot be empty, and now it’s time to wake up the receiver if it’s suspended waiting for input. If the receiver is already awake, the call to OS_WakeTask() is benign and does nothing.

Handling the SSH protocol

The SSH protocol is handled by a task dedicated to service the connection:

static void _ProtocolMain(void *pContext) {
  SSH_SESSION   * pSession;
  int             Socket;
  int             Status;
  //
  pSession = pContext;
  Socket = SSH_SESSION_QuerySocket(pSession);  
  for (;;) {
    Status = 0;
    if (SEGGER_SYS_IP_QueryCanRead(Socket)) {  
      Status = SSH_SESSION_Process(pSession);
    } else {
      Status = SSH_SESSION_IterateChannels(pSession, _ServiceChannel);  
    }
    if (Status < 0) {  
      OS_TerminateTask(0);
    } else if (Status == 0) {  
      OS_Delay(10);
    }
  }
}

  Recover socket

The function SSH_SESSION_QuerySocket() returns the socket index associated with a session set up using SSH_SESSION_Init(). We use the socket index to call socket-related functions.

  Inquire if protocol data is ready

The function SEGGER_SYS_IP_QueryCanRead() returns nonzero when data is available to read from the socket. If there is data ready, we process it through the SSH state machine using SSH_SESSION_Process().

  Handle channel data I/O

If there is no data on the socket, the task sees if any channel in the session requires servicing by iterating over them using SSH_SESSION_IterateChannels() with the callback _ServiceChannel().

  Handling an error

If there is an error during processing, the protocol task is terminated.

  Idling

After iterating all channels with none of them needing processing, the protocol task is put to sleep in the expectation that some data will arrive on the socket or through the ring buffer. This is a slight inefficiency, but there is no easily-presented mechanism in embOS and embOS/IP to wait on a socket and some other object simultaneously, so we resort to a simple polling scheme.

Servicing channels

Each channel in a SSH session is serviced from the protocol task, in this example, with the _ServiceChannel callback:

static int _ServiceChannel(SSH_SESSION *pSession, unsigned Channel) {
  SHELL_CONTEXT * pShell;
  unsigned        Len;
  U8              aData[64];
  int             Status;
  //
  pShell = &_aShell[SSH_SESSION_QueryIndex(pSession)];
  if (pShell == 0) {
    Status = 0;
  } else if (pShell->Exit) {  
    if (SSH_CHANNEL_QueryCanWrite(pSession, Channel) >= 12) {
      Status = SSH_CHANNEL_SendData(pShell->pSession, Channel,
                                    "\r\n\r\nBye!\r\n\r\n", 12);
    }
    SSH_SESSION_Disconnect(pShell->pSession, SSH_DISCONNECT_BY_APPLICATION);
    Status = -1;
  } else if (_RING_BUFFER_QueryCanRead(&pShell->TxBuffer) > 0) {  
    Len = _RING_BUFFER_QueryCanRead(&pShell->TxBuffer);  
    Len = SEGGER_MIN(Len, SSH_CHANNEL_QueryCanWrite(pShell->pSession, Channel));  
    Len = SEGGER_MIN(Len, sizeof(aData));  
    if (Len > 0) {
      _RING_BUFFER_Rd(&pShell->TxBuffer, aData, Len);    
      Status = SSH_CHANNEL_SendData(pShell->pSession, Channel, aData, Len);
      OS_WakeTask(&pShell->ShellTask);
    }
    Status = 1;  
  } else {
    Status = 0;  
  }
  //
  return Status;
}

  Handle an exit request

Receiving Ctrl+D in the shell task sets the Exit flag in the shell context. When the protocol task sees that the shell has requested an exit, it writes a termination message to the peer and disconnects.

The call to SSH_CHANNEL_QueryCanWrite() is something that hasn’t been presented before. Remember that there is a windowing mechanism for SSH channels and that a transmitter should not send more data than the receiver is able to accept. This code inquires whether the receiver is able to accept 12 bytes and, if so, proceeds to write the log-off message. This is perfectly acceptable because, immediately afterwards, the session is disconnected and no further data is sent to the receiver.

  Limit data transfer size

Using _RING_BUFFER_QueryCanRead, the protocol task sees if there is any data in the ring buffer that could possibly be sent to the receiver. If there is, at it notes the size of the outstanding data in the transmit buffer. At it reduces the size to that which can be accepted by the receiver. And at it limits the size to that which can be stored in a local buffer.

Note that it is entirely possible to remove the local buffer and transmit directly from the ring buffer, but this makes the code more complex and less readable. In the best textbook tradition, this is left as an exercise for the reader.

  Send data to receiver

Once the maximum transfer fragment length is calculated, the data is read from the ring buffer and sent to the receiver with SSH_CHANNEL_SendData(). Once the data is read and sent, we know that the ring buffer is nonfull and, because it is nonfull, the shell task can be woken if it is suspended waiting for the buffer to empty.

At we set the return status to one to indicate that we have done some processing and sent data to the receiver, and at we set it to zero to indicate that the channel state didn’t change. This status code is returned by the service callback, passes through SSH_SESSION_IterateChannels(), and guides _ProtocolMain actions—see Handling the SSH protocol.

Closing down

It’s possible for both ends of a connection to terminate a session. When a session is terminated from the client, the SSH server must clean up after itself. We haven’t needed to run any special processing in previous examples but, as we now have tasks, this example needs to shut down the session cleanly.

When a channel closes, emSSH activates the pfOnChannelClose() callback in the channel API and this is exactly what we need in our example:

static const SSH_CHANNEL_API _TerminalAPI = {
  _TerminalChannelData,
  0,
  0,
  _TerminalChannelClose,
};

The code that deals with channel closure is straightforward: it terminates the shell task that is associated with the channel.

static void _TerminalChannelClose(SSH_SESSION * pSession, unsigned Channel) {
  SHELL_CONTEXT *pShell;
  //
  SSH_USE_PARA(Channel);
  //
  pShell = &_aShell[SSH_SESSION_QueryIndex(pSession)];
  OS_TerminateTask(&pShell->ShellTask);
}

Starting up

It may seem strange to show the startup last, but it’s easy to code and is merely a rework of the previous startup code:

for (;;) {
  //
  // Try to allocate a session for the next incoming connection.
  //
  SSH_SESSION_Alloc(&pSession);  
  if (pSession == 0) {
    OS_Delay(100);
    continue;
  }
  //
  do {
    Socket = SEGGER_SYS_IP_Accept(BoundSocket);
  } while (Socket < 0);
  //
  SessionIndex = SSH_SESSION_QueryIndex(pSession);  
  pShell = &_aShell[SessionIndex];
  memset(pShell, 0, sizeof(*pShell));
  pShell->pSession = pSession;
  SSH_SESSION_Init(pShell->pSession, Socket, &_IP_Transport);
  SSH_SESSION_ConfBuffers(pShell->pSession,
                          pShell->aRxTxBuffer, sizeof(pShell->aRxTxBuffer),
                          pShell->aRxTxBuffer, sizeof(pShell->aRxTxBuffer));
  _RING_BUFFER_Init(&pShell->RxBuffer, pShell->aRxData, sizeof(pShell->aRxData));
  _RING_BUFFER_Init(&pShell->TxBuffer, pShell->aTxData, sizeof(pShell->aTxData));
  //
  OS_CreateTaskEx(&pShell->ProtocolTask,  
                  "ProtocolTask",
                  100,
                  _ProtocolMain,
                  &pShell->aProtocolTaskStack,
                  sizeof(pShell->aProtocolTaskStack),
                  10,
                  pSession);
}

  Wait for a new session

This waits for an incoming connection only if there is an SSH session available to service it.

  Set up the session

This sets up the per-session data which is an array of session data indexed by the session number. The session number, or session index, for a session is returned by SSH_SESSION_QueryIndex().

  Start the protocol task

Once the session is set up, a new SSH protocol handling task is started to handle that specific connection and the loop continues to handle other incoming connections. The SSH protocol task services channel requests and starts shell processes as necessary and acts as an “embedded sshd”.

This concludes our last example. You should now be familiar with how emSSH can be deployed in a user application.

SSH_Shell6.c complete listing

/*********************************************************************
*                   (c) SEGGER Microcontroller GmbH                  *
*                        The Embedded Experts                        *
*                           www.segger.com                           *
**********************************************************************

-------------------------- END-OF-HEADER -----------------------------

File        : SSH_Shell6.c
Purpose     : SSH server that offers simultaneous input and output.

*/

/*********************************************************************
*
*       #include Section
*
**********************************************************************
*/

#include "SSH.h"
#include "IP.h"
#include "RTOS.h"

/*********************************************************************
*
*       Defines, configurable
*
**********************************************************************
*/

#define PROMPT                                                        \
  "emSSH> "

#define SIGNON                                                        \
  "\r\n"                                                              \
  "Welcome to the emSSH command line!  Type Ctrl+D to exit.\r\n"      \
  "\r\n"

#define BANNER \
  "\r\n"                                                              \
  "*************************************************************\r\n" \
  "* This server is powered by SEGGER emSSH.  It simply works! *\r\n" \
  "*************************************************************\r\n" \
  "\r\n"

#define USE_RX_TASK 0

/*********************************************************************
*
*       Local data types
*
**********************************************************************
*/

//
// Task priorities
//
enum {
  TASK_PRIO_IP_APP = 150,
  TASK_PRIO_IP_TASK,         // Priority should be higher than all IP application tasks.
  TASK_PRIO_IP_RX_TASK       // Must be the highest priority of all IP related tasks, comment out to read packets in ISR
};

typedef struct {
  volatile unsigned RdPtr;  // Read pointer
  volatile unsigned WrPtr;  // Write pointer
  volatile unsigned RdCnt;  // Number of bytes read
  volatile unsigned WrCnt;  // Number of bytes written
  unsigned          Capacity;
  U8              * pData;
} RING_BUFFER;

typedef struct {
  SSH_SESSION * pSession;
  unsigned      Cursor;
  int           Ready;
  int           Exit;
  int           Socket;
  RING_BUFFER   TxBuffer;
  RING_BUFFER   RxBuffer;
  OS_TASK       ShellTask;
  OS_TASK       ProtocolTask;
  char          aCommandLine       [70];
  U8            aTxData            [1024];
  U8            aRxData            [128];
  U32           aShellTaskStack    [128];
  U32           aProtocolTaskStack [512];
  U8            aTxBuffer          [4096];
  U8            aRxBuffer          [4096];
} SHELL_CONTEXT;

/*********************************************************************
*
*       Prototypes
*
**********************************************************************
*/

void         MainTask            (void);
static void _ShellMain           (void *pContext);
static void _ProtocolMain        (void *pContext);
static void _TerminalChannelClose(SSH_SESSION *pSession, unsigned Channel);
static int  _TerminalChannelData (      SSH_SESSION * pSession,
                                        unsigned      Channel,
                                  const U8          * pData,
                                        unsigned      DataLen);
static int  _Send                (int Socket, const char *pData, int DataLen, int Flags);
static int  _Recv                (int Socket,       char *pData, int DataLen, int Flags);


/*********************************************************************
*
*       Static const data
*
**********************************************************************
*/

static const SSH_TRANSPORT_API _IP_Transport = {
  _Send,
  _Recv,
  closesocket,
};

static const SSH_CHANNEL_API _TerminalAPI = {
  _TerminalChannelData,
  0,
  0,
  _TerminalChannelClose,
};

/*********************************************************************
*
*       Static data
*
**********************************************************************
*/

//
// Assign one shell context per session
//
static SHELL_CONTEXT _aShell[SSH_CONFIG_MAX_SESSIONS];

static OS_STACKPTR int _IPStack[2048/sizeof(int)];
static OS_TASK         _IPTCB;

static OS_STACKPTR int _ServerStack[4096/sizeof(int)];
static OS_TASK         _ServerTCB;

#if USE_RX_TASK
static OS_STACKPTR int _IPRxStack[1024/sizeof(int)];
static OS_TASK         _IPRxTCB;
#endif

/*********************************************************************
*
*       Static code
*
**********************************************************************
*/

/*********************************************************************
*
*       _Bind()
*
*  Function description
*    Bind a TCP port.
*
*  Parameters
*    Port - Bound port.
*
*  Return value
*    >= 0 - Bound socket handle.
*    <  0 - Error.
*/
static int _Bind(int Port) {
  int    Socket;
  int    Status;
  int    Enable;
  struct sockaddr_in local_addr;
  //
  local_addr.sin_family = AF_INET;
  local_addr.sin_port = htons(Port);
  local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  //
  Socket = Status = socket(PF_INET, SOCK_STREAM, 0);
  if (Status >= 0) {
    Enable = 1;
    Status = setsockopt(Socket, SOL_SOCKET, SO_REUSEADDR, (char *)&Enable, sizeof(Enable));
  }
  if (Status >= 0) {
    Status = bind(Socket, (struct sockaddr *)&local_addr, sizeof(local_addr));
  }
  if (Status >= 0) {
    Status = listen(Socket, 10);
  }
  return Status >= 0 ? Socket : Status;
}

/*********************************************************************
*
*       _Accept()
*
*  Function description
*    Accept an incoming connection.
*
*  Parameters
*    Socket - Socket bound to port.
*
*  Return value
*    >= 0 - Socket handle.
*    <  0 - Error.
*/
static int _Accept(int Socket) {
  struct sockaddr_in client_addr;
  int                client_addr_len;
  //
  client_addr_len = sizeof(client_addr);
  Socket = accept(Socket, (struct sockaddr *) &client_addr, &client_addr_len);
  if (Socket < 0) {
    return -1;
  } else {
    return Socket;
  }
}

/*********************************************************************
*
*       _Send()
*
*  Function description
*    Send data to socket.
*
*  Parameters
*    Socket  - Socket to write to.
*    pData   - Pointer to octet string to send.
*    DataLen - Octet length of the octet string to send.
*    Flags   - Socket send flags.
*
*  Return value
*    >= 0 - Number of bytes sent.
*    <  0 - Error.
*
*  Additional information
*    The number of bytes sent can be less than the number
*    of bytes that were requested to be written.
*/
static int _Send(int Socket, const char *pData, int DataLen, int Flags) {
  int Status;
  //
  Status = send(Socket, pData, DataLen, Flags);
  //
  return Status < 0 ? -1 : Status;
}

/*********************************************************************
*
*       _Recv()
*
*  Function description
*    Receive data from socket.
*
*  Parameters
*    Socket  - Socket to read from.
*    pData   - Pointer to object that receives an octet string.
*    DataLen - Octet length of the octet string to receive.
*    Flags   - Socket-based additional flags.
*
*  Return value
*    >= 0 - Number of bytes received.
*    <  0 - Error.
*
*  Additional information
*    The number of bytes received can be less than the number
*    of bytes requested.
*/
static int _Recv(int Socket, char *pData, int DataLen, int Flags) {
  int Status;
  //
  Status = recv(Socket, pData, DataLen, Flags);
  //
  return Status < 0 ? -1 : Status;
}

/*********************************************************************
*
*       _CanRead()
*
*  Function description
*    Check if data can be read from socket.
*
*  Parameters
*    Socket  - Socket to query.
*
*  Return value
*    >  0 - Socket has data to read.
*    <= 0 - Socket has no data.
*/
static int _CanRead(int Socket) {
  IP_fd_set readset;
  int       Status;
  //
  IP_FD_ZERO(&readset);
  IP_FD_SET(Socket, &readset);
  Status = select(&readset, 0, 0, 0);
  //
  return Status > 0;
}

/*********************************************************************
*
*       _RING_BUFFER_Init()
*
*  Function description
*    Initialize rung buffer.
*
*  Parameters
*    pRB      - Pointer to ring buffer.
*    pData    - Pointer to data area to hold ring buffer content.
*    Capacity - Capacity of the ring buffer.
*/
static void _RING_BUFFER_Init(RING_BUFFER *pRB, void *pData, unsigned Capacity) {
  pRB->RdPtr    = 0;
  pRB->WrPtr    = 0;
  pRB->RdCnt    = 0;
  pRB->WrCnt    = 0;
  pRB->Capacity = Capacity;
  pRB->pData    = pData;
}

/*********************************************************************
*
*       _RING_BUFFER_QueryCanRead()
*
*  Function description
*    Query whether ring buffer can be read.
*
*  Parameters
*    pRB - Pointer to ring buffer.
*
*  Return value
*    Number of bytes that can be read from the buffer.
*/
static unsigned _RING_BUFFER_QueryCanRead(const RING_BUFFER *pRB) {
  return pRB->WrCnt - pRB->RdCnt;
}

/*********************************************************************
*
*       _RING_BUFFER_QueryCanWrite()
*
*  Function description
*    Query whether ring buffer can be written.
*
*  Parameters
*    pRB - Pointer to ring buffer.
*
*  Return value
*    Number of bytes that can be written to the buffer.
*/
static unsigned _RING_BUFFER_QueryCanWrite(const RING_BUFFER *pRB) {
  return pRB->Capacity - (pRB->WrCnt - pRB->RdCnt);
}

/*********************************************************************
*
*       _RING_BUFFER_Wr()
*
*  Function description
*    Write to ring buffer.
*
*  Parameters
*    pRB     - Pointer to ring buffer.
*    pData   - Pointer to data to write.
*    DataLen - Octet length of the data to write.
*
*  Return value
*    Number of bytes that were be written to the buffer.
*    If the buffer becomes full during writing, the
*    number of bytes written will be less than DataLen.
*/
static unsigned _RING_BUFFER_Wr(      RING_BUFFER * pRB,
                                const void        * pData,
                                      unsigned      DataLen) {
  unsigned N;
  unsigned WrPtr;
  unsigned LChunkLen;
  unsigned RChunkLen;
  //
  // If we don't have enough space to write all of this,
  // write part until the ring buffer is full.
  //
  N = _RING_BUFFER_QueryCanWrite(pRB);
  if (N < DataLen) {
    DataLen = N;
  }
  //
  // Divide into two cases: enough space to write without wrapping,
  // and wrapping required.
  //
  WrPtr = pRB->WrPtr;
  if (WrPtr + DataLen <= pRB->Capacity) {
    memcpy(pRB->pData+WrPtr, pData, DataLen);
    WrPtr += DataLen;
    if (WrPtr == pRB->Capacity) {
      WrPtr = 0;
    }
    pRB->WrPtr = WrPtr;
  } else {
    //
    LChunkLen = pRB->Capacity - WrPtr;
    RChunkLen = DataLen - LChunkLen;
    //
    memcpy(pRB->pData+WrPtr, pData, LChunkLen);
    memcpy(pRB->pData, (U8 *)pData+LChunkLen, RChunkLen);
    //
    pRB->WrPtr = RChunkLen;
  }
  //
  pRB->WrCnt += DataLen;
  //
  return DataLen;
}

/*********************************************************************
*
*       _RING_BUFFER_Rd()
*
*  Function description
*    Read from ring buffer.
*
*  Parameters
*    pRB     - Pointer to ring buffer.
*    pData   - Pointer to object that receives the data.
*    DataLen - Octet length of the data to read.
*
*  Return value
*    Number of bytes that were be read from  the buffer.
*    If the buffer becomes empty during reading, the
*    number of bytes read will be less than DataLen.
*/
static unsigned _RING_BUFFER_Rd(RING_BUFFER * pRB,
                                void        * pData,
                                unsigned      DataLen) {
  unsigned RdPtr;
  unsigned LChunkLen;
  unsigned RChunkLen;
  //
  // If we don't have enough space to write all of this,
  // write part until the ring buffer is full.
  //
  RdPtr = _RING_BUFFER_QueryCanRead(pRB);
  if (RdPtr < DataLen) {
    DataLen = RdPtr;
  }
  //
  // Null read?
  //
  if (DataLen == 0) {
    return DataLen;
  }
  //
  // Divide into two cases: enough space to read without wrapping,
  // and wrapping required.
  //
  RdPtr = pRB->RdPtr;
  if (RdPtr + DataLen <= pRB->Capacity) {
    //
    // Enough space to read, no wrapping required.
    //
    memcpy(pData, pRB->pData+RdPtr, DataLen);
    RdPtr += DataLen;
    if (RdPtr == pRB->Capacity) {
      RdPtr = 0;
    }
    pRB->RdPtr = RdPtr;
  } else {
    //
    // Straddles end of buffer, break into two reads.
    //
    LChunkLen = pRB->Capacity - RdPtr;
    RChunkLen = DataLen - LChunkLen;
    //
    memcpy(pData, pRB->pData+RdPtr, LChunkLen);
    memcpy((char *)pData+LChunkLen, pRB->pData, RChunkLen);
    //
    pRB->RdPtr = RChunkLen;
  }
  //
  pRB->RdCnt += DataLen;
  //
  return DataLen;
}

/*********************************************************************
*
*       _AppExit()
*
*  Function description
*    Exit the application with an error.
*
*  Parameters
*    sReason - Reason for exit, displayed for the user.
*/
static void _AppExit(const char *sReason) {
  SSH_Logf(SSH_LOG_APP, sReason);
  SSH_Panic(-100);
}

/*********************************************************************
*
*       _ShellRequest()
*
*  Function description
*    Handle a shell channel request.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel receiving the data.
*    pParas   - Pointer to channel request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _ShellRequest(SSH_SESSION               * pSession,
                         unsigned                    Channel,
                         SSH_CHANNEL_REQUEST_PARAS * pParas) {
  SHELL_CONTEXT * pShell;
  int             Status;
  //
  Status = 0;
  if (pParas->WantReply) {
    Status = SSH_CHANNEL_SendSuccess(pSession, Channel);
  }
  //
  pShell = &_aShell[SSH_SESSION_QueryIndex(pSession)];
  SSH_CHANNEL_Config(pSession, Channel, 128, &_TerminalAPI, pShell);
  OS_CreateTaskEx(&pShell->ShellTask,
                  "ShellTask",
                  100,
                  _ShellMain,
                  &pShell->aShellTaskStack,
                  sizeof(pShell->aShellTaskStack),
                  10,
                  pSession);
  //
  return Status;
}

/*********************************************************************
*
*       _TerminalChannelData()
*
*  Function description
*    Handle data received from peer.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel receiving the data.
*    pData    - Pointer to object that contains the data.
*    DataLen  - Octet length of the object that contains the data.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*
*  Additional information
*    Provide in-callback handling of a command line processor.
*    This sample supports only one connection at a time.
*/
static int _TerminalChannelData(      SSH_SESSION * pSession,
                                      unsigned      Channel,
                                const U8          * pData,
                                      unsigned      DataLen) {
  SHELL_CONTEXT *pShell;
  //
  SSH_USE_PARA(pSession);
  SSH_USE_PARA(Channel);
  //
  pShell = &_aShell[SSH_SESSION_QueryIndex(pSession)];
  _RING_BUFFER_Wr(&pShell->RxBuffer, pData, DataLen);
  OS_WakeTask(&pShell->ShellTask);
  //
  return 0;
}

/*********************************************************************
*
*       _TerminalChannelClose()
*
*  Function description
*    Handle channel closure.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel receiving the data.
*
*  Additional information
*    When the channel carrying the shell is closed, terminate
*    the associated shell task.
*/
static void _TerminalChannelClose(SSH_SESSION * pSession, unsigned Channel) {
  SHELL_CONTEXT *pShell;
  //
  SSH_USE_PARA(Channel);
  //
  pShell = &_aShell[SSH_SESSION_QueryIndex(pSession)];
  OS_TerminateTask(&pShell->ShellTask);
}

/*********************************************************************
*
*       _TerminalRequest()
*
*  Function description
*    Request a terminal.
*
*  Parameters
*    pSession - Pointer to session.
*    Channel  - Local channel requesting the terminal.
*    pParas   - Pointer to channel request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _TerminalRequest(SSH_SESSION               * pSession,
                            unsigned                    Channel,
                            SSH_CHANNEL_REQUEST_PARAS * pParas) {
  int Status;
  //
  if (pParas->WantReply) {
    Status = SSH_CHANNEL_SendSuccess(pSession, Channel);
  } else {
    Status = 0;
  }
  //
  return Status;
}

/*********************************************************************
*
*       _UserauthServiceRequest()
*
*  Function description
*    Request the user authentication service.
*
*  Parameters
*    pSession     - Pointer to session.
*    sServiceName - Service being requested.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*
*  Additional information
*    Displays a banner before user authentication commences.
*/
static int _UserauthServiceRequest(SSH_SESSION *pSession, const char *sServiceName) {
  int Status;
  //
  Status = SSH_SESSION_SendServiceAccept(pSession, sServiceName);
  if (Status >= 0) {
    Status = SSH_SESSION_SendUserauthBanner(pSession, BANNER, "en");
  }
  //
  return Status;
}

/*********************************************************************
*
*       _UserauthRequestNone()
*
*  Function description
*    Request authentication of user with method "none".
*
*  Parameters
*    pSession  - Pointer to session.
*    pReqParas - Pointer to user authentication request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _UserauthRequestNone(SSH_SESSION                * pSession,
                                SSH_USERAUTH_REQUEST_PARAS * pReqParas) {
  SSH_USERAUTH_NONE_PARAS NoneParas;
  int                     Status;
  //
  SSH_USE_PARA(pSession);
  //
  Status = SSH_USERAUTH_NONE_ParseParas(pReqParas, &NoneParas);
  if (Status < 0) {
    Status = SSH_ERROR_USERAUTH_FAIL;
  } else if (pReqParas->UserNameLen == 4 &&
             SSH_MEMCMP(pReqParas->pUserName, "anon", 4) == 0) {
    Status = 0;
  } else {
    Status = SSH_ERROR_USERAUTH_FAIL;
  }
  //
  return Status;
}

/*********************************************************************
*
*       _UserauthRequestPassword()
*
*  Function description
*    Request authentication of user with method "password".
*
*  Parameters
*    pSession  - Pointer to session.
*    pReqParas - Pointer to user authentication request parameters.
*
*  Return value
*   >= 0 - Success.
*   <  0 - Error.
*/
static int _UserauthRequestPassword(SSH_SESSION                * pSession,
                                    SSH_USERAUTH_REQUEST_PARAS * pReqParas) {
  SSH_USERAUTH_PASSWORD_PARAS PasswordParas;
  int                         Status;
  //
  SSH_USE_PARA(pSession);
  //
  Status = SSH_USERAUTH_PASSWORD_ParseParas(pReqParas, &PasswordParas);
  if (Status < 0) {
    Status = SSH_ERROR_USERAUTH_FAIL;
  } else if (pReqParas->UserNameLen == 5 &&
             memcmp(pReqParas->pUserName, "admin", 5) == 0) {
    if (PasswordParas.PasswordLen == 6 &&
        memcmp(PasswordParas.pPassword, "secret", 6) == 0) {
      Status = 0;
    } else {
      Status = SSH_ERROR_USERAUTH_FAIL;
    }
  } else {
    Status = SSH_ERROR_USERAUTH_FAIL;
  }
  //
  return Status;
}

/*********************************************************************
*
*       _Puts()
*
*  Function description
*    Send string to peer.
*
*  Parameters
*    pShell - Pointer to shell context.
*    sText  - String to send.
*/
static void _Puts(SHELL_CONTEXT *pShell, const char *sText) {
  unsigned Len;
  unsigned ChunkLen;
  //
  Len = strlen(sText);
  //
  while (Len > 0) {
    ChunkLen = _RING_BUFFER_Wr(&pShell->TxBuffer, sText, Len);
    sText += ChunkLen;
    Len   -= ChunkLen;
    if (Len != 0) {
      OS_Delay(10000);
    }
  }
}

/*********************************************************************
*
*       _Putc()
*
*  Function description
*    Send character peer.
*
*  Parameters
*    pShell - Pointer to shell context.
*    Ch     - Character to send.
*/
static void _Putc(SHELL_CONTEXT *pShell, U8 Ch) {
  char aBuf[2];
  //
  aBuf[0] = (char)Ch;
  aBuf[1] = 0;
  //
  _Puts(pShell, aBuf);
}

/*********************************************************************
*
*       _Getc()
*
*  Parameters
*    pShell - Pointer to shell context.
*
*  Function description
*    Read character from peer.
*
*  Return value
*    Character read.
*/
static U8 _Getc(SHELL_CONTEXT *pShell) {
  U8 Ch;
  //
  while (_RING_BUFFER_QueryCanRead(&pShell->RxBuffer) == 0) {
    OS_Delay(10000);
  }
  _RING_BUFFER_Rd(&pShell->RxBuffer, &Ch, 1);
  return Ch;
}

/*********************************************************************
*
*       _Gets()
*
*  Function description
*    Read string from peer.
*
*  Parameters
*    pShell - Pointer to shell context.
*
*  Return value
*    Character read.
*/
static int _Gets(SHELL_CONTEXT *pShell) {
  U8 Ch;
  //
  pShell->Cursor = 0;
  //
  for (;;) {
    Ch = _Getc(pShell);
    if (0x20 <= Ch && Ch <= 0x7E) {
      if (pShell->Cursor < sizeof(pShell->aCommandLine)-1) {
        pShell->aCommandLine[pShell->Cursor++] = Ch;
        _Putc(pShell, Ch);
      }
    } else if (Ch == 0x08 || Ch == 0x7F) {
      if (pShell->Cursor > 0) {
        --pShell->Cursor;
        _Puts(pShell, "\b \b");
      }
    } else if (Ch == '\r') {
      pShell->aCommandLine[pShell->Cursor++] = 0;
      return 0;
    } else if (Ch == 0x04) {
      return -1;
    }
  }
}

/*********************************************************************
*
*       _ShellMain()
*
*  Function description
*    Main command line processor acting as a shell.
*
*  Parameters
*    pContext - Pointer to SSH session.
*/
static void _ShellMain(void *pContext) {
  SSH_SESSION   * pSession;
  SHELL_CONTEXT * pShell;
  //
  pSession = pContext;
  pShell = &_aShell[SSH_SESSION_QueryIndex(pSession)];
  //
  _Puts(pShell, SIGNON);
  for (;;) {
    _Puts(pShell, PROMPT);
    if (_Gets(pShell) == 0) {
      _Puts(pShell, "\r\n...");
      _Puts(pShell, pShell->aCommandLine);
      _Puts(pShell, "\r\n");
    } else {
      pShell->Exit = 1;
    }
  }
}

/*********************************************************************
*
*       _ServiceChannel()
*
*  Function description
*    Service a shell channel.
*
*  Parameters
*    pSession - Pointer to SSH session.
*    Channel  - Local channel.
*
*  Return value
*    >  0 - Processed channel.
*    == 0 - Channel required no processing.
*    <  0 - Error.
*/
static int _ServiceChannel(SSH_SESSION *pSession, unsigned Channel) {
  SHELL_CONTEXT * pShell;
  unsigned        Len;
  U8              aData[64];
  int             Status;
  //
  pShell = &_aShell[SSH_SESSION_QueryIndex(pSession)];
  if (pShell == 0) {
    Status = 0;
  } else if (pShell->Exit) {
    if (SSH_CHANNEL_QueryCanWrite(pSession, Channel) >= 12) {
      Status = SSH_CHANNEL_SendData(pShell->pSession, Channel, "\r\n\r\nBye!\r\n\r\n", 12);
    }
    SSH_SESSION_Disconnect(pShell->pSession, SSH_DISCONNECT_BY_APPLICATION);
    Status = -1;
  } else if (_RING_BUFFER_QueryCanRead(&pShell->TxBuffer) > 0) {
    Len = _RING_BUFFER_QueryCanRead(&pShell->TxBuffer);
    Len = SEGGER_MIN(Len, SSH_CHANNEL_QueryCanWrite(pShell->pSession, Channel));
    Len = SEGGER_MIN(Len, sizeof(aData));
    if (Len > 0) {
      _RING_BUFFER_Rd(&pShell->TxBuffer, aData, Len);
      Status = SSH_CHANNEL_SendData(pShell->pSession, Channel, aData, Len);
      OS_WakeTask(&pShell->ShellTask);
    }
    Status = 1;
  } else {
    Status = 0;
  }
  //
  return Status;
}

/*********************************************************************
*
*       _ProtocolMain()
*
*  Function description
*    Handle SSH protocol.
*
*  Parameters
*    pContext - Pointer to SSH session.
*
*  Additional information
*    Acts rather like sshd.
*/
static void _ProtocolMain(void *pContext) {
  SSH_SESSION   * pSession;
  int             Socket;
  int             Status;
  //
  pSession = pContext;
  Socket = SSH_SESSION_QuerySocket(pSession);
  for (;;) {
    Status = 0;
    if (_CanRead(Socket)) {
      Status = SSH_SESSION_Process(pSession);
    } else {
      Status = SSH_SESSION_IterateChannels(pSession, _ServiceChannel);
    }
    if (Status < 0) {
      OS_TerminateTask(0);
    } else if (Status == 0) {
      OS_Delay(10);
    }
  }
}

/*********************************************************************
*
*       _SSHTask()
*
*  Function description
*    Listen for incoming connections and start SSH handler tasks.
*
*  Additional information
*    Acts rather like sshd.
*/
static void _SSHTask(void) {
  int             BoundSocket;
  int             Socket;
  unsigned        SessionIndex;
  SSH_SESSION   * pSession;
  SHELL_CONTEXT * pShell;
  //
  // Hook user authentication to display a banner.  Allow "none"
  // and "password" user authentication.
  //
  SSH_SERVICE_Add(&SSH_SERVICE_USERAUTH, _UserauthServiceRequest);
  SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_NONE,     _UserauthRequestNone);
  SSH_USERAUTH_METHOD_Add(&SSH_USERAUTH_METHOD_PASSWORD, _UserauthRequestPassword);
  //
  // Add support for interactive shells.
  //
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_SHELL,         _ShellRequest);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_ENV,           NULL);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_PTYREQ,        _TerminalRequest);
  SSH_CHANNEL_REQUEST_Add(&SSH_CHANNEL_REQUEST_WINDOW_CHANGE, NULL);
  //
  // Bind SSH port.
  //
  BoundSocket = _Bind(22);
  if (BoundSocket < 0) {
    _AppExit("Cannot bind port 22!");
  }
  //
  for (;;) {
    //
    // Try to allocate a session for the next incoming connection.
    //
    SSH_SESSION_Alloc(&pSession);
    if (pSession == NULL) {
      OS_Delay(100);
      continue;
    }
    //
    do {
      Socket = _Accept(BoundSocket);
    } while (Socket < 0);
    //
    SessionIndex = SSH_SESSION_QueryIndex(pSession);
    pShell = &_aShell[SessionIndex];
    memset(pShell, 0, sizeof(*pShell));
    pShell->pSession = pSession;
    SSH_SESSION_Init(pShell->pSession, Socket, &_IP_Transport);
    SSH_SESSION_ConfBuffers(pShell->pSession,
                            pShell->aRxBuffer, sizeof(pShell->aRxBuffer),
                            pShell->aTxBuffer, sizeof(pShell->aTxBuffer));
    _RING_BUFFER_Init(&pShell->RxBuffer, pShell->aRxData, sizeof(pShell->aRxData));
    _RING_BUFFER_Init(&pShell->TxBuffer, pShell->aTxData, sizeof(pShell->aTxData));
    //
    OS_CreateTaskEx(&pShell->ProtocolTask,
                    "ProtocolTask",
                    100,
                    _ProtocolMain,
                    &pShell->aProtocolTaskStack,
                    sizeof(pShell->aProtocolTaskStack),
                    10,
                    pSession);
  }
}

/*********************************************************************
*
*             Public code
*
**********************************************************************
*/

/*********************************************************************
*
*       MainTask()
*
*  Function description
*    Application entry point.
*/
void MainTask(void) {
  //
  int IFaceId;
  IP_Init();
  IFaceId = IP_INFO_GetNumInterfaces() - 1;  // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
  SSH_Init();
  //
  OS_CREATETASK(&_IPTCB,   "IP_Task",   IP_Task,    TASK_PRIO_IP_TASK,    _IPStack);    // Start the IP task
#if USE_RX_TASK
  OS_CREATETASK(&_IPRxTCB, "IP_RxTask", IP_RxTask , TASK_PRIO_IP_RX_TASK, _IPRxStack);  // Start the IP_RxTask, optional.
#endif
  IP_Connect(IFaceId);
  while (IP_IFaceIsReady() == 0) {
    OS_Delay(50);
  }
  //
  // SSH connections may require more stack than MainTask()
  // is given by the canned startup code.  Therefore we start the
  // SSH-based task with more stack so that it runs correctly and
  // kill off MainTask.
  //
  OS_CREATETASK(&_ServerTCB, "SSHTask", _SSHTask, TASK_PRIO_IP_APP, _ServerStack);
  OS_TerminateTask(0);
}

/*************************** End of file ****************************/

Adding emSSH to your project

In this section we assume that you have a fully-functioning embOS/IP project that is able to connect to the network and all that is required is to add emSSH to the project. You can use the sample “start” projects as a reference when setting up your own application.

  Set up include directories

You should make sure that the include path contains the following directories (the order of inclusion is of no importance):

Note

Always make sure that you have only one version of each file!

It is frequently a major problem when updating to a new version of emSSH if you have old files included and therefore mix different versions. If you keep emSSH in the directories as suggested (and only in these), this type of problem cannot occur. When updating to a newer version, you should be able to keep your configuration files and leave them unchanged. For safety reasons, we recommend backing up (or at least renaming) the existing directories before updating.

  Add source files

Add the source code files that you find in the shipment to your project. The SEGGER and CRYPTO folders are shared components.

  Initialize emSSH

You initialize emSSH using SSH_Init(). You must call SSH_Init() before using any other emSSH API function.

With these three steps, emSSH is installed and ready to run. You will need to add some additional code to handle incoming connection requests and configure the services and facilities that emSSH provides as described in previous sections.

Server identity

A key component of the SSH protocol is that the server is authenticated using public key cryptography: the server presents its public key that corresponds to the public key algorithm negotiated between the client and server.

The following sections describe how this key is generated and installed, and the performance of the key exchange and public key algorithms at the server end of the connection.

Public key types

emSSH supports four types of public keys:

Each of these is fully described in the SSH RFCs, but you do not need to be familiar with the SSH protocol or RFCs in order to add emSSH to your application.

Public keys for emSSH are created using SSH_KeyGen which is capable of generating all key types supported by SSH.

Each of the public key algorithms requires a different type of public and private key pair. This section describes how to generate and install those key pairs for emSSH.

RSA keys

The public key method SSH_PK_ALGORITHM_SSH_RSA requires an RSA public key pair to be installed. For reference, this method corresponds to the ssh-rsa public key method in the SSH specifications.

Creating keys

You can generate an RSA public key pair using SSH_KeyGen:

MacBook:~ paul$ ssh_keygen -rsa -k SSH_ServerKeys_RSA_ -x >SSH_ServerKeys_RSA.c

emSSH KeyGen V2.40 compiled Jun 20 2017 12:12:14
(c) 2014-2017 SEGGER Microcontroller GmbH    www.segger.com

Selecting a random initial seed
Generating probabilistic prime key pair with public modulus of 1024 bits
Checking keys are consistent: OK

MacBook:~ paul$ _

-rsa Commands the key generator to create RSA keys. ## -l 1024 Asks that the modulus, or key length, be set to 1,024 bits. The default is 2,048 bits, and we reduce it here by way of example, to keep the presented listing of the key pair within reason. ## -nf Selects non-FIPS generation of the key pair. For key lengths less than 1,024 bits, this is required. ## -k SSH_ServerKeys_RSA_ Sets the prefix used for the public key declarations. ## -x Requests that the key structures have external linkage so they can be separately compiled; the alternative, without -x, is to generate them with static storage and, in this case, the keys would need to be manually added to an existing source file that uses them. ## >ServerKeys_RSA.c Writes the generated key declarations to the file ServerKeys_RSA.c. ##

The unabridged output of the key generated above is presented in Generated RSA keys.

To generate smaller keys you must drop into non-FIPS mode. We recommend that you use FIPS generation of proven primes and a minimum key length of 2,048 bits, which is the default RSA key generation mode of SSH_KeyGen:

MacBook:~ paul$ ssh_keygen -rsa -k ServerKeys_RSA -x >ServerKeys_RSA.c

emSSH KeyGen V2.40 compiled Jun 20 2017 12:12:14
(c) 2014-2017 SEGGER Microcontroller GmbH    www.segger.com

Selecting a random initial seed
Generating proven prime key pair with public modulus of 2048 bits
Public encryption exponent is set to 65537
Initial seed is 0x3ACF10CB2B7BDBBEFCCEACB9F58176F4
Checking keys are consistent: OK

MacBook:~ paul$ _

The time to create the keys depends upon the length of the modulus, the speed of the PC used to generate them, and randomness: keys can take a few seconds to tens of seconds to create because of the algorithms involved and how lucky the algorithms are on stumbling upon, or constructing, a prime.

Installing keys

From the listing presented in Generated RSA keys, there are two relevant constant structures that define the public key pair. In this case, the structures are named ServerKeys_RSA_PrivateKey and ServerKeys_RSA_PublicKey.

To install these into emSSH we must create two functions that deliver them when requested:

static const CRYPTO_RSA_PUBLIC_KEY * _SSH_GetRSAPublicKey(const char *sName) {
  return &ServerKeys_RSA_PublicKey;
}

static const CRYPTO_RSA_PRIVATE_KEY * _SSH_GetRSAPrivateKey(const char *sName) {
  return &ServerKeys_RSA_PrivateKey;
}

In the same file we must declare the keys external…

extern const CRYPTO_RSA_PUBLIC_KEY  ServerKeys_RSA_PublicKey;
extern const CRYPTO_RSA_PRIVATE_KEY ServerKeys_RSA_PrivateKey;

…and add the two functions to the list of callbacks that emSSH can invoke to retrieve the keys:

static const SSH_HOSTKEY_API _SSH_HostKeyAPI = {
  _SSH_GetRSAPublicKey, _SSH_GetRSAPrivateKey,
  0,                    0,
  0,                    0,
  0,                    0,
};

The zeros in the API are placeholders for other types of key: we initialize them to zero to silence compilers that warn when an initializer does not have entries for every member.

Once this framework is set up, it’s installed into emSSH using SSH_SetHostKeyAPI which you should add to your SSH_X_Config() function (see Runtime configuration):

SSH_SetHostKeyAPI(&_SSH_HostKeyAPI);

That’s it! The RSA keys are set up and installed, ready for use.

DSA keys

The public key method SSH_PK_ALGORITHM_SSH_DSA requires a DSA public key pair to be installed. For reference, this method corresponds to the ssh-dss public key method in the SSH specifications.

Creating keys

Generating a DSA key pair is similar to RSA with SSH_KeyGen:

MacBook:~ paul$ ssh_keygen -dsa -k SSH_ServerKeys_DSA_ -x >SSH_ServerKeys_DSA.c

emSSH KeyGen V2.40 compiled Jun 20 2017 12:12:14
(c) 2014-2017 SEGGER Microcontroller GmbH    www.segger.com

Generating DSA domain (L=2048, N=256) with proven primes

MacBook:~ paul$ _

Only the key type differs here, using -dsa to generate the keys.

In contrast to RSA which uses the product of two primes of equal length as a modulus, DSA uses a different scheme with two primes (l and n) with differing lengths.

We recommend that you use FIPS generation of proven primes and a minimum l key length of 2,048 bits, which is the default DSA key generation mode of SSH_Keygen.

The prime lengths to use for l and n when creating keys can be set on SSH_KeyGen’s command line. If generating keys with prime lengths that do not conform to any FIPS combination, you must use non-FIPS algorithms, activated by -nf.

The unabridged output of a key generated with l=1024 and n=160 is presented in Generated DSA keys.

Note that the SSH protocol only supports DSA keys with with l=1024 and n=160.

Installing keys

Installing DSA keys is slightly different to installing RSA keys: there are three constant structures that define the public key pair. In this case, the structures are named ServerKeys_DSA_PrivateKey, ServerKeys_DSA_PublicKey, and ServerKeys_DSA_DomainParas.

To install these into emSSH we must create two functions that deliver them when requested:

static void _SSH_GetDSAPublicKey(const char                      * sName,
                                 const CRYPTO_DSA_PUBLIC_KEY    ** ppPublicKey,
                                 const CRYPTO_DSA_DOMAIN_PARAMS ** ppDomainParas) {
  (void)sName;
  //
  *ppPublicKey   = &SSH_ServerKeys_DSA_PublicKey;
  *ppDomainParas = &SSH_ServerKeys_DSA_DomainParas;
}

static void _SSH_GetDSAPrivateKey(const char                      * sName,
                                  const CRYPTO_DSA_PRIVATE_KEY   ** ppPrivateKey,
                                  const CRYPTO_DSA_DOMAIN_PARAMS ** ppDomainParas) {
  (void)sName;
  //
  *ppPrivateKey  = &SSH_ServerKeys_DSA_PrivateKey;
  *ppDomainParas = &SSH_ServerKeys_DSA_DomainParas;
}

In the same file we must declare the keys and domain external…

extern const CRYPTO_DSA_PUBLIC_KEY    SSH_ServerKeys_DSA_PublicKey;
extern const CRYPTO_DSA_PRIVATE_KEY   SSH_ServerKeys_DSA_PrivateKey;
extern const CRYPTO_DSA_DOMAIN_PARAMS SSH_ServerKeys_DSA_DomainParas;

…and add the two functions to the list of callbacks that emSSH can invoke to retrieve the keys:

static const SSH_HOSTKEY_API _SSH_HostKeyAPI = {
  0,                    0,
  0,                    0,
  0,                    0,
  _SSH_GetDSAPublicKey, _SSH_GetDSAPrivateKey,
};

The zeros in the API are placeholders for other types of key.

ECDSA keys

The public key methods

each require an ECDSA public key pair to be installed, generated from the appropriate curve. For reference, these methods correspond to the ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, and ecdsa-sha2-nistp521 public key methods in the SSH specifications.

Creating keys

Generating a ECDSA key pair requires that the curve be specified:

MacBook:~ paul$ ssh_keygen -ecdsa -p256 -k SSH_ServerKeys_ECDSA_P256_ -x \
> >SSH_ServerKeys_ECDSA_P256.c

emSSH KeyGen V2.40 compiled Jun 20 2017 12:12:14
(c) 2014-2017 SEGGER Microcontroller GmbH    www.segger.com

Generating random key pair on P-256
Checking generated keys for consistency: OK

MacBook:~ paul$ _

Installing keys

To install these into emSSH we must create two functions that deliver them when requested:

static const CRYPTO_ECDSA_PUBLIC_KEY * _SSH_GetECDSAPublicKey(const char *sName) {
  if (SSH_STRCMP(sName, "nistp256") == 0) {
    return &SSH_ServerKeys_ECDSA_P256_PublicKey;
  } else {
    return 0;
  }
}

static const CRYPTO_ECDSA_PRIVATE_KEY * _SSH_GetECDSAPrivateKey(const char *sName) {
  if (SSH_STRCMP(sName, "nistp256") == 0) {
    return &SSH_ServerKeys_ECDSA_P256_PrivateKey;
  } else {
    return 0;
  }
}

In the same file we must declare the keys external…

extern const CRYPTO_ECDSA_PUBLIC_KEY  SSH_ServerKeys_ECDSA_P256_PublicKey;
extern const CRYPTO_ECDSA_PRIVATE_KEY SSH_ServerKeys_ECDSA_P256_PrivateKey;

…and add the two functions to the list of callbacks that emSSH can invoke to retrieve the keys:

static const SSH_HOSTKEY_API _SSH_HostKeyAPI = {
  0,                      0,
  _SSH_GetECDSAPublicKey, _SSH_GetECDSAPrivateKey,
  0,                      0,
  0,                      0,
};

This example installs keys for the P-256 curve, but you can install keys for the curves you wish to support. Refer to Shipped sample configuration for further information.

EdDSA keys

The public key methods SSH_PK_ALGORITHM_SSH_ED25519 and SSH_PK_ALGORITHM_SSH_ED448 requires an EdDSA public key pair to be installed. For reference, these methods correspond to the ssh-ed25519 and ssh=ed448 public key methods in the SSH specifications.

Creating keys

Generating an EdDSA key pair requires no additional information as the key length is fixed:

MacBook:~ paul$ ssh_keygen -ed25519 -k SSH_ServerKeys_EdDSA_ -x \
> >SSH_ServerKeys_Ed25519.c

emSSH KeyGen V2.40 compiled Jun 20 2017 12:12:14
(c) 2014-2017 SEGGER Microcontroller GmbH    www.segger.com

Generating random EdDSA key pair

MacBook:~ paul$ _

Installing keys

To install these into emSSH we must create two functions that deliver them when requested:

static const CRYPTO_EdDSA_PUBLIC_KEY * _SSH_GetEdDSAPublicKey(const char *sName) {
  if (SSH_STRCMP(sName, "ssh-ed25519") == 0) {
    return &SSH_ServerKeys_Ed25519_PublicKey;
  } else {
    return NULL;
  }
}

static const CRYPTO_EdDSA_PRIVATE_KEY  * _SSH_GetEdDSAPrivateKey(const char *sName) {
  if (SSH_STRCMP(sName, "ssh-ed25519") == 0) {
    return &SSH_ServerKeys_Ed25519_PrivateKey;
  } else {
    return NULL;
  }
}

In the same file we must declare the keys external…

extern const CRYPTO_EdDSA_PUBLIC_KEY  SSH_ServerKeys_Ed25519_PublicKey;
extern const CRYPTO_EdDSA_PRIVATE_KEY SSH_ServerKeys_Ed25519_PrivateKey;

…and add the two functions to the list of callbacks that emSSH can invoke to retrieve the keys:

static const SSH_HOSTKEY_API _SSH_HostKeyAPI = {
  NULL,                   NULL,
  NULL,                   NULL,
  _SSH_GetEdDSAPublicKey, _SSH_GetEdDSAPrivateKey,
  NULL,                   NULL
};

SSH key generator reference

emSSH KeyGen generates a public and a private key. The generation parameters can be set via command line options, by default a random 2048 bit key is generated. The keys are saved in a common key file format and can be published and exchanged.

Usage

SSH_KeyGen.exe [<Options>]

General options

emSSH KeyGen accepts the following command line options.

Option Description
-h Print usage information and available command line options.
-q Operate silently and do not print log output.
-v Increase verbosity of log output.
-k string Set the object name prefix to string. Default is empty.
-rsa Generate RSA keys.
-dsa Generate DSA keys.
-ecdsa Generate ECDSA keys.
-ed25519 Generate Ed25519 keys.
-ed448 Generate Ed448 keys.

RSA options

emSSH KeyGen accepts the following command line options for RSA key generation.

Option Description
-l n Set the modulus length to n bits. Default is 2048. For modulus lengths that other than 2048 and 3072, -nf is required.
-e x Set the exponent to x bits. Default is 65537.
-seed n Set the initial seed for random number generation to n. Default is random.
-pw string Generate the initial seed from the pass phrase string.
-f Generate proven primes for the key pair according to FIPS algorithms. This option is default and recommended to be used with a key length of 2048 bits.
-nf Generate probabilistic primes for the key pair.

DSA options

emSSH KeyGen accepts the following command line options for DSA key generation.

Option Description
-l n Set L to n bits for the prime P. Default is 2048.
-n n Set N to n bits for the prime Q. Default is 256.
-f Generate proven primes for the key pair according to FIPS algorithms. This option is default and recommended to be used with a key length of 2048 bits.
-nf Generate probabilistic primes for the key pair.

ECDSA options

emSSH KeyGen accepts the following command line options for ECDSA key generation.

Option Description
-p256 Use P-256 as the base curve.
-p384 Use P-384 as the base curve.
-p521 Use P-521 as the base curve.

Example generated keys

The keys in the emSSH distribution and produced below were generated from the following Windows batch file:

@ECHO OFF

REM /*********************************************************************
REM *                   (c) SEGGER Microcontroller GmbH                  *
REM *                        The Embedded Experts                        *
REM *                           www.segger.com                           *
REM *********************************************************************/
REM 
REM File    : MakeKeys.bat
REM Purpose : Generate all sample SSH keys.
REM

SSH_KeyGen -rsa -nf -l 1024        -x -k SSH_ServerKeys_RSA_Temp_1024b_  >SSH_ServerKeys_RSA.c
SSH_KeyGen -rsa -nf -l 2048        -x -k SSH_ServerKeys_RSA_Temp_2048b_ >>SSH_ServerKeys_RSA.c
SSH_KeyGen -rsa -nf -l 2048        -x -k SSH_ServerKeys_RSA_Host_2048b_ >>SSH_ServerKeys_RSA.c

SSH_KeyGen -dsa -nf -l 1024 -n 160 -x -k SSH_ServerKeys_DSA_1024b_160b_  >SSH_ServerKeys_DSA.c
SSH_KeyGen -dsa -nf -l 2048 -n 160 -x -k SSH_ServerKeys_DSA_2048b_160b_ >>SSH_ServerKeys_DSA.c
SSH_KeyGen -dsa -nf -l 2048 -n 256 -x -k SSH_ServerKeys_DSA_2048b_256b_ >>SSH_ServerKeys_DSA.c
SSH_KeyGen -dsa -nf -l 3072 -n 256 -x -k SSH_ServerKeys_DSA_3072b_256b_ >>SSH_ServerKeys_DSA.c

SSH_KeyGen -ecdsa -p256            -x -k SSH_ServerKeys_ECDSA_P256_      >SSH_ServerKeys_ECDSA.c
SSH_KeyGen -ecdsa -p384            -x -k SSH_ServerKeys_ECDSA_P384_     >>SSH_ServerKeys_ECDSA.c
SSH_KeyGen -ecdsa -p521            -x -k SSH_ServerKeys_ECDSA_P521_     >>SSH_ServerKeys_ECDSA.c

SSH_KeyGen -ed25519                -x -k SSH_ServerKeys_Ed25519_         >SSH_ServerKeys_EdDSA.c
SSH_KeyGen -ed448                  -x -k SSH_ServerKeys_Ed448_          >>SSH_ServerKeys_EdDSA.c

ECHO Keys generated OK!

REM /*************************** End of file ****************************/

Generated RSA keys

#include "CRYPTO.h"

const CRYPTO_MPI_LIMB SSH_ServerKeys_RSA_Temp_1024b_PublicKey_N_aLimbs[] = {
  CRYPTO_MPI_LIMB_DATA4(0xBF, 0xBA, 0xFC, 0xB1),
  CRYPTO_MPI_LIMB_DATA4(0x82, 0x6E, 0x76, 0xD1),
  CRYPTO_MPI_LIMB_DATA4(0xC7, 0x85, 0xD7, 0x6D