Introducing dreiAttest — fraud protection without disrupting the user experience

dreiAttest unifies Apple’s DeviceCheck and Google’s SafteyNet into a single, easy-to-use framework for restricting API Access.

Laila Becker
dreipol

--

Sidney Widmer was an additional co-author. Visit www.dreipol.ch to learn more about us.

Pick an app on your phone. Maybe it’s an app for finding the best restaurants in your neighborhood, and it uses a web-service to let you post reviews and read reviews by other users. And therein lies the problem: anyone with an internet connection can access your web-service. Of course, this is kind of the point — you want users all over the world to be able to use your app — but it also means that anyone can run a bot on their laptop to generate countless fake reviews in just a few seconds.

«I’m not a robot»

The industry standard for dealing with this kind of issue is to use reCAPTCHA (the checkboxes you see on many websites requiring you to confirm that you’re not a robot). However, these annoy your legitimate users and barely offer any protection at all since, for only a couple of dollars, an attacker can hire a click-farm to solve reCAPTCHAs for them.

If you’re a mobile developer, you might be thinking right now: «This would all be so much easier if I could guarantee that the service is only accessed using my app, where I at least have some control over what the user can and can’t do.» Well, that’s exactly what Apple’s DeviceCheck and Google’s SafetyNet are for.

Enter DeviceCheck and SafetyNet

The foundation of both of these frameworks is a mechanism for verifying that a request to your service was generated by a legitimate instance of your app, running on a genuine device. This happens without the input of the user and, thus, without disruption of the user experience. On top of this foundation, the platform vendors have implemented a set of additional features such as a risk metric for identifying devices that switch accounts a lot (e.g. because they want to avoid rate-limiting or because you are closing fraudulent accounts) or devices that are rooted. With DeviceCheck it is also possible to associate two bits of information with a device and this information is persisted when your app is reinstalled or even when the device is reset and iOS is reinstalled. You can use this to e.g. permanently block a device from accessing your service.

Of course, fraudulent reviews are just one of many use-cases for such a technology: maybe you have a free trial of some premium feature in your app, and you want to prevent users from just creating a new account to use the trial over and over again. Maybe you want to prevent players with a version of your game that is modified to include cheats from joining multiplayer games. Maybe the user can download copy-righted material using your app, and you want to prevent them from downloading it to another device where it can be more easily accessed and redistributed. Maybe…

There is a broad spectrum of potential applications for these frameworks. One thing you should keep in mind, though, is that these frameworks cannot guarantee the integrity of the operating system itself. While it may create an additional obstacle, a sufficiently sophisticated attacker on a jailbroken device will ultimately be able to forge requests that look like they are coming from your app. Even when using these frameworks you should, therefore, still authenticate your users for every request and take every additional precaution you otherwise would.

Introducing dreiAttest

As you might expect, there are some similarities but also many differences in how Apple’s DeviceCheck and Google’s SafetyNet frameworks are implemented. For this reason, we have developed dreiAttest, a collection of open-source libraries that provides a unified interface for both platforms. It consists of four parts:

dreiAttest is designed to act as middleware, thus allowing you to add it to your project without or with only minimal modifications to your networking logic. dreiAttest itself implements only the basic verification that requests come from a genuine installation of your app. As described above, depending on the platform we are on, the underlying frameworks offer additional safeguards. dreiAttest supports server-side plugins, allowing you to use these safeguards to implement and enforce stricter security policies, such as permanently blocking specific devices.

Usage

To demonstrate how easy it is to use dreiAttest we will integrate it into the sample app we introduced in our blog post about our Kotlin Multiplatform architecture.

First, we need to obtain an AttestationProvider which dreiAttest uses to wrap the attestation service of the platform it is currently running on. We will add a field to our AppConfiguration to hold the AttestationProvider and initialize the provider in the application’s onCreate on Android:

similarly, we use the AppleAttestationProvider on iOS and initialize it in the app delegate’s application(_:didFinishLaunchingWithOptions:) method:

