339 lines
12 KiB
PowerShell
339 lines
12 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[string[]]$ExpectedUsers = @(),
|
|
[switch]$IncludeUnexpectedProfiles
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
function Get-UninstallEntries {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$NamePattern
|
|
)
|
|
|
|
$paths = @(
|
|
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
|
|
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
|
)
|
|
|
|
return @(
|
|
Get-ItemProperty $paths -ErrorAction SilentlyContinue |
|
|
Where-Object {
|
|
$_.PSObject.Properties.Name -contains 'DisplayName' -and
|
|
-not [string]::IsNullOrWhiteSpace([string]$_.DisplayName) -and
|
|
([string]$_.DisplayName -match $NamePattern)
|
|
} |
|
|
Select-Object DisplayName, DisplayVersion, Publisher
|
|
)
|
|
}
|
|
|
|
function Get-CryptoProToolPath {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$LeafName
|
|
)
|
|
|
|
$candidate = Join-Path 'C:\Program Files\Crypto Pro\CSP' $LeafName
|
|
if (Test-Path -LiteralPath $candidate) {
|
|
return $candidate
|
|
}
|
|
return $null
|
|
}
|
|
|
|
function Get-CryptoProStoreMapForCurrentUser {
|
|
param(
|
|
[string]$CertmgrPath
|
|
)
|
|
|
|
$map = @{}
|
|
if ([string]::IsNullOrWhiteSpace($CertmgrPath) -or -not (Test-Path -LiteralPath $CertmgrPath)) {
|
|
return $map
|
|
}
|
|
|
|
$raw = & $CertmgrPath -list -store uMy 2>&1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
return $map
|
|
}
|
|
|
|
$current = @{}
|
|
foreach ($line in @($raw | ForEach-Object { [string]$_ })) {
|
|
$trimmed = $line.Trim()
|
|
if ($trimmed -match '^SHA1 Thumbprint\s*:\s*(.+)$') {
|
|
$current.thumbprint = $Matches[1].Trim().ToUpperInvariant()
|
|
continue
|
|
}
|
|
if ($trimmed -match '^Embedded License\s*:\s*(.+)$') {
|
|
$current.embeddedLicense = $Matches[1].Trim()
|
|
continue
|
|
}
|
|
if ($trimmed -match '^Container\s*:\s*(.+)$') {
|
|
$current.container = $Matches[1].Trim()
|
|
continue
|
|
}
|
|
if ($trimmed -match '^\[ErrorCode:') {
|
|
if ($current.ContainsKey('thumbprint')) {
|
|
$map[$current.thumbprint] = [pscustomobject]@{
|
|
embeddedLicense = if ($current.ContainsKey('embeddedLicense')) { [string]$current.embeddedLicense } else { $null }
|
|
container = if ($current.ContainsKey('container')) { [string]$current.container } else { $null }
|
|
}
|
|
}
|
|
$current = @{}
|
|
}
|
|
}
|
|
|
|
return $map
|
|
}
|
|
|
|
function Test-CertificateEmbeddedLicense {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
|
|
[string]$CspTestPath
|
|
)
|
|
|
|
$result = [ordered]@{
|
|
ok = $null
|
|
status = $null
|
|
error = $null
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($CspTestPath) -or -not (Test-Path -LiteralPath $CspTestPath)) {
|
|
$result.error = 'csptest_missing'
|
|
return [pscustomobject]$result
|
|
}
|
|
|
|
$tempPath = Join-Path $env:TEMP ("cryptopro-audit-{0}.cer" -f ([Guid]::NewGuid().ToString('N')))
|
|
try {
|
|
Export-Certificate -Cert $Certificate -FilePath $tempPath -Force | Out-Null
|
|
$raw = & $CspTestPath -certlic -check -certfile $tempPath 2>&1
|
|
if ($LASTEXITCODE -eq 0) {
|
|
$licenseLine = @($raw | Where-Object { [string]$_ -match '^License:\s*' } | Select-Object -First 1)
|
|
if ($licenseLine.Count -gt 0) {
|
|
$statusText = (($licenseLine[0] -replace '^License:\s*', '').Trim())
|
|
$result.status = $statusText
|
|
$result.ok = ($statusText -match '^Good license\b')
|
|
}
|
|
else {
|
|
$result.ok = $false
|
|
$result.error = 'license_line_missing'
|
|
}
|
|
}
|
|
else {
|
|
$result.ok = $false
|
|
$result.error = ('csptest_exit_{0}' -f $LASTEXITCODE)
|
|
}
|
|
}
|
|
catch {
|
|
$result.ok = $false
|
|
$result.error = $_.Exception.Message
|
|
}
|
|
finally {
|
|
Remove-Item -LiteralPath $tempPath -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
return [pscustomobject]$result
|
|
}
|
|
|
|
function Get-ProfileMap {
|
|
$profileMap = @{}
|
|
foreach ($profile in @(Get-CimInstance Win32_UserProfile | Where-Object { -not $_.Special })) {
|
|
$leaf = Split-Path -Leaf ([string]$profile.LocalPath)
|
|
if ([string]::IsNullOrWhiteSpace($leaf)) { continue }
|
|
$profileMap[$leaf.ToUpperInvariant()] = [pscustomobject]@{
|
|
user = $leaf
|
|
sid = [string]$profile.SID
|
|
localPath = [string]$profile.LocalPath
|
|
loaded = [bool]$profile.Loaded
|
|
}
|
|
}
|
|
return $profileMap
|
|
}
|
|
|
|
function Get-UserAuditRows {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$RequestedUser,
|
|
[pscustomobject]$Profile,
|
|
[string]$CspTestPath,
|
|
[hashtable]$CurrentUserStoreMap,
|
|
[bool]$ExpectedSigner
|
|
)
|
|
|
|
if ($null -eq $Profile) {
|
|
return @(
|
|
[pscustomobject]@{
|
|
requestedUser = $RequestedUser
|
|
profileUser = $null
|
|
profilePath = $null
|
|
profileLoaded = $false
|
|
sid = $null
|
|
storeFilePath = $null
|
|
thumbprint = $null
|
|
subject = $null
|
|
issuer = $null
|
|
notAfterUtc = $null
|
|
hasPrivateKey = $false
|
|
embeddedLicenseOk = $null
|
|
embeddedLicenseStatus = $null
|
|
container = $null
|
|
expectedSigner = $ExpectedSigner
|
|
actionNeeded = 'profile_missing_or_no_login'
|
|
}
|
|
)
|
|
}
|
|
|
|
$certDir = Join-Path $Profile.localPath 'AppData\Roaming\Microsoft\SystemCertificates\My\Certificates'
|
|
$files = @()
|
|
if (Test-Path -LiteralPath $certDir) {
|
|
$files = @(Get-ChildItem -LiteralPath $certDir -File -ErrorAction SilentlyContinue)
|
|
}
|
|
if ($files.Count -eq 0) {
|
|
return @(
|
|
[pscustomobject]@{
|
|
requestedUser = $RequestedUser
|
|
profileUser = $Profile.user
|
|
profilePath = $Profile.localPath
|
|
profileLoaded = [bool]$Profile.loaded
|
|
sid = $Profile.sid
|
|
storeFilePath = $certDir
|
|
thumbprint = $null
|
|
subject = $null
|
|
issuer = $null
|
|
notAfterUtc = $null
|
|
hasPrivateKey = $false
|
|
embeddedLicenseOk = $null
|
|
embeddedLicenseStatus = $null
|
|
container = $null
|
|
expectedSigner = $ExpectedSigner
|
|
actionNeeded = 'certificate_missing'
|
|
}
|
|
)
|
|
}
|
|
|
|
$rows = @()
|
|
foreach ($file in $files) {
|
|
try {
|
|
$cert = Get-PfxCertificate -FilePath $file.FullName
|
|
$thumbprint = [string]$cert.Thumbprint
|
|
$license = Test-CertificateEmbeddedLicense -Certificate $cert -CspTestPath $CspTestPath
|
|
$storeMeta = $null
|
|
if ($CurrentUserStoreMap.ContainsKey($thumbprint.ToUpperInvariant())) {
|
|
$storeMeta = $CurrentUserStoreMap[$thumbprint.ToUpperInvariant()]
|
|
}
|
|
|
|
$actionNeeded = 'manual_review'
|
|
if ($cert.NotAfter -lt (Get-Date)) {
|
|
$actionNeeded = 'renew_certificate'
|
|
}
|
|
elseif (-not [bool]$cert.HasPrivateKey) {
|
|
$actionNeeded = 'attach_private_key_or_token'
|
|
}
|
|
elseif ($license.ok -eq $true) {
|
|
$actionNeeded = 'ready'
|
|
}
|
|
elseif ($license.ok -eq $false) {
|
|
$actionNeeded = 'embedded_license_missing_or_invalid'
|
|
}
|
|
|
|
$rows += [pscustomobject]@{
|
|
requestedUser = $RequestedUser
|
|
profileUser = $Profile.user
|
|
profilePath = $Profile.localPath
|
|
profileLoaded = [bool]$Profile.loaded
|
|
sid = $Profile.sid
|
|
storeFilePath = $file.FullName
|
|
thumbprint = $thumbprint
|
|
subject = [string]$cert.Subject
|
|
issuer = [string]$cert.Issuer
|
|
notAfterUtc = $cert.NotAfter.ToUniversalTime().ToString('o')
|
|
hasPrivateKey = [bool]$cert.HasPrivateKey
|
|
embeddedLicenseOk = $license.ok
|
|
embeddedLicenseStatus = $license.status
|
|
container = if ($null -ne $storeMeta) { [string]$storeMeta.container } else { $null }
|
|
expectedSigner = $ExpectedSigner
|
|
actionNeeded = $actionNeeded
|
|
}
|
|
}
|
|
catch {
|
|
$rows += [pscustomobject]@{
|
|
requestedUser = $RequestedUser
|
|
profileUser = $Profile.user
|
|
profilePath = $Profile.localPath
|
|
profileLoaded = [bool]$Profile.loaded
|
|
sid = $Profile.sid
|
|
storeFilePath = $file.FullName
|
|
thumbprint = $null
|
|
subject = $null
|
|
issuer = $null
|
|
notAfterUtc = $null
|
|
hasPrivateKey = $false
|
|
embeddedLicenseOk = $null
|
|
embeddedLicenseStatus = $null
|
|
container = $null
|
|
expectedSigner = $ExpectedSigner
|
|
actionNeeded = ('certificate_parse_error: {0}' -f $_.Exception.Message)
|
|
}
|
|
}
|
|
}
|
|
|
|
return $rows
|
|
}
|
|
|
|
$cspEntries = @(Get-UninstallEntries -NamePattern 'CryptoPro|КриптоПро|Крипто')
|
|
$cspMain = @($cspEntries | Where-Object { $_.DisplayName -match 'CSP' } | Select-Object -First 1)
|
|
$browserPlugin = @($cspEntries | Where-Object { $_.DisplayName -match 'Browser' } | Select-Object -First 1)
|
|
$cspTestPath = Get-CryptoProToolPath -LeafName 'csptest.exe'
|
|
$certmgrPath = Get-CryptoProToolPath -LeafName 'certmgr.exe'
|
|
$currentUserStoreMap = Get-CryptoProStoreMapForCurrentUser -CertmgrPath $certmgrPath
|
|
$profileMap = Get-ProfileMap
|
|
|
|
$targets = New-Object 'System.Collections.Generic.List[string]'
|
|
foreach ($expected in @($ExpectedUsers)) {
|
|
if (-not [string]::IsNullOrWhiteSpace([string]$expected)) {
|
|
$targets.Add([string]$expected)
|
|
}
|
|
}
|
|
if ($IncludeUnexpectedProfiles.IsPresent -or $targets.Count -eq 0) {
|
|
foreach ($profileKey in @($profileMap.Keys | Sort-Object)) {
|
|
$targets.Add([string]$profileMap[$profileKey].user)
|
|
}
|
|
}
|
|
|
|
$seen = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
|
$rows = @()
|
|
foreach ($target in $targets) {
|
|
if (-not $seen.Add($target)) { continue }
|
|
$key = $target.ToUpperInvariant()
|
|
$profile = if ($profileMap.ContainsKey($key)) { $profileMap[$key] } else { $null }
|
|
$rows += Get-UserAuditRows -RequestedUser $target -Profile $profile -CspTestPath $cspTestPath -CurrentUserStoreMap $currentUserStoreMap -ExpectedSigner:([bool]($ExpectedUsers -contains $target))
|
|
}
|
|
|
|
$summary = [ordered]@{
|
|
expectedSignerCount = [int](@($ExpectedUsers | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) }).Count)
|
|
readyCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'ready' }).Count)
|
|
missingProfileCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'profile_missing_or_no_login' }).Count)
|
|
missingCertificateCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'certificate_missing' }).Count)
|
|
noPrivateKeyCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'attach_private_key_or_token' }).Count)
|
|
licenseProblemCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'embedded_license_missing_or_invalid' }).Count)
|
|
renewalNeededCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'renew_certificate' }).Count)
|
|
}
|
|
|
|
[pscustomobject]@{
|
|
generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o')
|
|
computerName = [string]$env:COMPUTERNAME
|
|
currentUser = [string]$env:USERNAME
|
|
cryptopro = [ordered]@{
|
|
cspInstalled = ($null -ne $cspMain)
|
|
cspVersion = if ($null -ne $cspMain) { [string]$cspMain.DisplayVersion } else { $null }
|
|
browserPluginInstalled = ($null -ne $browserPlugin)
|
|
browserPluginVersion = if ($null -ne $browserPlugin) { [string]$browserPlugin.DisplayVersion } else { $null }
|
|
cspTestPath = $cspTestPath
|
|
certmgrPath = $certmgrPath
|
|
}
|
|
expectedUsers = @($ExpectedUsers)
|
|
signers = @($rows)
|
|
summary = $summary
|
|
}
|