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:
- Highly modular such that unused features are never linked into the application.
- Be completely runtime configurable, adding each modular feature as needed.
- Present a simple user-level API that is easy to use without extensive setup.
- Easy to maintain both by SEGGER and anybody with access to the sources.
- Conform to all necessary standards and current best practices.
- Be efficient both in terms of resource usage and execution speed.
- Target 8-bit to 32-bit processors with limited resources as well as workstations.
- Use the the SEGGER Cryptographic Toolkit, the foundation of all SEGGER security products.
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:
- ISO/ANSI C source code.
- High performance.
- Small footprint.
- Runs “out-of-the-box”.
- Highly compact implementation runs effortlessly on single-chip MCUs.
- Easy-to-understand and simple-to-use API.
- Simple configuration.
- Secure any open channel (e.g. serial line or wireless link).
- Wide range of encryption schemes, key exchange schemes, for interoperability with popular clients.
- Modular architecture links only what you need.
- Plug-in hardware acceleration.
- Broad support for confidentiality, integrity and authentication.
- Diffie-Hellman Ephemeral supporting forward secrecy.
- Elliptic curve key agreement for reduced key sizes.
- Royalty-free.
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:

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 then opens up a terminal window:

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:

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:

emSSH will then display its configuration and indicate that it’s 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:
- A pointer to the session that carries the channel.
- The channel that is to be configured.
- A data capacity that limits incoming data and bandwidth consumed.
- Callbacks for handling events that occur on the channel.
- A user context to be associated with the channel.
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:
- A pointer to the session that carries the channel.
- The channel that has received data.
- A pointer to the data that has been received.
- The number of bytes that have been received.
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:
- a task to handle the messaging layer of the SSH protocol and
to deal with emptying and filling application buffers;
- a task that runs as the client shell task that is decoupled
from SSH and interfaces to the outside using some high-level
functions.
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).
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
- SSH_PK_ALGORITHM_ECDSA_SHA2_NISTP256
- SSH_PK_ALGORITHM_ECDSA_SHA2_NISTP384
- SSH_PK_ALGORITHM_ECDSA_SHA2_NISTP521
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