Pular para o conteúdo
ecdsa.com
← Todos os artigos

Certificados SSH vs authorized_keys: CAs e principals

Com o authorized_keys, escalar significa copiar chaves para cada servidor e nunca esquecer de removê-las. Os certificados OpenSSH trocam isso por uma única CA confiável, chaves assinadas de curta duração e nomes em vez de impressões digitais. Aqui está a configuração completa, testada no OpenSSH 10.0, com todas as falhas que ela produz.

Resposta curta. Com o authorized_keys, cada servidor lista todas as chaves públicas autorizadas a fazer login, e o acesso dura até alguém apagar a linha. Com certificados OpenSSH, os servidores confiam em uma única CA de usuário (TrustedUserCAKeys). Cada usuário tem a própria chave assinada com ssh-keygen -s, que acrescenta uma identidade (-I), as contas em que ela pode fazer login (principals, -n) e uma janela de validade (-V). Os clientes confiam em uma única CA de host (@cert-authority no known_hosts) em vez de aceitar a impressão digital de cada host no primeiro uso. Certificados de curta duração tornam automática a remoção do acesso de quem sai, e as listas de revogação de chaves (RevokedKeys) cuidam do resto. O preço é que agora você opera uma CA e precisa proteger a chave dela.

O que o authorized_keys não faz

O ~/.ssh/authorized_keys é simples e não tem nenhuma peça extra para manter, e é por isso que está em toda parte. Os limites dele aparecem à medida que aumenta o número de pessoas e de servidores:

  • O acesso nunca expira. Uma chave adicionada para um prestador de serviço em 2023 ainda funciona em 2026, em todos os servidores para os quais foi copiada, a menos que alguém a remova. (Existe expiry-time= por linha, mas alguém precisa definir esse valor.)
  • Sem identidade. A linha é uma chave e um comentário. A qual pessoa, equipe ou job ela pertence fica registrado numa planilha, se é que fica registrado em algum lugar.
  • A distribuição é problema seu. Adicionar ou remover alguém significa mexer em cada servidor, com gerenciamento de configuração ou à mão.
  • As chaves de host dependem da confiança no primeiro uso. Cada cliente aceita a impressão digital de cada servidor na primeira vez, e reconstruir um servidor dispara REMOTE HOST IDENTIFICATION HAS CHANGED em toda parte.

Os certificados OpenSSH, disponíveis desde o OpenSSH 5.4 (2010), resolvem os quatro problemas. Eles não são X.509: o formato é próprio do OpenSSH, uma chave pública com identidade e restrições, assinada por uma chave de CA que é, ela mesma, uma chave SSH comum. Em 2025, a descrição do formato saiu do arquivo PROTOCOL.certkeys do código-fonte do OpenSSH e passou para um Internet-Draft, hoje o draft-ietf-sshm-cert no grupo de trabalho SSHM da IETF.

Duas CAs, duas direções

Você precisa de duas chaves de assinatura separadas, porque elas respondem a perguntas diferentes:

  • A CA de usuário responde à pergunta “esta chave pode fazer login nesta conta?”, e os servidores confiam nela.
  • A CA de host responde à pergunta “este é mesmo o web01?”, e os clientes confiam nela.
Criar as CAs (OpenSSH 10.0p2)
$ ssh-keygen -q -t ed25519 -f user_ca -C "Example user CA" -N ""
$ ssh-keygen -q -t ed25519 -f host_ca -C "Example host CA" -N ""

O -N "" mantém o laboratório simples. Uma chave de CA real deve ficar em um HSM, em um token de hardware ou em um serviço de assinatura (o SSH secrets engine do HashiCorp Vault, o smallstep, o Teleport e os equivalentes dos provedores de nuvem fazem isso). Quem tem a chave da CA de usuário consegue fazer login como qualquer usuário que os servidores permitam.

Certificados de usuário

Assine a chave pública que a Alice já tem, com validade de oito horas, para as contas alice e deploy:

