0

I've found several answer about how to download the certificate for a website in PowerShell using TcpClient.

function Get-RemoteCertificate {
       
    [CmdletBinding()]
    [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate])]
    param (
      [Parameter(Mandatory, ValueFromPipeline)]
      [ValidateNotNull()]
      [Uri]$Uri
    )

    process {
        try {# connecting
            $TcpClient = [System.Net.Sockets.TcpClient]::new($Uri.Host, $Uri.Port)
    
            try {# getting SSL
                $SslStream = [System.Net.Security.SslStream]::new($TcpClient.GetStream())
                $SslStream.AuthenticateAsClient($Uri.Host)
                $SslStream.RemoteCertificate
            } finally {
                $SslStream.Dispose()
            }# end SSL
    
        } finally {
            $TcpClient.Dispose()
        }# end connect
    }
}

But as the TLS handshake will fail when the certificate isn't trusted, I can't download self signed certificates and I will get the error

Exception calling ".ctor" with "2" argument(s): "A connection attempt failed because the connected party did not 
properly respond after a period of time, or established connection failed because connected host has failed to 
respond

Is there a way to download self signed certificates without using OpenSSL?

Dennis
  • 871
  • 9
  • 29
  • You are not showing how you are setting your connection. SSL-site requires this at the top of your script: *** [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]:: Tls12 ***. Are you saying the site is not SSL? – postanote Aug 29 '22 at 23:44
  • *"A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond"* - this error does not look like a certificate problem to me. The TCP connection already fails, which means it will not even try TLS and thus no certificates are involved here. – Steffen Ullrich Aug 30 '22 at 05:01
  • @postanote As I said, this isn't my code. But if you try it out on a site with a public certificate you will see that you get the certifcate... To connect to i.e. google, just enter `$TcpClient = [System.Net.Sockets.TcpClient]::new('www.google.com', 443)` `$SslStream = [System.Net.Security.SslStream]::new($TcpClient.GetStream())` – Dennis Aug 30 '22 at 06:44
  • @SteffenUllrich That's the only difference. Try it out on a site that you know are using a self signed certificate. – Dennis Aug 30 '22 at 06:45
  • @Dennis: *"But if you try it out on a site with a public certificate you will see that you get the certifcate... To connect to i.e. google ...."* - You comparing a site which is not reachable and has a self-signed certificate against a site which is reachable and has a publicly trusted certificate. Try instead a site which is reachable and has a self-signed certificate. – Steffen Ullrich Aug 30 '22 at 07:08
  • @SteffenUllrich Of course the site is reachable. `Invoke-WebRequest 'URI' -SkipCertificateCheck` is ok. That is not the issue... – Dennis Aug 30 '22 at 07:58

2 Answers2

2

This answer is using an old HttpWebRequest that is depreciated and will not work with powershell 7. Taken from this great answer:

The HttpWebRequest API surface has not been fully ported to the newer versions of .NET/Core, as detailed in this Github issue:

HttpWebRequest is API which is obsolete - see https://github.com/dotnet/platform-compat/blob/master/docs/DE0003.md.
We ported only the most important parts of it to .NET Core. The recommended Networking API is HttpClient.

Without using your sample, you can achieve this simple task based on this answer

$ub = new-object System.UriBuilder -argumentlist 'https', '1.1.1.1', 443
$uri = $ub.Uri

# Disable the verification: 
[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
$req = [Net.HttpWebRequest]::Create($Uri.AbsoluteUri)
try {
    $req.GetResponse() | Out-Null
    # Export the file, or return it if you want to keep a function
    $req.ServicePoint.Certificate.Export("Cert") | Set-Content -Path "C:\temp\test.crt"  -Encoding Byte
} catch {
    Write-Host Exception while checking URL $url`: $_ -f Red
}
Brice
  • 786
  • 4
  • 16
  • Why do I need to transform/replace my URI using `$uri = $ub.Uri`? I.e. Google would be [URI]$Uri = 'www.google.com'`. I don't understand where to enter the FQDN. – Dennis Aug 30 '22 at 16:10
  • 2
    it's just because in your sample you were using [Uri]$Uri as an argument. So I used an UriBuilder to get an URI Object, the FQDN in the example is "1.1.1.1". If you only want to supply the `$Uri = "https://www.google.com"` replace this line `$req = [Net.HttpWebRequest]::Create($Uri.AbsoluteUri)` with `$req = [Net.HttpWebRequest]::Create($Uri)` – Brice Aug 30 '22 at 16:16
  • Works like a charm in PoSH 5.1 :) (but it fails in PWSH 7.2.x) – Dennis Sep 09 '22 at 11:53
  • 1
    You're totally right. Seems like the HttpWebRequest API is depreciated, I will edit my answer to add a warning. – Brice Sep 10 '22 at 13:27
  • I don't need to use $req.EndGetResponse("some_arg_I_don't_know") to avoid memory leaks? – Dennis Sep 12 '22 at 18:58
1

NOTE: This is considered dangerous and you should think about the possible negative side effects of using this technique.

You can use a different overload when instantiating [System.Net.Security.SslStream] that ignores cert errors as shown here.

function Get-RemoteCertificate {
    [CmdletBinding()]
    [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate])]
    param (
        [Parameter(Mandatory,ValueFromPipeline)]
        [ValidateNotNull()]
        [Uri]$Uri
    )

    process {
        try {
            $TcpClient = [System.Net.Sockets.TcpClient]::new($Uri.Host, $Uri.Port)
            try {
                $SslStream = [System.Net.Security.SslStream]::new(
                    $TcpClient.GetStream(),
                    $True,
                    [System.Net.Security.RemoteCertificateValidationCallback]{$true}
                )
                $SslStream.AuthenticateAsClient($Uri.Host)
                $SslStream.RemoteCertificate
            } finally {
                $SslStream.Dispose()
            }
        } finally {
            $TcpClient.Dispose()
        }
    }
}
Doug Maurer
  • 8,090
  • 3
  • 12
  • 13
  • Line 12 | $TcpClient = [System.Net.Sockets.TcpClient]::new($Uri.Hos … | Exception calling ".ctor" with "2" argument(s): "A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. – Dennis Sep 09 '22 at 11:47