Clone
1
Android Secure Capture & Timestamp – Technical Blueprint
urko edited this page 2025-05-11 21:08:38 +01:00
This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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

@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

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)

// 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