]> sjero.net Git - wget/blobdiff - src/openssl.c
[svn] Merge of fix for bugs 20341 and 20410.
[wget] / src / openssl.c
index 93f00fc218ae7485379b6bf5f8b78c92820dd03c..df4a938d8be488c6f6014efd8d7891586e1c8ff2 100644 (file)
@@ -1,12 +1,12 @@
 /* SSL support via OpenSSL library.
-   Copyright (C) 2000-2005 Free Software Foundation, Inc.
+   Copyright (C) 2000-2006 Free Software Foundation, Inc.
    Originally contributed by Christian Fraenkel.
 
 This file is part of GNU Wget.
 
 GNU Wget is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
+the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version.
 
 GNU Wget is distributed in the hope that it will be useful,
@@ -15,8 +15,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
-along with Wget; if not, write to the Free Software
-Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+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
@@ -35,11 +34,7 @@ so, delete this exception statement from your version.  */
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif
-#ifdef HAVE_STRING_H
-# include <string.h>
-#else
-# include <strings.h>
-#endif
+#include <string.h>
 
 #include <openssl/ssl.h>
 #include <openssl/x509.h>
@@ -52,13 +47,9 @@ so, delete this exception statement from your version.  */
 #include "url.h"
 #include "ssl.h"
 
-#ifndef errno
-extern int errno;
-#endif
-
 /* Application-wide SSL context.  This is common to all SSL
    connections.  */
-SSL_CTX *ssl_ctx;
+static SSL_CTX *ssl_ctx;
 
 /* Initialize the SSL's PRNG using various methods. */
 
@@ -134,9 +125,9 @@ init_prng (void)
 static void
 print_errors (void) 
 {
-  unsigned long curerr = 0;
-  while ((curerr = ERR_get_error ()) != 0)
-    logprintf (LOG_NOTQUIET, "OpenSSL: %s\n", ERR_error_string (curerr, NULL));
+  unsigned long err;
+  while ((err = ERR_get_error ()) != 0)
+    logprintf (LOG_NOTQUIET, "OpenSSL: %s\n", ERR_error_string (err, NULL));
 }
 
 /* Convert keyfile type as used by options.h to a type as accepted by
@@ -162,16 +153,16 @@ key_type_to_ssl_type (enum keyfile_type type)
 /* Create an SSL Context and set default paths etc.  Called the first
    time an HTTP download is attempted.
 
-   Returns 1 on success, 0 otherwise.  */
+   Returns true on success, false otherwise.  */
 
-int
+bool
 ssl_init ()
 {
   SSL_METHOD *meth;
 
   if (ssl_ctx)
     /* The SSL has already been initialized. */
-    return 1;
+    return true;
 
   /* Init the PRNG.  If that fails, bail out.  */
   init_prng ();
@@ -233,71 +224,137 @@ ssl_init ()
      handles them correctly), allow them in OpenSSL.  */
   SSL_CTX_set_mode (ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
 
-  return 1;
+  /* The OpenSSL library can handle renegotiations automatically, so
+     tell it to do so.  */
+  SSL_CTX_set_mode (ssl_ctx, SSL_MODE_AUTO_RETRY);
+
+  return true;
 
  error:
   if (ssl_ctx)
     SSL_CTX_free (ssl_ctx);
   print_errors ();
-  return 0;
+  return false;
 }
 
+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 *ctx)
+openssl_read (int fd, char *buf, int bufsize, void *arg)
 {
   int ret;
-  SSL *ssl = (SSL *) ctx;
+  struct openssl_transport_context *ctx = arg;
+  SSL *conn = ctx->conn;
   do
-    ret = SSL_read (ssl, buf, bufsize);
+    ret = SSL_read (conn, buf, bufsize);
   while (ret == -1
-        && SSL_get_error (ssl, ret) == SSL_ERROR_SYSCALL
+        && SSL_get_error (conn, ret) == SSL_ERROR_SYSCALL
         && errno == EINTR);
   return ret;
 }
 
 static int
