[Huawei Developers] berkberber:
View attachment 5158901

These days mobile devices are part of our life. We do many operations from our mobile phones such as making payment, logging in to social media accounts, checking our bank accounts.

These are the operations which need high security level. If our device will have malicious apps or something like that, our accounts will have trouble and we may suffer many financial and moral damages.

In this article, I will talk about how to improve app security by using HMS Safety Detect Kit.

To do that, I have developed a simple secure web browser app. Because in web browsers, we can use bank websites, we can login to our social media, we can make some payment and use our credit/bank card information. We wouldn’t like to our information to be stolen.

App Preparations

I use Koin framework for dependency injection in my application.

To use Koin Framework in our application, we should add 3 dependencies to our app. In the above, you can find dependencies which you need to add in app-level build.gradle file.

def koinVersion = "2.2.0-rc-4"

dependencies {
....
// Koin for Android
implementation "org.koin:koin-android:$koinVersion"
// Koin Android Scope feature
implementation "org.koin:koin-android-scope:$koinVersion"
// Koin Android ViewModel feature
implementation "org.koin:koin-android-viewmodel:$koinVersion"
}

After we have implemented the Koin dependencies, we need to create our modules which we will add in our application class.

We will get necessary objects with the help of these modules. I prefer to define different module files for different works.

val applicationModule = module {
single(named("appContext")){ androidApplication().applicationContext }

factory { HmsHelper() }
factory { SystemHelper() }
}

val dataModule = module {
factory<ErrorItem>(named("HmsNotAvailable")) { ErrorItem(
icon = ContextCompat.getDrawable(get(named("appContext")), R.drawable.huawei)!!,
title = androidContext().getString(R.string.hms_not_available),
message = androidContext().getString(R.string.download_hms_core)) }

factory<ErrorItem>(named("DeviceNotSecure")) { ErrorItem(
icon = ContextCompat.getDrawable(get(named("appContext")), R.drawable.ic_device_not_secure)!!,
title = androidContext().getString(R.string.device_not_secure),
message = androidContext().getString(R.string.device_not_secure_message)) }

factory<ErrorItem>(named("MaliciousApps")) { ErrorItem(
icon = ContextCompat.getDrawable(get(named("appContext")), R.drawable.ic_malicious_apps)!!,
title = androidContext().getString(R.string.device_not_secure),
message = androidContext().getString(R.string.malicious_apps_message)) }
}

val viewModelModule = module {
viewModel { SplashViewModel() }
}

After we have defined our modules, we need to setup Koin in our application class.

While starting Koin, we should add our modules which we have defined above, and if we want to use app context, we should androidContext value.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.berkberber.hms_securewebbrowser">
....
<application
android:name=".SecureWebBrowserApp"
....
>
....
</application>

</manifest>

class SecureWebBrowserApp: Application(){
override fun onCreate() {
super.onCreate()

setup()
}

private fun setupKoin(){
startKoin {
androidContext(this@SecureWebBrowserApp)
modules(
applicationModule,
viewModelModule,
dataModule
)
}
}

private fun setup(){
setupKoin()
}
}

To get more information about app and to see how I used other things such as Navigation Component, MVVM, and etc. you can visit my GitHub repository.

HMS Safety Detect

Safety Detect Kit helps us to improve the security level of our apps. There are 5 different APIs we can use with HMS Safety Detect Kit.

SysIntegrity API: Helps us to check device security. We can determine that device has been rooted or has not.

AppsCheck API: Helps us to determine and list malicious apps which have installed to device.

URLCheck API: Helps us check whether websites are safe.

UserDetect API: Helps us to determine that user is fake or is not.

WifiDetect API: Helps us to check whether Wi-Fi which the device has connected is secure.

Note: UserDetect API is available outside of Chinese mainland. WifiDetect API is available only in the Chinese mainland.

In this article, I have been focused on app security. So, I used SysIntegrity API and AppsCheck API and I will give you informations about these APIs.

Checking is HMS available on device (optional)

We will use Safety Detect Kit in our application. Safety Detect Kit requires HMS Core to be installed on the device.

We don’t have to make this control, but if device doesn’t have HMS, we can’t use HMS Safety Detect Kit. That’s why I recommend you to check HMS Core availability on device and if device doesn’t have HMS, it is better to show an error screen to user.

To check HMS availability we need to add base HMS dependency to our app-level build.gradle file.

To check that device has HMS support or has not, we can write very basic function called as isHmsAvailable().

def hmsBaseVersion = "5.0.3.300"

dependencies {
...
// HMS Base
implementation "com.huawei.hms:base:${hmsBaseVersion}"
}

