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 .
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:-
signature algorithm ID
(uint32) -
digest
(com prefixo de comprimento) — consulte Conteúdo protegido por integridade
-
- 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
- sequência com prefixo de comprimento de
signatures
com prefixo de comprimento:-
signature algorithm ID
(uint32) -
signature
com prefixo de comprimento sobresigned 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:
- Conteúdo das entradas ZIP (do deslocamento 0 até o início do bloco de assinatura do APK)
- Bloco de assinatura de APK
- Diretório Central ZIP
- ZIP Fim do Diretório Central
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.
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.
Verificação do esquema de assinatura APK v2
- Localize o bloco de assinatura do APK e verifique se:
- Dois campos de tamanho do Bloco de assinatura de APK contêm o mesmo valor.
- O ZIP Central Directory é imediatamente seguido pelo registro ZIP End of Central Directory.
- ZIP O final do Diretório Central não é seguido por mais dados.
- 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 .
- Para cada
signer
no bloco APK Signature Scheme v2:- Escolha o
signature algorithm ID
com suporte mais fortesignatures
. A ordem de força depende de cada versão de implementação/plataforma. - Verifique a
signature
correspondente dassignatures
em relaçãosigned data
usandopublic key
. (Agora é seguro analisarsigned data
.) - Verifique se a lista ordenada de IDs de algoritmos de assinatura em
digests
esignatures
é idêntica. (Isso evita a remoção/adição de assinaturas.) - Calcule o resumo do conteúdo do APK usando o mesmo algoritmo de resumo usado pelo algoritmo de assinatura.
- Verifique se o resumo calculado é idêntico ao
digest
correspondente dedigests
. - Verifique se o SubjectPublicKeyInfo do primeiro
certificate
decertificates
é idêntico àpublic key
.
- Escolha o
- A verificação será bem-sucedida se pelo menos um
signer
for encontrado e a etapa 3 for bem-sucedida para cadasigner
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:
- Cada signatário é representado por uma entrada JAR META-INF/<signer>.SF e META-INF/<signer>.(RSA|DSA|EC).
- <signer>.(RSA|DSA|EC) é um PKCS #7 CMS ContentInfo com estrutura SignedData cuja assinatura é verificada no arquivo <signer>.SF.
- 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.
- 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.
- 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.