EWS failes with Could not create SSL/TLS secure channel - c#

I'm trying to fetch free / busy using EWS
Ive installed the latest nuget package Microsoft.Exchange.WebServices
I'm also setting everything I know of to ignore cert. errors:
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3;
The code I'm using to fetch appointments:
//Set up the service with correct credentials.
var service = new ExchangeService(ExchangeVersion.Exchange2007_SP1)
{
Credentials = new WebCredentials(Account, Password, Domain),
TraceEnabled = true,
EnableScpLookup = false
};
service.Url = new Uri(ServiceUrl);
// Create a list of attendees.
var attendees = Contacts.Select(contact => new AttendeeInfo { SmtpAddress = contact.Email, AttendeeType = MeetingAttendeeType.Required }).ToList();
// Specify availability options.
var myOptions = new AvailabilityOptions
{
MeetingDuration = 30,
RequestedFreeBusyView = FreeBusyViewType.DetailedMerged,
DetailedSuggestionsWindow = new TimeWindow(DateTime.Now, DateTime.Now.AddDays(Days))
};
// Return a set of free/busy times.
var freeBusyResults = service.GetUserAvailability(attendees, new TimeWindow(DateTime.Now, DateTime.Now.AddDays(Days)),AvailabilityData.FreeBusyAndSuggestions, myOptions);
This code works for 5 out of 6 exchange servers I have, but one of them gives "The request failed. The request was aborted: Could not create SSL/TLS secure channel." error message.
If I set up fiddler to act as an proxy for the call, and tell fiddler to decrypt, everything works.
I just want to ignore ALL ssl errors and get the data, how do I do that?

The Managed API is just using HTTPWebRequest as the underlying class to do the Request/response. If it works with fiddler mostly likely your problem is environmental/Client related. I would suggest you enable tracing https://blogs.msdn.microsoft.com/dgorti/2005/09/18/using-system-net-tracing/ you should then be able to see what happens at the lower level when it fails.

Related

(407) Proxy Authentication Required - Basic Authentication

