In my C# application, I got to call web services via https and validate using a .crt file that I already have. Here is the correct solution for such needs. I have updated this post once I got a working solution, thinking it might help others like me.
SOLUTION : The below code has to be executed only once in the whole application execution. With this we set the ServerCertification and SSL properties that will be used whenever a reqest will be called :
public static void setSSLCertificate()
{
clientCert = new X509Certificate2(AUTHEN_CERT_FILE); // Pointing to the .crt file that will be used for server certificate verification by the client
System.Net.ServicePointManager.ServerCertificateValidationCallback += new System.Net.Security.RemoteCertificateValidationCallback(customXertificateValidation);
}
public static bool customXertificateValidation(Object sender, X509Certificate certificate, X509Chain chain, System.Net.Security.SslPolicyErrors sslPoicyErrors)
{
switch (sslPoicyErrors)
{
case System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors:
case System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch:
case System.Net.Security.SslPolicyErrors.RemoteCertificateNotAvailable:
break;
}
return clientCert.开发者_JAVA技巧Verify(); // Perform the Verification and sends the result
}
A request is done normally like we do without implementing SSL. Here is a Post request code :
private static String SendPost(String uri, String post_data)
{
String resData = "";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.KeepAlive = false;
request.ProtocolVersion = HttpVersion.Version10;
request.ContentType = "application/x-www-form-urlencoded";
request.Method = "POST";
// turn request string into byte[]
byte[] postBytes = Encoding.ASCII.GetBytes(post_data);
Stream requestStream = null;
try
{
// Send it
request.ContentLength = postBytes.Length;
requestStream = request.GetRequestStream();
requestStream.Write(postBytes, 0, postBytes.Length);
}
catch (WebException we)
{ // If SSL throws exception that will be handled here
if (we.Status == WebExceptionStatus.TrustFailure)
throw new Exception("Exception Sending Data POST : Fail to verify server " + we.Message);
}
catch (Exception e)
{
throw new Exception("Exception Sending Data POST : " + e.Message, e.InnerException);
}
finally
{
if (requestStream != null)
requestStream.Close();
}
// Get the response
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.GetResponse();
if (response == null)
return "";
StreamReader sr = new StreamReader(response.GetResponseStream());
resData = sr.ReadToEnd().Trim();
sr.Close();
}
catch (Exception e)
{
throw new Exception("Error receiving response from POST : " + e.Message, e.InnerException);
}
finally
{
if (response != null)
response.Close();
}
return resData;
}
Special Thanks to Dipti Mehta whose explination helped me achieve the goal to a great extend by accepting the server certificate. She helped me solve my confussions. I finally found how to verify the server certificate using .crt file by the client.
Hope this helps somebody.
Thanks
When you browse to a HTTPS site, you probably get a dialog window asking you if you want to trust the certificate provided by the webserver. So the responsibility of accepting the certificate is handled by the user. Let's get back to the webservice scenario, if you want to invoke a webservice located on a webserver which uses SSL and HTTPS there is a problem.
When you make the call from code, there is no dialog window popping up, and asking if you trust the certificate ; probably you'll get following exception:
An unhandled exception of type 'System.Net.WebException' occurred in system.dll
Additional information: The underlying connection was closed: Could not establish trust relationship with remote server.
But there is a solution for this problem, you can solve this in your code by creating your own CertificatePolicy class (which implements the ICertificatePolicy
interface). In this class you will have to write your own CheckValidationResult function that has to return true or false, like you would press yes or no in the dialog window. For development purposes I've created the following class which accepts all certificates, so you won't get the nasty WebException anymore:
public class TrustAllCertificatePolicy : System.Net.ICertificatePolicy
{
public TrustAllCertificatePolicy()
{}
public bool CheckValidationResult(ServicePoint sp, X509Certificate cert,WebRequest req, int problem)
{
return true;
}
}
As you can see the CheckValidationResult function always returns true, so all certificates will be trusted. If you want to make this class a little bit more secure, you can add additional checks using the X509Certificate parameter for example. To use this CertificatePolicy, you'll have to tell the ServicePointManager to use it:
System.Net.ServicePointManager.CertificatePolicy = new TrustAllCertificatePolicy();
This must be done (one time during the application life cycle) before making the call to your webservice.
Hi Tvd,
I'm not sure that the solution you provide is actually a valid solution for the problem. Also, some of your comments regarding HttpWebRequest.ClientCertificates
indicate this.
First, it is important to distinguish between the server validating a client certificate and the client validating a server certificate. Collection HttpWebRequest.ClientCertificates
is used to send client certificates to the server, so the server can validate who the client is. Your question (as far as I understand it) was how server certificate which does not pass the default validation (such as a self-signed cert) can be validated against a certificate locally stored at the client.
In this case a solution is indeed to use System.Net.ServicePointManager.ServerCertificateValidationCallback
and provide a custom validation. However, your validation method seems wrong: it verifies the local certificate and does not care about the cert send by the server. What I'd use is something like this:
public static bool customXertificateValidation(
Object sender, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
return clientCert.Equals(certificate);
};
This method ensures that if the server certificate passes the default validation (no errors) it will accept it and then it compares the local, client copy of the certificate with what has been provided by the server. Only, if Equals test passes the client can safely proceed.
By "validate" you mean authenticate? In that case a .crt is not enough, it only contains a public key. You need the private key to authenticate yourself and put that into ClientCertificates. You can either read one from a .pfx file or import that into a certificate container and use it from there.
Since System.Net.ICertificatePolicy is deprecated I think a correct way to do it should be create a RemoteCertificateValidationCallback delegate:
void Awake()
{
System.Net.ServicePointManager.ServerCertificateValidationCallback += ValidateCertification;
}
void OnDestroy()
{
ServerCertificateValidationCallback = null;
}
public static bool ValidateCertification(object sender, X509Certificate certificate, X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
print("VALIDATE!");
return true;
}
精彩评论