Assinar builds para lançamento

As imagens do SO Android usam assinaturas criptográficas em dois lugares:

  1. Cada arquivo .apk dentro da imagem precisa ser assinado. O gerenciador de pacotes do Android usa uma assinatura .apk de duas maneiras:
    • Quando um aplicativo é substituído, ele precisa ser assinado com a mesma chave do aplicativo antigo para ter acesso aos dados dele. Isso é válido tanto para atualizar apps do usuário substituindo o .apk quanto para substituir um app do sistema por uma versão mais recente instalada em /data.
    • Se dois ou mais aplicativos quiserem compartilhar um ID de usuário (para compartilhar dados etc.), eles precisam ser assinados com a mesma chave.
  2. Os pacotes de atualização OTA precisam ser assinados com uma das chaves esperadas pelo sistema. Caso contrário, o processo de instalação vai rejeitá-los.

Chaves de lançamento

A árvore do Android inclui test-keys em build/target/product/security. A criação de uma imagem do SO Android usando make assina todos os arquivos .apk usando as chaves de teste. Como as chaves de teste são conhecidas publicamente, qualquer pessoa pode assinar os próprios arquivos .apk com as mesmas chaves, o que pode permitir que elas substituam ou sequestrem apps do sistema integrados à imagem do SO. Por isso, é fundamental assinar qualquer imagem do SO Android lançada ou implantada publicamente com um conjunto especial de chaves de lançamento a que só você tem acesso.

Para gerar seu próprio conjunto exclusivo de chaves de lançamento, execute estes comandos na raiz da sua árvore do Android:

subject='/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
mkdir ~/.android-certs
for x in releasekey platform shared media networkstack; do \
    ./development/tools/make_key ~/.android-certs/$x "$subject"; \
  done

$subject precisa ser alterado para refletir as informações da sua organização. Você pode usar qualquer diretório, mas escolha um local com backup e seguro. Alguns fornecedores criptografam a chave privada com uma senha longa forte e armazenam a chave criptografada no controle de origem. Outros armazenam as chaves de lançamento em outro lugar, como em um computador isolado.

Para gerar uma imagem de lançamento, use:

