Reputation: 955
Using framework 4.5.1 and the following requirement, am I doing this right?
The following passes, but is this sufficient?
In particular does the call to chain.Build(cert) satisfy #2 above?
protected bool ValidateDigitalSignature(Uri uri)
{
bool isValid = false;
X509Certificate2 cert = null;
HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
response.Close();
}
isValid = (request.ServicePoint.Certificate != null);
if(isValid)
cert = new X509Certificate2(request.ServicePoint.Certificate);
if (isValid)
{
X509Chain chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
chain.Build(cert);
isValid = (chain.ChainStatus.Length == 0);
}
if (isValid)
{
var dnsName = cert.GetNameInfo(X509NameType.DnsName, false);
isValid = (Uri.CheckHostName(dnsName) == UriHostNameType.Dns
&& uri.Host.Equals(dnsName, StringComparison.InvariantCultureIgnoreCase));
}
if (isValid)
{
//The certificate must not be expired
DateTimeOffset today = DateTimeOffset.Now;
isValid = (today >= cert.NotBefore && today <= cert.NotAfter);
}
return isValid;
}
Upvotes: 5
Views: 11202
Reputation: 53
With .NET 5 and higher, follow the example posted in https://github.com/dotnet/runtime/issues/39835:
RemoteCertificateValidationCallback callback = (object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) =>
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
else if (sslPolicyErrors != SslPolicyErrors.RemoteCertificateChainErrors || chain is null || certificate is null)
{
// Name mismatch or no cert
return false;
}
// name matches and there was a certificate
// Only works with .NET 5 and higher, taken from
// https://github.com/dotnet/runtime/issues/39835
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(/* Load your root certificate here */);
chain.ChainPolicy.ExtraStore.Add(X509Certificate2.CreateFromPemFile(/* Load any intermediate certificates here */);
return chain.Build(new X509Certificate2(certificate));
};
Upvotes: 2
Reputation: 33088
If you're trying to validate that an HTTPS certificate is valid, HttpWebRequest can do that for you.
To make HttpWebRequest check the revocation status you need to set the global ServicePointManager.CheckCertificateRevocationList = true
before calling GetResponse()
(I think it's GetResponse, as opposed to the call to Create()).
That will then check:
Which is all three points that you asked about. The hardest one is getting the hostname matching correct, because
In fact, the only thing that HttpWebRequest doesn't automatically do for you (unless you set the global) is check revocation. And you can do that via
HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
request.ServerCertificateValidationCallback = ValidationCallback;
private static bool ValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// Since you want to be more strict than the default, reject it if anything went wrong.
if (sslPolicyErrors != SslPolicyErrors.None)
{
return false;
}
// If the chain didn't suppress any type of error, and revocation
// was checked, then it's okay.
if (chain.ChainPolicy.VerificationFlags == X509VerificationFlags.None &&
chain.ChainPolicy.RevocationMode == X509RevocationMode.Online)
{
return true;
}
X509Chain newChain = new X509Chain();
// change any other ChainPolicy options you want.
X509ChainElementCollection chainElements = chain.ChainElements;
// Skip the leaf cert and stop short of the root cert.
for (int i = 1; i < chainElements.Count - 1; i++)
{
newChain.ChainPolicy.ExtraStore.Add(chainElements[i].Certificate);
}
// Use chainElements[0].Certificate since it's the right cert already
// in X509Certificate2 form, preventing a cast or the sometimes-dangerous
// X509Certificate2(X509Certificate) constructor.
// If the chain build successfully it matches all our policy requests,
// if it fails, it either failed to build (which is unlikely, since we already had one)
// or it failed policy (like it's revoked).
return newChain.Build(chainElements[0].Certificate);
}
And, of note, as I put in the sample code here, you only need to check the return value of chain.Build(), because that will be false if any cert is expired or whatnot. You also may want to check the root cert (or an intermediate, or whatever) out of the built chain for being an expected value (certificate pinning).
If the ServerCertificateValidationCallback returns false an exception is thrown on GetResponse().
You should try your validator out to make sure it works:
Upvotes: 10