Verificação completa de transparência do APK dos serviços do sistema do Google

Esta página descreve vários métodos para garantir que o APK instalado no dispositivo Android corresponda à declaração feita no Modelo de requerente. Isso envolve extrair o APK em questão do dispositivo, verificar a integridade do código e realizar uma prova de inclusão de registro no artefato extraído.

Processo de verificação

Um registro de transparência é implementado com uma árvore Merkle que consiste em hashes. Um nó folha contém dados, e um nó pai contém o hash dos filhos.

Basicamente, duas computações são realizadas na árvore de Merkle para verificar a propriedade de integridade dos registros de transparência: a prova de inclusão e a prova de consistência. O primeiro prova que o registro inclui uma entrada correspondente a uma versão específica do APK. A entrada de registro inclui um hash, que é o resumo SHA256 do token de assinatura de código na forma de um JSON Web Token (JWT), que pode ser obtido dos APKs correspondentes. O último prova que, quando novas entradas são adicionadas à árvore, o novo checkpoint é (criptograficamente) consistente com a versão anterior da árvore.

Para verificar um APK coberto, faça um teste de prova de inclusão com base em um ponto de verificação atestado. Estamos planejando integrar esse registro a uma rede pública de testemunhas usando um protocolo de testemunha padronizado. Isso vai fornecer um checkpoint testemunhado, que garante a consistência do registro.

Se quiser se convencer de que o APK no seu dispositivo está de acordo com a declaração feita no modelo do requerente, consulte o texto abaixo.

Prova de inclusão

Um usuário do Android pode verificar se um APK coberto no dispositivo está no registro. Para isso, primeiro extraia o APK e os metadados relevantes e compare o hash de raiz recalculado com o hash de raiz contido no ponto de verificação publicado. Se eles corresponderem, o usuário do Android poderá ter certeza de algumas proteções descritas no modelo de ameaça.

Como verificar a inclusão de um APK no registro

Conforme descrito anteriormente, a lista de APKs atualmente cobertos pode ser encontrada na página "Visão geral".

Pré-requisitos para verificação

Antes de verificar se o APK extraído do seu dispositivo está de acordo com nossa reivindicação, instale as seguintes ferramentas em um computador conectado à rede.

Android Debug Bridge

O ADB é uma ferramenta que se comunica com um dispositivo Android e está disponível no site do Android SDK Platform Tools .

AAPT2

O AAPT2 (Android Asset Packaging Tool) é uma ferramenta de build usada para a compilação e o empacotamento dos recursos de um app Android. Ele pode ser encontrado como uma ferramenta independente no Android SDK Build Tools 26.0.2 e versões mais recentes.

bundletool

O bundletool é uma ferramenta usada para criar um Android App Bundle (AAB). Ele também pode ser usado para converter um AAB em APKs que podem ser instalados em dispositivos. Ele pode ser baixado do GitHub.

Verificador de prova de inclusão

Este é um módulo Go que publicamos em um repositório git no Android Open Source Project (AOSP) chamado avb. Ele pode consultar o registro de transparência do APK dos serviços do sistema do Google e informar se um pacote está incluído no registro. Um exemplo de como isso é usado pode ser encontrado em uma seção posterior.

Para fazer o download dessa ferramenta, primeiro clone o repositório avb.

computer:~$ git clone https://android.googlesource.com/platform/external/avb

O código-fonte do verificador pode ser encontrado em tools/transparency/verify no repositório avb.

Criar um payload para verificação

Para verificar se o APK extraído do seu dispositivo está de acordo com nossas declarações, crie uma carga útil de registro com informações derivadas do APK.

Antes de começar, verifique se o adb pode ser usado no seu dispositivo ativando a depuração do adb.

Em seguida, localize onde o APK está instalado no seu dispositivo. Para fins deste guia, vamos usar o APK do Verificador de chaves do sistema Android (com.google.android.contactkeys) como exemplo de trabalho.

computer:~$ adb shell pm list packages -f | grep contactkeys
package:/data/app/~~i5WYSO4PuAAv798-eHdM7A==/com.google.android.contactkeys-PQCKjnn7xDqjeVhcUDibBA==/base.apk=com.google.android.contactkeys

Se o APK do Verificador de chaves do sistema Android estiver instalado no seu dispositivo, o comando acima vai retornar um caminho indicando onde ele está instalado. Caso contrário, não vai aparecer nada.

Em seguida, faça o download do APK do dispositivo Android para o computador em que você está trabalhando usando este comando. O local real e o nome do arquivo APK no dispositivo podem variar:

computer:~$ mkdir -p /tmp/testdir && cd /tmp/testdir
computer:/tmp/testdir$ adb pull /data/app/~~i5WYSO4PuAAv798-eHdM7A==/com.google.android.contactkeys-PQCKjnn7xDqjeVhcUDibBA==/base.apk ./contactkeys_candidate.apk

Para obter e verificar o nome do pacote do APK que você acabou de baixar, primeiro descompacte o APK, já que ele é um tipo especial de arquivo ZIP.

computer:/tmp/testdir$ mkdir extracted && unzip contactkeys_candidate.apk -d extracted/

