Firmar compilaciones para su lanzamiento

Las imágenes del sistema operativo Android utilizan firmas criptográficas en dos lugares:

  1. Cada archivo .apk dentro de la imagen debe estar firmado. El Administrador de paquetes de Android utiliza 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 poder acceder a los datos de la aplicación anterior. Esto es válido tanto para actualizar aplicaciones de usuario sobrescribiendo el .apk como para anular una aplicación del sistema con una versión más nueva instalada en /data .
    • Si dos o más aplicaciones quieren compartir una ID de usuario (para poder compartir datos, etc.), deben estar firmadas con la misma clave.
  2. Los paquetes de actualización OTA deben firmarse con una de las claves esperadas por el sistema o el proceso de instalación los rechazará.

Teclas de liberación

El árbol de Android incluye claves de prueba en build/target/product/security . Al crear una imagen del sistema operativo Android usando make se firmarán todos los archivos .apk usando las claves de prueba. Dado que las claves de prueba son de conocimiento público, cualquiera puede firmar sus propios archivos .apk con las mismas claves, lo que puede permitirles reemplazar o secuestrar aplicaciones del sistema integradas en la imagen de su sistema operativo. Por este motivo, es fundamental firmar cualquier imagen del sistema operativo Android publicada o implementada con un conjunto especial de claves de versión a las que solo usted tiene acceso.

Para generar su propio conjunto exclusivo de claves de liberación, ejecute estos comandos desde la raíz de su á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

$subject debe cambiarse para reflejar la información de su organización. Puede utilizar cualquier directorio, pero tenga cuidado de elegir una ubicación que tenga una copia de seguridad y sea segura. Algunos proveedores optan por cifrar su clave privada con una frase de contraseña segura y almacenar la clave cifrada en el control de fuente; otros almacenan sus claves de liberación en otro lugar, como en una computadora con espacio de aire.

Para generar una imagen de lanzamiento, use:

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

El script 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 han firmado con nuevas claves. Las imágenes recién firmadas se pueden encontrar en IMAGES/ en signed-target_files.zip .

Firmar paquetes OTA

Un zip de archivos de destino firmado se puede convertir en un zip de actualización OTA firmado mediante el siguiente procedimiento:
ota_from_target_files \
-k  (--package_key) 
signed-target_files.zip \
signed-ota_update.zip

Firmas y transferencia

La descarga no pasa por alto el mecanismo normal de verificación de la firma del paquete de recuperación: antes de instalar un paquete, la recuperación verificará que esté firmado con una de las claves privadas que coincidan con las claves públicas almacenadas en la partición de recuperación, tal como lo haría con un paquete entregado a través de la partición de recuperación. -aire.

Los paquetes de actualización recibidos del sistema principal generalmente se verifican dos veces: una vez por el sistema principal, usando el método RecoverySystem.verifyPackage() en la API de Android, y luego otra vez por recuperación. La API 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 verifica 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 configuran el certificado OTA para que coincida con la clave de prueba. En una imagen publicada, se debe utilizar un certificado diferente para que los dispositivos puedan verificar la autenticidad del paquete de actualización. Pasar el indicador -o a sign_target_files_apks , como se muestra en la sección anterior, reemplaza el certificado de clave de prueba con el certificado de clave de liberación de su directorio de certificados.

Normalmente, la imagen del sistema y la imagen de recuperación almacenan el mismo conjunto de claves públicas OTA. Al agregar una clave solo al conjunto de claves de recuperación, es posible firmar paquetes que solo se pueden instalar mediante descarga (suponiendo que el mecanismo de descarga de actualizaciones del sistema principal esté realizando correctamente la verificación con otacerts.zip). Puede especificar claves adicionales para que se incluyan solo en la recuperación configurando la variable PRODUCT_EXTRA_RECOVERY_KEYS en la definición de su 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 está incluida en otacerts.zip, por lo que los sistemas que verifican correctamente los paquetes descargados no invocan la recuperación de los paquetes firmados con esta clave.

Certificados y claves privadas

Cada clave viene 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, por el contrario, contiene sólo la mitad pública de la clave, por lo que puede distribuirse ampliamente. Se utiliza para verificar que un paquete ha sido firmado con la clave privada correspondiente.

La compilación estándar de Android utiliza cinco claves, todas las cuales residen en build/target/product/security :

clave de prueba
Clave predeterminada genérica para paquetes que de otro modo no especifican una clave.
plataforma
Clave de prueba para paquetes que forman parte de la plataforma principal.
compartido
Clave de prueba para cosas que se comparten en el proceso de inicio/contactos.
medios de comunicación
Clave de prueba para paquetes que forman parte del sistema de medios/descarga.
pila de red
Clave de prueba para paquetes que forman parte del sistema de red. La clave de networking se utiliza para firmar archivos binarios diseñados como componentes del sistema modular . Si las actualizaciones de su módulo se crean por separado y se integran como elementos precompilados en la imagen de su dispositivo, es posible que no necesite generar una clave de networking en el árbol de código fuente de Android.

