zeromq / jeromq

JeroMQ is a pure Java implementation of the ZeroMQ messaging library, offering high-performance asynchronous messaging for distributed or concurrent applications.
https://zeromq.org
Mozilla Public License 2.0
2.36k stars 485 forks source link

Can't get ZAuth to authorise Curve connection with unconventional REP, REQ socket pair. #872

Open DavidRobb opened 3 years ago

DavidRobb commented 3 years ago

I'm trying to use ZAuth to authorise a request reply socket pair where the curveServer is on the REQ socket that 'binds' and waits for the REP socket to connect.

It half works in that the REP socket is authorised and receives a request, then sends a reply. However, the REQ socket never sees this reply.

Setup

REQ CurveServer Config

    ZMQ.Socket reqSocket = context.createSocket(SocketType.REQ);
    reqSocket.setLinger(0);
    reqSocket.setZapDomain("global".getBytes());
    reqSocket.setCurveSecretKey(serverCert.getSecretKey());
    reqSocket.setCurveServer(true);
    reqSocket.bind("tcp://*:*");

    reqSocket.send("HELLO");

    byte[] ba = reqSocket.recv();  // Curve server hangs here

REP CurveClient Config

      ZMQ.Socket repSocket = context.createSocket(SocketType.REP);
      repSocket.curveServerKey = ZMQ.Curve.z85Decode(serverPub)
      repSocket.curvePublicKey = ZMQ.Curve.z85Decode(client2Pub)
      repSocket.curveSecretKey = ZMQ.Curve.z85Decode(client2Sec)

      repSocket.connect(serverUrl)

      byte[] ba = repSocket.recv();

      repSocket.send("WORLD");

Even with client2Sec key correctly set in my key store location, the CurveServer hangs waiting at the line indicated. Rep Client appears to go through the motions of receiving the message and sending the reply.

However, if I have auth configured as ALLOW_ANY it works fine.

Is there something a bit odd about this setup? Usually the CurveServer would be the 'Server' and reply to Client requests with a REP socket. But for this dynamically setup connection I want the CurveClient to be the 'Server' and respond to CurveServer requests.

If I swap the REP and REQ socket types over and send the initial Request from the CurveClient then it also works fine. Hence, I think I have my keys and ZAuth all configured correctly.

fredoboulo commented 3 years ago

Hi, I apologize for the delay.

Can you share a compilable code pinpointing your problem?

When I recycle the ZAuthTest#testCurveSuccessful, by binding a REQ and connecting a REP, it works.

    public void testBindREQConnectREP() throws IOException
    {
        try (ZContext ctx = new ZContext();
             ZAuth auth = new ZAuth(ctx, new ZCertStore.Hasher());
             ZMQ.Socket server = ctx.createSocket(SocketType.REQ);
             ZMQ.Socket client = ctx.createSocket(SocketType.REP)) {
            //  Start an authentication engine for this context. This engine
            //  allows or denies incoming connections (talking to the libzmq
            //  core over a protocol called ZAP).

            //  Get some indication of what the authenticator is deciding
            auth.setVerbose(VERBOSE_MODE);
            // auth send the replies
            auth.replies(true);

            //  Tell authenticator to use the certificate store in .curve
            auth.configureCurve(certificateFolder);

            //  We'll generate a new client certificate and save the public part
            //  in the certificate store (in practice this would be done by hand
            //  or some out-of-band process).
            ZCert clientCert = new ZCert();
            clientCert.setMeta("name", "Client test certificate");
            clientCert.setMeta("meta1/meta2/meta3", "Third level of meta");
            // wait a second before overwriting a cert, otherwise the certstore won't see that the file actually changed and will deny
            // if creating new files, this is not needed
            //            ZMQ.sleep(1);
            clientCert.savePublic(certificateFolder + "/testcert.pub");

            ZCert serverCert = new ZCert();

            //  Create and bind server socket
            server.setZapDomain("global".getBytes());
            server.setCurveServer(true);
            server.setCurvePublicKey(serverCert.getPublicKey());
            server.setCurveSecretKey(serverCert.getSecretKey());
            boolean rc = server.bind("tcp://localhost:*");
            assertThat(rc, is(true));

            //  Create and connect client socket
            client.setCurvePublicKey(clientCert.getPublicKey());
            client.setCurveSecretKey(clientCert.getSecretKey());
            client.setCurveServerKey(serverCert.getPublicKey());
            rc = client.connect(server.getLastEndpoint());
            assertThat(rc, is(true));

            //  By default PUSH sockets block if there's no peer
            rc = server.setSendTimeOut(200);
            assertThat(rc, is(true));
            //  Send a single message from server to client
            server.send("Hello");

            ZAuth.ZapReply reply = auth.nextReply(true);
            assertThat(reply.statusCode, is(200));
            assertThat(reply.userId, is(clientCert.getPublicKeyAsZ85()));

            String message = client.recvStr();
            assertThat(message, is("Hello"));
        }
    }
