Zinc: a new kernel cryptography API
Please consider subscribing to LWNSubscriptions are the lifeblood of LWN.net. If you appreciate this content and would like to see more of it, your subscription will help to ensure that LWN continues to thrive. Please visit this page to join up and keep LWN on the net.
We looked at the WireGuard virtual private network (VPN) back in August and noted that it is built on top of a new cryptographic API being developed for the kernel, which is called Zinc. There has been some controversy about Zinc and why a brand new API was needed when the kernel already has an extensive crypto API. A recent talk by lead WireGuard developer Jason Donenfeld at Kernel Recipes 2018 would appear to be a serious attempt to reach out, engage with that question, and explain the what, how, and why of Zinc.
WireGuard itself is small and, according to Linus Torvalds, a work of art. Two of its stated objectives are maximal simplicity and high auditability. Donenfeld initially did try to implement WireGuard using the existing kernel cryptography API, but after trying to do so, he found it impossible to do in any sane way. That led him to question whether it was even possible to meet those objectives using the existing API.
By way of a case study, he considered big_key.c. This is kernel code that is designed to take a key, store it encrypted on disk, and then return the key to someone asking for it if they are allowed to have access to it. Donenfeld had taken a look at it, and found that the crypto was totally broken. For a start, it used ciphers in Electronic Codebook (ECB) mode, which is known to leave gross structure in ciphertext — the encrypted image of Tux on the left may still contain data perceptible to your eye — and so is not recommended for any serious cryptographic use. Furthermore, according to Donenfeld, it was missing authentication tags (allowing ciphertext to be undetectably modified), it didn't zero keys out of memory after use, and it didn't use its sources of randomness correctly; there were many CVEs associated with it. So he set out to rewrite it using the crypto API, hoping to better learn the API with a view to using it for WireGuard.
The first step with the existing API is to allocate an instance of a cipher "object". The syntax for so doing is arguably confusing — for example, you pass the argument CRYPTO_ALG_ASYNC to indicate that you don't want the instance to be asynchronous. When you've got it set up and want to encrypt something, you can't simply pass data by address. You must use scatter/gather to pass it, which in turn means that data in the vmalloc() area or on the stack can't just be encrypted with this API. The key you're using ends up attached not to the object you just allocated, but to the global instance of the algorithm in question, so if you want to set the key you must take a mutex lock before doing so, in order to be sure that someone else isn't changing the key underneath you at the same time. This complexity has an associated resource cost: the memory requirements for a single key can approach a megabyte, and some platforms just can't spare that much. Normally one would use kvalloc() to get around this, but the crypto API doesn't permit it. Although this was eventually addressed, the fix was not trivial.
So Donenfeld's experiences left him convinced that although the current crypto API has "definitely been developed by some smart people who can really code [...] who can really push the limits of what we're used to in C", it is a big, fancy enterprise API that is hard to use, which means that people often use it wrong. It's also, he said, a museum of ciphers. Primitives and their multiple implementations lie stacked about the place, some getting quite dusty — MD4 is still in there, for example. It's hard to tell who wrote any given implementation, or whether it has been formally verified or how widely used it is, which makes it hard to know what's reliable. He has a strong preference for formally verified code. Failing that, he prefers code that is in widespread use and has received a lot of scrutiny, noting that these are often the fastest implementations as well. And failing that, he prefers code based on the reference implementations.
So Zinc's approach, and this is where feathers started to get ruffled, is not to be an API at all; it's just functions. Donenfeld argues that functions are well-understood in C, and people know about and are comfortable with them. Zinc is aiming for high-speed and high-assurance; these are easier goals to achieve without a big API, as is formal verification. Moreover, "tons of code" has already leaked out of the kernel and into lib/; it's clear that programmers want functions and Zinc is prepared to provide them in a non-haphazard way.
As for formal verification, there are apparently several teams working on that for crypto code, including MIT's fiat-crypto project and INRIA's HACL*. The latter project takes the approach of modeling the algorithm in F* and proving the model correct, which F* is designed to optimize. Then — in a term of art which never fails to make me think of Arnold Schwarzenegger's Terminator descending into a bath of molten metal — the model is "lowered into" C (or in some cases, all the way into assembly language). According to Donenfeld, this produces C which, though slightly non-idiomatic, is surprisingly readable, and much more likely to be bug-free than human-written code. It also produces some of the fastest C implementations that exist, which he suspects is because the formal verification process removes certain things that are not obviously removable when you're working the mathematics out by hand. In addition to using formal verification, all Zinc code has been, and will continue to be, heavily fuzzed.
Donenfeld has been working with the INRIA team to get as much as possible of their work into Zinc, and is trying generally to improve relations between the kernel community and academic cryptographers. He feels that the people who design cryptographic primitives, and their hordes of capable graduate students, generally don't come anywhere near kernel development, and that it's our loss.
Cryptographic primitives in Zinc are organized differently than the current API. Code is organized by the name of the cipher; for example, the ChaCha20 cipher lives under lib/zinc/chacha20/, where you can find the generic C implementation chacha20.c as well as architecture-specific assembly versions including chacha20-arm.S and chacha20-x86_64.S. Donenfeld feels this invites contribution in an approachable and manageable way. It also allows architecture implementation selection not via function pointers but by compiler inlining, "which makes things super fast" — and the absence of function pointers means no retpoline-induced slowdowns.
Zinc currently implements the ChaCha20 stream cipher, the Poly1305 message authentication code (MAC), the Blake 2s hash function, and Curve25519 for elliptic-curve cryptography. This is a long way from a complete replacement of the current API; it's essentially just what WireGuard uses. But Donenfeld and Samuel Neves have started the work of refactoring the existing crypto API to use Zinc functions under the hood. He also tried re-implementing big_key.c in Zinc, which ended up removing over 200 lines of a 500-line file and replacing them with 28 lines of new code.
Some of the current API will not be so easily refactored. One question from the floor asked about handling asynchronous callback for hardware crypto accelerators, which are important in lower-power CPU environments such as some ARM chips. Donenfeld's response was that he didn't like the idea of "polluting" the Zinc API by evolving it to handle asynchronous callback. He said that it would be better to build a layer on top of Zinc that either invoked Zinc for in-CPU crypto or handed the request out to the external hardware, which essentially dodges the question. It does appear that he has some clear views about how people should use crypto; uses that don't fit into those views aren't really his top priority.
Two easier questions related to naming and to learning opportunities. As to the former, Zinc apparently now stands for "Zinc Is Nice Cryptography"; the suggestion it might stand for "Zinc Is Not a CryptoAPI" elicited some laughter. The name also fits with the elemental naming scheme used in crypto projects like Sodium and libchloride. The other question was motivated by the questioner finding it increasingly difficult to know how to respond to requests for a good place for beginners to get started with kernel work. Donenfeld felt that Zinc may not be a great place to start contributing, but that the nature of the code and the tasks it performs — it's pure C, it takes in data, modifies it, and writes it out — makes it a great place to start reading and understanding.
Although his frustration with Zinc's reception by the kernel community occasionally leaked through, it's clear that Donenfeld has learned from the response to his initial 24,000-line patch. Talks like this are part of the outreach. But the process is made easier by the legitimate criticisms he makes of the current API and that, even though Zinc doesn't do everything the crypto API currently does, what it does do it does really well. I suspect that, in some form not yet determined, we will all benefit from this work for some time to come.
[We would like to thank LWN's travel sponsor, The Linux Foundation, for assistance with travel funding for Kernel Recipes.]
| Index entries for this article | |
|---|---|
| Kernel | Cryptography |
| Security | Cryptography |
| Security | Linux kernel/Virtual private network (VPN) |
| GuestArticles | Yates, Tom |
| Conference | Kernel Recipes/2018 |