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
andopensc
for communication with smartcardsapksigner
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.