1 /*
2 This file is part of libmicrohttpd
3 Copyright (C) 2007 Christian Grothoff
4
5 libmicrohttpd is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 2, or (at your
8 option) any later version.
9
10 libmicrohttpd is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with libmicrohttpd; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.
19 */
20
21 /**
22 * @file tls_test_common.c
23 * @brief Common tls test functions
24 * @author Sagie Amir
25 */
26 #include "tls_test_common.h"
27 #include "tls_test_keys.h"
28
29
30 int curl_check_version (const char *req_version, ...);
31
32 FILE *
setup_ca_cert()33 setup_ca_cert ()
34 {
35 FILE *cert_fd;
36
37 if (NULL == (cert_fd = fopen (ca_cert_file_name, "wb+")))
38 {
39 fprintf (stderr, "Error: failed to open `%s': %s\n",
40 ca_cert_file_name, strerror (errno));
41 return NULL;
42 }
43 if (fwrite (ca_cert_pem, sizeof (char), strlen (ca_cert_pem) + 1, cert_fd)
44 != strlen (ca_cert_pem) + 1)
45 {
46 fprintf (stderr, "Error: failed to write `%s. %s'\n",
47 ca_cert_file_name, strerror (errno));
48 fclose (cert_fd);
49 return NULL;
50 }
51 if (fflush (cert_fd))
52 {
53 fprintf (stderr, "Error: failed to flush ca cert file stream. %s\n",
54 strerror (errno));
55 fclose (cert_fd);
56 return NULL;
57 }
58 return cert_fd;
59 }
60
61
62 /*
63 * test HTTPS transfer
64 */
65 int
test_daemon_get(void * cls,const char * cipher_suite,int proto_version,int port,int ver_peer)66 test_daemon_get (void *cls,
67 const char *cipher_suite, int proto_version,
68 int port,
69 int ver_peer)
70 {
71 CURL *c;
72 struct CBC cbc;
73 CURLcode errornum;
74 char url[255];
75 size_t len;
76
77 len = strlen (test_data);
78 if (NULL == (cbc.buf = malloc (sizeof (char) * len)))
79 {
80 fprintf (stderr, MHD_E_MEM);
81 return -1;
82 }
83 cbc.size = len;
84 cbc.pos = 0;
85
86 /* construct url - this might use doc_path */
87 gen_test_file_url (url, port);
88
89 c = curl_easy_init ();
90 #if DEBUG_HTTPS_TEST
91 curl_easy_setopt (c, CURLOPT_VERBOSE, 1);
92 #endif
93 curl_easy_setopt (c, CURLOPT_URL, url);
94 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
95 curl_easy_setopt (c, CURLOPT_TIMEOUT, 10L);
96 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 10L);
97 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
98 curl_easy_setopt (c, CURLOPT_FILE, &cbc);
99
100 /* TLS options */
101 curl_easy_setopt (c, CURLOPT_SSLVERSION, proto_version);
102 curl_easy_setopt (c, CURLOPT_SSL_CIPHER_LIST, cipher_suite);
103
104 /* perform peer authentication */
105 /* TODO merge into send_curl_req */
106 curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, ver_peer);
107 curl_easy_setopt (c, CURLOPT_CAINFO, ca_cert_file_name);
108 curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 0);
109 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
110
111 /* NOTE: use of CONNECTTIMEOUT without also
112 setting NOSIGNAL results in really weird
113 crashes on my system! */
114 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
115 if (CURLE_OK != (errornum = curl_easy_perform (c)))
116 {
117 fprintf (stderr, "curl_easy_perform failed: `%s'\n",
118 curl_easy_strerror (errornum));
119 curl_easy_cleanup (c);
120 free (cbc.buf);
121 return errornum;
122 }
123
124 curl_easy_cleanup (c);
125
126 if (memcmp (cbc.buf, test_data, len) != 0)
127 {
128 fprintf (stderr, "Error: local file & received file differ.\n");
129 free (cbc.buf);
130 return -1;
131 }
132
133 free (cbc.buf);
134 return 0;
135 }
136
137
138 void
print_test_result(int test_outcome,char * test_name)139 print_test_result (int test_outcome, char *test_name)
140 {
141 #if 0
142 if (test_outcome != 0)
143 fprintf (stderr, "running test: %s [fail]\n", test_name);
144 else
145 fprintf (stdout, "running test: %s [pass]\n", test_name);
146 #endif
147 }
148
149 size_t
copyBuffer(void * ptr,size_t size,size_t nmemb,void * ctx)150 copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
151 {
152 struct CBC *cbc = ctx;
153
154 if (cbc->pos + size * nmemb > cbc->size)
155 return 0; /* overflow */
156 memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
157 cbc->pos += size * nmemb;
158 return size * nmemb;
159 }
160
161 /**
162 * HTTP access handler call back
163 */
164 int
http_ahc(void * cls,struct MHD_Connection * connection,const char * url,const char * method,const char * upload_data,const char * version,size_t * upload_data_size,void ** ptr)165 http_ahc (void *cls, struct MHD_Connection *connection,
166 const char *url, const char *method, const char *upload_data,
167 const char *version, size_t *upload_data_size, void **ptr)
168 {
169 static int aptr;
170 struct MHD_Response *response;
171 int ret;
172
173 if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
174 return MHD_NO; /* unexpected method */
175 if (&aptr != *ptr)
176 {
177 /* do never respond on first call */
178 *ptr = &aptr;
179 return MHD_YES;
180 }
181 *ptr = NULL; /* reset when done */
182 response = MHD_create_response_from_buffer (strlen (test_data),
183 (void *) test_data,
184 MHD_RESPMEM_PERSISTENT);
185 ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
186 MHD_destroy_response (response);
187 return ret;
188 }
189
190 /* HTTP access handler call back */
191 int
http_dummy_ahc(void * cls,struct MHD_Connection * connection,const char * url,const char * method,const char * upload_data,const char * version,size_t * upload_data_size,void ** ptr)192 http_dummy_ahc (void *cls, struct MHD_Connection *connection,
193 const char *url, const char *method, const char *upload_data,
194 const char *version, size_t *upload_data_size,
195 void **ptr)
196 {
197 return 0;
198 }
199
200 /**
201 * send a test http request to the daemon
202 * @param url
203 * @param cbc - may be null
204 * @param cipher_suite
205 * @param proto_version
206 * @return
207 */
208 /* TODO have test wrap consider a NULL cbc */
209 int
send_curl_req(char * url,struct CBC * cbc,const char * cipher_suite,int proto_version)210 send_curl_req (char *url, struct CBC * cbc, const char *cipher_suite,
211 int proto_version)
212 {
213 CURL *c;
214 CURLcode errornum;
215 c = curl_easy_init ();
216 #if DEBUG_HTTPS_TEST
217 curl_easy_setopt (c, CURLOPT_VERBOSE, CURL_VERBOS_LEVEL);
218 #endif
219 curl_easy_setopt (c, CURLOPT_URL, url);
220 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
221 curl_easy_setopt (c, CURLOPT_TIMEOUT, 60L);
222 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 60L);
223
224 if (cbc != NULL)
225 {
226 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
227 curl_easy_setopt (c, CURLOPT_FILE, cbc);
228 }
229
230 /* TLS options */
231 curl_easy_setopt (c, CURLOPT_SSLVERSION, proto_version);
232 curl_easy_setopt (c, CURLOPT_SSL_CIPHER_LIST, cipher_suite);
233
234 /* currently skip any peer authentication */
235 curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, 0);
236 curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 0);
237
238 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
239
240 /* NOTE: use of CONNECTTIMEOUT without also
241 setting NOSIGNAL results in really weird
242 crashes on my system! */
243 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
244 if (CURLE_OK != (errornum = curl_easy_perform (c)))
245 {
246 fprintf (stderr, "curl_easy_perform failed: `%s'\n",
247 curl_easy_strerror (errornum));
248 curl_easy_cleanup (c);
249 return errornum;
250 }
251 curl_easy_cleanup (c);
252
253 return CURLE_OK;
254 }
255
256
257 /**
258 * compile test file url pointing to the current running directory path
259 *
260 * @param url - char buffer into which the url is compiled
261 * @param port port to use for the test
262 * @return -1 on error
263 */
264 int
gen_test_file_url(char * url,int port)265 gen_test_file_url (char *url, int port)
266 {
267 int ret = 0;
268 char *doc_path;
269 size_t doc_path_len;
270 /* setup test file path, url */
271 doc_path_len = PATH_MAX > 4096 ? 4096 : PATH_MAX;
272 if (NULL == (doc_path = malloc (doc_path_len)))
273 {
274 fprintf (stderr, MHD_E_MEM);
275 return -1;
276 }
277 if (getcwd (doc_path, doc_path_len) == NULL)
278 {
279 fprintf (stderr, "Error: failed to get working directory. %s\n",
280 strerror (errno));
281 ret = -1;
282 }
283 #ifdef WINDOWS
284 {
285 int i;
286 for (i = 0; i < doc_path_len; i++)
287 {
288 if (doc_path[i] == 0)
289 break;
290 if (doc_path[i] == '\\')
291 {
292 doc_path[i] = '/';
293 }
294 if (doc_path[i] != ':')
295 continue;
296 if (i == 0)
297 break;
298 doc_path[i] = doc_path[i - 1];
299 doc_path[i - 1] = '/';
300 }
301 }
302 #endif
303 /* construct url - this might use doc_path */
304 if (sprintf (url, "%s:%d%s/%s", "https://127.0.0.1", port,
305 doc_path, "urlpath") < 0)
306 ret = -1;
307
308 free (doc_path);
309 return ret;
310 }
311
312 /**
313 * test HTTPS file transfer
314 */
315 int
test_https_transfer(void * cls,const char * cipher_suite,int proto_version)316 test_https_transfer (void *cls, const char *cipher_suite, int proto_version)
317 {
318 int len;
319 int ret = 0;
320 struct CBC cbc;
321 char url[255];
322
323 len = strlen (test_data);
324 if (NULL == (cbc.buf = malloc (sizeof (char) * len)))
325 {
326 fprintf (stderr, MHD_E_MEM);
327 return -1;
328 }
329 cbc.size = len;
330 cbc.pos = 0;
331
332 if (gen_test_file_url (url, DEAMON_TEST_PORT))
333 {
334 ret = -1;
335 goto cleanup;
336 }
337
338 if (CURLE_OK != send_curl_req (url, &cbc, cipher_suite, proto_version))
339 {
340 ret = -1;
341 goto cleanup;
342 }
343
344 /* compare test file & daemon responce */
345 if ( (len != strlen (test_data)) ||
346 (memcmp (cbc.buf,
347 test_data,
348 len) != 0) )
349 {
350 fprintf (stderr, "Error: local file & received file differ.\n");
351 ret = -1;
352 }
353 cleanup:
354 free (cbc.buf);
355 return ret;
356 }
357
358 /**
359 * setup test case
360 *
361 * @param d
362 * @param daemon_flags
363 * @param arg_list
364 * @return
365 */
366 int
setup_testcase(struct MHD_Daemon ** d,int daemon_flags,va_list arg_list)367 setup_testcase (struct MHD_Daemon **d, int daemon_flags, va_list arg_list)
368 {
369 *d = MHD_start_daemon_va (daemon_flags, DEAMON_TEST_PORT,
370 NULL, NULL, &http_ahc, NULL, arg_list);
371
372 if (*d == NULL)
373 {
374 fprintf (stderr, MHD_E_SERVER_INIT);
375 return -1;
376 }
377
378 return 0;
379 }
380
381 void
teardown_testcase(struct MHD_Daemon * d)382 teardown_testcase (struct MHD_Daemon *d)
383 {
384 MHD_stop_daemon (d);
385 }
386
387 int
setup_session(gnutls_session_t * session,gnutls_datum_t * key,gnutls_datum_t * cert,gnutls_certificate_credentials_t * xcred)388 setup_session (gnutls_session_t * session,
389 gnutls_datum_t * key,
390 gnutls_datum_t * cert,
391 gnutls_certificate_credentials_t * xcred)
392 {
393 int ret;
394 const char *err_pos;
395
396 gnutls_certificate_allocate_credentials (xcred);
397 key->size = strlen (srv_key_pem) + 1;
398 key->data = malloc (key->size);
399 if (NULL == key->data)
400 {
401 gnutls_certificate_free_credentials (*xcred);
402 return -1;
403 }
404 memcpy (key->data, srv_key_pem, key->size);
405 cert->size = strlen (srv_self_signed_cert_pem) + 1;
406 cert->data = malloc (cert->size);
407 if (NULL == cert->data)
408 {
409 gnutls_certificate_free_credentials (*xcred);
410 free (key->data);
411 return -1;
412 }
413 memcpy (cert->data, srv_self_signed_cert_pem, cert->size);
414 gnutls_certificate_set_x509_key_mem (*xcred, cert, key,
415 GNUTLS_X509_FMT_PEM);
416 gnutls_init (session, GNUTLS_CLIENT);
417 ret = gnutls_priority_set_direct (*session,
418 "NORMAL", &err_pos);
419 if (ret < 0)
420 {
421 gnutls_deinit (*session);
422 gnutls_certificate_free_credentials (*xcred);
423 free (key->data);
424 return -1;
425 }
426 gnutls_credentials_set (*session,
427 GNUTLS_CRD_CERTIFICATE,
428 *xcred);
429 return 0;
430 }
431
432 int
teardown_session(gnutls_session_t session,gnutls_datum_t * key,gnutls_datum_t * cert,gnutls_certificate_credentials_t xcred)433 teardown_session (gnutls_session_t session,
434 gnutls_datum_t * key,
435 gnutls_datum_t * cert,
436 gnutls_certificate_credentials_t xcred)
437 {
438 free (key->data);
439 key->data = NULL;
440 key->size = 0;
441 free (cert->data);
442 cert->data = NULL;
443 cert->size = 0;
444 gnutls_deinit (session);
445 gnutls_certificate_free_credentials (xcred);
446 return 0;
447 }
448
449 /* TODO test_wrap: change sig to (setup_func, test, va_list test_arg) */
450 int
test_wrap(const char * test_name,int (* test_function)(void * cls,const char * cipher_suite,int proto_version),void * cls,int daemon_flags,const char * cipher_suite,int proto_version,...)451 test_wrap (const char *test_name, int
452 (*test_function) (void * cls, const char *cipher_suite,
453 int proto_version), void * cls,
454 int daemon_flags, const char *cipher_suite, int proto_version, ...)
455 {
456 int ret;
457 va_list arg_list;
458 struct MHD_Daemon *d;
459
460 va_start (arg_list, proto_version);
461 if (setup_testcase (&d, daemon_flags, arg_list) != 0)
462 {
463 va_end (arg_list);
464 fprintf (stderr, "Failed to setup testcase %s\n", test_name);
465 return -1;
466 }
467 #if 0
468 fprintf (stdout, "running test: %s ", test_name);
469 #endif
470 ret = test_function (NULL, cipher_suite, proto_version);
471 #if 0
472 if (ret == 0)
473 {
474 fprintf (stdout, "[pass]\n");
475 }
476 else
477 {
478 fprintf (stdout, "[fail]\n");
479 }
480 #endif
481 teardown_testcase (d);
482 va_end (arg_list);
483 return ret;
484 }
485