Skip to main content

Signal Desktop on Fedora

Signal Desktop has been released some time ago and while a native application may have its advantages, it also needs time and effort until it will be available for other platforms.

Binary installation

The install routine for "Debian-based Linux" instructs us to do the following:

 > curl -s https://updates.signal.org/desktop/apt/keys.asc | sudo apt-key add -
 > $ echo "deb [arch=amd64] https://updates.signal.org/desktop/apt xenial main" | \
 >   sudo tee -a /etc/apt/sources.list.d/signal-xenial.list
 > $ sudo apt update && sudo apt install signal-desktop
With only a Fedora distribution around, we could of course use alien to install the package, but:

$ sudo dnf install alien
[...]
Transaction Summary
===============================
Install  70 Packages
....let's not and instead do this manually. Luckily, their download directory structure adhers to the Debian Repository Format, so with a bit of fiddling we can produce the necessary URLs:

$ curl -sLO https://updates.signal.org/desktop/apt/dists/xenial/InRelease
$ gpg --recv-keys D980A17457F6FB06

$ gpg --verify InRelease
gpg: Signature made Wed 20 Dec 2017 11:43:08 AM PST
gpg:                using RSA key D980A17457F6FB06
gpg: Good signature from "Open Whisper Systems " [unknown]
Primary key fingerprint: DBA3 6B51 81D0 C816 F630  E889 D980 A174 57F6 FB06
The InRelease is signed and contains checksums to the Packages file:

$ curl -sLO https://updates.signal.org/desktop/apt/dists/xenial/main/binary-amd64/Packages
$ sha256sum Packages 
121c0e796cef911240bb39b6d5ebed747202e9be8261808ecbf3fc4641da9e7b  Packages

$ grep 121c0e796cef911240bb39b6d5ebed747202e9be8261808ecbf3fc4641da9e7b InRelease 
121c0e796cef911240bb39b6d5ebed747202e9be8261808ecbf3fc4641da9e7b     2578 main/binary-amd64/Packages
Let's look at the Packages file for the actual packages available for download:

$ egrep '^(Package|SHA256|File|$)' Packages 
Package: signal-desktop
Filename: pool/main/s/signal-desktop/signal-desktop_1.1.0_amd64.deb
SHA256: 74ee408fa5c7047b1f2a7faa2a9fe0d5947f7f960bd7776636705af69a6b1eec

Package: signal-desktop
Filename: pool/main/s/signal-desktop/signal-desktop_1.0.41_amd64.deb
SHA256: 9cf87647e21bbe0c1b81e66f88832fe2ec7e868bf594413eb96f0bf3633a3f25

Package: signal-desktop-beta
Filename: pool/main/s/signal-desktop-beta/signal-desktop-beta_1.1.0-beta.6_amd64.deb
SHA256: a38eb35001618019affba7df4e54ccbb36581d232876e0f1af9622970b38aa12
We decide to use the signal-desktop-beta and continue:

$ curl -sLO https://updates.signal.org/desktop/apt/pool/main/s/signal-desktop-beta/signal-desktop-beta_1.1.0-beta.6_amd64.deb
$ sha256sum signal-desktop-beta_1.1.0-beta.6_amd64.deb 
a38eb35001618019affba7df4e54ccbb36581d232876e0f1af9622970b38aa12  signal-desktop-beta_1.1.0-beta.6_amd64.deb
To extract the package, we'll need the dpkg package:

$ sudo dnf install dpkg
$ dpkg -x signal-desktop-beta_1.1.0-beta.6_amd64.deb deb
Check if all libraries are installed:

$ ldd deb/opt/Signal\ Beta/signal-desktop-beta | grep not
Looks good - let's "install" the package in /opt now:
sudo mv deb/opt/Signal\ Beta /opt/
sudo chown -R root:root /opt/Signal\ Beta/
sudo ln -s /opt/Signal\ Beta/signal-desktop-beta /usr/local/bin/signal-desktop-beta
Create desktop shortcut and icons:

mv deb/usr/share/applications/signal-desktop-beta.desktop ~/.local/share/applications/signal-desktop-beta.desktop
rsync -av deb/usr/share/icons/hicolor/ ~/.local/share/icons/hicolor/
The .desktop file should contain something like this:

$ cat  ~/.local/share/applications/signal-desktop-beta.desktop
[Desktop Entry]
Name=Signal Desktop Beta
Comment=Private messaging from your desktop
Exec="/opt/Signal Beta/signal-desktop-beta" %U
Terminal=false
Type=Application
Icon=signal-desktop-beta
With all that in place, Signal Desktop Beta should be ready to go. Don't forget to migrate the data from the old installation!

