Securing Sockets with OpenSSL (Part 2)
- Writing an HTTPS Server
- Writing a Secure Client
- Dealing with the Limitations
- Looking at the Security Landscape
As you write programs that interface with networks, you quickly find that nothing is really private or confidential. Almost effortlessly, you can write network snoopers that grab every message that passes along your network segment. This also means that others, who may be not as trustworthy, can do the same to your messages. The Internet is moving more toward privacy. Of course, because of the public nature of the Internet, however, this is impossible. How close to data security and privacy can the industry get?
While privacy is unreachable in a public setting like the Internet, you can get pretty close with encryption. The Secure Socket Layer (SSL) provides a standard and reliable mechanism to interface two networked computers. This is the second part of an article on OpenSSL (from http://www.openssl.org), a production-ready SSL API. The first part discussed the terminology and issues of securing the channel over TCP/IP. This part completes the discussion with program examples for a secure client and server.
A secure HTTP server (or HTTPS) is a good example for demonstrating how to connect with existing web browser applications such as Netscape, Konqueror, or Internet Explorer. This article lists some of the program statements that you use to interface with OpenSSL's API. You can get the complete code listings for all the code fragments in this article from http://www.linuxsocket.org.
Writing an HTTPS Server
Writing a program to serve up secure messages requires only a few changes to the TCP server. Once you have completed those steps, you're free and clear to send and receive private information.
Before creating a socket, you must initialize the OpenSSL library. Initialization includes loading the ciphers, error messages, creating server instances, and creating an SSL context. The only time that you change the following code is when and if you need to set up separate contexts (for example, when supporting TLS and SSLv3 in the same program). To set up a context, you can use the following code fragment:
SSL_METHOD *method; SSL_CTX *ctx; OpenSSL_add_all_algorithms(); /* load & register cryptos */ SSL_load_error_strings(); /* load all error messages */ method = SSLv2_server_method(); /* create server instance */ ctx = SSL_CTX_new(method); /* create context */
The next step is to load the server's certificates. The certificates are stored in a file along with your private key. The certificates must be ready before the client connects; this is why you have to take these steps prior to setting up a socket. The code fragment below lists the code to load the certificate and private key from CertFile and KeyFile. (Remember, you can store both in the same file.)
CAUTION
This source listing leaves out the error checking for presentation clarity. Be sure you include checks for any errors in production code.
/* set the local certificate from CertFile */ SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM); /* set the private key from KeyFile */ SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM); /* verify private key */ if ( !SSL_CTX_check_private_key(ctx) ) abort();
The first two calls, SSL_CTX_use_certificate_file() and SSL_CTX_use_PrivateKey_file(), get the certificate and the private key, respectively. The last call, SSL_CTX_check_private_key(), verifies the private key against the known certificate. This makes sure that nothing got corrupted (or even cracked).
The next step is to create the server socket. This is identical to a typical TCP listening socket: You create the socket, bind it to a particular port, and convert it to a listening socket.
/*--- Standard TCP server setup and connection ---*/ int sd, client; struct sockaddr_in addr; sd = socket(PF_INET, SOCK_STREAM, 0); /* create stream socket */ memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); /* select 'port' */ addr.sin_addr.s_addr = INADDR_ANY; /* any available addr */ bind(sd, (struct sockaddr*)&addr, sizeof(addr)); /* bind it */ listen(sd, 10); /* make into listening socket */ client = accept(sd, 0, 0); /* await and accept connections */
OpenSSL sits on top of the TCP stack, so all you have to do is hand off the client socket descriptor. When the library gets the newly connected client, it begins the SSL handshake. And, when it's finally done, your program has a fully qualified and secure connection. The following code fragment demonstrates the last couple of steps to securing the connection: getting an SSL state and performing the handshake.
int client, bytes; SSL *ssl = SSL_new(ctx); /* get new SSL state with context */ SSL_set_fd(ssl, client); /* set connection to SSL state */ SSL_accept(ssl); /* start the handshaking */ /* now you can read/write */ bytes = SSL_read(ssl, buf, sizeof(buf)); /* get HTTP request */ /*...process request */ SSL_write(ssl, reply, strlen(reply)); /* send reply */ /*...*/ /* close connection & clean up */ client = SSL_get_fd(ssl); /* get the raw connection */ SSL_free(ssl); /* release SSL state */ close(sd); /* close connection */
Each connection gets its own SSL connection state with the SSL_new() library call. The program sets the raw client connection to this connection state. From that point on, the OpenSSL library uses the connection state for all I/O and control. The library replaces recv() and send() with SSL_read() and SSL_write(). The last step that calls SSL_accept() completes the SSL handshaking.