diff --git a/README.md b/README.md index 0d3fee5..47b7597 100644 --- a/README.md +++ b/README.md @@ -77,12 +77,14 @@ import com.securevale.rasp.android.api.SecureAppChecker val shouldCheckForEmulator = true val shouldCheckForDebugger = true val shouldCheckForRoot = true +val shouldCheckAppIntegrity = true val builder = SecureAppChecker.Builder( this, checkEmulator = shouldCheckForEmulator, checkDebugger = shouldCheckForDebugger, - checkRoot = shouldCheckForRoot + checkRoot = shouldCheckForRoot, + checkIntegrity = shouldCheckAppIntegrity ) ``` @@ -98,6 +100,7 @@ when (checkResult) { is Result.EmulatorFound -> {} // app is most likely running on emulator is Result.DebuggerEnabled -> {} // app is in debug mode is Result.Rooted -> {} // app is most likely rooted + is Result.IntegrityViolation -> {} // app was signed by an untrusted certificate is Result.Secure -> {} // OK, no threats detected } ``` @@ -165,6 +168,30 @@ class documentation. ## Supported Checks +### App Signature / Repackaging Detection + +Includes: + +- Native verification of the app signing certificate; +- Detection of apps repackaged and signed with an untrusted certificate; +- Support for signing certificate rotation on Android 9+. + +To configure the trusted certificate, build a signed APK and run: + +```bash +native/update_signature_hash.sh --apk path/to/signed.apk +cd native && ./build.sh +``` + +Multiple `--apk` arguments can be used to configure more than one trusted certificate. + +The signature check helps detect applications that were modified and signed again with an +untrusted certificate. The verification is performed in native code and the trusted certificate +fingerprint is obfuscated to increase the effort required to locate and bypass the check. However, +as with other local security checks, a skilled attacker might still be able to patch or hook the +verification. For this reason, signature verification should be used alongside other RASP checks +and server-side integrity validation whenever possible. + ### Debugger Detection Includes: @@ -268,4 +295,4 @@ dependency using exact version as the requirement. ## License This tool and code is released under Apache License v2.0 with Runtime Library Exception. Please -see [LICENSE](LICENSE) for more information. \ No newline at end of file +see [LICENSE](LICENSE) for more information. diff --git a/native/Cargo.toml b/native/Cargo.toml index 2b4d87d..83ff43a 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -8,6 +8,7 @@ log = "0.4.21" jni = "0.21.1" android_logger = "0.15.0" once_cell = "1.19.0" +sha2 = "0.10.9" [lib] name = "native" @@ -19,4 +20,4 @@ strip = true #[profile.release-with-debug] #inherits = "release" -#debug = true \ No newline at end of file +#debug = true diff --git a/native/src/integrity.rs b/native/src/integrity.rs new file mode 100644 index 0000000..4be70e8 --- /dev/null +++ b/native/src/integrity.rs @@ -0,0 +1,113 @@ +#![allow(non_snake_case)] + +use jni::objects::{JClass, JObject, JObjectArray, JValue}; +use jni::sys::jboolean; +use jni::JNIEnv; +use sha2::{Digest, Sha256}; + +mod trusted_signers; + +fn certificate_matches(certificate: &[u8]) -> bool { + let calculated = Sha256::digest(certificate); + trusted_signers::matches(&calculated) +} + +fn any_certificate_matches(env: &mut JNIEnv, certificates: JObject) -> jni::errors::Result { + let certificates = JObjectArray::from(certificates); + for index in 0..env.get_array_length(&certificates)? { + let signature = env.get_object_array_element(&certificates, index)?; + let bytes = env + .call_method(signature, "toByteArray", "()[B", &[])? + .l()?; + let bytes = jni::objects::JByteArray::from(bytes); + if certificate_matches(&env.convert_byte_array(&bytes)?) { + return Ok(true); + } + } + Ok(false) +} + +fn signing_certificates<'a>( + env: &mut JNIEnv<'a>, + context: &JObject<'a>, +) -> jni::errors::Result> { + let package_manager = env + .call_method( + context, + "getPackageManager", + "()Landroid/content/pm/PackageManager;", + &[], + )? + .l()?; + let package_name = env + .call_method(context, "getPackageName", "()Ljava/lang/String;", &[])? + .l()?; + let sdk = env + .get_static_field("android/os/Build$VERSION", "SDK_INT", "I")? + .i()?; + + if sdk >= 28 { + // GET_SIGNING_CERTIFICATES. Use the complete signing lineage for rotated keys. + let package_info = env + .call_method( + &package_manager, + "getPackageInfo", + "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;", + &[JValue::Object(&package_name), JValue::Int(0x0800_0000)], + )? + .l()?; + let signing_info = env + .get_field( + package_info, + "signingInfo", + "Landroid/content/pm/SigningInfo;", + )? + .l()?; + let has_multiple = env + .call_method(&signing_info, "hasMultipleSigners", "()Z", &[])? + .z()?; + let method = if has_multiple { + "getApkContentsSigners" + } else { + "getSigningCertificateHistory" + }; + env.call_method( + signing_info, + method, + "()[Landroid/content/pm/Signature;", + &[], + )? + .l() + } else { + // GET_SIGNATURES for the library's API 24-27 range. + let package_info = env + .call_method( + package_manager, + "getPackageInfo", + "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;", + &[JValue::Object(&package_name), JValue::Int(0x40)], + )? + .l()?; + env.get_field( + package_info, + "signatures", + "[Landroid/content/pm/Signature;", + )? + .l() + } +} + +/// Returns true when the installed package is not signed by a configured trusted signer. +#[no_mangle] +pub unsafe extern "C" fn Java_com_securevale_rasp_android_integrity_checks_SignatureChecks_a<'a>( + mut env: JNIEnv<'a>, + _class: JClass, + context: JObject<'a>, +) -> jboolean { + let trusted = signing_certificates(&mut env, &context) + .and_then(|certificates| any_certificate_matches(&mut env, certificates)) + .unwrap_or(false); + + // Fail closed: inability to obtain or inspect the signing certificate is a violation. + u8::from(!trusted) +} diff --git a/native/src/integrity/trusted_signers.rs b/native/src/integrity/trusted_signers.rs new file mode 100644 index 0000000..88c2bf7 --- /dev/null +++ b/native/src/integrity/trusted_signers.rs @@ -0,0 +1,32 @@ +include!("trusted_signers_data.rs"); + +#[inline(always)] +fn concealed(value: &u8) -> u8 { + // Volatile reads stop LTO from folding the arrays back into a plaintext digest. + // SAFETY: `value` is always a valid reference to an initialized byte in one of + // the fixed-size static arrays generated in `trusted_signers_data.rs`. + unsafe { std::ptr::read_volatile(value) } +} + +/// Compares a SHA-256 digest against every configured trusted signer. +/// +/// The complete expected digest is reconstructed only while each byte is compared. +#[inline(never)] +pub fn matches(candidate: &[u8]) -> bool { + if candidate.len() != 32 { + return false; + } + + let mut matched = 0u8; + for encoded in &ENCODED { + let mut difference = 0u8; + for index in 0..32 { + difference |= candidate[index] + ^ concealed(&encoded[index]) + ^ concealed(&MASK_A[index]) + ^ concealed(&MASK_B[index]); + } + matched |= u8::from(difference == 0); + } + matched != 0 +} diff --git a/native/src/integrity/trusted_signers_data.rs b/native/src/integrity/trusted_signers_data.rs new file mode 100644 index 0000000..39aa256 --- /dev/null +++ b/native/src/integrity/trusted_signers_data.rs @@ -0,0 +1,22 @@ +// Generated by native/update_signature_hash.sh. Do not edit. + +const MASK_A: [u8; 32] = [ + 0x77, 0x36, 0x9a, 0x7e, 0xcc, 0x76, 0xb0, 0x2e, + 0x81, 0xe0, 0x91, 0xfe, 0x75, 0xf8, 0x1d, 0x9b, + 0xf3, 0x56, 0x0a, 0xd8, 0x76, 0xac, 0x42, 0xfd, + 0xd4, 0x57, 0x31, 0xf2, 0xff, 0x19, 0xc9, 0xb9, +]; +const MASK_B: [u8; 32] = [ + 0x98, 0xca, 0xd2, 0x73, 0x86, 0xa9, 0xcd, 0xf6, + 0xc3, 0xfd, 0x2a, 0x63, 0x80, 0x56, 0x6d, 0x69, + 0xb6, 0x5a, 0xc1, 0x08, 0x11, 0x4d, 0x58, 0xe6, + 0x75, 0x90, 0xd8, 0xa0, 0xb2, 0x23, 0xd7, 0x71, +]; +const ENCODED: [[u8; 32]; 1] = [ + [ + 0x6a, 0x19, 0x0b, 0x5f, 0xf8, 0x01, 0xb8, 0x94, + 0x36, 0x75, 0xf2, 0x58, 0xa3, 0x4c, 0x0b, 0x3f, + 0x22, 0x1c, 0x5b, 0x00, 0xfb, 0xf7, 0x0c, 0x9a, + 0xc2, 0xfb, 0xcb, 0x32, 0x28, 0x36, 0x69, 0xad, + ], +]; diff --git a/native/src/lib.rs b/native/src/lib.rs index f063cf4..1c3214b 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -9,6 +9,7 @@ use jni::JNIEnv; mod debuggable; mod emulator; +mod integrity; mod common; mod root; diff --git a/native/tools/generate_trusted_signers.rs b/native/tools/generate_trusted_signers.rs new file mode 100644 index 0000000..6eba03f --- /dev/null +++ b/native/tools/generate_trusted_signers.rs @@ -0,0 +1,109 @@ +use std::env; +use std::fs::{self, File}; +use std::io::{Read, Write}; +use std::path::Path; + +fn parse_digest(value: &str) -> Result<[u8; 32], String> { + let hex: String = value + .chars() + .filter(|character| character.is_ascii_hexdigit()) + .collect(); + if hex.len() != 64 { + return Err(format!( + "expected a SHA-256 digest (64 hex digits), got {}", + hex.len() + )); + } + let mut digest = [0u8; 32]; + for (index, byte) in digest.iter_mut().enumerate() { + *byte = u8::from_str_radix(&hex[index * 2..index * 2 + 2], 16) + .map_err(|error| error.to_string())?; + } + Ok(digest) +} + +fn random_bytes() -> Result<[u8; 32], String> { + let mut result = [0u8; 32]; + File::open("/dev/urandom") + .and_then(|mut source| source.read_exact(&mut result)) + .map_err(|error| format!("could not read /dev/urandom: {error}"))?; + Ok(result) +} + +fn array(name: &str, value: &[u8; 32]) -> String { + let rows = value + .chunks(8) + .map(|row| { + format!( + " {},", + row.iter() + .map(|byte| format!("0x{byte:02x}")) + .collect::>() + .join(", ") + ) + }) + .collect::>() + .join("\n"); + format!("const {name}: [u8; 32] = [\n{rows}\n];\n") +} + +fn main() -> Result<(), String> { + let output = env::args().nth(1).ok_or("missing output file")?; + let digests = env::args() + .skip(2) + .map(|value| parse_digest(&value)) + .collect::, _>>()?; + if digests.is_empty() { + return Err("provide at least one trusted SHA-256 fingerprint".into()); + } + + let mask_a = random_bytes()?; + let mask_b = random_bytes()?; + let encoded = digests + .iter() + .map(|digest| { + let mut value = [0u8; 32]; + for index in 0..32 { + value[index] = digest[index] ^ mask_a[index] ^ mask_b[index]; + } + value + }) + .collect::>(); + + let encoded_rows = encoded + .iter() + .map(|value| { + let rows = value + .chunks(8) + .map(|row| { + format!( + " {},", + row.iter() + .map(|byte| format!("0x{byte:02x}")) + .collect::>() + .join(", ") + ) + }) + .collect::>() + .join("\n"); + format!(" [\n{rows}\n ],") + }) + .collect::>() + .join("\n"); + + let source = format!( + "// Generated by native/update_signature_hash.sh. Do not edit.\n\n{}{}const ENCODED: [[u8; 32]; {}] = [\n{}\n];\n", + array("MASK_A", &mask_a), + array("MASK_B", &mask_b), + encoded.len(), + encoded_rows + ); + + let output_path = Path::new(&output); + let temporary = output_path.with_extension("rs.tmp"); + File::create(&temporary) + .and_then(|mut file| file.write_all(source.as_bytes())) + .map_err(|error| error.to_string())?; + fs::rename(temporary, output_path).map_err(|error| error.to_string())?; + Ok(()) +} diff --git a/native/update_signature_hash.sh b/native/update_signature_hash.sh new file mode 100755 index 0000000..f2581af --- /dev/null +++ b/native/update_signature_hash.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly GENERATOR_SOURCE="$SCRIPT_DIR/tools/generate_trusted_signers.rs" +readonly GENERATOR_BINARY="${TMPDIR:-/tmp}/android-rasp-generate-trusted-signers" +readonly OUTPUT="$SCRIPT_DIR/src/integrity/trusted_signers_data.rs" + +usage() { + echo "Usage: $0 --apk [--apk ...]" + echo " $0 --sha256 [--sha256 ...]" +} + +digests=() +while [[ $# -gt 0 ]]; do + case "$1" in + --apk) + apk="$2" + apksigner="${APKSIGNER:-}" + if [[ -z "$apksigner" ]]; then + apksigner="$(find "${ANDROID_HOME:-$HOME/Library/Android/sdk}/build-tools" -name apksigner -type f | sort -V | tail -1)" + fi + [[ -x "$apksigner" ]] || { echo "apksigner not found" >&2; exit 1; } + digest="$($apksigner verify --print-certs "$apk" | sed -n 's/^.*certificate SHA-256 digest: //p' | head -1)" + [[ -n "$digest" ]] || { echo "could not read signing certificate from $apk" >&2; exit 1; } + digests+=("$digest") + shift 2 + ;; + --sha256) + digests+=("$2") + shift 2 + ;; + *) usage; exit 1 ;; + esac +done + +[[ ${#digests[@]} -gt 0 ]] || { usage; exit 1; } +rustc "$GENERATOR_SOURCE" -o "$GENERATOR_BINARY" +"$GENERATOR_BINARY" "$OUTPUT" "${digests[@]}" +echo "Updated $OUTPUT with ${#digests[@]} trusted signer(s)." diff --git a/rasp/src/main/java/com/securevale/rasp/android/api/SecureAppChecker.kt b/rasp/src/main/java/com/securevale/rasp/android/api/SecureAppChecker.kt index 1aeb90f..ccbb0e1 100644 --- a/rasp/src/main/java/com/securevale/rasp/android/api/SecureAppChecker.kt +++ b/rasp/src/main/java/com/securevale/rasp/android/api/SecureAppChecker.kt @@ -71,12 +71,14 @@ class SecureAppChecker private constructor() { * @property checkEmulator whether emulator checks should be triggered. * @property checkDebugger whether checks for debug should be triggered. * @property checkRoot whether checks for root should be triggered. + * @property checkIntegrity whether the app signing certificate should be verified. */ class Builder( private val context: Context, private val checkEmulator: Boolean = false, private val checkDebugger: Boolean = false, - private val checkRoot: Boolean = false + private val checkRoot: Boolean = false, + private val checkIntegrity: Boolean = false ) { /** @@ -92,7 +94,13 @@ class SecureAppChecker private constructor() { * @return the configured [SecureAppChecker] instance. */ fun build() = SecureAppChecker().apply { - mediator = ChecksMediator(context, checkEmulator, checkDebugger, checkRoot) + mediator = ChecksMediator( + context, + checkEmulator, + checkDebugger, + checkRoot, + checkIntegrity + ) } } } diff --git a/rasp/src/main/java/com/securevale/rasp/android/api/result/Checks.kt b/rasp/src/main/java/com/securevale/rasp/android/api/result/Checks.kt index 4f7c2a9..e1246a0 100644 --- a/rasp/src/main/java/com/securevale/rasp/android/api/result/Checks.kt +++ b/rasp/src/main/java/com/securevale/rasp/android/api/result/Checks.kt @@ -5,6 +5,15 @@ package com.securevale.rasp.android.api.result */ interface CheckType +/** + * The enum containing application integrity checks. + */ +enum class IntegrityChecks : CheckType { + IntegrityCheck, AppSignature; + + override fun toString(): String = "Integrity: $name" +} + /** * The enum containing all debugger checks. */ diff --git a/rasp/src/main/java/com/securevale/rasp/android/api/result/Result.kt b/rasp/src/main/java/com/securevale/rasp/android/api/result/Result.kt index 60a78ed..7b8d8b4 100644 --- a/rasp/src/main/java/com/securevale/rasp/android/api/result/Result.kt +++ b/rasp/src/main/java/com/securevale/rasp/android/api/result/Result.kt @@ -7,6 +7,7 @@ sealed class Result { data object EmulatorFound : Result() data object DebuggerEnabled : Result() data object Rooted : Result() + data object IntegrityViolation : Result() data object Secure : Result() } diff --git a/rasp/src/main/java/com/securevale/rasp/android/check/ChecksMediator.kt b/rasp/src/main/java/com/securevale/rasp/android/check/ChecksMediator.kt index 17d7d10..9488287 100644 --- a/rasp/src/main/java/com/securevale/rasp/android/check/ChecksMediator.kt +++ b/rasp/src/main/java/com/securevale/rasp/android/check/ChecksMediator.kt @@ -6,10 +6,12 @@ import com.securevale.rasp.android.api.result.CheckType import com.securevale.rasp.android.api.result.DebuggerChecks import com.securevale.rasp.android.api.result.EmulatorChecks import com.securevale.rasp.android.api.result.ExtendedResult +import com.securevale.rasp.android.api.result.IntegrityChecks import com.securevale.rasp.android.api.result.Result import com.securevale.rasp.android.api.result.RootChecks import com.securevale.rasp.android.debugger.DebuggerCheck import com.securevale.rasp.android.emulator.EmulatorCheck +import com.securevale.rasp.android.integrity.IntegrityCheck import com.securevale.rasp.android.root.RootCheck /** @@ -18,13 +20,15 @@ import com.securevale.rasp.android.root.RootCheck * @param emulator whether emulator checks should be triggered. * @param debugger whether checks for debug should be triggered. * @param root whether checks for root should be triggered. + * @param integrity whether the app signing certificate should be verified. */ @PublishedApi internal class ChecksMediator( context: Context, emulator: Boolean, debugger: Boolean, - root: Boolean + root: Boolean, + integrity: Boolean ) { /** @@ -42,10 +46,13 @@ internal class ChecksMediator( */ private var rootCheck: RootCheck? = null + private var integrityCheck: IntegrityCheck? = null + init { if (emulator) emulatorCheck = EmulatorCheck(context) if (debugger) debugCheck = DebuggerCheck(context) if (root) rootCheck = RootCheck(context) + if (integrity) integrityCheck = IntegrityCheck(context) } /** @@ -63,6 +70,7 @@ internal class ChecksMediator( emulatorCheck?.check(checkOnlyFor, true, internalSubscriber) debugCheck?.check(checkOnlyFor, true, internalSubscriber) rootCheck?.check(checkOnlyFor, true, internalSubscriber) + integrityCheck?.check(checkOnlyFor, true, internalSubscriber) } /** @@ -104,6 +112,15 @@ internal class ChecksMediator( root?.vulnerabilityFound() ?: false ) ) + + val integrity = integrityCheck?.check(checkOnlyFor) + internalSubscriber.onCheck( + ExtendedResult( + IntegrityChecks.IntegrityCheck, + integrity?.vulnerabilityFound() ?: false, + integrity?.vulnerabilityFound() ?: false + ) + ) } /** @@ -120,6 +137,9 @@ internal class ChecksMediator( rootCheck?.check()?.vulnerabilityFound() ?: false -> Result.Rooted + integrityCheck?.check()?.vulnerabilityFound() + ?: false -> Result.IntegrityViolation + else -> Result.Secure } diff --git a/rasp/src/main/java/com/securevale/rasp/android/integrity/IntegrityCheck.kt b/rasp/src/main/java/com/securevale/rasp/android/integrity/IntegrityCheck.kt new file mode 100644 index 0000000..ecb8d75 --- /dev/null +++ b/rasp/src/main/java/com/securevale/rasp/android/integrity/IntegrityCheck.kt @@ -0,0 +1,22 @@ +package com.securevale.rasp.android.integrity + +import android.content.Context +import com.securevale.rasp.android.api.result.CheckType +import com.securevale.rasp.android.api.result.IntegrityChecks +import com.securevale.rasp.android.check.ProbabilityCheck +import com.securevale.rasp.android.check.WrappedCheckResult +import com.securevale.rasp.android.check.wrappedCheck +import com.securevale.rasp.android.integrity.checks.SignatureChecks + +@PublishedApi +internal class IntegrityCheck(private val context: Context) : ProbabilityCheck() { + override val threshold = 1 + override val checkType: String = IntegrityChecks::class.java.simpleName + override val checksMap: Map WrappedCheckResult> = mapOf( + IntegrityChecks.AppSignature to ::checkAppSignature + ) + + private fun checkAppSignature() = wrappedCheck(2, IntegrityChecks.AppSignature) { + SignatureChecks.a(context) + } +} diff --git a/rasp/src/main/java/com/securevale/rasp/android/integrity/checks/SignatureChecks.kt b/rasp/src/main/java/com/securevale/rasp/android/integrity/checks/SignatureChecks.kt new file mode 100644 index 0000000..4e1cff8 --- /dev/null +++ b/rasp/src/main/java/com/securevale/rasp/android/integrity/checks/SignatureChecks.kt @@ -0,0 +1,7 @@ +package com.securevale.rasp.android.integrity.checks + +import android.content.Context + +internal object SignatureChecks { + external fun a(context: Context): Boolean +} diff --git a/sample-app/src/main/java/com/securevale/rasp/android/sample/CheckDetailsActivity.kt b/sample-app/src/main/java/com/securevale/rasp/android/sample/CheckDetailsActivity.kt index 6c51483..5f2c1e5 100644 --- a/sample-app/src/main/java/com/securevale/rasp/android/sample/CheckDetailsActivity.kt +++ b/sample-app/src/main/java/com/securevale/rasp/android/sample/CheckDetailsActivity.kt @@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment import com.securevale.rasp.android.sample.check.DebuggerCheckFragment import com.securevale.rasp.android.sample.check.EmulatorCheckFragment import com.securevale.rasp.android.sample.check.RootCheckFragment +import com.securevale.rasp.android.sample.check.IntegrityCheckFragment class CheckDetailsActivity : AppCompatActivity() { @@ -36,6 +37,7 @@ class CheckDetailsActivity : AppCompatActivity() { TestType.EMULATOR -> EmulatorCheckFragment() TestType.DEBUGGER -> DebuggerCheckFragment() TestType.ROOT -> RootCheckFragment() + TestType.INTEGRITY -> IntegrityCheckFragment() } @Suppress("OVERRIDE_DEPRECATION") @@ -57,4 +59,4 @@ class CheckDetailsActivity : AppCompatActivity() { } } -enum class TestType { EMULATOR, DEBUGGER, ROOT } \ No newline at end of file +enum class TestType { EMULATOR, DEBUGGER, ROOT, INTEGRITY } diff --git a/sample-app/src/main/java/com/securevale/rasp/android/sample/MainActivity.kt b/sample-app/src/main/java/com/securevale/rasp/android/sample/MainActivity.kt index 49cc712..6306a8e 100644 --- a/sample-app/src/main/java/com/securevale/rasp/android/sample/MainActivity.kt +++ b/sample-app/src/main/java/com/securevale/rasp/android/sample/MainActivity.kt @@ -30,6 +30,7 @@ class MainActivity : AppCompatActivity() { listOf( Check("Emulator", TestType.EMULATOR), Check("Debugger", TestType.DEBUGGER), - Check("Root", TestType.ROOT) + Check("Root", TestType.ROOT), + Check("App integrity", TestType.INTEGRITY) ) } diff --git a/sample-app/src/main/java/com/securevale/rasp/android/sample/check/IntegrityCheckFragment.kt b/sample-app/src/main/java/com/securevale/rasp/android/sample/check/IntegrityCheckFragment.kt new file mode 100644 index 0000000..ee15a2c --- /dev/null +++ b/sample-app/src/main/java/com/securevale/rasp/android/sample/check/IntegrityCheckFragment.kt @@ -0,0 +1,52 @@ +package com.securevale.rasp.android.sample.check + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.securevale.rasp.android.api.SecureAppChecker +import com.securevale.rasp.android.sample.R + +class IntegrityCheckFragment : Fragment() { + private lateinit var checkResultsAdapter: CheckResultAdapter + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = inflater.inflate(R.layout.fragment_check, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + checkResultsAdapter = CheckResultAdapter() + view.findViewById(R.id.checks_results).apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = checkResultsAdapter + } + + view.findViewById