aboutsummaryrefslogtreecommitdiff
path: root/src/feature/hs/hs_ob.c
blob: 9499c28d20f21d6f20c5502cfbc0a2e416522ec9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
/* Copyright (c) 2017-2020, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * \file hs_ob.c
 * \brief Implement Onion Balance specific code.
 **/

#define HS_OB_PRIVATE

#include "feature/hs/hs_service.h"

#include "feature/nodelist/networkstatus.h"
#include "feature/nodelist/networkstatus_st.h"

#include "lib/confmgt/confmgt.h"
#include "lib/encoding/confline.h"

#include "feature/hs/hs_ob.h"

/* Options config magic number. */
#define OB_OPTIONS_MAGIC 0x631DE7EA

/* Helper macros. */
#define VAR(varname, conftype, member, initvalue)                          \
  CONFIG_VAR_ETYPE(ob_options_t, varname, conftype, member, 0, initvalue)
#define V(member,conftype,initvalue)        \
  VAR(#member, conftype, member, initvalue)

/* Dummy instance of ob_options_t, used for type-checking its members with
 * CONF_CHECK_VAR_TYPE. */
DUMMY_TYPECHECK_INSTANCE(ob_options_t);

/* Array of variables for the config file options. */
static const config_var_t config_vars[] = {
  V(MasterOnionAddress, LINELIST, NULL),

  END_OF_CONFIG_VARS
};

/* "Extra" variable in the state that receives lines we can't parse. This
 * lets us preserve options from versions of Tor newer than us. */
static const struct_member_t config_extra_vars = {
  .name = "__extra",
  .type = CONFIG_TYPE_LINELIST,
  .offset = offsetof(ob_options_t, ExtraLines),
};

/* Configuration format of ob_options_t. */
static const config_format_t config_format = {
  .size = sizeof(ob_options_t),
  .magic = {
     "ob_options_t",
     OB_OPTIONS_MAGIC,
     offsetof(ob_options_t, magic_),
  },
  .vars = config_vars,
  .extra = &config_extra_vars,
};

/* Global configuration manager for the config file. */
static config_mgr_t *config_options_mgr = NULL;

/* Return the configuration manager for the config file. */
static const config_mgr_t *
get_config_options_mgr(void)
{
  if (PREDICT_UNLIKELY(config_options_mgr == NULL)) {
    config_options_mgr = config_mgr_new(&config_format);
    config_mgr_freeze(config_options_mgr);
  }
  return config_options_mgr;
}

#define ob_option_free(val) \
  FREE_AND_NULL(ob_options_t, ob_option_free_, (val))

/** Helper: Free a config options object. */
static void
ob_option_free_(ob_options_t *opts)
{
  if (opts == NULL) {
    return;
  }
  config_free(get_config_options_mgr(), opts);
}

/** Return an allocated config options object. */
static ob_options_t *
ob_option_new(void)
{
  ob_options_t *opts = config_new(get_config_options_mgr());
  config_init(get_config_options_mgr(), opts);
  return opts;
}

/** Helper function: From the configuration line value which is an onion
 * address with the ".onion" extension, find the public key and put it in
 * pkey_out.
 *
 * On success, true is returned. Else, false and pkey is untouched. */
static bool
get_onion_public_key(const char *value, ed25519_public_key_t *pkey_out)
{
  char address[HS_SERVICE_ADDR_LEN_BASE32 + 1];

  tor_assert(value);
  tor_assert(pkey_out);

  if (strcmpend(value, ".onion")) {
    /* Not a .onion extension, bad format. */
    return false;
  }

  /* Length validation. The -1 is because sizeof() counts the NUL byte. */
  if (strlen(value) >
      (HS_SERVICE_ADDR_LEN_BASE32 + sizeof(".onion") - 1)) {
    /* Too long, bad format. */
    return false;
  }

  /* We don't want the .onion so we add 2 because size - 1 is copied with
   * strlcpy() in order to accomodate the NUL byte and sizeof() counts the NUL
   * byte so we need to remove them from the equation. */
  strlcpy(address, value, strlen(value) - sizeof(".onion") + 2);

  if (hs_parse_address_no_log(address, pkey_out, NULL, NULL, NULL) < 0) {
    return false;
  }

  /* Success. */
  return true;
}

/** Parse the given ob options in opts and set the service config object
 * accordingly.
 *
 * Return 1 on success else 0. */
static int
ob_option_parse(hs_service_config_t *config, const ob_options_t *opts)
{
  int ret = 0;
  config_line_t *line;

  tor_assert(config);
  tor_assert(opts);

  for (line = opts->MasterOnionAddress; line; line = line->next) {
    /* Allocate config list if need be. */
    if (!config->ob_master_pubkeys) {
      config->ob_master_pubkeys = smartlist_new();
    }
    ed25519_public_key_t *pubkey = tor_malloc_zero(sizeof(*pubkey));

    if (!get_onion_public_key(line->value, pubkey)) {
      log_warn(LD_REND, "OnionBalance: MasterOnionAddress %s is invalid",
               line->value);
      tor_free(pubkey);
      goto end;
    }
    smartlist_add(config->ob_master_pubkeys, pubkey);
    log_notice(LD_REND, "OnionBalance: MasterOnionAddress %s registered",
               line->value);
  }
  /* Success. */
  ret = 1;

 end:
  /* No keys added, we free the list since no list means no onion balance
   * support for this tor instance. */
  if (smartlist_len(config->ob_master_pubkeys) == 0) {
    smartlist_free(config->ob_master_pubkeys);
  }
  return ret;
}

/** For the given master public key and time period, compute the subcredential
 * and put them into subcredential. The subcredential parameter needs to be at
 * least DIGEST256_LEN in size. */
static void
build_subcredential(const ed25519_public_key_t *pkey, uint64_t tp,
                    hs_subcredential_t *subcredential)
{
  ed25519_public_key_t blinded_pubkey;

  tor_assert(pkey);
  tor_assert(subcredential);

  hs_build_blinded_pubkey(pkey, NULL, 0, tp, &blinded_pubkey);
  hs_get_subcredential(pkey, &blinded_pubkey, subcredential);
}

/*
 * Public API.
 */

/** Return true iff the given service is configured as an onion balance
 * instance. To satisfy that condition, there must at least be one master
 * ed25519 public key configured. */
bool
hs_ob_service_is_instance(const hs_service_t *service)
{
  if (BUG(service == NULL)) {
    return false;
  }

  /* No list, we are not an instance. */
  if (!service->config.ob_master_pubkeys) {
    return false;
  }

  return smartlist_len(service->config.ob_master_pubkeys) > 0;
}

/** Read and parse the config file at fname on disk. The service config object
 * is populated with the options if any.
 *
 * Return 1 on success else 0. This is to follow the "ok" convention in
 * hs_config.c. */
int
hs_ob_parse_config_file(hs_service_config_t *config)
{
  static const char *fname = "ob_config";
  int ret = 0;
  char *content = NULL, *errmsg = NULL, *config_file_path = NULL;
  ob_options_t *options = NULL;
  config_line_t *lines = NULL;

  tor_assert(config);

  /* Read file from disk. */
  config_file_path = hs_path_from_filename(config->directory_path, fname);
  content = read_file_to_str(config_file_path, 0, NULL);
  if (!content) {
    log_warn(LD_FS, "OnionBalance: Unable to read config file %s",
             escaped(config_file_path));
    goto end;
  }

  /* Parse lines. */
  if (config_get_lines(content, &lines, 0) < 0) {
    goto end;
  }

  options = ob_option_new();
  config_assign(get_config_options_mgr(), options, lines, 0, &errmsg);
  if (errmsg) {
    log_warn(LD_REND, "OnionBalance: Unable to parse config file: %s",
             errmsg);
    tor_free(errmsg);
    goto end;
  }

  /* Parse the options and set the service config object with the details. */
  ret = ob_option_parse(config, options);

 end:
  config_free_lines(lines);
  ob_option_free(options);
  tor_free(content);
  tor_free(config_file_path);
  return ret;
}

/** Compute all possible subcredentials for every onion master key in the given
 * service config object. subcredentials_out is allocated and set as an
 * continous array containing all possible values.
 *
 * On success, return the number of subcredential put in the array which will
 * correspond to an arry of size: n * DIGEST256_LEN where DIGEST256_LEN is the
 * length of a single subcredential.
 *
 * If the given configuration object has no OB master keys configured, 0 is
 * returned and subcredentials_out is set to NULL.
 *
 * Otherwise, this can't fail. */
STATIC size_t
compute_subcredentials(const hs_service_t *service,
                       hs_subcredential_t **subcredentials_out)
{
  unsigned int num_pkeys, idx = 0;
  hs_subcredential_t *subcreds = NULL;
  const int steps[3] = {0, -1, 1};
  const unsigned int num_steps = ARRAY_LENGTH(steps);
  const uint64_t tp = hs_get_time_period_num(0);

  tor_assert(service);
  tor_assert(subcredentials_out);
  /* Our caller has checked these too */
  tor_assert(service->desc_current);
  tor_assert(service->desc_next);

  /* Make sure we are an OB instance, or bail out. */
  num_pkeys = smartlist_len(service->config.ob_master_pubkeys);
  if (!num_pkeys) {
    *subcredentials_out = NULL;
    return 0;
  }

  /* Time to build all the subcredentials for each time period: two for each
   * instance descriptor plus three for the onionbalance frontend service: the
   * previous one (-1), the current one (0) and the next one (1) for each
   * configured key in order to accomodate client and service consensus skew.
   *
   * If the client consensus after_time is at 23:00 but the service one is at
   * 01:00, the client will be using the previous time period where the
   * service will think it is the client next time period. Thus why we have
   * to try them all.
   *
   * The normal use case works because the service gets the descriptor object
   * that corresponds to the intro point's request, and because each
   * descriptor corresponds to a specific subcredential, we get the right
   * subcredential out of it, and use that to do the decryption.
   *
   * As a slight optimization, statistically, the current time period (0) will
   * be the one to work first so we'll put them first in the array to maximize
   * our chance of success. */

  /* We use a flat array, not a smartlist_t, in order to minimize memory
   * allocation.
   *
   * Size of array is: length of a single subcredential multiplied by the
   * number of time period we need to compute and finally multiplied by the
   * total number of keys we are about to process. In other words, for each
   * key, we allocate 3 subcredential slots. Then in the end we also add two
   * subcredentials for this instance's active descriptors. */
  subcreds =
    tor_calloc((num_steps * num_pkeys) + 2, sizeof(hs_subcredential_t));

  /* For each master pubkey we add 3 subcredentials: */
  for (unsigned int i = 0; i < num_steps; i++) {
    SMARTLIST_FOREACH_BEGIN(service->config.ob_master_pubkeys,
                            const ed25519_public_key_t *, pkey) {
      build_subcredential(pkey, tp + steps[i], &subcreds[idx]);
      idx++;
    } SMARTLIST_FOREACH_END(pkey);
  }

  /* And then in the end we add the two subcredentials of the current active
   * instance descriptors */
  memcpy(&subcreds[idx++], &service->desc_current->desc->subcredential,
         sizeof(hs_subcredential_t));
  memcpy(&subcreds[idx++], &service->desc_next->desc->subcredential,
         sizeof(hs_subcredential_t));

  log_info(LD_REND, "Refreshing %u onionbalance keys (TP #%d).",
             idx, (int)tp);

  *subcredentials_out = subcreds;
  return idx;
}

/**
 *  If we are an Onionbalance instance, refresh our keys.
 *
 *  If we are not an Onionbalance instance or we are not ready to do so, this
 *  is a NOP.
 *
 *  This function is called everytime we build a new descriptor. That's because
 *  we want our Onionbalance keys to always use up-to-date subcredentials both
 *  for the instance (ourselves) and for the onionbalance frontend.
 */
void
hs_ob_refresh_keys(hs_service_t *service)
{
  hs_subcredential_t *ob_subcreds = NULL;
  size_t num_subcreds;

  tor_assert(service);

  /* Don't do any of this if we are not configured as an OB instance */
  if (!hs_ob_service_is_instance(service)) {
    return;
  }

  /* We need both service descriptors created to make onionbalance keys.
   *
   * That's because we fetch our own (the instance's) subcredentials from our
   * own descriptors which should always include the latest subcredentials that
   * clients would use.
   *
   * This function is called with each descriptor build, so we will be
   * eventually be called when both descriptors are created. */
  if (!service->desc_current || !service->desc_next) {
    return;
  }

  /* Get a new set of subcreds */
  num_subcreds = compute_subcredentials(service, &ob_subcreds);
  if (BUG(!num_subcreds)) {
    return;
  }

  /* Delete old subcredentials if any */
  if (service->state.ob_subcreds) {
    tor_free(service->state.ob_subcreds);
  }

  service->state.ob_subcreds = ob_subcreds;
  service->state.n_ob_subcreds = num_subcreds;
}

/** Free any memory allocated by the onionblance subsystem. */
void
hs_ob_free_all(void)
{
  config_mgr_free(config_options_mgr);
}