Self-Hosted WCF Custom binding, Binary message, HTTPS transport WITHOUT certificate - wcf

I have some self-hosted WCF services using CustomBinding for HTTP protocol on a specific port.
I use BinaryMessageEncodingBindingElement and HttpTransportBindingElement so far without problem.
Now I need to secure a bit more by using HTTPS but with NO cert. I switched to HttpsTransportBindingElement and set RequireClientCertificate to false.
I have no cert installed on that port. I checked by running "netsh http show sslcert".
And I get follow error when I try to add my service to a WPF app (browsing with Chrome I get "This webpage is not available"):
There was an error downloading 'https://localhost:8080/myhost/myservice.svc'.
The underlying connection was closed: An unexpected error occurred on a send.
Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
An existing connection was forcibly closed by the remote host
Metadata contains a reference that cannot be resolved: 'https://localhost:8080/myhost/myservice.svc'.
An error occurred while making the HTTP request to 'https://localhost:8080/myhost/myservice.svc'.
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.
The underlying connection was closed: An unexpected error occurred on a send.
Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
An existing connection was forcibly closed by the remote host
If the service is defined in the current solution, try building the solution and adding the service reference again.
Here goes my binding:
private System.ServiceModel.Channels.Binding GetHttpBinding(String pName)
{
System.ServiceModel.Channels.BindingElementCollection elements = new System.ServiceModel.Channels.BindingElementCollection();
System.ServiceModel.Channels.BinaryMessageEncodingBindingElement binaryMessageEncoding = new System.ServiceModel.Channels.BinaryMessageEncodingBindingElement();
binaryMessageEncoding.MessageVersion = System.ServiceModel.Channels.MessageVersion.Default;
binaryMessageEncoding.ReaderQuotas.MaxArrayLength = this._maxArrayLength;
binaryMessageEncoding.ReaderQuotas.MaxBytesPerRead = this._maxBytesPerRead;
binaryMessageEncoding.ReaderQuotas.MaxDepth = this._maxDepth;
binaryMessageEncoding.ReaderQuotas.MaxNameTableCharCount = this._maxNameTableCharCount;
binaryMessageEncoding.ReaderQuotas.MaxStringContentLength = this._maxStringContentLength;
elements.Add(binaryMessageEncoding);
if (this._applyHttps)
{
System.ServiceModel.Channels.HttpsTransportBindingElement transport = new System.ServiceModel.Channels.HttpsTransportBindingElement()
{
MaxBufferSize = this._maxBufferSize,
MaxReceivedMessageSize = this._maxReceivedMessageSize,
AllowCookies = false,
BypassProxyOnLocal = false,
HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
MaxBufferPoolSize = this._maxBufferPoolSize,
TransferMode = TransferMode.Buffered,
UseDefaultWebProxy = true,
ProxyAddress = null,
RequireClientCertificate = false
};
elements.Add(transport);
}
else
{
System.ServiceModel.Channels.HttpTransportBindingElement transport = new System.ServiceModel.Channels.HttpTransportBindingElement()
{
MaxBufferSize = this._maxBufferSize,
MaxReceivedMessageSize = this._maxReceivedMessageSize,
};
elements.Add(transport);
}
System.ServiceModel.Channels.CustomBinding custB = new System.ServiceModel.Channels.CustomBinding(elements);
custB.Name = pName;
custB.SendTimeout = new TimeSpan(0, 2, 0);
return custB;
}
And I configure the service host with this method:
private void ConfigureBinaryService(ServiceHost pHost, Type pType, String pServiceName)
{
pHost.AddServiceEndpoint(pType, this.GetHttpBinding(pType.Name), String.Empty);
pHost.AddServiceEndpoint(pType, this.GetNetTcpBinding(pType.Name), String.Empty);
pHost.Description.Endpoints[0].Name = pType.Name + "_BasicBin";
pHost.Description.Endpoints[1].Name = pType.Name + "_TCP";
pHost.OpenTimeout = new TimeSpan(0, 2, 0);
pHost.CloseTimeout = new TimeSpan(0, 2, 0);
System.ServiceModel.Description.ServiceMetadataBehavior metadataBehavior = pHost.Description.Behaviors.Find<System.ServiceModel.Description.ServiceMetadataBehavior>();
if (metadataBehavior == null)
{
metadataBehavior = new System.ServiceModel.Description.ServiceMetadataBehavior();
pHost.Description.Behaviors.Add(metadataBehavior);
}
if (this._applyHttps)
metadataBehavior.HttpsGetEnabled = true;
else
metadataBehavior.HttpGetEnabled = true;
metadataBehavior.MetadataExporter.PolicyVersion = System.ServiceModel.Description.PolicyVersion.Policy15;
if (this._applyHttps)
pHost.AddServiceEndpoint(System.ServiceModel.Description.ServiceMetadataBehavior.MexContractName
, System.ServiceModel.Description.MetadataExchangeBindings.CreateMexHttpsBinding(), "mex");
else
pHost.AddServiceEndpoint(System.ServiceModel.Description.ServiceMetadataBehavior.MexContractName
, System.ServiceModel.Description.MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
pHost.AddServiceEndpoint(System.ServiceModel.Description.ServiceMetadataBehavior.MexContractName
, System.ServiceModel.Description.MetadataExchangeBindings.CreateMexTcpBinding(), this._NetTcpComm + #"/" + pServiceName + #"/mex");
pHost.Description.Endpoints[2].Name = pType.Name + "_mex_BasicBin";
pHost.Description.Endpoints[3].Name = pType.Name + "_mex_TCP";
foreach (var item in pHost.Description.Endpoints[0].Contract.Operations)
item.Behaviors.Find<System.ServiceModel.Description.DataContractSerializerOperationBehavior>().MaxItemsInObjectGraph = System.Int32.MaxValue;
foreach (var item in pHost.Description.Endpoints[1].Contract.Operations)
item.Behaviors.Find<System.ServiceModel.Description.DataContractSerializerOperationBehavior>().MaxItemsInObjectGraph = System.Int32.MaxValue;
System.ServiceModel.Description.ServiceDebugBehavior debugBehavior =
pHost.Description.Behaviors.Find<System.ServiceModel.Description.ServiceDebugBehavior>();
if (debugBehavior == null)
{
debugBehavior = new System.ServiceModel.Description.ServiceDebugBehavior();
pHost.Description.Behaviors.Add(debugBehavior);
}
debugBehavior.IncludeExceptionDetailInFaults = true;
}
When this._applyHttps is false, my service is accessible both by browser and reference in WPF project.
So I ask for help for the first time after enjoying for so long all your help without asking directly. What am I missing? As it's not hosted under IIS should I still need a cert to install on the server side only for the specific port?
Thanks guys in advance! And if someone has already answered this case I'm sorry for not finding it...

So like I had guessed I just needed to create a self-signed cert for the server side only and bind it to the port with netsh command.
No cert needed on client side meaning a quasi HTTPS WITHOUT CERT.
Note: I had it work on my computer. I will try to update this post once deployed in real environment and if I had to address any mistake.
And I know at some point we will go full cert on client side.
One stone at a time.

Related

Configuration for ClientCredentials ServiceCertificate authentication not applied or used

I am building a .NET Core 3.1 application where I am trying to call a WCF Service over HTTPS and temporarily disabling SSL authentication for the server certificate.
There is a clearly documented way to achieve this. Namely, by setting the ServiceCertificate.SslCertificateAuthentication property on the ChannelFactory class.
Below is code for setting up het Binding, endpoint and ClientCredentials.
var endpointAddress = new EndpointAddress("https://*.com");
var binding = new BasicHttpsBinding();
binding.Security.Mode = BasicHttpsSecurityMode.Transport;
binding.Security.Transport = new HttpTransportSecurity()
{
ClientCredentialType = HttpClientCredentialType.None
};
var factory = new ChannelFactory<IService>(binding, endpointAddress);
factory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication()
{
CertificateValidationMode = X509CertificateValidationMode.None,
RevocationMode = X509RevocationMode.NoCheck
};
factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
factory.Credentials.ServiceCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
var client = factory.CreateChannel();
client.Call();
However, when I run this code I receive the exception chain:
Could not establish trust relationship for the SSL/TLS secure channel
with authority 'domain'
The SSL connection could not be established,
see inner exception.
Authentication failed, see inner exception. The
message received was unexpected or badly formatted.
I would expect the WCF client to have skipped SSL authentication.
I also tried to use a custom certificate validator, by extending the X509CertificateValidator and configuring this in the following way:
factory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication()
{
CertificateValidationMode = X509CertificateValidationMode.Custom,
CustomCertificateValidator = new CustomCertificateValidator();
};
factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
factory.Credentials.ServiceCertificate.Authentication.CustomCertificateValidator = new CustomCertificateValidator();
As you might expect as this point, I receive the same exceptions as before. Even worse though, my CustomCertificate.Validate(..) method was not being called at all.
WCF seems to provide an API which allows for quite a bit of control but no matter what I try, my policies/configurations do not seem to by honoured in any way.
What might be going on here?
The below code will work when requiring the SSL authentication in a DotCore project.
Uri uri = new Uri("https://vabqia969vm:21011");
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
};
//these two lines will not work.
//factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
//factory.Credentials.ServiceCertificate.Authentication.RevocationMode = System.Security.Cryptography.X509Certificates.X509RevocationMode.NoCheck;
var client = factory.CreateChannel();
var result = client.TestAsync();
Console.WriteLine(result.Result);
On my side, it works perfectly. I think there is something wrong with the server-side. As you know, we should ensure that the binding type between the client-side and the server-side is consistent. What are the details on the server-side?

Using WCF in .net 2

I've got a method of connecting and use a WCF method, which is on HTTPS and requires a username and password in .net 4.
Now I need to do the same but within .Net 2 and I can't seem to get it to work. I keep on getting the below error. Can anyone help?
Error
{"The underlying connection was closed: An unexpected error occurred on a receive."}
Inner Exception
{"Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host."}
.Net 4 Original Code:
WSHttpBinding myBinding = new WSHttpBinding();
myBinding.Security.Mode = SecurityMode.Transport;
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
EndpointAddress ea = new EndpointAddress(wcfURL);
var web = new Gateway.GatewayClient(myBinding, ea);
// var web = new Gateway.GatewayClient();
XMLCrypto crypto = new XMLCrypto();
web.ClientCredentials.UserName.UserName = crypto.DecryptString(username);
web.ClientCredentials.UserName.Password = crypto.DecryptString(password);
web.Open();
web.Inbound("HOLog", message.Trim().Replace("\n", "").Replace(#"\\", ""));
web.Close();
.Net 2 Code
XMLCrypto crypto = new XMLCrypto();
url = "http://..../gateway/gateway.svc";
userName = crypto.DecryptString(userName);
password = crypto.DecryptString(password);
var web = new Gateway.Gateway();
var credentials = new NetworkCredential(userName, password);
CredentialCache credentialCache = new CredentialCache();
credentialCache.Add(new Uri(url), "Basic", credentials);
web.Credentials = credentials;
string returnMessage = web.Inbound("LSOA", " ");
After a long trolling over the web and testing different ways of talking to a WCF method, I have found the reason why it does not work.
Currently the WCF is set to use wsHttpBinding and now I know that .net 2, does not support it. My work around was to change the Binding from wsHttpBinding to basicHttpBinding within the Web.config of the WCF.
To do this and not effect anything using the WCF, I have to create a seprate Sub domain that will ref a WCF with the config that has the corrected Binding.
"The wsHttpBinding is not compatible with the ASMX-style web references used in .NET 2.0."
How to consume WCF wsHttpBinding Service in application built in 2.0?

WCF: Invalid Security Header

Usually when I consume a web service I add a service reference, put in the URL for the WSDL, and then finagle my way through the API's.
This time around I get a FaultException with the message: "Invalid security header".
Here is my binding:
CustomBinding bindingBNP = new CustomBinding();
SecurityBindingElement securityElement = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
securityElement.DefaultAlgorithmSuite = System.ServiceModel.Security.SecurityAlgorithmSuite.Basic128;
securityElement.KeyEntropyMode = System.ServiceModel.Security.SecurityKeyEntropyMode.CombinedEntropy;
securityElement.IncludeTimestamp = false;
securityElement.SecurityHeaderLayout = SecurityHeaderLayout.Lax;
MtomMessageEncodingBindingElement mtomElement = new MtomMessageEncodingBindingElement(MessageVersion.Soap11WSAddressing10, Encoding.UTF8);
HttpsTransportBindingElement httpsElement = new HttpsTransportBindingElement();
httpsElement.AuthenticationScheme = System.Net.AuthenticationSchemes.Anonymous;
httpsElement.BypassProxyOnLocal = false;
httpsElement.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
httpsElement.ManualAddressing = false;
httpsElement.ProxyAuthenticationScheme = System.Net.AuthenticationSchemes.Anonymous;
httpsElement.TransferMode = TransferMode.Buffered;
httpsElement.UnsafeConnectionNtlmAuthentication = false;
httpsElement.RequireClientCertificate = false;
httpsElement.UseDefaultWebProxy = false;
bindingBNP.Elements.Add(securityElement);
bindingBNP.Elements.Add(mtomElement);
bindingBNP.Elements.Add(httpsElement);
Related question: for diagnostic purposes, how do I know what the inbound/outbound communication is?
Fiddler doesn't seem to pick up anything (I guess it would have to be on the server machine, which I'm probably not going to be able to negotiate). WCF tracing only seems to surface communication "milestones" (if that word connotates some flavor of victory I am ways off!).
Fiddler runs on the client:
Did you start Fiddler before your client?
Did you configure your application to proxy its traffic?
Are you sure your WCF is using HTTP as the transport?

WCF : Configuring message security programmatically

I'm coding an Azure WCF Service Bus service, which is to be configured programmatically to have message security using certificates:
ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Tcp;
// create the service URI based on the service namespace
Uri address = ServiceBusEnvironment.CreateServiceUri("sb", ConfigurationManager.AppSettings["serviceNamespace"], "TestService");
// create the credentials object for the endpoint
TransportClientEndpointBehavior sharedSecretServiceBusCredential = new TransportClientEndpointBehavior();
sharedSecretServiceBusCredential.TokenProvider = TokenProvider.CreateSharedSecretTokenProvider(ConfigurationManager.AppSettings["issuerName"], ConfigurationManager.AppSettings["issuerSecret"]);
//Create and bind the serviceEndpoint
ContractDescription contractDescription = ContractDescription.GetContract(typeof(ITestContract), typeof(TestServiceImpl));
ServiceEndpoint serviceEndPoint = new ServiceEndpoint(contractDescription);
serviceEndPoint.Address = new EndpointAddress(address);
var NetTcpRelayBinding = new NetTcpRelayBinding(EndToEndSecurityMode.TransportWithMessageCredential, RelayClientAuthenticationType.RelayAccessToken);
NetTcpRelayBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate; //The serivice will check the TrustedPeople store for the client
serviceEndPoint.Binding = NetTcpRelayBinding;
serviceEndPoint.Behaviors.Add(sharedSecretServiceBusCredential);
Host = new ServiceHost(typeof(TestServiceImpl), address);
//Add a service certificate
Host.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerTrust;
Host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine,StoreName.My,X509FindType.FindByThumbprint,"E86870F0118CE39D771A49B9337C28444F3C7348");
// create the service host reading the configuration
Host.Description.Endpoints.Add(serviceEndPoint);
I can get this service up and running, however, any client )with just the ServiceBus SharedSecret, clientCredentials NOT set to use any cert) is able to call my service without any errors.
Is the above code sufficient to indicate that certificates (and only certificates base authorization) should be used for message security ?
Any good articles on configuring WCF message security programmatically ?
Turns out that lack of sleep was the culprit; I was running an older version of the service. Clients without any certificates do error out (with System.ServiceModel.ProtocolException was unhandled Message=Error while reading message framing format at position 1 of stream (state: Start).
A properly coded up client for this is :
ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Tcp;
string serviceNamespace = "valid-namespace";
string issuerName = "owner";
string issuerSecret = "validSecret";
// create the service URI based on the service namespace
Uri serviceUri = ServiceBusEnvironment.CreateServiceUri("sb", serviceNamespace, "valid-namespace");
// create the credentials object for the endpoint
TransportClientEndpointBehavior sharedSecretServiceBusCredential = new TransportClientEndpointBehavior();
sharedSecretServiceBusCredential.CredentialType = TransportClientCredentialType.SharedSecret;
sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerName = issuerName;
sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerSecret = issuerSecret;
ChannelFactory<ITestChannel> channelFactory = new ChannelFactory<ITestChannel>();
channelFactory.Endpoint.Address = new EndpointAddress(serviceUri);
var NTRB = new NetTcpRelayBinding();
NTRB.Security.Mode = EndToEndSecurityMode.TransportWithMessageCredential;
NTRB.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
channelFactory.Endpoint.Binding = NTRB;
channelFactory.Endpoint.Contract.ContractType = typeof(ITestChannel);
// apply the Service Bus credentials
channelFactory.Endpoint.Behaviors.Add(sharedSecretServiceBusCredential);
//Question : Why doesn't use of the following line effect Service-Validation ? I can successfully call the service from a machine where the server's certificate does NOT exist in the trusted-people store
//channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.PeerTrust;
channelFactory.Credentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "valid-thubmprint");
// create and open the client channel
ITestChannel channel = channelFactory.CreateChannel();
Console.WriteLine(channel.ServiceMethod());
Console.ReadKey();
channel.Close();
channelFactory.Close();
Still have the problem of the ServiceCertificate always being assumed valid, even when PeerTrust is used for channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode and the service certificate isn't in the TrustedPeople store.
Anyone with ideas on why this happens ?

