Десктопный клиент для macOS — руководство разработчика¶
Десктопный клиент для macOS (SwiftUI) платформы eID — приложение, которое выполняет
вход по QR-коду или push по регистрационному номеру через мобильное приложение
e-ID Mongolia и накладывает квалифицированную электронную подпись на PDF. Исходники:
desktop/macos-app/ (проект XcodeGen eIDMongolia).
First-party клиент. По тому же принципу, что и приложение для iOS, этот клиент не является RP — в нём нет ни RP-секрета, ни RP UUID, ни регистрации. Все вызовы идут через публичные маршруты
/api/*собственного веб-бэкенда (ровно тот же путь, что использует браузер).RP_API_SECRETдля Go RP-API (/v3/*) хранится только на веб-сервере (web/src/lib/rpclient.ts). Поэтому это НЕ интеграция RP — интеграцию RP выполняет сам веб-сервер, а десктоп лишь использует его публичный фронтенд.
1. Что он делает¶
- Вход — отправляет push по QR-коду или по регистрационному / civil-ID номеру, а мобильное
приложение подтверждает его с помощью PIN1. Веб-сервер криптографически проверяет подпись,
извлекает из subject сертификата имя, civil-ID номер и
documentNumberи возвращает их — десктоп сам сертификат не разбирает. - Подписание PDF — выберите PDF, его дайджест SHA-256 вычисляется локально, а подпись
выполняется с PIN2 в мобильном приложении. Проштампованный PDF (PAdES/PKCS#7 + страница
проверки) сохраняется в
~/Downloads. - Handle идентичности — Bearer-сессии нет; полученный при входе
documentNumberстановится handle идентичности и сохраняется в Keychain (при восстановлении — проверка Touch ID).
2. Архитектура¶
Клиент обращается к публичным маршрутам /api/* веб-приложения (web/src/app/api/*).
Используемые маршруты (по исходникам Core/Network/Endpoints.swift, Core/Network/APIClient.swift):
| Маршрут | Метод | Назначение |
|---|---|---|
/api/start |
POST | Старт QR-сессии → {sessionId, qr, deviceLinkBase, vc, pollToken} |
/api/login-notify |
POST | Push по регистрационному / civil-ID номеру → {sessionId, vc, pollToken} (rate limit 3 за 60 с) |
/api/status |
GET | ?sessionId=&pollToken= long-poll — сервер держит ~1 с, клиент повторяет |
/api/sign-pdf-start |
POST | {etsi, digestB64, fileName, callbackUrl} → push с PIN2 → {sessionId, vc, pollToken} (rate limit 3 за 60 с на etsi) |
/api/sign-pdf-download |
POST | multipart file + sessionId + pollToken → байты проштампованного PDF |
/api/health |
GET | Проверка состояния сервера |
Примечание.
pollTokenвозвращается в ответахstart/login-notify/sign-pdf-startи обязателен для/api/statusи/api/sign-pdf-download.sessionIdраскрывается в QR-коде, поэтому сам по себе он не даёт права читать персональные данные —pollTokenвыдаётся только инициатору сессии (Endpoints.swift, строки 18-23).
Секрета, RP UUID и имени RP в клиенте нет (Core/Network/AppConfig.swift).
Приоритет источников базового URL сервера (побеждает первый непустой):
| Источник | Описание |
|---|---|
UserDefaults["API_BASE_URL_OVERRIDE"] |
Задаётся в UI настроек |
env API_BASE_URL |
Переменная окружения |
| По умолчанию | DEBUG: http://localhost:3000, Release: https://eidmongolia.mn |
3. Требования¶
- macOS 14+ (deployment target 14.0)
- Xcode 16+, Swift 5.10
brew install xcodegen— генерирует.xcodeprojизproject.yml- Зависимости SPM: Sparkle 2 (автообновление),
../gerege-token-kit(пакет по локальному пути)
4. Сборка и запуск¶
cd desktop/macos-app
xcodegen generate
xcodebuild -project eIDMongolia.xcodeproj -scheme eIDMongolia \
-configuration Debug -destination 'platform=macOS,arch=arm64' build
open eIDMongolia.xcodeproj # ⌘R
Важно. Используйте
-scheme eIDMongolia, а не-target— схема необходима для разрешения локального SPM-пакетаGeregeTokenKit.
Локальное тестирование¶
Сервер по умолчанию для DEBUG-сборки — web (:3000). Запустите и Go API, и web:
cd ../../server && SMARTID_RP_API_SECRET= go run ./cmd/smartid # Go API :8080
cd ../../web && npm run dev # web :3000
Чтобы указать другой адрес, используйте Настройки → Сервер (или env API_BASE_URL).
Мобильное приложение должно указывать на тот же Go-сервер.
5. Основные сценарии¶
Вход (Features/Login/LoginView.swift)¶
QR (initQR):
1. POST /api/start → {sessionId, qr, vc, pollToken}.
2. Отрисовать значение qr (= sessionId) в виде QR через CoreImage; показать на экране
код подтверждения vc.
3. Отсканировать QR телефоном → подтвердить с PIN1.
Push по регистрационному номеру (initiateLogin):
1. Пользователь вводит регистрационный / civil-ID номер (register, в верхнем регистре).
2. POST /api/login-notify {register} → {sessionId, vc, pollToken}; на телефон уходит push.
Общее завершение (APIClient.waitForAuth):
- Повторяет GET /api/status?sessionId=&pollToken= с интервалом ~400 мс до COMPLETE
(на стороне сервера удержание 1 с).
- При COMPLETE + OK веб-сервер извлекает из subject сертификата name / idNumber
(serialNumber → civil-ID номер).
- Результат сохраняется в Keychain как StoredIdentity
(documentNumber, fullName, civilID, certificateLevel).
Подписание PDF (Features/Sign/SignView.swift)¶
Ровно тот же сценарий, что и на веб-демо-странице (web/src/app/demo/page.tsx):
- Выберите PDF (до 25 МБ). Дайджест SHA-256 исходного PDF вычисляется локально
на клиенте (
CryptoKit.SHA256) — это ТЕ ЖЕ байты, которые будут проштампованы. POST /api/sign-pdf-start {etsi, digestB64, fileName, callbackUrl:""}→{sessionId, vc, pollToken}.etsi= civil-ID номер, полученный при входе (civilID, fallbacknationalID, в верхнем регистре). На телефон уходит push с PIN2.callbackUrlпуст — телефон является отдельным устройством, поэтому возврата Web2App нет.- Опрос
GET /api/status?sessionId=&pollToken=(тот же путь, что и для auth) → COMPLETE/OK. POST /api/sign-pdf-download(multipartfile+sessionId+pollToken) → байты проштампованного PDF →~/Downloads/<имя>_signed.pdf(при совпадении имени добавляется числовой суффикс).
SEC-3: перед стартом сценария подписания выполняется
SecurityGuard.enforce()— проверки на вмешательство/отладку (активны в Release).
6. USB token kit¶
desktop/gerege-token-kit/ — локальный SPM-пакет без зависимостей для USB-токенов
FEITIAN (локальный PKCS#11/APDU, без подключения к серверу). Приложение для macOS
автоматически подключает ../gerege-token-kit как path-пакет и использует его в
Core/Token/ (TokenManager, TokenProvisioner) и в разделе Tokens.
Подробнее: USB token kit.
7. Исходники (ссылки)¶
- Приложение macOS:
https://github.com/gerege-systems/eid-platform-mn/tree/main/desktop/macos-app - Сетевой слой:
https://github.com/gerege-systems/eid-platform-mn/blob/main/desktop/macos-app/Core/Network/Endpoints.swift - HTTP-клиент:
https://github.com/gerege-systems/eid-platform-mn/blob/main/desktop/macos-app/Core/Network/APIClient.swift - Вход:
https://github.com/gerege-systems/eid-platform-mn/blob/main/desktop/macos-app/Features/Login/LoginView.swift - Подписание:
https://github.com/gerege-systems/eid-platform-mn/blob/main/desktop/macos-app/Features/Sign/SignView.swift - Детали по компонентам:
desktop/macos-app/CLAUDE.md, усиление безопасности:desktop/macos-app/SECURITY-HARDENING.md