Skip to content

XX variant

Justin Decker edited this page Jan 17, 2022 · 10 revisions

Key exchange using the XX variant

  • What the client needs to know about the server: nothing
  • What the server needs to know about the client: nothing

This is the most versatile variant, but it requires two round trips. In this variant, the client and the server don't need to share any prior data. However, the peers public keys will be exchanged. Discovered public keys can then be discarded, used for authentication, or reused later with the KK variant.

Example

Client: generate a long-term key pair

hydro_kx_keypair client_static_kp;

hydro_kx_keygen(&client_static_kp);

Server: generate a long-term key pair

hydro_kx_keypair server_static_kp;

hydro_kx_keygen(&server_static_kp);

Client: initiate a key exchange

hydro_kx_state st_client;

hydro_kx_xx_1(&st_client, packet1, NULL);

Server: process the initial request from the client

hydro_kx_state st_server;

if (hydro_kx_xx_2(&st_server, packet2, packet1, NULL, &server_static_kp) != 0) {
  // abort
}

Client: process the server packet and compute the session keys

hydro_kx_session_keypair session_kp;

if (hydro_kx_xx_3(&st_client, &session_kp, packet3, NULL, packet2, NULL,
                  &client_static_kp) != 0) {
  // abort
}
// Done! session_kp.tx is the key for sending data to the server,
// and session_kp.rx is the key for receiving data from the server.

Server: process the client packet and compute the session keys:

hydro_kx_session_keypair session_kp;

if (hydro_kx_xx_4(&st_server, &session_kp, NULL, packet3, NULL) != 0) {
  // abort
}
// Done! session_kp.tx is the key for sending data to the client,
// and session_kp.rx is the key for receiving data from the client.
// The session keys are the same as those computed by the client, but swapped.

Annex: declarations

uint8_t packet1[hydro_kx_XX_PACKET1BYTES];
uint8_t packet2[hydro_kx_XX_PACKET2BYTES];
uint8_t packet3[hydro_kx_XX_PACKET3BYTES];

Purpose

Using this API, a client and a server can securely generate and exchange session keys.

This API is designed for online protocols, and requires two round trips:

  • The initiator (client) sends a key exchange request.
  • The listener (server) receives the request, validates it, and sends a packet to the client.
  • The client validates the packet, compute the session keys, and sends a last packet to the server. The client learns the server public key as well, and can drop the connection if it doesn't match an expected public key.
  • The server use this packet and previous data in order to compute the same session keys. The server learns the client public key as well.

Two sessions keys are eventually computed. The former can be used to encrypt data sent from the client to the server, the later can be used in the other direction.

Usage

Generating static keys

void hydro_kx_keygen(hydro_kx_keypair *static_kp);

The hydro_kx_keygen() function generates a long-term key pair.

kp->pk contains the public key (hydro_kx_PUBLICKEYBYTES bytes) and kp->sk contains the secret key (hydro_kx_SECRETKEYBYTES bytes).

These long-term keys can be reused indefinitely, even though rotating them from time to time is highly recommended in case the secret key ever gets leaked.

First message (client->server)

int hydro_kx_xx_1(hydro_kx_state *state,
    uint8_t                       packet1[hydro_kx_XX_PACKET1BYTES],
    const uint8_t                 psk[hydro_kx_PSKBYTES]);

The hydro_kx_xx_1() function is called by a client initiating a connection. It initializes the local state state, computes an ephemeral key pair, and puts the first packet to send to the server into packet1.

If the the pre-shared secret psk is set, the server can detect a suspicious request after the first packet is received. Without a pre-shared secret, an additional round trip is required.

Second message (server->client)

int hydro_kx_xx_2(hydro_kx_state *state,
    uint8_t                       packet2[hydro_kx_XX_PACKET2BYTES],
    const uint8_t                 packet1[hydro_kx_XX_PACKET1BYTES],
    const uint8_t psk[hydro_kx_PSKBYTES], const hydro_kx_keypair *static_kp);

The hydro_kx_xx_2() function has to be called by the server after a client request packet1 has been received.

It validates the request, computes an ephemeral key pair, and puts the packet to send to the client into packet2.

The function returns 0 on success and -1 is the received packet doesn't appear to be valid.

Third message (client->server)

int hydro_kx_xx_3(hydro_kx_state *state, hydro_kx_session_keypair *kp,
    uint8_t       packet3[hydro_kx_XX_PACKET3BYTES],
    uint8_t       peer_static_pk[hydro_kx_PUBLICKEYBYTES],
    const uint8_t packet2[hydro_kx_XX_PACKET2BYTES],
    const uint8_t psk[hydro_kx_PSKBYTES], const hydro_kx_keypair *static_kp);

The hydro_kx_xx_3() function has to be called by the client after having received the first packet from the server, packet2.

It validates the packet, computes a session key and puts it into kp.

The client can send a dummy static public key if authentication is not required.

kp->tx (hydro_kx_SESSIONKEYBYTES bytes) is a key that the client can use in order to encrypt data sent to the server, and kp->rx (hydro_kx_SESSIONKEYBYTES bytes) is a key that can be used in the opposite direction.

peer_static_pk can be NULL, but if it is not, the function will use it to store a copy of the server's public key. The client can then decide to drop the connection if it doesn't match an expected key.

The function puts the packet to send to the server into packet3.

It returns 0 on success and -1 is the received packet doesn't appear to be valid.

kp contains the final session key at this point. A payload can already be sent by the client without waiting for the server to compute the session keys on its end.

Processing the last packet (from the client, by the server)

int hydro_kx_xx_4(hydro_kx_state *state, hydro_kx_session_keypair *kp,
    uint8_t       peer_static_pk[hydro_kx_PUBLICKEYBYTES],
    const uint8_t packet3[hydro_kx_XX_PACKET3BYTES],
    const uint8_t psk[hydro_kx_PSKBYTES]);

The hydro_kx_xx_4() function has to be called by the server after having received packet3 from the client.

It validates the packet, computes the session key (identical to the one computed by the client) and puts it into kp.

kp->tx (hydro_kx_SESSIONKEYBYTES bytes) is a key that the server can use in order to encrypt data sent to the client, and kp->rx (hydro_kx_SESSIONKEYBYTES bytes) is a key that can be used in the opposite direction.

peer_static_pk can be NULL, but if it is not, the function will use it to store a copy of the client's public key. The server can then decide to drop the connection if it doesn't match an expected key.

The function returns 0 on success and -1 is the received packet doesn't appear to be valid.

Constants

#define hydro_kx_XX_PACKET1BYTES 48
#define hydro_kx_XX_PACKET2BYTES 96
#define hydro_kx_XX_PACKET3BYTES 64