mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-09-19 04:59:59 +00:00
Reject future-dated packets, not just future-dated LEAVE
validatePacket bounds the timestamp of exactly one packet type. LEAVE
gets a symmetric five-minute window; ANNOUNCE gets one in
AnnouncementIdentityValidator. Everything else — MESSAGE, FRAGMENT,
FILE_TRANSFER, VOICE_FRAME — is admitted with any timestamp its sender
signs, because a signature authenticates the sender's choice without
sanity-checking it.
That timestamp then decides ordering downstream. GossipSyncManager builds
its advertised sync filter with sortByDescending { it.timestamp } and
takes the top N, so broadcasts stamped far in the future occupy every
slot and keep real traffic out of gossip sync — not just locally, but for
every neighbor that syncs against us. The public timeline orders on the
same field.
Bound the future direction for every type at the allowance that already
admits the peer's announcement. A device skewed further than that cannot
get an ANNOUNCE verified, so it has no working session today and nothing
that currently works starts failing. LEAVE keeps its tighter symmetric
window.
The past deliberately stays unbounded: store-and-forward replays packets
cached for up to twelve hours, and bounding it would drop them silently.
This commit is contained in:
parent
5156f7de89
commit
5e4afae4cd
@ -8,7 +8,11 @@ import com.bitchat.android.util.toHexString
|
||||
|
||||
/** Canonical, side-effect-free preflight for a self-signed mesh announcement. */
|
||||
object AnnouncementIdentityValidator {
|
||||
private const val MAX_CLOCK_SKEW_MS = 10 * 60 * 1_000L
|
||||
/**
|
||||
* Clock skew tolerated on a signed announcement. Shared with [SecurityManager] so no packet
|
||||
* type is held to a stricter future bound than the announcement that admits the peer.
|
||||
*/
|
||||
internal const val MAX_CLOCK_SKEW_MS = 10 * 60 * 1_000L
|
||||
|
||||
fun verify(
|
||||
packet: BitchatPacket,
|
||||
|
||||
@ -57,6 +57,29 @@ class SecurityManager(private val encryptionService: EncryptionService, private
|
||||
val currentTime = System.currentTimeMillis()
|
||||
val messageType = MessageType.fromValue(packet.type)
|
||||
|
||||
// A signed packet's timestamp is authenticated as the sender's choice, not sanity-checked,
|
||||
// and it survives into every downstream ordering decision. GossipSyncManager builds its
|
||||
// sync filter from `sortByDescending { it.timestamp }`, so future-dated broadcasts take
|
||||
// every advertised slot and crowd real traffic out of gossip sync across the mesh; the
|
||||
// public timeline orders on the same field. Only ANNOUNCE was bounded, in
|
||||
// AnnouncementIdentityValidator.
|
||||
//
|
||||
// Bound the future direction for every type, at the same allowance that admits the peer's
|
||||
// announcement — a device skewed further than this already cannot be verified, so nothing
|
||||
// that works today starts failing. The past stays open: store-and-forward replays packets
|
||||
// cached for up to twelve hours.
|
||||
val nowForSkew = currentTime.coerceAtLeast(0).toULong()
|
||||
if (packet.timestamp > nowForSkew &&
|
||||
packet.timestamp - nowForSkew >
|
||||
AnnouncementIdentityValidator.MAX_CLOCK_SKEW_MS.toULong()
|
||||
) {
|
||||
Log.w(
|
||||
TAG,
|
||||
"Dropping future-dated ${messageType?.name ?: packet.type} from $peerID"
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
// LEAVE mutates presence immediately and cannot be safely replayed after the in-memory
|
||||
// duplicate cache expires (or after an app restart). Bound it to the same five-minute
|
||||
// security window used for duplicate retention while tolerating symmetric clock skew.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user