How to work with WCF wsHttpBinding and SSL? - c#

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.

Related

FiddlerCore with Selenium proxy settings

I use FiddlerCore to capture HTTP traffic when performing web UI tests with Selenium (C#) ChromeDriver. I have finally managed to get it working by trial and error, but need a hint on why my solution works and the logical way (for me) doesn't.
I configure the fiddler proxy as:
CONFIG.IgnoreServerCertErrors = false;
FiddlerApplication.Prefs.SetBoolPref("fiddler.network.streaming.abortifclientaborts", true);
var startupSettings =
new FiddlerCoreStartupSettingsBuilder()
.ListenOnPort(0)
.DecryptSSL()
.OptimizeThreadPool()
.Build();
FiddlerApplication.Startup(startupSettings);
HttpPort = FiddlerApplication.oProxy.ListenPort;
FiddlerApplication.Log.LogFormat("Created HTTP endpoint listening on port {0}", HttpPort);
secureEndpoint = FiddlerApplication.CreateProxyEndpoint(HttpsPort, true, Host);
if (secureEndpoint != null)
{
HttpsPort = secureEndpoint.ListenPort;
FiddlerApplication.Log.LogFormat("Created secure endpoint listening on port {0}, using a HTTPS certificate for '{1}'",HttpsPort, Host);
}
CertMaker.trustRootCert();
This basically follows the demo project provided for FiddlerCore. Note, that the proxy is not registered as system proxy.
Then, ChromeDriver is configured as:
var chromeDriverService = ChromeDriverService.CreateDefaultService();
chromeDriverService.HideCommandPromptWindow = true;
var options = new ChromeOptions();
options.Proxy = new Proxy()
{
HttpProxy = $"{SkicoProxy.Host}:{SkicoProxy.HttpPort}",
SslProxy = $"{SkicoProxy.Host}:{SkicoProxy.HttpPort}"
};
_driver = new ChromeDriver(chromeDriverService, options);
What doesn't work, although I would expect it to is:
options.Proxy = new Proxy()
{
HttpProxy = $"{SkicoProxy.Host}:{SkicoProxy.HttpPort}",
SslProxy = $"{SkicoProxy.Host}:{SkicoProxy.HttpsPort}" // HTTPS PORT INSTEAD HTTP PORT
};
Still, if I do not configure the endpoint in Fiddler, SSL websites are shown as unsafe in Chrome.
Why doesn't SSL traffic needs to be routed through the according endpoint and why does it even work if it is not proxied through the secure endpoint?

Programmatically created web service fails on client service call

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.

Self hosting WCF without config - Hard time understanding setting up endpoints

I've researched this for a day now and am turning to you experts here for your advice.
I have REST service that is the main IIS hosted service.
This service needs to interface with a 32-bit unmanaged .dll. In order to do this I'm taking one common approach which is creating a self hosted service and making calls to it.
With that background, I'm having one heck of a time getting the self hosted one working propertly.
here is the code that WORKS:
static void Main(string[] args)
{
Uri baseAddress = new Uri("http://localhost:8080/theSvc");
Uri mexUri = new Uri("http://localhost:8080/theSvc/mex");
// Create the ServiceHost.
using (ServiceHost host = new ServiceHost(typeof(ImgService), baseAddress))
{
// var wsHttpBinding = new WSHttpBinding();
// wsHttpBinding.MaxReceivedMessageSize = int.MaxValue;
// host.AddServiceEndpoint(typeof(IImgService), wsHttpBinding, baseAddress);
// Enable metadata publishing.
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetUrl = mexUri;
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
host.Description.Behaviors.Add(smb);
// Open the ServiceHost to start listening for messages. Since
// no endpoints are explicitly configured, the runtime will create
// one endpoint per base address for each service contract implemented
// by the service.
host.Open();
Console.WriteLine("The service is ready at {0}", baseAddress);
Console.WriteLine("Press <Enter> to stop the service.");
Console.ReadLine();
// Close the ServiceHost.
host.Close();
}
}
}
Now...if I uncomment these lines in order to increase the maxreceivedMessageSize and remove the baseAddress from the using statement, I can no longer add a reference to the service:
So change this:
using (ServiceHost host = new ServiceHost(typeof(ImgService), baseAddress))
To this:
using (ServiceHost host = new ServiceHost(typeof(ImgService)))
And uncomment this:
// var wsHttpBinding = new WSHttpBinding();
// wsHttpBinding.MaxReceivedMessageSize = int.MaxValue;
// host.AddServiceEndpoint(typeof(IImgService), wsHttpBinding, baseAddress);
The error that I receive is:
... -> local host
There was an error downloading 'http://...:8080/theSvc/_vti_bin/ListData.svc/$metadata'.
The request failed with HTTP status 400: Bad Request.
Metadata contains a reference that cannot be resolved: 'http://...:8080/theSvc'.
Metadata contains a reference that cannot be resolved: 'http://...:8080/theSvc'.
If the service is defined in the current solution, try building the solution and adding the service reference again.
As like everyone else that posts here...I'm in a bind. Any help really appreciated.
It would appear you are not adding the service endpoint.
This is a function I'm using to startup an http host, however I am hosting it in a windows service. Which is the same as your console application. But IIS might think about it a bit differently.
static Uri scannerSvcBaseAddress;
static BasicHttpBinding scannerBinding;
static ServiceHost scannerHost;
public void startScannerWcfService(long eventID)
{
try
{
db.writeServiceLog(eventID, "STARTUP", "=== Scanner WCF Service Starting ===");
string addressToHostAt = ConfigurationManager.AppSettings["ScannerHostBaseAddress"];
if (addressToHostAt != null && addressToHostAt.Trim() != string.Empty)
{
scannerSvcBaseAddress = new Uri(addressToHostAt);
scannerHost = new ServiceHost(typeof(BarCodeService.ScannerService), scannerSvcBaseAddress);
//Allows publishing of METADATA to developers when self hosted on a remote system
ServiceMetadataBehavior smb = scannerHost.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (smb == null)
{
smb = new ServiceMetadataBehavior();
}
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
scannerHost.Description.Behaviors.Add(smb);
scannerHost.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
scannerBinding = new BasicHttpBinding();
scannerBinding.MaxReceivedMessageSize = 2147483647;
scannerBinding.Security.Mode = BasicHttpSecurityMode.None;
scannerBinding.OpenTimeout = new TimeSpan(0, 0, 10);
scannerBinding.CloseTimeout = new TimeSpan(0, 0, 10);
scannerBinding.SendTimeout = new TimeSpan(0, 5, 0);
scannerBinding.ReceiveTimeout = new TimeSpan(0, Global.scanUserIdleLogout * 2, 0);
//scannerBinding.ReliableSession.InactivityTimeout = new TimeSpan(2, 0, 0, 0);
scannerHost.AddServiceEndpoint(typeof(BarCodeService.IScannerService), scannerBinding, "");
var behavior = scannerHost.Description.Behaviors.Find<ServiceDebugBehavior>();
behavior.IncludeExceptionDetailInFaults = true;
scannerHost.Open();
db.writeServiceLog(eventID, "STARTUP", "=== Scanner WCF Service Started ===");
}
else
throw new Exception("Host Base Address not provided");
}
catch (Exception ex)
{
db.writeServiceLog(eventID, "STARTUP", string.Format("Error in ServiceManager.startScannerWcfService: {0} {1}", ex.Message, ex.StackTrace));
throw;
}
}
All that said, as was mentioned above if you only need to expose a string WCF may be overkill. I'm using WCF in a windows services to connect to handheld scanners in a warehouse environment, and I am really happy with how it performs. Though it was a huge pain to get it working initially.
But since the question was asked in the context of WCF I thought I would respond with a known to work function that does essentially what you want to do.

