diff --git a/.gitignore b/.gitignore index ba033b5..31f34e7 100644 --- a/.gitignore +++ b/.gitignore @@ -89,3 +89,9 @@ android-client/PMA.LocalConfig.inc android-client/theme/theme.RES android-client/theme/theme.res android-client/web/*.res + +# Magasins de cles de signature. Le perdre interdit toute mise a jour de +# l'application deja installee, Android refusant une signature differente : +# il se sauvegarde ailleurs, il ne se versionne pas. +*.jks +*.keystore diff --git a/android-client/PLAN.md b/android-client/PLAN.md index 4b771f7..c1e84d5 100644 --- a/android-client/PLAN.md +++ b/android-client/PLAN.md @@ -561,24 +561,44 @@ tombstones ni génération d'uuid. devenaient des membres inconnus. Invisible tant qu'on ne construit qu'en débogage — ce qui a été le cas jusqu'ici. -### Reste : la signature +### La signature — outillée le 2026-08-22 -Un magasin de clés dédié, jamais la clé de débogage — celle-ci est partagée par -toutes les installations de Delphi, donc n'authentifie rien. Le mot de passe du -magasin appartient à l'utilisateur ; il n'a sa place ni ici ni dans le dépôt. +`build-apk.ps1` sait construire et signer une release. **Pas par Delphi** : + +``` +.ndroid-clientuild-apk.ps1 -Release ` + -Keystore C:\cles\pmserver-release.jks -Alias pmserver +``` + +Le magasin se crée une fois, hors du dépôt : ``` keytool -genkeypair -v -keystore pmserver-release.jks -alias pmserver -keyalg RSA -keysize 4096 -validity 10000 ``` -Puis, dans Delphi : *Project > Options > Provisioning*, configuration **Release**, -pointer le fichier `.jks` et l'alias. Le magasin se range **hors du dépôt** et -se sauvegarde ailleurs : le perdre interdit toute mise à jour de l'application -déjà installée, Android refusant une signature différente. +**Le mot de passe ne touche rien de durable.** Il est demandé par +`Read-Host -AsSecureString` — rien ne s'affiche, rien ne reste dans +l'historique de la console —, passé à `apksigner` par une variable +d'environnement, et retiré dans un `finally` même si le script échoue. -Une release se construit ensuite comme le débogage, en changeant la -configuration ; `android-client/build-apk.ps1` ne connaît que Debug pour -l'instant, et ne saura signer que le jour où le magasin existe. +Trois raisons de ne pas passer par *Project > Options > Provisioning* : +le `.dproj` est versionné, `paclient --apksign` met le mot de passe **sur la +ligne de commande** où n'importe quel processus de la machine le lit, et +l'outillage doit rester lisible dans le dépôt plutôt que caché dans un +réglage d'IDE. `apksigner` appelé en direct accepte `--ks-pass env:`. + +Le script **affiche le certificat utilisé** après signature. Une release signée +par mégarde avec la clé de débogage s'installe et fonctionne : rien ne la +distingue à l'usage, et on ne s'en aperçoit qu'au refus de la première mise à +jour. + +Il **n'installe pas** la release : Android refuse une signature différente sur +un paquet déjà présent, et désinstaller effacerait le cache local et les +réglages de l'appareil. À faire sciemment, pas en effet de bord d'un script. + +`*.jks` et `*.keystore` sont ignorés par git. **Le magasin se sauvegarde +ailleurs** : le perdre interdit toute mise à jour de l'application déjà +installée. --- diff --git a/android-client/build-apk.ps1 b/android-client/build-apk.ps1 index d54dd3e..df56632 100644 --- a/android-client/build-apk.ps1 +++ b/android-client/build-apk.ps1 @@ -7,8 +7,22 @@ # d'avant l'edition, et le pont echouait pour une raison qui n'existait plus. # # -NoInstall compile et empaquette sans toucher au telephone. +# -Release configuration Release, signee avec VOTRE magasin de cles. +# +# LA RELEASE NE S'INSTALLE PAS PAR-DESSUS LE DEBOGAGE. Android refuse une +# signature differente sur un paquet deja installe ; il faut desinstaller +# d'abord, ce qui EFFACE le cache local et les reglages de l'appareil. Le +# script ne le fait donc pas a votre place - il produit l'APK et s'arrete. +# +# .\android-client\build-apk.ps1 -Release ` +# -Keystore C:\cles\pmserver-release.jks -Alias pmserver -param([switch]$NoInstall) +param( + [switch]$NoInstall, + [switch]$Release, + [string]$Keystore, + [string]$Alias = 'pmserver' +) $ErrorActionPreference = 'Stop' $root = Split-Path $PSScriptRoot -Parent @@ -17,9 +31,20 @@ $sdk = 'C:\Users\Public\Documents\Embarcadero\Studio\23.0\CatalogRepository' $adb = "$sdk\AndroidSDK-2525-23.0.51511.6924\platform-tools\Adb.exe" $strip = "$sdk\AndroidNDK-21-23.0.51511.6924\android-ndk-r21\toolchains\" + 'aarch64-linux-android-4.9\prebuilt\windows-x86_64\aarch64-linux-android\bin\strip.exe' -$out = "$root\Android64\Debug" +$cfg = if ($Release) { 'Release' } else { 'Debug' } +$out = "$root\Android64\$cfg" $stage = "$out\pmserverApk" +if ($Release) { + if (-not $Keystore) { throw 'Chemin du magasin de cles requis : -Keystore' } + if (-not (Test-Path $Keystore)) { throw "Magasin introuvable : $Keystore" } + # LU ICI ET NULLE PART AILLEURS. Read-Host -AsSecureString n'affiche rien et + # ne laisse pas la frappe dans l'historique de la console. + $sec = Read-Host "Mot de passe du magasin $([IO.Path]::GetFileName($Keystore))" -AsSecureString + $env:PM_KS_PASS = [Runtime.InteropServices.Marshal]::PtrToStringBSTR( + [Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec)) +} + # 1. La page HTML -> ressource liee dans l'executable. Push-Location "$PSScriptRoot\web" & "$bin\brcc32.exe" web.rc -foweb.res | Out-Null @@ -27,7 +52,7 @@ Pop-Location # 2. Compilation Delphi. $msbuild = "`"$bin\rsvars.bat`" && msbuild `"$root\pmserverApk.dproj`"" -$log = cmd /c "$msbuild /p:Platform=Android64 /p:Config=Debug /t:Build /v:minimal /nologo" 2>&1 +$log = cmd /c "$msbuild /p:Platform=Android64 /p:Config=$cfg /t:Build /v:minimal /nologo" 2>&1 $err = $log | Select-String -Pattern 'error' if ($err) { $err | ForEach-Object { $_.Line }; throw 'compilation echouee' } $log | Select-String -Pattern 'lines,' | ForEach-Object { $_.Line.Trim() } @@ -48,13 +73,29 @@ Copy-Item "$out\pmserverApk.classes\*.dex" "$stage\classes\" -Force # manifeste resout vers un identifiant reel. # # On ne masque donc que CETTE erreur, nommement, et tout autre message remonte. -$deploy = cmd /c "$msbuild /p:Platform=Android64 /p:Config=Debug /t:Deploy /v:minimal /nologo" 2>&1 +$deploy = cmd /c "$msbuild /p:Platform=Android64 /p:Config=$cfg /t:Deploy /v:minimal /nologo" 2>&1 $deploy | Select-String -Pattern 'error' | Where-Object { $_.Line -notmatch 'network_security_config|Unable to execute.*Aapt\.exe' } | ForEach-Object { $_.Line } # 4. aapt + zipalign + apksigner, et remise en place de ce que Deploy efface. -& "$PSScriptRoot\repack-apk.ps1" | Select-Object -Last 1 +try { + if ($Release) { + & "$PSScriptRoot\repack-apk.ps1" -Config Release -Keystore $Keystore -Alias $Alias + } else { + & "$PSScriptRoot\repack-apk.ps1" | Select-Object -Last 1 + } +} finally { + # Le mot de passe ne survit pas au script, meme si celui-ci echoue. + Remove-Item Env:PM_KS_PASS -ErrorAction SilentlyContinue +} + +if ($Release) { + "APK signe : $stage\bin\pmserverApk.apk" + 'Non installe : desinstaller d''abord la version de debogage effacerait le' + + ' coffre local et les reglages.' + return +} if ($NoInstall) { return } diff --git a/android-client/repack-apk.ps1 b/android-client/repack-apk.ps1 index b4d0810..05414c3 100644 --- a/android-client/repack-apk.ps1 +++ b/android-client/repack-apk.ps1 @@ -8,6 +8,21 @@ # remet ce qu'il a efface et on refait aapt / zipalign / signature. # # A lancer depuis la racine du depot, apres un Deploy reussi. +# +# -Config Release empaquette la sortie Release au lieu de Debug +# -Keystore -Alias signe avec CETTE cle au lieu de celle de debogage +# +# LE MOT DE PASSE NE PASSE PAS PAR LA LIGNE DE COMMANDE. paclient --apksign +# l'y met en clair, ou n'importe quel processus de la machine peut le lire. +# apksigner sait lire une variable d'environnement ; on l'appelle donc en +# direct pour une vraie cle, et on ne garde paclient que pour la cle de +# debogage, dont le mot de passe est public et vaut "android". + +param( + [ValidateSet('Debug', 'Release')] [string]$Config = 'Debug', + [string]$Keystore, + [string]$Alias +) $ErrorActionPreference = 'Stop' @@ -15,7 +30,7 @@ $sdk = 'C:\Users\Public\Documents\Embarcadero\Studio\23.0\CatalogRepository\And $bds = 'C:\Program Files (x86)\Embarcadero\Studio\23.0' $jdk = 'C:\Program Files\Eclipse Adoptium\jdk-11.0.15.10-hotspot' $pac = "$bds\bin\paclient.exe" -$p = '.\Android64\Debug\pmserverApk' +$p = ".\Android64\$Config\pmserverApk" # 1. Nos icones par-dessus celles de Delphi : # les proprietes Android_LauncherIcon* et Android_AdaptiveIcon* du .dproj @@ -46,8 +61,27 @@ Remove-Item "$p\bin\pmserverApk.apk" -Force -ErrorAction SilentlyContinue & $pac -u8 --zipalign="$sdk\build-tools\33.0.2\ZipAlign.exe,$p\bin\pmserverApk-unaligned.apk,$p\bin\pmserverApk.apk,4" if ($LASTEXITCODE -ne 0) { throw "zipalign a echoue" } -& $pac -u8 --apksign="$jdk\bin\java.exe,$sdk\build-tools\33.0.2\lib\apksigner.jar,$p\bin\pmserverApk.apk,androiddebugkey,$env:APPDATA\Embarcadero\BDS\23.0\debug.keystore,android,android" -if ($LASTEXITCODE -ne 0) { throw "signature echouee" } +if ($Keystore) { + # LA CLE DE L'UTILISATEUR. Le mot de passe arrive par PM_KS_PASS, pose par + # l'appelant juste avant et retire juste apres : il ne figure ni dans le + # depot, ni dans le .dproj, ni dans une ligne de commande. + if (-not $env:PM_KS_PASS) { throw 'PM_KS_PASS absent - mot de passe du magasin' } + & "$jdk\bin\java.exe" -jar "$sdk\build-tools\33.0.2\lib\apksigner.jar" sign ` + --ks "$Keystore" --ks-key-alias "$Alias" ` + --ks-pass env:PM_KS_PASS --key-pass env:PM_KS_PASS ` + "$p\bin\pmserverApk.apk" + if ($LASTEXITCODE -ne 0) { throw "signature echouee" } + # DIRE AVEC QUOI ON A SIGNE. Une release signee par megarde avec la cle de + # debogage s'installe et fonctionne : rien ne la distingue a l'usage, et on + # ne s'en apercoit qu'au refus de la premiere mise a jour. + & "$jdk\bin\java.exe" -jar "$sdk\build-tools\33.0.2\lib\apksigner.jar" ` + verify --print-certs "$p\bin\pmserverApk.apk" | + Select-String -Pattern 'Signer #1 certificate DN|SHA-256 digest' | + ForEach-Object { $_.Line.Trim() } +} else { + & $pac -u8 --apksign="$jdk\bin\java.exe,$sdk\build-tools\33.0.2\lib\apksigner.jar,$p\bin\pmserverApk.apk,androiddebugkey,$env:APPDATA\Embarcadero\BDS\23.0\debug.keystore,android,android" + if ($LASTEXITCODE -ne 0) { throw "signature echouee" } +} $apk = Get-Item "$p\bin\pmserverApk.apk" "APK {0:N1} Mo" -f ($apk.Length / 1MB)