Do you use NFC HCE API in Android? Check your code twice.

Artem Gapchenko
The Startup
Published in
6 min readMay 9, 2020
Photo by Mika Baumeister on Unsplash

TLDR: because you can spoil NFC so badly that the user will have to reboot their device to be able to work with the NFC-related functionality of your app.

But first — a little bit of history on what HCE is and why it’s being used.

When you tap any NFC card reader with your smartphone, the data exchange starts. The thing called NFC controller picks up the data frames coming from the reader (essentially, these data frames are just byte arrays constructed following some protocol), it reroutes them to something and then answers back to the reader. This communication can span across a series of steps.

Now, the question is — what this aforementioned something is? There’re two options.

SE and HCE

First, your smartphone can be equipped with a so-called Secure Element (or SE, for short). That’s a chip that’s capable of communicating with the card reader on itself — at no point in time does it need to communicate with Android OS to perform its duties. It contains the tiny apps (sometimes called applets, but I also saw some people calling them cardlets), which can respond to the reader’s queries.

Communication via a Secure Element, the image is taken from the Android docs.

These SEs are tamper-resistant, so it’s tough to get any data from them. Also, since they do not communicate with OS (remember — NFC controller reroutes the data frames directly to SE, these frames do not go through the OS), it means that even on the device with the compromised OS the data exchange between the reader and the smartphone will be secure.

There is another way to handle the connection, though. And that is where HCE comes into play. HCE stands for Host Card Emulation — in a nutshell, this is a process of emulating the card by the code running on the host operating system (in our case, this is Android).

Communication via HCE, the image is also taken from the Android docs.

What you do to implement HCE in your code is you extend the class called HostApduService and override its method called processCommandApdu(byte[] commandApdu, Bundle extras). The commandApdu parameter contains the data frame I mentioned before — you pick it up, you create a response and send it back to the reader. The Google Pay app on your smartphone works just like that.

SE looks like a more secure thingy, why do we even use HCE then?

Good question! Since the code responsible for host card emulation runs in the host OS, this means that in theory the data exchange can be compromised (in practice, though, this solution is sufficiently secure, at least on non-rooted devices; otherwise I doubt that the Google Pay would even exist in a first place). A couple of years ago, I read an article that explained it in excellent detail (I can’t find it now, unfortunately), but mostly it all boils down to the conflict of mobile carriers with Google back in 2010–2012.

Before you can run your applets on the Secure Elements, you need to distribute them onto the SEs somehow and make sure these applets are not compromised. You also need to be able to update these applets from time to time, and the only possible way to do that securely, according to that article, was to deliver the applets via the mobile carriers.

And here’s when the conflict started — mobile carriers wanted to have their piece of the cake too, and they either refused to roll out the applets needed for a Google Pay app (which was called Google Wallet back in the day) onto the devices equipped with SEs or they set a price which was too high. The result was that Google decided that they needed their own solution for card emulation, which they could distribute along with the Android apps. So Google ended up adopting the Host Card Emulation mode, which was first implemented in the CyanogenMod OS.

That’s how we ended up having a solution that is not as secure as it could be (but is still better than not having any solution at all).

That’s all nice, but why do I need to be careful?

Oh, wow, I almost forgot that I wanted to discuss a small but important finding I had back in October. Sorry for that, I love history and couldn’t resist a temptation to give a short history lesson!

Ok, so back in October, I’ve been working on the implementation of the HostApduService, and I noticed something weird. At some point, I realized that there was no NFC communication at all; the system just wasn’t starting my implementation of the HostApduService. The attempts to turn NFC on and off weren’t helping; the only thing that helped was rebooting the device. The only good thing was that only my own app seemed to be affected — I validated it by paying for purchase via Google Pay and NFC-initiated payment has worked just fine.

Later that evening, I realized that while I was working on that part of the code, I’ve made a small mistake, which resulted in the unhandled exception being thrown in the processCommandApdu method. I decided to check my suspicion, and it turns out I was right — if your code throws an exception from that method, the OS can not restart your implementation of the HostApduService service anymore. If you look at the system logs, here’s what you are going to see in case the card emulation does not crash:

What we can see here is that the OS activates the host emulation, then starts a new instance of our HostApduService implementation, waits for it, binds to it when it’s ready, and then starts the data exchange. When the data exchange is done, the system deactivates the host emulation and unbinds from the service (I assume it also destroys it, but it’s not shown in the logs). If you tap the card reader with your smartphone again, you’ll see more or less the same logs.

But after the crash has already happened, the system logs start looking very different:

The system activates host emulation, it tries to bind to the service, but the service is still bound. The system keeps on waiting, and then it deactivates the emulation. And there is no coming back from that limbo state unless you reboot your device; what’s even worse — there is little to nothing you can do to mitigate it after the crash has happened, you can’t notify your user — hey, it looks like NFC does not work for you, please reboot the device. Also, I managed to reproduce it on all devices that I own, OS versions 6.0, 9.0, and 10.0, so the issue seems to affect a wide range of devices.

As far as I remember, I dug into the Android source codes trying to figure out if that’s something that I can tackle, and I ended up neck-deep in some native C++ code. Pretty soon, I realized that Google folks should have a much better knowledge of NFC intrinsics than I do, so I ended up taking another route:

  1. First, I filed an issue in the Google Issue Tracker. As far as I can see, this issue is blocked by another one, which is not visible to non-Google employees, so there is at least some work going on in that direction (you can upvote it, though, to speed things up possibly).
  2. And I also rechecked my own code very carefully to make sure that at no point in time does it throw the unhandled exceptions. It’s one thing to find such an issue when you’re a developer that sits comfortably in his chair looking at the logs. It’s entirely another thing when you want to pay for a ride or open the door or whatever is it that you do with your smartphone, and you find that nothing works, and you don’t know what to do.

That’s more or less all that I wanted to share with you today. The moral of that story is simple — no one is perfect, but sometimes two imperfections meet each other and produce a very troublesome result. Check your HostApduServices if you have them, and it’ll surely contribute to the overall user experience that your clients have daily.

Curious to get in touch? Send me an email or write a message here in the comments section! It’ll be interesting to hear your feedback.

--

--

Artem Gapchenko
The Startup

Android Contractor, husband, 📚🐛. Currently @ Kisi. tema@hey.com