ЖТЯИ.00118-01 97 02-02 КриптоКлюч SDK. Руководство Разработчика
КриптоКлюч SDK для встраивания в мобильное приложение представляет собой набор программных компонентов для использования в мобильных приложениях, который позволяет производить удаленное выполнение операций подписи и управление сертификатами, а также подтверждать операции Пользователя в КриптоПро Ключ, инициированные другими способами.
Термины и определения, описание сущностей
Тип | Описание |
---|---|
SDK | CKey SDK версии 1.х для встраивания в Android-приложения, поставляемая в виде набора AAR-библиотек. |
User (Пользователь) | Объект, содержащий всю необходимую информацию для выполнения действий от имени пользователя. Содержит, в том числе, информацию для доступа к экземпляру (URL для взаимодействия, флаги, определяющие параметры взаимодействия), "вектор аутентификации" для подтверждения действий и пр. С точки зрения сервера данный объект является устройством, подключенным к учетной записи пользователя . |
Device (Устройство) | Объект, содержащий информацию об устройствах, подключенных к той же учетной записи, что и объект User. Данный объект не содержит "вектор аутентификации" и не может использоваться для подтверждения операций или выполнения других действий. |
Operation (Операция) | Операция , для которой требуется подтверждение/отклонение. Операция может содержать несколько документов или может не содержать документов вообще. Операция создается на сервере. SDK позволяет подписывать или отклонять как и все документы, входящие в операцию, целиком, так и часть документов. |
Operation.Document (Документ) | Документ, входящий в операцию, либо обрабатываемый самостоятельно. |
Certificate (Сертификат) | Сертификат, привязанный к ключу подписи в учетной записи на сервере. |
Включение CKey SDK в проект
Рекомендуемый способ подключения SDK к проекту Android-приложения - это добавление maven-зависимости.
Добавление maven-репозиториев
В корневой файл build.gradle
проекта необходимо добавить четыре maven-репозитория:
№ | Адрес | Пояснение |
---|---|---|
1 | https://repo.paycontrol.org/mydss/android/maven |
SDK |
2 | https://repo.paycontrol.org/android/maven |
Для подключения необходимых для работы в токенами Rutoken |
3 | mavenCentral() |
Для добавления библиотеки-сканера QR-кода (обычно уже прописан) |
4 | https://www.jitpack.io |
Для добавления вспомогательных библиотек, используемых SDK |
Примерный корневой 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-зависимости
Комплект поставки SDK включает в себя:
- Сам CKey SDK, артефакт ckey
- Библиотека функций-расширений kotlin, артефакт ckey-ktx
- Инструмент для миграции с SDK myDss 2.1.x, артефакт ckey-migration
SDK ckey
Необходимо добавить maven-зависимость в файлы build.gradle
модулей приложения, использующие функции SDK. Для базовых версии:
implementation 'ru.safetech.sdk:ckey:1.0.<Код последней версии API>.<Код последней версии>'
Для поддержки токенов Rutoken необходимо использовать версию SDK с суффиксом -nfc:
implementation 'ru.safetech.sdk:ckey:1.0.<Код последней версии API>.<Код последней версии>-nfc'
Добавление этой зависимости позволяет получить полный функционал SDK Моего ключа
Библиотека функций-расширений ckey-ktx
Для конформного программирования на kotlin-e в репозитории доступен артефакт ckey-ktx. Это набор функций-расширений. Например, такой код:
import ru.safetech.ckey.sdk.v1.DevicesManager
import ru.safetech.ckey.sdk.v1.utils.DevicesNetworkCallback
***
DevicesManager.listDevices(
dssUser!!,
object : DevicesNetworkCallback {
override fun error(error: NetworkError) {
devicesState.value =
Event(Error(DssSdkFailure(error.message, error.shouldShowAlert())))
}
override fun success(dssDevices: Array<Device>) {
devicesState.value = Event(State.Loaded)
items.value = dssDevices.toList()
}
})
Можно отрефакторить до такого:
import ru.safetech.ckey.sdk.v1.ktx.devicesManager
***
devicesManager.listDevices(
dssUser!!,
onError = { error ->
devicesState.value =
Event(Error(DssSdkFailure(error.message, error.shouldShowAlert())))
}) { dssDevices ->
devicesState.value = Event(State.Loaded)
items.value = dssDevices.toList()
}
Для их задействования необходимо добавить maven-зависимость в файлы build.gradle
модулей приложения, использующие функции SDK. Для базовых версии:
implementation 'ru.safetech.sdk:ckey-ktx:1.0.<Код последней версии API>.<Код последней версии>'
Для версии с поддержкой токенов Rutoken необходимо использовать версию с суффиксом -nfc:
implementation 'ru.safetech.sdk:ckey-ktx:1.0.<Код последней версии API>.<Код последней версии>-nfc'
Инструмент для миграции с SDK myDss 2.1.x
Для перевода DSSUser'ов, созданных с помощью SDK myDss 2.1.x на SDK Моего Ключа в репозитории доступен артефакт ckey-migration.
Миграция осуществляется вызовом метода public void migrate(@NonNull Context context)
после проверки на необходимость миграции через вызов isMigrationRequired
:
val myMigration = CKeyMigration.getInstance()
if (myMigration.isMigrationRequired(context)) {
myMigration.migrate(context)
}
// Далее идёт инициализация SDK Моего Ключа
...
Настройка правил минификации и обфускации
Если в проекте используются инструменты минификации и обфускации proguard
или D8
(R8
), то для корректной работы библиотеки в файл правил proguard
(например, proguard-rules.pro
) необходимо добавить следующие ограничения:
-keep public class ru.safetech.ckey.** { *; }
-dontwarn ru.safetech.ckey.**
-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.** {*;}
Требуемые разрешения
SDK объявляет в манифесте следующие разрешения, необходимые для корректной работы:
<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
необходимы для взаимодействия с сервером и проверки доступности сети Интернет на устройстве, разрешение CAMERA
используется при сканировании QR-кодов (предварительно запрашивается у пользователя внутри SDK при необходимости), разрешение USE_BIOMETRIC
применяется при защите ключевой информации при помощи отпечатка пальца. Разрешение 'NFC' необходимо только для работы с токенами Rutoken.
Настройки безопасности
При создании TLS-соединения с сервером осуществляется проверка сертификата сервера по спискам отзывов сертификатов (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. Более подробные сведения можно найти в Документации разработчика.
Прочие настройки
Минимальная поддерживаемая SDK версия ОС - это Android 7. Поэтому модули, использующие SDK, должны включать в build.gradle
строчку:
minSdk = 24
Необходимо добавить в build.gradle
на уровне модулей приложения, использующие функции SDK, в раздел packaging
для jniLibs
строку:
...
useLegacyPackaging = true
...
Обновление с myDSS SDK 2.1.x
SDK Моего ключа поддерживает обратную совместимость с myDss SDK версии 2.1.x, однако при переходе на новую версию в исходный код приложения потребуется внести незначительные изменения, по большей части механические.
Изменения, затрагивающие процесс компиляции
При простой замене maven-зависимости ru.cryptopro.sdk:mydss:2.1.x
в файле build.gradle
модуля на ru.safetech.sdk:ckey:1.0.y.z
, модуль не скомпилируется. Для устранения ошибок компиляции нужно предпринять следующие шаги:
Заменить
ru.cryptopro.mydss.sdk.v2
наru.safetech.ckey.sdk.v1
.Заменить
.DSS_
на.
Заменить
dssCertificateId
наcertificateId
,dssUserId
наuserId
иdssCertificateRequestId
наcertificateRequestId
.Удалить префикс
DSS
из всех мест, где используются сущности SDK (названий классов, объектов, методов и т.д).Заменить
MyDss
наCKey
.В методе инициализации SDK Моего ключа
CKey.init
в реализации интерфейса обратного вызоваInitCallback
заменить методonAppearanceReady
на метод без аргументовonCustomizationReady
.Метод
CertificatesManagerNonQual.isCertificateAccessibleOnThisDevice
вместоBoolean
теперь возвращает объектru.safetech.ckey.sdk.v1.utils.Error
с типом ошибкиERROR_OK
, если сертификат может быть использован на этом устройстве, либоError.getType == Error.ERROR_CERTIFICATE_NOT_ACCESSIBLE_ON_THIS_DEVICE
, если сертификат не может быть использован на этом устройстве, либо другую ошибку.Метод
CertificatesManagerNonQual.isCertificateInstalled
вместоBoolean
теперь возвращает либоError.getType == Error.ERROR_OK
, если сертификат был установлен, либоError.getType == Error.ERROR_CERTIFICATE_NOT_INSTALLED
, если сертификат не установлен, либо другую ошибку ErrorПо желанию, можно перейти на библиотеку функций-расширений ckey-ktx и избавится от реализаций интерфейсов обратного вызова.
Если требуется сохранить DSSUser-ов, которые были созданы в SDK MyDss 2.1.x и использовать их в SDK Моего Ключа, тогда задействовать класс
CKeyMigration
.
Устаревшие методы
Устарел | Аналог на который стоит перейти |
---|---|
UsersManagerNonQual.changePassword(@NonNull User user, @NonNull String newPassword, UserCallback callback) |
UsersManagerNonQual.changePassword(@NonNull User user, @NonNull String oldPassword, @NonNull String newPassword, UserCallback callback) |
UsersManager.listStorage() |
UsersManager.listStorage(UsersCallback callback) |
KeysManagerNonQual.listKeys() |
KeysManagerNonQual.listKeys(@NonNull KeyInfosCallback callback) |
KeysManagerNonQual.getKeysSourceIdentifier(User user, String certificateId) |
KeysManagerNonQual.getKeysSourceIdentifier(@NonNull User user, @Nullable String certificateId, @NonNull KeysSourceIdentifierCallback callback) |
KeysManagerNonQual.getKeysForUser(@NonNull User user) |
KeysManagerNonQual.getKeysForUser(@NonNull User user, @NonNull KeyInfosCallback callback) |
Прочие изменения
Добавлены новые ошибки:
ERROR_NOT_INITIALIZATED
- когда происходит вызов методов SDK, а инициализации SDK не было.ERROR_NOT_SUPPORTED
- когда идёт вызов метода, который не поддерживается.ERROR_CERTIFICATE_NOT_INSTALLED
- сертификат не был установлен.ERROR_CERTIFICATE_NOT_ACCESSIBLE_ON_THIS_DEVICE
- сертификат нельзя использовать для подтверждения операций на этом устройстве.
Добавлена сущность KeysSourceIdentifier.distributeKey
- распределенное хранилище ключей (DSK) для криптоключа.
Структура SDK
SDK может работать в режиме УКЭП
и УНЭП
. В режиме УКЭП
SDK предоставляет графический интерфейс для регистрации устройства, смены способа защиты ключей, подтверждения операций и подписания документов. В режиме УНЭП
приложение может вызывать методы SDK без задействования графического интерфейса, а использовать свой интерфейс для всех процессов.
Для использования SDK в режиме УКЭП
приложение может обращаться к методам следующих классов:
Класс | Предназначение |
---|---|
UsersManager | Создание учётных записей, привязка устройства к существующим учётным записям, управление учётными записями на устройстве (проверка статуса, смена пароля, обновление, удаление с устройства и т.д.) |
OperationsManager | Получение данных операций, подтверждение операций, подписание документов |
CertificatesManager | Управление сертификатами пользователя (создание запросов на сертификат, получение сведений о сертификатах и запросах, приостановка и удаление сертификата и .д.) |
DevicesManager | Управление устройствами, привязанными к учётной записи (получение списка устройств, подтверждение запросов на добавление нового устройства, удаление устройств и т.д.) |
PolicyManager | Получение информации о сервере |
CKey | Инициализация и деинициализация SDK, регулировка параметров работы (метод initNonQual предназначен для инициализации SDK в режиме УНЭП ) |
Appearance | Управление внешним видом SDK |
LayoutMapper | Управление внешним видом SDK |
Кроме того, SDK предоставляет классы-модели (User
, Device
, Certificate
, Operation
, Operation.Document
, Params
, SignServerParams
, PushNotificationData
) для работы с данными пользователя, устройства, сертификата, операции, документа, параметров сервера, параметров сервера подписи и настроек пуш-уведомлений соответственно.
При использовании SDK в режиме УНЭП
приложение дополнительно может использовать классы с суффиксом NonQual
:
Класс | Предназначение |
---|---|
UsersManagerNonQual | Управление учётными записями без использования графического интерфейса |
OperationsManagerNonQual | Работа с операциями и документами без использования графического интерфейса |
CertificatesManagerNonQual | Работа с сертификатами, хранимыми на устройстве (некоторые методы вовлекают использование графического интерфейса) |
DevicesManagerNonQual | Работа с привязанными устройствами без использования графического интерфейса |
KeysManagerNonQual | Управление ключами, хранимыми на устройстве (некоторые методы вовлекают использование графического интерфейса) |
Инициализация SDK
Инициализация SDK задаёт параметры её дальнейшего использования: уровень логирования, набор корневых сертификатов, до которых будет строиться цепочка проверки сертификатов серверов, с которыми работает приложение, а также режим работы: для разработки (development
) или для конечного пользователя (production
). Режим development
позволяет отключить проверку отзыва сертификата сервера, что может быть полезным на этапе разработки, если адреса CRL не настроены должным образом.
Инициализация SDK должна выполняться один раз в главном потоке приложения. При необходимости реинициализации SDK должна проводиться предварительная деинициализация.
Инициализация в режиме УКЭП
Инициализация SDK для работы в режиме УКЭП должна выполняться методом CKey.init(...)
как показано в примере ниже.
public class MainActivity extends AppCompatActivity {
static CKey CKey = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CKey.init(getApplicationContext(),
CKey.RootCertificateType.Development, // Тип корневого сертификата
CKey.LOG_DEBUG, // Уровень логирования SDK в LogCat
new InitCallback()
{
@Override
public void error(@NonNull Error Error) {
// Инициализация прошла неудачно. Библиотекой пользоваться нельзя
Log.e("ERROR", Error.getMessage());
}
@Override
public void success(@NonNull CKey myInstance) {
// Библиотека инициализирована успешно
// Необходимо сохранить созданный экземпляр myInstance для дальнейшего использования
CKey = myInstance;
}
@Override
public void onCustomizationReady() {
// Инициализация ещё не завершена, но уже доступно управление внешним видом SDK
Appearance appearance = CKey.getAppearance()
// Далее, кастомизация через appearance
LayoutsMapper layoutMapper = CKey.getLayoutsMapper()
// Далее, кастомизация через layoutMapper
}
});
}
}
Первый параметр метода init
- контекст приложения.
Следующий параметр - перечисление CKey.RootCertificateType
- определяет одновременно набор корневых сертификатов, с которым будет работать приложение, и режим работы SDK - для разработки (без проверки CRL) и для конечного использования (с проверкой CRL). В примере выше использован режим "Для разработки".
Третий параметр - уровень логирования - определяет, какую информацию будет записывать SDK в LogCat. Константа CKey.NO_LOGGING
предотвращает запись в логи каких-либо сообщений SDK (но не внутренних библиотек SDK). Значение CKey.LOG_INFO
включает логирование сообщений об ошибках, предупреждений и информационных сообщений, в то время как значение CKey.LOG_DEBUG
позволяет также включить в логи содержимое тел сетевых запросов и промежуточные состояния различных объектов.
В качестве последнего параметра в метод init
нужно передавать реализацию интерфейса InitCallback
для получения результатов инициализации
Инициализация в режиме УНЭП
При работе с неквалифицированной электронной подписью инициализацию SDK можно проводить вызовом метода CKey.initNonQual(...)
. Этот метод принимает только четыре параметра: список доверенных приложений передавать не нужно, так как в режиме УНЭП SDK не выполняет проверку наличия потенциально опасных приложений. Кроме того, в режиме УНЭП не проверяется наличие прав суперпользователя (root) на устройстве и не выполняется проверка на наличие доверенного антивируса.
Размещение набора корневых сертификатов
Второй параметр методов CKey.init(...)
и CKey.initNonQual(...)
определяет не только режим работы SDK, но и ожидаемое расположение списка корневых сертификатов. При использовании значения CKey.RootCertificateType.Development
корневые сертификаты должны быть размещены в папке "сырых" ресурсов в файле с именем development_root_cert и любым расширением (например, res/raw/development_root_cert.crt) - SDK будет обращаться к ресурсу списка сертификатов как R.raw.development_root_cert
. При использовании режима для конечного пользователя - CKey.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-----
Регистрация устройства
Отправным шагом при работе с SDK является регистрация устройства (привязка устройства к существующей учётной записи или регистрация первого анонимного устройства с последующим созданием новой учётной записи).
SDK предоставляет три сценария регистрации:
- Регистрация нового устройства онлайн.
- Регистрация нового устройства с использованием QR-кода, содержащего предварительные сведения регистрации.
- Регистрация второго или последующего устройства с подтверждением присоединения этого устройства на одном из ранее зарегистрированных устройств.
Результатом каждого из способов регистрации является создание нового объекта User
, содержащего уникальный набор ключей аутентификации (векторы аутентификации), используемых данным устройством, при выполнении действий от имени пользователя , к которому было привязано устройство.
Регистрация в режиме УКЭП
Ниже приведены примеры регистрации в режиме УКЭП для каждого из трёх сценариев. Регистрация инициируется вызовом одного из методов createUser*
класса UsersManager
.
Онлайн-регистрация (сценарий с уникальным идентификатором)
Онлайн-регистрация устройства выполняется в три логических шага, два из которых инициируются приложением на мобильном устройстве.
Шаг 1. Регистрация анонимного устройства
Первый шаг - регистрация анонимного устройства методом UsersManager
:
UsersManager.createUser(
@Nullable String externalId, // Идентификатор сущности вызывающего приложения,
// к которому будет "привязан" объект
@Nullable String alias, // Удобочитаемый идентификатор устройства
@NonNull String name, // Уникальное имя профиля в рамках приложения
@NonNull String serviceUrl, // URL для взаимодействия с my
@NonNull String deviceName, // Читаемое название устройства
@Nullable PushNotificationData pushData, // Данные для получения PUSH-уведомлений
boolean requirePassword, // Требуется ли установка пароля
@Nullable UserCallback callback // Callback для обработки результатов
)
Как правило, параметры externalId
и alias
не указываются (передаётся значение null
) и назначаются сервером . Однако, в зависимости от настроек сервера, эти параметры могут быть заданы из приложения.
Параметр name
задаёт уникальное имя будущего объекта User
в рамках приложения и не передаётся на сервер. SDK использует это имя в качестве уникального признака при выполнении некоторых операций. Предполагается, что имя задаётся вручную и применяется пользователем приложения для различения ключей. Однако, SDK не накладывает на это ограничений, и приложение может генерировать этот параметр иным образом и использовать в иных целях.
Параметр serviceUrl
должен быть известен приложению и определяет адрес сервиса mDAG, на который SDK будет отправлять запросы.
Значение параметра 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
) при условии, что параметры сервера и флаги ключа это допускают. В этом случае, ключи аутентификации будут защищены неким паролем по умолчанию.
Последний параметр - реализация интерфейса UserCallback
для обработки результатов онлайн-регистрации:
new UserCallback() {
@Override
public void success(@NonNull User user) {
// Пользователь создан успешно, данные сохранены в объекте user
}
@Override
public void error(@NonNull Error error) {
// Возникла внутренняя ошибка SDK - регистрация не пройдена
}
@Override
public void error(NetworkError error) {
// Возникла ошибка сетевого характера - регистрация не пройдена
}
}
Созданный объект User
уже сохранён в долгосрочную память приложения и может быть восстановлен из памяти при последующих запусках приложения.
Шаг 2. Подтверждение регистрации оператором
После успешного выполнения метода createUser(...)
созданный объект User
, а также объект Device
, соответствующий данному устройству, находятся в статусе Installed
(значение перечисления Device.DeviceStatus
). Это означает, что ключи аутентификации, полученные от сервера, были сохранены на устройстве. Далее, оператор переводит устройство в статус NotVerified
путём привязки устройства к учётной записи по значению alias
.
Шаг 3. Подтверждение привязки устройства к учётной записи
Заключительный шаг онлайн-регистрации - подтверждение привязки устройства к учётной записи на стороне приложения. Данная операция может быть выполнена при достижении статуса NotVerified
, поэтому предварительным действием будет получение текущего статуса устройства методом updateStatus(...)
:
UsersManager.updateStatus(user, new UserCallback() {
@Override
public void success(@NonNull User user) {
// Статус успешно обновился, теперь можно проверить текущий статус
if (user.getStatus() == Device.DeviceStatus.NotVerified) {
// Статус сменился, можно подтверждать привязку устройства
}
}
@Override
public void error(@NonNull Error error) {
// Ошибка при запросе статуса
}
@Override
public void error(@NonNull NetworkError error) {
// Ошибка при запросе статуса
}
});
Как только статус объекта User
станет Device.DeviceStatus.NotVerified
, можно запустить процесс подтверждения присоединения устройства к учётной записи методом acceptAccountChanges(...)
:
UsersManager.acceptAccountChanges(user, new UserCallback() {
@Override
public void success(@NonNull User user) {
// Подтверждение успешно.
// Статус пользователя - Device.DeviceStatus.Active
}
@Override
public void error(@NonNull Error error) {
// Ошибка подтверждения
}
@Override
public void error(@NonNull NetworkError error) {
// Ошибка подтверждения
}
});
При вызове этого метода будет показан экран с данными учётной записи (профиля пользователя), к которой привязывается устройство. При успешном выполнении метода объект User
переходит в статус Device.DeviceStatus.Active
- это означает, что устройство подтверждено и может использоваться для работы с сертификатами, операциями, документами и другими устройствами.
Регистрация с помощью QR-кода
Регистрация с помощью QR-кода, содержащего предварительные данные для регистрации, выполняется в два шага.
Шаг 1. Сканирование QR-кода и запуск регистрации
Для сканирования QR-кода и начала регистрации необходимо вызвать метод
UsersManager.createUserWithInitQR(
@Nullable String externalId, // Идентификатор сущности вызывающего приложения,
// к которому будет "привязан" объект
@Nullable String alias, // Удобочитаемый идентификатор устройства
@NonNull String name, // Уникальное имя профиля в рамках приложения
@NonNull String deviceName, // Читаемое название устройства
@Nullable PushNotificationData pushData, // Данные для получения PUSH-уведомлений
boolean requirePassword, // Требуется ли установка пароля
@Nullable UserCallback callback // Callback для обработки результатов
)
В отличие от метода createUser
параметр serviceUrl
не передаётся - адрес сервера извлекается из QR-кода. При успешном выполнении метода результат аналогичен - в коллбэк возвращается созданный объект User
, сохранённый в долгосрочную память.
Шаг 2. Подтверждение привязки устройства к учётной записи
После успешного выполнения метода createUserWithInitQR
объект User
будет сразу иметь статус NotVerified
(но нужно выполнить синхронизацию с сервером методом updateStatus(...)
), поэтому далее можно переходить к вызову acceptAccountChanges(...)
. Таким образом, шаг 2 полностью аналогичен шагу 3 сценария онлайн-регистрации.
Присоединение нового устройства
Данный способ применяется, когда для учётной записи уже есть привязанное подтверждённое устройство (объект User
в статусе Active
) и требуется привязать к этой же учётной записи новое устройство. Чтобы выполнить эту операцию, потребуется передать идентификатор данного пользователя на новое устройство.
Получить идентификатор можно у экземпляра пользователя:
// Получить идентификатор подтверждённого пользователя, к которому привязываем новое устройство
String uid = user.getUserId();
Передать идентификатор с одного устройства на другое можно любым удобным способом. Один из вариантов - сформировать на подтверждённом устройстве QR-код и отсканировать его на добавляемом устройстве.
Процесс добавления нового устройства выполняется по следующему алгоритму:
- На добавляемом устройстве вызывается метод
createUserWithApproval(...)
классаUsersManager
, который создаст нового пользователя со статусомDevice.DeviceStatus.ApproveRequired
. - На добавляемом устройстве отображается QR-код, содержащий необходимую информацию о данном устройстве. Этот экран показывается при выполнении метода
createUserWithApproval(...)
. Если его закрыть до завершения процесса присоединения нового устройства, то повторное открытие экрана нужно инициировать методомUsersManager.checkApprovalStatus(...)
. - На основном устройстве вызывается метод
processAwaitingDevice(...)
классаDevicesManager
для выполнения процедуры подтверждения или отклонения добавляемого устройства. При вызове этого метода пользователю будет предложено отсканировать QR-код с экрана добавляемого устройства. - После сканирования QR-кода и просмотра информации о добавляемом устройстве пользователь выполняет подтверждение или отклонение добавляемого устройства.
- На добавляемом устройстве на экране c QR-кодом отображается кнопка проверки статуса добавляемого устройства.
Пользователь может покинуть экран, дождавшись подтверждения с основного устройства, или сделать это сразу.
В зависимости от этого, в приложение будет возвращён объект
User
со статусомDevice.DeviceStatus.ApproveRequired
илиDevice.DeviceStatus.Active
. Кроме того, на добавляем устройстве будет производиться автоматическая проверка статуса, если в методcreateUserWithApproval(...)
(илиUsersManager.checkApprovalStatus(...)
при повторном открытии экрана) был передан ненулевой параметрcheckingInterval
.
Таким образом, сначала на новом устройстве вызываем метод:
UsersManager.createUserWithApproval(
@Nullable String externalId, // Идентификатор сущности вызывающего приложения,
// к которому будет "привязан" объект
@Nullable String alias, // Удобочитаемый идентификатор устройства
@NonNull String name, // Уникальное имя профиля в рамках приложения
@NonNull String serviceUrl, // URL для взаимодействия с my
@NonNull String deviceName, // Читаемое название устройства
@Nullable PushNotificationData pushData, // Данные для получения PUSH-уведомлений
@NonNull String uid, // Идентификатор пользователя, к которому привязываем устройство
checkingInterval, // Интервал проверки статуса в секундах
boolean requirePassword, // Требуется ли установка пароля
@Nullable UserCallback callback // Callback для обработки результатов
)
При вызове метода создаётся новый объект User
со статусом Device.DeviceStatus.ApproveRequired
. Теперь требуется получить подтверждение с основного устройства. На основном устройстве вызываем метод processAwaitingDevice(...)
класса DevicesManager
:
DevicesManager.processAwaitingDevice(currentUser, new DeviceApprovalCallback() {
@Override
public void success(@NonNull DevicesManager.Action action) {
// Устройство успешно подтверждено или отклонено
}
@Override
public void error(@NonNull Error error) {
// При обработке запроса возникла ошибка
}
@Override
public void error(@NonNull NetworkError error) {
// При обработке запроса возникла ошибка
}
});
При вызове метода processAwaitingDevice(...)
будет открыт экран для сканирования QR-кода с добавляемого устройства, а после сканирования будет открыт экран с информацией о добавляемом устройстве (полученной из QR-кода) и кнопки для добавления устройства и отказа в добавлении. В зависимости от того, какую кнопку нажмёт пользователь, в метод success(...)
коллбэка будет возвращено либо значение DevicesManager.Action.Approve
либо DevicesManager.Action.Reject
.
Регистрация в режиме УНЭП
Режим УНЭП допускает использование методов регистрации из класса UsersManagerNonQual
. Эти методы не открывают пользовательский интерфейс SDK.
Онлайн-регистрация
Онлайн-регистрация нового устройства осуществляется в четыре логических шага.
Шаг 1. Регистрация анонимного устройства
Для запуска регистрации анонимного устройства необходимо вызвать метод createUser(...)
класса UsersManagerNonQual
:
UsersManagerNonQual.createUser(
@Nullable String externalId,
@Nullable String alias,
@NonNull String serviceUrl,
@NonNull String deviceName,
@Nullable PushNotificationData pushData,
@Nullable UserCallback callback
)
Все параметры аналогичны параметрам метода createUser(...)
класса UsersManager
, но параметры name
и requirePassword
не используются - они потребуются на следующем шаге.
После успешного выполнения метода возвращается объект User
со статусом Device.DeviceStatus.Created
- это означает, ключи аутентификации ещё не были сохранены в долгосрочную память устройства - теперь их нужно сохранить.
Шаг 2. Сохранение в долгосрочную память
Теперь необходимо сохранить объект user
в долгосрочную память. Для сохранения необходимо задать уникальное имя объекта и пароль, при помощи которого будут зашифрованы ключи. Сохранение осуществляется вызовом метода store(...)
:
UsersManagerNonQual.store(
@NonNull User user,
@NonNull String name,
@NonNull String password,
@Nullable UserCallback callback
)
После успешного выполнения метода объект User
имеет статус Device.DeviceStatus.Installed
.
Передаваемый пароль должен соответствовать парольной политике (требованиям, предъявляемым к сложности пароля), возвращаемой методом
User.getPasswordPolicy()
.
Шаг 3. Подтверждение регистрации оператором
Аналогично онлайн-регистрации в режиме УКЭП, на этом этапе на стороне сервера статус устройства должен быть переведён из Installed
в NotVerified
.
Шаг 4. Подтверждение привязки устройства к учётной записи
После того, как вызов метода UsersManager.updateStatus(...)
обновил статус объекта на NotVerified
, необходимо выполнить подтверждение привязки устройства вызовом метода UsersManagerNonQual.acceptAccountChanges(...)
:
UsersManagerNonQual.acceptAccountChanges(
@NonNull User user,
@Nullable QRCodeVerification qrCodeVerification,
@Nullable UserCallback callback
)
Параметр qrCodeVerification
должен принимать значение null
, когда процедура подтверждения привязки устройства к профилю не требует сканирования QR-кода, т.е. выполняется условие:
user.qRVerificationRequired() == false
для заданного объекта User
.
В противном случае необходимо отсканировать QR-код, содержащий данные для подтверждения привязки устройства к учётной записи и передать в метод acceptAccountChanges
валидный объект типа QRCodeVerification
. Чтобы получить экземпляр такого объекта, нужно вызвать метод CKey.analyzeQr(String)
:
QRCode qrCode = CKey.analyzeQR(qrContent);
Далее, необходимо проверить, что QR-код был отсканирован корректно, и он действительно имеет заданный тип, т.е. должны выполняться условия:
qrCode != null && qrCode.isCorrect() && qrCode instanceof QRCodeVerification
Успешное выполнение метода acceptAccountChanges
завершает процесс онлайн-регистрации, статус объекта user
меняется на Active
.
Регистрация с помощью QR-кода
Шаг 1. Сканирование QR-кода
При регистрации с помощью QR-кода приложение сначала сканирует QR-кода типа QRCodeKinit
:
QRCode qrCode = CKey.analyzeQR(qrContent);
if (qrCode != null && qrCode.isCorrect() && qrCode instanceof QRCodeKinit) {
// Сканирован корректный QR-код - можно начинать регистрацию
}
Шаг 2. Регистрация устройства
Запуск процесса регистрации выполняется методом createUserWithInitQR(...)
:
UsersManagerNonQual.createUserWithInitQR(
@Nullable String externalId,
@Nullable String alias,
@NonNull QRCodeKinit qrCodeKinit,
@NonNull String serviceUrl,
@NonNull String deviceName,
@Nullable PushNotificationData pushData,
@Nullable UserCallback callback
)
Все параметры аналогичны параметрам метода UsersManagerNonQual.createUser(...)
, но ещё добавляется параметр с данными отсканированного ранее QR-кода.
Шаг 3. Сохранение в долгосрочную память
Как и для случая онлайн-регистрации, сохранить созданный объект User
необходимо методом UsersManagerNonQual.store(...)
.
Шаг 4. Подтверждение привязки устройства к учётной записи
После выполнения процедуры сохранения объекта User
статус устройства меняется с Created
на Installed
. После синхронизации статуса методом UsersManager.updateStatus(...)
и получения статуса NotVerified
нужно подтвердить привязку устройства к учётной записи вызовом UsersManagerNonQual.acceptAccountChanges(...)
, как это делается в сценарии онлайн-регистрации.
Присоединение нового устройства
Для режима УНЭП присоединение нового устройства требует большего числа вызовов, чем аналогичный сценарий для УКЭП.
Шаг 1. Запуск регистрации на добавляемом устройстве
Сначала на добавляемом устройстве вызывается метод createUserWithApproval
класса UsersManagerNonQual
:
UsersManagerNonQual.createUserWithApproval(
@Nullable String externalId,
@Nullable String alias,
@NonNull String serviceUrl,
@NonNull String deviceName,
@Nullable PushNotificationData pushData,
@NonNull String uid,
@Nullable UserCallback callback
)
Как и для режима УКЭП, параметр uid
должен быть предварительно передан на присоединяемое устройство. При успешном выполнении в метод коллбэка UserCallback.success(User)
будет возвращён объект User
в статусе Created
- его нужно сохранить в память.
Шаг 2. Сохранение в долгосрочную память
Как и при других способах регистрации, сохранение происходит методом UsersManagerNonQual.store(...)
.
Шаг 3. Поиск устройства, ожидающего подтверждения присоединения
Этот шаг выполняется на устройстве, с которого планируется выполнить подтверждение регистрации нового устройства. Для начала, необходимо найти неподтверждённое устройство в списке устройств:
DevicesManager.listDevices(user, new DevicesNetworkCallback() {
@Override
public void success(@NonNull Device[] devices) {
for (Device device: devices) {
// Ищем неподтверждённое устройство
if (device.getStatus() == Device.DeviceStatus.ApproveRequired) {
// Неподтверждённое устройство найдено
}
}
}
@Override
public void error(@NonNull NetworkError error) {
// Ошибка при запросе списка устройств
}
});
Шаг 4. Подтверждение присоединения устройства
После того как устройство, ожидающее подтверждения, было найдено, его необходимо подтвердить методом DevicesManagerNonQual.approve(...)
:
DevicesManagerNonQual.approve(user, device, new NetworkCallback() {
@Override
public void success() {
// Присоединение устройства успешно подтверждено
}
@Override
public void error(@NonNull NetworkError error) {
// При подтверждении возникла ошибка
}
});
Для отклонения запроса на регистрацию необходимо вызывать метод DevicesManagerNonQual.reject(...)
с аналогичной сигнатурой.
Шаг 5. Проверка статуса на присоединяемом устройстве
Чтобы проверить, было ли устройство подтверждено, необходимо воспользоваться методом UsersManagerNonQual.checkApprovalStatus(...)
:
UsersManagerNonQual.checkApprovalStatus(user, new UserCallback() {
@Override
public void success(@NonNull User approvedUser) {
// Присоединение данного устройства подтверждено на другом устройстве
}
@Override
public void error(@NonNull Error error) {
// Ошибка проверки статуса
}
@Override
public void error(@NonNull NetworkError error) {
if (error.getType() == NetworkError._ERROR_AWAITING_USER_CONFIRMATION) {
// Запрос на присоединение ещё не обработан
} else if (error.getType() == NetworkError._ERROR_ACTION_REJECTED_BY_USER) {
// Запрос на добавление этого устройства был отклонён на другом устройстве
} else {
// Ошибка проверки статуса
}
}
});
При успешном выполнении метода, объект approvedUser
(является ссылкой на тот же объект, что и переданный параметр user
) будет иметь статус Active
, как и при завершении регистрации другими способами.
Работа с пользователями и устройствами
Работа с пользователями (помимо описанных выше процедур регистрации) включает:
- Извлечение пользователей из долгосрочной памяти
- Предъявление пароля
- Смену пароля
- Смену имени пользователя
- Синхронизацию статуса пользователя
- Обновление профиля пользователя
- Получение истории действий пользователя
- Удаление пользователя с устройства
Напомним, что под пользователем подразумевается объект User
, включающий вектор аутентификации, установленный на данном устройстве, информацию о статусе устройства и другую сопутствующая информацию, описывающую параметры данного устройства и учётной записи, к которой устройство привязано.
Работа с привязанными к учётной записи устройствами включает:
- Запрос информации об устройствах
- Отзыв "вектора аутентификации" - делает невозможным дальнейшее взаимодействие с сервером с отзываемого устройства
- Подтверждение и отклонение устройства, ожидающего присоединения (как было описано в примерах выше)
Извлечение пользователей из памяти
Для того, чтобы получить список сохранённых на устройстве пользователей, необходимо вызвать метод UsersManager.listStorage()
:
UsersManager.listStorage(new UsersCallback() {
@Override
public void success(@NonNull List<User> users) {
for(User user: users) {
// Выполняем необходимые действия с каждым пользователем
}
}
Предъявление пароля
Некоторые методы SDK требуют выполнения условия user.isReadyToSign()
, т.е. требуют, чтобы перед их вызовом был предъявлен пароль. В документации по API для всех таких методов есть соответствующая пометка.
При работе в режиме УКЭП предъявление пароля осуществляется вызовом метода UsersManager.submitPassword(...)
. Допустим, приложение хочет вызвать метод удаления сертификата пользователя (требует ввода пароля). Тогда процедура вызова метода будет выглядеть следующим образом:
if (user.isReadyToSign()) {
// Предъявлять пароль не нужно - можно вызывать метод
CertificatesManager.deleteCertificate(...)
} else {
// Требуется ввод пароля
UsersManager.submitPassword(user, new UserCallback() {
@Override
public void success(@NonNull User user) {
// Предъявлен верный пароль - можно вызывать метод
CertificatesManager.deleteCertificate(...)
}
@Override
public void error(@NonNull Error error) {
// Ошибка при предъявлении пароля
}
@Override
public void error(@NonNull NetworkError error) {
// Сетевая ошибка - для случая ввода пароля она не возникает
// и этот метод не вызывается
}
});
}
Следует учитывать, что метод error(Error)
коллбэка будет вызван только при возникновении каких-либо критичных ошибок (либо при закрытии экрана SDK). При вводе неправильного пароля будет отображено соответствующее сообщение на экране SDK, коллбэк вызываться не будет.
Следует также иметь в виду, что если регистрация в режиме УКЭП проходила без ручного задания пароля (было передано значение false
в качестве параметра requirePassword
), и объект User
был сохранён с использованием пароля по умолчанию, то метод user.isReadyToSign()
всегда будет возвращать значение true
.
В режиме УНЭП предъявление пароля осуществляется без открытия пользовательского интерфейса путём передачи значения пароля напрямую в метод submitPassword(...)
класса UsersManagerNonQual
:
if (user.isReadyToSign()) {
// Предъявлять пароль не нужно - можно вызывать метод
CertificatesManager.deleteCertificate(...)
} else {
// Требуется ввод пароля
UsersManagerNonQual.submitPassword(user, password, new UserCallback() {
@Override
public void success(@NonNull User user) {
CertificatesManager.deleteCertificate(...)
}
@Override
public void error(@NonNull Error error) {
// При предъявлении пароль возникла ошибка (в т.ч., пароль мог оказаться неверным)
}
@Override
public void error(@NonNull NetworkError error) {
// При предъявлении пароль возникла ошибка (в т.ч., пароль мог оказаться неверным)
}
});
}
В независимости от того используется ли метод ввода пароля из класса UsersManager
или UsersManagerNonQual
, после однократного ввода пароля нет необходимости вводить его заново до тех пор, пока приложение находится на переднем плане. При сворачивании приложения более чем на 15 секунд, однако, потребуется снова вводить пароль.
Смена пароля
Смена пароля для режима УКЭП осуществляется вызовом метода changePassword
класса UsersManager
:
UsersManager.changePassword(user, true, new UserCallback() {
@Override
public void success(@NonNull User user) {
// Пароль успешно изменён
}
@Override
public void error(@NonNull Error error) {
// Ошибка смены пароля
}
@Override
public void error(@NonNull NetworkError error) {
// Не вызывается при смене пароля (нет сетевых запросов)
}
});
Этот метод предложит ввести текущий пароль (если он не был введён ранее), а затем откроет экран задания нового пароля.
В режиме УНЭП для смены пароля нужно передать старое и новое значение пароля в метод changePassword
класса UsersManagerNonQual
:
UsersManagerNonQual.changePassword(user, oldPassword, password, new UserCallback() {
@Override
public void success(@NonNull User user) {
}
@Override
public void error(@NonNull Error error) {
}
@Override
public void error(@NonNull NetworkError error) {
}
});
В режиме УКЭП при задании и смене пароля в пользовательском интерфейсе SDK может быть предложено использовать отпечаток пальца вместо пароля. При самостоятельной реализации такой функциональности в режиме УНЭП следует учитывать, допускают ли флаги ключа использование отпечатка пальца: метод user.isDenyStoreWithOSProtection()
должен возвращать false
.
Смена имени пользователя
Имя пользователя задаётся либо параметром name
методов регистрации класса usersManager
, либо одноимённым параметром метода store
класса UsersManagerNonQual
при работе в режиме УНЭП. В любом случае, для последующей смены этого имени необходимо вызывать метод rename
класса UsersManager
:
Error result = UsersManager.rename(user, newName);
if (result.getType() != Error._ERROR_OK) {
// Ошибка при смене имени
}
Синхронизация статуса пользователя
Для получения актуального статуса пользователя используется метод UsersManager.updateStatus(...)
, примеры использования которого описаны выше в сценариях регистрации.
Обновление профиля пользователя
Обновление профиля необходимо для того, чтобы не пришлось заново регистрировать устройство методами UsersManager.createUser(...)
после истечения срока действия ключей, привязанных к устройству. Время окончания срока действия ключей можно получить через вызов User.getNotAfter()
.
Обновление профиля для режима УКЭП осуществляется вызовом метода renew
класса UsersManager
:
UsersManager.renew(
user,
newName,
deviceName,
new UserCallback() {
@Override
public void success(@NonNull User renewedUser) {
// Профиль успешно обновлён
}
@Override
public void error(@NonNull Error error) {
// Ошибка обновления профиля
}
@Override
public void error(@NonNull NetworkError error) {
// Сетевая ошибка обновления профиля
}
});
Этот метод предложит ввести текущий пароль (вне зависимости от того, был он введён ранее или нет), а затем обновит профиль.
В режиме УНЭП для обновления профиля нужно передать значение пароля в метод renew
класса UsersManagerNonQual
:
UsersManagerNonQual.renew(
user,
newName,
deviceName,
password,
new UserCallback() {
@Override
public void success(@NonNull User renewedUser) {
// Профиль успешно обновлён
}
@Override
public void error(@NonNull Error error) {
// Ошибка обновления профиля
}
@Override
public void error(@NonNull NetworkError error) {
// Сетевая ошибка обновления профиля
}
});
Получение истории действий пользователя
Получить историю действия пользователя на сервере позволяет метод UsersManager.getOperationsHistory(...)
:
UsersManager.getOperationsHistory(
@NonNull User user,
int count,
int bookmark,
@Nullable int[] operationCodes,
@Nullable OperationsHistoryCallback callback
)
Параметры count
, bookmark
и operationCodes
позволяют сузить множество возвращаемых записей. Более подробно работа с объектами журнала действий пользователя описана в Документации по API.
Удаление пользователя с устройства
Удаление объекта User
из памяти приложения делает невозможным использование настоящего устройства для взаимодействия с сервером от имени учётной записи, к которой привязано устройство (однако, в дальнейшем устройство можно привязать заново).
Удаление объекта User
может быть произведено либо с уведомлением сервера (вызовом UsersManager.revoke(user, UserCallback)
), либо без уведомления сервера (вызовом usersManager.delete(User)
).
В первом случае устройство будет удалено и из памяти приложения и на стороне сервера, однако выполнение метода revoke()
требует предъявления пароля. Тем временем, вызов delete()
просто стирает данные из памяти приложения, но синхронизация с сервером не производится, пароль в этом случае предъявлять не требуется.
Работа с устройствами
Для работы со всеми текущими привязанными к учётной записи устройствами используются классы DevicesManager
и DevicesManagerNonQual
.
Пример получения списка всех привязанных устройств методом DevicesManager.listDevices()
, а также примеры обработки запроса на присоединение нового устройства в режимах УКЭП и УНЭП были описаны выше в разделах Присоединение нового устройства в режиме УКЭП и Присоединение нового устройства в режиме УНЭП.
Метод DevicesManager.revoke(...)
позволяет отозвать вектор аутентификации с заданного устройства (делает невозможным использование заданного устройства для взаимодействия с сервером ). Этот метод имеет смысл применять только при отзыве некоторого устройства отличного от данного (на котором выполняется вызов), для отзыва данного устройства необходимо вызывать метод UsersManager.revoke(user, UserCallback)
.
Работа с сертификатами
Для работы с сертификатами используется класс CertificatesManager
. Кроме того, есть класс CertificatesManagerNonQual
, который предоставляет функционал для работы с ключами и сертификатами, располагающимися на мобильном устройстве и на рутокенах. Подробнее об этом классе можно прочесть в разделе Работа с клиентской подписью.
SDK работает с тремя типами сущностей:
- Сертификатами, которыми оперирует SDK;
- Запросами на выпуск сертификата;
- Сертификатами, прочитанными из внешних хранилищ (с Рутокена) с помощью вызова метода
CertificatesManagerNonQual.getExternalCertificates(ExternalCertificatesCallback)
. После вызоваCertificatesManager.setCertificate(User, Certificate, CertificateNetworkCallback)
, с таким "внешнем сертификатом", он преобразуется в сертификат, которым оперирует SDK.
Все эти сущности представлены экземплярами класса Certificate
. Для сертификата метод Certificate.getType()
возвращает значение Certificate.Type.Crt
, в то время как для запроса на сертификат тип будет иметь значение Certificate.Type.Req
. Метод Certificate.getState()
возвращает текущее состояние сертификата или запроса на сертификат. Возможные возвращаемые значения этого метода будут разными для сертификатов и запросов. Подробно о возможных состояниях можно прочесть в описании перечисления Certificate.State.
Получение списка сертификатов
Для получения списка сертификатов пользователя и запросов на выпуск сертификата используется метод listCertificates(...)
:
CertificatesManager.listCertificates(user, new CertificatesNetworkCallback() {
@Override
public void success(@NonNull Certificate[] certificates) {
if (certificates.length > 0) {
// Список сертификатов и запросов непустой - можно выполнять какие-либо действия
for (Certificate item: certificates) {
if (item.getType() == Certificate.Type.Crt) {
// Выполняем действия с сертификатом
} else {
// Выполняем действия с запросом на сертификат
}
}
}
}
@Override
public void error(@NonNull NetworkError error) {
// Ошибка запроса списка
}
});
Создание запроса на сертификат
SDK поддерживает сценарий создания запроса на сертификат из мобильного приложения. Для этого необходимо указать идентификатор обработчика УЦ, идентификатор шаблона сертификата и различительное имя субъекта, состоящее из набора ключей и значений компонентов имени.
Для получения этих данных необходимо запросить политики сервиса подписи:
PolicyManager.getSignParams(user, new PolicySignServerNetworkCallback() {
@Override
public void success(@NonNull SignServerParams 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 (SignServerParams.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 NetworkError error) {
// Ошибка запроса параметров
}
});
Затем нужно передать собранные параметры методу CertificatesManager.createCertificate(...)
:
CertificatesManager.createCertificate(user, caId, tid, dn, new CertificateNetworkCallback() {
@Override
public void success(Certificate request) {
// Запрос на сертификат создан.
// Объект request содержит данные запроса
}
@Override
public void error(@NonNull NetworkError error) {
// Ошибка создания запроса
}
});
Управление сертификатами и запросами
SDK предоставляет функциональность для управления существующими сертификатами и запросами. Методы для управления сертификатами и запросами расположены в классе CertificatesManager
.
Название метода | Предназначение |
---|---|
deleteCertificate | Удаление сертификата или запроса на сертификат |
setCertificate | Установка сертификата пользователя |
setCertificateFriendlyName | Установка дружественного имени сертификата |
revokeCertificate | Отзыв сертификата |
suspendCertificate | Приостановка действия сертификата |
unSuspendCertificate | Возобновление действия сертификата |
setDefaultCertificate | Установка сертификата по умолчанию |
Подробнее о каждом методе можно прочесть в документации на класс CertificatesManager.
Работа с операциями и документами
Работа с операциями и документами осуществляется с помощью класса OperationsManager
. Класс OperationsManagerNonQual
предоставляет средства работы с операциями и документами в режиме УНЭП без запуска пользовательского интерфейса.
Запрос списка операций
Получить список операций, требующих обработки, можно вызовом метода OperationsManager.getOperationsList(...)
:
OperationsManager.getOperationsList(
@NonNull User user,
@Nullable OperationsManager.OperationType operationType,
@Nullable String operationId,
@Nullable OperationsNetworkCallback callback
)
Параметры operationType
и operationId
позволяют отфильтровать список либо по типу операции, либо по её идентификатору (тогда будет возвращён список из одной операции). Для возвращения полного списка на обеих позициях нужно передать значение null
.
Подтверждение операции
Подтверждение операции в режиме УКЭП
Для подтверждения или отклонения операции в режиме УКЭП достаточно вызвать один метод OperationsManager.confirmOperation(...)
:
OperationsManager.confirmOperation(
@NonNull User user,
@NonNull Operation operation,
@Nullable OperationsManager.SignMode signMode,
boolean isSelectionEnabled,
boolean skipSnippet,
@Nullable ApproveRequestNetworkCallback callback
)
Данный метод запустит экран отображения операции с кнопками подтверждения и отклонения. При необходимости будет также запрошен пароль.
Параметр signMode
определяет способ обработки: онлайн (с отправкой запроса на сервер) и оффлайн (создание запроса на подтверждение без отправки самого запроса). При указании null
используется онлайн-подтверждение.
Параметр isSelectionEnabled
определяет, разрешено ли отмечать галочкой документы для обработки, либо же все документы обрабатываются (подтверждаются или отклоняются) целиком.
При передаче true
в качестве параметра skipSnippet
при наличии одного единственного документа в операции будет сразу отображён этот документ (вместо списка документов, как это происходит по умолчанию).
Если при выполнении операции возникает ошибка, то вызывается метод
ApproveRequestNetworkCallback.error(OperationsManager.ApproveRequest)
Информацию о возникшей ошибке можно получить вызовом методов getOnlineConfirmationResult()
(возвращает объект сетевой ошибки NetworkError
) и getOfflineError()
(возвращает объект внутренней ошибки Error
). Так как возникает ошибка либо одного, либо другого типа, один из методов вернёт значение null
, а другой - объект ошибки.
При успешной обработке вызывается
ApproveRequestNetworkCallback.success(OperationsManager.ApproveRequest)
Информацию о том, какие документы были подтверждены и отклонены можно получить вызовом методов getConfirmedDocuments()
и getDeclinedDocuments()
полученного объекта ApproveRequest
.
Подтверждение операции в режиме УНЭП
В режиме УНЭП приложение может самостоятельно отобразить данные операции и интерфейс выбора документов для подтверждения и отклонения, а также запросить пароль. После этого необходимо вызвать метод OperationsManagerNonQual.confirmOperation(...)
и передать туда список документов на подтверждение и отклонение:
OperationsManagerNonQual.confirmOperation(
@NonNull User user,
@NonNull Operation operation,
@Nullable List<Operation.Document> documentsToConfirm,
@Nullable List<Operation.Document> documentsToDecline,
@Nullable OperationsManager.SignMode signMode,
@NonNull KeysSource keysSource,
@Nullable ApproveRequestNetworkCallback callback
)
Если нет документов на подтверждение, то в качестве documentsToConfirm
нужно передать null
(не нужно передавать пустой список). Таким же образом нужно действовать и при отсутствии документов на отклонение. Параметры documentsToConfirm
и documentsToDecline
не должны иметь значение null
одновременно.
Загрузка документов на сервер
Для загрузки документа на сервер необходимо использовать метод uploadDocument(...)
класса OperationsManager
:
OperationsManager.uploadDocument(
@NonNull User user,
@NonNull String title,
@Nullable String snippetTemplate,
@Nullable String previewTemplate,
@NonNull byte[] content,
@Nullable DocumentIdNetworkCallback callback
)
Параметры title
и content
задают название и содержимое документа соответственно. Параметры snippetTemplate
и previewTemplate
задаются в виде HTML-текста и определяют шаблон отображения сниппета документа (краткое описание для отображения документа в списке других документов) и шаблон предварительно просмотра документа. Оба параметра могут не задаваться (иметь значение null
) - формат отображения будет определяться настроенными на сервере шаблонами.
Получение данных документа
Скачать с сервера описание документа (представлено экземплярами класса Operation.Document
) можно методом getDocumentDescription(...)
класса OperationsManager
:
OperationsManager.getDocumentDescription(
@NonNull User user,
@NonNull String documentId,
@Nullable DocumentNetworkCallback callback
)
Получение содержимого документа
Для получения оригинального содержимого документа необходимо вызвать метод:
OperationsManager.getDocumentBinaryData(
@NonNull User user,
@NonNull String docId,
@NonNull GetDocumentBinaryDataCallback callback
)
Когда содержимое документа будет полностью скачано, будет вызван метод:
GetDocumentBinaryDataCallback.success(File)
Кроме того, для отслеживания прогресса скачивания в реализации интерфейса GetDocumentBinaryDataCallback
можно переопределить метод с реализацией по умолчанию:
default void onSomeBytesDownloaded(int downloaded, int total) {
// Скачано downloaded байт из всего total байт
}
При работе в режиме УНЭП возможно также получить представление документа для предварительного просмотра (метод OperationsManagerNonQual.getDocumentPreview(...)
) и "сырое" представление документа в формате PDF (метод OperationsManagerNonQual.getDocumentRawPDF(...)
).
Подписание документов
Подписание документов в режиме УКЭП
В режиме УКЭП подписание документов осуществляется с помощью вызова метода signDocuments(...)
класса OperationsManager
:
signDocuments(
@NonNull User user,
@NonNull ArrayList<String> documentIds,
@NonNull OperationsManager.SignParams params,
@Nullable SignResultNetworkCallback callback
)
Данный метод отобразит экран со списком подписываемых документов и кнопкой "Подписать", по нажатию на которую на сервер будет отправлен запрос на подписание (а также предварительно будет запрошен пароль при необходимости).
Данный метод требует передачи параметров подписания в виде объекта params
. Параметры подписания включают идентификатор сертификата для подписания и идентификатор шаблона подписи.
Идентификатор шаблона подписи можно получить из параметров сервиса подписи:
PolicyManager.getSignParams(user, new PolicySignServerNetworkCallback() {
@Override
public void success(@NonNull SignServerParams params) {
// Параметры сервиса подписи получены, извлекаем необходимые данные
// Дальнейший код дан ДЛЯ ПРИМЕРА:
// Берётся первый доступный шаблон подписи
String templateId = params.getProcessingTemplates()[0].getId();
}
@Override
public void error(@NonNull NetworkError error) {
// Ошибка запроса параметров
}
});
Подписание документов в режиме УНЭП
В режиме УНЭП подписание документов можно выполнить методом signDocuments(...)
класса OperationsManagerNonQual
:
OperationsManagerNonQual.signDocuments(
@NonNull User user,
@NonNull List<Operation.Document> confirmedDocuments,
@NonNull OperationsManager.SignParams params,
@NonNull KeysSource keysSource,
@Nullable SignResultNetworkCallback callback
)
В отличие от метода signDocuments
класса OperationsManager
данный метод принимает список самих документов (а не их идентификаторов), не отображает пользовательский интерфейс для просмотра документов и не отображает экран ввода пароля (на момент вызова этого метода пароль уже должен быть введён).
Работа с клиентской подписью
SDK поддерживает возможность подписания и расшифрования документов с помощью ключей, установленных на мобильном устройстве. При этом для мобильного приложения использование API SDK для подтверждения операций и подписания документов не меняется. Для использования функционала клиентской подписи приложению достаточно сгенерировать ключи на мобильном устройстве, подписать запрос на сертификат и установить сертификат в память устройства после его выпуска.
Создание неподписанного запроса на сертификат
Процесс установки ключей подписи на мобильном устройстве начинается с создания неподписанного запроса на сертификат. В наиболее типичном сценарии этот запрос будет создан оператором на сервере. Однако, SDK поддерживает возможность создания такого запроса на стороне приложения. Для создания неподписанного запроса нужно вызвать метод createCertificate
класса CertificatesManagerNonQual
:
CertificatesManagerNonQual.createCertificate(
@NonNull User user,
int caId,
@NonNull String templateId,
@NonNull Map<String, String> dn,
boolean isClient,
@Nullable CertificateNetworkCallback callback
)
Использование метода аналогично применению одноимённого метода из класса CertificatesManager
, за исключением того, что необходимо передать значение true
на месте параметра isClient
(передача параметра false
инициирует создание обычного запроса на сертификат, как при использовании CertificatesManager.createCertificate(...)
).
Подписание запроса на сертификат
На этом шаге в приложении создаётся ключевая пара и подписывается созданный запрос, после чего подписанный запрос отправляется на сервер. Далее этот запрос передаётся в УЦ для обработки.
Найти неподписанный запрос можно, запросив список запросов и сертификатов и найдя запрос со статусом sign_wait
:
CertificatesManager.listCertificates(user, new CertificatesNetworkCallback() {
@Override
public void success(Certificate[] certificates) {
for (Certificate item: certificates) {
if (item.getType() == Certificate.Type.Req && item.getState() == Certificate.State.sign_wait) {
// Неподписанный запрос найден
}
}
}
@Override
public void error(NetworkError NetworkError) {
// Ошибка запроса списка сертификатов и запросов
}
});
Далее, запрос нужно подписать при помощи метода signCertificateRequest(...)
класса CertificatesManagerNonQual
:
CertificatesManagerNonQual.signCertificateRequest(user, request, keysSourceIdentifier, new SignCertificateRequestCallback() {
@Override
public void success(@NonNull User user, @NonNull Certificate request) {
// Запрос успешно подписан
// Объект request содержит информацию о подписанном запросе
}
@Override
public void error(@NonNull Error error) {
// При подписании запроса возникла ошибка
}
@Override
public void error(@NonNull NetworkError error) {
// При отправке подписанного запроса возникла ошибка
}
});
Данный метод откроет экран ввода пароля (при необходимости), после чего запустит биологический датчик случайных чисел (пользователю будет предложено выполнить серию касаний экрана), после чего будет сгенерирована ключевая пара и подписан запрос, результат подписания будет отправлен на сервер. Если в качестве хранилища будет указан рутокен, тогда будет запрошен пин-код токена.
Установка сертификата
После выпуска сертификата удостоверяющим центром новый сертификат должен быть установлен в память приложения.
Для начала необходимо найти неустановленный сертификат в списке запросов и сертификатов:
CertificatesManager.listCertificates(currentUser, new CertificatesNetworkCallback() {
@Override
public void success(Certificate[] certificates) {
for (Certificate crt: certificates) {
if (crt.getType() == Certificate.Type.Crt
&& crt.getState() == Certificate.State.active
&& crt.isClient()
&& CertificatesManagerNonQual.isCertificateAccessibleOnThisDevice(user, crt).getType() == ru.safetech.ckey.sdk.v1.utils.Error.ERROR_OK
&& CertificatesManagerNonQual.isCertificateInstalled(user, crt).getType() == ru.safetech.ckey.sdk.v1.utils.Error.ERROR_CERTIFICATE_NOT_INSTALLED) {
// Найден неустановленный сертификат, необходимо выполнить установку
}
}
}
@Override
public void error(NetworkError error) {
// Ошибка получения списка сертификатов и запросов
}
});
Логическая конструкция включает проверку следующих обязательных для установки сертификата условий:
- Объект
Certificate
является сертификатом, а не запросом - Сертификат является активным (может использоваться для подписания)
- Сертификат выпущен по запросу, подписанному локальными (хранящимися на мобильном устройстве) ключами
- Создание ключей и подписание запроса производилось на этом устройстве
- Сертификат ещё не был установлен
Далее, можно устанавливать сертификат. Для этого нужно вызвать метод installCertificate
класса CertificatesManagerNonQual
:
CertificatesManagerNonQual.installCertificate(
@NonNull User user,
@NonNull Certificate certificate,
@Nullable UserCallback callback
)
После установки сертификата, хранимые на устройстве ключи могут использоваться при подписании и расшифровании документов наравне с ключами, хранимыми на сервере.
Подтверждение операций клиентскими ключами
Операции, созданные на сервере, содержат идентификатор сертификата, который будет использоваться при подписании. При вызове методов confirmOperation(...)
классов OperationsManager
и OperationsManagerNonQual
SDK самостоятельно определит, требуется ли использовать локальные ключи для обработки операции, поэтому никаких дополнительных действий со стороны приложения не нужно.
Подписание документов клиентскими ключами
Для подписания документов клиентскими ключами необходимо при вызове методов signDocuments(...)
классов OperationsManager
и OperationsManagerNonQual
в параметре типа OperationsManager.SignParams
указать идентификатор сертификата, ранее установленного в приложение методом CertificatesManagerNonQual.installCertificate(...)
.
Особенности работы с несколькими устройствами
Если к учётной записи привязано несколько устройств, то при подписании запроса на сертификат на одном устройстве, установка сертификата и дальнейшее его использование возможно только на этом же устройстве. Поэтому, перед подтверждением операций и подписанием документов для целевого сертификата необходимо проводить проверку вида:
if (!certificate.isClient() || CertificatesManagerNonQual.isCertificateAccessibleOnThisDevice(user, certificate).getType() == Error.ERROR_OK) {
// Сертификат либо облачный (ключи хранятся на сервере ),
// либо установлен на этом устройстве,
// то есть им можно пользоваться
}
Если сертификатом нельзя воспользоваться на этом устройстве, то методы подписания документов и подтверждения операций завершаться ошибкой типа Error._ERROR_KEY_PAIR_DOES_NOT_EXIST
.
Изменение внешнего вида БиоДСЧ
При вызове метода CertificatesManagerNonQual.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">
<string name="BioRnd">
<string name="Cancel">
Дополнительные параметры
Приведённые выше примеры описывают работу с клиентской подписью с использованием параметров по умолчанию (SDK самостоятельно задаёт имя контейнера для хранения ключей и самостоятельно генерирует ПИН-код для доступа к контейнеру). Такой режим работы SDK является рекомендованным (и наиболее простым в использовании). Однако при необходимости можно использовать класс KeysManagerNonQual
для самостоятельного управления ключами, а также использовать перегрузки методов signCertificateRequest(...)
и installCertificate(...)
класса CertificatesManagerNonQual
с расширенным набором параметров.
Автоматическая подпись (использование допустимо только при создании УНЭП)
Автоматическая подпись представляет собой механизм подтверждения серии операций без необходимости взаимодействия с приложением.
Для реализации такого механизма приложение должно выполнить следующие шаги.
1. Обнаружение первой операции в цепочке автоматического подписания
При запросе списка операций классическим методом OperationsManager,getOperationList(...) для возвращаемых операций необходимо проверить значение статуса автоподписи, возвращаемое методом Operation.getAutoSignState(). Первая операция в цепочке автоподписи будет иметь значение Aware
. С подтверждения этой операции запускается процесс автоподписания.
2. Подтверждение первой операции
Первая операция в цепочке автоподписания (со статусом Aware
) подтверждается обычным образом, как и все другие операции, через метод confirmOperation(...)
. Содержимое операции будет отображено пользователю, при необходимости будет запрошен пароль (т.е. визуально подтверждение такой операции ничем не отличается).
3. Запрос подтверждения перехода в режим автоматического подписания
После подтверждения операции со статусом Aware
приложение должно отобразить пользователю экран с предложением подтверждать дальнейшие операции из цепочки в автоматическом режиме. При показе этого экрана может быть использована информация о прикладной системе, создавшей операцию. Получить эту информацию можно при помощи метода Operation.getAppSystemInfo(). В частности, приложению необходимо сохранить идентификатор прикладной системы, возвращаемый методом AppSystemInfo.getClientId(). Данный идентификатор нужен для получения остальных операций из цепочки.
4. Получение последующих операций на автоподписание
После согласия пользователя перейти в режим автоматического подписания приложение должно отобразить экран, информирующий, что пользователь находится в режиме автоматической подписи. В этом режиме приложение должно периодически опрашивать сервер на наличие следующих операций в цепочке. Для этого нужно вызывать перегрузку метода OperationsManager.getOperationList(...)
, принимающую идентификатор прикладной системы (должен быть сохранён из первой операции вызовом AppSystemInfo.getClientId()
) в качестве параметра фильтрации. В качестве результата сервер будет возвращать операции, для которых Operation.getAutoSignState() == Enabled
, что означает возможность подтверждения этих операций автоматически без отображения пользователю.
5. Подтверждение полученных операций
Полученные операции со статусом Operation.getAutoSignState() == Enabled
должны быть подтверждены методом OperationsManagerNonQual.confirmOperation(...)
. При этом, перед вызовом необходимо проверить выполнение условия user.isReadyToSign() == true
, так как данный метод подтверждает операцию в фоновом режиме и не запрашивает пароль. При невыполнении условия user.isReadyToSign() == false
(может возникнуть, например, вследствие сворачивания приложения более чем на 15 секунд) следует вывести приложение из режима автоподписи и вернуться в точку, из которой операции запрашиваются классическим образом через метод OperationsManager.getOperationList(...) без указания clientId
для фильтрации. Кроме того, следует выводить приложение из режима автоподписи по желанию пользователя, например, через возврат на предыдущий экран при нажатии системной кнопки "Назад".
Создание и восстановление резервных копий
Создание и восстановление резервных копий может быть полезно при смене устройства или если пользователь забыл пароль к своему профилю, при этом он создавал резервную копию и запомнил пароль для восстановления (recovery password). Профиль пользователя и ключи пользователя можно экспортировать и/или восстановить с помощью методов из таблицы ниже.
Наименование | Предназначение |
---|---|
UsersManagerNonQual.createBackup(...) | Экспорт данных пользователя (объекта User ) в JSON с шифрованием при помощи указанного пароля. Резервная копия возвращается в приложение в виде JSON-строки. |
UsersManagerNonQual.restoreBackup(...) | Восстановление объекта User из резервной копии. Резервная копия представляет собой ранее созданную JSON-строку. |
CertificatesManagerNonQual.exportPfx(...) | Создание резервной копии (архивация) ключей подписи, хранимых на мобильном устройстве, на сервере . Опционально может быть указан ПИН-код для шифрования резервной копии. Если он не указан, сервером будет использован ПИН-код по умолчанию. |
CertificatesManagerNonQual.importPfx(...) | Восстановление резервной копии ключей подписи, ранее архивированных на сервере . Если при архивации был задан ПИН-код, его потребуется предъявить при восстановлении. |
CertificatesManagerNonQual.deletePfx(...) | Удаление ранее созданной резервной копии ключей с сервера |
KeysManagerNonQual.createBackup(...) | Создание резервной копии ключей подписи, хранимых на мобильном устройстве с шифрованием при помощи указанного пароля. Резервная копия возвращается в приложение в виде JSON-строки. |
KeysManagerNonQual.restoreBackup(...) | Восстановление локальных ключей подписи из резервной копии. Резервная копия представляет собой ранее созданную JSON-строку. |
Резервное копирование данных профиля
Данные профиля (информация об устройстве и симметричные ключи аутентификации, хранимые в объекте User
) могут быть архивированы в зашифрованном виде для дальнейшего восстановления на том же самом устройстве (например, при переустановке приложения), либо на другом устройстве (например, если пользователь решил сменить устройство или вынужден временно воспользоваться другим устройством).
Резервная копия профиля создаётся в виде JSON-строки, которая возвращается в приложение. JSON-строка содержит данные, зашифрованные при помощи указанного при создании копии пароля, и может безопасно быть сохранена приложением в желаемом месте.
Примером хранилища для резервных копий может служить личный Google Drive пользователя. Приложение может создать в Google Drive свою служебную директорию, доступную только самому приложению, и сохранять в неё резервную копию.
Пример экспорта профиля пользователя:
UsersManagerNonQual.createBackup(user, recoveryPassword, new UserBackupCallback() {
@Override
public void success(@NonNull String backup) {
// Профиль пользователя был экспортирован в JSON-строку.
// Приложение должно сохранить эту сроку таким образом, чтобы можно было отдать
// её назад в SDK при восстановлении профиля на этом же или другом устройстве
}
@Override
public void error(@NonNull Error error) {
// Не удалось экспортировать профиль пользователя
}
});
Когда потребуется восстановить данные профиля из резервной копии, приложение должно передать ту же самую JSON-строку и тот же пароль в метод восстановления резервной копии.
Пример импорта (восстановления) профиля пользователя:
UsersManagerNonQual.restoreBackup(backupString, recoveryPassword, new UserCallback() {
@Override
public void success(@NonNull User restoredUser) {
// Пользователь успешно импортирован, далее его необходимо сохранить в долгосрочную память
UsersManagerNonQual.store(restoredUser, restoredUser.getName(), new UserCallback() {
@Override
public void success(@NonNull User user) {
// Пользователь готов к работе
}
@Override
public void error(@NonNull Error error) {
// Ошибка при сохранении восстановленного профиля пользователя
}
@Override
public void error(@NonNull NetworkError error) {
// Сетевая ошибка при сохранении восстановленного профиля пользователя
}
});
}
@Override
public void error(@NonNull Error error) {
// Ошибка при импортировании профиля пользователя
}
@Override
public void error(@NonNull NetworkError error) {
// Сетевая ошибка при импортировании профиля пользователя
}
});
Архивирование ключей подписи на сервере
На сервере могут быть архивированы только ключи, созданные с флагом exportable
. Все ключи подписи создаются автоматически с таким флагом, если только приложение само не использует метод KeysManagerNonQual.createKeyPair(...)
с передачей объекта KeyInfo
с явно заданным параметром isExportable
как false
.
Для создания резервной копии ключей подписи в приложении нужно выбрать соответствующий сертификат и передать его в метод CertificatesManagerNonQual.exportPfx(...)
. В примере ниже выполняется резервное копирование всех доступных на устройстве ключей подписи:
CertificatesManager.listCertificates(user, new CertificatesNetworkCallback() {
@Override
public void success(@NonNull Certificate[] certificates) {
// Успешно получили все запросы и сертификаты
for (Certificate entity: certificates) {
// Проверяем каждый объект, чтобы найти сертификаты, пригодные для архивации ключей
if (entity.getType() == Certificate.Type.Req) {
// Пропускаем все запросы на сертификаты
continue;
}
if (!entity.isClient()) {
// Пропускаем сертификаты, для которых ключи хранятся на сервере
continue;
}
if (entity.isArchived()) {
// Пропускаем ключи, которые уже были архивированы
continue;
}
if (!CertificatesManagerNonQual.isCertificateAccessibleOnThisDevice(user, entity)) {
// Пропускаем ключи, которые хранятся на другом устройстве
continue;
}
// Все необходимые условия проверены - можем архивировать ключи
CertificatesManagerNonQual.exportPfx(user, entity, null, "My PIN", new ExportPfxCallback() {
@Override
public void success() {
// Ключи успешно архивированы на сервере
}
@Override
public void error(@NonNull Error error) {
// Архивация ключей неуспешна из-за внутренней ошибки SDK
}
@Override
public void error(@NonNull NetworkError error) {
// Архивация ключей неуспешна из-за сетевой ошибки
}
});
}
}
@Override
public void error(@NonNull NetworkError error) {
// Ошибка получения списка сертификатов
}
});
В качестве третьего параметра метод exportPfx(...)
требует ПИН-кода от контейнера с ключевой информацией, подлежащей архивации. В приведённом выше примере передается null
, так как SDK использует ПИН-код по умолчанию для защиты контейнеров. Если вы используете свой ПИН-код при создании объектов KeyInfo
, то вместо null
необходимо указывать его.
После архивации ключей их можно восстановить на том же самом устройстве (либо на другом устройстве, где есть объект User
, привязанный к той же учётной записи). Для этого необходимо вызвать метод importPfx(...)
:
if (certificate.isArchived()) {
// Ключи были архивированы ранее и могут быть восстановлены
CertificatesManagerNonQual.importPfx(user, certificate, null, "My PIN", new ImportPfxCallback() {
@Override
public void success() {
// Ключи успешно восстановлены на устройстве
}
@Override
public void error(@NonNull Error error) {
// Восстановление ключей невозможно из-за внутренней ошибки SDK
}
@Override
public void error(@NonNull NetworkError error) {
// Восстановление ключей невозможно из-за сетевой ошибки
}
});
}
Третий параметр задаёт ПИН-код для защиты контейнера с ключевой информацией. При указании null
будет использован ПИН-код по умолчанию.
Созданную ранее архивную копию ключей можно удалить с сервера:
if (certificate.isArchived()) {
// Ключи были архивированы ранее - резервная копия может быть удалена
CertificatesManagerNonQual.deletePfx(user, certificate, new DeletePfxCallback() {
@Override
public void success() {
// Копия ключей удалена с сервера
}
@Override
public void error(@NonNull Error error) {
// Удаление ключей провалилось из-за внутренней ошибки SDK
}
@Override
public void error(@NonNull NetworkError error) {
// Удаление ключей провалилось из-за сетевой ошибки
}
});
}
Архивирование ключей подписи с созданием резервной копии на устройстве
Альтернативно, приложение может выполнить резервное копирование ключей подписи аналогично данным профиля: создать резервную копию в виде JSON-строки с зашифрованными на пароле данными и сохранить эту строку в нужном месте средствами самого приложения, после чего восстановить ключи из этой же строки.
Пример создания резервной копии данных ключа подписи:
// Получить данные о локальных ключах пользователя
List<KeyInfo> keys = KeysManagerNonQual.getKeysForUser(user);
// Взять ключ, для которого будет создана резервная копия
// для примера - только первый ключ, но чаще всего необходимо обойти всю коллекцию и для каждого локального ключа создать резервную копию
KeyInfo key = keys.get(0);
KeysManagerNonQual.createBackup(key, recoveryPass, new KeyInfoBackupCallback() {
@Override
public void success(@NonNull String keysBackupJson) {
// Данные ключа подписи были экспортированы в строку keysBackupJson, далее её можно сохранить
}
@Override
public void error(@NonNull Error error) {
// Ошибка при экспортировании данных ключа подписи
}
});
Пример восстановления данных ключа подписи:
KeysManagerNonQual.restoreBackup(keysBackupJson, recoveryPass,
new KeyInfoRestorationCallback() {
@Override
public void success(@NonNull KeyInfo keyInfo) {
// Данные ключа подписи были успешно импортированы
}
@Override
public void error(@NonNull Error error) {
// Ошибка при импортировании данных ключа подписи
}
});
Кастомизация пользовательского интерфейса
При работе в режиме УКЭП SDK показывает пользовательский интерфейс при инициализации, регистрации устройства, подтверждении операций, подписании документов и других действиях.
Кастомизация с помощью класса Appearance
Класс Appearance
может быть использован для кастомизации пользовательского интерфейса, отображаемого SDK. Приложение может менять следующие параметры:
- Цвет фона экранов и отдельных элементов (кнопок, галочек и др.)
- Цвета, шрифты и размеры отдельных надписей
- Иконки, используемые в интерфейсе
- Параметры панели действий (ActionBar)
- Анимации смены фрагментов, диалоговых окон, некоторых представлений.
Полный список настроек можно посмотреть в документации на класс Appearance.
Ниже приведено несколько примеров применения класса Appearance
:
// Изменение цвета основных кнопок
CKey.getAppearance().getButtons().getPrimary().backgroundColor = R.color.some_color_in_my_app;
// Изменение ресурса иконки информации об операции
CKey.getAppearance().getImages().getOperationInfoIcon().icon = R.drawable.some_icon_in_my_app;
// Изменение фона под иконкой информации об операции
CKey.getAppearance().getImages().getOperationInfoIcon().iconTint = R.color.some_color_in_my_app;
// Изменение цвета заголовков модальных окон
CKey.getAppearance().getLabels().getModalTitle().color = R.color.some_color_in_my_app;
// Изменение начертания заголовков модальных окон
CKey.getAppearance().getLabels().getModalTitle().fontWeight = Appearance.LabelAppearance.FontWeight.Medium;
// Изменение размера кнопки виртуальной клавиатуры
CKey.getAppearance().getButtons().getKeyboard().getAppearance().size = R.dimen.some_dimen_in_my_app;
// Изменение фона индикатора прогресса
CKey.getAppearance().getViews().getProgressBar().backgroundColor = R.color.some_color_in_my_app;
// Изменение цвета заголовка на панели инструментов
CKey.getAppearance().getBaseAppearance().getMain().getTitle().color = R.color.some_color_in_my_app;
Кастомизация с помощью класса LayoutMapper
Класс LayoutMapper
может быть использован для замены файлов разметки, использованных в SDK. В таблице ниже представлены важные классы для кастомизации.
Наименование | Предназначение |
---|---|
FragmentsLayouts | Кастомизация фрагментов. |
DialogsLayouts | Кастомизация диалоговых окон. |
ListsItems | Кастомизация элементов списков. |
CustomViews | Кастомизация прочих представлений (клавиатура для ввода пина, визуализация пинов). |
По умолчанию используется разметка из макетов SDK, поэтому необязательно заменять их все. Однако, использование своей разметки экранов отключает возможность управления их внешним видом через класс Appearance
.
Макеты должны содержать необходимые для функционирования SDK идентификаторы, если их не будет (или их тип не будет совпадать), тогда SDK во время выполнения будет генерировать ошибки (для фрагментов) либо не выводить их на экран (для всего остального). В случае ошибки в лог будет сделана запись, начинающаяся с Error custom mapping view
.
Для упрощения замены вёрстки через класс LayoutMapper
разметки рекомендуется:
- Скачать aar-файл SDK своей версии из репозитория.
- Разархивировать файл SDK my-*.aar любой программой-архиватором, поддерживающем формат архивации файлов zip.
- Перейти в папку
\res\layout
и скопировать оттуда файлы с префиксамиckey_fragment_
,ckey_dialog_
,ckey_item_
,ckey_layout_
,ckey_include_toolbar_
, а так же файлckey_menu_document_view_options.xml
в свой проект. - Переименовать префикс
ckey_
в названии файлов на другой, например наcustom_
. При этом необходимо поправить ссылки на переименованные файлы с префиксомckey_include_toolbar_
в строках<include layout="@layout/ckey_include_toolbar_"
. - Произвести необходимые изменения в вёрстке макетов, при этом необходимо сохранить обязательные идентификаторы, их типы. Полный список обязательных полей можно посмотреть в документации на класс LayoutMapper.
- Перед инициализацией SDK получить объект класса
LayoutMapper
изCKey.getLayoutsMapper()
и установить идентификаторы файлов со своей вёрсткой.
Пример установки идентификатора кастомного диалога загрузки:
CKey.getLayoutsMapper().getDialogs().setLoaderDialogLayoutId(R.layout.custom_sdk_dialog_loader);
Дополнительная информация
Альтернативное логирование
По умолчанию SDK записывает логи в LogCat. Это поведение можно изменить, передав в метод CKey.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(User, certificateId)
с идентификатором сертификата.
Создание ключевой пары
Для создания ключевой пары и подписи запроса на Рутокене необходимо вызвать CertificatesManagerNonQual.signCertificateRequest(User, request, selectedKeysSourceIdentifier)
с параметром selectedKeysSourceIdentifier
равным предопределённому значению KeysSourceIdentifier.rutokenNFC
.
Если было успешное взаимодействие с Рутокеном (независимо, метод CertificatesManagerNonQual.signCertificateRequest
отработал штатно или с ошибкой), то будет означен параметр KeysSource
в обратном вызове. Если при этом токен был непроинициализирован, то SDK его проинициализирует и установит на него случайный PUK-код. Чтобы получить его в приложении из KeysSource
, необходимо преобразовать KeysSource
в KeysSourcePukInitializable
и вызвать getPuk()
.
Использование существующих на Рутокене сертификатов
Для подключения к SDK существующих сертификатов на Рутокене, необходимо:
- Вызвать
CertificatesManagerNonQual.getExternalCertificates(ExternalCertificatesCallback)
и получить список сертификатов на токене - На выбранном из списка сертификате вызвать
CertificatesManager.setCertificate(User user, Certificate, CertificateNetworkCallback)
Запуск Activity из SDK и задачи Android
По умолчанию, при вызове методов SDK, требующих показа пользовательского интерфейса, экземпляры Activity из SDK запускаются в составе новой задачи (используется вызов Context.startActivity()
с флагом Intent.FLAG_ACTIVITY_NEW_TASK
- это требование ОС). Однако, в некоторых случаях такое поведение может быть нежелательным и предпочтительнее запускать Activity SDK в составе той же самой задачи. Тогда необходимо использовать перегрузки методов SDK с первым параметром типа Activity
, подставляя в качестве аргумента экземпляр Activity приложения.
К таким методам относятся:
CKey.init(...)
(в качестве экземпляра Context можно передать Activity)UsersManager.createUser(...)
UsersManager.createUserWithInitQR(...)
UsersManager.createUserWithApproval(...)
UsersManager.acceptAccountChanges(...)
UsersManager.checkApprovalStatus(...)
UsersManager.submitPassword(...)
UsersManager.changePassword(...)
UsersManager.revoke(...)
UsersManager.renew(...)
DeviceManager.processAwaitingDevice(...)
OperationsManager.confirmOperation(...)
OperationsManager.signDocumentsOffline(...)
OperationsManager.signDocuments(...)
CertificatesManagerNonQual.signCertificateRequest(...)
CertificatesManagerNonQual.getExternalCertificates(...)