diff --git a/src/TurboMqtt/Client/ClientTlsOptions.cs b/src/TurboMqtt/Client/ClientTlsOptions.cs
new file mode 100644
index 00000000..9131d93f
--- /dev/null
+++ b/src/TurboMqtt/Client/ClientTlsOptions.cs
@@ -0,0 +1,21 @@
+// -----------------------------------------------------------------------
+// 
+//      Copyright (C) 2024 - 2024 Petabridge, LLC 
+// 
+// -----------------------------------------------------------------------
+
+using System.Net.Security;
+
+namespace TurboMqtt.Client;
+
+/// 
+/// Used to provide the necessary certificates and keys for establishing a secure connection with the MQTT broker.
+/// 
+public sealed record ClientTlsOptions
+{
+    public static readonly ClientTlsOptions Default = new();
+    
+    public bool UseTls { get; init; } = false;
+    
+    public SslClientAuthenticationOptions? SslOptions { get; init; }
+}
\ No newline at end of file
diff --git a/src/TurboMqtt/Client/IMqttClientFactory.cs b/src/TurboMqtt/Client/IMqttClientFactory.cs
index a6fcd3ad..a5347ee5 100644
--- a/src/TurboMqtt/Client/IMqttClientFactory.cs
+++ b/src/TurboMqtt/Client/IMqttClientFactory.cs
@@ -53,6 +53,9 @@ public MqttClientFactory(ActorSystem system)
     public async Task CreateTcpClient(MqttClientConnectOptions options, MqttClientTcpOptions tcpOptions)
     {
         AssertMqtt311(options);
+        if (tcpOptions.TlsOptions is { UseTls: true, SslOptions: null })
+            throw new NullReferenceException("TlsOptions.SslOptions can not be null if TlsOptions.UseTls is true");
+        
         var transportManager = new TcpMqttTransportManager(tcpOptions, _mqttClientManager, options.ProtocolVersion);
 
         // create the client
diff --git a/src/TurboMqtt/Client/MqttClientTcpOptions.cs b/src/TurboMqtt/Client/MqttClientTcpOptions.cs
index 5cc54591..7500e8f3 100644
--- a/src/TurboMqtt/Client/MqttClientTcpOptions.cs
+++ b/src/TurboMqtt/Client/MqttClientTcpOptions.cs
@@ -4,7 +4,6 @@
 // 
 // -----------------------------------------------------------------------
 
-using System.Net;
 using System.Net.Sockets;
 
 namespace TurboMqtt.Client;
@@ -23,26 +22,33 @@ public MqttClientTcpOptions(string host, int port)
     /// 
     /// Would love to just do IPV6, but that still meets resistance everywhere
     /// 
-    public AddressFamily AddressFamily { get; set; } = AddressFamily.Unspecified;
+    public AddressFamily AddressFamily { get; init; } = AddressFamily.Unspecified;
     
     /// 
     /// Frames are limited to this size in bytes. A frame can contain multiple packets.
     /// 
-    public int MaxFrameSize { get; set; } = 128 * 1024; // 128kb
+    public int MaxFrameSize { get; init; } = 128 * 1024; // 128kb
     
-    public string Host { get; }
+    public string Host { get; init; }
     
-    public int Port { get; }
+    public int Port { get; init; }
     
     /// 
     /// How long should we wait before attempting to reconnect the client?
     /// 
-    public TimeSpan ReconnectInterval { get; set; } = TimeSpan.FromSeconds(5);
+    public TimeSpan ReconnectInterval { get; init; } = TimeSpan.FromSeconds(5);
     
     /// 
     /// Maximum number of times we should attempt to reconnect the client before giving up.
     ///
     /// Resets back to 0 after a successful connection.
     /// 
-    public int MaxReconnectAttempts { get; set; } = 10;
+    public int MaxReconnectAttempts { get; init; } = 10;
+
+    /// 
+    /// The  to use when connecting to the server.
+    ///
+    /// Disabled by default.
+    /// 
+    public ClientTlsOptions TlsOptions { get; init; } = ClientTlsOptions.Default;
 }
\ No newline at end of file
diff --git a/src/TurboMqtt/IO/Tcp/FakeMqttTcpServer.cs b/src/TurboMqtt/IO/Tcp/FakeMqttTcpServer.cs
index bd4b758d..4bb7cdf2 100644
--- a/src/TurboMqtt/IO/Tcp/FakeMqttTcpServer.cs
+++ b/src/TurboMqtt/IO/Tcp/FakeMqttTcpServer.cs
@@ -8,13 +8,14 @@
 using System.Collections.Concurrent;
 using System.IO.Pipelines;
 using System.Net;
+using System.Net.Security;
 using System.Net.Sockets;
 using Akka.Event;
 using TurboMqtt.Protocol;
 
 namespace TurboMqtt.IO.Tcp;
 
-internal sealed class MqttTcpServerOptions
+public sealed record MqttTcpServerOptions
 {
     public MqttTcpServerOptions(string host, int port)
     {
@@ -25,16 +26,18 @@ public MqttTcpServerOptions(string host, int port)
     /// 
     /// Would love to just do IPV6, but that still meets resistance everywhere
     /// 
-    public AddressFamily AddressFamily { get; set; } = AddressFamily.Unspecified;
+    public AddressFamily AddressFamily { get; init; } = AddressFamily.Unspecified;
 
     /// 
     /// Frames are limited to this size in bytes. A frame can contain multiple packets.
     /// 
-    public int MaxFrameSize { get; set; } = 128 * 1024; // 128kb
+    public int MaxFrameSize { get; init; } = 128 * 1024; // 128kb
 
-    public string Host { get; }
+    public string Host { get; init; }
 
-    public int Port { get; }
+    public int Port { get; init; }
+
+    public SslServerAuthenticationOptions? SslOptions { get; init; }
 }
 
 /// 
@@ -47,7 +50,7 @@ internal sealed class FakeMqttTcpServer
     private readonly CancellationTokenSource _shutdownTcs = new();
     private readonly ILoggingAdapter _log;
     private readonly ConcurrentDictionary _clientCts = new();
-    private readonly ConcurrentDictionary _clientSockets = new();
+    private readonly ConcurrentDictionary _clientSockets = new();
     private readonly TimeSpan _heatBeatDelay;
     private readonly IFakeServerHandleFactory _handleFactory;
     private Socket? _bindSocket;
@@ -118,16 +121,16 @@ public bool TryKickClient(string clientId)
 
     public bool TryDisconnectClientSocket(string clientId)
     {
-        if (!_clientSockets.TryRemove(clientId, out var socket)) 
+        if (!_clientSockets.TryRemove(clientId, out var clientTcpStream))
             return false;
 
-        if (!socket.Connected) 
+        if (!clientTcpStream.CanRead)
             return false;
-        
-        socket.Disconnect(true);
+
+        clientTcpStream.Dispose();
         return true;
     }
-    
+
     public void Shutdown()
     {
         _log.Info("Shutting down server.");
@@ -154,7 +157,26 @@ private async Task BeginAcceptAsync()
         while (!_shutdownTcs.IsCancellationRequested)
         {
             var socket = await _bindSocket!.AcceptAsync();
-            _ = ProcessClientAsync(socket);
+            Stream readingStream = new NetworkStream(socket, true);
+            
+            // check for TLS
+            if (_options.SslOptions != null)
+            {
+                try
+                {
+                    var sslStream = new SslStream(readingStream, false);
+                    readingStream = sslStream;
+                    await sslStream.AuthenticateAsServerAsync(_options.SslOptions, _shutdownTcs.Token);
+                    _log.Info("Server authenticated successfully");
+                }
+                catch (Exception ex)
+                {
+                    _log.Error(ex, "Exception during authentication");
+                    throw;
+                }
+            }
+            
+            _ = ProcessClientAsync(readingStream);
         }
     }
 
@@ -174,7 +196,7 @@ private static async Task ReadFromPipeAsync(PipeReader reader, IFakeServerHandle
                     // once we hand the message over to the end-user.
                     var newMemory = new Memory(new byte[buffer.Length]);
                     buffer.CopyTo(newMemory.Span);
-                    
+
                     handle.HandleBytes(newMemory);
                 }
 
@@ -193,7 +215,9 @@ private static async Task ReadFromPipeAsync(PipeReader reader, IFakeServerHandle
                     catch (Exception ex)
                     {
                         // junk exception that occurs during shutdown
-                        handle.Log.Debug(ex, "Error advancing the reader with buffer size [{0}] with read result of [Completed={1}, Cancelled={2}]", buffer.Length, result.IsCompleted, result.IsCanceled);
+                        handle.Log.Debug(ex,
+                            "Error advancing the reader with buffer size [{0}] with read result of [Completed={1}, Cancelled={2}]",
+                            buffer.Length, result.IsCompleted, result.IsCanceled);
                         return;
                     }
                 }
@@ -204,10 +228,10 @@ private static async Task ReadFromPipeAsync(PipeReader reader, IFakeServerHandle
             }
         }
     }
