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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import '../models/server_config.dart';
|
||||
|
||||
void ensureInitialized() {}
|
||||
|
||||
void updateTrustedServers(Iterable<ServerConfig> configs) {}
|
||||
|
||||
void clearTrustedServers() {}
|
||||
Reference in New Issue
Block a user