WCF service behind loadbalancer with custom authorization - wcf

Our environment is based on server application which exposes service through WCF. Out customer uses a load balancer - F5. Client application hits it through secured channel and later it uses non-secured HTTP channel. Both client and server uses WsHttpBinding.
Client <> HTTPS <> F5 <> HTTP <> Server
I managed to work with such configuration, but our service uses custom authorization based on JWT tokens and then problem occurs.
I've tested many configurations, but there were various errors.
Currently configuration of client:
var binding = new WSHttpBinding();
binding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Message.NegotiateServiceCredential = true;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
Server configuration looks like:
var binding = new WSHttpBinding();
binding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
host.Description.Behaviors.Find<ServiceBehaviorAttribute>().AddressFilterMode = AddressFilterMode.Any;
binding.Security.Mode = SecurityMode.None;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Message.NegotiateServiceCredential = true;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
host.Description.Behaviors.Find<ServiceAuthorizationBehavior>().ServiceAuthorizationManager = new JWTAuthorizationManager();
Client application sets authorization headers in such way:
channelFactory.Credentials.UserName.UserName = userId;
credentialBehaviour.UserName.Password = token;
Current status is that request goes to the service, but in CheckAccess() method from JWTAuthorizationManager HttpRequestHeader.Authorization is empty. Moreover System.ServiceModel.MustUnderstandSoapException is thrown. When I switched Security.Mode of client to Transport same thing happens but exception isn't thrown.
I'm not familiar with the details of this technology and I'm not sure what really happens.
UPDATE:
I've checked what is received by service. I see that Security header exists in the message, but service can't interpret this because of Security Mode set to None. I can't set this to Message because it requires certificate on the server machine, we don't want this.

I managed with this problem using CustomBinding at the server side:
var binding = new CustomBinding();
var securityHeader = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
securityHeader.AllowInsecureTransport = true;
securityHeader.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10;
securityHeader.SecurityHeaderLayout = SecurityHeaderLayout.Strict;
securityHeader.IncludeTimestamp = true;
var textEncoding = new TextMessageEncodingBindingElement();
textEncoding.MessageVersion = MessageVersion.Soap12WSAddressing10;
binding.Elements.Add(textEncoding);
var httpTransport = new HttpTransportBindingElement();
httpTransport.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
httpTransport.MaxReceivedMessageSize = int.MaxValue;
httpTransport.MaxBufferPoolSize = int.MaxValue;
binding.Elements.Add(httpTransport);

Related

WSE2 to WCF: Signing a SOAP message

I need to covert code from WSE2 to WCF and need a few tips on how to implement signing a SOAP message with a X509Certificate2 object.
WSE2 code:
X509SecurityToken tok = new X509SecurityToken(cert);
SoapContext cont = cfs.RequestSoapContext;
cont.Security.Tokens.Add(tok);
cont.Security.Elements.Add(new MessageSignature(tok));
"cert" is my X509Certificate2 object and "cfs" is my Web Services client object.
How can I make this work without WSE2, how to do the same in WCF?
You can use a custom binding for that, but first you must figure out which kind of binding you need. Look here and here. On custom binding you can add security token for signing. My asymmetric binding looks like this: (but you can also use symmetric binding)
AsymmetricSecurityBindingElement asymmetricBinding = SecurityBindingElement.CreateMutualCertificateDuplexBindingElement(
MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10);
asymmetricBinding.InitiatorTokenParameters = new X509SecurityTokenParameters
{
InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient
};
asymmetricBinding.RecipientTokenParameters = new X509SecurityTokenParameters
{
InclusionMode = SecurityTokenInclusionMode.Never
};
asymmetricBinding.EndpointSupportingTokenParameters.SignedEncrypted.Add(new UserNameSecurityTokenParameters
{
InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient
});
asymmetricBinding.IncludeTimestamp = true;
asymmetricBinding.SecurityHeaderLayout = SecurityHeaderLayout.Strict;
asymmetricBinding.MessageProtectionOrder = MessageProtectionOrder.SignBeforeEncrypt;
var textMessageEncoding = new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8));
var httpsTransport = new HttpsTransportBindingElement();
CustomBinding b = new CustomBinding(asymmetricBinding, textMessageEncoding, httpsTransport);
Then you can set certificates on ClientCredentials of the EndpointClient
var wsClient = new YourEndpointClient(b, new EndpointAddress(yourWsEndPointAddress));
wsClient.ClientCredentials.ClientCertificate.Certificate = new X509Certificate2(cert);
wsClient.ClientCredentials.ServiceCertificate.DefaultCertificate = new X509Certificate2(cert);

WCF to WebLogic communication

