Sunday, May 31, 2020

SSL And Certificates For Many-to-Many Servers And Clients

I had a lot of trouble when I was trying to figure out how to configure my Windows Service applications to use https and Certificates. Part of the problem I had was the stuff I was finding when I Googled was often too simplistic.  My Windows Service applications (we call them Gateways) are both server and client, which is where my difficulty in configuration comes from. Each Gateway can send messages to other Gateways. It can also receive messages from other Gateways. We can have a network of many Gateways, all communicating with each other.

I eventually got it all sorted out and documented it so that it was easy to configure the next Gateway that got deployed. Here are the details:

We're using WCF (Windows Communication Foundation), and to illustrate what I'm writing about, here is part of the ServiceModel section of a sample config. First, just the the <services> and <client> :

<services>
  <service name="My.Application.Service" behaviorConfiguration="ServiceBehavior">
    <endpoint address="https://MyComputer/My.MessageService"
          binding="wsHttpBinding" bindingConfiguration="wsHttpServiceConfig"
          contract="My.IMessageService"
          name="ServiceEndpoint" />
  </service>
</services>
<client>
  <endpoint address="https://AddressDoesNotMatter/WeSubstituteWithAddressFromDatabase"
            binding="wsHttpBinding" bindingConfiguration="wsHttpClientConfig"
            contract="My.IMessageService"
            behaviorConfiguration="ClientBehavior"
            name="ClientEndpoint" />
</client>

In case there are different requirements for the Service and the Client, you should use two different BindingConfigurations (wsHttpServiceConfig and wsHttpClientConfig in this example). In our Service applications, there is usually no reason to have different configurations, but by specifying two of them, we have an easy way to change them.

Also note that there are two different behaviorConfigurations, ServiceBehavior and ClientBehavior. More on that later.

Without any kind of security at all, the two binding configurations look like this:

<bindings>
  <!-- NO security, NOT https -->
  <wsHttpBinding>
    <binding name="wsHttpServiceConfig" openTimeout="00:00:59" sendTimeout="00:00:59" 
             maxBufferPoolSize="4000000" maxReceivedMessageSize="4000000">
      <readerQuotas maxDepth="4000000" maxStringContentLength="4000000" maxArrayLength="4000000" 
                    maxBytesPerRead="4000000" maxNameTableCharCount="4000000" />
      <security mode="None" />
    </binding>
    <binding name="wsHttpClientConfig" openTimeout="00:00:59" sendTimeout="00:00:59" 
             maxBufferPoolSize="4000000" maxReceivedMessageSize="4000000">
      <readerQuotas maxDepth="4000000" maxStringContentLength="4000000" maxArrayLength="4000000" 
                    maxBytesPerRead="4000000" maxNameTableCharCount="4000000" />
      <security mode="None" />
    </binding>
  </wsHttpBinding>
</bindings>

