SSL layer

This page documents Web Polygraph traffic encryption capabilities related to SSL and TLS (a.k.a., HTTPS) protocols. This functionality is supported starting with Polygraph version 2.8.0.

Table of Contents

1. Terminology
2. Prerequisites
3. When to encrypt?
    3.1 Server-side
    3.2 Client-side
    3.3 Proxy side
    3.4 Configuration mismatch
4. SSL wraps
5. SSL Wrap
    5.1 Session caching and resumption
6. Caveats
7. Appendix: Key generation

1. Terminology

By default, the term SSL is used to denote all supported encryption protocols: SSLv2, SSLv3, and TLSv1. When applied to HTTP, this group of encryption protocols is also known as HTTPS.

2. Prerequisites

Polygraph SSL support is based on the OpenSSL library. A recent version of the library is required to compile Polygraph. We have tested with OpenSSL versions 0.9.6g and 0.9.7b. The presence of OpenSSL is determined at ./configure time. Please check that ./configure actually found SSL library and headers if you install Polygraph and want SSL support:

...
checking for CRYPTO_lock in -lcrypto... yes
checking for SSL_connect in -lssl... yes
checking for openssl/ssl.h... yes
checking for openssl/err.h... yes
checking for openssl/rand.h... yes

3. When to encrypt?

Using basic SSL features with Polygraph is quite simple, provided you understand this section.

SSL is, essentially, a security layer on top of another protocol. In our case, that other protocol is HTTP. Polygraph makes exactly the same HTTP-level decisions regardless of the SSL configuration options. The good old HTTP rules determine where Polygraph robots send their requests, whether they think they are talking to a proxy, etc. SSL does not change any of that.

The only big SSL-related decision Polygraph agents have to make is whether to treat an existing (i.e., already established) HTTP/TCP connection as an SSL-encrypted connection. To make that decision, a Polygraph agent (robot or origin server) must consult ssl_wraps array(s), a new configuration field added to Polygraph agents. The encryption decision algorithm is different on client and server sides and is documented below.

3.1 Server-side

On the server side, a server agent will treat an HTTP connection as an SSL-encrypted connection if and only if that server agent has a [non-empty] ssl_wraps array as a part of its PGL configuration:

Server srvSsl = {
    ...
    ssl_wraps = [ ... ];
};

In other words, servers do SSL when they are configured with ssl_wraps. Servers with ssl_wraps do SSL and only SSL.

3.2 Client-side

On the client side, a robot agent will treat an HTTP connection as an SSL-encrypted connection if and only if both of the following two conditions are met:

Robot rbtSsl = {
    ...
    ssl_wraps = [ ... ];
    origins = srvSsl.addresses;
};

In other words, Robots do SSL when they are configured with ssl_wraps and expect the final destination of the request to do SSL. As opposed to server agents, robots with ssl_wraps do either SSL or unencrypted HTTP, depending on the visible request destination. This asymmetry may seem unnatural, but it mimics real-life configurations since most SSL-enabled clients can do both SSL and HTTP (while SSL servers are usually dedicated to serving SSL requests).

See the next section for details about proxied cases.

3.3 Proxy side

It is important to remember that when deciding to enable SSL, SSL-capable robots look at the final visible destination of the request and not at the next hop on the request path. However, the next hop configuration may affect other SSL-related aspects of a test. The next hop can be:

The first two cases are equivalent from HTTP point of view because HTTP treats surrogates the same as origin server they represent (i.e., there is no protocol-level proxying going on).

The first case (a no-proxy mode) has already been discussed above.

When the next hop in your network setup is a surrogate doing SSL, you need to tell Polygraph that that proxy is actually an SSL server, using the Proxy PGL type. The Proxy type has been in PGL since year 2000, but was not used much. Now it will be:

Proxy pxySsl = {
    addresses = [ '172.16.0.5:443' ];
    server.ssl_wraps = [ ... ];
};
use(pxySsl); // do not forget to use all agents

The above Proxy configuration template is probably sufficient for most of your reverse proxy tests. The addresses array should contain the addresses where your proxy/ies are listening to SSL requests. Proxy addresses should also appear in the "names" array of an address map, as usual:

AddrMap accelerationMap = {
    names = pxySsl.addresses;      // from
    addresses = httpSrv.addresses; // to
};
use(accelerationMap);

These later mappings are needed for HTTP connections to work in the presence of reverse proxies accelerating Polygraph origin server. They are irrelevant to SSL. Recall that SSL decisions happen only after the two points of an HTTP connection are decided upon. The only change here is that SSL requires a Proxy agent so that we can put ssl_wraps settings somewhere.