I'm trying to talk to this service here:
https://dmrsit1gateway1.skat.dk/B2B/USImportoer/Service?WSDL
It's a Oracle Weblogic service using x509 certificates.
Whenever I send a request I receive a SecurityAccessDeniedException with the additional info:
{"Failed to derive subject from token.javax.security.auth.login.LoginException: javax.security.auth.callback.UnsupportedCallbackException: Unsupported callback class: NameCallback javax.security.auth.callback.NameCallback#15d38692"}
Somehow my requests does not seem to conform with what's expected.
This is my latest attempt:
var binding = new CustomBinding();
var sec = (AsymmetricSecurityBindingElement)SecurityBindingElement.CreateMutualCertificateBindingElement(MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10);
var x509token = new X509SecurityTokenParameters();
sec.EnableUnsecuredResponse = true;
sec.EndpointSupportingTokenParameters.Signed.Add(x509token);
sec.IncludeTimestamp = true;
sec.SecurityHeaderLayout = SecurityHeaderLayout.LaxTimestampLast;
sec.AllowInsecureTransport = true;
binding.Elements.Add(sec);
binding.Elements.Add(new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8));
HttpsTransportBindingElement httpsBindingElement = new HttpsTransportBindingElement();
httpsBindingElement.RequireClientCertificate = true;
binding.Elements.Add(httpsBindingElement);
string endpoint = "https://dmrsit1gateway1.skat.dk:443/B2B/USImportoer/Service?WSDL";
var client = new USImportoerServiceTypeClient(binding, new EndpointAddress(new Uri(endpoint), new DnsEndpointIdentity("REDACTED"), new AddressHeaderCollection()));
client.ClientCredentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindByThumbprint, "REDACTED");
client.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindByThumbprint, "REDACTED");
var response = client.getUSDispensationTypeListeHent(input);
Any input how to troubleshoot this would be highly appreciated.

Web service security using X509 certificate in Classic ASP

I'm trying to call a C# dll from classic ASP - this dll calls a webservice. I've been asked by someone that runs that ASP site to create this dll for him and had tons of problems doing that. He told me he has a *.crt file, and that the dll should use that to call his SSL-based webservice.
So since this is classic ASP, had to make it a COM object and also configure the service and endpoint through code, like so:
BasicHttpBinding binding = new BasicHttpBinding();
binding.Name = "Service1Binding";
binding.CloseTimeout = System.TimeSpan.Parse("00:01:00");
binding.OpenTimeout = System.TimeSpan.Parse("00:01:00");
binding.ReceiveTimeout = System.TimeSpan.Parse("00:10:00");
binding.SendTimeout = System.TimeSpan.Parse("00:01:00");
binding.AllowCookies = false;
binding.BypassProxyOnLocal = false;
binding.HostNameComparisonMode = System.ServiceModel.HostNameComparisonMode.StrongWildcard;
binding.MaxBufferSize = 65536;
binding.MaxBufferPoolSize = 524288;
binding.MaxReceivedMessageSize = 65536;
binding.MessageEncoding = System.ServiceModel.WSMessageEncoding.Text;
binding.TextEncoding = System.Text.Encoding.UTF8;
binding.TransferMode = System.ServiceModel.TransferMode.Buffered;
binding.UseDefaultWebProxy = true;
binding.ReaderQuotas.MaxDepth = 32;
binding.ReaderQuotas.MaxStringContentLength = 8192;
binding.ReaderQuotas.MaxArrayLength = 16384;
binding.ReaderQuotas.MaxBytesPerRead = 4096;
binding.ReaderQuotas.MaxNameTableCharCount = 16384;
binding.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.None;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
binding.Security.Transport.Realm = "";
binding.Security.Mode = BasicHttpSecurityMode.TransportWithMessageCredential;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.Certificate;
binding.Security.Message.AlgorithmSuite = System.ServiceModel.Security.SecurityAlgorithmSuite.Default;
EndpointAddress endpoint =
new EndpointAddress("https://webservice.XXXX.com/Service1.svc");
var cert = new X509Certificate2(certPath); //local path to *.crt file
Service1.ServiceClient client = new Service1.ServiceClient(binding, endpoint);
client.ClientCredentials.ClientCertificate.Certificate = cert;
var result = client.HelloWorld();
At first i got a Forbidden exception, so I changed the Security.Mode to be BasicHttpSecurityMode.TransportWithMessageCredential. Now, I'm getting this error:
The private key is not present in the X.509 certificate
Is it looking for the cert in the store, or using the path I gave it? How can i make this work?

Programmatic WCF Message Security with Certificates

