160 lines
5.3 KiB
Markdown
160 lines
5.3 KiB
Markdown
# Sécurité — iMedia TV+
|
|
|
|
## Principes
|
|
|
|
1. **Aucun secret en clair sur le disque ou dans les logs**
|
|
2. **Chiffrement de bout en bout** du stockage local des credentials
|
|
3. **Isolation** : l'application ne partage pas les credentials avec d'autres apps
|
|
4. **Principle of least privilege** : permissions Android minimales
|
|
|
|
## Stockage des credentials
|
|
|
|
### Implémentation
|
|
|
|
```kotlin
|
|
// data/storage/SecureStorage.kt
|
|
import androidx.security.crypto.EncryptedSharedPreferences
|
|
import androidx.security.crypto.MasterKey
|
|
|
|
class SecureStorage(context: Context) {
|
|
private val masterKey = MasterKey.Builder(context)
|
|
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
|
.build()
|
|
|
|
private val prefs = EncryptedSharedPreferences.create(
|
|
context,
|
|
"imedia_secure_prefs",
|
|
masterKey,
|
|
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
|
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
|
)
|
|
|
|
fun saveCredentials(server: String, username: String, password: String) {
|
|
prefs.edit()
|
|
.putString("server_url", server)
|
|
.putString("username", username)
|
|
.putString("password", password)
|
|
.apply()
|
|
}
|
|
|
|
fun getCredentials(): Credentials? {
|
|
val server = prefs.getString("server_url", null)
|
|
val username = prefs.getString("username", null)
|
|
val password = prefs.getString("password", null)
|
|
return if (server != null && username != null && password != null) {
|
|
Credentials(server, username, password)
|
|
} else null
|
|
}
|
|
|
|
fun clearCredentials() {
|
|
prefs.edit().clear().apply()
|
|
}
|
|
}
|
|
```
|
|
|
|
Pour les favoris et la chaîne épinglée (données non sensibles), la version 1 utilisait des `SharedPreferences` classiques — acceptable. Si on veut tout unifier, on utilise `EncryptedSharedPreferences` partout.
|
|
|
|
### Ce qui est chiffré
|
|
|
|
| Donnée | Niveau | Sensibilité |
|
|
|--------|--------|-------------|
|
|
| Server URL | AES-256 GCM | Haute |
|
|
| Username | AES-256 GCM | Haute |
|
|
| Password | AES-256 GCM | Critique |
|
|
| Favoris | AES-256 SIV keys | Moyenne |
|
|
| Chaîne épinglée | AES-256 SIV keys | Basse |
|
|
|
|
## Fichier de mapping des endpoints
|
|
|
|
**Important :** les URLs Xtream construites avec le builder ne doivent pas non plus être logguées.
|
|
|
|
```kotlin
|
|
// util/XtreamUrlBuilder.kt
|
|
class XtreamUrlBuilder(private val credentials: Credentials) {
|
|
|
|
fun liveUrl(streamId: Int): String =
|
|
"${credentials.server}/live/${credentials.username}/${credentials.password}/$streamId.m3u8"
|
|
|
|
// ⚠️ Attention : cette méthode est appelée par PlayerScreen
|
|
// — NE PAS logger son résultat
|
|
}
|
|
```
|
|
|
|
## Règles strictes
|
|
|
|
### ✅ À faire
|
|
|
|
- Utiliser `Timber.d` pour les logs de debug (supprimés en release par ProGuard)
|
|
- Vérifier les inputs utilisateur avant de les envoyer (URL bien formée)
|
|
- Timeout réseau configuré (15s connect, 30s read)
|
|
- Nettoyer les credentials à la déconnexion
|
|
- Minify + ProGuard en release (obfuscation)
|
|
|
|
### ❌ Interdit
|
|
|
|
```kotlin
|
|
// JAMAIS :
|
|
Log.d("Auth", "Login with $username / $password")
|
|
Timber.d("Server URL: $serverUrl")
|
|
println("Password is: $password")
|
|
Log.e("API", "Stream URL: ${urlBuilder.liveUrl(42)}")
|
|
|
|
// En release, ces logs ne devraient pas survivre,
|
|
// mais ne prenez pas le risque en debug non plus.
|
|
```
|
|
|
|
## ProGuard
|
|
|
|
```pro
|
|
# app/proguard-rules.pro
|
|
# Sécurité : ne pas exposer les données chiffrées
|
|
-keepclassmembers class ma.tkgraph.xtreamtv.data.storage.** { *; }
|
|
|
|
# OkHttp / Retrofit déjà couverts par leurs propres rules
|
|
# Ne pas garder de logs en release
|
|
-assumenosideeffects class timber.log.Timber {
|
|
public static *** d(...);
|
|
public static *** v(...);
|
|
}
|
|
```
|
|
|
|
## Permissions Android
|
|
|
|
```xml
|
|
<!-- AndroidManifest.xml — le strict minimum -->
|
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
<!-- ⚠️ PAS de READ_EXTERNAL_STORAGE ni WRITE_EXTERNAL_STORAGE -->
|
|
```
|
|
|
|
L'application ne lit pas de fichiers locaux et ne sauvegarde que des données dans son propre espace de stockage privé — pas besoin de permissions de stockage.
|
|
|
|
## HTTPS
|
|
|
|
Bien que le protocole Xtream utilise souvent HTTP (non chiffré), l'application doit :
|
|
1. **Accepter HTTP** pour la compatibilité
|
|
2. **Préférer HTTPS** si le serveur le supporte
|
|
3. **Ne pas désactiver la validation** SSL/TLS (pas de `TrustAllCertificates`)
|
|
|
|
Si le fournisseur Xtream le permet, les credentials transitent en HTTPS, ce qui protège le mot de passe en transit.
|
|
|
|
## Audit
|
|
|
|
Une amélioration future possible : `Sentry` ou `Firebase Crashlytics` pour monitorer les crashs. Attention cependant à ne pas envoyer de données sensibles dans les rapports :
|
|
|
|
```kotlin
|
|
// Dans Application.onCreate()
|
|
FirebaseCrashlytics.getInstance().setCustomKey("screen", "login")
|
|
// ❌ PAS FirebaseCrashlytics.getInstance().setCustomKey("username", username)
|
|
```
|
|
|
|
## Checklist de sécurité avant release
|
|
|
|
- [ ] Aucun log de credentials dans le code (grep "password" dans les fichiers source)
|
|
- [ ] EncryptedSharedPreferences en place, pas de fallback SharedPreferences
|
|
- [ ] Timeouts réseau configurés
|
|
- [ ] Permissions minimales (seulement INTERNET + ACCESS_NETWORK_STATE)
|
|
- [ ] ProGuard activé en release
|
|
- [ ] Pas de validation SSL désactivée
|
|
- [ ] Gestion de la déconnexion efface TOUTES les données
|