-
-    private async Task ProcessClientAsync(Socket socket)
+    
+    private async Task ProcessClientAsync(Stream stream)
     {
-        using (socket)
+        await using (stream)
         {
             var closed = false;
             var pipe = new Pipe(new PipeOptions(
@@ -217,18 +241,18 @@ private async Task ProcessClientAsync(Socket socket)
             var clientShutdownCts = new CancellationTokenSource();
             var linkedCts =
                 CancellationTokenSource.CreateLinkedTokenSource(clientShutdownCts.Token, _shutdownTcs.Token);
-            
+
             var handle = _handleFactory.CreateServerHandle(PushMessage, ClosingAction, _log, _version, _heatBeatDelay);
-            
+
             _ = handle.WhenClientIdAssigned.ContinueWith(t =>
             {
                 if (t.IsCompletedSuccessfully)
                 {
                     _clientCts.TryAdd(t.Result, (clientShutdownCts, handle.WhenTerminated));
-                    _clientSockets.TryAdd(t.Result, socket);
+                    _clientSockets.TryAdd(t.Result, stream);
                 }
             }, clientShutdownCts.Token);
-           
+
 
             _ = ReadFromPipeAsync(pipe.Reader, handle, linkedCts.Token);
 
@@ -239,7 +263,7 @@ private async Task ProcessClientAsync(Socket socket)
                 try
                 {
                     var memory = pipe.Writer.GetMemory(_options.MaxFrameSize / 4);
-                    var bytesRead = await socket.ReceiveAsync(memory, SocketFlags.None, linkedCts.Token);
+                    var bytesRead = await stream.ReadAsync(memory, linkedCts.Token);
                     if (bytesRead == 0)
                     {
                         _log.Info("Client {0} disconnected from server.",
@@ -287,7 +311,7 @@ private async Task ProcessClientAsync(Socket socket)
 
             // ensure we've cleaned up all resources
             await handle.WhenTerminated;
-            
+
             await pipe.Writer.CompleteAsync();
             await pipe.Reader.CompleteAsync();
 
@@ -297,21 +321,10 @@ bool PushMessage((IMemoryOwner buffer, int estimatedSize) msg)
             {
                 try
                 {
-                    if (socket.Connected && linkedCts.Token is { IsCancellationRequested: false })
+                    if (stream.CanWrite && linkedCts.Token is { IsCancellationRequested: false })
                     {
-                        var sent = socket.Send(msg.buffer.Memory.Span.Slice(0, msg.estimatedSize));
-                        while (sent < msg.estimatedSize)
-                        {
-                            if (sent == 0) return false; // we are shutting down
-
-                            var remaining = msg.buffer.Memory.Slice(sent);
-                            var sent2 = socket.Send(remaining.Span);
-                            if (sent2 == remaining.Length)
-                                sent += sent2;
-                            else
-                                return false;
-                        }
-
+                        var task = stream.WriteAsync(msg.buffer.Memory.Slice(0, msg.estimatedSize), linkedCts.Token);
+                        task.GetAwaiter().GetResult();
                         return true;
                     }
 
@@ -333,7 +346,16 @@ async Task ClosingAction()
                 closed = true;
                 // ReSharper disable once AccessToModifiedClosure
                 await clientShutdownCts.CancelAsync();
-                if (socket.Connected) socket.Close();
+                try
+                {
+                    stream?.Close();
+                    // ReSharper disable once MethodHasAsyncOverload
+                    stream?.Dispose();
+                }
+                catch
+                {
+                    // suppress exceptions during stream disposal
+                }
             }
         }
     }
diff --git a/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs b/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs
index 64333fd6..0ab11e4d 100644
--- a/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs
+++ b/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs
@@ -7,7 +7,9 @@
 using System.Buffers;
 using System.IO.Pipelines;
 using System.Net;
+using System.Net.Security;
 using System.Net.Sockets;
+using System.Security.Cryptography.X509Certificates;
 using System.Threading.Channels;
 using Akka.Actor;
 using Akka.Event;
@@ -43,11 +45,14 @@ public ConnectionState(ChannelWriter<(IMemoryOwner buffer, int readableByt
             MaxFrameSize = maxFrameSize;
             WaitForPendingWrites = waitForPendingWrites;
         }
-        
+
         private volatile ConnectionStatus _status = ConnectionStatus.NotStarted;
 
-        public ConnectionStatus Status { get => _status; 
-            set => _status = value; }
+        public ConnectionStatus Status
+        {
+            get => _status;
+            set => _status = value;
+        }
 
         public CancellationTokenSource ShutDownCts { get; set; } = new();
 
@@ -110,6 +115,7 @@ public sealed record ConnectionUnexpectedlyClosed(DisconnectReasonCode Reason, s
     public ConnectionState State { get; private set; }
 
     private Socket? _tcpClient;
+    private Stream? _tcpStream;
 
     private readonly Channel<(IMemoryOwner buffer, int readableBytes)> _writesToTransport =
         Channel.CreateUnbounded<(IMemoryOwner buffer, int readableBytes)>();
@@ -130,7 +136,8 @@ public TcpTransportActor(MqttClientTcpOptions tcpOptions)
         State = new ConnectionState(_writesToTransport.Writer, _readsFromTransport.Reader, _whenTerminated.Task,
             MaxFrameSize, _writesToTransport.Reader.Completion); // we signal completion when _writesToTransport is done
 
-        _pipe = new Pipe(new PipeOptions(pauseWriterThreshold: ScaleBufferSize(MaxFrameSize), resumeWriterThreshold: ScaleBufferSize(MaxFrameSize) / 2,
+        _pipe = new Pipe(new PipeOptions(pauseWriterThreshold: ScaleBufferSize(MaxFrameSize),
+            resumeWriterThreshold: ScaleBufferSize(MaxFrameSize) / 2,
             useSynchronizationContext: false));
     }
 
@@ -140,7 +147,7 @@ public TcpTransportActor(MqttClientTcpOptions tcpOptions)
      * Connecting --> DoConnect --> Connecting (already connecting) --> ConnectResult (Connected) BECOME Running
      * Running --> DoWriteToPipeAsync --> Running (read data from socket) --> DoWriteToSocketAsync --> Running (write data to socket)
      */
-    
+
     /// 
     /// Performs the max buffer size scaling for the socket.
     /// 
@@ -150,11 +157,11 @@ internal static int ScaleBufferSize(int maxFrameSize)
         // if the max frame size is under 128kb, scale it up to 512kb
         if (maxFrameSize <= 128 * 1024)
             return 512 * 1024;
-        
+
         // between 128kb and 1mb, scale it up to 2mb
         if (maxFrameSize <= 1024 * 1024)
             return 2 * 1024 * 1024;
-        
+
         // if the max frame size is above 1mb, 2x it
         return maxFrameSize * 2;
     }
@@ -219,6 +226,15 @@ private async Task DoConnectAsync(IPAddress[] addresses, int port, IActorRef des
             Debug.Assert(_tcpClient != null, nameof(_tcpClient) + " != null");
             await _tcpClient.ConnectAsync(addresses, port, ct).ConfigureAwait(false);
             connectResult = new ConnectResult(ConnectionStatus.Connected, "Connected.");
+            _tcpStream = new NetworkStream(_tcpClient, true);
+            
+            // Check for TLS
+            if (TcpOptions.TlsOptions.UseTls)
+            {
+                var sslStream = new SslStream(_tcpStream, false);
+                _tcpStream = sslStream;
+                await sslStream.AuthenticateAsClientAsync(TcpOptions.TlsOptions.SslOptions!, ct);
+            }
         }
         catch (Exception ex)
         {
@@ -244,7 +260,7 @@ private void TransportCreated(object message)
                     _log.Info("Attempting to connect to [{0}:{1}]", TcpOptions.Host, TcpOptions.Port);
 
                     var sender = Sender;
-                    
+
                     // set status to connecting
                     State.Status = ConnectionStatus.Connecting;
 
@@ -264,7 +280,7 @@ async Task ResolveAndConnect(CancellationToken ct)
                         await DoConnectAsync(resolved, TcpOptions.Port, sender, ct).ConfigureAwait(false);
                     }
                 });
-                
+
                 break;
             }
             case DoConnect:
@@ -322,19 +338,20 @@ private async Task DoWriteToSocketAsync(CancellationToken ct)
                     try
                     {
                         var workingBuffer = buffer.Memory;
-                        while (readableBytes > 0 && _tcpClient is { Connected: true })
+                        if (readableBytes > 0 && _tcpClient is { Connected: true } && _tcpStream is { CanWrite: true })
                         {
-                            var sent = await _tcpClient!.SendAsync(workingBuffer.Slice(0, readableBytes), ct)
+                            await _tcpStream!.WriteAsync(workingBuffer.Slice(0, readableBytes), ct)
                                 .ConfigureAwait(false);
-                            if (sent == 0)
+                            //await _tcpStream.FlushAsync(ct);
+                        }
+                        else
+                        {
+                            if (_tcpStream!.CanWrite == false)
                             {
-                                _log.Warning("Failed to write to socket - no bytes written.");
+                                _log.Warning("Socket is no longer writable - terminating");
                                 _closureSelf.Tell(ReadFinished.Instance);
                                 goto WritesFinished;
                             }
-
-                            readableBytes -= sent;
-                            workingBuffer = workingBuffer.Slice(sent);
                         }
                     }
                     finally
@@ -364,7 +381,7 @@ private async Task DoWriteToSocketAsync(CancellationToken ct)
         }
 
         WritesFinished:
-            _writesToTransport.Writer.TryComplete(); // can't write anymore either
+        _writesToTransport.Writer.TryComplete(); // can't write anymore either
     }
 
     private async Task DoWriteToPipeAsync(CancellationToken ct)
@@ -374,7 +391,8 @@ private async Task DoWriteToPipeAsync(CancellationToken ct)
             var memory = _pipe.Writer.GetMemory(TcpOptions.MaxFrameSize / 4);
             try
             {
-                int bytesRead = await _tcpClient!.ReceiveAsync(memory, SocketFlags.None, ct);
+                var bytesRead = await _tcpStream!.ReadAsync(memory, ct);
+                //int bytesRead = await _tcpClient!.ReceiveAsync(memory, SocketFlags.None, ct);
                 if (bytesRead == 0)
                 {
                     // we are done reading - socket was gracefully closed
@@ -476,10 +494,11 @@ private void Running(object message)
     private async Task CleanUpGracefully(bool waitOnReads = false)
     {
         // add a simulated DisconnectPacket to help ensure the stream gets terminated
-        _readsFromTransport.Writer.TryWrite(DisconnectToBinary.NormalDisconnectPacket.ToBinary(MqttProtocolVersion.V3_1_1));
+        _readsFromTransport.Writer.TryWrite(
+            DisconnectToBinary.NormalDisconnectPacket.ToBinary(MqttProtocolVersion.V3_1_1));
 
         State.Status = ConnectionStatus.Disconnected;
-        
+
         // no more writes to transport
         _writesToTransport.Writer.TryComplete();
 
@@ -515,6 +534,8 @@ private void DisposeSocket(ConnectionStatus newStatus)
 
             _pipe.Reader.Complete();
             _pipe.Writer.Complete();
+            _tcpStream?.Close();
+            _tcpStream?.Dispose();
             _tcpClient?.Close();
             _tcpClient?.Dispose();
         }
@@ -524,6 +545,7 @@ private void DisposeSocket(ConnectionStatus newStatus)
         }
         finally
         {
+            _tcpStream = null;
             _tcpClient = null;
         }
     }
diff --git a/tests/TurboMqtt.Tests/End2End/InMemoryMqtt311End2EndSpecs.cs b/tests/TurboMqtt.Tests/End2End/MQTT_311/InMemoryMqtt311End2EndSpecs.cs
similarity index 100%
rename from tests/TurboMqtt.Tests/End2End/InMemoryMqtt311End2EndSpecs.cs
rename to tests/TurboMqtt.Tests/End2End/MQTT_311/InMemoryMqtt311End2EndSpecs.cs
diff --git a/tests/TurboMqtt.Tests/End2End/TcpMqtt311End2EndSpecs.cs b/tests/TurboMqtt.Tests/End2End/MQTT_311/TcpMqtt311End2EndSpecs.cs
similarity index 93%
rename from tests/TurboMqtt.Tests/End2End/TcpMqtt311End2EndSpecs.cs
rename to tests/TurboMqtt.Tests/End2End/MQTT_311/TcpMqtt311End2EndSpecs.cs
index d1cc4f28..fc708d63 100644
--- a/tests/TurboMqtt.Tests/End2End/TcpMqtt311End2EndSpecs.cs
+++ b/tests/TurboMqtt.Tests/End2End/MQTT_311/TcpMqtt311End2EndSpecs.cs
@@ -16,6 +16,10 @@
 
 namespace TurboMqtt.Tests.End2End;
 
+[CollectionDefinition(nameof(TcpEnd2EndCollection))]
+public sealed class TcpEnd2EndCollection;
+
+[Collection(nameof(TcpEnd2EndCollection))]
 public class TcpMqtt311End2EndSpecs : TransportSpecBase
 {
     public static readonly Config DebugLogging = """
@@ -26,7 +30,7 @@ public TcpMqtt311End2EndSpecs(ITestOutputHelper output) : base(output: output, c
     {
         var logger = new BusLogging(Sys.EventStream, "FakeMqttTcpServer", typeof(FakeMqttTcpServer),
             Sys.Settings.LogFormatter);
-        _server = new FakeMqttTcpServer(new MqttTcpServerOptions("localhost", 21883), MqttProtocolVersion.V3_1_1,
+        _server = new FakeMqttTcpServer(DefaultTcpServerOptions, MqttProtocolVersion.V3_1_1,
             logger, TimeSpan.Zero, new DefaultFakeServerHandleFactory());
         _server.Bind();
     }
@@ -39,7 +43,8 @@ public override async Task CreateClient()
         return client;
     }
 
-    public MqttClientTcpOptions DefaultTcpOptions => new("localhost", 21883);
+    protected virtual MqttClientTcpOptions DefaultTcpOptions => new("localhost", 21883);
+    protected virtual MqttTcpServerOptions DefaultTcpServerOptions => new("localhost", 21883);
 
     protected override void AfterAll()
     {
@@ -128,7 +133,7 @@ public async Task ShouldReconnectSuccessfullyIfReconnectFlowFailed()
         _server.Shutdown();
         
         var server = new FakeMqttTcpServer(
-            options: new MqttTcpServerOptions("localhost", 21883), 
+            options: DefaultTcpServerOptions, 
             version: MqttProtocolVersion.V3_1_1,
             log: Log,
             heartbeatDelay: TimeSpan.Zero,
@@ -259,10 +264,7 @@ public async Task ShouldTerminateClientAfterMultipleFailedConnectionAttempts()
     [Fact]
     public async Task ShouldFailToConnectToNonExistentServer()
     {
-        var updatedTcpOptions = new MqttClientTcpOptions("localhost", 21884)
-        {
-            MaxReconnectAttempts = 0
-        };
+        var updatedTcpOptions = DefaultTcpOptions with { Port = 21884, MaxReconnectAttempts = 0 };
         var client = await ClientFactory.CreateTcpClient(DefaultConnectOptions, updatedTcpOptions);
         
         // we are going to do this, intentionally, without a CTS here - this operation MUST FAIL if we are unable to connect
@@ -275,10 +277,7 @@ public async Task ShouldFailToConnectToNonExistentServer()
     [Fact]
     public async Task ShouldSuccessFullyConnectWhenBrokerAvailableAfterFailedConnectionAttempt()
     {
-        var updatedTcpOptions = new MqttClientTcpOptions("localhost", 21889)
-        {
-            MaxReconnectAttempts = 0
-        };
+        var updatedTcpOptions = DefaultTcpOptions with { Port = 21889, MaxReconnectAttempts = 0 };
         var client = await ClientFactory.CreateTcpClient(DefaultConnectOptions, updatedTcpOptions);
         
         // we are going to do this, intentionally, without a CTS here - this operation MUST FAIL if we are unable to connect
@@ -287,8 +286,9 @@ public async Task ShouldSuccessFullyConnectWhenBrokerAvailableAfterFailedConnect
 
         client.IsConnected.Should().BeFalse();
         
+        var updatedServerOptions = DefaultTcpServerOptions with { Port = 21889 };
         // start up a new server
-        var newServer = new FakeMqttTcpServer(new MqttTcpServerOptions("localhost", 21889), MqttProtocolVersion.V3_1_1,
+        var newServer = new FakeMqttTcpServer(updatedServerOptions, MqttProtocolVersion.V3_1_1,
             Sys.Log, TimeSpan.Zero, new DefaultFakeServerHandleFactory());
         try
         {
diff --git a/tests/TurboMqtt.Tests/End2End/TcpMqtt311HeartbeatFailureEnd2EndSpecs.cs b/tests/TurboMqtt.Tests/End2End/MQTT_311/TcpMqtt311HeartbeatFailureEnd2EndSpecs.cs
similarity index 100%
rename from tests/TurboMqtt.Tests/End2End/TcpMqtt311HeartbeatFailureEnd2EndSpecs.cs
rename to tests/TurboMqtt.Tests/End2End/MQTT_311/TcpMqtt311HeartbeatFailureEnd2EndSpecs.cs
diff --git a/tests/TurboMqtt.Tests/End2End/MQTT_311/TlsMqtt311End2EndSpecs.cs b/tests/TurboMqtt.Tests/End2End/MQTT_311/TlsMqtt311End2EndSpecs.cs
new file mode 100644
index 00000000..f36d51ee
--- /dev/null
+++ b/tests/TurboMqtt.Tests/End2End/MQTT_311/TlsMqtt311End2EndSpecs.cs
@@ -0,0 +1,137 @@
+// -----------------------------------------------------------------------
+// 
+//      Copyright (C) 2024 - 2024 Petabridge, LLC 
+// 
+// -----------------------------------------------------------------------
+
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using Akka.Event;
+using TurboMqtt.Client;
+using TurboMqtt.IO.Tcp;
+using Xunit.Abstractions;
+
+namespace TurboMqtt.Tests.End2End;
+
+[Collection(nameof(TcpEnd2EndCollection))]
+public class TlsMqtt311End2EndSpecs : TcpMqtt311End2EndSpecs
+{
+    // This is a workaround for this issue:
+    // https://github.com/dotnet/runtime/issues/23749
+    private static readonly X509Certificate2 RootCert = new (
+        X509Certificate2.CreateFromEncryptedPemFile("./certs/root_cert.pem", "password")
+            .Export(X509ContentType.Pkcs12));
+
+    private static readonly X509ChainPolicy RootChainPolicy = new()
+    {
+        CustomTrustStore = { RootCert },
+        TrustMode = X509ChainTrustMode.CustomRootTrust, 
+        RevocationMode = X509RevocationMode.NoCheck
+    };
+
+    private static readonly X509Chain RootChain = new ()
+    {
+        ChainPolicy = RootChainPolicy
+    };
+
+    public TlsMqtt311End2EndSpecs(ITestOutputHelper output) : base(output)
+    {
+    }
+    
+    // This is a workaround for this issue:
+    // https://github.com/dotnet/runtime/issues/23749
+    private static readonly X509Certificate2 ServerCert =  new X509Certificate2(
+        X509Certificate2.CreateFromEncryptedPemFile("./certs/server_cert.pem", "password")
+            .Export(X509ContentType.Pkcs12));
+    
+    protected override MqttTcpServerOptions DefaultTcpServerOptions => new ("localhost", 21883)
+    {
+        SslOptions = new SslServerAuthenticationOptions
+        {
+            ServerCertificate = ServerCert,
+            ClientCertificateRequired = false,
+            RemoteCertificateValidationCallback = ValidateClientCertificate
+        } 
+    };
+    
+    private bool ValidateClientCertificate(
+        object sender,
+        X509Certificate? certificate,
+        X509Chain? chain,
+        SslPolicyErrors sslPolicyErrors)
+    {
+        if (sslPolicyErrors == SslPolicyErrors.None)
+            return true;
+
+        // Return true if client certificate is not required
+        if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateNotAvailable)
+            return true;
+        
+        // Validate client certificate with a custom chain
+        if (certificate is not null)
+        {
+            var isValid = RootChain.Build(new X509Certificate2(certificate));
+            if (!isValid)
+            {
+                foreach (var status in RootChain.ChainStatus)
+                {
+                    Log.Error("[Server] Chain error: {0}", status.StatusInformation);
+                }
+            }
+
+            return isValid;
+        }
+
+        // Refuse everything else
+        Log.Error("[Server] Certificate error: {0}", sslPolicyErrors);
+        return false;
+    }    
+
+    private bool ValidateServerCertificate(
+        object sender,
+        X509Certificate? certificate,
+        X509Chain? chain,
+        SslPolicyErrors errors)
+    {
+        if (errors == SslPolicyErrors.None)
+            return true;
+
+        // Missing cert or the destination hostname wasn't valid for the cert.
+        if ((errors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0)
+            return false;
+
+        // Validate client certificate with a custom chain
+        if (certificate is not null)
+        {
+            chain ??= RootChain;
+            var isValid = chain.Build(new X509Certificate2(certificate));
+            if (!isValid)
+            {
+                foreach (var status in chain.ChainStatus)
+                {
+                    Log.Error("[Client] Chain error: [{0}] {1}", status.Status, status.StatusInformation);
+                }
+            }
+
+            return isValid;
+        }
+        
+        // Refuse everything else
+        Log.Error("[Client] Certificate error: {0}", errors);
+        return false;
+    }
+    
+    protected override MqttClientTcpOptions DefaultTcpOptions => new("localhost", 21883)
+    {
+        TlsOptions = new ClientTlsOptions
+        {
+            UseTls = true,
+            SslOptions = new SslClientAuthenticationOptions
+            {
+                TargetHost = "localhost",
+                CertificateChainPolicy = RootChainPolicy,
+                RemoteCertificateValidationCallback = ValidateServerCertificate
+            }
+        }
+    };
+}
\ No newline at end of file
diff --git a/tests/TurboMqtt.Tests/TurboMqtt.Tests.csproj b/tests/TurboMqtt.Tests/TurboMqtt.Tests.csproj
index c25629ed..2e55ae85 100644
--- a/tests/TurboMqtt.Tests/TurboMqtt.Tests.csproj
+++ b/tests/TurboMqtt.Tests/TurboMqtt.Tests.csproj
@@ -30,4 +30,18 @@
       
     
 
+    
+      
+        certs\root_cert.pem
+        PreserveNewest
+      
+    
+
+    
+      
+        certs\server_cert.pem
+        PreserveNewest
+      
+    
+
 
diff --git a/tests/certs/README.md b/tests/certs/README.md
new file mode 100644
index 00000000..c0aa9680
--- /dev/null
+++ b/tests/certs/README.md
@@ -0,0 +1,30 @@
+# Generating Test Certificates
+
+* Use WSL to access OpenSSL (the keys in this folder was generated using Ubuntu)
+* All private key files uses the password "password"
+
+## 1. Generate Root CA
+
+```
+openssl req -x509 -new -nodes -key root_private_key.pem -sha256 -days 3650 -out root_cert.pem -config root_cert_config.cnf
+``` 
+
+* This will generate the **"root_cert.pem"** that is valid for 10 years.
+* Open the **"root_private_key.pem"** and copy-paste its content to the end of the **"root_cert.pem"** file.
+
+## 2. Generate CSR
+
+```
+openssl req -new -key server_private_key.pem -out server.csr -config server_cert_config.cnf
+```
+
+* This generates the **"server.csr"** file.
+
+## 3. Generate The Server Certificate
+
+```
+openssl x509 -req -in server.csr -CA root_cert.pem -CAkey root_private_key.pem -CAcreateserial -out server_cert.pem -days 365 -sha256 -extfile v3_ext.cnf
+```
+
+* This generates the **"server_cert.pem"** file.
+* Open the **"server_private_key.pem"** and copy-paste its content to the end of the **"server_cert.pem"** file.
\ No newline at end of file
diff --git a/tests/certs/private_key.pem b/tests/certs/private_key.pem
new file mode 100644
index 00000000..dbe0b3c6
--- /dev/null
+++ b/tests/certs/private_key.pem
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIxNg0aK6f288CAggA
+MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBCS/zUm44zbe78mqv4g5gtdBIIE
+0DP8qI40/EjZkXrb9FqxLCtYxdwvsbcwImYvyFZ5XR35nOVvP9CrHwUlgjC0nxfg
+gO2to11X/OK1DZY6pkIKSuXhDxAndbm1RuG52BVtEgob3M9FNgStuqlnayVanwSP
+QzEzqUUHB2bfFo5cNOAXB/Rgnhb1aDnvI7NS0+Y8Ml+lXUoDiru4zbEvx0K4Za6u
+zgjB4w+LYoiK2zg9kJbAtrXe1v6BsRVDvd0RShIjvcyHLQgt2q26Num1oioAiEc9
+sRG2djBLwmYbLPz4poGaXffwiFOIhjGghY1QozuE5V/BYi4S2Pb7LYUtMikHmN2x
+0Agv1AzNxvYG1DxZjVMph/TNGqsf1qGk3IBAhc0S2HFDDqB3Xy1VmEV8t55IRBSj
+q96zvSFrzLO1dLDOAxJ35l4asMDh9+CitP98l9r+C8mjsenILRyOI7F1bbLUGiI5
+hNY186zNT9j3pHyKxcZXReizoa3s+2yloHBoZ/Lu/TgnYAi1sFLWXdIuK50uunf8
+gJVStyCQz2xqF2W1D0Xz6KesjJXvHIXYpGQ5ej42sEmRVwz/LcCMhVB+H38bRQ6x
+0lk7LtszEqqg9PCmCQMD7U9vwCd5D6sT+MSRwtGakRrudOwsz2RixVL5dYkk2Wqx
+onY3jEGAbiAiR/s/UuCjXkmZ0SfMTf1Ene1CWghsLrSZBKSUUnm0Es9JqfjnI5qb
+CWTo5kNpkcJ01XPwpw54QS9NyDIx9yADlmiDSaycALOqOgU4Jv24tq6oiMJtl4Ju
+SZvA21lYWVSUtVv3o6pMH4sEar9yj0VteEhwwUfJ63FyTZkhQpHoq+AVbrsDZlSs
+HoM4mOkfaYUoFTnZfGzVwcxCcBEQLXgB6rr/sFwfdyAnE+maHpI/1iw1g3FicyNM
+z6NKNj4f98AfjsMqr9DGgh7e9noBrrPsmgAobV2xqObKa7chOPkl71BJet2oZKvX
+zuWzXSJOt+rYsO54o3rmuxraYHWpPzlwHD4CyehI5bgyv9lLG2KzKFcm0jtocjjJ
+XXS3q28xk+P0jzL1+ye6vrIoFMjjn+FEKx0deYlJX5YKVr4mqHroejRKQ/vtjdkX
+DdeWH0RQYIwqEQIeLaBkMA+cKjAlGPOiTxrSK1FEdhp55J1ebJWEaw59Bu7cXe7o
+0xSOKHHf+WQxZ3P9DqWLYSvIUlZaDSXLJcXJ/TVz78pm8XglBe2Ktj+H1RaHYFU9
+HkTVIWlMFfqsG8QQnd7U8Dm5X0VZiZPwWgEIIKedWnpdhGToYgnF5w9UIWArbYn3
+xgrVrDxL+LeaKm3BuA77d+nTSRG3KsVgnx0h1EW3Blv8XNO9zWSK9soySCQqeXs4
+BnMXuNl0SDhWePHTQVeF0iVBSPsjMKGNykS39qJax4p59nJOFNCyKPMTolytTjl4
+ua8azBeJK6baCWvlTPdKjMWqFhkcLga11XJbKDMF545XUubNuUS+HjHMmZnjLDlj
+Rd1njppd/XujKo+umejMmY8BysF4kiTOze2NFQblfCamrUEEGygPLpvx59zY6B3c
+oi22hvMct0jPGTgLNabBbYzRqP4B9UmdubRcsElf9kpnlNse7gwRk+2D8vOcesaU
+NQCied/Gy9UE2q4R7ilrSduO7H5B3EhwHnt3KvbuZwE9
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/certs/root_cert.pem b/tests/certs/root_cert.pem
new file mode 100644
index 00000000..89c7217a
--- /dev/null
+++ b/tests/certs/root_cert.pem
@@ -0,0 +1,59 @@
+-----BEGIN CERTIFICATE-----
+MIIE4TCCA8mgAwIBAgIUJASIiImA+7/0+3xWyh32+h7sX18wDQYJKoZIhvcNAQEL
+BQAwgZgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1TYW4gRnJhbmNpc2NvMRUwEwYDVQQKDAxFeGFtcGxlIENvcnAxGDAWBgNVBAsM
+D0V4YW1wbGUgUm9vdCBDQTErMCkGA1UEAwwiRXhhbXBsZSBSb290IENlcnRpZmlj
+YXRlIEF1dGhvcml0eTAeFw0yNDA1MzEwMDUyNTJaFw0zNDA1MjkwMDUyNTJaMIGY
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2Fu
+IEZyYW5jaXNjbzEVMBMGA1UECgwMRXhhbXBsZSBDb3JwMRgwFgYDVQQLDA9FeGFt
+cGxlIFJvb3QgQ0ExKzApBgNVBAMMIkV4YW1wbGUgUm9vdCBDZXJ0aWZpY2F0ZSBB
+dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDXfYkjS5Hn
+wWXo7v5ua177wS/5iLug83V4TBDSblmRD4Kk8NJ6RpEW1Si0W/jqMK/jwLlFB+Kc
+f96oSelPgtaM3aKBo4raPCByDvhLidfJAWRw0h3iP7oswbuT8RQv5Os0Q94nE5RL
+Zh45+/8XKMSAG8I22n8gIdQ44vscwo4l+GnlLVLRP790bxt7N6fhE3jYa+Rs6Dh2
+v7TTPHtZluihy2xEWFVXcCg8sIqnyZCvzIiNMBtoDghNBb1RULQ8RDqWUeu0D+Ij
+z2Fu24obHvPOA52hevwmx27qbzvGxIyI/BcnG347Qbzd9vWM+VkDC9Dl8rCzDvrL
+NgNKTV2PkrWnAgMBAAGjggEfMIIBGzAdBgNVHQ4EFgQUrdGURBOEJicERCvtOXUy
+ed9YXXkwgdgGA1UdIwSB0DCBzYAUrdGURBOEJicERCvtOXUyed9YXXmhgZ6kgZsw
+gZgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1T
+YW4gRnJhbmNpc2NvMRUwEwYDVQQKDAxFeGFtcGxlIENvcnAxGDAWBgNVBAsMD0V4
+YW1wbGUgUm9vdCBDQTErMCkGA1UEAwwiRXhhbXBsZSBSb290IENlcnRpZmljYXRl
+IEF1dGhvcml0eYIUJASIiImA+7/0+3xWyh32+h7sX18wDwYDVR0TAQH/BAUwAwEB
+/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggEBAAIUkLvonrDowP/e
+R30+071Vx+fyZwZgXkV87aMsT/KPSRlXnCzDoDHD1He/iJ0wku6tCQVrKDfKm444
+uOAlLUifJSENUmMAeXfG2Lso0G7r7W6lKY9FB+22HmixSHbmRQCObW3GSYVe7VpX
+RAxFCNlPBg6+PB9ZKo8q18+ONlWBYd/kh58ENzHGomfNUqNQm4es2PfZyT9xD2jB
+zR/Fq5bL54jFbiS3vjjcSLYAUITi1mFiuBgYuoCq7J/QjZUqQ4qMXQ4jGB697C8H
+nnWJSji64N45Tdng8DAhdS98ct1iF53/k85Z+eQ4Ggoy+OYS95RTAjqmu2CXg0/V
+MZmr+KU=
+-----END CERTIFICATE-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI4xTQhXI9erUCAggA
+MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBCRkkchLRvzqdLnnJxhDoqGBIIE
+0I7WahjiZ02rrexZ7CpwMNR4tM0i8eM+YiwsfHhSEvnTkd5CdmG6wJlJ6nuLIYzv
+C4PH5YRVYep1M6wI9Was5gRfRZtEJ63wP0hfAna/a4ihziianJ1TpKP34Sr1U47k
+nW3vv20wCAydPLq1jTN2MVH0IcnqaHjQ5Ku5LKD8gghwomNgJtVz+0oGBPL+0Ssz
+qEcg6Q+aGGGMyem9FJuk7YLpIY2Oosb7IET0bwRGGSzM3+BbGp4GYAajgWbTRfsw
+rvbjVNjdJeqXIf8hpZYY4fmhNUROZslhS7mCuZfqWbVz9HohUV9cKXXUzr+SYVCk
+oGsKehVPtOCosMXDuNTmpbXzjUTY0A5G/zgPfSOXZ9NviOhCRBvCzP5dGirOmpQb
+d1KsuhJtnPVLpn1cv3fPD1VwpI/JlNrU/UIx3mu65Qta4GFih3Ftdl3Cbf43JmSy
+XOWGDHnoIeTIHMkRNEcGJkqb3glMgmRBfg8KJeeZeKBPVMv8HbancVSvTBlmIXKl
+jiFpq4xgwXma0dppNNEbV7DuhnMKZfVQJkdeBXmbEfcqz1PvM3Cd+NDzCGAuR8DK
+lkxGY0wJz/1WzJs8q99LLp+ObrE4e45BIq5ZCnE/7HF9V3GDJ95G+icodrfOuE7M
+/Kb8Eor501CTJ9nGtIo9M/BuwCeTZs16J6nvV6L5aohfmp8nG099aBcESyEK7xYM
+GdZr6FLQq0i35M+8X023Oy5BEnLSDqTO4VYYHfAPdmwNtbul1uSwBfKWmgF7XdPK
+t1EVME+58NMjTmXCfFV6CycXa1bDw/snMBO2QBURKpGp6DqrYOPljihrkmEzYQst
+6stuKKNas2rmsA2A6nsViKkXeZTOWeiJAkZcxdI1KyKS/1BOEU1D5w/EwvuaivMz
+3/7VOcMBE8A8r+QPUyOAnqSm9QlODcc0/AdtYftIB3du0sKnEP+JKbDTlwgtEKg7
+dqLr3uTzhkB47/bkhEQkf72JYNfLo62kbEYGflsFKIjAMsgmf6290zME4c30/4oT
+Xhlhj/cun0fcAFQn405xUpToyxjkQ/TRDIuvWvJld2Juk49IszTy++lvrfiSfd4U
+UVcAqURbNPsPUu38CatWIIMAK9zXEVfqiDlUA/eyzY89R/nKfsQXwWhj4UxLvHqN
+w2XyXuJLCz7ism5sromT0Qw/xLSPzOdAeflytsDpzNUD8nFPTYOoC6+Lg6AO3DyG
+N86QIDhwnMqDaiV/5Rl/knVGhbVZVyck3F7z2+klmjJjjeNCZC3BqeQ7eOBkrWej
+LOIFClmZCfijw2yWnR4HZLc5o89SII2FwXDjTQXJtzjx3O77MBbNmYXbO3lu5PFG
+AyEi/tmwEADHs5V+jLMkzW3xe/nyX+tjyOEpHt3ZbFXY4HqyEV7nCGy72yuSlnz7
+ZtGWSfz4hEdEQOSYFRid6S2kmfA/H8DujA1SGZYLnEkh2/W7rw7hOt2XI8EV6TiV
+azVducdN37XqBjgJ/1z1GmO+CLKBG54C7N03cq9kbm/9ygc1J6LfRbht1m0MzClU
+IhgeKcs1jvjj55nyix/yBjcBBZE2SA4lbkpAhlmbBXQlStWs0ZfkFlN86o18qIfp
+s2zJZHFkn3VbtL7XzhTqsi7lhNLMbr2ZIc9j29okIHP6
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/certs/root_cert_config.cnf b/tests/certs/root_cert_config.cnf
new file mode 100644
index 00000000..893bcd6d
--- /dev/null
+++ b/tests/certs/root_cert_config.cnf
@@ -0,0 +1,19 @@
+[ req ]
+default_bits       = 4096
+distinguished_name = req_distinguished_name
+x509_extensions    = v3_ca
+prompt             = no
+
+[ req_distinguished_name ]
+C  = US
+ST = California
+L  = San Francisco
+O  = Example Corp
+OU = Example Root CA
+CN = Example Root Certificate Authority
+
+[ v3_ca ]
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+basicConstraints = critical, CA:TRUE
+keyUsage = critical, digitalSignature, cRLSign, keyCertSign
diff --git a/tests/certs/root_private_key.pem b/tests/certs/root_private_key.pem
new file mode 100644
index 00000000..0c16953d
--- /dev/null
+++ b/tests/certs/root_private_key.pem
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI4xTQhXI9erUCAggA
+MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBCRkkchLRvzqdLnnJxhDoqGBIIE
+0I7WahjiZ02rrexZ7CpwMNR4tM0i8eM+YiwsfHhSEvnTkd5CdmG6wJlJ6nuLIYzv
+C4PH5YRVYep1M6wI9Was5gRfRZtEJ63wP0hfAna/a4ihziianJ1TpKP34Sr1U47k
+nW3vv20wCAydPLq1jTN2MVH0IcnqaHjQ5Ku5LKD8gghwomNgJtVz+0oGBPL+0Ssz
+qEcg6Q+aGGGMyem9FJuk7YLpIY2Oosb7IET0bwRGGSzM3+BbGp4GYAajgWbTRfsw
+rvbjVNjdJeqXIf8hpZYY4fmhNUROZslhS7mCuZfqWbVz9HohUV9cKXXUzr+SYVCk
+oGsKehVPtOCosMXDuNTmpbXzjUTY0A5G/zgPfSOXZ9NviOhCRBvCzP5dGirOmpQb
+d1KsuhJtnPVLpn1cv3fPD1VwpI/JlNrU/UIx3mu65Qta4GFih3Ftdl3Cbf43JmSy
+XOWGDHnoIeTIHMkRNEcGJkqb3glMgmRBfg8KJeeZeKBPVMv8HbancVSvTBlmIXKl
+jiFpq4xgwXma0dppNNEbV7DuhnMKZfVQJkdeBXmbEfcqz1PvM3Cd+NDzCGAuR8DK
+lkxGY0wJz/1WzJs8q99LLp+ObrE4e45BIq5ZCnE/7HF9V3GDJ95G+icodrfOuE7M
+/Kb8Eor501CTJ9nGtIo9M/BuwCeTZs16J6nvV6L5aohfmp8nG099aBcESyEK7xYM
+GdZr6FLQq0i35M+8X023Oy5BEnLSDqTO4VYYHfAPdmwNtbul1uSwBfKWmgF7XdPK
+t1EVME+58NMjTmXCfFV6CycXa1bDw/snMBO2QBURKpGp6DqrYOPljihrkmEzYQst
+6stuKKNas2rmsA2A6nsViKkXeZTOWeiJAkZcxdI1KyKS/1BOEU1D5w/EwvuaivMz
+3/7VOcMBE8A8r+QPUyOAnqSm9QlODcc0/AdtYftIB3du0sKnEP+JKbDTlwgtEKg7
+dqLr3uTzhkB47/bkhEQkf72JYNfLo62kbEYGflsFKIjAMsgmf6290zME4c30/4oT
+Xhlhj/cun0fcAFQn405xUpToyxjkQ/TRDIuvWvJld2Juk49IszTy++lvrfiSfd4U
+UVcAqURbNPsPUu38CatWIIMAK9zXEVfqiDlUA/eyzY89R/nKfsQXwWhj4UxLvHqN
+w2XyXuJLCz7ism5sromT0Qw/xLSPzOdAeflytsDpzNUD8nFPTYOoC6+Lg6AO3DyG
+N86QIDhwnMqDaiV/5Rl/knVGhbVZVyck3F7z2+klmjJjjeNCZC3BqeQ7eOBkrWej
+LOIFClmZCfijw2yWnR4HZLc5o89SII2FwXDjTQXJtzjx3O77MBbNmYXbO3lu5PFG
+AyEi/tmwEADHs5V+jLMkzW3xe/nyX+tjyOEpHt3ZbFXY4HqyEV7nCGy72yuSlnz7
+ZtGWSfz4hEdEQOSYFRid6S2kmfA/H8DujA1SGZYLnEkh2/W7rw7hOt2XI8EV6TiV
+azVducdN37XqBjgJ/1z1GmO+CLKBG54C7N03cq9kbm/9ygc1J6LfRbht1m0MzClU
+IhgeKcs1jvjj55nyix/yBjcBBZE2SA4lbkpAhlmbBXQlStWs0ZfkFlN86o18qIfp
+s2zJZHFkn3VbtL7XzhTqsi7lhNLMbr2ZIc9j29okIHP6
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/certs/server.csr b/tests/certs/server.csr
new file mode 100644
index 00000000..072163a7
--- /dev/null
+++ b/tests/certs/server.csr
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICyDCCAbACAQAwgYIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh
+MRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRUwEwYDVQQKDAxFeGFtcGxlIENvcnAx
+GzAZBgNVBAsMEkV4YW1wbGUgRGVwYXJ0bWVudDESMBAGA1UEAwwJbG9jYWxob3N0
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnDmJguna7lC32Dh0G4nK
+/jkLALbZuy+O+ziro3sRYHczCBDONtrp0QTKcXguWM8SOzTzqyDbaxZqvhPZKTFl
+/YrhxnY09e2djFUbt+zIPlJstq9IbL+8h/OpilvaeyX6uvyp/+W/OVou2lStHEzj
+0HTJsEAW8szdKFZHSJE50N8zMZasIpwIbCY8H4VfA9iKa7l3nVuN8isHcfomCX2Q
++bJOVR8vb1FgfWUGuAJP5ioC+kCEDLDm3Kln3JewsbkVh/05haEGDA4NoSk+OQNT
+pF1sLMOV/1Dgu8mniVyujfPlEhV7BUujwxMo8BBXLNsz6wtQMa7hmICI9uL80Kqh
+tQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAFtSyqclx/PWVHkKQ5iZCXFXO9wC
+eAXJzLekDU6oCsfwuTv2GMJKQaGX713QU5TS3m8IjeVUjncvDKbOlMEPbTXeVZdk
+gm0iPya7sJUmVdA+wqYktoD7q4hYSeCx6iVqAUN4IJOqTucRw8lP3J2CDPP3751C
+xh3domptOhHJ/1IvOkc3x4KYzN7mmnCOnddX/2mq7LbYTAwyf1Dv/1fsyWbedBQu
+4Nzqm8sq0AYOx470MiSR04HNbD/rEEepfTCpX8g6VK62PcEmvV/fdalI9JhGpNGp
+MT5BcTVWhvQctkOYQ/t6lJr7rnwm01TMOXe/ZtjL+l9L9QhMNNISkQDQOKQ=
+-----END CERTIFICATE REQUEST-----
diff --git a/tests/certs/server_cert.pem b/tests/certs/server_cert.pem
new file mode 100644
index 00000000..90cf5331
--- /dev/null
+++ b/tests/certs/server_cert.pem
@@ -0,0 +1,54 @@
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwKgAwIBAgIUeBwwUREutXXE5quSykpkKWu6pgkwDQYJKoZIhvcNAQEL
+BQAwgZgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1TYW4gRnJhbmNpc2NvMRUwEwYDVQQKDAxFeGFtcGxlIENvcnAxGDAWBgNVBAsM
+D0V4YW1wbGUgUm9vdCBDQTErMCkGA1UEAwwiRXhhbXBsZSBSb290IENlcnRpZmlj
+YXRlIEF1dGhvcml0eTAeFw0yNDA1MzEwMjM2MjNaFw0yNTA1MzEwMjM2MjNaMIGC
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2Fu
+IEZyYW5jaXNjbzEVMBMGA1UECgwMRXhhbXBsZSBDb3JwMRswGQYDVQQLDBJFeGFt
+cGxlIERlcGFydG1lbnQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAJw5iYLp2u5Qt9g4dBuJyv45CwC22bsvjvs4q6N7
+EWB3MwgQzjba6dEEynF4LljPEjs086sg22sWar4T2SkxZf2K4cZ2NPXtnYxVG7fs
+yD5SbLavSGy/vIfzqYpb2nsl+rr8qf/lvzlaLtpUrRxM49B0ybBAFvLM3ShWR0iR
+OdDfMzGWrCKcCGwmPB+FXwPYimu5d51bjfIrB3H6Jgl9kPmyTlUfL29RYH1lBrgC
+T+YqAvpAhAyw5typZ9yXsLG5FYf9OYWhBgwODaEpPjkDU6RdbCzDlf9Q4LvJp4lc
+ro3z5RIVewVLo8MTKPAQVyzbM+sLUDGu4ZiAiPbi/NCqobUCAwEAAaNwMG4wHwYD
+VR0jBBgwFoAUrdGURBOEJicERCvtOXUyed9YXXkwCQYDVR0TBAIwADALBgNVHQ8E
+BAMCBPAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MB0GA1UdDgQWBBQNAYfs9I8zvd/k
+QVbOtbLwBRoN4TANBgkqhkiG9w0BAQsFAAOCAQEAcW8c+6tyYvy+hw+OvcZtRAFp
+SyZs4Wmse6Dd7buMaVcr0vOzTAlP/q8AJfsBuK6C8FP8SGKLGQlW55BarSRPr6Dp
+1s0Zy/nuyEpgZSOul3MG25YqLA/wjM+wObfQyPl/tN3UX7EWun3WU8ZuEtsedpeG
+Qiu8s/UOUXte8IjFtCgbKne3pYAnOWm/kwXx2PYvem+3OI+4zUngJfV4R+ly+L5L
+UEmqGRzYd57/eoxhSYIZEhwPaddqQe9N1bwoxyL9Fjk3mlSHWGm/Dg6DQWKNNbo4
+MBuGT9+EC1UWCfe/nmenACH9jseIzN8V5ycjKos+PnoL9rgyc6Y6EFCMIdPeQQ==
+-----END CERTIFICATE-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI9SpzeqXtXDUCAggA
+MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBI2eduVU7VV7g1UK3Z5UmaBIIE
+0GcnJXoUDvtxyZaP3x9jP4dc1GrLpagwY9IACECpAOdmBA0Z9v9eS5JrBYiaCZNR
+MukCuGMBuMB7c+a7jGvRKJqsXSrRj4TQ5Ii+IeJXznXQnSmUN36rGkR7KAONAL8I
+Q4dmfy4JMcrGrSTJ6koLrDl8yxiaAU8C9Ds7tdh61wnSZ1g5LpqgQwowxQPcdkmf
+CKqReD0xjvrBsr8z0Nv+rhpYyvSqNm/TMhp4IiUAt0gRCamMUlVV29mV5GgQjUfV
+F4O31xLnPi6pgQubzQ1L+VE258kiBDQ4LALE3oYkSaOcjvSEUBxBxe+B3zohmPGh
+dSeb3e83Ew+SvCjb+jvro6W4WieE0OBRNatVfbMy/Hz+qKOtko+nSXqdybLJ1BEh
+/QD514bg5at1A0LHqyPMU4ohh6AmI97L3UiNqF9m8DJvnb46SC61+CvjaT3flUtc
+dr1eDuARbxfP1lbgcxV3zTvZb9A+OEs2YmQvra1jJRxy3EpoNymGSa4ZgfS2pxLG
+o4xU0rRaRicLOSRUGTy9zlPRdzpzaHfMG3Ba6IeeVVNNi9EojnKGu0NaOM29lLya
+47VW8Mx/k8a7SjJOEPC39N8TX754w+7H7cuKivfSdzcjHVea9ny3rqSuzzn+gFKY
+NBRtDdUTXemQzgSuoOpVpw3KfEF7tnpB+wjqxOs4+pzFOxnVW8qaEMx2EiYIs4/G
+QE5+RrRzLFzUKOMbR7cvKbFTWNyyj0R7MOtPeizYsw6uhNgPCLkkTPO35QRKExyL
+tCf999m3VQORFRsmacBHGlJEEF/T4RWGNZ4Y5rgHnOvp1jiBIPNHWZel1pvn7iyY
+cUqzzIyOuAqfdm22SeKdoWG6514EeJWq+5bWtmoaT4M+gH8Kc35cwrMau1EdGhAb
+Ca6NU12WV2WUm0DpufL29HL/mJ6Wl5aZovmxvKEEg72S9msxa4qTIZOOGAFYPOyt
+tp3OgM8g6jSjPEz+MezTkMxFk5z7REqcrFkavPl6Fd7VnpJsMe4tF9HXiAfyJn25
+rAtwG8rkzyEmSyFaKGcYU/8bpi38KrfNz3dT0a20IE2Xfl06A5f9v3zh73BJ4B/V
+WwF7wG/+2cYBns/UI5H90f33imRoecyC3cGKibd3RYBv6xSDXf9qU7vp6h7a+zZH
+veyEeaD8uAA06u/4lCF0h1+cONBcOuXw49MIIab0271SjAfHNlra2S9me84mGGWM
+ntPuOWPdNFABjJeXBuDPprFQ6uGUXpnPsN5ETJ02+7L8HstmEc7YobwPN1yq3sNV
+Mb6WeqMrpIXT7b2S38ju9owvCSqCbCD5SZV4QRcL7/xC12dN2XpY5DC+yYcZMAgI
+toyTLGiEJcIf+atWiX0qbxzh5HkHiQQw8NWgQQTkkO/8CU+CVR70z5J6vlWtokO+
+cpyxLnfG6O/YkG/WH3u3IgvFXBwfHY0A5ZNLgkmlyPJXBPYHnJcgUeoYb3e/HpdD
+lQJdU7oNmV3nBw6u4jOjMnVxx3/50KWg15R2lr8DxlxgQapf7UTSXMTQK9NsAiqe
+vsnOs1leDJ4MFzYwhYWBbzkitds4OyxubvBBbDwMxpGp9VqLgxtrvIm/bkBGUKnh
+U5f+Y3htuj3SWG7NGvy+6wjDhrMYgiZ7X7fndEmXvs5x
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/certs/server_cert_config.cnf b/tests/certs/server_cert_config.cnf
new file mode 100644
index 00000000..7d003adc
--- /dev/null
+++ b/tests/certs/server_cert_config.cnf
@@ -0,0 +1,12 @@
+[ req ]
+default_bits       = 2048
+distinguished_name = req_distinguished_name
+prompt             = no
+
+[ req_distinguished_name ]
+C  = US
+ST = California
+L  = San Francisco
+O  = Example Corp
+OU = Example Department
+CN = localhost
\ No newline at end of file
diff --git a/tests/certs/server_private_key.pem b/tests/certs/server_private_key.pem
new file mode 100644
index 00000000..2f08bfb5
--- /dev/null
+++ b/tests/certs/server_private_key.pem
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI9SpzeqXtXDUCAggA
+MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBI2eduVU7VV7g1UK3Z5UmaBIIE
+0GcnJXoUDvtxyZaP3x9jP4dc1GrLpagwY9IACECpAOdmBA0Z9v9eS5JrBYiaCZNR
+MukCuGMBuMB7c+a7jGvRKJqsXSrRj4TQ5Ii+IeJXznXQnSmUN36rGkR7KAONAL8I
+Q4dmfy4JMcrGrSTJ6koLrDl8yxiaAU8C9Ds7tdh61wnSZ1g5LpqgQwowxQPcdkmf
+CKqReD0xjvrBsr8z0Nv+rhpYyvSqNm/TMhp4IiUAt0gRCamMUlVV29mV5GgQjUfV
+F4O31xLnPi6pgQubzQ1L+VE258kiBDQ4LALE3oYkSaOcjvSEUBxBxe+B3zohmPGh
+dSeb3e83Ew+SvCjb+jvro6W4WieE0OBRNatVfbMy/Hz+qKOtko+nSXqdybLJ1BEh
+/QD514bg5at1A0LHqyPMU4ohh6AmI97L3UiNqF9m8DJvnb46SC61+CvjaT3flUtc
+dr1eDuARbxfP1lbgcxV3zTvZb9A+OEs2YmQvra1jJRxy3EpoNymGSa4ZgfS2pxLG
+o4xU0rRaRicLOSRUGTy9zlPRdzpzaHfMG3Ba6IeeVVNNi9EojnKGu0NaOM29lLya
+47VW8Mx/k8a7SjJOEPC39N8TX754w+7H7cuKivfSdzcjHVea9ny3rqSuzzn+gFKY
+NBRtDdUTXemQzgSuoOpVpw3KfEF7tnpB+wjqxOs4+pzFOxnVW8qaEMx2EiYIs4/G
+QE5+RrRzLFzUKOMbR7cvKbFTWNyyj0R7MOtPeizYsw6uhNgPCLkkTPO35QRKExyL
+tCf999m3VQORFRsmacBHGlJEEF/T4RWGNZ4Y5rgHnOvp1jiBIPNHWZel1pvn7iyY
+cUqzzIyOuAqfdm22SeKdoWG6514EeJWq+5bWtmoaT4M+gH8Kc35cwrMau1EdGhAb
+Ca6NU12WV2WUm0DpufL29HL/mJ6Wl5aZovmxvKEEg72S9msxa4qTIZOOGAFYPOyt
+tp3OgM8g6jSjPEz+MezTkMxFk5z7REqcrFkavPl6Fd7VnpJsMe4tF9HXiAfyJn25
+rAtwG8rkzyEmSyFaKGcYU/8bpi38KrfNz3dT0a20IE2Xfl06A5f9v3zh73BJ4B/V
+WwF7wG/+2cYBns/UI5H90f33imRoecyC3cGKibd3RYBv6xSDXf9qU7vp6h7a+zZH
+veyEeaD8uAA06u/4lCF0h1+cONBcOuXw49MIIab0271SjAfHNlra2S9me84mGGWM
+ntPuOWPdNFABjJeXBuDPprFQ6uGUXpnPsN5ETJ02+7L8HstmEc7YobwPN1yq3sNV
+Mb6WeqMrpIXT7b2S38ju9owvCSqCbCD5SZV4QRcL7/xC12dN2XpY5DC+yYcZMAgI
+toyTLGiEJcIf+atWiX0qbxzh5HkHiQQw8NWgQQTkkO/8CU+CVR70z5J6vlWtokO+
+cpyxLnfG6O/YkG/WH3u3IgvFXBwfHY0A5ZNLgkmlyPJXBPYHnJcgUeoYb3e/HpdD
+lQJdU7oNmV3nBw6u4jOjMnVxx3/50KWg15R2lr8DxlxgQapf7UTSXMTQK9NsAiqe
+vsnOs1leDJ4MFzYwhYWBbzkitds4OyxubvBBbDwMxpGp9VqLgxtrvIm/bkBGUKnh
+U5f+Y3htuj3SWG7NGvy+6wjDhrMYgiZ7X7fndEmXvs5x
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/certs/v3_ext.cnf b/tests/certs/v3_ext.cnf
new file mode 100644
index 00000000..ab9de5fb
--- /dev/null
+++ b/tests/certs/v3_ext.cnf
@@ -0,0 +1,7 @@
+authorityKeyIdentifier=keyid,issuer
+basicConstraints=CA:FALSE
+keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
+subjectAltName = @alt_names
+
+[alt_names]
+DNS.1 = localhost