a-sit-plus / warden

Server-Side Mobile Client Attestation Library
https://a-sit-plus.github.io/warden/
Apache License 2.0
10 stars 2 forks source link
![WARDEN](warden.png) # Server-Side Mobile Client Attestation Library [![A-SIT Plus Official](https://img.shields.io/badge/A--SIT_Plus-official-005b79?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNDMuNzYyODYgMTg0LjgxOTk5Ij48ZGVmcz48Y2xpcFBhdGggaWQ9ImEiIGNsaXBQYXRoVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48cGF0aCBkPSJNMCA1OTUuMjhoODQxLjg5VjBIMFoiLz48L2NsaXBQYXRoPjwvZGVmcz48ZyBjbGlwLXBhdGg9InVybCgjYSkiIHRyYW5zZm9ybT0ibWF0cml4KDEuMzMzMzMzMyAwIDAgLTEuMzMzMzMzMyAtNDgyLjI1IDUxNy41MykiPjxwYXRoIGZpbGw9IiMwMDViNzkiIGQ9Ik00MTUuNjcgMjQ5LjUzYy03LjE1LjA4LTEzLjk0IDEtMjAuMTcgMi43NWE1Mi4zMyA1Mi4zMyAwIDAgMC0xNy40OCA4LjQ2IDQwLjQzIDQwLjQzIDAgMCAwLTExLjk2IDE0LjU2Yy0yLjY4IDUuNDEtNC4xNCAxMS44NC00LjM1IDE5LjA5bC0uMDIgNi4xMnYyLjE3YS43MS43MSAwIDAgMCAuNy43M2gxNi41MmMuMzkgMCAuNy0uMzIuNzEtLjdsLjAxLTIuMmMwLTIuNi4wMi01LjgyLjAzLTYuMDcuMi00LjYgMS4yNC04LjY2IDMuMDgtMTIuMDZhMjguNTIgMjguNTIgMCAwIDEgOC4yMy05LjU4IDM1LjI1IDM1LjI1IDAgMCAxIDExLjk2LTUuNTggNTUuMzggNTUuMzggMCAwIDEgMTIuNTgtMS43NmM0LjMyLjEgOC42LjcgMTIuNzQgMS44YTM1LjA3IDM1LjA3IDAgMCAxIDExLjk2IDUuNTcgMjguNTQgMjguNTQgMCAwIDEgOC4yNCA5LjU3YzEuOTYgMy42NCAzIDguMDIgMy4xMiAxMy4wMnYyNC4wOUgzNjIuNGEuNy43IDAgMCAwLS43MS43VjMzNWMwIDguNDMuMDEgOC4wNS4wMSA4LjE0LjIgNy4zIDEuNjcgMTMuNzcgNC4zNiAxOS4yMmE0MC40MyA0MC40MyAwIDAgMCAxMS45NiAxNC41N2M1IDMuNzYgMTAuODcgNi42MSAxNy40OCA4LjQ2YTc3LjUgNzcuNSAwIDAgMCAyMC4wMiAyLjc3YzcuMTUtLjA3IDEzLjk0LTEgMjAuMTctMi43NGE1Mi4zIDUyLjMgMCAwIDAgMTcuNDgtOC40NiA0MC40IDQwLjQgMCAwIDAgMTEuOTUtMTQuNTdjMS42Mi0zLjI2IDMuNzctMTAuMDQgMy43Ny0xNC42OCAwLS4zOC0uMTctLjc0LS41NC0uODJsLTE2Ljg5LS40Yy0uMi0uMDQtLjM0LjM0LS4zNC41NCAwIC4yNy0uMDMuNC0uMDYuNi0uNSAyLjgyLTEuMzggNS40LTIuNjEgNy42OWEyOC41MyAyOC41MyAwIDAgMS04LjI0IDkuNTggMzUuMDEgMzUuMDEgMCAwIDEtMTEuOTYgNS41NyA1NS4yNSA1NS4yNSAwIDAgMS0xMi41NyAxLjc3Yy00LjMyLS4xLTguNjEtLjcxLTEyLjc1LTEuOGEzNS4wNSAzNS4wNSAwIDAgMS0xMS45Ni01LjU3IDI4LjUyIDI4LjUyIDAgMCAxLTguMjMtOS41OGMtMS44Ni0zLjQ0LTIuOS03LjU1LTMuMDktMTIuMmwtLjAxLTcuNDdoODkuMTZhLjcuNyAwIDAgMCAuNy0uNzJ2LTM5LjVjLS4xLTcuNjUtMS41OC0xNC40LTQuMzgtMjAuMDZhNDAuNCA0MC40IDAgMCAwLTExLjk1LTE0LjU2IDUyLjM3IDUyLjM3IDAgMCAwLTE3LjQ4LTguNDcgNzcuNTYgNzcuNTYgMCAwIDAtMjAuMDEtMi43N1oiLz48cGF0aCBmaWxsPSIjY2U0OTJlIiBkPSJNNDE5LjM4IDI4MC42M2gtNy41N2EuNy43IDAgMCAwLS43MS43MXYxNS40MmE4LjE3IDguMTcgMCAwIDAtMy43OCA2LjkgOC4yOCA4LjI4IDAgMCAwIDE2LjU0IDAgOC4yOSA4LjI5IDAgMCAwLTMuNzYtNi45di0xNS40MmEuNy43IDAgMCAwLS43Mi0uNzEiLz48L2c%2BPC9zdmc%2B&logoColor=white&labelColor=white)](https://a-sit-plus.github.io) [![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-brightgreen.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) [![Kotlin](https://img.shields.io/badge/kotlin-2.0.20-blue.svg?logo=kotlin)](http://kotlinlang.org) ![Java](https://img.shields.io/badge/java-17-blue.svg?logo=OPENJDK) ![Build artifacts](https://github.com/a-sit-plus/warden/actions/workflows/gradle.yml/badge.svg) [![Maven Central](https://img.shields.io/maven-central/v/at.asitplus/warden)](https://mvnrepository.com/artifact/at.asitplus/warden/)

