Files
web-terminal/ios/Packages/ClientTLS/Sources/ClientTLS/KeychainItemReplace.swift
Yaojia Wang 07bcbf0c08 feat(ios): device enrollment flow + silent cert rotation (B3)
Wire the SecureEnclave enroll library into a real flow (login->bearer->CSR->
/device/enroll->keychain identity), presented on the existing mTLS path; add a
rotation scheduler. Atomic keychain replace (add-before-delete); renew body is
{csr}-only; renewal-failing surfaced in the UI. ClientTLS 48 tests pass.
2026-07-18 13:32:05 +02:00

31 lines
1.3 KiB
Swift

import Foundation
/// Replace a keychain-backed item so a crash/failure mid-write can NEVER leave
/// zero installed copies which, for the device identity, would lock the device
/// out of its own mTLS cert until a manual re-enroll. This is the inverse of a
/// delete-then-add sequence, whose window between the two calls has zero items:
///
/// 1. `addNew` FIRST. Once it succeeds the replacement is installed, so at
/// least one valid item exists from here on. If `addNew` throws, the prior
/// item is left untouched (the device keeps a working identity) and the
/// error propagates.
/// 2. `deleteOld` SECOND, removing the prior copy. A failure here is NON-fatal:
/// the new item is already installed, so the write is a success. The stale
/// leftover is reported via `reportStaleDeleteFailure` (never a lockout) and
/// swept on the next write throwing here would strand a working new cert.
///
/// Kept pure (closures, no `Security` types) so the ordering invariant is unit-
/// testable with fakes; the keychain plumbing lives at the call sites.
func replaceKeychainItemAtomically(
addNew: () throws -> Void,
deleteOld: () throws -> Void,
reportStaleDeleteFailure: (Error) -> Void = { _ in }
) throws {
try addNew()
do {
try deleteOld()
} catch {
reportStaleDeleteFailure(error)
}
}