Firma de compilaciones para el lanzamiento

Las imágenes del SO Android usan firmas de criptografía en dos lugares:

  1. Se debe firmar cada archivo .apk dentro de la imagen. El Administrador de paquetes de Android usa una firma .apk de dos maneras:
    • Cuando se reemplaza una aplicación, debe estar firmada con la misma clave que la aplicación anterior para obtener acceso a sus datos. Esto es válido tanto para actualizar las apps del usuario reemplazando .apk como para anular una app del sistema con una versión más reciente instalada en /data.
    • Si dos o más aplicaciones quieren compartir un ID de usuario (para que puedan compartir datos, etc.), deben estar firmadas con la misma clave.
  2. Los paquetes de actualización OTA deben estar firmados con una de las claves que espera el sistema, o el proceso de instalación los rechazará.

Liberar claves

El árbol de Android incluye test-keys en build/target/product/security. La compilación de una imagen del SO Android con make firmará todos los archivos .apk con las claves de prueba. Debido a que las claves de prueba se conocen de forma pública, cualquier persona puede firmar sus propios archivos .apk con las mismas claves, lo que les permite reemplazar o usurpar las apps del sistema integradas en tu imagen de SO. Por este motivo, es fundamental firmar cualquier imagen del SO Android que se lance o se implemente de forma pública con un conjunto especial de claves de lanzamiento al que solo tú tienes acceso.

Para generar tu propio conjunto único de claves de lanzamiento, ejecuta estos comandos desde la raíz del árbol de Android:

subject='/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
mkdir ~/.android-certs
for x in releasekey platform shared media networkstack; do \
    ./development/tools/make_key ~/.android-certs/$x "$subject"; \
  done

Se debe cambiar $subject para que refleje la información de tu organización. Puedes usar cualquier directorio, pero asegúrate de elegir una ubicación que tenga una copia de seguridad y sea segura. Algunos proveedores eligen encriptar su clave privada con una frase de contraseña segura y almacenar la clave encriptada en el control de código fuente. Otros almacenan sus claves de lanzamiento en otro lugar, como en una computadora con aislamiento de red.

Para generar una imagen de lanzamiento, usa lo siguiente:

