Руководство по использованию myDSS SDK 2.1 в Android-приложениях

Термины и определения, описание сущностей

Тип Описание
СДК myDSS SDK версии 2.1 для встраивания в Android-приложения, поставляемая в виде набора AAR-библиотек.
DSSUser (Пользователь) Объект, содержащий всю необходимую информацию для выполнения действий от имени пользователя. Содержит, в том числе, информацию для доступа к экземпляру DSS (URL для взаимодействия, флаги, определяющие параметры взаимодействия), "вектор аутентификации" для подтверждения действий и пр. С точки зрения DSS данный объект является устройством, подключенным к учетной записи пользователя DSS.
DSSDevice (Устройство) Объект, содержащий информацию об устройствах, подключенных к той же учетной записи, что и объект DSSUser. Данный объект не содержит "вектор аутентификации" и не может использоваться для подтверждения операций или выполнения других действий.
DSSOperation (Операция) Операция DSS, для которой требуется подтверждение/отклонение. Операция может содержать несколько документов или может не содержать документов вообще. Операция создается на сервере DSS. СДК позволяет подписывать или отклонять как и все документы, входящие в операцию, целиком, так и часть документов.
DSSOperation.DSSDocument (Документ) Документ, входящий в операцию, либо обрабатываемый самостоятельно.
DSSCertificate (Сертификат) Сертификат, привязанный к ключу подписи в учетной записи на DSS.

Включение myDSS SDK в проект

Рекомендуемый способ подключения СДК к проекту Android-приложения - это добавление maven-зависимости.

Добавление maven-репозиториев

В корневой файл build.gradle проекта необходимо добавить четыре maven-репозитория:

Адрес Пояснение
1 https://repo.paycontrol.org/mydss/android/maven СДК
2 https://repo.paycontrol.org/android/maven Для подключения необходимых для работы в токенами Rutoken
3 mavenCentral() Для добавления библиотеки-сканера QR-кода (обычно уже прописан)
4 https://www.jitpack.io Для добавления вспомогательных библиотек, используемых СДК

Примерный корневой build.gradle будет выглядеть следующим образом:

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:8.2.1"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url "https://repo.paycontrol.org/mydss/android/maven" } // Для версии с поддержкой токенов Rutoken
        maven { url "https://repo.paycontrol.org/android/maven" }
        maven { url "https://www.jitpack.io" }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Добавление maven-зависимости

Далее необходимо добавить maven-зависимость в файлы build.gradle модулей приложения, использующие функции СДК. До версии СДК 2.1.647 и для базовых версии после:

    implementation 'ru.cryptopro.sdk:mydss:2.1.<Код последней версии>'

После версии СДК 2.1.647 для поддержки токенов Rutoken необходимо использовать версию СДК с суффиксом -nfc:

    implementation 'ru.cryptopro.sdk:mydss:2.1.<Код последней версии>-nfc'

Настройка правил минификации и обфускации

Если в проекте используются инструменты минификации и обфускации proguard или D8 (R8), то для корректной работы библиотеки в файл правил proguard (например, proguard-rules.pro) необходимо добавить следующие ограничения:

-keep public class ru.cryptopro.mydss.** { *; }
-dontwarn ru.cryptopro.mydss.**
-keep public class ru.CryptoPro.** { *; }
-dontwarn ru.CryptoPro.**
-keep public class ru.cprocsp.** { *; }
-dontwarn ru.cprocsp.**
-keep public class org.ini4j.spi.** { *; }
-dontwarn org.ini4j.spi.**
-keep public class java.awt.event.** { *; }
-dontwarn java.awt.event.**
-keep public class javax.swing.** { *; }
-dontwarn javax.swing.**
-keep public class com.objsys.asn1j.runtime.** { *; }
-dontwarn com.objsys.asn1j.runtime.**
# Если есть поддержка токенов Rutoken:
-keep class ru.rutoken.**
-keepclassmembers class ru.rutoken.** {*;}

Требуемые разрешения

СДК объявляет в манифесте следующие разрешения, необходимые для корректной работы:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC" />

<uses-permission android:name="android.permission.NFC" />
<uses-feature
    android:name="android.hardware.nfc"
    android:required="false"
    tools:replace="required" />

Разрешения INTERNET и ACCESS_NETWORK_STATE необходимы для взаимодействия с сервером DSS и проверки доступности сети Интернет на устройстве, разрешение CAMERA используется при сканировании QR-кодов (предварительно запрашивается у пользователя внутри СДК при необходимости), разрешение USE_BIOMETRIC применяется при защите ключевой информации при помощи отпечатка пальца. Разрешение 'NFC' необходимо только для работы с токенами Rutoken.

Настройки безопасности

При создании TLS-соединения с сервером DSS осуществляется проверка сертификата сервера по спискам отзывов сертификатов (CRL). Доступ к этим спискам осуществляется по незащищённому протоколу http. Для аппаратов с версией ОС Android 9 и новее, использование http-соединений (clear text traffic) по умолчанию запрещено. Однако, для корректной обработки списков отзывов, http-соединение должно быть разрешено. Наиболее простым (и наименее безопасным) решением в этом случае является разрешение http-трафика на уровне всего приложения через настройку в файле AndroidManifest.xml:

...
<application
    android:usesCleartextTraffic="true"
    ...>

Однако, более правильным решением будет включение в манифест опции android:networkSecurityConfig:

...
<application
    android:networkSecurityConfig="@xml/network_security_config"
    ...>

Файл ресурсов res/xml/network_security_config.xml нужно настроить так, чтобы разрешить http-трафик для доменов, по которым ожидается размещение списков CRL. Более подробные сведения можно найти в Документации разработчика.

Прочие настройки

Минимальная поддерживаемая СДК версия ОС - это Android 7. Поэтому модули, использующие СДК, должны включать в build.gradle строчку:

minSdk = 24

Начиная с версии СДК 2.1.713 необходимо добавить в build.gradle на уровне модулей приложения, использующие функции СДК, в раздел packaging для jniLibs строку: gradle ... useLegacyPackaging = true ... Для более старых(<2.1.713) версий СДК в манифесте модуля (файл AndroidManifest.xml) для элемента application должен быть задан атрибут android:extractNativeLibs со значением true:

...
<application
    android:extractNativeLibs="true"
    ...>

Для более старых(<2.1.713) версий СДК при распространении приложения в виде Android App Bundle в файл gradle.properties проекта должна быть включена строка:

android.bundle.enableUncompressedNativeLibs = false

Обновление с версии myDSS SDK 2.0

СДК версии 2.1 поддерживает обратную совместимость с СДК версии 2.0, однако при переходе на новую версию в исходный код приложения потребуется внести незначительные изменения, по большей части механические.

Изменения, затрагивающие процесс компиляции

При простой замене maven-зависимости ru.cryptopro.sdk:mydss:2.0.x в файле build.gradle модуля на ru.cryptopro.sdk:mydss:2.1.x, модуль не скомпилируется. Для устранения ошибок компиляции нужно предпринять следующие шаги:

  1. Проставить опцию minSdkVersion 24, если стоит версия ниже. СДК версии 2.0 была совместима с аппаратами под управлением Android 4.1+ (API 16), в то время как СДК версии 2.1 рассчитана на использование на аппаратах с Android 7+ (API 24).

  2. Добавить в build.gradle проекта репозиторий gradle maven { url "https://www.jitpack.io" } если он не был добавлен ранее. СДК использует зависимости из этого репозитория.

  3. В реализации интерфейса DSSSignResultNetworkCallback в дополнение к методу error(DSSNetworkError) нужно переопределить error(DSSError), который срабатывает при возникновении ошибки типа DSSError (ошибка, не связанная с сетевыми запросами).

  4. Метод класса Appearance, выполняющий настройку вторичной кнопки модальных окон переименован из Appearance.getButtons().getModalSecond() в Appearance.getButtons().getModalSecondary(). В приложении необходимо ссылаться на новое имя.

  5. Методы классов DSSUsersManager и DSSUsersManagerNonQual, возвращавшие код ошибки типа DSSError в виде целого числа, теперь возвращают сам объект DSSError. Эти изменения затрагивают метод rename класса DSSUsersManager и методы submitPassword, changePassword класса DSSUsersManagerNonQual. Если приложение вызывает эти методы, то код обработки возвращаемого значения нужно поправить соответствующим образом.

  6. Все входные и выходные параметры методов СДК были снабжены аннотациями androidx.annotation.Nullable и androidx.annotation.NonNull. Если приложение написано на языке Kotlin, то по мере необходимости нужно изменить типы объектов на допускающие и недопускающие null в соответствии с аннотациями из СДК. Самый простой способ определить, в каких местах потребуются изменения - это попробовать запустить компиляцию и пройтись по всем местам, где возникнут ошибки. Для приложений на Java необходимо проверить предупреждения Lint и внести правки по мере надобности, чтобы не допустить возникновение NPE во время выполнения.

Изменения, влияющие на поведение во время выполнения

Все свойства-размеры объектов класса Appearance теперь задаются не в единицах px как было ранее, а в виде ссылок на ресурсы типа dimen. Это относится к свойствам Appearance.ButtonAppearance.cornerRadius, Appearance.ViewAppearance.cornerRadius и Appearance.LabelAppearance.size. Если в приложении вы переопределяли значения этих свойств для каких-либо элементов пользовательского интерфейса СДК, то необходимо заменить абсолютные значения идентификаторами R.dimen.*. В противном случае оформление "слетит": СДК не найдёт нужный ресурс и у элемента сохранится оформление по умолчанию.

Прочие изменения

Следующие изменения СДК не должны влиять на ход компиляции и выполнения приложения, если предыдущая версия СДК была встроена в соответствии с рекомендациями по встраиванию myDSS SDK 2.0. Однако, для улучшения качества взаимодействия СДК и приложения, вы можете захотеть адаптировать ваш исходный код под эти изменения.

  1. Все коллбэки, переданные в СДК, вызываются в главном потоке приложения, в не зависимости от того, в каком потоке был вызван метод СДК, принимающий коллбэк. В версии СДК 2.0, в зависимости от ситуации, коллбэк мог быть вызван либо в UI-потоке, либо в том же потоке, где был вызван метод, принимающий коллбэк. С целью достижения большей детерминированности поведения в версии 2.1 схема вызова коллбэков была переделана так, чтобы код коллбэка всегда исполнялся в UI-потоке.

  2. Классы ошибок DSSError и DSSNetworkError обновлены так, чтобы предоставлять больше полезной информации о возникшей ошибке. Добавлены новые типы ошибок, для некоторых типов метод getMessage() может возвращать разные сообщения, в зависимости от ситуации. В частности, это касается ошибок DSSError.DSS_ERROR_CRYPTOGRAPHY_UTILS_UNINITIALIZED и DSSError.DSS_ERROR_NATIVE_FUNCTION_FAILED.

  3. Методы СДК, открывающие пользовательский интерфейс, теперь сами могут запрашивать пароль при необходимости. Выполнять проверку вида

    if (!user.isReadyToSign()) {
        DSSUsersManager.submitPassword(user, callback);
    }
    

    перед выполнением этих методов больше не нужно. К таким методам относятся:

    • DSSUsersManager.acceptAccountChanges(DSSUser, DSSUserCallback)
    • DSSUsersManager.changePassword(DSSUser, boolean, DSSUserCallback)
    • DSSOperationsManager.confirmOperation(DSSUser, DSSOperation, SignMode, boolean, boolean, DSSApproveRequestNetworkCallback)
    • DSSOperationsManager.signDocuments(DSSUser, ArrayList, SignParams, DSSSignResultNetworkCallback)
    • DSSOperationsManager.signDocumentsOffline(DSSUser, ArrayList, SignParams, DSSApproveRequestNetworkCallback)
    • DSSDevicesManager.processAwaitingDevice(DSSUser, DSSDeviceApprovalCallback)
    • DSSCertificatesManagerNonQual.signCertificateRequest(DSSUser, DSSCertificate, DSSSignCertificateRequestCallback)
  4. В коллбэк инициализации СДК DSSInitCallack добавлен метод c реализацией по умолчанию:

    default void onAppearanceReady(@NonNull Appearance appearance) {
    
    }
    

    Он вызывается ещё до момента окончания инициализации СДК, но перед показом диалоговых окон с предупреждениями о проблемах безопасности (если таковые имеются). Таким образом, вид этих диалоговых окон можно также настроить в соответствии со стилем приложения.

Структура СДК

СДК может работать в режиме УКЭП и УНЭП. В режиме УКЭП СДК предоставляет графический интерфейс для регистрации устройства, смены способа защиты ключей, подтверждения операций и подписания документов. В режиме УНЭП приложение может вызывать методы СДК без задействования графического интерфейса, а использовать свой интерфейс для всех процессов.

