0

I have a certificate which I generated as self-signed and then had signed by a vendor. I have to connect to this vendor and authenticate using a certificate. When I do an AuthenticateAsClient, my LocalCertificateSelection callback gets called with the validIssuers parameter being set to the one who signed the certificate. What certificate should I be returning from the callback? When I return the certificate that was issued by the vendor I get an error because the certificate is not sent to the server. I am using the following code.

namespace TestClientAuthenticateHttps {
    using System;
    using System.Linq;
    using System.Net;
    using System.Net.Security;
    using System.Net.Sockets;
    using System.Security.Authentication;
    using System.Security.Cryptography.X509Certificates;

    class Program {
        private readonly X509Certificate2Collection _certs;
        private TcpClient _client;
        private SslStream _sslStream;
        private const string Host = "smr-staging.surescripts.net";
        private const string SsSerial = "40 1f c1 ea 00 00 00 00 02 44";
        private const SslProtocols EnabledSslProtocols = SslProtocols.Ssl3 | SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;

        private static void Main() {
            var program = new Program();
            program.Execute();
        }
        private Program() {
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            var certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            certStore.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
            _certs = certStore.Certificates.Find(X509FindType.FindBySerialNumber, SsSerial, false);
            certStore.Close();
        }
        private void Execute() {
            ConnectToHost();
            try {
                OpenSslConnection();
                DoAuthentication();
            } catch (Exception e) {
                do {
                    Console.WriteLine(e.Message);
                    Console.WriteLine(e.StackTrace);
                    e = e.InnerException;
                } while (e != null);
            } finally {
                _sslStream.Close();
            }
            Console.ReadLine();
        }

        private void DoAuthentication() {
            try {
                _sslStream.AuthenticateAsClient(Host, _certs, EnabledSslProtocols, true);
            } catch (Exception) {
                Console.WriteLine($"Host is ({Host})");
                Console.WriteLine($"_certs = ");
                _certs.Cast<X509Certificate>().ToList().ForEach(Console.WriteLine);
                throw;
            }
        }

        private void OpenSslConnection() {
            _sslStream = new SslStream(_client.GetStream(), false, RemoteCertificateValidate,
                LocalCertificateSelection, EncryptionPolicy.AllowNoEncryption);
        }

        private static bool RemoteCertificateValidate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
            return true;
        }

        private static X509Certificate LocalCertificateSelection(object sender, string host,
            X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers) {
            Console.WriteLine($"Selecting certificate for ({host})");
            var acceptable = acceptableIssuers.Select(s => new X500DistinguishedName(s).Name?.Trim()).Where(s => !string.IsNullOrWhiteSpace(s)).ToList();
            Console.WriteLine("Acceptable issuers.");
            acceptable.ForEach(Console.WriteLine);
            Console.WriteLine("Local certificate Selected");
            var result = acceptableIssuers.Length == 0 ? localCertificates[0] : localCertificates?.Cast<X509Certificate2>().First(
                c => acceptable.Any(n => n.Equals(c.IssuerName.Name?.Trim(), StringComparison.InvariantCultureIgnoreCase))
                );
            Console.WriteLine(result);
            return result;
        }
        private void ConnectToHost() {
            _client = new TcpClient(Host, 443);
        }
    }
}

1 Answers1

0

Just make sure that the certificate returned from the callback has a private key. You can do this by setting the PrivateKey property of the certificate to the PrivateKey property of the corresponding self-signed key. If reading the CA-issued key from the certificate store, ensure that the store is opened ReadWrite instead of ReadOnly.