make dist
sign_target_files_apks \
-o \    # explained in the next section
--default_key_mappings ~/.android-certs out/dist/*-target_files-*.zip \
signed-target_files.zip

O script sign_target_files_apks usa um .zip de arquivos de destino como entrada e produz um novo .zip de arquivos de destino em que todos os arquivos .apk foram assinados com novas chaves. As imagens recém- assinadas podem ser encontradas em IMAGES/ no signed-target_files.zip.

Assinar pacotes OTA

Um arquivo zip de destino assinado pode ser convertido em um arquivo zip de atualização OTA assinado usando o seguinte procedimento:
ota_from_target_files \
-k  (--package_key) 
signed-target_files.zip \
signed-ota_update.zip

Assinaturas e sideload

O sideload não ignora o mecanismo normal de verificação de assinatura de pacote da recuperação. Antes de instalar um pacote, a recuperação verifica se ele está assinado com uma das chaves privadas correspondentes às chaves públicas armazenadas na partição de recuperação, assim como faria com um pacote entregue por transmissão sem fio.

Os pacotes de atualização recebidos do sistema principal geralmente são verificados duas vezes: uma pelo sistema principal, usando o método RecoverySystem.verifyPackage() na API do Android, e outra pela recuperação. A API RecoverySystem verifica a assinatura em relação às chaves públicas armazenadas no sistema principal, no arquivo /system/etc/security/otacerts.zip (por padrão). A recuperação verifica a assinatura em relação às chaves públicas armazenadas no disco RAM da partição de recuperação, no arquivo /res/keys.

Por padrão, os .zip target-files produzidos pelo build definem o certificado OTA para corresponder à chave de teste. Em uma imagem lançada, um certificado diferente precisa ser usado para que os dispositivos possam verificar a autenticidade do pacote de atualização. A transmissão da flag -o para sign_target_files_apks, conforme mostrado na seção anterior, substitui o certificado de chave de teste pelo certificado de chave de lançamento do diretório certs.

Normalmente, a imagem do sistema e a imagem de recuperação armazenam o mesmo conjunto de chaves públicas de OTA. Ao adicionar uma chave apenas ao conjunto de chaves de recuperação, é possível assinar pacotes que podem ser instalados apenas por sideload (supondo que o mecanismo de download de atualização do sistema principal esteja fazendo a verificação correta em relação a otacerts.zip). É possível especificar chaves extras para serem incluídas apenas na recuperação definindo a variável PRODUCT_EXTRA_RECOVERY_KEYS na definição do produto:

vendor/yoyodyne/tardis/products/tardis.mk
 [...]

PRODUCT_EXTRA_RECOVERY_KEYS := vendor/yoyodyne/security/tardis/sideload

Isso inclui a chave pública vendor/yoyodyne/security/tardis/sideload.x509.pem no arquivo de chaves de recuperação para que ele possa instalar pacotes assinados com ela. No entanto, a chave extra não está incluída em otacerts.zip. Portanto, os sistemas que verificam corretamente os pacotes baixados não invocam a recuperação para pacotes assinados com essa chave.

Certificados e chaves privadas

Cada chave vem em dois arquivos: o certificado, que tem a extensão .x509.pem, e a chave privada, que tem a extensão .pk8. A chave privada precisa ser mantida em segredo e é necessária para assinar um pacote. A chave pode ser protegida por uma senha. O certificado, por outro lado, contém apenas a metade pública da chave, para que possa ser distribuído amplamente. Ela é usada para verificar se um pacote foi assinado pela chave privada correspondente.

O build padrão do Android usa cinco chaves, todas localizadas em build/target/product/security:

testkey
Chave padrão genérica para pacotes que não especificam uma chave de outra forma.
do Android
Chave de teste para pacotes que fazem parte da plataforma principal.
compartilhada
Chave de teste para itens compartilhados no processo de casa/contatos.
mídia
Chave de teste para pacotes que fazem parte do sistema de mídia/download.
networkstack
Chave de teste para pacotes que fazem parte do sistema de rede. A chave networkstack é usada para assinar binários projetados como Componentes modulares do sistema . Se as atualizações do módulo forem criadas separadamente e integradas como pré-builds na imagem do dispositivo, talvez não seja necessário gerar uma chave networkstack na árvore de origem do Android.

Pacotes individuais especificam uma dessas chaves definindo LOCAL_CERTIFICATE no arquivo Android.mk. (testkey é usado se essa variável não estiver definida.) Você também pode especificar uma chave totalmente diferente por nome do caminho, por exemplo:

device/yoyodyne/apps/SpecialApp/Android.mk
 [...]

LOCAL_CERTIFICATE := device/yoyodyne/security/special

Agora o build usa a chave device/yoyodyne/security/special.{x509.pem,pk8} para assinar o SpecialApp.apk. O build só pode usar chaves privadas que não são protegidas por senha.

Opções avançadas de assinatura

Substituição da chave de assinatura do APK

O script de assinatura sign_target_files_apks funciona nos arquivos de destino gerados para um build. Todas as informações sobre certificados e chaves privadas usadas durante o build são incluídas nos arquivos de destino. Ao executar o script de assinatura para assinar para lançamento, as chaves de assinatura podem ser substituídas com base no nome da chave ou no nome do APK.

Use as flags --key_mapping e --default_key_mappings para especificar a substituição de chaves com base nos nomes delas:

  • A flag --key_mapping src_key=dest_key especifica a substituição de uma chave por vez.
  • A flag --default_key_mappings dir especifica um diretório com cinco chaves para substituir todas as chaves em build/target/product/security. Isso é equivalente a usar --key_mapping cinco vezes para especificar os mapeamentos.
build/target/product/security/testkey      = dir/releasekey
build/target/product/security/platform     = dir/platform
build/target/product/security/shared       = dir/shared
build/target/product/security/media        = dir/media
build/target/product/security/networkstack = dir/networkstack

Use a flag --extra_apks apk_name1,apk_name2,...=key para especificar as substituições de chaves de assinatura com base nos nomes dos APKs. Se key for deixado em branco, o script vai tratar os APKs especificados como pré-assinados.

Para o produto hipotético tardis, você precisa de seis chaves protegidas por senha: cinco para substituir as cinco em build/target/product/security e uma para substituir a chave adicional device/yoyodyne/security/special exigida pelo SpecialApp no exemplo acima. Se as chaves estivessem nos seguintes arquivos:

vendor/yoyodyne/security/tardis/releasekey.x509.pem
vendor/yoyodyne/security/tardis/releasekey.pk8
vendor/yoyodyne/security/tardis/platform.x509.pem
vendor/yoyodyne/security/tardis/platform.pk8
vendor/yoyodyne/security/tardis/shared.x509.pem
vendor/yoyodyne/security/tardis/shared.pk8
vendor/yoyodyne/security/tardis/media.x509.pem
vendor/yoyodyne/security/tardis/media.pk8
vendor/yoyodyne/security/tardis/networkstack.x509.pem
vendor/yoyodyne/security/tardis/networkstack.pk8
vendor/yoyodyne/security/special.x509.pem
vendor/yoyodyne/security/special.pk8           # NOT password protected
vendor/yoyodyne/security/special-release.x509.pem
vendor/yoyodyne/security/special-release.pk8   # password protected

Em seguida, assine todos os apps assim:

./build/make/tools/releasetools/sign_target_files_apks \
    --default_key_mappings vendor/yoyodyne/security/tardis \
    --key_mapping vendor/yoyodyne/security/special=vendor/yoyodyne/security/special-release \
    --extra_apks PresignedApp= \
    -o tardis-target_files.zip \
    signed-tardis-target_files.zip

Isso vai mostrar o seguinte:

Enter password for vendor/yoyodyne/security/special-release key>
Enter password for vendor/yoyodyne/security/tardis/networkstack key>
Enter password for vendor/yoyodyne/security/tardis/media key>
Enter password for vendor/yoyodyne/security/tardis/platform key>
Enter password for vendor/yoyodyne/security/tardis/releasekey key>
Enter password for vendor/yoyodyne/security/tardis/shared key>
    signing: Phone.apk (vendor/yoyodyne/security/tardis/platform)
    signing: Camera.apk (vendor/yoyodyne/security/tardis/media)
    signing: NetworkStack.apk (vendor/yoyodyne/security/tardis/networkstack)
    signing: Special.apk (vendor/yoyodyne/security/special-release)
    signing: Email.apk (vendor/yoyodyne/security/tardis/releasekey)
        [...]
    signing: ContactsProvider.apk (vendor/yoyodyne/security/tardis/shared)
    signing: Launcher.apk (vendor/yoyodyne/security/tardis/shared)
NOT signing: PresignedApp.apk
        (skipped due to special cert string)
rewriting SYSTEM/build.prop:
  replace:  ro.build.description=tardis-user Eclair ERC91 15449 test-keys
     with:  ro.build.description=tardis-user Eclair ERC91 15449 release-keys
  replace: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/test-keys
     with: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/release-keys
    signing: framework-res.apk (vendor/yoyodyne/security/tardis/platform)
rewriting RECOVERY/RAMDISK/default.prop:
  replace:  ro.build.description=tardis-user Eclair ERC91 15449 test-keys
     with:  ro.build.description=tardis-user Eclair ERC91 15449 release-keys
  replace: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/test-keys
     with: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/release-keys
using:
    vendor/yoyodyne/security/tardis/releasekey.x509.pem
for OTA package verification
done.

Depois de pedir as senhas de todas as chaves protegidas por senha, o script assina novamente todos os arquivos APK no destino de entrada .zip com as chaves de lançamento. Antes de executar o comando, você também pode definir a variável de ambiente ANDROID_PW_FILE com um nome de arquivo temporário. Em seguida, o script invoca seu editor para permitir que você insira senhas para todas as chaves. Essa pode ser uma maneira mais conveniente de inserir senhas.

Substituição da chave de assinatura do APEX

O Android 10 introduz o formato de arquivo APEX para instalar módulos de sistema de nível mais baixo. Conforme explicado em assinatura do APEX, cada arquivo APEX é assinado com duas chaves: uma para a imagem do mini sistema de arquivos em um APEX e a outra para todo o APEX.

Ao assinar para lançamento, as duas chaves de assinatura de um arquivo APEX são substituídas por chaves de lançamento. A chave de payload do sistema de arquivos é especificada com a flag --extra_apex_payload, e toda a chave de assinatura do arquivo APEX é especificada com a flag --extra_apks.

Para o produto tardis, suponha que você tenha a seguinte configuração de chave para os arquivos APEX com.android.conscrypt.apex, com.android.media.apex e com.android.runtime.release.apex.

name="com.android.conscrypt.apex" public_key="PRESIGNED" private_key="PRESIGNED" container_certificate="PRESIGNED" container_private_key="PRESIGNED"
name="com.android.media.apex" public_key="PRESIGNED" private_key="PRESIGNED" container_certificate="PRESIGNED" container_private_key="PRESIGNED"
name="com.android.runtime.release.apex" public_key="vendor/yoyodyne/security/testkeys/com.android.runtime.avbpubkey" private_key="vendor/yoyodyne/security/testkeys/com.android.runtime.pem" container_certificate="vendor/yoyodyne/security/testkeys/com.google.android.runtime.release_container.x509.pem" container_private_key="vendor/yoyodyne/security/testkeys/com.google.android.runtime.release_container.pk8"

E você tem os seguintes arquivos que contêm as chaves de lançamento:

vendor/yoyodyne/security/runtime_apex_container.x509.pem
vendor/yoyodyne/security/runtime_apex_container.pk8
vendor/yoyodyne/security/runtime_apex_payload.pem

O comando a seguir substitui as chaves de assinatura para com.android.runtime.release.apex e com.android.tzdata.apex durante a assinatura da versão. Em particular, com.android.runtime.release.apex é assinado com as chaves de lançamento especificadas (runtime_apex_container para o arquivo APEX e runtime_apex_payload para o payload de imagem do arquivo). com.android.tzdata.apex é tratado como pré-assinado. Todos os outros arquivos APEX são processados pela configuração padrão, conforme listado nos arquivos de destino.

./build/make/tools/releasetools/sign_target_files_apks \
    --default_key_mappings   vendor/yoyodyne/security/tardis \
    --extra_apks             com.android.runtime.release.apex=vendor/yoyodyne/security/runtime_apex_container \
    --extra_apex_payload_key com.android.runtime.release.apex=vendor/yoyodyne/security/runtime_apex_payload.pem \
    --extra_apks             com.android.media.apex= \
    --extra_apex_payload_key com.android.media.apex= \
    -o tardis-target_files.zip \
    signed-tardis-target_files.zip

A execução do comando acima gera os seguintes registros:

        [...]
    signing: com.android.runtime.release.apex                  container (vendor/yoyodyne/security/runtime_apex_container)
           : com.android.runtime.release.apex                  payload   (vendor/yoyodyne/security/runtime_apex_payload.pem)
NOT signing: com.android.conscrypt.apex
        (skipped due to special cert string)
NOT signing: com.android.media.apex
        (skipped due to special cert string)
        [...]

Outras opções

O script de assinatura sign_target_files_apks reescreve a descrição e a impressão digital do build nos arquivos de propriedades para refletir que o build é assinado. A flag --tag_changes controla quais edições são feitas na impressão digital. Execute o script com -h para conferir a documentação de todas as flags.

Gerar chaves manualmente

O Android usa chaves RSA de 2048 bits com expoente público 3. Você pode gerar pares de certificado/chave privada usando a ferramenta openssl em openssl.org:

# generate RSA key
openssl genrsa -3 -out temp.pem 2048
Generating RSA private key, 2048 bit long modulus
....+++
.....................+++
e is 3 (0x3)

# create a certificate with the public part of the key
openssl req -new -x509 -key temp.pem -out releasekey.x509.pem -days 10000 -subj '/C=US/ST=California/L=San Narciso/O=Yoyodyne, Inc./OU=Yoyodyne Mobility/CN=Yoyodyne/emailAddress=yoyodyne@example.com'

# create a PKCS#8-formatted version of the private key
openssl pkcs8 -in temp.pem -topk8 -outform DER -out releasekey.pk8 -nocrypt

# securely delete the temp.pem file
shred --remove temp.pem

O comando openssl pkcs8 acima cria um arquivo .pk8 sem senha, adequado para uso com o sistema de build. Para criar um .pk8 protegido com uma senha (o que você precisa fazer para todas as chaves de lançamento reais), substitua o argumento -nocrypt por -passout stdin. Assim, o openssl vai criptografar a chave privada com uma senha lida da entrada padrão. Nenhum comando é impresso. Portanto, se stdin for o terminal, o programa vai parecer travado quando, na verdade, ele está apenas esperando você inserir uma senha. Outros valores podem ser usados para o argumento -passout e ler a senha de outros locais. Para mais detalhes, consulte a documentação do openssl.

O arquivo intermediário temp.pem contém a chave privada sem nenhum tipo de proteção por senha. Portanto, descarte-o com cuidado ao gerar chaves de lançamento. Em especial, o utilitário GNUshred pode não ser eficaz em sistemas de arquivos de rede ou com registro em diário. Você pode usar um diretório de trabalho localizado em um disco RAM (como uma partição tmpfs) ao gerar chaves para garantir que os intermediários não sejam expostos inadvertidamente.

Criar arquivos de imagem

Quando você tem signed-target_files.zip, é necessário criar a imagem para colocá-la em um dispositivo. Para criar a imagem assinada com base nos arquivos de destino, execute o seguinte comando na raiz da árvore do Android:

img_from_target_files signed-target_files.zip signed-img.zip
O arquivo resultante, signed-img.zip, contém todos os arquivos .img. Para carregar uma imagem em um dispositivo, use o fastboot da seguinte maneira:
fastboot update signed-img.zip