Esquema de assinatura de APK v2

APK Signature Scheme 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 esquema de assinatura APK v2 insere um bloco de assinatura APK no arquivo APK imediatamente antes da seção ZIP Central Directory. Dentro do APK Signature Block, as assinaturas v2 e as informações de identidade do signatário são armazenadas em um APK Signature Scheme v2 Block .

APK antes e depois de assinar

Figura 1. APK antes e depois da assinatura

APK Signature Scheme 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 do formato APK v1, assinaturas APK v2 e mais recentes são armazenadas dentro de um bloco de assinatura APK, um novo contêiner introduzido para dar suporte ao esquema de assinatura APK v2. Em um arquivo APK, o APK Signing Block está localizado imediatamente antes do ZIP Central Directory, localizado no final do arquivo.

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

Formato

O formato do APK Signing Block é 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 (variável-comprimento: 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 encontrando primeiro o início do diretório central ZIP (encontrando o registro ZIP End of Central Directory no final do arquivo e lendo o deslocamento inicial do diretório central 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 então aponta eficientemente para o início do bloco no arquivo.

Pares de valor de ID 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 APK Signature Scheme v2 Block. 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 assinante.
  • 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 várias assinaturas com suporte forem encontradas. Isso permite a introdução de métodos de assinatura mais fortes no futuro de maneira compatível com versões anteriores. A abordagem sugerida é verificar a assinatura mais forte.

Formato

O APK Signature Scheme v2 Block é armazenado dentro do APK Signing Block 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 do 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 (formulário ASN.1 DER)
      • sequência prefixada de comprimento de additional attributes prefixados por comprimento:
        • ID (uint32)
        • value (variable-length: 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 SHA2-256 digest, SHA2-256 MGF1, 32 bytes de sal, trailer: 0xbc
  • 0x0102—RSASSA-PSS com SHA2-512 digest, SHA2-512 MGF1, 64 bytes de sal, trailer: 0xbc
  • 0x0103—RSASSA-PKCS1-v1_5 com resumo SHA2-256. Isso é para sistemas de compilação que exigem assinaturas determinísticas.
  • 0x0104—RSASSA-PKCS1-v1_5 com resumo SHA2-512. Isso é para sistemas de compilação 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 suportados pela plataforma Android. As ferramentas de assinatura podem suportar um subconjunto dos algoritmos.

Tamanhos de chave e curvas EC suportados:

  • RSA: 1024, 2048, 4096, 8192, 16384
  • EC: 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 APK Signing Block)
  2. Bloco de assinatura de APK
  3. Diretório Central ZIP
  4. ZIP Fim do Diretório Central

Seções do APK após a assinatura

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

APK Signature Scheme v2 protege a integridade das seções 1, 3, 4 e os blocos de signed data do APK Signature Scheme v2 Block 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 de 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 forma, semelhante a uma árvore Merkle de dois níveis. Cada seção é dividida em blocos consecutivos de 1 MB (2 20 bytes). O último pedaço em cada seção pode ser mais curto. O resumo de cada fragmento é calculado sobre a 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 do byte 0x5a , o número de fragmentos (little-endian uint32) e a concatenação de resumos dos fragmentos na ordem em que os fragmentos aparecem no APK. O resumo é calculado em partes para permitir 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. Assim, ao calcular o resumo sobre o ZIP End of Central Directory, o campo que contém o deslocamento do ZIP Central Directory deve ser tratado como contendo o deslocamento do APK Signing Block.

Proteções de reversão

Um invasor pode tentar verificar um APK assinado por v2 como um APK assinado por v1 em plataformas Android compatíveis com a verificação de APK assinado por v2. Para mitigar esse ataque, os APKs assinados por v2 que também são assinados por 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 de IDs de esquema de assinatura de APK separados por vírgula (o ID desse esquema é 2). Ao verificar a assinatura v1, o verificador de APK é obrigado a rejeitar APKs que não têm uma assinatura para o esquema de assinatura de APK que o verificador prefere deste conjunto (por exemplo, esquema v2). Essa proteção se baseia no 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 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 APK v2+ ou assinatura JAR (esquema v1). Plataformas mais antigas ignoram assinaturas v2 e verificam apenas assinaturas v1.

Processo de verificação de assinatura do APK

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

Verificação do esquema de assinatura do APK v2

  1. Localize o bloco de assinatura do APK e verifique se:
    1. Dois campos de tamanho do APK Signing Block contêm o mesmo valor.
    2. O ZIP Central Directory é imediatamente seguido pelo registro ZIP End of Central Directory.
    3. ZIP End of Central Directory não é seguido por mais dados.
  2. Localize o primeiro APK Signature Scheme v2 Block dentro do APK Signing Block. Se o bloco v2 estiver presente, vá para a etapa 3. Caso contrário, volte para verificar o APK usando o esquema v1 .
  3. Para cada signer no bloco APK Signature Scheme v2:
    1. Escolha o signature algorithm ID suportado mais forte de 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 aos signed data usando public key . (Agora é seguro analisar signed data .)
    3. Verifique se a lista ordenada de IDs de algoritmo de assinatura em digests e signatures é idêntica. (Isso é para evitar remoção/adição de assinatura.)
    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 dos digests .
    6. Verifique se SubjectPublicKeyInfo do primeiro certificate de certificates é idêntico à public key .
  4. A verificação é 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 de todo o arquivo do META-INF/MANIFEST.MF e resumos de cada seção do 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. O META-INF/MANIFEST.MF contém, para cada entrada JAR protegida por integridade, uma seção com o nome correspondente contendo o resumo do conteúdo descompactado da entrada. Todos esses resumos são verificados.
  5. A verificação do APK falha se o APK contiver entradas JAR que não estão listadas no MANIFEST.MF e não fazem parte da assinatura JAR.

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