Some background:
in order to provide authentication I'm using certificates on client and server side (WCF) and use one certificate for all clients (manually loading it from application directory - not the safest way, but it doesn't require to manage certificate storage and making installation more difficult):
AddressHeader hostHdr = AddressHeader.CreateAddressHeader(ServiceFactory.CLIENT_HOST_HEADER, ServiceFactory.NAMESPACE, hostName);
builder.Headers.Add(hostHdr);
builder.Identity = new X509CertificateEndpointIdentity(GetServiceCertificate(name));
_factory = new ChannelFactory<T>(name, builder.ToEndpointAddress());
_factory.Credentials.ClientCertificate.Certificate = GetClientCertificate(name);
X509ServiceCertificateAuthentication auth = _factory.Credentials.ServiceCertificate.Authentication;
auth.CertificateValidationMode =X509CertificateValidationMode.Custom;
auth.CustomCertificateValidator = new CustomCertificateValidator(new[] {GetServiceCertificate(name)});
This is client side, and serverside host setting up looks like this:
private void CertificateSetup(ServiceHost host)
{
if (ServiceCertificate != null)
host.Credentials.ServiceCertificate.Certificate = ServiceCertificate;
X509ClientCertificateAuthentication authentication =
host.Credentials.ClientCertificate.Authentication;
authentication.CertificateValidationMode =
X509CertificateValidationMode.Custom;
authentication.CustomCertificateValidator =
new CustomCertificateValidator(new [] {ClientCertificate});
}
That works fine and allows to sign messages, but as far as security mode set in following way:
<security mode="Message">
<message clientCredentialType="Certificate" />
</security>
But i need
string name = OperationContext.Current.ServiceSecurityContext.WindowsIdentity.Name;
somehow to obtain WindowsIdentity in ServiceSecurityContext.
Mixed (Transport and Message) security mode is not helpful, because I don't know why but even if i set Windows clientCredentials in config for Transport part mode infrastructure tries to establish SSL connection.
Any ideas ????
Certificates are used for message signing,i.e. proving that other side is either real client or service (man-in-the-middle). But authorization in system being developed partially relies on WindowsIdentity in ServiceSecurityContect. All i want-include WindowsIdentity in sec.context, meanwhile PrimaryIdentity is X509CertIdentity.
So i need to know on serverside which domain user requested service operation.
Try this link re: Impersonation:
http://msdn.microsoft.com/en-us/library/ms730088.aspx
section on - "Mapping a Client Certificate to a Windows Account", seems like what you're after.
with config on client:
authentication mapClientCertificateToWindowsAccount="true"
Client code:
' Create a binding that sets a certificate as the client credential type.
Dim b As WSHttpBinding = New WSHttpBinding()
b.Security.Message.ClientCredentialType = MessageCredentialType.Certificate
' Create a service host that maps the certificate to a Windows account.
Dim httpUri As Uri = New Uri("http://localhost/Calculator")
Dim sh As ServiceHost = New ServiceHost(GetType(HelloService), httpUri)
sh.Credentials.ClientCertificate.Authentication.MapClientCertificateToWindowsAccount = True
Hope that helps
If you're using message security with certificates at each end securing it, I'm not quite sure why you would need a windows identity? What's the reasoning behind that?
Maybe this link will be of use - sections 6 & 7 have details of config settings for certificate authentiaction, which works similarly to SSL:
http://www.codeproject.com/KB/WCF/wcf_certificates.aspx
Related
I'm kind of new to the whole WCF and SOAP topic so please be kind.
I'm using a generated SOAP Client with .net6. In another project we successfully worked with the same Web Service using the old .net Framework 2.0 Web References and the same credentials.
Strange enough everything seemed to work fine at first. Until I realized, that it does not use the given credentials to authenticate. Instead it authenticates with my own domain user.
I also tried to get it to work with explicitly setting the binding with a BasicHttpBinding but I only could get the same broken logic to work or I got various authentication/protocol/security errors.
So it seems the authentication is basically working. It just doesn't use the provided credentials. So my question is: How can I configure it to work with the provided identity?
I also found out that it might have anything to do with a cached Windows token. But how can I get rid of it. How to prevent caching in the first place?
EDIT:
Specified the variable types explicitly.
string url = "http://someServer/AdministrationService.asmx";
AdministrationServiceSoapClient client = new AdministrationServiceSoapClient(
AdministrationServiceSoapClient.EndpointConfiguration.AdministrationServiceSoap,
url);
WindowsClientCredential credential = client.ClientCredentials.Windows;
credential.ClientCredential.UserName = "username";
credential.ClientCredential.Password = "password";
credential.ClientCredential.Domain = "DOMAIN";
GetServerInfoRequest getServerInfoRequest = new GetServerInfoRequest
{
// some stuff set here
};
GetServerInfoRequest getServerInfoReply = await client.GetServerInfoAsync(getServerInfoRequest);
As far as I know, BasicHttpBinding has security disabled by default, but can be added setting the BasicHttpSecurityMode to a value other than None in the constructor. It can be configured according to the instructions in BasicHttpBinding and BasicHttpBinding Constructors.
By default, setting up client credentials involves two steps: determining the type of client credential required by the service and specifying an actual client credential, as described in this document.
After waiting a day it is working. It seems that the cached credentials became invalid somehow.
Strange enough the simple service creation from above is not working anymore. Instead I have to use the following.
var client = new AdministrationServiceSoapClient(
new BasicHttpBinding()
{
Security = new BasicHttpSecurity()
{
Mode = BasicHttpSecurityMode.TransportCredentialOnly,
Message = new BasicHttpMessageSecurity()
{
ClientCredentialType = BasicHttpMessageCredentialType.UserName,
},
Transport = new HttpTransportSecurity()
{
ClientCredentialType = HttpClientCredentialType.Windows,
ProxyCredentialType = HttpProxyCredentialType.Windows,
}
},
},
new EndpointAddress(url));
I create a console application to test a WCF web services. To test my service, I add a Reference service with the URL of my WSDL(https://myservices.fr/Connectors/TokenConnector/ServiceToken.svc?wsdl).
I put in the main method the code to get a new token but I had an error :
application/xop+xml” does not match expected type "text/html; charset=UTF-8", iscontenttypesupported method is implemented properly.
have you any ideas to fix that please?
ChannelFactory<IServiceTokenChannel> factory = new ChannelFactory<IServiceTokenChannel>("BasicHttpBinding_IServiceToken");
factory.Open();
IServiceTokenChannel wcfClientChannel = factory.CreateChannel();
// Making calls.
Console.WriteLine("Le service return: " +
wcfClientChannel.getToken("myLogin", "mypassword", "myEmail")); //Error occurs here.
try to use mtomMessageEncoding in your config file
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding messageEncoding="Mtom">
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
After 3 days of struggling, finally I found a solution of my problem. The error occurs because I didn't define a certificate for my call request. Indeed, to program Windows Communication Foundation (WCF) security, X.509 digital certificates are commonly used to authenticate clients and servers, encrypt, and digitally sign messages (more details here : Working with Certificates).
So to sum up, if you want to test a WCF web services based on https protocol using a console application you can follow the steps below :
Create a console application in Visual studio.
Add a new reference(System.ServiceModel)
Add a new reference service (define the URL of
your web service ex :
https://myservices.fr/Connectors/TokenConnector/ServiceToken.svc)
In programm class paste the following code after adjusting it to your case :
static void Main(string[] args)
{
//instantiate the class ServiceTokenClient wish provide the getToken method with endpoint configuration name and remote adress.
//look at web.config or app.config to get the name of endpoint configuration <binding name="BasicHttpBinding_IServiceToken" />
//https://myservices.fr/Connectors/TokenConnector/ServiceToken.svc is the URL of our web services provided by the host.
ServiceTokenClient _srvToken = new ServiceTokenClient("BasicHttpBinding_IServiceToken", "https://myservices.fr/Connectors/TokenConnector/ServiceToken.svc");
//Set a certificate
// Use the X509Store class to get a handle to the local certificate stores. "My" is the "Personal" store. Don't forget using System.Security.Cryptography and System.Security.Cryptography.X509Certificates;
_srvToken.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySerialNumber, "XXXXXXXXXXXXXXXXXXXXXXXXXXX");
((BasicHttpBinding)_srvToken.Endpoint.Binding).Security.Mode = BasicHttpSecurityMode.Transport; //set the security mode
((BasicHttpBinding)_srvToken.Endpoint.Binding).Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate; //set the type of client credential.
//Making Call
var _objToken = _srvToken.getToken("myLogin", "mypassword", "myEmail");
Console.WriteLine("{0}", _objToken.Token); // Display the token
Console.ReadKey();
}
I'm trying to use C# to get RabbitMQ 3.6.2 to use SSL/TLS on Windows 7 against Erlang 18.0. I'm running into errors when I'm enabling SSL in my C# code. I have gone through the steps to set up SSL/TLS here. I've also gone through the [troubleshooting steps][2] which show turn up successful (except I couldn't do the stunnel step due to lack of knowledge of stunnel). Here's my C# code trying to connect to RabbitMQ:
var factory = new ConnectionFactory()
{
// NOTE: guest username ONLY works with HostName "localhost"!
//HostName = Environment.MachineName,
HostName = "localhost",
UserName = "guest",
Password = "guest",
};
// Without this line, RabbitMQ.log shows error: "SSL: hello: tls_handshake.erl:174:Fatal error: protocol version"
// When I add this line to go to TLS 1.2, .NET throws an exception: The remote certificate is invalid according to the validation procedure.
// https://stackoverflow.com/questions/9983265/the-remote-certificate-is-invalid-according-to-the-validation-procedure:
// Walked through this tutorial to add the client certificate as a Windows Trusted Root Certificate: http://www.sqlservermart.com/HowTo/Windows_Import_Certificate.aspx
factory.Ssl.Version = SslProtocols.Tls12;
factory.Ssl.ServerName = "localhost"; //System.Net.Dns.GetHostName();
factory.Ssl.CertPath = #"C:\OpenSSL-Win64\client\keycert.p12";
factory.Ssl.CertPassphrase = "Re$sp3cMyS3curi1ae!";
factory.Ssl.Enabled = true;
factory.Port = 5671;
// Error: "The remote certificate is invalid according to the validation procedure."
using (var connection = factory.CreateConnection())
{
}
There's a StackOverflow post regarding the "The remote certificate is invalid according to the validation procedure." exception, but the hack fix doesn't seem to take effect as the callback method suggested is never called. I think that I've added my certificate generated via OpenSSL to the Windows Trusted Root Certification Authorities certificates list for local computer. So I'm at a loss here. Any ideas on how to proceed?
Edit: Here's the final working code for anyone struggling to implement SSL on Rabbit:
var factory = new ConnectionFactory();
factory.HostName = ConfigurationManager.AppSettings["rabbitmqHostName"];
factory.AuthMechanisms = new AuthMechanismFactory[] { new ExternalMechanismFactory() };
// Note: This should NEVER be "localhost"
factory.Ssl.ServerName = ConfigurationManager.AppSettings["rabbitmqServerName"];
// Path to my .p12 file.
factory.Ssl.CertPath = ConfigurationManager.AppSettings["certificateFilePath"];
// Passphrase for the certificate file - set through OpenSSL
factory.Ssl.CertPassphrase = ConfigurationManager.AppSettings["certificatePassphrase"];
factory.Ssl.Enabled = true;
// Make sure TLS 1.2 is supported & enabled by your operating system
factory.Ssl.Version = SslProtocols.Tls12;
// This is the default RabbitMQ secure port
factory.Port = 5671;
factory.VirtualHost = "/";
// Standard RabbitMQ authentication (if not using ExternalAuthenticationFactory)
//factory.UserName = ConfigurationManager.AppSettings["rabbitmqUsername"];
//factory.Password = ConfigurationManager.AppSettings["rabbitmqPassword"];
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
// publish some messages...
}
}
Thanks,
Andy
Usual problem is mismatch between what you provide in Ssl.ServerName and host SSL certificate was issued for.
Also note that server-side SSL (encrypted connection between your client and server) and client-side authentication with certificate (you provide server with information which confirms that you have certificate it expects) are two different things. By providing Ssl.CertPath you intent to authorize at server using this certificate, which might or might not be what you want.
My problem was related to using self signed certificates. I had to add the SslOption AcceptablePolicyErrors = SslPolicyErrors.RemoteCertificateNameMismatch |
SslPolicyErrors.RemoteCertificateChainErrors
In the example connection factory creation code sslEnabled is true.
new ConnectionFactory()
{
Uri = uri,
ClientProvidedName = clientProvidedName,
AutomaticRecoveryEnabled = true,
Ssl = new SslOption(){
Enabled = sslEnabled,
AcceptablePolicyErrors = SslPolicyErrors.RemoteCertificateNameMismatch |
SslPolicyErrors.RemoteCertificateChainErrors} ,
NetworkRecoveryInterval = TimeSpan.FromSeconds(networkRecoveryIntervalSecs)
}
It can be done as simple as this
const string RabbitMqServerHostname = "myserver.northeurope.cloudapp.azure.com";
var factory = new ConnectionFactory()
{
HostName = RabbitMqServerHostname,
UserName = "myuser",
Password = "mypassword",
// The settings below turn on SSL
Port = 5671,
Ssl = new SslOption
{
Enabled = true,
ServerName = RabbitMqServerHostname
}
};
I just went through a similar frustrating exercise with the .NET 4.5 client (v. 3.6.6) and the RabbitMQ broker/service on Windows (v. 3.6.6, Erlang 19.2).
The correct combination of RabbitMQ config file options and client settings is not intuitive and the client factory object has changed since the documentation was last updated. Now there's an SslOption class.
Are you still having problems? Perhaps I can help you.
I've resolved the problem changing only the Ssl.ServerName to the Common Name (CN) of the issued certificate, because it was different of the server which hosts the service.
factory.Ssl.ServerName = "[certificate cn]";
I had tried with python (because the provider used that language) and it worked, I suppose then that Python doesn't validate that features of the certificates (it's more insecure?).
I am quite new to WCF and trying to get my head around the security. I am still reading and learning, but I came to a point where I got a working version of WCF with Certificate authentication. I know that the code has some weaknesses; however, my initial goal was to create communication using certificate authentication. Also, I wanted to create everything programmatically (no Web.config configurations for the services or clients). The reason for this is that the client should be able to link an Assembly (Class Library) and get access to the server. Also, I am loading the certificates from the file system (again, I know this is not secure). I would like to get a little bit feedback.
The following client snippet is creating an object that I can use to connect to the server. The anonymous type T is my service interface e.g. IService.
Here is my client implementation:
var url = "URL TO WS";
var binding = new WSHttpBinding
{
Security =
{
Mode = SecurityMode.Message,
Message = {ClientCredentialType = MessageCredentialType.Certificate}
}
};
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<T>(binding, endpoint);
if (channelFactory.Credentials != null)
{
channelFactory.Credentials.ClientCertificate.Certificate =
new X509Certificate2(#"PATH\TO\Client.pfx"); // Client Certificate PRIVATE & PUBLIC Key
channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None; // I know this is not good, but I dont have a valid certificate from a trusted entity
}
wcfClient = channelFactory.CreateChannel();
return wcfClient;
The service is a bit more complex. I use .svc files with their code-behind. If I understand the use of .svc files correctly, then I believe this is the entry point where the .NET framework creates a ServiceHost and automatically opens it? In my implementation I do not open the ServiceHost, I only implemented a ServiceHostFactoryBase and referenced it in the .svc Markup language. Look at the Factory section - this is the part where I implement my custom Host Factory.
<%# ServiceHost Language="C#" Debug="true"
Service="Service.Services.LevelService" CodeBehind="LevelService.svc.cs"
Factory="Service.Security.ServiceHostFactory.HostFactory" %>
And my custom Host Factory looks like this:
public class HostFactory : ServiceHostFactoryBase
{
public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
var serviceType = Type.GetType(constructorString);
if (serviceType.GetInterfaces().Count() != 1)
throw new NotImplementedException("The service can only have one implemented interface");
var interfaceType = serviceType.GetInterfaces()[0];
var myServiceHost = new ServiceHost(serviceType, baseAddresses);
var httpBinding = new WSHttpBinding();
httpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
httpBinding.Security.Mode = SecurityMode.Message;
myServiceHost.Credentials.ServiceCertificate.Certificate = new X509Certificate2(#"PATH\TO\Server.pfx");
myServiceHost.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
myServiceHost.Credentials.ClientCertificate.Authentication.CustomCertificateValidator = new MyX509CertificateValidator();
myServiceHost.Credentials.ClientCertificate.Certificate = new X509Certificate2(#"PATH\TO\Client.cer");
myServiceHost.AddServiceEndpoint(interfaceType, httpBinding, String.Empty);
return myServiceHost;
}
}
The custom validator doess't do much yet, but here it is as well:
public class MyX509CertificateValidator : X509CertificateValidator
{
public override void Validate(X509Certificate2 certificate)
{
// Check that there is a certificate.
if (certificate == null)
{
throw new ArgumentNullException("certificate");
}
// Check that the certificate issuer matches the configured issuer.
//throw new SecurityTokenValidationException("Certificate was not issued by a trusted issuer");
}
}
If I understand correctly, the Server has ONLY the PUBLIC key of the client registered since I only reference the .cer file.
My big question is now, if I would like to get anything like this on a production server - and lets assume nobody will actually get the executables (including the certificates), would this be a possible solution to keep unwanted people out of my webservice? Basically, I don't want anybody else consuming my webservice - only if you have the proper certificate. Also, how much of an issue is the part where I set on the client:
CertificateValidationMode = X509CertificateValidationMode.None
I know there are many questions - but overall, I would like to know if I made some fundamental mistakes in this implementation.
Ok,
after going through a lot of tutorials and demo applications, I figured out that the best way to go ahead is actually using the Certificate Store on Windows. However, I still might consider a hybrid solution where the Server has the certificates in the Certificate store and the client has it embedded in a resource. If you are struggling with WCF and Certificates, have a look at those links:
IIS7 Permissions Overview - ApplicationPoolIdentity
I was able to create Transport as well as Message secured WCF web services. I would suggest to READ the linked articles because there is so much information that will make you understand certificates and their usage. Especially when dealing with self-singed certificates!
I ended up implementing wsHttpBinding using Message Security Mode + Client Certificate with ChainTrust.
Hope this will help someone else!
I am trying to connect to a MQ server queue via a .NET client. I need to use the certificate for secured communication. Here is the code that I have:
MQEnvironment.SSLKeyRepository = "*SYSTEM";
MQEnvironment.ConnectionName = connectionName;
MQEnvironment.Channel = channelName;
MQEnvironment.properties.Add(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_MANAGED);
MQEnvironment.SSLCipherSpec = "TLS_RSA_WITH_AES_256_CBC_SHA";
queueManager = new MQQueueManager(queueManagerName, channelName, connectionName);
queue = queueManager.AccessQueue(SendQueueName,MQC.MQOO_OUTPUT + MQC.MQOO_FAIL_IF_QUIESCING);
queueMessage = new MQMessage();
queueMessage.WriteString(message);
queueMessage.Format = MQC.MQFMT_STRING;
queue.Put(queueMessage, new MQPutMessageOptions());
Every time I try to put the message on the queue, I get this error message
Reason Code: 2059
MQexp.Message: MQRC_Q_MGR_NOT_AVAILABLE
I have checked my variables for the queue manager name, queue name etc and they are correct.
I was also able to connect to a different queue without SSL, I believe that my code is not furnishing enough information to establish a successful connection.
Any help on this would be appreciated.
Thanks,
Kunal
I had the same problem and error message. After enabling tracing I was able to isolate the problem.
I always wondered, how the client is selecting the correct client certificate from the store. The trace output revealed following:
000001B2 15:53:46.828145 20776.10 Created an instance of SSLStreams
000001B3 15:53:46.828145 20776.10 Setting current certificate store as 'Computer'
000001B4 15:53:46.828145 20776.10 Created store object to access certificates
000001B5 15:53:46.834145 20776.10 Opened store
000001B6 15:53:46.834145 20776.10 Accessing certificate - ibmwebspheremqmyusername
000001B7 15:53:46.835145 20776.10 TLS12 supported - True
000001B8 15:53:46.837145 20776.10 Setting SslProtol as Tls
000001B9 15:53:46.837145 20776.10 Starting SSL Authentication
In my case, I had to set the friendly name of the client certificate to ibmwebspheremqmyusername (replace "myusername" with your userid) and set the label in the code aswell:
properties.Add(MQC.MQCA_CERT_LABEL, "ibmwebspheremqmyusername");
To enable tracing, add following to your app.config/web.config where the path points to a location that contains a file named mqtrace.config:
<appSettings>
<add key="MQTRACECONFIGFILEPATH" value="C:\MQTRACECONFIG" />
</appSettings>
Sample content of mqtrace.config (specified directories must exist in advance):
<?xml version="1.0" encoding="utf-8"?>
<traceSettings>
<MQTRACELEVEL>2</MQTRACELEVEL>
<MQTRACEPATH>C:\MQTRACEPATH</MQTRACEPATH>
<MQERRORPATH>C:\MQERRORLOGPATH</MQERRORPATH>
</traceSettings>
Here are some links for more detail:
Tracing:
https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.dev.doc/q123550_.htm
Why label:
http://www-01.ibm.com/support/docview.wss?uid=swg21245474