One WCF service on two addresses - c#

My situation:
I have one WCF service on address http://0.0.0.0:10004/Service.svc. Using Interface_1, Service_1_Class, ServiceHost_1_Class.
Now I need second version of this service with authorisation. Choosing address https://0.0.0.0:10004/Service2.svc, using Interface_1, Service_1_Class, ServiceHost_2_Class.
Got the error
HTTP could not register URL https://+:10004/Service2.svc/. Another application has already registered this URL with HTTP.SYS.
If copy-paste and use Interface_2, Service_2_Class, ServiceHost_2_Class (same classes with different name) everything works.
Not the big problem, but want to know: how to bind same service class on two addresses? So which step may I miss? I.e. binding one to one works, binding one to two - not, where is difference? If there should not be differences (i.e. problem in code), I'll close this question.
PS: sorry, cant post code: too big, too complicated, not clear. Question not about code, but conception

As suggested, you need to setup multiple service endpoints each with a different binding. One binding with no security enabled and the other binding with security enabled.
Here is a working example of how to host the same service at two different endpoints. One endpoint has no security at all and the other endpoint provides HTTP-based client authentication.
var baseUri = new Uri("http://localhost:10004/");
var serviceHost = new ServiceHost(typeof(HelloWorldService), baseUri);
try
{
var unsecureServiceEndpoint = serviceHost.AddServiceEndpoint(typeof(IHelloWorldService), new WebHttpBinding(), "Service.svc");
unsecureServiceEndpoint.Behaviors.Add(new WebHttpBehavior());
unsecureServiceEndpoint.Name = "UnsecureEndpoint";
var secureBinding = new WebHttpBinding(WebHttpSecurityMode.TransportCredentialOnly);
secureBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
var secureServiceEndpoint = serviceHost.AddServiceEndpoint(typeof(IHelloWorldService), secureBinding, "Service2.svc");
secureServiceEndpoint.Behaviors.Add(new WebHttpBehavior());
secureServiceEndpoint.Name = "SecureEndpoint";
serviceHost.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
serviceHost.Open();
Console.WriteLine("Hosting - {0} # {1}", unsecureServiceEndpoint.Name, unsecureServiceEndpoint.Address);
Console.WriteLine("Hosting - {0} # {1}", secureServiceEndpoint.Name, secureServiceEndpoint.Address);
Console.WriteLine("Press <Enter> to stop the services.");
Console.ReadLine();
serviceHost.Close();
}
catch (CommunicationException ce)
{
Console.WriteLine("An exception occurred: {0}", ce.Message);
serviceHost.Abort();
}

Related

Converting a WCF service to console application

I have a WCF service. Now I would like to convert it to console application. Basically, it is a simple service. It only does 2 jobs depending on client request by PowerShell request.
Invoke-WebRequest "http://localhost:10000/backup/DoBackup?id=BackupClient1"
or
Invoke-WebRequest "http://localhost:10000/backup/DoBackup?id=BackupClient2"
My service listens on request for this 2 requests.
I have this WCF code:
myService service = new myService(_settings);
backupServiceHost = new WebServiceHost(service, _backupAddress);
WebHttpBinding binding = new WebHttpBinding();
string address = "";
ServiceEndpoint endpoint = backupServiceHost .AddServiceEndpoint(typeof(ISnapshotServiceContract), binding, address);
backupServiceHost .Open();
I can't use WebServiceHost and WebHttpBinding in my console app. What is the best replacement for this? I'm thinking to use simple HttpClient. But I'm not sure is it a correct selection?
why don't use command line parameters and avoid WebServiceHost altogether? You can then just compare your args[] from Main function (see e.g. https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/main-and-command-args/) to determine if it is BackupClient1 command or BackupClient2 command.
If you want to take this concept further you can extract functionality of BackupClient1 command and BackupClient2 command into separate library and have frontends (WCF and command line) using same backend.
If you really need WCF service (as stated by comments below) please refer to this MSDN article; you will have to provide your configuration and figure out how to indicate that you wish to end the process in nice way but it should boil down to something like this:
String baseAddress = "http://localhost:10000/backup";
BasicHttpBinding binding = new BasicHttpBinding();
using (ServiceHost host = new ServiceHost(typeof(myService)))
{
host.AddServiceEndpoint(typeof(IMyService), binding, baseAddress);
host.Open();
while(true) {
// Figure out how to exit this loop - e.g. listen to some command from service
}
}

