X.509 Certificate Checking X.509

255 We use a previously unseen function, X509V3_set_ctx , to prepare a context for creating the extensions to the certificate: void X509V3_set_ctxX509V3_CTX ctx, X509 issuer, X509 subject, X509_REQ req, X509_CRL crl, int flags; This versatile function is called with several NULL s since we dont need to add extensions to any X509_REQ or X509_CRL objects. Additionally, we dont need any flags, so a 0 is used for the last argument. This X509V3_CTX object is used by the X509V3_EXT_conf to allow for some of the more complex extensions we wish to add. For example, the subjectKeyIdentifier extension is computed as a hash of part of the data in the certificate, and the X509V3_CTX object provides the routine access to this certificate we added it to the context earlier. We then loop through the array of extension data to create and add extensions to the new certificate. Unlike the process of adding extensions to a certificate request, we dont need to add the extensions to a stack before adding them to an X509 object. As we mentioned earlier, the extensions we add are the defaults for the OpenSSL command-line utility. After the loop is completed, we add the subjectAltName extension that we extracted from the request previously. The last task we perform before writing our new certificate is to sign it. This is critical, since an unsigned certificate is essentially useless. The function X509_sign performs this for us. As in Example 10-5 , we must perform a check to determine the type of the private key so that we can decide on a correct EVP_MD object to use as the hash algorithm. We now have a complete, valid certificate. This is a big step. Using what weve learned, we are armed with enough knowledge to implement a minimal CA. Before leaving the topic of programming with X.509, however, one important topic remains to be discussed: certificate verification.

10.3.3 X.509 Certificate Checking

