Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/NetMQ.Tests/CurveTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
Expand Down Expand Up @@ -40,6 +41,60 @@ public void CurveTest()


}

[Fact]
public void CurveAllowedClientsTest_AllowedClientCanConnect()
{
var serverPair = new NetMQCertificate();
using var server = new DealerSocket();
server.Options.CurveServer = true;
server.Options.CurveCertificate = serverPair;

var allowedClientPair = new NetMQCertificate();
// Only allow this specific client
server.Options.CurveAllowedClients.Add(allowedClientPair.PublicKey);

int port = server.BindRandomPort("tcp://127.0.0.1");

using var client = new DealerSocket();
client.Options.CurveServerKey = serverPair.PublicKey;
client.Options.CurveCertificate = allowedClientPair;
client.Connect($"tcp://127.0.0.1:{port}");

client.SendFrame("Hello");
var hello = server.ReceiveFrameString();
Assert.Equal("Hello", hello);
}

[Fact]
public void CurveAllowedClientsTest_BlockedClientCannotConnect()
{
var serverPair = new NetMQCertificate();
using var server = new DealerSocket();
server.Options.CurveServer = true;
server.Options.CurveCertificate = serverPair;

var allowedClientPair = new NetMQCertificate();
// Only allow this specific client
server.Options.CurveAllowedClients.Add(allowedClientPair.PublicKey);

int port = server.BindRandomPort("tcp://127.0.0.1");

// Connect with a different (not allowed) client
var blockedClientPair = new NetMQCertificate();
using var blockedClient = new DealerSocket();
blockedClient.Options.CurveServerKey = serverPair.PublicKey;
blockedClient.Options.CurveCertificate = blockedClientPair;
blockedClient.Connect($"tcp://127.0.0.1:{port}");

// The send may or may not succeed at the transport level (the handshake failure
// happens asynchronously), but the server must never deliver the message.
blockedClient.TrySendFrame(TimeSpan.FromMilliseconds(100), "ShouldBeBlocked");

// Server should not receive anything from the blocked client
var received = server.TryReceiveFrameString(TimeSpan.FromMilliseconds(500), out var message);
Assert.False(received, "Blocked client should not have been able to send a message");
}

#if NETFRAMEWORK
[Fact]
Expand Down
5 changes: 5 additions & 0 deletions src/NetMQ/Core/Mechanisms/CurveServerMechanism.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,11 @@ PushMsgResult ProcessInitiate(ref Msg msg)
if (!SpanUtility.Equals(vouchPlaintext.Slice(0, 32), m_cnClientKey))
return PushMsgResult.Error;

// Verify client long-term public key is in the allowed clients list (if configured).
// The byte[] is only allocated when the whitelist is non-empty.
if (Options.CurveAllowedClients.Count > 0 && !Options.CurveAllowedClients.Contains(clientKey.ToArray()))
return PushMsgResult.Error;

// Create the session box
m_box = new Curve25519XSalsa20Poly1305(m_cnSecretKey, m_cnClientKey);

Expand Down
8 changes: 8 additions & 0 deletions src/NetMQ/Core/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ You should have received a copy of the GNU Lesser General Public License
*/

using System;
using System.Collections.Generic;
using System.Text;
using NetMQ.Core.Utils;

namespace NetMQ.Core
{
Expand Down Expand Up @@ -302,6 +304,12 @@ public byte IdentitySize {
/// </summary>
public byte[] CurveServerKey { get; set; }

/// <summary>
/// The set of allowed client long-term public keys for a CURVE server socket.
/// When non-empty, only clients whose long-term public key is present in this set will be allowed to connect.
/// </summary>
public HashSet<byte[]> CurveAllowedClients { get; } = new HashSet<byte[]>(new ByteArrayEqualityComparer());

/// <summary>
/// If remote peer receives a PING message and doesn't receive another
/// message within the ttl value, it should close the connection
Expand Down
6 changes: 6 additions & 0 deletions src/NetMQ/Core/SocketBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,12 @@ public int GetSocketOption(ZmqSocketOption option)
}
}

/// <summary>
/// Gets the set of allowed client long-term public keys for a CURVE server socket.
/// When non-empty, only clients whose long-term public key is present in this set will be allowed to connect.
/// </summary>
internal System.Collections.Generic.HashSet<byte[]> CurveAllowedClients => m_options.CurveAllowedClients;

/// <summary>
/// Join the dish socket to a group
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions src/NetMQ/SocketOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

using NetMQ.Core;
Expand Down Expand Up @@ -475,6 +476,17 @@ public NetMQCertificate CurveServerCertificate
{
set => m_socket.SetSocketOption(ZmqSocketOption.CurveServerKey, value.PublicKey);
}

/// <summary>
/// Gets the set of allowed client public keys for a CURVE server socket.
/// When non-empty, only clients whose long-term public key is present in this set will be allowed to connect.
/// Add public keys to restrict which clients can connect:
/// <code>
/// server.Options.CurveAllowedClients.Add(clientCertificate.PublicKey);
/// </code>
/// </summary>
public HashSet<byte[]> CurveAllowedClients =>
m_socket.SocketHandle.CurveAllowedClients;

/// <summary>
/// If remote peer receives a PING message and doesn't receive another
Expand Down
12 changes: 12 additions & 0 deletions src/NetMQ/ThreadSafeSocketOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using NetMQ.Core;

Expand Down Expand Up @@ -368,6 +369,17 @@ public NetMQCertificate CurveServerCertificate
{
set => SetSocketOption(ZmqSocketOption.CurveServerKey, value.PublicKey);
}

/// <summary>
/// Gets the set of allowed client public keys for a CURVE server socket.
/// When non-empty, only clients whose long-term public key is present in this set will be allowed to connect.
/// Add public keys to restrict which clients can connect:
/// <code>
/// server.Options.CurveAllowedClients.Add(clientCertificate.PublicKey);
/// </code>
/// </summary>
public HashSet<byte[]> CurveAllowedClients =>
m_socket.CurveAllowedClients;

/// <summary>
/// If remote peer receives a PING message and doesn't receive another
Expand Down
Loading