// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2025 Felix Fietkau */ #include #include "unetd.h" #include "sha512.h" static uint8_t salt[8]; static uint64_t nonce; struct token_hdr { uint8_t src[PEX_ID_LEN]; uint8_t salt[8]; uint64_t nonce; uint8_t hmac[SHA512_HASH_SIZE / 2]; }; static bool token_init(void) { static bool init_done; FILE *f; if (init_done) return true; f = fopen("/dev/urandom", "r"); if (!f) return false; init_done = fread(salt, sizeof(salt), 1, f) == 1; fclose(f); return init_done; } static bool token_verify_service(struct network *net, const char *name, struct network_host *local_host, struct network_host *target) { struct network_service *s; bool dest_found = false; bool src_found = false; s = vlist_find(&net->services, name, s, node); if (!s) return false; for (size_t i = 0; i < s->n_members; i++) { if (s->members[i] == local_host) src_found = true; if (s->members[i] == target) dest_found = true; } if (!src_found || !dest_found) return false; return true; } void *token_create(struct network *net, struct network_host *target, const char *service, struct blob_attr *info, size_t *len) { struct network_host *local_host = net->net_config.local_host; size_t data_len = blob_pad_len(info); uint8_t dh_key[CURVE25519_KEY_SIZE]; uint8_t hmac[SHA512_HASH_SIZE]; struct sha512_state s; struct token_hdr *hdr; const void *key; void *data; if (!local_host || !token_init() || target == local_host) return NULL; if (service && !token_verify_service(net, service, local_host, target)) return NULL; hdr = data = malloc(sizeof(*hdr) + data_len); data += sizeof(*hdr); memcpy(hdr->src, local_host->peer.key, sizeof(hdr->src)); memcpy(hdr->salt, salt, sizeof(hdr->salt)); hdr->nonce = nonce++; curve25519(dh_key, net->config.key, target->peer.key); sha512_init(&s); sha512_add(&s, dh_key, sizeof(dh_key)); sha512_add(&s, salt, sizeof(salt)); key = sha512_final_get(&s); memcpy(data, info, data_len); chacha20_encrypt_msg(data, data_len, &hdr->nonce, key); hmac_sha512(hmac, key, SHA512_HASH_SIZE, data, data_len); memcpy(hdr->hmac, hmac, sizeof(hdr->hmac)); *len = data_len + sizeof(*hdr); return hdr; } static bool token_decrypt(struct network *net, struct token_hdr *hdr, size_t len, struct network_host **host) { struct network_host *local_host = net->net_config.local_host; uint8_t dh_key[CURVE25519_KEY_SIZE]; uint8_t pubkey[WG_KEY_LEN] = {}; uint8_t hmac[SHA512_HASH_SIZE]; struct network_peer *peer; struct sha512_state s; const void *key; void *data; data = hdr + 1; memcpy(pubkey, hdr->src, sizeof(hdr->src)); peer = avl_find_ge_element(&net->peers.avl, pubkey, peer, node.avl); if (!peer || peer == &local_host->peer) return false; if (memcmp(peer->key, pubkey, sizeof(hdr->src)) != 0) return false; memcpy(pubkey, peer->key, sizeof(pubkey)); curve25519(dh_key, net->config.key, pubkey); sha512_init(&s); sha512_add(&s, dh_key, sizeof(dh_key)); sha512_add(&s, hdr->salt, sizeof(hdr->salt)); key = sha512_final_get(&s); hmac_sha512(hmac, key, SHA512_HASH_SIZE, data, len); if (memcmp(hdr->hmac, hmac, sizeof(hdr->hmac)) != 0) return false; chacha20_encrypt_msg(data, len, &hdr->nonce, key); *host = container_of(peer, struct network_host, peer); return true; } bool token_parse(struct blob_buf *buf, const char *token) { enum { TOKEN_ATTR_SERVICE, __TOKEN_ATTR_MAX, }; struct blobmsg_policy policy[__TOKEN_ATTR_MAX] = { [TOKEN_ATTR_SERVICE] = { "service", BLOBMSG_TYPE_STRING }, }; struct blob_attr *tb[__TOKEN_ATTR_MAX], *cur; struct network_host *host; struct token_hdr *hdr; struct network *net; bool ret = false; size_t len; void *data; len = B64_DECODE_LEN(strlen(token)); hdr = malloc(len); len = b64_decode(token, hdr, len); if (len <= sizeof(*hdr) + sizeof(struct blob_attr)) goto out; data = hdr + 1; len -= sizeof(*hdr); avl_for_each_element(&networks, net, node) { struct network_host *local_host = net->net_config.local_host; if (!local_host) continue; ret = token_decrypt(net, hdr, len, &host); if (!ret) continue; blobmsg_add_string(buf, "network", network_name(net)); blobmsg_add_string(buf, "host", network_host_name(host)); if (blob_pad_len(data) != len) { ret = false; break; } blobmsg_parse_attr(policy, __TOKEN_ATTR_MAX, tb, data); cur = tb[TOKEN_ATTR_SERVICE]; if (cur && !token_verify_service(net, blobmsg_get_string(cur), local_host, host)) ret = false; break; } if (!ret) goto out; blob_put_raw(buf, blobmsg_data(data), blobmsg_len(data)); out: free(hdr); return ret; }