A very common need for e-commerce platform developers is to use multiple SSL certificates for a single IIS Web Site.
It happens mainly when the e-commerce platform is multi-tenant, in other words, a single IIS aplication/website contains different virtual stores for different clients/companies.
So, to illustrate our problem, lets think about this scenario:
We are an e-commerce platform provider with 4 clients, each one with their respective plan and urls:
-Client 1 (Premium): www.store1.com
-Client 2 (Silver): www.store2.com
-Client 3 (Silver): www.store3.com
-Client 4 (Free): www.store4.com
The Premium plan offers a dedicated environment to the client´s store, with support for our clients own domains for both HTTP and HTTPS.
The Silver plan offers a shared environment to the client´s store, with support for our clients own domains for both HTTP and HTTPS.
The Free Plan offers a shared environment to the client´s store, with support for our clients own domains only for HTTP. The HTTPS is one of our sub-domains (ex: https://store4.ecommerce.com).
Our application is already prepared to load the corrrect store according to the requested domain. If we receive an request for the domain www.store1.com we will load the Store 1 database and show the Store 1 to the user.
How to set it up on Windows Azure?
One way is to setup an enviroment similar as we would do on-premises, creating virtual machines, each one with their respective IP addresses and DNSs, using CNAMEs, wildcard SSL certificates and so on (IaaS scenario).
But then we would miss one of the greatest powers of Windows Azure, the platform as a service of the Cloud Services (PaaS scenario).
Using Cloud Services with Multiple SSL certificates
To setup our scenario using Cloud Services and obtain all the benefits of automation and scalability, we will do the following:
For Premium clients, we will treat each client store as an isolated application, each one having its own separated Cloud Service. This way we deliver the “dedicated environment” that we offer in the Premium plan. The process do use SSL on it is pretty straighfoward, we just need to create an HTTP endpoint with the respective SSL certificate. To use custom domains, all you need is to configure a CNAME record on your DNS server.
For the Free clients, the process is even simpler. We will have only one application/Cloud Service that will handle all stores in the Free plan. All we need is to create a single HTTPS endpoint with a SSL suubdomain wildcard certificate (in our case *.ecommerce.com). For each store, as we offer custom HTTP domains, we will need to configure a CNAME record in the DNS server. Our application, however, will have the intelligence to redirect the user to the respective secure subdomain everytime we need to use HTTPS (for exemple, in the checkout pages).
Therefore, each store would have to different urls, such as: http://www.store4.com for HTTP and https://store4.ecommerce.com for HTTPS.
And finally, for Silver clients, we have an special situation. We need to have all stores in the same shared application/CloudService and yet guarantee that every store has its own HTTP and HTTPS domain. To make it happpen, each store will have a CNAME record on the DNS server like we did before.
www.store2.com CNAME ecommerce.cloudapp.net
www.store3.com CNAME ecommerce.cloudapp.net
And now, for each store to have its own secure domain, we need to have a SSL certificate for each one.
The problem appears when we need to create the HTTPS endpoints, because it’s not possible to add more than one certificate for the same endpoint.
One solution would be to use agregatted certificates, where a single certificate file contains dozens of certificates for different domains, but this option might be more expensive and you will have to do a new deploy everytime you need to add a new domain in the certificate file.
Another solution, would be to create HTTPS endpoints in different ports, for instance, 443, 444, 445, but this might appear weird to the end user and might not work because of firewalls blocking non-standard ports, and there is also a limit of 25 external endpoints.
And finally, there is a way of creating a single HTTPS endpoint in port 443 and using a new IIS 8.0 feature that allow us to create multiple HTTPS bindings on the same port for different domains. This feature is based on a SSL extension called Server Name Indication (SNI), in which the great majority of webbrowsers supports (in practice, only browsers running on Windows XP don’t). On the server side, SNI is only available on IIS 8 of Windows 8 or Windows Server 2012.
To make it work, what we need to do is to install the certificates and create the bindings for the IIS website, flagging the SNI option. You can read a detailed explanation of this IIS feature and manual how-to of this configuration procedure here.
But how to make it happen in a Cloud Service, which is stateless and that at every deploy, or when needed, the Windows Azure will “erase” the IIS configuration?
We need to automate the instalation of the certificates and and the creation of the IIS bindings in the startup process of our Web Role running in the Cloud Service.
Installing a certificate
We can install a certificate with only 5 lines of C# code
var newCert = new X509Certificate2(“certificate.pfx”, “certificatePassword”, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
var readWriteMyStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
Creating the HTTPS binding with the SNI on
And with 5 more lines of code we create the binding (add the Microsoft.Web.Administration package via Nuget).
Attention: SNI is only available on IIS 8 of Windows 8 or Windows Server 2012.
var serverManager = new ServerManager();
var site = serverManager.Sites;
var binding = site.Bindings.Add(“:443:www.store2.com”, newCert.GetHash(), “My”);
binding.SetAttributeValue(“sslFlags”, 1); //enables the SNI
Remember that to make this code work, the role must run under elavated mode:
Write the following option in your ServiceDefinition.csdef, inside <WebRole>
<Runtime executionContext=”elevated” />
The Complete Solution
That would be enough to add the code in your Role.OnStart and setup your custom secure domains everytime the role starts.
However, to make sure the role doesn’t need to be restared everytime que we have a new certificate, I have create a simple “job” that checks from time to time if we it needs to install certificates and create bindings.
The solution is flexible enough to be incorporated in your code and you can store your certificates in a blob container or in a SQL database, for instance. All you need is to create your own provider that implements the ICertificateBindingStorage interface.
In the code sample, I have a container called “certificates” that contains the certificates as blobs and their metadata contains additional information to install the certificate and create the binding.
An example of a certificate in the blob storage:
This way, everytime I have a new store (in the Silver plan), I only need to create a CNAME record and upload the respective certificate with the metadata to the blob container. A minute later the store will be ready to be used with the custom SSL domain.
You can download the complete source-code here: http://sdrv.ms/Zasjbp
And if you wish to know more about certificates and SNI, check this post: http://blogs.msdn.com/b/kaushal/archive/2012/09/04/server-name-indication-sni-in-iis-8-windows-server-2012.aspx
Questions? Suggestions? -> Please leave your comments