Server-side library providing a unified interface for key attestation compatible with Android and iOS (yes, even iOS!). It also provides App attestation on both platforms (see our 2019 Paper on how to remotely establish trust in Android applications for more Android-specifics on this matter).

Under the hood, this library depends on the WARDEN-roboto Android attestation library and Vincent Haupert's excellent DeviceCheck/AppAttest library.

Full API docs are available here.

Demonstration / Usage Example

This library is intended for integration into back-end services which need to remotely establish trust in mobile clients (both Android and iOS). Usually, this means that a mobile client initially request a binding certificate from the back-end based on a public/private key pair stored inside cryptographic hardware. This binding is only granted if device and app integrity can be verified, and if the key can be proven to be stored in hardware.
Once a binding has been obtained, mobile clients can subsequently authenticate to the back-end (e.g. to access some protected resource). However, far more flexible scenarios can be implemented. Hence, Figure 1 depicts an abstract version of establishing trust in mobile clients.

See the provided sample service and its accompanying mobile clients for an MWE that integrates this library. (The sample also contains the Android and iOS clients.)

![flow.png](flow.png) Figure 1: Abstract example usage: remotely establishing trust in mobile clients

Background

Apple and Google pursue different strategies wrt. establishing trust in mobile clients. On Android, things are kept rather simpler from an architectural point of view, while iOS attestation depends on infrastructure operated by Apple.

Android

During a device's manufacturing process, manufacturers provision signing keys and matching certificates into every device's cryptographic hardware. The device manufacturers' certificates are signed by Google, resulting in a certificate chain from a certificate signed by the attestation root key published by Google. down to every individual Android device that ships with Google play services. Apps can then generate cryptographic keys, which are again securely stored in cryptographic hardware on the device and have this hardware module issue certificates for those keys. These certificates are signed by the previously mentioned device manufacturer signing key provisioned during the manufacturing process. In the end, this leads to a chain of trust from the Google root certificate to the cryptographic material created on the device.
The cryptographic material referenced by the leaf certificate of the aforementioned chain can be used by the app as desired (e.g. to perform signatures, etc.).

To establish trust in an Android device and a client app, quite some properties of such a leaf certificate need to be evaluated in a particular manner. From a high-level point of view, it really is simple: Validate the certificate chain just like any certificate chain, and evaluate a well-documented extension of the leaf certificate to establish trust in an Android client app (Figure 2 illustrates this high-level concept in more detail). This is one core feature of this library -- make establishing trust in client apps just as simple and straight-forward. The other one is providing a unified API to provide a inified API to achieve the same for iOS clients.

![android.png](android.png) Figure 2: High-level structure of an Android key attestation result

iOS

