Simple Root Certificate Sharing for .NET Core Microservices With Steeltoe

February 15, 2019 David Dieruf

TLS. SSL. The world of certificates isn’t glamorous, but it’s on the mind of programmers. In our modern world, users expect secure (HTTPS) connections everywhere.  And your challenge is to make data encryption in transit seamless. This way, your customers are delighted that you take their security seriously. Ideally, this tech would be embedded deep into your systems, so your developers don’t have to fiddle with it. It should “just work.”

Sound like a tall order? Lucky for you, it’s never been easier to make this security a transparent part of your stack. Consider the following example.

Let’s say you’re modernizing a .NET Framework app to Pivotal Application Service (PAS). Let’s further say the app has a dependency on an off-platform service. This backing service requires that you connect to it over HTTPS.  And to complicate things further, your app and its underlying platform, PAS, run in a different network than the backing service.

In this example, we will take the public key of the root CA that issued the backing service’s certificate. Then, we’ll load it in the certificate store of a .NET Core app running on PAS.

As we work towards the solution, we will want to keep a few things in mind. First, for our friends in security, it is necessary to build a separation of duty between the certificate being loaded and the app that consumes it. It is also important to follow good cloud-native practices, by loosely coupling the certificate to the app. And we need to change the certificate without recompiling, redeploying, or restarting the app. The good news is, Steeltoe is going to make all of these design choices easy!

Now, let’s review our configuration and prerequisites.

Configurations

Our environment version is PAS 2.3. We’ll use Visual Studio 2017 as the IDE, targeting .NET Core 2.1 running on Linux.

Prerequisites & Assumptions

The Windows root certificate store comes with certs preloaded, most of which are public certificate authorities (CA). In most organizations, these public CAs are not used. We’ll assume that PAS is not directly connected to the public Internet, and running on an organization’s intranet network). We’ll further assume that there’s an internal CA issuing certificates.

In PAS, the certificate will only be useable within the app’s container. This is a robust security posture, compared to a cert applying across the entire host. This feature can be easily extended to other apps running on PAS.

It is important to note that this solution is only applicable to .NET Core apps running on Linux as the Windows Server certificate store has a few limitations.

View the code

The Visual Studio solution is here.

Learn about the Steeltoe Framework

View samples, read the docs and see the source code at https://steeltoe.io.

Export the Root CA Key

This step is more for reference than any action. Your organization probably has a more elegant way of retrieving a certificate’s key.

  1. On the VM running the backing service, load Local Computer certificates in the Microsoft Management Console(MMC) and locate the needed certificate. Here is a quick tutorial on using the MMC.

PRO TIP: Don’t take the certificate’s name “RootCA” literally. I created it just for this test, to sign other certificates my IIS app is using.

 

NOTE: Ever wonder why self-signed certificates are not good practice? Because they can’t share an authority. If you randomly create a self-signed certificate and use it in IIS for HTTPS, the consumers of the app have no way to verify authenticity. However, if both the app and the consumer share an authority, then they can agree on the authenticity of signed certificates!

  1. Export the public key of the certificate as Base64 encoded. This is an important detail because we need the key in a format we can use as a key=value pair.

  1. The newly exported file will be text-based for convenient viewing in Notepad. You’ll notice it has the header/footer format of:

-----BEGIN CERTIFICATE-----

MII…..

-----END CERTIFICATE-----

This is our key of the Certificate Authority that signed the certificate of our IIS app. We can continue on with our app that is moving to PAS.

Create a User Provided Service

We never want to hard code any configuration values in our app. Instead, we want to build the app once and promote it through different environments without fiddling with config (or certificates). Our app should hold a reference to the certificate key, but not the actual value.

We can accomplish this with User Provided Services. This way, we bind the service to the app in the manifest. Then we use Steeltoe to parse the value and make it available within our app as a variable.

User Provided Services helpfully separate duties. For example, if your organization has an internal policy that says certificate managers must never share certificate keys (public or private) with other teams, then this is a great way to share responsibility. The certificate manager can create and load the User Provided Service and share the name of the service. The app team can then simply consume the value without ever seeing it.

PRO TIP: User Provided Services do not have encryption or abstraction capabilities. If that is a requirement, use CredHub. It’s simple to wire up Steeltoe packages and offer a definitive abstraction.

Let’s use a PowerShell script that makes a User Provided Service available in a given Org/Space. You’ll need modify the contents of the key file we exported previously.

  1. Remove the header and footer

-----BEGIN CERTIFICATE-----

-----END CERTIFICATE-----

2. Remove line breaks to make the encoded value one long string.

PRO TIP: This script assumes the user running it has already logged in and targeted the correct Org/Space. You’ll need a minimum role of SpaceManager to run this.

 
$ErrorActionPreference = "Stop"

#assume you have already logged in and targeted the correct Org/Space

$base64String = "MIID........"

$addressParamJSON = [string]::Format('{{\"base64-certificate\":\"{0}\"}}',$base64String)

#Create a user provided service with the network address of the SMB share
cf create-user-provided-service "root-certificate" -p $addressParamJSON

Load the Certificate and Call the Service

First, we need to tell PAS to bind the services to our application upon deployment. We can do this in the manifest.yml file.

---

services:
 - root-certificate

Now we have bound (i.e. connected) our application to the service. Every time PAS creates a new instance of our application, it will take care of the binding for us.

Next, we retrieve the values from the bound service. Steeltoe provides a very easy way of doing this through the Steeltoe.Extensions.Configuration.CloudFoundry package. (For a complete example, refer to the public repo.)

Let’s retrieve values from the User Provided Service:

