ssl_send_certificate_error(): Don't crash if there's no certificate information available
[privoxy.git] / ssl_common.c
index add68a2..4cf7291 100644 (file)
@@ -7,7 +7,7 @@
  *                not depend on particular TLS/SSL library.
  *
  * Copyright   :  Written by and Copyright (c) 2017 Vaclav Svec. FIT CVUT.
- *                Copyright (C) 2018-2020 by Fabian Keil <fk@fabiankeil.de>
+ *                Copyright (C) 2018-2021 by Fabian Keil <fk@fabiankeil.de>
  *
  *                This program is free software; you can redistribute it
  *                and/or modify it under the terms of the GNU General
@@ -31,6 +31,7 @@
 #include <string.h>
 
 #include <ctype.h>
+#include <unistd.h>
 #include "config.h"
 #include "project.h"
 #include "miscutil.h"
@@ -289,8 +290,8 @@ extern void free_certificate_chain(struct client_state *csp)
    /* Cleaning buffers */
    memset(csp->server_certs_chain.info_buf, 0,
       sizeof(csp->server_certs_chain.info_buf));
-   memset(csp->server_certs_chain.file_buf, 0,
-      sizeof(csp->server_certs_chain.file_buf));
+   freez(csp->server_certs_chain.file_buf);
+
    csp->server_certs_chain.next = NULL;
 
    /* Freeing memory in whole linked list */
@@ -298,6 +299,11 @@ extern void free_certificate_chain(struct client_state *csp)
    {
       struct certs_chain *cert_for_free = cert;
       cert = cert->next;
+
+      /* Cleaning buffers */
+      memset(cert_for_free->info_buf, 0, sizeof(cert_for_free->info_buf));
+      freez(cert_for_free->file_buf);
+
       freez(cert_for_free);
    }
 }
@@ -327,7 +333,7 @@ extern void ssl_send_certificate_error(struct client_state *csp)
 
    /* Header of message with certificate information */
    const char message_begin[] =
-      "HTTP/1.1 200 OK\r\n"
+      "HTTP/1.1 403 Certificate validation failed\r\n"
       "Content-Type: text/html\r\n"
       "Connection: close\r\n\r\n"
       "<!DOCTYPE html>\n"
@@ -336,7 +342,7 @@ extern void ssl_send_certificate_error(struct client_state *csp)
       "<p><a href=\"https://" CGI_SITE_2_HOST "/\">Privoxy</a> was unable "
       "to securely connect to the destination server.</p>"
       "<p>Reason: ";
-   const char message_end[] = "</body></html>\r\n\r\n";
+   const char message_end[] = "</body></html>\n";
    char reason[INVALID_CERT_INFO_BUF_SIZE];
    memset(reason, 0, sizeof(reason));
 
@@ -352,11 +358,22 @@ extern void ssl_send_certificate_error(struct client_state *csp)
    cert = &(csp->server_certs_chain);
    while (cert->next != NULL)
    {
-      size_t base64_len = 4 * ((strlen(cert->file_buf) + 2) / 3) + 1;
+      size_t base64_len;
+
+      if (cert->file_buf != NULL)
+      {
+         base64_len = 4 * ((strlen(cert->file_buf) + 2) / 3) + 1;
 
-      message_len += strlen(cert->info_buf) + strlen("<pre></pre>\n")
-                     +  base64_len + strlen("<a href=\"data:application"
-                        "/x-x509-ca-cert;base64,\">Download certificate</a>");
+         message_len += strlen(cert->info_buf) + strlen("<pre></pre>\n")
+            +  base64_len + strlen("<a href=\"data:application"
+            "/x-x509-ca-cert;base64,\">Download certificate</a>");
+      }
+      else
+      {
+         log_error(LOG_LEVEL_ERROR,
+            "Incomplete certificate information for %s.",
+            csp->http->hostport);
+      }
       cert = cert->next;
    }
 
@@ -373,47 +390,62 @@ extern void ssl_send_certificate_error(struct client_state *csp)
    cert = &(csp->server_certs_chain);
    while (cert->next != NULL)
    {
-      size_t olen = 0;
-      size_t base64_len = 4 * ((strlen(cert->file_buf) + 2) / 3) + 1; /* +1 for terminating null*/
-      char base64_buf[base64_len];
-      memset(base64_buf, 0, base64_len);
-
-      /* Encoding certificate into base64 code */
-      ret = ssl_base64_encode((unsigned char*)base64_buf,
-               base64_len, &olen, (const unsigned char*)cert->file_buf,
-               strlen(cert->file_buf));
-      if (ret != 0)
+      if (cert->file_buf != NULL)
       {
-         log_error(LOG_LEVEL_ERROR,
-            "Encoding to base64 failed, buffer is to small");
+                                                       /* +1 for terminating null */
+         size_t base64_len = base64_len = 4 * ((strlen(cert->file_buf) + 2) / 3) + 1;
+         size_t olen = 0;
+         char base64_buf[base64_len];
+
+         memset(base64_buf, 0, base64_len);
+
+         /* Encoding certificate into base64 code */
+         ret = ssl_base64_encode((unsigned char*)base64_buf,
+                  base64_len, &olen, (const unsigned char*)cert->file_buf,
+                  strlen(cert->file_buf));
+         if (ret != 0)
+         {
+            log_error(LOG_LEVEL_ERROR,
+               "Encoding to base64 failed, buffer is to small");
+         }
+
+         strlcat(message, "<pre>",        message_len);
+         strlcat(message, cert->info_buf, message_len);
+         strlcat(message, "</pre>\n",     message_len);
+
+         if (ret == 0)
+         {
+            strlcat(message, "<a href=\"data:application/x-x509-ca-cert;base64,",
+               message_len);
+            strlcat(message, base64_buf, message_len);
+            strlcat(message, "\">Download certificate</a>", message_len);
+         }
       }
 
-      strlcat(message, "<pre>",        message_len);
-      strlcat(message, cert->info_buf, message_len);
-      strlcat(message, "</pre>\n",     message_len);
+      cert = cert->next;
+   }
+   strlcat(message, message_end, message_len);
 