Edit:
After struggling for a long time to figure this out, I came across a potential solution. As of today (2021-10-19), the latest stable version of System.ServiceModel.*** packages is 4.8.1, but there are release candidates for 4.9.0 which seem to solve exactly the problem I'm having here.
I checked the .NET WCF GitHub source and found this release candidate (version 4.9.0-rc1.21431.2) which has exactly what I'm looking for. They've updated the HttpTransportBindingElement to include a Proxy property. Obviously it is not stable release yet, but it still gets the job done. With that I was able to solve the original problem using something that looks like this:
using (var myWsdlClient = new MyWsdlGeneratedClient())
{
var binding = myWsdlClient.Endpoint.Binding as BasicHttpBinding;
var customBinding = new CustomBinding(binding);
var htbe = customBinding.Elements.Find<HttpTransportBindingElement>();
htbe.AuthenticationScheme = AuthenticationSchemes.Basic;
htbe.ProxyAuthenticationScheme = AuthenticationSchemes.Basic;
htbe.UseDefaultWebProxy = false;
htbe.BypassProxyOnLocal = false;
htbe.Proxy = new WebProxy
{
Address = new Uri("http://myproxyaddress.com:8080"),
/* Proxy creds */
Credentials = new NetworkCredential("MyProxyUserName", "MyProxyPassword"),
BypassProxyOnLocal = false
};
myWsdlClient.Endpoint.Binding = customBinding;
/* Client creds */
myWsdlClient.ClientCredentials.UserName.UserName = "MyClientUserName";
myWsdlClient.ClientCredentials.UserName.Password = "MyClientPassword";
/* Send request */
myWsdlClient.Endpoint.Address = new EndpointAddress("https://myclientaddress.com");
myWsdlClient.doSomeAction(actionRequest); // <-- IT WORKS!!!
}
Original question:
I'm trying to send a WCF service request through a web proxy, and I'm receiving the error "Remote Server returned an error: (407) Proxy Authentication Required". I've already generated the proxy classes with a WSDL, set up the bindings/endpoints etc. in my app.config (it is a BasicHttpBinding). The problem is: both the client and the proxy require Basic authentication, and I can only seem be able to set the client credentials, not the proxy.
Things I've already tried:
I saw online you could try to pass credentials in the URL of the proxy itself. So I did this programatically for the ProxyAddress property on the binding, like so:
using (var myWsdlClient = new MyWsdlGeneratedClient())
{
var binding = myWsdlClient.Endpoint.Binding as BasicHttpBinding;
/* Client creds */
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
myWsdlClient.ClientCredentials.UserName.UserName = "MyClientUserName";
myWsdlClient.ClientCredentials.UserName.Password = "MyClientPassword";
/* Proxy creds */
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.Basic;
binding.UseDefaultWebProxy = false;
binding.BypassProxyOnLocal = false;
binding.ProxyAddress = new Uri("http://MyProxyUserName:MyProxyPassword#myproxyaddress.com:8080");
/* Send request */
myWsdlClient.Endpoint.Address = new EndpointAddress("https://myclientaddress.com");
myWsdlClient.doSomeAction(actionRequest); // <-- error is thrown here, inner exception is 407 HTTP response
}
I also tried with default web proxy (it sorta worked). Again, I set it programatically like so:
using (var myWsdlClient = new MyWsdlGeneratedClient())
{
var binding = myWsdlClient.Endpoint.Binding as BasicHttpBinding;
/* Client creds */
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
myWsdlClient.ClientCredentials.UserName.UserName = "MyClientUserName";
myWsdlClient.ClientCredentials.UserName.Password = "MyClientPassword";
/* Proxy creds */
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.Basic;
binding.UseDefaultWebProxy = true;
binding.BypassProxyOnLocal = false;
var defaultProxyBefore = WebRequest.DefaultWebProxy;
var newProxy = new WebProxy
{
Address = new Uri("http://myproxyaddress.com:8080"),
Credentials = new NetworkCredential("MyProxyUserName", "MyProxyPassword"),
BypassProxyOnLocal = false
};
WebRequest.DefaultWebProxy = newProxy;
/* Send request */
myWsdlClient.Endpoint.Address = new EndpointAddress("https://myclientaddress.com");
try
{
myWsdlClient.doSomeAction(actionRequest);
}
finally
{
WebRequest.DefaultWebProxy = defaultProxyBefore;
}
}
The good thing about this second approach is that it actually worked! However, it is not enough for the requirements of my project. The application I am developing is sending loads of requests per second on different threads, some of which are going through the default proxy. I don't want all those unrelated requests to go through my "new" proxy, they should continue to go through the default.
So to summarize, I need a way of setting the proxy per-request, while also being able to set Basic authentication for both the client and the proxy. I'm not very experienced with WCF and I have just stumbled along the concept of "Custom bindings", which seems promising, but I still haven't found if it can do what I need. Any help on this is incredibly appreciated!
Welcome to Stack Overflow. Thanks for your detailed question.
The "proper" solution is to use an HTTPS proxy (not an HTTP proxy).
If this isn't feasible, you can set the Binding's security mode to BasicHttpSecurityMode.TransportCredentialOnly. (Because Basic Authentication isn't encrypted, I don't recommend doing this in a Production application.)
Below is an example based on your original post. Let me know if it works for you.
using (var myWsdlClient = new MyWsdlGeneratedClient())
{
var binding = myWsdlClient.Endpoint.Binding as BasicHttpBinding;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
/* Client creds */
myWsdlClient.ClientCredentials.UserName.UserName = "MyClientUserName";
myWsdlClient.ClientCredentials.UserName.Password = "MyClientPassword";
/* Disable HTTPS requirement */
binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
/* Proxy creds */
/*
* Since the credentials for the Proxy are in the URL,
* set the proxy credential type to None (the default value).
* Otherwise, WCF may attempt using myWsdlClient.ClientCredentials to
* authenticate with the Proxy.
*/
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
/* Note: UseDefaultWebProxy is true by default. */
binding.UseDefaultWebProxy = false;
binding.BypassProxyOnLocal = false;
/* Ensure your Proxy Server supports passing credentials in the URL. */
binding.ProxyAddress = new Uri("http://MyProxyUserName:MyProxyPassword#myproxyaddress.com:8080");
/* Send request */
myWsdlClient.Endpoint.Address = new EndpointAddress("https://myclientaddress.com");
myWsdlClient.doSomeAction(actionRequest);
}

How to associate a certificate with a soap request in C#?

