Autenticação Mútua HTTPS no Android via STunnel

Há uns dias atrás adquiri um celular Motorola Milestone rodando o Android 2.1. Até agora estou gostando bastante, mas logo de cara deparei-me com uma limitação: vários sistemas web internos da Tempest empregam um recurso chamado "HTTPS com autenticação mútua". Tanto o Firefox, no Linux e no Windows, bem como o IE, suportam esse recurso; mas, no Android, não achei nenhum navegador que suporte. De fato, nos foruns dos desenvolvedores do Android, descobri que esse é um pedido de melhoria que muita gente já pediu. Consegui, porém, agregar esse recurso através de alguns artifícios técnicos (nome bonito para "gambiarras") que vou relatar aqui – essencialmente, portei o utilitário STunnel pro Android. Nesse post vou descrever como fiz e como usar.

Nota: Este é um post bem técnico, voltado para quem já usou o stunnel antes e conhece bem as entranhas dele, dos sitemas operacionais e dos protocolos de rede envolvidos.

Por brevidade, não vou discutir aqui o que venha a ser sites que usam HTTPS com autenticação mútua – isso vai ficar pra outro post. Vou me contentar dizendo que esse sistema tem uma série de vantagens práticas e de segurança tanto pros desenvolvedores/operadores do site, quanto para o usuário final. Se seu site ainda não usa isso, lamento informar, você ainda está na idade da pedra da informática. O restante desse artigo assume que você conhece o modelo conceitual da autenticação mútua por assinaturas digitais e já usou o stunnel anteriormente.

Compilando o STunnel para o Android

Antes de mais nada: você não precisa ler isso tudo pra usar minha solução – se tudo que você quer é usar e não se importa como eu fiz, vá direto para a próxima seção, onde você pode baixar meu "pacote" pronto e onde descrevo como usar.

Para portar o stunnel, baixei e instalei o kit de desenvolvimento do Android (um "downloadzinho" de 2GB) e fiz o build do ambiente de desenvolvimento – por sinal, levou umas 3 horas pra compilar e torrou nada menos que 7GB do meu disco rígido. O Android usa o kernel do Linux, mas o userspace é bem diferente do "mundo GNU" (Debian, Ubuntu, Fedora). A primeira coisa foi instalar o script agcc do Andrew Ross, que simplifica sobremaneira o uso do gcc no ambiente de desenvolvimento do Android.

Depois, baixei o código-fonte da a versão mais recente do stunnel (4.33, à época em que esse artigo foi escrito), desempacotei e chamei o configure usando a abordagem clássica para compilação cruzada (pra quem não sabe, os celulares baseados no Android usam processadores ARM, e não x86 como PCs comuns):

export PATH=$PATH:~/droid/prebuilt/linux-x86/toolchain/arm-eabi-4.3.1/bin
 ./configure --host=arm-none-linux-gnueabi --with-ssl=~/droid/external/openssl

O diretório "~/droid" é onde descompactei e montei o ambiente de desenvolvimento do Android. No diretŕio "prebuilt/linux-86/toolchain/arm-eabi-4.3.1" fica o toolchain para compilação cruzada (o compilador C, assembler, linker, arquivador, e demais utilitários do binutils). No diretório "external/openssl" fica o build da biblioteca critpográfica OpenSSL que já vem nativa no Android, e da qual o stunnel precisa.

Logo no começo do processo, o configure dá uma advertência assim:

configure: WARNING: If you wanted to set the --build type, don't use --host.
    If a cross compiler is detected then cross compile mode will be used.

Mas é balela – se você tentar chamar o configure sem a opção --host que ativa a compilação cruzada, ele não vai detetar direito o toolchain e vai gerar vários erros chatos de consertar.

Por outro lado, se você seguir o caminho que eu segui e colocar a opção --host=arm-none-linux-gnueabi, ele vai prosseguir até um ponto em que dá um erro reclamando que não pode testar a presença do /dev/ptmx, /dev/ptc e /dev/urandom em modo de compilação cruzada. Nesse ponto eu já fiz a primeira forçada de barra: editei o configure e alterei as linhas 21563, 21587 e 21637 para:

  test "$cross_compiling" = no &&

(no original, tinha "yes" ao invés de "no").

Feito isso, o configure termina normalmente. Sabia que isso não ia quebrar nada porque olhei manualmente o filesystem do Android e constatei que os arquivos que ele procura de fato existem, tal como também existem no meu Ubuntu.

