How to implement certificate pinning for a TcpClient

595 Views Asked by At

I'm trying to set up a TCP stream (non-HTTP) on a server that will be exposed to the public internet, but only "chosen" clients should be able to connect to. As I understand it, this is generally handled by certificate pinning, but I'm not familiar with all the details of how this is done.

  1. Generate a SSL cert for the server. That's easy enough.
  2. Set up the server. Have it call something like the following when a client connects:
private SslStream GetSslStream(TcpClient client, string certificateFile)
{
    var c = X509Certificate.CreateFromCertFile(certificateFile);
    var cert = new X509Certificate2(c);
    if (!cert.Verify()) {
        throw new Exception("Certificate failed verification");
    }
    var stream = new SslStream(client.GetStream(), false, VerifyClientCert);
    stream.AuthenticateAsServer(cert, true, true);
    return stream;
}
  1. Where does the client certificate come from? Is it a copy of the server's SSL certificate? Is it a separate cert that has to be derived from the server's cert in some way? Is it a completely different cert?
  2. What goes in the VerifyClientCert method to make sure it's the right certificate? Just Verify() and check the Thumbprint against an expected value, or is there more that needs to happen?
  3. Should every client get a copy of the same client cert?
1

There are 1 best solutions below

0
Evk On

Certificate pinning is a different concept. When server uses SSL certificate, it's usually issued by some Certificate Authority. Certificate of that authority can in turn be issued by yet another Certificate Authority, forming a chain. When client validates server certificate, it checks if that Certificate Authority is "trusted" by this particular client. "Trusted" in turn means that some certificate in the chain described above is trusted by this client. For example, Windows and Linux OS both come with certain set of Certificate Auhorities trusted by default. If server certificate has some of those authorities in their chain - then it's also trusted.

Certificate pinning means you, as a client, impose more restrictions on server certificate than what is described above. For example, you might say that for this domain \ endpoint, I only trust this specific certificate (with this specific digest), and I do not care about trust chain at all. Or that for this endpoint I only trust this specific Certificate Authority. This is certificate pinning. You can use it for your server, but it has its drawbacks which I won't describe here.

Now back to your problem. What you describe is called Client Certificate Authentication. In SSL handshake, not only server can present certificate for client to verify. Server might also request client to send his own certificate, to authenticate that client. This certificate is of course completely different from server's certificate.

Every client must have different certificate. Usually one of the two options are used:

  1. You (server owner) create your own Certificate Authority (for example with couple of simple openssl commands), and then you issue separate certificate for each client from this authority. Then you send those certificates (one for each client), including private keys, to their respective clients. After that you can throw away (delete) those certificates, because you will no longer need them. When validating client certificate - just check if it was issued by your Certificate Authority.

  2. OR separate certificate is issued for each client and you store thumbprints (digests\hashes) of those certificates, one for each client. Then during validation you ensure that Thumbprint has the expected value for given client. The benefit is that you can ask client to generate such certificate and send you (the server owner) the thumbprint. Then you never had private key of that certificate in your possession (unlike method with your own Certificate Authority where you generated certificate yourself and at one point posessed its private key).