ssh-keygen -s: emitir um certificado de usuário
$ ssh-keygen -s user_ca -I alice@example.test -n alice,deploy -V -5m:+8h -z 1001 id_ed25519.pub
Signed user key /home/alice-laptop/id_ed25519-cert.pub: id "alice@example.test" serial 1001 for alice,deploy valid from 2026-10-20T13:14:08 to 2026-10-20T21:19:08
  • -I é o ID da chave. O sshd registra esse valor em cada login, então faça com que ele identifique uma pessoa ou um job, não um nome de host.
  • -n lista os principals, os nomes que este certificado pode assumir. O manual do ssh-keygen diz que certificados sem principals são “válidos para todos os usuários ou hosts”, mas o sshd 10.0 recusa um certificado de usuário assim quando a CA está em TrustedUserCAKeys (Certificate lacks principal list). Sempre defina -n.
  • -V -5m:+8h define a validade de cinco minutos atrás (por causa da dessincronização de relógios) até oito horas a partir de agora.
  • -z define um número de série, que a revogação pode usar como referência.

O ssh-keygen -L mostra o que foi assinado:

ssh-keygen -L -f id_ed25519-cert.pub
/home/alice-laptop/id_ed25519-cert.pub:
        Type: ssh-ed25519-cert-v01@openssh.com user certificate
        Public key: ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s
        Signing CA: ED25519 SHA256:aG9VR0NrG4he7YusGsggg/Gt3WviRbg3Anbfz4HJe60 (using ssh-ed25519)
        Key ID: "alice@example.test"
        Serial: 1001
        Valid: from 2026-10-20T13:14:08 to 2026-10-20T21:19:08
        Principals: 
                alice
                deploy
        Critical Options: (none)
        Extensions: 
                permit-X11-forwarding
                permit-agent-forwarding
                permit-port-forwarding
                permit-pty
                permit-user-rc

Por padrão, um certificado de usuário concede as cinco extensões. -O clear remove todas, e depois você pode adicionar de volta só o que precisa, por exemplo -O clear -O permit-pty. As opções críticas restringem o certificado. force-command substitui qualquer comando que o cliente peça, e source-address limita de onde o certificado pode ser usado. Com source-address=10.0.0.0/8, um login a partir de 127.0.0.1 é recusado. Com uma origem permitida, o comando forçado roda independentemente do que o cliente pediu:

Opções críticas na prática (linhas de log do sshd com LogLevel VERBOSE)
        Critical Options: 
                force-command echo backup only
                source-address 10.0.0.0/8
$ ssh deploy@web01.example.test
deploy@web01.example.test: Permission denied (publickey).
cert: Authentication tried for deploy with valid certificate but not from a permitted source address (127.0.0.1).
Refused by certificate options
...
$ ssh deploy@web01.example.test "rm -rf /tmp/anything"
backup only
Starting session: forced-command (key-option) 'echo backup only' for deploy from 127.0.0.1 port 48976 id 0

O cliente ssh encontra o certificado automaticamente quando ele fica ao lado da chave privada como id_ed25519-cert.pub. Caso contrário, aponte para ele com CertificateFile. O ssh-add carrega os dois no agente.

Configuração do sshd

Todo o lado do servidor cabe em poucas linhas. Este arquivo drop-in passou no sshd -t e foi usado em todos os testes desta página:

/etc/ssh/sshd_config.d/ca.conf
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
TrustedUserCAKeys /etc/ssh/user_ca.pub
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
RevokedKeys /etc/ssh/revoked_keys
PasswordAuthentication no
LogLevel VERBOSE
  • TrustedUserCAKeys contém uma ou mais chaves públicas de CA. Durante uma rotação de CA, liste as duas.
  • AuthorizedPrincipalsFile associa contas a principals. Aqui, /etc/ssh/auth_principals/deploy contém uma única linha, deploy, então só certificados que listam o principal deploy podem fazer login como deploy. Sem essa opção, o sshd exige o próprio nome da conta entre os principals. O mapeamento é mais flexível: um principal como oncall pode ser permitido em várias contas, e o AuthorizedPrincipalsCommand pode consultá-lo em um serviço de diretório.
  • RevokedKeys aponta para uma lista de revogação (veja abaixo).
  • LogLevel VERBOSE registra o ID da chave e o número de série do certificado em cada login. É a trilha de auditoria que o authorized_keys nunca teve.