Для использования СДК в режиме УКЭП приложение может обращаться к методам следующих классов:

Класс Предназначение
DSSUsersManager Создание учётных записей, привязка устройства к существующим учётным записям, управление учётными записями на устройстве (проверка статуса, смена пароля, обновление, удаление с устройства и т.д.)
DSSOperationsManager Получение данных операций, подтверждение операций, подписание документов
DSSCertificatesManager Управление сертификатами пользователя (создание запросов на сертификат, получение сведений о сертификатах и запросах, приостановка и удаление сертификата и .д.)
DSSDevicesManager Управление устройствами, привязанными к учётной записи (получение списка устройств, подтверждение запросов на добавление нового устройства, удаление устройств и т.д.)
DSSPolicyManager Получение информации о сервере DSS
MyDss Инициализация и деинициализация СДК, регулировка параметров работы (метод initNonQual предназначен для инициализации СДК в режиме УНЭП)
Appearance Управление внешним видом СДК

Кроме того, СДК предоставляет классы-модели (DSSUser, DSSDevice, DSSCertificate, DSSOperation, DSSOperation.DSSDocument, DSSParams, DSSSignServerParams, PushNotificationData) для работы с данными пользователя, устройства, сертификата. операции, документа, параметров сервера, параметров сервера подписи и настроек пуш-уведомлений соответственно.

При использовании СДК в режиме УНЭП приложение дополнительно может использовать классы с суффиксом NonQual:

Класс Предназначение
DSSUsersManagerNonQual Управление учётными записями без использования графического интерфейса
DSSOperationsManagerNonQual Работа с операциями и документами без использования графического интерфейса
DSSCertificatesManagerNonQual Работа с сертификатами, хранимыми на устройстве (некоторые методы вовлекают использование графического интерфейса)
DSSDevicesManagerNonQual Работа с привязанными устройствами без использования графического интерфейса
DSSKeysManagerNonQual Управление ключами, хранимыми на устройстве (некоторые методы вовлекают использование графического интерфейса)

Инициализация СДК

Инициализация СДК задаёт параметры её дальнейшего использования: уровень логирования, набор корневых сертификатов, до которых будет строиться цепочка проверки сертификатов серверов, с которыми работает приложение, а также режим работы: для разработки (development) или для конечного пользователя (production). Режим development позволяет отключить проверку отзыва сертификата сервера, что может быть полезным на этапе разработки, если адреса CRL не настроены должным образом.

Инициализация СДК должна выполняться один раз в главном потоке приложения. При необходимости реинициализации СДК должна проводиться предварительная деинициализация.

Инициализация в режиме УКЭП

Инициализация СДК для работы в режиме УКЭП должна выполняться методом MyDss.init(...) как показано в примере ниже.

public class MainActivity extends AppCompatActivity {

    static MyDss myDSS = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        MyDss.init( getApplicationContext(), 
                    MyDss.RootCertificateType.Development, // Тип корневого сертификата
                    MyDss.DSS_LOG_DEBUG,    // Уровень логирования СДК в LogCat
                    null, // Список "доверенных" приложений, убран из аргументов начиная с версии 2.1.616
                    new DSSInitCallback() 
        {
            @Override
            public void error(@NonNull DSSError dssError) {
                // Инициализация прошла неудачно. Библиотекой пользоваться нельзя
                Log.e("ERROR", dssError.getMessage());
            }

            @Override
            public void success(@NonNull MyDss myDssInstance) {
                // Библиотека инициализирована успешно
                // Необходимо сохранить созданный экземпляр myDssInstance для дальнейшего использования
                myDSS = myDssInstance;
            }

            @Override
            public void onAppearanceReady(@NonNull Appearance appearance) {
                // Инициализация ещё не завершена, но уже доступно управление внешним видом СДК
                // через экземпляр класса Appearance
            }
        });
    }
}

Первый параметр метода init - контекст приложения.

Следующий параметр - перечисление MyDss.RootCertificateType - определяет одновременно набор корневых сертификатов, с которым будет работать приложение, и режим работы СДК - для разработки (без проверки CRL) и для конечного использования (с проверкой CRL). В примере выше использован режим "Для разработки".

Третий параметр - уровень логирования - определяет, какую информацию будет записывать СДК в LogCat. Константа MyDss.DSS_NO_LOGGING предотвращает запись в логи каких-либо сообщений СДК (но не внутренних библиотек СДК). Значение MyDss.DSS_LOG_INFO включает логирование сообщений об ошибках, предупреждений и информационных сообщений, в то время как значение MyDss.DSS_LOG_DEBUG позволяет также включить в логи содержимое тел сетевых запросов и промежуточные состояния различных объектов.

Четвёртый параметр (в примере выше - null) задаёт список доверенных приложений, устарел и был убран начиная с версии СДК 2.1.616. Во время инициализации SDK выполняет поиск установленных приложений с привилегиями android.permission.PACKAGE_USAGE_STATS и android.permission.SYSTEM_ALERT_WINDOW. Такие приложения являются потенциально опасными, так как могут рисовать окна "поверх" окон других процессов, в результате чего пользователь может видеть на экране искажённую информацию. Прежде чем процесс инициализации SDK будет завершён, пользователю будет показано диалоговое окно со списком потенциально опасных программ. При этом в SDK есть предопределённый белый список приложений с вышеуказанными привилегиями, которые не являются опасными (этот список включает системные приложения и некоторые другие). В силу непрерывного развития мобильной индустрии данный список не является исчерпывающим, поэтому вы можете пополнять его другими приложениями, которые на ваш взгляд SDK ошибочно определяет как вредоносные.
Подробнее о формировании списка "доверенных" приложений смотрите в разделе Инициализация SDK со списком доверенных приложений.

В качестве последнего параметра в метод init нужно передавать реализацию интерфейса DSSInitCallback для получения результатов инициализации.

Инициализация в режиме УНЭП

При работе с неквалифицированной электронной подписью инициализацию СДК можно проводить вызовом метода MyDss.initNonQual(...). Этот метод принимает только четыре параметра: список доверенных приложений передавать не нужно, так как в режиме УНЭП СДК не выполняет проверку наличия потенциально опасных приложений. Кроме того, в режиме УНЭП не проверяется наличие прав суперпользователя (root) на устройстве и не выполняется проверка на наличие доверенного антивируса.

Размещение набора корневых сертификатов

Второй параметр методов MyDss.init(...) и MyDss.initNonQual(...) определяет не только режим работы СДК, но и ожидаемое расположение списка корневых сертификатов. При использовании значения MyDss.RootCertificateType.Development корневые сертификаты должны быть размещены в папке "сырых" ресурсов в файле с именем development_root_cert и любым расширением (например, res/raw/development_root_cert.crt) - СДК будет обращаться к ресурсу списка сертификатов как R.raw.development_root_cert. При использовании режима для конечного пользователя - MyDss.RootCertificateType.Production - список корневых сертификатов должен располагаться в аналогичном файле, именованном production_root_cert.

В независимости от режима инициализации сертификаты должны быть сохранены в формате PEM. В общем случае, в одном файле ресурса можно разместить несколько сертификатов. Тогда файл ресурса со списком сертификатов будет выглядеть следующим образом:

-----BEGIN CERTIFICATE-----
Base64 encoded certificate 1
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
Base64 encoded certificate 2
-----END CERTIFICATE-----

...

-----BEGIN CERTIFICATE-----
Base64 encoded certificate N
-----END CERTIFICATE-----

Регистрация устройства

Отправным шагом при работе с СДК является регистрация устройства (привязка устройства к существующей учётной записи или регистрация первого анонимного устройства с последующим созданием новой учётной записи).

СДК предоставляет три сценария регистрации:

  1. Регистрация нового устройства онлайн.
  2. Регистрация нового устройства с использованием QR-кода, содержащего предварительные сведения регистрации.
  3. Регистрация второго или последующего устройства с подтверждением присоединения этого устройства на одном из ранее зарегистрированных устройств.

Результатом каждого из способов регистрации является создание нового объекта DSSUser, содержащего уникальный набор ключей аутентификации (векторы аутентификации), используемых данным устройством, при выполнении действий от имени пользователя DSS, к которому было привязано устройство.

Регистрация в режиме УКЭП

Ниже приведены примеры регистрации в режиме УКЭП для каждого из трёх сценариев. Регистрация инициируется вызовом одного из методов createDSSUser* класса DSSUsersManager.

Онлайн-регистрация

Онлайн-регистрация устройства выполняется в три логических шага, два из которых инициируются приложением на мобильном устройстве.

Шаг 1. Регистрация анонимного устройства

Первый шаг - регистрация анонимного устройства методом DSSUsersManager:

DSSUsersManager.createDSSUser(
    @Nullable String externalId,  // Идентификатор сущности вызывающего приложения,
                                  // к которому будет "привязан" объект 
    @Nullable String alias,       // Удобочитаемый идентификатор устройства
    @NonNull String name,        // Уникальное имя профиля в рамках приложения
    @NonNull String serviceUrl,  // URL для взаимодействия с myDSS
    @NonNull String deviceName,  // Читаемое название устройства
    @Nullable PushNotificationData pushData,  // Данные для получения PUSH-уведомлений
    boolean requirePassword,  // Требуется ли установка пароля                          
    @Nullable DSSUserCallback callback  // Callback для обработки результатов  
)

Как правило, параметры externalId и alias не указываются (передаётся значение null) и назначаются сервером DSS. Однако, в зависимости от настроек сервера, эти параметры могут быть заданы из приложения.

Параметр name задаёт уникальное имя будущего объекта DSSUser в рамках приложения и не передаётся на сервер. СДК использует это имя в качестве уникального признака при выполнении некоторых операций. Предполагается, что имя задаётся вручную и применяется пользователем приложения для различения ключей. Однако, СДК не накладывает на это ограничений, и приложение может генерировать этот параметр иным образом и использовать в иных целях.

Параметр serviceUrl должен быть известен приложению и определяет адрес сервиса mDAG, на который СДК будет отправлять запросы.

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

Необходимые данные для получения PUSH-уведомлений передаются в параметре pushData, представляющем экземпляр класса PushNotificationData, например. Создать такой экземпляр можно через конструктор, передав в него token, полученный от сервиса FireBase:

PushNotificationData pushData = new PushNotificationData("my firebase push token");

Если же приложение запущено на устройстве, где получение уведомлений планируется осуществлять посредствам сервиса HMS, то объект типа PushNotificationData следует получить следующим вызовом:

PushNotificationData pushData = new PushNotificationData("my HMS push token", 3);

Второй аргумент задаёт тип устройства (1 - устройство на базе iOS, 2 - устройство на базе Android с использованием FCM, 3 - устройство на базе Android с использованием HMS (обычно, аппараты Huawei)).

Логический параметр requirePassword позволяет пропустить ручное задание пароля (если установлено значение false) при условии, что параметры сервера и флаги ключа это допускают. В этом случае, ключи аутентификации будут защищены неким паролем по умолчанию.

Последний параметр - реализация интерфейса DSSUserCallback для обработки результатов онлайн-регистрации:

new DSSUserCallback() {

    @Override
    public void success(@NonNull DSSUser user) {
        // Пользователь создан успешно, данные сохранены в объекте user
    }

    @Override
    public void error(@NonNull DSSError error) {
        // Возникла внутренняя ошибка СДК - регистрация не пройдена
    }

    @Override
    public void error(DSSNetworkError error) {
        // Возникла ошибка сетевого характера - регистрация не пройдена
    }
}

Созданный объект DSSUser уже сохранён в долгосрочную память приложения и может быть восстановлен из памяти при последующих запусках приложения.

Шаг 2. Подтверждение регистрации оператором

После успешного выполнения метода createDSSUser(...) созданный объект DSSUser, а также объект DSSDevice, соответствующий данному устройству, находятся в статусе Installed (значение перечисления DSSDevice.DSSDeviceStatus). Это означает, что ключи аутентификации, полученные от сервера, были сохранены на устройстве. Далее, оператор переводит устройство в статус NotVerified путём привязки устройства к учётной записи по значению alias.

Шаг 3. Подтверждение привязки устройства к учётной записи

Заключительный шаг онлайн-регистрации - подтверждение привязки устройства к учётной записи на стороне приложения. Данная операция может быть выполнена при достижении статуса NotVerified, поэтому предварительным действием будет получение текущего статуса устройства методом updateStatus(...):

DSSUsersManager.updateStatus(user, new DSSUserCallback() {

    @Override
    public void success(@NonNull DSSUser user) {
        // Статус успешно обновился, теперь можно проверить текущий статус
        if (user.getStatus() == DSSDevice.DSSDeviceStatus.NotVerified) {
            // Статус сменился, можно подтверждать привязку устройства
        }
    }

    @Override
    public void error(@NonNull DSSError error) {
        // Ошибка при запросе статуса
    }

    @Override
    public void error(@NonNull DSSNetworkError error) {
        // Ошибка при запросе статуса
    }
});

