Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 0 additions & 50 deletions bitchat/Nostr/KeychainHelper.swift

This file was deleted.

4 changes: 2 additions & 2 deletions bitchat/Nostr/NostrIdentityBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ final class NostrIdentityBridge {
private var derivedIdentityCache: [String: NostrIdentity] = [:]
private let cacheLock = NSLock()

private let keychain: KeychainHelperProtocol
private let keychain: KeychainManagerProtocol

init(keychain: KeychainHelperProtocol = KeychainHelper()) {
init(keychain: KeychainManagerProtocol = KeychainManager()) {
self.keychain = keychain
}

Expand Down
6 changes: 3 additions & 3 deletions bitchat/Services/FavoritesPersistenceService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class FavoritesPersistenceService: ObservableObject {

private static let storageKey = "chat.bitchat.favorites"
private static let keychainService = "chat.bitchat.favorites"
private let keychain: KeychainHelperProtocol
private let keychain: KeychainManagerProtocol

@Published private(set) var favorites: [Data: FavoriteRelationship] = [:] // Noise pubkey -> relationship
@Published private(set) var mutualFavorites: Set<Data> = []
Expand All @@ -35,8 +35,8 @@ final class FavoritesPersistenceService: ObservableObject {
private var cancellables = Set<AnyCancellable>()

static let shared = FavoritesPersistenceService()
init(keychain: KeychainHelperProtocol = KeychainHelper()) {

init(keychain: KeychainManagerProtocol = KeychainManager()) {
self.keychain = keychain
loadFavorites()

Expand Down
59 changes: 56 additions & 3 deletions bitchat/Services/KeychainManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@ protocol KeychainManagerProtocol {
func getIdentityKey(forKey key: String) -> Data?
func deleteIdentityKey(forKey key: String) -> Bool
func deleteAllKeychainData() -> Bool

func secureClear(_ data: inout Data)
func secureClear(_ string: inout String)

func verifyIdentityKeyExists() -> Bool

// MARK: - Generic Data Storage (consolidated from KeychainHelper)
/// Save data with a custom service name
func save(key: String, data: Data, service: String, accessible: CFString?)
/// Load data from a custom service
func load(key: String, service: String) -> Data?
/// Delete data from a custom service
func delete(key: String, service: String)
}

final class KeychainManager: KeychainManagerProtocol {
Expand Down Expand Up @@ -309,9 +317,54 @@ final class KeychainManager: KeychainManagerProtocol {
}

// MARK: - Debug

func verifyIdentityKeyExists() -> Bool {
let key = "identity_noiseStaticKey"
return retrieveData(forKey: key) != nil
}

// MARK: - Generic Data Storage (consolidated from KeychainHelper)

/// Save data with a custom service name
func save(key: String, data: Data, service customService: String, accessible: CFString?) {
var query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: customService,
kSecAttrAccount as String: key,
kSecValueData as String: data
]
if let accessible = accessible {
query[kSecAttrAccessible as String] = accessible
}

SecItemDelete(query as CFDictionary)
SecItemAdd(query as CFDictionary, nil)
}

/// Load data from a custom service
func load(key: String, service customService: String) -> Data? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: customService,
kSecAttrAccount as String: key,
kSecReturnData as String: true
]

var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)

guard status == errSecSuccess else { return nil }
return result as? Data
}

/// Delete data from a custom service
func delete(key: String, service customService: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: customService,
kSecAttrAccount as String: key
]

SecItemDelete(query as CFDictionary)
}
}
33 changes: 26 additions & 7 deletions bitchat/_PreviewHelpers/PreviewKeychainManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,51 @@ import Foundation

final class PreviewKeychainManager: KeychainManagerProtocol {
private var storage: [String: Data] = [:]
private var serviceStorage: [String: [String: Data]] = [:]
init() {}

func saveIdentityKey(_ keyData: Data, forKey key: String) -> Bool {
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) {}

func secureClear(_ string: inout String) {}

func verifyIdentityKeyExists() -> Bool {
storage["identity_noiseStaticKey"] != nil
}

// MARK: - Generic Data Storage (consolidated from KeychainHelper)

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)
}
}
41 changes: 22 additions & 19 deletions bitchatTests/Mocks/MockKeychain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,54 +11,57 @@ import Foundation

final class MockKeychain: KeychainManagerProtocol {
private var storage: [String: Data] = [:]

private var serviceStorage: [String: [String: Data]] = [:]

func saveIdentityKey(_ keyData: Data, forKey key: String) -> Bool {
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
}
}

final class MockKeychainHelper: KeychainHelperProtocol {
private typealias Service = String
private typealias Key = String
private var storage: [Service: [Key: Data]] = [:]

// MARK: - Generic Data Storage (consolidated from KeychainHelper)

func save(key: String, data: Data, service: String, accessible: CFString?) {
storage[service]?[key] = data
if serviceStorage[service] == nil {
serviceStorage[service] = [:]
}
serviceStorage[service]?[key] = data
}

func load(key: String, service: String) -> Data? {
storage[service]?[key]
serviceStorage[service]?[key]
}

func delete(key: String, service: String) {
storage[service]?.removeValue(forKey: key)
serviceStorage[service]?.removeValue(forKey: key)
}
}

/// Typealias for backwards compatibility with tests using MockKeychainHelper
typealias MockKeychainHelper = MockKeychain