Signing F-Droid Repository with (Yubi)HSM

written on Sunday, June 30, 2024 by

At Threema (my employer), we publish a custom F-Droid repository. Every time a new app is published to such a repository, metadata needs to be updated and cryptographically signed. Normally this is done using a signing keypair stored in regular files on the file system. For improved security, we instead store the signing key securely on a HSM (Hardware Security Module). We picked the YubiHSM 2.

I've been asked on multiple occasions how we set up this HSM based repo signing. When we first got our process to work (roughly 2 years ago), this required jumping through a few extra hoops, like manually installing Java 8 (because HSM support in the Debian 11 apksigner package was broken) or manually installing an newer version of fdroidserver through pip and then applying a patch (due to a bug that prevented the smartcardoptions configuration from working). Nowadays things should be fairly smooth on Debian 12 without these extra steps. This blogpost outlines what's needed to sign a F-Droid repo with the YubiHSM 2. (Note that this is not a complete guide, it only contains the most important steps that may help you to set up your own process.)

(Regarding the choice of HSM: Note that you could also sign a repo with a regular YubiKey 5C, but it is slower, has less flexibility, can store fewer keys and only supports RSA keys up to 2048 bits. The YubiHSM is certainly much nicer, but also costs a bit more (around 700 € when we bought it). Yet another option would be the Nitrokey HSM 2, which can be obtained for around 100 €. I don't have any experience with the Nitrokey.)

Base System: Debian 12

Our signing system currently runs on Debian 12. To prepare it, we need to install a few packages:

  • libccid and opensc for communication with smartcards
  • apksigner for signing APKs
  • fdroidserver for signing the F-Droid repo

In a Dockerfile:

FROM debian:12-slim

# Install dependencies
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && \
    apt-get install -y --no-install-recommends libccid opensc apksigner fdroidserver && \
    rm -rf /var/lib/apt/lists/*

Install YubiHSM 2 SDK

Yubico provides YubiHSM 2 SDK packages for various Linux distributions (including Debian). You can grab them from and install the included .deb files.

In a Dockerfile:

# Install YubiHSM 2 SDK
RUN cd /tmp && \
    wget && \
    tar xf yubihsm2-sdk-*.tar.gz && \
    cd yubihsm2-sdk && \
    rm libykhsmauth-dev_*_amd64.deb && \
    apt-get update && \
    apt-get install -y --no-install-recommends ./*.deb && \
    rm -rf /var/lib/apt/lists/* && \
    rm -r /tmp/yubihsm*

If you want to sign in CI using Docker containers, you need to set up the YubiHSM connector on the host system, expose the service towards your signing container, and set up that container to connect to the HSM through the connector service. Make sure that /etc/yubihsm-connector.yaml is set up properly to allow connecting to your HSM.

The process of configuring the YubiHSM, as well as the creation and installation of signing keys, is not covered in this blog post. A lot of helpful information can be found in the YubiHSM 2 User Guide.

Add OpenSC Config

Install the following OpenSC config to /etc/opensc.conf:

app default {
    framework pkcs15 {
        # Workaround to solve CKR_USER_NOT_LOGGED_IN errors in jarsigner
        pin_cache_ignore_user_consent = true;
        use_pin_caching = true;

Add PKCS#11 Configs

Next, we install three PKCS#11 config files:

The YubiHSM 2 Config

This file configures, how the YubiHSM 2 is accessed. If you want to connect over USB or over the network, change the connector option.

# /etc/pkcs11/pkcs11_yubihsm2.cfg

# This is a sample configuration file for the YubiHSM PKCS#11 module
# Uncomment the various options as needed

# For further information:

# URL of the connector to use. This can be a comma-separated list
connector =
#connector = "yhusb://"

# Enables general debug output in the module
# debug

# Enables function tracing (ingress/egress) debug output in the module
# dinout

# Enables libyubihsm debug output in the module
# libdebug

# Redirects the debug output to a specific file. The file is created
# if it does not exist. The content is appended
# debug-file = /tmp/yubihsm_pkcs11_debug

# CA certificate to use for HTTPS validation. Point this variable to
# a file containing one or more certificates to use when verifying
# a peer. Currently not supported on Windows
# cacert = /tmp/cacert.pem

# Proxy server to use for the connector
# Currently not supported on Windows
# proxy =

# Timeout in seconds to use for the initial connection to the connector
timeout = 5

The OpenSC config

This file ensures that OpenSC is used to access the YubiHSM as a smartcard.

# /etc/pkcs11/sunpkcs11_opensc.cfg

name = OpenSC-PKCS11
description = SunPKCS11 via OpenSC
library = /usr/lib/pkcs11/
slotListIndex = 0

The PKCS#11 YubiHSM 2 config

This file contains the path to the shared library containing the YubiHSM PKCS#11 implementation.

# /etc/pkcs11/sunpkcs11_yubihsm2.cfg

name = yubihsm2-pkcs11
library = /usr/lib/x86_64-linux-gnu/pkcs11/

To know the path to the YubiHSM 2 config file (created previously), we need to export an env variable:

$ export YUBIHSM_PKCS11_CONF=/etc/pkcs11/pkcs11_yubihsm2.cfg

Test PKCS#11 Connection

If this is set up correctly, you should be able to connect to your HSM (in this example using the authentication key 0x0001 and the default password password):

$ pkcs11-tool --module /usr/lib/x86_64-linux-gnu/pkcs11/ -l --pin 0001password -L -O

As you can see, the PKCS#11 pin is the concatenation of the authentication key ID (4 hex characters) and the password for that authentication key.

To read the keys usable by Java:

$ keytool -list \
    -keystore NONE -storetype PKCS11 \
    -providerClass \
    -providerArg /etc/pkcs11/sunpkcs11_yubihsm2.cfg \
    -storepass 0001password \

Configure and Sign the Repo

In the repo config.yml, ensure that the following config is set:

# The key (from the keystore defined below) to be used for signing the
# repository itself. This is the same name you would give to keytool or
# jarsigner using -alias. (Not needed in an unsigned repository).
repo_keyalias: "F-Droid Repo Signing Cert"

# The keystore to use for release keys when building. This needs to be
# somewhere safe and secure, and backed up!  The best way to manage these
# sensitive keys is to use a "smartcard" (aka Hardware Security Module). To
# configure F-Droid to use a smartcard, set the keystore file using the keyword
# "NONE" (i.e. keystore: "NONE"). That makes Java find the keystore on the
# smartcard based on 'smartcardoptions' below.
keystore: "NONE"

# You should not need to change these at all, unless you have a very
# customized setup for using smartcards in Java with keytool/jarsigner
smartcardoptions: |
  -storetype PKCS11
  -providerArg /etc/pkcs11/sunpkcs11_yubihsm2.cfg

# The password for the keystore (at least 6 characters). If this password is
# different than the keypass below, it can be OK to store the password in this
# file for real use. But in general, sensitive passwords should not be stored
# in text files!
keystorepass: {env: FDROID_KEYSTOREPASS}

# The distinguished name used for all keys.
keydname: ", OU=Android, O=Example Company Ltd., L=SomeCity, ST=SG, C=CH"

Export the FDROID_KEYSTOREPASS variable, consisting of the HSM authkey ID concatenated with the password:


Running fdroid update should now do the job.

This entry was tagged f-droid, hsm, security and signing