111
presenting certificates of some smaller chain length, it is a good idea to set the value to exclude certificates composed of longer chains from being verified successfully. Setting the depth to zero
allows chains of unlimited length to be used.
There is a known security vulnerability in
SSL_CTX_set_verify_depth
in versions of OpenSSL prior to 0.9.6. The problem stemmed from the fact that the internal verification routine
did not properly check extensions on peer certificate chains; it approved certificate chains that contained non-CA certificates as long as they led to
a trusted root CA. Thus, using any verification depth greater than one left the application susceptible to attack from anyone signed by the trusted
root CA. Since this problem has been fixed in newer versions of OpenSSL by checking the X509v3 fields regarding CA authorization, this
vulnerability should be of only academic interest.
5.1.3.4 Incorporating certificate revocation lists
A large problem with SSL security is the availability and usage of certificate revocation lists. Since certificates can be revoked by the issuing CA, we must somehow account for this in our
SSL implementation. To do this, an application must load CRL files in order for the internal verification process to ensure each certificate it verifies is not revoked. Unfortunately, OpenSSLs
CRL functionality is incomplete in Version 0.9.6. The features necessary to utilize CRL information will be complete in new versions starting with 0.9.7.
Because this functionality is not available at the time of writing, CRL usage will not be incorporated in this example. However, we can tell you what you will need to do once newer
releases are made. Remember, it is paramount to include CRL checking when verifying certificates. If any new version of OpenSSL is used when building applications, this step is
required for security.
The SSL interface itself does not support CRL incorporation directly; instead, we must use the underlying
X509
interface. The function
SSL_CTX_get_cert_store
retrieves the internal
X509_STORE
object from the
SSL_CTX
object. Operations on this store object allow us to perform a variety of tweaks on the verification process. In fact, the functions
SSL_CTX_load_verify_locations
and
SSL_CTX_set_default_paths
call functions against this same
X509_STORE
object to perform their respective operations.
X509_STORE SSL_CTX_get_cert_storeSSL_CTX ctx;
All of the details for interacting with certificate stores to set further verification parameters or incorporate CRL data are discussed in
Chapter 10 with the verification of certificate chains. We
strongly recommend that developers implementing applications consult the verification process in Chapter 10
that uses
X509_STORE
objects to learn the proper method of SSL certificate verification against CRLs. The process involves adding the CRL files to the
X509_STORE
via a file lookup method and then setting the stores flags to check certificates against the CRLs.
5.1.3.5 Post-connection assertions
Essentially,
SSL_CTX_set_verify
and
SSL_CTX_set_verify_depth
are all we need to use in order for OpenSSL to verify the peer certificate chain upon connection. There is more,
however. After connecting the
SSL
object, we need to assert that some assumed properties about the connection are indeed true. OpenSSL provides several functions that allow us to create a post-
112
connection verification routine to make sure that we havent been fooled by a malicious peer. This post-connection verification routine is very important because it allows for much finer grained
control over the certificate that is presented by the peer, beyond the certificate verification that is required by the SSL protocol proper.
The function
SSL_get_peer_certificate
will return a pointer to an
X509
object that contains the peers certificate. While the handshake is complete and, presumably, the verification
completed correctly, we must still use this function. Consider the case in which the peer presents no certificate when one is requested but not required. The certificate verification routines—both
the built-in and the filter—will not return errors since there was nothing wrong with the
NULL
certificate. Thus, to prevent this condition, we must call this function and check that the return value is not
NULL
. If this function returns a non-
NULL
value, the reference count of the return object is increased. In order to prevent memory leaks, we must call
X509_free
to decrement the count after were done using the object.
Our application will be vulnerable if we do not check the peer certificate beyond verification of the chain. For example, lets say that were making a web browsing application. To keep it simple,
well allow just one trusted CA. When we do this, any SSL peer with a certificate signed by the same CA will be verified correctly. This isnt secure. Nothing prevents an attacker from getting his
own certificate signed by the CA and then hijacking all your sessions. We thwart this kind of masquerade by tying the certificate to some piece of information unique to the machine. For
purposes of SSL, this piece of information is the entitys fully qualified domain name FQDN, also called the DNS name.
The common practice with X.509v1 certificates was to put the FQDN in the certificates
commonName
field of the
subjectName
field. This practice is no longer recommended for new applications since X.509v3 allows certificate extensions to hold the FQDN as well as other
identifying information, such as IP address. The proper place for the FQDN is in the
dNSName
field of the
subjectAltName
extension. We use the function
post_connection_check
to perform these checks for us. We recommend always checking for the
dNSName
field first, and if it isnt present, we can check the
commonName
field. Checking the
commonName
field is strictly for backward compatibility, so if this isnt a concern, it can safely be omitted. Our example function will check for the extension
first and then fall back to the
commonName
. One feature our example does omit is the optional wildcard expansion. RFC 2818 specifies a paradigm for allowing FQDNs in certificates to contain
wildcards. Implementing this functionality is simply a text-processing issue and is thus omitted for clarity.
SSL_get_verify_result
is another API function that we will employ in our post-connection check. This function returns the error code last generated by the verification routines. If no error
has occurred,
X509_V_OK
is returned. We should call this function and make sure the returned value equals
X509_V_OK
. When browsing the example application, it is obvious that robust error handling has been left out for clarity. For example, the programs simply exit when an error occurs.
In most cases, we will want to do something better to handle errors in some application-specific way. Checking the verify result is always a good idea. It makes an assertion that no matter what
error handling occurred up to this point, if the result isnt OK now, we should disconnect.
Example 5-8 shows a function that performs the checks that weve just described. In the example,
well check to be sure that the certificate contains the FQDN of the peer to which we expect to be connecting. For the client, well want to make sure that the server presents a certificate that
contains the FQDN of the servers address. Likewise, for the server, well want to make sure that the client presents a certificate that contains the FQDN of the clients address. In this case, our
checking of the client certificate will be very strict because well be expecting the client to be
113
using a specific FQDN, and well allow only that one. For the purposes of our example client and server, this function should appear in common.c and be prototyped in common.h.
Example 5-8. A function to do post-connection assertions implemented in common.c and prototyped in common.h
long post_connection_checkSSL ssl, char host {
X509 cert; X509_NAME subj;
char data[256]; int extcount;
int ok = 0; Checking the return from SSL_get_peer_certificate here is not
strictly necessary. With our example programs, it is not possible for it to return NULL. However, it is good form to
check the return since it can return NULL if the examples are modified to enable anonymous ciphers or for the server to not
require a client certificate. if cert = SSL_get_peer_certificatessl || host
goto err_occured; if extcount = X509_get_ext_countcert 0
{ int i;
for i = 0; i extcount; i++ {
char extstr; X509_EXTENSION ext;
ext = X509_get_extcert, i; extstr =
OBJ_nid2snOBJ_obj2nidX509_EXTENSION_get_objectext; if strcmpextstr, subjectAltName
{ int j;
unsigned char data; STACK_OFCONF_VALUE val;
CONF_VALUE nval; X509V3_EXT_METHOD meth;
if meth = X509V3_EXT_getext break;
data = ext-value-data; val = meth-i2vmeth,
meth-d2iNULL, data, ext-value- length,
NULL; for j = 0; j sk_CONF_VALUE_numval; j++
{ nval = sk_CONF_VALUE_valueval, j;
if strcmpnval-name, DNS strcmpnval- value, host
{ ok = 1;
break;
114
} }
} if ok
break; }
} if ok subj = X509_get_subject_namecert
X509_NAME_get_text_by_NIDsubj, NID_commonName, data, 256 {
data[255] = 0; if strcasecmpdata, host = 0
goto err_occured; }
X509_freecert; return SSL_get_verify_resultssl;
err_occured: if cert
X509_freecert; return X509_V_ERR_APPLICATION_VERIFICATION;
}
At a high level, the function
post_connection_check
is implemented as a wrapper around
SSL_get_verify_result
, which performs our extra peer certificate checks. It uses the reserved error code
X509_V_ERR_APPLICATION_VERIFICATION
to indicate errors where there is no peer certificate present or the certificate presented does not match the expected FQDN.
This function will return an error in the following circumstances:
•
If no peer certificate is found
•
If it is called with a
NULL
second argument, i.e., if no FQDN is specified to compare against
•
If the
dNSName
fields found if any do not match the host argument and the
commonName
also doesnt match the host argument if found
•
Any time the
SSL_get_verify_result
routine returns an error As long as none of the above errors occurs, the value
X509_V_OK
will be returned. Our example programs are further extended below to employ this function.
Unfortunately, the code to check the
dNSName
is not very clear. We use the
X509
functions to access the extensions. We then iterate through the extensions and use the extension-specific
parsing routines to find all extensions that are
subjectAltName
fields. Since a
subjectAltName
field may itself contain several fields, we must then iterate through those fields to find any
dNSName
fields they are tagged with the short name
DNS
. Since it is rather confusing, we will step through this function to explain its behavior. Having a stronger
understanding of some of the more advanced programming techniques presented in Chapter 10
will help demystify the implementation of this function. At the onset, we simply get the peer certificate from the
SSL
object. If the function
X509_get_ext_count
returns a positive number, we know there are some X.509v3 extensions present in the peers certificate. Given this, we iterate over the extensions to look for a
subjectAltName
. The function
X509_get_ext
will retrieve an extension for us based on the counter. We also use the variable
extstr
to hold the extracted short name of the extension. Unfortunately, we must use three functions to perform this task: the innermost extracts the
115
ASN1_OBJECT
, the next fetches the
NID
, and the outermost function gets the short name as a
const char
from the
NID
. Next, we check if the extension is what were looking for by comparing the short name against the
string constant
subjectAltName
. Once were sure its the right extension, we need to extract the
X509V3_EXT_METHOD
object from the extension. This object is a container of extension-specific functions for manipulating the data within the extension. We access the data we wish to
manipulate directly through the
value
member of the
X509_EXTENSION
structure. The
d2i
and
i2v
functions serve the purpose of converting the raw data in the
subjectAltName
to a stack of
CONF_VALUE
objects. This is necessary to make it simple to iterate over the several kinds of fields in the
subjectAltName
so that we may find the
dNSName
fields. We check each member of this
CONF_VALUE
stack to see if we have a match for the host string in a
dNSName
field. Keep in mind that the
dNSName
field is named
DNS
in the extension itself, since its referenced by its short name. As soon as we find a match, we stop the iterations over all the
extensions. We only pursue checking the
commonName
of the certificate if no match is found in a
dNSName
field. If we fail to find an FQDN that matches the host argument in either the
dNSName
field or the
commonName
, we return the code for an application-specific error. In most real-world cases, matching one specific FQDN is not desirable. Most often, a server would have a list known as a
whitelist that contains all of the acceptable FQDNs for connecting clients. If the clients certificate contains an FQDN that appears on this list, the certificate is accepted; otherwise, it is
rejected.
5.1.3.6 Further extension of the examples