iOS's attestation, is a rather different beast compared to Android. Apple relies on their own heuristics employed as part of a service operated by the company to assess whether a device and an app can be trusted or not. While some of the same basic principles apply here as well (i.e. keys generated in hardware come with chain of trust rooted in the manufacturer's certificate), the semantics are quite different. Android primarily attests the properties of a cryptographic key. Apple's App Attest, on the other hand, attests the integrity of apps. The cryptographic material is in this case a mere vehicle to realise the idea of attesting app integrity. Therefore, the involved key material cannot be used for arbitrary cryptographic operations, but is only employed to sign attestations (and related assertions; see below).

This begs the question: How to enable key attestation on iOS? After all, many applications exist, which require some proof that a key used for critical operations resides in hardware.

Legacy Attestation Format (Deprecated, but still Supported since Version 2.2.0)

To emulate key attestation, the ability to obtain a so-called assertion comes to the rescue: iOS allows generating an assertion for some data by signing it using the same key backing a previously obtained attestation. By that logic, computing an assertion over the public key of a freshly generated public/private key pair proves that an authentic, uncompromised app on a non-jailbroken device was used to generate this key pair as intended by the app developer.

Supreme Attestation Format (Supported Since Version 2.2.0)

Following Apple's attestation format makes it clear that no data, but only hashes are ever encoded and signed. Hence, it allows for a lot of flexibility when it comes to the data to be hashed. The new Supreme attestation format exploits this and does not only pass the hash over a challenge to the AppAttest service, but instead constructs a structured (JSON) client data object, inspired by WebAuthn and passes tha hash of this data to DCAppAttest. This means that:

  1. A ClientData object is created based on the challenge and the public key to attest.
  2. The ClientData is serialized to JSON, and the ByteArray-representation of this JSON string are hashed using SHA-256:
    • val clientDataJSON = Json.encodeToString(clientData).encodeToByteArray()
    • val clientDataHash = Digest.SHA256.digest(clientDataJSON).toNSData()
  3. This hash is then passed to DCAppAttest.
    If your mobile clients are using the Supreme KMP crypto provider, this is procedure already implemented, and you don't have to worry about it.
  4. The IosHomebrewAttestation provided by Signum's Indispensable module lets you access both the raw bytes of this client data as well as the original ClientData object, so you can easily verify both the hash of this data and its contents.

For this whole routine to work, clients need to create a Secure-Enclave-protected key pair before calling DCAppattest and construct the structured client data, containing the public part of this key pair and the server challenge. The client data format is defined in the Signum's Indispensable module, as is the IosHomebrewAttestation containing it.

This library abstracts away all the nitty-gritty details of this verification process and provides a unified API which works with both Android and iOS. (The AndroidKeyStoreAttestation contains simply the certificate chain attached to an attested key.)
The test resources contain examples of Android and iOS attestation proofs.

Usage

Written in Kotlin, plays nicely with Java (cf. @JvmOverloads), published at maven central.

Gradle

Add the dependency:

 dependencies {
     implementation("at.asitplus:warden:$version")
 }

Configuration

Every parameter is configurable and multiple instance of an attestation service can be created and used in parallel.

Android and iOS attestation require different configuration parameters. Hence, distinct configuration classes exist. The following snippet lists all configuration values:

val warden = Warden(
    androidAttestationConfiguration = AndroidAttestationConfiguration(
       applications= listOf(   //REQUIRED: add applications to be attested
           AndroidAttestationConfiguration.AppData(
               packageName = "at.asitplus.attestation_client",
               signatureDigests = listOf("NLl2LE1skNSEMZQMV73nMUJYsmQg7=".encodeToByteArray()),
               appVersion = 5
           ),
           AndroidAttestationConfiguration.AppData( //we have a dedicated app for latest android version
               packageName = "at.asitplus.attestation_client-tiramisu",
               signatureDigests = listOf("NLl2LE1skNSEMZQMV73nMUJYsmQg7=".encodeToByteArray()),
               appVersion = 2, //with a different versioning scheme
               androidVersionOverride = 13000, //so we need to override this
               patchLevelOverride = PatchLevel(2023, 6) //also override patch level
           )
       ),
       androidVersion = 11000,                 //OPTIONAL, null by default
       patchLevel = PatchLevel(2022, 12),      //OPTIONAL, null by default
       requireStrongBox = false,               //OPTIONAL, defaults to false
       allowBootloaderUnlock = false,          //OPTIONAL, defaults to false
       requireRollbackResistance = false,      //OPTIONAL, defaults to false
       ignoreLeafValidity = false,             //OPTIONAL, defaults to false
       hardwareAttestationTrustAnchors = linkedSetOf(*DEFAULT_HARDWARE_TRUST_ANCHORS), //OPTIONAL, defaults shown here
       softwareAttestationTrustAnchors = linkedSetOf(*DEFAULT_SOFTWARE_TRUST_ANCHORS), //OPTIONAL, defaults shown here
       verificationSecondsOffset = -300,       //OPTIONAL, defaults to 0
       disableHardwareAttestation = false,     //OPTIONAL, defaults to false. Set to true to disable HW attestation
       enableNougatAttestation = false,        //OPTIONAL, defaults to false. Set to true to enable hybrid attestation
       enableSoftwareAttestation = false       //OPTIONAL, defaults to false. Set to true to enable SW attestation
   ),
   iosAttestationConfiguration = IOSAttestationConfiguration(
      applications = listOf(
        IOSAttestationConfiguration.AppData(
          teamIdentifier = "9CYHJNG644",
          bundleIdentifier = "at.asitplus.attestation-client",
          iosVersionOverride = "16.0",     //OPTIONAL, null by default
          sandbox = false                  //OPTIONAL, defaults to false
          )
      ),
      iosVersion = 14,                                               //OPTIONAL, null by default
   ),
   clock = FixedTimeClock(Instant.parse("2023-04-13T00:00:00Z")),   //OPTIONAL, system clock by default,
   verificationTimeOffset = Duration.ZERO                           //OPTIONAL, defaults to zero
)

