SslStream request to accept ONLY a certificate signed by a specific public key

I have a working implementation of this, but I want to make sure that it is safe. The goal is to use SSLStream and only accept SSL certificates from the server that are signed with a specific RSA key.

Here is my connection code:

var client = new TcpClient("server_address", port_number); var sslStream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null); sslStream.AuthenticateAsClient("SpeechGrid"); 

And here is my implementation of ValidateServerCertificate:

  private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { // Only accept our specific key pair foreach (var cert in chain.ChainElements) { if (cert.Certificate.GetPublicKeyString() == k_prodPublicKey) { return true; } } return false; } 

Due to the richness of the X509Chain object, I want to make sure that I do not need to check things like X509ChainStatusFlags.NotSignatureValid, etc.

For example, can an attacker “require” to be signed with my public key, send an invalid signature, and this attack will work because .NET assumes that I check all these flags?

Thanks!!

UPDATE: Well, so far I have decided to put the following checks above the original foreach. Please note that this is a somewhat specific application; for example, if I need certificates for expiration, I would check NotTimeValid, etc.

  foreach (var status in chain.ChainStatus) { switch (status.Status) { case X509ChainStatusFlags.Cyclic: case X509ChainStatusFlags.NotSignatureValid: case X509ChainStatusFlags.PartialChain: return false; } } 
+2
source share
2 answers

I would cancel the logic of the verification you added in the update to your question. Instead of looking for what might be wrong and accepting everything else:

 foreach (thing that I can think of that might be wrong) return false; if (public key matches regardless of other policy errors) return true; 

... Instead, I would look for what might be wrong, but acceptable, and reject any other policy errors:

 if (policy errors) { foreach (error that is acceptable: remote name mismatch, untrusted root, etc.) policy errors -= that particular error } if (any policy errors left) return false; else if (public key matches) return true; else return false; 

Something like this for the first part (I have not tested or compiled this):

 if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch) == SslPolicyErrors.RemoteCertificateNameMismatch) { sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateNameMismatch; } if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) == SslPolicyErrors.RemoteCertificateChainErrors) { var otherFlagsFound = from i in chain.ChainStatus where (i.Status & ~X509ChainStatusFlags.UntrustedRoot) != X509ChainStatusFlags.NoError select i; if (otherFlagsFound.Count() == 0) { sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors; } } 
+1
source

You can check the sslPolicyErrors parameter for additional errors, such as expired, or if certificates are not trusted. If everything is in order, it should return SslPolicyErrors.None. It is impractical to derive the private key from the public key, so you do not need to worry about someone else creating the same key pair and signing it.

0
source

All Articles