Accessing a web service and a HTTP interface using certificate authentication - c#

It is the first time I have to use certificate authentication.
A commercial partner expose two services, a XML Web Service and a HTTP service. I have to access both of them with .NET clients.
What I have tried
0. Setting up the environment
I have installed the SSLCACertificates (on root and two intermediate) and the client certificate in my local machine (win 7 professional) using certmgr.exe.
1. For the web service
I have the client certificate (der).
The service will be consumed via a .NET proxy.
Here's the code:
OrderWSService proxy = new OrderWSService();
string CertFile = "ClientCert_DER.cer";
proxy.ClientCertificates.Add(new System.Security.Cryptography.X509Certificates.X509Certificate(CertFile));
orderTrackingTO ot = new orderTrackingTO() { order_id = "80", tracking_id = "82", status = stateOrderType.IN_PREPARATION };
resultResponseTO res = proxy.insertOrderTracking(ot);
Exception reported at last statement: The request failed with an empty response.
2. For the HTTP interface
it is a HTTPS interface I have to call through POST method.
The HTTPS request will be send from a .NET client using HTTPWebRequest.
Here's the code:
string PostData = "MyPostData";
//setting the request
HttpWebRequest req;
req = (HttpWebRequest)HttpWebRequest.Create(url);
req.UserAgent = "MyUserAgent";
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.ClientCertificates.Add(new System.Security.Cryptography.X509Certificates.X509Certificate(CertFile, "MyPassword"));
//setting the request content
byte[] byteArray = Encoding.UTF8.GetBytes(PostData);
Stream dataStream = req.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
//obtaining the response
WebResponse res = req.GetResponse();
r = new StreamReader(res.GetResponseStream());
Exception reported at last statement: The request was aborted: Could not create SSL/TLS secure channel.
3. Last try: using the browser
In Chrome, after installing the certificates, if I try to access both urls I get a 107 error:
Error 107 (net::ERR_SSL_PROTOCOL_ERROR)
I am stuck.

The following should help you identify the issue, here are two methods to test SSL connectivity one tests the site whilst the other is a callback method to identify why SSL failed. If nothing else it should give you a better idea why it is failing.
When the method is called it will pop up with the select certificate dialog box, obviously when you do this for real you'll want to read from the cert store automatically. The reason I have put this in is because if no valid certificate is found then you will know your problem is with the way the certificate is installed.
The best thing to do is put this code in a simple console app:
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.Net;
private static void CheckSite(string url, string method)
{
X509Certificate2 cert = null;
ServicePointManager.ServerCertificateValidationCallback += ValidateRemoteCertificate;
X509Store store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection certcollection = (X509Certificate2Collection)store.Certificates;
// pick a certificate from the store
cert = X509Certificate2UI.SelectFromCollection(certcollection,
"Caption",
"Message", X509SelectionFlag.SingleSelection)[0];
store.Close();
HttpWebRequest ws = (HttpWebRequest)WebRequest.Create(url);
ws.Credentials = CredentialCache.DefaultCredentials;
ws.Method = method;
if (cert != null)
ws.ClientCertificates.Add(cert);
using (HttpWebResponse webResponse = (HttpWebResponse)ws.GetResponse())
{
using (Stream responseStream = webResponse.GetResponseStream())
{
using (StreamReader responseStreamReader = new StreamReader(responseStream, true))
{
string response = responseStreamReader.ReadToEnd();
Console.WriteLine(response);
responseStreamReader.Close();
}
responseStream.Close();
}
webResponse.Close();
}
}
/// <summary>
/// Certificate validation callback.
/// </summary>
private static bool ValidateRemoteCertificate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error)
{
// If the certificate is a valid, signed certificate, return true.
if (error == System.Net.Security.SslPolicyErrors.None)
{
return true;
}
Console.WriteLine("X509Certificate [{0}] Policy Error: '{1}'",
cert.Subject,
error.ToString());
return false;
}

Related

"Could not create SSL/TLS secure channel" while setting SecurityProtocol and ServerCertificateValidationCallback