class HmsHelper: KoinComponent{
private val appContext: Context by inject(named("appContext"))

fun isHmsAvailable(): Boolean {
val isAvailable = HuaweiApiAvailability.getInstance().isHuaweiMobileNoticeAvailable(appContext)
return (ConnectionResult.SUCCESS == isAvailable)
}
}

If this function returns true, that means device has HMS support and we can start our application.

If this function returns false, that means device doesn’t have HMS support and we shouldn’t start our application. We may show an error screen to user.

View attachment 5158921

SysIntegrity API

SysIntegrity API helps us to check that the user’s device is secure or is not. Even if the device has been rooted, SysIntegrity API will tell us that device is not secure.

To check the device security, we can call our isDeviceSecure() function.

As you see, this function will create a nonce value with an algorithm and pass this value to checkDeviceSecurity() function.

You may ask that, what is the algorithm value which I have used as “Constants.SAFETY_DETECT_ALGORITHM”. You can define this algorithm value as shown in below:

object Constants{
const val BASIC_INTEGRITY = "basicIntegrity"
const val SAFETY_DETECT_ALGORITHM = "SHA1PRNG"
}

As you see, we have defined two different values. We will use these values while checking device security.

You already know where to use SAFETY_DETECT_ALGORITHM value.

We will use BASIC_INTEGRITY value to get device security situation from JSON.

If this value returns true, that means user’s device is secure.

If this value returns false, that means device is not secure or device has been rooted.

object SafetyDetectService : KoinComponent {
private val appContext: Context by inject(named("appContext"))
private val client: SafetyDetectClient = SafetyDetect.getClient(appContext)

fun isDeviceSecure(serviceListener: IServiceListener<Boolean>) {
val nonce = ByteArray(24)
try {
val random: SecureRandom = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
SecureRandom.getInstanceStrong()
else
SecureRandom.getInstance(Constants.SAFETY_DETECT_ALGORITHM)
random.nextBytes(nonce)
} catch (error: NoSuchAlgorithmException) {
serviceListener.onError(ErrorType.NO_SUCH_OBJECT)
}

checkDeviceSecurity(nonce, serviceListener)
}

private fun checkDeviceSecurity(nonce: ByteArray, serviceListener: IServiceListener<Boolean>){
client.sysIntegrity(nonce, BuildConfig.APP_ID)
.addOnSuccessListener { sysIntegrityResp ->
SafetyDetectHelper.getPayloadDetailAsJson(sysIntegrityResp)?.let { jsonObject ->
serviceListener.onSuccess(jsonObject.getBoolean(Constants.BASIC_INTEGRITY))
} ?: kotlin.run {
serviceListener.onError(ErrorType.SERVICE_FAILURE)
}
}
.addOnFailureListener {
serviceListener.onError(ErrorType.SERVICE_FAILURE)
}
}
}

As I talked about above, we need to get a json object from SysIntegrityResp object which has been returned by SysIntegrity API. To get this value, we can define a helper object and we can add all operations about getting json in here.

As you see in the below, we will send a SysIntegrityResp object as parameter and with the help of this function, we can get json object about our device security.

object SafetyDetectHelper {
fun getPayloadDetailAsJson(sysIntegrityResp: SysIntegrityResp): JSONObject? {
val jwsStr = sysIntegrityResp.result
val jwsSplit = jwsStr.split(".").toTypedArray()
val jwsPayloadStr = jwsSplit[1]
val payloadDetail = String(Base64.decode(
jwsPayloadStr.toByteArray(StandardCharsets.UTF_8), Base64.URL_SAFE),
StandardCharsets.UTF_8)

return try {
JSONObject(payloadDetail)
}catch (jsonError: JSONException){
null
}
}
}

If device is secure, we can do our next operations which we need to do. If device is not secure, we should show an error screen to user and we shouldn’t let user to start our application.

View attachment 5158923

AppsCheck API

AppsCheck API helps us to determine malicious apps in user’s device. Thus, if device has some malicious apps, we will not let user to start our application for user’s security.

getMaliciousAppsList() function gives us a list of malicious app and it uses MaliciousAppsData class which has been defined by Huawei as a model class.

This API will return us a response object and this object will have the malicious apps list. If there is not any malicious apps on the device, we can return null and let user to use our application.

But if there are some malicious apps, we shouldn’t let user to start our application and we can show an error screen to user. If we would like to we can list malicious apps to user.

Note: It is better to list malicious apps and let user to delete these applications from device. That is what I am doing in my app. Also, if we would like to do more operations about malicious apps, we can define our own class like I talked about below.