-      if (ret == 0)
+   if (0 == strcmpic(csp->http->gpc, "HEAD"))
+   {
+      /* Cut off body */
+      char *header_end = strstr(message, "\r\n\r\n");
+      if (header_end != NULL)
       {
-         strlcat(message, "<a href=\"data:application/x-x509-ca-cert;base64,",
-            message_len);
-         strlcat(message, base64_buf, message_len);
-         strlcat(message, "\">Download certificate</a>", message_len);
+         header_end[3] = '\0';
       }
-
-      cert = cert->next;
    }
-   strlcat(message, message_end, message_len);
 
    /*
     * Sending final message to client
     */
-   ssl_send_data(ssl_attr, (const unsigned char *)message, strlen(message));
+   (void)ssl_send_data(ssl_attr, (const unsigned char *)message, strlen(message));
 
    free_certificate_chain(csp);
 
    log_error(LOG_LEVEL_CRUNCH, "Certificate error: %s: https://%s%s",
       reason, csp->http->hostport, csp->http->path);
-   log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s https://%s%s %s\" 200 %u",
+   log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s https://%s%s %s\" 403 %lu",
       csp->ip_addr_str, csp->http->gpc, csp->http->hostport, csp->http->path,
       csp->http->version, message_len-head_length);
 
@@ -487,12 +519,12 @@ extern char *make_certs_path(const char *conf_dir, const char *file_name,
       + strlen(file_name) + strlen(suffix) + 2;
 
    /* Setting delimiter and editing path length */
-#if defined(_WIN32) || defined(__OS2__)
+#if defined(_WIN32)
    char delim[] = "\\";
    path_size += 1;
-#else /* ifndef _WIN32 || __OS2__ */
+#else /* ifndef _WIN32 */
    char delim[] = "/";
-#endif /* ifndef _WIN32 || __OS2__ */
+#endif /* ifndef _WIN32 */
 
    /*
     * Building up path from many parts
@@ -659,42 +691,48 @@ extern int get_certificate_valid_to_date(char *buffer, size_t buffer_size, const
 
 /*********************************************************************
  *
- * Function    :  host_is_ip_address
+ * Function    :  enforce_sane_certificate_state
  *
- * Description :  Checks whether or not a host is specified by
- *                IP address. Does not actually validate the
- *                address.
+ * Description :  Makes sure the certificate state is sane.
  *
  * Parameters  :
- *          1  :  host = The host name to check
+ *          1  :  certificate = Path to the potentionally existing certifcate.
+ *          2  :  key = Path to the potentionally existing key.
  *
- * Returns     :   1 => Yes
- *                 0 => No
+ * Returns     :   -1 => Error
+ *                 0 => Certificate state is sane
  *
  *********************************************************************/
-extern int host_is_ip_address(const char *host)
+extern int enforce_sane_certificate_state(const char *certificate, const char *key)
 {
-   const char *p;
+   const int certificate_exists = file_exists(certificate);
+   const int key_exists = file_exists(key);
 
-   if (NULL != strstr(host, ":"))
+   if (!certificate_exists && key_exists)
    {
-      /* Assume an IPv6 address. */
-      return 1;
-   }
+      log_error(LOG_LEVEL_ERROR,
+         "A website key already exists but there's no matching certificate. "
+         "Removing %s before creating a new key and certificate.", key);
+      if (unlink(key))
+      {
+         log_error(LOG_LEVEL_ERROR, "Failed to unlink %s: %E", key);
 
-   for (p = host; *p; p++)
+         return -1;
+      }
+   }
+   if (certificate_exists && !key_exists)
    {
-      if ((*p != '.') && !privoxy_isdigit(*p))
+      log_error(LOG_LEVEL_ERROR,
+         "A certificate exists but there's no matching key. "
+         "Removing %s before creating a new key and certificate.", certificate);
+      if (unlink(certificate))
       {
-         /* Not a dot or digit so it can't be an IPv4 address. */
-         return 0;
+         log_error(LOG_LEVEL_ERROR, "Failed to unlink %s: %E", certificate);
+
+         return -1;
       }
    }
 
-   /*
-    * Host only consists of dots and digits so
-    * assume that is an IPv4 address.
-    */
-   return 1;
+   return 0;
 
 }