In Chapter 5 , we discussed SSL server certificate verification extensively. Here, well discuss the layer just below SSL that performs certificate verification. Specifically, well discuss how OpenSSLs SSL functionality verifies a certificate against CRLs and other certificates in the certificate hierarchy. To do this, well require functions from the X.509 package. The SSL protocol implementation handles much of what were about to discuss here for us; even so, some setup work is required on our part, particularly if we wish to include CRLs in the verification process, which we almost certainly do. Knowing how to programmatically perform the verification of a certificate chain gives us valuable insight that we might not ordinarily have into what is actually involved in properly verifying a certificate. It also provides us with the information necessary to verify certificates on our own when were not using the SSL protocol. Before delving into the verification process, however, its helpful to understand the purpose of some of the objects that are involved. In general, a certificate can be validated only against a collection of other certificate material, i.e., CA certificates and CRLs. OpenSSL uses the object type X509_STORE to represent a collection of certificates and certificate revocation lists to serve this purpose. Additionally, OpenSSL uses the type X509_STORE_CTX to hold the data used during an actual verification. This distinction is important; our implementations will look somewhat incongruous with respect to the context- object relationship weve seen with other OpenSSL packages. For certificate verification, we will create an X509_STORE first and populate it with all the available certificate and revocation list information. When its time to verify a peer certificate, we will use the store to create an X509_STORE_CTX to perform the actual verification. 256 Along with the certificate stores and the associated contexts, the X509_LOOKUP_METHOD object is also important. Objects of this type represent a general method of finding certificates or CRLs. For instance, the X509_LOOKUP_file function returns a method to find certificate-related objects within a single file, and the X509_LOOKUP_hash_dir function returns a method to find objects within a properly set up OpenSSL CA directory. X509_LOOKUP_METHOD objects are important for creating X509_LOOKUP objects. These objects aggregate the collection of certificates accessible through the underlying method. For instance, if we have a certificate directory, we can create an X509_LOOKUP from an X509_STORE and the return value of X509_LOOKUP_hash_dir ; this X509_LOOKUP object can then be assigned to a directory, and our X509_STORE will have access to all of the certificates and CRLs that the lookup aggregates. To review: an X509_STORE holds X509_LOOKUP objects built on X509_LOOKUP_METHOD s. This is how the store gains access to certificate and CRL data. The store can then be used to create an X509_STORE_CTX to perform a verification operation. Knowing the relationships between some of these objects, we can begin to see the general form of what our code will have to do to verify a peer certificate. However, a few other important subtleties about correctly verifying a certificate have not yet been discussed. These can be made clear by analyzing Example 10-7 , which demonstrates the whole process of validating a peer certificate. Example 10-7. Verifying a client certificate include stdio.h include stdlib.h include opensslx509_vfy.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 these are defintions to make the example simpler define CA_FILE CAfile.pem define CA_DIR etcssl define CRL_FILE CRLfile.pem define CLIENT_CERT cert.pem int verify_callbackint ok, X509_STORE_CTX stor { ifok fprintfstderr, Error: s\n, X509_verify_cert_error_stringstor-error; return ok; } int mainint argc, char argv[] { X509 cert; X509_STORE store; X509_LOOKUP lookup; X509_STORE_CTX verify_ctx; FILE fp; 257 OpenSSL_add_all_algorithms; ERR_load_crypto_strings; seed_prng; first read the client certificate if fp = fopenCLIENT_CERT, r int_errorError reading client certificate file; if cert = PEM_read_X509fp, NULL, NULL, NULL int_errorError reading client certificate in file; fclosefp; create the cert store and set the verify callback if store = X509_STORE_new int_errorError creating X509_STORE_CTX object; X509_STORE_set_verify_cb_funcstore, verify_callback; load the CA certificates and CRLs if X509_STORE_load_locationsstore, CA_FILE, CA_DIR = 1 int_errorError loading the CA file or directory; if X509_STORE_set_default_pathsstore = 1 int_errorError loading the system-wide CA certificates; if lookup = X509_STORE_add_lookupstore, X509_LOOKUP_file int_errorError creating X509_LOOKUP object; if X509_load_crl_filelookup, CRL_FILE, X509_FILETYPE_PEM = 1 int_errorError reading the CRL file; enabling verification against CRLs is not possible in prior versions if OPENSSL_VERSION_NUMBER 0x00907000L set the flags of the store so that CRLs are consulted X509_STORE_set_flagsstore, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL; endif create a verification context and initialize it if verify_ctx = X509_STORE_CTX_new int_errorError creating X509_STORE_CTX object; X509_STORE_CTX_init did not return an error condition in prior versions if OPENSSL_VERSION_NUMBER 0x00907000L if X509_STORE_CTX_initverify_ctx, store, cert, NULL = 1 int_errorError initializing verification context; else X509_STORE_CTX_initverify_ctx, store, cert, NULL; endif verify the certificate if X509_verify_certverify_ctx = 1 int_errorError verifying the certificate; else printfCertificate verified correctly\n; return 0; } After reading in the peer certificate, we create our certificate store as expected. We also assign a verification callback function. The form and purpose of this callback is identical to the verification callback for SSL connections we saw in Chapter 5 . 258 The following two function calls should also look familiar; weve already examined their mirrored functions for SSL_CTX objects. They behave just like the SSL-specific versions. To load the CRL file, however, we use the method described at the beginning of this section. The function X509_STORE_add_lookup will create the lookup object we need when we pass it the correct lookup method, given by X509_LOOKUP_file . After weve created the lookup its already added to the store, we need only assign the lookup the file from which to read. This is done by the call to X509_load_crl_file . In fact, the call to X509_STORE_load_locations could have been removed and done with lookups instead. For instance, the conditional clause using the function could be replaced by the following: if lookup = X509_STORE_add_lookupstore, X509_LOOKUP_file fprintfstderr, Error creating X509_LOOKUP object\n; if X509_LOOKUP_load_filelookup, CA_FILE, X509_FILETYPE_PEM = 1 fprintfstderr, Error reading the CA file\n; if lookup = X509_STORE_add_lookupstore, X509_LOOKUP_hash_dir fprintfstderr, Error creating X509_LOOKUP object\n; if X509_LOOKUP_add_dirlookup, CA_DIR, X509_FILETYPE_PEM = 1 fprintfstderr, Error reading the CRL file\n; This code snippet simply follows the paradigm laid out above; we create a lookup and then set the lookup in an appropriate location. Using this expanded code can be useful in applications in which we want to do a more specific type of loading of the store, such as an application that has several CA files, each of which may contain more than one certificate. Setting the flags for the certificate store is very important. By setting flags in the store, they are automatically copied to the store contexts created from it. Thus, setting the flag X509_V_FLAG_CRL_CHECK instructs the contexts to check client certificates for possible revocation. This flag will cause only the last item, the identity certificate itself, to be checked; the chain is not checked for possible revocation. To check the entire chain, we must also specify X509_V_FLAG_CRL_CHECK_ALL . As noted in the code, this capability is not available in versions of OpenSSL prior to Version 0.9.7. After setting the flags, our store is adequately set up, and we are ready to begin the rather simple process of verifying the actual certificate. We create an X509_STORE_CTX , initialize it, and then call the verify function to determine the result. Looking at the initialization function in more detail is helpful, however. int X509_STORE_CTX_initX509_STORE_CTX ctx, X509_STORE store, X509 x509, STACK_OFX509 chain; The last argument to this function optionally allows us to pass in the complete peer certificate chain for verification. This is often necessary, since the verifying party may not have a complete list of certificates that includes the identity certificate. This problem arises most commonly when CAs sign other CAs, as in the example SSL applications in Chapter 5 . By passing the entire peer chain, we can attempt to verify the whole chain and have fewer errors because valid issuer certificates could not be found. Of course, in some applications—namely those that want only directly signed, authorized clients—this is inappropriate and should be left as NULL . In versions prior to 0.9.7, this function does not return an integer error code. At the end of the main function, we can check the return value of X509_verify_cert and determine if the verification succeeded. As we would expect, our callback is used during this function call it was passed on from the store to the store context. 259

