Commit 74e0ffefe2bf1b8071ac6f97086246ca80c64a0d

humphreyj 2018-08-12T00:17:48

Finish adding test cases for x509 which brings us to 100% branch/code coverage.

diff --git a/README.md b/README.md
index 66525ed..de7a398 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Official repository location:
 * Specify multiple TO, CC, and BCC recipients
 * Simple API and simple error handling (see Examples section below)
 * Optional OpenSSL TLS connection and authentication methods
-* TODO (almost completed): Test cases with 100% code and branch coverage
+* Test cases with 100% code and branch coverage
 * Doxygen with 100% documentation including the test code
 * Free software (permissive - CC0)
 
diff --git a/test/seams.c b/test/seams.c
index dac7f48..290a8d8 100644
--- a/test/seams.c
+++ b/test/seams.c
@@ -162,6 +162,12 @@ int g_smtp_test_err_ssl_do_handshake_ctr = -1;
 int g_smtp_test_err_ssl_get_peer_certificate_ctr = -1;
 
 /**
+ * See @ref g_smtp_test_err_x509_check_host_ctr and
+ * @ref test_seams_countdown_global.
+ */
+int g_smtp_test_err_x509_check_host_ctr = -1;
+
+/**
  * See @ref g_smtp_test_err_ssl_new_ctr and @ref test_seams_countdown_global.
  */
 int g_smtp_test_err_ssl_new_ctr = -1;
@@ -632,6 +638,31 @@ smtp_test_seam_ssl_get_peer_certificate(const SSL *ssl){
 }
 
 /**
+ * Allows the test harness to control when X509_check_host() fails.
+ *
+ * @param[in] cert     X509 certificate handle.
+ * @param[in] name     Server name.
+ * @param[in] namelen  Number of characters in @p name or 0 if null-terminated.
+ * @param[in] flags    Usually set to 0.
+ * @param[in] peername Pointer to CN from certificate stored in this buffer
+ *                     if not NULL.
+ * @retval  1 Successful host check.
+ * @retval  0 Failed host check.
+ * @retval -1 Internal error.
+ */
+int
+smtp_test_seam_x509_check_host(X509 *cert,
+                               const char *name,
+                               size_t namelen,
+                               unsigned int flags,
+                               char **peername){
+  if(smtp_test_seam_dec_err_ctr(&g_smtp_test_err_x509_check_host_ctr)){
+    return -1;
+  }
+  return X509_check_host(cert, name, namelen, flags, peername);
+}
+
+/**
  * Allows the test harness to control when SSL_new() fails.
  *
  * @param[in] ctx OpenSSL TLS context.
diff --git a/test/seams.h b/test/seams.h
index 37bea6d..2a48775 100644
--- a/test/seams.h
+++ b/test/seams.h
@@ -40,6 +40,7 @@
 #undef SSL_CTX_new
 #undef SSL_do_handshake
 #undef SSL_get_peer_certificate
+#undef smtp_test_seam_x509_check_host
 #undef SSL_new
 #undef SSL_read
 #undef SSL_write
@@ -226,6 +227,14 @@
  * Redefine this function from smtp.c and inject a test seam which
  * can control when this function fails.
  *
+ * See @ref smtp_test_seam_x509_check_host.
+ */
+#define X509_check_host smtp_test_seam_x509_check_host
+
+/**
+ * Redefine this function from smtp.c and inject a test seam which
+ * can control when this function fails.
+ *
  * See @ref smtp_test_seam_ssl_new.
  */
 #define SSL_new                  smtp_test_seam_ssl_new
diff --git a/test/test.c b/test/test.c
index 7db737f..6b10c16 100644
--- a/test/test.c
+++ b/test/test.c
@@ -54,6 +54,11 @@
 #define SMTP_MAX_SERVER_LEN          255
 
 /**
+ * Maximum certificate path length.
+ */
+#define SMTP_MAX_CAFILE_PATH         255
+
+/**
  * Maximum server port buffer length.
  */
 #define SMTP_MAX_PORT_LEN            10
