Esquema de assinatura de APK v2

O esquema de assinatura de APK v2 é um esquema de assinatura de arquivo inteiro que aumenta a velocidade de verificação e reforça as garantias de integridade, detectando quaisquer mudanças nas partes protegidas do APK.

A assinatura usando o esquema de assinatura de APK v2 insere um bloco de assinatura de APK no arquivo APK imediatamente antes da seção do ZIP Central Directory. No bloco de assinatura do APK, as assinaturas v2 e as informações de identidade do signatário são armazenadas em um bloco do esquema de assinatura de APK v2.

APK antes e depois da assinatura

Figura 1. APK antes e depois da assinatura

O esquema de assinatura de APK v2 foi lançado no Android 7.0 (Nougat). Para que um APK possa ser instalado no Android 6.0 (Marshmallow) e em dispositivos mais antigos, ele precisa ser assinado usando a assinatura JAR antes de ser assinado com o esquema v2.

Bloco de assinatura de APK

Para manter a compatibilidade com o formato do APK v1, as assinaturas do v2 e versões mais recentes do APK são armazenadas em um bloco de assinatura de APK, um novo contêiner introduzido para oferecer suporte ao esquema de assinatura de APK v2. Em um arquivo APK, o bloco de assinatura do APK fica localizado imediatamente antes do diretório central ZIP, que fica no final do arquivo.

O bloco contém pares de ID e valor agrupados de uma maneira que facilita a localização do bloco no APK. A assinatura v2 do APK é armazenada como um par de ID-valor com ID 0x7109871a.

Formato

O formato do bloco de assinatura do APK é o seguinte (todos os campos numéricos são little-endian):

  • size of block em bytes (excluindo este campo) (uint64)
  • Sequência de pares de ID-valor com prefixo de comprimento uint64:
    • ID (uint32)
    • value (comprimento variável: comprimento do par - 4 bytes)
  • size of block em bytes: igual ao primeiro campo (uint64)
  • magic "APK Sig Block 42" (16 bytes)

O APK é analisado primeiro encontrando o início do diretório central ZIP (encontrando o registro "ZIP End of Central Directory" no final do arquivo e, em seguida, lendo o deslocamento inicial do diretório central do registro). O valor magic oferece uma maneira rápida de estabelecer que o que precede o diretório central é provavelmente o bloco de assinatura do APK. O valor size of block aponta de forma eficiente para o início do bloco no arquivo.

Pares de ID-valor com IDs desconhecidos precisam ser ignorados ao interpretar o bloco.

Bloco do esquema de assinatura de APK v2

O APK é assinado por um ou mais signatários/identidades, cada um representado por uma chave de assinatura. Essas informações são armazenadas como um bloco do esquema de assinatura de APK v2. Para cada signatário, as seguintes informações são armazenadas:

  • Tuplas (algoritmo de assinatura, resumo, assinatura). O resumo é armazenado para desacoplar a verificação de assinatura da verificação de integridade do conteúdo do APK.
  • Cadeia de certificados X.509 que representa a identidade do signatário.
  • Atributos adicionais como pares de chave-valor.

Para cada signatário, o APK é verificado usando uma assinatura com suporte da lista fornecida. Assinaturas com algoritmos de assinatura desconhecidos são ignoradas. Cabe a cada implementação escolher qual assinatura usar quando várias assinaturas compatíveis forem encontradas. Isso permite a introdução de métodos de assinatura mais seguros no futuro de forma compatível com versões anteriores. A abordagem sugerida é verificar a assinatura mais forte.

Formato

O bloco do esquema de assinatura de APK v2 é armazenado dentro do bloco de assinatura do APK no ID 0x7109871a.

O formato do bloco do esquema de assinatura do APK v2 é o seguinte (todos os valores numéricos são little-endian, todos os campos prefixados com comprimento usam uint32 para comprimento):

  • Sequência com prefixo de comprimento de signer com prefixo de comprimento:
    • signed data com prefixo de comprimento:
      • Sequência com prefixo de comprimento de digests com prefixo de comprimento:
      • Sequência com prefixo de comprimento de certificates X.509:
        • certificate X.509 com prefixo de comprimento (forma ASN.1 DER)
      • Sequência com prefixo de comprimento de additional attributes com prefixo de comprimento:
        • ID (uint32)
        • value (comprimento variável: comprimento do atributo extra - 4 bytes)
    • sequência com prefixo de comprimento de signatures com prefixo de comprimento:
      • signature algorithm ID (uint32)
      • signature com prefixo de comprimento sobre signed data
    • public key com prefixo de comprimento (SubjectPublicKeyInfo, formato ASN.1 DER)

