Coverage Report

Created: 2025-07-04 06:47

/src/libzmq/src/curve_server.cpp
Line
Count
Source (jump to first uncovered line)
1
/* SPDX-License-Identifier: MPL-2.0 */
2
3
#include "precompiled.hpp"
4
#include "macros.hpp"
5
6
#ifdef ZMQ_HAVE_CURVE
7
8
#include "msg.hpp"
9
#include "session_base.hpp"
10
#include "err.hpp"
11
#include "curve_server.hpp"
12
#include "wire.hpp"
13
#include "secure_allocator.hpp"
14
15
zmq::curve_server_t::curve_server_t (session_base_t *session_,
16
                                     const std::string &peer_address_,
17
                                     const options_t &options_,
18
                                     const bool downgrade_sub_) :
19
0
    mechanism_base_t (session_, options_),
20
0
    zap_client_common_handshake_t (
21
0
      session_, peer_address_, options_, sending_ready),
22
0
    curve_mechanism_base_t (session_,
23
0
                            options_,
24
0
                            "CurveZMQMESSAGES",
25
0
                            "CurveZMQMESSAGEC",
26
0
                            downgrade_sub_)
27
0
{
28
0
    int rc;
29
    //  Fetch our secret key from socket options
30
0
    memcpy (_secret_key, options_.curve_secret_key, crypto_box_SECRETKEYBYTES);
31
32
    //  Generate short-term key pair
33
0
    memset (_cn_secret, 0, crypto_box_SECRETKEYBYTES);
34
0
    memset (_cn_public, 0, crypto_box_PUBLICKEYBYTES);
35
0
    rc = crypto_box_keypair (_cn_public, _cn_secret);
36
0
    zmq_assert (rc == 0);
37
0
}
Unexecuted instantiation: zmq::curve_server_t::curve_server_t(zmq::session_base_t*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, zmq::options_t const&, bool)
Unexecuted instantiation: zmq::curve_server_t::curve_server_t(zmq::session_base_t*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, zmq::options_t const&, bool)
38
39
zmq::curve_server_t::~curve_server_t ()
40
0
{
41
0
}
42
43
int zmq::curve_server_t::next_handshake_command (msg_t *msg_)
44
0
{
45
0
    int rc = 0;
46
47
0
    switch (state) {
48
0
        case sending_welcome:
49
0
            rc = produce_welcome (msg_);
50
0
            if (rc == 0)
51
0
                state = waiting_for_initiate;
52
0
            break;
53
0
        case sending_ready:
54
0
            rc = produce_ready (msg_);
55
0
            if (rc == 0)
56
0
                state = ready;
57
0
            break;
58
0
        case sending_error:
59
0
            rc = produce_error (msg_);
60
0
            if (rc == 0)
61
0
                state = error_sent;
62
0
            break;
63
0
        default:
64
0
            errno = EAGAIN;
65
0
            rc = -1;
66
0
            break;
67
0
    }
68
0
    return rc;
69
0
}
70
71
int zmq::curve_server_t::process_handshake_command (msg_t *msg_)
72
0
{
73
0
    int rc = 0;
74
75
0
    switch (state) {
76
0
        case waiting_for_hello:
77
0
            rc = process_hello (msg_);
78
0
            break;
79
0
        case waiting_for_initiate:
80
0
            rc = process_initiate (msg_);
81
0
            break;
82
0
        default:
83
            // TODO I think this is not a case reachable with a misbehaving
84
            // client. It is not an "invalid handshake command", but would be
85
            // trying to process a handshake command in an invalid state,
86
            // which is purely under control of this peer.
87
            // Therefore, it should be changed to zmq_assert (false);
88
89
            // CURVE I: invalid handshake command
90
0
            session->get_socket ()->event_handshake_failed_protocol (
91
0
              session->get_endpoint (), ZMQ_PROTOCOL_ERROR_ZMTP_UNSPECIFIED);
92
0
            errno = EPROTO;
93
0
            rc = -1;
94
0
            break;
95
0
    }
96
0
    if (rc == 0) {
97
0
        rc = msg_->close ();
98
0
        errno_assert (rc == 0);
99
0
        rc = msg_->init ();
100
0
        errno_assert (rc == 0);
101
0
    }
102
0
    return rc;
103
0
}
104
105
int zmq::curve_server_t::encode (msg_t *msg_)
106
0
{
107
0
    zmq_assert (state == ready);
108
0
    return curve_mechanism_base_t::encode (msg_);
109
0
}
110
111
int zmq::curve_server_t::decode (msg_t *msg_)
112
0
{
113
0
    zmq_assert (state == ready);
114
0
    return curve_mechanism_base_t::decode (msg_);
115
0
}
116
117
int zmq::curve_server_t::process_hello (msg_t *msg_)
118
0
{
119
0
    int rc = check_basic_command_structure (msg_);
120
0
    if (rc == -1)
121
0
        return -1;
122
123
0
    const size_t size = msg_->size ();
124
0
    const uint8_t *const hello = static_cast<uint8_t *> (msg_->data ());
125
126
0
    if (size < 6 || memcmp (hello, "\x05HELLO", 6)) {
127
0
        session->get_socket ()->event_handshake_failed_protocol (
128
0
          session->get_endpoint (), ZMQ_PROTOCOL_ERROR_ZMTP_UNEXPECTED_COMMAND);
129
0
        errno = EPROTO;
130
0
        return -1;
131
0
    }
132
133
0
    if (size != 200) {
134
0
        session->get_socket ()->event_handshake_failed_protocol (
135
0
          session->get_endpoint (),
136
0
          ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_HELLO);
137
0
        errno = EPROTO;
138
0
        return -1;
139
0
    }
140
141
0
    const uint8_t major = hello[6];
142
0
    const uint8_t minor = hello[7];
143
144
0
    if (major != 1 || minor != 0) {
145
        // CURVE I: client HELLO has unknown version number
146
0
        session->get_socket ()->event_handshake_failed_protocol (
147
0
          session->get_endpoint (),
148
0
          ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_HELLO);
149
0
        errno = EPROTO;
150
0
        return -1;
151
0
    }
152
153
    //  Save client's short-term public key (C')
154
0
    memcpy (_cn_client, hello + 80, 32);
155
156
0
    uint8_t hello_nonce[crypto_box_NONCEBYTES];
157
0
    std::vector<uint8_t, secure_allocator_t<uint8_t> > hello_plaintext (
158
0
      crypto_box_ZEROBYTES + 64);
159
0
    uint8_t hello_box[crypto_box_BOXZEROBYTES + 80];
160
161
0
    memcpy (hello_nonce, "CurveZMQHELLO---", 16);
162
0
    memcpy (hello_nonce + 16, hello + 112, 8);
163
0
    set_peer_nonce (get_uint64 (hello + 112));
164
165
0
    memset (hello_box, 0, crypto_box_BOXZEROBYTES);
166
0
    memcpy (hello_box + crypto_box_BOXZEROBYTES, hello + 120, 80);
167
168
    //  Open Box [64 * %x0](C'->S)
169
0
    rc = crypto_box_open (&hello_plaintext[0], hello_box, sizeof hello_box,
170
0
                          hello_nonce, _cn_client, _secret_key);
171
0
    if (rc != 0) {
172
        // CURVE I: cannot open client HELLO -- wrong server key?
173
0
        session->get_socket ()->event_handshake_failed_protocol (
174
0
          session->get_endpoint (), ZMQ_PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC);
175
0
        errno = EPROTO;
176
0
        return -1;
177
0
    }
178
179
0
    state = sending_welcome;
180
0
    return rc;
181
0
}
182
183
int zmq::curve_server_t::produce_welcome (msg_t *msg_)
184
0
{
185
0
    uint8_t cookie_nonce[crypto_secretbox_NONCEBYTES];
186
0
    std::vector<uint8_t, secure_allocator_t<uint8_t> > cookie_plaintext (
187
0
      crypto_secretbox_ZEROBYTES + 64);
188
0
    uint8_t cookie_ciphertext[crypto_secretbox_BOXZEROBYTES + 80];
189
190
    //  Create full nonce for encryption
191
    //  8-byte prefix plus 16-byte random nonce
192
0
    memset (cookie_nonce, 0, crypto_secretbox_NONCEBYTES);
193
0
    memcpy (cookie_nonce, "COOKIE--", 8);
194
0
    randombytes (cookie_nonce + 8, 16);
195
196
    //  Generate cookie = Box [C' + s'](t)
197
0
    std::fill (cookie_plaintext.begin (),
198
0
               cookie_plaintext.begin () + crypto_secretbox_ZEROBYTES, 0);
199
0
    memcpy (&cookie_plaintext[crypto_secretbox_ZEROBYTES], _cn_client, 32);
200
0
    memcpy (&cookie_plaintext[crypto_secretbox_ZEROBYTES + 32], _cn_secret, 32);
201
202
    //  Generate fresh cookie key
203
0
    memset (_cookie_key, 0, crypto_secretbox_KEYBYTES);
204
0
    randombytes (_cookie_key, crypto_secretbox_KEYBYTES);
205
206
    //  Encrypt using symmetric cookie key
207
0
    int rc =
208
0
      crypto_secretbox (cookie_ciphertext, &cookie_plaintext[0],
209
0
                        cookie_plaintext.size (), cookie_nonce, _cookie_key);
210
0
    zmq_assert (rc == 0);
211
212
0
    uint8_t welcome_nonce[crypto_box_NONCEBYTES];
213
0
    std::vector<uint8_t, secure_allocator_t<uint8_t> > welcome_plaintext (
214
0
      crypto_box_ZEROBYTES + 128);
215
0
    uint8_t welcome_ciphertext[crypto_box_BOXZEROBYTES + 144];
216
217
    //  Create full nonce for encryption
218
    //  8-byte prefix plus 16-byte random nonce
219
0
    memset (welcome_nonce, 0, crypto_box_NONCEBYTES);
220
0
    memcpy (welcome_nonce, "WELCOME-", 8);
221
0
    randombytes (welcome_nonce + 8, crypto_box_NONCEBYTES - 8);
222
223
    //  Create 144-byte Box [S' + cookie](S->C')
224
0
    std::fill (welcome_plaintext.begin (),
225
0
               welcome_plaintext.begin () + crypto_box_ZEROBYTES, 0);
226
0
    memcpy (&welcome_plaintext[crypto_box_ZEROBYTES], _cn_public, 32);
227
0
    memcpy (&welcome_plaintext[crypto_box_ZEROBYTES + 32], cookie_nonce + 8,
228
0
            16);
229
0
    memcpy (&welcome_plaintext[crypto_box_ZEROBYTES + 48],
230
0
            cookie_ciphertext + crypto_secretbox_BOXZEROBYTES, 80);
231
232
0
    rc = crypto_box (welcome_ciphertext, &welcome_plaintext[0],
233
0
                     welcome_plaintext.size (), welcome_nonce, _cn_client,
234
0
                     _secret_key);
235
236
    //  TODO I think we should change this back to zmq_assert (rc == 0);
237
    //  as it was before https://github.com/zeromq/libzmq/pull/1832
238
    //  The reason given there was that secret_key might be 0ed.
239
    //  But if it were, we would never get this far, since we could
240
    //  not have opened the client's hello box with a 0ed key.
241
242
0
    if (rc == -1)
243
0
        return -1;
244
245
0
    rc = msg_->init_size (168);
246
0
    errno_assert (rc == 0);
247
248
0
    uint8_t *const welcome = static_cast<uint8_t *> (msg_->data ());
249
0
    memcpy (welcome, "\x07WELCOME", 8);
250
0
    memcpy (welcome + 8, welcome_nonce + 8, 16);
251
0
    memcpy (welcome + 24, welcome_ciphertext + crypto_box_BOXZEROBYTES, 144);
252
253
0
    return 0;
254
0
}
255
256
int zmq::curve_server_t::process_initiate (msg_t *msg_)
257
0
{
258
0
    int rc = check_basic_command_structure (msg_);
259
0
    if (rc == -1)
260
0
        return -1;
261
262
0
    const size_t size = msg_->size ();
263
0
    const uint8_t *initiate = static_cast<uint8_t *> (msg_->data ());
264
265
0
    if (size < 9 || memcmp (initiate, "\x08INITIATE", 9)) {
266
0
        session->get_socket ()->event_handshake_failed_protocol (
267
0
          session->get_endpoint (), ZMQ_PROTOCOL_ERROR_ZMTP_UNEXPECTED_COMMAND);
268
0
        errno = EPROTO;
269
0
        return -1;
270
0
    }
271
272
0
    if (size < 257) {
273
0
        session->get_socket ()->event_handshake_failed_protocol (
274
0
          session->get_endpoint (),
275
0
          ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_INITIATE);
276
0
        errno = EPROTO;
277
0
        return -1;
278
0
    }
279
280
0
    uint8_t cookie_nonce[crypto_secretbox_NONCEBYTES];
281
0
    uint8_t cookie_plaintext[crypto_secretbox_ZEROBYTES + 64];
282
0
    uint8_t cookie_box[crypto_secretbox_BOXZEROBYTES + 80];
283
284
    //  Open Box [C' + s'](t)
285
0
    memset (cookie_box, 0, crypto_secretbox_BOXZEROBYTES);
286
0
    memcpy (cookie_box + crypto_secretbox_BOXZEROBYTES, initiate + 25, 80);
287
288
0
    memcpy (cookie_nonce, "COOKIE--", 8);
289
0
    memcpy (cookie_nonce + 8, initiate + 9, 16);
290
291
0
    rc = crypto_secretbox_open (cookie_plaintext, cookie_box, sizeof cookie_box,
292
0
                                cookie_nonce, _cookie_key);
293
0
    if (rc != 0) {
294
        // CURVE I: cannot open client INITIATE cookie
295
0
        session->get_socket ()->event_handshake_failed_protocol (
296
0
          session->get_endpoint (), ZMQ_PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC);
297
0
        errno = EPROTO;
298
0
        return -1;
299
0
    }
300
301
    //  Check cookie plain text is as expected [C' + s']
302
0
    if (memcmp (cookie_plaintext + crypto_secretbox_ZEROBYTES, _cn_client, 32)
303
0
        || memcmp (cookie_plaintext + crypto_secretbox_ZEROBYTES + 32,
304
0
                   _cn_secret, 32)) {
305
        // TODO this case is very hard to test, as it would require a modified
306
        //  client that knows the server's secret temporary cookie key
307
308
        // CURVE I: client INITIATE cookie is not valid
309
0
        session->get_socket ()->event_handshake_failed_protocol (
310
0
          session->get_endpoint (), ZMQ_PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC);
311
0
        errno = EPROTO;
312
0
        return -1;
313
0
    }
314
315
0
    const size_t clen = (size - 113) + crypto_box_BOXZEROBYTES;
316
317
0
    uint8_t initiate_nonce[crypto_box_NONCEBYTES];
318
0
    std::vector<uint8_t, secure_allocator_t<uint8_t> > initiate_plaintext (
319
0
      crypto_box_ZEROBYTES + clen);
320
0
    std::vector<uint8_t> initiate_box (crypto_box_BOXZEROBYTES + clen);
321
322
    //  Open Box [C + vouch + metadata](C'->S')
323
0
    std::fill (initiate_box.begin (),
324
0
               initiate_box.begin () + crypto_box_BOXZEROBYTES, 0);
325
0
    memcpy (&initiate_box[crypto_box_BOXZEROBYTES], initiate + 113,
326
0
            clen - crypto_box_BOXZEROBYTES);
327
328
0
    memcpy (initiate_nonce, "CurveZMQINITIATE", 16);
329
0
    memcpy (initiate_nonce + 16, initiate + 105, 8);
330
0
    set_peer_nonce (get_uint64 (initiate + 105));
331
332
0
    const uint8_t *client_key = &initiate_plaintext[crypto_box_ZEROBYTES];
333
334
0
    rc = crypto_box_open (&initiate_plaintext[0], &initiate_box[0], clen,
335
0
                          initiate_nonce, _cn_client, _cn_secret);
336
0
    if (rc != 0) {
337
        // CURVE I: cannot open client INITIATE
338
0
        session->get_socket ()->event_handshake_failed_protocol (
339
0
          session->get_endpoint (), ZMQ_PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC);
340
0
        errno = EPROTO;
341
0
        return -1;
342
0
    }
343
344
0
    uint8_t vouch_nonce[crypto_box_NONCEBYTES];
345
0
    std::vector<uint8_t, secure_allocator_t<uint8_t> > vouch_plaintext (
346
0
      crypto_box_ZEROBYTES + 64);
347
0
    uint8_t vouch_box[crypto_box_BOXZEROBYTES + 80];
348
349
    //  Open Box Box [C',S](C->S') and check contents
350
0
    memset (vouch_box, 0, crypto_box_BOXZEROBYTES);
351
0
    memcpy (vouch_box + crypto_box_BOXZEROBYTES,
352
0
            &initiate_plaintext[crypto_box_ZEROBYTES + 48], 80);
353
354
0
    memset (vouch_nonce, 0, crypto_box_NONCEBYTES);
355
0
    memcpy (vouch_nonce, "VOUCH---", 8);
356
0
    memcpy (vouch_nonce + 8, &initiate_plaintext[crypto_box_ZEROBYTES + 32],
357
0
            16);
358
359
0
    rc = crypto_box_open (&vouch_plaintext[0], vouch_box, sizeof vouch_box,
360
0
                          vouch_nonce, client_key, _cn_secret);
361
0
    if (rc != 0) {
362
        // CURVE I: cannot open client INITIATE vouch
363
0
        session->get_socket ()->event_handshake_failed_protocol (
364
0
          session->get_endpoint (), ZMQ_PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC);
365
0
        errno = EPROTO;
366
0
        return -1;
367
0
    }
368
369
    //  What we decrypted must be the client's short-term public key
370
0
    if (memcmp (&vouch_plaintext[crypto_box_ZEROBYTES], _cn_client, 32)) {
371
        // TODO this case is very hard to test, as it would require a modified
372
        //  client that knows the server's secret short-term key
373
374
        // CURVE I: invalid handshake from client (public key)
375
0
        session->get_socket ()->event_handshake_failed_protocol (
376
0
          session->get_endpoint (), ZMQ_PROTOCOL_ERROR_ZMTP_KEY_EXCHANGE);
377
0
        errno = EPROTO;
378
0
        return -1;
379
0
    }
380
381
    //  Precompute connection secret from client key
382
0
    rc = crypto_box_beforenm (get_writable_precom_buffer (), _cn_client,
383
0
                              _cn_secret);
384
0
    zmq_assert (rc == 0);
385
386
    //  Given this is a backward-incompatible change, it's behind a socket
387
    //  option disabled by default.
388
0
    if (zap_required () || !options.zap_enforce_domain) {
389
        //  Use ZAP protocol (RFC 27) to authenticate the user.
390
0
        rc = session->zap_connect ();
391
0
        if (rc == 0) {
392
0
            send_zap_request (client_key);
393
0
            state = waiting_for_zap_reply;
394
395
            //  TODO actually, it is quite unlikely that we can read the ZAP
396
            //  reply already, but removing this has some strange side-effect
397
            //  (probably because the pipe's in_active flag is true until a read
398
            //  is attempted)
399
0
            if (-1 == receive_and_process_zap_reply ())
400
0
                return -1;
401
0
        } else if (!options.zap_enforce_domain) {
402
            //  This supports the Stonehouse pattern (encryption without
403
            //  authentication) in legacy mode (domain set but no handler).
404
0
            state = sending_ready;
405
0
        } else {
406
0
            session->get_socket ()->event_handshake_failed_no_detail (
407
0
              session->get_endpoint (), EFAULT);
408
0
            return -1;
409
0
        }
410
0
    } else {
411
        //  This supports the Stonehouse pattern (encryption without authentication).
412
0
        state = sending_ready;
413
0
    }
414
415
0
    return parse_metadata (&initiate_plaintext[crypto_box_ZEROBYTES + 128],
416
0
                           clen - crypto_box_ZEROBYTES - 128);
417
0
}
418
419
int zmq::curve_server_t::produce_ready (msg_t *msg_)
420
0
{
421
0
    const size_t metadata_length = basic_properties_len ();
422
0
    uint8_t ready_nonce[crypto_box_NONCEBYTES];
423
424
0
    std::vector<uint8_t, secure_allocator_t<uint8_t> > ready_plaintext (
425
0
      crypto_box_ZEROBYTES + metadata_length);
426
427
    //  Create Box [metadata](S'->C')
428
0
    std::fill (ready_plaintext.begin (),
429
0
               ready_plaintext.begin () + crypto_box_ZEROBYTES, 0);
430
0
    uint8_t *ptr = &ready_plaintext[crypto_box_ZEROBYTES];
431
432
0
    ptr += add_basic_properties (ptr, metadata_length);
433
0
    const size_t mlen = ptr - &ready_plaintext[0];
434
435
0
    memcpy (ready_nonce, "CurveZMQREADY---", 16);
436
0
    put_uint64 (ready_nonce + 16, get_and_inc_nonce ());
437
438
0
    std::vector<uint8_t> ready_box (crypto_box_BOXZEROBYTES + 16
439
0
                                    + metadata_length);
440
441
0
    int rc = crypto_box_afternm (&ready_box[0], &ready_plaintext[0], mlen,
442
0
                                 ready_nonce, get_precom_buffer ());
443
0
    zmq_assert (rc == 0);
444
445
0
    rc = msg_->init_size (14 + mlen - crypto_box_BOXZEROBYTES);
446
0
    errno_assert (rc == 0);
447
448
0
    uint8_t *ready = static_cast<uint8_t *> (msg_->data ());
449
450
0
    memcpy (ready, "\x05READY", 6);
451
    //  Short nonce, prefixed by "CurveZMQREADY---"
452
0
    memcpy (ready + 6, ready_nonce + 16, 8);
453
    //  Box [metadata](S'->C')
454
0
    memcpy (ready + 14, &ready_box[crypto_box_BOXZEROBYTES],
455
0
            mlen - crypto_box_BOXZEROBYTES);
456
457
0
    return 0;
458
0
}
459
460
int zmq::curve_server_t::produce_error (msg_t *msg_) const
461
0
{
462
0
    const size_t expected_status_code_length = 3;
463
0
    zmq_assert (status_code.length () == 3);
464
0
    const int rc = msg_->init_size (6 + 1 + expected_status_code_length);
465
0
    zmq_assert (rc == 0);
466
0
    char *msg_data = static_cast<char *> (msg_->data ());
467
0
    memcpy (msg_data, "\5ERROR", 6);
468
0
    msg_data[6] = expected_status_code_length;
469
0
    memcpy (msg_data + 7, status_code.c_str (), expected_status_code_length);
470
0
    return 0;
471
0
}
472
473
void zmq::curve_server_t::send_zap_request (const uint8_t *key_)
474
0
{
475
0
    zap_client_t::send_zap_request ("CURVE", 5, key_,
476
0
                                    crypto_box_PUBLICKEYBYTES);
477
0
}
478
479
#endif