Paranoid Penguin - Rehabilitating Clear-Text Network Applications with Stunnel

by Mick Bauer

The world is full of network applications that get things done, have withstood the test of time and have rotten security. Telnet? Brilliant in its simplicity and versatility, but it transmits your login credentials in clear text. rcp? Ever so ubiquitous and scriptable, but rhosts-based authentication's time has come and gone, thanks to IP spoofing.

Sure, you can swap those old warhorses for their encrypted successors—SSH instead of telnet and scp or rsync-over-SSH for rcp. Or, you can build an all-purpose IPSec tunnel to each remote host with which you communicate. But the latter often is overkill, and the former is easier said than done if your choice of applications in a given scenario isn't completely within your control. Surely there's a way to add strong encryption to legacy network applications.

Consider the mighty SSL wrapper, Stunnel. This month, I explain how to use Stunnel 4.0 in tandem with OpenSSL to bring your legacy applications into the modern era, security-wise. And what about wireless? If you're compelled to use a clear-text network application over a weakly secured wireless medium, such as 802.11b, can Stunnel help? Read on.

Background

You need to understand two things in order to use Stunnel. First, know how your network application uses the network. If it's a simple single-TCP-port application such as telnet, which does all its listening on TCP 23, Stunnel works. If it uses UDP, the portmapper service or any other dynamic port-allocation scheme, Stunnel can't help you. For example, RPC applications don't work, because they use portmapper. FTP uses TCP 21 for control traffic but dynamically assigns arbitrary high ports for data connections, so it's also disqualified.

Second, you need to understand the basics of public key cryptography but not necessarily the ins and outs of X.509 or Public Key Infrastructures. I've described how this works in several previous columns, such as “The 101 Uses of OpenSSH, Part II” [LJ, January 2001]. For now, suffice it to say that in public key cryptography, each participant has two keys: a public key that you share with the other participants and a private key that only you possess. Other people use your public key to encrypt things that they want only you to see; you use your private key to decrypt those things.

Digital signatures work backward from encryption. If you sign something with your private key, anybody can use your public key to verify that the signature was generated with your private key and therefore by you. Again, this depends on only you possessing your private key, no matter how many people have copies of your public key.

In the X.509 world, we call the public key a certificate, which, technically, is a public key bundled with digitally signed information about the public key's owner, including name and e-mail address. We call the private key simply the key. Somewhat confusingly, we sometimes refer to both of them, together, as a certificate. Context helps: when I talk about a passphrase-free certificate, you know I'm talking about a combined key/certificate because the certificate itself, being a public key, can't have a passphrase.

That's by far the most concise explanation I've ever given of public key cryptography and X.509. If it isn't enough for you to decipher the rest of this article, read the Stunnel FAQ or the mighty RSA Crypto FAQ (see the on-line Resources section) for more information. Now it's time to plunge into Stunnel proper.

Installing Stunnel

Chances are, your Linux distribution of choice includes a binary package for Stunnel. Recent releases of SuSE, Fedora and Red Hat Enterprise all include Stunnel version 4. Debian 3.0 (Woody) includes Stunnel version 3.22.

On the one hand, 3.22 is a stable version that's well documented and well understood. On the other hand, Stunnel version 4 is a major rewrite that, among other things, allows for easier management of multiple tunnels. It's the version I'm covering here. If you run Debian, I think it's worth your while to download the latest Stunnel source and compile it yourself.

Compiling Stunnel on any Linux distribution is quick and easy. First, make sure you've already got your distribution's packages for OpenSSL, probably called openssl; OpenSSL development libraries, openssl-devel or libssl096-dev; and TCPwrapper development libraries, libwrap0-dev on Debian, included as part of SuSE's and Fedora's base installations. Then, unpack Stunnel's source code tarball and do a quick:

./configure && make && make install

If for some reason that doesn't work, entering ./configure --help lists advanced precompile configuration options you can pass to the configure script. Once you've installed Stunnel, it's time to create some certificates and start tunneling.

Most of what follows applies only to Stunnel v.4.0.0 and later. If you choose to use, for example, Debian's Stunnel 3.22 package, you need to refer to the documentation included with that package or to the examples on the Stunnel Web site (see Resources); most of that Web site still covers the older version.

Creating Certificates with OpenSSL

A Stunnel server—that is, a host that receives encrypted packets destined for a local clear-text service—requires a server certificate. A Stunnel client does not, however, unless you intend to configure your Stunnel server to use client-certificate authentication. Sadly, client-certificate authentication is beyond the scope of this article, so none of our examples require the client to have its own certificate—only the server does.