Um login com o certificado da Alice, visto dos dois lados:

ssh -v (cliente) e log do sshd (servidor), com cortes
Server host certificate: ssh-ed25519-cert-v01@openssh.com SHA256:IuzvI4QsLmSx1+6p3yrvlRx2wdl+LUCY1zXqSzssLAc, serial 0 ID "web01" CA ssh-ed25519 SHA256:+iie/YHPulJReHN2GQ8UvrorBVrtayD2T1MGxeg/QCQ valid from 2026-10-20T13:14:08 to 2027-10-19T13:19:08
Host 'web01.example.test' is known and matches the ED25519-CERT host certificate.
Offering public key: /home/alice-laptop/id_ed25519 ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s explicit
Server accepts key: /home/alice-laptop/id_ed25519 ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s explicit
Authenticated to web01.example.test ([127.0.0.1]:22) using "publickey".

Accepted publickey for deploy from 127.0.0.1 port 57432 ssh2: ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s ID alice@example.test (serial 1001) CA ED25519 SHA256:aG9VR0NrG4he7YusGsggg/Gt3WviRbg3Anbfz4HJe60

Se você não puder editar o sshd_config, uma única conta pode confiar em uma CA pelo próprio authorized_keys. A opção principals= funciona como o arquivo de principals:

~deploy/.ssh/authorized_keys: confiar em uma CA para uma única conta (chave encurtada)
cert-authority,principals="deploy-team" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK63...
$ ssh deploy@web01.example.test 'echo logged in as $(whoami)'
logged in as deploy

Certificados de host

Assine a chave de host de cada servidor com a CA de host. -h faz do resultado um certificado de host, e os principals são os nomes que os clientes usam para se conectar:

Assinar uma chave de host
$ ssh-keygen -s host_ca -I web01 -h -n web01.example.test,web01 -V -5m:+52w /etc/ssh/ssh_host_ed25519_key.pub
Signed host key /etc/ssh/ssh_host_ed25519_key-cert.pub: id "web01" serial 0 for web01.example.test,web01 valid from 2026-10-20T13:14:08 to 2027-10-19T13:19:08

Depois, aponte HostCertificate para o resultado (como no arquivo do sshd acima) e dê aos clientes uma única linha no known_hosts (ou em /etc/ssh/ssh_known_hosts) em vez de uma impressão digital por host:

known_hosts: confiar na CA de host para um domínio
@cert-authority *.example.test ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBtSsqoqxwcl31DTJg3kTPsv9eLuuw5gU1BcvYybODlu Example host CA

A partir daí, servidores novos e reconstruídos são aceitos já na primeira conexão, sem pedir confirmação, desde que o certificado deles liste o nome que o cliente usou. Essa última condição é a surpresa mais comum. Conectar-se com um nome que não é um principal faz o cliente recorrer às chaves de host comuns e, com a verificação estrita, ele para:

Conectar-se como db01.example.test a um host certificado apenas como web01
$ ssh deploy@db01.example.test
Certificate invalid: name is not a listed principal
No ED25519 host key is known for db01.example.test and you have requested strict checking.
Host key verification failed.

Liste todos os nomes que os clientes usam, inclusive nomes curtos, CNAMEs, endereços IP (se as pessoas se conectam pelo IP) e nomes de balanceadores de carga. O ssh-keyscan -c obtém o certificado de um servidor para inspeção, e linhas @revoked no known_hosts rejeitam uma chave de host ou uma CA específica.

Como as falhas aparecem

Para qualquer problema com certificados de usuário, o cliente só diz Permission denied (publickey). — o motivo está no log do servidor, com LogLevel VERBOSE:

SituaçãoLog do sshd (OpenSSH 10.0p2)
Conta fora dos principals do certificado (após o mapeamento)Certificate does not contain an authorized principal
Certificado emitido sem -n, sem AuthorizedPrincipalsFileCertificate lacks principal list
Certificado depois do fim da janela -VCertificate invalid: expired
Número de série ou chave na KRLAuthentication key ED25519-CERT SHA256:... revoked by file /etc/ssh/revoked_keys
source-address não correspondecert: Authentication tried for deploy with valid certificate but not from a permitted source address (127.0.0.1).