upload streaming to WCF IIS 7 hosted service not working

I have a wcf service I am setting up to run under IIS 7. I have the service set to streaming for the transfermode.
When I self host the service in a console application every thing seems to work ok. But when the client connects to an iis hosted service it seems to be buffering, and the client eventual times out. I have used fiddler to determine that this client time out happens before the http request is even made.
Here is the servers binding.
var binding = new CustomBinding();
binding.Elements.Add( new TextMessageEncodingBindingElement()
{
MessageVersion = MessageVersion.Soap12WSAddressing10
} );
var secBinding = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
secBinding.AllowInsecureTransport = true;
binding.Elements.Add( secBinding );
binding.Elements.Add( new HttpTransportBindingElement()
{
TransferMode = TransferMode.Streamed,
MaxReceivedMessageSize = Int32.MaxValue,
} );
And the client binding:
var binding = new CustomBinding();
binding.Elements.Add( new TextMessageEncodingBindingElement()
{
MessageVersion = MessageVersion.Soap12WSAddressing10
} );
var secBinding = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
secBinding.AllowInsecureTransport = true;
binding.Elements.Add( secBinding );
binding.Elements.Add( new HttpTransportBindingElement()
{
TransferMode = TransferMode.Streamed,
MaxReceivedMessageSize = Int32.MaxValue,
MaxBufferSize = 400
} );
As an aside the connection is timing out because the stream is infinite and the server should read the first few bytes and then close the stream.
Recently, we had the same issue. When you host your service under IIS, no matter if you enable streaming or not, your service will buffer the entire message prior to sending it. The reason for this, is that it appears as though WCF does not set the Response.BufferOutput to "false" (default is true), when streaming is enabled on a service. A workaround can be found here:
http://weblogs.asp.net/jclarknet/archive/2008/02/14/wcf-streaming-issue-under-iis.aspx
Are you closing the Stream in the client? If true, try closing just in the service side.
Also, verify if its a OneWay operation.
Can you post the both binding nodes, for the endpoints?