BoarSSL and Twrch

BoarSSL is a new, independent SSL/TLS library written in C#. It uses its own implementations of cryptographic algorithms and handshake management.

BoarSSL is not intended for production purposes; it is meant to be used for automatic tests of SSL/TLS libraries, in particular BearSSL. This framework is called Twrch: it is a stand-alone .NET command-line application that repeatedly launches a configurable binary that runs the SSL implementation that is to be tested, and performs handshakes with various parameter combinations. This concept is not novel and is used, for instance, in BoringSSL’s test suite.

Reuse

Humans being who they are, it is a certainty that, despite the disclaimer above, some people will use BoarSSL for some production purpose. In that case, be aware of the following:

  • A secure SSL client MUST validate the server’s X.509 certificate chain against a proper set of trust anchors, and also check that at least one of the names included in the server’s certificate matches the intended server name1. In BoarSSL, this is done by an application-provided callback set in SSLClient.ServerCertValidator. An implementation is provided with BoarSSL, and is called InsecureCertValidator because it does not validate anything. The name is intentional. Using that validator implies throwing away almost all of the security guarantees provided by SSL, so do not ever do that in production. You have been warned.

  • The AES and DES (3DES) implementations are not constant-time. The other algorithms (including RSA and EC) are constant-time, subject to the usual caveats about multiplication opcodes.

  • The RSA and EC implementations use a generic implementation which is not the fastest ever (it is similar to BearSSL’s “i31” code, but the C#/.NET layer tends to add some extra overhead). The EC code does not take advantage of any specific format to the underlying field, which allows simpler but slower implementations.

  • No attempt is made at leveraging any platform-specific opcode such as SSE2 or AES-NI.

In general, when doing HTTPS calls from within a .NET environment, you are better off using the SSL implementation from the system class library, because that one will benefit from system-wide configuration, not only for certificates and trust anchors, but also for proxies, in particular those proxies that require some sort of authentication which may or may not be based on the current user’s domain credentials.

Features

In general, BoarSSL supports the same set of algorithms as BearSSL, i.e.:

  • 3DES/CBC, AES/CBC, AES/GCM and ChaCha20+Poly1305 for record encryption.
  • RSA signatures and encryption.
  • ECDHE over Curve25519 and the three main NIST curves (P-256, P-384 and P-521).
  • ECDSA over the NIST curves.
  • MD5, SHA-1, and the SHA-2 functions (SHA-224, SHA-256, SHA-384 and SHA-512) for hashing.
  • Session resumption (abbreviated handshake).
  • Server Name Indication extension.
  • Renegotiations, with RFC 5746 support (Secure Renegotiation).

Some features are currently missing and will be added in a later version:

  • Client certificates.
  • Maximum Fragment Length.
  • ALPN.

Downloading and Running

BoarSSL code (including Twrch) can be obtained from the git repository:

git clone https://www.bearssl.org/git/BoarSSL

The source tree can also be explored through the Gitweb-powered interface.

The source code is split into a number of sub-directories, which correspond to so many C# namespaces with the same names:

  • Asn1 is a generic ASN.1/DER encoding/decoding library.
  • Crypto contains the cryptographic algorithm implementations.
  • X500 contains some code specific to decoding and encoding X.500 distinguished names.
  • XKeys handles decoding and encoding of public and private keys (including automatic detection of PKCS#8 and “internal” formats, and PEM).
  • SSLTLS is the SSL engine properly said.

The five directories above constitute the BoarSSL library itself. Some extra directories are also present; they use BoarSSL, but there is no dependency from BoarSSL to any these:

  • Tests contains a command-line application that performs unit tests on the cryptographic algorithms implemented by BoarSSL.
  • ZInt is a stand-alone big-integer library used by the tests (and only the tests). It is not constant-time and is consider unfit for serious cryptography, but it is otherwise convenient for computations.
  • Twrch contains the Twrch test framework.

To compile, use the build.sh (on Unix-like systems) or build.cmd (on Windows) scripts. Primary development platform of BoarSSL is Linux, so you can expect the code to work well with Mono; however, it also runs on Windows. The build.cmd script invokes the .NET 4 command-line compiler (csc.exe) which should be present on any recent Windows, and can be added easily to older versions (even Windows XP).

To run Twrch, check the contents of conf/bearssl.json: this is the configuration file for the tests. It is a JSON file. You will want to verify that the commandFile parameter points to the location of the brssl file (or brssl.exe on Windows) that is a product of the compilation of BearSSL; the default contents of that configuration file assume that the BoarSSL source tree was rooted as a subdirectory of the BearSSL source tree.

Then run:

mono Twrch.exe conf/bearssl.json

(On Windows, drop the “mono” part, you can use the executable file name directly.)

This will run the tests in due order. Most of the tests are a systematic exploration of all combinations of protocol version, cipher suite, elliptic curve, and hash-and-sign algorithm; these can be skipped by adding the -noenum parameter to the Twrch command line. After these tests come a few dozen of specific tests that correspond to the tests element of the JSON file.

Each test has a name, and actually corresponds to two tests, one with the suffix “_client” (Twrch acts as a server to test the behaviour of the library as a client), the other with the suffix “_server”. When a test fails, the corresponding name is printed. The Twrch command-line optionally accepts one or several test names (with or without the _client and _server suffixes): if test names are provided, only these tests are executed.

Each test expects either a success or a failure; some tests exercise abnormal conditions that are supposed to be detected and punished by the SSL library. If the intended outcome is not obtained, then an explicit error message appears. More information can be obtained by running Twrch with the -cv and/or -trace command-line parameters:

  • -cv invokes the tested binary with the -v option, thereby instructing it to be more verbose about what happens.

  • -trace makes Twrch print an hexadecimal dump of all the bytes it sends and receives.

At the time of writing these lines, a full Twrch run involves 1702 executions of the test binary, hence at least that many handshakes (some tests trigger several handshakes). Moreover, for most of them, a few dozen messages will be exchanged back and forth, for a total size close to half a megabyte. The cumulated latencies of running the binary (once per test), doing the handshakes, and exchanging the messages, make the whole sequence take a few minutes. A test counter will keep you amused while the tests run. If everything runs fine, then Twrch should ultimately report exactly zero failure.

Porting

Twrch can conceptually be used to test other SSL libraries. In order to do so, you must provide two things: a JSON configuration file, and a test binary.

Configuration File

The JSON configuration file lists all the protocol versions, cipher suites, elliptic curves and hash-and-sign algorithms that you wish to test. Not all libraries support all the algorithms implemented by BoarSSL, and, of course, BoarSSL cannot test algorithms that it does not itself support.

Apart from the “enumeration” tests, the dedicated tests, that correspond to the tests section in the JSON file, are tuned to verify BearSSL’s intended behaviour. These may need some adaptations, for the following reasons:

  • There is some variance in the behaviour of libraries. For instance, some libraries won’t tolerate renegotiations, or, if instructed to accept renegotiations, will do so even if the peer does not provide support for the Secure Renegotiation extension (RFC 5746).

  • In many abnormal cases, BearSSL simply drops the connection without sending a fatal alert message. The two conceptual reasons to do so are the following:

    • When invalid messages are detected, it is proven that the peer does not follow the protocol properly. Therefore, there is no guarantee that an alert message will be understood at all and won’t make matters worse.

    • Most abnormal conditions occur when reading data from the peer, and sending an alert message then breaks half-duplex discipline. This is very problematic for constrained systems that use the same RAM buffer for sending and for receiving. If what the peer sends does not make sense, there is no way to reliably detect when the peer is finished and ready to receive a fatal alert.

    BearSSL still sends alerts when it is “safe”, i.e. on the server, after receiving the ClientHello, to explain why a handshake cannot proceed: at that point, the server knows that the client is waiting for a ServerHello, so sending a fatal alert works. Alerts are also used to deny renegotiation requests when appropriate, and to handle the correct closure protocol (close_notify).

    Not all libraries work that way. From the point of view of Twrch, this means that an expected failure may show up in several different ways (specifically, several different error messages), and the JSON file must be adjusted appropriately.

Protocol

For each test, Twrch launches the configured command. That command is supposed to run the SSL protocol (as a client or a server, depending on its command-line arguments) over its standard input and output (stdin and stdout, in C terminology). When the connection is terminated, the command should exit with an exit code of 0 in case of normal termination, or 1 if something went wrong. Twrch runs the opposite part of the connection and gathers the exit code.

The command-line parameters that Twrch may provide to the test binary are the following:

  • -client

    The test binary shall run a SSL client engine (Twrch will run the server part). Exactly one of -client and -server is specified.

  • -server

    The test binary shall run a SSL server engine (Twrch will run the client part). Exactly one of -client and -server is specified.

  • -cert fname

    The name of a file containing the certificate chain to use. For now, this is provided only to binaries running the server role; but clients will also receive it when client certificates are implemented in BoarSSL.

  • -key fname

    The name of a file containing the private key. This option is provided when -cert is also set; the file contains the private key corresponding to the leaf certificate in the chain (the first in the chain file).

  • -v

    When set, the test binary should become “verbose” about what happens. Since standard output is used for the handshake itself, such verbosity should happen on the standard error stream (stderr).

Each test in the JSON file may specify some custom extra arguments (with “extraArgs”).

Once the initial handshake has been done, the test binary is supposed to read application data and process it as lines:

  • A “line” is a sequence of bytes terminated by a newline character (a byte of value 0x0A).

  • If the line has length exactly 1 byte (not counting the terminating newline character), and that byte is an uppercase letter, then it is a command, to be executed by the test binary:

    • C

      The test binary shall initiate a connection closure (sending a close_notify, which triggers a close_notify response from Twrch, and then exiting with an exit code of 0).

    • T

      The test binary shall initiate a connection closure, just like the C command. However, it must then start a new connection (that is, a new initial handshake over the still open stdin and stdout) instead of exiting. The test binary shall endeavour to resume the session in that second handshake, unless instructed otherwise (see the U command).

    • R

      The test binary shall respond with a line containing “OK” (three bytes, including the terminating newline). The meaning of this command is that Twrch is about to close the connection (with the close_notify alerts); but the test binary should then start a new connection (new initial handshake) instead of exiting.

    • U

      The test binary shall “forget” the current session: this means that if reconnecting (after a T or R command), then the new initial handshake shall not attempt to resume the current session. In response to this command, the test binary must write a line containing “DONE” (five bytes, including the terminating newline).

    • G

      The test binary should attempt a renegotiation: as a client, it should send a new ClientHello; as a server, it should send an HelloRequest. If the test binary is willing to perform a renegotiation, then it should do so and send a line containing “OK” (three bytes, including the terminating newline). If the test binary is unwilling (e.g. Twrch did not advertise support for the Secure Renegotiation extension, and the test binary does not want to renegotiate without that extension), then it should respond with a line containing “DENIED” (seven bytes, including the terminating newline).

      Note that willingness to perform a renegotiation does not mean that it will actually happen. Twrch may ask the test binary to start a renegotiation, then deny it with a no_renegotiation warning alert.

  • If the line is not a command (it has length 0, it has length 2 or more, or it contains a single byte which is not an uppercase ASCII letter), then the test binary should hash the line contents (without the terminating newline) with SHA-1, and then respond with a line containing the hash result, in lowercase hexadecimal (41 bytes, including the terminating newline).

Note that line data may come over an arbitrary number of records. Some tests exercise record splitting, and additional records of length zero.


  1. Technically, a secure SSL client may also know the server’s public key through some appropriate out-of-band mechanism, e.g. hardcoded in the client code. But something must be done, instead of accepting the received certificate at face value and extracting the public key from it.