Duas coisas que parecem problemas de certificado, mas não são:

  • Relógios dessincronizados. A validade é conferida pelo relógio do servidor. Emita certificados válidos a partir de alguns minutos no passado (-V -5m:...) e mantenha os servidores sincronizados por NTP.
  • Penalidades por falhas repetidas. Desde o OpenSSH 9.8, o sshd penaliza endereços que falham repetidamente na autenticação (PerSourcePenalties, ativado por padrão). Nos testes de certificados a partir de uma única máquina, uma tentativa malsucedida fez a conexão seguinte ser derrubada antes da autenticação:
sshd 10.0p2 depois de um login com certificado que falhou
srclimit_penalise: 127.0.0.1/32: activating ipv4 penalty of 19 seconds for penalty: failed authentication
$ ssh deploy@web01.example.test
Connection closed by 127.0.0.1 port 22
drop connection #0 from [127.0.0.1]:55080 on [127.0.0.1]:22 penalty: failed authentication

Se o ssh informar Connection closed logo depois de uma série de falhas, espere a penalidade acabar antes de mudar a configuração de novo.

Revogação com KRLs

A validade curta é o principal mecanismo de revogação: um certificado de oito horas deixa de valer sozinho. Para o resto, o OpenSSH tem as listas de revogação de chaves (Key Revocation Lists, KRLs, desde o OpenSSH 6.2), um formato binário compacto que revoga por número de série, ID da chave ou chave. Para revogar o certificado da Alice com número de série 1001:

ssh-keygen -k: criar uma KRL e depois conferir um certificado nela
$ printf 'serial: 1001\n' > /tmp/krl-spec
$ ssh-keygen -k -f /etc/ssh/revoked_keys -s user_ca.pub /tmp/krl-spec
Revoking from /tmp/krl-spec
$ ssh-keygen -Q -f /etc/ssh/revoked_keys id_ed25519-cert.pub
/home/alice-laptop/id_ed25519-cert.pub (alice@laptop): REVOKED

O sshd lê o RevokedKeys a cada autenticação, então o próximo login já falha, sem reiniciar nada. Acrescente -u para atualizar uma KRL existente em vez de substituí-la. Distribua a KRL com as mesmas ferramentas usadas para a chave da CA. Um servidor com uma KRL desatualizada aceita o que ele não sabe que foi revogado. Esse é mais um motivo para manter os certificados com vida curta e tratar a KRL como uma rede de segurança para as horas restantes.

Lado a lado

authorized_keysCertificados
Conceder acessoCopiar uma chave para cada servidorAssinar uma chave uma vez
Remover acessoApagá-la em cada servidorDeixar expirar ou incluí-la na KRL
ExpiraçãoOpcional por linha (expiry-time=)Todo certificado tem uma janela de validade
Identidade nos logsImpressão digital da chaveID da chave e número de série
Restriçõesfrom=, command=, restrict por linhasource-address, force-command, extensões por certificado
Confiança no hostImpressão digital por host (TOFU)Uma linha @cert-authority
Segredo críticoNenhum centralA chave privada da CA
Peças a manterNenhumaEmissão, proteção da CA, distribuição da KRL

As restrições por linha do authorized_keys funcionam de verdade e foram testadas no mesmo servidor:

Opções do authorized_keys, OpenSSH 10.0p2 (chaves encurtadas)
restrict,from="10.0.0.0/8",command="echo backup only" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
$ ssh deploy@web01.example.test id
deploy@web01.example.test: Permission denied (publickey).
/home/deploy/.ssh/authorized_keys:1: Authentication tried for deploy with correct key but not from a permitted host (host=127.0.0.1, ip=127.0.0.1, required=10.0.0.0/8).

expiry-time="20261001" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
$ ssh deploy@web01.example.test id
deploy@web01.example.test: Permission denied (publickey).

Para um punhado de servidores e pessoas, o authorized_keys com restrict e expiry-time resolve. Os certificados compensam quando entradas e saídas de pessoas e reconstruções de hosts acontecem toda semana, ou quando você precisa saber quem fez login, e não só qual chave.