Как только статус объекта DSSUser станет DSSDevice.DSSDeviceStatus.NotVerified, можно запустить процесс подтверждения присоединения устройства к учётной записи методом acceptAccountChanges(...):

DSSUsersManager.acceptAccountChanges(user, new DSSUserCallback() {

    @Override
    public void success(@NonNull DSSUser user) {
        // Подтверждение успешно.
        // Статус пользователя - DSSDevice.DSSDeviceStatus.Active
    }

    @Override
    public void error(@NonNull DSSError error) {
        // Ошибка подтверждения
    }

    @Override
    public void error(@NonNull DSSNetworkError error) {
        // Ошибка подтверждения
    }
});

При вызове этого метода будет показан экран с данными учётной записи (профиля пользователя DSS), к которой привязывается устройство. При успешном выполнении метода объект DSSUser переходит в статус DSSDevice.DSSDeviceStatus.Active - это означает, что устройство подтверждено и может использоваться для работы с сертификатами, операциями, документами и другими устройствами.

Регистрация с помощью QR-кода

Регистрация с помощью QR-кода, содержащего предварительные данные для регистрации, выполняется в два шага.

Шаг 1. Сканирование QR-кода и запуск регистрации

Для сканирования QR-кода и начала регистрации необходимо вызвать метод

DSSUsersManager.createDSSUserWithInitQR(
    @Nullable String externalId,  // Идентификатор сущности вызывающего приложения,
                        // к которому будет "привязан" объект 
    @Nullable String alias,       // Удобочитаемый идентификатор устройства
    @NonNull String name,        // Уникальное имя профиля в рамках приложения
    @NonNull String deviceName,  // Читаемое название устройства
    @Nullable PushNotificationData pushData,  // Данные для получения PUSH-уведомлений
    boolean requirePassword,  // Требуется ли установка пароля                          
    @Nullable DSSUserCallback callback  // Callback для обработки результатов  
)

В отличие от метода createDSSUser параметр serviceUrl не передаётся - адрес сервера извлекается из QR-кода. При успешном выполнении метода результат аналогичен - в коллбэк возвращается созданный объект DSSUser, сохранённый в долгосрочную память.

Шаг 2. Подтверждение привязки устройства к учётной записи

После успешного выполнения метода createDSSUserWithInitQR объект DSSUser будет сразу иметь статус NotVerified (но нужно выполнить синхронизацию с сервером методом updateStatus(...)), поэтому далее можно переходить к вызову acceptAccountChanges(...). Таким образом, шаг 2 полностью аналогичен шагу 3 сценария онлайн-регистрации.

Присоединение нового устройства

Данный способ применяется, когда для учётной записи уже есть привязанное подтверждённое устройство (объект DSSUser в статусе Active) и требуется привязать к этой же учётной записи новое устройство. Чтобы выполнить эту операцию, потребуется передать идентификатор данного пользователя на новое устройство.

Получить идентификатор можно у экземпляра пользователя:

// Получить идентификатор подтверждённого пользователя, к которому привязываем новое устройство
String uid = user.getDSSUserId();

Передать идентификатор с одного устройства на другое можно любым удобным способом. Один из вариантов - сформировать на подтверждённом устройстве QR-код и отсканировать его на добавляемом устройстве.

Процесс добавления нового устройства выполняется по следующему алгоритму:

  1. На добавляемом устройстве вызывается метод createDSSUserWithApproval(...) класса DSSUsersManager, который создаст нового пользователя со статусом DSSDevice.DSSDeviceStatus.ApproveRequired.
  2. На добавляемом устройстве отображается QR-код, содержащий необходимую информацию о данном устройстве. Этот экран показывается при выполнении метода createDSSUserWithApproval(...). Если его закрыть до завершения процесса присоединения нового устройства, то повторное открытие экрана нужно инициировать методом DSSUsersManager.checkApprovalStatus(...).
  3. На основном устройстве вызывается метод processAwaitingDevice(...) класса DSSDevicesManager для выполнения процедуры подтверждения или отклонения добавляемого устройства. При вызове этого метода пользователю будет предложено отсканировать QR-код с экрана добавляемого устройства.
  4. После сканирования QR-кода и просмотра информации о добавляемом устройстве пользователь выполняет подтверждение или отклонение добавляемого устройства.
  5. На добавляемом устройстве на экране c QR-кодом отображается кнопка проверки статуса добавляемого устройства. Пользователь может покинуть экран, дождавшись подтверждения с основного устройства, или сделать это сразу. В зависимости от этого, в приложение будет возвращён объект DSSUser со статусом DSSDevice.DSSDeviceStatus.ApproveRequired или DSSDevice.DSSDeviceStatus.Active. Кроме того, на добавляем устройстве будет производиться автоматическая проверка статуса, если в метод createDSSUserWithApproval(...) (или DSSUsersManager.checkApprovalStatus(...) при повторном открытии экрана) был передан ненулевой параметр checkingInterval.

Таким образом, сначала на новом устройстве вызываем метод:

DSSUsersManager.createDSSUserWithApproval(
    @Nullable String externalId,    // Идентификатор сущности вызывающего приложения,
                        // к которому будет "привязан" объект
    @Nullable String alias,         // Удобочитаемый идентификатор устройства
    @NonNull String name,       // Уникальное имя профиля в рамках приложения
    @NonNull String serviceUrl,     // URL для взаимодействия с myDSS
    @NonNull String deviceName,  // Читаемое название устройства
    @Nullable PushNotificationData pushData,  // Данные для получения PUSH-уведомлений
    @NonNull String uid, // Идентификатор пользователя, к которому привязываем устройство
    checkingInterval, // Интервал проверки статуса в секундах
    boolean requirePassword,  // Требуется ли установка пароля
    @Nullable DSSUserCallback callback  // Callback для обработки результатов
)

При вызове метода создаётся новый объект DSSUser со статусом DSSDevice.DSSDeviceStatus.ApproveRequired. Теперь требуется получить подтверждение с основного устройства. На основном устройстве вызываем метод processAwaitingDevice(...) класса DSSDevicesManager:

DSSDevicesManager.processAwaitingDevice(currentUser, new DSSDeviceApprovalCallback() {

    @Override
    public void success(@NonNull DSSDevicesManager.Action action) {
        // Устройство успешно подтверждено или отклонено
    }

    @Override
    public void error(@NonNull DSSError error) {
        // При обработке запроса возникла ошибка
    }

    @Override
    public void error(@NonNull DSSNetworkError error) {
        // При обработке запроса возникла ошибка
    }
});

При вызове метода processAwaitingDevice(...) будет открыт экран для сканирования QR-кода с добавляемого устройства, а после сканирования будет открыт экран с информацией о добавляемом устройстве (полученной из QR-кода) и кнопки для добавления устройства и отказа в добавлении. В зависимости от того, какую кнопку нажмёт пользователь, в метод success(...) коллбэка будет возвращено либо значение DSSDevicesManager.Action.Approve либо DSSDevicesManager.Action.Reject.

Регистрация в режиме УНЭП

Режим УНЭП допускает использование методов регистрации из класса DSSUsersManagerNonQual. Эти методы не открывают пользовательский интерфейс СДК.

Онлайн-регистрация

Онлайн-регистрация нового устройства осуществляется в четыре логических шага.

Шаг 1. Регистрация анонимного устройства

Для запуска регистрации анонимного устройства необходимо вызвать метод createDSSUser(...) класса DSSUsersManagerNonQual:

DSSUsersManagerNonQual.createDSSUser(
    @Nullable String externalId,
    @Nullable String alias,
    @NonNull String serviceUrl,
    @NonNull String deviceName,
    @Nullable PushNotificationData pushData,
    @Nullable DSSUserCallback callback
)

Все параметры аналогичны параметрам метода createDSSUser(...) класса DSSUsersManager, но параметры name и requirePassword не используются - они потребуются на следующем шаге.

После успешного выполнения метода возвращается объект DSSUser со статусом DSSDevice.DSSDeviceStatus.Created - это означает, ключи аутентификации ещё не были сохранены в долгосрочную память устройства - теперь их нужно сохранить.

Шаг 2. Сохранение в долгосрочную память

Теперь необходимо сохранить объект DSSuser в долгосрочную память. Для сохранения необходимо задать уникальное имя объекта и пароль, при помощи которого будут зашифрованы ключи. Сохранение осуществляется вызовом метода store(...):

DSSUsersManagerNonQual.store(
    @NonNull DSSUser user,
    @NonNull String name,
    @NonNull String password,
    @Nullable DSSUserCallback callback
)

После успешного выполнения метода объект DSSUser имеет статус DSSDevice.DSSDeviceStatus.Installed.

Передаваемый пароль должен соответствовать парольной политике (требованиям, предъявляемым к сложности пароля), возвращаемой методом DSSUser.getPasswordPolicy().

Шаг 3. Подтверждение регистрации оператором

Аналогично онлайн-регистрации в режиме УКЭП, на этом этапе на стороне сервера статус устройства должен быть переведён из Installed в NotVerified.

Шаг 4. Подтверждение привязки устройства к учётной записи

После того, как вызов метода DSSUsersManager.updateStatus(...) обновил статус объекта на NotVerified, необходимо выполнить подтверждение привязки устройства вызовом метода DSSUsersManagerNonQual.acceptAccountChanges(...):

DSSUsersManagerNonQual.acceptAccountChanges(
    @NonNull DSSUser user,
    @Nullable DSSQRCodeVerification qrCodeVerification,
    @Nullable DSSUserCallback callback
)

Параметр qrCodeVerification должен принимать значение null, когда процедура подтверждения привязки устройства к профилю не требует сканирования QR-кода, т.е. выполняется условие:

user.qRVerificationRequired() == false

для заданного объекта DSSUser.

В противном случае необходимо отсканировать QR-код, содержащий данные для подтверждения привязки устройства к учётной записи и передать в метод acceptAccountChanges валидный объект типа DSSQRCodeVerification. Чтобы получить экземпляр такого объекта, нужно вызвать метод MyDss.analyzeQr(String):

DSSQRCode qrCode = MyDss.analyzeQR(qrContent);

Далее, необходимо проверить, что QR-код был отсканирован корректно, и он действительно имеет заданный тип, т.е. должны выполняться условия:

qrCode != null && qrCode.isCorrect() && qrCode instanceof DSSQRCodeVerification

Успешное выполнение метода acceptAccountChanges завершает процесс онлайн-регистрации, статус объекта DSSuser меняется на Active.

Регистрация с помощью QR-кода

Шаг 1. Сканирование QR-кода

При регистрации с помощью QR-кода приложение сначала сканирует QR-кода типа DSSQRCodeKinit:

DSSQRCode qrCode = MyDss.analyzeQR(qrContent);
if (qrCode != null && qrCode.isCorrect() && qrCode instanceof DSSQRCodeKinit) {
    // Сканирован корректный QR-код - можно начинать регистрацию
}

Шаг 2. Регистрация устройства

Запуск процесса регистрации выполняется методом createDSSUserWithInitQR(...):

DSSUsersManagerNonQual.createDSSUserWithInitQR(
    @Nullable String externalId,
    @Nullable String alias,
    @NonNull DSSQRCodeKinit qrCodeKinit,
    @NonNull String serviceUrl,
    @NonNull String deviceName,
    @Nullable PushNotificationData pushData,
    @Nullable DSSUserCallback callback
)

Все параметры аналогичны параметрам метода DSSUsersManagerNonQual.createDSSUser(...), но ещё добавляется параметр с данными отсканированного ранее QR-кода.

Шаг 3. Сохранение в долгосрочную память

Как и для случая онлайн-регистрации, сохранить созданный объект DSSUser необходимо методом DSSUsersManagerNonQual.store(...).

Шаг 4. Подтверждение привязки устройства к учётной записи

После выполнения процедуры сохранения объекта DSSUser статус устройства меняется с Created на Installed. После синхронизации статуса методом DSSUsersManager.updateStatus(...) и получения статуса NotVerified нужно подтвердить привязку устройства к учётной записи вызовом DSSUsersManagerNonQual.acceptAccountChanges(...), как это делается в сценарии онлайн-регистрации.

Присоединение нового устройства 

Для режима УНЭП присоединение нового устройства требует большего числа вызовов, чем аналогичный сценарий для УКЭП.

Шаг 1. Запуск регистрации на добавляемом устройстве

Сначала на добавляемом устройстве вызывается метод createDSSUserWithApproval класса DSSUsersManagerNonQual:

