242
The functions shown in Example 10-1
, like many members of the OpenSSL API, are implemented via macros. Thus, it should be obvious
that function pointer manipulation is not possible. In general, applications should use the API directly since the underlying implementation is subject
to change in future versions.
The operations on the stack should be self-explanatory; well cover them briefly. The
sk_TYPE_new_null
function simply creates an empty stack, while the
sk_TYPE_free
function frees a stack; the latter frees only the stack, not the objects contained in the stack. To free a stack and all its members, the
sk_TYPE_pop_free
function should be used; we must pass in a function pointer to the free method for this to work correctly. The last of the general manipulation
functions are
sk_TYPE_zero
to empty a stack and
sk_TYPE_dup
to copy a stack object. There are also the general functions
sk_TYPE_push
and
sk_TYPE_pop
that we would expect in a stack implementation. Two that are less likely are
sk_TYPE_unshift
and
sk_TYPE_shift
; the former pushes an element to the bottom of the stack, and the latter pops the bottom item off the
stack. The functions that add elements
sk_TYPE_push
and
sk_TYPE_unshift
return the total number of elements in the stack, and the other two functions return the item that was
removed from the stack. The last group of macros is a set of nontypical functions for a stack implementation; they add
functionality that is more commonly found in lists. The first,
sk_TYPE_num
, returns the total number of items in the stack. To retrieve an item by index number if the bottom item is zero
without changing the stack, we should use
sk_TYPE_value
. In order to set an item in the stack explicitly, the function
sk_TYPE_set
will replace the
i
th item with the item passed to the call. The delete functions delete a single item from the stack. The item to delete can be selected by
index or pointer value; the stack is shifted to fill the space left by the removed item. Finally,
sk_TYPE_insert
adds the indicated item to the
i
th position in the stack and moves all the items above it, including the
i
th item before the call, up one position. As well see when we move into further topics of this chapter, proper stack handling is critical for
setting up some data structures. While well probably put only a subset of this simple interface to use, we now have the tools to do more complex tasks.
10.2 Configuration Files
We learned how to create a CA by first creating a configuration file to hold our parameters in Chapter 3
. The command-line tool used this file to perform as we had configured, such as obeying choices for algorithms, providing default values for fields in the subject name, etc. The public API
has a suite of functions for processing and accessing values of configuration files. The files themselves simply organize and map keys to values. In general, the keys are strings, and the
values can be either integers or strings, although all values are stored internally as strings.
The goal of the configuration file interface is to make the format of the file opaque to the code that processes it. This is done through
NCONF
objects. When such objects are created, a
CONF_METHOD
structure is specified that aggregates the routines to perform the low-level file parsing operations. OpenSSL most commonly uses the function
NCONF_default
to get the
CONF_METHOD
object. This method reads files of the format we described in Chapter 2
. Because of the flexibility afforded by specifying the underlying
CONF_METHOD
, the
NCONF
interface may be extended in future versions of OpenSSL to include support for reading configuration files of
new formats, such as XML.
243
There are only a few functions to this simple interface, and well explore them by looking at an example.
Example 10-2 presents a small sample configuration file.
Example 10-2. A sample configuration file testconf.cnf
The config file GlobalVar = foo
GlobalNum = 12 [Params]
SectionName = mySection [mySection]
myVar = bar myNum = 7
Example 10-3 provides a test program to read the sample configuration file.
Example 10-3. Code to interact with the configuration file
include stdio.h include stdlib.h
include opensslconf.h void handle_errorconst char file, int lineno, const char msg
{ fprintfstderr, s:i s\n, file, lineno, msg;
ERR_print_errors_fpstderr; exit-1;
} define int_errormsg handle_error__FILE__, __LINE_ _, msg
define GLOB_VAR GlobalVar define GLOB_NUM GlobalNum
define PARAMS Params define SEC_NAME SectionName
define CONFFILE testconf.cnf int mainint argc, char argv[]
{ int i;
long i_val, err = 0; char key, s_val;
STACK_OFCONF_VALUE sec; CONF_VALUE item;
CONF conf; conf = NCONF_newNCONF_default;
if NCONF_loadconf, CONFFILE, err {
if err == 0 int_errorError opening configuration file;
else {
fprintfstderr, Error in s on line li\n, CONFFILE, err;
int_errorErrors parsing configuration file; }
}
244
if s_val = NCONF_get_stringconf, NULL, GLOB_VAR {
fprintfstderr, Error finding \s\ in [s]\n, GLOB_VAR, NULL;
int_errorError finding string; }
printfSec: s, Key: s, Val: s\n, NULL, GLOB_VAR, s_val; if OPENSSL_VERSION_NUMBER 0x00907000L
if err = NCONF_get_number_econf, NULL, GLOB_NUM, i_val {
fprintfstderr, Error finding \s\ in [s]\n, GLOB_NUM, NULL;
int_errorError finding number; }
else if s_val = NCONF_get_stringconf, NULL, GLOB_NUM
{ fprintfstderr, Error finding \s\ in [s]\n, GLOB_VAR,
NULL; int_errorError finding number;
} i_val = atois_val;
endif printfSec: s, Key: s, Val: i\n, NULL, GLOB_VAR, i_val;
if key = NCONF_get_stringconf, PARAMS, SEC_NAME {
fprintfstderr, Error finding \s\ in [s]\n, SEC_NAME, PARAMS;
int_errorError finding string; }
printfSec: s, Key: s, Val: s\n, PARAMS, SEC_NAME, key; if sec = NCONF_get_sectionconf, key
{ fprintfstderr, Error finding [s]\n, key;
int_errorError finding string; }
for i = 0; i sk_CONF_VALUE_numsec; i++ {
item = sk_CONF_VALUE_valuesec, i; printfSec: s, Key: s, Val: s\n,
item-section, item-name, item-value; }
NCONF_freeconf; return 0;
}
In the example program, a new
CONF
object is created using the default method, and the file is loaded by the call to
NCONF_load
. There are also other functions for loading the file from open
FILE
or
BIO
objects:
NCONF_load_fp
and
NCONF_load_bio
. Three different functions are used to probe the configuration file for values. The first one that we use is
NCONF_get_string
, which returns the string of the value corresponding to the section and key values passed to it. If
either the section or the key is undefined, it returns
NULL
. Our sample program uses a few preprocessor defines as shortcuts to the section and key strings:
char NCONF_get_stringconst CONF conf, const char section, const char key;
int NCONF_get_number_econst CONF conf, const char section, const char key, long result;
STACK_OFCONF_VALUE NCONF_get_sectionconst CONF conf,
245
const char section;
One interesting point made in the example is the preprocessor conditionals around usage of the function
NCONF_get_number_e
. Versions of OpenSSL prior to 0.9.7 have a function,
NCONF_get_number
, that takes three arguments, the same as
NCONF_get_string
, except that
NCONF_get_number
returns an integer value instead of a string. This function should be avoided since it does not allow for error checking. The better way to read integer values from a
configuration file is to get the value as a string, check for an error condition by checking for a
NULL
return value, and then convert the string to an integer ourselves. Of course, if the
NCONF_get_number_e
function is available, it can be used safely. This function writes the retrieved value to the last argument and returns nonzero on success.
The function
NCONF_get_number
may be reimplemented in 0.9.7 simply as a macro for
NCONF_get_number_e
, requiring calling applications to be changed to account for the modified interface. Because
of possible changes, it is safest to use
NCONF_get_number_e
. The last
NCONF
function used in the example is
NCONF_get_section
. This function returns a stack of all the configuration parameters in the specified section. Our sample program iterates over
the stack and prints all the pairs that it contains. This code actually manually accesses members of the
CONF_VALUE
structure; the declaration is provided in Example 10-4
.
Example 10-4. The declaration of CONF_VALUE
typedef struct {
char section; char name;
char value; } CONF_VALUE;
In general, the
NCONF
interface can provide a simple, readily available infrastructure for customized, application-specific configuration files. This interface is useful for other reasons, as
well. For instance, we can build a custom CA application that shares configuration files with the command-line tool.
10.3 X.509