For forward proxying, the proxy address will be used with --proxy command line option or in Robot.proxies array. When a robot decides to do SSL (using the above documented rules) but encounters a forward proxy on the way, the robot will use HTTP CONNECT method to establish a tunnel through the proxy first, just like a real web client would.

For example, the following trace shows robot CONNECT request, proxy Connection Established response, robot GET request, and origin server OK response. The latter two messages are tunneled through the proxy. Empty lines separate HTTP messages.

CONNECT 172.16.0.202:4443 HTTP/1.1

HTTP/1.0 200 Connection established

GET http://172.16.0.202:4443/... HTTP/1.1
Accept: */*
Host: 172.16.0.202:4443
X-Target: 172.16.0.202:4443

HTTP/1.1 200 OK
Cache-Control: public
Date: Mon, 06 Oct 2003 16:52:18 GMT
Content-Length: 309520
X-Target: 172.16.0.202:4443
... CRLF followed by 309520 octets of data ...

Usually, a tunneling proxy would not be able to look at the content of SSL-encrypted messages that it tunnels. However, if a proxy has origin server private keys or if the robot does not thoroughly validate server certificates, the proxy can terminate robot SSL encryption on itself (i.e., act as an origin server) and establish its own SSL session with the server. While this kind of behavior is a classic man-in-the-middle attack, it is actually useful in many controlled environments where the client trusts the proxy (e.g., a virus checking proxy on a corporate network).

CONNECT requests and responses are not counted separately from other HTTP messages and are viewed as a part of the following HTTP transaction. Thus, the first HTTP transaction on an SSL tunnel will have longer response times due to CONNECT overheads.

CONNECT phase of the tunnel establishment is performed in clear-text mode, without any SSL encryption. Thus, the level of concurrent SSL connections is often lower than the level of all concurrent HTTP/TCP connections (since some connections are in a CONNECT state and do not contribute towards SSL connections level).

3.4 Configuration mismatch

Note that SSL agents do not and cannot know whether the other end of the HTTP/TCP connection is actually doing SSL. You can, for example, [mis]configure an HTTP robot to talk to an SSL server. Similarly, if the proxy is not doing its SSL encryption and/or decryption part, it may end up speaking the "wrong" protocol to an agent. The result will be, of course, a bunch of errors from SSL and/or HTTP routines.

4. SSL wraps

Agents' ssl_wraps array is a selector for SslWrap objects:

SslWrap wrap1 = { ... };
SslWrap wrap2 = { ... };
SslWrap wrap3 = { ... };

Robot rbtSsl = {
    addresses = [ '172.16.0.1' ** 10 ];
    ssl_wraps = [ wrap1: 30%, wrap2: 20%, wrap3 ];
    ...
};

An SslWrap object describes SSL properties that should be used for SSL connections serviced by the corresponding agent. We will talk about SslWrap itself in the next section.

Note that ssl_wrap selection from ssl_wraps is "random" and "sticky". Random selection means that you cannot predict which robot will use what wrap unless you use a single wrap within a Robot configuration.

Being "sticky" means that a single wrap is assigned to each robot at the configuration time and the assignment does not change. This is natural -- real agents usually do not change their SSL properties (e.g., going from supporting SSLv2 to doing exclusively TLSv1). Sticky selection means that if you have many wraps and few robots, the specified wrap distribution (allocation percentages in ssl_wraps array) may not materialize.

You can have many Robot configurations, of course:

Robot rbtSslOld = {
    addresses = ...; // can be many
    ssl_wraps = [ sslWrapOld1, sslWrapOld2 ];
    ...
};

Robot rbtSslNew = {
    addresses = ...; // can be many
    ssl_wraps = [ sslWrapTlsOnly ];
    ...
};
...
use(rbtSslOld, rbtSslNew);

Now let's see what we can put inside the ssl_wraps array.

5. SSL Wrap

An SslWrap object describes SSL properties that should be used for SSL connections serviced by the corresponding agent. The setting available in SslWrap may grow as Polygraph abilities expand. For now, the following things can be specified:

SslWrap wrap = {
    protocols = [ "SSLv2":40%, "SSLv3", "TLSv1" ];
    root_certificate = "/tmp/root.pem";
    ciphers = [ "ALL:HIGH": 100% ];
    rsa_key_sizes = [ 512bit, 1024bit, 2048bit ];

    session_resumption = 40%;
    session_cache = 100;
};

If you are familiar with OpenSSL library design, you may notice that the above fields affect mostly SSL context (OpenSSL's SSL_CTX type).

"protocols" -- selects a protocol an agent will support; the selection is sticky per agent; defaults to "any" which stands for "SSLv23" in OpenSSL terminology.

"root_certificate" -- location of the root (CA) certificate file; that file is needed for servers to generate their certificates and for clients to verify server certificates; if not defined, servers will generate self-signed certificates and robots will not check server certificates; undefined by default

"ciphers" -- selects a ciphers the agent will use; the selection is sticky; see ciphers(1); defaults to "ALL";

"rsa_key_sizes" -- selects key length to use when auto-generating server private key; the selection is sticky; defaults to 1024 bits

"session_resumption" -- controls session caching and resumption algorithms; should be used together with session_cache, see below; defaults to 0%;

"session_cache" -- controls the size of the session cache for the session_resumption feature, see below; default varies based on the test side.

5.1 Session caching and resumption

Session caching algorithms differ on client and server sides.

Server-side session caching and resumption

A server agent enables OpenSSL session caching (with a configurable cache size) if session_resumption is positive and disables it otherwise.

The SSL session context ID is set if and only if session caching is enabled.

If the cache size is not configured, the OpenSSL default is used. OpenSSL documentation for SSL_CTX_sess_set_cache_size() says that the default is SSL_SESSION_CACHE_MAX_SIZE_DEFAULT (set to 1024*20 so that "up to 20000 sessions can be held"), but check what your OpenSSL is using by default. If the cache size is set to zero, OpenSSL uses an unlimited cache size that may be perceived as a memory leak.

There is currently no way to control server-side caching at a finer granularity because OpenSSL makes that very difficult if not impossible.

Client-side session caching and resumption

On the client side, robots maintain a stack of SSL sessions. The maximum size of the stack (in terms of number of sessions) is controlled by the session_cache setting.

Session_resumption determines the probability that the robot will attempt to resume the session for a new HTTP connection. Note that such attempts may fail for various reasons beyond Polygraph control. If the session was not resumed for that connection or the connection experienced I/O errors, the session object (if there was one used) is destroyed. Otherwise, the session object is put on the stack when the connection is closed.

If session_resumption is not positive, the robot disables all client-side session caching (which is also the OpenSSL default).

The session_cache option defaults to robot caches of unlimited size. Unlimited cache sizes may lead to a ever-growing stack of SSL sessions, which may be perceived as a memory leak. This default behavior may change in future Polygraph releases.

6. Caveats

If you use SSL servers, they will need to generate their certificates. The latter is done via calling OpenSSL command-line tools automatically from the server process. This scheme works, but is somewhat slow; a few second startup delay per server agent is typical.

An SSL configuration file, specified by the SslWrap.ssl_config_file parameter, is needed for server agents to generate their certificates runtime. This file can be rather complex but this simple example seems to be sufficient for our testing needs.

There are two ways Polygraph server can generate SSL certificates: self-signed certificates and CA-signed certificates.

Working with self-signed certificates is simpler because all needed certificates are generated runtime. Polygraph uses self-signed certificates if SslWrap.root_certificate parameter is not set.

Using CA-signed certificates requires generating a root CA certificate beforehand and setting the SslWrap.root_certificate parameter. Runtime, Polygraph would then generate server certificates signed with the configured root CA certificate. The Key generation appendix explains how to generate a root CA certificate.

Polygraph uses "/tmp/" directory to store temporary files needed to generate certificates.

Currently, SSL statistics are dumped on the console along with the usual stats for all traffic. SSL-related lines have "https" suffix as opposed to the "all" suffix of the lines that represent measurements for all protocols. If you get no lines marked with "https", then your test is not doing any SSL transactions.

000.54| i-test   4311 134.60    149   0.00   0   20 all
000.54| i-test   2111  66.40    145   0.00   0    8 https
000.63| i-test   5044 146.57    143   0.00   0   20 all
000.63| i-test   2450  67.79    158   0.00   0   10 https
000.71| i-test   5883 167.67    120   0.00   0   20 all
000.71| i-test   2871  84.13    118   0.00   0   10 https
...
001.14| p-test  10002 152.12    130   0.00   0 all
001.14| p-test   5016  76.29    130   0.00   0 https

We need to add a better interface to extract SSL-related stats from Polygraph. If there is a particular measurement you would like to see, please let us know.

7. Appendix: Key generation

This Appendix explains how to generate a root CA certificate useful for SSL tests.

You may use command-line tools that come with OpenSSL to create a private key and a self-signed certificate. You will need password-less keys unless you want to type passphrase(s) when Polygraph starts. A simple make-CA-cert.sh script does all the magic for you. It uses a special ca-ssl.conf SSL configuration file to generate a root CA certificate. The script puts both private (used by servers) and public (used by robots) keys into a single file, suitable for loading with the SslWrap.root_certificate option documented above.