If you installed Stunnel after compiling from source and issuing a make install command, you've already got a server certificate (/usr/local/etc/stunnel/stunnel.pem). If you installed Stunnel from a binary package, the package's post-installation script may or may not have created a server certificate for you.

Fedora Core 1's Stunnel RPM creates an empty certificate for some reason. Needless to say, you need a proper certificate, and the way to do that on Fedora is to change your working directory to /usr/share/ssl/certs, type make stunnel.pem and copy stunnel.pem to the directory /etc/stunnel. This certificate, as with certificates created by the Stunnel source-release Make script, has no passphrase.

The Danger of Passphrase-Free Certificates

Using a server certificate that's been generated automatically or semi-automatically by a script is quick and convenient, but there's one problem: such a certificate nearly always is created with no passphrase. On the one hand, you can and should set the permissions and ownership on your certificate to make it root-readable only, so at least unprivileged users on your system aren't able to read and thus use it. On the other hand, if your system is root-compromised, your passphrase-free certificate then can be used and abused by the attacker.

This may be an acceptable risk for you, and indeed, using passphrase-free server certificates is a common practice. After all, it's inconvenient to require a human being to enter a certificate's passphrase every time Stunnel starts up. However, as a matter of principle, crypto experts widely consider it reckless to use passphrase-free certificates. If a process is sensitive enough to warrant cryptography in the first place, the argument goes, it's sensitive enough to require a human being to start that process.

If your Stunnel server system needs a server certificate, or if your automatically generated certificate doesn't meet your needs—for example, it lacks a passphrase—you need to generate your own, using the OpenSSL command. Space does not permit me to give a full, proper explanation of this powerful tool. Luckily, the Stunnel FAQ (see Resources) answers some of the most common questions about how OpenSSL and Stunnel interact, and it includes pointers to further sources of information about OpenSSL.

Suffice it to say that for some users, there may be a compelling reason to create their own Certificate Authority (CA), install their CA certificate (but not key) on every system running Stunnel and use their CA certificate to sign all server certificates. This is necessary, for example, if you want your Stunnel server systems to accept client connections only from Stunnel client hosts with particular certificates.

For many if not most Stunnel users, however, it's good enough to use self-signed certificates and to not worry about being or not being a CA. Here's how to generate a self-signed server certificate using OpenSSL.

Listing 1. Creating a Server Certificate with OpenSSL

nearclient:/etc/stunnel# openssl req -x509 \
-newkey rsa:1024 -days 365 \
-keyout stunnel.pem -out stunnel.pem
Using configuration from /usr/lib/ssl/openssl.cnf
Generating a 1024 bit RSA private key
...++++++
.............................................++++++
writing new private key to 'key2.pem'
Enter PEM pass phrase: ************
Verifying password - Enter PEM pass phrase: ******
-----
You are about to be asked to enter information that will be
incorporated into your certificate request.
What you are about to enter is what is called a
Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) []:Minnesota
Locality Name (eg, city) []:St. Paul
Organization Name (eg, company) []:Wiremonkeys
Organizational Unit Name (eg, section) []:
Common Name (eg, YOUR name) []:nearclient.wiremonkeys.org
Email Address []:[email protected]

nearclient:/etc/stunnel# chmod 600 stunnel.pem

In Listing 1, the real meat is in the first line. Parsing from left to right, this command tells OpenSSL to create a certificate request in the X.509 digital certificate format, using the RSA cipher with a 1,024-bit key, good for 365 days and with both its key and (public) certificate written to the same file, stunnel.pem. If you instead want to create a password-free certificate, you can include the -nodes option at the end. Read the sidebar “The Danger of Passphrase-Free Certificates” first, though.

Most of the rest of Listing 1 shows a dialog in which OpenSSL prompts me for X.509-type information to include in the certificate, including Country and Region. That stuff should be self-explanatory, but Common Name is very important. If you're creating a server certificate, the certificate's Common Name must be set to the fully qualified domain name (that is, the full hostname) of the host that is going to use the certificate.

Most SSL/TLS-enabled applications expect this Common Name to resolve by way of DNS to the IP address of your server. Stunnel does not unless you configure it to, but setting Common Name to your FQDN is a good habit to get into nonetheless.