DSSUsersManagerNonQual.createDSSUserWithApproval(
    @Nullable String externalId,
    @Nullable String alias,
    @NonNull String serviceUrl,
    @NonNull String deviceName,
    @Nullable PushNotificationData pushData,
    @NonNull String uid,
    @Nullable DSSUserCallback callback
)

Как и для режима УКЭП, параметр uid должен быть предварительно передан на присоединяемое устройство. При успешном выполнении в метод коллбэка DSSUserCallback.success(DSSUser) будет возвращён объект DSSUser в статусе Created - его нужно сохранить в память.

Шаг 2. Сохранение в долгосрочную память

Как и при других способах регистрации, сохранение происходит методом DSSUsersManagerNonQual.store(...).

Шаг 3. Поиск устройства, ожидающего подтверждения присоединения

Этот шаг выполняется на устройстве, с которого планируется выполнить подтверждение регистрации нового устройства. Для начала, необходимо найти неподтверждённое устройство в списке устройств:

DSSDevicesManager.listDevices(user, new DSSDevicesNetworkCallback() {
    @Override
    public void success(@NonNull DSSDevice[] devices) {
        for (DSSDevice device: devices) {
            // Ищем неподтверждённое устройство
            if (device.getStatus() == DSSDevice.DSSDeviceStatus.ApproveRequired) {
                // Неподтверждённое устройство найдено
            }

        }

    }

    @Override
    public void error(@NonNull DSSNetworkError error) {
        // Ошибка при запросе списка устройств
    }
});

Шаг 4. Подтверждение присоединения устройства

После того как устройство, ожидающее подтверждения, было найдено, его необходимо подтвердить методом DSSDevicesManagerNonQual.approve(...):

DSSDevicesManagerNonQual.approve(user, device, new DSSNetworkCallback() {
    @Override
    public void success() {
        // Присоединение устройства успешно подтверждено
    }

    @Override
    public void error(@NonNull DSSNetworkError error) {
        // При подтверждении возникла ошибка 
    }
});

Для отклонения запроса на регистрацию необходимо вызывать метод DSSDevicesManagerNonQual.reject(...) с аналогичной сигнатурой.

Шаг 5. Проверка статуса на присоединяемом устройстве

Чтобы проверить, было ли устройство подтверждено, необходимо воспользоваться методом DSSUsersManagerNonQual.checkApprovalStatus(...):

DSSUsersManagerNonQual.checkApprovalStatus(user, new DSSUserCallback() {
    @Override
    public void success(@NonNull DSSUser approvedUser) {
        // Присоединение данного устройства подтверждено на другом устройстве
    }

    @Override
    public void error(@NonNull DSSError error) {
        // Ошибка проверки статуса
    }

    @Override
    public void error(@NonNull DSSNetworkError error) {
        if (error.getType() == DSSNetworkError.DSS_ERROR_AWAITING_USER_CONFIRMATION) {
            // Запрос на присоединение ещё не обработан
        } else if (error.getType() == DSSNetworkError.DSS_ERROR_ACTION_REJECTED_BY_USER) {
            // Запрос на добавление этого устройства был отклонён на другом устройстве
        } else {
            // Ошибка проверки статуса
        }
    }
});

При успешном выполнении метода, объект approvedUser (является ссылкой на тот же объект, что и переданный параметр user) будет иметь статус Active, как и при завершении регистрации другими способами.

Работа с пользователями и устройствами

Работа с пользователями (помимо описанных выше процедур регистрации) включает:

  • Извлечение пользователей из долгосрочной памяти
  • Предъявление пароля
  • Смену пароля
  • Смену имени пользователя
  • Синхронизацию статуса пользователя
  • Обновление профиля пользователя
  • Получение истории действий пользователя
  • Удаление пользователя с устройства

Напомним, что под пользователем подразумевается объект DSSUser, включающий вектор аутентификации, установленный на данном устройстве, информацию о статусе устройства и другую сопутствующая информацию, описывающую параметры данного устройства и учётной записи, к которой устройство привязано.

Работа с привязанными к учётной записи устройствами включает:

  • Запрос информации об устройствах
  • Отзыв "вектора аутентификации" - делает невозможным дальнейшее взаимодействие с сервером с отзываемого устройства
  • Подтверждение и отклонение устройства, ожидающего присоединения (как было описано в примерах выше)

Извлечение пользователей из памяти

Для того, чтобы получить список сохранённых на устройстве пользователей, необходимо вызвать метод DSSUsersManager.listStorage():

List<DSSUser> users = DSSUsersManager.listStorage();
for (DSSUser user: users) {
    // Выполняем необходимые действия с каждым пользователем
}

Предъявление пароля

Некоторые методы СДК требуют выполнения условия user.isReadyToSign(), т.е. требуют, чтобы перед их вызовом был предъявлен пароль. В документации по API для всех таких методов есть соответствующая пометка.

При работе в режиме УКЭП предъявление пароля осуществляется вызовом метода DSSUsersManager.submitPassword(...). Допустим, приложение хочет вызвать метод удаления сертификата пользователя (требует ввода пароля). Тогда процедура вызова метода будет выглядеть следующим образом:

if (user.isReadyToSign()) {
    // Предъявлять пароль не нужно - можно вызывать метод
    DSSCertificatesManager.deleteCertificate(...)
} else {
    // Требуется ввод пароля
    DSSUsersManager.submitPassword(user, new DSSUserCallback() {
        @Override
        public void success(@NonNull DSSUser user) {
            // Предъявлен верный пароль - можно вызывать метод
            DSSCertificatesManager.deleteCertificate(...)
        }

        @Override
        public void error(@NonNull DSSError error) {
            // Ошибка при предъявлении пароля
        }

        @Override
        public void error(@NonNull DSSNetworkError error) {
            // Сетевая ошибка - для случая ввода пароля она не возникает
            // и этот метод не вызывается
        }
    });
}

Следует учитывать, что метод error(DSSError) коллбэка будет вызван только при возникновении каких-либо критичных ошибок (либо при закрытии экрана СДК). При вводе неправильного пароля будет отображено соответствующее сообщение на экране СДК, коллбэк вызываться не будет.

Следует также иметь в виду, что если регистрация в режиме УКЭП проходила без ручного задания пароля (было передано значение false в качестве параметра requirePassword), и объект DSSUser был сохранён с использованием пароля по умолчанию, то метод user.isReadyToSign() всегда будет возвращать значение true.

В режиме УНЭП предъявление пароля осуществляется без открытия пользовательского интерфейса путём передачи значения пароля напрямую в метод submitPassword(...) класса DSSUsersManagerNonQual:

if (user.isReadyToSign()) {
    // Предъявлять пароль не нужно - можно вызывать метод
    DSSCertificatesManager.deleteCertificate(...)
} else {
    // Требуется ввод пароля
    DSSError result = DSSUsersManagerNonQual.submitPassword(user, password);
    if (result.getType() == DSSError.DSS_ERROR_OK) {
        // Предъявлен верный пароль - можно вызывать метод
        DSSCertificatesManager.deleteCertificate(...)
    } else {
        // При предъявлении пароль возникла ошибка (в т.ч., пароль мог оказаться неверным)
    }
}

В независимости от того используется ли метод ввода пароля из класса DSSUsersManager или DSSUsersManagerNonQual, после однократного ввода пароля нет необходимости вводить его заново до тех пор, пока приложение находится на переднем плане. При сворачивании приложения более чем на 15 секунд, однако, потребуется снова вводить пароль.

Смена пароля

Смена пароля для режима УКЭП осуществляется вызовом метода changePassword класса DSSUsersManager:

DSSUsersManager.changePassword(user, true, new DSSUserCallback() {
    @Override
    public void success(@NonNull DSSUser user) {
        // Пароль успешно изменён
    }

    @Override
    public void error(@NonNull DSSError error) {
        // Ошибка смены пароля
    }

    @Override
    public void error(@NonNull DSSNetworkError error) {
        // Не вызывается при смене пароля (нет сетевых запросов)
    }
});

Этот метод предложит ввести текущий пароль (если он не был введён ранее), а затем откроет экран задания нового пароля.

В режиме УНЭП для смены пароля нужно передать новое значение пароля в метод changePassword класса DSSUsersManagerNonQual:

if (user.isReadyToSign()) {
    // Можно сразу вызывать метод смены пароля
    DSSError result = DSSUsersManagerNonQual.changePassword(user, newPassword);
    if (result.getType() != DSSError.DSS_ERROR_OK) {
        // При смене пароля возникла ошибка
    }
} else {
    // Сначала требуется ввести старый пароль
    DSSError submissionResult = DSSUsersManagerNonQual.submitPassword(user, oldPassword);
    if (submissionResult.getType() == DSSError.DSS_ERROR_OK) {
        // Предъявлен верный пароль - можно вызывать метод смены пароля
        DSSError result = DSSUsersManagerNonQual.changePassword(user, newPassword);
        if (result.getType() != DSSError.DSS_ERROR_OK) {
            // При смене пароля возникла ошибка
        }
    } else {
        // При предъявлении пароль возникла ошибка (в т.ч., пароль мог оказаться неверным)
    }
}

В режиме УКЭП при задании и смене пароля в пользовательском интерфейсе СДК может быть предложено использовать отпечаток пальца вместо пароля. При самостоятельной реализации такой функциональности в режиме УНЭП следует учитывать, допускают ли флаги ключа использование отпечатка пальца: метод user.isDenyStoreWithOSProtection() должен возвращать false.

Смена имени пользователя

Имя пользователя задаётся либо параметром name методов регистрации класса DSSusersManager, либо одноимённым параметром метода store класса DSSUsersManagerNonQual при работе в режиме УНЭП. В любом случае, для последующей смены этого имени необходимо вызывать метод rename класса DSSUsersManager:

DSSError result = DSSUsersManager.rename(user, newName);
if (result.getType() != DSSError.DSS_ERROR_OK) {
    // Ошибка при смене имени 
}

Синхронизация статуса пользователя

Для получения актуального статуса пользователя используется метод DSSUsersManager.updateStatus(...), примеры использования которого описаны выше в сценариях регистрации.

Обновление профиля пользователя

Обновление профиля необходимо для того, чтобы не пришлось заново регистрировать устройство методами DSSUsersManager.createDSSUser(...) после истечения срока действия ключей, привязанных к устройству. Время окончания срока действия ключей можно получить через вызов DSSUser.getNotAfter().

Обновление профиля для режима УКЭП осуществляется вызовом метода renew класса DSSUsersManager:

DSSUsersManager.renew(
    user,
    newName,
    deviceName,
    new DSSUserCallback() {
        @Override
        public void success(@NonNull DSSUser renewedUser) {
            // Профиль успешно обновлён
        }

        @Override
        public void error(@NonNull DSSError error) {
            // Ошибка обновления профиля
        }

        @Override
        public void error(@NonNull DSSNetworkError error) {
            // Сетевая ошибка обновления профиля
        }
    });

Этот метод предложит ввести текущий пароль (вне зависимости от того, был он введён ранее или нет), а затем обновит профиль.

В режиме УНЭП для обновления профиля нужно передать значение пароля в метод renew класса DSSUsersManagerNonQual:

DSSUsersManagerNonQual.renew(
    user,
    newName,
    deviceName,
    password,
    new DSSUserCallback() {
        @Override
        public void success(@NonNull DSSUser renewedUser) {
            // Профиль успешно обновлён
        }

        @Override
        public void error(@NonNull DSSError error) {
            // Ошибка обновления профиля
        }

        @Override
        public void error(@NonNull DSSNetworkError error) {
            // Сетевая ошибка обновления профиля
        }
    });

Получение истории действий пользователя

Получить историю действия пользователя на сервере DSS позволяет метод DSSUsersManager.getOperationsHistory(...):

DSSUsersManager.getOperationsHistory(
    @NonNull DSSUser user,
    int count,
    int bookmark,
    @Nullable int[] operationCodes,
    @Nullable DSSOperationsHistoryCallback callback
)    

Параметры count, bookmark и operationCodes позволяют сузить множество возвращаемых записей. Более подробно работа с объектами журнала действий пользователя описана в Документации по API.

Удаление пользователя с устройства

Удаление объекта DSSUser из памяти приложения делает невозможным использование настоящего устройства для взаимодействия с сервером DSS от имени учётной записи, к которой привязано устройство (однако, в дальнейшем устройство можно привязать заново).

Удаление объекта DSSUser может быть произведено либо с уведомлением сервера (вызовом DSSUsersManager.revoke(DSSuser, DSSUserCallback)), либо без уведомления сервера (вызовом DSSusersManager.delete(DSSUser)).

В первом случае устройство будет удалено и из памяти приложения и на стороне сервера, однако выполнение метода revoke() требует предъявления пароля. Тем временем, вызов delete() просто стирает данные из памяти приложения, но синхронизация с сервером не производится, пароль в этом случае предъявлять не требуется.

Работа с устройствами