Feito isso, dei o make para passar para a fase de compilação – ela quebra no arquivo pty.c, porque a libc personalizada do Android parece que não tem a função openpty. Bem, eu pretendia usar o stunnel como proxy de rede, usando TCP apenas, então eu simplesmente desativei a funcionalidade de alocar pseudo-terminais do stunnel alterando o arquivo client.c e acrescentando um #ifdef no seguinte trecho de código começando na linha 835:

#ifdef HAVE_PTYS
    if(c->opt->option.pty) {
        char tty[STRLEN];

        if(pty_allocate(fd, fd+1, tty, STRLEN))
            longjmp(c->err, 1);
        s_log(LOG_DEBUG, "TTY=%s allocated", tty);
    } else
#endif // HAVE_PTYS
        make_sockets(c, fd);
    pid=fork();

Também movi o arquivo pty.c para outro diretório, de sorte que as regras automáticas do Makefile não o encontrassem.

Dei o make de novo e compilou até o fim, mas deu erro no linker reclamando que várias funções da OpenSSL eram "símbolos não-definidos". Catando na árvore ~/droid, onde instalei o ambiente de desenvolvimento do Android, descobri várias libssl.so e libcrypto.so, mas nenhuma libssl.a nem libcrypto.a, insumos indispensáveis para o linker.

Catando mais um pouco, descobri que os arquivos .o resultantes da compilção do OpenSSL do Android ficavam em ~/droid/out/target/product/generic/obj/EXECUTABLES/openssl_intermediates. Criei manualmente um arquivo libssl.a da seguinte forma:

ar a libssl.a *.o

Em seguida, movi a libssl.a para o local que o linker estava esperando que ele estivesse, em ~/droid/external/openssl/lib (este último não exsitia, eu tive de criá-lo). O certo mesmo seria ter criado dois arquivos, o libssl.a só com as funções de SSL e um libcrypto.a com as funções de criptografia, mas seria um preciosismo desnecessário naquele momento – funcionaria igualmente bem colocando tudo em um arquivo .a só. Feito isso, o processo de ligação ("link-edição", como muita gente chama, em portuquenglish) quase deu certo, mas reclamou da inexistência do símbolo RSA_generate_key_ex.

Isso acontece porque essa função é considerada "descontinuada" ("deprecated") e a OpenSSL do Android fora compilada com o flag OPENSSL_NO_DEPRECATED, que faz com que essas funções sejam omitidas. Todavia, a função ainda está lá no código fonte do OpenSSL, apenas desativada. Por isso, copiei o arquivo ~/droid/exernal/openssl/crypto/rsa/rsa_depr.c (que contém exatamente a função RSA_generate_key que precisamos) para dentro dos fontes do stunnel, ajustando também o Makefile para colocá-lo como dependência. Depois disso, finalmente, sucesso! A montagem funcionou até o fim e obtive o executável do stunnel.

Ufa! Agora entendi por que não tinha achado pela Internet afora um stunnel pra Android já prontinho – essas dificuldades podem ter sido suficientes para emperrar muita gente.

"Instalando" e Usando o Stunnel no Android

Exportei meu certificado e minha chave pública no formato que o stunnel requer, criei um arquivo de configuração para o stunnel e fiz uns scripts de partida e parada. Coloquei-os, junto com o executável compilado, dentro de uma pasta, que você pode baixar daqui (empacotado com o tar e comprimido com o bzip2). Ao usar no seu sistema, não esqueça de colocar as suas chaves e alterar o stunnel.conf para conectar no site HTTPS ou serviço sob SSL/TLS que você deseja – leia os comentários que eu deixei lá.

Se você já habilitou a conta de root no seu Android 2.1 (veja aqui como fazer isso), você poderá colocar essa pasta onde quiser e executá-lo diretamente. Mas, se você quiser evitar os perigos inerentes disso, pode executá-lo sem precisar de privilégios de root, mediante (mais) uma gambiarra: arranje dois programas de emulação de terminal (eu uso um tal de "Terminal Emulator" que eu achei no Android Market, mas pé duro, e o ConnectBot, bem mais polido).

Logo após reiniciar meu telefone, eu manualmente vou no Terminal Emulator e digito:

cp -r /sdcard/stunnel /tmp

Isso copia todo o meu diretório "stunnel" que eu deixei preparado no cartão, com o executável, minhas chave, certificado, arquivo de configuração e scripts de partida/parada, para o /tmp, para o qual todo e qualquer usuário tem permissão de escrita. Mas eu não posso executá-lo diretamente, porque o shell vem com uma umask de 0702, de sorte que os arquivos ficam com permissões 0075 (----rwxr-x) – ou seja, o próprio dono do arquivo não pode executá-lo.

