Criptografia no Linux: Agentes

by Tom Ryder (autoria) e Rafael Beraldo (tradução)

Este é o quinto post de uma série de dez posts traduzindo o original de Tom Ryder, Linux Crypto. Essa série está sob uma licença Creative Commons 3.0.

Para a lista de posts, veja a introdução.


Agora que o GnuPG e o SSH estão configurados seguramente, podemos criptografar, descriptografar, assinar e verificar mensagens, e autenticar seguramente a servidores remotos sem o risco de expor nossas senhas e com uma possibilidade efetivamente nula de ataques de força bruta. Isso tudo é ótimo, mas ainda existe um elo fraco em nossa corrente com o qual lidar — nossas senhas.

Se você usa essas ferramentas com frequência, inserir nossa senha para a maior parte das operações pode se tornar irritante. Podemos ficar tentados a incluir um meio de automatizar a inserção da senha, ou simplesmente não usá-la, deixando nossa chave privada descriptografada. Como usuários preocupados com a segurança, nós definitivamente desejamos evitar a última possibilidade, caso o arquivo de nossa chave privada seja roubado, e é aqui que o conceito de agentes para o SSH e GnuPG entra.

Um agente é um daemon projetado para otimizar o processo de usar chaves privadas descriptografadas, guardando os detalhes seguramente na memória, idealmente por um período limitado de tempo. Ele permite que você insira sua senha do SSH ou GnuPG apenas uma vez e usos subsequentes que requerem a chave privada descriptografada são gerenciados pelo agente.

Neste artigo, discutiremos o básico da configuração de agentes para o SSH e o GnuPG. Depois que você souber como eles funcionam, iremos apresentar uma ferramenta conveniente para iniciá-los e gerenciá-los facilmente.

Agentes para o SSH

O programa ssh-agent(1) vem como parte da suíte OpenSSH. Ele pode ser executado em dois modos, como parte de um processo pai ou daemonizado e rodando no plano de fundo. Iremos discutir o último método, uma vez que é mais comumente usado e mais flexível.

Instalação e configuração

Quando rodamos o ssh-agent(1) pela primeira vez, seu comportamento é curioso: ele não parece fazer nada além de soltar umas linhas de shell script enigmático:

$ ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-EYqoH3qwfvbe/agent.28881; export SSH_AUTH_SOCK;
SSH_AGENT_PID=28882; export SSH_AGENT_PID;
echo Agent pid 28882;

No entanto, podemos verificar que o daemon está rodando com o PID que ele menciona:

$ ps 28882
  PID TTY      STAT   TIME COMMAND
  28882 ?        Ss     0:00 ssh-agent

Mas se ele está executando sem problemas, qual o problema do shell script que ele produz? Por que ele não roda o script de uma vez?

A resposta é uma interessante solução alternativa para a rigidez do modelo de processos do Unix; especificamente, um processo não pode modificar seu ambiente pai. As variáveis SSH_AUTH_SOCK e SSH_AGENT_PID são projetadas para permitir que programas como o ssh(1) encontrem o agente e possam se comunicar com ele, então definitivamente precisamos atribuir valores a elas. No entanto, se o ssh-agent(1) tentasse configurá-las automaticamente, as configurações seriam aplicadas apenas para seu próprio processo e não para o shell no qual o executamos.

Assim, além de executar o ssh-agent(1), precisamos executar o código que ele imprime na tela para que as variáveis sejam atribuídas em nosso shell. Um bom método para fazer isso em Bash é usar o eval e a substituição de comando com $(...).

$ eval $(ssh-agent)
Agent 3954

Se executarmos esse comando, vemos não apenas que o ssh-agent(1) está rodando, mas que temos duas novas variáveis em nosso ambiente identificando o caminho de seu socket e a ID do processo:

$ pgrep ssh-agent
3954
$ env | grep ^SSH
SSH_AUTH_SOCK=/tmp/ssh-oF1sg154ygSt/agent.3953
SSH_AGENT_PID=3954

Com isso feito, o agente está pronto e podemos começar a usá-lo para gerenciar nossas chaves.

Uso

O próximo passo é carregar nossas chaves no agente com o ssh-add(1). Dê ao programa o caminho completo da chave privada que o agente deve usar. O caminho é, provavelmente, ~/.ssh/id_rsa ou ~/.ssh/id_dsa:

$ ssh-add ~/.ssh/id_rsa
Enter passphrase for /home/tim/.ssh/id_rsa:
Identity added: /home/tim/.ssh/id_rsa (/home/tim/.ssh/id_rsa)

Você pode deixar esse argumento de lado se desejar que o ssh-add adicione todos os tipos de chave padrão em ~/.ssh caso elas existam (id_dsa, id_rsa e id_ecdsa):

$ ssh-add

De qualquer maneira, sua senha será requerida; isso é esperado, e você deve digitá-la.

Se pedirmos ao ssh-add(1) que liste as chaves que está gerenciando, veremos a chave que acabamos de adicionar:

i$ ssh-add -l
4096 87:ec:57:8b:ea:24:56:0e:f1:54:2f:6b:ab:c0:e8:56 /home/tim/.ssh/id_rsa (RSA)

Agora, caso tentemos conectar a outro servidor utilizando essa chave, não teremos de providenciar nossa senha; seremos conectados imediatamente:

tim@local:~$ ssh remoto
Welcome to remote.sanctum.geek.nz, running GNU/Linux!
tim@remoto:~$

O padrão do agente é manter as chaves permanentemente, até que ele seja parado ou até que as chaves sejam explicitamente removidas, uma a uma, com o comando ssh-add -d <arquivo da chave>, ou até que todas sejam removidas de uma vez com ssh-add -D. Para os cautelosos, você pode configurar um tempo limite com o comando ssh-add -t. Por exemplo, para que o ssh-add esqueça sobre as chaves após duas horas, você pode executar:

$ ssh-add -t 7200 ~/.ssh/id_rsa

Para matar o agente completamente, você pode usar o comando ssh-agent -k, novamente com eval $(...) envolvendo o comando:

$ eval $(ssh-agent -k)
Agent pid 4501 killed

Para se livrar do agente após sair de sua sessão, você pode considerar adicionar o comando acimo ao script ~/.bash_logout ou similares.

Configuração permanente

Se você gostou do agente e ele torna o gerenciamento de suas chaves mais conveniente, faz sentido colocá-lo num script de inicialização como o ~/.bash_profile. Dessa maneira, o agente será iniciado a cada login e seremos capazes de comunicar com ele a partir de qualquer subshell (xterm, screen, ou o tmux, se configurado apropriadamente):

eval $(ssh-agent)
ssh-add ~/.ssh/id_rsa

Em nosso próximo login num TTY, nossa senha será requerida e, a partir daí, poderemos conectar a qualquer máquina usando as chaves gerenciadas pelo agente:

tim@local:~$ ssh remoto
Welcome to remote.sanctum.geek.nz, running GNU/Linux!

Se você deseja utilizar um gerenciador de desktop como o GDM ou o XDM, você pode adicionar uma variável apontando para o programa ssh-askpass(1):

eval $(ssh-agent)
export SSH_ASKPASS=/usr/bin/ssh-askpass
ssh-add ~/.ssh/id_rsa

Se a variável SSH_ASKPASS está atribuída como acima e DISPLAY se refere a um monitor em funcionamento, um simples aviso gráfico aparecerá, pedindo nossa senha:

ssh-askpass

Esse programa talvez tenha de ser instalado separadamente. Em sistemas derivados do Debian, o nome do pacote é ssh-askpass.

Todos os processos filhos e subshells do shell de login irão herdar as variáveis do agente, uma vez que foram exportadas utilizando o export:

tim@local:~$ screen
tim@local:~$ tmux bash
tim@local:~$ bash
tim@local:~$ ssh remote
Welcome to remote.sanctum.geek.nz, running GNU/Linux!
tim@remote:~$

Dessa maneira, temos que digitar nossa senha apenas uma vez por sessão de login e podemos conectar a todos os servidores aos quais nossas chaves conferem acesso… Muito conveniente!

Agentes do GnuPG

Assim como o ssh-agent(1), existe também um agente para gerenciar chaves do GnuPG, chamado gpg-agent(1). O seu comportamento é muito similar. Em sistemas derivados do Debian, ele pode ser instalado com o pacote gnupg-agent. Você também deve instalar um programa de pinentry [entrada de PIN]; como estamos focando no aprendizado dos aspectos básicos na linha de comando, utilizaremos o pinentry-curses(1):

# apt-get install gnupg-agent pinentry-curses

Configuração

Iniciaremos o agente usando o mesmo truque com eval $(...) que aprendemos com o ssh-agent:

$ eval $(gpg-agent --daemon)

Podemos verificar que o agente está rodando no plano de fundo com o PID infomado e também que temos uma nova variável de ambiente:

