fix(app): accept incoming calls, restore from recovery phrase, duress session isolation, message loss

Calls: `call` was missing from the known content types, so every offer was
acknowledged and dropped; the call screen column now fills the width (the
avatar jumped left after hang-up); a ring started from a push ends after
12 s when no offer arrives; STUN fallback when no TURN credentials.
Registration: X-Sepot-Identity-Proof lets a new device reclaim an existing
identity; retry backs off from 30 s to 10 min and exposes a conflict flag.
Prekeys: PrekeysLow triggers a batch of 50 one-time prekeys via POST
/v1/keys; the signed prekey rotates weekly, the Kyber prekey monthly.
Messaging: crypto operations run serially (concurrent encrypts derived the
same message key); stop() waits for in-flight deliveries (auto-lock between
decrypt and persist lost messages); disappearing-message cleanup no longer
overwrites fresh messages; a push wake-up reconnects stale connections; the
outbox carries polls and mentions; length limits on wire input.
Storage: a duress session never exposes the real identity or phrase, does
not start networking and cannot touch the keystore; deleting data in a
duress session leaves the real account alone; PIN guessing is throttled
(5 misses, then 30 s doubling up to 15 min); the duress blob shares the salt
so unlock time does not reveal which PIN was entered; iOS keychain items are
this-device only; biometrics are biometric-only; screen security defaults
to on and is applied after unlock; tapjacking filter in MainActivity.
Tests: content types, shared-salt unwrap, duress isolation.
This commit is contained in:
Julius Joska
2026-10-09 14:02:33 +02:00
parent 8b452a5593
commit e3a2378c34
33 changed files with 1055 additions and 162 deletions
@@ -1,5 +1,6 @@
package cz.sepot.sepot
import android.content.pm.ApplicationInfo
import android.os.Bundle
import android.view.WindowManager
import io.flutter.embedding.android.FlutterFragmentActivity
@@ -7,10 +8,20 @@ import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity : FlutterFragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Soukromí: zákaz screenshotů a náhledu obsahu v přepínači aplikací
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE,
)
// Soukromí: zákaz screenshotů a náhledu obsahu v přepínači aplikací.
// Výchozí stav; Dart přepínač „Zabezpečení obrazovky" ho může po
// odemčení vypnout (default zapnuto, jako u Signalu).
// Debug build: screenshoty povolené kvůli QA na emulátoru (adb screencap
// by u FLAG_SECURE okna selhal); release vždy chráněný.
val debuggable = (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
if (!debuggable) {
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE,
)
}
// Tapjacking: dotyky přes překryvné okno jiné appky (částečně
// průhledný overlay nad PIN klávesnicí) se zahodí.
window.decorView.filterTouchesWhenObscured = true
}
}