├── .gitignore ├── Makefile ├── README.md └── ssl-proxy.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | *.su 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -g -O2 -Wall 2 | LDFLAGS = -levent_openssl -levent_core -lssl -lcrypto 3 | 4 | ssl-proxy: ssl-proxy.c 5 | $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) 6 | 7 | clean: 8 | -rm -f ssl-proxy 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ssl-proxy 2 | A simple SSL/TLS proxy using libevent. 3 | 4 | This is a fork of [le-proxy.c](https://github.com/libevent/libevent/blob/master/sample/le-proxy.c) 5 | from [libevent](https://github.com/libevent/libevent). 6 | 7 | # Build 8 | 9 | libevent and OpenSSL are required. On Ubuntu, you can install them using the code below: 10 | 11 | ``` 12 | sudo apt-get install libevent-dev libssl-dev 13 | ``` 14 | 15 | Then just type `make` to build. 16 | 17 | # Usage 18 | 19 | ## Install a forward HTTP proxy server 20 | 21 | ssl-proxy works as an SSL/TLS tunnel between server and client. It doesn't handle HTTP protocol. 22 | It can only be used with a forward HTTP proxy server. You can install `squid` or `apache`, 23 | but not `nginx`, which doesn't support `CONNECT`. 24 | 25 | ## Create a self-signed certificate 26 | 27 | ssl-proxy uses SSL certificate to secure its connections, which you can easily create using the OpenSSL package: 28 | 29 | ``` 30 | openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes 31 | ``` 32 | 33 | Note that when creating the certificate, you will be asked for some information such as country and state, 34 | which you can enter whatever you like but when asked for "Common Name" you must enter the correct host name 35 | or IP address of your server. 36 | 37 | ## Run ssl-proxy 38 | 39 | Suppose the forward HTTP proxy server is listening on `127.0.0.1:8080`, then run ssl-proxy in server mode: 40 | 41 | ``` 42 | ./ssl-proxy -server -cert cert.pem -key key.pem 0.0.0.0:8443 127.0.0.1:8080 43 | ``` 44 | 45 | And on your local machine, run ssl-proxy in client mode: 46 | 47 | ``` 48 | ./ssl-proxy 127.0.0.1:8080 your-server-ip:8443 49 | ``` 50 | 51 | Now you have an HTTP proxy server listening on `127.0.0.1:8080` on your local machine, which will encrypt 52 | incoming packets and forward them to the remote ssl-proxy, and the remote ssl-proxy will decrypt them and 53 | forward the original packets to the forward HTTP proxy server. 54 | 55 | ### Use ssl-proxy as an HTTPS proxy server 56 | 57 | If your application supports HTTPS proxy, you can skip running ssl-proxy on your local machine. 58 | 59 | For example, you can start Chrome with the `--proxy-server=https://:` command line argument: 60 | 61 | ``` 62 | chrome --proxy-server=https://your-server-ip:8443 63 | ``` 64 | 65 | In this case, you may want to install the generated certificate as a Trusted Root CA to avoid your browser 66 | complaining about it. 67 | -------------------------------------------------------------------------------- /ssl-proxy.c: -------------------------------------------------------------------------------- 1 | /* 2 | This example code shows how to write an (optionally encrypting) SSL proxy 3 | with Libevent's bufferevent layer. 4 | 5 | XXX It's a little ugly and should probably be cleaned up. 6 | */ 7 | 8 | // Get rid of OSX 10.7 and greater deprecation warnings. 9 | #if defined(__APPLE__) && defined(__clang__) 10 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 11 | #endif 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #ifdef _WIN32 20 | #include 21 | #include 22 | #else 23 | #include 24 | #include 25 | #endif 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | static struct event_base *base; 38 | static struct sockaddr_storage listen_on_addr; 39 | static struct sockaddr_storage connect_to_addr; 40 | static int connect_to_addrlen; 41 | static int server_mode = 0; 42 | static const char *program_name = NULL; 43 | 44 | static SSL_CTX *ssl_ctx = NULL; 45 | 46 | #define MAX_OUTPUT (512*1024) 47 | 48 | static void drained_writecb(struct bufferevent *bev, void *ctx); 49 | static void eventcb(struct bufferevent *bev, short what, void *ctx); 50 | 51 | static void 52 | readcb(struct bufferevent *bev, void *ctx) 53 | { 54 | struct bufferevent *partner = ctx; 55 | struct evbuffer *src, *dst; 56 | size_t len; 57 | src = bufferevent_get_input(bev); 58 | len = evbuffer_get_length(src); 59 | if (!partner) { 60 | evbuffer_drain(src, len); 61 | return; 62 | } 63 | dst = bufferevent_get_output(partner); 64 | evbuffer_add_buffer(dst, src); 65 | 66 | if (evbuffer_get_length(dst) >= MAX_OUTPUT) { 67 | /* We're giving the other side data faster than it can 68 | * pass it on. Stop reading here until we have drained the 69 | * other side to MAX_OUTPUT/2 bytes. */ 70 | bufferevent_setcb(partner, readcb, drained_writecb, 71 | eventcb, bev); 72 | bufferevent_setwatermark(partner, EV_WRITE, MAX_OUTPUT/2, 73 | MAX_OUTPUT); 74 | bufferevent_disable(bev, EV_READ); 75 | } 76 | } 77 | 78 | static void 79 | drained_writecb(struct bufferevent *bev, void *ctx) 80 | { 81 | struct bufferevent *partner = ctx; 82 | 83 | /* We were choking the other side until we drained our outbuf a bit. 84 | * Now it seems drained. */ 85 | bufferevent_setcb(bev, readcb, NULL, eventcb, partner); 86 | bufferevent_setwatermark(bev, EV_WRITE, 0, 0); 87 | if (partner) 88 | bufferevent_enable(partner, EV_READ); 89 | } 90 | 91 | static void 92 | close_on_finished_writecb(struct bufferevent *bev, void *ctx) 93 | { 94 | struct evbuffer *b = bufferevent_get_output(bev); 95 | 96 | if (evbuffer_get_length(b) == 0) { 97 | bufferevent_free(bev); 98 | } 99 | } 100 | 101 | static void 102 | eventcb(struct bufferevent *bev, short what, void *ctx) 103 | { 104 | struct bufferevent *partner = ctx; 105 | 106 | if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { 107 | if (what & BEV_EVENT_ERROR) { 108 | unsigned long err; 109 | while ((err = (bufferevent_get_openssl_error(bev)))) { 110 | const char *msg = (const char*) 111 | ERR_reason_error_string(err); 112 | const char *lib = (const char*) 113 | ERR_lib_error_string(err); 114 | const char *func = (const char*) 115 | ERR_func_error_string(err); 116 | fprintf(stderr, 117 | "%s in %s %s\n", msg, lib, func); 118 | } 119 | if (errno) 120 | perror("connection error"); 121 | } 122 | 123 | if (partner) { 124 | /* Flush all pending data */ 125 | readcb(bev, ctx); 126 | 127 | if (evbuffer_get_length( 128 | bufferevent_get_output(partner))) { 129 | /* We still have to flush data from the other 130 | * side, but when that's done, close the other 131 | * side. */ 132 | bufferevent_setcb(partner, 133 | NULL, close_on_finished_writecb, 134 | eventcb, NULL); 135 | bufferevent_disable(partner, EV_READ); 136 | } else { 137 | /* We have nothing left to say to the other 138 | * side; close it. */ 139 | bufferevent_free(partner); 140 | } 141 | } 142 | bufferevent_free(bev); 143 | } 144 | } 145 | 146 | static void 147 | syntax(void) 148 | { 149 | fputs("Syntax:\n", stderr); 150 | fprintf(stderr, " %s [-server -cert certificate_chain_file -key private_key_file] \n", program_name); 151 | fputs("Example:\n", stderr); 152 | fprintf(stderr, " %s -server -cert certificate_chain_file -key private_key_file 0.0.0.0:8443 127.0.0.1:8080\n", program_name); 153 | fprintf(stderr, " %s 127.0.0.1:8080 1.2.3.4:8443\n", program_name); 154 | 155 | exit(1); 156 | } 157 | 158 | static void 159 | accept_cb(struct evconnlistener *listener, evutil_socket_t fd, 160 | struct sockaddr *a, int slen, void *p) 161 | { 162 | struct bufferevent *b_out, *b_in; 163 | SSL *ssl = SSL_new(ssl_ctx); 164 | 165 | /* Create two linked bufferevent objects: one to connect, one for the 166 | * new connection */ 167 | if (server_mode) { 168 | b_in = bufferevent_openssl_socket_new(base, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, 169 | BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); 170 | b_out = bufferevent_socket_new(base, -1, 171 | BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); 172 | } else { 173 | b_in = bufferevent_socket_new(base, fd, 174 | BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); 175 | b_out = bufferevent_openssl_socket_new(base, -1, ssl, 176 | BUFFEREVENT_SSL_CONNECTING, 177 | BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); 178 | } 179 | 180 | assert(b_in && b_out); 181 | 182 | if (bufferevent_socket_connect(b_out, 183 | (struct sockaddr*)&connect_to_addr, connect_to_addrlen)<0) { 184 | perror("bufferevent_socket_connect"); 185 | bufferevent_free(b_out); 186 | bufferevent_free(b_in); 187 | return; 188 | } 189 | 190 | bufferevent_setcb(b_in, readcb, NULL, eventcb, b_out); 191 | bufferevent_setcb(b_out, readcb, NULL, eventcb, b_in); 192 | 193 | bufferevent_enable(b_in, EV_READ|EV_WRITE); 194 | bufferevent_enable(b_out, EV_READ|EV_WRITE); 195 | } 196 | 197 | static int 198 | init_ssl(const char *certificate_chain_file, const char *private_key_file) 199 | { 200 | int r; 201 | #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER) 202 | #define TLS_method SSLv23_method 203 | SSL_library_init(); 204 | ERR_load_crypto_strings(); 205 | SSL_load_error_strings(); 206 | OpenSSL_add_all_algorithms(); 207 | #endif 208 | r = RAND_poll(); 209 | if (r == 0) { 210 | fprintf(stderr, "RAND_poll() failed.\n"); 211 | return 1; 212 | } 213 | ssl_ctx = SSL_CTX_new(TLS_method()); 214 | SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv3); 215 | if (server_mode) { 216 | if (!SSL_CTX_use_certificate_chain_file(ssl_ctx, certificate_chain_file) || 217 | !SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file, SSL_FILETYPE_PEM)) { 218 | fprintf(stderr, "Couldn't read %s or %s.\n", certificate_chain_file, private_key_file); 219 | return 2; 220 | } 221 | } 222 | return 0; 223 | } 224 | 225 | int 226 | main(int argc, char **argv) 227 | { 228 | int i; 229 | int socklen; 230 | const char *certificate_chain_file = NULL; 231 | const char *private_key_file = NULL; 232 | 233 | struct evconnlistener *listener; 234 | 235 | program_name = argv[0]; 236 | 237 | if (argc < 3) 238 | syntax(); 239 | 240 | for (i=1; i < argc; ++i) { 241 | if (!strcmp(argv[i], "-server")) { 242 | server_mode = 1; 243 | } else if (!strcmp(argv[i], "-cert")) { 244 | if (i + 1 >= argc) { 245 | syntax(); 246 | } 247 | certificate_chain_file = argv[++i]; 248 | } else if (!strcmp(argv[i], "-key")) { 249 | if (i + 1 >= argc) { 250 | syntax(); 251 | } 252 | private_key_file = argv[++i]; 253 | } else if (argv[i][0] == '-') { 254 | syntax(); 255 | } else 256 | break; 257 | } 258 | 259 | if (server_mode) { 260 | if (!certificate_chain_file || !private_key_file) { 261 | fputs("Should specify certificate_chain_file and private_key_file when in server mode.\n", stderr); 262 | return 1; 263 | } 264 | } 265 | 266 | if (i+2 != argc) 267 | syntax(); 268 | 269 | memset(&listen_on_addr, 0, sizeof(listen_on_addr)); 270 | socklen = sizeof(listen_on_addr); 271 | if (evutil_parse_sockaddr_port(argv[i], 272 | (struct sockaddr*)&listen_on_addr, &socklen)<0) { 273 | int p = atoi(argv[i]); 274 | struct sockaddr_in *sin = (struct sockaddr_in*)&listen_on_addr; 275 | if (p < 1 || p > 65535) 276 | syntax(); 277 | sin->sin_port = htons(p); 278 | sin->sin_addr.s_addr = htonl(0x7f000001); 279 | sin->sin_family = AF_INET; 280 | socklen = sizeof(struct sockaddr_in); 281 | } 282 | 283 | memset(&connect_to_addr, 0, sizeof(connect_to_addr)); 284 | connect_to_addrlen = sizeof(connect_to_addr); 285 | if (evutil_parse_sockaddr_port(argv[i+1], 286 | (struct sockaddr*)&connect_to_addr, &connect_to_addrlen)<0) 287 | syntax(); 288 | 289 | base = event_base_new(); 290 | if (!base) { 291 | perror("event_base_new()"); 292 | return 1; 293 | } 294 | 295 | if (init_ssl(certificate_chain_file, private_key_file) != 0) { 296 | return 1; 297 | } 298 | 299 | listener = evconnlistener_new_bind(base, accept_cb, NULL, 300 | LEV_OPT_CLOSE_ON_FREE|LEV_OPT_CLOSE_ON_EXEC|LEV_OPT_REUSEABLE, 301 | -1, (struct sockaddr*)&listen_on_addr, socklen); 302 | 303 | if (! listener) { 304 | fprintf(stderr, "Couldn't open listener.\n"); 305 | event_base_free(base); 306 | return 1; 307 | } 308 | event_base_dispatch(base); 309 | 310 | evconnlistener_free(listener); 311 | event_base_free(base); 312 | 313 | return 0; 314 | } 315 | --------------------------------------------------------------------------------