-openssl_write (int fd, char *buf, int bufsize, void *ctx)
+openssl_write (int fd, char *buf, int bufsize, void *arg)
 {
   int ret = 0;
-  SSL *ssl = (SSL *) ctx;
+  struct openssl_transport_context *ctx = arg;
+  SSL *conn = ctx->conn;
   do
-    ret = SSL_write (ssl, buf, bufsize);
+    ret = SSL_write (conn, buf, bufsize);
   while (ret == -1
-        && SSL_get_error (ssl, ret) == SSL_ERROR_SYSCALL
+        && SSL_get_error (conn, ret) == SSL_ERROR_SYSCALL
         && errno == EINTR);
   return ret;
 }
 
 static int
-openssl_poll (int fd, double timeout, int wait_for, void *ctx)
+openssl_poll (int fd, double timeout, int wait_for, void *arg)
 {
-  SSL *ssl = (SSL *) ctx;
+  struct openssl_transport_context *ctx = arg;
+  SSL *conn = ctx->conn;
   if (timeout == 0)
     return 1;
-  if (SSL_pending (ssl))
+  if (SSL_pending (conn))
     return 1;
   return select_fd (fd, timeout, wait_for);
 }
 
 static int
-openssl_peek (int fd, char *buf, int bufsize, void *ctx)
+openssl_peek (int fd, char *buf, int bufsize, void *arg)
 {
   int ret;
-  SSL *ssl = (SSL *) ctx;
+  struct openssl_transport_context *ctx = arg;
+  SSL *conn = ctx->conn;
   do
-    ret = SSL_peek (ssl, buf, bufsize);
+    ret = SSL_peek (conn, buf, bufsize);
   while (ret == -1
-        && SSL_get_error (ssl, ret) == SSL_ERROR_SYSCALL
+        && SSL_get_error (conn, ret) == SSL_ERROR_SYSCALL
         && errno == EINTR);
   return ret;
 }
 
+static const char *
+openssl_errstr (int fd, void *arg)
+{
+  struct openssl_transport_context *ctx = arg;
+  unsigned long errcode;
+  char *errmsg = NULL;
+  int msglen = 0;
+
+  /* If there are no SSL-specific errors, just return NULL. */
+  if ((errcode = ERR_get_error ()) == 0)
+    return NULL;
+
+  /* Get rid of previous contents of ctx->last_error, if any.  */
+  xfree_null (ctx->last_error);
+
+  /* Iterate over OpenSSL's error stack and accumulate errors in the
+     last_error buffer, separated by "; ".  This is better than using
+     a static buffer, which *always* takes up space (and has to be
+     large, to fit more than one error message), whereas these
+     allocations are only performed when there is an actual error.  */
+
+  for (;;)
+    {
+      const char *str = ERR_error_string (errcode, NULL);
+      int len = strlen (str);
+
+      /* Allocate space for the existing message, plus two more chars
+        for the "; " separator and one for the terminating \0.  */
+      errmsg = xrealloc (errmsg, msglen + len + 2 + 1);
+      memcpy (errmsg + msglen, str, len);
+      msglen += len;
+
+      /* Get next error and bail out if there are no more. */
+      errcode = ERR_get_error ();
+      if (errcode == 0)
+       break;
+
+      errmsg[msglen++] = ';';
+      errmsg[msglen++] = ' ';
+    }
+  errmsg[msglen] = '\0';
+
+  /* Store the error in ctx->last_error where openssl_close will
+     eventually find it and free it.  */
+  ctx->last_error = errmsg;
+
+  return errmsg;
+}
+
 static void
-openssl_close (int fd, void *ctx)
+openssl_close (int fd, void *arg)
 {
-  SSL *ssl = (SSL *) ctx;
-  SSL_shutdown (ssl);
-  SSL_free (ssl);
+  struct openssl_transport_context *ctx = arg;
+  SSL *conn = ctx->conn;
+
+  SSL_shutdown (conn);
+  SSL_free (conn);
+  xfree_null (ctx->last_error);
+  xfree (ctx);
 
 #ifdef WINDOWS
   closesocket (fd);
@@ -305,53 +362,64 @@ openssl_close (int fd, void *ctx)
   close (fd);
 #endif
 
-  DEBUGP (("Closed %d/SSL 0x%0lx\n", fd, (unsigned long) ssl));
+  DEBUGP (("Closed %d/SSL 0x%0*lx\n", fd, PTR_FORMAT (conn)));
 }
 
