I am building some .net code that will run unattended on a scheduled basis on one of our servers. Part of its execution requires that it execute a Java executable which talks to some web resources and services over SSL.
Java will absolutely throw a fit if it's doing something over SSL and it doesn't have every single certificate in the remote certificate's chain. So in .net I need to be able to specific an https:// [whatever] .com resource and need it to download the entire remote chain locally.
Why do I want to do this automatically? Because I want to make deployment simple and to not have to do this 4 times, and then again every time one of Oracle's certificates expires.
I am doing this:
X509Certificate2 lowestCert = SecurityHelper.DownloadSslCertificate(keyDownloadLocation);
X509Chain chain = new X509Chain();
chain.Build(lowestCert);
int counter = 0;
foreach (X509ChainElement el in chain.ChainElements) {
//Extract certificate
X509Certificate2 chainCert = el.Certificate;
//Come up with an alias and save location
string alias = certBaseName + counter;
string localLocation = Path.GetDirectoryName(
System.Reflection.Assembly.GetEntryAssembly().Location
) + @"\" +alias + ".cer";
//Save it locally
SecurityHelper.SaveCertificateToFile(chainCert, localLocation);
//Remove (if possible) and add to java's keystore
deleteKeyFromKeystore(
javaFolder,
alias,
certKeyStore,
SecurityHelper.GetEncryptedAppSetting("JavaKeystorePassword")
);
addKeytoKeyStore(
localLocation,
javaFolder,
alias,
certKeyStore,
SecurityHelper.GetEncryptedAppSetting("JavaKeystorePassword")
);
//Then delete
if (File.Exists(localLocation)) File.Delete(localLocation);
counter++;
}
SecurityHelper.DownloadSslCertificate is a custom method that creates an X509Certificate2 from a a website url.
"cert" is the lowest-level certificate and I can get that back, but what I can't do is get everything in the chain. chain.ChainElements is still only 1 level deep if the rest of the chain isn't already installed on the machine. My problem is that this is a brand new URL with totally unknown (to the machine) certificates, and I want to go up the stack and recursively download every one.
Is there a way to hit a URL unknown to the machine and programmatically downloading every certificate in the chain?
Edit: here is how I'm downloading eh SSL Certificate:
public static X509Certificate2 DownloadSslCertificate(string strDNSEntry)
{
X509Certificate2 cert = null;
using (TcpClient client = new TcpClient())
{
client.Connect(strDNSEntry, 443);
SslStream ssl = new SslStream(client.GetStream(), false,
new RemoteCertificateValidationCallback(
(sender, certificate, chain, sslPolicyErrors) => {
return true;
}
), null);
try
{
ssl.AuthenticateAsClient(strDNSEntry);
}
catch (AuthenticationException e)
{
ssl.Close();
client.Close();
return cert;
}
catch (Exception e)
{
ssl.Close();
client.Close();
return cert;
}
cert = new X509Certificate2(ssl.RemoteCertificate);
ssl.Close();
client.Close();
return cert;
}
}
Edit #2 The solution to obtain remote ssl certificates that worked for me was to capture the chain in the validation callback that occurs during an SslStream.AuthenticateAsClient(url);
private static X509ChainElementCollection callbackChainElements = null;
private static bool CertificateValidationCallback(
Object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
callbackChainElements = chain.ChainElements;
return true;
}
And then to kickoff the validation with this callback, I had something like:
public static X509Certificate2 DownloadSslCertificate(string url)
{
X509Certificate2 cert = null;
using (TcpClient client = new TcpClient())
{
client.Connect(url, 443);
SslStream ssl = new SslStream(client.GetStream(), false, CertificateValidationCallback, null);
try
{
ssl.AuthenticateAsClient(url); //will call CertificateValidationCallback
}
... etc
Then by the time AuthenticateAsClient has finished, the local "callbackChainElements" variable is set to a collection of X509ChainElement.