#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>
#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;
SSL_CTX_set_default_verify_paths (ssl_ctx);
SSL_CTX_load_verify_locations (ssl_ctx, opt.ca_cert, opt.ca_directory);
- /* Specify whether the connect should fail if the verification of
- the peer fails or if it should go ahead. */
- SSL_CTX_set_verify (ssl_ctx,
- opt.check_cert ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
+ /* 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,
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)
if (SSL_connect (ssl) <= 0 || ssl->state != SSL_ST_OK)
goto error;
- /* 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 fd_read, fd_write, and
- friends and not care what happens underneath. */
+ /* 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 (("Connected %d to SSL 0x%0*lx\n", fd, 2 * sizeof (void *),
- (unsigned long) ssl));
+ DEBUGP (("Handshake successful; connected socket %d to SSL handle 0x%0*lx\n",
+ fd, PTR_FORMAT (ssl)));
return 1;
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
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." */
+ 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)
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
return *n == '\0';
}
-/* Check that the identity of the remote host, as presented by its
- server certificate, corresponds to HOST, which is the host name the
- user thinks he's connecting to. This assumes that FD has been
- connected to an SSL context using ssl_connect. Return 1 if the
- identity checks out, 0 otherwise.
+/* 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.)
- If opt.check_cert is 0, this always returns 1, but still warns the
- user about the mismatches, if any. */
+ 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_server_identity (int fd, const char *host)
+ssl_check_certificate (int fd, const char *host)
{
- X509 *peer_cert = NULL;
- char peer_CN[256];
+ X509 *cert;
+ char common_name[256];
long vresult;
- int retval;
+ int success = 1;
/* If the user has specified --no-check-cert, we still want to warn
him about problems with the server's certificate. */
SSL *ssl = (SSL *) fd_transport_context (fd);
assert (ssl != NULL);
- peer_cert = SSL_get_peer_certificate (ssl);
- if (!peer_cert)
+ cert = SSL_get_peer_certificate (ssl);
+ if (!cert)
{
logprintf (LOG_NOTQUIET, _("%s: No certificate presented by %s.\n"),
severity, escnonprint (host));
- retval = 0;
- goto out;
+ 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 (peer_cert), 0, 0);
- char *issuer = X509_NAME_oneline (X509_get_issuer_name (peer_cert), 0, 0);
+ 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);
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));
- retval = 0;
- goto out;
+ success = 0;
+ /* Fall through, so that the user is warned about *all* issues
+ with the cert (important with --no-check-certificate.) */
}
- /* Check that the common name in the presented certificate matches
- HOST. This should be improved in the following ways:
+ /* 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
- 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. */
+ 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. */
- peer_CN[0] = '\0';
- X509_NAME_get_text_by_NID (X509_get_subject_name (peer_cert),
- NID_commonName, peer_CN, sizeof (peer_CN));
- if (!pattern_match (peer_CN, 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 (peer_CN), escnonprint (host));
- retval = 0;
- goto out;
+ severity, escnonprint (common_name), escnonprint (host));
+ success = 0;
}
- /* The certificate was found, verified, and matched HOST. */
- retval = 1;
+ if (success)
+ DEBUGP (("X509 certificate successfully verified and matches host %s\n",
+ escnonprint (host)));
+ X509_free (cert);
- out:
- if (peer_cert)
- X509_free (peer_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 ? retval : 1;
+ return opt.check_cert ? success : 1;
}