Esquema de firma de APK v2

El esquema de firma de APK v2 es un esquema de firma de archivos completos que aumenta la velocidad de verificación y refuerza las garantías de integridad, ya que detecta cualquier cambio en las partes protegidas del APK.

La firma con el esquema de firma de APK v2 inserta un bloque de firma de APK en el archivo APK inmediatamente antes de la sección del directorio central de ZIP. Dentro del bloque de firma de APK, las firmas v2 y la información de identidad del firmante se almacenan en un bloque de esquema de firma v2 de APK.

El APK antes y después de la firma

Figura 1: APK antes y después de la firma

El esquema de firma de APK v2 se introdujo en Android 7.0 (Nougat). Para que un APK se pueda instalar en Android 6.0 (Marshmallow) y dispositivos anteriores, se debe firmar el APK con la firma JAR antes de firmarlo con el esquema v2.

Bloqueo de firma de APK

Para mantener la retrocompatibilidad con el formato de APK v1, las firmas de APK v2 y versiones posteriores se almacenan dentro de un bloque de firma de APK, un nuevo contenedor que se introdujo para admitir el esquema de firma de APK v2. En un archivo APK, el bloque de firma de APK se encuentra inmediatamente antes del directorio central de ZIP, que se encuentra al final del archivo.

El bloque contiene pares de ID-valor unidos de manera que sea más fácil ubicar el bloque en el APK. La firma v2 del APK se almacena como un par de ID-valor con el ID 0x7109871a.

Formato

El formato del bloque de firma de APK es el siguiente (todos los campos numéricos son "little endian"):

  • size of block en bytes (sin incluir este campo) (uint64)
  • Secuencia de pares de ID-valor con prefijo de longitud uint64:
    • ID (uint32)
    • value (longitud variable: longitud del par - 4 bytes)
  • size of block en bytes, igual que el primer campo (uint64)
  • magic “APK Sig Block 42” (16 bytes)

Para analizar el APK, primero se encuentra el inicio del directorio central ZIP (encontrando el registro de fin del directorio central ZIP al final del archivo y, luego, leyendo el desplazamiento inicial del directorio central desde el registro). El valor magic proporciona una forma rápida de establecer que lo que precede al directorio central es probablemente el bloque de firma del APK. Luego, el valor size of block apunta de manera eficiente al inicio del bloque en el archivo.

Los pares de ID-valor con ID desconocidos deben ignorarse cuando se interpreta el bloque.

Bloque del esquema de firma de APK v2

El APK está firmado por uno o más firmantes o identidades, cada uno de ellos representado por una clave de firma. Esta información se almacena como un bloque del esquema de firma de APK v2. Para cada firmante, se almacena la siguiente información:

  • Tuplas (algoritmo de firma, resumen, firma). El resumen se almacena para desacoplar la verificación de la firma de la verificación de integridad del contenido del APK.
  • Cadena de certificados X.509 que representa la identidad del firmante
  • Atributos adicionales como pares clave-valor

Para cada firmante, el APK se verifica con una firma compatible de la lista proporcionada. Se ignoran las firmas con algoritmos de firma desconocidos. Depende de cada implementación elegir qué firma usar cuando se encuentran varias firmas compatibles. Esto permitirá la introducción de métodos de firma más seguros en el futuro de manera retrocompatible. El enfoque sugerido es verificar la firma más segura.

Formato

El bloque del esquema de firma de APK v2 se almacena dentro del bloque de firma de APK con el ID 0x7109871a.

El formato del bloque v2 del esquema de firma de APK es el siguiente (todos los valores numéricos son Little-endian, todos los campos con el prefijo de longitud usan uint32 para la longitud):

  • secuencia con prefijo de longitud de signer con prefijo de longitud:
    • signed data con prefijo de longitud:
      • Secuencia con prefijo de longitud de digests con prefijo de longitud:
      • Secuencia con prefijo de longitud de certificates X.509:
        • certificate X.509 con prefijo de longitud (formato ASN.1 DER)
      • Secuencia con prefijo de longitud de additional attributes con prefijo de longitud:
        • ID (uint32)
        • value (longitud variable: longitud del atributo adicional: 4 bytes)
    • Secuencia con prefijo de longitud de signatures con prefijo de longitud:
      • signature algorithm ID (uint32)
      • signature con prefijo de longitud sobre signed data
    • public key con prefijo de longitud (SubjectPublicKeyInfo, formato ASN.1 DER)

