Compilações reproduzíveis: provando que o software em execução é o software que inspeccionou
A Suíça publicou o código-fonte do seu sistema de votação para que qualquer pessoa o pudesse inspecionar — e investigadores descobriram imediatamente uma porta traseira oculta que poderia forjar uma prova impecável enquanto alterava silenciosamente os votos.
Estamos em Março de 2019, e em Berna, a Swiss Post está a fazer algo que parece o padrão-ouro da transparência eleitoral. O seu sistema de votação pela internet — construído com o fornecedor espanhol Scytl, destinado a ser utilizado em votações federais suíças vinculativas — está prestes a ter o seu código-fonte completo publicado online para o mundo ler.
Isto é aquilo que os defensores da democracia vinham exigindo há anos. Abrir a caixa-negra. Deixar os peritos ver. Confiança através da inspecção.
Poucas semanas depois, três criptógrafos independentes — Sarah Jamie Lewis, Olivier Pereira e Vanessa Teague — analisaram-no. E aquilo que encontraram não foi reconfortante.
A porta traseira que ninguém viu até alguém olhar
O centro do sistema era uma prova matemática. Após todos os votos serem misturados e embaralhados para preservar o anonimato, o software geraria uma "prova de embaralhamento" criptográfica — um pedaço de matemática que supostamente demonstraria, a qualquer pessoa que verificasse, que o embaralhamento tinha sido realizado correctamente e nenhum voto tinha sido alterado.
Era chamado verificabilidade universal. A ideia: não tem de confiar no servidor. Verifica a prova você mesmo.
Os investigadores descobriram que a prova era construída em algo chamado esquema de compromisso com porta traseira. Em linguagem simples: a prova de embaralhamento podia ser feita para "verificar" correctamente mesmo que os votos tivessem sido silenciosamente alterados — desde que soubesse o valor da porta traseira. E o valor da porta traseira era detido pelos operadores do sistema.
Por outras palavras, uma autoridade que conhecesse esse valor podia gerar uma transcrição de prova de embaralhamento que passava na verificação enquanto tinha reorganizado cada voto. A alegação de "verificabilidade universal" era infalsável de fora.
Chamaram ao seu artigo "Ceci n'est pas une preuve." Isto não é uma prova.
A Swiss Post e a Scytl reconheceram a descoberta. As autoridades suíças suspenderam o sistema.
A lição parece técnica. Não é. A falha era invisível enquanto o código era fechado. Tornou-se detectável no momento em que foi aberto. Mas encontrá-la ainda exigiu criptógrafos de classe mundial a trabalhar durante semanas. A questão mais ampla — aquela sobre a qual este artigo realmente trata — é: o que acontece entre "alguém consegue ler o código" e "pode ter a certeza de que o código lido é o que executou na máquina"?
O caso suíço abriu o código. Mas expôs uma lacuna mais profunda que o código aberto não fecha automaticamente.
Ler a receita não é a mesma coisa que provar o prato
Imagine um restaurante que publica as suas receitas. Cada ingrediente, cada técnica, cada passo — tudo online, o tempo todo.
Agora imagine que a cozinha está fechada. Pode ler o que é suposto acontecer. Não consegue ver o que realmente acontece.
Esta é precisamente a situação com a maioria dos softwares de votação, mesmo softwares de votação de código aberto. Publicar código-fonte é uma condição necessária para verificação independente. Não é uma condição suficiente.
Eis o porquê. O software que lê num repositório não é o software que funciona na máquina. Entre "código-fonte" e "programa em execução" há uma cadeia de passos:
- Um compilador toma o código-fonte e traduz-o em código binário legível pela máquina.
- Esse binário é embalado, instalado no hardware de votação ou servidores, e selado criptograficamente — ou não.
- No momento da eleição, uma máquina inicia e executa o que tem nela.
Cada um desses passos é um local onde o código em execução pode divergir do código-fonte que inspeccionou, sem nenhuma evidência visível. Um compilador pode ser manipulado para inserir código que não aparece no código-fonte. Um binário pode ser trocado após ser construído. Uma máquina pode iniciar uma versão diferente da que pensava estar carregada.
A porta traseira suíça vivia no código-fonte. Mas uma porta traseira diferente poderia viver em qualquer lugar dessa cadeia — e nunca aparecer no código.
O problema das compilações reproduzíveis, em linguagem simples
Há uma disciplina na engenharia de software chamada compilações reproduzíveis. O seu objetivo é simples: dado o mesmo código-fonte, o mesmo compilador e as mesmas instruções de compilação, qualquer pessoa que execute o processo de compilação deve obter um binário idêntico — byte por byte, bit por bit — todas as vezes.
Isto importa porque lhe dá uma forma de verificar. Se a autoridade eleitoral publica um hash binário — uma impressão digital digital única do software na máquina — e se conseguir reproduzir independentemente esse binário exacto a partir do código-fonte publicado, tem uma garantia criptográfica de que o código na máquina é o código que leu.
Se o binário não corresponder, sabe que algo mudou entre o código-fonte e a máquina. Não sabe o quê mudou. Mas sabe para questionar.
Sem compilações reproduzíveis, a lacuna entre "código-fonte inspeccionado" e "software em execução" é invisível. Não tem ferramenta para a verificar. Está de volta a confiar na autoridade que fez a compilação.
O projecto Reproducible Builds — um esforço multi-projectos na comunidade de engenharia de software — documentou sistematicamente o quão difícil é isto de conseguir e quantas formas a cadeia de compilação pode produzir saídas diferentes de código-fonte idêntico. Os compiladores incorporam registos de tempo. Os ligadores inserem variáveis de ambiente. A ordenação de ficheiros varia. Cada um é um mecanismo pelo qual duas compilações "idênticas" podem produzir binários diferentes sem que ninguém intencione fraude.
Atestação: a segunda metade do problema
Mesmo que a sua compilação seja reproduzível, um segundo problema permanece. A reprodutibilidade prova que alguém consegue reconstruir o mesmo binário a partir do código-fonte. Não prova que o binário agora em execução na sua máquina de votação é esse binário.
É aqui que entra a atestação.
Atestação é o processo de assinar criptograficamente uma declaração: "Este dispositivo está a executar o binário de software X, compilado no tempo T, com hash H." A assinatura deve vir de algo resistente a manipulações — idealmente de hardware que não pode ser substituído por software após o arranque, como um Módulo de Plataforma Fidedigna (TPM) ou um módulo de segurança de hardware. A declaração assinada pode então ser verificada por qualquer pessoa que tenha a chave pública.
Pense nisso como um selo à prova de manipulações num cartão de memória — excepto que em vez de um adesivo físico que se rasga quando aberto, é uma assinatura matemática que consegue verificar sem confiar na pessoa que lhe entrega o cartão.
Juntas, compilações reproduzíveis e atestação formam uma cadeia: lê o código-fonte → compila-o e obtém o binário H → verifica que o dispositivo em execução atesta o binário H → tem a confiança de que a máquina está a executar o código que leu.
Sem essa cadeia, tem dois factos desconectados: aqui está o código-fonte e aqui está uma máquina a executar algo. Se estas duas coisas correspondem é uma questão de confiança, não de verificação.
"Executamos software certificado e de código aberto" é uma alegação. Compilações reproduzíveis e atestação são o que a tornam verificável.
O que o caso suíço nos diz sobre o problema mais profundo
Volte à Suíça. Os investigadores encontraram a porta traseira porque leram o código-fonte. A Swiss Post reconheceu a falha e suspendeu o sistema. Até agora: o sistema funcionando como pretendido.
Mas repare no que não foi verificado em qualquer versão reportada dessa história. Mesmo que a porta traseira não existisse no código-fonte, nenhuma parte independente tinha um mecanismo para confirmar que o binário em execução nos servidores da Swiss Post foi compilado a partir desse código-fonte exacto — e não a partir de uma versão ligeiramente modificada com uma falha que nunca apareceu no código público.
Isto não é uma hipótese. Em 2003, o informático Ken Thompson, na sua palestra do Prémio Turing, descreveu como um compilador poderia ser modificado para inserir uma porta traseira num programa automaticamente — e depois ser ainda modificado para inserir a porta traseira em si mesmo, de modo que mesmo compilar o compilador a partir de código-fonte limpo produziria um binário comprometido. O código-fonte parece prístino. O compilador parece prístino. A saída é trojanizada.
Isto não é um ataque teórico exótico. É uma classe de ameaça bem compreendida. E a defesa contra ela é compilações reproduzíveis: se a saída é determinística e publicada, qualquer parte independente consegue reconstruir a partir do código-fonte e detectar uma discrepância. Se não for — se cada compilação produzir um binário diferente por razões técnicas inocentes — a comparação nunca poderá ser feita.
O caso suíço provou que código aberto é melhor que código fechado. Também provou que código aberto, sozinho, não é suficiente.
A lacuna que o nosso rastreador de lacunas está a acompanhar
Na TrustVoting, rastreamos uma lacuna específica: a ausência, na maioria dos sistemas de votação implementados, de compilações reproduzíveis verificadas publicamente e atestação de dispositivos. Pode ver com que frequência esta lacuna aparece, e onde, em todo o mundo.
O sistema suíço tinha código aberto. É um dos programas de votação pela internet mais orientados para a transparência no mundo democrático. Os investigadores ainda encontraram uma falha na criptografia — que teria sido impossível de encontrar num sistema fechado. E mesmo depois dessa falha ser encontrada e corrigida, um observador independente não teria um mecanismo para confirmar que o binário corrigido no servidor derivava do código-fonte corrigido.
Esta é a lacuna. Situa-se precisamente na costura entre o código que consegue ler e o software que realmente lida com o seu voto.
Algumas coisas que a encerrariam:
- Instruções de compilação publicadas publicamente que produzem o hash binário exacto que a autoridade alega estar a executar — para que qualquer observador suficientemente técnico conseguisse verificar independentemente.
- Atestação assinada a partir de hardware à prova de manipulações em cada dispositivo de votação, publicando o hash do binário em execução juntamente com cada lote de resultados.
- Monitorização contínua e pós-implementação contra o hash publicado — para que uma troca de software que aconteça após certificação mas antes do dia da eleição fosse detectável.
Nenhuma destas é exótica. São prática padrão em implementações de software de alta segurança. Estão largamente ausentes dos requisitos de certificação de sistemas de votação.
O que ainda não consegue verificar hoje — e o que a resolveria
Aqui está a posição desconfortável em que nos encontramos após a Suíça 2019.
Sabemos que código aberto é melhor. A porta traseira foi encontrada precisamente porque o código era legível. Um sistema fechado teria enviado a falha sem ser detectada.
Sabemos que verificabilidade criptográfica é melhor do que apenas registos de papel. A filosofia de design inteira do sistema suíço — verificabilidade universal, provas verificáveis matematicamente — é uma tentativa de deixar os eleitores confirmar, sem confiar na autoridade, que o seu voto foi contado.
E sabemos que ambas são insuficientes se não incluírem a ponte entre código-fonte e binário em execução.
Neste momento, para virtualmente cada sistema de votação implementado no mundo, a resposta honesta a "como sei que o software nessa máquina é o software que foi revisado?" é: não consegue. Confia na autoridade que o implementou.
Essa confiança pode estar bem colocada. Mas confiança não é verificação. O Tribunal Constitucional Federal Alemão compreendeu isto quando decidiu em 2009 que votação electrónica é apenas legítima quando cidadãos ordinários — não apenas peritos — conseguem verificar independentemente cada passo essencial desde o boletim até ao resultado. O raciocínio do tribunal aplica-se tão nitidamente à implementação de software como à contagem de votos: se confirmar que o código certo está em execução exige confiar nas pessoas que o executam, o passo essencial de verificação está em falta.
A solução não é derrubar esforços de votação de código aberto. É completá-los. Código aberto mais compilações reproduzíveis mais atestação assinada mais hashes binários publicados publicamente iguala um sistema onde "o software que inspeccionou é o software que executou" é verificável por qualquer pessoa com um portátil e curiosidade — não apenas pela autoridade que o implementou.
Até essa cadeia ser encerrada, cada alegação "usamos software aberto e certificado" é um convite à confiança. E um sistema eleitoral que lhe pede para confiar nele, em vez de lhe mostrar a prova, não terminou de resolver o problema.
Veja como a lacuna de compilações reproduzíveis aparece nos sistemas de votação que rastreamos
Leia a versão de 2 minutos deste argumento
Explore o mapa global de lacunas de verificação de tecnologia eleitoral
Fontes
- Lewis, Pereira, Teague — Ceci n'est pas une preuve (trapdoor commitments in the Scytl-SwissPost Internet voting system), 2019
- Bundesverfassungsgericht, Judgment of 3 March 2009, 2 BvC 3/07 and 2 BvC 4/07 (English translation)
- Springall, Finkbeiner, Durumeric, Kitcat, Hursti, MacAlpine, Halderman — Security Analysis of the Estonian Internet Voting System, ACM CCS 2014
- California Secretary of State news release (Aug. 21, 2018): certifying LA County VSAP Tally as California's first certified open-source election technology
- Halderman, Teague — The New South Wales iVote System: Security Failures and Verification Flaws in a Live Online Election, E-Vote-ID 2015 (arXiv:1504.05646)
- Curling v. Raffensperger, No. 1:17-cv-2989-AT, Opinion and Order (N.D. Ga. Oct. 11, 2020), Doc. 964 (Justia)