PeerID 5/n: Ephemeral and Secure Identities (#742)

This commit is contained in:
Islam
2025-10-02 13:29:48 +02:00
committed by GitHub
parent bd11940151
commit e4ec2ef3fe
6 changed files with 21 additions and 21 deletions
+1 -1
View File
@@ -87,7 +87,7 @@ import Foundation
/// Represents the ephemeral layer of identity - short-lived peer IDs that provide network privacy. /// Represents the ephemeral layer of identity - short-lived peer IDs that provide network privacy.
/// These IDs rotate periodically to prevent tracking while maintaining cryptographic relationships. /// These IDs rotate periodically to prevent tracking while maintaining cryptographic relationships.
struct EphemeralIdentity { struct EphemeralIdentity {
let peerID: String // 8 random bytes let peerID: PeerID // 8 random bytes
let sessionStart: Date let sessionStart: Date
var handshakeState: HandshakeState var handshakeState: HandshakeState
} }
@@ -103,7 +103,7 @@ protocol SecureIdentityStateManagerProtocol {
// MARK: Cryptographic Identities // MARK: Cryptographic Identities
func upsertCryptographicIdentity(fingerprint: String, noisePublicKey: Data, signingPublicKey: Data?, claimedNickname: String?) func upsertCryptographicIdentity(fingerprint: String, noisePublicKey: Data, signingPublicKey: Data?, claimedNickname: String?)
func getCryptoIdentitiesByPeerIDPrefix(_ peerID: String) -> [CryptographicIdentity] func getCryptoIdentitiesByPeerIDPrefix(_ peerID: PeerID) -> [CryptographicIdentity]
func updateSocialIdentity(_ identity: SocialIdentity) func updateSocialIdentity(_ identity: SocialIdentity)
// MARK: Favorites Management // MARK: Favorites Management
@@ -121,12 +121,12 @@ protocol SecureIdentityStateManagerProtocol {
func getBlockedNostrPubkeys() -> Set<String> func getBlockedNostrPubkeys() -> Set<String>
// MARK: Ephemeral Session Management // MARK: Ephemeral Session Management
func registerEphemeralSession(peerID: String, handshakeState: HandshakeState) func registerEphemeralSession(peerID: PeerID, handshakeState: HandshakeState)
func updateHandshakeState(peerID: String, state: HandshakeState) func updateHandshakeState(peerID: PeerID, state: HandshakeState)
// MARK: Cleanup // MARK: Cleanup
func clearAllIdentityData() func clearAllIdentityData()
func removeEphemeralSession(peerID: String) func removeEphemeralSession(peerID: PeerID)
// MARK: Verification // MARK: Verification
func setVerified(fingerprint: String, verified: Bool) func setVerified(fingerprint: String, verified: Bool)
@@ -143,7 +143,7 @@ final class SecureIdentityStateManager: SecureIdentityStateManagerProtocol {
private let encryptionKeyName = "identityCacheEncryptionKey" private let encryptionKeyName = "identityCacheEncryptionKey"
// In-memory state // In-memory state
private var ephemeralSessions: [String: EphemeralIdentity] = [:] private var ephemeralSessions: [PeerID: EphemeralIdentity] = [:]
private var cryptographicIdentities: [String: CryptographicIdentity] = [:] private var cryptographicIdentities: [String: CryptographicIdentity] = [:]
private var cache: IdentityCache = IdentityCache() private var cache: IdentityCache = IdentityCache()
@@ -321,11 +321,11 @@ final class SecureIdentityStateManager: SecureIdentityStateManagerProtocol {
} }
/// Find cryptographic identities whose fingerprint prefix matches a peerID (16-hex) short ID /// Find cryptographic identities whose fingerprint prefix matches a peerID (16-hex) short ID
func getCryptoIdentitiesByPeerIDPrefix(_ peerID: String) -> [CryptographicIdentity] { func getCryptoIdentitiesByPeerIDPrefix(_ peerID: PeerID) -> [CryptographicIdentity] {
queue.sync { queue.sync {
// Defensive: ensure hex and correct length // Defensive: ensure hex and correct length
guard peerID.count == 16, peerID.allSatisfy({ $0.isHexDigit }) else { return [] } guard peerID.isShort else { return [] }
return cryptographicIdentities.values.filter { $0.fingerprint.hasPrefix(peerID) } return cryptographicIdentities.values.filter { $0.fingerprint.hasPrefix(peerID.id) }
} }
} }
@@ -455,7 +455,7 @@ final class SecureIdentityStateManager: SecureIdentityStateManagerProtocol {
// MARK: - Ephemeral Session Management // MARK: - Ephemeral Session Management
func registerEphemeralSession(peerID: String, handshakeState: HandshakeState = .none) { func registerEphemeralSession(peerID: PeerID, handshakeState: HandshakeState = .none) {
queue.async(flags: .barrier) { queue.async(flags: .barrier) {
self.ephemeralSessions[peerID] = EphemeralIdentity( self.ephemeralSessions[peerID] = EphemeralIdentity(
peerID: peerID, peerID: peerID,
@@ -465,7 +465,7 @@ final class SecureIdentityStateManager: SecureIdentityStateManagerProtocol {
} }
} }
func updateHandshakeState(peerID: String, state: HandshakeState) { func updateHandshakeState(peerID: PeerID, state: HandshakeState) {
queue.async(flags: .barrier) { queue.async(flags: .barrier) {
self.ephemeralSessions[peerID]?.handshakeState = state self.ephemeralSessions[peerID]?.handshakeState = state
@@ -493,7 +493,7 @@ final class SecureIdentityStateManager: SecureIdentityStateManagerProtocol {
} }
} }
func removeEphemeralSession(peerID: String) { func removeEphemeralSession(peerID: PeerID) {
queue.async(flags: .barrier) { queue.async(flags: .barrier) {
self.ephemeralSessions.removeValue(forKey: peerID) self.ephemeralSessions.removeValue(forKey: peerID)
} }
+1 -1
View File
@@ -1786,7 +1786,7 @@ final class BLEService: NSObject {
// Fallback: verify signature using persisted signing key for this peerID's fingerprint prefix // Fallback: verify signature using persisted signing key for this peerID's fingerprint prefix
if let signature = packet.signature, let packetData = packet.toBinaryDataForSigning() { if let signature = packet.signature, let packetData = packet.toBinaryDataForSigning() {
// Find candidate identities by peerID prefix (16 hex) // Find candidate identities by peerID prefix (16 hex)
let candidates = identityManager.getCryptoIdentitiesByPeerIDPrefix(peerID) let candidates = identityManager.getCryptoIdentitiesByPeerIDPrefix(PeerID(str: peerID))
for candidate in candidates { for candidate in candidates {
if let signingKey = candidate.signingPublicKey, if let signingKey = candidate.signingPublicKey,
noiseService.verifySignature(signature, for: packetData, publicKey: signingKey) { noiseService.verifySignature(signature, for: packetData, publicKey: signingKey) {
+3 -3
View File
@@ -4607,7 +4607,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
isConnected = true isConnected = true
// Register ephemeral session with identity manager // Register ephemeral session with identity manager
identityManager.registerEphemeralSession(peerID: peerID, handshakeState: .none) identityManager.registerEphemeralSession(peerID: PeerID(str: peerID), handshakeState: .none)
// Intentionally do not resend favorites on reconnect. // Intentionally do not resend favorites on reconnect.
// We only send our npub when a favorite is toggled on, or if our npub changes. // We only send our npub when a favorite is toggled on, or if our npub changes.
@@ -4632,7 +4632,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
SecureLogger.debug("👋 Peer disconnected: \(peerID)", category: .session) SecureLogger.debug("👋 Peer disconnected: \(peerID)", category: .session)
// Remove ephemeral session from identity manager // Remove ephemeral session from identity manager
identityManager.removeEphemeralSession(peerID: peerID) identityManager.removeEphemeralSession(peerID: PeerID(str: peerID))
// If the open PM is tied to this short peer ID, switch UI context to the full Noise key (offline favorite) // If the open PM is tied to this short peer ID, switch UI context to the full Noise key (offline favorite)
var derivedStableKeyHex: String? = shortIDToNoiseKey[peerID] var derivedStableKeyHex: String? = shortIDToNoiseKey[peerID]
@@ -4739,7 +4739,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
// Register ephemeral sessions for all connected peers // Register ephemeral sessions for all connected peers
for peerID in peers { for peerID in peers {
self.identityManager.registerEphemeralSession(peerID: peerID, handshakeState: .none) self.identityManager.registerEphemeralSession(peerID: PeerID(str: peerID), handshakeState: .none)
} }
// Schedule UI refresh to ensure offline favorites are shown // Schedule UI refresh to ensure offline favorites are shown
+1 -1
View File
@@ -1407,7 +1407,7 @@ struct ContentView: View {
!fav.peerNickname.isEmpty { return fav.peerNickname } !fav.peerNickname.isEmpty { return fav.peerNickname }
// Fallback: resolve from persisted social identity via fingerprint mapping // Fallback: resolve from persisted social identity via fingerprint mapping
if headerPeerID.count == 16 { if headerPeerID.count == 16 {
let candidates = viewModel.identityManager.getCryptoIdentitiesByPeerIDPrefix(headerPeerID) let candidates = viewModel.identityManager.getCryptoIdentitiesByPeerIDPrefix(PeerID(str: headerPeerID))
if let id = candidates.first, if let id = candidates.first,
let social = viewModel.identityManager.getSocialIdentity(for: id.fingerprint) { let social = viewModel.identityManager.getSocialIdentity(for: id.fingerprint) {
if let pet = social.localPetname, !pet.isEmpty { return pet } if let pet = social.localPetname, !pet.isEmpty { return pet }
+4 -4
View File
@@ -28,7 +28,7 @@ final class MockIdentityManager: SecureIdentityStateManagerProtocol {
func upsertCryptographicIdentity(fingerprint: String, noisePublicKey: Data, signingPublicKey: Data?, claimedNickname: String?) {} func upsertCryptographicIdentity(fingerprint: String, noisePublicKey: Data, signingPublicKey: Data?, claimedNickname: String?) {}
func getCryptoIdentitiesByPeerIDPrefix(_ peerID: String) -> [CryptographicIdentity] { func getCryptoIdentitiesByPeerIDPrefix(_ peerID: PeerID) -> [CryptographicIdentity] {
[] []
} }
@@ -60,13 +60,13 @@ final class MockIdentityManager: SecureIdentityStateManagerProtocol {
Set() Set()
} }
func registerEphemeralSession(peerID: String, handshakeState: HandshakeState) {} func registerEphemeralSession(peerID: PeerID, handshakeState: HandshakeState) {}
func updateHandshakeState(peerID: String, state: HandshakeState) {} func updateHandshakeState(peerID: PeerID, state: HandshakeState) {}
func clearAllIdentityData() {} func clearAllIdentityData() {}
func removeEphemeralSession(peerID: String) {} func removeEphemeralSession(peerID: PeerID) {}
func setVerified(fingerprint: String, verified: Bool) {} func setVerified(fingerprint: String, verified: Bool) {}