aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changes/bug239544
-rw-r--r--changes/bug244694
-rw-r--r--changes/bug248594
-rw-r--r--changes/bug249274
-rw-r--r--changes/bug249525
-rw-r--r--changes/bug249724
-rw-r--r--changes/bug249756
-rw-r--r--changes/bug249765
-rw-r--r--changes/bug250089
-rw-r--r--changes/bug251055
-rw-r--r--changes/ticket248493
-rw-r--r--changes/ticket2490213
-rw-r--r--changes/ticket251083
-rw-r--r--doc/tor.1.txt88
-rw-r--r--src/common/crypto.c968
-rw-r--r--src/common/crypto.h68
-rw-r--r--src/common/crypto_rsa.c923
-rw-r--r--src/common/crypto_rsa.h104
-rw-r--r--src/common/include.am2
-rw-r--r--src/common/log.c2
-rw-r--r--src/common/torlog.h4
-rw-r--r--src/or/channel.c9
-rw-r--r--src/or/channel.h3
-rw-r--r--src/or/channeltls.c2
-rw-r--r--src/or/circuitlist.c7
-rw-r--r--src/or/circuituse.c7
-rw-r--r--src/or/command.c13
-rw-r--r--src/or/config.c74
-rw-r--r--src/or/config.h1
-rw-r--r--src/or/connection.c16
-rw-r--r--src/or/consdiffmgr.c7
-rw-r--r--src/or/dirvote.c8
-rw-r--r--src/or/dos.c769
-rw-r--r--src/or/dos.h140
-rw-r--r--src/or/entrynodes.c16
-rw-r--r--src/or/entrynodes.h2
-rw-r--r--src/or/geoip.c63
-rw-r--r--src/or/geoip.h27
-rw-r--r--src/or/hs_cache.c4
-rw-r--r--src/or/hs_client.c6
-rw-r--r--src/or/hs_descriptor.c9
-rw-r--r--src/or/include.am2
-rw-r--r--src/or/main.c3
-rw-r--r--src/or/networkstatus.c26
-rw-r--r--src/or/nodelist.c124
-rw-r--r--src/or/nodelist.h2
-rw-r--r--src/or/or.h106
-rw-r--r--src/or/rendmid.c12
-rw-r--r--src/or/routerkeys.c11
-rw-r--r--src/or/routerlist.c4
-rw-r--r--src/or/routerparse.c110
-rw-r--r--src/or/routerparse.h4
-rw-r--r--src/or/scheduler.c5
-rw-r--r--src/or/scheduler.h8
-rw-r--r--src/or/scheduler_kist.c20
-rw-r--r--src/or/status.c2
-rw-r--r--src/or/torcert.c21
-rw-r--r--src/or/torcert.h1
-rw-r--r--src/test/fuzz/fuzzing_common.c2
-rw-r--r--src/test/include.am1
-rw-r--r--src/test/test.c1
-rw-r--r--src/test/test.h1
-rw-r--r--src/test/test_dos.c394
-rw-r--r--src/test/test_hs_common.c2
-rw-r--r--src/test/test_hs_service.c3
-rw-r--r--src/test/test_scheduler.c16
-rw-r--r--src/test/testing_common.c1
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)) {