跳转至

macOS 桌面客户端 —— 开发者指南

eID 平台的 macOS(SwiftUI)桌面客户端 —— 该桌面应用通过 e-ID Mongolia 手机应用 完成二维码 / 登记号推送登录,并可为 PDF 加盖合格电子签名。源码位置: desktop/macos-app/(XcodeGen 工程 eIDMongolia)。

First-party 客户端。 与 iOS 应用遵循完全相同的原则,本客户端并非 RP —— 客户端不持有 RP secret、RP UUID 或注册信息。所有调用都经由自有 Web 后端的 公开 /api/* 路由(与浏览器所用路径完全一致)。Go RP-API(/v3/*)的 RP_API_SECRET 仅由 Web 服务端持有(web/src/lib/rpclient.ts)。因此这不是 RP 集成—— RP 集成由 Web 服务端自身完成,桌面端只是使用其公开前端。

1. 功能

  • 登录 —— 通过二维码或登记号 / civil-ID 号发送推送,由手机应用使用 PIN1 确认。 Web 服务端会对签名进行密码学校验,从证书 subject 中提取姓名、civil-ID 号与 documentNumber 并返回 —— 桌面端自身不解析证书。
  • PDF 签名 —— 选择 PDF,在本地计算其 SHA-256 摘要,并在手机应用中使用 PIN2 签名。 加盖签章后的 PDF(PAdES/PKCS#7 + 验证页)保存到 ~/Downloads
  • 身份句柄 —— 不存在 Bearer 会话;登录获得的 documentNumber 作为身份句柄, 保存在钥匙串中(恢复时需通过 Touch ID 校验)。

2. 架构

客户端调用 Web 应用的公开 /api/* 路由(web/src/app/api/*)。 所使用的路由(依据源码 Core/Network/Endpoints.swiftCore/Network/APIClient.swift):

路由 方法 用途
/api/start POST 启动二维码会话 → {sessionId, qr, deviceLinkBase, vc, pollToken}
/api/login-notify POST 按登记号 / civil-ID 号推送 → {sessionId, vc, pollToken}(限流 60 秒 3 次)
/api/status GET ?sessionId=&pollToken= 长轮询 —— 服务端保持约 1 秒,客户端重试
/api/sign-pdf-start POST {etsi, digestB64, fileName, callbackUrl} → PIN2 推送 → {sessionId, vc, pollToken}(每个 etsi 限流 60 秒 3 次)
/api/sign-pdf-download POST multipart file + sessionId + pollToken → 加盖签章后的 PDF 字节
/api/health GET 检查服务端健康状态

注意。 pollTokenstart / login-notify / sign-pdf-start 的响应返回, 且对 /api/status/api/sign-pdf-download必需sessionId 会暴露在二维码中, 因此仅凭它无法读取个人信息 —— pollToken 只签发给会话发起方 (Endpoints.swift,第 18-23 行)。

客户端中不存在 secret、RP UUID 与 RP 名称(Core/Network/AppConfig.swift)。 服务端基础 URL 的取值优先级(首个非空者生效):

来源 说明
UserDefaults["API_BASE_URL_OVERRIDE"] 通过设置界面配置
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 —— 从 project.yml 生成 .xcodeproj
  • 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 —— 解析本地 GeregeTokenKit SPM 包需要 scheme。

本地测试

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

若需指向其他地址,请使用 设置 → 服务器(或环境变量 API_BASE_URL)。 手机应用也必须指向同一台 Go 服务器。

5. 主要流程

登录(Features/Login/LoginView.swift

二维码(initQR): 1. POST /api/start{sessionId, qr, vc, pollToken}。 2. 使用 CoreImage 将 qr 值(= sessionId)渲染为二维码;在界面上显示验证码 vc。 3. 用手机扫描二维码 → 使用 PIN1 确认。

登记号推送(initiateLogin): 1. 用户输入登记号 / civil-ID 号(register,转为大写)。 2. POST /api/login-notify {register}{sessionId, vc, pollToken};推送发送至手机。

共同的收尾(APIClient.waitForAuth): - 以约 400 毫秒的间隔重复调用 GET /api/status?sessionId=&pollToken= 直至 COMPLETE (服务端每次保持 1 秒)。 - 当返回 COMPLETE + OK 时,Web 服务端从证书 subject 中提取 name / idNumberserialNumber → civil-ID 号)。 - 结果以 StoredIdentity 形式保存到钥匙串 (documentNumberfullNamecivilIDcertificateLevel)。

PDF 签名(Features/Sign/SignView.swift

与网页演示页面(web/src/app/demo/page.tsx)的流程完全一致:

  1. 选择 PDF(最大 25 MB)。源 PDF 的 SHA-256 摘要在客户端本地计算CryptoKit.SHA256)—— 与随后被加盖签章的字节完全相同。
  2. POST /api/sign-pdf-start {etsi, digestB64, fileName, callbackUrl:""}{sessionId, vc, pollToken}etsi = 登录时获得的 civil-ID 号 (civilID,回退到 nationalID,转为大写)。PIN2 推送发送至手机。 callbackUrl 为空 —— 由于手机是另一台设备,不存在 Web2App 返回。
  3. 轮询 GET /api/status?sessionId=&pollToken=(与认证同路径)→ COMPLETE/OK。
  4. POST /api/sign-pdf-download(multipart file + sessionId + pollToken) → 加盖签章后的 PDF 字节 → ~/Downloads/<名称>_signed.pdf(文件名冲突时追加数字后缀)。

SEC-3:在启动签名流程前会执行 SecurityGuard.enforce() —— 防篡改 / 反调试检查(仅在 Release 中启用)。

6. USB token kit

desktop/gerege-token-kit/ 是面向 FEITIAN USB 令牌的无依赖本地 SPM 包 (本地 PKCS#11/APDU,不连接服务器)。macOS 应用会自动将 ../gerege-token-kit 作为路径包解析,并在 Core/Token/TokenManagerTokenProvisioner)与 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