Add Android Secure Capture & Timestamp – Technical Blueprint
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 loss‑tolerant copy. On the server side we **hash** each media chunk and immediately obtain a **qualified timestamp (QTSP)** or blockchain attestation, preserving an end‑to‑end, court‑ready chain of custody.
|
||||
|
||||
---
|
||||
|
||||
## 2. Functional requirements
|
||||
|
||||
| ID | Requirement | Notes |
|
||||
| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
| FR‑1 | User can start/stop a secure recording session from a single UI toggle. | Must show foreground notification due to Android 13+ background recording rules. |
|
||||
| FR‑2 | App records **front or rear camera** and **microphone** simultaneously. | 1080 p @ 30 fps baseline. |
|
||||
| FR‑3 | App survives screen‑off / app switch (uses **Foreground Service**). | "MediaProjection + CameraX" or pure CameraX route. |
|
||||
| FR‑4 | Each encoded frame is sent via **WebRTC data/media channel** to server. | SRTP for privacy. |
|
||||
| FR‑5 | If network is lost, chunks are stored encrypted in app sandbox and retried with **WorkManager**. | |
|
||||
| FR‑6 | Server receives RTP, hashes every GOP with SHA‑256, requests timestamp, stores tuple *(hash, RFC 3161 TST, chunk path)* in DB + S3‑like object store. | |
|
||||
| FR‑7 | App exposes a "Verify" screen: download TST receipt & proof for any session, recompute local hash, and show green / red indicator. | |
|
||||
|
||||
---
|
||||
|
||||
## 3. High‑level architecture
|
||||
|
||||
```
|
||||
┌───────────────┐ SRTP / ICE ┌──────────────────┐
|
||||
│ Android App │ ─────── peer connection ─► │ Signalling + │
|
||||
│ Kotlin + │ ◄────── (TURN fallback) ◄─ │ Media SFU/Router│
|
||||
│ Foreground │ │ (Janus / Pion) │
|
||||
│ Service │ REST/WebSocket │ │
|
||||
└──────▲────────┘ │ └─────────▲────────┘
|
||||
│ local AES‑256 store │ SHA‑256(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 **user‑dismissible** 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. Server‑side 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 | Trade‑off | Recommendation |
|
||||
| ------------------------- | --------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| CameraX vs. MediaRecorder | CameraX gives **Surface** for WebRTC; slightly higher CPU | Acceptable on devices ≥ Snapdragon 730 |
|
||||
| Video codec | H.264 Baseline profile | Widely HW‑accelerated, 1 Mbps @1080p 30fps ≈ 2 h ≈ 1 GB |
|
||||
| Audio | OPUS 48 kHz 16 kbps | Low bandwidth, good quality |
|
||||
| Encryption | SRTP inline; local AES‑256 | negligible on ARMv8 AES‑NI |
|
||||
|
||||
---
|
||||
|
||||
## 7. Security considerations
|
||||
|
||||
* **Zero‑Config Keys**: generate on first run, stored in Android Keystore.
|
||||
* **Pinning**: TLS cert pin to signalling + REST host.
|
||||
* **Tamper‑evident 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, Doze‑mode job scheduling.
|
||||
|
||||
---
|
||||
|
||||
## 9. References / Tooling
|
||||
|
||||
* *CameraX* Jetpack lib.
|
||||
* *WebRTC‑KMP* for multiplatform client.
|
||||
* **Pion SFU** (Go) or **Janus** (C) backend.
|
||||
* **RFC 3161** Time‑Stamp Protocol.
|
||||
* **ETS I EN 319 421** QTSP TSA policy.
|
||||
* **SQLCipher** for on‑device encrypted DB.
|
||||
* **gRPC** for low‑latency hash receipt.
|
||||
|
||||
---
|
||||
|
||||
### Appendix A – Minimal Client ↔ Server handshake (pseudo‑sequence)
|
||||
|
||||
```
|
||||
User taps Record
|
||||
│
|
||||
├─► RecordingService.startForeground()
|
||||
│ ├─ CameraX open & startPreview
|
||||
│ ├─ WebRtcClient.createOffer()
|
||||
│ └─ Signal via WebSocket
|
||||
│
|
||||
Signalling Server ↔ offers/answers ICE
|
||||
│
|
||||
RTP/RTCP flow
|
||||
│
|
||||
Hasher splits GOP → SHA‑256 → TSA → OK
|
||||
│
|
||||
Server sends `hashReceipt` JSON over data‑channel ➜ App shows ✅
|
||||
```
|
||||
|
||||
*End of document*
|
Loading…
x
Reference in New Issue
Block a user