I began BearSSL in early 2015. Initially, I was motivated by yet another buffer overflow in OpenSSL. I have long been of the opinion that a good software project must be written at least three times: once to understand the problem, once to understand the solution, and once to actually write it properly. Thus, it is expected that such a project would be scraped at least twice. OpenSSL, with its very long history and code accumulation, looked like a project sorely needing yet another such scraping.
In my vanity I endeavoured to restart the whole SSL library from scratch, basically to show the World how such things should be done.
Then my ego deflated a tiny bit, and I asked myself whether a new SSL/TLS library could be, indeed, useful in general and not just to power my arrogance. This lead me to define the following goals:
A portable library that focuses on embedded systems. It so happens that several times in my career, I have been tasked, specifically, to write a SSL implementation that can run on some constrained systems, where existing libraries were not a good fit, for lack of available resources (especially RAM) or underlying OS functionality (in one case it was to run in bootstrap code, without any actual OS). Existing libraries like OpenSSL are ill-fitting for that, and even specialised implementations such as mbed TLS still need some facilities such as
BearSSL shall thus be runnable on “bare metal” with minimal resources. This implies moving a bit the usual trade-off, i.e. accepting to lose a bit of raw speed in order to get a smaller code footprint. It also requires a “streamed” design without growable buffers.
Portability unfortunately involves using C, which is the lingua franca of embedded systems. A case can be made that C is a dangerous tool and that most if not all software development should use alternatives that enforce more checks (be it Java, Rust, Ada, or hundreds of others), but the state of the software development industry, especially for firmware of small systems, is such that the choice is between using C and switching jobs.
Secure by default. Default settings and implementations should be safe. For instance, a lot of systems using SSL fail to have good security because they misuse the library, e.g. by not actually validating the server’s certificate, making them easy prey to server impersonation and man-in-the-middle attacks. BearSSL should endeavour to provide API such that the simplest way to use them is safe.
For instance, it would be relatively easy to add a simple certificate validator that does not actually validate the server’s certificate, and accepts it on face value; BearSSL even has all the code for that. But I won’t provide it directly, because the potential for catastrophic misuse is too great. I know that if I did offer such code, many device firmware writers would simply use it without wondering whether this is a good idea.
The “safe by default” stance implies not supporting by default (or at all) algorithms and options that have known serious defects. This however goes against interoperability with legacy systems, so there is some trade-off and a line to draw. For instance, BearSSL does not implement SSL 3.0, RC4, or RSA/MD5 signatures. It does, however, support TLS-1.0, 3DES and RSA/SHA-1 signatures, though it keeps them at the lowest preference orders, thereby using them only if the alternative is no SSL at all.
A facet of “safe by default” is the use of constant-time cryptographic implementations. This applies to hashing, symmetric encryption, asymmetric operations (RSA, elliptic curves), handling of CBC+HMAC records… Whether constant-time cryptography is important depends on the usage context, and is debatable in general, but BearSSL “plays safe” and thus enforces the constant-time implementations by default.
Link-time trimming. While in all generality any given code with optional elements can be trimmed down by spreading out some
#ifdefclauses throughout the source code, such a mechanism would require recompiling the library code itself whenever the set of options (algorithms, cipher suites…) to be used is to be changed. This is inconvenient, so I resolved to instead follow the static linking method, by which each feature is basically a function or set of functions, accessed through pointers, and the calling application triggers the inclusion of only the relevant part by referencing the corresponding pointers.
This does not preclude the compilation of BearSSL as a dynamic library, and, indeed, on “big” systems (e.g. desktop computers), dynamic libraries have huge maintainability benefits: if you have a security fix to apply in a single library, you don’t want to have to recompile or download again the hundreds of packages that use it.
(Note: BearSSL’s Makefile now supports DLL building. However, be mindful that its binary interface is not completely stable yet, and may still break at any time as long as version 1.0 is not reached.)
A state-machine API. Most SSL implementations assume the existence of some underlying transport medium accessible through a pair of callback functions or objects (in languages that try to be object-oriented). This is fine and works well until you have to run two connections concurrently, in which case you will need some sort of threading. Threads are a kind of luxury, if only because each thread has its own stack, which uses up the precious RAM. In the contexts on which BearSSL focuses, threads cannot be assumed to be available. Thus, the API should be designed as a state machine, into which data is pushed and from which it is retrieved; I/O then happens outside of the engine.
The command-line tool provided with BearSSL uses that API to perform a simple two-way transfer between a SSL connection and stdin/stdout, with a
poll()call to wait on the relevant file descriptor (see the
tools/sslio.cfile – note that the command-line tool uses unixisms, in particular descriptors and system calls, while the core library does not).
Using a state-machine API has some profund consequences on the design, especially when combined with the no-malloc goal. Note: this kind of state-machine API design was inspired from zlib, a well-known implementation of the Deflate compression algorithm.
Since in some situations a callback-based API with simple read and write calls can be convenient, I added such an API into BearSSL. This is easily done as a wrapper around a state machine; note that the converse is not true (you cannot get a state machine with a simple wrapper around a callback-based engine).
Extensible design. It should be possible to easily swap implementations of cryptographic algorithms, because different usage contexts call for distinct implementation strategies. For instance, a modern x86 system would use the dedicated opcodes for AES encryption, which are not available on older systems or non-x86 platforms.
Clear, documented, freely usable source code. Ideally, the source code should be usable as a teaching tool for some of the techniques used therein. Of course this will need a lot more comments and explanations than the current state, but that’s still a worthy goal.
On a less technical plane, existing SSL libraries that target embedded systems tend to have licensing constraints such as GPL, which tend to scare away some potential users because of legal uncertainties (whether such fears are justified is another debate). BearSSL provides a MIT-Licensed alternative, which is about as unrestrictive as you can get (depending on jurisdiction, putting things “in the public domain” can be confusing or downright impossible).
Now it is clear that while these goals are all nice and rationally explainable, what powers me is hubris. I consider programming as a kind of art, and, as any artist, I want to demonstrate my skill through my creations. I aim for people to look at my code and say to themselves, “man, that guy knows his craft” (or possibly, “this guy is very thorough in his specific brand of craziness”). It just so happens that in the case of software development, such creations can even be useful to other people.