Problem
For a new server application we are developing; I implemented a method to verify SSL certificates.
The use of this method is Mutual Authentication: to prove that the server and client are who they say they are, using a Root Certificate and Intermediate Certificate that I’ve created myself.
The SslStream is created as such (server-side):
this.stream = new SslStream(tcpClient.GetStream(), false, SSLVerification.VerifyCertificate, null);
var certificate = new X509Certificate2(Config.Instance.CertificatePath, Config.Instance.CertificatePassword);
stream.AuthenticateAsServer(certificate, true, SslProtocols.Tls, false);
And Client side (almost) the same:
SslStream stream = new SslStream(client.GetStream(), false, SSLVerification.VerifyCertificate, null);
X509Certificate2 certificate = new X509Certificate2(Config.Instance.CertificatePath, Config.Instance.CertificatePassword);
stream.AuthenticateAsClient("our.server.name", new X509Certificate2Collection(certificate), System.Security.Authentication.SslProtocols.Tls, false);
Both snippets make use of the same library, in which the SslVerification.VerifyCertificate()
can be found.
The implementation of that method is as follows:
public static bool VerifyCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
var rootCert = new X509Certificate2(X509Certificate.CreateFromCertFile(rootCertificate));
var interCert = new X509Certificate2(X509Certificate.CreateFromCertFile(intermediateCertificate));
chain.ChainPolicy.ExtraStore.Add(interCert);
chain.ChainPolicy.ExtraStore.Add(rootCert);
var isChainValid = chain.Build(new X509Certificate2(certificate));
return isChainValid && chain.ChainElements.OfType<X509ChainElement>().Any(c => c.Certificate.Thumbprint == rootCert.Thumbprint);
}
After the SslStream has been set-up, we run a check (server-side) on the common-name to verify exactly which client we are talking to. We just simply look up the Common Name (Certificate.Subject) in a database table.
My questions are:
- Is this the correct way to ensure that the certificate has been signed by our specific Root Certificate, and not any other signing certificate?
- Is there any way to fool this implementation?
- Maybe any other pitfalls in the implementation above?
Solution
The X509 certificate contains by itself a system to specify and verify chain and root certificates, by doing this you are actually creating a certificate without a proper root certificate but implementing a root certificate by yourself.
Looks like a waste of time just for the pleasure of doing something less secure…