246
10.3.1 Generating Requests
Recall that an X.509 certificate is a public key packaged with information about the certificate owner and issuer. Thus, to make a request, we must generate a public and private key on which
well base our certificate. Well start our discussion assuming generation of the key pair has already been completed see
Chapter 8 for details.
An X.509 certificate request is represented by an
X509_REQ
object in OpenSSL. As we learned in Chapter 3
, a certificate requests main component is the public half of the key pair. It also contains a
subjectName
field and additional X.509 attributes. In reality, the attributes are optional parameters for the request, but the subject name should always be present. As well see, creating a
certificate is not a very difficult task.
10.3.1.1 Subject name
Before looking at an example, we need a little more background information on subject name manipulation with the API. The object type
X509_NAME
represents a certificate name. Specifically, a certificate request has only a subject name, while full certificates contain a subject
name and an issuer name. The purpose of the name field is to fully identify an entity, whether it is a server, person, corporation, etc. To this end, a name field is composed of several entries for
country name, organization name, and common name, just to name a few. Again, we can think of the fields in a name as keyvalue pairs; the key is the name of the field, and the value is its content.
In theory, there can be arbitrary fields in a name, but in practice, a few standard ones are expected. In OpenSSL, fields are internally identified through an integer value known as the NID. All of this
information rapidly becomes relevant when it comes time to build the subject name of our request.
As weve already said, a certificate name is represented by an
X509_NAME
object. This object is essentially a collection of
X509_NAME_ENTRY
objects. Each
X509_NAME_ENTRY
object represents a single field and the corresponding value. Thus, our application needs to generate a
X509_NAME_ENTRY
object for each of the fields well put in the name of the certificate request. The process is simple. First, we look up the NID of the field we need to create. Using the NID, we
create the
X509_NAME_ENTRY
object and add our data. The entry is added to the
X509_NAME
, and we repeat the process until all the desired fields are entered. After the name is fully assembled,
we can add it to an
X509_REQ
object. OpenSSL provides many functions for manipulating
X509_NAME
and
X509_NAME_ENTRY
objects that enable us to perform the subject name assembly using many different methods. For instance, the function call
X509_NAME_add_entry_by_txt
automatically looks up the NID, creates the entry, and adds it to the
X509_NAME
. In the example below, we elected to show the explicit implementation instead of demonstrating the kinds of operations that are available.
10.3.1.2 X.509 Version 3 extensions
In previous chapters, we discussed the basics of X.509 Version 3 certificate extensions. In particular, in
Chapter 5 , we learned that the
subjectAltName
extension is very useful for SSL. This extension contains a field named
dNSName
that will hold the FQDN for the entity possessing the certificate. As far as certificate requests are concerned, we should add in the extension before
sending the request to the CA for certification. Doing this programmatically is straightforward. The
X509_EXTENSION
type object represents a single extension to an
X509
object. The process of adding extensions to a request requires us to put all the requested extensions into a
247
STACK_OFX509_EXTENSION
object. After the stack is created, we can add the stack to the request, and our task is complete.
10.3.1.3 Putting it all together
Now we know how to create a certificate request: we have to create an
X509_REQ
object, add a subject name and public key to it, add all desired extensions, and sign the request with the private
key. To represent the public and private key components, we should use the generic type
EVP_PKEY
and its corresponding functions. The slightly confusing part to this process is signing the request.
With OpenSSL, message digest algorithms are represented by
EVP_MD
objects. We need to specify a different
EVP_MD
object based on whether were signing with an RSA or DSA key, but the public key algorithm isnt known when we do the signing, since we must use the abstract
EVP_PKEY
interface. We can probe the internals of an
EVP_PKEY
object to find out the underlying algorithm, but doing so isnt very clean since it requires directly accessing members of
the structure. Unfortunately, this is the only solution to the problem. The example below shows how the
EVP_PKEY_type
function can be used in conjunction with a member of the type
EVP_PKEY
to perform this task. Were ready to create a request now that we know the theory behind it. The code appears in
Example 10-5 .
Example 10-5. A program to generate a certificate request
include stdio.h include stdlib.h
include opensslx509.h include opensslx509v3.h
include opensslerr.h include opensslpem.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 PKEY_FILE privkey.pem
define REQ_FILE newreq.pem define ENTRY_COUNT 6
struct entry {
char key; char value;
}; struct entry entries[ENTRY_COUNT] =
{ { countryName, US },
{ stateOrProvinceName, VA }, { localityName, Fairfax },
{ organizationName, Zork.org }, { organizationalUnitName, Server Division },
{ commonName, Server 36, Engineering },
248
}; int mainint argc, char argv[]
{ int i;
X509_REQ req; X509_NAME subj;
EVP_PKEY pkey; EVP_MD digest;
FILE fp; OpenSSL_add_all_algorithms;
ERR_load_crypto_strings; seed_prng;
first read in the private key if fp = fopenPKEY_FILE, r
int_errorError reading private key file; if pkey = PEM_read_PrivateKeyfp, NULL, NULL, secret
int_errorError reading private key in file; fclosefp;
create a new request and add the key to it if req = X509_REQ_new
int_errorFailed to create X509_REQ object; X509_REQ_set_pubkeyreq, pkey;
assign the subject name if subj = X509_NAME_new
int_errorFailed to create X509_NAME object; for i = 0; i ENTRY_COUNT; i++
{ int nid;
X509_NAME_ENTRY ent; if nid = OBJ_txt2nidentries[i].key == NID_undef
{ fprintfstderr, Error finding NID for s\n,
entries[i].key; int_errorError on lookup;
} if ent = X509_NAME_ENTRY_create_by_NIDNULL, nid,
MBSTRING_ASC, entries[i].value, -
1 int_errorError creating Name entry from NID;
if X509_NAME_add_entrysubj, ent, -1, 0 = 1 int_errorError adding entry to Name;
} if X509_REQ_set_subject_namereq, subj = 1
int_errorError adding subject to request; add an extension for the FQDN we wish to have
{ X509_EXTENSION ext;
STACK_OFX509_EXTENSION extlist; char name = subjectAltName;
char value = DNS:splat.zork.org; extlist = sk_X509_EXTENSION_new_null;
249
if ext = X509V3_EXT_confNULL, NULL, name, value int_errorError creating subjectAltName extension;
sk_X509_EXTENSION_pushextlist, ext; if X509_REQ_add_extensionsreq, extlist
int_errorError adding subjectAltName to the request; sk_X509_EXTENSION_pop_freeextlist, X509_EXTENSION_free;
} pick the correct digest and sign the request
if EVP_PKEY_typepkey-type == EVP_PKEY_DSA digest = EVP_dss1;
else if EVP_PKEY_typepkey-type == EVP_PKEY_RSA digest = EVP_sha1;
else int_errorError checking public key for a valid digest;
if X509_REQ_signreq, pkey, digest int_errorError signing request;
write the completed request if fp = fopenREQ_FILE, w
int_errorError writing to request file; if PEM_write_X509_REQfp, req = 1
int_errorError while writing request; fclosefp;
EVP_PKEY_freepkey; X509_REQ_freereq;
return 0; }
Using the appropriate PEM call, we read in our private key. Recall that a public key is a subset of the information in a private key, so we need not read in anything more than the private key. Using
the function
X509_REQ_set_pubkey
, we add the public key portion of the private key to the request:
int OBJ_txt2nidconst char field; X509_NAME_ENTRY X509_NAME_ENTRY_create_by_NIDX509_NAME_ENTRY ne,
int nid, int type, unsigned
char value, int len;
int X509_NAME_add_entryX509_NAME name, X509_NAME_ENTRY ne, int loc, int set;
Using a loop, we read from our global array containing the fields and values, and add to our subject name. The function
OBJ_txt2nid
performs a lookup of the built-in field definitions. This function returns the integer NID value. After obtaining the NID, we use the
X509_NAME_ENTRY_create_by_NID
function to create the
X509_NAME_ENTRY
object properly. The third argument to this function must specify the type of character encoding;
common specifications are
MBSTRING_ASC
for ASCII and
MBSTRING_UTF8
for UTF8 encoding. The last argument is the length of the value of the field we are setting. By passing in a -1 for this
argument, the data is interpreted as a C-style,
NULL
-terminated string. The length of the data is determined by searching the data for a
NULL
terminator. The last call used in the loop is
X509_NAME_add_entry
. This call adds the entry to the subject name. The third argument specifies the position at which we want to place the data. In essence, the
X509_NAME
is a stack of
X509_NAME_ENTRY
objects. Thus, there is an ordering to the fields in a name. Specifying -1 for this argument adds the new field after any other fields already in the
X509_NAME
. Alternatively,
TE AM
FL Y
Team-Fly
®
250
we could have passed in the return from
X509_NAME_entry_count
, but using -1 is better because it ensures that the field is added to the end of the list. The last argument to
X509_NAME_add_entry
specifies the operation to be performed on the item already in the location indicated by the third argument. For instance, if the
X509_NAME
object contained three fields, and we made a call to this function specifying 1 for the third argument and 0 for the last
argument, the field in the middle would be replaced by the new data. Using -1 for the last argument will cause the new data to be appended to the previous data, while using 1 would cause
it to be prepended.
After the subject name is fully built, we add it to the certificate request. Then we build and add our extension for the
subjectAltName
. This can be easily done through the
X509V3_EXT_conf
function:
X509_EXTENSION X509V3_EXT_confLHASH conf, X509V3_CTX ctx, char name, char value;
int X509_REQ_add_extensionsX509_REQ req, STACK_OFX509_EXTENSION exts;
The first two parameters to
X509_EXT_conf
arent important for creating the simple extension we need. This function returns
NULL
on error and the built object otherwise. We revisit this function in more detail when we discuss creating certificates below. After the
X509_EXTENSION
object is created, it is added to the stack. The stack is then added to the request through the function
X509_REQ_add_extensions
. This function will return 1 on success. Next, we perform the check for the key type to determine the correct
EVP_MD
object to pass to
X509_REQ_sign
. This check involves using
EVP_PKEY_type
to translate the member of the
EVP_PKEY
object to something we can test. If the key is RSA, we use
EVP_sha1
; if it is DSA, we use
EVP_dss1
. In either case, SHA1 is the algorithm used in the signing process. Using what weve learned about parsing configuration files, this example could easily be extended
to read the field names and values from an OpenSSL configuration file instead of the hardcoded information that we provided in the example. Doing this would allow the program to interact with
the same configuration files as the command-line tools.
10.3.2 Making Certificates