Trusted Entitlements: Making MiTM piracy a thing of the past

Safeguarding IAPs and premium features with cryptography and RevenueCat’s latest security feature

Trusted Entitlements: Making MiTM attacks a thing of the past
Nacho Soto
Toni Rico

Nacho Soto, Toni Rico

Published

In the past, cracking in-app purchases (IAP) was mostly relegated to jailbroken devices. But things have changed. Since iOS 12.2, Apple has made it remarkably easy to install on-device proxies, opening the door for machine-in-the-middle (MiTM) piracy without requiring a jailbreak. This development has given rise to an increasing number of attackers targeting major apps, including those powered by RevenueCat.

With the advent of these unauthorized access methods, there’s been an observable rise in machine-in-the-middle (MiTM) piracy on apps, affecting even robust systems like RevenueCat. These attacks expose a key vulnerability in apps that utilize backend-side receipt validation, including ours.

MiTM attacks operate by intercepting and altering requests to our API. This allows attackers to gain unauthorized access to paid features without making actual purchases. Our system, like many others, has been vulnerable to these types of intrusions. That is, until now.

We’ve taken these threats seriously and in response, have introduced Trusted Entitlements, a powerful new feature in the latest versions of our SDKs, to address this very problem. This blog post will walk you through what Trusted Entitlements are, how they work, how you can enable them in your app, and will provide a technical overview of the signature creation process in the backend.

Trusted Entitlements in our SDKs

Trusted Entitlements have now been integrated into our SDKs:

  • iOS: 4.25.0+
  • Android: 6.6.0+
  • Hybrids: Coming soon

Implementation

Trusted Entitlements uses public key cryptography to secure your app. RevenueCat’s backend signs the responses using a private key, and the SDKs verify these signatures using a corresponding public key. This approach guarantees that only the party with the private key (RevenueCat) can create valid signatures, and that responses have not been modified.

To enable this feature, developers need to opt-in when setting up the SDK. There are three possible modes (enforced is not available yet, but coming soon):