I am new to Web Services in general, and we are using .Net Framework 4.5.2, anyway I am trying to consume a web service that requires a certificate and a password.
I added the certificate gained from the providers in the project properties --> Resources --> file --> add, I also tried to use the SetCertificate() function but It seems to be a little complicated for me so I stick with loading the certificate from the properties as mentioned, however I already set all the binding setting as wanted, but somehow I am missing something, Here is my code:
string clientUrl = "some wsdl URL goes here";
BasicHttpsBinding binding = new BasicHttpsBinding
{
MaxReceivedMessageSize = Int32.MaxValue,
MaxBufferSize = Int32.MaxValue,
SendTimeout = new TimeSpan(0, 15, 0),
MessageEncoding = WSMessageEncoding.Text,
Security = {
Mode = BasicHttpsSecurityMode.Transport,
Transport = {
ClientCredentialType = HttpClientCredentialType.Certificate
}
}
};
ClientsClient testClient = new ClientsClient(binding, new EndpointAddress(new Uri(clientUrl)));
testClient.ClientCredentials.ClientCertificate.Certificate = LoadCertification();
private X509Certificate2 LoadCertification()
{
byte[] bytes = Properties.Resources.publicCert;
return new X509Certificate2(bytes, "password");
}
Note 1: The certificate extenstion is '.p12', It may be a list of certifications, if that is the case!, is it possible to pass them all?.
In the code I presented I am always getting The exception:
System.ServiceModel.ProtocolException: The 'Security' header from the namespace 'Some Http url goes here' not was understood by the recipient of the message. The message was not processed. The error usually indicates that the sender of the message has enabled a communication protocol that cannot be processed by the recipient. Verify that the client binding configuration is consistent with the service binding.
I tried to test the web service with "SOAP UI" and it worked, which made me sure that I am doing something wrong with the code, So I appreaciate any possible help that explains how to associate the certifcate in the code in the right way!.
EDIT:
in the .p12 file there are 3 certifications, which I tried to add also like this:
X509Certificate2Collection coll = LoadCertification();
int count = 0;
foreach (X509Certificate2 cert in coll)
{
testClient.ClientCredentials.ClientCertificate.Certificate = cert;
count++;// this variable is just to check the number of certificates
}
And I modified the loadCertification() method to look like this:
private X509Certificate2Collection LoadCertification()
{ string certPath = "C:/Users/ISA/Desktop/Progetti/Certificato e password/name.p12";
X509Certificate2Collection coll = new X509Certificate2Collection();
coll.Import(certPath , "password", X509KeyStorageFlags.DefaultKeySet);
return coll;
}

.Net Core + Kubernettes > AuthenticationException: The remote certificate is invalid according to the validation procedure

I'm working on several Dotnet Core APIs hosted on a Kubernettes cluster and some of the APIs do call other APIs, and that's when the exception in title is thrown.
It doesn't matter whether I edit the appsettings.json and replace all https by http -in fact people at devops team suggested me to do that- as the same exception is thrown.
This is the little piece of code I use for the http call:
int idCity = Convert.ToInt32(Utils.GetConfig().GetSection("Settings")["idCity"]);
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(Utils.GetConfig().GetSection("xxx")["xxxx"]);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string queryString = "?startDate=" + startDate + "&endDate=" + endDate + "&idCity=" + idCity;
HttpResponseMessage response = client.GetAsync(queryString).GetAwaiter().GetResult();
if (response.IsSuccessStatusCode)
{
var resultHolidays = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
return JsonConvert.DeserializeObject<JSONGeneric<HolidayDTO>>(resultHolidays);
}
else
{
return null;
}
}
I have a copy of the certificate in .crt format and also tried:
string certPath = Path.Combine(_env.ContentRootPath, _configuration.GetSection("Certificate")["certificatePath"]);
string pwd = _configuration.GetSection("Certificate")["certificatePwd"];
HttpClientHandler requestHandler = new HttpClientHandler();
requestHandler.ClientCertificates.Add(new X509Certificate2(certPath, pwd,
X509KeyStorageFlags.MachineKeySet));
using (HttpClient client = new HttpClient(requestHandler))
{
...
}
To no avail, as the same exception is thrown.
I'm not an expert on working with certificates, but I truly need to make this to work, to be able to make on api in a pod call other api, so any help will be much appreciated.
Update 1: The "weird" thing is that if I just copy the url to be requested -no matter if you use http or https- and paste it into a browser with the certificate installed it does work. If you copy and paste the http version of the url n the browser, Kubernettes (or whoever it is) does a redirection to the https version but in the end you get results. Not from .Net
I would start by disabling certificate validation in the client and see what is the behavior. You can do it like this:
var httpHandler = new HttpClientHandler {
ServerCertificateCustomValidationCallback = (m, crt, chn, e) => true
};
using var httpClient = new HttpClient(httpHandler);
// rest of the code
If the call succeeds, the next step is to adapt the certificate validation callback to check the server's certificate.
Note: in your example you're configuring a client certificate, which is useful if you host a service and want to authorize your clients based on their certificates, as described here. From the problem description I understand that what you need is the opposite: validate the server certificate in your client.
var srvCrt = new X509Certificate2(certPath, pwd);
var httpHandler = new HttpClientHandler {
ServerCertificateCustomValidationCallback = (m, crt, chn, e) => {
return crt.Thumbprint == srvCrt.Thumbprint;
}
};
using var httpClient = new HttpClient(httpHandler);
// rest of the code

