Add Android Secure Capture & Timestamp – Technical Blueprint

urko 2025-05-11 21:08:38 +01:00
parent dc82d318ce
commit f95c8d7620

@ -0,0 +1,175 @@
## 1. Product vision
A minimal **Android app (Kotlin)** that can, on user demand, **capture video + audio in the background**, stream it live through **WebRTC** to a backend, and locally buffer a losstolerant copy. On the server side we **hash** each media chunk and immediately obtain a **qualified timestamp (QTSP)** or blockchain attestation, preserving an endtoend, courtready chain of custody.
---
## 2. Functional requirements
|  ID | Requirement | Notes |
| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|  FR1 | User can start/stop a secure recording session from a single UI toggle. | Must show foreground notification due to Android 13+ background recording rules. |
|  FR2 | App records **front or rear camera** and **microphone** simultaneously. | 1080p @ 30fps baseline. |
|  FR3 | App survives screenoff / app switch (uses **Foreground Service**). | "MediaProjection + CameraX" or pure CameraX route. |
|  FR4 | Each encoded frame is sent via **WebRTC data/media channel** to server. | SRTP for privacy. |
|  FR5 | If network is lost, chunks are stored encrypted in app sandbox and retried with **WorkManager**. | |
|  FR6 | Server receives RTP, hashes every GOP with SHA256, requests timestamp, stores tuple *(hash, RFC 3161 TST, chunk path)* in DB + S3like object store. | |
|  FR7 | App exposes a "Verify" screen: download TST receipt & proof for any session, recompute local hash, and show green / red indicator. | |
---
## 3. Highlevel architecture
```
┌───────────────┐ SRTP / ICE ┌──────────────────┐
│ Android App │ ─────── peer connection ─► │ Signalling + │
│ Kotlin + │ ◄────── (TURN fallback) ◄─ │ Media SFU/Router│
│ Foreground │ │ (Janus / Pion) │
│ Service │ REST/WebSocket │ │
└──────▲────────┘ │ └─────────▲────────┘
│ local AES256 store │ SHA256(chunk) │ gRPC
│ (Room + SQLCipher) ▼ ▼
┌──────┴────────┐ ┌───────────────┐ ┌───────────────┐
│ WorkManager │ │ Hasher + │ TSP │ Qualified │
│ retry queue │ │ TimeStamper │◄────►│ TS Authority │
└───────────────┘ └───────────────┘ └───────────────┘
```
* **Media path**: CameraX → MediaCodec (H.264) → `SurfaceTexture` → WebRTC **VideoTrack**.
* **Audio path**: `AudioRecord` (OPUS) → WebRTC **AudioTrack**.
---
## 4. Android implementation details
### 4.1 Permissions & privacy
* `CAMERA`, `RECORD_AUDIO`, `FOREGROUND_SERVICE_CAMERA`, `POST_NOTIFICATIONS` (Tiramisu+).
* Runtime request flow wrapped in coroutine `suspend fun requestPermissions()`.
### 4.2 Foreground RecordingService
```kotlin
@ForegroundService
class RecordingService : Service() {
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
override fun onStartCommand(i: Intent?, flags: Int, startId: Int): Int {
startForeground(NOTIF_ID, buildNotif())
scope.launch { startCaptureSession() }
return START_STICKY
}
private suspend fun startCaptureSession() {
val camera = CameraXInitializer(cameraSelector).open()
val rtcClient = WebRtcClient(iceServers)
val videoSrc = CameraXVideoSource(camera)
rtcClient.addTrack(videoSrc.track)
rtcClient.addTrack(MicAudioSource().track)
rtcClient.connect(signalServer)
}
override fun onDestroy() {
scope.cancel();
super.onDestroy()
}
}
```
*Notification must be **userdismissible** and display recording timer — meets Android policy.*
### 4.3 Local buffering & WorkManager
```kotlin
class ChunkWriter(private val ctx: Context){
suspend fun saveChunk(bytes: ByteArray){
val encrypted = crypto.encrypt(bytes)
room.chunkDao().insert(ChunkEntity(ts=now(), blob=encrypted))
}
}
```
`PeriodicWorkRequest` checks DB → upload to server via REST → on success delete row.
---
## 5. Serverside pipeline (Golang example)
```go
// Media handler (Pion SFU hook)
func onRTPPacket(pkt *rtp.Packet){
chunkBuf.Write(pkt.Payload)
if gopFinished(pkt){
h := sha256.Sum256(chunkBuf.Bytes())
tst, _ := tspClient.Timestamp(h[:])
store.Save(chunkBuf.Bytes(), h[:], tst)
chunkBuf.Reset()
}
}
```
*Timestamp sources*: 
1. **QTSP (eIDAS)** Camerfirma TSA, FNMT, Poste Italiane, etc.
2. **Blockchain fallback** embed hash in Bitcoin OP\_RETURN; use public API for proof.
---
## 6. Performance & energy
| Component | Tradeoff | Recommendation |
| ------------------------- | --------------------------------------------------------- | ------------------------------------------------------- |
| CameraX vs. MediaRecorder | CameraX gives **Surface** for WebRTC; slightly higher CPU | Acceptable on devices ≥ Snapdragon 730 |
| Video codec | H.264 Baseline profile | Widely HWaccelerated, 1Mbps @1080p 30fps ≈ 2h ≈ 1GB |
| Audio | OPUS 48kHz 16kbps | Low bandwidth, good quality |
| Encryption | SRTP inline; local AES256 | negligible on ARMv8 AESNI |
---
## 7. Security considerations
* **ZeroConfig Keys**: generate on first run, stored in Android Keystore.
* **Pinning**: TLS cert pin to signalling + REST host.
* **Tamperevident log**: every state change appended to SQLCipher DB with incremental hash chain.
---
## 8. Roadmap
1. **P0** Local capture + save MP4.
2. **P1** Add WebRTC streaming via public TURN.
3. **P2** Server hash + QTSP integration.
4. **P3** Verification UI + export proof bundle (ZIP: video + JSON hash + TST).
5. **P4** Battery optimisation, Dozemode job scheduling.
---
## 9. References / Tooling
* *CameraX* Jetpack lib.
* *WebRTCKMP* for multiplatform client.
* **Pion SFU** (Go) or **Janus** (C) backend.
* **RFC 3161** TimeStamp Protocol.
* **ETS I EN 319421** QTSP TSA policy.
* **SQLCipher** for ondevice encrypted DB.
* **gRPC** for lowlatency hash receipt.
---
### Appendix A Minimal Client ↔ Server handshake (pseudosequence)
```
User taps Record
├─► RecordingService.startForeground()
│ ├─ CameraX open & startPreview
│ ├─ WebRtcClient.createOffer()
│ └─ Signal via WebSocket
Signalling Server ↔ offers/answers ICE
RTP/RTCP flow
Hasher splits GOP → SHA256 → TSA → OK
Server sends `hashReceipt` JSON over datachannel ➜ App shows ✅
```
*End of document*