Signing android releases with yubikey

3 minute read

At VideoLAN, we recently changed our signing procedure to leverage our security keys. As explained on Yubico website, Android signing is quite easy.

With this method, I can now sign the VLC releases on any of my computers without duplicating the keystore file. And keystore password is replaced by my Yubikey PIN.

Here is the precise process we went through to get this done.

Setup

Install dependencies

I am considering a Debian based distribution with open JDK 8 installed for this post.

First of all, we need to install the pkcs11 opensc lib and the zipalign tool

sudo apt-get install opensc-pkcs11 zipalign

zipalign can also be found in android_sdk_path/build-tools/version/

Prepare configuration file

Then, we prepare the pkcs11 configuration. Let’s create file pkcs11_java.cfg and fill it with:

name = OpenSC-PKCS11
description = SunPKCS11 via OpenSC
library = /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
slotListIndex = 0

Yubico How-To advises slotListIndex = 1, but I had to set it to 0 to make it work with my Yubikey 4.

Let’s assume we save it in: ~/.pkcs11_java.cfg

Set up your own management key

If you did not set your management key, you have to do it now:

key=`dd if=/dev/random bs=1 count=24 2>/dev/null | hexdump -v -e '/1 "%02X"'`
echo $key
yubico-piv-tool -a set-mgm-key -n $key

Change your PIN

Same for PIN setting, default one is 123456

yubico-piv-tool -a change-pin -P 123456 -N <NEW PIN>

Keystore import

Now it’s time to import our keystore to the key PIV slot.

keytool -importkeystore -srckeystore mykeystore.keystore -destkeystore mykeystorey.p12 -srcstoretype jks -deststoretype pkcs12
yubico-piv-tool -s 9a -a import-key -a import-cert -i mykeystorey.p12 -K PKCS12 -k

You will be asked to type in the keystore password, then the certificate management key.

Starting from now, you won’t have to type the keystore password anymore but your Yubikey PIN.

We can check that our key is ready to sign apps:

keytool -providerClass sun.security.pkcs11.SunPKCS11 -providerArg ~/.pkcs11_java.cfg -keystore NONE -storetype PKCS11 -list -J-Djava.security.debug=sunpkcs11

This is the Yubikey PIN you have to type-in now.
And don’t forget to touch it if you enabled the ‘touch-to-sign’ option.

App signature

In build tools 24.0.3 Google has released apksigner, a new signature tool with convenient arguments like --min-sdk-version to get sure the application signature is correct.

Until release (26.0.1) apksigner doesn’t handle pkcs11 protocol correctly. So, you need to use build-tools 26.0.1+

We now have to get an unsigned apk, so we must tell gradle to not apply any signing config for release builds

 buildTypes {
        release {
            signingConfig null
            //…
        }
    }

Finally we can sign an apk without our keystore, we just need the Yubikey to be plugged and fire up apksigner

ANDROID_SDK_PATH/build-tools/BUILD_TOOLS_VERSION/apksigner sign --ks NONE --ks-pass "pass:$YUBI_PIN" \
--min-sdk-version 9 --provider-class sun.security.pkcs11.SunPKCS11 \
--provider-arg pkcs11_java.cfg --ks-type PKCS11 app.apk

We can now verify the package is signed:

apksigner verify --verbose app.apk

verify accepts --min-sdk-version and --max-sdk-version to ensure your users won’t get 103 Play Store error code once the app is released.

Scripting

Here is the full bash script I use to sign all my apks at once:

#! /bin/sh

echo "Please enter Yubikey PIN code "
stty -echo
trap 'stty echo' EXIT
read -p 'PIN: ' YUBI_PIN
stty echo
trap - EXIT

BT_VERSION="26.0.1"

echo "\nSigning apks\n"
for i in `ls *.apk`;
do
$ANDROID_SDK/build-tools/$BT_VERSION/zipalign 4 $i $i.tmp && mv -vf $i.tmp $i
$ANDROID_SDK/build-tools/$BT_VERSION/apksigner sign --ks NONE \
        --ks-pass "pass:$YUBI_PIN" --min-sdk-version 9 \
        --max-sdk-version 26 --provider-class sun.security.pkcs11.SunPKCS11 \
        --provider-arg ~/.pkcs11_java.cfg --ks-type PKCS11 $i
done
unset YUBI_PIN

Jarsigner

We can also use jarsigner to sign your apk:

jarsigner -providerClass sun.security.pkcs11.SunPKCS11 -providerArg ~/.pkcs11_java.cfg \
  -keystore NONE -storetype PKCS11 -sigalg SHA1withRSA -digestalg SHA1 \
  app.apk "Certificate for PIV Authentication"

The -sigalg SHA1withRSA -digestalg SHA1 parameters are needed because we support old devices. If you don’t support Android 4.2 and older you can rip it off.

With jarsigner, we need to zipalign the apk after signing them.

And verify the package is signed:

jarsigner -verify app.apk

Leave a comment