5.3 KiB
Sécurité — iMedia TV+
Principes
- Aucun secret en clair sur le disque ou dans les logs
- Chiffrement de bout en bout du stockage local des credentials
- Isolation : l'application ne partage pas les credentials avec d'autres apps
- Principle of least privilege : permissions Android minimales
Stockage des credentials
Implémentation
// 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.
// 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.dpour 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
// 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
# 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
<!-- 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 :
- Accepter HTTP pour la compatibilité
- Préférer HTTPS si le serveur le supporte
- 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 :
// 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