1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-android-app-sec-guidebook

Клонировать/Скачать
5.2.1.md 30 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 27.11.2024 20:24 c13bfb7

5.2.1 Пример кода

5.2.1.1 Как использовать системные права Android OS

В операционной системе Android существует механизм безопасности, называемый «права», который защищает активы пользователя (например, контакты и функции GPS) от вредоносного программного обеспечения. Когда приложение запрашивает доступ к информации или функциям, защищённым Android OS, оно должно явно заявить о своих правах на доступ к ним. При установке приложения, которое запрашивает права, которые пользователь должен одобрить, появится следующее подтверждающее окно [23].

[23] В Android 6.0 (API Level 23) и более поздних версиях права не предоставляются и не отклоняются при установке приложения, а предоставляются во время выполнения, когда приложение запрашивает их. Для получения дополнительной информации см. разделы «5.2.1.4 Использование опасных прав в Android 6.0 и более поздних версиях» и «5.2.3.6 Изменения в модели разрешений для Android 6.0 и более поздних версий».

Из этого окна подтверждения пользователь может узнать, к каким типам функций или информации пытается получить доступ приложение. Если приложение пытается получить доступ к функциям или информации, которые явно не нужны, это приложение, скорее всего, является вредоносным программным обеспечением. Поэтому, чтобы ваше приложение не было заподозрено в том, что оно является вредоносным, необходимо максимально сократить использование прав доступа.

Основные моменты:

  1. Используйте uses-permission, чтобы объявить права, используемые в приложении.
  2. Не используйте uses-permission для объявления любых ненужных прав.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.jssec.android.permission.usespermission" >
    <!-- *** POINT 1 *** Declare a permission used in an application with uses-permission -->
    <!-- Permission to access Internet -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <!-- *** POINT 2 *** Do not declare any unnecessary permissions with uses-permission -->
    <!-- If declaring to use Permission that is unnecessary for application behaviors, it gives users a
sense of distrust. -->
    <application
        android:allowBackup="false"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

5.2.1.2 Как использовать внутренние права в коммуникации между внутренними приложениями

Помимо системных прав, определённых Android OS, приложения также могут определять свои собственные права. Используя внутренние права (точнее, внутренние подписанные права), можно создать механизм, который позволяет общаться только между внутренними приложениями. Предоставляя комплексные функции связи между несколькими внутренними приложениями, приложение становится более привлекательным, и ваша компания может получать больше прибыли, продавая его как серию. Это случай использования внутренних подписанных прав.

Пример приложения «Внутренние подписанные права (UserApp)» использует метод Context.startActivity() для запуска примера приложения «Внутренние подписанные права (ProtectedApp)». Оба приложения должны использовать один и тот же ключ разработчика для подписи. Если ключи, используемые для подписи, различаются, UserApp не будет отправлять намерения ProtectedApp, и ProtectedApp не обработает намерения, полученные от UserApp. Кроме того, он также может предотвратить использование вредоносными программами информации об установленном порядке для обхода ваших собственных подписанных прав, как описано в разделе «Продвинутые темы».

Основные моменты: предоставить компоненты приложения

  1. Использовать protectionLevel="signature" для определения прав.
  2. Для компонентов использовать атрибут прав для принудительного применения прав.
  3. Если компонент является активностью, то не следует определять фильтр намерений.
  4. Во время выполнения проверять, определены ли права кодом программы.
  5. При экспорте APK использовать тот же ключ разработчика, что и приложение, для подписания APK.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.jssec.android.permission.protectedapp" >
    <!-- *** POINT 1 *** Define a permission with protectionLevel="signature" -->
    <permission
        android:name="org.jssec.android.permission.protectedapp.MY_PERMISSION"
        android:protectionLevel="signature" />
    <application
        android:allowBackup="false"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <!-- *** POINT 2 *** For a component, enforce the permission with its permission attribute -->
        <activity
            android:name=".ProtectedActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:permission="org.jssec.android.permission.protectedapp.MY_PERMISSION" >
            <!-- *** POINT 3 *** If the component is an activity, you must define no intent-filter -->
        </activity>
    </application>
</manifest>

ProtectedActivity.java

package org.jssec.android.permission.protectedapp;

import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.widget.TextView;

public class ProtectedActivity extends Activity {

    // In-house Signature Permission
    private static final String MY_PERMISSION = "org.jssec.android.permission.protectedapp.MY_PERMISSION";
    // Hash value of in-house certificate
    private static String sMyCertHash = null;
    
