Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automated downloading of X509 certificate chain from remote host

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 specify an https:// 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 the 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.

like image 868
Tor Avatar asked Sep 30 '22 09:09

Tor


1 Answers

Actually I did something very similar to this using Powershell.

One piece is changing ServicePointManager to ignore SSL validation (since you may not have the root certs locally, it sounds). It's ServerCertificateValidationCallback that you want to set to true, which tells it you're using a custom validation (or in your case, perhaps no validation).

From there, you're most of the way there with your chainelements. You can use the Export method to export the cert into a byte array:

foreach (X509ChainElement el in chain.ChainElements) {
 var certToCreate = el.Certificate.Export(X509ContentType.Cert);
  ...
}

From there it's just a matter of doing what you want with the byte array. You can pass it to a constructor for another certificate object, which you can then import into the Java keystore.

Hope that helps, please let me know if you have further questions.

EDIT: Actually, I just noticed this post on tips working with X.509 certificates in .NET . The author recommends against writing directly from a byte array because it writes the temp file to a directory that doesn't get cleaned up. So his suggestion is to write the byte array to disk, and then manually clean it up when you're done.

like image 60
mppowe Avatar answered Oct 04 '22 19:10

mppowe