Reimplementing PKCS#11 Module

1 month ago 2
Back to Index

It has been more than two years since I released Baš Čelik, software for reading serbian smart-card documents. After the past two years I implemented everything I originally planned (and more), but one feature was always missing and users kept asking for it: the ability to use the personal certificate from the ID card to sign data. A driver for such functionality exists for Windows, but not for Linux or macOS.

I was strongly against implementing such a feature myself, since cryptography is something you should not touch unless you know exactly what you are doing, and I didn’t know how the Windows driver worked. However, mails with the same request kept piling up in the inbox…

Reversing the Smart Box

Around New Year I was told by one Baš Čelik user that the state had released the Smart Box application (creative name!) to help citizens use smart-card certificates to sign tax forms. It struck me as odd that Smart Box was available for both macOS and Linux, so I immediately wanted to see how they had done it. At that moment, I felt both happy and sad because I thought this new app would lift the burden from me and my project.

I downloaded the Ubuntu version and found it was a Java application. Java is always a good sign for reverse engineering, and indeed the application decompiled (too) easily with an online Java decompiler. After a bit of code exploration I saw it functions as a WebSocket server, listening on one of three predefined ports (17165, 20806 and 65097, in that order). I didn’t know before I saw this, but JavaScript in a browser tab can send requests to local web servers, and that is how this app works. Lazy to analyze the rest of the code, I implemented a sniffer in ~100 lines of Go code to help me understand the communication. I started my sniffer on port 17165 and then launched Smart Box. Since the first choice (17165) was already in use, Smart Box began listening on 20806. The code in the browser, however, did not know which port had been chosen and happily sent data to localhost:17165. (Looking back, I’m not sure why I didn’t just use Wireshark for this.)

┌─────────┐ ┌────┐ ┌───────────┐ │ browser ◄────► go ◄────► smart box │ └─────────┘ └────┘ └───────────┘

It turned out that communication between the government website and Smart Box is very simple. The conversation ends with the website sending a sign request to Smart Box, and Smart Box returning the request signed with the smart-card certificate.

With the complete communication log between the browser and the app, I started to look in the code for implementation details. I couldn’t find smart-card specific code, but the clues pointed to a standard with a scary name — PKCS#11.

If you don’t know anything about PKCS (as I didn’t at the time), the first thing to know is that the first ten PKCS standards are (mostly) irrelevant for this story. PKCS#11 modules are shared libraries that implement a set of functions allowing applications to use cryptographic functions provided by hardware tokens (such as smart cards). In this way, applications like web browsers and office suites do not need to know anything about the specific token; PKCS#11 abstracts everything for them.

The funny thing is that the application only works when an appropriate module is available. Since there are no modules for Linux and macOS, the application is useless on those platforms. On Windows, the module already works with major browsers. I guess that building useless Java applications is a good way to pump money out of the state budget.

Reimplementing the Smartbox

When I understood the high-level idea behind Smart Box, I decided to reimplement it (which was a stupid decision, since it is useless functionality). The communication between the application and the browser was the easy part; I had already logged all messages. The hard part was understanding how Java cryptographic libraries use the PKCS#11 module. Of course, I could have learned this by carefully analysing the code, but I could just use the same idea as before: make a sniffer again.

Luckily, there is an excellent PKCS#11 logger which records everything that happens between an application and a PKCS#11 module. It works by pretending to be a functional module, passing all function calls to the target module, returning responses, and logging everything. It saved me from digging deep into the Java code or writing my module.

┌───────────┐ ┌────────┐ ┌─────────────┐ │ smart box ◄────► logger ◄────► PKCS module │ └───────────┘ └────────┘ └─────────────┘

In two weekends, I reimplemented the complete functionality of Smart Box in Go.

Reversing the original module

I am almost sure that nobody will use my Smart Box implementation (probably true for the original Smart Box as well), but the time spent was not wasted because I realized how simple the PKCS#11 standard is. That encouraged me to attempt a reimplementation of the PKCS#11 module itself.

The PKCS#11 API may be simple, but the logic underneath doesn’t have to be. The first thing I wanted to know was how the original module actually functions.

The most obvious way to understand that is to disassemble the original DLL. Unfortunately, disassembly requires skills and patience I don’t have. Still, with modest skills and a free IDA license, I obtained some surface-level insights into the code. A bit deeper, I found calls to OpenSSL functions, which seemed odd at the time — after all, OpenSSL should use the module, not the other way around.

Since disassembly did not tell me enough, I used the trick for the third and final time: I captured USB traffic between the module and the smart card with Wireshark. Wireshark can decode CCID packets and the ISO 7816 packets inside them (ISO 7816 is the standard for exchanging data with smart cards).

┌─────────────┐ ┌───────────┐ ┌────────────┐ │ PKCS module ◄────► Wireshark ◄────► smart card │ └─────────────┘ └───────────┘ └────────────┘

A lot of data flows between the card and the module, and it can be hard at first to make sense of it. That’s why I wrote small Go programs that called functions from the original module with one-second delays between calls. By correlating those calls with USB packets I could see how functions map to packets. After doing this for a while, I concluded a few things:

  • The module reads two certificates from the card when the session is started.
  • Actual signing is performed with two simple commands.
  • Logging in is performed by sending a PIN-verification command to the card. The card’s state does not change after this command (well, except that it will lock after three consecutive failed PIN verifications).
  • All other commands (such as object search or attribute retrieval) are implemented entirely in the module, without needing to contact the smart card. Specifically, hashing algorithms are not implemented on the token (possibly one of the reasons why the original module depends on OpenSSL).

Combining these facts with the PKCS#11 specifications gave me a very detailed picture of how the module should work. The only thing left was to put what I learned into code.

Zig implementation

In the last few months, I patiently implemented an open source module, one function at a time. Originally, I started with C++, but I quickly switched to Zig, which is for me a modern C. Experience with Zig was mostly positive; however, I would like to see a stable standard library and a better language server. And of course, same as with Go, I think that an unused variable doesn’t make sense, no matter what others say.

Last weekend, the module finally got into a usable state. There is still a lot of work that can be done, but after two years of work on this project, I finally feel relieved.

Read Entire Article