IDs de algoritmos de assinatura

  • 0x0101: RSASSA-PSS com resumo SHA2-256, MGF1 SHA2-256, 32 bytes de salt, trailer: 0xbc
  • 0x0102: RSASSA-PSS com resumo SHA2-512, MGF1 SHA2-512, 64 bytes de sal, trailer: 0xbc
  • 0x0103: RSASSA-PKCS1-v1_5 com resumo SHA2-256. Isso é para sistemas de build que exigem assinaturas determinísticas.
  • 0x0104: RSASSA-PKCS1-v1_5 com resumo SHA2-512. Isso é para sistemas de build que exigem assinaturas determinísticas.
  • 0x0201: ECDSA com resumo SHA2-256
  • 0x0202: ECDSA com resumo SHA2-512
  • 0x0301: DSA com resumo SHA2-256

Todos os algoritmos de assinatura acima são compatíveis com a plataforma Android. As ferramentas de assinatura podem oferecer suporte a um subconjunto dos algoritmos.

Tamanhos de chaves e curvas de EC compatíveis:

  • RSA: 1024, 2048, 4096, 8192, 16384
  • EC: NIST P-256, P-384, P-521
  • DSA: 1024, 2048 e 3072

Conteúdos protegidos pela integridade

Para proteger o conteúdo de um APK, ele é dividido em quatro seções:

  1. Conteúdo das entradas ZIP (do deslocamento 0 até o início do bloco de assinatura do APK)
  2. Bloco de assinatura de APK
  3. Diretório central do ZIP
  4. ZIP End of Central Directory

Seções do APK após a assinatura

Figura 2. Seções do APK após a assinatura

O esquema de assinatura de APK v2 protege a integridade das seções 1, 3, 4 e dos blocos signed data do bloco do esquema de assinatura de APK v2 contidos na seção 2.

A integridade das seções 1, 3 e 4 é protegida por um ou mais resumos de conteúdo armazenados em blocos signed data, que, por sua vez, são protegidos por uma ou mais assinaturas.

O resumo das seções 1, 3 e 4 é calculado da seguinte maneira, semelhante a uma árvore de Merkle de dois níveis. Cada seção é dividida em blocos consecutivos de 1 MB (220 bytes). A última parte de cada seção pode ser mais curta. O resumo de cada fragmento é calculado com base na concatenação do byte 0xa5, o comprimento do fragmento em bytes (little-endian uint32) e o conteúdo do fragmento. O resumo de nível superior é calculado sobre a concatenação de bytes 0x5a, o número de blocos (little-endian uint32) e a concatenação de resumos dos blocos na ordem em que aparecem no APK. O resumo é calculado em partes para acelerar a computação, paralelizando-a.

Resumo do APK

Figura 3. Resumo do APK

A proteção da seção 4 (ZIP End of Central Directory) é complicada pela seção que contém o deslocamento do ZIP Central Directory. O deslocamento muda quando o tamanho do bloco de assinatura do APK muda, por exemplo, quando uma nova assinatura é adicionada. Portanto, ao calcular o resumo no fim do diretório central ZIP, o campo que contém o deslocamento do diretório central ZIP precisa ser tratado como contendo o deslocamento do bloco de assinatura do APK.

Proteções contra reversão

