Files
bitchat/bitchatTests/Services/SecureIdentityStateManagerTests.swift
T
jackandClaude Opus 5 ac13a0c22f Make the flake class unrepeatable, not just fixed
Raising four deadlines fixed the four tests that happened to fire. The
class was still there: fourteen separate waitUntil helpers, most defaulting
to 1.0s, plus wait call sites with literal budgets. The fifth instance
would have landed the same way, on someone else's unrelated PR.

The rule, now written down in TestConstants.settleTimeout: **a wait
deadline is not a latency budget.** It exists so a genuine hang eventually
fails the suite, so size it for the worst-case scheduler, never for how
long the operation should take. Waits return as soon as their condition
holds, so a generous deadline is free in the passing case and only extends
genuine failures.

- Every wait helper now defaults to TestConstants.settleTimeout (30s), and
  the literal wait call sites below the floor were converted too — sixteen
  sites across ten files.
- TestTimingHygieneTests enforces it by scanning the test sources: wait
  defaults and wait call sites must be at least minimumSettleTimeout, and
  no test may assert an upper bound on elapsed wall-clock time (the
  assertion that started this, which cannot separate correct behaviour from
  a slow machine).
- Both rules waive per line with "test-timing-ok: <reason>", accepted on
  the line or in the comment block above it so the reason has room to be a
  sentence. One legitimate use so far: a NEGATIVE wait in NoiseCoverageTests
  asserting a promotion has *not* completed within 50ms, where a long
  deadline would only make the suite slow while still passing.

Injected production timeouts are deliberately not matched — the Noise
handshake timeouts are the behaviour under test, and short values are
correct there.

Verified the guard actually fails: a canary file with both banned shapes
was flagged with file and line, and the suite went green again once it was
removed. Full suite passes with all 18 cores saturated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 22:23:40 +01:00

651 lines
25 KiB
Swift

