mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 05:05:19 +00:00
Fix: stabilize per-geohash identity seed by storing in Keychain with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly and caching in memory to avoid transient regenerations (#473)
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
@@ -5,13 +5,16 @@ import Security
|
|||||||
|
|
||||||
// Keychain helper for secure storage
|
// Keychain helper for secure storage
|
||||||
struct KeychainHelper {
|
struct KeychainHelper {
|
||||||
static func save(key: String, data: Data, service: String) {
|
static func save(key: String, data: Data, service: String, accessible: CFString? = nil) {
|
||||||
let query: [String: Any] = [
|
var query: [String: Any] = [
|
||||||
kSecClass as String: kSecClassGenericPassword,
|
kSecClass as String: kSecClassGenericPassword,
|
||||||
kSecAttrService as String: service,
|
kSecAttrService as String: service,
|
||||||
kSecAttrAccount as String: key,
|
kSecAttrAccount as String: key,
|
||||||
kSecValueData as String: data
|
kSecValueData as String: data
|
||||||
]
|
]
|
||||||
|
if let accessible = accessible {
|
||||||
|
query[kSecAttrAccessible as String] = accessible
|
||||||
|
}
|
||||||
|
|
||||||
SecItemDelete(query as CFDictionary)
|
SecItemDelete(query as CFDictionary)
|
||||||
SecItemAdd(query as CFDictionary, nil)
|
SecItemAdd(query as CFDictionary, nil)
|
||||||
@@ -106,6 +109,8 @@ struct NostrIdentityBridge {
|
|||||||
private static let keychainService = "chat.bitchat.nostr"
|
private static let keychainService = "chat.bitchat.nostr"
|
||||||
private static let currentIdentityKey = "nostr-current-identity"
|
private static let currentIdentityKey = "nostr-current-identity"
|
||||||
private static let deviceSeedKey = "nostr-device-seed"
|
private static let deviceSeedKey = "nostr-device-seed"
|
||||||
|
// In-memory cache to avoid transient keychain access issues
|
||||||
|
private static var deviceSeedCache: Data?
|
||||||
|
|
||||||
/// Get or create the current Nostr identity
|
/// Get or create the current Nostr identity
|
||||||
static func getCurrentNostrIdentity() throws -> NostrIdentity? {
|
static func getCurrentNostrIdentity() throws -> NostrIdentity? {
|
||||||
@@ -159,14 +164,20 @@ struct NostrIdentityBridge {
|
|||||||
/// Returns a stable device seed used to derive unlinkable per-geohash identities.
|
/// Returns a stable device seed used to derive unlinkable per-geohash identities.
|
||||||
/// Stored only on device keychain.
|
/// Stored only on device keychain.
|
||||||
private static func getOrCreateDeviceSeed() -> Data {
|
private static func getOrCreateDeviceSeed() -> Data {
|
||||||
|
if let cached = deviceSeedCache { return cached }
|
||||||
if let existing = KeychainHelper.load(key: deviceSeedKey, service: keychainService) {
|
if let existing = KeychainHelper.load(key: deviceSeedKey, service: keychainService) {
|
||||||
|
// Migrate to AfterFirstUnlockThisDeviceOnly for stability during lock
|
||||||
|
KeychainHelper.save(key: deviceSeedKey, data: existing, service: keychainService, accessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)
|
||||||
|
deviceSeedCache = existing
|
||||||
return existing
|
return existing
|
||||||
}
|
}
|
||||||
var seed = Data(count: 32)
|
var seed = Data(count: 32)
|
||||||
_ = seed.withUnsafeMutableBytes { ptr in
|
_ = seed.withUnsafeMutableBytes { ptr in
|
||||||
SecRandomCopyBytes(kSecRandomDefault, 32, ptr.baseAddress!)
|
SecRandomCopyBytes(kSecRandomDefault, 32, ptr.baseAddress!)
|
||||||
}
|
}
|
||||||
KeychainHelper.save(key: deviceSeedKey, data: seed, service: keychainService)
|
// Ensure availability after first unlock to prevent unintended rotation when locked
|
||||||
|
KeychainHelper.save(key: deviceSeedKey, data: seed, service: keychainService, accessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)
|
||||||
|
deviceSeedCache = seed
|
||||||
return seed
|
return seed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user