From b959f8333bd3d4140bb55cb956c6b7426c20d9d8 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Fri, 17 May 2024 16:00:35 -0500 Subject: [PATCH 1/8] stubbing out SSL support for MQTT clients --- src/TurboMqtt/Client/ClientTlsOptions.cs | 21 ++++++++++++++++++++ src/TurboMqtt/Client/MqttClientTcpOptions.cs | 19 ++++++++++++------ 2 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 src/TurboMqtt/Client/ClientTlsOptions.cs 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/MqttClientTcpOptions.cs b/src/TurboMqtt/Client/MqttClientTcpOptions.cs index 5cc54591..c476b6e5 100644 --- a/src/TurboMqtt/Client/MqttClientTcpOptions.cs +++ b/src/TurboMqtt/Client/MqttClientTcpOptions.cs @@ -23,26 +23,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 From edccdae6e7464ac551db4d10ba20366ccd329bab Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Fri, 17 May 2024 16:02:11 -0500 Subject: [PATCH 2/8] minor cleanup --- src/TurboMqtt/Client/MqttClientTcpOptions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/TurboMqtt/Client/MqttClientTcpOptions.cs b/src/TurboMqtt/Client/MqttClientTcpOptions.cs index c476b6e5..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; From fe5859a56053d3d56eff5cc84492608765db83ca Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Fri, 17 May 2024 16:18:55 -0500 Subject: [PATCH 3/8] migrated TCP socket over to using `NetworkStream` --- src/TurboMqtt/IO/Tcp/TcpTransportActor.cs | 53 ++++++++++++++--------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs b/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs index 64333fd6..000b7967 100644 --- a/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs +++ b/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs @@ -7,6 +7,7 @@ using System.Buffers; using System.IO.Pipelines; using System.Net; +using System.Net.Security; using System.Net.Sockets; using System.Threading.Channels; using Akka.Actor; @@ -43,11 +44,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 +114,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 +135,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 +146,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 +156,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 +225,7 @@ 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); } catch (Exception ex) { @@ -244,7 +251,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 +271,7 @@ async Task ResolveAndConnect(CancellationToken ct) await DoConnectAsync(resolved, TcpOptions.Port, sender, ct).ConfigureAwait(false); } }); - + break; } case DoConnect: @@ -322,19 +329,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 +372,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 +382,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 +485,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 +525,8 @@ private void DisposeSocket(ConnectionStatus newStatus) _pipe.Reader.Complete(); _pipe.Writer.Complete(); + _tcpStream?.Close(); + _tcpStream?.Dispose(); _tcpClient?.Close(); _tcpClient?.Dispose(); } @@ -524,6 +536,7 @@ private void DisposeSocket(ConnectionStatus newStatus) } finally { + _tcpStream = null; _tcpClient = null; } } From 27983c9f4e4838cc481f999a58dcf6a4c89f6bb2 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Fri, 17 May 2024 16:37:57 -0500 Subject: [PATCH 4/8] don't call flush explicitly --- src/TurboMqtt/IO/Tcp/TcpTransportActor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs b/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs index 000b7967..f33babb5 100644 --- a/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs +++ b/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs @@ -333,7 +333,7 @@ private async Task DoWriteToSocketAsync(CancellationToken ct) { await _tcpStream!.WriteAsync(workingBuffer.Slice(0, readableBytes), ct) .ConfigureAwait(false); - await _tcpStream.FlushAsync(ct); + //await _tcpStream.FlushAsync(ct); } else { From ddc8f277c2c3837c203998297fa1710090ea4041 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Wed, 29 May 2024 13:15:12 -0500 Subject: [PATCH 5/8] added self-signed certificates --- tests/certs/certificate.pem | 22 ++++++++++++++++ tests/certs/csr.pem | 18 +++++++++++++ tests/certs/private_key.pem | 30 +++++++++++++++++++++ tests/certs/server.pem | 52 +++++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 tests/certs/certificate.pem create mode 100644 tests/certs/csr.pem create mode 100644 tests/certs/private_key.pem create mode 100644 tests/certs/server.pem diff --git a/tests/certs/certificate.pem b/tests/certs/certificate.pem new file mode 100644 index 00000000..55df8a68 --- /dev/null +++ b/tests/certs/certificate.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDjTCCAnUCFH01uBdSeoiCJDk7UWnEUDLwrviEMA0GCSqGSIb3DQEBCwUAMIGB +MQswCQYDVQQGEwJVUzELMAkGA1UECAwCVFgxEDAOBgNVBAcMB0hvdXN0b24xGDAW +BgNVBAoMD1BldGFicmlkZ2UsIExMQzEXMBUGA1UEAwwOcGV0YWJyaWRnZS5jb20x +IDAeBgkqhkiG9w0BCQEWEWhpQHBldGFicmlkZ2UuY29tMCAXDTI0MDUyOTE4MTQy +MVoYDzIxMjQwNTA1MTgxNDIxWjCBgTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlRY +MRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9QZXRhYnJpZGdlLCBMTEMxFzAV +BgNVBAMMDnBldGFicmlkZ2UuY29tMSAwHgYJKoZIhvcNAQkBFhFoaUBwZXRhYnJp +ZGdlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMlKSnjXP0Ll +3xdDeHDTtIQX5iE/S0pXy93rvNhHUUgpypPBxXtThmQiXoS12cHHUoHcGeyxiOSO +GUf3kQiWRyLLd/v8ARWBX3gN1arpgC8+8DRVTntHuaLGpHFklfWQuOhJ2NciCr3F +qcPNKNpWd+75s5e6lK5Pt0kOjiXB8Bz60y564zyB2weFQvTlKa/frF5tEIw+U3kd +mrzRNmRlqBpPisEjjwmB6idRjN41XkbcO2p+GmfXgY/3eJASQzPaje6p5Y9CgdwQ +ybOFU4sFAq7XCpe9zBWjm0SRVcv2g2OYvljTa/nWM6lDfaQYQoeXGAvMPzV6gPx8 +qeUtTy/khwkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAkHZwsfZvMVRjC6PbpfY/ +tp3vWD1RoeWkyEPUGuGPCAoQuaAraBNFEW99y5Z7RUkzYmFlfV2O7dsEPmk3h937 +4+jz1oL9gFzAm+r0erkE8Al7RKgrK+bKWeSpMyRrtBdRDODV78a4x+iM0GFJ/Oyk +zDtZoXWhSSaGlaiSYqJAzyu3x78JGc9al/qukU/R/dpFG6H3WVzS8S2Ma+wHqMTE +jL9fs6SnkXuzVr0cXZZPNsZRfZV1CnG8KRmDdX4eRI0HWMzVe5Mkiv+DcfNL0y1o +0jWnmeo08hOo7bXFMLLq8AnD6ylRELvgRZBcZY6wXecuiwiA7cE5pzAKJb1Kag68 +4A== +-----END CERTIFICATE----- diff --git a/tests/certs/csr.pem b/tests/certs/csr.pem new file mode 100644 index 00000000..e6516e6a --- /dev/null +++ b/tests/certs/csr.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIC4jCCAcoCAQAwgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJUWDEQMA4GA1UE +BwwHSG91c3RvbjEYMBYGA1UECgwPUGV0YWJyaWRnZSwgTExDMRcwFQYDVQQDDA5w +ZXRhYnJpZGdlLmNvbTEgMB4GCSqGSIb3DQEJARYRaGlAcGV0YWJyaWRnZS5jb20w +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJSkp41z9C5d8XQ3hw07SE +F+YhP0tKV8vd67zYR1FIKcqTwcV7U4ZkIl6EtdnBx1KB3BnssYjkjhlH95EIlkci +y3f7/AEVgV94DdWq6YAvPvA0VU57R7mixqRxZJX1kLjoSdjXIgq9xanDzSjaVnfu ++bOXupSuT7dJDo4lwfAc+tMueuM8gdsHhUL05Smv36xebRCMPlN5HZq80TZkZaga +T4rBI48JgeonUYzeNV5G3Dtqfhpn14GP93iQEkMz2o3uqeWPQoHcEMmzhVOLBQKu +1wqXvcwVo5tEkVXL9oNjmL5Y02v51jOpQ32kGEKHlxgLzD81eoD8fKnlLU8v5IcJ +AgMBAAGgGzAZBgkqhkiG9w0BCQIxDAwKUGV0YWJyaWRnZTANBgkqhkiG9w0BAQsF +AAOCAQEAHRUovmreAXxBPI4MUEyFePZtgMDu+oDaMO/sPXA3zp4YG7fmRJkIiT37 +UjLLp3bCFqu1Smxdic8VVGTMHVbBvSs19qcUSBOQak0TEO/jZgmPpNOp+fKEp7g8 ++ZcLf+N/8EAwWF4GFL+XyLFMQaA4EqeGrOzCB/JhOzNIuO33fGIvYnZsDrM9Uy2d +puezS46ycGxty0QONrJI4XskM69eaKUP07cswu069BBlnH9ifBODSjyLw2k3uOEH +fO+QLL3IydRuxdC59iBDYM6vMxOSjGoSAAuywk3dbj6FOVgdN2zUSK8Bz52zC4Z3 +Z5d2KHgQjrM+3HJHDEDEp8BShsicog== +-----END CERTIFICATE REQUEST----- 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/server.pem b/tests/certs/server.pem new file mode 100644 index 00000000..bbadd809 --- /dev/null +++ b/tests/certs/server.pem @@ -0,0 +1,52 @@ +-----BEGIN CERTIFICATE----- +MIIDjTCCAnUCFH01uBdSeoiCJDk7UWnEUDLwrviEMA0GCSqGSIb3DQEBCwUAMIGB +MQswCQYDVQQGEwJVUzELMAkGA1UECAwCVFgxEDAOBgNVBAcMB0hvdXN0b24xGDAW +BgNVBAoMD1BldGFicmlkZ2UsIExMQzEXMBUGA1UEAwwOcGV0YWJyaWRnZS5jb20x +IDAeBgkqhkiG9w0BCQEWEWhpQHBldGFicmlkZ2UuY29tMCAXDTI0MDUyOTE4MTQy +MVoYDzIxMjQwNTA1MTgxNDIxWjCBgTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlRY +MRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9QZXRhYnJpZGdlLCBMTEMxFzAV +BgNVBAMMDnBldGFicmlkZ2UuY29tMSAwHgYJKoZIhvcNAQkBFhFoaUBwZXRhYnJp +ZGdlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMlKSnjXP0Ll +3xdDeHDTtIQX5iE/S0pXy93rvNhHUUgpypPBxXtThmQiXoS12cHHUoHcGeyxiOSO +GUf3kQiWRyLLd/v8ARWBX3gN1arpgC8+8DRVTntHuaLGpHFklfWQuOhJ2NciCr3F +qcPNKNpWd+75s5e6lK5Pt0kOjiXB8Bz60y564zyB2weFQvTlKa/frF5tEIw+U3kd +mrzRNmRlqBpPisEjjwmB6idRjN41XkbcO2p+GmfXgY/3eJASQzPaje6p5Y9CgdwQ +ybOFU4sFAq7XCpe9zBWjm0SRVcv2g2OYvljTa/nWM6lDfaQYQoeXGAvMPzV6gPx8 +qeUtTy/khwkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAkHZwsfZvMVRjC6PbpfY/ +tp3vWD1RoeWkyEPUGuGPCAoQuaAraBNFEW99y5Z7RUkzYmFlfV2O7dsEPmk3h937 +4+jz1oL9gFzAm+r0erkE8Al7RKgrK+bKWeSpMyRrtBdRDODV78a4x+iM0GFJ/Oyk +zDtZoXWhSSaGlaiSYqJAzyu3x78JGc9al/qukU/R/dpFG6H3WVzS8S2Ma+wHqMTE +jL9fs6SnkXuzVr0cXZZPNsZRfZV1CnG8KRmDdX4eRI0HWMzVe5Mkiv+DcfNL0y1o +0jWnmeo08hOo7bXFMLLq8AnD6ylRELvgRZBcZY6wXecuiwiA7cE5pzAKJb1Kag68 +4A== +-----END CERTIFICATE----- +-----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----- From ae192f096ed2f26af957a57103c7c47490077d25 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Wed, 29 May 2024 13:15:21 -0500 Subject: [PATCH 6/8] moved tests --- .../End2End/{ => MQTT_311}/InMemoryMqtt311End2EndSpecs.cs | 0 .../End2End/{ => MQTT_311}/TcpMqtt311End2EndSpecs.cs | 0 .../{ => MQTT_311}/TcpMqtt311HeartbeatFailureEnd2EndSpecs.cs | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename tests/TurboMqtt.Tests/End2End/{ => MQTT_311}/InMemoryMqtt311End2EndSpecs.cs (100%) rename tests/TurboMqtt.Tests/End2End/{ => MQTT_311}/TcpMqtt311End2EndSpecs.cs (100%) rename tests/TurboMqtt.Tests/End2End/{ => MQTT_311}/TcpMqtt311HeartbeatFailureEnd2EndSpecs.cs (100%) 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 100% rename from tests/TurboMqtt.Tests/End2End/TcpMqtt311End2EndSpecs.cs rename to tests/TurboMqtt.Tests/End2End/MQTT_311/TcpMqtt311End2EndSpecs.cs 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 From 9167ca22ce4fca4273e65319a9ca322406a572cf Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Wed, 29 May 2024 14:00:04 -0500 Subject: [PATCH 7/8] ported `FakeMqttTcpServer` to use `NetworkStream` --- src/TurboMqtt/IO/Tcp/FakeMqttTcpServer.cs | 79 +++++++++++++---------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/src/TurboMqtt/IO/Tcp/FakeMqttTcpServer.cs b/src/TurboMqtt/IO/Tcp/FakeMqttTcpServer.cs index bd4b758d..ca7b248a 100644 --- a/src/TurboMqtt/IO/Tcp/FakeMqttTcpServer.cs +++ b/src/TurboMqtt/IO/Tcp/FakeMqttTcpServer.cs @@ -8,6 +8,7 @@ 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; @@ -35,6 +36,8 @@ public MqttTcpServerOptions(string host, int port) public string Host { get; } public int Port { get; } + + public SslServerAuthenticationOptions? SslOptions { get; set; } } /// @@ -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,17 @@ 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) + { + var sslStream = new SslStream(readingStream, false); + await sslStream.AuthenticateAsServerAsync(_options.SslOptions, _shutdownTcs.Token); + readingStream = sslStream; + } + + _ = ProcessClientAsync(readingStream); } } @@ -174,7 +187,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 +206,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 +219,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 +232,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 +254,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 +302,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 +312,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 +337,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 + } } } } From 3b53deeb902fa4171c107a7e46a10d69bd444922 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Fri, 31 May 2024 21:23:18 +0700 Subject: [PATCH 8/8] Add TLS end to end unit test (#1) * Add TLS end to end unit test * Make TLS spec inherit TCP spec --- src/TurboMqtt/Client/IMqttClientFactory.cs | 3 + src/TurboMqtt/IO/Tcp/FakeMqttTcpServer.cs | 27 ++-- src/TurboMqtt/IO/Tcp/TcpTransportActor.cs | 9 ++ .../MQTT_311/TcpMqtt311End2EndSpecs.cs | 24 +-- .../MQTT_311/TlsMqtt311End2EndSpecs.cs | 137 ++++++++++++++++++ tests/TurboMqtt.Tests/TurboMqtt.Tests.csproj | 14 ++ tests/certs/README.md | 30 ++++ tests/certs/certificate.pem | 22 --- tests/certs/csr.pem | 18 --- tests/certs/root_cert.pem | 59 ++++++++ tests/certs/root_cert_config.cnf | 19 +++ tests/certs/root_private_key.pem | 30 ++++ tests/certs/server.csr | 17 +++ tests/certs/server.pem | 52 ------- tests/certs/server_cert.pem | 54 +++++++ tests/certs/server_cert_config.cnf | 12 ++ tests/certs/server_private_key.pem | 30 ++++ tests/certs/v3_ext.cnf | 7 + 18 files changed, 451 insertions(+), 113 deletions(-) create mode 100644 tests/TurboMqtt.Tests/End2End/MQTT_311/TlsMqtt311End2EndSpecs.cs create mode 100644 tests/certs/README.md delete mode 100644 tests/certs/certificate.pem delete mode 100644 tests/certs/csr.pem create mode 100644 tests/certs/root_cert.pem create mode 100644 tests/certs/root_cert_config.cnf create mode 100644 tests/certs/root_private_key.pem create mode 100644 tests/certs/server.csr delete mode 100644 tests/certs/server.pem create mode 100644 tests/certs/server_cert.pem create mode 100644 tests/certs/server_cert_config.cnf create mode 100644 tests/certs/server_private_key.pem create mode 100644 tests/certs/v3_ext.cnf 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/IO/Tcp/FakeMqttTcpServer.cs b/src/TurboMqtt/IO/Tcp/FakeMqttTcpServer.cs index ca7b248a..4bb7cdf2 100644 --- a/src/TurboMqtt/IO/Tcp/FakeMqttTcpServer.cs +++ b/src/TurboMqtt/IO/Tcp/FakeMqttTcpServer.cs @@ -15,7 +15,7 @@ namespace TurboMqtt.IO.Tcp; -internal sealed class MqttTcpServerOptions +public sealed record MqttTcpServerOptions { public MqttTcpServerOptions(string host, int port) { @@ -26,18 +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; set; } + public SslServerAuthenticationOptions? SslOptions { get; init; } } /// @@ -162,9 +162,18 @@ private async Task BeginAcceptAsync() // check for TLS if (_options.SslOptions != null) { - var sslStream = new SslStream(readingStream, false); - await sslStream.AuthenticateAsServerAsync(_options.SslOptions, _shutdownTcs.Token); - readingStream = sslStream; + 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); diff --git a/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs b/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs index f33babb5..0ab11e4d 100644 --- a/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs +++ b/src/TurboMqtt/IO/Tcp/TcpTransportActor.cs @@ -9,6 +9,7 @@ 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; @@ -226,6 +227,14 @@ private async Task DoConnectAsync(IPAddress[] addresses, int port, IActorRef des 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) { diff --git a/tests/TurboMqtt.Tests/End2End/MQTT_311/TcpMqtt311End2EndSpecs.cs b/tests/TurboMqtt.Tests/End2End/MQTT_311/TcpMqtt311End2EndSpecs.cs index d1cc4f28..fc708d63 100644 --- a/tests/TurboMqtt.Tests/End2End/MQTT_311/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/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/certificate.pem b/tests/certs/certificate.pem deleted file mode 100644 index 55df8a68..00000000 --- a/tests/certs/certificate.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDjTCCAnUCFH01uBdSeoiCJDk7UWnEUDLwrviEMA0GCSqGSIb3DQEBCwUAMIGB -MQswCQYDVQQGEwJVUzELMAkGA1UECAwCVFgxEDAOBgNVBAcMB0hvdXN0b24xGDAW -BgNVBAoMD1BldGFicmlkZ2UsIExMQzEXMBUGA1UEAwwOcGV0YWJyaWRnZS5jb20x -IDAeBgkqhkiG9w0BCQEWEWhpQHBldGFicmlkZ2UuY29tMCAXDTI0MDUyOTE4MTQy -MVoYDzIxMjQwNTA1MTgxNDIxWjCBgTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlRY -MRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9QZXRhYnJpZGdlLCBMTEMxFzAV -BgNVBAMMDnBldGFicmlkZ2UuY29tMSAwHgYJKoZIhvcNAQkBFhFoaUBwZXRhYnJp -ZGdlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMlKSnjXP0Ll -3xdDeHDTtIQX5iE/S0pXy93rvNhHUUgpypPBxXtThmQiXoS12cHHUoHcGeyxiOSO -GUf3kQiWRyLLd/v8ARWBX3gN1arpgC8+8DRVTntHuaLGpHFklfWQuOhJ2NciCr3F -qcPNKNpWd+75s5e6lK5Pt0kOjiXB8Bz60y564zyB2weFQvTlKa/frF5tEIw+U3kd -mrzRNmRlqBpPisEjjwmB6idRjN41XkbcO2p+GmfXgY/3eJASQzPaje6p5Y9CgdwQ -ybOFU4sFAq7XCpe9zBWjm0SRVcv2g2OYvljTa/nWM6lDfaQYQoeXGAvMPzV6gPx8 -qeUtTy/khwkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAkHZwsfZvMVRjC6PbpfY/ -tp3vWD1RoeWkyEPUGuGPCAoQuaAraBNFEW99y5Z7RUkzYmFlfV2O7dsEPmk3h937 -4+jz1oL9gFzAm+r0erkE8Al7RKgrK+bKWeSpMyRrtBdRDODV78a4x+iM0GFJ/Oyk -zDtZoXWhSSaGlaiSYqJAzyu3x78JGc9al/qukU/R/dpFG6H3WVzS8S2Ma+wHqMTE -jL9fs6SnkXuzVr0cXZZPNsZRfZV1CnG8KRmDdX4eRI0HWMzVe5Mkiv+DcfNL0y1o -0jWnmeo08hOo7bXFMLLq8AnD6ylRELvgRZBcZY6wXecuiwiA7cE5pzAKJb1Kag68 -4A== ------END CERTIFICATE----- diff --git a/tests/certs/csr.pem b/tests/certs/csr.pem deleted file mode 100644 index e6516e6a..00000000 --- a/tests/certs/csr.pem +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIC4jCCAcoCAQAwgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJUWDEQMA4GA1UE -BwwHSG91c3RvbjEYMBYGA1UECgwPUGV0YWJyaWRnZSwgTExDMRcwFQYDVQQDDA5w -ZXRhYnJpZGdlLmNvbTEgMB4GCSqGSIb3DQEJARYRaGlAcGV0YWJyaWRnZS5jb20w -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJSkp41z9C5d8XQ3hw07SE -F+YhP0tKV8vd67zYR1FIKcqTwcV7U4ZkIl6EtdnBx1KB3BnssYjkjhlH95EIlkci -y3f7/AEVgV94DdWq6YAvPvA0VU57R7mixqRxZJX1kLjoSdjXIgq9xanDzSjaVnfu -+bOXupSuT7dJDo4lwfAc+tMueuM8gdsHhUL05Smv36xebRCMPlN5HZq80TZkZaga -T4rBI48JgeonUYzeNV5G3Dtqfhpn14GP93iQEkMz2o3uqeWPQoHcEMmzhVOLBQKu -1wqXvcwVo5tEkVXL9oNjmL5Y02v51jOpQ32kGEKHlxgLzD81eoD8fKnlLU8v5IcJ -AgMBAAGgGzAZBgkqhkiG9w0BCQIxDAwKUGV0YWJyaWRnZTANBgkqhkiG9w0BAQsF -AAOCAQEAHRUovmreAXxBPI4MUEyFePZtgMDu+oDaMO/sPXA3zp4YG7fmRJkIiT37 -UjLLp3bCFqu1Smxdic8VVGTMHVbBvSs19qcUSBOQak0TEO/jZgmPpNOp+fKEp7g8 -+ZcLf+N/8EAwWF4GFL+XyLFMQaA4EqeGrOzCB/JhOzNIuO33fGIvYnZsDrM9Uy2d -puezS46ycGxty0QONrJI4XskM69eaKUP07cswu069BBlnH9ifBODSjyLw2k3uOEH -fO+QLL3IydRuxdC59iBDYM6vMxOSjGoSAAuywk3dbj6FOVgdN2zUSK8Bz52zC4Z3 -Z5d2KHgQjrM+3HJHDEDEp8BShsicog== ------END CERTIFICATE REQUEST----- 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.pem b/tests/certs/server.pem deleted file mode 100644 index bbadd809..00000000 --- a/tests/certs/server.pem +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDjTCCAnUCFH01uBdSeoiCJDk7UWnEUDLwrviEMA0GCSqGSIb3DQEBCwUAMIGB -MQswCQYDVQQGEwJVUzELMAkGA1UECAwCVFgxEDAOBgNVBAcMB0hvdXN0b24xGDAW -BgNVBAoMD1BldGFicmlkZ2UsIExMQzEXMBUGA1UEAwwOcGV0YWJyaWRnZS5jb20x -IDAeBgkqhkiG9w0BCQEWEWhpQHBldGFicmlkZ2UuY29tMCAXDTI0MDUyOTE4MTQy -MVoYDzIxMjQwNTA1MTgxNDIxWjCBgTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlRY -MRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9QZXRhYnJpZGdlLCBMTEMxFzAV -BgNVBAMMDnBldGFicmlkZ2UuY29tMSAwHgYJKoZIhvcNAQkBFhFoaUBwZXRhYnJp -ZGdlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMlKSnjXP0Ll -3xdDeHDTtIQX5iE/S0pXy93rvNhHUUgpypPBxXtThmQiXoS12cHHUoHcGeyxiOSO -GUf3kQiWRyLLd/v8ARWBX3gN1arpgC8+8DRVTntHuaLGpHFklfWQuOhJ2NciCr3F -qcPNKNpWd+75s5e6lK5Pt0kOjiXB8Bz60y564zyB2weFQvTlKa/frF5tEIw+U3kd -mrzRNmRlqBpPisEjjwmB6idRjN41XkbcO2p+GmfXgY/3eJASQzPaje6p5Y9CgdwQ -ybOFU4sFAq7XCpe9zBWjm0SRVcv2g2OYvljTa/nWM6lDfaQYQoeXGAvMPzV6gPx8 -qeUtTy/khwkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAkHZwsfZvMVRjC6PbpfY/ -tp3vWD1RoeWkyEPUGuGPCAoQuaAraBNFEW99y5Z7RUkzYmFlfV2O7dsEPmk3h937 -4+jz1oL9gFzAm+r0erkE8Al7RKgrK+bKWeSpMyRrtBdRDODV78a4x+iM0GFJ/Oyk -zDtZoXWhSSaGlaiSYqJAzyu3x78JGc9al/qukU/R/dpFG6H3WVzS8S2Ma+wHqMTE -jL9fs6SnkXuzVr0cXZZPNsZRfZV1CnG8KRmDdX4eRI0HWMzVe5Mkiv+DcfNL0y1o -0jWnmeo08hOo7bXFMLLq8AnD6ylRELvgRZBcZY6wXecuiwiA7cE5pzAKJb1Kag68 -4A== ------END CERTIFICATE----- ------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/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