I'm having the usual scenario with using Invoke-WebRequest in environments that have to use Powershell 5.1 or older, where the self-signed certificate errors the cmdlet with 'Invoke-WebRequest : The underlying connection was closed: An unexpected error occurred on a send.'
The C# code from this post is the only code that works after testing all the other solutions online: Where to place RemoteCertificateValidationCallback?
$code = @"
public class SSLHandler
{
public static System.Net.Security.RemoteCertificateValidationCallback GetSSLHandler()
{
return new System.Net.Security.RemoteCertificateValidationCallback((sender, certificate, chain, policyErrors) => { return true; });
}
}
"@
Add-Type -TypeDefinition $code
#disable checks
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = [SSLHandler]::GetSSLHandler()
#do the request
try
{
invoke-WebRequest -Uri myurl -UseBasicParsing
} catch {
# do something
} finally {
#enable checks again
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = $null
}
I'm trying to understand why this C# class works in Powershell, but when I try to rewrite the class as a class in Powershell or as a function, it does not work when setting the [System.Net.ServicePointManager]::ServerCertificateValidationCallback to use the Powershell equivalent class.
This is my code:
#Works
$UnsafeWebRequest = @'
public class UnsafeWebRequest
{
public static System.Net.Security.RemoteCertificateValidationCallback DangerousAcceptAnyServerCertificateValidator()
{
return new System.Net.Security.RemoteCertificateValidationCallback( (Sender, Certificate, Chain, PolicyErrors) => { return true; } );
}
}
'@
Add-Type -TypeDefinition $UnsafeWebRequest
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = [UnsafeWebRequest]::DangerousAcceptAnyServerCertificateValidator()
#Does not work
class UnsafeWebRequest
{
static [System.Net.Security.RemoteCertificateValidationCallback] DangerousAcceptAnyServerCertificateValidator()
{
return [System.Net.Security.RemoteCertificateValidationCallback]{
param
(
[System.Object] $Sender,
[System.Security.Cryptography.X509Certificates.X509Certificate] $X509Certificate,
[System.Security.Cryptography.X509Certificates.X509Chain] $X509Chain,
[System.Net.Security.SslPolicyErrors] $SslPolicyErrors
)
return $True
}
}
}
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = [UnsafeWebRequest]::DangerousAcceptAnyServerCertificateValidator()
The C# code will ignore self-signed certificates in Invoke-WebRequest calls, but the Powershell one will just error out with 'The underlying connection was closed: An unexpected error occurred on a send.' and I can't understand why.
I've tried to write the powershell code in different ways like the C# code, but the only thing that works with [System.Net.ServicePointManager]::ServerCertificateValidationCallback is the C# class.
Edit. After the explanation from mklement0 I figured I'll broaden my search and found two other solutions that could be Powershell-native. One using the obsolete [System.Net.ServicePointManager]::CertificatePolicy and one using the [System.Net.Security.RemoteCertificateValidationCallback] as a [System.Linq.Expressions.Expression]::Lambda expression as a Powershell function.
Thanks @mklement0 & https://github.com/PowerShell/PowerShell/issues/17340
Solution 1:
function New-RemoteCertificateValidationCallbackHandler
{
[CmdletBinding()]
[OutputType( [System.Net.Security.RemoteCertificateValidationCallback] )]
Param ()
Begin
{
Add-Type -AssemblyName System.Net
}
Process
{
$LinqLambdaExpression = [System.Linq.Expressions.Expression]::Lambda(
[System.Net.Security.RemoteCertificateValidationCallback],
[System.Linq.Expressions.Expression]::Block(
[System.Linq.Expressions.Expression]::Constant($True)
),
[System.Linq.Expressions.ParameterExpression[]](
[System.Linq.Expressions.Expression]::Variable([System.Object]),
[System.Linq.Expressions.Expression]::Variable([System.Security.Cryptography.X509Certificates.X509Certificate]),
[System.Linq.Expressions.Expression]::Variable([System.Security.Cryptography.X509Certificates.X509Chain]),
[System.Linq.Expressions.Expression]::Variable([System.Net.Security.SslPolicyErrors])
)
)
}
End
{
$LinqLambdaExpression.Compile()
}
}
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = New-RemoteCertificateValidationCallbackHandler
Solution 2:
#CertificatePolicy is obsoleted for this type, please use ServerCertificateValidationCallback instead.
class TrustAllCertificatePolicy : System.Net.ICertificatePolicy
{
[System.Boolean] CheckValidationResult (
[System.Net.ServicePoint] $ServicePoint,
[System.Security.Cryptography.X509Certificates.X509Certificate] $X509Certificate,
[System.Net.WebRequest] $WebRequest,
[System.Int32] $CertificateProblem
)
{
return $True
}
}
[System.Net.ServicePointManager]::CertificatePolicy = [TrustAllCertificatePolicy]::new()