I need to develop a C# client able to post XML docs into a SAP Bussiness Connector 4.6.
The Client:
.NET until 4.6, VS 2013 available, SO Win 8.1 Pro 64 bits.
The Server:
Windows 2000, with SAP Business Connector 4.6 (it's really equal to WebMethods 4.6), configured with https and Client certificate as authentication method. The server has a certificate that doesn't match the URL used in local development against it.
I'm trying several .NET methods and clients. I'm actually trying httpClient to connect with BC, without success.
The actual code is very similar to this:
try
{
System.Net.Http.HttpClient client;
//HttpClient uses the HttpMessageHandler pipeline for sending and receiving requests
//WebRequestHandler derives from HttpClientHandler but adds properties that generally only are available on full .NET
System.Net.Http.WebRequestHandler wrHandler = new System.Net.Http.WebRequestHandler();
System.Security.Cryptography.X509Certificates.X509Certificate x509cert = System.Security.Cryptography.X509Certificates.X509Certificate2.CreateFromCertFile("MyClientCertificate.crt");
wrHandler.ClientCertificates.Add(x509cert);
client = new System.Net.Http.HttpClient(wrHandler);
//THIS SKIPS SERVER CERTIFICATE
System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
System.Net.Http.HttpRequestMessage request = new System.Net.Http.HttpRequestMessage();
request.Method = System.Net.Http.HttpMethod.Post;
request.RequestUri = new System.Uri("https://192.168.12.12:3333/invoke/wm.PartnerMgr.flows.UCLR.0000000001:ORDERS");
request.Content = new System.Net.Http.StringContent(CtrXMLToSendContent.Text, Encoding.UTF8, CtrClient1MediaType.Text);
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Ssl3 | System.Net.SecurityProtocolType.Tls;
Task<System.Net.Http.HttpResponseMessage> taskResponse = client.SendAsync(request);
if (taskResponse.Wait(-1)) //-1 = infinite
{
//Response received
System.Net.Http.HttpResponseMessage response = taskResponse.Result;
TextResponseData.Text = response.ToString();
Task<String> taskResponseStr = response.Content.ReadAsStringAsync();
taskResponseStr.Wait();
String responseContent = taskResponseStr.Result;
TextResponseContent.Text = responseContent;
}
else
{
TextClient1Infolog.AppendText("Wait ");
}
}
catch (Exception _e)
{
TextClient1Infolog.AppendText("Error :" + Environment.NewLine);
TextClient1Infolog.AppendText(_e.ToString());
}
When I launch it, it throws an exception, giving this error:
System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel.
There's no server response at all I can explore or debug. I think it's a problem of handshaking SSL protocol. I must insist: There's no response text from server, no 402 or 404 message errors at all: Nothing is received.
Firefox over that direction says no connection. IE, with TSL 1.0, 1.2 and 1.3 activated, says it cannot connect; activating SSL 3.0 and reopening page says there's a problem with server certificate (yes, because I'm accesing from local network, where certificate states for something like "this.server.com").
Fiddler4 doesn't say me much (and autocertificates appears to be confusing something), SoapUI appears to be not very useful here (haven't see way to set certificates and SSL handshakes).
Found a tool (TestSSLServer, link: http://www.bolet.org/TestSSLServer/) that gave me this info:
Supported versions: SSLv3 TLSv1.0 Deflate compression: no Supported
cipher suites (ORDER IS NOT SIGNIFICANT):
SSLv3
RSA_EXPORT_WITH_RC4_40_MD5
RSA_WITH_RC4_128_MD5
RSA_WITH_RC4_128_SHA
RSA_EXPORT_WITH_RC2_CBC_40_MD5
RSA_EXPORT_WITH_DES40_CBC_SHA
RSA_WITH_DES_CBC_SHA
RSA_WITH_3DES_EDE_CBC_SHA
(TLSv1.0: idem)
---------------------- Server certificate(s):
188659f61762af0de690bf5cb76a8554e7ff7f23: CN=my.server.com,
OU=Domain Control Validated
---------------------- Minimal encryption strength: weak encryption (40-bit) Achievable encryption strength: strong encryption
(96-bit or more) BEAST status: vulnerable CRIME status: protected
So I suppose I have to force SSL3 handshake (I think this is OK in my code), validate server certificate (too)... perhaps I must do something with client certificate.
Must I generate one for my client computer and load it? How can I generate one with one of the encriptions of the TestSSLServer's list? Wich file should be installed in what computer?
And I'm open to hear about other http clients (ServerXMLHTTP, etc) or tools able to test xml postings over xml. Any ideas about how to proceed for now?
Related
Similar questions have been asked multiple times before - but before flagging this one as duplicate please read on. Most of these questions are very old. I have worked through a lot of questions and answers and did not find a suitable solution.
We have an Azure Cloud Service project in .net 4.5. It connects to dozens of our customers' APIs (not necessarily cloud hosted) without any problems, but a single API fails with this error message:
The request was aborted: Could not create SSL/TLS secure channel
What am I missing here?
This is the code (slightly condensed) that I am using to connect to the API (this runs per API, so the base URL does not change):
ServicePointManager.ServerCertificateValidationCallback += ValidateRemoteCertificate;
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol =
SecurityProtocolType.Tls12 |
SecurityProtocolType.Tls11 |
SecurityProtocolType.Tls |
SecurityProtocolType.Ssl3;
ApiClient = HttpClientFactory.Create();
ApiClient.DefaultRequestHeaders.Authorization = null;
ApiClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "Basic {passwordToken}");
ApiClient.DefaultRequestHeaders.Accept.Clear();
ApiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
SemaphoreSlim throttler = new SemaphoreSlim(initialCount: 50);
var sp = ServicePointManager.FindServicePoint(new Uri(baseUrl));
sp.SetTcpKeepAlive(true, 30000, 30000);
foreach (var request in urls)
{
Result = new HttpResponseMessage();
Result = await ApiClient.GetAsync(url);
...
}
This is what makes it hard to debug:
This problem only occurs in production, i.e. when running as an Azure Cloud Service. Not when debugging locally.
It only occurs with requests sent through HttpClient. Not with WebClient.
Further research (comparing the APIs) revealed that this API is the only one that has enabled SNI and ONLY supports TLS1.2.
Suggestions considered from other questions/answers regarding SNI in .net Framework:
To prevent misunderstandings: This is about the cloud service connecting to an API, not about a connection that is being made to the cloud service.
The HttpClient instance is being reused for all requests to a single API. (This is important as this answer suggests that the SNI tag will be created with the domain HttpClient has been initialized with). I have also tried configuring TLS after the Factory instantiated the HttpClient. No change.
The certificates are valid of course. No self-signed certificates but regular trusted ones off the shelf. Opening the API in any browser also works like a charm.
TLS1.2 is not enabled by default in .net framework 4.5, but the line ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12should actually enable it. Is there anything wrong with the way I am doing it?
Calling the API in curl (WSL and Azure remote bash) with curl --user 'user:pwd' https://myurl also works perfectly and returns the expected data.
Testing tls1.2 with openssl like openssl s_client -connect hostname:443 -tls1_2 does not reveal any issues. The chain is displayed correctly, and a TLSv1.2 session is confirmed. Testing the server's SNI feature with openssl with openssl s_client -connect host:443 -tls1_2 -servername host -tlsextdebug -msg reveals SNI support by returning TLS server extension "server name" (id=0), len=0 I get the same certificate if I provide a completely different fantasy hostname though.
I captured the TLS/SNI handshake when debugging locally (see screenshot below). No issues. My ability to debug ends with the cloud service. I would love to see the handshake between the cloud service and the API in WireShark, but I don't know of any option to analyze network traffic at that layer on an Azure cloud service. But if anyone knows how to capture the handshake process, I'd appreciate some hints.
The server selects ECDHE-RSA-AES256-GCM-SHA384 as a cipher suite during the handshake with openssl which is pretty much default for TLS1.2. I don't have access to the cloud services list of cipher suites that it would provide at Client Hello - any idea how to find out?
I don't have any proof that SNI is actually causing the problem, but this is the only difference between this API and dozens of others I can spot.
Stack trace:
System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel. at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult) at System.Net.Http.HttpClientHandler.GetResponseCallback(IAsyncResult ar) --- End of inner exception stack trace --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at WCFServiceWebRole1.Controllers... in [the line calling GetAsync()]
I ended up re-creating a simple API on one of my servers and configured the software in such a way to send its requests there. That way I could capture the TLS handshake and analyze it in Wireshark. These are the supported cipher suites (client side, which is Azure cloud service):
And these are the cipher suites supported by the API which is not working:
I would assume that there should be a match so that server and client can agree on one. However, I cannot find a match... guess that is what is causing the problem. In fact, the list of supported cipher suites is much longer in a local debugging session - and there is at least one match which explains why it works locally.
I have a .NET Core 3.1 C# application which is calling an API via HTTPS (and presenting its public key as part of getting the token as that certificate is later used to decrypt information sent back separately). On just about all our machines, it is working, but on one Windows 8.1 machine, we get the following series of exceptions when we try to initially connect for an authentication token:
The SSL connection could not be established, see inner exception.
---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
---> System.ComponentModel.Win32Exception (0x80090326): The message received was unexpected or badly formatted.
The exception is thrown from System.Net.Http.HttpClient.FinishSendAsyncBuffered so I suspect it is happening at the HTTPS level and our certificate stuff is not really relevant here anyway.
Our code to get the token looks like this:
The constructor for the auth service:
public XXXXAuthService(IXXDbService dbService, XXXXApiConfig config)
{
_dbService = dbService;
_config = config;
// try forcing TLS1.2 for SSL connection exceptions thrown in some operating environments
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
_httpClient = new HttpClient {BaseAddress = new Uri(config.BaseUrl)};
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
Code to get the auth token:
private async Task<string> GetXXXXBearerToken(string userId, DateTime creationTime)
{
var token = await GenerateProviderJwtForXXXX(userId, creationTime);
var kvp = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange"),
new KeyValuePair<string, string>("subject_token", token),
new KeyValuePair<string, string>("subject_token_type", "urn:ietf:params:oauth:token-type:jwt")
};
var data = new FormUrlEncodedContent(kvp);
var publicKey = await GetXXXXPublicKey();
_httpClient.DefaultRequestHeaders.Remove("X-XXXX-Public-Cert");
_httpClient.DefaultRequestHeaders.Add("X-XXXX-Public-Cert", publicKey);
var response = await _httpClient.PostAsync("Identity/token", data);
if (!response.IsSuccessStatusCode)
throw new Exception("XXXX Token Server Error: " + response.ReasonPhrase);
var result = await response.Content.ReadAsStringAsync();
var authResponse = JsonConvert.DeserializeObject<OAuthResponse>(result);
if (!string.IsNullOrEmpty(authResponse.access_token))
return authResponse.access_token;
System.Diagnostics.Trace.WriteLine("Token Exchange Result: " + result);
if (!string.IsNullOrEmpty(authResponse.error))
{
var outcome = new XXX.XXXX.Model.OperationOutcome();
outcome.Issue.Add(new XXX.XXXX.Model.OperationOutcome.IssueComponent()
{
//some code to throw an error is here
}
throw new XXX.XXXX.Rest.XXXXOperationException("Bearer Token Exchange failed", response.StatusCode);
}
Unfortunately none of the existing questions/advice anywhere on Stack Overflow, or the rest of the web, for this particular error seems to have helped. They are primarily about version discrepancies between client and server which seems not to be the case here as I am forcing TLS 1.2 (which is active and enabled on the failing machine).
Interestingly, I can visit the server URL in a browser via HTTPS just fine, which suggests there is something about my code that is the problem rather than the machine, but it works everywhere else.
I have confirmed that:
The certificate I am using to authenticate the connection on the machine is valid and has a chain of trust (though as above I don't think we are getting that far as the TLS connection itself is failing)
The server we are calling supports TLS 1.2 (by forcing it)
I can get to the website for the URL independently via the browser
Is there something I need to do either in the code or on the machine to get this call to work everywhere?
Things I have tried to resolve the issue
Installing all Windows 8.1 updates to present day
Forcing TLS 1.2 in the code (see above code sample)
Limiting VM to TLS 1.2 only
I might be able to at least point you in the right direction…
Same Symptoms
I had a .NET Core 3.1 web app running on IIS (Windows Server 2012 R2) that got the exact same error and stacktrace when it tried to connect to another server using TLS 1.2. I also had the symptom where I could connect with the browser (Chrome), but not with the app. (Would have been interesting to see if Internet Explorer browser worked though.)
Root Cause
The TLS handshake was failing because the two servers were unable to agree on a common cipher suite. (Using Wireshark, I discovered that when my app tried to connect it provided a more limited set of cipher suites than when the Chrome browser made the call.)
Solution
In my case, I used IIS Crypto (a small free tool: https://www.nartac.com/Products/IISCrypto/) to enable additional cipher suites on my web app's server. I downloaded and ran IIS Crypto, checkmarked additional cipher suites on its Cipher Suites tab, and then restarted the machine.
One of the new cipher suites worked with my app and the destination server, so the TLS handshake was successful and the error was resolved.
One quick caveat: Some cipher suites are more secure than others, so you'll want to read up on best practices.
Addendum
If you want to further diagnose the failure, I'd recommend installing Wireshark (another free tool: https://www.wireshark.org/#download) on the machine with your .NET Core app. If a TLS Handshake Failure is the issue, you will see a message like: Alert (Level: Fatal, Description: Handshake Failure)
This primer on wireshark output helped me:
https://blog.catchpoint.com/2017/05/12/dissecting-tls-using-wireshark/
I faced a simular issue, and in order to help others here's what I concluded:
Sucessfully executing this code doesn't mean that your application supports the specified protocol version, and the "SSL Error" can still occur later on when trying to establish a connection:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
In my case I was trying to force Tls13 and found out that my app configuration didn't actually support it:
net core 3.0 running on a Windows Server Datacenter 2019, version 1809
So I had to change my configuration to the following which provides support for the protocol version I needed:
Net framework 5.0 on a Windows Server Datacenter 2022, OS build 20348.288
I was trying to connect to an endpoint that suddenly dropped Tls 1.2 support (not sure why) and from then on only accepted Tls 1.3.
I have gRPC client / server code happily working on my local machine with the client using ChannelCredentials.Insecure but need to switch to a secure mode. I dont need any certificate checks just encrypted traffic.
Ive been unable to find any configuration of client server that talks to each other.
Running c# core server (gRPC 2.27.0), & c# .net framework client (gRPC 2.28.1).
The server says it publishing on http & https as below:
[20:12:58 DBG] Using development certificate: CN=localhost (Thumbprint: 3EDA2E5BD559D75C9DCF058E0A6994EED859CD34)
[20:12:58 INF] Now listening on: https://localhost:5001
[20:12:58 INF] Now listening on: http://localhost:5000
and client works with:
ChannelBase channel = new Channel("localhost:5000", ChannelCredentials.Insecure);
var client = new MyApp.MyAppClient(channel);
var response = client.Test(request)
If I switch client to SslCredentials as below
ChannelBase channel = new Channel("localhost:5001", new SslCredentials());
var client = new MyApp.MyAppClient(channel);
var response = client.Test(request)
I get the following errors.
server error
[19:32:53 DBG] Failed to authenticate HTTPS connection.
System.IO.IOException: Authentication failed because the remote party has closed the transport stream.
at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
client error
Grpc.Core.RpcException: 'Status(StatusCode=Unavailable, Detail="failed to connect to all addresses")'
I've also tried adding (server) with no change in errors.
services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.RevocationMode = X509RevocationMode.NoCheck;
options.ValidateCertificateUse = false;
options.AllowedCertificateTypes = CertificateTypes.SelfSigned;
});
Any suggestions as to what i need to do to make them talk.
So you need to specify roots.pem that correspond to the dev certificate you're using on your server when creating SslCredentials.
If you use parameterless SslCredentials(), it will use the default trust roots which only work for certificates that have been signed by publicly trusted certificate authorities (e.g. if you're talking "official" servers such as googleapis.com etc, definitely not the case for your development certificates that you are using on your server). So you need to use SslCredentials(yourCustomTrustRootsPem).
Btw, with SSL/TLS, there's no such thing as "no certificate checks". The client will ALWAYS check that it connected to the right server (server's certificate are verifiable by their trust roots) - without that the secure communication would make no sense because it would be too susceptible to man-in-the-middle attack. (the server checking authenticity of client is optional though).
Feel free to refer to https://github.com/jtattermusch/grpc-authentication-kubernetes-examples for extra resources.
I had a similar problem and finally found a solution to establish HTTPs connection between
.NET Framework 4.7.2 client (WPF app) and
ASP .NET Core 3.1 gRPC Server (Console).
Indeed the answer of Jan Tattermusch is correct. You need to supply the server side certificate as PEM to the constructor of SslCredentials and in addition, the certificate has to contain the DNS name or IP of the url you are contacting the server through. In your case, I suggest to manually create a self-signed certificate for each server instead of using the developer certificate. This one can be downloaded by your client and then be passed as SslCredentials. Works like a charm.
See my more details on how to do all the details in my answer to a similar question here:
https://stackoverflow.com/a/63565090/378415
Try to check on which port Grpc Service is running after deployment in any way either windows service or other , then give the same in client to consume the service. Eg: If your service is running on https://localhost:5000 then give the same in client.
It will work for sure.
Mostly you will get the above issue when you make a mistake of mismatching the address of service to client.
Azure Front Door is configured with minimum TLS 1.2.
The backend Azure App Service is also configured to use minimum TLS 1.2.
When running the .Net Framework 4.7.1 console app with the following code on Windows Server 2012 R2:
class Program
{
static async Task Main(string[] args)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var client = new HttpClient();
try
{
var OK = await client.GetAsync("https://foo-appservice.azurewebsites.net/"); // App service.
var NotOK = await client.GetAsync("https://foo.myfoo.io/"); // Front door.
}
catch (Exception e)
{
Console.WriteLine(e);
}
Console.ReadKey();
}
}
I get the following exception on the second call.
An error occurred while sending the request.
The underlying connection was closed: An unexpected error occurred on a send.
Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
Running this with curl from the same server works, however:
curl https://foo.myfoo.io/ -i --tlsv1.2 --tls-max 1.2
The same .net code runs fine from my other windows 10 laptop.
What can be the reason why the call fails on the windows server box?
Edit:
I think this has to do with custom domains, since performing a simple GET to the front door assigned domain works.
Turns out "someone" had disabled a lot of cipher suites on the Windows Server.
I used wireshark to look at the TLS handshake and noticed that the cipher suites listed at the Client Hello didn't match the servers.
So for anyone struggling with TLS errors, it can be worthwile to look at the TLS handshake in Wireshark!
I am trying to call SAP HANA Service Layer through my code but I am stumbling upon the below error : So can anyone please help me on this.
Error 1st :
AuthenticationException: The remote certificate is invalid according to the validation procedure.
Error 2nd :
WebException: The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.
Error 3rd :
HttpRequestException: An error occurred while sending the request.
Some more details about the App - I have created the console app in .NET and it executes on the server [HANA Server is locally accessed as both the servers are in the LAN] Linux & Windows Server.
One more thing - My client is not planning to purchase the domain name and SSL certificate for the SAP HANA Service Layer because it will be consumed by my app internally.
The thing is, when I tested the login service via Postman it's working fine and I am getting the session details as well but the same URL is not working through my code so is there anything more I need to do to access the service?
URL - https://172.17.100.35:50000/b1s/v1/Login
using (var client = new HttpClient())
{
var credentials = new { UserName = "admin", Password = "", "" };
var json = JsonConvert.SerializeObject(credentials);
var response = client.PostAsync(url),
new StringContent(json, Encoding.UTF8, "application/json")).Result;
if (response.IsSuccessStatusCode)
{
dynamic content = JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result);
// Access variables from the returned JSON object
var appHref = content.links.applications.href;
}
}
For future googlers getting a 500 error response after sending your request to the Login endpoint, disable ExpectContinue.
client.DefaultRequestHeaders.ExpectContinue = false;
You need to put this code before postAsync
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
This avoid the validation of the certificate.
If you are using self signed certificates you have to accepted them first. that can be done by add it to windows certificates ou by the browser!
if you are making cross-domain calls, you need to change the b1s.config
vim /usr/sap/SAPBusinessOne/ServiceLayer/conf/b1s.conf
(...)
"CorsEnable": true,
"CorsAllowedOrigins": "*"}
do note use * in production MODE <-<<<--