Securing a WCF Service

There is a lot of nice documentation at Microsoft's website along with some other good sites but I'm going to add some details and my working configuration for my service. For me, coming from Linux environments it has been a bit tedious to figure out how Windows manages the SSL certificates and all the stuff.
The definition of the service is something like this:

[ServiceContract()]
public interface IPaymentService
{
...
public class PaymentService : IPaymentService
{

I have it hosted on IIS as a virtual directory within the Default Web Site so the folder contains a web.config:

<system.serviceModel>
<services>
<service name="Services.Mail.MailService" behaviorConfiguration="KPMembershipProviderOverHttps">
<endpoint contract="Services.Mail.IMailService"
binding="wsHttpBinding" bindingConfiguration="MembershipBinding"/>
</service>
<service name="Services.Payment.PaymentService" behaviorConfiguration="KPMembershipProviderOverHttps">
<endpoint contract="Services.Payment.IPaymentService"
binding="wsHttpBinding" bindingConfiguration="MembershipBinding"/>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="MembershipBinding">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="KPMembershipProviderOverHttps">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="MembershipProvider"
membershipProviderName="KPMembershipProvider" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>

<system.web>
<authentication>
<forms requireSSL="true" />
</authentication>
<compilation debug="true">
<assemblies/>
</compilation>

<membership defaultProvider="KPMembershipProvider">
<providers>
<add name="KPMembershipProvider" type="Services.KPMembershipProvider, Services" />
</providers>
</membership>
</system.web>

I had to implement my MembershipProvider class within the same Services assembly:

public class KPMembershipProvider : MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
#region Business logic to validate an user
...

#endregion
}

#region the rest of the MembershipProvider overrided methods just throw a not implemented exception
...

#endregion
}

And this is the web.config in the client side:

<system.serviceModel>
<client>
<endpoint address="https://localhost/Services.Demo2/Mail.svc" contract="Services.Client.IMailService" name="MailService"
binding="wsHttpBinding" bindingConfiguration="MembershipBinding">
</endpoint>
<endpoint address="https://localhost/Services.Demo2/Payment.svc" contract="Services.Client.IPaymentService" name="PaymentService"
binding="wsHttpBinding" bindingConfiguration="MembershipBinding">
</endpoint>
</client>

<bindings>
<wsHttpBinding>
<!-- Set up a binding that uses UserName as the client credential type -->
<binding name="MembershipBinding">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>

<behaviors>
<serviceBehaviors>
<behavior name="KPMembershipProviderOverHttps">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceCredentials>
<!-- UsernameToken over Transport Security -->
<userNameAuthentication userNamePasswordValidationMode="MembershipProvider"
membershipProviderName="KPMembershipProvider" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>

Now in the C# code, everytime you use the client, you have to send the credentials as well:

PaymentServiceClient client = new PaymentServiceClient();
client.ChannelFactory.Credentials.UserName.UserName = "carlos";
client.ChannelFactory.Credentials.UserName.Password = "12341234";
client.CallWhatever(parameters);

In order to get a web server running on HTTPS, we need to create a SSL certificate. And also to get the client accepting the certificate it has to trust the server (which is a requirement for the service client to work) so that the it has to trust the certification authority which signed the certificate. I created the request using IIS and signed it using OpenSSL following these links:

I had to change few things regarding the steps described in thouse documents but they were tiny changes like renaming the serial.txt to serial or creating the index.txt file empty or removing the "-config openssl.config" option from the command line.
Once IIS has the signed certificate it enables to port 443 for ssl automatically and you have to run the certmgr tool (from then run item in the start menu) and add the ca.cer and iisx509.cer into the Trusted Root Certificate Authorities in order for the client to trust the certificate (I guess the ca.cer is enough). If you open IE and access the service in https://localhost/Services...../Service.pvc and you get the page without any certificate warnings or errors, your service client have to work as well.
Eventually, if you need to export the certificates you need the private key and you can do so generating the .pfx files from the certmgr tool or mmc: http://www.digicert.com/wildcard-export-import.htm
Now, ir order to avoid setting the credentials everytime and read them from the web.config, there are a few options:
http://msdn2.microsoft.com/en-us/library/ms730868.aspx
But I've implemented a quick hack to solve the problem too:

clientWrapper.jpg

Here you have the copy-paste version:

public class ClientWrapper
where T : System.ServiceModel.ClientBase
where Z : class
{
private T _service;
private static string _username = null;
private static string _password = null;

public string Username
{
get { return _username; }
set { _username = value; }
}

public string Password
{
get { return _password; }
set { _password = value; }
}

public T Service
{
get { return _service; }
set { _service = value; }
}

public ClientWrapper(T service)
{
_service = service;
readCredentialsFromConfig();
_service.ChannelFactory.Credentials.UserName.UserName = Username;
_service.ChannelFactory.Credentials.UserName.Password = Password;
}

private void readCredentialsFromConfig()
{
if (_username == null)
{
_username = ConfigurationManager.AppSettings["servicesUsername"];
_password = ConfigurationManager.AppSettings["servicesPassword"];
}
}
}

And the required entry in the web.config

appSettings
add key="servicesUsername" value="carlos"
add key="servicesPassword" value="12341234"
appSettings