Агуулгыг алгасах

eID Mongolia — iOS SDK (GeregeSmartID)

GeregeSmartID бол eID платформын iOS Swift SDK — иргэний утсан дээр distributed keygen, 2-of-2 threshold ECDSA гарын үсэг, App Attest, Keychain хадгалалтыг гүйцэтгэнэ. Крипто нь Go серверийн internal/crypto-той байт-нийцтэй (golden-vector-аар батлагдсан), тиймээс threshold-ECDSA / Paillier / Schnorr / PDL урсгал хоёр талд яг таарна.

Эх код: ios/ios-sdk/Sources/GeregeSmartID/GeregeSmartIDClient.swift (orchestrator), SecureKeyStore.swift (Keychain + PIN), PhoneCrypto.swift, Paillier.swift, Proofs.swift, AppAttestManager.swift.

Дугаарын нэр томьёо

Иргэн бүр нэг documentNumber (төхөөрөмжийн UUID)-тай ба ХОЁР тусдаа threshold түлхүүртэй: authentication (PIN1, нэвтрэх, clientAuth) ба signing (PIN2, гарын үсэг, non-repudiation). SDK-д эдгээр нь PINSlot.auth / PINSlot.sign (docs/IDENTIFIERS.md, docs/EID_2CERT_MILESTONE.md).


1. Товч танилцуулга

GeregeSmartIDClient нь backend (жишээ нь rp-api.eidmongolia.mn)-ийн enrollment ба threshold-signing endpoint-уудтай харьцаж, App Attest + крипто урсгалыг зохицуулна. Гол чадвар:

Үйлдэл Тайлбар
enroll DAN KYC consent код + PIN-ээр distributed keygen хийж, сертификат авч хадгална. authPin өгвөл хоёр keygen (auth + sign) зэрэг хийнэ (eID 2-cert).
approve RP-ийн QR/push session-ийг PIN-ээр зөвшөөрч, 3-round threshold ECDSA гарын үсэг угсарна.
changePIN / verifyPIN PIN-ийг локал шалгах/солих (сервер PIN-ийг ХЭЗЭЭ Ч хардаггүй).
pending / activity / sessionInfo Хүлээгдэж буй session poll, түүх, session мэдээлэл.

Хувийн түлхүүр нэг газар бүрэн орших нь үгүй: нэг хагас утсан дээр (PIN-ээр шифрлэгдэж Keychain-д), нэг хагас серверт.


2. Урьдчилсан нөхцөл

  • iOS 14.0+ (SDK), macOS 12.0+Package.swift дахь platforms. (Үндсэн eIDMongolia апп нь iOS 15.0+ тавьсан.)
  • App Attest симулятор дээр ажиллахгүй. Симулятор/emulator дээр App Attest / Secure Enclave байхгүй тул Go серверийг SMARTID_REQUIRE_ATTESTATION=false (dev default)-оор ажиллуулна. Production-д (=true) бодит төхөөрөмж + App Attest шаардана.
  • Backend URL нь тохиргоогоор тавигдана (hardcode биш) — GeregeSmartIDClient(baseURL:)-д дамжуулна. Жишээ production суурь: https://rp-api.eidmongolia.mn.
  • BigInt dependency (attaswift/BigInt) — threshold протоколын raw EC scalar/point + Paillier (2048-бит) арифметикт шаардлагатай (CryptoKit эдгээрийг ил гаргадаггүй). swift build үед автоматаар татагдана.

TLS pinning

