Networking - Adaptive Code via C#. Agile coding with design patterns and SOLID principles (2014)

Adaptive Code via C#. Agile coding with design patterns and SOLID principles (2014)

Chapter 9. Networking

Today, most applications perform some type of network activity. Unfortunately, many programmers don't know how to access a network securely. The recipes in this chapter aim to help you use a network in your application. To many developers, network security from the application standpoint means using the Secure Sockets Layer (SSL), but SSL isn't a magic solution. SSL can be difficult to use properly; in many cases, it is overkill, and in a few cases, it is insufficient. This chapter presents recipes for using OpenSSL to build SSL-enabled clients and servers and recipes for network and interprocess communication without SSL.

On the Windows platform, with the exception of SSL over HTTP (which we cover in Recipe 9.4), we've chosen to limit the SSL-specific recipes to OpenSSL, which is freely available and portable to a wide range of platforms, Windows included.

On Windows systems, Microsoft provides access to its SSL implementation through the Security Support Provider Interface (SSPI). SSPI is well documented, but unfortunately, the use of SSL is not. What's more unfortunate is that implementing an SSL-enabled client or server with SSPI on Windows is considerably more complex than using OpenSSL (which is saying quite a lot). The SSPI interface to SSL is surprisingly low-level, requiring programs that use it to do much of the work of exchanging protocol messages themselves. Because SSL is difficult to use properly, it is desirable to mask protocol details with a high-level implementation (such as OpenSSL). We therefore avoid the SSPI interface to SSL altogether.

If you are interested in finding out more about SSPI and the SSL interface, we recommend that you consult the Microsoft Developer's Network (MSDN) and the samples that are included with the Microsoft Windows Platform SDK, which is available from Microsoft on the Internet athttp://www.microsoft.com/msdownload/platformsdk/sdkupdate/. The relevant example code can be found in the directory Microsoft SDK\Samples\Security\SSPI\SSL from wherever you install it on your system (normally in \Program Files on your boot drive).

Additionally, over time, SSPI-specific recipes may end up on the book's companion web site, particularly if submitted by readers such as you.

9.1. Creating an SSL Client

Problem

You want to establish a connection from a client to a remote server using SSL.

Solution

Establishing a connection to a remote server using SSL is not entirely different from establishing a connection without using SSL—at least it doesn't have to be. Establishing an SSL connection requires a little more setup work, consisting primarily of building an spc_x509store_t object (seeRecipe 10.5) that contains the information necessary to verify the server. Once this is done, you need to create an SSL_CTX object and attach it to the connection. OpenSSL will handle the rest.

TIP

Before reading this recipe, make sure you understand the basics of public key infrastructure (see Recipe 10.1).

Discussion

Once you've created an spc_x509store_t object by loading it with the appropriate certificates and CRLs (see Recipe 10.10 and Recipe 10.11 for information on obtaining CRLs), connecting to a remote server over SSL can be as simple as making a call to the following function,spc_connect_ssl( ) . You can optionally create an SSL_CTX object yourself using spc_create_sslctx( ) or the OpenSSL API. Alternatively, you can share one that has already been created for other connections, or you can let spc_connect_ssl( ) do it for you. In the latter case, the connection will be established and the SSL_CTX object that was created will be returned by way of a pointer to the SSL_CTX object pointer in the function's argument list.

#include <openssl/bio.h>

#include <openssl/ssl.h>

BIO *spc_connect_ssl(char *host, int port, spc_x509store_t *spc_store,

SSL_CTX **ctx) {

BIO *conn = 0;

int our_ctx = 0;

if (*ctx) {

CRYPTO_add(&((*ctx)->references), 1, CRYPTO_LOCK_SSL_CTX);

if (spc_store && spc_store != SSL_CTX_get_app_data(*ctx)) {

SSL_CTX_set_cert_store(*ctx, spc_create_x509store(spc_store));

SSL_CTX_set_app_data(*ctx, spc_store);

}

} else {

*ctx = spc_create_sslctx(spc_store);

our_ctx = 1;

}

if (!(conn = BIO_new_ssl_connect(*ctx))) goto error_exit;

BIO_set_conn_hostname(conn, host);

BIO_set_conn_int_port(conn, &port);

if (BIO_do_connect(conn) <= 0) goto error_exit;

if (our_ctx) SSL_CTX_free(*ctx);

return conn;

error_exit:

if (conn) BIO_free_all(conn);

if (*ctx) SSL_CTX_free(*ctx);

if (our_ctx) *ctx = 0;

return 0;

}

We're providing an additional function here that will handle the differences between connecting to a remote server using SSL and connecting to a remote server not using SSL. In both cases, a BIO object is returned that can be used in the same way regardless of whether there is an SSL connection in place. If the ssl flag to this function is zero, the spc_store and ctx arguments will be ignored because they're only applicable to SSL connections.

OpenSSL makes heavy use of BIO objects, and many of the API functions require BIO arguments. What are these objects? Briefly, BIO objects are an abstraction for I/O that provides a uniform, medium-independent interface. BIO objects exist for file I/O, socket I/O, and memory. In addition, special BIO objects, known as BIO filters , can be used to filter data prior to writing to or reading from the underlying medium. BIO filters exist for operations such as base64 encoding and encryption using a symmetric cipher.

The OpenSSL SSL API is built on BIO objects, and a special filter handles the details of SSL. The SSL BIO filter is most useful when employed with a socket BIO object, but it can also be used for directly linking two BIO objects together (one for reading, one for writing) or to wrap pipes or some other type of connection-oriented communications primitive.

BIO *spc_connect(char *host, int port, int ssl, spc_x509store_t *spc_store,

SSL_CTX **ctx) {

BIO *conn;

SSL *ssl_ptr;

if (ssl) {

if (!(conn = spc_connect_ssl(host, port, spc_store, ctx))) goto error_exit;

BIO_get_ssl(conn, &ssl_ptr);

if (!spc_verify_cert_hostname(SSL_get_peer_certificate(ssl_ptr), host))

goto error_exit;

if (SSL_get_verify_result(ssl_ptr) != X509_V_OK) goto error_exit;

return conn;

}

*ctx = 0;

if (!(conn = BIO_new_connect(host))) goto error_exit;

BIO_set_conn_int_port(conn, &port);

if (BIO_do_connect(conn) <= 0) goto error_exit;

return conn;

error_exit:

if (conn) BIO_free_all(conn);

return 0;

}

As written, spc_connect( ) will attempt to perform post-connection verification of the remote peer's certificate. If you instead want to perform whitelist verification or no verification at all, you'll need to make the appropriate changes to the code using Recipe 10.9 for whitelist verification.

If a connection is successfully established, a BIO object will be returned regardless of whether you used spc_connect_ssl( ) or spc_connect( ) to establish the connection. With this BIO object, you can then use BIO_read( ) to read data, and BIO_write( ) to write data. You can also use other BIO functions, such as BIO_printf( ), for example. When you're done and want to terminate the connection, you should always use BIO_free_all( ) instead of BIO_free( ) to dispose of any chained BIO filters. When you've obtained an SSL-enabled BIO object from either of these functions, there will always be at least two BIO objects in the chain: one for the SSL filter and one for the socket connection.

See Also

§ OpenSSL home page: http://www.openssl.org/

§ Recipe 10.1, Recipe 10.5, Recipe 10.9, Recipe 10.10, Recipe 10.11

9.2. Creating an SSL Server

Problem

You want to write a network server that can accept SSL connections from clients.

Solution

Creating a server that speaks SSL is not that different from creating a client that speaks SSL (see Recipe 9.1). A small amount of additional setup work is required for servers. In particular, you need to create an spc_x509store_t object (see Recipe 10.5) with a certificate and a private key. The information contained in this object is sent to clients during the initial handshake. In addition, the SPC_X509STORE_USE_CERTIFICATE flag needs to be set in the spc_x509store_t object. With the spc_x509store_t created, calls need to be made to create the listening BIO object, put it into a listening state, and accept new connections. (See Recipe 9.1 for a brief discussion regarding BIO objects.)

Discussion

Once an spc_x509store_t object has been created and fully initialized, the first step in creating an SSL server is to call spc_listen( ) . The hostname may be specified as NULL, which indicates that the created socket should be bound to all interfaces. Anything else should be specified in string form as an IP address for the interface to bind to. For example, "127.0.0.1" would cause the server BIO object to bind only to the local loopback interface.

#include <stdlib.h>

#include <string.h>

#include <openssl/bio.h>

#include <openssl/ssl.h>

BIO *spc_listen(char *host, int port) {

BIO *acpt = 0;

int addr_length;

char *addr;

if (port < 1 || port > 65535) return 0;

if (!host) host = "*";

addr_length = strlen(host) + 6; /* 5 for int, 1 for colon */

if (!(addr = (char *)malloc(addr_length + 1))) return 0;

snprintf(addr, addr_length + 1, "%s:%d", host, port);

if ((acpt = BIO_new(BIO_s_accept( ))) != 0) {

BIO_set_accept_port(acpt, addr);

if (BIO_do_accept(acpt) <= 0) {

BIO_free_all(acpt);

acpt = 0;

}

}

free(addr);

return acpt;

}

The call to spc_listen( ) will create a BIO object that has an underlying socket that is in a listening state. There isn't actually any SSL work occurring here because an SSL connection will only come into being when a new socket connection is established. The spc_listen( ) call is nonblocking and will return immediately.

The next step is to call spc_accept( ) to establish a new socket and possibly an SSL connection between the server and an incoming client. This function should be called repeatedly in order to continually accept connections. However, be aware that it will block if there are no incoming connections pending. The call to spc_accept( ) will either return a new BIO object that is the connection to the new client, or return NULL indicating that there was some failure in establishing the connection.

TIP

The spc_accept( ) function will automatically create an SSL_CTX object for you in the same manner spc_connect( ) does (see Recipe 9.1); however, because of the way that spc_accept( ) works (it is called repeatedly using the same parent BIO object for accepting new connections), you should call spc_create_sslctx( ) yourself to create a single SSL_CTX object that will be shared among all accepted connections.

BIO *spc_accept(BIO *parent, int ssl, spc_x509store_t *spc_store, SSL_CTX **ctx) {

BIO *child = 0, *ssl_bio = 0;

int our_ctx = 0;

SSL *ssl_ptr = 0;

if (BIO_do_accept(parent) <= 0) return 0;

if (!(child = BIO_pop(parent))) return 0;

if (ssl) {

if (*ctx) {

CRYPTO_add(&((*ctx)->references), 1, CRYPTO_LOCK_SSL_CTX);

if (spc_store && spc_store != SSL_CTX_get_app_data(*ctx)) {

SSL_CTX_set_cert_store(*ctx, spc_create_x509store(spc_store));

SSL_CTX_set_app_data(*ctx, spc_store);

}

} else {

*ctx = spc_create_sslctx(spc_store);

our_ctx = 1;

}

if (!(ssl_ptr = SSL_new(*ctx))) goto error_exit;

SSL_set_bio(ssl_ptr, child, child);

if (SSL_accept(ssl_ptr) <= 0) goto error_exit;

if (!(ssl_bio = BIO_new(BIO_f_ssl( )))) goto error_exit;

BIO_set_ssl(ssl_bio, ssl_ptr, 1);

child = ssl_bio;

ssl_bio = 0;

}

return child;

error_exit:

if (child) BIO_free_all(child);

if (ssl_bio) BIO_free_all(ssl_bio);

if (ssl_ptr) SSL_free(ssl_ptr);

if (*ctx) SSL_CTX_free(*ctx);

if (our_ctx) *ctx = 0;

return 0;

}

