|
1 | | -# Connect-SimplySign-Enhanced.ps1 |
2 | | -# Registry-Enhanced TOTP Authentication for SimplySign Desktop |
3 | | -# Uses registry pre-configuration + TOTP credential injection approach |
4 | | - |
5 | 1 | param( |
6 | | - [string]$OtpUri = $env:CERTUM_OTP_URI, |
7 | | - [string]$UserId = $env:CERTUM_USERNAME, |
8 | | - [string]$ExePath = $env:CERTUM_EXE_PATH |
| 2 | + [Parameter(Mandatory=$true)] |
| 3 | + [string]$FileToSign |
9 | 4 | ) |
10 | 5 |
|
11 | | -# Validate required parameters |
12 | | -if (-not $OtpUri) { |
13 | | - Write-Host "ERROR: CERTUM_OTP_URI environment variable not provided" |
14 | | - exit 1 |
15 | | -} |
| 6 | +$ErrorActionPreference = "Stop" |
16 | 7 |
|
17 | | -if (-not $UserId) { |
18 | | - Write-Host "ERROR: CERTUM_USERNAME environment variable not provided" |
19 | | - exit 1 |
| 8 | +function Find-SignTool { |
| 9 | + if ($env:TAURI_WINDOWS_SIGNTOOL_PATH -and (Test-Path $env:TAURI_WINDOWS_SIGNTOOL_PATH)) { |
| 10 | + return $env:TAURI_WINDOWS_SIGNTOOL_PATH |
| 11 | + } |
| 12 | + $hits = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe" -ErrorAction SilentlyContinue | |
| 13 | + Sort-Object FullName -Descending |
| 14 | + if ($hits -and $hits.Count -gt 0) { return $hits[0].FullName } |
| 15 | + throw "signtool.exe not found. Set TAURI_WINDOWS_SIGNTOOL_PATH." |
20 | 16 | } |
21 | 17 |
|
22 | | -if (-not $ExePath) { |
23 | | - $ExePath = "C:\Program Files\Certum\SimplySign Desktop\SimplySignDesktop.exe" |
| 18 | +function Ensure-SmartCardService { |
| 19 | + sc.exe config SCardSvr start= demand | Out-Null |
| 20 | + sc.exe start SCardSvr | Out-Null |
24 | 21 | } |
25 | 22 |
|
26 | | -Write-Host "=== REGISTRY-ENHANCED TOTP AUTHENTICATION ===" |
27 | | -Write-Host "Using registry pre-configuration + credential injection" |
28 | | -Write-Host "OTP URI provided (length: $($OtpUri.Length))" |
29 | | -Write-Host "User ID: $UserId" |
30 | | -Write-Host "Executable: $ExePath" |
31 | | -Write-Host "" |
32 | | - |
33 | | -# Verify SimplySign Desktop exists |
34 | | -if (-not (Test-Path $ExePath)) { |
35 | | - Write-Host "ERROR: SimplySign Desktop not found at: $ExePath" |
36 | | - exit 1 |
| 23 | +function Clear-ReadOnly($path) { |
| 24 | + if (Test-Path $path) { & attrib -R $path | Out-Null } |
37 | 25 | } |
38 | 26 |
|
39 | | -# Parse the otpauth:// URI |
40 | | -$uri = [Uri]$OtpUri |
41 | | - |
42 | | -# Parse query parameters (compatible with both PowerShell 5.1 and 7+) |
43 | | -try { |
44 | | - $q = [System.Web.HttpUtility]::ParseQueryString($uri.Query) |
45 | | -} catch { |
46 | | - $q = @{} |
47 | | - foreach ($part in $uri.Query.TrimStart('?') -split '&') { |
48 | | - $kv = $part -split '=', 2 |
49 | | - if ($kv.Count -eq 2) { |
50 | | - $q[$kv[0]] = [Uri]::UnescapeDataString($kv[1]) |
51 | | - } |
52 | | - } |
| 27 | +function Restart-SimplySignDesktop { |
| 28 | + $p = Get-Process -Name "SimplySignDesktop" -ErrorAction SilentlyContinue |
| 29 | + if ($p) { |
| 30 | + $p | Stop-Process -Force -ErrorAction SilentlyContinue |
| 31 | + Start-Sleep -Seconds 2 |
| 32 | + } |
| 33 | + |
| 34 | + # best-effort paths |
| 35 | + $cands = @( |
| 36 | + "$env:ProgramFiles\SimplySign\SimplySignDesktop.exe", |
| 37 | + "$env:ProgramFiles(x86)\SimplySign\SimplySignDesktop.exe" |
| 38 | + ) |
| 39 | + foreach ($c in $cands) { |
| 40 | + if (Test-Path $c) { Start-Process -FilePath $c | Out-Null; break } |
| 41 | + } |
| 42 | + Start-Sleep -Seconds 5 |
53 | 43 | } |
54 | 44 |
|
55 | | -$Base32 = $q['secret'] |
56 | | -$Digits = if ($q['digits']) { [int]$q['digits'] } else { 6 } |
57 | | -$Period = if ($q['period']) { [int]$q['period'] } else { 30 } |
58 | | -$Algorithm = if ($q['algorithm']) { $q['algorithm'].ToUpper() } else { 'SHA256' } |
59 | | - |
60 | | -# Validate supported algorithms |
61 | | -$SupportedAlgorithms = @('SHA1', 'SHA256', 'SHA512') |
62 | | -if ($Algorithm -notin $SupportedAlgorithms) { |
63 | | - Write-Host "ERROR: Unsupported algorithm: $Algorithm. Supported: $($SupportedAlgorithms -join ', ')" |
64 | | - exit 1 |
| 45 | +function Run-SignTool($signtool, $args) { |
| 46 | + $p = Start-Process -FilePath $signtool -ArgumentList $args -NoNewWindow -Wait -PassThru |
| 47 | + return $p.ExitCode |
65 | 48 | } |
66 | 49 |
|
67 | | -# TOTP Generator (inline C# implementation) |
68 | | -Add-Type -Language CSharp @" |
69 | | -using System; |
70 | | -using System.Security.Cryptography; |
71 | | -
|
72 | | -public static class Totp |
73 | | -{ |
74 | | - private const string B32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; |
75 | | -
|
76 | | - private static byte[] Base32Decode(string s) |
77 | | - { |
78 | | - s = s.TrimEnd('=').ToUpperInvariant(); |
79 | | - int byteCount = s.Length * 5 / 8; |
80 | | - byte[] bytes = new byte[byteCount]; |
81 | | -
|
82 | | - int bitBuffer = 0, bitsLeft = 0, idx = 0; |
83 | | - foreach (char c in s) |
84 | | - { |
85 | | - int val = B32.IndexOf(c); |
86 | | - if (val < 0) throw new ArgumentException("Invalid Base32 char: " + c); |
87 | | -
|
88 | | - bitBuffer = (bitBuffer << 5) | val; |
89 | | - bitsLeft += 5; |
90 | | -
|
91 | | - if (bitsLeft >= 8) |
92 | | - { |
93 | | - bytes[idx++] = (byte)(bitBuffer >> (bitsLeft - 8)); |
94 | | - bitsLeft -= 8; |
95 | | - } |
96 | | - } |
97 | | - return bytes; |
98 | | - } |
99 | | -
|
100 | | - private static HMAC GetHmacAlgorithm(string algorithm, byte[] key) |
101 | | - { |
102 | | - switch (algorithm.ToUpper()) |
103 | | - { |
104 | | - case "SHA1": |
105 | | - return new HMACSHA1(key); |
106 | | - case "SHA256": |
107 | | - return new HMACSHA256(key); |
108 | | - case "SHA512": |
109 | | - return new HMACSHA512(key); |
110 | | - default: |
111 | | - throw new ArgumentException("Unsupported algorithm: " + algorithm); |
112 | | - } |
113 | | - } |
114 | | -
|
115 | | - public static string Now(string secret, int digits, int period, string algorithm = "SHA256") |
116 | | - { |
117 | | - byte[] key = Base32Decode(secret); |
118 | | - long counter = DateTimeOffset.UtcNow.ToUnixTimeSeconds() / period; |
119 | | -
|
120 | | - byte[] cnt = BitConverter.GetBytes(counter); |
121 | | - if (BitConverter.IsLittleEndian) Array.Reverse(cnt); |
122 | | -
|
123 | | - byte[] hash; |
124 | | - using (var hmac = GetHmacAlgorithm(algorithm, key)) |
125 | | - { |
126 | | - hash = hmac.ComputeHash(cnt); |
127 | | - } |
128 | | -
|
129 | | - int offset = hash[hash.Length - 1] & 0x0F; |
130 | | - int binary = |
131 | | - ((hash[offset] & 0x7F) << 24) | |
132 | | - ((hash[offset + 1] & 0xFF) << 16) | |
133 | | - ((hash[offset + 2] & 0xFF) << 8) | |
134 | | - (hash[offset + 3] & 0xFF); |
135 | | -
|
136 | | - int otp = binary % (int)Math.Pow(10, digits); |
137 | | - return otp.ToString(new string('0', digits)); |
138 | | - } |
139 | | -} |
140 | | -"@ |
| 50 | +$signtool = Find-SignTool |
| 51 | +$ts = $env:TAURI_SIGN_TIMESTAMP_URL |
| 52 | +if (-not $ts) { $ts = "http://time.certum.pl" } |
141 | 53 |
|
142 | | -function Get-TotpCode { |
143 | | - param([string]$Secret, [int]$Digits = 6, [int]$Period = 30, [string]$Algorithm = 'SHA256') |
144 | | - [Totp]::Now($Secret, $Digits, $Period, $Algorithm) |
145 | | -} |
| 54 | +Write-Host "== SimplySign custom signing ==" |
| 55 | +Write-Host "signtool: $signtool" |
| 56 | +Write-Host "file: $FileToSign" |
| 57 | +Write-Host "ts: $ts" |
146 | 58 |
|
147 | | -# Generate current TOTP code |
148 | | -$otp = Get-TotpCode -Secret $Base32 -Digits $Digits -Period $Period -Algorithm $Algorithm |
149 | | -Write-Host "Generated TOTP: $otp (using $Algorithm algorithm)" |
150 | | -Write-Host "" |
| 59 | +Ensure-SmartCardService |
| 60 | +Clear-ReadOnly $FileToSign |
151 | 61 |
|
152 | | -# Launch SimplySign Desktop (registry should auto-open login dialog) |
153 | | -Write-Host "Launching SimplySign Desktop..." |
154 | | -Write-Host "Registry pre-configuration should auto-open login dialog" |
155 | | -$proc = Start-Process -FilePath $ExePath -PassThru |
156 | | -Write-Host "Process started with ID: $($proc.Id)" |
157 | | -Write-Host "" |
| 62 | +$base = @("sign", "/fd", "sha256", "/tr", $ts, "/td", "sha256") |
158 | 63 |
|
159 | | -# Wait for the application to initialize |
160 | | -Write-Host "Waiting for SimplySign Desktop to initialize..." |
161 | | -Start-Sleep -Seconds 3 |
162 | | - |
163 | | -# Create WScript.Shell for window interaction |
164 | | -$wshell = New-Object -ComObject WScript.Shell |
| 64 | +# Try KSP first, then legacy CSP. NO /as. |
| 65 | +$strategies = @( |
| 66 | + @{ name="KSP-auto"; args= $base + @("/csp","SimplySign KSP","/a",$FileToSign) }, |
| 67 | + @{ name="CSP-auto"; args= $base + @("/csp","SimplySign CSP","/a",$FileToSign) } |
| 68 | +) |
165 | 69 |
|
166 | | -# Try to focus the SimplySign Desktop window |
167 | | -Write-Host "Attempting to focus SimplySign Desktop window..." |
168 | | -$focused = $false |
| 70 | +$max = 10 |
| 71 | +$ok = $false |
169 | 72 |
|
170 | | -# Method 1: Focus by process ID (most reliable) |
171 | | -$focused = $wshell.AppActivate($proc.Id) |
| 73 | +for ($i=1; $i -le $max; $i++) { |
| 74 | + foreach ($s in $strategies) { |
| 75 | + Write-Host "Attempt $i/$max => $($s.name)" |
| 76 | + $code = Run-SignTool $signtool $s.args |
| 77 | + if ($code -eq 0) { $ok = $true; break } |
| 78 | + Write-Host " Sign failed exit=$code" |
| 79 | + } |
172 | 80 |
|
173 | | -# Method 2: Focus by window title (fallback) |
174 | | -if (-not $focused) { |
175 | | - $focused = $wshell.AppActivate('SimplySign Desktop') |
176 | | -} |
| 81 | + if ($ok) { break } |
177 | 82 |
|
178 | | -# Method 3: Multiple attempts with slight delays |
179 | | -for ($i = 0; (-not $focused) -and ($i -lt 10); $i++) { |
180 | | - Start-Sleep -Milliseconds 500 |
181 | | - $focused = $wshell.AppActivate($proc.Id) -or $wshell.AppActivate('SimplySign Desktop') |
182 | | - Write-Host "Focus attempt $($i + 1): $focused" |
| 83 | + Write-Host "Self-heal: ensure SCardSvr + restart SimplySignDesktop (attempt $i)" |
| 84 | + Ensure-SmartCardService |
| 85 | + Restart-SimplySignDesktop |
| 86 | + Start-Sleep -Seconds (2 * $i) |
183 | 87 | } |
184 | 88 |
|
185 | | -if (-not $focused) { |
186 | | - Write-Host "ERROR: Could not bring SimplySign Desktop to foreground" |
187 | | - Write-Host "Login dialog may not be visible for credential injection" |
188 | | - exit 1 |
| 89 | +if (-not $ok) { |
| 90 | + throw "Signing failed after $max attempts for: $FileToSign" |
189 | 91 | } |
190 | 92 |
|
191 | | -Write-Host "Successfully focused SimplySign Desktop window" |
192 | | -Write-Host "" |
193 | | - |
194 | | -# Small delay to ensure window is ready for input |
195 | | -Start-Sleep -Milliseconds 400 |
196 | | - |
197 | | -# Inject credentials: Username + TAB + TOTP + ENTER |
198 | | -Write-Host "Injecting credentials into login dialog..." |
199 | | -Write-Host "Sending: Username -> TAB -> TOTP -> ENTER" |
200 | | - |
201 | | -# Send the credential sequence |
202 | | -$wshell.SendKeys($UserId) |
203 | | -Start-Sleep -Milliseconds 200 |
204 | | -$wshell.SendKeys("{TAB}") |
205 | | -Start-Sleep -Milliseconds 200 |
206 | | -$wshell.SendKeys($otp) |
207 | | -Start-Sleep -Milliseconds 200 |
208 | | -$wshell.SendKeys("{ENTER}") |
209 | | - |
210 | | -Write-Host "Credentials injected successfully" |
211 | | -Write-Host "" |
212 | | - |
213 | | -# Wait for authentication to process |
214 | | -Write-Host "Waiting for authentication to complete..." |
215 | | -Start-Sleep -Seconds 5 |
216 | | - |
217 | | -# Verify SimplySign Desktop is still running |
218 | | -$stillRunning = Get-Process -Id $proc.Id -ErrorAction SilentlyContinue |
219 | | -if ($stillRunning) { |
220 | | - Write-Host "SUCCESS: SimplySign Desktop is running" |
221 | | - Write-Host "Authentication should be complete" |
222 | | - Write-Host "Cloud certificate should now be available" |
223 | | -} else { |
224 | | - Write-Host "WARNING: SimplySign Desktop process has exited" |
225 | | - Write-Host "This may indicate authentication failure" |
| 93 | +Write-Host "Verifying signature..." |
| 94 | +$verifyCode = Run-SignTool $signtool @("verify","/pa","/v",$FileToSign) |
| 95 | +if ($verifyCode -ne 0) { |
| 96 | + throw "signtool verify failed exit=$verifyCode for: $FileToSign" |
226 | 97 | } |
227 | | - |
228 | | -Write-Host "" |
229 | | -Write-Host "=== TOTP AUTHENTICATION COMPLETE ===" |
230 | | -Write-Host "Registry pre-configuration + credential injection finished" |
| 98 | +Write-Host "✅ signed + verified" |
0 commit comments