Same x509certificate in server and client

I have the same certificate (use x509certificate2) in the application and in the server (both in C#),
I have to extend the validity of the certificate file in the server,
I use in SSL3 with the certificate to connect to soap web service from client (with set or get)
My question Is it mandatory for the same certificate to be in the app?
try
{
// Initiate a new request to the WS
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
MyServiceSSL.MyWebServiceClient clClient = new MyServiceSSL.MyWebServiceClient();
clClient.Endpoint.Address = new System.ServiceModel.EndpointAddress(sConnStr);
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(ValidateRemoteCertificate);
try
{
clClient.ClientCredentials.ClientCertificate.SetCertificate(
StoreLocation.LocalMachine,
StoreName.TrustedPeople,
X509FindType.FindBySubjectName,
"myDomain.com");
}
catch (Exception e)
{
...
}
MyServiceSSL.myDataRequest clRequest = new MyServiceSSL.myDataRequest();
// Set up request parameters
clRequest = new myDataRequest();
clResult = clClient.myFunc(clRequest.MyRequest_1);
I have to extend the validity of the certificate file in the server,
Does that mean the validity date? If so, of course you need to renew both otherwise one would be expired and therefore not valid any more.
BTW: You should not use SSL3

gRPC and ASP Net Core: using SslCredentials with non-null arguments is not supported by GrpcChannel

I am trying to connect from a client to the service. The service is configurated to use a self signed Ssl certificate and I am trying to configurate the client with the client certificate. I am using this code:
string cacert = System.IO.File.ReadAllText("certificados/ca.crt");
string cert = System.IO.File.ReadAllText("certificados/client.crt");
string key = System.IO.File.ReadAllText("certificados/client.key");
KeyCertificatePair keypair = new KeyCertificatePair(cert, key);
SslCredentials sslCreds = new SslCredentials(cacert, keypair);
var channel = GrpcChannel.ForAddress("https://x.x.x.x:5001", new GrpcChannelOptions { Credentials = sslCreds });
var client = new Gestor.GestorClient(channel);
But I am getting the following error: using SslCredentials with non-null arguments is not supported by GrpcChannel.
I don't understand very good the message error. SslCredentials is ChannelCredentials? type, and SslCreds is Grpc.Core.SslCredentials. It can be compiled, so the type I guess it is correct.
What I would like to know it is how I can configure the client to use the self signed certificate that I have created.
Thanks.
The SslCredentials support in only available grpc-dotnet is to provide some level of compatibility with Grpc.Core in the most common use case, it doesn't expose all the functionality though. In grpc-dotnet, only SslCredentials() (parameterless which uses the default roots) is supported. If you want to provide your self-signed creds, you can certainly do that, you'll need to use a different API for configuring GrpcChannel:
See example here (creating a GrpcChannel with custom credentials).
https://github.com/grpc/grpc-dotnet/blob/dd72d6a38ab2984fd224aa8ed53686dc0153b9da/testassets/InteropTestsClient/InteropClient.cs#L170
I spend a fair bit of time googling around for solutions to this problem, and didn't find a concise answer. Here is ultimately how I was able to configure a dotnet client to use mutual SSL authentication:
MyService.MyServiceClient GetClient(){
var httpClientHandler = new HttpClientHandler();
// Validate the server certificate with the root CA
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, _) => {
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(new X509Certificate2("ca.crt"));
return chain.Build(cert);
};
// Pass the client certificate so the server can authenticate the client
var clientCert = X509Certificate2.CreateFromPemFile("client.crt", "client.key");
httpClientHandler.ClientCertificates.Add(clientCert);
// Create a GRPC Channel
var httpClient = new HttpClient(httpClientHandler);
var channel = GrpcChannel.ForAddress("https://localhost:8080", new GrpcChannelOptions{
HttpClient = httpClient,
});
return new MyService.MyServiceClient(channel);
}

Categories