Essa etapa descompacta todos os arquivos que compõem o APK. O nome do pacote e a versão podem ser encontrados no manifesto do APK, que geralmente está em um arquivo chamado AndroidManifest.xml.

No entanto, o arquivo de manifesto obtido está em formato binário, que não é legível. Para converter o XML binário em um formato legível, usamos a ferramenta aapt2 instalada anteriormente, conforme necessário na seção pré-requisito.

computer:/tmp/testdir$ aapt2 dump badging ./contactkeys_candidate.apk
package: name='com.google.android.contactkeys' versionCode='7805' versionName='1.219.791156583' platformBuildVersionName='Baklava' platformBuildVersionCode='36' compileSdkVersion='36' compileSdkVersionCodename='Baklava'

Com base na saída acima, agora podemos ter certeza de que o nome do pacote desse APK é com.google.android.contactkeys e o número da versão (versionCode) é 1413.

Agora, vamos procurar a assinatura de transparência do código no APK. Ele precisa ser um arquivo chamado code_transparency_signed.jwt contido na pasta META-INF entre os outros arquivos extraídos do APK.

computer:/tmp/testdir$ sha256sum extracted/META-INF/code_transparency_signed.jwt
1779a2aee029112c2c9bfc9390b9678f3e5f4595b39705e8528dd522e8042f11  code_transparency_signed.jwt

Com essa string de hash, temos todas as informações necessárias para montar um payload de registro de acordo com o formato descrito na seção Conteúdo do registro. Neste exemplo, um payload de registro correspondente seria assim:

1779a2aee029112c2c9bfc9390b9678f3e5f4595b39705e8528dd522e8042f11
SHA256(Signed Code Transparency JWT)
com.google.android.contactkeys
1143

Observe também o caractere de nova linha após a versão do pacote.

Você pode salvar o conteúdo em um arquivo, como payload.txt. Isso será útil ao fazer o teste de prova de inclusão mais tarde.

Verificar a autenticidade da assinatura de código do APK

Agora, vamos verificar a autenticidade do token de assinatura de código incorporado no APK. Para isso, usamos bundletool e a chave pública do par de chaves usado para assinar o certificado. Eles são publicados em cada seção dos respectivos APKs. Supondo que você tenha salvo o certificado de chave pública (por exemplo, para o Verificador de chaves do sistema Android) em um arquivo chamado signing_cert_pubkey.pem, siga o guia abaixo para realizar a verificação da assinatura de código.

Primeiro, crie um arquivo ZIP e adicione o APK candidato a ele.

computer:/tmp/testdir$ zip -u test.zip contactkeys_candidate.apk
        zip warning: test.zip not found or empty
  adding: contactkeys_candidate.apk (deflated 58%)

computer:/tmp/testdir$ file test.zip
test.zip: Zip archive data, at least v2.0 to extract, compression method=deflate

Agora estamos prontos para usar o comando check-transparency do bundletool para verificar se a assinatura de código incorporada no APK candidato corresponde à publicada.

computer:/tmp/testdir$ java -jar BUNDLETOOL_INSTALL_PATH/bundletool-all-version.jar check-transparency \
  --mode=apk \
  --apk-zip=test.zip \
  --transparency-key-certificate=signing_cert_pubkey.pem

APK signature is valid. SHA-256 fingerprint of the apk signing key certificate (must be compared with the developer's public key manually): D9 E1 73 5B 2A 39 51 27 3A 87 35 B7 66 9E F1 9E F5 3A F1 C1 27 5C BA 31 39 3C 18 40 8B 03 79 D0
Code transparency signature verified for the provided code transparency key certificate.
Code transparency verified: code related file contents match the code transparency file.

Verifique se a saída do comando acima afirma que a assinatura de transparência de código e a transparência de código foram verificadas. Se não forem, por exemplo, se você estiver vendo uma saída como Code transparency verification failed because the provided public key certificate does not match the transparency file, isso significa que a integridade do código do APK em questão pode estar comprometida e você não deve confiar no APK. Confirme se você está verificando com o certificado de chave pública correto. Caso contrário, se tudo estiver correto, isso significa que a autenticidade da assinatura do código foi verificada para o APK que você está validando.

Verificação da inclusão de pacotes (prova de inclusão)

Usando o payload que você construiu antes, agora é possível testar se o pacote em questão foi incluído no registro de transparência.

Uma ferramenta de prova de inclusão foi publicada no avb repositório no Android Open Source Project. Para executar:

computer:external/avb/tools/transparency/verify$ PAYLOAD_PATH=PATH_TO_PAYLOAD_DIR/payload.txt
computer:external/avb/tools/transparency/verify$ go build cmd/verifier/verifier.go
computer:external/avb/tools/transparency/verify$ ./verifier --payload_path=${PAYLOAD_PATH} --log_type=google_system_apk

O verificador usa o ponto de verificação correspondente e o conteúdo do registro (encontrado no diretório de blocos) para verificar se o payload do APK está no registro de transparência, confirmando que ele foi publicado pelo Google.

A saída do comando é gravada em stdout:

  • OK. inclusion check success! se o código do pacote estiver incluído no registro;
  • FAILURE, se não for.