How can you verify validity of an HTTPS/SSL certificate in .NET?
Ideally I want to establish an HTTPS connection to a website and then find out if that was done in a valid way (certificate not expired, host name matches, certificate chain trusted etc), but the built in HTTP Client seems to ignore certificate errors (and I'm not sure that physically downloading a web page is necessary to verify a certificate?).
I've tried to use the code below (adapted from an answer in the comments) but the ValidationCallback never gets called:
static void Main(string[] args)
{
String url = "https://www.example.com";
HttpWebRequest request = WebRequest.CreateHttp(url);
request.GetResponse();
request.ServerCertificateValidationCallback += ServerCertificateValidationCallback;
Console.WriteLine("End");
Console.ReadKey();
}
private static bool ServerCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
Console.WriteLine("Certificate OK");
return true;
}
else
{
Console.WriteLine("Certificate ERROR");
return false;
}
}
It doesn't get called because you're setting the ValidationCallback after you've already made the request.
Change it to this:
HttpWebRequest request = WebRequest.CreateHttp( url );
request.ServerCertificateValidationCallback += ServerCertificateValidationCallback;
using( HttpWebResponse response = (HttpWebResponse)request.GetResponse() ) { }
Console.WriteLine("End");
...and it will work.
Related
I'm trying to utilize a REST API on a local web server with a self-signed certificate. At runtime, the application throws the error
AuthenticationException: The remote certificate is invalid according to the validation procedure.
I have tried the fix in this answer: https://stackoverflow.com/a/1386568/8980983 however the error remains. Code is below:
static void Main(string[] args)
{
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Clear();
ServicePointManager.ServerCertificateValidationCallback = delegate (
object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
};
var loginPage = httpClient.GetAsync("https://<local IP address>/loginpage.html").GetAwaiter().GetResult();
//do stuff with response...
}
Any ideas of what I can do to effectively ignore SSL policy errors?
Figured it out. Turns out the HttpClient class doesn't use the ServicePointManager.ServerCertificateValidationCallback method. Solution was as follows:
static void Main(string[] args)
{
HttpClientHandler httpClientHandler = new HttpClientHandler();
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
HttpClient httpClient = new HttpClient(httpClientHandler);
httpClient.DefaultRequestHeaders.Clear();
var loginPage = httpClient.GetAsync("https://<local IP address>/loginpage.html").GetAwaiter().GetResult();
}
I am trying to consume Client's Web Service from Web API, Below is the code we are currently using to bypass SSL certificate
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
It was working fine, until they disabled TLS 1.0 and TLS 1.1 from their end. Now we have added follwing code to use TLS 1.2 for client server connection
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
Now i am getting "The request was aborted: Could not create SSL/TLS secure channel." Error only when i am hitting the API for first time, and then getting results if i am continuously hitting the API, If i wait for some time like a minute or so, Again getting same error for first time only.
Setting the security protocol type needs to be done before the issuing request is created. So this:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
Should appear before this:
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
So if you're seeing it work on subsequent requests, it might be that you're setting the protocol too late.
The following code can be used to help troubleshoot the issue.
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertificate;
...
private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// If the certificate is a valid, signed certificate, return true to short circuit any add'l processing.
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
else
{
// cast cert as v2 in order to expose thumbprint prop - if needed
var requestCertificate = (X509Certificate2)certificate;
// init string builder for creating a long log entry
var logEntry = new StringBuilder();
// capture initial info for the log entry
logEntry.AppendFormat("SSL Policy Error(s): {0} - Cert Issuer: {1} - SubjectName: {2}",
sslPolicyErrors.ToString(),
requestCertificate.Issuer,
requestCertificate.SubjectName.Name);
// check for other error types as needed
if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors) //Root CA problem
{
// check chain status and log
if (chain != null && chain.ChainStatus != null)
{
// check errors in chain and add to log entry
foreach (var chainStatus in chain.ChainStatus)
{
logEntry.AppendFormat("|Chain Status: {0} - {1}", chainStatus.Status.ToString(), chainStatus.StatusInformation.Trim());
}
}
}
// replace with your logger
MyLogger.Info(logEntry.ToString().Trim());
}
return false;
}
For those running .NET version 4, they can use below
ServicePointManager.SecurityProtocol = CType(768, SecurityProtocolType) Or CType(3072,SecurityProtocolType)
ServicePointManager.Expect100Continue = True
I have seen code samples of how to use WebRequestHandler with HttpClient to embed a certificate in my http request (see snippet below). However, I just tested this with a self-signed cert and it is not working. According to this post this method will not work without a trusted certificate.
I can see the certificate on the server if I send it through the browser or Postman, but not programmatically.
Can someone confirm or deny if HttpClient or WebRequestHandlerperform any kind of certificate validation before sending it as part of the request?
A quick de-compile did not show anything obvious, but there are many things in play along the request pipeline.
Sample code:
var cert = new X509Certificate2(rawCert);
//This call fails, cannot check revocation authority.
//Specific error: The revocation function was unable to check
//revocation for the certificate.
//X509CertificateValidator.ChainTrust.Validate(cert);
var certHandler = new WebRequestHandler()
{
ClientCertificateOptions = ClientCertificateOption.Manual,
UseDefaultCredentials = false
};
certHandler.ClientCertificates.Add(cert);
var Certificateclient = new HttpClient(certHandler)
{
BaseAddress = new Uri("https://web.local")
};
var response = await Certificateclient.GetAsync("somepath");
To use a self signed cert, you can add using System.Net.Security;
add a handler callback method
public static bool ValidateServerCertificate(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
}
and set before invoke the API:
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate);
See:
https://es.stackoverflow.com/a/153207/86150
https://social.msdn.microsoft.com/Forums/es-ES/130308da-4092-4f21-8355-ee3c77a22f97/llamar-web-service-con-certificado?forum=netfxwebes
Can you force HttpClient to only trust a single certificate?
I know you can do:
WebRequestHandler handler = new WebRequestHandler();
X509Certificate2 certificate = GetMyX509Certificate();
handler.ClientCertificates.Add(certificate);
HttpClient client = new HttpClient(handler);
But will this force it to only trust that single certificate, or will it trust that certifate AND all certificates that fx. GlobalSign can verify?
Basicly I want to ensure that it can ONLY be my server/certificate that my client is talking to.
Can you force HttpClient to only trust a single certificate?
...
Basically I want to ensure that it can ONLY be my server/certificate that my client is talking to.
Yes. But what type of certificate? Server or CA? Examples for both follow.
Also, it might be better to pin the public key rather than the certificate in the case of a server. That's because some organizations, like Google, rotate their server certificates every 30 days or so in an effort to keep the CRLs small for mobile clients. However, the organizations will re-certify the same public key.
Here's an example of pinning the CA from Use a particular CA for a SSL connection. It does not require placing the certificate in a Certificate Store. You can carry the CA around in your app.
static bool VerifyServerCertificate(object sender, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
try
{
String CA_FILE = "ca-cert.der";
X509Certificate2 ca = new X509Certificate2(CA_FILE);
X509Chain chain2 = new X509Chain();
chain2.ChainPolicy.ExtraStore.Add(ca);
// Check all properties (NoFlag is correct)
chain2.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
// This setup does not have revocation information
chain2.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
// Build the chain
chain2.Build(new X509Certificate2(certificate));
// Are there any failures from building the chain?
if (chain2.ChainStatus.Length == 0)
return false;
// If there is a status, verify the status is NoError
bool result = chain2.ChainStatus[0].Status == X509ChainStatusFlags.NoError;
Debug.Assert(result == true);
return result;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return false;
}
I have not figured out how to use this chain (chain2 above) by default such that there's no need for the callback. That is, install it on the ssl socket and the connection will "just work".
And I have not figured out how install it such that its passed into the callback. That is, I have to build the chain for each invocation of the callback because my chain2 is not passed into the functions as chain.
Here's an example of pinning the server certificate from OWASP's Certificate and Public Key Pinning. It does not require placing the certificate in a Certificate Store. You can carry the certificate or public key around in your app.
// Encoded RSAPublicKey
private static String PUB_KEY = "30818902818100C4A06B7B52F8D17DC1CCB47362" +
"C64AB799AAE19E245A7559E9CEEC7D8AA4DF07CB0B21FDFD763C63A313A668FE9D764E" +
"D913C51A676788DB62AF624F422C2F112C1316922AA5D37823CD9F43D1FC54513D14B2" +
"9E36991F08A042C42EAAEEE5FE8E2CB10167174A359CEBF6FACC2C9CA933AD403137EE" +
"2C3F4CBED9460129C72B0203010001";
public static void Main(string[] args)
{
ServicePointManager.ServerCertificateValidationCallback = PinPublicKey;
WebRequest wr = WebRequest.Create("https://encrypted.google.com/");
wr.GetResponse();
}
public static bool PinPublicKey(object sender, X509Certificate certificate, X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (null == certificate)
return false;
String pk = certificate.GetPublicKeyString();
if (pk.Equals(PUB_KEY))
return true;
// Bad dog
return false;
}
For anyone who comes across this in the future tou should be aware that some certificate authorities will no longer reissue certificates with the same public key when the certificate is renewed. We had this problem specifically with Globalsign who left us with the very difficult logistical problem of updating the client software with new public key pinning details for all our customers in a very short space of time, despite their published policy documents saying that they provided the option to reuse the public key. If this may be an issue for you confirm your certificate provider's policy in advance, and don't use Globalsign!
Client can use ServerCertificateValidationCallback like below -
System.Net.ServicePointManager.ServerCertificateValidationCallback +=
delegate(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate,
System.Security.Cryptography.X509Certificates.X509Chain chain,
System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
return true;
};
Since HTTPS proxies will replace the SSL certificate with their own, what are my options to determine if a given HTTPS connection has a proxy in the middle?
I will use this information to determine my application policy, since there are cases where I want a 100% end-to-end encrypted tunnel with no decryption by any 3rd party.
Even better if you can tell me how to determine this via C# in a .NET application or Silverlight.
For starters, here is a sample method to validate a certificate using .NET, but I'm still not sure how to use this to determine what part of the cert to validate. In addition, I think the ServicePointManger is more of a "global" connection class. Using this may be too broad when I'm testing a single HTTP connection, and I'm not sure if ServicePointManager is available within Silverlight.
http://msdn.microsoft.com/en-us/library/bb408523.aspx
You have a couple of options. The first option is to use the ServicePointManager class. You are correct in that it manages all service points, but you can use the "sender" parameter in the callback method to differentiate between the different service points:
void SomeMethod()
{
ServicePointManager.ServerCertificateValidationCallback +=
ValidateServerCertificate;
var url = "https://mail.google.com/mail/?shva=1#inbox";
var request = (HttpWebRequest)WebRequest.Create(url);
request.GetResponse();
}
private static bool ValidateServerCertificate(object sender,
X509Certificate certificate, X509Chain chain,
SslPolicyErrors sslpolicyerrors)
{
if(sender is HttpWebRequest)
{
var request = (HttpWebRequest) sender;
if(request.RequestUri.ToString() == "https://mail.google.com/mail/?shva=1#inbox")
{
return (certificate.GetPublicKeyString() == "The public key string you expect");
}
}
return true;
}
This option will work for manually-created HttpWebRequest and WCF-created requests, as the "sender" will be HttpWebRequest for both. I'm not sure if the "sender" will be anything other than an HttpWebRequest.
The second option is to get the certificate from the service point directly:
void SomeMethod()
{
ServicePointManager.ServerCertificateValidationCallback +=
ValidateServerCertificate;
var url = "https://mail.google.com/mail/?shva=1#inbox";
var request = (HttpWebRequest)WebRequest.Create(url);
request.GetResponse();
var serverCert = request.ServicePoint.Certificate;
// Validate the certificate.
}
I couldn't figure out if it's possible to get the ServicePoint used by a WCF proxy. If it's not possible, this option won't work for WCF. Other than that, the biggest difference is that the first option prevents the connection if the certificate validation fails, while the second method won't validate until after the connection has been made.
If you just need to determine if a request is going to pass through a proxy:
var httpRequest = (HttpWebRequest)WebRequest.Create("someurl");
var isUsingProxy = DoesRequstUseProxy(request);
bool DoesRequestUseProxy(HttpWebRequest request)
{
if(request.Proxy == null)
{
return false;
}
return request.Proxy.GetProxy(request.RequestUri) != request.RequestUri;
}