/* * Copyright (c) 2016 Thomas Pornin * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #ifdef _WIN32 #include #include #else #include #include #include #include #include #include #include #include #define SOCKET int #define INVALID_SOCKET (-1) #endif #include "brssl.h" static void dump_blob(const char *name, const void *data, size_t len) { const unsigned char *buf; size_t u; buf = data; fprintf(stderr, "%s (len = %lu)", name, (unsigned long)len); for (u = 0; u < len; u ++) { if ((u & 15) == 0) { fprintf(stderr, "\n%08lX ", (unsigned long)u); } else if ((u & 7) == 0) { fprintf(stderr, " "); } fprintf(stderr, " %02x", buf[u]); } fprintf(stderr, "\n"); } /* * Inspect the provided data in case it is a "command" to trigger a * special behaviour. If the command is recognised, then it is executed * and this function returns 1. Otherwise, this function returns 0. */ static int run_command(br_ssl_engine_context *cc, unsigned char *buf, size_t len) { /* * A single static slot for saving session parameters. */ static br_ssl_session_parameters slot; static int slot_used = 0; size_t u; if (len < 2 || len > 3) { return 0; } if (len == 3 && (buf[1] != '\r' || buf[2] != '\n')) { return 0; } if (len == 2 && buf[1] != '\n') { return 0; } switch (buf[0]) { case 'Q': fprintf(stderr, "closing...\n"); br_ssl_engine_close(cc); return 1; case 'R': if (br_ssl_engine_renegotiate(cc)) { fprintf(stderr, "renegotiating...\n"); } else { fprintf(stderr, "not renegotiating.\n"); } return 1; case 'F': /* * Session forget is nominally client-only. But the * session parameters are in the engine structure, which * is the first field of the client context, so the cast * still works properly. On the server, this forgetting * has no effect. */ fprintf(stderr, "forgetting session...\n"); br_ssl_client_forget_session((br_ssl_client_context *)cc); return 1; case 'S': fprintf(stderr, "saving session parameters...\n"); br_ssl_engine_get_session_parameters(cc, &slot); fprintf(stderr, " id = "); for (u = 0; u < slot.session_id_len; u ++) { fprintf(stderr, "%02X", slot.session_id[u]); } fprintf(stderr, "\n"); slot_used = 1; return 1; case 'P': if (slot_used) { fprintf(stderr, "restoring session parameters...\n"); fprintf(stderr, " id = "); for (u = 0; u < slot.session_id_len; u ++) { fprintf(stderr, "%02X", slot.session_id[u]); } fprintf(stderr, "\n"); br_ssl_engine_set_session_parameters(cc, &slot); return 1; } return 0; default: return 0; } } #ifdef _WIN32 typedef struct { unsigned char buf[1024]; size_t ptr, len; } in_buffer; static int in_return_bytes(in_buffer *bb, unsigned char *buf, size_t len) { if (bb->ptr < bb->len) { size_t clen; if (buf == NULL) { return 1; } clen = bb->len - bb->ptr; if (clen > len) { clen = len; } memcpy(buf, bb->buf + bb->ptr, clen); bb->ptr += clen; if (bb->ptr == bb->len) { bb->ptr = bb->len = 0; } return (int)clen; } return 0; } /* * A buffered version of in_read(), using a buffer to return only * full lines when feasible. */ static int in_read_buffered(HANDLE h_in, in_buffer *bb, unsigned char *buf, size_t len) { int n; if (len == 0) { return 0; } n = in_return_bytes(bb, buf, len); if (n != 0) { return n; } for (;;) { INPUT_RECORD inrec; DWORD v; if (!PeekConsoleInput(h_in, &inrec, 1, &v)) { fprintf(stderr, "ERROR: PeekConsoleInput()" " failed with 0x%08lX\n", (unsigned long)GetLastError()); return -1; } if (v == 0) { return 0; } if (!ReadConsoleInput(h_in, &inrec, 1, &v)) { fprintf(stderr, "ERROR: ReadConsoleInput()" " failed with 0x%08lX\n", (unsigned long)GetLastError()); return -1; } if (v == 0) { return 0; } if (inrec.EventType == KEY_EVENT && inrec.Event.KeyEvent.bKeyDown) { int c; c = inrec.Event.KeyEvent.uChar.AsciiChar; if (c == '\n' || c == '\r' || c == '\t' || (c >= 32 && c != 127)) { if (c == '\r') { c = '\n'; } bb->buf[bb->ptr ++] = (unsigned char)c; printf("%c", c); fflush(stdout); bb->len = bb->ptr; if (bb->len == sizeof bb->buf || c == '\n') { bb->ptr = 0; return in_return_bytes(bb, buf, len); } } } } } static int in_avail_buffered(HANDLE h_in, in_buffer *bb) { return in_read_buffered(h_in, bb, NULL, 1); } #endif /* see brssl.h */ int run_ssl_engine(br_ssl_engine_context *cc, unsigned long fd, unsigned flags) { int hsdetails; int retcode; int verbose; int trace; #ifdef _WIN32 WSAEVENT fd_event; int can_send, can_recv; HANDLE h_in, h_out; in_buffer bb; #endif hsdetails = 0; retcode = 0; verbose = (flags & RUN_ENGINE_VERBOSE) != 0; trace = (flags & RUN_ENGINE_TRACE) != 0; /* * Print algorithm details. */ if (verbose) { const char *rngname; fprintf(stderr, "Algorithms:\n"); br_prng_seeder_system(&rngname); fprintf(stderr, " RNG: %s\n", rngname); if (cc->iaes_cbcenc != 0) { fprintf(stderr, " AES/CBC (enc): %s\n", get_algo_name(cc->iaes_cbcenc, 0)); } if (cc->iaes_cbcdec != 0) { fprintf(stderr, " AES/CBC (dec): %s\n", get_algo_name(cc->iaes_cbcdec, 0)); } if (cc->iaes_ctr != 0) { fprintf(stderr, " AES/CTR: %s\n", get_algo_name(cc->iaes_cbcdec, 0)); } if (cc->iaes_ctrcbc != 0) { fprintf(stderr, " AES/CCM: %s\n", get_algo_name(cc->iaes_ctrcbc, 0)); } if (cc->ides_cbcenc != 0) { fprintf(stderr, " DES/CBC (enc): %s\n", get_algo_name(cc->ides_cbcenc, 0)); } if (cc->ides_cbcdec != 0) { fprintf(stderr, " DES/CBC (dec): %s\n", get_algo_name(cc->ides_cbcdec, 0)); } if (cc->ighash != 0) { fprintf(stderr, " GHASH (GCM): %s\n", get_algo_name(cc->ighash, 0)); } if (cc->ichacha != 0) { fprintf(stderr, " ChaCha20: %s\n", get_algo_name(cc->ichacha, 0)); } if (cc->ipoly != 0) { fprintf(stderr, " Poly1305: %s\n", get_algo_name(cc->ipoly, 0)); } if (cc->iec != 0) { fprintf(stderr, " EC: %s\n", get_algo_name(cc->iec, 0)); } if (cc->iecdsa != 0) { fprintf(stderr, " ECDSA: %s\n", get_algo_name(cc->iecdsa, 0)); } if (cc->irsavrfy != 0) { fprintf(stderr, " RSA (vrfy): %s\n", get_algo_name(cc->irsavrfy, 0)); } } #ifdef _WIN32 fd_event = WSA_INVALID_EVENT; can_send = 0; can_recv = 0; bb.ptr = bb.len = 0; #endif /* * On Unix systems, we need to follow three descriptors: * standard input (0), standard output (1), and the socket * itself (for both read and write). This is done with a poll() * call. * * On Windows systems, we use WSAEventSelect() to associate * an event handle with the network activity, and we use * WaitForMultipleObjectsEx() on that handle and the standard * input handle, when appropriate. Standard output is assumed * to be always writeable, and standard input to be the console; * this does not work well (or at all) with redirections (to * pipes or files) but it should be enough for a debug tool * (TODO: make something that handles redirections as well). */ #ifdef _WIN32 fd_event = WSACreateEvent(); if (fd_event == WSA_INVALID_EVENT) { fprintf(stderr, "ERROR: WSACreateEvent() failed with %d\n", WSAGetLastError()); retcode = -2; goto engine_exit; } WSAEventSelect(fd, fd_event, FD_READ | FD_WRITE | FD_CLOSE); h_in = GetStdHandle(STD_INPUT_HANDLE); h_out = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleMode(h_in, ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); #else /* * Make sure that stdin and stdout are non-blocking. */ fcntl(0, F_SETFL, O_NONBLOCK); fcntl(1, F_SETFL, O_NONBLOCK); #endif /* * Perform the loop. */ for (;;) { unsigned st; int sendrec, recvrec, sendapp, recvapp; #ifdef _WIN32 HANDLE pfd[2]; DWORD wt; #else struct pollfd pfd[3]; int n; #endif size_t u, k_fd, k_in, k_out; int sendrec_ok, recvrec_ok, sendapp_ok, recvapp_ok; /* * Get current engine state. */ st = br_ssl_engine_current_state(cc); if (st == BR_SSL_CLOSED) { int err; err = br_ssl_engine_last_error(cc); if (err == BR_ERR_OK) { if (verbose) { fprintf(stderr, "SSL closed normally\n"); } retcode = 0; goto engine_exit; } else { fprintf(stderr, "ERROR: SSL error %d", err); retcode = err; if (err >= BR_ERR_SEND_FATAL_ALERT) { err -= BR_ERR_SEND_FATAL_ALERT; fprintf(stderr, " (sent alert %d)\n", err); } else if (err >= BR_ERR_RECV_FATAL_ALERT) { err -= BR_ERR_RECV_FATAL_ALERT; fprintf(stderr, " (received alert %d)\n", err); } else { const char *ename; ename = find_error_name(err, NULL); if (ename == NULL) { ename = "unknown"; } fprintf(stderr, " (%s)\n", ename); } goto engine_exit; } } /* * Compute descriptors that must be polled, depending * on engine state. */ sendrec = ((st & BR_SSL_SENDREC) != 0); recvrec = ((st & BR_SSL_RECVREC) != 0); sendapp = ((st & BR_SSL_SENDAPP) != 0); recvapp = ((st & BR_SSL_RECVAPP) != 0); if (verbose && sendapp && !hsdetails) { char csn[80]; const char *pname; fprintf(stderr, "Handshake completed\n"); fprintf(stderr, " version: "); switch (cc->session.version) { case BR_SSL30: fprintf(stderr, "SSL 3.0"); break; case BR_TLS10: fprintf(stderr, "TLS 1.0"); break; case BR_TLS11: fprintf(stderr, "TLS 1.1"); break; case BR_TLS12: fprintf(stderr, "TLS 1.2"); break; default: fprintf(stderr, "unknown (0x%04X)", (unsigned)cc->session.version); break; } fprintf(stderr, "\n"); get_suite_name_ext( cc->session.cipher_suite, csn, sizeof csn); fprintf(stderr, " cipher suite: %s\n", csn); if (uses_ecdhe(cc->session.cipher_suite)) { get_curve_name_ext( br_ssl_engine_get_ecdhe_curve(cc), csn, sizeof csn); fprintf(stderr, " ECDHE curve: %s\n", csn); } fprintf(stderr, " secure renegotiation: %s\n", cc->reneg == 1 ? "no" : "yes"); pname = br_ssl_engine_get_selected_protocol(cc); if (pname != NULL) { fprintf(stderr, " protocol name (ALPN): %s\n", pname); } hsdetails = 1; } k_fd = (size_t)-1; k_in = (size_t)-1; k_out = (size_t)-1; u = 0; #ifdef _WIN32 /* * If we recorded that we can send or receive data, and we * want to do exactly that, then we don't wait; we just do * it. */ recvapp_ok = 0; sendrec_ok = 0; recvrec_ok = 0; sendapp_ok = 0; if (sendrec && can_send) { sendrec_ok = 1; } else if (recvrec && can_recv) { recvrec_ok = 1; } else if (recvapp) { recvapp_ok = 1; } else if (sendapp && in_avail_buffered(h_in, &bb)) { sendapp_ok = 1; } else { /* * If we cannot do I/O right away, then we must * wait for some event, and try again. */ pfd[u] = (HANDLE)fd_event; k_fd = u; u ++; if (sendapp) { pfd[u] = h_in; k_in = u; u ++; } wt = WaitForMultipleObjectsEx(u, pfd, FALSE, INFINITE, FALSE); if (wt == WAIT_FAILED) { fprintf(stderr, "ERROR:" " WaitForMultipleObjectsEx()" " failed with 0x%08lX", (unsigned long)GetLastError()); retcode = -2; goto engine_exit; } if (wt == k_fd) { WSANETWORKEVENTS e; if (WSAEnumNetworkEvents(fd, fd_event, &e)) { fprintf(stderr, "ERROR:" " WSAEnumNetworkEvents()" " failed with %d\n", WSAGetLastError()); retcode = -2; goto engine_exit; } if (e.lNetworkEvents & (FD_WRITE | FD_CLOSE)) { can_send = 1; } if (e.lNetworkEvents & (FD_READ | FD_CLOSE)) { can_recv = 1; } } continue; } #else if (sendrec || recvrec) { pfd[u].fd = fd; pfd[u].revents = 0; pfd[u].events = 0; if (sendrec) { pfd[u].events |= POLLOUT; } if (recvrec) { pfd[u].events |= POLLIN; } k_fd = u; u ++; } if (sendapp) { pfd[u].fd = 0; pfd[u].revents = 0; pfd[u].events = POLLIN; k_in = u; u ++; } if (recvapp) { pfd[u].fd = 1; pfd[u].revents = 0; pfd[u].events = POLLOUT; k_out = u; u ++; } n = poll(pfd, u, -1); if (n < 0) { if (errno == EINTR) { continue; } perror("ERROR: poll()"); retcode = -2; goto engine_exit; } if (n == 0) { continue; } /* * We transform closures/errors into read+write accesses * so as to force the read() or write() call that will * detect the situation. */ while (u -- > 0) { if (pfd[u].revents & (POLLERR | POLLHUP)) { pfd[u].revents |= POLLIN | POLLOUT; } } recvapp_ok = recvapp && (pfd[k_out].revents & POLLOUT) != 0; sendrec_ok = sendrec && (pfd[k_fd].revents & POLLOUT) != 0; recvrec_ok = recvrec && (pfd[k_fd].revents & POLLIN) != 0; sendapp_ok = sendapp && (pfd[k_in].revents & POLLIN) != 0; #endif /* * We give preference to outgoing data, on stdout and on * the socket. */ if (recvapp_ok) { unsigned char *buf; size_t len; #ifdef _WIN32 DWORD wlen; #else ssize_t wlen; #endif buf = br_ssl_engine_recvapp_buf(cc, &len); #ifdef _WIN32 if (!WriteFile(h_out, buf, len, &wlen, NULL)) { if (verbose) { fprintf(stderr, "stdout closed...\n"); } retcode = -2; goto engine_exit; } #else wlen = write(1, buf, len); if (wlen <= 0) { if (verbose) { fprintf(stderr, "stdout closed...\n"); } retcode = -2; goto engine_exit; } #endif br_ssl_engine_recvapp_ack(cc, wlen); continue; } if (sendrec_ok) { unsigned char *buf; size_t len; int wlen; buf = br_ssl_engine_sendrec_buf(cc, &len); wlen = send(fd, buf, len, 0); if (wlen <= 0) { #ifdef _WIN32 int err; err = WSAGetLastError(); if (err == EWOULDBLOCK || err == WSAEWOULDBLOCK) { can_send = 0; continue; } #else if (errno == EINTR || errno == EWOULDBLOCK) { continue; } #endif if (verbose) { fprintf(stderr, "socket closed...\n"); } retcode = -1; goto engine_exit; } if (trace) { dump_blob("Outgoing bytes", buf, wlen); } br_ssl_engine_sendrec_ack(cc, wlen); continue; } if (recvrec_ok) { unsigned char *buf; size_t len; int rlen; buf = br_ssl_engine_recvrec_buf(cc, &len); rlen = recv(fd, buf, len, 0); if (rlen == 0) { if (verbose) { fprintf(stderr, "socket closed...\n"); } retcode = -1; goto engine_exit; } if (rlen < 0) { #ifdef _WIN32 int err; err = WSAGetLastError(); if (err == EWOULDBLOCK || err == WSAEWOULDBLOCK) { can_recv = 0; continue; } #else if (errno == EINTR || errno == EWOULDBLOCK) { continue; } #endif if (verbose) { fprintf(stderr, "socket broke...\n"); } retcode = -1; goto engine_exit; } if (trace) { dump_blob("Incoming bytes", buf, rlen); } br_ssl_engine_recvrec_ack(cc, rlen); continue; } if (sendapp_ok) { unsigned char *buf; size_t len; #ifdef _WIN32 int rlen; #else ssize_t rlen; #endif buf = br_ssl_engine_sendapp_buf(cc, &len); #ifdef _WIN32 rlen = in_read_buffered(h_in, &bb, buf, len); #else rlen = read(0, buf, len); #endif if (rlen <= 0) { if (verbose) { fprintf(stderr, "stdin closed...\n"); } br_ssl_engine_close(cc); } else if (!run_command(cc, buf, rlen)) { br_ssl_engine_sendapp_ack(cc, rlen); } br_ssl_engine_flush(cc, 0); continue; } /* We should never reach that point. */ fprintf(stderr, "ERROR: poll() misbehaves\n"); retcode = -2; goto engine_exit; } /* * Release allocated structures. */ engine_exit: #ifdef _WIN32 if (fd_event != WSA_INVALID_EVENT) { WSACloseEvent(fd_event); } #endif return retcode; }