1/*
2    This file is part of libmicrospdy
3    Copyright Copyright (C) 2012 Andrey Uzunov
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17*/
18
19/**
20 * @file io_openssl.c
21 * @brief  TLS handling using libssl. The current code assumes that
22 * 			blocking I/O is in use.
23 * @author Andrey Uzunov
24 */
25
26#include "platform.h"
27#include "internal.h"
28#include "session.h"
29#include "io_openssl.h"
30
31
32/**
33 * Callback to advertise spdy ver. 3 in Next Protocol Negotiation
34 *
35 * @param ssl openssl context for a connection
36 * @param out must be set to the raw data that is advertised in NPN
37 * @param outlen must be set to size of out
38 * @param arg
39 * @return SSL_TLSEXT_ERR_OK to do advertising
40 */
41static int
42spdyf_next_protos_advertised_cb (SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg)
43{
44	(void)ssl;
45	(void)arg;
46	static unsigned char npn_spdy3[] = {0x06, // length of "spdy/3"
47		0x73,0x70,0x64,0x79,0x2f,0x33};// spdy/3
48
49	*out = npn_spdy3;
50	*outlen = 7; // total length of npn_spdy3
51	return SSL_TLSEXT_ERR_OK;
52}
53
54
55void
56SPDYF_openssl_global_init()
57{
58	//error strings are now not used by the lib
59    //SSL_load_error_strings();
60    //init libssl
61    SSL_library_init(); //always returns 1
62    //the table for looking up algos is not used now by the lib
63    //OpenSSL_add_all_algorithms();
64}
65
66
67void
68SPDYF_openssl_global_deinit()
69{
70	//if SSL_load_error_strings was called
71    //ERR_free_strings();
72    //if OpenSSL_add_all_algorithms was called
73    //EVP_cleanup();
74}
75
76
77int
78SPDYF_openssl_init(struct SPDY_Daemon *daemon)
79{
80    int options;
81    //create ssl context. TLSv1 used
82    if(NULL == (daemon->io_context = SSL_CTX_new(TLSv1_server_method())))
83    {
84		SPDYF_DEBUG("Couldn't create ssl context");
85		return SPDY_NO;
86        }
87	//set options for tls
88	//TODO DH is not enabled for easier debugging
89    //SSL_CTX_set_options(daemon->io_context, SSL_OP_SINGLE_DH_USE);
90
91    //TODO here session tickets are disabled for easier debuging with
92    //wireshark when using Chrome
93    // SSL_OP_NO_COMPRESSION disables TLS compression to avoid CRIME attack
94    options = SSL_OP_NO_TICKET;
95#ifdef SSL_OP_NO_COMPRESSION
96    options |= SSL_OP_NO_COMPRESSION;
97#elif OPENSSL_VERSION_NUMBER >= 0x00908000L /* workaround for OpenSSL 0.9.8 */
98    sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
99#endif
100
101    SSL_CTX_set_options(daemon->io_context, options);
102    if(1 != SSL_CTX_use_certificate_file(daemon->io_context, daemon->certfile , SSL_FILETYPE_PEM))
103    {
104		SPDYF_DEBUG("Couldn't load the cert file");
105		SSL_CTX_free(daemon->io_context);
106		return SPDY_NO;
107	}
108    if(1 != SSL_CTX_use_PrivateKey_file(daemon->io_context, daemon->keyfile, SSL_FILETYPE_PEM))
109    {
110		SPDYF_DEBUG("Couldn't load the name file");
111		SSL_CTX_free(daemon->io_context);
112		return SPDY_NO;
113	}
114    SSL_CTX_set_next_protos_advertised_cb(daemon->io_context, &spdyf_next_protos_advertised_cb, NULL);
115    if (1 != SSL_CTX_set_cipher_list(daemon->io_context, "HIGH"))
116    {
117		SPDYF_DEBUG("Couldn't set the desired cipher list");
118		SSL_CTX_free(daemon->io_context);
119		return SPDY_NO;
120	}
121
122	return SPDY_YES;
123}
124
125
126void
127SPDYF_openssl_deinit(struct SPDY_Daemon *daemon)
128{
129    SSL_CTX_free(daemon->io_context);
130}
131
132
133int
134SPDYF_openssl_new_session(struct SPDY_Session *session)
135{
136	int ret;
137
138	if(NULL == (session->io_context = SSL_new(session->daemon->io_context)))
139    {
140		SPDYF_DEBUG("Couldn't create ssl structure");
141		return SPDY_NO;
142	}
143	if(1 != (ret = SSL_set_fd(session->io_context, session->socket_fd)))
144    {
145		SPDYF_DEBUG("SSL_set_fd %i",ret);
146		SSL_free(session->io_context);
147		session->io_context = NULL;
148		return SPDY_NO;
149	}
150
151	//for non-blocking I/O SSL_accept may return -1
152	//and this function won't work
153	if(1 != (ret = SSL_accept(session->io_context)))
154    {
155		SPDYF_DEBUG("SSL_accept %i",ret);
156		SSL_free(session->io_context);
157		session->io_context = NULL;
158		return SPDY_NO;
159	}
160	/* alternatively
161	SSL_set_accept_state(session->io_context);
162	* may be called and then the negotiation will be done on reading
163	*/
164
165	return SPDY_YES;
166}
167
168
169void
170SPDYF_openssl_close_session(struct SPDY_Session *session)
171{
172	//SSL_shutdown sends TLS "close notify" as in TLS standard.
173	//The function may fail as it waits for the other party to also close
174	//the TLS session. The lib just sends it and will close the socket
175	//after that because the browsers don't seem to care much about
176	//"close notify"
177	SSL_shutdown(session->io_context);
178
179	SSL_free(session->io_context);
180}
181
182
183int
184SPDYF_openssl_recv(struct SPDY_Session *session,
185				void * buffer,
186				size_t size)
187{
188	int ret;
189	int n = SSL_read(session->io_context,
190					buffer,
191					size);
192	//if(n > 0) SPDYF_DEBUG("recvd: %i",n);
193	if (n <= 0)
194	{
195		ret = SSL_get_error(session->io_context, n);
196		switch(ret)
197		{
198			case SSL_ERROR_ZERO_RETURN:
199				return 0;
200
201			case SSL_ERROR_WANT_READ:
202			case SSL_ERROR_WANT_WRITE:
203				return SPDY_IO_ERROR_AGAIN;
204
205			case SSL_ERROR_SYSCALL:
206				if(EINTR == errno)
207					return SPDY_IO_ERROR_AGAIN;
208				return SPDY_IO_ERROR_ERROR;
209			default:
210				return SPDY_IO_ERROR_ERROR;
211		}
212	}
213
214	return n;
215}
216
217
218int
219SPDYF_openssl_send(struct SPDY_Session *session,
220				const void * buffer,
221				size_t size)
222{
223	int ret;
224
225	int n = SSL_write(session->io_context,
226					buffer,
227					size);
228	//if(n > 0) SPDYF_DEBUG("sent: %i",n);
229	if (n <= 0)
230	{
231		ret = SSL_get_error(session->io_context, n);
232		switch(ret)
233		{
234			case SSL_ERROR_ZERO_RETURN:
235				return 0;
236
237			case SSL_ERROR_WANT_READ:
238			case SSL_ERROR_WANT_WRITE:
239				return SPDY_IO_ERROR_AGAIN;
240
241			case SSL_ERROR_SYSCALL:
242				if(EINTR == errno)
243					return SPDY_IO_ERROR_AGAIN;
244				return SPDY_IO_ERROR_ERROR;
245			default:
246				return SPDY_IO_ERROR_ERROR;
247		}
248	}
249
250	return n;
251}
252
253
254int
255SPDYF_openssl_is_pending(struct SPDY_Session *session)
256{
257	/* From openssl docs:
258	 * BUGS
259SSL_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().
260	 */
261	return SSL_pending(session->io_context) > 0 ? SPDY_YES : SPDY_NO;
262}
263
264
265int
266SPDYF_openssl_before_write(struct SPDY_Session *session)
267{
268  (void)session;
269
270  return SPDY_YES;
271}
272
273
274int
275SPDYF_openssl_after_write(struct SPDY_Session *session, int was_written)
276{
277  (void)session;
278
279  return was_written;
280}
281