diff --git a/Document/0x06a-Platform-Overview.md b/Document/0x06a-Platform-Overview.md index bf1bb42cfb5..0e0eec08d12 100644 --- a/Document/0x06a-Platform-Overview.md +++ b/Document/0x06a-Platform-Overview.md @@ -172,16 +172,8 @@ The following APIs [require user permission](https://www.apple.com/business/docs ### DeviceCheck -The DeviceCheck framework, including its components DeviceCheck and App Attest, helps you prevent fraudulent use of your services. It consists of a framework that you use from your app and an Apple server which is accessible only to your own server. DeviceCheck allows you to persistently store information on the device and on Apple servers. The stored information remains intact across app reinstallation, device transfers, or resets, with the option to reset this data periodically. - -DeviceCheck is typically used to mitigate fraud by restricting access to sensitive resources. For example, limiting promotions to once per device, identify and flag fraudulent devices, etc. However, it definitely cannot prevent all fraud. For example, it is [not meant to detect compromised operating systems](https://swiftrocks.com/app-attest-apple-protect-ios-jailbreak "App Attest: How to prevent an iOS app's APIs from being abused") (aka. jailbreak detection). - -For more information, refer to the [DeviceCheck documentation](https://developer.apple.com/documentation/devicecheck "DeviceCheck documentation"). +See @MASTG-KNOW-0x02. #### App Attest -App Attest, available under the DeviceCheck framework, helps you verify instances of the app running on a device by enabling apps to attach a hardware-backed assertion to requests, ensuring they originate from the legitimate app on a genuine Apple device. This feature aids in preventing modified apps from communicating with your server. - -The process involves generating and validating cryptographic keys, along with a set of verifications performed by your server, ensuring the authenticity of the request. It is important to note that while App Attest enhances security, it does not guarantee complete protection against all forms of fraudulent activities. - -For more detailed information, refer to the [WWDC 2021](https://developer.apple.com/videos/play/wwdc2021/10244 "WWDC 2021") session, along with the ["DeviceCheck documentation"](https://developer.apple.com/documentation/devicecheck/) and ["Validating apps that connect to your server"](https://developer.apple.com/documentation/devicecheck/validating-apps-that-connect-to-your-server). +See @MASTG-KNOW-0x03. diff --git a/best-practices/MASTG-BEST-0x01.md b/best-practices/MASTG-BEST-0x01.md new file mode 100644 index 00000000000..52afdc7e922 --- /dev/null +++ b/best-practices/MASTG-BEST-0x01.md @@ -0,0 +1,74 @@ +--- +title: Use Hardware-Backed Key Attestation for Device and App Integrity +alias: android-hardware-backed-attestation +id: MASTG-BEST-0x01 +platform: android +knowledge: [MASTG-KNOW-0035, MASTG-KNOW-0044, MASTG-KNOW-0047, MASTG-KNOW-0119, MASTG-KNOW-0120] +--- + +Applications that perform business-critical operations, such as financial transactions, multi-factor authentication, or sensitive data handling, should verify the integrity of the device environment before trusting it as well as the integrity of the application binary. + +For the majority of apps, **Google Play Integrity API** (@MASTG-KNOW-0035) is the recommended starting point. It provides a managed, server-backed attestation signal covering device integrity, app integrity, and licensing - without requiring apps to implement low-level certificate verification themselves. + +**This best practice covers the manual attestation approach** using Android's hardware-backed Key Attestation (@MASTG-KNOW-0044). This approach should be used when Play Integrity limitations need to be addressed, or when the application requires more control. In this scenario, use Android's Key Attestation to cryptographically verify that the client's keys reside in hardware-backed storage and that the device has not been compromised. + +## Implement Server-Driven Attestation with a Fresh Challenge + +Always drive attestation from the server using the challenge-response flow described in @MASTG-KNOW-0044. Generate a unique, cryptographically random challenge (nonce) for each attestation request using a Cryptographically Secure Pseudorandom Number Generator (CSPRNG). Never reuse challenges across requests. Never implement attestation verification solely on the client side. + +Since attestation reflects the state of the device and application at the time of key generation rather than at the time of use, require fresh key generation: + +- At critical moments such as the first app launch, account binding, or sensitive operation. +- When the installed app version falls below the minimum acceptable threshold. + +Enforce short-lived keys or periodic re-attestation policies to reduce the window of exposure between a device's state changing and that change being detected. + +## Verify the Attestation Certificate Chain + +On the server side, verify the attestation certificate chain (@MASTG-KNOW-0044): + +- Verify the chain of trust up to the Google Hardware Attestation Root Certificate. +- Check each certificate against Google's Certificate Revocation Status List. +- Confirm that the embedded challenge matches the one the server originally issued. + +## Verify Device Integrity + +Using the `rootOfTrust` fields described in @MASTG-KNOW-0120, confirm the device is in a trusted state: + +- Require that `attestationSecurityLevel` is `TrustedEnvironment` or `StrongBox`, confirming the device integrity data is hardware-enforced and cannot be falsified by the Android OS. +- Verify `verifiedBootState` is `Verified`, indicating the full boot chain was validated against OEM keys. +- Verify `deviceLocked` is `true`, confirming the bootloader is locked and the system partition cannot be modified without detection. + +The following conditions indicate low or no device integrity: + +- **`verifiedBootState` is not `Verified`**: The boot chain was not fully verified against OEM keys. A `SelfSigned` state means the device is running a custom ROM with a user-installed key; `Unverified` means no verification was performed at all; `Failed` means verification was attempted and failed. +- **`deviceLocked` is `false`**: The bootloader is unlocked, meaning the system partition can be modified without triggering a boot failure. This is a strong signal that the device may have been tampered with. +- **`attestationSecurityLevel` is `Software`**: The attestation was generated entirely in the Android OS with no hardware involvement. It can be trusted as long as the device is running an operating system that complies with the [Android Platform Security Model](https://arxiv.org/pdf/1904.05572) (that is, the `deviceLocked` is `true` and the `verifiedBootState` is `Verified`). + +## Verify Application Integrity + +Using the `attestationApplicationId` fields described in @MASTG-KNOW-0119, confirm the key was generated by the legitimate application: + +- Verify `packageName` matches the expected application identifier. +- Verify `signatureDigests` match the expected signing certificate digests pre-provisioned on the server (e.g., from the app's release certificate). A mismatch indicates the APK has been repackaged or signed with a different key. +- Verify `version` is within an acceptable range to reject outdated or known-vulnerable versions. + +The following conditions indicate low or no application integrity: + +- **`signatureDigests` do not match**: The SHA-256 digests of the app's signing certificates differ from the legitimate app's known digests at the time of release. This is the primary indicator that the APK has been repackaged or signed with a different key (e.g., a malicious clone or a patched version of the app). +- **`version` is below the minimum acceptable version**: The app version recorded at key generation time is too old, indicating a version with known vulnerabilities or one that predates a security-relevant update. + +## Enforce Key Properties + +Use the attestation extension data (@MASTG-KNOW-0044) to confirm that the attested key pair was generated with the expected properties: + +- Restrict the key purpose to only the intended operations (e.g., signing, encryption). +- Require user authentication before key use when applicable (e.g., biometric binding via [`setUserAuthenticationRequired`](https://developer.android.com/reference/kotlin/android/security/keystore/KeyGenParameterSpec.Builder#setuserauthenticationrequired)). + +## Handle Attestation Failures Securely + +If attestation verification fails, the server must not grant the client elevated trust or access to sensitive operations. Depending on the application's risk profile: + +- Deny access to high-assurance features entirely. +- Fall back to alternative verification mechanisms with appropriate risk acceptance. +- Log the failure for monitoring and incident response. diff --git a/best-practices/MASTG-BEST-0x02.md b/best-practices/MASTG-BEST-0x02.md new file mode 100644 index 00000000000..8ec081dd8a3 --- /dev/null +++ b/best-practices/MASTG-BEST-0x02.md @@ -0,0 +1,73 @@ +--- +title: Mitigate the Risk of API Keys Hardcoded in the App Package +alias: mitigate-hardcoded-api-keys +id: MASTG-BEST-0x02 +platform: generic +knowledge: [MASTG-KNOW-0015, MASTG-KNOW-0035, MASTG-KNOW-0072, MASTG-KNOW-0118, MASTG-KNOW-0x01, MASTG-KNOW-0x02, MASTG-KNOW-0x03] +--- + +API keys embedded in the app package - whether in source code, resource files, or build artifacts - can be extracted through static analysis or binary inspection, even without a rooted device or special tooling. The ideal solution is to move the key server-side entirely so it never ships with the app binary. Often, that is not possible, so the following measures reduce the window of exploitation. + +In all cases, never expose sensitive data such as API keys to clients whose integrity has not been verified. + +## Deliver API Keys Over-The-Air After Integrity Verification + +Instead of embedding the key in the binary, fetch it at runtime from your backend only after the device and app have been attested: + +1. At app startup, perform app and device attestation (see @MASTG-BEST-0043). +2. If attestation passes, the backend issues an API key (or a short-lived derivative token) over a secure, pinned channel. +3. The app holds the key in memory for the session. If persistence is required, store it in platform-provided secure storage (Keychain on iOS, Android Keystore) rather than plain files. + +This approach keeps the key out of the binary entirely, allows rotation without a new app release, and ensures only verified app instances on genuine devices ever receive it. It combines the protections described in the sections below into a single cohesive architecture. + +### Prefer a Server-Side Proxy for Third-Party API Calls + +Where the architecture allows it, do not ship the API key in the app at all. Instead: + +- Have the mobile app call your own backend (sometimes known as API proxy or API Gateway), which authenticates the request, then calls the third-party API using a server-stored key. +- This eliminates the key from the app package entirely and gives you full control over auditing and rotation. + +### Enforce App and Device Integrity Verification Before Use + +Require the client to pass app and device integrity verification before any API key or scoped token is issued or accepted. Do not treat attestation as an optional layer on top of a hardcoded key - make it a precondition enforced on the server: + +- **Android**: Play Integrity API (@MASTG-KNOW-0035) or Firebase App Check (@MASTG-KNOW-0x01). +- **iOS**: App Attest (@MASTG-KNOW-0x03) or DeviceCheck (@MASTG-KNOW-0x02) via Firebase App Check (@MASTG-KNOW-0x01). + +See @MASTG-BEST-0043 for server-side enforcement requirements. + +### Enforce Network Integrity with Certificate Pinning + +A network-level attacker who intercepts traffic can observe or replay API keys even if they were never in the binary. Pin the server certificate so that the key is only ever transmitted over a channel the app explicitly trusts: + +- **Android**: configure pinning via the Network Security Configuration (@MASTG-KNOW-0015). +- **iOS**: implement server trust evaluation (@MASTG-KNOW-0072). + +Security controls such as Certificate Pinning can only be trusted if the app's integrity has been verified and the device's controls can be trusted. + +### Apply RASP Controls Before and Between Attestations + +Attestation is a point-in-time check - it verifies the environment at session start but does not detect changes that occur during execution. RASP (see @MASTG-KNOW-0118) fills this gap by running continuous in-process checks throughout the app's lifetime: + +- **Before attestation**: run environment checks (debugger detection, emulator detection, hooking framework detection) before initiating the attestation flow. Abort early if the environment is hostile - do not expose network calls or key requests to an already-compromised process. +- **Between attestations**: monitor for runtime changes that could indicate an active attack - hook injection, memory tampering, or the appearance of reverse engineering tools after a previously clean attestation. Respond by revoking the locally held key, clearing it from memory, and requiring re-attestation before resuming sensitive operations. + +See @MASTG-BEST-0029 for RASP signal implementation guidance. + +### Limit Credential Lifetime + +Credentials - whether static API keys or issued tokens - should be treated as perishable. The shorter their effective lifetime, the smaller the window of exploitation if they are extracted or intercepted. + +**If you control the API and issue tokens to the app**, do not issue long-lived static keys. Issue short-lived tokens instead: + +- Use short-lived JWTs with an `exp` claim of minutes to a few hours, depending on the sensitivity of the operation. A stolen token becomes useless once it expires. +- Issue a refresh token alongside the access token and invalidate it on each use (refresh token rotation). This limits the damage window if a refresh token is intercepted and makes reuse detectable. +- Bind tokens to the attested client by including attestation claims in the JWT payload (e.g., device integrity verdict, app version, platform). The server can then reject tokens presented from a context that no longer passes integrity checks. +- Scope tokens narrowly to the minimum set of operations the app needs at that point in the session. +- Support revocation via a server-side denylist for high-risk scenarios where waiting for natural expiry is unacceptable. + +**If you rely on third-party API keys that cannot be moved server-side**, treat them as perishable static secrets: + +- Rotate keys on a defined schedule and immediately upon any suspected compromise. +- Use distinct keys per platform and per environment (development, staging, production) so that a leak in one context does not affect others. +- Automate rotation where the API provider supports it, and ensure the app can receive updated keys without a forced upgrade (e.g., via a remote configuration service backed by attestation). diff --git a/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/MASTG-DEMO-0x01.md b/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/MASTG-DEMO-0x01.md new file mode 100644 index 00000000000..e57621c5b64 --- /dev/null +++ b/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/MASTG-DEMO-0x01.md @@ -0,0 +1,31 @@ +--- +platform: android +title: Key Attestation Without a Server-Issued Challenge +id: MASTG-DEMO-0x01 +code: [kotlin] +test: MASTG-TEST-0x01 +--- + +## Sample + +This sample generates an EC key pair in the Android KeyStore (hardware-backed via the TEE) and retrieves the resulting certificate chain to send to a server for verification. However, no attestation challenge is set via [`setAttestationChallenge`](https://developer.android.com/reference/kotlin/android/security/keystore/KeyGenParameterSpec.Builder#setattestationchallenge) during key generation, so the server cannot determine when the attestation was produced. + +{{ MastgTest.kt # MastgTest_reversed.java }} + +## Steps + +Let's run @MASTG-TOOL-0110 against the reversed Java code. + +{{ ../../../../rules/mastg-android-key-attestation-missing-challenge.yml }} + +{{ run.sh }} + +## Observation + +The rule identifies one location where a `KeyGenParameterSpec` is built via method chaining without `setAttestationChallenge`. The certificate chain created from this spec is sent to the server without any embedded nonce. + +{{ output.txt }} + +## Evaluation + +The test fails because the output shows that `KeyGenParameterSpec.build()` is invoked without `setAttestationChallenge` anywhere in the chain. The `attestationChallenge` field in the leaf certificate will be null, so the server cannot verify the freshness of the attestation and cannot distinguish a freshly generated certificate chain from a replayed one. See @MASTG-BEST-0x01 for the correct server-driven challenge-response flow. diff --git a/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/MastgTest.kt b/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/MastgTest.kt new file mode 100644 index 00000000000..7a24863fcfc --- /dev/null +++ b/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/MastgTest.kt @@ -0,0 +1,51 @@ +package org.owasp.mastestapp + +import android.content.Context +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Base64 +import java.security.KeyPairGenerator +import java.security.KeyStore +import java.security.spec.ECGenParameterSpec + +class MastgTest(private val context: Context) { + + private val keyAlias = "mastgAttestationKey" + + fun mastgTest(): String { + val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } + + if (keyStore.containsAlias(keyAlias)) { + keyStore.deleteEntry(keyAlias) + } + + val kpg = KeyPairGenerator.getInstance( + KeyProperties.KEY_ALGORITHM_EC, + "AndroidKeyStore" + ) + + // INSECURE: No attestation challenge is set. + // The server cannot verify when this attestation was produced. + val spec = KeyGenParameterSpec.Builder( + keyAlias, + KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY + ) + .setDigests(KeyProperties.DIGEST_SHA256) + .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1")) + .build() + + kpg.initialize(spec) + kpg.generateKeyPair() + + val certChain = keyStore.getCertificateChain(keyAlias) + + val sb = StringBuilder() + sb.appendLine("Sending certificate chain to https://example.com/attestation-verify ...") + sb.appendLine("Chain length: ${certChain.size}") + certChain.forEachIndexed { i, cert -> + val pem = Base64.encodeToString(cert.encoded, Base64.DEFAULT) + sb.appendLine("Certificate[$i] (first 60 chars): ${pem.take(60)}...") + } + return sb.toString() + } +} \ No newline at end of file diff --git a/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/MastgTest_reversed.java b/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/MastgTest_reversed.java new file mode 100644 index 00000000000..202c10bb356 --- /dev/null +++ b/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/MastgTest_reversed.java @@ -0,0 +1,67 @@ +package org.owasp.mastestapp; + +import android.content.Context; +import android.security.keystore.KeyGenParameterSpec; +import android.util.Base64; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.spec.ECGenParameterSpec; +import kotlin.Metadata; +import kotlin.jvm.internal.Intrinsics; +import kotlin.text.StringsKt; + +/* compiled from: MastgTest.kt */ +@Metadata(d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0002\b\u0002\b\u0007\u0018\u00002\u00020\u0001B\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0006\u0010\b\u001a\u00020\u0007R\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000R\u000e\u0010\u0006\u001a\u00020\u0007X\u0082D¢\u0006\u0002\n\u0000¨\u0006\t"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "", "(Landroid/content/Context;)V", "keyAlias", "", "mastgTest", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48) +/* loaded from: classes3.dex */ +public final class MastgTest { + public static final int $stable = 8; + private final Context context; + private final String keyAlias; + + public MastgTest(Context context) { + Intrinsics.checkNotNullParameter(context, "context"); + this.context = context; + this.keyAlias = "mastgAttestationKey"; + } + + public final String mastgTest() throws NoSuchAlgorithmException, IOException, KeyStoreException, CertificateException, NoSuchProviderException, InvalidAlgorithmParameterException { + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + if (keyStore.containsAlias(this.keyAlias)) { + keyStore.deleteEntry(this.keyAlias); + } + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "AndroidKeyStore"); + int i = 0; + KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(this.keyAlias, 12).setDigests("SHA-256").setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")).build(); + Intrinsics.checkNotNullExpressionValue(spec, "build(...)"); + kpg.initialize(spec); + kpg.generateKeyPair(); + Certificate[] certChain = keyStore.getCertificateChain(this.keyAlias); + StringBuilder sb = new StringBuilder(); + sb.append("Sending certificate chain to https://example.com/attestation-verify ...").append('\n'); + sb.append("Chain length: " + certChain.length).append('\n'); + Intrinsics.checkNotNull(certChain); + int index$iv = 0; + int length = certChain.length; + int i2 = 0; + while (i2 < length) { + String pem = Base64.encodeToString(certChain[i2].getEncoded(), i); + Intrinsics.checkNotNull(pem); + sb.append("Certificate[" + index$iv + "] (first 60 chars): " + StringsKt.take(pem, 60) + "...").append('\n'); + i2++; + index$iv++; + kpg = kpg; + i = 0; + } + String string = sb.toString(); + Intrinsics.checkNotNullExpressionValue(string, "toString(...)"); + return string; + } +} diff --git a/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/output.txt b/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/output.txt new file mode 100644 index 00000000000..162e7cbda5f --- /dev/null +++ b/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/output.txt @@ -0,0 +1,16 @@ + + +┌────────────────┐ +│ 1 Code Finding │ +└────────────────┘ + + MastgTest_reversed.java + ❯❱ rules.mastg-android-key-attestation-missing-challenge + ❰❰ Blocking ❱❱ + [MASVS-RESILIENCE-2] KeyGenParameterSpec built without setAttestationChallenge. The server cannot + verify when this attestation was produced. + + 42┆ KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(this.keyAlias, + 12).setDigests("SHA-256").setAlgorithmParameterSpec(new + ECGenParameterSpec("secp256r1")).build(); + diff --git a/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/run.sh b/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/run.sh new file mode 100644 index 00000000000..b5502367952 --- /dev/null +++ b/demos/android/MASVS-RESILIENCE/MASTG-DEMO-0x01/run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +NO_COLOR=true semgrep -c ../../../../rules/mastg-android-key-attestation-missing-challenge.yml ./MastgTest_reversed.java > output.txt diff --git a/demos/ios/MASVS-RESILIENCE/MASTG-DEMO-0x02/MASTG-DEMO-0x02.md b/demos/ios/MASVS-RESILIENCE/MASTG-DEMO-0x02/MASTG-DEMO-0x02.md new file mode 100644 index 00000000000..80060da6e2f --- /dev/null +++ b/demos/ios/MASVS-RESILIENCE/MASTG-DEMO-0x02/MASTG-DEMO-0x02.md @@ -0,0 +1,10 @@ +--- +platform: ios +title: App Attest Assertion Without a Server-Issued Challenge +id: MASTG-DEMO-0x02 +code: [swift] +test: MASTG-TEST-0x02 +kind: fail +status: placeholder +note: This demo shows an app using App Attest (DCAppAttestService) whose clientDataHash is not derived from a one-time, server-provided challenge, allowing the attestation and assertions to be replayed. +--- diff --git a/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0035.md b/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0035.md index 63f8c99a10d..3645c0772e5 100644 --- a/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0035.md +++ b/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0035.md @@ -4,13 +4,17 @@ platform: android title: Google Play Integrity API --- -Google has launched the [Google Play Integrity API](https://developer.android.com/google/play/integrity/overview "Google Play Integrity API") to improve the security and integrity of apps and games on Android starting with Android 4.4 (level 19). The previous official API, [SafetyNet](https://developer.android.com/training/safetynet), did not cover all the security needs that Google wanted for the platform, so Play Integrity was developed with the basic functions of the previous API and integrated additional features. This change aims to protect users from dangerous and fraudulent interactions. +[Google Play Integrity API](https://developer.android.com/google/play/integrity/overview "Google Play Integrity API") was launched to improve the security and integrity of apps and games on Android starting with Android 4.4 (level 19). It supersedes the deprecated [SafetyNet Attestation API](https://developer.android.com/training/safetynet/attestation), which was shut down in 2024. Play Integrity was developed with the core capabilities of SafetyNet and added broader coverage for app and account integrity signals. -**Google Play Integrity offers the following safeguards:** +Internally, the Play Integrity API uses the same cryptographic techniques as @MASTG-KNOW-0044 to root its verdicts in hardware-backed trust. It is the recommended attestation provider for Android when using Firebase App Check (see @MASTG-KNOW-0x01). -- Verification of genuine Android device: It verifies that the application is running on a legitimate Android device. -- User license validation: It indicates whether the application or game was installed or purchased through the Google Play Store. -- Unmodified binary verification: It determines whether the application is interacting with the original binary recognized by Google Play. +## Safeguards + +The Play Integrity API provides a server-verifiable verdict covering: + +- Whether the app binary is the original, unmodified version from Google Play +- Whether the app is running on a genuine Android device +- Whether the device passes basic integrity checks The API provides four macro categories of information to help the security team make a decision. These categories include: @@ -27,44 +31,12 @@ The API provides four macro categories of information to help the security team - `MEETS_STRONG_INTEGRITY`: The app is on a device with Google Play Services, ensuring robust system integrity with features like hardware-protected boot. - `MEETS_VIRTUAL_INTEGRITY`: The app runs in an emulator with Google Play Services, passing system integrity checks and meeting Android compatibility requirements. -**API Errors:** +## API Errors The API can return local errors such as `APP_NOT_INSTALLED` and `APP_UID_MISMATCH`, which can indicate a fraud attempt or attack. In addition, outdated Google Play Services or Play Store can also cause errors, and it is important to check these situations to ensure proper integrity verification functionality and to ensure the environment is not intentionally set up for an attack. You can find more details on the [official page](https://developer.android.com/google/play/integrity/error-codes). -**Best practices:** - -1. Use Play Integrity as part of a broader security strategy. Complement it with additional security measures such as input data validation, user authentication, and anti-fraud protection. -2. Minimize queries to the Play Protect API to reduce device resource impact. For example, employ the API only for essential device integrity verifications. - -3. Include a `NONCE` with integrity verification requests. This random value, generated by the app or server, helps the verification server confirm that responses match the original requests without third-party tampering. - -**Limitations:** -The default daily limit for Google Play Services Integrity Verification API requests is 10,000 requests per day. Applications needing more must contact Google to request an increased limit. - -**Example Request:** +## Limitations -```json -{ - "requestDetails": { - "requestPackageName": "com.example.your.package", - "timestampMillis": "1666025823025", - "nonce": "kx7QEkGebwQfBalJ4...Xwjhak7o3uHDDQTTqI" - }, - "appIntegrity": { - "appRecognitionVerdict": "UNRECOGNIZED_VERSION", - "packageName": "com.example.your.package", - "certificateSha256Digest": [ - "vNsB0...ww1U" - ], - "versionCode": "1" - }, - "deviceIntegrity": { - "deviceRecognitionVerdict": [ - "MEETS_DEVICE_INTEGRITY" - ] - }, - "accountDetails": { - "appLicensingVerdict": "UNEVALUATED" - } - } -``` +- **Quota**: The default daily limit is 10.000 requests per day. Applications needing more must contact Google to request an increased limit. +- **Requires Google Play Services**: The API only works on devices with Google Play Services. Apps distributed outside Google Play or running on non-GMS devices (e.g., custom ROMs, some enterprise deployments) cannot obtain a verdict. +- **Requires network connectivity**: Verdict generation requires a live connection to Google's servers. The API cannot be used offline. diff --git a/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0044.md b/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0044.md new file mode 100644 index 00000000000..c63af606226 --- /dev/null +++ b/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0044.md @@ -0,0 +1,98 @@ +--- +masvs_category: MASVS-RESILIENCE +platform: android +title: Key Attestation +available_since: 24 +--- + +Android provides the [Key Attestation](https://developer.android.com/training/articles/security-key-attestation) feature, which allows a verifier (usually a remote server) to cryptographically verify the security properties of asymmetric keys managed through the Android KeyStore (@MASTG-KNOW-0043). By inspecting and validating the signed certificate chain of the generated (public) key, a third party can establish trust in the integrity of both the device environment (see @MASTG-KNOW-0120) and the identity of the calling application (see @MASTG-KNOW-0119). Starting with Android 8.0 (API level 26), key attestation became mandatory for all new devices that require device certification for Google apps. These devices use attestation keys signed by the [Google Hardware Attestation Root Certificate](https://developer.android.com/training/articles/security-key-attestation#root_certificate). + +@MASTG-KNOW-0035 is solving the same cryptographic goals. + +## Chain of Trust + +A chain of trust is only as strong as its weakest link, and key attestation plays a fundamental role in establishing it. For a remote server to trust a client, it must first trust the platform the client runs on, and then trust the application running on that platform. + +A typical chain of trust for a mobile application is built from the bottom up: + +- **Device integrity** is the foundation: if the device runs a modified OS, has an unlocked bootloader, or is rooted, an attacker controls the execution environment and can bypass, spoof, or intercept any application-level control. +- **Application integrity** builds on top: even on a trusted device, a repackaged or patched APK may have had its security controls removed or its behavior altered. +- **In-app security controls** are the final layer: only when both the device and the binary are trusted can controls (such as TLS certificate pinning) be meaningfully enforced. + +If any link in this chain is broken, the security guarantees of all higher layers are void. Tools like @MASTG-TOOL-0001 exploit exactly this - they are designed to operate on untrusted devices or tampered apps where these controls are absent. + +## Attestation Certificate Chain + +During an asymmetric key pair generation in the Android KeyStore using [`KeyGenParameterSpec.Builder`](https://developer.android.com/reference/kotlin/android/security/keystore/KeyGenParameterSpec.Builder), the keys can be generated and remain in hardware-backed security modules by enabling [`setIsStrongBoxBacked`](https://developer.android.com/reference/kotlin/android/security/keystore/KeyGenParameterSpec.Builder#setisstrongboxbacked) (available since API level 28) if the device allows it. This uses the [StrongBox Keymaster](https://developer.android.com/training/articles/keystore#HardwareSecurityModule) for stronger isolation. If StrongBox is not available on the device, Trusted Execution Environment (TEE) would be used as secure hardware. If no secure hardware is available, the key would be generated in software. + +After key generation, the public key certificate (and its chain) can be requested and inspected as described in [Reading the X.509 Certificate](#reading-the-x509-certificate). A challenge (nonce) can be included to provide freshness, proving the attestation was generated in response to a specific request. In this scenario, the verifier (usually a server) generates a random challenge, and the client passes it to [`setAttestationChallenge`](https://developer.android.com/reference/kotlin/android/security/keystore/KeyGenParameterSpec.Builder#setattestationchallenge) during key generation. When the challenge is set and if secure hardware supports attestation, this certificate will be signed by a chain of certificates rooted at a trustworthy CA key. Otherwise (such as when the public key was created in software), the chain will be rooted at an untrusted certificate. If the attestation challenge was not set, the public key certificate will be self-signed. Therefore, to obtain a certificate chain rooted at a trustworthy CA key, both hardware support and the attestation challenge are required. + +If the chain's root certificate is the [Google Hardware Attestation Root Certificate](https://developer.android.com/training/articles/security-key-attestation#root_certificate) and the hardware-backed storage checks are satisfied, this provides assurance that the device supports hardware-level key attestation and that the key is stored in a keystore that Google considers secure. If the certificate or the attestation chain has any other root certificate, Google does not make any claims about the security of the hardware. This does not mean the key or the device is compromised. It just indicates the key is not confirmed to be in secure hardware by Google. + +From API level 31, if an attestation challenge is set, [`setDevicePropertiesAttestationIncluded`](https://developer.android.com/reference/kotlin/android/security/keystore/KeyGenParameterSpec.Builder#setdevicepropertiesattestationincluded) can also be enabled to include device properties (brand, device, manufacturer, model, product) in the attestation. + +The resulting certificate chain is then returned to the verifier for inspection: + +- The certificate chain up to the root, including validity, integrity, and [revocation status](https://developer.android.com/training/articles/security-key-attestation#certificate_status). +- The attestation extension data in the leaf certificate, which follows an [ASN.1 schema](https://source.android.com/docs/security/features/keystore/attestation#schema), including: + - `attestationChallenge`: the challenge value matches the server-issued nonce. + - `attestationSecurityLevel`: the Keymaster security level (`Software`, `TrustedEnvironment`, or `StrongBox`). + - `softwareEnforced` / `hardwareEnforced`: the key pair attributes (see [Key Properties](#key-properties) below). + - `rootOfTrust`: device integrity signals including `verifiedBootState`, `verifiedBootKey`, and `deviceLocked` (see @MASTG-KNOW-0120). + - `attestationApplicationId`: the application identity including package name and signing certificate digests (see @MASTG-KNOW-0119). + +The `attestationSecurityLevel` determines the overall trust of the attestation. The higher the security level, the more confident the verifier can be that the reported device and application properties reflect reality and cannot be falsified by the Android OS. For device integrity signals and how to interpret them (e.g., unlocked bootloader), see @MASTG-KNOW-0120. For application identity signals and how to interpret them (e.g., repackaged or tampered app), see @MASTG-KNOW-0119. + +**Example:** + +The following example shows how to configure a `KeyGenParameterSpec` for key attestation using an EC key pair on the `secp256r1` (P-256) curve with StrongBox, an attestation challenge, and device properties attestation: + +```kotlin +val keyGenParameterSpec = KeyGenParameterSpec + .Builder( + keyAlias, + KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY + ) + .setDigests(KeyProperties.DIGEST_SHA256) + .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1")) + .setIsStrongBoxBacked(true) + .setAttestationChallenge(attestationChallenge) + .setDevicePropertiesAttestationIncluded(true) + .build() +``` + +For a reference implementation, see [Dionysis Lorentzos' Android-Security sample](https://github.com/Diolor/Android-Security/blob/main/app/src/main/java/dio/security/crypto/KeyManager.kt#L45-L70). + +## Reading the X.509 Certificate + +The returned [X509Certificate](https://developer.android.com/reference/kotlin/java/security/cert/X509Certificate) chain from [`KeyStore.getCertificateChain(alias)`](https://developer.android.com/reference/kotlin/java/security/KeyStore#getcertificatechain) can be inspected to determine the key properties. X.509 certificates are described by [ASN.1 format](https://source.android.com/docs/security/features/keystore/attestation#tbscertificate-sequence) and the Android-specific extensions (certificate's payload) can be requested with OID `1.3.6.1.4.1.11129.2.1.17`. This attestation extension content is described by the [ASN.1 schema KeyDescription](https://source.android.com/docs/security/features/keystore/attestation#schema). + +For a sample decoding functionality of X.509 certificate's payload, you may consult [Dionysis Lorentzos' - Simple attestation converter](https://github.com/Diolor/Android-Security/blob/main/app/src/main/java/dio/security/crypto/attestation/Attestation.kt#L34-L64). + +The extension content contains the `attestationSecurityLevel` and `keyMintSecurityLevel` values, which indicate the methodology used to generate the key pair, and their respective `attestationVersion` and `keyMintVersion` values, which determine the schema used to encode the attestation extension. There are also two [`AuthorizationList`](https://source.android.com/docs/security/features/keystore/attestation#authorizationlist-fields) fields that describe the configuration of the attested key pair: `softwareEnforced` and `hardwareEnforced` for properties enforced by the Android OS or by the TEE or StrongBox respectively. + +### Key Properties + +Some of the key properties are described below: + +- The `attestationSecurityLevel` and `keyMintSecurityLevel` contain the **`SecurityLevel`** indicating how the key was generated. You can refer to its [schema](https://source.android.com/docs/security/features/keystore/attestation#securitylevel-values) and [Hardware versus software enforcement](https://source.android.com/docs/security/features/keystore/features#hardware_vs_software_enforcement) for more information. + - **`Software`**: Attestation was performed in the Android system, with no hardware-backed guarantee. This is usually what emulators use. + - **`TrustedEnvironment`**: Attestation was performed by the Trusted Execution Environment (TEE), providing hardware-enforced isolation via a dedicated CPU execution environment. + - **`StrongBox`**: Attestation was performed by a dedicated secure element (StrongBox), offering the highest level of hardware protection. +- **`attestationChallenge`**: The nonce provided by the server and passed to `setAttestationChallenge()` during key generation. The server checks this value to confirm the attestation was produced in response to its specific request to prevent replay attacks. +- The **`softwareEnforced`** or **`hardwareEnforced`** `AuthorizationList` contains the following fields: + - **`rootOfTrust`**: Device integrity signals used for device attestation (see @MASTG-KNOW-0120), including: + - **`verifiedBootState`**: Whether the device's boot chain was verified as unmodified (`Verified`, `SelfSigned`, `Unverified`, or `Failed`). + - **`verifiedBootKey`**: The public key used to verify the boot image. On unmodified devices this matches the OEM's embedded key. + - **`deviceLocked`**: Whether the bootloader is locked. An unlocked bootloader indicates the device may have been modified. + - **`attestationApplicationId`**: Application identity fields used for application attestation (see @MASTG-KNOW-0119), including: + - **`packageInfos`**: A set of entries each containing the app's `packageName` and `version` code. + - **`signatureDigests`**: SHA-256 digests of the app's signing certificates, allowing the server to verify the app has not been repackaged. + - **Authentication requirements**: Whether user authentication (e.g., biometric via [`setUserAuthenticationRequired`](https://developer.android.com/reference/kotlin/android/security/keystore/KeyGenParameterSpec.Builder#setuserauthenticationrequired)) is required before key use, indicated by fields such as `noAuthRequired` and `userAuthType`. + +## Interpreting the Certificate Chain + +When reading the certificate chain, a verifier can inspect: + +- The `rootOfTrust` object for device integrity signals — see @MASTG-KNOW-0120 for field meanings and how to interpret them. +- The `attestationApplicationId` object for application identity signals — see @MASTG-KNOW-0119 for field meanings and how to interpret them. diff --git a/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0118.md b/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0118.md index 0be05b95788..0ec2355e0eb 100644 --- a/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0118.md +++ b/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0118.md @@ -40,13 +40,14 @@ Several commercial solutions provide RASP capabilities as SDKs or compiler toolc The biggest difference is that SDKs operate as standalone frameworks delivered within the application. They sit alongside the app's core functionality, and the app must 'call out' to the security SDK. By contrast, compiler toolchains inject detections between user code and randomly obfuscate the resulting code at each compilation. -### Google Play Integrity API +## RASP vs. Device and App Attestation -For Android apps distributed through Google Play, the Play Integrity API (see @MASTG-KNOW-0035) provides device and app attestation. It can verify: +RASP and attestation are complementary but fundamentally different approaches to integrity protection: -- The app binary is the original, unmodified version from Google Play -- The app is running on a genuine Android device -- The device passes basic integrity checks +- **RASP** runs entirely within the app process. It performs self-checks at runtime and reacts locally - terminating the session, wiping data, or alerting a backend. Because RASP logic lives inside the app, a sufficiently privileged attacker (e.g., on a rooted device) can inspect, disable, or spoof it. +- **Attestation** relies on a trusted third party to produce a cryptographically signed verdict about the device and app state, verified server-side and outside the attacker's reach. Android provides two complementary mechanisms: hardware Key Attestation (see @MASTG-KNOW-0044), which proves that a cryptographic key was generated and stored inside a TEE or StrongBox; and the Play Integrity API (see @MASTG-KNOW-0035), which issues a server-verifiable verdict covering device integrity, app authenticity, and installation source. + +Because attestation verdicts are evaluated server-side, they are significantly harder to spoof than client-side RASP checks. However, attestation is not a drop-in replacement for RASP: it operates asynchronously, requires network connectivity, and does not provide real-time, in-process reaction capabilities. The failure modes also differ - when attestation fails, the _server_ rejects the request before any sensitive operation proceeds; when RASP detects a threat, the _app_ reacts locally, which is valuable for offline scenarios but is subject to bypass on a compromised device. The two approaches work best when combined: attestation to establish verified trust at session start, and RASP to detect and respond to threats during execution. ## Limitations diff --git a/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0119.md b/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0119.md index 6f910ba8318..626ae2b71ef 100644 --- a/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0119.md +++ b/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0119.md @@ -1,7 +1,20 @@ --- masvs_category: MASVS-RESILIENCE platform: android -title: Key Attestation -status: placeholder -note: For now, see https://developer.android.com/privacy-and-security/security-key-attestation +title: Application Attestation via Hardware-Backed Key Attestation --- + +Application attestation uses the `attestationApplicationId` field in the `softwareEnforced` or `hardwareEnforced` `AuthorizationList` of a Key Attestation (@MASTG-KNOW-0044) certificate chain, which allows a verifier (usually a remote server) to verify the identity and integrity of the calling application. For the full list of fields and their meanings, see @MASTG-KNOW-0044. + +## Attestation Application ID Fields + +The `attestationApplicationId` object contains the following fields: + +- **`packageInfos`**: A set of entries, each containing: + - `packageName`: The application's package identifier (e.g., `com.example.app`). + - `version`: The application's version code at the time of key generation. +- **`signatureDigests`**: A set of SHA-256 digests of the DER-encoded X.509 signing certificates used to sign the application APK. These allow the server to verify the app's signing identity and detect repackaged or cloned variants. The server must pre-provision the expected digests from the legitimate app's signing certificate (e.g., obtained at app release time or from the Play Console) and compare them against this field during attestation verification. + +## Limitations + +Application attestation via Key Attestation reflects the identity of the application **at the time the key was generated**. A key generated by a legitimate version of the app remains associated with that version's package name and signing certificate, even if the app is later updated, reinstalled, or replaced with a malicious version on the same device. diff --git a/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0120.md b/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0120.md index 34050e13f0d..d745424f477 100644 --- a/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0120.md +++ b/knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0120.md @@ -1,7 +1,27 @@ --- masvs_category: MASVS-RESILIENCE platform: android -title: Device Attestation -status: placeholder -note: For now, see https://developer.android.com/google/play/integrity/verdicts#device-integrity-field +title: Device Attestation via Hardware-Backed Key Attestation --- + +Device attestation uses the `rootOfTrust` field in the `AuthorizationList` of a Key Attestation (@MASTG-KNOW-0044) certificate chain, which allows a verifier (usually a remote server) to verify the integrity of the device's software stack. For the full list of fields and their meanings, see @MASTG-KNOW-0044. + +## Root of Trust Fields + +The `rootOfTrust` object populated by the secure hardware (TEE or StrongBox) during key generation is placed in the `hardwareEnforced` `AuthorizationList`. Because it is hardware-enforced, these values cannot be tampered with by the Android OS. When the `attestationSecurityLevel` is `Software`, the `rootOfTrust` may instead appear in the `softwareEnforced` list, providing no hardware-backed guarantee. + +The `rootOfTrust` object contains the following fields: + +- **`verifiedBootState`**: Reflects the result of Android's [Verified Boot](https://source.android.com/docs/security/features/verifiedboot) process: + - **`Verified`**: The entire boot chain, from bootloader to system partition, was verified against known-good OEM keys. This is the expected state for an unmodified production device. + - **`SelfSigned`**: The device booted with a user-installed root of trust (e.g., a custom key). The bootloader is unlocked and a user-provided key was accepted. + - **`Unverified`**: No verification was performed. The bootloader is unlocked and no custom key was set. + - **`Failed`**: Verification was attempted but failed. The device should not have booted in this state under normal conditions. +- **`verifiedBootKey`**: The public key used to verify the boot image. On unmodified production devices, this matches the OEM's embedded root-of-trust key. +- **`deviceLocked`**: `true` if the bootloader is locked, preventing unauthorized modifications to the system partition. + +## Limitations + +Device attestation via Key Attestation reflects the state of the device **at the time the key was generated**, not at the time of any subsequent API call or use. A key generated on a clean device retains its attestation even if the device is later rooted or its bootloader is unlocked after key generation. + +Device attestation also cannot detect all forms of compromise. A device that was unlocked and re-locked, or that had its verified boot state manipulated at a hardware level (which is extremely difficult but theoretically possible), would still pass verification. It cannot replace a comprehensive device integrity strategy. diff --git a/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0044.md b/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0044.md deleted file mode 100644 index dcc6b47177e..00000000000 --- a/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0044.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -masvs_category: MASVS-STORAGE -platform: android -title: Key Attestation -available_since: 24 ---- - -For applications that rely heavily on @MASTG-KNOW-0043 for business-critical operations, such as multi-factor authentication using cryptographic primitives and secure client-side storage of sensitive data, Android provides the [Key Attestation](https://developer.android.com/training/articles/security-key-attestation "Key Attestation") feature, which helps analyze and verify the security of cryptographic material managed through the Android KeyStore. Starting with Android 8.0 (API level 26), key attestation became mandatory for all new devices (Android 7.0 or higher) that require device certification for Google apps. These devices use attestation keys signed by the [Google Hardware Attestation Root Certificate](https://developer.android.com/training/articles/security-key-attestation#root_certificate "Google Hardware Attestation Root Certificate"), and these keys can be verified through the key attestation process. - -During key attestation, we can specify the alias of a key pair and, in return, receive a certificate chain that we can use to verify the properties of that key pair. If the chain's root certificate is the [Google Hardware Attestation Root certificate](https://developer.android.com/training/articles/security-key-attestation#root_certificate "Google Hardware Attestation Root certificate") and the hardware-backed key pair storage checks are satisfied, this provides assurance that the device supports hardware-level key attestation and that the key is stored in the hardware-backed keystore that Google believes to be secure. Alternatively, if the attestation chain has any other root certificate, Google does not make any claims about the security of the hardware. - -Although the key attestation process can be implemented directly in the application, it is recommended that it be implemented on the server side for security reasons. The following are the high-level guidelines for the secure implementation of Key Attestation: - -- The server should initiate the key attestation process by generating a secure random number using a CSPRNG (Cryptographically Secure Random Number Generator) and sending it to the client as a challenge. -- The client should call the `setAttestationChallenge` API with the challenge from the server, then retrieve the attestation certificate chain using the `KeyStore.getCertificateChain` method. -- The attestation response should be sent to the server for verification, and the following checks should be performed: - - Verify the certificate chain up to the root and perform certificate sanity checks, including validity, integrity, and trustworthiness. Check the [Certificate Revocation Status List](https://developer.android.com/training/articles/security-key-attestation#certificate_status "Certificate Revocation Status List") maintained by Google to confirm that none of the certificates in the chain were revoked. - - Check whether the root certificate is signed with the Google attestation root key, which makes the attestation process trustworthy. - - Extract the attestation [certificate extension data](https://developer.android.com/training/articles/security-key-attestation#certificate_schema "Certificate extension data schema") from the first element of the certificate chain and perform the following checks: - - Verify that the attestation challenge matches the value generated by the server when initiating the attestation process. - - Verify the signature in the key attestation response. - - Verify the Keymaster's security level to determine whether the device has a secure key storage mechanism. The security level will be one of `Software`, `TrustedEnvironment`, or `StrongBox` (see @MASTG-KNOW-0043 for details on hardware-backed KeyStore). The client supports hardware-level key attestation when the security level is `TrustedEnvironment` or `StrongBox` and the attestation certificate chain includes a root certificate signed by the Google attestation root key. - - Verify the client's status to ensure a full chain of trust - verified boot key, locked bootloader, and verified boot state. - - Additionally, you can verify the key pair's attributes, such as purpose, access time, authentication requirement, etc. - -!!! note - If that process fails for any reason, the key is not stored in the security hardware. That does not mean the key is compromised. - -The typical Android Keystore attestation response is as follows: - -```json -{ - "fmt": "android-key", - "authData": "9569088f1ecee3232954035dbd10d7cae391305a2751b559bb8fd7cbb229bd...", - "attStmt": { - "alg": -7, - "sig": "304402202ca7a8cfb6299c4a073e7e022c57082a46c657e9e53...", - "x5c": [ - "308202ca30820270a003020102020101300a06082a8648ce3d040302308188310b30090603550406130...", - "308202783082021ea00302010202021001300a06082a8648ce3d040302308198310b300906035504061...", - "3082028b30820232a003020102020900a2059ed10e435b57300a06082a8648ce3d040302308198310b3..." - ] - } -} -``` - -In the JSON snippet above, the keys have the following meanings: - -- `fmt`: Attestation statement format identifier -- `authData`: Authenticator data for the attestation -- `alg`: Algorithm used for the signature -- `sig`: Signature -- `x5c`: Attestation certificate chain - -!!! note - The `sig` is generated by concatenating `authData` and `clientDataHash` (the challenge sent by the server) and signing with the credential's private key using the `alg` signing algorithm. The same is verified on the server side using the public key in the first certificate. - -For more information on the implementation guidelines, you can refer to [Google Sample Code](https://github.com/google/android-key-attestation/blob/master/src/main/java/com/android/example/KeyAttestationExample.java "Google Sample Code For Android Key Attestation"). - -From a security analysis perspective, the following checks can help ensure the secure implementation of Key Attestation: - -- Check whether key attestation is implemented entirely on the client side. In that case, it can be more easily bypassed through application tampering, method hooking, etc. -- Check whether the server uses a random challenge when initiating key attestation. Failing to do so results in an insecure implementation vulnerable to replay attacks. Also, verify the challenge's randomness. -- Check whether the server verifies the integrity of the key attestation response. -- Check whether the server performs basic checks, such as integrity and trust verification, validity, etc., on the certificates in the chain. diff --git a/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0045.md b/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0045.md index 4218b8c9108..5b52f11dbc8 100644 --- a/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0045.md +++ b/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0045.md @@ -4,7 +4,7 @@ platform: android title: Secure Key Import into Keystore --- -Android 9 (API level 28) adds the ability to import keys securely into the `AndroidKeystore`. First, `AndroidKeystore` generates a key pair using `PURPOSE_WRAP_KEY`, which should also be protected with an attestation certificate. This pair aims to protect the Keys being imported to `AndroidKeystore`. The encrypted keys are generated as ASN.1-encoded message in the `SecureKeyWrapper` format, which also contains a description of the ways the imported key is allowed to be used. The keys are then decrypted inside the `AndroidKeystore` hardware belonging to the specific device that generated the wrapping key, so that they never appear as plaintext in the device's host memory. +Android 9 (API level 28) adds the ability to import keys securely into the `AndroidKeystore`. First, `AndroidKeystore` generates a key pair using `PURPOSE_WRAP_KEY`, which should also be protected with an attestation certificate (see @MASTG-KNOW-0044). This pair aims to protect the keys being imported to `AndroidKeystore`. The encrypted keys are generated as ASN.1-encoded message in the `SecureKeyWrapper` format, which also contains a description of the ways the imported key is allowed to be used. The keys are then decrypted inside the `AndroidKeystore` hardware belonging to the specific device that generated the wrapping key, so that they never appear as plaintext in the device's host memory. Secure key import into Keystore diff --git a/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0047.md b/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0047.md index b111b2aacf6..866397474ee 100644 --- a/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0047.md +++ b/knowledge/android/MASVS-STORAGE/MASTG-KNOW-0047.md @@ -21,7 +21,7 @@ Storing a Key - from most secure to least secure: ## Storing Keys Using Hardware-backed Android KeyStore -You can use the hardware-backed Android KeyStore (@MASTG-KNOW-0043) if the device is running Android 7.0 (API level 24) and above with available hardware component (Trusted Execution Environment (TEE) or a Secure Element (SE)). You can even verify that the keys are hardware-backed by using the guidelines provided for the secure implementation of Key Attestation (@MASTG-KNOW-0044). If a hardware component is not available and/or support for Android 6.0 (API level 23) and below is required, then you might want to store your keys on a remote server and make them available after authentication. +You can use the hardware-backed Android KeyStore (@MASTG-KNOW-0043) if the device is running Android 7.0 (API level 24) and above with an available hardware component (Trusted Execution Environment (TEE) or a Secure Element (SE)). The keys can be verified as hardware-backed through Key Attestation (@MASTG-KNOW-0044). If a hardware component is not available and/or support for Android 6.0 (API level 23) and below is required, then you might want to store your keys on a remote server and make them available after authentication. ## Storing Keys on the Server diff --git a/knowledge/generic/MASVS-RESILIENCE/MASTG-KNOW-0x01.md b/knowledge/generic/MASVS-RESILIENCE/MASTG-KNOW-0x01.md new file mode 100644 index 00000000000..8c83c2c22bd --- /dev/null +++ b/knowledge/generic/MASVS-RESILIENCE/MASTG-KNOW-0x01.md @@ -0,0 +1,26 @@ +--- +masvs_category: MASVS-RESILIENCE +platform: generic +title: Firebase App Check +--- + +[Firebase App Check](https://firebase.google.com/docs/app-check) is a Google service that protects backend resources (Firebase services and custom backends) from abuse by verifying that requests originate from legitimate, unmodified instances of the app running on genuine devices. It delegates the actual attestation to platform-specific providers and forwards a short-lived token to the backend, which validates it against Google's App Check servers. + +## Attestation Providers + +App Check is provider-agnostic. The app registers a provider at startup. Both platforms offer a debug provider that can be used for development and CI environments. + +### Android + +- **Play Integrity API** (recommended) - issues a verdict covering device integrity, app authenticity, and installation source (see @MASTG-KNOW-0035). + +### iOS + +- **App Attest** (recommended, iOS 14+) - Apple's hardware-backed attestation service using the Secure Enclave (see @MASTG-KNOW-0x03). +- **DeviceCheck** (fallback, iOS 11+) - an older Apple service providing per-device flags persisted by Apple, without cryptographic app-identity guarantees (see @MASTG-KNOW-0x02). + +## Limitations + +- **Server-side enforcement required**: App Check tokens are only meaningful if the backend validates them. Skipping server-side checks renders the integration ineffective. +- **Depends on underlying provider availability**: All limitations of the underlying provider apply (see @MASTG-KNOW-0035 for Play Integrity limitations). App Attest requires iOS 14+ and is unavailable on simulators without the debug provider. +- **Token replay**: App Check tokens have a short TTL but are bearer tokens. They should be transmitted over TLS, not logged or caches locally. diff --git a/knowledge/ios/MASVS-RESILIENCE/MASTG-KNOW-0x02.md b/knowledge/ios/MASVS-RESILIENCE/MASTG-KNOW-0x02.md new file mode 100644 index 00000000000..973116d8e35 --- /dev/null +++ b/knowledge/ios/MASVS-RESILIENCE/MASTG-KNOW-0x02.md @@ -0,0 +1,13 @@ +--- +masvs_category: MASVS-RESILIENCE +platform: ios +title: DeviceCheck +--- + +[DeviceCheck](https://developer.apple.com/documentation/devicecheck "DeviceCheck documentation") is an Apple framework that lets apps persistently store two bits of data per device on Apple's servers. The stored state survives app reinstallation, device transfers, and factory resets, and can optionally be cleared periodically by the developer. + +DeviceCheck is typically used to mitigate fraud by restricting access to sensitive resources — for example, limiting a promotional offer to once per device or flagging suspicious devices. Because the flags are stored server-side by Apple and keyed to the device hardware, they cannot be reset by the user without Apple's involvement. + +However, DeviceCheck does not attest the app's identity or verify that the app binary is unmodified. It provides no cryptographic proof that the request came from a genuine, unaltered app running on a real device. For stronger app-identity guarantees, use App Attest (see @MASTG-KNOW-0x03) instead. + +Both DeviceCheck and App Attest serve as iOS attestation providers for Firebase App Check (see @MASTG-KNOW-0x01). diff --git a/knowledge/ios/MASVS-RESILIENCE/MASTG-KNOW-0x03.md b/knowledge/ios/MASVS-RESILIENCE/MASTG-KNOW-0x03.md new file mode 100644 index 00000000000..acb40de64ff --- /dev/null +++ b/knowledge/ios/MASVS-RESILIENCE/MASTG-KNOW-0x03.md @@ -0,0 +1,28 @@ +--- +masvs_category: MASVS-RESILIENCE +platform: ios +title: App Attest +available_since: 14 +--- + +[App Attest](https://developer.apple.com/documentation/devicecheck/validating-apps-that-connect-to-your-server "Validating apps that connect to your server") is part of the DeviceCheck framework and provides hardware-backed app attestation on iOS 14+. It allows a server to cryptographically verify that a request originates from a legitimate, unmodified instance of the app running on a genuine Apple device. + +## How It Works + +The process uses a device-bound key pair generated in the Secure Enclave: + +1. The app generates an attestation key via `DCAppAttestService.generateKey()`. +2. It requests an attestation certificate from Apple's servers, binding the key to the app's App ID and the device's hardware identifier. +3. For each sensitive request, the app generates an assertion (a signature over a hash of the request payload) using `DCAppAttestService.generateAssertion()`. +4. The server verifies the assertion against the attested public key and checks the App ID, receipt freshness, and risk metric returned by Apple. + +For more detailed information, refer to the [WWDC 2021](https://developer.apple.com/videos/play/wwdc2021/10244 "WWDC 2021"). + +## Limitations + +- **iOS 14+ only**: App Attest is unavailable on older OS versions. DeviceCheck (see @MASTG-KNOW-0x02) can serve as a fallback. +- **Unavailable on simulators**: The Secure Enclave is not present in the iOS simulator. Use the debug provider (e.g., via Firebase App Check) during development. +- **Not jailbreak detection**: App Attest attests the app binary and device hardware but does not detect a compromised operating system. A jailbroken device may still pass attestation. +- **Requires network**: Attestation and assertion verification require a live connection to Apple's servers. + +Both App Attest and DeviceCheck are used as iOS attestation providers for Firebase App Check (see @MASTG-KNOW-0x01). diff --git a/rules/mastg-android-key-attestation-missing-challenge.yml b/rules/mastg-android-key-attestation-missing-challenge.yml new file mode 100644 index 00000000000..f877105298a --- /dev/null +++ b/rules/mastg-android-key-attestation-missing-challenge.yml @@ -0,0 +1,11 @@ +rules: + - id: mastg-android-key-attestation-missing-challenge + severity: WARNING + languages: + - java + metadata: + summary: Detects Key Attestation performed without a server-issued challenge, removing freshness guarantees. + message: "[MASVS-RESILIENCE-2] KeyGenParameterSpec built without setAttestationChallenge. The server cannot verify when this attestation was produced." + patterns: + - pattern: new KeyGenParameterSpec.Builder(...). ... .build() + - pattern-not: new KeyGenParameterSpec.Builder(...). ... .setAttestationChallenge(...). ... .build() \ No newline at end of file diff --git a/tests-beta/android/MASVS-RESILIENCE/MASTG-TEST-0x01.md b/tests-beta/android/MASVS-RESILIENCE/MASTG-TEST-0x01.md new file mode 100644 index 00000000000..4a2d64d51cb --- /dev/null +++ b/tests-beta/android/MASVS-RESILIENCE/MASTG-TEST-0x01.md @@ -0,0 +1,34 @@ +--- +platform: android +title: Testing Device and App Binary Integrity Verification +id: MASTG-TEST-0x01 +type: [static] +weakness: MASWE-0104 +best-practices: [MASTG-BEST-0x01] +profiles: [R] +knowledge: [MASTG-KNOW-0035, MASTG-KNOW-0044] +status: placeholder +--- + +## Overview + +This test checks whether the app implements device and app integrity verification by statically analyzing the app binary for relevant API usage patterns. These may include calls to the Google Play Integrity API or Key Attestation related APIs (@MASTG-KNOW-0044). + +See @MASTG-KNOW-0044 for more information on Key Attestation and the specific APIs and fields to look for. + +## Steps + +1. Use @MASTG-TECH-0013 to reverse engineer the app. +2. Use @MASTG-TECH-0014 to look for uses of [`IntegrityManagerFactory`](https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/IntegrityManagerFactory) (Google Play Integrity API) and [`KeyStore.getCertificateChain(alias)`](https://developer.android.com/reference/kotlin/java/security/KeyStore#getcertificatechain) and [`setAttestationChallenge`](https://developer.android.com/reference/kotlin/android/security/keystore/KeyGenParameterSpec.Builder#setattestationchallenge) (manual Key Attestation APIs). + +## Observation + +The output should contain a list of locations where those APIs are used. + +## Evaluation + +The test case fails if none of the above APIs are found in the app. +The test case also fails if the app implements `getCertificateChain` but no instances of `setAttestationChallenge` are found to ensure freshness of the attestation. + +!!! note + Even if the app implements app integrity verification APIs, the server must still verify the results from Google Play Integrity API or certificate chains for the control to be trusted. See [Interpreting the Certificate Chain](../../../knowledge/android/MASVS-RESILIENCE/MASTG-KNOW-0044.md#interpreting-the-certificate-chain) for more information. diff --git a/tests-beta/ios/MASVS-RESILIENCE/MASTG-TEST-0x02.md b/tests-beta/ios/MASVS-RESILIENCE/MASTG-TEST-0x02.md new file mode 100644 index 00000000000..e3941ae2002 --- /dev/null +++ b/tests-beta/ios/MASVS-RESILIENCE/MASTG-TEST-0x02.md @@ -0,0 +1,39 @@ +--- +platform: ios +title: Testing Device and App Binary Integrity Verification +id: MASTG-TEST-0x02 +apis: [DCAppAttestService, DCAppAttestService.generateKey, DCAppAttestService.attestKey, DCAppAttestService.generateAssertion] +type: [static] +weakness: MASWE-0104 +best-practices: [MASTG-BEST-0x01] +profiles: [R] +knowledge: [MASTG-KNOW-0x03] +status: placeholder +--- + +## Overview + +This test checks whether the app implements device and app integrity verification by statically analyzing the app binary for relevant API usage patterns. On iOS, modern app attestation is provided by [App Attest](https://developer.apple.com/documentation/devicecheck), part of the DeviceCheck framework, which lets a server cryptographically verify that a request comes from a genuine, unmodified instance of the app running on a real Apple device (@MASTG-KNOW-0x03). + +See @MASTG-KNOW-0x03 for more information on App Attest and the specific APIs and fields to look for. + +## Steps + +1. Use @MASTG-TECH-0058 to extract the relevant binaries from app package. +2. Use @MASTG-TECH-0066 to look for the relevant APIs in the app binaries. + +## Observation + +The output should contain a list of locations where the App Attest APIs are used. + +## Evaluation + +The test case fails if none of the App Attest APIs (`DCAppAttestService.generateKey`, `attestKey`, `generateAssertion`) are found in the app. + +**Further Validation Required:** + +The presence of these APIs does not by itself confirm a robust implementation. The following properties cannot be determined from a reference search alone and require manual or dynamic analysis: + +- The `clientDataHash` passed to `attestKey` and `generateAssertion` must be derived from a one-time, server-provided challenge; otherwise the attestation can be replayed. +- The app should generate an assertion (`generateAssertion`) for sensitive requests, not only attest the key once (`attestKey`), so that ongoing requests remain bound to the attested app instance. +- The server must verify the attestation object and assertions against Apple's [published verification steps](https://developer.apple.com/documentation/devicecheck/validating-apps-that-connect-to-your-server); client-side use of `DCAppAttestService` alone provides no security guarantee.