I'm experiencing a problem with WCF client connections using a self-created certificate.
Certificate created as follows:
Makecert -r -pe -n "CN=MySslSocketCertificate" -b 01/01/2015 -e 01/01/2025 -sk exchange -ss my
Server code:
Public Sub StartWcfServer()
Dim binding As New NetTcpBinding()
binding.Security.Mode = SecurityMode.Transport
binding.Security.Transport.ProtectionLevel = Net.Security.ProtectionLevel.EncryptAndSign
binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Certificate
binding.TransferMode = TransferMode.Streamed
Dim baseAddress As New Uri($"net.tcp://192.168.1.1:1234/WcfServer")
_serviceHost = New ServiceHost(GetType(WcfServer), baseAddress)
_serviceHost.Credentials.ServiceCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindByIssuerName, "MySslSocketCertificate")
_serviceHost.Credentials.ClientCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck
_serviceHost.Credentials.ClientCertificate.Authentication.CertificateValidationMode = ServiceModel.Security.X509CertificateValidationMode.None
_serviceHost.Credentials.ClientCertificate.Authentication.TrustedStoreLocation = StoreLocation.CurrentUser
ServicePointManager.ServerCertificateValidationCallback = New RemoteCertificateValidationCallback(AddressOf ValidateServerCertificate)
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls12
_serviceHost.AddServiceEndpoint(GetType(IWcfServer), binding, baseAddress)
_serviceHost.Open()
End Sub
Private Function ValidateServerCertificate(sender As Object, certificate As X509Certificate, chain As X509Chain, sslPolicyErrors As SslPolicyErrors) As Boolean
Return True
End Function
Client code:
private void InitialiseWcfClient()
{
var binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.Transport;
binding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;
binding.TransferMode = TransferMode.Streamed;
var url = $"net.tcp://192.168.1.1:1234/WcfServer";
var address = new EndpointAddress(url);
var channelFactory = new ChannelFactory<IWcfServer>(binding, address);
WcfServer = channelFactory.CreateChannel();
}
// call to server which causes the error
WcfServer.CallMethod();
Client-side error:
System.IdentityModel.Tokens.SecurityTokenValidationException: 'The X.509 certificate CN=MySslSocketCertificate chain building failed. The certificate that was used has a trust chain that cannot be verified. Replace the certificate or change the certificateValidationMode. A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider.
Server-side error:
System.Security.Authentication.AuthenticationException: 'The remote certificate is invalid according to the validation procedure.'
Bro,regardless of whether we specify the Authencation mode on the server-side, we should establish the trust relationship between the server and the client when authenticating the client with a certificate.
Namely, we should install the server certificate on the client-side and install the client certificate on the server side. Based on the authentication mode value, the certificate installation place is difference, commonly we should install it in the Local CA. Besides, considering some access permission issues, we had better install the certificate in Local machine store location other than Current User.
Also, when we explicitly specify the security mode to Transport, we should provide a certificate on the server side.
sh.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "cbc81f77ed01a9784a12483030ccd497f01be71c");
At the same time, the client is supposed to provide a certificate to represent identity.
factory.Credentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "9ee8be61d875bd6e1108c98b590386d0a489a9ca");
I have made a demo, ,wish it is helpful to you.
Server.
class Program
{
static void Main(string[] args)
{
using (ServiceHost sh = new ServiceHost(typeof(MyService)))
{
sh.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "cbc81f77ed01a9784a12483030ccd497f01be71c");
sh.Open();
Console.WriteLine("serivce is ready....");
Console.ReadLine();
sh.Close();
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
string Test();
}
public class MyService : IService
{
public string Test()
{
return DateTime.Now.ToString();
}
}
App.config(Server side)
<system.serviceModel>
<services>
<service name="VM1.MyService">
<endpoint address="" binding="netTcpBinding" contract="VM1.IService" bindingConfiguration="mybinding">
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" ></endpoint>
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:5566"/>
</baseAddresses>
</host>
</service>
</services>
<bindings>
<netTcpBinding>
<binding name="mybinding">
<security mode="Transport">
<transport clientCredentialType="Certificate"></transport>
</security>
</binding>
</netTcpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Client.
class Program
{
static void Main(string[] args)
{
Uri uri = new Uri("net.tcp://vabqia969vm:5566");
NetTcpBinding binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Certificate;
ChannelFactory<IService> factory = new ChannelFactory<IService>(binding, new EndpointAddress(uri));
factory.Credentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "9ee8be61d875bd6e1108c98b590386d0a489a9ca");
IService service = factory.CreateChannel();
try
{
var result = service.Test();
Console.WriteLine(result);
}
catch (Exception)
{
throw;
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
string Test();
}
Result.
One more thing must be noted that we should ensure the client certificate have the client authentication Intended purposes.
Feel free to let me know if there is anything I can help with.
Related
I am completely new to this Microsoft Dynamics AX 2012 tool and WCF service. I have a self hosted WCF service, where it takes AX 2012 service wsdl URL, AX server domain name, user name and password as inputs and will try to download metadata of this wsdl url without any user authentication mechanism in place.
MY AX 2012 service WSDl URL below:
http://####:8##1/DynamicsAx/Services/TestService?wsdl ---> WSDLEndpoint
I am dynamically creating WSHttpBinding, MetadataExchangeClient and assigned all it's properties and passed my wsdl endpoint.
Below is my sample code :
var binding = new WSHttpBinding(SecurityMode.None) { MaxReceivedMessageSize = int.MaxValue, MaxBufferPoolSize = int.MaxValue };
var mexClient = new MetadataExchangeClient(binding)
{
ResolveMetadataReferences = true,
MaximumResolvedReferences = int.MaxValue,
OperationTimeout = TimeSpan.FromSeconds(TimeOutInSeconds),
HttpCredentials =
new NetworkCredential(Username, Password, Domain)
};
mexClient.GetMetadata(new Uri(WSDLEndpoint), MetadataExchangeClientMode.HttpGet);
Log.Info("Metadata successfully downloaded.");
But above code won't bother about user credentials validation, it directly downloads metadata out of the WSDL URL, but I am looking to validate user credentials and after successful authentication, will download metadata.
Please help me with some authentication approach to introduce on top of wshttpbinding that supports cross platforms.
I don’t fully understand your meaning. Are you trying to create a WCF service with custom username/password authentication? This requires that we configure a certificate on the server-side. I created an example, wishing it is instrumental for you.
Server-side.
class Program
{
static void Main(string[] args)
{
Uri uri = new Uri("http://localhost:21011");
WSHttpBinding binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
using (ServiceHost sh = new ServiceHost(typeof(MyService), uri))
{
sh.AddServiceEndpoint(typeof(IService), binding, "");
ServiceMetadataBehavior smb;
smb = sh.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (smb == null)
{
smb = new ServiceMetadataBehavior()
{
HttpGetEnabled = true
};
sh.Description.Behaviors.Add(smb);
}
sh.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "5ba5022f527e32ac02548fc5afc558de1d314cb6");
Binding mexbinding = MetadataExchangeBindings.CreateMexHttpBinding();
sh.AddServiceEndpoint(typeof(IMetadataExchange), mexbinding, "mex");
sh.Opened += delegate
{
Console.WriteLine("Service is ready");
};
sh.Closed += delegate
{
Console.WriteLine("Service is clsoed");
};
sh.Open();
Console.ReadLine();
//pause
sh.Close();
Console.ReadLine();
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
string Test();
}
public class MyService : IService
{
public string Test()
{
return DateTime.Now.ToString();
}
}
On the Client-side, we create a client proxy by adding service reference.
ServiceReference1.ServiceClient client = new ServiceClient();
client.ClientCredentials.UserName.UserName = "administrator";
client.ClientCredentials.UserName.Password = "abcd1234!";
var result = client.Test();
Console.WriteLine(result);
The configuration automatically generated on the client-side.
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IService">
<security>
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://10.157.13.69:21011/" binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_IService" contract="ServiceReference1.IService"
name="WSHttpBinding_IService">
<identity>
<certificate encodedValue="blabla… " />
</identity>
</endpoint>
</client>
</system.serviceModel>
In the above example, the client should provide username/password to be authenticated by the server so that call the remote service.
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/message-security-with-a-user-name-client
Feel free to let me know if there is anything I can help with.
I have read some posts here, claiming that WCF can allow http and https on the same endpoint/port, but that sounds a bit wrong to me. I tried to set it up but I cannot figure out how to enable both https and http listening on the same port/ip, programmtically.
I get the following error when I try to add https:
'System.ServiceModel.AddressAlreadyInUseException' in
System.ServiceModel.dll HTTP could not register URL
https://+:10901/Service/. Another application has already
registered this URL with HTTP.SYS.
It works fine if I add just one of them.
Code:
Uri httpsUri = new Uri("https://" + localIp.ToString() + ":" + settings._WebservicePort.ToString() + "/Service/");
Uri httpUri = new Uri("http://" + localIp.ToString() + ":" + settings._WebservicePort.ToString() + "/Service/");
host = new WebServiceHost(typeof(Service), httpUri, httpsUri);
WebHttpBinding whbHttp = new WebHttpBinding
{
CrossDomainScriptAccessEnabled = true,
Security = { Mode = WebHttpSecurityMode.None },
MaxReceivedMessageSize = 10000000
};
WebHttpBinding whbHttps = new WebHttpBinding
{
CrossDomainScriptAccessEnabled = true,
Security = { Mode = WebHttpSecurityMode.Transport },
MaxReceivedMessageSize = 10000000
};
ServiceEndpoint seHttp = host.AddServiceEndpoint(typeof(IService), whbHttp, httpUri);
seHttp.Behaviors.Add(new WebHttpBehavior());
ServiceEndpoint seHttps = host.AddServiceEndpoint(typeof(IService), whbHttps, httpsUri);
seHttps.Behaviors.Add(new WebHttpBehavior());
ServiceDebugBehavior stp = host.Description.Behaviors.Find<ServiceDebugBehavior>();
stp.HttpHelpPageEnabled = true;
host.Open(); // <-- Exception: 'System.ServiceModel.AddressAlreadyInUseException' in System.ServiceModel.dll HTTP could not register URL https://+:10901/Service/. Another application has already registered this URL with HTTP.SYS.
I realize that web http is port 80 and https is normally 443, but why am I then reading that http and https can be hosted on the same port? Am i misreading, and its actually not possible? =)
I'm not sure this is what you want. but i just go ahead and post my answer.
hosting http and https both on single port is impossible. but you can have both http and https binding like this:
<bindings>
<webHttpBinding>
<binding name="RestBinding"/>
<binding name="SecureRestBinding" >
<security mode="Transport"/>
</binding>
</webHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="ServiceBehavior" name="YOURPROJECT.YOURSERVICE">
<endpoint address="" behaviorConfiguration="Web" binding="webHttpBinding" bindingConfiguration="RestBinding" name="Rest" contract="YOURPROJECT.IYOURSERVICE"/>
<endpoint address="" behaviorConfiguration="Web" binding="webHttpBinding" bindingConfiguration="SecureRestBinding" name="SecureRest" contract="YOURPROJECT.IYOURSERVICE"/>
</service>
</services>
I have added a service reference to the third party webservice and on making a WCF call from my console application, I get the error message below:
System.ServiceModel.ProtocolException: 'The content type application/xml; charset=utf-8 of the response message does not match the content type of the binding (text/xml; charset=utf-8). If using a custom encoder, be sure that the IsContentTypeSupported method is implemented properly. The first 610 bytes of the response were: '<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="urn:CancelServiceResponse">
<soapenv:Body>
<tns:CancelServiceResponse>
<CancelServiceResult>
<Status_Code>FAILED</Status_Code>
<Status_Description>Service_ID= not found.</Status_Description>
<Order_ID></Order_ID>
</CancelServiceResult>
</tns:CancelServiceResponse>
</soapenv:Body>
</soapenv:Envelope>
Config file as below:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IB2BService">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://thirdpartyendpointaddress"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IB2BService"
contract="b2bService.IB2BService" name="BasicHttpBinding_IB2BService" />
</client>
</system.serviceModel>
Can someone advise, what needs to be done to fix this issue? I have searched all over SO and have not been able to find how to overcome this.
There is no problem the code snippets seem to me. One thing must be noted is to make sure the binding is consistent between the server and the client and has the correct service endpoint. On the client end, we could generate the configuration with Adding service reference tool. What I consider the best reply is to give you an example of invocation service with TransportWithMessageCredential.
Server end (10.157.13.69. Console application)
class Program
{
static void Main(string[] args)
{
Uri uri = new Uri("https://localhost:11011");
BasicHttpBinding binding = new BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.TransportWithMessageCredential;
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
using (ServiceHost sh = new ServiceHost(typeof(MyService), uri))
{
sh.AddServiceEndpoint(typeof(IService), binding, "");
ServiceMetadataBehavior smb;
smb = sh.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (smb == null)
{
smb = new ServiceMetadataBehavior()
{
HttpsGetEnabled = true
};
sh.Description.Behaviors.Add(smb);
}
sh.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
sh.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustUserNamePasswordVal();
Binding mexbinding = MetadataExchangeBindings.CreateMexHttpsBinding();
sh.AddServiceEndpoint(typeof(IMetadataExchange), mexbinding, "mex");
sh.Opened += delegate
{
Console.WriteLine("Service is ready");
};
sh.Closed += delegate
{
Console.WriteLine("Service is clsoed");
};
sh.Open();
Console.ReadLine();
sh.Close();
Console.ReadLine();
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
string SayHello();
}
public class MyService : IService
{
public string SayHello()
{
return $"Hello Stranger,{DateTime.Now.ToLongTimeString()}";
}
}
internal class CustUserNamePasswordVal : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (userName != "jack" || password != "123456")
{
throw new Exception("Username/Password is not correct");
}
}
}
Bind the certificate to the port.
netsh http add sslcert ipport=0.0.0.0:11011 certhash=6e48c590717cb2c61da97346d5901b260e983850 appid={ED4CE60F-6B2E-4EE6-828F-C1A6A1B12565}
Client end (calling service by adding service reference)
var client = new ServiceReference1.ServiceClient();
client.ClientCredentials.UserName.UserName = "jack";
client.ClientCredentials.UserName.Password = "123456";
try
{
var result = client.SayHello();
Console.WriteLine(result);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Configuration file(auto-generated)
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IService">
<security mode="TransportWithMessageCredential" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://10.157.13.69:11011/" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IService" contract="ServiceReference1.IService"
name="BasicHttpBinding_IService" />
</client>
</system.serviceModel>
Feel free to let me know if there is anything I can help with.
I'm busy writing a file server/client tool that basically uses a hosted Service to send and receive data to and from the server. Since this solution will be used by many different people, its not really advisable to have them go and edit the App.Config file for their setup. What I would like to do is change this at runtime so that the user(s) have full control over the settings to use. So, this is my App.Config file:
<system.serviceModel>
<services>
<service name="FI.ProBooks.FileSystem.FileRepositoryService">
<endpoint name="" binding="netTcpBinding"
address="net.tcp://localhost:5000"
contract="FI.ProBooks.FileSystem.IFileRepositoryService"
bindingConfiguration="customTcpBinding" />
</service>
</services>
<bindings>
<netTcpBinding>
<binding name="customTcpBinding" transferMode="Streamed" maxReceivedMessageSize="20480000" />
</netTcpBinding>
</bindings>
</system.serviceModel>
What I would like to do is to change only the address (in this example, net.tcp://localhost:5000) when the application is executed. So I must be able to read the current value and display that to the user, and then take their input and save it back into that field.
The test below may help you. Essentially the steps are
Instantiate an instance of the host that reads the configuration from the .config file;
Create a new instance of EndpointAddress using the same configuration as the old one, but changing the uri and assign it to the Address property of your ServiceEndpoint.
[TestMethod]
public void ChangeEndpointAddressAtRuntime()
{
var host = new ServiceHost(typeof(FileRepositoryService));
var serviceEndpoint = host.Description.Endpoints.First(e => e.Contract.ContractType == typeof (IFileRepositoryService));
var oldAddress = serviceEndpoint.Address;
Console.WriteLine("Curent Address: {0}", oldAddress.Uri);
var newAddress = "net.tcp://localhost:5001";
Console.WriteLine("New Address: {0}", newAddress);
serviceEndpoint.Address = new EndpointAddress(new Uri(newAddress), oldAddress.Identity, oldAddress.Headers);
Task.Factory.StartNew(() => host.Open());
var channelFactory = new ChannelFactory<IFileRepositoryService>(new NetTcpBinding("customTcpBinding"), new EndpointAddress(newAddress));
var channel = channelFactory.CreateChannel();
channel.Method();
(channel as ICommunicationObject).Close();
channelFactory = new ChannelFactory<IFileRepositoryService>(new NetTcpBinding("customTcpBinding"), oldAddress);
channel = channelFactory.CreateChannel();
bool failedWithOldAddress = false;
try
{
channel.Method();
}
catch (Exception e)
{
failedWithOldAddress = true;
}
(channel as ICommunicationObject).Close();
Assert.IsTrue(failedWithOldAddress);
}
you can create the service instance providing a configuration name and endpoint. So you can use;
EndpointAddress endpoint = new EndpointAddress(serviceUri);
var client= new MyServiceClient(endpointConfigurationName,endpoint )
look at msdn article.
I have a basic WCF library project which I am trying to host in console application. The Program.cs file's Main method is as given below:
static void Main(string[] args)
{
// Create a binding and set the security mode to Message.
BasicHttpBinding b = new BasicHttpBinding();//WSHttpBinding(SecurityMode.Message);
Type contractType = typeof(SecureWCFLib.IService1);
Type implementedContract = typeof(SecureWCFLib.Service1);
Uri baseAddress = new Uri("http://localhost:8733/Design_Time_Addresses/SecureWCFLib/Service1/");
ServiceHost sh = new ServiceHost(implementedContract, baseAddress);
sh.AddServiceEndpoint(contractType, b, "Service1");
ServiceMetadataBehavior sm = new ServiceMetadataBehavior();
sm.HttpGetEnabled = true;
sh.Description.Behaviors.Add(sm);
sh.Open();
Console.WriteLine("Listening");
Console.ReadLine();
sh.Close();
}
I have another console application which acts as a client. I am trying to consume the service in Program.cs as given below:
static void Main(string[] args)
{
IService1 productChannel = null;
EndpointAddress productAddress = new EndpointAddress("http://localhost:8733/Design_Time_Addresses/SecureWCFLib/Service1/");
productChannel = ChannelFactory<IService1>.CreateChannel(new BasicHttpBinding(), productAddress);
string result = productChannel.GetData(123);
Console.WriteLine(result);
Console.Read();
}
But I get exception as
{"The remote server returned an error: (404) Not Found."}
Please let me know what I am doing wrong over here.
EndpointAddress productAddress = new EndpointAddress("http://localhost:8733/Design_Time_Addresses/SecureWCFLib/Service1/Service1");
add Services1 to the end of constructor parameter.
client endpointaddress = serviceshost_baseAddress + relative address.
Your code is equale to configuration:
<service name="SecureWCFLib.Service1">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8733/Design_Time_Addresses/SecureWCFLib/Service1/" />
</baseAddresses>
</host>
<endpoint address="Service1" binding="basicHttpBinding" contract="SecureWCFLib.IService1">
</endpoint>
<behaviors>
<!--......-->
<behaviors>
</service>
If you want have access to your web service on IIS express by other clients , you must grant remote access to IIS express's port.
Please see below link .
http://johan.driessen.se/posts/Accessing-an-IIS-Express-site-from-a-remote-computer
http://www.iis.net/learn/extensions/using-iis-express/handling-url-binding-failures-in-iis-express