aboutsummaryrefslogtreecommitdiffstats
path: root/src/wireguard.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/wireguard.c')
-rw-r--r--src/wireguard.c358
1 files changed, 204 insertions, 154 deletions
diff --git a/src/wireguard.c b/src/wireguard.c
index 758d707..557d6de 100644
--- a/src/wireguard.c
+++ b/src/wireguard.c
@@ -74,10 +74,13 @@ void wg_keypair_generate(struct wg_keypair *);
enum wg_error wg_msg_initiation_valid_mac2(struct wg_msg_initiation *, struct wg_cookie *);
enum wg_error wg_msg_response_valid_mac2(struct wg_msg_response *, struct wg_cookie *);
+void wg_session_drop(struct wg_session *);
+void wg_session_ref(struct wg_session *);
+
/* Some crappy API */
void
wg_device_init(struct wg_device *dev,
- void (*outq)(struct wg_peer *, enum wg_pkt_type))
+ void (*outq)(struct wg_peer *, enum wg_pkt_type, uint32_t))
{
bzero(dev, sizeof(*dev));
dev->d_outq = outq;
@@ -105,174 +108,232 @@ wg_device_new_peer(struct wg_device *dev, struct wg_pubkey *key, void *arg)
peer->p_device = dev;
peer->p_remote = *key;
rw_init(&peer->p_lock, "wg_peer");
- SLIST_INIT(&peer->p_sessions);
- rw_enter_write(&dev->d_lock);
+ rw_enter_write(&peer->p_lock);
peer->p_id = fm_insert(&dev->d_peers, peer);
- rw_exit_write(&dev->d_lock);
+ rw_exit_write(&peer->p_lock);
+
+ /* All other elements of wg_peer are nulled by M_ZERO */
return peer;
}
+struct wg_session *
+wg_device_new_session(struct wg_device *dev)
+{
+ struct wg_session *session;
+ session = malloc(sizeof(*session), M_DEVBUF, M_WAITOK | M_ZERO);
+
+ getnanotime(&session->s_created);
+
+ rw_enter_write(&peer->p_lock);
+ session->s_local_id = fm_insert(&dev->d_sessions, session);
+ rw_exit_write(&peer->p_lock);
+
+ return session;
+}
+
+struct wg_peer *
+wg_device_ref_peerid(struct wg_device *dev, uint32_t id)
+{
+ return fm_ref(&dev->d_peers, id);
+}
+
struct wg_peer *
wg_device_ref_peerkey(struct wg_device *dev, struct wg_pubkey *key)
{
+ /* For the time being, we just iterate through peers to find the
+ * matching peer */
uint32_t id = 0;
struct wg_peer *peer;
struct map_item *item;
- rw_enter_read(&dev->d_lock);
+ rw_enter_read(&dev->d_peers.d_lock);
FM_FOREACH_FILLED(item, &dev->d_peers) {
peer = item->value;
if (memcmp(key->k, peer->p_remote.k, sizeof(key->k)) == 0)
id = peer->p_id;
}
- rw_exit_read(&dev->d_lock);
- return wg_device_ref_peerid(dev, id);
-}
+ rw_exit_read(&dev->d_peers.d_lock);
-struct wg_peer *
-wg_device_ref_peerid(struct wg_device *dev, uint32_t id)
-{
- struct wg_peer *peer;
- rw_enter_read(&dev->d_lock);
- peer = fm_ref(&dev->d_peers, id);
- rw_exit_read(&dev->d_lock);
- return peer;
+ /* Take reference and return peer */
+ return wg_device_ref_peerid(dev, id);
}
void
wg_peer_ref(struct wg_peer *peer)
{
- wg_device_ref_peerid(peer->p_device, peer->p_id);
+ /* Don't return anything, just take a reference and expect it exists */
+ KASSERT(wg_device_ref_peerid(peer->p_device, peer->p_id) != NULL);
}
void
wg_peer_put(struct wg_peer *peer)
{
- struct wg_device *dev = peer->p_device;
- rw_enter_read(&dev->d_lock);
- fm_put(&dev->d_peers, peer->p_id);
- rw_exit_read(&dev->d_lock);
+ fm_put(&peer->p_device->d_peers, peer->p_id);
}
void
wg_peer_drop(struct wg_peer *peer)
{
- struct wg_device *dev = peer->p_device;
- rw_enter_read(&dev->d_lock);
- fm_put(&dev->d_peers, peer->p_id);
- fm_remove(&dev->d_peers, peer->p_id);
- rw_exit_read(&dev->d_lock);
+ fm_remove(&peer->p_device->d_peers, peer->p_id);
+ free(peer, M_DEVBUF, sizeof(*peer));
+}
+
+struct wg_session *
+wg_device_ref_session(struct wg_device *dev, uint32_t id)
+{
+ return fm_ref(&dev->d_sessions, id);
}
void
-wg_peer_attach_session(struct wg_peer *peer, struct wg_session *session)
+wg_session_ref(struct wg_session *session)
{
- rw_assert_wrlock(&peer->p_lock);
- KASSERT(session->s_peer == NULL);
- session->s_peer = peer;
- SLIST_INSERT_HEAD(&peer->p_sessions, session, s_entry);
+ KASSERT(wg_device_ref_session(session->s_peer->p_device,
+ session->s_local_id) != NULL);
}
void
-wg_peer_setshared(struct wg_peer *peer, struct wg_privkey *key)
+wg_session_put(struct wg_session *session)
{
- rw_enter_write(&peer->p_lock);
- peer->p_shared = *key;
- rw_exit_write(&peer->p_lock);
+ fm_put(&session->s_peer->p_device->d_sessions, session->s_local_id);
}
void
-wg_peer_getshared(struct wg_peer *peer, struct wg_privkey *key)
+wg_session_drop(struct wg_session *session)
{
- rw_enter_read(&peer->p_lock);
- *key = peer->p_shared;
- rw_exit_read(&peer->p_lock);
+ fm_remove(&session->s_peer->p_device->d_sessions, session->s_local_id);
+ free(session, M_DEVBUF, sizeof(*session));
}
void
-wg_peer_clean(struct wg_peer *peer)
+wg_peer_attach_session(struct wg_peer *peer, struct wg_session *session)
{
- struct wg_session *session;
- rw_enter_write(&peer->p_lock);
- SLIST_FOREACH(session, &peer->p_sessions, s_entry)
- wg_session_drop(session);
+ /* Assert the session is not attached to another peer.
+ * Assert the session is for the same device that the peer is for.
+ * Assert the session is newly created, by checking s_state. */
+ KASSERT(session->s_peer == NULL);
+ //KASSERT(session->s_device == peer->p_device);
+ KASSERT(session->s_state == WG_STATE_RECV_INITIATION ||
+ session->s_state == WG_STATE_MADE_INITIATION);
+
rw_enter_write(&peer->p_lock);
+ if (peer->p_hs_session) {
+ wg_session_drop(peer->p_hs_session);
+ peer->p_hs_session = NULL;
+ }
+
+ peer->p_hs_session = session;
+ session->s_peer = peer;
+ rw_exit_write(&peer->p_lock);
}
-struct timespec
-wg_peer_last_handshake(struct wg_peer *peer)
+void
+wg_session_promote(struct wg_session *session)
{
- struct timespec ret;
- rw_enter_read(&peer->p_lock);
- ret = peer->p_last_handshake;
- rw_exit_read(&peer->p_lock);
- return ret;
+ /* TODO check locking state before */
+ struct wg_peer *peer = session->s_peer;
+ struct wg_keyset *ks = &session->s_keyset;
+ struct wg_handshake *hs = &session->s_handshake;
+
+ /* Setup session: derive keys, initialise the antireplay structure */
+ rw_enter_write(&peer->p_lock);
+ rw_enter_write(&session->s_lock);
+ if (session->s_state == WG_STATE_INITIATOR ||
+ session->s_state == WG_STATE_RESPONDER) {
+ goto leave;
+ } else if (session->s_state == WG_STATE_RECV_RESPONSE) {
+ session->s_state = WG_STATE_INITIATOR;
+ wg_kdf(ks->k_txkey.k, ks->k_rxkey.k, NULL, hs->h_ck, NULL, 0);
+ } else if (session->s_state == WG_STATE_MADE_RESPONSE) {
+ session->s_state = WG_STATE_RESPONDER;
+ wg_kdf(ks->k_rxkey.k, ks->k_txkey.k, NULL, hs->h_ck, NULL, 0);
+ } else
+ panic("invalid session state");
+
+ antireplay_init(&ks->k_ar);
+
+ /* Move session to p_ks_session by shuffling older sessions. */
+ if (peer->p_ks_session_old != NULL)
+ wg_session_drop(peer->p_ks_session_old);
+ if (peer->p_ks_session != NULL)
+ peer->p_ks_session_old = peer->p_ks_session;
+ peer->p_ks_session = peer->p_hs_session;
+ peer->p_hs_session = NULL;
+leave:
+ rw_exit_write(&session->s_lock);
+ rw_exit_write(&peer->p_lock);
}
-enum wg_error
-wg_peer_has_transport_session(struct wg_peer *peer)
+struct wg_session *
+wg_peer_hs_session(struct wg_peer *peer)
{
- enum wg_error err = WG_REJECT;
struct wg_session *session;
rw_enter_read(&peer->p_lock);
- SLIST_FOREACH(session, &peer->p_sessions, s_entry) {
- if (session->s_state == WG_STATE_INITIATOR ||
- session->s_state == WG_STATE_RESPONDER) {
- err = WG_OK;
- break;
- }
- }
+ if ((session = peer->p_hs_session) != NULL)
+ wg_session_ref(session);
rw_exit_read(&peer->p_lock);
- return err;
+ return session;
}
struct wg_session *
-wg_device_new_session(struct wg_device *dev)
+wg_peer_ks_session(struct wg_peer *peer)
{
struct wg_session *session;
- session = malloc(sizeof(*session), M_DEVBUF, M_WAITOK | M_ZERO);
-
- getnanotime(&session->s_created);
-
- rw_enter_write(&dev->d_lock);
- session->s_local_id = fm_insert(&dev->d_sessions, session);
- rw_exit_write(&dev->d_lock);
+ rw_enter_read(&peer->p_lock);
+ session = peer->p_ks_session;
+ if (session != NULL &&
+ (wg_timespec_timedout(&session->s_created, WG_REJECT_AFTER_TIME) ||
+ session->s_keyset.k_txcounter > WG_REJECT_AFTER_MESSAGES))
+ session = NULL;
+ if (session != NULL)
+ wg_session_ref(session);
+ rw_exit_read(&peer->p_lock);
return session;
}
-struct wg_session *
-wg_device_ref_session(struct wg_device *dev, uint32_t id)
+void
+wg_peer_setshared(struct wg_peer *peer, struct wg_privkey *key)
{
- struct wg_session *session;
- rw_enter_read(&dev->d_lock);
- session = fm_ref(&dev->d_sessions, id);
- rw_exit_read(&dev->d_lock);
- return session;
+ rw_enter_write(&peer->p_lock);
+ peer->p_shared = *key;
+ rw_exit_write(&peer->p_lock);
}
void
-wg_session_ref(struct wg_session *session)
+wg_peer_getshared(struct wg_peer *peer, struct wg_privkey *key)
{
- wg_device_ref_session(session->s_peer->p_device, session->s_local_id);
+ rw_enter_read(&peer->p_lock);
+ *key = peer->p_shared;
+ rw_exit_read(&peer->p_lock);
}
-void
-wg_session_put(struct wg_session *session)
+struct timespec
+wg_peer_last_handshake(struct wg_peer *peer)
{
- struct wg_device *dev = session->s_peer->p_device;
- rw_enter_read(&dev->d_lock);
- fm_put(&dev->d_sessions, session->s_local_id);
- rw_exit_read(&dev->d_lock);
+ struct timespec ret = { 0, 0 };
+ rw_enter_read(&peer->p_lock);
+ if (peer->p_ks_session)
+ ret = peer->p_ks_session->s_created;
+ rw_exit_read(&peer->p_lock);
+ return ret;
}
void
-wg_session_drop(struct wg_session *session)
+wg_peer_clean(struct wg_peer *peer)
{
- struct wg_device *dev = session->s_peer->p_device;
- rw_enter_write(&dev->d_lock);
- fm_put(&dev->d_sessions, session->s_local_id);
- fm_remove(&dev->d_sessions, session->s_local_id);
- rw_exit_write(&dev->d_lock);
+ rw_enter_write(&peer->p_lock);
+
+ if (peer->p_hs_session != NULL)
+ wg_session_drop(peer->p_hs_session);
+ if (peer->p_ks_session != NULL)
+ wg_session_drop(peer->p_ks_session);
+ if (peer->p_ks_session_old != NULL)
+ wg_session_drop(peer->p_ks_session_old);
+
+ peer->p_hs_session = NULL;
+ peer->p_ks_session = NULL;
+ peer->p_ks_session_old = NULL;
+
+ rw_enter_write(&peer->p_lock);
}
/* Crypto */
@@ -331,8 +392,7 @@ wg_device_rx_initiation(struct wg_device *dev, struct wg_msg_initiation *init,
rw_exit_read(&dev->d_lock);
- /* TODO Fix locking */
- /* TODO rotate out old peers */
+ /* Create new session and add to peer */
session = wg_device_new_session(dev);
session->s_handshake = hs;
session->s_remote_id = init->sender;
@@ -340,15 +400,13 @@ wg_device_rx_initiation(struct wg_device *dev, struct wg_msg_initiation *init,
explicit_bzero(&hs, sizeof(hs));
*s = session;
- rw_enter_write(&peer->p_lock);
wg_peer_attach_session(peer, session);
- rw_exit_write(&peer->p_lock);
-
- dev->d_outq(peer, WG_PKT_RESPONSE);
+ dev->d_outq(peer, WG_PKT_RESPONSE, session->s_local_id);
+ /* Take reference to return to caller */
+ wg_session_ref(session);
wg_peer_put(peer);
-
return WG_OK;
leave:
rw_exit_read(&dev->d_lock);
@@ -402,13 +460,16 @@ wg_device_rx_response(struct wg_device *dev, struct wg_msg_response *resp,
session->s_handshake = hs;
session->s_remote_id = resp->sender;
- session->s_state = WG_STATE_INITIATOR;
+ session->s_state = WG_STATE_RECV_RESPONSE;
- wg_kdf(session->s_keyset.k_txkey.k, session->s_keyset.k_rxkey.k,
- NULL, session->s_handshake.h_ck, NULL, 0);
- antireplay_init(&session->s_keyset.k_ar);
+ rw_exit_write(&session->s_lock);
+ rw_exit_read(&dev->d_lock);
+ explicit_bzero(&hs, sizeof(hs));
+ wg_session_promote(session);
*s = session;
+
+ return ret;
leave:
rw_exit_write(&session->s_lock);
rw_exit_read(&dev->d_lock);
@@ -433,8 +494,8 @@ wg_device_rx_cookie(struct wg_device *dev, struct wg_msg_cookie *cookie,
rw_enter_write(&session->s_peer->p_lock);
rw_enter_read(&session->s_lock);
- if (session->s_state != WG_STATE_WAIT_CONFIRM &&
- session->s_state != WG_STATE_MADE_INITIATION)
+ if (session->s_state != WG_STATE_MADE_INITIATION &&
+ session->s_state != WG_STATE_MADE_RESPONSE)
ret_error(WG_STATE);
wg_hash2(key, WG_COOKIE, strlen(WG_COOKIE), session->s_peer->p_remote.k,
@@ -467,14 +528,9 @@ wg_device_rx_transport(struct wg_device *dev, struct wg_msg_transport *msg,
if ((session = wg_device_ref_session(dev, msg->receiver)) == NULL)
return WG_ID;
+ wg_session_promote(session);
+
rw_enter_read(&session->s_lock);
-
- if (session->s_state == WG_STATE_WAIT_CONFIRM) {
- wg_kdf(session->s_keyset.k_rxkey.k, session->s_keyset.k_txkey.k,
- NULL, session->s_handshake.h_ck, NULL, 0);
- antireplay_init(&session->s_keyset.k_ar);
- session->s_state = WG_STATE_RESPONDER;
- }
if (session->s_state != WG_STATE_INITIATOR &&
session->s_state != WG_STATE_RESPONDER)
@@ -496,7 +552,7 @@ wg_device_rx_transport(struct wg_device *dev, struct wg_msg_transport *msg,
if (session->s_state == WG_STATE_RESPONDER &&
wg_timespec_timedout(&session->s_created, WG_REKEY_AFTER_TIME_RECV))
- dev->d_outq(session->s_peer, WG_PKT_INITIATION);
+ dev->d_outq(session->s_peer, WG_PKT_INITIATION, session->s_peer->p_id);
*s = session;
leave:
@@ -518,7 +574,7 @@ wg_device_tx_initiation(struct wg_device *dev, struct wg_msg_initiation *init,
if ((peer = wg_device_ref_peerid(dev, id)) == NULL)
return WG_ID;
- rw_enter_write(&peer->p_lock);
+ rw_enter_read(&peer->p_lock);
if (!wg_timespec_timedout(&peer->p_last_initiation, WG_REKEY_TIMEOUT))
ret_error(WG_HS_RATE);
@@ -567,14 +623,24 @@ wg_device_tx_initiation(struct wg_device *dev, struct wg_msg_initiation *init,
sizeof(init->mac2), offsetof(struct wg_msg_initiation, mac2),
sizeof(peer->p_cookie.cookie));
- peer->p_attempts++;
+ rw_exit_read(&peer->p_lock);
+
+ /* TODO lock? */
getnanotime(&peer->p_last_initiation);
+ peer->p_attempts++;
+
+ /* Attach session to peer */
session->s_state = WG_STATE_MADE_INITIATION;
wg_peer_attach_session(peer, session);
*s = session;
-leave:
+
+ /* Take reference to return to caller */
+ wg_session_ref(session);
wg_peer_put(peer);
+ return WG_OK;
+leave:
rw_exit_write(&peer->p_lock);
+ wg_peer_put(peer);
return ret;
}
@@ -582,27 +648,20 @@ enum wg_error
wg_device_tx_response(struct wg_device *dev, struct wg_msg_response *resp,
uint32_t id, struct wg_session **s)
{
- struct wg_peer *peer;
+ enum wg_error ret = WG_OK;
+
struct wg_handshake *hs;
struct wg_session *session;
- enum wg_error ret = WG_OK;
-
- if ((peer = wg_device_ref_peerid(dev, id)) == NULL)
+ if ((session = wg_device_ref_session(dev, id)) == NULL)
return WG_ID;
- rw_enter_read(&peer->p_lock);
-
- if ((session = SLIST_FIRST(&peer->p_sessions)) == NULL)
- ret_error(WG_ID);
-
- /* TODO lock session */
+ rw_enter_read(&session->s_peer->p_lock);
+ rw_enter_write(&session->s_lock);
if (session->s_state != WG_STATE_RECV_INITIATION)
ret_error(WG_STATE);
- wg_session_ref(session);
-
resp->type = WG_MSG_RESPONSE;
resp->sender = session->s_local_id;
resp->receiver = session->s_remote_id;
@@ -615,31 +674,35 @@ wg_device_tx_response(struct wg_device *dev, struct wg_msg_response *resp,
memcpy(resp->ephemeral, hs->h_local.pub.k, WG_KEY_SIZE);
wg_mix_dh(hs, hs->h_local.priv.k, hs->h_remote.k);
- wg_mix_dh(hs, hs->h_local.priv.k, peer->p_remote.k);
+ wg_mix_dh(hs, hs->h_local.priv.k, session->s_peer->p_remote.k);
- wg_mix_psk(hs, peer->p_shared.k);
+ wg_mix_psk(hs, session->s_peer->p_shared.k);
wg_handshake_encrypt(hs, resp->empty, NULL, 0);
wg_mix_hash(hs, resp->empty, WG_ENCRYPTED_SIZE(0));
- wg_hash2(hs->h_k, WG_MAC1, strlen(WG_MAC1), peer->p_remote.k, WG_KEY_SIZE);
+ wg_hash2(hs->h_k, WG_MAC1, strlen(WG_MAC1), session->s_peer->p_remote.k, WG_KEY_SIZE);
blake2s(resp->mac1, (void *)resp, hs->h_k, sizeof(resp->mac1),
offsetof(struct wg_msg_response, mac1), sizeof(hs->h_k));
memcpy(hs->h_mac, resp->mac1, sizeof(hs->h_mac));
- if (wg_timespec_timedout(&peer->p_cookie.time, WG_COOKIE_VALID_TIME))
+ if (wg_timespec_timedout(&session->s_peer->p_cookie.time, WG_COOKIE_VALID_TIME))
bzero(resp->mac2, WG_MAC_SIZE);
else
- blake2s(resp->mac2, (void *)resp, peer->p_cookie.cookie,
+ blake2s(resp->mac2, (void *)resp, session->s_peer->p_cookie.cookie,
sizeof(resp->mac2), offsetof(struct wg_msg_response, mac2),
- sizeof(peer->p_cookie.cookie));
+ sizeof(session->s_peer->p_cookie.cookie));
- session->s_state = WG_STATE_WAIT_CONFIRM;
+ session->s_state = WG_STATE_MADE_RESPONSE;
*s = session;
+ rw_exit_write(&session->s_lock);
+ rw_exit_read(&session->s_peer->p_lock);
+ return ret;
leave:
- wg_peer_put(peer);
- rw_exit_read(&peer->p_lock);
+ rw_exit_write(&session->s_lock);
+ rw_exit_read(&session->s_peer->p_lock);
+ wg_session_put(session);
return ret;
}
@@ -654,22 +717,12 @@ enum wg_error
wg_device_tx_transport(struct wg_device *dev, struct wg_msg_transport *msg,
size_t len, uint32_t id, struct wg_session **s)
{
-
- uint64_t counter = letoh64(msg->counter);
- struct wg_session *session;
- struct wg_peer *peer;
enum wg_error ret = WG_OK;
+ struct wg_session *session;
- if ((peer = wg_device_ref_peerid(dev, id)) == NULL)
+ if ((session = wg_device_ref_session(dev, id)) == NULL)
return WG_ID;
- rw_enter_read(&peer->p_lock);
-
- if ((session = SLIST_FIRST(&peer->p_sessions)) == NULL)
- ret_error(WG_ID);
-
- wg_session_ref(session);
-
rw_enter_read(&session->s_lock);
if (session->s_state != WG_STATE_INITIATOR &&
@@ -680,11 +733,9 @@ wg_device_tx_transport(struct wg_device *dev, struct wg_msg_transport *msg,
session->s_keyset.k_txcounter > WG_REJECT_AFTER_MESSAGES)
ret_error(WG_REJECT);
- counter = session->s_keyset.k_txcounter++;
-
msg->type = WG_MSG_TRANSPORT;
msg->receiver = session->s_remote_id;
- msg->counter = htole64(counter);
+ msg->counter = htole64(session->s_keyset.k_txcounter++);
chacha20poly1305_encrypt(msg->data, msg->data, len, NULL, 0, msg->counter,
session->s_keyset.k_txkey.k);
@@ -693,12 +744,11 @@ wg_device_tx_transport(struct wg_device *dev, struct wg_msg_transport *msg,
if ((session->s_state == WG_STATE_INITIATOR &&
wg_timespec_timedout(&session->s_created, WG_REKEY_AFTER_TIME)) ||
session->s_keyset.k_txcounter > WG_REKEY_AFTER_MESSAGES)
- ret_error(WG_REKEY);
+ dev->d_outq(session->s_peer, WG_PKT_INITIATION, session->s_peer->p_id);
*s = session;
leave:
- rw_exit(&session->s_lock);
- rw_exit_read(&peer->p_lock);
+ rw_exit_read(&session->s_lock);
return ret;
}