I know that this question was asked many times on Stackoverflow, however I looked at many of the answers, yet I still couldn't find a solution that worked for me.
I have this block of code, in which I am trying to send a request to a server using WebClient
using (var client = new CertificateWebClient())
{
var postData = $"<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:v3='http://xmldefs.volkswagenag.com/PP/QM/GroupProblemManagementService/V3'><soapenv:Header><To xmlns='http://www.w3.org/2005/08/addressing'>ws://volkswagenag.com/PP/QM/GroupProblemManagementService/V3</To><Action xmlns='http://www.w3.org/2005/08/addressing'>http://xmldefs.volkswagenag.com/PP/QM/GroupProblemManagementService/V3/KpmService/GetMultipleProblemDataRequest</Action><MessageID xmlns='http://www.w3.org/2005/08/addressing'>${{= \"urn:uuid:\" + UUID.randomUUID()}}</MessageID></soapenv:Header><soapenv:Body><v3:GetMultipleProblemData><UserAuthentification><UserId>{Username}</UserId></UserAuthentification><OverviewAddress><AddressTimestamp/><ContactPerson/><Description/><OrganisationalUnit>KPMEE-05</OrganisationalUnit><Group/><Plant>Z$</Plant></OverviewAddress><ActiveOverview>true</ActiveOverview><PassiveOverview>false</PassiveOverview></v3:GetMultipleProblemData></soapenv:Body></soapenv:Envelope>";
var postArray = Encoding.UTF8.GetBytes(postData);
try
{
var resposneData = Encoding.UTF8
.GetString(client.UploadData("https://ws-gateway-cert.volkswagenag.com/services", postArray));
Debug.WriteLine(resposneData);
}
catch (WebException we)
{
var webResponse = (HttpWebResponse)we.Response;
if (webResponse != null)
{
Debug.WriteLine(GetErrorText(webResponse.StatusCode.ToString()));
}
else
{
Debug.WriteLine(we);
}
}
}
Now since the server requires a certificate for authentication, I created a sub class that inherits from WebClient and uses HttpWebRequest to add the certificate.
internal class CertificateWebClient : WebClient
{
protected override WebRequest GetWebRequest(Uri address)
{
HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
string filePath = "../../Resources/Systemuser PAG R_ PAG_KPM_PREH_MFL VWPKI A145CCD2D02592DB.p12";
X509Certificate2 certificate = new X509Certificate2(filePath, ""); //password is not shown in here
request.ClientCertificates.Add(certificate);
return request;
}
}
When starting the program in debug mode, I get the following error:
The request was aborted: Could not create SSL/TLS secure channel.
I tried fixing this problem by setting the SecurityProtocol to Tls1.2, since this is what the server is using, setting Expect100Continue to true and setting the ServerCertificateValidationCallback, as it was suggeested in many responses.
internal class CertificateWebClient : WebClient
{
protected override WebRequest GetWebRequest(Uri address)
{
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, sslError) => true;
HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
string filePath = "../../Resources/Systemuser PAG R_ PAG_KPM_PREH_MFL VWPKI A145CCD2D02592DB.p12";
X509Certificate2 certificate = new X509Certificate2(filePath, ""); //password is not shown in here
request.ClientCertificates.Add(certificate);
return request;
}
}
That unfortunately couldn't fix the problem either. I know that the certificate is working tho, since I set up a test environment via Soap UI, using the same certificate, the same username and the same URI. And it worked perfectly. I use .NET Framework version 4.8
What am I missing out on?
I was able to solve the problem by using another Constructor for the X509Certificate2 class. Instead of using X509Certificate2(string, string) I used X509Certificate(string, string, X509KeyStorageFlags)
internal class CertificateWebClient : WebClient
{
protected override WebRequest GetWebRequest(Uri address)
{
HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
string filePath = "../../Resources/Systemuser PAG R_ PAG_KPM_PREH_MFL VWPKI A145CCD2D02592DB.p12";
X509Certificate2 certificate = new X509Certificate2(filePath, "", X509KeyStorageFlags.UserKeySet); //password is not shown here
request.ClientCertificates.Add(certificate);
return request;
}
}
By doing so, the server accepted the certificate and the request was successful.

Unable to make HttpWebRequest that is working on Postman

