mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-10 21:23:58 +01:00
Move v2 hs parsing into feature/rend
This commit is contained in:
parent
430ca38f70
commit
82f4d3ca75
@ -121,6 +121,7 @@ LIBTOR_APP_A_SOURCES = \
|
||||
src/feature/rend/rendclient.c \
|
||||
src/feature/rend/rendcommon.c \
|
||||
src/feature/rend/rendmid.c \
|
||||
src/feature/rend/rendparse.c \
|
||||
src/feature/rend/rendservice.c \
|
||||
src/feature/stats/geoip_stats.c \
|
||||
src/feature/stats/rephist.c \
|
||||
@ -359,6 +360,7 @@ noinst_HEADERS += \
|
||||
src/feature/rend/rendclient.h \
|
||||
src/feature/rend/rendcommon.h \
|
||||
src/feature/rend/rendmid.h \
|
||||
src/feature/rend/rendparse.h \
|
||||
src/feature/rend/rendservice.h \
|
||||
src/feature/stats/geoip_stats.h \
|
||||
src/feature/stats/rephist.h \
|
||||
|
@ -83,6 +83,7 @@
|
||||
#include "feature/relay/selftest.h"
|
||||
#include "feature/rend/rendclient.h"
|
||||
#include "feature/rend/rendcommon.h"
|
||||
#include "feature/rend/rendparse.h"
|
||||
#include "feature/rend/rendservice.h"
|
||||
#include "feature/stats/geoip_stats.h"
|
||||
#include "feature/stats/predict_ports.h"
|
||||
|
@ -209,40 +209,6 @@ static token_rule_t rtrstatus_token_table[] = {
|
||||
END_OF_TABLE
|
||||
};
|
||||
|
||||
/** 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
|
||||
};
|
||||
|
||||
/** List of tokens recognized in V3 networkstatus votes. */
|
||||
static token_rule_t networkstatus_token_table[] = {
|
||||
T1_START("network-status-version", K_NETWORK_STATUS_VERSION,
|
||||
@ -366,7 +332,6 @@ static addr_policy_t *router_parse_addr_policy_private(directory_token_t *tok);
|
||||
|
||||
static smartlist_t *find_all_exitpolicy(smartlist_t *s);
|
||||
|
||||
|
||||
/** Set <b>digest</b> to the SHA-1 digest of the hash of the directory in
|
||||
* <b>s</b>. Return 0 on success, -1 on failure.
|
||||
*/
|
||||
@ -3702,550 +3667,6 @@ microdescs_parse_from_string(const char *s, const char *eos,
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Parse and validate the ASCII-encoded v2 descriptor in <b>desc</b>,
|
||||
* write the parsed descriptor to the newly allocated *<b>parsed_out</b>, the
|
||||
* binary descriptor ID of length DIGEST_LEN to <b>desc_id_out</b>, the
|
||||
* encrypted introduction points to the newly allocated
|
||||
* *<b>intro_points_encrypted_out</b>, their encrypted size to
|
||||
* *<b>intro_points_encrypted_size_out</b>, the size of the encoded descriptor
|
||||
* to *<b>encoded_size_out</b>, and a pointer to the possibly next
|
||||
* descriptor to *<b>next_out</b>; return 0 for success (including validation)
|
||||
* and -1 for failure.
|
||||
*
|
||||
* If <b>as_hsdir</b> 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 <b>ipos_encrypted</b> of
|
||||
* length <b>ipos_encrypted_size</b> using <b>descriptor_cookie</b> and
|
||||
* write the result to a newly allocated string that is pointed to by
|
||||
* <b>ipos_decrypted</b> and its length to <b>ipos_decrypted_size</b>.
|
||||
* 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 <b>intro_points_encoded</b> of
|
||||
* length <b>intro_points_encoded_size</b> and write the result to the
|
||||
* descriptor in <b>parsed</b>; 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 <b>ckstr</b> and add
|
||||
* rend_authorized_client_t's for each parsed client to
|
||||
* <b>parsed_clients</b>. 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;
|
||||
}
|
||||
|
||||
/** Called on startup; right now we just handle scanning the unparseable
|
||||
* descriptor dumps, but hang anything else we might need to do in the
|
||||
* future here as well.
|
||||
|
@ -63,23 +63,6 @@ smartlist_t *microdescs_parse_from_string(const char *s, const char *eos,
|
||||
saved_location_t where,
|
||||
smartlist_t *invalid_digests_out);
|
||||
|
||||
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);
|
||||
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);
|
||||
int rend_parse_introduction_points(rend_service_descriptor_t *parsed,
|
||||
const char *intro_points_encoded,
|
||||
size_t intro_points_encoded_size);
|
||||
int rend_parse_client_keys(strmap_t *parsed_clients, const char *str);
|
||||
|
||||
void routerparse_init(void);
|
||||
void routerparse_free_all(void);
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "feature/nodelist/routerlist.h"
|
||||
#include "feature/dirparse/routerparse.h"
|
||||
#include "feature/rend/rendcommon.h"
|
||||
#include "feature/rend/rendparse.h"
|
||||
|
||||
#include "core/or/extend_info_st.h"
|
||||
#include "feature/rend/rend_intro_point_st.h"
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "feature/rend/rendclient.h"
|
||||
#include "feature/rend/rendcommon.h"
|
||||
#include "feature/rend/rendmid.h"
|
||||
#include "feature/rend/rendparse.h"
|
||||
#include "feature/rend/rendservice.h"
|
||||
#include "feature/stats/rephist.h"
|
||||
#include "feature/hs_common/replaycache.h"
|
||||
|
600
src/feature/rend/rendparse.c
Normal file
600
src/feature/rend/rendparse.c
Normal file
@ -0,0 +1,600 @@
|
||||
/* 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 <b>desc</b>,
|
||||
* write the parsed descriptor to the newly allocated *<b>parsed_out</b>, the
|
||||
* binary descriptor ID of length DIGEST_LEN to <b>desc_id_out</b>, the
|
||||
* encrypted introduction points to the newly allocated
|
||||
* *<b>intro_points_encrypted_out</b>, their encrypted size to
|
||||
* *<b>intro_points_encrypted_size_out</b>, the size of the encoded descriptor
|
||||
* to *<b>encoded_size_out</b>, and a pointer to the possibly next
|
||||
* descriptor to *<b>next_out</b>; return 0 for success (including validation)
|
||||
* and -1 for failure.
|
||||
*
|
||||
* If <b>as_hsdir</b> 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 <b>ipos_encrypted</b> of
|
||||
* length <b>ipos_encrypted_size</b> using <b>descriptor_cookie</b> and
|
||||
* write the result to a newly allocated string that is pointed to by
|
||||
* <b>ipos_decrypted</b> and its length to <b>ipos_decrypted_size</b>.
|
||||
* 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 <b>intro_points_encoded</b> of
|
||||
* length <b>intro_points_encoded_size</b> and write the result to the
|
||||
* descriptor in <b>parsed</b>; 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 <b>ckstr</b> and add
|
||||
* rend_authorized_client_t's for each parsed client to
|
||||
* <b>parsed_clients</b>. 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;
|
||||
}
|
32
src/feature/rend/rendparse.h
Normal file
32
src/feature/rend/rendparse.h
Normal file
@ -0,0 +1,32 @@
|
||||
/* 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 rend_parse.h
|
||||
* \brief Header file for rend_parse.c.
|
||||
**/
|
||||
|
||||
#ifndef TOR_REND_PARSE_H
|
||||
#define TOR_REND_PARSE_H
|
||||
|
||||
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);
|
||||
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);
|
||||
int rend_parse_introduction_points(rend_service_descriptor_t *parsed,
|
||||
const char *intro_points_encoded,
|
||||
size_t intro_points_encoded_size);
|
||||
int rend_parse_client_keys(strmap_t *parsed_clients, const char *str);
|
||||
|
||||
#endif
|
@ -35,6 +35,7 @@
|
||||
#include "feature/nodelist/routerset.h"
|
||||
#include "feature/rend/rendclient.h"
|
||||
#include "feature/rend/rendcommon.h"
|
||||
#include "feature/rend/rendparse.h"
|
||||
#include "feature/rend/rendservice.h"
|
||||
#include "feature/stats/predict_ports.h"
|
||||
#include "lib/crypt_ops/crypto_dh.h"
|
||||
|
@ -1,10 +1,9 @@
|
||||
/* Copyright (c) 2016-2018, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
#define ROUTERPARSE_PRIVATE
|
||||
#include "core/or/or.h"
|
||||
#include "feature/dirparse/routerparse.h"
|
||||
#include "feature/dirparse/unparseable.h"
|
||||
#include "feature/rend/rendcommon.h"
|
||||
#include "feature/rend/rendparse.h"
|
||||
#include "lib/crypt_ops/crypto_ed25519.h"
|
||||
#include "test/fuzz/fuzzing.h"
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
/* Copyright (c) 2016-2018, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
#define ROUTERPARSE_PRIVATE
|
||||
|
||||
#include "core/or/or.h"
|
||||
#include "feature/dirparse/routerparse.h"
|
||||
#include "feature/dirparse/unparseable.h"
|
||||
#include "feature/rend/rendcommon.h"
|
||||
#include "feature/rend/rendparse.h"
|
||||
#include "lib/crypt_ops/crypto_ed25519.h"
|
||||
|
||||
#include "feature/rend/rend_service_descriptor_st.h"
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include "core/or/connection_edge.h"
|
||||
#include "feature/rend/rendcommon.h"
|
||||
#include "feature/rend/rendcache.h"
|
||||
#include "feature/rend/rendparse.h"
|
||||
#include "test/test.h"
|
||||
#include "core/mainloop/mainloop.h"
|
||||
#include "lib/memarea/memarea.h"
|
||||
|
Loading…
Reference in New Issue
Block a user