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

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

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

String myCertHash(Context context)

{
    if (sMyCertHash == null) {
        if (Utils.isDebuggable(context)) {
            // значение хэша сертификата "androiddebugkey" в debug.keystore.
            sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
        } else {
            // значение хэша сертификата «my company key» в keystore.
            sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
        }
    }
    return sMyCertHash;
}

@Override

public boolean onCreate() {
    return true;
}

@Override

public String getType(Uri uri) {
    switch (sUriMatcher.match(uri)) {
        case DOWNLOADS_CODE:
        case ADDRESSES_CODE:
            return CONTENT_TYPE;
        case DOWNLOADS_ID_CODE:
        case ADDRESSES_ID_CODE:
            return CONTENT_ITEM_TYPE;
        default:
            throw new IllegalArgumentException("Invalid URI:" + uri);
    }
}

@Override

public Cursor query(Uri uri, String[] projection, String selection,
    String[] selectionArgs, String sortOrder) {

    // *** POINT 4 *** Проверьте, определено ли разрешение на подпись от внутреннего приложения.
    if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) {
        throw new SecurityException("Разрешение на внутреннюю подпись не объявлено внутренним приложением.");
    }

    // *** POINT 5 *** Обрабатывайте полученные данные запроса осторожно и безопасно,
    // даже если данные поступили от внутреннего приложения.
    // Здесь соответствие uri ожиданиям проверяется с помощью UriMatcher#match() и switch case.
    // Проверка других параметров здесь опущена из-за примера.
    // См. «3.2 Осторожно и безопасно обрабатывайте входные данные».

    // *** POINT 6 *** Может быть возвращена конфиденциальная информация, поскольку запрашивающее приложение является внутренним.
    // Смысл результата запроса зависит от приложения.

    switch (sUriMatcher.match(uri)) {
        case DOWNLOADS_CODE:
        case DOWNLOADS_ID_CODE:
            return sDownloadCursor;
        case ADDRESSES_CODE:
        case ADDRESSES_ID_CODE:
            return sAddressCursor;
        default:
            throw new IllegalArgumentException("Неверный URI:" + uri);
    }
}

@Override

public Uri insert(Uri uri, ContentValues values) {

    // *** POINT 4 *** Убедитесь, что разрешение на внутреннюю подпись определено внутренним приложением.
    if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) {
        throw new SecurityException("Разрешение на внутреннюю подпись не заявлено внутренним приложением.");
    }

    // *** POINT 5 *** Обращайтесь с полученными данными запроса осторожно и безопасно,
    // несмотря на то, что данные поступили из внутреннего приложения.
    // Соответствие uri ожиданиям здесь проверяется с помощью UriMatcher#match() и switch case.
    // Другие параметры здесь не проверяются из-за образца.
    // Обратитесь к разделу «3.2 Обрабатывать входные данные осторожно и безопасно».

    // *** POINT 6 *** Конфиденциальная информация может быть возвращена, так как запрашивающее приложение внутреннее.
    // Это зависит от приложения, имеет ли выданный ID конфиденциальное значение или нет.

    switch (sUriMatcher.match(uri)) {
        case DOWNLOADS_CODE:
            return ContentUris.withAppendedId(Download.CONTENT_URI, 3);
        case ADDRESSES_CODE:
            return ContentUris.withAppendedId(Address.CONTENT_URI, 4);
        default:
            throw new IllegalArgumentException("Неверный URI:" + uri);
    }
}

@Override

