This is a prerequisite patch for the following openssl API optimization patch, which tries to offload openssl ctx init and key expansion work to the initialization stage. Wireguard adds crypto keys via vnet_crypto_key_add (), and whenever it modifies the keys, the underneath openssl crypto engine shoud be informed of the changes to update the openssl ctx. Type: feature Signed-off-by: Lijian Zhang <Lijian.Zhang@arm.com> Change-Id: I3e8f033f3f77eebcecfbd06e8e3bbbfdc95a50e2
769 lines
22 KiB
C
769 lines
22 KiB
C
/*
|
|
* Copyright (c) 2020 Doc.ai and/or its affiliates.
|
|
* Copyright (c) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>.
|
|
* Copyright (c) 2019-2020 Matt Dunwoodie <ncon@noconroy.net>.
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <openssl/hmac.h>
|
|
#include <wireguard/wireguard.h>
|
|
#include <wireguard/wireguard_chachapoly.h>
|
|
|
|
/* This implements Noise_IKpsk2:
|
|
*
|
|
* <- s
|
|
* ******
|
|
* -> e, es, s, ss, {t}
|
|
* <- e, ee, se, psk, {}
|
|
*/
|
|
|
|
noise_local_t *noise_local_pool;
|
|
|
|
/* Private functions */
|
|
static noise_keypair_t *noise_remote_keypair_allocate (noise_remote_t *);
|
|
static void noise_remote_keypair_free (vlib_main_t * vm, noise_remote_t *,
|
|
noise_keypair_t **);
|
|
static uint32_t noise_remote_handshake_index_get (vlib_main_t *vm,
|
|
noise_remote_t *);
|
|
static void noise_remote_handshake_index_drop (vlib_main_t *vm,
|
|
noise_remote_t *);
|
|
|
|
static uint64_t noise_counter_send (noise_counter_t *);
|
|
bool noise_counter_recv (noise_counter_t *, uint64_t);
|
|
|
|
static void noise_kdf (uint8_t *, uint8_t *, uint8_t *, const uint8_t *,
|
|
size_t, size_t, size_t, size_t,
|
|
const uint8_t[NOISE_HASH_LEN]);
|
|
static bool noise_mix_dh (uint8_t[NOISE_HASH_LEN],
|
|
uint8_t[NOISE_SYMMETRIC_KEY_LEN],
|
|
const uint8_t[NOISE_PUBLIC_KEY_LEN],
|
|
const uint8_t[NOISE_PUBLIC_KEY_LEN]);
|
|
static bool noise_mix_ss (uint8_t ck[NOISE_HASH_LEN],
|
|
uint8_t key[NOISE_SYMMETRIC_KEY_LEN],
|
|
const uint8_t ss[NOISE_PUBLIC_KEY_LEN]);
|
|
static void noise_mix_hash (uint8_t[NOISE_HASH_LEN], const uint8_t *, size_t);
|
|
static void noise_mix_psk (uint8_t[NOISE_HASH_LEN],
|
|
uint8_t[NOISE_HASH_LEN],
|
|
uint8_t[NOISE_SYMMETRIC_KEY_LEN],
|
|
const uint8_t[NOISE_SYMMETRIC_KEY_LEN]);
|
|
static void noise_param_init (uint8_t[NOISE_HASH_LEN],
|
|
uint8_t[NOISE_HASH_LEN],
|
|
const uint8_t[NOISE_PUBLIC_KEY_LEN]);
|
|
|
|
static void noise_msg_encrypt (vlib_main_t * vm, uint8_t *, uint8_t *, size_t,
|
|
uint32_t key_idx, uint8_t[NOISE_HASH_LEN]);
|
|
static bool noise_msg_decrypt (vlib_main_t * vm, uint8_t *, uint8_t *, size_t,
|
|
uint32_t key_idx, uint8_t[NOISE_HASH_LEN]);
|
|
static void noise_msg_ephemeral (uint8_t[NOISE_HASH_LEN],
|
|
uint8_t[NOISE_HASH_LEN],
|
|
const uint8_t src[NOISE_PUBLIC_KEY_LEN]);
|
|
|
|
static void noise_tai64n_now (uint8_t[NOISE_TIMESTAMP_LEN]);
|
|
|
|
/* Set/Get noise parameters */
|
|
void
|
|
noise_local_init (noise_local_t * l, struct noise_upcall *upcall)
|
|
{
|
|
clib_memset (l, 0, sizeof (*l));
|
|
l->l_upcall = *upcall;
|
|
}
|
|
|
|
bool
|
|
noise_local_set_private (noise_local_t * l,
|
|
const uint8_t private[NOISE_PUBLIC_KEY_LEN])
|
|
{
|
|
clib_memcpy (l->l_private, private, NOISE_PUBLIC_KEY_LEN);
|
|
|
|
return curve25519_gen_public (l->l_public, private);
|
|
}
|
|
|
|
void
|
|
noise_remote_init (vlib_main_t *vm, noise_remote_t *r, uint32_t peer_pool_idx,
|
|
const uint8_t public[NOISE_PUBLIC_KEY_LEN],
|
|
u32 noise_local_idx)
|
|
{
|
|
clib_memset (r, 0, sizeof (*r));
|
|
clib_memcpy (r->r_public, public, NOISE_PUBLIC_KEY_LEN);
|
|
clib_rwlock_init (&r->r_keypair_lock);
|
|
r->r_peer_idx = peer_pool_idx;
|
|
r->r_local_idx = noise_local_idx;
|
|
r->r_handshake.hs_state = HS_ZEROED;
|
|
|
|
noise_remote_precompute (vm, r);
|
|
}
|
|
|
|
void
|
|
noise_remote_precompute (vlib_main_t *vm, noise_remote_t *r)
|
|
{
|
|
noise_local_t *l = noise_local_get (r->r_local_idx);
|
|
|
|
if (!curve25519_gen_shared (r->r_ss, l->l_private, r->r_public))
|
|
clib_memset (r->r_ss, 0, NOISE_PUBLIC_KEY_LEN);
|
|
|
|
noise_remote_handshake_index_drop (vm, r);
|
|
wg_secure_zero_memory (&r->r_handshake, sizeof (r->r_handshake));
|
|
}
|
|
|
|
/* Handshake functions */
|
|
bool
|
|
noise_create_initiation (vlib_main_t * vm, noise_remote_t * r,
|
|
uint32_t * s_idx, uint8_t ue[NOISE_PUBLIC_KEY_LEN],
|
|
uint8_t es[NOISE_PUBLIC_KEY_LEN + NOISE_AUTHTAG_LEN],
|
|
uint8_t ets[NOISE_TIMESTAMP_LEN + NOISE_AUTHTAG_LEN])
|
|
{
|
|
noise_handshake_t *hs = &r->r_handshake;
|
|
noise_local_t *l = noise_local_get (r->r_local_idx);
|
|
uint8_t _key[NOISE_SYMMETRIC_KEY_LEN] = { 0 };
|
|
uint32_t key_idx;
|
|
uint8_t *key;
|
|
int ret = false;
|
|
|
|
key_idx =
|
|
vnet_crypto_key_add (vm, VNET_CRYPTO_ALG_CHACHA20_POLY1305, _key,
|
|
NOISE_SYMMETRIC_KEY_LEN);
|
|
key = vnet_crypto_get_key (key_idx)->data;
|
|
|
|
noise_param_init (hs->hs_ck, hs->hs_hash, r->r_public);
|
|
|
|
/* e */
|
|
curve25519_gen_secret (hs->hs_e);
|
|
if (!curve25519_gen_public (ue, hs->hs_e))
|
|
goto error;
|
|
noise_msg_ephemeral (hs->hs_ck, hs->hs_hash, ue);
|
|
|
|
/* es */
|
|
if (!noise_mix_dh (hs->hs_ck, key, hs->hs_e, r->r_public))
|
|
goto error;
|
|
vnet_crypto_key_update (vm, key_idx);
|
|
|
|
/* s */
|
|
noise_msg_encrypt (vm, es, l->l_public, NOISE_PUBLIC_KEY_LEN, key_idx,
|
|
hs->hs_hash);
|
|
|
|
/* ss */
|
|
if (!noise_mix_ss (hs->hs_ck, key, r->r_ss))
|
|
goto error;
|
|
vnet_crypto_key_update (vm, key_idx);
|
|
|
|
/* {t} */
|
|
noise_tai64n_now (ets);
|
|
noise_msg_encrypt (vm, ets, ets, NOISE_TIMESTAMP_LEN, key_idx, hs->hs_hash);
|
|
noise_remote_handshake_index_drop (vm, r);
|
|
hs->hs_state = CREATED_INITIATION;
|
|
hs->hs_local_index = noise_remote_handshake_index_get (vm, r);
|
|
*s_idx = hs->hs_local_index;
|
|
ret = true;
|
|
error:
|
|
wg_secure_zero_memory (key, NOISE_SYMMETRIC_KEY_LEN);
|
|
vnet_crypto_key_del (vm, key_idx);
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
noise_consume_initiation (vlib_main_t * vm, noise_local_t * l,
|
|
noise_remote_t ** rp, uint32_t s_idx,
|
|
uint8_t ue[NOISE_PUBLIC_KEY_LEN],
|
|
uint8_t es[NOISE_PUBLIC_KEY_LEN +
|
|
NOISE_AUTHTAG_LEN],
|
|
uint8_t ets[NOISE_TIMESTAMP_LEN +
|
|
NOISE_AUTHTAG_LEN])
|
|
{
|
|
noise_remote_t *r;
|
|
noise_handshake_t hs;
|
|
uint8_t _key[NOISE_SYMMETRIC_KEY_LEN] = { 0 };
|
|
uint8_t r_public[NOISE_PUBLIC_KEY_LEN] = { 0 };
|
|
uint8_t timestamp[NOISE_TIMESTAMP_LEN] = { 0 };
|
|
u32 key_idx;
|
|
uint8_t *key;
|
|
int ret = false;
|
|
|
|
key_idx =
|
|
vnet_crypto_key_add (vm, VNET_CRYPTO_ALG_CHACHA20_POLY1305, _key,
|
|
NOISE_SYMMETRIC_KEY_LEN);
|
|
key = vnet_crypto_get_key (key_idx)->data;
|
|
|
|
noise_param_init (hs.hs_ck, hs.hs_hash, l->l_public);
|
|
|
|
/* e */
|
|
noise_msg_ephemeral (hs.hs_ck, hs.hs_hash, ue);
|
|
|
|
/* es */
|
|
if (!noise_mix_dh (hs.hs_ck, key, l->l_private, ue))
|
|
goto error;
|
|
vnet_crypto_key_update (vm, key_idx);
|
|
|
|
/* s */
|
|
|
|
if (!noise_msg_decrypt (vm, r_public, es,
|
|
NOISE_PUBLIC_KEY_LEN + NOISE_AUTHTAG_LEN, key_idx,
|
|
hs.hs_hash))
|
|
goto error;
|
|
|
|
/* Lookup the remote we received from */
|
|
if ((r = l->l_upcall.u_remote_get (r_public)) == NULL)
|
|
goto error;
|
|
|
|
/* ss */
|
|
if (!noise_mix_ss (hs.hs_ck, key, r->r_ss))
|
|
goto error;
|
|
vnet_crypto_key_update (vm, key_idx);
|
|
|
|
/* {t} */
|
|
if (!noise_msg_decrypt (vm, timestamp, ets,
|
|
NOISE_TIMESTAMP_LEN + NOISE_AUTHTAG_LEN, key_idx,
|
|
hs.hs_hash))
|
|
goto error;
|
|
;
|
|
|
|
hs.hs_state = CONSUMED_INITIATION;
|
|
hs.hs_local_index = 0;
|
|
hs.hs_remote_index = s_idx;
|
|
clib_memcpy (hs.hs_e, ue, NOISE_PUBLIC_KEY_LEN);
|
|
|
|
/* Replay */
|
|
if (clib_memcmp (timestamp, r->r_timestamp, NOISE_TIMESTAMP_LEN) > 0)
|
|
clib_memcpy (r->r_timestamp, timestamp, NOISE_TIMESTAMP_LEN);
|
|
else
|
|
goto error;
|
|
|
|
/* Flood attack */
|
|
if (wg_birthdate_has_expired (r->r_last_init, REJECT_INTERVAL))
|
|
r->r_last_init = vlib_time_now (vm);
|
|
else
|
|
goto error;
|
|
|
|
/* Ok, we're happy to accept this initiation now */
|
|
noise_remote_handshake_index_drop (vm, r);
|
|
r->r_handshake = hs;
|
|
*rp = r;
|
|
ret = true;
|
|
|
|
error:
|
|
wg_secure_zero_memory (key, NOISE_SYMMETRIC_KEY_LEN);
|
|
vnet_crypto_key_del (vm, key_idx);
|
|
wg_secure_zero_memory (&hs, sizeof (hs));
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
noise_create_response (vlib_main_t * vm, noise_remote_t * r, uint32_t * s_idx,
|
|
uint32_t * r_idx, uint8_t ue[NOISE_PUBLIC_KEY_LEN],
|
|
uint8_t en[0 + NOISE_AUTHTAG_LEN])
|
|
{
|
|
noise_handshake_t *hs = &r->r_handshake;
|
|
uint8_t _key[NOISE_SYMMETRIC_KEY_LEN] = { 0 };
|
|
uint8_t e[NOISE_PUBLIC_KEY_LEN] = { 0 };
|
|
uint32_t key_idx;
|
|
uint8_t *key;
|
|
int ret = false;
|
|
|
|
key_idx =
|
|
vnet_crypto_key_add (vm, VNET_CRYPTO_ALG_CHACHA20_POLY1305, _key,
|
|
NOISE_SYMMETRIC_KEY_LEN);
|
|
key = vnet_crypto_get_key (key_idx)->data;
|
|
|
|
if (hs->hs_state != CONSUMED_INITIATION)
|
|
goto error;
|
|
|
|
/* e */
|
|
curve25519_gen_secret (e);
|
|
if (!curve25519_gen_public (ue, e))
|
|
goto error;
|
|
noise_msg_ephemeral (hs->hs_ck, hs->hs_hash, ue);
|
|
|
|
/* ee */
|
|
if (!noise_mix_dh (hs->hs_ck, NULL, e, hs->hs_e))
|
|
goto error;
|
|
|
|
/* se */
|
|
if (!noise_mix_dh (hs->hs_ck, NULL, e, r->r_public))
|
|
goto error;
|
|
|
|
/* psk */
|
|
noise_mix_psk (hs->hs_ck, hs->hs_hash, key, r->r_psk);
|
|
vnet_crypto_key_update (vm, key_idx);
|
|
|
|
/* {} */
|
|
noise_msg_encrypt (vm, en, NULL, 0, key_idx, hs->hs_hash);
|
|
|
|
|
|
hs->hs_state = CREATED_RESPONSE;
|
|
hs->hs_local_index = noise_remote_handshake_index_get (vm, r);
|
|
*r_idx = hs->hs_remote_index;
|
|
*s_idx = hs->hs_local_index;
|
|
ret = true;
|
|
error:
|
|
wg_secure_zero_memory (key, NOISE_SYMMETRIC_KEY_LEN);
|
|
vnet_crypto_key_del (vm, key_idx);
|
|
wg_secure_zero_memory (e, NOISE_PUBLIC_KEY_LEN);
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
noise_consume_response (vlib_main_t * vm, noise_remote_t * r, uint32_t s_idx,
|
|
uint32_t r_idx, uint8_t ue[NOISE_PUBLIC_KEY_LEN],
|
|
uint8_t en[0 + NOISE_AUTHTAG_LEN])
|
|
{
|
|
noise_local_t *l = noise_local_get (r->r_local_idx);
|
|
noise_handshake_t hs;
|
|
uint8_t _key[NOISE_SYMMETRIC_KEY_LEN] = { 0 };
|
|
uint8_t preshared_key[NOISE_PUBLIC_KEY_LEN] = { 0 };
|
|
uint32_t key_idx;
|
|
uint8_t *key;
|
|
int ret = false;
|
|
|
|
key_idx =
|
|
vnet_crypto_key_add (vm, VNET_CRYPTO_ALG_CHACHA20_POLY1305, _key,
|
|
NOISE_SYMMETRIC_KEY_LEN);
|
|
key = vnet_crypto_get_key (key_idx)->data;
|
|
|
|
hs = r->r_handshake;
|
|
clib_memcpy (preshared_key, r->r_psk, NOISE_SYMMETRIC_KEY_LEN);
|
|
|
|
if (hs.hs_state != CREATED_INITIATION || hs.hs_local_index != r_idx)
|
|
goto error;
|
|
|
|
/* e */
|
|
noise_msg_ephemeral (hs.hs_ck, hs.hs_hash, ue);
|
|
|
|
/* ee */
|
|
if (!noise_mix_dh (hs.hs_ck, NULL, hs.hs_e, ue))
|
|
goto error;
|
|
|
|
/* se */
|
|
if (!noise_mix_dh (hs.hs_ck, NULL, l->l_private, ue))
|
|
goto error;
|
|
|
|
/* psk */
|
|
noise_mix_psk (hs.hs_ck, hs.hs_hash, key, preshared_key);
|
|
vnet_crypto_key_update (vm, key_idx);
|
|
|
|
/* {} */
|
|
|
|
if (!noise_msg_decrypt
|
|
(vm, NULL, en, 0 + NOISE_AUTHTAG_LEN, key_idx, hs.hs_hash))
|
|
goto error;
|
|
|
|
|
|
hs.hs_remote_index = s_idx;
|
|
|
|
if (r->r_handshake.hs_state == hs.hs_state &&
|
|
r->r_handshake.hs_local_index == hs.hs_local_index)
|
|
{
|
|
r->r_handshake = hs;
|
|
r->r_handshake.hs_state = CONSUMED_RESPONSE;
|
|
ret = true;
|
|
}
|
|
error:
|
|
wg_secure_zero_memory (&hs, sizeof (hs));
|
|
wg_secure_zero_memory (key, NOISE_SYMMETRIC_KEY_LEN);
|
|
vnet_crypto_key_del (vm, key_idx);
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
noise_remote_begin_session (vlib_main_t * vm, noise_remote_t * r)
|
|
{
|
|
noise_handshake_t *hs = &r->r_handshake;
|
|
noise_keypair_t kp, *next, *current, *previous;
|
|
|
|
uint8_t key_send[NOISE_SYMMETRIC_KEY_LEN];
|
|
uint8_t key_recv[NOISE_SYMMETRIC_KEY_LEN];
|
|
|
|
/* We now derive the keypair from the handshake */
|
|
if (hs->hs_state == CONSUMED_RESPONSE)
|
|
{
|
|
kp.kp_is_initiator = 1;
|
|
noise_kdf (key_send, key_recv, NULL, NULL,
|
|
NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, 0,
|
|
hs->hs_ck);
|
|
}
|
|
else if (hs->hs_state == CREATED_RESPONSE)
|
|
{
|
|
kp.kp_is_initiator = 0;
|
|
noise_kdf (key_recv, key_send, NULL, NULL,
|
|
NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, 0,
|
|
hs->hs_ck);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
kp.kp_valid = 1;
|
|
kp.kp_send_index = vnet_crypto_key_add (vm,
|
|
VNET_CRYPTO_ALG_CHACHA20_POLY1305,
|
|
key_send, NOISE_SYMMETRIC_KEY_LEN);
|
|
kp.kp_recv_index = vnet_crypto_key_add (vm,
|
|
VNET_CRYPTO_ALG_CHACHA20_POLY1305,
|
|
key_recv, NOISE_SYMMETRIC_KEY_LEN);
|
|
kp.kp_local_index = hs->hs_local_index;
|
|
kp.kp_remote_index = hs->hs_remote_index;
|
|
kp.kp_birthdate = vlib_time_now (vm);
|
|
clib_memset (&kp.kp_ctr, 0, sizeof (kp.kp_ctr));
|
|
|
|
/* Now we need to add_new_keypair */
|
|
clib_rwlock_writer_lock (&r->r_keypair_lock);
|
|
/* Activate barrier to synchronization keys between threads */
|
|
vlib_worker_thread_barrier_sync (vm);
|
|
next = r->r_next;
|
|
current = r->r_current;
|
|
previous = r->r_previous;
|
|
|
|
if (kp.kp_is_initiator)
|
|
{
|
|
if (next != NULL)
|
|
{
|
|
r->r_next = NULL;
|
|
r->r_previous = next;
|
|
noise_remote_keypair_free (vm, r, ¤t);
|
|
}
|
|
else
|
|
{
|
|
r->r_previous = current;
|
|
}
|
|
|
|
noise_remote_keypair_free (vm, r, &previous);
|
|
|
|
r->r_current = noise_remote_keypair_allocate (r);
|
|
*r->r_current = kp;
|
|
}
|
|
else
|
|
{
|
|
noise_remote_keypair_free (vm, r, &next);
|
|
r->r_previous = NULL;
|
|
noise_remote_keypair_free (vm, r, &previous);
|
|
|
|
r->r_next = noise_remote_keypair_allocate (r);
|
|
*r->r_next = kp;
|
|
}
|
|
vlib_worker_thread_barrier_release (vm);
|
|
clib_rwlock_writer_unlock (&r->r_keypair_lock);
|
|
|
|
wg_secure_zero_memory (&r->r_handshake, sizeof (r->r_handshake));
|
|
|
|
wg_secure_zero_memory (&kp, sizeof (kp));
|
|
return true;
|
|
}
|
|
|
|
void
|
|
noise_remote_clear (vlib_main_t * vm, noise_remote_t * r)
|
|
{
|
|
noise_remote_handshake_index_drop (vm, r);
|
|
wg_secure_zero_memory (&r->r_handshake, sizeof (r->r_handshake));
|
|
|
|
clib_rwlock_writer_lock (&r->r_keypair_lock);
|
|
noise_remote_keypair_free (vm, r, &r->r_next);
|
|
noise_remote_keypair_free (vm, r, &r->r_current);
|
|
noise_remote_keypair_free (vm, r, &r->r_previous);
|
|
r->r_next = NULL;
|
|
r->r_current = NULL;
|
|
r->r_previous = NULL;
|
|
clib_rwlock_writer_unlock (&r->r_keypair_lock);
|
|
}
|
|
|
|
void
|
|
noise_remote_expire_current (noise_remote_t * r)
|
|
{
|
|
clib_rwlock_writer_lock (&r->r_keypair_lock);
|
|
if (r->r_next != NULL)
|
|
r->r_next->kp_valid = 0;
|
|
if (r->r_current != NULL)
|
|
r->r_current->kp_valid = 0;
|
|
clib_rwlock_writer_unlock (&r->r_keypair_lock);
|
|
}
|
|
|
|
bool
|
|
noise_remote_ready (noise_remote_t * r)
|
|
{
|
|
noise_keypair_t *kp;
|
|
int ret;
|
|
|
|
clib_rwlock_reader_lock (&r->r_keypair_lock);
|
|
if ((kp = r->r_current) == NULL ||
|
|
!kp->kp_valid ||
|
|
wg_birthdate_has_expired (kp->kp_birthdate, REJECT_AFTER_TIME) ||
|
|
kp->kp_ctr.c_recv >= REJECT_AFTER_MESSAGES ||
|
|
kp->kp_ctr.c_send >= REJECT_AFTER_MESSAGES)
|
|
ret = false;
|
|
else
|
|
ret = true;
|
|
clib_rwlock_reader_unlock (&r->r_keypair_lock);
|
|
return ret;
|
|
}
|
|
|
|
enum noise_state_crypt
|
|
noise_remote_encrypt (vlib_main_t * vm, noise_remote_t * r, uint32_t * r_idx,
|
|
uint64_t * nonce, uint8_t * src, size_t srclen,
|
|
uint8_t * dst)
|
|
{
|
|
noise_keypair_t *kp;
|
|
enum noise_state_crypt ret = SC_FAILED;
|
|
|
|
if ((kp = r->r_current) == NULL)
|
|
goto error;
|
|
|
|
/* We confirm that our values are within our tolerances. We want:
|
|
* - a valid keypair
|
|
* - our keypair to be less than REJECT_AFTER_TIME seconds old
|
|
* - our receive counter to be less than REJECT_AFTER_MESSAGES
|
|
* - our send counter to be less than REJECT_AFTER_MESSAGES
|
|
*/
|
|
if (!kp->kp_valid ||
|
|
wg_birthdate_has_expired (kp->kp_birthdate, REJECT_AFTER_TIME) ||
|
|
kp->kp_ctr.c_recv >= REJECT_AFTER_MESSAGES ||
|
|
((*nonce = noise_counter_send (&kp->kp_ctr)) > REJECT_AFTER_MESSAGES))
|
|
goto error;
|
|
|
|
/* We encrypt into the same buffer, so the caller must ensure that buf
|
|
* has NOISE_AUTHTAG_LEN bytes to store the MAC. The nonce and index
|
|
* are passed back out to the caller through the provided data pointer. */
|
|
*r_idx = kp->kp_remote_index;
|
|
|
|
wg_chacha20poly1305_calc (vm, src, srclen, dst, NULL, 0, *nonce,
|
|
VNET_CRYPTO_OP_CHACHA20_POLY1305_ENC,
|
|
kp->kp_send_index);
|
|
|
|
/* If our values are still within tolerances, but we are approaching
|
|
* the tolerances, we notify the caller with ESTALE that they should
|
|
* establish a new keypair. The current keypair can continue to be used
|
|
* until the tolerances are hit. We notify if:
|
|
* - our send counter is valid and not less than REKEY_AFTER_MESSAGES
|
|
* - we're the initiator and our keypair is older than
|
|
* REKEY_AFTER_TIME seconds */
|
|
ret = SC_KEEP_KEY_FRESH;
|
|
if ((kp->kp_valid && *nonce >= REKEY_AFTER_MESSAGES) ||
|
|
(kp->kp_is_initiator &&
|
|
wg_birthdate_has_expired (kp->kp_birthdate, REKEY_AFTER_TIME)))
|
|
goto error;
|
|
|
|
ret = SC_OK;
|
|
error:
|
|
return ret;
|
|
}
|
|
|
|
/* Private functions - these should not be called outside this file under any
|
|
* circumstances. */
|
|
static noise_keypair_t *
|
|
noise_remote_keypair_allocate (noise_remote_t * r)
|
|
{
|
|
noise_keypair_t *kp;
|
|
kp = clib_mem_alloc (sizeof (*kp));
|
|
return kp;
|
|
}
|
|
|
|
static uint32_t
|
|
noise_remote_handshake_index_get (vlib_main_t *vm, noise_remote_t *r)
|
|
{
|
|
noise_local_t *local = noise_local_get (r->r_local_idx);
|
|
struct noise_upcall *u = &local->l_upcall;
|
|
return u->u_index_set (vm, r);
|
|
}
|
|
|
|
static void
|
|
noise_remote_handshake_index_drop (vlib_main_t *vm, noise_remote_t *r)
|
|
{
|
|
noise_handshake_t *hs = &r->r_handshake;
|
|
noise_local_t *local = noise_local_get (r->r_local_idx);
|
|
struct noise_upcall *u = &local->l_upcall;
|
|
if (hs->hs_state != HS_ZEROED)
|
|
u->u_index_drop (vm, hs->hs_local_index);
|
|
}
|
|
|
|
static void
|
|
noise_kdf (uint8_t * a, uint8_t * b, uint8_t * c, const uint8_t * x,
|
|
size_t a_len, size_t b_len, size_t c_len, size_t x_len,
|
|
const uint8_t ck[NOISE_HASH_LEN])
|
|
{
|
|
uint8_t out[BLAKE2S_HASH_SIZE + 1];
|
|
uint8_t sec[BLAKE2S_HASH_SIZE];
|
|
|
|
/* Extract entropy from "x" into sec */
|
|
u32 l = 0;
|
|
HMAC (EVP_blake2s256 (), ck, NOISE_HASH_LEN, x, x_len, sec, &l);
|
|
ASSERT (l == BLAKE2S_HASH_SIZE);
|
|
if (a == NULL || a_len == 0)
|
|
goto out;
|
|
|
|
/* Expand first key: key = sec, data = 0x1 */
|
|
out[0] = 1;
|
|
HMAC (EVP_blake2s256 (), sec, BLAKE2S_HASH_SIZE, out, 1, out, &l);
|
|
ASSERT (l == BLAKE2S_HASH_SIZE);
|
|
clib_memcpy (a, out, a_len);
|
|
|
|
if (b == NULL || b_len == 0)
|
|
goto out;
|
|
|
|
/* Expand second key: key = sec, data = "a" || 0x2 */
|
|
out[BLAKE2S_HASH_SIZE] = 2;
|
|
HMAC (EVP_blake2s256 (), sec, BLAKE2S_HASH_SIZE, out, BLAKE2S_HASH_SIZE + 1,
|
|
out, &l);
|
|
ASSERT (l == BLAKE2S_HASH_SIZE);
|
|
clib_memcpy (b, out, b_len);
|
|
|
|
if (c == NULL || c_len == 0)
|
|
goto out;
|
|
|
|
/* Expand third key: key = sec, data = "b" || 0x3 */
|
|
out[BLAKE2S_HASH_SIZE] = 3;
|
|
HMAC (EVP_blake2s256 (), sec, BLAKE2S_HASH_SIZE, out, BLAKE2S_HASH_SIZE + 1,
|
|
out, &l);
|
|
ASSERT (l == BLAKE2S_HASH_SIZE);
|
|
|
|
clib_memcpy (c, out, c_len);
|
|
|
|
out:
|
|
/* Clear sensitive data from stack */
|
|
wg_secure_zero_memory (sec, BLAKE2S_HASH_SIZE);
|
|
wg_secure_zero_memory (out, BLAKE2S_HASH_SIZE + 1);
|
|
}
|
|
|
|
static bool
|
|
noise_mix_dh (uint8_t ck[NOISE_HASH_LEN],
|
|
uint8_t key[NOISE_SYMMETRIC_KEY_LEN],
|
|
const uint8_t private[NOISE_PUBLIC_KEY_LEN],
|
|
const uint8_t public[NOISE_PUBLIC_KEY_LEN])
|
|
{
|
|
uint8_t dh[NOISE_PUBLIC_KEY_LEN];
|
|
if (!curve25519_gen_shared (dh, private, public))
|
|
return false;
|
|
noise_kdf (ck, key, NULL, dh,
|
|
NOISE_HASH_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN,
|
|
ck);
|
|
wg_secure_zero_memory (dh, NOISE_PUBLIC_KEY_LEN);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
noise_mix_ss (uint8_t ck[NOISE_HASH_LEN],
|
|
uint8_t key[NOISE_SYMMETRIC_KEY_LEN],
|
|
const uint8_t ss[NOISE_PUBLIC_KEY_LEN])
|
|
{
|
|
static uint8_t null_point[NOISE_PUBLIC_KEY_LEN];
|
|
if (clib_memcmp (ss, null_point, NOISE_PUBLIC_KEY_LEN) == 0)
|
|
return false;
|
|
noise_kdf (ck, key, NULL, ss,
|
|
NOISE_HASH_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN,
|
|
ck);
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
noise_mix_hash (uint8_t hash[NOISE_HASH_LEN], const uint8_t * src,
|
|
size_t src_len)
|
|
{
|
|
blake2s_state_t blake;
|
|
|
|
blake2s_init (&blake, NOISE_HASH_LEN);
|
|
blake2s_update (&blake, hash, NOISE_HASH_LEN);
|
|
blake2s_update (&blake, src, src_len);
|
|
blake2s_final (&blake, hash, NOISE_HASH_LEN);
|
|
}
|
|
|
|
static void
|
|
noise_mix_psk (uint8_t ck[NOISE_HASH_LEN], uint8_t hash[NOISE_HASH_LEN],
|
|
uint8_t key[NOISE_SYMMETRIC_KEY_LEN],
|
|
const uint8_t psk[NOISE_SYMMETRIC_KEY_LEN])
|
|
{
|
|
uint8_t tmp[NOISE_HASH_LEN];
|
|
|
|
noise_kdf (ck, tmp, key, psk,
|
|
NOISE_HASH_LEN, NOISE_HASH_LEN, NOISE_SYMMETRIC_KEY_LEN,
|
|
NOISE_SYMMETRIC_KEY_LEN, ck);
|
|
noise_mix_hash (hash, tmp, NOISE_HASH_LEN);
|
|
wg_secure_zero_memory (tmp, NOISE_HASH_LEN);
|
|
}
|
|
|
|
static void
|
|
noise_param_init (uint8_t ck[NOISE_HASH_LEN], uint8_t hash[NOISE_HASH_LEN],
|
|
const uint8_t s[NOISE_PUBLIC_KEY_LEN])
|
|
{
|
|
blake2s_state_t blake;
|
|
|
|
blake2s (ck, NOISE_HASH_LEN, (uint8_t *) NOISE_HANDSHAKE_NAME,
|
|
strlen (NOISE_HANDSHAKE_NAME), NULL, 0);
|
|
|
|
blake2s_init (&blake, NOISE_HASH_LEN);
|
|
blake2s_update (&blake, ck, NOISE_HASH_LEN);
|
|
blake2s_update (&blake, (uint8_t *) NOISE_IDENTIFIER_NAME,
|
|
strlen (NOISE_IDENTIFIER_NAME));
|
|
blake2s_final (&blake, hash, NOISE_HASH_LEN);
|
|
|
|
noise_mix_hash (hash, s, NOISE_PUBLIC_KEY_LEN);
|
|
}
|
|
|
|
static void
|
|
noise_msg_encrypt (vlib_main_t * vm, uint8_t * dst, uint8_t * src,
|
|
size_t src_len, uint32_t key_idx,
|
|
uint8_t hash[NOISE_HASH_LEN])
|
|
{
|
|
/* Nonce always zero for Noise_IK */
|
|
wg_chacha20poly1305_calc (vm, src, src_len, dst, hash, NOISE_HASH_LEN, 0,
|
|
VNET_CRYPTO_OP_CHACHA20_POLY1305_ENC, key_idx);
|
|
noise_mix_hash (hash, dst, src_len + NOISE_AUTHTAG_LEN);
|
|
}
|
|
|
|
static bool
|
|
noise_msg_decrypt (vlib_main_t * vm, uint8_t * dst, uint8_t * src,
|
|
size_t src_len, uint32_t key_idx,
|
|
uint8_t hash[NOISE_HASH_LEN])
|
|
{
|
|
/* Nonce always zero for Noise_IK */
|
|
if (!wg_chacha20poly1305_calc (vm, src, src_len, dst, hash, NOISE_HASH_LEN,
|
|
0, VNET_CRYPTO_OP_CHACHA20_POLY1305_DEC,
|
|
key_idx))
|
|
return false;
|
|
noise_mix_hash (hash, src, src_len);
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
noise_msg_ephemeral (uint8_t ck[NOISE_HASH_LEN], uint8_t hash[NOISE_HASH_LEN],
|
|
const uint8_t src[NOISE_PUBLIC_KEY_LEN])
|
|
{
|
|
noise_mix_hash (hash, src, NOISE_PUBLIC_KEY_LEN);
|
|
noise_kdf (ck, NULL, NULL, src, NOISE_HASH_LEN, 0, 0,
|
|
NOISE_PUBLIC_KEY_LEN, ck);
|
|
}
|
|
|
|
static void
|
|
noise_tai64n_now (uint8_t output[NOISE_TIMESTAMP_LEN])
|
|
{
|
|
uint32_t unix_sec;
|
|
uint32_t unix_nanosec;
|
|
|
|
uint64_t sec;
|
|
uint32_t nsec;
|
|
|
|
unix_time_now_nsec_fraction (&unix_sec, &unix_nanosec);
|
|
|
|
/* Round down the nsec counter to limit precise timing leak. */
|
|
unix_nanosec &= REJECT_INTERVAL_MASK;
|
|
|
|
/* https://cr.yp.to/libtai/tai64.html */
|
|
sec = htobe64 (0x400000000000000aULL + unix_sec);
|
|
nsec = htobe32 (unix_nanosec);
|
|
|
|
/* memcpy to output buffer, assuming output could be unaligned. */
|
|
clib_memcpy (output, &sec, sizeof (sec));
|
|
clib_memcpy (output + sizeof (sec), &nsec, sizeof (nsec));
|
|
}
|
|
|
|
/*
|
|
* fd.io coding-style-patch-verification: ON
|
|
*
|
|
* Local Variables:
|
|
* eval: (c-set-style "gnu")
|
|
* End:
|
|
*/
|