Atualizações automáticas e por que você precisa de mais uma camada de assinatura
Na Parte 1, concluímos a configuração do certificado Developer ID e da notarização. Com isso, você está pronto para entregar o app aos usuários pela primeira vez. Mas um app não termina após um único lançamento — você precisa continuar enviando novas versões que corrigem bugs e adicionam funcionalidades.
Para um app da Mac App Store, a App Store trata as atualizações para você. Um app distribuído diretamente não tem esse benefício, então você precisa incluir um recurso de atualização automática no próprio app. No macOS, o padrão de fato para esse papel é o framework de código aberto Sparkle. Com o Sparkle configurado, o app verifica periodicamente um “feed de atualizações (appcast)” e, se existir uma nova versão, notifica o usuário, faz o download e a instala.
Isso levanta uma questão. Você já assina o app com o certificado Developer ID criado na Parte 1, então por que você precisa de mais uma chave?
A razão é que as duas assinaturas verificam coisas diferentes.
- Certificado Developer ID — usado pelo Gatekeeper do macOS para decidir “é seguro instalar este app?”
- Chave EdDSA do Sparkle — usada pelo Sparkle dentro do app para decidir “o arquivo de atualização que acabei de baixar foi realmente feito pelo desenvolvedor deste app?”
As atualizações automáticas são uma operação sensível à segurança: o app baixa um arquivo da internet e se sobrescreve. Se alguém interceptar o servidor de atualizações ou o caminho de comunicação e inserir um arquivo falso, isso se torna um problema sério. Para evitar isso, o Sparkle só aceita atualizações assinadas com uma chave privada que apenas o desenvolvedor possui e se recusa a instalar qualquer coisa cuja assinatura não corresponda. É efetivamente uma camada de verificação separada do certificado.
Neste artigo, criaremos o par de chaves EdDSA (Ed25519) que será usado para essa verificação.
Como na Parte 1, todos os nomes e caminhos (
FocusTimer,example.com, etc.) são valores de exemplo. Na prática, substitua-os pelas informações do seu próprio app.
Pré-requisito — o Sparkle já deve estar adicionado ao app
Antes de criar a chave, o framework Sparkle já deve estar adicionado como dependência do seu projeto de app. Se ainda não estiver, adicione-o no Xcode via Swift Package Manager (SPM).
- Abra seu projeto no Xcode → File → Add Package Dependencies…
- Insira o endereço do repositório na caixa de busca:
https://github.com/sparkle-project/Sparkle - Defina a regra de versão como 2.x (o major mais recente) e adicione
Depois disso, compile o projeto uma vez para que o SPM baixe o pacote Sparkle. As ferramentas de linha de comando que vêm empacotadas com ele são a chave para o próximo passo.
Passo 1 — Localizar as ferramentas de linha de comando do Sparkle
O pacote Sparkle inclui ferramentas de linha de comando usadas para geração de chaves e assinatura. Essas ferramentas ficam dentro da pasta onde o SPM baixou o pacote, mas esse local varia dependendo da versão do Xcode e das configurações do DerivedData. Por isso, é mais seguro encontrá-lo diretamente.
SPARKLE_BIN=$(find ~/Library/Developer/Xcode/DerivedData \
-path "*/artifacts/sparkle/Sparkle/bin" -type d 2>/dev/null | head -1)
echo "$SPARKLE_BIN"
Se um único caminho for impresso, funcionou. Se nada aparecer, você pulou o passo “compilar o projeto uma vez” acima — execute um build no Xcode e tente novamente.
Dentro desta pasta estão as seguintes ferramentas.
generate_keys— gera, faz backup, restaura e verifica a chave de assinatura (usada neste artigo)sign_update— assina arquivos de atualização (usado durante lançamentos reais)generate_appcast— gera o feed de atualizações (appcast.xml) (aparece na Parte 3)
Passo 2 — Gerar a chave de assinatura
Agora crie o par de chaves.
"$SPARKLE_BIN/generate_keys"
Uma saída semelhante à seguinte aparece.
Generating a new signing key...
A key has been generated and saved in your keychain. Add the SUPublicEDKey
key to the Info.plist of each app for which you intend to use Sparkle...
<key>SUPublicEDKey</key>
<string>5vT3kQbA9mZ0wR1yX8cD2eF4gH6jK7lN0pS2uV5xW8c=</string>
Este único comando cria duas chaves.
- Chave pública (public key) — o valor
SUPublicEDKeymostrado na saída acima. Não é um segredo e é a chave que você vai incorporar no app. - Chave privada (private key) — não aparece na tela. É armazenada automaticamente no Keychain do macOS como um item chamado “Private key for signing Sparkle updates”. É um segredo verdadeiro que nunca deve ser deixado em disco como um arquivo de texto simples.
Incorporando a chave pública no app
Coloque a string da chave pública da saída no Info.plist do app. Para o app de exemplo, adicione as seguintes chaves ao FocusTimer-Info.plist.
<key>SUFeedURL</key>
<string>https://updates.example.com/appcast.xml</string>
<key>SUPublicEDKey</key>
<string>5vT3kQbA9mZ0wR1yX8cD2eF4gH6jK7lN0pS2uV5xW8c=</string>
SUPublicEDKey— a chave pública que você acabou de gerar. O app usa essa chave para verificar a assinatura das atualizações baixadas.SUFeedURL— o endereço do feed de atualizações. Este domínio ainda não existe; o criamos na Parte 3. Por enquanto, é apenas um placeholder.
Como a chave pública está incorporada no app e apenas o desenvolvedor possui a chave privada, o app só aceitará atualizações assinadas com a chave privada. Esta é a estrutura central da verificação de atualizações do Sparkle.
Passo 3 — Fazer backup da chave privada (obrigatório!)
Se você pular este passo, poderá se arrepender profundamente mais tarde.
A chave privada está armazenada no Keychain, então está tudo bem no computador que você está usando agora. Mas se você perder o computador, o disco falhar ou reinstalar o macOS, essa chave desaparece junto.
O que acontece se a chave privada for perdida? As atualizações assinadas com uma nova chave serão rejeitadas pelos apps dos usuários existentes (apps com a chave pública antiga incorporada). Em outras palavras, você nunca mais poderá enviar atualizações automáticas para os usuários que já estão executando seu app. Sua única opção seria dizer a cada usuário individualmente: “por favor, baixe a nova versão você mesmo e reinstale-a.”
Portanto, faça o backup da chave logo após criá-la.
"$SPARKLE_BIN/generate_keys" -x ~/focustimer-sparkle-private.key
cat ~/focustimer-sparkle-private.key
A string base64 de uma única linha impressa pelo cat é a chave privada. Exemplo:
Hn4Kp9Lr2Qs5Tv8Wx1Yz3Ab6Cd0Ef7Gh4Ij5Kl8MnQ0=
Armazene essa string como uma nota segura (secure note) em um gerenciador de senhas como o 1Password. Dê à nota um nome fácil de encontrar mais tarde, como FocusTimer Sparkle EdDSA Private Key.
Logo após confirmar o salvamento, exclua o arquivo de texto simples deixado no disco.
rm ~/focustimer-sparkle-private.key
A regra é nunca deixar a chave privada no disco como um arquivo de texto simples. Mantenha o backup apenas dentro de um gerenciador de senhas criptografado.
Uma armadilha importante — o símbolo % não faz parte da chave
Quando você imprime a chave com cat, o terminal (especialmente o zsh) pode acrescentar um símbolo % ao final da linha.
Hn4Kp9Lr2Qs5Tv8Wx1Yz3Ab6Cd0Ef7Gh4Ij5Kl8MnQ0=%
Esse % é apenas o indicador do shell de que “a saída terminou sem uma nova linha” — não faz parte da chave. Se você copiar esse % para o seu backup, a chave ficará corrompida quando você restaurá-la mais tarde. Uma string base64 geralmente termina com =, então exclua o % após o = ao salvar.
Passo 4 — Restaurando em outro computador
Quando você precisar compilar o app em um novo computador, coloque a chave privada que você fez backup de volta no Keychain.
echo "sua_string_base64_de_backup" > ~/focustimer-sparkle-private.key
"$SPARKLE_BIN/generate_keys" -f ~/focustimer-sparkle-private.key
rm ~/focustimer-sparkle-private.key
A opção -f significa “importar a chave no arquivo para o Keychain”. Depois que a restauração for concluída, exclua o arquivo de texto simples aqui também, imediatamente.
Passo 5 — Verificação
Verifique se a chave foi instalada corretamente.
"$SPARKLE_BIN/generate_keys" -p
Uma única linha contendo a chave pública é impressa. Esse valor deve corresponder exatamente ao valor que você colocou em SUPublicEDKey no Info.plist no Passo 2. Se forem diferentes, a chave pública incorporada no app e a chave de assinatura real estão fora de sincronia, e a verificação de atualização falhará.
Resumo da Parte 2
Se você acompanhou até aqui, agora tem o seguinte.
- ✅ Um par de chaves EdDSA do Sparkle gerado (chave pública + chave privada)
- ✅ A chave pública incorporada no
Info.plistdo app (SUPublicEDKey) - ✅ A chave privada com backup seguro em um gerenciador de senhas
- ✅ Familiaridade com como restaurá-la em outro computador
O app agora tem uma forma de verificar “se uma atualização baixada é genuína”. Mas uma coisa ainda está faltando. Na Parte 2, você escreveu https://updates.example.com/appcast.xml para SUFeedURL, mas ainda não há nada nesse endereço.
Na próxima parte, criaremos o repositório público onde o feed de atualizações (appcast.xml) e os arquivos .dmg ficarão, o conectaremos a um domínio que controlamos e finalizaremos as configurações de build — concluindo a configuração inicial.