-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Port MASTG-TEST-0018 (Testing Biometric Authentication) to MASTG v2 #3560
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Copilot
wants to merge
24
commits into
master
Choose a base branch
from
copilot/add-biometric-authentication-test
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,637
−2
Open
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
87024d9
Initial plan
Copilot 194cce1
Add Android biometric authentication tests and demo for MASTG-TEST-00…
Copilot a31e605
Remove accidentally added package files
Copilot 0b4a607
Remove unused imports from demo code files
Copilot 4693ffb
Finalize biometric authentication test port
Copilot 092bb50
Remove accidentally committed gradle files
Copilot 3e336e2
Add MASTG-TEST-0316 for authentication without explicit user action
Copilot 27deb7a
Fix long line in MASTG-DEMO-0077 markdown
Copilot 3af543c
updated test cases 313, 314 and 316 and added MastgTest.kt and revers…
0267cfb
new test cases added
dfa904d
changed IDs as DEMO-IDs were duplicates
3664490
updated IDs for tests and fixed markdown issues
ed2dd2e
to build the app androidx.biometric dependency is needed and Fragmen…
60595da
update test cases and added build.gradle.kts.libs
fa38f7a
fix Unordered list indentation
3db5f03
updated test cases, semgrep rules and 2 biometric apps for the demos
696ca77
deprecate test case
ed4ca89
update IDs
3230c5c
Address PR review feedback: fix deprecation field names, update test …
Copilot 070bc00
updated semgrep rule event-bound
3b81a00
remove space
50d401a
updated note in tests
cea9ffb
remove empty lines
2b9fa8b
added best practice
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| --- | ||
| title: Enforce Strong Biometrics for Sensitive Operations | ||
| alias: enforce-strong-biometrics-for-sensitive-operations | ||
| id: MASTG-BEST-0031 | ||
| platform: android | ||
| knowledge: [MASTG-KNOW-0001] | ||
| --- | ||
|
|
||
| Apps should use the [`BIOMETRIC_STRONG`](https://developer.android.com/reference/android/hardware/biometrics/BiometricManager.Authenticators) authenticator for sensitive operations protected by biometrics. Using `DEVICE_CREDENTIAL` (PINs, patterns or passwords) are more susceptible to shoulder surfing and social engineering. | ||
|
Check failure on line 9 in best-practices/MASTG-BEST-0031.md
|
||
|
|
||
| For high-security operations (e.g. payments or access to health data), enforcing biometrics only provides strong protection and verifies user presence. | ||
31 changes: 31 additions & 0 deletions
31
demos/android/MASVS-AUTH/MASTG-DEMO-0089/AndroidManifest.xml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| xmlns:tools="http://schemas.android.com/tools"> | ||
|
|
||
| <uses-permission android:name="android.permission.INTERNET" /> | ||
| <uses-permission android:name="android.permission.USE_BIOMETRIC" /> | ||
|
|
||
| <application | ||
| android:allowBackup="true" | ||
| android:dataExtractionRules="@xml/data_extraction_rules" | ||
| android:fullBackupContent="@xml/backup_rules" | ||
| android:icon="@mipmap/ic_launcher" | ||
| android:label="@string/app_name" | ||
| android:roundIcon="@mipmap/ic_launcher_round" | ||
| android:supportsRtl="true" | ||
| android:theme="@style/Theme.MASTestApp" | ||
| tools:targetApi="31"> | ||
| <activity | ||
| android:name=".MainActivity" | ||
| android:exported="true" | ||
| android:windowSoftInputMode="adjustResize" | ||
| android:theme="@style/Theme.MASTestApp"> | ||
| <intent-filter> | ||
| <action android:name="android.intent.action.MAIN" /> | ||
|
|
||
| <category android:name="android.intent.category.LAUNCHER" /> | ||
| </intent-filter> | ||
| </activity> | ||
| </application> | ||
|
|
||
| </manifest> |
39 changes: 39 additions & 0 deletions
39
demos/android/MASVS-AUTH/MASTG-DEMO-0089/MASTG-DEMO-0089.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| --- | ||
| platform: android | ||
| title: Uses of BiometricPrompt with Device Credential Fallback with semgrep | ||
| id: MASTG-DEMO-0089 | ||
| code: [kotlin] | ||
| test: MASTG-TEST-0326 | ||
| --- | ||
|
|
||
| ### Sample | ||
|
|
||
| The following sample demonstrates the use of the `BiometricPrompt` API with different authenticator configurations used in `BiometricPrompt.Builder()`. It shows both weaker configurations that allow fallback to device credentials (PIN, pattern, password), which are more susceptible to compromise (e.g., through shoulder surfing) and secure configurations that requires a strong biometric authentication only. | ||
|
|
||
| {{ MastgTest.kt # MastgTest_reversed.java }} | ||
|
|
||
| ### Steps | ||
|
|
||
| Let's run @MASTG-TOOL-0110 rules against the sample code. | ||
|
|
||
| {{ ../../../../rules/mastg-android-biometric-device-credential-fallback.yml }} | ||
|
|
||
| {{ run.sh }} | ||
|
|
||
| ### Observation | ||
|
|
||
| The output shows all usages of APIs that configure biometric authentication. | ||
|
|
||
| {{ output.txt }} | ||
|
|
||
| ### Evaluation | ||
|
|
||
| The test fails because the output shows references to biometric authentication configurations that allow fallback to device credentials: | ||
|
|
||
| - Line 38: `setAllowedAuthenticators(32783)` is called with `BIOMETRIC_STRONG | DEVICE_CREDENTIAL`, which allows the user to authenticate with either biometrics or their device PIN/pattern/password. The value `32783` is the sum of `32768` and `15`. Decompiled code contains integer values of the [`Authenticator` constants](https://developer.android.com/reference/android/hardware/biometrics/BiometricManager.Authenticators) instead of the name: | ||
| - `BIOMETRIC_STRONG` = 15 (0x000F) | ||
| - `BIOMETRIC_WEAK` = 255 (0x00FF) | ||
| - `DEVICE_CREDENTIAL` = 32768 (0x8000) | ||
| - Also in line 38: [`setDeviceCredentialAllowed(true)`](https://developer.android.com/reference/android/hardware/biometrics/BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)) is called and can give the user the option to authenticate with their device PIN, pattern, or password instead of a biometric. | ||
|
|
||
| For sensitive operations, the app should use [`BIOMETRIC_STRONG`](https://developer.android.com/identity/sign-in/biometric-auth#declare-supported-authentication-types) to enforce biometric-only authentication. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| package org.owasp.mastestapp | ||
|
|
||
| import android.content.Context | ||
| import android.hardware.biometrics.BiometricManager.Authenticators.* | ||
| import android.hardware.biometrics.BiometricPrompt | ||
| import android.os.Build | ||
| import android.os.CancellationSignal | ||
| import android.os.Handler | ||
| import android.os.Looper | ||
| import android.util.Log | ||
| import androidx.annotation.RequiresApi | ||
| import java.util.concurrent.CountDownLatch | ||
|
|
||
| // SUMMARY: This sample demonstrates insecure biometric authentication that allows fallback to device credentials (PIN, pattern, password). | ||
|
|
||
| class MastgTest(private val context: Context) { | ||
sushi2k marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Run in background thread - we'll post UI operations to main thread | ||
| val shouldRunInMainThread = false | ||
|
|
||
| private val mainHandler = Handler(Looper.getMainLooper()) | ||
|
|
||
| @RequiresApi(Build.VERSION_CODES.R) | ||
| fun mastgTest(): String { | ||
| val results = DemoResults("0083") | ||
|
|
||
| // Test 1: DEVICE_CREDENTIAL - This FAILS the security test | ||
| // Allows PIN/pattern/password which is less secure than biometrics | ||
| val latch1 = CountDownLatch(1) | ||
| var authResult1: String? = null | ||
|
|
||
| // FAIL: [MASTG-TEST-0326] Using DEVICE_CREDENTIAL allows fallback to PIN/pattern/password | ||
| val prompt1 = BiometricPrompt.Builder(context) | ||
| .setTitle("Test 1: Device Credential") | ||
| .setSubtitle("Using DEVICE_CREDENTIAL (Security: FAIL)") | ||
| .setDescription("This allows also PIN/pattern/password authentication") | ||
| .setAllowedAuthenticators( | ||
| BIOMETRIC_STRONG or | ||
| DEVICE_CREDENTIAL | ||
| ) | ||
| .setDeviceCredentialAllowed(true) | ||
| .build() | ||
sushi2k marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Post authenticate to main thread | ||
| mainHandler.post { | ||
| prompt1.authenticate( | ||
| CancellationSignal(), | ||
| context.mainExecutor, | ||
| object : BiometricPrompt.AuthenticationCallback() { | ||
| override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { | ||
| Log.d("MASTG-TEST", "DEVICE_CREDENTIAL authentication succeeded") | ||
| authResult1 = "User can authenticate with DEVICE_CREDENTIAL (PIN/pattern/password)" | ||
| results.add( | ||
| Status.FAIL, | ||
| "$authResult1. " + | ||
| "\n🔓 AUTH - Success!\n" + | ||
| "⚠️ Allows also PIN/Pattern/Password\n" + | ||
| "⚠️ Uses DEVICE_CREDENTIAL fallback\n" | ||
| ) | ||
| latch1.countDown() | ||
| } | ||
|
|
||
| override fun onAuthenticationFailed() { | ||
| Log.d("MASTG-TEST", "DEVICE_CREDENTIAL authentication failed") | ||
| authResult1 = "Authentication attempt failed" | ||
| results.add( | ||
| Status.FAIL, | ||
| "$authResult1. " + | ||
| "\n⚠️ AUTH - Failed\n" + | ||
| "⚠️ Allows PIN/Pattern/Password\n" + | ||
| "⚠️ Uses DEVICE_CREDENTIAL fallback\n" | ||
| ) | ||
| latch1.countDown() | ||
| } | ||
|
|
||
| override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { | ||
| Log.d("MASTG-TEST", "DEVICE_CREDENTIAL authentication error: $errString") | ||
| authResult1 = "Authentication error: $errString (code: $errorCode)" | ||
| results.add( | ||
| Status.ERROR, | ||
| "$authResult1. " + | ||
| "\n⚠️ AUTH - Error\n" + | ||
| "⚠️ Allows PIN/Pattern/Password\n" + | ||
| "⚠️ Uses DEVICE_CREDENTIAL fallback\n" | ||
| ) | ||
| latch1.countDown() | ||
| } | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| // Wait for first authentication to complete (background thread waits, main thread is free) | ||
| latch1.await() | ||
|
|
||
|
|
||
| // Test 2: BIOMETRIC_STRONG - This PASSES the security test | ||
| // Only allows Class 3 biometrics (fingerprint, face with depth) | ||
| val latch2 = CountDownLatch(1) | ||
| var authResult2: String? = null | ||
|
|
||
| // PASS: [MASTG-TEST-0326] Using BIOMETRIC_STRONG only requires biometric authentication | ||
| val prompt2 = BiometricPrompt.Builder(context) | ||
| .setTitle("Test 2: Biometric Strong") | ||
| .setSubtitle("Using BIOMETRIC_STRONG (Security: PASS)") | ||
| .setDescription("This only allows Class 3 biometrics") | ||
| .setAllowedAuthenticators(BIOMETRIC_STRONG) | ||
| .setNegativeButton("Cancel", context.mainExecutor) { _, _ -> | ||
| Log.d("MASTG-TEST", "BIOMETRIC_STRONG authentication cancelled") | ||
| authResult2 = "User cancelled authentication" | ||
| latch2.countDown() | ||
| } | ||
| .build() | ||
|
|
||
| // Post authenticate to main thread | ||
| mainHandler.post { | ||
| prompt2.authenticate( | ||
| CancellationSignal(), | ||
| context.mainExecutor, | ||
| object : BiometricPrompt.AuthenticationCallback() { | ||
| override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { | ||
| Log.d("MASTG-TEST", "BIOMETRIC_STRONG authentication succeeded") | ||
| authResult2 = "User authenticated with BIOMETRIC_STRONG" | ||
| results.add( | ||
| Status.PASS, | ||
| "$authResult2. " + | ||
| "\n🔓 AUTH - Success!\n" + | ||
| "✅ Allows only Strong Biometric\n" + | ||
| "\nThis configuration is secure because it only allows Class 3 biometrics." | ||
| ) | ||
| latch2.countDown() | ||
| } | ||
|
|
||
| override fun onAuthenticationFailed() { | ||
| Log.d("MASTG-TEST", "BIOMETRIC_STRONG authentication failed") | ||
| authResult2 = "Authentication attempt failed (biometric not recognized)" | ||
| results.add( | ||
| Status.FAIL, | ||
| "$authResult2. " + | ||
| "\n⚠️ AUTH - Failed\n" | ||
| ) | ||
| latch2.countDown() | ||
| } | ||
|
|
||
| override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { | ||
| Log.d("MASTG-TEST", "BIOMETRIC_STRONG authentication error: $errString") | ||
| authResult2 = "Authentication error: $errString (code: $errorCode)" | ||
| results.add( | ||
| Status.ERROR, | ||
| "$authResult2. " + | ||
| "\n⚠️ AUTH - Error\n" | ||
| ) | ||
| latch2.countDown() | ||
| } | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| // Wait for second authentication to complete | ||
| latch2.await() | ||
|
|
||
| return results.toJson() | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.