├── .gitignore ├── .gitmodules ├── .travis.yml ├── default.nix ├── common.h ├── js ├── jquery.jsonp-2.1.4.min.js └── rfc5077-server.js ├── common.c ├── Makefile ├── rfc5077-server.html ├── README.md ├── common-client.c ├── openssl-client.c ├── gnutls-client.c ├── nss-client.c ├── rfc5077-stats.py ├── rfc5077-client.c ├── rfc5077-pcap.c ├── rfc5077-server.c └── ssl-handshake.svg /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *~ 3 | *.pyc 4 | *-client 5 | *-server 6 | *-pcap 7 | *.pem 8 | *.csv 9 | /csv/ 10 | /pcap/ 11 | /result -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "httpagentparser"] 2 | path = httpagentparser 3 | url = git://github.com/shon/httpagentparser.git 4 | [submodule "http-parser"] 5 | path = http-parser 6 | url = https://github.com/joyent/http-parser 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | sudo: false 3 | dist: bionic 4 | script: make 5 | addons: 6 | apt: 7 | packages: 8 | - libssl-dev 9 | - libgnutls28-dev 10 | - libnss3-dev 11 | - libpcap-dev 12 | - libev-dev 13 | - libnspr4-dev 14 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} 2 | }: 3 | 4 | pkgs.stdenv.mkDerivation rec { 5 | name = "rfc5077"; 6 | src = pkgs.nix-gitignore.gitignoreSource [] ./.; 7 | 8 | buildInputs = [ 9 | pkgs.openssl 10 | pkgs.gnutls 11 | pkgs.nss 12 | pkgs.libpcap 13 | pkgs.libev 14 | pkgs.pkg-config 15 | ]; 16 | buildPhase = "make"; 17 | installPhase = '' 18 | mkdir -p $out/bin 19 | cp *-client *-server *-pcap $out/bin 20 | ''; 21 | outputs = [ "out" ]; 22 | } 23 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* Client side */ 4 | extern int 5 | client(int, char * const [], 6 | int (*)(char *, char *, int, int, int, int, const char *, const char *, const char *, const char *)); 7 | extern int 8 | connect_socket(struct addrinfo *, char *, char *); 9 | extern struct addrinfo* 10 | solve(char *, char*); 11 | 12 | /* Display functions */ 13 | extern void 14 | start(const char *, ...) 15 | __attribute__ ((format (printf, 1, 2))); 16 | 17 | extern void 18 | end(const char *, ...) 19 | __attribute__ ((format (printf, 1, 2))); 20 | 21 | extern void 22 | fail(const char *, ...) 23 | __attribute__ ((format (printf, 1, 2))); 24 | 25 | extern void 26 | warn(const char *, ...) 27 | __attribute__ ((format (printf, 1, 2))); 28 | -------------------------------------------------------------------------------- /js/jquery.jsonp-2.1.4.min.js: -------------------------------------------------------------------------------- 1 | // jquery.jsonp 2.1.4 (c)2010 Julian Aubourg | MIT License 2 | // http://code.google.com/p/jquery-jsonp/ 3 | (function(e,b){function d(){}function t(C){c=[C]}function m(C){f.insertBefore(C,f.firstChild)}function l(E,C,D){return E&&E.apply(C.context||C,D)}function k(C){return/\?/.test(C)?"&":"?"}var n="async",s="charset",q="",A="error",r="_jqjsp",w="on",o=w+"click",p=w+A,a=w+"load",i=w+"readystatechange",z="removeChild",g=" 5 | 6 | 7 | 32 | Test for RFC 5077 support 33 | 34 | 35 |

Test for RFC 5077 support