public int update(Uri uri, ContentValues values, String selection,
    String[] selectionArgs) {

    // *** POINT 4 *** Убедитесь, что разрешение на внутреннюю подпись определено

В запросе отсутствует перевод фрагмента кода. Данный фрагмент написан на языке Java.

Текст описывает процесс проверки разрешения на доступ к данным в приложении, а также обработку запросов на получение и удаление данных.

В тексте проверяется наличие определённого разрешения у приложения, которое запрашивает данные. Если разрешение не определено, то генерируется исключение. Затем происходит обработка запроса с использованием метода UriMatcher#match() и switch case. В зависимости от типа запроса возвращается соответствующее значение.

Также в тексте упоминается, что приложение может возвращать чувствительную информацию, и это зависит от конкретного приложения.

Обратите внимание, что данный текст может содержать ошибки или неточности перевода. (NameNotFoundException e) { return null; }


PkgCert.java

```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);
        } 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();
    }
}

Важно: 7 — при экспорте APK используйте тот же ключ разработчика для подписи APK.


Изображение:

Использование внутреннего поставщика контента.

Важные моменты:

  1. Объявите использование разрешения на внутреннюю подпись.
  2. Проверьте, определено ли внутреннее разрешение на подпись внутренним приложением.
  3. Проверьте, использует ли целевое приложение внутренний сертификат подписи.
  4. Поскольку целевое приложение является внутренним, можно отправлять конфиденциальную информацию.
  5. Даже если данные поступают от внутреннего приложения, будьте осторожны и безопасно обрабатывайте полученные результаты.
  6. При экспорте 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.provider.inhouseuser">
    <!-- *** POINT 8 *** Declare to use the in-house signature permission. -->
    <uses-permission
    android:name="org.jssec.android.provider.inhouseprovider.MY_PERMISSION" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".InhouseUserActivity"
            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>

InhouseUserActivity.java

package org.jssec.android.provider.inhouseuser;

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.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class InhouseUserActivity extends Activity {

    // Target Content Provider Information
    private static final String AUTHORITY = "org.jssec.android.provider.inhouseprovider";

    private interface Address {
        public static final String PATH = "addresses";
        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH);
    }

    // In-house Signature Permission
    private static final String MY_PERMISSION = "org.jssec.android.provider.inhouseprovider.MY_PERMISSION";
    //

``` Значение хэша сертификата
    private static String sMyCertHash = null;
    
    private static String myCertHash(Context context) {
        if (sMyCertHash == null) {
            if (Utils.isDebuggable(context)) {
                // Значение хэша сертификата "androiddebugkey" в debug.keystore.
                sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
            } else {
                // Значение хэша сертификата «ключа компании» в keystore.
                sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
            }
        }
        return sMyCertHash;
    }
    
    // Получить имя пакета целевого поставщика контента.
    private static String providerPkgname(Context context, Uri uri) {
        String pkgname = null;
        PackageManager pm = context.getPackageManager();
        ProviderInfo pi = pm.resolveContentProvider(uri.getAuthority(), 0);
        if (pi != null) pkgname = pi.packageName;
        return pkgname;
    }
    
    public void onQueryClick(View view) {
        logLine("[Query]");
        // *** ТОЧКА 9 *** Убедитесь, что разрешение на подпись от имени компании определено приложением от компании.
        if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {
            logLine(" Разрешение на подпись от имени компании не объявлено приложением от компании.");
            return;
        }
        // *** ТОЧКА 10 *** Убедитесь, что целевое приложение подписано сертификатом от компании.
        String pkgname = providerPkgname(this, Address.CONTENT_URI);
        if (!PkgCert.test(this, pkgname, myCertHash(this))) {
            logLine(" Целевой поставщик контента не обслуживается приложениями от компании.");
            return;
        }
        Cursor cursor = null;
        try {
            // *** ТОЧКА 11 *** Можно отправлять конфиденциальную информацию, поскольку целевое приложение является внутренним.
            cursor = getContentResolver().query(Address.CONTENT_URI, null, null, null, null);
            // *** ТОЧКА 12 *** Обрабатывайте полученные данные осторожно и безопасно,
            // даже если данные поступают из внутреннего приложения.
            // Опущено, так как это пример. Пожалуйста, обратитесь к разделу «3.2. Обработка данных с осторожностью и безопасностью».
                if (cursor == null) {
            logLine(" нулевой курсор");
            } else {
                boolean moved = cursor.moveToFirst();
                while (moved) {
                    logLine(String.format(" %d, %s", cursor.getInt(0), cursor.getString(1)));
                    moved = cursor.moveToNext();
                }
            }
        } finally {
            if (cursor != null) cursor.close();
        }
    }
    
    public void onInsertClick(View view) {
        logLine("[Insert]");
        // *** ТОЧКА 9 *** Убедитесь, что разрешение на подпись от имени компании определено приложением от компании.
        String correctHash = myCertHash(this);
        if (!SigPerm.test(this, MY_PERMISSION, correctHash)) {
            logLine(" Разрешение на подпись от имени компании не объявлено приложением от компании.");
            return;
        }
        // *** ТОЧКА 10 *** Убедитесь, что целевое приложение подписано сертификатом от компании.
        String pkgname = providerPkgname(this, Address.CONTENT_URI);
        if (!PkgCert.test(this, pkgname, correctHash)) {
            logLine(" Целевой поставщик контента не обслуживается приложениями от компании.");
            return;
        }
        // *** ТОЧКА 11 *** Можно отправлять конфиденциальную информацию, поскольку целевое приложение является внутренним.
        ContentValues values = new ContentValues();
        values.put("city", "Tokyo");
        Uri uri = getContentResolver().insert(Address.CONTENT_URI, values);
        // *** ТОЧКА 12 *** Обрабатывайте полученные данные осторожно и безопасно,
        // даже если данные поступают из внутреннего приложения.
        // Опущено, так как это пример. Пожалуйста, обратитесь к разделу «3.2. Обработка данных с осторожностью и безопасностью». ПакэджМенеджер.ГЭТ_МЭТА_ДАТА);

Строка пкгнэйм = пи.пакетнэйм;
// Ошибка, если разрешение с именем сигПёрнэйм не является Сигнатурным Разрешением
если (пи.протэктионлэвэл != ПёрмишнИнфо.ПРОТЭКТИОН_СИГНАТУРА) вернуть ноль;
// Возвратить значение хэша сертификата приложения, которое объявляет разрешение с именем сигПёрнэйм.
вернуть ПкгСёрт.хэш(контекст, пкгнэйм);
}
поймать (НэймНотфаундИксэпшэн и) {
    вернуть ноль;
}
}

