<!doctype html> <html lang="en-gb"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link href="https://blog.polynom.me/css/index.css" rel="stylesheet" integrity="sha384-R7KUcezOBiIXJ95JUBiXFdX0mMReehb8omi2xIGyZ6mbgXtQ3spxTx4c9BfffIA8" /> <link rel="alternate" type="application/rss+xml" title="blog.polynom.me Atom feed" href="https://blog.polynom.me/atom.xml"> <meta property="og:description" content="" /> <meta property="og:title" content="Signing Android Apps Using a YubiKey (on NixOS)" /> <title>Signing Android Apps Using a YubiKey (on NixOS)</title> </head> <body> <div class="flex flex-col p-2 md:p-8 items-start md:w-4/5 mx-auto"> <!-- Header --> <div class="flex flex-row self-center"> <img class="w-12 h-12 md:w-24 md:h-24 rounded-lg" src="https://blog.polynom.me/img/avatar.jpg" integrity="sha384-uiNteVXosQ2+o/izp41L1G9VwuwYDYCOPxzFWks058DMUhW7KfQXcipM7WqgSgEZ" alt="Profile picture"/> <div class="ml-4 self-center"> <a class="self-center text-2xl font-bold" href="/">PapaTutuWawa's Blog</a> <ul class="list-none"> <li class="inline mr-8"><a href="/">Posts</a></li> <li class="inline mr-8"><a href="https://blog.polynom.me/atom.xml">RSS</a></li> <li class="inline mr-8"><a href="https://polynom.me">About</a></li> </ul> </div> </div> <!-- Container for posts --> <div class="mx-auto mt-4 w-full md:max-w-prose"> <h1 class="text-indigo-400 text-3xl">Signing Android Apps Using a YubiKey (on NixOS)</h1> <span class="text-md mt-2">Posted on 2023-07-24</span> <!-- Actual article --> <article class="prose lg:prose-lg text-white mt-4"> <p>In my spare time, I currently develop two Android apps using <em>Flutter</em>: <a href="https://codeberg.org/PapaTutuWawa/anitrack">AniTrack</a>, a simple anime and manga tracker based on my own needs, and <a href="https://moxxy.org">Moxxy</a>, a modern XMPP client. While I don't provide release builds for AniTrack, I do for Moxxy. Those are signed using the key-pair that Flutter generates. I thought to myself: "Wouldn't it be cool if I could keep the key-pair on a separate device which does the signing for me?". The consequence of this thought is that I bought a <em>YubiKey 5c</em>. However, as always, using it for my purposes did not go without issues.</p> <span id="continue-reading"></span> <p>The first issue is that the official <a href="https://developer.android.com/build/building-cmdline#deploy_from_bundle"><em>Android</em> documentation</a> says to use the <code>apksigner</code> tool for creating the signature. <a href="https://developers.yubico.com/PIV/Guides/Android_code_signing.html">The <em>YubiKey</em> documentation</a>, however, uses <code>jarsigner</code>. While I, at first, did not think much of it, <em>Android</em> has <a href="https://source.android.com/docs/security/features/apksigning/">different versions of the signature algorithm</a>: <code>v1</code> (what <code>jarsigner</code> does), <code>v2</code>, <code>v3</code>, <code>v3.1</code> and <code>v4</code>. While it seems like it would be no problem to just use <code>v1</code> signatures, <em>Flutter</em>, by default, generates <code>v1</code> and <code>v2</code> signatures, so I thought that I should keep it like that.</p> <p>So, the solution is to just use <code>apksigner</code> instead of <code>jarsigner</code>, like <a href="https://geoffreymetais.github.io/code/key-signing/">another person on the Internet</a> did. But that did not work for me. Running <code>apksigner</code> like that makes it complain that <code>apksigner</code> cannot access the required <code>sun.security.pkcs11.SunPKCS11</code> Java class.</p> <pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>> /nix/store/ib27l0593bi4ybff06ndhpb8gyhx5zfv-android-sdk-env/share/android-sdk/build-tools/34.0.0/apksigner sign \ </span><span> --ks NONE \ </span><span> --ks-pass "pass:<YubiKey PIN>" \ </span><span> --provider-class sun.security.pkcs11.SunPKCS11 \ </span><span> --provider-arg ./provider.cfg \ </span><span> --ks-type PKCS11 \ </span><span> --min-sdk-version 24 \ </span><span> --max-sdk-version 34 \ </span><span> --in unsigned.apk \ </span><span> --out signed.apk </span><span> </span><span>Exception in thread "main" java.lang.IllegalAccessException: class com.android.apksigner.ApkSignerTool$ProviderInstallSpec cannot access class sun.security.pkcs11.SunPKCS11 (in module jdk.crypto.cryptoki) because module jdk.crypto.cryptoki does not export sun.security.pkcs11 to unnamed module @75640fdb </span><span> at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:392) </span><span> at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:674) </span><span> at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:489) </span><span> at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480) </span><span> at com.android.apksigner.ApkSignerTool$ProviderInstallSpec.installProvider(ApkSignerTool.java:1233) </span><span> at com.android.apksigner.ApkSignerTool$ProviderInstallSpec.access$200(ApkSignerTool.java:1201) </span><span> at com.android.apksigner.ApkSignerTool.sign(ApkSignerTool.java:343) </span><span> at com.android.apksigner.ApkSignerTool.main(ApkSignerTool.java:92) </span></code></pre> <p>It may only be an issue because I use NixOS, as I cannot find another instance of someone else having this issue. But I still want my APK signed using the key-pair on my <em>YubiKey</em>. After a lot of trial and error, I found out that I can force Java to export certain classes using the <code>--add-exports</code> flag. Since <code>apksigner</code> complained that the security classes are not exported to its unnamed class, I had to specify <code>--add-exports sun.security.pkcs11.SunPKCS11=ALL-UNNAMED</code>.</p> <h2 id="my-setup">My Setup</h2> <p>TL;DR: I wrapped this entire setup (minus the Gradle config as that's a per-project thing) into a fancy <a href="https://codeberg.org/PapaTutuWawa/bits-and-bytes/src/branch/master/src/flutter/build.sh">script</a>.</p> <p>My provider configuration for the signature is exactly like the one provided in <a href="https://geoffreymetais.github.io/code/key-signing/#set-up-your-own-management-key">previously mentioned blog post</a>, with the difference that I cannot use the specified path to the <code>opensc-pkcs11.so</code> as I am on NixOS, where such paths are not used. So in my setup, I either use the Nix REPL to build the derivation for <code>opensc</code> and then use its <code>lib/opensc-pkcs11.so</code> path (<code>/nix/store/h2bn9iz4zqzmkmmjw9b43v30vhgillw4-opensc-0.22.0</code> in this case) for testing or, as used in <a href="https://codeberg.org/PapaTutuWawa/anitrack/src/branch/master/flake.nix">AniTrack</a>, let Nix figure out the path by building the config file from within my Nix Flake:</p> <pre data-lang="nix" style="background-color:#2b303b;color:#c0c5ce;" class="language-nix "><code class="language-nix" data-lang="nix"><span>{ </span><span> </span><span style="color:#65737e;"># ... </span><span> </span><span style="color:#d08770;">providerArg </span><span>= </span><span style="color:#bf616a;">pkgs</span><span>.</span><span style="color:#bf616a;">writeText </span><span>"</span><span style="color:#a3be8c;">provider-arg.cfg</span><span>" '' </span><span style="color:#a3be8c;"> name = OpenSC-PKCS11 </span><span style="color:#a3be8c;"> description = SunPKCS11 via OpenSC </span><span style="color:#a3be8c;"> library = </span><span style="font-style:italic;color:#ab7967;">${</span><span style="font-style:italic;color:#bf616a;">pkgs</span><span style="font-style:italic;color:#c0c5ce;">.</span><span style="font-style:italic;color:#bf616a;">opensc</span><span style="font-style:italic;color:#ab7967;">}</span><span style="color:#a3be8c;">/lib/opensc-pkcs11.so </span><span style="color:#a3be8c;"> slotListIndex = 0 </span><span style="color:#a3be8c;"> </span><span>''; </span><span> </span><span style="color:#65737e;"># ... </span><span>} </span></code></pre> <p>Next, to force Java to export the <code>sun.security.pkcs11.SunPKCS11</code> class to <code>apksigner</code>'s unnamed class, I added <code>--add-exports sun.security.pkcs11.SunPKCS11</code> to the Java command line. There are two ways of doing this:</p> <ol> <li>Since <code>apksigner</code> is just a wrapper script around calling <code>apksigner.jar</code>, we could patch the wrapper script to include this parameter.</li> <li>Use the wrapper script's built-in mechanism to pass arguments to the <code>java</code> command.</li> </ol> <p>While option 1 would work, it would require, in my case, to override the derivation that builds my Android SDK environment, which I am not that fond of. Using <code>apksigner</code>'s way of specifying Java arguments (<code>-J</code>) is much easier. However, there is a little trick to it: When you pass <code>-Jsomething</code> to <code>apksigner</code>, the wrapper scripts transforms it to <code>java -something</code>. As such, we cannot pass <code>-Jadd-exports sun.security.pkcs11.SunPKCS11</code> because it would get transformed to <code>java -add-exports sun.security.[...]</code>, which is not what we want. To work around this, I quote the entire parameter to trick Bash into thinking that I'm passing a single argument: <code>-J"-add-exports sun.security.pkcs11.SunPKCS11"</code>. This makes the wrapper append <code>--add-exports sun.security.pkcs11.SunPKCS11</code> to the Java command line, ultimately allowing me to sign unsigned Android APKs with the key-pair on my <em>YubiKey</em>.</p> <p>Since signing a signed APK makes little sense, we also need to tell Gradle to <em>not</em> sign the APK. In the case of Flutter apps, I modified the <code>android/app/build.gradle</code> file to use a null signing config:</p> <pre data-lang="gradle" style="background-color:#2b303b;color:#c0c5ce;" class="language-gradle "><code class="language-gradle" data-lang="gradle"><span>android { </span><span> </span><span style="color:#65737e;">// ... </span><span> buildTypes { </span><span> release { </span><span> </span><span style="color:#65737e;">// This prevents Gradle from signing release builds. </span><span> </span><span style="color:#65737e;">// I don't care what happens to debug builds as I'm not distributing them. </span><span> signingConfig </span><span style="color:#d08770;">null </span><span> } </span><span> } </span><span>} </span></code></pre> </article> <!-- Common post footer --> <div class="mt-6"> <span class="prose lg:prose-lg text-md text-white"> If you have any questions or comments, then feel free to send me an email (Preferably with GPG encryption) to papatutuwawa [at] polynom.me or reach out to me on the Fediverse at <a href="https://social.polynom.me/papatutuwawa">@papatutuwawa@social.polynom.me</a>. </span> </div> </div> </div> </body> </html>