feat(security): SPKI pinning rotation Firebase + Play Integrity/App Attest + freerasp 7.5.1

P0-NEW-21 — SPKI Pinning service avec rotation Firebase Remote Config
  - Remplace ancien check CN par digest SHA-256 SPKI
  - Liste pins dynamique depuis Firebase RC (clé 'spki_pins')
  - Multi-pin (leaf + backup + intermediate)
  - Câblé dans ApiClient._configureSslPinning()

P0-NEW-22 — App Device Integrity (Play Integrity Android + App Attest iOS)
  - Token attestation court cache 60s
  - Bypass kDebugMode
  - Obligatoire audit BCEAO PI-SPI banking-grade

pubspec.yaml :
  - freerasp 7.0.0 → 7.5.1
  - +app_device_integrity 1.1.0
  - +firebase_core 3.6.0 + firebase_remote_config 5.1.3
This commit is contained in:
2026-04-25 01:27:44 +00:00
parent 37db88672b
commit 8356ccc0b0
4 changed files with 271 additions and 14 deletions

View File

@@ -8,6 +8,7 @@ import 'package:injectable/injectable.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../../app/app.dart';
import '../config/environment.dart';
import '../security/spki_pinning_service.dart';
import '../di/injection.dart';
import '../error/error_handler.dart';
import '../utils/logger.dart';
@@ -126,22 +127,20 @@ class ApiClient {
}
}
/// Configure SSL pinning pour les connexions prod.
/// Rejette tout certificat dont le subject/issuer ne correspond pas au CN attendu.
/// TODO avant go-live : remplacer le check CN par une vérification de hash de clé publique (SPKI).
/// Configure SSL pinning pour les connexions prod (P0-NEW-21, 2026-04-25).
///
/// Implémente un pinning par digest SHA-256 de SubjectPublicKeyInfo (SPKI), avec rotation
/// dynamique via Firebase Remote Config. Conforme aux recommandations 2026 :
/// - Plus de pinning de cert statique (rotation cert prod = brick app garanti)
/// - SPKI digest survit aux rotations tant que la clé publique reste la même
/// - Multi-pin (leaf + backup + intermediate) pour transitions sans downtime
///
/// Couplé à freeRASP + AppDeviceIntegrityService pour résister aux bypass Frida.
void _configureSslPinning() {
(_dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
final client = HttpClient();
client.badCertificateCallback = (cert, host, port) {
// Accepter uniquement si le host correspond à notre domaine prod
const allowedHost = 'api.lions.dev';
if (!host.endsWith(allowedHost)) {
AppLogger.warning(
'SSL pinning: hôte inattendu rejeté: $host',
tag: 'ApiClient');
return false;
}
return true;
return SpkiPinningService.instance.verifyCertificate(cert, host);
};
return client;
};