4

My C#.NET SSL connect works when I import the certificate manually in IE (Tools/Internet Options/Content/Certificates), but how can I load the certificate by code? Here is my code:

TcpClient client = new TcpClient(ConfigManager.SSLSwitchIP, Convert.ToInt32(ConfigManager.SSLSwitchPort));

SslStream sslStream = new SslStream(
                client.GetStream(),
                false,
                new RemoteCertificateValidationCallback(ValidateServerCertificate),
                null
                );
sslStream.AuthenticateAsClient("Test");

The above code works fine if i import my certificate file manually in Internet Explorer. But if i remove my certificate from IE and use the following code instead, i get Authentication exception:

sslStream.AuthenticateAsClient("Test", GetX509CertificateCollection(), SslProtocols.Default, false);

and here is the 'GetX509CertificateCollection' method :

public static X509CertificateCollection GetX509CertificateCollection()
        {
            X509Certificate2 certificate1 = new X509Certificate2("c:\\ssl.txt");
            X509CertificateCollection collection1 = new X509CertificateCollection();
            collection1.Add(certificate1);
            return collection1;
        }

What should I do to load my certificate dynamically?

Chad
  • 1,531
  • 3
  • 20
  • 46
losingsleeep
  • 1,849
  • 7
  • 29
  • 46

3 Answers3

6

To build upon owlstead's answer, here's how I use a single CA certificate and a custom chain in the verification callback to avoid Microsoft's store.

I have not figured out how to use this chain (chain2 below) by default such that there's no need for the callback. That is, install it on the ssl socket and the connection will "just work". And I have not figured out how install it such that its passed into the callback. That is, I have to build the chain for each invocation of the callback. I think these are architectural defects in .Net, but I might be missing something obvious.

The name of the function does not matter. Below, VerifyServerCertificate is the same callback as RemoteCertificateValidationCallback. You can also use it for the ServerCertificateValidationCallback in ServicePointManager.

static bool VerifyServerCertificate(object sender, X509Certificate certificate,
    X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    try
    {
        String CA_FILE = "ca-cert.der";
        X509Certificate2 ca = new X509Certificate2(CA_FILE);

        X509Chain chain2 = new X509Chain();
        chain2.ChainPolicy.ExtraStore.Add(ca);

        // Check all properties
        chain2.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;

        // This setup does not have revocation information
        chain2.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;

        // Build the chain
        chain2.Build(new X509Certificate2(certificate));

        // Are there any failures from building the chain?
        if (chain2.ChainStatus.Length == 0)
            return true;

        // If there is a status, verify the status is NoError
        bool result = chain2.ChainStatus[0].Status == X509ChainStatusFlags.NoError;
        Debug.Assert(result == true);

        return result;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }

    return false;
}
jww
  • 97,681
  • 90
  • 411
  • 885
3

A quick Google pointed me to a piece of text from the Microsoft SslStream class.

The authentication is handled by the Security Support Provider (SSPI) channel provider. The client is given an opportunity to control validation of the server's certificate by specifying a RemoteCertificateValidationCallback delegate when creating an SslStream. The server can also control validation by supplying a RemoteCertificateValidationCallback delegate. The method referenced by the delegate includes the remote party's certificate and any errors SSPI encountered while validating the certificate. Note that if the server specifies a delegate, the delegate's method is invoked regardless of whether the server requested client authentication. If the server did not request client authentication, the server's delegate method receives a null certificate and an empty array of certificate errors.

So simply implement the delegate and do the verification yourself.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • 2
    thank you. I've done that before, but it has errors if i haven't added the certificate to root store. by the way, how can i verify remote certificate? what's the manual check instructions and rules? – losingsleeep Oct 14 '12 at 11:27
  • You have implemented the callback function and when it returned `true` it still did not work? Really? – Maarten Bodewes Oct 14 '12 at 16:09
  • 1
    Yes. As I understood , when i call "AuthenticateAsClient" method , .NET does SSL handshake using keys in "Trusted Root Certification Authorities" (Internet Explorer/Tools/Internet Options/Content/Certificates) that is called ROOT when we wanna add it by code. And if the needed public key doesn't exist there, .NET's program control goes into the "RemoteCertificateValidationCallback" callback method with some errors, and if i check remote certificate manually and return "true" in that method, no problem like "AuthenticationException" will occur. So what's the rules of verification? – losingsleeep Oct 15 '12 at 12:20
  • Well, that's up to you. You should at least verify a chain to a trusted certificate (but that chain may be one cert long). Then it would be wise to check that the current date is within the validity period. Then there are key usages and other extended elements. In that regard it is a solution, but not an easy solution. – Maarten Bodewes Oct 15 '12 at 12:36
2

I wrote another method to add my certificate to Trusted Root Certification Authorities (root) before attempting to authenticate as client via SSLStream object:

public static void InstallCertificate()
    {
        X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
        store.Open(OpenFlags.ReadWrite);
        string fileName = "sslcert.pem";
        X509Certificate2 certificate1;
        try
        {
            certificate1 = new X509Certificate2(fileName);
        }
        catch (Exception ex)
        {
            throw new Exception("Error loading SSL certificate file." + Environment.NewLine + fileName);
        }

        store.Add(certificate1);
        store.Close();
    }

And then:

InstallCertificate();
sslStream.AuthenticateAsClient("Test");

It works fine without any warnings or errors. But base question still remains unsolved:

How can I use a certificate to authenticate as client without installing it in Windows?

losingsleeep
  • 1,849
  • 7
  • 29
  • 46
  • 1
    "How can I use a certificate to authenticate as client without installing it in Windows?" - see the answer below. It builds upon owlstead's answer. – jww Mar 28 '14 at 01:15