I made a console application project to host a web service programmatically, but when I try to create a client proxy to my web service and call a method on it, I get the following error:
An error occurred while making the HTTP request to
https://localhost:8000/FileRetrievalPoC. This could be due to the fact
that the server certificate is not configured properly with HTTP.SYS
in the HTTPS case. This could also be caused by a mismatch of the
security binding between the client and the server.
Its inner exception:
The underlying connection was closed: An unexpected error occurred on
a send.
Its inner exception:
Unable to read data from the transport connection: An existing
connection was forcibly closed by the remote host.
Its inner exception:
An existing connection was forcibly closed by the remote host
Program.cs:
class Program
{
static void Main(string[] args)
{
var address = "https://localhost:8000/FileRetrievalPoC";
Console.WriteLine("Starting a service at {0}...", address);
FileRetrievalService.Start(address, StoreLocation.LocalMachine, StoreName.My, "localhost");
Console.WriteLine("Service started.");
Console.WriteLine("Press Enter to create a new proxy client and call the Get method.");
Console.WriteLine("Press Escape to end the application.");
while (true)
{
var key = Console.ReadKey();
if (key.Key == ConsoleKey.Enter)
{
var proxy = FileRetrievalService.Connect(address, "localhost", "exampleUsername", "examplePassword", StoreLocation.LocalMachine, StoreName.My, "localhost");
proxy.Get(#"C:\Users\User\Desktop\Document.txt");
((IClientChannel)proxy).Close();
}
else if (key.Key == ConsoleKey.Escape)
break;
}
FileRetrievalService.Stop();
}
}
IFileRetrieval.cs:
[ServiceContract]
public interface IFileRetrieval
{
[OperationContract]
string Get(string path);
[OperationContract]
void Set(string path, string contents);
}
FileRetrievalService.cs:
class FileRetrievalService : IFileRetrieval
{
private static BasicHttpsBinding _binding = new BasicHttpsBinding()
{
Name = "FileRetrievalPoC",
HostNameComparisonMode = HostNameComparisonMode.Exact,
Security = new BasicHttpsSecurity()
{
Message = new BasicHttpMessageSecurity()
{
AlgorithmSuite = SecurityAlgorithmSuite.Basic256Sha256Rsa15,
ClientCredentialType = BasicHttpMessageCredentialType.UserName
},
Mode = BasicHttpsSecurityMode.TransportWithMessageCredential,
Transport = new HttpTransportSecurity()
{
ClientCredentialType = HttpClientCredentialType.Windows
}
},
SendTimeout = TimeSpan.FromMinutes(1),
CloseTimeout = TimeSpan.FromMinutes(1),
OpenTimeout = TimeSpan.FromMinutes(1),
ReceiveTimeout = TimeSpan.FromMinutes(1)
};
private static ChannelFactory<IFileRetrieval> _channelFactory;
private static ServiceHost _host;
public static void Start(string address, StoreLocation location, StoreName name, string subject)
{
_host = new ServiceHost(typeof(FileRetrievalService));
_host.Credentials.ServiceCertificate.SetCertificate(location, name, X509FindType.FindBySubjectName, subject);
_host.AddServiceEndpoint(typeof(IFileRetrieval), _binding, address);
_host.Open();
}
public static void Stop()
{
if (_host != null)
_host.Close();
if (_channelFactory != null)
_channelFactory.Close();
}
public static IFileRetrieval Connect(string address, string domain, string username, string password, StoreLocation location, StoreName name, string subject)
{
if (_channelFactory == null)
_channelFactory = new ChannelFactory<IFileRetrieval>(_binding, address);
_channelFactory.Credentials.ClientCertificate.SetCertificate(location, name, X509FindType.FindBySubjectName, subject);
_channelFactory.Credentials.UserName.UserName = username;
_channelFactory.Credentials.UserName.Password = password;
_channelFactory.Credentials.Windows.ClientCredential = new NetworkCredential(username, password, domain);
return _channelFactory.CreateChannel();
}
public string Get(string path)
{
throw new NotImplementedException();
}
public void Set(string path, string contents)
{
throw new NotImplementedException();
}
}
Its all done programmatically, and I've looked on Stack Overflow but couldn't find a good reason why this is happening. Does anyone know what the problem is? This source code, you can add to a new console application and run it to try it out on your local machine and see it happen for yourself. Is it the SSL certificate? If so, how can I get more verbosity for the error reason here? Its not a very helpful exception.
Edit: I think I may have missed a step here, such as using netsh to bind a certificate to my machine's port.
My issue was that I did not use netsh to bind the certificate to my machine's port. Open up an administrative command prompt and call:
netsh http add sslcert ipport=0.0.0.0:8000 appid=<A randomly generated GUID for your application> certhash=<Your localhost certificate's thumbprint from the default MY store, which is under Local Machine -> Personal, which you can get from the MMC Certificates snap-in>
The next step is to make sure its under Trusted People on the client side. At least, for me this is the case since I am using a self-signed certificate that I generated for testing purposes for localhost. So for example, if you get a certificate from Comodo or Verisign or some other CA, your certificate may not need this at all since the root CA will be trusted, usually, by default in Windows, since the root CA public certificate for these is shipped out of the box inside of the Trusted Root Certification Authorities section of the Certificates MMC snap-in.
Then, all you need to do, is make sure that your machine credentials are correct. I am using Windows authentication so it tries to assert that my credentials are valid (these are specified in my code on the call to the Connect method).
As I get older, I find I tend to answer my own questions more and more often...
Edit: You only need to use the Trusted People store for all of this. If you do want to do this, then use StoreName.TrustedPeople in my code above and in your netsh command, specify certstorename=TrustedPeople, otherwise it defaults to MY, which is the Personal store in the Certificates MMC snap-in.
Also, to delete an SSL certificate that has been bound, use netsh http delete sslcert ipport=0.0.0.0:8000
Also, my code doesn't need the client certificate to be set in order to function, so that can be removed from the Connect method. Also needs some more tightening up if any of you plan to use it in production.
Related
I have a solution like this :
-MySolution
|--MyWCFWrapper
|--MyaspnetcoreWebApp
|--ConsoleTestApp
MyWCFWrapper is a .NET Standard library consumes the WCF service added as a WCF reference using the Visual Studio import wizard.
The MyaspnetcoreWebApp application provides controllers for a front end to consume. In this controller, I am instantiating and making calls to MyWCFWrapper library.
The ConsoleTestApp is a console application that also makes calls to MyWCFWrapper library for testing.
I get an error:
System.ServiceModel.Security.SecurityNegotiationException: 'Could not establish trust relationship for the SSL/TLS secure channel with authority 'exampleabc.com'
when I make WCF service calls.
The reason for this error is my WCF service at exampleablc.com is a test server and has a self signed certificate (name different to the webservice) and is also expired.
Workaround that works in ConsoleTestApp :
System.Net.ServicePointManager.ServerCertificateValidationCallback +=
(se, cert, chain, sslerror) =>
{
return true;
};
MyWCFWrapperClass api = new MyWCFWrapperClass(..);
api.SendNewInfo("NewInfo");
This is not recommended, but this is ok for me for now because it's a test server.
The same workaround does not work in the MyaspnetcoreWebApp controller. What I have tried to make it work :.
In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("SeitaCertificate").ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = (request, certificate, certificateChain, policy) =>
{
return true;
}
};
});
services.AddControllersWithViews();
----
}
Added the certificate to Trusted Root Certificate Authorities on my local PC. This fails probably because the certificate has expired.
The certificate error is raised in the WCF call library and I have not been able to find a way to ignore the cert error.
What I can do is at least have the certificate updated so that it is not expired. (I have started this process and it is likely to take some time)
I would like to learn a way how to capture and ignore these certificate errors selectively and appropriately for calling a WCF library in a asp .net core 3.1 web application. How can I do that?
You can refer to the following code to bypass certificate verification:
BasicHttpsBinding binding = new BasicHttpsBinding();
binding.Security.Mode = BasicHttpsSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
ChannelFactory<IService> factory = new ChannelFactory<IService>(binding, new EndpointAddress(uri));
factory.Credentials.ServiceCertificate.SslCertificateAuthentication = new System.ServiceModel.Security.X509ServiceCertificateAuthentication()
{
CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None,
RevocationMode = System.Security.Cryptography.X509Certificates.X509RevocationMode.NoCheck
};
You can resolve this issue by overriding the certificate validation method X509CertificateValidator
class Program
{
static void Main(string[] args)
{
var client = new YourServiceReference.Cient();
// Bypass certificate verification
client.ClientCredentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication
{
CertificateValidationMode = X509CertificateValidationMode.Custom,
CustomCertificateValidator = new CustomCertificateValidator()
};
// --------
}
internal class CustomCertificateValidator : X509CertificateValidator
{
// Override certificate validation
public override void Validate(X509Certificate2 certificate) { }
}
}
In Titanium-Web-Proxy is possible to exclude Https addresses you don't want to proxy. The examples use the OnBeforeTunnelConnectRequest for this, but at this moment only the request is known.
private async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
string hostname = e.HttpClient.Request.RequestUri.Host;
await WriteToConsole("Tunnel to: " + hostname);
if (hostname.Contains("dropbox.com"))
{
// Exclude Https addresses you don't want to proxy
// Useful for clients that use certificate pinning
// for example dropbox.com
e.DecryptSsl = false;
}
}
But I need to get information from the server certificate to exclude the address. I can get the server certificate only in ServerCertificateValidationCallback, but at this moment I can not exclude the address. How can this be done?
I have a client that calls an API that signs its response messages. The signature validation setup requires special binding that looks like this:
public class SignatureBinding : Binding
{
public override BindingElementCollection CreateBindingElements()
{
var signingElement = new AsymmetricSecurityBindingElement
{
AllowInsecureTransport = false,
RecipientTokenParameters = new X509SecurityTokenParameters(X509KeyIdentifierClauseType.IssuerSerial, SecurityTokenInclusionMode.Never),
InitiatorTokenParameters = new X509SecurityTokenParameters(X509KeyIdentifierClauseType.IssuerSerial, SecurityTokenInclusionMode.AlwaysToRecipient),
DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic256,
SecurityHeaderLayout = SecurityHeaderLayout.Strict,
MessageProtectionOrder = MessageProtectionOrder.SignBeforeEncrypt,
MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10,
AllowSerializedSigningTokenOnReply = true
};
signingElement.SetKeyDerivation(false);
return new BindingElementCollection
{
signingElement,
new HttpsTransportBindingElement()
};
}
}
And in the ClientCredentials behavior:
public class CredentialsBehavior : ClientCredentials
{
public CredentialsBehavior()
{
base.ServiceCertificate.DefaultCertificate = store.FindBySerialNumber(signatureCertSN);
}
//Code omitted
}
I have confirmed that the above code works fine when run from an ordinary computer. The message is sent, the server crafts a response and signs it, it comes back, the client validates the signature, and all is well.
However, there is a failure when running from the intended server, which cannot access CRL services due to firewalls. The ServiceModel call returns an error when I send the message over the channel. The error pertains to the certificate that contains the public key for validating the signature. The error is:
The X.509 certificate CN=somecert.somedomain.com, OU=CE_Operations, O="MyCompany, Inc.", L=City, S=State, C=US chain building failed. The certificate that was used has a trust chain that cannot be verified. Replace the certificate or change the certificateValidationMode. The revocation function was unable to check revocation because the revocation server was offline.
The server exists in a domain that can't access CRLs so I disabled the check with help from this answer:
ServicePointManager.ServerCertificateValidationCallback += ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;
ServicePointManager.CheckCertificateRevocationList = false;
However, the error persists. I'm guessing that ServerCertificateValidationCallback only fires for server certificates, and this certificate is different.
How do I convince the service model to allow the use of this certificate without checking the CRL or performing other validation procedures?
Set certificateValidationMode to None to ignore certificate validation X509CertificateValidationMode
This is a behavior, so if you want to do it programmatically you should bind it as new behavior to your service :
ServiceHost host = new ServiceHost(typeof(Service));
ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(IService), new WebHttpBinding(), "http://...");
var endpointClientbehavior = new ClientCredentials();
endpointClientbehavior.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
endpoint.Behaviors.Add(endpointClientbehavior);
I have a C# app which is calling a WCF service via a proxy server.
Both the proxy and the WCF service require (different) credentials.
I am using the svcutil-generated proxy classes to access the WCF service.
When I switch off the proxy server's requirement for credentials then I can access the WCF service fine so all that I want is to know where to apply the credentials for the proxy server (I'm using Fiddler in my dev environment).
I've read various posts which say to set the credentials in client.ClientCredentials and this seems to work for either the proxy server credentials OR the WCF service credentials but I can't store both.
If I put the proxy server credentials in client.ClientCredentials then the request hits the WCF service but then gets rejected.
If I put the WCF service credentials in client.ClientCredentials then the request does not get past the proxy server.
Is there a way to supply two different sets of credentials so I can get authenticated by the proxy server and the WCF service? I need to do this in code, not in the config file.
Code is below:
public class Runner
{
public int GetAvailableFacilities(int timeoutInSeconds, string uri, string userName, string password)
{
FacilitySearchServiceClient client = null;
try
{
client = SetClient(timeoutInSeconds, uri, userName, password, client);
string outputMessage = null;
int[] availableFacilities = client.GetAvailableFacilities(out outputMessage);
return availableFacilities.Length;
}
finally
{
if (client != null)
{
client.Close();
}
}
}
private FacilitySearchServiceClient SetClient(int timeoutInSeconds, string uri, string wcfServiceUserName, string wcfServicePassword, FacilitySearchServiceClient client)
{
string proxyServerUsername = "1";//Fiddler
string proxyServerPassword = "1";//Fiddler
client = new FacilitySearchServiceClient(ConfigureServiceBinding(timeoutInSeconds), new EndpointAddress(uri));
//If this is the only uncommented call to SetServiceCredentials the proxy server transmits the request
//to the wcf service which then rejects the authentication attempt.
SetServiceCredentials(client.ClientCredentials, proxyServerUsername, proxyServerPassword);
//If this is the only uncommented call to SetServiceCredentials the proxy server rejects the authentication attempt,
//resulting in an EndpointNotFoundException.
SetServiceCredentials(client.ClientCredentials, wcfServiceUserName, wcfServicePassword);
return client;
}
protected static void SetServiceCredentials(ClientCredentials credentials, string userName, string password)
{
credentials.UserName.UserName = userName;
credentials.UserName.Password = password;
}
protected CustomBinding ConfigureServiceBinding(int timeoutInSeconds)
{
CustomBinding binding = new CustomBinding();
SecurityBindingElement sbe = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
sbe.IncludeTimestamp = false;
sbe.EnableUnsecuredResponse = true;
sbe.AllowInsecureTransport = true;
binding.Elements.Add(sbe);
binding.Elements.Add(new TextMessageEncodingBindingElement(MessageVersion.Soap11, System.Text.Encoding.UTF8));
binding.Elements.Add(CreateHttpsBindingElement(Const.ProxyServerUrl));
return binding;
}
/// <summary>
/// Sets up the element needed for the web service call.
/// </summary>
private static HttpsTransportBindingElement CreateHttpsBindingElement(string proxyUri)
{
HttpsTransportBindingElement tpt = new HttpsTransportBindingElement();
tpt.TransferMode = TransferMode.Streamed;
tpt.MaxReceivedMessageSize = Int32.MaxValue;
tpt.MaxBufferSize = Int32.MaxValue;
tpt.AuthenticationScheme = System.Net.AuthenticationSchemes.Anonymous;
if (string.IsNullOrEmpty(proxyUri) == false)
{
tpt.ProxyAddress = new Uri(proxyUri);
tpt.UseDefaultWebProxy = false;
tpt.ProxyAuthenticationScheme = System.Net.AuthenticationSchemes.Basic;
}
return tpt;
}
}
I ran in to this exact same problem a few months back. The problem is the WCF client proxy does not expose a separate property for specifying proxy server credentials like say, the HttpWebRequest object which exposes a proxy property of type System.Net.WebProxy. In that case you could just use the Credentials property of the System.Net.WebProxy type to achieve what you are trying to do. In your case you can do this for your WCF client by setting the proxy credentials for all the web requests made by your .NET application by modifying the Application_Start method in Global.asax.cs (assuming that your client is also a web app). Otherwise you could just use this code in the appropriate startup method of your client application.
protected void Application_Start(Object sender, EventArgs e)
{
var proxy = new WebProxy("myproxyservername", <myproxyPort>) { Credentials = new System.Net.NetworkCredential("yourproxyusername", "yourproxypassword") };
WebRequest.DefaultWebProxy = proxy;
}
I need to develop a WCF Hosted in a console app WebService.
I made it work using the Mutual Certificate (service and client) method using SecurityMode.Message.
But now i need to change the Security Mode to SecurityMode.Transport and use the wsHttpBinding with SSL. I made this code to host the service but i cannot get the wsdl with the browser, or execute some webmethod in the console app client.
static void Main()
{
var httpsUri = new Uri("https://localhost:8089/HelloServer");
var binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
var host = new ServiceHost(typeof(WcfFederationServer.HelloWorld), httpsUri);
host.AddServiceEndpoint(typeof(WcfFederationServer.IHelloWorld), binding, "", httpsUri);
var mex = new ServiceMetadataBehavior();
mex.HttpsGetEnabled = true;
host.Description.Behaviors.Add(mex);
// Open the service.
host.Open();
Console.WriteLine("Listening on {0}...", httpsUri);
Console.ReadLine();
// Close the service.
host.Close();
}
The service is up, but i cannot get nothing on the https://localhost:8089/HelloServer.
On fiddler the get request via browser shows me this message:
fiddler.network.https> HTTPS handshake to localhost failed. System.IO.IOException
What im missing here?
Thanks
EDIT:
The Console Application Client Code
static void Main()
{
try
{
var client = new HelloWorldHttps.HelloWorldClient();
client.ClientCredentials.ClientCertificate.SetCertificate(
StoreLocation.LocalMachine,
StoreName.TrustedPeople,
X509FindType.FindBySubjectName,
"www.client.com");
Console.WriteLine(client.GetData());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
Getting this error:
Could not establish trust relationship for the SSL/TLS secure channel
When it comes to the service, you need to map the certificate to the specific port as described here
http://msdn.microsoft.com/en-us/library/ms733791(v=vs.110).aspx
As for the client, you need to skip the verification of certificate properties like valid date, the domain by relaxing the certificate acceptance policy. An easiest way would be to accept any certiticate
ServicePointManager.ServerCertificateValidationCallback = (a,b,c,d) => true
You can finetune the acceptance callback according to the docs to best fit your needs.