Build from source

Building from source may need a ton of dependencies, so it may or may not be desirable to install all that on a desktop system. The short version of the install routine would be:

git clone https://github.com/WhisperSystems/Signal-Desktop.git Signal-Desktop-git
cd Signal-Desktop-git

yarn config set cache-folder /usr/local/src/tmp/yarn/
npm config set cache /usr/local/src/tmp/npm/
TMPDIR=/usr/local/src/tmp/ npm install
So far, so good, but then there's some grunt breakage:

$ node_modules/grunt-cli/bin/grunt 
Loading "sass.js" tasks...ERROR
>> Error: ENOENT: no such file or directory, scandir '../node_modules/node-sass/vendor'
Loading "sass.js" tasks...ERROR
>> Error: ENOENT: no such file or directory, scandir '../node_modules/node-sass/vendor'
Warning: Task "sass" not found. Use --force to continue.

Aborted due to warnings.
...TBD :-\

Compression benchmarks 2017

As I had to send a disk based backup to another machine on a local network, I wanted to compress the backup data before sending it over the wire, of course. And as the last benchmark has been done one year ago, it was time for another one anyway :-)

$ ls -hgo disk.img
 -rw------- 1 861M Oct 25 23:02 disk.img

$ ./compress-test.sh -n 3 -f disk.img | tee foo.out

$ ./compress-test.sh -r foo.out
### Fastest compressor:
### pzstd/1c:      .75 seconds / 61.900% smaller 
### pigz/1c:      3.25 seconds / 60.100% smaller 
### zstd/1c:      3.25 seconds / 62.000% smaller 
### bro/1c:       3.50 seconds / 59.500% smaller 
### pzstd/9c:     6.00 seconds / 67.700% smaller 
### pigz/9c:     11.00 seconds / 63.800% smaller 
### gzip/1c:     12.00 seconds / 59.900% smaller 
### zstd/9c:     17.50 seconds / 68.000% smaller 
### pbzip2/9c:   18.00 seconds / 67.400% smaller 
### pbzip2/1c:   27.50 seconds / 64.900% smaller 
### lzma/1c:     53.50 seconds / 68.700% smaller 
### xz/1c:       54.75 seconds / 68.700% smaller 
### gzip/9c:     65.00 seconds / 63.700% smaller 
### bzip2/1c:    66.00 seconds / 64.900% smaller 
### bzip2/9c:    66.00 seconds / 67.500% smaller 
### bro/9c:      83.25 seconds / 70.500% smaller 
### lzma/9c:    240.50 seconds / 76.700% smaller 
### xz/9c:      243.75 seconds / 76.700% smaller 

### Smallest size:
### xz/9c:      243.75 seconds / 76.700% smaller 
### lzma/9c:    240.50 seconds / 76.700% smaller 
### bro/9c:      83.25 seconds / 70.500% smaller 
### xz/1c:       54.75 seconds / 68.700% smaller 
### lzma/1c:     53.50 seconds / 68.700% smaller 
### zstd/9c:     17.50 seconds / 68.000% smaller 
### pzstd/9c:     6.00 seconds / 67.700% smaller 
### bzip2/9c:    66.00 seconds / 67.500% smaller 
### pbzip2/9c:   18.00 seconds / 67.400% smaller 
### pbzip2/1c:   27.50 seconds / 64.900% smaller 
### bzip2/1c:    66.00 seconds / 64.900% smaller 
### pigz/9c:     11.00 seconds / 63.800% smaller 
### gzip/9c:     65.00 seconds / 63.700% smaller 
### zstd/1c:      3.25 seconds / 62.000% smaller 
### pzstd/1c:      .75 seconds / 61.900% smaller 
### pigz/1c:      3.25 seconds / 60.100% smaller 
### gzip/1c:     12.00 seconds / 59.900% smaller 
### bro/1c:       3.50 seconds / 59.500% smaller 

### Fastest decompressor:
### pzstd/dc:      .25 seconds
### zstd/dc:      1.00 seconds
### bro/dc:       2.25 seconds
### pigz/dc:      2.75 seconds
### gzip/dc:      4.25 seconds
### pbzip2/dc:    5.00 seconds
### lzma/dc:     14.25 seconds
### xz/dc:       15.25 seconds
### bzip2/dc:    20.75 seconds
Now the only thing left is to extend our little benchmark scripts to actually compare these to last year's results...

letsencrypt.sh