    private static String myCertHash(Context context) {
        if (sMyCertHash == null) {

``` ```
if (Utils.isDebuggable(context)) {
    // Certificate hash value of "androiddebugkey" of debug.keystore
    sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
} else {
    // Certificate hash value of "my company key" of keystore
    sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
}

return sMyCertHash;

SigPerm.java

package org.jssec.android.shared;

import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionInfo;

public class SigPerm {

    public static boolean test(Context ctx, String sigPermName, String correctHash) {
        if (correctHash == null) return false;
        correctHash = correctHash.replaceAll(" ", "");
        return correctHash.equals(hash(ctx, sigPermName));
    }
    
    public static String hash(Context ctx, String sigPermName) {
        if (sigPermName == null) return null;
        try {
            // Get the package name of the application which declares a permission named sigPermName.
            PackageManager pm = ctx.getPackageManager();
            PermissionInfo pi;
            pi = pm.getPermissionInfo(sigPermName, PackageManager.GET_META_DATA);
            String pkgname = pi.packageName;
            // Fail if the permission named sigPermName is not a Signature Permission
            if (pi.protectionLevel != PermissionInfo.PROTECTION_SIGNATURE) return null;
            // Return the certificate hash value of the application which declares a permission named sigPermName.
            return PkgCert.hash(ctx, pkgname);
        } catch (NameNotFoundException e) {
            return null;
        }
    }
}

PkgCert.java

package org.jssec.android.shared;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;

public class PkgCert {

    public static boolean test(Context ctx, String pkgname, String correctHash) {
        if (correctHash == null) return false;
        correctHash = correctHash.replaceAll(" ", "");
        return correctHash.equals(hash(ctx, pkgname));
    }
    
    public static String hash(Context ctx, String pkgname) {
        if (pkgname == null) return null;
        try {
            PackageManager pm = ctx.getPackageManager();
            PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);
            if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures.
            Signature sig = pkginfo.signatures[0];
            byte[] cert = sig.toByteArray();
            byte[] sha256 = computeSha256(cert);
            return byte2hex(sha256);
        } catch (NameNotFoundException e) {
            return null;
        }
    }
    
    private static byte[] computeSha256(byte[] data) {
        try {
            return MessageDigest.getInstance("SHA-256").digest(data);
``` **Надо отметить 5:** при экспорте APK используйте тот же ключ разработчика, что и в приложении, использующем этот компонент.

**Надо отметить**: используйте приложение с компонентом.

6) Не следует определять те же права подписи, которые использует приложение.

7) Объявите внутренние разрешения с помощью тега uses-permission.

8) Проверьте внутренние права подписи: определите, определены ли они приложением, предоставляющим компонент.

9) Убедитесь, что целевое приложение является внутренним.

10) Если целевой компонент является действием, используйте явное намерение.

11) При экспорте APK используйте тот же ключ разработчика, что и в приложении, использующем этот компонент.  

AndroidManifest.xml
```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.jssec.android.permission.userapp" >
    <!-- *** POINT 6 *** The same signature permission that the application uses must not be defined -->
    <!-- *** POINT 7 *** Declare the in-house permission with uses-permission tag -->
    <uses-permission
    android:name="org.jssec.android.permission.protectedapp.MY_PERMISSION" />
    <application
        android:allowBackup="false"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
        android:name=".UserActivity"
            android:label="@string/app_name"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

UserActivity.java

package org.jssec.android.permission.userapp;

import org.jssec.android.shared.PkgCert;
import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class UserActivity extends Activity {

    // Requested (Destination) application's Activity information
    private static final String TARGET_PACKAGE = "org.jssec.android.permission.protectedapp";
    private static final String TARGET_ACTIVITY = "org.jssec.android.permission.protectedapp.ProtectedActivity";
    // In-house Signature Permission
    private static final String MY_PERMISSION = "org.jssec.android.permission.protectedapp.MY_PERMISSION";
    // Hash value of in-house certificate
    private static String sMyCertHash = null;
    
    private static String myCertHash(Context context) {
        if (sMyCertHash == null) {
            if (Utils.isDebuggable(context)) {
                // Certificate hash value of "androiddebugkey" of debug.keystore.
                sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
            } else {
                // Certificate hash value of "my company key" of keystore.
                sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
            }
        }
        return sMyCertHash;
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
    
    public void onSendButtonClicked(View view) {
        // *** POINT 8 *** Verify if the in-house signature permission is defined by the application that provides the component on the program code.
        if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
            Toast.makeText(this, "In-house-defined signature permission is not defined by In house application.", Toast.LENGTH_LONG).show();
            return;
        }
        // *** POINT 9 *** Verify if the destination application is an in-house application.
        if (!PkgCert.test(this, TARGET_PACKAGE, myCertHash(this))) {
            Toast.makeText(this, "Requested (Destination) application is not in-house application.", Toast.LENGTH_LONG).show();
            return;
        }
        // ***

В тексте запроса присутствуют фрагменты кода на языке Java. Это язык программирования, который используется для разработки приложений под операционную систему Android. **POINT 10. Используйте явное намерение, когда целевой компонент является активностью.**

    try {
        Intent intent = new Intent();
        intent.setClassName(TARGET_PACKAGE, TARGET_ACTIVITY);
        startActivity(intent);
    } catch (Exception e) {
        Toast.makeText(this, String.format("Exception occurs:%s", e.getMessage()), Toast.LENGTH_LONG).show();
    }
}

PkgCertWhitelists.java

package org.jssec.android.shared;

import java.util.HashMap;
import java.util.Map;
import android.content.Context;

public class PkgCertWhitelists {

    private Map<String, String> mWhitelists = new HashMap<String, String>();

    public boolean add(String pkgname, String sha256) {
        if (pkgname == null) return false;
        if (sha256 == null) return false;
        sha256 = sha256.replaceAll(" ", "");
        if (sha256.length() != 64) return false; // SHA-256 -> 32 bytes -> 64 chars
        sha256 = sha256.toUpperCase();
        if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char
        mWhitelists.put(pkgname, sha256);
        return true;
    }

    public boolean test(Context ctx, String pkgname) {
        // Get the correct hash value which corresponds to pkgname.
        String correctHash = mWhitelists.get(pkgname);
        // Compare the actual hash value of pkgname with the correct hash value.
        return PkgCert.test(ctx, pkgname, correctHash);
    }
}

PkgCert.java

package org.jssec.android.shared;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;

public class PkgCert {

    public static boolean test(Context ctx, String pkgname, String correctHash) {
        if (correctHash == null) return false;
        correctHash = correctHash.replaceAll(" ", "");
        return correctHash.equals(hash(ctx, pkgname));
    }

    public static String hash(Context ctx, String pkgname) {
        if (pkgname == null) return null;
        try {
            PackageManager pm = ctx.getPackageManager();
            PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);
            if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures.
            Signature sig = pkginfo.signatures[0];
            byte[] cert = sig.toByteArray();
            byte[] sha256 = computeSha256(cert);
            return byte2hex(sha256);
        } catch (NameNotFoundException e) {
            return null;
        }
    }

    private static byte[] computeSha256(byte[] data) {
        try {
            return MessageDigest.getInstance("SHA-256").digest(data);
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
    }

    private static String byte2hex(byte[] data) {
        if (data == null) return null;
        final StringBuilder hexadecimal = new StringBuilder();
        for (final byte b : data) {
            hexadecimal.append(String.format("%02X", b));
        }
        return hexadecimal.toString();
    }
}

Надо отметить, что в запросе есть текст на русском языке, который не был переведён.

Пункт 11: При экспорте APK используйте для подписи APK тот же ключ разработчика, который использовался для приложения.

Рисунок 4-2-1: Не удалось определить, что изображено на рисунке.

5.2.1.3 Как проверить хеш-значение сертификата приложения

В этом разделе описывается, как проверить хеш-значения сертификатов приложений, которые встречаются в данном руководстве. Строго говоря, хеш-значение означает «хеш-значение открытого ключа сертификата разработчика, используемого для подписания APK».

Как использовать Keytool для проверки

С помощью программы keytool, которая поставляется вместе с JDK, можно получить хеш-значение открытого ключа сертификата разработчика (также известное как отпечаток сертификата). Существует несколько алгоритмов хеширования, таких как MD5, SHA1 и SHA256, но из соображений безопасности рекомендуется использовать SHA256. К сожалению, keytool, используемый в Android SDK, не поддерживает вычисление хеша с использованием SHA256. Поэтому необходимо использовать keytool, поставляемый с JDK7.

Пример вывода содержимого отладочного сертификата Android с помощью keytool

> keytool -list -v -keystore <файл хранилища ключей> -storepass <пароль>
Тип хранилища ключей: JKS
Поставщик хранилища ключей: SUN
В хранилище ключей содержится одна запись
Другое имя: androiddebugkey
Дата создания: 2012/01/11
Тип записи: PrivateKeyEntry
Длина цепочки сертификатов: 1
Сертификат [1]:
Владелец: CN=Android Debug, O=Android, C=US
Эмитент:
``` **Как использовать JSSEC-проверку хешей сертификатов**

JSSEC-проверка хешей сертификатов позволяет легко проверить хеш сертификата.

На рисунке изображено Android-приложение, которое отображает список хешей установленных на устройстве сертификатов. В правой части рисунка показана шестнадцатеричная строка длиной 64 символа — это хеш сертификата. 

В папке JSSEC CertHash Checker есть примеры исходного кода. Если хотите, вы можете скомпилировать код и использовать его.

**Использование опасных разрешений в Android 6.0 и более поздних версиях**

Android 6.0 (API Level 23) включает обновлённые правила, связанные с реализацией приложений, особенно с предоставлением им разрешений.

До Android 5.1 (API 22) и более ранних версий все разрешения предоставлялись при установке приложения. Однако в Android 6.0 и выше разработчики должны запрашивать опасные разрешения у пользователя в подходящее время. Когда приложение запрашивает разрешение, операционная система Android показывает пользователю окно подтверждения, чтобы он решил, предоставлять ли разрешение. Если пользователь разрешает использование разрешения, приложение может выполнять любые операции, требующие этого разрешения.

Также были изменены единицы предоставления разрешений: раньше все разрешения предоставлялись одновременно, а в Android 6.0 и позже — по группам. Теперь пользователь видит отдельные окна подтверждения для каждого разрешения и может принимать более гибкие решения о предоставлении или отказе в разрешении. Разработчики должны пересмотреть спецификации своих приложений и учесть возможность отказа в предоставлении разрешений.

Подробности о модели разрешений в Android 6.0 и выше можно найти в разделе «Модификация правил предоставления разрешений в Android 6.0».

Основные моменты:
* Приложения должны декларировать используемые разрешения.
* Не следует объявлять использование ненужных разрешений.
* Проверять, предоставлены ли приложению разрешения.
* Запрашивать разрешения (открывать диалоговое окно для запроса разрешения от пользователя).
* Реализовывать соответствующее поведение в случае отказа в использовании разрешений.

Пример файла AndroidManifest.xml:
```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.jssec.android.permission.permissionrequestingpermissionatruntime">
    <!-- *** POINT 1 *** Apps declare the Permissions they will use -->
    <!-- Permission to read information on contacts (Protection Level: dangerous) -->
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <!-- *** POINT 2 *** Do not declare the use of unnecessary Permissions -->
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".ContactListActivity" android:exported="false">
        </activity>
    </application>
</manifest>

Пример кода MainActivity.java:

package org.jssec.android.permission.permissionrequestingpermissionatruntime;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final int REQUEST_CODE_READ_CONTACTS = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        readContacts();
    }

    private void readContacts() {
        // *** POINT 3 *** Check whether or not Permissions have been granted to the app
        if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            // Permission was not granted
            // *** POINT 4 *** Request Permissions (open a dialog to request permission from

*Примечание:* в тексте запроса присутствуют фрагменты кода на языке Java, но они не переведены, так как не являются частью основного текста. ```
users)
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CODE_READ_CONTACTS);
} else {
    // Permission was previously granted
    showContactList();
}
// A callback method that receives the result of the user's selection
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch (requestCode) {
        case REQUEST_CODE_READ_CONTACTS:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permissions were granted; we may execute operations that use contact information
                showContactList();
            } else {
                // Because the Permission was denied, we may not execute operations that use contact information
                // *** POINT 5 *** Implement appropriate behavior for cases in which the use of a Permission is refused
                Toast.makeText(this, String.format("Использование контактов запрещено."), Toast.LENGTH_LONG).show();
            }
            return;
    }
}

// Show contact list
private void showContactList() {
    // Launch ContactListActivity
    Intent intent = new Intent();
    intent.setClass(getApplicationContext(), ContactListActivity.class);
    startActivity(intent);
}

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-android-app-sec-guidebook.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-android-app-sec-guidebook.git
oschina-mirror
wizardforcel-android-app-sec-guidebook
wizardforcel-android-app-sec-guidebook
master