Compare commits

...

1 Commits

Author SHA1 Message Date
Pikachu Ren
e9e4cd6b3c 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 <Susus0175@proton.me>
Co-authored-by: OpenP2P <89245779+TenderIronh@users.noreply.github.com>
2026-06-03 15:39:14 +08:00
6 changed files with 1643 additions and 0 deletions

View File

@@ -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
}

View File

@@ -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"

138
.github/scripts/install-simplysign.sh vendored Normal file
View File

@@ -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!"

182
.github/scripts/sign-windows.ps1 vendored Normal file
View File

@@ -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

510
.github/workflows/release.yml vendored Normal file
View File

@@ -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 }}

171
.github/workflows/signfile.yml vendored Normal file
View File

@@ -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