O'Reilly logo

Network Security with OpenSSL by Pravir Chandra, Matt Messier, John Viega

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Support Infrastructure

The OpenSSL library is composed of many different packages. Some of the lower-level packages can be used independently, while the higher-level ones may make use of several of the lower-level ones. To use the OpenSSL library effectively, it is important to understand the fundamental concepts of cryptography that we've already introduced, and to gain familiarity with the more important supplemental package offerings.

In this chapter, we concentrate on the lower-level APIs that are most useful with the higher-level APIs that we discuss through the rest of this book. We'll start by demonstrating what is necessary when using the OpenSSL library in a multithreaded environment by developing a small "drop-in" library for Windows and Unix platforms that use POSIX threads. We'll also examine OpenSSL's error handling and its input/output interface, which are both quite different from how most other development libraries deal with the same things. OpenSSL also provides packages for arbitrary precision math and secure random number generation, as we already mentioned. These packages are both fundamental to strong crypto, and we'll cover them as well.

For all of the packages that we examine in this chapter, we'll discuss how to use them and provide examples. Additionally, we'll discuss some of the common pitfalls that developers often encounter.

Note that if some of the material in this chapter doesn't seem immediately relevant and interesting, it is safe to skip it, and come back to this chapter when necessary.

Multithread Support

Most modern operating systems provide support for multithreaded applications, and it is becoming increasingly more common for applications to take advantage of that support. OpenSSL can certainly be used in a multithreaded environment; however, it requires that the developer do some work in order to make a program thread-safe. A common mistake that many developers make with OpenSSL is that they assume the library is thread-safe without requiring anything special to be done in the application. This is most certainly an incorrect assumption, and failing to set up OpenSSL for use in a multithreaded environment can result in unpredictable behavior and seemingly random crashes that are very difficult to debug.

OpenSSL uses many data structures on which operations must be atomic. That is, it must be guaranteed that only one thread will access them at a time. If two or more threads are allowed to modify the same structure concurrently, there is no way to predict which one's changes will be realized. What's worse, the operations could end up mixed—part of the first thread's changes could be made, while part of the second thread's changes could also be made. In either case, the results are unpredictable, so steps must be taken to make the structures thread-safe.

OpenSSL provides for the thread safety of its data structures by requiring each thread to acquire a mutually exclusive lock known as a mutex that protects the structure before allowing it to be accessed. When the thread is finished with the data structure, it releases the mutex, allowing another thread to acquire the lock and access the data structure. Because OpenSSL is designed for use on multiple platforms that differ in their implementation of threading, OpenSSL doesn't make direct calls to create, destroy, acquire, and release mutexes: it requires the application programmer to perform these operations in a manner appropriate for the platform it's running on by making callbacks to functions that the application registers with OpenSSL for this purpose.

There are two different sets of callbacks that an application is expected to provide to safely operate in a multithreaded environment. Static locks provide a fixed number of mutexes available for OpenSSL's use. Dynamic locks allow OpenSSL to create mutexes as it needs them. OpenSSL does not currently make use of dynamic locks, but reserves the right to do so in the future. If you want your applications to continue working with a minimal amount of effort in the future, we recommend that you implement both static and dynamic locks now.

Static Locking Callbacks

The static locking mechanism requires the application to provde two callback functions. In addition to providing an implementation for the functions, the application must tell OpenSSL about them so that it knows to call them when appropriate. The first callback function is used either to acquire or release a lock, and is defined like this:

void locking_function(int mode, int n, const char *file, int line);
mode

Determines the action that the locking function should take. When the CRYPTO_LOCK flag is set, the lock should be acquired; otherwise, it should be released.

n

The number of the lock that should be acquired or released. The number is zero-based, meaning that the first lock is identified by 0. The value will never be greater than or equal to the return from the CRYPTO_num_locks function.

file

The name of the source file requesting the locking operation to take place. It is intended to aid in debugging and is usually supplied by the _ _FILE_ _ preprocessor macro.

line

The source line number requesting the locking operation to take place. Like the file argument, it is also intended to aid in debugging, and it is usually supplied by the _ _LINE_ _ preprocessor macro.

The next callback function is used to get a unique identifier for the calling thread. For example, GetCurrentThreadId on Windows will do just that. For reasons that will soon become clear, it is important the value returned from this function be consistent across calls for the same thread, but different for each thread within the same process. The return value from the function should be the unique identifier. The function is defined like this:

unsigned long id_function(void);

