Convince WCF To Trust Self-Signed Certificates

When using Self-Signed Certificates in a WCF Service, you may receive the error

SecurityNegotiationException was unhandled: Could not establish trust relationship for the SSL/TLS secure channel with authority ‘localhost:8080’ (your service’s url may be different).

A common cause for the exception is due to the fact that the WCF runtime does not trust Self-Signed Certificates by default. It is, however, possible to override this default behavior.

First we will create a class that does the work of convincing the WCF runtime that our Self-Signed Certificate is trusted.

using System.Security.Cryptography.X509Certificates;
using System.Net;

namespace WindowsClient
{
    class PermissiveCertificatePolicy
    {
        //The name of the certificate
        string subjectName;
        static PermissiveCertificatePolicy currentPolicy;

        PermissiveCertificatePolicy(string subjectName)
        {
            this.subjectName = subjectName;

            //Set the ServerCertificateValidationCallback property to our custom validator method
            ServicePointManager.ServerCertificateValidationCallback +=
              new System.Net.Security.RemoteCertificateValidationCallback(RemoteCertValidate);
        }

        public static void Enact(string subjectName)
        {
            currentPolicy = new PermissiveCertificatePolicy(subjectName);
        }

        bool RemoteCertValidate(object sender, X509Certificate cert, X509Chain chain, System.Net.Security.SslPolicyErrors error)
        {
            //This is our custom validator method. The methods signature is defined simply to satisfy the 
            //RemoteCertificateValidationCallback delegate. 
            //Our custom validator will ignore the parameters and simply return true to convince the WCF runtime
            //that the self-signed certificate can be trusted.
            return true;
        }
    }
}

Now that we have a class that can override WCF’s rejection of a Self-Signed Certificate, we will have the client use that class prior to instantiating the service proxy.

private void InstantiateServiceProxy()
        {
            //Convince WCF that our Self-Signed Certificate is trusted
            PermissiveCertificatePolicy.Enact("CN=MySelfSignedCert");

            //Instantiate the service proxy using the basicHttpBinding
            proxy = new MyWcfService("MyEndpoint_BasicHttp");
        }
Advertisements

How to Configure a TCP Port with an SSL Certificate

Scenario: You have a WCF service that uses the basicHttpBinding binding and you would like to configure the basicHttpBinding with an SSL certificate.

This post will walk you through using IIS Manager and a Command Prompt for creating, configuring, and installing the SSL certificate.

Create a Self-Signed Certificate

Under production scenarios you will not use a Self-Signed Certificate, but to get good idea as to how to configure a TCP port with an SSL cert, a self-signed cert is sufficient.

Open IIS Manager, then open “Server Certificates”

In the Server Certificates window, click the “Create Self-Signed Certificate” link, give the cert a name, then click “OK”.

We will eventually need the Thumbprint of the certificate. So, double-click the certificate that you just created and copy the “Thumbprint” value to the clipboard.

Configure a TCP Port with the SSL Certificate

Copy the Thumbprint of the SSL cert

Open a command prompt and enter:

netsh http add sslcert ipport=0.0.0.0:8080 certhash=bb14d78228ff2c8965de040c2cc1a1fff3132d76 appid={B8CA0613-6250-4DDB-A693-74B8678C2DF6}

