Cuando se trata de apps macOS estoy seguro de que ya estarás familiarizado con términos como Sandboxing, hardened runtime o Notarización. Después de todo, estos son algunos de los pasos requeridos si tienes pensado distribuir tus apps mediante la Mac App Store. Además, a partir de macOS 15 (Sequoia), Apple ha decidido endurecer incluso más la protección del ejecutable. Por ejemplo, era habitual la posibilidad de hacer Control + clic sobre cualquier app macOS descargada de Internet que incluso no estuviese firmada… y simplemente elegir la opción “Open” en el menú contextual para ejecutarla sin más. Esta opción no estará disponible en Sequoia (aunque aún será posible ejecutar este tipo de apps).
De hecho, Apple recomiendo que notarices tu software incluso si sólo vas a distribuirlo desde tu propio sitio Web; es decir, fuera de la Mac App Store. ¡No te preocupes! Actualmente tienes algunas buenas opciones de terceros que te facilitan el proceso, como por ejemplo App Wrapper de Ohanaware; y a lo largo de este artículo veremos como puedes activar el Sandboxing, el Hardened Runtime y realizar el proceso de Notarización sobre una app sencilla de ejemplo. Por supuesto, este artículo sólo se centrará en los fundamentos… de modo que tendrás que leer la documentación disponible en el sitio Web de Apple para añadir las entradas, tanto de los Entitlements como los pares de Clave/Valor adicionales del archivo Info.Plist específicos para el funcionamiento de tu app; como por ejemplo puedan ser los relativos al acceso de archivos, cámara, micrófono, acceso de red, etc.
Algo de Información Previa
Pero probablemente tu cabeza esté dando vueltas en este momento en el caso de que nunca antes hayas oído hablar de términos como Sandbox, hardened runtime o notarización, y el significado que dichos términos tienen cuando se aplican a las apps de macOS.
Sandboxing
Cuando se activa el Sandboxing a una app macOS significa que macOS creará un contenedor exclusivo para todo lo relacionado con la app la primera vez que la ejecutes. Esto también es lo que ocurre cuando instalas una app de iOS, por ejemplo. Por tanto, dicho contendor tendrá su propia estructura de archivos para acceder a cosas como Documentos, Imágenes, Descargas, etc.
Por supuesto existen entradas de entitlements que te están esperando para que tu app con sandboxing pueda acceder a los archivos creados por otras apps, entre otras capacidades.
Hardened Runtime
Cuando está activado en tu app macOS se añade una capa adicional de protección al código en ejecución propiamente dicho. Por ejemplo, esto previene determinadas clases de exploits, como la inyección de código, suplantación en el linkado dinámico de librerías (DLL) y accesos no deseados de memoria. Este tipo de protección también está mejorado por la tecnología SIP de macOS (System Integrity Protection).
Notarización
De forma breve, consiste en una tercera capa de confianza para los potenciales usuarios de tu app macOS.
Cuando se notariza la app, se garantiza al usuario que el software firmado y distribuido con un certificado de desarrollador ha sido comprobado por Apple en busca de componentes maliciosos. Este proceso no tiene relación con el proceso de revisión de la app realizado cuando se envía para su distribución en la Mac App Store, sino más bien relacionado con la tecnología Gatekeeper de macOS. Por tanto, cuando por ejemplo una app notarizada se descarga desde internet, Gatekeeper utilizará el ticket de notarizado adjuntado a la app para proporcionar información de utilidad sobre la procedencia de la app, incluyendo si es seguro ejecutarla.
Los Preparativos
Para seguir este artículo necesitarás:
- Xojo. Descarga el IDE para macOS en el caso de que no lo hayas hecho ya.
- Xcode 14 o posterior. Ejecútalo aunque sólo sea una vez para asegurarte de que se han instalado todos los componentes y SDKs requeridos.
- Certificado de Apple Developer ID. Esto precisa de una membresía de pago en el Programa de Desarrolladores de Apple. Asegúrate de que el certificado esté instalado en tu Mac.
- Conexión activa a Internet.
Con los anteriores requerimientos satisfechos, abre Xojo para crear un proyecto Desktop para macOS y crea un diseño básico de interfaz de usuario en la ventana por omisión (Window1).
No es necesario que cuente con ningún tipo de funcionalidad, dado que nos centraremos en el tema a tratar en este artículo. A continuación, utiliza la opción Build Settings > macOS > Mac App Name para dar un nombre apropiada a la aplicación compilada (en este ejemplo he utilizado: “SandboxedApp”).
Por último, guarda el proyecto (por ejemplo en la carpeta Documentos) y haz clic en el botón Build del IDE de Xojo para crear la app. No es preciso en este punto que introduzcas el Developer ID en la sección Build Settings > macOS > Sign, dado que vamos a firmar el bundle de nuevo durante los siguientes pasos.
Crear el Archivo Entitlements
El archivo de entitlements es muy parecido al archivo Info.Plist que probablemente ya conozcas, y es que está encargado de almacenar los pares de claves y valores requeridos para que la app funcione correctamente. El contenido de ambos archivos está en formato XML, y la única diferencia es que mientras que el archivo Info.Plist es creado de forma automática por el IDE de Xojo, el archivo de Entitlements necesita ser creado, por ahora, de forma manual.
Por tanto, abre tu editor de textos favorito (hay una buena cantidad de ellos, tanto gratuitos como de pago; personalmente tiendo a utilizar BBEdit de BareBones Software). Añade las siguientes líneas al documento de texto y guárdalo con el nombre “Entitlements.plist” (si lo guardas en la misma ubicación que la app macOS de ejemplo, mejor). Este es el archivo donde probablemente querrás añadir más entradas de entitlements y capacidades a medida que los requiera tu app:
<?xml version=”1.0″ encoding=”UTF-8″?>
<!DOCTYPE plist SYSTEM “file://localhost/System/Library/DTDs/PropertyList.dtd”>
<plist version=”0.9″>
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
Activar el Sandbox en tu App
Con la app ya creada como bundle y el archivo de entitlements en sus ubicaciones, abre una nueva ventana o pestaña en la app Terminal y escribe el siguiente comando, pulsando la tecla Retorno a continuación:
> codesign –force –deep –timestamp –entitlements <path-to-your-entitlements.plist-file> -s “Developer ID Application: <your-full-developer-name (including-the-team-id)>” <path-to-the-bundle-of-your-app>
Una vez ejecutado el comando, ejecuta la app “SandboxedApp”, abre la app Monitor de Actividad y asegúrate de que esté activa la opción Sandbox en el menú Ver > Columnas. Luego, utiliza el campo de búsqueda para filtrar los contenidos del listado de modo que sólo se muestra la app SandboxedApp. Echa un vistazo al valor bajo la columna Sandbox y deberías de ver que, ahora, la app tiene activado el Sanboxing y que macOS ha creado un nuevo Container para ella en la ruta Library/Containers:
Hardened Runtime
Con el sandboxing de nuestra app activado, echemos ahora un vistazo a lo fácil que resulta activar el hardened runtime. Nuevamente, escribe el siguiente comando en el prompt de la ventana del Terminal:
> codesign –force –deep –options runtime –timestamp –entitlements <path-to-your-entitlements.plist-file> -s “Developer ID Application: <your-full-developer-name (including-the-team-id)>” <path-to-the-bundle-of-your-app>
Como puedes ver no varía mucho en comparación con el anterior comando. Todo lo que añade es el texto “–options runtime” a cargo de activar el hardened runtime. De nuevo, y como puedes imaginar, el uso de este comando también activa el sandboxing para la app, todo a la vez.
¿Quieres comprobar si ha funcionado? Bueno, escribe el siguiente comando en el Terminal:
> codesign –display –verbose <path-to-the-bundle-of-your-app>
Producirá un resultado similar al siguiente:
Executable=<path-to-the-executable>
Identifier=com.xojo.sandboxedapp
Format=app bundle with Mach-O universal (x86_64 arm64)
CodeDirectory v=20500 size=43297 flags=0x10000(runtime) hashes=1342+7 location=embedded
Signature size=9100
Timestamp=13 Aug 2024 at 12:51:28 PM
Info.plist entries=15
TeamIdentifier=************
Runtime Version=11.1.0
Sealed Resources version=2 rules=13 files=4
Internal requirements count=1 size=184
Es el texto “flags=0x1000(runtime)” el que nos indica que, de hecho, se ha aplicado el hardened runtime a la app de ejemplo. ¡Enhorabuena!
Notarizar la App
Este es el paso final, pero va a requerir de algunas acciones adicionales por tu parte. Dado que el comando notarytool, empleado para notarizar la app, requiere del ID y contraseña correspondientes a tu cuenta de Apple ID, además del hecho de que utiliza autenticación 2FA, es muy conveniente crear una contraseña específica para la aplicación.
Crear una Contraseña Específica de Aplicación
Sigue estos pasos para crear una contraseña que pueda ser utilizada por el comando notarytool:
- Entra con tus credenciales en appleid.apple.com
- En la sección Sign-in and Security, selecciona la opción App-Specific Passwords:
- La anterior acción abrirá un nuevo diálogo mostrando todas las contraseñas específicas de app que ya se hubiesen creado previamente. Haz clic en el botón “+” para añadir una nueva entrada:
- Teclea un nombre significativo como el “Título” o descripción para la nueva contraseña en el diálogo presentado (“notarytool” podría ser uno bueno):
- Cuando hagas clic en el botón Create es posible que debas de autenticarte de nuevo utilizando tu Apple ID. Cuando esté hecho, se presentará un nuevo diálogo con la contraseña generada. Cópiala y escríbela (o pégala) en un lugar seguro, dado que tendremos que utilizarla en un paso posterior.
Añadir la Contraseña Específica de Notarytool al Llavero
Dado que la contraseña específica de app se utilizará con el comando notarytool sería muy conveniente almacenarla en el Llavero de macOS. Para ello, introduce el siguiente comando en el Terminal y pulsa la tecla Retorno:
> xcrun notarytool store-credentials “notarytool-password” –apple-id “<your-apple-ID>” –team-id your-developer-team-id –password <the-password-copied-from-the-previous-step>
Una vez ejecutado podrás ver que la contraseña ha sido añadida en la app Llavero bajo el nombre de “notarytool-password”:
Crear un archivo Zip para tu App
El proceso de notarización se lleva a cabo mediante el proceso Apple notary Service que se ejecuta en los servidores de Apple, lo que significa que necesitamos enviar (subir) el bundle de nuestra app en el formato adecuado. Existen dos opciones: como archivo DMG (que precisa ser firmado con el certificado), o bien como un archivo Zip, lo cual resulta más sencillo y rápido (Consejo: ¿Sabías lo sencillo que resulta crear archivos Zip en código Xojo?)
Por tanto, para subir nuestra app para su notarización hemos de crear en primer lugar un archivo Zip. Nuevamente, introduce el siguiente comando en el Terminal:
> /usr/bin/ditto -c -k –keepParent <path-to-app-bundle> <path-to-zip-file/file-name.zip>
Subir la App para Notarizarla
Con el archivo Zip ya creado, ya tenemos todo lo necesario para enviar la app al proceso de notarización. El tiempo requerido para ello variará en función de múltiples factores.
Para enviar el archivo, introduce el siguiente comando en la ventana del Terminal:
> xcrun notarytool submit <path-to-zip-file/file-name.zip> –keychain-profile “notarytool-password” –wait
Tras pulsar la tecla Retorno, comenzará el proceso y el Terminal te mostrará información sobre su progreso; algo similar a esto:
Conducting pre-submission checks for <name-of-your-zip-file> and initiating connection to the Apple notary service…
Submission ID received
id: <some-id-number-goes-here>
Upload progress: 100.00% (8.65 MB of 8.65 MB)
Successfully uploaded file
id: <some-id-number-goes-here>
path: <path-of-the-zip-file>
Waiting for processing to complete.
Current status: Accepted……..
Processing complete
id: <keep-this-id-in-a-safe-place-you-will-need-it-later>
status: Accepted
¿Te has percatado de la última línea? “status: Accepted” ¿Significa que todo ha ido bien y que el proceso de notarización ha tenido éxito? ¡Mejor si lo comprobamos! Escribe el siguiente comando en la línea del Terminal. Este solicitará a notarytool que descargue el archivo de log en formato JSON en la ruta que le indiquemos. Es un buen hábito hacerlo, dado que dicho archivo incluirá cualquier error eventual detectado durante el proceso de notarización, incluyendo aquellos relacionados con la propia app:
> xcrun notarytool log <put-here-the-value-you-saved-in-a-secure-place-from-the-id-field-in-the-previous-output> –keychain-profile “notarytool-password” <path-to-save-the-log.json>
¡Grapa el Ticket!
Asumiendo que todo haya funcionado correctamente, es momento de grapar el ticket de notarización sobre la app propiamente dicha. No es requerido, pero sí que resulta conveniente para evitar comprobaciones en línea cuando se ejecute la app, o bien cuando sea chequeada por Gatekeeper.
Sip, esto significa utilizar un nuevo comando desde el Terminal sobre el bundle de la app sobre el que ya hemos activado el sandboxing, y el hardened runtime (no sobre el archivo Zip creado para enviarlo utilizando notarytool):
> xcrun stapler staple “<path-to-the-signed-sandboxed-and-hardened-app-bundle>”
Tras ello, puedes comprobar que todo ha ido bien utilizando el siguiente comando:
spctl -a -vvv -t install <path-to-the-signed-sandboxed-and-hardened-app-bundle>
Y deberías de obtener una salida similar a la siguiente:
source=Notarized Developer ID
origin=<your-full-developer-ID-Application>
Distribución
Muy bien, pero… probablemente querrás distribuir tu app desde tu sitio Web utilizando un contenedor (archivo) DMG. En tal caso, sólo tendrás que seguir estos pasos adicionales:
- Crea un archivo DMG.
- Copia el bundle de tu app ya notarizado en el DMG.
- Notariza el archivo DMG.
- Grapa el ticket en el archivo DMG.
De esta forma tanto el contendor DMG como el bundle de tu app estarán debidamente notarizados.
En Resumen
Como hemos visto, todo el proceso de activar el Sandboxing, activar el hardening en el runtime y realizar el proceso de notarización requieren de una buena cantidad de comandos ejecutados desde el Terminal, incluyendo también la creación del archivo Zip. Pero la buena noticia es que todo el proceso puede automatizarse… ¡utilizando Xojo! (echa un vistazo a la clase Shell) y al método Zip de la clase FolderItem en el supuesto de que no estuvieses familiarizado aún con estos.
Como se indicó previamente, este artículo sólo se ha centrado en los fundamentos y no profundiza o toca temas como la creación de Perfiles de Provisionamiento (asociados con las capacidades de la app), los Entitlementes que pueda requerir una app en particular para que funcione debidamente, o bien otros temas afines; de modo que probablemente querrás echar un vistazo a los siguientes enlaces recomendados, correspondientes a la Documentación para Desarrolladores de Apple:
¡Feliz programación con Xojo!