
Olá pessoal!
Gosto de Tox e respeito os participantes deste projeto e seu trabalho. Em um esforço para ajudar os desenvolvedores e usuários do Tox, examinei o código e notei possíveis problemas que poderiam levar a uma falsa sensação de segurança. Desde que publiquei este artigo originalmente em 2016 ( em russo ), muitas melhorias foram feitas no Tox, e eu lidero uma equipe que reescreveu o software seguro do Tox do zero usando a linguagem de programação Rust (confira o Tox-rs ). Eu recomendo usar o tox em 2019. Vamos dar uma olhada no que realmente nos fez reescrever o Tox in Rust.
Artigo original de 2016
Há uma tendência doentia de superestimar a segurança dos sistemas E2E apenas com base em que eles são E2E. Apresentarei fatos objetivos complementados com meus próprios comentários para que você tire suas próprias conclusões.
Spoiler: Os desenvolvedores do Tox concordam com meus pontos e minha solicitação de recebimento de código-fonte foi aceita.
Tudo começou com artigos sobre Habr sobre a instalação do nó ( em russo ).
Nos comentários, as pessoas se queixaram da complexidade de criar e instalar o nó no CentOS, então decidi escrever um sistema de compilação no CMake. Depois de alguns dias, eu estava pronto para apresentar meu PR à comunidade Tox em Freenode, mas me deparei com uma falta de entendimento:
alguém contribuiu com o cmake inicialmente, mas outros desenvolvedores não sabiam como usá-lo e não conseguiam fazer com que ele construísse seu código; portanto, eles mudaram para o autotools (sic!), que eles agora conhecem melhor.
Observei que o código que falha nos testes no Travis CI ainda é aceito no ramo mestre, mas eles responderam: "entendemos que precisamos fazer algo com os testes, mas deixe por enquanto".
Em seguida, mergulhei em uma análise mais profunda do código desse atraente mensageiro.
Fato No. 2. memset (ptr, 0, tamanho) antes de ligar gratuitamente
Meu olho chamou
memset(c, 0, sizeof(Net_Crypto)); free(c);
Se você ainda não conhece o PVS-Studio e seu artigo sobre a função memset PVS-Studio: o compilador pode excluir a chamada de função 'memset' se essa região de memória não for usada posteriormente. A lógica do compilador é direta: "Você não usará essa variável depois de chamar 'livre', o memset não afetará o comportamento observado, deixe-me excluir essa chamada inútil para 'memset'".
Como estudante diligente, substituí cada ocorrência de memset($DST, 0, $SIZE)
por sodium_memzero e os TESTES CRASHED.
Fato No. 3. Comparação de chaves públicas é vulnerável a ataques de tempo
Existe uma excelente função especial para comparar chaves públicas no toxcore
:
int public_key_cmp(const uint8_t *pk1, const uint8_t *pk2) { return crypto_verify_32(pk1, pk2); }
crypto_verify_32 - é uma função da biblioteca de criptografia NaCL / Sodium , que pode ajudar a evitar ataques de tempo, porque funciona em tempo constante, enquanto o memcmp pode ser interrompido no primeiro byte desigual. Você deve usar crypto_verify_32 para comparar dados confidenciais como chaves.
As comparações de cadeia de caracteres executadas byte por byte são vulneráveis à exploração por ataques de tempo, por exemplo, para forjar MACs (consulte essa vulnerabilidade na biblioteca de criptografia Keyczar do Google).
A base de código do projeto toxcore é bastante extensa, e é por isso que o Tox nasceu com um bug de vulnerabilidade de tempo:
bool id_equal(const uint8_t *dest, const uint8_t *src) { return memcmp(dest, src, crypto_box_PUBLICKEYBYTES) == 0; }
Mas isso não é tudo. Os desenvolvedores ainda preferem comparar as chaves à sua maneira, usando três funções diferentes: id_equal ou public_key_cmp e crypto_verify_32 .
Aqui está uma pequena saída grep do DHT, roteamento de cebola e outros 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)
Fato No. 4. increment_nonce em um tempo não 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;
Se essas operações envolverem parâmetros secretos, essas variações de tempo poderão vazar algumas informações. Com o conhecimento suficiente da implementação disponível, uma análise estatística cuidadosa pode até levar à recuperação total de parâmetros secretos.
Existe uma função especial no sódio para incrementar nonces:
Um ovo de páscoa acidentalmente irônico é que a função increment_nonce está localizada em um arquivo que começa com as palavras:
Este código tem que ser perfeito. Nós não mexemos com criptografia.
Vamos dar uma olhada mais de perto neste arquivo perfeito.
Fato No. 5. Você pode encontrar chaves e dados particulares na pilha
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);
chamadas encrypt_data_symmetric crypto_box_detached_afternm da Nacl / Sodium, não colocarei o código inteiro, aqui está um link para verificar você mesmo.
Parece difícil cometer um erro em quatro linhas de código, não é?
Vamos cavar em sódio:
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; }
Apagando todas as verificações recebidas:
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;
Parece familiar? Sim É um código de função ligeiramente modificado encrypt_data do toxcore, a única diferença é que eles esqueceram de limpar a chave na pilha com sodium_memzero ... E também há erros em: handle_TCP_handshake , handle_handshake , e talvez em outro lugar também.
Fato Nº 6. Os avisos do compilador são para dummiez!
Os desenvolvedores do projeto toxcore negam categoricamente a necessidade de ativar todos os avisos do compilador, ou eles não sabem sobre eles.
Funções não utilizadas (estou particularmente satisfeito com os avisos nos testes):
../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__,\ ^
E algumas dezenas de avisos sobre variáveis não utilizadas, a comparação de assinados e não assinados e muito mais.
Minha conclusão
Citação do repositório:
Queremos que o Tox seja o mais simples possível, permanecendo o mais seguro possível.
Se eu, um não criptografador, pudesse encontrar bugs tão horríveis em um dia, imagine quantas coisas um especialista em criptografia pode encontrar depois de buscá-los de propósito por um mês?
As primeiras versões do Tox representavam um grande perigo para os usuários que confiam na segurança do Tox. As soluções proprietárias não são dignas de confiança e até mesmo as soluções de código aberto não são tão seguras quanto você deseja. Veja a recente violação de segurança em Matrix . Atualmente, muitos bugs são corrigidos e o Tox é a melhor opção disponível para segurança e privacidade dos usuários.
Da próxima vez, vou falar mais sobre o estado atual dos tox-rs. O que implementamos no Rust e por que você deve experimentá-lo.
Reddit: comentários