?

Log in

 
 
18 January 2009 @ 03:36 pm
sslrsh: Remote Shell over SSL using certificate authentication  
sslrsh, which stands for SSL Remote Shell, allows you to log in to a remote system over an SSL connection, using X.509 certificates for encryption and for authentication. It's similar to SSH. sslrsh is a shell script. Most of the heavy lifting in the script is done by socat. The same script can run as both client and server.

This script signals a return to my favorite subject, tunneling. My last discussion on this subject got a bit out of hand. My last useful discussion on this subject was based on SSH, and was unique in that it worked without needing root privileges on the remote side of the tunnel. sslrsh is not actually a tunneling tool. It's a remote shell tool. But it's a good introduction for future posts that will use some of these same tools to set up VPN-style tunnels. Before I wrap up this trip through memory lane and get to the point, I want to remind you about mini_ca. We'll be needing some certificate action for this script, and for that we need a Certificate Authority. You can use mini_ca to generate the needed certificates, or you can be difficult and get them some other way.

Here's the usage & license statement:

BEGIN USAGE

Usage: sslrsh [-h]
sslrsh [-p port] [-P proxy:port] [-c /path/to/cert] [-a /path/to/cacert] remotehost
sslrsh -s [-p port] [-c /path/to/certificate] [-a /path/to/cacertificate] [ -e shell commands to execute ]

-h This helpful information.
-p Port to use (currently 1479).
-P CONNECT proxy server to use. http_proxy environment variable will be used if set, but will be overridden by this flag.
-c Path to the client or server certificate (must include key)
(currently "sslrsh-client.pem" for client, "sslrsh-server.pem" for server)
-a Path to the signing Certificate Authority's certificate
(currently "sslrsh-cacert.pem")
remotehost System to connect to (client mode)
-s Listen for connections (server mode)
-e Shell command or commands to execute as the server, defaults to "echo Welcome to sslrsh on argentina; /bin/bash --login -i"

sslrsh is copyright 2009 by Jeremy D. Impson <jdimpson@acm.org>.
Licensed under the Apache License, Version 2.0 (the License); you
may not use this file except in compliance with the License. You may
obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
END USAGE

sslrsh needs three files: a Certificate Authority (CA) certificate. a server certificate, and a client certificate. The client and server certificates must also have their private keys embedded within them. The CA certificate must have issued both the server and client certificates. Actually, the server instance of sslrsh needs to have the CA certificate that signed the client's certificate, and the client instance needs to have the CA certificate that signed the server's certificate. Got that? Good.

You can specify the CA Certificate with "-a", and the server/client certificate with "-c". By default the port is 1479, which can be changed with "-p". "-P" will let you specify a web CONNECT proxy for the client. If you want to run as the server use "-s", and supply a remote host destination if you want to run as a client. The server will run a bash shell by default, but you can change what it runs through the "-e" command. NOTE NOTE NOTE: the value specified with "-e" is passed to the system() call, which can have severe security repercussions, especially if in your own script that calls sslrsh, you pass unverified data as the value for "-e". (Hmm. A Taint Mode for the shell would be pretty cool.) Finally, "-h" gets the help/usage and license statement.

Here's an example. It runs the server on host "artoo", and the client on host "argentina", connecting to "artoo" using sslrsh:

BEGIN EXAMPLE
On the server

jdimpson@artoo:~$ sslrsh -s
Listening on "1479"
And on the client

jdimpson@argentina:~$ sslrsh artoo
Connecting to "artoo:1479"
welcome to sslrsh
jdimpson@artoo:~$
END EXAMPLE

Here's the code:

BEGIN CODE

#!/bin/sh

#####################################################################
## sslrsh - Remote Shell over SSL using certificate authentication ##
#####################################################################

###
# defaults
###
PORT=1479 # 433(SSL) + 22(SSH) + 1024(non-priv)
CACERT=sslrsh-cacert.pem
SRVCERT=sslrsh-server.pem
CLNTCERT=sslrsh-client.pem
PROXYPORT=8888 # default port, should be 80?
ME=`basename "$0"`;
HOSTNAME=`uname -n`;
SHELL="echo Welcome to $ME on $HOSTNAME; /bin/bash --login -i";
# use the following if you want to challenge for password,
# but then the server needs to run as root!
#SHELL="echo welcome to $ME on $HOSTNAME; /bin/login"