Example 4-1 introduces two new OpenSSL library functions: CRYPTO_set_id_callback and CRYPTO_set_locking_callback . These two functions are used to tell OpenSSL about the callbacks that we've implemented for the static locking mechanism. We can either pass a pointer to a function to install a callback or NULL to remove a callback.

Example 4-1.  Static locking callbacks for WIN32 and POSIX threads systems

int THREAD_setup(void);
int THREAD_cleanup(void);
 
#if defined(WIN32)
    #define MUTEX_TYPE HANDLE
    #define MUTEX_SETUP(x)   (x) = CreateMutex(NULL, FALSE, NULL)
    #define MUTEX_CLEANUP(x) CloseHandle(x)
    #define MUTEX_LOCK(x)    WaitForSingleObject((x), INFINITE)
    #define MUTEX_UNLOCK(x)  ReleaseMutex(x)
    #define THREAD_ID        GetCurrentThreadId(  )
#elif _POSIX_THREADS
    /* _POSIX_THREADS is normally defined in unistd.h if pthreads are available
       on your platform. */
    #define MUTEX_TYPE       pthread_mutex_t
    #define MUTEX_SETUP(x)   pthread_mutex_init(&(x), NULL)
    #define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x))
    #define MUTEX_LOCK(x)    pthread_mutex_lock(&(x))
    #define MUTEX_UNLOCK(x)  pthread_mutex_unlock(&(x))
    #define THREAD_ID        pthread_self(  )
#else
    #error You must define mutex operations appropriate for your platform!
#endif
 
/* This array will store all of the mutexes available to OpenSSL. */
static MUTEX_TYPE mutex_buf[] = NULL;
 
static void locking_function(int mode, int n, const char * file, int line)
{
    if (mode & CRYPTO_LOCK)
        MUTEX_LOCK(mutex_buf[n]);
    else
        MUTEX_UNLOCK(mutex_buf[n]);
}
 
static unsigned long id_function(void)
{
    return ((unsigned long)THREAD_ID);
}
 
int THREAD_setup(void)
{
    int i;
 
    mutex_buf = (MUTEX_TYPE *)malloc(CRYPTO_num_locks(  ) * sizeof(MUTEX_TYPE));
    if (!mutex_buf)
        return 0;
    for (i = 0;  i < CRYPTO_num_locks(  );  i++)
        MUTEX_SETUP(mutex_buf[i]);
    CRYPTO_set_id_callback(id_function);
    CRYPTO_set_locking_callback(locking_function);
    return 1;
}
 
int THREAD_cleanup(void)
{
    int i;
 
    if (!mutex_buf)
        return 0;
    CRYPTO_set_id_callback(NULL);
    CRYPTO_set_locking_callback(NULL);
    for (i = 0;  i < CRYPTO_num_locks(  );  i++)
        MUTEX_CLEANUP(mutex_buf[i]);
    free(mutex_buf);
    mutex_buf = NULL;
    return 1;
}

To use these static locking functions, we need to make one function call before our program starts threads or calls OpenSSL functions, and we must call THREAD_setup , which returns 1 normally and 0 if it is unable to allocate the memory required to hold the mutexes. In our example code, we do make a potentially unsafe assumption that the initialization of each individual mutex will succeed. You may wish to add additional error handling code to your programs. Once we've called THREAD_setup and it returns successfully, we can safely make calls into OpenSSL from multiple threads. After our program's threads are finished, or if we are done using OpenSSL, we should call THREAD_cleanup to reclaim any memory used for the mutex structures.

Dynamic Locking Callbacks

The dynamic locking mechanism requires a data structure (CRYPTO_dynlock_value ) and three callback functions. The structure is meant to hold the data necessary for the mutex, and the three functions correspond to the operations for creation, locking/unlocking, and destruction. As with the static locking mechanism, we must also tell OpenSSL about the callback functions so that it knows to call them when appropriate.

The first thing that we must do is define the CRYPTO_dynlock_value structure. We'll be building on the static locking support that we built in Example 4-1, so we can use the same platform-dependent macros that we defined already. For our purposes, this structure will be quite simple, containing only one member:

struct CRYPTO_dynlock_value
{
    MUTEX_TYPE mutex;
};

The first callback function that we need to define is used to create a new mutex that OpenSSL will be able to use to protect a data structure. Memory must be allocated for the structure, and the structure should have any necessary initialization performed on it. The newly created and initialized mutex should be returned in a released state from the function. The callback is defined like this:

struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line);
file

The name of the source file requesting that the mutex be created. It is intended to aid in debugging and is usually supplied by the _ _FILE_ _ preprocessor macro.