Acontece que, quando você instala pacotes apk através do instalador embutido do Android, ele aloca um usuário diferente para cada aplicativo (uma boa prática que muitos admins seguem em outros Linuxes e Unixes também). Então, fecho o "Terminal Emulator" (aqui no meu ambiente ele é uid=10061) e abro o ConnectBot (uid=10047) e digito:

cd /tmp
./start

E aí roda bonitinho – nesse ponto o stunnel me pede a senha da minha chave privada e depois entra em segundo plano. Para confirmar que o stunnel está no ar, eu dou em seguida um netstat e vejo se a porta 8080, definida no stunnel.conf está aberta em modo LISTEN.

O figura ao lado mostra o resultado final – eu criando um novo post aqui no Blog. Note a URL "http://localhost:8080", ao invés do endereço "https://...". É o stunnel que está convertendo o HTTP para HTTPS. Leitores atentos, notarão, adicionalmente, que essa captura de tela foi feita no emulador, não no meu aparelho real. Mas, acreditem, funciona redondinho no meu Milestone.

A única chatice desse método é que, se você desligar seu telefone, terá de fazer esse procedimento manual novamente ao religá-lo. Pra mim, é um preço pequeno a pagar para manter o isolamento de processos e privilégios padrão do Android. Quem sabe, quando eu fizer as pazes com Java, eu faço um miniaplicativo gráfico para instalar e configurar tudo isso automaticamente. Por ora, fiz um outro scriptzinho em perl com o SL4A/ASE (uma maravilha que merece não um, mas vários posts só sobre ele) que já automatiza isso pra mim. Mas se você chegou até aqui, saberá fazer isso por si próprio.

Ajustes nos Sites

Usar o stunnel como intermediário para acessar websites que usem HTTP tem uma desvantagem quase fatal – o site tem de ser "bem comportado", no sentido que ele não pode usar links absolutos, entre outras restrições. A maior parte dos sistemas web internos da Tempest já satisfaz esse quesito nas páginas HTML, mas há pelo menos um lugar em que eles não têm como fazer isso – nos redirecionamentos HTTP 302, pois o cabeçalho Location requer uma URI absoluta.

Esses redirecionamentos acontecem, em geral, após um POST que completa alguma operação, tal como salvar uma matéria ou anexar um arquivo aqui no Blog. Por isso, fiz uma pequena gambiarra: alterei o código do pulpit (o software do Blog) para, se o Referer do pedido HTTP vier começar com "http://localhost:8080", ele gera os redirecionamentos usando essa URL-base, e não a URL original "https://...". Com isso, por exemplo, agora eu consigo editar, postar matérias e moderar o blog direto do meu Android.

Os outros vários sistemas internos da Tempest que eu quero acessar ficam atrás de um proxy reverso chamado ViaProxy, que faz um monte de coisas – balanceia/redireciona carga, comprime o HTML, transforma HTTPS com autenticação mútua em nome-e-senha para alguns sistemas internos que não suportam nativamente autenticação por certificados digitais, entre outros. Para cumprir essas funções, o ViaProxy tem todo um framework para alterar HTTP em tempo real. Lá, alterei algumas coisas para implementar truques semelhantes para permitir o uso dos sistemas através do stunnel.

Ah,... a liberdade de mudar o software para ele fazer o que eu quero e não o que o fabricante acha que eu devo poder fazer... isso não tem preço! Por acaso eu já disse que estou gostando desse tal de Android e que esse negócio de software de fonte aberto realmente presta?

Comentários
Aceita-se formatação à la TWiki. HTML e scripts são filtrados. Máximo 15KiB.

 
Enviando... por favor aguarde...
Comentário enviado com suceso -- obrigado.
Ele aparecerá quando os moderadores o aprovarem.
Houve uma falha no envio do formulário!
Deixei uma nota para os admins verificarem o problema.
Perdoe-nos o transtorno. Por favor tente novamente mais tarde.
Vishal | 2011-11-01 11:22:47 | permalink | topo

I want to bind it with the application , can you explain me the process. i really need it. Please help

Fabio | 2010-11-16 21:41:44 | permalink | topo

Uma outra gambiarra, mas que pode ajudar usuários mais leigos:

http://androidero.blogspot.com/2010/11/acessando-wifi-via-proxy-com.html

Abs!

Victor Hora | 2010-08-02 10:28:41 | permalink | topo

Aqui funcionou perfeitamente! :D

-o/ \o/ \o-