]> sjero.net Git - wget/blobdiff - src/openssl.c
Fix compiler warnings
[wget] / src / openssl.c
index 71faa0c0b26cda27f61f2335ef56da61b2806a66..b725a065f8336aec2d15a3a923fb624f379e1793 100644 (file)
@@ -1,5 +1,6 @@
 /* SSL support via OpenSSL library.
-   Copyright (C) 2000-2006 Free Software Foundation, Inc.
+   Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
+   2009, 2010, 2011, 2012 Free Software Foundation, Inc.
    Originally contributed by Christian Fraenkel.
 
 This file is part of GNU Wget.
@@ -17,36 +18,38 @@ GNU General Public License for more details.
 You should have received a copy of the GNU General Public License
 along with Wget.  If not, see <http://www.gnu.org/licenses/>.
 
-In addition, as a special exception, the Free Software Foundation
-gives permission to link the code of its release of Wget with the
-OpenSSL project's "OpenSSL" library (or with modified versions of it
-that use the same license as the "OpenSSL" library), and distribute
-the linked executables.  You must obey the GNU General Public License
-in all respects for all of the code used other than "OpenSSL".  If you
-modify this file, you may extend this exception to your version of the
-file, but you are not obligated to do so.  If you do not wish to do
-so, delete this exception statement from your version.  */
+Additional permission under GNU GPL version 3 section 7
 
-#include <config.h>
+If you modify this program, or any covered work, by linking or
+combining it with the OpenSSL project's OpenSSL library (or a
+modified version of that library), containing parts covered by the
+terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
+grants you additional permission to convey the resulting work.
+Corresponding Source for a non-source form of such a combination
+shall include the source code for the parts of OpenSSL used as well
+as that of the covered work.  */
+
+#include "wget.h"
 
 #include <assert.h>
 #include <errno.h>
-#ifdef HAVE_UNISTD_H
-# include <unistd.h>
-#endif
+#include <unistd.h>
 #include <string.h>
 
 #include <openssl/ssl.h>
-#include <openssl/x509.h>
+#include <openssl/x509v3.h>
 #include <openssl/err.h>
 #include <openssl/rand.h>
 
-#include "wget.h"
 #include "utils.h"
 #include "connect.h"
 #include "url.h"
 #include "ssl.h"
 
+#ifdef WINDOWS
+# include <w32sock.h>
+#endif
+
 /* Application-wide SSL context.  This is common to all SSL
    connections.  */
 static SSL_CTX *ssl_ctx;
@@ -123,7 +126,7 @@ init_prng (void)
 /* Print errors in the OpenSSL error stack. */
 
 static void
