I am trying to create a secure socket with Powershell, but it fails during the call to AuthenticateAsClient with a variety of errors like
Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
or
A call to SSPI failed, see inner exception.
The actual error seems to vary according to which host I am trying to connect with. The code needed to replicate the issue is very simple assuming you have a server available that has disabled TLS 1.0. Note I have tried a variety of server versions, but the below is tested on Windows Server 2019, default configuration and Powershell 5.1.
$hostName = "myserver.com"
$port = 443
$client = New-Object Net.Sockets.TcpClient
$client.Connect($hostName,$port)
$ssl = New-Object System.Net.Security.SslStream($client.GetStream())
try {
$ssl.AuthenticateAsClient($hostName)
"Host: $hostName"
"Authenticated: $($ssl.IsAuthenticated)"
"Algorithm: $($ssl.CipherAlgorithm) $($ssl.CipherStrength)"
"Hash: $($ssl.HashAlgorithm) $($ssl.HashStrength)"
"Key exchange algorithm: $($ssl.KeyExchangeAlgorithm) $($ssl.KeyExchangeStrength)"
"SSL protocol: $($ssl.SslProtocol)"
} catch {
Write-Error $_
} finally {
$client.close()
}
There is a common suggestion here and on the interwebs to set the available protocols to use TLS 1.2 like the following:
[System.Net.ServicePointManager]::SecurityProtocol =[System.Net.SecurityProtocolType]'Tls, Tls11, Tls12, Tls13'
The implication is that this will allow dot net to automatically negotiate the highest available version of TLS on the server, but it will never attempt to negotiate more than TLS 1.0. When that old protocol version is disabled on the server, as it commonly is these days, the call will fail.
If this code does connect, it will display the version of TLS that it used to connect and you can confirm if your server still has TLS 1.0 enabled.
After a bunch of trial and error, the only conclusion I could come to is there is some bug in Powershell associated with the TLS protocol that basically ignores the values set globally. However, there is a solution if the TLS protocols are configured on the local machine. Simply modify your call as follows:
$ssl.AuthenticateAsClient( $HostAddress, $null, [System.Net.SecurityProtocolType]'Tls, Tls12', $false )
List whatever TLS versions you need directly in this overloaded version of the call. Now it will negotiate the highest one as expected.