WSE2 to WCF: Signing a SOAP message - wcf

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);

Related

WCF service behind loadbalancer with custom authorization

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);

How to pass a certificate to WSTrust to get Saml Token

Here is an example of getting tokem using WSTrustChannelFactory. From here.
var stsBinding = new WS2007HttpBinding();
stsBinding.Security.Mode = SecurityMode.TransportWithMessageCredential;
stsBinding.Security.Message.EstablishSecurityContext = false;
stsBinding.Security.Message.NegotiateServiceCredential = false;
stsBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory(
stsBinding
, new EndpointAddress(tokenurl)
);
trustChannelFactory.TrustVersion = System.ServiceModel.Security.TrustVersion.WSTrust13;
X509Store myStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
myStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection coll = myStore.Certificates.Find(X509FindType.FindBySerialNumber, "MycertSerialNumber", true);
X509Certificate2 cert = coll[0];
trustChannelFactory.Credentials.ClientCertificate.Certificate = cert;
WSTrustChannel channel = (WSTrustChannel)trustChannelFactory.CreateChannel();
RequestSecurityToken rst = new RequestSecurityToken(RequestTypes.Issue, keyType);
rst.AppliesTo = new EndpointAddress(realm);
RequestSecurityTokenResponse rstr = null;
rst.TokenType = SecurityTokenTypes.Saml;
SecurityToken token = channel.Issue(rst, out rstr);
Now I don't have a username/password but the provider has given me certificate .pfx file.
How do I pass it to the WSTrushChannelFactory? I have tried using CertificateBinding but no success.
Updated Code above: 11/05/2014:
Getting this error: ID3242: The security token could not be authenticated or authorized.
Use the ClientCertificate property:
var stsBinding = new WS2007HttpBinding();
stsBinding.Security.Mode = SecurityMode.TransportWithMessageCredential;
stsBinding.Security.Message.EstablishSecurityContext = false;
stsBinding.Security.Message.NegotiateServiceCredential = false;
// select the authentication mode of Client Certificate
stsBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
var wifChannelFactory = new WSTrustChannelFactory(stsBinding, stsEndpoint);
wifChannelFactory.TrustVersion = TrustVersion.WSTrust13;
// Supply the credentials
wifChannelFactory.Credentials.ClientCertificate.Certificate = config.Certificate;
The PFX you can import to your certificate store via the certmgr.msc snapin. Make sure that the account your application is running as has access to the private key. You can reference it in the store using the x509certificate2 classes.
Here you go.
private static SecurityToken RequestSecurityToken()
{
// set up the ws-trust channel factory
var factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(
SecurityMode.TransportWithMessageCredential),
_idpAddress);
factory.TrustVersion = TrustVersion.WSTrust13;
var authCertificate = X509.LocalMachine.My.Thumbprint.Find(Properties.Settings.Default.RassCertificateThumbprint).FirstOrDefault();
if (authCertificate == null)
throw new InternalException(String.Format("No atuhentication certificate found in store with thumbprint {0}.", Properties.Settings.Default.ClientCertificateThumbprint));
// overenie je na zaklade certifikatu RASS
factory.Credentials.ClientCertificate.Certificate = authCertificate;
// create token request
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Symmetric,
AppliesTo = new EndpointReference(_serviceAddress.AbsoluteUri)
};
// request token and return
return factory.CreateChannel().Issue(rst);
}
BTW: #Mitch is right about access to the private key. I just took your method and replaced few lines of code.

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.

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";