├── .gitignore ├── .gitmodules ├── .travis.yml ├── Makefile ├── README.md ├── common-client.c ├── common.c ├── common.h ├── default.nix ├── gnutls-client.c ├── js ├── jquery.jsonp-2.1.4.min.js └── rfc5077-server.js ├── nss-client.c ├── openssl-client.c ├── rfc5077-client.c ├── rfc5077-pcap.c ├── rfc5077-server.c ├── rfc5077-server.html ├── rfc5077-stats.py └── 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -g -Werror -Wall -ansi -std=c99 -D_DEFAULT_SOURCE -D_GNU_SOURCE 2 | LDFLAGS= 3 | EVCFLAGS=$(shell pkg-config --silence-errors --cflags libev) 4 | OPENSSL_LIBS=$(shell pkg-config --libs "openssl >= 1.1") 5 | OPENSSL_CFLAGS=$(shell pkg-config --cflags "openssl >= 1.1") 6 | EXEC=rfc5077-client rfc5077-server rfc5077-pcap openssl-client gnutls-client nss-client 7 | 8 | all: $(EXEC) 9 | 10 | openssl-client.o: openssl-client.c 11 | $(CC) $(CFLAGS) $(OPENSSL_CFLAGS) -c -o $@ $^ 12 | 13 | openssl-client: openssl-client.o common-client.o common.o 14 | $(CC) -o $@ $^ $(LDFLAGS) $(OPENSSL_LIBS) 15 | 16 | gnutls-client: gnutls-client.o common-client.o common.o 17 | $(CC) -o $@ $^ $(LDFLAGS) -lgnutls 18 | 19 | nss-client: nss-client.o common-client.o common.o 20 | $(CC) -o $@ $^ $(LDFLAGS) $(shell nss-config --libs) $(shell nspr-config --libs) 21 | 22 | nss-client.o: nss-client.c 23 | $(CC) $(CFLAGS) $(shell nss-config --cflags) $(shell nspr-config --cflags) -c -o $@ $^ 24 | 25 | rfc5077-client.o: rfc5077-client.c 26 | $(CC) $(CFLAGS) $(OPENSSL_CFLAGS) -c -o $@ $^ 27 | 28 | rfc5077-client: rfc5077-client.o common.o 29 | $(CC) -o $@ $^ $(LDFLAGS) $(OPENSSL_LIBS) 30 | 31 | rfc5077-server.o: rfc5077-server.c 32 | $(CC) $(CFLAGS) $(OPENSSL_CFLAGS) $(EVCFLAGS) -c -o $@ $^ 33 | 34 | rfc5077-server: rfc5077-server.o common.o http-parser/libhttp_parser.a 35 | $(CC) -o $@ $^ $(LDFLAGS) -lev $(OPENSSL_LIBS) 36 | 37 | http-parser/libhttp_parser.a: http-parser/http_parser.c 38 | $(MAKE) -C http-parser package 39 | 40 | rfc5077-pcap.o: rfc5077-pcap.c 41 | $(CC) $(CFLAGS) $(shell pcap-config --cflags) -c -o $@ $^ 42 | 43 | rfc5077-pcap: rfc5077-pcap.o common.o 44 | $(CC) -o $@ $^ $(LDFLAGS) $(shell pcap-config --libs) 45 | 46 | certificate: key.pem cert.pem dh.pem 47 | key.pem: 48 | certtool --bits 2048 --generate-privkey --outfile $@ 49 | cert.pem: key.pem 50 | certtool --generate-self-signed --load-privkey $^ --outfile $@ 51 | dh.pem: 52 | certtool --bits 2048 --generate-dh-params --outfile $@ 53 | # for later gnutls utils 54 | # certtool --sec-param normal --generate-privkey --outfile $@ 55 | # certtool --sec-param normal --generate-dh-params --outfile $@ 56 | 57 | clean: 58 | rm -f *.pem *.o $(EXEC) 59 | $(MAKE) -C http-parser clean 60 | 61 | .PHONY: clean certificates all 62 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /common.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static char current[2048]; 9 | static int running = 0; 10 | 11 | #define BEGIN "\r\033[2K" 12 | #define CHECK "\033[1;32m✔\033[0m" 13 | #define BALLOT "\033[1;31m✘\033[0m" 14 | #define WARN "\033[1;33m‼\033[0m" 15 | 16 | static void 17 | display(const char *sign, const char *format, va_list ap) { 18 | if (running) { 19 | running = 0; 20 | fprintf(stderr, BEGIN "[%s] %s%c\n", sign, current, format?':':'.'); 21 | fflush(stderr); 22 | } 23 | 24 | if (format) { 25 | /* We indent the message */ 26 | int n = 0; 27 | int size = 0; 28 | char *message = NULL; 29 | char *cur; 30 | while (n >= size) { 31 | if ((message = realloc(message, size + 8192)) == NULL) return; 32 | size = size + 8192; 33 | if ((n = vsnprintf(message, size, format, ap)) == -1) return; 34 | } 35 | cur = message; 36 | fprintf(stderr, " │ "); 37 | while (*cur) { 38 | if (*cur == '\n') 39 | fprintf(stderr, "\n │ "); 40 | else 41 | fprintf(stderr, "%c", *cur); 42 | cur++; 43 | } 44 | fprintf(stderr, "\n"); 45 | fflush(stderr); 46 | free(message); 47 | } 48 | } 49 | 50 | void 51 | start(const char *format, ...) { 52 | va_list ap; 53 | int n; 54 | if (running) end(NULL); 55 | 56 | /* Save the current message */ 57 | va_start(ap, format); 58 | n = vsnprintf(current, sizeof(current), format, ap); 59 | va_end(ap); 60 | if (n == -1 || n >= sizeof(current)) 61 | exit(EXIT_FAILURE); 62 | 63 | /* Display */ 64 | fprintf(stderr, "[ ] %s ...", current); 65 | fflush(stderr); 66 | running = 1; 67 | } 68 | 69 | void 70 | end(const char *format, ...) { 71 | va_list ap; 72 | va_start(ap, format); 73 | display(CHECK, format, ap); 74 | va_end(ap); 75 | } 76 | 77 | void 78 | fail(const char *format, ...) { 79 | va_list ap; 80 | va_start(ap, format); 81 | display(BALLOT, format, ap); 82 | va_end(ap); 83 | exit(EXIT_FAILURE); 84 | } 85 | 86 | void 87 | warn(const char *format, ...) { 88 | va_list ap; 89 | va_start(ap, format); 90 | display(WARN, format, ap); 91 | va_end(ap); 92 | } 93 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 |
    38 |
  • 39 | You need to enable Javascript to make this page work. 40 |
  • 41 | 42 |
  • 43 | Your user agent is ?. 44 |
  • 45 | 46 |
  • 47 | We are fetching server ports. 48 |
  • 49 | 50 |
  • 51 | Before starting, you need to accept the certificate 52 | available for ports . 53 |
  • 54 | 55 |
  • 56 |
    57 | Once you are ready, click the 58 | button. 59 |
    60 |
  • 61 |
  • 62 | OK, starting tests. 63 |
  • 64 | 65 |
  • 66 | Checking used cipher suite. 67 |
  • 68 |
  • 69 | Your browser is actually using ? cipher suite and ?. 70 |
  • 71 | 72 |
  • 73 | Checking session resumption without cache nor tickets. 74 |
  • 75 |
  • 76 | Your browser ? support session resume 77 | without cache and without tickets (and it should not). 78 |
  • 79 | 80 |
  • 81 | Checking support for session resume without tickets. 82 |
  • 83 |
  • 84 | Your browser ? support session resume 85 | without tickets. 86 |
  • 87 | 88 |
  • 89 | Checking support for session resume with tickets. 90 |
  • 91 |
  • 92 | Your browser ? support session resume 93 | with tickets. 94 |
  • 95 | 96 |
  • 97 | Checking support for session resume with both cache and tickets. 98 |
  • 99 |
  • 100 | Your browser ? support session resume 101 | with both cache and tickets. 102 |
  • 103 | 104 |
  • 105 | That's all! 106 |
  • 107 | 108 |
  • 109 | Sorry, a fatal error shot us. 110 |
  • 111 | 112 |
113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------