Fixed documentation (new include file for AEAD).
[BearSSL] / tools / sslio.c
index e784d07..3a8a6f3 100644 (file)
 #include <stdint.h>
 #include <errno.h>
 
+#ifdef _WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netdb.h>
 #include <fcntl.h>
 #include <poll.h>
 
+#define SOCKET           int
+#define INVALID_SOCKET   (-1)
+#endif
+
 #include "brssl.h"
-#include "bearssl.h"
 
 static void
 dump_blob(const char *name, const void *data, size_t len)
@@ -67,6 +74,14 @@ dump_blob(const char *name, const void *data, size_t len)
 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;
        }
@@ -82,8 +97,11 @@ run_command(br_ssl_engine_context *cc, unsigned char *buf, size_t len)
                br_ssl_engine_close(cc);
                return 1;
        case 'R':
-               fprintf(stderr, "renegotiating...\n");
-               br_ssl_engine_renegotiate(cc);
+               if (br_ssl_engine_renegotiate(cc)) {
+                       fprintf(stderr, "renegotiating...\n");
+               } else {
+                       fprintf(stderr, "not renegotiating.\n");
+               }
                return 1;
        case 'F':
                /*
@@ -96,30 +114,251 @@ run_command(br_ssl_engine_context *cc, unsigned char *buf, size_t len)
                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, int fd, unsigned flags)
+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) {
+               fprintf(stderr, "Algorithms:\n");
+               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->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.
@@ -127,9 +366,15 @@ run_ssl_engine(br_ssl_engine_context *cc, int fd, unsigned flags)
        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.
@@ -180,6 +425,7 @@ run_ssl_engine(br_ssl_engine_context *cc, int fd, unsigned flags)
                recvapp = ((st & BR_SSL_RECVAPP) != 0);
                if (verbose && sendapp && !hsdetails) {
                        char csn[80];
+                       const char *pname;
 
                        fprintf(stderr, "Handshake completed\n");
                        fprintf(stderr, "   version:               ");
@@ -205,16 +451,92 @@ run_ssl_engine(br_ssl_engine_context *cc, int fd, unsigned flags)
                        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 = 0;
-               k_in = 0;
-               k_out = 0;
+               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;
@@ -266,96 +588,149 @@ run_ssl_engine(br_ssl_engine_context *cc, int fd, unsigned flags)
                        }
                }
 
+               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) {
-                       if (pfd[k_out].revents & POLLOUT) {
-                               unsigned char *buf;
-                               size_t len;
-                               ssize_t wlen;
-
-                               buf = br_ssl_engine_recvapp_buf(cc, &len);
-                               wlen = write(1, buf, len);
-                               if (wlen <= 0) {
-                                       if (verbose) {
-                                               fprintf(stderr,
-                                                       "stdout closed...\n");
-                                       }
-                                       retcode = -2;
-                                       goto engine_exit;
+               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");
                                }
-                               br_ssl_engine_recvapp_ack(cc, wlen);
-                               continue;
+                               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) {
-                       if (pfd[k_fd].revents & POLLOUT) {
-                               unsigned char *buf;
-                               size_t len;
-                               ssize_t wlen;
-
-                               buf = br_ssl_engine_sendrec_buf(cc, &len);
-                               wlen = write(fd, buf, len);
-                               if (wlen <= 0) {
-                                       if (verbose) {
-                                               fprintf(stderr,
-                                                       "socket closed...\n");
-                                       }
-                                       retcode = -1;
-                                       goto engine_exit;
+               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;
                                }
-                               if (trace) {
-                                       dump_blob("Outgoing bytes", buf, wlen);
+#else
+                               if (errno == EINTR || errno == EWOULDBLOCK) {
+                                       continue;
                                }
-                               br_ssl_engine_sendrec_ack(cc, wlen);
-                               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) {
-                       if (pfd[k_fd].revents & POLLIN) {
-                               unsigned char *buf;
-                               size_t len;
-                               ssize_t rlen;
-
-                               buf = br_ssl_engine_recvrec_buf(cc, &len);
-                               rlen = read(fd, buf, len);
-                               if (rlen <= 0) {
-                                       if (verbose) {
-                                               fprintf(stderr,
-                                                       "socket closed...\n");
-                                       }
-                                       retcode = -1;
-                                       goto engine_exit;
+               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");
                                }
-                               if (trace) {
-                                       dump_blob("Incoming bytes", buf, rlen);
+                               retcode = -1;
+                               goto engine_exit;
+                       }
+                       if (rlen < 0) {
+#ifdef _WIN32
+                               int err;
+
+                               err = WSAGetLastError();
+                               if (err == EWOULDBLOCK
+                                       || err == WSAEWOULDBLOCK)
+                               {
+                                       can_recv = 0;
+                                       continue;
                                }
-                               br_ssl_engine_recvrec_ack(cc, rlen);
-                               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) {
-                       if (pfd[k_in].revents & POLLIN) {
-                               unsigned char *buf;
-                               size_t len;
-                               ssize_t rlen;
-
-                               buf = br_ssl_engine_sendapp_buf(cc, &len);
-                               rlen = read(0, buf, len);
-                               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);
+               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_flush(cc, 0);
-                               continue;
+                               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. */
@@ -368,5 +743,10 @@ run_ssl_engine(br_ssl_engine_context *cc, int fd, unsigned flags)
         * Release allocated structures.
         */
 engine_exit:
+#ifdef _WIN32
+       if (fd_event != WSA_INVALID_EVENT) {
+               WSACloseEvent(fd_event);
+       }
+#endif
        return retcode;
 }