Public/Repair-GSAAuthCertificate.ps1
|
# # Function: Repair-GSAAuthCertificate # Public function for the GSA client diagnostics toolkit. # function Repair-GSAAuthCertificate { <# .SYNOPSIS Backs up and removes the GSA mTLS authentication certificate so the client re-enrolls a new one. .DESCRIPTION Implements the remediation steps documented for the GSA client's "Authentication certificate exists"/"Authentication certificate is valid" Health check tests: 1. Locates certificate(s) with a subject ending in 'gsa.client' in the LocalMachine\My store. 2. Backs up each certificate BEFORE touching anything - as a password-protected .pfx (with the private key) when exportable, otherwise falls back to a public-key-only .cer file. 3. Requires TWO separate explicit confirmations before deleting a certificate: the standard -WhatIf/-Confirm prompt, plus a second prompt requiring you to type the certificate's thumbprint. This is a destructive, hard-to-reverse action. 4. Deletes the certificate and clears the CertCommonName registry value so the GSA Engine service re-provisions a new certificate. A new certificate is only issued after the GlobalSecureAccessEngineService is restarted (pass -RestartEngineService to do this automatically); provisioning can take a few minutes. .PARAMETER BackupPath Directory to export the certificate backup(s) to before deletion. Defaults to the current directory. .PARAMETER RestartEngineService Restart the GlobalSecureAccessEngineService after removing the certificate, so a new one is provisioned immediately instead of on its next natural cycle. .PARAMETER PassThru Return the $true/$false result. By default nothing is returned - only logged. .PARAMETER LogLevel Sets the minimum log level written by the module. Valid values are ERROR, WARNING, INFO, DEBUG, and SUCCESS. .OUTPUTS System.Boolean when -PassThru is specified. No output is returned by default. .EXAMPLE Repair-GSAAuthCertificate -BackupPath C:\Temp\GSACertBackup -RestartEngineService #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [string]$BackupPath = (Get-Location).Path, [switch]$RestartEngineService, [switch]$PassThru, [ValidateSet("ERROR", "WARNING", "INFO", "DEBUG", "SUCCESS")] [string]$LogLevel ) if ($PSBoundParameters.ContainsKey('LogLevel')) { $script:LogLevel = $LogLevel } # Ensure the script is running with elevated privileges (as Administrator) to access the LocalMachine certificate store and registry. if (-not (Test-IsElevated)) { Write-Log "This function must be run elevated (as Administrator) to access the LocalMachine certificate store and registry." "ERROR" if ($PassThru) { return $false } return } # Open the LocalMachine\My certificate store and locate GSA authentication certificates. $store = New-Object System.Security.Cryptography.X509Certificates.X509Store('My', 'LocalMachine') try { $store.Open('ReadOnly') $certs = @($store.Certificates | Where-Object { $_.Subject -match 'gsa\.client$' }) } catch { Write-Log "Failed to read the LocalMachine\My certificate store: $($_.Exception.Message)" "ERROR" if ($PassThru) { return $false } return } finally { $store.Close() } # Check if any GSA authentication certificates were found; if none, exit early. if ($certs.Count -eq 0) { Write-Log "No GSA authentication certificate was found - nothing to repair." "WARNING" if ($PassThru) { return $false } return } # Ensure the backup directory exists; create it if it does not. if (-not (Test-Path $BackupPath)) { if ($PSCmdlet.ShouldProcess($BackupPath, 'Create backup directory')) { New-Item -ItemType Directory -Path $BackupPath -Force | Out-Null } } # Back up the GSA authentication certificates before making any changes. Write-Log "Backing up the certificate(s) before making any changes..." "INFO" $exportedFiles = @(Export-GSAAuthCertificate -BackupPath $BackupPath -PassThru -WhatIf:$WhatIfPreference -Confirm:$false) $allSucceeded = $true # Iterate through each GSA authentication certificate and attempt to repair it (delete and back up). foreach ($cert in $certs) { Write-Log "Found GSA authentication certificate '$($cert.Subject)' (Thumbprint: $($cert.Thumbprint))." "INFO" # Always back up the certificate before touching anything (skip this gate under -WhatIf, # since the backup step above is itself only previewed, not actually performed) $backupFile = @($exportedFiles) | Where-Object { [System.IO.Path]::GetFileNameWithoutExtension($_) -eq $cert.Thumbprint } | Select-Object -First 1 if (-not $backupFile -and -not $WhatIfPreference) { Write-Log "Skipping deletion of '$($cert.Thumbprint)' because a backup could not be created." "ERROR" $allSucceeded = $false continue } # First confirmation: standard -WhatIf/-Confirm support if (-not $PSCmdlet.ShouldProcess("Certificate '$($cert.Subject)' (Thumbprint: $($cert.Thumbprint))", "Delete from LocalMachine\My")) { Write-Log "Deletion of '$($cert.Thumbprint)' was skipped (not confirmed)." "WARNING" $allSucceeded = $false continue } # Second, explicit confirmation - deliberately separate from -Confirm so -Confirm:$false cannot bypass it $confirmation = Read-Host -Prompt "Type the certificate thumbprint '$($cert.Thumbprint)' to confirm PERMANENT deletion (backup saved to $backupFile)" if ($confirmation -ne $cert.Thumbprint) { Write-Log "Deletion of '$($cert.Thumbprint)' was NOT confirmed (input did not match the thumbprint). Skipping." "WARNING" $allSucceeded = $false continue } # Attempt to delete the current GSA authentication certificate from the LocalMachine\My store. try { Remove-Item -Path "Cert:\LocalMachine\My\$($cert.Thumbprint)" -Force -ErrorAction Stop Write-Log "Deleted certificate '$($cert.Thumbprint)' from LocalMachine\My." "SUCCESS" } catch { Write-Log "Failed to delete certificate '$($cert.Thumbprint)': $($_.Exception.Message)" "ERROR" $allSucceeded = $false continue } } # After attempting to delete all certificates, proceed to clear the registry value and optionally restart the engine service. Write-Log "Clearing the CertCommonName registry value so the GSA client re-enrolls a new certificate..." "INFO" if ($PSCmdlet.ShouldProcess("$script:GSAFwdProfileRegistryPath\CertCommonName", "Remove registry value")) { try { Remove-ItemProperty -Path $script:GSAFwdProfileRegistryPath -Name "CertCommonName" -ErrorAction Stop Write-Log "Removed the CertCommonName registry value." "SUCCESS" } catch { Write-Log "CertCommonName registry value was not found or could not be removed (it may already be absent): $($_.Exception.Message)" "WARNING" } } # If the user has requested a restart of the engine service, attempt to restart it; otherwise, provide a recommendation. if ($RestartEngineService) { if ($PSCmdlet.ShouldProcess("GlobalSecureAccessEngineService", "Restart")) { try { Restart-Service -Name "GlobalSecureAccessEngineService" -Force -ErrorAction Stop Write-Log "Restarted GlobalSecureAccessEngineService. A new certificate should be provisioned within a few minutes." "SUCCESS" } catch { Write-Log "Failed to restart GlobalSecureAccessEngineService: $($_.Exception.Message)" "ERROR" $allSucceeded = $false } } } else { Write-Log "Recommended action: Restart the 'GlobalSecureAccessEngineService' service (or re-run with -RestartEngineService) so a new certificate is provisioned." "WARNING" } if ($PassThru) { return $allSucceeded } } |