Термины и определения
Тип | Описание |
---|---|
DSSUser | Объект, содержащий всю необходимую информацию для выполнения действий от имени пользователя. Содержит, в том числе, информацию для доступа к экземпляру DSS (url, корневой сертификат и пр.), "вектор аутентификации" для подтверждения действий и пр. С точки зрения DSS данный объект является устройством, подключенным к учетной записи пользователя DSS |
DSSDevice | Объект, содержащий информацию об устройствах, подключенных к той же учетной записи, что и объект DSSUser. Данный объект не содержит "вектор аутентификации" и не может использоваться для подтверждения операций или выполнения других действий |
DSSOperation | Операция DSS, для которой требуется подтверждение/отклонение. Операция может содержать несколько документов. При подтверждении/отклонении операции все содержащиеся в ней документы будут подписаны/отклонены. Операция создается на сервере DSS |
DSSOperation.DSSDocument | Документ, входящий в операцию, либо обрабатываемый самостоятельно |
Document Description | "Сниппет" документа, сформированный сервером DSS на основе шаблона для сниппета и содержания документа |
Document Preview | Визуализированный в человеко-читаемую форму документ, сформированный сервером DSS на основе шаблона для визуализации и содержания документа |
Document RawPDF | "Сырое" содержание документа (например, текстового файла), преобразованное в формат PDF |
DSSCertificate | Сертификат, привязанный к ключу подписи в учетной записи на DSS |
Установка
Для использования myDSSSSD
необходимо добавить в проект:
- Фреймворк
- Ресурсы
- Корневые сертификаты
Добавление фреймворка
Swift Package Manager
Добавьте пакет myDSSSDK
из репозитория:
https://repo.paycontrol.org/git/ios/mydss.git
CocoaPods
Добавьте в файл Podfile
зависимости:
pod 'myDSSSDK', :git => 'https://repo.paycontrol.org/git/ios/mydss.git'
Ручная установка
Добавьте в проект myDSSSDK.xcframework и myDSSSDKSupplement.xcframework.
Carthage
Т.к. Carthage не поддерживает распространение бинарных фреймворков, данный метод мы не используем.
Ресурсы
Ресурсы лежат в бандле фреймворка в папке Resources
. Их необходимо прикрепить к приложению. Папку locale
нужно добавить с флагом Create folder references
.
При интеграции с помощью SPM/CocoaPods фреймворк можно найти в Project Navigator
.
- SPM:
Package Dependencies -> myDSSSDK -> Referenced Binaries -> myDSSSDK.xcframework
- CocoaPods:
Pods -> Pods -> myDSSSDK -> Frameworks -> myDSSSDK.xcframework
Корневые сертификаты
Для взаимодействия с серверами в ресурсы приложения в папку root-certs
нужно поместить сертификаты:
AppName.app/root-certs/
cert_1.crt
cert_2.cer
...
cert_N.certificate
Дополнительные действия
В процесс сборки таргета необходимо добавить новую скриптовую фазу (Build Phases -> New Run Script Phase
), которая запускает скрипт ConfigureApplication
внутри бандла фреймворка. Примеры для различных способов установки:
# SPM
sh ${BUILD_DIR%Build/*}/SourcePackages/artifacts/mydss/myDSSSDK.xcframework/Tools/ConfigureApplication
# CocoaPods
sh ${PROJECT_DIR}/Pods/myDSSSDK/myDSSSDK.xcframework/Tools/ConfigureApplication
# Ручная установка
sh myDSSSDK.xcframework/Tools/ConfigureApplication
Так же можно переместить содержимое папки myDSSSDK.xcframework/Tools
в любое удобное место и указать нужный путь.
Инициализация
Инициализировать библиотеку нужно перед вызовом внутренних функций SDK. Можно выставить уровень логирования и режим работы.
import UIKit
import myDSSSDK
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Установка уровня логирования и инициализация библиотеки
#if DEBUG
myDSS.setLogLevel([.debug, .keys])
myDSS.initialize(debugMode: true)
#else
myDSS.initialize()
#endif
...
}
}
При debugMode: true
проверка сертификатов на отзыв отключена, при запуске приложения будет пояляться экран с напоминанием о работе в режиме разработчика.
Объекты и функции
Для работы c УКЭП можно использовать:
- DSSUsersManager: Создаёт и управляет учётными записями пользователя на устройстве
- DSSOperationsManager: Получает информацию и подписывает операции и документы
- DSSCertificatesManager: Управляет сертификатами пользователей
- DSSDevicesManager: Управляет устройствами, привязанными к учётной записи
- DSSPolicyManager: Получает информацию о сервере DSS
Для работы c УНЭП можно использовать любые объекты и функции. Есть классы, которые включают в названии NonQual
, в них есть расширенный набор методов:
- DSSUsersManagerNonQual: Создаёт и управляет учётными записями пользователя на устройстве
- DSSOperationsManagerNonQual: Получает информацию и подписывает операции и документы
- DSSCertificatesManagerNonQual: Управляет сертификатами пользователей
- DSSKeysManagerNonQual: Управляет сертификатами пользователей.
Регистрация
Перед тем, как начать подписывать документы и операции, нужно создать пользователя. Есть 3 способа это сделать:
- Зарегистрировать онлайн
- Зарегистрировать с использованием QR-кода
- Привязать к другому устройству
Сценарии УКЭП
Зарегистрировать онлайн
DSSUsersManager.createDSSUser(
serviceURL: serverURL, // Адрес сервера DSS
name: name, // Имя для сохранения учетной записи
pushNotificationsData: data, // Данные для отправки пуш-уведомлений
deviceName: deviceName, // Отображаемое дружественное имя устройства
externalId: externalId, // Внешний идентификатор
alias: alias, // Человекочитаемый идентификатор устройства
requirePassword: requirePwd // Требуется ли установка пароля
) { result in
// Обрабатываем результат
switch result {
case let .success(user): ... // Пользователь создан и сохранён в хранилище
case let .failure(error): ...
}
}
Пользователь создан и сохранён в хранилище, его статус — .installed
. Теперь нужно дождаться, когда его статус на сервере DSS сменится на .notVerified
. Чтобы проверить текущий статус:
DSSUsersManager.updateStatus(
user: user
) { result in
switch result {
case let .success(user): // Сведения о пользователе обновлены
if user.state == .notVerified { // Проверяем статус
...
}
case let .failure(error): ...
}
}
Как только статус пользователя станет .notVerified
, мы сможем его верифицировать:
DSSUsersManager.acceptAccountChanges(
user: user
) { result in
switch result {
case let .success(user): ... // Пользователь подтверждён
case let .failure(error): ...
}
}
При верификации может потребоваться QR-код. Это зависит от настроек политики сервера.
Теперь статус пользователя .active
, с ним можно работать дальше.
Зарегистрировать с использованием QR-кода
DSSUsersManager.createDSSUserWithInitQR(
name: name, // Имя для сохранения учетной записи
pushNotificationsData: data, // Данные для отправки пуш-уведомлений
deviceName: deviceName, // Отображаемое дружественное имя устройства
externalId: externalId, // Внешний идентификатор
alias: alias, // Человекочитаемый идентификатор устройства
requirePassword: true // Требуется ли установка пароля
) { result in
switch result {
case let .success(user): ... // Пользователь создан и сохранён в хранилище
case let .failure(error): ...
}
}
При создании пользователя данным способом потребуется отсканировать QR-код, сгенерированый сервером. После создания статус пользователя — .active
, с ним можно работать дальше.
Привязать к другому устройству
Данный способ применяется, когда уже есть устройство с подтверждённым пользователем и нужно привязать новое устройство. Потребуется передать идентификатор данного пользователя на новое устройство.
Получить идентификатор можно у экземпляра пользователя:
// Идентификатор подтверждённого пользователя, к которому привязываем новое устройство
let uid = user.dssUserID
На новом устройстве вызываем метод:
DSSUsersManager.createDSSUserWithApproval(
serviceURL: serverURL, // Адрес сервера DSS
uid: uid, // Идентификатор пользователя, к которому привязываем устройство
name: name, // Имя для сохранения учетной записи
pushNotificationsData: data, // Данные для отправки пуш-уведомлений
deviceName: deviceName, // Отображаемое дружественное имя устройства
externalID: externalID, // Внешний идентификатор
alias: alias, // Человекочитаемый идентификатор устройства
requirePassword: true // Требуется ли установка пароля
) { result in
switch result {
case let .success(newUser): ... // Пользователь создан и сохранён в хранилище
case let .failure(error): ...
}
}
Мы создали нового пользователя, его статус — .approveRequired
, требуется подтверждение с основного устройства. На экране отобразится QR-код, который нужно отсканировать основным устройством.
На основном устройстве вызываем метод processAwaitingDevice
:
DSSDevicesManager.processAwaitingDevice(
user: user // Пользователь к которому привязываем устройство
) { result in
switch result {
case .success: ... // Новое устройство подтверждено/отклонено
case let .failure(error): ...
}
}
Далее, на новом устройстве проверяем статус:
DSSUsersManager.checkApprovalStatus(
user: newUser
) { result in
switch result {
case let .success(approvedUser): ... // Пользователь добавлен
case let .failure(error): ... // Пользователь отклонён/не обработан
}
}
Теперь новое устройство подтверждено, статус пользователя — .active
и с ним можно работать на новом устройстве.
Сценарии УНЭП
В сценариях УНЭП интерфейс SDK не используется, применяются дополнительные методы для работы с данными и используется класс DSSUsersManagerNonQual
.
Зарегистрировать онлайн
Для начала регистрации мы вызываем метод createDSSUser
:
DSSUsersManagerNonQual.createDSSUser(
serviceURL: serverURL, // Адрес сервера DSS
pushNotificationsData: data, // Данные для отправки пуш-уведомлений
deviceName: deviceName, // Отображаемое дружественное имя устройства
externalId: externalId, // Внешний идентификатор
alias: alias, // Человекочитаемый идентификатор устройства
) { result in
// Обрабатываем результат
switch result {
case let .success(user): ... // Пользователь создан
case let .failure(error): ...
}
}
После регистрации будет создан DSSUser
в статусе created
. Для дальнейшей работы его нужно сохранить в хранилище.
DSSUsersManagerNonQual.store(
user: user, // Новый пользователь
name: name, // Имя для хранения
password: password // Пароль для хранения
) { result in
// Обрабатываем результат
switch result {
case let .success(user): ... // Пользователь сохранён
case let .failure(error): ...
}
}
После успешного сохранения у пользователя сменится статус на .installed
. Далее, необходимо дождаться от сервера смены статуса на .notVerified
. Для проверки статуса нужно вызвать DSSUsersManager.updateStatus
:
DSSUsersManager.updateStatus(
user: user
) { result in
switch result {
case let .success(user): // Сведения о пользователе обновлены
if user.state == .notVerified { // Проверяем статус
...
}
case let .failure(error): ...
}
}
После смены статуса на .notVerified
необходимо вызвать метод DSSUsersManagerNonQual.acceptAccountChanges
. Данный метод требует, чтобы перед выполнением user.isReadyToSign == true
.
Так же, для присоединения может потребоваться QR-код: если user.verification == .qrRequired
. Его необходимо отсканировать и распарсить заранее, а содержимое передать в параметре verificationQRValue
.
Персональные данные клиента для присоединения можно проверить в DSSUser.profile
.
if !user.isReadyToSign {
// Предъявляем пароль
try DSSUsersManagerNonQual.submitPassword(user: user, password: password)
}
if user.verification == .qrRequired {
// Парсим QR-код
let qrValue = ...
// Проверяем
let verificationQRCode: DSSQRCodeVerification = try myDSS.analyzeQR(qrValue)
}
// Присоединяем к УЗ
DSSUsersManagerNonQual.acceptAccountChanges(
user: user, // Пользователь
verificationQRCode: verificationQRCode // QR-код
) { result in
// Обрабатываем результат
switch result {
case let .success(user): ... // Пользователь присоединён у УЗ
case let .failure(error): ...
}
}
Зарегистрировать с использованием QR-кода
Для начала регистрации с QR кодом его нужно получить и распарсить. После этого его можно передать в метод DSSUsersManagerNonQual.createDSSUserWithInitQR
.
QR-код может требовать активации. Код активации предоставляет сервер. Длину кода активации можно узнать, вызвав DSSPolicyManager.getDSSParams
, в переменной DSSParams.activationCodeLength
.
// Парсим QR-код
let registrationQRCode: DSSQRCodeKinit = try myDSS.analyzeQR(qrValue)
// Проверяем активацию
if !qrCode.isActivated {
let activatedQRCode = try myDSS.activate(qrCodeKinit: qrCode, code: activationCode)
}
// Регистрируем
DSSUsersManagerNonQual.createDSSUserWithInitQR(
qrCode: qrCode, // QR-код
pushNotificationsData: pushNotificationsData, // Данные для отправки пуш-уведомлений
deviceName: deviceName, // Отображаемое дружественное имя устройства
externalId: externalId, // Внешний идентификатор
alias: alias, // Человекочитаемый идентификатор устройства
) { result in
switch result {
case let .success(user): ... // Пользователь зарегистрирован
case let .failure(error): ...
}
}
После регистрации будет создан DSSUser
в статусе created
. Для дальнейшей работы его нужно сохранить в хранилище.
DSSUsersManagerNonQual.store(
user: user, // Новый пользователь
name: name, // Имя для хранения
password: password // Пароль для хранения
) { result in
// Обрабатываем результат
switch result {
case let .success(user): ... // Пользователь сохранён
case let .failure(error): ...
}
}
Привязать к другому устройству
Для привязки к другому устройству требуется знать uid
и serviceURL
уже существующего на другом устройствке пользователя. На новом устройстве нужно вызвать метод DSSUsersManagerNonQual.createDSSUserWithApproval
с этими данными:
DSSUsersManagerNonQual.createDSSUserWithApproval(
serviceURL: serviceURL, // Адрес для взаимодействия существующего пользователя
uid: userId, // UserID существующего пользователя
pushNotificationsData: pushNotificationsData, // Данные для отправки пуш-уведомлений
deviceName: deviceName, // Отображаемое дружественное имя устройства
externalId: externalId, // Внешний идентификатор
alias: alias, // Человекочитаемый идентификатор устройства
) { result in
switch result {
case let .success(user): ... // Пользователь зарегистрирован
case let .failure(error): ...
}
}
После регистрации будет создан DSSUser
в статусе created
. Для дальнейшей работы его нужно сохранить в хранилище.
DSSUsersManagerNonQual.store(
user: user, // Новый пользователь
name: name, // Имя для хранения
password: password // Пароль для хранения
) { result in
// Обрабатываем результат
switch result {
case let .success(user): ... // Пользователь сохранён
case let .failure(error): ...
}
}
После сохранения статус сменится на .approveRequired
— присоединение данного пользователя нужно подтвердить или отклонить на основном устройстве. Для этого нужно вызвать DSSDevicesManagerNonQual.approve
или DSSDevicesManagerNonQual.reject
.
Если для присоединения требуется отобразить персональные данные нового пользователя, то их можно найти в unapprovedUser.qrCode
. В сценарии УКЭП на новом устройстве отображается этот QR-код, старое устройство его сканирует и отображает содержимое на экране.
// Получаем список устройств
DSSDevicesManager.listDevices(
user: user // Основной активный пользователь
) { result in
switch result {
case let .success(devices): ... // Список устройств
case let .failure(error): ...
}
}
// Находим неподтверждённое устройство
let notApprovedDevice = devices.first(where { $0.state == .approveRequired })
if !user.isReadyToSign {
// Предъявляем пароль
try DSSUsersManagerNonQual.submitPassword(user: user, password: password)
}
// Подтверждаем присоединение
DSSDevicesManagerNonQual.approve(
device: notApprovedDevice, // Устройство, ожидающее подтверждения
user: user // Основной активный пользователь
) { result in
switch result {
case .success: ... // Присоединение устройства одобрено
case let .failure(error): ...
}
}
// ... или отклоняем
DSSDevicesManagerNonQual.reject(
device: notApprovedDevice, // Устройство, ожидающее подтверждения
user: user // Основной активный пользователь
) { [weak self] result in
switch result {
case .success: ... // Присоединение устройства отклонено
case let .failure(error): ...
}
}
После этого на новом устройстве нужно проверить и обновить статус пользователя:
DSSUsersManagerNonQual.checkApprovalStatus(
user: userToCheck // Новый пользователь
) { result in
switch result {
case let .success(user): ... // Пользователь присоединён
case let .failure(error):
switch error {
case DSSError.approveRequired: ... // По-прежнему ожидаем подтверждение на основном устройстве
default: ...
}
}
}
Устройства и пользователи
Управления пользователями на текущем устройстве осуществляется с помощью классов DSSUsersManager
и DSSUsersManagerNonQual
.
Управление устройствами осуществляется с помощью DSSDevicesManager
и DSSDevicesManagerNonQual
.
Пользователей, установленных на устройстве, можно получить в DSSUsersManager.users
.
Связанные устройства (в том числе и ожидающие подтверждения) можно получить, вызвав DSSDevicesManager.listDevices
:
DSSDevicesManager.listDevices(
user: user
) { result in
switch result {
case let .success(devices): ... // Все устройства
case let .failure(error): ...
}
}
Сертификаты
Для подтверждения операций и подписи документов нужны действующие сертификаты. Управлять сертификатами можно с помощью классов DSSCertificatesManager
и DSSCertificatesManagerNonQual
.
Получение списка сертификатов
DSSCertificatesManager.listCertificates(
user: user // Пользователь, запрашивающий информацию
) { result in
switch result {
case let .success(certificates): ... // Вернётся массив сертификатов и запросов
case let .failure(error): ...
}
}
// Отфильтруем выпущеные сертификаты
let issuedCertificates = certificates.filter { $0.type == .certificate }
Создание запроса на сертификат
// Получаем параметры сервера подписания
DSSPolicyManager.getDSSSignServerParams(
user: user // Пользователь, запрашивающий параметры сервера подписания
) { result in
switch result {
case let .success(signServerParams): ... // Вернутся параметры сервера подписания
case let .failure(error): ...
}
}
...
// Подготавливаем параметры. Значения даны для примера
let policyId = signServerParams.caPolicies.first(where: { $0.caType == .DSSOutOfBandEnroll }).id
let templateId = policy.ekuTemplates.first?.value.first
let testDN = ["2.5.4.3": "Test"]
// Создаём запрос на сертификат
DSSCertificatesManager.createCertificate(
user: user, // Пользователь, создающий запрос на сертификат
dn: testDN, // Различительное имя субъекта
templateId: templateId, // Идентификатор шаблона сертификата
caId: policyId // Идентификатор обработчика УЦ
) { result in
switch result {
case let .success(certRequest): ... // Вернётся запрос на сертификат
case let .failure(error): ...
}
}
Сертификат с ключами на устройстве
Для работы с ключами на устройстве необходимо сначала подписать запрос на сертификат у которого параметр isClient: true
. Данный запрос создаётся на сервере.
// Находим неподписаный запрос
let requestToSign = certificates.first {
$0.type == .request && $0.isClient = true && $0.dssCertificateId == nil
}
// Подписываем
DSSCertificatesManagerNonQual.sign(
certificateRequest: requestToSign,
user: user
) { result in
switch result {
case .success: ... // Запрос подписан
case let .failure(error): ...
}
}
После этого необходимо дождаться выпуска сертификата и установить его:
// Отсеиваем сертификаты, недоступные на этом устройстве
// (например, у которых запрос был подписан на другом устройстве)
let availableCertificates = certificates
.filter { $0.type == .certificate && DSSCertificatesManagerNonQual.checkIfAccessibleOnThisDevice(certificate: $0, for: user) }
// Получаем неустановленные сертификаты
let certificateToInstall = availableCertificates
.filter { $0.isClient && !DSSCertificatesManagerNonQual.checkIfInstalled(certificate: $0, for: user) }
.first
// Устанавливаем
DSSCertificatesManagerNonQual.install(
certificate: certificateToInstall,
user: user
) {
switch result {
case .success: ... // Сертификат установлен
case let .failure(error): ...
}
}
Теперь данный сертификат можно использовать для подтверждения операций и подписания документов.
Управление сертификатами
С остальными доступными методами можно ознакомиться в описании API DSSCertificatesManager и DSSCertificatesManagerNonQual.
Операции и документы
Получение списка операций
DSSOperationsManager.getOperationsList(
user: user, // Пользователь, для которого проверяются операции
operationType: nil, // Фильтр операций по типу
operationID: nil // Фильтр по идентификатору
) { result in
switch result {
case let .success(operations): ... // Получен список операций
case let .failure(error): ...
}
}
Подтверждение операции
DSSOperationsManager.confirmOperation(
operation: operation, // Операция для подтверждения
user: user, // Пользователь, подтверждающий операцию
signMode: .online // Способ подтверждения. В данном случае — online
) { result in
switch result {
case let .success(approveRequest): ... // Операция подтверждена, вернули
case let .failure(error): ...
}
}
Загрузка документов
DSSOperationsManager.uploadDocument(
documentContent: documentData, // Бинарные данные документа
title: documentTitle, // Заголовок документа
snippetTemplate: documentSnippet, // HTML-сниппет документа
previewTemplate: documentPreview, // HTML-превью документа
user: user // Пользователь-владелец документа
) { result in
switch result {
case let .success(documentID): // Вернётся идентификатор документа
case let .failure(error): ...
}
}
Подпись документов
// Формируем параметры подписания
let templateId = signServerParams.processingTemplates.first.id
let certificateId = certificate.dssCertificateId
let signParams = SignParams(
signTemplateId: templateId, // Идентификатор шаблона подписи
certId: certificateId, // Идентификатор сертификата, которым будем подписывать
pinCode: "" // Пин-код от сертификата. Если пин-код не установлен, то укажите пустую строку
)
DSSOperationsManager.signDocuments(
documentsIDs: confirmedDocuments, // Документы для подписания
user: user, // Пользователь, подписывающий документы
signParams: signParams // Параметры подписания
) { result in
switch result {
case let .success(signingResults): // Вернутся результаты подписания
case let .failure(error): ...
}
}
Дополнительная информация
Параметры подписания
Для параметров подписания нужно получить параметры сервера подписания и идентификатор сертификата:
// Получаем параметры сервера подписания
DSSPolicyManager.getDSSSignServerParams(
user: user // Пользователь, запрашивающий параметры сервера подписания
) { result in
switch result {
case let .success(signServerParams): ... // Вернутся параметры сервера подписания
case let .failure(error): ...
}
}
...
// Получаем сертификат
DSSCertificatesManager.listCertificates(
user: user // Пользователь, запрашивающий сертификаты
) { result in
switch result {
case let .success(certificates): ... // Вернутся сертификаты и запросы на сертификаты
case let .failure(error): ...
}
}
...
// Выбираем сертификат, например, первый в списке. Запросы на сертификат игнорируем
let certificate: DSSCertificate = certificates.first(where: {
$0.type == .certificate
})
...
С остальными доступными методами можно ознакомиться в описании API DSSOperationsManager и DSSOperationsManagerNonQual.
Резервное копирование
Создание и восстановление резервных копий может быть полезно при смене устройства или если пользователь забыл пароль к своему профилю. Для создания резервной копии и восстановления понадобится придумать пароль для восстановления — recovery password
.
Резервная копия профиля пользователя
// Создание резервной копии
let userBackupData: Data = try DSSUsersManagerNonQual.createBackup(
user: user,
recoveryPassword: recoveryPassword
)
// Восстановление
let restoredUser: DSSUser = try DSSUsersManagerNonQual.restoreFromBackup(
backupData: userBackupData,
recoveryPassword: recoveryPassword
)
DSSUsersManagerNonQual.store(
user: restoredUser,
name: username,
password: password
) { storingResult in
switch result {
case let .success(storedUser): // Сохранённый пользователь
case let .failure(error): ...
}
}
Резервная копия userBackupData
после создания находится в памяти. Необходимо самостоятельно сохранить её на устройство любым возможным способом.
Резервая копия ключей подписи на устройстве
// Создание резервной копии
let keyBackupData: Data = try DSSKeysManagerNonQual.createBackup(
keyInfo: keyInfo,
recoveryPassword: recoveryPassword
)
// Восстановление
let restoredKeyInfo: DSSKeyInfo = try DSSKeysManagerNonQual.restoreFromBackup(
backupData: keyBackupData,
recoveryPassword: recoveryPassword
)
Резервная копия keyBackupData
после создания находится в памяти. Необходимо самостоятельно сохранить её на устройство любым возможным способом.
Архивирование ключей подписи на сервере DSS
Ключи, созданные с флагом isExportable
могут быть архивированы на сервере DSS. Начиная с версии 2.1.267, можно создать ключи с таким флагом:
// При подписи сертификата
DSSCertificatesManagerNonQual.sign(
certificateRequest: request,
user: user,
isExportable: true,
completion: { ... }
)
// При создании ключей
let keyInfo = try DSSKeysManagerNonQual.createKeyPair(
for: user,
pin: pin,
isExportable: true
)
Для создания резервной копии сертификат ключей подписи нужно передать в метод DSSCertificatesManagerNonQual.exportPfx(...)
. Метод требует ПИН-код от контейнера с ключевой информацией. Если передать nil
, то будет использован пин-код по умолчанию.
После архивации ключей их можно восстановить как на том же самом устройстве, так и на другом устройстве, где есть объект DSSUser
, привязанный к той же учётной записи.
// Экспорт
// Получаем список сертификатов
DSSCertificatesManager.listCertificates(
user: user,
сompletion: { ... }
)
// // Выбираем сертификат для экспорта
let certificateToExport = certificatesList.first {
$0.type == .certificate &&
!$0.isArchived &&
DSSCertificatesManagerNonQual.checkIfAccessibleOnThisDevice(certificate: $0, for: user)
}
// Выгружаем архив
DSSCertificatesManagerNonQual.exportPfx(
user: user,
certificate: certificateToExport, // Сертификат, ключи которого архивируем
pin: pin, // Пин-код от контейнера ключей
pfxPin: nil, // Дополнительный пин-код, на котором будет зашифрован архив (опционально)
completion: { ... }
)
...
// Импорт
// Получаем список сертификатов
DSSCertificatesManager.listCertificates(
user: user,
completion: { ... }
)
// Находим доступный для импорта сертификат
let archivedCertificate = certificatesList.first {
$0.type == .certificate &&
$0.isArchived
}
// Импортируем. После импорта на устройство будут загружены ключи
DSSCertificatesManagerNonQual.importPfx(
user: user,
certificate: archivedCertificate,
pin: newPin, // Новый пин-код для контейнера ключей
pfxPin: nil, // Пин-код для расшифрования архива
completion: { ... }
)
Для удаления архивной копии на сервере доступен метод DSSCertificatesManagerNonQual.removePfx(user:certificate:completion:)
.
Обновление профиля устройства
Для обновление профиля устройства и симметричных ключей можно воспользоваться методом DSSUsersManager.renew(user:callback:)
. После использования метода старый DSSUser
будет заменён новым.
let userToRenew: DSSUser = ... // Пользователь, которого хотим обновить
DSSUsersManagerNonQual.renew(
user: userToRenew
) { renewingResult in
switch result {
case let .success(renewedUser): // Обновлённый пользователь
case let .failure(error): ...
}
}
Работа с NFC картами и токенами Рутокен
Для работы с NFC требуется отдельная сборка myDSSSDK_NFC
Установка сборки с NFC
Swift Package Manager
Добавьте пакет myDSSSDK_NFC
из репозитория:
https://repo.paycontrol.org/git/ios/mydssnfc.git
CocoaPods
Добавьте в файл Podfile
зависимости:
pod 'myDSSSDK_NFC', :git => 'https://repo.paycontrol.org/git/ios/mydssnfc.git'
Ручная установка
Добавьте в проект myDSSSDK_NFC.xcframework.
Ресурсы для NFC
Необходимо добавить в проект ресурсы аналогично инструкции.
Настройка проекта
Для работы с картами и токенами Рутокен нужно дополнительно настроить проект по документации Рутокен.
Убедитесь, что в проект добавлены все необходимые библиотеки и указаны все идентификаторы карт.
Для работы с NFC токенами доступно 2 сценария:
Импорт сертификата с карты
Для импорта сертификата сначала нужно получить с карты все доступные сертификаты, а потом нужный загрузить на сервер.
При перечислении сертификатов будет возвращён массив [Result<DSSCertificate, Error>]
. Считывание данных сертификата — отдельная операция. Поэтому возвращаются результаты всех считываний по-отдельности.
После перечисления необходимо выбрать нужный сертификат и выгрузить его в DSS. Для этого нужно передать его в метод DSSCertificateManager.setCertificate(user:certificate:)
.
После выгрузки сертификата NFC-токен можно использовать для подписании документов.
// Перечисление доступных сертификатов
DSSCertificatesManager.listExternalCertificates { listingResult in
switch listingResult {
case let .success(certificatesResults): ... // Результаты считывания сертификатов
case let .failure(error): ... // Ошибка при работе с картой
}
}
let certificateToImport = ... // Выбираем сертификат для импорта
// Загружаем сертификат на сервер
DSSCertificatesManager.setCertificate(
user: user,
certificate: certificateToImport
) { settingResult in
switch settingResult {
case .success: ... // Выгрузили и установили на сервере
case let .failure(error): ... // Ошибка загрузки
}
}
Создание ключей на карте
Создание ключей происходит при вызове метода DSSCertificatesManagerNonQual.signCertificateRequest
. Место для создания ключей выбирается параметром keysSource
. По-умолчанию выбрано KeysSourceIdentifier.localGeneric
— ключи создаются на устройстве. Для NFC-токенов необходимо выбрать KeysSourceIdentifier.rutokenNFC
.
После создания ключей и подписания запроса на сертификат, нужно дождаться выпуска сертификата. Текущие сертификаты проверяем методом DSSCertificatesManager.listCertificates(user:callback:)
.
Как только сертификат будет выпущен, его можно установить на устройство методом DSSCertificatesManager.install
.
После установки сертификата NFC-токен можно использовать для подписания документов.
// Подписываем запрос на сертификат
public static func sign(
certificateRequest: certificateRequest,
user: user,
keysSource: .rutokenNFC // Используем NFC-токен Рутокен
) { signingResult in
switch signingResult {
case .success: ... // Запрос подписан, ключи созданы
case let .failure(error): ... // Ошибка при работе с карто
}
}
// Получаем список сертификатов
let certificates = DSSCertificatesManager.listCertificates(...)
// Проверяем, выпущен ли сертификат
let certificateToInstall: DSSCertificate = certificates.first {
// Сертификат активный
$0.state == .active &&
// Ключи хранятся на клиенте
$0.isClient &&
// Ключи хранятся на текущем устройстве
DSSCertificatesManagerNonQual.checkIfAccessibleOnThisDevice(certificate: $0, for: user) &&
// Сертификт не установлен
!DSSCertificatesManagerNonQual.checkIfInstalled(certificate: $0, for: user)
}
// Устанавливаем выпущеный сертификат
public static func install(
certificate: certificateToInstall,
user: user
) { installingResult in
switch signingResult {
case .success: ... // Сертификат установлен
case let .failure(error): ... // Ошибка при установке сертификата
}
}
Логирование
У фреймворка есть возможность подключить внешний логгер. Для этого логгер должен наследовать протокол DSSLoggerProtocol
.
public protocol DSSLoggerProtocol {
func debug(_ message: String, category: DSSLoggingCategory)
func error(_ message: String, category: DSSLoggingCategory)
func sensitive(_ message: String, category: DSSLoggingCategory)
}
/// Пример логгера
struct MyLogger: DSSLoggerProtocol {
func debug(_ message: String, category: DSSLoggingCategory) {
...
}
func error(_ message: String, category: DSSLoggingCategory) {
...
}
func sensitive(_ message: String, category: DSSLoggingCategory) {
...
}
}
После определения логгера, его нужно передать в метод myDSS.setLogger(...)
:
let myLogger = MyLogger()
// myDSS.setLogLevels([.debug, .keys]) <- Теперь можно убрать из кода приложения
myDSS.setLogger(myLogger, options: [.debug, .sensitive])
После этого сообщения из SDK будут передаваться в установленный логгер.
Сохранение логов в файл
В SDK нет встроенных методов для сохранения логов файл. Но можно подключить стороннюю библиотеку, например CocoaLumberjack:
import CocoaLumberjack
import CocoaLumberjackSwift
...
// Настраиваем CocoaLumberjack
let fileLogger = DDFileLogger()
fileLogger.logFileManager.maximumNumberOfLogFiles = 2 // Храним только 2 файла
fileLogger.maximumFileSize = 256 * 1024 // не более 256kb каждый
fileLogger.rollingFrequency = 60 * 60 * 24 // с сообщениями за последние 24 часа
DDLog.add(fileLogger)
...
// Описываем методы логгера
final class MyLogger: DSSLoggerProtocol {
func debug(_ message: String, category: DSSLoggingCategory) {
DDLogInfo("[SDK][\(category)] \(message)")
}
func error(_ message: String, category: DSSLoggingCategory) {
DDLogError("[SDK][\(category)] \(message)")
}
func sensitive(_ message: String, category: DSSLoggingCategory) {
#if DEBUG
DDLogInfo("[SDK][Sensitive][\(category)] \(message)")
#endif
}
}
...
// Назначаем логгер
myDSS.setLogger(MyLogger(), options: [.debug, .sensitive])
Чтобы получить логи, используем API CocoaLumberjack:
fileLogger
.logFileManager
.sortedLogFileInfos
.compactMap { (info: DDInfo) -> Data in
FileManager.default.contents(atPath: info.filePath) // Читаем логи из файла
}
.forEach { (logData: Data) in
... // Обрабатываем логи каждого файла
}
Оформление интерфейса
За внешний вид интерфейса отвечают файлы экранов *.xib
и ассеты myDSSSDK_Assets.xcassets
.
Xib файлы
Xib | Описание |
---|---|
myDSSSDK_MainNavigationController.xib | Контроллер навигации всех экранов |
myDSSSDK_MainNavigationController.xib | Контроллер навигации всех экранов |
myDSSSDK_CameraNavigationController.xib | Контроллер навигации экрана с камерой |
myDSSSDK_OverlayViewController.xib | Оверлей с индикатором активности. Закрывает экран приложения при взаимодействии с фреймворком. |
myDSSSDK_CameraViewController.xib | Экран с камерой |
myDSSSDK_ApprovingQRViewController.xib | Экран с QR-кодом для присоединения другого устройства |
myDSSSDK_PasswordViewController.xib | Экран с вводом и созданием ПИН-кода |
myDSSSDK_BiometrySuggestionViewController.xib | Экран с предложением использовать биометрию |
myDSSSDK_DocumentsListViewController.xib | Экран со списком документов |
myDSSSDK_DocumentPreviewCell.xib | Ячейка с превью документа |
myDSSSDK_PDFViewController.xib | Просмотр PDF документа |
myDSSSDK_AcceptAccountActionsViewController.xib | Действия при подтверждении присоединения к УЗ |
myDSSSDK_ProcessDeviceActionsViewController.xib | Действия при подтверждении присоединения к другому устройству |
myDSSSDK_DocumentsActionsViewController.xib | Действия при подписании документов |
myDSSSDK_OperationActionsViewController.xib | Действия при работе с операцией с документами |
myDSSSDK_InfoViewController.xib | Общие экран с таблицей значений |
myDSSSDK_KeyValueCell.xib | Ячейка с заголовком и значением |
RndmBioViewController.xib | Генератор случайных чисел CPROCSP |
RndmBioViewControllerIPhone.xib | Генератор случайных чисел CPROCSP для iPhone |
Ассеты
В ассетах myDSSSDK_Assets.xcassets
лежат цвета и изображения по-умолчанию.
Совместимость с DSSAppearance
Для обратной совместимости созданы классы с префиксом Custom
. Они применяют к элементам стили, описаные в myDSS.appearance.*
:
- CustomView
- CustomImageView
- CustomLabel
- CustomButton
- CustomKeyboardButton
- CustomGradientView
- CustomPDFView
Если сбросить класс компонентов на стандартный (UIView
, UILabel
, ...), то стили DSSAppearance
применяться не будут.
Работа с распространёнными ошибками
DSSError
Данные ошибки возвращает SDK.
При возникновении ошибки DSSError.networkError
, необходимо смотреть на параметр code
:
12002
The request has timed out
Превышено ожидание ответа от сервера.
12007
The server name could not be resolved.
Не удаётся подключиться к ендпоинту. Возможные причины: - отсутствие интернета - некорректный адрес сервера - сервер недоступен
12175
WinINet failed to perform content decoding on the response. For more information, see the Content Encoding topic.
Возникла проблема с сертификатами. Возможные причины:
- В бандле приложения нет папок
root-certs/prod
с корневыми сертификатами сервера - Корневые сертификаты имеют некорректный формат. Например, у них расширение
.cer
, а не.crt
. Либо они не в формате PEM. - Среди сертификатов нет подходящих для работы с данным сервером
- Сервер не настроен должным образом
Работа с UI
Методы SDK в которых есть интерфейс, могут возвращать ошибку DSSError.canceled
— она означает, что пользователь закрыл UI.
DSSNetworkError
Данные ошибки возвращает сервер. При обработке статуса пользователя можно наткнуться на следующие ошибки:
.userNotFound
: Пользователь был удалён на сервере.deviceBlocked
: Устройство заблокировано на сервере.keyExpiredOrNotYetValid
: Срок действия ключей истёк ещё не наступил
Т.е. например при вызове DSSUsersManager.updateStatus
мы не получим DSSUser
со статусом .expired
, а метод вернёт ошибку DSSNetworkError.keyExpiredOrNotYetValid
История изменений
2.1.324
- Исправлена проблема с поиском ресурсов SDK
2.1.323
- SDK теперь делится на версию NFC и без NFC.
- Исправлена ошибка «Неверный HMAC», возникающая на iOS 17.
- При подписании документов кнопка подписать будет неактивной, если у пользователя нет сертификатов.
- Добавлена дополнительная информация в структуру
DSSOperationHistory
. - Внутренние оптимизации и улучшения.
2.1.312
- Добавлена поддержка работы с NFC-токенами.
- Обновили методы
DSSCertificatesManagerNonQual
для работы с NFC. - Исправлено дублирование страниц PDF документов.
- Добавлена поддержка ошибок
prov_type_not_defined
,invalid_chv
при работе с NFC. - Исправлена видимость ошибок
CPROCSPError
. - Исправлена кнопка «Закрыть» при подписании запроса на сертификат.
- Исправления локализации.
- Исправлены ошибки при работе с новыми экранами ввода пин-кодов и паролей.
- Исправлено падение на iPad при попытке загрузить документ.
- Внутренние оптимизации и улучшения.
2.1.288
- Небольшие исправления локализации
2.1.287
- Добавлена поддержка парольных политик. Теперь пароль может содержать буквы и символы. Для сложных паролей добавлен отдельный тип экранов.
- Если пользователь запретил доступ к камере, то теперь ему будет предложено перейти в настройки.
- Исправлены условия проверки активных сертификатов при подписании
2.1.276
- Добавлена проверка сертификатов при подписании документов. Если нет активных сертификатов, то кнопка «Подписать» не будет активной.
2.1.274
- Добавлена возможность скачать документ при подписании
2.1.272
- Ошибка
DSSNetworkError
переименована вDSSServerError
. - Добавлена серверная ошибка
operation_expired
.
2.1.271
- Обновлены параметры ошибки
DSSError.confirmationError
иDSSError.signingError
.