From 27c1f49be180b06768788264def6252b19dab48b Mon Sep 17 00:00:00 2001 From: PTah Date: Fri, 12 Jun 2026 21:12:37 +1000 Subject: [PATCH] =?UTF-8?q?fix:=20=D1=81=D1=80=D0=B0=D0=B2=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20SSLCertificateSHA1Hash=20=E2=80=94=20?= =?UTF-8?q?=D0=B1=D0=B0=D0=B9=D1=82=D1=8B=20=D1=80=D0=B5=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=B0=20vs=20hex=20thumbprint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Реестр отдаёт byte[]; TermService принимал cert, но проверка Persisted ложно падала. Добавлены ConvertTo-RdpRepairThumbprintHex и самотесты на форматы DAK-PC. Co-authored-by: Cursor --- docs/TROUBLESHOOTING.md | 13 ++++++++ scripts/Invoke-RdpRepairSelfTest.ps1 | 17 +++++++++++ scripts/lib/RdpRepair.Common.ps1 | 45 ++++++++++++++++++++++++---- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 1ff39d0..a51808a 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -18,6 +18,19 @@ TermService **отверг** сертификат при старте. Возм - CNG vs CSP — скрипт пробует `RSA\MachineKeys`. - Повреждение Schannel → repair install. +**Важно:** в реестре `SSLCertificateSHA1Hash` часто хранится как **массив байт**, а не hex-строка thumbprint. Скрипт нормализует оба формата перед сравнением. Если BEFORE и AFTER совпадают (в hex), привязка успешна — включайте `SecurityLayer=2` (`-RestoreTls`). + +## Просмотр store Remote Desktop + +`Get-ChildItem Cert:\LocalMachine\Remote Desktop` на части систем не работает (пробел в имени store). Используйте: + +```powershell +. .\lib\RdpRepair.Common.ps1 +Get-RdpRepairRemoteDesktopCertificates | Format-List +``` + +или `.\Fix-RdpTls.ps1 -DiagnoseOnly`. + ## Нативный cert не создаётся после reboot Store `Remote Desktop` пустой после цикла «выкл RDP → очистка → вкл RDP → reboot»: diff --git a/scripts/Invoke-RdpRepairSelfTest.ps1 b/scripts/Invoke-RdpRepairSelfTest.ps1 index 5f3bda3..16380b6 100644 --- a/scripts/Invoke-RdpRepairSelfTest.ps1 +++ b/scripts/Invoke-RdpRepairSelfTest.ps1 @@ -135,6 +135,23 @@ Assert-Test 'Registry path constants well-formed' { if ($script:RdpRepairRdpTcpKey -notmatch 'RDP-Tcp$') { throw 'bad rdp tcp key' } } +Assert-Test 'ConvertTo-RdpRepairThumbprintHex from decimal byte string' { + $hex = ConvertTo-RdpRepairThumbprintHex -HashValue '20 77 141 24 197 69 45 187 141 61 233 254 108 97 230 158 36 151 225 169' + if ($hex -ne '144D8D18C5452DBB8D3DE9FE6C61E69E2497E1A9') { throw "got $hex" } +} + +Assert-Test 'ConvertTo-RdpRepairThumbprintHex from byte array' { + $bytes = [byte[]](0x14, 0x4D, 0x8D, 0x18, 0xC5, 0x45, 0x2D, 0xBB, 0x8D, 0x3D, 0xE9, 0xFE, 0x6C, 0x61, 0xE6, 0x9E, 0x24, 0x97, 0xE1, 0xA9) + $hex = ConvertTo-RdpRepairThumbprintHex -HashValue $bytes + if ($hex -ne '144D8D18C5452DBB8D3DE9FE6C61E69E2497E1A9') { throw "got $hex" } +} + +Assert-Test 'ConvertTo-RdpRepairThumbprintHex hex equals byte forms' { + $fromHex = ConvertTo-RdpRepairThumbprintHex -HashValue '144D8D18C5452DBB8D3DE9FE6C61E69E2497E1A9' + $fromBytes = ConvertTo-RdpRepairThumbprintHex -HashValue ([byte[]](20, 77, 141, 24, 197, 69, 45, 187, 141, 61, 233, 254, 108, 97, 230, 158, 36, 151, 225, 169)) + if ($fromHex -ne $fromBytes) { throw 'format mismatch' } +} + Assert-Test 'Export-RdpRepairDiagnosticReport writes file' { if (-not (Test-RdpRepairIsAdministrator)) { Write-Host ' (skip report write - not admin)' -ForegroundColor DarkYellow diff --git a/scripts/lib/RdpRepair.Common.ps1 b/scripts/lib/RdpRepair.Common.ps1 index fc1899b..3b026a8 100644 --- a/scripts/lib/RdpRepair.Common.ps1 +++ b/scripts/lib/RdpRepair.Common.ps1 @@ -56,12 +56,46 @@ function Get-RdpRepairComputerDnsNames { return $names | Select-Object -Unique } +function ConvertTo-RdpRepairThumbprintHex { + param([object]$HashValue) + if ($null -eq $HashValue) { return $null } + + if ($HashValue -is [byte[]]) { + if ($HashValue.Length -eq 0) { return $null } + return -join ($HashValue | ForEach-Object { '{0:X2}' -f $_ }) + } + + if ($HashValue -is [System.Array] -and $HashValue.Count -gt 0) { + $allNumeric = $true + foreach ($b in $HashValue) { + if ($b -isnot [byte] -and $b -isnot [int] -and $b -isnot [short]) { + $allNumeric = $false + break + } + } + if ($allNumeric -and ($HashValue.Count -eq 20 -or $HashValue.Count -eq 16)) { + return -join ($HashValue | ForEach-Object { '{0:X2}' -f [byte]$_ }) + } + } + + $text = [string]$HashValue + if ([string]::IsNullOrWhiteSpace($text)) { return $null } + $text = $text.Trim() + + if ($text -match '^\d+( \d+)+$') { + $bytes = $text -split '\s+' | ForEach-Object { [byte]$_ } + return -join ($bytes | ForEach-Object { '{0:X2}' -f $_ }) + } + + $hex = ($text -replace '\s', '').ToUpper() + if ($hex -match '^[0-9A-F]+$') { return $hex } + return $null +} + function Get-RdpRepairSslCertificateHash { $p = Get-ItemProperty -Path $script:RdpRepairRdpTcpKey -Name SSLCertificateSHA1Hash -ErrorAction SilentlyContinue - if ($null -eq $p -or [string]::IsNullOrWhiteSpace($p.SSLCertificateSHA1Hash)) { - return $null - } - return $p.SSLCertificateSHA1Hash + if ($null -eq $p -or -not $p.PSObject.Properties['SSLCertificateSHA1Hash']) { return $null } + return ConvertTo-RdpRepairThumbprintHex -HashValue $p.SSLCertificateSHA1Hash } function Get-RdpRepairSecurityLayer { @@ -210,7 +244,8 @@ function Test-RdpRepairCertificateBindingPersisted { Restart-RdpRepairTermService $after = Get-RdpRepairSslCertificateHash Write-RdpRepairLog "Hash AFTER TermService start: $(if ($after) { $after } else { '(empty)' })" 'INFO' $LogPath - $ok = ($after -eq $ExpectedThumbprint.ToUpper()) + $expected = ConvertTo-RdpRepairThumbprintHex -HashValue $ExpectedThumbprint + $ok = ($after -eq $expected) if ($ok) { Write-RdpRepairLog 'Привязка cert принята TermService' 'OK' $LogPath } else {