10.4 PKCS7 and SMIME

PKCS7 defines a standard format for data that has had cryptography applied to it. Like most standards, using this format will guarantee a level of interoperability with both existing and future applications. The standard itself is based on other PKCS standards for performing cryptographic operations. It is important to note that PKCS7 specifies only a data format, not the choice of any specific algorithms. Perhaps the most important trait of PKCS7 is that it is the basis for Secure Multipurpose Internet Mail Extensions SMIME. SMIME is a specification for sending secure email. Built on top of PKCS7 and the former MIME standard, SMIME allows us to email messages that can assure secrecy, integrity, authentication, and non-repudiation. Using SMIME, we can sign, verify, encrypt, and decrypt messages. This is very useful when developing mail applications, but it can also be used by programs that need to transmit data over a text-based medium, such as an instant-messaging implementation. As well see, programming with OpenSSLs PKCS7 and SMIME packages requires us to use much of our knowledge of the other packages. A common misconception is that PKCS7 and SMIME are one in the same. In fact, they are not. SMIME merely defines a specific encoding for PKCS7 data. Using the SMIME standard as implemented by OpenSSL, we can create applications that securely interact with other SMIME- compliant applications, since the data encoding is standardized. It is also important to note here that OpenSSLs support for PKCS7 and SMIME is limited. Only SMIMEv2 and PKCS7 v1.5 are supported.

10.4.1 Signing and Verifying

The concept of signing and verifying is familiar by this point. Conceptually, to sign a message, we will need the senders private key and the message to sign; the verification operation will require the senders public key and the signed message. With SMIME, the implementation is rather simple. The signing process is opaque to the calling application. We simply provide all of the information with one function call to PKCS7_sign , and we get back a PKCS7 object. From there, we can use SMIME_write_PKCS7 to output the SMIME armored message. Likewise, with verification, we obtain a PKCS7 object using SMIME_read_PKCS7 and perform the verification by calling the PKCS_verify function. While the calls to the actual PKCS7 and SMIME family of functions are simple, we must perform some nontrivial setup for all of the arguments to these functions. In Example 10-8 below, our implementation focuses on that critical setup. Before we get ahead of ourselves, we should first look at these four functions in more detail. PKCS7 PKCS7_signX509 signcert, EVP_PKEY pkey, STACK_OFX509 certs, BIO data, int flags; The first argument to PKCS7_sign is the certificate with which well sign the message. The second argument is the corresponding private key to the certificate. The third argument allows us to add other certificates to the SMIME message. This is most useful when we have a long certificate chain and we wish to aid the receiving partys verification process. The fourth argument accepts a BIO object from which the message will be read. The last argument, flags , allows us to set properties of the resulting PKCS7 object. Its potential values are discussed at the end of this section. TE AM FL Y Team-Fly ®