Fixing my fingerprint reader on Linux by writing a driver for it

Fixing my fingerprint reader on Linux by writing a driver for it
Photo by George Prentzas / Unsplash

TL;DR: You can find the driver by clicking here. It is also available as an AUR package. For a tutorial on how to flash the firmware, please follow this guide's step 1. Also you will need version 1.92 of fprintd for this driver to work as the fork hasn't been updated to 1.94 yet.

Why I made my own driver

A few days ago I received a new work computer. As I use Manjaro Linux as my daily driver OS, it was important to me to choose something that will run it without any bigger issues. After doing my research, I settled on a Asus ROG Zephyrus G14 (2021). Basically everything it has to offer works fine under Linux, even the fancy LED cover is fully controllable. There is one noteworthy exception though: The fingerprint reader does not work under Linux. The particular fingerprint reader my machine has is a Goodix 521d. But this isn't an issue with this particular one: A quick search will reveal a whole list of unsupported fingerprint readers. While there are different reasons as to why they are not supported, for my reader there were multiple.

First of all its resolution is too small to properly work with the detection code used in the Linux library, libfprint,  responsible for fingerprint reading. While this can be worked around, there is a different issue regaring TLS communication with the device. The existing SSL library used in libfprint will not work with my reader and a second, different one would have to be introducted to the library. Last but not least, there is just no support from the vendor whatsoever. There is little to no documentation available on how to communicate with these readers.

lsusb output showing my fingerprint reader
The marked line shows the fingerprint reading device in my computer

Community Efforts

Since there is a substantial amount of users with unsupported readers of the same vendor, there has been a community effort to start working out on how to implement drivers for these devices. This is where I started my quest of getting a working driver for my reader.

After joining their discord server, I was presented with a long list of devices that are currently being tracked by the community. Tracked does not necessarily mean being worked on though, as there is too many readers and not enough programmers with respective devices around to do them all at the same time. However, for many readers there were working python scripts that are able to not only communicate with the device but also request an image from it. I was in luck, my reader had such a script as well. I had to tinker a bit with the script though to ultimately get a proper image off of my sensor.

Most development effort was focused on a different reader as they had made promising advancements towards a working driver. I survived whole 2 days of waiting before I decided that it was time to start development efforts myself. There was only two problems: First of all I have no clue about drivers or fingerprints and second I don't know how to program in C. But I figured its worth a shot anyway, there was not much to loose except a few hours of my life.

How I did it

As previously mentioned, a lot of development effort had been put into a different reader of the same vendor and there was a working python script available for my reader. The first thing I did was clone the fork of libfprint where the changes for the other reader had been made. I started comparing the libfprint implementation for that reader to what had been done in the python script for that reader to understand what I would need to change in order to make it work with my reader. So I created a copy of the existing reader's implementation and started to replace the static values of the old reader with the ones I found in the python script for my reader. Now, this was done fairly quickly and I went ahead and tested it. Absolutely nothing worked. Obviously, the devices are not identical and there was more to it than just copying over static values like configurations and shared keys.

I discovered that the protocol differs between the two readers in a few ways. For example, the bytes sent over to the device for PSK verification were not the same, despite them using the same protocol in python. So I had to expand the existing protocol implementation with the missing fields and voila, it failed again. There were not only fields missing, but their order was also incorrect. This meant that I had to shuffle the order of the fields to match the python script which finally allowed me to verify the PSK of the device. Yay!

The next big roadblock was getting the device to send me an image. While I definitely got a request to the device, and I definitely got something back from it, it came out as pure random data. I knew that the image data was supposed to be encrypted but every time I tried to decrypt the chunk of data I received, the TLS library would tell me that the data was not in fact valid TLS data. Again, a small difference between the other reader and mine was, that I would have to discard the first 9 bytes of my data chunk to get the valid TLS encrypted data to feed into the decryptor. And it took me far too long to realise that.

The third and last bigger roadblock was that I was not getting a clear image from the sensor. In fact, even after decryption it still looked like utter garbage. I checked everything: The image decoder which is needed to turn the image sent by the reader into an actual viewable picture, the decryption, every receiver the data went through. After many more hours I figured out that one of my configuration calls to the device were not working properly. There was something wrong in the protocol implementation that prevented my request from being encoded into bytes successfully and the reader never got the configuration I wanted it to have in the first place. Once I resolved this, I was able to get pictures from my reader in libfprint which meant I was technically able to enroll and verify fingers.

But there is more. Since the fingerprint reader is so small, libfprint's way of verifying fingerprints just cannot find enough details to compare against and simply fails the verification everytime, even if the finger wasn't moved since the enrollment. To counter this, instead of taking one shot of the finger, I made the program take 10 shots and stitch them together. Thats also how the swipe-style fingerprint readers work! After providing libfprint with a far bigger picture and therefore far mor details, I was finally able to enroll and verify fingerprints. Now, this method might not be the safest and there will definitely be work done for a better one. So I wouldn't recommend relying this for top secrecy, although when enrolled properly I only managed to get a false positive once with one of my other fingers.

Conclusion

I never took on this project because I had particular knowledge in fingerprint scanning, drivers or even C itself. This was sort of a fun coding challenge to myself, trying out something new I had never done before and no clue about. I definitely enjoyed the 2 days journey of writing this driver and experimenting with it. It was painful sometimes and many stupid mistakes, but getting to the stage where you can unlock your machine with a driver you (mostly) wrote is very rewarding. If you got a Goodix reader that isn't working, join the discord and check out what work has been done for your device. And if you got some spare time on your hand, you might just try to fix it yourself.

Working verification of my finger with the new driver