[CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [string]$CertificatePath, [string]$BrokerConfigurationPath = 'C:\Program Files\SGU\AuthBroker\appsettings.Production.json' ) $ErrorActionPreference = 'Stop' $serviceName = 'SGUAuthBroker' $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal]::new($identity) if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'Run this script from an elevated PowerShell session on the broker server.' } if (-not (Test-Path -LiteralPath $CertificatePath -PathType Leaf)) { throw 'CertificatePath does not exist.' } if (-not (Test-Path -LiteralPath $BrokerConfigurationPath -PathType Leaf)) { throw 'The broker production configuration does not exist.' } $candidate = [Security.Cryptography.X509Certificates.X509Certificate2]::new($CertificatePath) if ($candidate.NotBefore -gt (Get-Date) -or $candidate.NotAfter -lt (Get-Date)) { throw 'The client certificate is outside its validity period.' } $clientAuthenticationOid = '1.3.6.1.5.5.7.3.2' $ekuExtension = $candidate.Extensions | Where-Object { $_.Oid.Value -eq '2.5.29.37' } | Select-Object -First 1 if (-not $ekuExtension -or $ekuExtension.EnhancedKeyUsages.Value -notcontains $clientAuthenticationOid) { throw 'The certificate is not valid for TLS client authentication.' } $mutex = [Threading.Mutex]::new($false, 'Global\SGUAuthBroker-Client-AllowList') $lockTaken = $false try { try { $lockTaken = $mutex.WaitOne([TimeSpan]::FromSeconds(30)) } catch [Threading.AbandonedMutexException] { # The previous updater exited unexpectedly, but this process now owns # the abandoned mutex and can safely rebuild the allow-list from disk. $lockTaken = $true } if (-not $lockTaken) { throw 'Timed out waiting to update the broker client allow-list.' } if ($PSCmdlet.ShouldProcess($candidate.Thumbprint, 'Trust and allow the SGU client certificate')) { $trustedCertificate = Import-Certificate ` -FilePath $CertificatePath ` -CertStoreLocation Cert:\LocalMachine\Root | Select-Object -First 1 $configuration = Get-Content -LiteralPath $BrokerConfigurationPath -Raw | ConvertFrom-Json $allowed = @($configuration.Broker.Tls.AllowedClientThumbprints | ForEach-Object { $_ -replace ' ', '' }) if ($allowed -notcontains $trustedCertificate.Thumbprint) { $configuration.Broker.Tls.AllowedClientThumbprints = @($allowed + $trustedCertificate.Thumbprint) $backupPath = "$BrokerConfigurationPath.before-$($trustedCertificate.Thumbprint.Substring(0, 12)).bak" Copy-Item -LiteralPath $BrokerConfigurationPath -Destination $backupPath -Force [IO.File]::WriteAllText( $BrokerConfigurationPath, ($configuration | ConvertTo-Json -Depth 8), [Text.UTF8Encoding]::new($false)) } Restart-Service -Name $serviceName -Force (Get-Service -Name $serviceName).WaitForStatus( [System.ServiceProcess.ServiceControllerStatus]::Running, [TimeSpan]::FromSeconds(20)) } } finally { if ($lockTaken) { $mutex.ReleaseMutex() } $mutex.Dispose() } [pscustomobject]@{ Subject = $candidate.Subject Thumbprint = $candidate.Thumbprint NotAfter = $candidate.NotAfter Allowed = @((Get-Content -LiteralPath $BrokerConfigurationPath -Raw | ConvertFrom-Json).Broker.Tls.AllowedClientThumbprints) -contains $candidate.Thumbprint ServiceStatus = (Get-Service -Name $serviceName).Status }