Como migrar do authorized_keys

Não é preciso fazer tudo num único dia de virada. O sshd verifica o authorized_keys e as CAs confiáveis lado a lado, então os dois tipos de login funcionam durante a transição:

  1. Implante primeiro a confiança na CA. Distribua TrustedUserCAKeys, os arquivos de principals e uma KRL vazia para todos os servidores, e não mexa no authorized_keys.
  2. Emita certificados para uma primeira equipe e deixe que ela os use por algumas semanas. Agora cada login aparece no log com ID ... (serial ...).
  3. Encontre os retardatários. Um login pelo authorized_keys é registrado sem ID de certificado, por exemplo Accepted publickey for deploy from 127.0.0.1 port 58770 ssh2: ED25519 SHA256:tT5X32Wds5InYaLSh7mV+QRqAtxA4KXwlKV3hkf0YkI. Em geral, são automações: runners de CI, jobs de backup, monitoramento. Dê a cada uma delas um certificado próprio, de curta duração ou com restrições bem estreitas.
  4. Desative os arquivos de chaves com AuthorizedKeysFile none quando nada legítimo depender mais deles. Mantenha um caminho de acesso de emergência (break-glass), como acesso pelo console ou uma CA de emergência separada e mantida offline, para o dia em que o serviço de assinatura estiver fora do ar.
  5. Cuide do lado dos hosts separadamente. Assine as chaves de host e adicione a linha @cert-authority ao /etc/ssh/ssh_known_hosts global dos clientes. As entradas antigas por host no known_hosts podem ficar: elas continuam funcionando até as chaves de host mudarem.

Operação no dia a dia

  • Emita certificados a partir do provedor de identidade. Um serviço de assinatura confere o login SSO do usuário (OIDC) e devolve um certificado com os principals dele, válido por algumas horas. Ninguém manipula a chave da CA, e o acesso SSH de quem sai da empresa termina no máximo no dia seguinte, sem que seja preciso mexer em nenhum servidor.
  • Se puder, mantenha a chave do usuário em hardware. O ssh-keygen -t ed25519-sk cria uma chave baseada em FIDO2. Os certificados funcionam com ela como com qualquer outra chave.
  • Faça a rotação da CA com sobreposição. Adicione a nova CA de usuário como segunda linha em TrustedUserCAKeys, ou a nova CA de host como segunda linha @cert-authority. Emita pela nova CA e depois remova a antiga.
  • Acompanhe também o lado dos hosts. Assine de novo as chaves de host antes que os certificados delas expirem. Um certificado de host expirado faz os clientes recorrerem às chaves de host comuns, que eles geralmente não têm:
ssh para um host cujo certificado expirou ontem
Certificate invalid: expired
No ED25519 host key is known for web01.example.test and you have requested strict checking.
Host key verification failed.

Para inspecionar um arquivo de chave ou de certificado sem executar nada, o inspetor de chaves SSH decodifica chaves OpenSSH e mostra o tipo, o tamanho e a impressão digital de cada uma. A auditoria de servidor SSH verifica o que um servidor oferece, inclusive os algoritmos de chave de host e de certificado e a troca de chaves pós-quântica.

Fontes

  • Páginas de manual do OpenSSH: ssh-keygen(1) (seções CERTIFICATES e KEY REVOCATION LISTS), sshd_config(5) (TrustedUserCAKeys, AuthorizedPrincipalsFile, RevokedKeys, HostCertificate, PerSourcePenalties), sshd(8) (formatos do authorized_keys e do known_hosts).
  • draft-ietf-sshm-cert: SSH Certificate Format (sucessor do PROTOCOL.certkeys do OpenSSH). PROTOCOL.krl: o formato da KRL.
  • Notas de versão do OpenSSH: 5.4 (certificados), 6.2 (KRLs), 9.8 (PerSourcePenalties).

Toda a saída desta página vem do OpenSSH 10.0p2 (pacotes do Debian 13) em debian:trixie-20261005-slim, em 20 de outubro de 2026, com chaves de teste geradas no contêiner.