mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-27 06:25:22 +00:00
Pin announce signing keys to stop mesh identity spoofing
BLE announce trust verified the packet signature against the Ed25519 signing key carried inside the same announce, and the trust policy only rejected on noise-key mismatch. Since peerIDs derive from the broadcast (public) noise key, an on-mesh attacker could replay a victim's peerID+noiseKey with their own signing key, nickname, and a valid self-signature; the registry/persisted identity was then overwritten unconditionally, enabling mesh nickname spoofing and forged attribution of signed public/broadcast messages. Fix: TOFU-pin the Ed25519 signing key per peer (noise-key-derived peerID) on the announce path. - BLEAnnounceTrustPolicy rejects announces whose signing key differs from the one already recorded for the peer (.signingKeyMismatch). - BLEPeerRegistry.upsertVerifiedAnnounce refuses to replace a pinned signing key (returns nil) and never drops a pinned key. - BLEAnnounceHandler falls back to the persisted cryptographic identity (persistedSigningPublicKey) when the registry has no signing key, so the pin survives registry eviction and app restart. - SecureIdentityStateManager.upsertCryptographicIdentity refuses to replace a persisted signing key with a different one, and the cryptographic identities (incl. the pin) now live in the encrypted, persisted IdentityCache with synchronous, teardown-safe saves. Rebased onto main and integrated with #1432 (Noise session identity binding + signed leaves): both are complementary. #1432's signed-leave verification reads the same registry/persisted signing key that this change protects from announce-path poisoning. main's evolution is kept: BLEPeerRegistry.upsertVerifiedAnnounce still takes capabilities/ bridgeGeohash (nil return propagates as a refusal), the handler's linkBoundToOtherPeer env is preserved, CryptographicIdentity keeps main's field set, and EphemeralIdentity uses main's initializer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,10 +6,13 @@ import Testing
|
||||
struct BLEAnnounceHandlerTests {
|
||||
private final class Recorder {
|
||||
var existingNoisePublicKey: Data?
|
||||
var existingSigningPublicKey: Data?
|
||||
var persistedSigningPublicKey: Data?
|
||||
var persistedSigningKeyQueries: [PeerID] = []
|
||||
var signatureValid = true
|
||||
var linkState: (hasPeripheral: Bool, hasCentral: Bool) = (false, false)
|
||||
var linkBoundToOtherPeer = false
|
||||
var upsertResult = BLEPeerAnnounceUpdate(isNewPeer: false, wasDisconnected: false, previousNickname: nil)
|
||||
var upsertResult: BLEPeerAnnounceUpdate? = BLEPeerAnnounceUpdate(isNewPeer: false, wasDisconnected: false, previousNickname: nil)
|
||||
var dedupSeenIDs: Set<String> = []
|
||||
var shouldEmitReconnectLogResult = true
|
||||
|
||||
@@ -36,7 +39,11 @@ struct BLEAnnounceHandlerTests {
|
||||
localPeerID: { localPeerID },
|
||||
messageTTL: TransportConfig.messageTTLDefault,
|
||||
now: { now },
|
||||
existingNoisePublicKey: { _ in recorder.existingNoisePublicKey },
|
||||
existingPeerKeys: { _ in (recorder.existingNoisePublicKey, recorder.existingSigningPublicKey) },
|
||||
persistedSigningPublicKey: { peerID in
|
||||
recorder.persistedSigningKeyQueries.append(peerID)
|
||||
return recorder.persistedSigningPublicKey
|
||||
},
|
||||
verifySignature: { packet, signingPublicKey in
|
||||
recorder.verifySignatureCalls.append((packet, signingPublicKey))
|
||||
return recorder.signatureValid
|
||||
@@ -482,6 +489,168 @@ struct BLEAnnounceHandlerTests {
|
||||
#expect(recorder.topologyUpdates.first?.neighbors == neighbors)
|
||||
}
|
||||
|
||||
@Test
|
||||
func matchingPinnedSigningKeyIsAccepted() throws {
|
||||
let now = Date(timeIntervalSince1970: 1_000)
|
||||
let noiseKey = Data(repeating: 0x9A, count: 32)
|
||||
let peerID = PeerID(publicKey: noiseKey)
|
||||
let packet = try makeAnnouncePacket(
|
||||
noisePublicKey: noiseKey,
|
||||
peerID: peerID,
|
||||
timestamp: timestamp(now),
|
||||
signature: Data(repeating: 0xEE, count: 64)
|
||||
)
|
||||
|
||||
let recorder = Recorder()
|
||||
recorder.existingNoisePublicKey = noiseKey
|
||||
// Matches the signing key encoded by makeAnnouncePacket.
|
||||
recorder.existingSigningPublicKey = Data(repeating: 0x99, count: 32)
|
||||
let handler = makeHandler(recorder: recorder, now: now)
|
||||
|
||||
handler.handle(packet, from: peerID)
|
||||
|
||||
#expect(recorder.upsertCalls.count == 1)
|
||||
#expect(recorder.persistedIdentities.count == 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
func signingKeyMismatchWithPinnedKeySkipsUpsertAndIdentityPersistence() throws {
|
||||
let now = Date(timeIntervalSince1970: 1_000)
|
||||
let noiseKey = Data(repeating: 0x9B, count: 32)
|
||||
let peerID = PeerID(publicKey: noiseKey)
|
||||
// Attacker announce: victim's noiseKey/peerID, attacker's signing key
|
||||
// (0x99 from makeAnnouncePacket) with a "valid" self-signature.
|
||||
let packet = try makeAnnouncePacket(
|
||||
noisePublicKey: noiseKey,
|
||||
peerID: peerID,
|
||||
timestamp: timestamp(now),
|
||||
signature: Data(repeating: 0xEE, count: 64)
|
||||
)
|
||||
|
||||
let recorder = Recorder()
|
||||
recorder.existingNoisePublicKey = noiseKey
|
||||
recorder.existingSigningPublicKey = Data(repeating: 0x42, count: 32) // victim's pinned key
|
||||
recorder.signatureValid = true
|
||||
let handler = makeHandler(recorder: recorder, now: now)
|
||||
|
||||
handler.handle(packet, from: peerID)
|
||||
|
||||
#expect(recorder.upsertCalls.isEmpty)
|
||||
#expect(recorder.persistedIdentities.isEmpty)
|
||||
#expect(recorder.topologyUpdates.isEmpty)
|
||||
#expect(recorder.uiEventDeliveries.count == 1)
|
||||
#expect(recorder.uiEventDeliveries.first?.notifyPeerConnected == false)
|
||||
}
|
||||
|
||||
@Test
|
||||
func persistedSigningKeyMismatchWithoutRegistryEntryIsRejected() throws {
|
||||
// Registry has no entry (app restart or offline-peer eviction), but
|
||||
// the persisted cryptographic identity still pins the victim's
|
||||
// signing key. An attacker replaying the victim's noiseKey/peerID
|
||||
// with their own signing key must not be treated as first contact.
|
||||
let now = Date(timeIntervalSince1970: 1_000)
|
||||
let noiseKey = Data(repeating: 0x9D, count: 32)
|
||||
let peerID = PeerID(publicKey: noiseKey)
|
||||
let packet = try makeAnnouncePacket(
|
||||
noisePublicKey: noiseKey,
|
||||
peerID: peerID,
|
||||
timestamp: timestamp(now),
|
||||
signature: Data(repeating: 0xEE, count: 64)
|
||||
)
|
||||
|
||||
let recorder = Recorder()
|
||||
recorder.existingNoisePublicKey = nil
|
||||
recorder.existingSigningPublicKey = nil
|
||||
recorder.persistedSigningPublicKey = Data(repeating: 0x42, count: 32) // victim's persisted pin
|
||||
let handler = makeHandler(recorder: recorder, now: now)
|
||||
|
||||
handler.handle(packet, from: peerID)
|
||||
|
||||
#expect(recorder.persistedSigningKeyQueries == [peerID])
|
||||
#expect(recorder.upsertCalls.isEmpty)
|
||||
#expect(recorder.persistedIdentities.isEmpty)
|
||||
#expect(recorder.topologyUpdates.isEmpty)
|
||||
#expect(recorder.uiEventDeliveries.count == 1)
|
||||
#expect(recorder.uiEventDeliveries.first?.notifyPeerConnected == false)
|
||||
}
|
||||
|
||||
@Test
|
||||
func persistedSigningKeyMatchWithoutRegistryEntryIsAccepted() throws {
|
||||
// Legitimate returning peer: registry entry evicted, persisted pin
|
||||
// matches the announced signing key — accepted like a normal announce.
|
||||
let now = Date(timeIntervalSince1970: 1_000)
|
||||
let noiseKey = Data(repeating: 0x9E, count: 32)
|
||||
let peerID = PeerID(publicKey: noiseKey)
|
||||
let packet = try makeAnnouncePacket(
|
||||
noisePublicKey: noiseKey,
|
||||
peerID: peerID,
|
||||
timestamp: timestamp(now),
|
||||
signature: Data(repeating: 0xEE, count: 64)
|
||||
)
|
||||
|
||||
let recorder = Recorder()
|
||||
// Matches the signing key encoded by makeAnnouncePacket.
|
||||
recorder.persistedSigningPublicKey = Data(repeating: 0x99, count: 32)
|
||||
let handler = makeHandler(recorder: recorder, now: now)
|
||||
|
||||
handler.handle(packet, from: peerID)
|
||||
|
||||
#expect(recorder.upsertCalls.count == 1)
|
||||
#expect(recorder.persistedIdentities.count == 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
func registryPinnedSigningKeySkipsPersistedLookup() throws {
|
||||
let now = Date(timeIntervalSince1970: 1_000)
|
||||
let noiseKey = Data(repeating: 0x9F, count: 32)
|
||||
let peerID = PeerID(publicKey: noiseKey)
|
||||
let packet = try makeAnnouncePacket(
|
||||
noisePublicKey: noiseKey,
|
||||
peerID: peerID,
|
||||
timestamp: timestamp(now),
|
||||
signature: Data(repeating: 0xEE, count: 64)
|
||||
)
|
||||
|
||||
let recorder = Recorder()
|
||||
recorder.existingNoisePublicKey = noiseKey
|
||||
recorder.existingSigningPublicKey = Data(repeating: 0x99, count: 32)
|
||||
let handler = makeHandler(recorder: recorder, now: now)
|
||||
|
||||
handler.handle(packet, from: peerID)
|
||||
|
||||
#expect(recorder.persistedSigningKeyQueries.isEmpty)
|
||||
#expect(recorder.upsertCalls.count == 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
func registryPinRejectionSkipsTopologyAndIdentityPersistence() throws {
|
||||
let now = Date(timeIntervalSince1970: 1_000)
|
||||
let noiseKey = Data(repeating: 0x9C, count: 32)
|
||||
let peerID = PeerID(publicKey: noiseKey)
|
||||
let packet = try makeAnnouncePacket(
|
||||
noisePublicKey: noiseKey,
|
||||
peerID: peerID,
|
||||
timestamp: timestamp(now),
|
||||
signature: Data(repeating: 0xEE, count: 64),
|
||||
directNeighbors: [Data(repeating: 0xAB, count: 8)]
|
||||
)
|
||||
|
||||
// Pre-barrier trust check sees no pinned key (e.g. concurrent race),
|
||||
// but the registry itself refuses to replace its pinned signing key.
|
||||
let recorder = Recorder()
|
||||
recorder.upsertResult = nil
|
||||
let handler = makeHandler(recorder: recorder, now: now)
|
||||
|
||||
handler.handle(packet, from: peerID)
|
||||
|
||||
#expect(recorder.upsertCalls.count == 1)
|
||||
#expect(recorder.persistedIdentities.isEmpty)
|
||||
#expect(recorder.topologyUpdates.isEmpty)
|
||||
#expect(recorder.uiEventDeliveries.count == 1)
|
||||
#expect(recorder.uiEventDeliveries.first?.notifyPeerConnected == false)
|
||||
#expect(recorder.afterglowDelays.isEmpty)
|
||||
}
|
||||
|
||||
@Test
|
||||
func keyMismatchWithExistingPeerKeepsAnnounceUnverified() throws {
|
||||
let now = Date(timeIntervalSince1970: 1_000)
|
||||
@@ -506,6 +675,253 @@ struct BLEAnnounceHandlerTests {
|
||||
#expect(recorder.uiEventDeliveries.first?.notifyPeerConnected == false)
|
||||
}
|
||||
|
||||
@Test
|
||||
func attackerReplayingVictimNoiseKeyWithOwnSigningKeyIsRejectedEndToEnd() throws {
|
||||
// Real crypto: the attacker crafts a fully self-consistent announce
|
||||
// (victim's noiseKey/peerID, attacker's signing key and nickname,
|
||||
// valid packet signature made with the attacker's key). Without
|
||||
// signing-key pinning this used to overwrite the victim's registry
|
||||
// entry and persisted identity.
|
||||
let victim = NoiseEncryptionService(keychain: MockKeychain())
|
||||
let attacker = NoiseEncryptionService(keychain: MockKeychain())
|
||||
let victimNoiseKey = victim.getStaticPublicKeyData()
|
||||
let peerID = PeerID(publicKey: victimNoiseKey)
|
||||
let now = Date()
|
||||
|
||||
final class RegistryBox {
|
||||
var registry = BLEPeerRegistry()
|
||||
var persistedIdentities: [AnnouncementPacket] = []
|
||||
}
|
||||
let box = RegistryBox()
|
||||
|
||||
let environment = BLEAnnounceHandlerEnvironment(
|
||||
localPeerID: { PeerID(str: "0102030405060708") },
|
||||
messageTTL: TransportConfig.messageTTLDefault,
|
||||
now: { now },
|
||||
existingPeerKeys: { peerID in
|
||||
let info = box.registry.info(for: peerID)
|
||||
return (info?.noisePublicKey, info?.signingPublicKey)
|
||||
},
|
||||
persistedSigningPublicKey: { _ in nil },
|
||||
verifySignature: { packet, signingPublicKey in
|
||||
victim.verifyPacketSignature(packet, publicKey: signingPublicKey)
|
||||
},
|
||||
linkState: { _ in (hasPeripheral: true, hasCentral: false) },
|
||||
linkBoundToOtherPeer: { _, _ in false },
|
||||
withRegistryBarrier: { body in body() },
|
||||
upsertVerifiedAnnounce: { peerID, announcement, isConnected, now in
|
||||
box.registry.upsertVerifiedAnnounce(
|
||||
peerID: peerID,
|
||||
nickname: announcement.nickname,
|
||||
noisePublicKey: announcement.noisePublicKey,
|
||||
signingPublicKey: announcement.signingPublicKey,
|
||||
isConnected: isConnected,
|
||||
now: now
|
||||
)
|
||||
},
|
||||
shouldEmitReconnectLog: { _, _ in false },
|
||||
updateTopology: { _, _ in },
|
||||
persistIdentity: { announcement in
|
||||
box.persistedIdentities.append(announcement)
|
||||
},
|
||||
dedupContains: { _ in true },
|
||||
dedupMarkProcessed: { _ in },
|
||||
deliverAnnounceUIEvents: { _, _, _ in },
|
||||
trackPacketSeen: { _ in },
|
||||
sendAnnounceBack: {},
|
||||
scheduleAfterglow: { _ in }
|
||||
)
|
||||
let handler = BLEAnnounceHandler(environment: environment)
|
||||
|
||||
func makeSignedAnnounce(nickname: String, signer: NoiseEncryptionService) throws -> BitchatPacket {
|
||||
let announcement = AnnouncementPacket(
|
||||
nickname: nickname,
|
||||
noisePublicKey: victimNoiseKey,
|
||||
signingPublicKey: signer.getSigningPublicKeyData(),
|
||||
directNeighbors: nil
|
||||
)
|
||||
let payload = try #require(announcement.encode())
|
||||
let packet = BitchatPacket(
|
||||
type: MessageType.announce.rawValue,
|
||||
senderID: Data(hexString: peerID.id) ?? Data(),
|
||||
recipientID: nil,
|
||||
timestamp: UInt64(now.timeIntervalSince1970 * 1000),
|
||||
payload: payload,
|
||||
signature: nil,
|
||||
ttl: TransportConfig.messageTTLDefault
|
||||
)
|
||||
return try #require(signer.signPacket(packet))
|
||||
}
|
||||
|
||||
// Legitimate announce from the victim is accepted and pinned.
|
||||
let victimAnnounce = try makeSignedAnnounce(nickname: "victim", signer: victim)
|
||||
handler.handle(victimAnnounce, from: peerID)
|
||||
|
||||
#expect(box.registry.info(for: peerID)?.nickname == "victim")
|
||||
#expect(box.registry.info(for: peerID)?.signingPublicKey == victim.getSigningPublicKeyData())
|
||||
#expect(box.persistedIdentities.count == 1)
|
||||
|
||||
// Attacker announce with a valid self-signature must be rejected.
|
||||
let attackerAnnounce = try makeSignedAnnounce(nickname: "attacker", signer: attacker)
|
||||
handler.handle(attackerAnnounce, from: peerID)
|
||||
|
||||
#expect(box.registry.info(for: peerID)?.nickname == "victim")
|
||||
#expect(box.registry.info(for: peerID)?.signingPublicKey == victim.getSigningPublicKeyData())
|
||||
#expect(box.persistedIdentities.count == 1)
|
||||
|
||||
// The victim's subsequent announces (same pinned key) still work.
|
||||
let victimRename = try makeSignedAnnounce(nickname: "victim-renamed", signer: victim)
|
||||
handler.handle(victimRename, from: peerID)
|
||||
|
||||
#expect(box.registry.info(for: peerID)?.nickname == "victim-renamed")
|
||||
#expect(box.persistedIdentities.count == 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
func signingKeyPinSurvivesRegistryEvictionAndRestartEndToEnd() throws {
|
||||
// Real crypto + real persistence: the victim announces and gets
|
||||
// pinned, then the registry entry disappears (offline-peer eviction
|
||||
// via reconcileConnectivity, or app restart which starts with an
|
||||
// empty registry). The attacker replays the victim's
|
||||
// noiseKey/peerID with their own signing key and a valid
|
||||
// self-signature — the persisted identity must still block the
|
||||
// takeover, and must not be overwritten. The victim (same signing
|
||||
// key) must be re-accepted.
|
||||
let victim = NoiseEncryptionService(keychain: MockKeychain())
|
||||
let attacker = NoiseEncryptionService(keychain: MockKeychain())
|
||||
let victimNoiseKey = victim.getStaticPublicKeyData()
|
||||
let peerID = PeerID(publicKey: victimNoiseKey)
|
||||
let now = Date()
|
||||
|
||||
let identityKeychain = MockKeychain()
|
||||
let identityManager = SecureIdentityStateManager(identityKeychain)
|
||||
|
||||
final class RegistryBox {
|
||||
var registry = BLEPeerRegistry()
|
||||
}
|
||||
let box = RegistryBox()
|
||||
|
||||
func makeEnvironment(identityManager: SecureIdentityStateManager) -> BLEAnnounceHandlerEnvironment {
|
||||
BLEAnnounceHandlerEnvironment(
|
||||
localPeerID: { PeerID(str: "0102030405060708") },
|
||||
messageTTL: TransportConfig.messageTTLDefault,
|
||||
now: { now },
|
||||
existingPeerKeys: { peerID in
|
||||
let info = box.registry.info(for: peerID)
|
||||
return (info?.noisePublicKey, info?.signingPublicKey)
|
||||
},
|
||||
// Mirrors the BLEService wiring: fall back to the persisted
|
||||
// cryptographic identity.
|
||||
persistedSigningPublicKey: { peerID in
|
||||
identityManager.getCryptoIdentitiesByPeerIDPrefix(peerID)
|
||||
.compactMap { $0.signingPublicKey }
|
||||
.first
|
||||
},
|
||||
verifySignature: { packet, signingPublicKey in
|
||||
victim.verifyPacketSignature(packet, publicKey: signingPublicKey)
|
||||
},
|
||||
linkState: { _ in (hasPeripheral: true, hasCentral: false) },
|
||||
linkBoundToOtherPeer: { _, _ in false },
|
||||
withRegistryBarrier: { body in body() },
|
||||
upsertVerifiedAnnounce: { peerID, announcement, isConnected, now in
|
||||
box.registry.upsertVerifiedAnnounce(
|
||||
peerID: peerID,
|
||||
nickname: announcement.nickname,
|
||||
noisePublicKey: announcement.noisePublicKey,
|
||||
signingPublicKey: announcement.signingPublicKey,
|
||||
isConnected: isConnected,
|
||||
now: now
|
||||
)
|
||||
},
|
||||
shouldEmitReconnectLog: { _, _ in false },
|
||||
updateTopology: { _, _ in },
|
||||
persistIdentity: { announcement in
|
||||
identityManager.upsertCryptographicIdentity(
|
||||
fingerprint: announcement.noisePublicKey.sha256Fingerprint(),
|
||||
noisePublicKey: announcement.noisePublicKey,
|
||||
signingPublicKey: announcement.signingPublicKey,
|
||||
claimedNickname: announcement.nickname
|
||||
)
|
||||
},
|
||||
dedupContains: { _ in true },
|
||||
dedupMarkProcessed: { _ in },
|
||||
deliverAnnounceUIEvents: { _, _, _ in },
|
||||
trackPacketSeen: { _ in },
|
||||
sendAnnounceBack: {},
|
||||
scheduleAfterglow: { _ in }
|
||||
)
|
||||
}
|
||||
let handler = BLEAnnounceHandler(environment: makeEnvironment(identityManager: identityManager))
|
||||
|
||||
func makeSignedAnnounce(nickname: String, signer: NoiseEncryptionService) throws -> BitchatPacket {
|
||||
let announcement = AnnouncementPacket(
|
||||
nickname: nickname,
|
||||
noisePublicKey: victimNoiseKey,
|
||||
signingPublicKey: signer.getSigningPublicKeyData(),
|
||||
directNeighbors: nil
|
||||
)
|
||||
let payload = try #require(announcement.encode())
|
||||
let packet = BitchatPacket(
|
||||
type: MessageType.announce.rawValue,
|
||||
senderID: Data(hexString: peerID.id) ?? Data(),
|
||||
recipientID: nil,
|
||||
timestamp: UInt64(now.timeIntervalSince1970 * 1000),
|
||||
payload: payload,
|
||||
signature: nil,
|
||||
ttl: TransportConfig.messageTTLDefault
|
||||
)
|
||||
return try #require(signer.signPacket(packet))
|
||||
}
|
||||
|
||||
func persistedIdentity() -> CryptographicIdentity? {
|
||||
// queue.sync read; fences the manager's pending barrier writes.
|
||||
identityManager.getCryptoIdentitiesByPeerIDPrefix(peerID).first
|
||||
}
|
||||
|
||||
// 1. Victim announces: pinned in the registry and persisted.
|
||||
handler.handle(try makeSignedAnnounce(nickname: "victim", signer: victim), from: peerID)
|
||||
#expect(box.registry.info(for: peerID)?.signingPublicKey == victim.getSigningPublicKeyData())
|
||||
#expect(persistedIdentity()?.signingPublicKey == victim.getSigningPublicKeyData())
|
||||
|
||||
// 2. Registry entry disappears (eviction / restart).
|
||||
_ = box.registry.remove(peerID)
|
||||
#expect(box.registry.info(for: peerID) == nil)
|
||||
|
||||
// 3. Attacker replay with own signing key: rejected via the persisted
|
||||
// pin, and neither the registry nor the persisted identity change.
|
||||
handler.handle(try makeSignedAnnounce(nickname: "attacker", signer: attacker), from: peerID)
|
||||
#expect(box.registry.info(for: peerID) == nil)
|
||||
#expect(persistedIdentity()?.signingPublicKey == victim.getSigningPublicKeyData())
|
||||
#expect(identityManager.getSocialIdentity(for: victimNoiseKey.sha256Fingerprint())?.claimedNickname == "victim")
|
||||
|
||||
// 4. Victim re-announces with the same signing key: accepted again.
|
||||
handler.handle(try makeSignedAnnounce(nickname: "victim", signer: victim), from: peerID)
|
||||
#expect(box.registry.info(for: peerID)?.nickname == "victim")
|
||||
#expect(box.registry.info(for: peerID)?.signingPublicKey == victim.getSigningPublicKeyData())
|
||||
|
||||
// 5. Simulated app restart: a fresh identity manager reloads the pin
|
||||
// from the (mock) keychain, and a fresh registry starts empty. The
|
||||
// attacker replay is still rejected.
|
||||
identityManager.forceSave()
|
||||
let reloadedManager = SecureIdentityStateManager(identityKeychain)
|
||||
#expect(
|
||||
reloadedManager.getCryptoIdentitiesByPeerIDPrefix(peerID).first?.signingPublicKey
|
||||
== victim.getSigningPublicKeyData()
|
||||
)
|
||||
box.registry = BLEPeerRegistry()
|
||||
let restartedHandler = BLEAnnounceHandler(environment: makeEnvironment(identityManager: reloadedManager))
|
||||
restartedHandler.handle(try makeSignedAnnounce(nickname: "attacker", signer: attacker), from: peerID)
|
||||
#expect(box.registry.info(for: peerID) == nil)
|
||||
#expect(
|
||||
reloadedManager.getCryptoIdentitiesByPeerIDPrefix(peerID).first?.signingPublicKey
|
||||
== victim.getSigningPublicKeyData()
|
||||
)
|
||||
|
||||
// ...while the victim is accepted after the restart.
|
||||
restartedHandler.handle(try makeSignedAnnounce(nickname: "victim", signer: victim), from: peerID)
|
||||
#expect(box.registry.info(for: peerID)?.signingPublicKey == victim.getSigningPublicKeyData())
|
||||
}
|
||||
|
||||
private func expectNoSideEffects(_ recorder: Recorder) {
|
||||
#expect(recorder.barrierCount == 0)
|
||||
#expect(recorder.upsertCalls.isEmpty)
|
||||
|
||||
Reference in New Issue
Block a user