Skip to content

Code signing for Windows RT with open source tools

Paul McArrow edited this page Feb 1, 2023 · 7 revisions

Update (January 31, 2023)

Since commit 29e6b0cb30f16cb825e5dd9a7d760ef3efb9d141 (pull request 22) generation of self-signing certificates and signing of target code is automatic. Timestamping is implemented with a command line OpenSSL call (openssl ts -reply reading from stdin and writing to stdout). A fork of osslsigncode (branch short-circuit and/or tag 2.5-cmd) with command line TSA support is retrieved and built for the host configuration. The snakeoil component (similar to the alike-named package found in many Linux distros) is responsible for generating the private key, creating the CSR (certificate signing request) and approving it twice into two distinct certificates with codeSigning and timeStamping extended purposes. All installed target *.exe and *.dll binaries are thus signed, including the MinGW runtime itself (provided by llvm-mingw).

To sign an arbitrary PE32 executable manually, run ./usr/bin/selfsign.sh <binary-path> from the MXE root anytime after snakeoil is built.

The following articles have been used in preparation of this solution:

See the below article for steps to sign PE32 code the original and manual way (e.g. if you must use original Windows SDK tools or their unmodified open source equivalents without Internet access).


Why sign?

While there are exploits that allow running arbitrary (including unsigned) code on Windows RT, they are relatively intrusive. Compared to them, the testsigning exploit can be applied irrespectively of the OS version, secure boot state and other exploits installed. It requires, however, that every binary (exe and dll) being loaded contained a digital signature. The trust chain does not matter (the certificate used to sign the code can be self-signed); however, a valid secure timestamp must be present.

How to sign?

While unsigned MXE targets can be build entirely on Linux without access to Windows systems, code signing typically involves running signtool.exe, a closed-source Microsoft-provided binary. The process also requires accessing a digital timestamp service, involving a network connection and a third party.

It is possible, however, to utilize alternative tools to render the build process entirely self-contained.

Prerequisites

The following steps apply to Ubuntu 20.04 "Focal Fossa". The optional steps to monitor the timestamp server with a web browser are loosely specific to WSL2; the keyword, however, is "optional".

Setup

Obtain a certificate

If you already have a code signing certificate issued by a trusted authority, skip this chapter. (Keep in mind, however, that "official" certificates tend to die young, while self-signed certificates can have expiration dates millennia away.)

Otherwise, make sure OpenSSL is installed:

sudo apt-get install openssl

Then run:

openssl req -x509 -nodes -days 365000 -newkey rsa:2048 -keyout mxe.key -out mxe.crt

You will be asked for identification information (e.g. your country, state, company and contacts). Enter something that isn't likely to scare your eventual users. Now you have a certificate and a private key.

Before leaving the directory, pack the certificate and the key into a PKCS#12 store:

openssl pkcs12 -export -name Rita -in mxe.crt -inkey mxe.key -out mxe.p12 -password pass:

This is the credential storage format that the timestamp server would expect.

Set up the secure timestamp server

Install a recent JDK:

sudo apt-get install openjdk-17-jdk-headless

Clone the TSA server (or download a source tarball). The version I tested was tagged v2.2.2.

git clone https://github.com/dnl50/tsa-server
cd tsa-server
git checkout v2.2.2

Create the application.properties text file with tsa.certificate.path pointing to the PKCS#12 store path, e.g. with the following command:

echo tsa.certificate.path=/home/mxe/Sign/mxe.p12 > application.properties

You needn't indicate the store password unless you indicated it earlier in openssl pkcs12.

The default port of the timestamp authority protocol is decimal 318, which is well below 1024. To avoid running the server as root, we can either change the port, or grant the java executable the required capability, or use authbind. More information can be found here. I went the manual way:

$ which java
/usr/bin/java
$ ls -l /usr/bin/java
lrwxrwxrwx 1 root root 22 Oct  9 18:06 /usr/bin/java -> /etc/alternatives/java
$ sudo setcap CAP_NET_BIND_SERVICE=+eip `which java`
Failed to set capabilities on file `/usr/bin/java' (Invalid argument)
The value of the capability argument is not permitted for a file. Or the file is not a regular (non-symlink) file

Okay, let me look further.

$ ls -l /etc/alternatives/java
lrwxrwxrwx 1 root root 43 Oct 22 01:03 /etc/alternatives/java -> /usr/lib/jvm/java-17-openjdk-amd64/bin/java
$ ls -l /usr/lib/jvm/java-17-openjdk-amd64/bin/java
-rwxr-xr-x 1 root root 14496 Jul 22 01:57 /usr/lib/jvm/java-17-openjdk-amd64/bin/java
$ sudo setcap CAP_NET_BIND_SERVICE=+eip /usr/lib/jvm/java-17-openjdk-amd64/bin/java
$

Success! Now run:

./gradlew
./gradlew tasks
./gradlew bootRun

to see the progressive deployment of Gradle and project parsing; or just do ./gradlew bootRun right away. The server is now running in the current terminal session. Launch another terminal (e.g. another terminal window) to build and run the signing client.

There are other open TSA implementations, such as El Bosso's, but they either require exotic SDKs/toolchains (C#, Go…) or are harder to configure.

Build the code signing tool

There are two open source alternatives to signtool.exe:

The latter is written in C++, builds with CMake and comes with clear installation instructions:

$ sudo apt update && sudo apt install cmake libssl-dev libcurl4-openssl-dev
$ mkdir ../build-osslsc
$ cd ../build-osslsc
$ cmake ../osslsigncode

Ouch. It wants CMake version 3.17 or above, and Ubuntu 20.04 only bundles 3.16.3. Apparently, editing CMakeLists.txt helps:

$ nano ../osslsigncode/CMakeLists.txt
$ head -n 3 ../osslsigncode/CMakeLists.txt
# required cmake version
cmake_minimum_required(VERSION 3.16)

$ cmake ../osslsigncode
$ make -j

Success. Looks like no animal was harmed by relaxing the minor version.

Now let's have fun.

Signing

Let's assume we have just built a relatively standalone target, and let it be lua.exe.

$ cd ~/Code/mxe
$ make lua
$ ~/Code/build-osslsc/osslsigncode sign -certs ~/Sign/mxe.crt -key ~/Sign/mxe.key -n Lua -i http://github.com/armdevvel -in usr/armv7-w64-mingw32/bin/lua.exe -out ~/Downloads/lua.exe -ts 127.0.0.1:318

Alternatively, it is possible to pass the entire credential store with -pkcs12 ~/Sign/mxe.p12 [-pass <pkcs12-password>].

Note that we have to indicate the timestamp server port explicitly. We could have changed it to 1318 and saved a trip.(

My ~/Downloads is symlinked to my host W10 Downloads folder. I copy the file to my Surface RT. No complaint about the signature, but it needs lua53.dll, and I use a similar command line to sign the dynamic library. It turns out to have been the only missing dependency; lua.exe runs now.

Monitoring (optional)

tsa-server exposes a web interface on port 8080 to view the signing history. If you are on WSL (or another VM), you have to look up the IP address of the guest OS:

sudo apt-get install net-tools # if necessary
ifconfig

The first lines of the output will be:

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.28.204.23  netmask 255.255.240.0  broadcast 172.28.207.255
        inet6 fe80::215:5dff:fec1:ead0  prefixlen 64  scopeid 0x20<link>

http://172.28.204.23:8080 will be the web interface front page, and http://172.28.204.23:8080/web/history the signing history log.

Alternatives

.NET

At some point we may provide Mono build instructions for both tools. File an issue if you are interested in having them sooner than later.