_base64String = _serviceOptions.Services["user-provided"]
       .First(q => q.Name.Equals("root-certificate"))
       .Credentials["base64-certificate"].Value;

 

Notice we used the values established from running the above cf script. We had to tell the Steeltoe Cloud Foundry package to choose the correct service, and the correct value saved within that service. This way, the app can have many other services bound to it without conflict.

We now have everything we need to store our certificate.

The snippet below converts our Base64 string into a byte array and uses the CurrentUser.Root certificate store to hold our key.

private void LoadCertificate()
{        
    X509Certificate2 xcert;
    byte[] bytes = Convert.FromBase64String(_base64String);

    try {
        xcert = new X509Certificate2(bytes, "", X509KeyStorageFlags.PersistKeySet);
    } catch (System.Security.Cryptography.CryptographicException ex) {
        //Did the Base64 string get cut off?
        throw new Exception("An error occurred converting to X509Certificate2 object. " + ex.Message);
    }

    using (X509Store certStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser)) {
        certStore.Open(OpenFlags.ReadWrite);
        certStore.Add(xcert);
        certStore.Close();
    }

    return;
}

With it loaded, we can make calls to any service that shares that same root CA with no added code, using a straight forward WebRequest. Underneath, the .NET framework assumes the use of the root certificate store.

// GET api/values
[HttpGet]
public ActionResult<string> Get(int id)
{
    HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("https://my_service_address");
    request.Method = "GET";
    //request.ServerCertificateValidationCallback = CertificateValidationCallBack;

    string ret = "";
    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) {
        using (StreamReader sr = new StreamReader(response.GetResponseStream())) {
            ret = sr.ReadToEnd();
        }
    }

    return ret;
}

Notice the commented out Callback in the snippet. Look in the references area of this post to learn about debugging SSL policy errors.

Extending the Solution

The example above features User Provided Services to make our certificate more portable, rather than a compiled dependency of the app. The news gets better: this snippet of code and this pattern, in general, can be applied to all your apps running on PAS. Make this solution something shareable with other teams in your organization! Here are two ideas to boost adoption of this approach:

  1. CredHub. This gives you options around encryption. It also helps you meet compliance requirements when only a few people are allowed to see the certificate string.

  2. Create a Supply Buildpack to the Dotnet-core Buildpack. This feature allows you to direct a cf push to run multiple actions (bundled in a buildpack) on your app or on its container during the time of deployment. A supply buildpack could be created that looks for the service name in the app’s manifest and loads the certificate into the store. As a buildpack, you would have the option to add it to the PAS foundation and let others use it.

Sign-Up for a Free Trial of PAS, and Start Using Steeltoe

Ready to get more familiar with Steeltoe? Then head on over to Pivotal Web Services (PWS) and sign-up for a free trial. PWS is a hosted Cloud Foundry run by the experts at Pivotal. It’s a great place to get started with modern software development. And thanks to all the sample apps created by the Steeltoe team, you can easily run their starter scripts and see everything in action.

Using PCF? Then you can replay our tutorial in your own environment. When connecting to the backing service from your foundation, take note of the pre-req’s and assumptions to make sure everything is configured properly. As a first step, simply push the demo app, and then work from there.

References

The below example is a handy tool when trying to debug an HTTP 400ish error. Sometimes when your app errors on an internal WebRequest, it’s difficult to get feedback about why. Tying the WebRequest.ServerCertificateValidationCallback back to the below function can help. This example is based on this Microsoft helper.

private static bool CertificateValidationCallBack(
    object sender,
    System.Security.Cryptography.X509Certificates.X509Certificate certificate,
    System.Security.Cryptography.X509Certificates.X509Chain chain,
    System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
    // If the certificate is a valid, signed certificate, return true.
    if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None) {
        Console.WriteLine("It's ok");
        return true;
    }

    // If there are errors in the certificate chain, look at each error to determine the cause.
    if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) != 0) {
        if (chain != null && chain.ChainStatus != null) {
            foreach (System.Security.Cryptography.X509Certificates.X509ChainStatus status in chain.ChainStatus) {
                if ((certificate.Subject == certificate.Issuer) &&
                        (status.Status == System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.UntrustedRoot)) {
                    // Self-signed certificates with an untrusted root are valid.
                    Console.WriteLine("Untrusted root certificate");
                    continue;
                } else {
                    if (status.Status != System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.NoError) {

                        Console.WriteLine("Another error");
                        // If there are any other errors in the certificate chain, the certificate is invalid,
                        // so the method returns false.
                        return false;
                    }
                }
            }
        }

        // When processing reaches this line, the only errors in the certificate chain are
        // untrusted root errors for self-signed certificates. These certificates are valid
        // for default Exchange server installations, so return true.
        Console.WriteLine("Everything seems ok");
        return true;
    } else {
        Console.WriteLine("All other cases");
        // In all other cases, return false.
        return false;
    }
}

About the Author

David Dieruf

David is a part of the tech marketing team within VMware Tanzu, bringing a focus to .NET-related technologies. He has been writing C# for as long as it’s been a thing. In his spare time he enjoys breaking things, only to fix them. Prior to VMware, David had a wide-ranging background, from making things go fast to building computers to writing bad code.

Follow on Linkedin More Content by David Dieruf
Previous
Using Metadata to Label PAS App Resources with a git SHA
Using Metadata to Label PAS App Resources with a git SHA

The PAS 2.5 release added the ability to metadata to a given resource. Learn more in this blog.

Next
The runC Vulnerability Shows Why You Need a Secure by Default Platform
The runC Vulnerability Shows Why You Need a Secure by Default Platform

Why PAS customers weren’t affected by the recent runC vulnerability.