refactor: remove self-signed certificate manager and streamline certificate handling

- Deleted the SelfSignedCertificateManager and its associated files to simplify the certificate management process.
- Updated ApiService and ConnectivityService to include self-signed certificate configuration directly, enhancing clarity and maintainability.
- Adjusted comments to reflect the new approach to handling self-signed certificates, ensuring better understanding of security considerations.
- Improved the application startup sequence by deferring unnecessary initializations, contributing to a more efficient first paint performance.
This commit is contained in:
cogwheel0
2025-10-11 13:16:31 +05:30
parent 968c02940f
commit 7a8bd54dba
7 changed files with 27 additions and 165 deletions

View File

@@ -128,6 +128,16 @@ class ApiService {
}
}
/// Configures this Dio instance to accept self-signed certificates.
///
/// When [ServerConfig.allowSelfSignedCertificates] is enabled, this method
/// sets up a [badCertificateCallback] that trusts certificates from the
/// configured server's host and port.
///
/// Security considerations:
/// - Only certificates from the exact host/port are trusted
/// - If no port is specified, all ports on the host are trusted
/// - Web platforms ignore this (browsers handle TLS validation)
void _configureSelfSignedSupport() {
if (kIsWeb || !serverConfig.allowSelfSignedCertificates) {
return;
@@ -149,12 +159,15 @@ class ApiService {
final port = baseUri.hasPort ? baseUri.port : null;
client.badCertificateCallback =
(X509Certificate cert, String requestHost, int requestPort) {
// Only trust certificates from our configured server
if (requestHost.toLowerCase() != host) {
return false;
}
// If no specific port configured, trust any port on this host
if (port == null) {
return true;
}
// Otherwise, port must match exactly
return requestPort == port;
};
return client;

View File

@@ -288,6 +288,16 @@ class ConnectivityService with WidgetsBindingObserver {
}
/// Configures the Dio instance to accept self-signed certificates.
///
/// This method sets up a [badCertificateCallback] that trusts certificates
/// from the specified server's host and port for health check requests.
///
/// Security considerations:
/// - Only certificates from the exact host/port are trusted
/// - If no port is specified in the URL, all ports on the host are trusted
/// - Web platforms ignore this (browsers handle TLS validation)
///
/// This is called per-Dio-instance rather than using global HttpOverrides.
static void configureSelfSignedCerts(Dio dio, String serverUrl) {
if (kIsWeb) return;
@@ -304,8 +314,11 @@ class ConnectivityService with WidgetsBindingObserver {
client.badCertificateCallback =
(X509Certificate cert, String requestHost, int requestPort) {
// Only trust certificates from our configured server
if (requestHost.toLowerCase() != host) return false;
// If no specific port configured, trust any port on this host
if (port == null) return true;
// Otherwise, port must match exactly
return requestPort == port;
};

View File

@@ -9,7 +9,6 @@ import '../persistence/hive_boxes.dart';
import '../persistence/persistence_keys.dart';
import '../utils/debug_logger.dart';
import 'secure_credential_storage.dart';
import 'self_signed_certificate_manager.dart';
/// Optimized storage service backed by Hive for non-sensitive data and
/// FlutterSecureStorage for credentials.
@@ -197,9 +196,6 @@ class OptimizedStorageService {
await _secureCredentialStorage.saveServerConfigs(jsonString);
_cache['server_config_count'] = configs.length;
_cacheTimestamps['server_config_count'] = DateTime.now();
SelfSignedCertificateManager.instance
..ensureInitialized()
..updateTrustedServers(configs);
DebugLogger.log(
'Server configs saved (${configs.length} entries)',
scope: 'storage/optimized',
@@ -219,9 +215,6 @@ class OptimizedStorageService {
if (jsonString == null || jsonString.isEmpty) {
_cache['server_config_count'] = 0;
_cacheTimestamps['server_config_count'] = DateTime.now();
SelfSignedCertificateManager.instance
..ensureInitialized()
..clearTrustedServers();
return const [];
}
@@ -231,16 +224,12 @@ class OptimizedStorageService {
.toList();
_cache['server_config_count'] = configs.length;
_cacheTimestamps['server_config_count'] = DateTime.now();
SelfSignedCertificateManager.instance
..ensureInitialized()
..updateTrustedServers(configs);
return configs;
} catch (error) {
DebugLogger.log(
'Failed to retrieve server configs: $error',
scope: 'storage/optimized',
);
SelfSignedCertificateManager.instance.clearTrustedServers();
return const [];
}
}

View File

@@ -1,23 +0,0 @@
import '../models/server_config.dart';
import 'self_signed_certificate_manager_io.dart'
if (dart.library.html) 'self_signed_certificate_manager_stub.dart'
as platform;
/// Coordinates opt-in trust for self-signed TLS certificates.
///
/// On IO platforms we install an [HttpOverrides] that whitelists the servers
/// flagged in [ServerConfig.allowSelfSignedCertificates]. On web platforms the
/// helpers are no-ops because browsers manage TLS validation themselves.
class SelfSignedCertificateManager {
const SelfSignedCertificateManager._();
static const SelfSignedCertificateManager instance =
SelfSignedCertificateManager._();
void ensureInitialized() => platform.ensureInitialized();
void updateTrustedServers(Iterable<ServerConfig> configs) =>
platform.updateTrustedServers(configs);
void clearTrustedServers() => platform.clearTrustedServers();
}

View File

@@ -1,117 +0,0 @@
import 'dart:io';
import '../models/server_config.dart';
final _IoSelfSignedCertificateManager _manager =
_IoSelfSignedCertificateManager();
void ensureInitialized() => _manager.ensureInitialized();
void updateTrustedServers(Iterable<ServerConfig> configs) =>
_manager.updateTrustedServers(configs);
void clearTrustedServers() => _manager.clearTrustedServers();
class _IoSelfSignedCertificateManager {
_IoSelfSignedCertificateManager();
_ConduitHttpOverrides? _overrides;
void ensureInitialized() {
if (_overrides != null) return;
final overrides = _ConduitHttpOverrides();
HttpOverrides.global = overrides;
_overrides = overrides;
}
void updateTrustedServers(Iterable<ServerConfig> configs) {
ensureInitialized();
_overrides?.updateTrustedServers(configs);
}
void clearTrustedServers() {
_overrides?.clearTrustedServers();
}
}
class _ConduitHttpOverrides extends HttpOverrides {
final Set<_TrustedEndpoint> _trustedEndpoints = {};
void updateTrustedServers(Iterable<ServerConfig> configs) {
_trustedEndpoints
..clear()
..addAll(
configs
.where((config) => config.allowSelfSignedCertificates)
.map((config) => _TrustedEndpoint.fromUrl(config.url))
.whereType<_TrustedEndpoint>(),
);
}
void clearTrustedServers() {
_trustedEndpoints.clear();
}
bool _shouldTrust(String host, int port) {
for (final endpoint in _trustedEndpoints) {
if (endpoint.matches(host, port)) {
return true;
}
}
return false;
}
@override
HttpClient createHttpClient(SecurityContext? context) {
final client = super.createHttpClient(context);
client.badCertificateCallback =
(X509Certificate cert, String host, int port) =>
_shouldTrust(host, port);
return client;
}
}
class _TrustedEndpoint {
const _TrustedEndpoint({required this.host, this.port});
final String host;
final int? port;
static _TrustedEndpoint? fromUrl(String url) {
final uri = _normalizeUrl(url);
if (uri == null || uri.host.isEmpty) {
return null;
}
final normalizedHost = uri.host.toLowerCase();
final normalizedPort = uri.hasPort ? uri.port : null;
return _TrustedEndpoint(host: normalizedHost, port: normalizedPort);
}
static Uri? _normalizeUrl(String value) {
if (value.trim().isEmpty) {
return null;
}
Uri? parsed = Uri.tryParse(value.trim());
if (parsed == null) {
return null;
}
if (!parsed.hasScheme) {
parsed =
Uri.tryParse('https://${value.trim()}') ??
Uri.tryParse('http://${value.trim()}');
}
return parsed;
}
bool matches(String otherHost, int otherPort) {
final normalizedHost = otherHost.toLowerCase();
if (normalizedHost != host) {
return false;
}
if (port == null) {
return true;
}
return port == otherPort;
}
}

View File

@@ -1,7 +0,0 @@
import '../models/server_config.dart';
void ensureInitialized() {}
void updateTrustedServers(Iterable<ServerConfig> configs) {}
void clearTrustedServers() {}

View File

@@ -11,7 +11,6 @@ import 'core/providers/app_providers.dart';
import 'core/persistence/hive_bootstrap.dart';
import 'core/persistence/persistence_migrator.dart';
import 'core/persistence/persistence_providers.dart';
import 'core/services/self_signed_certificate_manager.dart';
import 'core/router/app_router.dart';
import 'features/auth/providers/unified_auth_providers.dart';
import 'core/auth/auth_state_manager.dart';
@@ -57,16 +56,11 @@ void main() {
_startupTimeline!.start('app_startup');
_startupTimeline!.instant('bindings_initialized');
// Defer edge-to-edge mode and certificate manager to post-frame to avoid
// impacting first paint
// Defer edge-to-edge mode to post-frame to avoid impacting first paint
WidgetsBinding.instance.addPostFrameCallback((_) {
// ignore: discarded_futures
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
_startupTimeline?.instant('edge_to_edge_enabled');
// Initialize certificate manager lazily after first frame
SelfSignedCertificateManager.instance.ensureInitialized();
_startupTimeline?.instant('cert_manager_ready');
});
const secureStorage = FlutterSecureStorage(