In the last line of Listing 1, I've set the permissions of my new server certificate to 0600 (-rw-------). Because I ran my OpenSSL command as root, this file already is owned by root. It's important for any server certificate, whether it's password-protected or not, to be root-readable only. I ran my OpenSSL command from within /etc/stunnel, so my certificate was created in place, and I didn't have to move my new certificate there by hand.

Configuring Stunnel's Global Settings

Once you've got a suitable server certificate, it's time to configure yourself a tunnel. This is considerably simpler than the previous task. This part is also much more version-specific. In versions of Stunnel prior to version 4.0, Stunnel accepted all of its configuration parameters as command options. In current versions, however, the only actionable command-line argument it expects is the nondefault path to its configuration file.

If you installed Stunnel from source with default compile-time options, Stunnel expects its configuration file to reside in /usr/local/etc/stunnel. If you installed from a binary package, this path is more likely to be /etc/stunnel. Listing 2 shows the global settings from an abbreviated sample stunnel.conf file (abbreviated mainly in that I've omitted comment lines).

Listing 2. Global Settings in stunnel.conf

cert = /etc/stunnel/stunnel.pem
chroot = /var/run/stunnel/
pid = /stunnel.pid
setuid = nobody
setgid = nogroup
debug = 7
output = /var/log/stunnel.log
client = yes

The cert parameter tells Stunnel where to look for its server certificate; it therefore follows that you need this parameter only on your Stunnel server host, not on client hosts. The chroot parameter tells Stunnel which directory to chroot itself (reset / to) after starting up; this happens after Stunnel has read its configuration and server certificate files. You probably need to create this chroot jail manually and populate it with a few things, for example, its own etc/hosts.allow and etc/hosts.deny files, if you want to use TCPwrappers-style access controls.

pid tells Stunnel where to write its process ID. This path is relative to that set by chroot; that is, Stunnel writes its PID after chrooting itself.

setuid and setgid tell Stunnel which user and group to demote itself to after starting. If Stunnel is to listen on any TCP ports lower than 1025 it must be started as root, but it demotes itself after reading its configuration file, reading its server certificate and binding to the privileged port.

By default, Stunnel sends its log messages of severity notice or higher to the local dæmon syslog facility. Fedora's version sends it to authpriv, which in turn logs to /var/log/secure. You can use the debug option to set a different log level. Seven is the highest level and is best if you're having trouble getting Stunnel to work. You can use the output option to tell Stunnel to send its messages to a specific file rather than handing its messages off to syslog.

The last line in Listing 2 sets the client parameter to yes, which means that on this particular system, I intend to initiate SSL transactions, not receive them. On the server with which I intend to communicate, I need to leave this parameter set to its default, no.

Configuring a Tunnel

Now, finally, we come to the payoff—an actual tunnel. For this example, we're going to tunnel telnet from the host nearclient to the server farserver. The global section in farserver's stunnel.conf file can be almost identical to the one in Listing 2, except that the client needs to be set to no. The major difference between the two hosts' configurations is in their service definitions.

Before I dive into that, however, let's flesh out the example scenario a little more. Suppose farserver already is configured as a telnet server; it already accepts telnet sessions on TCP port 23. But, we don't want nearclient to connect to the clear-text port; we need to use something else for an SSL connection. As it happens, IANA has already designated a port for SSL-enabled telnet (aka telnets): TCP 992.

Therefore, we want a tunnel from nearserver to TCP 992 on farserver. But how will our non-SSL-enabled telnet command and our equally non-SSL-savvy telnet server process know how to use this tunnel? That's a trick question; the tunnel is completely transparent to the sending and receiving telnet processes.

nearserver's Stunnel process accepts the connection on the usual port—TCP 23 although this is user-defined—and then encrypts the packets with SSL before forwarding them to TCP port 992 on farserver. farserver decrypts the packets and then forwards them to its local telnet process on TCP 23. Actually, xinetd or inetd receive the packets before in.telnetd does, but you get the picture.

In this way, when users on nearserver want to connect to farserver, they enter the command telnet 127.0.0.1, and connection is encrypted, forwarded to farserver and decrypted. farserver's reply packets follow the same path but backward. Each telnet process (telnet and in.telnetd) thinks it's communicating with a local user, but the packets in fact are traversing an SSL-encrypted Stunnel session. All of which is a wordy way of explaining the six total lines that comprise Listings 3 and 4.

Listing 3. Service Definition on nearclient

[telnets]
accept = 23
connect = farserver.wiremonkeys.org:992

Listing 4. Service Definition on farserver

[telnets]
accept = 992
connect = 23

As you can see, a service definition can be as simple as a pair of lines, an accept line to designate a listening port and a connect line to designate a destination port. Precisely what this means depends on whether a given system is a Stunnel client or a Stunnel server. On a client system, client = yes, Stunnel expects clear-text packets on its accept port and sends encrypted packets to the connect port. On a server system, client = no, Stunnel listens for SSL connections (encrypted packets) on its accept port and sends decrypted packets to the connect port.

Also notice that on a client, in your connect statement you probably need to specify not only a port but also a remote hostname or IP address, as in Listing 3, connect = farserver.wiremonkeys.org:992.

According to the way we've configured things in Listings 3 and 4, any host can connect to TCP port 23 on nearserver and traverse nearserver's tunnel to farserver. We can restrict this in any number of ways, including by using iptables. We can tell Stunnel to accept only local connections to the Stunnel client process, however, by using accept = 127.0.0.1:23 on nearclient.

This technique would work on farserver as well. If farserver has multiple interfaces, eth0, wlan0, ppp0 and so on, you can write your accept statement to specify the IP address of the interface on which you want it to receive tunneled packets in the same way. On both Stunnel clients and servers, the default IP for accept statements is any (all local interfaces), and the default IP for connect statements is 127.0.0.1 (localhost).

What about the telnet server on farserver? After all, clear-text telnet sessions seldom are a good idea. Therefore, in practice you want to be sure to add the line bind = 127.0.0.1 to your /etc/xinetd/telnet script, so that only local processes can connect to TCP 23.

Once you've finished configuring Stunnel on client and server, start Stunnel simply by typing the command stunnel. You don't need to worry about starting it on the server before starting it on the client; the client doesn't initiate a tunnel until you try to use it, for example, by trying to start a telnet session. If either or both hosts are using a password-protected server certificate, you are prompted for it now. Keep this in mind if you set up a Stunnel startup script. Once you've entered the certificate, you should be up and running.

You now can check for successful startup by issuing a quick ps auxw and looking for a Stunnel process—Stunnel returns no output to the console whether or not it starts cleanly. It does, however, send messages to your system's syslog facility, including startup messages.

Once stunnel is running on both client and server, our users on nearclient can trigger a tunnel by connecting to nearclient's TCP port 23, for example, with the command telnet 127.0.0.1 (23 is telnet's default port). Listing 5 shows a sample encrypted-telnet session.

