TLSleuth.psm1
|
#Region '.\private\Close-NetworkResources.ps1' -1 function Close-NetworkResources { <# .SYNOPSIS Safely disposes network resources used during TLS operations. #> [CmdletBinding()] param( [System.Net.Security.SslStream]$SslStream, [System.IO.Stream]$NetworkStream, [System.Net.Sockets.TcpClient]$TcpClient ) $fn = $MyInvocation.MyCommand.Name $sw = [System.Diagnostics.Stopwatch]::StartNew() Write-Verbose "[$fn] Begin (SslStream=$($null -ne $SslStream), NetworkStream=$($null -ne $NetworkStream), TcpClient=$($null -ne $TcpClient))" try { foreach ($resource in @($SslStream, $NetworkStream, $TcpClient)) { if ($null -eq $resource) { continue } try { if ($resource -is [System.IDisposable]) { $resource.Dispose() Write-Verbose "[$fn] Disposed $($resource.GetType().FullName)" } } catch { Write-Debug "[$fn] Dispose failed: $($_.Exception.GetType().FullName)" } } } finally { $sw.Stop() Write-Verbose "[$fn] Complete in $($sw.Elapsed)" } } #EndRegion '.\private\Close-NetworkResources.ps1' 37 #Region '.\private\Connect-TcpWithTimeout.ps1' -1 function Connect-TcpWithTimeout { <# .SYNOPSIS Opens a TcpClient and connects with a timeout. .OUTPUTS PSCustomObject { TcpClient, NetworkStream } #> [CmdletBinding()] param( [Parameter(Mandatory)] [Alias('Host')] [ValidateNotNullOrEmpty()] [string]$Hostname, [Parameter(Mandatory)] [ValidateRange(1,65535)] [int]$Port, [ValidateRange(1000,600000)] [int]$TimeoutMs = 10000 ) $fn = $MyInvocation.MyCommand.Name $sw = [System.Diagnostics.Stopwatch]::StartNew() Write-Verbose "[$fn] Begin (Target=$Hostname :$Port, TimeoutMs=$TimeoutMs)" $tcp = $null try { $tcp = [System.Net.Sockets.TcpClient]::new() $tcp.NoDelay = $true $task = $tcp.ConnectAsync($Hostname, $Port) if (-not $task.Wait($TimeoutMs)) { throw [System.TimeoutException]::new("Connection timeout after ${TimeoutMs}ms to $Hostname :$Port") } $netStream = $tcp.GetStream() Write-Verbose "[$fn] Connected to $Hostname :$Port" [PSCustomObject]@{ TcpClient = $tcp; NetworkStream = $netStream } } catch { try { if ($tcp) { $tcp.Dispose() } } catch {} $errorToThrow = $_.Exception if ($errorToThrow -is [System.AggregateException] -and $errorToThrow.InnerException) { throw $errorToThrow.InnerException } throw } finally { $sw.Stop() Write-Verbose "[$fn] Complete in $($sw.Elapsed)" } } #EndRegion '.\private\Connect-TcpWithTimeout.ps1' 56 #Region '.\private\ConvertTo-TlsCertificateResult.ps1' -1 function ConvertTo-TlsCertificateResult { <# .SYNOPSIS Builds a stable output object for certificate retrieval results. .OUTPUTS PSCustomObject #> [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Hostname, [Parameter(Mandatory)] [ValidateRange(1,65535)] [int]$Port, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$TargetHost, [Parameter(Mandatory)] [ValidateNotNull()] [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, [Parameter(Mandatory)] [ValidateNotNull()] [pscustomobject]$Validity, [System.Security.Authentication.SslProtocols]$NegotiatedProtocol, $CipherAlgorithm, [int]$CipherStrength, [timespan]$Elapsed = [timespan]::Zero ) $fn = $MyInvocation.MyCommand.Name $sw = [System.Diagnostics.Stopwatch]::StartNew() Write-Verbose "[$fn] Begin (Target=$Hostname :$Port, TargetHost=$TargetHost)" try { $result = [PSCustomObject]@{ PSTypeName = 'TLSleuth.CertificateResult' Hostname = $Hostname Port = $Port TargetHost = $TargetHost Subject = $Certificate.Subject Issuer = $Certificate.Issuer Thumbprint = $Certificate.Thumbprint SerialNumber = $Certificate.SerialNumber NotBefore = $Certificate.NotBefore NotAfter = $Certificate.NotAfter IsValidNow = $Validity.IsValidNow DaysUntilExpiry = $Validity.DaysUntilExpiry NegotiatedProtocol = $NegotiatedProtocol CipherAlgorithm = $CipherAlgorithm CipherStrength = $CipherStrength ElapsedMs = [int][Math]::Round($Elapsed.TotalMilliseconds) Certificate = $Certificate } Write-Verbose "[$fn] Built result for Subject='$($Certificate.Subject)' with protocol '$NegotiatedProtocol'." $result } finally { $sw.Stop() Write-Verbose "[$fn] Complete in $($sw.Elapsed)" } } #EndRegion '.\private\ConvertTo-TlsCertificateResult.ps1' 73 #Region '.\private\ConvertTo-TlsProtocolOptions.ps1' -1 function ConvertTo-TlsProtocolOptions { <# .SYNOPSIS Converts user protocol names into an SslProtocols flag enum. .OUTPUTS System.Security.Authentication.SslProtocols #> [CmdletBinding()] [OutputType([System.Security.Authentication.SslProtocols])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string[]]$TlsProtocols ) $fn = $MyInvocation.MyCommand.Name $sw = [System.Diagnostics.Stopwatch]::StartNew() Write-Verbose "[$fn] Begin (TlsProtocols=$($TlsProtocols -join ','))" try { $map = @{ SystemDefault = [System.Security.Authentication.SslProtocols]::None Ssl3 = [System.Security.Authentication.SslProtocols]::Ssl3 Tls = [System.Security.Authentication.SslProtocols]::Tls Tls11 = [System.Security.Authentication.SslProtocols]::Tls11 Tls12 = [System.Security.Authentication.SslProtocols]::Tls12 Tls13 = [System.Security.Authentication.SslProtocols]::Tls13 } $result = [System.Security.Authentication.SslProtocols]::None foreach ($name in $TlsProtocols) { if (-not $map.ContainsKey($name)) { throw [System.ArgumentException]::new("Unsupported TLS protocol value: $name") } if ($name -eq 'SystemDefault') { if ($TlsProtocols.Count -gt 1) { throw [System.ArgumentException]::new('SystemDefault cannot be combined with explicit protocol values.') } Write-Verbose "[$fn] Using SystemDefault TLS policy." return [System.Security.Authentication.SslProtocols]::None } $result = $result -bor $map[$name] } Write-Verbose "[$fn] Resolved protocols: $result" $result } finally { $sw.Stop() Write-Verbose "[$fn] Complete in $($sw.Elapsed)" } } #EndRegion '.\private\ConvertTo-TlsProtocolOptions.ps1' 57 #Region '.\private\Get-RemoteCertificate.ps1' -1 function Get-RemoteCertificate { <# .SYNOPSIS Extracts the remote certificate from an authenticated SslStream. .OUTPUTS System.Security.Cryptography.X509Certificates.X509Certificate2 #> [CmdletBinding()] [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])] param( [Parameter(Mandatory)] [ValidateNotNull()] [System.Net.Security.SslStream]$SslStream ) $fn = $MyInvocation.MyCommand.Name $sw = [System.Diagnostics.Stopwatch]::StartNew() Write-Verbose "[$fn] Begin (IsAuthenticated=$($SslStream.IsAuthenticated))" try { if (-not $SslStream.RemoteCertificate) { throw [System.InvalidOperationException]::new('Remote endpoint did not provide a certificate.') } $certificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($SslStream.RemoteCertificate) Write-Verbose "[$fn] Retrieved certificate Subject='$($certificate.Subject)'" $certificate } finally { $sw.Stop() Write-Verbose "[$fn] Complete in $($sw.Elapsed)" } } #EndRegion '.\private\Get-RemoteCertificate.ps1' 35 #Region '.\private\Invoke-SmtpStartTlsNegotiation.ps1' -1 function Invoke-SmtpStartTlsNegotiation { <# .SYNOPSIS Performs SMTP STARTTLS negotiation over an existing plaintext stream. .OUTPUTS PSCustomObject #> [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNull()] [System.IO.Stream]$NetworkStream, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$EhloName, [ValidateRange(1000,600000)] [int]$TimeoutMs = 10000 ) $fn = $MyInvocation.MyCommand.Name $sw = [System.Diagnostics.Stopwatch]::StartNew() Write-Verbose "[$fn] Begin (EhloName=$EhloName, TimeoutMs=$TimeoutMs)" if (-not $NetworkStream.CanRead -or -not $NetworkStream.CanWrite) { throw [System.InvalidOperationException]::new('SMTP STARTTLS negotiation requires a readable and writable stream.') } function Read-SmtpLine { param( [Parameter(Mandatory)] [System.IO.Stream]$Stream, [Parameter(Mandatory)] [int]$ReadTimeoutMs ) $bytes = [System.Collections.Generic.List[byte]]::new() $buffer = New-Object byte[] 1 while ($true) { try { $read = $Stream.Read($buffer, 0, 1) } catch [System.IO.IOException] { $inner = $_.Exception.InnerException if ($inner -is [System.Net.Sockets.SocketException] -and $inner.SocketErrorCode -eq [System.Net.Sockets.SocketError]::TimedOut) { throw [System.TimeoutException]::new("SMTP negotiation timed out after ${ReadTimeoutMs}ms.") } throw } if ($read -eq 0) { throw [System.IO.EndOfStreamException]::new('SMTP server closed the connection unexpectedly.') } $b = $buffer[0] if ($b -eq 10) { break } if ($b -ne 13) { $bytes.Add($b) } if ($bytes.Count -gt 4096) { throw [System.InvalidOperationException]::new('SMTP response line exceeded 4096 bytes.') } } [System.Text.Encoding]::ASCII.GetString($bytes.ToArray()) } function Read-SmtpResponse { param( [Parameter(Mandatory)] [System.IO.Stream]$Stream, [Parameter(Mandatory)] [int]$ReadTimeoutMs ) $lines = [System.Collections.Generic.List[string]]::new() $firstLine = Read-SmtpLine -Stream $Stream -ReadTimeoutMs $ReadTimeoutMs $lines.Add($firstLine) $statusCode = 0 if ($firstLine.Length -lt 3 -or -not [int]::TryParse($firstLine.Substring(0, 3), [ref]$statusCode)) { throw [System.InvalidOperationException]::new("Invalid SMTP response line: '$firstLine'") } $statusPrefix = '{0:D3}' -f $statusCode $isMultiline = ($firstLine.Length -ge 4 -and $firstLine[3] -eq '-') if ($isMultiline) { while ($true) { $line = Read-SmtpLine -Stream $Stream -ReadTimeoutMs $ReadTimeoutMs $lines.Add($line) if ($line.Length -ge 4 -and $line.StartsWith($statusPrefix) -and $line[3] -eq '-') { continue } if ($line.Length -ge 4 -and $line.StartsWith($statusPrefix) -and $line[3] -eq ' ') { break } throw [System.InvalidOperationException]::new("Invalid SMTP multiline response continuation: '$line'") } } [PSCustomObject]@{ Code = $statusCode Lines = [string[]]$lines Message = ($lines -join "`n") } } function Send-SmtpCommand { param( [Parameter(Mandatory)] [System.IO.Stream]$Stream, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Command ) $payload = [System.Text.Encoding]::ASCII.GetBytes("$Command`r`n") $Stream.Write($payload, 0, $payload.Length) $Stream.Flush() } $originalReadTimeout = $null $originalWriteTimeout = $null $timeoutsApplied = $false try { if ($NetworkStream.CanTimeout) { $originalReadTimeout = $NetworkStream.ReadTimeout $originalWriteTimeout = $NetworkStream.WriteTimeout $NetworkStream.ReadTimeout = $TimeoutMs $NetworkStream.WriteTimeout = $TimeoutMs $timeoutsApplied = $true } $banner = Read-SmtpResponse -Stream $NetworkStream -ReadTimeoutMs $TimeoutMs if ($banner.Code -ne 220) { throw [System.InvalidOperationException]::new("SMTP server did not return 220 greeting. Received: $($banner.Message)") } Write-Verbose "[$fn] Received SMTP greeting code $($banner.Code)." Send-SmtpCommand -Stream $NetworkStream -Command "EHLO $EhloName" $ehloResponse = Read-SmtpResponse -Stream $NetworkStream -ReadTimeoutMs $TimeoutMs if ($ehloResponse.Code -ne 250) { throw [System.InvalidOperationException]::new("SMTP EHLO failed. Received: $($ehloResponse.Message)") } Write-Verbose "[$fn] EHLO accepted with code $($ehloResponse.Code)." $supportsStartTls = $false foreach ($line in $ehloResponse.Lines) { if ($line.Length -lt 4) { continue } $capability = $line.Substring(4).Trim() if ($capability -match '^(?i)STARTTLS(?:\s|$)') { $supportsStartTls = $true break } } if (-not $supportsStartTls) { throw [System.InvalidOperationException]::new('SMTP server does not advertise STARTTLS in EHLO response.') } Send-SmtpCommand -Stream $NetworkStream -Command 'STARTTLS' $startTlsResponse = Read-SmtpResponse -Stream $NetworkStream -ReadTimeoutMs $TimeoutMs if ($startTlsResponse.Code -ne 220) { throw [System.InvalidOperationException]::new("SMTP STARTTLS command was not accepted. Received: $($startTlsResponse.Message)") } Write-Verbose "[$fn] STARTTLS accepted with code $($startTlsResponse.Code)." [PSCustomObject]@{ GreetingCode = $banner.Code EhloCode = $ehloResponse.Code StartTlsCode = $startTlsResponse.Code } } catch { Write-Debug "[$fn] STARTTLS negotiation failed for EHLO name '$EhloName': $($_.Exception.GetType().FullName)" throw } finally { if ($timeoutsApplied) { try { $NetworkStream.ReadTimeout = $originalReadTimeout } catch {} try { $NetworkStream.WriteTimeout = $originalWriteTimeout } catch {} } $sw.Stop() Write-Verbose "[$fn] Complete in $($sw.Elapsed)" } } #EndRegion '.\private\Invoke-SmtpStartTlsNegotiation.ps1' 204 #Region '.\private\Invoke-WithRetry.ps1' -1 function Invoke-WithRetry { <# .SYNOPSIS Invokes a script block with bounded retry behavior for transient operations. .OUTPUTS Any output from the script block. #> [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNull()] [scriptblock]$ScriptBlock, [ValidateRange(1,10)] [int]$MaxAttempts = 3, [ValidateRange(0,60000)] [int]$DelayMs = 250, [string[]]$RetryOnExceptionType = @( 'System.TimeoutException', 'System.Net.Sockets.SocketException', 'System.IO.IOException' ) ) $fn = $MyInvocation.MyCommand.Name $sw = [System.Diagnostics.Stopwatch]::StartNew() Write-Verbose "[$fn] Begin (MaxAttempts=$MaxAttempts, DelayMs=$DelayMs)" try { $attempt = 0 while ($true) { $attempt++ try { Write-Verbose "[$fn] Attempt $attempt of $MaxAttempts." return & $ScriptBlock } catch { $exceptionType = $_.Exception.GetType().FullName $canRetry = ($attempt -lt $MaxAttempts) -and ($RetryOnExceptionType -contains $exceptionType) if (-not $canRetry) { Write-Verbose "[$fn] Stopping retries after $exceptionType on attempt $attempt." throw } Write-Verbose "[$fn] Retrying after $exceptionType (attempt $attempt of $MaxAttempts)." Write-Debug "[$fn] Retrying after $exceptionType (attempt $attempt of $MaxAttempts)." if ($DelayMs -gt 0) { Start-Sleep -Milliseconds $DelayMs } } } } finally { $sw.Stop() Write-Verbose "[$fn] Complete in $($sw.Elapsed)" } } #EndRegion '.\private\Invoke-WithRetry.ps1' 61 #Region '.\private\Start-TlsHandshake.ps1' -1 function Start-TlsHandshake { <# .SYNOPSIS Starts a TLS handshake on an existing network stream. .OUTPUTS PSCustomObject { SslStream, NegotiatedProtocol, CipherAlgorithm, CipherStrength } #> [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNull()] [System.IO.Stream]$NetworkStream, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$TargetHost, [Parameter(Mandatory)] [System.Security.Authentication.SslProtocols]$SslProtocols, [ValidateRange(1000,600000)] [int]$TimeoutMs = 10000, [switch]$SkipCertificateValidation ) $fn = $MyInvocation.MyCommand.Name $sw = [System.Diagnostics.Stopwatch]::StartNew() Write-Verbose "[$fn] Begin (Target=$TargetHost, Protocols=$SslProtocols, TimeoutMs=$TimeoutMs, SkipValidation=$SkipCertificateValidation)" $ssl = $null $validationCallback = $null if ($SkipCertificateValidation) { if (-not ('TLSleuth.CertificateValidationCallbacks' -as [type])) { Add-Type -TypeDefinition @" using System.Net.Security; using System.Security.Cryptography.X509Certificates; namespace TLSleuth { public static class CertificateValidationCallbacks { public static bool AcceptAll(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; } } } "@ } $validationCallback = [System.Net.Security.RemoteCertificateValidationCallback][TLSleuth.CertificateValidationCallbacks]::AcceptAll } try { $ssl = [System.Net.Security.SslStream]::new($NetworkStream, $false, $validationCallback) $task = $ssl.AuthenticateAsClientAsync($TargetHost, $null, $SslProtocols, $false) if (-not $task.Wait($TimeoutMs)) { throw [System.TimeoutException]::new("TLS handshake timeout after ${TimeoutMs}ms for $TargetHost") } Write-Verbose "[$fn] Handshake succeeded (Protocol=$($ssl.SslProtocol), Cipher=$($ssl.CipherAlgorithm), Strength=$($ssl.CipherStrength))." [PSCustomObject]@{ SslStream = $ssl NegotiatedProtocol = $ssl.SslProtocol CipherAlgorithm = $ssl.CipherAlgorithm CipherStrength = $ssl.CipherStrength } } catch { Write-Debug "[$fn] Handshake failed for ${TargetHost}: $($_.Exception.GetType().FullName)" try { if ($ssl) { $ssl.Dispose() } } catch {} $errorToThrow = $_.Exception if ($errorToThrow -is [System.AggregateException] -and $errorToThrow.InnerException) { throw $errorToThrow.InnerException } throw } finally { $sw.Stop() Write-Verbose "[$fn] Complete in $($sw.Elapsed)" } } #EndRegion '.\private\Start-TlsHandshake.ps1' 86 #Region '.\private\Test-TlsCertificateValidity.ps1' -1 function Test-TlsCertificateValidity { <# .SYNOPSIS Evaluates date-based validity of an X509 certificate. .OUTPUTS PSCustomObject { IsValidNow, NotBefore, NotAfter, DaysUntilExpiry } #> [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNull()] [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, [datetime]$AsOf = (Get-Date) ) $fn = $MyInvocation.MyCommand.Name $sw = [System.Diagnostics.Stopwatch]::StartNew() Write-Verbose "[$fn] Begin (Subject=$($Certificate.Subject), AsOf=$AsOf)" try { $notBefore = $Certificate.NotBefore $notAfter = $Certificate.NotAfter $isValid = ($AsOf -ge $notBefore) -and ($AsOf -le $notAfter) $daysUntilExpiry = [int][Math]::Floor(($notAfter - $AsOf).TotalDays) Write-Verbose "[$fn] Validity computed (IsValidNow=$isValid, DaysUntilExpiry=$daysUntilExpiry)." [PSCustomObject]@{ IsValidNow = $isValid NotBefore = $notBefore NotAfter = $notAfter DaysUntilExpiry = $daysUntilExpiry } } finally { $sw.Stop() Write-Verbose "[$fn] Complete in $($sw.Elapsed)" } } #EndRegion '.\private\Test-TlsCertificateValidity.ps1' 41 #Region '.\public\Get-TLSleuthCertificate.ps1' -1 function Get-TLSleuthCertificate { [CmdletBinding()] [OutputType([pscustomobject])] param( # Accepts strings directly from the pipeline AND by matching property name [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('Host','DnsName','ComputerName','Target','Name')] [ValidateNotNullOrEmpty()] [string]$Hostname, # Accepts values from objects with a matching property name in the pipeline [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(1,65535)] [int]$Port = 443, # Accepts values from objects with a matching property name in the pipeline [Parameter(ValueFromPipelineByPropertyName)] [Alias('SNI','ServerName')] [string]$TargetHost, [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('ImplicitTls','SmtpStartTls')] [string]$Transport = 'ImplicitTls', [Parameter(ValueFromPipelineByPropertyName)] [Alias('EhloName','ClientName')] [string]$SmtpEhloName, [ValidateSet('SystemDefault','Ssl3','Tls','Tls11','Tls12','Tls13')] [string[]]$TlsProtocols = @('SystemDefault'), [ValidateRange(1,600)] [int]$TimeoutSec = 10, [switch]$SkipCertificateValidation ) begin { $fn = $MyInvocation.MyCommand.Name $pipelineSw = [System.Diagnostics.Stopwatch]::StartNew() $processed = 0 $timeoutMs = $TimeoutSec * 1000 # foreach ($requiredFunction in @( # 'ConvertTo-TlsProtocolOptions', # 'Connect-TcpWithTimeout', # 'Invoke-SmtpStartTlsNegotiation', # 'Start-TlsHandshake', # 'Get-RemoteCertificate', # 'Test-TlsCertificateValidity', # 'ConvertTo-TlsCertificateResult', # 'Close-NetworkResources', # 'Invoke-WithRetry' # )) { # if (-not (Get-Command -Name $requiredFunction -ErrorAction SilentlyContinue)) { # $helperPath = Join-Path (Join-Path $PSScriptRoot '..\private') "$requiredFunction.ps1" # if (-not (Test-Path -Path $helperPath)) { # throw "Required helper function '$requiredFunction' was not found at path '$helperPath'." # } # . $helperPath # } # } $sslProtocols = ConvertTo-TlsProtocolOptions -TlsProtocols $TlsProtocols Write-Verbose "[$fn] Begin (Transport=$Transport, TimeoutSec=$TimeoutSec, Protocols=$($TlsProtocols -join ','))" } process { $itemSw = [System.Diagnostics.Stopwatch]::StartNew() $processed++ $target = if ([string]::IsNullOrWhiteSpace($TargetHost)) { $Hostname } else { $TargetHost } $tcpConnection = $null $tlsSession = $null $certificate = $null try { $tcpConnection = Invoke-WithRetry -ScriptBlock { Connect-TcpWithTimeout -Hostname $Hostname -Port $Port -TimeoutMs $timeoutMs } if ($Transport -eq 'SmtpStartTls') { $ehloName = $SmtpEhloName if ([string]::IsNullOrWhiteSpace($ehloName)) { $ehloName = [System.Net.Dns]::GetHostName() if ([string]::IsNullOrWhiteSpace($ehloName)) { $ehloName = 'localhost' } } Invoke-SmtpStartTlsNegotiation ` -NetworkStream $tcpConnection.NetworkStream ` -EhloName $ehloName ` -TimeoutMs $timeoutMs | Out-Null } $tlsSession = Start-TlsHandshake ` -NetworkStream $tcpConnection.NetworkStream ` -TargetHost $target ` -SslProtocols $sslProtocols ` -TimeoutMs $timeoutMs ` -SkipCertificateValidation:$SkipCertificateValidation $certificate = Get-RemoteCertificate -SslStream $tlsSession.SslStream $validity = Test-TlsCertificateValidity -Certificate $certificate ConvertTo-TlsCertificateResult ` -Hostname $Hostname ` -Port $Port ` -TargetHost $target ` -Certificate $certificate ` -Validity $validity ` -NegotiatedProtocol $tlsSession.NegotiatedProtocol ` -CipherAlgorithm $tlsSession.CipherAlgorithm ` -CipherStrength $tlsSession.CipherStrength ` -Elapsed $itemSw.Elapsed } finally { $itemSw.Stop() Close-NetworkResources -SslStream $tlsSession.SslStream -NetworkStream $tcpConnection.NetworkStream -TcpClient $tcpConnection.TcpClient } } end { $pipelineSw.Stop() Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete (Processed=$processed) in $($pipelineSw.Elapsed)" } } #EndRegion '.\public\Get-TLSleuthCertificate.ps1' 137 |