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?
Related
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?
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?
I have some code which consumes a WCF service. The service is protected by basic authentication, so on creating the client, I'm using the following code:
BasicHttpBinding httpBinding = new BasicHttpBinding();
httpBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
httpBinding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
httpBinding.Security.Transport.Realm = service_realm;
EndpointAddress address = new EndpointAddress(service_address);
Service.ServiceClient client = new Service.ServiceClient(httpBinding, address);
client.ClientCredentials.UserName.UserName = service_username;
client.ClientCredentials.UserName.Password = service_password;
Works fine when I run the code from a console app. But when I run the same code from a windows service, a MessageSecurityException is being thrown telling me that my request was unauthorized. For some reason it seems to be using the current Windows account for authentication, because my own account does have access to the service. But I don't want it to, I want it to use the stored credentials. What am I missing here?
WCF basicHttpBinding does not support plaintext credentials; the reason is because the moment you want pass credentials around on a transport binding, WCF requires the underlying transport to be a secure transport, such as SSL.
In order for your code to work, you would then need to use service via https or using certificates or encryption.
Seems to be fixed using this config:
_httpBinding = new BasicHttpBinding();
_httpBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
_httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
_httpBinding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
_httpBinding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
_httpBinding.Security.Message.AlgorithmSuite = SecurityAlgorithmSuite.Default;
_httpBinding.AllowCookies = false;
_httpBinding.BypassProxyOnLocal = false;
_httpBinding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
_httpBinding.MessageEncoding = WSMessageEncoding.Text;
_httpBinding.TextEncoding = Encoding.UTF8;
_httpBinding.TransferMode = TransferMode.Buffered;
_httpBinding.UseDefaultWebProxy = false;
Service.ServiceClient client = new Service.ServiceClient(_httpBinding, _address);
client.ClientCredentials.UserName.UserName = service_username;
client.ClientCredentials.UserName.Password = service_password;
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 ?
How do I make my WCF client authenticate using the ACS to my internally hosted WCF service? The issue revolves around setting a custom Realm (which I can't figure out how to set.)
My ACS is configured similar to the ACS Samples however the "Realm" is defined as shown below.
Excerpt from Azure ACS Configuration page
Client Side Code
EndpointAddress serviceEndpointAddress = new EndpointAddress( new Uri( "http://localhost:7000/Service/Default.aspx"),
EndpointIdentity.CreateDnsIdentity( GetServiceCertificateSubjectName() ),
new AddressHeaderCollection() );
ChannelFactory<IStringService> stringServiceFactory = new ChannelFactory<IStringService>(Bindings.CreateServiceBinding("https://agent7.accesscontrol.appfabriclabs.com/v2/wstrust/13/certificate"), serviceEndpointAddress );
// Set the service credentials.
stringServiceFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
stringServiceFactory.Credentials.ServiceCertificate.DefaultCertificate = GetServiceCertificate();
// Set the client credentials.
stringServiceFactory.Credentials.ClientCertificate.Certificate = GetClientCertificateWithPrivateKey();
Server Side Code
string acsCertificateEndpoint = String.Format( "https://{0}.{1}/v2/wstrust/13/certificate", AccessControlNamespace, AccessControlHostName );
ServiceHost rpHost = new ServiceHost( typeof( StringService ) );
rpHost.Credentials.ServiceCertificate.Certificate = GetServiceCertificateWithPrivateKey();
rpHost.AddServiceEndpoint( typeof( IStringService ),
Bindings.CreateServiceBinding( acsCertificateEndpoint ),
"http://localhost:7000/Service/Default.aspx"
);
//
// This must be called after all WCF settings are set on the service host so the
// Windows Identity Foundation token handlers can pick up the relevant settings.
//
ServiceConfiguration serviceConfiguration = new ServiceConfiguration();
serviceConfiguration.CertificateValidationMode = X509CertificateValidationMode.None;
// Accept ACS signing certificate as Issuer.
serviceConfiguration.IssuerNameRegistry = new X509IssuerNameRegistry( GetAcsSigningCertificate().SubjectName.Name );
// Add the SAML 2.0 token handler.
serviceConfiguration.SecurityTokenHandlers.AddOrReplace( new Saml2SecurityTokenHandler() );
// Add the address of this service to the allowed audiences.
serviceConfiguration.SecurityTokenHandlers.Configuration.AudienceRestriction.AllowedAudienceUris.Add( new Uri( "urn:federation:customer:222:agent:11") );
FederatedServiceCredentials.ConfigureServiceHost( rpHost, serviceConfiguration );
return rpHost;
... where urn:federation:customer:222:agent:11 is the Relying party ID
... and http://localhost:7000/Service/Default.aspx is the location I want the above WCF / WIF client to bind to once the ACS authentication is made.
Question
How do I edit the code above so that the client and server will both operate against a certain port (localhost:700) and also with a realm of urn:federation:customer:222:agent:11
I think I have the server code correct; however how do I set AudienceRestriction on the client?
Your server side code looks fine, but Sixto is right about standard channel factories. Luckily, you can request a security token from ACS yourself using a WSTrustChannelFactory. In the context of your sample, your code would look like this:
//
// Get the token from ACS
//
WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory(
Bindings.CreateAcsCertificateBinding(),
new EndpointAddress( acsCertificateEndpoint ) );
trustChannelFactory.Credentials.ClientCertificate.Certificate = GetClientCertificateWithPrivateKey();
RequestSecurityToken rst = new RequestSecurityToken()
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointAddress( new Uri( "urn:federation:customer:222:agent:11" ) ),
KeyType = KeyTypes.Symmetric
};
WSTrustChannel wsTrustChannel = (WSTrustChannel)trustChannelFactory.CreateChannel();
SecurityToken token = wsTrustChannel.Issue( rst );
//
// Call StringService, authenticating with the retrieved token
//
WS2007FederationHttpBinding binding = new WS2007FederationHttpBinding( WSFederationHttpSecurityMode.Message );
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Message.NegotiateServiceCredential = false;
ChannelFactory<IStringService> factory = new ChannelFactory<IStringService>(
binding,
new EndpointAddress(
new Uri( ServiceAddress ),
EndpointIdentity.CreateDnsIdentity(GetServiceCertificateSubjectName()) ) );
factory.ConfigureChannelFactory<IStringService>();
factory.Credentials.SupportInteractive = false;
factory.Credentials.ServiceCertificate.DefaultCertificate = GetServiceCertificate();
IStringService channel = factory.CreateChannelWithIssuedToken<IStringService>( token );
string reversedString = channel.Reverse( "string to reverse" );
Some answers may be better late than never. I've been unable to find any official documentation on using WCF in this fashion, however in reading the WS-Trust papers and the MSDN documentation on configuration, I have come up with the following solution which appears to work.
From the service consuming client's config at configuration/system.serviceModel/bindings/ws2007FederationHttpbinding/binding/security/message. It overrides the AppliesTo element of the token request message.
<tokenRequestParameters>
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
<Address>urn:x-Organization:Testing</Address>
</EndpointReference>
</wsp:AppliesTo>
</tokenRequestParameters>
Adding this same snippet in the configuration of the service, will cause the Service Reference utility to include this within the trust:SecondaryParameters element of the service client. It must be moved into the parent tokenRequestParameters element to work properly.
Haven't actually tried the approach referenced in this MSDN article but from reading it sounds like the standard channel factory doesn't have the right hooks to do what you want. The WSTrustChannelFactory is built for WIF & SAML but I'm not familiar enough with ACS to determine if it is applicable. This article in this six-part series will probably be worthwhile perusing too.