Programmatically implement WCF with Certificate

I am quite new to WCF and trying to get my head around the security. I am still reading and learning, but I came to a point where I got a working version of WCF with Certificate authentication. I know that the code has some weaknesses; however, my initial goal was to create communication using certificate authentication. Also, I wanted to create everything programmatically (no Web.config configurations for the services or clients). The reason for this is that the client should be able to link an Assembly (Class Library) and get access to the server. Also, I am loading the certificates from the file system (again, I know this is not secure). I would like to get a little bit feedback.
The following client snippet is creating an object that I can use to connect to the server. The anonymous type T is my service interface e.g. IService.
Here is my client implementation:
var url = "URL TO WS";
var binding = new WSHttpBinding
{
Security =
{
Mode = SecurityMode.Message,
Message = {ClientCredentialType = MessageCredentialType.Certificate}
}
};
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<T>(binding, endpoint);
if (channelFactory.Credentials != null)
{
channelFactory.Credentials.ClientCertificate.Certificate =
new X509Certificate2(#"PATH\TO\Client.pfx"); // Client Certificate PRIVATE & PUBLIC Key
channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None; // I know this is not good, but I dont have a valid certificate from a trusted entity
}
wcfClient = channelFactory.CreateChannel();
return wcfClient;
The service is a bit more complex. I use .svc files with their code-behind. If I understand the use of .svc files correctly, then I believe this is the entry point where the .NET framework creates a ServiceHost and automatically opens it? In my implementation I do not open the ServiceHost, I only implemented a ServiceHostFactoryBase and referenced it in the .svc Markup language. Look at the Factory section - this is the part where I implement my custom Host Factory.
<%# ServiceHost Language="C#" Debug="true"
Service="Service.Services.LevelService" CodeBehind="LevelService.svc.cs"
Factory="Service.Security.ServiceHostFactory.HostFactory" %>
And my custom Host Factory looks like this:
public class HostFactory : ServiceHostFactoryBase
{
public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
var serviceType = Type.GetType(constructorString);
if (serviceType.GetInterfaces().Count() != 1)
throw new NotImplementedException("The service can only have one implemented interface");
var interfaceType = serviceType.GetInterfaces()[0];
var myServiceHost = new ServiceHost(serviceType, baseAddresses);
var httpBinding = new WSHttpBinding();
httpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
httpBinding.Security.Mode = SecurityMode.Message;
myServiceHost.Credentials.ServiceCertificate.Certificate = new X509Certificate2(#"PATH\TO\Server.pfx");
myServiceHost.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
myServiceHost.Credentials.ClientCertificate.Authentication.CustomCertificateValidator = new MyX509CertificateValidator();
myServiceHost.Credentials.ClientCertificate.Certificate = new X509Certificate2(#"PATH\TO\Client.cer");
myServiceHost.AddServiceEndpoint(interfaceType, httpBinding, String.Empty);
return myServiceHost;
}
}
The custom validator doess't do much yet, but here it is as well:
public class MyX509CertificateValidator : X509CertificateValidator
{
public override void Validate(X509Certificate2 certificate)
{
// Check that there is a certificate.
if (certificate == null)
{
throw new ArgumentNullException("certificate");
}
// Check that the certificate issuer matches the configured issuer.
//throw new SecurityTokenValidationException("Certificate was not issued by a trusted issuer");
}
}
If I understand correctly, the Server has ONLY the PUBLIC key of the client registered since I only reference the .cer file.
My big question is now, if I would like to get anything like this on a production server - and lets assume nobody will actually get the executables (including the certificates), would this be a possible solution to keep unwanted people out of my webservice? Basically, I don't want anybody else consuming my webservice - only if you have the proper certificate. Also, how much of an issue is the part where I set on the client:
CertificateValidationMode = X509CertificateValidationMode.None
I know there are many questions - but overall, I would like to know if I made some fundamental mistakes in this implementation.
Ok,
after going through a lot of tutorials and demo applications, I figured out that the best way to go ahead is actually using the Certificate Store on Windows. However, I still might consider a hybrid solution where the Server has the certificates in the Certificate store and the client has it embedded in a resource. If you are struggling with WCF and Certificates, have a look at those links:
IIS7 Permissions Overview - ApplicationPoolIdentity
I was able to create Transport as well as Message secured WCF web services. I would suggest to READ the linked articles because there is so much information that will make you understand certificates and their usage. Especially when dealing with self-singed certificates!
I ended up implementing wsHttpBinding using Message Security Mode + Client Certificate with ChainTrust.
Hope this will help someone else!

Programmatically create a secure web http rest service

I would like to programmatically create a secure webhttp REST service in a self host environment i.e in a Console Application but i cannot find any tutorials which allow me to do this.
However i have created secure webhttp rest service using the WCF service library project without any C# code. I used netsh command to insert the certficiate that i had made and updated my config appropriately.
Can anyone advise please?
Just a quick update, I got it working but only http side as of yet.
Here is my service side code:
WebServiceHost serviceHost = new WebServiceHost(typeof(ProductServiceTest), new Uri("http://localhost:9000/"));
WebHttpBinding webHttpBinding = new WebHttpBinding();
webHttpBinding.MaxReceivedMessageSize = 65536 * 2;
webHttpBinding.MaxBufferPoolSize=2147483647L;
webHttpBinding.MaxBufferSize=2147483647;
webHttpBinding.MaxReceivedMessageSize = 2147483647L;
serviceHost.AddServiceEndpoint(typeof(IProductServiceTest), webHttpBinding, "");
// Check to see if the service host already has a ServiceMetadataBehavior
ServiceMetadataBehavior smb = serviceHost.Description.Behaviors.Find<ServiceMetadataBehavior>();
// If not, add one
if (smb == null)
smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
serviceHost.Description.Behaviors.Add(smb);
serviceHost.Open();
Console.WriteLine("Press <ENTER> to terminate the service host");
Console.ReadLine();
serviceHost.Close();
I created a console app for the client side and got my client side working by doing this:
ChannelFactory<IProductServiceTest> cf = new ChannelFactory<IProductServiceTest>(new WebHttpBinding(), "http://localhost:9000");
cf.Endpoint.Behaviors.Add(new WebHttpBehavior());
IProductServiceTest channel = cf.CreateChannel();
var test = channel.GetProductData("8");
Console.WriteLine(test.ProductDescription);
Console.Read();

Onvif SOAP request with SOAP level authentication and HTTP authentication

This question has been discussed in several topics here but I could not find the answer for me.
What I'm trying to do is use an IP camera through the Onvif interface. I've generated the web services from the WSDL files available in the Onvif homepage, and added the custom SOAP authentication code as suggested here, and I am able to retrieve the device capabilities etc. etc.
But for some services, e.g, PTZ control, also HTTP authentication is needed. My code removes the ClientCredentials behaivor (so yeah, I guess setting them does not make any sense, but I still left those lines in hope that maybe the HTTP transport would try to use them):
HttpTransportBindingElement httpBindingElement = new HttpTransportBindingElement();
httpBindingElement.AuthenticationScheme = AuthenticationSchemes.Basic;
...
PTZClient ptzClient = new PTZClient(customBinding, endPointAddress);
ptzClient.Endpoint.Behaviors.Remove(typeof(System.ServiceModel.Description.ClientCredentials));
UsernameClientCredentials onvifCredentials = new UsernameClientCredentials(new UsernameInfo(_username, _password));
ptzClient.Endpoint.Behaviors.Add(onvifCredentials);
ptzClient.ClientCredentials.UserName.UserName = _username;
ptzClient.ClientCredentials.UserName.Password = _password;
Still when I look at wireshark, i see that the SOAP authentication is generated but no HTTP authentication header is set (well, I already expected that since i have a custom behaivor here). So the question is, if I am creating the binding this way, what are my best options to add HTTP authentication headers? Can I just add a message inspector, and if so, any examples? Must I create a different transport binding? I've seen people advising others to use BasicHttpBinding and then setting the Security property on that, but where do the credentials go in that case and how do I apply the BasicHttpBinding instance to my binding? Are there any callbacks in the WCF that get triggered by the HTTP 401 code that i can hook up to and then provide the header? This is actually my first experience with WCF and so far I've done everything from examples found in the internet, but as for this particular issue I haven't been able to find anything.
If anyone is interested this is how I got it working. I combined the BasicHttpBinding with the client credentials in a following way:
TransportSecurityBindingElement transportSecurity = new TransportSecurityBindingElement();
// UsernameCredentials is a class implementing WS-UsernameToken authentication
transportSecurity.EndpointSupportingTokenParameters.SignedEncrypted.Add(new UsernameTokenParameters());
transportSecurity.AllowInsecureTransport = true;
transportSecurity.IncludeTimestamp = false;
TextMessageEncodingBindingElement messageEncoding = new TextMessageEncodingBindingElement(MessageVersion.Soap12, Encoding.UTF8);
HttpClientCredentialType[] credentialTypes = new HttpClientCredentialType[3] { HttpClientCredentialType.None, HttpClientCredentialType.Basic, HttpClientCredentialType.Digest };
...
foreach (HttpClientCredentialType credentialType in credentialTypes)
{
BasicHttpBinding httpBinding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
httpBinding.Security.Transport.ClientCredentialType = credentialType;
BindingElementCollection elements = new BindingElementCollection(new BindingElement[1]{messageEncoding});
foreach(BindingElement element in httpBinding.CreateBindingElements())
{
if (element is TextMessageEncodingBindingElement)
continue;
elements.Add(element);
}
CustomBinding customBinding = new CustomBinding(elements);
DeviceClient deviceClient = new DeviceClient(customBinding, endPointAddress);
if (credentialType == HttpClientCredentialType.Basic)
{
// Set all credentials, not sure from which one WCF actually takes the value
deviceClient.ClientCredentials.UserName.UserName = pair[0];
deviceClient.ClientCredentials.UserName.Password = pair[1];
}
else if (credentialType == HttpClientCredentialType.Digest)
{
deviceClient.ClientCredentials.HttpDigest.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Delegation;
deviceClient.ClientCredentials.HttpDigest.ClientCredential.UserName = pair[0];
deviceClient.ClientCredentials.HttpDigest.ClientCredential.Password = pair[1];
}
}
This works efficiently with a device for which we do not know the authentication mode and works on both (HTTP/SOAP) authentication level.
I detailed how HTTP digest works in another answer.
Remember that only functions of class PRE_AUTH, according to ยง5.12.1 of the Core spec, require authentication.
You should invoke a function of any class but PRE_AUTH without any form authentication. If you get a HTTP 401 then you have to use HTTP digset, otherwise you'll have to got with WS-UsernameToken.
You can't directly use HTTP digest because you'll need at least the device to send you the challange for HTTP digest.

WCF .net TCP service fails to start when extra properties are set

I have a current project that runs fine with a self-hosted net.tcp binding if I use the following
host.AddServiceEndpoint(typeof(IMonitorService), new NetTcpBinding() {PortSharingEnabled = false }, "");
host.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexTcpBinding(), "mex");
However, if I amend to the below it fails to run with the message that there is already an endpoint on the port, can anyone explain why adding the extra properties causes it to fail?
host.AddServiceEndpoint(typeof(IMonitorService), new NetTcpBinding() {PortSharingEnabled = false,ListenBacklog=1000,ReceiveTimeout=new TimeSpan(0,3,0) }, "");
host.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexTcpBinding(), "mex");
Edit:
Testing confirms that the ReceiveTimeout property works Ok, as soon as I add the MaxConnections or ListenBacklog the service fails to start
Edit 2:
This link seems to imply I need port sharing if I want to modify these properties, not sure I am understanding it.

Categories