PkgCert.java

пакет орг.джс сек.андроид.шаред;

импорт джава.сэкьюрити.МэссэджДигэст;
импорт джава.сэкьюрити.НоСачАлгоритэмИксэпшэн;
импорт андроид.контент.Контекст;
импорт андройд.контент.пм.ПакэджИнфо;
импорт андройд.контент.пм.ПакэджМэнеджер;
импорт андройд.контент.пм.НэймНотфэундИксэпшэн;
импорт андройд.контент.пм.Сигнатура;

публичный класс ПкгСёрт {

    публичная статическая брэоуан тест(Контекст контекст, Строка пкгнэйм, Строка коррэктХэш) {
        если (коррэкхтХэш == ноль) вернуть фэйлс;
        коррэкхтХэш = коррэкхтХэш.рэплейсОлл(" ", "");
        вернуть коррэкхтХэш.иквэлс(хэши(контекст, пкгнэйм));
    }
    
    публичный статический хэши(Контекст контекст, Строка пкгнэйм) {
        если (пкгнэйм == ноль) вернуть ноль;
        попробуй {
            ПакэджМэнеджер пм = контекст.гэтуПакэджМэнеджер();
            ПакэджИнфо пкгинфо = пм.гэтПакэджИнфо(пкгнэйм, ПакэджМэнеджер.ГЭТ_СИНГЭТЮРЭС);
            если (пкгинфо.сингэтюрс.лэнхт != 1) вернуть ноль; // Не будет обрабатывать множественные подписи.
            Сигнатура сиг = пкгинфо.сингэтюрс[0];
            байт[] сирт = сиг.тоБайтАррэу();
            байт[] ша256 = компутэШа256(сирт);
            вернуть байт ту хэкс(ша256);
        } поймать (НэймНотфэундИксэпшэн е) {
            вернуть ноль;
        }
    }
    
    приватный статический байт аррэу компутэШа256(байт[] данные) {
        попробуй {
            возвратить МэссэджДигэст.джинсэнс("ША-256").дайджест(данные);
        } поймать (НоСачАлгоритэмИксэпшэн е) {
            вернуть ноль;
        }
    }
    
    приватная статическая строка байт ту хэкс(байт[] данные) {
        если (данные == ноль) вернуть ноль;
        финал СтракБилдер хэксэдэцимал = новый СтракБилдер();
        для (финал байт б : данные) {
            хэксэдэцимал.аппенд(стрэнг.формат("%02X", б));
        }
        вернуть хэксэдэцимал.тустрнг();
    }
}

Надо отметить 13: при экспорте APK используйте тот же ключ разработчика для подписи APK, что и у запрашиваемого приложения.

Опубликовать ( 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