X.509 Version 3 extensions Putting it all together

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