Asking SslStream to accept ONLY a certificate sign

2019-07-15 11:06发布

I have a working implementation of this but want to make sure it is secure. The goal is to use SSLStream and only accept SSL certificates from the server that are signed by a particular 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;
    }

Because of the richness of the X509Chain object I want to make sure that I don't need to check for things like X509ChainStatusFlags.NotSignatureValid, etc.

For example, would it be possible for an attacker to "claim" to be signed by my public key, send an invalid signature, and this attack would work because .NET assumes I'm checking all of these flags?

Thanks!!

UPDATE: Ok, so far I've decided to put the following checks above the original foreach. Note that this is somewhat application specific; for example if I wanted certificates to expire I would check for NotTimeValid, etc.

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

2条回答
闹够了就滚
2楼-- · 2019-07-15 11:10

I would reverse the logic of the check 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;

...I would instead look for what might be wrong yet 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 did not test or compile 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;
    }
}
查看更多
爱情/是我丢掉的垃圾
3楼-- · 2019-07-15 11:35

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

查看更多
登录 后发表回答