I have a token request that works on Postman on a server.
No body, just basic authentication with username and password:
However, I have this code below that returns the error:
The request was aborted: Could not create SSL/TLS secure channel.
Here is the code below:
string responsedata = string.Empty;
String encoded = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(urlToken);
request.Method = "POST";
request.Headers.Add("Authorization", "Basic " + encoded);
request.PreAuthenticate = true;
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (Stream stream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(stream))
{
responsedata = reader.ReadToEnd();
}
What am I doing worng?
I suspect your problem is related to the SecurityProtocol your application runs on.
Try running this before your request.
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
The endpoint your trying to connect probably requires a higher version of Tls than what your application is providing.
The default value of this is defined by the system it runs on see:
https://learn.microsoft.com/en-us/dotnet/api/system.net.securityprotocoltype?view=net-7.0#system-net-securityprotocoltype-systemdefault
So when running on an older OS this often is too low for modern API endpoints.
You can test this by enabling or disabling specific versions of TLS/SSL in Postman, look for "Protocols disabled during handshake" in the settings tab for your request.
Like Neil Moss already commented above...
Think you are in use of untrusted certificate on server side so try to ignore certificate validation...
request.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;

Http.WinHttpException sent on SOAP request .NET CORE 2.2. The message received was unexpected or badly formatted

We have three IHostedService in our .NETCore2.0 webapp performing operations periodically. Two of them are in polling on an external system asking for new data; the third sends to the same external system some data gathered by our webapp. Every request is SOAP and it's done with the following code:
try
{
#region PFC Certificate
// Pfx certificate management
string certPath = GetCertPath();
string certPass = GetCertPassword();
X509Certificate2Collection X509collection = new X509Certificate2Collection();
X509collection.Import(certPath, certPass, X509KeyStorageFlags.PersistKeySet);
#endregion
if (X509collection.Count > 0)
{
X509Certificate2 x509 = X509collection[0];
var request = CreateSOAPWebRequest(url, x509);
byte[] bytes;
bytes = Encoding.ASCII.GetBytes(xmlRequestContent);
request.ContentType = "application/xml; encoding='utf-8'";
request.ContentLength = bytes.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(bytes, 0, bytes.Length);
requestStream.Close();
if (request == null) throw new Exception($"url:{url}: Request NULL - xml: {xmlRequestContent}");
try
{
using (HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync())
{
if (response.StatusCode == HttpStatusCode.OK)
{
using (Stream responseStream = response.GetResponseStream())
{
// Response deserialization
string responseStr = await new StreamReader(responseStream).ReadToEndAsync();
T result = new T();
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (StringReader reader = new StringReader(responseStr))
{
result = (T)(serializer.Deserialize(reader));
return result;
}
}
}
}
}
catch (WebException ex)
{
_logger.LogError(ex);
throw;
}
}
return default(T);
}
catch(Exception ex)
{
_logger.LogError(ex);
throw;
}
The CreateSOAPWebRequest method is defined as:
private HttpWebRequest CreateSOAPWebRequest(string url, X509Certificate certificate)
{
Uri uri = new Uri(url);
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);
webRequest.Proxy = null;
webRequest.Headers.Add("SOAP:Action");
webRequest.KeepAlive = true;
webRequest.ContentType = "text/xml;charset=\"utf-8\"";
webRequest.Accept = "text/xml";
webRequest.Method = "POST";
webRequest.AuthenticationLevel = AuthenticationLevel.MutualAuthRequired;
if (certificate != null)
webRequest.ClientCertificates.Add(certificate);
return webRequest;
}
The first two hosted services worked very well together for years since the third cames in: some requests go OK in the beginning, then this exception is thrown and no one of the services is able to send the SOAP request anymore (until we restart the webapp):
The SSL connection could not be established, see inner exception. Authentication failed, see inner exception.
---> The SSL connection could not be established, see inner exception.
---> Authentication failed, see inner exception.
---> The message received was unexpected or badly formatted
This is thrown on the line
HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync()
This seems to be a certificate/security/SSL problem. But the requests work well in the beginning and/or without the third hosted service, so we thought it could be a syncronization problem between the services and we detached the third one by running it on a separate cloned webapp, alone, but we got the same error on the second SOAP call anyway (while the first worked).
We were able to reproduce this error in debug only by disabling the service in the production environment and running the webapp locally in debug mode, reading and sending production data.
We have no idea on what is causing this, so thank you in advance for every advice.
I finally figured it out. The whole thing was a bit misleading while the exception was telling the truth: at some point in our code, after the SOAP request, the flow COULD go through this:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls
So everything was fine until this instruction and of course everything was broken after this instruction.

