#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/bio.h>
-#include <openssl/crypto.h>
-#include <openssl/x509.h>
#include <openssl/ssl.h>
+#include <openssl/x509.h>
#include <openssl/err.h>
-#include <openssl/pem.h>
#include <openssl/rand.h>
#include "wget.h"
#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;
+/* Initialize the SSL's PRNG using various methods. */
+
static void
-ssl_init_prng (void)
+init_prng (void)
{
- /* It is likely that older versions of OpenSSL will fail on
- non-Linux machines because this code is unable to seed the PRNG
- on older versions of the library. */
+ char namebuf[256];
+ const char *random_file;
-#if SSLEAY_VERSION_NUMBER >= 0x00905100
- char rand_file[256];
+ if (RAND_status ())
+ /* The PRNG has been seeded; no further action is necessary. */
+ return;
- /* First, seed from a file specified by the user. This will be
- $RANDFILE, if set, or ~/.rnd. */
- RAND_file_name (rand_file, sizeof (rand_file));
- if (rand_file)
- /* Seed at most 16k (value borrowed from curl) from random file. */
- RAND_load_file (rand_file, 16384);
+ /* Seed from a file specified by the user. This will be the file
+ specified with --random-file, $RANDFILE, if set, or ~/.rnd, if it
+ exists. */
+ if (opt.random_file)
+ random_file = opt.random_file;
+ else
+ {
+ /* Get the random file name using RAND_file_name. */
+ namebuf[0] = '\0';
+ random_file = RAND_file_name (namebuf, sizeof (namebuf));
+ }
+
+ if (random_file && *random_file)
+ /* Seed at most 16k (apparently arbitrary value borrowed from
+ curl) from random file. */
+ RAND_load_file (random_file, 16384);
if (RAND_status ())
return;
- /* Get random data from EGD if opt.egd_file was set. */
+ /* Get random data from EGD if opt.egd_file was used. */
if (opt.egd_file && *opt.egd_file)
RAND_egd (opt.egd_file);
PRNG. This is cryptographically weak and defeats the purpose
of using OpenSSL, which is why it is highly discouraged. */
- logprintf (LOG_VERBOSE, _("WARNING: using a weak random seed.\n"));
+ logprintf (LOG_NOTQUIET, _("WARNING: using a weak random seed.\n"));
while (RAND_status () == 0 && maxrand-- > 0)
{
}
}
#endif
+}
+
+/* Print errors in the OpenSSL error stack. */
-#endif /* SSLEAY_VERSION_NUMBER >= 0x00905100 */
+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));
}
+/* Convert keyfile type as used by options.h to a type as accepted by
+ SSL_CTX_use_certificate_file and SSL_CTX_use_PrivateKey_file.
+
+ (options.h intentionally doesn't use values from openssl/ssl.h so
+ it doesn't depend specifically on OpenSSL for SSL functionality.) */
+
static int
-verify_callback (int ok, X509_STORE_CTX *ctx)
+key_type_to_ssl_type (enum keyfile_type type)
{
- char *s, buf[256];
- s = X509_NAME_oneline (X509_get_subject_name (ctx->current_cert),
- buf, sizeof (buf));
- if (ok == 0)
+ switch (type)
{
- switch (ctx->error)
- {
- case X509_V_ERR_CERT_NOT_YET_VALID:
- case X509_V_ERR_CERT_HAS_EXPIRED:
- /* This mean the CERT is not valid !!! */
- ok = 0;
- break;
- case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
- /* Unsure if we should handle that this way */
- ok = 1;
- break;
- }
+ case keyfile_pem:
+ return SSL_FILETYPE_PEM;
+ case keyfile_asn1:
+ return SSL_FILETYPE_ASN1;
+ default:
+ abort ();
}
- return ok;
}
-/* Print SSL errors. */
+/* Create an SSL Context and set default paths etc. Called the first
+ time an HTTP download is attempted.
-static void
-ssl_print_errors (void)
-{
- unsigned long curerr = 0;
- while ((curerr = ERR_get_error ()) != 0)
- logprintf (LOG_NOTQUIET, "OpenSSL: %s\n", ERR_error_string (curerr, NULL));
-}
+ Returns 1 on success, 0 otherwise. */
-/* Creates a SSL Context and sets some defaults for it */
-uerr_t
+int
ssl_init ()
{
- SSL_METHOD *meth = NULL;
+ SSL_METHOD *meth;
if (ssl_ctx)
- return 0;
+ /* The SSL has already been initialized. */
+ return 1;
/* Init the PRNG. If that fails, bail out. */
- ssl_init_prng ();
- if (RAND_status () == 0)
+ init_prng ();
+ if (RAND_status () != 1)
{
logprintf (LOG_NOTQUIET,
- _("Could not seed OpenSSL PRNG; disabling SSL.\n"));
- scheme_disable (SCHEME_HTTPS);
- return SSLERRCTXCREATE;
+ _("Could not seed PRNG; consider using --random-file.\n"));
+ goto error;
}
SSL_library_init ();
SSL_load_error_strings ();
SSLeay_add_all_algorithms ();
SSLeay_add_ssl_algorithms ();
+
switch (opt.secure_protocol)
{
case secure_protocol_auto:
case secure_protocol_tlsv1:
meth = TLSv1_client_method ();
break;
- }
- if (meth == NULL)
- {
- ssl_print_errors ();
- return SSLERRCTXCREATE;
+ default:
+ abort ();
}
ssl_ctx = SSL_CTX_new (meth);
- if (meth == NULL)
- {
- ssl_print_errors ();
- return SSLERRCTXCREATE;
- }
+ if (!ssl_ctx)
+ goto error;
SSL_CTX_set_default_verify_paths (ssl_ctx);
SSL_CTX_load_verify_locations (ssl_ctx, opt.ca_cert, opt.ca_directory);
- SSL_CTX_set_verify (ssl_ctx,
- opt.check_cert ? SSL_VERIFY_PEER : SSL_VERIFY_NONE,
- verify_callback);
- if (opt.cert_file != NULL || opt.cert_key != NULL)
- {
- int ssl_cert_type = SSL_FILETYPE_PEM;
- switch (opt.cert_type)
- {
- case cert_type_pem:
- ssl_cert_type = SSL_FILETYPE_PEM;
- break;
- case cert_type_asn1:
- ssl_cert_type = SSL_FILETYPE_ASN1;
- break;
- }
-
-#if 0 /* what was this supposed to achieve? */
- if (opt.cert_key == NULL)
- opt.cert_key = opt.cert_file;
- if (opt.cert_file == NULL)
- opt.cert_file = opt.cert_key;
-#endif
+ /* SSL_VERIFY_NONE instructs OpenSSL not to abort SSL_connect if the
+ certificate is invalid. We verify the certificate separately in
+ ssl_check_certificate, which provides much better diagnostics
+ than examining the error stack after a failed SSL_connect. */
+ SSL_CTX_set_verify (ssl_ctx, SSL_VERIFY_NONE, NULL);
+
+ if (opt.cert_file)
+ if (SSL_CTX_use_certificate_file (ssl_ctx, opt.cert_file,
+ key_type_to_ssl_type (opt.cert_type))
+ != 1)
+ goto error;
+ if (opt.private_key)
+ if (SSL_CTX_use_PrivateKey_file (ssl_ctx, opt.private_key,
+ key_type_to_ssl_type (opt.private_key_type))
+ != 1)
+ goto error;
+
+ /* Since fd_write unconditionally assumes partial writes (and
+ handles them correctly), allow them in OpenSSL. */
+ SSL_CTX_set_mode (ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
- if (SSL_CTX_use_certificate_file (ssl_ctx, opt.cert_file,
- ssl_cert_type) != 1)
- {
- ssl_print_errors ();
- return SSLERRCERTFILE;
- }
- if (SSL_CTX_use_PrivateKey_file (ssl_ctx, opt.cert_key,
- ssl_cert_type) != 1)
- {
- ssl_print_errors ();
- return SSLERRCERTKEY;
- }
- }
+ return 1;
- return 0; /* Succeded */
+ error:
+ if (ssl_ctx)
+ SSL_CTX_free (ssl_ctx);
+ print_errors ();
+ return 0;
}
static int
-ssl_read (int fd, char *buf, int bufsize, void *ctx)
+openssl_read (int fd, char *buf, int bufsize, void *ctx)
{
int ret;
SSL *ssl = (SSL *) ctx;
}
static int
-ssl_write (int fd, char *buf, int bufsize, void *ctx)
+openssl_write (int fd, char *buf, int bufsize, void *ctx)
{
int ret = 0;
SSL *ssl = (SSL *) ctx;
}
static int
-ssl_poll (int fd, double timeout, int wait_for, void *ctx)
+openssl_poll (int fd, double timeout, int wait_for, void *ctx)
{
SSL *ssl = (SSL *) ctx;
if (timeout == 0)
}
static int
-ssl_peek (int fd, char *buf, int bufsize, void *ctx)
+openssl_peek (int fd, char *buf, int bufsize, void *ctx)
{
int ret;
SSL *ssl = (SSL *) ctx;
}
static void
-ssl_close (int fd, void *ctx)
+openssl_close (int fd, void *ctx)
{
SSL *ssl = (SSL *) ctx;
SSL_shutdown (ssl);
DEBUGP (("Closed %d/SSL 0x%0lx\n", fd, (unsigned long) ssl));
}
-/* Sets up a SSL structure and performs the handshake on fd. The
- resulting SSL structure is registered with the file descriptor FD
- using fd_register_transport. That way subsequent calls to xread,
- xwrite, etc., will use the appropriate SSL functions.
+/* 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. */
{
SSL *ssl;
+ DEBUGP (("Initiating SSL handshake.\n"));
+
assert (ssl_ctx != NULL);
ssl = SSL_new (ssl_ctx);
if (!ssl)
- goto err;
+ goto error;
if (!SSL_set_fd (ssl, fd))
- goto err;
+ goto error;
SSL_set_connect_state (ssl);
if (SSL_connect (ssl) <= 0 || ssl->state != SSL_ST_OK)
- goto err;
-
- /* Register FD with Wget's transport layer, i.e. arrange that
- SSL-enabled functions are used for reading, writing, and polling.
- That way the rest of Wget can keep using xread, xwrite, and
- friends and not care what happens underneath. */
- fd_register_transport (fd, ssl_read, ssl_write, ssl_poll, ssl_peek,
- ssl_close, ssl);
- DEBUGP (("Connected %d to SSL 0x%0*lx\n", fd, 2 * sizeof (void *),
- (unsigned long) ssl));
+ goto error;
+
+ /* 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);
+ DEBUGP (("Handshake successful; connected socket %d to SSL handle 0x%0*lx\n",
+ fd, PTR_FORMAT (ssl)));
return 1;
- err:
- ssl_print_errors ();
+ error:
+ DEBUGP (("SSL handshake failed.\n"));
+ print_errors ();
if (ssl)
SSL_free (ssl);
return 0;
}
+
+#define ASTERISK_EXCLUDES_DOT /* mandated by rfc2818 */
+
+/* Return 1 is STRING (case-insensitively) matches PATTERN, 0
+ otherwise. The recognized wildcard character is "*", which matches
+ any character in STRING except ".". Any number of the "*" wildcard
+ may be present in the pattern.
+
+ This is used to match of hosts as indicated in rfc2818: "Names may
+ contain the wildcard character * which is considered to match any
+ single domain name component or component fragment. E.g., *.a.com
+ matches foo.a.com but not bar.foo.a.com. f*.com matches foo.com but
+ not bar.com [or foo.bar.com]."
+
+ If the pattern contain no wildcards, pattern_match(a, b) is
+ equivalent to !strcasecmp(a, b). */
+
+static int
+pattern_match (const char *pattern, const char *string)
+{
+ const char *p = pattern, *n = string;
+ char c;
+ for (; (c = TOLOWER (*p++)) != '\0'; n++)
+ if (c == '*')
+ {
+ for (c = TOLOWER (*p); c == '*'; c = TOLOWER (*++p))
+ ;
+ for (; *n != '\0'; n++)
+ if (TOLOWER (*n) == c && pattern_match (p, n))
+ return 1;
+#ifdef ASTERISK_EXCLUDES_DOT
+ else if (*n == '.')
+ return 0;
+#endif
+ return c == '\0';
+ }
+ else
+ {
+ if (c != TOLOWER (*n))
+ return 0;
+ }
+ return *n == '\0';
+}
+
+/* Verify the validity of the certificate presented by the server.
+ Also check that the "common name" of the server, as presented by
+ 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
+ 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
+ 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
+ssl_check_certificate (int fd, const char *host)
+{
+ X509 *cert;
+ char common_name[256];
+ long vresult;
+ int success = 1;
+
+ /* 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);
+
+ cert = SSL_get_peer_certificate (ssl);
+ if (!cert)
+ {
+ logprintf (LOG_NOTQUIET, _("%s: No certificate presented by %s.\n"),
+ severity, escnonprint (host));
+ success = 0;
+ goto no_cert; /* must bail out since CERT is NULL */
+ }
+
+#ifdef ENABLE_DEBUG
+ if (opt.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);
+ DEBUGP (("certificate:\n subject: %s\n issuer: %s\n",
+ escnonprint (subject), escnonprint (issuer)));
+ OPENSSL_free (subject);
+ OPENSSL_free (issuer);
+ }
+#endif
+
+ vresult = SSL_get_verify_result (ssl);
+ 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. */
+ logprintf (LOG_NOTQUIET,
+ _("%s: Certificate verification error for %s: %s\n"),
+ severity, escnonprint (host),
+ X509_verify_cert_error_string (vresult));
+ success = 0;
+ /* Fall through, so that the user is warned about *all* issues
+ with the cert (important with --no-check-certificate.) */
+ }
+
+ /* 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.
+
+ - 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))
+ {
+ logprintf (LOG_NOTQUIET, _("\
+%s: certificate common name `%s' doesn't match requested host name `%s'.\n"),
+ severity, escnonprint (common_name), escnonprint (host));
+ success = 0;
+ }
+
+ if (success)
+ DEBUGP (("X509 certificate successfully verified and matches host %s\n",
+ escnonprint (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));
+
+ /* Allow --no-check-cert to disable certificate checking. */
+ return opt.check_cert ? success : 1;
+}