Interest Article

Migrating SecureDrop’s PGP backend from GnuPG to Sequoia

November 1, 2023

In the upcoming SecureDrop 2.7.0 release, we’ve made a technical change that will be invisible to users but improve reliability and robustness — and provide better security going forward.

SecureDrop relies on the OpenPGP standard for encrypting all data and messages that pass through the server. Historically, we have used GnuPG, or GPG for this; we are now switching to the newer Sequoia-PGP library implemented in Rust.

In this blog post, we’ll give an overview of the encryption in SecureDrop, and explain why and how we switched to Sequoia.

Background

The very first commit to the SecureDrop Git repository, by Aaron Swartz, created a code file called crypto.py that implemented basic encryption and decryption functions using GPG. The specific cryptographic operations can be broken down as follows:

  • Generate a new OpenPGP key pair for a specified user ID, encrypted using a passphrase
  • Encrypt a message (UTF-8 text) and store on disk
  • Encrypt a file (from contents stored on disk) and store on disk
  • Decrypt arbitrary bytes, given a secret key and passphrase

When a source logs into SecureDrop, we create a PGP key pair for that person, protected by a passphrase only the source knows. Submissions (messages or files) from the source are encrypted to the journalist’s public key. If a journalist wants to reply to the source, that reply is encrypted for the source, and when the source logs in again, any replies are decrypted and displayed in plain text.

Using GPG

GPG is a command-line tool that SecureDrop has historically invoked via shell commands handled by the pretty-bad-protocol Python library. Unfortunately, this package has not been updated since 2018. For a while, we applied our own runtime updates (also known as “monkey-patching”); eventually, we dropped this approach and imported (”vendored”) just the parts of the code we use directly into SecureDrop’s codebase.

Because GPG is a general purpose tool, we’ve also run into issues with functionality we don’t use. For example, GPG will automatically recalculate the web of trust regularly, which, until we disabled that feature, caused instances with a large number of sources to see timeouts for any GPG operation.

There was also a fundamental architectural issue: GPG stores keys on disk, which meant it needed to be kept in sync with our database, and we had to handle cases when they weren’t in sync.

Finally, GPG is implemented using the C programming language, and we are generally looking to migrate to code written in memory-safe languages for security reasons.

Migrating to Sequoia

In the PGP landscape, Sequoia-PGP is a relatively new project focusing on safety and correctness. Crucially, it was implemented first as a library, which was then exposed for interactive use in the sq command-line client. This made it rather straightforward for us to integrate into SecureDrop.

We were also glad to see that earlier this year, the RPM project switched to using Sequoia for verifying package signatures. RPM is the package manager used by Red Hat, Fedora, and other major Linux distributions, so RPM’s adoption of Sequoia is a significant vote of confidence.

Sequoia has also taken steps to improve the overall ecosystem by participating in the Internet Engineering Task Force's OpenPGP working group and pushing for the deprecation of insecure SHA-1 signatures.

We wrote a small Rust library, internally named redwood, which provides the four cryptographic functions listed above. It is intentionally minimalist, providing only the features we need; this means less opportunity for developer error and a lower review burden.

To make it callable from Python, it uses the PyO3 Rust/Python bridge project to build a shared library that is packaged inside a Python wheel. We then install the wheel in a virtualenv shipped in our Debian package.

The actual migration to Sequoia takes place in three steps. First, all new sources have their key pair generated by Sequoia instead of GPG, then stored in the database. All encryption operations are also taken over by Sequoia, since we can export public keys from GPG pretty trivially, with decryption using either Sequoia or GPG, depending on where the secret key was stored.

Next, during the 2.7.0 upgrade, we do an offline migration that exports every source’s public key and fingerprint, and stores it in the database, just like Sequoia-native sources. Because the upgrade happens automatically and without supervision, we’ve erred on the side of skipping migrating individual sources if there is an error exporting their key, instead of failing and blocking the whole upgrade.

Finally, when a preexisting source logs in, we use the source’s passphrase to export the secret key from GPG and store it in our database, in the standard OpenPGP format for Sequoia to consume. During development, we learned that GPG stores secret keys in a custom format that Sequoia is unable to read (so far). Since a source’s reply key can only be decrypted using the source’s passphrase, preexisting secret keys can only be moved to the database on the source’s next login.

This is not ideal, since it means that we need to keep GPG support and a subset of the pretty-bad-protocol library around for a while—but this final migration is the last time that sources will use GPG, and new SecureDrop instances will never use it at all.

By the end of this migration, preexisting “GPG-era” sources will act identically to Sequoia-backed sources, with the source’s keys stored in the database, and all encryption and decryption operations handled by Sequoia. And this whole process should be entirely transparent, with no action required by administrators, journalists, or sources.

As mentioned earlier, Sequoia forbids the use of keys with SHA-1 signatures. While this is configurable, we’ve kept this restriction and implemented extra checks for insecure keys used by administrators and journalists.

Adopting Rust

We evaluated some of the projects that provide Python bindings for Sequoia (johnnycanencrypt, pysequoia), but decided to write our own so we could have the minimalist interface previously described. This also led us to begin to ship Rust code written by us.

We are excited by Rust’s strict typing, integrated tooling, and memory safety, and plan to use it in other projects, so we’re glad to have gotten started.

During the course of development, we added Rust to our continuous integration, developed a policy for upgrading the Rust toolchain, and began auditing our dependencies using Cargo Vet. Team members spent time improving their Rust skills through books, exercises, and patches to SecureDrop’s Rust code.

Future work and thanks

Once 2.7.0 ships, we will still have more work to do. As previously discussed, we will soon be shifting focus to work on the SecureDrop Workstation, which still uses GPG for decryption and encryption. We need to investigate and develop a plan for switching to Sequoia there, which would let us implement extra security features, among other improvements.

We are also working on coordinating a security audit of SecureDrop’s Sequoia integration and remaining GPG-related code, among other areas; the results will be shared publicly afterward, as all our audits are.

We’d like to thank the Sequoia team for its work on developing such a useful and straightforward library, and a special shout-out to Neal Walfield and Wiktor Kwapisiewicz for their advice and reviews of our code. We’re also thankful for Alban Diquet’s valuable review contributions.

Return to News