The (nullable) properties like patch level, iOS version or Android app version essentially allow for excluding outdated devices. Custom android challenge verification has been omitted by design, considering iOS constraints and inconsistencies resulting from such a customisation. More details on the configuration can be found in the API documentation

A Note on Android Attestation

This library allows for using combining different flavours of Android attestation, ranging from full hardware attestation to (rather useless in practice) software-only attestation (see WARDEN-roboto for details). Hardware attestation is enabled by default, while hybrid and software-only attestation need to be explicitly enabled through enableNougatAttestation and enableSoftwareAttestation, respectively. Doing so, will chain the corresponding AndroidAttestationCheckers initially from strictest (hardware) to most useless (software-only). Naturally, hardware attestation can also be disabled by setting disableHardwareAttestation = true although there is probably no real use case for such a configuration. Note that not all flavours use different the same root of trust by default.

Example Usage

While still not complete, the test suite in this repository should provide a nice overview. FeatureDemonstration, in particular, was designed to demonstrate this library's API.
See the provided sample service and its mobile clients for an MWE that integrates this library. The sample also contains Android and iOS clients.

Obtaining a Key Attestation Result

DEPRECETED, but still supported

END DEPRECATION. The structure of the platform-specific proofs can be found here.

Various advanced, platform-specific variants of this verifyKeyAttestation() call exist, to cater towards features specific to Android and iOS (do see FeatureDemonstration for details). However, only verifyKeyAttestation() works for both Android and iOS and returns a KeyAttestation object:

fun verifyKeyAttestation(
  attestationProof: Attestation,
  challenge: ByteArray)
: KeyAttestation<PublicKey>

The returned KeyAttestation object contains the attested key on success, or an error on failure.

Semantics

The call succeeds if attestation data structures of the client (in attestationProof) can be verified and expectedChallenge matches the attestation challenge and if keyToBeAttested matches the key contained in the proof.

As mentioned, the contents of attestationProof are platform-specific! On Android, this is simply the certificate chain from the attestation certificate (i.e. the certificate corresponding to the key to be attested) up to one of the Google hardware attestation root certificates. on iOS this must contain the AppAttest attestation statement at index 0 and an assertion at index 1, which, is verified for integrity and to match keyToBeAttested if the deprecated ios Attestation is used. The signature counter in the attestation must be 0 (and the signature counter in the assertion must be 1 if the deprecated ios Attestation is used).

Passing a public key created in the same app on an iDevice's secure hardware as clientData to create an assertion effectively emulates Android's key attestation: Attesting such a secondary key through an assertion proves that it was also created within the same app, on the same device, resulting in an attested key, which can then be used for general-purpose crypto.
Limitation: supports only EC key on iOS (either ANSI X9.63 encoded or DER encoded). The key can be passed in either encoding to the secure enclave when creating an assertion.


Contributing

External contributions are greatly appreciated! Just be sure to observe the contribution guidelines (see CONTRIBUTING.md).


This project has received funding from the European Union’s Horizon 2020 research and innovation programme under grant agreement No 959072.

EU flag

The Apache License does not apply to the logos, (including the A-SIT logo) and the project/module name(s), as these are the sole property of A-SIT/A-SIT Plus GmbH and may not be used in derivative works without explicit permission!