Для работы со всеми текущими привязанными к учётной записи устройствами используются классы DSSDevicesManager и DSSDevicesManagerNonQual.

Пример получения списка всех привязанных устройств методом DSSDevicesManager.listDevices(), а также примеры обработки запроса на присоединение нового устройства в режимах УКЭП и УНЭП были описаны выше в разделах Присоединение нового устройства в режиме УКЭП и Присоединение нового устройства в режиме УНЭП.

Метод DSSDevicesManager.revoke(...) позволяет отозвать вектор аутентификации с заданного устройства (делает невозможным использование заданного устройства для взаимодействия с сервером DSS). Этот метод имеет смысл применять только при отзыве некоторого устройства отличного от данного (на котором выполняется вызов), для отзыва данного устройства необходимо вызывать метод DSSUsersManager.revoke(DSSuser, DSSUserCallback).

Работа с сертификатами

Для работы с сертификатами используется класс DSSCertificatesManager. Кроме того, есть класс DSSCertificatesManagerNonQual, который предоставляет функционал для работы с ключами и сертификатами, располагающимися на мобильном устройстве и на рутокенах. Подробнее об этом классе можно прочесть в разделе Работа с клиентской подписью.

СДК работает с тремя типами сущностей:

  1. Сертификатами, которыми оперирует СДК;
  2. Запросами на выпуск сертификата;
  3. Сертификатами, прочитанными из внешних хранилищ (с Рутокена) с помощью вызова метода DSSCertificatesManagerNonQual.getExternalCertificates(DSSExternalCertificatesCallback). После вызова DSSCertificatesManager.setCertificate(DSSUser, DSSCertificate, DSSCertificateNetworkCallback), с таким "внешнем сертификатом", он преобразуется в сертификат, которым оперирует СДК.

Все эти сущности представлены экземплярами класса DSSCertificate. Для сертификата метод DSSCertificate.getType() возвращает значение DSSCertificate.Type.Crt, в то время как для запроса на сертификат тип будет иметь значение DSSCertificate.Type.Req. Метод DSSCertificate.getState() возвращает текущее состояние сертификата или запроса на сертификат. Возможные возвращаемые значения этого метода будут разными для сертификатов и запросов. Подробно о возможных состояниях можно прочесть в описании перечисления DSSCertificate.State.

Получение списка сертификатов

Для получения списка сертификатов пользователя и запросов на выпуск сертификата используется метод listCertificates(...):

DSSCertificatesManager.listCertificates(user, new DSSCertificatesNetworkCallback() {
    @Override
    public void success(@NonNull DSSCertificate[] certificates) {
        if (certificates.length > 0) {
            // Список сертификатов и запросов непустой - можно выполнять какие-либо действия
            for (DSSCertificate item: certificates) {
                if (item.getType() == DSSCertificate.Type.Crt) {
                    // Выполняем действия с сертификатом
                } else {
                    // Выполняем действия с запросом на сертификат
                }
            }
        }
    }

    @Override
    public void error(@NonNull DSSNetworkError error) {
        // Ошибка запроса списка
    }
});

Создание запроса на сертификат

СДК поддерживает сценарий создания запроса на сертификат из мобильного приложения. Для этого необходимо указать идентификатор обработчика УЦ, идентификатор шаблона сертификата и различительное имя субъекта, состоящее из набора ключей и значений компонентов имени.

Для получения этих данных необходимо запросить политики сервиса подписи:

DSSPolicyManager.getDSSSignParams(user, new DSSPolicySignServerNetworkCallback() {
    @Override
    public void success(@NonNull DSSSignServerParams params) {
        // Параметры сервиса подписи получены, извлекаем необходимые данные
        // Дальнейший код дан ДЛЯ ПРИМЕРА:
        // Берётся первый доступный УЦ и первый доступный шаблон

        HashMap<String, String> dn = new HashMap<>();
        // Для примера в качестве Distinguished name задаём только
        // компонент Common name (OID 2.5.4.3)
        dn.put("2.5.4.3", "TestCommonName"); 

        int caId = 0;
        String tid = "";
        // Ищем УЦ и шаблон
        for (DSSSignServerParams.CaPolicies policies :params.getCaPolicies()) {
            caId = policies.getId();
            Set<String> keys = policies.getEkuTemplates().keySet();
            for (String key : keys) {
                String[] list = policies.getEkuTemplates().get(key);
                if (list != null && list.length > 0)
                    tid = list[0];
                break;
            }
            break;
        }
    }

    @Override
    public void error(@NonNull DSSNetworkError error) {
        // Ошибка запроса параметров
    }
});    

Затем нужно передать собранные параметры методу DSSCertificatesManager.createCertificate(...):

DSSCertificatesManager.createCertificate(user, caId, tid, dn, new DSSCertificateNetworkCallback() {
    @Override
    public void success(DSSCertificate request) {
        // Запрос на сертификат создан.
        // Объект request содержит данные запроса
    }

    @Override
    public void error(@NonNull DSSNetworkError error) {
        // Ошибка создания запроса
    }
});

Управление сертификатами и запросами

СДК предоставляет функциональность для управления существующими сертификатами и запросами. Методы для управления сертификатами и запросами расположены в классе DSSCertificatesManager.

Название метода Предназначение
deleteCertificate Удаление сертификата или запроса на сертификат
setCertificate Установка сертификата пользователя
setCertificateFriendlyName Установка дружественного имени сертификата
revokeCertificate Отзыв сертификата
suspendCertificate Приостановка действия сертификата
unSuspendCertificate Возобновление действия сертификата
setDefaultCertificate Установка сертификата по умолчанию

Подробнее о каждом методе можно прочесть в документации на класс DSSCertificatesManager.

Работа с операциями и документами

Работа с операциями и документами осуществляется с помощью класса DSSOperationsManager. Класс DSSOperationsManagerNonQual предоставляет средства работы с операциями и документами в режиме УНЭП без запуска пользовательского интерфейса.

Запрос списка операций

Получить список операций, требующих обработки, можно вызовом метода DSSOperationsManager.getOperationsList(...):

DSSOperationsManager.getOperationsList(
    @NonNull DSSUser user,
    @Nullable DSSOperationsManager.OperationType operationType,
    @Nullable String operationId,
    @Nullable DSSOperationsNetworkCallback callback
)

Параметры operationType и operationId позволяют отфильтровать список либо по типу операции, либо по её идентификатору (тогда будет возвращён список из одной операции). Для возвращения полного списка на обеих позициях нужно передать значение null.

Подтверждение операции

Подтверждение операции в режиме УКЭП

Для подтверждения или отклонения операции в режиме УКЭП достаточно вызвать один метод DSSOperationsManager.confirmOperation(...):

DSSOperationsManager.confirmOperation(
    @NonNull DSSUser user,
    @NonNull DSSOperation operation,
    @Nullable DSSOperationsManager.SignMode signMode,
    boolean isSelectionEnabled,
    boolean skipSnippet,
    @Nullable DSSApproveRequestNetworkCallback callback
)

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

Параметр signMode определяет способ обработки: онлайн (с отправкой запроса на сервер) и оффлайн (создание запроса на подтверждение без отправки самого запроса). При указании null используется онлайн-подтверждение.

Параметр isSelectionEnabled определяет, разрешено ли отмечать галочкой документы для обработки, либо же все документы обрабатываются (подтверждаются или отклоняются) целиком.

При передаче true в качестве параметра skipSnippet при наличии одного единственного документа в операции будет сразу отображён этот документ (вместо списка документов, как это происходит по умолчанию).

Если при выполнении операции возникает ошибка, то вызывается метод

DSSApproveRequestNetworkCallback.error(DSSOperationsManager.ApproveRequest)

Информацию о возникшей ошибке можно получить вызовом методов getOnlineConfirmationResult() (возвращает объект сетевой ошибки DSSNetworkError) и getOfflineError() (возвращает объект внутренней ошибки DSSError). Так как возникает ошибка либо одного, либо другого типа, один из методов вернёт значение null, а другой - объект ошибки.

При успешной обработке вызывается

DSSApproveRequestNetworkCallback.success(DSSOperationsManager.ApproveRequest)

Информацию о том, какие документы были подтверждены и отклонены можно получить вызовом методов getConfirmedDocuments() и getDeclinedDocuments() полученного объекта ApproveRequest.

Подтверждение операции в режиме УНЭП

В режиме УНЭП приложение может самостоятельно отобразить данные операции и интерфейс выбора документов для подтверждения и отклонения, а также запросить пароль. После этого необходимо вызвать метод DSSOperationsManagerNonQual.confirmOperation(...) и передать туда список документов на подтверждение и отклонение:

DSSOperationsManagerNonQual.confirmOperation(
    @NonNull DSSUser user,
    @NonNull DSSOperation operation,
    @Nullable List<DSSOperation.DSSDocument> documentsToConfirm,
    @Nullable List<DSSOperation.DSSDocument> documentsToDecline,
    @Nullable DSSOperationsManager.SignMode signMode,
    @NonNull KeysSource keysSource,
    @Nullable DSSApproveRequestNetworkCallback callback
) 

Если нет документов на подтверждение, то в качестве documentsToConfirm нужно передать null (не нужно передавать пустой список). Таким же образом нужно действовать и при отсутствии документов на отклонение. Параметры documentsToConfirm и documentsToDecline не должны иметь значение null одновременно.

Загрузка документов на сервер

Для загрузки документа на сервер необходимо использовать метод uploadDocument(...) класса DSSOperationsManager:

DSSOperationsManager.uploadDocument(
    @NonNull DSSUser user,
    @NonNull String title,
    @Nullable String snippetTemplate,
    @Nullable String previewTemplate,
    @NonNull byte[] content,
    @Nullable DSSDocumentIdNetworkCallback callback
)

Параметры title и content задают название и содержимое документа соответственно. Параметры snippetTemplate и previewTemplate задаются в виде HTML-текста и определяют шаблон отображения сниппета документа (краткое описание для отображения документа в списке других документов) и шаблон предварительно просмотра документа. Оба параметра могут не задаваться (иметь значение null) - формат отображения будет определяться настроенными на сервере шаблонами.

Получение данных документа

Скачать с сервера описание документа (представлено экземплярами класса DSSOperation.DSSDocument) можно методом getDocumentDescription(...) класса DSSOperationsManager:

DSSOperationsManager.getDocumentDescription(
    @NonNull DSSUser user,
    @NonNull String documentId,
    @Nullable DSSDocumentNetworkCallback callback
)

Получение содержимого документа

Для получения оригинального содержимого документа необходимо вызвать метод:

DSSOperationsManager.getDocumentBinaryData(
    @NonNull DSSUser user,
    @NonNull String docId,
    @NonNull DSSGetDocumentBinaryDataCallback callback
)

Когда содержимое документа будет полностью скачано, будет вызван метод:

DSSGetDocumentBinaryDataCallback.success(File)

Кроме того, для отслеживания прогресса скачивания в реализации интерфейса DSSGetDocumentBinaryDataCallback можно переопределить метод с реализацией по умолчанию:

default void onSomeBytesDownloaded(int downloaded, int total) {
    // Скачано downloaded байт из всего total байт
}

При работе в режиме УНЭП возможно также получить представление документа для предварительного просмотра (метод DSSOperationsManagerNonQual.getDocumentPreview(...)) и "сырое" представление документа в формате PDF (метод DSSOperationsManagerNonQual.getDocumentRawPDF(...)).

Подписание документов

Подписание документов в режиме УКЭП

В режиме УКЭП подписание документов осуществляется с помощью вызова метода signDocuments(...) класса DSSOperationsManager:

signDocuments(
    @NonNull DSSUser user,
    @NonNull ArrayList<String> documentIds,
    @NonNull DSSOperationsManager.SignParams params,
    @Nullable DSSSignResultNetworkCallback callback
) 

Данный метод отобразит экран со списком подписываемых документов и кнопкой "Подписать", по нажатию на которую на сервер будет отправлен запрос на подписание (а также предварительно будет запрошен пароль при необходимости).

Данный метод требует передачи параметров подписания в виде объекта params. Параметры подписания включают идентификатор сертификата для подписания и идентификатор шаблона подписи.

Идентификатор шаблона подписи можно получить из параметров сервиса подписи:

DSSPolicyManager.getDSSSignParams(user, new DSSPolicySignServerNetworkCallback() {
    @Override
    public void success(@NonNull DSSSignServerParams params) {
        // Параметры сервиса подписи получены, извлекаем необходимые данные
        // Дальнейший код дан ДЛЯ ПРИМЕРА:
        // Берётся первый доступный шаблон подписи
        String templateId = params.getProcessingTemplates()[0].getId();
    }

    @Override
    public void error(@NonNull DSSNetworkError error) {
        // Ошибка запроса параметров
    }
});    

