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.
31 lines
1.3 KiB
Swift
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)
|
|
}
|
|
}
|