DavidRobb commented 3 years ago

Hi, Thanks for looking into this. Maybe you can see if I have done anything stupid:

I have the keys hard coded into the source but also present in the Appdata .curve directory. Doesn't appear to be a directory problem as ZAuth reports allowed:

> Task :run
ZAuth: Enabled replies
ZAuth: Using C:\Users\David\AppData\Roaming\ZMQTest\.curve as certificates directory
Bind...
Send Hello...
Recv world...
ZAuth: Allowed (CURVE) client_key=2c*Um{$$YK7Tbd2)W5nq/{o=bXLM6DkS=!H0/%C=
ZAuth: Allowed (CURVE) client_key=2c*Um{$$YK7Tbd2)W5nq/{o=bXLM6DkS=!H0/%C=

But Server does not report 'Done' unless I set CURVE_ALLOW_ANY

Server Main is

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package ReqServer;

import java.io.IOException;
import org.zeromq.SocketType;
import org.zeromq.ZAuth;
import org.zeromq.ZCertStore;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;

/**
 *
 * @author David
 */
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws IOException {

        String server_secret = "?y$]][$s+e:GHh#z!CN9ZJKvu)4sZw.y^>tDRLwD";
        String client_secret = "Rg1UHpL(qiw<lh7b{(Im-{1v9w#3m){mM]+hNXYA";
        String client_public = "2c*Um{$$YK7Tbd2)W5nq/{o=bXLM6DkS=!H0/%C=";
        String server_public = "0pITW):T^ip-7ZT+*9ZqL6mY42IF.}wUSKC%nH5R";

        var ctx = new ZContext(1);
        ZAuth auth = new ZAuth(ctx, new ZCertStore.Hasher());
        auth.setVerbose(true);
        auth.replies(true);
//        auth.configureCurve(ZAuth.CURVE_ALLOW_ANY);
        auth.configureCurve("C:\\Users\\David\\AppData\\Roaming\\ZMQTest\\.curve");

        ZMQ.Socket reqSocket = ctx.createSocket(SocketType.REQ);
        reqSocket.setLinger(0);
        reqSocket.setZapDomain("global".getBytes());
        reqSocket.setCurveSecretKey(ZMQ.Curve.z85Decode(server_secret));
        reqSocket.setCurveServer(true);

        System.out.println("Bind...");
        reqSocket.bind("tcp://*:5432");

        System.out.println("Send Hello...");

        reqSocket.send("HELLO");

        System.out.println("Recv world...");
        byte[] ba = reqSocket.recv();  // Curve server hangs here

        System.out.println("Done...");
    }

}

Client main is

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package RepClient;

import java.io.IOException;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;

/**
 *
 * @author David
 */
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws IOException, InterruptedException {

        String client_secret = "Rg1UHpL(qiw<lh7b{(Im-{1v9w#3m){mM]+hNXYA";
        String client_public = "2c*Um{$$YK7Tbd2)W5nq/{o=bXLM6DkS=!H0/%C=";
        String server_public = "0pITW):T^ip-7ZT+*9ZqL6mY42IF.}wUSKC%nH5R";

        var context = new ZContext(1);
        ZMQ.Socket repSocket = context.createSocket(SocketType.REP);
        repSocket.setCurveServerKey(ZMQ.Curve.z85Decode(server_public));
        repSocket.setCurvePublicKey(ZMQ.Curve.z85Decode(client_public));
        repSocket.setCurveSecretKey(ZMQ.Curve.z85Decode(client_secret));

        System.out.println("Connect...");
        repSocket.connect("tcp://localhost:5432");

        System.out.println("Recv...");
        byte[] ba = repSocket.recv();

        System.out.println("Send World...");
        repSocket.send("WORLD");

        System.out.println("Done...");

        Thread.sleep(5000);
    }

}

Zipped gradle project files are:

ReqServerRepClientExample.zip