@@ -1439,6 +1444,11 @@ struct smtp_test_config{
   char server[SMTP_MAX_SERVER_LEN];
 
   /**
+   * Path to server certificate file.
+   */
+  char cafile[SMTP_MAX_CAFILE_PATH];
+
+  /**
    * Server port number.
    */
   char port[SMTP_MAX_PORT_LEN];
@@ -1544,6 +1554,9 @@ smtp_test_config_load_from_file(const char *const config_path,
     if(strcmp(key, "server") == 0){
       smtp_strlcpy(config->server, value, sizeof(config->server));
     }
+    else if(strcmp(key, "cafile") == 0){
+      smtp_strlcpy(config->cafile, value, sizeof(config->cafile));
+    }
     else if(strcmp(key, "port") == 0){
       smtp_strlcpy(config->port, value, sizeof(config->port));
     }
@@ -1768,16 +1781,14 @@ smtp_func_test_all_connection_security(struct smtp_test_config *const config){
  */
 static void
 smtp_func_test_all_cafile(struct smtp_test_config *const config){
-  const char *const PATH_CERT = "test/config/dovecot.pem";
-
   smtp_func_test_send_email(config,
                             config->port,
                             SMTP_SECURITY_STARTTLS,
                             SMTP_DEBUG,
                             SMTP_TEST_DEFAULT_AUTH_METHOD,
-                            PATH_CERT,
+                            config->cafile,
                             "SMTP Test: cafile",
-                            PATH_CERT);
+                            config->cafile);
 }
 
 /**
@@ -2566,22 +2577,43 @@ test_failure_open(struct smtp_test_config *const config){
   rc = smtp_close(config->smtp);
   assert(rc == SMTP_STATUS_HANDSHAKE);
 
-  /*
-   * SSL_get_peer_certificate() failure.
-   * @todo This fails on SSL_connect because local server uses self-signed certificate.
-   */
+  /* SSL_CTX_load_verify_locations() failure. */
+  rc = smtp_open(config->server,
+                 config->port,
+                 SMTP_SECURITY_STARTTLS,
+                 SMTP_DEBUG,
+                 "test/config/file_does_not_exist",
+                 &config->smtp);
+  assert(rc == SMTP_STATUS_HANDSHAKE);
+  rc = smtp_close(config->smtp);
+  assert(rc == SMTP_STATUS_HANDSHAKE);
+
+  /* SSL_get_peer_certificate() failure. */
   g_smtp_test_err_ssl_get_peer_certificate_ctr = 0;
   rc = smtp_open(config->server,
                  config->port,
                  SMTP_SECURITY_STARTTLS,
                  SMTP_DEBUG,
-                 SMTP_TEST_DEFAULT_CAFILE,
+                 config->cafile,
                  &config->smtp);
   g_smtp_test_err_ssl_get_peer_certificate_ctr = -1;
   assert(rc == SMTP_STATUS_HANDSHAKE);
   rc = smtp_close(config->smtp);
   assert(rc == SMTP_STATUS_HANDSHAKE);
 
+  /* X509_check_host() failure.  */
+  g_smtp_test_err_x509_check_host_ctr = 0;
+  rc = smtp_open(config->server,
+                 config->port,
+                 SMTP_SECURITY_STARTTLS,
+                 SMTP_DEBUG,
+                 config->cafile,
+                 &config->smtp);
+  g_smtp_test_err_x509_check_host_ctr = -1;
+  assert(rc == SMTP_STATUS_HANDSHAKE);
+  rc = smtp_close(config->smtp);
+  assert(rc == SMTP_STATUS_HANDSHAKE);
+
   /*
    * TLS failure in @ref smtp_initiate_handshake (1) when using direct
    * TLS connection.
diff --git a/test/test.h b/test/test.h
index d604d2f..e6b4a0e 100644
--- a/test/test.h
+++ b/test/test.h
@@ -220,6 +220,13 @@ smtp_test_seam_ssl_do_handshake(SSL *ssl);
 X509 *
 smtp_test_seam_ssl_get_peer_certificate(const SSL *ssl);
 
+int
+smtp_test_seam_x509_check_host(X509 *cert,
+                               const char *name,
+                               size_t namelen,
+                               unsigned int flags,
+                               char **peername);
+
 SSL *
 smtp_test_seam_ssl_new(SSL_CTX *ctx);
 
@@ -424,6 +431,13 @@ int g_smtp_test_err_ssl_do_handshake_ctr;
 int g_smtp_test_err_ssl_get_peer_certificate_ctr;
 
 /**
+ * Counter for @ref smtp_test_seam_x509_check_host.
+ *
+ * See @ref test_seams_countdown_global for more details.
+ */
+int g_smtp_test_err_x509_check_host_ctr;
+
+/**
  * Counter for @ref smtp_test_seam_ssl_new.
  *
  * See @ref test_seams_countdown_global for more details.
diff --git a/www/index.wiki b/www/index.wiki
index dd4073f..ae8ab31 100644
--- a/www/index.wiki
+++ b/www/index.wiki
@@ -16,7 +16,7 @@ Feature list:
   *  Specify multiple TO, CC, and BCC recipients
   *  Simple API and simple error handling (see Examples section below)
   *  Optional OpenSSL TLS connection and authentication methods
-  *  TODO (almost completed): Test cases with 100% code and branch coverage
+  *  Test cases with 100% code and branch coverage
   *  Doxygen with 100% documentation including the test code
   *  Free software (permissive - CC0)