mirror of
https://github.com/openp2p-cn/openp2p.git
synced 2026-07-02 02:07:04 +08:00
* 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>
391 lines
13 KiB
PowerShell
391 lines
13 KiB
PowerShell
# 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"
|