At RubyConf in Denver, I gave a talk presenting krypt. This here shall be the first in a series of posts where I describe in more detail what krypt is all about, what it does and finally, why I believe it to be a good idea.
I’d like to start this series with the obligatory introduction, describing what motivated the project, some of the fundamental design principles, and exactly why and how it deviates from other cryptography projects. I will also try to explain where krypt may be headed in the future and what you can finally expect from it, should you want to use it.
How it all started
Roughly two years ago, I had just finished a project at work where we tested the interoperability of our implementation of some digital signature standard with the implementations written by others. It was a really exciting project, because it meant interacting with people from all around the globe, and the cool thing was there was a web chat implemented where you could exchange ideas or quick questions. The question came up about what language and libraries we had used for our products. I was hoping for a huge variety of answers, but sadly, the answer was exclusively Java or C# in combination with Bouncy Castle. I mean on one hand I had expected this, but on the other I would have really enjoyed to hear a few other names as well.
I started investigating what Python and Ruby offered in that area, since I
had fooled around with them for some time and started to really like them.
It was then when I found out about Ruby OpenSSL and its built-in support
for ASN.1 with OpenSSL::ASN1.
I immediately fell in love with OpenSSL::ASN1
. Sure, there was no documentation at all at the
time, but that was just the more reason to dive into the source code. Bouncy
Castle had just switched to serializing CMS signatures
using “indefinite-length” BER
encodings, and unfortunately, OpenSSL::ASN1
could only handle proper
DER encodings.
I found the reason for this and decided to write a patch for it. Hiroshi
Nakamura (@nahi) and Aaron Patterson(@tenderlove)
from ruby-core reviewed the patch and seemed to like it. A couple of weeks and patches
later they asked me if I didn’t want to help maintaining the OpenSSL
extension and they were really encouraging and very nice. I knew immediately
that I could learn a lot from such great people and it was not really a
hard decision. If it weren’t for these two guys, I probably wouldn’t be
writing this now and krypt would have never existed.
Lessons learned from OpenSSL: Cool, but…
Over the course of working on Ruby OpenSSL, and from past experiences with OpenSSL itself, Java crypto libraries and the software that I had written myself, I had collected a mental list of things that I would really like to see different in a crypto library. Not that I didn’t like Ruby OpenSSL, to the contrary - I loved how simple some things would become when leveraging the power of Ruby - but there were certain aspects about OpenSSL that I disliked. Two things that had always bothered me since I had to deal a lot with them professionally were certificate validation and the way how ASN.1 (or more exactly, DER/BER) parsing was handled.
I had some ideas and I started discussing them with Hiroshi. He was very patient and kind considering my often very lengthy mails. He agreed with some of my points, but he did not think we should put those ideas into Ruby OpenSSL itself.
He believed and believes (and rightly so!) that we should keep Ruby OpenSSL as what it was always supposed to be: a thin wrapper around OpenSSL that allows us convenient usage of common OpenSSL features in Ruby. But he also encouraged me: if I really wanted to change things, I should better think about writing something new, something that fully embraces Ruby. I got excited about the idea, and I started making lists of all the things such a library should contain and we discussed many ideas. “Project crypto”, as we called it back then, was born.
krypt
I probably start boring you with my anecdotes, so let’s end this historical tour now. After this much sentimentality, let’s get back to the sober facts. krypt’s goal summarized in one sentence is to provide
platform- and library-independent cryptography for Ruby
But what does that mean?
(Ruby) OpenSSL problems
Well, let’s look at some of the problems that we currently face with Ruby OpenSSL and after that at how krypt tries to solve them.
First of all, there’s the fact that right now, if you want to do serious cryptography code in Ruby, you’re pretty much stuck with Ruby OpenSSL and therefore OpenSSL itself. There is no real alternative, if for whatever reason you would like to use a different library, a specific algorithm not supported by OpenSSL or a smart card or HSM (hardware security module) using e.g. a PKCS#11 interface, then the sad answer is: “Please use a different language”.
Probably one of the most annoying problems with Ruby’s OpenSSL extension right now is that there are subtle issues depending on where you run it. OpenSSL (the native library) itself is a very opinionated library and you will quickly notice if you’re running it on Windows. Certain things like for example accessing the default set of trusted system certificates just work differently on Windows than they would do on a Unix-like platform. There have been patches to mitigate the situation, but unfortunately to no avail. OpenSSL likes to keep their dependencies as minimal as possible, and that’s why dealing with exceptional cases in Windows is out of the picture.
That philosophy causes trouble in other areas as well. Proper RFC 5280 certificate validation (“PKIX validation”) requires the ability to download intermediate certificates, CRLs and OCSP messages online, sometimes even from LDAP repositories. However, proper processing of HTTP is complicated, but it would require including a dependency. Since this is not possible in OpenSSL, but also since OCSP is unthinkable without HTTP, we end up with a, pardon my French, half-assed HTTP implementation for OCSP and with more than quirky callbacks that in theory allow us to plug into the validation process in order to provide the necessary resources ourselves. Describing the work needed to get this done right as “complicated” is a joke. The callback design is far from optimal, instead of giving you per-certificate callbacks that are being called for each certificate in a validation path successively, all you get is one global callback, you have to keep track of state yourself.
This is why any project using OpenSSL for certificate validation ends up writing their own code to do so, and not surprisingly this is why most projects doing so fail to do it correctly. There is a recent, very insightful paper describing the dilemma in more detail. I would really like to see at least per-certificate callbacks among other things, much like Java does with its PKIXCertPathChecker, but unfortunately the API seems to stay the way it is…
Refusing to rely on a proper third-party HTTP implementation turns out to have far-reaching consequences: we either end up with ad-hoc extensions on top of OpenSSL that are often not optimal, or we use certificate validation without online revocation checking, as happens by default in Ruby. But completely foregoing online revocation checking comes at a price. We simply won’t notice if a certificate ever gets revoked during its lifetime - incidents such as the Comodo or DigiNotar cases would have simply gone by unnoticed.
The JRuby team faces another challenge with regard to OpenSSL: it is very hard to emulate it using other crypto libraries, in their case using Java libraries. Since OpenSSL is quite opinionated, JRuby devs are having a hard time providing the exact same functionality that Ruby OpenSSL gives us in C-based Rubies. This problem is not to be underestimated. After all, HTTPS connections are backed by the OpenSSL extension(think gem downloads!), and so the problems need to be sorted out somehow.
Last but not least, we experience yet another problem when running Ruby OpenSSL on Rubinius: although both CRuby and Rubinius run the same C code that makes up the OpenSSL extension, performance varies quite noticeably between both Rubies, and since JRuby uses completely different code, the situation is similar there.
The design philosophy of krypt
Over the months, I have collected those problems, written them down and formed and reshaped a picture of what I believed the perfect Ruby cryptography library should look like and what goals it should accomplish. As the shapes got clearer and clearer, a couple of goals emerged, and with them, certain design philosophies that would be necessary to accomplish these goals. Let me present them, along with a few explanatory words.
Diversity
Matz in his keynote talk at RubyConf mentioned that “diversity is the basis for innovation”. On top of that, I also believe that diversity gives us the ability to choose, to choose the right tool for a job. That’s why I believe Ruby cryptography should not just be restricted to using OpenSSL. We should have the choice to use a different library such as NSS or NaCl if we want to. We should be able to use the platform default library such as CAPI or CNG on Windows and it should be possible to somehow access their extended functionality. In the case of Common Crypto on OS X, it should be easy to replace using OpenSSL with it - Apple deprecated OpenSSL on OS X and is replacing it with Common Crypto. In the not-too-distant future, OpenSSL will no longer be available by default in OS X. The same is to be expected in Fedora. Windows never had it in the first place. It should be possible for us to use cryptography in Ruby without having to install external dependencies if there is a perfectly well-suited library available as the platform default.
krypt aims to give you diversity. It does so using a “Provider” principle - there is no one particular library backing all of the functionality. Instead, many different providers may be registered (also at runtime) to replace the defaults either entirely or just partially. It is also possible to extend core functionality with custom providers that add specific functionality if needed.
Those familiar with the Java Security API will notice the similarity. I’m not making a big secret out of this, I admit that this is clearly my role model for krypt. I worked a lot with that API in the past and it never disappointed me, I truly believe it’s one of the finer examples of design in the Java standard library.
A “Provider” exposes a set of services - core cryptography functionality such as message digests (hashes), ciphers or signatures. Multiple providers can be registered in parallel, if two providers offer the same services, the conflict is handled in LIFO fashion. Common provider services should follow a common API, but in theory it is entirely possible to register completely arbitrary services with a provider.
Provider itself is an API written in Ruby, but it will often require to fall back to native code in order to implement one. While it is possible to write a provider completely in Ruby, for example implementing a provider that integrates OpenSSL requires writing glue code that exposes the native functionality in the Ruby world. The same problem occurs with JRuby and native Java libraries. As this will require pretty much exactly the same boilerplate code for every “native” provider, krypt already provides this glue code for you. In the end, if you want to add another provider implementation, all you need to do is to either implement a C interface defined in the “krypt-provider.h” header file or the “KryptProvider” interface in Java - krypt takes care of the glue code for you.
Run on all Rubies, equally well
krypt was developed to run without restrictions on CRuby, JRuby and Rubinius right from the start. It shouldn’t be a big problem running it on any other C(++)- or Java-based Ruby as well. Because each implementation, although with native parts being written in different languages, follows the same overall design, there are no more outliers when it comes to performance. An extensive test suite with CI on Travis ensures that there are no more surprises once you run your code on a different Ruby. All of the core features are available anywhere, gone are the times when specific features would not be available on your particular platform.
Security by default
Let’s face it, most developers dealing with cryptography in their code are not very happy about it. Most of the time, they are not really familiar with the subject in depth, but circumstances mandate that certain features need to be implemented nonetheless. With tight schedules and immense pressure on their backs, these developers clearly have better things to do than sorting out the corner cases of whatever cryptographic primitive they plan to apply.
All any of us really care about is that what we end up writing is secure.
We don’t care about whether it’s SHA-256 or SHA-512, RSA or ECC, AES-128 or AES-256 that we’re using. Let alone padding schemes, number of iterations, key sizes, we don’t care! We hate that shit! We simply don’t want to be bothered with it. All we need is security and as few options as possible so that we don’t shoot ourselves while fiddling with parameters.
Why on earth must all crypto APIs be so goddamn complicated? Well, there are attempts like keyczar that try to make developer’s lives easier. But the problem with such a library? Most of the time when dealing with cryptographic primitives and protocols, we are far from being in the position to dictate the entire process. No, what happens much too often unfortunately is that the requirement is to integrate software A with software B’s century-old “security interface”. It’s not secure, you might reply after having studied the details of B’s interface. “Who gives a fuck? Just do it, we’re on a schedule here!” is often the answer. I know it’s completely wrong, and I am a perfectionist and optimist as much as the next developer out there, but we all can’t deny that it has happened - and it sure will continue to. Tell those poor bastards to use keyczar. It simply won’t support any of the ancient algorithms and broken padding schemes! I could put myself in a position and claim that I don’t want to support such behavior - but then I would deny reality. I am an optimist, I am not an idiot. I want to give the poor bastards facing legacy monoliths a chance to get their job done, despite my contempt for the decision made by those in charge forcing them to do so.
That’s why I believe focusing just on one aspect, either full flexibility or full security by minimizing parameters, will always leave someone behind. Consequently, I really believe in offering both. krypt will attempt to give you both. A simple-to-use, minimal-option API for those lucky enough to be in a position where they are in full control of the process and simply care about security, but also an “advanced” API that gives you full flexibility by exposing each and every parameter that affects the outcome of a cryptographic primitive. If you know what you are doing or if you are one of the unlucky ones mentioned above, why should you be restricted in any way? We are all grown-ups and old enough to decide our course of action. Not surprisingly, one of my favorite Ruby mantras.
Fix existing problems
So certificate validation in OpenSSL is far from optimal. Here’s where coming from a clean slate can make a difference. krypt does not need to obey any legacies and it certainly won’t need to keep dependencies low. There’s the full Ruby standard library that can be used for implementing it! Since we don’t need to be idealistic about this, nothing holds us back from using HTTP, LDAP and friends in order to implement the PKIX algorithm in its full glory. While attempting to give you a bullet-proof default implementation, krypt will also give you full control to customize the process in the form of callbacks and customization parameters. Again, the philosophy of having a secure default but not leaving those in need of flexibility behind.
Another issue that has been bothering me for a long time now is that while the crypto community uniformly agrees that encryption as being used today is mostly broken, it is unbelievably hard to actually use Authenticated Encryption (AE) with today’s libraries. It is only now that we see adoption in popular libraries. Which is really surprising, because AE has not been only invented yesterday. In fact, almost ten years ago, in Secure Programming Cookbook for C and C++, the authors already recommended to use AE over any other encryption mode. It is one thing using an insecure primitive, but it is just plain evil if you know you should use something else but are forced to use the insecure one because the alternative is simply not available yet. krypt will attempt to provide AE out of the box and it will also be its default mode of encryption.
krypt also attempts to mitigate the situation for Ruby users that operate on platforms where OpenSSL is not the platform default security library. Both the C and the Java version of krypt will come with a default provider for the cryptographic primitives. While in the beginning this will still be OpenSSL for the C part, it is very likely that krypt will ship with a provider that integrates the platform default cryptography library in the future. This means that for example Ruby users on Windows would no longer be second class citizens when it comes to cryptography - instead of living through the hell of installing OpenSSL on their systems just to be rewarded by missing features, krypt would ship with a default provider that leverages CAPI or CNG by default. The same plan exists for other platforms as well, e.g. using CommonCrypto by default on OS X, NSS on Fedora etc.
Testing and Documentation
Testing should be a number one priority for any software project, agreed. But even more so for a security-related project. And yet, testing is something where traditional cryptography libraries come off as a little meek. In their defense, C code is not particularly easy to test. But this is where Ruby usually shines, right?
Apart from the usual suspects such as an extensive RSpec test suite, using official test vectors for algorithms where available and CI runs on Travis I looked into other ways in order to make krypt one of the best-tested cryptography libraries out there. That’s why we measure code coverage not only for Ruby code, but also for C and Java code. That’s why we use Valgrind for finding memory issues in the C part. Currently, I’m also implementing specs using FFI that test the internally used C APIs - way more comfortable than testing the C code directly.
But I still felt like something was missing. I recalled that a large part of the vulnerabilities being filed against popular libraries had been discovered using some form of automated tool fuzzing selected parts of the library. Instead of waiting for someone to report a vulnerability one day, I sought for ways to make random testing/fuzzing an integral part of krypt’s test suite. FuzzBert was born. It allows for quickly setting up a random test suite in a DSL that should be familiar to RSpec users. It also features a tiny templating language that comes in handy when dealing with string-based data. FuzzBert has been released as a gem and can be used right away. You may want to try it out on your apps/libraries, it doesn’t even have to be Ruby that you’re testing - examples and details may be found in the github repository. Why wait for the bad guys to find those errors if we can find them right away using the exact same tools?
Documentation. Those who live in glass houses… I’ll admit, Ruby OpenSSL still has some undocumented corners, but the situation has greatly improved over the years.
It always makes me sad that after more than a decade, the OpenSSL project itself still has many black holes in its documentation. I’m not talking about the “app” documentation, that one is just fine, but about the C API documentation. Nobody likes to write documentation, I know, most can hardly bear it even if being paid good money for it. But we need to bite the bullet, because documentation is vital to a security project! I believe that documentation of a cryptography library should take on educational responsibility. I mean how can we assume our users to know about every fallacy, about every implication of every parameter if we lose not a single word describing the feature? Reusing a key with Stream Ciphers is dangerous? Then tell people! I know, in an ideal world, every developer would pick up a couple of books about cryptography, study source code in detail if available and publish two or three relevant research papers before ever daring to write any code using cryptography. Not gonna happen. And a quite dubious world view if we really believed that. Sometimes other people just aren’t lazy or dumb, it should be considered that they might have different priorities or fields of expertise. In the end, all anybody cares about is the code being secure, remember?
There is already quite a bit of documentation available in krypt, and the goal is to achieve 100% documentation. Not the “Dude, we promised docs, so start CTRL+C/CTRL+V-ing faster, would you?” 100%, but actual meaningful and helpful documentation. Including example usage and explanation of where to be cautious and why. I know, that’s quite some task, but isn’t that what we always hope for when looking up a particular method in a crypto library, only to be disappointed yet another time?
Use Ruby wherever possible
I won’t lie to you, originally the main reason for choosing Ruby for krypt was that I am a huge fan of Ruby. But the longer I work on krypt the more I find evidence that this was the perfect choice. Not necessarily because it is Ruby, but certainly because it is a higher-level language than the traditional crypto languages C or C++. But why would I want to break with the long tradition of integrating battle-tested C libraries into a higher-level language when that is what basically any other C-based programming language does? Why reinvent the wheel? When discussing low-level vs. high-level language for security/cryptography purposes the argument of the low-levelionados is usually this: We need to be in control of everything. We need to be able to wipe memory for sensitive data, we need to be able to squeeze every bit of performance out of that code. Dynamic typing? Bitch please, did anyone ever tell you that being naive is not always cute?
I respectfully dissent. Not necessarily with the type argument. I do believe that in an ideal world my language should be typed, but it also shouldn’t get in my way with its typing mechanism. It just sucks being ripped out of your train of thought because you must figure out whether A should be covariant or contravariant in some context. Until I can get that, I refuse to discuss the issue further. Wherever this discussion pops up, it is usually followed by a strong odor of bikeshedding. But I actually disagree with the first argument. Let me explain a bit why.
The “we need to be in control” argument always reminds me of this classic. Sure, I agree that we should be able to wipe memory of security-critical data right after use. But instead of rejecting let’s say Python or Ruby just because right now they can’t, I say let’s add this feature to Python and Ruby! I firmly believe in the idea that cryptography- or security-related code should be written in a high-level language. As high as it gets. Just think of the myriads of things a developer needs to keep an eye on when writing every single line of C code. Did I allocate the memory? Was it allocated on the stack or on the heap? Did I free it yet? Should I allocate and free it within the boundaries of the function or will it be allocated and freed by the caller? There’s many, many more things. Getting just a single one wrong usually introduces a security vulnerability immediately. Now cryptography in and by itself is hard enough as it is, without these additional complexities. We recall that really secure code should also pay attention to leakage-resilient cryptography, i.e. at all times should our code not be vulnerable to any side-channel attacks. If you tell me that all of this can be done right in C or C++ the first time it is being written, then you’re just a plain liar. Our brains just aren’t wired to deal with this kind of complexity. And you want to add memory management and different levels of pointer indirection to the mix? That’s just desperately begging for trouble. No one can write such code without introducing errors at some point. Not even the punch card fraction in the last row - and no, your programs didn’t run correctly every time the first time you ran them. If your C code doesn’t segfault the first time you run it, then chances are that there is something really wrong with your code. That’s just the way it is.
And yet, we continue to write crypto code in the hope that some day this code will finally end up bug-free. I say it’s impossible, because every refactoring, every new feature is likely to introduce a slew of new bugs into the system. I mean come on, that’s the very DRY principle right there. Everywhere else everyone will immediately agree that it is a fabulous idea to isolate every meaningful functionality in its own function (or whatever other abstractions your language has to offer) in order to be reused from all sorts of places. Still we nod, mumble “yeah, right”, and happily allocate and free our memory when it comes to crypto code? I don’t really get it. Our brains are exceptionally good at creating and understanding abstractions. So let’s use them. The performance argument for using low-level code doesn’t exactly count for me either. Go buy a HSM, for Chrissake. It almost seems as when it comes to crypto, the people writing it seem to utterly embrace what repels everyone else. It sucks and everbody seems to hate it? - right on with it!
But honestly, I think everyone will agree with me that DRY can be taken as a generally accepted principle. I’m not saying we should abandon all C code everywhere, don’t get me wrong. But I truly believe we can only benefit if we keep the ratio small. Especially in krypt, where I want to keep native code to a minimum, for the very reason of not having to deal with memory management, overflows and company. Right now, there are things where Ruby just isn’t a good fit, though. The issues mainly revolve around being able to do low-level bit fiddling. Ruby currently is quite slow for these sorts of tasks. That’s how the idea of binyo was born. The plan is to find ways to speed up these tasks by resorting to native code. Isolate native code and keep it at a scale where there actually is a chance for humans to fully understand and reason about it. I believe that once this is accomplished, we can get decent performance out of cryptography code entirely implemented in Ruby. As our bytecode VMs evolve, the plan is to implement more and more things in Ruby directly, using as few native code as possible. binyo will also try to add other features that are currently not available in standard Ruby, most noticeably the ability to clear memory securely after usage of sensitive data. Symmetric and asymmetric keys are good examples where this would make a lot of sense.
Where krypt is headed
Until version 1.0 will finally be released (soon, I hope), krypt still needs some more heavy testing and code review. The plan is to release a version that already offers you some basic provider services like for instance digests, ciphers and probably signatures, too. From there, there are different paths that could be taken. It will be largely guided by demand, but eventually you should expect krypt to offer the same functionality that Ruby OpenSSL offers today, probably even a bit more than that. I also hope that I (or you!) can offer different platform-specific default providers in the not too-distant future. With krypt in place, it would also be very easy to build extensions on top of it, XML signatures, GPG and PDF signatures are things that immediately come to mind.
The long-term goal of krypt is to replace the OpenSSL extension as the default cryptography provider in Ruby. This would certainly be easier to accomplish with the gemification of the stdlib becoming reality, but due to krypt’s flexible design it would also be very possible to simply write an “OpenSSL adapter” - a wrapper around krypt that quacks like OpenSSL.
I also experimented with FFI, there is a good chance that the glue between native provider code and Ruby will be written using FFI soon. This opens interesting perspectives, for example it makes using a C-based provider for example based on OpenSSL a possibility in JRuby.
Who knows, maybe the general provider design could also become a blueprint for other C-based languages as well. I pay attention to keeping any Rubyisms out of the Provider APIs - it should be completely agnostic of the actual platform that it is being used in. I see the role of krypt’s Provider layer as similar to what role Rack plays for Ruby web servers or the Servlet API does for Java web servers. After all, adding yet another layer of abstraction is the panacea of Computer Science anyway, right?
In the coming weeks, I will breathe life into the krypt wiki on github, a kind of roadmap with different stages is one of the things that are planned. But I will also put up the API of krypt for discussion, I want to make sure to meet your needs, should you be interested in using krypt in the future. I would be very grateful for your contribution, so don’t be shy!
Final words
Let’s make an effort to replace OpenSSL in the standard library. At RubyConf and also from what I read on the web, I am under the impression that nobody seems to be utterly happy with Ruby OpenSSL. Being one of its maintainers, this always makes me sad a little, but I get the frustration and I think I know many of the reasons for it. So at least with krypt, we have the chance to steer the story of Ruby cryptography towards a happy ending, and I would greatly appreciate any contribution and participation. I am very excited about this project, and after a year of constantly designing, tearing down and rebuilding I got a pretty good picture of it in my head and I believe in it more than ever. I hope that I could whet your appetite a little as well.
I also want to apologize to the many people involved in the development of OpenSSL if my criticism seemed too harsh at some points. I have the highest respect for that project, and I sure don’t want to chime into the typical OpenSSL bashing that seems to have become popular among the infsec community. It is mean to bash the hard work that many of you do for free - if anyone felt offended, please accept my apologies! If I didn’t like OpenSSL, I would probably never have become a maintainer of Ruby OpenSSL in the first place!
Thanks for bearing with me this far, the next time I hope to dig deeper into the architecture of krypt, shedding a bit more light on the Provider principle and how it works. See you next time!
At this point, I would also like to express my gratitude to some of the people without whom krypt would not be where it is right now:
Aaron Patterson and Hiroshi Nakamura: I can’t thank you enough for asking me to help out at that time!
Matz and the whole Ruby Association for trusting me with one of their first Ruby Association Grants. That grant was the single-most important thing that helped me kicking off the project. I had the time of my life, and I can only encourage anyone to apply for such a grant. I can personally guarantee you that you wouldn’t ever want to miss the experience!
Charles Nutter (@headius) for letting me participate in JRuby’s GSoC and for encouraging me to proceed with krypt and for believing in the idea.
Vipul Amler (@vipulnsward) for working with me on krypt during JRuby GSoC and for still contributing and for still being interested in cryptography.
Finally, the many people that encourage me to keep on working on this project, that help me out whenever I’m stuck or are simply interested in krypt.