Rustls: new, modern TLS library written in Rust
github.com
github.com
According to https://doc.rust-lang.org/book/ffi.html it is possible to make callbacks from C code to Rust functions. This way other languages could take advantage of Rust's safe libraries.
In that regard Rust has reduced both 1) and 3) by only exposing dangerous features in unsafe {}, and greatly improved 2) since the compiler itself is checking those errors.
First of all, it's the opposite of Post-Modern, to the perl style do it all in myriads of ways, everything is allowed, OpenSSL style. With Modern, only the best API and implementation is allowed. API's are Stanford style well-planned ahead of time, and not adhoc added New-Jersey style. Changes to the API need a rename, not just a major version bump. Hand-waving simplified development models are okay for a post-modern everything is allowed world, whilst modernism aims for long-term goals, making it easier for the user, not the developer.
Modernism is based on "Form Follows Function", and not the other way round. Reduce it, abstract it. Functionalism is everything, marketing is less important.
And to avoid popular misconceptions, in our current era "Modern" doesn't mean "New" at all. Modern is more old Stanford-style development. Check the Unix Haters handbook e.g. Rust is new, but this choice alone doesn't allow the use of Modern. Rust is better, because of its superior semantics and guarantees, whilst still adhering to the C ABI. It's a library for everybody afterall, and not comparable to the latest ocaml, haskell or lisp TLS library, which only adheres to their ABI and needs wrappers to be useful for projects in other languages.
So while I appreciate your attempt to redefine "modern" with all the above I'd rather people just used the more verbose explanation instead of using the meaningless term.
Grandparent referred to the meaning of the jargon term in architecture. It's not his personal redefinition; it's an interpretation of a widespread understanding of the word mapped analogously to software.
Jargon is important. We'd have a hard time communicating if we could never create new words or imbue existing ones with new meanings by analogy. The more verbose explanation is mostly useful for laymen and beginners.
The word has spread in popular vernacular to mean 'the present' or 'contemporary' but for historians, philosophers, architects, art critics, critical theorists, etc. "modern" refers to a bunch of things, and the "modern era" is considered by many of them to be either over, extended, or exaggerated.
2. New and common; trite; commonplace. [Obs.]
[1913 Webster]
We have our philosophical persons, to make modern
and familiar, things supernatural and causeless.
--Shak.
Isn't that what we want from "modern" software? To "make modern and familiar" "things supernatural and causeless"? :-)A modern SW project consists typically of 1-2, max. 3 devs, a typical post-modern project of 20-200. With such member sizes consensus is rarely practical, political correctness and CoC discussions are taking over development, ABI and API's are driven by design and not by functionality and longevity.
I.e. modern development is small, functional and not designed by committee.
Is this something you feel to be true, or do you have any evidence to back up that claim?
New Jersey style? The PERL style? I've never heard of "New Jersey style." Are you joking? I'm almost positive that Perl didn't influence openssl development at all.
"Modern is more old Stanford-style development." I think you've read way to much into this. And sorry citing the "The UNIX Hater's Handbook" doesn't do much for the credibility of your odd argument.
So, who won? Nobody. Both had success. Most successful, though, was combining a little bit of MIT method, cathedral, and ship fast from "worse is better." That's IBM and Microsoft's approach. A hybrid worthy of another Gabriel essay.
http://windowsitpro.com/windows-client/windows-nt-and-vms-re...
Bill robbing Apple of GUI is well-known. Less known is he stole a more robust and secure architecture from OpenVMS that was most rock-solid of OS's. They managed to show what would happen if OpenVMS had no QA process during development. Yet, recent efforts have gotten Windows Server reliable enough that the connection is more believable. :)
- C++: uses C++11 features, avoids non-RAII resources, prefers standard libraries over older, platform-specific libraries, not written as "C with classes."
- C: doesn't do weird stuff like bypassing malloc(), avoids undefined behavior.
- in general: has a test suite, probably uses continuous testing like Travis, uses modern language features to achieve cleaner & more robust code.
Can you elaborate?
> Speed is important for a malloc implementation because if malloc is not fast enough, application writers are inclined to write their own custom free lists on top of malloc. This can lead to extra complexity, and more memory usage unless the application writer is very careful to appropriately size the free lists and scavenge idle objects out of the free list.
An example of such an optimization is the arena allocator[1][2] employed by protobuf. Custom memory management schemes are not uncommon in performance critical code.
0: http://goog-perftools.sourceforge.net/doc/tcmalloc.html
1: https://en.m.wikipedia.org/wiki/Region-based_memory_manageme...
2: https://developers.google.com/protocol-buffers/docs/referenc...
I work on the protobuf team at Google, so I'm aware of this.
Two things about that:
1. The underlying blocks for the arena allocator still come from the system allocator.
2. Because the arena allocator inhibits the capabilities of standard malloc-debugging tools like ASAN and Valgrind, the protobuf arena allocator includes special ASAN-aware code to mitigate this:
https://github.com/google/protobuf/blob/d64a2d9941c36a7bc2a7...
However, that code is ASAN-specific. It won't help other tools like Valgrind. So yes, different allocators are sometimes warranted for specific patterns like arenas. But if all you want is plain malloc()/free(), you should call malloc()/free().
If you're writing a library, letting the user specify their own allocation callback is also great, since it lets the user do whatever custom bookkeeping/pooling/etc. they want to do. But by default just call malloc()/free() (IMHO).
Use any of the debugging libraries that shim in their own alloc routines, you bypass malloc (or at least do a bunch of things that impact what gets malloc'd).
- in reference to Mozilla's "Modern TLS" profile: https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_com...
- as an antonym for the unlimited backwards-compatibility, kitchen-sink-comprehensive approach of (say) OpenSSL or NSS.
1. client auth in TLS1.2 and earlier is done at the wrong time in the handshake. As a result the client's identity (which unlike the server identity usually identifies a user; see sibling comment which confirms this) is sent in the clear. That's a big privacy failure.
2. to work around (1), some implementations do an initial server-auth handshake, then immediately renegotiate up to mutual-auth (renegotiations are encrypted). Renegotiation has quite a dismal history, and I definitely want to avoid it.
3. as a follow on from (2), the standard never described what implementations are expected to do if client/server identities change during renegotiation. This (partially) resulted in https://mitls.org/pages/attacks/3SHAKE
All of these are fixed in TLS1.3: client identities are encrypted and renegotiation is dropped.
At least in the environments I use TLS, which is interservice datacenter communications, there are no privacy issues (especially since you can just look at what container a connection comes from for the same amount of identity leaking).
But I do need mutual authentication, and want to avoid rolling my own crypto for it--so does Rustls expect to support client auth in TLS 1.3 when that spec is finalized and implemented?
My guess? They don't use it in their applications, so they don't think anyone else uses it, either.
TLS client authentication is widely used in 802.1X (WiFi and wired) authentication. I've seen it used in a lot of other situations, e.g. web client access (client cert + user password), LDAP client access, etc.
Maybe there are security issues in client authentication which they're aware of. If so, they should share them. But simply labeling client authentication as "obsolete" shows a close-minded attitude.
Client auth is simple to use - our internal services are given the username from the CN, which they use to perform authorization checks. For a lot of simple internal services that don't require two-factor auth it works great.
Am I missing something better?
(We already have the infrastructure in place to deal with client keys)
It's a very handy tool when you have disparate networks.
> Client authentication.
> Kerberos.
From a complexity perspective I understand why these things would not be the first choice to implement in a new library. What I don't understand is why they're on the "will never implement" list. Public-key client authentication is to this day one of the strongest methods of authentication that can be used, and not having that available in a library greatly limits its usages in high-security applications, exactly the places where someone might want to stop using OpenSSL and its broken peers.
Kerberos... well... Kerberos is a cluster-fuck. I think everyone knows that. But there are specific applications where Kerberos (or something similar) is exceptionally useful and maybe even necessary. I will acknowledge though that if Kerberos is missing its not a world-ender, because you can implement your own token-based authentication on top of normal TLS without using Kerberos, which in some cases might even be easier because Kerberos is a cluster-fuck. Despite this, though, if configured properly Kerberos is still one of the best methods for doing secure authentication between multiple servers/services via a central authentication mechanism.
If the justification here is simply implementation complexity that might cloud the codebase or otherwise make it harder to audit for security, I do understand that reasoning. I'm just curious specifically in these two cases because they stood out from the list as things that I don't consider "insane", unlike most of the rest of the list.
As an aside, thanks for not implementing RC4. RC4 needs to die die die die. I don't know why anyone is still using it, but nonetheless I see it in the wild still sometimes :(
Other than that, looks like a nice and sane subset they've picked.
Real shame if they end up avoiding cert-based authentication, though. All other options for authentication are strictly worse, from a security perspective - and leaves more room for implementors to shoot themselves in the foot. For passwords that are intended for human users, for example, you really need some form of rate-limiting. Not to mention the problem of setting up a session first, and then later binding to a user (if authentication succeeds) rather than the simpler "only valid client can connect".
How much does formal verification matter/increase confidence?
Is the rust version easier to integrate with other stacks?
By moving the protocol logic to Rust, the amount of code that needs to be reviewed for memory safety in a TLS library is drastically reduced.
edit: thanks for the explanations!
Also, one of my goals with the ring project is to identify exactly what constant-time utilities are needed for a crypto library, so that I can draft a proposal for improving the Rust language and libraries to provide such features.
Does even C provide such guarantees? Isn't the compiler free to rewrite the code it's compiling in whatever way it wishes as long as the output is the same?
The assembly language code we inherited from BoringSSL (and OpenSSL) is really important for performance and for safety from side-channel attacks, like timing attacks. I believe that rewriting most of the assembly language code in Rust would be a net loss for security. I have very long-term ideas for how to avoid needing so much hand-coded assembly, but we have higher-priority things to do now. And, the assembly language code is really good. Really.
The C code is increasingly getting replaced (not rewritten or just transliterated) with Rust code, wherever it makes sense to do so. You can see some of the planned work of this type at https://github.com/briansmith/ring/labels/oxidation. To see the past work, review the commit log of ring.
However, we've done tons of work to make the C code safer too. For example, I've written dozens of patches to eliminate cases of undefined behavior and other unsafe coding patterns in the C code. Many of these changes have been integrated into BoringSSL.
Also, we've greatly reduced the usage of the heap. Already, you can use most of ring's functionality without a heap. Importantly, this means that we have solid evidence that, for almost every ring feature, there is zero chance of use-after-frees, double-frees, or memory leaks. It also means that the memory usage is very predictable, which makes it easier to use in constrained environments.
In addition, I've tried to design the ring API very carefully to limit the potential for things built on top of it to misuse the crypto. For example, the API enforces--statically, at compile time--that an ECDHE key can be used only once. Similarly, it enforces--statically, at compile time--that an AES/ChaCha20 encryption key is never used for decryption, and vice versa. Similarly, it ensures that encryption is always properly authenticated--there's no way to accidentally do "MAC before encrypt" and similar things. We even make sure that you don't use less-safe AES-GCM nonces that aren't exactly 96 bits.
Finally, anything that uses ring get all the advantages that come with Rust automatically, such as Rust's use-after-free protection and data race protection. (ring replaced all the C threading/synchronization stuff using the safer Rust constructs already.)
So, even though there is some C code, and even though there's a lot of assembly language code, things that use ring are still getting lots of Rust's advantages.
There are other alternatives that are "pure" or close to "pure" Rust, such as rust-crypto. But, those libraries are missing important things like RSA and ECDH and ECDSA over the NIST P-256 and P-384 curves. That's all needed for a practical TLS implementation.
① Everything you Never Wanted to Know about PKI but were Forced to Find Out (https://www.cs.auckland.ac.nz/~pgut001/pubs/pkitutorial.pdf)
I'm not aware of any serious use of RFC 6091. GnuTLS supports it, but I don't think any other implementations do.
Was it really your intention to dismiss my application¹ as not serious?
It seems your project could use x509 client certs as well as gpg ones if you wanted to use a different TLS library that didn't support GPG.
We use RFC 6091 because it’s the best fit for our problem, and allows us to avoid the needless complexity of X.509 certificates, which has caused many bugs in the past (in essentially all TLS libraries).
If compatibility doesn't matter, I'd probably use something less crufty, like http://noiseprotocol.org/
Wow, the future is here!
Isn't TLS 1.3 currently still a draft? [1]
[1]: https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_1...
I think BoringSSL is also[3] working on their implementation and NSS (Firefox's SSL/TLS library) implemented[4] draft 11 in v3.23, but OpenSSL doesn't have plans to until after 1.1 ships[5]; I've heard the former is expected to land about 6 months prior to the latter as OpenSSL isn't starting until the RFC is finalized.
1 - https://tools.ietf.org/html/draft-ietf-tls-tls13-11 (draft 13) 2 - https://nightly.mozilla.org/ 3 - https://www.imperialviolet.org/2015/10/17/boringssl.html 4 - https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NS... 5 - https://www.openssl.org/policies/roadmap.html
Writing a cryptography library from scratch because the old one has too many security holes? Your implementation is likely to have even more.
We should really concentrate on making one implementation secure, instead of creating more libraries.
Making things even worse is that you can't really write a crypto library without using C/assembly. So why write a new crypto library?
If that were true, then it would be a good counterpoint worthy of long thought. The reality of crypto libraries is that popular ones often had preventable errors due to bad coding and/or unsafe language that also took entirely too long to notice.
"you can't really write a crypto library without using C/assembly"
I'd argue you can't write a crypto library using C by default unless you're a really good coder. C is just an accident of two team's bad hardware:
Languages like Modula-3, Free Pascal, and even Fortran were easier to analyze to ascertain the program's properties. It's why Modula-3 had first, standard library verified free of specific types of errors. Also took years to make a certified compiler for C which had been done in other, simpler languages. Finally, the top performer in secure, systems code is SPARK as illustrated when re-implementing C crypto in it caught a problem.
http://www.adacore.com/press/spark-skein/
Note: The portability result was also impressive given it's one of C's strengths and difficulties.
That's not a reason to never write another crypto library ever. It's a reason to get started on a new one as soon as possible so it can start the process of being vetted.
Attacks like downgrade attacks, for example, are not memory unsafety issues.
Servo would need a library that is battle-tested and has a vulnerability policy. The Rust libraries could be in this category in the future, but not right now. Using something like nss or boringssl, like other browsers, would be our best bet.
First, this library only implements TLS 1.2 with AEAD cipher suites (AES-128-GCM, AES-256-GCM, and ChaCha20-Poly1305) and perfect forward secrecy (using ECDHE with the X25519, P-256, and P-384 curves, and RSA and ECDSA signatures). Thus, downgrade to something worse than what NSS or OpenSSL or BoringSSL consider to be the very best crypto is avoided at a very fundamental level.
Second, I and other people have been advocating strongly against design decisions that force implementations to add downgrade vectors. In particular, there is a version number field in the TLS ClientHello that we know causes problems. People have proposed solutions to avoid this version number causing compatibility problems for TLS 1.3, but Mozilla's TLS people have not supported making this improvement. Thus, we're likely going to have downgrade issues by design in TLS 1.3 that would be completely avoidable, due in large part to the people making NSS and other C crypto libraries.
1. Abstraction gap issues where security analysis of algorithm/protocol didn't reflect a realistic implementation. Padding errors were an old example of that.
2. Issues with each individual component where it might have not been instantiated or removed correctly.
3. Interface errors where things were connected in a dangerous way. Examples include wrong ordering or meet-in-middle.
4. Parsing errors.
5. Memory safety issues.
6. Compiler optimizations introducing security issues or removing security checks.
7. Covert storage or timing channels. Mainstream INFOSEC have re-discovered them as "side channels." I'm not sure you can even do covert, channel analysis in Rust yet as it requires a clear mapping from language to assembler. Most projects just ignore this requirement although BoringSSL addresses it with hand-coded assembly as you pointed out.
So, there's a lot of ground to cover implementing even a straight-forward, crypto protocol to ensure it's secure. Most of that has nothing to do with the protections Rust offers. Matter of fact, it might be a step back in many ways due to immature (or non-existent) tooling for specific requirements on the list. Side-by-side SPARK and MISRA C implementations with design-by-contract and some assembly are the strongest here for now.
Keep at your project, though. Your longer comment detailing the work was quite impressive. Great work. Even better that you're gradually tweaking one that already works in real-world deployments. Will avoid many issues that way.
It's a tradeoff.