C++程序  |  281行  |  6.76 KB

/*
    This file is part of libmicrospdy
    Copyright Copyright (C) 2012 Andrey Uzunov

    This program 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 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * @file io_openssl.c
 * @brief  TLS handling using libssl. The current code assumes that
 * 			blocking I/O is in use.
 * @author Andrey Uzunov
 */

#include "platform.h"
#include "internal.h"
#include "session.h"
#include "io_openssl.h"


/**
 * Callback to advertise spdy ver. 3 in Next Protocol Negotiation
 *
 * @param ssl openssl context for a connection
 * @param out must be set to the raw data that is advertised in NPN
 * @param outlen must be set to size of out
 * @param arg
 * @return SSL_TLSEXT_ERR_OK to do advertising
 */
static int
spdyf_next_protos_advertised_cb (SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg)
{
	(void)ssl;
	(void)arg;
	static unsigned char npn_spdy3[] = {0x06, // length of "spdy/3"
		0x73,0x70,0x64,0x79,0x2f,0x33};// spdy/3

	*out = npn_spdy3;
	*outlen = 7; // total length of npn_spdy3
	return SSL_TLSEXT_ERR_OK;
}


void
SPDYF_openssl_global_init()
{
	//error strings are now not used by the lib
    //SSL_load_error_strings();
    //init libssl
    SSL_library_init(); //always returns 1
    //the table for looking up algos is not used now by the lib
    //OpenSSL_add_all_algorithms();
}


void
SPDYF_openssl_global_deinit()
{
	//if SSL_load_error_strings was called
    //ERR_free_strings();
    //if OpenSSL_add_all_algorithms was called
    //EVP_cleanup();
}


int
SPDYF_openssl_init(struct SPDY_Daemon *daemon)
{
    int options;
    //create ssl context. TLSv1 used
    if(NULL == (daemon->io_context = SSL_CTX_new(TLSv1_server_method())))
    {
		SPDYF_DEBUG("Couldn't create ssl context");
		return SPDY_NO;
        }
	//set options for tls
	//TODO DH is not enabled for easier debugging
    //SSL_CTX_set_options(daemon->io_context, SSL_OP_SINGLE_DH_USE);

    //TODO here session tickets are disabled for easier debuging with
    //wireshark when using Chrome
    // SSL_OP_NO_COMPRESSION disables TLS compression to avoid CRIME attack
    options = SSL_OP_NO_TICKET;
#ifdef SSL_OP_NO_COMPRESSION
    options |= SSL_OP_NO_COMPRESSION;
#elif OPENSSL_VERSION_NUMBER >= 0x00908000L /* workaround for OpenSSL 0.9.8 */
    sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
#endif

    SSL_CTX_set_options(daemon->io_context, options);
    if(1 != SSL_CTX_use_certificate_file(daemon->io_context, daemon->certfile , SSL_FILETYPE_PEM))
    {
		SPDYF_DEBUG("Couldn't load the cert file");
		SSL_CTX_free(daemon->io_context);
		return SPDY_NO;
	}
    if(1 != SSL_CTX_use_PrivateKey_file(daemon->io_context, daemon->keyfile, SSL_FILETYPE_PEM))
    {
		SPDYF_DEBUG("Couldn't load the name file");
		SSL_CTX_free(daemon->io_context);
		return SPDY_NO;
	}
    SSL_CTX_set_next_protos_advertised_cb(daemon->io_context, &spdyf_next_protos_advertised_cb, NULL);
    if (1 != SSL_CTX_set_cipher_list(daemon->io_context, "HIGH"))
    {
		SPDYF_DEBUG("Couldn't set the desired cipher list");
		SSL_CTX_free(daemon->io_context);
		return SPDY_NO;
	}

	return SPDY_YES;
}


void
SPDYF_openssl_deinit(struct SPDY_Daemon *daemon)
{
    SSL_CTX_free(daemon->io_context);
}


int
SPDYF_openssl_new_session(struct SPDY_Session *session)
{
	int ret;

	if(NULL == (session->io_context = SSL_new(session->daemon->io_context)))
    {
		SPDYF_DEBUG("Couldn't create ssl structure");
		return SPDY_NO;
	}
	if(1 != (ret = SSL_set_fd(session->io_context, session->socket_fd)))
    {
		SPDYF_DEBUG("SSL_set_fd %i",ret);
		SSL_free(session->io_context);
		session->io_context = NULL;
		return SPDY_NO;
	}

	//for non-blocking I/O SSL_accept may return -1
	//and this function won't work
	if(1 != (ret = SSL_accept(session->io_context)))
    {
		SPDYF_DEBUG("SSL_accept %i",ret);
		SSL_free(session->io_context);
		session->io_context = NULL;
		return SPDY_NO;
	}
	/* alternatively
	SSL_set_accept_state(session->io_context);
	* may be called and then the negotiation will be done on reading
	*/

	return SPDY_YES;
}


void
SPDYF_openssl_close_session(struct SPDY_Session *session)
{
	//SSL_shutdown sends TLS "close notify" as in TLS standard.
	//The function may fail as it waits for the other party to also close
	//the TLS session. The lib just sends it and will close the socket
	//after that because the browsers don't seem to care much about
	//"close notify"
	SSL_shutdown(session->io_context);

	SSL_free(session->io_context);
}


int
SPDYF_openssl_recv(struct SPDY_Session *session,
				void * buffer,
				size_t size)
{
	int ret;
	int n = SSL_read(session->io_context,
					buffer,
					size);
	//if(n > 0) SPDYF_DEBUG("recvd: %i",n);
	if (n <= 0)
	{
		ret = SSL_get_error(session->io_context, n);
		switch(ret)
		{
			case SSL_ERROR_ZERO_RETURN:
				return 0;

			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_WRITE:
				return SPDY_IO_ERROR_AGAIN;

			case SSL_ERROR_SYSCALL:
				if(EINTR == errno)
					return SPDY_IO_ERROR_AGAIN;
				return SPDY_IO_ERROR_ERROR;
			default:
				return SPDY_IO_ERROR_ERROR;
		}
	}

	return n;
}


int
SPDYF_openssl_send(struct SPDY_Session *session,
				const void * buffer,
				size_t size)
{
	int ret;

	int n = SSL_write(session->io_context,
					buffer,
					size);
	//if(n > 0) SPDYF_DEBUG("sent: %i",n);
	if (n <= 0)
	{
		ret = SSL_get_error(session->io_context, n);
		switch(ret)
		{
			case SSL_ERROR_ZERO_RETURN:
				return 0;

			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_WRITE:
				return SPDY_IO_ERROR_AGAIN;

			case SSL_ERROR_SYSCALL:
				if(EINTR == errno)
					return SPDY_IO_ERROR_AGAIN;
				return SPDY_IO_ERROR_ERROR;
			default:
				return SPDY_IO_ERROR_ERROR;
		}
	}

	return n;
}


int
SPDYF_openssl_is_pending(struct SPDY_Session *session)
{
	/* From openssl docs:
	 * BUGS
SSL_pending() takes into account only bytes from the TLS/SSL record that is currently being processed (if any). If the SSL object's read_ahead flag is set, additional protocol bytes may have been read containing more TLS/SSL records; these are ignored by SSL_pending().
	 */
	return SSL_pending(session->io_context) > 0 ? SPDY_YES : SPDY_NO;
}


int
SPDYF_openssl_before_write(struct SPDY_Session *session)
{
  (void)session;

  return SPDY_YES;
}


int
SPDYF_openssl_after_write(struct SPDY_Session *session, int was_written)
{
  (void)session;

  return was_written;
}