Подписание документов в режиме УНЭП

В режиме УНЭП подписание документов можно выполнить методом signDocuments(...) класса DSSOperationsManagerNonQual:

DSSOperationsManagerNonQual.signDocuments(
    @NonNull DSSUser user,
    @NonNull List<DSSOperation.DSSDocument> confirmedDocuments,
    @NonNull DSSOperationsManager.SignParams params,
    @NonNull KeysSource keysSource,
    @Nullable DSSSignResultNetworkCallback callback
)

В отличие от метода signDocuments класса DSSOperationsManager данный метод принимает список самих документов (а не их идентификаторов), не отображает пользовательский интерфейс для просмотра документов и не отображает экран ввода пароля (на момент вызова этого метода пароль уже должен быть введён).

Работа с клиентской подписью

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

Создание неподписанного запроса на сертификат

Процесс установки ключей подписи на мобильном устройстве начинается с создания неподписанного запроса на сертификат. В наиболее типичном сценарии этот запрос будет создан оператором на сервере DSS. Однако, СДК поддерживает возможность создания такого запроса на стороне приложения. Для создания неподписанного запроса нужно вызвать метод createCertificate класса DSSCertificatesManagerNonQual:

DSSCertificatesManagerNonQual.createCertificate(
    @NonNull DSSUser user,
    int caId,
    @NonNull String templateId,
    @NonNull Map<String, String> dn,
    boolean isClient,
    @Nullable DSSCertificateNetworkCallback callback
)

Использование метода аналогично применению одноимённого метода из класса DSSCertificatesManager, за исключением того, что необходимо передать значение true на месте параметра isClient (передача параметра false инициирует создание обычного запроса на сертификат, как при использовании DSSCertificatesManager.createCertificate(...)).

Подписание запроса на сертификат

На этом шаге в приложении создаётся ключевая пара и подписывается созданный запрос, после чего подписанный запрос отправляется на сервер. Далее этот запрос передаётся в УЦ для обработки.

Найти неподписанный запрос можно, запросив список запросов и сертификатов и найдя запрос со статусом sign_wait:

DSSCertificatesManager.listCertificates(user, new DSSCertificatesNetworkCallback() {
    @Override
    public void success(DSSCertificate[] certificates) {
        for (DSSCertificate item: certificates) {
            if (item.getType() == DSSCertificate.Type.Req && item.getState() == DSSCertificate.State.sign_wait) {
                // Неподписанный запрос найден
            }
        }
    }

    @Override
    public void error(DSSNetworkError dssNetworkError) {
        // Ошибка запроса списка сертификатов и запросов
    }
});

Далее, запрос нужно подписать при помощи метода signCertificateRequest(...) класса DSSCertificatesManagerNonQual:

DSSCertificatesManagerNonQual.signCertificateRequest(user, request, keysSource, new DSSSignCertificateRequestCallback() {
    @Override
    public void success(@NonNull DSSUser user, @NonNull DSSCertificate request) {
        // Запрос успешно подписан
        // Объект request содержит информацию о подписанном запросе
    }

    @Override
    public void error(@NonNull DSSError error) {
        // При подписании запроса возникла ошибка
    }

    @Override
    public void error(@NonNull DSSNetworkError error) {
        // При отправке подписанного запроса возникла ошибка
    }
});

Данный метод откроет экран ввода пароля (при необходимости), после чего запустит биологический датчик случайных чисел (пользователю будет предложено выполнить серию касаний экрана), после чего будет сгенерирована ключевая пара и подписан запрос, результат подписания будет отправлен на сервер. Если в качестве хранилища будет указан рутокен, тогда будет запрошен пин-код токена.

Установка сертификата

После выпуска сертификата удостоверяющим центром новый сертификат должен быть установлен в память приложения.

Для начала необходимо найти неустановленный сертификат в списке запросов и сертификатов:

DSSCertificatesManager.listCertificates(currentUser, new DSSCertificatesNetworkCallback() {
    @Override
    public void success(DSSCertificate[] certificates) {
        for (DSSCertificate crt: certificates) {
            if (crt.getType() == DSSCertificate.Type.Crt
                    && crt.getState() == DSSCertificate.State.active
                    && crt.isClient()
                    && DSSCertificatesManagerNonQual.isCertificateAccessibleOnThisDevice(user, crt)
                    && !DSSCertificatesManagerNonQual.isCertificateInstalled(user, crt)) {
                // Найден неустановленный сертификат, необходимо выполнить установку
            }
        }
    }

    @Override
    public void error(DSSNetworkError error) {
        // Ошибка получения списка сертификатов и запросов
    }
});

Логическая конструкция включает проверку следующих обязательных для установки сертификата условий:

  1. Объект DSSCertificate является сертификатом, а не запросом
  2. Сертификат является активным (может использоваться для подписания)
  3. Сертификат выпущен по запросу, подписанному локальными (хранящимися на мобильном устройстве) ключами
  4. Создание ключей и подписание запроса производилось на этом устройстве
  5. Сертификат ещё не был установлен

Далее, можно устанавливать сертификат. Для этого нужно вызвать метод installCertificate класса DSSCertificatesManagerNonQual:

DSSCertificatesManagerNonQual.installCertificate(
    @NonNull DSSUser user,
    @NonNull DSSCertificate certificate,
    @Nullable DSSUserCallback callback
)

После установки сертификата, хранимые на устройстве ключи могут использоваться при подписании и расшифровании документов наравне с ключами, хранимыми на сервере DSS.

Подтверждение операций клиентскими ключами

Операции, созданные на сервере DSS, содержат идентификатор сертификата, который будет использоваться при подписании. При вызове методов confirmOperation(...) классов DSSOperationsManager и DSSOperationsManagerNonQual СДК самостоятельно определит, требуется ли использовать локальные ключи для обработки операции, поэтому никаких дополнительных действий со стороны приложения не нужно.

Подписание документов клиентскими ключами

Для подписания документов клиентскими ключами необходимо при вызове методов signDocuments(...) классов DSSOperationsManager и DSSOperationsManagerNonQual в параметре типа DSSOperationsManager.SignParams указать идентификатор сертификата, ранее установленного в приложение методом DSSCertificatesManagerNonQual.installCertificate(...).

Особенности работы с несколькими устройствами

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

if (!certificate.isClient() || DSSCertificatesManagerNonQual.isCertificateAccessibleOnThisDevice(user, certificate)) {
    // Сертификат либо облачный (ключи хранятся на сервере DSS),
    // либо установлен на этом устройстве,
    // то есть им можно пользоваться
}

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

Изменение внешнего вида БиоДСЧ

При вызове метода DSSCertificatesManagerNonQual.signCertificateRequest(...) открывается экран с биологическим датчиком случайных чисел. Чтобы изменить внешний вид этого экрана под стиль приложения, необходимо переопределить следующие ресурсы цветов:

<!-- Цвет полосы состояния (StatusBar) -->
<color name="brand_dark_blue">#002f6e</color>
<!-- Цвет кнопки "Отмена" и заполненной части полосы прогресса -->
<color name="brand_blue">#22579D</color>

<!-- Цвет полосы состояния для ночного режима (StatusBar) -->
<color name="night_brand_dark_blue">#121E29</color>
<!-- Цвет кнопки "Отмена" и заполненной части полосы прогресса для ночного режима -->
<color name="night_brand_light_blue">#66A3D8</color>
<!-- Цвет фона ДСЧ для ночного режима -->
<color name="night_brand_color_background">#101A23</color>

<!-- Цвет ячейки, где было касание экрана (для обычного и ночного режима) -->
<color name="bio_cell_color">#888888</color>

Кроме того, можно задать следующие размеры:

<!-- Размер заголовка окна ДСЧ -->
<dimen name="dialog_title_text_size">15sp</dimen>
<!-- Размер кнопки отмены -->
<dimen name="mng_sub_text_size">14sp</dimen>

Следующие строки формируют заголовок окна, центральный текст и текст кнопки отмены соответственно:

<string name="CompanyName">КриптоПро CSP</string>
<string name="BioRnd">Биологический датчик случайных чисел.\n Нажимайте на экран, пока ключ \n не будет создан.</string>
<string name="Cancel">Отмена</string>

Дополнительные параметры

Приведённые выше примеры описывают работу с клиентской подписью с использованием параметров по умолчанию (СДК самостоятельно задаёт имя контейнера для хранения ключей и самостоятельно генерирует ПИН-код для доступа к контейнеру). Такой режим работы СДК является рекомендованным (и наиболее простым в использовании). Однако при необходимости можно использовать класс DSSKeysManagerNonQual для самостоятельного управления ключами, а также использовать перегрузки методов signCertificateRequest(...) и installCertificate(...) класса DSSCertificatesManagerNonQual с расширенным набором параметров.

Автоматическая подпись

Автоматическая подпись представляет собой механизм подтверждения серии операций без необходимости взаимодействия с приложением.

Для реализации такого механизма приложение должно выполнить следующие шаги.

1. Обнаружение первой операции в цепочке автоматического подписания

При запросе списка операций классическим методом DSSOperationsManager,getOperationList(...) для возвращаемых операций необходимо проверить значение статуса автоподписи, возвращаемое методом DSSOperation.getAutoSignState(). Первая операция в цепочке автоподписи будет иметь значение Aware. С подтверждения этой операции запускается процесс автоподписания.

2. Подтверждение первой операции

Первая операция в цепочке автоподписания (со статусом Aware) подтверждается обычным образом, как и все другие операции, через метод confirmOperation(...). Содержимое операции будет отображено пользователю, при необходимости будет запрошен пароль (т.е. визуально подтверждение такой операции ничем не отличается).

3. Запрос подтверждения перехода в режим автоматического подписания

После подтверждения операции со статусом Aware приложение должно отобразить пользователю экран с предложением подтверждать дальнейшие операции из цепочки в автоматическом режиме. При показе этого экрана может быть использована информация о прикладной системе, создавшей операцию. Получить эту информацию можно при помощи метода DSSOperation.getAppSystemInfo(). В частности, приложению необходимо сохранить идентификатор прикладной системы, возвращаемый методом AppSystemInfo.getClientId(). Данный идентификатор нужен для получения остальных операций из цепочки.

4. Получение последующих операций на автоподписание

После согласия пользователя перейти в режим автоматического подписания приложение должно отобразить экран, информирующий, что пользователь находится в режиме автоматической подписи. В этом режиме приложение должно периодически опрашивать сервер на наличие следующих операций в цепочке. Для этого нужно вызывать перегрузку метода DSSOperationsManager.getOperationList(...), принимающую идентификатор прикладной системы (должен быть сохранён из первой операции вызовом AppSystemInfo.getClientId()) в качестве параметра фильтрации. В качестве результата сервер будет возвращать операции, для которых DSSOperation.getAutoSignState() == Enabled, что означает возможность подтверждения этих операций автоматически без отображения пользователю.

5. Подтверждение полученных операций

Полученные операции со статусом DSSOperation.getAutoSignState() == Enabled должны быть подтверждены методом DSSOperationsManagerNonQual.confirmOperation(...). При этом, перед вызовом необходимо проверить выполнение условия user.isReadyToSign() == true, так как данный метод подтверждает операцию в фоновом режиме и не запрашивает пароль. При невыполнении условия user.isReadyToSign() == false (может возникнуть, например, вследствие сворачивания приложения более чем на 15 секунд) следует вывести приложение из режима автоподписи и вернуться в точку, из которой операции запрашиваются классическим образом через метод DSSOperationsManager.getOperationList(...) без указания clientId для фильтрации. Кроме того, следует выводить приложение из режима автоподписи по желанию пользователя, например, через возврат на предыдущий экран при нажатии системной кнопки "Назад".

Создание и восстановление резервных копий

Создание и восстановление резервных копий может быть полезно при смене устройства или если пользователь забыл пароль к своему профилю, при этом он создавал резервную копию и запомнил пароль для восстановления (recovery password). Профиль пользователя и ключи пользователя можно экспортировать и/или восстановить с помощью методов из таблицы ниже.

Наименование Предназначение
DSSUsersManagerNonQual.createBackup(...) Экспорт данных пользователя (объекта DSSUser) в JSON с шифрованием при помощи указанного пароля. Резервная копия возвращается в приложение в виде JSON-строки.
DSSUsersManagerNonQual.restoreBackup(...) Восстановление объекта DSSUser из резервной копии. Резервная копия представляет собой ранее созданную JSON-строку.
DSSCertificatesManagerNonQual.exportPfx(...) Создание резервной копии (архивация) ключей подписи, хранимых на мобильном устройстве, на сервере DSS. Опционально может быть указан ПИН-код для шифрования резервной копии. Если он не указан, сервером будет использован ПИН-код по умолчанию.
DSSCertificatesManagerNonQual.importPfx(...) Восстановление резервной копии ключей подписи, ранее архивированных на сервере DSS. Если при архивации был задан ПИН-код, его потребуется предъявить при восстановлении.
DSSCertificatesManagerNonQual.deletePfx(...) Удаление ранее созданной резервной копии ключей с сервера DSS
DSSKeysManagerNonQual.createBackup(...) Создание резервной копии ключей подписи, хранимых на мобильном устройстве с шифрованием при помощи указанного пароля. Резервная копия возвращается в приложение в виде JSON-строки.
DSSKeysManagerNonQual.restoreBackup(...) Восстановление локальных ключей подписи из резервной копии. Резервная копия представляет собой ранее созданную JSON-строку.