1enum EntitlementVerificationMode {
2    /// The SDK will not perform any entitlement verification. This is the default behavior.
3    case disabled
4    /// Enable entitlement verification.
5    /// If verification fails, this will be indicated with ``VerificationResult/failed``
6    /// but parsing will not fail.
7    /// This can be useful if you want to handle validation failures but still grant access.
8    case informational
9    /// Enable entitlement verification.
10    /// If verification fails when fetching ``CustomerInfo`` and/or ``EntitlementInfos``
11    /// ``ErrorCode/signatureVerificationFailed`` will be thrown.
12    /// This is coming soon. In this mode the SDK takes care of all the validation for you
13    /// so you don't have to do anything. Hijack attempts will automatically return an error 
14    /// with a specific error code you can use to surface the information to a user.
15    case enforced

Then initialize the SDK with the desired mode.

iOS:

1let purchases = Purchases.configure(
2    with: .builder(withAPIKey: apiKey)
3        .with(entitlementVerificationMode: .informational)
4        .build()
5)

Android:

1fun configureRevenueCat() {
2    Purchases.configure(
3        PurchasesConfiguration.Builder(context, apiKey)
4.entitlementVerificationMode(EntitlementVerificationMode.Informational)
5        	.build()
6    )
7}

Disabled

This is the same behavior our SDKs have had until now. We won’t perform any cryptographic verification in the requests to our backend. This is the default.

Informational

With this mode, you can choose how to handle verification errors. Rather than block users, as with Enforced below, Informational allows you to create custom experiences or tracking. EntitlementInfo has a new VerificationResult property with four possible cases:

1public enum VerificationResult: Int {
2    /// No verification was done.
3    /// This can happen for multiple reasons:
4    ///  1. Verification is not enabled in ``Configuration``
5    ///  2. Verification can't be performed prior to iOS 13.0
6    case notRequested = 0
7    /// Entitlements were verified with our server.
8    case verified = 1
9    /// Entitlement verification failed, possibly due to a MiTM attack.
10    case failed = 2    
11    /// Entitlements were created and verified on device. This may happen in the unlikely case our servers are down.
12    case verifiedOnDevice = 3
13

Enforced (coming soon)

With this mode, RevenueCat takes care of all the validation for you so you don’t have to do anything. Hijack attempts will automatically return an error with a new ErrorCode.signatureVerificationFailed you can use to surface the information to a user, but your existing error handling should still work

You can find the full details on implementation in our documentation.

Signature generation

The signature creation process in the backend involves several key components:

  • Salt: A unique, secret value used to increase the complexity of the signature
  • API key: The public api key provided by RevenueCat
  • URL: The path of the request
  • Random nonce: A one-time-use random number generated by the SDK for each request
  • Request time: The timestamp when the request was made
  • Response body: The content of the backend response
  • Intermediate key: Key generated and stored in the backend with an expiration date attached. We use this key to sign the above components.
  • Private root key: The intermediate keys are signed using the root private key

These components are combined to generate a unique and secure signature for each response. The SDK then uses the public root key to verify the intermediate key signature and its expiration date, then uses that intermediate key to verify the response of the other components ensuring the authenticity of the backend’s response.

Why not use SSL pinning?

SSL pinning is a popular technique to prevent MITM attacks. It does this by hardcoding the SSL/TLS certificate’s public key into the app or device. Then, it uses that key to verify the response comes from the source you expect it to.

SSL pinning was considered as a potential solution to prevent MITM attacks, but it was ultimately rejected due to the risk of replacing the certificate. Replacing a pinned certificate can cause major issues for apps that use an older version of the SDK, effectively breaking tens of thousands of apps in production. 

One of the main differences in using this is that SSL pinning runs at the OS level and we can’t decide what to do when validation fails. Using signature verification provides a more flexible and scalable solution that can be easily updated without causing disruptions for apps running on older SDK versions. For example, using signature verification allows us to, in the very unlikely event the root key gets compromised (which should hopefully never happen):

  • Continue signing requests with the compromised key and trusting that one, so existing apps continue to work (they’ll just trust things the way they did before this feature)
  • Send out a new SDK version that uses a new key ASAP, so customers can upgrade when they get a chance

This kind of behavior wouldn’t be possible using SSL pinning. 

Transitioning to Trusted Entitlements

Transitioning an app from ResponseVerificationMode.disabled to ResponseVerificationMode.informational means cached data cannot be verified. To avoid myriad corner cases, the SDK simply invalidates all caches when the validation mode is changed. This means that when you enable Trusted Entitlements, the very first time the app opens it will need to refresh cache. So the first time your users open the app, they will need to connect to the network at least once to regain their entitlements. Subsequent app opens will be able to use the cache as usual.

Compatibility

Trusted Entitlements is available now, for Android 4.4+ and iOS 13.0 and above.

Securing your IAPs with RevenueCat

The new Trusted Entitlements feature in the RevenueCat SDK is a powerful tool for enhancing the security of your app and safeguarding your premium features from unauthorized access. By enabling this feature, you can protect your revenue and ensure a fair user experience. Additionally, the decision to use signature verification instead of SSL pinning showcases RevenueCat’s commitment to providing secure, scalable, and robust solutions that don’t compromise the functionality of your app.

In-App Subscriptions Made Easy

See why thousands of the world's tops apps use RevenueCat to power in-app purchases, analyze subscription data, and grow revenue on iOS, Android, and the web.

Related posts

Cache at RevenueCat
Engineering

Scaling smoothly: RevenueCat’s data-caching techniques for 1.2 billion daily API requests

A deep-dive into the techniques that fuel our efficient cache management.

Guillermo Pérez

Guillermo Pérez

October 31, 2023

Engineering

How to use offering metadata to A/B test your paywall with Experiments

Even if you’re using a custom paywall, you can run experiments with RevenueCat

Charlie Chapman

Charlie Chapman

August 31, 2023

Engineering

How to use StoreKit views to build a subscription app paywall with SwiftUI

A guide on Apple’s new StoreView, ProductView, and SubscriptionStoreView APIs for building native paywalls for your subscription app.

Charlie Chapman

Charlie Chapman

August 25, 2023

Want to see how RevenueCat can help?

RevenueCat enables us to have one single source of truth for subscriptions and revenue data.

Olivier Lemarie, PhotoRoomOlivier Lemarie, PhotoRoom
Read case study