There has been a lot of work on better cryptography libraries in the wake of a number of OpenSSL bugs. One of the major steps forward in this realm is NaCl, or the Networking and Cryptography Library. NaCl aims to address the fact that most older crypto libraries are quite difficult to use, and misuse is often the source of vulnerabilities.
In my recent work on FreeBSD, I ran into the kernel crypto code. It is worth mentioning that older crypto, particularly kernel crypto frameworks tend to hearken back to days when things were different than they are now. For one, strong crypto was classified as a munition, and exporting it from various countries ran afoul of international arms trafficking laws. Second, CPUs were much slower back then, crypto represented a more significant overhead, and most hardware crypto devices were attached to the PCI bus. In the modern world, we have Bernstein v. United States (publication of crypto is protected free speech), CPUs are much faster, and hardware crypto typically takes the form of special CPU instructions, not devices that have to be accessed through kernel interfaces.
This state of affairs tends to lead to fairly fragmented crypto codebases, which is exactly what the FreeBSD kernel crypto codebase looks like. Moreover, it completely lacks any public-key algorithms, which are necessary for kernel and driver signing. Lastly, existing userland crypto libraries tend not to fair so well when being converted into kernel libraries, as they tend to rely on userland utilities to operate.
To address this, I recently started working on ideas for a lightweight, embeddable crypto library I’m calling LiCl. The name of course is the chemical symbol for lithium chloride: a salt similar to sodium chloride (NaCl). An interpretation of the name could be “lightweight interoperable crypto library”, though it occurred to me that “Lego-inspired crypto library” also works, as the design involves building cryptosystems out of “blocks”.
LiCl aims to produce a lightweight crypto library that is easy to use and also easy to drop into any application (userland, kernel, or embedded). It has several design goals, which I’ll discuss here.
Control over Crypto through Policies
Aspects of the library should be governed by policies which can be set both at library build time as well as in any application that uses the library. Policies should be as fine-grained as “don’t use these specific algorithms”, all the way up to things like “don’t use hardware random number generators”, or “only use safecurves-approved ECC”. If done right, this also captures the configuration options necessary to say things like “don’t use anything that depends on POSIX userland”.
This is done in the implementation through a variety of C preprocessor definitions that control which implementations are present in the library, and which can be used by an application.
NaCl-Style Easy Interfaces
NaCl is designed to eliminate many bugs that can arise from improper use of crypto by providing the simplest possible interface through its “box” functions. In NaCl, this works, as it aims to provide a crypto interface for network applications.
LiCl, on the other hand, aims to provide a more general toolbox. Thus, it needs a way to build up a NaCl-style box out of components. As we’ll see, I have a plan for this.
Curate Crypto, Don’t Implement It
Most of LiCl will be the code devoted to assembling the external crypto interfaces. The actual crypto implementations themselves will be curated from various BSD-compatible licensed or public-domain source. Now, of course, I may run into some algorithms that require direct implementation; however, I intend to actually write crypto code myself as a last resort.
My plans for LiCl actually draw on programming language concepts to some degree, where objects describing components of a crypto scheme represent an AST-like structure that is used to generate a NaCl-style interface. I’ll go into the various components I’ve worked out, and how they all fit together.
It should be remembered that this is a design in progress; I’m still considering alternatives.
The User-Facing Crypto Interfaces
Right now, there are six user-facing interfaces, all of which take the form of structs with function pointers, each of which take a user data pointer (in other words, the standard method for doing object-oriented programming in C). The exact length of the user data depends on the components from which the interface was built. The six interfaces are as follows:
- Symmetric-key encryption (stream cipher or block cipher with a mode)
- Symmetric-key authenticated encryption
- Symmetric-key authentication (hashing with a salt)
- Public-key encryption
- Public-key authenticated encryption (encryption with signature checking)
- Public-key authentication (signature verification)
These interfaces represent the combination of multiple crypto methods to create a complete package that should handle all the details in a secure fashion. The result is that we can support encryption/decryption and signing/verification in a NaCl box-like interface.
Creating User-Facing Interfaces
A user creates one of the above interfaces by assembling components, each of which represents some cryptographic primitive or method (for example, a hash function, or a block cipher mode). The key is ensuring that users assemble these components in a way that is valid and secure. This will be guaranteed by a “build cryptosystem” function that performs a consistency check on the specification it’s given. For example, it shouldn’t allow you to encrypt an authenticated message (encrypt-then-MAC). Another reason for this is for supporting hardware crypto, which may impose various limits on how the primitives those implementations provide can be used.
I come from a programming language background, so I like to think about this in those terms. The “build cryptosystem” function acts similarly to a compiler, and the rules are similar to a type system. The key here is figuring out exactly what the “types” are. This is an ongoing task, but it starts with figuring out what the basic component model looks like. I have a good start on that, and have identified several kinds of components.
Ultimately, we’ll build up a cryptosystem out of components. A components is essentially a “block” of crypto functionality, which itself may be built out of other components. For example, a keystore may require a random source. I’ve sketched a list of components so far, and will discuss each one here:
Random sources are essential in any cryptosystem. In LiCl, I want to support an HSM-style interface for key generation and storage, so it’s necessary to provide a random source for generating keys. There are also concerns such as padding that require random bits. Random sources are the only thing in the GitHub repo at the moment, and the only one is the POSIX urandom source. The first curation task is to identify a high-quality software random number generator implementation that’s BSD/MIT licensed or public domain.
LiCl’s interfaces are built around an assumption that there’s a layer of indirection between keys and their representation in memory. This is done to enable use of HSMs and other hardware crypto. A keystore interface represents such an indirection.
Keystores have a notion of an external representation for keys. In the “direct” keystore implementation, this is the same as the actual representation; in an HSM-based keystore, it might be an ID number. Keystores provide the ability to generate keys internally, add keys to the store, delete keys, and extract a key given its external representation.
The only implementation so far is the “direct” keystore, which is just a passthrough interface. It requires a random source for its keygen functionality.
One major building block is the ability to perform a given operation on arbitrary-sized data. This is innate in some primitives, such as stream ciphers and hash functions. In others, it involves things like modes of operation and padding.
This is where the type-like aspects begin to become visible. For example, the GCM block cipher mode takes a fixed-size symmetric-key encryption operation and produces an arbitrary-sized symmetric-key authenticated encryption operation. We could write this down in a semi-formal notation as “symmetric enc fixed size (n) -> symmetric auth enc variable block size(n)”. Padding operations would eliminate the restriction on input size, and could be written as “algo variable block size (n), randsrc -> algo output variable output block size (n)”.
Of course, we wouldn’t write down this notation anywhere in the actual implementation (except maybe in the documentation). In the code, it would all be represented as data structures.
Ultimately, we’d need to assemble components to get an arbitrary-sized operation with no input block size restriction. We’d also need to match the algorithm type of the scheme we’re trying to build (so if we want authenticated symmetric key encryption, we need to ensure that’s what we build).
MAC Schemes and Signing
MAC schemes and signing algorithms both take a hash function and an encryption scheme and produce an authenticated encryption scheme. Signing algorithms also require a public-key crypto scheme. In the semi-formal notation, a MAC scheme might look something like this: “symmetric enc variable, hash -> symmetric auth enc variable”
Ciphers and Hashes
Ciphers are of course the basic building blocks of all this. Ciphers may have different characteristics. Block ciphers might be written as “symmetric enc fixed size(n)”. An authenticated stream cipher would be written as “symmetric auth enc variable”.
Putting it All Together
Ultimately, the “build cryptosystem” functions will take a tree-like structure as an argument that describes how to combine all the various components to build a cryptosystem. They then perform a consistency check on the whole tree to ensure that everything is put together correctly and then fill up a cryptosystem structure with all the implementation functions and data necessary to make it work.
With the design I’ve described, it should be possible to build a crypto library that will serve the needs of kernel and systems developer, but will also make it easier to use crypto in a manner that is correct.
The biggest remaining question is whether this design can effectively deal with the legacy interfaces that kernel developers must deal with. However, it seems at least plausible that the model of assembling components should be able to handle this. Afterall, even legacy systems are ultimately just assembling crypto primitives in a particular way; if a particular system can’t be modeled by the components LiCl provides, it should be possible to implement new components within the model I’ve described.
The repository for the project is here, however, there isn’t much there at this point.