Compare commits

...
2 Commits
Author SHA1 Message Date
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 <[email protected]>
Co-authored-by: OpenP2P <[email protected]>
2026-06-03 15:39:14 +08:00
TenderIronh 07836fc162 3.25.11 2026-05-15 17:53:49 +08:00
13 changed files with 1694 additions and 12 deletions
@@ -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
}
@@ -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
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
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
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
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
+10 -1
View File
@@ -1,10 +1,19 @@
ChangeLog ChangeLog
v3.25.11更新 (2026.5.15)
Feature
1.
Issue
1. 修复编辑组网成员网络资源时某些情况网络不通
1. 修复安卓版本输错token登录崩溃,下次重启自动登录仍会崩溃,不断循环
1. 修复某些情况导致客户端异常重启
v3.25.8更新 (2026.3.13) v3.25.8更新 (2026.3.13)
Feature Feature
1. web控制台可以修改公网监听端口 1. web控制台可以修改公网监听端口
1. 可以修改虚拟网络网段 1. 可以修改虚拟网络网段
1. 回滚至go1.20因为需要支持老版本的macos和windows 1. 回滚至go1.20支持老版本的macos和windows
Issue Issue
1. 修复客户端重装后强制v6连接失效bug 1. 修复客户端重装后强制v6连接失效bug
+1 -1
View File
@@ -144,7 +144,7 @@ func netInfo() *NetInfo {
} }
rsp := NetInfo{} rsp := NetInfo{}
if err = json.Unmarshal(buf[:n], &rsp); err != nil { if err = json.Unmarshal(buf[:n], &rsp); err != nil {
gLog.e("wrong NetInfo:%s", err) gLog.d("wrong NetInfo:%s", err)
continue continue
} }
return &rsp return &rsp
+9 -5
View File
@@ -119,6 +119,10 @@ func (c *Config) resetSDWAN() {
func (c *Config) setSDWAN(s SDWANInfo) { func (c *Config) setSDWAN(s SDWANInfo) {
c.sdwanMtx.Lock() c.sdwanMtx.Lock()
defer c.sdwanMtx.Unlock() defer c.sdwanMtx.Unlock()
allNew := false
if c.sdwan.GetResourceByNodeName(c.Network.Node) != s.GetResourceByNodeName(c.Network.Node) {
allNew = true
}
// get old-new // get old-new
c.delNodes = []*SDWANNode{} c.delNodes = []*SDWANNode{}
for _, oldNode := range c.sdwan.Nodes { for _, oldNode := range c.sdwan.Nodes {
@@ -129,7 +133,7 @@ func (c *Config) setSDWAN(s SDWANInfo) {
break break
} }
} }
if isDeleted { if isDeleted || allNew {
c.delNodes = append(c.delNodes, oldNode) c.delNodes = append(c.delNodes, oldNode)
} }
} }
@@ -143,7 +147,7 @@ func (c *Config) setSDWAN(s SDWANInfo) {
break break
} }
} }
if isNew { if isNew || allNew {
c.addNodes = append(c.addNodes, newNode) c.addNodes = append(c.addNodes, newNode)
} }
} }
@@ -151,9 +155,9 @@ func (c *Config) setSDWAN(s SDWANInfo) {
if c.sdwan.TunnelNum < 2 { if c.sdwan.TunnelNum < 2 {
c.sdwan.TunnelNum = 2 // DEBUG c.sdwan.TunnelNum = 2 // DEBUG
} }
if c.sdwan.TunnelNum > 3 { // if c.sdwan.TunnelNum > 3 {
c.sdwan.TunnelNum = 3 // c.sdwan.TunnelNum = 3
} // }
} }
func (c *Config) switchApp(app AppConfig, enabled int) { func (c *Config) switchApp(app AppConfig, enabled int) {
+7 -2
View File
@@ -147,12 +147,17 @@ func handlePush(subType uint16, msg []byte) error {
gLog.i("MsgPushUpdate") gLog.i("MsgPushUpdate")
err := update(gConf.Network.ServerHost, gConf.Network.ServerPort) err := update(gConf.Network.ServerHost, gConf.Network.ServerPort)
if err == nil { if err == nil {
os.Exit(9) // 9 tell daemon this exit because of update if !isAndroid() {
os.Exit(9) // 9 tell daemon this exit because of update
}
} }
return err return err
case MsgPushRestart: case MsgPushRestart:
gLog.i("MsgPushRestart") gLog.i("MsgPushRestart")
os.Exit(0) if !isAndroid() {
os.Exit(0)
}
return err return err
case MsgPushReportApps: case MsgPushReportApps:
err = handleReportApps() err = handleReportApps()
+8
View File
@@ -135,6 +135,14 @@ func GetToken(baseDir string) string {
return fmt.Sprintf("%d", gConf.Network.Token) return fmt.Sprintf("%d", gConf.Network.Token)
} }
func SetToken(token string) {
n, err := strconv.ParseUint(token, 10, 64)
if err == nil && n > 0 {
gConf.setToken(n)
gConf.save()
}
}
func Stop() { func Stop() {
os.Exit(0) os.Exit(0)
} }
+6 -2
View File
@@ -153,7 +153,9 @@ func (pn *P2PNetwork) keepAlive() {
} }
gLog.e("P2PNetwork keepAlive error, exit worker") gLog.e("P2PNetwork keepAlive error, exit worker")
dumpStack() dumpStack()
os.Exit(9) if !isAndroid() {
os.Exit(9)
}
} }
} }
} }
@@ -811,7 +813,9 @@ func (pn *P2PNetwork) init() error {
head, _ := pn.read("", MsgReport, MsgReportBasicRsp, ClientAPITimeout) head, _ := pn.read("", MsgReport, MsgReportBasicRsp, ClientAPITimeout)
if head == nil { if head == nil {
gLog.e("read MsgReportBasic rsp error again, exit") gLog.e("read MsgReportBasic rsp error again, exit")
os.Exit(9) if !isAndroid() {
os.Exit(9)
}
} }
return return
} }
+10 -1
View File
@@ -10,7 +10,7 @@ import (
"time" "time"
) )
const OpenP2PVersion = "3.25.8" const OpenP2PVersion = "3.25.11"
const ProductName string = "openp2p" const ProductName string = "openp2p"
const LeastSupportVersion = "3.0.0" const LeastSupportVersion = "3.0.0"
const SyncServerTimeVersion = "3.9.0" const SyncServerTimeVersion = "3.9.0"
@@ -547,6 +547,15 @@ type SDWANInfo struct {
Nodes []*SDWANNode Nodes []*SDWANNode
} }
func (s *SDWANInfo) GetResourceByNodeName(nodeName string) string {
for _, node := range s.Nodes {
if node.Name == nodeName {
return node.Resource
}
}
return ""
}
const ( const (
SDWANModeFullmesh = "fullmesh" SDWANModeFullmesh = "fullmesh"
SDWANModeCentral = "central" SDWANModeCentral = "central"