Further extension of the examples

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

Employing what we know about verifying the authenticity of the peer, we can extend our example applications to make them one step closer to being secure. For these examples, weve added the functions verify_callback and post_connection_check to common.c and their prototypes to common.h. The code for our revised client application, client2.c, is provided in Example 5-9 . The lines that differ from client1.c are marked. Line 3 defines the file we use to store trusted certificates. We define CADIR to be NULL on line 4 since we will use a flat file instead. Nothing prevents us from specifying both a file and a directory; but in this case, we do not need a directory. Example 5-9. client2.c 1 include common.h 2 3 define CAFILE rootcert.pem 4 define CADIR NULL 5 define CERTFILE client.pem 6 SSL_CTX setup_client_ctxvoid 7 { 8 SSL_CTX ctx; 9 10 ctx = SSL_CTX_newSSLv23_method; 11 if SSL_CTX_load_verify_locationsctx, CAFILE, CADIR = 1 12 int_errorError loading CA file andor directory; 13 if SSL_CTX_set_default_verify_pathsctx = 1 14 int_errorError loading default CA file andor directory; 15 if SSL_CTX_use_certificate_chain_filectx, CERTFILE = 1 16 int_errorError loading certificate from file; 116 17 if SSL_CTX_use_PrivateKey_filectx, CERTFILE, SSL_FILETYPE_PEM = 1 18 int_errorError loading private key from file; 19 SSL_CTX_set_verifyctx, SSL_VERIFY_PEER, verify_callback; 20 SSL_CTX_set_verify_depthctx, 4; 21 return ctx; 22 } 23 24 int do_client_loopSSL ssl 25 { 26 int err, nwritten; 27 char buf[80]; 28 29 for ;; 30 { 31 if fgetsbuf, sizeofbuf, stdin 32 break; 33 for nwritten = 0; nwritten sizeofbuf; nwritten += err 34 { 35 err = SSL_writessl, buf + nwritten, strlenbuf - nwritten; 36 if err = 0 37 return 0; 38 } 39 } 40 return 1; 41 } 42 43 int mainint argc, char argv[] 44 { 45 BIO conn; 46 SSL ssl; 47 SSL_CTX ctx; 48 long err; 49 50 init_OpenSSL; 51 seed_prng; 52 53 ctx = setup_client_ctx; 54 55 conn = BIO_new_connectSERVER : PORT; 56 if conn 57 int_errorError creating connection BIO; 58 59 if BIO_do_connectconn = 0 60 int_errorError connecting to remote machine; 61 62 ssl = SSL_newctx; 63 SSL_set_biossl, conn, conn; 64 if SSL_connectssl = 0 65 int_errorError connecting SSL object; 66 if err = post_connection_checkssl, SERVER = X509_V_OK 67 { 68 fprintfstderr, -Error: peer certificate: s\n, 69 X509_verify_cert_error_stringerr; 70 int_errorError checking SSL object after connection; 71 } 72 fprintfstderr, SSL Connection opened\n; 73 if do_client_loopssl 74 SSL_shutdownssl; 117 75 else 76 SSL_clearssl; 77 fprintfstderr, SSL Connection closed\n; 78 79 SSL_freessl; 80 SSL_CTX_freectx; 81 return 0; 82 } To load the trusted certificates from root.pem, we call SSL_CTX_load_verify_locations on line 11 and check for errors. Since we trust the users of the system on which this client will run, we also call SSL_CTX_set_default_verify_paths to load the built-in certificate stores. Our example does not explicitly require the loading of the default locations; it is included for illustration. It is good design practice to load these defaults only when the application will run on a trusted system and when the application itself needs to incorporate these extra certificates. After loading the trusted certificates, we set the verification mode to SSL_VERIFY_PEER and assign the callback line 19. When implementing SSL clients, the verification mode should always include SSL_VERIFY_PEER . Without this, we could never tell if the server we connect with is properly authenticated. As we discussed in the section above, the verify_callback function simply reports errors in more detail and does not change the behavior of the internal verification process. The following line, line 20, sets the maximum depth for verification to four. For this client example, four levels of verification ought to be plenty because our certificate hierarchy is not too complex. Given the details from the previous sidebar describing our example PKI, the minimum depth we can assign to our client is two, since the servers certificate is signed by the server CA, which, in turn, is signed by the root CA that we trust. The last major change to this version of the client is in lines 66-71. These lines use the post_connection_check function that we developed in Example 5-8 . This call asserts that the server we are connected with did present a certificate and the certificate it provided has splat.zork.org as the FQDN. If any errors occur, we can call X509_verify_cert_error_string to convert our error code into a string to print to the console. Example 5-10 shows the contents of server2.c, our example server program. The changes made to it are congruent with the changes made to the client application. Example 5-10. server2.c 1 include common.h 2 3 define CAFILE rootcert.pem 4 define CADIR NULL 5 define CERTFILE server.pem 6 SSL_CTX setup_server_ctxvoid 7 { 8 SSL_CTX ctx; 9 10 ctx = SSL_CTX_newSSLv23_method; 11 if SSL_CTX_load_verify_locationsctx, CAFILE, CADIR = 1 12 int_errorError loading CA file andor directory; 13 if SSL_CTX_set_default_verify_pathsctx = 1 14 int_errorError loading default CA file andor directory; 15 if SSL_CTX_use_certificate_chain_filectx, CERTFILE = 1 16 int_errorError loading certificate from file; 17 if SSL_CTX_use_PrivateKey_filectx, CERTFILE, SSL_FILETYPE_PEM = 1 18 int_errorError loading private key from file; 19 SSL_CTX_set_verifyctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 118 20 verify_callback; 21 SSL_CTX_set_verify_depthctx, 4; 22 return ctx; 23 } 24 25 int do_server_loopSSL ssl 26 { 27 int err, nread; 28 char buf[80]; 29 30 for ;; 31 { 32 for nread = 0; nread sizeofbuf; nread += err 33 { 34 err = SSL_readssl, buf + nread, sizeofbuf - nread; 35 if err = 0 36 break; 37 } 38 fwritebuf, 1, nread, stdout; 39 } 40 return SSL_get_shutdownssl SSL_RECEIVED_SHUTDOWN ? 1 : 0; 41 } 42 43 void THREAD_CC server_threadvoid arg 44 { 45 SSL ssl = SSL arg; 46 long err; 47 48 ifndef WIN32 49 pthread_detachpthread_self; 50 endif 51 if SSL_acceptssl = 0 52 int_errorError accepting SSL connection; 53 if err = post_connection_checkssl, CLIENT = X509_V_OK 54 { 55 fprintfstderr, -Error: peer certificate: s\n, 56 X509_verify_cert_error_stringerr; 57 int_errorError checking SSL object after connection; 58 } 59 fprintfstderr, SSL Connection opened\n; 60 if do_server_loopssl 61 SSL_shutdownssl; 62 else 63 SSL_clearssl; 64 fprintfstderr, SSL Connection closed\n; 65 SSL_freessl; 66 ERR_remove_state0; 67 ifdef WIN32 68 _endthread; 69 endif 70 } 71 72 int mainint argc, char argv[] 73 { 74 BIO acc, client; 75 SSL ssl; 76 SSL_CTX ctx; 77 THREAD_TYPE tid; 78 79 init_OpenSSL; 80 seed_prng; 81 82 ctx = setup_server_ctx; 83 84 acc = BIO_new_acceptPORT; 85 if acc 86 int_errorError creating server socket; 87 119 88 if BIO_do_acceptacc = 0 89 int_errorError binding server socket; 90 91 for ;; 92 { 93 if BIO_do_acceptacc = 0 94 int_errorError accepting connection; 95 96 client = BIO_popacc; 97 if ssl = SSL_newctx 98 int_errorError creating SSL context; 99 SSL_set_accept_statessl; 100 SSL_set_biossl, client, client; 101 THREAD_CREATEtid, server_thread, ssl; 102 } 103 104 SSL_CTX_freectx; 105 BIO_freeacc; 106 return 0; 107 } One way the changes to the server stray from the changes to the client is in the verification mode. In server applications, the behavior of SSL_VERIFY_PEER is slightly different; it causes the server to solicit a certificate from the client. The other verification flag used is SSL_VERIFY_FAIL_IF_NO_PEER_CERT . This flag instructs the server to fail on the handshake if the client presents no certificate. The choice of whether to require a client to supply a certificate depends on the type of server application youre building. In many cases, requiring a client certificate may not be strictly necessary, but its generally a bad idea to request a certificate without requiring one because it may often cause a client to present the user with a prompt to select the certificate to use. Particularly in a web-based environment, this is not desirable. If you need to require a certificate only for specific commands or options, its better to force a renegotiation instead. The clients usage of the SERVER -defined value and the servers usage of CLIENT is an oversimplification. In many cases, especially with that of a server, the string containing the peers FQDN will not be so readily available. Instead, we may need to look at the IP address to which we are connected and use it to discover the FQDN. For purposes of clarity, this process was omitted, and the names were hardcoded. Additionally, when verifying peer certificates, the owner will often not be a machine with a FQDN but rather a person, organization, etc. For these cases, its important to change the post_connection_check routine accordingly to allow for successful connections. Using the tricks we learned in this step, we have now developed a viable framework for verifying a peer and assuring authentication is done properly. The majority of the battle to SSL-enable our application is over; however, we still have one more step.

5.1.4 Step 3: SSL Options and Cipher Suites