When a new socket connection is accepted, SSL_accept( ) is called to perform the SSL handshake. The server's certificate (and possibly its chain, depending on how you configure the spc_x509store_t object) is sent to the peer, and if a client certificate is requested and received, it will be verified. If the handshake is successful, the returned BIO object behaves exactly the same as the BIO object that is returned by spc_connect( ) or spc_connect_ssl( ). Regardless of whether a new connection was successfully established, the listening BIO object passed into SSL_accept( ) will be ready for another call to SSL_accept( ) to accept the next connection.

See Also

Recipe 9.1, Recipe 10.5

9.3. Using Session Caching to Make SSL Servers More Efficient

Problem

You have a client and server pair that speak SSL to each other. The same client often makes several connections to the same server in a short period of time. You need a way to speed up the process of the client's reconnecting to the server without sacrificing security.

Solution

The terms SSL session and SSL connection are often confused or used interchangeably, but they are, in fact, two different things. An SSL session refers to the set of parameters and encryption keys created by performing an SSL handshake. An SSL connection is an active conversation between two peers that uses an SSL session. Normally, when an SSL connection is established, the handshake process negotiates the parameters that become a session. It is this negotiation that causes establishment of SSL connections to be such an expensive operation.

Luckily, it is possible to cache sessions. Once a client has connected to the server and successfully completed the normal handshake process, both the client and the server can save the session parameters so that the next time the client connects to the server, it can simply reuse the session, thus avoiding the overhead of negotiating new parameters and encryption keys.

Discussion

Session caching is normally not enabled by default, but enabling it is a relatively painless process. OpenSSL does most of the work for you, although you can override much of the default behavior (for example, you might build your own caching mechanism on the server side). By default, OpenSSL uses an in-memory session cache, but if you will be caching a large number of sessions, or if you want sessions to persist across boots, you may be better off using some kind of disk-based cache.

Most of the work required to enable session caching has to be done on the server side, but there's not all that much that needs to be done:

1. Set a session ID context. The purpose of the session ID context is to make sure the session is reused for the same purpose for which it was created. For instance, a session created for an SSL web server should not be automatically allowed for an SSL FTP server. A session ID context can be any arbitrary binary data up to 32 bytes in length. There are no requirements for what the data should be, other than that it should be unique for the purpose your server serves—you don't want to find your server getting sessions from other servers.

2. Set a session timeout. The OpenSSL default is 300 seconds, which is probably a reasonable default for most applications. When a session times out, it is not immediately purged from the server's cache, but it will not be accepted when presented by the client. If a client attempts to use an expired session, the server will remove it from its cache.

3. Set a caching mode. OpenSSL supports a number of possible mode options, specified as a bit mask:

SSL_SESS_CACHE_OFF

Setting this mode disables session caching altogether. If you want to disable session caching, you should specify this flag by itself; you do not need to set a session ID context or a timeout.

SSL_SESS_CACHE_SERVER

Setting this mode causes sessions that are generated by the server to be cached. This is the default mode and should be included whenever you're setting any of the other flags described here, except for SSL_SESS_CACHE_OFF.

SSL_SESS_CACHE_NO_AUTO_CLEAR

By default, the session cache is checked for expired entries once for every 255 connections that are established. Sometimes this can cause an undesirable delay, so it may be desirable to disable this automatic flushing of the cache. If you set this mode, you should make sure that you periodically call SSL_CTX_flush_sessions( ) yourself.

SSL_SESS_CACHE_NO_INTERNAL_LOOKUP

If you want to replace OpenSSL's internal caching mechanism with one of your own devising, you should set this mode. We do not include a recipe that demonstrates the use of this flag in the book, but you can find one on the book's companion web site.

You can use the following convenience function to enable session caching on the server side. If you want to use it with the SSL server functions presented in Recipe 9.2, you should create an SSL_CTX object using spc_create_sslctx( ) yourself. Then call spc_enable_sessions( ) using thatSSL_CTX object, and pass the SSL_CTX object to spc_accept( ) so that a new one will not be created automatically for you. Whether you enable session caching or not, it's a good idea to create your own SSL_CTX object before calling spc_accept( ) anyway, so that a fresh SSL_CTX object isn't created for each and every client connection.

#include <openssl/bio.h>

#include <openssl/ssl.h>

void spc_enable_sessions(SSL_CTX *ctx, unsigned char *id, unsigned int id_len,

long timeout, int mode) {

SSL_CTX_set_session_id_context(ctx, id, id_len);

SSL_CTX_set_timeout(ctx, timeout);

SSL_CTX_set_session_cache_mode(ctx, mode);

}

Enabling session caching on the client side is even easier than it is on the server side. All that's required is setting the SSL_SESSION object in the SSL_CTX object before actually establishing the connection. The following function, spc_reconnect( ) , is a re-implementation of spc_connect_ssl( )with the necessary changes to enable client-side session caching.

BIO *spc_reconnect(char *host, int port, SSL_SESSION *session,

spc_x509store_t *spc_store, SSL_CTX **ctx) {

BIO *conn = 0;

int our_ctx = 0;

SSL *ssl_ptr;

if (*ctx) {

CRYPTO_add(&((*ctx)->references), 1, CRYPTO_LOCK_SSL_CTX);

if (spc_store && spc_store != SSL_CTX_get_app_data(*ctx)) {

SSL_CTX_set_cert_store(*ctx, spc_create_x509store(spc_store));

SSL_CTX_set_app_data(*ctx, spc_store);

}

} else {

*ctx = spc_create_sslctx(spc_store);

our_ctx = 1;

}

if (!(conn = BIO_new_ssl_connect(*ctx))) goto error_exit;

BIO_set_conn_hostname(conn, host);

BIO_set_conn_int_port(conn, &port);

if (session) {

BIO_get_ssl(conn, &ssl_ptr);

SSL_set_session(ssl_ptr, session);

}

if (BIO_do_connect(conn) <= 0) goto error_exit;

if (!our_ctx) SSL_CTX_free(*ctx);

if (session) SSL_SESSION_free(session);

return conn;

error_exit:

if (conn) BIO_free_all(conn);

if (*ctx) SSL_CTX_free(*ctx);

if (our_ctx) *ctx = 0;

return 0;

}

Establishing an SSL connection as a client may be as simple as setting the SSL_SESSION object in the SSL_CTX object, but where does this mysterious SSL_SESSION come from? When a connection is established, OpenSSL creates an SSL session object and tucks it away in the SSL object that is normally hidden away in the BIO object that is returned by spc_connect_ssl( ). You can retrieve it by calling spc_getsession( ) .

SSL_SESSION *spc_getsession(BIO *conn) {

SSL *ssl_ptr;

BIO_get_ssl(conn, &ssl_ptr);

if (!ssl_ptr) return 0;

return SSL_get1_session(ssl_ptr);

}

The SSL_SESSION object that is returned by spc_getsession( ) has its reference count incremented, so you must be sure to call SSL_SESSION_free( ) at some point to release the reference. You can obtain the SSL_SESSION object as soon as you've successfully established a connection, but because the value can change between the time the connection is first established and the time it's terminated due to renegotiation, you should always get the SSL_SESSION object just before the connection is terminated. That way, you can be sure you have the most recent session object.

See Also

Recipe 9.2

9.4. Securing Web Communication on Windows Using the WinInet API

Problem

You are developing a Windows program that needs to connect to an HTTP server with SSL enabled. You want to use the Microsoft WinInet API to communicate with the HTTP server.

Solution

The Microsoft WinInet API was introduced with Internet Explorer 3.0. It provides a set of functions that allow programs easy access to FTP, Gopher, HTTP, and HTTPS servers. For HTTPS servers, the details of using SSL are hidden from the programmer, allowing the programmer to concentrate on the data that needs to be exchanged, rather than protocol details.

Discussion

The Microsoft WinInet API is a rich API that makes client-side interaction with FTP, Gopher, HTTP, and HTTPS servers easy; as with most Windows APIs, however, a sizable amount of code is still required. Because of the wealth of options available, we won't provide fully working code for a WinInet API wrapper here. Instead, we'll discuss the API and provide code samples for the parts of the API that are interesting from a security standpoint. We encourage you to consult Microsoft's documentation on the API to learn about all that the API can do.

If you're going to establish a connection to a web server using SSL with WinInet, the first thing you need to do is create an Internet session by calling InternetOpen( ). This function initializes and returns an object handle that is needed to actually establish a connection. It takes care of such details as presenting the user with the dial-in UI if the user is not connected to the Internet and the system is so configured. Although any number of calls may be made to InternetOpen( ) by a single application, it generally needs to be called only once. The handle it returns can be reused any number of times.

#include <windows.h>

#include <wininet.h>

HINTERNET hInternetSession;

LPSTR lpszAgent = "Secure Programming Cookbook Recipe 9.4";

DWORD dwAccessType = INTERNET_OPEN_TYPE_PROXY;

LPSTR lpszProxyName = 0;

LPSTR lpszProxyBypass = 0;

DWORD dwFlags = 0;

hInternetSession = InternetOpen(lpszAgent, dwAccessType, lpszProxyName,

lpszProxyBypass, dwFlags);

If you set dwAccessType to INTERNET_OPEN_TYPE_PROXY, lpszProxyName to 0, and lpszProxyBypass to 0, the system defaults for HTTP access are used. If the system is configured to use a proxy, it will be used as required. The lpszAgent argument is passed to servers as the client's HTTP agent string. It may be set as any custom string, or it may be set to the same string a specific browser might send to a web server when making a request.

The next step is to connect to the server. You do this by calling InternetConnect( ), which will return a new handle to an object that stores all of the relevant connection information. The two obvious requirements for this function are the name of the server to connect to and the port on which to connect. The name of the server may be specified as either a hostname or a dotted-decimal IP address. You can specify the port as a number or use the constant INTERNET_DEFAULT_HTTPS_PORT to connect to the default SSL-enabled HTTP port 443.

HINTERNET hConnection;

LPSTR lpszServerName = "www.amazon.com";

INTERNET_PORT nServerPort = INTERNET_DEFAULT_HTTPS_PORT;

LPSTR lpszUsername = 0;

LPSTR lpszPassword = 0;

DWORD dwService = INTERNET_SERVICE_HTTP;

DWORD dwFlags = 0;

DWORD dwContext = 0;

hConnection = InternetConnect(hInternetSession, lpszServerName, nServerPort,

lpszUsername, lpszPassword, dwService, dwFlags,

dwContext);

The call to InternetConnect( ) actually establishes a connection to the remote server. If the connection attempt fails for some reason, the return value is NULL, and the error code can be retrieved via GetLastError( ). Otherwise, the new object handle is returned. If multiple requests to the same server are necessary, you should use the same handle, to avoid the overhead of establishing multiple connections.

Once a connection to the server has been established, a request object must be constructed. This object is a container for various information: the resource that will be requested, the headers that will be sent, a set of flags that dictate how the request is to behave, header information returned by the server after the request has been submitted, and other information. A new request object is constructed by calling HttpOpenRequest( ).

HINTERNET hRequest;

LPSTR lpszVerb = "GET";

LPSTR lpszObjectName = "/";

LPSTR lpszVersion = "HTTP/1.1";

LPSTR lpszReferer = 0;

LPSTR lpszAcceptTypes = 0;