36 | 37 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /js/rfc5077-server.js: -------------------------------------------------------------------------------- 1 | var rfc = function() { 2 | 3 | var ports = []; 4 | var step = 0; 5 | var steps = { 6 | /* Grab list of servers */ 7 | 1: function() { 8 | $.jsonp({ 9 | url: "servers?callback=?", 10 | success: function(data) { 11 | ports = data.servers; 12 | for (var i = 0; i < ports.length; i++) { 13 | var el = $("#ports") 14 | if (i > 0) el.append(", "); 15 | $("" 18 | + ports[i] + "").appendTo(el); 19 | } 20 | 21 | /* Setup the start button */ 22 | $("#start").click(function() { 23 | nextstep(); 24 | }); 25 | 26 | nextstep(); 27 | }, 28 | error: error 29 | }); 30 | }, 31 | /* Get cipher */ 32 | 3: function() { 33 | $.jsonp({ 34 | url: "session?callback=?", 35 | success: function(data) { 36 | var cipher = data.cipher; 37 | var version = data.version 38 | $("#cipher").text(cipher); 39 | $("#version").text(version); 40 | nextstep(); 41 | }, 42 | error: error 43 | }); 44 | }, 45 | /* Session ID without cache nor tickets */ 46 | 4: function() { 47 | checksessionid(ports[0], function(same) { 48 | var wotickets = same; 49 | $("#resume1").text(wotickets?"does":"does not"); 50 | nextstep(); 51 | }); 52 | }, 53 | /* Session ID without tickets */ 54 | 5: function() { 55 | checksessionid(ports[1], function(same) { 56 | var wotickets = same; 57 | $("#resume2").text(wotickets?"does":"does not"); 58 | nextstep(); 59 | }); 60 | }, 61 | /* Session ID with tickets */ 62 | 6: function() { 63 | checksessionid(ports[3], function(same) { 64 | var wtickets = same; 65 | $("#resume3").text(wtickets?"does":"does not"); 66 | nextstep(); 67 | }); 68 | }, 69 | /* Session ID with both cache and tickets */ 70 | 7: function() { 71 | checksessionid(ports[2], function(same) { 72 | var wtickets = same; 73 | $("#resume4").text(wtickets?"does":"does not"); 74 | nextstep(); 75 | }); 76 | } 77 | }; 78 | 79 | function nextstep() { 80 | $(".step" + step) 81 | .filter(".running") 82 | .removeClass("current") 83 | .hide(); 84 | $(".step" + step) 85 | .filter(".done").fadeIn(); 86 | step = step + 1; 87 | $(".step" + step) 88 | .filter(".running") 89 | .addClass("current") 90 | .fadeIn(200, function() { 91 | $('html, body') 92 | .stop() 93 | .animate({scrollTop: $('body').height()}, 94 | 800); 95 | }); 96 | if (steps[step] !== undefined) 97 | setTimeout(steps[step], 200); 98 | } 99 | 100 | function error() { 101 | $(".error").fadeIn(200); 102 | } 103 | 104 | function checksessionid(port, cb) { 105 | /* Ask for /session several time and check that session ID are 106 | * still the same */ 107 | var tries = 4; var errtries = 2; 108 | var sessions = []; 109 | var url = "https://" 110 | + location.hostname 111 | + ":" + port 112 | + "/session"; 113 | var dotry = function() { 114 | $.jsonp({ 115 | url: url + "?callback=?", 116 | success: function(data) { 117 | sessions.push(data.sessionid); 118 | tries = tries - 1; 119 | if (tries === 0) { 120 | /* Check if session ID are the same */ 121 | /* Skip the first result. This may be empty or 122 | * an old session. */ 123 | for (var i=2; i < sessions.length; i++) { 124 | if (sessions[i] !== sessions[1]) { 125 | cb(false); 126 | return; 127 | } 128 | } 129 | /* Maybe there is no session at all */ 130 | cb(sessions[1] !== ''); 131 | } else dotry(); 132 | }, 133 | error: function() { 134 | /* Because of some obscure bug in Internet 135 | Explorer SSL handshake may fail the first time 136 | we request something on server without cache 137 | and with tickets. */ 138 | errtries -= 1; 139 | if (errtries > 0) dotry() 140 | else error(); 141 | } 142 | }); 143 | }; 144 | dotry(); 145 | } 146 | 147 | $(function() { 148 | /* Fill navigator name */ 149 | $("#ua").text(navigator.userAgent); 150 | nextstep(); 151 | }); 152 | 153 | }(); 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Various tools for testing RFC 5077 2 | ================================== 3 | 4 | [RFC 5077](http://tools.ietf.org/html/rfc5077) is a session resumption 5 | mechanism for TLS without server-side state. You'll find here various 6 | tools related to testing availability of RFC 5077. 7 | 8 | This mechanism is an extension for TLS. If a client or a server does 9 | not support TLS, it does not support RFC 5077. 10 | 11 | Clients 12 | ------- 13 | 14 | The following clients are implemented: 15 | 16 | - `openssl-client` 17 | - `gnutls-client` 18 | - `nss-client` 19 | 20 | They all take an host and a port as argument. You need to use `-r` 21 | flag to really test reconnection. You can also add `-T` to disable 22 | ticket supports (RFC 5077) and `-S` to disable session ID 23 | support. However, disabling session ID may be difficult, therefore, it 24 | may not really have the expected effect. 25 | 26 | Only OpenSSL client is complete enough. GNU TLS does not allow easy 27 | display of session contents and NSS does not allow to check if a 28 | session was resumed. 29 | 30 | Additionally, `rfc5077-client` proposes some more advanced tests 31 | against a server or a pool of servers. It will try to reuse sessions 32 | with and without tickets and will query several time each IP of a pool 33 | of servers. Use this if you want to check support of SSL session 34 | resume of a server or a pool of servers. 35 | 36 | It is possible that those clients may fail if you don't have a working 37 | IPv6 connectivity. Get an IPv6 connectivity. ;-) 38 | 39 | Servers 40 | ------- 41 | 42 | `rfc5077-server` allows you to test support of RFC 5077 in the client 43 | of your choice. It will returns an HTML page containing some 44 | Javascript code to test browsers. You need to specify 4 ports. They 45 | will respectively behave as follow: 46 | 47 | 1. No session cache, no ticket support 48 | 2. Session cache, no ticket support 49 | 3. Session cache, ticket support 50 | 4. No session cache, ticket support 51 | 52 | While this server has some shortcoming, it should be relatively 53 | performant and you can try to bench it. It should also be secure 54 | enough to be put on the Internet. 55 | 56 | Misc 57 | ---- 58 | 59 | `rfc5077-pcap` will analyze SSL handshakes contained in PCAP files. It 60 | will try to detect "Client Hello". It will extract IP addresses, 61 | protocol version, Session ID, cipher suites, compression methods and 62 | detect the use of SNI extension and ticket extension. It should be 63 | used to determine how many clients support one cipher suite or how 64 | many clients support ticket extension. 65 | 66 | The CSV file generated by this program can then be used with 67 | `rfc5077-stats.py` that will produce some graphics (and also build a 68 | SQLite database that you can use to make queries). 69 | 70 | Getting Started 71 | --------------- 72 | 73 | If you've just cloned this from git, run the following to ensure that 74 | the submodules `http-parser` and `httpagentparser` are installed: 75 | 76 | - `git submodule init` 77 | - `git submodule update` 78 | 79 | Then run `make` to build the executables. This currently needs **OpenSSL 1.1**. 80 | If you have an older version, go back to branch `openssl-1.0`: 81 | 82 | - `git checkout openssl-1.0` 83 | 84 | Dependencies 85 | ------------ 86 | 87 | To compile these you will need a few dependencies that are the nss, 88 | openssl, gnutls, libpcap, libev and nspr headers and libraries: 89 | 90 | On Fedora the dependencies are: 91 | * openssl-devel 92 | * gnutls-devel 93 | * nss-devel 94 | * libpcap-devel 95 | * libev-devel 96 | * nspr-devel 97 | * pkgconfig 98 | 99 | On Debian, the dependencies can be installed with the following command: 100 | 101 | ```bash 102 | apt-get install libssl-dev gnutls-dev libnss3-dev libpcap-dev libev-dev libnspr4-dev pkg-config 103 | ``` 104 | 105 | On Osx the dependencies are: (which can be installed via homebrew) 106 | * openssl@1.1 107 | * gnutls 108 | * nss 109 | * libpcap 110 | * libev 111 | * pkg-config 112 | 113 | ```bash 114 | # install dependencies 115 | brew install openssl@1.1 gnutls nss libpcap libev pkg-config 116 | 117 | # openssl@1.1, nss, libpcap are keg-only we should export some env before make 118 | export PATH=$(brew --prefix)/opt/nss/bin:$PATH 119 | export PATH=$(brew --prefix)/opt/libpcap/bin:$PATH 120 | export PKG_CONFIG_PATH=$(brew --prefix)/opt/openssl@1.1/lib/pkgconfig:$PKG_CONFIG_PATH 121 | export PKG_CONFIG_PATH=$(brew --prefix)/opt/nss/lib/pkgconfig:$PKG_CONFIG_PATH 122 | export PKG_CONFIG_PATH=$(brew --prefix)/opt/libpcap/lib/pkgconfig:$PKG_CONFIG_PATH 123 | 124 | # compile 125 | make 126 | ``` 127 | -------------------------------------------------------------------------------- /common-client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vincent Bernat 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* Common client functions */ 18 | 19 | #include "common.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | /* Display usage for clients and exit */ 33 | static void 34 | usage(char * const name) { 35 | fail("Usage: %s [-r] [-R {number}] [-d {secs}] [-S] [-T] [-C {client_cert}] [-K {client_key}] [-U URI ] [-M METHOD] host port\n" 36 | "\n" 37 | " Connect to an SSL HTTP server and requests `/'\n" 38 | "\n" 39 | "Options:\n" 40 | "\t-r: reconnect (may be repeated)\n" 41 | "\t-R: number of reconnects\n" 42 | "\t-d: delay between each renegotiation in seconds\n" 43 | "\t-S: disable support for session identifier\n" 44 | "\t-T: disable support for tickets\n" 45 | "\t-C: use a client certificate for the connection and this specifies a certificate as a file in PEM format. Optionally the key can be here too\n" 46 | "\t-K: use the key {client_key}, a PEM formated key file, in the connection\n" 47 | "\t-U: use a different URI\n" 48 | "\t-M: use a different method\n" 49 | , name); 50 | } 51 | 52 | /* Parse arguments and call back connect function */ 53 | int client(int argc, char * const argv[], 54 | int (*connect)(char *, char *, int, int, int, int, 55 | const char *, const char *, const char *,const char *)) { 56 | int opt, status; 57 | int reconnect = 0; 58 | int use_sessionid = 1; 59 | int use_ticket = 1; 60 | int delay = 0; 61 | char *host = NULL; 62 | char *port = NULL; 63 | const char *client_cert = NULL; 64 | const char *client_key = NULL; 65 | const char *opt_uri = "/"; 66 | const char *opt_method = "GET"; 67 | 68 | /* Parse arguments */ 69 | opterr = 0; 70 | start("Parse arguments"); 71 | while ((opt = getopt(argc, argv, "rR:d:STC:K:U:M:")) != -1) { 72 | switch (opt) { 73 | case 'r': 74 | reconnect++; 75 | break; 76 | case 'R': 77 | reconnect = atoi(optarg); 78 | break; 79 | case 'S': 80 | use_sessionid = 0; 81 | break; 82 | case 'T': 83 | use_ticket = 0; 84 | break; 85 | case 'd': 86 | delay = atoi(optarg); 87 | break; 88 | case 'C': 89 | client_cert = optarg; 90 | break; 91 | case 'K': 92 | client_key = optarg; 93 | break; 94 | case 'U': 95 | opt_uri = optarg; 96 | break; 97 | case 'M': 98 | opt_method = optarg; 99 | break; 100 | default: 101 | usage(argv[0]); 102 | } 103 | } 104 | if (client_key && !client_cert) { 105 | fail("a client key_file is specified without a client_certificate file. If both are in the same file use -C"); 106 | } 107 | if (client_cert && !client_key) { 108 | client_key = client_cert; 109 | } 110 | if (optind != argc - 2) 111 | usage(argv[0]); 112 | 113 | host = argv[optind]; 114 | port = argv[optind + 1]; 115 | 116 | /* Callback */ 117 | status = connect(host, port, reconnect, use_sessionid, use_ticket, delay, client_cert, client_key, opt_uri, opt_method); 118 | end(NULL); 119 | return status; 120 | } 121 | 122 | struct addrinfo * 123 | solve(char *host, char *port) { 124 | int err; 125 | char name[INET6_ADDRSTRLEN]; 126 | struct addrinfo hints; 127 | struct addrinfo *result; 128 | 129 | start("Solve %s:%s", host, port); 130 | memset(&hints, 0, sizeof(struct addrinfo)); 131 | hints.ai_family = AF_UNSPEC; 132 | hints.ai_socktype = SOCK_STREAM; 133 | hints.ai_flags = 0; 134 | hints.ai_protocol = 0; 135 | if ((err = getaddrinfo(host, port, &hints, &result)) != 0) 136 | fail("Unable to solve ‘%s:%s’:\n%s", host, port, gai_strerror(err)); 137 | 138 | if ((err = getnameinfo(result->ai_addr, result->ai_addrlen, 139 | name, sizeof(name), NULL, 0, 140 | NI_NUMERICHOST)) != 0) 141 | fail("Unable to format ‘%s:%s’:\n%s", host, port, gai_strerror(err)); 142 | end("Will connect to %s", name); 143 | return result; 144 | } 145 | 146 | int 147 | connect_socket(struct addrinfo *result, char *host, char *port) { 148 | int s, err; 149 | start("Connect to %s:%s", host, port); 150 | if ((s = socket(result->ai_family, 151 | result->ai_socktype, 152 | result->ai_protocol)) == -1) 153 | fail("Unable to create socket:\n%m"); 154 | 155 | if ((err = connect(s, result->ai_addr, result->ai_addrlen)) == -1) 156 | fail("Unable to connect to ‘%s:%s’:\n%m", host, port); 157 | 158 | return s; 159 | } 160 | -------------------------------------------------------------------------------- /openssl-client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vincent Bernat 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* Simple client using OpenSSL as backend. */ 18 | 19 | #include "common.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | int 28 | connect_ssl(char *host, char *port, 29 | int reconnect, 30 | int use_sessionid, int use_ticket, 31 | int delay, 32 | const char *client_cert, const char *client_key, 33 | const char *opt_uri, const char *opt_method) { 34 | SSL_CTX* ctx; 35 | SSL* ssl; 36 | SSL_SESSION* ssl_session = NULL; 37 | int s, n; 38 | char buffer[256]; 39 | struct addrinfo* addr; 40 | int stat_reused=0; 41 | int stat_notreused=0; 42 | 43 | start("Initialize OpenSSL library"); 44 | SSL_load_error_strings(); 45 | SSL_library_init(); 46 | if ((ctx = SSL_CTX_new(TLS_client_method())) == NULL) 47 | fail("Unable to initialize SSL context:\n%s", 48 | ERR_error_string(ERR_get_error(), NULL)); 49 | 50 | if (client_cert || client_key) { 51 | if (SSL_CTX_use_certificate_chain_file(ctx,client_cert)==0) { 52 | fail("failed to read X509 certificate from file %s into PEM format",client_key); 53 | } 54 | } 55 | if (client_key) { 56 | if (SSL_CTX_use_PrivateKey_file(ctx,client_key,SSL_FILETYPE_PEM)==0) { 57 | fail("failed to read private key from file %s into PEM format",client_key); 58 | } 59 | } 60 | if (!use_ticket) { 61 | start("Disable use of session tickets (RFC 5077)"); 62 | SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET); 63 | } 64 | 65 | addr = solve(host, port); 66 | do { 67 | s = connect_socket(addr, host, port); 68 | start("Start TLS renegotiation"); 69 | if ((ssl = SSL_new(ctx)) == NULL) 70 | fail("Unable to create new SSL struct:\n%s", 71 | ERR_error_string(ERR_get_error(), NULL)); 72 | SSL_set_fd(ssl, s); 73 | if (ssl_session) { 74 | if (!SSL_set_session(ssl, ssl_session)) { 75 | fail("Unable to set session to previous one:\n%s", 76 | ERR_error_string(ERR_get_error(), NULL)); 77 | } 78 | } 79 | if (SSL_connect(ssl) != 1) 80 | fail("Unable to start TLS renegotiation:\n%s", 81 | ERR_error_string(ERR_get_error(), NULL)); 82 | 83 | start("Check if session was reused"); 84 | if (!SSL_session_reused(ssl) && ssl_session) 85 | warn("No session was reused."); 86 | else if (SSL_session_reused(ssl) && !ssl_session) { 87 | warn("Session was reused."); 88 | stat_notreused++; 89 | } 90 | else if (SSL_session_reused(ssl)) { 91 | end("SSL session correctly reused"); 92 | stat_reused++; 93 | } 94 | else 95 | end("SSL session was not used"); 96 | start("Get current session"); 97 | if (ssl_session) { 98 | SSL_SESSION_free(ssl_session); 99 | ssl_session = NULL; 100 | } 101 | if (!(ssl_session = SSL_get1_session(ssl))) 102 | warn("No session available"); 103 | else { 104 | BIO *mem = BIO_new(BIO_s_mem()); 105 | char *buf; 106 | if (SSL_SESSION_print(mem, ssl_session) != 1) 107 | fail("Unable to print session:\n%s", 108 | ERR_error_string(ERR_get_error(), NULL)); 109 | n = BIO_get_mem_data(mem, &buf); 110 | buf[n-1] = '\0'; 111 | end("Session content:\n%s", buf); 112 | BIO_free(mem); 113 | } 114 | if ((!use_sessionid && !use_ticket) || 115 | (!use_sessionid && !SSL_SESSION_has_ticket(ssl_session))) { 116 | SSL_SESSION_free(ssl_session); 117 | ssl_session = NULL; 118 | } 119 | 120 | start("Send HTTP %s for %s",opt_method, opt_uri); 121 | n = snprintf(buffer, sizeof(buffer), 122 | "%s %s HTTP/1.0\r\n" 123 | "Host: %s:%s\r\n" 124 | "\r\n", opt_method, opt_uri,host, port); 125 | if (n == -1 || n >= sizeof(buffer)) 126 | fail("Unable to build request to send"); 127 | if (SSL_write(ssl, buffer, strlen(buffer)) != strlen(buffer)) 128 | fail("SSL write request failed:\n%s", 129 | ERR_error_string(ERR_get_error(), NULL)); 130 | 131 | start("Get HTTP answer"); 132 | if ((n = SSL_read(ssl, buffer, sizeof(buffer) - 1)) <= 0) 133 | fail("SSL read request failed:\n%s", 134 | ERR_error_string(ERR_get_error(), NULL)); 135 | buffer[n] = '\0'; 136 | if (strchr(buffer, '\r')) 137 | *strchr(buffer, '\r') = '\0'; 138 | end("%s", buffer); 139 | 140 | start("End TLS connection"); 141 | SSL_shutdown(ssl); 142 | close(s); 143 | SSL_free(ssl); 144 | --reconnect; 145 | if (reconnect < 0) break; 146 | else { 147 | start("waiting %d seconds",delay); 148 | sleep(delay); 149 | } 150 | } while (1); 151 | 152 | start("STAT: reused: %d notreused: %d", stat_reused, stat_notreused); 153 | SSL_CTX_free(ctx); 154 | if ( stat_notreused > 0 ) 155 | return 1; 156 | return 0; 157 | } 158 | 159 | int 160 | main(int argc, char * const argv[]) { 161 | return client(argc, argv, connect_ssl); 162 | } 163 | -------------------------------------------------------------------------------- /gnutls-client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vincent Bernat 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* Simple client using GNU TLS as backend. */ 18 | 19 | #include "common.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #ifdef DEBUG 29 | void 30 | debug(int level, const char *s) { 31 | printf(s); 32 | } 33 | #endif 34 | 35 | int 36 | connect_ssl(char *host, char *port, 37 | int reconnect, 38 | int use_sessionid, int use_ticket, 39 | int delay, 40 | const char *client_cert, 41 | const char *client_key, 42 | const char *opt_uri, const char *opt_method) { 43 | struct addrinfo* addr; 44 | int err, s; 45 | char buffer[256]; 46 | gnutls_anon_client_credentials_t anoncred; 47 | gnutls_certificate_credentials_t xcred; 48 | gnutls_session_t session; 49 | char *session_data = NULL; 50 | size_t session_data_size = 0; 51 | char *session_id = NULL; 52 | size_t session_id_size = 0; 53 | char *session_id_hex = NULL; 54 | char *session_id_p = NULL; 55 | unsigned session_id_idx; 56 | const char *hex = "0123456789ABCDEF"; 57 | 58 | start("Initialize GNU TLS library"); 59 | if ((err = gnutls_global_init())) 60 | fail("Unable to initialize GNU TLS:\n%s", 61 | gnutls_strerror(err)); 62 | if ((err = gnutls_anon_allocate_client_credentials(&anoncred))) 63 | fail("Unable to allocate anonymous client credentials:\n%s", 64 | gnutls_strerror(err)); 65 | if ((err = gnutls_certificate_allocate_credentials(&xcred))) 66 | fail("Unable to allocate X509 credentials:\n%s", 67 | gnutls_strerror(err)); 68 | 69 | #ifdef DEBUG 70 | gnutls_global_set_log_function(debug); 71 | gnutls_global_set_log_level(10); 72 | #endif 73 | 74 | addr = solve(host, port); 75 | do { 76 | start("Initialize TLS session"); 77 | if ((err = gnutls_init(&session, GNUTLS_CLIENT))) 78 | fail("Unable to initialize the current session:\n%s", 79 | gnutls_strerror(err)); 80 | if ((err = gnutls_priority_set_direct(session, "PERFORMANCE:NORMAL:EXPORT", NULL))) 81 | fail("Unable to initialize cipher suites:\n%s", 82 | gnutls_strerror(err)); 83 | gnutls_dh_set_prime_bits(session, 512); 84 | if (client_cert == NULL) { 85 | if ((err = gnutls_credentials_set(session, GNUTLS_CRD_ANON, anoncred))) 86 | fail("Unable to set anonymous credentials for session:\n%s", 87 | gnutls_strerror(err)); 88 | } else { 89 | if ((err = gnutls_certificate_set_x509_key_file(xcred, client_cert, client_key, GNUTLS_X509_FMT_PEM))) { 90 | fail("failed to load x509 certificate from file %s or key from %s: %s",client_cert,client_key,gnutls_strerror(err)); 91 | } 92 | } 93 | if ((err = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, xcred))) 94 | fail("Unable to set credentials for session:\n%s", 95 | gnutls_strerror(err)); 96 | 97 | if (use_ticket) { 98 | start("Enable use of session tickets (RFC 5077)"); 99 | if (gnutls_session_ticket_enable_client(session)) 100 | fail("Unable to enable session tickets:\n%s", 101 | gnutls_strerror(err)); 102 | } 103 | 104 | if (session_data) { 105 | start("Copy old session"); 106 | if ((err = gnutls_session_set_data(session, session_data, session_data_size))) 107 | fail("Unable to set session to previous one:\n%s", 108 | gnutls_strerror(err)); 109 | } 110 | 111 | s = connect_socket(addr, host, port); 112 | start("Start TLS renegotiation"); 113 | gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t)(uintptr_t)s); 114 | if ((err = gnutls_handshake(session))) { 115 | fail("Unable to start TLS renegotiation:\n%s", 116 | gnutls_strerror(err)); 117 | } 118 | 119 | start("Check if session was reused"); 120 | if (!gnutls_session_is_resumed(session) && session_data) 121 | warn("No session was reused."); 122 | else if (gnutls_session_is_resumed(session) && !session_data) 123 | warn("Session was reused."); 124 | else if (gnutls_session_is_resumed(session)) 125 | end("SSL session correctly reused"); 126 | else 127 | end("SSL session was not used"); 128 | 129 | start("Get current session"); 130 | if (session_data) { 131 | free(session_data); session_data = NULL; 132 | } 133 | session_data_size = 8192; 134 | if ((err = gnutls_session_get_data(session, NULL, &session_data_size))) 135 | warn("No session available:\n%s", 136 | gnutls_strerror(err)); 137 | else { 138 | session_data = malloc(session_data_size); 139 | if (!session_data) fail("No memory available"); 140 | gnutls_session_get_data(session, session_data, &session_data_size); 141 | 142 | if ((err = gnutls_session_get_id( session, NULL, &session_id_size))) 143 | warn("No session id available:\n%s", 144 | gnutls_strerror(err)); 145 | session_id = malloc(session_id_size); 146 | if (!session_id) fail("No memory available"); 147 | else { 148 | if ((err = gnutls_session_get_id( session, session_id, &session_id_size))) 149 | warn("No session id available:\n%s", 150 | gnutls_strerror(err)); 151 | session_id_hex = malloc(session_id_size * 2 + 1); 152 | if (!session_id_hex) fail("No memory available"); 153 | else { 154 | for (session_id_p = session_id_hex, session_id_idx = 0; 155 | session_id_idx < session_id_size; 156 | ++session_id_idx) { 157 | *session_id_p++ = hex[ (session_id[session_id_idx] >> 4) & 0xf]; 158 | *session_id_p++ = hex[ session_id[session_id_idx] & 0xf]; 159 | } 160 | *session_id_p = '\0'; 161 | 162 | end("Session context:\nProtocol : %s\nCipher : %s\nKx : %s\nPSK : %s\nID : %s", 163 | gnutls_protocol_get_name( gnutls_protocol_get_version(session) ), 164 | gnutls_cipher_get_name( gnutls_cipher_get(session) ), 165 | gnutls_kx_get_name( gnutls_kx_get(session) ), 166 | gnutls_psk_server_get_username(session), 167 | session_id_hex 168 | ); 169 | free(session_id_hex); 170 | } 171 | free(session_id); 172 | } 173 | 174 | } 175 | if (!use_sessionid && !use_ticket) { 176 | free(session_data); session_data = NULL; 177 | } 178 | 179 | start("Send HTTP %s for %s",opt_method, opt_uri); 180 | err = snprintf(buffer, sizeof(buffer), 181 | "%s %s HTTP/1.0\r\n" 182 | "Host: %s:%s\r\n" 183 | "\r\n", opt_method, opt_uri, host, port); 184 | if (err == -1 || err >= sizeof(buffer)) 185 | fail("Unable to build request to send"); 186 | if (gnutls_record_send(session, buffer, strlen(buffer)) < 0) 187 | fail("SSL write request failed:\n%s", 188 | gnutls_strerror(err)); 189 | 190 | start("Get HTTP answer"); 191 | if ((err = gnutls_record_recv(session, buffer, sizeof(buffer) - 1)) <= 0) 192 | fail("SSL read request failed:\n%s", 193 | gnutls_strerror(err)); 194 | buffer[err] = '\0'; 195 | if (strchr(buffer, '\r')) 196 | *strchr(buffer, '\r') = '\0'; 197 | end("%s", buffer); 198 | 199 | start("End TLS connection"); 200 | gnutls_bye(session, GNUTLS_SHUT_RDWR); 201 | close(s); 202 | gnutls_deinit (session); 203 | --reconnect; 204 | if (reconnect < 0) break; 205 | else { 206 | start("waiting %d seconds",delay); 207 | sleep(delay); 208 | } 209 | } while (1); 210 | 211 | if (session_data) free(session_data); 212 | gnutls_anon_free_client_credentials(anoncred); 213 | gnutls_global_deinit(); 214 | return 0; 215 | } 216 | 217 | int 218 | main(int argc, char * const argv[]) { 219 | return client(argc, argv, connect_ssl); 220 | } 221 | -------------------------------------------------------------------------------- /nss-client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vincent Bernat 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* Simple client using NSS as backend. */ 18 | 19 | #include "common.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | /* For PR_ImportTCPSocket() */ 30 | #include 31 | 32 | /* See also: 33 | http://www.mail-archive.com/dev-tech-crypto@lists.mozilla.org/msg01208.html */ 34 | 35 | /* Convert error number to string. Copy from nss/cmd/lib/secutil.c */ 36 | static char * 37 | SECU_ErrorString(int16 err) { 38 | switch (err) { 39 | case 0: return "No error"; 40 | case SEC_ERROR_BAD_DATA: return "Bad data"; 41 | case SEC_ERROR_BAD_DATABASE: return "Problem with database"; 42 | case SEC_ERROR_BAD_DER: return "Problem with DER"; 43 | case SEC_ERROR_BAD_KEY: return "Problem with key"; 44 | case SEC_ERROR_BAD_PASSWORD: return "Incorrect password"; 45 | case SEC_ERROR_BAD_SIGNATURE: return "Bad signature"; 46 | case SEC_ERROR_EXPIRED_CERTIFICATE: return "Expired certificate"; 47 | case SEC_ERROR_INPUT_LEN: return "Problem with input length"; 48 | case SEC_ERROR_INVALID_ALGORITHM: return "Invalid algorithm"; 49 | case SEC_ERROR_INVALID_ARGS: return "Invalid arguments"; 50 | case SEC_ERROR_INVALID_AVA: return "Invalid AVA"; 51 | case SEC_ERROR_INVALID_TIME: return "Invalid time"; 52 | case SEC_ERROR_IO: return "Security I/O error"; 53 | case SEC_ERROR_LIBRARY_FAILURE: return "Library failure"; 54 | case SEC_ERROR_NO_MEMORY: return "Out of memory"; 55 | case SEC_ERROR_OLD_CRL: return "CRL is older than the current one"; 56 | case SEC_ERROR_OUTPUT_LEN: return "Problem with output length"; 57 | case SEC_ERROR_UNKNOWN_ISSUER: return "Unknown issuer"; 58 | case SEC_ERROR_UNTRUSTED_CERT: return "Untrusted certificate"; 59 | case SEC_ERROR_UNTRUSTED_ISSUER: return "Untrusted issuer"; 60 | case SSL_ERROR_BAD_CERTIFICATE: return "Bad certificate"; 61 | case SSL_ERROR_BAD_CLIENT: return "Bad client"; 62 | case SSL_ERROR_BAD_SERVER: return "Bad server"; 63 | case SSL_ERROR_EXPORT_ONLY_SERVER: return "Export only server"; 64 | case SSL_ERROR_NO_CERTIFICATE: return "No certificate"; 65 | case SSL_ERROR_NO_CYPHER_OVERLAP: return "No cypher overlap"; 66 | case SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE: return "Unsupported certificate type"; 67 | case SSL_ERROR_UNSUPPORTED_VERSION: return "Unsupported version"; 68 | case SSL_ERROR_US_ONLY_SERVER: return "U.S. only server"; 69 | case PR_IO_ERROR: return "I/O error"; 70 | case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: return "Expired Issuer Certificate"; 71 | case SEC_ERROR_REVOKED_CERTIFICATE: return "Revoked certificate"; 72 | case SEC_ERROR_NO_KEY: return "No private key in database for this cert"; 73 | case SEC_ERROR_CERT_NOT_VALID: return "Certificate is not valid"; 74 | case SEC_ERROR_EXTENSION_NOT_FOUND: return "Certificate extension was not found"; 75 | case SEC_ERROR_EXTENSION_VALUE_INVALID: return "Certificate extension value invalid"; 76 | case SEC_ERROR_CA_CERT_INVALID: return "Issuer certificate is invalid"; 77 | case SEC_ERROR_CERT_USAGES_INVALID: return "Certificate usages is invalid"; 78 | case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: return "Certificate has unknown critical extension"; 79 | case SEC_ERROR_PKCS7_BAD_SIGNATURE: return "Bad PKCS7 signature"; 80 | case SEC_ERROR_INADEQUATE_KEY_USAGE: return "Certificate not approved for this operation"; 81 | case SEC_ERROR_INADEQUATE_CERT_TYPE: return "Certificate not approved for this operation"; 82 | default: return "Unknown error"; 83 | } 84 | } 85 | 86 | static SECStatus 87 | nss_auth_cert_hook(void *arg, PRFileDesc *fd, PRBool checksig, 88 | PRBool isServer) 89 | { 90 | /* Bypass */ 91 | return SECSuccess; 92 | } 93 | 94 | int 95 | connect_ssl(char *host, char *port, 96 | int reconnect, 97 | int use_sessionid, int use_ticket, 98 | int delay, 99 | const char *client_cert, const char *client_key, 100 | const char *opt_uri, const char *opt_method) { 101 | SECStatus err; 102 | PRFileDesc *tcpSocket, *sslSocket; 103 | int s, n; 104 | char buffer[256]; 105 | struct addrinfo* addr; 106 | 107 | start("Initialize NSS library"); 108 | PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 1); 109 | if ((err = NSS_NoDB_Init(NULL)) != SECSuccess) 110 | fail("Unable to initialize NSS:\n%s", SECU_ErrorString(PR_GetError())); 111 | 112 | if (!use_ticket && !use_sessionid) { 113 | start("Disable tickets and session ID"); 114 | if ((err = SSL_OptionSetDefault(SSL_NO_CACHE, PR_TRUE)) != SECSuccess) 115 | fail("Unable to disable cache mechanism:\n%s", SECU_ErrorString(PR_GetError())); 116 | } 117 | 118 | if (use_ticket) { 119 | start("Enable session tickets (RFC 5077)"); 120 | if ((err = SSL_OptionSetDefault(SSL_ENABLE_SESSION_TICKETS, PR_TRUE)) != SECSuccess) 121 | fail("Unable to enable session tickets:\n%s", SECU_ErrorString(PR_GetError())); 122 | } 123 | 124 | start("Ask for US Domestic policy"); 125 | if ((err = NSS_SetDomesticPolicy()) != SECSuccess) 126 | fail("Unable to configure US domestic policy:\n%s", SECU_ErrorString(PR_GetError())); 127 | 128 | if (client_cert || client_key) { 129 | fail("Client certificates not supported"); 130 | } 131 | addr = solve(host, port); 132 | do { 133 | s = connect_socket(addr, host, port); 134 | 135 | start("Setup sockets"); 136 | if (!(tcpSocket = PR_ImportTCPSocket(s))) 137 | fail("Unable to convert socket:\n%s", SECU_ErrorString(PR_GetError())); 138 | if (!(sslSocket = SSL_ImportFD(NULL, tcpSocket))) 139 | fail("unable to enable SSL socket:\n%s", SECU_ErrorString(PR_GetError())); 140 | if ((err = SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE)) != SECSuccess) 141 | fail("Unable to setup handshake mode:\n%s", SECU_ErrorString(PR_GetError())); 142 | if ((err = SSL_OptionSet(sslSocket, SSL_ENABLE_FDX, PR_TRUE)) != SECSuccess) 143 | fail("Unable to setup full duplex mode:\n%s", SECU_ErrorString(PR_GetError())); 144 | if ((err = SSL_SetURL(sslSocket, host)) != SECSuccess) 145 | fail("Unable to register target host:\n%s", SECU_ErrorString(PR_GetError())); 146 | if ((err = SSL_AuthCertificateHook(sslSocket, nss_auth_cert_hook, NULL)) != SECSuccess) 147 | fail("Unable to register certificate check hook:\n%s", SECU_ErrorString(PR_GetError())); 148 | 149 | start("Start TLS renegotiation"); 150 | if ((err = SSL_ResetHandshake(sslSocket, PR_FALSE)) != SECSuccess) 151 | fail("Unable to renegotiation TLS (1/2):\n%s", SECU_ErrorString(PR_GetError())); 152 | if ((err = SSL_ForceHandshake(sslSocket)) != SECSuccess) 153 | fail("Unable to renegotiation TLS (2/2):\n%s", SECU_ErrorString(PR_GetError())); 154 | 155 | /* TODO: session resume check */ 156 | 157 | start("Send HTTP %s for %s",opt_method, opt_uri); 158 | n = snprintf(buffer, sizeof(buffer), 159 | "%s %s HTTP/1.0\r\n" 160 | "Host: %s:%s\r\n" 161 | "\r\n", opt_method, opt_uri, host, port); 162 | if (n == -1 || n >= sizeof(buffer)) 163 | fail("Unable to build request to send"); 164 | if ((err = PR_Write(sslSocket, buffer, n)) != n) 165 | fail("SSL write request failed:\n%s", SECU_ErrorString(PR_GetError())); 166 | 167 | start("Get HTTP answer"); 168 | if ((n = PR_Read(sslSocket, buffer, sizeof(buffer) - 1)) <= 0) 169 | fail("SSL read request failed:\n%s", SECU_ErrorString(PR_GetError())); 170 | buffer[n] = '\0'; 171 | if (strchr(buffer, '\r')) 172 | *strchr(buffer, '\r') = '\0'; 173 | end("%s", buffer); 174 | 175 | start("End TLS connection"); 176 | PR_Close(sslSocket); 177 | --reconnect; 178 | if (reconnect < 0) break; 179 | else { 180 | start("waiting %d seconds",delay); 181 | sleep(delay); 182 | } 183 | } while (1); 184 | SSL_ClearSessionCache(); 185 | NSS_Shutdown(); 186 | PR_Cleanup(); 187 | return 0; 188 | } 189 | 190 | int 191 | main(int argc, char * const argv[]) { 192 | return client(argc, argv, connect_ssl); 193 | } 194 | -------------------------------------------------------------------------------- /rfc5077-stats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | 4 | # Copyright (c) 2011 Vincent Bernat 5 | # 6 | # Permission to use, copy, modify, and/or distribute this software for any 7 | # purpose with or without fee is hereby granted, provided that the above 8 | # copyright notice and this permission notice appear in all copies. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | 18 | """ 19 | Generate some graphics from the output of `rfc5077-pcap`. 20 | """ 21 | 22 | import sys 23 | import os 24 | import gzip 25 | import time 26 | import cgi 27 | import sqlite3 28 | 29 | # httpagentparser 30 | try: 31 | import httpagentparser 32 | except ImportError: 33 | sys.path.append("httpagentparser") 34 | try: 35 | import httpagentparser 36 | except ImportError: 37 | print "[!] httpagentparser module not available" 38 | httpagentparser = None 39 | 40 | from matplotlib.pylab import * 41 | matplotlib.rcParams['font.size'] = 8 42 | 43 | data = sys.argv[1] 44 | browsers = len(sys.argv) > 2 and sys.argv[2] or None 45 | base = ".".join(data.split(".")[:-1]) 46 | sql = "%s.sqlite" % base 47 | pdf = "%s.pdf" % base 48 | 49 | conn = sqlite3.connect(sql) 50 | cur = conn.cursor() 51 | 52 | def create_database_hello(cur, data): 53 | print "[+] Build `hello` table" 54 | cur.execute("DROP TABLE IF EXISTS hello") 55 | cur.execute("CREATE TABLE hello (date INTEGER, client TEXT, server TEXT," 56 | " ssl2 INTEGER, version TEXT, sessionid TEXT, ciphers TEXT," 57 | " compression TEXT, servername TEXT, ticket INTEGER, ticketlen INTEGER)") 58 | cur.execute("DROP TABLE IF EXISTS ciphers") 59 | cur.execute("CREATE TABLE ciphers (client TEXT, cipher TEXT)") 60 | i = 0 61 | for row in gzip.open(data): 62 | row = row.strip() 63 | if i == 0: 64 | i = i + 1 65 | continue 66 | date, client, server, ssl2, version, sessionid, ciphers, \ 67 | compression, servername, ticket, ticketlen = row.split(";") 68 | cur.execute("INSERT INTO hello (date, client, server, ssl2, version," 69 | " sessionid, ciphers, compression, servername," 70 | " ticket, ticketlen) VALUES (?,?,?,?,?,?,?,?,?,?,?)", 71 | (int(float(date)), client, server, int(ssl2), version, sessionid, 72 | ciphers, compression, servername or None, int(ticket), int(ticketlen))) 73 | for cipher in ciphers.split(":"): 74 | cur.execute("INSERT INTO ciphers (client, cipher) VALUES (?,?)", 75 | (client, cipher)) 76 | i = i + 1 77 | 78 | def create_database_browsers(cur, browsers): 79 | print "[+] Build `browsers` table" 80 | cur.execute("DROP TABLE IF EXISTS browsers") 81 | cur.execute("CREATE TABLE browsers (ip TEXT, ua TEXT, name TEXT, os TEXT)") 82 | for row in gzip.open(browsers): 83 | row = row.strip() 84 | ip, ua = row.split(";", 1) 85 | if not httpagentparser: 86 | cur.execute("INSERT INTO browsers (ip, ua) VALUES (?,?)", (ip, ua)) 87 | else: 88 | os, name = httpagentparser.simple_detect(ua) 89 | cur.execute("INSERT INTO browsers (ip, ua, name, os) " 90 | " VALUES (?,?,?,?)", (ip, ua, name, os)) 91 | # Remove IP with several browsers 92 | cur.execute("DELETE FROM browsers WHERE ip IN " 93 | " (SELECT ip FROM (SELECT COUNT(ua) AS uas, ip FROM browsers GROUP BY ip) WHERE uas > 1);") 94 | 95 | def build_pdf(cur): 96 | print("[+] Build PDF") 97 | f = figure(num=None, figsize=(8.27, 11.69), dpi=100) 98 | 99 | # Plot 1: number of clients supporting resume with tickets 100 | print("[+] Plot 1") 101 | cur.execute("SELECT COUNT(client), ticket FROM hello WHERE ssl2 = 0 GROUP BY ticket ORDER by ticket") 102 | 103 | r = cur.fetchall() 104 | ax1 = subplot2grid((4, 2), (0, 0)) 105 | ax1.set_aspect(1./ax1.get_data_ratio()) 106 | pie((r[0][0], r[1][0]), 107 | explode=(0, 0.1), 108 | colors=("#FF7373", "#00CC00"), 109 | labels=("No tickets", "Tickets"), 110 | labeldistance=1.15, 111 | autopct='%1.1f%%', shadow=True) 112 | title("Support of RFC 5077") 113 | 114 | # Plot 2: number of clients supporting SNI 115 | print("[+] Plot 2") 116 | cur.execute("SELECT COUNT(client), sni FROM " 117 | " (SELECT client, CASE WHEN length(servername)>0 THEN 1 ELSE 0 END AS sni FROM hello WHERE ssl2 = 0)" 118 | " GROUP BY sni ORDER BY sni") 119 | 120 | r = cur.fetchall() 121 | ax2 = subplot2grid((4, 2), (0, 1)) 122 | ax2.set_aspect(1./ax2.get_data_ratio()) 123 | pie((r[0][0], r[1][0]), 124 | explode=(0, 0.1), 125 | colors=("#FF7373", "#00CC00"), 126 | labels=("No SNI", "SNI"), 127 | labeldistance=1.15, 128 | autopct='%1.1f%%', shadow=True) 129 | title("Server Name Indication support") 130 | 131 | # Plot 3: SSL version 132 | print("[+] Plot 3") 133 | cur.execute("SELECT COUNT(client) AS c, version FROM hello WHERE ssl2 = 0 GROUP BY version ORDER BY c DESC") 134 | 135 | r = cur.fetchall() 136 | ax3 = subplot2grid((4,2), (1, 0)) 137 | ax3.set_aspect(1./ax3.get_data_ratio()) 138 | pie([x[0] for x in r], 139 | colors=("#62E200", "#AA00A2", "#C9F600", "#E60042"), 140 | explode=(0.1,)*len(r), 141 | labels=[x[1] for x in r], 142 | labeldistance=1.15, 143 | autopct='%1.1f%%', shadow=True) 144 | title("Most common SSL versions") 145 | 146 | # Plot 4: Resumed sessions 147 | print("[+] Plot 4") 148 | cur.execute("SELECT COUNT(client), length(sessionid)>0, ticketlen>0 FROM hello " 149 | " GROUP BY length(sessionid)>0, ticketlen>0 " 150 | " ORDER BY length(sessionid)>0, ticketlen>0 ") 151 | 152 | r = dict([((x[1], x[2]), x[0]) for x in cur.fetchall()]) 153 | results = { "No resume": r[0,0], 154 | "Resume without tickets": r[1,0], 155 | "Resume with tickets": r.get((0,1),0) + r[1,1]} 156 | ax4 = subplot2grid((4,2), (1, 1)) 157 | ax4.set_aspect(1./ax3.get_data_ratio()) 158 | pie(results.values(), 159 | colors=("#62E200", "#AA00A2", "#C9F600", "#E60042"), 160 | explode=["with tickets" in x and 0.1 or 0 for x in results.keys()], 161 | labels=results.keys(), 162 | labeldistance=1.15, 163 | autopct='%1.1f%%', shadow=True) 164 | title("Session resumed") 165 | 166 | # Plot 5: most commonly proposed ciphers 167 | print("[+] Plot 5") 168 | cur.execute("SELECT COUNT(client) AS clients, cipher FROM ciphers GROUP BY cipher ORDER BY clients DESC LIMIT 15") 169 | 170 | pretty = dict( 171 | TLS_RSA_WITH_3DES_EDE_CBC_SHA="3DES+SHA", 172 | TLS_RSA_WITH_RC4_128_MD5="RC4+MD5", 173 | TLS_RSA_WITH_RC4_128_SHA="RC4+SHA", 174 | TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA="DHE-DSS-3DES+SHA", 175 | TLS_RSA_WITH_AES_128_CBC_SHA="AES128+SHA", 176 | TLS_RSA_WITH_AES_256_CBC_SHA="AES256+SHA", 177 | TLS_DHE_DSS_WITH_AES_128_CBC_SHA="DHE-DSS-AES128+SHA", 178 | TLS_DHE_DSS_WITH_AES_256_CBC_SHA="DHE-DSS-AES256+SHA", 179 | TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA="ECDHE-DSA-AES128+SHA", 180 | TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA="ECDHE-DSA-AES256+SHA", 181 | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA="ECDHE-AES128+SHA", 182 | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA="ECDHE-AES256+SHA", 183 | TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA="DHE-3DES+SHA", 184 | TLS_DHE_RSA_WITH_AES_128_CBC_SHA="DHE-AES128+SHA", 185 | TLS_DHE_RSA_WITH_AES_256_CBC_SHA="DHE-AES256+SHA", 186 | ) 187 | r = cur.fetchall() 188 | ax5 = subplot2grid((4,2), (2, 0)) 189 | bar(range(1, len(r)+1), 190 | [y[0] for y in r], 191 | color="#99FF00", 192 | width=0.7, 193 | align="center") 194 | ylabel("Requests") 195 | xticks(range(1, len(r) + 1), 196 | [pretty.get(y[1], y[1]) 197 | for y in r], 198 | rotation=90, size=7) 199 | title("Most common cipher suites") 200 | 201 | # Plot 6: Top ten browsers 202 | if httpagentparser: 203 | print("[+] Plot 6") 204 | cur.execute("SELECT b.name,COUNT(h.client) AS c FROM browsers b, hello h " 205 | " WHERE b.ip = h.client GROUP BY b.name" 206 | " ORDER BY c DESC LIMIT 15"); 207 | 208 | r = cur.fetchall() 209 | ax7 = subplot2grid((4,2), (2, 1)) 210 | bar(range(1, len(r)+1), 211 | [y[1] for y in r], 212 | color="#99FF00", 213 | width=0.7, 214 | align="center") 215 | xticks(range(1, len(r) + 1), 216 | [y[0] for y in r], 217 | rotation=90, size=6) 218 | title("Most popular browsers") 219 | 220 | # Statistics 221 | print("[+] Statistics") 222 | 223 | cur.execute("SELECT MAX(date), MIN(date) FROM hello") 224 | r = cur.fetchall() 225 | f.text(0.5, 0.15, "Start date:") 226 | f.text(0.7, 0.15, time.strftime("%a, %d %b %Y %H:%M:%S", 227 | time.localtime(r[0][1])), weight="demibold") 228 | f.text(0.5, 0.14, "End date:") 229 | f.text(0.7, 0.14, time.strftime("%a, %d %b %Y %H:%M:%S", 230 | time.localtime(r[0][0])), weight="demibold") 231 | cur.execute("SELECT COUNT(client) FROM hello") 232 | r = cur.fetchall() 233 | requests = r[0][0] 234 | f.text(0.5, 0.12, "Number of requests:") 235 | f.text(0.7, 0.12, "%d" % requests, weight="demibold") 236 | 237 | cur.execute("SELECT COUNT(client) FROM (SELECT DISTINCT client FROM hello)") 238 | r = cur.fetchall() 239 | clients = r[0][0] 240 | f.text(0.5, 0.11, "Number of clients:") 241 | f.text(0.7, 0.11, "%d" % clients, weight="demibold") 242 | 243 | cur.execute("SELECT COUNT(server) FROM (SELECT DISTINCT server FROM hello)") 244 | r = cur.fetchall() 245 | f.text(0.5, 0.10, "Number of servers:") 246 | f.text(0.7, 0.10, "%d" % r[0][0], weight="demibold") 247 | 248 | f.text(0.5, 0.09, "Average requests by client:") 249 | f.text(0.7, 0.09, "%d" % (requests/clients), weight="demibold") 250 | 251 | cur.execute("SELECT COUNT(client) FROM hello WHERE ssl2 = 1") 252 | r = cur.fetchall() 253 | f.text(0.5, 0.08, "Number of SSLv2 requests:") 254 | f.text(0.7, 0.08, "%d" % r[0][0], weight="demibold") 255 | 256 | cur.execute("SELECT COUNT(ua) FROM (SELECT DISTINCT ua FROM browsers)") 257 | r = cur.fetchall() 258 | f.text(0.5, 0.07, "Number of browsers UA:") 259 | f.text(0.7, 0.07, "%d" % r[0][0], weight="demibold") 260 | 261 | f.text(0.5, 0.05, "Source:") 262 | f.text(0.7, 0.05, "%s" % data, weight="demibold") 263 | 264 | print("[+] Build PDF") 265 | savefig(pdf) 266 | 267 | def build_html(cur): 268 | # Support of SNI and RFC 5077 269 | print "[+] Table of SNI and RFC 5077 support" 270 | 271 | tables = open("%s.html" % base, "w") 272 | tables.write(""" 273 | 274 | 275 | 276 | 281 | 319 | 320 | """) 321 | 322 | tables.write(u"

Table of SNI and RFC 5077 support

\n") 323 | cur.execute("SELECT name,os,GROUP_CONCAT(ua,'::::'),ticket,sni, SUM(c) FROM " 324 | " (SELECT DISTINCT b.name,b.os,b.ua,h.ticket,CASE WHEN length(servername)>0 THEN 1 ELSE 0 END AS sni, " 325 | " COUNT(h.client) AS c" 326 | " FROM browsers b, hello h" 327 | " WHERE b.ip = h.client AND h.ssl2 = 0" 328 | " GROUP BY b.name, b.os, b.ua, h.ticket, sni) " 329 | " GROUP BY name, os, ticket, sni" 330 | " ORDER BY name ASC, os ASC, c DESC, ticket ASC, sni ASC") 331 | 332 | tables.write(""" 333 | 334 | 335 | """) 336 | for row in cur: 337 | if row[0] == "Unknown Browser": 338 | continue 339 | browsers = row[2].split(u"::::") 340 | browsers.sort() 341 | tables.write((u" \n" % 342 | (cgi.escape(row[0]), cgi.escape(row[1]), 343 | u"
".join(browsers), 344 | row[3] and u"✔" or u"✘", 345 | row[4] and u"✔" or u"✘", 346 | row[5])).encode("utf-8")) 347 | 348 | tables.write("
Browser nameOSUARFC 5077SNIRequests
%s%s%s%s%s%d
") 349 | 350 | 351 | create_database_hello(cur, data) 352 | if browsers: 353 | create_database_browsers(cur, browsers) 354 | conn.commit() 355 | 356 | build_pdf(cur) 357 | if browsers: 358 | build_html(cur) 359 | -------------------------------------------------------------------------------- /rfc5077-client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vincent Bernat 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* RFC 5077 client test */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "common.h" 28 | 29 | #define PORT "https" 30 | #define TRY 5 31 | #define UA "Mozilla/5.0 (compatible; RFC5077-Checker/0.1; +https://github.com/vincentbernat/rfc5077)" 32 | 33 | /* Display usage and exit */ 34 | static void 35 | usage(char * const name) { 36 | fail("Usage: %s [-p {port}] [-s {sni name}] [-4] host [host ...]\n" 37 | "\n" 38 | " Check if a host or a pool of hosts support RFC 5077." 39 | "\n" 40 | "Options:\n" 41 | "\t-p: specify a port to connect to\n" 42 | "\t-s: specify an sni name\n" 43 | "\t-4: use only ipv4 addresses\n" 44 | , name); 45 | } 46 | 47 | /* Solve hostname to IPs */ 48 | static void 49 | resolve(const char *host, const char *port, struct addrinfo **result, int ipv4only) { 50 | int err, count; 51 | char name[INET6_ADDRSTRLEN*4]; 52 | char *p; 53 | struct addrinfo hints; 54 | struct addrinfo *next; 55 | 56 | start("Solve %s", host); 57 | memset(&hints, 0, sizeof(struct addrinfo)); 58 | hints.ai_family = ipv4only ? AF_INET : AF_UNSPEC; 59 | hints.ai_socktype = SOCK_STREAM; 60 | hints.ai_flags = 0; 61 | hints.ai_protocol = 0; 62 | if ((err = getaddrinfo(host, port, &hints, result)) != 0) 63 | fail("Unable to solve ‘%s:%s’:\n%s", host, port, gai_strerror(err)); 64 | 65 | count = 0; 66 | name[0] = '\0'; 67 | for (next = *result, p = name; next; next = next->ai_next, count++) { 68 | strcat(p, "\n"); p++; 69 | err = getnameinfo(next->ai_addr, next->ai_addrlen, 70 | p, sizeof(name) - (p - name), NULL, 0, 71 | NI_NUMERICHOST); 72 | if ((err == EAI_OVERFLOW) || 73 | (err == EAI_SYSTEM && errno == ENOSPC) || /* Odd... Bug #13166 */ 74 | (sizeof(name) - strlen(name)) < 2) { 75 | p--; *p = '\0'; 76 | while (sizeof(name) - strlen(name) < strlen("\n[...]") + 1) { 77 | p = strrchr(name, '\n'); 78 | *p = '\0'; 79 | } 80 | strncat(name, "\n[...]", sizeof(name) - strlen(name) - 1); 81 | name[sizeof(name) - 1] = '\0'; 82 | while ((next = next->ai_next)) count++; 83 | break; 84 | } 85 | if (err != 0) 86 | fail("Unable to format ‘%s:%s’:\n%s\n%m", host, port, gai_strerror(err)); 87 | p = p + strlen(p); 88 | } 89 | end("Got %d result%s:%s", count, (count > 1)?"s":"", name); 90 | } 91 | 92 | struct resultinfo { 93 | char *host; /* Tested host */ 94 | int try; /* Try number (0, 1, 2, ...) */ 95 | int session_reused; /* Is session reused? */ 96 | const char *version; /* SSL version */ 97 | SSL_SESSION *session; /* SSL session */ 98 | char *answer; /* HTTP answer */ 99 | struct resultinfo *next; /* Next result */ 100 | }; 101 | 102 | static void 103 | resultinfo_free(struct resultinfo *result) { 104 | struct resultinfo *r, *n; 105 | for (r = n = result; r; r = n = n->next) { 106 | if (r->host) free(r->host); 107 | if (r->session) SSL_SESSION_free(r->session); 108 | if (r->answer) free(r->answer); 109 | free(r); 110 | } 111 | } 112 | 113 | /* Display results as a table (best-effort) */ 114 | static void 115 | resultinfo_display(struct resultinfo *result) { 116 | unsigned i, n; 117 | char *buf; 118 | BIO *mem = BIO_new(BIO_s_mem()); 119 | SSL_SESSION *x; 120 | 121 | start("Display result set"); 122 | if (!result) fail("No memory"); 123 | if (BIO_printf(mem, 124 | " IP address │ Try │ Cipher │ Reuse │ " 125 | " SSL Session ID │ Master key │ Ticket │ Answer │ TLS version\n" 126 | "───────────────────────────────┼─────┼───────────────────────────────┼───────┼─" 127 | "────────────────────┼─────────────────────┼────────┼──────────────────────┼──────────") <= 0) 128 | goto err; 129 | 130 | for(; result; result = result->next) { 131 | x = result->session; 132 | if (BIO_printf(mem, "\n%-30s │ %3d │ %-29s │ %s │ ", 133 | result->host, 134 | result->try, 135 | SSL_CIPHER_get_name(SSL_SESSION_get0_cipher(x)), 136 | result->session_reused?"✔":"✘") <= 0) goto err; 137 | 138 | /* Session ID */ 139 | unsigned int id_len = 0; 140 | const unsigned char *id = SSL_SESSION_get_id(x, &id_len); 141 | if (id_len == 0) { 142 | if (BIO_printf(mem, "%19s", "") <= 0) 143 | goto err; 144 | } else { 145 | for (i = 0; (i < id_len) && (i < 9); i++) { 146 | if (BIO_printf(mem, "%02X", id[i]) <= 0) goto err; 147 | } 148 | if ((i != id_len) && 149 | (BIO_puts(mem, "…") <= 0)) goto err; 150 | } 151 | if (BIO_puts(mem, " │ ") <=0) goto err; 152 | 153 | /* Master key */ 154 | size_t master_key_len = SSL_SESSION_get_master_key(x, NULL, 0); 155 | if (master_key_len == 0) { 156 | if (BIO_printf(mem, "%19s", "") <= 0) 157 | goto err; 158 | } else { 159 | unsigned char *master_key = calloc(1, master_key_len); 160 | if (master_key == NULL) fail("Unable to allocate memory"); 161 | SSL_SESSION_get_master_key(x, master_key, master_key_len); 162 | for (i = 0; (i < master_key_len) && (i < 9); i++) { 163 | if (BIO_printf(mem, "%02X", master_key[i]) <= 0) goto err; 164 | } 165 | if ((i != master_key_len) && 166 | (BIO_puts(mem, "…") <= 0)) goto err; 167 | free(master_key); 168 | } 169 | if (BIO_printf(mem, " │ %s │ %-20s ", 170 | SSL_SESSION_has_ticket(x)?"✔":"✘", 171 | result->answer?result->answer:"") <= 0) goto err; 172 | 173 | if (BIO_printf(mem, "│ %s ", result->version) <= 0 ) goto err; 174 | } 175 | 176 | n = BIO_get_mem_data(mem, &buf); 177 | buf[n-1] = '\0'; 178 | end("%s",buf); 179 | BIO_free(mem); 180 | return; 181 | 182 | err: 183 | fail("BIO failure"); 184 | } 185 | 186 | /* Dump results in a CSV file */ 187 | static void 188 | resultinfo_write(const char *comment, struct resultinfo *result, 189 | FILE *output, int write_header) { 190 | SSL_SESSION *x; 191 | 192 | start("Dump results to file"); 193 | if (write_header) 194 | fprintf(output, 195 | "test;IP;try;version;cipher;compression;" 196 | "reuse;session id;master key;ticket;answer\n"); 197 | for(; result; result = result->next) { 198 | x = result->session; 199 | 200 | /* Comment, host and try number */ 201 | fprintf(output, "%s;%s;%d;", 202 | comment, 203 | result->host, 204 | result->try); 205 | 206 | /* Version, cipher, compression method */ 207 | fprintf(output, "%s;%s;%d", 208 | result->version, 209 | SSL_CIPHER_get_name(SSL_SESSION_get0_cipher(x)), 210 | SSL_SESSION_get_compress_id(x)); 211 | 212 | /* Session stuff */ 213 | unsigned int id_len = 0; 214 | const unsigned char *id = SSL_SESSION_get_id(x, &id_len); 215 | fprintf(output, ";%d;", result->session_reused?1:0); 216 | for (unsigned int i = 0; i < id_len; i++) 217 | fprintf(output, "%02X", id[i]); 218 | fprintf(output, ";"); 219 | size_t master_key_len = SSL_SESSION_get_master_key(x, NULL, 0); 220 | unsigned char *master_key = calloc(1, master_key_len); 221 | if (master_key == NULL) fail("Unable to allocate memory"); 222 | SSL_SESSION_get_master_key(x, master_key, master_key_len); 223 | for (size_t i = 0; i < master_key_len; i++) 224 | fprintf(output, "%02X", master_key[i]); 225 | free(master_key); 226 | fprintf(output, ";%d;%s\n", SSL_SESSION_has_ticket(x)?1:0, 227 | result->answer?result->answer:""); 228 | } 229 | return; 230 | } 231 | 232 | static struct resultinfo* 233 | tests(SSL_CTX *ctx, const char *port, struct addrinfo *hosts, const char *sni_name, int tickets) { 234 | SSL* ssl; 235 | SSL_SESSION* ssl_session = NULL; 236 | int s, err, n; 237 | char name[INET6_ADDRSTRLEN]; 238 | char buffer[256]; 239 | struct resultinfo *results = NULL, *r; 240 | struct resultinfo **p; 241 | 242 | p = &results; 243 | 244 | if (tickets) 245 | start("Run tests with use of tickets"); 246 | else 247 | start("Run tests without use of tickets"); 248 | 249 | for (struct addrinfo *current = hosts; 250 | current; 251 | current = current->ai_next) { 252 | 253 | /* For diagnostic purpose, we want to keep the IP address we test */ 254 | if ((err = getnameinfo(current->ai_addr, current->ai_addrlen, 255 | name, sizeof(name), NULL, 0, 256 | NI_NUMERICHOST))) 257 | fail("Unable to format IP address:\n%s\n%m", gai_strerror(err)); 258 | name[sizeof(name) - 1] = '\0'; 259 | 260 | for (int try = 0; try < TRY; try++) { 261 | 262 | /* Create socket and connect. */ 263 | if ((s = socket(current->ai_family, 264 | current->ai_socktype, 265 | current->ai_protocol)) == -1) 266 | fail("Unable to create socket for ‘%s’:\n%m", name); 267 | if ((err = connect(s, current->ai_addr, 268 | current->ai_addrlen)) == -1) 269 | fail("Unable to connect to ‘%s:%s’:\n%m", name, port); 270 | 271 | /* SSL handshake */ 272 | if ((ssl = SSL_new(ctx)) == NULL) 273 | fail("Unable to create new SSL struct:\n%s", 274 | ERR_error_string(ERR_get_error(), NULL)); 275 | if (sni_name) { 276 | if (SSL_set_tlsext_host_name(ssl, sni_name) != 1) { 277 | fail("Unable to set SNI name to %s", sni_name); 278 | } 279 | } 280 | SSL_set_fd(ssl, s); 281 | if (!tickets) SSL_set_options(ssl, SSL_OP_NO_TICKET); 282 | if (ssl_session) { 283 | if (!SSL_set_session(ssl, ssl_session)) { 284 | fail("Unable to set session to previous one:\n%s", 285 | ERR_error_string(ERR_get_error(), NULL)); 286 | } 287 | } 288 | if (SSL_connect(ssl) != 1) 289 | fail("Unable to start TLS renegotiation with ‘%s’:\n%s", 290 | name, 291 | ERR_error_string(ERR_get_error(), NULL)); 292 | 293 | r = malloc(sizeof(struct resultinfo)); 294 | if (r == NULL) fail("Unable to allocate memory"); 295 | r->host = strndup(name, sizeof(name)); 296 | r->try = try; 297 | r->session_reused = SSL_session_reused(ssl); 298 | r->version = SSL_get_version(ssl); 299 | r->answer = NULL; 300 | r->next = NULL; 301 | *p = r; 302 | p = &r->next; 303 | 304 | /* Send HTTP request */ 305 | n = snprintf(buffer, sizeof(buffer), 306 | "HEAD / HTTP/1.0\r\n" 307 | "User-Agent: " UA "\r\n" 308 | "\r\n"); 309 | if (n == -1 || n >= sizeof(buffer)) 310 | fail("Unable to build request to send to ‘%s’", name); 311 | if (SSL_write(ssl, buffer, strlen(buffer)) != strlen(buffer)) 312 | fail("SSL write request to ‘%s’ failed:\n%s", 313 | name, 314 | ERR_error_string(ERR_get_error(), NULL)); 315 | 316 | /* Read answer */ 317 | if ((n = SSL_read(ssl, buffer, sizeof(buffer) - 1)) <= 0) 318 | fail("SSL read request failed:\n%s", 319 | ERR_error_string(ERR_get_error(), NULL)); 320 | buffer[n] = '\0'; 321 | if (strchr(buffer, '\r')) 322 | *strchr(buffer, '\r') = '\0'; 323 | r->answer = strndup(buffer, sizeof(buffer)); 324 | 325 | /* Grab session to store it */ 326 | if (!(ssl_session = SSL_get1_session(ssl))) 327 | fail("No session available"); 328 | 329 | r->session = ssl_session; 330 | 331 | SSL_shutdown(ssl); 332 | close(s); 333 | SSL_free(ssl); 334 | } 335 | } 336 | return results; 337 | } 338 | 339 | int 340 | main(int argc, char * const argv[]) { 341 | int opt; 342 | char *port = PORT; 343 | int ipv4only = 0; 344 | char *sni_name = NULL; 345 | 346 | /* We need at least one host */ 347 | start("Check arguments"); 348 | 349 | while ((opt = getopt(argc, argv, "s:p:4")) != -1) { 350 | switch (opt) { 351 | case 's': 352 | sni_name = optarg; 353 | break; 354 | case 'p': 355 | port = optarg; 356 | break; 357 | case '4': 358 | ipv4only = 1; 359 | break; 360 | default: 361 | usage(argv[0]); 362 | } 363 | 364 | } 365 | if (argc <= optind) usage(argv[0]); 366 | 367 | /* Solve all hosts given on the command line */ 368 | int i; 369 | struct addrinfo *hosts, **next; 370 | next = &hosts; 371 | for (i = optind; i < argc; i++) { 372 | resolve(argv[i], port, next, ipv4only); 373 | next = &((*next)->ai_next); 374 | } 375 | 376 | if (sni_name) 377 | start("Using SNI name %s", sni_name); 378 | 379 | start("Prepare tests"); 380 | 381 | /* Initialize OpenSSL library */ 382 | SSL_CTX *ctx; 383 | SSL_load_error_strings(); 384 | SSL_library_init(); 385 | if ((ctx = SSL_CTX_new(TLS_client_method())) == NULL) 386 | fail("Unable to initialize SSL context:\n%s", 387 | ERR_error_string(ERR_get_error(), NULL)); 388 | 389 | /* Run tests without and with tickets and store them to a file */ 390 | struct resultinfo *results; 391 | FILE *output; 392 | char name[1024]; 393 | time_t now = time(NULL); 394 | int n; 395 | 396 | /* Build file name */ 397 | n = snprintf(name, sizeof(name), 398 | "rfc5077-output-%llu", (unsigned long long)now); 399 | if (n == -1 || n >= sizeof(name)) 400 | fail("Not possible..."); 401 | for (i = 1; i < argc; i++) { 402 | strncat(name, "-", sizeof(name) - 1); 403 | strncat(name, argv[i], sizeof(name) - 1); 404 | } 405 | strncat(name, ".csv", sizeof(name) - 1); 406 | if ((output = fopen(name, "w+")) == NULL) 407 | fail("Unable to create output file ‘%s’:\n%m", name); 408 | 409 | /* Run tests */ 410 | results = tests(ctx, port, hosts, sni_name, 0); 411 | resultinfo_display(results); 412 | resultinfo_write("Without tickets", results, output, 1); 413 | resultinfo_free(results); 414 | 415 | results = tests(ctx, port, hosts, sni_name, 1); 416 | resultinfo_display(results); 417 | resultinfo_write("With tickets", results, output, 0); 418 | resultinfo_free(results); 419 | 420 | fclose(output); 421 | SSL_CTX_free(ctx); 422 | freeaddrinfo(hosts); 423 | end(NULL); 424 | return 0; 425 | } 426 | -------------------------------------------------------------------------------- /rfc5077-pcap.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vincent Bernat 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* Parse SSL/TLS Client Hello to extract statistics. Should be feeded 18 | with PCAP files on stdin. */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "common.h" 28 | 29 | struct ip4hdr { 30 | u_int8_t ihlv; 31 | u_int8_t tos; 32 | u_int16_t tot_len; 33 | u_int16_t id; 34 | u_int16_t frag_off; 35 | u_int8_t ttl; 36 | u_int8_t protocol; 37 | u_int16_t check; 38 | u_int32_t saddr; 39 | u_int32_t daddr; 40 | /* Options */ 41 | } __attribute__ ((__packed__)); 42 | 43 | struct tcphdr { 44 | u_int16_t source; 45 | u_int16_t dest; 46 | u_int32_t seq; 47 | u_int32_t ack_seq; 48 | u_int16_t flags; 49 | u_int16_t window; 50 | u_int16_t checksum; 51 | u_int16_t urg; 52 | /* Options */ 53 | } __attribute__ ((__packed__)); 54 | 55 | struct value_string { 56 | u_int32_t value; 57 | const char *string; 58 | }; 59 | 60 | /* Stolen from Wireshark */ 61 | const struct value_string ssl_compression_method[] = { 62 | { 0, "NULL" }, 63 | { 1, "DEFLATE" }, 64 | { 64, "LZS" }, 65 | { 0x00, NULL } 66 | }; 67 | /* Also stolen from Wireshark */ 68 | static const struct value_string ssl_cipher_suites[] = { 69 | { 0x000000, "TLS_NULL_WITH_NULL_NULL" }, 70 | { 0x000001, "TLS_RSA_WITH_NULL_MD5" }, 71 | { 0x000002, "TLS_RSA_WITH_NULL_SHA" }, 72 | { 0x000003, "TLS_RSA_EXPORT_WITH_RC4_40_MD5" }, 73 | { 0x000004, "TLS_RSA_WITH_RC4_128_MD5" }, 74 | { 0x000005, "TLS_RSA_WITH_RC4_128_SHA" }, 75 | { 0x000006, "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5" }, 76 | { 0x000007, "TLS_RSA_WITH_IDEA_CBC_SHA" }, 77 | { 0x000008, "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA" }, 78 | { 0x000009, "TLS_RSA_WITH_DES_CBC_SHA" }, 79 | { 0x00000a, "TLS_RSA_WITH_3DES_EDE_CBC_SHA" }, 80 | { 0x00000b, "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA" }, 81 | { 0x00000c, "TLS_DH_DSS_WITH_DES_CBC_SHA" }, 82 | { 0x00000d, "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA" }, 83 | { 0x00000e, "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA" }, 84 | { 0x00000f, "TLS_DH_RSA_WITH_DES_CBC_SHA" }, 85 | { 0x000010, "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA" }, 86 | { 0x000011, "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA" }, 87 | { 0x000012, "TLS_DHE_DSS_WITH_DES_CBC_SHA" }, 88 | { 0x000013, "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA" }, 89 | { 0x000014, "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA" }, 90 | { 0x000015, "TLS_DHE_RSA_WITH_DES_CBC_SHA" }, 91 | { 0x000016, "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA" }, 92 | { 0x000017, "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5" }, 93 | { 0x000018, "TLS_DH_anon_WITH_RC4_128_MD5" }, 94 | { 0x000019, "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA" }, 95 | { 0x00001a, "TLS_DH_anon_WITH_DES_CBC_SHA" }, 96 | { 0x00001b, "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA" }, 97 | { 0x00001c, "SSL_FORTEZZA_KEA_WITH_NULL_SHA" }, 98 | { 0x00001d, "SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA" }, 99 | #if 0 100 | { 0x00001e, "SSL_FORTEZZA_KEA_WITH_RC4_128_SHA" }, 101 | #endif 102 | /* RFC 2712 */ 103 | { 0x00001E, "TLS_KRB5_WITH_DES_CBC_SHA" }, 104 | { 0x00001F, "TLS_KRB5_WITH_3DES_EDE_CBC_SHA" }, 105 | { 0x000020, "TLS_KRB5_WITH_RC4_128_SHA" }, 106 | { 0x000021, "TLS_KRB5_WITH_IDEA_CBC_SHA" }, 107 | { 0x000022, "TLS_KRB5_WITH_DES_CBC_MD5" }, 108 | { 0x000023, "TLS_KRB5_WITH_3DES_EDE_CBC_MD5" }, 109 | { 0x000024, "TLS_KRB5_WITH_RC4_128_MD5" }, 110 | { 0x000025, "TLS_KRB5_WITH_IDEA_CBC_MD5" }, 111 | { 0x000026, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA" }, 112 | { 0x000027, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA" }, 113 | { 0x000028, "TLS_KRB5_EXPORT_WITH_RC4_40_SHA" }, 114 | { 0x000029, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5" }, 115 | { 0x00002A, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5" }, 116 | { 0x00002B, "TLS_KRB5_EXPORT_WITH_RC4_40_MD5" }, 117 | /* RFC 4785 */ 118 | { 0x00002C, "TLS_PSK_WITH_NULL_SHA" }, 119 | { 0x00002D, "TLS_DHE_PSK_WITH_NULL_SHA" }, 120 | { 0x00002E, "TLS_RSA_PSK_WITH_NULL_SHA" }, 121 | /* RFC 5246 */ 122 | { 0x00002f, "TLS_RSA_WITH_AES_128_CBC_SHA" }, 123 | { 0x000030, "TLS_DH_DSS_WITH_AES_128_CBC_SHA" }, 124 | { 0x000031, "TLS_DH_RSA_WITH_AES_128_CBC_SHA" }, 125 | { 0x000032, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA" }, 126 | { 0x000033, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA" }, 127 | { 0x000034, "TLS_DH_anon_WITH_AES_128_CBC_SHA" }, 128 | { 0x000035, "TLS_RSA_WITH_AES_256_CBC_SHA" }, 129 | { 0x000036, "TLS_DH_DSS_WITH_AES_256_CBC_SHA" }, 130 | { 0x000037, "TLS_DH_RSA_WITH_AES_256_CBC_SHA" }, 131 | { 0x000038, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA" }, 132 | { 0x000039, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA" }, 133 | { 0x00003A, "TLS_DH_anon_WITH_AES_256_CBC_SHA" }, 134 | { 0x00003B, "TLS_RSA_WITH_NULL_SHA256" }, 135 | { 0x00003C, "TLS_RSA_WITH_AES_128_CBC_SHA256" }, 136 | { 0x00003D, "TLS_RSA_WITH_AES_256_CBC_SHA256" }, 137 | { 0x00003E, "TLS_DH_DSS_WITH_AES_128_CBC_SHA256" }, 138 | { 0x00003F, "TLS_DH_RSA_WITH_AES_128_CBC_SHA256" }, 139 | { 0x000040, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256" }, 140 | { 0x000041, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA" }, 141 | { 0x000042, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA" }, 142 | { 0x000043, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA" }, 143 | { 0x000044, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA" }, 144 | { 0x000045, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA" }, 145 | { 0x000046, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA" }, 146 | { 0x000047, "TLS_ECDH_ECDSA_WITH_NULL_SHA" }, 147 | { 0x000048, "TLS_ECDH_ECDSA_WITH_RC4_128_SHA" }, 148 | { 0x000049, "TLS_ECDH_ECDSA_WITH_DES_CBC_SHA" }, 149 | { 0x00004A, "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA" }, 150 | { 0x00004B, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA" }, 151 | { 0x00004C, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA" }, 152 | { 0x000060, "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5" }, 153 | { 0x000061, "TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5" }, 154 | { 0x000062, "TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA" }, 155 | { 0x000063, "TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA" }, 156 | { 0x000064, "TLS_RSA_EXPORT1024_WITH_RC4_56_SHA" }, 157 | { 0x000065, "TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA" }, 158 | { 0x000066, "TLS_DHE_DSS_WITH_RC4_128_SHA" }, 159 | { 0x000067, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256" }, 160 | { 0x000068, "TLS_DH_DSS_WITH_AES_256_CBC_SHA256" }, 161 | { 0x000069, "TLS_DH_RSA_WITH_AES_256_CBC_SHA256" }, 162 | { 0x00006A, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256" }, 163 | { 0x00006B, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256" }, 164 | { 0x00006C, "TLS_DH_anon_WITH_AES_128_CBC_SHA256" }, 165 | { 0x00006D, "TLS_DH_anon_WITH_AES_256_CBC_SHA256" }, 166 | /* 0x00,0x6E-83 Unassigned */ 167 | { 0x000084, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA" }, 168 | { 0x000085, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA" }, 169 | { 0x000086, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA" }, 170 | { 0x000087, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA" }, 171 | { 0x000088, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA" }, 172 | { 0x000089, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA" }, 173 | /* RFC 4279 */ 174 | { 0x00008A, "TLS_PSK_WITH_RC4_128_SHA" }, 175 | { 0x00008B, "TLS_PSK_WITH_3DES_EDE_CBC_SHA" }, 176 | { 0x00008C, "TLS_PSK_WITH_AES_128_CBC_SHA" }, 177 | { 0x00008D, "TLS_PSK_WITH_AES_256_CBC_SHA" }, 178 | { 0x00008E, "TLS_DHE_PSK_WITH_RC4_128_SHA" }, 179 | { 0x00008F, "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA" }, 180 | { 0x000090, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA" }, 181 | { 0x000091, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA" }, 182 | { 0x000092, "TLS_RSA_PSK_WITH_RC4_128_SHA" }, 183 | { 0x000093, "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA" }, 184 | { 0x000094, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA" }, 185 | { 0x000095, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA" }, 186 | /* RFC 4162 */ 187 | { 0x000096, "TLS_RSA_WITH_SEED_CBC_SHA" }, 188 | { 0x000097, "TLS_DH_DSS_WITH_SEED_CBC_SHA" }, 189 | { 0x000098, "TLS_DH_RSA_WITH_SEED_CBC_SHA" }, 190 | { 0x000099, "TLS_DHE_DSS_WITH_SEED_CBC_SHA" }, 191 | { 0x00009A, "TLS_DHE_RSA_WITH_SEED_CBC_SHA" }, 192 | { 0x00009B, "TLS_DH_anon_WITH_SEED_CBC_SHA" }, 193 | /* RFC 5288 */ 194 | { 0x00009C, "TLS_RSA_WITH_AES_128_GCM_SHA256" }, 195 | { 0x00009D, "TLS_RSA_WITH_AES_256_GCM_SHA384" }, 196 | { 0x00009E, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256" }, 197 | { 0x00009F, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384" }, 198 | { 0x0000A0, "TLS_DH_RSA_WITH_AES_128_GCM_SHA256" }, 199 | { 0x0000A1, "TLS_DH_RSA_WITH_AES_256_GCM_SHA384" }, 200 | { 0x0000A2, "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256" }, 201 | { 0x0000A3, "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384" }, 202 | { 0x0000A4, "TLS_DH_DSS_WITH_AES_128_GCM_SHA256" }, 203 | { 0x0000A5, "TLS_DH_DSS_WITH_AES_256_GCM_SHA384" }, 204 | { 0x0000A6, "TLS_DH_anon_WITH_AES_128_GCM_SHA256" }, 205 | { 0x0000A7, "TLS_DH_anon_WITH_AES_256_GCM_SHA384" }, 206 | /* RFC 5487 */ 207 | { 0x0000A8, "TLS_PSK_WITH_AES_128_GCM_SHA256" }, 208 | { 0x0000A9, "TLS_PSK_WITH_AES_256_GCM_SHA384" }, 209 | { 0x0000AA, "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256" }, 210 | { 0x0000AB, "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384" }, 211 | { 0x0000AC, "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256" }, 212 | { 0x0000AD, "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384" }, 213 | { 0x0000AE, "TLS_PSK_WITH_AES_128_CBC_SHA256" }, 214 | { 0x0000AF, "TLS_PSK_WITH_AES_256_CBC_SHA384" }, 215 | { 0x0000B0, "TLS_PSK_WITH_NULL_SHA256" }, 216 | { 0x0000B1, "TLS_PSK_WITH_NULL_SHA384" }, 217 | { 0x0000B2, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256" }, 218 | { 0x0000B3, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384" }, 219 | { 0x0000B4, "TLS_DHE_PSK_WITH_NULL_SHA256" }, 220 | { 0x0000B5, "TLS_DHE_PSK_WITH_NULL_SHA384" }, 221 | { 0x0000B6, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256" }, 222 | { 0x0000B7, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384" }, 223 | { 0x0000B8, "TLS_RSA_PSK_WITH_NULL_SHA256" }, 224 | { 0x0000B9, "TLS_RSA_PSK_WITH_NULL_SHA384" }, 225 | /* From RFC 5932 */ 226 | { 0x0000BA, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256" }, 227 | { 0x0000BB, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256" }, 228 | { 0x0000BC, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256" }, 229 | { 0x0000BD, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256" }, 230 | { 0x0000BE, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256" }, 231 | { 0x0000BF, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256" }, 232 | { 0x0000C0, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256" }, 233 | { 0x0000C1, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256" }, 234 | { 0x0000C2, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256" }, 235 | { 0x0000C3, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256" }, 236 | { 0x0000C4, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256" }, 237 | { 0x0000C5, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256" }, 238 | /* 0x00,0xC6-FE Unassigned */ 239 | { 0x0000FF, "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" }, 240 | /* 0x01-BF,* Unassigned */ 241 | /* From RFC 4492 */ 242 | { 0x00c001, "TLS_ECDH_ECDSA_WITH_NULL_SHA" }, 243 | { 0x00c002, "TLS_ECDH_ECDSA_WITH_RC4_128_SHA" }, 244 | { 0x00c003, "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA" }, 245 | { 0x00c004, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA" }, 246 | { 0x00c005, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA" }, 247 | { 0x00c006, "TLS_ECDHE_ECDSA_WITH_NULL_SHA" }, 248 | { 0x00c007, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA" }, 249 | { 0x00c008, "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA" }, 250 | { 0x00c009, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" }, 251 | { 0x00c00a, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" }, 252 | { 0x00c00b, "TLS_ECDH_RSA_WITH_NULL_SHA" }, 253 | { 0x00c00c, "TLS_ECDH_RSA_WITH_RC4_128_SHA" }, 254 | { 0x00c00d, "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA" }, 255 | { 0x00c00e, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA" }, 256 | { 0x00c00f, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA" }, 257 | { 0x00c010, "TLS_ECDHE_RSA_WITH_NULL_SHA" }, 258 | { 0x00c011, "TLS_ECDHE_RSA_WITH_RC4_128_SHA" }, 259 | { 0x00c012, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA" }, 260 | { 0x00c013, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" }, 261 | { 0x00c014, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" }, 262 | { 0x00c015, "TLS_ECDH_anon_WITH_NULL_SHA" }, 263 | { 0x00c016, "TLS_ECDH_anon_WITH_RC4_128_SHA" }, 264 | { 0x00c017, "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA" }, 265 | { 0x00c018, "TLS_ECDH_anon_WITH_AES_128_CBC_SHA" }, 266 | { 0x00c019, "TLS_ECDH_anon_WITH_AES_256_CBC_SHA" }, 267 | /* RFC 5054 */ 268 | { 0x00C01A, "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA" }, 269 | { 0x00C01B, "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA" }, 270 | { 0x00C01C, "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA" }, 271 | { 0x00C01D, "TLS_SRP_SHA_WITH_AES_128_CBC_SHA" }, 272 | { 0x00C01E, "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA" }, 273 | { 0x00C01F, "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA" }, 274 | { 0x00C020, "TLS_SRP_SHA_WITH_AES_256_CBC_SHA" }, 275 | { 0x00C021, "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA" }, 276 | { 0x00C022, "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA" }, 277 | /* RFC 5589 */ 278 | { 0x00C023, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" }, 279 | { 0x00C024, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384" }, 280 | { 0x00C025, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256" }, 281 | { 0x00C026, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384" }, 282 | { 0x00C027, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" }, 283 | { 0x00C028, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384" }, 284 | { 0x00C029, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256" }, 285 | { 0x00C02A, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384" }, 286 | { 0x00C02B, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" }, 287 | { 0x00C02C, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" }, 288 | { 0x00C02D, "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256" }, 289 | { 0x00C02E, "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384" }, 290 | { 0x00C02F, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" }, 291 | { 0x00C030, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" }, 292 | { 0x00C031, "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256" }, 293 | { 0x00C032, "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384" }, 294 | /* RFC 5489 */ 295 | { 0x00C033, "TLS_ECDHE_PSK_WITH_RC4_128_SHA" }, 296 | { 0x00C034, "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA" }, 297 | { 0x00C035, "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA" }, 298 | { 0x00C036, "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA" }, 299 | { 0x00C037, "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256" }, 300 | { 0x00C038, "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384" }, 301 | { 0x00C039, "TLS_ECDHE_PSK_WITH_NULL_SHA" }, 302 | { 0x00C03A, "TLS_ECDHE_PSK_WITH_NULL_SHA256" }, 303 | { 0x00C03B, "TLS_ECDHE_PSK_WITH_NULL_SHA384" }, 304 | /* 0xC0,0x3C-FF Unassigned 305 | 0xC1-FD,* Unassigned 306 | 0xFE,0x00-FD Unassigned 307 | 0xFE,0xFE-FF Reserved to avoid conflicts with widely deployed implementations [Pasi_Eronen] 308 | 0xFF,0x00-FF Reserved for Private Use [RFC5246] 309 | */ 310 | 311 | /* these from http://www.mozilla.org/projects/ 312 | security/pki/nss/ssl/fips-ssl-ciphersuites.html */ 313 | { 0x00fefe, "SSL_RSA_FIPS_WITH_DES_CBC_SHA"}, 314 | { 0x00feff, "SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA" }, 315 | { 0x00ffe0, "SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA" }, 316 | { 0x00ffe1, "SSL_RSA_FIPS_WITH_DES_CBC_SHA"}, 317 | /* note that ciphersuites of {0x00????} are TLS cipher suites in 318 | * a sslv2 client hello message; the ???? above is the two-byte 319 | * tls cipher suite id 320 | */ 321 | 322 | { 0x010080, "SSL2_RC4_128_WITH_MD5" }, 323 | { 0x020080, "SSL2_RC4_128_EXPORT40_WITH_MD5" }, 324 | { 0x030080, "SSL2_RC2_CBC_128_CBC_WITH_MD5" }, 325 | { 0x040080, "SSL2_RC2_CBC_128_CBC_WITH_MD5" }, 326 | { 0x050080, "SSL2_IDEA_128_CBC_WITH_MD5" }, 327 | { 0x060040, "SSL2_DES_64_CBC_WITH_MD5" }, 328 | { 0x0700c0, "SSL2_DES_192_EDE3_CBC_WITH_MD5" }, 329 | { 0x080080, "SSL2_RC4_64_WITH_MD5" }, 330 | 331 | /* Microsoft's old PCT protocol. These are from Eric Rescorla's 332 | book "SSL and TLS" */ 333 | { 0x800001, "PCT_SSL_CERT_TYPE | PCT1_CERT_X509" }, 334 | { 0x800003, "PCT_SSL_CERT_TYPE | PCT1_CERT_X509_CHAIN" }, 335 | { 0x810001, "PCT_SSL_HASH_TYPE | PCT1_HASH_MD5" }, 336 | { 0x810003, "PCT_SSL_HASH_TYPE | PCT1_HASH_SHA" }, 337 | { 0x820001, "PCT_SSL_EXCH_TYPE | PCT1_EXCH_RSA_PKCS1" }, 338 | { 0x830004, "PCT_SSL_CIPHER_TYPE_1ST_HALF | PCT1_CIPHER_RC4" }, 339 | { 0x842840, "PCT_SSL_CIPHER_TYPE_2ND_HALF | PCT1_ENC_BITS_40 | PCT1_MAC_BITS_128" }, 340 | { 0x848040, "PCT_SSL_CIPHER_TYPE_2ND_HALF | PCT1_ENC_BITS_128 | PCT1_MAC_BITS_128" }, 341 | { 0x8f8001, "PCT_SSL_COMPAT | PCT_VERSION_1" }, 342 | { 0x00, NULL } 343 | }; 344 | 345 | struct hello { 346 | char client[INET6_ADDRSTRLEN]; 347 | char server[INET6_ADDRSTRLEN]; 348 | char ssl2; /* Inside a SSL2 packet */ 349 | unsigned char vmajor; /* Major SSL version */ 350 | unsigned char vminor; /* Minor SSL version */ 351 | int sessionidlen; 352 | unsigned char sessionid[32]; /* Session ID */ 353 | char *ciphers; /* List of cipher suites */ 354 | char *compression; /* List of compression methods */ 355 | char *servername; /* TLS extension: server name */ 356 | char ticket; /* Ticket extension present */ 357 | int ticketlen; /* Ticket length */ 358 | }; 359 | 360 | static void 361 | append(char **to, const char *what) { 362 | if (!*to) { 363 | if ((*to = strdup(what)) == NULL) 364 | fail("Not enough memory"); 365 | } else { 366 | char *new; 367 | if (asprintf(&new, "%s:%s", *to, what) == -1) 368 | fail("Not enough memory"); 369 | free(*to); 370 | *to = new; 371 | } 372 | } 373 | 374 | static struct hello* 375 | parse(const u_char *data, int len) { 376 | static struct hello hello; 377 | memset(&hello, 0, sizeof(hello)); 378 | 379 | /* Ethernet header */ 380 | if (len < 14) return NULL; 381 | int proto; 382 | memcpy(&proto, data + 12, 2); 383 | if (ntohs(proto) != 0x800) return NULL; 384 | data += 14; 385 | len -= 14; 386 | 387 | /* IP header. */ 388 | u_int8_t version; 389 | if (len < 1) return NULL; 390 | version = (data[0] & 0xf0) >> 4; 391 | switch (version) { 392 | case 4: 393 | /* IPv4 */ 394 | if (len < 20) return NULL; 395 | struct ip4hdr ip4; 396 | memcpy(&ip4, data, sizeof(ip4)); 397 | if ((ntohs(ip4.frag_off) & 0xbf) != 0) return NULL; /* Don't handle fragments */ 398 | if (ip4.protocol != 6) return NULL; /* TCP only */ 399 | if (ntohs(ip4.tot_len) < len) return NULL; /* Packet too small */ 400 | len = ntohs(ip4.tot_len); /* Keep only real data */ 401 | if (!inet_ntop(AF_INET, &ip4.saddr, 402 | hello.client, sizeof(hello.client))) return NULL; 403 | if (!inet_ntop(AF_INET, &ip4.daddr, 404 | hello.server, sizeof(hello.server))) return NULL; 405 | data += (ip4.ihlv & 0xf) * 4; 406 | len -= (ip4.ihlv & 0xf) * 4; 407 | /* TCPv4 */ 408 | if (len < 20) return NULL; 409 | struct tcphdr tcp4; 410 | memcpy(&tcp4, data, sizeof(tcp4)); 411 | if (ntohs(tcp4.flags) & 0x7) return NULL; /* SYN, FIN, RST */ 412 | data += ((ntohs(tcp4.flags) & 0xf000) >> 12) * 4; 413 | len -= ((ntohs(tcp4.flags) & 0xf000) >> 12) * 4; 414 | break; 415 | case 6: 416 | /* IPv6 */ 417 | return NULL; /* TODO */ 418 | break; 419 | default: return NULL; 420 | } 421 | 422 | /* SSLv2 + SSLv3/TLS. See ssl/s23_srvr.c for detection logic */ 423 | if (len < 11) return NULL; 424 | u_int16_t tlen; 425 | if ((data[0] & 0x80) && (data[2] == 1)) { 426 | /* Let's assume SSLv2. This is now prohibited, see RFC 6176, but 427 | we want to keep track of clients still using it. */ 428 | hello.ssl2 = 1; 429 | memcpy(&tlen, data, 2); 430 | tlen = ntohs(tlen) & 0x2fff; 431 | hello.vmajor = data[3]; hello.vminor = data[4]; 432 | if (hello.vmajor != 2 && hello.vmajor != 3) return NULL; 433 | if (hello.vmajor == 2 && hello.vminor != 0) return NULL; 434 | if (hello.vmajor == 3 && hello.vminor > 3) return NULL; 435 | if (tlen != len - 2) return NULL; 436 | 437 | u_int16_t sidlen; 438 | u_int16_t ciphlen; 439 | memcpy(&sidlen, data + 7, 2); 440 | sidlen = ntohs(sidlen); 441 | memcpy(&ciphlen, data + 5, 2); 442 | ciphlen = ntohs(ciphlen); 443 | if (len < 11 + sidlen + ciphlen) return NULL; 444 | 445 | /* Session ID */ 446 | if (sidlen != 16 && sidlen != 0) return NULL; 447 | memcpy(hello.sessionid, data + 11 + ciphlen, sidlen); 448 | hello.sessionidlen = sidlen; 449 | 450 | /* Ciphers */ 451 | u_int32_t cipher; 452 | while (ciphlen) { 453 | memcpy(&cipher, data + 11 + ciphlen - 3, 3); 454 | cipher = ntohl(cipher); 455 | cipher = cipher >> 8; 456 | ciphlen -= 3; 457 | const struct value_string *cs = ssl_cipher_suites; 458 | while (cs->string) { 459 | if (cs->value == cipher) { 460 | append(&hello.ciphers, cs->string); 461 | break; 462 | } 463 | cs++; 464 | } 465 | } 466 | } else { 467 | /* SSLv3 or TLS */ 468 | if (data[0] != 22) return NULL; /* Not TLS Handshake */ 469 | if (data[1] != 3) return NULL; /* Not TLS 1.x */ 470 | if (data[2] > 3) return NULL; /* TLS 1.3 or more */ 471 | memcpy(&tlen, data + 3, 2); 472 | tlen = ntohs(tlen); 473 | data += 5; 474 | len -= 5; 475 | if (tlen != len) return NULL; 476 | if (len < 5) return NULL; 477 | if (data[0] != 1) return NULL; /* Client Hello */ 478 | if (data[1] != 0) return NULL; /* We don't handle fragmentation */ 479 | memcpy(&tlen, data + 2, 2); 480 | if (ntohs(tlen) != len - 4) return NULL; 481 | hello.vmajor = data[4]; hello.vminor = data[5]; 482 | if (hello.vmajor != 3) return NULL; 483 | if (hello.vminor > 3) return NULL; 484 | data += 38; 485 | len -= 38; 486 | 487 | /* Session ID */ 488 | if (len < 1) return NULL; 489 | hello.sessionidlen = data[0]; 490 | if (hello.sessionidlen != 0 && 491 | hello.sessionidlen != 32) return NULL; /* Session ID should be 32 */ 492 | memcpy(hello.sessionid, data + 1, hello.sessionidlen); 493 | data += 1 + hello.sessionidlen; 494 | len -= 1 + hello.sessionidlen; 495 | 496 | /* Ciphers */ 497 | if (len < 2) return NULL; 498 | u_int16_t ciphlen; 499 | u_int16_t cipher; 500 | memcpy(&ciphlen, data, 2); 501 | ciphlen = ntohs(ciphlen); 502 | if (len < 2 + ciphlen) return NULL; 503 | while (ciphlen) { 504 | memcpy(&cipher, data + 2 + ciphlen - 2, 2); 505 | cipher = ntohs(cipher); 506 | ciphlen -= 2; 507 | const struct value_string *cs = ssl_cipher_suites; 508 | while (cs->string) { 509 | if (cs->value == cipher) { 510 | append(&hello.ciphers, cs->string); 511 | break; 512 | } 513 | cs++; 514 | } 515 | } 516 | memcpy(&ciphlen, data, 2); 517 | ciphlen = ntohs(ciphlen); 518 | data += ciphlen + 2; 519 | len -= ciphlen + 2; 520 | 521 | /* Compression methods */ 522 | if (len < 1) goto err; 523 | unsigned char complen = data[0]; 524 | if (complen == 0) goto err; 525 | if (len < 1 + complen) goto err; 526 | while (complen--) { 527 | const struct value_string *cm = ssl_compression_method; 528 | while (cm->string) { 529 | if (cm->value == data[complen + 1]) { 530 | append(&hello.compression, cm->string); 531 | break; 532 | } 533 | cm++; 534 | } 535 | } 536 | len -= data[0] + 1; 537 | data += data[0] + 1; 538 | 539 | /* Extensions */ 540 | if (len > 2) { 541 | u_int16_t extlen; 542 | memcpy(&extlen, data, 2); 543 | extlen = ntohs(extlen); 544 | if (len != extlen + 2) goto err; 545 | data += 2; 546 | len -= 2; 547 | while (len > 0) { 548 | u_int16_t exttype; 549 | char *sni; /* Current name */ 550 | u_int16_t snilen; /* Length of current name */ 551 | u_int16_t clen; /* Remaining length in extension */ 552 | const unsigned char *p; /* Current position in data */ 553 | if (len < 4) goto err; 554 | memcpy(&exttype, data, 2); 555 | exttype = ntohs(exttype); 556 | memcpy(&extlen, data + 2, 2); 557 | extlen = ntohs(extlen); 558 | if (len + 4 < extlen) goto err; 559 | switch (exttype) { 560 | case 0: /* Server name */ 561 | if (extlen < 2) break; 562 | memcpy(&clen, data + 4, 2); 563 | clen = ntohs(clen); 564 | if (clen + 2 != extlen) break; 565 | p = data + 6; 566 | while (clen >= 3) { 567 | memcpy(&snilen, p + 1, 2); 568 | snilen = ntohs(snilen); 569 | if (clen < snilen + 3) break; 570 | if (*p == 0) { 571 | if ((sni = malloc(snilen + 1)) == NULL) 572 | fail("Not enough memory"); 573 | memcpy(sni, p + 3, snilen); 574 | sni[snilen] = '\0'; 575 | append(&hello.servername, sni); 576 | free(sni); 577 | } 578 | p += 3 + snilen; 579 | clen -= 3 + snilen; 580 | } 581 | break; 582 | case 0x23: 583 | hello.ticket = 1; 584 | hello.ticketlen = extlen; 585 | break; 586 | } 587 | data += extlen + 4; 588 | len -= extlen + 4; 589 | } 590 | if (len != 0) goto err; 591 | } 592 | } 593 | 594 | return &hello; 595 | err: 596 | if (hello.compression) free(hello.compression); 597 | if (hello.ciphers) free(hello.ciphers); 598 | if (hello.servername) free(hello.servername); 599 | return NULL; 600 | } 601 | 602 | static int 603 | count(const char *list) { 604 | if (!list) return 0; 605 | int c = 1; 606 | while ((list = strchr(list, ':'))) { 607 | c++; 608 | list++; 609 | } 610 | return c; 611 | } 612 | 613 | void 614 | display(struct timeval *ts, struct hello *hello) { 615 | /* Date */ 616 | struct tm *tm = gmtime(&ts->tv_sec); 617 | char date[100]; 618 | strftime(date, sizeof(date), "%FT%TZ", tm); 619 | 620 | end("Packet received at %s\n" 621 | " %s → %s\n" 622 | " Version: %d.%d%s\n" 623 | " Session ID len: %d\n" 624 | " Cipher suites: %d\n" 625 | " Compression methods: %d\n" 626 | " Server Name: %s\n" 627 | " Ticket extension: %s\n" 628 | " Ticket length %d", 629 | date, hello->client, hello->server, 630 | hello->vmajor, hello->vminor, 631 | hello->ssl2?" (inside SSLv2)":"", 632 | hello->sessionidlen, 633 | count(hello->ciphers), 634 | count(hello->compression), 635 | hello->servername?hello->servername:"Not present", 636 | hello->ticket?"Present":"Absent", 637 | hello->ticketlen); 638 | } 639 | 640 | void 641 | dump(FILE *output, struct timeval *ts, struct hello *hello) { 642 | if (ftell(output) == 0) 643 | fprintf(output, 644 | "date;client;server;ssl2;version;sessionid;ciphers;compression;" 645 | "servername;ticket;ticketlen\n"); 646 | fprintf(output, 647 | "%ld.%ld;%s;%s;%d;%d.%d;", 648 | (long)ts->tv_sec, (long)ts->tv_usec, 649 | hello->client, hello->server, 650 | hello->ssl2, 651 | hello->vmajor, hello->vminor); 652 | for (int i=0; i < hello->sessionidlen; i++) 653 | fprintf(output, "%02X", hello->sessionid[i]); 654 | fprintf(output, ";%s;%s;%s;%d;%d\n", 655 | hello->ciphers, 656 | hello->compression?hello->compression:"", 657 | hello->servername?hello->servername:"", 658 | hello->ticket, hello->ticketlen); 659 | } 660 | 661 | int 662 | main(int argc, char * const argv[]) { 663 | 664 | FILE *output; 665 | 666 | start("Check arguments"); 667 | if (argc != 2) 668 | fail("Usage: %s output\n" 669 | "\n" 670 | " Decode TLS Client Hello packets in PCAP files from stdin.\n" 671 | "\n" 672 | " For example, ‘%s output.csv < my.pcap‘ or\n" 673 | " ‘tcpdump -r my.pcap dst port 443 | %s output.csv‘", 674 | argv[0], argv[0], argv[0]); 675 | if (!(output = fopen(argv[1], "a"))) 676 | fail("Unable to open output file:\n%m"); 677 | 678 | start("Preparing libpcap to read input"); 679 | pcap_t *pcap; 680 | char errbuf[PCAP_ERRBUF_SIZE]; 681 | if ((pcap = pcap_open_offline("-", errbuf)) == NULL) 682 | fail("Unable to open standard input for packets:\n%s", 683 | errbuf); 684 | 685 | struct hello *hello; 686 | struct pcap_pkthdr *h; 687 | const u_char *data; 688 | int n; 689 | start("Read packets"); 690 | while (1) { 691 | n = pcap_next_ex(pcap, &h, &data); 692 | if (n == -2) { 693 | end("No more packets available."); 694 | break; 695 | } 696 | if (n == -1) 697 | fail("Unable to read one packet:\n%s", pcap_geterr(pcap)); 698 | hello = parse(data, h->caplen); 699 | if (hello) { 700 | dump(output, &h->ts, hello); 701 | display(&h->ts, hello); 702 | if (hello->ciphers) free(hello->ciphers); 703 | if (hello->compression) free(hello->compression); 704 | if (hello->servername) free(hello->servername); 705 | start("Read other packets"); 706 | } 707 | } 708 | 709 | pcap_close(pcap); 710 | end(NULL); 711 | return 0; 712 | } 713 | -------------------------------------------------------------------------------- /rfc5077-server.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vincent Bernat 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* RFC 5077 server test */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "common.h" 36 | #include "http-parser/http_parser.h" 37 | 38 | #define PEM_CERTIFICATE "cert.pem" 39 | #define PEM_KEY "key.pem" 40 | #define PEM_DH "dh.pem" 41 | 42 | #define NONBLOCKING(s) do { \ 43 | int val = 1; \ 44 | if (ioctl(s, FIONBIO, &val) == -1) \ 45 | fail("unable to set non blocking socket:\n%m"); \ 46 | } while(0); 47 | 48 | /* One server */ 49 | struct server { 50 | /* Configuration */ 51 | int session_cache; /* Setup a session cache? */ 52 | int accept_tickets; /* Accept tickets? */ 53 | /* State */ 54 | struct server *servers; /* List of available servers */ 55 | char *port; /* Port we should listen to */ 56 | int socket; /* Socket the server is listening to */ 57 | SSL_CTX *ctx; /* SSL context */ 58 | ev_io listener; 59 | }; 60 | 61 | /* Buffer size. You can extend it, but don't shrink it. We use a large 62 | buffer because this app does not know how to split a response 63 | message. The buffer should be able to fit the largest response 64 | message (including headers). With a value of 16 kbytes, we won't be 65 | able to send messages larger than 8 kbytes. */ 66 | #define BUFSIZE 1024*8 /* 8 kbytes of buffer */ 67 | 68 | /* One connection. */ 69 | struct connection { 70 | ev_tstamp start; /* Timestamp for the start of the request */ 71 | int client; /* Client socket */ 72 | struct server *server; 73 | ev_io ev_read; /* Reading from the client */ 74 | ev_io ev_write; /* Writing to the client */ 75 | ev_io ev_read_handshake; /* SSL handshake with the client (read) */ 76 | ev_io ev_write_handshake;/* SSL handshake with the client (write) */ 77 | ev_io ev_read_shutdown; /* SSL shutdown with the client (read) */ 78 | ev_io ev_write_shutdown; /* SSL shutdown with the client (write) */ 79 | ev_timer ev_timeout; /* Basic timeout */ 80 | char buffer[BUFSIZE]; /* Read/write buffer */ 81 | int buffer_size; 82 | /* HTTP */ 83 | int response; /* We are currently sending HTTP response */ 84 | int uastate; /* State for parsing User-Agent header */ 85 | http_parser parser; /* HTTP parser */ 86 | int method; /* Action requested */ 87 | char *path; /* Path requested */ 88 | char *callback; /* Callback param (other params are discarded) */ 89 | char *ua; /* User agent */ 90 | char *protocol; /* Protocol */ 91 | int code; /* Code for response */ 92 | int size; /* Size of response */ 93 | /* SSL */ 94 | SSL *ssl; /* SSL state */ 95 | /* IP */ 96 | char ip[INET6_ADDRSTRLEN]; /* IP address of the client */ 97 | /* Loop */ 98 | struct ev_loop *loop; 99 | }; 100 | 101 | /* Make a listening socket. */ 102 | static void 103 | setup_socket(struct server *server) { 104 | struct addrinfo hints; 105 | memset(&hints, 0, sizeof(struct addrinfo)); 106 | hints.ai_family = AF_UNSPEC; /* IPv4 or IPv6 */ 107 | hints.ai_socktype = SOCK_STREAM; 108 | hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; 109 | 110 | /* Solve port name and get the binding address */ 111 | int s, sfd; 112 | struct addrinfo *result, *rp; 113 | if ((s = getaddrinfo(NULL, server->port, &hints, &result)) != 0) 114 | fail("Unable to solve ‘%s‘:\n%s", server->port, gai_strerror(s)); 115 | 116 | /* Try to bind */ 117 | for (rp = result; rp; rp = rp->ai_next) { 118 | if ((sfd = socket(rp->ai_family, rp->ai_socktype, 119 | rp->ai_protocol)) == -1) 120 | continue; /* Cannot bind. */ 121 | 122 | int val = 1; 123 | if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, 124 | &val,sizeof(val)) == -1) 125 | fail("Unable to set socket options:\n%m"); 126 | 127 | NONBLOCKING(sfd); 128 | 129 | if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == -1) 130 | fail("Unable to bind to ‘%s‘:\n%m", server->port); 131 | 132 | #ifdef TCP_DEFER_ACCEPT 133 | setsockopt(sfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val) ); 134 | #endif 135 | 136 | break; 137 | } 138 | 139 | /* Then, listen */ 140 | if (listen(sfd, 100) == -1) 141 | fail("Unable to listen to ‘%s‘:\n%m", server->port); 142 | 143 | server->socket = sfd; 144 | freeaddrinfo(result); 145 | } 146 | 147 | #ifndef SSL_OP_NO_COMPRESSION 148 | #define SSL_OP_NO_COMPRESSION 0 149 | #endif 150 | 151 | /* Setup SSL context for each server */ 152 | static void 153 | setup_ssl(struct server *server) { 154 | SSL_CTX *ctx; 155 | 156 | /* Create context */ 157 | if ((ctx = server->ctx = SSL_CTX_new(TLS_server_method())) == NULL) 158 | fail("Unable to create SSL context:\n%s", 159 | ERR_error_string(ERR_get_error(), NULL)); 160 | SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_ALL | SSL_OP_NO_COMPRESSION | 161 | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); 162 | 163 | /* Load certificate and key */ 164 | if (SSL_CTX_use_certificate_file(ctx, 165 | PEM_CERTIFICATE, 166 | SSL_FILETYPE_PEM) != 1) 167 | fail("Unable to load certificate from ‘%s‘:\n%s", 168 | PEM_CERTIFICATE, 169 | ERR_error_string(ERR_get_error(), NULL)); 170 | if (SSL_CTX_use_PrivateKey_file(ctx, 171 | PEM_KEY, 172 | SSL_FILETYPE_PEM) != 1) 173 | fail("Unable to load key from ‘%s‘:\n%s", 174 | PEM_KEY, 175 | ERR_error_string(ERR_get_error(), NULL)); 176 | 177 | /* Load DH param */ 178 | DH *dh; 179 | BIO *bio = BIO_new_file(PEM_DH, "r"); 180 | if (!bio) 181 | fail("Unable to load DH params from ‘%s‘:\n%s", 182 | PEM_DH, 183 | ERR_error_string(ERR_get_error(), NULL)); 184 | dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); 185 | BIO_free(bio); 186 | if (!dh) 187 | fail("Unable to find DH params in ‘%s‘", 188 | PEM_DH); 189 | SSL_CTX_set_tmp_dh(ctx, dh); 190 | DH_free(dh); 191 | 192 | /* Disable tickets */ 193 | if (!server->accept_tickets) 194 | SSL_CTX_set_options(server->ctx, SSL_OP_NO_TICKET); 195 | 196 | /* Enable/disable session cache */ 197 | SSL_CTX_set_session_cache_mode(server->ctx, 198 | (server->session_cache)? 199 | SSL_SESS_CACHE_SERVER:SSL_SESS_CACHE_OFF); 200 | } 201 | 202 | /* Handle shutdown of the connection */ 203 | static void 204 | shutdown_connection(struct ev_loop *loop, struct connection *conn) { 205 | ev_io_stop(loop, &conn->ev_read); 206 | ev_io_stop(loop, &conn->ev_write); 207 | ev_io_stop(loop, &conn->ev_read_handshake); 208 | ev_io_stop(loop, &conn->ev_write_handshake); 209 | ev_io_stop(loop, &conn->ev_read_shutdown); 210 | ev_io_stop(loop, &conn->ev_write_shutdown); 211 | ev_timer_stop(loop, &conn->ev_timeout); 212 | close(conn->client); 213 | SSL_set_shutdown(conn->ssl, SSL_SENT_SHUTDOWN); 214 | SSL_free(conn->ssl); 215 | free(conn->path); 216 | free(conn->callback); 217 | free(conn->ua); 218 | free(conn->protocol); 219 | free(conn); 220 | } 221 | 222 | /* Be ready for shutdown */ 223 | static void 224 | start_shutdown(struct ev_loop *loop, struct connection *conn) { 225 | /* Stop any pending reading */ 226 | ev_io_stop(loop, &conn->ev_read); 227 | ev_io_stop(loop, &conn->ev_write); 228 | /* Start shutdown loop */ 229 | ev_io_start(loop, &conn->ev_write_shutdown); 230 | } 231 | 232 | /* Handle shutdown in case of an SSL error */ 233 | static void 234 | shutdown_connection_with_err(struct ev_loop *loop, struct connection *conn, 235 | int err) { 236 | int noerr = 0; 237 | if (SSL_get_shutdown(conn->ssl) & SSL_RECEIVED_SHUTDOWN) { 238 | start_shutdown(loop, conn); 239 | return; 240 | } 241 | else if (err == SSL_ERROR_ZERO_RETURN) 242 | warn("Connection with %s closed while receiving data", conn->ip); 243 | else if (err == SSL_ERROR_SYSCALL) { 244 | if (errno != 0) 245 | warn("Got an error with %s, closing:\n%m", conn->ip); 246 | else noerr = 1; 247 | } else 248 | warn("Unexpected SSL error with %s: %d", conn->ip, err); 249 | shutdown_connection(loop, conn); 250 | if (!noerr) 251 | start("Ready for the next connection"); 252 | } 253 | 254 | /* Return the requested HTTP answer */ 255 | static void 256 | http_answer(struct ev_loop *loop, struct connection *conn, 257 | int code, const char *reason, const char *content_type, 258 | const char *body) { 259 | int n; 260 | int jsonp = 0; 261 | int len; 262 | conn->response = 1; 263 | ev_io_stop(loop, &conn->ev_read); 264 | ev_io_start(loop, &conn->ev_write); 265 | 266 | jsonp = (!strcmp(content_type, "application/json") && conn->callback); 267 | len = jsonp?(strlen(conn->callback) + 3 + strlen(body)):strlen(body); 268 | n = snprintf(conn->buffer, sizeof(conn->buffer), 269 | "HTTP/1.0 %d %s\r\n" 270 | "Cache-Control: no-cache\r\n" 271 | "Content-Type: %s\r\n" 272 | "Content-Length: %d\r\n" 273 | "Connection: close\r\n" 274 | "\r\n" 275 | "%s%s%s%s", 276 | code, reason, 277 | jsonp?"application/javascript":content_type, 278 | len, 279 | jsonp?conn->callback:"", 280 | jsonp?"(":"", 281 | body, 282 | jsonp?");":""); 283 | if (n == -1 || n >= sizeof(conn->buffer)) { 284 | http_answer(loop, conn, 500, "Internal Error", "text/html", 285 | "

Answer too large

\r\n"); 286 | return; 287 | } 288 | conn->buffer_size = n; 289 | conn->code = code; 290 | conn->size = len; 291 | } 292 | 293 | struct handler { 294 | char *path; /* Requested path */ 295 | int (*handle)(struct ev_loop *, struct connection *, void *); 296 | void *data; 297 | }; 298 | 299 | /* Serve a static file */ 300 | static int 301 | http_handle_file(struct ev_loop *loop, struct connection *conn, void *data) { 302 | char *path = (char *)data; 303 | char *buf = malloc(BUFSIZE); 304 | int n, m = 0, buflen = BUFSIZE; 305 | int fd = open(path, O_RDONLY); 306 | char *type = "text/html"; 307 | char *ext; 308 | 309 | if (!buf) fail("Out of memory"); 310 | if (!fd) { 311 | warn("Unable to open %s:\n%m", path); 312 | return 1; 313 | } 314 | 315 | /* Set content-type */ 316 | ext = strrchr(path, '.'); 317 | if (ext) { 318 | ext++; 319 | if (!strcmp(ext, "js")) type = "application/javascript"; 320 | } 321 | 322 | /* Read file content (we may truncate) */ 323 | while ((n = read(fd, buf + m, buflen - 1 - m)) > 0) m += n; 324 | buf[m] = '\0'; 325 | close(fd); 326 | 327 | /* Answer */ 328 | http_answer(loop, conn, 200, "OK", type, buf); 329 | return 0; 330 | } 331 | 332 | /* Grab current session parameters */ 333 | static int 334 | http_handle_session(struct ev_loop *loop, struct connection *conn, void *_) { 335 | SSL_SESSION *x = SSL_get_session(conn->ssl); 336 | char *answer; 337 | const char *version = SSL_get_version(conn->ssl); 338 | 339 | unsigned int sessionid_len = 0; 340 | const unsigned char *sessionid_raw = SSL_SESSION_get_id(x, &sessionid_len); 341 | char *sessionid = malloc(2*sessionid_len + 1); 342 | if (!sessionid) fail("Out of memory"); 343 | for (unsigned int i = 0; i < sessionid_len; i++) 344 | snprintf(sessionid + 2*i, 3, "%02X", sessionid_raw[i]); 345 | sessionid[2*sessionid_len] = '\0'; 346 | 347 | size_t masterkey_len = SSL_SESSION_get_master_key(x, NULL, 0); 348 | unsigned char *masterkey_raw = calloc(1, masterkey_len); 349 | if (!masterkey_raw) fail("Out of memory"); 350 | SSL_SESSION_get_master_key(x, masterkey_raw, masterkey_len); 351 | char *masterkey = malloc(2*masterkey_len + 1); 352 | if (!masterkey) fail("Out of memory"); 353 | for (size_t i = 0; i < masterkey_len; i++) 354 | snprintf(masterkey + 2*i, 3, "%02X", masterkey_raw[i]); 355 | masterkey[2*masterkey_len] = '\0'; 356 | 357 | const unsigned char *ticket_raw; 358 | size_t ticket_len = 0; 359 | SSL_SESSION_get0_ticket(x, &ticket_raw, &ticket_len); 360 | char *ticket = malloc(2*ticket_len + 1); 361 | if (!ticket) fail("Out of memory"); 362 | for (size_t i = 0; i < ticket_len; i++) 363 | snprintf(ticket + 2*i, 3, "%02X", ticket_raw[i]); 364 | ticket[2*ticket_len] = '\0'; 365 | 366 | int n = asprintf(&answer, 367 | "{ version: '%s', \r\n" 368 | " cipher: '%s', \r\n" 369 | " sessionid: '%s', \r\n" 370 | " masterkey: '%s', \r\n" 371 | " ticket: '%s' \r\n" 372 | "}", 373 | version, 374 | SSL_CIPHER_get_name(SSL_SESSION_get0_cipher(x)), 375 | sessionid, 376 | masterkey, 377 | ticket); 378 | free(sessionid); 379 | free(masterkey_raw); 380 | free(masterkey); 381 | free(ticket); 382 | if (n == -1) return 1; 383 | 384 | http_answer(loop, conn, 200, "OK", 385 | "application/json", 386 | answer); 387 | free(answer); 388 | return 0; 389 | } 390 | 391 | /* Return params for current server */ 392 | static int 393 | http_handle_params(struct ev_loop *loop, struct connection *conn, void *_) { 394 | char *answer; 395 | int n = asprintf(&answer, 396 | "{ cache: %d,\r\n" 397 | " tickets: %d\r\n" 398 | "}", 399 | conn->server->session_cache, 400 | conn->server->accept_tickets); 401 | if (n == -1) return 1; 402 | http_answer(loop, conn, 200, "OK", "application/json", answer); 403 | free(answer); 404 | return 0; 405 | } 406 | 407 | /* Return list of available servers */ 408 | static int 409 | http_handle_servers(struct ev_loop *loop, struct connection *conn, void *_) { 410 | char *answer = NULL; 411 | struct server *server; 412 | for (server = conn->server->servers; 413 | server->session_cache != -1; server++) { 414 | char *old = answer; 415 | int n = asprintf(&answer, "%s%s%s%s", 416 | (old)?old:"", 417 | (!old)?"{ servers: [ ":", ", 418 | server->port, 419 | ((server+1)->session_cache == -1)?" ] }":""); 420 | free(old); 421 | if (n == -1) return 1; 422 | } 423 | http_answer(loop, conn, 200, "OK", "application/json", answer); 424 | free(answer); 425 | return 0; 426 | } 427 | 428 | static struct handler http_handlers[] = { 429 | { "/", http_handle_file, "rfc5077-server.html" }, 430 | { "/rfc5077-server.js", http_handle_file, "js/rfc5077-server.js" }, 431 | { "/jquery.jsonp.js", http_handle_file, "js/jquery.jsonp-2.1.4.min.js" }, 432 | { "/session", http_handle_session }, 433 | { "/params" , http_handle_params }, 434 | { "/servers", http_handle_servers }, 435 | { NULL } 436 | }; 437 | 438 | /* HTTP request received is complete */ 439 | static int 440 | http_cb_message_complete(http_parser *p) { 441 | struct connection *conn = (struct connection*)p->data; 442 | struct handler *h; 443 | 444 | for (h = http_handlers; h->path; h++) { 445 | if (strcmp(h->path, conn->path)) continue; 446 | if (h->handle(conn->loop, conn, h->data)) 447 | http_answer(conn->loop, conn, 500, "Internal Error", "text/html", 448 | "\r\n" 449 | "

Internal error

\r\n" 450 | "An internal error occurred while serving the request\r\n" 451 | "\r\n"); 452 | break; 453 | } 454 | if (!h->path) { 455 | http_answer(conn->loop, conn, 404, "Page not found", "text/html", 456 | "\r\n" 457 | "

Not found

\r\n" 458 | "The requested page was not found\r\n" 459 | "\r\n"); 460 | return 0; 461 | } 462 | return 0; 463 | } 464 | 465 | /* Headers are complete, grab HTTP method and version */ 466 | static int 467 | http_cb_headers_complete(http_parser *p) { 468 | struct connection *conn = (struct connection*)p->data; 469 | int n = asprintf(&conn->protocol, "HTTP/%d.%d", p->http_major, p->http_minor); 470 | conn->method = p->method; 471 | if (n == -1) fail("Out of memory"); 472 | 473 | /* Strip params and extract callback */ 474 | char *q = strchr(conn->path, '?'); 475 | if (q) { 476 | *q = '\0'; q++; 477 | /* We keep callback only if this is the first param */ 478 | if (!strncmp(q, "callback=", 9)) { 479 | char *q2 = strchr(q, '&'); /* We hope this is not an entity */ 480 | if (q2) *q2 = '\0'; 481 | conn->callback = strdup(q + 9); 482 | if (!conn->callback) fail("Out of memory"); 483 | } 484 | } 485 | 486 | return 0; 487 | } 488 | 489 | /* Received some bytes of the URL */ 490 | static int 491 | http_cb_url(http_parser *p, const char *buf, size_t len) { 492 | struct connection *conn = (struct connection*)p->data; 493 | 494 | char *url; 495 | int newlen = conn->path?strlen(conn->path):0 + len; 496 | url = realloc(conn->path, newlen + 1); 497 | if (!url) fail("Out of memory"); 498 | if (!conn->path) strncpy(url, buf, len); 499 | else strncat(url, buf, len); 500 | url[newlen] = '\0'; /* strncpy */ 501 | conn->path = url; 502 | return 0; 503 | } 504 | 505 | /* Received some bytes for a header field */ 506 | static int 507 | http_cb_header_field(http_parser *p, const char *buf, size_t len) { 508 | struct connection *conn = (struct connection*)p->data; 509 | 510 | switch (conn->uastate) { 511 | case -2: 512 | /* We were reading user agent value. */ 513 | case 0: 514 | /* We were not reading an header field. Check if we have 515 | user-agent field. */ 516 | if (strncasecmp("user-agent", buf, len)) { 517 | conn->uastate = -1; /* No */ 518 | break; 519 | } 520 | conn->uastate = len; /* Yes */ 521 | break; 522 | case -1: 523 | /* We were reading an header field and it does not match 524 | user-agent. We are not interested in the remaining value of 525 | this header field. */ 526 | break; 527 | default: 528 | /* We were reading an header field and it matches user-agent. */ 529 | if (strncasecmp(&"user-agent"[conn->uastate], buf, len)) { 530 | conn->uastate = -1; /* No */ 531 | break; 532 | } 533 | conn->uastate += len; /* Yes */ 534 | break; 535 | } 536 | if (conn->uastate > 0 && conn->uastate > strlen("user-agent")) 537 | conn->uastate = -1; /* Too long */ 538 | 539 | return 0; 540 | } 541 | 542 | /* Received some bytes for a header value */ 543 | static int 544 | http_cb_header_value(http_parser *p, const char *buf, size_t len) { 545 | struct connection *conn = (struct connection*)p->data; 546 | switch (conn->uastate) { 547 | case -2: 548 | /* Continuation of a previous user-agent */ 549 | conn->ua = realloc(conn->ua, strlen(conn->ua) + len + 1); 550 | if (!conn->ua) fail("Out of memory"); 551 | strncat(conn->ua, buf, len); 552 | break; 553 | case 10: /* strlen("user-agent") */ 554 | /* New user-agent value */ 555 | conn->uastate = -2; 556 | if (conn->ua) { 557 | /* This is odd for a user agent, but HTTP requires to 558 | concatenate fields with the same name */ 559 | conn->ua = realloc(conn->ua, strlen(conn->ua) + 2 + len + 1); 560 | if (!conn->ua) fail("Out of memory"); 561 | strcat(conn->ua, ", "); 562 | strncat(conn->ua, buf, len); 563 | } else { 564 | /* We don't have user-agent yet */ 565 | conn->ua = strndup(buf, len); 566 | if (!conn->ua) fail("Out of memory"); 567 | } 568 | break; 569 | default: 570 | /* Not a user-agent value */ 571 | conn->uastate = 0; 572 | } 573 | return 0; 574 | } 575 | 576 | /* HTTP Parser settings */ 577 | static http_parser_settings parser_settings = { 578 | .on_header_field = http_cb_header_field, 579 | .on_header_value = http_cb_header_value, 580 | .on_headers_complete = http_cb_headers_complete, 581 | .on_url = http_cb_url, 582 | .on_message_complete = http_cb_message_complete 583 | }; 584 | 585 | /* Be ready for an handshake */ 586 | static void 587 | start_handshake(struct ev_loop *loop, struct connection *conn, int err) { 588 | /* Stop normal reading */ 589 | ev_io_stop(loop, &conn->ev_read); 590 | ev_io_stop(loop, &conn->ev_write); 591 | /* Start handshake loop */ 592 | if (err == SSL_ERROR_WANT_READ) 593 | ev_io_start(loop, &conn->ev_read_handshake); 594 | else if (err == SSL_ERROR_WANT_WRITE) 595 | ev_io_start(loop, &conn->ev_write_handshake); 596 | } 597 | 598 | /* Do the SSL handshake */ 599 | static void 600 | handle_client_handshake(struct ev_loop *loop, ev_io *w, int revents) { 601 | struct connection *conn = (struct connection *)w->data; 602 | ev_timer_again(loop, &conn->ev_timeout); /* Reinit timeout */ 603 | 604 | int err; 605 | err = SSL_do_handshake(conn->ssl); 606 | if (err == 1) { 607 | /* Handshake is successful */ 608 | ev_io_stop(loop, &conn->ev_read_handshake); 609 | ev_io_stop(loop, &conn->ev_write_handshake); 610 | if (conn->response) 611 | ev_io_start(loop, &conn->ev_write); /* Keep sending HTTP response */ 612 | else 613 | ev_io_start(loop, &conn->ev_read); /* Continue reading request */ 614 | } else { 615 | err = SSL_get_error(conn->ssl, err); 616 | switch (err) { 617 | case SSL_ERROR_WANT_READ: 618 | ev_io_stop(loop, &conn->ev_write_handshake); 619 | ev_io_start(loop, &conn->ev_read_handshake); 620 | break; 621 | case SSL_ERROR_WANT_WRITE: 622 | ev_io_stop(loop, &conn->ev_read_handshake); 623 | ev_io_start(loop, &conn->ev_write_handshake); 624 | break; 625 | case SSL_ERROR_ZERO_RETURN: 626 | warn("Connection close from %s (in handshake)", conn->ip); 627 | shutdown_connection(loop, conn); 628 | break; 629 | case SSL_ERROR_SSL: 630 | warn("Unable to do SSL handshake with %s:\n %s", conn->ip, 631 | ERR_error_string(ERR_get_error(), NULL)); 632 | shutdown_connection(loop, conn); 633 | break; 634 | case SSL_ERROR_SYSCALL: 635 | if (errno != 0) 636 | warn("Unable to do SSL handshake with %s:\n %m", conn->ip); 637 | shutdown_connection(loop, conn); 638 | break; 639 | default: 640 | warn("Unable to do SSL handshake with %s (%d)", conn->ip, err); 641 | shutdown_connection(loop, conn); 642 | } 643 | } 644 | } 645 | 646 | /* Do the SSL shutdown. This part seems a bit buggy, we ensure we 647 | don't loop and always call shutdown_connection() in case of any 648 | problem. */ 649 | static void 650 | handle_client_shutdown(struct ev_loop *loop, ev_io *w, int revents) { 651 | struct connection *conn = (struct connection *)w->data; 652 | ev_timer_again(loop, &conn->ev_timeout); /* Reinit timeout */ 653 | 654 | int err = 0, try = 2; 655 | while (err == 0 && try-- > 0) 656 | err = SSL_shutdown(conn->ssl); 657 | if (err == 1) 658 | /* Handshake is successful. Terminate. */ 659 | shutdown_connection(loop, conn); 660 | else { 661 | err = SSL_get_error(conn->ssl, err); 662 | switch (err) { 663 | case SSL_ERROR_WANT_READ: 664 | ev_io_stop(loop, &conn->ev_write_shutdown); 665 | ev_io_start(loop, &conn->ev_read_shutdown); 666 | break; 667 | case SSL_ERROR_WANT_WRITE: 668 | ev_io_stop(loop, &conn->ev_read_shutdown); 669 | ev_io_start(loop, &conn->ev_write_shutdown); 670 | break; 671 | default: 672 | /* We did our best, just shut the connection */ 673 | shutdown_connection(loop, conn); 674 | } 675 | } 676 | } 677 | 678 | /* Handle read from the client (request) */ 679 | static void 680 | handle_client_read(struct ev_loop *loop, ev_io *w, int revents) { 681 | struct connection *conn = (struct connection *)w->data; 682 | ev_timer_again(loop, &conn->ev_timeout); /* Reinit timeout */ 683 | 684 | int n; 685 | n = SSL_read(conn->ssl, conn->buffer, 686 | sizeof(conn->buffer)); 687 | if (n > 0) { 688 | /* Feed the buffer to the HTTP parser */ 689 | size_t nparsed = http_parser_execute(&conn->parser, 690 | &parser_settings, 691 | conn->buffer, 692 | n); 693 | if (conn->parser.upgrade || nparsed != n) 694 | /* Issue a 400 bad request */ 695 | http_answer(loop, conn, 696 | 400, "Bad request", "text/html", 697 | "

400 Bad request

\r\n" 698 | "Your browser sent an invalid request.\r\n" 699 | "\r\n"); 700 | } else { 701 | n = SSL_get_error(conn->ssl, n); 702 | switch (n) { 703 | case SSL_ERROR_WANT_WRITE: 704 | /* Handshake wanted */ 705 | start_handshake(loop, conn, n); 706 | break; 707 | case SSL_ERROR_WANT_READ: 708 | /* Not enough data */ 709 | break; 710 | default: 711 | /* Fatal error */ 712 | shutdown_connection_with_err(loop, conn, n); 713 | break; 714 | } 715 | } 716 | } 717 | 718 | /* Write data to the client (response) */ 719 | static void 720 | handle_client_write(struct ev_loop *loop, ev_io *w, int revents) { 721 | struct connection *conn = (struct connection *)w->data; 722 | ev_timer_again(loop, &conn->ev_timeout); /* Reinit timeout */ 723 | 724 | int n; 725 | n = SSL_write(conn->ssl, conn->buffer, conn->buffer_size); 726 | if (n > 0) { 727 | if (n != conn->buffer_size) { 728 | memmove(conn->buffer, conn->buffer + n, 729 | conn->buffer_size - n); 730 | conn->buffer_size -= n; 731 | } else { 732 | /* No more to write. Display log line */ 733 | char ctime[100]; 734 | time_t now = conn->start; 735 | struct tm *tmp = gmtime(&now); 736 | strftime(ctime, sizeof(ctime), "%FT%TZ", tmp); 737 | end("%s - - [%s] \"%s %s %s\" %d %d \"%s\"", 738 | conn->ip, ctime, 739 | http_method_str(conn->method), /* GET */ 740 | conn->path, /* /foobar */ 741 | conn->protocol, /* HTTP/1.0 */ 742 | conn->code, /* 200 */ 743 | conn->size, /* 4874 */ 744 | conn->ua?conn->ua:""); /* Mozilla/5.0 ... */ 745 | start_shutdown(loop, conn); 746 | } 747 | } else { 748 | n = SSL_get_error(conn->ssl, n); 749 | switch (n) { 750 | case SSL_ERROR_WANT_READ: 751 | /* Handshake wanted */ 752 | start_handshake(loop, conn, n); 753 | break; 754 | case SSL_ERROR_WANT_WRITE: 755 | /* Not enough data */ 756 | break; 757 | default: 758 | /* Fatal error */ 759 | shutdown_connection_with_err(loop, conn, n); 760 | break; 761 | } 762 | } 763 | } 764 | 765 | /* Timeout for a client */ 766 | static void 767 | handle_client_timeout(struct ev_loop *loop, ev_timer *w, int revents) { 768 | struct connection *conn = (struct connection *)w->data; 769 | warn("Timeout occurred with %s. Aborting connection", conn->ip); 770 | shutdown_connection(loop, conn); 771 | } 772 | 773 | /* Handle accept */ 774 | static void 775 | handle_accept(struct ev_loop *loop, ev_io *w, int revents) { 776 | struct sockaddr_storage addr; 777 | socklen_t addrlen = sizeof(addr); 778 | 779 | struct connection *conn = malloc(sizeof(struct connection)); 780 | if (!conn) fail("Out of memory."); 781 | memset(conn, 0, sizeof(struct connection)); 782 | conn->loop = loop; 783 | conn->start = ev_now(loop); 784 | conn->server = (struct server *)w->data; 785 | 786 | /* Accept the client */ 787 | if ((conn->client = accept(w->fd, 788 | (struct sockaddr *)&addr, 789 | &addrlen)) == -1) { 790 | if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) 791 | return; 792 | fail("Unable to accept():\n%m"); 793 | } 794 | 795 | /* Setup TCP_NODELAY, ignore any error */ 796 | int flag = 1; 797 | setsockopt(conn->client, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)); 798 | 799 | NONBLOCKING(conn->client); 800 | 801 | /* Grab IP address of the client */ 802 | int err; 803 | if ((err = getnameinfo((struct sockaddr *)&addr, addrlen, 804 | conn->ip, sizeof(conn->ip), NULL, 0, 805 | NI_NUMERICHOST)) != 0) 806 | fail("Unable to get format client address:\n%s", 807 | gai_strerror(err)); 808 | 809 | /* SSL setup */ 810 | long mode = SSL_MODE_ENABLE_PARTIAL_WRITE; 811 | #ifdef SSL_MODE_RELEASE_BUFFERS 812 | mode |= SSL_MODE_RELEASE_BUFFERS; 813 | #endif 814 | conn->ssl = SSL_new(conn->server->ctx); 815 | SSL_set_mode(conn->ssl, mode); 816 | SSL_set_accept_state(conn->ssl); 817 | SSL_set_fd(conn->ssl, conn->client); 818 | 819 | /* Setup libev */ 820 | ev_io_init(&conn->ev_read, handle_client_read, conn->client, EV_READ); 821 | conn->ev_read.data = conn; 822 | ev_io_init(&conn->ev_write, handle_client_write, conn->client, EV_WRITE); 823 | conn->ev_write.data = conn; 824 | ev_io_init(&conn->ev_read_handshake, handle_client_handshake, conn->client, EV_READ); 825 | conn->ev_read_handshake.data = conn; 826 | ev_io_init(&conn->ev_write_handshake, handle_client_handshake, conn->client, EV_WRITE); 827 | conn->ev_write_handshake.data = conn; 828 | ev_io_init(&conn->ev_read_shutdown, handle_client_shutdown, conn->client, EV_READ); 829 | conn->ev_read_shutdown.data = conn; 830 | ev_io_init(&conn->ev_write_shutdown, handle_client_shutdown, conn->client, EV_WRITE); 831 | conn->ev_write_shutdown.data = conn; 832 | ev_init(&conn->ev_timeout, handle_client_timeout); 833 | conn->ev_timeout.repeat = 10.; 834 | conn->ev_timeout.data = conn; 835 | ev_timer_again(loop, &conn->ev_timeout); 836 | 837 | /* Initialize the HTTP parser */ 838 | http_parser_init(&conn->parser, HTTP_REQUEST); 839 | conn->parser.data = conn; 840 | 841 | /* Let's start the handshake */ 842 | start_handshake(loop, conn, SSL_ERROR_WANT_READ); 843 | } 844 | 845 | int 846 | main(int argc, char * const argv[]) { 847 | 848 | /* Servers configuration */ 849 | struct server servers[] = { 850 | { 0, 0 }, 851 | { 1, 0 }, 852 | { 1, 1 }, 853 | { 0, 1 }, 854 | { -1, -1 } 855 | }; 856 | 857 | int nb = sizeof(servers)/sizeof(struct server) - 1; 858 | 859 | start("Check arguments"); 860 | if (argc != nb + 1) 861 | fail("Usage: %s ports\n" 862 | "\n" 863 | " Start a small web server listening on %d ports with\n" 864 | " different SSL parameters on each port.", argv[0], nb); 865 | 866 | /* Initialize OpenSSL library */ 867 | start("Initialize OpenSSL"); 868 | signal(SIGPIPE, SIG_IGN); 869 | SSL_load_error_strings(); 870 | SSL_library_init(); 871 | 872 | /* Check libev version */ 873 | start("Setup libev"); 874 | if (ev_version_major() != EV_VERSION_MAJOR || 875 | ev_version_minor() < EV_VERSION_MINOR) 876 | fail("libev version mismatch:\n%d.%d vs %d.%d", 877 | ev_version_major(), ev_version_minor(), 878 | EV_VERSION_MAJOR, EV_VERSION_MINOR); 879 | 880 | struct ev_loop *loop; 881 | loop = EV_DEFAULT; 882 | if (!loop) 883 | fail("Unable to setup the default event loop!"); 884 | 885 | for (int i = 0; i < nb; i++) { 886 | servers[i].port = argv[i+1]; 887 | servers[i].servers = servers; 888 | start("Setup server listening on %s %s cache and %s tickets", 889 | servers[i].port, 890 | servers[i].session_cache?"with":"without", 891 | servers[i].accept_tickets?"with":"without"); 892 | setup_socket(&servers[i]); 893 | setup_ssl(&servers[i]); 894 | ev_io_init(&servers[i].listener, handle_accept, 895 | servers[i].socket, EV_READ); 896 | servers[i].listener.data = &servers[i]; 897 | ev_io_start(loop, &servers[i].listener); 898 | } 899 | 900 | start("Start main loop and serve requests"); 901 | ev_run(loop, 0); 902 | 903 | /* Free some stuff */ 904 | for (int i = 0; i < nb; i++) 905 | SSL_CTX_free(servers[i].ctx); 906 | 907 | end(NULL); 908 | return 0; 909 | } 910 | -------------------------------------------------------------------------------- /ssl-handshake.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 23 | 27 | 31 | 32 | 39 | 45 | 46 | 53 | 59 | 60 | 70 | 73 | 77 | 81 | 82 | 92 | 95 | 99 | 103 | 104 | 106 | 110 | 114 | 115 | 125 | 135 | 145 | 155 | 165 | 174 | 184 | 194 | 203 | 213 | 223 | 233 | 242 | 252 | 262 | 272 | 282 | 292 | 301 | 302 | 324 | 326 | 327 | 329 | image/svg+xml 330 | 332 | 333 | 334 | 335 | 336 | 341 | 343 | 348 | 356 | 364 | 372 | 380 | 388 | 0 ms 399 | 50 ms 410 | 100 ms 421 | 150 ms 432 | 200 ms 443 | 250 ms 454 | 459 | 467 | 472 | 480 | 488 | 496 | 504 | Client 515 | Server 526 | 529 | 531 | 540 | 542 | 552 | 1 563 | 564 | 565 | Client Hello 576 | 577 | 580 | 583 | 592 | 594 | 604 | 2 615 | 616 | 617 | Server HelloCertificateServer Hello Done 636 | 637 | 640 | 643 | 652 | 654 | 664 | 3 675 | 676 | 677 | Client Key ExchangeChange Cipher SpecFinished 696 | 697 | 700 | 703 | 712 | 714 | 724 | 4 735 | 736 | 737 | Change Cipher SpecFinished 752 | 753 | 756 | 758 | 767 | 769 | 779 | 790 | 791 | 792 | GET / HTTP/1.0 803 | 804 | Without resume 815 | 816 | 819 | 824 | 832 | 840 | 848 | 0 ms 859 | 50 ms 870 | 100 ms 881 | 150 ms 892 | 897 | 902 | 907 | 915 | 923 | 931 | Client 942 | Server 953 | 956 | 958 | 967 | 969 | 979 | 1 990 | 991 | 992 | Client Hello 1003 | 1004 | 1007 | 1010 | 1019 | 1021 | 1031 | 2 1042 | 1043 | 1044 | Server HelloChange Cipher SpecFinished 1063 | 1064 | 1067 | 1069 | 1078 | 1080 | 1090 | 1101 | 1102 | 1103 | GET / HTTP/1.0 1114 | 1115 | 1118 | 1121 | 1130 | 1132 | 1142 | 3 1153 | 1154 | 1155 | Change Cipher SpecFinished 1170 | 1171 | With resume 1182 | 1183 | 1184 | 1185 | --------------------------------------------------------------------------------