From e9e4cd6b3c5f58ee832f3098aa1f0bd7ed12fdc7 Mon Sep 17 00:00:00 2001 From: Pikachu Ren <40362270+PIKACHUIM@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:39:14 +0800 Subject: [PATCH] add(ci): support core build & signing by cert (#166) * add(ci): github action & signing * add(ci): github action & signing * add(ci): github action & signing * add(ci): github action & signing * add(ci): github action & signing * add(ci): github action & signing * add(ci): github action & signing * add(ci): github action & signing * add(ci): github action & signing * add(ci): github action & signing * add(ci): github action & signing * add(ci): github action & signing * chore: update signature CI * fix(ci): sign windows binaries with direct CERTUM SHA1 * fix(ci): replace cert-store wait with simplysign readiness check * feat(ci): refactor certum signing flow for release * fix(ci): fail certum auth when session not ready * fix(ci): retry certum auth and fail critical steps * fix(ci): add cert preflight checks before signtool * fix(install): update SimplySign Desktop MSI download link to version 9.4.3.90 * fix(ci): mask totp and certificate identifiers in scripts * fix(ci): remove certificate inventory logs from release * add(ci): CHANGE beta release * feat(ci): resolve certum msi url dynamically from download page * add(ci): github action & signing * add(ci): github action & signing * revert(ci): app/app/build.gradle for build * add(ci): github action & signing * add(ci): github action & signing * Update default URLs for binary file downloads --------- Co-authored-by: Suyunmeng Co-authored-by: OpenP2P <89245779+TenderIronh@users.noreply.github.com> --- .../scripts/configure-simplysign-registry.ps1 | 252 +++++++++ .../scripts/connect-simplySign-enhanced.ps1 | 390 ++++++++++++++ .github/scripts/install-simplysign.sh | 138 +++++ .github/scripts/sign-windows.ps1 | 182 +++++++ .github/workflows/release.yml | 510 ++++++++++++++++++ .github/workflows/signfile.yml | 171 ++++++ 6 files changed, 1643 insertions(+) create mode 100644 .github/scripts/configure-simplysign-registry.ps1 create mode 100644 .github/scripts/connect-simplySign-enhanced.ps1 create mode 100644 .github/scripts/install-simplysign.sh create mode 100644 .github/scripts/sign-windows.ps1 create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/signfile.yml diff --git a/.github/scripts/configure-simplysign-registry.ps1 b/.github/scripts/configure-simplysign-registry.ps1 new file mode 100644 index 0000000..5e04a3c --- /dev/null +++ b/.github/scripts/configure-simplysign-registry.ps1 @@ -0,0 +1,252 @@ +param( + [switch]$DebugMode = $false, + [switch]$VerifyOnly = $false +) + +# SimplySign Desktop Registry Configuration Script +# Pre-configures optimal registry settings for automated login dialog display + +Write-Host "=== SimplySign Desktop Registry Configuration ===" + +if ($DebugMode) { + Write-Host "Debug mode enabled - verbose logging active" +} + +# Registry path for SimplySign Desktop settings +$RegistryPath = "HKCU:\Software\Certum\SimplySign" + +# Optimal configuration values for automation +$OptimalSettings = @{ + "ShowLoginDialogOnStart" = 1 + "ShowLoginDialogOnAppRequest" = 1 + "RememberLastUserName" = 1 + "Autostart" = 0 + "UnregisterCertificatesOnDisconnect" = 0 + "RememberPINinCSP" = 1 + "ForgetPINinCSPonDisconnect" = 1 + "LangID" = 9 +} + +# Function to check if registry path exists +function Test-RegistryPath { + param([string]$Path) + + try { + $null = Get-Item -Path $Path -ErrorAction Stop + return $true + } catch { + return $false + } +} + +# Function to get current registry value +function Get-RegistryValue { + param( + [string]$Path, + [string]$Name + ) + + try { + $value = Get-ItemProperty -Path $Path -Name $Name -ErrorAction Stop + return $value.$Name + } catch { + return $null + } +} + +# Function to set registry value safely +function Set-RegistryValue { + param( + [string]$Path, + [string]$Name, + [int]$Value + ) + + try { + Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type DWord -ErrorAction Stop + if ($DebugMode) { + Write-Host " Set $Name = $Value" + } + return $true + } catch { + Write-Host " ERROR: Failed to set $Name = $Value - $($_.Exception.Message)" + return $false + } +} + +# Function to display current settings +function Show-CurrentSettings { + Write-Host "Current SimplySign Desktop registry settings:" + Write-Host "=============================================" + + if (-not (Test-RegistryPath $RegistryPath)) { + Write-Host "Registry path does not exist: $RegistryPath" + return + } + + foreach ($setting in $OptimalSettings.Keys) { + $currentValue = Get-RegistryValue -Path $RegistryPath -Name $setting + if ($null -eq $currentValue) { + Write-Host " $setting : NOT SET" + } else { + Write-Host " $setting : $currentValue" + } + } + Write-Host "" +} + +# Function to create registry structure +function Initialize-RegistryStructure { + Write-Host "Initializing registry structure..." + + # Create parent keys if they don't exist + $ParentPaths = @( + "HKCU:\Software\Certum", + $RegistryPath + ) + + $allCreated = $true + foreach ($path in $ParentPaths) { + if (-not (Test-RegistryPath $path)) { + try { + New-Item -Path $path -Force -ErrorAction Stop | Out-Null + if ($DebugMode) { + Write-Host " Created registry path: $path" + } + } catch { + Write-Host " ERROR: Failed to create registry path: $path - $($_.Exception.Message)" + $allCreated = $false + } + } else { + if ($DebugMode) { + Write-Host " Registry path exists: $path" + } + } + } + + return $allCreated +} + +# Function to apply optimal configuration +function Set-OptimalConfiguration { + Write-Host "Applying optimal configuration for automation..." + + $successCount = 0 + $totalSettings = $OptimalSettings.Count + + foreach ($setting in $OptimalSettings.Keys) { + $value = $OptimalSettings[$setting] + if (Set-RegistryValue -Path $RegistryPath -Name $setting -Value $value) { + $successCount++ + } + } + + Write-Host "Applied $successCount of $totalSettings settings successfully" + return ($successCount -eq $totalSettings) +} + +# Function to verify configuration +function Test-Configuration { + Write-Host "Verifying configuration..." + + $verificationResults = @{} + $allCorrect = $true + + foreach ($setting in $OptimalSettings.Keys) { + $expectedValue = $OptimalSettings[$setting] + $actualValue = Get-RegistryValue -Path $RegistryPath -Name $setting + + $isCorrect = ($actualValue -eq $expectedValue) + $verificationResults[$setting] = @{ + Expected = $expectedValue + Actual = $actualValue + Correct = $isCorrect + } + + if (-not $isCorrect) { + $allCorrect = $false + } + + if ($DebugMode -or -not $isCorrect) { + $status = if ($isCorrect) { "OK" } else { "MISMATCH" } + Write-Host " $setting : Expected=$expectedValue, Actual=$actualValue [$status]" + } + } + + return $verificationResults, $allCorrect +} + +# Main execution +try { + Write-Host "Starting registry configuration process..." + Write-Host "" + + # Show current state + Write-Host "BEFORE CONFIGURATION:" + Show-CurrentSettings + + if ($VerifyOnly) { + Write-Host "Verification-only mode - no changes will be made" + $verificationResults, $allCorrect = Test-Configuration + + if ($allCorrect) { + Write-Host "SUCCESS: All settings are correctly configured" + exit 0 + } else { + Write-Host "CONFIGURATION NEEDED: Some settings require adjustment" + exit 1 + } + } + + # Initialize registry structure + if (-not (Initialize-RegistryStructure)) { + Write-Host "FATAL ERROR: Failed to initialize registry structure" + exit 1 + } + + # Apply optimal configuration + if (-not (Set-OptimalConfiguration)) { + Write-Host "ERROR: Failed to apply complete configuration" + exit 1 + } + + Write-Host "" + Write-Host "AFTER CONFIGURATION:" + Show-CurrentSettings + + # Verify the configuration was applied correctly + $verificationResults, $allCorrect = Test-Configuration + + if ($allCorrect) { + Write-Host "SUCCESS: Registry configuration completed successfully" + Write-Host "" + Write-Host "Key automation settings enabled:" + Write-Host " ShowLoginDialogOnStart = 1 (Login dialog will appear automatically)" + Write-Host " ShowLoginDialogOnAppRequest = 1 (Dialog appears when apps request access)" + Write-Host " RememberLastUserName = 1 (Username persistence for efficiency)" + Write-Host "" + Write-Host "Next steps:" + Write-Host "1. Launch SimplySign Desktop" + Write-Host "2. Login dialog should appear automatically" + Write-Host "3. Complete authentication process" + + # Create a status file for the workflow to check + "REGISTRY_CONFIGURATION_SUCCESS" | Out-File -FilePath "registry_config_status.log" -Encoding UTF8 + + exit 0 + } else { + Write-Host "ERROR: Configuration verification failed" + Write-Host "Some settings were not applied correctly" + + "REGISTRY_CONFIGURATION_PARTIAL" | Out-File -FilePath "registry_config_status.log" -Encoding UTF8 + + exit 1 + } + +} catch { + Write-Host "FATAL ERROR: Registry configuration failed - $($_.Exception.Message)" + + "REGISTRY_CONFIGURATION_FAILED" | Out-File -FilePath "registry_config_status.log" -Encoding UTF8 + + exit 1 +} diff --git a/.github/scripts/connect-simplySign-enhanced.ps1 b/.github/scripts/connect-simplySign-enhanced.ps1 new file mode 100644 index 0000000..bfb2c8b --- /dev/null +++ b/.github/scripts/connect-simplySign-enhanced.ps1 @@ -0,0 +1,390 @@ +# Connect-SimplySign-Enhanced.ps1 +# Registry-Enhanced TOTP Authentication for SimplySign Desktop +# Uses registry pre-configuration + TOTP credential injection approach + +param( + [string]$OtpUri = $env:CERTUM_OTP_URI, + [string]$UserId = $env:CERTUM_USERNAME, + [string]$ExePath = $env:CERTUM_EXE_PATH, + [string]$ExpectedCertificateSHA1 = $env:CERTUM_CERTIFICATE_SHA1 +) + +function Normalize-Sha1 { + param([string]$InputSha1) + if (-not $InputSha1) { + return $null + } + return ($InputSha1 -replace "[^a-fA-F0-9]", "").ToUpperInvariant() +} + +function Find-CertificateByThumbprint { + param([string]$Thumbprint) + + if (-not $Thumbprint) { + return @() + } + + $all = Get-ChildItem -Path "Cert:\CurrentUser\My", "Cert:\LocalMachine\My" -ErrorAction SilentlyContinue + # 对证书库中的 Thumbprint 同样做规范化(去除不可见字符、统一大写),避免 BOM 或格式差异导致匹配失败 + return @($all | Where-Object { + $normalizedStoreThumbprint = ($_.Thumbprint -replace "[^a-fA-F0-9]", "").ToUpperInvariant() + $normalizedStoreThumbprint -eq $Thumbprint + }) +} + +# Validate required parameters +if (-not $OtpUri) { + Write-Host "ERROR: CERTUM_OTP_URI environment variable not provided" + exit 1 +} + +if (-not $UserId) { + Write-Host "ERROR: CERTUM_USERNAME environment variable not provided" + exit 1 +} + +if (-not $ExePath) { + $ExePath = "C:\Program Files\Certum\SimplySign Desktop\SimplySignDesktop.exe" +} + +Write-Host "=== REGISTRY-ENHANCED TOTP AUTHENTICATION ===" +Write-Host "Using registry pre-configuration + credential injection" +Write-Host "OTP URI provided (length: $($OtpUri.Length))" +Write-Host "User ID: $UserId" +Write-Host "Executable: $ExePath" +Write-Host "" + +# Verify SimplySign Desktop exists +if (-not (Test-Path $ExePath)) { + Write-Host "ERROR: SimplySign Desktop not found at: $ExePath" + exit 1 +} + +# Parse the otpauth:// URI +$uri = [Uri]$OtpUri + +# Parse query parameters (compatible with both PowerShell 5.1 and 7+) +try { + $q = [System.Web.HttpUtility]::ParseQueryString($uri.Query) +} catch { + $q = @{} + foreach ($part in $uri.Query.TrimStart('?') -split '&') { + $kv = $part -split '=', 2 + if ($kv.Count -eq 2) { + $q[$kv[0]] = [Uri]::UnescapeDataString($kv[1]) + } + } +} + +$Base32 = $q['secret'] +$Digits = if ($q['digits']) { [int]$q['digits'] } else { 6 } +$Period = if ($q['period']) { [int]$q['period'] } else { 30 } +$Algorithm = if ($q['algorithm']) { $q['algorithm'].ToUpper() } else { 'SHA256' } + +# Validate supported algorithms +$SupportedAlgorithms = @('SHA1', 'SHA256', 'SHA512') +if ($Algorithm -notin $SupportedAlgorithms) { + Write-Host "ERROR: Unsupported algorithm: $Algorithm. Supported: $($SupportedAlgorithms -join ', ')" + exit 1 +} + +# TOTP Generator (inline C# implementation) +Add-Type -Language CSharp @" +using System; +using System.Security.Cryptography; + +public static class Totp +{ + private const string B32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + + private static byte[] Base32Decode(string s) + { + s = s.TrimEnd('=').ToUpperInvariant(); + int byteCount = s.Length * 5 / 8; + byte[] bytes = new byte[byteCount]; + + int bitBuffer = 0, bitsLeft = 0, idx = 0; + foreach (char c in s) + { + int val = B32.IndexOf(c); + if (val < 0) throw new ArgumentException("Invalid Base32 char: " + c); + + bitBuffer = (bitBuffer << 5) | val; + bitsLeft += 5; + + if (bitsLeft >= 8) + { + bytes[idx++] = (byte)(bitBuffer >> (bitsLeft - 8)); + bitsLeft -= 8; + } + } + return bytes; + } + + private static HMAC GetHmacAlgorithm(string algorithm, byte[] key) + { + switch (algorithm.ToUpper()) + { + case "SHA1": + return new HMACSHA1(key); + case "SHA256": + return new HMACSHA256(key); + case "SHA512": + return new HMACSHA512(key); + default: + throw new ArgumentException("Unsupported algorithm: " + algorithm); + } + } + + public static string Now(string secret, int digits, int period, string algorithm = "SHA256") + { + byte[] key = Base32Decode(secret); + long counter = DateTimeOffset.UtcNow.ToUnixTimeSeconds() / period; + + byte[] cnt = BitConverter.GetBytes(counter); + if (BitConverter.IsLittleEndian) Array.Reverse(cnt); + + byte[] hash; + using (var hmac = GetHmacAlgorithm(algorithm, key)) + { + hash = hmac.ComputeHash(cnt); + } + + int offset = hash[hash.Length - 1] & 0x0F; + int binary = + ((hash[offset] & 0x7F) << 24) | + ((hash[offset + 1] & 0xFF) << 16) | + ((hash[offset + 2] & 0xFF) << 8) | + (hash[offset + 3] & 0xFF); + + int otp = binary % (int)Math.Pow(10, digits); + return otp.ToString(new string('0', digits)); + } +} +"@ + +function Get-TotpCode { + param([string]$Secret, [int]$Digits = 6, [int]$Period = 30, [string]$Algorithm = 'SHA256') + [Totp]::Now($Secret, $Digits, $Period, $Algorithm) +} + +# Add Win32 API for force foreground window +Add-Type @" +using System; +using System.Runtime.InteropServices; + +public static class Win32 { + [DllImport("user32.dll")] + public static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + [DllImport("user32.dll")] + public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + + [DllImport("user32.dll")] + public static extern bool AllowSetForegroundWindow(int dwProcessId); + + public const int SW_RESTORE = 9; + public const int SW_SHOW = 5; +} +"@ + +# 预先验证证书 SHA1 +$normalizedExpectedSha1 = Normalize-Sha1 -InputSha1 $ExpectedCertificateSHA1 +if ($normalizedExpectedSha1) { + if ($normalizedExpectedSha1.Length -ne 40) { + Write-Host "ERROR: CERTUM_CERTIFICATE_SHA1 is invalid after normalization" + Write-Host "Raw length: $($ExpectedCertificateSHA1.Length), normalized length: $($normalizedExpectedSha1.Length)" + exit 1 + } +} + +# === 认证重试循环(最多 10 次) === +$maxAttempts = 10 +$authSuccess = $false + +for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { + Write-Host "" + Write-Host "==========================================" + Write-Host "=== AUTHENTICATION ATTEMPT $attempt / $maxAttempts ===" + Write-Host "==========================================" + Write-Host "" + + # 每次重试都重新生成 TOTP(确保验证码有效) + $otp = Get-TotpCode -Secret $Base32 -Digits $Digits -Period $Period -Algorithm $Algorithm + Write-Host "Generated TOTP code successfully (masked) using $Algorithm algorithm" + Write-Host "" + + # 终止之前可能残留的 SimplySign Desktop 进程 + Get-Process -Name "SimplySignDesktop" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 2 + + # 启动 SimplySign Desktop + Write-Host "Launching SimplySign Desktop..." + Write-Host "Registry pre-configuration should auto-open login dialog" + $proc = Start-Process -FilePath $ExePath -PassThru + Write-Host "Process started with ID: $($proc.Id)" + Write-Host "" + + # 等待应用初始化 + Write-Host "Waiting for SimplySign Desktop to initialize..." + Start-Sleep -Seconds 10 + + # Allow our process to set foreground window + [Win32]::AllowSetForegroundWindow($proc.Id) | Out-Null + + # Create WScript.Shell for window interaction + $wshell = New-Object -ComObject WScript.Shell + + # 尝试聚焦 SimplySign Desktop 窗口 + Write-Host "Attempting to focus SimplySign Desktop window..." + $focused = $false + + # Method 1: Use Win32 API to find and activate window + $mainWindowHandle = $proc.MainWindowHandle + if ($mainWindowHandle -ne $null -and $mainWindowHandle -ne [IntPtr]::Zero) { + [Win32]::ShowWindow($mainWindowHandle, [Win32]::SW_RESTORE) | Out-Null + [Win32]::SetForegroundWindow($mainWindowHandle) | Out-Null + $focused = $true + Write-Host "Focused via MainWindowHandle" + } else { + Write-Host "MainWindowHandle not available yet, will try other methods..." + } + + # Method 2: Find window by title + if (-not $focused) { + $hwnd = [Win32]::FindWindow($null, "SimplySign Desktop") + if ($hwnd -ne [IntPtr]::Zero) { + [Win32]::ShowWindow($hwnd, [Win32]::SW_RESTORE) | Out-Null + [Win32]::SetForegroundWindow($hwnd) | Out-Null + $focused = $true + Write-Host "Focused via FindWindow" + } + } + + # Method 3: AppActivate with extended retries + for ($i = 0; (-not $focused) -and ($i -lt 20); $i++) { + Start-Sleep -Milliseconds 1000 + + # Refresh process handle + $proc.Refresh() + $mainWindowHandle = $proc.MainWindowHandle + if ($mainWindowHandle -ne [IntPtr]::Zero) { + [Win32]::ShowWindow($mainWindowHandle, [Win32]::SW_RESTORE) | Out-Null + [Win32]::SetForegroundWindow($mainWindowHandle) | Out-Null + $focused = $true + Write-Host "Focused via MainWindowHandle (attempt $($i + 1))" + break + } + + $focused = $wshell.AppActivate($proc.Id) + if (-not $focused) { + $focused = $wshell.AppActivate('SimplySign Desktop') + } + if (-not $focused) { + $focused = $wshell.AppActivate('SimplySign') + } + Write-Host "Focus attempt $($i + 1): $focused" + } + + if (-not $focused) { + Write-Host "WARNING: Could not bring SimplySign Desktop to foreground via window handle" + Write-Host "SimplySign Desktop may be running as a background/tray process - proceeding with credential injection anyway" + } + + Write-Host "" + + # Small delay to ensure window is ready for input + Start-Sleep -Milliseconds 400 + + # 注入凭据: Username + TAB + TOTP + ENTER + Write-Host "Injecting credentials into login dialog..." + Write-Host "Sending: Username -> TAB -> TOTP -> ENTER" + + $wshell.SendKeys($UserId) + Start-Sleep -Milliseconds 200 + $wshell.SendKeys("{TAB}") + Start-Sleep -Milliseconds 200 + $wshell.SendKeys($otp) + Start-Sleep -Milliseconds 200 + $wshell.SendKeys("{ENTER}") + + Write-Host "Credentials injected successfully" + Write-Host "" + + # 等待认证处理 + Write-Host "Waiting for authentication to complete..." + Start-Sleep -Seconds 5 + + # 验证证书是否可用 + if ($normalizedExpectedSha1) { + Write-Host "Validating certificate availability for expected signing certificate" + $ready = $false + $withPrivateKey = $false + + for ($i = 0; $i -lt 15; $i++) { + $matched = Find-CertificateByThumbprint -Thumbprint $normalizedExpectedSha1 + if ($matched.Count -gt 0) { + $ready = $true + $withPrivateKey = ($matched | Where-Object { $_.HasPrivateKey }).Count -gt 0 + if ($withPrivateKey) { + Write-Host "Certificate is available and has private key" + break + } + } + Start-Sleep -Seconds 2 + } + + if ($ready -and $withPrivateKey) { + $authSuccess = $true + Write-Host "SUCCESS: Authentication verified - certificate with private key is available" + break + } + + # 认证失败,准备重试 + if (-not $ready) { + Write-Host "WARNING: Target certificate was not found after attempt $attempt" + } elseif (-not $withPrivateKey) { + Write-Host "WARNING: Target certificate found but no private key available after attempt $attempt" + } + } else { + # 没有指定证书 SHA1,无法验证,假设成功 + $authSuccess = $true + break + } + + # 如果不是最后一次尝试,等待后重试 + if ($attempt -lt $maxAttempts) { + Write-Host "Authentication attempt $attempt failed, will retry in 5 seconds..." + # 终止当前 SimplySign Desktop 进程 + $stillRunning = Get-Process -Id $proc.Id -ErrorAction SilentlyContinue + if ($stillRunning) { + Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue + } + Start-Sleep -Seconds 5 + } +} + +if (-not $authSuccess) { + Write-Host "" + Write-Host "ERROR: Authentication failed after $maxAttempts attempts" + Write-Host "All TOTP injection attempts were unsuccessful" + exit 1 +} + +# Verify SimplySign Desktop is still running +$stillRunning = Get-Process -Id $proc.Id -ErrorAction SilentlyContinue +if ($stillRunning) { + Write-Host "SUCCESS: SimplySign Desktop is running" + Write-Host "Authentication should be complete" + Write-Host "Cloud certificate should now be available" +} else { + Write-Host "WARNING: SimplySign Desktop process has exited" + Write-Host "This may indicate authentication failure" +} + +Write-Host "" +Write-Host "=== TOTP AUTHENTICATION COMPLETE ===" +Write-Host "Registry pre-configuration + credential injection finished" diff --git a/.github/scripts/install-simplysign.sh b/.github/scripts/install-simplysign.sh new file mode 100644 index 0000000..2044aa0 --- /dev/null +++ b/.github/scripts/install-simplysign.sh @@ -0,0 +1,138 @@ +#!/bin/bash + +# Install SimplySign Desktop - Clean MSI Installation + +set -euo pipefail + +echo "=== INSTALLING SIMPLYSIGN DESKTOP ===" +echo "Using proven installation method from successful testing..." + +# Download SimplySign Desktop MSI +CERTUM_INSTALLER="SimplySignDesktop.msi" +CERTUM_DOWNLOAD_PAGE="https://pomoc.certum.pl/pl/oprogramowanie/procertum-smartsign/" +FALLBACK_MSI_URL="https://files.certum.eu/software/SimplySignDesktop/Windows/9.4.3.90/SimplySignDesktop-9.4.3.90-64-bit-pl.msi" +echo "Downloading SimplySign Desktop MSI..." + +# Resolve the latest 64-bit MSI URL from Certum software page to avoid hardcoded version expiry. +PAGE_CONTENT="$(curl -L "$CERTUM_DOWNLOAD_PAGE" --fail --max-time 60 || true)" + +MSI_CANDIDATES="$(printf '%s' "$PAGE_CONTENT" | grep -oE 'https://(www\.)?files\.certum\.eu/software/SimplySignDesktop/Windows/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/SimplySignDesktop-[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+-64-bit[^"[:space:]]*\.msi' | sort -u || true)" + +DOWNLOAD_URL="" +if [ -n "$MSI_CANDIDATES" ]; then + LATEST_VERSION="$(printf '%s\n' "$MSI_CANDIDATES" | sed -E 's#^.*/Windows/([0-9.]+)/.*$#\1#' | sort -V | tail -n1)" + LATEST_VERSION_URLS="$(printf '%s\n' "$MSI_CANDIDATES" | grep "/Windows/${LATEST_VERSION}/" || true)" + DOWNLOAD_URL="$(printf '%s\n' "$LATEST_VERSION_URLS" | grep -- '-64-bit-pl\.msi$' | head -n1 || true)" + + if [ -z "$DOWNLOAD_URL" ]; then + DOWNLOAD_URL="$(printf '%s\n' "$LATEST_VERSION_URLS" | head -n1)" + fi +fi + +if [ -z "$DOWNLOAD_URL" ]; then + echo "WARNING: Could not resolve latest MSI URL from Certum page, using fallback URL" + DOWNLOAD_URL="$FALLBACK_MSI_URL" +fi + +RESOLVED_VERSION="$(printf '%s' "$DOWNLOAD_URL" | sed -E 's#^.*/Windows/([0-9.]+)/.*$#\1#')" +echo "Resolved SimplySign Desktop MSI version: $RESOLVED_VERSION" + +if curl -L "$DOWNLOAD_URL" -o "$CERTUM_INSTALLER" --fail --max-time 60; then + echo "✅ Downloaded SimplySign Desktop MSI ($(ls -lh "$CERTUM_INSTALLER" | awk '{print $5}'))" +else + echo "❌ Failed to download SimplySign Desktop" + exit 1 +fi + +# Install with proven method (matching successful test) +echo "Installing SimplySign Desktop..." +echo "Full command: msiexec /i \"$CERTUM_INSTALLER\" /quiet /norestart /l*v install.log ALLUSERS=1 REBOOT=ReallySuppress" + +# Check for administrative privileges (like the successful test) +ADMIN_RIGHTS=false +if powershell -Command "([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)" 2>/dev/null; then + echo "✅ Running with administrative privileges" + ADMIN_RIGHTS=true +else + echo "⚠️ No explicit administrative privileges detected" +fi + +# Use the exact method that worked: PowerShell with admin privileges +if [ "$ADMIN_RIGHTS" = true ]; then + echo "Running MSI installation with administrator privileges..." + powershell -Command "Start-Process -FilePath 'msiexec.exe' -ArgumentList '/i', '\"$CERTUM_INSTALLER\"', '/quiet', '/norestart', '/l*v', 'install.log', 'ALLUSERS=1', 'REBOOT=ReallySuppress' -Wait -NoNewWindow -PassThru" & + INSTALL_PID=$! +else + echo "Running MSI installation without explicit admin elevation..." + timeout 300 msiexec /i "$CERTUM_INSTALLER" /quiet /norestart /l*v install.log ALLUSERS=1 REBOOT=ReallySuppress & + INSTALL_PID=$! +fi + +# Monitor with the same logic as successful test +echo "Monitoring installation progress..." +INSTALL_START_TIME=$(date +%s) +sleep 10 + +# Check if msiexec process is actually running (like successful test) +if kill -0 $INSTALL_PID 2>/dev/null; then + echo "MSI installation process is running (PID: $INSTALL_PID)" + + # Monitor for up to 3 minutes with status updates + for i in {1..18}; do + sleep 10 + CURRENT_TIME=$(date +%s) + ELAPSED=$((CURRENT_TIME - INSTALL_START_TIME)) + + if kill -0 $INSTALL_PID 2>/dev/null; then + echo "Installation still running after ${ELAPSED} seconds..." + + # Check log file growth + if [ -f "install.log" ]; then + LOG_SIZE=$(stat -c%s "install.log" 2>/dev/null || stat -f%z "install.log" 2>/dev/null || echo 0) + echo " Log file size: $LOG_SIZE bytes" + fi + else + echo "MSI installation completed after ${ELAPSED} seconds" + break + fi + done + + # Final wait if still running + if kill -0 $INSTALL_PID 2>/dev/null; then + echo "Installation taking longer, waiting for completion..." + wait $INSTALL_PID 2>/dev/null || echo "Installation process ended" + fi +else + echo "MSI installation process ended quickly" +fi + +# Quick success check using proven patterns +INSTALLATION_SUCCESSFUL=false +if [ -f "install.log" ]; then + if grep -qi "Installation.*operation.*completed.*successfully\|Installation.*success.*or.*error.*status.*0\|MainEngineThread.*is.*returning.*0\|Windows.*Installer.*installed.*the.*product" install.log 2>/dev/null; then + echo "✅ Installation successful (confirmed by log patterns)" + INSTALLATION_SUCCESSFUL=true + fi +fi + +# Verify installation directory +INSTALL_PATH="/c/Program Files/Certum/SimplySign Desktop" +if [ -d "$INSTALL_PATH" ]; then + echo "✅ SimplySign Desktop installed successfully" + echo "✅ Virtual card emulation now active for code signing" + INSTALLATION_SUCCESSFUL=true + + # Set output for GitHub Actions + if [ -n "${GITHUB_OUTPUT:-}" ]; then + echo "SIMPLYSIGN_PATH=$INSTALL_PATH" >> "$GITHUB_OUTPUT" + fi +fi + +if [ "$INSTALLATION_SUCCESSFUL" = false ]; then + echo "❌ Installation verification failed" + echo "Last 10 lines of install log:" + tail -10 install.log 2>/dev/null || echo "No install log available" + exit 1 +fi + +echo "🎉 SimplySign Desktop installation completed successfully!" diff --git a/.github/scripts/sign-windows.ps1 b/.github/scripts/sign-windows.ps1 new file mode 100644 index 0000000..c31111f --- /dev/null +++ b/.github/scripts/sign-windows.ps1 @@ -0,0 +1,182 @@ +# Sign-Windows.ps1 +# Signs Windows EasyTier executables and libraries with a Certum SimplySign cloud certificate. + +param( + [string]$TargetDirectory = "sign_binaries", + [string]$CertificateSHA1 = $env:CERTUM_CERTIFICATE_SHA1, + [string]$TimestampServer = "http://time.certum.pl" +) + +function Get-LatestSignToolPath { + $windowsKitsBin = Join-Path ${env:ProgramFiles(x86)} "Windows Kits\10\bin" + if (Test-Path $windowsKitsBin) { + $candidate = ( + Get-ChildItem -Path $windowsKitsBin -Recurse -File -Filter "signtool.exe" -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -match "\\x64\\signtool\.exe$" } | + ForEach-Object { + $version = [version]"0.0" + if ($_.FullName -match "\\bin\\([^\\]+)\\x64\\signtool\.exe$") { + try { + $version = [version]$matches[1] + } catch { + $version = [version]"0.0" + } + } + [PSCustomObject]@{ + Path = $_.FullName + Version = $version + } + } | + Sort-Object -Property Version -Descending | + Select-Object -First 1 + ) + + if ($candidate) { + return $candidate.Path + } + } + + $cmd = Get-Command "signtool.exe" -ErrorAction SilentlyContinue + if ($cmd) { + return $cmd.Source + } + + return $null +} + +function Find-TargetCertificate { + param([string]$Thumbprint) + + $all = Get-ChildItem -Path "Cert:\CurrentUser\My", "Cert:\LocalMachine\My" -ErrorAction SilentlyContinue + # 对证书库中的 Thumbprint 同样做规范化(去除不可见字符、统一大写),避免 BOM 或格式差异导致匹配失败 + return @($all | Where-Object { + $normalizedStoreThumprint = ($_.Thumbprint -replace "[^a-fA-F0-9]", "").ToUpperInvariant() + $normalizedStoreThumprint -eq $Thumbprint + }) +} + +function Show-PrivateKeyCertificateHints { + $candidates = Get-ChildItem -Path "Cert:\CurrentUser\My", "Cert:\LocalMachine\My" -ErrorAction SilentlyContinue | + Where-Object { $_.HasPrivateKey } + + if (($null -eq $candidates) -or ($candidates.Count -eq 0)) { + Write-Host "No certificates with private keys were found in Personal stores" + return + } + + Write-Host "Certificates with private keys are present in Personal stores, but details are hidden for security" +} + +Write-Host "=== WINDOWS BINARY SIGNING (CERTUM SIMPLYSIGN) ===" +Write-Host "Target directory: $TargetDirectory" + +if (-not (Test-Path $TargetDirectory)) { + Write-Host "ERROR: Target directory not found: $TargetDirectory" + exit 1 +} + +if (-not $CertificateSHA1) { + Write-Host "ERROR: CERTUM_CERTIFICATE_SHA1 environment variable not provided" + exit 1 +} + +$normalizedSha1 = ($CertificateSHA1 -replace "[^a-fA-F0-9]", "").ToUpperInvariant() +if ($normalizedSha1.Length -ne 40) { + Write-Host "ERROR: CERTUM_CERTIFICATE_SHA1 is invalid after normalization" + Write-Host "Raw length: $($CertificateSHA1.Length), normalized length: $($normalizedSha1.Length)" + exit 1 +} + +Write-Host "Expected signing certificate thumbprint has been received (masked)" + +$targetCerts = Find-TargetCertificate -Thumbprint $normalizedSha1 +if (($null -eq $targetCerts) -or ($targetCerts.Count -eq 0)) { + Write-Host "ERROR: Target certificate not found in Cert:\CurrentUser\My or Cert:\LocalMachine\My" + Write-Host "Authentication likely failed or CERTUM_CERTIFICATE_SHA1 is incorrect" + Show-PrivateKeyCertificateHints + exit 1 +} + +$targetWithPrivateKey = @($targetCerts | Where-Object { $_.HasPrivateKey }) +if (($null -eq $targetWithPrivateKey) -or ($targetWithPrivateKey.Count -eq 0)) { + Write-Host "ERROR: Target certificate exists but has no available private key" + Write-Host "Signing cannot continue without private key access" + Show-PrivateKeyCertificateHints + exit 1 +} + +Write-Host "Locating signtool..." +$signTool = Get-LatestSignToolPath +if (-not $signTool) { + Write-Host "ERROR: signtool.exe not found" + exit 1 +} + +Write-Host "Found signtool: $signTool" + +Write-Host "Scanning for Windows binaries to sign (.exe, .dll)..." +$filesToSign = Get-ChildItem -Path $TargetDirectory -Recurse -File | + Where-Object { $_.Extension -iin @(".exe", ".dll") } + +if (($null -eq $filesToSign) -or ($filesToSign.Count -eq 0)) { + Write-Host "WARNING: No signable files (.exe, .dll) found to sign" + exit 0 +} + +Write-Host "Found $($filesToSign.Count) files to sign" +$signedCount = 0 +$failedCount = 0 + +foreach ($file in $filesToSign) { + Write-Host "=== Signing: $($file.Name) ===" + Write-Host "Path: $($file.FullName)" + + $attempts = @( + @{ Name = "SHA1 thumbprint + /td SHA256"; Args = @("sign", "/sha1", $normalizedSha1, "/tr", $TimestampServer, "/td", "SHA256", "/fd", "SHA256", "/v", $file.FullName) }, + @{ Name = "SHA1 thumbprint in CurrentUser\\My"; Args = @("sign", "/sha1", $normalizedSha1, "/s", "My", "/tr", $TimestampServer, "/td", "SHA256", "/fd", "SHA256", "/v", $file.FullName) }, + @{ Name = "SHA1 thumbprint in LocalMachine\\My"; Args = @("sign", "/sha1", $normalizedSha1, "/sm", "/s", "My", "/tr", $TimestampServer, "/td", "SHA256", "/fd", "SHA256", "/v", $file.FullName) }, + @{ Name = "Auto-select cert (fallback)"; Args = @("sign", "/a", "/tr", $TimestampServer, "/td", "SHA256", "/fd", "SHA256", "/v", $file.FullName) } + ) + + $signed = $false + foreach ($attempt in $attempts) { + Write-Host "Attempt: $($attempt.Name)" + $signOutput = & $signTool @($attempt.Args) 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Host "SUCCESS: $($attempt.Name)" + $signed = $true + break + } + + Write-Host "FAILED: $($attempt.Name)" + Write-Host "signtool returned a non-zero exit code; detailed output is hidden for security" + } + + if ($signed) { + $signedCount++ + $verifyOutput = & $signTool verify /pa /v $file.FullName 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Host "VERIFIED: Signature verification successful" + } else { + Write-Host "WARNING: Signature verification failed" + Write-Host "Detailed verification output is hidden for security" + } + } else { + $failedCount++ + } + + Write-Host "" +} + +Write-Host "=== SIGNING SUMMARY ===" +Write-Host "Total files: $($filesToSign.Count)" +Write-Host "Successfully signed: $signedCount" +Write-Host "Failed to sign: $failedCount" + +if ($failedCount -eq 0) { + Write-Host "ALL WINDOWS BINARIES SIGNED SUCCESSFULLY" + exit 0 +} + +Write-Host "SOME WINDOWS BINARIES FAILED TO SIGN" +exit 1 \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2ab387a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,510 @@ +name: OpenP2P Release + +on: + #push: + # branches: [master] + workflow_dispatch: + inputs: + release_type: + description: '发布类型 (beta: 测试版, stable: 正式版)' + type: choice + options: + - beta + - stable + default: beta + required: true + version: + description: '正式版版本号 (e.g. v3.25.11),beta发布时可留空' + type: string + required: false + +permissions: + contents: write + +env: + BINARY_NAME: openp2p + +jobs: + build: + name: Build ${{ matrix.os }}-${{ matrix.arch }} + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + # Windows + - { os: windows, arch: amd64, runner: ubuntu-latest, goos: windows, goarch: amd64, ext: .exe } + - { os: windows, arch: arm64, runner: ubuntu-latest, goos: windows, goarch: arm64, ext: .exe } + - { os: windows, arch: i386, runner: ubuntu-latest, goos: windows, goarch: 386, ext: .exe } + # Linux + - { os: linux, arch: amd64, runner: ubuntu-latest, goos: linux, goarch: amd64, ext: '' } + - { os: linux, arch: arm64, runner: ubuntu-latest, goos: linux, goarch: arm64, ext: '' } + - { os: linux, arch: i386, runner: ubuntu-latest, goos: linux, goarch: 386, ext: '' } + - { os: linux, arch: mips, runner: ubuntu-latest, goos: linux, goarch: mips, ext: '' } + - { os: linux, arch: mips64, runner: ubuntu-latest, goos: linux, goarch: mips64, ext: '' } + # Darwin + - { os: darwin, arch: amd64, runner: ubuntu-latest, goos: darwin, goarch: amd64, ext: '' } + - { os: darwin, arch: arm64, runner: ubuntu-latest, goos: darwin, goarch: arm64, ext: '' } + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.20' + + - name: Build + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + CGO_ENABLED: '0' + GOPROXY: https://goproxy.io,direct + run: | + OUTPUT_NAME="${{ env.BINARY_NAME }}-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.ext }}" + go build -trimpath -ldflags="-s -w" -o "$OUTPUT_NAME" cmd/openp2p.go + echo "OUTPUT_NAME=$OUTPUT_NAME" >> $GITHUB_ENV + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.BINARY_NAME }}-${{ matrix.os }}-${{ matrix.arch }} + path: ${{ env.OUTPUT_NAME }} + retention-days: 7 + + # build-android: + # name: Build Android APK + # runs-on: ubuntu-latest + # steps: + # - name: Checkout + # uses: actions/checkout@v6 + # + # - name: Setup Go + # uses: actions/setup-go@v5 + # with: + # go-version: '1.23' + # + # - name: Setup JDK + # uses: actions/setup-java@v4 + # with: + # java-version: '17' + # distribution: 'temurin' + # + # - name: Setup Android SDK & NDK + # uses: android-actions/setup-android@v3 + # with: + # packages: 'build-tools;30.0.3 ndk;21.4.7075529 platform-tools platforms;android-31' + # + # - name: Setup Android Environment + # run: | + # echo "$ANDROID_HOME/ndk/21.4.7075529" >> $GITHUB_PATH + # echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/21.4.7075529" >> $GITHUB_ENV + # + # - name: Build Go mobile library (gomobile bind) + # env: + # GOPROXY: https://goproxy.io,direct + # run: | + # # Install gomobile and gobind at the same pinned commit to avoid @latest resolution + # go install golang.org/x/mobile/cmd/gomobile@7c4916698cc93475ebfea76748ee0faba2deb2a5 + # go install golang.org/x/mobile/cmd/gobind@7c4916698cc93475ebfea76748ee0faba2deb2a5 + # gomobile init + # go get -v golang.org/x/mobile/bind@7c4916698cc93475ebfea76748ee0faba2deb2a5 + # + # cd core + # gomobile bind -target android -v -androidapi 16 + # + # # Copy artifacts to app libs + # mkdir -p ../app/app/libs + # cp openp2p.aar openp2p-sources.jar ../app/app/libs/ + # echo "Go mobile library built and copied to app/app/libs/" + # ls -la ../app/app/libs/ + # + # - name: Build unsigned APK + # working-directory: app + # run: | + # chmod +x gradlew + # ./gradlew assembleRelease + # + # # Find the built APK + # APK_PATH=$(find . -name "*.apk" -path "*/release/*" | head -1) + # if [ -z "$APK_PATH" ]; then + # APK_PATH=$(find . -name "*.apk" | head -1) + # fi + # + # if [ -n "$APK_PATH" ]; then + # cp "$APK_PATH" ../openp2p-android.apk + # echo "APK built: $APK_PATH" + # else + # echo "ERROR: No APK found" + # exit 1 + # fi + # + # - name: Upload APK artifact + # uses: actions/upload-artifact@v4 + # with: + # name: openp2p-android-apk + # path: openp2p-android.apk + # retention-days: 7 + + sign: + name: Sign Artifacts (Certum SimplySign) + needs: [build] + runs-on: windows-latest + continue-on-error: true + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Download Windows artifacts + uses: actions/download-artifact@v4 + with: + pattern: openp2p-windows-* + path: sign_binaries/windows + merge-multiple: true + + # - name: Download Android APK + # uses: actions/download-artifact@v4 + # with: + # name: openp2p-android-apk + # path: sign_binaries/android + + # - name: Setup JDK (for jarsigner) + # uses: actions/setup-java@v4 + # with: + # java-version: '17' + # distribution: 'temurin' + + - name: Setup Certum Code Signing (Windows) + shell: bash + run: | + echo "=== SETTING UP CERTUM CODE SIGNING FOR WINDOWS ===" + echo "Installing SimplySign Desktop and configuring for automatic authentication" + + chmod +x ./.github/scripts/install-simplysign.sh + ./.github/scripts/install-simplysign.sh + + echo "Configuring registry for automatic login dialog..." + powershell -ExecutionPolicy Bypass -File "./.github/scripts/configure-simplysign-registry.ps1" + + echo "Certum signing environment ready" + + - name: Authenticate Certum (Windows) + shell: bash + env: + CERTUM_OTP_URI: ${{ secrets.CERTUM_OTP_URI }} + CERTUM_USERNAME: ${{ secrets.CERTUM_USERNAME }} + CERTUM_CERTIFICATE_SHA1: ${{ secrets.CERTUM_CERTIFICATE_SHA1 }} + CERTUM_EXE_PATH: ${{ secrets.CERTUM_EXE_PATH }} + run: | + echo "=== CERTUM AUTHENTICATION ===" + echo "Authenticating with Certum cloud certificate using TOTP" + + for attempt in 1 2 3; do + echo "Authentication attempt ${attempt}/3" + if powershell -ExecutionPolicy Bypass -File "./.github/scripts/connect-simplySign-enhanced.ps1"; then + echo "Authentication completed" + exit 0 + fi + + if [ "$attempt" -lt 3 ]; then + echo "Authentication attempt failed, retrying in 10 seconds..." + sleep 10 + fi + done + + echo "ERROR: Certum authentication failed after 3 attempts" + exit 1 + + - name: Verify Certificate and Sign Windows Binaries + shell: bash + env: + CERTUM_CERTIFICATE_SHA1: ${{ secrets.CERTUM_CERTIFICATE_SHA1 }} + run: | + echo "=== CERTIFICATE VERIFICATION AND WINDOWS BINARY SIGNING ===" + echo "Allowing connection to stabilize..." + sleep 10 + + echo "Comprehensive certificate availability check..." + echo "" + + echo "Skipping certificate store dump to avoid exposing certificate metadata in logs" + + echo "" + echo "=== PKCS#11 Library Check ===" + if [ -f "/c/Windows/System32/SimplySignPKCS.dll" ]; then + echo "PKCS#11 library present: /c/Windows/System32/SimplySignPKCS.dll" + ls -la "/c/Windows/System32/SimplySignPKCS.dll" + else + echo "PKCS#11 library not found" + fi + + echo "" + echo "=== SimplySign Desktop Status ===" + powershell -Command " + Write-Host 'SimplySign Desktop process status:' + Get-Process -Name '*SimplySign*' -ErrorAction SilentlyContinue | + Select-Object Name, Id, MainWindowTitle, Responding | + Format-Table -AutoSize + " + + echo "" + echo "Certificate debugging completed - proceeding to signing..." + echo "" + + powershell -ExecutionPolicy Bypass -File "./.github/scripts/sign-windows.ps1" -TargetDirectory "sign_binaries/windows" + + echo "Windows binary signing completed" + + # - name: Sign Android APK (jarsigner + PKCS#11) + # shell: pwsh + # run: | + # Write-Host "=== SIGNING ANDROID APK WITH CERTUM CLOUD CERTIFICATE (PKCS#11) ===" + # + # # Create PKCS#11 config file for Certum SimplySign + # $pkcs11Config = @" + # name = CertumSimplySign + # library = C:\Program Files\Certum\SimplySign Desktop\cryptoCertum3PKCS.dll + # slot = 0 + # "@ + # $pkcs11Config | Out-File -FilePath "pkcs11.cfg" -Encoding ASCII + # + # Write-Host "PKCS#11 config created" + # + # # Find APK files + # $apkFiles = Get-ChildItem -Path "sign_binaries/android" -Recurse -Include *.apk + # + # if ($apkFiles.Count -eq 0) { + # Write-Host "No APK files found to sign" + # exit 0 + # } + # + # # Certum SimplySign default key alias is "1" + # $keyAlias = "1" + # Write-Host "Using key alias: $keyAlias" + # + # foreach ($apk in $apkFiles) { + # Write-Host "Signing APK: $($apk.FullName)" + # try { + # & jarsigner -verbose ` + # -keystore NONE ` + # -storetype PKCS11 ` + # -providerClass sun.security.pkcs11.SunPKCS11 ` + # -providerArg pkcs11.cfg ` + # -tsa http://time.certum.pl ` + # -signedjar "$($apk.DirectoryName)\signed-$($apk.Name)" ` + # "$($apk.FullName)" ` + # $keyAlias + # + # # Replace original with signed version + # Move-Item -Path "$($apk.DirectoryName)\signed-$($apk.Name)" -Destination $apk.FullName -Force + # Write-Host " OK: APK signed successfully" + # } catch { + # Write-Host " WARNING: Failed to sign APK - $($_.Exception.Message)" + # } + # } + # + # Write-Host "=== ANDROID APK SIGNING COMPLETE ===" + # continue-on-error: true + + - name: Verify Windows Signatures + shell: pwsh + run: | + $signedFiles = Get-ChildItem -Path "sign_binaries/windows" -Recurse -Include *.exe + foreach ($file in $signedFiles) { + $result = Get-AuthenticodeSignature -FilePath $file.FullName + $status = if ($result.Status -eq "Valid") { "VALID" } else { "INVALID/UNSIGNED ($($result.Status))" } + Write-Host "$($file.Name): $status" + } + continue-on-error: true + + # - name: Verify APK Signature + # shell: pwsh + # run: | + # $apkFiles = Get-ChildItem -Path "sign_binaries/android" -Recurse -Include *.apk + # foreach ($apk in $apkFiles) { + # Write-Host "Verifying: $($apk.Name)" + # & jarsigner -verify -verbose -certs "$($apk.FullName)" 2>&1 | Select-Object -First 10 + # } + # continue-on-error: true + + - name: Upload signed Windows artifacts + uses: actions/upload-artifact@v4 + with: + name: signed-windows-artifacts + path: sign_binaries/windows + retention-days: 7 + + # - name: Upload signed Android APK + # uses: actions/upload-artifact@v4 + # with: + # name: signed-android-apk + # path: sign_binaries/android + # retention-days: 7 + + release: + name: Create Release + needs: [build, sign] + runs-on: ubuntu-latest + if: always() + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Determine version + id: version + run: | + RELEASE_TYPE="${{ inputs.release_type }}" + if [ "$RELEASE_TYPE" = "stable" ]; then + VERSION="${{ inputs.version }}" + if [ -z "$VERSION" ]; then + echo "ERROR: stable release requires a version number" + exit 1 + fi + IS_BETA="false" + else + VERSION="beta" + IS_BETA="true" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "is_beta=$IS_BETA" >> $GITHUB_OUTPUT + echo "Version: $VERSION, Is Beta: $IS_BETA, Release Type: $RELEASE_TYPE" + + # Try to download signed Windows artifacts first, fall back to unsigned + - name: Download signed Windows artifacts + id: download-signed-win + uses: actions/download-artifact@v4 + with: + name: signed-windows-artifacts + path: release_binaries + continue-on-error: true + + - name: Download unsigned Windows artifacts (fallback) + if: steps.download-signed-win.outcome != 'success' + uses: actions/download-artifact@v4 + with: + pattern: openp2p-windows-* + path: release_binaries + merge-multiple: true + + # Try to download signed Android APK first, fall back to unsigned + # - name: Download signed Android APK + # id: download-signed-android + # uses: actions/download-artifact@v4 + # with: + # name: signed-android-apk + # path: release_binaries + # continue-on-error: true + # + # - name: Download unsigned Android APK (fallback) + # if: steps.download-signed-android.outcome != 'success' + # uses: actions/download-artifact@v4 + # with: + # name: openp2p-android-apk + # path: release_binaries + + - name: Download Linux artifacts + uses: actions/download-artifact@v4 + with: + pattern: openp2p-linux-* + path: release_binaries + merge-multiple: true + + - name: Download Darwin artifacts + uses: actions/download-artifact@v4 + with: + pattern: openp2p-darwin-* + path: release_binaries + merge-multiple: true + + - name: Package release assets + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + mkdir -p release_assets + cd release_binaries + chmod +x * 2>/dev/null || true + + for file in *; do + if [ -f "$file" ]; then + if [[ "$file" == *.exe ]]; then + zip "../release_assets/${file%.exe}-${VERSION}.zip" "$file" + elif [[ "$file" == *.apk ]]; then + # APK files: rename with version, no compression needed + cp "$file" "../release_assets/${file%.apk}-${VERSION}.apk" + else + tar czf "../release_assets/${file}-${VERSION}.tar.gz" "$file" + fi + fi + done + + cd ../release_assets + echo "=== Release Assets ===" + ls -la + + - name: Generate release notes + id: notes + env: + VERSION: ${{ steps.version.outputs.version }} + IS_BETA: ${{ steps.version.outputs.is_beta }} + run: | + if [ "$IS_BETA" = "true" ]; then + cat > release_notes.md << EOF + ## OpenP2P Beta Release (latest unstable) + + Built from commit \`${{ github.sha }}\` on $(date -u +"%Y-%m-%d %H:%M UTC"). + + ### Supported Platforms + | OS | Architectures | + |---|---| + | Windows | amd64, arm64, i386 | + | Linux | amd64, arm64, i386, mips, mips64 | + | Darwin (macOS) | amd64, arm64 | + | Android | amd64, arm64 | + + > **Note**: This is a pre-release build and may be unstable. This release is automatically updated on every push to master. + EOF + else + cat > release_notes.md << EOF + ## OpenP2P $VERSION + + ### Supported Platforms + | OS | Architectures | + |---|---| + | Windows | amd64, arm64, i386 | + | Linux | amd64, arm64, i386, mips, mips64 | + | Darwin (macOS) | amd64, arm64 | + | Android | amd64, arm64 | + EOF + fi + + - name: Delete existing Beta Release + if: steps.version.outputs.is_beta == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Delete existing beta release if it exists + gh release delete beta --yes --cleanup-tag 2>/dev/null || true + + - name: Create Beta Release + if: steps.version.outputs.is_beta == 'true' + uses: softprops/action-gh-release@v2 + with: + name: "Beta (latest unstable)" + tag_name: beta + body_path: release_notes.md + prerelease: true + files: release_assets/* + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Create Stable Release + if: steps.version.outputs.is_beta == 'false' + uses: softprops/action-gh-release@v2 + with: + name: ${{ steps.version.outputs.version }} + tag_name: ${{ steps.version.outputs.version }} + body_path: release_notes.md + prerelease: false + make_latest: true + files: release_assets/* + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/signfile.yml b/.github/workflows/signfile.yml new file mode 100644 index 0000000..971abdc --- /dev/null +++ b/.github/workflows/signfile.yml @@ -0,0 +1,171 @@ +name: Sign External Binaries + +on: + workflow_dispatch: + inputs: + url_x86: + description: 'X86 (i386) 二进制文件下载地址' + type: string + required: false + default: 'https://console.openpxp.com/download/v1/latest/openp2p386-latest.exe' + url_x64: + description: 'X64 (amd64) 二进制文件下载地址' + type: string + required: false + default: 'https://console.openpxp.com/download/v1/latest/openp2p64-latest.exe' + url_arm: + description: 'ARM (arm64) 二进制文件下载地址' + type: string + required: false + default: 'https://console.openpxp.com/download/v1/latest/openp2parm64-latest.exe' + +permissions: + contents: read + +jobs: + sign: + name: Sign Binaries (Certum SimplySign) + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Validate inputs + shell: bash + run: | + if [ -z "${{ inputs.url_x86 }}" ] && [ -z "${{ inputs.url_x64 }}" ] && [ -z "${{ inputs.url_arm }}" ]; then + echo "ERROR: 至少需要提供一个二进制文件下载地址" + exit 1 + fi + echo "=== 输入的下载地址 ===" + [ -n "${{ inputs.url_x86 }}" ] && echo "X86: ${{ inputs.url_x86 }}" + [ -n "${{ inputs.url_x64 }}" ] && echo "X64: ${{ inputs.url_x64 }}" + [ -n "${{ inputs.url_arm }}" ] && echo "ARM: ${{ inputs.url_arm }}" + + - name: Download binaries + shell: bash + run: | + mkdir -p sign_binaries + + download_file() { + local url="$1" + local label="$2" + if [ -z "$url" ]; then + echo "跳过 ${label}: 未提供下载地址" + return + fi + echo "正在下载 ${label}: ${url}" + # 从 URL 中提取文件名 + local filename=$(basename "$url" | sed 's/[?#].*//') + # 如果文件名为空或不合理,使用 label 作为文件名 + if [ -z "$filename" ] || [ "$filename" = "/" ]; then + filename="${label}-binary.exe" + fi + curl -fSL --retry 3 --retry-delay 5 -o "sign_binaries/${filename}" "$url" + if [ $? -eq 0 ]; then + echo "下载成功: ${filename}" + else + echo "ERROR: 下载失败 ${label}: ${url}" + exit 1 + fi + } + + download_file "${{ inputs.url_x86 }}" "x86" + download_file "${{ inputs.url_x64 }}" "x64" + download_file "${{ inputs.url_arm }}" "arm" + + echo "" + echo "=== 已下载的文件 ===" + ls -la sign_binaries/ + + - name: Setup Certum Code Signing (Windows) + shell: bash + run: | + echo "=== SETTING UP CERTUM CODE SIGNING FOR WINDOWS ===" + echo "Installing SimplySign Desktop and configuring for automatic authentication" + + chmod +x ./.github/scripts/install-simplysign.sh + ./.github/scripts/install-simplysign.sh + + echo "Configuring registry for automatic login dialog..." + powershell -ExecutionPolicy Bypass -File "./.github/scripts/configure-simplysign-registry.ps1" + + echo "Certum signing environment ready" + + - name: Authenticate Certum (Windows) + shell: bash + env: + CERTUM_OTP_URI: ${{ secrets.CERTUM_OTP_URI }} + CERTUM_USERNAME: ${{ secrets.CERTUM_USERNAME }} + CERTUM_CERTIFICATE_SHA1: ${{ secrets.CERTUM_CERTIFICATE_SHA1 }} + CERTUM_EXE_PATH: ${{ secrets.CERTUM_EXE_PATH }} + run: | + echo "=== CERTUM AUTHENTICATION ===" + echo "Authenticating with Certum cloud certificate using TOTP" + + for attempt in 1 2 3; do + echo "Authentication attempt ${attempt}/3" + if powershell -ExecutionPolicy Bypass -File "./.github/scripts/connect-simplySign-enhanced.ps1"; then + echo "Authentication completed" + exit 0 + fi + + if [ "$attempt" -lt 3 ]; then + echo "Authentication attempt failed, retrying in 10 seconds..." + sleep 10 + fi + done + + echo "ERROR: Certum authentication failed after 3 attempts" + exit 1 + + - name: Sign Binaries + shell: bash + env: + CERTUM_CERTIFICATE_SHA1: ${{ secrets.CERTUM_CERTIFICATE_SHA1 }} + run: | + echo "=== SIGNING BINARIES ===" + echo "Allowing connection to stabilize..." + sleep 10 + + echo "=== PKCS#11 Library Check ===" + if [ -f "/c/Windows/System32/SimplySignPKCS.dll" ]; then + echo "PKCS#11 library present: /c/Windows/System32/SimplySignPKCS.dll" + else + echo "PKCS#11 library not found" + fi + + echo "" + echo "=== SimplySign Desktop Status ===" + powershell -Command " + Write-Host 'SimplySign Desktop process status:' + Get-Process -Name '*SimplySign*' -ErrorAction SilentlyContinue | + Select-Object Name, Id, MainWindowTitle, Responding | + Format-Table -AutoSize + " + + echo "" + echo "Proceeding to signing..." + echo "" + + powershell -ExecutionPolicy Bypass -File "./.github/scripts/sign-windows.ps1" -TargetDirectory "sign_binaries" + + echo "Binary signing completed" + + - name: Verify Signatures + shell: pwsh + run: | + $signedFiles = Get-ChildItem -Path "sign_binaries" -Recurse -File + foreach ($file in $signedFiles) { + $result = Get-AuthenticodeSignature -FilePath $file.FullName + $status = if ($result.Status -eq "Valid") { "VALID" } else { "INVALID/UNSIGNED ($($result.Status))" } + Write-Host "$($file.Name): $status" + } + continue-on-error: true + + - name: Upload signed artifacts + uses: actions/upload-artifact@v4 + with: + name: signed-binaries + path: sign_binaries/ + retention-days: 30