DWORD dwFlags = INTERNET_FLAG_SECURE |

INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |

INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS;

DWORD dwContext = 0;

hRequest = HttpOpenRequest(hConnection, lpszVerb, lpszObjectName, lpszVersion,

lpszReferer, lpszAcceptTypes, dwFlags, dwContext);

The lpszVerb argument controls the type of request that will be made, which can be any valid HTTP request, such as GET or POST. The lpszObjectName argument is the resource that is to be requested, which is normally the part of a URL that follows the server name, starting with the forward slash and ending before the query string (which starts with a question mark). Specifying lpszAcceptTypes as 0 tells the server that we can accept any kind of text document; it is equivalent to a MIME type of "text/*".

The most interesting argument passed to HttpOpenRequest( ) is dwFlags. A large number of flags are defined, but only five deal specifically with HTTP over SSL:

INTERNET_FLAG_IGNORE_CERT_CN_INVALID

Normally, as part of verification of the server's certificate, WinInet will verify that the hostname is contained in the certificate's commonName field or subjectAltName extension. If this flag is specified, the hostname check will not be performed. (See Recipe 10.4 and Recipe 10.8 for discussions of the importance of performing hostname checks on certificates.)

INTERNET_FLAG_IGNORE_CERT_DATE_INVALID

An important part of verifying the validity of an X.509 certificate involves checking the dates for which a certificate is valid. If the current date is outside the certificate's valid date range, the certificate should be considered invalid. If this flag is specified, the certificate's validity dates are not checked. This option should never be used in a released version of a product.

INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP

If this flag is specified and the server attempts to redirect the client to a non-SSL URL, the redirection will be ignored. You should always include this flag so you can be sure you are not transferring in the clear data that you expect to be protected.

INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS

If this flag is specified and the server attempts to redirect the client to an SSL- protected URL, the redirection will be ignored. If you're expecting to be communicating only with servers under your own control, it's safe to omit this flag; if not, you might want to consider including it so you're not transferred somewhere other than expected.

INTERNET_FLAG_SECURE

This is the all-important flag. When this flag is included, the use of SSL on the connection is enabled. Without it, SSL is not used, and all data is transferred in the clear. Obviously, you want to include this flag.

Once the request object has been constructed, the request needs to be sent to the server. This is done by calling HttpSendRequest( ) with the request object. Additional headers can be included with the request submission, as well as any optional data to be sent after the headers. You will want to send optional data when performing a POST operation. Additional headers and optional data are both specified as strings and the lengths of the strings.

BOOL bResult;

LPSTR lpszHeaders = 0;

DWORD dwHeadersLength = 0;

LPSTR lpszOptional = 0;

DWORD dwOptionalLength = 0;

bResult = HttpSendRequest(hRequest, lpszHeaders, dwHeadersLength, lpOptional,

dwOptionalLength);

After sending the request, the server's response can be retrieved. As part of sending the request, WinInet will retrieve the response headers from the server. Information about the response can be obtained using the HttpQueryInfo( ) function. A complete list of the information that may be available can be found in the WinInet documentation, but for our purposes, the only information we're concerned with is the content length. The server is not required to send a content length header back as part of its response, so we must also be able to handle the case where it is not sent. Response data sent by the server after its response headers can be obtained by calling InternetReadFile( ) as many times as necessary to retrieve all of the data.

DWORD dwContentLength, dwIndex, dwInfoLevel;

DWORD dwBufferLength, dwNumberOfBytesRead, dwNumberOfBytesToRead;

LPVOID lpBuffer, lpFullBuffer, lpvBuffer;

dwInfoLevel = HTTP_QUERY_CONTENT_LENGTH;

lpvBuffer = (LPVOID)&dwContentLength;

dwBufferLength = sizeof(dwContentLength);

dwIndex = 0;

HttpQueryInfo(hRequest, dwInfoLevel, lpvBuffer, &dwBufferLength, &dwIndex);

if (dwIndex != ERROR_HTTP_HEADER_NOT_FOUND) {

/* Content length is known. Read only that much data. */

lpBuffer = GlobalAlloc(GMEM_FIXED, dwContentLength);

InternetReadFile(hRequest, lpBuffer, dwContentLength, &dwNumberOfBytesRead);

} else {

/* Content length is not known. Read until EOF is reached. */

dwContentLength = 0;

dwNumberOfBytesToRead = 4096;

lpFullBuffer = lpBuffer = GlobalAlloc(GMEM_FIXED, dwNumberOfBytesToRead);

while (InternetReadFile(hRequest, lpBuffer, dwNumberOfBytesToRead,

&dwNumberOfBytesRead)) {

dwContentLength += dwNumberOfBytesRead;

if (dwNumberOfBytesRead != dwNumberOfBytesToRead) break;

lpFullBuffer = GlobalReAlloc(lpFullBuffer, dwContentLength +

dwNumberOfBytesToRead, 0);

lpBuffer = (LPVOID)((LPBYTE)lpFullBuffer + dwContentLength);

}

lpFullBuffer = lpBuffer = GlobalReAlloc(lpFullBuffer, dwContentLength, 0);

}

After the data has been read with InternetReadFile( ), the variable lpBuffer will hold the contents of the server's response, and the variable dwContentLength will hold the number of bytes contained in the response data buffer. At this point, the request has been completed, and the request object should be destroyed by calling InternetCloseHandle( ). If additional requests to the same connection are required, a new request object can be created and used with the same connection handle from the call to InternetConnect( ). When no more requests are to be made on the same connection,InternetCloseHandle( ) should be used to close the connection. Finally, when no more WinInet activity is to take place using the Internet session object created by InternetConnect( ), InternetCloseHandle( ) should be called to clean up that object as well.

InternetCloseHandle(hRequest);

InternetCloseHandle(hConnection);

InternetCloseHandle(hInternetSession);

See Also

Recipe 10.4, Recipe 10.8

9.5. Enabling SSL without Modifying Source Code

Problem

You have an existing client or server that is not SSL-enabled, and you want to make it so without modifying its source code to add SSL support.

Solution

Stunnel is a program that uses OpenSSL to create SSL tunnels between clients and servers that do not natively support SSL. At the time of this writing, the latest release is 4.04, and it is available for Unix and Windows from http://www.stunnel.org. For servers, it listens on another socket for SSL connections and forwards data bidirectionally to the real server over a non-SSL connection. SSL-enabled clients can then connect to Stunnel's listening port and communicate with the server that is not SSL-enabled. For clients, it listens on a socket for non-SSL connections and forwards data bidirectionally to the server over an SSL-enabled connection.

Stunnel has existed for a number of years and has traditionally used command-line switches to control its behavior. Version 4.00 changed that. Stunnel now uses a configuration file to control its behavior, and all formerly supported command-line switches have been removed. We'll cover the latest version, 4.04, in this recipe.

Discussion

While this recipe does not actually contain any code, we've included this section because we consider Stunnel a tool worth discussing, particularly if you are developing SSL-enabled clients and servers. It can be quite a frustrating experience to attempt to develop and debug SSL-enabled clients and servers together from the ground up, especially if you do not have any prior experience programming with SSL. Stunnel will help you debug your SSL code.

A Stunnel configuration file is organized in sections. Each section contains a set of keys, and each key has an associated value. Sections and keys are both named and case-insensitive. A configuration file is parsed from top to bottom with sections delimited by a line containing the name of the section surrounded by square brackets. The other lines contain key and value pairs that belong to the most recently parsed section delimiter. In addition, an optional global section that is unnamed occurs before the first named section in the file. Keys are separated from their associated value by an equal sign (=).