Note the <security> tag specifies a mode of "None" (you won't be using https with this set to "None").

Types of Security

There are two types of security when using https://.
  • There is SSL only: the message is encrypted and the client must trust the server’s Certificate (it must be created from a trusted Certificate Authority common to both client and server).
  • There is mutual authentication: in which both parties, client and server, trust each other’s Certificate (and there is additional encryption) This is what I needed to use for my Gateways.

For Both Types

For both types of security (your URLs will obviously be https:// rather than http://), the following must be done for each application:
The Certificate must be bound to a Port. Use netsh in a Command window to do that:

netsh http add sslcert ipport=0.0.0.0:ServicePort certhash=CertThumbprint appid={48d00867-cd90-45a2-b398-0def54c7e671}
  • Where ServicePort is the port associated with the URL endpoint address in the config.
    • SSL uses port 443 by default, in which case, you don't need to specify 443 in the config's endpoint address.
    • But you can use other ports if you need to. Then you would need to specify a port in the config's endpoint address and in the above netsh command.
  • CertThumbprint is the Thumbprint of the Certificate that you're binding to this port.
  • appid can be any valid GUID and the above setting is is just an example. In a .NET application, you could grab a GUID from the project's AssemblyInfo.cs (under the project's Properties in Solution Explorer).
Next question, how do we implement security in the config? It depends on whether you're using SSL only or SSL with authentication, as follows:

SSL Only

Change the <security mode="None" /> tags, in both binding configurations shown above, to the following:

<security mode="Transport">
  <transport clientCredentialType="None" />
</security>

SSL With Authentication

For Authentication, we’ll need to make changes to the same two binding configs as shown above for “SSL Only”, but the security mode will be different, and we will be specifying that we’re using Certificates, as follows:

<security mode="TransportWithMessageCredential">
    <transport clientCredentialType="Certificate" />
    <message clientCredentialType="Certificate" />
</security>

If you remember, I promised more about the behaviorConfigurations. Here's where they come into play. First, here is an example of what the <behaviors> section might look like if you're *not* using https and Certificates:

<behaviors>
  <!-- NO security, NOT https -->
  <serviceBehaviors>
    <behavior name="ServiceBehavior">
      <serviceMetadata httpGetEnabled="true" httpGetUrl="http://MyComputer/My.MessageService" />
      <serviceDebug includeExceptionDetailInFaults="true"/>
    </behavior>
  </serviceBehaviors>
  <endpointBehaviors>
    <behavior name="ClientBehavior">
      <!-- I have nothing, you may have something here already already -->
    </behavior>
  </endpointBehaviors>
</behaviors>

To implement SSL with authentication, we need to add the to the above <behaviors> section to incorporate the actual Certificate credentials. We'll be making changes under both the <serviceBehaviors> section (adding a <serviceCredentials> section) and the <endpointBehaviors> section (adding a <clientCredentials> section).

The ServiceBehavior:

<serviceBehaviors>
  <behavior name="ServiceBehavior">
    <!-- Changed to use https -->
    <serviceMetadata httpsGetEnabled="true" httpsGetUrl="https://MyComputer/My.MessageService" />
    <serviceDebug includeExceptionDetailInFaults="true"/>
    <!-- Added <serviceCredentials> -->
    <serviceCredentials>
      <serviceCertificate findValue="ThumbprintForThisServiceCertificate"
toreLocation="LocalMachine"
                          x509FindType="FindByThumbprint"/>
      <clientCertificate>
          <authentication certificateValidationMode="ChainTrust"
                          revocationMode="NoCheck"
                          trustedStoreLocation="LocalMachine" />
      </clientCertificate>
    </serviceCredentials>
  </behavior>
</serviceBehaviors>

The ClientBehavior (which is "opposite" of the settings in the ServiceBehavior):

<endpointBehaviors>
  <behavior name="ClientBehavior">
    <!-- I have nothing, you may have something here already already -->
    <!-- Added the <clientCredentials> -->
    <clientCredentials>
      <clientCertificate findValue="ThumbprintForThisServiceCertificate"
toreLocation="LocalMachine"
                          x509FindType="FindByThumbprint"/>
      <serviceCertificate>
          <authentication certificateValidationMode="ChainTrust"
                          revocationMode="NoCheck"
                          trustedStoreLocation="LocalMachine" />
      </serviceCertificate>
    </clientCredentials>
  </behavior>
</endpointBehaviors>

That's pretty much it! Not so bad when it's all laid out for you, I suppose.

Now, you may still have some questions, such as how to find the Certificate's thumbprint or what kind of Certificate to use. You can Google for that, if the following stuff I'll show below isn't sufficient for you (although, it should be).

First, a few links for reference. You'll be using the MMC (Microsoft Management Console) snap-in tool for viewing your Certificates:


Next, a shortcut for bringing up the MMC snap-in tool. Run from the Windows Command window:

certlm.msc (for Local Computer Certificates --- you must be an Administrator on the Local Computer)
certmgr.msc (for Current User Certificates --- no Admin rights required)

Here is a screenshot with my test Certificate ... simply double-click on the Certificate you need to look at:


For the purposes in the scenario I've outlined above, we need both Client Authentication and Server Authentication. You can see that under the "Intended Purposes" column. The description in the dialog window further describes what that intended purpose means.

Now click on the Details tab:


You can see the Client and Server Authentication when the "Enhanced Key Usage" property is selected.

The  "Key Usage" property is not selected in this screenshot, but you can see enough of it to see that it says Digital Signature and Key Encipherment.

Notice the Thumbprint just below that (I have redacted most of its value). Click on it and copy it from the textbox to use in your config:


I have seen this thumbprint displayed two different ways ... one, like the screenshot shows, with spaces between each character. I have also seen it with no spaces (on Server OS's I think). If your version has the spaces you need to delete them one by one. And here's one more thing to be aware of: Not only are there spaces between each character, but there is an unprintable character at the beginning of the string also!!!   Be sure to delete that too!

No comments:

Post a Comment