Esquema de assinatura de APK v2

O esquema de assinatura do APK v2 é um esquema de assinatura de arquivo inteiro que aumenta a velocidade de verificação e fortalece as garantias de integridade ao detectar quaisquer alterações nas partes protegidas do APK.

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

APK antes e depois da assinatura

Figura 1. APK antes e depois da assinatura

O esquema de assinatura APK v2 foi introduzido no Android 7.0 (Nougat). Para tornar um APK instalável no Android 6.0 (Marshmallow) e em dispositivos mais antigos, o APK deve ser assinado usando a assinatura JAR antes de ser assinado com o esquema v2.

Bloco de assinatura de APK

Para manter a compatibilidade com versões anteriores com o formato APK v1, as assinaturas APK v2 e mais recentes são armazenadas dentro de 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 está localizado imediatamente antes do diretório central ZIP, localizado no final do arquivo.

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

Formatar

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 valores de ID 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 a partir do registro). O valor magic fornece uma maneira rápida de estabelecer que o que precede o Diretório Central é provavelmente o Bloco de Assinatura do APK. O size of block aponta eficientemente para o início do bloco no arquivo.

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

Bloco do esquema de assinatura 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 APK Signature Scheme v2. Para cada signatário, as seguintes informações são armazenadas:

  • (algoritmo de assinatura, resumo, assinatura) tuplas. O resumo é armazenado para dissociar 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 valores-chave.

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

Formatar

O bloco APK Signature Scheme v2 é armazenado dentro do bloco de assinatura APK sob o ID 0x7109871a .

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

  • sequência com prefixo de comprimento 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 de certificates X.509 com prefixo de comprimento:
        • certificate X.509 com prefixo de comprimento (formulário 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 adicional - 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, formulário ASN.1 DER)

IDs de algoritmo de assinatura

  • 0x0101—RSASSA-PSS com resumo SHA2-256, SHA2-256 MGF1, 32 bytes de salt, trailer: 0xbc
  • 0x0102—RSASSA-PSS com resumo SHA2-512, SHA2-512 MGF1, 64 bytes de sal, trailer: 0xbc
  • 0x0103—RSASSA-PKCS1-v1_5 com resumo SHA2-256. Isto é para sistemas de construção que requerem assinaturas determinísticas.
  • 0x0104—RSASSA-PKCS1-v1_5 com resumo SHA2-512. Isto é para sistemas de construção que requerem 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 suportados pela plataforma Android. As ferramentas de assinatura podem oferecer suporte a um subconjunto de algoritmos.

Tamanhos de chaves e curvas EC suportados:

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

Conteúdo protegido por integridade

Para fins de proteção do conteúdo do APK, um APK consiste 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 ZIP
  4. ZIP Fim do Diretório Central

Seções do APK após assinatura

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

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

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

O resumo das seções 1, 3 e 4 é calculado da seguinte forma, semelhante a uma árvore Merkle de dois níveis. Cada seção é dividida em pedaços consecutivos de 1 MB (2 20 bytes). O último pedaço de cada seção pode ser mais curto. O resumo de cada pedaço é calculado sobre a concatenação do byte 0xa5 , o comprimento do pedaço em bytes (little-endian uint32) e o conteúdo do pedaço. O resumo de nível superior é calculado sobre a concatenação do byte 0x5a , o número de pedaços (little-endian uint32) e a concatenação dos resumos dos pedaços na ordem em que aparecem no APK. O resumo é calculado em partes para permitir acelerar o cálculo, paralelizando-o.

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. Assim, ao calcular o resumo no final ZIP do diretório central, o campo que contém o deslocamento do diretório central ZIP deve ser tratado como contendo o deslocamento do bloco de assinatura APK.

Proteções contra reversão

Um invasor pode tentar verificar um APK assinado v2 como um APK assinado v1 em plataformas Android compatíveis com a verificação de APK assinado v2. Para mitigar esse ataque, os APKs assinados pela v2 que também são assinados pela v1 devem conter um atributo X-Android-APK-Signed na seção principal de seus arquivos META-INF/*.SF. O valor do atributo é um conjunto separado por vírgulas de IDs de esquema de assinatura APK (o ID deste esquema é 2). Ao verificar a assinatura v1, o verificador de APK é obrigado a rejeitar APKs que não possuem uma assinatura para o esquema de assinatura de APK que o verificador prefere deste conjunto (por exemplo, esquema v2). Esta proteção depende do fato de que os conteúdos dos arquivos META-INF/*.SF são protegidos por assinaturas v1. Consulte a seção sobre verificação de APK assinado por JAR .

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

Verificação

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

Processo de verificação de assinatura APK

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

Verificação do esquema de assinatura APK v2

  1. Localize o bloco de assinatura do APK e verifique se:
    1. Dois campos de tamanho do Bloco de assinatura de APK contêm o mesmo valor.
    2. O ZIP Central Directory é imediatamente seguido pelo registro ZIP End of Central Directory.
    3. ZIP O final do Diretório Central não é seguido por mais dados.
  2. Localize o primeiro bloco APK Signature Scheme v2 dentro do bloco de assinatura APK. Se o bloco v2 estiver presente, prossiga para a etapa 3. Caso contrário, volte para a verificação do APK usando o esquema v1 .
  3. Para cada signer no bloco APK Signature Scheme v2:
    1. Escolha o signature algorithm ID com suporte mais forte signatures . A ordem de força depende de cada versão de implementação/plataforma.
    2. Verifique a signature correspondente das signatures em relação signed data usando public key . (Agora é seguro analisar signed data .)
    3. Verifique se a lista ordenada de IDs de algoritmos de assinatura em digests e signatures é idêntica. (Isso evita 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 calculado é idêntico ao digest correspondente de digests .
    6. Verifique se o SubjectPublicKeyInfo do primeiro certificate de certificates é idêntico à 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 deve ser verificado usando o esquema v1 se ocorrer uma falha 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 deve conter exatamente as entradas listadas em META-INF/MANIFEST.MF e onde todas as entradas devem ser assinadas pelo mesmo conjunto de assinantes. Sua 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 do arquivo inteiro do META-INF/MANIFEST.MF e resumos de cada seção do META-INF/MANIFEST.MF. O resumo do arquivo inteiro 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 com integridade protegida, uma seção nomeada correspondentemente contendo o resumo do conteúdo descompactado da entrada. Todos esses resumos são verificados.
  5. A verificação do APK falhará se o APK contiver entradas JAR que não estejam listadas no MANIFEST.MF e não façam parte da assinatura JAR.

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