From 8d62f44d25c673992e7eed4bdda909367262c089 Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Sun, 3 Nov 2024 10:44:51 +0100 Subject: [PATCH 1/8] initial draft --- .../android/MASVS-AUTH/MASTG-TEST-0x17.md | 26 +++++++++++++++++++ tests/android/MASVS-AUTH/MASTG-TEST-0017.md | 3 +++ weaknesses/MASVS-AUTH/MASWE-0034.md | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md diff --git a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md new file mode 100644 index 0000000000..b8989dceaf --- /dev/null +++ b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md @@ -0,0 +1,26 @@ +--- +platform: android +title: Insecure Implementation of Confirm Credentials +id: MASTG-TEST-0x017 +type: [static, dynamic] +weakness: MASWE-0034 +--- + +## Overview + +The ["Confirm Credential Flow"](../../../Document/0x05f-Testing-Local-Authentication.md#confirm-credential-flow) (since Android 6.0) is a convenience feature to reduce the number of times that a user has to authenticate to the device (e.g. via biometrics). It allows the app to unlock cryptographic materials from the `AndroidKeystore` whenever users unlocked the device within the set time limits (`setUserAuthenticationValidityDurationSeconds`), otherwise the device needs to be unlocked again. + +If the app simply checks whether the user has unlocked a key or not, but the key is not actually used, e.g. to decrypt local storage or a message received from a remote endpoint, the app may be vulnerable to a local authentication bypass. + +## Steps + +1. Reverse engineer the app (@MASTG-TECH-0014) +2. Search for uses of ConfirmCredentials and `setUserAuthenticationValidityDurationSeconds`. + +## Observation + +The output should contain the reverse engineered code that uses Confirm Credential. + +## Evaluation + +The test case fails if the app does not use the key and simply checks if the user authenticated. diff --git a/tests/android/MASVS-AUTH/MASTG-TEST-0017.md b/tests/android/MASVS-AUTH/MASTG-TEST-0017.md index 4b6a5c3284..2e380e82ea 100644 --- a/tests/android/MASVS-AUTH/MASTG-TEST-0017.md +++ b/tests/android/MASVS-AUTH/MASTG-TEST-0017.md @@ -8,6 +8,9 @@ platform: android title: Testing Confirm Credentials masvs_v1_levels: - L2 +status: deprecated +cavered_by: [MASTG-TEST-0x17] +deprecation_reason: New version available in MASTG V2 --- ## Overview diff --git a/weaknesses/MASVS-AUTH/MASWE-0034.md b/weaknesses/MASVS-AUTH/MASWE-0034.md index 56e267fa51..13eb1cbb78 100644 --- a/weaknesses/MASVS-AUTH/MASWE-0034.md +++ b/weaknesses/MASVS-AUTH/MASWE-0034.md @@ -12,6 +12,6 @@ draft: topics: - Confirm Credentials status: draft - +note: double check masvs-v2, the original MASTG-TEST-0017 is AUTH-2 --- From 4f043d104b64d778126a4849283a139a1f3edb66 Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Sun, 3 Nov 2024 12:04:31 +0100 Subject: [PATCH 2/8] more context --- tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md index b8989dceaf..fd64e8ff24 100644 --- a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md +++ b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md @@ -10,6 +10,10 @@ weakness: MASWE-0034 The ["Confirm Credential Flow"](../../../Document/0x05f-Testing-Local-Authentication.md#confirm-credential-flow) (since Android 6.0) is a convenience feature to reduce the number of times that a user has to authenticate to the device (e.g. via biometrics). It allows the app to unlock cryptographic materials from the `AndroidKeystore` whenever users unlocked the device within the set time limits (`setUserAuthenticationValidityDurationSeconds`), otherwise the device needs to be unlocked again. +Typically the app creates a key in the keystore with `setUserAuthenticationValidityDurationSeconds` and tries to encrypt some data with that key. + +If the user was authenticated the app will proceed to the target screen. Otherwise it will prompt the user to re-authenticate. + If the app simply checks whether the user has unlocked a key or not, but the key is not actually used, e.g. to decrypt local storage or a message received from a remote endpoint, the app may be vulnerable to a local authentication bypass. ## Steps From 42e4e3a9d9183e5bf7e1199a2caa4bfcf108d0bb Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Mon, 4 Nov 2024 12:15:37 +0000 Subject: [PATCH 3/8] add fields for OS versions --- tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md index fd64e8ff24..3933c15c63 100644 --- a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md +++ b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md @@ -3,6 +3,8 @@ platform: android title: Insecure Implementation of Confirm Credentials id: MASTG-TEST-0x017 type: [static, dynamic] +available_since: 21 +deprecated_since: 29 weakness: MASWE-0034 --- From 94cb2d415ff3e7865d0406658c51f1bd5b5a92ca Mon Sep 17 00:00:00 2001 From: Carlos Holguera Date: Mon, 4 Nov 2024 17:59:43 +0000 Subject: [PATCH 4/8] update MASWE-0044 title; deprecate MASWE-0034; update steps in the new test; update section in 0x05f --- .../0x05f-Testing-Local-Authentication.md | 4 +++- .../android/MASVS-AUTH/MASTG-TEST-0x17.md | 20 +++++++++---------- weaknesses/MASVS-AUTH/MASWE-0034.md | 8 +++++--- weaknesses/MASVS-AUTH/MASWE-0044.md | 2 +- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Document/0x05f-Testing-Local-Authentication.md b/Document/0x05f-Testing-Local-Authentication.md index 89c450dcdf..442169d47c 100644 --- a/Document/0x05f-Testing-Local-Authentication.md +++ b/Document/0x05f-Testing-Local-Authentication.md @@ -15,7 +15,9 @@ On Android, there are two mechanisms supported by the Android Runtime for local ### Confirm Credential Flow -The confirm credential flow is available since Android 6.0 and is used to ensure that users do not have to enter app-specific passwords together with the lock screen protection. Instead: if a user has logged in to the device recently, then confirm-credentials can be used to unlock cryptographic materials from the `AndroidKeystore`. That is, if the user unlocked the device within the set time limits (`setUserAuthenticationValidityDurationSeconds`), otherwise the device needs to be unlocked again. +The ["Confirm Credential Flow"](https://developer.android.com/about/versions/marshmallow/android-6.0#confirm-credential) (introduced in Android 6.0) is designed to reduce the number of times a user must authenticate to the device (e.g., via biometrics). This flow allows apps to unlock cryptographic materials from the `AndroidKeystore` if the user has unlocked the device within a set time limit, configured using [`setUserAuthenticationValidityDurationSeconds`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationValidityDurationSeconds(int)). If this limit has expired, the device prompts the user to re-authenticate. + +In typical use, the app creates a key in the Keystore configured with `setUserAuthenticationValidityDurationSeconds`, then uses that key to attempt an encryption operation. If the authentication is successful, the app continues to the next step, such as navigating to a target screen. If not, the app will prompt the user to re-authenticate using the Confirm Credentials dialog. Note that the security of Confirm Credentials is only as strong as the protection set at the lock screen. This often means that simple predictive lock-screen patterns are used and therefore we do not recommend any apps which require L2 of security controls to use Confirm Credentials. diff --git a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md index 3933c15c63..86279d3076 100644 --- a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md +++ b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md @@ -5,23 +5,17 @@ id: MASTG-TEST-0x017 type: [static, dynamic] available_since: 21 deprecated_since: 29 -weakness: MASWE-0034 +weakness: MASWE-0044 --- ## Overview -The ["Confirm Credential Flow"](../../../Document/0x05f-Testing-Local-Authentication.md#confirm-credential-flow) (since Android 6.0) is a convenience feature to reduce the number of times that a user has to authenticate to the device (e.g. via biometrics). It allows the app to unlock cryptographic materials from the `AndroidKeystore` whenever users unlocked the device within the set time limits (`setUserAuthenticationValidityDurationSeconds`), otherwise the device needs to be unlocked again. - -Typically the app creates a key in the keystore with `setUserAuthenticationValidityDurationSeconds` and tries to encrypt some data with that key. - -If the user was authenticated the app will proceed to the target screen. Otherwise it will prompt the user to re-authenticate. - -If the app simply checks whether the user has unlocked a key or not, but the key is not actually used, e.g. to decrypt local storage or a message received from a remote endpoint, the app may be vulnerable to a local authentication bypass. +When an app implements a ["Confirm Credential Flow"](../../../Document/0x05f-Testing-Local-Authentication.md#confirm-credential-flow), if it only verifies whether the key is unlocked without actually using it (e.g., for decrypting local storage or validating data from a remote source), it may be vulnerable to local authentication bypass. Attackers could use dynamic instrumentation tools like @MASTG-TOOL-0001 to intercept and manipulate the logic, falsely simulating successful authentication. ## Steps -1. Reverse engineer the app (@MASTG-TECH-0014) -2. Search for uses of ConfirmCredentials and `setUserAuthenticationValidityDurationSeconds`. +1. Reverse engineer the app (@MASTG-TECH-0014). +2. Search for uses of [`setUserAuthenticationRequired`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean)) or [`createConfirmDeviceCredentialIntent`](https://developer.android.com/reference/android/app/KeyguardManager#createConfirmDeviceCredentialIntent(java.lang.CharSequence,%20java.lang.CharSequence)) and [`setUserAuthenticationValidityDurationSeconds`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationValidityDurationSeconds(int)). ## Observation @@ -29,4 +23,8 @@ The output should contain the reverse engineered code that uses Confirm Credenti ## Evaluation -The test case fails if the app does not use the key and simply checks if the user authenticated. +The test case fails if the app only checks whether the key is unlocked without performing actual cryptographic operations, such as decrypting or verifying sensitive data. + +## Mitigation + +Ensure that the app uses the key to decrypt local storage or validate data from a remote source after the user has authenticated. diff --git a/weaknesses/MASVS-AUTH/MASWE-0034.md b/weaknesses/MASVS-AUTH/MASWE-0034.md index 13eb1cbb78..21cec0af80 100644 --- a/weaknesses/MASVS-AUTH/MASWE-0034.md +++ b/weaknesses/MASVS-AUTH/MASWE-0034.md @@ -5,13 +5,15 @@ alias: insecure-confirm-credentials platform: [android] profiles: [L2] mappings: - masvs-v2: [MASVS-AUTH-1] + masvs-v2: [MASVS-AUTH-2] draft: description: https://mas.owasp.org/MASTG/tests/android/MASVS-AUTH/MASTG-TEST-0017/ topics: - Confirm Credentials -status: draft -note: double check masvs-v2, the original MASTG-TEST-0017 is AUTH-2 +status: deprecated --- +!!! warning "Deprecated" + + This weakness was deprecated in favor of @MASWE-0044 because of an overlap in the content. Confirm Credentials is a form of local authentication and becomes a test for that weakness. diff --git a/weaknesses/MASVS-AUTH/MASWE-0044.md b/weaknesses/MASVS-AUTH/MASWE-0044.md index f88d598fd2..3810f4fa78 100644 --- a/weaknesses/MASVS-AUTH/MASWE-0044.md +++ b/weaknesses/MASVS-AUTH/MASWE-0044.md @@ -1,5 +1,5 @@ --- -title: Biometric Authentication is Event-bound +title: Local Authentication is Event-bound id: MASWE-0044 alias: event-bound-biometric-auth platform: [android, ios] From 164e61af424407e8941419be13c45aa207ef2b33 Mon Sep 17 00:00:00 2001 From: Jeroen Beckers Date: Tue, 5 Nov 2024 11:03:41 +0000 Subject: [PATCH 5/8] draft --- Document/0x05f-Testing-Local-Authentication.md | 2 +- tests/android/MASVS-AUTH/MASTG-TEST-0017.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Document/0x05f-Testing-Local-Authentication.md b/Document/0x05f-Testing-Local-Authentication.md index 442169d47c..b85b9f7032 100644 --- a/Document/0x05f-Testing-Local-Authentication.md +++ b/Document/0x05f-Testing-Local-Authentication.md @@ -9,7 +9,7 @@ platform: android During local authentication, an app authenticates the user against credentials stored locally on the device. In other words, the user "unlocks" the app or some inner layer of functionality by providing a valid PIN, password or biometric characteristics such as face or fingerprint, which is verified by referencing local data. Generally, this is done so that users can more conveniently resume an existing session with a remote service or as a means of step-up authentication to protect some critical function. -As stated before in chapter ["Mobile App Authentication Architectures"](0x04e-Testing-Authentication-and-Session-Management.md): The tester should be aware that local authentication should always be enforced at a remote endpoint or based on a cryptographic primitive. Attackers can easily bypass local authentication if no data returns from the authentication process. +As stated before in chapter ["Mobile App Authentication Architectures"](0x04e-Testing-Authentication-and-Session-Management.md): The tester should be aware that local should always be enforced at a remote endpoint if possible, or linked to the system's KeyStore. Attackers can bypass local authentication this is not the case. On Android, there are two mechanisms supported by the Android Runtime for local authentication: the Confirm Credential flow and the Biometric Authentication flow. diff --git a/tests/android/MASVS-AUTH/MASTG-TEST-0017.md b/tests/android/MASVS-AUTH/MASTG-TEST-0017.md index 2e380e82ea..2656e98e12 100644 --- a/tests/android/MASVS-AUTH/MASTG-TEST-0017.md +++ b/tests/android/MASVS-AUTH/MASTG-TEST-0017.md @@ -9,7 +9,7 @@ title: Testing Confirm Credentials masvs_v1_levels: - L2 status: deprecated -cavered_by: [MASTG-TEST-0x17] +covered_by: [MASTG-TEST-0x17] deprecation_reason: New version available in MASTG V2 --- From ce22d56e4dfcd077f56c9da994ba7a0388ebb700 Mon Sep 17 00:00:00 2001 From: Jeroen Beckers Date: Wed, 6 Nov 2024 15:30:33 +0000 Subject: [PATCH 6/8] wip update --- .../0x05f-Testing-Local-Authentication.md | 291 +++--------------- .../MASVS-AUTH/MASTG-TEST-0x17-static.md | 33 ++ .../android/MASVS-AUTH/MASTG-TEST-0x17.md | 279 ++++++++++++++++- weaknesses/MASVS-AUTH/MASWE-0034.md | 2 +- weaknesses/MASVS-AUTH/MASWE-0044.md | 4 +- 5 files changed, 341 insertions(+), 268 deletions(-) create mode 100644 tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17-static.md diff --git a/Document/0x05f-Testing-Local-Authentication.md b/Document/0x05f-Testing-Local-Authentication.md index b85b9f7032..3b9d7ec7c5 100644 --- a/Document/0x05f-Testing-Local-Authentication.md +++ b/Document/0x05f-Testing-Local-Authentication.md @@ -9,290 +9,71 @@ platform: android During local authentication, an app authenticates the user against credentials stored locally on the device. In other words, the user "unlocks" the app or some inner layer of functionality by providing a valid PIN, password or biometric characteristics such as face or fingerprint, which is verified by referencing local data. Generally, this is done so that users can more conveniently resume an existing session with a remote service or as a means of step-up authentication to protect some critical function. -As stated before in chapter ["Mobile App Authentication Architectures"](0x04e-Testing-Authentication-and-Session-Management.md): The tester should be aware that local should always be enforced at a remote endpoint if possible, or linked to the system's KeyStore. Attackers can bypass local authentication this is not the case. +As stated in chapter ["Mobile App Authentication Architectures"](0x04e-Testing-Authentication-and-Session-Management.md), authentication should always be enforced by a remote endpoint if possible. If local authentication is required, it should be linked to the system's KeyStore, since attackers can bypass local authentication if this is not the case. -On Android, there are two mechanisms supported by the Android Runtime for local authentication: the Confirm Credential flow and the Biometric Authentication flow. +There are two ways of implementing local authentication: -### Confirm Credential Flow +- **Event-based:** The application simply checks if the user can authenticate themselves, via either a biometric prompt or a device credentials prompt. After successful authentication, the application launches a specific functionality or shows additional data. +- **Result-based:** The application unlocks a cryptographic key which is protected by either biometrics or device credentials. The cryptographic key is securely stored in a HSM and cannot be used without the user (re)authenticating themselves. The unlocked key is then used to decrypt or sign sensitive data. -The ["Confirm Credential Flow"](https://developer.android.com/about/versions/marshmallow/android-6.0#confirm-credential) (introduced in Android 6.0) is designed to reduce the number of times a user must authenticate to the device (e.g., via biometrics). This flow allows apps to unlock cryptographic materials from the `AndroidKeystore` if the user has unlocked the device within a set time limit, configured using [`setUserAuthenticationValidityDurationSeconds`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationValidityDurationSeconds(int)). If this limit has expired, the device prompts the user to re-authenticate. +The first implementation, event-based, is inherently insecure for multiple reasons: -In typical use, the app creates a key in the Keystore configured with `setUserAuthenticationValidityDurationSeconds`, then uses that key to attempt an encryption operation. If the authentication is successful, the app continues to the next step, such as navigating to a target screen. If not, the app will prompt the user to re-authenticate using the Confirm Credentials dialog. +- Somewhere in the application there will be an if/else that distinguishes between a successful or a failed authentication attempt. By tampering with the application at runtime, it is possible to convince the application that the authentication attempt was succesful. +- After a legitimate successful authentication, the application will either present sensitive information to the user, or it will allow the user to perform a specific action. This means that either some data is stored locally which is not properly protected, or some functionality is not properly protected and can be triggered even without proper user authentication. -Note that the security of Confirm Credentials is only as strong as the protection set at the lock screen. This often means that simple predictive lock-screen patterns are used and therefore we do not recommend any apps which require L2 of security controls to use Confirm Credentials. +The second implementation, result-based, is considered secure because: -### Biometric Authentication Flow +- The unlocked key will be used to either decrypt sensitive information, or to unlock some sort of session token which can be used to trigger specific functionalities. Without unlocking the key, the data is not available and the functionality cannot be triggered. -Biometric authentication is a convenient mechanism for authentication, but also introduces an additional attack surface when using it. The Android developer documentation gives an interesting overview and indicators for [measuring biometric unlock security](https://source.android.com/security/biometric/measure#strong-weak-unlocks "Measuring Biometric Unlock Security"). +Over the years, Android has introduced many different APIs for dealing with lockscreens, credentials, fingerprints and biometrics: -The Android platform offers three different classes for biometric authentication: +- KeyguardManager: Can authenticate the user, but only event-based. This is considered insecure. +- FingerprintManager: Deprecated in Android 9 (API level 28) in favor of BiometricPrompt. +- BiometricPrompt: Can authenticate the user, both event-based and result-based. This is considered secure if correctly implemented via result-based. +- BiometricManager: Can be used to verify if the device supports biometric authentication. This cannot be used to authenticate the user in any way. -- Android 10 (API level 29) and higher: `BiometricManager` -- Android 9 (API level 28) and higher: `BiometricPrompt` -- Android 6.0 (API level 23) and higher: `FingerprintManager` (deprecated in Android 9 (API level 28)) +### Biometric Authentication - +The class [`BiometricManager`](https://developer.android.com/reference/kotlin/android/hardware/biometrics/BiometricManager "BiometricManager") can be used to verify if biometric hardware is available on the device and if it is configured by the user. This class does not offer any real security, as it only allows the application to check if biometric authentication is supported or not. -The class [`BiometricManager`](https://developer.android.com/reference/kotlin/android/hardware/biometrics/BiometricManager "BiometricManager") can be used to verify if biometric hardware is available on the device and if it's configured by the user. If that's the case, the class [`BiometricPrompt`](https://developer.android.com/reference/kotlin/android/hardware/biometrics/BiometricPrompt "BiometricPrompt") can be used to show a system-provided biometric dialog. +If biometric authentication is supported, the [`BiometricPrompt`](https://developer.android.com/reference/kotlin/android/hardware/biometrics/BiometricPrompt "BiometricPrompt") class can be used to show a system-provided biometric authentication dialog. -The `BiometricPrompt` class is a significant improvement, as it allows to have a consistent UI for biometric authentication on Android and also supports more sensors than just fingerprint. +A detailed overview and explanation of the Biometric API on Android was published on the [Android Developer Blog](https://android-developers.googleblog.com/2019/10/one-biometric-api-over-all-android.html "One Biometric API Over all Android"). -This is different to the `FingerprintManager` class which only supports fingerprint sensors and provides no UI, forcing developers to build their own fingerprint UI. - -A very detailed overview and explanation of the Biometric API on Android was published on the [Android Developer Blog](https://android-developers.googleblog.com/2019/10/one-biometric-api-over-all-android.html "One Biometric API Over all Android"). - -### FingerprintManager (deprecated in Android 9 (API level 28)) - -Android 6.0 (API level 23) introduced public APIs for authenticating users via fingerprint, but is deprecated in Android 9 (API level 28). Access to the fingerprint hardware is provided through the [`FingerprintManager`](https://developer.android.com/reference/android/hardware/fingerprint/ "FingerprintManager") class. An app can request fingerprint authentication by instantiating a `FingerprintManager` object and calling its `authenticate` method. The caller registers callback methods to handle possible outcomes of the authentication process (i.e. success, failure, or error). Note that this method doesn't constitute strong proof that fingerprint authentication has actually been performed - for example, the authentication step could be patched out by an attacker, or the "success" callback could be overloaded using dynamic instrumentation. - -You can achieve better security by using the fingerprint API in conjunction with the Android `KeyGenerator` class. With this approach, a symmetric key is stored in the Android KeyStore and unlocked with the user's fingerprint. For example, to enable user access to a remote service, an AES key is created which encrypts the authentication token. By calling `setUserAuthenticationRequired(true)` when creating the key, it is ensured that the user must re-authenticate to retrieve it. The encrypted authentication token can then be saved directly on the device (e.g. via Shared Preferences). This design is a relatively safe way to ensure the user actually entered an authorized fingerprint. - -An even more secure option is using asymmetric cryptography. Here, the mobile app creates an asymmetric key pair in the KeyStore and enrolls the public key on the server backend. Later transactions are then signed with the private key and verified by the server using the public key. - -### Biometric Library - -Android provides a library called [Biometric](https://developer.android.com/jetpack/androidx/releases/biometric "Biometric library for Android") which offers a compatibility version of the `BiometricPrompt` and `BiometricManager` APIs, as implemented in Android 10, with full feature support back to Android 6.0 (API 23). - -You can find a reference implementation and instructions on how to [show a biometric authentication dialog](https://developer.android.com/training/sign-in/biometric-auth "Show a biometric authentication dialog") in the Android developer documentation. - -There are two `authenticate` methods available in the `BiometricPrompt` class. One of them expects a [`CryptoObject`](https://developer.android.com/reference/android/hardware/biometrics/BiometricPrompt.CryptoObject.html "CryptoObject"), which adds an additional layer of security for the biometric authentication. - -The authentication flow would be as follows when using CryptoObject: +To correctly implement local authentication, the following steps need to be followed: - The app creates a key in the KeyStore with `setUserAuthenticationRequired` and `setInvalidatedByBiometricEnrollment` set to true. Additionally, `setUserAuthenticationValidityDurationSeconds` should be set to -1. - This key is used to encrypt information that is authenticating the user (e.g. session information or authentication token). - A valid set of biometrics must be presented before the key is released from the KeyStore to decrypt the data, which is validated through the `authenticate` method and the `CryptoObject`. +- After successful authentication, the released `CryptoObject` is used to decrypt the previously encrypted information. - This solution cannot be bypassed, even on rooted devices, as the key from the KeyStore can only be used after successful biometric authentication. -If `CryptoObject` is not used as part of the authenticate method, it can be bypassed by using Frida. See the "Dynamic Instrumentation" section for more details. - -Developers can use several [validation classes](https://source.android.com/security/biometric#validation "Validation of Biometric Auth") offered by Android to test the implementation of biometric authentication in their app. - -### FingerprintManager - -> This section describes how to implement biometric authentication by using the `FingerprintManager` class. Please keep in mind that this class is deprecated and the [Biometric library](https://developer.android.com/jetpack/androidx/releases/biometric "Biometric library for Android") should be used instead as a best practice. This section is just for reference, in case you come across such an implementation and need to analyze it. - -Begin by searching for `FingerprintManager.authenticate` calls. The first parameter passed to this method should be a `CryptoObject` instance which is a [wrapper class for crypto objects](https://developer.android.com/reference/android/hardware/fingerprint/FingerprintManager.CryptoObject.html "FingerprintManager.CryptoObject") supported by FingerprintManager. Should the parameter be set to `null`, this means the fingerprint authorization is purely event-bound, likely creating a security issue. - -The creation of the key used to initialize the cipher wrapper can be traced back to the `CryptoObject`. Verify the key was both created using the `KeyGenerator` class in addition to `setUserAuthenticationRequired(true)` being called during creation of the `KeyGenParameterSpec` object (see code samples below). - -Make sure to verify the authentication logic. For the authentication to be successful, the remote endpoint **must** require the client to present the secret retrieved from the KeyStore, a value derived from the secret, or a value signed with the client private key (see above). - -Safely implementing fingerprint authentication requires following a few simple principles, starting by first checking if that type of authentication is even available. On the most basic front, the device must run Android 6.0 or higher (API 23+). Four other prerequisites must also be verified: - -- The permission must be requested in the Android Manifest: - - ```xml - - ``` +### Biometric Compatibility Library -- Fingerprint hardware must be available: - - ```java - FingerprintManager fingerprintManager = (FingerprintManager) - context.getSystemService(Context.FINGERPRINT_SERVICE); - fingerprintManager.isHardwareDetected(); - ``` - -- The user must have a protected lock screen: - - ```java - KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); - keyguardManager.isKeyguardSecure(); //note if this is not the case: ask the user to setup a protected lock screen - ``` - -- At least one finger should be registered: - - ```java - fingerprintManager.hasEnrolledFingerprints(); - ``` - -- The application should have permission to ask for a user fingerprint: - - ```java - context.checkSelfPermission(Manifest.permission.USE_FINGERPRINT) == PermissionResult.PERMISSION_GRANTED; - ``` - -If any of the above checks fail, the option for fingerprint authentication should not be offered. - -It is important to remember that not every Android device offers hardware-backed key storage. The `KeyInfo` class can be used to find out whether the key resides inside secure hardware such as a Trusted Execution Environment (TEE) or Secure Element (SE). - -```java -SecretKeyFactory factory = SecretKeyFactory.getInstance(getEncryptionKey().getAlgorithm(), ANDROID_KEYSTORE); -KeyInfo secetkeyInfo = (KeyInfo) factory.getKeySpec(yourencryptionkeyhere, KeyInfo.class); -secetkeyInfo.isInsideSecureHardware() -``` - -On certain systems, it is possible to enforce the policy for biometric authentication through hardware as well. This is checked by: - -```java -keyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware(); -``` - -The following describes how to do fingerprint authentication using a symmetric key pair. - -Fingerprint authentication may be implemented by creating a new AES key using the `KeyGenerator` class by adding `setUserAuthenticationRequired(true)` in `KeyGenParameterSpec.Builder`. - -```java -generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE); - -generator.init(new KeyGenParameterSpec.Builder (KEY_ALIAS, - KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) - .setBlockModes(KeyProperties.BLOCK_MODE_CBC) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) - .setUserAuthenticationRequired(true) - .build() -); - -generator.generateKey(); -``` - -To perform encryption or decryption with the protected key, create a `Cipher` object and initialize it with the key alias. +Android provides a library called [Biometric](https://developer.android.com/jetpack/androidx/releases/biometric "Biometric library for Android") which offers a compatibility version of the `BiometricPrompt` and `BiometricManager` APIs, as implemented in Android 10, with full feature support back to Android 6.0 (API 23). -```java -SecretKey keyspec = (SecretKey)keyStore.getKey(KEY_ALIAS, null); +You can find a reference implementation and instructions on how to [show a biometric authentication dialog](https://developer.android.com/training/sign-in/biometric-auth "Show a biometric authentication dialog") in the Android developer documentation. -if (mode == Cipher.ENCRYPT_MODE) { - cipher.init(mode, keyspec); -``` +There are two `authenticate` methods available in the `BiometricPrompt` class. One of them expects a [`CryptoObject`](https://developer.android.com/reference/android/hardware/biometrics/BiometricPrompt.CryptoObject.html "CryptoObject"), which allows you to perform result-based biometric authentication. -Keep in mind, a new key cannot be used immediately - it has to be authenticated through the `FingerprintManager` first. This involves wrapping the `Cipher` object into `FingerprintManager.CryptoObject` which is passed to `FingerprintManager.authenticate` before it will be recognized. +### Biometric Authentication VS Device Credentials -```java -cryptoObject = new FingerprintManager.CryptoObject(cipher); -fingerprintManager.authenticate(cryptoObject, new CancellationSignal(), 0, this, null); -``` +When creating the key in the KeyStore, it is possible to specify which authentication methods are allowed to unlock the key. This can be done by calling `setUserAuthenticationType` which accepts the following arguments: -The callback method `onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result)` is called when the authentication succeeds. The authenticated `CryptoObject` can then be retrieved from the result. +- KeyProperties.AUTH_BIOMETRIC_STRONG: Biometric only (face, fingerprint, iris) +- KeyProperties.AUTH_DEVICE_CREDENTIAL: Device credentials only (PIN, pattern, password) +- KeyProperties.AUTH_BIOMETRIC_STRONG | KeyProperties.AUTH_DEVICE_CREDENTIAL: Either biometric or device credential -```java -public void authenticationSucceeded(FingerprintManager.AuthenticationResult result) { - cipher = result.getCryptoObject().getCipher(); +Generally speaking, biometrics are more secure than device credentials as they cannot be copied from the legitimate user. Through shoulder-surfing, it is possible to obtain the device credentials from a user without their cooperation. On the other hand, in certain circumstances a PIN or password may offer more security, since a user can be physically forced to authenticate via biometrics. - //(... do something with the authenticated cipher object ...) -} -``` +### Remove Authentication Protected by Local Authentication -The following describes how to do fingerprint authentication using an asymmetric key pair. +Local authentication is often added on top of an existing backend authentication flow (e.g. username + password). A common mistake is to encrypt the user's password with a biometrics-protected key so that local authentication can be used to unlock the password and authenticate to the backend. -To implement fingerprint authentication using asymmetric cryptography, first create a signing key using the `KeyPairGenerator` class, and enroll the public key with the server. You can then authenticate pieces of data by signing them on the client and verifying the signature on the server. A detailed example for authenticating to remote servers using the fingerprint API can be found in the [Android Developers Blog](https://android-developers.googleblog.com/2015/10/new-in-android-samples-authenticating.html "Authenticating to remote servers using the Fingerprint API"). +This is undesirable and unnecessary. The user's password should only be known by them, and should not be stored anywhere on the device. A better approach is to obtain a device-specific authentication token and protect this with local authentication. This way it is not possible for an attacker to extract the password from the application and use it on different services. -A key pair is generated as follows: +### Invalidation Upon New Biometric Enrollment -```java -KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); -keyPairGenerator.initialize( - new KeyGenParameterSpec.Builder(MY_KEY, - KeyProperties.PURPOSE_SIGN) - .setDigests(KeyProperties.DIGEST_SHA256) - .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")) - .setUserAuthenticationRequired(true) - .build()); -keyPairGenerator.generateKeyPair(); -``` - -To use the key for signing, you need to instantiate a CryptoObject and authenticate it through `FingerprintManager`. - -```java -Signature.getInstance("SHA256withECDSA"); -KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); -keyStore.load(null); -PrivateKey key = (PrivateKey) keyStore.getKey(MY_KEY, null); -signature.initSign(key); -CryptoObject cryptoObject = new FingerprintManager.CryptoObject(signature); +Android 7.0 (API level 24) adds the `setInvalidatedByBiometricEnrollment(boolean invalidateKey)` method to `KeyGenParameterSpec.Builder`. When `invalidateKey` value is set to `true` (the default), keys that are valid for fingerprint authentication are irreversibly invalidated when a new fingerprint is enrolled. This prevents an attacker from retrieving the key even if they are able to enroll an additional fingerprint. -CancellationSignal cancellationSignal = new CancellationSignal(); -FingerprintManager fingerprintManager = - context.getSystemService(FingerprintManager.class); -fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, this, null); -``` - -You can now sign the contents of a byte array `inputBytes` as follows. - -```java -Signature signature = cryptoObject.getSignature(); -signature.update(inputBytes); -byte[] signed = signature.sign(); -``` - -- Note that in cases where transactions are signed, a random nonce should be generated and added to the signed data. Otherwise, an attacker could replay the transaction. -- To implement authentication using symmetric fingerprint authentication, use a challenge-response protocol. - -### Additional Security Features - -Android 7.0 (API level 24) adds the `setInvalidatedByBiometricEnrollment(boolean invalidateKey)` method to `KeyGenParameterSpec.Builder`. When `invalidateKey` value is set to `true` (the default), keys that are valid for fingerprint authentication are irreversibly invalidated when a new fingerprint is enrolled. This prevents an attacker from retrieving they key even if they are able to enroll an additional fingerprint. - -Android 8.0 (API level 26) adds two additional error codes: - -- `FINGERPRINT_ERROR_LOCKOUT_PERMANENT`: The user has tried too many times to unlock their device using the fingerprint reader. -- `FINGERPRINT_ERROR_VENDOR`: A vendor-specific fingerprint reader error occurred. - -### Implementing biometric authentication - -Reassure that the lock screen is set: - -```java -KeyguardManager mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); -if (!mKeyguardManager.isKeyguardSecure()) { - // Show a message that the user hasn't set up a lock screen. -} -``` - -- Create the key protected by the lock screen. In order to use this key, the user needs to have unlocked the device in the last X seconds, or the device needs to be unlocked again. Make sure that this timeout is not too long, as it becomes harder to ensure that it was the same user using the app as the user unlocking the device: - - ```java - try { - KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); - keyStore.load(null); - KeyGenerator keyGenerator = KeyGenerator.getInstance( - KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); - - // Set the alias of the entry in Android KeyStore where the key will appear - // and the constrains (purposes) in the constructor of the Builder - keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, - KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) - .setBlockModes(KeyProperties.BLOCK_MODE_CBC) - .setUserAuthenticationRequired(true) - // Require that the user has unlocked in the last 30 seconds - .setUserAuthenticationValidityDurationSeconds(30) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) - .build()); - keyGenerator.generateKey(); - } catch (NoSuchAlgorithmException | NoSuchProviderException - | InvalidAlgorithmParameterException | KeyStoreException - | CertificateException | IOException e) { - throw new RuntimeException("Failed to create a symmetric key", e); - } - ``` - -- Set up the lock screen to confirm: - - ```java - private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 1; //used as a number to verify whether this is where the activity results from - Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null); - if (intent != null) { - startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS); - } - ``` - -- Use the key after lock screen: - - ```java - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) { - // Challenge completed, proceed with using cipher - if (resultCode == RESULT_OK) { - //use the key for the actual authentication flow - } else { - // The user canceled or didn’t complete the lock screen - // operation. Go to error/cancellation flow. - } - } - } - ``` - -### Third party SDKs - -Make sure that fingerprint authentication and/or other types of biometric authentication are exclusively based on the Android SDK and its APIs. If this is not the case, ensure that the alternative SDK has been properly vetted for any weaknesses. Make sure that the SDK is backed by the TEE/SE which unlocks a (cryptographic) secret based on the biometric authentication. This secret should not be unlocked by anything else, but a valid biometric entry. That way, it should never be the case that the fingerprint logic can be bypassed. +When a new fingerprint is enrolled, all previously protected keys are invalidated, which means the application needs to foresee a fallback authentication method to allow the user to enable biometric authentication again. diff --git a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17-static.md b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17-static.md new file mode 100644 index 0000000000..86bdb4bbe9 --- /dev/null +++ b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17-static.md @@ -0,0 +1,33 @@ +--- +platform: android +title: Insecure Local Authentication +id: MASTG-TEST-0x017-static +type: [static] +available_since: 21 +weakness: MASWE-0044 +--- + +## Overview + +Applications can implement local authentication in various ways, as explained in ["Android Local Authentication"](../../../Document/0x05f-Testing-Local-Authentication.md#Android%20Local%20Authentication). To make sure the application uses local authentication correctly, you need to verify if the application uses result-based authentication. + +If the application uses event-based authentication instead of result-based authentication, the authentication flow can be bypassed by tools such as @MASTG-TOOL-0001 or @MASTG-TOOL-0029. + +## Steps + +1. Identify keys created in the KeyStore which are protected by `setUserAuthenticationRequired` +2. Identify how the key is used after the user has succesfully provided their biometrics or device credential. The exact method depends on the used API, but can include: + 1. BiometricPrompt.AuthenticationCallback.onAuthenticationSucceeded + 2. FingerprintManager.AuthenticationCallback.onAuthenticationSucceeded + +## Observation + +The application may use the unlocked key to decrypt sensitive information, or it may simply continue with the flow and not use the key in any meaningful way. Note that only checking for the occurence of `setUserAuthenticationRequired` is not enough, as some applications will protect a key with user authentication in order to trigger the local authentication prompt, but not actually use it once it is unlocked. + +## Evaluation + +The test case fails if the unlocked key is not used to unlock the protected data. + +## Mitigation + +Ensure that the app uses the unlocked key to decrypt local storage after the user has authenticated. diff --git a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md index 86279d3076..26ee13883f 100644 --- a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md +++ b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md @@ -1,30 +1,289 @@ --- platform: android -title: Insecure Implementation of Confirm Credentials +title: Insecure Local Authentication id: MASTG-TEST-0x017 -type: [static, dynamic] +type: [dynamic] available_since: 21 -deprecated_since: 29 weakness: MASWE-0044 --- ## Overview -When an app implements a ["Confirm Credential Flow"](../../../Document/0x05f-Testing-Local-Authentication.md#confirm-credential-flow), if it only verifies whether the key is unlocked without actually using it (e.g., for decrypting local storage or validating data from a remote source), it may be vulnerable to local authentication bypass. Attackers could use dynamic instrumentation tools like @MASTG-TOOL-0001 to intercept and manipulate the logic, falsely simulating successful authentication. +Applications can implement local authentication in various ways, as explained in ["Android Local Authentication"](../../../Document/0x05f-Testing-Local-Authentication.md#Android%20Local%20Authentication). To make sure the application uses local authentication correctly, you need to verify if the application uses result-based authentication. -## Steps +If the application uses event-based authentication instead of result-based authentication, the authentication flow can be bypassed by tools such as @MASTG-TOOL-0001 or @MASTG-TOOL-0029. -1. Reverse engineer the app (@MASTG-TECH-0014). -2. Search for uses of [`setUserAuthenticationRequired`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean)) or [`createConfirmDeviceCredentialIntent`](https://developer.android.com/reference/android/app/KeyguardManager#createConfirmDeviceCredentialIntent(java.lang.CharSequence,%20java.lang.CharSequence)) and [`setUserAuthenticationValidityDurationSeconds`](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setUserAuthenticationValidityDurationSeconds(int)). +## Steps +1. Onboard the application and enable local authentication. This is an application-specific feature that may or may not be available. If no local authentication is available, the test is not applicable. +2. Launch the application with @MASTG-TOOL-0038 and use the [fingerprint-bypass.js script](https://github.com/WithSecureLABS/android-keystore-audit/blob/master/frida-scripts/fingerprint-bypass.js) and [fingerprint-bypass-via-exception-handling.js](https://github.com/WithSecureLabs/android-keystore-audit/blob/master/frida-scripts/fingerprint-bypass-via-exception-handling.js) scripts. In the first case, the flow should continue automatically, while in the second case, you have to run the bypass() function once the device credentials or biometrics are requested. + ## Observation -The output should contain the reverse engineered code that uses Confirm Credential. +The application may respond in different ways: + +- The application may crash, due to the crypto object not being available. +- The application may continue with the authentication flow and authenticate the user. +- The application doesn't respond in any way and the biometrics or device credential screen remains open. ## Evaluation -The test case fails if the app only checks whether the key is unlocked without performing actual cryptographic operations, such as decrypting or verifying sensitive data. +The test case fails if you were able to authenticate to the application without providing your biometrics or device credentials. If the application did not respond differently due to the scripts, a custom hook can potentially still be used to bypass the local authentication flow and the results are inconclusive. ## Mitigation -Ensure that the app uses the key to decrypt local storage or validate data from a remote source after the user has authenticated. +Ensure that the app uses the unlocked key to decrypt local storage after the user has authenticated. + + + + + + + + + +### Third party SDKs + +Make sure that fingerprint authentication and/or other types of biometric authentication are exclusively based on the Android SDK and its APIs. If this is not the case, ensure that the alternative SDK has been properly vetted for any weaknesses. Make sure that the SDK is backed by the TEE/SE which unlocks a (cryptographic) secret based on the biometric authentication. This secret should not be unlocked by anything else, but a valid biometric entry. That way, it should never be the case that the fingerprint logic can be bypassed. + + + + + + + +### FingerprintManager + +> This section describes how to implement biometric authentication by using the `FingerprintManager` class. Please keep in mind that this class is deprecated and the [Biometric library](https://developer.android.com/jetpack/androidx/releases/biometric "Biometric library for Android") should be used instead as a best practice. This section is just for reference, in case you come across such an implementation and need to analyze it. + +Begin by searching for `FingerprintManager.authenticate` calls. The first parameter passed to this method should be a `CryptoObject` instance which is a [wrapper class for crypto objects](https://developer.android.com/reference/android/hardware/fingerprint/FingerprintManager.CryptoObject.html "FingerprintManager.CryptoObject") supported by FingerprintManager. Should the parameter be set to `null`, this means the fingerprint authorization is purely event-based, likely creating a security issue. + +The creation of the key used to initialize the cipher wrapper can be traced back to the `CryptoObject`. Verify the key was both created using the `KeyGenerator` class in addition to `setUserAuthenticationRequired(true)` being called during creation of the `KeyGenParameterSpec` object (see code samples below). + +Make sure to verify the authentication logic. For the authentication to be successful, the remote endpoint **must** require the client to present the secret retrieved from the KeyStore, a value derived from the secret, or a value signed with the client private key (see above). + +Safely implementing fingerprint authentication requires following a few simple principles, starting by first checking if that type of authentication is even available. On the most basic front, the device must run Android 6.0 or higher (API 23+). Four other prerequisites must also be verified: + +- The permission must be requested in the Android Manifest: + + ```xml + + ``` + +- Fingerprint hardware must be available: + + ```java + FingerprintManager fingerprintManager = (FingerprintManager) + context.getSystemService(Context.FINGERPRINT_SERVICE); + fingerprintManager.isHardwareDetected(); + ``` + +- The user must have a protected lock screen: + + ```java + KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + keyguardManager.isKeyguardSecure(); //note if this is not the case: ask the user to setup a protected lock screen + ``` + +- At least one finger should be registered: + + ```java + fingerprintManager.hasEnrolledFingerprints(); + ``` + +- The application should have permission to ask for a user fingerprint: + + ```java + context.checkSelfPermission(Manifest.permission.USE_FINGERPRINT) == PermissionResult.PERMISSION_GRANTED; + ``` + +If any of the above checks fail, the option for fingerprint authentication should not be offered. + +It is important to remember that not every Android device offers hardware-backed key storage. The `KeyInfo` class can be used to find out whether the key resides inside secure hardware such as a Trusted Execution Environment (TEE) or Secure Element (SE). + +```java +SecretKeyFactory factory = SecretKeyFactory.getInstance(getEncryptionKey().getAlgorithm(), ANDROID_KEYSTORE); +KeyInfo secetkeyInfo = (KeyInfo) factory.getKeySpec(yourencryptionkeyhere, KeyInfo.class); +secetkeyInfo.isInsideSecureHardware() +``` + +On certain systems, it is possible to enforce the policy for biometric authentication through hardware as well. This is checked by: + +```java +keyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware(); +``` + +The following describes how to do fingerprint authentication using a symmetric key pair. + +Fingerprint authentication may be implemented by creating a new AES key using the `KeyGenerator` class by adding `setUserAuthenticationRequired(true)` in `KeyGenParameterSpec.Builder`. + +```java +generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE); + +generator.init(new KeyGenParameterSpec.Builder (KEY_ALIAS, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) + .setUserAuthenticationRequired(true) + .build() +); + +generator.generateKey(); +``` + +To perform encryption or decryption with the protected key, create a `Cipher` object and initialize it with the key alias. + +```java +SecretKey keyspec = (SecretKey)keyStore.getKey(KEY_ALIAS, null); + +if (mode == Cipher.ENCRYPT_MODE) { + cipher.init(mode, keyspec); +``` + +Keep in mind, a new key cannot be used immediately - it has to be authenticated through the `FingerprintManager` first. This involves wrapping the `Cipher` object into `FingerprintManager.CryptoObject` which is passed to `FingerprintManager.authenticate` before it will be recognized. + +```java +cryptoObject = new FingerprintManager.CryptoObject(cipher); +fingerprintManager.authenticate(cryptoObject, new CancellationSignal(), 0, this, null); +``` + +The callback method `onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result)` is called when the authentication succeeds. The authenticated `CryptoObject` can then be retrieved from the result. + +```java +public void authenticationSucceeded(FingerprintManager.AuthenticationResult result) { + cipher = result.getCryptoObject().getCipher(); + + //(... do something with the authenticated cipher object ...) +} +``` + +The following describes how to do fingerprint authentication using an asymmetric key pair. + +To implement fingerprint authentication using asymmetric cryptography, first create a signing key using the `KeyPairGenerator` class, and enroll the public key with the server. You can then authenticate pieces of data by signing them on the client and verifying the signature on the server. A detailed example for authenticating to remote servers using the fingerprint API can be found in the [Android Developers Blog](https://android-developers.googleblog.com/2015/10/new-in-android-samples-authenticating.html "Authenticating to remote servers using the Fingerprint API"). + +A key pair is generated as follows: + +```java +KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); +keyPairGenerator.initialize( + new KeyGenParameterSpec.Builder(MY_KEY, + KeyProperties.PURPOSE_SIGN) + .setDigests(KeyProperties.DIGEST_SHA256) + .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")) + .setUserAuthenticationRequired(true) + .build()); +keyPairGenerator.generateKeyPair(); +``` + +To use the key for signing, you need to instantiate a CryptoObject and authenticate it through `FingerprintManager`. + +```java +Signature.getInstance("SHA256withECDSA"); +KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); +keyStore.load(null); +PrivateKey key = (PrivateKey) keyStore.getKey(MY_KEY, null); +signature.initSign(key); +CryptoObject cryptoObject = new FingerprintManager.CryptoObject(signature); + +CancellationSignal cancellationSignal = new CancellationSignal(); +FingerprintManager fingerprintManager = + context.getSystemService(FingerprintManager.class); +fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, this, null); +``` + +You can now sign the contents of a byte array `inputBytes` as follows. + +```java +Signature signature = cryptoObject.getSignature(); +signature.update(inputBytes); +byte[] signed = signature.sign(); +``` + +- Note that in cases where transactions are signed, a random nonce should be generated and added to the signed data. Otherwise, an attacker could replay the transaction. +- To implement authentication using symmetric fingerprint authentication, use a challenge-response protocol. + + + + + + + +- Check for setInvalidatedByBiometricEnrollment + + + + + + +If `CryptoObject` is not used as part of the authenticate method, it can be bypassed by using Frida. See the "Dynamic Instrumentation" section for more details. + + + + + +### Implementing biometric authentication + +Reassure that the lock screen is set: + +```java +KeyguardManager mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); +if (!mKeyguardManager.isKeyguardSecure()) { + // Show a message that the user hasn't set up a lock screen. +} +``` + +- Create the key protected by the lock screen. In order to use this key, the user needs to have unlocked the device in the last X seconds, or the device needs to be unlocked again. Make sure that this timeout is not too long, as it becomes harder to ensure that it was the same user using the app as the user unlocking the device: + + ```java + try { + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + KeyGenerator keyGenerator = KeyGenerator.getInstance( + KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); + + // Set the alias of the entry in Android KeyStore where the key will appear + // and the constrains (purposes) in the constructor of the Builder + keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + .setUserAuthenticationRequired(true) + // Require that the user has unlocked in the last 30 seconds + .setUserAuthenticationValidityDurationSeconds(30) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) + .build()); + keyGenerator.generateKey(); + } catch (NoSuchAlgorithmException | NoSuchProviderException + | InvalidAlgorithmParameterException | KeyStoreException + | CertificateException | IOException e) { + throw new RuntimeException("Failed to create a symmetric key", e); + } + ``` + +- Set up the lock screen to confirm: + + ```java + private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 1; //used as a number to verify whether this is where the activity results from + Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null); + if (intent != null) { + startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS); + } + ``` + +- Use the key after lock screen: + + ```java + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) { + // Challenge completed, proceed with using cipher + if (resultCode == RESULT_OK) { + //use the key for the actual authentication flow + } else { + // The user canceled or didn’t complete the lock screen + // operation. Go to error/cancellation flow. + } + } + } + ``` + diff --git a/weaknesses/MASVS-AUTH/MASWE-0034.md b/weaknesses/MASVS-AUTH/MASWE-0034.md index 21cec0af80..b47b5b3b42 100644 --- a/weaknesses/MASVS-AUTH/MASWE-0034.md +++ b/weaknesses/MASVS-AUTH/MASWE-0034.md @@ -1,5 +1,5 @@ --- -title: Insecure Implementation of Confirm Credentials +title: Insecure Local Authentication id: MASWE-0034 alias: insecure-confirm-credentials platform: [android] diff --git a/weaknesses/MASVS-AUTH/MASWE-0044.md b/weaknesses/MASVS-AUTH/MASWE-0044.md index 3810f4fa78..2a71abe503 100644 --- a/weaknesses/MASVS-AUTH/MASWE-0044.md +++ b/weaknesses/MASVS-AUTH/MASWE-0044.md @@ -1,7 +1,7 @@ --- -title: Local Authentication is Event-bound +title: Local Authentication is Event-based id: MASWE-0044 -alias: event-bound-biometric-auth +alias: event-based-biometric-auth platform: [android, ios] profiles: [L2] mappings: From 26232b680dfca2bb2e8f5cce4498ab5dc43c5946 Mon Sep 17 00:00:00 2001 From: Jeroen Beckers Date: Wed, 6 Nov 2024 15:52:21 +0000 Subject: [PATCH 7/8] Update testcase --- .../MASVS-AUTH/MASTG-TEST-0x17-static.md | 200 +++++++++++++++ .../android/MASVS-AUTH/MASTG-TEST-0x17.md | 228 +++++++----------- 2 files changed, 287 insertions(+), 141 deletions(-) diff --git a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17-static.md b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17-static.md index 86bdb4bbe9..9ffe3661c1 100644 --- a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17-static.md +++ b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17-static.md @@ -31,3 +31,203 @@ The test case fails if the unlocked key is not used to unlock the protected data ## Mitigation Ensure that the app uses the unlocked key to decrypt local storage after the user has authenticated. + +### Implementing biometric authentication + +Make sure that the user has configured local authentication: + +```java +KeyguardManager mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); +if (!mKeyguardManager.isKeyguardSecure()) { + // Show a message that the user hasn't set up a lock screen. +} +``` + +- Create the key protected by local authentication. In order to use this key, the user needs to have unlocked the device in the last X seconds, or the device needs to be unlocked again. + + ```java + + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); + keyGenerator.init(new KeyGenParameterSpec.Builder( + "myLocalAuthenticationKey", + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setUserAuthenticationRequired(true) + // Require that the user has unlocked in the last 30 seconds + .setUserAuthenticationValidityDurationSeconds(30) + .build()); + SecretKey key = keyGenerator.generateKey(); + } catch (Exception e) { + throw new RuntimeException("Failed to generate key", e); + } + ``` + +- Obtain a reference to a `Cipher` for the generated key: + + ```java + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + + SecretKey key = (SecretKey) keyStore.getKey("myLocalAuthenticationKey", null); + + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE , key); + ``` + +- Create a new BiometricPrompt.CryptoObject from the generated cipher. + + ```java + BiometricPrompt.CryptoObject cryptoObject = null; + try { + cryptoObject = new BiometricPrompt.CryptoObject(cipher); + } catch (Exception e) { + e.printStackTrace(); + } + ``` + +- Trigger a BiometricPrompt to unlock the key: + + ```java + BiometricPrompt.AuthenticationCallback authenticationCallback = new BiometricPrompt.AuthenticationCallback() { + @Override + public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) { + Toast.makeText(context, "Authentication Succeeded", Toast.LENGTH_SHORT).show(); + + if (result.getCryptoObject() != null) { + // Perform secure operation with the CryptoObject (e.g., decryption) + try { + Cipher cipher = result.getCryptoObject().getCipher(); + // Use unlocked cipher + byte[] decryptedData = cipher.doFinal(encryptedData); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Override + public void onAuthenticationFailed() { + Toast.makeText(context, "Authentication Failed", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onAuthenticationError(int errorCode, CharSequence errString) { + Toast.makeText(context, "Authentication Error: " + errString, Toast.LENGTH_SHORT).show(); + } + }; + + // Configure the prompt + BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder() + .setTitle("Authenticate") + .setSubtitle("Confirm your identity to proceed") + .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL) + .build(); + + // Launch the prompt + BiometricPrompt biometricPrompt = new BiometricPrompt((FragmentActivity) context, + Executors.newSingleThreadExecutor(), authenticationCallback); + + biometricPrompt.authenticate(promptInfo, cryptoObject); + ``` + +### FingerprintManager + +> This section describes how to implement biometric authentication by using the `FingerprintManager` class. Please keep in mind that this class is deprecated and the [Biometric library](https://developer.android.com/jetpack/androidx/releases/biometric "Biometric library for Android") should be used instead as a best practice. This section is just for reference, in case you come across such an implementation and need to analyze it. + +The creation of the key used to initialize the cipher wrapper can be traced back to the `CryptoObject`. Verify the key was both created using the `KeyGenerator` class in addition to `setUserAuthenticationRequired(true)` being called during creation of the `KeyGenParameterSpec` object (see code samples below). + +Safely implementing fingerprint authentication requires following a few simple principles, starting by first checking if that type of authentication is even available. On the most basic front, the device must run Android 6.0 or higher (API 23+). Four other prerequisites must also be verified: + +- The permission must be requested in the Android Manifest: + + ```xml + + ``` + +- Fingerprint hardware must be available: + + ```java + FingerprintManager fingerprintManager = (FingerprintManager) + context.getSystemService(Context.FINGERPRINT_SERVICE); + fingerprintManager.isHardwareDetected(); + ``` + +- The user must have a protected lock screen: + + ```java + KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + keyguardManager.isKeyguardSecure(); //note if this is not the case: ask the user to setup a protected lock screen + ``` + +- At least one finger should be registered: + + ```java + fingerprintManager.hasEnrolledFingerprints(); + ``` + +- The application should have permission to ask for a user fingerprint: + + ```java + context.checkSelfPermission(Manifest.permission.USE_FINGERPRINT) == PermissionResult.PERMISSION_GRANTED; + ``` + +If any of the above checks fail, the option for fingerprint authentication should not be offered. + +Not every Android device offers hardware-backed key storage. The `KeyInfo` class can be used to find out whether the key resides inside secure hardware such as a Trusted Execution Environment (TEE) or Secure Element (SE). + +```java +SecretKeyFactory factory = SecretKeyFactory.getInstance(getEncryptionKey().getAlgorithm(), ANDROID_KEYSTORE); +KeyInfo secetkeyInfo = (KeyInfo) factory.getKeySpec(yourencryptionkeyhere, KeyInfo.class); +secetkeyInfo.isInsideSecureHardware() +``` + +On certain systems, it is possible to enforce the policy for biometric authentication through hardware as well. This is checked by: + +```java +keyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware(); +``` + +If all requirements are met, the actual key can be created via the `KeyGenerator` class by adding `setUserAuthenticationRequired(true)` in `KeyGenParameterSpec.Builder`: + +```java +generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE); + +generator.init(new KeyGenParameterSpec.Builder (KEY_ALIAS, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) + .setUserAuthenticationRequired(true) + .build() +); + +generator.generateKey(); +``` + +To perform encryption or decryption with the protected key, create a `Cipher` object and initialize it with the key alias. + +```java +SecretKey keyspec = (SecretKey)keyStore.getKey(KEY_ALIAS, null); + +if (mode == Cipher.ENCRYPT_MODE) { + cipher.init(mode, keyspec); +``` + +Keep in mind, a new key cannot be used immediately - it has to be authenticated through the `FingerprintManager` first. This involves wrapping the `Cipher` object into `FingerprintManager.CryptoObject` which is passed to `FingerprintManager.authenticate` before it will be recognized. + +```java +cryptoObject = new FingerprintManager.CryptoObject(cipher); +fingerprintManager.authenticate(cryptoObject, new CancellationSignal(), 0, this, null); +``` + +The callback method `onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result)` is called when the authentication succeeds. The authenticated `CryptoObject` can then be retrieved from the result. + +```java +public void authenticationSucceeded(FingerprintManager.AuthenticationResult result) { + cipher = result.getCryptoObject().getCipher(); + + //(... do something with the authenticated cipher object ...) +} +``` diff --git a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md index 26ee13883f..b90258b191 100644 --- a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md +++ b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17.md @@ -17,7 +17,7 @@ If the application uses event-based authentication instead of result-based authe 1. Onboard the application and enable local authentication. This is an application-specific feature that may or may not be available. If no local authentication is available, the test is not applicable. 2. Launch the application with @MASTG-TOOL-0038 and use the [fingerprint-bypass.js script](https://github.com/WithSecureLABS/android-keystore-audit/blob/master/frida-scripts/fingerprint-bypass.js) and [fingerprint-bypass-via-exception-handling.js](https://github.com/WithSecureLabs/android-keystore-audit/blob/master/frida-scripts/fingerprint-bypass-via-exception-handling.js) scripts. In the first case, the flow should continue automatically, while in the second case, you have to run the bypass() function once the device credentials or biometrics are requested. - + ## Observation The application may respond in different ways: @@ -34,34 +34,112 @@ The test case fails if you were able to authenticate to the application without Ensure that the app uses the unlocked key to decrypt local storage after the user has authenticated. +### Implementing biometric authentication + +Make sure that the user has configured local authentication: + +```java +KeyguardManager mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); +if (!mKeyguardManager.isKeyguardSecure()) { + // Show a message that the user hasn't set up a lock screen. +} +``` + +- Create the key protected by local authentication. In order to use this key, the user needs to have unlocked the device in the last X seconds, or the device needs to be unlocked again. + ```java + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); + keyGenerator.init(new KeyGenParameterSpec.Builder( + "myLocalAuthenticationKey", + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setUserAuthenticationRequired(true) + // Require that the user has unlocked in the last 30 seconds + .setUserAuthenticationValidityDurationSeconds(30) + .build()); + SecretKey key = keyGenerator.generateKey(); + } catch (Exception e) { + throw new RuntimeException("Failed to generate key", e); + } + ``` +- Obtain a reference to a `Cipher` for the generated key: + ```java + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + SecretKey key = (SecretKey) keyStore.getKey("myLocalAuthenticationKey", null); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE , key); + ``` +- Create a new BiometricPrompt.CryptoObject from the generated cipher. -### Third party SDKs + ```java + BiometricPrompt.CryptoObject cryptoObject = null; + try { + cryptoObject = new BiometricPrompt.CryptoObject(cipher); + } catch (Exception e) { + e.printStackTrace(); + } + ``` -Make sure that fingerprint authentication and/or other types of biometric authentication are exclusively based on the Android SDK and its APIs. If this is not the case, ensure that the alternative SDK has been properly vetted for any weaknesses. Make sure that the SDK is backed by the TEE/SE which unlocks a (cryptographic) secret based on the biometric authentication. This secret should not be unlocked by anything else, but a valid biometric entry. That way, it should never be the case that the fingerprint logic can be bypassed. +- Trigger a BiometricPrompt to unlock the key: + ```java + BiometricPrompt.AuthenticationCallback authenticationCallback = new BiometricPrompt.AuthenticationCallback() { + @Override + public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) { + Toast.makeText(context, "Authentication Succeeded", Toast.LENGTH_SHORT).show(); + + if (result.getCryptoObject() != null) { + // Perform secure operation with the CryptoObject (e.g., decryption) + try { + Cipher cipher = result.getCryptoObject().getCipher(); + // Use unlocked cipher + byte[] decryptedData = cipher.doFinal(encryptedData); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + @Override + public void onAuthenticationFailed() { + Toast.makeText(context, "Authentication Failed", Toast.LENGTH_SHORT).show(); + } + @Override + public void onAuthenticationError(int errorCode, CharSequence errString) { + Toast.makeText(context, "Authentication Error: " + errString, Toast.LENGTH_SHORT).show(); + } + }; + // Configure the prompt + BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder() + .setTitle("Authenticate") + .setSubtitle("Confirm your identity to proceed") + .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL) + .build(); + // Launch the prompt + BiometricPrompt biometricPrompt = new BiometricPrompt((FragmentActivity) context, + Executors.newSingleThreadExecutor(), authenticationCallback); + biometricPrompt.authenticate(promptInfo, cryptoObject); + ``` ### FingerprintManager > This section describes how to implement biometric authentication by using the `FingerprintManager` class. Please keep in mind that this class is deprecated and the [Biometric library](https://developer.android.com/jetpack/androidx/releases/biometric "Biometric library for Android") should be used instead as a best practice. This section is just for reference, in case you come across such an implementation and need to analyze it. -Begin by searching for `FingerprintManager.authenticate` calls. The first parameter passed to this method should be a `CryptoObject` instance which is a [wrapper class for crypto objects](https://developer.android.com/reference/android/hardware/fingerprint/FingerprintManager.CryptoObject.html "FingerprintManager.CryptoObject") supported by FingerprintManager. Should the parameter be set to `null`, this means the fingerprint authorization is purely event-based, likely creating a security issue. - The creation of the key used to initialize the cipher wrapper can be traced back to the `CryptoObject`. Verify the key was both created using the `KeyGenerator` class in addition to `setUserAuthenticationRequired(true)` being called during creation of the `KeyGenParameterSpec` object (see code samples below). -Make sure to verify the authentication logic. For the authentication to be successful, the remote endpoint **must** require the client to present the secret retrieved from the KeyStore, a value derived from the secret, or a value signed with the client private key (see above). - Safely implementing fingerprint authentication requires following a few simple principles, starting by first checking if that type of authentication is even available. On the most basic front, the device must run Android 6.0 or higher (API 23+). Four other prerequisites must also be verified: - The permission must be requested in the Android Manifest: @@ -100,7 +178,7 @@ Safely implementing fingerprint authentication requires following a few simple p If any of the above checks fail, the option for fingerprint authentication should not be offered. -It is important to remember that not every Android device offers hardware-backed key storage. The `KeyInfo` class can be used to find out whether the key resides inside secure hardware such as a Trusted Execution Environment (TEE) or Secure Element (SE). +Not every Android device offers hardware-backed key storage. The `KeyInfo` class can be used to find out whether the key resides inside secure hardware such as a Trusted Execution Environment (TEE) or Secure Element (SE). ```java SecretKeyFactory factory = SecretKeyFactory.getInstance(getEncryptionKey().getAlgorithm(), ANDROID_KEYSTORE); @@ -114,9 +192,7 @@ On certain systems, it is possible to enforce the policy for biometric authentic keyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware(); ``` -The following describes how to do fingerprint authentication using a symmetric key pair. - -Fingerprint authentication may be implemented by creating a new AES key using the `KeyGenerator` class by adding `setUserAuthenticationRequired(true)` in `KeyGenParameterSpec.Builder`. +If all requirements are met, the actual key can be created via the `KeyGenerator` class by adding `setUserAuthenticationRequired(true)` in `KeyGenParameterSpec.Builder`: ```java generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE); @@ -157,133 +233,3 @@ public void authenticationSucceeded(FingerprintManager.AuthenticationResult resu //(... do something with the authenticated cipher object ...) } ``` - -The following describes how to do fingerprint authentication using an asymmetric key pair. - -To implement fingerprint authentication using asymmetric cryptography, first create a signing key using the `KeyPairGenerator` class, and enroll the public key with the server. You can then authenticate pieces of data by signing them on the client and verifying the signature on the server. A detailed example for authenticating to remote servers using the fingerprint API can be found in the [Android Developers Blog](https://android-developers.googleblog.com/2015/10/new-in-android-samples-authenticating.html "Authenticating to remote servers using the Fingerprint API"). - -A key pair is generated as follows: - -```java -KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); -keyPairGenerator.initialize( - new KeyGenParameterSpec.Builder(MY_KEY, - KeyProperties.PURPOSE_SIGN) - .setDigests(KeyProperties.DIGEST_SHA256) - .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")) - .setUserAuthenticationRequired(true) - .build()); -keyPairGenerator.generateKeyPair(); -``` - -To use the key for signing, you need to instantiate a CryptoObject and authenticate it through `FingerprintManager`. - -```java -Signature.getInstance("SHA256withECDSA"); -KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); -keyStore.load(null); -PrivateKey key = (PrivateKey) keyStore.getKey(MY_KEY, null); -signature.initSign(key); -CryptoObject cryptoObject = new FingerprintManager.CryptoObject(signature); - -CancellationSignal cancellationSignal = new CancellationSignal(); -FingerprintManager fingerprintManager = - context.getSystemService(FingerprintManager.class); -fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, this, null); -``` - -You can now sign the contents of a byte array `inputBytes` as follows. - -```java -Signature signature = cryptoObject.getSignature(); -signature.update(inputBytes); -byte[] signed = signature.sign(); -``` - -- Note that in cases where transactions are signed, a random nonce should be generated and added to the signed data. Otherwise, an attacker could replay the transaction. -- To implement authentication using symmetric fingerprint authentication, use a challenge-response protocol. - - - - - - - -- Check for setInvalidatedByBiometricEnrollment - - - - - - -If `CryptoObject` is not used as part of the authenticate method, it can be bypassed by using Frida. See the "Dynamic Instrumentation" section for more details. - - - - - -### Implementing biometric authentication - -Reassure that the lock screen is set: - -```java -KeyguardManager mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); -if (!mKeyguardManager.isKeyguardSecure()) { - // Show a message that the user hasn't set up a lock screen. -} -``` - -- Create the key protected by the lock screen. In order to use this key, the user needs to have unlocked the device in the last X seconds, or the device needs to be unlocked again. Make sure that this timeout is not too long, as it becomes harder to ensure that it was the same user using the app as the user unlocking the device: - - ```java - try { - KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); - keyStore.load(null); - KeyGenerator keyGenerator = KeyGenerator.getInstance( - KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); - - // Set the alias of the entry in Android KeyStore where the key will appear - // and the constrains (purposes) in the constructor of the Builder - keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, - KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) - .setBlockModes(KeyProperties.BLOCK_MODE_CBC) - .setUserAuthenticationRequired(true) - // Require that the user has unlocked in the last 30 seconds - .setUserAuthenticationValidityDurationSeconds(30) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) - .build()); - keyGenerator.generateKey(); - } catch (NoSuchAlgorithmException | NoSuchProviderException - | InvalidAlgorithmParameterException | KeyStoreException - | CertificateException | IOException e) { - throw new RuntimeException("Failed to create a symmetric key", e); - } - ``` - -- Set up the lock screen to confirm: - - ```java - private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 1; //used as a number to verify whether this is where the activity results from - Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null); - if (intent != null) { - startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS); - } - ``` - -- Use the key after lock screen: - - ```java - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) { - // Challenge completed, proceed with using cipher - if (resultCode == RESULT_OK) { - //use the key for the actual authentication flow - } else { - // The user canceled or didn’t complete the lock screen - // operation. Go to error/cancellation flow. - } - } - } - ``` - From 94ed17902a958aea4a3b0b4b4e00ec49335813ad Mon Sep 17 00:00:00 2001 From: Jeroen Beckers Date: Wed, 6 Nov 2024 15:54:36 +0000 Subject: [PATCH 8/8] Typos --- Document/0x05f-Testing-Local-Authentication.md | 2 +- tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17-static.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Document/0x05f-Testing-Local-Authentication.md b/Document/0x05f-Testing-Local-Authentication.md index 3b9d7ec7c5..99d7a90abb 100644 --- a/Document/0x05f-Testing-Local-Authentication.md +++ b/Document/0x05f-Testing-Local-Authentication.md @@ -18,7 +18,7 @@ There are two ways of implementing local authentication: The first implementation, event-based, is inherently insecure for multiple reasons: -- Somewhere in the application there will be an if/else that distinguishes between a successful or a failed authentication attempt. By tampering with the application at runtime, it is possible to convince the application that the authentication attempt was succesful. +- Somewhere in the application there will be an if/else that distinguishes between a successful or a failed authentication attempt. By tampering with the application at runtime, it is possible to convince the application that the authentication attempt was successful. - After a legitimate successful authentication, the application will either present sensitive information to the user, or it will allow the user to perform a specific action. This means that either some data is stored locally which is not properly protected, or some functionality is not properly protected and can be triggered even without proper user authentication. The second implementation, result-based, is considered secure because: diff --git a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17-static.md b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17-static.md index 9ffe3661c1..bd56ac7311 100644 --- a/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17-static.md +++ b/tests-beta/android/MASVS-AUTH/MASTG-TEST-0x17-static.md @@ -16,13 +16,13 @@ If the application uses event-based authentication instead of result-based authe ## Steps 1. Identify keys created in the KeyStore which are protected by `setUserAuthenticationRequired` -2. Identify how the key is used after the user has succesfully provided their biometrics or device credential. The exact method depends on the used API, but can include: +2. Identify how the key is used after the user has successfully provided their biometrics or device credential. The exact method depends on the used API, but can include: 1. BiometricPrompt.AuthenticationCallback.onAuthenticationSucceeded 2. FingerprintManager.AuthenticationCallback.onAuthenticationSucceeded ## Observation -The application may use the unlocked key to decrypt sensitive information, or it may simply continue with the flow and not use the key in any meaningful way. Note that only checking for the occurence of `setUserAuthenticationRequired` is not enough, as some applications will protect a key with user authentication in order to trigger the local authentication prompt, but not actually use it once it is unlocked. +The application may use the unlocked key to decrypt sensitive information, or it may simply continue with the flow and not use the key in any meaningful way. Note that only checking for the occurrence of `setUserAuthenticationRequired` is not enough, as some applications will protect a key with user authentication in order to trigger the local authentication prompt, but not actually use it once it is unlocked. ## Evaluation