
Hola a todos!
Me gusta Tox y respeto a los participantes de este proyecto y su trabajo. En un esfuerzo por ayudar a los desarrolladores y usuarios de Tox, examiné el código y noté posibles problemas que podrían conducir a una falsa sensación de seguridad. Desde que originalmente publiqué este artículo en 2016 ( en ruso ), se han realizado muchas mejoras en Tox, y lidero un equipo que reescribió el software seguro de Tox desde cero utilizando el lenguaje de programación Rust (consulte Tox-rs ). Recomiendo usar tox en 2019. Echemos un vistazo a lo que realmente nos hizo reescribir Tox in Rust.
Artículo original de 2016
Existe una tendencia poco saludable a sobreestimar la seguridad de los sistemas E2E solo porque son E2E. Presentaré hechos objetivos complementados con mis propios comentarios para que saque sus propias conclusiones.
Spoiler: Los desarrolladores de Tox están de acuerdo con mis puntos y mi solicitud de extracción de código fuente fue aceptada.
Todo comenzó con artículos sobre Habr sobre la instalación del nodo ( en ruso ).
En los comentarios, la gente se quejaba de la complejidad de construir e instalar el nodo en CentOS, así que decidí escribir un sistema de construcción en CMake. Después de unos días, estaba listo para presentar mi RP a la comunidad Tox en Freenode, pero me encontré con una falta de comprensión:
Al principio, alguien ha contribuido con cmake, pero otros desarrolladores no sabían cómo usarlo y no podían hacer que creara su código, por lo que cambiaron a autotools (sic!), que ahora conocen mejor.
Noté que el código que falla las pruebas en Travis CI todavía es aceptado en la rama maestra, pero respondieron: "entendemos que necesitamos hacer algo con las pruebas, pero que sea así por ahora".
Luego, me sumergí en profundizar en el código de este atractivo mensajero.
Hecho No. 2. memset (ptr, 0, tamaño) antes de llamar gratis
Me llamó la atención
memset(c, 0, sizeof(Net_Crypto)); free(c);
Si aún no está familiarizado con PVS-Studio y su artículo sobre la función memset PVS-Studio: el compilador puede eliminar la llamada a la función 'memset' si esa región de memoria no se usa después. La lógica del compilador es sencilla: "No va a utilizar esta variable después de llamar 'libre', memset no afectará el comportamiento observado, permítame eliminar esta llamada inútil a 'memset'".
Como un estudiante diligente, reemplacé cada aparición de memset($DST, 0, $SIZE)
con sodium_memzero y las PRUEBAS CRASHED.
Hecho No. 3. La comparación de claves públicas es vulnerable a ataques de tiempo
Hay una función especial realmente genial para comparar claves públicas en toxcore
:
int public_key_cmp(const uint8_t *pk1, const uint8_t *pk2) { return crypto_verify_32(pk1, pk2); }
crypto_verify_32 : es una función de la biblioteca criptográfica NaCL / Sodium , que puede ayudarlo a evitar ataques de tiempo, porque funciona en tiempo constante, mientras que memcmp puede romperse en el primer byte desigual. Debe usar crypto_verify_32 para comparar datos confidenciales como claves.
Las comparaciones de cadenas realizadas byte por byte son vulnerables a la explotación por ataques de tiempo, por ejemplo, para falsificar MAC (consulte esta vulnerabilidad en la biblioteca criptográfica Keyczar de Google).
La base del código del proyecto toxcore es bastante extensa, por eso Tox nació con un error de vulnerabilidad de tiempo:
bool id_equal(const uint8_t *dest, const uint8_t *src) { return memcmp(dest, src, crypto_box_PUBLICKEYBYTES) == 0; }
Pero eso no es todo. Los desarrolladores aún prefieren comparar las claves a su manera usando tres funciones diferentes: id_equal o public_key_cmp y crypto_verify_32 .
Aquí hay una breve salida grep de DHT, enrutamiento de cebolla y otros subsistemas críticos:
if (memcmp(ping->to_ping[i].public_key, public_key, crypto_box_PUBLICKEYBYTES) == 0) { if (memcmp(public_key, onion_c->friends_list[i].real_public_key, crypto_box_PUBLICKEYBYTES) == 0) if (memcmp(public_key, onion_c->path_nodes_bs[i].public_key, crypto_box_PUBLICKEYBYTES) == 0) if (memcmp(dht_public_key, dht_public_key_temp, crypto_box_PUBLICKEYBYTES) != 0) if (Local_ip(ip_port.ip) && memcmp(friend_con->dht_temp_pk, public_key, crypto_box_PUBLICKEYBYTES) == 0)
Hecho No. 4. increment_nonce en un tiempo no constante
void increment_nonce(uint8_t *nonce) { uint32_t i; for (i = crypto_box_NONCEBYTES; i != 0; --i) { ++nonce[i - 1]; if (nonce[i - 1] != 0) break;
Si tales operaciones involucran parámetros secretos, estas variaciones de tiempo pueden filtrar alguna información. Con suficiente conocimiento de la implementación, un análisis estadístico cuidadoso podría incluso conducir a la recuperación total de parámetros secretos.
Hay una función especial en sodio para incrementar nonces:
Un huevo de Pascua accidentalmente irónico es que la función increment_nonce se encuentra en un archivo que comienza con las palabras:
Este código tiene que ser perfecto. No jugamos con el cifrado.
Echemos un vistazo más de cerca a este archivo perfecto.
Hecho No. 5. Puedes encontrar claves y datos privados en la pila
Código problemático:
void encrypt_precompute(const uint8_t *public_key, const uint8_t *secret_key, uint8_t *enc_key) { crypto_box_beforenm(enc_key, public_key, secret_key);
encrypt_data_symmetric llama a crypto_box_detached_afternm desde Nacl / Sodium, no pondré el código completo, aquí hay un enlace para verificarlo usted mismo.
Parece difícil cometer un error en cuatro líneas de código, ¿no?
Vamos a cavar en sodio:
int crypto_box_detached(unsigned char *c, unsigned char *mac, const unsigned char *m, unsigned long long mlen, const unsigned char *n, const unsigned char *pk, const unsigned char *sk) { unsigned char k[crypto_box_BEFORENMBYTES]; int ret; (void) sizeof(int[crypto_box_BEFORENMBYTES >= crypto_secretbox_KEYBYTES ? 1 : -1]); if (crypto_box_beforenm(k, pk, sk) != 0) { return -1; } ret = crypto_box_detached_afternm(c, mac, m, mlen, n, k); sodium_memzero(k, sizeof k); return ret; }
Borrando todos los cheques que obtenemos:
unsigned char k[crypto_box_BEFORENMBYTES]; int ret; crypto_box_beforenm(k, pk, sk); ret = crypto_box_detached_afternm(c, mac, m, mlen, n, k); sodium_memzero(k, sizeof k); return ret;
¿Te parece familiar? Si! Es un código ligeramente modificado de la función encrypt_data de toxcore, la única diferencia es que olvidaron limpiar la clave de la pila con sodium_memzero ... Y también hay errores en: handle_TCP_handshake , handle_handshake , y tal vez en otro lugar también.
Hecho No. 6. ¡Las advertencias del compilador son para dummiez!
Los desarrolladores del proyecto toxcore niegan rotundamente la necesidad de activar todas las advertencias del compilador, o no lo saben.
Funciones no utilizadas (estoy particularmente satisfecho con las advertencias en las pruebas):
../auto_tests/dht_test.c:351:12: warning: unused function 'test_addto_lists_ipv4' [-Wunused-function] START_TEST(test_addto_lists_ipv4) ^ ../auto_tests/dht_test.c:360:12: warning: unused function 'test_addto_lists_ipv6' [-Wunused-function] START_TEST(test_addto_lists_ipv6) ^ ../toxcore/TCP_server.c:1026:13: warning: unused function 'do_TCP_accept_new' [-Wunused-function] static void do_TCP_accept_new(TCP_Server *TCP_server) ^ ../toxcore/TCP_server.c:1110:13: warning: unused function 'do_TCP_incomming' [-Wunused-function] static void do_TCP_incomming(TCP_Server *TCP_server) ^ ../toxcore/TCP_server.c:1119:13: warning: unused function 'do_TCP_unconfirmed' [-Wunused-function] static void do_TCP_unconfirmed(TCP_Server *TCP_server) ^
../toxcore/Messenger.c:2040:28: warning: comparison of constant 256 with expression of type 'uint8_t' (aka 'unsigned char') is always false [-Wtautological-constant-out-of-range-compare] if (filenumber >= MAX_CONCURRENT_FILE_PIPES) ~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~ ../toxcore/Messenger.c:2095:28: warning: comparison of constant 256 with expression of type 'uint8_t' (aka 'unsigned char') is always false [-Wtautological-constant-out-of-range-compare] if (filenumber >= MAX_CONCURRENT_FILE_PIPES) ~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~ ../toxcore/Messenger.c:2110:28: warning: comparison of constant 256 with expression of type 'uint8_t' (aka 'unsigned char') is always false [-Wtautological-constant-out-of-range-compare] if (filenumber >= MAX_CONCURRENT_FILE_PIPES) ~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~
../auto_tests/TCP_test.c:205:24: warning: unsequenced modification and access to 'len' [-Wunsequenced] ck_assert_msg((len = recv(con->sock, data, length, 0)) == length, "wrong len %i\n", len); ^ ~~~ /usr/include/check.h:273:18: note: expanded from macro 'ck_assert_msg' _ck_assert_msg(expr, __FILE__, __LINE__,\ ^
Y unas pocas docenas de advertencias sobre variables no utilizadas, la comparación de firmado y no firmado, y más.
Mi conclusión
Cita del repositorio:
Queremos que Tox sea lo más simple posible sin dejar de ser lo más seguro posible.
Si yo, que no es criptógrafo, pudiera encontrar errores tan horribles en un día, ¿imagina cuántas cosas podría encontrar un especialista en criptografía después de excavar a propósito durante un mes?
Las primeras versiones de Tox representaban un gran peligro para los usuarios que confiaban en la seguridad de Tox. Las soluciones patentadas no son confiables e incluso las soluciones de código abierto no son tan seguras como usted quiere que sean. Eche un vistazo a la reciente violación de seguridad en Matrix . Hoy en día se corrigen muchos errores y Tox es la mejor opción disponible para la seguridad y privacidad de los usuarios.
La próxima vez te contaré más sobre el estado actual de los tóxicos. Lo que hemos implementado en Rust y por qué deberías probarlo.
Reddit: comentarios