IDs de algoritmos de firma

  • 0x0101: RSASSA-PSS con resumen SHA2-256, MGF1 SHA2-256, 32 bytes de sal, tráiler: 0xbc
  • 0x0102: RSASA-PSS con resumen SHA2-512, SHA2-512 MGF1, 64 bytes de sal, avance: 0xbc
  • 0x0103: RSASSA-PKCS1-v1_5 con resumen SHA2-256 Esto es para sistemas de compilación que requieren firmas deterministas.
  • 0x0104: RSASSA-PKCS1-v1_5 con resumen SHA2-512 Esto es para sistemas de compilación que requieren firmas deterministas.
  • 0x0201: ECDSA con resumen SHA2-256
  • 0x0202: ECDSA con resumen SHA2-512
  • 0x0301: DSA con resumen SHA2-256

La plataforma de Android admite todos los algoritmos de firma anteriores. Las herramientas de firma pueden admitir un subconjunto de los algoritmos.

Tamaños de claves y curvas EC compatibles:

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

Contenido protegido por integridad

A los efectos de proteger el contenido de un APK, este consta de cuatro secciones:

  1. Contenido de las entradas ZIP (desde el desplazamiento 0 hasta el inicio del bloque de firma de APK)
  2. Bloqueo de firma de APK
  3. Directorio central de ZIP
  4. Fin del directorio central de ZIP

Secciones del APK después de la firma

Figura 2: Secciones del APK después de la firma

El esquema de firma de APK v2 protege la integridad de las secciones 1, 3 y 4, y los bloques signed data del bloque de esquema de firma de APK v2 que se encuentra en la sección 2.

La integridad de las secciones 1, 3 y 4 está protegida por uno o más resúmenes de su contenido almacenados en bloques signed data que, a su vez, están protegidos por una o más firmas.

El resumen de las secciones 1, 3 y 4 se calcula de la siguiente manera, similar a un árbol de Merkle de dos niveles. Cada sección se divide en fragmentos consecutivos de 1 MB (220 bytes). El último fragmento de cada sección puede ser más corto. El resumen de cada fragmento se calcula sobre la concatenación del byte 0xa5, la longitud del fragmento en bytes (little-endian uint32) y el contenido del fragmento. El resumen de nivel superior se calcula sobre la concatenación del byte 0x5a, la cantidad de fragmentos (little-endian uint32) y la concatenación de resúmenes de los fragmentos en el orden en que aparecen en el APK. El resumen se calcula en fragmentos para permitir acelerar el procesamiento mediante la paralelización.

Resumen del APK

Figura 3: Resumen del APK

La sección que contiene el desplazamiento del directorio central de ZIP complica la protección de la sección 4 (extremo de ZIP del directorio central). El desplazamiento cambia cuando cambia el tamaño del bloque de firma del APK, por ejemplo, cuando se agrega una firma nueva. Por lo tanto, cuando se calcula el resumen sobre el final del directorio central de ZIP, el campo que contiene el desplazamiento del directorio central de ZIP se debe tratar como que contiene el desplazamiento del bloque de firma de APK.

Protecciones contra la reversión

