sslhandling.psm1
|
<# Copyright (c) 2026 One Identity LLC. All rights reserved. #> # SSL handling helpers # Nothing is exported from here. # Tracks whether -Insecure is active. Defaults to $false (verification on). $script:SkipCertificateCheck = $false function Disable-SslVerification { [CmdletBinding()] Param( ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } $script:SkipCertificateCheck = $true if ($PSVersionTable.PSEdition -eq "Core") { if ($PSVersionTable.PSVersion.Major -lt 6) { Write-Verbose "Unable to disable SSL on PowerShell Core version less than 6" } else { Write-Verbose "Disabling SSL on cross-platform PowerShell (module-scoped; non-Safeguard cmdlets are unaffected)" } } else { Write-Verbose "Disabling SSL on Windows platform" if (-not ([System.Management.Automation.PSTypeName]"TrustEverything").Type) { Write-Verbose "Adding the PSType for SSL trust override" Add-Type -TypeDefinition @" using System.Net.Security; using System.Security.Cryptography.X509Certificates; public static class TrustEverything { private static bool ValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; } public static void SetCallback() { System.Net.ServicePointManager.ServerCertificateValidationCallback = ValidationCallback; } public static void UnsetCallback() { System.Net.ServicePointManager.ServerCertificateValidationCallback = null; } } "@ } Write-Verbose "Adding the trust everything callback" [TrustEverything]::SetCallback() } } function Enable-SslVerification { [CmdletBinding()] Param( ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } $script:SkipCertificateCheck = $false if ($PSVersionTable.PSEdition -eq "Core") { if ($PSVersionTable.PSVersion.Major -lt 6) { Write-Verbose "Unable to enable SSL on PowerShell Core version less than 6" } else { Write-Verbose "Enabling SSL on cross-platform PowerShell" } } else { Write-Verbose "Enabling SSL on Windows platform" if (([System.Management.Automation.PSTypeName]"TrustEverything").Type) { Write-Verbose "Removing the trust everything callback" [TrustEverything]::UnsetCallback() } } } # Returns a hashtable suitable for assignment to a function-scoped # $PSDefaultParameterValues so that Invoke-RestMethod and Invoke-WebRequest # calls within the *current function* honour the module-scoped TLS bypass. # This replaces the prior pattern of cloning $global:PSDefaultParameterValues # (which only worked because Disable-SslVerification was polluting it). # # On Windows PowerShell 5.1 the [TrustEverything] callback handles the bypass # process-wide, so this helper returns an empty hashtable on that edition. function Get-SafeguardSslPreferences { [CmdletBinding()] [OutputType([hashtable])] Param( ) if (-not $script:SkipCertificateCheck) { return @{} } if ($PSVersionTable.PSEdition -ne "Core" -or $PSVersionTable.PSVersion.Major -lt 6) { return @{} } return @{ 'Invoke-RestMethod:SkipCertificateCheck' = $true 'Invoke-WebRequest:SkipCertificateCheck' = $true } } # Maps a caller-facing TLS version string ("1.2", "1.3") to the corresponding # .NET enum member name ("Tls12", "Tls13") used by both SecurityProtocolType # (Windows PowerShell) and WebSslProtocol (PowerShell 7 -SslProtocol). function Get-SafeguardTlsEnumName { [CmdletBinding()] [OutputType([string])] Param( [Parameter(Mandatory=$true,Position=0)] [string]$TlsVersion ) switch ($TlsVersion) { "1.2" { "Tls12" } "1.3" { "Tls13" } default { throw "Unsupported TLS version '$TlsVersion'. Supported values are 1.2 and 1.3." } } } # Resolves an optional [MinimumTlsVersion, MaximumTlsVersion] range into the # ordered set of secure TLS versions the module should enable. Only TLS 1.2 and # TLS 1.3 are ever enabled (1.0/1.1 are intentionally excluded). Returns $null # when neither bound is specified, meaning "negotiate normally" (the default). function Get-SafeguardTlsVersionSet { [CmdletBinding()] [OutputType([object[]])] Param( [Parameter(Mandatory=$false)] [string]$MinimumTlsVersion, [Parameter(Mandatory=$false)] [string]$MaximumTlsVersion ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } if ([string]::IsNullOrEmpty($MinimumTlsVersion) -and [string]::IsNullOrEmpty($MaximumTlsVersion)) { return $null } if ((-not [string]::IsNullOrEmpty($MinimumTlsVersion)) -and (-not [string]::IsNullOrEmpty($MaximumTlsVersion)) -and ` ([System.Version]$MinimumTlsVersion -gt [System.Version]$MaximumTlsVersion)) { throw "MinimumTlsVersion ($MinimumTlsVersion) cannot be greater than MaximumTlsVersion ($MaximumTlsVersion)." } # Secure TLS versions this module is willing to enable, in ascending order. $local:Known = @("1.2", "1.3") $local:Selected = @($local:Known | Where-Object { (([string]::IsNullOrEmpty($MinimumTlsVersion)) -or ([System.Version]$_ -ge [System.Version]$MinimumTlsVersion)) -and (([string]::IsNullOrEmpty($MaximumTlsVersion)) -or ([System.Version]$_ -le [System.Version]$MaximumTlsVersion)) }) if ((-not $local:Selected) -or ($local:Selected.Count -eq 0)) { throw "The requested TLS version range (min='$MinimumTlsVersion', max='$MaximumTlsVersion') does not include any supported TLS version (1.2 or 1.3)." } return ,([string[]]$local:Selected) } function Edit-SslVersionSupport { [CmdletBinding()] Param( [Parameter(Mandatory=$false)] [string]$MinimumTlsVersion, [Parameter(Mandatory=$false)] [string]$MaximumTlsVersion ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } $local:VersionSet = (Get-SafeguardTlsVersionSet -MinimumTlsVersion $MinimumTlsVersion -MaximumTlsVersion $MaximumTlsVersion) if (($null -ne $local:VersionSet) -and ($PSVersionTable.PSEdition -ne "Core")) { # Windows PowerShell negotiates TLS through ServicePointManager, so an explicit # TLS version range means enabling *only* the requested versions (fail closed # outside the range). On PowerShell 7+ this is done per-request via the # -SslProtocol parameter instead (see Get-SafeguardWebRequestPreference). Write-Verbose "Restricting TLS to ($($local:VersionSet -join ', ')) for Windows PowerShell" $local:Protocol = 0 foreach ($local:Version in $local:VersionSet) { $local:Name = (Get-SafeguardTlsEnumName $local:Version) if (-not ([System.Net.SecurityProtocolType].GetEnumNames() -contains $local:Name)) { throw "TLS $local:Version was requested, but this Windows PowerShell runtime does not support it. Upgrade the operating system/.NET Framework or use PowerShell 7." } $local:Protocol = $local:Protocol -bor [int][System.Enum]::Parse([System.Net.SecurityProtocolType], $local:Name) } [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]$local:Protocol return } Write-Verbose "Configuring SSL version support to be secure" # Remove SSLv3, if present if ([bool]([System.Net.ServicePointManager]::SecurityProtocol -band [System.Net.SecurityProtocolType]::Ssl3)) { [System.Net.ServicePointManager]::SecurityProtocol = ` [System.Net.ServicePointManager]::SecurityProtocol -band (-bnot [System.Net.SecurityProtocolType]::Ssl3) } # Remove TLS 1.0 and TLS 1.1, if present. Both are deprecated and Safeguard # requires TLS 1.2 or higher, so strip them to fail closed rather than let the # client silently negotiate a weak protocol. if ([bool]([System.Net.ServicePointManager]::SecurityProtocol -band [System.Net.SecurityProtocolType]::Tls)) { [System.Net.ServicePointManager]::SecurityProtocol = ` [System.Net.ServicePointManager]::SecurityProtocol -band (-bnot [System.Net.SecurityProtocolType]::Tls) } if ([bool]([System.Net.ServicePointManager]::SecurityProtocol -band [System.Net.SecurityProtocolType]::Tls11)) { [System.Net.ServicePointManager]::SecurityProtocol = ` [System.Net.ServicePointManager]::SecurityProtocol -band (-bnot [System.Net.SecurityProtocolType]::Tls11) } # Add TLS 1.2, if missing if (-not ([bool]([System.Net.ServicePointManager]::SecurityProtocol -band [System.Net.SecurityProtocolType]::Tls12))) { [System.Net.ServicePointManager]::SecurityProtocol = ` [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 } # Add TLS 1.3 if the runtime supports it $local:Tls13 = ([System.Net.SecurityProtocolType].GetEnumNames() -contains 'Tls13') if ($local:Tls13) { $local:Tls13Value = [System.Enum]::Parse([System.Net.SecurityProtocolType], 'Tls13') if (-not ([bool]([System.Net.ServicePointManager]::SecurityProtocol -band $local:Tls13Value))) { [System.Net.ServicePointManager]::SecurityProtocol = ` [System.Net.ServicePointManager]::SecurityProtocol -bor $local:Tls13Value } } } # Returns a hashtable of extra parameters that should be splatted onto every # Invoke-RestMethod / Invoke-WebRequest call that talks to Safeguard so that the # module's HTTP/TLS behavior is explicit: # # * HttpVersion = '1.1' -- SPP 9.0 exposes an HTTP/2-capable Standard binding. # HTTP/2 disallows the post-handshake TLS certificate request that client # certificate authentication relies on, so pin HTTP/1.1 to keep cert-auth # working regardless of the ingress in front of the appliance. The # -HttpVersion parameter only exists on PowerShell 7.3+, so it is omitted # where unavailable (older PowerShell 7 and Windows PowerShell 5.1 already # default to HTTP/1.1). # # * SslProtocol -- only when an explicit -MinimumTlsVersion/-MaximumTlsVersion # range is supplied. This makes PowerShell 7 negotiate exclusively within the # requested TLS version range, failing closed outside it. Windows PowerShell # 5.1 has no -SslProtocol parameter; there the range is enforced process-wide # by Edit-SslVersionSupport. # # By default (no -AsDefaultParameterValues) the keys are bare parameter names # suitable for splatting directly onto an Invoke-RestMethod call. With # -AsDefaultParameterValues the keys are qualified (e.g. # 'Invoke-RestMethod:HttpVersion') for assignment to $PSDefaultParameterValues. function Get-SafeguardWebRequestPreference { [CmdletBinding()] [OutputType([hashtable])] Param( [Parameter(Mandatory=$false)] [string]$MinimumTlsVersion, [Parameter(Mandatory=$false)] [string]$MaximumTlsVersion, [Parameter(Mandatory=$false)] [switch]$AsDefaultParameterValues ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } $local:Splat = @{} $local:VersionSet = (Get-SafeguardTlsVersionSet -MinimumTlsVersion $MinimumTlsVersion -MaximumTlsVersion $MaximumTlsVersion) if ($PSVersionTable.PSEdition -eq "Core") { $local:WebParameters = (Get-Command Invoke-RestMethod).Parameters if ($local:WebParameters.ContainsKey("HttpVersion")) { $local:Splat["HttpVersion"] = "1.1" } if ($null -ne $local:VersionSet) { if (-not ($local:WebParameters.ContainsKey("SslProtocol"))) { throw "A TLS version range was requested, but this PowerShell runtime does not support the -SslProtocol parameter." } $local:ProtocolInt = 0 foreach ($local:Version in $local:VersionSet) { $local:Name = (Get-SafeguardTlsEnumName $local:Version) if (-not ([System.Enum]::GetNames([Microsoft.PowerShell.Commands.WebSslProtocol]) -contains $local:Name)) { throw "TLS $local:Version was requested, but this PowerShell runtime does not support the $local:Name SSL protocol." } $local:ProtocolInt = $local:ProtocolInt -bor [int][Microsoft.PowerShell.Commands.WebSslProtocol]$local:Name } $local:Splat["SslProtocol"] = [Microsoft.PowerShell.Commands.WebSslProtocol]$local:ProtocolInt } } # Windows PowerShell 5.1: -HttpVersion does not exist (HTTP/1.1 is the default) # and the TLS version range is enforced by Edit-SslVersionSupport. if (-not $AsDefaultParameterValues) { return $local:Splat } $local:Defaults = @{} foreach ($local:Key in $local:Splat.Keys) { $local:Defaults["Invoke-RestMethod:$($local:Key)"] = $local:Splat[$local:Key] $local:Defaults["Invoke-WebRequest:$($local:Key)"] = $local:Splat[$local:Key] } return $local:Defaults } |