line

The source line number requesting that the mutex be created. Like the file argument, it is also intended to aid in debugging, and it is usually supplied by the _ _LINE_ _ preprocessor macro.

The next callback function is used for acquiring or releasing a mutex. It behaves almost identically to the corresponding static locking mechanism's callback, which performs the same operation. It is defined like this:

void dyn_lock_function(int mode, struct CRYPTO_dynlock_value
                      *mutex, const char *file, int line);
mode

Determines the action that the locking function should take. When the CRYPTO_LOCK flag is set, the lock should be acquired; otherwise, it should be released.

mutex

The mutex that should be either acquired or released. It will never be NULL and will always be created and initialized by the mutex creation callback first.

file

The name of the source file requesting that the locking operation take place. It is intended to aid in debugging and is usually supplied by the _ _FILE_ _ preprocessor macro.

line

The source line number requesting that the locking operation take place. Like the file argument, it is also intended to aid in debugging, and it is usually supplied by the _ _LINE_ _ preprocessor macro.

The third and final callback function is used to destroy a mutex that OpenSSL no longer requires. It should perform any platform-dependent destruction of the mutex and free any memory that was allocated for the CRYPTO_dynlock_value structure. It is defined like this:

void dyn_destroy_function(struct CRYPTO_dynlock_value *mutex,
                         const char *file, int line);
mutex

The mutex that should be destroyed. It will never be NULL and will always have first been created and initialized by the mutex creation callback.

file

The name of the source file requesting that the mutex be destroyed. It is intended to aid in debugging and is usually supplied by the _ _FILE_ _ preprocessor macro.

line

The source line number requesting that the mutex be destroyed. Like the file argument, it is also intended to aid in debugging, and it is usually supplied by the _ _LINE_ _ preprocessor macro.

Using the static locking mechanism's code from Example 4-1, we can easily build a dynamic locking mechanism implementation. Example 4-2 shows an implementation of the three dynamic locking callback functions. It also includes new versions of the THREAD_setup and THREAD_cleanup functions extended to support the dynamic locking mechanism in addition to the static locking mechanism. The modifications to these two functions are simply to make the appropriate OpenSSL library calls to install and remove the dynamic locking callback functions.

Example 4-2. E xtensions to the library to support the dynamic locking mechanism

struct CRYPTO_dynlock_value
{
    MUTEX_TYPE mutex;
};
 
static struct CRYPTO_dynlock_value * dyn_create_function(const char *file,
                                                         int line)
{
    struct CRYPTO_dynlock_value *value;
 
    value = (struct CRYPTO_dynlock_value *)malloc(sizeof(
                                                  struct CRYPTO_dynlock_value));
    if (!value)
        return NULL;
    MUTEX_SETUP(value->mutex);
    return value;
}
 
static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l,
                              const char *file, int line)
{
    if (mode & CRYPTO_LOCK)
        MUTEX_LOCK(l->mutex);
    else
        MUTEX_UNLOCK(l->mutex);
}
 
static void dyn_destroy_function(struct CRYPTO_dynlock_value *l,
                                 const char *file, int line)
{
    MUTEX_CLEANUP(l->mutexp);
    free(l);
}
 
int THREAD_setup(void)
{
    int i;
 
    mutex_buf = (MUTEX_TYPE *)malloc(CRYPTO_num_locks(  ) * sizeof(MUTEX_TYPE));
    if (!mutex_buf)
        return 0;
    for (i = 0;  i < CRYPTO_num_locks(  );  i++)
        MUTEX_SETUP(mutex_buf[i]);
    CRYPTO_set_id_callback(id_function);
    CRYPTO_set_locking_callback(locking_function);
 
    /* The following three CRYPTO_... functions are the OpenSSL functions
       for registering the callbacks we implemented above */
    CRYPTO_set_dynlock_create_callback(dyn_create_function);
    CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
    CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
 
    return 1;
}
 
int THREAD_cleanup(void)
{
    int i;
 
    if (!mutex_buf)
        return 0;
    CRYPTO_set_id_callback(NULL);
    CRYPTO_set_locking_callback(NULL);
    CRYPTO_set_dynlock_create_callback(NULL);
    CRYPTO_set_dynlock_lock_callback(NULL);
    CRYPTO_set_dynlock_destroy_callback(NULL);
    for (i = 0;  i < CRYPTO_num_locks(  );  i++)
        MUTEX_CLEANUP(mutex_buf[i]);
    free(mutex_buf);
    mutex_buf = NULL;
    return 1;
}

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required