The certhash is the certificate’s thumbprint (Note that you must remove the spaces that may exist in the thumbprint!). The appid is an arbitrary GUID (Note: You can easily create a GUID using Visual Studio’s “Create Guid” tool. Just make sure that you choose the “Registry Format” option when creating the GUID.

If you are running a WCF service and fail to assign a certificate with the port that your service is running on, you may receive any one of the following exceptions:

An error occurred while making the HTTP request to https://localhost:8080/CustomersService. This could be due to the fact that the server certificate is not configured properly with HTTP.SYS in the HTTPS case. This could also be caused by a mismatch of the security binding between the client and the server.

An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail.

If you receive any one of the exceptions above, you may resolve the exception by creating a new self signed cert and assigning it to your service’s port.

How To Delete an SSL Certificate From a Port Number

Open a command prompt and use Netsh.

Netsh http delete sslcert ipport=0.0.0.0:8005

Where :8005 is the port number that was associated with the SSL cert.

If everything went well, the netsh command will respond with “SSL Certificate successfully installed.”

Print All SSL Certificate Bindings

If you would like to view the existing SSL Certificate Bindings (or list of ports that are assigned to an SSL Certificate), run the following netsh command:

netsh http show sslcert

The netsh command above will return a list of binding info that looks something like this:

C:\Windows\system32>netsh http show sslcert

SSL Certificate bindings:
-------------------------

IP:port : 0.0.0.0:443
Certificate Hash : 77e20073484988523a67fe6ea3e43e569e4ded37
Application ID : {4dc3e181-e14b-4a21-b022-59fc669b0914}
Certificate Store Name : MY
Verify Client Certificate Revocation : Enabled
Verify Revocation Using Cached Client Certificate Only : Disabled
Usage Check : Enabled
Revocation Freshness Time : 0
URL Retrieval Timeout : 0
Ctl Identifier : (null)
Ctl Store Name : (null)
DS Mapper Usage : Disabled
Negotiate Client Certificate : Disabled

IP:port : 0.0.0.0:56789
Certificate Hash : bff283154709a0e0ff5c3fc8d8b4567e1a5d9999
Application ID : {38afa4c0-5eba-427a-aff7-e612ed8fc4f0}
Certificate Store Name : (null)
Verify Client Certificate Revocation : Enabled
Verify Revocation Using Cached Client Certificate Only : Disabled
Usage Check : Enabled
Revocation Freshness Time : 0
URL Retrieval Timeout : 0
Ctl Identifier : (null)
Ctl Store Name : (null)
DS Mapper Usage : Disabled
Negotiate Client Certificate : Disabled

Security

In some cases you may get an exception error that states:

Keyset does not exist.

Typically, this exception is thrown when your certificate is being used by IIS (I’ve experienced this error when I was running a WCF service hosted by IIS) and IIS does not have rights to perform a signature. To give IIS rights to the cert;

  1. Open C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys
  2. Open the Security settings for the key in question – You can usually guess which key is the one you want to configure by looking at the “Date Modified” value.
  3. Add the “Network Service” user name to the list of users and grant the Network Service user “Full control”.

For more netsh command, check out Technet.

WCF Encryption

Although WCF supports many different types of bindings, there are only three types of encryption that WCF supports.

WCF Encryption Types

  1. None – Bindings that use the “None” encryption has no encryption whatsoever
  2. Message – Bindings that use the “Message” encryption will encrypt the data that goes back and forth from the client and service
  3. Transport – Bindings that use the “Transport” encryption will NOT encrypt the messages, but will encrypt the TCP packets that go back forth from the client and service.

Here are three examples of the messages that go back and forth between a client and a WCF service that show you the None, Message, and Transport encryption. The bindings that we use for these examples are: 1. basicHttpBinding 2. wsHttpBinding and 3. netTcpBinding

BasicHttpBinding Message

The BasicHttpBinding does not use encryption. Take a look at the message below that was sent from the service to the client. You will notice that the service sent a list of customer information and that the list is readable and not secure.

<MessageLogTraceRecord>
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/ICustomersService/ListCustomersResponse
</s:Header>
<s:Body>
<ListCustomersResponse xmlns="http://tempuri.org/">
<ListCustomersResult xmlns:d4p1="http://schemas.datacontract.org/2004/07/CustomersServiceLibrary" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<d4p1:Customer>
<d4p1:CompanyName>Alfreds Futterkiste
<d4p1:CustomerId>ALFKI
d4p1:Customer>
<d4p1:Customer>
<d4p1:CompanyName>Ana Trujillo Emparedados y helados
<d4p1:CustomerId>ANATR
d4p1:Customer>
<d4p1:CompanyName>Wolski  Zajazd
<d4p1:CustomerId>WOLZA
d4p1:Customer>
ListCustomersResult>
ListCustomersResponse>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>

WsHttpBinding Message

The WsHttpBinding does use encryption. Take a look at the message below that was sent from the service to the client. The first set of XML is the data that the service created prior to sending it to the client. The second set of XML is the encrypted message that the service sent over the wire to the client.

The message before encryption…

<MessageLogTraceRecord>
xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">http://tempuri.org/ICustomersService/ListCustomersResponse</a:Action>
</s:Header>
<s:Body>
xmlns="http://tempuri.org/">
xmlns:d4p1="http://schemas.datacontract.org/2004/07/CustomersServiceLibrary" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<d4p1:Customer>
<d4p1:CompanyName>Alfreds Futterkiste
<d4p1:CustomerId>ALFKI
d4p1:Customer>
<d4p1:Customer>
<d4p1:CompanyName>Ana Trujillo Emparedados y helados
<d4p1:CustomerId>ANATR
d4p1:Customer>
<d4p1:Customer>
<d4p1:CompanyName>Antonio Moreno Taquería
<d4p1:CustomerId>ANTON
d4p1:Customer>
<d4p1:Customer>
<d4p1:CompanyName>Around the Horn
<d4p1:CustomerId>AROUT
d4p1:Customer>
<d4p1:Customer>
<d4p1:CompanyName>Berglunds snabbköp
<d4p1:CustomerId>BERGS
d4p1:Customer>
</ListCustomersResult>
</ListCustomersResponse>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>

The message after encryption…
Note the cipher values were truncated for brevity

<MessageLogTraceRecord>
xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header>
<a:Action s:mustUnderstand="1" u:Id="_2">http://tempuri.org/ICustomersService/ListCustomersResponse</a:Action>
RelatesTo u:Id="_3">urn:uuid:40e0f79c-4f0a-4b9d-8ecb-c92d589c867e
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="uuid-ec955797-de1d-4fad-87e5-0ab3e8987068-11">
2010-09-14T16:18:09.733Z
2010-09-14T16:23:09.733Z
</u:Timestamp>
DerivedKeyToken u:Id="uuid-ec955797-de1d-4fad-87e5-0ab3e8987068-7" xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc">
SecurityTokenReference>
uuid:3085906f-ea5b-407d-8b71-fe606433748d" ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct">
</o:SecurityTokenReference>
<c:Offset>0</c:Offset>
<c:Length>24</c:Length>
<c:Nonce>
<!-- Removed-->
</c:Nonce>
DerivedKeyToken>
<c:DerivedKeyToken u:Id="uuid-ec955797-de1d-4fad-87e5-0ab3e8987068-8" xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc">
<o:SecurityTokenReference>
uuid:3085906f-ea5b-407d-8b71-fe606433748d" ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct">
</o:SecurityTokenReference>
<c:Nonce>
<!-- Removed-->
</c:Nonce>
</c:DerivedKeyToken>
ReferenceList xmlns:e="http://www.w3.org/2001/04/xmlenc#">
DataReference URI="#_1">
<e:DataReference URI="#_4"></e:DataReference>
</e:ReferenceList>
EncryptedData Id="_4" Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns:e="http://www.w3.org/2001/04/xmlenc#">
EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc">
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<o:SecurityTokenReference>
ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/dk" URI="#uuid-ec955797-de1d-4fad-87e5-0ab3e8987068-8">
</o:SecurityTokenReference>
KeyInfo>
<e:CipherData>
V79JZafU+fAhkafqBOkZ0rdMwtqEqh
</e:CipherData>
EncryptedData>
</o:Security>
</s:Header>
<s:Body u:Id="_0">
xmlns:e="http://www.w3.org/2001/04/xmlenc#">
<e:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"></e:EncryptionMethod>
xmlns="http://www.w3.org/2000/09/xmldsig#">
xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/dk" URI="#uuid-ec955797-de1d-4fad-87e5-0ab3e8987068-8">
</o:SecurityTokenReference>
</KeyInfo>
<e:CipherData>
Mg5F29dMPn5VEHva/H85KAB2K97pLXaqyOXdvFey2NcLTeQgNdMAS+JyOy4O52Oi3ECVVof3iM9q434E4gs=
</e:CipherData>
</e:EncryptedData>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>

NetTcpBinding Message

The NetTcpBinding uses encryption, but not on the message. The encryption occurs on the TCP packets when sent over the wire – known as Transport Security.

Since the messages aren’t encrypted, the message will look the same (in the .svclog file) as the BasicHttpBinding messages – So, there is no need to show you what a NetTcpBinding message looks like. To view the encrypted packet, we would need to use a tool such as WireShark – which I don’t have the time to do right now :)