diff options
67 files changed, 3037 insertions, 1256 deletions
diff --git a/changes/bug23954 b/changes/bug23954 new file mode 100644 index 0000000000..185814f12e --- /dev/null +++ b/changes/bug23954 @@ -0,0 +1,4 @@ + o Minor bugfixes (logging, race conditions): + - Fix a (mostly harmless) race condition when invoking + LOG_PROTOCOL_WARN message from a subthread while the options are + changing. Fixes bug 23954; bugfix on 0.1.1.9-alpha. diff --git a/changes/bug24469 b/changes/bug24469 new file mode 100644 index 0000000000..2e137b49b8 --- /dev/null +++ b/changes/bug24469 @@ -0,0 +1,4 @@ + o Minor bugfixes (circuit, cannibalization): + - Don't cannibalize circuits for which we don't know the first hop which + can happen if our Guard relay went off the consensus after the circuit + was created preemptively. Fixes bug 24469; bugfix on 0.0.6. diff --git a/changes/bug24859 b/changes/bug24859 new file mode 100644 index 0000000000..122109d650 --- /dev/null +++ b/changes/bug24859 @@ -0,0 +1,4 @@ + o Minor bugfixes (logging): + - Don't treat inability to store a cached consensus object as a + bug: it can happen normally when we are out of disk space. + Fixes bug 24859; bugfix on 0.3.1.1-alpha. diff --git a/changes/bug24927 b/changes/bug24927 new file mode 100644 index 0000000000..6997306956 --- /dev/null +++ b/changes/bug24927 @@ -0,0 +1,4 @@ + o Minor bugfixes (correctness): + - Remove nonworking, unnecessary check to see whether a circuit hop's + identity was set when the circuit failed. Fixes bug 24927; bugfix on + 0.2.4.4-alpha. diff --git a/changes/bug24952 b/changes/bug24952 new file mode 100644 index 0000000000..93174c04f5 --- /dev/null +++ b/changes/bug24952 @@ -0,0 +1,5 @@ + o Minor bugfix (channel connection): + - The accurate address of a connection is real_addr, not the addr member. + TLS Channel remote address is now real_addr content instead of addr + member. Fixes bug 24952; bugfix on 707c1e2e26 in 0.2.4.11-alpha. + Patch by "ffmancera". diff --git a/changes/bug24972 b/changes/bug24972 new file mode 100644 index 0000000000..5adf970abf --- /dev/null +++ b/changes/bug24972 @@ -0,0 +1,4 @@ + o Minor features (logging, diagnostic): + - When logging a failure to check a hidden service's certificate, + also log what the problem with the certificate was. Diagnostic + for ticket 24972. diff --git a/changes/bug24975 b/changes/bug24975 new file mode 100644 index 0000000000..32a5dfc929 --- /dev/null +++ b/changes/bug24975 @@ -0,0 +1,6 @@ + o Major bugfixes (scheduler, consensus): + - A logic in the code was preventing the scheduler subystem to properly + make a decision based on the latest consensus when it arrives. This lead + to the scheduler failing to notice any consensus parameters that might + have changed between consensuses. Fixes bug 24975; bugfix on + 0.3.2.1-alpha. diff --git a/changes/bug24976 b/changes/bug24976 new file mode 100644 index 0000000000..9c3be86eab --- /dev/null +++ b/changes/bug24976 @@ -0,0 +1,5 @@ + o Minor bugfixes (hidden service v3 client): + - Remove a BUG() statement which can be triggered in normal circumstances + where a client fetches a descriptor that has a lower revision counter + than the one in its cache. This can happen due to HSDir desync. Fixes + bug 24976; bugfix on 0.3.2.1-alpha. diff --git a/changes/bug25008 b/changes/bug25008 new file mode 100644 index 0000000000..5ddc062982 --- /dev/null +++ b/changes/bug25008 @@ -0,0 +1,9 @@ + o Minor bugfixes (performance): + - Avoid calling protocol_list_supports_protocol() from inside tight loops + when running with cached routerinfo_t objects. Instead, + summarize the relevant protocols as flags in the routerinfo_t, as we do + for routerstatus_t objects. This change simplifies our code a little, + and saves a large amount of short-term memory allocation operations. + Fixes bug 25008; bugfix on 0.2.9.4-alpha. + + diff --git a/changes/bug25105 b/changes/bug25105 new file mode 100644 index 0000000000..36d1a5f16f --- /dev/null +++ b/changes/bug25105 @@ -0,0 +1,5 @@ + o Minor bugfixes (v3 onion services): + - Look at the "HSRend" protocol version, not the "HSDir" protocol + version, when deciding whether a consensus entry can support + the v3 onion service protocol as a rendezvous point. + Fixes bug 25105; bugfix on 0.3.2.1-alpha. diff --git a/changes/ticket24849 b/changes/ticket24849 new file mode 100644 index 0000000000..fd9492acb9 --- /dev/null +++ b/changes/ticket24849 @@ -0,0 +1,3 @@ + o Minor features (directory authority): + - When unable to add signatures to a pending consensus, log the reason + why. Closes ticket 24849. diff --git a/changes/ticket24902 b/changes/ticket24902 new file mode 100644 index 0000000000..1a2ef95cc9 --- /dev/null +++ b/changes/ticket24902 @@ -0,0 +1,13 @@ + o Major features (denial of service mitigation): + - Give relays some defenses against the recent network overload. We start + with three defenses (default parameters in parentheses). First: if a + single client address makes too many concurrent connections (>100), hang + up on further connections. Second: if a single client address makes + circuits too quickly (more than 3 per second, with an allowed burst of + 90) while also having too many connections open (3), refuse new create + cells for the next while (1-2 hours). Third: if a client asks to + establish a rendezvous point to you directly, ignore the request. These + defenses can be manually controlled by new torrc options, but relays + will also take guidance from consensus parameters, so there's no need to + configure anything manually. Implements ticket 24902. + diff --git a/changes/ticket25108 b/changes/ticket25108 new file mode 100644 index 0000000000..6aefac16db --- /dev/null +++ b/changes/ticket25108 @@ -0,0 +1,3 @@ + o Code simplification and refactoring: + - Remove the unused nodelist_recompute_all_hsdir_indices(). Closes ticket + 25108. diff --git a/doc/tor.1.txt b/doc/tor.1.txt index ef3d1eb9ee..5ad8183650 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -2752,6 +2752,94 @@ The following options are used to configure a hidden service. including setting SOCKSPort to "0". Can not be changed while tor is running. (Default: 0) +DENIAL OF SERVICE MITIGATION OPTIONS +------------------------------------ + +The following options are useful only for a public relay. They control the +Denial of Service mitigation subsystem. + +[[DoSCircuitCreationEnabled]] **DoSCircuitCreationEnabled** **0**|**1**|**auto**:: + + Enable circuit creation DoS mitigation. If enabled, tor will cache client + IPs along with statistics in order to detect circuit DoS attacks. If an + address is positively identified, tor will activate defenses against the + address. See the DoSCircuitCreationDefenseType option for more details. + This is a client to relay detection only. "auto" means use the consensus + parameter. + (Default: auto) + +[[DoSCircuitCreationMinConnections]] **DoSCircuitCreationMinConnections** __NUM__:: + + Minimum threshold of concurrent connections before a client address can be + flagged as executing a circuit creation DoS. In other words, once a client + address reaches the circuit rate and has a minimum of NUM concurrent + connections, a detection is positive. "0" means use the consensus + parameter. + (Default: 0) + +[[DoSCircuitCreationRate]] **DoSCircuitCreationRate** __NUM__:: + + The allowed circuit creation rate per second applied per client IP + address. If this option is 0, it obeys a consensus parameter. (Default: 0) + +[[DoSCircuitCreationBurst]] **DoSCircuitCreationBurst** __NUM__:: + + The allowed circuit creation burst per client IP address. If the circuit + rate and the burst are reached, a client is marked as executing a circuit + creation DoS. "0" means use the consensus parameter. + (Default: 0) + +[[DoSCircuitCreationDefenseType]] **DoSCircuitCreationDefenseType** __NUM__:: + + This is the type of defense applied to a detected client address. The + possible values are: + + 1: No defense. + 2: Refuse circuit creation for the DoSCircuitCreationDefenseTimePeriod period of time. ++ + "0" means use the consensus parameter. + (Default: 0) + +[[DoSCircuitCreationDefenseTimePeriod]] **DoSCircuitCreationDefenseTimePeriod** __NUM__:: + + The base time period that the DoS defense is activated for. The actual + value is selected randomly for each activation from NUM+1 to 3/2 * NUM. + "0" means use the consensus parameter. + (Default: 0) + +[[DoSConnectionEnabled]] **DoSConnectionEnabled** **0**|**1**|**auto**:: + + Enable the connection DoS mitigation. For client address only, this allows + tor to mitigate against large number of concurrent connections made by a + single IP address. "auto" means use the consensus parameter. + (Default: auto) + +[[DoSConnectionMaxConcurrentCount]] **DoSConnectionMaxConcurrentCount** __NUM__:: + + The maximum threshold of concurrent connection from a client IP address. + Above this limit, a defense selected by DoSConnectionDefenseType is + applied. "0" means use the consensus parameter. + (Default: 0) + +[[DoSConnectionDefenseType]] **DoSConnectionDefenseType** __NUM__:: + + This is the type of defense applied to a detected client address for the + connection mitigation. The possible values are: + + 1: No defense. + 2: Immediately close new connections. ++ + "0" means use the consensus parameter. + (Default: 0) + +[[DoSRefuseSingleHopClientRendezvous]] **DoSRefuseSingleHopClientRendezvous** **0**|**1**|**auto**:: + + Refuse establishment of rendezvous points for single hop clients. In other + words, if a client directly connects to the relay and sends an + ESTABLISH_RENDEZVOUS cell, it is silently dropped. "auto" means use the + consensus parameter. + (Default: auto) + TESTING NETWORK OPTIONS ----------------------- diff --git a/src/common/crypto.c b/src/common/crypto.c index 107b53ad29..2ecf64c393 100644 --- a/src/common/crypto.c +++ b/src/common/crypto.c @@ -28,6 +28,7 @@ #include "crypto_curve25519.h" #include "crypto_ed25519.h" #include "crypto_format.h" +#include "crypto_rsa.h" DISABLE_GCC_WARNING(redundant-decls) @@ -88,13 +89,6 @@ ENABLE_GCC_WARNING(redundant-decls) /** Largest strong entropy request */ #define MAX_STRONGEST_RAND_SIZE 256 -/** A public key, or a public/private key-pair. */ -struct crypto_pk_t -{ - int refs; /**< reference count, so we don't have to copy keys */ - RSA *key; /**< The key itself */ -}; - /** A structure to hold the first half (x, g^x) of a Diffie-Hellman handshake * while we're waiting for the second.*/ struct crypto_dh_t { @@ -103,30 +97,6 @@ struct crypto_dh_t { static int tor_check_dh_key(int severity, const BIGNUM *bn); -/** Return the number of bytes added by padding method <b>padding</b>. - */ -static inline int -crypto_get_rsa_padding_overhead(int padding) -{ - switch (padding) - { - case RSA_PKCS1_OAEP_PADDING: return PKCS1_OAEP_PADDING_OVERHEAD; - default: tor_assert(0); return -1; // LCOV_EXCL_LINE - } -} - -/** Given a padding method <b>padding</b>, return the correct OpenSSL constant. - */ -static inline int -crypto_get_rsa_padding(int padding) -{ - switch (padding) - { - case PK_PKCS1_OAEP_PADDING: return RSA_PKCS1_OAEP_PADDING; - default: tor_assert(0); return -1; // LCOV_EXCL_LINE - } -} - /** Boolean: has OpenSSL's crypto been initialized? */ static int crypto_early_initialized_ = 0; @@ -363,73 +333,6 @@ crypto_thread_cleanup(void) #endif } -/** used internally: quicly validate a crypto_pk_t object as a private key. - * Return 1 iff the public key is valid, 0 if obviously invalid. - */ -static int -crypto_pk_private_ok(const crypto_pk_t *k) -{ -#ifdef OPENSSL_1_1_API - if (!k || !k->key) - return 0; - - const BIGNUM *p, *q; - RSA_get0_factors(k->key, &p, &q); - return p != NULL; /* XXX/yawning: Should we check q? */ -#else /* !(defined(OPENSSL_1_1_API)) */ - return k && k->key && k->key->p; -#endif /* defined(OPENSSL_1_1_API) */ -} - -/** used by tortls.c: wrap an RSA* in a crypto_pk_t. */ -crypto_pk_t * -crypto_new_pk_from_rsa_(RSA *rsa) -{ - crypto_pk_t *env; - tor_assert(rsa); - env = tor_malloc(sizeof(crypto_pk_t)); - env->refs = 1; - env->key = rsa; - return env; -} - -/** Helper, used by tor-gencert.c. Return the RSA from a - * crypto_pk_t. */ -RSA * -crypto_pk_get_rsa_(crypto_pk_t *env) -{ - return env->key; -} - -/** used by tortls.c: get an equivalent EVP_PKEY* for a crypto_pk_t. Iff - * private is set, include the private-key portion of the key. Return a valid - * pointer on success, and NULL on failure. */ -MOCK_IMPL(EVP_PKEY *, -crypto_pk_get_evp_pkey_,(crypto_pk_t *env, int private)) -{ - RSA *key = NULL; - EVP_PKEY *pkey = NULL; - tor_assert(env->key); - if (private) { - if (!(key = RSAPrivateKey_dup(env->key))) - goto error; - } else { - if (!(key = RSAPublicKey_dup(env->key))) - goto error; - } - if (!(pkey = EVP_PKEY_new())) - goto error; - if (!(EVP_PKEY_assign_RSA(pkey, key))) - goto error; - return pkey; - error: - if (pkey) - EVP_PKEY_free(pkey); - if (key) - RSA_free(key); - return NULL; -} - /** Used by tortls.c: Get the DH* from a crypto_dh_t. */ DH * @@ -438,38 +341,6 @@ crypto_dh_get_dh_(crypto_dh_t *dh) return dh->dh; } -/** Allocate and return storage for a public key. The key itself will not yet - * be set. - */ -MOCK_IMPL(crypto_pk_t *, -crypto_pk_new,(void)) -{ - RSA *rsa; - - rsa = RSA_new(); - tor_assert(rsa); - return crypto_new_pk_from_rsa_(rsa); -} - -/** Release a reference to an asymmetric key; when all the references - * are released, free the key. - */ -void -crypto_pk_free_(crypto_pk_t *env) -{ - if (!env) - return; - - if (--env->refs > 0) - return; - tor_assert(env->refs == 0); - - if (env->key) - RSA_free(env->key); - - tor_free(env); -} - /** Allocate and return a new symmetric cipher using the provided key and iv. * The key is <b>bits</b> bits long; the IV is CIPHER_IV_LEN bytes. Both * must be provided. Key length must be 128, 192, or 256 */ @@ -528,543 +399,6 @@ crypto_cipher_free_(crypto_cipher_t *env) /* public key crypto */ -/** Generate a <b>bits</b>-bit new public/private keypair in <b>env</b>. - * Return 0 on success, -1 on failure. - */ -MOCK_IMPL(int, -crypto_pk_generate_key_with_bits,(crypto_pk_t *env, int bits)) -{ - tor_assert(env); - - if (env->key) { - RSA_free(env->key); - env->key = NULL; - } - - { - BIGNUM *e = BN_new(); - RSA *r = NULL; - if (!e) - goto done; - if (! BN_set_word(e, 65537)) - goto done; - r = RSA_new(); - if (!r) - goto done; - if (RSA_generate_key_ex(r, bits, e, NULL) == -1) - goto done; - - env->key = r; - r = NULL; - done: - if (e) - BN_clear_free(e); - if (r) - RSA_free(r); - } - - if (!env->key) { - crypto_log_errors(LOG_WARN, "generating RSA key"); - return -1; - } - - return 0; -} - -/** A PEM callback that always reports a failure to get a password */ -static int -pem_no_password_cb(char *buf, int size, int rwflag, void *u) -{ - (void)buf; - (void)size; - (void)rwflag; - (void)u; - return 0; -} - -/** Read a PEM-encoded private key from the <b>len</b>-byte string <b>s</b> - * into <b>env</b>. Return 0 on success, -1 on failure. If len is -1, - * the string is nul-terminated. - */ -int -crypto_pk_read_private_key_from_string(crypto_pk_t *env, - const char *s, ssize_t len) -{ - BIO *b; - - tor_assert(env); - tor_assert(s); - tor_assert(len < INT_MAX && len < SSIZE_T_CEILING); - - /* Create a read-only memory BIO, backed by the string 's' */ - b = BIO_new_mem_buf((char*)s, (int)len); - if (!b) - return -1; - - if (env->key) - RSA_free(env->key); - - env->key = PEM_read_bio_RSAPrivateKey(b,NULL,pem_no_password_cb,NULL); - - BIO_free(b); - - if (!env->key) { - crypto_log_errors(LOG_WARN, "Error parsing private key"); - return -1; - } - return 0; -} - -/** Read a PEM-encoded private key from the file named by - * <b>keyfile</b> into <b>env</b>. Return 0 on success, -1 on failure. - */ -int -crypto_pk_read_private_key_from_filename(crypto_pk_t *env, - const char *keyfile) -{ - char *contents; - int r; - - /* Read the file into a string. */ - contents = read_file_to_str(keyfile, 0, NULL); - if (!contents) { - log_warn(LD_CRYPTO, "Error reading private key from \"%s\"", keyfile); - return -1; - } - - /* Try to parse it. */ - r = crypto_pk_read_private_key_from_string(env, contents, -1); - memwipe(contents, 0, strlen(contents)); - tor_free(contents); - if (r) - return -1; /* read_private_key_from_string already warned, so we don't.*/ - - /* Make sure it's valid. */ - if (crypto_pk_check_key(env) <= 0) - return -1; - - return 0; -} - -/** Helper function to implement crypto_pk_write_*_key_to_string. Return 0 on - * success, -1 on failure. */ -static int -crypto_pk_write_key_to_string_impl(crypto_pk_t *env, char **dest, - size_t *len, int is_public) -{ - BUF_MEM *buf; - BIO *b; - int r; - - tor_assert(env); - tor_assert(env->key); - tor_assert(dest); - - b = BIO_new(BIO_s_mem()); /* Create a memory BIO */ - if (!b) - return -1; - - /* Now you can treat b as if it were a file. Just use the - * PEM_*_bio_* functions instead of the non-bio variants. - */ - if (is_public) - r = PEM_write_bio_RSAPublicKey(b, env->key); - else - r = PEM_write_bio_RSAPrivateKey(b, env->key, NULL,NULL,0,NULL,NULL); - - if (!r) { - crypto_log_errors(LOG_WARN, "writing RSA key to string"); - BIO_free(b); - return -1; - } - - BIO_get_mem_ptr(b, &buf); - - *dest = tor_malloc(buf->length+1); - memcpy(*dest, buf->data, buf->length); - (*dest)[buf->length] = 0; /* nul terminate it */ - *len = buf->length; - - BIO_free(b); - - return 0; -} - -/** PEM-encode the public key portion of <b>env</b> and write it to a - * newly allocated string. On success, set *<b>dest</b> to the new - * string, *<b>len</b> to the string's length, and return 0. On - * failure, return -1. - */ -int -crypto_pk_write_public_key_to_string(crypto_pk_t *env, char **dest, - size_t *len) -{ - return crypto_pk_write_key_to_string_impl(env, dest, len, 1); -} - -/** PEM-encode the private key portion of <b>env</b> and write it to a - * newly allocated string. On success, set *<b>dest</b> to the new - * string, *<b>len</b> to the string's length, and return 0. On - * failure, return -1. - */ -int -crypto_pk_write_private_key_to_string(crypto_pk_t *env, char **dest, - size_t *len) -{ - return crypto_pk_write_key_to_string_impl(env, dest, len, 0); -} - -/** Read a PEM-encoded public key from the first <b>len</b> characters of - * <b>src</b>, and store the result in <b>env</b>. Return 0 on success, -1 on - * failure. - */ -int -crypto_pk_read_public_key_from_string(crypto_pk_t *env, const char *src, - size_t len) -{ - BIO *b; - - tor_assert(env); - tor_assert(src); - tor_assert(len<INT_MAX); - - b = BIO_new(BIO_s_mem()); /* Create a memory BIO */ - if (!b) - return -1; - - BIO_write(b, src, (int)len); - - if (env->key) - RSA_free(env->key); - env->key = PEM_read_bio_RSAPublicKey(b, NULL, pem_no_password_cb, NULL); - BIO_free(b); - if (!env->key) { - crypto_log_errors(LOG_WARN, "reading public key from string"); - return -1; - } - - return 0; -} - -/** Write the private key from <b>env</b> into the file named by <b>fname</b>, - * PEM-encoded. Return 0 on success, -1 on failure. - */ -int -crypto_pk_write_private_key_to_filename(crypto_pk_t *env, - const char *fname) -{ - BIO *bio; - char *cp; - long len; - char *s; - int r; - - tor_assert(crypto_pk_private_ok(env)); - - if (!(bio = BIO_new(BIO_s_mem()))) - return -1; - if (PEM_write_bio_RSAPrivateKey(bio, env->key, NULL,NULL,0,NULL,NULL) - == 0) { - crypto_log_errors(LOG_WARN, "writing private key"); - BIO_free(bio); - return -1; - } - len = BIO_get_mem_data(bio, &cp); - tor_assert(len >= 0); - s = tor_malloc(len+1); - memcpy(s, cp, len); - s[len]='\0'; - r = write_str_to_file(fname, s, 0); - BIO_free(bio); - memwipe(s, 0, strlen(s)); - tor_free(s); - return r; -} - -/** Return true iff <b>env</b> has a valid key. - */ -int -crypto_pk_check_key(crypto_pk_t *env) -{ - int r; - tor_assert(env); - - r = RSA_check_key(env->key); - if (r <= 0) - crypto_log_errors(LOG_WARN,"checking RSA key"); - return r; -} - -/** Return true iff <b>key</b> contains the private-key portion of the RSA - * key. */ -int -crypto_pk_key_is_private(const crypto_pk_t *key) -{ - tor_assert(key); - return crypto_pk_private_ok(key); -} - -/** Return true iff <b>env</b> contains a public key whose public exponent - * equals 65537. - */ -int -crypto_pk_public_exponent_ok(crypto_pk_t *env) -{ - tor_assert(env); - tor_assert(env->key); - - const BIGNUM *e; - -#ifdef OPENSSL_1_1_API - const BIGNUM *n, *d; - RSA_get0_key(env->key, &n, &e, &d); -#else - e = env->key->e; -#endif /* defined(OPENSSL_1_1_API) */ - return BN_is_word(e, 65537); -} - -/** Compare the public-key components of a and b. Return less than 0 - * if a\<b, 0 if a==b, and greater than 0 if a\>b. A NULL key is - * considered to be less than all non-NULL keys, and equal to itself. - * - * Note that this may leak information about the keys through timing. - */ -int -crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b) -{ - int result; - char a_is_non_null = (a != NULL) && (a->key != NULL); - char b_is_non_null = (b != NULL) && (b->key != NULL); - char an_argument_is_null = !a_is_non_null | !b_is_non_null; - - result = tor_memcmp(&a_is_non_null, &b_is_non_null, sizeof(a_is_non_null)); - if (an_argument_is_null) - return result; - - const BIGNUM *a_n, *a_e; - const BIGNUM *b_n, *b_e; - -#ifdef OPENSSL_1_1_API - const BIGNUM *a_d, *b_d; - RSA_get0_key(a->key, &a_n, &a_e, &a_d); - RSA_get0_key(b->key, &b_n, &b_e, &b_d); -#else - a_n = a->key->n; - a_e = a->key->e; - b_n = b->key->n; - b_e = b->key->e; -#endif /* defined(OPENSSL_1_1_API) */ - - tor_assert(a_n != NULL && a_e != NULL); - tor_assert(b_n != NULL && b_e != NULL); - - result = BN_cmp(a_n, b_n); - if (result) - return result; - return BN_cmp(a_e, b_e); -} - -/** Compare the public-key components of a and b. Return non-zero iff - * a==b. A NULL key is considered to be distinct from all non-NULL - * keys, and equal to itself. - * - * Note that this may leak information about the keys through timing. - */ -int -crypto_pk_eq_keys(const crypto_pk_t *a, const crypto_pk_t *b) -{ - return (crypto_pk_cmp_keys(a, b) == 0); -} - -/** Return the size of the public key modulus in <b>env</b>, in bytes. */ -size_t -crypto_pk_keysize(const crypto_pk_t *env) -{ - tor_assert(env); - tor_assert(env->key); - - return (size_t) RSA_size((RSA*)env->key); -} - -/** Return the size of the public key modulus of <b>env</b>, in bits. */ -int -crypto_pk_num_bits(crypto_pk_t *env) -{ - tor_assert(env); - tor_assert(env->key); - -#ifdef OPENSSL_1_1_API - /* It's so stupid that there's no other way to check that n is valid - * before calling RSA_bits(). - */ - const BIGNUM *n, *e, *d; - RSA_get0_key(env->key, &n, &e, &d); - tor_assert(n != NULL); - - return RSA_bits(env->key); -#else /* !(defined(OPENSSL_1_1_API)) */ - tor_assert(env->key->n); - return BN_num_bits(env->key->n); -#endif /* defined(OPENSSL_1_1_API) */ -} - -/** Increase the reference count of <b>env</b>, and return it. - */ -crypto_pk_t * -crypto_pk_dup_key(crypto_pk_t *env) -{ - tor_assert(env); - tor_assert(env->key); - - env->refs++; - return env; -} - -#ifdef TOR_UNIT_TESTS -/** For testing: replace dest with src. (Dest must have a refcount - * of 1) */ -void -crypto_pk_assign_(crypto_pk_t *dest, const crypto_pk_t *src) -{ - tor_assert(dest); - tor_assert(dest->refs == 1); - tor_assert(src); - RSA_free(dest->key); - dest->key = RSAPrivateKey_dup(src->key); -} -#endif /* defined(TOR_UNIT_TESTS) */ - -/** Make a real honest-to-goodness copy of <b>env</b>, and return it. - * Returns NULL on failure. */ -crypto_pk_t * -crypto_pk_copy_full(crypto_pk_t *env) -{ - RSA *new_key; - int privatekey = 0; - tor_assert(env); - tor_assert(env->key); - - if (crypto_pk_private_ok(env)) { - new_key = RSAPrivateKey_dup(env->key); - privatekey = 1; - } else { - new_key = RSAPublicKey_dup(env->key); - } - if (!new_key) { - /* LCOV_EXCL_START - * - * We can't cause RSA*Key_dup() to fail, so we can't really test this. - */ - log_err(LD_CRYPTO, "Unable to duplicate a %s key: openssl failed.", - privatekey?"private":"public"); - crypto_log_errors(LOG_ERR, - privatekey ? "Duplicating a private key" : - "Duplicating a public key"); - tor_fragile_assert(); - return NULL; - /* LCOV_EXCL_STOP */ - } - - return crypto_new_pk_from_rsa_(new_key); -} - -/** Encrypt <b>fromlen</b> bytes from <b>from</b> with the public key - * in <b>env</b>, using the padding method <b>padding</b>. On success, - * write the result to <b>to</b>, and return the number of bytes - * written. On failure, return -1. - * - * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be - * at least the length of the modulus of <b>env</b>. - */ -int -crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen, - const char *from, size_t fromlen, int padding) -{ - int r; - tor_assert(env); - tor_assert(from); - tor_assert(to); - tor_assert(fromlen<INT_MAX); - tor_assert(tolen >= crypto_pk_keysize(env)); - - r = RSA_public_encrypt((int)fromlen, - (unsigned char*)from, (unsigned char*)to, - env->key, crypto_get_rsa_padding(padding)); - if (r<0) { - crypto_log_errors(LOG_WARN, "performing RSA encryption"); - return -1; - } - return r; -} - -/** Decrypt <b>fromlen</b> bytes from <b>from</b> with the private key - * in <b>env</b>, using the padding method <b>padding</b>. On success, - * write the result to <b>to</b>, and return the number of bytes - * written. On failure, return -1. - * - * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be - * at least the length of the modulus of <b>env</b>. - */ -int -crypto_pk_private_decrypt(crypto_pk_t *env, char *to, - size_t tolen, - const char *from, size_t fromlen, - int padding, int warnOnFailure) -{ - int r; - tor_assert(env); - tor_assert(from); - tor_assert(to); - tor_assert(env->key); - tor_assert(fromlen<INT_MAX); - tor_assert(tolen >= crypto_pk_keysize(env)); - if (!crypto_pk_key_is_private(env)) - /* Not a private key */ - return -1; - - r = RSA_private_decrypt((int)fromlen, - (unsigned char*)from, (unsigned char*)to, - env->key, crypto_get_rsa_padding(padding)); - - if (r<0) { - crypto_log_errors(warnOnFailure?LOG_WARN:LOG_DEBUG, - "performing RSA decryption"); - return -1; - } - return r; -} - -/** Check the signature in <b>from</b> (<b>fromlen</b> bytes long) with the - * public key in <b>env</b>, using PKCS1 padding. On success, write the - * signed data to <b>to</b>, and return the number of bytes written. - * On failure, return -1. - * - * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be - * at least the length of the modulus of <b>env</b>. - */ -MOCK_IMPL(int, -crypto_pk_public_checksig,(const crypto_pk_t *env, char *to, - size_t tolen, - const char *from, size_t fromlen)) -{ - int r; - tor_assert(env); - tor_assert(from); - tor_assert(to); - tor_assert(fromlen < INT_MAX); - tor_assert(tolen >= crypto_pk_keysize(env)); - r = RSA_public_decrypt((int)fromlen, - (unsigned char*)from, (unsigned char*)to, - env->key, RSA_PKCS1_PADDING); - - if (r<0) { - crypto_log_errors(LOG_INFO, "checking RSA signature"); - return -1; - } - return r; -} - /** Check a siglen-byte long signature at <b>sig</b> against * <b>datalen</b> bytes of data at <b>data</b>, using the public key * in <b>env</b>. Return 0 if <b>sig</b> is a correct signature for @@ -1108,38 +442,6 @@ crypto_pk_public_checksig_digest,(crypto_pk_t *env, const char *data, return 0; } -/** Sign <b>fromlen</b> bytes of data from <b>from</b> with the private key in - * <b>env</b>, using PKCS1 padding. On success, write the signature to - * <b>to</b>, and return the number of bytes written. On failure, return - * -1. - * - * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be - * at least the length of the modulus of <b>env</b>. - */ -int -crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen, - const char *from, size_t fromlen) -{ - int r; - tor_assert(env); - tor_assert(from); - tor_assert(to); - tor_assert(fromlen < INT_MAX); - tor_assert(tolen >= crypto_pk_keysize(env)); - if (!crypto_pk_key_is_private(env)) - /* Not a private key */ - return -1; - - r = RSA_private_encrypt((int)fromlen, - (unsigned char*)from, (unsigned char*)to, - (RSA*)env->key, RSA_PKCS1_PADDING); - if (r<0) { - crypto_log_errors(LOG_WARN, "generating RSA signature"); - return -1; - } - return r; -} - /** Compute a SHA1 digest of <b>fromlen</b> bytes of data stored at * <b>from</b>; sign the data with the private key in <b>env</b>, and * store it in <b>to</b>. Return the number of bytes written on @@ -1303,51 +605,6 @@ crypto_pk_obsolete_private_hybrid_decrypt(crypto_pk_t *env, return -1; } -/** ASN.1-encode the public portion of <b>pk</b> into <b>dest</b>. - * Return -1 on error, or the number of characters used on success. - */ -int -crypto_pk_asn1_encode(crypto_pk_t *pk, char *dest, size_t dest_len) -{ - int len; - unsigned char *buf = NULL; - - len = i2d_RSAPublicKey(pk->key, &buf); - if (len < 0 || buf == NULL) - return -1; - - if ((size_t)len > dest_len || dest_len > SIZE_T_CEILING) { - OPENSSL_free(buf); - return -1; - } - /* We don't encode directly into 'dest', because that would be illegal - * type-punning. (C99 is smarter than me, C99 is smarter than me...) - */ - memcpy(dest,buf,len); - OPENSSL_free(buf); - return len; -} - -/** Decode an ASN.1-encoded public key from <b>str</b>; return the result on - * success and NULL on failure. - */ -crypto_pk_t * -crypto_pk_asn1_decode(const char *str, size_t len) -{ - RSA *rsa; - unsigned char *buf; - const unsigned char *cp; - cp = buf = tor_malloc(len); - memcpy(buf,str,len); - rsa = d2i_RSAPublicKey(NULL, &cp, len); - tor_free(buf); - if (!rsa) { - crypto_log_errors(LOG_WARN,"decoding public key"); - return NULL; - } - return crypto_new_pk_from_rsa_(rsa); -} - /** Given a private or public key <b>pk</b>, put a SHA1 hash of the * public key into <b>digest_out</b> (must have DIGEST_LEN bytes of space). * Return 0 on success, -1 on failure. @@ -1355,18 +612,24 @@ crypto_pk_asn1_decode(const char *str, size_t len) int crypto_pk_get_digest(const crypto_pk_t *pk, char *digest_out) { - unsigned char *buf = NULL; + char *buf; + size_t buflen; int len; + int rv = -1; - len = i2d_RSAPublicKey((RSA*)pk->key, &buf); - if (len < 0 || buf == NULL) - return -1; - if (crypto_digest(digest_out, (char*)buf, len) < 0) { - OPENSSL_free(buf); - return -1; - } - OPENSSL_free(buf); - return 0; + buflen = crypto_pk_keysize(pk)*2; + buf = tor_malloc(buflen); + len = crypto_pk_asn1_encode(pk, buf, buflen); + if (len < 0) + goto done; + + if (crypto_digest(digest_out, buf, len) < 0) + goto done; + + rv = 0; + done: + tor_free(buf); + return rv; } /** Compute all digests of the DER encoding of <b>pk</b>, and store them @@ -1374,18 +637,24 @@ crypto_pk_get_digest(const crypto_pk_t *pk, char *digest_out) int crypto_pk_get_common_digests(crypto_pk_t *pk, common_digests_t *digests_out) { - unsigned char *buf = NULL; + char *buf; + size_t buflen; int len; + int rv = -1; - len = i2d_RSAPublicKey(pk->key, &buf); - if (len < 0 || buf == NULL) - return -1; - if (crypto_common_digests(digests_out, (char*)buf, len) < 0) { - OPENSSL_free(buf); - return -1; - } - OPENSSL_free(buf); - return 0; + buflen = crypto_pk_keysize(pk)*2; + buf = tor_malloc(buflen); + len = crypto_pk_asn1_encode(pk, buf, buflen); + if (len < 0) + goto done; + + if (crypto_common_digests(digests_out, (char*)buf, len) < 0) + goto done; + + rv = 0; + done: + tor_free(buf); + return rv; } /** Copy <b>in</b> to the <b>outlen</b>-byte buffer <b>out</b>, adding spaces @@ -1408,127 +677,6 @@ crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in) *out = '\0'; } -/** Given a private or public key <b>pk</b>, put a fingerprint of the - * public key into <b>fp_out</b> (must have at least FINGERPRINT_LEN+1 bytes of - * space). Return 0 on success, -1 on failure. - * - * Fingerprints are computed as the SHA1 digest of the ASN.1 encoding - * of the public key, converted to hexadecimal, in upper case, with a - * space after every four digits. - * - * If <b>add_space</b> is false, omit the spaces. - */ -int -crypto_pk_get_fingerprint(crypto_pk_t *pk, char *fp_out, int add_space) -{ - char digest[DIGEST_LEN]; - char hexdigest[HEX_DIGEST_LEN+1]; - if (crypto_pk_get_digest(pk, digest)) { - return -1; - } - base16_encode(hexdigest,sizeof(hexdigest),digest,DIGEST_LEN); - if (add_space) { - crypto_add_spaces_to_fp(fp_out, FINGERPRINT_LEN+1, hexdigest); - } else { - strncpy(fp_out, hexdigest, HEX_DIGEST_LEN+1); - } - return 0; -} - -/** Given a private or public key <b>pk</b>, put a hashed fingerprint of - * the public key into <b>fp_out</b> (must have at least FINGERPRINT_LEN+1 - * bytes of space). Return 0 on success, -1 on failure. - * - * Hashed fingerprints are computed as the SHA1 digest of the SHA1 digest - * of the ASN.1 encoding of the public key, converted to hexadecimal, in - * upper case. - */ -int -crypto_pk_get_hashed_fingerprint(crypto_pk_t *pk, char *fp_out) -{ - char digest[DIGEST_LEN], hashed_digest[DIGEST_LEN]; - if (crypto_pk_get_digest(pk, digest)) { - return -1; - } - if (crypto_digest(hashed_digest, digest, DIGEST_LEN) < 0) { - return -1; - } - base16_encode(fp_out, FINGERPRINT_LEN + 1, hashed_digest, DIGEST_LEN); - return 0; -} - -/** Given a crypto_pk_t <b>pk</b>, allocate a new buffer containing the - * Base64 encoding of the DER representation of the private key as a NUL - * terminated string, and return it via <b>priv_out</b>. Return 0 on - * sucess, -1 on failure. - * - * It is the caller's responsibility to sanitize and free the resulting buffer. - */ -int -crypto_pk_base64_encode(const crypto_pk_t *pk, char **priv_out) -{ - unsigned char *der = NULL; - int der_len; - int ret = -1; - - *priv_out = NULL; - - der_len = i2d_RSAPrivateKey(pk->key, &der); - if (der_len < 0 || der == NULL) - return ret; - - size_t priv_len = base64_encode_size(der_len, 0) + 1; - char *priv = tor_malloc_zero(priv_len); - if (base64_encode(priv, priv_len, (char *)der, der_len, 0) >= 0) { - *priv_out = priv; - ret = 0; - } else { - tor_free(priv); - } - - memwipe(der, 0, der_len); - OPENSSL_free(der); - return ret; -} - -/** Given a string containing the Base64 encoded DER representation of the - * private key <b>str</b>, decode and return the result on success, or NULL - * on failure. - */ -crypto_pk_t * -crypto_pk_base64_decode(const char *str, size_t len) -{ - crypto_pk_t *pk = NULL; - - char *der = tor_malloc_zero(len + 1); - int der_len = base64_decode(der, len, str, len); - if (der_len <= 0) { - log_warn(LD_CRYPTO, "Stored RSA private key seems corrupted (base64)."); - goto out; - } - - const unsigned char *dp = (unsigned char*)der; /* Shut the compiler up. */ - RSA *rsa = d2i_RSAPrivateKey(NULL, &dp, der_len); - if (!rsa) { - crypto_log_errors(LOG_WARN, "decoding private key"); - goto out; - } - - pk = crypto_new_pk_from_rsa_(rsa); - - /* Make sure it's valid. */ - if (crypto_pk_check_key(pk) <= 0) { - crypto_pk_free(pk); - pk = NULL; - goto out; - } - - out: - memwipe(der, 0, len + 1); - tor_free(der); - return pk; -} - /* symmetric crypto */ /** Encrypt <b>fromlen</b> bytes from <b>from</b> using the cipher @@ -3273,56 +2421,6 @@ memwipe(void *mem, uint8_t byte, size_t sz) memset(mem, byte, sz); } -#if 0 -/* This code is disabled, because OpenSSL never actually uses these callbacks. - */ - -/** OpenSSL helper type: wraps a Tor mutex so that OpenSSL can use it - * as a lock. */ -struct CRYPTO_dynlock_value { - tor_mutex_t *lock; -}; - -/** OpenSSL callback function to allocate a lock: see CRYPTO_set_dynlock_* - * documentation in OpenSSL's docs for more info. */ -static struct CRYPTO_dynlock_value * -openssl_dynlock_create_cb_(const char *file, int line) -{ - struct CRYPTO_dynlock_value *v; - (void)file; - (void)line; - v = tor_malloc(sizeof(struct CRYPTO_dynlock_value)); - v->lock = tor_mutex_new(); - return v; -} - -/** OpenSSL callback function to acquire or release a lock: see - * CRYPTO_set_dynlock_* documentation in OpenSSL's docs for more info. */ -static void -openssl_dynlock_lock_cb_(int mode, struct CRYPTO_dynlock_value *v, - const char *file, int line) -{ - (void)file; - (void)line; - if (mode & CRYPTO_LOCK) - tor_mutex_acquire(v->lock); - else - tor_mutex_release(v->lock); -} - -/** OpenSSL callback function to free a lock: see CRYPTO_set_dynlock_* - * documentation in OpenSSL's docs for more info. */ -static void -openssl_dynlock_destroy_cb_(struct CRYPTO_dynlock_value *v, - const char *file, int line) -{ - (void)file; - (void)line; - tor_mutex_free(v->lock); - tor_free(v); -} -#endif /* 0 */ - /** @{ */ /** Uninitialize the crypto library. Return 0 on success. Does not detect * failure. diff --git a/src/common/crypto.h b/src/common/crypto.h index 3caa23773d..a9c8837b9e 100644 --- a/src/common/crypto.h +++ b/src/common/crypto.h @@ -20,6 +20,7 @@ #include "testsupport.h" #include "compat.h" #include "util.h" +#include "crypto_rsa.h" #include "keccak-tiny/keccak-tiny.h" @@ -36,8 +37,6 @@ #define CIPHER_IV_LEN 16 /** Length of our symmetric cipher's keys of 256-bit. */ #define CIPHER256_KEY_LEN 32 -/** Length of our public keys. */ -#define PK_BYTES (1024/8) /** Length of our DH keys. */ #define DH_BYTES (1024/8) @@ -54,12 +53,6 @@ * signs removed. */ #define BASE64_DIGEST512_LEN 86 -/** Constant used to indicate OAEP padding for public-key encryption */ -#define PK_PKCS1_OAEP_PADDING 60002 - -/** Number of bytes added for PKCS1-OAEP padding. */ -#define PKCS1_OAEP_PADDING_OVERHEAD 42 - /** Length of encoded public key fingerprints, including space; but not * including terminating NUL. */ #define FINGERPRINT_LEN 49 @@ -92,7 +85,6 @@ typedef struct { char d[N_COMMON_DIGEST_ALGORITHMS][DIGEST256_LEN]; } common_digests_t; -typedef struct crypto_pk_t crypto_pk_t; typedef struct aes_cnt_cipher crypto_cipher_t; typedef struct crypto_digest_t crypto_digest_t; typedef struct crypto_xof_t crypto_xof_t; @@ -111,10 +103,6 @@ void crypto_thread_cleanup(void); int crypto_global_cleanup(void); /* environment setup */ -MOCK_DECL(crypto_pk_t *,crypto_pk_new,(void)); -void crypto_pk_free_(crypto_pk_t *env); -#define crypto_pk_free(pk) FREE_AND_NULL(crypto_pk_t, crypto_pk_free_, (pk)) - void crypto_set_tls_dh_prime(void); crypto_cipher_t *crypto_cipher_new(const char *key); crypto_cipher_t *crypto_cipher_new_with_bits(const char *key, int bits); @@ -126,47 +114,10 @@ void crypto_cipher_free_(crypto_cipher_t *env); #define crypto_cipher_free(c) \ FREE_AND_NULL(crypto_cipher_t, crypto_cipher_free_, (c)) -/* public key crypto */ -MOCK_DECL(int, crypto_pk_generate_key_with_bits,(crypto_pk_t *env, int bits)); -#define crypto_pk_generate_key(env) \ - crypto_pk_generate_key_with_bits((env), (PK_BYTES*8)) - -int crypto_pk_read_private_key_from_filename(crypto_pk_t *env, - const char *keyfile); -int crypto_pk_write_public_key_to_string(crypto_pk_t *env, - char **dest, size_t *len); -int crypto_pk_write_private_key_to_string(crypto_pk_t *env, - char **dest, size_t *len); -int crypto_pk_read_public_key_from_string(crypto_pk_t *env, - const char *src, size_t len); -int crypto_pk_read_private_key_from_string(crypto_pk_t *env, - const char *s, ssize_t len); -int crypto_pk_write_private_key_to_filename(crypto_pk_t *env, - const char *fname); - -int crypto_pk_check_key(crypto_pk_t *env); -int crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b); -int crypto_pk_eq_keys(const crypto_pk_t *a, const crypto_pk_t *b); -size_t crypto_pk_keysize(const crypto_pk_t *env); -int crypto_pk_num_bits(crypto_pk_t *env); -crypto_pk_t *crypto_pk_dup_key(crypto_pk_t *orig); -crypto_pk_t *crypto_pk_copy_full(crypto_pk_t *orig); -int crypto_pk_key_is_private(const crypto_pk_t *key); -int crypto_pk_public_exponent_ok(crypto_pk_t *env); - -int crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen, - const char *from, size_t fromlen, int padding); -int crypto_pk_private_decrypt(crypto_pk_t *env, char *to, size_t tolen, - const char *from, size_t fromlen, - int padding, int warnOnFailure); -MOCK_DECL(int, crypto_pk_public_checksig,(const crypto_pk_t *env, - char *to, size_t tolen, - const char *from, size_t fromlen)); +/* public key crypto */ MOCK_DECL(int, crypto_pk_public_checksig_digest,(crypto_pk_t *env, const char *data, size_t datalen, const char *sig, size_t siglen)); -int crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen, - const char *from, size_t fromlen); int crypto_pk_private_sign_digest(crypto_pk_t *env, char *to, size_t tolen, const char *from, size_t fromlen); int crypto_pk_obsolete_public_hybrid_encrypt(crypto_pk_t *env, char *to, @@ -177,17 +128,9 @@ int crypto_pk_obsolete_private_hybrid_decrypt(crypto_pk_t *env, char *to, size_t tolen, const char *from, size_t fromlen, int padding, int warnOnFailure); - -int crypto_pk_asn1_encode(crypto_pk_t *pk, char *dest, size_t dest_len); -crypto_pk_t *crypto_pk_asn1_decode(const char *str, size_t len); int crypto_pk_get_digest(const crypto_pk_t *pk, char *digest_out); int crypto_pk_get_common_digests(crypto_pk_t *pk, common_digests_t *digests_out); -int crypto_pk_get_fingerprint(crypto_pk_t *pk, char *fp_out,int add_space); -int crypto_pk_get_hashed_fingerprint(crypto_pk_t *pk, char *fp_out); - -int crypto_pk_base64_encode(const crypto_pk_t *pk, char **priv_out); -crypto_pk_t *crypto_pk_base64_decode(const char *str, size_t len); /* symmetric crypto */ const char *crypto_cipher_get_key(crypto_cipher_t *env); @@ -303,13 +246,7 @@ void memwipe(void *mem, uint8_t byte, size_t sz); /* Prototypes for private functions only used by tortls.c, crypto.c, and the * unit tests. */ -struct rsa_st; -struct evp_pkey_st; struct dh_st; -struct rsa_st *crypto_pk_get_rsa_(crypto_pk_t *env); -crypto_pk_t *crypto_new_pk_from_rsa_(struct rsa_st *rsa); -MOCK_DECL(struct evp_pkey_st *, crypto_pk_get_evp_pkey_,(crypto_pk_t *env, - int private)); struct dh_st *crypto_dh_get_dh_(crypto_dh_t *dh); void crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in); @@ -326,7 +263,6 @@ extern int break_strongest_rng_fallback; #endif /* defined(CRYPTO_PRIVATE) */ #ifdef TOR_UNIT_TESTS -void crypto_pk_assign_(crypto_pk_t *dest, const crypto_pk_t *src); digest_algorithm_t crypto_digest_get_algorithm(crypto_digest_t *digest); #endif diff --git a/src/common/crypto_rsa.c b/src/common/crypto_rsa.c new file mode 100644 index 0000000000..92b5978eaf --- /dev/null +++ b/src/common/crypto_rsa.c @@ -0,0 +1,923 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_rsa.c + * \brief Block of functions related with RSA utilities and operations. + **/ + +#include "crypto_rsa.h" +#include "crypto.h" +#include "compat_openssl.h" +#include "crypto_curve25519.h" +#include "crypto_ed25519.h" +#include "crypto_format.h" + +DISABLE_GCC_WARNING(redundant-decls) + +#include <openssl/err.h> +#include <openssl/rsa.h> +#include <openssl/pem.h> +#include <openssl/evp.h> +#include <openssl/engine.h> +#include <openssl/rand.h> +#include <openssl/bn.h> +#include <openssl/dh.h> +#include <openssl/conf.h> +#include <openssl/hmac.h> + +ENABLE_GCC_WARNING(redundant-decls) + +#include "torlog.h" +#include "util.h" +#include "util_format.h" + +/** Declaration for crypto_pk_t structure. */ +struct crypto_pk_t +{ + int refs; /**< reference count, so we don't have to copy keys */ + RSA *key; /**< The key itself */ +}; + +/** Log all pending crypto errors at level <b>severity</b>. Use + * <b>doing</b> to describe our current activities. + */ +static void +crypto_log_errors(int severity, const char *doing) +{ + unsigned long err; + const char *msg, *lib, *func; + while ((err = ERR_get_error()) != 0) { + msg = (const char*)ERR_reason_error_string(err); + lib = (const char*)ERR_lib_error_string(err); + func = (const char*)ERR_func_error_string(err); + if (!msg) msg = "(null)"; + if (!lib) lib = "(null)"; + if (!func) func = "(null)"; + if (BUG(!doing)) doing = "(null)"; + tor_log(severity, LD_CRYPTO, "crypto error while %s: %s (in %s:%s)", + doing, msg, lib, func); + } +} + +/** Return the number of bytes added by padding method <b>padding</b>. + */ +int +crypto_get_rsa_padding_overhead(int padding) +{ + switch (padding) + { + case RSA_PKCS1_OAEP_PADDING: return PKCS1_OAEP_PADDING_OVERHEAD; + default: tor_assert(0); return -1; // LCOV_EXCL_LINE + } +} + +/** Given a padding method <b>padding</b>, return the correct OpenSSL constant. + */ +int +crypto_get_rsa_padding(int padding) +{ + switch (padding) + { + case PK_PKCS1_OAEP_PADDING: return RSA_PKCS1_OAEP_PADDING; + default: tor_assert(0); return -1; // LCOV_EXCL_LINE + } +} + +/** used internally: quicly validate a crypto_pk_t object as a private key. + * Return 1 iff the public key is valid, 0 if obviously invalid. + */ +static int +crypto_pk_private_ok(const crypto_pk_t *k) +{ +#ifdef OPENSSL_1_1_API + if (!k || !k->key) + return 0; + + const BIGNUM *p, *q; + RSA_get0_factors(k->key, &p, &q); + return p != NULL; /* XXX/yawning: Should we check q? */ +#else /* !(defined(OPENSSL_1_1_API)) */ + return k && k->key && k->key->p; +#endif /* defined(OPENSSL_1_1_API) */ +} + +/** used by tortls.c: wrap an RSA* in a crypto_pk_t. */ +crypto_pk_t * +crypto_new_pk_from_rsa_(RSA *rsa) +{ + crypto_pk_t *env; + tor_assert(rsa); + env = tor_malloc(sizeof(crypto_pk_t)); + env->refs = 1; + env->key = rsa; + return env; +} + +/** Helper, used by tor-gencert.c. Return the RSA from a + * crypto_pk_t. */ +RSA * +crypto_pk_get_rsa_(crypto_pk_t *env) +{ + return env->key; +} + +/** used by tortls.c: get an equivalent EVP_PKEY* for a crypto_pk_t. Iff + * private is set, include the private-key portion of the key. Return a valid + * pointer on success, and NULL on failure. */ +MOCK_IMPL(EVP_PKEY *, +crypto_pk_get_evp_pkey_,(crypto_pk_t *env, int private)) +{ + RSA *key = NULL; + EVP_PKEY *pkey = NULL; + tor_assert(env->key); + if (private) { + if (!(key = RSAPrivateKey_dup(env->key))) + goto error; + } else { + if (!(key = RSAPublicKey_dup(env->key))) + goto error; + } + if (!(pkey = EVP_PKEY_new())) + goto error; + if (!(EVP_PKEY_assign_RSA(pkey, key))) + goto error; + return pkey; + error: + if (pkey) + EVP_PKEY_free(pkey); + if (key) + RSA_free(key); + return NULL; +} + +/** Allocate and return storage for a public key. The key itself will not yet + * be set. + */ +MOCK_IMPL(crypto_pk_t *, +crypto_pk_new,(void)) +{ + RSA *rsa; + + rsa = RSA_new(); + tor_assert(rsa); + return crypto_new_pk_from_rsa_(rsa); +} + +/** Release a reference to an asymmetric key; when all the references + * are released, free the key. + */ +void +crypto_pk_free_(crypto_pk_t *env) +{ + if (!env) + return; + + if (--env->refs > 0) + return; + tor_assert(env->refs == 0); + + if (env->key) + RSA_free(env->key); + + tor_free(env); +} + +/** Generate a <b>bits</b>-bit new public/private keypair in <b>env</b>. + * Return 0 on success, -1 on failure. + */ +MOCK_IMPL(int, +crypto_pk_generate_key_with_bits,(crypto_pk_t *env, int bits)) +{ + tor_assert(env); + + if (env->key) { + RSA_free(env->key); + env->key = NULL; + } + + { + BIGNUM *e = BN_new(); + RSA *r = NULL; + if (!e) + goto done; + if (! BN_set_word(e, 65537)) + goto done; + r = RSA_new(); + if (!r) + goto done; + if (RSA_generate_key_ex(r, bits, e, NULL) == -1) + goto done; + + env->key = r; + r = NULL; + done: + if (e) + BN_clear_free(e); + if (r) + RSA_free(r); + } + + if (!env->key) { + crypto_log_errors(LOG_WARN, "generating RSA key"); + return -1; + } + + return 0; +} + +/** A PEM callback that always reports a failure to get a password */ +static int +pem_no_password_cb(char *buf, int size, int rwflag, void *u) +{ + (void)buf; + (void)size; + (void)rwflag; + (void)u; + return 0; +} + +/** Read a PEM-encoded private key from the <b>len</b>-byte string <b>s</b> + * into <b>env</b>. Return 0 on success, -1 on failure. If len is -1, + * the string is nul-terminated. + */ +int +crypto_pk_read_private_key_from_string(crypto_pk_t *env, + const char *s, ssize_t len) +{ + BIO *b; + + tor_assert(env); + tor_assert(s); + tor_assert(len < INT_MAX && len < SSIZE_T_CEILING); + + /* Create a read-only memory BIO, backed by the string 's' */ + b = BIO_new_mem_buf((char*)s, (int)len); + if (!b) + return -1; + + if (env->key) + RSA_free(env->key); + + env->key = PEM_read_bio_RSAPrivateKey(b,NULL,pem_no_password_cb,NULL); + + BIO_free(b); + + if (!env->key) { + crypto_log_errors(LOG_WARN, "Error parsing private key"); + return -1; + } + return 0; +} + +/** Read a PEM-encoded private key from the file named by + * <b>keyfile</b> into <b>env</b>. Return 0 on success, -1 on failure. + */ +int +crypto_pk_read_private_key_from_filename(crypto_pk_t *env, + const char *keyfile) +{ + char *contents; + int r; + + /* Read the file into a string. */ + contents = read_file_to_str(keyfile, 0, NULL); + if (!contents) { + log_warn(LD_CRYPTO, "Error reading private key from \"%s\"", keyfile); + return -1; + } + + /* Try to parse it. */ + r = crypto_pk_read_private_key_from_string(env, contents, -1); + memwipe(contents, 0, strlen(contents)); + tor_free(contents); + if (r) + return -1; /* read_private_key_from_string already warned, so we don't.*/ + + /* Make sure it's valid. */ + if (crypto_pk_check_key(env) <= 0) + return -1; + + return 0; +} + +/** Helper function to implement crypto_pk_write_*_key_to_string. Return 0 on + * success, -1 on failure. */ +static int +crypto_pk_write_key_to_string_impl(crypto_pk_t *env, char **dest, + size_t *len, int is_public) +{ + BUF_MEM *buf; + BIO *b; + int r; + + tor_assert(env); + tor_assert(env->key); + tor_assert(dest); + + b = BIO_new(BIO_s_mem()); /* Create a memory BIO */ + if (!b) + return -1; + + /* Now you can treat b as if it were a file. Just use the + * PEM_*_bio_* functions instead of the non-bio variants. + */ + if (is_public) + r = PEM_write_bio_RSAPublicKey(b, env->key); + else + r = PEM_write_bio_RSAPrivateKey(b, env->key, NULL,NULL,0,NULL,NULL); + + if (!r) { + crypto_log_errors(LOG_WARN, "writing RSA key to string"); + BIO_free(b); + return -1; + } + + BIO_get_mem_ptr(b, &buf); + + *dest = tor_malloc(buf->length+1); + memcpy(*dest, buf->data, buf->length); + (*dest)[buf->length] = 0; /* nul terminate it */ + *len = buf->length; + + BIO_free(b); + + return 0; +} + +/** PEM-encode the public key portion of <b>env</b> and write it to a + * newly allocated string. On success, set *<b>dest</b> to the new + * string, *<b>len</b> to the string's length, and return 0. On + * failure, return -1. + */ +int +crypto_pk_write_public_key_to_string(crypto_pk_t *env, char **dest, + size_t *len) +{ + return crypto_pk_write_key_to_string_impl(env, dest, len, 1); +} + +/** PEM-encode the private key portion of <b>env</b> and write it to a + * newly allocated string. On success, set *<b>dest</b> to the new + * string, *<b>len</b> to the string's length, and return 0. On + * failure, return -1. + */ +int +crypto_pk_write_private_key_to_string(crypto_pk_t *env, char **dest, + size_t *len) +{ + return crypto_pk_write_key_to_string_impl(env, dest, len, 0); +} + +/** Read a PEM-encoded public key from the first <b>len</b> characters of + * <b>src</b>, and store the result in <b>env</b>. Return 0 on success, -1 on + * failure. + */ +int +crypto_pk_read_public_key_from_string(crypto_pk_t *env, const char *src, + size_t len) +{ + BIO *b; + + tor_assert(env); + tor_assert(src); + tor_assert(len<INT_MAX); + + b = BIO_new(BIO_s_mem()); /* Create a memory BIO */ + if (!b) + return -1; + + BIO_write(b, src, (int)len); + + if (env->key) + RSA_free(env->key); + env->key = PEM_read_bio_RSAPublicKey(b, NULL, pem_no_password_cb, NULL); + BIO_free(b); + if (!env->key) { + crypto_log_errors(LOG_WARN, "reading public key from string"); + return -1; + } + + return 0; +} + +/** Write the private key from <b>env</b> into the file named by <b>fname</b>, + * PEM-encoded. Return 0 on success, -1 on failure. + */ +int +crypto_pk_write_private_key_to_filename(crypto_pk_t *env, + const char *fname) +{ + BIO *bio; + char *cp; + long len; + char *s; + int r; + + tor_assert(crypto_pk_private_ok(env)); + + if (!(bio = BIO_new(BIO_s_mem()))) + return -1; + if (PEM_write_bio_RSAPrivateKey(bio, env->key, NULL,NULL,0,NULL,NULL) + == 0) { + crypto_log_errors(LOG_WARN, "writing private key"); + BIO_free(bio); + return -1; + } + len = BIO_get_mem_data(bio, &cp); + tor_assert(len >= 0); + s = tor_malloc(len+1); + memcpy(s, cp, len); + s[len]='\0'; + r = write_str_to_file(fname, s, 0); + BIO_free(bio); + memwipe(s, 0, strlen(s)); + tor_free(s); + return r; +} + +/** Return true iff <b>env</b> has a valid key. + */ +int +crypto_pk_check_key(crypto_pk_t *env) +{ + int r; + tor_assert(env); + + r = RSA_check_key(env->key); + if (r <= 0) + crypto_log_errors(LOG_WARN,"checking RSA key"); + return r; +} + +/** Return true iff <b>key</b> contains the private-key portion of the RSA + * key. */ +int +crypto_pk_key_is_private(const crypto_pk_t *key) +{ + tor_assert(key); + return crypto_pk_private_ok(key); +} + +/** Return true iff <b>env</b> contains a public key whose public exponent + * equals 65537. + */ +int +crypto_pk_public_exponent_ok(crypto_pk_t *env) +{ + tor_assert(env); + tor_assert(env->key); + + const BIGNUM *e; + +#ifdef OPENSSL_1_1_API + const BIGNUM *n, *d; + RSA_get0_key(env->key, &n, &e, &d); +#else + e = env->key->e; +#endif /* defined(OPENSSL_1_1_API) */ + return BN_is_word(e, 65537); +} + +/** Compare the public-key components of a and b. Return less than 0 + * if a\<b, 0 if a==b, and greater than 0 if a\>b. A NULL key is + * considered to be less than all non-NULL keys, and equal to itself. + * + * Note that this may leak information about the keys through timing. + */ +int +crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b) +{ + int result; + char a_is_non_null = (a != NULL) && (a->key != NULL); + char b_is_non_null = (b != NULL) && (b->key != NULL); + char an_argument_is_null = !a_is_non_null | !b_is_non_null; + + result = tor_memcmp(&a_is_non_null, &b_is_non_null, sizeof(a_is_non_null)); + if (an_argument_is_null) + return result; + + const BIGNUM *a_n, *a_e; + const BIGNUM *b_n, *b_e; + +#ifdef OPENSSL_1_1_API + const BIGNUM *a_d, *b_d; + RSA_get0_key(a->key, &a_n, &a_e, &a_d); + RSA_get0_key(b->key, &b_n, &b_e, &b_d); +#else + a_n = a->key->n; + a_e = a->key->e; + b_n = b->key->n; + b_e = b->key->e; +#endif /* defined(OPENSSL_1_1_API) */ + + tor_assert(a_n != NULL && a_e != NULL); + tor_assert(b_n != NULL && b_e != NULL); + + result = BN_cmp(a_n, b_n); + if (result) + return result; + return BN_cmp(a_e, b_e); +} + +/** Compare the public-key components of a and b. Return non-zero iff + * a==b. A NULL key is considered to be distinct from all non-NULL + * keys, and equal to itself. + * + * Note that this may leak information about the keys through timing. + */ +int +crypto_pk_eq_keys(const crypto_pk_t *a, const crypto_pk_t *b) +{ + return (crypto_pk_cmp_keys(a, b) == 0); +} + +/** Return the size of the public key modulus in <b>env</b>, in bytes. */ +size_t +crypto_pk_keysize(const crypto_pk_t *env) +{ + tor_assert(env); + tor_assert(env->key); + + return (size_t) RSA_size((RSA*)env->key); +} + +/** Return the size of the public key modulus of <b>env</b>, in bits. */ +int +crypto_pk_num_bits(crypto_pk_t *env) +{ + tor_assert(env); + tor_assert(env->key); + +#ifdef OPENSSL_1_1_API + /* It's so stupid that there's no other way to check that n is valid + * before calling RSA_bits(). + */ + const BIGNUM *n, *e, *d; + RSA_get0_key(env->key, &n, &e, &d); + tor_assert(n != NULL); + + return RSA_bits(env->key); +#else /* !(defined(OPENSSL_1_1_API)) */ + tor_assert(env->key->n); + return BN_num_bits(env->key->n); +#endif /* defined(OPENSSL_1_1_API) */ +} + +/** Increase the reference count of <b>env</b>, and return it. + */ +crypto_pk_t * +crypto_pk_dup_key(crypto_pk_t *env) +{ + tor_assert(env); + tor_assert(env->key); + + env->refs++; + return env; +} + +#ifdef TOR_UNIT_TESTS +/** For testing: replace dest with src. (Dest must have a refcount + * of 1) */ +void +crypto_pk_assign_(crypto_pk_t *dest, const crypto_pk_t *src) +{ + tor_assert(dest); + tor_assert(dest->refs == 1); + tor_assert(src); + RSA_free(dest->key); + dest->key = RSAPrivateKey_dup(src->key); +} +#endif /* defined(TOR_UNIT_TESTS) */ + +/** Make a real honest-to-goodness copy of <b>env</b>, and return it. + * Returns NULL on failure. */ +crypto_pk_t * +crypto_pk_copy_full(crypto_pk_t *env) +{ + RSA *new_key; + int privatekey = 0; + tor_assert(env); + tor_assert(env->key); + + if (crypto_pk_private_ok(env)) { + new_key = RSAPrivateKey_dup(env->key); + privatekey = 1; + } else { + new_key = RSAPublicKey_dup(env->key); + } + if (!new_key) { + /* LCOV_EXCL_START + * + * We can't cause RSA*Key_dup() to fail, so we can't really test this. + */ + log_err(LD_CRYPTO, "Unable to duplicate a %s key: openssl failed.", + privatekey?"private":"public"); + crypto_log_errors(LOG_ERR, + privatekey ? "Duplicating a private key" : + "Duplicating a public key"); + tor_fragile_assert(); + return NULL; + /* LCOV_EXCL_STOP */ + } + + return crypto_new_pk_from_rsa_(new_key); +} + +/** Encrypt <b>fromlen</b> bytes from <b>from</b> with the public key + * in <b>env</b>, using the padding method <b>padding</b>. On success, + * write the result to <b>to</b>, and return the number of bytes + * written. On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +int +crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen, int padding) +{ + int r; + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(fromlen<INT_MAX); + tor_assert(tolen >= crypto_pk_keysize(env)); + + r = RSA_public_encrypt((int)fromlen, + (unsigned char*)from, (unsigned char*)to, + env->key, crypto_get_rsa_padding(padding)); + if (r<0) { + crypto_log_errors(LOG_WARN, "performing RSA encryption"); + return -1; + } + return r; +} + +/** Decrypt <b>fromlen</b> bytes from <b>from</b> with the private key + * in <b>env</b>, using the padding method <b>padding</b>. On success, + * write the result to <b>to</b>, and return the number of bytes + * written. On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +int +crypto_pk_private_decrypt(crypto_pk_t *env, char *to, + size_t tolen, + const char *from, size_t fromlen, + int padding, int warnOnFailure) +{ + int r; + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(env->key); + tor_assert(fromlen<INT_MAX); + tor_assert(tolen >= crypto_pk_keysize(env)); + if (!crypto_pk_key_is_private(env)) + /* Not a private key */ + return -1; + + r = RSA_private_decrypt((int)fromlen, + (unsigned char*)from, (unsigned char*)to, + env->key, crypto_get_rsa_padding(padding)); + + if (r<0) { + crypto_log_errors(warnOnFailure?LOG_WARN:LOG_DEBUG, + "performing RSA decryption"); + return -1; + } + return r; +} + +/** Check the signature in <b>from</b> (<b>fromlen</b> bytes long) with the + * public key in <b>env</b>, using PKCS1 padding. On success, write the + * signed data to <b>to</b>, and return the number of bytes written. + * On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +MOCK_IMPL(int, +crypto_pk_public_checksig,(const crypto_pk_t *env, char *to, + size_t tolen, + const char *from, size_t fromlen)) +{ + int r; + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(fromlen < INT_MAX); + tor_assert(tolen >= crypto_pk_keysize(env)); + r = RSA_public_decrypt((int)fromlen, + (unsigned char*)from, (unsigned char*)to, + env->key, RSA_PKCS1_PADDING); + + if (r<0) { + crypto_log_errors(LOG_INFO, "checking RSA signature"); + return -1; + } + return r; +} + +/** Sign <b>fromlen</b> bytes of data from <b>from</b> with the private key in + * <b>env</b>, using PKCS1 padding. On success, write the signature to + * <b>to</b>, and return the number of bytes written. On failure, return + * -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +int +crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen) +{ + int r; + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(fromlen < INT_MAX); + tor_assert(tolen >= crypto_pk_keysize(env)); + if (!crypto_pk_key_is_private(env)) + /* Not a private key */ + return -1; + + r = RSA_private_encrypt((int)fromlen, + (unsigned char*)from, (unsigned char*)to, + (RSA*)env->key, RSA_PKCS1_PADDING); + if (r<0) { + crypto_log_errors(LOG_WARN, "generating RSA signature"); + return -1; + } + return r; +} + +/** ASN.1-encode the public portion of <b>pk</b> into <b>dest</b>. + * Return -1 on error, or the number of characters used on success. + */ +int +crypto_pk_asn1_encode(const crypto_pk_t *pk, char *dest, size_t dest_len) +{ + int len; + unsigned char *buf = NULL; + + len = i2d_RSAPublicKey(pk->key, &buf); + if (len < 0 || buf == NULL) + return -1; + + if ((size_t)len > dest_len || dest_len > SIZE_T_CEILING) { + OPENSSL_free(buf); + return -1; + } + /* We don't encode directly into 'dest', because that would be illegal + * type-punning. (C99 is smarter than me, C99 is smarter than me...) + */ + memcpy(dest,buf,len); + OPENSSL_free(buf); + return len; +} + +/** Decode an ASN.1-encoded public key from <b>str</b>; return the result on + * success and NULL on failure. + */ +crypto_pk_t * +crypto_pk_asn1_decode(const char *str, size_t len) +{ + RSA *rsa; + unsigned char *buf; + const unsigned char *cp; + cp = buf = tor_malloc(len); + memcpy(buf,str,len); + rsa = d2i_RSAPublicKey(NULL, &cp, len); + tor_free(buf); + if (!rsa) { + crypto_log_errors(LOG_WARN,"decoding public key"); + return NULL; + } + return crypto_new_pk_from_rsa_(rsa); +} + +/** Given a private or public key <b>pk</b>, put a fingerprint of the + * public key into <b>fp_out</b> (must have at least FINGERPRINT_LEN+1 bytes of + * space). Return 0 on success, -1 on failure. + * + * Fingerprints are computed as the SHA1 digest of the ASN.1 encoding + * of the public key, converted to hexadecimal, in upper case, with a + * space after every four digits. + * + * If <b>add_space</b> is false, omit the spaces. + */ +int +crypto_pk_get_fingerprint(crypto_pk_t *pk, char *fp_out, int add_space) +{ + char digest[DIGEST_LEN]; + char hexdigest[HEX_DIGEST_LEN+1]; + if (crypto_pk_get_digest(pk, digest)) { + return -1; + } + base16_encode(hexdigest,sizeof(hexdigest),digest,DIGEST_LEN); + if (add_space) { + crypto_add_spaces_to_fp(fp_out, FINGERPRINT_LEN+1, hexdigest); + } else { + strncpy(fp_out, hexdigest, HEX_DIGEST_LEN+1); + } + return 0; +} + +/** Given a private or public key <b>pk</b>, put a hashed fingerprint of + * the public key into <b>fp_out</b> (must have at least FINGERPRINT_LEN+1 + * bytes of space). Return 0 on success, -1 on failure. + * + * Hashed fingerprints are computed as the SHA1 digest of the SHA1 digest + * of the ASN.1 encoding of the public key, converted to hexadecimal, in + * upper case. + */ +int +crypto_pk_get_hashed_fingerprint(crypto_pk_t *pk, char *fp_out) +{ + char digest[DIGEST_LEN], hashed_digest[DIGEST_LEN]; + if (crypto_pk_get_digest(pk, digest)) { + return -1; + } + if (crypto_digest(hashed_digest, digest, DIGEST_LEN) < 0) { + return -1; + } + base16_encode(fp_out, FINGERPRINT_LEN + 1, hashed_digest, DIGEST_LEN); + return 0; +} + +/** Given a crypto_pk_t <b>pk</b>, allocate a new buffer containing the + * Base64 encoding of the DER representation of the private key as a NUL + * terminated string, and return it via <b>priv_out</b>. Return 0 on + * sucess, -1 on failure. + * + * It is the caller's responsibility to sanitize and free the resulting buffer. + */ +int +crypto_pk_base64_encode(const crypto_pk_t *pk, char **priv_out) +{ + unsigned char *der = NULL; + int der_len; + int ret = -1; + + *priv_out = NULL; + + der_len = i2d_RSAPrivateKey(pk->key, &der); + if (der_len < 0 || der == NULL) + return ret; + + size_t priv_len = base64_encode_size(der_len, 0) + 1; + char *priv = tor_malloc_zero(priv_len); + if (base64_encode(priv, priv_len, (char *)der, der_len, 0) >= 0) { + *priv_out = priv; + ret = 0; + } else { + tor_free(priv); + } + + memwipe(der, 0, der_len); + OPENSSL_free(der); + return ret; +} + +/** Given a string containing the Base64 encoded DER representation of the + * private key <b>str</b>, decode and return the result on success, or NULL + * on failure. + */ +crypto_pk_t * +crypto_pk_base64_decode(const char *str, size_t len) +{ + crypto_pk_t *pk = NULL; + + char *der = tor_malloc_zero(len + 1); + int der_len = base64_decode(der, len, str, len); + if (der_len <= 0) { + log_warn(LD_CRYPTO, "Stored RSA private key seems corrupted (base64)."); + goto out; + } + + const unsigned char *dp = (unsigned char*)der; /* Shut the compiler up. */ + RSA *rsa = d2i_RSAPrivateKey(NULL, &dp, der_len); + if (!rsa) { + crypto_log_errors(LOG_WARN, "decoding private key"); + goto out; + } + + pk = crypto_new_pk_from_rsa_(rsa); + + /* Make sure it's valid. */ + if (crypto_pk_check_key(pk) <= 0) { + crypto_pk_free(pk); + pk = NULL; + goto out; + } + + out: + memwipe(der, 0, len + 1); + tor_free(der); + return pk; +} + diff --git a/src/common/crypto_rsa.h b/src/common/crypto_rsa.h new file mode 100644 index 0000000000..5b9025c629 --- /dev/null +++ b/src/common/crypto_rsa.h @@ -0,0 +1,104 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_rsa.h + * + * \brief Headers for crypto_rsa.c + **/ + +#ifndef TOR_CRYPTO_RSA_H +#define TOR_CRYPTO_RSA_H + +#include "orconfig.h" + +#include <stdio.h> +#include "torint.h" +#include "testsupport.h" +#include "compat.h" +#include "util.h" +#include "torlog.h" +#include "crypto_curve25519.h" + +/** Length of our public keys. */ +#define PK_BYTES (1024/8) + +/** Constant used to indicate OAEP padding for public-key encryption */ +#define PK_PKCS1_OAEP_PADDING 60002 + +/** Number of bytes added for PKCS1-OAEP padding. */ +#define PKCS1_OAEP_PADDING_OVERHEAD 42 + +/** A public key, or a public/private key-pair. */ +typedef struct crypto_pk_t crypto_pk_t; + +/* RSA enviroment setup */ +MOCK_DECL(crypto_pk_t *,crypto_pk_new,(void)); +void crypto_pk_free_(crypto_pk_t *env); +#define crypto_pk_free(pk) FREE_AND_NULL(crypto_pk_t, crypto_pk_free_, (pk)) +int crypto_get_rsa_padding_overhead(int padding); +int crypto_get_rsa_padding(int padding); + +/* public key crypto */ +MOCK_DECL(int, crypto_pk_generate_key_with_bits,(crypto_pk_t *env, int bits)); +#define crypto_pk_generate_key(env) \ + crypto_pk_generate_key_with_bits((env), (PK_BYTES*8)) + +int crypto_pk_read_private_key_from_filename(crypto_pk_t *env, + const char *keyfile); +int crypto_pk_write_public_key_to_string(crypto_pk_t *env, + char **dest, size_t *len); +int crypto_pk_write_private_key_to_string(crypto_pk_t *env, + char **dest, size_t *len); +int crypto_pk_read_public_key_from_string(crypto_pk_t *env, + const char *src, size_t len); +int crypto_pk_read_private_key_from_string(crypto_pk_t *env, + const char *s, ssize_t len); +int crypto_pk_write_private_key_to_filename(crypto_pk_t *env, + const char *fname); + +int crypto_pk_check_key(crypto_pk_t *env); +int crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b); +int crypto_pk_eq_keys(const crypto_pk_t *a, const crypto_pk_t *b); +size_t crypto_pk_keysize(const crypto_pk_t *env); +int crypto_pk_num_bits(crypto_pk_t *env); +crypto_pk_t *crypto_pk_dup_key(crypto_pk_t *orig); +crypto_pk_t *crypto_pk_copy_full(crypto_pk_t *orig); +int crypto_pk_key_is_private(const crypto_pk_t *key); +int crypto_pk_public_exponent_ok(crypto_pk_t *env); +int crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen, int padding); +int crypto_pk_private_decrypt(crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen, + int padding, int warnOnFailure); +MOCK_DECL(int, crypto_pk_public_checksig,(const crypto_pk_t *env, + char *to, size_t tolen, + const char *from, size_t fromlen)); +int crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen); +int crypto_pk_asn1_encode(const crypto_pk_t *pk, char *dest, size_t dest_len); +crypto_pk_t *crypto_pk_asn1_decode(const char *str, size_t len); +int crypto_pk_get_fingerprint(crypto_pk_t *pk, char *fp_out,int add_space); +int crypto_pk_get_hashed_fingerprint(crypto_pk_t *pk, char *fp_out); + +int crypto_pk_base64_encode(const crypto_pk_t *pk, char **priv_out); +crypto_pk_t *crypto_pk_base64_decode(const char *str, size_t len); + +/* Prototypes for private functions only used by tortls.c, crypto.c, and the + * unit tests. */ +struct rsa_st; +struct rsa_st *crypto_pk_get_rsa_(crypto_pk_t *env); +crypto_pk_t *crypto_new_pk_from_rsa_(struct rsa_st *rsa); +MOCK_DECL(struct evp_pkey_st *, crypto_pk_get_evp_pkey_,(crypto_pk_t *env, + int private)); +struct evp_pkey_st; + +#ifdef TOR_UNIT_TESTS +void crypto_pk_assign_(crypto_pk_t *dest, const crypto_pk_t *src); +#endif + +#endif + diff --git a/src/common/include.am b/src/common/include.am index b84f6ad817..1777f33ad9 100644 --- a/src/common/include.am +++ b/src/common/include.am @@ -113,6 +113,7 @@ LIBOR_CRYPTO_A_SRC = \ src/common/compress_zlib.c \ src/common/compress_zstd.c \ src/common/crypto.c \ + src/common/crypto_rsa.c \ src/common/crypto_openssl_mgt.c \ src/common/crypto_pwbox.c \ src/common/crypto_s2k.c \ @@ -166,6 +167,7 @@ COMMONHEADERS = \ src/common/crypto_ed25519.h \ src/common/crypto_format.h \ src/common/crypto_openssl_mgt.h \ + src/common/crypto_rsa.h \ src/common/crypto_pwbox.h \ src/common/crypto_s2k.h \ src/common/di_ops.h \ diff --git a/src/common/log.c b/src/common/log.c index ac6d07a929..d59e5a4036 100644 --- a/src/common/log.c +++ b/src/common/log.c @@ -1263,7 +1263,7 @@ static const char *domain_list[] = { "GENERAL", "CRYPTO", "NET", "CONFIG", "FS", "PROTOCOL", "MM", "HTTP", "APP", "CONTROL", "CIRC", "REND", "BUG", "DIR", "DIRSERV", "OR", "EDGE", "ACCT", "HIST", "HANDSHAKE", "HEARTBEAT", "CHANNEL", - "SCHED", "GUARD", "CONSDIFF", NULL + "SCHED", "GUARD", "CONSDIFF", "DOS", NULL }; /** Return a bitmask for the log domain for which <b>domain</b> is the name, diff --git a/src/common/torlog.h b/src/common/torlog.h index b7d033adb9..cadfe3b879 100644 --- a/src/common/torlog.h +++ b/src/common/torlog.h @@ -103,8 +103,10 @@ #define LD_GUARD (1u<<23) /** Generation and application of consensus diffs. */ #define LD_CONSDIFF (1u<<24) +/** Denial of Service mitigation. */ +#define LD_DOS (1u<<25) /** Number of logging domains in the code. */ -#define N_LOGGING_DOMAINS 25 +#define N_LOGGING_DOMAINS 26 /** This log message is not safe to send to a callback-based logger * immediately. Used as a flag, not a log domain. */ diff --git a/src/or/channel.c b/src/or/channel.c index 345a90a004..094bf93e66 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -1888,6 +1888,7 @@ channel_do_open_actions(channel_t *chan) if (!connection_or_digest_is_known_relay(chan->identity_digest)) { if (channel_get_addr_if_possible(chan, &remote_addr)) { char *transport_name = NULL; + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); if (chan->get_transport_name(chan, &transport_name) < 0) transport_name = NULL; @@ -1895,6 +1896,10 @@ channel_do_open_actions(channel_t *chan) &remote_addr, transport_name, now); tor_free(transport_name); + /* Notify the DoS subsystem of a new client. */ + if (tlschan && tlschan->conn) { + dos_new_client_conn(tlschan->conn); + } } /* Otherwise the underlying transport can't tell us this, so skip it */ } @@ -2914,8 +2919,8 @@ channel_get_canonical_remote_descr(channel_t *chan) * supports this operation, and return 1. Return 0 if the underlying transport * doesn't let us do this. */ -int -channel_get_addr_if_possible(channel_t *chan, tor_addr_t *addr_out) +MOCK_IMPL(int, +channel_get_addr_if_possible,(channel_t *chan, tor_addr_t *addr_out)) { tor_assert(chan); tor_assert(addr_out); diff --git a/src/or/channel.h b/src/or/channel.h index 4b60b7a7d6..9128403ce7 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -590,7 +590,8 @@ MOCK_DECL(void, channel_dump_statistics, (channel_t *chan, int severity)); void channel_dump_transport_statistics(channel_t *chan, int severity); const char * channel_get_actual_remote_descr(channel_t *chan); const char * channel_get_actual_remote_address(channel_t *chan); -int channel_get_addr_if_possible(channel_t *chan, tor_addr_t *addr_out); +MOCK_DECL(int, channel_get_addr_if_possible, (channel_t *chan, + tor_addr_t *addr_out)); const char * channel_get_canonical_remote_descr(channel_t *chan); int channel_has_queued_writes(channel_t *chan); int channel_is_bad_for_new_circs(channel_t *chan); diff --git a/src/or/channeltls.c b/src/or/channeltls.c index 023ccdefd3..ba35178520 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -522,7 +522,7 @@ channel_tls_get_remote_addr_method(channel_t *chan, tor_addr_t *addr_out) tor_assert(addr_out); if (tlschan->conn) { - tor_addr_copy(addr_out, &(TO_CONN(tlschan->conn)->addr)); + tor_addr_copy(addr_out, &(tlschan->conn->real_addr)); rv = 1; } else tor_addr_make_unspec(addr_out); diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 27d8c62b5b..8c02cd1c19 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -1803,6 +1803,13 @@ circuit_find_to_cannibalize(uint8_t purpose_to_produce, extend_info_t *info, goto next; } + /* Ignore any circuits for which we can't use the Guard. It is possible + * that the Guard was removed from the samepled set after the circuit + * was created so avoid using it. */ + if (!entry_guard_could_succeed(circ->guard_state)) { + goto next; + } + if ((!need_uptime || circ->build_state->need_uptime) && (!need_capacity || circ->build_state->need_capacity) && (internal == circ->build_state->is_internal) && diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 1ff1de4650..3a14a3ccfc 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1773,7 +1773,8 @@ circuit_build_failed(origin_circuit_t *circ) /* We failed at the first hop for some reason other than a DESTROY cell. * If there's an OR connection to blame, blame it. Also, avoid this relay * for a while, and fail any one-hop directory fetches destined for it. */ - const char *n_chan_id = circ->cpath->extend_info->identity_digest; + const char *n_chan_ident = circ->cpath->extend_info->identity_digest; + tor_assert(n_chan_ident); int already_marked = 0; if (circ->base_.n_chan) { n_chan = circ->base_.n_chan; @@ -1801,7 +1802,7 @@ circuit_build_failed(origin_circuit_t *circ) "with no connection", TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier); } - if (n_chan_id && !already_marked) { + if (!already_marked) { /* * If we have guard state (new guard API) and our path selection * code actually chose a full path, then blame the failure of this @@ -1821,7 +1822,7 @@ circuit_build_failed(origin_circuit_t *circ) entry_guard_failed(&circ->guard_state); /* if there are any one-hop streams waiting on this circuit, fail * them now so they can retry elsewhere. */ - connection_ap_fail_onehop(n_chan_id, circ->build_state); + connection_ap_fail_onehop(n_chan_ident, circ->build_state); } } diff --git a/src/or/command.c b/src/or/command.c index bd70e37a07..185596a65a 100644 --- a/src/or/command.c +++ b/src/or/command.c @@ -46,6 +46,7 @@ #include "config.h" #include "control.h" #include "cpuworker.h" +#include "dos.h" #include "hibernate.h" #include "nodelist.h" #include "onion.h" @@ -247,6 +248,11 @@ command_process_create_cell(cell_t *cell, channel_t *chan) (unsigned)cell->circ_id, U64_PRINTF_ARG(chan->global_identifier), chan); + /* First thing we do, even though the cell might be invalid, is inform the + * DoS mitigation subsystem layer of this event. Validation is done by this + * function. */ + dos_cc_new_create_cell(chan); + /* We check for the conditions that would make us drop the cell before * we check for the conditions that would make us send a DESTROY back, * since those conditions would make a DESTROY nonsensical. */ @@ -284,6 +290,13 @@ command_process_create_cell(cell_t *cell, channel_t *chan) return; } + /* Check if we should apply a defense for this channel. */ + if (dos_cc_get_defense_type(chan) == DOS_CC_DEFENSE_REFUSE_CELL) { + channel_send_destroy(cell->circ_id, chan, + END_CIRC_REASON_RESOURCELIMIT); + return; + } + if (!server_mode(options) || (!public_server_mode(options) && channel_is_outgoing(chan))) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, diff --git a/src/or/config.c b/src/or/config.c index afaf867851..e525624acf 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -81,6 +81,7 @@ #include "dirserv.h" #include "dirvote.h" #include "dns.h" +#include "dos.h" #include "entrynodes.h" #include "git_revision.h" #include "geoip.h" @@ -316,6 +317,19 @@ static config_var_t option_vars_[] = { OBSOLETE("DynamicDHGroups"), VPORT(DNSPort), OBSOLETE("DNSListenAddress"), + /* DoS circuit creation options. */ + V(DoSCircuitCreationEnabled, AUTOBOOL, "auto"), + V(DoSCircuitCreationMinConnections, UINT, "0"), + V(DoSCircuitCreationRate, UINT, "0"), + V(DoSCircuitCreationBurst, UINT, "0"), + V(DoSCircuitCreationDefenseType, INT, "0"), + V(DoSCircuitCreationDefenseTimePeriod, INTERVAL, "0"), + /* DoS connection options. */ + V(DoSConnectionEnabled, AUTOBOOL, "auto"), + V(DoSConnectionMaxConcurrentCount, UINT, "0"), + V(DoSConnectionDefenseType, INT, "0"), + /* DoS single hop client options. */ + V(DoSRefuseSingleHopClientRendezvous, AUTOBOOL, "auto"), V(DownloadExtraInfo, BOOL, "0"), V(TestingEnableConnBwEvent, BOOL, "0"), V(TestingEnableCellStatsEvent, BOOL, "0"), @@ -766,6 +780,8 @@ static int options_validate_cb(void *old_options, void *options, int from_setconf, char **msg); static uint64_t compute_real_max_mem_in_queues(const uint64_t val, int log_guess); +static void cleanup_protocol_warning_severity_level(void); +static void set_protocol_warning_severity_level(int warning_severity); /** Magic value for or_options_t. */ #define OR_OPTIONS_MAGIC 9090909 @@ -999,6 +1015,8 @@ config_free_all(void) tor_free(the_short_tor_version); tor_free(the_tor_version); + cleanup_protocol_warning_severity_level(); + have_parsed_cmdline = 0; libevent_initialized = 0; } @@ -1064,17 +1082,46 @@ escaped_safe_str(const char *address) * The severity level that should be used for warnings of severity * LOG_PROTOCOL_WARN. * - * We keep this outside the options, in case somebody needs to use - * LOG_PROTOCOL_WARN while an option transition is happening. + * We keep this outside the options, and we use an atomic_counter_t, in case + * one thread needs to use LOG_PROTOCOL_WARN while an option transition is + * happening in the main thread. */ -static int protocol_warning_severity_level = LOG_WARN; +static atomic_counter_t protocol_warning_severity_level; /** Return the severity level that should be used for warnings of severity * LOG_PROTOCOL_WARN. */ int get_protocol_warning_severity_level(void) { - return protocol_warning_severity_level; + return (int) atomic_counter_get(&protocol_warning_severity_level); +} + +/** Set the protocol warning severity level to <b>severity</b>. */ +static void +set_protocol_warning_severity_level(int warning_severity) +{ + atomic_counter_exchange(&protocol_warning_severity_level, + warning_severity); +} + +/** + * Initialize the log warning severity level for protocol warnings. Call + * only once at startup. + */ +void +init_protocol_warning_severity_level(void) +{ + atomic_counter_init(&protocol_warning_severity_level); + set_protocol_warning_severity_level(LOG_WARN); +} + +/** + * Tear down protocol_warning_severity_level. + */ +static void +cleanup_protocol_warning_severity_level(void) +{ + atomic_counter_destroy(&protocol_warning_severity_level); } /** List of default directory authorities */ @@ -1794,10 +1841,10 @@ options_act(const or_options_t *old_options) return -1; } - if (options->ProtocolWarnings) - protocol_warning_severity_level = LOG_WARN; - else - protocol_warning_severity_level = LOG_INFO; + { + int warning_severity = options->ProtocolWarnings ? LOG_WARN : LOG_INFO; + set_protocol_warning_severity_level(warning_severity); + } if (consider_adding_dir_servers(options, old_options) < 0) { // XXXX This should get validated earlier, and committed here, to @@ -2323,6 +2370,17 @@ options_act(const or_options_t *old_options) } } + /* DoS mitigation subsystem only applies to public relay. */ + if (public_server_mode(options)) { + /* If we are configured as a relay, initialize the subsystem. Even on HUP, + * this is safe to call as it will load data from the current options + * or/and the consensus. */ + dos_init(); + } else if (old_options && public_server_mode(old_options)) { + /* Going from relay to non relay, clean it up. */ + dos_free_all(); + } + /* Load the webpage we're going to serve every time someone asks for '/' on our DirPort. */ tor_free(global_dirfrontpagecontents); diff --git a/src/or/config.h b/src/or/config.h index 7c7ef1825a..2f23809b2e 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -31,6 +31,7 @@ const char *safe_str_client(const char *address); const char *safe_str(const char *address); const char *escaped_safe_str_client(const char *address); const char *escaped_safe_str(const char *address); +void init_protocol_warning_severity_level(void); int get_protocol_warning_severity_level(void); const char *get_version(void); const char *get_short_version(void); diff --git a/src/or/connection.c b/src/or/connection.c index a020bef775..5bbb61dfa9 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -80,6 +80,7 @@ #include "dirserv.h" #include "dns.h" #include "dnsserv.h" +#include "dos.h" #include "entrynodes.h" #include "ext_orport.h" #include "geoip.h" @@ -703,6 +704,13 @@ connection_free_,(connection_t *conn)) "connection_free"); } #endif /* 1 */ + + /* Notify the circuit creation DoS mitigation subsystem that an OR client + * connection has been closed. And only do that if we track it. */ + if (conn->type == CONN_TYPE_OR) { + dos_close_client_conn(TO_OR_CONN(conn)); + } + connection_unregister_events(conn); connection_free_minimal(conn); } @@ -1608,6 +1616,14 @@ connection_handle_listener_read(connection_t *conn, int new_type) return 0; } } + if (new_type == CONN_TYPE_OR) { + /* Assess with the connection DoS mitigation subsystem if this address + * can open a new connection. */ + if (dos_conn_addr_get_defense_type(&addr) == DOS_CONN_DEFENSE_CLOSE) { + tor_close_socket(news); + return 0; + } + } newconn = connection_new(new_type, conn->socket_family); newconn->s = news; diff --git a/src/or/consdiffmgr.c b/src/or/consdiffmgr.c index 38d901a3ae..02b905a520 100644 --- a/src/or/consdiffmgr.c +++ b/src/or/consdiffmgr.c @@ -1313,8 +1313,13 @@ store_multiple(consensus_cache_entry_handle_t **handles_out, labels, body_out, bodylen_out); - if (BUG(ent == NULL)) + if (ent == NULL) { + static ratelim_t cant_store_ratelim = RATELIM_INIT(5*60); + log_fn_ratelim(&cant_store_ratelim, LOG_WARN, LD_FS, + "Unable to store object %s compressed with %s.", + description, methodname); continue; + } status = CDM_DIFF_PRESENT; handles_out[i] = consensus_cache_entry_handle_new(ent); diff --git a/src/or/dirvote.c b/src/or/dirvote.c index a75d9e55a5..465c4148bc 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -3560,7 +3560,13 @@ dirvote_add_signatures_to_pending_consensus( } r = networkstatus_add_detached_signatures(pc->consensus, sigs, source, severity, msg_out); - log_info(LD_DIR,"Added %d signatures to consensus.", r); + if (r >= 0) { + log_info(LD_DIR,"Added %d signatures to consensus.", r); + } else { + log_fn(LOG_PROTOCOL_WARN, LD_DIR, + "Unable to add signatures to consensus: %s", + *msg_out ? *msg_out : "(unknown)"); + } if (r >= 1) { char *new_signatures = diff --git a/src/or/dos.c b/src/or/dos.c new file mode 100644 index 0000000000..c221e5ecdf --- /dev/null +++ b/src/or/dos.c @@ -0,0 +1,769 @@ +/* Copyright (c) 2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/* + * \file dos.c + * \brief Implement Denial of Service mitigation subsystem. + */ + +#define DOS_PRIVATE + +#include "or.h" +#include "channel.h" +#include "config.h" +#include "geoip.h" +#include "main.h" +#include "networkstatus.h" +#include "router.h" + +#include "dos.h" + +/* + * Circuit creation denial of service mitigation. + * + * Namespace used for this mitigation framework is "dos_cc_" where "cc" is for + * Circuit Creation. + */ + +/* Is the circuit creation DoS mitigation enabled? */ +static unsigned int dos_cc_enabled = 0; + +/* Consensus parameters. They can be changed when a new consensus arrives. + * They are initialized with the hardcoded default values. */ +static uint32_t dos_cc_min_concurrent_conn; +static uint32_t dos_cc_circuit_rate; +static uint32_t dos_cc_circuit_burst; +static dos_cc_defense_type_t dos_cc_defense_type; +static int32_t dos_cc_defense_time_period; + +/* Keep some stats for the heartbeat so we can report out. */ +static uint64_t cc_num_rejected_cells; +static uint32_t cc_num_marked_addrs; + +/* + * Concurrent connection denial of service mitigation. + * + * Namespace used for this mitigation framework is "dos_conn_". + */ + +/* Is the connection DoS mitigation enabled? */ +static unsigned int dos_conn_enabled = 0; + +/* Consensus parameters. They can be changed when a new consensus arrives. + * They are initialized with the hardcoded default values. */ +static uint32_t dos_conn_max_concurrent_count; +static dos_conn_defense_type_t dos_conn_defense_type; + +/* Keep some stats for the heartbeat so we can report out. */ +static uint64_t conn_num_addr_rejected; + +/* + * General interface of the denial of service mitigation subsystem. + */ + +/* Keep stats for the heartbeat. */ +static uint64_t num_single_hop_client_refused; + +/* Return true iff the circuit creation mitigation is enabled. We look at the + * consensus for this else a default value is returned. */ +MOCK_IMPL(STATIC unsigned int, +get_param_cc_enabled, (const networkstatus_t *ns)) +{ + if (get_options()->DoSCircuitCreationEnabled != -1) { + return get_options()->DoSCircuitCreationEnabled; + } + + return !!networkstatus_get_param(ns, "DoSCircuitCreationEnabled", + DOS_CC_ENABLED_DEFAULT, 0, 1); +} + +/* Return the parameter for the minimum concurrent connection at which we'll + * start counting circuit for a specific client address. */ +STATIC uint32_t +get_param_cc_min_concurrent_connection(const networkstatus_t *ns) +{ + if (get_options()->DoSCircuitCreationMinConnections) { + return get_options()->DoSCircuitCreationMinConnections; + } + return networkstatus_get_param(ns, "DoSCircuitCreationMinConnections", + DOS_CC_MIN_CONCURRENT_CONN_DEFAULT, + 1, INT32_MAX); +} + +/* Return the parameter for the time rate that is how many circuits over this + * time span. */ +static uint32_t +get_param_cc_circuit_rate(const networkstatus_t *ns) +{ + /* This is in seconds. */ + if (get_options()->DoSCircuitCreationRate) { + return get_options()->DoSCircuitCreationRate; + } + return networkstatus_get_param(ns, "DoSCircuitCreationRate", + DOS_CC_CIRCUIT_RATE_DEFAULT, + 1, INT32_MAX); +} + +/* Return the parameter for the maximum circuit count for the circuit time + * rate. */ +STATIC uint32_t +get_param_cc_circuit_burst(const networkstatus_t *ns) +{ + if (get_options()->DoSCircuitCreationBurst) { + return get_options()->DoSCircuitCreationBurst; + } + return networkstatus_get_param(ns, "DoSCircuitCreationBurst", + DOS_CC_CIRCUIT_BURST_DEFAULT, + 1, INT32_MAX); +} + +/* Return the consensus parameter of the circuit creation defense type. */ +static uint32_t +get_param_cc_defense_type(const networkstatus_t *ns) +{ + if (get_options()->DoSCircuitCreationDefenseType) { + return get_options()->DoSCircuitCreationDefenseType; + } + return networkstatus_get_param(ns, "DoSCircuitCreationDefenseType", + DOS_CC_DEFENSE_TYPE_DEFAULT, + DOS_CC_DEFENSE_NONE, DOS_CC_DEFENSE_MAX); +} + +/* Return the consensus parameter of the defense time period which is how much + * time should we defend against a malicious client address. */ +static int32_t +get_param_cc_defense_time_period(const networkstatus_t *ns) +{ + /* Time in seconds. */ + if (get_options()->DoSCircuitCreationDefenseTimePeriod) { + return get_options()->DoSCircuitCreationDefenseTimePeriod; + } + return networkstatus_get_param(ns, "DoSCircuitCreationDefenseTimePeriod", + DOS_CC_DEFENSE_TIME_PERIOD_DEFAULT, + 0, INT32_MAX); +} + +/* Return true iff connection mitigation is enabled. We look at the consensus + * for this else a default value is returned. */ +MOCK_IMPL(STATIC unsigned int, +get_param_conn_enabled, (const networkstatus_t *ns)) +{ + if (get_options()->DoSConnectionEnabled != -1) { + return get_options()->DoSConnectionEnabled; + } + return !!networkstatus_get_param(ns, "DoSConnectionEnabled", + DOS_CONN_ENABLED_DEFAULT, 0, 1); +} + +/* Return the consensus parameter for the maximum concurrent connection + * allowed. */ +STATIC uint32_t +get_param_conn_max_concurrent_count(const networkstatus_t *ns) +{ + if (get_options()->DoSConnectionMaxConcurrentCount) { + return get_options()->DoSConnectionMaxConcurrentCount; + } + return networkstatus_get_param(ns, "DoSConnectionMaxConcurrentCount", + DOS_CONN_MAX_CONCURRENT_COUNT_DEFAULT, + 1, INT32_MAX); +} + +/* Return the consensus parameter of the connection defense type. */ +static uint32_t +get_param_conn_defense_type(const networkstatus_t *ns) +{ + if (get_options()->DoSConnectionDefenseType) { + return get_options()->DoSConnectionDefenseType; + } + return networkstatus_get_param(ns, "DoSConnectionDefenseType", + DOS_CONN_DEFENSE_TYPE_DEFAULT, + DOS_CONN_DEFENSE_NONE, DOS_CONN_DEFENSE_MAX); +} + +/* Set circuit creation parameters located in the consensus or their default + * if none are present. Called at initialization or when the consensus + * changes. */ +static void +set_dos_parameters(const networkstatus_t *ns) +{ + /* Get the default consensus param values. */ + dos_cc_enabled = get_param_cc_enabled(ns); + dos_cc_min_concurrent_conn = get_param_cc_min_concurrent_connection(ns); + dos_cc_circuit_rate = get_param_cc_circuit_rate(ns); + dos_cc_circuit_burst = get_param_cc_circuit_burst(ns); + dos_cc_defense_time_period = get_param_cc_defense_time_period(ns); + dos_cc_defense_type = get_param_cc_defense_type(ns); + + /* Connection detection. */ + dos_conn_enabled = get_param_conn_enabled(ns); + dos_conn_max_concurrent_count = get_param_conn_max_concurrent_count(ns); + dos_conn_defense_type = get_param_conn_defense_type(ns); +} + +/* Free everything for the circuit creation DoS mitigation subsystem. */ +static void +cc_free_all(void) +{ + /* If everything is freed, the circuit creation subsystem is not enabled. */ + dos_cc_enabled = 0; +} + +/* Called when the consensus has changed. Do appropriate actions for the + * circuit creation subsystem. */ +static void +cc_consensus_has_changed(const networkstatus_t *ns) +{ + /* Looking at the consensus, is the circuit creation subsystem enabled? If + * not and it was enabled before, clean it up. */ + if (dos_cc_enabled && !get_param_cc_enabled(ns)) { + cc_free_all(); + } +} + +/** Return the number of circuits we allow per second under the current + * configuration. */ +STATIC uint64_t +get_circuit_rate_per_second(void) +{ + return dos_cc_circuit_rate; +} + +/* Given the circuit creation client statistics object, refill the circuit + * bucket if needed. This also works if the bucket was never filled in the + * first place. The addr is only used for logging purposes. */ +STATIC void +cc_stats_refill_bucket(cc_client_stats_t *stats, const tor_addr_t *addr) +{ + uint32_t new_circuit_bucket_count; + uint64_t num_token, elapsed_time_last_refill = 0, circuit_rate = 0; + time_t now; + int64_t last_refill_ts; + + tor_assert(stats); + tor_assert(addr); + + now = approx_time(); + last_refill_ts = (int64_t)stats->last_circ_bucket_refill_ts; + + /* If less than a second has elapsed, don't add any tokens. + * Note: If a relay's clock is ever 0, any new clients won't get a refill + * until the next second. But a relay that thinks it is 1970 will never + * validate the public consensus. */ + if ((int64_t)now == last_refill_ts) { + goto done; + } + + /* At this point, we know we might need to add token to the bucket. We'll + * first get the circuit rate that is how many circuit are we allowed to do + * per second. */ + circuit_rate = get_circuit_rate_per_second(); + + /* We've never filled the bucket so fill it with the maximum being the burst + * and we are done. + * Note: If a relay's clock is ever 0, all clients that were last refilled + * in that zero second will get a full refill here. */ + if (last_refill_ts == 0) { + num_token = dos_cc_circuit_burst; + goto end; + } + + /* Our clock jumped backward so fill it up to the maximum. Not filling it + * could trigger a detection for a valid client. Also, if the clock jumped + * negative but we didn't notice until the elapsed time became positive + * again, then we potentially spent many seconds not refilling the bucket + * when we should have been refilling it. But the fact that we didn't notice + * until now means that no circuit creation requests came in during that + * time, so the client doesn't end up punished that much from this hopefully + * rare situation.*/ + if ((int64_t)now < last_refill_ts) { + /* Use the maximum allowed value of token. */ + num_token = dos_cc_circuit_burst; + goto end; + } + + /* How many seconds have elapsed between now and the last refill? + * This subtraction can't underflow, because now >= last_refill_ts. + * And it can't overflow, because INT64_MAX - (-INT64_MIN) == UINT64_MAX. */ + elapsed_time_last_refill = (uint64_t)now - last_refill_ts; + + /* If the elapsed time is very large, it means our clock jumped forward. + * If the multiplication would overflow, use the maximum allowed value. */ + if (elapsed_time_last_refill > UINT32_MAX) { + num_token = dos_cc_circuit_burst; + goto end; + } + + /* Compute how many circuits we are allowed in that time frame which we'll + * add to the bucket. This can't overflow, because both multiplicands + * are less than or equal to UINT32_MAX, and num_token is uint64_t. */ + num_token = elapsed_time_last_refill * circuit_rate; + + end: + /* If the sum would overflow, use the maximum allowed value. */ + if (num_token > UINT32_MAX - stats->circuit_bucket) { + new_circuit_bucket_count = dos_cc_circuit_burst; + } else { + /* We cap the bucket to the burst value else this could overflow uint32_t + * over time. */ + new_circuit_bucket_count = MIN(stats->circuit_bucket + (uint32_t)num_token, + dos_cc_circuit_burst); + } + /* This function is not allowed to make the bucket count smaller */ + tor_assert_nonfatal(new_circuit_bucket_count >= stats->circuit_bucket); + log_debug(LD_DOS, "DoS address %s has its circuit bucket value: %" PRIu32 + ". Filling it to %" PRIu32 ". Circuit rate is %" PRIu64 + ". Elapsed time is %" PRIi64, + fmt_addr(addr), stats->circuit_bucket, new_circuit_bucket_count, + circuit_rate, (int64_t)elapsed_time_last_refill); + + stats->circuit_bucket = new_circuit_bucket_count; + stats->last_circ_bucket_refill_ts = now; + + done: + return; +} + +/* Return true iff the circuit bucket is down to 0 and the number of + * concurrent connections is greater or equal the minimum threshold set the + * consensus parameter. */ +static int +cc_has_exhausted_circuits(const dos_client_stats_t *stats) +{ + tor_assert(stats); + return stats->cc_stats.circuit_bucket == 0 && + stats->concurrent_count >= dos_cc_min_concurrent_conn; +} + +/* Mark client address by setting a timestamp in the stats object which tells + * us until when it is marked as positively detected. */ +static void +cc_mark_client(cc_client_stats_t *stats) +{ + tor_assert(stats); + /* We add a random offset of a maximum of half the defense time so it is + * less predictable. */ + stats->marked_until_ts = + approx_time() + dos_cc_defense_time_period + + crypto_rand_int_range(1, dos_cc_defense_time_period / 2); +} + +/* Return true iff the given channel address is marked as malicious. This is + * called a lot and part of the fast path of handling cells. It has to remain + * as fast as we can. */ +static int +cc_channel_addr_is_marked(channel_t *chan) +{ + time_t now; + tor_addr_t addr; + clientmap_entry_t *entry; + cc_client_stats_t *stats = NULL; + + if (chan == NULL) { + goto end; + } + /* Must be a client connection else we ignore. */ + if (!channel_is_client(chan)) { + goto end; + } + /* Without an IP address, nothing can work. */ + if (!channel_get_addr_if_possible(chan, &addr)) { + goto end; + } + + /* We are only interested in client connection from the geoip cache. */ + entry = geoip_lookup_client(&addr, NULL, GEOIP_CLIENT_CONNECT); + if (entry == NULL) { + /* We can have a connection creating circuits but not tracked by the geoip + * cache. Once this DoS subsystem is enabled, we can end up here with no + * entry for the channel. */ + goto end; + } + now = approx_time(); + stats = &entry->dos_stats.cc_stats; + + end: + return stats && stats->marked_until_ts >= now; +} + +/* Concurrent connection private API. */ + +/* Free everything for the connection DoS mitigation subsystem. */ +static void +conn_free_all(void) +{ + dos_conn_enabled = 0; +} + +/* Called when the consensus has changed. Do appropriate actions for the + * connection mitigation subsystem. */ +static void +conn_consensus_has_changed(const networkstatus_t *ns) +{ + /* Looking at the consensus, is the connection mitigation subsystem enabled? + * If not and it was enabled before, clean it up. */ + if (dos_conn_enabled && !get_param_conn_enabled(ns)) { + conn_free_all(); + } +} + +/* General private API */ + +/* Return true iff we have at least one DoS detection enabled. This is used to + * decide if we need to allocate any kind of high level DoS object. */ +static inline int +dos_is_enabled(void) +{ + return (dos_cc_enabled || dos_conn_enabled); +} + +/* Circuit creation public API. */ + +/* Called when a CREATE cell is received from the given channel. */ +void +dos_cc_new_create_cell(channel_t *chan) +{ + tor_addr_t addr; + clientmap_entry_t *entry; + + tor_assert(chan); + + /* Skip everything if not enabled. */ + if (!dos_cc_enabled) { + goto end; + } + + /* Must be a client connection else we ignore. */ + if (!channel_is_client(chan)) { + goto end; + } + /* Without an IP address, nothing can work. */ + if (!channel_get_addr_if_possible(chan, &addr)) { + goto end; + } + + /* We are only interested in client connection from the geoip cache. */ + entry = geoip_lookup_client(&addr, NULL, GEOIP_CLIENT_CONNECT); + if (entry == NULL) { + /* We can have a connection creating circuits but not tracked by the geoip + * cache. Once this DoS subsystem is enabled, we can end up here with no + * entry for the channel. */ + goto end; + } + + /* General comment. Even though the client can already be marked as + * malicious, we continue to track statistics. If it keeps going above + * threshold while marked, the defense period time will grow longer. There + * is really no point at unmarking a client that keeps DoSing us. */ + + /* First of all, we'll try to refill the circuit bucket opportunistically + * before we assess. */ + cc_stats_refill_bucket(&entry->dos_stats.cc_stats, &addr); + + /* Take a token out of the circuit bucket if we are above 0 so we don't + * underflow the bucket. */ + if (entry->dos_stats.cc_stats.circuit_bucket > 0) { + entry->dos_stats.cc_stats.circuit_bucket--; + } + + /* This is the detection. Assess at every CREATE cell if the client should + * get marked as malicious. This should be kept as fast as possible. */ + if (cc_has_exhausted_circuits(&entry->dos_stats)) { + /* If this is the first time we mark this entry, log it a info level. + * Under heavy DDoS, logging each time we mark would results in lots and + * lots of logs. */ + if (entry->dos_stats.cc_stats.marked_until_ts == 0) { + log_debug(LD_DOS, "Detected circuit creation DoS by address: %s", + fmt_addr(&addr)); + cc_num_marked_addrs++; + } + cc_mark_client(&entry->dos_stats.cc_stats); + } + + end: + return; +} + +/* Return the defense type that should be used for this circuit. + * + * This is part of the fast path and called a lot. */ +dos_cc_defense_type_t +dos_cc_get_defense_type(channel_t *chan) +{ + tor_assert(chan); + + /* Skip everything if not enabled. */ + if (!dos_cc_enabled) { + goto end; + } + + /* On an OR circuit, we'll check if the previous channel is a marked client + * connection detected by our DoS circuit creation mitigation subsystem. */ + if (cc_channel_addr_is_marked(chan)) { + /* We've just assess that this circuit should trigger a defense for the + * cell it just seen. Note it down. */ + cc_num_rejected_cells++; + return dos_cc_defense_type; + } + + end: + return DOS_CC_DEFENSE_NONE; +} + +/* Concurrent connection detection public API. */ + +/* Return true iff the given address is permitted to open another connection. + * A defense value is returned for the caller to take appropriate actions. */ +dos_conn_defense_type_t +dos_conn_addr_get_defense_type(const tor_addr_t *addr) +{ + clientmap_entry_t *entry; + + tor_assert(addr); + + /* Skip everything if not enabled. */ + if (!dos_conn_enabled) { + goto end; + } + + /* We are only interested in client connection from the geoip cache. */ + entry = geoip_lookup_client(addr, NULL, GEOIP_CLIENT_CONNECT); + if (entry == NULL) { + goto end; + } + + /* Need to be above the maximum concurrent connection count to trigger a + * defense. */ + if (entry->dos_stats.concurrent_count > dos_conn_max_concurrent_count) { + conn_num_addr_rejected++; + return dos_conn_defense_type; + } + + end: + return DOS_CONN_DEFENSE_NONE; +} + +/* General API */ + +/* Take any appropriate actions for the given geoip entry that is about to get + * freed. This is called for every entry that is being freed. + * + * This function will clear out the connection tracked flag if the concurrent + * count of the entry is above 0 so if those connections end up being seen by + * this subsystem, we won't try to decrement the counter for a new geoip entry + * that might have been added after this call for the same address. */ +void +dos_geoip_entry_about_to_free(const clientmap_entry_t *geoip_ent) +{ + tor_assert(geoip_ent); + + /* The count is down to 0 meaning no connections right now, we can safely + * clear the geoip entry from the cache. */ + if (geoip_ent->dos_stats.concurrent_count == 0) { + goto end; + } + + /* For each connection matching the geoip entry address, we'll clear the + * tracked flag because the entry is about to get removed from the geoip + * cache. We do not try to decrement if the flag is not set. */ + SMARTLIST_FOREACH_BEGIN(get_connection_array(), connection_t *, conn) { + if (conn->type == CONN_TYPE_OR) { + or_connection_t *or_conn = TO_OR_CONN(conn); + if (!tor_addr_compare(&geoip_ent->addr, &or_conn->real_addr, + CMP_EXACT)) { + or_conn->tracked_for_dos_mitigation = 0; + } + } + } SMARTLIST_FOREACH_END(conn); + + end: + return; +} + +/* Note down that we've just refused a single hop client. This increments a + * counter later used for the heartbeat. */ +void +dos_note_refuse_single_hop_client(void) +{ + num_single_hop_client_refused++; +} + +/* Return true iff single hop client connection (ESTABLISH_RENDEZVOUS) should + * be refused. */ +int +dos_should_refuse_single_hop_client(void) +{ + /* If we aren't a public relay, this shouldn't apply to anything. */ + if (!public_server_mode(get_options())) { + return 0; + } + + if (get_options()->DoSRefuseSingleHopClientRendezvous != -1) { + return get_options()->DoSRefuseSingleHopClientRendezvous; + } + + return (int) networkstatus_get_param(NULL, + "DoSRefuseSingleHopClientRendezvous", + 0 /* default */, 0, 1); +} + +/* Log a heartbeat message with some statistics. */ +void +dos_log_heartbeat(void) +{ + char *conn_msg = NULL; + char *cc_msg = NULL; + char *single_hop_client_msg = NULL; + + if (!dos_is_enabled()) { + goto end; + } + + if (dos_cc_enabled) { + tor_asprintf(&cc_msg, + " %" PRIu64 " circuits rejected," + " %" PRIu32 " marked addresses.", + cc_num_rejected_cells, cc_num_marked_addrs); + } + + if (dos_conn_enabled) { + tor_asprintf(&conn_msg, + " %" PRIu64 " connections closed.", + conn_num_addr_rejected); + } + + if (dos_should_refuse_single_hop_client()) { + tor_asprintf(&single_hop_client_msg, + " %" PRIu64 " single hop clients refused.", + num_single_hop_client_refused); + } + + log_notice(LD_HEARTBEAT, + "DoS mitigation since startup:%s%s%s", + (cc_msg != NULL) ? cc_msg : " [cc not enabled]", + (conn_msg != NULL) ? conn_msg : " [conn not enabled]", + (single_hop_client_msg != NULL) ? single_hop_client_msg : ""); + + tor_free(conn_msg); + tor_free(cc_msg); + tor_free(single_hop_client_msg); + + end: + return; +} + +/* Called when a new client connection has been established on the given + * address. */ +void +dos_new_client_conn(or_connection_t *or_conn) +{ + clientmap_entry_t *entry; + + tor_assert(or_conn); + + /* Past that point, we know we have at least one DoS detection subsystem + * enabled so we'll start allocating stuff. */ + if (!dos_is_enabled()) { + goto end; + } + + /* We are only interested in client connection from the geoip cache. */ + entry = geoip_lookup_client(&or_conn->real_addr, NULL, + GEOIP_CLIENT_CONNECT); + if (BUG(entry == NULL)) { + /* Should never happen because we note down the address in the geoip + * cache before this is called. */ + goto end; + } + + entry->dos_stats.concurrent_count++; + or_conn->tracked_for_dos_mitigation = 1; + log_debug(LD_DOS, "Client address %s has now %u concurrent connections.", + fmt_addr(&or_conn->real_addr), + entry->dos_stats.concurrent_count); + + end: + return; +} + +/* Called when a client connection for the given IP address has been closed. */ +void +dos_close_client_conn(const or_connection_t *or_conn) +{ + clientmap_entry_t *entry; + + tor_assert(or_conn); + + /* We have to decrement the count on tracked connection only even if the + * subsystem has been disabled at runtime because it might be re-enabled + * after and we need to keep a synchronized counter at all time. */ + if (!or_conn->tracked_for_dos_mitigation) { + goto end; + } + + /* We are only interested in client connection from the geoip cache. */ + entry = geoip_lookup_client(&or_conn->real_addr, NULL, + GEOIP_CLIENT_CONNECT); + if (entry == NULL) { + /* This can happen because we can close a connection before the channel + * got to be noted down in the geoip cache. */ + goto end; + } + + /* Extra super duper safety. Going below 0 means an underflow which could + * lead to most likely a false positive. In theory, this should never happen + * but lets be extra safe. */ + if (BUG(entry->dos_stats.concurrent_count == 0)) { + goto end; + } + + entry->dos_stats.concurrent_count--; + log_debug(LD_DOS, "Client address %s has lost a connection. Concurrent " + "connections are now at %u", + fmt_addr(&or_conn->real_addr), + entry->dos_stats.concurrent_count); + + end: + return; +} + +/* Called when the consensus has changed. We might have new consensus + * parameters to look at. */ +void +dos_consensus_has_changed(const networkstatus_t *ns) +{ + cc_consensus_has_changed(ns); + conn_consensus_has_changed(ns); + + /* We were already enabled or we just became enabled but either way, set the + * consensus parameters for all subsystems. */ + set_dos_parameters(ns); +} + +/* Return true iff the DoS mitigation subsystem is enabled. */ +int +dos_enabled(void) +{ + return dos_is_enabled(); +} + +/* Free everything from the Denial of Service subsystem. */ +void +dos_free_all(void) +{ + /* Free the circuit creation mitigation subsystem. It is safe to do this + * even if it wasn't initialized. */ + cc_free_all(); + + /* Free the connection mitigation subsystem. It is safe to do this even if + * it wasn't initialized. */ + conn_free_all(); +} + +/* Initialize the Denial of Service subsystem. */ +void +dos_init(void) +{ + /* To initialize, we only need to get the parameters. */ + set_dos_parameters(NULL); +} + diff --git a/src/or/dos.h b/src/or/dos.h new file mode 100644 index 0000000000..5d35a2b12e --- /dev/null +++ b/src/or/dos.h @@ -0,0 +1,140 @@ +/* Copyright (c) 2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/* + * \file dos.h + * \brief Header file for dos.c + */ + +#ifndef TOR_DOS_H +#define TOR_DOS_H + +/* Structure that keeps stats of client connection per-IP. */ +typedef struct cc_client_stats_t { + /* Number of allocated circuits remaining for this address. It is + * decremented every time a new circuit is seen for this client address and + * if the count goes to 0, we have a positive detection. */ + uint32_t circuit_bucket; + + /* When was the last time we've refilled the circuit bucket? This is used to + * know if we need to refill the bucket when a new circuit is seen. It is + * synchronized using approx_time(). */ + time_t last_circ_bucket_refill_ts; + + /* This client address was detected to be above the circuit creation rate + * and this timestamp indicates until when it should remain marked as + * detected so we can apply a defense for the address. It is synchronized + * using the approx_time(). */ + time_t marked_until_ts; +} cc_client_stats_t; + +/* This object is a top level object that contains everything related to the + * per-IP client DoS mitigation. Because it is per-IP, it is used in the geoip + * clientmap_entry_t object. */ +typedef struct dos_client_stats_t { + /* Concurrent connection count from the specific address. 2^32 is most + * likely way too big for the amount of allowed file descriptors. */ + uint32_t concurrent_count; + + /* Circuit creation statistics. This is only used if the circuit creation + * subsystem has been enabled (dos_cc_enabled). */ + cc_client_stats_t cc_stats; +} dos_client_stats_t; + +/* General API. */ + +/* Stub. */ +struct clientmap_entry_t; + +void dos_init(void); +void dos_free_all(void); +void dos_consensus_has_changed(const networkstatus_t *ns); +int dos_enabled(void); +void dos_log_heartbeat(void); +void dos_geoip_entry_about_to_free(const struct clientmap_entry_t *geoip_ent); + +void dos_new_client_conn(or_connection_t *or_conn); +void dos_close_client_conn(const or_connection_t *or_conn); + +int dos_should_refuse_single_hop_client(void); +void dos_note_refuse_single_hop_client(void); + +/* + * Circuit creation DoS mitigation subsystemn interface. + */ + +/* DoSCircuitCreationEnabled default. Disabled by default. */ +#define DOS_CC_ENABLED_DEFAULT 0 +/* DoSCircuitCreationDefenseType maps to the dos_cc_defense_type_t enum. */ +#define DOS_CC_DEFENSE_TYPE_DEFAULT DOS_CC_DEFENSE_REFUSE_CELL +/* DoSCircuitCreationMinConnections default */ +#define DOS_CC_MIN_CONCURRENT_CONN_DEFAULT 3 +/* DoSCircuitCreationRateTenths is 3 per seconds. */ +#define DOS_CC_CIRCUIT_RATE_DEFAULT 3 +/* DoSCircuitCreationBurst default. */ +#define DOS_CC_CIRCUIT_BURST_DEFAULT 90 +/* DoSCircuitCreationDefenseTimePeriod in seconds. */ +#define DOS_CC_DEFENSE_TIME_PERIOD_DEFAULT (60 * 60) + +/* Type of defense that we can use for the circuit creation DoS mitigation. */ +typedef enum dos_cc_defense_type_t { + /* No defense used. */ + DOS_CC_DEFENSE_NONE = 1, + /* Refuse any cells which means a DESTROY cell will be sent back. */ + DOS_CC_DEFENSE_REFUSE_CELL = 2, + + /* Maximum value that can be used. Useful for the boundaries of the + * consensus parameter. */ + DOS_CC_DEFENSE_MAX = 2, +} dos_cc_defense_type_t; + +void dos_cc_new_create_cell(channel_t *channel); +dos_cc_defense_type_t dos_cc_get_defense_type(channel_t *chan); + +/* + * Concurrent connection DoS mitigation interface. + */ + +/* DoSConnectionEnabled default. Disabled by default. */ +#define DOS_CONN_ENABLED_DEFAULT 0 +/* DoSConnectionMaxConcurrentCount default. */ +#define DOS_CONN_MAX_CONCURRENT_COUNT_DEFAULT 100 +/* DoSConnectionDefenseType maps to the dos_conn_defense_type_t enum. */ +#define DOS_CONN_DEFENSE_TYPE_DEFAULT DOS_CONN_DEFENSE_CLOSE + +/* Type of defense that we can use for the concurrent connection DoS + * mitigation. */ +typedef enum dos_conn_defense_type_t { + /* No defense used. */ + DOS_CONN_DEFENSE_NONE = 1, + /* Close immediately the connection meaning refuse it. */ + DOS_CONN_DEFENSE_CLOSE = 2, + + /* Maximum value that can be used. Useful for the boundaries of the + * consensus parameter. */ + DOS_CONN_DEFENSE_MAX = 2, +} dos_conn_defense_type_t; + +dos_conn_defense_type_t dos_conn_addr_get_defense_type(const tor_addr_t *addr); + +#ifdef DOS_PRIVATE + +STATIC uint32_t get_param_conn_max_concurrent_count( + const networkstatus_t *ns); +STATIC uint32_t get_param_cc_circuit_burst(const networkstatus_t *ns); +STATIC uint32_t get_param_cc_min_concurrent_connection( + const networkstatus_t *ns); + +STATIC uint64_t get_circuit_rate_per_second(void); +STATIC void cc_stats_refill_bucket(cc_client_stats_t *stats, + const tor_addr_t *addr); + +MOCK_DECL(STATIC unsigned int, get_param_cc_enabled, + (const networkstatus_t *ns)); +MOCK_DECL(STATIC unsigned int, get_param_conn_enabled, + (const networkstatus_t *ns)); + +#endif /* TOR_DOS_PRIVATE */ + +#endif /* TOR_DOS_H */ + diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 292a393e51..2b6ff38c9c 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -3307,6 +3307,22 @@ entry_guards_update_state(or_state_t *state) entry_guards_dirty = 0; } +/** Return true iff the circuit's guard can succeed that is can be used. */ +int +entry_guard_could_succeed(const circuit_guard_state_t *guard_state) +{ + if (!guard_state) { + return 0; + } + + entry_guard_t *guard = entry_guard_handle_get(guard_state->guard); + if (!guard || BUG(guard->in_selection == NULL)) { + return 0; + } + + return 1; +} + /** * Format a single entry guard in the format expected by the controller. * Return a newly allocated string. diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index aa9c8fe193..b7b110eeb7 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -386,6 +386,8 @@ void entry_guards_note_internet_connectivity(guard_selection_t *gs); int update_guard_selection_choice(const or_options_t *options); +int entry_guard_could_succeed(const circuit_guard_state_t *guard_state); + MOCK_DECL(int,num_bridges_usable,(int use_maybe_reachable)); #ifdef ENTRYNODES_PRIVATE diff --git a/src/or/geoip.c b/src/or/geoip.c index d7411e6aaa..5b954979b9 100644 --- a/src/or/geoip.c +++ b/src/or/geoip.c @@ -34,6 +34,7 @@ #include "config.h" #include "control.h" #include "dnsserv.h" +#include "dos.h" #include "geoip.h" #include "routerlist.h" @@ -473,24 +474,6 @@ geoip_db_digest(sa_family_t family) return hex_str(geoip6_digest, DIGEST_LEN); } -/** Entry in a map from IP address to the last time we've seen an incoming - * connection from that IP address. Used by bridges only, to track which - * countries have them blocked. */ -typedef struct clientmap_entry_t { - HT_ENTRY(clientmap_entry_t) node; - tor_addr_t addr; - /* Name of pluggable transport used by this client. NULL if no - pluggable transport was used. */ - char *transport_name; - - /** Time when we last saw this IP address, in MINUTES since the epoch. - * - * (This will run out of space around 4011 CE. If Tor is still in use around - * 4000 CE, please remember to add more bits to last_seen_in_minutes.) */ - unsigned int last_seen_in_minutes:30; - unsigned int action:2; -} clientmap_entry_t; - /** Largest allowable value for last_seen_in_minutes. (It's a 30-bit field, * so it can hold up to (1u<<30)-1, or 0x3fffffffu. */ @@ -537,6 +520,10 @@ clientmap_entry_free_(clientmap_entry_t *ent) if (!ent) return; + /* This entry is about to be freed so pass it to the DoS subsystem to see if + * any actions can be taken about it. */ + dos_geoip_entry_about_to_free(ent); + tor_free(ent->transport_name); tor_free(ent); } @@ -568,14 +555,17 @@ geoip_note_client_seen(geoip_client_action_t action, time_t now) { const or_options_t *options = get_options(); - clientmap_entry_t lookup, *ent; - memset(&lookup, 0, sizeof(clientmap_entry_t)); + clientmap_entry_t *ent; if (action == GEOIP_CLIENT_CONNECT) { - /* Only remember statistics as entry guard or as bridge. */ - if (!options->EntryStatistics && - (!(options->BridgeRelay && options->BridgeRecordUsageByCountry))) - return; + /* Only remember statistics if the DoS mitigation subsystem is enabled. If + * not, only if as entry guard or as bridge. */ + if (!dos_enabled()) { + if (!options->EntryStatistics && + (!(options->BridgeRelay && options->BridgeRecordUsageByCountry))) { + return; + } + } } else { /* Only gather directory-request statistics if configured, and * forcibly disable them on bridge authorities. */ @@ -587,11 +577,7 @@ geoip_note_client_seen(geoip_client_action_t action, safe_str_client(fmt_addr((addr))), transport_name ? transport_name : "<no transport>"); - tor_addr_copy(&lookup.addr, addr); - lookup.action = (int)action; - lookup.transport_name = (char*) transport_name; - ent = HT_FIND(clientmap, &client_history, &lookup); - + ent = geoip_lookup_client(addr, transport_name, action); if (! ent) { ent = tor_malloc_zero(sizeof(clientmap_entry_t)); tor_addr_copy(&ent->addr, addr); @@ -639,6 +625,25 @@ geoip_remove_old_clients(time_t cutoff) &cutoff); } +/* Return a client entry object matching the given address, transport name and + * geoip action from the clientmap. NULL if not found. The transport_name can + * be NULL. */ +clientmap_entry_t * +geoip_lookup_client(const tor_addr_t *addr, const char *transport_name, + geoip_client_action_t action) +{ + clientmap_entry_t lookup; + + tor_assert(addr); + + /* We always look for a client connection with no transport. */ + tor_addr_copy(&lookup.addr, addr); + lookup.action = action; + lookup.transport_name = (char *) transport_name; + + return HT_FIND(clientmap, &client_history, &lookup); +} + /** How many responses are we giving to clients requesting v3 network * statuses? */ static uint32_t ns_v3_responses[GEOIP_NS_RESPONSE_NUM]; diff --git a/src/or/geoip.h b/src/or/geoip.h index acf61b97ae..ccedc1bc1f 100644 --- a/src/or/geoip.h +++ b/src/or/geoip.h @@ -13,6 +13,7 @@ #define TOR_GEOIP_H #include "testsupport.h" +#include "dos.h" #ifdef GEOIP_PRIVATE STATIC int geoip_parse_entry(const char *line, sa_family_t family); @@ -20,6 +21,29 @@ STATIC int geoip_get_country_by_ipv4(uint32_t ipaddr); STATIC int geoip_get_country_by_ipv6(const struct in6_addr *addr); STATIC void clear_geoip_db(void); #endif /* defined(GEOIP_PRIVATE) */ + +/** Entry in a map from IP address to the last time we've seen an incoming + * connection from that IP address. Used by bridges only to track which + * countries have them blocked, or the DoS mitigation subsystem if enabled. */ +typedef struct clientmap_entry_t { + HT_ENTRY(clientmap_entry_t) node; + tor_addr_t addr; + /* Name of pluggable transport used by this client. NULL if no + pluggable transport was used. */ + char *transport_name; + + /** Time when we last saw this IP address, in MINUTES since the epoch. + * + * (This will run out of space around 4011 CE. If Tor is still in use around + * 4000 CE, please remember to add more bits to last_seen_in_minutes.) */ + unsigned int last_seen_in_minutes:30; + unsigned int action:2; + + /* This object is used to keep some statistics per client address for the + * DoS mitigation subsystem. */ + dos_client_stats_t dos_stats; +} clientmap_entry_t; + int should_record_bridge_info(const or_options_t *options); int geoip_load_file(sa_family_t family, const char *filename); MOCK_DECL(int, geoip_get_country_by_addr, (const tor_addr_t *addr)); @@ -33,6 +57,9 @@ void geoip_note_client_seen(geoip_client_action_t action, const tor_addr_t *addr, const char *transport_name, time_t now); void geoip_remove_old_clients(time_t cutoff); +clientmap_entry_t *geoip_lookup_client(const tor_addr_t *addr, + const char *transport_name, + geoip_client_action_t action); void geoip_note_ns_response(geoip_ns_response_t response); char *geoip_get_transport_history(void); diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c index 1a8fdbd03b..ed38a603b4 100644 --- a/src/or/hs_cache.c +++ b/src/or/hs_cache.c @@ -636,8 +636,8 @@ cache_store_as_client(hs_cache_client_descriptor_t *client_desc) if (cache_entry != NULL) { /* If we have an entry in our cache that has a revision counter greater * than the one we just fetched, discard the one we fetched. */ - if (BUG(cache_entry->desc->plaintext_data.revision_counter > - client_desc->desc->plaintext_data.revision_counter)) { + if (cache_entry->desc->plaintext_data.revision_counter > + client_desc->desc->plaintext_data.revision_counter) { cache_client_desc_free(client_desc); goto done; } diff --git a/src/or/hs_client.c b/src/or/hs_client.c index c863475aff..2999f85d3e 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -1235,10 +1235,12 @@ hs_client_decode_descriptor(const char *desc_str, /* Make sure the descriptor signing key cross certifies with the computed * blinded key. Without this validation, anyone knowing the subcredential * and onion address can forge a descriptor. */ - if (tor_cert_checksig((*desc)->plaintext_data.signing_key_cert, + tor_cert_t *cert = (*desc)->plaintext_data.signing_key_cert; + if (tor_cert_checksig(cert, &blinded_pubkey, approx_time()) < 0) { log_warn(LD_GENERAL, "Descriptor signing key certificate signature " - "doesn't validate with computed blinded key."); + "doesn't validate with computed blinded key: %s", + tor_cert_describe_signature_status(cert)); goto err; } diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 789bc1d046..98942e8680 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -1233,7 +1233,8 @@ cert_is_valid(tor_cert_t *cert, uint8_t type, const char *log_obj_type) /* The following will not only check if the signature matches but also the * expiration date and overall validity. */ if (tor_cert_checksig(cert, &cert->signing_key, approx_time()) < 0) { - log_warn(LD_REND, "Invalid signature for %s.", log_obj_type); + log_warn(LD_REND, "Invalid signature for %s: %s", log_obj_type, + tor_cert_describe_signature_status(cert)); goto err; } @@ -1728,7 +1729,8 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) /* Validate authentication certificate with descriptor signing key. */ if (tor_cert_checksig(ip->auth_key_cert, &desc->plaintext_data.signing_pubkey, 0) < 0) { - log_warn(LD_REND, "Invalid authentication key signature"); + log_warn(LD_REND, "Invalid authentication key signature: %s", + tor_cert_describe_signature_status(ip->auth_key_cert)); goto err; } @@ -1765,7 +1767,8 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) } if (tor_cert_checksig(ip->enc_key_cert, &desc->plaintext_data.signing_pubkey, 0) < 0) { - log_warn(LD_REND, "Invalid encryption key signature"); + log_warn(LD_REND, "Invalid encryption key signature: %s", + tor_cert_describe_signature_status(ip->enc_key_cert)); goto err; } /* It is successfully cross certified. Flag the object. */ diff --git a/src/or/include.am b/src/or/include.am index 09be125617..e0366a0cac 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -47,6 +47,7 @@ LIBTOR_A_SOURCES = \ src/or/dirvote.c \ src/or/dns.c \ src/or/dnsserv.c \ + src/or/dos.c \ src/or/fp_pair.c \ src/or/geoip.c \ src/or/entrynodes.c \ @@ -189,6 +190,7 @@ ORHEADERS = \ src/or/dns.h \ src/or/dns_structs.h \ src/or/dnsserv.h \ + src/or/dos.h \ src/or/ext_orport.h \ src/or/fallback_dirs.inc \ src/or/fp_pair.h \ diff --git a/src/or/main.c b/src/or/main.c index 10e606f3ac..98566c0c91 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -75,6 +75,7 @@ #include "dirvote.h" #include "dns.h" #include "dnsserv.h" +#include "dos.h" #include "entrynodes.h" #include "geoip.h" #include "hibernate.h" @@ -3486,6 +3487,7 @@ tor_free_all(int postfork) bridges_free_all(); consdiffmgr_free_all(); hs_free_all(); + dos_free_all(); if (!postfork) { config_free_all(); or_state_free_all(); @@ -4009,6 +4011,7 @@ tor_run_main(const tor_main_configuration_t *tor_cfg) #endif /* defined(_WIN32) */ configure_backtrace_handler(get_version()); + init_protocol_warning_severity_level(); update_approx_time(time(NULL)); tor_threads_init(); diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index aa0a8d15c3..31ecb20985 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -51,6 +51,7 @@ #include "directory.h" #include "dirserv.h" #include "dirvote.h" +#include "dos.h" #include "entrynodes.h" #include "hibernate.h" #include "main.h" @@ -1599,13 +1600,21 @@ notify_control_networkstatus_changed(const networkstatus_t *old_c, smartlist_free(changed); } -/* Called when the consensus has changed from old_c to new_c. */ +/* Called before the consensus changes from old_c to new_c. */ static void -notify_networkstatus_changed(const networkstatus_t *old_c, - const networkstatus_t *new_c) +notify_before_networkstatus_changes(const networkstatus_t *old_c, + const networkstatus_t *new_c) { notify_control_networkstatus_changed(old_c, new_c); - scheduler_notify_networkstatus_changed(old_c, new_c); + dos_consensus_has_changed(new_c); +} + +/* Called after a new consensus has been put in the global state. It is safe + * to use the consensus getters in this function. */ +static void +notify_after_networkstatus_changes(void) +{ + scheduler_notify_networkstatus_changed(); } /** Copy all the ancillary information (like router download status and so on) @@ -1932,8 +1941,11 @@ networkstatus_set_current_consensus(const char *consensus, const int is_usable_flavor = flav == usable_consensus_flavor(); + /* Before we switch to the new consensus, notify that we are about to change + * it using the old consensus and the new one. */ if (is_usable_flavor) { - notify_networkstatus_changed(networkstatus_get_latest_consensus(), c); + notify_before_networkstatus_changes(networkstatus_get_latest_consensus(), + c); } if (flav == FLAV_NS) { if (current_ns_consensus) { @@ -1976,6 +1988,10 @@ networkstatus_set_current_consensus(const char *consensus, } if (is_usable_flavor) { + /* Notify that we just changed the consensus so the current global value + * can be looked at. */ + notify_after_networkstatus_changes(); + /* The "current" consensus has just been set and it is a usable flavor so * the first thing we need to do is recalculate the voting schedule static * object so we can use the timings in there needed by some subsystems diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 2f039a9a52..17a50ca862 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -373,27 +373,6 @@ node_set_hsdir_index(node_t *node, const networkstatus_t *ns) return; } -/** Recompute all node hsdir indices. */ -void -nodelist_recompute_all_hsdir_indices(void) -{ - networkstatus_t *consensus; - if (!the_nodelist) { - return; - } - - /* Get a live consensus. Abort if not found */ - consensus = networkstatus_get_live_consensus(approx_time()); - if (!consensus) { - return; - } - - /* Recompute all hsdir indices */ - SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { - node_set_hsdir_index(node, consensus); - } SMARTLIST_FOREACH_END(node); -} - /** Called when a node's address changes. */ static void node_addrs_changed(node_t *node) @@ -447,7 +426,7 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out) /* Setting the HSDir index requires the ed25519 identity key which can * only be found either in the ri or md. This is why this is called here. * Only nodes supporting HSDir=2 protocol version needs this index. */ - if (node->rs && node->rs->supports_v3_hsdir) { + if (node->rs && node->rs->pv.supports_v3_hsdir) { node_set_hsdir_index(node, networkstatus_get_latest_consensus()); } @@ -487,7 +466,7 @@ nodelist_add_microdesc(microdesc_t *md) /* Setting the HSDir index requires the ed25519 identity key which can * only be found either in the ri or md. This is why this is called here. * Only nodes supporting HSDir=2 protocol version needs this index. */ - if (rs->supports_v3_hsdir) { + if (rs->pv.supports_v3_hsdir) { node_set_hsdir_index(node, ns); } node_add_to_ed25519_map(node); @@ -531,7 +510,7 @@ nodelist_set_consensus(networkstatus_t *ns) } } - if (rs->supports_v3_hsdir) { + if (rs->pv.supports_v3_hsdir) { node_set_hsdir_index(node, ns); } node_set_country(node); @@ -959,6 +938,30 @@ node_ed25519_id_matches(const node_t *node, const ed25519_public_key_t *id) } } +/** Dummy object that should be unreturnable. Used to ensure that + * node_get_protover_summary_flags() always returns non-NULL. */ +static const protover_summary_flags_t zero_protover_flags = { + 0,0,0,0,0,0,0 +}; + +/** Return the protover_summary_flags for a given node. */ +static const protover_summary_flags_t * +node_get_protover_summary_flags(const node_t *node) +{ + if (node->rs) { + return &node->rs->pv; + } else if (node->ri) { + return &node->ri->pv; + } else { + /* This should be impossible: every node should have a routerstatus or a + * router descriptor or both. But just in case we've messed up somehow, + * return a nice empty set of flags to indicate "this node supports + * nothing." */ + tor_assert_nonfatal_unreached_once(); + return &zero_protover_flags; + } +} + /** Return true iff <b>node</b> supports authenticating itself * by ed25519 ID during the link handshake. If <b>compatible_with_us</b>, * it needs to be using a link authentication method that we understand. @@ -969,23 +972,13 @@ node_supports_ed25519_link_authentication(const node_t *node, { if (! node_get_ed25519_id(node)) return 0; - if (node->ri) { - const char *protos = node->ri->protocol_list; - if (protos == NULL) - return 0; - if (compatible_with_us) - return protocol_list_supports_protocol(protos, PRT_LINKAUTH, 3); - else - return protocol_list_supports_protocol_or_later(protos, PRT_LINKAUTH, 3); - } - if (node->rs) { - if (compatible_with_us) - return node->rs->supports_ed25519_link_handshake_compat; - else - return node->rs->supports_ed25519_link_handshake_any; - } - tor_assert_nonfatal_unreached_once(); - return 0; + + const protover_summary_flags_t *pv = node_get_protover_summary_flags(node); + + if (compatible_with_us) + return pv->supports_ed25519_link_handshake_compat; + else + return pv->supports_ed25519_link_handshake_any; } /** Return true iff <b>node</b> supports the hidden service directory version @@ -995,27 +988,7 @@ node_supports_v3_hsdir(const node_t *node) { tor_assert(node); - if (node->rs) { - return node->rs->supports_v3_hsdir; - } - if (node->ri) { - if (node->ri->protocol_list == NULL) { - return 0; - } - /* Bug #22447 forces us to filter on tor version: - * If platform is a Tor version, and older than 0.3.0.8, return False. - * Else, obey the protocol list. */ - if (node->ri->platform) { - if (!strcmpstart(node->ri->platform, "Tor ") && - !tor_version_as_new_as(node->ri->platform, "0.3.0.8")) { - return 0; - } - } - return protocol_list_supports_protocol(node->ri->protocol_list, - PRT_HSDIR, PROTOVER_HSDIR_V3); - } - tor_assert_nonfatal_unreached_once(); - return 0; + return node_get_protover_summary_flags(node)->supports_v3_hsdir; } /** Return true iff <b>node</b> supports ed25519 authentication as an hidden @@ -1025,18 +998,7 @@ node_supports_ed25519_hs_intro(const node_t *node) { tor_assert(node); - if (node->rs) { - return node->rs->supports_ed25519_hs_intro; - } - if (node->ri) { - if (node->ri->protocol_list == NULL) { - return 0; - } - return protocol_list_supports_protocol(node->ri->protocol_list, - PRT_HSINTRO, PROTOVER_HS_INTRO_V3); - } - tor_assert_nonfatal_unreached_once(); - return 0; + return node_get_protover_summary_flags(node)->supports_ed25519_hs_intro; } /** Return true iff <b>node</b> supports to be a rendezvous point for hidden @@ -1046,19 +1008,7 @@ node_supports_v3_rendezvous_point(const node_t *node) { tor_assert(node); - if (node->rs) { - return node->rs->supports_v3_rendezvous_point; - } - if (node->ri) { - if (node->ri->protocol_list == NULL) { - return 0; - } - return protocol_list_supports_protocol(node->ri->protocol_list, - PRT_HSREND, - PROTOVER_HS_RENDEZVOUS_POINT_V3); - } - tor_assert_nonfatal_unreached_once(); - return 0; + return node_get_protover_summary_flags(node)->supports_v3_rendezvous_point; } /** Return the RSA ID key's SHA1 digest for the provided node. */ diff --git a/src/or/nodelist.h b/src/or/nodelist.h index e879b4e8ff..0abdcd6045 100644 --- a/src/or/nodelist.h +++ b/src/or/nodelist.h @@ -35,8 +35,6 @@ void nodelist_remove_routerinfo(routerinfo_t *ri); void nodelist_purge(void); smartlist_t *nodelist_find_nodes_with_microdesc(const microdesc_t *md); -void nodelist_recompute_all_hsdir_indices(void); - void nodelist_free_all(void); void nodelist_assert_ok(void); diff --git a/src/or/or.h b/src/or/or.h index c81e29c95c..311a1faf32 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1636,6 +1636,10 @@ typedef struct or_connection_t { /** True iff this connection has had its bootstrap failure logged with * control_event_bootstrap_problem. */ unsigned int have_noted_bootstrap_problem:1; + /** True iff this is a client connection and its address has been put in the + * geoip cache and handled by the DoS mitigation subsystem. We use this to + * insure we have a coherent count of concurrent connection. */ + unsigned int tracked_for_dos_mitigation : 1; uint16_t link_proto; /**< What protocol version are we using? 0 for * "none negotiated yet." */ @@ -2196,6 +2200,43 @@ typedef struct signed_descriptor_t { /** A signed integer representing a country code. */ typedef int16_t country_t; +/** Flags used to summarize the declared protocol versions of a relay, + * so we don't need to parse them again and again. */ +typedef struct protover_summary_flags_t { + /** True iff we have a proto line for this router, or a versions line + * from which we could infer the protocols. */ + unsigned int protocols_known:1; + + /** True iff this router has a version or protocol list that allows it to + * accept EXTEND2 cells. This requires Relay=2. */ + unsigned int supports_extend2_cells:1; + + /** True iff this router has a protocol list that allows it to negotiate + * ed25519 identity keys on a link handshake with us. This + * requires LinkAuth=3. */ + unsigned int supports_ed25519_link_handshake_compat:1; + + /** True iff this router has a protocol list that allows it to negotiate + * ed25519 identity keys on a link handshake, at all. This requires some + * LinkAuth=X for X >= 3. */ + unsigned int supports_ed25519_link_handshake_any:1; + + /** True iff this router has a protocol list that allows it to be an + * introduction point supporting ed25519 authentication key which is part of + * the v3 protocol detailed in proposal 224. This requires HSIntro=4. */ + unsigned int supports_ed25519_hs_intro : 1; + + /** True iff this router has a protocol list that allows it to be an hidden + * service directory supporting version 3 as seen in proposal 224. This + * requires HSDir=2. */ + unsigned int supports_v3_hsdir : 1; + + /** True iff this router has a protocol list that allows it to be an hidden + * service rendezvous point supporting version 3 as seen in proposal 224. + * This requires HSRend=2. */ + unsigned int supports_v3_rendezvous_point: 1; +} protover_summary_flags_t; + /** Information about another onion router in the network. */ typedef struct { signed_descriptor_t cache_info; @@ -2264,6 +2305,9 @@ typedef struct { * this routerinfo. Used only during voting. */ unsigned int omit_from_vote:1; + /** Flags to summarize the protocol versions for this routerinfo_t. */ + protover_summary_flags_t pv; + /** Tor can use this router for general positions in circuits; we got it * from a directory server as usual, or we're an authority and a server * uploaded it. */ @@ -2342,42 +2386,15 @@ typedef struct routerstatus_t { unsigned int is_v2_dir:1; /** True iff this router publishes an open DirPort * or it claims to accept tunnelled dir requests. */ - /** True iff we have a proto line for this router, or a versions line - * from which we could infer the protocols. */ - unsigned int protocols_known:1; - - /** True iff this router has a version or protocol list that allows it to - * accept EXTEND2 cells */ - unsigned int supports_extend2_cells:1; - - /** True iff this router has a protocol list that allows it to negotiate - * ed25519 identity keys on a link handshake with us. */ - unsigned int supports_ed25519_link_handshake_compat:1; - - /** True iff this router has a protocol list that allows it to negotiate - * ed25519 identity keys on a link handshake, at all. */ - unsigned int supports_ed25519_link_handshake_any:1; - - /** True iff this router has a protocol list that allows it to be an - * introduction point supporting ed25519 authentication key which is part of - * the v3 protocol detailed in proposal 224. This requires HSIntro=4. */ - unsigned int supports_ed25519_hs_intro : 1; - - /** True iff this router has a protocol list that allows it to be an hidden - * service directory supporting version 3 as seen in proposal 224. This - * requires HSDir=2. */ - unsigned int supports_v3_hsdir : 1; - - /** True iff this router has a protocol list that allows it to be an hidden - * service rendezvous point supporting version 3 as seen in proposal 224. - * This requires HSRend=2. */ - unsigned int supports_v3_rendezvous_point: 1; unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */ unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */ unsigned int bw_is_unmeasured:1; /**< This is a consensus entry, with * the Unmeasured flag set. */ + /** Flags to summarize the protocol versions for this routerstatus_t. */ + protover_summary_flags_t pv; + uint32_t bandwidth_kb; /**< Bandwidth (capacity) of the router as reported in * the vote/consensus, in kilobytes/sec. */ @@ -4701,6 +4718,35 @@ typedef struct { * running embedded inside another process. */ int DisableSignalHandlers; + + /** Autobool: Is the circuit creation DoS mitigation subsystem enabled? */ + int DoSCircuitCreationEnabled; + /** Minimum concurrent connection needed from one single address before any + * defense is used. */ + int DoSCircuitCreationMinConnections; + /** Circuit rate used to refill the token bucket. */ + int DoSCircuitCreationRate; + /** Maximum allowed burst of circuits. Reaching that value, the address is + * detected as malicious and a defense might be used. */ + int DoSCircuitCreationBurst; + /** When an address is marked as malicous, what defense should be used + * against it. See the dos_cc_defense_type_t enum. */ + int DoSCircuitCreationDefenseType; + /** For how much time (in seconds) the defense is applicable for a malicious + * address. A random time delta is added to the defense time of an address + * which will be between 1 second and half of this value. */ + int DoSCircuitCreationDefenseTimePeriod; + + /** Autobool: Is the DoS connection mitigation subsystem enabled? */ + int DoSConnectionEnabled; + /** Maximum concurrent connection allowed per address. */ + int DoSConnectionMaxConcurrentCount; + /** When an address is reaches the maximum count, what defense should be + * used against it. See the dos_conn_defense_type_t enum. */ + int DoSConnectionDefenseType; + + /** Autobool: Do we refuse single hop client rendezvous? */ + int DoSRefuseSingleHopClientRendezvous; } or_options_t; #define LOG_PROTOCOL_WARN (get_protocol_warning_severity_level()) diff --git a/src/or/rendmid.c b/src/or/rendmid.c index 66d2f93113..c4a34ca62c 100644 --- a/src/or/rendmid.c +++ b/src/or/rendmid.c @@ -8,10 +8,12 @@ **/ #include "or.h" +#include "channel.h" #include "circuitlist.h" #include "circuituse.h" #include "config.h" #include "crypto.h" +#include "dos.h" #include "relay.h" #include "rendmid.h" #include "rephist.h" @@ -231,6 +233,16 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request, goto err; } + /* Check if we are configured to accept established rendezvous cells from + * client or in other words tor2web clients. */ + if (channel_is_client(circ->p_chan) && + dos_should_refuse_single_hop_client()) { + /* Note it down for the heartbeat log purposes. */ + dos_note_refuse_single_hop_client(); + /* Silent drop so the client has to time out before moving on. */ + return 0; + } + if (circ->base_.n_chan) { log_warn(LD_PROTOCOL, "Tried to establish rendezvous on non-edge circuit"); diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c index af230f07bf..1933aaf4b6 100644 --- a/src/or/routerkeys.c +++ b/src/or/routerkeys.c @@ -536,7 +536,8 @@ ed_key_init_from_file(const char *fname, uint32_t flags, bad_cert = 1; } else if (signing_key && tor_cert_checksig(cert, &signing_key->pubkey, now) < 0) { - tor_log(severity, LD_OR, "Can't check certificate"); + tor_log(severity, LD_OR, "Can't check certificate: %s", + tor_cert_describe_signature_status(cert)); bad_cert = 1; } else if (cert->cert_expired) { tor_log(severity, LD_OR, "Certificate is expired"); @@ -872,8 +873,12 @@ load_ed_keys(const or_options_t *options, time_t now) if (! ed25519_pubkey_eq(&sign_cert->signing_key, &id->pubkey)) FAIL("The signing cert we have was not signed with the master key " "we loaded!"); - if (tor_cert_checksig(sign_cert, &id->pubkey, 0) < 0) - FAIL("The signing cert we loaded was not signed correctly!"); + if (tor_cert_checksig(sign_cert, &id->pubkey, 0) < 0) { + log_warn(LD_OR, "The signing cert we loaded was not signed " + "correctly: %s!", + tor_cert_describe_signature_status(sign_cert)); + goto err; + } } if (want_new_signing_key && sign_signing_key_with_id) { diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 2815c60963..061eba2d22 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -5665,11 +5665,11 @@ routerstatus_version_supports_extend2_cells(const routerstatus_t *rs, return allow_unknown_versions; } - if (!rs->protocols_known) { + if (!rs->pv.protocols_known) { return allow_unknown_versions; } - return rs->supports_extend2_cells; + return rs->pv.supports_extend2_cells; } /** Assert that the internal representation of <b>rl</b> is diff --git a/src/or/routerparse.c b/src/or/routerparse.c index f1895ce313..1b79a6fe24 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -1895,12 +1895,19 @@ router_parse_entry_from_string(const char *s, const char *end, } } - if ((tok = find_opt_by_keyword(tokens, K_PLATFORM))) { - router->platform = tor_strdup(tok->args[0]); - } + { + const char *version = NULL, *protocols = NULL; + if ((tok = find_opt_by_keyword(tokens, K_PLATFORM))) { + router->platform = tor_strdup(tok->args[0]); + version = tok->args[0]; + } + + if ((tok = find_opt_by_keyword(tokens, K_PROTO))) { + router->protocol_list = tor_strdup(tok->args[0]); + protocols = tok->args[0]; + } - if ((tok = find_opt_by_keyword(tokens, K_PROTO))) { - router->protocol_list = tor_strdup(tok->args[0]); + summarize_protover_flags(&router->pv, protocols, version); } if ((tok = find_opt_by_keyword(tokens, K_CONTACT))) { @@ -2530,6 +2537,50 @@ routerstatus_parse_guardfraction(const char *guardfraction_str, return 0; } +/** Summarize the protocols listed in <b>protocols</b> into <b>out</b>, + * falling back or correcting them based on <b>version</b> as appropriate. + */ +STATIC void +summarize_protover_flags(protover_summary_flags_t *out, + const char *protocols, + const char *version) +{ + tor_assert(out); + memset(out, 0, sizeof(*out)); + if (protocols) { + out->protocols_known = 1; + out->supports_extend2_cells = + protocol_list_supports_protocol(protocols, PRT_RELAY, 2); + out->supports_ed25519_link_handshake_compat = + protocol_list_supports_protocol(protocols, PRT_LINKAUTH, 3); + out->supports_ed25519_link_handshake_any = + protocol_list_supports_protocol_or_later(protocols, PRT_LINKAUTH, 3); + out->supports_ed25519_hs_intro = + protocol_list_supports_protocol(protocols, PRT_HSINTRO, 4); + out->supports_v3_hsdir = + protocol_list_supports_protocol(protocols, PRT_HSDIR, + PROTOVER_HSDIR_V3); + out->supports_v3_rendezvous_point = + protocol_list_supports_protocol(protocols, PRT_HSREND, + PROTOVER_HS_RENDEZVOUS_POINT_V3); + } + if (version && !strcmpstart(version, "Tor ")) { + if (!out->protocols_known) { + /* The version is a "Tor" version, and where there is no + * list of protocol versions that we should be looking at instead. */ + + out->supports_extend2_cells = + tor_version_as_new_as(version, "0.2.4.8-alpha"); + out->protocols_known = 1; + } else { + /* Bug #22447 forces us to filter on this version. */ + if (!tor_version_as_new_as(version, "0.3.0.8")) { + out->supports_v3_hsdir = 0; + } + } + } +} + /** Given a string at *<b>s</b>, containing a routerstatus object, and an * empty smartlist at <b>tokens</b>, parse and return the first router status * object in the string, and advance *<b>s</b> to just after the end of the @@ -2695,44 +2746,21 @@ routerstatus_parse_entry_from_string(memarea_t *area, if (consensus_method >= MIN_METHOD_FOR_EXCLUDING_INVALID_NODES) rs->is_valid = 1; } - int found_protocol_list = 0; - if ((tok = find_opt_by_keyword(tokens, K_PROTO))) { - found_protocol_list = 1; - rs->protocols_known = 1; - rs->supports_extend2_cells = - protocol_list_supports_protocol(tok->args[0], PRT_RELAY, 2); - rs->supports_ed25519_link_handshake_compat = - protocol_list_supports_protocol(tok->args[0], PRT_LINKAUTH, 3); - rs->supports_ed25519_link_handshake_any = - protocol_list_supports_protocol_or_later(tok->args[0], PRT_LINKAUTH, 3); - rs->supports_ed25519_hs_intro = - protocol_list_supports_protocol(tok->args[0], PRT_HSINTRO, 4); - rs->supports_v3_hsdir = - protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, - PROTOVER_HSDIR_V3); - rs->supports_v3_rendezvous_point = - protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, - PROTOVER_HS_RENDEZVOUS_POINT_V3); - } - if ((tok = find_opt_by_keyword(tokens, K_V))) { - tor_assert(tok->n_args == 1); - if (!strcmpstart(tok->args[0], "Tor ") && !found_protocol_list) { - /* We only do version checks like this in the case where - * the version is a "Tor" version, and where there is no - * list of protocol versions that we should be looking at instead. */ - rs->supports_extend2_cells = - tor_version_as_new_as(tok->args[0], "0.2.4.8-alpha"); - rs->protocols_known = 1; - } - if (!strcmpstart(tok->args[0], "Tor ") && found_protocol_list) { - /* Bug #22447 forces us to filter on this version. */ - if (!tor_version_as_new_as(tok->args[0], "0.3.0.8")) { - rs->supports_v3_hsdir = 0; + { + const char *protocols = NULL, *version = NULL; + if ((tok = find_opt_by_keyword(tokens, K_PROTO))) { + tor_assert(tok->n_args == 1); + protocols = tok->args[0]; + } + if ((tok = find_opt_by_keyword(tokens, K_V))) { + tor_assert(tok->n_args == 1); + version = tok->args[0]; + if (vote_rs) { + vote_rs->version = tor_strdup(tok->args[0]); } } - if (vote_rs) { - vote_rs->version = tor_strdup(tok->args[0]); - } + + summarize_protover_flags(&rs->pv, protocols, version); } /* handle weighting/bandwidth info */ diff --git a/src/or/routerparse.h b/src/or/routerparse.h index c4c8635f4b..418fd3acdb 100644 --- a/src/or/routerparse.h +++ b/src/or/routerparse.h @@ -133,6 +133,10 @@ MOCK_DECL(STATIC int, router_compute_hash_final,(char *digest, digest_algorithm_t alg)); MOCK_DECL(STATIC int, signed_digest_equals, (const uint8_t *d1, const uint8_t *d2, size_t len)); + +STATIC void summarize_protover_flags(protover_summary_flags_t *out, + const char *protocols, + const char *version); #endif /* defined(ROUTERPARSE_PRIVATE) */ #define ED_DESC_SIGNATURE_PREFIX "Tor router descriptor signature v1" diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 058efc7bee..749df5a248 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -465,15 +465,14 @@ scheduler_conf_changed(void) * Whenever we get a new consensus, this function is called. */ void -scheduler_notify_networkstatus_changed(const networkstatus_t *old_c, - const networkstatus_t *new_c) +scheduler_notify_networkstatus_changed(void) { /* Maybe the consensus param made us change the scheduler. */ set_scheduler(); /* Then tell the (possibly new) scheduler that we have a new consensus */ if (the_scheduler->on_new_consensus) { - the_scheduler->on_new_consensus(old_c, new_c); + the_scheduler->on_new_consensus(); } } diff --git a/src/or/scheduler.h b/src/or/scheduler.h index ac405c21b7..aeba9e2b75 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -80,8 +80,7 @@ typedef struct scheduler_s { * (which might be new) will call this so it has the chance to react to the * new consensus too. If there's a consensus parameter that your scheduler * wants to keep an eye on, this is where you should check for it. */ - void (*on_new_consensus)(const networkstatus_t *old_c, - const networkstatus_t *new_c); + void (*on_new_consensus)(void); /* (Optional) To be called when a channel is being freed. Sometimes channels * go away (for example: the relay on the other end is shutting down). If @@ -119,8 +118,7 @@ typedef struct scheduler_s { void scheduler_init(void); void scheduler_free_all(void); void scheduler_conf_changed(void); -void scheduler_notify_networkstatus_changed(const networkstatus_t *old_c, - const networkstatus_t *new_c); +void scheduler_notify_networkstatus_changed(void); MOCK_DECL(void, scheduler_release_channel, (channel_t *chan)); /* @@ -200,7 +198,7 @@ int scheduler_can_use_kist(void); void scheduler_kist_set_full_mode(void); void scheduler_kist_set_lite_mode(void); scheduler_t *get_kist_scheduler(void); -int kist_scheduler_run_interval(const networkstatus_t *ns); +int kist_scheduler_run_interval(void); #ifdef TOR_UNIT_TESTS extern int32_t sched_run_interval; diff --git a/src/or/scheduler_kist.c b/src/or/scheduler_kist.c index 246c73009a..424ef05906 100644 --- a/src/or/scheduler_kist.c +++ b/src/or/scheduler_kist.c @@ -362,10 +362,10 @@ outbuf_table_remove(outbuf_table_t *table, channel_t *chan) /* Set the scheduler running interval. */ static void -set_scheduler_run_interval(const networkstatus_t *ns) +set_scheduler_run_interval(void) { int old_sched_run_interval = sched_run_interval; - sched_run_interval = kist_scheduler_run_interval(ns); + sched_run_interval = kist_scheduler_run_interval(); if (old_sched_run_interval != sched_run_interval) { log_info(LD_SCHED, "Scheduler KIST changing its running interval " "from %" PRId32 " to %" PRId32, @@ -481,13 +481,9 @@ kist_on_channel_free_fn(const channel_t *chan) /* Function of the scheduler interface: on_new_consensus() */ static void -kist_scheduler_on_new_consensus(const networkstatus_t *old_c, - const networkstatus_t *new_c) +kist_scheduler_on_new_consensus(void) { - (void) old_c; - (void) new_c; - - set_scheduler_run_interval(new_c); + set_scheduler_run_interval(); } /* Function of the scheduler interface: on_new_options() */ @@ -497,7 +493,7 @@ kist_scheduler_on_new_options(void) sock_buf_size_factor = get_options()->KISTSockBufSizeFactor; /* Calls kist_scheduler_run_interval which calls get_options(). */ - set_scheduler_run_interval(NULL); + set_scheduler_run_interval(); } /* Function of the scheduler interface: init() */ @@ -764,7 +760,7 @@ get_kist_scheduler(void) * - If consensus doesn't say anything, return 10 milliseconds, default. */ int -kist_scheduler_run_interval(const networkstatus_t *ns) +kist_scheduler_run_interval(void) { int run_interval = get_options()->KISTSchedRunInterval; @@ -778,7 +774,7 @@ kist_scheduler_run_interval(const networkstatus_t *ns) /* Will either be the consensus value or the default. Note that 0 can be * returned which means the consensus wants us to NOT use KIST. */ - return networkstatus_get_param(ns, "KISTSchedRunInterval", + return networkstatus_get_param(NULL, "KISTSchedRunInterval", KIST_SCHED_RUN_INTERVAL_DEFAULT, KIST_SCHED_RUN_INTERVAL_MIN, KIST_SCHED_RUN_INTERVAL_MAX); @@ -817,7 +813,7 @@ scheduler_can_use_kist(void) /* We do have the support, time to check if we can get the interval that the * consensus can be disabling. */ - int run_interval = kist_scheduler_run_interval(NULL); + int run_interval = kist_scheduler_run_interval(); log_debug(LD_SCHED, "Determined KIST sched_run_interval should be " "%" PRId32 ". Can%s use KIST.", run_interval, (run_interval > 0 ? "" : " not")); diff --git a/src/or/status.c b/src/or/status.c index 187dc4ad22..3b4c605853 100644 --- a/src/or/status.c +++ b/src/or/status.c @@ -29,6 +29,7 @@ #include "statefile.h" #include "hs_stats.h" #include "hs_service.h" +#include "dos.h" static void log_accounting(const time_t now, const or_options_t *options); #include "geoip.h" @@ -167,6 +168,7 @@ log_heartbeat(time_t now) if (public_server_mode(options)) { rep_hist_log_circuit_handshake_stats(now); rep_hist_log_link_protocol_counts(); + dos_log_heartbeat(); } circuit_log_ancient_one_hop_circuits(1800); diff --git a/src/or/torcert.c b/src/or/torcert.c index bd677d1f4a..51935ddf72 100644 --- a/src/or/torcert.c +++ b/src/or/torcert.c @@ -93,7 +93,8 @@ tor_cert_sign_impl(const ed25519_keypair_t *signing_key, if (tor_cert_checksig(torcert, &signing_key->pubkey, now) < 0) { /* LCOV_EXCL_START */ - log_warn(LD_BUG, "Generated a certificate whose signature we can't check"); + log_warn(LD_BUG, "Generated a certificate whose signature we can't " + "check: %s", tor_cert_describe_signature_status(torcert)); goto err; /* LCOV_EXCL_STOP */ } @@ -267,6 +268,24 @@ tor_cert_checksig(tor_cert_t *cert, } } +/** Return a string describing the status of the signature on <b>cert</b> + * + * Will always be "unchecked" unless tor_cert_checksig has been called. + */ +const char * +tor_cert_describe_signature_status(const tor_cert_t *cert) +{ + if (cert->cert_expired) { + return "expired"; + } else if (cert->sig_bad) { + return "mis-signed"; + } else if (cert->sig_ok) { + return "okay"; + } else { + return "unchecked"; + } +} + /** Return a new copy of <b>cert</b> */ tor_cert_t * tor_cert_dup(const tor_cert_t *cert) diff --git a/src/or/torcert.h b/src/or/torcert.h index 0a8a252049..18ca60b5a8 100644 --- a/src/or/torcert.h +++ b/src/or/torcert.h @@ -67,6 +67,7 @@ int tor_cert_get_checkable_sig(ed25519_checkable_t *checkable_out, int tor_cert_checksig(tor_cert_t *cert, const ed25519_public_key_t *pubkey, time_t now); +const char *tor_cert_describe_signature_status(const tor_cert_t *cert); tor_cert_t *tor_cert_dup(const tor_cert_t *cert); int tor_cert_eq(const tor_cert_t *cert1, const tor_cert_t *cert2); diff --git a/src/test/fuzz/fuzzing_common.c b/src/test/fuzz/fuzzing_common.c index 1d54e41dbd..7c9fac748b 100644 --- a/src/test/fuzz/fuzzing_common.c +++ b/src/test/fuzz/fuzzing_common.c @@ -152,6 +152,8 @@ main(int argc, char **argv) } } + init_protocol_warning_severity_level(); + { log_severity_list_t s; memset(&s, 0, sizeof(s)); diff --git a/src/test/include.am b/src/test/include.am index a4b9705fdd..9783f93d57 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -114,6 +114,7 @@ src_test_test_SOURCES = \ src/test/test_dir.c \ src/test/test_dir_common.c \ src/test/test_dir_handle_get.c \ + src/test/test_dos.c \ src/test/test_entryconn.c \ src/test/test_entrynodes.c \ src/test/test_guardfraction.c \ diff --git a/src/test/test.c b/src/test/test.c index f225fd277d..cba7465179 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1193,6 +1193,7 @@ struct testgroup_t testgroups[] = { { "dir/", dir_tests }, { "dir_handle_get/", dir_handle_get_tests }, { "dir/md/", microdesc_tests }, + { "dos/", dos_tests }, { "entryconn/", entryconn_tests }, { "entrynodes/", entrynodes_tests }, { "guardfraction/", guardfraction_tests }, diff --git a/src/test/test.h b/src/test/test.h index 93d527ba17..b41f0e54bb 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -202,6 +202,7 @@ extern struct testcase_t crypto_tests[]; extern struct testcase_t crypto_openssl_tests[]; extern struct testcase_t dir_tests[]; extern struct testcase_t dir_handle_get_tests[]; +extern struct testcase_t dos_tests[]; extern struct testcase_t entryconn_tests[]; extern struct testcase_t entrynodes_tests[]; extern struct testcase_t guardfraction_tests[]; diff --git a/src/test/test_dos.c b/src/test/test_dos.c new file mode 100644 index 0000000000..9a10a2084a --- /dev/null +++ b/src/test/test_dos.c @@ -0,0 +1,394 @@ +/* Copyright (c) 2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define DOS_PRIVATE +#define TOR_CHANNEL_INTERNAL_ +#define CIRCUITLIST_PRIVATE + +#include "or.h" +#include "dos.h" +#include "circuitlist.h" +#include "geoip.h" +#include "channel.h" +#include "test.h" +#include "log_test_helpers.h" + +static unsigned int +mock_enable_dos_protection(const networkstatus_t *ns) +{ + (void) ns; + return 1; +} + +/** Test that the connection tracker of the DoS subsystem will block clients + * who try to establish too many connections */ +static void +test_dos_conn_creation(void *arg) +{ + (void) arg; + + MOCK(get_param_cc_enabled, mock_enable_dos_protection); + MOCK(get_param_conn_enabled, mock_enable_dos_protection); + + /* Initialize test data */ + or_connection_t or_conn; + time_t now = 1281533250; /* 2010-08-11 13:27:30 UTC */ + tt_int_op(AF_INET,OP_EQ, tor_addr_parse(&or_conn.real_addr, + "18.0.0.1")); + tor_addr_t *addr = &or_conn.real_addr; + + /* Get DoS subsystem limits */ + dos_init(); + uint32_t max_concurrent_conns = get_param_conn_max_concurrent_count(NULL); + + /* Introduce new client */ + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, addr, NULL, now); + { /* Register many conns from this client but not enough to get it blocked */ + unsigned int i; + for (i = 0; i < max_concurrent_conns; i++) { + dos_new_client_conn(&or_conn); + } + } + + /* Check that new conns are still permitted */ + tt_int_op(DOS_CONN_DEFENSE_NONE, OP_EQ, + dos_conn_addr_get_defense_type(addr)); + + /* Register another conn and check that new conns are not allowed anymore */ + dos_new_client_conn(&or_conn); + tt_int_op(DOS_CONN_DEFENSE_CLOSE, OP_EQ, + dos_conn_addr_get_defense_type(addr)); + + /* Close a client conn and see that a new conn will be permitted again */ + dos_close_client_conn(&or_conn); + tt_int_op(DOS_CONN_DEFENSE_NONE, OP_EQ, + dos_conn_addr_get_defense_type(addr)); + + /* Register another conn and see that defense measures get reactivated */ + dos_new_client_conn(&or_conn); + tt_int_op(DOS_CONN_DEFENSE_CLOSE, OP_EQ, + dos_conn_addr_get_defense_type(addr)); + + done: + dos_free_all(); +} + +/** Helper mock: Place a fake IP addr for this channel in <b>addr_out</b> */ +static int +mock_channel_get_addr_if_possible(channel_t *chan, tor_addr_t *addr_out) +{ + (void)chan; + tt_int_op(AF_INET,OP_EQ, tor_addr_parse(addr_out, "18.0.0.1")); + return 1; + + done: + return 0; +} + +/** Test that the circuit tracker of the DoS subsystem will block clients who + * try to establish too many circuits. */ +static void +test_dos_circuit_creation(void *arg) +{ + (void) arg; + unsigned int i; + + MOCK(get_param_cc_enabled, mock_enable_dos_protection); + MOCK(get_param_conn_enabled, mock_enable_dos_protection); + MOCK(channel_get_addr_if_possible, + mock_channel_get_addr_if_possible); + + /* Initialize channels/conns/circs that will be used */ + channel_t *chan = tor_malloc_zero(sizeof(channel_t)); + channel_init(chan); + chan->is_client = 1; + + /* Initialize test data */ + or_connection_t or_conn; + time_t now = 1281533250; /* 2010-08-11 13:27:30 UTC */ + tt_int_op(AF_INET,OP_EQ, tor_addr_parse(&or_conn.real_addr, + "18.0.0.1")); + tor_addr_t *addr = &or_conn.real_addr; + + /* Get DoS subsystem limits */ + dos_init(); + uint32_t max_circuit_count = get_param_cc_circuit_burst(NULL); + uint32_t min_conc_conns_for_cc = + get_param_cc_min_concurrent_connection(NULL); + + /* Introduce new client and establish enough connections to activate the + * circuit counting subsystem */ + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, addr, NULL, now); + for (i = 0; i < min_conc_conns_for_cc ; i++) { + dos_new_client_conn(&or_conn); + } + + /* Register new circuits for this client and conn, but not enough to get + * detected as dos */ + for (i=0; i < max_circuit_count-1; i++) { + dos_cc_new_create_cell(chan); + } + /* see that we didn't get detected for dosing */ + tt_int_op(DOS_CC_DEFENSE_NONE, OP_EQ, dos_cc_get_defense_type(chan)); + + /* Register another CREATE cell that will push us over the limit. Check that + * the cell gets refused. */ + dos_cc_new_create_cell(chan); + tt_int_op(DOS_CC_DEFENSE_REFUSE_CELL, OP_EQ, dos_cc_get_defense_type(chan)); + + /* TODO: Wait a few seconds before sending the cell, and check that the + buckets got refilled properly. */ + /* TODO: Actually send a Tor cell (instead of calling the DoS function) and + * check that it will get refused */ + + done: + tor_free(chan); + dos_free_all(); +} + +/** Test that the DoS subsystem properly refills the circuit token buckets. */ +static void +test_dos_bucket_refill(void *arg) +{ + (void) arg; + int i; + /* For this test, this variable is set to the current circ count of the token + * bucket. */ + uint32_t current_circ_count; + + MOCK(get_param_cc_enabled, mock_enable_dos_protection); + MOCK(get_param_conn_enabled, mock_enable_dos_protection); + MOCK(channel_get_addr_if_possible, + mock_channel_get_addr_if_possible); + + time_t now = 1281533250; /* 2010-08-11 13:27:30 UTC */ + update_approx_time(now); + + /* Initialize channels/conns/circs that will be used */ + channel_t *chan = tor_malloc_zero(sizeof(channel_t)); + channel_init(chan); + chan->is_client = 1; + or_connection_t or_conn; + tt_int_op(AF_INET,OP_EQ, tor_addr_parse(&or_conn.real_addr, + "18.0.0.1")); + tor_addr_t *addr = &or_conn.real_addr; + + /* Initialize DoS subsystem and get relevant limits */ + dos_init(); + uint32_t max_circuit_count = get_param_cc_circuit_burst(NULL); + uint64_t circ_rate = get_circuit_rate_per_second(); + /* Check that the circuit rate is a positive number and smaller than the max + * circuit count */ + tt_int_op(circ_rate, OP_GT, 1); + tt_int_op(circ_rate, OP_LT, max_circuit_count); + + /* Register this client */ + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, addr, NULL, now); + dos_new_client_conn(&or_conn); + + /* Fetch this client from the geoip cache and get its DoS structs */ + clientmap_entry_t *entry = geoip_lookup_client(addr, NULL, + GEOIP_CLIENT_CONNECT); + tt_assert(entry); + dos_client_stats_t* dos_stats = &entry->dos_stats; + /* Check that the circuit bucket is still uninitialized */ + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, 0); + + /* Send a create cell: then check that the circ token bucket got initialized + * and one circ was subtracted. */ + dos_cc_new_create_cell(chan); + current_circ_count = max_circuit_count - 1; + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now send 29 more CREATEs and ensure that the bucket is missing 30 + * tokens */ + for (i=0; i < 29; i++) { + dos_cc_new_create_cell(chan); + current_circ_count--; + } + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* OK! Progress time forward one sec, refill the bucket and check that the + * refill happened correctly. */ + now += 1; + update_approx_time(now); + cc_stats_refill_bucket(&dos_stats->cc_stats, addr); + /* check refill */ + current_circ_count += circ_rate; + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now send as many CREATE cells as needed to deplete our token bucket + * completely */ + for (; current_circ_count != 0; current_circ_count--) { + dos_cc_new_create_cell(chan); + } + tt_uint_op(current_circ_count, OP_EQ, 0); + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now progress time a week forward, and check that the token bucket does not + * have more than max_circs allowance, even tho we let it simmer for so + * long. */ + now += 604800; /* a week */ + update_approx_time(now); + cc_stats_refill_bucket(&dos_stats->cc_stats, addr); + current_circ_count += max_circuit_count; + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now send as many CREATE cells as needed to deplete our token bucket + * completely */ + for (; current_circ_count != 0; current_circ_count--) { + dos_cc_new_create_cell(chan); + } + tt_uint_op(current_circ_count, OP_EQ, 0); + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now use a very large time, and check that the token bucket does not have + * more than max_circs allowance, even tho we let it simmer for so long. */ + now = INT32_MAX; /* 2038? */ + update_approx_time(now); + cc_stats_refill_bucket(&dos_stats->cc_stats, addr); + current_circ_count += max_circuit_count; + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now send as many CREATE cells as needed to deplete our token bucket + * completely */ + for (; current_circ_count != 0; current_circ_count--) { + dos_cc_new_create_cell(chan); + } + tt_uint_op(current_circ_count, OP_EQ, 0); + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now use a very small time, and check that the token bucket has exactly + * the max_circs allowance, because backward clock jumps are rare. */ + now = INT32_MIN; /* 19?? */ + update_approx_time(now); + cc_stats_refill_bucket(&dos_stats->cc_stats, addr); + current_circ_count += max_circuit_count; + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now send as many CREATE cells as needed to deplete our token bucket + * completely */ + for (; current_circ_count != 0; current_circ_count--) { + dos_cc_new_create_cell(chan); + } + tt_uint_op(current_circ_count, OP_EQ, 0); + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Progress time forward one sec again, refill the bucket and check that the + * refill happened correctly. */ + now += 1; + update_approx_time(now); + cc_stats_refill_bucket(&dos_stats->cc_stats, addr); + /* check refill */ + current_circ_count += circ_rate; + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now send as many CREATE cells as needed to deplete our token bucket + * completely */ + for (; current_circ_count != 0; current_circ_count--) { + dos_cc_new_create_cell(chan); + } + tt_uint_op(current_circ_count, OP_EQ, 0); + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now use a very large time (again), and check that the token bucket does + * not have more than max_circs allowance, even tho we let it simmer for so + * long. */ + now = INT32_MAX; /* 2038? */ + update_approx_time(now); + cc_stats_refill_bucket(&dos_stats->cc_stats, addr); + current_circ_count += max_circuit_count; + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now send as many CREATE cells as needed to deplete our token bucket + * completely */ + for (; current_circ_count != 0; current_circ_count--) { + dos_cc_new_create_cell(chan); + } + tt_uint_op(current_circ_count, OP_EQ, 0); + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* This code resets the time to zero with 32-bit time_t, which triggers the + * code that initialises the bucket. */ +#if SIZEOF_TIME_T == 8 + /* Now use a very very small time, and check that the token bucket has + * exactly the max_circs allowance, because backward clock jumps are rare. + */ + now = (time_t)INT64_MIN; /* ???? */ + update_approx_time(now); + cc_stats_refill_bucket(&dos_stats->cc_stats, addr); + current_circ_count += max_circuit_count; + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now send as many CREATE cells as needed to deplete our token bucket + * completely */ + for (; current_circ_count != 0; current_circ_count--) { + dos_cc_new_create_cell(chan); + } + tt_uint_op(current_circ_count, OP_EQ, 0); + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Progress time forward one sec again, refill the bucket and check that the + * refill happened correctly. */ + now += 1; + update_approx_time(now); + cc_stats_refill_bucket(&dos_stats->cc_stats, addr); + /* check refill */ + current_circ_count += circ_rate; + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now send as many CREATE cells as needed to deplete our token bucket + * completely */ + for (; current_circ_count != 0; current_circ_count--) { + dos_cc_new_create_cell(chan); + } + tt_uint_op(current_circ_count, OP_EQ, 0); + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now use a very very small time, and check that the token bucket has + * exactly the max_circs allowance, because backward clock jumps are rare. + */ + now = (time_t)INT64_MIN; /* ???? */ + update_approx_time(now); + cc_stats_refill_bucket(&dos_stats->cc_stats, addr); + current_circ_count += max_circuit_count; + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now send as many CREATE cells as needed to deplete our token bucket + * completely */ + for (; current_circ_count != 0; current_circ_count--) { + dos_cc_new_create_cell(chan); + } + tt_uint_op(current_circ_count, OP_EQ, 0); + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now use a very very large time, and check that the token bucket does not + * have more than max_circs allowance, even tho we let it simmer for so + * long. */ + now = (time_t)INT64_MAX; /* ???? */ + update_approx_time(now); + cc_stats_refill_bucket(&dos_stats->cc_stats, addr); + current_circ_count += max_circuit_count; + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); + + /* Now send as many CREATE cells as needed to deplete our token bucket + * completely */ + for (; current_circ_count != 0; current_circ_count--) { + dos_cc_new_create_cell(chan); + } + tt_uint_op(current_circ_count, OP_EQ, 0); + tt_uint_op(dos_stats->cc_stats.circuit_bucket, OP_EQ, current_circ_count); +#endif + + done: + tor_free(chan); + dos_free_all(); +} + +struct testcase_t dos_tests[] = { + { "conn_creation", test_dos_conn_creation, TT_FORK, NULL, NULL }, + { "circuit_creation", test_dos_circuit_creation, TT_FORK, NULL, NULL }, + { "bucket_refill", test_dos_bucket_refill, TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index 21daa58abd..8c273c9639 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -289,7 +289,7 @@ helper_add_hsdir_to_networkstatus(networkstatus_t *ns, memcpy(rs->identity_digest, identity, DIGEST_LEN); rs->is_hs_dir = is_hsdir; - rs->supports_v3_hsdir = 1; + rs->pv.supports_v3_hsdir = 1; strlcpy(rs->nickname, nickname, sizeof(rs->nickname)); tor_addr_parse(&ipv4_addr, "1.2.3.4"); ri->addr = tor_addr_to_ipv4h(&ipv4_addr); diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 3e3a7d8e0d..fad65e61f7 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -20,6 +20,7 @@ #define STATEFILE_PRIVATE #define TOR_CHANNEL_INTERNAL_ #define HS_CLIENT_PRIVATE +#define ROUTERPARSE_PRIVATE #include "test.h" #include "test_helpers.h" @@ -37,6 +38,7 @@ #include "networkstatus.h" #include "nodelist.h" #include "relay.h" +#include "routerparse.h" #include "hs_common.h" #include "hs_config.h" @@ -1210,6 +1212,7 @@ test_build_update_descriptors(void *arg) /* Ugly yes but we never free the "ri" object so this just makes things * easier. */ ri.protocol_list = (char *) "HSDir=1-2 LinkAuth=3"; + summarize_protover_flags(&ri.pv, ri.protocol_list, NULL); ret = curve25519_secret_key_generate(&curve25519_secret_key, 0); tt_int_op(ret, OP_EQ, 0); ri.onion_curve25519_pkey = diff --git a/src/test/test_scheduler.c b/src/test/test_scheduler.c index cb5c245cea..26b412f890 100644 --- a/src/test/test_scheduler.c +++ b/src/test/test_scheduler.c @@ -947,7 +947,7 @@ test_scheduler_can_use_kist(void *arg) clear_options(); mocked_options.KISTSchedRunInterval = 1234; res_should = scheduler_can_use_kist(); - res_freq = kist_scheduler_run_interval(NULL); + res_freq = kist_scheduler_run_interval(); #ifdef HAVE_KIST_SUPPORT tt_int_op(res_should, ==, 1); #else /* HAVE_KIST_SUPPORT */ @@ -959,7 +959,7 @@ test_scheduler_can_use_kist(void *arg) clear_options(); mocked_options.KISTSchedRunInterval = 0; res_should = scheduler_can_use_kist(); - res_freq = kist_scheduler_run_interval(NULL); + res_freq = kist_scheduler_run_interval(); #ifdef HAVE_KIST_SUPPORT tt_int_op(res_should, ==, 1); #else /* HAVE_KIST_SUPPORT */ @@ -972,7 +972,7 @@ test_scheduler_can_use_kist(void *arg) clear_options(); mocked_options.KISTSchedRunInterval = 0; res_should = scheduler_can_use_kist(); - res_freq = kist_scheduler_run_interval(NULL); + res_freq = kist_scheduler_run_interval(); #ifdef HAVE_KIST_SUPPORT tt_int_op(res_should, ==, 1); #else /* HAVE_KIST_SUPPORT */ @@ -986,7 +986,7 @@ test_scheduler_can_use_kist(void *arg) clear_options(); mocked_options.KISTSchedRunInterval = 0; res_should = scheduler_can_use_kist(); - res_freq = kist_scheduler_run_interval(NULL); + res_freq = kist_scheduler_run_interval(); tt_int_op(res_should, ==, 0); tt_int_op(res_freq, ==, 0); UNMOCK(networkstatus_get_param); @@ -1020,7 +1020,7 @@ test_scheduler_ns_changed(void *arg) /* Change from vanilla to kist via consensus */ the_scheduler = get_vanilla_scheduler(); MOCK(networkstatus_get_param, mock_kist_networkstatus_get_param); - scheduler_notify_networkstatus_changed(NULL, NULL); + scheduler_notify_networkstatus_changed(); UNMOCK(networkstatus_get_param); #ifdef HAVE_KIST_SUPPORT tt_ptr_op(the_scheduler, ==, get_kist_scheduler()); @@ -1031,14 +1031,14 @@ test_scheduler_ns_changed(void *arg) /* Change from kist to vanilla via consensus */ the_scheduler = get_kist_scheduler(); MOCK(networkstatus_get_param, mock_vanilla_networkstatus_get_param); - scheduler_notify_networkstatus_changed(NULL, NULL); + scheduler_notify_networkstatus_changed(); UNMOCK(networkstatus_get_param); tt_ptr_op(the_scheduler, ==, get_vanilla_scheduler()); /* Doesn't change when using KIST */ the_scheduler = get_kist_scheduler(); MOCK(networkstatus_get_param, mock_kist_networkstatus_get_param); - scheduler_notify_networkstatus_changed(NULL, NULL); + scheduler_notify_networkstatus_changed(); UNMOCK(networkstatus_get_param); #ifdef HAVE_KIST_SUPPORT tt_ptr_op(the_scheduler, ==, get_kist_scheduler()); @@ -1049,7 +1049,7 @@ test_scheduler_ns_changed(void *arg) /* Doesn't change when using vanilla */ the_scheduler = get_vanilla_scheduler(); MOCK(networkstatus_get_param, mock_vanilla_networkstatus_get_param); - scheduler_notify_networkstatus_changed(NULL, NULL); + scheduler_notify_networkstatus_changed(); UNMOCK(networkstatus_get_param); tt_ptr_op(the_scheduler, ==, get_vanilla_scheduler()); diff --git a/src/test/testing_common.c b/src/test/testing_common.c index 142c681079..52729147b2 100644 --- a/src/test/testing_common.c +++ b/src/test/testing_common.c @@ -278,6 +278,7 @@ main(int c, const char **v) s.masks[LOG_WARN-LOG_ERR] |= LD_BUG; add_stream_log(&s, "", fileno(stdout)); } + init_protocol_warning_severity_level(); options->command = CMD_RUN_UNITTESTS; if (crypto_global_init(accel_crypto, NULL, NULL)) { |