/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
* Copyright (c) 2007-2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file rendparse.c
* \brief Code to parse and validate v2 hidden service descriptors.
**/
#include "core/or/or.h"
#include "feature/dirparse/parsecommon.h"
#include "feature/dirparse/sigcommon.h"
#include "feature/rend/rendcommon.h"
#include "feature/rend/rendparse.h"
#include "lib/memarea/memarea.h"
#include "core/or/extend_info_st.h"
#include "feature/rend/rend_authorized_client_st.h"
#include "feature/rend/rend_intro_point_st.h"
#include "feature/rend/rend_service_descriptor_st.h"
/** List of tokens recognized in rendezvous service descriptors */
static token_rule_t desc_token_table[] = {
T1_START("rendezvous-service-descriptor", R_RENDEZVOUS_SERVICE_DESCRIPTOR,
EQ(1), NO_OBJ),
T1("version", R_VERSION, EQ(1), NO_OBJ),
T1("permanent-key", R_PERMANENT_KEY, NO_ARGS, NEED_KEY_1024),
T1("secret-id-part", R_SECRET_ID_PART, EQ(1), NO_OBJ),
T1("publication-time", R_PUBLICATION_TIME, CONCAT_ARGS, NO_OBJ),
T1("protocol-versions", R_PROTOCOL_VERSIONS, EQ(1), NO_OBJ),
T01("introduction-points", R_INTRODUCTION_POINTS, NO_ARGS, NEED_OBJ),
T1_END("signature", R_SIGNATURE, NO_ARGS, NEED_OBJ),
END_OF_TABLE
};
/** List of tokens recognized in the (encrypted) list of introduction points of
* rendezvous service descriptors */
static token_rule_t ipo_token_table[] = {
T1_START("introduction-point", R_IPO_IDENTIFIER, EQ(1), NO_OBJ),
T1("ip-address", R_IPO_IP_ADDRESS, EQ(1), NO_OBJ),
T1("onion-port", R_IPO_ONION_PORT, EQ(1), NO_OBJ),
T1("onion-key", R_IPO_ONION_KEY, NO_ARGS, NEED_KEY_1024),
T1("service-key", R_IPO_SERVICE_KEY, NO_ARGS, NEED_KEY_1024),
END_OF_TABLE
};
/** List of tokens recognized in the (possibly encrypted) list of introduction
* points of rendezvous service descriptors */
static token_rule_t client_keys_token_table[] = {
T1_START("client-name", C_CLIENT_NAME, CONCAT_ARGS, NO_OBJ),
T1("descriptor-cookie", C_DESCRIPTOR_COOKIE, EQ(1), NO_OBJ),
T01("client-key", C_CLIENT_KEY, NO_ARGS, NEED_SKEY_1024),
END_OF_TABLE
};
/** Parse and validate the ASCII-encoded v2 descriptor in desc,
* write the parsed descriptor to the newly allocated *parsed_out, the
* binary descriptor ID of length DIGEST_LEN to desc_id_out, the
* encrypted introduction points to the newly allocated
* *intro_points_encrypted_out, their encrypted size to
* *intro_points_encrypted_size_out, the size of the encoded descriptor
* to *encoded_size_out, and a pointer to the possibly next
* descriptor to *next_out; return 0 for success (including validation)
* and -1 for failure.
*
* If as_hsdir is 1, we're parsing this as an HSDir, and we should
* be strict about time formats.
*/
int
rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out,
char *desc_id_out,
char **intro_points_encrypted_out,
size_t *intro_points_encrypted_size_out,
size_t *encoded_size_out,
const char **next_out, const char *desc,
int as_hsdir)
{
rend_service_descriptor_t *result =
tor_malloc_zero(sizeof(rend_service_descriptor_t));
char desc_hash[DIGEST_LEN];
const char *eos;
smartlist_t *tokens = smartlist_new();
directory_token_t *tok;
char secret_id_part[DIGEST_LEN];
int i, version, num_ok=1;
smartlist_t *versions;
char public_key_hash[DIGEST_LEN];
char test_desc_id[DIGEST_LEN];
memarea_t *area = NULL;
const int strict_time_fmt = as_hsdir;
tor_assert(desc);
/* Check if desc starts correctly. */
if (strcmpstart(desc, "rendezvous-service-descriptor ")) {
log_info(LD_REND, "Descriptor does not start correctly.");
goto err;
}
/* Compute descriptor hash for later validation. */
if (router_get_hash_impl(desc, strlen(desc), desc_hash,
"rendezvous-service-descriptor ",
"\nsignature", '\n', DIGEST_SHA1) < 0) {
log_warn(LD_REND, "Couldn't compute descriptor hash.");
goto err;
}
/* Determine end of string. */
eos = strstr(desc, "\nrendezvous-service-descriptor ");
if (!eos)
eos = desc + strlen(desc);
else
eos = eos + 1;
/* Check length. */
if (eos-desc > REND_DESC_MAX_SIZE) {
/* XXXX+ If we are parsing this descriptor as a server, this
* should be a protocol warning. */
log_warn(LD_REND, "Descriptor length is %d which exceeds "
"maximum rendezvous descriptor size of %d bytes.",
(int)(eos-desc), REND_DESC_MAX_SIZE);
goto err;
}
/* Tokenize descriptor. */
area = memarea_new();
if (tokenize_string(area, desc, eos, tokens, desc_token_table, 0)) {
log_warn(LD_REND, "Error tokenizing descriptor.");
goto err;
}
/* Set next to next descriptor, if available. */
*next_out = eos;
/* Set length of encoded descriptor. */
*encoded_size_out = eos - desc;
/* Check min allowed length of token list. */
if (smartlist_len(tokens) < 7) {
log_warn(LD_REND, "Impossibly short descriptor.");
goto err;
}
/* Parse base32-encoded descriptor ID. */
tok = find_by_keyword(tokens, R_RENDEZVOUS_SERVICE_DESCRIPTOR);
tor_assert(tok == smartlist_get(tokens, 0));
tor_assert(tok->n_args == 1);
if (!rend_valid_descriptor_id(tok->args[0])) {
log_warn(LD_REND, "Invalid descriptor ID: '%s'", tok->args[0]);
goto err;
}
if (base32_decode(desc_id_out, DIGEST_LEN,
tok->args[0], REND_DESC_ID_V2_LEN_BASE32) < 0) {
log_warn(LD_REND, "Descriptor ID contains illegal characters: %s",
tok->args[0]);
goto err;
}
/* Parse descriptor version. */
tok = find_by_keyword(tokens, R_VERSION);
tor_assert(tok->n_args == 1);
result->version =
(int) tor_parse_long(tok->args[0], 10, 0, INT_MAX, &num_ok, NULL);
if (result->version != 2 || !num_ok) {
/* If it's <2, it shouldn't be under this format. If the number
* is greater than 2, we bumped it because we broke backward
* compatibility. See how version numbers in our other formats
* work. */
log_warn(LD_REND, "Unrecognized descriptor version: %s",
escaped(tok->args[0]));
goto err;
}
/* Parse public key. */
tok = find_by_keyword(tokens, R_PERMANENT_KEY);
result->pk = tok->key;
tok->key = NULL; /* Prevent free */
/* Parse secret ID part. */
tok = find_by_keyword(tokens, R_SECRET_ID_PART);
tor_assert(tok->n_args == 1);
if (strlen(tok->args[0]) != REND_SECRET_ID_PART_LEN_BASE32 ||
strspn(tok->args[0], BASE32_CHARS) != REND_SECRET_ID_PART_LEN_BASE32) {
log_warn(LD_REND, "Invalid secret ID part: '%s'", tok->args[0]);
goto err;
}
if (base32_decode(secret_id_part, DIGEST_LEN, tok->args[0], 32) < 0) {
log_warn(LD_REND, "Secret ID part contains illegal characters: %s",
tok->args[0]);
goto err;
}
/* Parse publication time -- up-to-date check is done when storing the
* descriptor. */
tok = find_by_keyword(tokens, R_PUBLICATION_TIME);
tor_assert(tok->n_args == 1);
if (parse_iso_time_(tok->args[0], &result->timestamp,
strict_time_fmt, 0) < 0) {
log_warn(LD_REND, "Invalid publication time: '%s'", tok->args[0]);
goto err;
}
/* Parse protocol versions. */
tok = find_by_keyword(tokens, R_PROTOCOL_VERSIONS);
tor_assert(tok->n_args == 1);
versions = smartlist_new();
smartlist_split_string(versions, tok->args[0], ",",
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
for (i = 0; i < smartlist_len(versions); i++) {
version = (int) tor_parse_long(smartlist_get(versions, i),
10, 0, INT_MAX, &num_ok, NULL);
if (!num_ok) /* It's a string; let's ignore it. */
continue;
if (version >= REND_PROTOCOL_VERSION_BITMASK_WIDTH)
/* Avoid undefined left-shift behaviour. */
continue;
result->protocols |= 1 << version;
}
SMARTLIST_FOREACH(versions, char *, cp, tor_free(cp));
smartlist_free(versions);
/* Parse encrypted introduction points. Don't verify. */
tok = find_opt_by_keyword(tokens, R_INTRODUCTION_POINTS);
if (tok) {
if (strcmp(tok->object_type, "MESSAGE")) {
log_warn(LD_DIR, "Bad object type: introduction points should be of "
"type MESSAGE");
goto err;
}
*intro_points_encrypted_out = tor_memdup(tok->object_body,
tok->object_size);
*intro_points_encrypted_size_out = tok->object_size;
} else {
*intro_points_encrypted_out = NULL;
*intro_points_encrypted_size_out = 0;
}
/* Parse and verify signature. */
tok = find_by_keyword(tokens, R_SIGNATURE);
if (check_signature_token(desc_hash, DIGEST_LEN, tok, result->pk, 0,
"v2 rendezvous service descriptor") < 0)
goto err;
/* Verify that descriptor ID belongs to public key and secret ID part. */
if (crypto_pk_get_digest(result->pk, public_key_hash) < 0) {
log_warn(LD_REND, "Unable to compute rend descriptor public key digest");
goto err;
}
rend_get_descriptor_id_bytes(test_desc_id, public_key_hash,
secret_id_part);
if (tor_memneq(desc_id_out, test_desc_id, DIGEST_LEN)) {
log_warn(LD_REND, "Parsed descriptor ID does not match "
"computed descriptor ID.");
goto err;
}
goto done;
err:
rend_service_descriptor_free(result);
result = NULL;
done:
if (tokens) {
SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
smartlist_free(tokens);
}
if (area)
memarea_drop_all(area);
*parsed_out = result;
if (result)
return 0;
return -1;
}
/** Decrypt the encrypted introduction points in ipos_encrypted of
* length ipos_encrypted_size using descriptor_cookie and
* write the result to a newly allocated string that is pointed to by
* ipos_decrypted and its length to ipos_decrypted_size.
* Return 0 if decryption was successful and -1 otherwise. */
int
rend_decrypt_introduction_points(char **ipos_decrypted,
size_t *ipos_decrypted_size,
const char *descriptor_cookie,
const char *ipos_encrypted,
size_t ipos_encrypted_size)
{
tor_assert(ipos_encrypted);
tor_assert(descriptor_cookie);
if (ipos_encrypted_size < 2) {
log_warn(LD_REND, "Size of encrypted introduction points is too "
"small.");
return -1;
}
if (ipos_encrypted[0] == (int)REND_BASIC_AUTH) {
char iv[CIPHER_IV_LEN], client_id[REND_BASIC_AUTH_CLIENT_ID_LEN],
session_key[CIPHER_KEY_LEN], *dec;
int declen, client_blocks;
size_t pos = 0, len, client_entries_len;
crypto_digest_t *digest;
crypto_cipher_t *cipher;
client_blocks = (int) ipos_encrypted[1];
client_entries_len = client_blocks * REND_BASIC_AUTH_CLIENT_MULTIPLE *
REND_BASIC_AUTH_CLIENT_ENTRY_LEN;
if (ipos_encrypted_size < 2 + client_entries_len + CIPHER_IV_LEN + 1) {
log_warn(LD_REND, "Size of encrypted introduction points is too "
"small.");
return -1;
}
memcpy(iv, ipos_encrypted + 2 + client_entries_len, CIPHER_IV_LEN);
digest = crypto_digest_new();
crypto_digest_add_bytes(digest, descriptor_cookie, REND_DESC_COOKIE_LEN);
crypto_digest_add_bytes(digest, iv, CIPHER_IV_LEN);
crypto_digest_get_digest(digest, client_id,
REND_BASIC_AUTH_CLIENT_ID_LEN);
crypto_digest_free(digest);
for (pos = 2; pos < 2 + client_entries_len;
pos += REND_BASIC_AUTH_CLIENT_ENTRY_LEN) {
if (tor_memeq(ipos_encrypted + pos, client_id,
REND_BASIC_AUTH_CLIENT_ID_LEN)) {
/* Attempt to decrypt introduction points. */
cipher = crypto_cipher_new(descriptor_cookie);
if (crypto_cipher_decrypt(cipher, session_key, ipos_encrypted
+ pos + REND_BASIC_AUTH_CLIENT_ID_LEN,
CIPHER_KEY_LEN) < 0) {
log_warn(LD_REND, "Could not decrypt session key for client.");
crypto_cipher_free(cipher);
return -1;
}
crypto_cipher_free(cipher);
len = ipos_encrypted_size - 2 - client_entries_len - CIPHER_IV_LEN;
dec = tor_malloc_zero(len + 1);
declen = crypto_cipher_decrypt_with_iv(session_key, dec, len,
ipos_encrypted + 2 + client_entries_len,
ipos_encrypted_size - 2 - client_entries_len);
if (declen < 0) {
log_warn(LD_REND, "Could not decrypt introduction point string.");
tor_free(dec);
return -1;
}
if (fast_memcmpstart(dec, declen, "introduction-point ")) {
log_warn(LD_REND, "Decrypted introduction points don't "
"look like we could parse them.");
tor_free(dec);
continue;
}
*ipos_decrypted = dec;
*ipos_decrypted_size = declen;
return 0;
}
}
log_warn(LD_REND, "Could not decrypt introduction points. Please "
"check your authorization for this service!");
return -1;
} else if (ipos_encrypted[0] == (int)REND_STEALTH_AUTH) {
char *dec;
int declen;
if (ipos_encrypted_size < CIPHER_IV_LEN + 2) {
log_warn(LD_REND, "Size of encrypted introduction points is too "
"small.");
return -1;
}
dec = tor_malloc_zero(ipos_encrypted_size - CIPHER_IV_LEN - 1 + 1);
declen = crypto_cipher_decrypt_with_iv(descriptor_cookie, dec,
ipos_encrypted_size -
CIPHER_IV_LEN - 1,
ipos_encrypted + 1,
ipos_encrypted_size - 1);
if (declen < 0) {
log_warn(LD_REND, "Decrypting introduction points failed!");
tor_free(dec);
return -1;
}
*ipos_decrypted = dec;
*ipos_decrypted_size = declen;
return 0;
} else {
log_warn(LD_REND, "Unknown authorization type number: %d",
ipos_encrypted[0]);
return -1;
}
}
/** Parse the encoded introduction points in intro_points_encoded of
* length intro_points_encoded_size and write the result to the
* descriptor in parsed; return the number of successfully parsed
* introduction points or -1 in case of a failure. */
int
rend_parse_introduction_points(rend_service_descriptor_t *parsed,
const char *intro_points_encoded,
size_t intro_points_encoded_size)
{
const char *current_ipo, *end_of_intro_points;
smartlist_t *tokens = NULL;
directory_token_t *tok;
rend_intro_point_t *intro;
extend_info_t *info;
int result, num_ok=1;
memarea_t *area = NULL;
tor_assert(parsed);
/** Function may only be invoked once. */
tor_assert(!parsed->intro_nodes);
if (!intro_points_encoded || intro_points_encoded_size == 0) {
log_warn(LD_REND, "Empty or zero size introduction point list");
goto err;
}
/* Consider one intro point after the other. */
current_ipo = intro_points_encoded;
end_of_intro_points = intro_points_encoded + intro_points_encoded_size;
tokens = smartlist_new();
parsed->intro_nodes = smartlist_new();
area = memarea_new();
while (!fast_memcmpstart(current_ipo, end_of_intro_points-current_ipo,
"introduction-point ")) {
/* Determine end of string. */
const char *eos = tor_memstr(current_ipo, end_of_intro_points-current_ipo,
"\nintroduction-point ");
if (!eos)
eos = end_of_intro_points;
else
eos = eos+1;
tor_assert(eos <= intro_points_encoded+intro_points_encoded_size);
/* Free tokens and clear token list. */
SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
smartlist_clear(tokens);
memarea_clear(area);
/* Tokenize string. */
if (tokenize_string(area, current_ipo, eos, tokens, ipo_token_table, 0)) {
log_warn(LD_REND, "Error tokenizing introduction point");
goto err;
}
/* Advance to next introduction point, if available. */
current_ipo = eos;
/* Check minimum allowed length of introduction point. */
if (smartlist_len(tokens) < 5) {
log_warn(LD_REND, "Impossibly short introduction point.");
goto err;
}
/* Allocate new intro point and extend info. */
intro = tor_malloc_zero(sizeof(rend_intro_point_t));
info = intro->extend_info = tor_malloc_zero(sizeof(extend_info_t));
/* Parse identifier. */
tok = find_by_keyword(tokens, R_IPO_IDENTIFIER);
if (base32_decode(info->identity_digest, DIGEST_LEN,
tok->args[0], REND_INTRO_POINT_ID_LEN_BASE32) < 0) {
log_warn(LD_REND, "Identity digest contains illegal characters: %s",
tok->args[0]);
rend_intro_point_free(intro);
goto err;
}
/* Write identifier to nickname. */
info->nickname[0] = '$';
base16_encode(info->nickname + 1, sizeof(info->nickname) - 1,
info->identity_digest, DIGEST_LEN);
/* Parse IP address. */
tok = find_by_keyword(tokens, R_IPO_IP_ADDRESS);
if (tor_addr_parse(&info->addr, tok->args[0])<0) {
log_warn(LD_REND, "Could not parse introduction point address.");
rend_intro_point_free(intro);
goto err;
}
if (tor_addr_family(&info->addr) != AF_INET) {
log_warn(LD_REND, "Introduction point address was not ipv4.");
rend_intro_point_free(intro);
goto err;
}
/* Parse onion port. */
tok = find_by_keyword(tokens, R_IPO_ONION_PORT);
info->port = (uint16_t) tor_parse_long(tok->args[0],10,1,65535,
&num_ok,NULL);
if (!info->port || !num_ok) {
log_warn(LD_REND, "Introduction point onion port %s is invalid",
escaped(tok->args[0]));
rend_intro_point_free(intro);
goto err;
}
/* Parse onion key. */
tok = find_by_keyword(tokens, R_IPO_ONION_KEY);
if (!crypto_pk_public_exponent_ok(tok->key)) {
log_warn(LD_REND,
"Introduction point's onion key had invalid exponent.");
rend_intro_point_free(intro);
goto err;
}
info->onion_key = tok->key;
tok->key = NULL; /* Prevent free */
/* Parse service key. */
tok = find_by_keyword(tokens, R_IPO_SERVICE_KEY);
if (!crypto_pk_public_exponent_ok(tok->key)) {
log_warn(LD_REND,
"Introduction point key had invalid exponent.");
rend_intro_point_free(intro);
goto err;
}
intro->intro_key = tok->key;
tok->key = NULL; /* Prevent free */
/* Add extend info to list of introduction points. */
smartlist_add(parsed->intro_nodes, intro);
}
result = smartlist_len(parsed->intro_nodes);
goto done;
err:
result = -1;
done:
/* Free tokens and clear token list. */
if (tokens) {
SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
smartlist_free(tokens);
}
if (area)
memarea_drop_all(area);
return result;
}
/** Parse the content of a client_key file in ckstr and add
* rend_authorized_client_t's for each parsed client to
* parsed_clients. Return the number of parsed clients as result
* or -1 for failure. */
int
rend_parse_client_keys(strmap_t *parsed_clients, const char *ckstr)
{
int result = -1;
smartlist_t *tokens;
directory_token_t *tok;
const char *current_entry = NULL;
memarea_t *area = NULL;
char *err_msg = NULL;
if (!ckstr || strlen(ckstr) == 0)
return -1;
tokens = smartlist_new();
/* Begin parsing with first entry, skipping comments or whitespace at the
* beginning. */
area = memarea_new();
current_entry = eat_whitespace(ckstr);
while (!strcmpstart(current_entry, "client-name ")) {
rend_authorized_client_t *parsed_entry;
/* Determine end of string. */
const char *eos = strstr(current_entry, "\nclient-name ");
if (!eos)
eos = current_entry + strlen(current_entry);
else
eos = eos + 1;
/* Free tokens and clear token list. */
SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
smartlist_clear(tokens);
memarea_clear(area);
/* Tokenize string. */
if (tokenize_string(area, current_entry, eos, tokens,
client_keys_token_table, 0)) {
log_warn(LD_REND, "Error tokenizing client keys file.");
goto err;
}
/* Advance to next entry, if available. */
current_entry = eos;
/* Check minimum allowed length of token list. */
if (smartlist_len(tokens) < 2) {
log_warn(LD_REND, "Impossibly short client key entry.");
goto err;
}
/* Parse client name. */
tok = find_by_keyword(tokens, C_CLIENT_NAME);
tor_assert(tok == smartlist_get(tokens, 0));
tor_assert(tok->n_args == 1);
if (!rend_valid_client_name(tok->args[0])) {
log_warn(LD_CONFIG, "Illegal client name: %s. (Length must be "
"between 1 and %d, and valid characters are "
"[A-Za-z0-9+-_].)", tok->args[0], REND_CLIENTNAME_MAX_LEN);
goto err;
}
/* Check if client name is duplicate. */
if (strmap_get(parsed_clients, tok->args[0])) {
log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains a "
"duplicate client name: '%s'. Ignoring.", tok->args[0]);
goto err;
}
parsed_entry = tor_malloc_zero(sizeof(rend_authorized_client_t));
parsed_entry->client_name = tor_strdup(tok->args[0]);
strmap_set(parsed_clients, parsed_entry->client_name, parsed_entry);
/* Parse client key. */
tok = find_opt_by_keyword(tokens, C_CLIENT_KEY);
if (tok) {
parsed_entry->client_key = tok->key;
tok->key = NULL; /* Prevent free */
}
/* Parse descriptor cookie. */
tok = find_by_keyword(tokens, C_DESCRIPTOR_COOKIE);
tor_assert(tok->n_args == 1);
if (rend_auth_decode_cookie(tok->args[0], parsed_entry->descriptor_cookie,
NULL, &err_msg) < 0) {
tor_assert(err_msg);
log_warn(LD_REND, "%s", err_msg);
tor_free(err_msg);
goto err;
}
}
result = strmap_size(parsed_clients);
goto done;
err:
result = -1;
done:
/* Free tokens and clear token list. */
SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
smartlist_free(tokens);
if (area)
memarea_drop_all(area);
return result;
}