mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-10-27 12:45:35 +08:00
1237 lines
41 KiB
C
1237 lines
41 KiB
C
/**
|
|
* Copyright (c) 2020 Paul-Louis Ageneau
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include "stun.h"
|
|
#include "base64.h"
|
|
#include "const_time.h"
|
|
#include "crc32.h"
|
|
#include "juice.h"
|
|
#include "log.h"
|
|
#include "udp.h"
|
|
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define STUN_MAGIC 0x2112A442
|
|
#define STUN_FINGERPRINT_XOR 0x5354554E // "STUN"
|
|
#define STUN_ATTR_SIZE sizeof(struct stun_attr)
|
|
|
|
// STUN_MAX_PASSWORD_LEN > HASH_SHA256_SIZE > HASH_MD5_SIZE
|
|
#define MAX_HMAC_KEY_LEN STUN_MAX_PASSWORD_LEN
|
|
|
|
#define MAX_HMAC_INPUT_LEN (STUN_MAX_USERNAME_LEN + STUN_MAX_REALM_LEN + STUN_MAX_PASSWORD_LEN + 2)
|
|
|
|
#define MAX_USERHASH_INPUT_LEN (STUN_MAX_USERNAME_LEN + STUN_MAX_REALM_LEN + 1)
|
|
|
|
#ifndef htonll
|
|
#define htonll(x) \
|
|
((uint64_t)(((uint64_t)htonl((uint32_t)(x))) << 32) | (uint64_t)htonl((uint32_t)((x) >> 32)))
|
|
#endif
|
|
#ifndef ntohll
|
|
#define ntohll(x) htonll(x)
|
|
#endif
|
|
|
|
static size_t align32(size_t len) {
|
|
while (len & 0x03)
|
|
++len;
|
|
return len;
|
|
}
|
|
|
|
static size_t generate_hmac_key(const stun_message_t *msg, const char *password, void *key) {
|
|
if (*msg->credentials.realm != '\0') {
|
|
// long-term credentials
|
|
if (*msg->credentials.username == '\0')
|
|
JLOG_WARN("Generating HMAC key for long-term credentials with empty STUN username");
|
|
|
|
char input[MAX_HMAC_INPUT_LEN];
|
|
int input_len = snprintf(input, MAX_HMAC_INPUT_LEN, "%s:%s:%s", msg->credentials.username,
|
|
msg->credentials.realm, password ? password : "");
|
|
if (input_len < 0)
|
|
return 0;
|
|
|
|
if (input_len >= MAX_HMAC_INPUT_LEN)
|
|
input_len = MAX_HMAC_INPUT_LEN - 1;
|
|
|
|
switch (msg->credentials.password_algorithm) {
|
|
case STUN_PASSWORD_ALGORITHM_SHA256:
|
|
hash_sha256(input, input_len, key);
|
|
return HASH_SHA256_SIZE;
|
|
default:
|
|
hash_md5(input, input_len, key);
|
|
return HASH_MD5_SIZE;
|
|
}
|
|
} else {
|
|
// short-term credentials
|
|
int key_len = snprintf((char *)key, MAX_HMAC_KEY_LEN, "%s", password ? password : "");
|
|
if (key_len < 0)
|
|
return 0;
|
|
|
|
if (key_len >= MAX_HMAC_KEY_LEN)
|
|
key_len = MAX_HMAC_KEY_LEN - 1;
|
|
|
|
return key_len;
|
|
}
|
|
}
|
|
|
|
static size_t generate_password_algorithms_attr(uint8_t *attr) {
|
|
// attr size must be at least STUN_PASSWORD_ALGORITHMS_ATTR_MAX_SIZE
|
|
struct stun_value_password_algorithm *pwa = (struct stun_value_password_algorithm *)attr;
|
|
pwa->algorithm = htons(STUN_PASSWORD_ALGORITHM_SHA256);
|
|
pwa->parameters_length = 0;
|
|
++pwa;
|
|
pwa->algorithm = htons(STUN_PASSWORD_ALGORITHM_MD5);
|
|
pwa->parameters_length = 0;
|
|
++pwa;
|
|
return (uint8_t *)pwa - attr;
|
|
}
|
|
|
|
int stun_write(void *buf, size_t size, const stun_message_t *msg, const char *password) {
|
|
uint8_t *begin = buf;
|
|
uint8_t *pos = begin;
|
|
uint8_t *end = begin + size;
|
|
|
|
JLOG_VERBOSE("Writing STUN message, class=0x%X, method=0x%X", (unsigned int)msg->msg_class,
|
|
(unsigned int)msg->msg_method);
|
|
|
|
size_t len =
|
|
stun_write_header(pos, end - pos, msg->msg_class, msg->msg_method, msg->transaction_id);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
uint8_t *attr_begin = pos;
|
|
|
|
if (msg->error_code) {
|
|
const char *reason = stun_get_error_reason(msg->error_code);
|
|
char buffer[sizeof(struct stun_value_error_code) + STUN_MAX_ERROR_REASON_LEN + 1];
|
|
struct stun_value_error_code *error = (struct stun_value_error_code *)buffer;
|
|
memset(error, 0, sizeof(*error));
|
|
error->code_class = (msg->error_code / 100) & 0x07;
|
|
error->code_number = msg->error_code % 100;
|
|
strcpy((char *)error->reason, reason);
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_ERROR_CODE, error,
|
|
sizeof(struct stun_value_error_code) + strlen(reason));
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
if (msg->mapped.len) {
|
|
JLOG_VERBOSE("Writing XOR mapped address");
|
|
uint8_t value[32];
|
|
uint8_t mask[16];
|
|
*((uint32_t *)mask) = htonl(STUN_MAGIC);
|
|
memcpy(mask + 4, msg->transaction_id, 12);
|
|
int value_len = stun_write_value_mapped_address(
|
|
value, 32, (const struct sockaddr *)&msg->mapped.addr, msg->mapped.len, mask);
|
|
if (value_len > 0) {
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_XOR_MAPPED_ADDRESS, value, value_len);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
}
|
|
if (msg->priority) {
|
|
uint32_t priority = htonl(msg->priority);
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_PRIORITY, &priority, 4);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
if (msg->use_candidate) {
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_USE_CANDIDATE, NULL, 0);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
if (msg->ice_controlling) {
|
|
uint64_t ice_controlling = htonll(msg->ice_controlling);
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_ICE_CONTROLLING, &ice_controlling, 8);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
if (msg->ice_controlled) {
|
|
uint64_t ice_controlled = htonll(msg->ice_controlled);
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_ICE_CONTROLLED, &ice_controlled, 8);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
if (msg->channel_number) {
|
|
struct stun_value_channel_number channel_number;
|
|
memset(&channel_number, 0, sizeof(channel_number));
|
|
channel_number.channel_number = htons(msg->channel_number);
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_CHANNEL_NUMBER, &channel_number,
|
|
sizeof(channel_number));
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
if (msg->lifetime_set || msg->lifetime) {
|
|
uint32_t lifetime = htonl(msg->lifetime);
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_LIFETIME, &lifetime, 4);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
if (msg->peer.len) {
|
|
JLOG_VERBOSE("Writing XOR peer address");
|
|
uint8_t value[32];
|
|
uint8_t mask[16];
|
|
*((uint32_t *)mask) = htonl(STUN_MAGIC);
|
|
memcpy(mask + 4, msg->transaction_id, 12);
|
|
int value_len = stun_write_value_mapped_address(
|
|
value, 32, (const struct sockaddr *)&msg->peer.addr, msg->peer.len, mask);
|
|
if (value_len > 0) {
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_XOR_PEER_ADDRESS, value, value_len);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
}
|
|
if (msg->relayed.len) {
|
|
JLOG_VERBOSE("Writing XOR relay address");
|
|
uint8_t value[32];
|
|
uint8_t mask[16];
|
|
*((uint32_t *)mask) = htonl(STUN_MAGIC);
|
|
memcpy(mask + 4, msg->transaction_id, 12);
|
|
int value_len = stun_write_value_mapped_address(
|
|
value, 32, (const struct sockaddr *)&msg->relayed.addr, msg->relayed.len, mask);
|
|
if (value_len > 0) {
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_XOR_RELAYED_ADDRESS, value, value_len);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
}
|
|
if (msg->data) {
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_DATA, (const uint8_t *)msg->data,
|
|
msg->data_size);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
if (msg->even_port) {
|
|
struct stun_value_even_port even_port;
|
|
memset(&even_port, 0, sizeof(even_port));
|
|
if (msg->next_port)
|
|
even_port.r |= 0x80;
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_CHANNEL_NUMBER, &even_port,
|
|
sizeof(even_port));
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
if (msg->requested_transport) {
|
|
struct stun_value_requested_transport requested_transport;
|
|
memset(&requested_transport, 0, sizeof(requested_transport));
|
|
requested_transport.protocol = 17;
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_REQUESTED_TRANSPORT, &requested_transport,
|
|
sizeof(requested_transport));
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
if (msg->dont_fragment) {
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_DONT_FRAGMENT, NULL, 0);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
if (msg->reservation_token) {
|
|
uint64_t reservation_token = htonll(msg->reservation_token);
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_RESERVATION_TOKEN, &reservation_token, 8);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
|
|
const char *software = "libjuice";
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_SOFTWARE, software, strlen(software));
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
|
|
if (msg->msg_class == STUN_CLASS_REQUEST) {
|
|
if (msg->credentials.enable_userhash) {
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_USERHASH, msg->credentials.userhash,
|
|
USERHASH_SIZE);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
|
|
} else if (*msg->credentials.username != '\0') {
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_USERNAME, msg->credentials.username,
|
|
strlen(msg->credentials.username));
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
}
|
|
if (msg->msg_class == STUN_CLASS_REQUEST ||
|
|
(msg->msg_class == STUN_CLASS_RESP_ERROR &&
|
|
(msg->error_code == 401 || msg->error_code == 438) // Unauthenticated or Stale Nonce
|
|
)) {
|
|
if (*msg->credentials.realm != '\0') {
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_REALM, msg->credentials.realm,
|
|
strlen(msg->credentials.realm));
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
if (*msg->credentials.nonce != '\0') {
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_NONCE, msg->credentials.nonce,
|
|
strlen(msg->credentials.nonce));
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
|
|
if (msg->credentials.password_algorithm > 0) {
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_PASSWORD_ALGORITHMS,
|
|
msg->credentials.password_algorithms_value,
|
|
msg->credentials.password_algorithms_value_size);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
|
|
} else if (msg->msg_class != STUN_CLASS_REQUEST) {
|
|
uint8_t pwa_value[STUN_MAX_PASSWORD_ALGORITHMS_VALUE_SIZE];
|
|
size_t pwa_size = generate_password_algorithms_attr(pwa_value);
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_PASSWORD_ALGORITHMS, pwa_value,
|
|
pwa_size);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
|
|
if (msg->msg_class == STUN_CLASS_REQUEST &&
|
|
msg->credentials.password_algorithm != STUN_PASSWORD_ALGORITHM_UNSET) {
|
|
struct stun_value_password_algorithm pwa;
|
|
pwa.algorithm = htons(msg->credentials.password_algorithm);
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_PASSWORD_ALGORITHM, &pwa,
|
|
sizeof(pwa));
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
}
|
|
}
|
|
if (msg->msg_class != STUN_CLASS_INDICATION && password) {
|
|
uint8_t key[MAX_HMAC_KEY_LEN];
|
|
size_t key_len = generate_hmac_key(msg, password, key);
|
|
|
|
size_t tmp_length = pos - attr_begin + STUN_ATTR_SIZE + HMAC_SHA1_SIZE;
|
|
stun_update_header_length(begin, tmp_length);
|
|
|
|
uint8_t hmac[HMAC_SHA1_SIZE];
|
|
hmac_sha1(begin, pos - begin, key, key_len, hmac);
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_MESSAGE_INTEGRITY, hmac, HMAC_SHA1_SIZE);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
|
|
// According to RFC 8489, the agent must include both MESSAGE-INTEGRITY and
|
|
// MESSAGE-INTEGRITY-SHA256. However, this makes legacy agents and servers fail with error
|
|
// 420 Unknown Attribute. Therefore, unless the password algorithm SHA-256 is enabled, only
|
|
// MESSAGE-INTEGRITY is included in the message for compatibility.
|
|
if (msg->credentials.password_algorithm != STUN_PASSWORD_ALGORITHM_UNSET) {
|
|
// If the response contains a PASSWORD-ALGORITHMS attribute, all the
|
|
// subsequent requests MUST be authenticated using MESSAGE-INTEGRITY-
|
|
// SHA256 only.
|
|
size_t tmp_length = pos - attr_begin + STUN_ATTR_SIZE + HMAC_SHA256_SIZE;
|
|
stun_update_header_length(begin, tmp_length);
|
|
|
|
uint8_t hmac[HMAC_SHA256_SIZE];
|
|
hmac_sha256(begin, pos - begin, key, key_len, hmac);
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_MESSAGE_INTEGRITY_SHA256, hmac,
|
|
HMAC_SHA256_SIZE);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
}
|
|
}
|
|
|
|
size_t length = pos - attr_begin + STUN_ATTR_SIZE + 4;
|
|
if (length & 0x03) {
|
|
JLOG_ERROR("Written STUN message length is not multiple of 4, length=%zu", length);
|
|
return -1;
|
|
}
|
|
stun_update_header_length(begin, length);
|
|
|
|
uint32_t fingerprint = htonl(CRC32(buf, pos - begin) ^ STUN_FINGERPRINT_XOR);
|
|
len = stun_write_attr(pos, end - pos, STUN_ATTR_FINGERPRINT, &fingerprint, 4);
|
|
if (len <= 0)
|
|
goto overflow;
|
|
pos += len;
|
|
|
|
return (int)(pos - begin);
|
|
|
|
overflow:
|
|
JLOG_ERROR("Not enough space in buffer for STUN message, size=%zu", size);
|
|
return -1;
|
|
}
|
|
|
|
int stun_write_header(void *buf, size_t size, stun_class_t class, stun_method_t method,
|
|
const uint8_t *transaction_id) {
|
|
if (size < sizeof(struct stun_header))
|
|
return -1;
|
|
|
|
uint16_t type = (uint16_t) class | (uint16_t)method;
|
|
|
|
struct stun_header *header = buf;
|
|
header->type = htons(type);
|
|
header->length = htons(0);
|
|
header->magic = htonl(STUN_MAGIC);
|
|
memcpy(header->transaction_id, transaction_id, STUN_TRANSACTION_ID_SIZE);
|
|
|
|
return sizeof(struct stun_header);
|
|
}
|
|
|
|
size_t stun_update_header_length(void *buf, size_t length) {
|
|
struct stun_header *header = buf;
|
|
size_t previous = ntohs(header->length);
|
|
header->length = htons((uint16_t)length);
|
|
return previous;
|
|
}
|
|
|
|
int stun_write_attr(void *buf, size_t size, uint16_t type, const void *value, size_t length) {
|
|
JLOG_VERBOSE("Writing STUN attribute type 0x%X, length=%zu", (unsigned int)type, length);
|
|
|
|
if (size < sizeof(struct stun_attr) + length)
|
|
return -1;
|
|
|
|
struct stun_attr *attr = buf;
|
|
attr->type = htons(type);
|
|
attr->length = htons((uint16_t)length);
|
|
|
|
if (length > 0) {
|
|
memcpy(attr->value, value, length);
|
|
|
|
// Pad to align on 4 bytes
|
|
while (length & 0x03)
|
|
attr->value[length++] = 0;
|
|
}
|
|
|
|
return (int)(sizeof(struct stun_attr) + length);
|
|
}
|
|
|
|
int stun_write_value_mapped_address(void *buf, size_t size, const struct sockaddr *addr,
|
|
socklen_t addrlen, const uint8_t *mask) {
|
|
if (size < sizeof(struct stun_value_mapped_address))
|
|
return -1;
|
|
|
|
struct stun_value_mapped_address *value = buf;
|
|
value->padding = 0;
|
|
switch (addr->sa_family) {
|
|
case AF_INET: {
|
|
value->family = STUN_ADDRESS_FAMILY_IPV4;
|
|
if (size < sizeof(struct stun_value_mapped_address) + 4)
|
|
return -1;
|
|
if (addrlen < (socklen_t)sizeof(struct sockaddr_in))
|
|
return -1;
|
|
JLOG_VERBOSE("Writing IPv4 address");
|
|
const struct sockaddr_in *sin = (const struct sockaddr_in *)addr;
|
|
value->port = sin->sin_port ^ *((uint16_t *)mask);
|
|
const uint8_t *bytes = (const uint8_t *)&sin->sin_addr;
|
|
for (int i = 0; i < 4; ++i)
|
|
value->address[i] = bytes[i] ^ mask[i];
|
|
return sizeof(struct stun_value_mapped_address) + 4;
|
|
}
|
|
case AF_INET6: {
|
|
value->family = STUN_ADDRESS_FAMILY_IPV6;
|
|
if (size < sizeof(struct stun_value_mapped_address) + 16)
|
|
return -1;
|
|
if (addrlen < (socklen_t)sizeof(struct sockaddr_in6))
|
|
return -1;
|
|
JLOG_VERBOSE("Writing IPv6 address");
|
|
const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)addr;
|
|
value->port = sin6->sin6_port ^ *((uint16_t *)mask);
|
|
const uint8_t *bytes = (const uint8_t *)&sin6->sin6_addr;
|
|
for (int i = 0; i < 16; ++i)
|
|
value->address[i] = bytes[i] ^ mask[i];
|
|
return sizeof(struct stun_value_mapped_address) + 16;
|
|
}
|
|
default: {
|
|
JLOG_DEBUG("Unknown address family %u", (unsigned int)addr->sa_family);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool is_stun_datagram(const void *data, size_t size) {
|
|
// RFC 8489: The most significant 2 bits of every STUN message MUST be zeroes. This can be used
|
|
// to differentiate STUN packets from other protocols when STUN is multiplexed with other
|
|
// protocols on the same port.
|
|
if (!size || *((uint8_t *)data) & 0xC0) {
|
|
JLOG_VERBOSE("Not a STUN message: first 2 bits are not zeroes");
|
|
return false;
|
|
}
|
|
|
|
if (size < sizeof(struct stun_header)) {
|
|
JLOG_VERBOSE("Not a STUN message: message too short, size=%zu", size);
|
|
return false;
|
|
}
|
|
|
|
const struct stun_header *header = data;
|
|
if (ntohl(header->magic) != STUN_MAGIC) {
|
|
JLOG_VERBOSE("Not a STUN message: magic number invalid");
|
|
return false;
|
|
}
|
|
|
|
// RFC 8489: The message length MUST contain the size of the message in bytes, not including the
|
|
// 20-byte STUN header. Since all STUN attributes are padded to a multiple of 4 bytes, the last
|
|
// 2 bits of this field are always zero. This provides another way to distinguish STUN packets
|
|
// from packets of other protocols.
|
|
const size_t length = ntohs(header->length);
|
|
if (length & 0x03) {
|
|
JLOG_VERBOSE("Not a STUN message: invalid length %zu not multiple of 4", length);
|
|
return false;
|
|
}
|
|
if (size != sizeof(struct stun_header) + length) {
|
|
JLOG_VERBOSE("Not a STUN message: invalid length %zu while expecting %zu", length,
|
|
size - sizeof(struct stun_header));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int stun_read(void *data, size_t size, stun_message_t *msg) {
|
|
memset(msg, 0, sizeof(*msg));
|
|
|
|
if (size < sizeof(struct stun_header)) {
|
|
JLOG_ERROR("STUN message too short, size=%zu", size);
|
|
return -1;
|
|
}
|
|
|
|
const struct stun_header *header = data;
|
|
const size_t length = ntohs(header->length);
|
|
if (size < sizeof(struct stun_header) + length) {
|
|
JLOG_ERROR("Invalid STUN message length, length=%zu, available=%zu", length,
|
|
size - sizeof(struct stun_header));
|
|
return -1;
|
|
}
|
|
|
|
uint16_t type = ntohs(header->type);
|
|
msg->msg_class = (stun_class_t)(type & STUN_CLASS_MASK);
|
|
msg->msg_method = (stun_method_t)(type & ~STUN_CLASS_MASK);
|
|
memcpy(msg->transaction_id, header->transaction_id, STUN_TRANSACTION_ID_SIZE);
|
|
JLOG_VERBOSE("Reading STUN message, class=0x%X, method=0x%X", (unsigned int)msg->msg_class,
|
|
(unsigned int)msg->msg_method);
|
|
|
|
uint32_t security_bits = 0;
|
|
|
|
uint8_t *begin = data;
|
|
uint8_t *attr_begin = begin + sizeof(struct stun_header);
|
|
uint8_t *end = attr_begin + length;
|
|
const uint8_t *pos = attr_begin;
|
|
while (pos < end) {
|
|
int ret = stun_read_attr(pos, end - pos, msg, begin, attr_begin, &security_bits);
|
|
if (ret <= 0) {
|
|
JLOG_DEBUG("Reading STUN attribute failed");
|
|
return -1;
|
|
}
|
|
pos += ret;
|
|
}
|
|
|
|
JLOG_VERBOSE("Finished reading STUN attributes");
|
|
|
|
stun_credentials_t *credentials = &msg->credentials;
|
|
|
|
// RFC 8489: If the response is an error response with an error code of 401 (Unauthenticated) or
|
|
// 438 (Stale Nonce), the client MUST test if the NONCE attribute value starts with the "nonce
|
|
// cookie". If so and the "nonce cookie" has the STUN Security Feature "Password algorithms"
|
|
// bit set to 1 but no PASSWORD-ALGORITHMS attribute is present, then the client MUST NOT retry
|
|
// the request with a new transaction. See
|
|
// https://www.rfc-editor.org/rfc/rfc8489.html#section-9.2.5
|
|
if (msg->msg_class == STUN_CLASS_RESP_ERROR &&
|
|
(msg->error_code == 401 || msg->error_code == 438) &&
|
|
security_bits & STUN_SECURITY_PASSWORD_ALGORITHMS_BIT &&
|
|
credentials->password_algorithms_value_size == 0) {
|
|
JLOG_INFO("STUN Security Feature \"Password algorithms\" bit is set in %u error response "
|
|
"but the corresponding attribute is missing",
|
|
msg->error_code);
|
|
msg->error_code = STUN_ERROR_INTERNAL_VALIDATION_FAILED; // so the agent will give up
|
|
}
|
|
|
|
// RFC 8489: If the request contains neither the PASSWORD-ALGORITHMS nor the
|
|
// PASSWORD-ALGORITHM algorithm, then the request is processed as though
|
|
// PASSWORD-ALGORITHM were MD5.
|
|
// Otherwise, unless (1) PASSWORD-ALGORITHM and PASSWORD-ALGORITHMS are both
|
|
// present, (2) PASSWORD-ALGORITHMS matches the value sent in the response that sent
|
|
// this NONCE, and (3) PASSWORD-ALGORITHM matches one of the entries in
|
|
// PASSWORD-ALGORITHMS, the server MUST generate an error response with an error code of
|
|
// 400 (Bad Request). See https://www.rfc-editor.org/rfc/rfc8489.html#section-9.2.4
|
|
if (!STUN_IS_RESPONSE(msg->msg_class)) {
|
|
if (credentials->password_algorithms_value_size == 0 &&
|
|
credentials->password_algorithm == STUN_PASSWORD_ALGORITHM_UNSET) {
|
|
credentials->password_algorithm = STUN_PASSWORD_ALGORITHM_MD5;
|
|
|
|
} else if (credentials->password_algorithm == STUN_PASSWORD_ALGORITHM_UNSET) {
|
|
JLOG_INFO("No suitable password algorithm in STUN request");
|
|
msg->error_code = STUN_ERROR_INTERNAL_VALIDATION_FAILED;
|
|
|
|
} else if (credentials->password_algorithms_value_size == 0) {
|
|
JLOG_INFO("Missing password algorithms list in STUN request");
|
|
msg->error_code = STUN_ERROR_INTERNAL_VALIDATION_FAILED;
|
|
|
|
} else {
|
|
uint8_t pwa_value[STUN_MAX_PASSWORD_ALGORITHMS_VALUE_SIZE];
|
|
size_t pwa_size = generate_password_algorithms_attr(pwa_value);
|
|
if (pwa_size != credentials->password_algorithms_value_size ||
|
|
memcmp(credentials->password_algorithms_value, pwa_value, pwa_size) != 0) {
|
|
JLOG_INFO("Password algorithms list is invalid in STUN request");
|
|
msg->error_code = STUN_ERROR_INTERNAL_VALIDATION_FAILED;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (security_bits & STUN_SECURITY_USERNAME_ANONYMITY_BIT) {
|
|
JLOG_DEBUG("Remote agent supports user anonymity");
|
|
credentials->enable_userhash = true;
|
|
}
|
|
|
|
return (int)(sizeof(struct stun_header) + length);
|
|
}
|
|
|
|
int stun_read_attr(const void *data, size_t size, stun_message_t *msg, uint8_t *begin,
|
|
uint8_t *attr_begin, uint32_t *security_bits) {
|
|
// RFC 8489: When present, the FINGERPRINT attribute MUST be the last attribute in the
|
|
// message and thus will appear after MESSAGE-INTEGRITY and MESSAGE-INTEGRITY-SHA256.
|
|
if (msg->has_fingerprint) {
|
|
JLOG_DEBUG("Invalid STUN attribute after fingerprint");
|
|
return -1;
|
|
}
|
|
|
|
if (size < sizeof(struct stun_attr)) {
|
|
JLOG_VERBOSE("STUN attribute too short");
|
|
return -1;
|
|
}
|
|
|
|
const struct stun_attr *attr = data;
|
|
size_t length = ntohs(attr->length);
|
|
stun_attr_type_t type = (stun_attr_type_t)ntohs(attr->type);
|
|
JLOG_VERBOSE("Reading attribute 0x%X, length=%zu", (unsigned int)type, length);
|
|
if (size < sizeof(struct stun_attr) + length) {
|
|
JLOG_DEBUG("STUN attribute length invalid, length=%zu, available=%zu", length,
|
|
size - sizeof(struct stun_attr));
|
|
return -1;
|
|
}
|
|
|
|
// RFC 8489: Note that agents MUST ignore all attributes that follow MESSAGE-INTEGRITY, with
|
|
// the exception of the MESSAGE-INTEGRITY-SHA256 and FINGERPRINT attributes.
|
|
if (msg->has_integrity && type != STUN_ATTR_MESSAGE_INTEGRITY &&
|
|
type != STUN_ATTR_MESSAGE_INTEGRITY_SHA256 && type != STUN_ATTR_FINGERPRINT) {
|
|
JLOG_DEBUG("Ignoring STUN attribute 0x%X after message integrity", (unsigned int)type);
|
|
while (length & 0x03)
|
|
++length; // attributes are aligned on 4 bytes
|
|
return (int)(sizeof(struct stun_attr) + length);
|
|
}
|
|
|
|
switch (type) {
|
|
case STUN_ATTR_MAPPED_ADDRESS: {
|
|
JLOG_VERBOSE("Reading mapped address");
|
|
uint8_t zero_mask[16] = {0};
|
|
if (stun_read_value_mapped_address(attr->value, length, &msg->mapped, zero_mask) < 0)
|
|
return -1;
|
|
break;
|
|
}
|
|
case STUN_ATTR_XOR_MAPPED_ADDRESS: {
|
|
JLOG_VERBOSE("Reading XOR mapped address");
|
|
uint8_t mask[16];
|
|
*((uint32_t *)mask) = htonl(STUN_MAGIC);
|
|
memcpy(mask + 4, msg->transaction_id, 12);
|
|
if (stun_read_value_mapped_address(attr->value, length, &msg->mapped, mask) < 0)
|
|
return -1;
|
|
break;
|
|
}
|
|
case STUN_ATTR_ALTERNATE_SERVER: {
|
|
JLOG_VERBOSE("Reading alternate server");
|
|
uint8_t zero_mask[16] = {0};
|
|
if (stun_read_value_mapped_address(attr->value, length, &msg->alternate_server, zero_mask) <
|
|
0)
|
|
return -1;
|
|
break;
|
|
}
|
|
case STUN_ATTR_ERROR_CODE: {
|
|
JLOG_VERBOSE("Reading error code");
|
|
if (length < sizeof(struct stun_value_error_code)) {
|
|
JLOG_DEBUG("STUN error code value too short, length=%zu", length);
|
|
return -1;
|
|
}
|
|
const struct stun_value_error_code *error =
|
|
(const struct stun_value_error_code *)attr->value;
|
|
msg->error_code = (error->code_class & 0x07) * 100 + error->code_number;
|
|
|
|
if (msg->error_code == 401 || msg->error_code == 438) { // Unauthenticated or Stale Nonce
|
|
JLOG_DEBUG("Got STUN error code %u", msg->error_code);
|
|
|
|
} else if (JLOG_INFO_ENABLED) {
|
|
size_t reason_length = length - sizeof(struct stun_value_error_code);
|
|
if (reason_length >= STUN_MAX_ERROR_REASON_LEN)
|
|
reason_length = STUN_MAX_ERROR_REASON_LEN - 1;
|
|
|
|
char buffer[STUN_MAX_ERROR_REASON_LEN];
|
|
memcpy(buffer, (const char *)error->reason, reason_length);
|
|
buffer[reason_length] = '\0';
|
|
|
|
JLOG_INFO("Got STUN error code %u, reason \"%s\"", msg->error_code, buffer);
|
|
}
|
|
break;
|
|
}
|
|
case STUN_ATTR_UNKNOWN_ATTRIBUTES: {
|
|
JLOG_VERBOSE("Reading STUN unknown attributes");
|
|
const uint16_t *attributes = (const uint16_t *)attr->value;
|
|
for (int i = 0; i < (int)ntohs(attr->length) / 2; ++i) {
|
|
stun_attr_type_t type = (stun_attr_type_t)ntohs(attributes[i]);
|
|
JLOG_INFO("Got unknown attribute response for attribute 0x%X", (unsigned int)type);
|
|
}
|
|
break;
|
|
}
|
|
case STUN_ATTR_USERNAME: {
|
|
JLOG_VERBOSE("Reading username");
|
|
if (length + 1 > STUN_MAX_USERNAME_LEN) {
|
|
JLOG_WARN("STUN username attribute value too long, length=%zu", length);
|
|
return -1;
|
|
}
|
|
memcpy(msg->credentials.username, (const char *)attr->value, length);
|
|
msg->credentials.username[length] = '\0';
|
|
JLOG_VERBOSE("Got username: %s", msg->credentials.username);
|
|
break;
|
|
}
|
|
case STUN_ATTR_MESSAGE_INTEGRITY: {
|
|
JLOG_VERBOSE("Reading message integrity");
|
|
if (length != HMAC_SHA1_SIZE) {
|
|
JLOG_DEBUG("STUN message integrity length invalid, length=%zu", length);
|
|
return -1;
|
|
}
|
|
msg->has_integrity = true;
|
|
break;
|
|
}
|
|
case STUN_ATTR_MESSAGE_INTEGRITY_SHA256: {
|
|
JLOG_VERBOSE("Reading message integrity SHA256");
|
|
if (length != HMAC_SHA256_SIZE) {
|
|
JLOG_DEBUG("STUN message integrity SHA256 length invalid, length=%zu", length);
|
|
return -1;
|
|
}
|
|
msg->has_integrity = true;
|
|
break;
|
|
}
|
|
case STUN_ATTR_FINGERPRINT: {
|
|
JLOG_VERBOSE("Reading fingerprint");
|
|
if (length != 4) {
|
|
JLOG_DEBUG("STUN fingerprint length invalid, length=%zu", length);
|
|
return -1;
|
|
}
|
|
size_t tmp_length = (uint8_t *)data - attr_begin + STUN_ATTR_SIZE + 4;
|
|
size_t prev_length = stun_update_header_length(begin, tmp_length);
|
|
uint32_t expected = CRC32(begin, (uint8_t *)data - begin) ^ STUN_FINGERPRINT_XOR;
|
|
stun_update_header_length(begin, prev_length);
|
|
|
|
uint32_t fingerprint = ntohl(*((uint32_t *)attr->value));
|
|
if (fingerprint != expected) {
|
|
JLOG_ERROR("STUN fingerprint check failed, expected=%lX, actual=%lX",
|
|
(unsigned long)expected, (unsigned long)fingerprint);
|
|
return -1;
|
|
}
|
|
JLOG_VERBOSE("STUN fingerprint check succeeded");
|
|
msg->has_fingerprint = true;
|
|
break;
|
|
}
|
|
case STUN_ATTR_REALM: {
|
|
JLOG_VERBOSE("Reading realm");
|
|
if (length + 1 > STUN_MAX_REALM_LEN) {
|
|
JLOG_WARN("STUN realm attribute value too long, length=%zu", length);
|
|
return -1;
|
|
}
|
|
memcpy(msg->credentials.realm, (const char *)attr->value, length);
|
|
msg->credentials.realm[length] = '\0';
|
|
JLOG_VERBOSE("Got realm: %s", msg->credentials.realm);
|
|
break;
|
|
}
|
|
case STUN_ATTR_NONCE: {
|
|
JLOG_VERBOSE("Reading nonce");
|
|
if (length + 1 > STUN_MAX_NONCE_LEN) {
|
|
JLOG_WARN("STUN nonce attribute value too long, length=%zu", length);
|
|
return -1;
|
|
}
|
|
memcpy(msg->credentials.nonce, (const char *)attr->value, length);
|
|
msg->credentials.nonce[length] = '\0';
|
|
JLOG_VERBOSE("Got nonce: %s", msg->credentials.nonce);
|
|
|
|
// If the nonce of a response starts with the nonce cookie, decode the Security Feature bits
|
|
// See https://www.rfc-editor.org/rfc/rfc8489.html#section-9.2
|
|
if (STUN_IS_RESPONSE(msg->msg_class) &&
|
|
strlen(msg->credentials.nonce) > STUN_NONCE_COOKIE_LEN + 4 &&
|
|
strncmp(msg->credentials.nonce, STUN_NONCE_COOKIE, STUN_NONCE_COOKIE_LEN) == 0) {
|
|
char encoded_security_bits[5];
|
|
memcpy(encoded_security_bits, msg->credentials.nonce + STUN_NONCE_COOKIE_LEN, 4);
|
|
encoded_security_bits[4] = '\0';
|
|
|
|
uint8_t bytes[4];
|
|
bytes[0] = 0;
|
|
int len = BASE64_DECODE(encoded_security_bits, bytes + 1, 3);
|
|
if (len == 3) {
|
|
*security_bits = ntohl(*((uint32_t *)bytes));
|
|
JLOG_VERBOSE("Nonce has cookie, Security Feature bits are 0x%lX",
|
|
(unsigned long)*security_bits);
|
|
} else {
|
|
JLOG_WARN("Nonce has cookie, but the encoded Security Feature bits field \"%s\" is "
|
|
"invalid",
|
|
encoded_security_bits);
|
|
security_bits = 0;
|
|
}
|
|
} else if (msg->msg_class == STUN_CLASS_RESP_ERROR) {
|
|
JLOG_DEBUG("Remote agent does not support RFC 8489");
|
|
}
|
|
break;
|
|
}
|
|
case STUN_ATTR_PASSWORD_ALGORITHM: {
|
|
JLOG_VERBOSE("Reading password algorithm");
|
|
if (length < sizeof(struct stun_value_password_algorithm)) {
|
|
JLOG_WARN("STUN password algorithm value too short, length=%zu", length);
|
|
return -1;
|
|
}
|
|
if (!STUN_IS_RESPONSE(msg->msg_class)) {
|
|
const struct stun_value_password_algorithm *pwa =
|
|
(const struct stun_value_password_algorithm *)attr->value;
|
|
stun_password_algorithm_t algorithm = ntohs(pwa->algorithm);
|
|
if (algorithm == STUN_PASSWORD_ALGORITHM_MD5 ||
|
|
algorithm == STUN_PASSWORD_ALGORITHM_SHA256)
|
|
msg->credentials.password_algorithm = algorithm;
|
|
else
|
|
JLOG_WARN("Unknown password algorithm 0x%hX", algorithm);
|
|
} else {
|
|
JLOG_WARN("Found password algorithm in response, ignoring");
|
|
}
|
|
break;
|
|
}
|
|
case STUN_ATTR_PASSWORD_ALGORITHMS: {
|
|
JLOG_VERBOSE("Reading password algorithms list");
|
|
if (length < sizeof(struct stun_value_password_algorithm)) {
|
|
JLOG_WARN("STUN password algorithms list too short, length=%zu", length);
|
|
return -1;
|
|
}
|
|
if (length > STUN_MAX_PASSWORD_ALGORITHMS_VALUE_SIZE) {
|
|
JLOG_WARN("STUN password algorithms list too long, length=%zu", length);
|
|
return -1;
|
|
}
|
|
|
|
memcpy(msg->credentials.password_algorithms_value, attr->value, length);
|
|
msg->credentials.password_algorithms_value_size = length;
|
|
|
|
if (!STUN_IS_RESPONSE(msg->msg_class)) {
|
|
const uint8_t *pos = attr->value;
|
|
const uint8_t *end = pos + length;
|
|
while (pos < end) {
|
|
if ((size_t)(end - pos) < sizeof(struct stun_value_password_algorithm)) {
|
|
JLOG_WARN("STUN password algorithms list truncated, available=%zu", end - pos);
|
|
return -1;
|
|
}
|
|
const struct stun_value_password_algorithm *pwa =
|
|
(const struct stun_value_password_algorithm *)pos;
|
|
stun_password_algorithm_t algorithm = ntohs(pwa->algorithm);
|
|
size_t parameters_length = ntohs(pwa->parameters_length);
|
|
size_t padded_length = align32(parameters_length);
|
|
|
|
pos += sizeof(struct stun_value_password_algorithm);
|
|
|
|
if ((size_t)(end - pos) < padded_length) {
|
|
JLOG_WARN(
|
|
"STUN password algorithm parameters too long, length=%zu, padded=%zu, "
|
|
"available=%zu",
|
|
parameters_length, padded_length, end - pos);
|
|
return -1;
|
|
}
|
|
|
|
pos += padded_length;
|
|
|
|
if (algorithm == STUN_PASSWORD_ALGORITHM_MD5 ||
|
|
algorithm == STUN_PASSWORD_ALGORITHM_SHA256) {
|
|
msg->credentials.password_algorithm = algorithm;
|
|
break;
|
|
}
|
|
|
|
JLOG_DEBUG("Unknown password algorithm 0x%hX", algorithm);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case STUN_ATTR_USERHASH: {
|
|
JLOG_VERBOSE("Reading user hash");
|
|
if (length != USERHASH_SIZE) {
|
|
JLOG_WARN("STUN user hash value too long, length=%zu", length);
|
|
return -1;
|
|
}
|
|
memcpy(msg->credentials.userhash, attr->value, USERHASH_SIZE);
|
|
msg->credentials.enable_userhash = true;
|
|
break;
|
|
}
|
|
case STUN_ATTR_SOFTWARE: {
|
|
JLOG_VERBOSE("Reading software");
|
|
if (length + 1 > STUN_MAX_SOFTWARE_LEN) {
|
|
JLOG_WARN("STUN software attribute value too long, length=%zu", length);
|
|
return -1;
|
|
}
|
|
char buffer[STUN_MAX_SOFTWARE_LEN];
|
|
memcpy(buffer, (const char *)attr->value, length);
|
|
buffer[length] = '\0';
|
|
JLOG_VERBOSE("Remote agent is \"%s\"", buffer);
|
|
break;
|
|
}
|
|
case STUN_ATTR_PRIORITY: {
|
|
JLOG_VERBOSE("Reading priority");
|
|
if (length != 4) {
|
|
JLOG_DEBUG("STUN priority length invalid, length=%zu", length);
|
|
return -1;
|
|
}
|
|
msg->priority = ntohl(*((uint32_t *)attr->value));
|
|
JLOG_VERBOSE("Got priority: %lu", (unsigned long)msg->priority);
|
|
break;
|
|
}
|
|
case STUN_ATTR_USE_CANDIDATE: {
|
|
JLOG_VERBOSE("Found use candidate flag");
|
|
msg->use_candidate = true;
|
|
break;
|
|
}
|
|
case STUN_ATTR_ICE_CONTROLLING: {
|
|
JLOG_VERBOSE("Found ICE controlling attribute");
|
|
if (length != 8) {
|
|
JLOG_DEBUG("STUN ICE controlling attribute length invalid, length=%zu", length);
|
|
return -1;
|
|
}
|
|
msg->ice_controlling = ntohll(*((uint64_t *)attr->value));
|
|
break;
|
|
}
|
|
case STUN_ATTR_ICE_CONTROLLED: {
|
|
JLOG_VERBOSE("Found ICE controlled attribute");
|
|
if (length != 8) {
|
|
JLOG_DEBUG("STUN ICE controlled attribute length invalid, length=%zu", length);
|
|
return -1;
|
|
}
|
|
msg->ice_controlled = ntohll(*((uint64_t *)attr->value));
|
|
break;
|
|
}
|
|
case STUN_ATTR_CHANNEL_NUMBER: {
|
|
JLOG_VERBOSE("Reading channel number attribute");
|
|
if (length < sizeof(struct stun_value_channel_number)) {
|
|
JLOG_DEBUG("STUN channel number attribute value too short, length=%zu", length);
|
|
return -1;
|
|
}
|
|
const struct stun_value_channel_number *channel_number =
|
|
(const struct stun_value_channel_number *)attr->value;
|
|
msg->channel_number = ntohs(channel_number->channel_number);
|
|
break;
|
|
}
|
|
case STUN_ATTR_LIFETIME: {
|
|
JLOG_VERBOSE("Reading lifetime attribute");
|
|
if (length != 4) {
|
|
JLOG_DEBUG("STUN lifetime attribute length invalid, length=%zu", length);
|
|
return -1;
|
|
}
|
|
msg->lifetime = ntohl(*((uint32_t *)attr->value));
|
|
msg->lifetime_set = true;
|
|
break;
|
|
}
|
|
case STUN_ATTR_XOR_PEER_ADDRESS: {
|
|
JLOG_VERBOSE("Reading XOR peer address");
|
|
uint8_t mask[16];
|
|
*((uint32_t *)mask) = htonl(STUN_MAGIC);
|
|
memcpy(mask + 4, msg->transaction_id, 12);
|
|
if (stun_read_value_mapped_address(attr->value, length, &msg->peer, mask) < 0)
|
|
return -1;
|
|
break;
|
|
}
|
|
case STUN_ATTR_XOR_RELAYED_ADDRESS: {
|
|
JLOG_VERBOSE("Reading XOR relayed address");
|
|
uint8_t mask[16];
|
|
*((uint32_t *)mask) = htonl(STUN_MAGIC);
|
|
memcpy(mask + 4, msg->transaction_id, 12);
|
|
if (stun_read_value_mapped_address(attr->value, length, &msg->relayed, mask) < 0)
|
|
return -1;
|
|
break;
|
|
}
|
|
case STUN_ATTR_DATA: {
|
|
JLOG_VERBOSE("Found data");
|
|
msg->data = (const char *)attr->value;
|
|
msg->data_size = length;
|
|
break;
|
|
}
|
|
case STUN_ATTR_EVEN_PORT: {
|
|
JLOG_VERBOSE("Found even port attribute");
|
|
if (length < 1) {
|
|
JLOG_DEBUG("STUN even port attribute length invalid, length=%zu", length);
|
|
return -1;
|
|
}
|
|
msg->even_port = true;
|
|
msg->next_port = ((struct stun_value_even_port *)attr->value)->r & 0x80;
|
|
break;
|
|
}
|
|
case STUN_ATTR_REQUESTED_TRANSPORT: {
|
|
JLOG_VERBOSE("Found requested transport attribute");
|
|
if (length < sizeof(struct stun_value_requested_transport)) {
|
|
JLOG_DEBUG("STUN requested transport attribute length invalid, length=%zu", length);
|
|
return -1;
|
|
}
|
|
const struct stun_value_requested_transport *requested_transport =
|
|
(const struct stun_value_requested_transport *)attr->value;
|
|
if (requested_transport->protocol != 17) { // UDP
|
|
JLOG_WARN("Unexpected requested transport protocol: %d",
|
|
(int)requested_transport->protocol);
|
|
return -1;
|
|
}
|
|
msg->requested_transport = true;
|
|
break;
|
|
}
|
|
case STUN_ATTR_DONT_FRAGMENT: {
|
|
JLOG_VERBOSE("Found don't fragment attribute");
|
|
msg->dont_fragment = true;
|
|
break;
|
|
}
|
|
case STUN_ATTR_RESERVATION_TOKEN: {
|
|
JLOG_VERBOSE("Found reservation token");
|
|
if (length != 8) {
|
|
JLOG_DEBUG("STUN reservation token length invalid, length=%zu", length);
|
|
return -1;
|
|
}
|
|
msg->reservation_token = ntohll(*((uint64_t *)attr->value));
|
|
break;
|
|
}
|
|
default: {
|
|
// Ignore
|
|
if (STUN_IS_OPTIONAL_ATTR(type))
|
|
JLOG_DEBUG("Ignoring unknown optional STUN attribute type 0x%X", (unsigned int)type);
|
|
else
|
|
JLOG_WARN("Unknown STUN attribute type 0x%X, ignoring", (unsigned int)type);
|
|
break;
|
|
}
|
|
}
|
|
return (int)(sizeof(struct stun_attr) + align32(length));
|
|
}
|
|
|
|
int stun_read_value_mapped_address(const void *data, size_t size, addr_record_t *mapped,
|
|
const uint8_t *mask) {
|
|
size_t len = sizeof(struct stun_value_mapped_address);
|
|
if (size < len) {
|
|
JLOG_VERBOSE("STUN mapped address value too short, size=%zu", size);
|
|
return -1;
|
|
}
|
|
const struct stun_value_mapped_address *value = data;
|
|
stun_address_family_t family = (stun_address_family_t)value->family;
|
|
switch (family) {
|
|
case STUN_ADDRESS_FAMILY_IPV4: {
|
|
len += 4;
|
|
if (size < len) {
|
|
JLOG_DEBUG("IPv4 mapped address value too short, size=%zu", size);
|
|
return -1;
|
|
}
|
|
JLOG_VERBOSE("Reading IPv4 address");
|
|
mapped->len = sizeof(struct sockaddr_in);
|
|
struct sockaddr_in *sin = (struct sockaddr_in *)&mapped->addr;
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_port = value->port ^ *((uint16_t *)mask);
|
|
uint8_t *bytes = (uint8_t *)&sin->sin_addr;
|
|
for (int i = 0; i < 4; ++i)
|
|
bytes[i] = value->address[i] ^ mask[i];
|
|
break;
|
|
}
|
|
case STUN_ADDRESS_FAMILY_IPV6: {
|
|
len += 16;
|
|
if (size < len) {
|
|
JLOG_DEBUG("IPv6 mapped address value too short, size=%zu", size);
|
|
return -1;
|
|
}
|
|
JLOG_VERBOSE("Reading IPv6 address");
|
|
mapped->len = sizeof(struct sockaddr_in6);
|
|
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&mapped->addr;
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = value->port ^ *((uint16_t *)mask);
|
|
uint8_t *bytes = (uint8_t *)&sin6->sin6_addr;
|
|
for (int i = 0; i < 16; ++i)
|
|
bytes[i] = value->address[i] ^ mask[i];
|
|
break;
|
|
}
|
|
default: {
|
|
JLOG_DEBUG("Unknown STUN address family 0x%X", (unsigned int)family);
|
|
len = size;
|
|
break;
|
|
}
|
|
}
|
|
return (int)len;
|
|
}
|
|
|
|
bool stun_check_integrity(void *buf, size_t size, const stun_message_t *msg, const char *password) {
|
|
if (!msg->has_integrity)
|
|
return false;
|
|
|
|
const struct stun_header *header = buf;
|
|
const size_t length = ntohs(header->length);
|
|
if (size < sizeof(struct stun_header) + length)
|
|
return false;
|
|
|
|
uint8_t key[MAX_HMAC_KEY_LEN];
|
|
size_t key_len = generate_hmac_key(msg, password, key);
|
|
|
|
bool success = false;
|
|
uint8_t *begin = buf;
|
|
const uint8_t *attr_begin = begin + sizeof(struct stun_header);
|
|
const uint8_t *end = attr_begin + length;
|
|
const uint8_t *pos = attr_begin;
|
|
while (pos < end) {
|
|
const struct stun_attr *attr = (const struct stun_attr *)pos;
|
|
size_t attr_length = ntohs(attr->length);
|
|
if (size < sizeof(struct stun_attr) + attr_length)
|
|
return false;
|
|
|
|
stun_attr_type_t type = (stun_attr_type_t)ntohs(attr->type);
|
|
switch (type) {
|
|
case STUN_ATTR_MESSAGE_INTEGRITY: {
|
|
if (attr_length != HMAC_SHA1_SIZE)
|
|
return false;
|
|
|
|
size_t tmp_length = pos - attr_begin + STUN_ATTR_SIZE + HMAC_SHA1_SIZE;
|
|
size_t prev_length = stun_update_header_length(begin, tmp_length);
|
|
uint8_t hmac[HMAC_SHA1_SIZE];
|
|
hmac_sha1(begin, pos - begin, key, key_len, hmac);
|
|
stun_update_header_length(begin, prev_length);
|
|
|
|
const uint8_t *expected_hmac = attr->value;
|
|
if (const_time_memcmp(hmac, expected_hmac, HMAC_SHA1_SIZE) != 0) {
|
|
JLOG_DEBUG("STUN message integrity SHA1 check failed");
|
|
return false;
|
|
}
|
|
|
|
success = true;
|
|
break;
|
|
}
|
|
case STUN_ATTR_MESSAGE_INTEGRITY_SHA256: {
|
|
if (attr_length != HMAC_SHA256_SIZE)
|
|
return false;
|
|
|
|
size_t tmp_length = pos - attr_begin + STUN_ATTR_SIZE + HMAC_SHA256_SIZE;
|
|
size_t prev_length = stun_update_header_length(begin, tmp_length);
|
|
uint8_t hmac[HMAC_SHA256_SIZE];
|
|
hmac_sha256(begin, pos - begin, key, key_len, hmac);
|
|
stun_update_header_length(begin, prev_length);
|
|
|
|
const uint8_t *expected_hmac = attr->value;
|
|
if (const_time_memcmp(hmac, expected_hmac, HMAC_SHA256_SIZE) != 0) {
|
|
JLOG_DEBUG("STUN message integrity SHA256 check failed");
|
|
return false;
|
|
}
|
|
|
|
success = true;
|
|
break;
|
|
}
|
|
default:
|
|
// Ignore
|
|
break;
|
|
}
|
|
|
|
pos += sizeof(struct stun_attr) + align32(attr_length);
|
|
}
|
|
|
|
if (!success)
|
|
return false;
|
|
|
|
JLOG_VERBOSE("STUN message integrity check succeeded");
|
|
return true;
|
|
}
|
|
|
|
void stun_prepend_nonce_cookie(char *nonce) {
|
|
// RFC 8489: To indicate that it supports this specification, a server MUST prepend the
|
|
// NONCE attribute value with the character string composed of "obMatJos2" concatenated with
|
|
// the (4-character) base64 [RFC4648] encoding of the 24-bit STUN Security Features See
|
|
// https://www.rfc-editor.org/rfc/rfc8489.html#section-9.2
|
|
char copy[STUN_MAX_NONCE_LEN];
|
|
strcpy(copy, nonce);
|
|
|
|
char encoded_security_bits[5];
|
|
uint32_t security_bits =
|
|
htonl(STUN_SECURITY_PASSWORD_ALGORITHMS_BIT | STUN_SECURITY_USERNAME_ANONYMITY_BIT);
|
|
BASE64_ENCODE((uint8_t *)&security_bits + 1, 3, encoded_security_bits, 5);
|
|
|
|
snprintf(nonce, STUN_MAX_NONCE_LEN, "%s%s%.*s", STUN_NONCE_COOKIE, encoded_security_bits,
|
|
STUN_MAX_NONCE_LEN - (STUN_NONCE_COOKIE_LEN + 5), copy);
|
|
}
|
|
|
|
void stun_compute_userhash(const char *username, const char *realm, uint8_t *out) {
|
|
char input[MAX_USERHASH_INPUT_LEN];
|
|
int input_len = snprintf(input, MAX_USERHASH_INPUT_LEN, "%s:%s", username, realm);
|
|
if (input_len < 0)
|
|
return;
|
|
|
|
if (input_len >= MAX_USERHASH_INPUT_LEN)
|
|
input_len = MAX_USERHASH_INPUT_LEN - 1;
|
|
|
|
hash_sha256(input, input_len, out);
|
|
}
|
|
|
|
void stun_process_credentials(const stun_credentials_t *credentials, stun_credentials_t *dst) {
|
|
char username[STUN_MAX_USERNAME_LEN];
|
|
strcpy(username, dst->username);
|
|
*dst = *credentials;
|
|
strcpy(dst->username, username);
|
|
|
|
if (credentials->enable_userhash)
|
|
stun_compute_userhash(username, credentials->realm, dst->userhash);
|
|
}
|
|
|
|
const char *stun_get_error_reason(unsigned int code) {
|
|
switch (code) {
|
|
case 0:
|
|
return "";
|
|
case 300:
|
|
return "Try Alternate";
|
|
case 400:
|
|
return "Bad Request";
|
|
case 401:
|
|
return "Unauthenticated";
|
|
case 403:
|
|
return "Forbidden";
|
|
case 420:
|
|
return "Unknown Attribute";
|
|
case 437:
|
|
return "Allocation Mismatch";
|
|
case 438:
|
|
return "Stale Nonce";
|
|
case 440:
|
|
return "Address Family not Supported";
|
|
case 441:
|
|
return "Wrong credentials";
|
|
case 442:
|
|
return "Unsupported Transport Protocol";
|
|
case 443:
|
|
return "Peer Address Family Mismatch";
|
|
case 486:
|
|
return "Allocation Quota Reached";
|
|
case 500:
|
|
return "Server Error";
|
|
case 508:
|
|
return "Insufficient Capacity";
|
|
default:
|
|
return "Error";
|
|
}
|
|
}
|
|
|
|
JUICE_EXPORT bool _juice_is_stun_datagram(const void *data, size_t size) {
|
|
return is_stun_datagram(data, size);
|
|
}
|
|
|
|
JUICE_EXPORT int _juice_stun_read(void *data, size_t size, stun_message_t *msg) {
|
|
return stun_read(data, size, msg);
|
|
}
|
|
|
|
JUICE_EXPORT bool _juice_stun_check_integrity(void *buf, size_t size, const stun_message_t *msg,
|
|
const char *password) {
|
|
return stun_check_integrity(buf, size, msg, password);
|
|
}
|