So, while this site cannot be equipped with any kind of TLS certificates (don't ask), I'm using Let's Encrypt certificates for some other web sites. But as much as I like everything the EFF does, I despise their official LE client, for apparent reasons:

$ sudo apt-get install certbot
[....]
The following NEW packages will be installed:
  certbot python-acme python-certbot python-cffi-backend python-configargparse python-configobj python-cryptography python-enum34 python-funcsigs python-idna python-ipaddress
  python-mock python-openssl python-parsedatetime python-pbr python-pyasn1 python-requests python-rfc3339 python-six python-tz python-urllib3 python-zope.component
  python-zope.event python-zope.hookable python-zope.interface
0 upgraded, 25 newly installed, 0 to remove and 3 not upgraded.
No, thank you :-\ Luckily, their ACME protocol allows for many more client options to choose from. After some experiments, I almost settled for letsencrypt.sh, but ran into unfixed bugs and ended up with a fork of the same. With that, I wanted to cover two use cases:

Local server

In this scenario, the letsencrypt.sh client is requesting (and validating) certificates for the same machine it's running on. This is also the machine where our account key resides. The work flow is basically:
## Needs to be done only once:
$ letsencrypt.sh register -a letsencrypt-account-key.pem \
      -e webmaster@example.org

$ umask 0022
$ letsencrypt.sh sign -a letsencrypt-account-key.pem \
      -k letsencrypt-example-key.pem \
      -w /var/www/.well-known/acme-challenge/ \
      -c letsencrypt-$(date -I)-example-cert.pem www.example.org mail.example.org
The well-known path should be writeable by the user executing the letsencrypt.sh script and readable by the webserver. That way, we don't have to play funky games with our webserver configuration, trying to generate the responses dynamically but instead we (temporarily) create actual files in that directory to be validated in the process. This may not work when renewing certificates for different domains, though.

We may need the full certificate chain (and some DH parameters too), so let's concatenate them altogether:
$ cat letsencrypt-$(date -I)-example-cert.pem \
      letsencrypt-$(date -I)-example-cert.pem_chain \
      dhparams-2048.pem \
      > letsencrypt-$(date -I)-example-cert-combined.pem
The resulting file can then be installed as SSLCertificateFile or ssl_certificate or ssl_cert or whatever service is in use here.

Remote server

While the letsencrypt.sh client seems small enough, we still don't want to install it on every server that needs a certificate. Instead, we'll use letsencrypt.sh to issue certificates for a remote machine. However, as the certificates are domain-validated, we need a way to transfer the validation token to the remote server. Luckily, this version of letsencrypt.sh is able to do just that:
$ letsencrypt.sh sign -a letsencrypt-account-key.pem \
      -k letsencrypt-foobar-key.pem \
      -P /usr/local/bin/push-response-ssh \
      -c letsencrypt-$(date -I)-foobar-cert.pem foobar.net www.foobar.net
Here, a hook script is transferring the token to the remote server (configured in the same script). On the remote side, another hook will read the validation token from stdin and install it in its own well-known location (TOKEN_DIR). This can all be configured with SSH key authentication:
foobar$ cat ~www-data/.ssh/authorized_keys 
command="/usr/local/sbin/push-response-ssh-remote" ssh-ed25519 AAAAC3[...] admin@local

foobar$ grep ^TOKEN_DIR /usr/local/sbin/push-response-ssh-remote 
TOKEN_DIR="/var/www/.well-known/acme-challenge"
The resulting certificate is still installed locally and needs to be transferred to the remote side. Since we configured the remote www-data account to only allow the hook script to execute, we have adjusted the same somewhat to allow the certificate to be installed by the same user. Since we're now abusing the original hook script (and multiple command directives for a single key are not supported), our deployment command looks somewhat convoluted:
$ cat letsencrypt-foobar-key.pem \
      letsencrypt-$(date -I)-foobar-cert.pem{,_chain} | \
      ssh -i ~/.ssh/letsencrypt-key www-data@foobar.net \
      installkey \
      aol.com \
      $(openssl rand -hex 32 | cut -c-43) \
      $(letsencrypt.sh thumbprint -a letsencrypt-account-key.pem | awk '{print $NF}')
So, the new installkey parameter tells the remote hook script what to do. The aol.com and the random value are just place holders for a valid domain name resp. something that looks like a validation token. This is all because push-response-ssh-remote expects all these things. The deployment would be much easier if we 1) used a different user or key or 2) re-write the remote hook to allow for a simpler deployment :-)

With that in place, the certificate for the remote side has been saved to whatever is configured in push-response-ssh-remote and can now be used in the respective services. Yay! \o/