Firma e Distribuzione .NET MAUI 10: Keystore Android e Provisioning iOS (2026)
Playbook DevOps completo per firmare e distribuire un'app .NET MAUI 10 nel 2026: keystore Android, provisioning iOS, workflow GitHub Actions pronto all'uso e gotcha su Play Store e App Store.
Firmare un'app .NET MAUI 10 significa produrre due artefatti pronti per gli store: un .aab Android firmato con il tuo keystore JKS e un .ipa iOS firmato con un provisioning profile di distribuzione e un certificato Apple. Nel 2026, con l'obbligo di targetSdkVersion=35 su Google Play e del privacy manifest su App Store, la firma non è più un dettaglio: è il passaggio dove più release si bloccano. Onestamente, questa è la parte del ciclo di rilascio che mi ha fatto perdere più notti negli ultimi due anni, e questa guida è il playbook DevOps che uso in produzione (checklist, comandi dotnet publish e pipeline GitHub Actions incluse).
Android richiede un keystore JKS generato con keytool e conservato fuori dal repository. La prima release su Google Play va caricata manualmente.
iOS richiede un certificato di distribuzione .p12, un provisioning profile App Store e, dal 2024, un privacy manifest completo.
Il comando corretto è dotnet publish -f net10.0-android -c Release con le proprietà di firma passate via CLI, non via .csproj.
In CI/CD, keystore e .p12 vanno passati come segreti base64: mai committati, mai in file di configurazione.
Fastlane Match resta l'opzione più stabile per gestire i profili iOS di più sviluppatori senza scaricarli a mano.
Google Play impone API 35 come minimo per nuove app e aggiornamenti dal 31 agosto 2025; Apple richiede lo SDK di Xcode 16 per le nuove submission dal 2025.
Prerequisiti per firmare un'app .NET MAUI 10
La maggior parte dei team salta questa parte, e poi passa due giorni a debuggare un profilo di provisioning invece di scrivere codice. Ci sono passato di persona più di una volta. Prima di toccare dotnet publish, verifica di avere in mano ogni singolo elemento della checklist: senza uno solo di questi, la pipeline si romperà nel modo più costoso possibile, cioè al momento del caricamento sullo store.
Checklist Android:
JDK 17 installato e JAVA_HOME configurato. .NET MAUI 10 non è compatibile con JDK 21 nelle pipeline Android.
Android SDK con Platform 35 e Build-Tools 35.0.0.
Un account Google Play Developer attivo (una tantum, 25 $) e la prima release già creata come bozza sulla Play Console.
keytool disponibile in PATH (arriva con il JDK).
Checklist iOS:
macOS con Xcode 16.2 o superiore, richiesto per le submission Apple dal 2025.
Apple Developer Program attivo (99 $/anno).
Un App ID e un bundle identifier registrati nel portale sviluppatori (es. com.mia-azienda.myapp).
Un certificato di distribuzione (Apple Distribution) esportato come .p12 con password.
Un provisioning profile di distribuzione App Store scaricato in ~/Library/MobileDevice/Provisioning Profiles/.
File PrivacyInfo.xcprivacy incluso nel bundle iOS del progetto.
Come firmare un'app .NET MAUI per Android
Su Android il flusso è lineare: crei un keystore una volta sola, lo backuppi in un password manager, e da lì in avanti lo passi a ogni build come coppia di variabili. Il keystore Android è un file binario che contiene i certificati e le chiavi private che identificano univocamente la tua app su Google Play, e perderlo significa non poter più pubblicare aggiornamenti con lo stesso pacchetto.
Generare il keystore JKS
Usa il tool keytool del JDK. Passa alias e password come parametri per rendere il comando riproducibile in CI:
Il flag -validity 10000 è il valore raccomandato da Google (circa 27 anni): un keystore che scade prima della durata di vita dell'app è un incidente in attesa di accadere. Il file .keystore risultante non deve mai finire in Git: aggiungilo subito a .gitignore e archivialo in un vault (1Password, Bitwarden, GitHub Secrets, Azure Key Vault).
Firmare con dotnet publish
La firma avviene passando le proprietà MSBuild direttamente al comando dotnet publish. Non mettere password nel .csproj: sono variabili d'ambiente.
Nota tre dettagli non ovvi. AndroidPackageFormat=aab produce l'App Bundle richiesto da Google Play (l'APK non è più accettato per nuove app dal 2021). ApplicationVersion è il versionCode intero e deve incrementare a ogni release: un pattern affidabile è passare il numero di run della pipeline. Infine, ApplicationDisplayVersion è la versionName mostrata all'utente.
Come configurare il provisioning iOS in .NET MAUI 10
Il provisioning iOS è la parte che rompe più build su .NET MAUI. Il modello di firma di Apple lega tre cose (bundle identifier, certificato di distribuzione e provisioning profile) e se una sola non combacia, xcrun rifiuta di firmare l'IPA senza spiegazioni utili. Ci sono due strade: automatica (buona per sviluppo, fragile in CI) e manuale (obbligata per pipeline riproducibili).
Provisioning automatico: solo per sviluppo locale
Nelle proprietà del progetto MAUI, tab iOS > Bundle Signing, seleziona Automatic Provisioning e scegli il team. Visual Studio crea signing identity, App ID e profile in automatico. Questo funziona bene solo su una workstation collegata al tuo Apple ID. In CI non esiste il concetto di Visual Studio, quindi il provisioning automatico non è una strada percorribile per la pipeline.
Provisioning manuale: obbligato per CI/CD
Per le pipeline devi esportare a mano il certificato e scaricare il provisioning profile dal portale sviluppatori Apple. Il certificato di distribuzione va esportato dalla Keychain macOS come .p12 protetto da password. Il profile è un file .mobileprovision.
Nel .csproj del progetto MAUI (o via CLI in CI) imposta il codice di firma esplicito:
CodesignKey è il nome visualizzato del certificato nella Keychain (non l'ID), CodesignProvision è il nome del profile mostrato nel portale. Il flag ArchiveOnBuild=true produce l'archivio Xcode necessario per esportare l'IPA firmato con xcrun.
L'output finisce in bin/Release/net10.0-ios/ios-arm64/publish/MyApp.ipa. Verifica sempre la firma con codesign -dvvv MyApp.ipa prima di caricare: se il team ID o il profile non combaciano, Transporter lo scoprirà solo al momento dell'upload, dopo aver aspettato 15 minuti di validazione. Ci sono passato la scorsa primavera con una release urgente, e non è un'esperienza che consiglio a fine giornata.
Automatizzare la firma con GitHub Actions
Una pipeline CI/CD ben progettata rimuove tre categorie di errori: password digitate male, versioni di build che si sovrappongono, e artefatti costruiti su macchine con SDK diversi. Ecco il workflow che uso come base per progetti .NET MAUI 10, Android su Linux e iOS su macOS 14, entrambi da singolo push su tag v*.
Preparare i segreti
Su GitHub Actions, i file binari (keystore, .p12, .mobileprovision) vanno codificati in base64 prima di essere inseriti nei repository secrets:
Se hai più di uno sviluppatore che genera build locali, considera Fastlane Match per centralizzare i certificati iOS in un repo Git cifrato. Match risolve il problema del "sul mio Mac funziona" con certificati e profili scaricati automaticamente da tutti. Ho scritto una guida separata sui test in .NET MAUI che può stare comodamente prima di questa pipeline, come step di validazione automatica prima della firma.
Pubblicare su Google Play e App Store nel 2026
Firmato l'artefatto, resta il caricamento sugli store, passaggio che nel 2026 ha vincoli diversi rispetto a due anni fa. Sia Google che Apple hanno alzato l'asticella sulla compliance e sul metadata: pipeline che funzionavano nel 2024 vengono rifiutate oggi senza modifiche.
Google Play Console
La prima release deve essere caricata manualmente attraverso la Google Play Console. Google usa quella prima firma come "impronta" del tuo pacchetto: tutti gli aggiornamenti successivi devono essere firmati con lo stesso keystore. Da lì in avanti, puoi automatizzare gli upload via Google Play Developer Publishing API o con action come r0adkll/upload-google-play.
Assets richiesti dallo store listing:
Descrizione breve (max 80 caratteri), descrizione lunga (max 4.000 caratteri)
Icona 512×512 PNG (senza alpha)
Feature graphic 1024×500
Almeno 2 screenshot per smartphone (1080×1920 raccomandato)
URL della privacy policy
Data Safety form compilata (obbligatoria dal 2022 ma bloccante nel 2026)
App Store Connect
Su iOS l'upload avviene con xcrun altool o con Transporter. Il primo upload su una nuova bundle ID va accompagnato dai metadati completi. Attenzione: dal 2025 la validazione include controlli automatici sul privacy manifest e sulle API considerate sensibili. Se manca la dichiarazione per un'API che usi, l'IPA viene rifiutato in fase di validazione, non in review. Il messaggio è criptico ma il fix è: aggiungi la voce corretta a PrivacyInfo.xcprivacy secondo la documentazione ufficiale Apple sui privacy manifest.
Versionamento automatico
Un pattern che funziona bene in produzione è legare ApplicationVersion al numero di run della pipeline (${{ github.run_number }}) e mantenere ApplicationDisplayVersion come SemVer nel .csproj. Questo elimina completamente il "collision" degli upload doppi, un errore comune quando due sviluppatori lanciano una release nello stesso giorno. Chi vuole spingere oltre le ottimizzazioni post-firma può leggere la guida sulle prestazioni .NET MAUI: startup time e dimensione dell'IPA hanno un impatto diretto sulla percentuale di installazioni completate.
Gotcha comuni e come evitarli
Ecco la lista dei problemi che ho visto rompere più pipeline di firma .NET MAUI negli ultimi 12 mesi, con la soluzione a fianco. Tienila come riferimento offline. Nove volte su dieci l'errore che vedrai è già qui dentro.
Sintomo
Causa
Soluzione
"Could not find matching provisioning profile"
Xcode salva i profile con nomi GUID casuali; MSBuild non riesce a matchare per nome
Passa CodesignProvision con il nome esatto dal portale sviluppatori, non il GUID
Upload Google Play rifiutato: "targetSdkVersion troppo basso"
Nuova app o aggiornamento con target < API 35
Imposta <SupportedOSPlatformVersion> e <TargetSdkVersion>35</TargetSdkVersion> nel csproj
Errore "keystore was tampered with" in CI
Password errata o keystore corrotto durante il base64 decode
Usa base64 -w 0 su Linux; verifica con keytool -list -keystore ... in un job separato
App Store rifiuta la validazione: privacy manifest mancante
Il file PrivacyInfo.xcprivacy non è incluso nel bundle iOS
Aggiungilo con <BundleResource Include="Platforms/iOS/PrivacyInfo.xcprivacy" />
versionCode duplicato al secondo upload
ApplicationVersion hard-coded a 1 nel csproj
Sovrascrivi in CI con -p:ApplicationVersion=${{ github.run_number }}
"No installed provisioning profiles match" su macOS runner
Il profile non è nella cartella corretta o non è stato importato
Copialo esplicitamente in ~/Library/MobileDevice/Provisioning Profiles/ prima del publish
Domande frequenti
Come firmo un'app .NET MAUI 10 per Android da riga di comando?
Usa dotnet publish -f net10.0-android -c Release -p:AndroidKeyStore=true passando keystore, alias e password come parametri MSBuild. Il file .aab firmato apparirà in bin/Release/net10.0-android/publish/. Non inserire mai le password nel csproj: usa variabili d'ambiente o secrets della pipeline.
Qual è la differenza tra provisioning automatico e manuale su iOS?
Il provisioning automatico fa gestire a Visual Studio o Xcode i certificati e i profile, comodo per lo sviluppo locale, impossibile in CI dove non esiste una UI Visual Studio. Il provisioning manuale richiede di generare certificati (.p12) e profili (.mobileprovision) a mano dal portale Apple e passarli esplicitamente via CLI: è l'unica strada per pipeline riproducibili.
Perché la mia app .NET MAUI viene rifiutata da Google Play nel 2026?
Le tre cause più comuni sono: targetSdkVersion inferiore a 35 (obbligatorio dal 31 agosto 2025), Data Safety form non compilata, e caricamento di un APK invece di un AAB. Verifica il .csproj e assicurati di usare -p:AndroidPackageFormat=aab.
Posso automatizzare l'upload su App Store senza Fastlane?
Sì, con xcrun altool --upload-app autenticato tramite una App Store Connect API Key (issuer ID + key ID + chiave privata .p8). Fastlane semplifica solo la parte di gestione certificati con Match; l'upload puro è supportato nativamente dagli strumenti Apple e non richiede Ruby.
Cosa succede se perdo il keystore Android della mia app?
Se hai attivato Google Play App Signing (raccomandato per tutte le nuove app), Google conserva la chiave di upload e puoi richiedere un reset dell'upload key contattando il supporto. Se invece hai firmato direttamente con la chiave di distribuzione senza App Signing, l'app è persa: nessun aggiornamento sarà più possibile e dovrai pubblicare un nuovo package con bundle ID diverso, perdendo tutte le installazioni esistenti.
Gli Handler di .NET MAUI 10 sono il ponte tra i controlli cross-platform e le view native. Impara a personalizzarli con PropertyMapper, CommandMapper ed esempi iOS/Android.
Guida pratica al testing di app .NET MAUI nel 2026: unit test dei ViewModel con xUnit e NSubstitute, device test per handler custom, UI test end-to-end con Appium 2.11 e integrazione CI/CD con GitHub Actions, con esempi di codice reali.