Резервное копирование данных профиля

Данные профиля (информация об устройстве и симметричные ключи аутентификации, хранимые в объекте DSSUser) могут быть архивированы в зашифрованном виде для дальнейшего восстановления на том же самом устройстве (например, при переустановке приложения), либо на другом устройстве (например, если пользователь решил сменить устройство или вынужден временно воспользоваться другим устройством).

Резервная копия профиля создаётся в виде JSON-строки, которая возвращается в приложение. JSON-строка содержит данные, зашифрованные при помощи указанного при создании копии пароля, и может безопасно быть сохранена приложением в желаемом месте.

Примером хранилища для резервных копий может служить личный Google Drive пользователя. Приложение может создать в Google Drive свою служебную директорию, доступную только самому приложению, и сохранять в неё резервную копию.

Пример экспорта профиля пользователя:

DSSUsersManagerNonQual.createBackup(user, recoveryPassword, new DSSUserBackupCallback() {

    @Override
    public void success(@NonNull String backup) {
        // Профиль пользователя был экспортирован в JSON-строку.
        // Приложение должно сохранить эту сроку таким образом, чтобы можно было отдать
        // её назад в СДК при восстановлении профиля на этом же или другом устройстве
    }

    @Override
    public void error(@NonNull DSSError error) {
        // Не удалось экспортировать профиль пользователя
    }
});

Когда потребуется восстановить данные профиля из резервной копии, приложение должно передать ту же самую JSON-строку и тот же пароль в метод восстановления резервной копии.

Пример импорта (восстановления) профиля пользователя:

DSSUsersManagerNonQual.restoreBackup(backupString, recoveryPassword, new DSSUserCallback() {

    @Override
    public void success(@NonNull DSSUser restoredUser) {
        // Пользователь успешно импортирован, далее его необходимо сохранить в долгосрочную память
        DSSUsersManagerNonQual.store(restoredUser, restoredUser.getName(), new DSSUserCallback() {

            @Override
            public void success(@NonNull DSSUser user) {
                // Пользователь готов к работе
            }

            @Override
            public void error(@NonNull DSSError error) {
                // Ошибка при сохранении восстановленного профиля пользователя
            }

            @Override
            public void error(@NonNull DSSNetworkError error) {
                // Сетевая ошибка при сохранении восстановленного профиля пользователя
            }
        });
    }

    @Override
    public void error(@NonNull DSSError error) {
        // Ошибка при импортировании профиля пользователя
    }

    @Override
    public void error(@NonNull DSSNetworkError error) {
        // Сетевая ошибка при импортировании профиля пользователя
    }
});

Архивирование ключей подписи на сервере DSS

На сервере DSS могут быть архивированы только ключи, созданные с флагом exportable. Все ключи подписи создаются автоматически с таким флагом, начиная с версии СДК 2.1.392, если только приложение само не использует метод DSSKeysManagerNonQual.createKeyPair(...) с передачей объекта KeyInfo с явно заданным параметром isExportable как false. До версии СДК 2.1.392 все ключи создавались как неэкспортируемые.

Для создания резервной копии ключей подписи в приложении нужно выбрать соответствующий сертификат и передать его в метод DSSCertificatesManagerNonQual.exportPfx(...). В примере ниже выполняется резервное копирование всех доступных на устройстве ключей подписи:

DSSCertificatesManager.listCertificates(user, new DSSCertificatesNetworkCallback() {
    @Override
    public void success(@NonNull DSSCertificate[] certificates) {
        // Успешно получили все запросы и сертификаты
        for (DSSCertificate entity: certificates) {
            // Проверяем каждый объект, чтобы найти сертификаты, пригодные для архивации ключей

            if (entity.getType() == DSSCertificate.Type.Req) {
                // Пропускаем все запросы на сертификаты
                continue;
            }

            if (!entity.isClient()) {
                // Пропускаем сертификаты, для которых ключи хранятся на сервере
                continue;
            }

            if (entity.isArchived()) {
                // Пропускаем ключи, которые уже были архивированы
                continue;
            }

            if (!DSSCertificatesManagerNonQual.isCertificateAccessibleOnThisDevice(user, entity)) {
                // Пропускаем ключи, которые хранятся на другом устройстве
                continue;
            }

            // Все необходимые условия проверены - можем архивировать ключи

            DSSCertificatesManagerNonQual.exportPfx(user, entity, null, "My PIN", new DSSExportPfxCallback() {
                @Override
                public void success() {
                    // Ключи успешно архивированы на сервере DSS
                }

                @Override
                public void error(@NonNull DSSError error) {
                    // Архивация ключей неуспешна из-за внутренней ошибки СДК
                }

                @Override
                public void error(@NonNull DSSNetworkError error) {
                    // Архивация ключей неуспешна из-за сетевой ошибки
                }
            });

        }
    }

    @Override
    public void error(@NonNull DSSNetworkError error) {
        // Ошибка получения списка сертификатов
    }
});

В качестве третьего параметра метод exportPfx(...) требует ПИН-кода от контейнера с ключевой информацией, подлежащей архивации. В приведённом выше примере передается null, так как СДК использует ПИН-код по умолчанию для защиты контейнеров. Если вы используете свой ПИН-код при создании объектов KeyInfo, то вместо null необходимо указывать его.

После архивации ключей их можно восстановить на том же самом устройстве (либо на другом устройстве, где есть объект DSSUser, привязанный к той же учётной записи). Для этого необходимо вызвать метод importPfx(...):

if (certificate.isArchived()) {
    // Ключи были архивированы ранее и могут быть восстановлены
    DSSCertificatesManagerNonQual.importPfx(user, certificate, null, "My PIN", new DSSImportPfxCallback() {
        @Override
        public void success() {
            // Ключи успешно восстановлены на устройстве
        }

        @Override
        public void error(@NonNull DSSError error) {
            // Восстановление ключей невозможно из-за внутренней ошибки СДК
        }

        @Override
        public void error(@NonNull DSSNetworkError error) {
            // Восстановление ключей невозможно из-за сетевой ошибки
        }
    });
}

Третий параметр задаёт ПИН-код для защиты контейнера с ключевой информацией. При указании null будет использован ПИН-код по умолчанию.

Созданную ранее архивную копию ключей можно удалить с сервера DSS:

if (certificate.isArchived()) {
    // Ключи были архивированы ранее - резервная копия может быть удалена
    DSSCertificatesManagerNonQual.deletePfx(user, certificate, new DSSDeletePfxCallback() {
        @Override
        public void success() {
            // Копия ключей удалена с сервера DSS
        }

        @Override
        public void error(@NonNull DSSError error) {
            // Удаление ключей провалилось из-за внутренней ошибки СДК
        }

        @Override
        public void error(@NonNull DSSNetworkError error) {
            // Удаление ключей провалилось из-за сетевой ошибки
        }
    });
}    

Архивирование ключей подписи с созданием резервной копии на устройстве

Альтернативно, приложение может выполнить резервное копирование ключей подписи аналогично данным профиля: создать резервную копию в виде JSON-строки с зашифрованными на пароле данными и сохранить эту строку в нужном месте средствами самого приложения, после чего восстановить ключи из этой же строки.

Пример создания резервной копии данных ключа подписи:

// Получить данные о локальных ключах пользователя
List<KeyInfo> keys = DSSKeysManagerNonQual.getKeysForUser(user);
// Взять ключ, для которого будет создана резервная копия
// для примера - только первый ключ, но чаще всего необходимо обойти всю коллекцию и для каждого локального ключа создать резервную копию
KeyInfo key = keys.get(0); 
DSSKeysManagerNonQual.createBackup(key, recoveryPass, new DSSKeyInfoBackupCallback() {

    @Override
    public void success(@NonNull String keysBackupJson) {
        // Данные ключа подписи были экспортированы в строку keysBackupJson, далее её можно сохранить
    }

    @Override
    public void error(@NonNull DSSError error) {
        // Ошибка при экспортировании данных ключа подписи
    }
});

Пример восстановления данных ключа подписи:

DSSKeysManagerNonQual.restoreBackup(keysBackupJson, recoveryPass,
                new DSSKeyInfoRestorationCallback() {

    @Override
    public void success(@NonNull KeyInfo keyInfo) {
        // Данные ключа подписи были успешно импортированы
    }

    @Override
    public void error(@NonNull DSSError error) {
        // Ошибка при импортировании данных ключа подписи
    }
});

Кастомизация пользовательского интерфейса

При работе в режиме УКЭП СДК показывает пользовательский интерфейс при инициализации, регистрации устройства, подтверждении операций, подписании документов и других действиях.

Кастомизация с помощью класса Appearance

Класс Appearance может быть использован для кастомизации пользовательского интерфейса, отображаемого СДК. Приложение может менять следующие параметры:

  • Цвет фона экранов и отдельных элементов (кнопок, галочек и др.)
  • Цвета, шрифты и размеры отдельных надписей
  • Иконки, используемые в интерфейсе
  • Параметры панели действий (ActionBar)
  • Анимации смены фрагментов, диалоговых окон, некоторых представлений.

Полный список настроек можно посмотреть в документации на класс Appearance.

Ниже приведено несколько примеров применения класса Appearance:

// Изменение цвета основных кнопок
MyDss.getAppearance().getButtons().getPrimary().backgroundColor = R.color.some_color_in_my_app;

// Изменение ресурса иконки информации об операции
MyDss.getAppearance().getImages().getOperationInfoIcon().icon = R.drawable.some_icon_in_my_app;
// Изменение фона под иконкой информации об операции
MyDss.getAppearance().getImages().getOperationInfoIcon().iconTint = R.color.some_color_in_my_app;

// Изменение цвета заголовков модальных окон
MyDss.getAppearance().getLabels().getModalTitle().color = R.color.some_color_in_my_app;
// Изменение начертания заголовков модальных окон
MyDss.getAppearance().getLabels().getModalTitle().fontWeight = Appearance.LabelAppearance.FontWeight.Medium;

// Изменение размера кнопки виртуальной клавиатуры
MyDss.getAppearance().getButtons().getKeyboard().getAppearance().size = R.dimen.some_dimen_in_my_app;

// Изменение фона индикатора прогресса
MyDss.getAppearance().getViews().getProgressBar().backgroundColor = R.color.some_color_in_my_app;

// Изменение цвета заголовка на панели инструментов
MyDss.getAppearance().getBaseAppearance().getMain().getTitle().color = R.color.some_color_in_my_app;

Кастомизация с помощью класса LayoutMapper

Класс LayoutMapper может быть использован для замены файлов разметки, использованных в СДК. В таблице ниже представлены важные классы для кастомизации.

Наименование Предназначение
FragmentsLayouts Кастомизация фрагментов.
DialogsLayouts Кастомизация диалоговых окон.
ListsItems Кастомизация элементов списков.
CustomViews Кастомизация прочих представлений (клавиатура для ввода пина, визуализация пинов).

По умолчанию используется разметка из макетов SDK, поэтому необязательно заменять их все. Однако, использование своей разметки экранов отключает возможность управления их внешним видом через класс Appearance.

Макеты должны содержать необходимые для функционирования SDK идентификаторы, если их не будет (или их тип не будет совпадать), тогда SDK во время выполнения будет генерировать ошибки (для фрагментов) либо не выводить их на экран (для всего остального). В случае ошибки в лог будет сделана запись, начинающаяся с Error custom mapping view.

Для упрощения замены вёрстки через класс LayoutMapper разметки рекомендуется:

  1. Скачать aar-файл SDK своей версии из репозитория.
  2. Разархивировать файл SDK mydss-*.aar любой программой-архиватором, поддерживающем формат архивации файлов zip.
  3. Перейти в папку \res\layout и скопировать оттуда файлы с префиксами dsssdk_fragment_, dsssdk_dialog_, dsssdk_item_, dsssdk_layout_, dsssdk_include_toolbar_, а так же файл dsssdk_menu_document_view_options.xml в свой проект.
  4. Переименовать префикс dsssdk_ в названии файлов на другой, например на custom_. При этом необходимо поправить ссылки на переименованные файлы с префиксом dsssdk_include_toolbar_ в строках <include layout="@layout/dsssdk_include_toolbar_".
  5. Произвести необходимые изменения в вёрстке макетов, при этом необходимо сохранить обязательные идентификаторы, их типы. Полный список обязательных полей можно посмотреть в документации на класс LayoutMapper.
  6. Перед инициализацией SDK получить объект класса LayoutMapper из MyDss.getLayoutsMapper() и установить идентификаторы файлов со своей вёрсткой.

