Generating signing keys
This guide explains how to generate signing keys on a hardware token. This is a prerequisite for the signing guides for APKs, F-Droid repos, and the server ZIPs. These guides assume that the keys already exist on the hardware token.
The motivation of doing key generation separately from signing is the following:
- Key generation is usually done once, whereas signing is a regular activity.
- Key generation can be done "centrally" with the
pkcs11-tool. This is more direct than going through another tool (such as Java'skeytool). - Java's
keytoolcannot securely generate key pairs on a PKCS#11 token. We found this out during our initial research; see the details posted on the Nitrokey forum.
Prerequisites
- A hardware token that exposes a PKCS#11 API. See the key management guide. This guide was tested with a Nitrokey HSM.
Make sure that you have exactly one PKCS#11 token inserted. This avoid problems with selecting the correct token.
List the token
Before you start, list the PKCS#11 tokens. Note down the name/label and slot of the token that you intend to use.
export MODULE=/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
export TOKEN=my-token-label
pkcs11-tool --module $MODULE --list-token
Create a signing key
Create a new key pair.
This creates two objects on the token: a private key and a public key.
To use elliptic curve instead of RSA, set --key-type EC:secp256r1.
export KEY_ALIAS=my-key-label
pkcs11-tool --module $MODULE --login --token-label $TOKEN --keypairgen --key-type RSA:4096 --label $KEY_ALIAS --sensitive
Choose your settings during key generation carefully! It is difficult or even impossible to modify a key later. For example, you cannot rename the label. You would need to generate a completely fresh key, and rotate everything to the new key.
Export the public key
Later, you will need the public key to verify signatures. Export it to a file:
pkcs11-tool --module ${MODULE} --token-label ${TOKEN} --read-object --type pubkey --label ${KEY_ALIAS} --output-file "${KEY_ALIAS}.pub.der"
Export the CVC-REQ for attestation
If you want to prove to your community that you securely generated your signing keys in a hardware token,
you may be interested in key attestation.
A SmartCard-HSM generates a so-called "CVC-REQ" for every key that is generated with pkcs11-tool.
This CVC-REQ is the leaf of a certificate chain, whose root CA is run by the hardware vendor.
If you want to use attestation, you must export the CVC-REQ now. Please see the attestation guide for details.
The next step (loading a self-signed X.509 certificate) will overwrite the CVC-REQ!
If you proceed, you will no longer be able to export the CVC-REQ. If later you want to use key attestation, you will need to generate a fresh key.
Create a certificate
For many use cases (such as Android APK signing), there needs to be a self-signed X.509 certificate on the hardware token, with the same label as the private key.
This certificate is not created automatically.
Instead, you need to use an external tool (such as certtool or openssl) to create a certificate
and then write the certificate to the token.
We use certtool because it is easier to work with via PKCS#11 than openssl.
We will use a self-signed certificate because this is sufficient for Android APKs. You could also sign a CSR and obtain a CA-signed certificate.
# Create the self-signed certificate
certtool --provider $MODULE --ask-pass --generate-self-signed --hash SHA512 --load-privkey "pkcs11:token=${TOKEN};type=private;object=${KEY_ALIAS}" --outfile "${KEY_ALIAS}.crt.pem"
# Inspect the certificate
openssl x509 -in "${KEY_ALIAS}.crt.pem" -text -noout | less
# Load the certiticate to the token
# WARNING: This overwrites any existing CVC-REQ!
pkcs11-tool --module $MODULE --login --token-label $TOKEN --write-object "${KEY_ALIAS}.crt.pem" --type cert --label $KEY_ALIAS
For the certificate validity, choose 30 years: 365 * 30 = 10950.
This is because Android apps are supposed to have the same signing key for the entire duration of the app lifetime.
Backup
-
List the keys and read out the Key ref of the key you want to back up.
pkcs15-tool -D | less -
Back up (export) the key:
sc-hsm-tool --wrap-key wrap-key.bin --key-reference 1This wraps (encrypts) the referenced key under the DKEK that was used to initialize the Nitrokey HSM. The file itself is ciphertext, so it can be backed up to normal storage locations.
warningThis step only works if the Nitrokey HSM has been initialized with a DKEK! Otherwise, the command will fail with
Card not initialized for key wrap. -
Theoretically, you could now delete the key from the hardware token (to test restoring it):
pkcs11-tool --module $MODULE --login --token-label $TOKEN --delete-object --type privkey --label $KEY_ALIAS -
To restore (import) the key, initialize a Nitrokey with the same DKEK. Then load the key into hardware token:
sc-hsm-tool --unwrap-key wrap-key.bin --key-reference 1
For details, see:
- https://www.smartcard-hsm.com/2014/09/25/Desaster_Recovery_for_your_SmartCard-HSM.html
- https://github.com/OpenSC/OpenSC/wiki/SmartCardHSM#using-key-backup-and-restore
This allows you to transfer/copy your signing keys to a different physical Nitrokey HSM. The only requirement is that both hardware tokens are initialised with the same DKEK.
This is useful to share signing keys between multiple maintainers.
Repeat
Repeat the above for all signing key pairs that you need.
Tips
- Naming:
It is common practice to use the same human-readable label for the private key, public key, certificate that belong together.
- In other words, there will be three objects with the same label. These objects are only distinguished by their object type.
- This is acceptable and allowed by the PKCS#11 standard. In fact, many applications expect this. Often, you can only specify a single label, and applications will automatically look for a private key and a certificate of that label.
- Certificate fields:
You don't need to fill out all fields that
certtoolrequests. For example, you could only fill out the "Common Name (CN)", and leave the other fields empty. Setting a meaningful CN makes it easier to identify the certificate later.
Importing an existing signing key
For security reasons, it is not recommended to import an existing (often file-based) signing key into the HSM. The reason is that the whole point of using an HSM is to ensure that the secret key has never existed in a potentially insecure place. If you must import the key into the HSM, at least import it in wrapped form and not in the clear:
Importing keys in clear is a major security issue, as the key material traverses a lot of unprotected interfaces (PC/SC API, CCID API, USB-HW, Reader, CL or CC interface). ~ SmartCard-HSM developer, Nitrokey forum
It is better to generate a fresh secret key inside the HSM and start using that.
- Generate a fresh signing key within the HSM.
- Rotate the signing key from the old to the new key.
On Android, rotating the APK signing key of an existing, installed app only works with the APK signature scheme v3. Thus it requires minSdk 28 (Android 9, released in August 2018). Alternatively, you can change your app's package id.
References
- Scripts that automate the above:
- Tutorial of PKCS#11 CLI tools:
- https://docs.securosys.com/pkcs/Tutorials/key_management (From another HSM vendor, so ignore their PKCS#11 provider. The normal commands are the same.)
- Key rotation: