- Tokens de segurança assinam dentro do dispositivo sem exportar a chave privada para fora dele e exigem uma ação física do usuário, o que dificulta que um atacante remoto produza assinaturas arbitrárias
- Eles podem ser usados para autenticação SSH, U2F, login local sem senha,
sudo e assinatura de commits do git, e os dispositivos de segurança embutidos em notebooks e smartphones modernos podem substituir uma YubiKey
- O arquivo de “chave privada” criado com
ssh-keygen -t ed25519-sk não é a chave privada real, mas um handle que aponta para a chave dentro do token, e é possível gerar o mesmo arquivo de chave SSH em outros computadores com o mesmo token
- Em um MacBook, foi possível configurar o secure element como chave SSH para permitir login SSH baseado em Touch ID, e para assinatura de commits do git foi necessário usar
ssh-agent e definir user.signingKey no formato key::, em vez de usar um caminho de arquivo
- Tokens de segurança não permitem recuperar a chave privada em caso de perda e têm um risco de usabilidade por acostumar o usuário a tocar repetidamente no dispositivo; em notebooks Windows, o Windows Hello pôde confirmar o uso da chave SSH com reconhecimento facial, impressão digital ou PIN
Vantagens e limitações dos tokens de segurança
-
Estrutura central para bloquear ataques remotos
- Um token de segurança mantém o par de chave privada/pública dentro do dispositivo; a chave pública pode ser extraída facilmente, mas a chave privada não sai do dispositivo
- Ao enviar ao dispositivo o pacote de dados a ser assinado, ele faz a assinatura internamente com a chave privada e normalmente exige alguma ação física do usuário, como pressionar um botão tátil piscando
- Mesmo que um atacante remoto consiga acesso ao computador, se o usuário não agir no mundo real o token de segurança não fará assinaturas arbitrárias, então isso parece melhor do que manter um par completo de chave privada/pública SSH como arquivo no diretório
~/.ssh
- Há opções para quem quer firmware FOSS, como SoloKeys e Nitrokeys
- Tokens de segurança mais avançados adicionam recursos biométricos, como leitor de impressão digital embutido, mas o ponto central continua sendo a estrutura que não permite que a chave privada saia do dispositivo
-
Riscos de usabilidade
- Se o usuário se acostumar a pressionar o token de segurança sempre que ele pisca, pode acabar respondendo sem pensar até a solicitações maliciosas
- Durante sequências contínuas de assinatura, quando o token precisa ser pressionado repetidamente, pode ser difícil perceber de fato mais um pedido piscando
- Apple e Microsoft usam, em apps autenticadores no smartphone, um método que mostra um código numérico aleatório a cada solicitação de acesso para o usuário digitá-lo, mas isso é incômodo e reduz a vantagem de usabilidade dos tokens de segurança em relação a apps como Authy ou Google Authenticator que usam TOTP
-
Perda e backup
- Se você perder um token de segurança, aquela chave privada desaparece para sempre e não há como fazer backup dela
- Para evitar o risco de ficar bloqueado em várias contas, o ideal é comprar pelo menos 2 tokens de segurança e registrá-los no mesmo serviço
- Como alternativa, existe um método de backup e restauração que transforma a chave privada em uma lista de palavras legíveis por humanos, como no BIP 39
- Se a chave privada puder sair do enclave seguro, também se torna possível um ataque de phishing que induza o usuário a anotar a lista de palavras em um lugar indevido
- Se a possibilidade de perder todos os tokens de segurança for uma preocupação séria, a lista de palavras do BIP 39 pode ser o último recurso para recuperar o acesso ao sistema
Usando tokens de segurança com SSH e git
-
Colocando a chave privada SSH em um token de segurança
- Normalmente, ao executar
ssh-keygen, é criado um par de arquivos contendo a chave privada completa
- Para manter a chave privada em um token de segurança, instala-se o libfido2 seguindo as instruções FIDO/U2F da Yubico e, com o token conectado, executa-se
ssh-keygen -t ed25519-sk
- Mesmo nesse caso, é gerado um par de arquivos, mas o arquivo de “chave privada” não é a chave privada real e sim um handle que aponta para a chave privada dentro do token de segurança
- Se você executar
ssh-keygen -t ed25519-sk novamente com o mesmo token de segurança, poderá criar os mesmos arquivos de chave privada/pública em qualquer computador, então o acesso SSH se desloca com o token de segurança, não ficando preso a um arquivo específico de um computador específico
-
Autenticação no git e assinatura de commits
- Cerca de 90% das vezes em que se precisa tocar no token de segurança é por causa do uso de git
- Os forges de git implementam autenticação SSH para operações de push e pull, e ao enviar o arquivo
id_ed25519_sk.pub criado acima é possível autorizar o par de chaves do token de segurança
- O git também aceita chaves SSH para assinatura de commits; depois de seguir a documentação do GitHub em Configurar sua chave de assinatura com uma chave SSH e executar
git config --global commit.gpgsign true, todos os commits passam a ser assinados automaticamente
- Para que o forge de git reconheça o commit como assinado por você, é preciso enviar novamente a chave pública, e esse campo geralmente é separado do campo usado para autenticação SSH
-
Incômodos da assinatura de commits
- Ao fazer rebase em uma lista longa de commits, é preciso assinar novamente todos eles
- Em uma YubiKey com leitor de impressão digital, a taxa de falha do reconhecimento foi alta demais para assinar dezenas de commits seguidos, a ponto de abandonar o uso
- O jujutsu, um wrapper do git centrado em “rebase/amend”, tem uma forma de assinar commits apenas no momento do push
-
Login local no Linux e sudo
Usando o secure element do MacBook como chave SSH
- Deixar um token de segurança conectado o tempo todo à porta USB-C faz com que ele fique para fora como uma pequena alavanca, podendo danificar a porta e o token se for esbarrado ou derrubado
- Em um MacBook Air M1 de 2020, o secure element embutido foi configurado como chave SSH seguindo as instruções de Arian van Putten
sc_auth create-ctk-identity -l ssh -k p-256-ne -t bio
ssh-keygen -w /usr/lib/ssh-keychain.dylib -K -N ""
- Esses comandos criam um par de arquivos de chave privada/pública
id_ecdsa_sk_rk, e esses arquivos são movidos para o diretório ~/.ssh
- Também aqui o arquivo de chave privada não é a chave privada real, mas um handle para a chave dentro do dispositivo, então ele pode ser copiado e colado publicamente
- Para adicionar a chave pública como authorized key em um servidor de homelab, executa-se o seguinte
ssh-copy-id -i ~/.ssh/id_ecdsa_sk_rk.pub <server nickname>
- Depois disso, adiciona-se a seguinte configuração em
~/.ssh/config
Host *
IdentityFile ~/.ssh/id_ecdsa_sk_rk
SecurityKeyProvider=/usr/lib/ssh-keychain.dylib
- Ao executar
ssh <server nickname>, o macOS mostra automaticamente a solicitação de impressão digital antes do login, e em seguida o login via SSH prossegue normalmente
Assinando commits do git com o secure element do MacBook
- Mesmo configurando
git config --global user.signingKey /Users/ahelwer/.ssh/id_ecdsa_sk_rk e atualizando o arquivo .ssh/allowed_signers, a assinatura de commits do git não funciona de imediato
- O git falha ao assinar commits e exibe erros como
device not found?
error: Signing file /var/folders/l5/5wqvq2l10p96wtdtfr6lvrvw0000gn/T//.git_signing_buffer_tmpc4uQgO
Confirm user presence for key ECDSA-SK SHA256:oQDA2SNYb2MoSQcxJVSmWyAeAWPqMp7rxliBRfi87as
Couldn't sign message: device not found?
Signing /var/folders/l5/5wqvq2l10p96wtdtfr6lvrvw0000gn/T//.git_signing_buffer_tmpc4uQgO failed: device not found?
fatal: failed to write commit object
- A solução é usar o ssh-agent em vez de apontar diretamente para os arquivos no diretório
~/.ssh
- Seguindo o tutorial acima, registra-se o par de chaves no ssh-agent com o comando abaixo
ssh-add -K -S /usr/lib/ssh-keychain.dylib
- Depois disso, em
user.signingKey não se usa um caminho de arquivo: coloca-se na ~/.gitconfig a própria chave com o prefixo key:: antes do conteúdo de ~/.ssh/id_ecdsa_sk_rk.pub
[user]
name = Andrew Helwer
signingKey = "key::sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGxFEdnIg6ppz+pQCdd1eisjOV4gxrjMv1Y4SbtdLoSm6CJCgPZ6q7lnNyuQQsdnS4/Tllsc656AQL7BO3OS47cAAAAEc3NoOg== ssh:"
- Depois dessa configuração, foi possível assinar arquivos com a chave armazenada no secure element do MacBook e fazer push do site no GitLab Pages
Resultados no Windows e no Linux
- Também foi feito um teste rápido no notebook Windows fornecido pela empresa
winget install Microsoft.OpenSSH.preview
ssh-keygen -t ecdsa-sk
- Esse comando também gerou um par de arquivos de chave privada/pública e, nas conexões SSH, aceitou reconhecimento facial, impressão digital ou PIN pelo fluxo padrão de login do Windows Hello
- No Linux, não foi possível demonstrar porque não havia acesso a um notebook com secure element após uma verificação semelhante de presença real do usuário
1 comentários
Comentários no Lobste.rs
Excelente artigo, e já é muito útil só por mostrar que isso é possível
Pessoalmente, não consegui achar uma versão de biblioteca adequada para fazer isso funcionar, mas descobri que o 1Password 8 armazena chaves SSH com segurança e o agente suporta desbloquear a chave com biometria
Então agora consigo fazer operações com git e também entrar em hosts SSH só encostando o dedo
Instruções: https://developer.1password.com/docs/ssh/get-started/
Isso parece ser só para Mac
Não tive a chance de testar um sistema Linux com esse tipo de elemento de segurança, e minha workstation Linux tem TPM V1, mas não sei bem como garantir que as operações de assinatura só sejam executadas depois de confirmar a presença real do usuário
Talvez alguém com um notebook Linux como um Framework possa tentar. Talvez até funcione de verdade no Asahi
Então o que exatamente fica dentro do arquivo de chave privada fornecido?
ssh:, ou seja, o aplicativo, corresponde à origin da passkey e é útil para criar resident keys por host ou domínioPor exemplo, isso é usado para separar chaves por finalidade mesmo dentro da mesma Yubikey física
flagsespecifica como o hardware deve tratar a chave [1]. O agente também pode acrescentar suas próprias restriçõesTecnicamente, também dá para armazenar outros blobs ou extensões na chave FIDO, e num emprego anterior eu já usei isso para passar credenciais auxiliares, como uma chave pública X.509, junto com a autenticação. É uma abordagem bem interessante
[1]
O invólucro externo tem o valor mágico
openssh-key-v1\0,cipher=none,kdf=none, então não é texto cifradoO blob da chave pública, com 74 bytes, contém o tipo de chave
sk-ssh-ed25519@openssh.com, o ponto Ed25519 de 32 bytesfdcce889…03e7852be o aplicativossh:, e esse valor separa por namespace as credenciais FIDO de SSH e WebAuthnA seção privada tem 248 bytes e, como
cipher=none, está em texto puro. Ela contém o valor aleatóriocheckint1 == checkint2 == 0x46744267, o tipo de chave e a chave pública repetidos, o aplicativossh:eflags: 0x01Esse flag significa
USER_PRESENCE_REQUIRED, então exige toque, mas não PIN/verificação do usuário, e é uma chave não residenteO
key_handleé um ID opaco de credencial de 128 bytes passado paraauthenticatorGetAssertion, que o dispositivo decodifica internamente para recuperar a seed Ed25519Além disso, há um
reservedvazio, o comentárioahelwer@ah-mbair.locale padding01 02 03Esse texto parece tratar só de SSH, mas existe alguma forma de usar o Secure Enclave ou o TPM do meu computador como chave FIDO2 ou U2F?
Passkeys também são uma forma desse mesmo modelo, usando uma chave privada separada para cada site
Isso faz parecer estranho que o suporte a chaves de API assimétricas ou HMAC que possam ser vinculadas ao hardware não seja mais comum
É bom ver mais especificações empurrando nessa direção, como WebAuthn, DBSC (Device-Bound Session Credentials) e OAuth2 DPOP