Self-hosted WCF service works with HTTP not with HTTPS

Ok so I am hosting a WCF service within a console application.
all bindings are created programatically, so no config settings.
I have a working service as long as I use HttpTransportBindingElement however as soon as I use HttpsTransportBindingElement then nothing works, the service does not display within the browser and the client application returns a 405 (Method Not Allowed) CommunicationException
I have tried setting a SecurityBindingElement to my CustomBinding but I am not sure which option I should be using.
SecurityBindingElement.CreateCertificateOverTransportBindingElement()
SecurityBindingElement.CreateAnonymousForCertificateBindingElement()
etc.
The code for the creation of the host is below
baseAddress = new Uri(string.Format("{0}://{1}", strConnectionType, ConfigurationManager.AppSettings["baseAddress"]));
ServiceHost host = new ServiceHost(typeof(IMyService), baseAddress);
host.AddServiceEndpoint(typeof(MyService), binding, String.Empty);
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpsGetEnabled = certificate != null;
smb.HttpGetEnabled = certificate == null;
host.Description.Behaviors.Add(smb);
ServiceDebugBehavior sdb = host.Description.Behaviors.Find<ServiceDebugBehavior>();
if (sdb == null)
{
host.Description.Behaviors.Add(new ServiceDebugBehavior() { IncludeExceptionDetailInFaults = true });
}
else
{
if (!sdb.IncludeExceptionDetailInFaults)
{
sdb.IncludeExceptionDetailInFaults = true;
}
}
if (certificate != null)
{
host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, certificate.Thumbprint);
}
I followed this blog http://blogs.msdn.com/b/james_osbornes_blog/archive/2010/12/10/selfhosting-a-wcf-service-over-https.aspx which highlighted that in order for HTTPS to work you need to bind the port to the certificate you are using.
Process bindPortToCertificate = new Process();
bindPortToCertificate.StartInfo.FileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "netsh.exe");
bindPortToCertificate.StartInfo.Arguments = string.Format("http add sslcert ipport=0.0.0.0:{0} certhash={1} appid={{{2}}}", port, certificate.Thumbprint, Guid.NewGuid());
bindPortToCertificate.Start();
bindPortToCertificate.WaitForExit();
once this was done it all worked.
contact me if any requires my example code of setting up and configuring a self-hosted WCF server with bindings programatically set. :)

