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
®