$ pgrep gpg-agent
5131
$ env | grep ^GPG
GPG_AGENT_INFO=/tmp/gpg-hbro8r/S.gpg-agent:5131:1

Também iremos atribuir a variável GPG_TTY, que informa o programa pinentry sobre o terminal no qual ele deve desenhar sua tela de inserção de senha:

$ export GPG_TTY=$(tty)
$ echo $GPG_TTY
/dev/pts/2

Finalmente, para estimular o gpg(1) a realmente usar o agente, precisamos adicionar uma linha aqui arquivo ~/.gnupg/gpg.conf. Você pode criar esse arquivo, caso ele não exista.

use-agent

Uso

Isso feito, caso tentemos realizar qualquer operação que requeira nossa chave privada, a senha será requerida não diretamente na linha de comando, mas por nosso programa de entrada de PIN:

$ gpg --armor --sign mensagem1.txt

┌──────────────────────────────────────────────────────────┐
│ You need a passphrase to unlock the secret key for user: │
│ "Timoteo Aspargos (taspargos) <taspargos@exemplo.com.br>"│
│ 4096-bit RSA key, ID 25926609, created 2013-08-22        │
│ (main key ID 1FC2985D)                                   │
│                                                          │
│                                                          │
│ Passphrase ***__________________________________________ │
│                                                          │
│       <OK>                                 <Cancel>      │
└──────────────────────────────────────────────────────────┘

Após inserirmos a senha, a operação é realizada:

$ ls mensagem1*
mensagem1.txt
mensagem1.txt.asc

Mais tarde, se realizarmos outra opção que requeira a chave privada, veremos que a senha não é pedida:

$ gpg --armor --sign mensagem2.txt
$ ls mensagem2*
mensagem2.txt
mensagem2.txt.asc

O agente armazenou nossa chave privada em cache, tornando muito mais fácil realizar uma série de operações utilizando-a. O tempo limite padrão é de 10 minutos, mas você pode mudá-lo com as configurações default-cache-ttl e max-cache-ttl em ~/.gnupg/gpg-agent.conf. Por exemplo, para reter a chave privada por uma hora após o seu último uso, com um máximo de duas horas a partir do primeiro uso, escreveríamos:

default-cache-ttl 3600
max-cache-ttl 7200

Para que esses valores façam efeito, precisamos recarregar o agente:

$ gpg-connect-agent <<<RELOADAGENT
OK

Configuração permanente

Assim como o ssh-agent(1), um local ideal para o código de inicialização do gpg-agent(1) é um script de login como o ~/.bash_profile:

eval $(gpg-agent --daemon)

O agente será iniciado e todas as suas variáveis de ambiente serão atribuídas para todos os subshells, assim como com o ssh-agent.

Se você estiver usando uma ferramenta de entrada de PIN, você também deve adicionar o seguinte ao fim de seu script de inicialização interativo. No Linux, ele é arquivo ~/.bashrc; no Mac OS X, você talvez precise colocá-lo em ~/.bashrc.

export GPG_TTY=$(tty)

Keychain

Para gerenciar tanto o ssh-agent(1) quanto o gpg-agent(1) eficientemente, existe uma ferramenta chamada keychain(1). Ela fornece uma maneira simples de iniciar ambos os agentes com um único comando, incluindo carregar as chaves durante a inicialização. Ele também evita que qualquer um dos agentes seja executado duas vezes, identificando agentes inciados em outros locais do sistema. Muitos ambientes de desktop são configurados para iniciar um ou ambos os agentes, por isso faz sentido reutilizá-los quando possível, e nisso o keychain(1) se sobressai.

Em sistemas baseados no Debian, o programa está disponível no pacote keychain:

# apt-get install keychain

Com o keychain instalado, podemos iniciar ambos os agentes com apenas um comando em ~/.bash_profile:

eval $(keychain --eval)

Opcionalmente, podemos incluir os nomes dos arquivos das chaves SSH em ~/.ssh ou das IDs em hexadecimal das chaves do GnuPG como argumentos para o carregamento imediato das chaves privadas (incluindo a solicitação das senhas) durante a inicialização.

eval $(keychain --eval id_rsa 0x1FC2985D)

Se esse programa estiver disponível em seu sistema, ele é altamente recomendável; gerenciar seus agentes e ambientes pode ser trabalhoso, e o keychain(1) faz todo o trabalho duro, sem que você tenha de se preocupar se um agente está disponível em seu contexto em particular. Confira a página do projeto para mais informações sobre essa ferramenta.

Essa entrada é a parte 5 de 10 na série Criptografia no Linux.