-print_errors (void) 
+print_errors (void)
 {
   unsigned long err;
   while ((err = ERR_get_error ()) != 0)
@@ -156,9 +159,9 @@ key_type_to_ssl_type (enum keyfile_type type)
    Returns true on success, false otherwise.  */
 
 bool
-ssl_init ()
+ssl_init (void)
 {
-  SSL_METHOD *meth;
+  SSL_METHOD const *meth;
 
   if (ssl_ctx)
     /* The SSL has already been initialized. */
@@ -183,12 +186,15 @@ ssl_init ()
     case secure_protocol_auto:
       meth = SSLv23_client_method ();
       break;
+#ifndef OPENSSL_NO_SSL2
     case secure_protocol_sslv2:
       meth = SSLv2_client_method ();
       break;
+#endif
     case secure_protocol_sslv3:
       meth = SSLv3_client_method ();
       break;
+    case secure_protocol_pfs:
     case secure_protocol_tlsv1:
       meth = TLSv1_client_method ();
       break;
@@ -196,10 +202,18 @@ ssl_init ()
       abort ();
     }
 
-  ssl_ctx = SSL_CTX_new (meth);
+  /* The type cast below accommodates older OpenSSL versions (0.9.8)
+     where SSL_CTX_new() is declared without a "const" argument. */
+  ssl_ctx = SSL_CTX_new ((SSL_METHOD *)meth);
   if (!ssl_ctx)
     goto error;
 
+  /* OpenSSL ciphers: https://www.openssl.org/docs/apps/ciphers.html
+   * Since we want a good protection, we also use HIGH (that excludes MD4 ciphers and some more)
+   */
+  if (opt.secure_protocol == secure_protocol_pfs)
+    SSL_CTX_set_cipher_list (ssl_ctx, "HIGH:MEDIUM:!RC4:!SRP:!PSK:!RSA:!aNULL@STRENGTH");
+
   SSL_CTX_set_default_verify_paths (ssl_ctx);
   SSL_CTX_load_verify_locations (ssl_ctx, opt.ca_cert, opt.ca_directory);
 
@@ -209,6 +223,13 @@ ssl_init ()
      than examining the error stack after a failed SSL_connect.  */
   SSL_CTX_set_verify (ssl_ctx, SSL_VERIFY_NONE, NULL);
 
+  /* Use the private key from the cert file unless otherwise specified. */
+  if (opt.cert_file && !opt.private_key)
+    {
+      opt.private_key = opt.cert_file;
+      opt.private_key_type = opt.cert_type;
+    }
+
   if (opt.cert_file)
     if (SSL_CTX_use_certificate_file (ssl_ctx, opt.cert_file,
                                       key_type_to_ssl_type (opt.cert_type))
@@ -237,27 +258,54 @@ ssl_init ()
   return false;
 }
 
-struct openssl_transport_context {
+struct openssl_transport_context
+{
   SSL *conn;                    /* SSL connection handle */
   char *last_error;             /* last error printed with openssl_errstr */
 };
 
-static int
-openssl_read (int fd, char *buf, int bufsize, void *arg)
+struct openssl_read_args
 {
-  int ret;
-  struct openssl_transport_context *ctx = arg;
+  int fd;
+  struct openssl_transport_context *ctx;
+  char *buf;
+  int bufsize;
+  int retval;
+};
+
+static void openssl_read_callback(void *arg)
+{
+  struct openssl_read_args *args = (struct openssl_read_args *) arg;
+  struct openssl_transport_context *ctx = args->ctx;
   SSL *conn = ctx->conn;
+  char *buf = args->buf;
+  int bufsize = args->bufsize;
+  int ret;
+
   do
     ret = SSL_read (conn, buf, bufsize);
-  while (ret == -1
-         && SSL_get_error (conn, ret) == SSL_ERROR_SYSCALL
+  while (ret == -1 && SSL_get_error (conn, ret) == SSL_ERROR_SYSCALL
          && errno == EINTR);
-  return ret;
+  args->retval = ret;
+}
+
+static int
+openssl_read (int fd, char *buf, int bufsize, void *arg)
+{
+  struct openssl_read_args args;
+  args.fd = fd;
+  args.buf = buf;
+  args.bufsize = bufsize;
+  args.ctx = (struct openssl_transport_context*) arg;
+
+  if (run_with_timeout(opt.read_timeout, openssl_read_callback, &args)) {
+    return -1;
+  }
+  return args.retval;
 }
 
 static int
-openssl_write (int fd, char *buf, int bufsize, void *arg)
+openssl_write (int fd _GL_UNUSED, char *buf, int bufsize, void *arg)
 {
   int ret = 0;
   struct openssl_transport_context *ctx = arg;
@@ -275,10 +323,10 @@ openssl_poll (int fd, double timeout, int wait_for, void *arg)
 {
   struct openssl_transport_context *ctx = arg;
   SSL *conn = ctx->conn;
-  if (timeout == 0)
-    return 1;
   if (SSL_pending (conn))
     return 1;
+  if (timeout == 0)
+    return 1;
   return select_fd (fd, timeout, wait_for);
 }
 
@@ -288,6 +336,8 @@ openssl_peek (int fd, char *buf, int bufsize, void *arg)
   int ret;
   struct openssl_transport_context *ctx = arg;
   SSL *conn = ctx->conn;
+  if (! openssl_poll (fd, 0.0, WAIT_FOR_READ, arg))
+    return 0;
   do
     ret = SSL_peek (conn, buf, bufsize);
   while (ret == -1
@@ -297,7 +347,7 @@ openssl_peek (int fd, char *buf, int bufsize, void *arg)
 }
 
 static const char *
-openssl_errstr (int fd, void *arg)
+openssl_errstr (int fd _GL_UNUSED, void *arg)
 {
   struct openssl_transport_context *ctx = arg;
   unsigned long errcode;
@@ -356,11 +406,7 @@ openssl_close (int fd, void *arg)
   xfree_null (ctx->last_error);
   xfree (ctx);
 
-#ifdef WINDOWS
-  closesocket (fd);
-#else
   close (fd);
-#endif
 
   DEBUGP (("Closed %d/SSL 0x%0*lx\n", fd, PTR_FORMAT (conn)));
 }
@@ -373,6 +419,19 @@ static struct transport_implementation openssl_transport = {
   openssl_peek, openssl_errstr, openssl_close
 };
 
+struct scwt_context
+{
+  SSL *ssl;
+  int result;
+};
+
+static void
+ssl_connect_with_timeout_callback(void *arg)
+{
+  struct scwt_context *ctx = (struct scwt_context *)arg;
+  ctx->result = SSL_connect(ctx->ssl);
+}
+
 /* Perform the SSL handshake on file descriptor FD, which is assumed
    to be connected to an SSL server.  The SSL handle provided by
    OpenSSL is registered with the file descriptor FD using
@@ -382,9 +441,10 @@ static struct transport_implementation openssl_transport = {
    Returns true on success, false on failure.  */
 
 bool
-ssl_connect (int fd) 
+ssl_connect_wget (int fd, const char *hostname)
 {
   SSL *conn;
+  struct scwt_context scwt_ctx;
   struct openssl_transport_context *ctx;
 
   DEBUGP (("Initiating SSL handshake.\n"));
@@ -393,10 +453,33 @@ ssl_connect (int fd)
   conn = SSL_new (ssl_ctx);
   if (!conn)
     goto error;
-  if (!SSL_set_fd (conn, fd))
+#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
+  /* If the SSL library was build with support for ServerNameIndication
+     then use it whenever we have a hostname.  If not, don't, ever. */
+  if (! is_valid_ip_address (hostname))
+    {
+      if (! SSL_set_tlsext_host_name (conn, hostname))
+       {
+       DEBUGP (("Failed to set TLS server-name indication."));
+       goto error;
+       }
+    }
+#endif
+
+#ifndef FD_TO_SOCKET
+# define FD_TO_SOCKET(X) (X)
+#endif
+  if (!SSL_set_fd (conn, FD_TO_SOCKET (fd)))
     goto error;
   SSL_set_connect_state (conn);
-  if (SSL_connect (conn) <= 0 || conn->state != SSL_ST_OK)
+
+  scwt_ctx.ssl = conn;
+  if (run_with_timeout(opt.read_timeout, ssl_connect_with_timeout_callback,
+                       &scwt_ctx)) {
+    DEBUGP (("SSL handshake timed out.\n"));
+    goto timeout;
+  }
+  if (scwt_ctx.result <= 0 || conn->state != SSL_ST_OK)
     goto error;
 
   ctx = xnew0 (struct openssl_transport_context);
@@ -412,6 +495,7 @@ ssl_connect (int fd)
  error:
   DEBUGP (("SSL handshake failed.\n"));
   print_errors ();
+ timeout:
   if (conn)
     SSL_free (conn);
   return false;
@@ -438,13 +522,13 @@ pattern_match (const char *pattern, const char *string)
 {
   const char *p = pattern, *n = string;
   char c;
-  for (; (c = TOLOWER (*p++)) != '\0'; n++)
+  for (; (c = c_tolower (*p++)) != '\0'; n++)
     if (c == '*')
       {
-        for (c = TOLOWER (*p); c == '*'; c = TOLOWER (*++p))
+        for (c = c_tolower (*p); c == '*'; c = c_tolower (*++p))
           ;
         for (; *n != '\0'; n++)
-          if (TOLOWER (*n) == c && pattern_match (p, n))
+          if (c_tolower (*n) == c && pattern_match (p, n))
             return true;
 #ifdef ASTERISK_EXCLUDES_DOT
           else if (*n == '.')
@@ -454,7 +538,7 @@ pattern_match (const char *pattern, const char *string)
       }
     else
       {
-        if (c != TOLOWER (*n))
+        if (c != c_tolower (*n))
           return false;
       }
   return *n == '\0';
@@ -465,7 +549,7 @@ pattern_match (const char *pattern, const char *string)
    its certificate, corresponds to HOST.  (HOST typically comes from
    the URL and is what the user thinks he's connecting to.)
 
-   This assumes that ssl_connect has successfully finished, i.e. that
+   This assumes that ssl_connect_wget has successfully finished, i.e. that
    the SSL handshake has been performed and that FD is connected to an
    SSL handle.
 
@@ -478,9 +562,11 @@ bool
 ssl_check_certificate (int fd, const char *host)
 {
   X509 *cert;
+  GENERAL_NAMES *subjectAltNames;
   char common_name[256];
   long vresult;
   bool success = true;
+  bool alt_name_checked = false;
 
   /* If the user has specified --no-check-cert, we still want to warn
      him about problems with the server's certificate.  */
@@ -494,7 +580,7 @@ ssl_check_certificate (int fd, const char *host)
   if (!cert)
     {
       logprintf (LOG_NOTQUIET, _("%s: No certificate presented by %s.\n"),
-                 severity, escnonprint (host));
+                 severity, quotearg_style (escape_quoting_style, host));
       success = false;
       goto no_cert;             /* must bail out since CERT is NULL */
     }
@@ -504,7 +590,8 @@ ssl_check_certificate (int fd, const char *host)
       char *subject = X509_NAME_oneline (X509_get_subject_name (cert), 0, 0);
       char *issuer = X509_NAME_oneline (X509_get_issuer_name (cert), 0, 0);
       DEBUGP (("certificate:\n  subject: %s\n  issuer:  %s\n",
-               escnonprint (subject), escnonprint (issuer)));
+               quotearg_n_style (0, escape_quoting_style, subject),
+               quotearg_n_style (1, escape_quoting_style, issuer)));
       OPENSSL_free (subject);
       OPENSSL_free (issuer);
     }
@@ -514,8 +601,9 @@ ssl_check_certificate (int fd, const char *host)
     {
       char *issuer = X509_NAME_oneline (X509_get_issuer_name (cert), 0, 0);
       logprintf (LOG_NOTQUIET,
-                 _("%s: cannot verify %s's certificate, issued by `%s':\n"),
-                 severity, escnonprint (host), escnonprint (issuer));
+                 _("%s: cannot verify %s's certificate, issued by %s:\n"),
+                 severity, quotearg_n_style (0, escape_quoting_style, host),
+                 quote_n (1, issuer));
       /* Try to print more user-friendly (and translated) messages for
          the frequent verification errors.  */
       switch (vresult)
@@ -526,7 +614,8 @@ ssl_check_certificate (int fd, const char *host)
           break;
         case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
         case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
-          logprintf (LOG_NOTQUIET, _("  Self-signed certificate encountered.\n"));
+          logprintf (LOG_NOTQUIET,
+                     _("  Self-signed certificate encountered.\n"));
           break;
         case X509_V_ERR_CERT_NOT_YET_VALID:
           logprintf (LOG_NOTQUIET, _("  Issued certificate not yet valid.\n"));
@@ -548,10 +637,6 @@ ssl_check_certificate (int fd, const char *host)
   /* Check that HOST matches the common name in the certificate.
      #### The following remains to be done:
 
-     - It should use dNSName/ipAddress subjectAltName extensions if
-       available; according to rfc2818: "If a subjectAltName extension
-       of type dNSName is present, that MUST be used as the identity."
-
      - When matching against common names, it should loop over all
        common names and choose the most specific one, i.e. the last
        one, not the first one, which the current code picks.
@@ -559,28 +644,143 @@ ssl_check_certificate (int fd, const char *host)
      - Ensure that ASN1 strings from the certificate are encoded as
        UTF-8 which can be meaningfully compared to HOST.  */
 
-  common_name[0] = '\0';
-  X509_NAME_get_text_by_NID (X509_get_subject_name (cert),
-                             NID_commonName, common_name, sizeof (common_name));
-  if (!pattern_match (common_name, host))
+  subjectAltNames = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL);
+
+  if (subjectAltNames)
     {
-      logprintf (LOG_NOTQUIET, _("\
-%s: certificate common name `%s' doesn't match requested host name `%s'.\n"),
-                 severity, escnonprint (common_name), escnonprint (host));
-      success = false;
+      /* Test subject alternative names */
+
+      /* Do we want to check for dNSNAmes or ipAddresses (see RFC 2818)?
+       * Signal it by host_in_octet_string. */
+      ASN1_OCTET_STRING *host_in_octet_string = a2i_IPADDRESS (host);
+
+      int numaltnames = sk_GENERAL_NAME_num (subjectAltNames);
+      int i;
+      for (i=0; i < numaltnames; i++)
+        {
+          const GENERAL_NAME *name =
+            sk_GENERAL_NAME_value (subjectAltNames, i);
+          if (name)
+            {
+              if (host_in_octet_string)
+                {
+                  if (name->type == GEN_IPADD)
+                    {
+                      /* Check for ipAddress */
+                      /* TODO: Should we convert between IPv4-mapped IPv6
+                       * addresses and IPv4 addresses? */
+                      alt_name_checked = true;
+                      if (!ASN1_STRING_cmp (host_in_octet_string,
+                            name->d.iPAddress))
+                        break;
+                    }
+                }
+              else if (name->type == GEN_DNS)
+                {
+                  /* dNSName should be IA5String (i.e. ASCII), however who
+                   * does trust CA? Convert it into UTF-8 for sure. */
+                  unsigned char *name_in_utf8 = NULL;
+
+                  /* Check for dNSName */
+                  alt_name_checked = true;
+
+                  if (0 <= ASN1_STRING_to_UTF8 (&name_in_utf8, name->d.dNSName))
+                    {
+                      /* Compare and check for NULL attack in ASN1_STRING */
+                      if (pattern_match ((char *)name_in_utf8, host) &&
+                            (strlen ((char *)name_in_utf8) ==
+                                (size_t) ASN1_STRING_length (name->d.dNSName)))
+                        {
+                          OPENSSL_free (name_in_utf8);
+                          break;
+                        }
+                      OPENSSL_free (name_in_utf8);
+                    }
+                }
+            }
+        }
+      sk_GENERAL_NAME_free (subjectAltNames);
+      if (host_in_octet_string)
+        ASN1_OCTET_STRING_free(host_in_octet_string);
+
+      if (alt_name_checked == true && i >= numaltnames)
+        {
+          logprintf (LOG_NOTQUIET,
+              _("%s: no certificate subject alternative name matches\n"
+                "\trequested host name %s.\n"),
+                     severity, quote_n (1, host));
+          success = false;
+        }
     }
+  
+  if (alt_name_checked == false)
+    {
+      /* Test commomName */
+      X509_NAME *xname = X509_get_subject_name(cert);
+      common_name[0] = '\0';
+      X509_NAME_get_text_by_NID (xname, NID_commonName, common_name,
+                                 sizeof (common_name));
+
+      if (!pattern_match (common_name, host))
+        {
+          logprintf (LOG_NOTQUIET, _("\
+    %s: certificate common name %s doesn't match requested host name %s.\n"),
+                     severity, quote_n (0, common_name), quote_n (1, host));
+          success = false;
+        }
+      else
+        {
+          /* We now determine the length of the ASN1 string. If it
+           * differs from common_name's length, then there is a \0
+           * before the string terminates.  This can be an instance of a
+           * null-prefix attack.
+           *
+           * https://www.blackhat.com/html/bh-usa-09/bh-usa-09-archives.html#Marlinspike
+           * */
+
+          int i = -1, j;
+          X509_NAME_ENTRY *xentry;
+          ASN1_STRING *sdata;
+
+          if (xname) {
+            for (;;)
+              {
+                j = X509_NAME_get_index_by_NID (xname, NID_commonName, i);
+                if (j == -1) break;
+                i = j;
+              }
+          }
+
+          xentry = X509_NAME_get_entry(xname,i);
+          sdata = X509_NAME_ENTRY_get_data(xentry);
+          if (strlen (common_name) != (size_t) ASN1_STRING_length (sdata))
+            {
+              logprintf (LOG_NOTQUIET, _("\
+    %s: certificate common name is invalid (contains a NUL character).\n\
+    This may be an indication that the host is not who it claims to be\n\
+    (that is, it is not the real %s).\n"),
+                         severity, quote (host));
+              success = false;
+            }
+        }
+    }
+
 
   if (success)
     DEBUGP (("X509 certificate successfully verified and matches host %s\n",
-             escnonprint (host)));
+             quotearg_style (escape_quoting_style, host)));
   X509_free (cert);
 
  no_cert:
   if (opt.check_cert && !success)
     logprintf (LOG_NOTQUIET, _("\
 To connect to %s insecurely, use `--no-check-certificate'.\n"),
-               escnonprint (host));
+               quotearg_style (escape_quoting_style, host));
 
   /* Allow --no-check-cert to disable certificate checking. */
   return opt.check_cert ? success : true;
 }
+
+/*
+ * vim: tabstop=2 shiftwidth=2 softtabstop=2
+ */