object SafetyDetectService : KoinComponent {
private val appContext: Context by inject(named("appContext"))
private val client: SafetyDetectClient = SafetyDetect.getClient(appContext)

fun checkMaliciousApps(serviceListener: IServiceListener<ArrayList<MaliciousApps>?>){
client.maliciousAppsList
.addOnSuccessListener { maliciousAppsListResp ->
if(maliciousAppsListResp.rtnCode == CommonCode.OK){
val maliciousAppsList: List<MaliciousAppsData> = maliciousAppsListResp.maliciousAppsList
if(maliciousAppsList.isEmpty())
serviceListener.onSuccess(null)
else{
var maliciousApps = arrayListOf<MaliciousApps>()
for(maliciousApp in maliciousAppsList){
maliciousApp.apply {
maliciousApps.add(MaliciousApps(packageName = apkPackageName,
sha256 = apkSha256,
apkCategory = apkCategory))
}
}
serviceListener.onSuccess(maliciousApps)
}
}
}
.addOnFailureListener {
serviceListener.onError(ErrorType.SERVICE_FAILURE)
}
}
}

If we would like to do more operations like getting app icon, app name and etc. we can define our own data class.

I defined my own data class as shown in the below to do more specific operations with malicious apps.

data class MaliciousApps(
val packageName: String,
val sha256: String,
val apkCategory: Int
): KoinComponent{
private val appContext: Context = get(named("appContext"))
private val systemHelper: SystemHelper = get()

fun getAppIcon(): Drawable = systemHelper.getAppIconByPackageName(packageName)
fun getAppName(): String = systemHelper.getAppNameByPackageName(packageName)

fun getThreatDescription(): String {
return when(apkCategory){
1 -> appContext.getString(R.string.risky_app_description)
2 -> appContext.getString(R.string.virus_app_description)
else -> ""
}
}
}

Here I am just using same values with Huawei’s MaliciousAppsData class. But I added my own functions in here to get app icon, app name and threat description.

To get more information about application by package name, we can define new object called as SystemHelper and we can do these operations in here.

class SystemHelper: KoinComponent {
private val appContext: Context by inject(named("appContext"))

/**
* Getting application information by package name
* @param packageName: Package name of the app that we want to get information about
* @return ApplicationInfo class to get app icons, app names and etc. by package name
*/
private fun getAppByPackageName(packageName: String): ApplicationInfo{
return appContext.packageManager.getApplicationInfo(packageName, 0)
}

/**
* Getting application icon by package name
* @param packageName: Package name of the app which we want to get icon
* @return Icon of the application as drawable
*/
fun getAppIconByPackageName(packageName: String): Drawable{
val app = getAppByPackageName(packageName)
return appContext.packageManager.getApplicationIcon(app)
}

/**
* Getting application name by package name
* @param packageName: Package name of the app which we want to get name
* @return Name of the application as drawable
*/
fun getAppNameByPackageName(packageName: String): String{
val app = getAppByPackageName(packageName)
return appContext.packageManager.getApplicationLabel(app).toString()
}
}

View attachment 5158925

When API founds malicious apps we need to list these apps to user and let user to delete these apps from device.

To do that, we can use selectedApp() function. This function will take the malicious app and ask user to delete them.

We need to detect that user has accepted to deleting application or has not. We need to start activity with result and we need to listen this result. If user really delete the application, we need to remove it from list. If there is not any malicious app on list after removing it, we can navigate user to our app.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)

if(requestCode == DELETE_REQUEST_CODE){
when(resultCode){
Activity.RESULT_OK -> {
maliciousApps.remove(selectedMaliciousApp)
setRecyclerView()
}
Activity.RESULT_CANCELED -> {
Toast.makeText(requireContext(), requireContext().getString(R.string.should_delete_app), Toast.LENGTH_LONG).show()
}
}
}
}

private var deleteClickListener = object: DeleteClickListener{
override fun selectedApp(maliciousApp: MaliciousApps) {
var deleteIntent = Intent(Intent.ACTION_DELETE).apply {
data = Uri.parse("package:${maliciousApp.packageName}")
putExtra(Intent.EXTRA_RETURN_RESULT, true)
}
startActivityForResult(deleteIntent, DELETE_REQUEST_CODE)
}

}

View attachment 5158927

To learn more about the app and examine it, you can visit my GitHub repository.

References

berkberberr/HMS-SecureWebBrowserExample: This repository is a secure web browser app which is using Huawei Mobile Services. (github.com)

Safety Detect: SysIntegrity, URLCheck, AppsCheck, UserDetect - HUAWEI Developer