[ Team LiB ] |
10.5 Performing X.509 Certificate Verification with OpenSSL10.5.1 ProblemYou have an X.509 certificate and you want to verify its validity using OpenSSL. 10.5.2 SolutionOpenSSL represents an X.509 certificate using an X509 object. Another object, an X509_STORE, must be combined with the X509 object to be verified into an X509_STORE_CTX object. An X509_STORE object contains the certificates that OpenSSL will use to verify the certificate under scrutiny, as well as an optional CRL. The X509_STORE_CTX object simply combines the X509_STORE and X509 objects. The actual certificate verification is performed by calling X509_verify_cert( ) and passing it the X509_STORE_CTX object. 10.5.3 DiscussionActually performing the certificate verification requires a significant amount of setup work. Much of the work should not really be necessary, but there are some issues with the current version of OpenSSL that need to be addressed. The OpenSSL team is aware of the problems we have encountered, and we anticipate that they will be fixed at some point in the future, but unfortunately, we do not know when that might be. OpenSSL provides a set of functions for manipulating X509_STORE objects, and we will be using them, but in versions of OpenSSL up to and including the initial release of 0.9.7, no X.509 objects are reference counted while other OpenSSL objects (including EVP_PKEY, SSL_CTX, and many others) are. This presents a problem for us because much of the code that we will be presenting needs to have only a single X509_STORE object used for different purposes. If we attach the X509_STORE object to an SSL_CTX, for example, when the SSL_CTX is destroyed, so is the X509_STORE object. When trying to build a higher-level API on top of OpenSSL's API, things quickly get ugly. The situation is complicated by the fact that OpenSSL provides no APIs to duplicate objects. Our solution to this problem as a whole is to create a new structure that contains everything we might need, then to create X509_STORE objects from that structure as we need them. It is obviously not optimal, and it is also not a perfect solution, but it is difficult to do any better. The proper solution is OpenSSL's to implement, but it's not a small task. Reference counting is often difficult to get right, and adding that kind of memory management into a large body of existing code is even harder. We begin our solution by defining two data types. One is merely a convenience for a function pointer. The other is the core of our X509_STORE wrapper: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <openssl/evp.h> #include <openssl/x509.h> typedef int (*spc_x509verifycallback_t)(int, X509_STORE_CTX *); typedef struct { char *cafile; char *capath; char *crlfile; spc_x509verifycallback_t callback; STACK_OF(X509) *certs; STACK_OF(X509_CRL) *crls; char *use_certfile; STACK_OF(X509) *use_certs; char *use_keyfile; EVP_PKEY *use_key; int flags; } spc_x509store_t; We will not get into any detailed explanation of this structure here. Instead, we will provide a complete set of functions to manipulate the structure and explain as we go along. The first two functions are used to initialize and clean up an spc_x509store_t object. The caller is responsible for allocating memory for the object as necessary. Our API will only manage the object's contents. void spc_init_x509store(spc_x509store_t *spc_store) { spc_store->cafile = 0; spc_store->capath = 0; spc_store->crlfile = 0; spc_store->callback = 0; spc_store->certs = sk_X509_new_null( ); spc_store->crls = sk_X509_CRL_new_null( ); spc_store->use_certfile = 0; spc_store->use_certs = sk_X509_new_null( ); spc_store->use_keyfile = 0; spc_store->use_key = 0; spc_store->flags = 0; } void spc_cleanup_x509store(spc_x509store_t *spc_store) { if (spc_store->cafile) free(spc_store->cafile); if (spc_store->capath) free(spc_store->capath); if (spc_store->crlfile) free(spc_store->crlfile); if (spc_store->use_certfile) free(spc_store->use_certfile); if (spc_store->use_keyfile) free(spc_store->use_keyfile); if (spc_store->use_key) EVP_PKEY_free(spc_store->use_key); sk_X509_free(spc_store->certs); sk_X509_free(spc_store->crls); sk_X509_free(spc_store->use_certs); } The next three functions are used to set the locations from which trusted certificates and certificate revocation lists will be loaded:
For any of the functions, NULL may be specified for the filename or pathname, in which case the system defaults will be used. void spc_x509store_setcafile(spc_x509store_t *spc_store, char *cafile) { if (spc_store->cafile) free(spc_store->cafile); spc_store->cafile = (cafile ? strdup(cafile) : 0); } void spc_x509store_setcapath(spc_x509store_t *spc_store, char *capath) { if (spc_store->capath) free(spc_store->capath); spc_store->capath = (capath ? strdup(capath) : 0); } void spc_x509store_setcrlfile(spc_x509store_t *spc_store, char *crlfile) { if (spc_store->crlfile) free(spc_store->crlfile); spc_store->crlfile = (crlfile ? strdup(crlfile) : 0); } Additional certificates and CRLs can be added to the store using one of the next two functions. Note that if duplicate certificates or CRLs are included in the spc_x509store_t object, spc_create_x509store( ) will not be able to successfully create an X509_STORE object. These two functions should only be used to add certificates and CRLs to the store that are not present in the certificate file, certificate path, or CRL file. void spc_x509store_addcert(spc_x509store_t *spc_store, X509 *cert) { sk_X509_push(spc_store->certs, cert); } void spc_x509store_addcrl(spc_x509store_t *spc_store, X509_CRL *crl) { sk_X509_CRL_push(spc_store->crls, crl); } The last set of functions for manipulating spc_x509store_t objects is used for setting up a certificate verification callback function and for defining flags that control various aspects of the X509_STORE and certificate verification behavior. If no verification callback function is defined, spc_verify_callback( ) is the default; it simply prints any errors encountered out to stderr. void spc_x509store_setcallback(spc_x509store_t *spc_store, spc_x509verifycallback_t callback) { spc_store->callback = callback; } #define SPC_X509STORE_NO_DEFAULT_CAFILE 0x01 #define SPC_X509STORE_NO_DEFAULT_CAPATH 0x02 void spc_x509store_setflags(spc_x509store_t *spc_store, int flags) { spc_store->flags |= flags; } void spc_x509store_clearflags(spc_x509store_t *spc_store, int flags) { spc_store->flags &= ~flags; } int spc_verify_callback(int ok, X509_STORE_CTX *store) { if (!ok) fprintf(stderr, "Error: %s\n", X509_verify_cert_error_string(store->error)); return ok; } Only two flags are defined here, leaving plenty of room to expand the implementation and add additional flags as needed:
The last function, spc_create_x509store( ), creates a new X509_STORE object from the information contained in the spc_x509store_t object that it accepts as its only argument. Attentive readers will notice at this point that we have omitted discussion of several fields in the spc_x509store_t structure. We will address them in Recipe 10.7. X509_STORE *spc_create_x509store(spc_x509store_t *spc_store) { int i; X509_STORE *store; X509_LOOKUP *lookup; store = X509_STORE_new( ); if (spc_store->callback) X509_STORE_set_verify_cb_func(store, spc_store->callback); else X509_STORE_set_verify_cb_func(store, spc_verify_callback); if (!(lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file( )))) goto error_exit; if (!spc_store->cafile) { if (!(spc_store->flags & SPC_X509STORE_NO_DEFAULT_CAFILE)) X509_LOOKUP_load_file(lookup, 0, X509_FILETYPE_DEFAULT); } else if (!X509_LOOKUP_load_file(lookup, spc_store->cafile, X509_FILETYPE_PEM)) goto error_exit; if (spc_store->crlfile) { if (!X509_load_crl_file(lookup, spc_store->crlfile, X509_FILETYPE_PEM)) goto error_exit; X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); } if (!(lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir( )))) goto error_exit; if (!spc_store->capath) { if (!(spc_store->flags & SPC_X509STORE_NO_DEFAULT_CAPATH)) X509_LOOKUP_add_dir(lookup, 0, X509_FILETYPE_DEFAULT); } else if (!X509_LOOKUP_add_dir(lookup, spc_store->capath, X509_FILETYPE_PEM)) goto error_exit; for (i = 0; i < sk_X509_num(spc_store->certs); i++) if (!X509_STORE_add_cert(store, sk_X509_value(spc_store->certs, i))) goto error_exit; for (i = 0; i < sk_X509_CRL_num(spc_store->crls); i++) if (!X509_STORE_add_crl(store, sk_X509_CRL_value(spc_store->crls, i))) goto error_exit; return store; error_exit: if (store) X509_STORE_free(store); return 0; } We can now use the functions to manipulate spc_x509store_t objects in verifying an X.509 certificate's validity. The function spc_verify_cert( ) requires an X509 object and spc_x509store_t object. It creates an X509_STORE object from the information in the spc_x509store_t object, and combines it with the X509 object to create an X509_STORE_CTX object as required by X509_verify_cert( ). The return value from spc_verify_cert( ) will be -1 if some kind of error occurred that was not related to the validity of the certificate. If the certificate is valid, the return value will be 1; otherwise, the return value will be 0. #include <openssl/x509.h> int spc_verify_cert(X509 *cert, spc_x509store_t *spc_store) { int result = -1; X509_STORE *store = 0; X509_STORE_CTX *ctx = 0; if (!(store = spc_create_x509store(spc_store))) return -1; if ((ctx = X509_STORE_CTX_new( )) != 0) { if (X509_STORE_CTX_init(ctx, store, cert, 0) = = 1) result = (X509_verify_cert(ctx) = = 1); X509_STORE_CTX_free(ctx); } X509_STORE_free(store); return result; } 10.5.4 See Also |
[ Team LiB ] |