Um invasor pode tentar fazer com que um APK assinado com a v2 seja verificado como um APK assinado com a v1 em plataformas Android que oferecem suporte à verificação de APKs assinados com a v2. Para atenuar esse ataque, os APKs assinados pela v2 que também são assinados pela v1 precisam conter um atributo X-Android-APK-Signed na seção principal dos arquivos META-INF/*.SF. O valor do atributo é um conjunto separado por vírgulas de IDs de esquema de assinatura de APK. O ID desse esquema é 2. Ao verificar a assinatura v1, o verificador de APK precisa rejeitar APKs que não têm uma assinatura para o esquema de assinatura de APK que o verificador prefere neste conjunto (por exemplo, o esquema v2). Essa proteção depende do fato de que os arquivos META-INF/*.SF de conteúdo são protegidos por assinaturas v1. Consulte a seção sobre verificação de APKs assinados por JAR.

Um invasor pode tentar remover assinaturas mais fortes do bloco do esquema de assinatura de APK v2. Para mitigar esse ataque, a lista de IDs de algoritmos de assinatura com que o APK estava sendo assinado é armazenada no bloco signed data, que é protegido por cada assinatura.

Verificação

No Android 7.0 e mais recentes, os APKs podem ser verificados de acordo com o esquema de assinatura v2+ do APK ou a assinatura JAR (esquema v1). Plataformas mais antigas ignoram as assinaturas v2 e verificam apenas as assinaturas v1.

Processo de verificação de assinatura de APK

Figura 4. Processo de verificação de assinatura do APK (novas etapas em vermelho)

Verificação do esquema de assinatura de APK v2

  1. Localize o bloco de assinatura do APK e verifique se:
    1. Dois campos de tamanho do bloco de assinatura do APK contêm o mesmo valor.
    2. O diretório central ZIP é seguido imediatamente pelo registro ZIP End of Central Directory.
    3. O fim do diretório central ZIP não é seguido por mais dados.
  2. Localize o primeiro bloco do esquema de assinatura do APK v2 dentro do bloco de assinatura do APK. Se o bloco v2 estiver presente, prossiga para a etapa 3. Caso contrário, volte a verificar o APK usando o esquema v1.
  3. Para cada signer no bloco do Esquema de assinatura de APK v2:
    1. Escolha a signature algorithm ID com suporte mais forte de signatures. A ordem de força depende de cada implementação/versão da plataforma.
    2. Verifique o signature correspondente de signatures em relação a signed data usando public key. Agora é possível analisar signed data.
    3. Verifique se a lista ordenada de IDs de algoritmos de assinatura em digests e signatures é idêntica. Isso é para impedir a remoção/adição de assinaturas.
    4. Calcule o resumo do conteúdo do APK usando o mesmo algoritmo de resumo usado pelo algoritmo de assinatura.
    5. Verifique se o resumo computado é idêntico ao digest correspondente de digests.
    6. Verifique se o SubjectPublicKeyInfo do primeiro certificate de certificates é idêntico a public key.
  4. A verificação será bem-sucedida se pelo menos um signer for encontrado e a etapa 3 for bem-sucedida para cada signer encontrado.

Observação: o APK não precisa ser verificado usando o esquema v1 se uma falha ocorrer na etapa 3 ou 4.

Verificação de APK assinado por JAR (esquema v1)

O APK assinado por JAR é um JAR assinado padrão, que precisa conter exatamente as entradas listadas em META-INF/MANIFEST.MF e em que todas as entradas precisam ser assinadas pelo mesmo conjunto de assinantes. A integridade é verificada da seguinte forma:

  1. Cada signatário é representado por uma entrada JAR META-INF/<signer>.SF e META-INF/<signer>.(RSA|DSA|EC).
  2. <signer>.(RSA|DSA|EC) é um PKCS #7 CMS ContentInfo com estrutura SignedData, cuja assinatura é verificada no arquivo <signer>.SF.
  3. O arquivo <signer>.SF contém um resumo de todo o arquivo do META-INF/MANIFEST.MF e resumos de cada seção de META-INF/MANIFEST.MF. O resumo de todo o arquivo do MANIFEST.MF é verificado. Se isso falhar, o resumo de cada seção MANIFEST.MF será verificado.
  4. META-INF/MANIFEST.MF contém, para cada entrada JAR protegida por integridade, uma seção com nome correspondente que contém o resumo do conteúdo não compactado da entrada. Todos esses resumos são verificados.
  5. A verificação do APK falhará se ele contiver entradas JAR que não estejam listadas em MANIFEST.MF e não façam parte da assinatura JAR.

A cadeia de proteção é <signer>.(RSA|DSA|EC) -> <signer>.SF -> MANIFEST.MF -> conteúdo de cada entrada JAR protegida por integridade.