Merge dda024166fddf603d824a70f068a156ab842d9d1 into 9b84b361225facd8e623f25d76f889d3dc54a879

This commit is contained in:
krish rathi 2026-08-29 11:04:41 +00:00 committed by GitHub
commit f2e82a797c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 53 additions and 3 deletions

View File

@ -66,12 +66,24 @@ final class SecureNoiseSession: NoiseSession {
if messageCount >= messageThreshold {
return true
}
// Check if last activity was more than 30 minutes ago
// Check if last activity exceeded sessionTimeout
if Date().timeIntervalSince(lastActivityTime) > NoiseSecurityConstants.sessionTimeout {
return true
}
// A session that never goes idle (a long-lived conversation with
// steady traffic) keeps refreshing lastActivityTime forever, so the
// check above never fires -- only sessionStartTime tracks how close
// encrypt/decrypt are to the hard sessionExpired cutoff above. Firing
// at 90% of sessionTimeout leaves the rekey timer's polling interval
// (checked every 60s, see NoiseEncryptionService.rekeyCheckInterval)
// room to complete a full handshake well before the old session
// starts throwing sessionExpired on every call.
if Date().timeIntervalSince(sessionStartTime) > NoiseSecurityConstants.sessionTimeout * 0.9 {
return true
}
return false
}

View File

@ -893,6 +893,44 @@ struct NoiseCoverageTests {
}
}
@Test("A continuously active session still schedules a rekey before it hard-expires")
func continuouslyActiveSessionStillNeedsRenegotiationBeforeExpiry() throws {
// A session with steady traffic never goes idle, so lastActivityTime
// stays fresh right up to the moment sessionStartTime crosses the
// hard sessionTimeout cutoff above and encrypt/decrypt start throwing
// sessionExpired on every call. needsRenegotiation() has to notice
// the session is old on its own -- it can't rely on the idle check,
// because there is no idle time to find.
let session = SecureNoiseSession(
peerID: alicePeerID,
role: .initiator,
keychain: keychain,
localStaticKey: aliceStaticKey
)
let responder = SecureNoiseSession(
peerID: bobPeerID,
role: .responder,
keychain: keychain,
localStaticKey: bobStaticKey
)
try establishSessions(initiator: session, responder: responder)
// Just under the hard cutoff, but past the 90% rekey threshold.
session.setSessionStartTimeForTesting(
Date().addingTimeInterval(-(NoiseSecurityConstants.sessionTimeout * 0.95))
)
session.setLastActivityTimeForTesting(Date())
session.setMessageCountForTesting(0)
#expect(session.needsRenegotiation())
// The session is still within its hard cutoff, so it must keep
// working right up until a rekey actually replaces it -- the fix is
// to schedule the rekey earlier, not to expire the session sooner.
let ciphertext = try session.encrypt(Data("still working".utf8))
#expect(try responder.decrypt(ciphertext) == Data("still working".utf8))
}
@Test("Rate limiter handles global message caps and per-peer resets")
func rateLimiterGlobalMessageCapAndReset() async throws {
let globalLimiter = NoiseRateLimiter()