Los paquetes individuales especifican una de estas claves configurando LOCAL_CERTIFICATE en su archivo Android.mk. (Se utiliza testkey si esta variable no está configurada). También puede especificar una clave completamente diferente por nombre de ruta, por ejemplo:

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 utilizar claves privadas que no estén protegidas con contraseña.

Opciones de firma avanzadas

Reemplazo de clave de firma de APK

El script 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 utilizadas en el momento de la compilación se incluye en los archivos de destino. Al ejecutar el script de firma para firmar para su lanzamiento, las claves de firma se pueden reemplazar según el nombre de la clave o el nombre del APK.

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

  • El indicador --key_mapping src_key = dest_key especifica el reemplazo de una clave a la vez.
  • El indicador --default_key_mappings dir especifica un directorio con cinco claves para reemplazar todas las claves en build/target/product/security ; es equivalente 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

Utilice el indicador --extra_apks apk_name1,apk_name2,... = key para especificar los reemplazos de claves de firma según los nombres de APK. Si key se deja vacía, el script trata los APK especificados como prefirmados.

Para el producto tardis hipotético, necesita seis claves protegidas con contraseña: cinco para reemplazar las cinco en build/target/product/security y una para reemplazar la clave adicional device/yoyodyne/security/special requerida por SpecialApp en el ejemplo anterior. Si las claves estuvieran 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 aplicaciones de esta 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 trae a colación 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 solicitar al usuario las contraseñas para todas las claves protegidas con contraseña, el script vuelve a firmar todos los archivos APK en el archivo .zip de destino de entrada con las claves de liberación. Antes de ejecutar el comando, también puede configurar la variable de entorno ANDROID_PW_FILE en un nombre de archivo temporal; Luego, el script invoca a su editor para permitirle ingresar contraseñas para todas las claves (esta puede ser una forma más conveniente de ingresar contraseñas).

Reemplazo de clave de firma APEX

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

Al firmar para la liberación, las dos claves de firma de un archivo APEX se reemplazan por claves de liberación. La clave de carga útil del sistema de archivos se especifica con el indicador --extra_apex_payload y toda la clave de firma del archivo APEX se especifica con el indicador --extra_apks .

Para el producto tardis, suponga que tiene la siguiente configuración de clave 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 tiene los siguientes archivos que contienen las claves de liberación:

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 para com.android.runtime.release.apex y com.android.tzdata.apex durante la firma de la versión. En particular, com.android.runtime.release.apex está firmado con las claves de versión 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 prefirmado. Todos los demás archivos APEX se manejan mediante la configuración predeterminada que se enumera 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

Al ejecutar el comando anterior se obtienen 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

El script de firma sign_target_files_apks reescribe la descripción de la compilación y la huella digital en los archivos de propiedades de la compilación para reflejar que la compilación es una compilación firmada. El indicador --tag_changes controla qué ediciones se realizan en la huella digital. Ejecute el script con -h para ver la documentación sobre todos los indicadores.

Generar claves manualmente

Android utiliza claves RSA de 2048 bits con exponente público 3. Puede generar pares de certificado/clave privada utilizando la herramienta openssl de 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 proporcionado anteriormente crea un archivo .pk8 sin contraseña, adecuado para usar con el sistema de compilación. Para crear un .pk8 protegido con una contraseña (lo que debe hacer para todas las claves de liberación reales), reemplace el argumento -nocrypt con -passout stdin ; luego openssl cifrará 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 el terminal, el programa parecerá bloquearse cuando en realidad solo está esperando que ingrese una contraseña. Se pueden usar otros valores para el argumento-passout para leer la contraseña desde otras ubicaciones; Para obtener más detalles, consulte la documentación de openssl .

El archivo intermedio temp.pem contiene la clave privada sin ningún tipo de protección con contraseña, así que deséchelo con cuidado al generar claves de liberación. En particular, es posible que la utilidad GNUshred no sea eficaz en sistemas de archivos de red o registrados por diario. Puede utilizar un directorio de trabajo ubicado en un disco RAM (como una partición tmpfs) al generar claves para garantizar que los intermedios no queden expuestos inadvertidamente.

Crear archivos de imagen

Cuando haya signed-target_files.zip , deberá crear la imagen para poder colocarla en un dispositivo. Para crear la imagen firmada a partir de los archivos de destino, ejecute 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, use fastboot de la siguiente manera:
fastboot update signed-img.zip