blog.dbrgn.ch

Signing F-Droid Repository with (Yubi)HSM

written on Wednesday, June 26, 2024 by

At Threema (my employer), we publish our own F-Droid repository. Every time a new app is published to such a repository, metadata needs to be updated and signed. Normally this is done using a signing keypair stored in plain files. We instead store the signing key securely on a YubiHSM 2.

(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.)

The fdroidserver project (which is used to sign a repository) doesn't have explicit support for HSMs. However, under the hood apksigner is used, and apksigner supports any backend with a PKCS#11 compatible API (e.g. OpenSC).

Since I've been asked on multiple occasions on how we set up HSM based repo signing (even by developers of fdroidserver 😄), here's the summary. Note that a lot of this can be found in the YubiHSM2 User Guide. Note also that this is not a complete guide, it only contains the most important steps that may help you to set up your own process.

Preparations

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

  • libccid, pcscd and opensc for communication with smartcards
  • apksigner for signing APKs

Ensure that pcscd is running on the system where the HSM will be plugged in. If you do the signing in a Docker container, then the pcscd socket must be mounted into the container:

--mount type=bind,source=/var/run/pcscd,target=/var/run/pcscd,readonly

Note that the Debian 11 package of apksigner has a bug that makes it incompatible with system versions of Java (11 / 17). Thus, if you run Debian 11, you need to manually install Java 8 and set it as default using update-alternatives. This should be fixed on Debian 12.

Install YubiHSM 2 SDK

Yubico provides various packages (including Debian) for the YubiHSM 2 SDK. You can grab them from https://developers.yubico.com/YubiHSM2/Releases/ and install the included .deb files.

Make sure that /etc/yubihsm-connector.yaml is set up properly to allow connecting to your HSM. The process of configuring the YubiHSM and setup of signing keys is not covered in this blog post.

Add OpenSC Configs

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 YubiHSM2 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:
# https://docs.yubico.com/software/yubihsm-2/component-reference/hsm2-ref-pkcs11.html

# URL of the connector to use. This can be a comma-separated list
connector = http://127.0.0.1:12345
#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 = http://proxyserver.local.com:8080

# 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/opensc-pkcs11.so
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/yubihsm_pkcs11.so

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 (in this example using the default password):

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

To read the keys usable by Java:

$ keytool -list -keystore NONE -storetype PKCS11 -providerClass sun.security.pkcs11.SunPKCS11 -providerArg /etc/pkcs11/sunpkcs11_yubihsm2.cfg -storepass 0001password -v

Install fdroidserver

You can install it from the Python Package Index:

pip install fdroidserver

The version of fdroidserver we used originally also needed a small patch, but that was fixed in the meantime.

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
  -providerClass sun.security.pkcs11.SunPKCS11
  -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: "CN=example.org, 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:

$ export FDROID_KEYSTOREPASS="${HSM_FDROID_AUTHKEY_ID}${HSM_FDROID_AUTHKEY_PASS}"

Running fdroid update should now do the job.

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