In my application, I call the .NET method X509Certificate2.Verify on a client certificate and sometimes it returns True (valid) when it should not. This happens when I deliberately try to fool it.
I have code like this:
' Obtain a client cert in an application-dependent way.
Dim clientCert As X509Certificate2 = GetX509Certificate2()
' Don't call .Verify on the entire certificate itself, as it will
' return False unless the client certificate itself is trusted
' by the system, not just the issuer. Instead, loop through the
' chain, considering the cert valid if any element of the chain
' is trusted.
Dim chain As X509Chain = New X509Chain()
chain.Build(clientCert)
For Each element In chain.ChainElements
' ToDo: figure out why the Verify method apparently can be fooled
' by a cert whose signer DN is the same as a trusted cert.
isTrustedCertificate = element.Certificate.Verify() And
IsDateValid(element.Certificate)
If isTrustedCertificate Then
Exit For
End If
Next
This code considers a certificate valid if its issuer has the exact same distinguished name as a trusted certificate in the Window store. What's more, when I inspect the chain element corresponding to the bogus CA, it reports the thumbprint of the actual, valid CA rather than that of the bogus one. Weird.
Of course, it's easy to create your own bogus certificating authority certificate with an arbitrary DN. This means you can create your own client certificates and have them trusted by my application, as long as you know the DN of a trusted cert in my Windows store. Bad news.
I know that Windows has the ability to detect counterfeit certificates like this, because if I configure an IIS virtual directory to require client certificates and use a client cert issued by a bogus CA, IIS correctly returns an HTTP 403 error. However, within my application, the above .NET method is not as clever.
What am I doing wrong in my .NET code?
Thanks.
UPDATE:
It seems that my problem was two-fold:
- clientCert.Verify() was returning False because the system was unable to find cert revocation information.
- I failed to check the result of chain.Build(clientCert). It was returning True for the good cert, and False for the forged cert.
I don't know how to change the policy used by Verify on the entire cert, so I made these changes:
- I set a non-default policy for chain.Build to use, ignoring problems when no revocation info was available.
- I checked the result of chain.Build
Here's the final code:
Dim isTrustedCertificate As Boolean = False
Dim chain As X509Chain = New X509Chain()
' Set the chain policy to not complain if the revocation status is unknown.
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreCertificateAuthorityRevocationUnknown _
Or X509VerificationFlags.IgnoreEndRevocationUnknown
' The chain will fail to build if the issuer is untrusted.
' Checking the result of chain.Build seems to be important to detecting forged certs.
Dim bChainOK As Boolean = chain.Build(clientCert)
If bChainOK Then
For Each element In chain.ChainElements
isTrustedCertificate = element.Certificate.Verify() And
IsDateValid(element.Certificate)
If isTrustedCertificate Then
Exit For
End If
Next
End If