+/* openssl_transport is the singleton that describes the SSL transport
+   methods provided by this file.  */
+
+static struct transport_implementation openssl_transport = {
+  openssl_read, openssl_write, openssl_poll,
+  openssl_peek, openssl_errstr, openssl_close
+};
+
 /* 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
    fd_register_transport, so that subsequent calls to fd_read,
    fd_write, etc., will use the corresponding SSL functions.
 
-   Returns 1 on success, 0 on failure.  */
+   Returns true on success, false on failure.  */
 
-int
+bool
 ssl_connect (int fd) 
 {
-  SSL *ssl;
+  SSL *conn;
+  struct openssl_transport_context *ctx;
 
   DEBUGP (("Initiating SSL handshake.\n"));
 
   assert (ssl_ctx != NULL);
-  ssl = SSL_new (ssl_ctx);
-  if (!ssl)
+  conn = SSL_new (ssl_ctx);
+  if (!conn)
     goto error;
-  if (!SSL_set_fd (ssl, fd))
+  if (!SSL_set_fd (conn, fd))
     goto error;
-  SSL_set_connect_state (ssl);
-  if (SSL_connect (ssl) <= 0 || ssl->state != SSL_ST_OK)
+  SSL_set_connect_state (conn);
+  if (SSL_connect (conn) <= 0 || conn->state != SSL_ST_OK)
     goto error;
 
+  ctx = xnew0 (struct openssl_transport_context);
+  ctx->conn = conn;
+
   /* Register FD with Wget's transport layer, i.e. arrange that our
      functions are used for reading, writing, and polling.  */
-  fd_register_transport (fd, openssl_read, openssl_write, openssl_poll,
-                        openssl_peek, openssl_close, ssl);
+  fd_register_transport (fd, &openssl_transport, ctx);
   DEBUGP (("Handshake successful; connected socket %d to SSL handle 0x%0*lx\n",
-          fd, PTR_FORMAT (ssl)));
-  return 1;
+          fd, PTR_FORMAT (conn)));
+  return true;
 
  error:
   DEBUGP (("SSL handshake failed.\n"));
   print_errors ();
-  if (ssl)
-    SSL_free (ssl);
-  return 0;
+  if (conn)
+    SSL_free (conn);
+  return false;
 }
 
 #define ASTERISK_EXCLUDES_DOT  /* mandated by rfc2818 */
 
-/* Return 1 is STRING (case-insensitively) matches PATTERN, 0
+/* Return true is STRING (case-insensitively) matches PATTERN, false
    otherwise.  The recognized wildcard character is "*", which matches
    any character in STRING except ".".  Any number of the "*" wildcard
    may be present in the pattern.
@@ -365,7 +433,7 @@ ssl_connect (int fd)
    If the pattern contain no wildcards, pattern_match(a, b) is
    equivalent to !strcasecmp(a, b).  */
 
-static int
+static bool
 pattern_match (const char *pattern, const char *string)
 {
   const char *p = pattern, *n = string;
@@ -377,17 +445,17 @@ pattern_match (const char *pattern, const char *string)
          ;
        for (; *n != '\0'; n++)
          if (TOLOWER (*n) == c && pattern_match (p, n))
-           return 1;
+           return true;
 #ifdef ASTERISK_EXCLUDES_DOT
          else if (*n == '.')
-           return 0;
+           return false;
 #endif
        return c == '\0';
       }
     else
       {
        if (c != TOLOWER (*n))
-         return 0;
+         return false;
       }
   return *n == '\0';
 }
@@ -401,37 +469,37 @@ pattern_match (const char *pattern, const char *string)
    the SSL handshake has been performed and that FD is connected to an
    SSL handle.
 
-   If opt.check_cert is non-zero (the default), this returns 1 if the
+   If opt.check_cert is true (the default), this returns 1 if the
    certificate is valid, 0 otherwise.  If opt.check_cert is 0, the
    function always returns 1, but should still be called because it
    warns the user about any problems with the certificate.  */
 
