Code signing: verificando autenticidade de artefatos
1. Por que Code Signing é Essencial para a Segurança do Desenvolvedor
1.1. O risco de artefatos não assinados: injeção de código malicioso e supply chain attacks
Artefatos de software não assinados são vulneráveis a ataques de supply chain. Um atacante que comprometa um servidor de build ou um repositório de pacotes pode substituir um binário legítimo por uma versão modificada contendo backdoors, ransomware ou stealers. Sem assinatura digital, o consumidor do artefato não tem como garantir que aquele arquivo veio realmente da fonte original.
Exemplos reais incluem o ataque ao SolarWinds Orion (2020), onde agentes maliciosos inseriram código malicioso em builds legítimos, e o comprometimento do repositório npm event-stream (2018), que introduziu malware em pacotes JavaScript amplamente utilizados.
1.2. Diferença entre assinatura digital e checksum (hash simples)
Checksums (SHA-256, MD5) apenas verificam integridade — se o arquivo foi alterado após a geração do hash. Mas eles não provam autoria. Um atacante pode gerar um novo checksum para um artefato malicioso e publicá-lo no mesmo site. Já a assinatura digital combina hashing com criptografia assimétrica, provando que o hash foi gerado por quem possui a chave privada correspondente.
1.3. Code signing como camada de confiança entre build e deploy
A assinatura de código estabelece uma cadeia de confiança: o desenvolvedor assina o artefato no final do pipeline de build; o sistema de deploy ou o cliente final verifica a assinatura antes de executar ou instalar. Isso cria uma barreira contra adulteração em trânsito (man-in-the-middle) ou em repouso (servidores comprometidos).
2. Fundamentos Criptográficos da Assinatura de Código
2.1. Par de chaves assimétricas: chave privada para assinar, chave pública para verificar
O modelo é simples: quem assina usa a chave privada (secreta); quem verifica usa a chave pública (distribuída abertamente). A segurança depende do sigilo absoluto da chave privada.
2.2. Processo: hashing do artefato + criptografia com chave privada
Passo a passo:
1. Gera-se um hash criptográfico do artefato (ex: SHA-256).
2. Criptografa-se esse hash com a chave privada do assinante.
3. O resultado (assinatura) é anexado ou distribuído junto com o artefato.
4. O verificador recalcula o hash, descriptografa a assinatura com a chave pública e compara os dois hashes.
2.3. Algoritmos comuns: RSA, ECDSA, Ed25519 e suas implicações de segurança
- RSA (2048/4096 bits): amplamente suportado, mas chaves longas e operações mais lentas. Seguro quando usado com padding adequado (PSS).
- ECDSA (P-256, P-384): chaves menores, mesma segurança que RSA. Depende de boa geração de nonce.
- Ed25519: moderna, rápida, resistente a side-channels. Recomendada para novos projetos.
3. Fluxo Prático de Code Signing em Projetos de Software
3.1. Geração e proteção da chave privada (HSM, vaults, env vars seguras)
Nunca armazene chaves privadas em repositórios Git ou variáveis de ambiente não criptografadas. Use:
- HSM (Hardware Security Module) para ambientes corporativos.
- HashiCorp Vault ou AWS KMS para armazenamento seguro em nuvem.
- GitHub Secrets / GitLab CI Variables para chaves efêmeras de curta duração.
3.2. Assinatura automatizada no pipeline CI/CD (ex: GitHub Actions, GitLab CI)
Exemplo de pipeline com Cosign (para containers) e GPG (para binários):
# .github/workflows/sign.yml (GitHub Actions)
jobs:
sign:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build binary
run: go build -o myapp .
- name: Sign with Cosign
env:
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
run: |
cosign sign-blob --key env://COSIGN_PRIVATE_KEY myapp > myapp.sig
- name: Upload signed artifact
uses: actions/upload-artifact@v4
with:
name: signed-release
path: |
myapp
myapp.sig
3.3. Distribuição do artefato junto com a assinatura (arquivo .sig ou metadados)
A assinatura pode ser:
- Um arquivo separado (ex: meuapp.sig) publicado lado a lado com o binário.
- Embutida em metadados (assinatura de container no registry, assinatura de pacote Debian/RPM).
- Registrada em um log público (Sigstore Rekor).
4. Verificação de Assinatura: Garantindo Autenticidade na Prática
4.1. Comandos para verificação com ferramentas como gpg, cosign ou openssl
Com GPG (assinatura de arquivo com chave pública):
# Importar chave pública do desenvolvedor
gpg --import developer-public-key.asc
# Verificar assinatura
gpg --verify myapp.sig myapp
# Saída esperada: "gpg: Good signature from ..."
Com Cosign (assinatura de blob):
# Verificar assinatura contra chave pública
cosign verify-blob --key public-key.pem --signature myapp.sig myapp
# Saída esperada: "Verified OK"
Com OpenSSL (assinatura RSA):
# Extrair hash do artefato
sha256sum myapp > hash.txt
# Verificar assinatura
openssl dgst -sha256 -verify public-key.pem -signature myapp.sig myapp
# Saída esperada: "Verified OK"
4.2. Validação da cadeia de certificados (CA pública ou PKI interna)
Para assinaturas com certificados X.509, é necessário validar toda a cadeia até uma raiz confiável:
openssl smime -verify -in signed-file.p7s -inform DER -content myapp \
-CAfile ca-chain.pem -out /dev/null
4.3. Exemplo prático: verificar assinatura de um binário antes da instalação
Script de instalação seguro:
#!/bin/bash
# Instalação verificada com Cosign
BINARY_URL="https://releases.example.com/myapp-linux-amd64"
SIG_URL="${BINARY_URL}.sig"
PUB_KEY="https://releases.example.com/public-key.pem"
curl -fsSL -o myapp "$BINARY_URL"
curl -fsSL -o myapp.sig "$SIG_URL"
curl -fsSL -o pubkey.pem "$PUB_KEY"
cosign verify-blob --key pubkey.pem --signature myapp.sig myapp || {
echo "Falha na verificação! Abortando instalação."
exit 1
}
chmod +x myapp
sudo mv myapp /usr/local/bin/
5. Ferramentas e Padrões Modernos para Code Signing
5.1. Cosign e Sigstore: assinatura transparente com logs públicos (ReKor)
Sigstore é um ecossistema open-source que simplifica code signing:
- Cosign: ferramenta CLI para assinar e verificar blobs e containers.
- Rekor: ledger público imutável que registra assinaturas, permitindo auditoria.
- Fulcio: CA raiz que emite certificados de curta duração baseados em identidade OIDC.
Vantagem: não requer gestão manual de chaves — o desenvolvedor usa sua identidade GitHub/GitLab/Google para assinar.
5.2. Notary e TUF (The Update Framework): atualizações seguras de software
TUF é um framework para proteger sistemas de atualização contra ataques de rollback, key compromise e mirror poisoning. Notary é a implementação de referência da Docker, usada pelo Docker Content Trust.
5.3. Comparação: assinatura de containers (Docker Content Trust) vs binários nativos
| Aspecto | Docker Content Trust (Notary) | Binários nativos (GPG/Cosign) |
|---|---|---|
| Mecanismo | Assinatura de manifest no registry | Assinatura de arquivo individual |
| Verificação | Automática com DOCKER_CONTENT_TRUST=1 |
Manual ou via script |
| Chaves | Delegation hierarchy | Par único ou certificado |
| Ideal para | Imagens de container | Binários, pacotes, scripts |
6. Boas Práticas de Gestão de Chaves para Equipes de Desenvolvimento
6.1. Ciclo de vida da chave: rotação, revogação e expiração
- Rotação: troque as chaves a cada 6-12 meses ou imediatamente após suspeita de comprometimento.
- Revogação: mantenha uma CRL (Certificate Revocation List) ou use OCSP para chaves comprometidas.
- Expiração: configure chaves com data de expiração (ex: GPG
--expires-in 1y).
6.2. Separação de responsabilidades: quem pode assinar vs quem pode verificar
- Apenas um grupo restrito (ex: release managers) deve ter acesso à chave privada.
- Todos os desenvolvedores e sistemas de deploy devem ter a chave pública para verificação.
- Considere usar multi-signature (M-of-N) para releases críticas.
6.3. Armazenamento seguro: evitar chaves hardcoded ou em repositórios git
- Use gerenciadores de segredos (Vault, AWS Secrets Manager).
- Para GPG, use
gpg --export-secret-keyse armazene em cofre criptografado. - Para Cosign, use
cosign import-key-paire armazene em variáveis de ambiente seguras do CI.
7. Integração com SBOM e Reproducible Builds
7.1. Assinar o SBOM junto com o artefato para auditabilidade completa
Um SBOM (Software Bill of Materials) lista todas as dependências. Assiná-lo garante que a lista de componentes não foi adulterada:
# Gerar SBOM com syft
syft myapp -o cyclonedx-json > sbom.json
# Assinar SBOM junto com o artefato
cosign sign-blob --key cosign.key sbom.json > sbom.json.sig
7.2. Como reproducible builds facilitam a verificação de assinatura
Builds reproduzíveis permitem que qualquer pessoa reconstrua o binário a partir do código-fonte e compare o hash. Quando combinado com code signing, a verificação é dupla: (1) a assinatura prova autenticidade, (2) o build reproduzível prova que o código-fonte corresponde ao binário.
7.3. Exemplo: pipeline que gera build reproduzível, assina e publica SBOM assinado
# Etapas do pipeline (Makefile)
build:
go build -trimpath -ldflags="-buildid=" -o myapp .
sha256sum myapp > myapp.sha256
sbom:
syft myapp -o cyclonedx-json > sbom.json
sign:
cosign sign-blob --key cosign.key myapp > myapp.sig
cosign sign-blob --key cosign.key sbom.json > sbom.json.sig
publish:
gh release upload v1.0.0 myapp myapp.sig myapp.sha256 sbom.json sbom.json.sig
8. Cenários de Falha e Mitigações
8.1. Chave privada comprometida: plano de resposta e rotação emergencial
- Imediato: revogar a chave (CRL/OCSP) e gerar novo par.
- Investigação: auditar logs de assinatura para identificar artefatos assinados com a chave comprometida.
- Notificação: comunicar usuários para rejeitarem assinaturas antigas.
- Reassinatura: assinar novamente todos os artefatos legítimos com a nova chave.
8.2. Assinatura expirada ou revogada: como o cliente deve reagir
O verificador deve:
- Rejeitar artefatos com assinatura expirada (a menos que haja timestamp válido).
- Verificar CRL ou OCSP antes de aceitar a assinatura.
- Exibir erro claro: "Assinatura expirada desde 2024-01-15. Baixe a versão mais recente."
8.3. Ataques de replay e timestamping (TSA) para evitar reutilização de assinaturas
Um atacante pode capturar uma assinatura válida antiga e anexá-la a um artefato novo e malicioso. Para mitigar:
- Use Timestamp Authority (TSA): um servidor confiável que carimba a assinatura com data/hora.
- O verificador deve rejeitar assinaturas cujo timestamp seja anterior à última versão conhecida do artefato.
- Ferramentas como openssl ts e Sigstore já incluem timestamping automático.
Referências
- Sigstore Documentation — Documentação oficial do ecossistema Sigstore, incluindo Cosign, Rekor e Fulcio.
- OpenPGP (GnuPG) - The GNU Privacy Guard — Guia completo de uso do GPG para assinatura e verificação de arquivos.
- The Update Framework (TUF) Specification — Especificação oficial do TUF para atualizações seguras de software.
- Docker Content Trust — Documentação oficial da Docker sobre assinatura de imagens de container.
- NIST SP 800-57: Recommendation for Key Management — Diretrizes do NIST para gerenciamento seguro de chaves criptográficas.
- Reproducible Builds - Why does it matter? — Definição e benefícios de builds reproduzíveis para segurança de software.
- CycloneDX SBOM Standard — Especificação do formato CycloneDX para SBOM, incluindo suporte a assinatura digital.