All that is left to do on the app side now is to go into our service factory and configure and install the dreiAttest ktor feature whenever we create a new network service:

And with that, we have completed the setup for our app. You can find more information on configuration options and how to set up a bypass during development in the GitHub repository. Setting up the iOS only library is similarly simple. You can find more information on its GitHub page.

Setting up the server

On the server, the setup is also done in just a few minutes. Install the dreiattest-django package, register the django app and decorate any of your views with the signature_required decorator. Once decorated, the view will only render if the request contains a valid assertion.

The package also adds two endpoints to your application, one for generating nonces and one for storing the public key of validated attestations. Thanks to the modular approach, you could also write your own middleware to require a signature for every route or only specific subgroups.

Technical overview

In this section, we give a rough overview of how dreiAttest works «under the hood». If you are interested in more details, we encourage you to read our whitepaper.

dreiAttest is largely modeled after how Apple’s Device Check is supposed to be implemented: whenever a request is made, dreiAttest first checks whether it matches the configured base address. If that is the case, it uses a private key it previously registered with the server to sign the request. This ensures a minimal performance overhead for most requests since signing happens on devices without the need to contact Apple’s / Google’s attestation servers and most modern devices have hardware acceleration to make the signing itself as efficient as possible. It should be noted that replay attacks are possible. We recommend using SSL combined with certificate pinning to mitigate the risk of such attacks. In the future, we plan on making additional security options configurable to avoid replay attacks. Enabling these options will, however, likely reduce the performance of dreiAttest.

If no key is registered or the server requests that a device re-registers, the device requests a nonce from the server and generates a new key pair (stored in a hardware key store whenever available). It then constructs a registration request and calculates a cryptographic hash over that request. This hash is passed to the platform’s attestation provider and an attestation is generated. At this point, Apple / Google has essentially signed our key registration request as coming from a genuine installation of our app. We append the attestation to our request and send it to the dreiAttest server. The last step is for the server to verify the attestation and store the public key, so it can use it to verify future requests signed by that device.

Conclusion

With dreiAttest, we have introduced a framework that is effective at protecting your services without disrupting the user experience of your app. It is easy to set up and can be effortlessly integrated into new and existing projects alike. In the future, we plan to implement support for additional attestation services and add a set of prebuilt server plugins. These could help you with auditing access, block specific devices or implement more granular attestation checks. As these features become available, this blog post will be updated.

. . .

Feel free to like this post, share it, follow us or dreipol on Social Media:

medium.com/@nils.becker_40934
medium.com/@sidneywidmer
twitter.com/sidneywidmer
twitter.com/dreipol
www.dreipol.ch

Postscript: Comparison to Firebase

Earlier this year, Google launched the first beta of its Firebase App Check feature. While it is the only feasible solution for protecting the resources you host on Firebase, we believe that dreiAttest offers a number of advantages over Firebase when it comes to protecting resources you host yourself:

  • Easy Integration: Firebase App Check requires you to adjust your network logic to obtain a token, include it in your request and verify the token on the server. With dreiAttest there are only a few lines of setup required at app launch and all your requests are automatically protected.
  • Flexible Security Policies: through our plugin architecture, you can enforce a wide variety of security policies and even implement your own policies that take user data into account. Firebase only offers a limited set of settings to set your security policy.
  • Security: dreiAttest signs every request to your server, it is therefore not possible to intercept a request and modify it. A Firebase App Check token is independent of the request it belongs to and continues to be valid at least for a little while after an attacker intercepts a request to your server.
  • Out-of-the-box Support for Kotlin Multiplatform: Firebase does not offer a Multiplatform package. Obtaining a token on iOS in a Multiplatform project, therefore, requires adding a dependency on the iOS library and calling it. This is quite tedious.
  • Self-hosting: For legal or privacy-related reasons, you might not want to rely on a third-party service such as Firebase. dreiAttest is hosted locally on your own servers.
  • Performance: Before the server can process a request, it first needs to make a call to a Firebase server to verify the validity of the token. Verification of dreiAttest signatures is done entirely locally.

--

--