From f9222ab884c616c39b96a6eb8890567358ee2899 Mon Sep 17 00:00:00 2001 From: icd360sevofficial Date: Mon, 6 Apr 2026 22:06:50 +0000 Subject: [PATCH] feat: add SecurityContext support for mTLS (mutual TLS) Add an optional `securityContext` parameter to `ClientBase`, `ImapClient`, and `SmtpClient` to enable client certificate authentication (mTLS). This allows passing a `SecurityContext` with client certificates and private keys to `SecureSocket.connect()` and `SecureSocket.secure()`, which is required for servers that enforce mutual TLS. Changes: - `ClientBase`: new `securityContext` field, passed to `SecureSocket.connect()` and `SecureSocket.secure()` (also fixes missing `onBadCertificate` in `upgradeToSslSocket()`) - `ImapClient`: forward `securityContext` to `ClientBase` super constructor - `SmtpClient`: forward `securityContext` to `ClientBase` super constructor The parameter is optional and defaults to `null`, so this is fully backwards-compatible with existing code. --- lib/src/imap/imap_client.dart | 5 +++++ lib/src/private/util/client_base.dart | 19 ++++++++++++++++++- lib/src/smtp/smtp_client.dart | 5 +++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/src/imap/imap_client.dart b/lib/src/imap/imap_client.dart index 7c6b36bf..686f698e 100644 --- a/lib/src/imap/imap_client.dart +++ b/lib/src/imap/imap_client.dart @@ -236,6 +236,9 @@ class ImapClient extends ClientBase { /// (or let the user decide) whether to accept the connection or not. /// The handler should return `true` to continue the [SecureSocket] /// connection. + /// + /// [securityContext] is an optional [SecurityContext] for mTLS + /// (mutual TLS / client certificate authentication). ImapClient({ EventBus? bus, bool isLogEnabled = false, @@ -243,11 +246,13 @@ class ImapClient extends ClientBase { this.defaultWriteTimeout, this.defaultResponseTimeout, bool Function(X509Certificate)? onBadCertificate, + SecurityContext? securityContext, }) : _eventBus = bus ?? EventBus(), super( isLogEnabled: isLogEnabled, logName: logName, onBadCertificate: onBadCertificate, + securityContext: securityContext, ) { _imapResponseReader = ImapResponseReader(onServerResponse); } diff --git a/lib/src/private/util/client_base.dart b/lib/src/private/util/client_base.dart index 72c726e1..74429a85 100644 --- a/lib/src/private/util/client_base.dart +++ b/lib/src/private/util/client_base.dart @@ -29,10 +29,16 @@ abstract class ClientBase { /// The handler receives the [X509Certificate], and can inspect it and decide /// (or let the user decide) whether to accept the connection or not. /// The handler should return true to continue the [SecureSocket] connection. + /// + /// [securityContext] is an optional security context for mTLS + /// (mutual TLS / client certificate authentication). + /// Create a [SecurityContext] with `useCertificateChain()` and + /// `usePrivateKey()` to enable client certificate authentication. ClientBase({ this.isLogEnabled = false, this.logName, this.onBadCertificate, + this.securityContext, }); /// Initial for a client log output @@ -76,6 +82,12 @@ abstract class ClientBase { /// The handler should return true to continue the [SecureSocket] connection. final bool Function(X509Certificate)? onBadCertificate; + /// Optional [SecurityContext] for mTLS (mutual TLS). + /// + /// When set, it is passed to [SecureSocket.connect] and + /// [SecureSocket.secure] to enable client certificate authentication. + final SecurityContext? securityContext; + /// Is called when data is received void onDataReceived(Uint8List data); @@ -112,6 +124,7 @@ abstract class ClientBase { host, port, onBadCertificate: onBadCertificate, + context: securityContext, ).timeout(timeout) : await Socket.connect(host, port).timeout(timeout); _greetingsCompleter = Completer(); @@ -177,7 +190,11 @@ abstract class ClientBase { /// Upgrades the current connection to a secure socket Future upgradeToSslSocket() async { _socketStreamSubscription.pause(); - final secureSocket = await SecureSocket.secure(_socket); + final secureSocket = await SecureSocket.secure( + _socket, + context: securityContext, + onBadCertificate: onBadCertificate, + ); logApp('now using secure connection.'); await _socketStreamSubscription.cancel(); isSocketClosingExpected = true; diff --git a/lib/src/smtp/smtp_client.dart b/lib/src/smtp/smtp_client.dart index d85449d8..138c1a4f 100644 --- a/lib/src/smtp/smtp_client.dart +++ b/lib/src/smtp/smtp_client.dart @@ -103,18 +103,23 @@ class SmtpClient extends ClientBase { /// The handler receives the [X509Certificate], and can inspect it and /// decide (or let the user decide) whether to accept the connection or not. /// The handler should return true to continue the [SecureSocket] connection. + /// + /// [securityContext] is an optional [SecurityContext] for mTLS + /// (mutual TLS / client certificate authentication). SmtpClient( String clientDomain, { EventBus? bus, bool isLogEnabled = false, String? logName, bool Function(X509Certificate)? onBadCertificate, + SecurityContext? securityContext, }) : _eventBus = bus ?? EventBus(), _clientDomain = clientDomain, super( isLogEnabled: isLogEnabled, logName: logName, onBadCertificate: onBadCertificate, + securityContext: securityContext, ); /// Information about the SMTP service