-int
+bool
 ssl_check_certificate (int fd, const char *host)
 {
   X509 *cert;
   char common_name[256];
   long vresult;
-  int success = 1;
+  bool success = true;
 
   /* If the user has specified --no-check-cert, we still want to warn
      him about problems with the server's certificate.  */
   const char *severity = opt.check_cert ? _("ERROR") : _("WARNING");
 
-  SSL *ssl = (SSL *) fd_transport_context (fd);
-  assert (ssl != NULL);
+  struct openssl_transport_context *ctx = fd_transport_context (fd);
+  SSL *conn = ctx->conn;
+  assert (conn != NULL);
 
-  cert = SSL_get_peer_certificate (ssl);
+  cert = SSL_get_peer_certificate (conn);
   if (!cert)
     {
       logprintf (LOG_NOTQUIET, _("%s: No certificate presented by %s.\n"),
                 severity, escnonprint (host));
-      success = 0;
-      goto out;                        /* must bail out since CERT is NULL */
+      success = false;
+      goto no_cert;            /* must bail out since CERT is NULL */
     }
 
-#ifdef ENABLE_DEBUG
-  if (opt.debug)
+  IF_DEBUG
     {
       char *subject = X509_NAME_oneline (X509_get_subject_name (cert), 0, 0);
       char *issuer = X509_NAME_oneline (X509_get_issuer_name (cert), 0, 0);
@@ -440,25 +508,39 @@ ssl_check_certificate (int fd, const char *host)
       OPENSSL_free (subject);
       OPENSSL_free (issuer);
     }
-#endif
 
-  vresult = SSL_get_verify_result (ssl);
+  vresult = SSL_get_verify_result (conn);
   if (vresult != X509_V_OK)
     {
-      /* #### We might want to print saner (and translatable) error
-        messages for several frequently encountered errors.  The
-        candidates would include
-        X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
-        X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,
-        X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
-        X509_V_ERR_CERT_NOT_YET_VALID, X509_V_ERR_CERT_HAS_EXPIRED,
-        and possibly others.  The current approach would still be
-        used for the less frequent failure cases.  */
+      char *issuer = X509_NAME_oneline (X509_get_issuer_name (cert), 0, 0);
       logprintf (LOG_NOTQUIET,
-                _("%s: Certificate verification error for %s: %s\n"),
-                severity, escnonprint (host),
-                X509_verify_cert_error_string (vresult));
-      success = 0;
+                _("%s: cannot verify %s's certificate, issued by `%s':\n"),
+                severity, escnonprint (host), escnonprint (issuer));
+      /* Try to print more user-friendly (and translated) messages for
+        the frequent verification errors.  */
+      switch (vresult)
+       {
+       case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+         logprintf (LOG_NOTQUIET,
+                    _("  Unable to locally verify the issuer's authority.\n"));
+         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"));
+         break;
+       case X509_V_ERR_CERT_NOT_YET_VALID:
+         logprintf (LOG_NOTQUIET, _("  Issued certificate not yet valid.\n"));
+         break;
+       case X509_V_ERR_CERT_HAS_EXPIRED:
+         logprintf (LOG_NOTQUIET, _("  Issued certificate has expired.\n"));
+         break;
+       default:
+         /* For the less frequent error strings, simply provide the
+            OpenSSL error message.  */
+         logprintf (LOG_NOTQUIET, "  %s\n",
+                    X509_verify_cert_error_string (vresult));
+       }
+      success = false;
       /* Fall through, so that the user is warned about *all* issues
         with the cert (important with --no-check-certificate.)  */
     }
@@ -474,8 +556,8 @@ ssl_check_certificate (int fd, const char *host)
        common names and choose the most specific one, i.e. the last
        one, not the first one, which the current code picks.
 
-     - Make sure that the names are encoded as UTF-8 which, being
-       ASCII-compatible, can be easily compared against 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),
@@ -485,22 +567,20 @@ ssl_check_certificate (int fd, const char *host)
       logprintf (LOG_NOTQUIET, _("\
 %s: certificate common name `%s' doesn't match requested host name `%s'.\n"),
                 severity, escnonprint (common_name), escnonprint (host));
-      success = 0;
+      success = false;
     }
 
   if (success)
     DEBUGP (("X509 certificate successfully verified and matches host %s\n",
             escnonprint (host)));
+  X509_free (cert);
 
- out:
-  if (cert)
-    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));
 
   /* Allow --no-check-cert to disable certificate checking. */
-  return opt.check_cert ? success : 1;
+  return opt.check_cert ? success : true;
 }