SDK бүх backend холболтоо CertPinner (public-key pinning)-ээр шалгадаг. Хэрэв сервер нь хүлээгдсэн CA (Let's Encrypt)-аас өөр гэрчилгээ өгвөл холболт NSURLErrorCancelled (-999)-ээр цуцлагдана.


3. Суулгах (Swift Package Manager)

Package нэр: GeregeSmartID, product: GeregeSmartID (ios/ios-sdk/Package.swift).

Монорепо доторх апп (XcodeGen project.yml) нь SDK-г локал path-аар авдаг:

packages:
  GeregeSmartID:
    path: ../ios-sdk             # local Swift package (GeregeSmartID)

Гадны төслөөс SPM dependency-гээр авах бол Package.swift-д:

dependencies: [
    .package(url: "https://github.com/gerege-systems/eid-platform-mn.git", branch: "main"),
    // эсвэл SDK-г тусад нь host хийсэн repo-гоос
],
targets: [
    .target(name: "MyApp", dependencies: [
        .product(name: "GeregeSmartID", package: "eid-platform-mn")
    ])
]

4. Үндсэн ашиглалт

4.1 Client үүсгэх

import GeregeSmartID

let client = GeregeSmartIDClient(
    baseURL: URL(string: "https://rp-api.eidmongolia.mn")!,
    account: "default")   // Keychain доторх бүртгэлийн нэр (default)

account нь Keychain slot-ийн нэр. SDK signing-ийг account, authentication-ийг account + ".auth" доор хадгална (init(baseURL:account:)).

4.2 Бүртгэл (enroll)

enroll нь DAN KYC consent код + PIN-ээр distributed keygen хийж, сертификат авч Keychain-д хадгалаад documentNumber (UUID) буцаана. authPin өгвөл хоёр keygen (auth + sign) хийж, signing-ийг pin-ээр, authentication-ийг authPin-ээр тус тусын slot-д хадгална; authPin == nil бол зөвхөн signing cert (хуучин зан).

@discardableResult
public func enroll(danAuthCode: String, pin: String, authPin: String? = nil,
                   pushToken: String,
                   reviewLatin: LatinNameReview? = nil,
                   progress: ProgressHandler? = nil) async throws -> String

Жишээ (eID 2-cert — хоёр PIN):

let documentNumber = try await client.enroll(
    danAuthCode: danState,   // DAN-аас баталгаажсан consent код (state)
    pin: "1234",             // PIN2 → signing (гарын үсэг)
    authPin: "5678",         // PIN1 → authentication (нэвтрэх); nil бол зөвхөн signing
    pushToken: apnsToken)

reviewLatin callback-аар латин галигийг иргэнд харуулж засуулж болно (LatinNameProposal → засварласан (givenNameLatin, surnameLatin)). progress нь round бүрийг монгол label-аар UI-д харуулна (ProgressHandler = @MainActor (String) -> Void).

DAN KYC урсгал

enroll-д өгөх danAuthCode нь DAN баталгаажуулалтын state. Урьдчилан: danInit(registrationNumber:) → verify URL нээх → danStatus(state:)-аар poll → баталгаажвал enroll(danAuthCode: state, …). (kycMethods()-оор боломжит арга: DAN / G-Sign / passport / citizen card.)

4.3 Session зөвшөөрөх (approve)

RP-ийн QR / push-аас авсан sessionId-ийг PIN-ээр зөвшөөрнө. authentication: true (нэвтрэх flow) үед authentication түлхүүр (PIN1, .auth slot), эс бөгөөс signing түлхүүр (PIN2) ашиглагдана — серверийн сонгосон түлхүүртэй таарна. flowType-ийг урьдчилан sessionInfo(sessionId:)-оор мэднэ.

@discardableResult
public func approve(sessionId: String, pin: String,
                    authentication: Bool = false,
                    confirmVc: String? = nil,
                    progress: ProgressHandler? = nil) async throws -> String

Жишээ:

// Нэвтрэх (authentication) session
let status = try await client.approve(
    sessionId: sessionId, pin: "5678", authentication: true)  // "OK"

// Гарын үсэг (signature) session
let status = try await client.approve(
    sessionId: sessionId, pin: "1234")  // authentication: false (default)

approve нь дотроо 3-round threshold ECDSA хийнэ: commit → prove → finish (/v3/mobile/session/{id}/sign/{commit,prove,finish}). Гарын үсгийг серверт ИЛГЭЭХЭЭСЭЭ ӨМНӨ утас session-ий мессежийг (SIGN: digest; AUTH: ACSP_V2(rpChallenge, өөрийн SPKI)) бие даан тооцоолж, эцсийн (r,s)-г өөрийн сертификатын public key-д ECDSA-verify хийнэ (transcript binding — WYSIWYS). Хурдасгахын тулд presign(sessionId:documentNumber:)-ээр PIN шаардахгүй nonce round-уудыг урьдчилан бэлдэж болно.

4.4 PINSlot ба PIN удирдлага

public enum PINSlot { case auth, sign }

public func verifyPIN(slot: PINSlot, pin: String) -> Bool          // локал шалгалт
public func changePIN(slot: PINSlot, oldPIN: String, newPIN: String) throws
public func deleteRegistration()                                    // локал устгах
  • verifyPIN — хадгалсан identity-г PIN-ээр тайлж үзнэ (сервер рүү хандахгүй).
  • changePIN — хуучин PIN-ээр тайлж, шинэ PIN-ээр дахин шифрлэнэ; гэрчилгээ + public key өөрчлөгдөхгүй. Буруу хуучин PIN → SDKError.wrongPIN.
  • deleteRegistration — зөвхөн ЛОКАЛ устгана (signing + auth slot хоёулаа); серверийн сертификатыг revoke хийхгүй (тэр нь админ үйлдэл).

4.5 Алдаа (SDKError)

public enum SDKError: Error {
    case crypto(String)   // криптограф алдаа
    case http(String)     // сүлжээ / сервер алдаа
    case wrongPIN         // PIN буруу (түлхүүр задрахгүй)
    case locked           // хэт олон буруу PIN → бүртгэл устгагдсан (дахин бүртгүүлэх)
}

SDKError нь LocalizedError тул errorDescription монголоор бодит шалтгааныг өгнө.


5. Keychain / Secure Enclave / биометр

Утасны нууцуудыг SecureKeyStore (SecureKeyStore.swift) удирдана:

  • PIN → PBKDF2 (HMAC-SHA256, 210,000 iteration) → AES-GCM шифрлэлт, дараа нь Secure Enclave-ийн P-256 түлхүүрээр ECIES wrap (hardware binding). PIN сервер рүү ХЭЗЭЭ Ч очдоггүй.
  • Хадгалах формат: [salt(16) | iv(12) | ciphertext | tag(16)] → SE wrap → [1 | wrapped]. Бодит төхөөрөмж дээр SE боломжгүй бол хатуу алдаа (SDKError.crypto); зөвхөн симулятор дээр [0 | blob] (hardware-binding-гүй, dev-only) зөвшөөрнө.
  • Биометр (H8): SE wrap түлхүүр нь .userPresence access-control-той (Face ID / Touch ID эсвэл passcode). Signing key-г unwrap хийх (= PIN-ээр identity задлах) үед биометр prompt гарна; хэрэглэгч цуцалбал unwrap болохгүй → signing зогсоно.
  • Brute-force хамгаалалт: дараалсан 10 буруу PIN хэтэрвэл шифрлэгдсэн бүртгэлийг УСТГАНА (SDKError.locked). Тоологч нь blob устсан ч Keychain-д тогтвортой үлдэнэ.
  • Accessibility: бүх зүйл kSecAttrAccessibleWhenUnlockedThisDeviceOnly — энэ төхөөрөмжид холбогдоно, iCloud-д sync хийгдэхгүй.
  • Device-binding token: /v3/mobile/* хүсэлт бүрд X-Device-Token-оор илгээх PIN-гүй bearer credential (pending/activity polling нь PIN-ээс өмнө хэрэгтэй) — мөн ThisDeviceOnly.

Keychain namespace

GeregeSmartIDKeychain.service нь брэндийн namespace (eIDMongolia"mn.eidmongolia.smartid"). Апп startup-д НЭГ УДАА тавина. Аль хэдийн хэрэглэгчтэй апп-д БҮҮ СОЛЬ — өмнөх нэрийн доорх бүртгэл уншигдахгүй болно.


6. Үндсэн апп (eIDMongolia) build хийх

eIDMongolia (ios/eIDMongolia/) нь энэ repo-гийн цорын ганц core / reference апп (XcodeGen project.yml, bundle mn.eidmongolia.dan). Дэлгэрэнгүй: ios/README.md.

brew install xcodegen
cd ios/eIDMongolia
export GEREGE_BACKEND_URL=https://rp-api.eidmongolia.mn   # эсвэл AppConfig default-оор
xcodegen                                                  # eIDMongolia.xcodeproj үүсгэнэ
open eIDMongolia.xcodeproj                                # Xcode-оос Run

Холбогдсон төхөөрөмж рүү CLI-аар build + суулгах (automatic signing, -scheme ашиглана, -target биш):

xcodebuild -project eIDMongolia.xcodeproj -scheme eIDMongolia \
  -configuration Debug -destination 'id=<DEVICE_ID>' \
  -allowProvisioningUpdates build
xcrun devicectl device install app --device <DEVICE_ID> <path>/eIDMongolia.app

Backend URL эрэмбэ (eIDMongolia/App/AppConfig.swift): Xcode scheme env (GEREGE_BACKEND_URL) → Info.plist (project.yml build setting) → код default (AppConfig.defaultBackendURL).

SDK-г CLI-аар шалгах (iOS simulator target):

cd ios/ios-sdk
swift build --sdk "$(xcrun --sdk iphonesimulator --show-sdk-path)" \
            --triple arm64-apple-ios16.0-simulator

(swift build дангаараа macOS руу build хийж App Attest API дээр зогсоно — энэ хэвийн, SDK iOS-only.)


7. App Attest урсгал (товч)

AppAttestManager нь backend руу явах эхний хүсэлтэд төхөөрөмжийг батална:

  1. Эхний хүсэлт: generateKeyattestKey → header Type: attest (сервер keyId / pubkey / counter бүртгэнэ).
  2. Дараагийн хүсэлт бүр: generateAssertion → header Type: assert + KeyId (сервер counter хатуу өсөхийг шалгана).

Сервер хариунд X-Next-Attestation-Nonce header өгдөг тул апп тусдаа challenge GET (GET /v3/attestation/challenge)-ийг алгасч хурдасна.


8. Backend endpoint-ууд (SDK ашигладаг)

Бүгд Go серверийн internal/httpapi-д таарна:

Endpoint Зориулалт
GET /v3/attestation/challenge App Attest nonce
POST /v3/enrollment/init · /complete distributed keygen + сертификат
GET /v3/mobile/session/{id} · /v3/mobile/pending/{doc} session мэдээлэл / pending poll
POST /v3/mobile/session/{id}/sign/{commit,prove,finish} 2-of-2 threshold ECDSA (3 round)

9. Холбоос