The function takes 3 parameters, -Server, -Port and -ToBase64. I think all of them are self describing. Without the -ToBase64 the function returns a X509Certificate2 object from which you can get the Subject, Issuer, Thumbprint (...) fields.
Sample execution:
PS C:\> Get-CertificateChain -server lon001 -Port 636 -ToBase64 > c:\cert.cer
I used the function to collect all certificates from all our Domain Controller in our Active Directory domain, lo and behold, we had a few Domain Controllers with old certs. So it was very useful. Here's the function...
Function Get-CertificateChain {
param(
[string]$server=$(throw "Mandatory parameter -Server is missing."),
[int]$port=$(throw "Mandatory parameter -Port is missing."),
[switch]$ToBase64
)
$code=@"
using System;
using System.Collections;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Threading;
namespace CosmosKey.Powershell
{
public class SslUtility
{
private static byte[] CertChain;
private static object Lock = new object();
private static Hashtable certificateErrors = new Hashtable();
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
byte[] data = certificate.Export(X509ContentType.Cert);
lock (Lock)
{
CertChain = data;
Monitor.Pulse(Lock);
}
return true;
}
public static byte[] GetCertificate(string serverName, int port)
{
TcpClient client = new TcpClient(serverName,port);
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback (ValidateServerCertificate),
null
);
try
{
lock (Lock)
{
sslStream.BeginAuthenticateAsClient(serverName,null,null);
bool didTimeout = Monitor.Wait(Lock);
}
}
finally
{
client.Close();
}
return CertChain;
}
}
}
"@
Add-Type $code
[byte[]]$certData = [CosmosKey.Powershell.SslUtility]::GetCertificate($server,$port)
if($ToBase64){
[convert]::ToBase64String($certData)
} else {
$cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.import($certData)
$cert
}
}
param(
[string]$server=$(throw "Mandatory parameter -Server is missing."),
[int]$port=$(throw "Mandatory parameter -Port is missing."),
[switch]$ToBase64
)
$code=@"
using System;
using System.Collections;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Threading;
namespace CosmosKey.Powershell
{
public class SslUtility
{
private static byte[] CertChain;
private static object Lock = new object();
private static Hashtable certificateErrors = new Hashtable();
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
byte[] data = certificate.Export(X509ContentType.Cert);
lock (Lock)
{
CertChain = data;
Monitor.Pulse(Lock);
}
return true;
}
public static byte[] GetCertificate(string serverName, int port)
{
TcpClient client = new TcpClient(serverName,port);
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback (ValidateServerCertificate),
null
);
try
{
lock (Lock)
{
sslStream.BeginAuthenticateAsClient(serverName,null,null);
bool didTimeout = Monitor.Wait(Lock);
}
}
finally
{
client.Close();
}
return CertChain;
}
}
}
"@
Add-Type $code
[byte[]]$certData = [CosmosKey.Powershell.SslUtility]::GetCertificate($server,$port)
if($ToBase64){
[convert]::ToBase64String($certData)
} else {
$cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.import($certData)
$cert
}
}
Please enjoy!
Thanks so much for taking the time to post this! This was exactly what I was looking for and although there are a few other sites that mention this same approach this is the only all-inclusive example that I have seen of something like this. It was super helpful to me.
ReplyDeleteThis was exactly what I was looking for and although there are a few other sites that mention this same approach this is the only all-inclusive example that I have seen of something like this. It was super helpful to me.
ReplyDeleteOxford Security
If you are getting error in Powershell Core
ReplyDeleteException calling "Import" with "1" argument(s): "X509Certificate is immutable on this platform. Use the equivalent constructor instead."
Then edit the last else clause from
$cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.import($certData)
$cert
to
new-object System.Security.Cryptography.X509Certificates.X509Certificate2(,$certData)