I've written a self-hosted WCF service using WSHttpBindings and I'm trying to implement message-level security using certificates I've generated myself. Unfortunately I'm getting a buried exception (via the Service Trace Viewer) stating "The credentials supplied to the package were not recognized."
A couple notes:
This has to be done in code, not
in configuration
(Server/Client)Cert are certificates
that are in the local machine store
with accessible private keys to my
user while debugging.
I've googled the hell out of this and
found a good resource for setting up
WCF message based security here
I'm not sure what I'm missing. Most of this stuff seems straight forward except for creating the endpoint identities. It fails with the same message whether I use DnsEndpointIdentities, cert based ones, or no identities at all.
Can anyone point me in the right direction?
Server side:
var binding = new WSHttpBinding
{
Security =
{
Mode = SecurityMode.Message,
Message =
{
ClientCredentialType = MessageCredentialType.Certificate,
AlgorithmSuite = SecurityAlgorithmSuite.Basic256Sha256Rsa15
}
}
};
_host = new ServiceHost(this)
{
Credentials =
{
ServiceCertificate =
{
Certificate = ServiceCert
},
ClientCertificate =
{
Certificate = ClientCert,
Authentication =
{
TrustedStoreLocation = StoreLocation.LocalMachine,
RevocationMode = X509RevocationMode.NoCheck,
CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust
}
}
}
};
var address = new Uri(string.Format(#"http://serviceaddress"));
var ep = _host.AddServiceEndpoint(typeof (IService), binding, address);
ep.Address = new EndpointAddress(address, EndpointIdentity.CreateX509CertificateIdentity(ServiceCert));
_host.Open();
Client side:
var binding = new WSHttpBinding
{
Security =
{
Mode = SecurityMode.Message,
Message =
{
ClientCredentialType = MessageCredentialType.Certificate,
AlgorithmSuite = SecurityAlgorithmSuite.Basic256Sha256Rsa15
}
}
};
var address = new Uri(#"http://serviceaddress");
var endpoint = new EndpointAddress(address, EndpointIdentity.CreateX509CertificateIdentity(ServerCert));
var channelFactory = new ChannelFactory<IService>(binding, endpoint)
{
Credentials =
{
ServiceCertificate =
{
DefaultCertificate = ServerCert,
Authentication =
{
RevocationMode = X509RevocationMode.NoCheck,
TrustedStoreLocation = StoreLocation.LocalMachine,
CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust
}
},
ClientCertificate =
{
Certificate = ClientCert
}
}
};
var channel = channelFactory.CreateChannel();
this msdn article helped tremendously. I think the root of the problem was setting the following message security parameters to false:
httpBinding.Security.Message.NegotiateServiceCredential = false;
httpBinding.Security.Message.EstablishSecurityContext = false;
So now the overall code for the server side looks more like:
var httpBinding = new WSHttpBinding(SecurityMode.Message);
httpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
httpBinding.Security.Message.NegotiateServiceCredential = false;
httpBinding.Security.Message.EstablishSecurityContext = false;
var httpUri = new Uri("http://serviceaddress");
_host = new ServiceHost(this, httpUri);
_host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, serverThumbprint);
_host.Credentials.ClientCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
_host.Credentials.ClientCertificate.Authentication.TrustedStoreLocation = StoreLocation.LocalMachine;
_host.AddServiceEndpoint(typeof(IMetaService), httpBinding, httpUri);
_host.Open();
and the client side:
var httpBinding = new WSHttpBinding(SecurityMode.Message);
httpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
httpBinding.Security.Message.NegotiateServiceCredential = false;
httpBinding.Security.Message.EstablishSecurityContext = false;
var httpUri = new Uri("http://serviceaddress");
var httpEndpoint = new EndpointAddress(httpUri, EndpointIdentity.CreateDnsIdentity("name of server cert"));
var newFactory = new ChannelFactory<IMetaService>(httpBinding, httpEndpoint);
newFactory.Credentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, "client certificate thumbprint");
newFactory.Credentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, "server certificate thumbprint");
var channel = newFactory.CreateChannel();

How do configure username/password authentication for WCF netTcpBinding?

I would like to be able to use username/password authentication with nettcpbinding, is that possible? (UserNamePasswordValidator or something like that), no windows authentication.
I'm configuring everything with code, so please only use code and not app.config in any examples.
This is what I came up with, I have no idea if some of the code is not required:
Service host:
ServiceHost host = new ServiceHost(concreteType);
var binding = new NetTcpBinding(SecurityMode.TransportWithMessageCredential, true);
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
host.AddServiceEndpoint(serviceType, binding, "net.tcp://someaddress:9000/" + name);
host.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator();
host.Credentials.ServiceCertificate.Certificate = new X509Certificate2("mycertificate.p12", "password");
host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode =
UserNamePasswordValidationMode.Custom;
And client side:
var binding = new NetTcpBinding(SecurityMode.TransportWithMessageCredential, true);
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
var factory = new ChannelFactory<ISwitchService>(binding,
new EndpointAddress(
new Uri("net.tcp://someaddress:9000/switch")));
factory.Credentials.UserName.UserName = "myUserName";
factory.Credentials.UserName.Password = "myPassword";