Пример установки идентификатора кастомного диалога загрузки:

MyDss.getLayoutsMapper().getDialogs().setLoaderDialogLayoutId(R.layout.custom_dsssdk_dialog_loader);

Дополнительная информация

Альтернативное логирование

По умолчанию СДК записывает логи в LogCat. Это поведение можно изменить, передав в метод MyDss.setAlternativeLogger(AlternativeLogger) свою реализацию интерфейса AlternativeLogger.

Базовая реализация интерфейса AlternativeLogger выглядит следующим образом:

import android.util.Log;

public interface AlternativeLogger {

    default int v(String tag, String msg) {
        return Log.v(tag, msg);
    }

    default int v(String tag, String msg, Throwable tr) {
        return Log.v(tag, msg, tr);
    }

    default int d(String tag, String msg) {
        return Log.d(tag, msg);
    }

    default int d(String tag, String msg, Throwable tr) {
        return Log.d(tag, msg, tr);
    }

    default int i(String tag, String msg) {
        return Log.i(tag, msg);
    }

    default int i(String tag, String msg, Throwable tr) {
        return Log.i(tag, msg, tr);
    }

    default int w(String tag, String msg) {
        return Log.w(tag, msg);
    }

    default int w(String tag, String msg, Throwable tr) {
        return Log.w(tag, msg, tr);
    }

    default int e(String tag, String msg) {
        return Log.e(tag, msg);
    }

    default int e(String tag, String msg, Throwable tr) {
        return Log.e(tag, msg, tr);
    }

}

Таким образом, приложение может переопределить только те методы, которые нужно.

Работа с Рутокеном

При выполнении основных сценариев SDK самостоятельно выполняет необходимые взаимодействия с токенами (проверяет или запрашивает включение модуля NFC, установку ПУР (панель управления Рутокен), ожидает, когда пользователь приложит токен). Для того, чтобы получить информацию о хранилище ключей, необходимо использовать метод KeysSourceIdentifier.getKeysSourceIdentifier(DSSUser, certificateId) с идентификатором сертификата.

Создание ключевой пары

Для создания ключевой пары и подписи запроса на Рутокене необходимо вызвать DSSCertificatesManagerNonQual.signCertificateRequest(dssUser, request, selectedKeysSourceIdentifier) с параметром selectedKeysSourceIdentifier равным предопределённому значению KeysSourceIdentifier.rutokenNFC. Если было успешное взаимодействие с Рутокеном (независимо, метод DSSCertificatesManagerNonQual.signCertificateRequest отработал штатно или с ошибкой), то будет означен параметр KeysSource в обратном вызове. Если при этом токен был непроинициализирован, то SDK его проинициализирует и установит на него случайный PUK-код. Чтобы получить его в приложении из KeysSource, необходимо преобразовать KeysSource в KeysSourcePukInitializable и вызвать getPuk().

Использование существующих на Рутокене сертификатов

Для подключения к СДК существующих сертификатов на Рутокене, необходимо: 1. Вызвать DSSCertificatesManagerNonQual.getExternalCertificates(DSSExternalCertificatesCallback) и получить список сертификатов на токене 2. На выбранном из списка сертификате вызвать DSSCertificatesManager.setCertificate(DSSUser user, DSSCertificate, DSSCertificateNetworkCallback)

Инициализация SDK со списком доверенных приложений

При инициализации SDK функцией init класса MyDss выполняются проверки (с отображением соответствующих предупреждений) устройства на наличие прав суперпользователя (root), отсутствие антивируса, а также наличие приложений с правами android.permission.PACKAGE_USAGE_STATS и android.permission.SYSTEM_ALERT_WINDOW.

Если по вашему мнению SDK ошибочно определило некоторое приложение как вредоносное и показывает пользователю предупреждение, которое вы считаете избыточным, вы можете передать в функцию init список "доверенных" приложений в виде параметра HashMap<String, String[]> trustedApps, где ключом выступает идентификатор приложения, а значением - список хэшей (полученных по алгоритму SHA-256) подписей разработчика приложения.

В подавляющем большинстве случаев разработчики используют один ключ для подписания приложения перед публикацией в Play Market, поэтому чаще всего массив String[] будет содержать один элемент.

Получение подписи приложения и передача её в SDK

Рассмотрим алгоритм включения некоторого приложения в белый список приложений MyDSS SDK на примере приложения Google Photos.

Шаг 1. Установка приложения на устройство

Сначала необходимо установить из Play Market требуемое приложение на любой аппарат под управлением ОС Android, после чего подключить устройство к компьютеру по USB-кабелю в режиме отладки.

Шаг 2. Получение доступа к файлу apk приложения

Далее необходимо скопировать apk-архив из директории приложения на компьютер. Для этого необходимо использовать утилиту adb. При этом следует учесть, что к компьютеру не должно быть подключено по USB-кабелю других Android-устройств.

Утилита adb может быть установлена через Android Studio посредством инструмента SDK Manager. Подробнее на сайте разработчиков.

Команда

adb shell pm list packages

перечислит все установленные на подключённом устройстве приложения в виде пакетов. При желании можно отфильтровать полученный список удобным образом. В полученном списке будет строка package:com.google.android.apps.photos, где com.google.android.apps.photos - имя пакета (очевидно, соответствующее приложению Google Photos), которое понадобится для получения apk-файла, а также в качестве ключа в параметре trustedApps.

Выполнение команды

adb shell pm path com.google.android.apps.photos

позволяет получить пути ко всем apk-файлам приложения Google Photos. Результат выполнения команды будет примерно следующим:

package:/data/app/com.google.android.apps.photos-2/base.apk
package:/data/app/com.google.android.apps.photos-2/split_config.en.apk
package:/data/app/com.google.android.apps.photos-2/split_config.ru.apk
package:/data/app/com.google.android.apps.photos-2/split_config.uk.apk
package:/data/app/com.google.android.apps.photos-2/split_config.xxxhdpi.apk

Подпись приложения можно получить из файла base.apk, поэтому именно его необходимо скопировать на компьютер. Для этого можно выполнить команду

adb pull /data/app/com.google.android.apps.photos-2/base.apk

Она скопирует base.apk в текущую директорию на вашем ПК.

Шаг 3. Извлечение подписи разработчика

Откройте apk-файл любым архиватором и перейдите в папку META-INF. Внутри этой папки будет один файл с расширением .RSA, именно он содержит подпись разработчика. Его необходимо разархивировать. Как правило, файл называется CERT.RSA или BNDLTOOL.RSA (иногда могут встречаться другие названия).

Далее необходимо прочитать содержимое файла. Сделать это можно, например, при помощи утилиты keytool, входящей в состав JRE.

keytool -printcert -file BNDLTOOL.RSA

Результат выполнения команды будет выглядеть примерно следующим образом:

Owner: CN=Unknown, OU="Google, Inc", O="Google, Inc", L=Mountain View, ST=CA, C=US
Issuer: CN=Unknown, OU="Google, Inc", O="Google, Inc", L=Mountain View, ST=CA, C=US
Serial number: 4934987e
Valid from: Tue Dec 02 05:07:58 MSK 2008 until: Sat Apr 19 05:07:58 MSK 2036
Certificate fingerprints:
         MD5:  D0:46:FC:5D:1F:C3:CD:0E:57:C5:44:40:97:CD:54:49
         SHA1: 24:BB:24:C0:5E:47:E0:AE:FA:68:A5:8A:76:61:79:D9:B6:13:A6:00
         SHA256: 3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A
Signature algorithm name: MD5withRSA (weak)
Subject Public Key Algorithm: 1024-bit RSA key
Version: 1

Нам необходим хэш, полученный по алгоритму SHA-256. Для этого копируем значение:

3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A

Шаг 4. Добавление приложения в SDK в качестве доверенного

После того, как у на есть App ID приложения и значение хэша подписи разработчика, мы можем проинициализировать SDK следующим образом:

String appId = "com.google.android.apps.photos";
String hash = "3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A";
HashMap<String, String[]> trustedApps = new HashMap<>();
trustedApps.put(appId, new String[]{hash});
MyDss.init(
    activity.getApplicationContext(), 
    MyDss.RootCertificateType.Development, 
    MyDss.DSS_LOG_DEBUG, 
    trustedApps, // Список "доверенных" приложений, убран из аргументов начиная с версии 2.1.616
    new DSSInitCallback() {
        @Override
        public void error(DSSError error) {
            // ...
        }

        @Override
        public void success(MyDss myDssInstance) {
            // ...
        }
    });

Альтернативный способ получения подписи разработчика приложения

Подпись разработчика приложения можно также получить программно, написав и установив небольшое приложение на Android-устройство.

Код получения подписей всех установленных приложений выглядит следующим образом:

private void getSHA256SignaturesForAllApps() throws NoSuchAlgorithmException {
    PackageManager p = getApplicationContext().getPackageManager();
    final List<PackageInfo> installedApps = p.getInstalledPackages(
            PackageManager.GET_PROVIDERS |
            (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
                    ? PackageManager.GET_SIGNING_CERTIFICATES
                    : PackageManager.GET_SIGNATURES));
    for (PackageInfo i : installedApps) {
        Log.w(TAG, "Package: "+i.packageName);
        Log.i(TAG, " -- name: "+p.getApplicationLabel(i.applicationInfo).toString());
        Signature[]  appSignatures;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
            appSignatures = i.signatures;
        } else {
            appSignatures = i.signingInfo.getSigningCertificateHistory();
        }
        for (Signature s: appSignatures) {
            String sigHex = bytesToHex(s.toByteArray());
            String sigSHA256Hex = bytesToHex(MessageDigest.getInstance("SHA-256").digest(s.toByteArray()));
            Log.i(TAG, " -- signature: "+sigHex);
            Log.i(TAG, " -- signature SHA256 digest: "+sigSHA256Hex);
        }
    }
}

private String bytesToHex(byte[] bytes) {
    // Представление байтов в hex-формате
    char[] hexArray = "0123456789abcdef".toCharArray();
    char[] hexChars = new char[bytes.length * 2];
    for (int j = 0; j < bytes.length; j++) {
        int v = bytes[j] & 0xFF;
        hexChars[j * 2] = hexArray[v >>> 4];
        hexChars[j * 2 + 1] = hexArray[v & 0x0F];
    }
    return new String(hexChars);
}

Особенности работы на Android 11+

При запуске СДК на устройстве под управлением Android 11 или новее, проверка наличия потенциально вредоносных приложений осуществляться не будет. Это связано с ограничениями ОС на доступ к списку установленных приложений. Чтобы обеспечить запуск такой проверки, необходимо в манифест приложения добавить разрешение:

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />

Использование этого разрешения потребует заполнения дополнительной декларации при публикации приложения в Google Play. Подробнее об этом прочесть в соответствующем разделе документации.

Запуск Activity из СДК и задачи Android

По умолчанию, при вызове методов СДК, требующих показа пользовательского интерфейса, экземпляры Activity из СДК запускаются в составе новой задачи (используется вызов Context.startActivity() с флагом Intent.FLAG_ACTIVITY_NEW_TASK - это требование ОС). Однако, в некоторых случаях такое поведение может быть нежелательным и предпочтительнее запускать Activity СДК в составе той же самой задачи. Тогда необходимо использовать перегрузки методов СДК с первым параметром типа Activity, подставляя в качестве аргумента экземпляр Activity приложения.

К таким методам относятся:

  1. MyDss.init(...) (в качестве экземпляра Context можно передать Activity)
  2. DSSUsersManager.createDSSUser(...)
  3. DSSUsersManager.createDSSUserWithInitQR(...)
  4. DSSUsersManager.createDSSUserWithApproval(...)
  5. DSSUsersManager.acceptAccountChanges(...)
  6. DSSUsersManager.checkApprovalStatus(...)
  7. DSSUsersManager.submitPassword(...)
  8. DSSUsersManager.changePassword(...)
  9. DSSUsersManager.revoke(...)
  10. DSSUsersManager.renew(...)
  11. DSSDeviceManager.processAwaitingDevice(...)
  12. DSSOperationsManager.confirmOperation(...)
  13. DSSOperationsManager.signDocumentsOffline(...)
  14. DSSOperationsManager.signDocuments(...)
  15. DSSCertificatesManagerNonQual.signCertificateRequest(...)
  16. DSSCertificatesManagerNonQual.getExternalCertificates(...)