Listing 5. Sample Telnet Session

nearserver:/usr/local/etc/stunnel# telnet 127.0.0.1
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Fedora Core release 1 (Yarrow)
Kernel 2.4.22-1.2115.nptl on an i686
login: myfaraccount
Password: **********
Last login: Sun Jun 13 21:39:17 from localhost.localdomain
[[email protected] myfaraccount]$

As you can see, from the end-user's perspective, connecting to farserver in this way is practically indistinguishable from any other telnet session. The bash prompt at the end of the listing, however, confirms that we have in fact connected to farserver.

Stunnel and inetd/xinetd

I have a confession. The examples in this article do not show the most efficient way to use Stunnel to encrypt telnet, although I assure you I've tested these examples, and they do work.

My justification is I wanted to illustrate the concept of tunneling generic TCP services quickly, using a technique that works for practically any single-TCP-port service. But telnet, pop3 and other services typically started by a super-server, such as inetd or xinetd, are supported through several different Stunnel features that are beyond the scope of this article.

For example, a Stunnel server can, rather than forward packets with a connect statement, pass them to a process that Stunnel invokes itself with the exec parameter in stunnel.conf. Alternatively, you can configure Stunnel itself to be invoked dynamically by inetd or xinetd.

For more information on using Stunnel with dynamically started dæmons or in conjunction with inetd or xinetd, see the stunnel(8) man page and the Stunnel FAQ.

Conclusion

And that, I hope, is enough to start you down the path of Stunnel mayhem. As usual, I've scratched only the surface. I leave it up to you to explore Stunnel's ability to authenticate tunnels with client-certificate checking, its support for TCPwrappers-style access controls and the myriad global and service-specific options supported in stunnel.conf. Let the stunnel(8) man page be your guide, and may your single-TCP-port-using unencrypted-TCP applications be eavesdroppable no more.

Resources for this article: /article/7646.

Mick Bauer, CISSP, is Linux Journal's security editor and an IS security consultant in Minneapolis, Minnesota. He's the author of Building Secure Servers With Linux (O'Reilly & Associates, 2002).

Load Disqus comments