SignalR C# Client 407 Proxy Authentication Required

I'm trying to build a C# SignalR app (console app on the server, winforms app on the client), and it works great (in Visual Studio) when both the client and server are on the same PC, but when I deploy the server and repoint the client to the server, I'm getting a "407 Proxy Authentication Required" exception when it tries to start.
This is my code...
var _connection = new HubConnection("http://www.redacted.com:8088/");
var _hub = _connection.CreateProxy("MyHub");
_connection.Start().ContinueWith(task =>
{
if (task.IsFaulted)
MessageBox.Show(string.Format("Could not connect - {0}", task.Exception.ToString()));
}).Wait();
I noticed that HubConnection has a Credentials property, and so I figured I'd try replacating some code I've used with WebServices when dealing with proxies (shown below, where I just make an HTTP request out and then pick up the proxy settings and credentials from that), but I get the same exception.
var _connection = new HubConnection("http://www.redacted.com:8088/");
var req = (HttpWebRequest)WebRequest.Create("http://www.google.com");
var proxy = new WebProxy(req.Proxy.GetProxy(req.RequestUri).AbsoluteUri) {UseDefaultCredentials = true};
_connection.Credentials = proxy.Credentials;
var _hub = _connection.CreateProxy("MyHub");
_connection.Start().ContinueWith(task =>
{
if (task.IsFaulted)
MessageBox.Show(string.Format("Could not connect - {0}", task.Exception.ToString()));
}).Wait();
This is required for some work I'm doing for a client where they want their local PCs to be able to receive messages from a remote system that's hosted outside the company.
Thanks!
Try to set the DefaultWebProxy:
WebProxy wproxy = new WebProxy("new proxy",true);
wproxy.Credentials = new NetworkCredential("user", "pass");
WebRequest.DefaultWebProxy = wproxy;

Categories