Un atacante podría intentar que un APK firmado con v2 se verifique como un APK firmado con v1 en plataformas de Android que admitan la verificación de APKs firmados con v2. Para mitigar este ataque, los APK firmados con v2 que también estén firmados con v1 deben contener un atributo X-Android-APK-Signed en la sección principal de sus archivos META-INF/*.SF. El valor del atributo es un conjunto de IDs de esquemas de firma de APK separados por comas (el ID de este esquema es 2). Cuando se verifica la firma v1, el verificador de APK debe rechazar los APKs que no tengan una firma para el esquema de firmas de APK que el verificador prefiere de este conjunto (p.ej., esquema v2). Esta protección se basa en el hecho de que el contenido de los archivos META-INF/*.SF está protegido por firmas v1. Consulta la sección sobre la verificación de APK firmados con JAR.

Un atacante podría intentar quitar firmas más seguras del bloque del esquema de firma de APK v2. Para mitigar este ataque, la lista de IDs de algoritmos de firma con los que se firmó el APK se almacena en el bloque signed data que está protegido por cada firma.

Verificación

En Android 7.0 y versiones posteriores, los APKs se pueden verificar de acuerdo con el esquema de firma de APK v2+ o la firma JAR (esquema v1). Las plataformas más antiguas ignoran las firmas v2 y solo verifican las firmas v1.

Proceso de verificación de la firma de APK

Figura 4: Proceso de verificación de la firma del APK (pasos nuevos en rojo)

Verificación del esquema de firma de APK v2

  1. Busca el bloque de firma del APK y verifica lo siguiente:
    1. Dos campos de tamaño del bloque de firma de APK contienen el mismo valor.
    2. El directorio central de ZIP está seguido inmediatamente por el registro de fin del directorio central de ZIP.
    3. ZIP End of Central Directory no está seguido de más datos.
  2. Ubica el primer bloque de esquema de firma de APK v2 dentro del bloque de firma de APK. Si el bloque v2 está presente, continúa con el paso 3. De lo contrario, recurre a verificar el APK con el esquema v1.
  3. Para cada signer en el bloque del esquema de firma de APK v2, haz lo siguiente:
    1. Elige el objeto signature algorithm ID compatible más seguro de signatures. El orden de las fortalezas depende de cada versión de implementación/plataforma.
    2. Verifica el signature correspondiente de signatures en signed data con public key. (Ahora es seguro analizar signed data).
    3. Verifica que la lista ordenada de IDs de algoritmo de firma en digests y signatures sea idéntica. (Esto es para evitar la eliminación o adición de firmas).
    4. Calcula el resumen del contenido del APK con el mismo algoritmo de resumen que usa el algoritmo de firma.
    5. Verifica que el resumen calculado sea idéntico al digest correspondiente de digests.
    6. Verifica que SubjectPublicKeyInfo del primer certificate de certificates sea idéntico a public key.
  4. La verificación se realiza correctamente si se encontró al menos un signer y el paso 3 se realizó correctamente para cada signer encontrado.

Nota: No se debe verificar el APK con el esquema v1 si se produce una falla en el paso 3 o 4.

Verificación de APK firmado con JAR (esquema v1)

El APK firmado con JAR es un archivo JAR firmado estándar, que debe contener exactamente las entradas que se enumeran en META-INF/MANIFEST.MF y en el que todas las entradas deben estar firmadas por el mismo conjunto de firmantes. Su integridad se verifica de la siguiente manera:

  1. Cada firmante está representado por una entrada de JAR META-INF/<signer>.SF y META-INF/<signer>.(RSA|DSA|EC).
  2. <signer>.(RSA|DSA|EC) es un ContentInfo de CMS PKCS #7 con estructura SignedData cuya firma se verifica en el archivo <signer>.SF.
  3. El archivo <signer>.SF contiene un resumen de todo el archivo META-INF/MANIFEST.MF y los resúmenes de cada sección de META-INF/MANIFEST.MF. Se verifica el resumen de todo el archivo MANIFEST.MF. Si eso falla, se verifica el resumen de cada sección de MANIFEST.MF.
  4. META-INF/MANIFEST.MF contiene, para cada entrada de JAR protegida por integridad, una sección con el nombre correspondiente que contiene el resumen del contenido sin comprimir de la entrada. Todos estos resúmenes se verifican.
  5. La verificación de APK falla si el APK contiene entradas JAR que no se enumeran en MANIFEST.MF y no forman parte de la firma JAR.

Por lo tanto, la cadena de protección es <signer>.(RSA|DSA|EC) -> <signer>.SF -> MANIFEST.MF -> contenido de cada entrada de JAR protegida por integridad.