Comments may only begin at the start of a line that begins with a hash mark (#) (optionally preceded by whitespace), and the whole line is treated as a comment. Any leading or trailing whitespace surrounding a key or a value is stripped. Any other whitespace is significant, including leading or trailing whitespace surrounding a section name (as it would occur between the square brackets). For example, "[ my_section ]" is not the same as "[my_section]". The documentation included with Stunnel describes the supported keys sufficiently well, so we won't duplicate it here.

One nice advantage of the configuration files over the old command-line interface is that each section in the configuration file defines either a client or a server, so a single instance of Stunnel can be used to run multiple clients or servers. If you want to run both clients and servers, you still need two instances of Stunnel running because the flag that determines which mode to run in is a global option. With the command-line interface, multiple instances of Stunnel used to be required, one for each client or server that you wanted to run. Therefore, if you wanted to use Stunnel for POP3, IMAP, and SMTPS servers, you needed to run three instances of Stunnel.

Each section name defines the name of the service that will be used with TCP Wrappers and for logging purposes. For both clients and servers, specify the accept and connect keys. The accept key specifies the port on which Stunnel will listen for incoming connections, and the connect key specifies the port that Stunnel will attempt to connect to for outgoing connections. At a minimum, these two keys must specify a port number, but they may also optionally include a hostname or IP address. To include a hostname or IP address, precede the port number with the hostname or IP address, and separate the two with a colon (:).

You enable the mode for Stunnel as follows:

Server mode

To enable server mode, set the global option key client to no. When running in server mode, Stunnel expects incoming connections to speak SSL and makes outgoing connections without SSL. You will also need to set the two global options cert and key to the names of files containing the certificate and key to use.

Client mode

To enable client mode, set the global option key client to yes. In client mode, Stunnel expects incoming connection to be operating without SSL and makes outgoing connections using SSL. A certificate and key may be specified, but they are not required.

The following example starts up two servers. The first is for IMAP over SSL, which will listen for SSL connections on port 993 and redirect traffic without SSL to a connection on port 110. The second is for POP3 over SSL, which will listen for SSL connections on port 995 for the localhost (127.0.0.1) interface only. Outgoing connections will be made to port 110 on the localhost interface.

client = no

cert = /home/mmessier/ssl/servercert.pem

key = /home/mmessier/ssl/serverkey.pem

[imaps]

accept = 993

connect = 143

[pop3]

accept = localhost:995

connect = localhost:110

In the following example, Stunnel operates in client mode. It listens for connections on the localhost interface on port 25, and it redirects traffic to port 465 on smtp.secureprogramming.com. This example would be useful for a mail client that does not support SMTP over SSL.

client = yes

[smtp]

accept = localhost:25

connect = smtp.secureprogramming.com:465

See Also

Stunnel web page: http://www.stunnel.org

9.6. Using Kerberos Encryption

Problem

You need to use encryption in code that already uses Kerberos for authentication.

Solution

Kerberos is primarily an authentication service employed for network services. As a side effect of the requirements to perform authentication, Kerberos also provides an API for encryption and decryption, although the number of supported ciphers is considerably fewer than those provided by other cryptographic protocols. Authentication yields a cryptographically strong session key that can be used as a key for encryption.

This recipe works on Unix and Windows with the Heimdal and MIT Kerberos implementations. The code presented here will not work on Windows systems that are Kerberos-enabled with the built-in Windows support, because Windows does not expose the Kerberos API in such a way that the code could be made to work. In particular, the encryption and decryption functions used in this recipe are not present on Windows unless you are using either Heimdal or MIT Kerberos. Instead, you should use CryptoAPI on Windows (see Recipe 5.25).

Discussion

Kerberos provides authentication between clients and servers, communicating over an established data connection. The Kerberos API provides no support for establishing, terminating, or passing arbitrary data over a data connection, whether pipes, sockets, or otherwise. Once its job has been successfully performed, a cryptographically strong session key that can be used as a key for encryption is "left behind."

We present a discussion of how to authenticate using Kerberos in Recipe 8.13. In this recipe, we pick up at the point where Kerberos authentication has completed successfully. At this point, you'll be left with at least a krb5_context object and a krb5_auth_context object. Using these two objects, you can obtain a krb5_keyblock object that contains the session key by calling krb5_auth_con_getremotesubkey( ) . The prototype for this function is as follows:

krb5_error_code krb5_auth_con_getremotesubkey(krb5_context context,

krb5_auth_context auth_context,

krb5_keyblock **key_block);

Once you have the session key, you can use it for encryption and decryption.

Kerberos supports only a limited number of symmetric ciphers, which may vary depending on the version of Kerberos that you are using. For maximum portability, you are limited primarily to DES and 3-key Triple-DES in CBC mode. The key returned from krb_auth_con_getremotesubkey( )will have an algorithm already associated with it, so you don't even have to choose. As part of the authentication process, the client and server will negotiate the strongest cipher that both are capable of supporting, which will (we hope) be Triple-DES (or something stronger) instead of DES, which is actually rather weak. In fact, if DES is negotiated, you may want to consider refusing to proceed.

Many different implementations of Kerberos exist today. The most prominent among the free implementations is the MIT implementation, which is distributed with Darwin and many Linux distributions. Another popular implementation is the Heimdal implementation, which is distributed with FreeBSD and OpenBSD. Unfortunately, while the two implementations share much of the same API, there are differences. In particular, the API for encryption services that we will be using in this recipe differs between the two. To determine which implementation is being used, we test for the existence of the KRB5_GENERAL_ _ preprocessor macro, which will be defined by the MIT implementation but not the Heimdal implementation.

Given a krb5_keyblock object, you can determine whether DES was negotiated using the following function:

#include <krb5.h>

int spc_krb5_isdes(krb5_keyblock *key) {

#ifdef KRB5_GENERAL_ _

if (key->enctype = = ENCTYPE_DES_CBC_CRC || key->enctype = = ENCTYPE_DES_CBC_MD4 ||

key->enctype = = ENCTYPE_DES_CBC_MD5 || key->enctype = = ENCTYPE_DES_CBC_RAW)

return 1;

#else

if (key->keytype = = ETYPE_DES_CBC_CRC || key->keytype = = ETYPE_DES_CBC_MD4 ||

key->keytype = = ETYPE_DES_CBC_MD5 || key->keytype = = ETYPE_DES_CBC_NONE ||

key->keytype = = ETYPE_DES_CFB64_NONE || key->keytype = = ETYPE_DES_PCBC_NONE)

return 1;

#endif

return 0;

}

The krb5_context object and the krb5_keyblock object can then be used together as arguments to spc_krb5_encrypt( ) , which we implement below. The function also requires a buffer that holds the data to be encrypted along with the size of the buffer, as well as a pointer to receive a dynamically allocated buffer that will hold the encrypted data on return, and a pointer to receive the size of the encrypted data buffer.

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <krb5.h>

int spc_krb5_encrypt(krb5_context ctx, krb5_keyblock *key, void *inbuf,

size_t inlen, void **outbuf, size_t *outlen) {

#ifdef KRB5_GENERAL_ _

size_t blksz, newlen;

krb5_data in_data;

krb5_enc_data out_data;

if (krb5_c_block_size(ctx, key->enctype, &blksz)) return 0;

if (!(inlen % blksz)) newlen = inlen + blksz;

else newlen = ((inlen + blksz - 1) / blksz) * blksz;

in_data.magic = KV5M_DATA;

in_data.length = newlen;

in_data.data = malloc(newlen);

if (!in_data.data) return 0;

memcpy(in_data.data, inbuf, inlen);

spc_add_padding((unsigned char *)in_data.data + inlen, inlen, blksz);

if (krb5_c_encrypt_length(ctx, key->enctype, in_data.length, outlen)) {

free(in_data.data);

return 0;

}

out_data.magic = KV5M_ENC_DATA;

out_data.enctype = key->enctype;

out_data.kvno = 0;

out_data.ciphertext.magic = KV5M_ENCRYPT_BLOCK;

out_data.ciphertext.length = *outlen;

out_data.ciphertext.data = malloc(*outlen);

if (!out_data.ciphertext.data) {

free(in_data.data);

return 0;

}

if (krb5_c_encrypt(ctx, key, 0, 0, &in_data, &out_data)) {

free(in_data.data);

return 0;

}

*outbuf = out_data.ciphertext.data;

free(in_data.data);

return 1;

#else

int result;

void *tmp;

size_t blksz, newlen;

krb5_data edata;

krb5_crypto crypto;

if (krb5_crypto_init(ctx, key, 0, &crypto) != 0) return 0;

if (krb5_crypto_getblocksize(ctx, crypto, &blksz)) {

krb5_crypto_destroy(ctx, crypto);

return 0;

}

if (!(inlen % blksz)) newlen = inlen + blksz;

else newlen = ((inlen + blksz - 1) / blksz) * blksz;

if (!(tmp = malloc(newlen))) {

krb5_crypto_destroy(ctx, crypto);

return 0;

}

memcpy(tmp, inbuf, inlen);

spc_add_padding((unsigned char *)tmp + inlen, inlen, blksz);

if (!krb5_encrypt(ctx, crypto, 0, tmp, inlen, &edata)) {

if ((*outbuf = malloc(edata.length)) != 0) {

result = 1;

memcpy(*outbuf, edata.data, edata.length);

*outlen = edata.length;

}

krb5_data_free(&edata);

}

free(tmp);

krb5_crypto_destroy(ctx, crypto);

return result;

#endif

}

The decryption function works identically to the encryption function. Remember that DES and Triple-DES are block mode ciphers, so padding may be necessary if the data you're encrypting is not an exact multiple of the block size. While the Kerberos library will do any necessary padding for you, it does so by padding with zero bytes, which is a poor way to pad out the block. Therefore, we do our own padding using the code from Recipe 5.11 to perform PKCS block padding.

#include <stdlib.h>

#include <string.h>

#include <krb5.h>

int spc_krb5_decrypt(krb5_context ctx, krb5_keyblock *key, void *inbuf,

size_t inlen, void **outbuf, size_t *outlen) {

#ifdef KRB5_GENERAL_ _

int padding;

krb5_data out_data;

krb5_enc_data in_data;

in_data.magic = KV5M_ENC_DATA;

in_data.enctype = key->enctype;

in_data.kvno = 0;

in_data.ciphertext.magic = KV5M_ENCRYPT_BLOCK;

in_data.ciphertext.length = inlen;

in_data.ciphertext.data = inbuf;

out_data.magic = KV5M_DATA;

out_data.length = inlen;

out_data.data = malloc(inlen);

if (!out_data.data) return 0;

if (krb5_c_block_size(ctx, key->enctype, &blksz)) {

free(out_data.data);

return 0;

}

if (krb5_c_decrypt(ctx, key, 0, 0, &in_data, &out_data)) {

free(out_data.data);

return 0;

}

if ((padding = spc_remove_padding((unsigned char *)out_data.data +

out_data.length - blksz, blksz)) = = -1) {

free(out_data.data);

return 0;

}

*outlen = out_data.length - (blksz - padding);

if (!(*outbuf = realloc(out_data.data, *outlen))) *outbuf = out_data.data;

return 1;

#else

int padding, result;

void *tmp;

size_t blksz;

krb5_data edata;

krb5_crypto crypto;

if (krb5_crypto_init(ctx, key, 0, &crypto) != 0) return 0;

if (krb5_crypto_getblocksize(ctx, crypto, &blksz) != 0) {

krb5_crypto_destroy(ctx, crypto);

return 0;

}

if (!(tmp = malloc(inlen))) {

krb5_crypto_destroy(ctx, crypto);

return 0;

}

memcpy(tmp, inbuf, inlen);

if (!krb5_decrypt(ctx, crypto, 0, tmp, inlen, &edata)) {

if ((padding = spc_remove_padding((unsigned char *)edata.data + edata.length -

blksz, blksz)) != -1) {

*outlen = edata.length - (blksz - padding);

if ((*outbuf = malloc(*outlen)) != 0) {

result = 1;

memcpy(*outbuf, edata.data, *outlen);

}

}

krb5_data_free(&edata);

}

free(tmp);

krb5_crypto_destroy(ctx, crypto);

return result;

#endif

}

See Also

Recipe 5.11, Recipe 5.25, Recipe 8.13

9.7. Performing Interprocess Communication Using Sockets

Problem

You have two or more processes running on the same machine that need to communicate with each other.

Solution

Modern operating systems support a variety of interprocess communications primitives that vary from system to system. If you intend to make your program portable among different platforms and even different implementations of Unix, your best bet is to use sockets. All modern operating systems support the Berkeley socket interface for TCP/IP at a minimum, while most—if not all—Unix implementations also support Unix domain sockets.

Discussion

Many operating systems support various methods of allowing two or more processes to communicate with each other. Most systems (including both Unix and Windows) support anonymous and named pipes. Many Unix systems (including BSD) also support message queues, which have their origins in AT&T's System V Unix. Windows systems have a similar construct known as mailslots. Unix systems also have Unix domain sockets, which share the Berkeley socket interface with TCP/IP sockets. Here's an overview of common mechanisms:

Anonymous pipes

Anonymous pipes are useful for communication between a parent and a child process. The parent can create the two endpoints of the pipe before spawning the child process, and the child process will inherit the file descriptors from the parent. There are ways on both Unix and Windows for two otherwise unrelated processes to exchange file descriptors, but this is rarely done. On Unix, you can use Unix Domain sockets. On Windows, you can use the OpenProcess( ) and DuplicateHandle( ) Win32 API functions.

Named pipes

Instead of using anonymous pipes between unrelated processes, a better solution may be to use named pipes. With named pipes, a process can create a pipe that has a name associated with it. Another process that knows the name of the pipe can then open the pipe. On Unix, named pipes are actually special files created in the filesystem, and the name used for the pipe is the name of that special file. Windows uses a special namespace in the kernel and does not actually use the filesystem at all, although the restrictions on the names given to pipes are similar to those for files. Pipes work well when there are only two processes involved, but adding additional processes to the mix quickly complicates matters. Pipes were not designed for use by more than two processes at a time and we strongly advise against attempting to use pipes in this way.

Message queues (Unix)

Unix message queues are named with an arbitrary integer value called a key . Often a file is created, and that file's inode is used as the key for the message queue. Any process that has permission to read from the message queue can do so. Likewise, any process with the proper permissions may write to the message queue. Message queues require cooperation among the processes that will use the queues. A malicious program can easily subvert that cooperation and steal messages away from the queue. Message queues are also limited such that they can only handle small amounts of data.

Mailslots (Windows)

Windows mailslots can be named just as named pipes can be, though there are two separate and distinct namespaces. Mailslots are a one-way communication mechanism. Only the process that creates the mailslot can read from it; other processes can only write to it. Mailslots work well when there is a single process that needs to receive information from other processes but does not need to send anything back.

Sockets

It is difficult these days to find an operating system that does not support the Berkeley socket interface for TCP/IP sockets. While most TCP/IP connections are established over a network between two different machines, it is also possible to connect two processes running on the same machine without ever touching a network using TCP/IP. On Unix, the same interface can also be used for Unix Domain sockets, which are faster, can be used to exchange file descriptors, and can also be used to exchange credentials (see Recipe 9.8).

Using TCP/IP sockets for interprocess communication (IPC) is not very different from using them for network communications. In fact, you could use them in exactly the same way and it would work just fine, but if your intent is to use the sockets strictly for local IPC, there are a couple of additional things that you should do, which we discuss in the following paragraphs.

If you are using TCP/IP sockets for local IPC, the most important thing for you to know is that you should always use the loopback address. When you bind a socket, do not bind to INADDR_ANY, but instead use 127.0.0.1. If you do this, you will only be able to connect to the port using the 127.0.0.1 address. This means that the server will be unreachable from any other machine, whether or not you have blocked the port in your firewall.

On Windows systems, the following code will strictly use TCP/IP sockets, but on Unix systems, we have made an optimization to use Unix sockets if the loopback address of 127.0.0.1 is used. We have created a wrapper around the socket descriptor that keeps track of the type of socket (Unix or TCP/IP) and the address to which it is bound. This information is then used in spc_socket_accept( ) , spc_socket_sendto( ) , and spc_socket_recvfrom( ) , which act as wrappers around accept( ), sendto( ), and recvfrom( ), respectively.

Remember that on Windows you must call WSAStartup( ) before you can use any socket functions. You should also be sure to call WSACleanup( ) when you have finished using sockets in your program.

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#ifndef WIN32

#include <errno.h>

#include <netdb.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <sys/un.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#define INVALID_SOCKET -1

#define closesocket(x) close((x))

#else

#include <windows.h>

#include <winsock2.h>

#endif

#define SPC_SOCKETFLAG_BOUND 0x1

#define SPC_SOCKETFLAG_DGRAM 0x2

typedef struct {

#ifdef WIN32

SOCKET sd;

#else

int sd;

#endif

int domain;

struct sockaddr *addr;

int addrlen;

int flags;

} spc_socket_t;

void spc_socket_close(spc_socket_t *);

static int make_sockaddr(int *domain, struct sockaddr **addr, char *host,

int port) {

int addrlen;

in_addr_t ipaddr;

struct hostent *he;

struct sockaddr_in *addr_inet;

if (!host) ipaddr = INADDR_ANY;

else {

if (!(he = gethostbyname(host))) {

if ((ipaddr = inet_addr(host)) = = INADDR_NONE) return 0;

} else ipaddr = *(in_addr_t *)he->h_addr_list[0];

endhostent( );

}

#ifndef WIN32

if (inet_addr("127.0.0.1") = = ipaddr) {

struct sockaddr_un *addr_unix;

*domain = PF_LOCAL;

addrlen = sizeof(struct sockaddr_un);

if (!(*addr = (struct sockaddr *)malloc(addrlen))) return 0;

addr_unix = (struct sockaddr_un *)*addr;

addr_unix->sun_family = AF_LOCAL;

snprintf(addr_unix->sun_path, sizeof(addr_unix->sun_path),

"/tmp/127.0.0.1:%d", port);

#ifndef linux

addr_unix->sun_len = SUN_LEN(addr_unix) + 1;

#endif

return addrlen;

}

#endif

*domain = PF_INET;

addrlen = sizeof(struct sockaddr_in);

if (!(*addr = (struct sockaddr *)malloc(addrlen))) return 0;

addr_inet = (struct sockaddr_in *)*addr;

addr_inet->sin_family = AF_INET;

addr_inet->sin_port = htons(port);

addr_inet->sin_addr.s_addr = ipaddr;

return addrlen;

}

static spc_socket_t *create_socket(int type, int protocol, char *host, int port) {

spc_socket_t *sock;

if (!(sock = (spc_socket_t *)malloc(sizeof(spc_socket_t)))) return 0;

sock->sd = INVALID_SOCKET;

sock->addr = 0;

sock->flags = 0;

if (!(sock->addrlen = make_sockaddr(&sock->domain, &sock->addr, host, port)))

goto error_exit;

if ((sock->sd = socket(sock->domain, type, protocol)) = = INVALID_SOCKET)

goto error_exit;

return sock;

error_exit:

if (sock) spc_socket_close(sock);

return 0;

}

void spc_socket_close(spc_socket_t *sock) {

if (!sock) return;

if (sock->sd != INVALID_SOCKET) closesocket(sock->sd);

if (sock->domain = = PF_LOCAL && (sock->flags & SPC_SOCKETFLAG_BOUND))

remove(((struct sockaddr_un *)sock->addr)->sun_path);

if (sock->addr) free(sock->addr);

free(sock);

}

spc_socket_t *spc_socket_listen(int type, int protocol, char *host, int port) {

int opt = 1;

spc_socket_t *sock = 0;

if (!(sock = create_socket(type, protocol, host, port))) goto error_exit;

if (sock->domain = = PF_INET) {

if (setsockopt(sock->sd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) = = -1)

goto error_exit;

if (bind(sock->sd, sock->addr, sock->addrlen) = = -1) goto error_exit;

} else {

if (bind(sock->sd, sock->addr, sock->addrlen) = = -1) {

if (errno != EADDRINUSE) goto error_exit;

if (connect(sock->sd, sock->addr, sock->addrlen) != -1) goto error_exit;

remove(((struct sockaddr_un *)sock->addr)->sun_path);

if (bind(sock->sd, sock->addr, sock->addrlen) = = -1) goto error_exit;

}

}

sock->flags |= SPC_SOCKETFLAG_BOUND;

if (type = = SOCK_STREAM && listen(sock->sd, SOMAXCONN) = = -1) goto error_exit;

else sock->flags |= SPC_SOCKETFLAG_DGRAM;

return sock;

error_exit:

if (sock) spc_socket_close(sock);

return 0;

}

spc_socket_t *spc_socket_accept(spc_socket_t *sock) {

spc_socket_t *new_sock = 0;

if (!(new_sock = (spc_socket_t *)malloc(sizeof(spc_socket_t))))

goto error_exit;

new_sock->sd = INVALID_SOCKET;

new_sock->domain = sock->domain;

new_sock->addrlen = sock->addrlen;

new_sock->flags = 0;

if (!(new_sock->addr = (struct sockaddr *)malloc(sock->addrlen)))

goto error_exit;

if (!(new_sock->sd = accept(sock->sd, new_sock->addr, &(new_sock->addrlen))))

goto error_exit;

return new_sock;

error_exit:

if (new_sock) spc_socket_close(new_sock);

return 0;

}

spc_socket_t *spc_socket_connect(char *host, int port) {

spc_socket_t *sock = 0;

if (!(sock = create_socket(SOCK_STREAM, 0, host, port))) goto error_exit;

if (connect(sock->sd, sock->addr, sock->addrlen) = = -1) goto error_exit;

return sock;

error_exit:

if (sock) spc_socket_close(sock);

return 0;

}

int spc_socket_sendto(spc_socket_t *sock, const void *msg, int len, int flags,

char *host, int port) {

int addrlen, domain, result = -1;

struct sockaddr *addr = 0;

if (!(addrlen = make_sockaddr(&domain, &addr, host, port))) goto end;

result = sendto(sock->sd, msg, len, flags, addr, addrlen);

end:

if (addr) free(addr);

return result;

}

int spc_socket_recvfrom(spc_socket_t *sock, void *buf, int len, int flags,

spc_socket_t **src) {

int result;

if (!(*src = (spc_socket_t *)malloc(sizeof(spc_socket_t)))) goto error_exit;

(*src)->sd = INVALID_SOCKET;

(*src)->domain = sock->domain;

(*src)->addrlen = sock->addrlen;

(*src)->flags = 0;

if (!((*src)->addr = (struct sockaddr *)malloc((*src)->addrlen)))

goto error_exit;

result = recvfrom(sock->sd, buf, len, flags, (*src)->addr, &((*src)->addrlen));

if (result = = -1) goto error_exit;

return result;

error_exit:

if (*src) {

spc_socket_close(*src);

*src = 0;

}

return -1;

}

int spc_socket_send(spc_socket_t *sock, const void *buf, int buflen) {

int nb, sent = 0;

while (sent < buflen) {

nb = send(sock->sd, (const char *)buf + sent, buflen - sent, 0);

if (nb = = -1 && (errno = = EAGAIN || errno = = EINTR)) continue;

if (nb <= 0) return nb;

sent += nb;

}

return sent;

}

int spc_socket_recv(spc_socket_t *sock, void *buf, int buflen) {

int nb, recvd = 0;

while (recvd < buflen) {

nb = recv(sock->sd, (char *)buf + recvd, buflen - recvd, 0);

if (nb = = -1 && (errno = = EAGAIN || errno = = EINTR)) continue;

if (nb <= 0) return nb;

recvd += nb;

}

return recvd;

}

See Also

Recipe 9.8

9.8. Performing Authentication with Unix Domain Sockets

Problem

Using a Unix domain socket, you want to find out information about the process that is on the other end of the connection, such as its user and group IDs.

Solution

Most Unix domain socket implementations provide support for receiving the credentials of the peer process involved in a Unix domain socket connection. Using this information, we can discover the user ID and group ID of the process on the other end of the connection. Credential information is not passed automatically. For all implementations, the receiver must explicitly ask for the information. With some implementations, the information must be explicitly sent. In general, when you're designing a system that will exchange credentials, you should be sure to coordinate on both ends exactly when the credentials will be requested and sent.

This recipe works on FreeBSD, Linux, and NetBSD. Unfortunately, not all Unix domain socket implementations provide support for credentials. At the time of this writing, the Darwin kernel (the core of MacOS X), OpenBSD, and Solaris do not support credentials.

Discussion

In addition to the previously mentioned platform support limitations with credentials, a second problem is that different implementations exchange the information in different ways. On FreeBSD systems, for example, the information must be explicitly sent, and the receiver must be able to handle receiving it. On Linux systems, the information is automatically sent if the receiver asks for it.

A third problem is that not all implementations pass the same information. Linux passes the process ID, user ID, and group ID of the sending process. FreeBSD includes all groups that the process is a member of, but it does not include the process ID. At a minimum, you can expect to get the process's user and group IDs and nothing more.

#include <errno.h>

#include <stdlib.h>

#include <string.h>

#include <sys/param.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <sys/uio.h>

#if !defined(linux) && !defined(__NetBSD__)

#include <sys/ucred.h>

#endif

#ifndef SCM_CREDS

#define SCM_CREDS SCM_CREDENTIALS

#endif

#ifndef linux

# ifndef __NetBSD__

# define SPC_PEER_UID(c) ((c)->cr_uid)

# define SPC_PEER_GID(c) ((c)->cr_groups[0])

# else

# define SPC_PEER_UID(c) ((c)->sc_uid)

# define SPC_PEER_GID(c) ((c)->sc_gid)

# endif

#else

# define SPC_PEER_UID(c) ((c)->uid)

# define SPC_PEER_GID(c) ((c)->gid)

#endif

#ifdef __NetBSD__

typedef struct sockcred spc_credentials;

#else

typedef struct ucred spc_credentials;

#endif

spc_credentials *spc_get_credentials(int sd) {

int nb, sync;

char ctrl[CMSG_SPACE(sizeof(struct ucred))];

size_t size;

struct iovec iov[1] = { { 0, 0 } };

struct msghdr msg = { 0, 0, iov, 1, ctrl, sizeof(ctrl), 0 };

struct cmsghdr *cmptr;

spc_credentials *credentials;

#ifdef LOCAL_CREDS

nb = 1;

if (setsockopt(sd, 0, LOCAL_CREDS, &nb, sizeof(nb)) == -1) return 0;

#else

#ifdef SO_PASSCRED

nb = 1;

if (setsockopt(sd, SOL_SOCKET, SO_PASSCRED, &nb, sizeof(nb)) == -1)

return 0;

#endif

#endif

do {

msg.msg_iov->iov_base = (void *)&sync;

msg.msg_iov->iov_len = sizeof(sync);

nb = recvmsg(sd, &msg, 0);

} while (nb == -1 && (errno == EINTR || errno == EAGAIN));

if (nb == -1) return 0;

if (msg.msg_controllen < sizeof(struct cmsghdr)) return 0;

cmptr = CMSG_FIRSTHDR(&msg);

#ifndef __NetBSD__

size = sizeof(spc_credentials);

#else

if (cmptr->cmsg_len < SOCKCREDSIZE(0)) return 0;

size = SOCKCREDSIZE(((cred *)CMSG_DATA(cmptr))->sc_ngroups);

#endif

if (cmptr->cmsg_len != CMSG_LEN(size)) return 0;

if (cmptr->cmsg_level != SOL_SOCKET) return 0;

if (cmptr->cmsg_type != SCM_CREDS) return 0;

if (!(credentials = (spc_credentials *)malloc(size))) return 0;

*credentials = *(spc_credentials *)CMSG_DATA(cmptr);

return credentials;

}

int spc_send_credentials(int sd) {

int sync = 0x11223344;

struct iovec iov[1] = { { 0, 0, } };

struct msghdr msg = { 0, 0, iov, 1, 0, 0, 0 };

#if !defined(linux) && !defined(__NetBSD__)

char ctrl[CMSG_SPACE(sizeof(spc_credentials))];

struct cmsghdr *cmptr;

msg.msg_control = ctrl;

msg.msg_controllen = sizeof(ctrl);

cmptr = CMSG_FIRSTHDR(&msg);

cmptr->cmsg_len = CMSG_LEN(sizeof(spc_credentials));

cmptr->cmsg_level = SOL_SOCKET;

cmptr->cmsg_type = SCM_CREDS;

memset(CMSG_DATA(cmptr), 0, sizeof(spc_credentials));

#endif

msg.msg_iov->iov_base = (void *)&sync;

msg.msg_iov->iov_len = sizeof(sync);

return (sendmsg(sd, &msg, 0) != -1);

}

On all platforms, it is possible to obtain credentials from a peer at any point during the connection; however, it often makes the most sense to get the information immediately after the connection is established. For example, if your server needs to get the credentials of each client that connects, the server code might look something like this:

typedef void (*spc_client_fn)(spc_socket_t *, spc_credentials *, void *);

void spc_unix_server(spc_client_fn callback, void *arg) {

spc_socket_t *client, *listener;

spc_credentials *credentials;

listener = spc_socket_listen(SOCK_STREAM, 0, "127.0.0.1", 2222);

while ((client = spc_socket_accept(listener)) != 0) {

if (!(credentials = spc_get_credentials(client->sd))) {

printf("Unable to get credentials from connecting client!\n");

spc_socket_close(client);

} else {

printf("Client credentials:\n\tuid: %d\n\tgid: %d\n",

SPC_PEER_UID(credentials), SPC_PEER_GID(credentials));

/* do something with the credentials and the connection ... */

callback(client, credentials, arg);

}

}

}

The corresponding client code might look something like this:

spc_socket_t *spc_unix_connect(void) {

spc_socket_t *conn;

if (!(conn = spc_socket_connect("127.0.0.1", 2222))) {

printf("Unable to connect to the server!\n");

return 0;

}

if (!spc_send_credentials(conn->sd)) {

printf("Unable to send credentials to the server!\n");

spc_socket_close(conn);

return 0;

}

printf("Credentials were successfully sent to the server.\n");

return conn;

}

Note finally that while it is possible to obtain credentials from a peer at any point during the connection, many implementations will send the credentials only once. If you need the credential information at more than one point during a conversation, you should make sure to save the information that was obtained the first time it was needed.

9.9. Performing Session ID Management

Problem

Your web application requires users to log in before they can perform meaningful transactions with the application. Once the user is logged in, you need to track the session until the user logs out.

Solution

The solution to this problem is actually straightforward. If the user presents the proper password, you generate a session ID and return it to the client in a cookie. While the session is active, the client sends the session ID back to the server; the server verifies it against an internal table of sessions that has the relevant user information associated with each session ID, allowing the server to proceed without requiring the client to continually send the user name and password to the server. For maximum security, all communications should be done over an SSL-enabled connection.

The only trick is that the ID should be large and cryptographically random, in order to prevent hijacking attempts.

Discussion

Unfortunately, there is little that can be done to prevent session hijacking if an attacker can somehow gain access to the session ID that you need to generate for the user if the login attempt is successful. Normally, the cookie used to carry the session ID should not be permanent (i.e., it expires when the user shuts down the browser), so most browsers will never store the cookie on disk, keeping the cookie only in memory. While this does not make it impossible for an attacker to obtain the session ID, it makes it considerably more difficult.

This issue underscores the need to use SSL properly, which is usually not a problem between browsers and web servers. Take heed of this for other applications of SSL, however. If certificates are not properly verified, allowing an attacker to execute a man-in-the-middle attack, the session ID can be captured. At that point, it hardly matters, though. If such an attack can be mounted, the attacker can do far worse than simply capture session IDs.

The only real requirement for generating a session ID is that it should be unique and not predictable. A base64-encoded 128-bit cryptographically strong random number should generally suffice for the task, but there are many other ways to achieve the same end. For example, you could hash a random number or encrypt some identifying data using a symmetric cipher. However you want to do it is fine—just make sure it's unique and unpredictable! You'll always need some element of randomness in your session IDs, though, so we recommend that you always include at least a 64-bit, cryptographically strong, random number.

Depending on how you choose to generate your session ID, you may require a lookup table keyed by the session ID. In the table, you'll need at least to keep the username associated with the session ID so that you know which user you're dealing with. You can also attach timing information to perform session expiration. If you don't want to get that fancy, and all you need to keep track of is the user's name or some kind of internal user ID, a good solution is to encrypt that information along with some other information. If you choose to do this, be sure to include a nonce, and properly MAC and encrypt the data (e.g., with CWC mode from Recipe 5.10, or as described in Recipe 6.18); the result will be the session ID. In some instances, you may want to bind the IP address into the cookie as well.

WARNING

You may be tempted to bind the IP address of the client into the session identifier. Think carefully before doing this because it is common for clients to change IP addresses, particularly if they are mobile or connecting to your server through a proxy that is actually a pool of machines, all with different IP addresses. Two connections from the same client are not guaranteed to have the same IP address.

See Also

Recipe 5.10, Recipe 6.18

9.10. Securing Database Connections

Problem

You're using a database backend in your application, and you want to ensure that network traffic between your application and the database server is secured with SSL.

Solution

MySQL 4.00, PostgreSQL 7.1, and newer versions of each of these servers support SSL-enabled connections between clients and servers. If you're using older versions or another server that's not covered here that does not support SSL natively, you may wish to use Stunnel (see Recipe 9.5) to secure connections to the server.

Discussion

In the following subsections we'll look at the different issues for MySQL and PostgreSQL.

MySQL

By default, SSL support is disabled when you are building MySQL. To build MySQL with OpenSSL support enabled, you must specify the --with-vio and --with-openssl options on the command line to the configuration script. Once you have an SSL-enabled version of MySQL built, installed, and running, you can verify that SSL is supported with the following SQL command:

SHOW VARIABLES LIKE 'have_openssl'

If the result of the command is yes, SSL support is enabled.

With an SSL-enabled version of MySQL running, you can use the GRANT command to designate SSL requirements for accessing a particular database or table by user. Any client can specify that it wants to connect to the server using SSL, but with the GRANT options, it can be required.

When writing code using the MySQL C API, use the following mysql_real_connect( ) function to establish a connection to the server instead of using mysql_connect( ), which has been deprecated. All that is actually required to establish an SSL connection from the client to the server is to specify the CLIENT_SSL flag to mysql_real_connect( ).

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <mysql.h>

int spc_mysql_real_connect(MYSQL *mysql, const char *host, const char *pw,

const char *db, unsigned int flags) {

int port = 0, result = 0;

char *host_copy = 0, *p;

const char *socket = 0, *user = 0;

if (host) {

if (!(host_copy = strdup(host))) return 0;

if ((p = strchr(host_copy, '@')) != 0) {

user = host_copy;

*p++ = 0;

host = p;

}

if ((p = strchr((p ? p : host_copy), ':')) != 0) {

*p++ = 0;

port = atoi(p);

}

if (*host = = '/') {

socket = host;

host = 0;

}

}

/* this bit of magic is all it takes to enable SSL connections */

flags |= CLIENT_SSL;

if (mysql_real_connect(mysql, host, user, pw, db, port, socket, flags))

result = 1;

if (host_copy) free(host_copy);

return result;

}

If the server is configured to require a peer certificate, the certificate and key to use can be specified in my.cnf, and you should use mysql_options( ) with the MYSQL_READ_DEFAULT_GROUP option to read the appropriate configuration group for your application. The options for the certificate and key to use are ssl-cert and ssl-key, respectively. In addition, use ssl-ca and ssl-capath to set a file or directory containing trusted certificates that are to be used when verifying the peer's certificate. The final option is ssl-cipher, which can be used to specify a specific cipher or cipher set to be used. All of these keys also apply for server configuration.

Alternately, you can use the undocumented mysql_ssl_set( ) function to set the key, certificate, trusted certificate file, trusted certificate directory, and cipher. Because this function is undocumented, it is possible that it will go away or change at any point without warning.[1]

The prototype for this function is in mysql.h and is as follows:

int STDCALL mysql_ssl_set(MYSQL *mysql, const char *key, const char *cert,

const char *ca, const char *capath, const char *cipher);

Finally, note that examination of the MySQL-4.0.10-gamma source code (the latest available at the time of this writing) reveals that if you set a certificate using either configuration file options or the undocumented mysql_ssl_set( ) API, the client will attempt to connect to the server using SSL regardless of whether you specify CLIENT_SSL in the flag passed to mysql_real_connect( ).

PostgreSQL

By default, SSL support is disabled when you are building PostgreSQL. To build PostgreSQL with OpenSSL support enabled, you must specify the --with-openssl option on the command line to the configuration script. Even with a PostgreSQL server build that has OpenSSL support compiled in, the default is still to have SSL support disabled. To enable it, you'll need to set the ssl parameter to on in your postgresql.conf configuration file. When SSL support is enabled, make sure that the files server.key and server.crt contain the server's private key and certificate, respectively. PostgreSQL will look for the two files in the data directory, and they must be present for the server to start.

In a default configuration, PostgreSQL does not require clients to connect to the server with SSL; the use of SSL is strictly a client option. However, clients can be required to use SSL using the hostssl record format in the pg_hba.conf file.

The PostgreSQL C API function PQconnectdb( ) requires that a conninfo object be filled in and passed to it to establish a connection to the server. One of the fields in the conninfo structure is an integer field called requiressl, which allows the client to decide whether SSL should or should not be required for the connection. If this field is set to 1, the connection will fail if the server does not support SSL; otherwise, the use of SSL will be negotiated as part of the connection handshake. In the latter case, SSL will only be used if a hostssl record exists in pg_hba.conf requiring the use of SSL by clients.

See Also

Recipe 9.5


[1] Versions of MySQL prior to 4.00 seem to have included at least partial support for SSL connections, but no configuration options exist to enable it. The function mysql_ssl_set( ) exists in the 3.23 series, and possibly earlier versions as well, but its signature is different from what exists in 4.00.

9.11. Using a Virtual Private Network to Secure Network Connections

Problem

Your program operates over a network and interacts with an existing network infrastructure that provides no support for secure communications such as SSL. You're guaranteed that your program will be used only by a select group of people, and you need to secure its network traffic against sniffing and hijacking.

Solution

For this type of problem, using an SSL tunnel such as Stunnel is sufficient, but the certificate requirements and limited verification options provided by Stunnel may not provide everything you need. In addition, some network protocols do not lend themselves to SSL tunneling. (FTP is such a protocol because it may use random ports in both directions.) An alternate solution is to use a virtual private network (VPN) for the network services that your program needs.

Discussion

VPNs can be tricky to set up and get to work properly. There can be many interoperability problems across platforms, but VPNs provide a clean solution insofar as requiring fewer modifications to firewall rules (especially if there are many insecure network services involved), less deployment of tunneling software, and less ongoing maintenance. Adding or removing services becomes an issue of turning the service on or off—no changes to firewalls or tunneling configurations are required. Once the VPN is up and running, it essentially takes care of itself.

Although we do suggest the possibility of using a VPN when the other solutions we've provided here aren't feasible for your situation, a complete discussion of VPN solutions is well beyond the scope of this book. Entire volumes have been dedicated to the topic, and we recommend that you consult one or more of those books if you want to pursue the use of VPNs. A good launch point for VPN information is Building & Managing Virtual Private Networks by Dave Kosiur (John Wiley & Sons).

9.12. Building an Authenticated Secure Channel Without SSL

Problem

You want to encrypt communications between two peers without using SSL and the overhead that it incurs. Because it is normally a bad idea to encrypt without integrity checking (to avoid attacks such as man-in-the-middle, capture replay, and bit-flipping in stream ciphers), you also want to employ some kind of integrity checking so you'll be able to determine whether the data has been tampered with in transit.

We also assume here that you'd like to stay away from a full-fledged PKI, instead using a more traditional model of user accounts managed on a per-machine basis.

Solution

Use an authenticating key exchange mechanism from Chapter 8, and use the resulting session key with a solution for authenticated encryption, while performing proper key and nonce management.

In this recipe, we provide an infrastructure for the simple secure channel, for use once authentication and key exchange is performed.

Discussion

Given the tools we've discussed in previous recipes for authentication, key exchange, and the creation of secure channels, producing an end-to-end solution isn't drastically difficult. Nonetheless, there are some potential "gotchas" that we still need to address.

In protocols such as SSL/TLS, connection establishment is a bit more complex than simply authenticating and exchanging a key. In particular, such protocols tend to negotiate which version of the protocol is to be used, and perhaps negotiate which cryptographic algorithms and key sizes are to be used.

In situations like this, there is the threat of a rollback attack , which occurs when an attacker tampers with messages during establishment and tricks the parties into negotiating an insecure set of parameters (such as an old, broken version of a protocol).

A good protocol for authentication and key exchange, such as PAX or SAX (see Recipe 8.15), ensures that there are no opportunities for rollback in the context of the protocol. If you don't have messages that come before the key exchange, and if you immediately start using your encryption key after the exchange using an authenticated encryption solution, you can do other kinds of negotiation (such as agreeing on a protocol) and not have to worry about rollback.

If, on the other hand, you send messages before your key exchange, or you create your own end-to-end protocol (neither is a solution we recommend), you will need to protect against replay attacks on your own. To accomplish this, after connection establishment, have each side MAC every message that it thinks took place during the establishment. If the client sends its MAC first, and the server validates it, the server should MAC not only the establishment messages but also the MAC value sent by the client. Similarly, if the server sends the MAC first, the client should include the server's MAC in its response.

Our overall recommendation is not to introduce SSL-style configurability for your cryptography. If, for example, you use PAX, the only real option possible in the whole key exchange and authentication process is the size of the key that gets exchanged. We recommend that you use that key in a strong predetermined authenticated encryption scheme without negotiation. If you feel that you absolutely must allow for algorithm negotiation, we recommend you have a highly conservative default that you immediately start using after the key exchange, such as AES in CWC mode with 256-bit keys, and allow for renegotiation.

As we discuss in Recipe 6.21, you should use a message counter along with a MAC to thwart capture replay attacks. Message counters can also help determine when messages arrive out of order or are dropped, if you always check that the message number increases by exactly one (standard capture replay detection only checks to make sure the message number always increases).

Note that if you're using a "reliable" transport such as TCP, you will get modest prevention against message reordering and dropped messages. TCP's protection against these problems is not cryptographically secure, however. A savvy attacker can still launch such attacks in a manner that the TCP layer will not detect.

In some environments, message ordering and dropping aren't a big deal. These are the environments in which you would traditionally use an "unreliable" protocol such as UDP. Generally, cryptographically strong protocols may be able to tolerate drops, but they shouldn't tolerate reordering, because doing so means foregoing standard capture replay prevention. You can always drop out-of-order messages or explicitly keep track of recent message numbers that have been seen, then drop any duplicates or any messages with a number that comes before that window.

Particularly if you're using TCP, if a message fails to authenticate cryptographically, recovering is tremendously difficult. Accidental errors will almost always be caught at the TCP level, and you can assume that if the cryptography catches it, an attacker is tampering. In such a case, a smart attacker can cause a denial of service, no matter what. It's generally easiest to terminate the connection, perhaps sending back an error packet first.

Often, unrecoverable errors result in plaintext error messages. In such cases, you should be conservative and send no reason code signaling why you failed. There are instances in major protocols where verbose errors led to an important information leak.

When you're designing your protocol for client-server communications, you should include a sequence of messages between both parties to communicate to the other side that the connection is being terminated normally. That way, when a connection is prematurely terminated, both sides of the connection have some way of knowing whether the connection was terminated legitimately or was the result of a possible attack. In the latter case, you may wish to take appropriate action. For example, if the connection is prematurely terminated in the process of performing some database operation, you may want to roll back any changes that were made.

The next consideration is what the message format should look like. Generally, a message format will start out with a plaintext, fixed-size field encoding the length of the remainder of the message. Then, there may or may not be plaintext values, such as the message number (the message number can go inside the ciphertext, but often it's useful for computing the nonce, as opposed to assuming it). Finally comes the ciphertext and the MAC value (which may be one unit, depending on whether you use an authenticating encryption mode such as CWC).

Any unencrypted data in the message should be authenticated in a secure manner along with the encrypted data. Modes like CWC and CCM allow you to authenticate both plaintext and ciphertext with a single MAC value. CMAC has the same capability. With other MACs, you can simulate this behavior by MAC'ing the length of the plaintext portion, concatenated with the plaintext portion, concatenated with the ciphertext. To do this correctly, however, you must always include the plaintext length, even if it is zero.

Assume that we've established a TCP connection and exchanged a 128-bit key using a protocol such as PAX (as discussed in Recipe 8.15). Now, what should we do with that key? The answer depends on a few things. First, we might need separate keys for encryption and MAC'ing if we're not using a dual-use mode such as CWC. Second, we might have the client and server send messages in lockstep, or we might have them send messages asynchronously. If they send messages asynchronously, we can use a separate key for each direction or, if using a nonced encryption mode, manage two nonces, while ensuring that the client and server nonces never collide (we'll use this trick in the code below).

If you do need multiple keys for your setup, you can take the exchanged key and use it to derive those keys, as discussed in Recipe 4.11. If you do that, use the exchanged key only for derivation. Do not use it for anything else.

At this point, on each end of the connection, we should be left with an open file descriptor and whatever keys we need. Let's assume at this point that we're using CWC mode (using the API discussed in Recipe 5.10), our communication is synchronous, the file descriptor is in blocking mode, and the client sends the first message. We are using a random session key, so we don't have to make a derived key, as happens in Recipe 5.16.

The first thing we have to do is figure out how we're going to lay out the 11-byte nonce CWC mode gives us. We'll use the first byte to distinguish who is doing the sending, just in case we want to switch to asynchronous communication at a future point. The client will send with the high byte set to 0x80, and the server will send with that byte set to 0x00. We will then have a session-specific 40-bit (5-byte) random value chosen by the client, followed by a 5-byte message counter.

The message elements will be a status byte followed by the fixed-size nonce, followed by the length of the ciphertext encoded as a 32-bit big-endian value, followed finally by the CWC ciphertext (which includes the authentication value). The byte, the nonce, and the length field will be sent in the clear.

The status byte will always be 0x00, unless we're closing the connection, in which case we'll send 0xff. (If there is an error on the sender's end, we simply drop the connection instead of sending back an error status.) If we receive any nonzero value, we will terminate the connection. If the value is not 0x00 or 0xff, there was probably some sort of tampering.

When MAC'ing, we do not need to consider the nonce, because it is an integral element when the CWC message is validated. Similarly, the length field is implicitly authenticated during CWC decryption. The status byte should be authenticated, and we can pass it as associated data to CWC.

Now we have all the tools we need to complete our authenticated secure channel. First, let's create an abstraction for the connection, which will consist of a CWC encryption context, state information about the nonce, and the file descriptor over which we are communicating:

#include <stdlib.h>

#include <errno.h>

#include <cwc.h>

#define SPC_CLIENT_DISTINGUISHER 0x80

#define SPC_SERVER_DISTINGUISHER 0x00

#define SPC_SERVER_LACKS_NONCE 0xff

#define SPC_IV_IX 1

#define SPC_CTR_IX 6

#define SPC_IV_LEN 5

#define SPC_CTR_LEN 5

#define SPC_CWC_NONCE_LEN (SPC_IV_LEN + SPC_CTR_LEN + 1)

typedef struct {

cwc_t cwc;

unsigned char nonce[SPC_CWC_NONCE_LEN];

int fd;

} spc_ssock_t;

After the key exchange completes, the client will have a key and a file descriptor connected to the server. We can use this information to initialize an spc_ssock_t :

/* keylen is in bytes. Note that, on errors, we abort(), whereas you will

* probably want to perform exception handling, as discussed in Recipe 13.1.

* In any event, we never report an error to the other side; simply drop the

* connection (by aborting). We'll send a message when shutting down properly.

*/

void spc_init_client(spc_ssock_t *ctx, unsigned char *key, size_t klen, int fd) {

if (klen != 16 && klen != 24 && klen != 32) abort();

/* Remember that cwc_init() erases the key we pass in! */

cwc_init(&(ctx->cwc), key, klen * 8);

/* select 5 random bytes to place starting at nonce[1]. We use the API from

* Recipe 11.2.

*/

spc_rand(ctx->nonce + SPC_IV_IX, SPC_IV_LEN);

/* Set the 5 counterbytes to 0, indicating that we've sent no messages. */

memset(ctx->nonce + SPC_CTR_IX, 0, SPC_CTR_LEN);

ctx->fd = fd;

/* This value always holds the value of the last person to send a message.

* If the client goes to send a message, and this is sent to

* SPC_CLIENT_DISTINGUISHER, then we know there has been an error.

*/

ctx->nonce[0] = SPC_SERVER_DISTINGUISHER;

}

The client may now send a message to the server using the following function, which accepts plaintext and encrypts it before sending:

#define SPC_CWC_TAG_LEN 16

#define SPC_MLEN_FIELD_LEN 4

#define SPC_MAX_MLEN 0xffffffff

static unsigned char spc_msg_ok = 0x00;

static unsigned char spc_msg_end = 0xff;

static void spc_increment_counter(unsigned char *, size_t);

static void spc_ssock_write(int, unsigned char *, size_t);

static void spc_base_send(spc_ssock_t *ctx, unsigned char *msg, size_t mlen);

void spc_ssock_client_send(spc_ssock_t *ctx, unsigned char *msg, size_t mlen) {

/* If it's not our turn to speak, abort. */

if (ctx->nonce[0] != SPC_SERVER_DISTINGUISHER) abort();

/* Set the distinguisher, then bump the counter before we actually send. */

ctx->nonce[0] = SPC_CLIENT_DISTINGUISHER;

spc_increment_counter(ctx->nonce + SPC_CTR_IX, SPC_CTR_LEN);

spc_base_send(ctx, msg, mlen);

}

static void spc_base_send(spc_ssock_t *ctx, unsigned char *msg, size_t mlen) {

unsigned char encoded_len[SPC_MLEN_FIELD_LEN];

size_t i;

unsigned char *ct;

/* If it's not our turn to speak, abort. */

if (ctx->nonce[0] != SPC_SERVER_DISTINGUISHER) abort();

/* First, write the status byte, then the nonce. */

spc_ssock_write(ctx->fd, &spc_msg_ok, sizeof(spc_msg_ok));

spc_ssock_write(ctx->fd, ctx->nonce, sizeof(ctx->nonce));

/* Next, write the length of the ciphertext,

* which will be the size of the plaintext plus SPC_CWC_TAG_LEN bytes for

* the tag. We abort if the string is more than 2^32-1 bytes.

* We do this in a way that is mostly oblivious to word size.

*/

if (mlen > (unsigned long)SPC_MAX_MLEN || mlen < 0) abort( );

for (i = 0; i < SPC_MLEN_FIELD_LEN; i++)

encoded_len[SPC_MLEN_FIELD_LEN - i - 1] = (mlen >> (8 * i)) & 0xff;

spc_ssock_write(ctx->fd, encoded_len, sizeof(encoded_len));

/* Now, we perform the CWC encryption, and send the result. Note that,

* if the send fails, and you do not abort as we do, you should remember to

* deallocate the message buffer.

*/

mlen += SPC_CWC_TAG_LEN;

if (mlen < SPC_CWC_TAG_LEN) abort(); /* Message too long, mlen overflowed. */

if (!(ct = (unsigned char *)malloc(mlen))) abort(); /* Out of memory. */

cwc_encrypt_message(&(ctx->cwc), &spc_msg_ok, sizeof(spc_msg_ok), msg,

mlen - SPC_CWC_TAG_LEN, ctx->nonce, ct);

spc_ssock_write(ctx->fd, ct, mlen);

free(ct);

}

static void spc_increment_counter(unsigned char *ctr, size_t len) {

while (len--) if (++ctr[len]) return;

abort(); /* Counter rolled over, which is an error condition! */

}

static void spc_ssock_write( int fd, unsigned char *msg, size_t mlen) {

ssize_t w;

while (mlen) {

if ((w = write(fd, msg, mlen)) == -1) {

switch (errno) {

case EINTR:

break;

default:

abort();

}

} else {

mlen -= w;

msg += w;

}

}

}

Let's look at the rest of the client side of the connection, before we turn our attention to the server side. When the client wishes to terminate the connection politely, it will send an empty message but pass 0xff as the status byte. It must still send the proper nonce and encrypt a zero-length message (which CWC will quite happily do). That can be done with code very similar to the code shown previously, so we won't waste space by duplicating the code.

Now let's look at what happens when the client receives a message. The status byte should be 0x00. The nonce we get from the server should be unchanged from the one we just sent, except that the first byte should be SPC_SERVER_DISTINGUISHER. If the nonce is invalid, we'll just fail by aborting, though you could instead discard the message if you choose to do so (doing so is a bit problematic, though, because you then have to resync the connection somehow).

Next, we'll read the length value, dynamically allocating a buffer that's big enough to hold the ciphertext. This code can never allocate more than 232-1 bytes of memory. In practice, you should probably have a maximum message length and check to make sure the length field doesn't exceed that. Such a test can keep an attacker from launching a denial of service attack in which she has you allocate enough memory to slow down your machine.

Finally, we'll call cwc_decrypt_message( ) and see if the MAC validates. If it does, we'll return the message. Otherwise, we will abort.

static void spc_ssock_read(int, unsigned char *, size_t);

static void spc_get_status_and_nonce(int, unsigned char *, unsigned char *);

static unsigned char *spc_finish_decryption(spc_ssock_t *, unsigned char,

unsigned char *, size_t *);

unsigned char *spc_client_read(spc_ssock_t *ctx, size_t *len, size_t *end) {

unsigned char status;

unsigned char nonce[SPC_CWC_NONCE_LEN];

/* If it's the client's turn to speak, abort. */

if (ctx->nonce[0] != SPC_CLIENT_DISTINGUISHER) abort();

ctx->nonce[0] = SPC_SERVER_DISTINGUISHER;

spc_get_status_and_nonce(ctx->fd, &status, nonce);

*end = status;

return spc_finish_decryption(ctx, status, nonce, len);

}

static void spc_get_status_and_nonce(int fd, unsigned char *status,

unsigned char *nonce) {

/* Read the status byte. If it's 0x00 or 0xff, we're going to look at

* the rest of the message, otherwise we'll just give up right away. */

spc_ssock_read(fd, status, 1);

if (*status != spc_msg_ok && *status != spc_msg_end) abort( );

spc_ssock_read(fd, nonce, SPC_CWC_NONCE_LEN);

}

static unsigned char *spc_finish_decryption(spc_ssock_t *ctx, unsigned char status,

unsigned char *nonce, size_t *len) {

size_t ctlen = 0, i;

unsigned char *ct, encoded_len[SPC_MLEN_FIELD_LEN];

/* Check the nonce. */

for (i = 0; i < SPC_CWC_NONCE_LEN; i++)

if (nonce[i] != ctx->nonce[i]) abort();

/* Read the length field. */

spc_ssock_read(ctx->fd, encoded_len, SPC_MLEN_FIELD_LEN);

for (i = 0; i < SPC_MLEN_FIELD_LEN; i++) {

ctlen <<= 8;

ctlen += encoded_len[i];

}

/* Read the ciphertext. */

if (!(ct = (unsigned char *)malloc(ctlen))) abort();

spc_ssock_read(ctx->fd, ct, ctlen);

/* Decrypt the ciphertext, and abort if decryption fails.

* We decrypt into the buffer in which the ciphertext already lives.

*/

if (!cwc_decrypt_message(&(ctx->cwc), &status, 1, ct, ctlen, nonce, ct)) {

free(ct);

abort();

}

*len = ctlen - SPC_CWC_TAG_LEN;

/* We'll go ahead and avoid the realloc(), leaving SPC_CWC_TAG_LEN extra

* bytes at the end of the buffer than we need to leave.

*/

return ct;

}

static void spc_ssock_read(int fd, unsigned char *msg, size_t mlen) {

ssize_t r;

while (mlen) {

if ((r = read(fd, msg, mlen)) == -1) {

switch (errno) {

case EINTR:

break;

default:

abort();

}

} else {

mlen -= r;

msg += r;

}

}

}

WARNING

The client is responsible for deallocating the memory for messages. We recommend securely wiping messages before doing so, as discussed in Recipe 13.2. In addition, you should securely erase the spc_ssock_t context when you are done with it.

That's everything on the client side. Now we can move on to the server. The server can share the spc_ssock_t type that the client uses, as well as all the helper functions, such as spc_ssock_read( ) and spc_ssock_write( ). But the API for initialization, reading, and writing must change.

Here's the server-side initialization function that should get called once the key exchange is complete but before the client's first message is read:

void spc_init_server(spc_ssock_t *ctx, unsigned char *key, size_t klen, int fd) {

if (klen != 16 && klen != 24 && klen != 32) abort();

/* Remember that cwc_init() erases the key we pass in! */

cwc_init(&(ctx->cwc), key, klen * 8);

/* We need to wait for the random portion of the nonce from the client.

* The counter portion we can initialize to zero. We'll set the distinguisher

* to SPC_SERVER_LACKS_NONCE, so that we know to copy in the random portion

* of the nonce when we receive a message.

*/

ctx->nonce[0] = SPC_SERVER_LACKS_NONCE;

memset(ctx->nonce + SPC_CTR_IX, 0, SPC_CTR_LEN);

ctx->fd = fd;

}

The first thing the server does is read data from the client's socket. In practice, the following code isn't designed for a single-threaded server that uses select( ) to determine which client has data to be read. This is because once we start reading data, we keep reading until we've taken in the entire message, and all the reads are blocking. The code is not designed to work in a nonblocking environment.

Instead, you should use this code from a thread, or use the traditional Unix model, where you fork( ) off a new process for each client connection. Or you can simply rearrange the code so that you incrementally read data without blocking.

unsigned char *spc_server_read(spc_ssock_t *ctx, size_t *len, size_t *end) {

unsigned char nonce[SPC_CWC_NONCE_LEN], status;

/* If it's the server's turn to speak, abort. We know it's the server's turn

* to speak if the first byte of the nonce is the CLIENT distinguisher.

*/

if (ctx->nonce[0] != SPC_SERVER_DISTINGUISHER &&

ctx->nonce[0] != SPC_SERVER_LACKS_NONCE) abort();

spc_get_status_and_nonce(ctx->fd, &status, nonce);

*end = status;

/* If we need to do so, copy over the random bytes of the nonce. */

if (ctx->nonce[0] == SPC_SERVER_LACKS_NONCE)

memcpy(ctx->nonce + SPC_IV_IX, nonce + SPC_IV_IX, SPC_IV_LEN);

/* Now, set the distinguisher field to client, and increment our copy of

* the nonce.

*/

ctx->nonce[0] = SPC_CLIENT_DISTINGUISHER;

spc_increment_counter(ctx->nonce + SPC_CTR_IX, SPC_CTR_LEN);

return spc_finish_decryption(ctx, status, nonce, len);

}

Now we just need to handle the server-side sending of messages, which requires only a little bit of work:

void spc_ssock_server_send(spc_ssock_t *ctx, unsigned char *msg, size_t mlen) {

/* If it's not our turn to speak, abort. We know it's our turn if the client

* spoke last.

*/

if (ctx->nonce[0] != SPC_CLIENT_DISTINGUISHER) abort();

/* Set the distinguisher, but don't bump the counter, because we already did

* when we received the message from the client.

*/

ctx->nonce[0] = SPC_SERVER_DISTINGUISHER;

spc_base_send(ctx, msg, mlen);

}

There is one more potential issue that we should note. In some situations in which you're going to be dealing with incredibly long messages, it does not make sense to have to know how much data is going to be in a message before you start to send it. Doing so will require buffering up large amounts of data, which might not always be possible, particularly in an embedded device.

In such cases, you need to be able to read the message incrementally, yet have some indication of where the message stops, so you know where to stop decrypting. Such scenarios require a special message format.

In this situation, we recommend sending data in fixed-size "frames." At the end of each frame is a field that indicates the length of the data that was in that frame, and a field that indicates whether the frame represents the end of a message. In nonfull frames, the bytes from the end of the data to the informational fields should be set to 0.

See Also

Recipe 4.11, Recipe 5.10, Recipe 5.16, Recipe 6.21, Recipe 8.15, Recipe 13.2