HttpWebRequest.GetResponse() gives SendFailure to EC2 behind ELB over https

Our .NET code is posting using JSON to an HTTPS REST API. It works on machines from Vista up (I think they all have .NET 4.5), however, we need to support down to Win XP with .Net 4.0 and on that machine our code fails on the last line shown below.
If we change the URI to HTTP from HTTPS, it works fine. A number of the lines you see below (such those for CertificateValidationCallBack) were added in an attempt to fix this issue).
BIG FLAG: We have noticed that on this XP machine, IE 8 gives "Internet Explorer cannot display the webpage" over HTTPS, though HTTP is fine. We added the CertificateValidationCallBack handler hoping it would get around this issue. I'm not sure it does.
Some information about the server configuration. It is an EC2 instance behind an ELB. The ELB holds the SSL cert and sends data received from port 443 to port 80. Apache on the EC2 instance just deals with port 80.
ADDED: sslchecker.com shows a good certificate.
private static bool CertificateValidationCallBack(
object sender,
System.Security.Cryptography.X509Certificates.X509Certificate certificate,
System.Security.Cryptography.X509Certificates.X509Chain chain,
System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
return true;
}
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);
webRequest.PreAuthenticate = true;
using (var client = new WebClient())
{
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
try
{
string responseString ;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallBack;
_webEx = null;
webRequest.ContentType = "application/json";
webRequest.Method = "POST";
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(credsString);
// webRequest.ContentLength = bytes.Length;
webRequest.KeepAlive = false;
webRequest.Timeout = -1;
System.IO.Stream os = webRequest.GetRequestStream();
os.Write(bytes, 0, bytes.Length); //Push it out there
os.Close();
System.Net.WebResponse resp = webRequest.GetResponse();
. . . . .
}
}

How to: C# WebClient to hit service endpoint (Oauth 2.0 like protocol) with Mutual TLS

I need to hit a service to obtain the app ticket to authenticate against another service. The solution is hosted on a endpoint.
I tried using the POSTMAN app in google chrome and it succeeds and returns me the AppID. When I submit the (POST)request in POSTMAN app, it prompts for a certificate. When I select the correct certificate the call succeeds.
I need to implement the same in C# (in a web application)
I tried using the RestSharp.RestClient library and am constantly getting the following error:
"Client certificate not found in site certificates".
Attaching the code for reference.
var client = new RestSharp.RestClient("MyUrl");
var request = new RestSharp.RestRequest("pksecure/oauth20_clientcredentials.srf", RestSharp.Method.POST);
request.AddParameter("grant_type", "client_credentials");
request.AddParameter("param2", "value2");
request.AddParameter("scope", "machinename");
client.ClientCertificates = new System.Security.Cryptography.X509Certificates.X509CertificateCollection();
client.ClientCertificates.Add(new System.Security.Cryptography.X509Certificates.X509Certificate(
#"E:\MyCertificate.pfx"
, "MyPassword"
));
System.Net.ServicePointManager.ServerCertificateValidationCallback +=
delegate(object sender, System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors sslError)
{
bool validationResult = true;
return validationResult;
};
var response = client.Execute(request);
var content = response.Content; // raw content as string
Pls. help to make this call using the mutual TLS.
TIA,
Sam.
I am not sure why I was not able to get it done using RestSharp.
But I could get it working by using HttpWebRequest Instead.
Also, earlier I was using the Certficate.Pfx file which caused the error. Using the Certificate.Cer file solved the issue.
Posting the code for reference:
var request = (HttpWebRequest)WebRequest.Create("MyURL/oauth20_clientcredentials.srf");
var postData = "grant_type=client_credentials";
postData += "&param2=value2";
postData += "&scope=" + HttpUtility.UrlEncode("machinename");
var data = Encoding.ASCII.GetBytes(postData);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = data.Length;
using (var stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
request.ClientCertificates.Add(new System.Security.Cryptography.X509Certificates.X509Certificate(#"E:\MyCertificate.cer"));
var response = (HttpWebResponse)request.GetResponse();
var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
return appToken;
Thanks,
Sam Jayander Thiagarajan.

Categories