###
# process command line
###
while [ ! -z "$1" ]; do
case "$1" in
"-h") HELP=1;;
"-s") SERVER=1;;
"-p") shift; PORT="$1";;
"-P") shift; PROXY="$1";;
"-c") shift; CERT="$1";;
"-a") shift; CACERT="$1";;
"-e") shift; SHELL=$1;;
*) DEST="$1";
esac;
shift;
done

if [ ! -z "$CERT" ]; then
test -z $SERVER && CLNTCERT="$CERT" || SRVCERT="$CERT";
fi

###
# usage
###
if [ ! -z $HELP ] || ( [ -z "$DEST" ] && [ -z $SERVER ] ); then
echo "Usage: $ME [-h]
$ME [-p port] [-P proxy:port] [-c /path/to/cert] [-a /path/to/cacert] remotehost
$ME -s [-p port] [-c /path/to/certificate] [-a /path/to/cacertificate] [ -e "shell commands to execute" ]

-h This helpful information.
-p Port to use (currently $PORT).
-P CONNECT proxy server to use. http_proxy environment variable will be used if set, but will be overridden by this flag.
-c Path to the client or server certificate (must include key)
(currently \"$CLNTCERT\" for client, \"$SRVCERT\" for server)
-a Path to the signing Certificate Authority's certificate
(currently \"$CACERT\")
remotehost System to connect to (client mode)
-s Listen for connections (server mode)
-e Shell command or commands to execute as the server, defaults to \"$SHELL\"

$ME is copyright 2009 by Jeremy D. Impson <jdimpson@acm.org>.
Licensed under the Apache License, Version 2.0 (the "License"); you
may not use this file except in compliance with the License. You may
obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
";
exit 1;
fi

###
# check for cert files
###
if [ ! -r "$CACERT" ]; then
echo "Can't open CA certficate \"$CACERT\" for reading! Use \"-a\" to set.";
exit 2;
fi
if [ -f $SERVER ]; then
if [ ! -r "$CLNTCERT" ]; then
echo "Can't open client certficate/key \"$CLNTCERT\" for reading! Use \"-c\" to set.";
exit 2;
fi
else
if [ ! -r "$SRVCERT" ]; then
echo "Can't open server certficate/key \"$SRVCERT\" for reading! Use \"-c\" to set.";
exit 2;
fi
fi

###
# figure out proxy setting
###
if [ -z "$PROXY" ] && [ ! -z "$http_proxy" ] && [ -z "$SERVER" ]; then
echo "Detected http_proxy environment variable \"$http_proxy\"";
PROXY="$http_proxy";
fi
if [ ! -z "$PROXY" ]; then
# strip out starting http:// and trailing /'s
if echo "$PROXY" | grep -q 'http:'; then
PROXY=`echo "$PROXY" | sed -e 's/^http:\/\///' -e 's/\/*$//'`;
fi
# find port (following proxy hostname and colon)
if echo "$PROXY" | grep -q ':'; then
PROXYPORT=`echo "$PROXY" | sed -e 's/.*://'`;
PROXY=`echo "$PROXY" | sed -e 's/:.*//'`;
fi
fi

###
# figure out mode and run
###
if [ -z "$SERVER" ]; then
# Client mode
echo "Connecting to \"$DEST:$PORT\"";
if [ ! -z "$PROXY" ]; then
echo " via \"$PROXY:$PROXYPORT\" proxy";
# socat 2.0 should make the following go away
socat -ls -lp "$ME(proxy)" TCP-L:$PORT,reuseaddr PROXY:$PROXY:$DEST:$PORT,proxyport=$PROXYPORT &
DEST="localhost";
fi

#socat -,raw,echo=0 TCP:$DEST:$PORT
exec socat -lp "$ME(client)" -,raw,echo=0 SSL:$DEST:$PORT,cert="$CLNTCERT",cafile="$CACERT",verify=1
else
# Server mode
echo "Listening on \"$PORT\"";
if [ ! -z "$DEST" ]; then
echo " Ignoring destination \"$DEST\" while in server mode.";
fi
if [ ! -z "$PROXY" ]; then
echo " Ignoring CONNECT proxy \"$PROXY:$PROXYPORT\" while in server mode.";
fi
#socat -ls -d -d TCP-L:$PORT,reuseaddr,fork SYSTEM:"$SHELL",pty,setsid,setpgid,stderr,ctty
exec socat -ls -lp "$ME(server)" -d -d \
SSL-L:$PORT,cert="$SRVCERT",cafile="$CACERT",verify=1,reuseaddr,fork \
SYSTEM:"$SHELL",pty,setsid,setpgid,stderr,ctty
fi
echo "Shouldn't reach here; something went wrong!";
exit 3;
END CODE

You can download sslrsh here: http://impson.tzo.com/~jdimpson/bin/sslrsh.

Let's dive in. After setting default values, processing the command line, and providing a usage and license statement, sslrsh gets its certificate material in order. It makes sure the CA certificate is readable, and does the same for the client or server (depending on which mode it runs in) combined certificate and key file. By default, it looks for files in the current environment, with names that just happen to match up with the file names you'd get if you followed the directions below to create them with mini_ca. (What a happy coincidence.) Otherwise, you can use the "-a" flag to direct sslrsh to the CA cert you want. Similarly, the client or server cert can be set with "-c".

Then it figures out what kind of proxying, if any, should be done. If the user has the http_proxy environment variable set, that will be used. If the user specified the "-P" flag, the provided value will be used as the proxy. Regardless of which source the proxy setting comes from, it gets scrubbed and parsed. First, any URL-related text (e.g. "http://") is removed. If it matches the form "server:port", the port is stripped out and assigned to another variable.

With all that out of the way, the script proceeds to figure out if it's meant to run as a client or a server. If as a client, it then checks the proxy settings. If present, the script forks (via the ampersand) an instance of socat that listens on a local TCP port and forwards anything sent to that port on to the specified web CONNECT proxy. It tells the proxy to redirect the connection to the final destination, as given on the command line. This is effectively a proxy to the proxy, because socat's SSL functionality doesn't know how to talk to a web proxy directly.

Then the script executes socat, listening on standard input, connecting it to an SSL socket. The "-,raw,echo=0" argument to socats says: listen on standard input, turn off all processing that the TTY layer normally does, and similarly tell it not to echo input typed by the user back to the user. This is important as we want the server side to receive everything we type, and to present us with everything on the screen. The argument that starts with "SSL:..." controls SSL connection. If no proxy was configured, the SSL connection will connect to the final destination as given on the command line. If there is a proxy, the SSL connection will connect to the listening port of the above described socat instance. Either way, the SSL connection uses both the CA certificate (for authenticating the server), and the client certificate (to present to the server). The rest of the argument uses a number of options to control the SSL connection. The "-ls" and "lp" arguments control how socat performs logging.

It's unfortunate that we must a second instance of socat just to perform this proxying; it would be preferable if the SSL capability within socat could utilize the proxy directly. Apparantly socat version 2.0 will be able to rectify this situation.

If running as a server, sslrsh again executes socat, using it to create a listening SSL socket, then fork and exec a shell command, which by default prints a welcome message then runs an instance of the bash shell. The argument to socat that begins "SSL-L:..." tells it to listen for incoming SSL connections (from the client). Every new connection causes the process to fork and run a shell command, as described in the argument that starts "SYSTEM:...". The rest of that argument has a bunch of options ("pty,setsid,setpgid,ctty") that are some Unix/POSIX voodoo necessary to give the client the appropriate interactive shell experience with its own TTY and job control, while "stderr" makes sure the error from the shell command gets sent to the client. The "-ls" and "lp" arguments control how socat performs logging. The two "-d" flags increase the verbosity level of the logging.

Although I think sslrsh is a neat script, and it has its uses as a lightweight and customizable remote access server, be aware of its limitations. It doesn't do tunneling/port forwarding like SSH does. It may violate the access policy of the system you're running the server on (if you aren't its administrator). It doesn't have robust error checking, especially in the set up of the proxy process, so it's not an easily supportable, enterprise-quality service.

Also, it doesn't matter which client certificate you use to connect to the server, the server will authenticate you as long as the CA created the client certificate. There's no differentiation between clients. A "normal" SSL application would actually read a certificate after validating it. We're not doing that here. It would be nice if the contents of the validated certificate were made available to our server. One way to do this would be to have an option to the SSL function that runs a script after a cert is validated, and is fed the certificate on input or in the environment. The script would return true or false to specify whether the certificate should be accepted. Or, if the SSL function placed the validated certificate, or even just the "Subject" line in the certificate, into an environment variable, our shell (as specified by "-e") could use it to make decisions.

But probably the biggest limitation is that, by default, when you log in to the sslrsh server, the shell you get will be running as the user who started the sslrsh server. But, here's an alternative way to run server which makes it prompt for username and password (in addition to the certificate-base authentication). However, for this to work, the server has to be run as root. It works by replacing the call to the bash shell with a call to the login program. login prompts for username and password, checks them against the server system's password mechanism, then uses setuid() to become whatever username was provided.

BEGIN EXAMPLE
On the server

jdimpson@artoo:~$ sudo ./sslrsh -s -e "/bin/login"
Listening on "1479"
On the client, you can see the change

jdimpson@argentina:~$ sslrsh artoo
Connecting to "artoo:1479"
via "localhost:8888" proxy
artoo login: sysadmin
Password:
Last login: Wed Nov 26 09:34:29 EST 2008 from argentina.apt.net on pts/8
Linux artoo 2.6.24-22-generic #1 SMP Mon Nov 24 18:32:42 UTC 2008 i686

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

To access official Ubuntu documentation, please visit:
http://help.ubuntu.com/
You have mail.
sysadmin@artoo:~$

END EXAMPLE

A note about socat: Its man page describes it like this:

socat - Multipurpose relay (SOcket CAT)

Socat is a command line based utility that establishes two bidirectional
byte streams and transfers data between them. Because the streams can be
constructed from a large set of different types of data sinks and sources
(see address types), and because lots of address options may be applied to
the streams, socat can be used for many different purposes. It might be
one of the tools that one ‘has already needed´.

That's accurate, but doesn't really capture the scope of socat's capability. Just like its namesake cat, socat is all about connecting standard input to standard output. Unlike cat, which primarily operates on files and TTYs, socat can operate on (and create, if necessary) files, TTYs, TCP sockets, UDP sockets, raw sockets, Domain sockets, raw IP frames, child processes, named pipes, arbitrary file handles, as well as files and standard input, output, and error. Additionally, socat knows about a few application-level protocols like SSL, Web CONNECT Proxy, and SOCKS. It also knows about various network access methods, like IPv4, IPv6, IP Multicast, IP Broadcast, and datagram- and session-style transmit and receive. All of these communication mechanism are called addresses, and socat has a rich set of options that apply to numerous address types, such as setting device-specific ioctl()s, IP headers, socket options, and security ciphers, and performing functions like fork(), chroot(), and setuid() in order to get various security and performance behaviours.

Folks who know about netcat might wonder how it compares to socat. Given socat, you don't need netcat, although netcat is a lot simpler to use if all you need is basic TCP or UDP streaming. Personally, I plan on keeping netcat in my own personal toolbox, if only because I can bang out a netcat command line without any conscious thought.

A note on certificates: As mentioned above, the client and server need certificates & keys, and they both need access to a Certificate Authority certificate in order validate each other's certificates. Here's how to use mini_ca to create the necessary certs. (Follow the link to find out where to download mini_ca.)

Create the Certificate Authority, and the CA certificate, like this:

BEGIN EXAMPLE

jdimpson@argentina:~$ mini_ca sslrsh
Created /home/jdimpson/sslrsh-ca/sslrsh-ca_openssl.cnf CA configuration file.
Creating sslrsh CA certificate.
Generating a 2048 bit RSA private key
.................................................................................................+++
....................................+++
unable to write 'random state'
writing new private key to '/home/jdimpson/sslrsh-ca/private/cakey.pem'
-----
Created sslrsh-cacert.pem CA certificate
Created sslrsh project certificate generator script "create_sslrsh_user_cert.sh"
END EXAMPLE

You'll find the CA certificate at "sslrsh-ca/sslrsh-cacert.pem".

Use the CA to create a server certificate/key.

BEGIN EXAMPLE

jdimpson@argentina:~$ ./sslrsh-ca/create_sslrsh_user_cert.sh server
Creating key, req, & cert for server
Generating a 2048 bit RSA private key
............................+++
....................................+++
unable to write 'random state'
writing new private key to 'sslrsh-server-key.pem'
-----
Using configuration from /home/jdimpson/sslrsh-ca/sslrsh-ca_openssl.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
domainComponent :IA5STRING:'sslrsh'
stateOrProvinceName :PRINTABLE:'New York'
countryName :PRINTABLE:'US'
emailAddress :IA5STRING:'server@localhost'
organizationName :PRINTABLE:'sslrsh Users'
commonName :PRINTABLE:'user server'
Certificate is to be certified until Dec 18 23:37:23 2010 GMT (700 days)
Sign the certificate? [y/n]:

1 out of 1 certificate requests certified, commit? [y/n]Write out database with 1 new entries
Data Base Updated
unable to write 'random state'
2+0 records in
2+0 records out
1024 bytes (1.0 kB) copied, 0.000283845 s, 3.6 MB/s
0 semi-random bytes loaded
Generating DH parameters, 512 bit long safe prime, generator 2
This is going to take a long time
......+.......+......+................+..........................+..............+.......+........+......+............................+......................+........+.+..............+..........+.........+...........................................+.....................+...+..........................................................................+....................................+.....................+.......................+...............+.....+......+..................................+.+................................+.......................................................................................+............+......................+..............................+.........+......+..+.......+............+...............................................+............+....+....................+.++*++*++*++*++*++*
unable to write 'random state'
Use file /home/jdimpson/sslrsh-ca/certs/sslrsh-server.pem, it contains server's key, certicicate, and diffie hellman seed.
END EXAMPLE

You'll find the server certificate/key at "sslrsh-ca/certs/sslrsh-server.pem ".

Use the CA to create a client certificate/key.

BEGIN EXAMPLE

jdimpson@argentina:~$ ./sslrsh-ca/create_sslrsh_user_cert.sh client
Creating key, req, & cert for client
Generating a 2048 bit RSA private key
...................................................................................+++
....................................+++
unable to write 'random state'
writing new private key to 'sslrsh-client-key.pem'
-----
Using configuration from /home/jdimpson/sslrsh-ca/sslrsh-ca_openssl.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
domainComponent :IA5STRING:'sslrsh'
stateOrProvinceName :PRINTABLE:'New York'
countryName :PRINTABLE:'US'
emailAddress :IA5STRING:'client@localhost'
organizationName :PRINTABLE:'sslrsh Users'
commonName :PRINTABLE:'user client'
Certificate is to be certified until Dec 18 23:37:32 2010 GMT (700 days)
Sign the certificate? [y/n]:

1 out of 1 certificate requests certified, commit? [y/n]Write out database with 1 new entries
Data Base Updated
unable to write 'random state'
2+0 records in
2+0 records out
1024 bytes (1.0 kB) copied, 0.000235715 s, 4.3 MB/s
0 semi-random bytes loaded
Generating DH parameters, 512 bit long safe prime, generator 2
This is going to take a long time
.........+........++*++*++*++*++*++*
unable to write 'random state'
Use file /home/jdimpson/sslrsh-ca/certs/sslrsh-client.pem, it contains client's key, certicicate, and diffie hellman seed.
jdimpson@argentina:~$
END EXAMPLE

You'll find the client certificate/key at "sslrsh-ca/certs/sslrsh-client.pem ".