import Foundation
import XCTest
import BitFoundation
@testable import bitchat
final class SecureIdentityStateManagerTests: XCTestCase {
func test_upsertCryptographicIdentity_withoutClaimedNicknameDoesNotCreateSocialIdentity() async {
let manager = SecureIdentityStateManager(MockKeychain())
let fingerprint = String(repeating: "aa", count: 32)
let peerID = PeerID(str: String(fingerprint.prefix(16)))
manager.upsertCryptographicIdentity(
fingerprint: fingerprint,
noisePublicKey: Data(repeating: 0x11, count: 32),
signingPublicKey: Data(repeating: 0x22, count: 32),
claimedNickname: nil
)
let inserted = await waitUntil {
manager.getCryptoIdentitiesByPeerIDPrefix(peerID).count == 1
}
XCTAssertTrue(inserted)
XCTAssertNil(manager.getSocialIdentity(for: fingerprint))
}
func test_upsertCryptographicIdentity_updatesExistingKeyAndPreservesSigningKey() async {
let manager = SecureIdentityStateManager(MockKeychain())
let fingerprint = String(repeating: "ab", count: 32)
let peerID = PeerID(str: String(fingerprint.prefix(16)))
let originalNoiseKey = Data(repeating: 0x11, count: 32)
let updatedNoiseKey = Data(repeating: 0x33, count: 32)
let signingKey = Data(repeating: 0x22, count: 32)
manager.upsertCryptographicIdentity(
fingerprint: fingerprint,
noisePublicKey: originalNoiseKey,
signingPublicKey: signingKey,
claimedNickname: nil
)
_ = await waitUntil {
manager.getCryptoIdentitiesByPeerIDPrefix(peerID).first?.publicKey == originalNoiseKey
}
manager.upsertCryptographicIdentity(
fingerprint: fingerprint,
noisePublicKey: updatedNoiseKey,
signingPublicKey: nil,
claimedNickname: nil
)
let updated = await waitUntil {
guard let identity = manager.getCryptoIdentitiesByPeerIDPrefix(peerID).first else { return false }
return identity.publicKey == updatedNoiseKey && identity.signingPublicKey == signingKey
}
XCTAssertTrue(updated)
}
func test_upsertCryptographicIdentity_tracksByPeerIDPrefixAndClaimedNickname() async {
let manager = SecureIdentityStateManager(MockKeychain())
let noisePublicKey = Data(repeating: 0x11, count: 32)
let signingPublicKey = Data(repeating: 0x22, count: 32)
let fingerprint = noisePublicKey.sha256Fingerprint()
manager.upsertCryptographicIdentity(
fingerprint: fingerprint,
noisePublicKey: noisePublicKey,
signingPublicKey: signingPublicKey,
claimedNickname: "Alice"
)
let socialIdentityLoaded = await waitUntil {
manager.getSocialIdentity(for: fingerprint)?.claimedNickname == "Alice"
}
XCTAssertTrue(socialIdentityLoaded)
let matches = manager.getCryptoIdentitiesByPeerIDPrefix(PeerID(publicKey: noisePublicKey))
XCTAssertEqual(matches.count, 1)
XCTAssertEqual(matches.first?.fingerprint, fingerprint)
XCTAssertEqual(matches.first?.publicKey, noisePublicKey)
XCTAssertEqual(matches.first?.signingPublicKey, signingPublicKey)
}
func test_upsertCryptographicIdentity_refusesToReplacePinnedSigningKey() async {
let manager = SecureIdentityStateManager(MockKeychain())
let noisePublicKey = Data(repeating: 0x11, count: 32)
let fingerprint = noisePublicKey.sha256Fingerprint()
let peerID = PeerID(publicKey: noisePublicKey)
let victimSigningKey = Data(repeating: 0x22, count: 32)
let attackerSigningKey = Data(repeating: 0x66, count: 32)
manager.upsertCryptographicIdentity(
fingerprint: fingerprint,
noisePublicKey: noisePublicKey,
signingPublicKey: victimSigningKey,
claimedNickname: "victim"
)
let pinned = await waitUntil {
manager.getCryptoIdentitiesByPeerIDPrefix(peerID).first?.signingPublicKey == victimSigningKey
}
XCTAssertTrue(pinned)
// Attacker upsert with a different signing key must be refused in
// full — signing key AND claimed nickname stay the victim's.
manager.upsertCryptographicIdentity(
fingerprint: fingerprint,
noisePublicKey: noisePublicKey,
signingPublicKey: attackerSigningKey,
claimedNickname: "attacker"
)
// Synchronous reads fence the manager's pending barrier writes.
XCTAssertEqual(
manager.getCryptoIdentitiesByPeerIDPrefix(peerID).first?.signingPublicKey,
victimSigningKey
)
XCTAssertEqual(manager.getSocialIdentity(for: fingerprint)?.claimedNickname, "victim")
// The legitimate peer (same signing key) can still update.
manager.upsertCryptographicIdentity(
fingerprint: fingerprint,
noisePublicKey: noisePublicKey,
signingPublicKey: victimSigningKey,
claimedNickname: "victim-renamed"
)
let renamed = await waitUntil {
manager.getSocialIdentity(for: fingerprint)?.claimedNickname == "victim-renamed"
}
XCTAssertTrue(renamed)
XCTAssertEqual(
manager.getCryptoIdentitiesByPeerIDPrefix(peerID).first?.signingPublicKey,
victimSigningKey
)
}
func test_cryptographicIdentity_persistsAcrossReinitAndKeepsSigningKeyPin() async {
let keychain = MockKeychain()
let manager = SecureIdentityStateManager(keychain)
let noisePublicKey = Data(repeating: 0x13, count: 32)
let fingerprint = noisePublicKey.sha256Fingerprint()
let peerID = PeerID(publicKey: noisePublicKey)
let victimSigningKey = Data(repeating: 0x24, count: 32)
let attackerSigningKey = Data(repeating: 0x77, count: 32)
manager.upsertCryptographicIdentity(
fingerprint: fingerprint,
noisePublicKey: noisePublicKey,
signingPublicKey: victimSigningKey,
claimedNickname: "victim"
)
let pinned = await waitUntil {
manager.getCryptoIdentitiesByPeerIDPrefix(peerID).first?.signingPublicKey == victimSigningKey
}
XCTAssertTrue(pinned)
manager.forceSave()
// Simulated app restart: the pin must survive and still refuse a
// different signing key.
let reloaded = SecureIdentityStateManager(keychain)
XCTAssertEqual(
reloaded.getCryptoIdentitiesByPeerIDPrefix(peerID).first?.signingPublicKey,
victimSigningKey
)
reloaded.upsertCryptographicIdentity(
fingerprint: fingerprint,
noisePublicKey: noisePublicKey,
signingPublicKey: attackerSigningKey,
claimedNickname: "attacker"
)
XCTAssertEqual(
reloaded.getCryptoIdentitiesByPeerIDPrefix(peerID).first?.signingPublicKey,
victimSigningKey
)
XCTAssertEqual(reloaded.getSocialIdentity(for: fingerprint)?.claimedNickname, "victim")
}
func test_setBlocked_clearsFavoriteState() async {
let manager = SecureIdentityStateManager(MockKeychain())
let fingerprint = String(repeating: "ab", count: 32)
manager.setFavorite(fingerprint, isFavorite: true)
let favoriteSet = await waitUntil { manager.isFavorite(fingerprint: fingerprint) }
XCTAssertTrue(favoriteSet)
manager.setBlocked(fingerprint, isBlocked: true)
let blockedSet = await waitUntil { manager.isBlocked(fingerprint: fingerprint) }
XCTAssertTrue(blockedSet)
XCTAssertFalse(manager.isFavorite(fingerprint: fingerprint))
XCTAssertEqual(manager.getSocialIdentity(for: fingerprint)?.claimedNickname, "Unknown")
}
func test_isBlocked_unknownFingerprintReturnsFalse() {
let manager = SecureIdentityStateManager(MockKeychain())
XCTAssertFalse(manager.isBlocked(fingerprint: String(repeating: "ff", count: 32)))
}
func test_setVerified_updatesTrustLevelAndVerifiedSet() async {
let manager = SecureIdentityStateManager(MockKeychain())
let fingerprint = String(repeating: "cd", count: 32)
manager.setFavorite(fingerprint, isFavorite: false)
_ = await waitUntil { manager.getSocialIdentity(for: fingerprint) != nil }
manager.setVerified(fingerprint: fingerprint, verified: true)
let verifiedSet = await waitUntil { manager.isVerified(fingerprint: fingerprint) }
XCTAssertTrue(verifiedSet)
XCTAssertTrue(manager.getVerifiedFingerprints().contains(fingerprint))
XCTAssertEqual(manager.getSocialIdentity(for: fingerprint)?.trustLevel, .verified)
}
func test_forceSave_persistsFavoriteStateAcrossReinit() async {
let keychain = MockKeychain()
let manager = SecureIdentityStateManager(keychain)
let fingerprint = String(repeating: "ef", count: 32)
manager.setFavorite(fingerprint, isFavorite: true)
let favoriteSet = await waitUntil { manager.isFavorite(fingerprint: fingerprint) }
XCTAssertTrue(favoriteSet)
manager.forceSave()
let reloaded = SecureIdentityStateManager(keychain)
XCTAssertTrue(reloaded.isFavorite(fingerprint: fingerprint))
}
func test_updateSocialIdentity_reindexesClaimedNickname() async {
let manager = SecureIdentityStateManager(MockKeychain())
let fingerprint = String(repeating: "34", count: 32)
manager.updateSocialIdentity(
SocialIdentity(
fingerprint: fingerprint,
localPetname: nil,
claimedNickname: "Alice",
trustLevel: .unknown,
isFavorite: false,
isBlocked: false,
notes: nil
)
)
let initialIndexed = await waitUntil {
manager.debugNicknameIndex["Alice"]?.contains(fingerprint) == true
}
XCTAssertTrue(initialIndexed)
manager.updateSocialIdentity(
SocialIdentity(
fingerprint: fingerprint,
localPetname: "Friend",
claimedNickname: "Bob",
trustLevel: .trusted,
isFavorite: true,
isBlocked: false,
notes: "updated"
)
)
let reindexed = await waitUntil {
manager.debugNicknameIndex["Alice"]?.contains(fingerprint) != true &&
manager.debugNicknameIndex["Bob"]?.contains(fingerprint) == true
}
XCTAssertTrue(reindexed)
XCTAssertEqual(manager.getSocialIdentity(for: fingerprint)?.claimedNickname, "Bob")
}
func test_upsertCryptographicIdentity_sameClaimedNicknamePreservesExistingSocialIdentity() async {
let manager = SecureIdentityStateManager(MockKeychain())
let fingerprint = String(repeating: "35", count: 32)
manager.updateSocialIdentity(
SocialIdentity(
fingerprint: fingerprint,
localPetname: "Pal",
claimedNickname: "Alice",
trustLevel: .trusted,
isFavorite: true,
isBlocked: false,
notes: "keep me"
)
)
_ = await waitUntil { manager.getSocialIdentity(for: fingerprint) != nil }
manager.upsertCryptographicIdentity(
fingerprint: fingerprint,
noisePublicKey: Data(repeating: 0x11, count: 32),
signingPublicKey: Data(repeating: 0x22, count: 32),
claimedNickname: "Alice"
)
let inserted = await waitUntil {
manager.getCryptoIdentitiesByPeerIDPrefix(PeerID(str: String(fingerprint.prefix(16)))).count == 1
}
XCTAssertTrue(inserted)
XCTAssertEqual(manager.getSocialIdentity(for: fingerprint)?.localPetname, "Pal")
XCTAssertEqual(manager.getSocialIdentity(for: fingerprint)?.notes, "keep me")
XCTAssertTrue(manager.getSocialIdentity(for: fingerprint)?.isFavorite == true)
}
func test_getFavorites_returnsOnlyFavoritedFingerprints() async {
let manager = SecureIdentityStateManager(MockKeychain())
let favoriteOne = String(repeating: "45", count: 32)
let favoriteTwo = String(repeating: "56", count: 32)
let other = String(repeating: "67", count: 32)
manager.setFavorite(favoriteOne, isFavorite: true)
manager.setFavorite(favoriteTwo, isFavorite: true)
manager.setFavorite(other, isFavorite: false)
let favoritesLoaded = await waitUntil {
manager.getFavorites() == Set([favoriteOne, favoriteTwo])
}
XCTAssertTrue(favoritesLoaded)
}
func test_setFavorite_existingIdentityCanBeClearedWithoutChangingNickname() async {
let manager = SecureIdentityStateManager(MockKeychain())
let fingerprint = String(repeating: "68", count: 32)
manager.updateSocialIdentity(
SocialIdentity(
fingerprint: fingerprint,
localPetname: nil,
claimedNickname: "Alice",
trustLevel: .trusted,
isFavorite: false,
isBlocked: false,
notes: nil
)
)
_ = await waitUntil { manager.getSocialIdentity(for: fingerprint) != nil }
manager.setFavorite(fingerprint, isFavorite: true)
_ = await waitUntil { manager.isFavorite(fingerprint: fingerprint) }
manager.setFavorite(fingerprint, isFavorite: false)
let cleared = await waitUntil {
!manager.isFavorite(fingerprint: fingerprint) &&
manager.getSocialIdentity(for: fingerprint)?.claimedNickname == "Alice" &&
manager.getSocialIdentity(for: fingerprint)?.trustLevel == .trusted
}
XCTAssertTrue(cleared)
}
func test_setBlocked_createsIdentityAndCanLaterUnblock() async {
let manager = SecureIdentityStateManager(MockKeychain())
let fingerprint = String(repeating: "78", count: 32)
manager.setBlocked(fingerprint, isBlocked: true)
let blocked = await waitUntil {
manager.isBlocked(fingerprint: fingerprint)
}
XCTAssertTrue(blocked)
XCTAssertEqual(manager.getSocialIdentity(for: fingerprint)?.claimedNickname, "Unknown")
manager.setBlocked(fingerprint, isBlocked: false)
let unblocked = await waitUntil {
!manager.isBlocked(fingerprint: fingerprint)
}
XCTAssertTrue(unblocked)
}
func test_setVerified_false_downgradesTrustLevelToCasual() async {
let manager = SecureIdentityStateManager(MockKeychain())
let fingerprint = String(repeating: "89", count: 32)
manager.updateSocialIdentity(
SocialIdentity(
fingerprint: fingerprint,
localPetname: nil,
claimedNickname: "Verifier",
trustLevel: .trusted,
isFavorite: false,
isBlocked: false,
notes: nil
)
)
_ = await waitUntil { manager.getSocialIdentity(for: fingerprint) != nil }
manager.setVerified(fingerprint: fingerprint, verified: true)
_ = await waitUntil { manager.isVerified(fingerprint: fingerprint) }
manager.setVerified(fingerprint: fingerprint, verified: false)
let downgraded = await waitUntil {
!manager.isVerified(fingerprint: fingerprint) &&
manager.getSocialIdentity(for: fingerprint)?.trustLevel == .casual
}
XCTAssertTrue(downgraded)
}
func test_ephemeralSessionLifecycle_tracksHandshakeProgressAndLastInteraction() async {
let manager = SecureIdentityStateManager(MockKeychain())
let peerID = PeerID(str: "1234567890abcdef")
let fingerprint = String(repeating: "90", count: 32)
manager.registerEphemeralSession(peerID: peerID, handshakeState: .initiated)
let registered = await waitUntil {
if case .initiated? = manager.debugEphemeralSession(for: peerID)?.handshakeState {
return true
}
return false
}
XCTAssertTrue(registered)
manager.updateHandshakeState(peerID: peerID, state: .inProgress)
let progressed = await waitUntil {
if case .inProgress? = manager.debugEphemeralSession(for: peerID)?.handshakeState {
return true
}
return false
}
XCTAssertTrue(progressed)
manager.updateHandshakeState(peerID: peerID, state: .completed(fingerprint: fingerprint))
let completed = await waitUntil {
if case .completed(let completedFingerprint)? = manager.debugEphemeralSession(for: peerID)?.handshakeState {
return completedFingerprint == fingerprint && manager.debugLastInteraction(for: fingerprint) != nil
}
return false
}
XCTAssertTrue(completed)
manager.removeEphemeralSession(peerID: peerID)
let removed = await waitUntil {
manager.debugEphemeralSession(for: peerID) == nil
}
XCTAssertTrue(removed)
}
func test_setNostrBlocked_normalizesToLowercaseAndPersists() async {
let keychain = MockKeychain()
let manager = SecureIdentityStateManager(keychain)
let pubkey = "ABCDEF1234"
manager.setNostrBlocked(pubkey, isBlocked: true)
let nostrBlocked = await waitUntil {
manager.isNostrBlocked(pubkeyHexLowercased: pubkey.lowercased())
}
XCTAssertTrue(nostrBlocked)
manager.forceSave()
let reloaded = SecureIdentityStateManager(keychain)
XCTAssertEqual(reloaded.getBlockedNostrPubkeys(), Set([pubkey.lowercased()]))
XCTAssertTrue(reloaded.isNostrBlocked(pubkeyHexLowercased: pubkey))
}
func test_setNostrBlocked_falseRemovesExistingKey() async {
let manager = SecureIdentityStateManager(MockKeychain())
let pubkey = "ABCDEF1234"
manager.setNostrBlocked(pubkey, isBlocked: true)
_ = await waitUntil { manager.isNostrBlocked(pubkeyHexLowercased: pubkey) }
manager.setNostrBlocked(pubkey, isBlocked: false)
let cleared = await waitUntil {
!manager.isNostrBlocked(pubkeyHexLowercased: pubkey) &&
manager.getBlockedNostrPubkeys().isEmpty
}
XCTAssertTrue(cleared)
}
func test_corruptPersistedCache_fallsBackToEmptyState() {
let keychain = MockKeychain()
_ = keychain.saveIdentityKey(Data(repeating: 0x01, count: 32), forKey: "identityCacheEncryptionKey")
_ = keychain.saveIdentityKey(Data([0xFF, 0x00, 0xAA]), forKey: "bitchat.identityCache.v2")
let manager = SecureIdentityStateManager(keychain)
XCTAssertTrue(manager.getFavorites().isEmpty)
XCTAssertTrue(manager.getVerifiedFingerprints().isEmpty)
XCTAssertTrue(manager.getBlockedNostrPubkeys().isEmpty)
XCTAssertNil(keychain.getIdentityKey(forKey: "bitchat.identityCache.v2"))
}
func test_clearAllIdentityData_removesCachedState() async {
let manager = SecureIdentityStateManager(MockKeychain())
let fingerprint = String(repeating: "12", count: 32)
manager.setFavorite(fingerprint, isFavorite: true)
manager.setVerified(fingerprint: fingerprint, verified: true)
manager.setNostrBlocked("ABCD", isBlocked: true)
let primed = await waitUntil {
manager.isFavorite(fingerprint: fingerprint) &&
manager.isVerified(fingerprint: fingerprint)
}
XCTAssertTrue(primed)
manager.clearAllIdentityData()
let cleared = await waitUntil {
!manager.isFavorite(fingerprint: fingerprint) &&
!manager.isVerified(fingerprint: fingerprint) &&
manager.getBlockedNostrPubkeys().isEmpty
}
XCTAssertTrue(cleared)
}
func test_privateMediaCapabilityPinPersistsMonotonicallyAndPanicClearRemovesIt() async {
let keychain = MockKeychain()
let fingerprint = Data(repeating: 0x42, count: 32).sha256Fingerprint()
let manager = SecureIdentityStateManager(keychain)
XCTAssertFalse(manager.hasObservedPrivateMediaCapability(fingerprint: fingerprint))
manager.markPrivateMediaCapable(fingerprint: fingerprint)
XCTAssertTrue(
manager.hasObservedPrivateMediaCapability(fingerprint: fingerprint),
"pin insertion must be synchronously visible to the next downgrade decision"
)
// Re-marking is idempotent, and the encrypted cache carries the pin
// across launches.
manager.markPrivateMediaCapable(fingerprint: fingerprint)
manager.forceSave()
let reloaded = SecureIdentityStateManager(keychain)
XCTAssertTrue(reloaded.hasObservedPrivateMediaCapability(fingerprint: fingerprint))
// ChatViewModel's panic path calls this same wipe after deleting
// keychain data; the in-memory pin must disappear immediately too.
reloaded.clearAllIdentityData()
let cleared = await waitUntil {
!reloaded.hasObservedPrivateMediaCapability(fingerprint: fingerprint)
}
XCTAssertTrue(cleared)
}
func test_noiseAuthenticatedSigningKeyBindingPersistsAndPanicClearRemovesIt() async {
let keychain = MockKeychain()
let fingerprint = Data(repeating: 0x31, count: 32).sha256Fingerprint()
let firstKey = Data(repeating: 0x41, count: 32)
let rotatedKey = Data(repeating: 0x42, count: 32)
let manager = SecureIdentityStateManager(keychain)
manager.bindAuthenticatedSigningPublicKey(firstKey, fingerprint: fingerprint)
XCTAssertEqual(manager.authenticatedSigningPublicKey(forFingerprint: fingerprint), firstKey)
// A later authenticated Noise session may legitimately rotate the
// announcement signing key.
manager.bindAuthenticatedSigningPublicKey(rotatedKey, fingerprint: fingerprint)
XCTAssertEqual(manager.authenticatedSigningPublicKey(forFingerprint: fingerprint), rotatedKey)
manager.forceSave()
let reloaded = SecureIdentityStateManager(keychain)
XCTAssertEqual(reloaded.authenticatedSigningPublicKey(forFingerprint: fingerprint), rotatedKey)
reloaded.clearAllIdentityData()
let cleared = await waitUntil {
reloaded.authenticatedSigningPublicKey(forFingerprint: fingerprint) == nil
}
XCTAssertTrue(cleared)
}
func test_forceSave_withFailingCacheWriteDoesNotPersistCache() async {
let keychain = FailingCacheSaveKeychain()
let manager = SecureIdentityStateManager(keychain)
let fingerprint = String(repeating: "de", count: 32)
manager.setFavorite(fingerprint, isFavorite: true)
let primed = await waitUntil { manager.isFavorite(fingerprint: fingerprint) }
XCTAssertTrue(primed)
manager.forceSave()
let reloaded = SecureIdentityStateManager(keychain)
XCTAssertFalse(reloaded.isFavorite(fingerprint: fingerprint))
}
private func waitUntil(
timeout: TimeInterval = TestConstants.settleTimeout,
condition: @escaping () -> Bool
) async -> Bool {
let deadline = Date().addingTimeInterval(timeout)
while Date() < deadline {
if condition() {
return true
}
try? await Task.sleep(nanoseconds: 10_000_000)
}
return condition()
}
}
private final class FailingCacheSaveKeychain: KeychainManagerProtocol {
private var storage: [String: Data] = [:]
private var serviceStorage: [String: [String: Data]] = [:]
func saveIdentityKey(_ keyData: Data, forKey key: String) -> Bool {
if key == "bitchat.identityCache.v2" {
return false
}
storage[key] = keyData
return true
}
func getIdentityKey(forKey key: String) -> Data? {
storage[key]
}
func deleteIdentityKey(forKey key: String) -> Bool {
storage.removeValue(forKey: key)
return true
}
func deleteAllKeychainData() -> Bool {
storage.removeAll()
serviceStorage.removeAll()
return true
}
func secureClear(_ data: inout Data) {
data = Data()
}
func secureClear(_ string: inout String) {
string = ""
}
func verifyIdentityKeyExists() -> Bool {
storage["identity_noiseStaticKey"] != nil
}
func getIdentityKeyWithResult(forKey key: String) -> KeychainReadResult {
if let data = storage[key] {
return .success(data)
}
return .itemNotFound
}
func saveIdentityKeyWithResult(_ keyData: Data, forKey key: String) -> KeychainSaveResult {
if saveIdentityKey(keyData, forKey: key) {
return .success
}
return .otherError(OSStatus(-1))
}
func save(key: String, data: Data, service: String, accessible: CFString?) {
if serviceStorage[service] == nil {
serviceStorage[service] = [:]
}
serviceStorage[service]?[key] = data
}
func load(key: String, service: String) -> Data? {
serviceStorage[service]?[key]
}
func delete(key: String, service: String) {
serviceStorage[service]?.removeValue(forKey: key)
}
func deleteAll(service: String) {
serviceStorage.removeValue(forKey: service)
}
}