make dist
sign_target_files_apks \
-o \    # explained in the next section
--default_key_mappings ~/.android-certs out/dist/*-target_files-*.zip \
signed-target_files.zip

La secuencia de comandos sign_target_files_apks toma un .zip de archivos de destino como entrada y produce un nuevo .zip de archivos de destino en el que todos los archivos .apk se firmaron con claves nuevas. Las imágenes firmadas recientemente se pueden encontrar en IMAGES/ en signed-target_files.zip.

Firma paquetes OTA

Un archivo ZIP de archivos de destino firmado se puede convertir en un archivo ZIP de actualización inalámbrica firmado con el siguiente procedimiento:
ota_from_target_files \
-k  (--package_key) 
signed-target_files.zip \
signed-ota_update.zip

Firmas y transferencia lateral

La transferencia no omite el mecanismo de verificación normal de la firma del paquete de la recuperación. Antes de instalar un paquete, la recuperación verificará que esté firmada con una de las claves privadas que coincida con las claves públicas almacenadas en la partición de recuperación, al igual que lo haría con un paquete entregado de forma inalámbrica.

Por lo general, los paquetes de actualización recibidos del sistema principal se verifican dos veces: una vez por el sistema principal, con el método RecoverySystem.verifyPackage() en la API de Android, y otra vez por la recuperación. La API de RecoverySystem verifica la firma con las claves públicas almacenadas en el sistema principal, en el archivo /system/etc/security/otacerts.zip (de forma predeterminada). La recuperación compara la firma con las claves públicas almacenadas en el disco RAM de la partición de recuperación, en el archivo /res/keys.

De forma predeterminada, los archivos de destino .zip producidos por la compilación establecen el certificado inalámbrico para que coincida con la clave de prueba. En una imagen publicada, se debe usar un certificado diferente para que los dispositivos puedan verificar la autenticidad del paquete de actualización. Pasar la marca -o a sign_target_files_apks, como se muestra en la sección anterior, reemplaza el certificado de clave de prueba por el certificado de clave de lanzamiento de tu directorio de certificados.

Por lo general, la imagen del sistema y la de recuperación almacenan el mismo conjunto de claves públicas de OTA. Si agregas una clave a solo el conjunto de claves de recuperación, es posible firmar paquetes que solo se pueden instalar mediante sideloading (suponiendo que el mecanismo de descarga de actualizaciones del sistema principal realice correctamente la verificación en otacerts.zip). Puedes especificar claves adicionales que se incluirán solo en la recuperación configurando la variable PRODUCT_EXTRA_RECOVERY_KEYS en la definición de tu producto:

vendor/yoyodyne/tardis/products/tardis.mk
 [...]

PRODUCT_EXTRA_RECOVERY_KEYS := vendor/yoyodyne/security/tardis/sideload

Esto incluye la clave pública vendor/yoyodyne/security/tardis/sideload.x509.pem en el archivo de claves de recuperación para que pueda instalar paquetes firmados con ella. Sin embargo, la clave adicional no se incluye en otacerts.zip, por lo que los sistemas que verifican correctamente los paquetes descargados no invocan la recuperación para los paquetes firmados con esta clave.

Certificados y claves privadas

Cada clave se incluye en dos archivos: el certificado, que tiene la extensión .x509.pem, y la clave privada, que tiene la extensión .pk8. La clave privada debe mantenerse en secreto y es necesaria para firmar un paquete. La clave puede estar protegida por una contraseña. El certificado, en cambio, contiene solo la mitad pública de la clave, por lo que se puede distribuir ampliamente. Se usa para verificar que un paquete haya sido firmado por la clave privada correspondiente.

La compilación estándar de Android usa cinco claves, que se encuentran en build/target/product/security:

testkey
Clave predeterminada genérica para paquetes que no especifican una clave.
de Android
Claves de prueba para paquetes que forman parte de la plataforma principal.
compartido
Clave de prueba para los elementos que se comparten en el proceso de la casa o los contactos.
contenido multimedia
Clave de prueba para paquetes que forman parte del sistema de contenido multimedia o de descarga.
networkstack
Clave de prueba para paquetes que forman parte del sistema de redes. La clave de networkstack se usa para firmar objetos binarios diseñados como componentes del sistema modular . Si las actualizaciones de tu módulo se compilan por separado y se integran como compilaciones previas en la imagen de tu dispositivo, es posible que no necesites generar una clave de pila de red en el árbol de origen de Android.

Los paquetes individuales especifican una de estas claves configurando LOCAL_CERTIFICATE en su archivo Android.mk. (se usa testkey si no se configura esta variable). También puedes especificar una clave completamente diferente por ruta de acceso, p.ej.:

device/yoyodyne/apps/SpecialApp/Android.mk
 [...]

LOCAL_CERTIFICATE := device/yoyodyne/security/special

Ahora, la compilación usa la clave device/yoyodyne/security/special.{x509.pem,pk8} para firmar SpecialApp.apk. La compilación solo puede usar claves privadas que no estén protegidas por contraseña.

Opciones de firma avanzadas

Reemplazo de la clave de firma de APK

La secuencia de comandos de firma sign_target_files_apks funciona en los archivos de destino generados para una compilación. Toda la información sobre los certificados y las claves privadas que se usan en el tiempo de compilación se incluye en los archivos de destino. Cuando se ejecuta la secuencia de comandos de firma para firmar la versión, las claves de firma se pueden reemplazar según el nombre de la clave o el nombre del APK.

Usa las marcas --key_mapping y --default_key_mappings para especificar el reemplazo de claves según los nombres de las claves:

  • La marca --key_mapping src_key=dest_key especifica el reemplazo de una clave a la vez.
  • La marca --default_key_mappings dir especifica un directorio con cinco claves para reemplazar todas las claves en build/target/product/security; equivale a usar --key_mapping cinco veces para especificar las asignaciones.
build/target/product/security/testkey      = dir/releasekey
build/target/product/security/platform     = dir/platform
build/target/product/security/shared       = dir/shared
build/target/product/security/media        = dir/media
build/target/product/security/networkstack = dir/networkstack

Usa la marca --extra_apks apk_name1,apk_name2,...=key para especificar los reemplazos de claves de firma según los nombres de los APK. Si key se deja vacío, la secuencia de comandos trata los APKs especificados como firmados previamente.

Para el producto hipotético tardis, necesitas seis claves protegidas por contraseña: cinco para reemplazar las cinco de build/target/product/security y una para reemplazar la clave adicional device/yoyodyne/security/special que requiere SpecialApp en el ejemplo anterior. Si las claves estaban en los siguientes archivos:

vendor/yoyodyne/security/tardis/releasekey.x509.pem
vendor/yoyodyne/security/tardis/releasekey.pk8
vendor/yoyodyne/security/tardis/platform.x509.pem
vendor/yoyodyne/security/tardis/platform.pk8
vendor/yoyodyne/security/tardis/shared.x509.pem
vendor/yoyodyne/security/tardis/shared.pk8
vendor/yoyodyne/security/tardis/media.x509.pem
vendor/yoyodyne/security/tardis/media.pk8
vendor/yoyodyne/security/tardis/networkstack.x509.pem
vendor/yoyodyne/security/tardis/networkstack.pk8
vendor/yoyodyne/security/special.x509.pem
vendor/yoyodyne/security/special.pk8           # NOT password protected
vendor/yoyodyne/security/special-release.x509.pem
vendor/yoyodyne/security/special-release.pk8   # password protected

Luego, firmarías todas las apps de la siguiente manera:

./build/make/tools/releasetools/sign_target_files_apks \
    --default_key_mappings vendor/yoyodyne/security/tardis \
    --key_mapping vendor/yoyodyne/security/special=vendor/yoyodyne/security/special-release \
    --extra_apks PresignedApp= \
    -o tardis-target_files.zip \
    signed-tardis-target_files.zip

Esto muestra lo siguiente:

Enter password for vendor/yoyodyne/security/special-release key>
Enter password for vendor/yoyodyne/security/tardis/networkstack key>
Enter password for vendor/yoyodyne/security/tardis/media key>
Enter password for vendor/yoyodyne/security/tardis/platform key>
Enter password for vendor/yoyodyne/security/tardis/releasekey key>
Enter password for vendor/yoyodyne/security/tardis/shared key>
    signing: Phone.apk (vendor/yoyodyne/security/tardis/platform)
    signing: Camera.apk (vendor/yoyodyne/security/tardis/media)
    signing: NetworkStack.apk (vendor/yoyodyne/security/tardis/networkstack)
    signing: Special.apk (vendor/yoyodyne/security/special-release)
    signing: Email.apk (vendor/yoyodyne/security/tardis/releasekey)
        [...]
    signing: ContactsProvider.apk (vendor/yoyodyne/security/tardis/shared)
    signing: Launcher.apk (vendor/yoyodyne/security/tardis/shared)
NOT signing: PresignedApp.apk
        (skipped due to special cert string)
rewriting SYSTEM/build.prop:
  replace:  ro.build.description=tardis-user Eclair ERC91 15449 test-keys
     with:  ro.build.description=tardis-user Eclair ERC91 15449 release-keys
  replace: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/test-keys
     with: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/release-keys
    signing: framework-res.apk (vendor/yoyodyne/security/tardis/platform)
rewriting RECOVERY/RAMDISK/default.prop:
  replace:  ro.build.description=tardis-user Eclair ERC91 15449 test-keys
     with:  ro.build.description=tardis-user Eclair ERC91 15449 release-keys
  replace: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/test-keys
     with: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/release-keys
using:
    vendor/yoyodyne/security/tardis/releasekey.x509.pem
for OTA package verification
done.

Después de solicitarle al usuario las contraseñas de todas las claves protegidas por contraseña, la secuencia de comandos vuelve a firmar todos los archivos APK en el destino de entrada .zip con las claves de lanzamiento. Antes de ejecutar el comando, también puedes establecer la variable de entorno ANDROID_PW_FILE en un nombre de archivo temporal. Luego, la secuencia de comandos invocará tu editor para permitirte ingresar contraseñas para todas las claves (esta puede ser una forma más conveniente de ingresar contraseñas).

Reemplazo de la clave de firma de APEX

Android 10 introduce el formato de archivo APEX para instalar módulos del sistema de nivel inferior. Como se explica en Firma de APEX, cada archivo APEX se firma con dos claves: una para la imagen del sistema de archivos en miniatura dentro de un APEX y la otra para todo el APEX.

Cuando firmas la versión, las dos claves de firma para un archivo APEX se reemplazan con claves de lanzamiento. La clave de carga útil del sistema de archivos se especifica con la marca --extra_apex_payload, y la clave de firma de archivos APEX completa se especifica con la marca --extra_apks.

Para el producto tardis, supongamos que tienes la siguiente configuración de claves para los archivos APEX com.android.conscrypt.apex, com.android.media.apex y com.android.runtime.release.apex.

name="com.android.conscrypt.apex" public_key="PRESIGNED" private_key="PRESIGNED" container_certificate="PRESIGNED" container_private_key="PRESIGNED"
name="com.android.media.apex" public_key="PRESIGNED" private_key="PRESIGNED" container_certificate="PRESIGNED" container_private_key="PRESIGNED"
name="com.android.runtime.release.apex" public_key="vendor/yoyodyne/security/testkeys/com.android.runtime.avbpubkey" private_key="vendor/yoyodyne/security/testkeys/com.android.runtime.pem" container_certificate="vendor/yoyodyne/security/testkeys/com.google.android.runtime.release_container.x509.pem" container_private_key="vendor/yoyodyne/security/testkeys/com.google.android.runtime.release_container.pk8"

Y tienes los siguientes archivos que contienen las claves de lanzamiento:

vendor/yoyodyne/security/runtime_apex_container.x509.pem
vendor/yoyodyne/security/runtime_apex_container.pk8
vendor/yoyodyne/security/runtime_apex_payload.pem

El siguiente comando anula las claves de firma de com.android.runtime.release.apex y com.android.tzdata.apex durante la firma de lanzamiento. En particular, com.android.runtime.release.apex se firma con las claves de lanzamiento especificadas (runtime_apex_container para el archivo APEX y runtime_apex_payload para la carga útil de la imagen del archivo). com.android.tzdata.apex se trata como firmado previamente. La configuración predeterminada controla todos los demás archivos APEX, como se indica en los archivos de destino.

./build/make/tools/releasetools/sign_target_files_apks \
    --default_key_mappings   vendor/yoyodyne/security/tardis \
    --extra_apks             com.android.runtime.release.apex=vendor/yoyodyne/security/runtime_apex_container \
    --extra_apex_payload_key com.android.runtime.release.apex=vendor/yoyodyne/security/runtime_apex_payload.pem \
    --extra_apks             com.android.media.apex= \
    --extra_apex_payload_key com.android.media.apex= \
    -o tardis-target_files.zip \
    signed-tardis-target_files.zip

Si ejecutas el comando anterior, se muestran los siguientes registros:

        [...]
    signing: com.android.runtime.release.apex                  container (vendor/yoyodyne/security/runtime_apex_container)
           : com.android.runtime.release.apex                  payload   (vendor/yoyodyne/security/runtime_apex_payload.pem)
NOT signing: com.android.conscrypt.apex
        (skipped due to special cert string)
NOT signing: com.android.media.apex
        (skipped due to special cert string)
        [...]

Otras opciones

La secuencia de comandos de firma sign_target_files_apks reescribe la descripción y la huella digital de la compilación en los archivos de propiedades de compilación para reflejar que la compilación es una compilación firmada. La marca --tag_changes controla qué ediciones se realizan en la huella dactilar. Ejecuta la secuencia de comandos con -h para ver la documentación sobre todas las marcas.

Genera claves de forma manual

Android usa claves RSA de 2,048 bits con exponente público 3. Puedes generar pares de certificado/clave privada con la herramienta openssl desde openssl.org:

# generate RSA key
openssl genrsa -3 -out temp.pem 2048
Generating RSA private key, 2048 bit long modulus
....+++
.....................+++
e is 3 (0x3)

# create a certificate with the public part of the key
openssl req -new -x509 -key temp.pem -out releasekey.x509.pem -days 10000 -subj '/C=US/ST=California/L=San Narciso/O=Yoyodyne, Inc./OU=Yoyodyne Mobility/CN=Yoyodyne/emailAddress=yoyodyne@example.com'

# create a PKCS#8-formatted version of the private key
openssl pkcs8 -in temp.pem -topk8 -outform DER -out releasekey.pk8 -nocrypt

# securely delete the temp.pem file
shred --remove temp.pem

El comando openssl pkcs8 mencionado anteriormente crea un archivo .pk8 sin contraseña sin contraseña, adecuado para usarse con el sistema de compilación. Para crear un archivo .pk8 protegido con una contraseña (lo que debes hacer para todas las claves de lanzamiento reales), reemplaza el argumento -nocrypt por -passout stdin. Luego, openssl encriptará la clave privada con una contraseña leída de la entrada estándar. No se imprime ningún mensaje, por lo que, si stdin es la terminal, el programa parecerá colgarse cuando, en realidad, solo está esperando que ingreses una contraseña. Se pueden usar otros valores para el argumento the-passout para leer la contraseña desde otras ubicaciones. Para obtener más información, consulta la documentación de openssl.

El archivo intermedio temp.pem contiene la clave privada sin ningún tipo de protección con contraseña, por lo que debes desecharlo con cuidado cuando generes claves de lanzamiento. En particular, es posible que la utilidad GNUshred no sea eficaz en sistemas de archivos de red o con registro. Puedes usar un directorio de trabajo ubicado en un disco RAM (como una partición tmpfs) cuando generas claves para asegurarte de que los intermedios no se expongan de forma involuntaria.

Crea archivos de imagen

Cuando tengas signed-target_files.zip, deberás crear la imagen para poder colocarla en un dispositivo. Para crear la imagen firmada a partir de los archivos de destino, ejecuta el siguiente comando desde la raíz del árbol de Android:

img_from_target_files signed-target_files.zip signed-img.zip
El archivo resultante, signed-img.zip, contiene todos los archivos .img. Para cargar una imagen en un dispositivo, usa fastboot de la siguiente manera:
fastboot update signed-img.zip