hs_pow.c (18946B)
1 /* Copyright (c) 2017-2020, The Tor Project, Inc. */ 2 /* See LICENSE for licensing information */ 3 4 /** 5 * \file hs_pow.c 6 * \brief Contains code to handle proof-of-work computations 7 * when a hidden service is defending against DoS attacks. 8 **/ 9 10 #include <stdio.h> 11 12 #include "core/or/or.h" 13 #include "app/config/config.h" 14 #include "ext/ht.h" 15 #include "ext/compat_blake2.h" 16 #include "core/or/circuitlist.h" 17 #include "core/or/origin_circuit_st.h" 18 #include "ext/equix/include/equix.h" 19 #include "feature/hs/hs_cache.h" 20 #include "feature/hs/hs_descriptor.h" 21 #include "feature/hs/hs_circuitmap.h" 22 #include "feature/hs/hs_client.h" 23 #include "feature/hs/hs_pow.h" 24 #include "lib/crypt_ops/crypto_rand.h" 25 #include "lib/crypt_ops/crypto_format.h" 26 #include "lib/arch/bytes.h" 27 #include "lib/cc/ctassert.h" 28 #include "core/mainloop/cpuworker.h" 29 #include "lib/evloop/workqueue.h" 30 #include "lib/time/compat_time.h" 31 32 /** Replay cache set up */ 33 /** Cache entry for (nonce, seed) replay protection. */ 34 typedef struct nonce_cache_entry_t { 35 HT_ENTRY(nonce_cache_entry_t) node; 36 struct { 37 uint8_t nonce[HS_POW_NONCE_LEN]; 38 uint8_t seed_head[HS_POW_SEED_HEAD_LEN]; 39 } bytes; 40 } nonce_cache_entry_t; 41 42 /** Return true if the two (nonce, seed) replay cache entries are the same */ 43 static inline int 44 nonce_cache_entries_eq_(const struct nonce_cache_entry_t *entry1, 45 const struct nonce_cache_entry_t *entry2) 46 { 47 return fast_memeq(&entry1->bytes, &entry2->bytes, sizeof entry1->bytes); 48 } 49 50 /** Hash function to hash the (nonce, seed) tuple entry. */ 51 static inline unsigned 52 nonce_cache_entry_hash_(const struct nonce_cache_entry_t *ent) 53 { 54 return (unsigned)siphash24g(&ent->bytes, sizeof ent->bytes); 55 } 56 57 static HT_HEAD(nonce_cache_table_ht, nonce_cache_entry_t) 58 nonce_cache_table = HT_INITIALIZER(); 59 60 HT_PROTOTYPE(nonce_cache_table_ht, nonce_cache_entry_t, node, 61 nonce_cache_entry_hash_, nonce_cache_entries_eq_); 62 63 HT_GENERATE2(nonce_cache_table_ht, nonce_cache_entry_t, node, 64 nonce_cache_entry_hash_, nonce_cache_entries_eq_, 0.6, 65 tor_reallocarray_, tor_free_); 66 67 /** This is a callback used to check replay cache entries against a provided 68 * seed head, or NULL to operate on the entire cache. Matching entries return 69 * 1 and their internal cache entry is freed, non-matching entries return 0. */ 70 static int 71 nonce_cache_entry_match_seed_and_free(nonce_cache_entry_t *ent, void *data) 72 { 73 if (data == NULL || 74 fast_memeq(ent->bytes.seed_head, data, HS_POW_SEED_HEAD_LEN)) { 75 tor_free(ent); 76 return 1; 77 } 78 return 0; 79 } 80 81 /** Helper: Increment a given nonce and set it in the challenge at the right 82 * offset. Use by the solve function. */ 83 static inline void 84 increment_and_set_nonce(uint8_t *nonce, uint8_t *challenge) 85 { 86 for (unsigned i = 0; i < HS_POW_NONCE_LEN; i++) { 87 uint8_t prev = nonce[i]; 88 if (++nonce[i] > prev) { 89 break; 90 } 91 } 92 memcpy(challenge + HS_POW_NONCE_OFFSET, nonce, HS_POW_NONCE_LEN); 93 } 94 95 /* Helper: Build EquiX challenge (P || ID || C || N || INT_32(E)) and return 96 * a newly allocated buffer containing it. */ 97 static uint8_t * 98 build_equix_challenge(const ed25519_public_key_t *blinded_id, 99 const uint8_t *seed, const uint8_t *nonce, 100 const uint32_t effort) 101 { 102 size_t offset = 0; 103 uint8_t *challenge = tor_malloc_zero(HS_POW_CHALLENGE_LEN); 104 105 CTASSERT(HS_POW_ID_LEN == sizeof *blinded_id); 106 tor_assert_nonfatal(!ed25519_public_key_is_zero(blinded_id)); 107 108 log_debug(LD_REND, 109 "Constructing EquiX challenge with " 110 "blinded service id %s, effort: %d", 111 safe_str_client(ed25519_fmt(blinded_id)), 112 effort); 113 114 memcpy(challenge + offset, HS_POW_PSTRING, HS_POW_PSTRING_LEN); 115 offset += HS_POW_PSTRING_LEN; 116 memcpy(challenge + offset, blinded_id, HS_POW_ID_LEN); 117 offset += HS_POW_ID_LEN; 118 memcpy(challenge + offset, seed, HS_POW_SEED_LEN); 119 offset += HS_POW_SEED_LEN; 120 tor_assert(HS_POW_NONCE_OFFSET == offset); 121 memcpy(challenge + offset, nonce, HS_POW_NONCE_LEN); 122 offset += HS_POW_NONCE_LEN; 123 set_uint32(challenge + offset, tor_htonl(effort)); 124 offset += HS_POW_EFFORT_LEN; 125 tor_assert(HS_POW_CHALLENGE_LEN == offset); 126 127 return challenge; 128 } 129 130 /** Helper: Return true iff the given challenge and solution for the given 131 * effort do validate as in: R * E <= UINT32_MAX. */ 132 static bool 133 validate_equix_challenge(const uint8_t *challenge, 134 const uint8_t *solution_bytes, 135 const uint32_t effort) 136 { 137 /* Fail if R * E > UINT32_MAX. */ 138 uint8_t hash_result[HS_POW_HASH_LEN]; 139 blake2b_state b2_state; 140 141 if (BUG(blake2b_init(&b2_state, HS_POW_HASH_LEN) < 0)) { 142 return false; 143 } 144 145 /* Construct: blake2b(C || N || E || S) */ 146 blake2b_update(&b2_state, challenge, HS_POW_CHALLENGE_LEN); 147 blake2b_update(&b2_state, solution_bytes, HS_POW_EQX_SOL_LEN); 148 blake2b_final(&b2_state, hash_result, HS_POW_HASH_LEN); 149 150 /* Scale to 64 bit so we can avoid 32 bit overflow. */ 151 uint64_t RE = tor_htonl(get_uint32(hash_result)) * (uint64_t) effort; 152 153 return RE <= UINT32_MAX; 154 } 155 156 /** Helper: Convert equix_solution to a byte array in little-endian order */ 157 static void 158 pack_equix_solution(const equix_solution *sol_in, 159 uint8_t *bytes_out) 160 { 161 for (unsigned i = 0; i < EQUIX_NUM_IDX; i++) { 162 bytes_out[i*2+0] = (uint8_t)sol_in->idx[i]; 163 bytes_out[i*2+1] = (uint8_t)(sol_in->idx[i] >> 8); 164 } 165 } 166 167 /** Helper: Build an equix_solution from its corresponding byte array. */ 168 static void 169 unpack_equix_solution(const uint8_t *bytes_in, 170 equix_solution *sol_out) 171 { 172 for (unsigned i = 0; i < EQUIX_NUM_IDX; i++) { 173 sol_out->idx[i] = (uint16_t)bytes_in[i*2+0] | 174 (uint16_t)bytes_in[i*2+1] << 8; 175 } 176 } 177 178 /** Helper: Map the CompiledProofOfWorkHash configuration option to its 179 * corresponding equix_ctx_flags bit. */ 180 static equix_ctx_flags 181 hs_pow_equix_option_flags(int CompiledProofOfWorkHash) 182 { 183 if (CompiledProofOfWorkHash == 0) { 184 return 0; 185 } else if (CompiledProofOfWorkHash == 1) { 186 return EQUIX_CTX_MUST_COMPILE; 187 } else { 188 tor_assert_nonfatal(CompiledProofOfWorkHash == -1); 189 return EQUIX_CTX_TRY_COMPILE; 190 } 191 } 192 193 /** Solve the EquiX/blake2b PoW scheme using the parameters in pow_params, and 194 * store the solution in pow_solution_out. Returns 0 on success and -1 195 * otherwise. Called by a client, from a cpuworker thread. */ 196 int 197 hs_pow_solve(const hs_pow_solver_inputs_t *pow_inputs, 198 hs_pow_solution_t *pow_solution_out) 199 { 200 int ret = -1; 201 uint8_t nonce[HS_POW_NONCE_LEN]; 202 uint8_t *challenge = NULL; 203 equix_ctx *ctx = NULL; 204 205 tor_assert(pow_inputs); 206 tor_assert(pow_solution_out); 207 const uint32_t effort = pow_inputs->effort; 208 209 /* Generate a random nonce N. */ 210 crypto_rand((char *)nonce, sizeof nonce); 211 212 /* Build EquiX challenge string */ 213 challenge = build_equix_challenge(&pow_inputs->service_blinded_id, 214 pow_inputs->seed, nonce, effort); 215 216 /* This runs on a cpuworker, let's not access global get_options(). 217 * Instead, the particular options we need are captured in pow_inputs. */ 218 ctx = equix_alloc(EQUIX_CTX_SOLVE | 219 hs_pow_equix_option_flags(pow_inputs->CompiledProofOfWorkHash)); 220 if (!ctx) { 221 goto end; 222 } 223 224 uint8_t sol_bytes[HS_POW_EQX_SOL_LEN]; 225 monotime_t start_time; 226 monotime_get(&start_time); 227 log_info(LD_REND, "Solving proof of work (effort %u)", effort); 228 229 for (;;) { 230 /* Calculate solutions to S = equix_solve(C || N || E), */ 231 equix_solutions_buffer buffer; 232 equix_result result; 233 result = equix_solve(ctx, challenge, HS_POW_CHALLENGE_LEN, &buffer); 234 switch (result) { 235 236 case EQUIX_OK: 237 for (unsigned i = 0; i < buffer.count; i++) { 238 pack_equix_solution(&buffer.sols[i], sol_bytes); 239 240 /* Check an Equi-X solution against the effort threshold */ 241 if (validate_equix_challenge(challenge, sol_bytes, effort)) { 242 /* Store the nonce N. */ 243 memcpy(pow_solution_out->nonce, nonce, HS_POW_NONCE_LEN); 244 /* Store the effort E. */ 245 pow_solution_out->effort = effort; 246 /* We only store the first 4 bytes of the seed C. */ 247 memcpy(pow_solution_out->seed_head, pow_inputs->seed, 248 sizeof(pow_solution_out->seed_head)); 249 /* Store the solution S */ 250 memcpy(&pow_solution_out->equix_solution, 251 sol_bytes, sizeof sol_bytes); 252 253 monotime_t end_time; 254 monotime_get(&end_time); 255 int64_t duration_usec = monotime_diff_usec(&start_time, &end_time); 256 log_info(LD_REND, "Proof of work solution (effort %u) found " 257 "using %s implementation in %u.%06u seconds", 258 effort, 259 (EQUIX_SOLVER_DID_USE_COMPILER & buffer.flags) 260 ? "compiled" : "interpreted", 261 (unsigned)(duration_usec / 1000000), 262 (unsigned)(duration_usec % 1000000)); 263 264 /* Indicate success and we are done. */ 265 ret = 0; 266 goto end; 267 } 268 } 269 break; 270 271 case EQUIX_FAIL_CHALLENGE: 272 /* This happens occasionally due to HashX rejecting some program 273 * configurations. For our purposes here it's the same as count==0. 274 * Increment the nonce and try again. */ 275 break; 276 277 case EQUIX_FAIL_COMPILE: 278 /* The interpreter is disabled and the compiler failed */ 279 log_warn(LD_REND, "Proof of work solver failed, " 280 "compile error with no fallback enabled."); 281 goto end; 282 283 /* These failures are not applicable to equix_solve, but included for 284 * completeness and to satisfy exhaustive enum warnings. */ 285 case EQUIX_FAIL_ORDER: 286 case EQUIX_FAIL_PARTIAL_SUM: 287 case EQUIX_FAIL_FINAL_SUM: 288 /* And these really should not happen, and indicate 289 * programming errors if they do. */ 290 case EQUIX_FAIL_NO_SOLVER: 291 case EQUIX_FAIL_INTERNAL: 292 default: 293 tor_assert_nonfatal_unreached(); 294 goto end; 295 } 296 297 /* No solutions for this nonce and/or none that passed the effort 298 * threshold, increment and try again. */ 299 increment_and_set_nonce(nonce, challenge); 300 } 301 302 end: 303 tor_free(challenge); 304 equix_free(ctx); 305 return ret; 306 } 307 308 /** Verify the solution in pow_solution using the service's current PoW 309 * parameters found in pow_state. Returns 0 on success and -1 otherwise. Called 310 * by the service. */ 311 int 312 hs_pow_verify(const ed25519_public_key_t *service_blinded_id, 313 const hs_pow_service_state_t *pow_state, 314 const hs_pow_solution_t *pow_solution) 315 { 316 int ret = -1; 317 uint8_t *challenge = NULL; 318 nonce_cache_entry_t search, *entry = NULL; 319 equix_ctx *ctx = NULL; 320 const uint8_t *seed = NULL; 321 322 tor_assert(pow_state); 323 tor_assert(pow_solution); 324 tor_assert(service_blinded_id); 325 tor_assert_nonfatal(!ed25519_public_key_is_zero(service_blinded_id)); 326 327 /* Find a valid seed C that starts with the seed head. Fail if no such seed 328 * exists. */ 329 if (fast_memeq(pow_state->seed_current, pow_solution->seed_head, 330 HS_POW_SEED_HEAD_LEN)) { 331 seed = pow_state->seed_current; 332 } else if (fast_memeq(pow_state->seed_previous, pow_solution->seed_head, 333 HS_POW_SEED_HEAD_LEN)) { 334 seed = pow_state->seed_previous; 335 } else { 336 log_warn(LD_REND, "Seed head didn't match either seed."); 337 goto done; 338 } 339 340 /* Fail if N = POW_NONCE is present in the replay cache. */ 341 memcpy(search.bytes.nonce, pow_solution->nonce, HS_POW_NONCE_LEN); 342 memcpy(search.bytes.seed_head, pow_solution->seed_head, 343 HS_POW_SEED_HEAD_LEN); 344 entry = HT_FIND(nonce_cache_table_ht, &nonce_cache_table, &search); 345 if (entry) { 346 log_warn(LD_REND, "Found (nonce, seed) tuple in the replay cache."); 347 goto done; 348 } 349 350 /* Build the challenge with the params we have. */ 351 challenge = build_equix_challenge(service_blinded_id, seed, 352 pow_solution->nonce, pow_solution->effort); 353 354 if (!validate_equix_challenge(challenge, pow_solution->equix_solution, 355 pow_solution->effort)) { 356 log_warn(LD_REND, "Verification of challenge effort in PoW failed."); 357 goto done; 358 } 359 360 ctx = equix_alloc(EQUIX_CTX_VERIFY | 361 hs_pow_equix_option_flags(get_options()->CompiledProofOfWorkHash)); 362 if (!ctx) { 363 goto done; 364 } 365 366 /* Fail if equix_verify() != EQUIX_OK */ 367 equix_solution equix_sol; 368 unpack_equix_solution(pow_solution->equix_solution, &equix_sol); 369 equix_result result = equix_verify(ctx, challenge, HS_POW_CHALLENGE_LEN, 370 &equix_sol); 371 if (result != EQUIX_OK) { 372 log_warn(LD_REND, "Verification of EquiX solution in PoW failed."); 373 goto done; 374 } 375 376 /* PoW verified successfully. */ 377 ret = 0; 378 379 /* Add the (nonce, seed) tuple to the replay cache. */ 380 entry = tor_malloc_zero(sizeof(nonce_cache_entry_t)); 381 memcpy(entry->bytes.nonce, pow_solution->nonce, HS_POW_NONCE_LEN); 382 memcpy(entry->bytes.seed_head, pow_solution->seed_head, 383 HS_POW_SEED_HEAD_LEN); 384 HT_INSERT(nonce_cache_table_ht, &nonce_cache_table, entry); 385 386 done: 387 tor_free(challenge); 388 equix_free(ctx); 389 return ret; 390 } 391 392 /** Remove entries from the (nonce, seed) replay cache which are for the seed 393 * beginning with seed_head. If seed_head is NULL, remove all cache entries. */ 394 void 395 hs_pow_remove_seed_from_cache(const uint8_t *seed_head) 396 { 397 /* If nonce_cache_entry_has_seed returns 1, the entry is removed. */ 398 HT_FOREACH_FN(nonce_cache_table_ht, &nonce_cache_table, 399 nonce_cache_entry_match_seed_and_free, (void*)seed_head); 400 } 401 402 /** Free a given PoW service state. */ 403 void 404 hs_pow_free_service_state(hs_pow_service_state_t *state) 405 { 406 if (state == NULL) { 407 return; 408 } 409 rend_pqueue_clear(state); 410 tor_assert(smartlist_len(state->rend_request_pqueue) == 0); 411 smartlist_free(state->rend_request_pqueue); 412 mainloop_event_free(state->pop_pqueue_ev); 413 tor_free(state); 414 } 415 416 /* ===== 417 Thread workers 418 =====*/ 419 420 /** 421 * An object passed to a worker thread that will try to solve the pow. 422 */ 423 typedef struct pow_worker_job_t { 424 425 /** Inputs for the PoW solver (seed, chosen effort) */ 426 hs_pow_solver_inputs_t pow_inputs; 427 428 /** State: we'll look these up to figure out how to proceed after. */ 429 uint32_t intro_circ_identifier; 430 uint8_t rend_circ_cookie[HS_REND_COOKIE_LEN]; 431 432 /** Output: The worker thread will malloc and write its answer here, 433 * or set it to NULL if it produced no useful answer. */ 434 hs_pow_solution_t *pow_solution_out; 435 436 } pow_worker_job_t; 437 438 /** 439 * Worker function. This function runs inside a worker thread and receives 440 * a pow_worker_job_t as its input. 441 */ 442 static workqueue_reply_t 443 pow_worker_threadfn(void *state_, void *work_) 444 { 445 (void)state_; 446 pow_worker_job_t *job = work_; 447 job->pow_solution_out = tor_malloc_zero(sizeof(hs_pow_solution_t)); 448 449 if (hs_pow_solve(&job->pow_inputs, job->pow_solution_out)) { 450 tor_free(job->pow_solution_out); 451 job->pow_solution_out = NULL; /* how we signal that we came up empty */ 452 } 453 return WQ_RPL_REPLY; 454 } 455 456 /** 457 * Helper: release all storage held in <b>job</b>. 458 */ 459 static void 460 pow_worker_job_free(pow_worker_job_t *job) 461 { 462 if (!job) 463 return; 464 tor_free(job->pow_solution_out); 465 tor_free(job); 466 } 467 468 /** 469 * Worker function: This function runs in the main thread, and receives 470 * a pow_worker_job_t that the worker thread has already processed. 471 */ 472 static void 473 pow_worker_replyfn(void *work_) 474 { 475 tor_assert(in_main_thread()); 476 tor_assert(work_); 477 478 pow_worker_job_t *job = work_; 479 480 /* Look up the circuits that we're going to use this pow in. 481 * There's room for improvement here. We already had a fast mapping to 482 * rend circuits from some kind of identifier that we can keep in a 483 * pow_worker_job_t, but we don't have that index for intro circs at this 484 * time. If the linear search in circuit_get_by_global_id() is ever a 485 * noticeable bottleneck we should add another map. 486 */ 487 origin_circuit_t *intro_circ = 488 circuit_get_by_global_id(job->intro_circ_identifier); 489 origin_circuit_t *rend_circ = 490 hs_circuitmap_get_established_rend_circ_client_side(job->rend_circ_cookie); 491 492 /* try to re-create desc and ip */ 493 const ed25519_public_key_t *service_identity_pk = NULL; 494 const hs_descriptor_t *desc = NULL; 495 const hs_desc_intro_point_t *ip = NULL; 496 if (intro_circ) 497 service_identity_pk = &intro_circ->hs_ident->identity_pk; 498 if (service_identity_pk) 499 desc = hs_cache_lookup_as_client(service_identity_pk); 500 if (desc) 501 ip = find_desc_intro_point_by_ident(intro_circ->hs_ident, desc); 502 503 if (intro_circ && rend_circ && service_identity_pk && desc && ip && 504 job->pow_solution_out) { 505 506 /* successful pow solve, and circs still here */ 507 log_info(LD_REND, "Got a PoW solution we like! Shipping it!"); 508 509 /* Set flag to reflect that the HS we are attempting to rendezvous has PoW 510 * defenses enabled, and as such we will need to be more lenient with 511 * timing out while waiting for the service-side circuit to be built. */ 512 rend_circ->hs_with_pow_circ = 1; 513 514 /* Remember the PoW effort we chose, for client-side rend circuits. */ 515 rend_circ->hs_pow_effort = job->pow_inputs.effort; 516 517 // and then send that intro cell 518 if (send_introduce1(intro_circ, rend_circ, 519 desc, job->pow_solution_out, ip) < 0) { 520 /* if it failed, mark the intro point as ready to start over */ 521 intro_circ->hs_currently_solving_pow = 0; 522 } 523 524 } else { 525 if (!job->pow_solution_out) { 526 log_warn(LD_REND, "PoW cpuworker returned with no solution"); 527 } else { 528 log_info(LD_REND, "PoW solution completed but we can " 529 "no longer locate its circuit"); 530 } 531 if (intro_circ) { 532 intro_circ->hs_currently_solving_pow = 0; 533 } 534 } 535 536 pow_worker_job_free(job); 537 } 538 539 /** 540 * Queue the job of solving the pow in a worker thread. 541 */ 542 int 543 hs_pow_queue_work(uint32_t intro_circ_identifier, 544 const uint8_t *rend_circ_cookie, 545 const hs_pow_solver_inputs_t *pow_inputs) 546 { 547 tor_assert(in_main_thread()); 548 tor_assert(rend_circ_cookie); 549 tor_assert(pow_inputs); 550 tor_assert_nonfatal( 551 !ed25519_public_key_is_zero(&pow_inputs->service_blinded_id)); 552 553 pow_worker_job_t *job = tor_malloc_zero(sizeof(*job)); 554 job->intro_circ_identifier = intro_circ_identifier; 555 memcpy(&job->rend_circ_cookie, rend_circ_cookie, 556 sizeof job->rend_circ_cookie); 557 memcpy(&job->pow_inputs, pow_inputs, sizeof job->pow_inputs); 558 559 workqueue_entry_t *work; 560 work = cpuworker_queue_work(WQ_PRI_LOW, 561 pow_worker_threadfn, 562 pow_worker_replyfn, 563 job); 564 if (!work) { 565 pow_worker_job_free(job); 566 return -1; 567 } 568 return 0; 569 }